重大版本调整: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

View File

@@ -178,12 +178,12 @@ class DashboardServer:
return {"success": False, "message": "WCF实例不可用"} return {"success": False, "message": "WCF实例不可用"}
# 获取当前登录的微信ID # 获取当前登录的微信ID
wx_id = self.wcf.get_self_wxid() wx_id = self.self.message_util.get_self_wxid()
if not wx_id: if not wx_id:
return {"success": False, "message": "未获取到微信ID"} return {"success": False, "message": "未获取到微信ID"}
# 获取用户详细信息 # 获取用户详细信息
user_info = self.wcf.get_user_info() user_info = self.self.message_util.get_user_info()
self.logger.info(f"获取用户信息:{user_info}") self.logger.info(f"获取用户信息:{user_info}")
return { return {

View File

@@ -1,45 +0,0 @@
# ChatGLM3 集成使用说明
1. 需要取消配置中 chatglm 的注释, 并配置对应信息,使用 [ChatGLM3](https://github.com/THUDM/ChatGLM3), 启用最新版 ChatGLM3 根目录下 openai_api.py 获取 api 地址:
```yaml
# 如果要使用 chatglm取消下面的注释并填写相关内容
chatglm:
key: sk-012345678901234567890123456789012345678901234567 # 根据需要自己做key校验
api: http://localhost:8000/v1 # 根据自己的chatglm地址修改
proxy: # 如果你在国内你可能需要魔法大概长这样http://域名或者IP地址:端口号
prompt: 你是智能聊天机器人,你叫小薇 # 根据需要对角色进行设定
file_path: F:/Pictures/temp #设定生成图片和代码使用的文件夹路径
```
2. 修改 chatglm/tool_registry.py 工具里面的一下配置comfyUI 地址或者根据需要自己配置一些工具,函数名上需要加 @register_tool, 函数里面需要叫'''函数描述''',参数需要用 Annotated[str,'',True] 修饰,分别是类型,参数说明,是否必填,再加 ->加上对应的返回类型
```python
@register_tool
def get_confyui_image(prompt: Annotated[str, '要生成图片的提示词,注意必须是英文', True]) -> dict:
'''
生成图片
'''
with open("func_chatglm\\base.json", "r", encoding="utf-8") as f:
data2 = json.load(f)
data2['prompt']['3']['inputs']['seed'] = ''.join(
random.sample('123456789012345678901234567890', 14))
# 模型名称
data2['prompt']['4']['inputs']['ckpt_name'] = 'chilloutmix_NiPrunedFp32Fix.safetensors'
data2['prompt']['6']['inputs']['text'] = prompt # 正向提示词
# data2['prompt']['7']['inputs']['text']='' #反向提示词
cfui = ComfyUIApi(server_address="127.0.0.1:8188") # 根据自己comfyUI地址修改
images = cfui.get_images(data2['prompt'])
return {'res': images[0]['image'], 'res_type': 'image', 'filename': images[0]['filename']}
```
3. 使用 Code Interpreter 还需要安装 Jupyter 内核,默认名称叫 chatglm3
```
ipython kernel install --name chatglm3 --user
```
如果名称需要自定义可以配置系统环境变量IPYKERNEL 或者修改 chatglm/code_kernel.py
```
IPYKERNEL = os.environ.get('IPYKERNEL', 'chatglm3')
```
4. 启动后,发送 #帮助 可以查看 模式和常用指令

View File

@@ -1,13 +0,0 @@
import sys
class UnsupportedPythonVersionError(Exception):
def __init__(self, error_msg: str):
super().__init__(error_msg)
python_version_info = sys.version_info
if not sys.version_info >= (3, 9):
msg = "当前Python版本: " + ".".join(map(str, python_version_info[:3])) + (', 需要python版本 >= 3.9, 前往下载: '
'https://www.python.org/downloads/')
raise UnsupportedPythonVersionError(msg)

View File

@@ -1,88 +0,0 @@
{
"prompt": {
"3": {
"inputs": {
"seed": 1000573256060686,
"steps": 20,
"cfg": 8,
"sampler_name": "euler",
"scheduler": "normal",
"denoise": 1,
"model": [
"4",
0
],
"positive": [
"6",
0
],
"negative": [
"7",
0
],
"latent_image": [
"5",
0
]
},
"class_type": "KSampler"
},
"4": {
"inputs": {
"ckpt_name": "(修复)512-inpainting-ema.safetensors"
},
"class_type": "CheckpointLoaderSimple"
},
"5": {
"inputs": {
"width": 512,
"height": 512,
"batch_size": 1
},
"class_type": "EmptyLatentImage"
},
"6": {
"inputs": {
"text": "beautiful scenery nature glass bottle landscape, , purple galaxy bottle,dress, ",
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode"
},
"7": {
"inputs": {
"text": "text, watermark",
"clip": [
"4",
1
]
},
"class_type": "CLIPTextEncode"
},
"8": {
"inputs": {
"samples": [
"3",
0
],
"vae": [
"4",
2
]
},
"class_type": "VAEDecode"
},
"9": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"8",
0
]
},
"class_type": "SaveImage"
}
}
}

View File

@@ -1,199 +0,0 @@
import base64
import os
import queue
import re
from io import BytesIO
from subprocess import PIPE
from typing import Optional, Union
import jupyter_client
from PIL import Image
IPYKERNEL = os.environ.get('IPYKERNEL', 'chatglm3')
class CodeKernel(object):
def __init__(self,
kernel_name='kernel',
kernel_id=None,
kernel_config_path="",
python_path=None,
ipython_path=None,
init_file_path="./startup.py",
verbose=1):
self.kernel_name = kernel_name
self.kernel_id = kernel_id
self.kernel_config_path = kernel_config_path
self.python_path = python_path
self.ipython_path = ipython_path
self.init_file_path = init_file_path
self.verbose = verbose
if python_path is None and ipython_path is None:
env = None
else:
env = {"PATH": self.python_path + ":$PATH",
"PYTHONPATH": self.python_path}
# Initialize the backend kernel
self.kernel_manager = jupyter_client.KernelManager(kernel_name=IPYKERNEL,
connection_file=self.kernel_config_path,
exec_files=[
self.init_file_path],
env=env)
if self.kernel_config_path:
self.kernel_manager.load_connection_file()
self.kernel_manager.start_kernel(stdout=PIPE, stderr=PIPE)
print("Backend kernel started with the configuration: {}".format(
self.kernel_config_path))
else:
self.kernel_manager.start_kernel(stdout=PIPE, stderr=PIPE)
print("Backend kernel started with the configuration: {}".format(
self.kernel_manager.connection_file))
if verbose:
print(self.kernel_manager.get_connection_info())
# Initialize the code kernel
self.kernel = self.kernel_manager.blocking_client()
# self.kernel.load_connection_file()
self.kernel.start_channels()
print("Code kernel started.")
def execute(self, code):
self.kernel.execute(code)
try:
shell_msg = self.kernel.get_shell_msg(timeout=40)
io_msg_content = self.kernel.get_iopub_msg(timeout=40)['content']
while True:
msg_out = io_msg_content
# Poll the message
try:
io_msg_content = self.kernel.get_iopub_msg(timeout=40)[
'content']
if 'execution_state' in io_msg_content and io_msg_content['execution_state'] == 'idle':
break
except queue.Empty:
break
return shell_msg, msg_out
except Exception as e:
print(e)
return None
def execute_interactive(self, code, verbose=False):
shell_msg = self.kernel.execute_interactive(code)
if shell_msg is queue.Empty:
if verbose:
print("Timeout waiting for shell message.")
self.check_msg(shell_msg, verbose=verbose)
return shell_msg
def inspect(self, code, verbose=False):
msg_id = self.kernel.inspect(code)
shell_msg = self.kernel.get_shell_msg(timeout=30)
if shell_msg is queue.Empty:
if verbose:
print("Timeout waiting for shell message.")
self.check_msg(shell_msg, verbose=verbose)
return shell_msg
def get_error_msg(self, msg, verbose=False) -> Optional[str]:
if msg['content']['status'] == 'error':
try:
error_msg = msg['content']['traceback']
except BaseException:
try:
error_msg = msg['content']['traceback'][-1].strip()
except BaseException:
error_msg = "Traceback Error"
if verbose:
print("Error: ", error_msg)
return error_msg
return None
def check_msg(self, msg, verbose=False):
status = msg['content']['status']
if status == 'ok':
if verbose:
print("Execution succeeded.")
elif status == 'error':
for line in msg['content']['traceback']:
if verbose:
print(line)
def shutdown(self):
# Shutdown the backend kernel
self.kernel_manager.shutdown_kernel()
print("Backend kernel shutdown.")
# Shutdown the code kernel
self.kernel.shutdown()
print("Code kernel shutdown.")
def restart(self):
# Restart the backend kernel
self.kernel_manager.restart_kernel()
# print("Backend kernel restarted.")
def interrupt(self):
# Interrupt the backend kernel
self.kernel_manager.interrupt_kernel()
# print("Backend kernel interrupted.")
def is_alive(self):
return self.kernel.is_alive()
def b64_2_img(data):
buff = BytesIO(base64.b64decode(data))
return Image.open(buff)
def clean_ansi_codes(input_string):
ansi_escape = re.compile(r'(\x9B|\x1B\[|\u001b\[)[0-?]*[ -/]*[@-~]')
return ansi_escape.sub('', input_string)
def execute(code, kernel: CodeKernel) -> tuple[str, Union[str, Image.Image]]:
res = ""
res_type = None
code = code.replace("<|observation|>", "")
code = code.replace("<|assistant|>interpreter", "")
code = code.replace("<|assistant|>", "")
code = code.replace("<|user|>", "")
code = code.replace("<|system|>", "")
msg, output = kernel.execute(code)
if msg['metadata']['status'] == "timeout":
return res_type, 'Timed out'
elif msg['metadata']['status'] == 'error':
return res_type, clean_ansi_codes('\n'.join(kernel.get_error_msg(msg, verbose=True)))
if 'text' in output:
res_type = "text"
res = output['text']
elif 'data' in output:
for key in output['data']:
if 'image/png' in key:
res_type = "image"
res = output['data'][key]
break
elif 'text/plain' in key:
res_type = "text"
res = output['data'][key]
if res_type == "image":
return res_type, b64_2_img(res)
elif res_type == "text" or res_type == "traceback":
res = res
return res_type, res
def extract_code(text: str) -> str:
pattern = r'```([^\n]*)\n(.*?)```'
matches = re.findall(pattern, text, re.DOTALL)
return matches[-1][1]

View File

@@ -1,186 +0,0 @@
# This is an example that uses the websockets api to know when a prompt execution is done
# Once the prompt execution is done it downloads the images using the /history endpoint
import io
import json
import random
import urllib
import uuid
import requests
# NOTE: websocket-client (https://github.com/websocket-client/websocket-client)
import websocket
from PIL import Image
class ComfyUIApi():
def __init__(self, server_address="127.0.0.1:8188"):
self.server_address = server_address
self.client_id = str(uuid.uuid4())
self.ws = websocket.WebSocket()
self.ws.connect(
"ws://{}/ws?clientId={}".format(server_address, self.client_id))
def queue_prompt(self, prompt):
p = {"prompt": prompt, "client_id": self.client_id}
data = json.dumps(p).encode('utf-8')
req = requests.post(
"http://{}/prompt".format(self.server_address), data=data)
print(req.text)
return json.loads(req.text)
def get_image(self, filename, subfolder, folder_type):
data = {"filename": filename,
"subfolder": subfolder, "type": folder_type}
url_values = urllib.parse.urlencode(data)
with requests.get("http://{}/view?{}".format(self.server_address, url_values)) as response:
image = Image.open(io.BytesIO(response.content))
return image
def get_image_url(self, filename, subfolder, folder_type):
data = {"filename": filename,
"subfolder": subfolder, "type": folder_type}
url_values = urllib.parse.urlencode(data)
return "http://{}/view?{}".format(self.server_address, url_values)
def get_history(self, prompt_id):
with requests.get("http://{}/history/{}".format(self.server_address, prompt_id)) as response:
return json.loads(response.text)
def get_images(self, prompt, isUrl=False):
prompt_id = self.queue_prompt(prompt)['prompt_id']
output_images = []
while True:
out = self.ws.recv()
if isinstance(out, str):
message = json.loads(out)
if message['type'] == 'executing':
data = message['data']
if data['node'] is None and data['prompt_id'] == prompt_id:
break # Execution is done
else:
continue # previews are binary data
history = self.get_history(prompt_id)[prompt_id]
for o in history['outputs']:
for node_id in history['outputs']:
node_output = history['outputs'][node_id]
if 'images' in node_output:
for image in node_output['images']:
image_data = self.get_image_url(image['filename'], image['subfolder'], image['type']) if isUrl else self.get_image(
image['filename'], image['subfolder'], image['type'])
image['image'] = image_data
output_images.append(image)
return output_images
prompt_text = """
{
"3": {
"class_type": "KSampler",
"inputs": {
"cfg": 8,
"denoise": 1,
"latent_image": [
"5",
0
],
"model": [
"4",
0
],
"negative": [
"7",
0
],
"positive": [
"6",
0
],
"sampler_name": "euler",
"scheduler": "normal",
"seed": 8566257,
"steps": 20
}
},
"4": {
"class_type": "CheckpointLoaderSimple",
"inputs": {
"ckpt_name": "chilloutmix_NiPrunedFp32Fix.safetensors"
}
},
"5": {
"class_type": "EmptyLatentImage",
"inputs": {
"batch_size": 1,
"height": 512,
"width": 512
}
},
"6": {
"class_type": "CLIPTextEncode",
"inputs": {
"clip": [
"4",
1
],
"text": "masterpiece best quality girl"
}
},
"7": {
"class_type": "CLIPTextEncode",
"inputs": {
"clip": [
"4",
1
],
"text": "bad hands"
}
},
"8": {
"class_type": "VAEDecode",
"inputs": {
"samples": [
"3",
0
],
"vae": [
"4",
2
]
}
},
"9": {
"class_type": "SaveImage",
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"8",
0
]
}
}
}
"""
if __name__ == '__main__':
prompt = json.loads(prompt_text)
# set the text prompt for our positive CLIPTextEncode
prompt["6"]["inputs"]["text"] = "masterpiece best quality man"
# set the seed for our KSampler node
prompt["3"]["inputs"]["seed"] = ''.join(
random.sample('123456789012345678901234567890', 14))
cfui = ComfyUIApi()
images = cfui.get_images(prompt)
# Commented out code to display the output images:
for node_id in images:
for image_data in images[node_id]:
import io
from PIL import Image
image = Image.open(io.BytesIO(image_data))
image.show()

View File

@@ -1,167 +0,0 @@
import inspect
import json
import random
import re
import traceback
from copy import deepcopy
from datetime import datetime
from types import GenericAlias
from typing import Annotated, get_origin
from base.chatglm.comfyUI_api import ComfyUIApi
from base.func_news import News
from zhdate import ZhDate
_TOOL_HOOKS = {}
_TOOL_DESCRIPTIONS = {}
def extract_code(text: str) -> str:
pattern = r'```([^\n]*)\n(.*?)```'
matches = re.findall(pattern, text, re.DOTALL)
return matches[-1][1]
def register_tool(func: callable):
tool_name = func.__name__
tool_description = inspect.getdoc(func).strip()
python_params = inspect.signature(func).parameters
tool_params = []
for name, param in python_params.items():
annotation = param.annotation
if annotation is inspect.Parameter.empty:
raise TypeError(f"Parameter `{name}` missing type annotation")
if get_origin(annotation) != Annotated:
raise TypeError(
f"Annotation type for `{name}` must be typing.Annotated")
typ, (description, required) = annotation.__origin__, annotation.__metadata__
typ: str = str(typ) if isinstance(typ, GenericAlias) else typ.__name__
if not isinstance(description, str):
raise TypeError(f"Description for `{name}` must be a string")
if not isinstance(required, bool):
raise TypeError(f"Required for `{name}` must be a bool")
tool_params.append({
"name": name,
"description": description,
"type": typ,
"required": required
})
tool_def = {
"name": tool_name,
"description": tool_description,
"parameters": tool_params
}
# print("[registered tool] " + pformat(tool_def))
_TOOL_HOOKS[tool_name] = func
_TOOL_DESCRIPTIONS[tool_name] = tool_def
return func
def dispatch_tool(tool_name: str, tool_params: dict) -> str:
if tool_name not in _TOOL_HOOKS:
return f"Tool `{tool_name}` not found. Please use a provided tool."
tool_call = _TOOL_HOOKS[tool_name]
try:
ret = tool_call(**tool_params)
except BaseException:
ret = traceback.format_exc()
return ret
def get_tools() -> dict:
return deepcopy(_TOOL_DESCRIPTIONS)
# Tool Definitions
# @register_tool
# def random_number_generator(
# seed: Annotated[int, 'The random seed used by the generator', True],
# range: Annotated[tuple[int, int], 'The range of the generated numbers', True],
# ) -> int:
# """
# Generates a random number x, s.t. range[0] <= x < range[1]
# """
# if not isinstance(seed, int):
# raise TypeError("Seed must be an integer")
# if not isinstance(range, tuple):
# raise TypeError("Range must be a tuple")
# if not isinstance(range[0], int) or not isinstance(range[1], int):
# raise TypeError("Range must be a tuple of integers")
# import random
# return random.Random(seed).randint(*range)
@register_tool
def get_weather(
city_name: Annotated[str, 'The name of the city to be queried', True],
) -> str:
"""
Get the current weather for `city_name`
"""
if not isinstance(city_name, str):
raise TypeError("City name must be a string")
key_selection = {
"current_condition": ["temp_C", "FeelsLikeC", "humidity", "weatherDesc", "observation_time"],
}
import requests
try:
resp = requests.get(f"https://wttr.in/{city_name}?format=j1")
resp.raise_for_status()
resp = resp.json()
ret = {k: {_v: resp[k][0][_v] for _v in v}
for k, v in key_selection.items()}
except BaseException:
import traceback
ret = "Error encountered while fetching weather data!\n" + traceback.format_exc()
return str(ret)
@register_tool
def get_confyui_image(prompt: Annotated[str, '要生成图片的提示词,注意必须是英文', True]) -> dict:
'''
生成图片
'''
with open("chatglm\\base.json", "r", encoding="utf-8") as f:
data2 = json.load(f)
data2['prompt']['3']['inputs']['seed'] = ''.join(
random.sample('123456789012345678901234567890', 14))
# 模型名称
data2['prompt']['4']['inputs']['ckpt_name'] = 'chilloutmix_NiPrunedFp32Fix.safetensors'
data2['prompt']['6']['inputs']['text'] = prompt # 正向提示词
# data2['prompt']['7']['inputs']['text']='' #反向提示词
cfui = ComfyUIApi(server_address="127.0.0.1:8188") # 根据自己comfyUI地址修改
images = cfui.get_images(data2['prompt'])
return {'res': images[0]['image'], 'res_type': 'image', 'filename': images[0]['filename']}
@register_tool
def get_news() -> str:
'''
获取最新新闻
'''
news = News()
return news.get_important_news()
@register_tool
def get_time() -> str:
'''
获取当前日期,时间,农历日期,星期几
'''
time = datetime.now()
date2 = ZhDate.from_datetime(time)
week_list = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"]
return '{} {} {}'.format(time.strftime("%Y年%m月%d%H:%M:%S"), week_list[time.weekday()], '农历:' + date2.chinese())
if __name__ == "__main__":
print(dispatch_tool("get_weather", {"city_name": "beijing"}))
print(get_tools())

View File

@@ -1,44 +0,0 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import google.generativeai as genai
class BardAssistant:
def __init__(self, conf: dict) -> None:
self._api_key = conf["api_key"]
self._model_name = conf["model_name"]
self._prompt = conf['prompt']
self._proxy = conf['proxy']
genai.configure(api_key=self._api_key)
self._bard = genai.GenerativeModel(self._model_name)
def __repr__(self):
return 'BardAssistant'
@staticmethod
def value_check(conf: dict) -> bool:
if conf:
if conf.get("api_key") and conf.get("model_name") and conf.get("prompt"):
return True
return False
def get_answer(self, msg: str, sender: str = None) -> str:
response = self._bard.generate_content([{'role': 'user', 'parts': [msg]}])
return response.text
if __name__ == "__main__":
from configuration import Config
config = Config().BardAssistant
if not config:
exit(0)
bard_assistant = BardAssistant(config)
if bard_assistant._proxy:
os.environ['HTTP_PROXY'] = bard_assistant._proxy
os.environ['HTTPS_PROXY'] = bard_assistant._proxy
rsp = bard_assistant.get_answer(bard_assistant._prompt)
print(rsp)

View File

@@ -1,195 +0,0 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import os
import random
from datetime import datetime
from typing import Optional
import httpx
from openai import OpenAI
from base.chatglm.code_kernel import CodeKernel, execute
from base.chatglm.tool_registry import dispatch_tool, extract_code, get_tools
from wcferry import Wcf
functions = get_tools()
class ChatGLM:
def __init__(self, config={}, wcf: Optional[Wcf] = None, max_retry=5) -> None:
key = config.get("key", 'empty')
api = config.get("api")
proxy = config.get("proxy")
if proxy:
self.client = OpenAI(api_key=key, base_url=api, http_client=httpx.Client(proxy=proxy))
else:
self.client = OpenAI(api_key=key, base_url=api)
self.conversation_list = {}
self.chat_type = {}
self.max_retry = max_retry
self.wcf = wcf
self.filePath = config["file_path"]
self.kernel = CodeKernel()
self.system_content_msg = {"chat": [{"role": "system", "content": config["prompt"]}],
"tool": [{"role": "system",
"content": "Answer the following questions as best as you can. You have access to the following tools:"}],
"code": [{"role": "system",
"content": "你是一位智能AI助手你叫ChatGLM你连接着一台电脑但请注意不能联网。在使用Python解决任务时你可以运行代码并得到结果如果运行结果有错误你需要尽可能对代码进行改进。你可以处理用户上传到电脑上的文件文件默认存储路径是{}".format(
self.filePath)}]}
def __repr__(self):
return 'ChatGLM'
@staticmethod
def value_check(conf: dict) -> bool:
if conf:
if conf.get("api") and conf.get("prompt") and conf.get("file_path"):
return True
return False
def get_answer(self, question: str, wxid: str) -> str:
# wxid或者roomid,个人时为微信id群消息时为群id
if '#帮助' == question:
return '本助手有三种模式,#聊天模式 = #1 #工具模式 = #2 #代码模式 = #3 , #清除模式会话 = #4 , #清除全部会话 = #5 可用发送#对应模式 或者 #编号 进行切换'
elif '#聊天模式' == question or '#1' == question:
self.chat_type[wxid] = 'chat'
return '已切换#聊天模式'
elif '#工具模式' == question or '#2' == question:
self.chat_type[wxid] = 'tool'
return '已切换#工具模式 \n工具有:查看天气,日期,新闻,comfyUI文生图。例如\n帮我生成一张小鸟的图片,提示词必须是英文'
elif '#代码模式' == question or '#3' == question:
self.chat_type[wxid] = 'code'
return '已切换#代码模式 \n代码模式可以用于写python代码例如\n用python画一个爱心'
elif '#清除模式会话' == question or '#4' == question:
self.conversation_list[wxid][self.chat_type[wxid]
] = self.system_content_msg[self.chat_type[wxid]]
return '已清除'
elif '#清除全部会话' == question or '#5' == question:
self.conversation_list[wxid] = self.system_content_msg
return '已清除'
self.updateMessage(wxid, question, "user")
try:
params = dict(model="chatglm3", temperature=1.0,
messages=self.conversation_list[wxid][self.chat_type[wxid]], stream=False)
if 'tool' == self.chat_type[wxid]:
params["tools"] = [dict(type='function', function=d) for d in functions.values()]
response = self.client.chat.completions.create(**params)
for _ in range(self.max_retry):
if response.choices[0].message.get("function_call"):
function_call = response.choices[0].message.function_call
print(
f"Function Call Response: {function_call.to_dict_recursive()}")
function_args = json.loads(function_call.arguments)
observation = dispatch_tool(
function_call.name, function_args)
if isinstance(observation, dict):
res_type = observation['res_type'] if 'res_type' in observation else 'text'
res = observation['res'] if 'res_type' in observation else str(
observation)
if res_type == 'image':
filename = observation['filename']
filePath = os.path.join(self.filePath, filename)
res.save(filePath)
self.wcf and self.wcf.send_image(filePath, wxid)
tool_response = '[Image]' if res_type == 'image' else res
else:
tool_response = observation if isinstance(
observation, str) else str(observation)
print(f"Tool Call Response: {tool_response}")
params["messages"].append(response.choices[0].message)
params["messages"].append(
{
"role": "function",
"name": function_call.name,
"content": tool_response, # 调用函数返回结果
}
)
self.updateMessage(wxid, tool_response, "function")
response = self.client.chat.completions.create(**params)
elif response.choices[0].message.content.find('interpreter') != -1:
output_text = response.choices[0].message.content
code = extract_code(output_text)
self.wcf and self.wcf.send_text('代码如下:\n' + code, wxid)
self.wcf and self.wcf.send_text('执行代码...', wxid)
try:
res_type, res = execute(code, self.kernel)
except Exception as e:
rsp = f'代码执行错误: {e}'
break
if res_type == 'image':
filename = '{}.png'.format(''.join(random.sample(
'abcdefghijklmnopqrstuvwxyz1234567890', 8)))
filePath = os.path.join(self.filePath, filename)
res.save(filePath)
self.wcf and self.wcf.send_image(filePath, wxid)
else:
self.wcf and self.wcf.send_text("执行结果:\n" + res, wxid)
tool_response = '[Image]' if res_type == 'image' else res
print("Received:", res_type, res)
params["messages"].append(response.choices[0].message)
params["messages"].append(
{
"role": "function",
"name": "interpreter",
"content": tool_response, # 调用函数返回结果
}
)
self.updateMessage(wxid, tool_response, "function")
response = self.client.chat.completions.create(**params)
else:
rsp = response.choices[0].message.content
break
self.updateMessage(wxid, rsp, "assistant")
except Exception as e0:
rsp = "发生未知错误:" + str(e0)
return rsp
def updateMessage(self, wxid: str, question: str, role: str) -> None:
now_time = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
# 初始化聊天记录,组装系统信息
if wxid not in self.conversation_list.keys():
self.conversation_list[wxid] = self.system_content_msg
if wxid not in self.chat_type.keys():
self.chat_type[wxid] = 'chat'
# 当前问题
content_question_ = {"role": role, "content": question}
self.conversation_list[wxid][self.chat_type[wxid]].append(
content_question_)
# 只存储10条记录超过滚动清除
i = len(self.conversation_list[wxid][self.chat_type[wxid]])
if i > 10:
print("滚动清除微信记录:" + wxid)
# 删除多余的记录,倒着删,且跳过第一个的系统消息
del self.conversation_list[wxid][self.chat_type[wxid]][1]
if __name__ == "__main__":
from configuration import Config
config = Config().CHATGLM
if not config:
exit(0)
chat = ChatGLM(config)
while True:
q = input(">>> ")
try:
time_start = datetime.now() # 记录开始时间
print(chat.get_answer(q, "wxid"))
time_end = datetime.now() # 记录结束时间
# 计算的时间差为程序的执行时间,单位为秒/s
print(f"{round((time_end - time_start).total_seconds(), 2)}s")
except Exception as e:
print(e)

View File

@@ -140,3 +140,12 @@ redis_config:
port: 6379 port: 6379
db: 0 db: 0
decode_responses: true decode_responses: true
#gewechat 配置
gewechat:
base_url: "http://192.168.2.240:2531/v2/api"
gewechat_token: "cb43f52db27e4a56bb6ec7da54373582"
app_id: "wx_3BC6eSHGE5xEm_hH3__7c"
callback_url : "http://192.168.2.192:8999/gewechat/callback"

View File

@@ -44,3 +44,42 @@ class Config(object):
# DB config # DB config
self.mariadb = yconfig.get("db_config", {}) self.mariadb = yconfig.get("db_config", {})
self.redis = yconfig.get("redis_config", {}) self.redis = yconfig.get("redis_config", {})
#gewechat config
gewechat_config = yconfig['gewechat']
self.BASE_URL = gewechat_config.get("base_url", "")
self.GEWECHAT_TOKEN = gewechat_config.get("gewechat_token", "")
self.APP_ID = gewechat_config.get("app_id", "")
self.CALLBACK_URL = gewechat_config.get("callback_url", "")
def update_config(self, section, key, value):
"""更新配置文件中指定部分的键值
Args:
section: 配置部分名称,如 'gewechat'
key: 键名,如 'app_id'
value: 要设置的值
"""
import yaml
import os
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.yaml')
# 读取当前配置
with open(config_path, 'r', encoding='utf-8') as f:
config_data = yaml.safe_load(f)
# 更新配置
if section in config_data:
config_data[section][key] = value
# 写回配置文件
with open(config_path, 'w', encoding='utf-8') as f:
yaml.dump(config_data, f, default_flow_style=False, allow_unicode=True)
# 更新当前实例的属性
if hasattr(self, key.upper()):
setattr(self, key.upper(), value)
return True
return False

458
db/contacts_db.py Normal file
View File

@@ -0,0 +1,458 @@
# -*- coding: utf-8 -*-
"""
微信联系人数据库操作类
用于管理微信联系人信息的存储和查询
"""
import logging
import json
from typing import List, Dict, Optional, Union, Any
from db.connection import DBConnectionManager
logger = logging.getLogger(__name__)
class ContactsDBOperator:
"""微信联系人数据库操作类"""
def __init__(self, db_manager: Optional[DBConnectionManager] = None):
"""初始化联系人数据库操作类
Args:
db_manager: 数据库连接管理器如果为None则自动获取单例
"""
self.db_manager = db_manager or DBConnectionManager.get_instance()
self._ensure_table_exists()
def _ensure_table_exists(self):
"""确保联系人表存在"""
try:
# 创建联系人表
sql = """
CREATE TABLE IF NOT EXISTS t_wechat_contacts (
id INT AUTO_INCREMENT PRIMARY KEY,
user_name VARCHAR(64) NOT NULL COMMENT '微信ID',
nick_name VARCHAR(128) COMMENT '昵称',
py_initial VARCHAR(128) COMMENT '拼音首字母',
quan_pin VARCHAR(256) COMMENT '全拼',
sex TINYINT COMMENT '性别1男2女0未知',
remark VARCHAR(128) COMMENT '备注',
remark_py_initial VARCHAR(128) COMMENT '备注拼音首字母',
remark_quan_pin VARCHAR(256) COMMENT '备注全拼',
signature TEXT COMMENT '个性签名',
alias VARCHAR(128) COMMENT '微信号',
sns_bg_img TEXT COMMENT '朋友圈背景图',
country VARCHAR(64) COMMENT '国家',
province VARCHAR(64) COMMENT '省份',
city VARCHAR(64) COMMENT '城市',
big_head_img_url TEXT COMMENT '大头像URL',
small_head_img_url TEXT COMMENT '小头像URL',
description TEXT COMMENT '描述',
card_img_url TEXT COMMENT '名片图片URL',
label_list TEXT COMMENT '标签列表',
phone_num_list TEXT COMMENT '电话号码列表',
type ENUM('friends', 'chatrooms', 'ghs') NOT NULL COMMENT '联系人类型:好友、群聊、公众号',
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_user_name` (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信联系人信息表';
"""
conn = self.db_manager.get_connection()
cursor = conn.cursor()
cursor.execute(sql)
# 创建群成员表 - 增加了更多字段以支持详细信息
sql_chatroom_member = """
CREATE TABLE IF NOT EXISTS t_chatroom_member (
id INT AUTO_INCREMENT PRIMARY KEY,
chatroom_id VARCHAR(64) NOT NULL COMMENT '群聊ID',
wxid VARCHAR(64) NOT NULL COMMENT '成员微信ID',
nick_name VARCHAR(128) COMMENT '成员昵称',
display_name VARCHAR(128) COMMENT '群内显示名称',
inviter_user_name VARCHAR(64) COMMENT '邀请人微信ID',
member_flag INT COMMENT '成员标志2049表示管理员',
big_head_img_url TEXT COMMENT '大头像URL',
small_head_img_url TEXT COMMENT '小头像URL',
is_owner TINYINT(1) DEFAULT 0 COMMENT '是否群主0否1是',
is_admin TINYINT(1) DEFAULT 0 COMMENT '是否管理员0否1是',
sex TINYINT COMMENT '性别1男2女0未知',
signature TEXT COMMENT '个性签名',
alias VARCHAR(128) COMMENT '微信号',
country VARCHAR(64) COMMENT '国家',
province VARCHAR(64) COMMENT '省份',
city VARCHAR(64) COMMENT '城市',
label_list TEXT COMMENT '标签列表',
phone_num_list TEXT COMMENT '电话号码列表',
py_initial VARCHAR(128) COMMENT '拼音首字母',
quan_pin VARCHAR(256) COMMENT '全拼',
remark_py_initial VARCHAR(128) COMMENT '备注拼音首字母',
remark_quan_pin VARCHAR(256) COMMENT '备注全拼',
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_member` (`chatroom_id`, `wxid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信群成员信息表';
"""
cursor.execute(sql_chatroom_member)
conn.commit()
logger.info("成功创建或确认微信联系人表和群成员表存在")
except Exception as e:
logger.error(f"创建微信联系人表或群成员表失败: {e}")
raise
finally:
if 'cursor' in locals():
cursor.close()
if 'conn' in locals():
self.db_manager.release_connection(conn)
def save_contacts(self, contacts_data: List[Dict], contact_type: str) -> bool:
"""保存联系人信息到数据库
Args:
contacts_data: 联系人数据列表
contact_type: 联系人类型,可选值:'friends', 'chatrooms', 'ghs'
Returns:
bool: 是否成功保存
"""
if not contacts_data:
logger.warning(f"没有{contact_type}类型的联系人数据需要保存")
return True
try:
conn = self.db_manager.get_connection()
cursor = conn.cursor()
for contact in contacts_data:
# 将驼峰命名转换为下划线命名
data = {
'user_name': contact.get('userName', ''),
'nick_name': contact.get('nickName', ''),
'py_initial': contact.get('pyInitial', ''),
'quan_pin': contact.get('quanPin', ''),
'sex': contact.get('sex', 0),
'remark': contact.get('remark', ''),
'remark_py_initial': contact.get('remarkPyInitial', ''),
'remark_quan_pin': contact.get('remarkQuanPin', ''),
'signature': contact.get('signature', ''),
'alias': contact.get('alias', ''),
'sns_bg_img': contact.get('snsBgImg', ''),
'country': contact.get('country', ''),
'province': contact.get('province', ''),
'city': contact.get('city', ''),
'big_head_img_url': contact.get('bigHeadImgUrl', ''),
'small_head_img_url': contact.get('smallHeadImgUrl', ''),
'description': contact.get('description', ''),
'card_img_url': contact.get('cardImgUrl', ''),
'label_list': contact.get('labelList', ''),
'phone_num_list': json.dumps(contact.get('phoneNumList', [])) if contact.get('phoneNumList') else '',
'type': contact_type
}
# 构建SQL语句
fields = ', '.join(data.keys())
placeholders = ', '.join(['%s'] * len(data))
values = tuple(data.values())
# 使用INSERT ... ON DUPLICATE KEY UPDATE语法
update_clause = ', '.join([f"{k}=VALUES({k})" for k in data.keys() if k != 'user_name'])
sql = f"""
INSERT INTO t_wechat_contacts ({fields})
VALUES ({placeholders})
ON DUPLICATE KEY UPDATE {update_clause}
"""
cursor.execute(sql, values)
conn.commit()
logger.info(f"成功保存{len(contacts_data)}{contact_type}类型的联系人")
return True
except Exception as e:
logger.error(f"保存{contact_type}类型的联系人失败: {e}")
if 'conn' in locals():
conn.rollback()
return False
finally:
if 'cursor' in locals():
cursor.close()
if 'conn' in locals():
self.db_manager.release_connection(conn)
def save_simple_contacts(self, contact_list: List[str], contact_type: str) -> bool:
"""保存简单联系人列表只有user_name到数据库
Args:
contact_list: 联系人ID列表
contact_type: 联系人类型,可选值:'friends', 'chatrooms', 'ghs'
Returns:
bool: 是否成功保存
"""
if not contact_list:
logger.warning(f"没有{contact_type}类型的联系人数据需要保存")
return True
try:
conn = self.db_manager.get_connection()
cursor = conn.cursor()
for user_name in contact_list:
# 构建SQL语句
sql = """
INSERT INTO t_wechat_contacts (user_name, type)
VALUES (%s, %s)
ON DUPLICATE KEY UPDATE type = VALUES(type), update_time = CURRENT_TIMESTAMP
"""
cursor.execute(sql, (user_name, contact_type))
conn.commit()
logger.info(f"成功保存{len(contact_list)}{contact_type}类型的简单联系人")
return True
except Exception as e:
logger.error(f"保存{contact_type}类型的简单联系人失败: {e}")
if 'conn' in locals():
conn.rollback()
return False
finally:
if 'cursor' in locals():
cursor.close()
if 'conn' in locals():
self.db_manager.release_connection(conn)
def get_contacts_by_type(self, contact_type: str) -> List[Dict]:
"""根据类型获取联系人列表
Args:
contact_type: 联系人类型,可选值:'friends', 'chatrooms', 'ghs'
Returns:
List[Dict]: 联系人列表
"""
try:
conn = self.db_manager.get_connection()
cursor = conn.cursor(dictionary=True)
sql = """
SELECT * FROM t_wechat_contacts
WHERE type = %s
ORDER BY nick_name
"""
cursor.execute(sql, (contact_type,))
results = cursor.fetchall()
return results
except Exception as e:
logger.error(f"获取{contact_type}类型的联系人失败: {e}")
return []
finally:
if 'cursor' in locals():
cursor.close()
if 'conn' in locals():
self.db_manager.release_connection(conn)
def get_contact_by_user_name(self, user_name: str) -> Optional[Dict]:
"""根据user_name获取联系人信息
Args:
user_name: 联系人ID
Returns:
Optional[Dict]: 联系人信息如果不存在则返回None
"""
try:
conn = self.db_manager.get_connection()
cursor = conn.cursor(dictionary=True)
sql = """
SELECT * FROM t_wechat_contacts
WHERE user_name = %s
LIMIT 1
"""
cursor.execute(sql, (user_name,))
result = cursor.fetchone()
return result
except Exception as e:
logger.error(f"获取联系人{user_name}失败: {e}")
return None
finally:
if 'cursor' in locals():
cursor.close()
if 'conn' in locals():
self.db_manager.release_connection(conn)
def get_display_name(self, user_name: str) -> str:
"""获取联系人的显示名称优先使用备注其次是昵称最后是微信ID
Args:
user_name: 联系人ID
Returns:
str: 显示名称
"""
contact = self.get_contact_by_user_name(user_name)
if not contact:
return user_name
return contact.get('remark') or contact.get('nick_name') or user_name
def get_all_contacts_name_map(self) -> Dict[str, str]:
"""获取所有联系人的ID到显示名称的映射
Returns:
Dict[str, str]: 联系人ID到显示名称的映射
"""
try:
conn = self.db_manager.get_connection()
cursor = conn.cursor()
sql = """
SELECT user_name, remark, nick_name FROM t_wechat_contacts
"""
cursor.execute(sql)
results = cursor.fetchall()
name_map = {}
for user_name, remark, nick_name in results:
display_name = remark or nick_name or user_name
name_map[user_name] = display_name
return name_map
except Exception as e:
logger.error(f"获取所有联系人名称映射失败: {e}")
return {}
finally:
if 'cursor' in locals():
cursor.close()
if 'conn' in locals():
self.db_manager.release_connection(conn)
def save_chatroom_member_detail(self, chatroom_id: str, member_details: List[Dict]) -> bool:
"""保存群成员详细信息到数据库
Args:
chatroom_id: 群聊ID
member_details: 群成员详细信息列表
Returns:
bool: 是否成功保存
"""
if not member_details or not chatroom_id:
logger.warning(f"没有群聊{chatroom_id}的成员详细信息需要保存")
return False
try:
conn = self.db_manager.get_connection()
cursor = conn.cursor()
# 获取现有的群成员信息,以便更新而不是替换
existing_members_sql = """
SELECT wxid, is_owner, is_admin FROM t_chatroom_member
WHERE chatroom_id = %s
"""
cursor.execute(existing_members_sql, (chatroom_id,))
existing_members = {row[0]: (row[1], row[2]) for row in cursor.fetchall()}
for member in member_details:
wxid = member.get('userName', '')
if not wxid:
continue
# 保留现有的群主和管理员标识
is_owner, is_admin = 0, 0
if wxid in existing_members:
is_owner, is_admin = existing_members[wxid]
# 处理电话号码列表
phone_num_list = member.get('phoneNumList', [])
if phone_num_list:
phone_num_str = json.dumps(phone_num_list)
else:
phone_num_str = ''
# 构建数据
data = {
'chatroom_id': chatroom_id,
'wxid': wxid,
'nick_name': member.get('nickName', ''),
'display_name': member.get('remark', ''), # 使用备注作为群内显示名称
'inviter_user_name': member.get('inviterUserName', ''),
'member_flag': member.get('memberFlag', 0),
'big_head_img_url': member.get('bigHeadImgUrl', ''),
'small_head_img_url': member.get('smallHeadImgUrl', ''),
'is_owner': is_owner,
'is_admin': is_admin,
# 额外的详细信息字段
'sex': member.get('sex', 0),
'signature': member.get('signature', ''),
'alias': member.get('alias', ''),
'country': member.get('country', ''),
'province': member.get('province', ''),
'city': member.get('city', ''),
'label_list': member.get('labelList', ''),
'phone_num_list': phone_num_str,
'py_initial': member.get('pyInitial', ''),
'quan_pin': member.get('quanPin', ''),
'remark_py_initial': member.get('remarkPyInitial', ''),
'remark_quan_pin': member.get('remarkQuanPin', '')
}
# 构建SQL语句 - 使用REPLACE INTO确保更新现有记录
fields = ', '.join(data.keys())
placeholders = ', '.join(['%s'] * len(data))
values = tuple(data.values())
sql = f"""
REPLACE INTO t_chatroom_member ({fields})
VALUES ({placeholders})
"""
cursor.execute(sql, values)
conn.commit()
logger.info(f"成功保存群聊{chatroom_id}{len(member_details)}个成员详细信息")
return True
except Exception as e:
logger.error(f"保存群聊{chatroom_id}的成员详细信息失败: {e}")
if 'conn' in locals():
conn.rollback()
return False
finally:
if 'cursor' in locals():
cursor.close()
if 'conn' in locals():
self.db_manager.release_connection(conn)
def process_chatroom_member_detail_response(self, chatroom_id: str, response: Dict) -> bool:
"""处理获取群成员详情的API响应
Args:
chatroom_id: 群聊ID
response: API响应数据
Returns:
bool: 是否成功处理
"""
try:
if response.get('ret') != 200:
logger.error(f"获取群聊{chatroom_id}成员详情失败: {response.get('msg')}")
return False
data = response.get('data', [])
if not data:
logger.warning(f"群聊{chatroom_id}成员详情数据为空")
return False
return self.save_chatroom_member_detail(chatroom_id, data)
except Exception as e:
logger.error(f"处理群聊{chatroom_id}成员详情数据失败: {e}")
return False

View File

@@ -3,10 +3,9 @@
from datetime import datetime from datetime import datetime
from typing import Dict, List, Optional from typing import Dict, List, Optional
from wcferry import WxMsg
from db.base import BaseDBOperator from db.base import BaseDBOperator
from db.connection import DBConnectionManager from db.connection import DBConnectionManager
from gewechat.call_back_message.message import WxMessage
class MessageStorageDB(BaseDBOperator): class MessageStorageDB(BaseDBOperator):
@@ -15,14 +14,15 @@ class MessageStorageDB(BaseDBOperator):
def __init__(self, db_manager: DBConnectionManager): def __init__(self, db_manager: DBConnectionManager):
super().__init__(db_manager) super().__init__(db_manager)
def archive_message(self, msg: WxMsg) -> bool: def archive_message(self, msg: WxMessage) -> bool:
"""存档消息""" """存档消息"""
now_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") now_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
sql = """ sql = """
INSERT INTO messages (group_id, timestamp, sender, content, message_type, attachment_url, message_id, message_xml, message_thumb) INSERT INTO messages (group_id, timestamp, sender, content, message_type, attachment_url, message_id, message_xml, message_thumb)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
""" """
params = (msg.roomid, now_time, msg.sender, msg.content, msg.type, msg.extra, msg.id, msg.xml, msg.thumb) params = (
msg.roomid, now_time, msg.sender, msg.content, msg.msg_type, msg.content, msg.msg_id, msg.msg_source, "")
result = self.execute_update(sql, params) result = self.execute_update(sql, params)
return result return result
@@ -51,7 +51,6 @@ class MessageStorageDB(BaseDBOperator):
""" """
return self.execute_query(sql, (date,)) or [] return self.execute_query(sql, (date,)) or []
def get_speech_ranking(self, date: str, group_id: str, limit: int = 20) -> List[Dict]: def get_speech_ranking(self, date: str, group_id: str, limit: int = 20) -> List[Dict]:
"""获取指定日期和群组的发言排名""" """获取指定日期和群组的发言排名"""
sql = """ sql = """
@@ -87,7 +86,6 @@ class MessageStorageDB(BaseDBOperator):
params = (group_id, wx_id, date, count) params = (group_id, wx_id, date, count)
return self.execute_update(sql, params) return self.execute_update(sql, params)
def get_message_trend(self, group_id: str, days: int = 7) -> List[Dict]: def get_message_trend(self, group_id: str, days: int = 7) -> List[Dict]:
"""获取指定群组的消息趋势数据 """获取指定群组的消息趋势数据

0
gewechat/__init__.py Normal file
View File

122
gewechat/api/callback.py Normal file
View File

@@ -0,0 +1,122 @@
from fastapi import APIRouter, Request
from gewechat.call_back_message.message import WxMessage, MessageType, AppMessageType
import logging
from robot import Robot
router = APIRouter()
logger = logging.getLogger(__name__)
# 存储Robot实例的字典以appid为键
robot_instances = {}
def register_robot(appid, robot_instance):
"""注册Robot实例"""
robot_instances[appid] = robot_instance
logger.info(f"已注册appid为{appid}的Robot实例")
@router.post("/gewechat/callback")
async def callback(request: Request):
"""接收微信消息回调"""
try:
# 获取原始JSON数据
json_data = await request.json()
# 创建消息对象
msg = WxMessage.from_json(json_data)
# 根据消息类型处理
if msg.type_name == "AddMsg":
await handle_add_message(msg)
elif msg.type_name == "ModContacts":
await handle_mod_contacts(msg)
elif msg.type_name == "DelContacts":
await handle_del_contacts(msg)
elif msg.type_name == "Offline":
await handle_offline(msg)
return {"code": 0, "message": "success"}
except Exception as e:
logger.error(f"处理回调消息失败: {str(e)}", exc_info=True)
return {"code": -1, "message": f"处理失败: {str(e)}"}
async def handle_add_message(msg: WxMessage):
"""处理新消息"""
try:
# 获取对应的Robot实例
robot: Robot = robot_instances.get(msg.appid)
if robot:
# 调用Robot的onMsg方法处理消息
robot.onMsg(msg)
else:
logger.warning(f"未找到appid为{msg.appid}的Robot实例")
except Exception as e:
logger.error(f"处理新消息失败: {str(e)}", exc_info=True)
raise
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):
"""处理联系人删除"""
logger.info(f"联系人被删除: {msg.raw_data}")
# 获取对应的Robot实例并刷新联系人
robot = robot_instances.get(msg.appid)
if robot:
robot.refresh_contacts()
async def handle_offline(msg: WxMessage):
"""处理离线通知"""
logger.info(f"账号离线: {msg.wxid}")
# 可以在这里处理账号离线逻辑
async def handle_text_message(msg: WxMessage):
"""处理文本消息"""
logger.info(f"收到文本消息: {msg.content.raw_content}")
# TODO: 实现文本消息处理逻辑
async def handle_image_message(msg: WxMessage):
"""处理图片消息"""
image_content = msg.get_image_content()
if image_content:
logger.info(f"收到图片消息: {image_content.url}")
# TODO: 实现图片消息处理逻辑
async def handle_app_message(msg: WxMessage):
"""处理应用消息"""
app_type = msg.get_app_message_type()
if app_type == AppMessageType.LINK:
logger.info("收到链接消息")
elif app_type == AppMessageType.FILE:
logger.info("收到文件消息")
elif app_type == AppMessageType.MINIPROGRAM:
logger.info("收到小程序消息")
# TODO: 实现应用消息处理逻辑
async def handle_system_message(msg: WxMessage):
"""处理系统消息"""
logger.info(f"收到系统消息: {msg.content.raw_content}")
# TODO: 实现系统消息处理逻辑
async def handle_system_notify(msg: WxMessage):
"""处理系统通知"""
logger.info(f"收到系统通知: {msg.content.raw_content}")
# TODO: 实现系统通知处理逻辑

View File

@@ -0,0 +1,85 @@
import threading
import logging
import socket
import uvicorn
from fastapi import FastAPI
from gewechat.api.callback import router as callback_router
# 配置日志
logger = logging.getLogger(__name__)
def is_port_in_use(port, host='0.0.0.0'):
"""检查端口是否被占用"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.bind((host, port))
return False
except socket.error:
return True
def start_fastapi_server(host="0.0.0.0", port=8999):
"""启动FastAPI服务器"""
# 检查端口是否被占用
if is_port_in_use(port, host):
logger.warning(f"端口 {port} 已被占用,尝试使用其他端口")
# 尝试其他端口
for test_port in range(9000, 9100):
if not is_port_in_use(test_port, host):
port = test_port
break
else:
logger.error("无法找到可用端口,服务器启动失败")
return False
try:
app = FastAPI()
app.include_router(callback_router)
# 添加健康检查路由
@app.get("/health")
async def health_check():
return {"status": "ok"}
logger.info(f"正在启动FastAPI服务器地址: http://{host}:{port}")
# 使用线程启动uvicorn服务器
server_thread = threading.Thread(
target=uvicorn.run,
args=(app,),
kwargs={"host": host, "port": port, "log_level": "info"},
daemon=True
)
server_thread.start()
logger.info(f"FastAPI 服务已在 http://{host}:{port} 启动")
logger.info(f"回调URL: http://{host}:{port}/gewechat/callback")
# 返回启动的端口,以便调用者知道实际使用的端口
return port
except Exception as e:
logger.error(f"启动FastAPI服务器失败: {e}", exc_info=True)
return False
if __name__ == '__main__':
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# 启动服务器
port = start_fastapi_server()
if port:
print(f"服务器启动成功,端口: {port}")
print(f"回调URL: http://localhost:{port}/gewechat/callback")
print(f"健康检查URL: http://localhost:{port}/health")
# 保持主线程运行
try:
import time
while True:
time.sleep(1)
except KeyboardInterrupt:
print("服务器已停止")
else:
print("服务器启动失败")

View File

@@ -0,0 +1,299 @@
from dataclasses import dataclass
from typing import Optional, Dict, Any
from enum import Enum
import xml.etree.ElementTree as ET
import json
class MessageType(Enum):
"""消息类型枚举"""
TEXT = 1 # 文本消息
IMAGE = 3 # 图片消息
VOICE = 34 # 语音消息
VIDEO = 43 # 视频消息
EMOJI = 47 # emoji表情
LOCATION = 48 # 地理位置
APP = 49 # 应用消息(链接、文件、小程序等)
SYSTEM = 10000 # 系统消息
SYSTEM_NOTIFY = 10002 # 系统通知
class AppMessageType(Enum):
"""应用消息类型枚举"""
LINK = 5 # 链接消息
FILE = 6 # 文件消息
FILE_NOTICE = 74 # 文件上传通知
MINIPROGRAM = 33 # 小程序消息
QUOTE = 57 # 引用消息
TRANSFER = 2000 # 转账消息
RED_PACKET = 2001 # 红包消息
CHANNELS = 51 # 视频号消息
@dataclass
class MessageContent:
"""消息内容"""
raw_content: str # 原始内容
xml_content: Optional[ET.Element] = None # XML内容(如果有)
def __post_init__(self):
"""处理XML内容"""
if self.raw_content.startswith('<?xml') or self.raw_content.startswith('<msg'):
try:
self.xml_content = ET.fromstring(self.raw_content)
except ET.ParseError:
self.xml_content = None
@dataclass
class ImageContent:
"""图片消息特定内容"""
aes_key: str
url: str
length: int
md5: str
thumb_base64: Optional[str] = None
@dataclass
class VoiceContent:
"""语音消息特定内容"""
voice_length: int
aes_key: str
url: str
voice_base64: Optional[str] = None
@dataclass
class VideoContent:
"""视频消息特定内容"""
aes_key: str
video_url: str
thumb_url: str
length: int
play_length: int
@dataclass
class LocationContent:
"""地理位置特定内容"""
x: float # 纬度
y: float # 经度
label: str # 地址标签
poi_name: Optional[str] = None # 地点名称
@dataclass
class WxMessage:
"""消息基础类"""
type_name: str
appid: str
wxid: str
msg_id: int
sender: str
to_user: str
roomid: str # 新增room_id属性
msg_type: MessageType
content: MessageContent
create_time: int
push_content: Optional[str]
new_msg_id: int
msg_seq: int
msg_source: str
raw_data: Dict[str, Any] # 原始JSON数据
@classmethod
def from_json(cls, json_data: Dict[str, Any]) -> 'WxMessage':
"""从JSON数据创建消息对象"""
data = json_data.get("Data", {})
to_user = data.get("ToUserName", {}).get("string", "")
return cls(
type_name=json_data.get("TypeName", ""),
appid=json_data.get("Appid", ""),
wxid=json_data.get("Wxid", ""),
msg_id=data.get("MsgId", 0),
sender=data.get("FromUserName", {}).get("string", ""),
to_user=to_user,
roomid=to_user if to_user.endswith("@chatroom") else "", # 设置room_id
msg_type=MessageType(data.get("MsgType", 0)),
content=MessageContent(data.get("Content", {}).get("string", "")),
create_time=data.get("CreateTime", 0),
push_content=data.get("PushContent"),
new_msg_id=data.get("NewMsgId", 0),
msg_seq=data.get("MsgSeq", 0),
msg_source=data.get("MsgSource", ""),
raw_data=json_data
)
def __str__(self) -> str:
"""返回消息的字符串表示,用于打印和日志"""
# 获取消息类型的名称
msg_type_name = self.msg_type.name if self.msg_type else "UNKNOWN"
# 处理不同类型的消息内容
content_str = ""
if self.msg_type == MessageType.TEXT:
# 文本消息直接显示内容
content_str = self.content.raw_content
elif self.msg_type == MessageType.IMAGE:
# 图片消息显示图片信息
img_content = self.get_image_content()
if img_content:
content_str = f"[图片] 大小: {img_content.length}字节, MD5: {img_content.md5}"
else:
content_str = "[图片]"
elif self.msg_type == MessageType.VOICE:
# 语音消息显示语音信息
voice_content = self.get_voice_content()
if voice_content:
content_str = f"[语音] 长度: {voice_content.voice_length}ms"
else:
content_str = "[语音]"
elif self.msg_type == MessageType.VIDEO:
# 视频消息显示视频信息
video_content = self.get_video_content()
if video_content:
content_str = f"[视频] 长度: {video_content.play_length}ms, 大小: {video_content.length}字节"
else:
content_str = "[视频]"
elif self.msg_type == MessageType.LOCATION:
# 位置消息显示位置信息
location_content = self.get_location_content()
if location_content:
content_str = f"[位置] {location_content.label}"
else:
content_str = "[位置]"
elif self.msg_type == MessageType.APP:
# 应用消息显示应用类型
app_type = self.get_app_message_type()
if app_type:
content_str = f"[应用消息] 类型: {app_type.name}"
else:
content_str = "[应用消息]"
elif self.msg_type == MessageType.EMOJI:
content_str = "[表情]"
elif self.msg_type == MessageType.SYSTEM:
content_str = f"[系统消息] {self.content.raw_content}"
elif self.msg_type == MessageType.SYSTEM_NOTIFY:
content_str = f"[系统通知] {self.content.raw_content}"
else:
# 其他类型消息
content_str = f"[未知类型消息] {self.content.raw_content[:30]}..."
# 限制内容长度,避免过长
if len(content_str) > 100:
content_str = content_str[:97] + "..."
# 构建基本信息
from_info = f"发送者: {self.sender}"
to_info = f"接收者: {self.to_user}"
# 如果是群消息,添加群信息
group_info = ""
if self.from_group():
group_info = f"群聊: {self.roomid}, "
# 构建完整的消息字符串
return (f"WxMessage[ID: {self.msg_id}, 类型: {msg_type_name}, "
f"{group_info}{from_info}, {to_info}, "
f"内容: {content_str}]")
def __repr__(self) -> str:
"""返回消息的详细表示,用于调试"""
return self.__str__()
def from_self(self) -> bool:
"""判断是否是自己发送的消息"""
return self.sender == self.wxid
def from_group(self) -> bool:
return self.to_user.endswith("@chatroom")
def get_app_message_type(self) -> Optional[AppMessageType]:
"""获取应用消息类型"""
if self.msg_type != MessageType.APP or not self.content.xml_content:
return None
try:
appmsg = self.content.xml_content.find('.//appmsg')
if appmsg is not None:
type_value = int(appmsg.find('type').text)
return AppMessageType(type_value)
except (AttributeError, ValueError):
pass
return None
def get_image_content(self) -> Optional[ImageContent]:
"""获取图片消息内容"""
if self.msg_type != MessageType.IMAGE or not self.content.xml_content:
return None
try:
img = self.content.xml_content.find('img')
if img is not None:
return ImageContent(
aes_key=img.get('aeskey', ''),
url=img.get('cdnthumburl', ''),
length=int(img.get('length', 0)),
md5=img.get('md5', ''),
thumb_base64=self.raw_data.get("Data", {}).get("ImgBuf", {}).get("buffer")
)
except (AttributeError, ValueError):
pass
return None
def get_voice_content(self) -> Optional[VoiceContent]:
"""获取语音消息内容"""
if self.msg_type != MessageType.VOICE or not self.content.xml_content:
return None
try:
voice = self.content.xml_content.find('.//voicemsg')
if voice is not None:
return VoiceContent(
voice_length=int(voice.get('voicelength', 0)),
aes_key=voice.get('aeskey', ''),
url=voice.get('voiceurl', ''),
voice_base64=self.raw_data.get("Data", {}).get("ImgBuf", {}).get("buffer")
)
except (AttributeError, ValueError):
pass
return None
def get_video_content(self) -> Optional[VideoContent]:
"""获取视频消息内容"""
if self.msg_type != MessageType.VIDEO or not self.content.xml_content:
return None
try:
video = self.content.xml_content.find('.//videomsg')
if video is not None:
return VideoContent(
aes_key=video.get('aeskey', ''),
video_url=video.get('cdnvideourl', ''),
thumb_url=video.get('cdnthumburl', ''),
length=int(video.get('length', 0)),
play_length=int(video.get('playlength', 0))
)
except (AttributeError, ValueError):
pass
return None
def get_location_content(self) -> Optional[LocationContent]:
"""获取地理位置内容"""
if self.msg_type != MessageType.LOCATION or not self.content.xml_content:
return None
try:
location = self.content.xml_content.find('location')
if location is not None:
return LocationContent(
x=float(location.get('x', 0)),
y=float(location.get('y', 0)),
label=location.get('label', ''),
poi_name=location.get('poiname')
)
except (AttributeError, ValueError):
pass
return None

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
import requests
url = "/tools/getTokenId"
payload={}
headers = {}
base_url="http://192.168.2.240:2531/v2/api"
response = requests.request("POST", base_url+url, headers=headers, data=payload)
print(response.text)

12
gewechat/client/login.py Normal file
View File

@@ -0,0 +1,12 @@
import requests
def login():
url = "/tools/getTokenId"
payload = {}
headers = {}
base_url = "http://192.168.2.240:2531/v2/api"
response = requests.request("POST", base_url + url, headers=headers, data=payload)
print(response.text)

146
main.py
View File

@@ -1,35 +1,126 @@
#! /usr/bin/env python3 #! /usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging
import signal
import threading import threading
from argparse import ArgumentParser from argparse import ArgumentParser
import uvicorn
from fastapi import FastAPI
from gewechat_client import GewechatClient
import socket
# 启动FastAPI服务器
# 从callback_url中提取主机和端口
import urllib.parse
from configuration import Config from configuration import Config
from constants import ChatType from constants import ChatType
from robot import Robot, __version__ from robot import Robot
from wcferry import Wcf from gewechat.api.callback import router as callback_router
# 配置日志
logger = logging.getLogger(__name__)
def is_port_in_use(port, host='0.0.0.0'):
"""检查端口是否被占用"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.bind((host, port))
return False
except socket.error:
return True
def start_fastapi_server(host="0.0.0.0", port=8999):
"""启动FastAPI服务器"""
# 检查端口是否被占用
if is_port_in_use(port, host):
logger.warning(f"端口 {port} 已被占用,尝试使用其他端口")
# 尝试其他端口
for test_port in range(9000, 9100):
if not is_port_in_use(test_port, host):
port = test_port
break
else:
logger.error("无法找到可用端口,服务器启动失败")
return False
try:
app = FastAPI()
app.include_router(callback_router)
# 添加健康检查路由
@app.get("/health")
async def health_check():
return {"status": "ok"}
logger.info(f"正在启动FastAPI服务器地址: http://{host}:{port}")
# 使用线程启动uvicorn服务器
server_thread = threading.Thread(
target=uvicorn.run,
args=(app,),
kwargs={"host": host, "port": port, "log_level": "info"},
daemon=True
)
server_thread.start()
logger.info(f"FastAPI 服务已在 http://{host}:{port} 启动")
logger.info(f"回调URL: http://{host}:{port}/gewechat/callback")
# 返回启动的端口,以便调用者知道实际使用的端口
return port
except Exception as e:
logger.error(f"启动FastAPI服务器失败: {e}", exc_info=True)
return False
def main(chat_type: int): def main(chat_type: int):
config = Config() config = Config()
wcf = Wcf(debug=True) base_url = config.BASE_URL
token = config.GEWECHAT_TOKEN
app_id = config.APP_ID
callback_url = config.CALLBACK_URL
send_msg_wxid = "filehelper" # 要发送消息的好友昵称
def handler(sig, frame): parsed_url = urllib.parse.urlparse(callback_url)
# 在退出前先关闭插件系统 host = parsed_url.hostname or "0.0.0.0"
robot.plugin_manager.shutdown_plugins() port = parsed_url.port or 8999
wcf.cleanup() # 退出前清理环境
exit(0)
signal.signal(signal.SIGINT, handler) # start_fastapi_server(host, port)
robot = Robot(config, wcf, chat_type) # 创建 GewechatClient 实例
robot.LOG.info(f"WeChatRobot【{__version__}】成功启动···") client = GewechatClient(base_url, token)
# 登录, 自动创建二维码,扫码后自动登录
app_id, error_msg = client.login(app_id=app_id)
if error_msg:
print("登录失败")
return
resp = client.set_callback(token, callback_url)
print(f"set_callback:{resp}")
# 如果启动时配置文件中的app_id为空那么将app_id写入配置文件
if not config.APP_ID:
# 更新配置文件中的APP_ID
config.update_config('gewechat', 'app_id', app_id)
print(f"已将新的APP_ID: {app_id} 写入配置文件")
# 同时更新当前配置对象中的APP_ID
config.APP_ID = app_id
# 创建机器人实例
robot = Robot(config, app_id, client, chat_type)
robot.LOG.info(f"WeChatRobot gewechat 成功启动···")
# # 注册Robot实例到callback模块
# from gewechat.api.callback import register_robot
# register_robot(app_id, robot)
# 机器人启动发送测试消息 # 机器人启动发送测试消息
robot.send_text_msg("启动成功!", "filehelper") client.post_text(app_id, send_msg_wxid, "gewechat client 启动成功!")
# 接收消息
robot.enableReceivingMsg() # 加队列
# 每天 8:30 发送新闻 # 每天 8:30 发送新闻
robot.onEveryTime("08:30", robot.news_baidu_report_auto) robot.onEveryTime("08:30", robot.news_baidu_report_auto)
@@ -52,21 +143,22 @@ def main(chat_type: int):
# 启动Dashboard服务器 # 启动Dashboard服务器
dashboard_server = None dashboard_server = None
try: # try:
# 创建Dashboard服务器实例共享robot对象 # # 创建Dashboard服务器实例共享robot对象
from admin.dashboard.server import DashboardServer # from admin.dashboard.server import DashboardServer
dashboard_server = DashboardServer(robot_instance=robot) # dashboard_server = DashboardServer(robot_instance=robot)
#
# 在单独的线程中启动Dashboard服务器 # # 在单独的线程中启动Dashboard服务器
dashboard_thread = threading.Thread(target=dashboard_server.run, daemon=True) # dashboard_thread = threading.Thread(target=dashboard_server.run, daemon=True)
dashboard_thread.start() # dashboard_thread.start()
robot.LOG.info(f"Dashboard服务器已在 http://{dashboard_server.host}:{dashboard_server.port} 启动") # robot.LOG.info(f"Dashboard服务器已在 http://{dashboard_server.host}:{dashboard_server.port} 启动")
except Exception as e: # except Exception as e:
robot.LOG.error(f"Dashboard服务器启动失败: {e}") # robot.LOG.error(f"Dashboard服务器启动失败: {e}")
# 让机器人一直跑 # 让机器人一直跑
robot.keep_running_and_block_process() robot.keep_running_and_block_process()
if __name__ == "__main__": if __name__ == "__main__":
parser = ArgumentParser() parser = ArgumentParser()
parser.add_argument('-c', type=int, default=0, help=f'选择模型参数序号: {ChatType.help_hint()}') parser.add_argument('-c', type=int, default=0, help=f'选择模型参数序号: {ChatType.help_hint()}')

View File

@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging import logging
import os.path
import random import random
import time import time
from typing import Optional from typing import Optional
from wcferry import Wcf from gewechat_client import GewechatClient
from utils.wechat.contact_manager import ContactManager
class MessageUtil: class MessageUtil:
@@ -13,8 +16,9 @@ class MessageUtil:
""" """
# 修改 MessageUtil 类的初始化方法,接受联系人管理器而不是联系人字典 # 修改 MessageUtil 类的初始化方法,接受联系人管理器而不是联系人字典
def __init__(self, wcf, contact_manager): def __init__(self, app_id: str, base_url: str, client: GewechatClient, contact_manager: ContactManager):
self.wcf = wcf self.app_id = app_id
self.client = client
self.contact_manager = contact_manager self.contact_manager = contact_manager
self.LOG = logging.getLogger("MessageUtil") self.LOG = logging.getLogger("MessageUtil")
@@ -35,19 +39,18 @@ class MessageUtil:
ats = " @所有人" ats = " @所有人"
else: else:
wxids = at_list.split(",") wxids = at_list.split(",")
for wxid in wxids: if len(wxids) > 0:
# 根据 wxid 查找群昵称 ats += self.get_user_chatroom_nickname(receiver, wxids)
ats += f" @{self.wcf.get_alias_in_chatroom(wxid, receiver)}"
# {msg}{ats} 表示要发送的消息内容后面紧跟@,例如 北京天气情况为xxx @张三 # {msg}{ats} 表示要发送的消息内容后面紧跟@,例如 北京天气情况为xxx @张三
if ats == "": if ats == "":
self.LOG.info(f"To {receiver}: {msg}") self.LOG.info(f"To {receiver}: {msg}")
self.wcf.send_text(f"{msg}", receiver, at_list) self.client.post_text(self.app_id, receiver, "{msg}", "")
else: else:
self.LOG.info(f"To {receiver}: {ats}\r{msg}") self.LOG.info(f"To {receiver}: {ats}\r{msg}")
self.wcf.send_text(f"{ats}\n{msg}", receiver, at_list) self.client.post_text(self.app_id, receiver, f"{ats}\n{msg}", at_list)
def send_file(self, file_path: str, receiver: str) -> None: def send_file(self, file_path: str, receiver: str) -> str:
""" """
发送文件消息 发送文件消息
@@ -58,9 +61,11 @@ class MessageUtil:
time.sleep(random.uniform(0.5, 1.5)) time.sleep(random.uniform(0.5, 1.5))
self.LOG.info(f"Sending file to {receiver}: {file_path}") self.LOG.info(f"Sending file to {receiver}: {file_path}")
self.wcf.send_file(file_path, receiver) (path, filename) = os.path.split(file_path)
self.LOG.info(f"Sending file to {path}: {filename}")
return self.client.post_file(self.app_id, receiver, file_path, filename)
def send_image(self, image_path: str, receiver: str) -> None: def send_image(self, image_path: str, receiver: str) -> str:
""" """
发送文件消息 发送文件消息
@@ -71,7 +76,7 @@ class MessageUtil:
time.sleep(random.uniform(0.5, 1.5)) time.sleep(random.uniform(0.5, 1.5))
self.LOG.info(f"Sending file to {receiver}: {image_path}") self.LOG.info(f"Sending file to {receiver}: {image_path}")
self.wcf.send_image(image_path, receiver) return self.client.post_image(self.app_id, receiver, image_path)
def send_rich_text(self, name: str, account: str, title: str, digest: str, url: str, thumburl: str, def send_rich_text(self, name: str, account: str, title: str, digest: str, url: str, thumburl: str,
receiver: str) -> int: receiver: str) -> int:
@@ -101,17 +106,21 @@ class MessageUtil:
time.sleep(random.uniform(0.5, 1.5)) time.sleep(random.uniform(0.5, 1.5))
self.LOG.info(f"Sending rich text to {receiver}: {title}") self.LOG.info(f"Sending rich text to {receiver}: {title}")
return self.wcf.send_rich_text(name, account, title, digest, url, thumburl, receiver) return self.client.post_link(self.app_id, receiver, title, digest, url, thumburl)
def update_contacts(self, contacts: dict) -> None: def get_user_chatroom_nickname(self, chatroom_id: str, member_wxids: list[str]) -> str:
""" data = self.client.get_chatroom_member_detail(self.app_id, chatroom_id, member_wxids)
更新联系人字典 nicknames_with_at = [" @" + member["nickName"] for member in data["data"] if member.get("nickName")]
return " ".join(nicknames_with_at)
:param contacts: 联系人字典,格式为 {"wxid": "NickName"} def invite_member(self, group_id, sender):
""" return self.client.invite_member(self.app_id, sender, group_id, "自动加群邀请")
self.contacts.update(contacts)
# 修改使用 allContacts 的地方,改为使用 contact_manager def get_chatroom_members(self, group_id) -> dict:
# 例如: data = self.client.get_chatroom_member_list(self.app_id, group_id)
# 原来的代码: nickname = self.allContacts.get(wxid, wxid) members = {member["wxid"]: member["nickName"] for member in data["data"]["memberList"]}
# 修改为: nickname = self.contact_manager.get_nickname(wxid) return members
def download_file_from_url(self, url: str, target_dir: str) -> str:
# 根据获取的文件地址从server 下载 http://{服务ip}:2532/download/{接口返回的文件路径}
return ""

View File

@@ -117,7 +117,7 @@ class BeautyLegPlugin(MessagePluginInterface):
# 发送图片 # 发送图片
random_file_path = os.path.abspath(random_file_path) random_file_path = os.path.abspath(random_file_path)
self.LOG.info(f"BeautyLeg.random_file_path: {random_file_path}") self.LOG.info(f"BeautyLeg.random_file_path: {random_file_path}")
result = wcf.send_file(random_file_path, (roomid if roomid else sender)) result = self.message_util.send_file(random_file_path, (roomid if roomid else sender))
self.LOG.info(f"发送图片结果: {result}") self.LOG.info(f"发送图片结果: {result}")
return True, "发送成功" return True, "发送成功"

View File

@@ -6,7 +6,6 @@ import time
import re # 添加re模块导入 import re # 添加re模块导入
from typing import Dict, Any, List, Optional, Tuple from typing import Dict, Any, List, Optional, Tuple
from wcferry import Wcf
from message_util import MessageUtil from message_util import MessageUtil
from plugin_common.message_plugin_interface import MessagePluginInterface from plugin_common.message_plugin_interface import MessagePluginInterface
@@ -63,7 +62,6 @@ class DifyPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...") self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象 # 保存上下文对象
self.wcf = context.get("wcf")
self.event_system = context.get("event_system") self.event_system = context.get("event_system")
self.message_util: MessageUtil = context.get("message_util") self.message_util: MessageUtil = context.get("message_util")
self.gbm = context.get("gbm") self.gbm = context.get("gbm")

View File

@@ -6,8 +6,7 @@ import traceback
import requests import requests
from typing import Dict, Any, List, Optional, Tuple from typing import Dict, Any, List, Optional, Tuple
from wcferry import Wcf from message_util import MessageUtil
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
@@ -61,9 +60,8 @@ class DouyinParserPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...") self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象 # 保存上下文对象
self.wcf = context.get("wcf")
self.event_system = context.get("event_system") self.event_system = context.get("event_system")
self.message_util = context.get("message_util") self.message_util:MessageUtil = context.get("message_util")
self.gbm = context.get("gbm") self.gbm = context.get("gbm")
# 从配置中获取参数 # 从配置中获取参数
@@ -103,7 +101,6 @@ class DouyinParserPlugin(MessagePluginInterface):
self.LOG.info(f"插件执行: {self.name}{content}") self.LOG.info(f"插件执行: {self.name}{content}")
sender = message.get("sender") sender = message.get("sender")
roomid = message.get("roomid", "") roomid = message.get("roomid", "")
wcf: Wcf = message.get("wcf")
gbm: GroupBotManager = message.get("gbm") gbm: GroupBotManager = message.get("gbm")
# 检查权限 # 检查权限
@@ -138,14 +135,14 @@ class DouyinParserPlugin(MessagePluginInterface):
# 下载并发送文件 # 下载并发送文件
mp4_path = self._download_stream(video_url, os.path.join(self.download_dir, "douyin.mp4")) mp4_path = self._download_stream(video_url, os.path.join(self.download_dir, "douyin.mp4"))
if mp4_path: if mp4_path:
wcf.send_file(mp4_path, (roomid if roomid else sender)) self.message_util.send_file(mp4_path, (roomid if roomid else sender))
return True, "发送视频文件成功" return True, "发送视频文件成功"
else: else:
print(f"❌下载视频失败") print(f"❌下载视频失败")
return False, "下载视频失败" return False, "下载视频失败"
else: else:
# 发送卡片 # 发送卡片
wcf.send_rich_text( self.message_util.send_rich_text(
"BOT-PC直接查看", "BOT-PC直接查看",
"gh_11", "gh_11",
title[:30], title[:30],

View File

@@ -3,8 +3,6 @@ import logging
from datetime import datetime from datetime import datetime
from typing import Dict, Any, List, Optional, Tuple from typing import Dict, Any, List, Optional, Tuple
from wcferry import Wcf
from message_util import MessageUtil from message_util import MessageUtil
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
@@ -52,7 +50,6 @@ class GameTaskPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...") self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象 # 保存上下文对象
self.wcf = context.get("wcf")
self.event_system = context.get("event_system") self.event_system = context.get("event_system")
self.message_util: MessageUtil = context.get("message_util") self.message_util: MessageUtil = context.get("message_util")
@@ -115,7 +112,6 @@ class GameTaskPlugin(MessagePluginInterface):
command = content.split(" ")[0].lower() command = content.split(" ")[0].lower()
sender = message.get("sender") sender = message.get("sender")
roomid = message.get("roomid", "") roomid = message.get("roomid", "")
wcf: Wcf = message.get("wcf")
gbm: GroupBotManager = message.get("gbm") gbm: GroupBotManager = message.get("gbm")
all_contacts = message.get("all_contacts", {}) all_contacts = message.get("all_contacts", {})

View File

@@ -4,8 +4,7 @@ import threading
import time # 添加这一行 import time # 添加这一行
from typing import Dict, Any, List, Optional, Tuple from typing import Dict, Any, List, Optional, Tuple
from wcferry import Wcf from message_util import MessageUtil
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
@@ -55,12 +54,13 @@ class GlobalNewsPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...") self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象 # 保存上下文对象
self.wcf = context.get("wcf")
self.event_system = context.get("event_system") self.event_system = context.get("event_system")
self.message_util = context.get("message_util") self.message_util: MessageUtil = context.get("message_util")
self._commands = self._config.get("GlobalNews", {}).get("command", ["全球新闻", "国际新闻", "环球新闻", "政经新闻"]) self._commands = self._config.get("GlobalNews", {}).get("command",
self.command_format = self._config.get("GlobalNews", {}).get("command-format", "全球新闻 - 获取最新的全球政治经济新闻") ["全球新闻", "国际新闻", "环球新闻", "政经新闻"])
self.command_format = self._config.get("GlobalNews", {}).get("command-format",
"全球新闻 - 获取最新的全球政治经济新闻")
self.enable = self._config.get("GlobalNews", {}).get("enable", True) self.enable = self._config.get("GlobalNews", {}).get("enable", True)
self.LOG.info(f"[{self.name}] 插件初始化完成,指令:{self._commands}") self.LOG.info(f"[{self.name}] 插件初始化完成,指令:{self._commands}")
@@ -96,7 +96,6 @@ class GlobalNewsPlugin(MessagePluginInterface):
self.LOG.info(f"插件执行: {self.name}{content}") self.LOG.info(f"插件执行: {self.name}{content}")
sender = message.get("sender") sender = message.get("sender")
roomid = message.get("roomid", "") roomid = message.get("roomid", "")
wcf: Wcf = message.get("wcf")
gbm: GroupBotManager = message.get("gbm") gbm: GroupBotManager = message.get("gbm")
# 检查权限 # 检查权限
@@ -107,26 +106,26 @@ class GlobalNewsPlugin(MessagePluginInterface):
task_id = f"{sender}_{roomid}_{int(time.time())}" task_id = f"{sender}_{roomid}_{int(time.time())}"
# 发送等待消息 # 发送等待消息
wcf.send_text("🌍正在获取全球新闻,请稍候...", self.message_util.send_text("🌍正在获取全球新闻,请稍候...",
(roomid if roomid else sender), sender) (roomid if roomid else sender), sender)
# 启动异步任务 # 启动异步任务
self._start_news_task(task_id, sender, roomid, wcf) self._start_news_task(task_id, sender, roomid)
return True, "新闻获取任务已启动" return True, "新闻获取任务已启动"
def _start_news_task(self, task_id: str, sender: str, roomid: str, wcf: Wcf): def _start_news_task(self, task_id: str, sender: str, roomid: str):
"""启动异步新闻获取任务""" """启动异步新闻获取任务"""
thread = threading.Thread( thread = threading.Thread(
target=self._fetch_news_thread, target=self._fetch_news_thread,
args=(task_id, sender, roomid, wcf) args=(task_id, sender, roomid)
) )
thread.daemon = True thread.daemon = True
thread.start() thread.start()
self._news_tasks[task_id] = thread self._news_tasks[task_id] = thread
self.LOG.info(f"启动新闻获取任务: {task_id}") self.LOG.info(f"启动新闻获取任务: {task_id}")
def _fetch_news_thread(self, task_id: str, sender: str, roomid: str, wcf: Wcf): def _fetch_news_thread(self, task_id: str, sender: str, roomid: str):
"""在单独的线程中运行异步新闻获取任务""" """在单独的线程中运行异步新闻获取任务"""
try: try:
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
@@ -138,14 +137,14 @@ class GlobalNewsPlugin(MessagePluginInterface):
if news_result: if news_result:
# 发送新闻图片 # 发送新闻图片
receiver = roomid if roomid else sender receiver = roomid if roomid else sender
wcf.send_image(news_result, receiver) self.message_util.send_image(news_result, receiver)
wcf.send_text("🌍全球新闻获取完成!", receiver, sender) self.message_util.send_text("🌍全球新闻获取完成!", receiver, sender)
else: else:
wcf.send_text("❌获取新闻失败,请稍后再试", self.message_util.send_text("❌获取新闻失败,请稍后再试",
(roomid if roomid else sender), sender) (roomid if roomid else sender), sender)
except Exception as e: except Exception as e:
self.LOG.error(f"新闻获取任务出错: {e}") self.LOG.error(f"新闻获取任务出错: {e}")
wcf.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)
finally: finally:
# 清理任务 # 清理任务

View File

@@ -3,7 +3,6 @@ import re
from datetime import datetime from datetime import datetime
from typing import Dict, Any, List, Optional, Tuple from typing import Dict, Any, List, Optional, Tuple
from wcferry import Wcf
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
@@ -45,8 +44,6 @@ class GroupAddPlugin(MessagePluginInterface):
self.LOG = logging.getLogger(f"Plugin.{self.name}") self.LOG = logging.getLogger(f"Plugin.{self.name}")
self.LOG.info(f"正在初始化 {self.name} 插件...") self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象
self.wcf = context.get("wcf")
# 获取群管理器 # 获取群管理器
self.gbm = context.get("gbm") self.gbm = context.get("gbm")
@@ -99,7 +96,6 @@ class GroupAddPlugin(MessagePluginInterface):
"""处理消息""" """处理消息"""
content = message.get("content", "") content = message.get("content", "")
roomid = message.get("roomid", "") roomid = message.get("roomid", "")
wcf: Wcf = message.get("wcf")
self.LOG.info(f"插件执行: {self.name}{content}") self.LOG.info(f"插件执行: {self.name}{content}")
@@ -116,7 +112,7 @@ class GroupAddPlugin(MessagePluginInterface):
welcome_message = f"🎉 欢迎 【{nickname}】 加入群聊👋 \n 🕒 {now_time} 🕒 " welcome_message = f"🎉 欢迎 【{nickname}】 加入群聊👋 \n 🕒 {now_time} 🕒 "
# 发送欢迎消息 # 发送欢迎消息
wcf.send_text(welcome_message, roomid) # self.me.send_text(welcome_message, roomid)
self.LOG.info(f"已发送欢迎消息给新成员 {nickname} 在群 {roomid}") self.LOG.info(f"已发送欢迎消息给新成员 {nickname} 在群 {roomid}")
return True, f"已欢迎 {nickname}" return True, f"已欢迎 {nickname}"

View File

@@ -3,8 +3,9 @@ import redis
import re import re
from typing import Dict, Any, List, Optional, Tuple from typing import Dict, Any, List, Optional, Tuple
from wcferry import Wcf from gewechat_client import GewechatClient
from message_util import MessageUtil
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
@@ -50,13 +51,13 @@ class GroupAutoInvitePlugin(MessagePluginInterface):
self.LOG = logging.getLogger(f"Plugin.{self.name}") self.LOG = logging.getLogger(f"Plugin.{self.name}")
self.LOG.info(f"正在初始化 {self.name} 插件...") self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象
self.wcf = context.get("wcf")
# 获取群管理器 # 获取群管理器
self.gbm = context.get("gbm") self.gbm = context.get("gbm")
# 获取Redis连接池 # 获取Redis连接池
self.redis_pool = context.get("redis_pool") self.redis_pool = context.get("redis_pool")
self.clent:GewechatClient = context.get("clent")
self.message_util: MessageUtil = context.get("message_util")
# 从配置中获取命令和启用状态 # 从配置中获取命令和启用状态
plugin_config = self._config.get("GroupAutoInvite", {}) plugin_config = self._config.get("GroupAutoInvite", {})
self._commands = plugin_config.get("command", ["#加群配置"]) self._commands = plugin_config.get("command", ["#加群配置"])
@@ -111,27 +112,26 @@ class GroupAutoInvitePlugin(MessagePluginInterface):
sender = message.get("sender") sender = message.get("sender")
roomid = message.get("roomid", "") roomid = message.get("roomid", "")
wcf: Wcf = message.get("wcf")
gbm: GroupBotManager = message.get("gbm") gbm: GroupBotManager = message.get("gbm")
# 处理加群配置命令 # 处理加群配置命令
if content.startswith("#加群配置|"): if content.startswith("#加群配置|"):
return self._handle_config_command(content, sender, roomid, wcf, gbm) return self._handle_config_command(content, sender, roomid, gbm)
# 处理加群请求 # 处理加群请求
match = re.search(r"^#加群\s+(\w+)$", content) match = re.search(r"^#加群\s+(\w+)$", content)
if match: if match:
return self._handle_join_request(match.group(1), sender, roomid, wcf, gbm) return self._handle_join_request(match.group(1), sender, roomid, gbm)
return False, "无法处理的消息" return False, "无法处理的消息"
def _handle_config_command(self, content: str, sender: str, roomid: str, wcf: Wcf, gbm: GroupBotManager) -> Tuple[ def _handle_config_command(self, content: str, sender: str, roomid: str, gbm: GroupBotManager) -> Tuple[
bool, Optional[str]]: bool, Optional[str]]:
"""处理配置命令""" """处理配置命令"""
# 检查是否为管理员 # 检查是否为管理员
admin_list = self.gbm.get_admin_list() admin_list = self.gbm.get_admin_list()
if sender not in admin_list: if sender not in admin_list:
wcf.send_text("⚠️ 权限不足,只有管理员才能配置群邀请功能", self.message_util.send_text("⚠️ 权限不足,只有管理员才能配置群邀请功能",
(roomid if roomid else sender), sender) (roomid if roomid else sender), sender)
return True, "权限不足" return True, "权限不足"
@@ -140,10 +140,10 @@ class GroupAutoInvitePlugin(MessagePluginInterface):
result = self.process_command(command) result = self.process_command(command)
# 发送结果 # 发送结果
wcf.send_text(result, (roomid if roomid else sender), sender) self.message_util.send_text(result, (roomid if roomid else sender), sender)
return True, "配置命令处理成功" return True, "配置命令处理成功"
def _handle_join_request(self, key: str, sender: str, roomid: str, wcf: Wcf, gbm: GroupBotManager) -> Tuple[ def _handle_join_request(self, key: str, sender: str, roomid: str, gbm: GroupBotManager) -> Tuple[
bool, Optional[str]]: bool, Optional[str]]:
"""处理加群请求""" """处理加群请求"""
try: try:
@@ -152,29 +152,29 @@ class GroupAutoInvitePlugin(MessagePluginInterface):
# 检查是否找到群ID # 检查是否找到群ID
if isinstance(group_id, str) and "没有关联的群ID" in group_id: if isinstance(group_id, str) and "没有关联的群ID" in group_id:
wcf.send_text(f"⚠️ 未找到关键词 '{key}' 对应的群聊", sender) self.message_util.send_text(f"⚠️ 未找到关键词 '{key}' 对应的群聊", sender)
return True, "未找到群聊" return True, "未找到群聊"
# 判断是否在群里面,如果在,则不添加 # 判断是否在群里面,如果在,则不添加
con = ContactManager.get_instance() con = ContactManager.get_instance()
members = con.get_group_members(group_id) members = con.get_group_members(group_id)
# 如果在群里面,则不添加 # 如果在群里面,则不添加
if sender in members: if sender in members:
wcf.send_text(f"⚠️ 你已经在群聊中了,无需重复添加", sender) self.message_util.send_text(f"⚠️ 你已经在群聊中了,无需重复添加", sender)
return True, "你已经在群聊中了" return True, "你已经在群聊中了"
# 发送邀请 # 发送邀请
self.LOG.info(f"邀请用户 {sender} 加入群 {group_id}") self.LOG.info(f"邀请用户 {sender} 加入群 {group_id}")
result = wcf.invite_chatroom_members(group_id, sender) result = self.message_util.invite_member(group_id, sender)
if result: if result:
wcf.send_text(f"✅ 已发送邀请,请查看群聊邀请通知", sender) self.message_util.send_text(f"✅ 已发送邀请,请查看群聊邀请通知", sender)
return True, "邀请发送成功" return True, "邀请发送成功"
else: else:
wcf.send_text(f"❌ 邀请发送失败,请稍后再试", sender) self.message_util.send_text(f"❌ 邀请发送失败,请稍后再试", sender)
return False, "邀请发送失败" return False, "邀请发送失败"
except Exception as e: except Exception as e:
self.LOG.error(f"处理加群请求出错: {e}") self.LOG.error(f"处理加群请求出错: {e}")
wcf.send_text(f"❌ 处理加群请求出错: {e}", sender) self.message_util.send_text(f"❌ 处理加群请求出错: {e}", sender)
return False, f"处理出错: {e}" return False, f"处理出错: {e}"
def add_mapping(self, key, group_id): def add_mapping(self, key, group_id):

View File

@@ -4,8 +4,6 @@ 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
from wcferry import Wcf
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
@@ -51,8 +49,6 @@ class GroupMemberChangePlugin(MessagePluginInterface):
"""初始化插件""" """初始化插件"""
self.LOG.info(f"正在初始化 {self.name} 插件...") self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象
self.wcf: Wcf = context.get("wcf")
# 创建消息工具实例message_util # 创建消息工具实例message_util
self.message_util: MessageUtil = context.get("message_util") self.message_util: MessageUtil = context.get("message_util")
@@ -130,7 +126,7 @@ class GroupMemberChangePlugin(MessagePluginInterface):
"""检查指定群的成员变化""" """检查指定群的成员变化"""
try: try:
# 获取当前群成员 # 获取当前群成员
current_members = self.wcf.get_chatroom_members(group_id) current_members = self.message_util.get_chatroom_members(group_id)
# 添加安全检查:如果获取到的成员列表为空,可能是接口异常 # 添加安全检查:如果获取到的成员列表为空,可能是接口异常
if not current_members: if not current_members:

View File

@@ -1,8 +1,7 @@
import logging import logging
from typing import Dict, Any, List, Optional, Tuple from typing import Dict, Any, List, Optional, Tuple
from wcferry import Wcf from gewechat.call_back_message.message import WxMessage
from message_util import MessageUtil from message_util import MessageUtil
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
@@ -51,7 +50,6 @@ class GroupVirtualPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...") self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象 # 保存上下文对象
self.wcf: Wcf = context.get("wcf")
self.event_system = context.get("event_system") self.event_system = context.get("event_system")
self.message_util: MessageUtil = context.get("message_util") self.message_util: MessageUtil = context.get("message_util")
@@ -99,9 +97,9 @@ class GroupVirtualPlugin(MessagePluginInterface):
"""处理消息""" """处理消息"""
roomid = message.get("roomid", "") roomid = message.get("roomid", "")
sender = message.get("sender", "") sender = message.get("sender", "")
full_wx_msg: WxMessage = message.get("full_wx_msg", "")
# 检查是否是机器人自己发送的消息 # 检查是否是机器人自己发送的消息
if sender == self.wcf.self_wxid: if full_wx_msg.from_self():
return False, "不转发自己的消息" return False, "不转发自己的消息"
# 获取该群所在的所有虚拟聊天组 # 获取该群所在的所有虚拟聊天组

View File

@@ -6,7 +6,6 @@ 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
from utils.robot_cmd.robot_command import GroupBotManager from utils.robot_cmd.robot_command import GroupBotManager
from wcferry import Wcf
class MessageRecallPlugin(MessagePluginInterface): class MessageRecallPlugin(MessagePluginInterface):
@@ -44,8 +43,6 @@ class MessageRecallPlugin(MessagePluginInterface):
self.LOG = logging.getLogger(f"Plugin.{self.name}") self.LOG = logging.getLogger(f"Plugin.{self.name}")
self.LOG.info(f"正在初始化 {self.name} 插件...") self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象
self.wcf: Wcf = context.get("wcf")
self.event_system = context.get("event_system") self.event_system = context.get("event_system")
self.message_util: MessageUtil = context.get("message_util") self.message_util: MessageUtil = context.get("message_util")
@@ -92,48 +89,47 @@ class MessageRecallPlugin(MessagePluginInterface):
sender = message.get("sender") sender = message.get("sender")
roomid = message.get("roomid", "") roomid = message.get("roomid", "")
wcf: Wcf = message.get("wcf")
# 检查是否是管理员 # 检查是否是管理员
admin_list = GroupBotManager.get_admin_list() admin_list = GroupBotManager.get_admin_list()
self.LOG.info(f"admin_list={admin_list}") self.LOG.info(f"admin_list={admin_list}")
# if sender not in admin_list: # if sender not in admin_list:
# wcf.send_text("⚠️ 权限不足,只有管理员才能撤回消息", # self.message_util.send_text("⚠️ 权限不足,只有管理员才能撤回消息",
# (roomid if roomid else sender), sender) # (roomid if roomid else sender), sender)
# return True, "权限不足" # return True, "权限不足"
# 解析命令获取消息ID # 解析命令获取消息ID
parts = content.split(" ", 1) parts = content.split(" ", 1)
if len(parts) < 2: if len(parts) < 2:
wcf.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 True, "命令格式错误" return True, "命令格式错误"
#
try: # try:
# 从数据库里面提取可以处理的消息and StrTalker ={roomid} # # 从数据库里面提取可以处理的消息and StrTalker ={roomid}
sql = (f"SELECT * FROM MSG where IsSender=1 and CreateTime > (strftime('%s', 'now') - 120) " # sql = (f"SELECT * FROM MSG where IsSender=1 and CreateTime > (strftime('%s', 'now') - 120) "
f"limit {parts[1]}") # f"limit {parts[1]}")
data = wcf.query_sql('MSG0.db', sql) # data = self.message_util.query_sql('MSG0.db', sql)
self.LOG.info(f"SQL:{sql}\n 查询到可撤回数据: {data}") # self.LOG.info(f"SQL:{sql}\n 查询到可撤回数据: {data}")
if not data: # if not data:
wcf.send_text("❌ 没有可撤回的消息", (roomid if roomid else sender), sender) # self.message_util.send_text("❌ 没有可撤回的消息", (roomid if roomid else sender), sender)
return True, "没有可撤回的消息" # return True, "没有可撤回的消息"
for item in data: # for item in data:
if item["MsgSvrID"]: # if item["MsgSvrID"]:
# 调用撤回消息API # # 调用撤回消息API
result = wcf.revoke_msg(item["MsgSvrID"]) # result = self.message_util.revoke_msg(item["MsgSvrID"])
if result: # if result:
wcf.send_text("✅ 消息撤回成功", (roomid if roomid else sender), sender) # self.message_util.send_text("✅ 消息撤回成功", (roomid if roomid else sender), sender)
return True, "撤回成功" # return True, "撤回成功"
else: # else:
wcf.send_text("❌ 消息撤回失败可能是消息ID无效或已超过撤回时间限制2分钟", # self.message_util.send_text("❌ 消息撤回失败可能是消息ID无效或已超过撤回时间限制2分钟",
(roomid if roomid else sender), sender) # (roomid if roomid else sender), sender)
return True, "撤回失败" # return True, "撤回失败"
#
except Exception as e: # except Exception as e:
self.LOG.error(f"撤回消息出错: {e}") # self.LOG.error(f"撤回消息出错: {e}")
wcf.send_text(f"❌ 撤回消息出错: {str(e)}", (roomid if roomid else sender), sender) # self.message_util.send_text(f"❌ 撤回消息出错: {str(e)}", (roomid if roomid else sender), sender)
return True, f"处理出错: {e}" # return True, f"处理出错: {e}"
def get_help(self) -> str: def get_help(self) -> str:
"""获取插件帮助信息""" """获取插件帮助信息"""

View File

@@ -3,7 +3,6 @@ import logging
import pytz import pytz
from typing import Dict, Any, List, Optional, Tuple from typing import Dict, Any, List, Optional, Tuple
from wcferry import Wcf
from db.connection import DBConnectionManager from db.connection import DBConnectionManager
from message_util import MessageUtil from message_util import MessageUtil
@@ -64,7 +63,6 @@ class MessageSignPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...") self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象 # 保存上下文对象
self.wcf = context.get("wcf")
self.event_system = context.get("event_system") self.event_system = context.get("event_system")
self.message_util: MessageUtil = context.get("message_util") self.message_util: MessageUtil = context.get("message_util")
self.gbm = context.get("gbm") self.gbm = context.get("gbm")
@@ -217,7 +215,6 @@ class MessageSignPlugin(MessagePluginInterface):
"""处理签到请求""" """处理签到请求"""
sender = message.get("sender") sender = message.get("sender")
roomid = message.get("roomid", "") roomid = message.get("roomid", "")
wcf: Wcf = message.get("wcf")
all_contacts = message.get("all_contacts", {}) all_contacts = message.get("all_contacts", {})
try: try:

View File

@@ -5,6 +5,7 @@ from typing import Dict, Any, Tuple, Optional, List
import requests import requests
from message_util import MessageUtil
from utils.string_utils import remove_trailing_content from utils.string_utils import remove_trailing_content
from utils.wechat.message_to_db import MessageStorage from utils.wechat.message_to_db import MessageStorage
from utils.compress_chat_data import compress_chat_data from utils.compress_chat_data import compress_chat_data
@@ -54,6 +55,8 @@ class MessageSummaryPlugin(MessagePluginInterface):
self._api_key = api_config.get("api_key", "app-McGLzBhBjeBCSEi7n83MtuTo") self._api_key = api_config.get("api_key", "app-McGLzBhBjeBCSEi7n83MtuTo")
self._api_url = api_config.get("api_url", "http://192.168.2.240/v1/chat-messages") self._api_url = api_config.get("api_url", "http://192.168.2.240/v1/chat-messages")
self.message_storage = MessageStorage() self.message_storage = MessageStorage()
self.message_util: MessageUtil = context.get("message_util")
self.LOG.info(f"初始化 {self.name} 插件成功") self.LOG.info(f"初始化 {self.name} 插件成功")
return True return True
except Exception as e: except Exception as e:
@@ -88,13 +91,10 @@ class MessageSummaryPlugin(MessagePluginInterface):
command = content[len(self.command_prefix):].split()[0] command = content[len(self.command_prefix):].split()[0]
if command not in self.commands: if command not in self.commands:
return False, None return False, None
wcf = message.get("wcf")
# 获取需要总结的内容 # 获取需要总结的内容
group_id = message.get("roomid") group_id = message.get("roomid")
if not group_id: if not group_id:
# 直接发送消息 self.message_util.send_text("只支持群聊消息总结", message.get("sender"))
if wcf:
wcf.send_text("只支持群聊消息总结", message.get("sender"))
return False, None return False, None
# 权限判断 # 权限判断
gbm: GroupBotManager = message.get("gbm") gbm: GroupBotManager = message.get("gbm")
@@ -110,8 +110,7 @@ class MessageSummaryPlugin(MessagePluginInterface):
# 获取群名并处理 # 获取群名并处理
group_name = all_contacts.get(group_id, group_id) group_name = all_contacts.get(group_id, group_id)
group_name = self._sanitize_group_name(group_name) group_name = self._sanitize_group_name(group_name)
if wcf: self.message_util.send_text("⏳群消息总结中… 😊", group_id)
wcf.send_text("⏳群消息总结中… 😊", group_id)
# 创建线程异步处理总结生成和发送 # 创建线程异步处理总结生成和发送
summary_thread = threading.Thread( summary_thread = threading.Thread(
@@ -138,17 +137,17 @@ class MessageSummaryPlugin(MessagePluginInterface):
wcf = message.get("wcf") wcf = message.get("wcf")
if wcf: if wcf:
# if summary: # if summary:
# wcf.send_text(f"总结已生成:\n{summary}", group_id, message.get("sender")) # self.message_util.send_text(f"总结已生成:\n{summary}", group_id, message.get("sender"))
if image_path: if image_path:
wcf.send_file(image_path, group_id) self.message_util.send_file(image_path, group_id)
else: else:
wcf.send_text("❌ 生成总结图片失败", group_id) self.message_util.send_text("❌ 生成总结图片失败", group_id)
except Exception as e: except Exception as e:
self.LOG.error(f"异步生成总结失败: {e}") self.LOG.error(f"异步生成总结失败: {e}")
wcf = message.get("wcf") wcf = message.get("wcf")
if wcf: if wcf:
wcf.send_text(f"❌ 生成总结失败: {str(e)}", group_id) self.message_util.send_text(f"❌ 生成总结失败: {str(e)}", group_id)
def _sanitize_group_name(self, group_name: str) -> str: def _sanitize_group_name(self, group_name: str) -> str:
"""处理群名,去除特殊字符并限制长度""" """处理群名,去除特殊字符并限制长度"""

View File

@@ -3,7 +3,6 @@ import requests
import lz4.block as lb import lz4.block as lb
from typing import Dict, Any, List, Optional, Tuple from typing import Dict, Any, List, Optional, Tuple
from wcferry import Wcf
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
@@ -48,7 +47,6 @@ class MusicPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...") self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象 # 保存上下文对象
self.wcf = context.get("wcf")
self.event_system = context.get("event_system") self.event_system = context.get("event_system")
self.message_util = context.get("message_util") self.message_util = context.get("message_util")
@@ -90,12 +88,11 @@ class MusicPlugin(MessagePluginInterface):
command = content.split(" ")[0] command = content.split(" ")[0]
sender = message.get("sender") sender = message.get("sender")
roomid = message.get("roomid", "") roomid = message.get("roomid", "")
wcf: Wcf = message.get("wcf")
gbm: GroupBotManager = message.get("gbm") gbm: GroupBotManager = message.get("gbm")
# 检查命令格式 # 检查命令格式
if len(content.split(" ")) == 1: if len(content.split(" ")) == 1:
wcf.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, "命令格式错误"
@@ -110,7 +107,7 @@ 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"):
wcf.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, "未找到歌曲"
@@ -204,19 +201,18 @@ class MusicPlugin(MessagePluginInterface):
</appinfo> </appinfo>
<commenturl /> <commenturl />
</msg>""" </msg>"""
# # 修改消息数据库里面的消息content内容
# 修改消息数据库里面的消息content内容 # text_bytes = xml_message.encode('utf-8')
text_bytes = xml_message.encode('utf-8') # compressed_data = lb.compress(text_bytes, store_size=False).hex()
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")
data = wcf.query_sql('MSG0.db', "SELECT * FROM MSG where type = 49 limit 1") # self.message_util.query_sql('MSG0.db',
wcf.query_sql('MSG0.db', # f"""UPDATE MSG SET CompressContent = x'{compressed_data}', BytesExtra=x'', type=49, SubType=3,
f"""UPDATE MSG SET CompressContent = x'{compressed_data}', BytesExtra=x'', type=49, SubType=3, # IsSender=0, TalkerId=2 WHERE MsgSvrID={data[0]['MsgSvrID']}"""
IsSender=0, TalkerId=2 WHERE MsgSvrID={data[0]['MsgSvrID']}""" # )
) #
# result = self.message_util.forward_msg(data[0]["MsgSvrID"], receiver)
result = wcf.forward_msg(data[0]["MsgSvrID"], receiver) # self.LOG.info(f"插件化:点歌发送结果: {result}")
self.LOG.info(f"插件化:点歌发送结果: {result}")
return True return True
except Exception as e: except Exception as e:

View File

@@ -94,14 +94,14 @@ class PluginManagerPlugin(MessagePluginInterface):
# 检查命令格式 # 检查命令格式
parts = content.split(" ") parts = content.split(" ")
if len(parts) == 1: if len(parts) == 1:
wcf.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, "命令格式错误"
# 只有使用的时候才全局获取对象。防止在预加载的时候跟主线程冲突 # 只有使用的时候才全局获取对象。防止在预加载的时候跟主线程冲突
self.plugin_registry = PluginRegistry() self.plugin_registry = PluginRegistry()
self.plugin_manager = PluginManager().get_instance() self.plugin_manager = PluginManager().get_instance()
# 检查权限 (只允许管理员操作) # 检查权限 (只允许管理员操作)
if not self._is_admin(sender, gbm): if not self._is_admin(sender, gbm):
wcf.send_text(f"❌权限不足,只有管理员可以管理插件", target, sender) self.message_util.send_text(f"❌权限不足,只有管理员可以管理插件", target, sender)
return True, "权限不足" return True, "权限不足"
# 解析子命令 # 解析子命令
@@ -125,14 +125,14 @@ class PluginManagerPlugin(MessagePluginInterface):
if handler and (sub_command == "列表" or plugin_name): if handler and (sub_command == "列表" or plugin_name):
return handler(wcf, sender, roomid) return handler(wcf, sender, roomid)
else: else:
wcf.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, "未知命令"
except Exception as e: except Exception as e:
import traceback import traceback
error_trace = traceback.format_exc() error_trace = traceback.format_exc()
self.LOG.error(f"处理插件管理请求出错: {e}\n{error_trace}") self.LOG.error(f"处理插件管理请求出错: {e}\n{error_trace}")
wcf.send_text(f"❌操作失败: {str(e)}", target, sender) self.message_util.send_text(f"❌操作失败: {str(e)}", target, sender)
return True, f"处理出错: {e}" return True, f"处理出错: {e}"
def _is_admin(self, user_id: str, gbm: GroupBotManager) -> bool: def _is_admin(self, user_id: str, gbm: GroupBotManager) -> bool:
@@ -153,7 +153,7 @@ class PluginManagerPlugin(MessagePluginInterface):
module_name = plugin.__class__.__module__.split('.')[-2] module_name = plugin.__class__.__module__.split('.')[-2]
message += f"{status}-{plugin.name} [模块: {module_name}]\n" message += f"{status}-{plugin.name} [模块: {module_name}]\n"
wcf.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, wcf, sender: str, roomid: str,
@@ -165,12 +165,12 @@ class PluginManagerPlugin(MessagePluginInterface):
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:
wcf.send_text(f"❌未找到插件 {plugin_name},请检查名称是否正确", target, sender) self.message_util.send_text(f"❌未找到插件 {plugin_name},请检查名称是否正确", target, sender)
return True, f"未找到插件 {plugin_name}" return True, f"未找到插件 {plugin_name}"
# 不允许操作自身(对于某些操作) # 不允许操作自身(对于某些操作)
if display_name == self.name and operation_func in [self._unload_plugin, self._disable_plugin]: if display_name == self.name and operation_func in [self._unload_plugin, self._disable_plugin]:
wcf.send_text(f"⚠️不能对插件管理插件自身执行此操作", target, sender) self.message_util.send_text(f"⚠️不能对插件管理插件自身执行此操作", target, sender)
return True, "不能对插件管理插件自身执行此操作" return True, "不能对插件管理插件自身执行此操作"
# 执行具体操作 # 执行具体操作
@@ -182,11 +182,11 @@ class PluginManagerPlugin(MessagePluginInterface):
plugin = self.plugin_registry.get_plugin(plugin_name) plugin = self.plugin_registry.get_plugin(plugin_name)
if not plugin: if not plugin:
wcf.send_text(f"❌插件 {plugin_name} 不存在", target, sender) self.message_util.send_text(f"❌插件 {plugin_name} 不存在", target, sender)
return True, f"插件 {plugin_name} 不存在" return True, f"插件 {plugin_name} 不存在"
if plugin.status == PluginStatus.RUNNING: if plugin.status == PluginStatus.RUNNING:
wcf.send_text(f"⚠️插件 {plugin_name} 已经处于启用状态", target, sender) self.message_util.send_text(f"⚠️插件 {plugin_name} 已经处于启用状态", target, sender)
return True, f"插件 {plugin_name} 已经处于启用状态" return True, f"插件 {plugin_name} 已经处于启用状态"
# 获取插件的模块名 # 获取插件的模块名
@@ -194,10 +194,10 @@ class PluginManagerPlugin(MessagePluginInterface):
# 启动插件 # 启动插件
if self.plugin_manager.start_plugin(module_name): if self.plugin_manager.start_plugin(module_name):
wcf.send_text(f"✅插件 {plugin_name} 启用成功", target, sender) self.message_util.send_text(f"✅插件 {plugin_name} 启用成功", target, sender)
return True, f"插件 {plugin_name} 启用成功" return True, f"插件 {plugin_name} 启用成功"
else: else:
wcf.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, wcf, sender: str, roomid: str) -> Tuple[bool, str]:
@@ -206,11 +206,11 @@ class PluginManagerPlugin(MessagePluginInterface):
plugin = self.plugin_registry.get_plugin(plugin_name) plugin = self.plugin_registry.get_plugin(plugin_name)
if not plugin: if not plugin:
wcf.send_text(f"❌插件 {plugin_name} 不存在", target, sender) self.message_util.send_text(f"❌插件 {plugin_name} 不存在", target, sender)
return True, f"插件 {plugin_name} 不存在" return True, f"插件 {plugin_name} 不存在"
if plugin.status != PluginStatus.RUNNING: if plugin.status != PluginStatus.RUNNING:
wcf.send_text(f"⚠️插件 {plugin_name} 已经处于禁用状态", target, sender) self.message_util.send_text(f"⚠️插件 {plugin_name} 已经处于禁用状态", target, sender)
return True, f"插件 {plugin_name} 已经处于禁用状态" return True, f"插件 {plugin_name} 已经处于禁用状态"
# 获取插件的模块名 # 获取插件的模块名
@@ -218,10 +218,10 @@ class PluginManagerPlugin(MessagePluginInterface):
# 停止插件 # 停止插件
if self.plugin_manager.stop_plugin(module_name): if self.plugin_manager.stop_plugin(module_name):
wcf.send_text(f"✅插件 {plugin_name} 禁用成功", target, sender) self.message_util.send_text(f"✅插件 {plugin_name} 禁用成功", target, sender)
return True, f"插件 {plugin_name} 禁用成功" return True, f"插件 {plugin_name} 禁用成功"
else: else:
wcf.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, wcf, sender: str, roomid: str) -> Tuple[bool, str]:
@@ -230,7 +230,7 @@ class PluginManagerPlugin(MessagePluginInterface):
plugin = self.plugin_registry.get_plugin(plugin_name) plugin = self.plugin_registry.get_plugin(plugin_name)
if not plugin: if not plugin:
wcf.send_text(f"❌插件 {plugin_name} 不存在", target, sender) self.message_util.send_text(f"❌插件 {plugin_name} 不存在", target, sender)
return True, f"插件 {plugin_name} 不存在" return True, f"插件 {plugin_name} 不存在"
# 记录原插件状态 # 记录原插件状态
@@ -243,10 +243,10 @@ class PluginManagerPlugin(MessagePluginInterface):
reloaded_plugin = self.plugin_manager.reload_plugin(module_name) reloaded_plugin = self.plugin_manager.reload_plugin(module_name)
if reloaded_plugin: if reloaded_plugin:
wcf.send_text(f"✅插件 {plugin_name} 重载成功", target, sender) self.message_util.send_text(f"✅插件 {plugin_name} 重载成功", target, sender)
return True, f"插件 {plugin_name} 重载成功" return True, f"插件 {plugin_name} 重载成功"
else: else:
wcf.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, wcf, sender: str, roomid: str) -> Tuple[bool, str]:
@@ -255,7 +255,7 @@ class PluginManagerPlugin(MessagePluginInterface):
plugin = self.plugin_registry.get_plugin(plugin_name) plugin = self.plugin_registry.get_plugin(plugin_name)
if not plugin: if not plugin:
wcf.send_text(f"❌插件 {plugin_name} 不存在", target, sender) self.message_util.send_text(f"❌插件 {plugin_name} 不存在", target, sender)
return True, f"插件 {plugin_name} 不存在" return True, f"插件 {plugin_name} 不存在"
# 获取插件的模块名 # 获取插件的模块名
@@ -263,10 +263,10 @@ class PluginManagerPlugin(MessagePluginInterface):
# 卸载插件 # 卸载插件
if self.plugin_manager.unload_plugin(module_name): if self.plugin_manager.unload_plugin(module_name):
wcf.send_text(f"✅插件 {plugin_name} 卸载成功", target, sender) self.message_util.send_text(f"✅插件 {plugin_name} 卸载成功", target, sender)
return True, f"插件 {plugin_name} 卸载成功" return True, f"插件 {plugin_name} 卸载成功"
else: else:
wcf.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, wcf, sender: str, roomid: str, silent: bool = False) -> Tuple[bool, str]:
@@ -276,7 +276,7 @@ class PluginManagerPlugin(MessagePluginInterface):
plugin_dir = os.path.join("plugins", plugin_name) plugin_dir = os.path.join("plugins", plugin_name)
if not os.path.exists(plugin_dir): if not os.path.exists(plugin_dir):
if not silent: if not silent:
wcf.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} 不存在"
@@ -285,7 +285,7 @@ class PluginManagerPlugin(MessagePluginInterface):
existing_module_name = existing_plugin.__class__.__module__.split('.')[-2] existing_module_name = existing_plugin.__class__.__module__.split('.')[-2]
if existing_module_name == plugin_name: if existing_module_name == plugin_name:
if not silent: if not silent:
wcf.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} 已经加载"
@@ -294,18 +294,18 @@ class PluginManagerPlugin(MessagePluginInterface):
plugin = self.plugin_manager.load_plugin(plugin_name) plugin = self.plugin_manager.load_plugin(plugin_name)
if plugin: if plugin:
if not silent: if not silent:
wcf.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:
wcf.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:
wcf.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}"
@@ -315,13 +315,13 @@ class PluginManagerPlugin(MessagePluginInterface):
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:
wcf.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:
wcf.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} 不存在"
@@ -340,5 +340,5 @@ class PluginManagerPlugin(MessagePluginInterface):
🔑 命令:{', '.join(plugin.commands) if hasattr(plugin, 'commands') else ''} 🔑 命令:{', '.join(plugin.commands) if hasattr(plugin, 'commands') else ''}
""" """
wcf.send_text(message, (roomid if roomid else sender), sender) self.message_util.send_text(message, (roomid if roomid else sender), sender)
return True, "查看插件详情成功" return True, "查看插件详情成功"

View File

@@ -4,7 +4,6 @@ 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 import xml.etree.ElementTree as ET
from wcferry import Wcf
from db.connection import DBConnectionManager from db.connection import DBConnectionManager
from db.points_db import PointsDBOperator, PointSource from db.points_db import PointsDBOperator, PointSource
@@ -54,7 +53,6 @@ class PointTradePlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...") self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象 # 保存上下文对象
self.wcf = context.get("wcf")
self.event_system = context.get("event_system") self.event_system = context.get("event_system")
self.message_util: MessageUtil = context.get("message_util") self.message_util: MessageUtil = context.get("message_util")
self.gbm = context.get("gbm") self.gbm = context.get("gbm")
@@ -129,7 +127,6 @@ class PointTradePlugin(MessagePluginInterface):
command = content.split(" ") command = content.split(" ")
sender = message.get("sender") sender = message.get("sender")
roomid = message.get("roomid", "") roomid = message.get("roomid", "")
wcf: Wcf = message.get("wcf")
gbm: GroupBotManager = message.get("gbm") gbm: GroupBotManager = message.get("gbm")
xml = message.get("xml", "") xml = message.get("xml", "")
@@ -159,7 +156,6 @@ class PointTradePlugin(MessagePluginInterface):
command = content.split(" ") command = content.split(" ")
sender = message.get("sender") sender = message.get("sender")
roomid = message.get("roomid", "") roomid = message.get("roomid", "")
wcf: Wcf = message.get("wcf")
xml = message.get("xml", "") xml = message.get("xml", "")
# 检查命令格式 # 检查命令格式
@@ -312,7 +308,6 @@ class PointTradePlugin(MessagePluginInterface):
"""处理积分排行榜命令""" """处理积分排行榜命令"""
sender = message.get("sender") sender = message.get("sender")
roomid = message.get("roomid", "") roomid = message.get("roomid", "")
wcf: Wcf = message.get("wcf")
if not roomid: if not roomid:
self.message_util.send_text("❌积分排行榜仅在群聊中可用!", sender, "") self.message_util.send_text("❌积分排行榜仅在群聊中可用!", sender, "")
@@ -452,7 +447,6 @@ class PointTradePlugin(MessagePluginInterface):
content = str(message.get("content", "")).strip() content = str(message.get("content", "")).strip()
sender = message.get("sender") sender = message.get("sender")
roomid = message.get("roomid", "") roomid = message.get("roomid", "")
wcf: Wcf = message.get("wcf")
xml = message.get("xml", "") xml = message.get("xml", "")
# 检查是否在群聊中 # 检查是否在群聊中
@@ -633,7 +627,6 @@ class PointTradePlugin(MessagePluginInterface):
"""处理保释命令""" """处理保释命令"""
sender = message.get("sender") sender = message.get("sender")
roomid = message.get("roomid", "") roomid = message.get("roomid", "")
wcf: Wcf = message.get("wcf")
xml = message.get("xml", "") xml = message.get("xml", "")
# 检查是否在群聊中 # 检查是否在群聊中

View File

@@ -78,7 +78,6 @@ class SystemUpdaterPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...") self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象 # 保存上下文对象
self.wcf = context.get("wcf")
self.message_util = context.get("message_util") self.message_util = context.get("message_util")
self.config = self._config self.config = self._config
@@ -127,12 +126,11 @@ class SystemUpdaterPlugin(MessagePluginInterface):
content = str(message.get("content", "")).strip() content = str(message.get("content", "")).strip()
sender = message.get("sender") sender = message.get("sender")
roomid = message.get("roomid", "") roomid = message.get("roomid", "")
wcf = message.get("wcf")
gbm = message.get("gbm", None) gbm = message.get("gbm", None)
# 检查权限 # 检查权限
if self.admin_wxids and sender not in self.admin_wxids: if self.admin_wxids and sender not in self.admin_wxids:
wcf.send_text("⚠️ 您没有执行此操作的权限", self.message_util.send_text("⚠️ 您没有执行此操作的权限",
(roomid if roomid else sender), sender) (roomid if roomid else sender), sender)
return True, "无权限" return True, "无权限"
@@ -156,12 +154,12 @@ class SystemUpdaterPlugin(MessagePluginInterface):
# 检查win_click模块是否可用 # 检查win_click模块是否可用
if win_click is None: if win_click is None:
wcf.send_text("⚠️ 无法执行更新操作,系统缺少必要的组件", self.message_util.send_text("⚠️ 无法执行更新操作,系统缺少必要的组件",
(roomid if roomid else sender), sender) (roomid if roomid else sender), sender)
return True, "缺少win_click模块" return True, "缺少win_click模块"
# 发送更新通知 # 发送更新通知
wcf.send_text(f"🔄 系统即将更新并重启,等待时间设置为{wait_time}秒...", self.message_util.send_text(f"🔄 系统即将更新并重启,等待时间设置为{wait_time}秒...",
(roomid if roomid else sender), sender) (roomid if roomid else sender), sender)
# 启动更新流程 # 启动更新流程

View File

@@ -3,7 +3,6 @@ import os
import requests import requests
from typing import Dict, Any, List, Optional, Tuple from typing import Dict, Any, List, Optional, Tuple
from wcferry import Wcf
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
@@ -50,7 +49,6 @@ class VideoPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...") self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象 # 保存上下文对象
self.wcf = context.get("wcf")
self.event_system = context.get("event_system") self.event_system = context.get("event_system")
self.message_util = context.get("message_util") self.message_util = context.get("message_util")
self.gbm = context.get("gbm") self.gbm = context.get("gbm")
@@ -96,7 +94,6 @@ class VideoPlugin(MessagePluginInterface):
self.LOG.info(f"插件执行: {self.name}{content}") self.LOG.info(f"插件执行: {self.name}{content}")
sender = message.get("sender") sender = message.get("sender")
roomid = message.get("roomid", "") roomid = message.get("roomid", "")
wcf: Wcf = message.get("wcf")
gbm: GroupBotManager = message.get("gbm") gbm: GroupBotManager = message.get("gbm")
# 检查权限 # 检查权限
@@ -109,18 +106,18 @@ class VideoPlugin(MessagePluginInterface):
file_abspath = self._download_stream("https://api.guiguiya.com/api/hook/heisis", save_path) file_abspath = self._download_stream("https://api.guiguiya.com/api/hook/heisis", save_path)
if not file_abspath or not file_abspath.endswith("mp4"): if not file_abspath or not file_abspath.endswith("mp4"):
wcf.send_text(f"\n❌视频下载失败,请稍后再试", self.message_util.send_text(f"\n❌视频下载失败,请稍后再试",
(roomid if roomid else sender), sender) (roomid if roomid else sender), sender)
return False, "视频下载失败" return False, "视频下载失败"
# 发送视频 # 发送视频
result = wcf.send_file(file_abspath, (roomid if roomid else sender)) result = self.message_util.send_file(file_abspath, (roomid if roomid else sender))
self.LOG.info(f"发送视频结果: {result}") self.LOG.info(f"发送视频结果: {result}")
return True, "发送成功" return True, "发送成功"
except Exception as e: except Exception as e:
self.LOG.error(f"处理视频请求出错: {e}") self.LOG.error(f"处理视频请求出错: {e}")
wcf.send_text(f"\n❌请求出错:{e}", self.message_util.send_text(f"\n❌请求出错:{e}",
(roomid if roomid else sender), sender) (roomid if roomid else sender), sender)
return False, f"处理出错: {e}" return False, f"处理出错: {e}"

View File

@@ -3,8 +3,6 @@ import os
import requests import requests
from typing import Dict, Any, List, Optional, Tuple from typing import Dict, Any, List, Optional, Tuple
from wcferry import Wcf
from message_util import MessageUtil from message_util import MessageUtil
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
@@ -51,7 +49,6 @@ class VideoManPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...") self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象 # 保存上下文对象
self.wcf = context.get("wcf")
self.event_system = context.get("event_system") self.event_system = context.get("event_system")
self.message_util: MessageUtil = context.get("message_util") self.message_util: MessageUtil = context.get("message_util")
self.gbm = context.get("gbm") self.gbm = context.get("gbm")
@@ -97,7 +94,6 @@ class VideoManPlugin(MessagePluginInterface):
self.LOG.info(f"插件执行: {self.name}{content}") self.LOG.info(f"插件执行: {self.name}{content}")
sender = message.get("sender") sender = message.get("sender")
roomid = message.get("roomid", "") roomid = message.get("roomid", "")
wcf: Wcf = message.get("wcf")
gbm: GroupBotManager = message.get("gbm") gbm: GroupBotManager = message.get("gbm")
# 检查权限 # 检查权限
@@ -114,7 +110,7 @@ class VideoManPlugin(MessagePluginInterface):
return False, "视频下载失败" return False, "视频下载失败"
# 发送视频 # 发送视频
result = wcf.send_file(file_abspath, (roomid if roomid else sender)) result = self.message_util.send_file(file_abspath, (roomid if roomid else sender))
self.LOG.info(f"发送视频结果: {result}") self.LOG.info(f"发送视频结果: {result}")
return True, "发送成功" return True, "发送成功"

View File

@@ -3,8 +3,6 @@ import os
import random import random
from typing import Dict, Any, List, Optional, Tuple from typing import Dict, Any, List, Optional, Tuple
from wcferry import Wcf
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
@@ -49,7 +47,6 @@ class XiurenImagePlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...") self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象 # 保存上下文对象
self.wcf = context.get("wcf")
self.event_system = context.get("event_system") self.event_system = context.get("event_system")
self.message_util = context.get("message_util") self.message_util = context.get("message_util")
@@ -95,7 +92,6 @@ class XiurenImagePlugin(MessagePluginInterface):
self.LOG.info(f"插件执行: {self.name}{content}") self.LOG.info(f"插件执行: {self.name}{content}")
sender = message.get("sender") sender = message.get("sender")
roomid = message.get("roomid", "") roomid = message.get("roomid", "")
wcf: Wcf = message.get("wcf")
gbm: GroupBotManager = message.get("gbm") gbm: GroupBotManager = message.get("gbm")
# 检查权限 # 检查权限
@@ -106,12 +102,12 @@ class XiurenImagePlugin(MessagePluginInterface):
# 获取随机图片 # 获取随机图片
pic_path = self._get_random_pic() pic_path = self._get_random_pic()
if not pic_path: if not pic_path:
wcf.send_text(f"❌未找到图片资源", self.message_util.send_text(f"❌未找到图片资源",
(roomid if roomid else sender), sender) (roomid if roomid else sender), sender)
return False, "未找到图片资源" return False, "未找到图片资源"
# 发送图片 # 发送图片
result = wcf.send_file(pic_path, (roomid if roomid else sender)) result = self.message_util.send_file(pic_path, (roomid if roomid else sender))
self.LOG.info(f"发送图片结果: {result}") self.LOG.info(f"发送图片结果: {result}")
return True, "发送成功" return True, "发送成功"

View File

@@ -7,7 +7,6 @@ requests~=2.32.3
schedule~=1.2.2 schedule~=1.2.2
pyhandytools pyhandytools
sparkdesk-api==1.3.0 sparkdesk-api==1.3.0
wcferry==39.5.1.0
websocket~=0.2.1 websocket~=0.2.1
pillow~=11.0.0 pillow~=11.0.0
jupyter_client~=8.6.3 jupyter_client~=8.6.3
@@ -46,3 +45,6 @@ pywin32==306
opencv-python~=4.11.0.86 opencv-python~=4.11.0.86
deepface~=0.0.93 deepface~=0.0.93
scikit-learn>=1.0.2 scikit-learn>=1.0.2
gewechat-client==0.1.5
fastapi~=0.115.12
uvicorn~=0.34.2

273
robot.py
View File

@@ -7,14 +7,12 @@ from queue import Empty
from threading import Thread from threading import Thread
import random import random
from gewechat_client import GewechatClient
from base.func_doubao import Doubao from base.func_doubao import Doubao
from base.func_epic import is_friday, get_free from base.func_epic import is_friday, get_free
from base.func_zhipu import ZhiPu 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_chatgpt import ChatGPT
from base.func_news import News from base.func_news import News
from base.func_tigerbot import TigerBot from base.func_tigerbot import TigerBot
@@ -22,6 +20,7 @@ from base.func_xinghuo_web import XinghuoWeb
from base.func_claude import Claude from base.func_claude import Claude
from configuration import Config from configuration import Config
from constants import ChatType from constants import ChatType
from gewechat.call_back_message.message import WxMessage, MessageType
from utils.wechat.message_to_db import MessageStorage from utils.wechat.message_to_db import MessageStorage
from plugin_common.event_system import EventType, EventSystem from plugin_common.event_system import EventType, EventSystem
from plugin_common.message_plugin_interface import MessagePluginInterface 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 Feature
from utils.robot_cmd.robot_command import PermissionStatus from utils.robot_cmd.robot_command import PermissionStatus
__version__ = "39.5.1.0"
from sehuatang.shehuatang import pdf_file_path from sehuatang.shehuatang import pdf_file_path
from utils.wechat.contact_manager import ContactManager from utils.wechat.contact_manager import ContactManager
from xiuren.meitu_dl import meitu_dowload_pub_pic 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: def __init__(self, config: Config, app_id: str, client: GewechatClient, chat_type: int) -> None:
self.wcf = wcf self.app_id = app_id
self.client = client
self.config = config self.config = config
self.LOG = logging.getLogger("Robot") self.LOG = logging.getLogger("Robot")
self.wxid = self.wcf.get_self_wxid()
# 初始化联系人管理器并设置联系人 # 初始化联系人管理器并设置联系人
self.contact_manager = ContactManager.get_instance() self.contact_manager = ContactManager.get_instance()
self.allContacts = self.get_all_contacts() self.allContacts = self.get_all_contacts()
self.all_head_img = self.get_all_head_img_url() self.contact_manager.set_contacts(self.allContacts)
self.contact_manager.set_contacts(self.allContacts, self.all_head_img, self.wcf)
self.LOG.info(f"DB+REDIS 连接池开始初始化") self.LOG.info(f"DB+REDIS 连接池开始初始化")
# 使用单例模式获取实例 # 使用单例模式获取实例
@@ -73,7 +69,7 @@ class Robot(Job):
self.redis_pool = self.db_manager.redis_pool 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条消息 self.groups = {} # 存储按group_id分组的消息列表每个group_id最多保留10条消息
GroupBotManager.load_local_cache() GroupBotManager.load_local_cache()
@@ -89,7 +85,7 @@ class Robot(Job):
# 设置插件系统上下文 # 设置插件系统上下文
self.system_context = { self.system_context = {
"config": config, "config": config,
"wcf": wcf, "client": client,
"event_system": self.event_system, "event_system": self.event_system,
"plugin_registry": self.plugin_registry, "plugin_registry": self.plugin_registry,
"db_pool": self.db_pool, "db_pool": self.db_pool,
@@ -105,48 +101,7 @@ class Robot(Job):
self.LOG.info("插件系统初始化完成") self.LOG.info("插件系统初始化完成")
# 消息存档模块初始化,自动完成入库动作 # 消息存档模块初始化,自动完成入库动作
self.message_storage = MessageStorage(self.wcf) self.message_storage = MessageStorage(self.client)
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}")
@staticmethod @staticmethod
def value_check(args: dict) -> bool: 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 all(value is not None for key, value in args.items() if key != 'proxy')
return False return False
def toChitchat(self, msg: WxMsg) -> bool: def toChitchat(self, msg: WxMessage) -> bool:
"""闲聊,接入 ChatGPT """闲聊,接入 ChatGPT
""" """
# 去除@的人和空格等字符 # 去除@的人和空格等字符
q = re.sub(r"@.*?[\u2005|\s]", "", msg.content).replace(" ", "") q = re.sub(r"@.*?[\u2005|\s]", "", msg.content.raw_content).replace(" ", "")
if q == "#今日百度新闻": if q == "#今日百度新闻":
self.news_baidu_report((msg.roomid if msg.from_group() else msg.sender)) self.news_baidu_report((msg.roomid if msg.from_group() else msg.sender))
@@ -176,7 +131,7 @@ class Robot(Job):
else: else:
return True return True
def processMsg(self, msg: WxMsg) -> None: def processMsg(self, msg: WxMessage) -> None:
"""当接收到消息的时候,会调用本方法。如果不实现本方法,则打印原始消息。 """当接收到消息的时候,会调用本方法。如果不实现本方法,则打印原始消息。
此处可进行自定义发送的内容,如通过 msg.content 关键字自动获取当前天气信息,并发送到对应的群组@发送者 此处可进行自定义发送的内容,如通过 msg.content 关键字自动获取当前天气信息,并发送到对应的群组@发送者
群号msg.roomid 微信IDmsg.sender 消息内容msg.content 群号msg.roomid 微信IDmsg.sender 消息内容msg.content
@@ -222,7 +177,7 @@ class Robot(Job):
try: try:
self.message_storage.archive_message(msg) self.message_storage.archive_message(msg)
# 单独处理图片消息 # 单独处理图片消息
if msg.type == 3: # 图片消息类型 if msg.msg_type == 3: # 图片消息类型
self.message_storage.process_image(msg) self.message_storage.process_image(msg)
except Exception as e: except Exception as e:
self.LOG.error(f"archive_message error: {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) rsp = self.gbm.handle_command(msg.roomid, msg.content)
# 不在群里发送,防止被骚扰 # 不在群里发送,防止被骚扰
if rsp is not None: 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 return
except Exception as e: except Exception as e:
self.LOG.error(f"revoke_receive_message error: {e}") self.LOG.error(f"revoke_receive_message error: {e}")
@@ -248,15 +203,7 @@ class Robot(Job):
if plugin_processed: if plugin_processed:
return return
# 非群聊信息,按消息类型进行处理 elif msg.msg_type == MessageType.TEXT: # 文本消息
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: # 文本消息
# 让配置加载更灵活,自己可以更新配置。也可以利用定时任务更新。 # 让配置加载更灵活,自己可以更新配置。也可以利用定时任务更新。
if msg.from_self(): if msg.from_self():
if msg.content == "^更新$": if msg.content == "^更新$":
@@ -274,79 +221,15 @@ class Robot(Job):
else: else:
self.toChitchat(msg) # 闲聊 self.toChitchat(msg) # 闲聊
def onMsg(self, msg: WxMsg) -> int: def onMsg(self, msg: WxMessage) -> int:
try: try:
self.LOG.debug(msg) # 打印信息 self.LOG.info(msg) # 打印信息
self.processMsg(msg) # self.processMsg(msg)
except Exception as e: except Exception as e:
self.LOG.error(e) self.LOG.error(e)
return 0 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: def keep_running_and_block_process(self) -> None:
""" """
保持机器人运行,不让进程退出 保持机器人运行,不让进程退出
@@ -355,31 +238,11 @@ class Robot(Job):
self.runPendingJobs() self.runPendingJobs()
time.sleep(1) 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): def refresh_contacts(self):
"""刷新联系人信息""" """刷新联系人信息"""
self.allContacts = self.get_all_contacts() self.allContacts = self.get_all_contacts()
self.all_head_img = self.get_all_head_img_url() self.contact_manager.refresh_contacts(self.allContacts)
self.contact_manager.refresh_contacts(self.allContacts, self.all_head_img, self.wcf)
self.LOG.info("联系人信息已刷新") self.LOG.info("联系人信息已刷新")
def send_group_txt_message(self, msg: str, feature: Feature): def send_group_txt_message(self, msg: str, feature: Feature):
@@ -389,7 +252,7 @@ class Robot(Job):
return return
for r in receivers: for r in receivers:
if self.gbm.get_group_permission(r, feature) == PermissionStatus.ENABLED: 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: except Exception as e:
self.LOG.error(f"send_group_txt_message:{feature.description} error{e}") self.LOG.error(f"send_group_txt_message:{feature.description} error{e}")
@@ -400,11 +263,11 @@ class Robot(Job):
return return
for r in receivers: for r in receivers:
if self.gbm.get_group_permission(r, feature) == PermissionStatus.ENABLED: 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: except Exception as e:
self.LOG.error(f"send_group_file_message:{feature.description} error{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) message_plugins = self.plugin_registry.get_plugins_by_type(MessagePluginInterface)
@@ -415,16 +278,15 @@ class Robot(Job):
continue continue
try: try:
# 转换WxMsg为插件可处理的格式 # 转换WxMessage为插件可处理的格式
plugin_msg = { plugin_msg = {
"type": msg.type, "type": msg.msg_type,
"content": msg.content, "content": msg.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.xml, "xml": msg.content.xml_content,
"is_at": msg.is_at(self.wxid), # 确保正确设置is_at标志 "is_at": msg.is_at(self.wxid), # 确保正确设置is_at标志
"timestamp": time.time(), "timestamp": time.time(),
"wcf": self.wcf, # 提供wcf对象让插件可以直接发送消息
"message_util": self.message_util, # 提供消息工具类 "message_util": self.message_util, # 提供消息工具类
"gbm": self.gbm, # 每次从程序变量中取,保证最新 "gbm": self.gbm, # 每次从程序变量中取,保证最新
"all_contacts": self.allContacts, "all_contacts": self.allContacts,
@@ -536,6 +398,87 @@ class Robot(Job):
def xiu_ren_pdf_send(self): def xiu_ren_pdf_send(self):
try: try:
pub_path = generate_pdf_from_images("xiuren") 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: 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:
"""获取所有联系人信息并保存到数据库"""
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

View File

@@ -143,7 +143,7 @@ def plugin_points_cost(points: int, description: str = None, feature: Feature =
# 积分不足 # 积分不足
wcf = message.get("wcf") wcf = message.get("wcf")
if wcf: if wcf:
wcf.send_text( self.message_util.send_text(
f"❌ 积分不足\n无法使用 {plugin_name} 功能\n" f"❌ 积分不足\n无法使用 {plugin_name} 功能\n"
f"🪙 先参与积分活动[签到,答题/t]赚取吧!\n" f"🪙 先参与积分活动[签到,答题/t]赚取吧!\n"
f"💰 有: {user_points['total_points']} | 需: {points} |差: {points - user_points['total_points']} ", f"💰 有: {user_points['total_points']} | 需: {points} |差: {points - user_points['total_points']} ",
@@ -170,7 +170,7 @@ def plugin_points_cost(points: int, description: str = None, feature: Feature =
response += f"\n\n💰 已消费 {points} 积分" response += f"\n\n💰 已消费 {points} 积分"
wcf = message.get("wcf") wcf = message.get("wcf")
if wcf: if wcf:
wcf.send_text( self.message_util.send_text(
f"💰消费 {points} 积分", f"💰消费 {points} 积分",
(roomid if roomid else sender), sender (roomid if roomid else sender), sender
) )

131
utils/json_converter.py Normal file
View File

@@ -0,0 +1,131 @@
# -*- coding: utf-8 -*-
"""
JSON数据转换工具
用于将JSON数据转换为Python对象
"""
class DictObject:
"""将字典转换为对象,使得可以通过属性访问"""
def __init__(self, data):
for key, value in data.items():
if isinstance(value, dict):
setattr(self, key, DictObject(value))
elif isinstance(value, list):
setattr(self, key, [
DictObject(item) if isinstance(item, dict) else item
for item in value
])
else:
setattr(self, key, value)
def __repr__(self):
"""打印对象时的表示形式"""
attrs = ', '.join(f"{key}={repr(value)}" for key, value in self.__dict__.items())
return f"{self.__class__.__name__}({attrs})"
def to_dict(self):
"""将对象转换回字典"""
result = {}
for key, value in self.__dict__.items():
if isinstance(value, DictObject):
result[key] = value.to_dict()
elif isinstance(value, list):
result[key] = [
item.to_dict() if isinstance(item, DictObject) else item
for item in value
]
else:
result[key] = value
return result
def json_to_object(json_data):
"""
将JSON数据转换为Python对象
Args:
json_data: JSON字符串或字典
Returns:
DictObject: 转换后的Python对象
"""
import json
# 如果输入是字符串,则解析为字典
if isinstance(json_data, str):
data = json.loads(json_data)
else:
data = json_data
# 转换为对象
return DictObject(data)
# 使用示例
if __name__ == "__main__":
# 示例JSON数据
example_json = {
"ret": 200,
"msg": "操作成功",
"data": {
"friends": [
"tmessage",
"medianote",
"qmessage",
"qqmail",
"wxid_910acevfm2nb21",
"qqsafe",
"wxid_9299552988412",
"weixin",
"exmail_tool",
"wxid_mp05xmje0ctn22",
"wxid_09oq4f4j4wg912",
"wxid_6bfguz79h8n122",
"wxid_lyuq4hr4lrjq22",
"wxid_a1zqyljsrsdu12",
"wxid_lv3pb3zhna3522",
"wxid_k2biq6fuinsr22",
"wxid_ujredjhxz9y712",
"wxid_uwb7989u0jea12",
"wxid_in46ey732vxu12",
"wxid_3rvervwohj6921",
"wxid_4wkls7tu62ua12",
"wxid_g0bdknnotx2f12",
"wxid_ce5fgp0icb3y21",
"wxid_1482424825211",
"wxid_vw3p4f6jy7bm12",
"wxid_o2m8xm71c23522",
"wxid_bclqpc2ho6o412",
"wxid_98pjjzpiisi721",
"wxid_noq2wsn5c8h222"
],
"chatrooms": [
"2180313478@chatroom",
"14358945067@chatroom",
"17362526147@chatroom",
"11685224357@chatroom",
"17522822550@chatroom"
],
"ghs": [
"gh_7aac992b0363",
"gh_d7293b5f14f4",
"gh_f51ce3ef83a4",
"gh_7d20df86e26b",
"gh_69bfb92a3e43"
]
}
}
# 转换为对象
obj = json_to_object(example_json)
# 通过属性访问
print(f"返回码: {obj.ret}")
print(f"消息: {obj.msg}")
print(f"好友数量: {len(obj.data.friends)}")
print(f"第一个好友: {obj.data.friends[0]}")
print(f"第一个群聊: {obj.data.chatrooms[0]}")
# 转换回字典
dict_data = obj.to_dict()
print(f"转换回字典: {dict_data}")

View File

@@ -41,7 +41,7 @@ class Feature(Enum):
VIDEO = 14, "黑丝视频 [黑丝视频, 黑丝, 来个黑丝,搞个黑丝]" VIDEO = 14, "黑丝视频 [黑丝视频, 黑丝, 来个黑丝,搞个黑丝]"
VIDEO_MAN = 15, "肌肉视频 [猛男, 肌肉, 帅哥]" VIDEO_MAN = 15, "肌肉视频 [猛男, 肌肉, 帅哥]"
# GROUP_ADD = 16, "加群提醒" # GROUP_ADD = 16, "加群提醒"
# DOUYIN_PARSER = 17, "抖音链接转视频" DOUYIN_PARSER = 17, "抖音链接转视频"
GROUP_MEMBER_CHANGE = 18, "群成员变更提醒功能" GROUP_MEMBER_CHANGE = 18, "群成员变更提醒功能"
# KID_PHOTO_EXTRACT = 19, "儿童照片提取转发功能" # 小朋友照片提取功能 # KID_PHOTO_EXTRACT = 19, "儿童照片提取转发功能" # 小朋友照片提取功能
NEWS = 20, "全球政治经济新闻" NEWS = 20, "全球政治经济新闻"

View File

@@ -5,7 +5,9 @@
import logging import logging
from typing import Dict, Optional, List, Tuple from typing import Dict, Optional, List, Tuple
from wcferry import Wcf from gewechat_client import GewechatClient
from utils.json_converter import json_to_object
class ContactManager: class ContactManager:
@@ -50,7 +52,7 @@ class ContactManager:
cls._instance = ContactManager() cls._instance = ContactManager()
return cls._instance return cls._instance
def set_contacts(self, contacts: Dict[str, str], head_imgs: Dict[str, str], wcf: Wcf) -> None: def set_contacts(self, contacts: Dict[str, str]) -> None:
"""设置联系人字典 """设置联系人字典
Args: Args:
@@ -67,13 +69,12 @@ class ContactManager:
"gender": gender} "gender": gender}
""" """
self._contacts = contacts self._contacts = contacts
self._friends = wcf.get_friends() self._friends = contacts
self._head_images = head_imgs
self._logger.info(f"联系人信息已更新,共 {len(contacts)} 个联系人") self._logger.info(f"联系人信息已更新,共 {len(contacts)} 个联系人")
# 分类联系人 # 分类联系人
self._classify_contacts(wcf) self._classify_contacts()
def _classify_contacts(self, wcf: Wcf) -> None: def _classify_contacts(self) -> None:
"""将联系人分类为群组、个人联系人、公共好友和公众号""" """将联系人分类为群组、个人联系人、公共好友和公众号"""
self._group_contacts = {} self._group_contacts = {}
self._personal_contacts = {} self._personal_contacts = {}
@@ -90,8 +91,6 @@ class ContactManager:
# 判断是否为群组wxid以@chatroom结尾 # 判断是否为群组wxid以@chatroom结尾
elif wxid.endswith('@chatroom'): elif wxid.endswith('@chatroom'):
self._group_contacts[wxid] = nickname self._group_contacts[wxid] = nickname
# 如果是群,这处理群列表内容
self._group_contacts_friends[wxid] = wcf.get_chatroom_members(wxid)
# # 其他为普通好友和群成员 # # 其他为普通好友和群成员
# else: # else:
@@ -216,7 +215,7 @@ class ContactManager:
self._personal_contacts[wxid] = nickname self._personal_contacts[wxid] = nickname
self._logger.debug(f"已更新联系人: {wxid} -> {nickname}") self._logger.debug(f"已更新联系人: {wxid} -> {nickname}")
def refresh_contacts(self, new_contacts: Dict[str, str], head_imgs: Dict[str, str], wcf: Wcf) -> None: def refresh_contacts(self, new_contacts: Dict[str, str]) -> None:
"""刷新联系人信息 """刷新联系人信息
Args: Args:
@@ -235,11 +234,9 @@ class ContactManager:
# "province": cnt.get("province", ""), # "province": cnt.get("province", ""),
# "city": cnt.get("city", ""), # "city": cnt.get("city", ""),
# "gender": gender} # "gender": gender}
self._friends = wcf.get_friends() self._friends = self.message_util.get_friends()
self._head_images = head_imgs
self._logger.info(f"联系人信息已刷新,共 {len(new_contacts)} 个联系人") self._logger.info(f"联系人信息已刷新,共 {len(new_contacts)} 个联系人")
# 重新分类联系人 self._classify_contacts()
self._classify_contacts(wcf)
def get_contact_statistics(self) -> Tuple[int, int, int, int, int]: def get_contact_statistics(self) -> Tuple[int, int, int, int, int]:
"""获取联系人统计信息 """获取联系人统计信息

View File

@@ -3,12 +3,15 @@ import xml.etree.ElementTree as ET
import logging import logging
import concurrent.futures # 添加线程池支持 import concurrent.futures # 添加线程池支持
import os import os
from wcferry import WxMsg, Wcf
from gewechat_client import GewechatClient
from db.connection import DBConnectionManager from db.connection import DBConnectionManager
from db.message_storage import MessageStorageDB from db.message_storage import MessageStorageDB
# 导入积分系统 # 导入积分系统
from db.points_db import PointsDBOperator, PointSource from db.points_db import PointsDBOperator, PointSource
from gewechat.call_back_message.message import WxMessage
# 配置日志 # 配置日志
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
@@ -19,7 +22,7 @@ logger = logging.getLogger("MessageStorage")
class MessageStorage: class MessageStorage:
def __init__(self, wcf: Wcf = None): def __init__(self, client: GewechatClient = None):
# 获取数据库连接管理器的单例 # 获取数据库连接管理器的单例
self.db_manager = DBConnectionManager.get_instance() self.db_manager = DBConnectionManager.get_instance()
self.message_db = MessageStorageDB(self.db_manager) self.message_db = MessageStorageDB(self.db_manager)
@@ -34,7 +37,7 @@ class MessageStorage:
self.pending_tasks = [] self.pending_tasks = []
# 保存WCF实例用于图片处理 # 保存WCF实例用于图片处理
self.wcf = wcf self.client = client
# 图片处理相关初始化 # 图片处理相关初始化
self.image_executor = concurrent.futures.ThreadPoolExecutor(max_workers=3) # 专用于图片处理的线程池 self.image_executor = concurrent.futures.ThreadPoolExecutor(max_workers=3) # 专用于图片处理的线程池
@@ -46,7 +49,7 @@ class MessageStorage:
os.makedirs(self.image_dir, exist_ok=True) os.makedirs(self.image_dir, exist_ok=True)
logger.info(f"图片存储目录: {self.image_dir}") logger.info(f"图片存储目录: {self.image_dir}")
def process_message(self, message: WxMsg): def process_message(self, message: WxMessage):
# 示例message字符串 # 示例message字符串
current_date = datetime.now().strftime('%Y-%m-%d') current_date = datetime.now().strftime('%Y-%m-%d')
# 生成Redis key # 生成Redis key
@@ -59,7 +62,7 @@ class MessageStorage:
redis_conn.expire(key, 86400 * 2) redis_conn.expire(key, 86400 * 2)
# 或者使用字符串r.incr(key) # 如果只存储一个整数值,字符串类型可能更简单 # 或者使用字符串r.incr(key) # 如果只存储一个整数值,字符串类型可能更简单
def archive_message(self, msg: WxMsg): def archive_message(self, msg: WxMessage):
"""异步存档消息,防止堵塞主线程""" """异步存档消息,防止堵塞主线程"""
# 提交任务到线程池 # 提交任务到线程池
future = self.executor.submit(self._archive_message_task, msg) future = self.executor.submit(self._archive_message_task, msg)
@@ -70,7 +73,7 @@ class MessageStorage:
# 清理已完成的任务 # 清理已完成的任务
self._cleanup_completed_tasks() self._cleanup_completed_tasks()
def _archive_message_task(self, msg: WxMsg): def _archive_message_task(self, msg: WxMessage):
"""实际执行消息存档的任务函数""" """实际执行消息存档的任务函数"""
try: try:
# 使用 MessageStorageDB 类存档消息 # 使用 MessageStorageDB 类存档消息
@@ -80,7 +83,7 @@ class MessageStorage:
'roomid': msg.roomid, 'roomid': msg.roomid,
'sender': msg.sender, 'sender': msg.sender,
'content': msg.content, # 添加消息内容 'content': msg.content, # 添加消息内容
'message_id': msg.id # 添加消息ID 'message_id': msg.msg_id # 添加消息ID
} }
except Exception as e: except Exception as e:
logger.error(f"存档消息出错: {e}") logger.error(f"存档消息出错: {e}")
@@ -89,13 +92,13 @@ class MessageStorage:
'roomid': msg.roomid, 'roomid': msg.roomid,
'sender': msg.sender, 'sender': msg.sender,
'content': msg.content, # 添加消息内容 'content': msg.content, # 添加消息内容
'message_id': msg.id, # 添加消息ID 'message_id': msg.msg_id, # 添加消息ID
'error': str(e) 'error': str(e)
} }
def process_image(self, msg: WxMsg): def process_image(self, msg: WxMessage):
"""异步处理图片消息,与消息存档分离""" """异步处理图片消息,与消息存档分离"""
if msg.type != 3 or not self.wcf: # 不是图片消息或没有WCF实例 if msg.msg_type != 3 or not self.client: # 不是图片消息或没有WCF实例
return False return False
# 提交任务到图片处理线程池 # 提交任务到图片处理线程池
@@ -108,11 +111,11 @@ class MessageStorage:
self._cleanup_completed_tasks() self._cleanup_completed_tasks()
return True return True
def _process_image_task(self, msg: WxMsg): def _process_image_task(self, msg: WxMessage):
"""实际执行图片处理的任务函数""" """实际执行图片处理的任务函数"""
try: try:
# 使用wcf下载图片确保图片存在 # 使用wcf下载图片确保图片存在
if self.wcf and msg.id: if self.client and msg.msg_id:
# 创建按群ID或个人wxid分割的目录 # 创建按群ID或个人wxid分割的目录
target_dir = os.path.join(self.image_dir, msg.roomid if msg.roomid else msg.sender) target_dir = os.path.join(self.image_dir, msg.roomid if msg.roomid else msg.sender)
# 确保目标目录存在 # 确保目标目录存在
@@ -120,16 +123,27 @@ class MessageStorage:
os.makedirs(target_dir, exist_ok=True) os.makedirs(target_dir, exist_ok=True)
# 尝试使用wcf下载图片到分组后的目录 # 尝试使用wcf下载图片到分组后的目录
download_path = self.wcf.download_image(msg.id, msg.extra, target_dir) json = self.client.download_image(msg.msg_id, msg.content.xml_content, 1)
# {
# "ret": 200,
# "msg": "操作成功",
# "data": {
# "fileUrl": "/download/20240720/wx_BTVoJ_o_r6DpxNCNiycFE/0ca5b675-8e2c-4dc1-b288-3c44a40086ec4"
# }
# }
# 解析JSON
if json and json.get('data') and json['data'].get('fileUrl'):
file_url = json['data']['fileUrl']
download_path = self.download_file_from_url(file_url, target_dir)
if download_path: if download_path:
logger.info(f"使用wcf下载图片成功: {msg.id} -> {download_path}") logger.info(f"使用wcf下载图片成功: {msg.msg_id} -> {download_path}")
# 直接使用下载后的路径更新数据库 # 直接使用下载后的路径更新数据库
self.message_db.update_message_image_path(msg.id, download_path) self.message_db.update_message_image_path(msg.msg_id, download_path)
return { return {
'success': True, 'success': True,
'message_id': msg.id, 'message_id': msg.msg_id,
'roomid': msg.roomid, 'roomid': msg.roomid,
'sender': msg.sender, 'sender': msg.sender,
'file_path': download_path 'file_path': download_path
@@ -137,7 +151,7 @@ class MessageStorage:
else: else:
return { return {
'success': False, 'success': False,
'message_id': msg.id, 'message_id': msg.msg_id,
'roomid': msg.roomid, 'roomid': msg.roomid,
'sender': msg.sender, 'sender': msg.sender,
'error': "图片下载失败" 'error': "图片下载失败"
@@ -145,21 +159,25 @@ class MessageStorage:
else: else:
return { return {
'success': False, 'success': False,
'message_id': msg.id, 'message_id': msg.msg_id,
'roomid': msg.roomid, 'roomid': msg.roomid,
'sender': msg.sender, 'sender': msg.sender,
'error': "WCF实例不存在或消息ID无效" 'error': "WCF实例不存在或消息ID无效"
} }
except Exception as e: except Exception as e:
logger.error(f"图片处理出错: {msg.id}, 错误: {e}") logger.error(f"图片处理出错: {msg.msg_id}, 错误: {e}")
return { return {
'success': False, 'success': False,
'message_id': msg.id, 'message_id': msg.msg_id,
'roomid': msg.roomid, 'roomid': msg.roomid,
'sender': msg.sender, 'sender': msg.sender,
'error': str(e) 'error': str(e)
} }
def download_file_from_url(self, url: str, target_dir: str) -> str:
# TODO 根据获取的文件地址从server 下载 http://{服务ip}:2532/download/{接口返回的文件路径}
return ""
def _process_image_callback(self, future): def _process_image_callback(self, future):
"""处理异步图片处理任务完成后的回调""" """处理异步图片处理任务完成后的回调"""
try: try: