update 0.0.7.0

This commit is contained in:
HibiKier
2021-12-16 11:16:28 +08:00
parent b892617824
commit 2653f67193
141 changed files with 2693 additions and 547 deletions
+26
View File
@@ -0,0 +1,26 @@
from configs.config import Config
import nonebot
Config.add_plugin_config(
"genshin",
"mhyVersion",
"2.11.1"
)
Config.add_plugin_config(
"genshin",
"salt",
"xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs"
)
Config.add_plugin_config(
"genshin",
"client_type",
"5"
)
nonebot.load_plugins("plugins/genshin/query_user")
@@ -0,0 +1,97 @@
from nonebot import on_command
from nonebot.typing import T_State
from nonebot.adapters.cqhttp import Bot, MessageEvent, GroupMessageEvent
from utils.utils import get_message_text, is_number
from ..models import Genshin
from services.log import logger
__zx_plugin_name__ = "原神绑定"
__plugin_usage__ = """
usage:
绑定原神uid等数据,cookie极为重要,请谨慎绑定
** 如果对拥有者不熟悉,并不建议添加cookie **
该项目只会对cookie用于”米游社签到“,“原神玩家查询”,“原神便笺查询”
指令:
原神绑定uid [uid]
原神绑定米游社id [mys_id]
原神绑定cookie [cookie] # 该绑定请私聊
原神解绑 [uid]
示例:原神绑定uid 92342233
如果不明白怎么获取cookie请输入“原神绑定cookie”。
""".strip()
__plugin_des__ = "绑定自己的原神uid等"
__plugin_cmd__ = ["原神绑定uid [uid]", "原神绑定米游社id [mys_id]", "原神绑定cookie [cookie]", "原神解绑"]
__plugin_type__ = ("原神相关",)
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_settings__ = {
"level": 5,
"default_status": True,
"limit_superuser": False,
"cmd": ["原神绑定"],
}
bind = on_command(
"原神绑定uid", aliases={"原神绑定米游社id", "原神绑定cookie"}, priority=5, block=True
)
unbind = on_command("原神解绑", priority=5, block=True)
@bind.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
msg = get_message_text(event.json())
if state["_prefix"]["raw_command"] in ["原神绑定uid", "原神绑定米游社id"]:
if not is_number(msg):
await bind.finish("uid/id必须为纯数字!", at_senders=True)
msg = int(msg)
if state["_prefix"]["raw_command"] == "原神绑定uid":
uid = await Genshin.get_user_uid(event.user_id)
if uid:
await bind.finish(f"您已绑定过uid:{uid},如果希望更换uid,请先发送原神解绑")
flag = await Genshin.add_uid(event.user_id, msg)
if not flag:
await bind.finish("添加失败,该uid可能已存在...")
_x = f"已成功添加原神uid:{msg}"
elif state["_prefix"]["raw_command"] == "原神绑定米游社id":
uid = await Genshin.get_user_uid(event.user_id)
if not uid:
await bind.finish("请先绑定原神uid..")
await Genshin.set_mys_id(uid, msg)
_x = f"已成功为uid:{uid} 设置米游社id:{msg}"
else:
if not msg:
await bind.finish(
"私聊发送!!\n打开https://bbs.mihoyo.com/ys/登录后按F12点击控制台输入document.cookie复制输出的内容即可"
)
if isinstance(event, GroupMessageEvent):
await bind.finish("请立即撤回你的消息并私聊发送!")
uid = await Genshin.get_user_uid(event.user_id)
if not uid:
await bind.finish("请先绑定原神uid..")
if msg.startswith('"'):
msg = msg[1:]
if msg.endswith('"'):
msg = msg[:-1]
await Genshin.set_cookie(uid, msg)
_x = f"已成功为uid:{uid} 设置cookie"
await bind.send(_x)
logger.info(
f"(USER {event.user_id}, "
f"GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
f" {state['_prefix']['raw_command']}:{msg}"
)
@unbind.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
if await Genshin.delete_user(event.user_id):
await unbind.send("用户数据删除成功...")
logger.info(
f"(USER {event.user_id}, GROUP "
f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})"
f"原神解绑"
)
else:
await unbind.send("该用户数据不存在..")
@@ -0,0 +1,210 @@
from services.db_context import db
from typing import Optional, Union
class Genshin(db.Model):
__tablename__ = "genshin"
id = db.Column(db.Integer(), primary_key=True)
user_qq = db.Column(db.BigInteger(), nullable=False)
uid = db.Column(db.BigInteger())
mys_id = db.Column(db.BigInteger())
cookie = db.Column(db.String(), default="")
today_query_uid = db.Column(db.String(), default="") # 该cookie今日查询的uid
auto_sign = db.Column(db.Boolean(), default=False) # 自动签到
_idx1 = db.Index("genshin_uid_idx1", "user_qq", "uid", unique=True)
@classmethod
async def add_uid(cls, user_qq: int, uid: int):
"""
说明:
添加一个uid
参数:
:param user_qq: 用户qq
:param uid: 原神uid
"""
query = cls.query.where((cls.user_qq == user_qq) & (cls.uid == uid))
user = await query.gino.first()
if not user:
await cls.create(
user_qq=user_qq,
uid=uid,
)
return True
return False
@classmethod
async def set_mys_id(cls, uid: int, mys_id: int) -> bool:
"""
说明:
设置米游社id
参数:
:param uid: 原神uid
:param mys_id: 米游社id
"""
query = cls.query.where(cls.uid == uid).with_for_update()
user = await query.gino.first()
if user:
await user.update(mys_id=mys_id).apply()
return True
return False
@classmethod
async def set_cookie(cls, uid: int, cookie: str) -> bool:
"""
说明:
设置cookie
参数:
:param uid: 原神uid
:param cookie: 米游社id
"""
query = cls.query.where(cls.uid == uid).with_for_update()
user = await query.gino.first()
if user:
await user.update(cookie=cookie).apply()
return True
return False
@classmethod
async def set_auto_sign(cls, uid: int, flag: bool) -> bool:
"""
说明:
设置米游社自动签到
参数:
:param uid: 原神uid
:param flag: 开关状态
"""
query = cls.query.where(cls.uid == uid).with_for_update()
user = await query.gino.first()
if user:
await user.update(auto_sign=flag).apply()
return True
return False
@classmethod
async def get_query_cookie(cls, uid: int) -> Optional[str]:
"""
说明:
获取查询角色信息cookie
参数:
:param uid: 原神uid
"""
# 查找用户今日是否已经查找过,防止重复
query = cls.query.where(cls.today_query_uid.contains(str(uid)))
x = await query.gino.first()
if x:
return x.cookie
for u in [x for x in await cls.query.order_by(db.func.random()).gino.all() if x.cookie]:
if not u.today_query_uid or len(u.today_query_uid[:-1].split()) < 30:
await cls._add_query_uid(uid, u.uid)
return u.cookie
return None
@classmethod
async def get_user_cookie(cls, uid: int, flag: bool = False) -> Optional[str]:
"""
说明:
获取用户cookie
参数:
:param uid:原神uid
:param flag:必须使用自己的cookie
"""
cookie = await cls._get_user_data(None, uid, "cookie")
print(uid, cookie)
if not cookie and not flag:
cookie = await cls.get_query_cookie(uid)
print(uid, cookie)
return cookie
@classmethod
async def get_user_uid(cls, user_qq: int) -> Optional[int]:
"""
说明:
获取用户uid
参数:
:param user_qq:用户qq
"""
return await cls._get_user_data(user_qq, None, "uid")
@classmethod
async def get_user_mys_id(cls, uid: int) -> Optional[int]:
"""
说嘛:
获取用户米游社id
参数:
:param uid:原神id
"""
return await cls._get_user_data(None, uid, "mys_id")
@classmethod
async def delete_user_cookie(cls, uid: int):
"""
说明:
删除用户cookie
参数:
:param uid: 原神uid
"""
query = cls.query.where(cls.uid == uid).with_for_update()
user = await query.gino.first()
if user:
await user.update(cookie="").apply()
@classmethod
async def delete_user(cls, user_qq: int):
"""
说明:
删除用户数据
参数:
:param user_qq: 用户qq
"""
query = cls.query.where(cls.user_qq == user_qq).with_for_update()
user = await query.gino.first()
if not user:
return False
await user.delete()
return True
@classmethod
async def _add_query_uid(cls, uid: int, cookie_uid: int):
"""
说明:
添加每日查询重复uid的cookie
参数:
:param uid: 原神uid
:param cookie_uid: cookie的uid
"""
query = cls.query.where(cls.uid == cookie_uid).with_for_update()
user = await query.gino.first()
await user.update(today_query_uid=cls.today_query_uid + f"{uid} ").apply()
@classmethod
async def _get_user_data(
cls, user_qq: Optional[int], uid: Optional[int], type_: str
) -> Optional[Union[int, str]]:
"""
说明:
获取用户数据
参数:
:param user_qq: 用户qq
:param uid: uid
:param type_: 数据类型
"""
if type_ == "uid":
user = await cls.query.where(cls.user_qq == user_qq).gino.first()
return user.uid if user else None
user = await cls.query.where(cls.uid == uid).gino.first()
if not user:
return None
if type_ == "mys_id":
return user.mys_id
elif type_ == "cookie":
return user.cookie
@classmethod
async def reset_today_query_uid(cls):
for u in await cls.query.with_for_update().gino.all():
if u.today_query_uid:
await u.update(
today_query_uid=""
).apply()
@@ -0,0 +1,53 @@
from nonebot import on_command
from nonebot.typing import T_State
from nonebot.adapters.cqhttp import Bot, MessageEvent, GroupMessageEvent
from services.log import logger
from .data_source import get_user_memo
from ..models import Genshin
__zx_plugin_name__ = "原神便笺查询"
__plugin_usage__ = """
usage:
通过指定cookie和uid查询事实数据
指令:
原神便笺查询/yss
示例:原神便笺查询 92342233
""".strip()
__plugin_des__ = "不能浪费丝毫体力"
__plugin_cmd__ = ["原神便笺查询/yss"]
__plugin_type__ = ("原神相关",)
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_settings__ = {
"level": 5,
"default_status": True,
"limit_superuser": False,
"cmd": ["原神便笺查询"],
}
query_memo_matcher = on_command("原神便签查询", aliases={"原神便笺查询", "yss"}, priority=5, block=True)
@query_memo_matcher.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
uid = await Genshin.get_user_uid(event.user_id)
if not uid or not await Genshin.get_user_cookie(uid, True):
await query_memo_matcher.finish("请先绑定uid和cookie!")
if isinstance(event, GroupMessageEvent):
uname = event.sender.card or event.sender.nickname
else:
uname = event.sender.nickname
data = await get_user_memo(event.user_id, uid, uname)
if data:
await query_memo_matcher.send(data)
logger.info(
f"(USER {event.user_id}, "
f"GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) 使用原神便笺查询 uid:{uid}"
)
else:
await query_memo_matcher.send("未查询到数据...")
@@ -0,0 +1,237 @@
from typing import Optional, Union
from nonebot.adapters.cqhttp import MessageSegment
from configs.config import Config
from asyncio.exceptions import TimeoutError
from services.log import logger
from configs.path_config import IMAGE_PATH
from utils.image_utils import BuildImage
from utils.http_utils import AsyncHttpx
from utils.utils import get_user_avatar
from utils.message_builder import image
from ..utils import get_ds
from ..models import Genshin
from io import BytesIO
from pathlib import Path
from nonebot import Driver
import asyncio
import nonebot
driver: Driver = nonebot.get_driver()
memo_path = Path(IMAGE_PATH) / "genshin" / "genshin_memo"
memo_path.mkdir(exist_ok=True, parents=True)
@driver.on_startup
async def _():
for name, url in zip(
["resin.png", "task.png", "resin_discount.png"],
[
"https://upload-bbs.mihoyo.com/upload/2021/09/29/8819732/54266243c7d15ba31690c8f5d63cc3c6_71491376413333325"
"20.png?x-oss-process=image//resize,s_600/quality,q_80/auto-orient,0/interlace,1/format,png",
"https://patchwiki.biligame.com/images/ys/thumb/c/cc/6k6kuj1kte6m1n7hexqfrn92z6h4yhh.png/60px-委托任务logo.png",
"https://patchwiki.biligame.com/images/ys/d/d9/t1hv6wpucbwucgkhjntmzroh90nmcdv.png",
],
):
file = memo_path / name
if not file.exists():
await AsyncHttpx.download_file(url, file)
logger.info(f"已下载原神便签资源 -> {file}...")
async def get_user_memo(user_id: int, uid: int, uname: str) -> Optional[Union[str, MessageSegment]]:
uid = str(uid)
if uid[0] == "1" or uid[0] == "2":
server_id = "cn_gf01"
elif uid[0] == "5":
server_id = "cn_qd01"
else:
return None
return await parse_data_and_draw(user_id, uid, server_id, uname)
async def get_memo(uid: str, server_id: str) -> "Union[str, dict], int":
try:
req = await AsyncHttpx.get(
url=f"https://api-takumi.mihoyo.com/game_record/app/genshin/api/dailyNote?server={server_id}&role_id={uid}",
headers={
"DS": get_ds(f"role_id={uid}&server={server_id}"),
"x-rpc-app_version": Config.get_config("genshin", "mhyVersion"),
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1",
"x-rpc-client_type": Config.get_config("genshin", "client_type"),
"Referer": "https://webstatic.mihoyo.com/",
"Cookie": await Genshin.get_user_cookie(int(uid))
},
)
data = req.json()
if data["message"] == "OK":
return data["data"], 200
return data["message"], 999
except TimeoutError:
return "访问超时,请稍后再试", 997
except Exception as e:
logger.info(f"便签查询获取失败未知错误 {e}:{e}")
return "发生了一些错误,请稍后再试", 998
def create_border(
image_name: str, content: str, notice_text: str, value: str
) -> BuildImage:
border = BuildImage(500, 100, color="#E0D9D1", font="HYWenHei-85W.ttf", font_size=20)
text_bk = BuildImage(350, 96, color="#F5F1EB", font_size=23, font="HYWenHei-85W.ttf")
_x = 70 if image_name == "resin.png" else 50
_px = 10 if image_name == "resin.png" else 20
text_bk.paste(
BuildImage(_x, _x, background=memo_path / image_name),
(_px, 0),
True,
center_type="by_height",
)
text_bk.text((87, 20), content)
text_bk.paste(
BuildImage(
0,
0,
plain_text=notice_text,
font_color=(203, 189, 175),
font="HYWenHei-85W.ttf",
font_size=17,
),
(87, 50),
True,
)
font_width, _ = border.getsize(value)
border.text((350 + 76 - int(font_width / 2), 0), value, center_type="by_height")
border.paste(text_bk, (2, 0), center_type="by_height")
return border
async def parse_data_and_draw(
user_id: int, uid: str, server_id: str, uname: str
) -> Union[str, MessageSegment]:
data, code = await get_memo(uid, server_id)
if code != 200:
return data
user_avatar = BytesIO(await get_user_avatar(user_id))
for x in data["expeditions"]:
file_name = x["avatar_side_icon"].split("_")[-1]
role_avatar = memo_path / "role_avatar" / file_name
if not role_avatar.exists():
await AsyncHttpx.download_file(x["avatar_side_icon"], role_avatar)
return await asyncio.get_event_loop().run_in_executor(
None, _parse_data_and_draw, data, user_avatar, uid, uname
)
def _parse_data_and_draw(
data: dict, user_avatar: BytesIO, uid: int, uname: str
) -> Union[str, MessageSegment]:
current_resin = data["current_resin"] # 当前树脂
max_resin = data["max_resin"] # 最大树脂
resin_recovery_time = data["resin_recovery_time"] # 树脂全部回复时间
finished_task_num = data["finished_task_num"] # 完成的每日任务
total_task_num = data["total_task_num"] # 每日任务总数
remain_resin_discount_num = data["remain_resin_discount_num"] # 值得铭记的强敌总数
resin_discount_num_limit = data["resin_discount_num_limit"] # 剩余值得铭记的强敌
current_expedition_num = data["current_expedition_num"] # 当前挖矿人数
max_expedition_num = data["max_expedition_num"] # 每日挖矿最大人数
expeditions = data["expeditions"] # 挖矿详情
minute, second = divmod(int(resin_recovery_time), 60)
hour, minute = divmod(minute, 60)
A = BuildImage(1030, 520, color="#f1e9e1", font_size=15, font="HYWenHei-85W.ttf")
A.text((10, 15), "原神便笺 | Create By ZhenXun", (198, 186, 177))
ava = BuildImage(100, 100, background=user_avatar)
ava.circle()
A.paste(ava, (40, 40), True)
A.paste(
BuildImage(0, 0, plain_text=uname, font_size=20, font="HYWenHei-85W.ttf"),
(160, 62),
True,
)
A.paste(
BuildImage(
0,
0,
plain_text=f"UID:{uid}",
font_size=15,
font="HYWenHei-85W.ttf",
font_color=(21, 167, 89),
),
(160, 92),
True,
)
border = create_border(
"resin.png",
"原粹树脂",
"将在{:0>2d}:{:0>2d}:{:0>2d}秒后全部恢复".format(hour, minute, second),
f"{current_resin}/{max_resin}",
)
A.paste(border, (10, 155))
border = create_border(
"task.png",
"每日委托",
"今日委托已全部完成" if finished_task_num == total_task_num else "今日委托完成数量不足",
f"{finished_task_num}/{total_task_num}",
)
A.paste(border, (10, 265))
border = create_border(
"resin_discount.png",
"值得铭记的强敌",
"本周剩余消耗减半次数",
f"{remain_resin_discount_num}/{resin_discount_num_limit}",
)
A.paste(border, (10, 375))
expeditions_border = BuildImage(
470, 430, color="#E0D9D1", font="HYWenHei-85W.ttf", font_size=20
)
expeditions_text = BuildImage(
466, 426, color="#F5F1EB", font_size=23, font="HYWenHei-85W.ttf"
)
expeditions_text.text(
(5, 5), f"探索派遣限制{current_expedition_num}/{max_expedition_num}", (100, 100, 98)
)
h = 45
for x in expeditions:
_bk = BuildImage(400, 66, color="#ECE3D8", font="HYWenHei-85W.ttf", font_size=21)
file_name = x["avatar_side_icon"].split("_")[-1]
role_avatar = memo_path / "role_avatar" / file_name
_ava_img = BuildImage(75, 75, background=role_avatar)
_ava_img.circle()
if x["status"] == "Finished":
msg = "探索完成"
font_color = (146, 188, 63)
_circle_color = (146, 188, 63)
else:
minute, second = divmod(int(x["remained_time"]), 60)
hour, minute = divmod(minute, 60)
font_color = (193, 180, 167)
msg = "还剩{:0>2d}小时{:0>2d}分钟{:0>2d}秒".format(hour, minute, second)
_circle_color = "#DE9C58"
_circle_bk = BuildImage(60, 60)
_circle_bk.circle()
a_circle = BuildImage(55, 55, color=_circle_color)
a_circle.circle()
b_circle = BuildImage(47, 47)
b_circle.circle()
a_circle.paste(b_circle, (4, 4), alpha=True)
_circle_bk.paste(a_circle, (4, 4), alpha=True)
_bk.paste(_circle_bk, (25, 0), True, center_type="by_height")
_bk.paste(_ava_img, (19, -13), True)
_bk.text((100, 0), msg, font_color, "by_height")
_bk.circle_corner(20)
expeditions_text.paste(_bk, (25, h), True)
h += 75
expeditions_border.paste(expeditions_text, center_type="center")
A.paste(expeditions_border, (550, 45))
return image(b64=A.pic2bs4())
@@ -0,0 +1,61 @@
from nonebot import on_command
from nonebot.typing import T_State
from nonebot.adapters.cqhttp import Bot, MessageEvent, GroupMessageEvent
from services.log import logger
from .data_source import query_role_data
from ..models import Genshin
from utils.utils import get_message_text, is_number
__zx_plugin_name__ = "原神玩家查询"
__plugin_usage__ = """
usage:
通过uid查询原神玩家信息
指令:
原神玩家查询/ys ?[uid]
示例:原神玩家查询 92342233
""".strip()
__plugin_des__ = "请问你们有几个肝?"
__plugin_cmd__ = ["原神玩家查询/ys"]
__plugin_type__ = ("原神相关",)
__plugin_version__ = 0.1
__plugin_author__ = "HibiKier"
__plugin_settings__ = {
"level": 5,
"default_status": True,
"limit_superuser": False,
"cmd": ["原神玩家查询"],
}
query_role_info_matcher = on_command("原神玩家查询", aliases={"原神玩家查找", "ys"}, priority=5, block=True)
@query_role_info_matcher.handle()
async def _(bot: Bot, event: MessageEvent, state: T_State):
msg = get_message_text(event.json())
if msg:
if not is_number(msg):
await query_role_info_matcher.finish("查询uid必须为数字!")
msg = int(msg)
if not msg:
uid = await Genshin.get_user_uid(event.user_id)
else:
uid = msg
if not uid: # or not await Genshin.get_user_cookie(uid):
await query_role_info_matcher.finish("请先绑定uid和cookie!")
nickname = event.sender.card if event.sender.card else event.sender.nickname
mys_id = await Genshin.get_user_mys_id(uid)
data = await query_role_data(event.user_id, uid, mys_id, nickname)
if data:
await query_role_info_matcher.send(data)
logger.info(
f"(USER {event.user_id}, "
f"GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) 使用原神玩家查询 uid:{uid}"
)
else:
await query_role_info_matcher.send("查询失败..")
@@ -0,0 +1,249 @@
from typing import Optional, List, Dict, Union
from .draw_image import init_image, get_genshin_image
from nonebot.adapters.cqhttp import MessageSegment
from ..utils import get_ds, element_mastery
from services.log import logger
from utils.http_utils import AsyncHttpx
from configs.config import Config
from ..models import Genshin
try:
import ujson as json
except ModuleNotFoundError:
import json
async def query_role_data(
user_id: int, uid: int, mys_id: Optional[str] = None, nickname: Optional[str] = None
) -> Optional[Union[MessageSegment, str]]:
uid = str(uid)
if uid[0] == "1" or uid[0] == "2":
server_id = "cn_gf01"
elif uid[0] == "5":
server_id = "cn_qd01"
else:
return None
return await get_image(user_id, uid, server_id, mys_id, nickname)
async def get_image(
user_id: int,
uid: str,
server_id: str,
mys_id: Optional[str] = None,
nickname: Optional[str] = None,
) -> Optional[Union[MessageSegment, str]]:
"""
生成图片
:param user_id:用户qq
:param uid: 用户uid
:param server_id: 服务器
:param mys_id: 米游社id
:param nickname: QQ昵称
:return:
"""
data, code = await get_info(uid, server_id)
if code != 200:
return data
if data:
char_data_list, role_data, world_data_dict, home_data_list = parsed_data(data)
mys_data = await get_mys_data(uid, mys_id)
if mys_data:
nickname = None
if char_data_list:
char_detailed_data = await get_character(
uid, [x["id"] for x in char_data_list], server_id
)
_x = {}
if char_detailed_data:
for char in char_detailed_data["avatars"]:
_x[char["name"]] = {
"weapon": char["weapon"]["name"],
"weapon_image": char["weapon"]["icon"],
"level": char["weapon"]["level"],
"affix_level": char["weapon"]["affix_level"],
}
await init_image(char_data_list, _x)
return await get_genshin_image(
user_id,
uid,
char_data_list,
role_data,
world_data_dict,
home_data_list,
_x,
mys_data,
nickname,
)
return "未找到用户数据..."
# Github-@lulu666lulu https://github.com/Azure99/GenshinPlayerQuery/issues/20
"""
{body:"",query:{"action_ticket": undefined, "game_biz": "hk4e_cn”}}
对应 https://api-takumi.mihoyo.com/binding/api/getUserGameRolesByCookie?game_biz=hk4e_cn //查询米哈游账号下绑定的游戏(game_biz可留空)
{body:"",query:{"uid": 12345(被查询账号米哈游uid)}}
对应 https://api-takumi.mihoyo.com/game_record/app/card/wapi/getGameRecordCard?uid=
{body:"",query:{'role_id': '查询账号的uid(游戏里的)' ,'server': '游戏服务器'}}
对应 https://api-takumi.mihoyo.com/game_record/app/genshin/api/index?server= server信息 &role_id= 游戏uid
{body:"",query:{'role_id': '查询账号的uid(游戏里的)' , 'schedule_type': 1(我这边只看到出现过1和2), 'server': 'cn_gf01'}}
对应 https://api-takumi.mihoyo.com/game_record/app/genshin/api/spiralAbyss?schedule_type=1&server= server信息 &role_id= 游戏uid
{body:"",query:{game_id: 2(目前我知道有崩坏3是1原神是2)}}
对应 https://api-takumi.mihoyo.com/game_record/app/card/wapi/getAnnouncement?game_id= 这个是公告api
b=body q=query
其中b只在post的时候有内容,q只在get的时候有内容
"""
async def get_info(uid_: str, server_id: str) -> "Optional[Union[dict, str]], int":
try:
req = await AsyncHttpx.get(
url=f"https://api-takumi.mihoyo.com/game_record/app/genshin/api/index?server={server_id}&role_id={uid_}",
headers={
"Accept": "application/json, text/plain, */*",
"DS": get_ds(f"role_id={uid_}&server={server_id}"),
"Origin": "https://webstatic.mihoyo.com",
"x-rpc-app_version": Config.get_config("genshin", "mhyVersion"),
"User-Agent": "Mozilla/5.0 (Linux; Android 9; Unspecified Device) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36 miHoYoBBS/2.2.0",
"x-rpc-client_type": Config.get_config("genshin", "client_type"),
"Referer": "https://webstatic.mihoyo.com/app/community-game-records/index.html?v=6",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,en-US;q=0.8",
"X-Requested-With": "com.mihoyo.hyperion",
"Cookie": await Genshin.get_user_cookie(int(uid_))
},
)
data = req.json()
if data["message"] == "OK":
return data["data"], 200
return data["message"], 999
except Exception as e:
logger.error(f"访问失败,请重试! {type(e)}: {e}")
return None, -1
async def get_character(
uid: str, character_ids: List[str], server_id="cn_gf01"
) -> Optional[dict]:
try:
req = await AsyncHttpx.post(
url="https://api-takumi.mihoyo.com/game_record/app/genshin/api/character",
headers={
"Accept": "application/json, text/plain, */*",
"DS": get_ds(
"",
{
"character_ids": character_ids,
"role_id": uid,
"server": server_id,
},
),
"Origin": "https://webstatic.mihoyo.com",
"Cookie": await Genshin.get_user_cookie(int(uid)),
"x-rpc-app_version": Config.get_config("genshin", "mhyVersion"),
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1",
"x-rpc-client_type": "5",
"Referer": "https://webstatic.mihoyo.com/",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,en-US;q=0.8",
"X-Requested-With": "com.mihoyo.hyperion",
},
json={"character_ids": character_ids, "role_id": uid, "server": server_id},
)
data = req.json()
if data["message"] == "OK":
return data["data"]
except Exception as e:
logger.error(f"访问失败,请重试! {type(e)}: {e}")
return None
def parsed_data(
data: dict,
) -> "Optional[List[Dict[str, str]]], Dict[str, str], Optional[List[Dict[str, str]]], Optional[List[Dict[str, str]]]":
"""
解析数据
:param data: 数据
"""
char_data_list = []
for char in data["avatars"]:
_x = {
"id": char["id"],
"image": char["image"],
"name": char["name"],
"element": element_mastery[char["element"].lower()],
"fetter": char["fetter"],
"level": char["level"],
"rarity": char["rarity"],
"actived_constellation_num": char["actived_constellation_num"],
}
char_data_list.append(_x)
role_data = {
"active_day_number": data["stats"]["active_day_number"], # 活跃天数
"achievement_number": data["stats"]["achievement_number"], # 达成成就数量
"win_rate": data["stats"]["win_rate"],
"anemoculus_number": data["stats"]["anemoculus_number"], # 风神瞳已收集
"geoculus_number": data["stats"]["geoculus_number"], # 岩神瞳已收集
"avatar_number": data["stats"]["avatar_number"], # 获得角色数量
"way_point_number": data["stats"]["way_point_number"], # 传送点已解锁
"domain_number": data["stats"]["domain_number"], # 秘境解锁数量
"spiral_abyss": data["stats"]["spiral_abyss"], # 深渊当期进度
"precious_chest_number": data["stats"]["precious_chest_number"], # 珍贵宝箱
"luxurious_chest_number": data["stats"]["luxurious_chest_number"], # 华丽宝箱
"exquisite_chest_number": data["stats"]["exquisite_chest_number"], # 精致宝箱
"magic_chest_number": data["stats"]["magic_chest_number"], # 奇馈宝箱
"common_chest_number": data["stats"]["common_chest_number"], # 普通宝箱
"electroculus_number": data["stats"]["electroculus_number"], # 雷神瞳已收集
}
world_data_dict = {}
for world in data["world_explorations"]:
_x = {
"level": world["level"], # 声望等级
"exploration_percentage": world["exploration_percentage"], # 探索进度
"image": world["icon"],
"name": world["name"],
"offerings": world["offerings"],
}
world_data_dict[world["name"]] = _x
home_data_list = []
for home in data["homes"]:
_x = {
"level": home["level"], # 最大信任等级
"visit_num": home["visit_num"], # 最高历史访客数
"comfort_num": home["comfort_num"], # 最高洞天仙力
"item_num": home["item_num"], # 已获得摆件数量
"name": home["name"],
"icon": home["icon"],
"comfort_level_name": home["comfort_level_name"],
"comfort_level_icon": home["comfort_level_icon"],
}
home_data_list.append(_x)
return char_data_list, role_data, world_data_dict, home_data_list
async def get_mys_data(uid: str, mys_id: Optional[str]) -> Optional[List[Dict]]:
"""
获取用户米游社数据
:param uid: 原神uid
:param mys_id: 米游社id
"""
if mys_id:
try:
req = await AsyncHttpx.get(
url=f"https://api-takumi.mihoyo.com/game_record/card/wapi/getGameRecordCard?uid={mys_id}",
headers={
"DS": get_ds(f"uid={mys_id}"),
"x-rpc-app_version": Config.get_config("genshin", "mhyVersion"),
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1",
"x-rpc-client_type": "5",
"Referer": "https://webstatic.mihoyo.com/",
"Cookie": await Genshin.get_user_cookie(int(uid))
},
)
data = req.json()
if data["message"] == "OK":
return data["data"]["list"]
except Exception as e:
logger.error(f"访问失败,请重试! {type(e)}: {e}")
return None
@@ -0,0 +1,530 @@
from configs.path_config import IMAGE_PATH
from pathlib import Path
from utils.image_utils import BuildImage
from typing import List, Dict, Optional
from utils.message_builder import image
from nonebot.adapters.cqhttp import MessageSegment
from utils.http_utils import AsyncHttpx
from utils.utils import get_user_avatar
from io import BytesIO
import random
import asyncio
import os
image_path = Path(IMAGE_PATH) / "genshin" / "genshin_card"
async def get_genshin_image(
user_id: int,
uid: str,
char_data_list: List[Dict],
role_data: Dict,
world_data_dict: Dict,
home_data_list: List[Dict],
char_detailed_dict: dict = None,
mys_data: Optional[List[Dict]] = None,
nickname: Optional[str] = None,
) -> MessageSegment:
"""
生成图片数据
:param user_id:用户qq
:param uid: 原神uid
:param char_data_list: 角色列表
:param role_data: 玩家数据
:param world_data_dict: 国家数据字典
:param home_data_list: 家园列表
:param char_detailed_dict: 角色武器字典
:param mys_data: 用户米游社数据
:param nickname: 用户昵称
"""
user_ava = BytesIO(await get_user_avatar(user_id))
return await asyncio.get_event_loop().run_in_executor(
None,
_get_genshin_image,
uid,
char_data_list,
role_data,
world_data_dict,
home_data_list,
char_detailed_dict,
mys_data,
nickname,
user_ava,
)
def _get_genshin_image(
uid: str,
char_data_list: List[Dict],
role_data: Dict,
world_data_dict: Dict,
home_data_list: List[Dict],
char_detailed_dict: dict = None,
mys_data: Optional[Dict] = None,
nickname: Optional[str] = None,
user_ava: Optional[BytesIO] = None,
) -> MessageSegment:
"""
生成图片数据
:param uid: 原神uid
:param char_data_list: 角色列表
:param role_data: 玩家数据
:param world_data_dict: 国家数据字典
:param home_data_list: 家园列表
:param char_detailed_dict: 角色武器字典
:param mys_data: 用户米游社数据
:param nickname: 用户昵称
:param user_ava:用户头像
"""
x = 450 if char_detailed_dict else 330
char_height = (
len(char_data_list) / 7
if len(char_data_list) % 7 == 0
else len(char_data_list) / 7 + 1
)
foot = BuildImage(1700, 87, background=image_path / "head.png")
head = BuildImage(1700, 87, background=image_path / "head.png")
head.rotate(180)
middle = BuildImage(
1700, int(1600 + 200 + x * char_height), background=image_path / "middle.png"
)
A = BuildImage(middle.w, middle.h + foot.h + head.h)
A.paste(head, (-5, 0), True)
A.paste(middle, (0, head.h), True)
A.paste(foot, (0, head.h + middle.h), True)
A.crop((0, 0, A.w - 5, A.h))
user_image = get_user_data_image(uid, role_data, mys_data, nickname, user_ava)
home_image = get_home_data_image(home_data_list)
country_image = get_country_data_image(world_data_dict)
char_image, _h = get_char_data_image(char_data_list, char_detailed_dict)
top_bk = BuildImage(user_image.w, user_image.h + home_image.h + 100, color="#F9F6F2")
top_bk.paste(user_image, alpha=True)
top_bk.paste(home_image, (0, user_image.h + 50), alpha=True)
top_bk.paste(country_image, (home_image.w + 100, user_image.h + 50), alpha=True)
bar = BuildImage(1600, 200, font_size=50, color="#F9F6F2", font="HYWenHei-85W.ttf")
bar.text((50, 10), "角色背包", (104, 103, 101))
bar.line((50, 90, 1550, 90), (227, 219, 209), width=10)
if A.h - top_bk.h - bar.h - _h > 200:
_h = A.h - top_bk.h - bar.h - _h - 200
A.crop((0, 0, A.w, A.h - _h))
A.paste(foot, (0, A.h - 87))
A.paste(top_bk, (0, 100), center_type="by_width")
A.paste(bar, (50, top_bk.h + 80))
A.paste(char_image, (0, top_bk.h + bar.h + 10), center_type="by_width")
rand = random.randint(1, 10000)
A.save(Path(IMAGE_PATH) / "temp" / f"genshin_user_card_{rand}.png")
return image(f"genshin_user_card_{rand}.png", "temp")
def get_user_data_image(
uid: str,
role_data: Dict,
mys_data: Optional[Dict] = None,
nickname: Optional[str] = None,
user_ava: Optional[BytesIO] = None,
) -> BuildImage:
"""
画出玩家基本数据
:param uid: 原神uid
:param role_data: 玩家数据
:param mys_data: 玩家米游社数据
:param nickname: 用户昵称
:param user_ava:用户头像
"""
if mys_data:
nickname = [x["nickname"] for x in mys_data if x["game_id"] == 2][0]
region = BuildImage(1440, 450, color="#E3DBD1", font="HYWenHei-85W.ttf")
region.circle_corner(30)
uname_img = BuildImage(
0,
0,
plain_text=nickname,
font_size=40,
color=(255, 255, 255, 0),
font="HYWenHei-85W.ttf",
)
uid_img = BuildImage(
0,
0,
plain_text=f"UID: {uid}",
font_size=25,
color=(255, 255, 255, 0),
font="HYWenHei-85W.ttf",
font_color=(21, 167, 89),
)
ava_bk = BuildImage(270, 270, background=image_path / "cover.png")
# 用户头像
if user_ava:
ava_img = BuildImage(200, 200, background=user_ava)
ava_img.circle()
ava_bk.paste(ava_img, alpha=True, center_type="center")
else:
ava_img = BuildImage(
245,
245,
background=image_path
/ "chars_ava"
/ random.choice(os.listdir(image_path / "chars_ava")),
)
ava_bk.paste(ava_img, (12, 16), alpha=True)
region.paste(uname_img, (int(170 + uid_img.w / 2 - uname_img.w / 2), 305), True)
region.paste(uid_img, (170, 355), True)
region.paste(ava_bk, (int(550 / 2 - ava_bk.w / 2), 40), True)
data_img = BuildImage(
800, 400, color="#E3DBD1", font="HYWenHei-85W.ttf", font_size=40
)
_height = 0
keys = [
["活跃天数", "成就达成", "获得角色", "深境螺旋"],
["华丽宝箱", "珍贵宝箱", "精致宝箱", "普通宝箱"],
["奇馈宝箱", "风神瞳", "岩神瞳", "雷神瞳"],
]
values = [
[
role_data["active_day_number"],
role_data["achievement_number"],
role_data["avatar_number"],
role_data["spiral_abyss"],
],
[
role_data["luxurious_chest_number"],
role_data["precious_chest_number"],
role_data["exquisite_chest_number"],
role_data["common_chest_number"],
],
[
role_data["magic_chest_number"],
role_data["anemoculus_number"],
role_data["geoculus_number"],
role_data["electroculus_number"],
],
]
for key, value in zip(keys, values):
_tmp_data_img = BuildImage(
800, 200, color="#E3DBD1", font="HYWenHei-85W.ttf", font_size=40
)
_width = 10
for k, v in zip(key, value):
t_ = BuildImage(
0,
0,
plain_text=k,
color=(255, 255, 255, 0),
font_color=(138, 143, 143),
font="HYWenHei-85W.ttf",
font_size=30,
)
tmp_ = BuildImage(
t_.w, t_.h + 70, color="#E3DBD1", font="HYWenHei-85W.ttf", font_size=40
)
tmp_.text((0, 0), str(v), center_type="by_width")
tmp_.paste(t_, (0, 50), True, "by_width")
_tmp_data_img.paste(tmp_, (_width if len(key) > 3 else _width + 15, 0))
_width += 200
data_img.paste(_tmp_data_img, (0, _height))
_height += _tmp_data_img.h - 70
region.paste(data_img, (510, 50))
return region
def get_home_data_image(home_data_list: List[Dict]) -> BuildImage:
"""
画出家园数据
:param home_data_list: 家园列表
"""
region = BuildImage(
550, 1050, color="#E3DBD1", font="HYWenHei-85W.ttf", font_size=40
)
try:
region.text(
(0, 30), f'尘歌壶 Lv.{home_data_list[0]["level"]}', center_type="by_width"
)
region.text(
(0, 980), f'仙力: {home_data_list[0]["comfort_num"]}', center_type="by_width"
)
except (IndexError, KeyError):
region.text((0, 30), f"尘歌壶 Lv.0", center_type="by_width")
region.text((0, 980), f"仙力: 0", center_type="by_width")
region.circle_corner(30)
height = 100
homes = os.listdir(image_path / "homes")
homes.remove("lock.png")
homes.sort()
unlock_home = [x["name"] for x in home_data_list]
for i, file in enumerate(homes):
home_img = image_path / "homes" / file
x = BuildImage(500, 250, background=home_img)
if file.split(".")[0] not in unlock_home:
black_img = BuildImage(500, 250, color="black")
lock_img = BuildImage(0, 0, background=image_path / "homes" / "lock.png")
black_img.circle_corner(50)
black_img.transparent(1)
black_img.paste(lock_img, alpha=True, center_type="center")
x.paste(black_img, alpha=True)
else:
black_img = BuildImage(
500, 150, color="black", font="HYWenHei-85W.ttf", font_size=40
)
black_img.text((55, 55), file.split(".")[0], fill=(226, 211, 146))
black_img.transparent(1)
text_img = BuildImage(
0,
0,
plain_text="洞天等级",
font="HYWenHei-85W.ttf",
font_color=(203, 200, 184),
font_size=35,
color=(255, 255, 255, 0),
)
level_img = BuildImage(
0,
0,
plain_text=f'{home_data_list[0]["comfort_level_name"]}',
font="HYWenHei-85W.ttf",
font_color=(211, 213, 207),
font_size=30,
color=(255, 255, 255, 0),
)
black_img.paste(text_img, (270, 25), True)
black_img.paste(level_img, (278, 85), True)
x.paste(black_img, alpha=True, center_type="center")
x.circle_corner(50)
region.paste(x, (0, height), True, "by_width")
height += 300
return region
def get_country_data_image(world_data_dict: Dict) -> BuildImage:
"""
画出国家探索供奉等图像
:param world_data_dict: 国家数据字典
"""
region = BuildImage(790, 1050, color="#F9F6F2")
height = 0
for country in ["蒙德", "龙脊雪山", "璃月", "稻妻"]:
x = BuildImage(790, 250, color="#3A4467")
logo = BuildImage(180, 180, background=image_path / "logo" / f"{country}.png")
tmp_bk = BuildImage(770, 230, color="#606779")
tmp_bk.circle_corner(10)
content_bk = BuildImage(
755, 215, color="#3A4467", font_size=40, font="HYWenHei-85W.ttf"
)
content_bk.paste(logo, (50, 0), True, "by_height")
if country in ["蒙德", "璃月"]:
content_bk.text((300, 40), "探索", fill=(239, 211, 114))
content_bk.text(
(450, 40),
f"{world_data_dict[country]['exploration_percentage'] / 10}%",
fill=(255, 255, 255),
)
content_bk.text((300, 120), "声望", fill=(239, 211, 114))
content_bk.text(
(450, 120),
f"Lv.{world_data_dict[country]['level']}",
fill=(255, 255, 255),
)
elif country in ["龙脊雪山"]:
content_bk.text((300, 40), "探索", fill=(239, 211, 114))
content_bk.text(
(450, 40),
f"{world_data_dict[country]['exploration_percentage'] / 10}%",
fill=(255, 255, 255),
)
content_bk.text((300, 120), "供奉", fill=(239, 211, 114))
content_bk.text(
(450, 120),
f"Lv.{world_data_dict[country]['offerings'][0]['level']}",
fill=(255, 255, 255),
)
elif country in ["稻妻"]:
content_bk.text((300, 20), "探索", fill=(239, 211, 114))
content_bk.text(
(450, 20),
f"{world_data_dict[country]['exploration_percentage'] / 10}%",
fill=(255, 255, 255),
)
content_bk.text((300, 85), "声望", fill=(239, 211, 114))
content_bk.text(
(450, 85),
f"Lv.{world_data_dict[country]['level']}",
fill=(255, 255, 255),
)
content_bk.text((300, 150), "神樱", fill=(239, 211, 114))
content_bk.text(
(450, 150),
f"Lv.{world_data_dict[country]['offerings'][0]['level']}",
fill=(255, 255, 255),
)
x.paste(tmp_bk, alpha=True, center_type="center")
x.paste(content_bk, alpha=True, center_type="center")
x.circle_corner(20)
region.paste(x, (0, height), center_type="by_width")
height += 267
return region
def get_char_data_image(
char_data_list: List[Dict], char_detailed_dict: dict
) -> "BuildImage, int":
"""
画出角色列表
:param char_data_list: 角色列表
:param char_detailed_dict: 角色武器
"""
x = 420 if char_detailed_dict else 350
_h = x * int(
len(char_data_list) / 7
if len(char_data_list) % 7 == 0
else len(char_data_list) / 7 + 1
)
region = BuildImage(
1600,
_h,
color="#F9F6F2",
)
width = 120
height = 0
idx = 0
for char in char_data_list:
if width + 230 > 1550:
width = 120
height += x
idx += 1
char_img = image_path / "chars" / f'{char["name"]}.png'
char_bk = BuildImage(
270,
500 if char_detailed_dict else 400,
background=image_path / "element.png",
font="HYWenHei-85W.ttf",
font_size=35,
)
char_img = BuildImage(0, 0, background=char_img)
actived_constellation_num = BuildImage(
0,
0,
plain_text=f"命之座: {char['actived_constellation_num']}层",
font="HYWenHei-85W.ttf",
font_size=25,
color=(255, 255, 255, 0),
)
level = BuildImage(
0,
0,
plain_text=f"Lv.{char['level']}",
font="HYWenHei-85W.ttf",
font_size=30,
color=(255, 255, 255, 0),
font_color=(21, 167, 89),
)
love_log = BuildImage(
0,
0,
plain_text="♥",
font="HWZhongSong.ttf",
font_size=40,
color=(255, 255, 255, 0),
font_color=(232, 31, 168),
)
fetter = BuildImage(
0,
0,
plain_text=f'{char["fetter"]}',
font="HYWenHei-85W.ttf",
font_size=30,
color=(255, 255, 255, 0),
font_color=(232, 31, 168),
)
if char_detailed_dict.get(char["name"]):
weapon = BuildImage(
100,
100,
background=image_path
/ "weapons"
/ f'{char_detailed_dict[char["name"]]["weapon"]}.png',
)
weapon_name = BuildImage(
0,
0,
plain_text=f"{char_detailed_dict[char['name']]['weapon']}",
font="HYWenHei-85W.ttf",
font_size=25,
color=(255, 255, 255, 0),
)
weapon_affix_level = BuildImage(
0,
0,
plain_text=f"精炼: {char_detailed_dict[char['name']]['affix_level']}",
font="HYWenHei-85W.ttf",
font_size=20,
color=(255, 255, 255, 0),
)
weapon_level = BuildImage(
0,
0,
plain_text=f"Lv.{char_detailed_dict[char['name']]['level']}",
font="HYWenHei-85W.ttf",
font_size=25,
color=(255, 255, 255, 0),
font_color=(21, 167, 89),
)
char_bk.paste(weapon, (20, 380), True)
char_bk.paste(
weapon_name,
(100 + int((char_bk.w - 22 - weapon.w - weapon_name.w) / 2 - 10), 390),
True,
)
char_bk.paste(
weapon_affix_level,
(
(
100
+ int(
(char_bk.w - 10 - weapon.w - weapon_affix_level.w) / 2 - 10
),
420,
)
),
True,
)
char_bk.paste(
weapon_level,
(
(
100
+ int((char_bk.w - 10 - weapon.w - weapon_level.w) / 2 - 10),
450,
)
),
True,
)
char_bk.paste(char_img, (0, 5), alpha=True, center_type="by_width")
char_bk.text((0, 270), char["name"], center_type="by_width")
char_bk.paste(actived_constellation_num, (0, 310), True, "by_width")
char_bk.paste(level, (60, 340), True)
char_bk.paste(love_log, (155, 330), True)
char_bk.paste(fetter, (180, 340), True)
char_bk.resize(0.8)
region.paste(char_bk, (width, height), True)
width += 230
return region, _h
async def init_image(char_data_list: List[Dict], char_detailed_dict: dict):
"""
下载头像
:param char_data_list: 角色列表
:param char_detailed_dict: 角色武器
"""
for char in char_data_list:
file = image_path / "chars" / f'{char["name"]}.png'
file.parent.mkdir(parents=True, exist_ok=True)
if not file.exists():
await AsyncHttpx.download_file(char["image"], file)
for char in char_detailed_dict.keys():
file = image_path / "weapons" / f'{char_detailed_dict[char]["weapon"]}.png'
file.parent.mkdir(parents=True, exist_ok=True)
if not file.exists():
await AsyncHttpx.download_file(
char_detailed_dict[char]["weapon_image"], file
)
@@ -0,0 +1,17 @@
from utils.utils import scheduler
from ..models import Genshin
from services.log import logger
@scheduler.scheduled_job(
"cron",
hour=0,
minute=1,
)
async def _():
try:
await Genshin.reset_today_query_uid()
logger.warning(f"重置原神查询记录成功..")
except Exception as e:
logger.error(f"重置原神查询记录失败. {type(e)}:{e}")
@@ -0,0 +1,35 @@
from configs.config import Config
import json
import time
import random
import hashlib
def _md5(text):
md5 = hashlib.md5()
md5.update(text.encode())
return md5.hexdigest()
def get_ds(q: str = "", b: dict = None):
if b:
br = json.dumps(b)
else:
br = ""
s = Config.get_config("genshin", "salt")
t = str(int(time.time()))
r = str(random.randint(100000, 200000))
c = _md5("salt=" + s + "&t=" + t + "&r=" + r + "&b=" + br + "&q=" + q)
return t + "," + r + "," + c
element_mastery = {
"anemo": "风",
"pyro": "火",
"geo": "岩",
"electro": "雷",
"cryo": "冰",
"hydro": "水",
"dendro": "草",
"none": "无",
}