diff --git a/core/config.py b/core/config.py
index 657c2e14..d0c6ec9a 100644
--- a/core/config.py
+++ b/core/config.py
@@ -22,21 +22,21 @@ dotenv.load_dotenv()
class BotConfig(BaseSettings):
debug: bool = False
- db_host: str
- db_port: int
- db_username: str
- db_password: str
- db_database: str
+ db_host: str = ""
+ db_port: int = 0
+ db_username: str = ""
+ db_password: str = ""
+ db_database: str = ""
- redis_host: str
- redis_port: int
- redis_db: int
+ redis_host: str = ""
+ redis_port: int = 0
+ redis_db: int = 0
- bot_token: str
- error_notification_chat_id: str
+ bot_token: str = ""
+ error_notification_chat_id: str = ""
- api_id: Optional[int]
- api_hash: Optional[str]
+ api_id: Optional[int] = None
+ api_hash: Optional[str] = None
channels: List["ConfigChannel"] = []
admins: List["ConfigUser"] = []
diff --git a/metadata/shortname.py b/metadata/shortname.py
index f5a84d03..14094657 100644
--- a/metadata/shortname.py
+++ b/metadata/shortname.py
@@ -9,7 +9,7 @@ __all__ = ["roles", "weapons", "roleToId", "roleToName", "weaponToName", "weapon
# noinspection SpellCheckingInspection
roles = {
20000000: ["主角", "旅行者", "卑鄙的外乡人", "荣誉骑士", "爷", "风主", "岩主", "雷主", "草主", "履刑者", "抽卡不歪真君"],
- 10000002: ["神里绫华", "Ayaka", "ayaka", "Kamisato Ayaka", "神里", "绫华", "神里凌华", "凌华", "白鹭公主", "神里大小 姐"],
+ 10000002: ["神里绫华", "Ayaka", "ayaka", "Kamisato Ayaka", "神里", "绫华", "神里凌华", "凌华", "白鹭公主", "神里大小姐"],
10000003: ["琴", "Jean", "jean", "团长", "代理团长", "琴团长", "蒲公英骑士"],
10000005: ["空", "Aether", "aether", "男主", "男主角", "龙哥", "空哥"],
10000006: ["丽莎", "Lisa", "lisa", "图书管理员", "图书馆管理员", "蔷薇魔女"],
diff --git a/modules/apihelper/hyperion.py b/modules/apihelper/hyperion.py
index 4e8d0952..bb1ed0d1 100644
--- a/modules/apihelper/hyperion.py
+++ b/modules/apihelper/hyperion.py
@@ -1,13 +1,18 @@
import asyncio
import re
import time
-from typing import List
+from json import JSONDecodeError
+from typing import List, Optional
+from genshin import Client, InvalidCookies
+from genshin.utility.uid import recognize_genshin_server
+from genshin.utility.ds import generate_dynamic_secret
from httpx import AsyncClient
from modules.apihelper.base import ArtworkImage, PostInfo
from modules.apihelper.helpers import get_device_id
from modules.apihelper.request.hoyorequest import HOYORequest
+from utils.log import logger
from utils.typedefs import JSONDict
@@ -169,7 +174,7 @@ class GachaInfo:
class SignIn:
LOGIN_URL = "https://webapi.account.mihoyo.com/Api/login_by_mobilecaptcha"
S_TOKEN_URL = (
- "https://api-takumi.mihoyo.com/auth/api/getMultiTokenByLoginTicket?" "login_ticket={0}&token_types=3&uid={1}"
+ "https://api-takumi.mihoyo.com/auth/api/getMultiTokenByLoginTicket?login_ticket={0}&token_types=3&uid={1}"
)
BBS_URL = "https://api-takumi.mihoyo.com/account/auth/api/webLoginByMobile"
USER_AGENT = (
@@ -209,8 +214,21 @@ class SignIn:
"Referer": "https://bbs.mihoyo.com/",
"Accept-Language": "zh-CN,zh-Hans;q=0.9",
}
+ AUTHKEY_API = "https://api-takumi.mihoyo.com/binding/api/genAuthKey"
+ GACHA_HEADERS = {
+ "User-Agent": "okhttp/4.8.0",
+ "x-rpc-app_version": "2.28.1",
+ "x-rpc-sys_version": "12",
+ "x-rpc-client_type": "5",
+ "x-rpc-channel": "mihoyo",
+ "x-rpc-device_id": get_device_id(USER_AGENT),
+ "x-rpc-device_name": "Mi 10",
+ "x-rpc-device_model": "Mi 10",
+ "Referer": "https://app.mihoyo.com",
+ "Host": "api-takumi.mihoyo.com",
+ }
- def __init__(self, phone: int):
+ def __init__(self, phone: int = 0):
self.phone = phone
self.client = AsyncClient()
self.uid = 0
@@ -287,3 +305,27 @@ class SignIn:
self.cookie[k] = v
return "cookie_token" in self.cookie
+
+ @staticmethod
+ async def get_authkey_by_stoken(client: Client) -> Optional[str]:
+ """通过 stoken 获取 authkey"""
+ try:
+ headers = SignIn.GACHA_HEADERS.copy()
+ headers["DS"] = generate_dynamic_secret("ulInCDohgEs557j0VsPDYnQaaz6KJcv5")
+ data = await client.cookie_manager.request(
+ SignIn.AUTHKEY_API,
+ method="POST",
+ json={
+ "auth_appid": "webview_gacha",
+ "game_biz": "hk4e_cn",
+ "game_uid": client.uid,
+ "region": recognize_genshin_server(client.uid),
+ },
+ headers=headers,
+ )
+ return data.get("authkey")
+ except JSONDecodeError:
+ logger.warning("Stoken 获取 Authkey JSON解析失败")
+ except InvalidCookies:
+ logger.warning("Stoken 获取 Authkey 失败 | 用户 Stoken 失效")
+ return None
diff --git a/plugins/genshin/gacha/gacha_log.py b/plugins/genshin/gacha/gacha_log.py
index b01ace1c..0be759e0 100644
--- a/plugins/genshin/gacha/gacha_log.py
+++ b/plugins/genshin/gacha/gacha_log.py
@@ -1,4 +1,6 @@
import json
+import genshin
+
from io import BytesIO
from genshin.models import BannerType
@@ -9,17 +11,20 @@ from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filter
from core.base.assets import AssetsService
from core.baseplugin import BasePlugin
from core.cookies.error import CookiesNotFoundError
+from core.cookies import CookiesService
from core.plugin import Plugin, handler, conversation
from core.template import TemplateService
from core.user import UserService
from core.user.error import UserNotFoundError
from modules.apihelper.gacha_log import GachaLog as GachaLogService
+from modules.apihelper.hyperion import SignIn
from utils.bot import get_all_args
from utils.decorators.admins import bot_admins_rights_check
from utils.decorators.error import error_callable
from utils.decorators.restricts import restricts
from utils.helpers import get_genshin_client
from utils.log import logger
+from utils.models.base import RegionEnum
INPUT_URL, INPUT_FILE, CONFIRM_DELETE = range(10100, 10103)
@@ -28,11 +33,16 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
"""抽卡记录导入/导出/分析"""
def __init__(
- self, template_service: TemplateService = None, user_service: UserService = None, assets: AssetsService = None
+ self,
+ template_service: TemplateService = None,
+ user_service: UserService = None,
+ assets: AssetsService = None,
+ cookie_service: CookiesService = None,
):
self.template_service = template_service
self.user_service = user_service
self.assets_service = assets
+ self.cookie_service = cookie_service
@staticmethod
def from_url_get_authkey(url: str) -> str:
@@ -78,7 +88,8 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
data = data.getvalue().decode("utf-8")
data = json.loads(data)
except Exception as exc:
- logger.error(f"文件解析失败:{repr(exc)}")
+ if not isinstance(exc, UnicodeDecodeError):
+ logger.error(f"文件解析失败:{repr(exc)}")
await message.reply_text("文件解析失败,请检查文件是否符合 UIGF 标准")
return
await message.reply_chat_action(ChatAction.TYPING)
@@ -101,6 +112,7 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
user = update.effective_user
args = get_all_args(context)
logger.info(f"用户 {user.full_name}[{user.id}] 导入抽卡记录命令请求")
+ authkey = self.from_url_get_authkey(args[0] if args else "")
if not args:
if message.document:
await self.import_from_file(user, message)
@@ -108,18 +120,46 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
elif message.reply_to_message and message.reply_to_message.document:
await self.import_from_file(user, message, document=message.reply_to_message.document)
return ConversationHandler.END
+ try:
+ user_info = await self.user_service.get_user_by_id(user.id)
+ except UserNotFoundError:
+ user_info = None
+ if user_info and user_info.region == RegionEnum.HYPERION:
+ try:
+ cookies = await self.cookie_service.get_cookies(user_info.user_id, user_info.region)
+ except CookiesNotFoundError:
+ cookies = None
+ if cookies and cookies.cookies and "stoken" in cookies.cookies:
+ if stuid := next(
+ (value for key, value in cookies.cookies.items() if key in ["ltuid", "login_uid"]), None
+ ):
+ cookies.cookies["stuid"] = stuid
+ client = genshin.Client(
+ cookies=cookies.cookies,
+ game=genshin.types.Game.GENSHIN,
+ region=genshin.Region.CHINESE,
+ lang="zh-cn",
+ uid=user_info.yuanshen_uid,
+ )
+ authkey = await SignIn.get_authkey_by_stoken(client)
+ if not authkey:
await message.reply_text(
- "导入祈愿历史记录\n\n"
- "1.请发送从其他工具导出的 UIGF JSON 标准的记录文件\n"
- "2.你还可以向派蒙发送从游戏中获取到的抽卡记录链接\n\n"
- "注意:导入的数据将会与旧数据进行合并。\n"
- "获取抽卡记录链接可以参考:https://paimon.moe/wish/import",
+ "开始导入祈愿历史记录:请通过 https://paimon.moe/wish/import 获取抽卡记录链接后发送给我"
+ "(非 paimon.moe 导出的文件数据)\n\n"
+ "> 你还可以向派蒙发送从其他工具导出的 UIGF JSON 标准的记录文件\n"
+ "> 在绑定 Cookie 时添加 stoken 可能有特殊效果哦(仅限国服)\n"
+ "注意:导入的数据将会与旧数据进行合并。",
parse_mode="html",
)
return INPUT_URL
- authkey = self.from_url_get_authkey(args[0])
+ text = "小派蒙正在从米哈游服务器获取数据,请稍后"
+ if not args:
+ text += "\n\n> 由于你绑定的 Cookie 中存在 stoken ,本次通过 stoken 自动刷新数据"
+ reply = await message.reply_text(text)
+ await message.reply_chat_action(ChatAction.TYPING)
data = await self._refresh_user_data(user, authkey=authkey)
- await message.reply_text(data)
+ await reply.edit_text(data)
+ return ConversationHandler.END
@conversation.state(state=INPUT_URL)
@handler.message(filters=~filters.COMMAND, block=False)
@@ -133,6 +173,7 @@ class GachaLog(Plugin.Conversation, BasePlugin.Conversation):
return ConversationHandler.END
authkey = self.from_url_get_authkey(message.text)
reply = await message.reply_text("小派蒙正在从米哈游服务器获取数据,请稍后")
+ await message.reply_chat_action(ChatAction.TYPING)
text = await self._refresh_user_data(user, authkey=authkey)
await reply.edit_text(text)
return ConversationHandler.END
diff --git a/plugins/genshin/sign.py b/plugins/genshin/sign.py
index 0aa4e76f..d465cac5 100644
--- a/plugins/genshin/sign.py
+++ b/plugins/genshin/sign.py
@@ -100,14 +100,14 @@ class Sign(Plugin, BasePlugin):
"x-rpc-seccode": f'{data["data"]["validate"]}|jordan',
}
except JSONDecodeError:
- logger.warning("签到ajax自动通过JSON解析失败")
+ logger.warning("签到 ajax 请求 JSON 解析失败")
except TimeoutException:
- logger.warning("签到ajax自动通过请求超时")
+ logger.warning("签到 ajax 请求超时")
except (KeyError, IndexError):
- logger.warning("签到ajax自动通过数据错误")
+ logger.warning("签到 ajax 请求数据错误")
except RuntimeError:
- logger.warning("签到ajax自动通过请求错误")
- logger.warning("ajax自动通过失败")
+ logger.warning("签到 ajax 请求错误")
+ logger.warning("签到 ajax 请求失败")
if not config.pass_challenge_api:
return None
pass_challenge_params = {
@@ -125,12 +125,11 @@ class Sign(Plugin, BasePlugin):
params=pass_challenge_params,
timeout=45,
)
- logger.info(f"签到自定义打码平台返回:{resp.text}")
+ logger.info(f"签到请求返回:{resp.text}")
data = resp.json()
status = data.get("status")
- if status is not None:
- if status != 0:
- logger.error(f"签到自定义打码平台解析错误:{data.get('msg')}")
+ if status is not None and status != 0:
+ logger.error(f"签到请求解析错误:{data.get('msg')}")
if data.get("code", 0) != 0:
raise RuntimeError
return {
@@ -139,13 +138,13 @@ class Sign(Plugin, BasePlugin):
"x-rpc-seccode": f'{data["data"]["validate"]}|jordan',
}
except JSONDecodeError:
- logger.warning("签到自定义打码平台JSON解析失败")
+ logger.warning("签到请求 JSON 解析失败")
except TimeoutException:
- logger.warning("签到自定义打码平台请求超时")
+ logger.warning("签到请求超时")
except KeyError:
- logger.warning("签到自定义打码平台数据错误")
+ logger.warning("签到请求数据错误")
except RuntimeError:
- logger.warning("签到自定义打码平台自动通过失败")
+ logger.warning("签到请求失败")
return None
@staticmethod
@@ -166,7 +165,6 @@ class Sign(Plugin, BasePlugin):
"sign", method="POST", game=Game.GENSHIN, lang="zh-cn"
)
if request_daily_reward and request_daily_reward.get("success", 0) == 1:
- # 米游社国内签到自动打码
headers = await Sign.pass_challenge(
request_daily_reward.get("gt", ""),
request_daily_reward.get("challenge", ""),
@@ -184,7 +182,7 @@ class Sign(Plugin, BasePlugin):
if request_daily_reward and request_daily_reward.get("success", 0) == 1:
logger.warning(f"UID {client.uid} 签到失败,触发验证码风控")
return f"UID {client.uid} 签到失败,触发验证码风控,请尝试重新签到。"
- logger.info(f"UID {client.uid} 通过自动打码签到成功")
+ logger.info(f"UID {client.uid} 签到成功")
except AlreadyClaimed:
logger.info(f"UID {client.uid} 已经签到")
result = "今天旅行者已经签到过了~"
diff --git a/plugins/jobs/sign.py b/plugins/jobs/sign.py
index f8b8537e..a9940924 100644
--- a/plugins/jobs/sign.py
+++ b/plugins/jobs/sign.py
@@ -41,13 +41,12 @@ class SignJob(Plugin):
if not daily_reward_info.signed_in:
request_daily_reward = await client.request_daily_reward("sign", method="POST", game=Game.GENSHIN)
if request_daily_reward and request_daily_reward.get("success", 0) == 1:
- # 米游社国内签到自动打码
headers = await Sign.pass_challenge(
request_daily_reward.get("gt", ""),
request_daily_reward.get("challenge", ""),
)
if not headers:
- logger.warning(f"UID {client.uid} 签到失败,触发验证码风控 | 打码平台打码失败,请检查")
+ logger.warning(f"UID {client.uid} 签到失败,触发验证码风控")
raise NeedChallenge
request_daily_reward = await client.request_daily_reward(
"sign",
@@ -57,9 +56,9 @@ class SignJob(Plugin):
headers=headers,
)
if request_daily_reward and request_daily_reward.get("success", 0) == 1:
- logger.warning(f"UID {client.uid} 签到失败,触发验证码风控 | 打码平台打码失败,请检查")
+ logger.warning(f"UID {client.uid} 签到失败,触发验证码风控")
raise NeedChallenge
- logger.info(f"UID {client.uid} 签到请求 {request_daily_reward} | 通过自动打码签到成功")
+ logger.info(f"UID {client.uid} 签到请求 {request_daily_reward} | 签到成功")
else:
logger.info(f"UID {client.uid} 签到请求 {request_daily_reward}")
result = "OK"
diff --git a/utils/log/_logger.py b/utils/log/_logger.py
index 44b5cbb4..0f999329 100644
--- a/utils/log/_logger.py
+++ b/utils/log/_logger.py
@@ -35,7 +35,7 @@ from rich.traceback import (
)
from ujson import JSONDecodeError
-from core.config import BotConfig
+from core.config import config
from utils.const import NOT_SET, PROJECT_ROOT
from utils.log._file import FileIO
from utils.log._style import (
@@ -64,7 +64,6 @@ __initialized__ = False
FormatTimeCallable = Callable[[datetime], Text]
-config = BotConfig()
logging.addLevelName(5, "TRACE")
logging.addLevelName(25, "SUCCESS")
color_system: Literal["windows", "truecolor"]