调整代码

This commit is contained in:
liuwei
2025-04-23 16:11:06 +08:00
parent b919a3834e
commit fc757151e4
11 changed files with 345 additions and 214 deletions

View File

@@ -86,7 +86,27 @@ class ContactsDBOperator(BaseDBOperator):
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信群成员信息表'; ) 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: except Exception as e:
self.logger.error(f"创建微信联系人表或群成员表失败: {e}") self.logger.error(f"创建微信联系人表或群成员表失败: {e}")
raise raise
@@ -255,7 +275,11 @@ class ContactsDBOperator(BaseDBOperator):
""" """
try: try:
sql = """ 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) results = self.execute_query(sql)
@@ -411,3 +435,73 @@ class ContactsDBOperator(BaseDBOperator):
except Exception as e: except Exception as e:
self.logger.error(f"处理群聊{chatroom_id}成员详情数据失败: {e}") self.logger.error(f"处理群聊{chatroom_id}成员详情数据失败: {e}")
return False 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

View File

@@ -3,6 +3,7 @@ from gewechat.call_back_message.message import WxMessage, MessageType, AppMessag
import logging import logging
from robot import Robot from robot import Robot
from utils.json_converter import json_to_object
router = APIRouter() router = APIRouter()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -67,12 +68,7 @@ async def handle_add_message(msg: WxMessage):
async def handle_mod_contacts(msg: WxMessage): async def handle_mod_contacts(msg: WxMessage):
"""处理联系人变更"""
logger.info(f"联系人信息变更: {msg.raw_data}") 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): async def handle_del_contacts(msg: WxMessage):

View File

@@ -56,5 +56,24 @@ def set_call_back():
print(response.text) 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__': if __name__ == '__main__':
set_call_back() send_file()

View File

View File

@@ -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"<GeweResponse ret={self.ret} msg={self.msg} data_type={type(self.data).__name__}>"

View File

@@ -4,6 +4,10 @@ import time
from datetime import datetime from datetime import datetime
from typing import Dict, Any, List, Optional, Tuple 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.message_plugin_interface import MessagePluginInterface
from plugin_common.plugin_interface import PluginStatus from plugin_common.plugin_interface import PluginStatus
from utils.robot_cmd.robot_command import Feature, PermissionStatus, GroupBotManager 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]]: 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, "无需执行" 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&amp;",
group_id)
self.LOG.info(f"已发送入群通知: {nickname} 加入群 {group_id}")
@property @property
def commands(self) -> List[str]: def commands(self) -> List[str]:
"""插件支持的命令列表""" """插件支持的命令列表"""
@@ -223,3 +140,30 @@ class GroupMemberChangePlugin(MessagePluginInterface):
def get_help(self) -> str: def get_help(self) -> str:
"""获取插件帮助信息""" """获取插件帮助信息"""
return "群成员变更监控插件:自动监控群成员变动并发送通知。" 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

View File

@@ -1,9 +1,9 @@
import logging import logging
import requests import requests
import lz4.block as lb
from typing import Dict, Any, List, Optional, Tuple 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.message_plugin_interface import MessagePluginInterface
from plugin_common.plugin_interface import PluginStatus from plugin_common.plugin_interface import PluginStatus
from utils.decorator.plugin_decorators import plugin_stats_decorator from utils.decorator.plugin_decorators import plugin_stats_decorator
@@ -93,7 +93,7 @@ class MusicPlugin(MessagePluginInterface):
# 检查命令格式 # 检查命令格式
if len(content.split(" ")) == 1: if len(content.split(" ")) == 1:
self.message_util.send_text(f"❌命令格式错误!\n{self.command_format}", self.message_util.send_text(f"❌命令格式错误!\n{self.command_format}",
(roomid if roomid else sender), sender) (roomid if roomid else sender), sender)
return False, "命令格式错误" return False, "命令格式错误"
# 检查权限 # 检查权限
@@ -108,11 +108,11 @@ class MusicPlugin(MessagePluginInterface):
song_info = self._search_song(user_song_name) song_info = self._search_song(user_song_name)
if not song_info or not song_info.get("play_url"): if not song_info or not song_info.get("play_url"):
self.message_util.send_text(f"❌未找到歌曲:{user_song_name}", self.message_util.send_text(f"❌未找到歌曲:{user_song_name}",
(roomid if roomid else sender), sender) (roomid if roomid else sender), sender)
return False, "未找到歌曲" return False, "未找到歌曲"
# 发送音乐 # 发送音乐
self._send_music_message(wcf, song_info, roomid or sender) self._send_music_message(song_info, roomid or sender)
return True, "发送成功" return True, "发送成功"
except Exception as e: except Exception as e:
@@ -143,7 +143,7 @@ class MusicPlugin(MessagePluginInterface):
self.LOG.error(f"搜索歌曲出错: {e}") self.LOG.error(f"搜索歌曲出错: {e}")
return {} 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: try:
song_name = song_info.get("song_name", "") song_name = song_info.get("song_name", "")
@@ -201,19 +201,11 @@ class MusicPlugin(MessagePluginInterface):
</appinfo> </appinfo>
<commenturl /> <commenturl />
</msg>""" </msg>"""
# # 修改消息数据库里面的消息content内容 resp = gewe_client.client.post_app_msg(gewe_client.client.app_id, receiver, xml_message)
# text_bytes = xml_message.encode('utf-8') data = GeweResponse(resp)
# compressed_data = lb.compress(text_bytes, store_size=False).hex() self.LOG.info(f"发送音乐消息:{data}")
# if data.is_success:
# data = self.message_util.query_sql('MSG0.db', "SELECT * FROM MSG where type = 49 limit 1") return True
# 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
except Exception as e: except Exception as e:
self.LOG.error(f"发送音乐消息出错: {e}") self.LOG.error(f"发送音乐消息出错: {e}")

View File

@@ -110,18 +110,18 @@ class PluginManagerPlugin(MessagePluginInterface):
# 根据子命令执行相应操作 # 根据子命令执行相应操作
command_handlers = { command_handlers = {
"列表": self._list_plugins, "列表": self._list_plugins,
"启用": lambda w, s, r: self._operate_plugin(plugin_name, w, s, r, self._enable_plugin), "启用": lambda s, r: self._operate_plugin(plugin_name, s, r, self._enable_plugin),
"禁用": lambda w, s, r: self._operate_plugin(plugin_name, w, s, r, self._disable_plugin), "禁用": lambda s, r: self._operate_plugin(plugin_name, s, r, self._disable_plugin),
"重载": lambda w, s, r: self._operate_plugin(plugin_name, w, s, r, self._reload_plugin), "重载": lambda s, r: self._operate_plugin(plugin_name, 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._unload_plugin),
# 修改这一行,使用 lambda 函数而不是直接调用 # 修改这一行,使用 lambda 函数而不是直接调用
"加载": lambda w, s, r: self._load_plugin(plugin_name, w, s, r), "加载": lambda s, r: self._load_plugin(plugin_name, s, r),
"信息": lambda w, s, r: self._operate_plugin(plugin_name, w, s, r, self._plugin_info) "信息": lambda s, r: self._operate_plugin(plugin_name, s, r, self._plugin_info)
} }
handler = command_handlers.get(sub_command) handler = command_handlers.get(sub_command)
if handler and (sub_command == "列表" or plugin_name): if handler and (sub_command == "列表" or plugin_name):
return handler(wcf, sender, roomid) return handler(sender, roomid)
else: else:
self.message_util.send_text(f"❌未知命令或缺少参数!\n{self.command_format}", target, sender) self.message_util.send_text(f"❌未知命令或缺少参数!\n{self.command_format}", target, sender)
return True, "未知命令" return True, "未知命令"
@@ -154,7 +154,7 @@ class PluginManagerPlugin(MessagePluginInterface):
self.message_util.send_text(message, target, sender) self.message_util.send_text(message, target, sender)
return True, "列出插件成功" 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]: operation_func) -> Tuple[bool, str]:
"""通用插件操作函数""" """通用插件操作函数"""
target = roomid if roomid else sender target = roomid if roomid else sender
@@ -172,9 +172,9 @@ class PluginManagerPlugin(MessagePluginInterface):
return True, "不能对插件管理插件自身执行此操作" 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 target = roomid if roomid else sender
plugin = self.plugin_registry.get_plugin(plugin_name) 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) self.message_util.send_text(f"❌插件 {plugin_name} 启用失败", target, sender)
return False, f"插件 {plugin_name} 启用失败" 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 target = roomid if roomid else sender
plugin = self.plugin_registry.get_plugin(plugin_name) 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) self.message_util.send_text(f"❌插件 {plugin_name} 禁用失败", target, sender)
return False, f"插件 {plugin_name} 禁用失败" 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 target = roomid if roomid else sender
plugin = self.plugin_registry.get_plugin(plugin_name) 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) self.message_util.send_text(f"❌插件 {plugin_name} 重载失败", target, sender)
return False, f"插件 {plugin_name} 重载失败" 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 target = roomid if roomid else sender
plugin = self.plugin_registry.get_plugin(plugin_name) 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) self.message_util.send_text(f"❌插件 {plugin_name} 卸载失败", target, sender)
return False, f"插件 {plugin_name} 卸载失败" 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 os.path.exists(plugin_dir):
if not silent: if not silent:
self.message_util.send_text(f"❌插件目录 {plugin_dir} 不存在", 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} 不存在" return False, f"插件目录 {plugin_dir} 不存在"
# 检查插件是否已加载 - 遍历所有插件查找模块名匹配的 # 检查插件是否已加载 - 遍历所有插件查找模块名匹配的
@@ -284,7 +284,7 @@ class PluginManagerPlugin(MessagePluginInterface):
if existing_module_name == plugin_name: if existing_module_name == plugin_name:
if not silent: if not silent:
self.message_util.send_text(f"⚠️插件 {existing_plugin.name} (模块名: {plugin_name}) 已经加载", 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} 已经加载" return True, f"插件 {existing_plugin.name} 已经加载"
try: try:
@@ -293,34 +293,34 @@ class PluginManagerPlugin(MessagePluginInterface):
if plugin: if plugin:
if not silent: if not silent:
self.message_util.send_text(f"✅插件 {plugin.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} 加载成功" return True, f"插件 {plugin.name} 加载成功"
else: else:
if not silent: if not silent:
self.message_util.send_text(f"❌插件 {plugin_name} 加载失败", 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} 加载失败" return False, f"插件 {plugin_name} 加载失败"
except Exception as e: except Exception as e:
self.LOG.error(f"加载插件 {plugin_name} 出错: {e}") self.LOG.error(f"加载插件 {plugin_name} 出错: {e}")
if not silent: if not silent:
self.message_util.send_text(f"❌加载插件出错: {str(e)}", self.message_util.send_text(f"❌加载插件出错: {str(e)}",
(roomid if roomid else sender), sender) (roomid if roomid else sender), sender)
return False, f"加载插件出错: {e}" 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) display_name, plugin = self.plugin_manager.find_plugin_by_name(plugin_name)
if not display_name: if not display_name:
self.message_util.send_text(f"❌未找到插件 {plugin_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}" return True, f"未找到插件 {plugin_name}"
plugin = self.plugin_registry.get_plugin(display_name) plugin = self.plugin_registry.get_plugin(display_name)
if not plugin: if not plugin:
self.message_util.send_text(f"❌插件 {display_name} 不存在", 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} 不存在" return True, f"插件 {display_name} 不存在"
# 获取插件模块名 # 获取插件模块名

View File

@@ -103,7 +103,7 @@ class VideoManPlugin(MessagePluginInterface):
try: try:
# 下载视频 # 下载视频
file_abspath = self._download_video("https://api.guiguiya.com/api/video/fuji?type=json") file_abspath = self._download_video("https://api.guiguiya.com/api/video/fuji?type=json")
#FIXME 需要换成web容器地址。否则无法获取。
if not file_abspath: if not file_abspath:
self.message_util.send_text(f"\n❌视频下载失败,请稍后再试", self.message_util.send_text(f"\n❌视频下载失败,请稍后再试",
(roomid if roomid else sender), sender) (roomid if roomid else sender), sender)

View File

@@ -152,9 +152,32 @@ class Robot(Job):
# 设置ROBOT功能为启用状态 # 设置ROBOT功能为启用状态
GroupBotManager.set_group_permission(msg.roomid, Feature.ROBOT, PermissionStatus.ENABLED) GroupBotManager.set_group_permission(msg.roomid, Feature.ROBOT, PermissionStatus.ENABLED)
# 更新联系人信息 # 更新联系人信息
# 群第一次加入机器人管理,自动添加并开启机器人功能,需要进行群成员信息初始化。请完成写入数据库,并更新联系人信息
except Exception as e: except Exception as e:
self.LOG.error(f"加入新群,自动添加并开启机器人功能 error: {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}) self.event_system.publish(EventType.MESSAGE_RECEIVED, {"message": msg})
@@ -206,18 +229,18 @@ class Robot(Job):
elif msg.msg_type == MessageType.TEXT: # 文本消息 elif msg.msg_type == MessageType.TEXT: # 文本消息
# 让配置加载更灵活,自己可以更新配置。也可以利用定时任务更新。 # 让配置加载更灵活,自己可以更新配置。也可以利用定时任务更新。
if msg.from_self(): if msg.from_self():
if msg.content == "^更新$": if msg.content.clean_content == "^更新$":
self.config.reload() self.config.reload()
self.gbm.load_local_cache() self.gbm.load_local_cache()
self.LOG.info("已更新") self.LOG.info("已更新")
if msg.content == "今日百度新闻": if msg.content.clean_content == "今日百度新闻":
self.news_baidu_report() self.news_baidu_report()
if msg.content == "TO_DB": if msg.content.clean_content == "TO_DB":
self.message_count_to_db() self.message_count_to_db()
if msg.content == "PDF": if msg.content.clean_content == "PDF":
self.generate_sehuatang_pdf() self.generate_sehuatang_pdf()
if msg.content.raw_content.startswith("清除群-"): 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: else:
self.toChitchat(msg) # 闲聊 self.toChitchat(msg) # 闲聊
@@ -281,7 +304,7 @@ class Robot(Job):
# 转换WxMessage为插件可处理的格式 # 转换WxMessage为插件可处理的格式
plugin_msg = { plugin_msg = {
"type": msg.msg_type, "type": msg.msg_type,
"content": msg.content, "content": msg.content.clean_content,
"sender": msg.sender, "sender": msg.sender,
"roomid": msg.roomid if msg.from_group() else "", "roomid": msg.roomid if msg.from_group() else "",
"xml": msg.content.xml_content, "xml": msg.content.xml_content,
@@ -402,18 +425,23 @@ class Robot(Job):
except Exception as e: except Exception as e:
self.LOG.error(f"xiu_ren_pdf_send error{e}") self.LOG.error(f"xiu_ren_pdf_send error{e}")
# 本逻辑主要解决加载联系人信息的问题,只从数据库里面提取,不完成下载行为。
def get_all_contacts(self) -> dict: def get_all_contacts(self) -> dict:
"""获取所有联系人信息并返回字典格式 {wxid: nickname}""" """获取所有联系人信息并返回字典格式 {wxid: nickname}"""
# 从数据库提取信息,如果数据库没内容,则完成第一次初始化。 # 从数据库提取信息,如果数据库没内容,则完成第一次初始化。
try: try:
# 先尝试从数据库获取联系人信息 # 先尝试从数据库获取联系人信息
contacts_dict = self.contacts_db.get_all_contacts() contacts_dict = self.contacts_db.get_all_contacts()
# 获取群成员列表
return contacts_dict
# 如果数据库中有联系人信息,直接返回 except Exception as e:
if contacts_dict: self.LOG.error(f"获取联系人信息失败: {e}")
self.LOG.info(f"从数据库成功获取了 {len(contacts_dict)} 个联系人信息") return {}
return contacts_dict
def sync_all_contacts(self):
"""同步所有联系人信息"""
try:
# 数据库中没有联系人信息,需要初始化 # 数据库中没有联系人信息,需要初始化
self.LOG.info("数据库中没有联系人信息,开始初始化...") self.LOG.info("数据库中没有联系人信息,开始初始化...")

View File

@@ -204,6 +204,8 @@ class ContactManager:
self._official_accounts[wxid] = nickname self._official_accounts[wxid] = nickname
elif wxid.endswith('@chatroom'): elif wxid.endswith('@chatroom'):
self._group_contacts[wxid] = nickname self._group_contacts[wxid] = nickname
# 需要获取群成员昵称信息; 从数据库里面提取。
# self._group_contacts_friends[wxid] = {}
else: else:
self._personal_contacts[wxid] = nickname self._personal_contacts[wxid] = nickname
self._logger.debug(f"已更新联系人: {wxid} -> {nickname}") self._logger.debug(f"已更新联系人: {wxid} -> {nickname}")