from simnet import GenshinClient, Region from telegram import Update, User, InlineKeyboardButton, InlineKeyboardMarkup from telegram.constants import ChatAction from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters, ConversationHandler from telegram.helpers import create_deep_linked_url from core.basemodel import RegionEnum from core.plugin import Plugin, handler, conversation from core.services.cookies import CookiesService from core.services.players.services import PlayersService from core.services.template.services import TemplateService from modules.gacha_log.helpers import from_url_get_authkey from modules.pay_log.error import PayLogNotFound, PayLogAccountNotFound, PayLogInvalidAuthkey, PayLogAuthkeyTimeout from modules.pay_log.log import PayLog from plugins.tools.genshin import PlayerNotFoundError from plugins.tools.player_info import PlayerInfoSystem from utils.log import logger INPUT_URL, CONFIRM_DELETE = range(10100, 10102) class PayLogPlugin(Plugin.Conversation): """充值记录导入/导出/分析""" def __init__( self, template_service: TemplateService, players_service: PlayersService, cookie_service: CookiesService, player_info: PlayerInfoSystem, ): self.template_service = template_service self.players_service = players_service self.cookie_service = cookie_service self.pay_log = PayLog() self.player_info = player_info async def get_player_id(self, uid: int) -> int: """获取绑定的游戏ID""" logger.debug("尝试获取已绑定的原神账号") player = await self.players_service.get_player(uid) if player is None: raise PlayerNotFoundError(uid) return player.player_id async def _refresh_user_data(self, user: User, authkey: str = None) -> str: """刷新用户数据 :param user: 用户 :param authkey: 认证密钥 :return: 返回信息 """ try: player_id = await self.get_player_id(user.id) new_num = await self.pay_log.get_log_data(user.id, player_id, authkey) return "更新完成,本次没有新增数据" if new_num == 0 else f"更新完成,本次共新增{new_num}条充值记录" except PayLogNotFound: return "派蒙没有找到你的充值记录,快去充值吧~" except PayLogAccountNotFound: return "导入失败,可能文件包含的祈愿记录所属 uid 与你当前绑定的 uid 不同" except PayLogInvalidAuthkey: return "更新数据失败,authkey 无效" except PayLogAuthkeyTimeout: return "更新数据失败,authkey 已经过期" except PlayerNotFoundError: logger.info("未查询到用户 %s[%s] 所绑定的账号信息", user.full_name, user.id) return "派蒙没有找到您所绑定的账号信息,请先私聊派蒙绑定账号" @conversation.entry_point @handler(CommandHandler, command="pay_log_import", filters=filters.ChatType.PRIVATE, block=False) @handler(MessageHandler, filters=filters.Regex("^导入充值记录$") & filters.ChatType.PRIVATE, block=False) async def command_start(self, update: Update, context: CallbackContext) -> int: message = update.effective_message user = update.effective_user args = self.get_args(context) logger.info("用户 %s[%s] 导入充值记录命令请求", user.full_name, user.id) authkey = from_url_get_authkey(args[0] if args else "") if not args: player_info = await self.players_service.get_player(user.id, region=RegionEnum.HYPERION) if player_info is not None: cookies = await self.cookie_service.get(user.id, account_id=player_info.account_id) if cookies is not None and cookies.data and "stoken" in cookies.data: if stuid := next( (value for key, value in cookies.data.items() if key in ["ltuid", "login_uid"]), None ): cookies.data["stuid"] = stuid async with GenshinClient( cookies=cookies.data, region=Region.CHINESE, lang="zh-cn", player_id=player_info.player_id ) as client: authkey = await client.get_authkey_by_stoken("csc") if not authkey: await message.reply_text( "开始导入充值历史记录:请通过 https://paimon.moe/wish/import 获取抽卡记录链接后发送给我" "(非 paimon.moe 导出的文件数据)\n\n" "> 在绑定 Cookie 时添加 stoken 可能有特殊效果哦(仅限国服)\n" "注意:导入的数据将会与旧数据进行合并。", parse_mode="html", ) return INPUT_URL 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 reply.edit_text(data) return ConversationHandler.END @conversation.state(state=INPUT_URL) @handler.message(filters=~filters.COMMAND, block=False) async def import_data_from_message(self, update: Update, _: CallbackContext) -> int: message = update.effective_message user = update.effective_user if message.document: await message.reply_text("呜呜呜~本次导入不支持文件导入,请尝试获取连接") return INPUT_URL if not message.text: await message.reply_text("呜呜呜~输入错误,请尝试重新获取连接") return INPUT_URL authkey = 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 @conversation.entry_point @handler(CommandHandler, command="pay_log_delete", filters=filters.ChatType.PRIVATE, block=False) @handler(MessageHandler, filters=filters.Regex("^删除充值记录$") & filters.ChatType.PRIVATE, block=False) async def command_start_delete(self, update: Update, context: CallbackContext) -> int: message = update.effective_message user = update.effective_user logger.info("用户 %s[%s] 删除充值记录命令请求", user.full_name, user.id) player_info = await self.players_service.get_player(user.id) if player_info is None: logger.info("未查询到用户 %s[%s] 所绑定的账号信息", user.full_name, user.id) await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号") return ConversationHandler.END _, status = await self.pay_log.load_history_info(str(user.id), str(player_info.player_id), only_status=True) if not status: await message.reply_text("你还没有导入充值记录哦~") return ConversationHandler.END context.chat_data["uid"] = player_info.player_id await message.reply_text("你确定要删除充值记录吗?(此项操作无法恢复),如果确定请发送 ”确定“,发送其他内容取消") return CONFIRM_DELETE @conversation.state(state=CONFIRM_DELETE) @handler.message(filters=filters.TEXT & ~filters.COMMAND, block=False) async def command_confirm_delete(self, update: Update, context: CallbackContext) -> int: message = update.effective_message user = update.effective_user if message.text == "确定": status = await self.pay_log.remove_history_info(str(user.id), str(context.chat_data["uid"])) await message.reply_text("充值记录已删除" if status else "充值记录删除失败") return ConversationHandler.END await message.reply_text("已取消") return ConversationHandler.END @handler(CommandHandler, command="pay_log_force_delete", block=False, admin=True) async def command_pay_log_force_delete(self, update: Update, context: CallbackContext): message = update.effective_message args = self.get_args(context) if not args: await message.reply_text("请指定用户ID") return try: cid = int(args[0]) if cid < 0: raise ValueError("Invalid cid") player_info = await self.players_service.get_player(cid) if player_info is None: await message.reply_text("该用户暂未绑定账号") return _, status = await self.pay_log.load_history_info(str(cid), str(player_info.player_id), only_status=True) if not status: await message.reply_text("该用户还没有导入充值记录") return status = await self.pay_log.remove_history_info(str(cid), str(player_info.player_id)) await message.reply_text("充值记录已强制删除" if status else "充值记录删除失败") except PayLogNotFound: await message.reply_text("该用户还没有导入充值记录") except (ValueError, IndexError): await message.reply_text("用户ID 不合法") @handler(CommandHandler, command="pay_log_export", filters=filters.ChatType.PRIVATE, block=False) @handler(MessageHandler, filters=filters.Regex("^导出充值记录$") & filters.ChatType.PRIVATE, block=False) async def command_start_export(self, update: Update, context: CallbackContext) -> None: message = update.effective_message user = update.effective_user logger.info("用户 %s[%s] 导出充值记录命令请求", user.full_name, user.id) try: await message.reply_chat_action(ChatAction.TYPING) player_id = await self.get_player_id(user.id) path = self.pay_log.get_file_path(str(user.id), str(player_id)) if not path.exists(): raise PayLogNotFound await message.reply_chat_action(ChatAction.UPLOAD_DOCUMENT) await message.reply_document(document=open(path, "rb+"), caption="充值记录导出文件") except PayLogNotFound: buttons = [ [InlineKeyboardButton("点我导入", url=create_deep_linked_url(context.bot.username, "pay_log_import"))] ] await message.reply_text("派蒙没有找到你的充值记录,快来私聊派蒙导入吧~", reply_markup=InlineKeyboardMarkup(buttons)) except PayLogAccountNotFound: await message.reply_text("导出失败,可能文件包含的祈愿记录所属 uid 与你当前绑定的 uid 不同") except PlayerNotFoundError: logger.info("未查询到用户 %s[%s] 所绑定的账号信息", user.full_name, user.id) await message.reply_text("未查询到您所绑定的账号信息,请先绑定账号") @handler(CommandHandler, command="pay_log", block=False) @handler(MessageHandler, filters=filters.Regex("^充值记录$"), block=False) async def command_start_analysis(self, update: Update, context: CallbackContext) -> None: message = update.effective_message user = update.effective_user logger.info("用户 %s[%s] 充值记录统计命令请求", user.full_name, user.id) try: await message.reply_chat_action(ChatAction.TYPING) player_id = await self.get_player_id(user.id) data = await self.pay_log.get_analysis(user.id, player_id) await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) name_card = await self.player_info.get_name_card(player_id, user) data["name_card"] = name_card png_data = await self.template_service.render( "genshin/pay_log/pay_log.jinja2", data, full_page=True, query_selector=".container" ) await png_data.reply_photo(message) except PayLogNotFound: buttons = [ [InlineKeyboardButton("点我导入", url=create_deep_linked_url(context.bot.username, "pay_log_import"))] ] await message.reply_text("派蒙没有找到你的充值记录,快来点击按钮私聊派蒙导入吧~", reply_markup=InlineKeyboardMarkup(buttons))