From fc757151e4c0e0c0270067df812ff55ce4df0891 Mon Sep 17 00:00:00 2001 From: liuwei Date: Wed, 23 Apr 2025 16:11:06 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/contacts_db.py | 98 +++++++++- gewechat/api/callback.py | 10 +- gewechat/client/get_chatroom_members.py | 21 +- gewechat/response/__init__.py | 0 gewechat/response/gewe_resp.py | 56 ++++++ plugins/group_member_change/main.py | 248 +++++++++--------------- plugins/music/main.py | 30 ++- plugins/plugin_manager/main.py | 44 ++--- plugins/video_man/main.py | 2 +- robot.py | 48 ++++- utils/wechat/contact_manager.py | 2 + 11 files changed, 345 insertions(+), 214 deletions(-) create mode 100644 gewechat/response/__init__.py create mode 100644 gewechat/response/gewe_resp.py diff --git a/db/contacts_db.py b/db/contacts_db.py index 53c8fc6..6881791 100644 --- a/db/contacts_db.py +++ b/db/contacts_db.py @@ -86,7 +86,27 @@ class ContactsDBOperator(BaseDBOperator): ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信群成员信息表'; """) - self.logger.info("成功创建或确认微信联系人表和群成员表存在") + self.execute_update(""" + CREATE TABLE IF NOT EXISTS t_chatrooms ( + id INT AUTO_INCREMENT PRIMARY KEY, + chatroom_id VARCHAR(64) NOT NULL COMMENT '群聊ID', + nick_name VARCHAR(128) COMMENT '群昵称', + py_initial VARCHAR(128) COMMENT '群昵称拼音首字母', + quan_pin VARCHAR(256) COMMENT '群昵称全拼', + sex TINYINT COMMENT '性别', + remark VARCHAR(128) COMMENT '备注', + remark_py_initial VARCHAR(128) COMMENT '备注拼音首字母', + remark_quan_pin VARCHAR(256) COMMENT '备注全拼', + chat_room_notify TINYINT COMMENT '群通知', + chat_room_owner VARCHAR(64) COMMENT '群主微信ID', + small_head_img_url TEXT COMMENT '群头像URL', + member_list TEXT COMMENT '成员列表(JSON)', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + UNIQUE KEY `idx_chatroom_id` (`chatroom_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信群信息表'; + """) + self.logger.info("成功创建或确认微信群信息表存在") except Exception as e: self.logger.error(f"创建微信联系人表或群成员表失败: {e}") raise @@ -255,7 +275,11 @@ class ContactsDBOperator(BaseDBOperator): """ try: sql = """ - SELECT user_name, nick_name, remark FROM t_wechat_contacts + SELECT user_name, nick_name, remark + FROM t_wechat_contacts + union all + SELECT wxid as user_name, nick_name, display_name as remark + FROM t_chatroom_member """ results = self.execute_query(sql) @@ -411,3 +435,73 @@ class ContactsDBOperator(BaseDBOperator): except Exception as e: self.logger.error(f"处理群聊{chatroom_id}成员详情数据失败: {e}") return False + + def save_chatroom_info(self, chatroom_data: dict) -> bool: + """保存群信息到数据库""" + try: + data = { + 'chatroom_id': chatroom_data.get('chatroomId', ''), + 'nick_name': chatroom_data.get('nickName', ''), + 'py_initial': chatroom_data.get('pyInitial', ''), + 'quan_pin': chatroom_data.get('quanPin', ''), + 'sex': chatroom_data.get('sex', 0), + 'remark': chatroom_data.get('remark', ''), + 'remark_py_initial': chatroom_data.get('remarkPyInitial', ''), + 'remark_quan_pin': chatroom_data.get('remarkQuanPin', ''), + 'chat_room_notify': chatroom_data.get('chatRoomNotify', 0), + 'chat_room_owner': chatroom_data.get('chatRoomOwner', ''), + 'small_head_img_url': chatroom_data.get('smallHeadImgUrl', ''), + 'member_list': json.dumps(chatroom_data.get('memberList', [])) + } + fields = ', '.join(data.keys()) + placeholders = ', '.join(['%s'] * len(data)) + update_clause = ', '.join([f"{k}=VALUES({k})" for k in data.keys() if k != 'chatroom_id']) + values = tuple(data.values()) + sql = f""" + INSERT INTO t_chatrooms ({fields}) + VALUES ({placeholders}) + ON DUPLICATE KEY UPDATE {update_clause} + """ + self.execute_update(sql, values) + self.logger.info(f"成功保存群聊 {data['chatroom_id']} 信息") + return True + except Exception as e: + self.logger.error(f"保存群聊信息失败: {e}") + return False + + def get_chatroom_info(self, chatroom_id: str) -> Optional[dict]: + """获取群信息""" + try: + sql = "SELECT * FROM t_chatrooms WHERE chatroom_id = %s LIMIT 1" + result = self.execute_query(sql, (chatroom_id,), fetch_one=True) + if result and result.get('member_list'): + result['member_list'] = json.loads(result['member_list']) + return result + except Exception as e: + self.logger.error(f"获取群聊{chatroom_id}信息失败: {e}") + return None + + def update_chatroom_info(self, chatroom_id: str, update_data: dict) -> bool: + """更新群信息""" + try: + set_clause = ', '.join([f"{k}=%s" for k in update_data.keys()]) + values = list(update_data.values()) + values.append(chatroom_id) + sql = f"UPDATE t_chatrooms SET {set_clause} WHERE chatroom_id = %s" + self.execute_update(sql, tuple(values)) + self.logger.info(f"成功更新群聊 {chatroom_id} 信息") + return True + except Exception as e: + self.logger.error(f"更新群聊{chatroom_id}信息失败: {e}") + return False + + def delete_chatroom_info(self, chatroom_id: str) -> bool: + """删除群信息""" + try: + sql = "DELETE FROM t_chatrooms WHERE chatroom_id = %s" + self.execute_update(sql, (chatroom_id,)) + self.logger.info(f"成功删除群聊 {chatroom_id} 信息") + return True + except Exception as e: + self.logger.error(f"删除群聊{chatroom_id}信息失败: {e}") + return False diff --git a/gewechat/api/callback.py b/gewechat/api/callback.py index c263b28..9c4be16 100644 --- a/gewechat/api/callback.py +++ b/gewechat/api/callback.py @@ -3,6 +3,7 @@ from gewechat.call_back_message.message import WxMessage, MessageType, AppMessag import logging from robot import Robot +from utils.json_converter import json_to_object router = APIRouter() logger = logging.getLogger(__name__) @@ -24,12 +25,12 @@ async def callback(request: Request): # 获取原始JSON数据 json_data = await request.json() logger.info(f"收到回调消息: {json_data}") - + # 检查是否为测试消息 if 'testMsg' in json_data and json_data.get('testMsg') == '验证回调地址是否可用': logger.info("收到回调地址验证消息,返回成功") return {"code": 0, "message": "success"} - + # 创建消息对象 msg = WxMessage.from_json(json_data) @@ -67,12 +68,7 @@ async def handle_add_message(msg: WxMessage): async def handle_mod_contacts(msg: WxMessage): - """处理联系人变更""" logger.info(f"联系人信息变更: {msg.raw_data}") - # 获取对应的Robot实例并刷新联系人 - # robot = robot_instances.get(msg.appid) - # if robot: - # robot.refresh_contacts() async def handle_del_contacts(msg: WxMessage): diff --git a/gewechat/client/get_chatroom_members.py b/gewechat/client/get_chatroom_members.py index a395115..1caa840 100644 --- a/gewechat/client/get_chatroom_members.py +++ b/gewechat/client/get_chatroom_members.py @@ -56,5 +56,24 @@ def set_call_back(): print(response.text) +def send_file(): + + url = "/message/postFile" + + payload = json.dumps({ + "appId": app_id, + "toWxid": "52418238895@chatroom", + "fileName": "favicon.ico", + "fileUrl": "http://192.168.2.210:8888/static/favicon.ico" + }) + headers = { + 'X-GEWE-TOKEN': 'cb43f52db27e4a56bb6ec7da54373582', + 'Content-Type': 'application/json' + } + + response = requests.request("POST", base_url+url, headers=headers, data=payload) + + print(response.text) + if __name__ == '__main__': - set_call_back() + send_file() diff --git a/gewechat/response/__init__.py b/gewechat/response/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gewechat/response/gewe_resp.py b/gewechat/response/gewe_resp.py new file mode 100644 index 0000000..6303d1b --- /dev/null +++ b/gewechat/response/gewe_resp.py @@ -0,0 +1,56 @@ +import typing + + +class GeweResponse: + """ + 通用的 Gewechat 响应处理类 + """ + + def __init__(self, resp: dict): + """ + :param resp: gewechat接口返回的原始字典 + """ + self.raw = resp + self.ret = resp.get("ret") + self.msg = resp.get("msg") + self.data = resp.get("data") + + def is_success(self) -> bool: + """ + 判断请求是否成功 + :return: True/False + """ + return self.ret == 200 + + def get_msg(self) -> str: + """ + 获取返回消息 + :return: 消息字符串 + """ + return self.msg or "" + + def get_data(self) -> typing.Any: + """ + 获取返回的数据对象,类型可能为dict、list、str等 + :return: data字段内容 + """ + return self.data + + def get_data_as_list(self) -> list: + """ + 获取data字段为list类型(若不是则返回空列表) + """ + if isinstance(self.data, list): + return self.data + return [] + + def get_data_as_dict(self) -> dict: + """ + 获取data字段为dict类型(若不是则返回空字典) + """ + if isinstance(self.data, dict): + return self.data + return {} + + def __repr__(self): + return f"" diff --git a/plugins/group_member_change/main.py b/plugins/group_member_change/main.py index a75ceaa..b599912 100644 --- a/plugins/group_member_change/main.py +++ b/plugins/group_member_change/main.py @@ -4,6 +4,10 @@ import time from datetime import datetime from typing import Dict, Any, List, Optional, Tuple +import xml.etree.ElementTree as ET + +from gewechat.client import gewe_client +from gewechat.response.gewe_resp import GeweResponse from plugin_common.message_plugin_interface import MessagePluginInterface from plugin_common.plugin_interface import PluginStatus from utils.robot_cmd.robot_command import Feature, PermissionStatus, GroupBotManager @@ -61,160 +65,73 @@ class GroupMemberChangePlugin(MessagePluginInterface): def process_message(self, message: Dict[str, Any]) -> Tuple[bool, Optional[str]]: """处理接收到的消息""" - # 此插件主要通过定时任务工作,不处理消息 + + content = str(message.get("content", "")).strip() + self.LOG.info(f"插件执行: {self.name}:{content}") + command = content.split(" ")[0] + sender = message.get("sender") + roomid = message.get("roomid", "") + gbm: GroupBotManager = message.get("gbm") + + # 检查权限 + if roomid and gbm.get_group_permission(roomid, Feature.GROUP_MEMBER_CHANGE) == PermissionStatus.DISABLED: + return False, "没有权限" + + xml_content = str(content).strip().replace("\n", "").replace("\t", "") + root = ET.fromstring(xml_content) + + if root.tag != "sysmsg": + return False, "非本次需要处理消息" + + # 检查是否是进群消息 + if root.attrib.get("type") == "sysmsgtemplate": + sys_msg_template = root.find("sysmsgtemplate") + if sys_msg_template is None: + return False, "非本次需要处理消息" + + template = sys_msg_template.find("content_template") + if template is None: + return False, "非本次需要处理消息" + + template_type = template.attrib.get("type") + if template_type not in ["tmpl_type_profile", "tmpl_type_profilewithrevoke"]: + return False, "非本次需要处理消息" + + template_text = template.find("template").text + + if '"$names$"加入了群聊' in template_text: # 直接加入群聊 + new_members = self._parse_member_info(root, "names") + elif '"$username$"邀请"$names$"加入了群聊' in template_text: # 通过邀请加入群聊 + new_members = self._parse_member_info(root, "names") + elif '你邀请"$names$"加入了群聊' in template_text: # 自己邀请成员加入群聊 + new_members = self._parse_member_info(root, "names") + elif '"$adder$"通过扫描"$from$"分享的二维码加入群聊' in template_text: # 通过二维码加入群聊 + new_members = self._parse_member_info(root, "adder") + elif '"$adder$"通过"$from$"的邀请二维码加入群聊' in template_text: + new_members = self._parse_member_info(root, "adder") + else: + self.LOG.warning(f"未知的入群方式: {template_text}") + return False, "非本次需要处理消息" + + if not new_members: + return False, "非本次需要处理消息" + + for member in new_members: + wxid = member["wxid"] + nickname = member["nickname"] + + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + member_wxids = [wxid] + profile = gewe_client.client.get_chatroom_member_detail(gewe_client.client.app_id, member_wxids) + data = GeweResponse(profile).get_data() + gewe_client.client.post_link(gewe_client.client.app_id, sender, + title=f"👏欢迎 {nickname} 加入群聊!🎉", + description=f"⌚时间:{now}\n", + url="", + thumb_url=data[0].get("BigHeadImgUrl", "")) + return True, "已发送进群欢迎语" return False, "无需执行" - def start(self) -> bool: - """启动插件""" - if self.status == PluginStatus.RUNNING: - self.LOG.warning(f"{self.name} 插件已经在运行中") - return True - - self.stop_flag = False - - # 启动监控线程 - self.monitor_thread = threading.Thread(target=self._monitor_groups, daemon=True) - self.monitor_thread.start() - - self.status = PluginStatus.RUNNING - self.LOG.info(f"[{self.name}] 插件已启动") - return True - - def stop(self) -> bool: - """停止插件""" - if self.status != PluginStatus.RUNNING: - self.LOG.warning(f"{self.name} 插件未在运行中") - return True - - self.stop_flag = True - - if self.monitor_thread and self.monitor_thread.is_alive(): - self.monitor_thread.join(timeout=5) - - self.status = PluginStatus.STOPPED - self.LOG.info(f"[{self.name}] 插件已停止") - return True - - def _monitor_groups(self): - """监控群成员变化的线程函数""" - self.LOG.info("群成员监控线程已启动") - - while not self.stop_flag: - try: - # 获取所有启用了机器人的群组 - group_list = GroupBotManager.get_group_list() - - for group_id in group_list: - # 检查群是否启用了成员变更提醒功能 - if GroupBotManager.get_group_permission(group_id, - Feature.GROUP_MEMBER_CHANGE) == PermissionStatus.ENABLED: - self._check_group_members(group_id) - - # 等待指定的时间间隔 - for _ in range(self.check_interval): - if self.stop_flag: - break - time.sleep(1) - - except Exception as e: - self.LOG.error(f"监控群成员变化时发生错误: {e}", exc_info=True) - time.sleep(5) # 发生错误时短暂休眠 - - self.LOG.info("群成员监控线程已停止") - - def _check_group_members(self, group_id: str): - """检查指定群的成员变化""" - try: - # 获取当前群成员 - current_members = self.message_util.get_chatroom_members(group_id) - - # 添加安全检查:如果获取到的成员列表为空,可能是接口异常 - if not current_members: - self.LOG.warning(f"获取群 {group_id} 成员列表为空,可能是接口异常,跳过本次检查") - return - - # 如果是首次检查该群 - if group_id not in self.local_members: - self.LOG.info(f"首次检查群 {group_id},记录当前成员") - self.local_members[group_id] = current_members - return - - # 获取上次记录的成员 - previous_members = self.local_members[group_id] - - # 添加安全检查:如果上次记录的成员为空,重新初始化 - if not previous_members: - self.LOG.warning(f"群 {group_id} 上次记录的成员列表为空,重新初始化") - self.local_members[group_id] = current_members - return - - # 比较成员变化 - current_member_ids = set(current_members.keys()) - previous_member_ids = set(previous_members.keys()) - - # 找出退群的成员 - left_members = previous_member_ids - current_member_ids - - # 添加安全检查:如果退群人数超过阈值,可能是异常情况 - if len(left_members) > len(previous_member_ids) * 0.5: # 如果超过50%的成员"退群" - self.LOG.warning( - f"群 {group_id} 检测到超过50%的成员退群 ({len(left_members)}/{len(previous_member_ids)}),判定为异常情况,跳过通知") - # 更新本地缓存但不发送通知 - self.local_members[group_id] = current_members - return - - # 找出新加入的成员 - joined_members = current_member_ids - previous_member_ids - - # 如果有成员变化 - if left_members or joined_members: - self.LOG.info(f"群 {group_id} 成员发生变化: {len(joined_members)}人加入, {len(left_members)}人退出") - - # 处理退群成员 - for wxid in left_members: - nickname = previous_members[wxid] - self._send_leave_notification(group_id, wxid, nickname) - - # 处理新加入成员 - for wxid in joined_members: - nickname = current_members[wxid] - self._send_join_notification(group_id, wxid, nickname) # 添加欢迎新成员的功能 - - # 更新本地缓存 - self.local_members[group_id] = current_members - - except Exception as e: - self.LOG.error(f"检查群 {group_id} 成员变化时发生错误: {e}", exc_info=True) - # 异常情况下不更新缓存,避免错误数据导致误判 - - def _send_leave_notification(self, group_id: str, wxid: str, nickname: str): - """发送成员退群通知""" - now_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - - message = f"""【退群提醒】 -用户: {nickname} -微信号: {wxid} -退群时间: {now_time} -""" - # 使用message_util发送消息 - self.message_util.send_text(message, group_id) - self.LOG.info(f"已发送退群通知: {nickname} 退出群 {group_id}") - - def _send_join_notification(self, group_id: str, wxid: str, nickname: str): - """发送成员入群通知""" - now_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - - message = f""" -欢迎新成员: {nickname} -⌚️: {now_time} -""" - # 使用message_util发送消息 编写一个 send_rich_text 方法 - self.message_util.send_rich_text("bot", "gh_bot", f"欢迎 {nickname} 加入群聊", message, - "https://hot.imsyy.top/#/", - "https://mmbiz.qpic.cn/mmbiz_png/bS1825ympzGML4gV6ibEFiaNA8Ycv6kCOo6tAwy5VntjeUGS0O2QQYeiakgIcpeFR9e0uCwl6nuQGib6f39xxkeFUQ/640?wx_fmt=png&", - group_id) - self.LOG.info(f"已发送入群通知: {nickname} 加入群 {group_id}") - @property def commands(self) -> List[str]: """插件支持的命令列表""" @@ -223,3 +140,30 @@ class GroupMemberChangePlugin(MessagePluginInterface): def get_help(self) -> str: """获取插件帮助信息""" return "群成员变更监控插件:自动监控群成员变动并发送通知。" + + def _parse_member_info(self, root: ET.Element, link_name: str = "names") -> list[dict]: + """解析新成员信息""" + new_members = [] + try: + # 查找指定链接中的成员列表 + names_link = root.find(f".//link[@name='{link_name}']") + if names_link is None: + return new_members + + memberlist = names_link.find("memberlist") + + if memberlist is None: + return new_members + + for member in memberlist.findall("member"): + username = member.find("username").text + nickname = member.find("nickname").text + new_members.append({ + "wxid": username, + "nickname": nickname + }) + + except Exception as e: + self.LOG.warning(f"解析新成员信息失败: {e}") + + return new_members diff --git a/plugins/music/main.py b/plugins/music/main.py index dbcaf5e..7d14385 100644 --- a/plugins/music/main.py +++ b/plugins/music/main.py @@ -1,9 +1,9 @@ import logging import requests -import lz4.block as lb from typing import Dict, Any, List, Optional, Tuple - +from gewechat.client import gewe_client +from gewechat.response.gewe_resp import GeweResponse from plugin_common.message_plugin_interface import MessagePluginInterface from plugin_common.plugin_interface import PluginStatus from utils.decorator.plugin_decorators import plugin_stats_decorator @@ -93,7 +93,7 @@ class MusicPlugin(MessagePluginInterface): # 检查命令格式 if len(content.split(" ")) == 1: self.message_util.send_text(f"❌命令格式错误!\n{self.command_format}", - (roomid if roomid else sender), sender) + (roomid if roomid else sender), sender) return False, "命令格式错误" # 检查权限 @@ -108,11 +108,11 @@ class MusicPlugin(MessagePluginInterface): song_info = self._search_song(user_song_name) if not song_info or not song_info.get("play_url"): self.message_util.send_text(f"❌未找到歌曲:{user_song_name}", - (roomid if roomid else sender), sender) + (roomid if roomid else sender), sender) return False, "未找到歌曲" # 发送音乐 - self._send_music_message(wcf, song_info, roomid or sender) + self._send_music_message(song_info, roomid or sender) return True, "发送成功" except Exception as e: @@ -143,7 +143,7 @@ class MusicPlugin(MessagePluginInterface): self.LOG.error(f"搜索歌曲出错: {e}") return {} - def _send_music_message(self, wcf, song_info: Dict[str, Any], receiver: str) -> bool: + def _send_music_message(self, song_info: Dict[str, Any], receiver: str) -> bool: """发送音乐消息""" try: song_name = song_info.get("song_name", "") @@ -201,19 +201,11 @@ class MusicPlugin(MessagePluginInterface): """ - # # 修改消息数据库里面的消息content内容 - # text_bytes = xml_message.encode('utf-8') - # compressed_data = lb.compress(text_bytes, store_size=False).hex() - # - # data = self.message_util.query_sql('MSG0.db', "SELECT * FROM MSG where type = 49 limit 1") - # self.message_util.query_sql('MSG0.db', - # f"""UPDATE MSG SET CompressContent = x'{compressed_data}', BytesExtra=x'', type=49, SubType=3, - # IsSender=0, TalkerId=2 WHERE MsgSvrID={data[0]['MsgSvrID']}""" - # ) - # - # result = self.message_util.forward_msg(data[0]["MsgSvrID"], receiver) - # self.LOG.info(f"插件化:点歌发送结果: {result}") - return True + resp = gewe_client.client.post_app_msg(gewe_client.client.app_id, receiver, xml_message) + data = GeweResponse(resp) + self.LOG.info(f"发送音乐消息:{data}") + if data.is_success: + return True except Exception as e: self.LOG.error(f"发送音乐消息出错: {e}") diff --git a/plugins/plugin_manager/main.py b/plugins/plugin_manager/main.py index 9aafe6d..40e8c23 100644 --- a/plugins/plugin_manager/main.py +++ b/plugins/plugin_manager/main.py @@ -110,18 +110,18 @@ class PluginManagerPlugin(MessagePluginInterface): # 根据子命令执行相应操作 command_handlers = { "列表": self._list_plugins, - "启用": lambda w, s, r: self._operate_plugin(plugin_name, w, s, r, self._enable_plugin), - "禁用": lambda w, s, r: self._operate_plugin(plugin_name, w, s, r, self._disable_plugin), - "重载": lambda w, s, r: self._operate_plugin(plugin_name, w, s, r, self._reload_plugin), - "卸载": lambda w, s, r: self._operate_plugin(plugin_name, w, s, r, self._unload_plugin), + "启用": lambda s, r: self._operate_plugin(plugin_name, s, r, self._enable_plugin), + "禁用": lambda s, r: self._operate_plugin(plugin_name, s, r, self._disable_plugin), + "重载": lambda s, r: self._operate_plugin(plugin_name, s, r, self._reload_plugin), + "卸载": lambda s, r: self._operate_plugin(plugin_name, s, r, self._unload_plugin), # 修改这一行,使用 lambda 函数而不是直接调用 - "加载": lambda w, s, r: self._load_plugin(plugin_name, w, s, r), - "信息": lambda w, s, r: self._operate_plugin(plugin_name, w, s, r, self._plugin_info) + "加载": lambda s, r: self._load_plugin(plugin_name, s, r), + "信息": lambda s, r: self._operate_plugin(plugin_name, s, r, self._plugin_info) } handler = command_handlers.get(sub_command) if handler and (sub_command == "列表" or plugin_name): - return handler(wcf, sender, roomid) + return handler(sender, roomid) else: self.message_util.send_text(f"❌未知命令或缺少参数!\n{self.command_format}", target, sender) return True, "未知命令" @@ -154,7 +154,7 @@ class PluginManagerPlugin(MessagePluginInterface): self.message_util.send_text(message, target, sender) return True, "列出插件成功" - def _operate_plugin(self, plugin_name: str, wcf, sender: str, roomid: str, + def _operate_plugin(self, plugin_name: str, sender: str, roomid: str, operation_func) -> Tuple[bool, str]: """通用插件操作函数""" target = roomid if roomid else sender @@ -172,9 +172,9 @@ class PluginManagerPlugin(MessagePluginInterface): return True, "不能对插件管理插件自身执行此操作" # 执行具体操作 - return operation_func(display_name, wcf, sender, roomid) + return operation_func(display_name, sender, roomid) - def _enable_plugin(self, plugin_name: str, wcf, sender: str, roomid: str) -> Tuple[bool, str]: + def _enable_plugin(self, plugin_name: str, sender: str, roomid: str) -> Tuple[bool, str]: """启用插件""" target = roomid if roomid else sender plugin = self.plugin_registry.get_plugin(plugin_name) @@ -198,7 +198,7 @@ class PluginManagerPlugin(MessagePluginInterface): self.message_util.send_text(f"❌插件 {plugin_name} 启用失败", target, sender) return False, f"插件 {plugin_name} 启用失败" - def _disable_plugin(self, plugin_name: str, wcf, sender: str, roomid: str) -> Tuple[bool, str]: + def _disable_plugin(self, plugin_name: str, sender: str, roomid: str) -> Tuple[bool, str]: """禁用插件""" target = roomid if roomid else sender plugin = self.plugin_registry.get_plugin(plugin_name) @@ -222,7 +222,7 @@ class PluginManagerPlugin(MessagePluginInterface): self.message_util.send_text(f"❌插件 {plugin_name} 禁用失败", target, sender) return False, f"插件 {plugin_name} 禁用失败" - def _reload_plugin(self, plugin_name: str, wcf, sender: str, roomid: str) -> Tuple[bool, str]: + def _reload_plugin(self, plugin_name: str, sender: str, roomid: str) -> Tuple[bool, str]: """重载插件""" target = roomid if roomid else sender plugin = self.plugin_registry.get_plugin(plugin_name) @@ -247,7 +247,7 @@ class PluginManagerPlugin(MessagePluginInterface): self.message_util.send_text(f"❌插件 {plugin_name} 重载失败", target, sender) return False, f"插件 {plugin_name} 重载失败" - def _unload_plugin(self, plugin_name: str, wcf, sender: str, roomid: str) -> Tuple[bool, str]: + def _unload_plugin(self, plugin_name: str, sender: str, roomid: str) -> Tuple[bool, str]: """卸载插件""" target = roomid if roomid else sender plugin = self.plugin_registry.get_plugin(plugin_name) @@ -267,7 +267,7 @@ class PluginManagerPlugin(MessagePluginInterface): self.message_util.send_text(f"❌插件 {plugin_name} 卸载失败", target, sender) return False, f"插件 {plugin_name} 卸载失败" - def _load_plugin(self, plugin_name: str, wcf, sender: str, roomid: str, silent: bool = False) -> Tuple[bool, str]: + def _load_plugin(self, plugin_name: str, sender: str, roomid: str, silent: bool = False) -> Tuple[bool, str]: """加载插件""" # 对于加载操作,我们直接使用目录名作为模块名 # 检查插件目录是否存在 @@ -275,7 +275,7 @@ class PluginManagerPlugin(MessagePluginInterface): if not os.path.exists(plugin_dir): if not silent: self.message_util.send_text(f"❌插件目录 {plugin_dir} 不存在", - (roomid if roomid else sender), sender) + (roomid if roomid else sender), sender) return False, f"插件目录 {plugin_dir} 不存在" # 检查插件是否已加载 - 遍历所有插件查找模块名匹配的 @@ -284,7 +284,7 @@ class PluginManagerPlugin(MessagePluginInterface): if existing_module_name == plugin_name: if not silent: self.message_util.send_text(f"⚠️插件 {existing_plugin.name} (模块名: {plugin_name}) 已经加载", - (roomid if roomid else sender), sender) + (roomid if roomid else sender), sender) return True, f"插件 {existing_plugin.name} 已经加载" try: @@ -293,34 +293,34 @@ class PluginManagerPlugin(MessagePluginInterface): if plugin: if not silent: self.message_util.send_text(f"✅插件 {plugin.name} 加载成功", - (roomid if roomid else sender), sender) + (roomid if roomid else sender), sender) return True, f"插件 {plugin.name} 加载成功" else: if not silent: self.message_util.send_text(f"❌插件 {plugin_name} 加载失败", - (roomid if roomid else sender), sender) + (roomid if roomid else sender), sender) return False, f"插件 {plugin_name} 加载失败" except Exception as e: self.LOG.error(f"加载插件 {plugin_name} 出错: {e}") if not silent: self.message_util.send_text(f"❌加载插件出错: {str(e)}", - (roomid if roomid else sender), sender) + (roomid if roomid else sender), sender) return False, f"加载插件出错: {e}" - def _plugin_info(self, plugin_name: str, wcf, sender: str, roomid: str) -> Tuple[bool, str]: + def _plugin_info(self, plugin_name: str, sender: str, roomid: str) -> Tuple[bool, str]: """查看插件详情""" # 查找匹配的插件名称 display_name, plugin = self.plugin_manager.find_plugin_by_name(plugin_name) if not display_name: self.message_util.send_text(f"❌未找到插件 {plugin_name},请检查名称是否正确", - (roomid if roomid else sender), sender) + (roomid if roomid else sender), sender) return True, f"未找到插件 {plugin_name}" plugin = self.plugin_registry.get_plugin(display_name) if not plugin: self.message_util.send_text(f"❌插件 {display_name} 不存在", - (roomid if roomid else sender), sender) + (roomid if roomid else sender), sender) return True, f"插件 {display_name} 不存在" # 获取插件模块名 diff --git a/plugins/video_man/main.py b/plugins/video_man/main.py index 4edd954..f4ef08d 100644 --- a/plugins/video_man/main.py +++ b/plugins/video_man/main.py @@ -103,7 +103,7 @@ class VideoManPlugin(MessagePluginInterface): try: # 下载视频 file_abspath = self._download_video("https://api.guiguiya.com/api/video/fuji?type=json") - + #FIXME 需要换成web容器地址。否则无法获取。 if not file_abspath: self.message_util.send_text(f"\n❌视频下载失败,请稍后再试", (roomid if roomid else sender), sender) diff --git a/robot.py b/robot.py index 1d971f3..05d8252 100644 --- a/robot.py +++ b/robot.py @@ -152,9 +152,32 @@ class Robot(Job): # 设置ROBOT功能为启用状态 GroupBotManager.set_group_permission(msg.roomid, Feature.ROBOT, PermissionStatus.ENABLED) # 更新联系人信息 + # 群第一次加入机器人管理,自动添加并开启机器人功能,需要进行群成员信息初始化。请完成写入数据库,并更新联系人信息 + except Exception as e: self.LOG.error(f"加入新群,自动添加并开启机器人功能 error: {e}") + # 如果用户信息缓存里面没有这个用户昵称,则添加用户信息,并且维护该用户信息 + # 以 wxid 作为唯一标识 + try: + if msg.from_group(): + wxid = msg.sender + if wxid and wxid not in self.allContacts: + # 添加到数据库 + # 这里假设 contacts_db 有 save_contact_info 方法,参数为 dict + resp = self.client.get_chatroom_member_detail(self.app_id, msg.roomid, [wxid]) + resp_obj = json_to_object(resp) + infos = resp_obj.data + for info in infos: + self.LOG.info(f"已添加新用户信息到数据库: {wxid}") + # 更新缓存 + self.allContacts[wxid] = info.get("nickName", "wxid") + self.contact_manager.set_contacts(self.allContacts) + self.LOG.info(f"已维护新用户信息到缓存: {wxid}") + self.contacts_db.save_chatroom_member_detail(msg.roomid, infos) + except Exception as e: + self.LOG.error(f"添加新用户信息到数据库失败: {e}") + # 发布消息接收事件 self.event_system.publish(EventType.MESSAGE_RECEIVED, {"message": msg}) @@ -206,18 +229,18 @@ class Robot(Job): elif msg.msg_type == MessageType.TEXT: # 文本消息 # 让配置加载更灵活,自己可以更新配置。也可以利用定时任务更新。 if msg.from_self(): - if msg.content == "^更新$": + if msg.content.clean_content == "^更新$": self.config.reload() self.gbm.load_local_cache() self.LOG.info("已更新") - if msg.content == "今日百度新闻": + if msg.content.clean_content == "今日百度新闻": self.news_baidu_report() - if msg.content == "TO_DB": + if msg.content.clean_content == "TO_DB": self.message_count_to_db() - if msg.content == "PDF": + if msg.content.clean_content == "PDF": self.generate_sehuatang_pdf() if msg.content.raw_content.startswith("清除群-"): - self.gbm.handle_command(msg.roomid, msg.content) + self.gbm.handle_command(msg.roomid, msg.content.clean_content) else: self.toChitchat(msg) # 闲聊 @@ -281,7 +304,7 @@ class Robot(Job): # 转换WxMessage为插件可处理的格式 plugin_msg = { "type": msg.msg_type, - "content": msg.content, + "content": msg.content.clean_content, "sender": msg.sender, "roomid": msg.roomid if msg.from_group() else "", "xml": msg.content.xml_content, @@ -402,18 +425,23 @@ class Robot(Job): except Exception as e: self.LOG.error(f"xiu_ren_pdf_send error:{e}") + # 本逻辑主要解决加载联系人信息的问题,只从数据库里面提取,不完成下载行为。 def get_all_contacts(self) -> dict: """获取所有联系人信息并返回字典格式 {wxid: nickname}""" # 从数据库提取信息,如果数据库没内容,则完成第一次初始化。 try: # 先尝试从数据库获取联系人信息 contacts_dict = self.contacts_db.get_all_contacts() + # 获取群成员列表 + return contacts_dict - # 如果数据库中有联系人信息,直接返回 - if contacts_dict: - self.LOG.info(f"从数据库成功获取了 {len(contacts_dict)} 个联系人信息") - return contacts_dict + except Exception as e: + self.LOG.error(f"获取联系人信息失败: {e}") + return {} + def sync_all_contacts(self): + """同步所有联系人信息""" + try: # 数据库中没有联系人信息,需要初始化 self.LOG.info("数据库中没有联系人信息,开始初始化...") diff --git a/utils/wechat/contact_manager.py b/utils/wechat/contact_manager.py index 74de9bc..9779ac7 100644 --- a/utils/wechat/contact_manager.py +++ b/utils/wechat/contact_manager.py @@ -204,6 +204,8 @@ class ContactManager: self._official_accounts[wxid] = nickname elif wxid.endswith('@chatroom'): self._group_contacts[wxid] = nickname + # 需要获取群成员昵称信息; 从数据库里面提取。 + # self._group_contacts_friends[wxid] = {} else: self._personal_contacts[wxid] = nickname self._logger.debug(f"已更新联系人: {wxid} -> {nickname}")