diff --git a/core/bot.py b/core/bot.py index 4d43cee..9c19d85 100644 --- a/core/bot.py +++ b/core/bot.py @@ -24,6 +24,7 @@ from telegram.ext.filters import StatusUpdate from core.config import BotConfig, config # pylint: disable=W0611 from core.error import ServiceNotFoundError + # noinspection PyProtectedMember from core.plugin import Plugin, _Plugin from core.service import Service @@ -275,7 +276,7 @@ class Bot: def find_service(self, target: Type[T]) -> T: """查找服务。若没找到则抛出 ServiceNotFoundError""" - if result := self._services.get(target) is None: + if (result := self._services.get(target)) is None: raise ServiceNotFoundError(target) return result diff --git a/core/config.py b/core/config.py index db17f44..0a42ab4 100644 --- a/core/config.py +++ b/core/config.py @@ -116,6 +116,7 @@ class BotConfig(Settings): enka_network_api_agent: str = "" pass_challenge_api: str = "" pass_challenge_app_key: str = "" + pass_challenge_user_web: str = "" mysql: MySqlConfig = MySqlConfig() logger: LoggerConfig = LoggerConfig() diff --git a/plugins/genshin/sign.py b/plugins/genshin/sign.py index 1484711..3e92c47 100644 --- a/plugins/genshin/sign.py +++ b/plugins/genshin/sign.py @@ -3,7 +3,7 @@ import json import re import time from json import JSONDecodeError -from typing import Optional, Dict +from typing import Optional, Dict, Tuple from genshin import Game, GenshinException, AlreadyClaimed, Client from httpx import AsyncClient, TimeoutException @@ -13,7 +13,9 @@ from telegram.ext import CommandHandler, CallbackContext from telegram.ext import MessageHandler, filters from core.admin.services import BotAdminService +from core.base.redisdb import RedisDB from core.baseplugin import BasePlugin +from core.bot import bot from core.config import config from core.cookies.error import CookiesNotFoundError from core.cookies.services import CookiesService @@ -29,6 +31,20 @@ from utils.helpers import get_genshin_client from utils.log import logger +class SignRedis: + client = bot.find_service(RedisDB).client + qname = "plugin:sign:" + + @staticmethod + async def get(uid: int) -> Optional[bytes]: + return await SignRedis.client.get(f"{SignRedis.qname}{uid}") + + @staticmethod + async def set(uid: int, challenge: str): + await SignRedis.client.set(f"{SignRedis.qname}{uid}", challenge) + await SignRedis.client.expire(f"{SignRedis.qname}{uid}", 10 * 60) + + class Sign(Plugin, BasePlugin): """每日签到""" @@ -153,21 +169,44 @@ class Sign(Plugin, BasePlugin): return None @staticmethod - async def _start_sign(client: Client) -> str: + async def gen_challenge_header(uid: int, validate: str) -> Optional[Dict]: + challenge = await SignRedis.get(uid) + if not challenge: + return + return { + "x-rpc-challenge": challenge.decode("utf-8"), + "x-rpc-validate": validate, + "x-rpc-seccode": f"{validate}|jordan", + } + + @staticmethod + async def gen_challenge_button(uid: int, gt: str, challenge: str): + if not config.pass_challenge_user_web: + return None + await SignRedis.set(uid, challenge) + url = f"{config.pass_challenge_user_web}?username={bot.app.bot.username}>={gt}&challenge={challenge}" + return InlineKeyboardMarkup([[InlineKeyboardButton("请尽快点我进行手动验证", url=url)]]) + + @staticmethod + async def start_sign(client: Client, headers: Dict = None) -> Tuple[str, Optional[InlineKeyboardMarkup]]: try: rewards = await client.get_monthly_rewards(game=Game.GENSHIN, lang="zh-cn") except GenshinException as error: logger.warning(f"UID {client.uid} 获取签到信息失败,API返回信息为 {str(error)}") - return f"获取签到信息失败,API返回信息为 {str(error)}" + return f"获取签到信息失败,API返回信息为 {str(error)}", None try: daily_reward_info = await client.get_reward_info(game=Game.GENSHIN, lang="zh-cn") # 获取签到信息失败 except GenshinException as error: logger.warning(f"UID {client.uid} 获取签到状态失败,API返回信息为 {str(error)}") - return f"获取签到状态失败,API返回信息为 {str(error)}" + return f"获取签到状态失败,API返回信息为 {str(error)}", None if not daily_reward_info.signed_in: try: request_daily_reward = await client.request_daily_reward( - "sign", method="POST", game=Game.GENSHIN, lang="zh-cn" + "sign", + method="POST", + game=Game.GENSHIN, + lang="zh-cn", + headers=headers, ) if request_daily_reward and request_daily_reward.get("success", 0) == 1: headers = await Sign.pass_challenge( @@ -175,8 +214,13 @@ class Sign(Plugin, BasePlugin): request_daily_reward.get("challenge", ""), ) if not headers: + button = await Sign.gen_challenge_button( + client.uid, + request_daily_reward.get("gt", ""), + request_daily_reward.get("challenge", ""), + ) logger.warning(f"UID {client.uid} 签到失败,触发验证码风控") - return f"UID {client.uid} 签到失败,触发验证码风控,请尝试重新签到。" + return f"UID {client.uid} 签到失败,触发验证码风控,请尝试重新签到。", button request_daily_reward = await client.request_daily_reward( "sign", method="POST", @@ -185,17 +229,22 @@ class Sign(Plugin, BasePlugin): headers=headers, ) if request_daily_reward and request_daily_reward.get("success", 0) == 1: + button = await Sign.gen_challenge_button( + client.uid, + request_daily_reward.get("gt", ""), + request_daily_reward.get("challenge", ""), + ) logger.warning(f"UID {client.uid} 签到失败,触发验证码风控") - return f"UID {client.uid} 签到失败,触发验证码风控,请尝试重新签到。" + return f"UID {client.uid} 签到失败,触发验证码风控,请尝试重新签到。", button logger.info(f"UID {client.uid} 签到成功") except TimeoutException: - return "签到失败了呜呜呜 ~ 服务器连接超时 服务器熟啦 ~ " + return "签到失败了呜呜呜 ~ 服务器连接超时 服务器熟啦 ~ ", None except AlreadyClaimed: logger.info(f"UID {client.uid} 已经签到") result = "今天旅行者已经签到过了~" except GenshinException as error: logger.warning(f"UID {client.uid} 签到失败,API返回信息为 {str(error)}") - return f"获取签到状态失败,API返回信息为 {str(error)}" + return f"获取签到状态失败,API返回信息为 {str(error)}", None else: logger.info(f"UID {client.uid} 签到成功") result = "OK" @@ -217,7 +266,7 @@ class Sign(Plugin, BasePlugin): f"本月漏签次数:{missed_days}\n" f"签到结果: {result}" ) - return message + return message, None async def _process_auto_sign(self, user_id: int, chat_id: int, method: str) -> str: try: @@ -256,6 +305,7 @@ class Sign(Plugin, BasePlugin): user = update.effective_user message = update.effective_message args = get_all_args(context) + validate = None if len(args) >= 1: msg = None if args[0] == "开启自动签到": @@ -266,6 +316,8 @@ class Sign(Plugin, BasePlugin): msg = await self._process_auto_sign(user.id, user.id, "开启") elif args[0] == "关闭自动签到": msg = await self._process_auto_sign(user.id, message.chat_id, "关闭") + else: + validate = args[0] if msg: logger.info(f"用户 {user.full_name}[{user.id}] 自动签到命令请求 || 参数 {args[0]}") reply_message = await message.reply_text(msg) @@ -278,9 +330,10 @@ class Sign(Plugin, BasePlugin): self._add_delete_message_job(context, message.chat_id, message.message_id) try: client = await get_genshin_client(user.id) + headers = await Sign.gen_challenge_header(client.uid, validate) await message.reply_chat_action(ChatAction.TYPING) - sign_text = await self._start_sign(client) - reply_message = await message.reply_text(sign_text, allow_sending_without_reply=True) + sign_text, button = await self.start_sign(client, headers) + reply_message = await message.reply_text(sign_text, allow_sending_without_reply=True, reply_markup=button) if filters.ChatType.GROUPS.filter(reply_message): self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id) except (UserNotFoundError, CookiesNotFoundError): diff --git a/plugins/system/start.py b/plugins/system/start.py index db35c8f..f5647e5 100644 --- a/plugins/system/start.py +++ b/plugins/system/start.py @@ -1,9 +1,16 @@ +import contextlib + from telegram import Update, ReplyKeyboardRemove +from telegram.constants import ChatAction from telegram.ext import CallbackContext, CommandHandler from telegram.helpers import escape_markdown +from core.cookies.error import CookiesNotFoundError from core.plugin import handler, Plugin +from core.user.error import UserNotFoundError +from plugins.genshin.sign import Sign from utils.decorators.restricts import restricts +from utils.helpers import get_genshin_client class StartPlugin(Plugin): @@ -29,11 +36,25 @@ class StartPlugin(Plugin): f"你好 {user.mention_markdown_v2()} {escape_markdown('!我是派蒙 !')}\n" f"{escape_markdown('发送 /setuid 或 /setcookie 命令进入绑定账号流程')}" ) + elif args[0].startswith("challenge_"): + await StartPlugin.process_sign_validate(update, args[0][10:]) else: await message.reply_html(f"你好 {user.mention_html()} !我是派蒙 !\n请点击 /{args[0]} 命令进入对应流程") return await message.reply_markdown_v2(f"你好 {user.mention_markdown_v2()} {escape_markdown('!我是派蒙 !')}") + @staticmethod + async def process_sign_validate(update: Update, validate: str): + with contextlib.suppress(UserNotFoundError, CookiesNotFoundError): + client = await get_genshin_client(update.effective_user.id) + await update.effective_message.reply_chat_action(ChatAction.TYPING) + headers = await Sign.gen_challenge_header(update.effective_user.id, validate) + if not headers: + await update.effective_message.reply_text("验证请求已过期。", allow_sending_without_reply=True) + return + sign_text, button = await Sign.start_sign(client, headers) + await update.effective_message.reply_text(sign_text, allow_sending_without_reply=True, reply_markup=button) + @staticmethod @restricts() async def unknown_command(update: Update, _: CallbackContext) -> None: