调整代码
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
0
gewechat/response/__init__.py
Normal file
0
gewechat/response/__init__.py
Normal file
56
gewechat/response/gewe_resp.py
Normal file
56
gewechat/response/gewe_resp.py
Normal 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__}>"
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
</appinfo>
|
||||
<commenturl />
|
||||
</msg>"""
|
||||
# # 修改消息数据库里面的消息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}")
|
||||
|
||||
@@ -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} 不存在"
|
||||
|
||||
# 获取插件模块名
|
||||
|
||||
@@ -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)
|
||||
|
||||
48
robot.py
48
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("数据库中没有联系人信息,开始初始化...")
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user