diff --git a/app/chain/message.py b/app/chain/message.py index 31e53b7a..6db079ab 100644 --- a/app/chain/message.py +++ b/app/chain/message.py @@ -115,11 +115,19 @@ class MessageChain(ChainBase): if not text: logger.debug(f'未识别到消息内容::{body}{form}{args}') return + + # 获取原消息ID信息 + original_message_id = info.message_id + original_chat_id = info.chat_id + # 处理消息 - self.handle_message(channel=channel, source=source, userid=userid, username=username, text=text) + self.handle_message(channel=channel, source=source, userid=userid, username=username, text=text, + original_message_id=original_message_id, original_chat_id=original_chat_id) def handle_message(self, channel: MessageChannel, source: str, - userid: Union[str, int], username: str, text: str) -> None: + userid: Union[str, int], username: str, text: str, + original_message_id: Optional[int] = None, + original_chat_id: Optional[str] = None) -> None: """ 识别消息内容,执行操作 """ @@ -150,7 +158,7 @@ class MessageChain(ChainBase): if text.startswith('CALLBACK:'): # 处理按钮回调(适配支持回调的渠道) if ChannelCapabilityManager.supports_callbacks(channel): - self._handle_callback(text, channel, source, userid, username) + self._handle_callback(text, channel, source, userid, username, original_message_id, original_chat_id) else: logger.warning(f"渠道 {channel.value} 不支持回调,但收到了回调消息:{text}") elif text.startswith('/'): @@ -269,7 +277,9 @@ class MessageChain(ChainBase): title=mediainfo.title, items=contexts[:self._page_size], userid=userid, - total=len(contexts)) + total=len(contexts), + original_message_id=original_message_id, + original_chat_id=original_chat_id) elif cache_type in ["Subscribe", "ReSubscribe"]: # 订阅或洗版媒体 @@ -350,7 +360,9 @@ class MessageChain(ChainBase): title=_current_media.title, items=cache_list[start:end], userid=userid, - total=len(cache_list)) + total=len(cache_list), + original_message_id=original_message_id, + original_chat_id=original_chat_id) else: # 发送媒体数据 self.__post_medias_message(channel=channel, @@ -358,7 +370,9 @@ class MessageChain(ChainBase): title=_current_meta.name, items=cache_list[start:end], userid=userid, - total=len(cache_list)) + total=len(cache_list), + original_message_id=original_message_id, + original_chat_id=original_chat_id) elif text.lower() == "n": # 下一页 @@ -390,7 +404,9 @@ class MessageChain(ChainBase): title=_current_media.title, items=cache_list, userid=userid, - total=total) + total=total, + original_message_id=original_message_id, + original_chat_id=original_chat_id) else: # 发送媒体数据 self.__post_medias_message(channel=channel, @@ -398,7 +414,9 @@ class MessageChain(ChainBase): title=_current_meta.name, items=cache_list, userid=userid, - total=total) + total=total, + original_message_id=original_message_id, + original_chat_id=original_chat_id) else: # 搜索或订阅 @@ -458,7 +476,9 @@ class MessageChain(ChainBase): source=source, title=meta.name, items=medias[:self._page_size], - userid=userid, total=len(medias)) + userid=userid, total=len(medias), + original_message_id=original_message_id, + original_chat_id=original_chat_id) else: # 广播事件 self.eventmanager.send_event( @@ -481,7 +501,9 @@ class MessageChain(ChainBase): gc.collect() def _handle_callback(self, text: str, channel: MessageChannel, source: str, - userid: Union[str, int], username: str) -> None: + userid: Union[str, int], username: str, + original_message_id: Optional[int] = None, + original_chat_id: Optional[str] = None) -> None: """ 处理按钮回调 """ @@ -505,14 +527,14 @@ class MessageChain(ChainBase): # 解析系统回调数据 if callback_data.startswith("page_"): - # 翻页操作(旧格式,保持兼容) - self._handle_page_callback(callback_data, channel, source, userid) + # 翻页操作 + self._handle_page_callback(callback_data, channel, source, userid, original_message_id, original_chat_id) elif callback_data.startswith("select_"): # 选择操作或翻页操作 if callback_data in ["select_p", "select_n"]: - # 翻页操作:直接调用原来的文本处理逻辑 + # 翻页操作 page_text = callback_data.split("_")[1] # 提取 "p" 或 "n" - self.handle_message(channel, source, userid, username, page_text) + self.handle_message(channel, source, userid, username, page_text, original_message_id, original_chat_id) else: # 选择操作 self._handle_select_callback(callback_data, channel, source, userid, username) @@ -527,7 +549,8 @@ class MessageChain(ChainBase): logger.info(f"未知的回调数据:{callback_data}") def _handle_page_callback(self, callback_data: str, channel: MessageChannel, source: str, - userid: Union[str, int]) -> None: + userid: Union[str, int], original_message_id: Optional[int], + original_chat_id: Optional[str]): """ 处理翻页回调 """ @@ -540,10 +563,10 @@ class MessageChain(ChainBase): # 判断是上一页还是下一页 if page < _current_page: # 上一页,调用原来的 "p" 逻辑 - self.handle_message(channel, source, userid, "", "p") + self.handle_message(channel, source, userid, "", "p", original_message_id, original_chat_id) elif page > _current_page: # 下一页,调用原来的 "n" 逻辑 - self.handle_message(channel, source, userid, "", "n") + self.handle_message(channel, source, userid, "", "n", original_message_id, original_chat_id) # 如果 page == _current_page,说明是当前页,不需要处理 except (ValueError, IndexError) as e: @@ -640,7 +663,9 @@ class MessageChain(ChainBase): note=note) def __post_medias_message(self, channel: MessageChannel, source: str, - title: str, items: list, userid: str, total: int): + title: str, items: list, userid: str, total: int, + original_message_id: Optional[int] = None, + original_chat_id: Optional[str] = None): """ 发送媒体列表消息 """ @@ -668,7 +693,9 @@ class MessageChain(ChainBase): source=source, title=title, userid=userid, - buttons=buttons + buttons=buttons, + original_message_id=original_message_id, + original_chat_id=original_chat_id ) self.post_medias_message(notification, medias=items) @@ -725,7 +752,9 @@ class MessageChain(ChainBase): return buttons def __post_torrents_message(self, channel: MessageChannel, source: str, - title: str, items: list, userid: str, total: int): + title: str, items: list, userid: str, total: int, + original_message_id: Optional[int] = None, + original_chat_id: Optional[str] = None): """ 发送种子列表消息 """ @@ -754,7 +783,9 @@ class MessageChain(ChainBase): title=title, userid=userid, link=settings.MP_DOMAIN('#/resource'), - buttons=buttons + buttons=buttons, + original_message_id=original_message_id, + original_chat_id=original_chat_id ) self.post_torrents_message(notification, torrents=items) diff --git a/app/modules/telegram/__init__.py b/app/modules/telegram/__init__.py index 55420629..95609352 100644 --- a/app/modules/telegram/__init__.py +++ b/app/modules/telegram/__init__.py @@ -246,7 +246,9 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]): if client: client.send_msg(title=message.title, text=message.text, image=message.image, userid=userid, link=message.link, - buttons=message.buttons) + buttons=message.buttons, + original_message_id=message.original_message_id, + original_chat_id=message.original_chat_id) def post_medias_message(self, message: Notification, medias: List[MediaInfo]) -> None: """ @@ -262,7 +264,9 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]): if client: client.send_medias_msg(title=message.title, medias=medias, userid=message.userid, link=message.link, - buttons=message.buttons) + buttons=message.buttons, + original_message_id=message.original_message_id, + original_chat_id=message.original_chat_id) def post_torrents_message(self, message: Notification, torrents: List[Context]) -> None: """ @@ -278,7 +282,9 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]): if client: client.send_torrents_msg(title=message.title, torrents=torrents, userid=message.userid, link=message.link, - buttons=message.buttons) + buttons=message.buttons, + original_message_id=message.original_message_id, + original_chat_id=message.original_chat_id) def register_commands(self, commands: Dict[str, dict]): """ diff --git a/app/modules/telegram/telegram.py b/app/modules/telegram/telegram.py index bb86349f..51897799 100644 --- a/app/modules/telegram/telegram.py +++ b/app/modules/telegram/telegram.py @@ -9,6 +9,7 @@ from urllib.parse import urljoin import telebot from telebot import apihelper from telebot.types import InputFile, InlineKeyboardMarkup, InlineKeyboardButton +from telebot.types import InputMediaPhoto from app.core.config import settings from app.core.context import MediaInfo, Context @@ -119,7 +120,9 @@ class Telegram: def send_msg(self, title: str, text: Optional[str] = None, image: Optional[str] = None, userid: Optional[str] = None, link: Optional[str] = None, - buttons: Optional[List[List[dict]]] = None) -> Optional[bool]: + buttons: Optional[List[List[dict]]] = None, + original_message_id: Optional[int] = None, + original_chat_id: Optional[str] = None) -> Optional[bool]: """ 发送Telegram消息 :param title: 消息标题 @@ -128,6 +131,8 @@ class Telegram: :param userid: 用户ID,如有则只发消息给该用户 :param link: 跳转链接 :param buttons: 按钮列表,格式:[[{"text": "按钮文本", "callback_data": "回调数据"}]] + :param original_message_id: 原消息ID,如果提供则编辑原消息 + :param original_chat_id: 原消息的聊天ID,编辑消息时需要 :userid: 发送消息的目标用户ID,为空则发给管理员 """ if not self._telegram_token or not self._telegram_chat_id: @@ -158,7 +163,13 @@ class Telegram: if buttons: reply_markup = self._create_inline_keyboard(buttons) - return self.__send_request(userid=chat_id, image=image, caption=caption, reply_markup=reply_markup) + # 判断是编辑消息还是发送新消息 + if original_message_id and original_chat_id: + # 编辑消息 + return self.__edit_message(original_chat_id, original_message_id, caption, buttons, image) + else: + # 发送新消息 + return self.__send_request(userid=chat_id, image=image, caption=caption, reply_markup=reply_markup) except Exception as msg_e: logger.error(f"发送消息失败:{msg_e}") @@ -166,7 +177,9 @@ class Telegram: def send_medias_msg(self, medias: List[MediaInfo], userid: Optional[str] = None, title: Optional[str] = None, link: Optional[str] = None, - buttons: Optional[List[List[Dict]]] = None) -> Optional[bool]: + buttons: Optional[List[List[Dict]]] = None, + original_message_id: Optional[int] = None, + original_chat_id: Optional[str] = None) -> Optional[bool]: """ 发送媒体列表消息 :param medias: 媒体信息列表 @@ -174,6 +187,8 @@ class Telegram: :param title: 消息标题 :param link: 跳转链接 :param buttons: 按钮列表,格式:[[{"text": "按钮文本", "callback_data": "回调数据"}]] + :param original_message_id: 原消息ID,如果提供则编辑原消息 + :param original_chat_id: 原消息的聊天ID,编辑消息时需要 """ if not self._telegram_token or not self._telegram_chat_id: return None @@ -211,7 +226,13 @@ class Telegram: if buttons: reply_markup = self._create_inline_keyboard(buttons) - return self.__send_request(userid=chat_id, image=image, caption=caption, reply_markup=reply_markup) + # 判断是编辑消息还是发送新消息 + if original_message_id and original_chat_id: + # 编辑消息 + return self.__edit_message(original_chat_id, original_message_id, caption, buttons, image) + else: + # 发送新消息 + return self.__send_request(userid=chat_id, image=image, caption=caption, reply_markup=reply_markup) except Exception as msg_e: logger.error(f"发送消息失败:{msg_e}") @@ -219,24 +240,25 @@ class Telegram: def send_torrents_msg(self, torrents: List[Context], userid: Optional[str] = None, title: Optional[str] = None, - link: Optional[str] = None, buttons: Optional[List[List[Dict]]] = None) -> Optional[bool]: + link: Optional[str] = None, buttons: Optional[List[List[Dict]]] = None, + original_message_id: Optional[int] = None, + original_chat_id: Optional[str] = None) -> Optional[bool]: """ - 发送列表消息 - :param torrents: Torrent信息列表 + 发送种子列表消息 + :param torrents: 种子信息列表 :param userid: 用户ID,如有则只发消息给该用户 :param title: 消息标题 :param link: 跳转链接 :param buttons: 按钮列表,格式:[[{"text": "按钮文本", "callback_data": "回调数据"}]] + :param original_message_id: 原消息ID,如果提供则编辑原消息 + :param original_chat_id: 原消息的聊天ID,编辑消息时需要 """ if not self._telegram_token or not self._telegram_chat_id: return None - if not torrents: - return False - try: index, caption = 1, "*%s*" % title - mediainfo = torrents[0].media_info + image = torrents[0].media_info.get_message_image() for context in torrents: torrent = context.torrent_info site_name = torrent.site_name @@ -266,8 +288,13 @@ class Telegram: if buttons: reply_markup = self._create_inline_keyboard(buttons) - return self.__send_request(userid=chat_id, caption=caption, - image=mediainfo.get_message_image(), reply_markup=reply_markup) + # 判断是编辑消息还是发送新消息 + if original_message_id and original_chat_id: + # 编辑消息(种子消息通常没有图片) + return self.__edit_message(original_chat_id, original_message_id, caption, buttons, image) + else: + # 发送新消息 + return self.__send_request(userid=chat_id, image=image, caption=caption, reply_markup=reply_markup) except Exception as msg_e: logger.error(f"发送消息失败:{msg_e}") @@ -298,18 +325,62 @@ class Telegram: show_alert: bool = False) -> Optional[bool]: """ 回应回调查询 - :param callback_query_id: 回调查询ID - :param text: 提示文本 - :param show_alert: 是否显示弹窗提示 - :return: 回应结果 """ + if not self._bot: + return None + try: - self._bot.answer_callback_query(callback_query_id, text, show_alert) + self._bot.answer_callback_query(callback_query_id, text=text, show_alert=show_alert) return True except Exception as e: logger.error(f"回应回调查询失败:{str(e)}") return False + def __edit_message(self, chat_id: str, message_id: int, text: str, + buttons: Optional[List[List[dict]]] = None, + image: Optional[str] = None) -> Optional[bool]: + """ + 编辑已发送的消息 + :param chat_id: 聊天ID + :param message_id: 消息ID + :param text: 新的消息内容 + :param buttons: 按钮列表 + :param image: 图片URL或路径 + :return: 编辑是否成功 + """ + if not self._bot: + return None + + try: + + # 创建按钮键盘 + reply_markup = None + if buttons: + reply_markup = self._create_inline_keyboard(buttons) + + if image: + # 如果有图片,使用edit_message_media + media = InputMediaPhoto(media=image, caption=text, parse_mode="Markdown") + self._bot.edit_message_media( + chat_id=chat_id, + message_id=message_id, + media=media, + reply_markup=reply_markup + ) + else: + # 如果没有图片,使用edit_message_text + self._bot.edit_message_text( + chat_id=chat_id, + message_id=message_id, + text=text, + parse_mode="Markdown", + reply_markup=reply_markup + ) + return True + except Exception as e: + logger.error(f"编辑消息失败:{str(e)}") + return False + @retry(Exception, logger=logger) def __send_request(self, userid: Optional[str] = None, image="", caption="", reply_markup: Optional[InlineKeyboardMarkup] = None) -> bool: diff --git a/app/schemas/message.py b/app/schemas/message.py index 804e8cfa..fbfd1025 100644 --- a/app/schemas/message.py +++ b/app/schemas/message.py @@ -79,6 +79,10 @@ class Notification(BaseModel): targets: Optional[dict] = None # 按钮列表,格式:[[{"text": "按钮文本", "callback_data": "回调数据", "url": "链接"}]] buttons: Optional[List[List[dict]]] = None + # 原消息ID,用于编辑消息 + original_message_id: Optional[int] = None + # 原消息的聊天ID,用于编辑消息 + original_chat_id: Optional[str] = None def to_dict(self): """ @@ -279,6 +283,13 @@ class ChannelCapabilityManager: """ return cls.supports_capability(channel, ChannelCapability.CALLBACK_QUERIES) + @classmethod + def supports_editing(cls, channel: MessageChannel) -> bool: + """ + 检查渠道是否支持消息编辑 + """ + return cls.supports_capability(channel, ChannelCapability.MESSAGE_EDITING) + @classmethod def get_max_buttons_per_row(cls, channel: MessageChannel) -> int: """