diff --git a/.env.example b/.env.example index ded2509..70744d0 100644 --- a/.env.example +++ b/.env.example @@ -38,6 +38,8 @@ OWNER=0 # 文章推送群组 可选配置项 # CHANNELS=[] +# 消息帮助频道 可选配置项 +# CHANNELS_HELPER=0 # 是否允许机器人邀请到其他群 默认不允许 如果允许 可以允许全部人或有认证选项 可选配置项 # JOIN_GROUPS = "NO_ALLOW" diff --git a/gram_core b/gram_core index c478924..9bce0f9 160000 --- a/gram_core +++ b/gram_core @@ -1 +1 @@ -Subproject commit c478924e064d9dff9a944107ce34ee639f3febf6 +Subproject commit 9bce0f921f7400701edcf5191b97ce89e58007db diff --git a/plugins/app/inline.py b/plugins/app/inline.py index dc00c9d..3fee344 100644 --- a/plugins/app/inline.py +++ b/plugins/app/inline.py @@ -1,5 +1,5 @@ import asyncio -from typing import Awaitable, Dict, List, cast +from typing import Awaitable, Dict, List, cast, Tuple from uuid import uuid4 from telegram import ( @@ -10,15 +10,22 @@ from telegram import ( InputTextMessageContent, Update, InlineQueryResultsButton, + InlineKeyboardMarkup, + InlineKeyboardButton, + InlineQueryResultPhoto, ) from telegram.constants import ParseMode from telegram.error import BadRequest -from telegram.ext import CallbackContext +from telegram.ext import CallbackContext, ContextTypes from core.dependence.assets import AssetsService from core.plugin import Plugin, handler from core.services.search.services import SearchServices from core.services.wiki.services import WikiService +from gram_core.config import config +from gram_core.plugin.methods.inline_use_data import IInlineUseData +from gram_core.services.cookies import CookiesService +from gram_core.services.players import PlayersService from utils.log import logger @@ -30,6 +37,8 @@ class Inline(Plugin): asset_service: AssetsService, search_service: SearchServices, wiki_service: WikiService, + cookies_service: CookiesService, + players_service: PlayersService, ): self.asset_service = asset_service self.wiki_service = wiki_service @@ -41,6 +50,11 @@ class Inline(Plugin): self.relics_list: List[Dict[str, str]] = [] self.refresh_task: List[Awaitable] = [] self.search_service = search_service + self.cookies_service = cookies_service + self.players_service = players_service + self.inline_use_data: List[IInlineUseData] = [] + self.inline_use_data_map: Dict[str, IInlineUseData] = {} + self.img_url = "https://i.dawnlab.me/b1bdf9cc3061d254f038e557557694bc.jpg" async def initialize(self): async def task_light_cone(): @@ -120,8 +134,113 @@ class Inline(Plugin): self.refresh_task.append(asyncio.create_task(task_light_cone())) self.refresh_task.append(asyncio.create_task(task_relics())) + async def init_inline_use_data(self): + if self.inline_use_data: + return + for _, instance in self.application.managers.plugins_map.items(): + if _data := await instance.get_inline_use_data(): + self.inline_use_data.extend(_data) + for data in self.inline_use_data: + self.inline_use_data_map[data.hash] = data + + async def user_base_data(self, user_id: int, player_id: int, offset: int) -> Tuple[int, bool, bool]: + uid, has_cookie, has_player = 0, False, False + player = await self.players_service.get_player(user_id, None, player_id, offset) + if player is not None: + uid = player.player_id + has_player = True + if player.account_id is not None: + cookie_model = await self.cookies_service.get(player.user_id, player.account_id, player.region) + if cookie_model is not None: + has_cookie = True + return uid, has_cookie, has_player + + def get_inline_use_button_data(self, user_id: int, uid: int, cookie: bool, player: bool) -> InlineKeyboardMarkup: + button_data = [] + start = f"use_inline_func|{user_id}|{uid}" + for data in self.inline_use_data: + if data.is_show(cookie, player): + button_data.append( + InlineKeyboardButton(text=data.text, callback_data=data.get_button_callback_data(start)) + ) + # 每三个一行 + button_data = [button_data[i : i + 3] for i in range(0, len(button_data), 3)] + return InlineKeyboardMarkup(button_data) + + @handler.callback_query(pattern=r"^use_inline_func\|", block=False) + async def use_by_inline_query_callback(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> None: + user = update.effective_user + callback_query = update.callback_query + + async def get_inline_query_callback(callback_query_data: str) -> Tuple[int, int, str]: + _data = callback_query_data.split("|") + _user_id = int(_data[1]) + _uid = int(_data[2]) + _hash = _data[3] + logger.debug("callback_query_data函数返回 user_id[%s] uid[%s] hash[%s]", _user_id, _uid, _hash) + return _user_id, _uid, _hash + + user_id, uid, hash_str = await get_inline_query_callback(callback_query.data) + if user.id != user_id: + await callback_query.answer(text="这不是你的按钮!\n" + config.notice.user_mismatch, show_alert=True) + return + callback = self.inline_use_data_map.get(hash_str) + if callback is None: + await callback_query.answer(text="数据不存在,请重新生成按钮", show_alert=True) + return + IInlineUseData.set_uid_to_context(context, uid) + await callback.callback(update, context) + + @handler.inline_query(pattern="^功能", block=False) + async def use_by_inline_query(self, update: "Update", _: "ContextTypes.DEFAULT_TYPE") -> None: + if not config.channels_helper: + logger.warning("未设置 helper 频道") + return + await self.init_inline_use_data() + user = update.effective_user + ilq = cast(InlineQuery, update.inline_query) + query = ilq.query + switch_pm_text = "需要帮助嘛?" + logger.info("用户 %s[%s] inline_query 功能查询\nquery[%s]", user.full_name, user.id, query) + user_id = user.id + uid, offset = self.get_real_uid_or_offset(update) + real_uid, has_cookie, has_player = await self.user_base_data(user_id, uid, offset) + button_data = self.get_inline_use_button_data(user_id, real_uid, has_cookie, has_player) + try: + await ilq.answer( + results=[ + InlineQueryResultPhoto( + id=str(uuid4()), + photo_url=self.img_url, + thumbnail_url=self.img_url, + caption="请从下方按钮选择功能", + reply_markup=button_data, + ) + ], + cache_time=0, + auto_pagination=True, + button=InlineQueryResultsButton( + text=switch_pm_text, + start_parameter="inline_message", + ), + ) + except BadRequest as exc: + if "Query is too old" in exc.message: # 过时请求全部忽略 + logger.warning("用户 %s[%s] inline_query 请求过时", user.full_name, user.id) + return + if "can't parse entities" not in exc.message: + raise exc + logger.warning("inline_query发生BadRequest错误", exc_info=exc) + await ilq.answer( + results=[], + button=InlineQueryResultsButton( + text="糟糕,发生错误了。", + start_parameter="inline_message", + ), + ) + @handler.inline_query(block=False) - async def inline_query(self, update: Update, _: CallbackContext) -> None: + async def z_inline_query(self, update: Update, _: CallbackContext) -> None: user = update.effective_user ilq = cast(InlineQuery, update.inline_query) query = ilq.query @@ -146,8 +265,18 @@ class Inline(Plugin): input_message_content=InputTextMessageContent(i[0]), ) ) + results_list.append( + InlineQueryResultArticle( + id=str(uuid4()), + title="使用功能", + description="输入 功能 即可直接使用 BOT 功能", + input_message_content=InputTextMessageContent("Inline 模式下输入 功能 即可直接使用 BOT 功能"), + ) + ) elif args[0] == "cookies_export": return + elif args[0] == "功能": + return else: if args[0] in [ "查看角色攻略列表并查询", diff --git a/plugins/starrail/action_log.py b/plugins/starrail/action_log.py index 2a13235..fa1a5be 100644 --- a/plugins/starrail/action_log.py +++ b/plugins/starrail/action_log.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING, Dict, List, Optional from telegram.constants import ChatAction from telegram.ext import filters @@ -7,6 +7,7 @@ from simnet import Region from core.services.self_help.services import ActionLogService from gram_core.plugin import Plugin, handler +from gram_core.plugin.methods.inline_use_data import IInlineUseData from gram_core.services.template.services import TemplateService from modules.action_log.client import ActionLogAnalyse from plugins.tools.action_log_system import ActionLogSystem @@ -22,6 +23,8 @@ if TYPE_CHECKING: from simnet import StarRailClient + from gram_core.services.template.models import RenderResult + class NotSupport(Exception): """不支持的服务器""" @@ -100,6 +103,15 @@ class ActionLogPlugins(Plugin): data["background"] = (await self.phone_theme.get_phone_theme(player_id)).as_uri() return data + async def render(self, client: "StarRailClient") -> "RenderResult": + data = await self.get_render_data(client.player_id) + return await self.template_service.render( + "starrail/action_log/action_log.html", + await self.add_theme_data(data, client.player_id), + full_page=True, + query_selector=".container", + ) + @handler.command(command="action_log", cookie=True, block=False) async def action_log(self, update: "Update", _: "ContextTypes.DEFAULT_TYPE") -> None: user_id = await self.get_real_user_id(update) @@ -110,16 +122,37 @@ class ActionLogPlugins(Plugin): try: async with self.helper.genshin(user_id, player_id=uid, offset=offset) as client: client: "StarRailClient" - data = await self.get_render_data(client.player_id) - render = await self.template_service.render( - "starrail/action_log/action_log.html", - await self.add_theme_data(data, client.player_id), - full_page=True, - query_selector=".container", - ) + render = await self.render(client) await render.reply_photo(message) except NotSupport as e: msg = await message.reply_text(e.msg) if filters.ChatType.GROUPS.filter(message): self.add_delete_message_job(message, delay=60) self.add_delete_message_job(msg, delay=60) + + async def action_log_use_by_inline(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> None: + callback_query = update.callback_query + user = update.effective_user + user_id = user.id + uid = IInlineUseData.get_uid_from_context(context) + self.log_user(update, logger.info, "查询登录记录") + + try: + async with self.helper.genshin(user_id, player_id=uid) as client: + client: "StarRailClient" + render = await self.render(client) + except NotSupport as e: + await callback_query.answer(e.msg, show_alert=True) + return + await render.edit_inline_media(callback_query) + + async def get_inline_use_data(self) -> List[Optional[IInlineUseData]]: + return [ + IInlineUseData( + text="登录统计", + hash="action_log", + callback=self.action_log_use_by_inline, + cookie=True, + player=True, + ) + ] diff --git a/plugins/starrail/avatar_list.py b/plugins/starrail/avatar_list.py index 77ce006..880860a 100644 --- a/plugins/starrail/avatar_list.py +++ b/plugins/starrail/avatar_list.py @@ -13,6 +13,7 @@ from core.services.cookies import CookiesService from core.services.template.models import FileType from core.services.template.services import TemplateService from core.services.wiki.services import WikiService +from gram_core.plugin.methods.inline_use_data import IInlineUseData from gram_core.services.template.models import RenderGroupResult from plugins.tools.genshin import GenshinHelper, CharacterDetails from plugins.tools.head_icon import HeadIconService @@ -185,6 +186,25 @@ class AvatarListPlugin(Plugin): tasks = [render_task(i * image_count, c) for i, c in enumerate(avatar_datas_group)] return await asyncio.gather(*tasks) + async def render(self, client: "StarRailClient", all_avatars: bool = False) -> List["RenderResult"]: + characters: List["StarRailDetailCharacter"] = await self.get_avatars_data(client) + record_card = await client.get_record_card() + nickname = record_card.nickname + has_more = (not all_avatars) and len(characters) > MAX_AVATAR_COUNT + if has_more: + characters = characters[:MAX_AVATAR_COUNT] + avatar_datas = await self.get_final_data(characters, client) + + base_render_data = { + "uid": mask_number(client.player_id), # 玩家uid + "nickname": nickname, # 玩家昵称 + "has_more": has_more, # 是否显示了全部角色 + "avatar": (await self.head_icon.get_head_icon(client.player_id)).as_uri(), + "background": (await self.phone_theme.get_phone_theme(client.player_id)).as_uri(), + } + + return await self.avatar_list_render(base_render_data, avatar_datas, has_more) + @handler.command("avatars", cookie=True, block=False) @handler.message(filters.Regex(r"^(全部)?练度统计$"), cookie=True, block=False) async def avatar_list(self, update: "Update", _: "ContextTypes.DEFAULT_TYPE"): @@ -198,23 +218,7 @@ class AvatarListPlugin(Plugin): async with self.helper.genshin(user_id, player_id=uid, offset=offset) as client: notice = await message.reply_text("彦卿需要收集整理数据,还请耐心等待哦~") self.add_delete_message_job(notice, delay=60) - characters: List["StarRailDetailCharacter"] = await self.get_avatars_data(client) - record_card = await client.get_record_card() - nickname = record_card.nickname - has_more = (not all_avatars) and len(characters) > MAX_AVATAR_COUNT - if has_more: - characters = characters[:MAX_AVATAR_COUNT] - avatar_datas = await self.get_final_data(characters, client) - - base_render_data = { - "uid": mask_number(client.player_id), # 玩家uid - "nickname": nickname, # 玩家昵称 - "has_more": has_more, # 是否显示了全部角色 - "avatar": (await self.head_icon.get_head_icon(client.player_id)).as_uri(), - "background": (await self.phone_theme.get_phone_theme(client.player_id)).as_uri(), - } - - images = await self.avatar_list_render(base_render_data, avatar_datas, has_more) + images = await self.render(client, all_avatars) for group in ArkoWrapper(images).group(10): # 每 10 张图片分一个组 await RenderGroupResult(results=group).reply_media_group(message, write_timeout=60) @@ -225,3 +229,27 @@ class AvatarListPlugin(Plugin): "[bold]练度统计[/bold]发送图片成功", extra={"markup": True}, ) + + async def avatar_list_use_by_inline(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> None: + callback_query = update.callback_query + user = update.effective_user + user_id = user.id + uid = IInlineUseData.get_uid_from_context(context) + self.log_user(update, logger.info, "查询练度统计") + + async with self.helper.genshin(user_id, player_id=uid) as client: + client: "StarRailClient" + images = await self.render(client) + render = images[0] + await render.edit_inline_media(callback_query) + + async def get_inline_use_data(self) -> List[Optional[IInlineUseData]]: + return [ + IInlineUseData( + text="练度统计", + hash="avatar_list", + callback=self.avatar_list_use_by_inline, + cookie=True, + player=True, + ) + ] diff --git a/plugins/starrail/challenge.py b/plugins/starrail/challenge.py index 75132a9..6731f9c 100644 --- a/plugins/starrail/challenge.py +++ b/plugins/starrail/challenge.py @@ -3,7 +3,7 @@ import asyncio import math import re -from functools import lru_cache +from functools import lru_cache, partial from typing import Any, List, Optional, Tuple, Union, TYPE_CHECKING from arkowrapper import ArkoWrapper @@ -21,6 +21,7 @@ from core.services.template.models import RenderGroupResult, RenderResult from core.services.template.services import TemplateService from gram_core.config import config from gram_core.dependence.redisdb import RedisDB +from gram_core.plugin.methods.inline_use_data import IInlineUseData from plugins.tools.genshin import GenshinHelper from utils.enkanetwork import RedisCache from utils.log import logger @@ -584,3 +585,45 @@ class ChallengePlugin(Plugin): await self.get_abyss_history_floor(update, data_id, detail) return await self.get_abyss_history_season(update, data_id) + + async def abyss_use_by_inline(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE", previous: bool): + callback_query = update.callback_query + user = update.effective_user + user_id = user.id + uid = IInlineUseData.get_uid_from_context(context) + + self.log_user(update, logger.info, "查询混沌回忆挑战总览数据 previous[%s]", previous) + notice = None + try: + async with self.helper.genshin_or_public(user_id, uid=uid) as client: + if not client.public: + await client.get_record_cards() + abyss_data = await self.get_rendered_pic_data(client, uid, previous) + images = await self.get_rendered_pic(abyss_data, uid, 0, False) + image = images[0] + except AbyssUnlocked: # 若深渊未解锁 + notice = "还未解锁混沌回忆哦~" + except TooManyRequestPublicCookies: + notice = "查询次数太多,请您稍后重试" + + if notice: + await callback_query.answer(notice, show_alert=True) + return + + await image.edit_inline_media(callback_query) + + async def get_inline_use_data(self) -> List[Optional[IInlineUseData]]: + return [ + IInlineUseData( + text="本期混沌回忆总览", + hash="challenge_current", + callback=partial(self.abyss_use_by_inline, previous=False), + player=True, + ), + IInlineUseData( + text="上期混沌回忆总览", + hash="challenge_previous", + callback=partial(self.abyss_use_by_inline, previous=True), + player=True, + ), + ] diff --git a/plugins/starrail/challenge_story.py b/plugins/starrail/challenge_story.py index 5dbf526..326575d 100644 --- a/plugins/starrail/challenge_story.py +++ b/plugins/starrail/challenge_story.py @@ -3,7 +3,7 @@ import asyncio import math import re -from functools import lru_cache +from functools import lru_cache, partial from typing import Any, List, Optional, Tuple, Union, TYPE_CHECKING from arkowrapper import ArkoWrapper @@ -21,6 +21,7 @@ from core.services.template.models import RenderGroupResult, RenderResult from core.services.template.services import TemplateService from gram_core.config import config from gram_core.dependence.redisdb import RedisDB +from gram_core.plugin.methods.inline_use_data import IInlineUseData from plugins.tools.genshin import GenshinHelper from utils.enkanetwork import RedisCache from utils.log import logger @@ -600,3 +601,47 @@ class ChallengeStoryPlugin(Plugin): await self.get_challenge_story_history_floor(update, data_id, detail) return await self.get_challenge_story_history_season(update, data_id) + + async def challenge_story_use_by_inline( + self, update: "Update", context: "ContextTypes.DEFAULT_TYPE", previous: bool + ): + callback_query = update.callback_query + user = update.effective_user + user_id = user.id + uid = IInlineUseData.get_uid_from_context(context) + + self.log_user(update, logger.info, "查询虚构叙事挑战总览数据 previous[%s]", previous) + notice = None + try: + async with self.helper.genshin_or_public(user_id, uid=uid) as client: + if not client.public: + await client.get_record_cards() + abyss_data, season = await self.get_rendered_pic_data(client, uid, previous) + images = await self.get_rendered_pic(abyss_data, season, uid, 0, False) + image = images[0] + except AbyssUnlocked: # 若深渊未解锁 + notice = "还未解锁虚构叙事哦~" + except TooManyRequestPublicCookies: + notice = "查询次数太多,请您稍后重试" + + if notice: + await callback_query.answer(notice, show_alert=True) + return + + await image.edit_inline_media(callback_query) + + async def get_inline_use_data(self) -> List[Optional[IInlineUseData]]: + return [ + IInlineUseData( + text="本期虚构叙事总览", + hash="challenge_story_current", + callback=partial(self.challenge_story_use_by_inline, previous=False), + player=True, + ), + IInlineUseData( + text="上期虚构叙事总览", + hash="challenge_story_previous", + callback=partial(self.challenge_story_use_by_inline, previous=True), + player=True, + ), + ] diff --git a/plugins/starrail/daily_note.py b/plugins/starrail/daily_note.py index b4b11b4..9245182 100644 --- a/plugins/starrail/daily_note.py +++ b/plugins/starrail/daily_note.py @@ -1,21 +1,24 @@ import datetime from datetime import datetime -from typing import Optional, TYPE_CHECKING +from typing import Optional, TYPE_CHECKING, List from simnet.errors import DataNotPublic -from telegram import Update, InlineKeyboardMarkup, InlineKeyboardButton +from telegram import InlineKeyboardMarkup, InlineKeyboardButton from telegram.constants import ChatAction -from telegram.ext import ConversationHandler, filters, CallbackContext +from telegram.ext import ConversationHandler, filters from telegram.helpers import create_deep_linked_url from core.plugin import Plugin, handler from core.services.template.models import RenderResult from core.services.template.services import TemplateService +from gram_core.plugin.methods.inline_use_data import IInlineUseData from plugins.tools.genshin import GenshinHelper from utils.log import logger from utils.uid import mask_number if TYPE_CHECKING: + from telegram import Update + from telegram.ext import ContextTypes from simnet import StarRailClient @@ -89,7 +92,7 @@ class DailyNotePlugin(Plugin): @handler.command("dailynote", cookie=True, block=False) @handler.message(filters.Regex("^当前状态(.*)"), cookie=True, block=False) - async def command_start(self, update: Update, context: CallbackContext) -> Optional[int]: + async def command_start(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> Optional[int]: user_id = await self.get_real_user_id(update) message = update.effective_message uid, offset = self.get_real_uid_or_offset(update) @@ -113,3 +116,33 @@ class DailyNotePlugin(Plugin): filename=f"{client.player_id}.png", reply_markup=self.get_task_button(context.bot.username), ) + + async def daily_note_use_by_inline(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE"): + callback_query = update.callback_query + user = update.effective_user + user_id = user.id + uid = IInlineUseData.get_uid_from_context(context) + self.log_user(update, logger.info, "每日便签命令请求") + + try: + async with self.helper.genshin(user_id, player_id=uid) as client: + render_result = await self._get_daily_note(client) + except DataNotPublic: + await callback_query.answer( + "查询失败惹,可能是便签功能被禁用了?请尝试通过米游社或者 hoyolab 获取一次便签信息后重试。", + show_alert=True, + ) + return ConversationHandler.END + + await render_result.edit_inline_media(callback_query) + + async def get_inline_use_data(self) -> List[Optional[IInlineUseData]]: + return [ + IInlineUseData( + text="当前状态", + hash="dailynote", + callback=self.daily_note_use_by_inline, + cookie=True, + player=True, + ) + ] diff --git a/plugins/starrail/help.py b/plugins/starrail/help.py index b4d904f..3b568fb 100644 --- a/plugins/starrail/help.py +++ b/plugins/starrail/help.py @@ -1,13 +1,19 @@ +from typing import List, Optional, TYPE_CHECKING + from telegram import Update from telegram.constants import ChatAction from telegram.ext import CallbackContext, filters from core.plugin import Plugin, handler from core.services.template.services import TemplateService +from gram_core.plugin.methods.inline_use_data import IInlineUseData from utils.log import logger __all__ = ("HelpPlugin",) +if TYPE_CHECKING: + from gram_core.services.template.models import RenderResult + class HelpPlugin(Plugin): def __init__(self, template_service: TemplateService = None): @@ -15,17 +21,35 @@ class HelpPlugin(Plugin): raise ModuleNotFoundError self.template_service = template_service + async def get_help_render(self) -> "RenderResult": + return await self.template_service.render( + "bot/help/help.html", + {"bot_username": self.application.bot.username}, + {"width": 1280, "height": 900}, + ttl=30 * 24 * 60 * 60, + ) + @handler.command(command="help", block=False) @handler.command(command="start", filters=filters.Regex("inline_message$"), block=False) async def start(self, update: Update, _: CallbackContext): message = update.effective_message self.log_user(update, logger.info, "发出help命令") await message.reply_chat_action(ChatAction.TYPING) - render_result = await self.template_service.render( - "bot/help/help.html", - {"bot_username": self.application.bot.username}, - {"width": 1280, "height": 900}, - ttl=30 * 24 * 60 * 60, - ) + render_result = await self.get_help_render() await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) await render_result.reply_photo(message, filename="help.png") + + async def start_use_by_inline(self, update: Update, _: CallbackContext): + callback_query = update.callback_query + self.log_user(update, logger.info, "发出help命令") + render_result = await self.get_help_render() + await render_result.edit_inline_media(callback_query) + + async def get_inline_use_data(self) -> List[Optional[IInlineUseData]]: + return [ + IInlineUseData( + text="帮助", + hash="help", + callback=self.start_use_by_inline, + ) + ] diff --git a/plugins/starrail/ledger.py b/plugins/starrail/ledger.py index d9e8860..a6e09ae 100644 --- a/plugins/starrail/ledger.py +++ b/plugins/starrail/ledger.py @@ -2,7 +2,7 @@ import math import os import re from datetime import datetime, timedelta -from typing import TYPE_CHECKING, List, Tuple +from typing import TYPE_CHECKING, List, Tuple, Optional from simnet.errors import BadRequest as SimnetBadRequest, DataNotPublic from simnet.models.starrail.diary import StarRailDiary @@ -18,6 +18,7 @@ from core.services.template.models import RenderResult from core.services.template.services import TemplateService from gram_core.config import config from gram_core.dependence.redisdb import RedisDB +from gram_core.plugin.methods.inline_use_data import IInlineUseData from plugins.tools.genshin import GenshinHelper from utils.enkanetwork import RedisCache from utils.log import logger @@ -313,3 +314,44 @@ class LedgerPlugin(Plugin): await callback_query.answer("正在渲染图片中 请稍等 请不要重复点击按钮") render = await self._start_get_ledger_render(user_id, HistoryDataLedger.from_data(data).diary_data) await render.edit_media(message) + + async def ledger_use_by_inline(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE"): + callback_query = update.callback_query + user = update.effective_user + user_id = user.id + uid = IInlineUseData.get_uid_from_context(context) + + self.log_user(update, logger.info, "查询开拓月历") + now = datetime.now() + now_time = (now - timedelta(days=1)) if now.day == 1 and now.hour <= 4 else now + year, month = now_time.year, now_time.month + try: + async with self.helper.genshin(user_id, player_id=uid) as client: + render_result = await self._start_get_ledger(client, year, month) + except DataNotPublic: + await callback_query.answer( + "查询失败惹,可能是开拓月历功能被禁用了?请先通过米游社或者 hoyolab 获取一次开拓月历后重试。", + show_alert=True, + ) + return + except SimnetBadRequest as exc: + if exc.ret_code == -120: + await callback_query.answer( + "当前角色开拓等级不足,暂时无法获取信息", + show_alert=True, + ) + return + raise exc + + await render_result.edit_inline_media(callback_query) + + async def get_inline_use_data(self) -> List[Optional[IInlineUseData]]: + return [ + IInlineUseData( + text="当月开拓月历", + hash="ledger", + callback=self.ledger_use_by_inline, + cookie=True, + player=True, + ) + ] diff --git a/plugins/starrail/stats.py b/plugins/starrail/stats.py index 9ced9b9..92af138 100644 --- a/plugins/starrail/stats.py +++ b/plugins/starrail/stats.py @@ -12,6 +12,8 @@ from core.services.cookies.error import TooManyRequestPublicCookies from core.services.players.services import PlayerInfoService from core.services.template.models import RenderResult from core.services.template.services import TemplateService +from gram_core.config import config +from gram_core.plugin.methods.inline_use_data import IInlineUseData from plugins.tools.genshin import GenshinHelper from plugins.tools.head_icon import HeadIconService from plugins.tools.phone_theme import PhoneThemeService @@ -142,3 +144,41 @@ class PlayerStatsPlugins(Plugin): return await self.phone_theme.set_to_cache(player_id, phone_theme_id) await self.player_info_service.set_name_card(player_id, phone_theme_id) + + async def stats_use_by_inline(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE"): + callback_query = update.callback_query + user = update.effective_user + user_id = user.id + uid = IInlineUseData.get_uid_from_context(context) + + self.log_user(update, logger.info, "查询游戏用户命令请求") + notice = None + try: + async with self.helper.genshin_or_public(user_id, uid=uid) as client: + if not client.public: + await client.get_record_cards() + render_result = await self.render(client, client.player_id) + except TooManyRequestPublicCookies: + notice = "用户查询次数过多 请稍后重试" + except AttributeError as exc: + logger.error("角色数据有误") + logger.exception(exc) + notice = f"角色数据有误 估计是{config.notice.bot_name}晕了" + except ValueError as exc: + logger.warning("获取 uid 发生错误! 错误信息为 %s", str(exc)) + notice = "UID 内部错误" + + if notice: + await callback_query.answer(notice, show_alert=True) + return + await render_result.edit_inline_media(callback_query) + + async def get_inline_use_data(self) -> List[Optional[IInlineUseData]]: + return [ + IInlineUseData( + text="玩家统计", + hash="stats", + callback=self.stats_use_by_inline, + player=True, + ), + ] diff --git a/plugins/starrail/wish_log.py b/plugins/starrail/wish_log.py index 34c7086..7a655bf 100644 --- a/plugins/starrail/wish_log.py +++ b/plugins/starrail/wish_log.py @@ -1,10 +1,11 @@ +from functools import partial from io import BytesIO from typing import Optional, TYPE_CHECKING, List, Union, Tuple, Dict from simnet.models.starrail.wish import StarRailBannerType -from telegram import Document, InlineKeyboardButton, InlineKeyboardMarkup, Message, Update, User +from telegram import InlineKeyboardButton, InlineKeyboardMarkup from telegram.constants import ChatAction -from telegram.ext import CallbackContext, ConversationHandler, filters +from telegram.ext import ConversationHandler, filters from telegram.helpers import create_deep_linked_url from core.dependence.assets import AssetsService @@ -14,6 +15,7 @@ from core.services.players import PlayersService from core.services.template.models import FileType from core.services.template.services import TemplateService from gram_core.config import config +from gram_core.plugin.methods.inline_use_data import IInlineUseData from modules.gacha_log.const import SRGF_VERSION, GACHA_TYPE_LIST_REVERSE from modules.gacha_log.error import ( GachaLogAccountNotFound, @@ -80,7 +82,7 @@ class WishLogPlugin(Plugin.Conversation): return player.player_id async def _refresh_user_data( - self, user: User, player_id: int, data: dict = None, authkey: str = None, verify_uid: bool = True + self, user: "User", player_id: int, data: dict = None, authkey: str = None, verify_uid: bool = True ) -> str: """刷新用户数据 :param user: 用户 @@ -112,7 +114,9 @@ class WishLogPlugin(Plugin.Conversation): logger.info("未查询到用户 %s[%s] 所绑定的账号信息", user.full_name, user.id) return config.notice.user_not_found - async def import_from_file(self, user: User, player_id: int, message: Message, document: Document = None) -> None: + async def import_from_file( + self, user: "User", player_id: int, message: "Message", document: "Document" = None + ) -> None: if not document: document = message.document # TODO: 使用 mimetype 判断文件类型 @@ -189,7 +193,7 @@ class WishLogPlugin(Plugin.Conversation): @conversation.state(state=INPUT_URL) @handler.message(filters=~filters.COMMAND, block=False) - async def import_data_from_message(self, update: Update, context: CallbackContext) -> int: + async def import_data_from_message(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> int: message = update.effective_message user = update.effective_user player_id = context.chat_data["uid"] @@ -209,7 +213,7 @@ class WishLogPlugin(Plugin.Conversation): @conversation.entry_point @handler.command(command="warp_log_delete", filters=filters.ChatType.PRIVATE, block=False) @handler.message(filters=filters.Regex("^删除跃迁记录(.*)") & filters.ChatType.PRIVATE, block=False) - async def command_start_delete(self, update: Update, context: CallbackContext) -> int: + async def command_start_delete(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> int: uid, offset = self.get_real_uid_or_offset(update) message = update.effective_message user = update.effective_user @@ -232,7 +236,7 @@ class WishLogPlugin(Plugin.Conversation): @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: + async def command_confirm_delete(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> int: message = update.effective_message user = update.effective_user if message.text == "确定": @@ -243,7 +247,7 @@ class WishLogPlugin(Plugin.Conversation): return ConversationHandler.END @handler.command(command="warp_log_force_delete", block=False, admin=True) - async def command_warp_log_force_delete(self, update: Update, context: CallbackContext): + async def command_warp_log_force_delete(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE"): uid, offset = self.get_real_uid_or_offset(update) message = update.effective_message args = self.get_args(context) @@ -270,7 +274,7 @@ class WishLogPlugin(Plugin.Conversation): @handler.command(command="warp_log_export", filters=filters.ChatType.PRIVATE, block=False) @handler.message(filters=filters.Regex("^导出跃迁记录(.*)") & filters.ChatType.PRIVATE, block=False) - async def command_start_export(self, update: Update, context: CallbackContext) -> None: + async def command_start_export(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> None: uid, offset = self.get_real_uid_or_offset(update) message = update.effective_message user = update.effective_user @@ -373,7 +377,7 @@ class WishLogPlugin(Plugin.Conversation): @handler.command(command="warp_log", block=False) @handler.message(filters=filters.Regex("^跃迁记录?(光锥|角色|常驻|新手)$"), block=False) - async def command_start_analysis(self, update: Update, context: CallbackContext) -> None: + async def command_start_analysis(self, update: "Update", context: "ContextTypes.DEFAULT_TYPE") -> None: user_id = await self.get_real_user_id(update) uid, offset = self.get_real_uid_or_offset(update) message = update.effective_message @@ -513,3 +517,44 @@ class WishLogPlugin(Plugin.Conversation): old_user_id: int, new_user_id: int, old_players: List["Player"] ) -> Optional[GachaLogMigrate]: return await GachaLogMigrate.create(old_user_id, new_user_id, old_players) + + async def wish_log_use_by_inline( + self, update: "Update", context: "ContextTypes.DEFAULT_TYPE", pool_type: "StarRailBannerType" + ): + callback_query = update.callback_query + user = update.effective_user + user_id = user.id + uid = IInlineUseData.get_uid_from_context(context) + + self.log_user(update, logger.info, "跃迁记录命令请求 || 参数 %s", pool_type.name if pool_type else None) + notice = None + try: + render_result = await self.rander_wish_log_analysis(user_id, uid, pool_type) + if isinstance(render_result, str): + notice = render_result + else: + await render_result.edit_inline_media(callback_query, filename="跃迁统计.png") + except GachaLogNotFound: + self.log_user(update, logger.info, "未找到跃迁记录") + notice = "未找到跃迁记录" + if notice: + await callback_query.answer(notice, show_alert=True) + + async def get_inline_use_data(self) -> List[Optional[IInlineUseData]]: + types = { + "角色": StarRailBannerType.CHARACTER, + "武器": StarRailBannerType.WEAPON, + "常驻": StarRailBannerType.STANDARD, + "新手": StarRailBannerType.NOVICE, + } + data = [] + for k, v in types.items(): + data.append( + IInlineUseData( + text=f"{k}跃迁", + hash=f"wish_log_{v.value}", + callback=partial(self.wish_log_use_by_inline, pool_type=v), + player=True, + ) + ) + return data diff --git a/plugins/system/errorhandler.py b/plugins/system/errorhandler.py index 99155ce..cab12e9 100644 --- a/plugins/system/errorhandler.py +++ b/plugins/system/errorhandler.py @@ -111,7 +111,7 @@ class ErrorHandler(Plugin): else: buttons = ReplyKeyboardRemove() - if chat.id == user.id: + if (not chat) or chat.id == user.id: logger.info("用户 %s[%s] 尝试通知错误信息[%s]", user.full_name, user.id, content) else: self.log_user(update, logger.info, "尝试通知在 %s[%s] 的错误信息[%s]", chat.title, chat.id, content) diff --git a/utils/patch/telegram.py b/utils/patch/telegram.py new file mode 100644 index 0000000..b01dc50 --- /dev/null +++ b/utils/patch/telegram.py @@ -0,0 +1,14 @@ +import telegram + +from utils.patch.methods import patch, patchable + +# https://github.com/python-telegram-bot/python-telegram-bot/issues/4295 + + +@patch(telegram.Bot) +class Bot: + @patchable + def _effective_inline_results(self, results, next_offset=None, current_offset=None): + if current_offset == "[]": + current_offset = 50 + return self.old__effective_inline_results(results, next_offset, current_offset)