Skip to content

Instantly share code, notes, and snippets.

@monsterxcn
Last active June 8, 2023 06:30
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save monsterxcn/218e453d0c1018e33ecfef913dfa82ef to your computer and use it in GitHub Desktop.
Save monsterxcn/218e453d0c1018e33ecfef913dfa82ef to your computer and use it in GitHub Desktop.
NoneBot2 原神充值二维码插件,将 nonebot_plugin_gspay.py 补全后放入插件文件夹下重启 Bot 即可使用
"""
需要绑定个人 Cookie 的充值插件,不再即插即用,使用前必须自行修改 Cookie 处理等代码,溜了
哦对了,由于我的机机在境外,访问米哈游接口需要梯子,代码里 httpx.AsyncClient 都设置了代理
境内机机使用 / 没有代理的话,务必记得删除文件中的 proxies={"all://": "socks5://127.0.0.1:1080"}
参考 https://github.com/KimigaiiWuyi/GenshinUID/pull/463
"""
import hmac
import json
from time import time
from io import BytesIO
from pathlib import Path
from hashlib import md5, sha256
from uuid import NAMESPACE_URL, uuid3
from datetime import datetime, timezone, timedelta
from typing import Any, Dict, List, Tuple, Union, Literal
from qrcode import QRCode
from httpx import AsyncClient
from PIL import Image, ImageDraw, ImageFont
from nonebot import require
from nonebot.log import logger
from nonebot.typing import T_State
from nonebot.plugin import on_command
from nonebot.adapters.onebot.v11.event import MessageEvent
from nonebot.adapters.onebot.v11 import Bot, MessageSegment
from nonebot.adapters.onebot.v11.exception import ActionFailed
require("nonebot_plugin_apscheduler")
from nonebot_plugin_apscheduler import scheduler # noqa: E402
# require("plugins.genshin_info")
from plugins.genshin_info.mihoyo_api import getUid, retcodeChecker # noqa: E402
RES_PATH = Path("data/niao/pay")
FONT_PATH = Path("data/niao/font/zh-cn.ttf")
TZ = timezone(timedelta(hours=8))
HK4E_SDK = "https://hk4e-sdk.mihoyo.com"
MYS_VER = "2.44.1"
METHOD: Literal["weixin", "alipay"] = "weixin" # 默认付款方式
THEME_COLOR = {"weixin": "#29ac66", "alipay": "#1678ff"}
_GOODS = [
# await fetchGoods()
# ProductIdConfigData.json
{
"config_id": 1,
"tier_id": "Tier_1",
"goods_id": "ys_chn_primogem1ststall_tier1",
"itemNameTextMapHash": 592163336, # "60创世结晶"
"primNameTextMapHash": 3399738308, # "60枚创世结晶"
"icon": "UI_Mall_Purchase_Primogem1ststall",
"goods_name": "创世结晶",
"goods_unit": "60",
"goods_icon": "https://uploadstatic.mihoyo.com/payment-center/2022/09/07/0f362595da2e37a7a8fde1bb120656d2_594155779359709441.png",
"price": "600",
"aliases": ["创世结晶×60", "创世结晶x60", "结晶×60", "结晶x60", "创世结晶60", "结晶60"],
},
{
"config_id": 2,
"tier_id": "Tier_5",
"goods_id": "ys_chn_primogem2ndstall_tier5",
"itemNameTextMapHash": 1264426216, # "300创世结晶"
"primNameTextMapHash": 2700754972, # "300枚创世结晶"
"icon": "UI_Mall_Purchase_Primogem2ndstall",
"goods_name": "创世结晶",
"goods_unit": "300",
"goods_icon": "https://uploadstatic.mihoyo.com/payment-center/2022/09/07/830e247bb0cfffa5c74a04e79c0040f5_1814106121630644354.png",
"price": "3000",
"aliases": ["创世结晶×300", "创世结晶x300", "结晶×300", "结晶x300", "创世结晶300", "结晶300"],
},
{
"config_id": 3,
"tier_id": "Tier_15",
"goods_id": "ys_chn_primogem3rdstall_tier15",
"itemNameTextMapHash": 4078387144, # "980创世结晶"
"primNameTextMapHash": 320967628, # "980枚创世结晶"
"icon": "UI_Mall_Purchase_Primogem3rdstall",
"goods_name": "创世结晶",
"goods_unit": "980",
"goods_icon": "https://uploadstatic.mihoyo.com/payment-center/2022/09/07/dfefc92ce56e3b5615ef28d6b1119b8b_5835214000384994274.png",
"price": "9800",
"aliases": [
"创世结晶×980",
"创世结晶x980",
"结晶×980",
"结晶x980",
"创世结晶980",
"结晶980",
"98",
],
},
{
"config_id": 4,
"tier_id": "Tier_30",
"goods_id": "ys_chn_primogem4thstall_tier30",
"itemNameTextMapHash": 1175452288, # "1980创世结晶"
"primNameTextMapHash": 1588912924, # "1980枚创世结晶"
"icon": "UI_Mall_Purchase_Primogem4thstall",
"goods_name": "创世结晶",
"goods_unit": "1980",
"goods_icon": "https://uploadstatic.mihoyo.com/payment-center/2022/09/07/e918ecfedcb13113eb627fd944199272_3460055016813877022.png",
"price": "19800",
"aliases": [
"创世结晶×1980",
"创世结晶x1980",
"结晶×1980",
"结晶x1980",
"创世结晶1980",
"结晶1980",
"198",
],
},
{
"config_id": 5,
"tier_id": "Tier_50",
"goods_id": "ys_chn_primogem5thstall_tier50",
"itemNameTextMapHash": 982800008, # "3280创世结晶"
"primNameTextMapHash": 1861426364, # "3280枚创世结晶"
"icon": "UI_Mall_Purchase_Primogem5thstall",
"goods_name": "创世结晶",
"goods_unit": "3280",
"goods_icon": "https://uploadstatic.mihoyo.com/payment-center/2022/09/07/70e703a64e8786390ab8b7cdc35dbeeb_6358952826545947027.png",
"price": "32800",
"aliases": [
"创世结晶×3280",
"创世结晶x3280",
"结晶×3280",
"结晶x3280",
"创世结晶3280",
"结晶3280",
"328",
],
},
{
"config_id": 6,
"tier_id": "Tier_60",
"goods_id": "ys_chn_primogem6thstall_tier60",
"itemNameTextMapHash": 2819973760, # "6480创世结晶"
"primNameTextMapHash": 2491404828, # "6480枚创世结晶"
"icon": "UI_Mall_Purchase_Primogem6thstall",
"goods_name": "创世结晶",
"goods_unit": "6480",
"goods_icon": "https://uploadstatic.mihoyo.com/payment-center/2022/09/07/24fa6b6190ce5da6928e431a832d85c3_5932007685099741224.png",
"price": "64800",
"aliases": [
"创世结晶×6480",
"创世结晶x6480",
"结晶×6480",
"结晶x6480",
"创世结晶6480",
"结晶6480",
"648",
],
},
# ProductCardDetailConfigData.json
{
"config_id": 101,
"tier_id": "Tier_5",
"goods_id": "ys_chn_blessofmoon_tier5",
"icon": "UI_Mall_Purchase_Blessofmoon",
"itemNameTextMapHash": 3486638812, # "空月祝福"
"replaceMcoinNum": 330,
"goods_name": "空月祝福",
"goods_unit": "0",
"goods_icon": "https://uploadstatic.mihoyo.com/payment-center/2020/06/08/2da77803b9b2ffc2a2b763a59e9c125f_5219067706372136934.png",
"price": "3000",
"aliases": ["空月祝福", "空月", "祝福", "月卡", "小月卡"],
},
# ProductIdConfigData.json
# ProductPlayDetailConfigData.json
# PriceTierConfigData.json
{
"config_id": 201,
"tier_id": "Tier_10",
"goods_id": "ys_chn_bp_normal_tier10",
"itemNameTextMapHash": 1087749475, # "珍珠纪行"
"replaceMcoinNum": 750,
"goods_name": "珍珠纪行",
"goods_unit": "0",
"goods_icon": "https://cdn.monsterx.cn/bot/Battle_Pass.png",
"price": "6800",
"aliases": ["珍珠纪行", "纪行", "大月卡", "小纪行", "68"],
},
{
"config_id": 202,
"tier_id": "Tier_20",
"goods_id": "ys_chn_bp_extra_tier20",
"itemNameTextMapHash": 374976595, # "珍珠之歌"
"replaceMcoinNum": 1410,
"goods_name": "珍珠之歌",
"goods_unit": "0",
"goods_icon": "https://cdn.monsterx.cn/bot/Battle_Pass.png",
"price": "12800",
"aliases": ["珍珠之歌", "之歌", "大大月卡", "大纪行", "128"],
},
{
"config_id": 203,
"tier_id": "Tier_12",
"goods_id": "ys_chn_bp_upgrade_tier12",
"itemNameTextMapHash": 3178887299, # "纪行成歌"
"replaceMcoinNum": 860,
"goods_name": "纪行成歌",
"goods_unit": "0",
"goods_icon": "https://cdn.monsterx.cn/bot/Battle_Pass.png",
"price": "7800",
"aliases": ["纪行成歌", "成歌", "月卡升级", "纪行升级", "78"],
},
]
def font(size: int) -> ImageFont.FreeTypeFont:
"""Pillow 绘制字体设置"""
return ImageFont.truetype(str(FONT_PATH), size=size)
async def fetchGoods() -> Union[List, str]:
async with AsyncClient(
base_url=HK4E_SDK, proxies={"all://": "socks5://127.0.0.1:1080"}
) as client:
res = await client.post(
"/hk4e_cn/mdk/shopwindow/shopwindow/fetchGoods",
json={
"released_flag": True,
"game": "hk4e_cn",
"region": "cn_gf01",
"uid": "1",
"account": "1",
},
)
resJson = res.json()
check = retcodeChecker(resJson)
return check if isinstance(check, str) else resJson["data"]["goods_list"]
def getOrderSign(data: Dict) -> str:
data = dict(sorted(data.items(), key=lambda x: x[0]))
value = "".join([str(i) for i in data.values()])
return (
hmac.new(
"6bdc3982c25f3f3c38668a32d287d16b".encode("utf-8"),
value.encode("utf-8"),
digestmod=sha256,
)
.digest()
.hex()
)
async def createOrder(
goodData: Dict,
uid: str,
accountId: str,
cookie: str,
method: Literal["weixin", "alipay"],
) -> Union[Dict, str]:
device = f"NB-{md5(uid.encode()).hexdigest()[:5]}"
headers = {
"User-Agent": (
f"Mozilla/5.0 (Linux; Android 12; {device}) AppleWebKit/537.36 (KHTML, "
f"like Gecko) Chrome/99.0.4844.73 Mobile Safari/537.36 miHoYoBBS/{MYS_VER}"
),
"Referer": "https://webstatic.mihoyo.com/",
"Origin": "https://webstatic.mihoyo.com",
"Cookie": cookie,
"x-rpc-app_version": MYS_VER,
"x-rpc-device_id": str(uuid3(NAMESPACE_URL, uid)),
"x-rpc-client_type": "4",
}
order = {
"account": accountId,
"region": "cn_gf01",
"uid": uid,
"delivery_url": "",
"device": str(uuid3(NAMESPACE_URL, uid)),
"channel_id": 1,
"client_ip": "",
"client_type": 4,
"game": "hk4e_cn",
"amount": goodData["price"],
"goods_num": 1,
"goods_id": goodData["goods_id"],
"goods_title": f'{goodData["goods_name"]}×{str(goodData["goods_unit"])}'
if int(goodData["goods_unit"]) > 0
else goodData["goods_name"],
"price_tier": goodData["tier_id"],
"currency": "CNY",
"pay_plat": method,
}
async with AsyncClient(
base_url=HK4E_SDK, proxies={"all://": "socks5://127.0.0.1:1080"}
) as client:
res = await client.post(
"/hk4e_cn/mdk/atropos/api/createOrder",
headers=headers,
json={"order": order, "sign": getOrderSign(order)},
)
resJson = res.json()
check = retcodeChecker(resJson)
return check if isinstance(check, str) else resJson["data"]
async def getImages(
itemId: int, orderData: Dict[str, Any], method: str
) -> Tuple[Image.Image, Image.Image]:
"""商品图片、二维码图片、账单信息"""
async with AsyncClient(proxies={"all://": "socks5://127.0.0.1:1080"}) as client:
itemPic = RES_PATH / f"{itemId}.png"
if itemPic.exists():
itemImg = Image.open(itemPic)
else:
_img = await client.get(_GOODS[itemId]["goods_icon"], timeout=10.0)
itemImg = Image.open(BytesIO(_img.content)).convert("RGBA")
itemImg.save(itemPic, quality=100)
_qrcode = QRCode()
_qrcode.add_data(orderData["encode_order"])
_qrcode.make()
qrcodeImg = _qrcode.make_image(fill=THEME_COLOR[method], back_color="white")
return itemImg, qrcodeImg
async def draw(
uid: str,
icon: Image.Image,
qrcode: Image.Image,
good: Dict[str, Any],
order: Dict[str, str],
) -> bytes:
"""充值图片绘制"""
method = "weixin" if order["encode_order"].startswith("weixin") else "alipay"
themeColor = THEME_COLOR[method]
warning = 20 if good.get("replaceMcoinNum") else 0
res = Image.new("RGBA", (450, 520), themeColor)
drawer = ImageDraw.Draw(res)
resample = getattr(Image, "Resampling", Image).LANCZOS
# 头部矩形背景
drawer.rectangle((75, 50 - warning, 375 - 1, 150 - warning), fill="#E5F9FF", width=0)
# 商品图片
item = icon.resize((90, 90), resample=resample)
res.paste(item, (80, 55 - warning), item)
# 商品名称
drawer.text(
(
int(175 + (195 - font(25).getlength(good["aliases"][0])) / 2),
int(70 - warning + (30 - font(25).getbbox(good["aliases"][0])[-1]) / 2),
),
good["aliases"][0],
fill="#000000",
font=font(25),
)
# 商品充值 UID
drawer.text(
(
int(175 + (195 - font(15).getlength(f"充值到 UID{uid}")) / 2),
int(110 - warning + (20 - font(15).getbbox(f"充值到 UID{uid}")[-1]) / 2),
),
f"充值到 UID{uid}",
fill="#333333",
font=font(15),
)
# 二维码图片
qrcode = qrcode.resize((300, 300), resample=resample)
res.paste(qrcode, (75, 150 + warning), qrcode)
# 月卡及纪行相关商品警告
if warning:
# 首部矩形背景
# drawer.rectangle((75, 145, 375 - 1, 155), fill="#ffffff", width=0)
drawer.rectangle((75, 130, 375 - 1, 170), fill="#eeeeee", width=0)
# 转换警告文字
warning_text = f"特殊情况将直接返还 {good['replaceMcoinNum']} 创世结晶"
drawer.text(
(
int((450 - font(15).getlength(warning_text)) / 2),
int(130 + (40 - font(15).getbbox(warning_text)[-1]) / 2),
),
warning_text,
fill="#ff5652",
font=font(15),
)
# 尾部矩形背景
drawer.rectangle(
(75, 450 + warning, 375 - 1, 460 + warning), fill="#ffffff", width=0
)
# 图片生成时间
timestamp = datetime.fromtimestamp(int(order["create_time"]), TZ).strftime(
"%Y-%m-%d %H:%M:%S"
)
drawer.text(
(
int((450 - font(15).getlength(timestamp)) / 2),
int(450 + warning - font(15).getbbox(timestamp)[-1] - 3),
),
timestamp,
fill=themeColor,
font=font(15),
)
# 账单信息
ticket = f"{'微信支付商户单号' if method == 'weixin' else '支付宝商家订单号'} {order['order_no']}"
drawer.text(
(int((450 - font(15).getlength(ticket)) / 2), 470 + warning),
ticket,
fill="#000000",
font=font(15),
)
buf = BytesIO()
res.convert("RGB").save(buf, format="PNG")
return buf.getvalue()
async def withdraw(bot: Bot, message_id: int) -> None:
await bot.delete_msg(message_id=message_id)
payMatcher = on_command("原神充值", aliases={"充值", "pay"}, priority=11)
@payMatcher.handle()
async def pay_handle(bot: Bot, event: MessageEvent, state: T_State):
arg = str(state["_prefix"]["command_arg"])
qq: str = (
event.message["at"][0].data["qq"]
if event.message.get("at")
else event.get_user_id()
)
itemId, uid, method = 0, None, METHOD
for s in arg.split():
# 支持指定支付方式
if "微信" in s:
method = "weixin"
continue
elif "支付宝" in s:
method = "alipay"
continue
# 仅支持国内官服 UID
if len(s) == 9 and s.isdigit() and s[0] in ["1", "2"]:
uid = s
continue
# 输入物品别名识别
for gIdx, gData in enumerate(_GOODS):
if (s == str(gIdx)) or (s in gData["aliases"]):
itemId = gIdx
break
uid = uid or (await getUid(qq)) # 由 QQ 号查询 UID
if not uid:
await payMatcher.finish("UID 捏?", at_sender=True)
# 处理 Cookie
configs = json.loads((RES_PATH.parent / "users.json").read_text(encoding="UTF-8"))
if not configs.get(uid):
await payMatcher.finish(f"缺少 {uid} 的 Cookie 无法创建订单!")
cookie = configs[uid]["cookie"]
accountId = cookie.split("account_id=", 1)[1].split(";", 1)[0]
if not accountId:
await payMatcher.finish(f"{uid} 的 Cookie 缺少 account_id!")
# 从网页接口获取商品列表(不包含纪行商品)
# goodsData = await fetchGoods()
# if isinstance(goodsData, str):
# await payMatcher.finish(goodsData)
# if itemId >= len(goodsData):
# await payMatcher.finish(f"暂不支持充值 {_GOODS[itemId]['aliases'][0]}!")
# goodData = goodsData[itemId]
goodData = _GOODS[itemId]
order = await createOrder(goodData, uid, accountId, cookie, method)
# (RES_PATH / f"order.{uid}-{itemId}.json").write_text(
# json.dumps(order, ensure_ascii=False, indent=4), encoding="UTF-8"
# )
if isinstance(order, str):
await payMatcher.finish(order)
itemImg, qrcodeImg = await getImages(itemId, order, method)
try:
msg = await payMatcher.send(
MessageSegment.image(await draw(uid, itemImg, qrcodeImg, goodData, order))
)
# expiry = (3 if method == "weixin" else 1) * 60
scheduler.add_job(
withdraw,
"date",
args=[bot, dict(msg)["message_id"]],
run_date=datetime.fromtimestamp(time() + 100, TZ), # 100 秒后撤回
)
except ActionFailed:
logger.error("充值二维码发送失败!")
except Exception as e:
logger.opt(exception=e).error("充值二维码撤回任务异常!")
"""
NoneBot2 原神充值二维码插件,触发命令:原神充值/充值/pay
阉割了指令的 @QQ 功能,其余功能完整
如需启用自动撤回功能,请先安装 nonebot_plugin_apscheduler 插件并定义第 45 行撤回等待秒数
第 43 行需要手动填写 API 地址用于第 138 行拼接,不要问我去哪找 API
"""
from base64 import b64decode
from datetime import datetime
from io import BytesIO
from pathlib import Path
from re import findall
from time import localtime, strftime, time
from typing import Dict, Literal, Optional, Tuple
from httpx import AsyncClient, stream
from PIL import Image, ImageDraw, ImageFont
from nonebot import require
from nonebot.adapters.onebot.v11 import Bot, MessageSegment
from nonebot.adapters.onebot.v11.event import MessageEvent
from nonebot.adapters.onebot.v11.exception import ActionFailed
from nonebot.log import logger
from nonebot.plugin import on_command
from nonebot.typing import T_State
# require("plugins.genshin_info")
# from plugins.genshin_info import getUid
RES_PATH = Path("data/pay")
RES_PATH.mkdir(parents=True, exist_ok=True)
FONT_PATH = Path("data/font/zh-cn.ttf")
FONT_PATH.parent.mkdir(parents=True, exist_ok=True)
if not FONT_PATH.exists():
with stream("GET", "https://cdn.monsterx.cn/bot/zh-cn.ttf", verify=False) as r:
with open(FONT_PATH, "wb") as f:
for chunk in r.iter_bytes():
f.write(chunk)
API = "http://这里填写 API 地址"
METHOD: Literal["微信", "支付宝"] = "支付宝" # 默认付款方式
WITHDRAW: int = 0 # 自动撤回等待秒数,为 0 即不自动撤回,非管理员无法撤回 120 秒以上的消息
GOODS = {
"0": {
"title": "创世结晶×60",
"aliases": ["创世结晶x60", "结晶×60", "结晶x60", "创世结晶60", "结晶60"],
"cost": 6,
"img": "https://uploadstatic.mihoyo.com/payment-center/2022/09/07/0f362595da2e37a7a8fde1bb120656d2_594155779359709441.png",
},
"1": {
"title": "创世结晶×300",
"aliases": ["创世结晶x300", "结晶×300", "结晶x300", "创世结晶300", "结晶300"],
"cost": 30,
"img": "https://uploadstatic.mihoyo.com/payment-center/2022/09/07/830e247bb0cfffa5c74a04e79c0040f5_1814106121630644354.png",
},
"2": {
"title": "创世结晶×980",
"aliases": ["创世结晶x980", "结晶×980", "结晶x980", "创世结晶980", "结晶980", "98"],
"cost": 98,
"img": "https://uploadstatic.mihoyo.com/payment-center/2022/09/07/dfefc92ce56e3b5615ef28d6b1119b8b_5835214000384994274.png",
},
"3": {
"title": "创世结晶×1980",
"aliases": ["创世结晶x1980", "结晶×1980", "结晶x1980", "创世结晶1980", "结晶1980", "198"],
"cost": 198,
"img": "https://uploadstatic.mihoyo.com/payment-center/2022/09/07/e918ecfedcb13113eb627fd944199272_3460055016813877022.png",
},
"4": {
"title": "创世结晶×3280",
"aliases": ["创世结晶x3280", "结晶×3280", "结晶x3280", "创世结晶3280", "结晶3280", "328"],
"cost": 328,
"img": "https://uploadstatic.mihoyo.com/payment-center/2022/09/07/70e703a64e8786390ab8b7cdc35dbeeb_6358952826545947027.png",
},
"5": {
"title": "创世结晶×6480",
"aliases": ["创世结晶x6480", "结晶×6480", "结晶x6480", "创世结晶6480", "结晶6480", "648"],
"cost": 648,
"img": "https://uploadstatic.mihoyo.com/payment-center/2022/09/07/24fa6b6190ce5da6928e431a832d85c3_5932007685099741224.png",
},
"6": {
"title": "空月祝福",
"aliases": ["空月", "祝福", "月卡", "小月卡"],
"cost": 30,
"error": 330,
"img": "https://uploadstatic.mihoyo.com/payment-center/2020/06/08/2da77803b9b2ffc2a2b763a59e9c125f_5219067706372136934.png",
},
"7": {
"title": "珍珠纪行",
"aliases": ["纪行", "大月卡", "小纪行", "68"],
"cost": 68,
"error": 750,
"img": "https://cdn.monsterx.cn/bot/Battle_Pass.png",
},
"8": {
"title": "珍珠之歌",
"aliases": ["之歌", "大大月卡", "大纪行", "128"],
"cost": 128,
"error": 1410,
"img": "https://cdn.monsterx.cn/bot/Battle_Pass.png",
},
"9": {
"title": "纪行成歌",
"aliases": ["成歌", "月卡升级", "纪行升级", "78"],
"cost": 78,
"error": 860,
"img": "https://cdn.monsterx.cn/bot/Battle_Pass.png",
},
}
def font(size: int) -> ImageFont.FreeTypeFont:
"""Pillow 绘制字体设置"""
return ImageFont.truetype(str(FONT_PATH), size=size)
async def withdraw(bot: Bot, message_id: int) -> None:
"""撤回消息任务"""
await bot.delete_msg(message_id=message_id)
async def getImages(
itemId: str, uid: str, method: str
) -> Tuple[Image.Image, Optional[Image.Image], Dict[str, str]]:
"""商品图片、二维码图片、账单信息"""
async with AsyncClient() as client:
itemPic = RES_PATH / f"{itemId}.png"
if itemPic.exists():
itemImg = Image.open(itemPic)
else:
_img = await client.get(GOODS[itemId]["img"], timeout=10.0)
itemImg = Image.open(BytesIO(_img.content)).convert("RGBA")
itemImg.save(itemPic, quality=100)
api = f"{API}/{itemId}/{uid}/{method}"
html = (await client.get(api, timeout=10.0)).text
qrcode = findall(r'data:;base64,(.*)"\salt', html)
if not qrcode:
return itemImg, None, {}
orderId = findall(r"order:(\d+)", html)
orderUrl = findall(r"<br>url:(.*)</p>", html)
info = {
"uid": uid,
"id": orderId[0].strip(),
"url": orderUrl[0].strip(),
"time": time(),
}
byteData = b64decode(qrcode[0])
qrcodeImg = Image.open(BytesIO(byteData))
return itemImg, qrcodeImg, info
async def draw(
itemId: str, item: Image.Image, qrcode: Image.Image, info: Dict[str, str]
) -> bytes:
"""充值图片绘制"""
themeColor = "#29ac66" if info["url"].startswith("weixin") else "#1678ff"
warning = 20 if GOODS[itemId].get("error") else 0
res = Image.new("RGBA", (450, 520), themeColor)
drawer = ImageDraw.Draw(res)
resample = getattr(Image, "Resampling", Image).LANCZOS
# 头部矩形背景
drawer.rectangle((75, 50 - warning, 375 - 1, 150 - warning), fill="#E5F9FF", width=0)
# 商品图片
item = item.resize((90, 90), resample=resample)
res.paste(item, (80, 55 - warning), item)
# 商品名称
drawer.text(
(
int(175 + (195 - font(25).getlength(GOODS[itemId]["title"])) / 2),
int(70 - warning + (30 - font(25).getbbox(GOODS[itemId]["title"])[-1]) / 2),
),
GOODS[itemId]["title"],
fill="#000000",
font=font(25),
)
# 商品充值 UID
drawer.text(
(
int(175 + (195 - font(15).getlength(f"充值到 UID{info['uid']}")) / 2),
int(110 - warning + (20 - font(15).getbbox(f"充值到 UID{info['uid']}")[-1]) / 2),
),
f"充值到 UID{info['uid']}",
fill="#333333",
font=font(15),
)
# 二维码图片
qrcode = qrcode.resize((300, 300), resample=resample)
res.paste(qrcode, (75, 150 + warning), qrcode)
# 月卡及纪行相关商品警告
if warning:
# 首部矩形背景
drawer.rectangle((75, 130, 375 - 1, 170), fill="#eeeeee", width=0)
# 转换警告文字
warning_text = f"特殊情况将直接返还 {GOODS[itemId]['error']} 创世结晶"
drawer.text(
(
int((450 - font(15).getlength(warning_text)) / 2),
int(130 + (40 - font(15).getbbox(warning_text)[-1]) / 2),
),
warning_text,
fill="#ff5652",
font=font(15),
)
# 尾部矩形背景
drawer.rectangle((75, 450 + warning, 375 - 1, 460 + warning), fill="#ffffff", width=0)
# 图片生成时间
timestamp = strftime("%Y-%m-%d %H:%M:%S", localtime(int(info["time"])))
drawer.text(
(
int((450 - font(15).getlength(timestamp)) / 2),
int(450 + warning - font(15).getbbox(timestamp)[-1] - 3),
),
timestamp,
fill=themeColor,
font=font(15),
)
# 账单信息
ticket = (
f"{'微信支付商户单号' if info['url'].startswith('weixin') else '支付宝商家订单号'} {info['id']}"
)
drawer.text(
(int((450 - font(15).getlength(ticket)) / 2), 470 + warning),
ticket,
fill="#000000",
font=font(15),
)
buf = BytesIO()
res.convert("RGB").save(buf, format="PNG")
return buf.getvalue()
payMatcher = on_command("原神充值", aliases={"充值", "pay"}, priority=11)
@payMatcher.handle()
async def pay_handle(bot: Bot, event: MessageEvent, state: T_State):
arg = str(state["_prefix"]["command_arg"])
# qq: str = (
# event.message["at"][0].data["qq"]
# if event.message.get("at")
# else event.get_user_id()
# )
itemId, uid, method = "0", None, ("0" if METHOD == "支付宝" else "1")
for s in arg.split():
# 支持指定支付方式
if "微信" in s:
method = "1"
continue
elif "支付宝" in s:
method = "0"
continue
# 仅支持国内官服 UID
if len(s) == 9 and s.isdigit() and s[0] in ["1", "2"]:
uid = s
continue
# 输入物品别名识别
for gId, gData in GOODS.items():
if (s == gId) or (s in gData["aliases"]):
itemId = gId
break
uid = uid # or (await getUid(qq)) # 由 QQ 号查询 UID
if not uid:
await payMatcher.finish("UID 捏?", at_sender=True)
debugMsg = f"UID[{uid}] ITEM[{itemId}] METHOD[{method}]"
itemImg, qrcodeImg, info = await getImages(itemId, uid, method)
if not qrcodeImg:
logger.info(f"未能从 API 获取二维码!{debugMsg}")
await payMatcher.finish("未能从 API 获取二维码!")
try:
msg = await payMatcher.send(
MessageSegment.image(await draw(itemId, itemImg, qrcodeImg, info))
)
if WITHDRAW:
require("nonebot_plugin_apscheduler")
from nonebot_plugin_apscheduler import scheduler
scheduler.add_job(
withdraw,
"date",
args=[bot, dict(msg)["message_id"]],
run_date=datetime.fromtimestamp(time() + WITHDRAW),
)
except ActionFailed:
logger.error(f"充值二维码发送失败!{debugMsg}")
except Exception as e:
logger.opt(exception=e).error(f"充值二维码撤回任务异常!{debugMsg}")
@monsterxcn
Copy link
Author

monsterxcn commented Jan 1, 2023

通过加载 src/plugins/GenshinUID 文件夹方式安装 GenshinUID 的可以按下面格式修改,以启用 ”不填 UID 时自动使用发送消息的 QQ 绑定的 UID“ 及 @QQ 功能

# 第 31 行
- # from plugins.genshin_info import getUid
+ from src.plugins.GenshinUID.GenshinUID.utils.db_operation.db_operation import select_db

# 第 247-251 行
-     # qq: str = (
-     #     event.message["at"][0].data["qq"]
-     #     if event.message.get("at")
-     #     else event.get_user_id()
-     # )
+     qq: str = (
+         event.message["at"][0].data["qq"]
+         if event.message.get("at")
+         else event.get_user_id()
+     )

# 第 270 行
-     uid = uid  # or (await getUid(qq))  # 由 QQ 号查询 UID
+     uid = uid or str(await select_db(qq, mode='uid'))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment