Created
January 2, 2023 10:48
-
-
Save shakir915/883db9971e8529d67d754d7c8a428628 to your computer and use it in GitHub Desktop.
NoneBot2 原神充值二维码插件,将 nonebot_plugin_gspay.py 补全后放入插件文件夹下重启 Bot 即可使用
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
NoneBot2 原神充值二维码插件,触发命令:原神充值/充值/pay | |
阉割了指令的 @QQ 功能,其余功能完整 | |
第 40 行需要手动填写 API 地址 | |
""" | |
from base64 import b64decode | |
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.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["微信", "支付宝"] = "支付宝" # 默认付款方式 | |
GOODS = { | |
"0": { | |
"title": "创世结晶×60", | |
"cost": 6, | |
"img": "https://uploadstatic.mihoyo.com/payment-center/2022/09/07/0f362595da2e37a7a8fde1bb120656d2_594155779359709441.png", | |
}, | |
"1": { | |
"title": "创世结晶×300", | |
"cost": 30, | |
"img": "https://uploadstatic.mihoyo.com/payment-center/2022/09/07/830e247bb0cfffa5c74a04e79c0040f5_1814106121630644354.png", | |
}, | |
"2": { | |
"title": "创世结晶×980", | |
"cost": 98, | |
"img": "https://uploadstatic.mihoyo.com/payment-center/2022/09/07/dfefc92ce56e3b5615ef28d6b1119b8b_5835214000384994274.png", | |
}, | |
"3": { | |
"title": "创世结晶×1980", | |
"cost": 198, | |
"img": "https://uploadstatic.mihoyo.com/payment-center/2022/09/07/e918ecfedcb13113eb627fd944199272_3460055016813877022.png", | |
}, | |
"4": { | |
"title": "创世结晶×3280", | |
"cost": 328, | |
"img": "https://uploadstatic.mihoyo.com/payment-center/2022/09/07/70e703a64e8786390ab8b7cdc35dbeeb_6358952826545947027.png", | |
}, | |
"5": { | |
"title": "创世结晶×6480", | |
"cost": 648, | |
"img": "https://uploadstatic.mihoyo.com/payment-center/2022/09/07/24fa6b6190ce5da6928e431a832d85c3_5932007685099741224.png", | |
}, | |
"6": { | |
"title": "空月祝福", | |
"cost": 30, | |
"img": "https://uploadstatic.mihoyo.com/payment-center/2020/06/08/2da77803b9b2ffc2a2b763a59e9c125f_5219067706372136934.png", | |
}, | |
} | |
def font(size: int) -> ImageFont.FreeTypeFont: | |
"""Pillow 绘制字体设置""" | |
return ImageFont.truetype(str(FONT_PATH), size=size) | |
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" | |
res = Image.new("RGBA", (450, 520), themeColor) | |
drawer = ImageDraw.Draw(res) | |
resample = getattr(Image, "Resampling", Image).LANCZOS | |
# 头部矩形背景 | |
drawer.rectangle((75, 50, 375 - 1, 150), fill="#E5F9FF", width=0) | |
# 商品图片 | |
item = item.resize((90, 90), resample=resample) | |
res.paste(item, (80, 55), item) | |
# 商品名称 | |
drawer.text( | |
( | |
int(175 + (195 - font(25).getlength(GOODS[itemId]["title"])) / 2), | |
int(70 + (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 + (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), qrcode) | |
# 尾部矩形背景 | |
drawer.rectangle((75, 450, 375 - 1, 460), 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 - 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), | |
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") | |
goodAliases = { | |
gId: [ # 物品别名 | |
gId, # 数字编号形如 1 | |
good["title"], # 商品完整名称形如 创世结晶×300 | |
good["title"][2:], # 商品名称形如 结晶×300 | |
good["title"].replace("×", "x"), # 商品名称形如 创世结晶x300 | |
good["title"][2:].replace("×", "x"), # 商品名称形如 结晶x300 | |
good["title"].replace("×", ""), # 商品名称形如 创世结晶300 | |
good["title"][2:].replace("×", ""), # 商品名称形如 结晶300 | |
] | |
for gId, good in GOODS.items() | |
} | |
for s in arg.split(): | |
if "微信" in s: | |
# 输入含微信关键字的识别为指定微信支付 | |
method = "1" | |
continue | |
elif "支付宝" in s: | |
# 输入含支付宝关键字的识别为指定支付宝支付 | |
method = "0" | |
continue | |
elif "月卡" in s: | |
# 输入含月卡关键字的识别为指定充值空月祝福 | |
itemId = "6" | |
continue | |
for gId, aliases in goodAliases.items(): | |
# 输入物品别名识别 | |
# 结晶 98 及以上额外支持直接输金额数字,如:原神充值 648 | |
if s in aliases or ( | |
s.isdigit() and int(s) == GOODS[gId]["cost"] and GOODS[gId]["cost"] > 30 | |
): | |
itemId = gId | |
continue | |
if len(s) == 9 and s.isdigit() and s[0] in ["1", "2"]: | |
# 仅支持国内官服 UID | |
uid = s | |
continue | |
uid = uid # or (await getUid(qq)) # 由 QQ 号查询 UID | |
if not uid: | |
await payMatcher.finish("UID 捏?", at_sender=True) | |
itemImg, qrcodeImg, info = await getImages(itemId, uid, method) | |
if not qrcodeImg: | |
await payMatcher.finish("未能从 API 获取二维码!") | |
await payMatcher.finish( | |
MessageSegment.image(await draw(itemId, itemImg, qrcodeImg, info)) | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment