Skip to content

Instantly share code, notes, and snippets.

@shakir915
Created January 2, 2023 10:48
Show Gist options
  • Save shakir915/883db9971e8529d67d754d7c8a428628 to your computer and use it in GitHub Desktop.
Save shakir915/883db9971e8529d67d754d7c8a428628 to your computer and use it in GitHub Desktop.
NoneBot2 原神充值二维码插件,将 nonebot_plugin_gspay.py 补全后放入插件文件夹下重启 Bot 即可使用
"""
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