From bd241a51647d367ee5e9fd94eb18d6237cc758dd Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 22 Jun 2025 09:37:01 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=88=A0=E9=99=A4=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/chain/__init__.py | 15 ++++++- app/chain/message.py | 14 ++++++- app/modules/slack/__init__.py | 27 ++++++++++++- app/modules/slack/slack.py | 67 ++++++++++++++++++++++++-------- app/modules/telegram/__init__.py | 23 +++++++++++ app/modules/telegram/telegram.py | 31 ++++++++++++++- app/schemas/message.py | 12 ++++++ 7 files changed, 167 insertions(+), 22 deletions(-) diff --git a/app/chain/__init__.py b/app/chain/__init__.py index e5e9acd9..13c45f24 100644 --- a/app/chain/__init__.py +++ b/app/chain/__init__.py @@ -22,7 +22,7 @@ from app.helper.service import ServiceConfigHelper from app.log import logger from app.schemas import TransferInfo, TransferTorrent, ExistMediaInfo, DownloadingTorrent, CommingMessage, Notification, \ WebhookEventInfo, TmdbEpisode, MediaPerson, FileItem, TransferDirectoryConf -from app.schemas.types import TorrentStatus, MediaType, MediaImageType, EventType +from app.schemas.types import TorrentStatus, MediaType, MediaImageType, EventType, MessageChannel from app.utils.object import ObjectUtils @@ -641,6 +641,19 @@ class ChainBase(metaclass=ABCMeta): return self.messagequeue.send_message("post_torrents_message", message=message, torrents=torrents, immediately=True if message.userid else False) + def delete_message(self, channel: MessageChannel, source: str, + message_id: Union[str, int], chat_id: Optional[Union[str, int]] = None) -> bool: + """ + 删除消息 + :param channel: 消息渠道 + :param source: 消息源(指定特定的消息模块) + :param message_id: 消息ID + :param chat_id: 聊天ID(如群组ID) + :return: 删除是否成功 + """ + return self.run_module("delete_message", channel=channel, source=source, + message_id=message_id, chat_id=chat_id) + def metadata_img(self, mediainfo: MediaInfo, season: Optional[int] = None, episode: Optional[int] = None) -> Optional[dict]: """ diff --git a/app/chain/message.py b/app/chain/message.py index 1e7b3ff9..b478b028 100644 --- a/app/chain/message.py +++ b/app/chain/message.py @@ -1,4 +1,3 @@ -import gc import re from typing import Any, Optional, Dict, Union, List @@ -275,6 +274,14 @@ class MessageChain(ChainBase): _current_page = 0 # 保存缓存 self.save_cache(user_cache, self._cache_file) + # 删除原消息 + if ChannelCapabilityManager.supports_deletion(channel): + self.delete_message( + channel=channel, + source=source, + message_id=original_message_id, + chat_id=original_chat_id + ) # 发送种子数据 logger.info(f"搜索到 {len(contexts)} 条数据,开始发送选择消息 ...") self.__post_torrents_message(channel=channel, @@ -505,6 +512,9 @@ class MessageChain(ChainBase): """ 处理按钮回调 """ + + global _current_media + # 提取回调数据 callback_data = text[9:] # 去掉 "CALLBACK:" 前缀 logger.info(f"处理按钮回调:{callback_data}") @@ -537,7 +547,7 @@ class MessageChain(ChainBase): text=page_text, original_message_id=original_message_id, original_chat_id=original_chat_id) else: - # 发送新消息 + # 处理新消息 self.handle_message(channel=channel, source=source, userid=userid, username=username, text=page_text) except IndexError: diff --git a/app/modules/slack/__init__.py b/app/modules/slack/__init__.py index f5a14d55..fc0e078c 100644 --- a/app/modules/slack/__init__.py +++ b/app/modules/slack/__init__.py @@ -222,13 +222,13 @@ class SlackModule(_ModuleBase, _MessageBase[Slack]): # 使用CALLBACK前缀标识按钮回调 text = f"CALLBACK:{callback_data}" username = msg_json.get("user", {}).get("name") - + # 获取原消息信息用于编辑 message_info = msg_json.get("message", {}) # Slack消息的时间戳作为消息ID message_ts = message_info.get("ts") channel_id = msg_json.get("channel", {}).get("id") or msg_json.get("container", {}).get("channel_id") - + logger.info(f"收到来自 {client_config.name} 的Slack按钮回调:" f"userid={userid}, username={username}, callback_data={callback_data}") @@ -320,3 +320,26 @@ class SlackModule(_ModuleBase, _MessageBase[Slack]): userid=message.userid, buttons=message.buttons, original_message_id=message.original_message_id, original_chat_id=message.original_chat_id) + + def delete_message(self, channel: MessageChannel, source: str, + message_id: str, chat_id: Optional[str] = None) -> bool: + """ + 删除消息 + :param channel: 消息渠道 + :param source: 指定的消息源 + :param message_id: 消息ID(Slack中为时间戳) + :param chat_id: 聊天ID(频道ID) + :return: 删除是否成功 + """ + success = False + for conf in self.get_configs().values(): + if channel != self._channel: + continue + if source != conf.name: + continue + client: Slack = self.get_instance(conf.name) + if client: + result = client.delete_msg(message_id=message_id, chat_id=chat_id) + if result: + success = True + return success diff --git a/app/modules/slack/slack.py b/app/modules/slack/slack.py index 155ea4bb..6cc78127 100644 --- a/app/modules/slack/slack.py +++ b/app/modules/slack/slack.py @@ -13,18 +13,16 @@ from app.core.metainfo import MetaInfo from app.log import logger from app.utils.string import StringUtils - lock = Lock() class Slack: - _client: WebClient = None _service: SocketModeHandler = None _ds_url = f"http://127.0.0.1:{settings.PORT}/api/v1/message?token={settings.API_TOKEN}" _channel = "" - def __init__(self, SLACK_OAUTH_TOKEN: Optional[str] = None, SLACK_APP_TOKEN: Optional[str] = None, + def __init__(self, SLACK_OAUTH_TOKEN: Optional[str] = None, SLACK_APP_TOKEN: Optional[str] = None, SLACK_CHANNEL: Optional[str] = None, **kwargs): if not SLACK_OAUTH_TOKEN or not SLACK_APP_TOKEN: @@ -197,7 +195,7 @@ class Slack: } ] }) - + # 判断是编辑消息还是发送新消息 if original_message_id and original_chat_id: # 编辑消息 @@ -258,7 +256,7 @@ class Slack: "type": "divider" }) index = 1 - + # 如果有自定义按钮,先添加所有媒体项,然后添加统一的按钮 if buttons: # 添加媒体列表(不带单独的选择按钮) @@ -288,7 +286,7 @@ class Slack: } ) index += 1 - + # 添加统一的自定义按钮(在所有媒体项之后) for button_row in buttons: elements = [] @@ -366,7 +364,7 @@ class Slack: } ) index += 1 - + # 判断是编辑消息还是发送新消息 if original_message_id and original_chat_id: # 编辑消息 @@ -423,7 +421,7 @@ class Slack: }] # 列表 index = 1 - + # 如果有自定义按钮,先添加种子列表,然后添加统一的按钮 if buttons: # 添加种子列表(不带单独的选择按钮) @@ -433,9 +431,9 @@ class Slack: meta = MetaInfo(torrent.title, torrent.description) link = torrent.page_url title_text = f"{meta.season_episode} " \ - f"{meta.resource_term} " \ - f"{meta.video_term} " \ - f"{meta.release_group}" + f"{meta.resource_term} " \ + f"{meta.video_term} " \ + f"{meta.release_group}" title_text = re.sub(r"\s+", " ", title_text).strip() free = torrent.volume_factor seeder = f"{torrent.seeders}↑" @@ -453,7 +451,7 @@ class Slack: } ) index += 1 - + # 添加统一的自定义按钮 for button_row in buttons: elements = [] @@ -493,9 +491,9 @@ class Slack: meta = MetaInfo(torrent.title, torrent.description) link = torrent.page_url title_text = f"{meta.season_episode} " \ - f"{meta.resource_term} " \ - f"{meta.video_term} " \ - f"{meta.release_group}" + f"{meta.resource_term} " \ + f"{meta.video_term} " \ + f"{meta.release_group}" title_text = re.sub(r"\s+", " ", title_text).strip() free = torrent.volume_factor seeder = f"{torrent.seeders}↑" @@ -530,7 +528,7 @@ class Slack: } ) index += 1 - + # 判断是编辑消息还是发送新消息 if original_message_id and original_chat_id: # 编辑消息 @@ -552,6 +550,43 @@ class Slack: logger.error(f"Slack消息发送失败: {msg_e}") return False + def delete_msg(self, message_id: str, chat_id: Optional[str] = None) -> Optional[bool]: + """ + 删除Slack消息 + :param message_id: 消息时间戳(Slack消息ID) + :param chat_id: 频道ID + :return: 删除是否成功 + """ + if not self._client: + return None + + try: + # 确定要删除消息的频道ID + if chat_id: + target_channel = chat_id + else: + target_channel = self.__find_public_channel() + + if not target_channel: + logger.error("无法确定要删除消息的Slack频道") + return False + + # 删除消息 + result = self._client.chat_delete( + channel=target_channel, + ts=message_id + ) + + if result.get("ok"): + logger.info(f"成功删除Slack消息: channel={target_channel}, ts={message_id}") + return True + else: + logger.error(f"删除Slack消息失败: {result.get('error', 'unknown error')}") + return False + except Exception as e: + logger.error(f"删除Slack消息异常: {str(e)}") + return False + def __find_public_channel(self): """ 查找公共频道 diff --git a/app/modules/telegram/__init__.py b/app/modules/telegram/__init__.py index 95609352..a747dd68 100644 --- a/app/modules/telegram/__init__.py +++ b/app/modules/telegram/__init__.py @@ -286,6 +286,29 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]): original_message_id=message.original_message_id, original_chat_id=message.original_chat_id) + def delete_message(self, channel: MessageChannel, source: str, + message_id: int, chat_id: Optional[int] = None) -> bool: + """ + 删除消息 + :param channel: 消息渠道 + :param source: 指定的消息源 + :param message_id: 消息ID + :param chat_id: 聊天ID + :return: 删除是否成功 + """ + success = False + for conf in self.get_configs().values(): + if channel != self._channel: + continue + if source != conf.name: + continue + client: Telegram = self.get_instance(conf.name) + if client: + result = client.delete_msg(message_id=message_id, chat_id=chat_id) + if result: + success = True + return success + def register_commands(self, commands: Dict[str, dict]): """ 注册命令,实现这个函数接收系统可用的命令菜单 diff --git a/app/modules/telegram/telegram.py b/app/modules/telegram/telegram.py index 51897799..feb6a437 100644 --- a/app/modules/telegram/telegram.py +++ b/app/modules/telegram/telegram.py @@ -336,6 +336,35 @@ class Telegram: logger.error(f"回应回调查询失败:{str(e)}") return False + def delete_msg(self, message_id: int, chat_id: Optional[int] = None) -> Optional[bool]: + """ + 删除Telegram消息 + :param message_id: 消息ID + :param chat_id: 聊天ID + :return: 删除是否成功 + """ + if not self._telegram_token or not self._telegram_chat_id: + return None + + try: + # 确定要删除消息的聊天ID + if chat_id: + target_chat_id = chat_id + else: + target_chat_id = self._telegram_chat_id + + # 删除消息 + result = self._bot.delete_message(chat_id=target_chat_id, message_id=int(message_id)) + if result: + logger.info(f"成功删除Telegram消息: chat_id={target_chat_id}, message_id={message_id}") + return True + else: + logger.error(f"删除Telegram消息失败: chat_id={target_chat_id}, message_id={message_id}") + return False + except Exception as e: + logger.error(f"删除Telegram消息异常: {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]: @@ -352,7 +381,7 @@ class Telegram: return None try: - + # 创建按钮键盘 reply_markup = None if buttons: diff --git a/app/schemas/message.py b/app/schemas/message.py index 59345c89..cf200877 100644 --- a/app/schemas/message.py +++ b/app/schemas/message.py @@ -145,6 +145,8 @@ class ChannelCapability(Enum): MENU_COMMANDS = "menu_commands" # 支持消息编辑 MESSAGE_EDITING = "message_editing" + # 支持消息删除 + MESSAGE_DELETION = "message_deletion" # 支持回调查询 CALLBACK_QUERIES = "callback_queries" # 支持富文本 @@ -182,6 +184,7 @@ class ChannelCapabilityManager: ChannelCapability.INLINE_BUTTONS, ChannelCapability.MENU_COMMANDS, ChannelCapability.MESSAGE_EDITING, + ChannelCapability.MESSAGE_DELETION, ChannelCapability.CALLBACK_QUERIES, ChannelCapability.RICH_TEXT, ChannelCapability.IMAGES, @@ -205,6 +208,8 @@ class ChannelCapabilityManager: channel=MessageChannel.Slack, capabilities={ ChannelCapability.INLINE_BUTTONS, + ChannelCapability.MESSAGE_EDITING, + ChannelCapability.MESSAGE_DELETION, ChannelCapability.CALLBACK_QUERIES, ChannelCapability.RICH_TEXT, ChannelCapability.IMAGES, @@ -290,6 +295,13 @@ class ChannelCapabilityManager: """ return cls.supports_capability(channel, ChannelCapability.MESSAGE_EDITING) + @classmethod + def supports_deletion(cls, channel: MessageChannel) -> bool: + """ + 检查渠道是否支持消息删除 + """ + return cls.supports_capability(channel, ChannelCapability.MESSAGE_DELETION) + @classmethod def get_max_buttons_per_row(cls, channel: MessageChannel) -> int: """