From 135d0292de30b0d7fa490ab65afdb006a236c2df Mon Sep 17 00:00:00 2001 From: liuwei Date: Wed, 30 Apr 2025 17:43:28 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=85=A5=E5=8A=9F=E8=83=BD,=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=92=A4=E5=9B=9E,=E5=B0=86=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E5=8F=91=E9=80=81=E5=90=8E,5=E7=A7=92?= =?UTF-8?q?=E6=92=A4=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/xiuren_image/main.py | 9 +- robot.py | 9 +- utils/__init__.py | 0 utils/revoke/__init__.py | 0 utils/revoke/message_auto_revoke.py | 47 ++++++++++ utils/revoke/message_revoke_manager.py | 118 +++++++++++++++++++++++++ utils/robot_cmd/robot_command.py | 2 +- wechat_ipad/client/message.py | 7 +- 8 files changed, 181 insertions(+), 11 deletions(-) create mode 100644 utils/__init__.py create mode 100644 utils/revoke/__init__.py create mode 100644 utils/revoke/message_auto_revoke.py create mode 100644 utils/revoke/message_revoke_manager.py diff --git a/plugins/xiuren_image/main.py b/plugins/xiuren_image/main.py index 64dbcfe..b0b8d84 100644 --- a/plugins/xiuren_image/main.py +++ b/plugins/xiuren_image/main.py @@ -8,6 +8,7 @@ from typing import Dict, Any, List, Optional, Tuple from plugin_common.message_plugin_interface import MessagePluginInterface from plugin_common.plugin_interface import PluginStatus from utils.decorator.plugin_decorators import plugin_stats_decorator +from utils.revoke.message_auto_revoke import MessageAutoRevoke from utils.robot_cmd.robot_command import Feature, PermissionStatus, GroupBotManager from utils.decorator.points_decorator import plugin_points_cost from wechat_ipad import WechatAPIClient @@ -98,7 +99,7 @@ class XiurenImagePlugin(MessagePluginInterface): roomid = message.get("roomid", "") gbm: GroupBotManager = message.get("gbm") bot: WechatAPIClient = message.get("bot") - + revoke: MessageAutoRevoke = message.get("revoke") # 检查权限 if roomid and gbm.get_group_permission(roomid, Feature.PIC) == PermissionStatus.DISABLED: return False, "没有权限" @@ -107,8 +108,10 @@ class XiurenImagePlugin(MessagePluginInterface): # 获取随机图片 pic_path = self._get_random_pic() if not pic_path: - await bot.send_text_message((roomid if roomid else sender), f"❌未找到图片资源", - sender) + client_msg_id, create_time, new_msg_id = await bot.send_text_message((roomid if roomid else sender), + f"❌未找到图片资源", + sender) + revoke.add_message_to_revoke(roomid, client_msg_id, create_time, new_msg_id, 30) return False, "未找到图片资源" # 发送图片 diff --git a/robot.py b/robot.py index 4b79c69..0dfed54 100644 --- a/robot.py +++ b/robot.py @@ -16,6 +16,7 @@ from plugin_common.message_plugin_interface import MessagePluginInterface from plugin_common.plugin_interface import PluginStatus from plugin_common.plugin_manager import PluginManager from plugin_common.plugin_registry import PluginRegistry +from utils.revoke.message_auto_revoke import MessageAutoRevoke from utils.robot_cmd.robot_command import GroupBotManager, Feature, PermissionStatus from db.connection import DBConnectionManager @@ -46,7 +47,7 @@ class Robot(Job): self.nickname = None self.alias = None self.phone = None - + self.message_auto_revoke: MessageAutoRevoke = None self.LOG.info(f"DB+REDIS 连接池开始初始化") # 使用单例模式获取实例 self.db_manager = DBConnectionManager.get_instance( @@ -140,6 +141,7 @@ class Robot(Job): self.LOG.info("启动wechat_ipad bot") # 调用登录接口 self.ipad_bot = wechat_ipad.WechatAPIClient(server_ip, server_port) + self.message_auto_revoke = MessageAutoRevoke(self.ipad_bot) wxid = self.ipad_config.get("wxid", "") device_name = self.ipad_config.get("device_name", "") device_id = self.ipad_config.get("device_id", "") @@ -369,7 +371,7 @@ class Robot(Job): # 处理群聊命令或私聊命令 if from_user == self.wxid: # 自己发送的消息 if is_group: - rsp = self.gbm.handle_command(group_id, content) + rsp = self.gbm.handle_command(group_id, content.clean_content) if rsp is not None: await self.send_text(rsp, group_id) else: @@ -518,7 +520,8 @@ class Robot(Job): "all_contacts": self.allContacts, "full_wx_msg": msg, "gbm": self.gbm, - "bot": self.ipad_bot + "bot": self.ipad_bot, + "revoke": self.message_auto_revoke } # 检查插件是否可以处理该消息 diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/revoke/__init__.py b/utils/revoke/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/revoke/message_auto_revoke.py b/utils/revoke/message_auto_revoke.py new file mode 100644 index 0000000..3a66873 --- /dev/null +++ b/utils/revoke/message_auto_revoke.py @@ -0,0 +1,47 @@ +from typing import Optional +from utils.revoke.message_revoke_manager import MessageRevokeManager +from wechat_ipad import WechatAPIClient + + +class MessageAutoRevoke: + """消息自动撤回工具类""" + + def __init__(self, client: WechatAPIClient, default_delay: float = 90.0): + """ + 初始化消息自动撤回工具 + + Args: + client: WechatAPIClient实例 + default_delay: 默认撤回延时(秒) + """ + self.client = client + self.revoke_manager = MessageRevokeManager(default_delay) + # 设置撤回回调函数 + self.revoke_manager.set_revoke_callback(self.client.revoke_message) + + def add_message_to_revoke(self, wxid: str, client_msg_id: int, create_time: int, new_msg_id: int, + delay: Optional[float] = None) -> None: + """ + 添加消息到撤回队列 + + Args: + wxid: 接收人wxid + client_msg_id: 消息ID + create_time: 消息创建时间 + new_msg_id: 新消息ID + delay: 延迟撤回时间(秒),如果为None则使用默认值 + """ + self.revoke_manager.add_message_to_revoke(wxid, client_msg_id, create_time, new_msg_id, delay) + + def set_default_delay(self, delay: float) -> None: + """ + 设置默认撤回延时 + + Args: + delay: 默认撤回延时(秒) + """ + self.revoke_manager.set_default_delay(delay) + + def stop(self) -> None: + """停止消息撤回任务""" + self.revoke_manager.stop() \ No newline at end of file diff --git a/utils/revoke/message_revoke_manager.py b/utils/revoke/message_revoke_manager.py new file mode 100644 index 0000000..19eb28f --- /dev/null +++ b/utils/revoke/message_revoke_manager.py @@ -0,0 +1,118 @@ +import asyncio +import time +from typing import Dict, List, Tuple, Optional, Callable +from loguru import logger + + +class MessageRevokeManager: + """消息撤回管理器,用于管理需要延时撤回的消息""" + + def __init__(self, default_delay: float = 90.0): + """ + 初始化消息撤回管理器 + + Args: + default_delay: 默认撤回延时时间(秒),默认为90秒 + """ + self.revoke_queue: List[Tuple[float, str, int, int, int]] = [] # (撤回时间, wxid, client_msg_id, create_time, new_msg_id) + self.default_delay = default_delay + self.running = False + self.task = None + self.logging = logger + self.revoke_callback = None + + def add_message_to_revoke(self, wxid: str, client_msg_id: int, create_time: int, new_msg_id: int, + delay: Optional[float] = None) -> None: + """ + 添加消息到撤回队列 + + Args: + wxid: 接收人wxid + client_msg_id: 消息ID + create_time: 消息创建时间 + new_msg_id: 新消息ID + delay: 延迟撤回时间(秒),如果为None则使用默认值 + """ + if delay is None: + delay = self.default_delay + + revoke_time = time.time() + delay + self.revoke_queue.append((revoke_time, wxid, client_msg_id, create_time, new_msg_id)) + self.revoke_queue.sort(key=lambda x: x[0]) # 按撤回时间排序 + + # 如果撤回任务未运行,则启动 + if not self.running: + self.start() + + def start(self) -> None: + """启动消息撤回任务""" + if self.running: + return + + self.running = True + self.task = asyncio.create_task(self._process_revoke_queue()) + self.logging.info("消息自动撤回任务已启动") + + def stop(self) -> None: + """停止消息撤回任务""" + if not self.running: + return + + self.running = False + if self.task: + self.task.cancel() + self.logging.info("消息自动撤回任务已停止") + + def set_revoke_callback(self, callback: Callable) -> None: + """ + 设置撤回回调函数 + + Args: + callback: 撤回消息的回调函数,接受wxid, client_msg_id, create_time, new_msg_id参数 + """ + self.revoke_callback = callback + + def set_default_delay(self, delay: float) -> None: + """ + 设置默认撤回延时 + + Args: + delay: 默认撤回延时(秒) + """ + self.default_delay = delay + + async def _process_revoke_queue(self) -> None: + """处理撤回队列的异步任务""" + try: + while self.running: + now = time.time() + + # 检查是否有需要撤回的消息 + while self.revoke_queue and self.revoke_queue[0][0] <= now: + revoke_time, wxid, client_msg_id, create_time, new_msg_id = self.revoke_queue.pop(0) + try: + # 通过回调函数调用实际的撤回方法 + if self.revoke_callback and callable(self.revoke_callback): + await self.revoke_callback(wxid, client_msg_id, create_time, new_msg_id) + self.logging.info(f"已自动撤回消息: wxid={wxid}, msg_id={client_msg_id}") + except Exception as e: + self.logging.error(f"自动撤回消息失败: {e}") + + # 如果队列为空,等待一段时间 + if not self.revoke_queue: + self.running = False + self.logging.info("撤回队列为空,撤回任务已停止") + break + + # 计算下一个需要撤回的消息的等待时间 + next_revoke_time = self.revoke_queue[0][0] + wait_time = max(0.1, next_revoke_time - time.time()) + + # 等待到下一个撤回时间 + await asyncio.sleep(wait_time) + + except asyncio.CancelledError: + self.logging.info("消息撤回任务被取消") + except Exception as e: + self.logging.error(f"消息撤回任务异常: {e}") + self.running = False \ No newline at end of file diff --git a/utils/robot_cmd/robot_command.py b/utils/robot_cmd/robot_command.py index 60b00f6..32a4905 100644 --- a/utils/robot_cmd/robot_command.py +++ b/utils/robot_cmd/robot_command.py @@ -142,7 +142,7 @@ class GroupBotManager: @staticmethod def handle_command(group_id, command_str): """统一处理群功能指令""" - print(f"PermissionStatus handle_command command_str: {command_str}") + # print(f"PermissionStatus handle_command command_str: {command_str}") # 命令解析 command_parts = command_str.strip().split("-") diff --git a/wechat_ipad/client/message.py b/wechat_ipad/client/message.py index 349dd23..216aef4 100644 --- a/wechat_ipad/client/message.py +++ b/wechat_ipad/client/message.py @@ -93,12 +93,12 @@ class MessageMixin(WechatAPIClientBase): "NewMsgId": new_msg_id} response = await session.post(f'http://{self.ip}:{self.port}/api/Msg/Revoke', json=json_param) json_resp = await response.json() - if json_resp.get("Success"): self.logging.info("消息撤回成功: 对方wxid:{} ClientMsgId:{} CreateTime:{} NewMsgId:{}", wxid, client_msg_id, - new_msg_id) + create_time, + new_msg_id) # 确保四个参数都正确传入 return True else: self.error_handler(json_resp) @@ -144,8 +144,7 @@ class MessageMixin(WechatAPIClientBase): if json_resp.get("Success"): self.logging.info("发送文字消息: 对方wxid:{} at:{} 内容:{}", wxid, at, content) data = json_resp.get("Data") - self.logging.info("send msg:{} ", data) - return data.get("List")[0].get("ClientMsgid"), data.get("List")[0].get("Createtime"), data.get("List")[ + return data.get("List")[0].get("ClientMsgId"), data.get("List")[0].get("CreateTime"), data.get("List")[ 0].get("NewMsgId") else: self.error_handler(json_resp)