From 410dad1a03c6da4aba17da17e08b42add99ae30a Mon Sep 17 00:00:00 2001 From: omg-xtao <100690902+omg-xtao@users.noreply.github.com> Date: Mon, 10 Oct 2022 13:34:06 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E7=BB=99=E7=AD=BE=E5=88=B0?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0ajax=E8=AF=B7=E6=B1=82=E6=88=96=E6=89=93?= =?UTF-8?q?=E7=A0=81=E5=B9=B3=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/config.py | 18 +++-- plugins/genshin/sign.py | 148 +++++++++++++++++++++++++++++++++---- plugins/jobs/sign.py | 87 ++++++++++++++-------- plugins/system/sign_all.py | 85 +++++++++++++++++++++ 4 files changed, 287 insertions(+), 51 deletions(-) create mode 100644 plugins/system/sign_all.py diff --git a/core/config.py b/core/config.py index 6ef6d52..657c2e1 100644 --- a/core/config.py +++ b/core/config.py @@ -12,7 +12,7 @@ from pydantic import ( BaseSettings, ) -__all__ = ['BotConfig', 'config'] +__all__ = ["BotConfig", "config"] from utils.const import PROJECT_ROOT @@ -38,17 +38,19 @@ class BotConfig(BaseSettings): api_id: Optional[int] api_hash: Optional[str] - channels: List['ConfigChannel'] = [] - admins: List['ConfigUser'] = [] + channels: List["ConfigChannel"] = [] + admins: List["ConfigUser"] = [] verify_groups: List[Union[int, str]] = [] logger_width: int = 180 - logger_log_path: str = './logs' + logger_log_path: str = "./logs" logger_time_format: str = "[%Y-%m-%d %X]" logger_traceback_max_frames: int = 20 - logger_render_keywords: List[str] = ['BOT'] + logger_render_keywords: List[str] = ["BOT"] enka_network_api_agent: str = "" + pass_challenge_api: str = "" + pass_challenge_app_key: str = "" class Config: case_sensitive = False @@ -110,7 +112,7 @@ class MySqlConfig(BaseModel): class RedisConfig(BaseModel): - host: str = '127.0.0.1' + host: str = "127.0.0.1" port: int database: int = 0 @@ -119,8 +121,8 @@ class LoggerConfig(BaseModel): width: int = 180 time_format: str = "[%Y-%m-%d %X]" traceback_max_frames: int = 20 - path: Path = PROJECT_ROOT / 'logs' - render_keywords: List[str] = ['BOT'] + path: Path = PROJECT_ROOT / "logs" + render_keywords: List[str] = ["BOT"] class MTProtoConfig(BaseModel): diff --git a/plugins/genshin/sign.py b/plugins/genshin/sign.py index 854a68b..343e6a0 100644 --- a/plugins/genshin/sign.py +++ b/plugins/genshin/sign.py @@ -1,13 +1,18 @@ import datetime import time +from json import JSONDecodeError +from typing import Optional, Dict from genshin import Game, GenshinException, AlreadyClaimed, Client +from httpx import AsyncClient, Timeout from telegram import Update +from telegram.constants import ChatAction from telegram.ext import CommandHandler, CallbackContext from telegram.ext import MessageHandler, filters from core.admin.services import BotAdminService from core.baseplugin import BasePlugin +from core.config import config from core.cookies.error import CookiesNotFoundError from core.cookies.services import CookiesService from core.plugin import Plugin, handler @@ -27,13 +32,105 @@ class Sign(Plugin, BasePlugin): CHECK_SERVER, COMMAND_RESULT = range(10400, 10402) - def __init__(self, user_service: UserService = None, cookies_service: CookiesService = None, - sign_service: SignServices = None, bot_admin_service: BotAdminService = None): + def __init__( + self, + user_service: UserService = None, + cookies_service: CookiesService = None, + sign_service: SignServices = None, + bot_admin_service: BotAdminService = None, + ): self.bot_admin_service = bot_admin_service self.cookies_service = cookies_service self.user_service = user_service self.sign_service = sign_service + @staticmethod + async def pass_challenge(gt: str, challenge: str, referer: str = None) -> Optional[Dict]: + """尝试自动通过验证,感谢 @coolxitech 大佬提供的方案 + + https://github.com/coolxitech/mihoyo + """ + if not gt or not challenge: + return None + if not referer: + referer = ( + "https://webstatic.mihoyo.com/bbs/event/signin-ys/index.html?" + "bbs_auth_required=true&act_id=e202009291139501&utm_source=bbs&utm_medium=mys&utm_campaign=icon" + ) + header = { + "Accept": "*/*", + "X-Requested-With": "com.mihoyo.hyperion", + "User-Agent": "Mozilla/5.0 (Linux; Android 12; Unspecified Device) AppleWebKit/537.36 (KHTML, like Gecko) " + "Version/4.0 Chrome/103.0.5060.129 Mobile Safari/537.36 miHoYoBBS/2.37.1", + "Referer": referer, + "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", + } + # ajax auto pass + async with AsyncClient() as client: + try: + # gt={gt}&challenge={challenge}&lang=zh-cn&pt=3 + # client_type=web_mobile&callback=geetest_{int(time.time() * 1000)} + req = await client.get( + "https://api.geetest.com/ajax.php", + params={ + "gt": gt, + "challenge": challenge, + "lang": "zh-cn", + "pt": 3, + "client_type": "web_mobile", + "callback": f"geetest_{int(time.time() * 1000)}", + }, + headers=header, + timeout=20, + ) + logger.info(f"ajax 返回:{req.text}") + if req.status_code != 200: + raise RuntimeError + data = req.json() + if "success" in data["status"] and "success" in data["data"]["result"]: + return { + "x-rpc-challenge": challenge, + "x-rpc-validate": data["data"]["validate"], + "x-rpc-seccode": f'{data["data"]["validate"]}|jordan', + } + except ( + JSONDecodeError, + KeyError, + Timeout, + RuntimeError, + ) as exc: + logger.warning(f"ajax 自动通过失败:{repr(exc)}") + logger.warning("ajax 自动通过失败") + if not config.pass_challenge_api: + return None + pass_challenge_params = { + "gt": gt, + "challenge": challenge, + "referer": referer, + } + if config.pass_challenge_app_key: + pass_challenge_params["appkey"] = config.pass_challenge_app_key + # custom api auto pass + async with AsyncClient() as client: + try: + resp = await client.post( + config.pass_challenge_api, + params=pass_challenge_params, + timeout=45, + ) + logger.info(f"签到自定义打码平台返回:{resp.text}") + data = resp.json() + if data["code"] != 0: + raise RuntimeError + return { + "x-rpc-challenge": data["data"]["challenge"], + "x-rpc-validate": data["data"]["validate"], + "x-rpc-seccode": f'{data["data"]["validate"]}|jordan', + } + except (JSONDecodeError, KeyError, Timeout, RuntimeError) as exc: + logger.warning(f"签到自定义打码平台自动通过失败:{repr(exc)}") + return None + @staticmethod async def _start_sign(client: Client) -> str: try: @@ -48,11 +145,29 @@ class Sign(Plugin, BasePlugin): return f"获取签到状态失败,API返回信息为 {str(error)}" 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") + request_daily_reward = await client.request_daily_reward( + "sign", method="POST", game=Game.GENSHIN, lang="zh-cn" + ) if request_daily_reward and request_daily_reward.get("success", 0) == 1: - logger.warning(f"UID {client.uid} 签到失败,触发验证码风控") - return f"UID {client.uid} 签到失败,触发验证码风控,请尝试重新签到。" + # 米游社国内签到自动打码 + headers = await Sign.pass_challenge( + request_daily_reward.get("gt", ""), + request_daily_reward.get("challenge", ""), + ) + if not headers: + logger.warning(f"UID {client.uid} 签到失败,触发验证码风控") + return f"UID {client.uid} 签到失败,触发验证码风控,请尝试重新签到。" + request_daily_reward = await client.request_daily_reward( + "sign", + method="POST", + game=Game.GENSHIN, + lang="zh-cn", + headers=headers, + ) + 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} 通过自动打码签到成功") except AlreadyClaimed: logger.info(f"UID {client.uid} 已经签到") result = "今天旅行者已经签到过了~" @@ -73,11 +188,13 @@ class Sign(Plugin, BasePlugin): missed_days = now.day - daily_reward_info.claimed_rewards if not daily_reward_info.signed_in: missed_days -= 1 - message = f"#### {today} (UTC+8) ####\n" \ - f"UID: {client.uid}\n" \ - f"今日奖励: {reward.name} × {reward.amount}\n" \ - f"本月漏签次数:{missed_days}\n" \ - f"签到结果: {result}" + message = ( + f"#### {today} (UTC+8) ####\n" + f"UID: {client.uid}\n" + f"今日奖励: {reward.name} × {reward.amount}\n" + f"本月漏签次数:{missed_days}\n" + f"签到结果: {result}" + ) return message async def _process_auto_sign(self, user_id: int, chat_id: int, method: str) -> str: @@ -99,8 +216,12 @@ class Sign(Plugin, BasePlugin): elif method == "关闭": return "您还没有开启自动签到" elif method == "开启": - user = SignUser(user_id=user_id, chat_id=chat_id, time_created=datetime.datetime.now(), - status=SignStatusEnum.STATUS_SUCCESS) + user = SignUser( + user_id=user_id, + chat_id=chat_id, + time_created=datetime.datetime.now(), + status=SignStatusEnum.STATUS_SUCCESS, + ) await self.sign_service.add(user) return "开启自动签到成功" @@ -134,6 +255,7 @@ class Sign(Plugin, BasePlugin): self._add_delete_message_job(context, message.chat_id, message.message_id) try: client = await get_genshin_client(user.id) + 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) if filters.ChatType.GROUPS.filter(reply_message): diff --git a/plugins/jobs/sign.py b/plugins/jobs/sign.py index 133d4a8..f8b8537 100644 --- a/plugins/jobs/sign.py +++ b/plugins/jobs/sign.py @@ -13,6 +13,7 @@ from core.plugin import Plugin, job from core.sign.models import SignStatusEnum from core.sign.services import SignServices from core.user import UserService +from plugins.genshin.sign import Sign from utils.helpers import get_genshin_client from utils.log import logger @@ -22,13 +23,64 @@ class NeedChallenge(Exception): class SignJob(Plugin): - - def __init__(self, sign_service: SignServices = None, user_service: UserService = None, - cookies_service: CookiesService = None): + def __init__( + self, + sign_service: SignServices = None, + user_service: UserService = None, + cookies_service: CookiesService = None, + ): self.sign_service = sign_service self.cookies_service = cookies_service self.user_service = user_service + @staticmethod + async def single_sign(user_id: int) -> str: + client = await get_genshin_client(user_id) + rewards = await client.get_monthly_rewards(game=Game.GENSHIN, lang="zh-cn") + daily_reward_info = await client.get_reward_info(game=Game.GENSHIN) + 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} 签到失败,触发验证码风控 | 打码平台打码失败,请检查") + raise NeedChallenge + request_daily_reward = await client.request_daily_reward( + "sign", + method="POST", + game=Game.GENSHIN, + lang="zh-cn", + headers=headers, + ) + if request_daily_reward and request_daily_reward.get("success", 0) == 1: + logger.warning(f"UID {client.uid} 签到失败,触发验证码风控 | 打码平台打码失败,请检查") + raise NeedChallenge + logger.info(f"UID {client.uid} 签到请求 {request_daily_reward} | 通过自动打码签到成功") + else: + logger.info(f"UID {client.uid} 签到请求 {request_daily_reward}") + result = "OK" + else: + result = "今天旅行者已经签到过了~" + reward = rewards[daily_reward_info.claimed_rewards - (1 if daily_reward_info.signed_in else 0)] + today = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + cn_timezone = datetime.timezone(datetime.timedelta(hours=8)) + now = datetime.datetime.now(cn_timezone) + missed_days = now.day - daily_reward_info.claimed_rewards + if not daily_reward_info.signed_in: + missed_days -= 1 + return ( + f"########### 定时签到 ###########\n" + f"#### {today} (UTC+8) ####\n" + f"UID: {client.uid}\n" + f"今日奖励: {reward.name} × {reward.amount}\n" + f"本月漏签次数:{missed_days}\n" + f"签到结果: {result}" + ) + @job.run_daily(time=datetime.time(hour=0, minute=1, second=0), name="SignJob") async def sign(self, context: CallbackContext): logger.info("正在执行自动签到") @@ -38,32 +90,7 @@ class SignJob(Plugin): continue user_id = sign_db.user_id try: - client = await get_genshin_client(user_id) - rewards = await client.get_monthly_rewards(game=Game.GENSHIN, lang="zh-cn") - daily_reward_info = await client.get_reward_info(game=Game.GENSHIN) - 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: - logger.warning(f"UID {client.uid} 签到失败,触发验证码风控") - raise NeedChallenge - else: - logger.info(f"UID {client.uid} 签到请求 {request_daily_reward}") - result = "OK" - else: - result = "今天旅行者已经签到过了~" - reward = rewards[daily_reward_info.claimed_rewards - (1 if daily_reward_info.signed_in else 0)] - today = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) - cn_timezone = datetime.timezone(datetime.timedelta(hours=8)) - now = datetime.datetime.now(cn_timezone) - missed_days = now.day - daily_reward_info.claimed_rewards - if not daily_reward_info.signed_in: - missed_days -= 1 - text = f"########### 定时签到 ###########\n" \ - f"#### {today} (UTC+8) ####\n" \ - f"UID: {client.uid}\n" \ - f"今日奖励: {reward.name} × {reward.amount}\n" \ - f"本月漏签次数:{missed_days}\n" \ - f"签到结果: {result}" + text = await self.single_sign(user_id) except InvalidCookies: text = "自动签到执行失败,Cookie无效" sign_db.status = SignStatusEnum.INVALID_COOKIES @@ -84,7 +111,7 @@ class SignJob(Plugin): logger.exception(exc) text = "签到失败了呜呜呜 ~ 执行自动签到时发生错误" if sign_db.chat_id < 0: - text = f"NOTICE {sign_db.user_id}\n\n{text}" + text = f'NOTICE {sign_db.user_id}\n\n{text}' try: await context.bot.send_message(sign_db.chat_id, text, parse_mode=ParseMode.HTML) await asyncio.sleep(5) # 回复延迟5S避免触发洪水防御 diff --git a/plugins/system/sign_all.py b/plugins/system/sign_all.py new file mode 100644 index 0000000..6483af3 --- /dev/null +++ b/plugins/system/sign_all.py @@ -0,0 +1,85 @@ +import datetime +import asyncio + +from aiohttp import ClientConnectorError +from genshin import InvalidCookies, AlreadyClaimed, GenshinException +from telegram import Update +from telegram.constants import ParseMode +from telegram.error import BadRequest, Forbidden +from telegram.ext import CommandHandler, CallbackContext + +from core.cookies import CookiesService +from core.plugin import Plugin, handler +from core.sign import SignServices +from core.sign.models import SignStatusEnum +from core.user import UserService +from plugins.jobs.sign import NeedChallenge, SignJob +from utils.decorators.admins import bot_admins_rights_check +from utils.log import logger + + +class SignAll(Plugin): + def __init__( + self, + sign_service: SignServices = None, + user_service: UserService = None, + cookies_service: CookiesService = None, + ): + self.sign_service = sign_service + self.cookies_service = cookies_service + self.user_service = user_service + + @handler(CommandHandler, command="sign_all", block=False) + @bot_admins_rights_check + async def sign_all(self, update: Update, context: CallbackContext): + user = update.effective_user + logger.info(f"用户 {user.full_name}[{user.id}] sign_all 命令请求") + message = update.effective_message + reply = await message.reply_text("正在全部重新签到,请稍后...") + sign_list = await self.sign_service.get_all() + for sign_db in sign_list: + user_id = sign_db.user_id + old_status = sign_db.status + try: + text = await SignJob.single_sign(user_id) + except InvalidCookies: + text = "自动签到执行失败,Cookie无效" + sign_db.status = SignStatusEnum.INVALID_COOKIES + except AlreadyClaimed: + text = "今天旅行者已经签到过了~" + sign_db.status = SignStatusEnum.ALREADY_CLAIMED + except GenshinException as exc: + text = f"自动签到执行失败,API返回信息为 {str(exc)}" + sign_db.status = SignStatusEnum.GENSHIN_EXCEPTION + except ClientConnectorError: + text = "签到失败了呜呜呜 ~ 服务器连接超时 服务器熟啦 ~ " + sign_db.status = SignStatusEnum.TIMEOUT_ERROR + except NeedChallenge: + text = "签到失败,触发验证码风控,自动签到自动关闭" + sign_db.status = SignStatusEnum.NEED_CHALLENGE + except BaseException as exc: + logger.error(f"执行自动签到时发生错误 用户UID[{user_id}]") + logger.exception(exc) + text = "签到失败了呜呜呜 ~ 执行自动签到时发生错误" + if sign_db.chat_id < 0: + text = f'NOTICE {sign_db.user_id}\n\n{text}' + try: + if "今天旅行者已经签到过了~" not in text: + await context.bot.send_message(sign_db.chat_id, text, parse_mode=ParseMode.HTML) + await asyncio.sleep(5) # 回复延迟5S避免触发洪水防御 + except BadRequest as exc: + logger.error(f"执行自动签到时发生错误 用户UID[{user_id}]") + logger.exception(exc) + sign_db.status = SignStatusEnum.BAD_REQUEST + except Forbidden as exc: + logger.error(f"执行自动签到时发生错误 用户UID[{user_id}]") + logger.exception(exc) + sign_db.status = SignStatusEnum.FORBIDDEN + except BaseException as exc: + logger.error(f"执行自动签到时发生错误 用户UID[{user_id}]") + logger.exception(exc) + continue + sign_db.time_updated = datetime.datetime.now() + if sign_db.status != old_status: + await self.sign_service.update(sign_db) + await reply.edit_text("全部账号重新签到完成")