重大版本调整:gewechat兼容。

This commit is contained in:
liuwei
2025-04-22 11:17:03 +08:00
parent 41def62467
commit a62bb61901
48 changed files with 2855 additions and 1420 deletions
+108 -165
View File
@@ -7,14 +7,12 @@ from queue import Empty
from threading import Thread
import random
from gewechat_client import GewechatClient
from base.func_doubao import Doubao
from base.func_epic import is_friday, get_free
from base.func_zhipu import ZhiPu
from wcferry import Wcf, WxMsg
from base.func_bard import BardAssistant
from base.func_chatglm import ChatGLM
from base.func_chatgpt import ChatGPT
from base.func_news import News
from base.func_tigerbot import TigerBot
@@ -22,6 +20,7 @@ from base.func_xinghuo_web import XinghuoWeb
from base.func_claude import Claude
from configuration import Config
from constants import ChatType
from gewechat.call_back_message.message import WxMessage, MessageType
from utils.wechat.message_to_db import MessageStorage
from plugin_common.event_system import EventType, EventSystem
from plugin_common.message_plugin_interface import MessagePluginInterface
@@ -33,8 +32,6 @@ from job_mgmt import Job
from utils.robot_cmd.robot_command import Feature
from utils.robot_cmd.robot_command import PermissionStatus
__version__ = "39.5.1.0"
from sehuatang.shehuatang import pdf_file_path
from utils.wechat.contact_manager import ContactManager
from xiuren.meitu_dl import meitu_dowload_pub_pic
@@ -48,17 +45,16 @@ class Robot(Job):
"""个性化自己的机器人
"""
def __init__(self, config: Config, wcf: Wcf, chat_type: int) -> None:
self.wcf = wcf
def __init__(self, config: Config, app_id: str, client: GewechatClient, chat_type: int) -> None:
self.app_id = app_id
self.client = client
self.config = config
self.LOG = logging.getLogger("Robot")
self.wxid = self.wcf.get_self_wxid()
# 初始化联系人管理器并设置联系人
self.contact_manager = ContactManager.get_instance()
self.allContacts = self.get_all_contacts()
self.all_head_img = self.get_all_head_img_url()
self.contact_manager.set_contacts(self.allContacts, self.all_head_img, self.wcf)
self.contact_manager.set_contacts(self.allContacts)
self.LOG.info(f"DB+REDIS 连接池开始初始化")
# 使用单例模式获取实例
@@ -73,7 +69,7 @@ class Robot(Job):
self.redis_pool = self.db_manager.redis_pool
# 初始化消息工具类 - 使用联系人管理器
self.message_util = MessageUtil(wcf, self.contact_manager)
self.message_util = MessageUtil(app_id, client, self.contact_manager)
self.groups = {} # 存储按group_id分组的消息列表,每个group_id最多保留10条消息
GroupBotManager.load_local_cache()
@@ -89,7 +85,7 @@ class Robot(Job):
# 设置插件系统上下文
self.system_context = {
"config": config,
"wcf": wcf,
"client": client,
"event_system": self.event_system,
"plugin_registry": self.plugin_registry,
"db_pool": self.db_pool,
@@ -105,48 +101,7 @@ class Robot(Job):
self.LOG.info("插件系统初始化完成")
# 消息存档模块初始化,自动完成入库动作
self.message_storage = MessageStorage(self.wcf)
if ChatType.is_in_chat_types(chat_type):
if chat_type == ChatType.TIGER_BOT.value and TigerBot.value_check(self.config.TIGERBOT):
self.chat = TigerBot(self.config.TIGERBOT)
elif chat_type == ChatType.CHATGPT.value and ChatGPT.value_check(self.config.CHATGPT):
self.chat = ChatGPT(self.config.CHATGPT)
elif chat_type == ChatType.XINGHUO_WEB.value and XinghuoWeb.value_check(self.config.XINGHUO_WEB):
self.chat = XinghuoWeb(self.config.XINGHUO_WEB)
elif chat_type == ChatType.CHATGLM.value and ChatGLM.value_check(self.config.CHATGLM):
self.chat = ChatGLM(self.config.CHATGLM)
elif chat_type == ChatType.BardAssistant.value and BardAssistant.value_check(self.config.BardAssistant):
self.chat = BardAssistant(self.config.BardAssistant)
elif chat_type == ChatType.ZhiPu.value and ZhiPu.value_check(self.config.ZhiPu):
self.chat = ZhiPu(self.config.ZhiPu)
elif chat_type == ChatType.CLAUDE.value and Claude.value_check(self.config.CLAUDE):
self.chat = Claude(self.config.CLAUDE)
elif chat_type == ChatType.DOUBAO.value and Claude.value_check(self.config.DOUBAO):
self.chat = Doubao(self.config.DOUBAO)
else:
self.LOG.warning("未配置模型")
self.chat = None
else:
if TigerBot.value_check(self.config.TIGERBOT):
self.chat = TigerBot(self.config.TIGERBOT)
elif ChatGPT.value_check(self.config.CHATGPT):
self.chat = ChatGPT(self.config.CHATGPT)
elif XinghuoWeb.value_check(self.config.XINGHUO_WEB):
self.chat = XinghuoWeb(self.config.XINGHUO_WEB)
elif ChatGLM.value_check(self.config.CHATGLM):
self.chat = ChatGLM(self.config.CHATGLM)
elif BardAssistant.value_check(self.config.BardAssistant):
self.chat = BardAssistant(self.config.BardAssistant)
elif ZhiPu.value_check(self.config.ZhiPu):
self.chat = ZhiPu(self.config.ZhiPu)
elif Claude.value_check(self.config.CLAUDE):
self.chat = Claude(self.config.CLAUDE)
elif Doubao.value_check(self.config.DOUBAO):
self.chat = Doubao(self.config.DOUBAO)
else:
self.LOG.warning("未配置模型")
self.chat = None
self.LOG.info(f"已选择: {self.chat}")
self.message_storage = MessageStorage(self.client)
@staticmethod
def value_check(args: dict) -> bool:
@@ -154,11 +109,11 @@ class Robot(Job):
return all(value is not None for key, value in args.items() if key != 'proxy')
return False
def toChitchat(self, msg: WxMsg) -> bool:
def toChitchat(self, msg: WxMessage) -> bool:
"""闲聊,接入 ChatGPT
"""
# 去除@的人和空格等字符
q = re.sub(r"@.*?[\u2005|\s]", "", msg.content).replace(" ", "")
q = re.sub(r"@.*?[\u2005|\s]", "", msg.content.raw_content).replace(" ", "")
if q == "#今日百度新闻":
self.news_baidu_report((msg.roomid if msg.from_group() else msg.sender))
@@ -176,7 +131,7 @@ class Robot(Job):
else:
return True
def processMsg(self, msg: WxMsg) -> None:
def processMsg(self, msg: WxMessage) -> None:
"""当接收到消息的时候,会调用本方法。如果不实现本方法,则打印原始消息。
此处可进行自定义发送的内容,如通过 msg.content 关键字自动获取当前天气信息,并发送到对应的群组@发送者
群号:msg.roomid 微信IDmsg.sender 消息内容:msg.content
@@ -222,7 +177,7 @@ class Robot(Job):
try:
self.message_storage.archive_message(msg)
# 单独处理图片消息
if msg.type == 3: # 图片消息类型
if msg.msg_type == 3: # 图片消息类型
self.message_storage.process_image(msg)
except Exception as e:
self.LOG.error(f"archive_message error: {e}")
@@ -237,7 +192,7 @@ class Robot(Job):
rsp = self.gbm.handle_command(msg.roomid, msg.content)
# 不在群里发送,防止被骚扰
if rsp is not None:
self.send_text_msg(rsp, msg.roomid, msg.sender)
self.message_util.send_text(rsp, msg.roomid, msg.sender)
return
except Exception as e:
self.LOG.error(f"revoke_receive_message error: {e}")
@@ -248,15 +203,7 @@ class Robot(Job):
if plugin_processed:
return
# 非群聊信息,按消息类型进行处理
if msg.type == 37: # 好友请求
self.LOG.info(f"收到好友请求:{msg}")
self.auto_accept_friend_request(msg)
elif msg.type == 10000: # 系统信息
self.say_hi_to_new_friend(msg)
elif msg.type == 0x01: # 文本消息
elif msg.msg_type == MessageType.TEXT: # 文本消息
# 让配置加载更灵活,自己可以更新配置。也可以利用定时任务更新。
if msg.from_self():
if msg.content == "^更新$":
@@ -274,79 +221,15 @@ class Robot(Job):
else:
self.toChitchat(msg) # 闲聊
def onMsg(self, msg: WxMsg) -> int:
def onMsg(self, msg: WxMessage) -> int:
try:
self.LOG.debug(msg) # 打印信息
self.processMsg(msg)
self.LOG.info(msg) # 打印信息
# self.processMsg(msg)
except Exception as e:
self.LOG.error(e)
return 0
def enableRecvMsg(self) -> None:
self.wcf.enable_recv_msg(self.onMsg)
def enableReceivingMsg(self) -> None:
def innerProcessMsg(wcf: Wcf):
while wcf.is_receiving_msg():
try:
msg = wcf.get_msg()
self.LOG.debug(msg)
self.processMsg(msg)
except Empty:
continue # Empty message
except Exception as e:
self.LOG.error(f"Receiving message error: {e}")
self.wcf.enable_receiving_msg()
Thread(target=innerProcessMsg, name="GetMessage", args=(self.wcf,), daemon=True).start()
def send_text_msg(self, msg: str, receiver: str, at_list: str = "") -> None:
""" 发送消息
:param msg: 消息字符串
:param receiver: 接收人wxid或者群id
:param at_list: 要@的wxid, @所有人的wxid为:notify@all
"""
# msg 中需要有 @ 名单中一样数量的 @
# 风控处理,随机延迟发送,解决群消息高频发送导致的微信风险
time.sleep(random.uniform(0.3, 1.0))
ats = ""
if at_list:
if at_list == "notify@all": # @所有人
ats = " @所有人"
else:
wxids = at_list.split(",")
for wxid in wxids:
# 根据 wxid 查找群昵称
ats += f" @{self.wcf.get_alias_in_chatroom(wxid, receiver)}"
# {msg}{ats} 表示要发送的消息内容后面紧跟@,例如 北京天气情况为:xxx @张三
if ats == "":
self.LOG.info(f"To {receiver}: {msg}")
self.wcf.send_text(f"{msg}", receiver, at_list)
else:
self.LOG.info(f"To {receiver}: {ats}\r{msg}")
self.wcf.send_text(f"{ats}\n\n{msg}", receiver, at_list)
def get_all_contacts(self) -> dict:
"""
获取联系人(包括好友、公众号、服务号、群成员……)
格式: {"wxid": "NickName"}
"""
contacts = self.wcf.query_sql("MicroMsg.db", "SELECT UserName, NickName FROM Contact;")
return {contact["UserName"]: contact["NickName"] for contact in contacts}
def get_all_head_img_url(self) -> dict:
try:
head_img_urls = self.wcf.query_sql("MicroMsg.db", "SELECT usrName ,bigHeadImgUrl FROM ContactHeadImgUrl;")
# self.LOG.info(f"head_img_urls: {head_img_urls}")
return {contact["usrName"]: contact["bigHeadImgUrl"] for contact in head_img_urls}
except Exception as e:
self.LOG.error(f"get_all_head_img_url error: {e}")
return {}
def keep_running_and_block_process(self) -> None:
"""
保持机器人运行,不让进程退出
@@ -355,31 +238,11 @@ class Robot(Job):
self.runPendingJobs()
time.sleep(1)
def auto_accept_friend_request(self, msg: WxMsg) -> None:
try:
xml = ET.fromstring(msg.content)
v3 = xml.attrib["encryptusername"]
v4 = xml.attrib["ticket"]
scene = int(xml.attrib["scene"])
res = self.wcf.accept_new_friend(v3, v4, scene)
self.LOG.info(f"同意好友请求:{res}")
except Exception as e:
self.LOG.error(f"同意好友出错:{e}")
def say_hi_to_new_friend(self, msg: WxMsg) -> None:
nickName = re.findall(r"你已添加了(.*),现在可以开始聊天了。", msg.content)
if nickName:
# 添加了好友,更新好友列表和联系人管理器
self.allContacts[msg.sender] = nickName[0]
self.contact_manager.update_contact(msg.sender, nickName[0])
self.send_text_msg(f"Hi {nickName[0]},我自动通过了你的好友请求。", msg.sender)
# 添加一个方法用于刷新联系人信息
def refresh_contacts(self):
"""刷新联系人信息"""
self.allContacts = self.get_all_contacts()
self.all_head_img = self.get_all_head_img_url()
self.contact_manager.refresh_contacts(self.allContacts, self.all_head_img, self.wcf)
self.contact_manager.refresh_contacts(self.allContacts)
self.LOG.info("联系人信息已刷新")
def send_group_txt_message(self, msg: str, feature: Feature):
@@ -389,7 +252,7 @@ class Robot(Job):
return
for r in receivers:
if self.gbm.get_group_permission(r, feature) == PermissionStatus.ENABLED:
self.send_text_msg(msg, r)
self.message_util.send_text(msg, r)
except Exception as e:
self.LOG.error(f"send_group_txt_message:{feature.description} error{e}")
@@ -400,11 +263,11 @@ class Robot(Job):
return
for r in receivers:
if self.gbm.get_group_permission(r, feature) == PermissionStatus.ENABLED:
self.wcf.send_file(path, r)
self.message_util.send_file(path, r)
except Exception as e:
self.LOG.error(f"send_group_file_message:{feature.description} error{e}")
def process_plugin_message(self, msg: WxMsg) -> bool:
def process_plugin_message(self, msg: WxMessage) -> bool:
"""使用插件处理消息"""
# 获取所有消息处理插件
message_plugins = self.plugin_registry.get_plugins_by_type(MessagePluginInterface)
@@ -415,16 +278,15 @@ class Robot(Job):
continue
try:
# 转换WxMsg为插件可处理的格式
# 转换WxMessage为插件可处理的格式
plugin_msg = {
"type": msg.type,
"type": msg.msg_type,
"content": msg.content,
"sender": msg.sender,
"roomid": msg.roomid if msg.from_group() else "",
"xml": msg.xml,
"xml": msg.content.xml_content,
"is_at": msg.is_at(self.wxid), # 确保正确设置is_at标志
"timestamp": time.time(),
"wcf": self.wcf, # 提供wcf对象,让插件可以直接发送消息
"message_util": self.message_util, # 提供消息工具类
"gbm": self.gbm, # 每次从程序变量中取,保证最新
"all_contacts": self.allContacts,
@@ -536,6 +398,87 @@ class Robot(Job):
def xiu_ren_pdf_send(self):
try:
pub_path = generate_pdf_from_images("xiuren")
self.wcf.send_file(pub_path, "45317011307@chatroom")
self.message_util.send_file(pub_path, "45317011307@chatroom")
except Exception as e:
self.LOG.error(f"xiu_ren_pdf_send error{e}")
def get_all_contacts(self) -> dict:
"""获取所有联系人信息并保存到数据库"""
from db.contacts_db import ContactsDBOperator
contacts_dict = {}
contacts_wxids = self.client.fetch_contacts_list(self.app_id)
# 初始化联系人数据库操作类
contacts_db = ContactsDBOperator()
for wxid in contacts_wxids:
# 获取联系人详细信息
contact_info = self.client.get_detail_info(self.app_id, wxid)
# 将联系人信息添加到字典中
if contact_info and contact_info.get("ret") == 200 and "data" in contact_info:
contact_data = contact_info.get("data", [])
# 保存联系人信息到数据库
if contact_data:
try:
# 判断联系人类型
contact_type = "friends" # 默认为好友类型
if wxid.endswith("@chatroom"):
contact_type = "chatrooms"
# 如果是这个类型,则提取群成员信息
# 提取群成员信息
self.update_chatroom_member_details(wxid)
elif wxid.startswith("gh_"):
contact_type = "ghs"
# 保存到数据库
contacts_db.save_contacts(contact_data, contact_type)
# 添加到返回字典
for contact in contact_data:
user_name = contact.get("userName")
if user_name:
contacts_dict[user_name] = contact.get("nickName") or user_name
except Exception as e:
self.LOG.error(f"保存联系人信息到数据库失败: {e}")
self.LOG.info(f"成功获取并保存{len(contacts_dict)}个联系人信息")
return contacts_dict
def update_chatroom_member_details(self, chatroom_id):
"""更新群成员详细信息"""
try:
# 首先获取群成员列表
members_response = self.client.get_chatroom_member_list(self.app_id, chatroom_id)
if members_response and members_response.get('ret') == 200:
member_list = members_response.get('data', {}).get('memberList', [])
# 提取成员wxid列表
member_wxids = [member.get('wxid') for member in member_list if member.get('wxid')]
if member_wxids:
# 获取成员详细信息
details_response = self.client.get_chatroom_member_detail(self.app_id, chatroom_id, member_wxids)
# 使用ContactsDBOperator处理响应
from db.contacts_db import ContactsDBOperator
contacts_db = ContactsDBOperator()
success = contacts_db.process_chatroom_member_detail_response(chatroom_id, details_response)
if success:
self.LOG.info(f"成功更新群聊{chatroom_id}的成员详细信息")
else:
self.LOG.error(f"更新群聊{chatroom_id}的成员详细信息失败")
return success
else:
self.LOG.warning(f"群聊{chatroom_id}没有成员")
return False
else:
self.LOG.error(f"获取群聊{chatroom_id}成员列表失败")
return False
except Exception as e:
self.LOG.error(f"更新群聊成员详细信息出错: {e}")
return False