diff --git a/admin/dashboard/server.py b/admin/dashboard/server.py
index 7b6e879..6eb767a 100644
--- a/admin/dashboard/server.py
+++ b/admin/dashboard/server.py
@@ -178,12 +178,12 @@ class DashboardServer:
return {"success": False, "message": "WCF实例不可用"}
# 获取当前登录的微信ID
- wx_id = self.wcf.get_self_wxid()
+ wx_id = self.self.message_util.get_self_wxid()
if not wx_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}")
return {
diff --git a/base/chatglm/README.MD b/base/chatglm/README.MD
deleted file mode 100644
index 852aa16..0000000
--- a/base/chatglm/README.MD
+++ /dev/null
@@ -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. 启动后,发送 #帮助 可以查看 模式和常用指令
diff --git a/base/chatglm/__init__.py b/base/chatglm/__init__.py
deleted file mode 100644
index fede0f3..0000000
--- a/base/chatglm/__init__.py
+++ /dev/null
@@ -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)
\ No newline at end of file
diff --git a/base/chatglm/base.json b/base/chatglm/base.json
deleted file mode 100644
index 6bfcd3f..0000000
--- a/base/chatglm/base.json
+++ /dev/null
@@ -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"
- }
- }
-}
\ No newline at end of file
diff --git a/base/chatglm/code_kernel.py b/base/chatglm/code_kernel.py
deleted file mode 100644
index 3cbd273..0000000
--- a/base/chatglm/code_kernel.py
+++ /dev/null
@@ -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]
diff --git a/base/chatglm/comfyUI_api.py b/base/chatglm/comfyUI_api.py
deleted file mode 100644
index aefe6cc..0000000
--- a/base/chatglm/comfyUI_api.py
+++ /dev/null
@@ -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()
diff --git a/base/chatglm/tool_registry.py b/base/chatglm/tool_registry.py
deleted file mode 100644
index 27279df..0000000
--- a/base/chatglm/tool_registry.py
+++ /dev/null
@@ -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())
diff --git a/base/func_bard.py b/base/func_bard.py
deleted file mode 100644
index da3bee8..0000000
--- a/base/func_bard.py
+++ /dev/null
@@ -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)
diff --git a/base/func_chatglm.py b/base/func_chatglm.py
deleted file mode 100644
index 7db2bdf..0000000
--- a/base/func_chatglm.py
+++ /dev/null
@@ -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)
diff --git a/config.yaml b/config.yaml
index 382520c..4f6efc6 100644
--- a/config.yaml
+++ b/config.yaml
@@ -139,4 +139,13 @@ redis_config:
host: "192.168.2.40"
port: 6379
db: 0
- decode_responses: true
\ No newline at end of file
+ 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"
diff --git a/configuration.py b/configuration.py
index 959b193..df07977 100644
--- a/configuration.py
+++ b/configuration.py
@@ -44,3 +44,42 @@ class Config(object):
# DB config
self.mariadb = yconfig.get("db_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
\ No newline at end of file
diff --git a/db/contacts_db.py b/db/contacts_db.py
new file mode 100644
index 0000000..6b2c798
--- /dev/null
+++ b/db/contacts_db.py
@@ -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
\ No newline at end of file
diff --git a/db/message_storage.py b/db/message_storage.py
index 1b4825b..3c4143a 100644
--- a/db/message_storage.py
+++ b/db/message_storage.py
@@ -3,10 +3,9 @@
from datetime import datetime
from typing import Dict, List, Optional
-from wcferry import WxMsg
-
from db.base import BaseDBOperator
from db.connection import DBConnectionManager
+from gewechat.call_back_message.message import WxMessage
class MessageStorageDB(BaseDBOperator):
@@ -15,14 +14,15 @@ class MessageStorageDB(BaseDBOperator):
def __init__(self, db_manager: DBConnectionManager):
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")
sql = """
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)
"""
- 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)
return result
@@ -51,7 +51,6 @@ class MessageStorageDB(BaseDBOperator):
"""
return self.execute_query(sql, (date,)) or []
-
def get_speech_ranking(self, date: str, group_id: str, limit: int = 20) -> List[Dict]:
"""获取指定日期和群组的发言排名"""
sql = """
@@ -87,7 +86,6 @@ class MessageStorageDB(BaseDBOperator):
params = (group_id, wx_id, date, count)
return self.execute_update(sql, params)
-
def get_message_trend(self, group_id: str, days: int = 7) -> List[Dict]:
"""获取指定群组的消息趋势数据
@@ -111,7 +109,7 @@ class MessageStorageDB(BaseDBOperator):
return self.execute_query(sql, (group_id, days)) or []
def get_messages_by_filter(self, group_id=None, start_date=None, end_date=None,
- search_text=None, page=1, page_size=20) -> Dict:
+ search_text=None, page=1, page_size=20) -> Dict:
"""按条件筛选消息并支持分页和模糊搜索
Args:
@@ -200,11 +198,11 @@ class MessageStorageDB(BaseDBOperator):
WHERE message_id = %s
"""
params = (image_path, message_id)
-
+
# 执行更新操作
result = self.execute_update(sql, params)
return result
except Exception as e:
# 使用已有的日志记录方式
print(f"更新消息图片路径出错: {e}")
- return False
\ No newline at end of file
+ return False
diff --git a/gewechat/__init__.py b/gewechat/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/gewechat/api/callback.py b/gewechat/api/callback.py
new file mode 100644
index 0000000..8da4a00
--- /dev/null
+++ b/gewechat/api/callback.py
@@ -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: 实现系统通知处理逻辑
diff --git a/gewechat/api/start_server.py b/gewechat/api/start_server.py
new file mode 100644
index 0000000..8f0daaa
--- /dev/null
+++ b/gewechat/api/start_server.py
@@ -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("服务器启动失败")
\ No newline at end of file
diff --git a/gewechat/call_back_message/message.py b/gewechat/call_back_message/message.py
new file mode 100644
index 0000000..08c4cba
--- /dev/null
+++ b/gewechat/call_back_message/message.py
@@ -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(' '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
diff --git a/gewechat/call_back_message/model.md b/gewechat/call_back_message/model.md
new file mode 100644
index 0000000..2bf724a
--- /dev/null
+++ b/gewechat/call_back_message/model.md
@@ -0,0 +1,1200 @@
+# 回调消息详解
+
+### 回调消息常见问题
+
+Q. **微信在线为什么没有消息推送?**
+```
+当回调消息未能通过 HTTP POST/JSON 方式成功推送至接收方时,请考虑使用 Apifox 向接收地址发送一条测试消息。如果仍然未能接收到消息,请检查接收地址的可用性。反之,若能成功接收测试消息,请联系客服,我们将协助您进行进一步的问题排查。
+```
+
+Q. **如何判断是否是自己发送的消息?**
+```
+可通过消息发送人($.Data.FromUserName.string)与所属微信($.Wxid)是否一致进行判断。
+```
+
+Q. **为什么同一条消息会重复回调?**
+```
+因服务重启、同步历史消息、失败重试等原因,同一条消息可能会重复推送,接收方需根据$.Appid+$.Data.NewMsgId字段做消息排重,以防消息重复处理。
+```
+
+---
+
+#### 文本消息
+```json
+ {
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356095, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_phyyedw9xap22" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 1, 消息类型 1是文本消息
+ "Content":
+ {
+ "string": "123" # 消息内容
+ },
+ "Status": 3,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705043418, 消息发送时间
+ "MsgSource": "\n\t\n\t\t1\n\t\n\tv1_volHXhv4\n\t\n\t\t\n\t\n\n",
+ "PushContent": "朝夕。 : 123", 消息通知内容
+ "NewMsgId": 7773749793478223190, 消息ID
+ "MsgSeq": 640356095
+ }
+ }
+```
+
+
+#### 图片消息
+```json
+{
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356099, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_phyyedw9xap22" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 3, 消息类型 3是图片消息
+ "Content":
+ {
+ "string": "\n\n\t
\n\t\n\t\n\n" 图片的cdn信息,可用此字段做转发图片
+ },
+ "Status": 3,
+ "ImgStatus": 2,
+ "ImgBuf":
+ {
+ "iLen": 2146,
+ "buffer": "/9j/4AAQSkZJRgABAQAASABIAAD/4QBM..." # 缩略图的base64
+ },
+ "CreateTime": 1705043678, 消息发送时间
+ "MsgSource": "\n\t\n\t\t2\n\t\n\t\n\t\t5b04ea0181f86c7f3d126e9a7fe5038b_\n\t\n\tv1_5WGxwSEj\n\t\n\t\t\n\t\n\n",
+ "PushContent": "朝夕。 : [图片]", 消息通知内容
+ "NewMsgId": 6906713067183447582, 消息ID
+ "MsgSeq": 640356099
+ }
+}
+```
+
+#### 语音消息
+```json
+{
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356100, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_phyyedw9xap22" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 34, 消息类型,34是语音消息
+ "Content":
+ {
+ "string": "" 语音消息的下载信息,可用于下载语音文件
+ },
+ "Status": 3,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 3600,
+ "buffer": "AiMhU0lMS19WMxMApzi9JA+qToPB..." 语音文件的base64,并非所有语音消息都有本字段
+ },
+ "CreateTime": 1705043782, 消息发送时间
+ "MsgSource": "\n\tv1_j+rf/Jnp\n\t\n\t\t\n\t\n\n",
+ "PushContent": "朝夕。 : [语音]", 消息通知内容
+ "NewMsgId": 1428830975092239121, 消息ID
+ "MsgSeq": 640356100
+ }
+}
+```
+
+#### 视频消息
+```json
+{
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356101, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_phyyedw9xap22" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 43, 消息类型,43是视频消息
+ "Content":
+ {
+ "string": "\n\n\t\n\n" 视频消息的cdn信息,可用此字段做转发视频
+ },
+ "Status": 3,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705043879, 消息发送时间
+ "MsgSource": "\n\t0\n\t\n\t\tce3ebc6d2893c7a2669ac5d2eaa4aadf_\n\t\n\tv1_kk/psF9W\n\t\n\t\t\n\t\n\n",
+ "PushContent": "朝夕。 : [视频]", 消息通知内容
+ "NewMsgId": 6628526085342711793, 消息ID
+ "MsgSeq": 640356101
+ }
+}
+```
+
+#### emoji表情
+```json
+{
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356102, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_phyyedw9xap22" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 47, 消息类型,47是emoji消息
+ "Content":
+ {
+ "string": " " 可解析xml中的md5用与发送emoji消息
+ },
+ "Status": 3,
+ "ImgStatus": 2,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705043947, 消息发送时间
+ "MsgSource": "\n\tv1_vy/xC7WS\n\t\n\t\t\n\t\n\n",
+ "PushContent": "朝夕。 : [动画表情]", 消息通知内容
+ "NewMsgId": 6674256223577965652, 消息ID
+ "MsgSeq": 640356102
+ }
+}
+```
+
+#### 公众号链接
+- 判断链接消息的逻辑:\$.Data.MsgType=49 并且 解析\$.Data.Content.string中的xml msg.appmsg.type=5,按此逻辑会匹配到两种消息,链接消息及邀请进群的通知,可依据xml msg.appmsg.title做区分
+```json
+{
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356105, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_phyyedw9xap22" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 49,
+ "Content":
+ {
+ "string": "\n\n\t\n\t\t尔滨,又有好消息!\n\t\t\n\t\t\n\t\t5\n\t\t0\n\t\t0\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t0\n\t\thttp://mp.weixin.qq.com/s?__biz=MzA4NDI3NjcyNA==&mid=2650011300&idx=1&sn=52739c3d39c030394da972e3d83efc98&chksm=86ed931f730a3e19a5edc840896d9bf1ad1f8b60cdccafea6a9e7a38a0a33f261877d334622b&scene=0&xtrack=1#rd\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t3057020100044b304902010002048399cc8402032f7e350204a810d83a020465a0e829042462343663343435612d333737392d346230612d616434622d6263383038633562643562340204051408030201000405004c53d900\n\t\t\tadd1b4bcf9cc50c6a8f14ff334bc3d5c\n\t\t\t83741\n\t\t\t1000\n\t\t\t426\n\t\t\t37889a1e22c1e58ebd4e6589b999f63e\n\t\t\t\n\t\t\n\t\t\n\t\tgh_6651e07e4b2d\n\t\t新华社\n\t\thttps://mmbiz.qpic.cn/mmbiz_jpg/azXQmS1HA7mOP6LHArYqZ5ypK4iajvBdfhNxzyANcQ1eW7ec6yZVj7tv8Lt6tWftSNckDz3j4FqkP04TxARG8dQ/640?wxtype=jpeg&wxfrom=0\n\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\n\t\n\twxid_phyyedw9xap22\n\t0\n\t\n\t\t1\n\t\t\n\t\n\t\n\n" 可用此字段做转发链接
+ },
+ "Status": 3,
+ "ImgStatus": 2,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705044033, 消息发送时间
+ "MsgSource": "\n\t\n\t\t\n\t\n\t\n\t\t4\n\t\n\t\n\t\tba15c632e8fa89ed84bd027f09495591_\n\t\n\tv1_ptaEL1bv\n\n",
+ "PushContent": "朝夕。 : [链接]尔滨,又有好消息!", 消息通知内容
+ "NewMsgId": 1623411326098221490, 消息ID
+ "MsgSeq": 640356105
+ }
+}
+```
+
+#### 文件消息(发送文件的通知)
+- **注意**:收到本条消息仅代表对方在向你发送文件,并不可以用本条做转发及下载
+- 判断此类消息的逻辑:\$.Data.MsgType=49 并且 解析\$.Data.Content.string中的xml msg.appmsg.type=74
+```json
+{
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356106, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_phyyedw9xap22" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 49,
+ "Content":
+ {
+ "string": "\n \n \n 74\n 0\n \n 8939\n \n v1_paVQtd+CWGr2I3eOg71E6KBpQf0yY9RFQkqDPwT4yMnnbawqveao1vAE0qCOhWcIPkMGZavimUTDFcImr+SaManD8pKVQbBPTUvSmA6UsXgZWqQDOT00VLx7U/hoP3/CwveN2Lk56nxcef/XJiGKrOpAHKHcZvccaGk9/68wsBCOyanya/9xgdHTYxyQp4IadiSe\n 0\n \n \n \n \n wxid_phyyedw9xap22\n"
+ },
+ "Status": 3,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705044119, 消息发送时间
+ "MsgSource": "\n\tv1_WyLyIcy+\n\t\n\t\t\n\t\n\n",
+ "PushContent": "朝夕。 : [文件]hhh.xlsx", 消息通知内容
+ "NewMsgId": 1789783684714859663, 消息ID
+ "MsgSeq": 640356106
+ }
+}
+```
+
+#### 文件消息(文件发送完成)
+- **注意**:收到本条消息表示对方给你的文件发送完成,可用本条消息做转发及下载
+- 判断此类消息的逻辑:\$.Data.MsgType=49 并且 解析\$.Data.Content.string中的xml msg.appmsg.type=6
+```json
+{
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356107, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_phyyedw9xap22" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 49,
+ "Content":
+ {
+ "string": "\n\n\t\n\t\thhh.xlsx\n\t\t\n\t\t\n\t\t6\n\t\t0\n\t\t0\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t0\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\t8939\n\t\t\t@cdn_3057020100044b304902010002043904752002032f7e350204aa0dd83a020465a0e897042430373538386564322d353866642d343234342d386563652d6236353536306438623936610204011800050201000405004c56f900_3f28b0cbd65a86c3a980f3e22808c0fe_1\n\t\t\t\n\t\t\txlsx\n\t\t\t3057020100044b304902010002043904752002032f7e350204aa0dd83a020465a0e897042430373538386564322d353866642d343234342d386563652d6236353536306438623936610204011800050201000405004c56f900\n\t\t\t3f28b0cbd65a86c3a980f3e22808c0fe\n\t\t\t0\n\t\t\t1789783684714859663\n\t\t\tv1_paVQtd+CWGr2I3eOg71E6KBpQf0yY9RFQkqDPwT4yMnnbawqveao1vAE0qCOhWcIPkMGZavimUTDFcImr+SaManD8pKVQbBPTUvSmA6UsXgZWqQDOT00VLx7U/hoP3/CwveN2Lk56nxcef/XJiGKrOpAHKHcZvccaGk9/68wsBCOyanya/9xgdHTYxyQp4IadiSe\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t84c6737fe9549270c9b3ca4f6fc88f6f\n\t\t\n\t\n\twxid_phyyedw9xap22\n\t0\n\t\n\t\t1\n\t\t\n\t\n\t\n\n"
+ },
+ "Status": 3,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705044119, 消息发送时间
+ "MsgSource": "\n\t\n\t\t3\n\t\n\t\n\t\t896374a2b5979141804d509256c22f0b_\n\t\n\tv1_n7kZ01bp\n\t\n\t\t\n\t\n\n",
+ "PushContent": "朝夕。 : [文件]hhh.xlsx", 消息通知内容
+ "NewMsgId": 3617029648443513152, 消息ID
+ "MsgSeq": 640356107
+ }
+}
+```
+
+#### 名片消息
+```json
+ {
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356108, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_phyyedw9xap22" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 42, 消息类型,42是名片消息
+ "Content":
+ {
+ "string": "\n\n" 名片中微信号的基本信息,可用于添加好友
+ },
+ "Status": 3,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705044829, 消息发送时间
+ "MsgSource": "\n\t0\n\t\n\t\t2\n\t\n\tv1_bawbB33Z\n\t\n\t\t\n\t\n\n",
+ "PushContent": "朝夕。 : [名片]Ashley", 消息通知内容
+ "NewMsgId": 766322251431765776, 消息ID
+ "MsgSeq": 640356108
+ }
+ }
+```
+
+#### 好友添加请求通知
+```json
+{
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356166, 消息ID
+ "FromUserName":
+ {
+ "string": "fmessage" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 37, 消息类型,37是好友添加请求通知
+ "Content":
+ {
+ "string": "" 请求添加好友微信号的基本信息,可用于添加好友
+ },
+ "Status": 3,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705045979, 消息发送时间
+ "MsgSource": "\n\tv1_GOrHWRNL\n\t\n\t\t\n\t\n\n",
+ "NewMsgId": 1109510141823131559, 消息ID
+ "MsgSeq": 640356166
+ }
+}
+```
+
+#### 好友通过验证及好友资料变更的通知消息
+```json
+{
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4",
+ "TypeName": "ModContacts",
+ "Data":
+ {
+ "UserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22"
+ },
+ "NickName":
+ {
+ "string": "chaoxi。"
+ },
+ "PyInitial":
+ {
+ "string": "CX"
+ },
+ "QuanPin":
+ {
+ "string": "chaoxi"
+ },
+ "Sex": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "BitMask": 4294967295,
+ "BitVal": 3,
+ "ImgFlag": 1,
+ "Remark":
+ {},
+ "RemarkPyinitial":
+ {},
+ "RemarkQuanPin":
+ {},
+ "ContactType": 0,
+ "RoomInfoCount": 0,
+ "DomainList": [
+ {}],
+ "ChatRoomNotify": 0,
+ "AddContactScene": 0,
+ "Province": "Jiangsu",
+ "City": "Nanjing",
+ "Signature": "......",
+ "PersonalCard": 0,
+ "HasWeiXinHdHeadImg": 1,
+ "VerifyFlag": 0,
+ "Level": 6,
+ "Source": 14,
+ "WeiboFlag": 0,
+ "AlbumStyle": 0,
+ "AlbumFlag": 3,
+ "SnsUserInfo":
+ {
+ "SnsFlag": 1,
+ "SnsBgimgId": "http://shmmsns.qpic.cn/mmsns/FzeKA69P5uIdqPfQxp59LvOohoE2iaiaj86IBH1jl0F76aGvg8AlU7giaMtBhQ3bPibunbhVLb3aEq4/0",
+ "SnsBgobjectId": 14216284872728580667,
+ "SnsFlagEx": 7297
+ },
+ "Country": "CN",
+ "BigHeadImgUrl": "https://wx.qlogo.cn/mmhead/ver_1/qqncCu2avRYruPcQbav3PrwaGSS31QgN6dqW8q1XuDKjgiaAuwoFPw3kN8Cj3zIBL36M93R2Xwib0IddUK3gqbFeezEiaA8K2mMdibT5VUDDrbn7F7M1Mxicmows9cdYNOicjI/0",
+ "SmallHeadImgUrl": "https://wx.qlogo.cn/mmhead/ver_1/qqncCu2avRYruPcQbav3PrwaGSS31QgN6dqW8q1XuDKjgiaAuwoFPw3kN8Cj3zIBL36M93R2Xwib0IddUK3gqbFeezEiaA8K2mMdibT5VUDDrbn7F7M1Mxicmows9cdYNOicjI/132",
+ "CustomizedInfo":
+ {
+ "BrandFlag": 0
+ },
+ "EncryptUserName": "v3_020b3826fd03010000000000feba078fc1e760000000501ea9a3dba12f95f6b60a0536a1adb6f6352c38d0916c9c74045d85aa602efa2d81b84adde05d285124e8a54b9fcd039f725d6ac0d3bd651c7c74503a@stranger",
+ "AdditionalContactList":
+ {
+ "LinkedinContactItem":
+ {}
+ },
+ "ChatroomMaxCount": 0,
+ "DeleteFlag": 0,
+ "Description": "\b\u0000\u0018\u0000\"\u0000(\u00008\u0000",
+ "ChatroomStatus": 0,
+ "Extflag": 0,
+ "ChatRoomBusinessType": 0
+ }
+}
+```
+
+
+#### 小程序消息
+- 判断此类消息的逻辑:\$.Data.MsgType=49 并且 解析\$.Data.Content.string中的xml msg.appmsg.type=33/36
+```json
+{
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356109, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_phyyedw9xap22" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 49,
+ "Content":
+ {
+ "string": "\n\n\t\n\t\t腾讯云助手\n\t\t腾讯云助手\n\t\t33\n\t\thttps://mp.weixin.qq.com/mp/waerrpage?appid=wxe2039b83454e49ed&type=upgrade&upgradetype=3#wechat_redirect\n\t\t\n\t\t\t3057020100044b304902010002048399cc8402032df731020414e461b4020465a0eb8f042463626430353633382d376263632d346161642d396234372d3435613131336339326231640204051808030201000405004c550500\n\t\t\te1284d4ae13ebd9bb2cde5251cdd05e4\n\t\t\t52357\n\t\t\t720\n\t\t\t576\n\t\t\td4142726bc730088f0fa44c9161a0992\n\t\t\td4142726bc730088f0fa44c9161a0992\n\t\t\t0\n\t\t\twxid_0xsqb3o0tsvz22_38_1705044879\n\t\t\n\t\tgh_44fc2ced7f87@app\n\t\t腾讯云助手\n\t\te1284d4ae13ebd9bb2cde5251cdd05e4\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t2\n\t\t\t594\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t0\n\t\t\t0\n\t\t\t538\n\t\t\t0\n\t\t\t0\n\t\t\t0\n\t\t\t0\n\t\t\n\t\n\twxid_phyyedw9xap22\n\t0\n\t\n\t\t1\n\t\t\n\t\n\t\n\n"
+ },
+ "Status": 3,
+ "ImgStatus": 2,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705044879, 消息发送时间
+ "MsgSource": "\n\t0\n\t\n\t\t2\n\t\n\t\n\t\tdb46d46fe0a926c4b571dfe9d8096bfa_\n\t\n\tv1_DkelOoZN\n\t\n\t\t\n\t\n\n",
+ "PushContent": "朝夕。 : [小程序]腾讯云助手", 消息通知内容
+ "NewMsgId": 572974861799389774, 消息ID
+ "MsgSeq": 640356109
+ }
+}
+```
+
+#### 引用消息
+- 判断此类消息的逻辑:\$.Data.MsgType=49 并且 解析\$.Data.Content.string中的xml msg.appmsg.type=57
+```json
+{
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356110, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_phyyedw9xap22" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 49,
+ "Content":
+ {
+ "string": "\n\n\t\n\t\t看看这个\n\t\t\n\t\t\n\t\t57\n\t\t0\n\t\t0\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t0\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\t49\n\t\t\t3617029648443513152\n\t\t\twxid_phyyedw9xap22\n\t\t\twxid_phyyedw9xap22\n\t\t\t朝夕。\n\t\t\t<msg><appmsg appid=\"\" sdkver=\"0\"><title>hhh.xlsx</title><des></des><action></action><type>6</type><showtype>0</showtype><soundtype>0</soundtype><mediatagname></mediatagname><messageext></messageext><messageaction></messageaction><content></content><contentattr>0</contentattr><url></url><lowurl></lowurl><dataurl></dataurl><lowdataurl></lowdataurl><appattach><totallen>8939</totallen><attachid>@cdn_3057020100044b304902010002043904752002032f7e350204aa0dd83a020465a0e897042430373538386564322d353866642d343234342d386563652d6236353536306438623936610204011800050201000405004c56f900_3f28b0cbd65a86c3a980f3e22808c0fe_1</attachid><emoticonmd5></emoticonmd5><fileext>xlsx</fileext><cdnattachurl>3057020100044b304902010002043904752002032f7e350204aa0dd83a020465a0e897042430373538386564322d353866642d343234342d386563652d6236353536306438623936610204011800050201000405004c56f900</cdnattachurl><aeskey>3f28b0cbd65a86c3a980f3e22808c0fe</aeskey><encryver>0</encryver><overwrite_newmsgid>1789783684714859663</overwrite_newmsgid><fileuploadtoken>v1_paVQtd+CWGr2I3eOg71E6KBpQf0yY9RFQkqDPwT4yMnnbawqveao1vAE0qCOhWcIPkMGZavimUTDFcImr+SaManD8pKVQbBPTUvSmA6UsXgZWqQDOT00VLx7U/hoP3/CwveN2Lk56nxcef/XJiGKrOpAHKHcZvccaGk9/68wsBCOyanya/9xgdHTYxyQp4IadiSe</fileuploadtoken></appattach><extinfo></extinfo><sourceusername></sourceusername><sourcedisplayname></sourcedisplayname><thumburl></thumburl><md5>84c6737fe9549270c9b3ca4f6fc88f6f</md5><statextstr></statextstr></appmsg><fromusername></fromusername><appinfo><version>0</version><appname></appname><isforceupdate>1</isforceupdate></appinfo></msg>\n\t\t\t<msgsource>\n\t<alnode>\n\t\t<cf>3</cf>\n\t</alnode>\n\t<sec_msg_node>\n\t\t<uuid>896374a2b5979141804d509256c22f0b_</uuid>\n\t</sec_msg_node>\n</msgsource>\n\n\t\t\n\t\n\twxid_phyyedw9xap22\n\t0\n\t\n\t\t1\n\t\t\n\t\n\t\n\n"
+ },
+ "Status": 3,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705044946, 消息发送时间
+ "MsgSource": "\n\t\n\t\tea25ade83dc4b9ec91060ca3e1a0f5a2_\n\t\n\tv1_oTWRYdd1\n\t\n\t\t\n\t\n\n",
+ "PushContent": "看看这个", 消息通知内容
+ "NewMsgId": 4334300109515885085, 消息ID
+ "MsgSeq": 640356110
+ }
+}
+```
+
+#### 转账消息
+- 判断此类消息的逻辑:\$.Data.MsgType=49 并且 解析\$.Data.Content.string中的xml msg.appmsg.type=2000
+```json
+ {
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356112, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_phyyedw9xap22" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 49,
+ "Content":
+ {
+ "string": "\n\n\n\n\n2000\n\n\n\n\n\n\n\n1\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
+ },
+ "Status": 3,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705044984, 消息发送时间
+ "MsgSource": "\n\tv1_eDcIna+F\n\t\n\t\t\n\t\n\n",
+ "PushContent": "朝夕。 : [转账]", 消息通知内容
+ "NewMsgId": 7290406378327063279, 消息ID
+ "MsgSeq": 640356112
+ }
+ }
+```
+
+#### 红包消息
+- 判断此类消息的逻辑:\$.Data.MsgType=49 并且 解析\$.Data.Content.string中的xml msg.appmsg.type=2001
+```json
+ {
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356113, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_phyyedw9xap22" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 49,
+ "Content":
+ {
+ "string": "\n\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t微信红包\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\n"
+ },
+ "Status": 3,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705045011, 消息发送时间
+ "MsgSource": "\n\t\n\t\n\tv1_Js6wJde/\n\t\n\t\t\n\t\n\n",
+ "PushContent": "朝夕。 : [红包]恭喜发财,大吉大利", 消息通知内容
+ "NewMsgId": 5517720959405775296, 消息ID
+ "MsgSeq": 640356113
+ }
+ }
+```
+
+#### 视频号消息
+- 判断此类消息的逻辑:\$.Data.MsgType=49 并且 解析\$.Data.Content.string中的xml msg.appmsg.type=51
+```json
+ {
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356115, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_phyyedw9xap22" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 49,
+ "Content":
+ {
+ "string": "\n\n\t\n\t\t当前微信版本不支持展示该内容,请升级至最新版本。\n\t\t\n\t\t\n\t\t51\n\t\t0\n\t\t0\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t0\n\t\thttps://support.weixin.qq.com/update/\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\t0\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\t\t14264358459626428566\n\t\t\t4\n\t\t\t国风锦鲤\n\t\t\thttps://wx.qlogo.cn/finderhead/ver_1/x2LxetmLmgoo9jp69R3wcrtZ0LBLdjVv9vrK9HmPNGEdD1iawdrPffPvMmFUez8pWqRIfs7DtgPiaV5C7DZpibH8b3y0jG178aIict6uPf0Vht4/0\n\t\t\t还招人么?我不要工资#逆水寒cos\n\t\t\t1\n\t\t\t8046877030770906689_0_0_0_0_0\n\t\t\t0\n\t\t\tv2_060000231003b20faec8cae08b19c7d2c702e834b077fb74f482543ff67f0cc66363057a5443@finder\n\t\t\t\n\t\t\t0\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t4\n\t\t\t\t\thttp://wxapp.tc.qq.com/251/20302/stodownload?encfilekey=Cvvj5Ix3eez3Y79SxtvVL0L7CkPM6dFibFeI6caGYwFFDAZJzcvicKz3jic4UfNeiaWTwH9gTlYiafAxVkMZRXicBUBk2Ms7lauAj6SArUu0P9ddKiaa8IWZzYaaKLf1WddH4G8T0KicxQV3hQPH3pQgEMTscw&a=1&bizid=1023&dotrans=0&hy=SH&idx=1&m=4c4c7f3ed03a14a6b99d0d19176c12ac&upid=290110\n\t\t\t\t\thttp://wxapp.tc.qq.com/251/20304/stodownload?encfilekey=oibeqyX228riaCwo9STVsGLPj9UYCicgttvO59vjtcQ7Jviaia0q4bnpVP2ia7ibqzacPo0z4nIRtWom80ZXwL64icZO2q6ibVBQLZQftMwU3SHj5uplsIFroHeF0QNcCkXX3RtibaWCHJQjfqZUk&bizid=1023&dotrans=0&hy=SH&idx=1&m=7522250b4d15e5df866bf23da9f117d6&token=oA9SZ4icv8IssuhLtacX13nAzXiaf8y52juKW4ibUDN7a2vn71bbrCR0LZiabddvTsLLMvnELnuAwNxViclRT7wT9IyibzFw1pq9wdichRYaEmb6Js&ctsc=2-20\n\t\t\t\t\t1080\n\t\t\t\t\t1920\n\t\t\t\t\thttp://wxapp.tc.qq.com/251/20304/stodownload?encfilekey=oibeqyX228riaCwo9STVsGLPj9UYCicgttvO59vjtcQ7Jviaia0q4bnpVP2ia7ibqzacPo0z4nIRtWom80ZXwL64icZO2q6ibVBQLZQftMwU3SHj5uplsIFroHeF0QNcCkXX3RtibaWCHJQjfqZUk&bizid=1023&dotrans=0&hy=SH&idx=1&m=7522250b4d15e5df866bf23da9f117d6&token=oA9SZ4icv8IssuhLtacX13nAzXiaf8y52juKW4ibUDN7a2vn71bbrCR0LZiabddvTsLLMvnELnuAwNxViclRT7wT9IyibzFw1pq9wdichRYaEmb6Js&ctsc=2-20\n\t\t\t\t\thttp://wxapp.tc.qq.com/251/20350/stodownload?encfilekey=oibeqyX228riaCwo9STVsGLPj9UYCicgttv1FCQXwResqN75zI4n65zY5tkAficEPWbbClq2VcicqMYaSLK7nrAVMasrIhvsCXJib5cOLib98JgWPr4SP92W6YEkVN5Uv0TKAdyRryQ3Qxk7jU&bizid=1023&dotrans=0&hy=SH&idx=1&m=731b89683dd3cb866cdf96dab70ac183&token=KkOFht0mCXlnmicFbJnvymIJOEfZgzia8PY0ZzOdaIYTJXwfblvK4U1ibntribm1beupHwictGWs9hpMiclyhfSb6766Lnb3ib0j14bENm6u1tHpeo&ctsc=3-20\n\t\t\t\t\t10>>\n\t\t\t\t\n\t\t\t\n\t\t\n\t\n\twxid_phyyedw9xap22\n\t0\n\t\n\t\t1\n\t\t\n\t\n\t\n\n"
+ },
+ "Status": 3,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705045057, 消息发送时间
+ "MsgSource": "\n\t\n\t\t\n\t\n\t\n\t\t4\n\t\n\t\n\t\tbb2cbd9d3290e7a3d35f183eaade2213_\n\t\n\tv1_+Tfo41HS\n\n",
+ "PushContent": "你收到了一条消息", 消息通知内容
+ "NewMsgId": 5576224237104747184, 消息ID
+ "MsgSeq": 640356115
+ }
+ }
+```
+
+#### 撤回消息
+- 判断此类消息的逻辑:\$.Data.MsgType=10002 并且 解析\$.Data.Content.string中的xml sysmsg.type=revokemsg
+```json
+ {
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356116, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_phyyedw9xap22" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 10002,
+ "Content":
+ {
+ "string": "wxid_phyyedw9xap2210403561155576224237104747184"
+ },
+ "Status": 3,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705045083, 消息发送时间
+ "MsgSource": "\n\t\n\t\t\n\t\n\n",
+ "NewMsgId": 1968256046, 消息ID
+ "MsgSeq": 640356116
+ }
+ }
+```
+
+#### 拍一拍消息
+- 判断此类消息的逻辑:\$.Data.MsgType=10002 并且 解析\$.Data.Content.string中的xml sysmsg.type=pat
+```json
+{
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356117, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_phyyedw9xap22" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 10002,
+ "Content":
+ {
+ "string": "\n\n wxid_phyyedw9xap22\n wxid_0xsqb3o0tsvz22\n wxid_0xsqb3o0tsvz22\n \n 0\n\n\n\n\n \n\n\n\n\n\n"
+ },
+ "Status": 3,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705045115, 消息发送时间
+ "MsgSource": "\n\t\n\t\t\n\t\n\n",
+ "NewMsgId": 5709690173850254331, 消息ID
+ "MsgSeq": 640356117
+ }
+}
+```
+
+#### 地理位置
+```json
+{
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356118, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_phyyedw9xap22" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 48, 消息类型,48是地理位置消息
+ "Content":
+ {
+ "string": "\n\n\t\n\n"
+ },
+ "Status": 3,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705045153, 消息发送时间
+ "MsgSource": "\n\t0\n\tv1_KgQA8C+H\n\t\n\t\t\n\t\n\n",
+ "PushContent": "朝夕。分享了一个地理位置", 消息通知内容
+ "NewMsgId": 2112726776406556053, 消息ID
+ "MsgSeq": 640356118
+ }
+}
+```
+
+#### 群聊邀请
+- 判断此类消息的逻辑:\$.Data.MsgType=49 并且 解析\$.Data.Content.string中的xml msg.appmsg.title=邀请你加入群聊(根据手机设置的系统语言title会有调整,不同语言关键字不同)
+```json
+{
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356119, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_phyyedw9xap22" 消息发送人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 49,
+ "Content":
+ {
+ "string": "view500"
+ },
+ "Status": 3,
+ "ImgStatus": 0,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705045206, 消息发送时间
+ "MsgSource": "\n\tv1_uHiWbihr\n\t\n\t\t\n\t\n\n",
+ "NewMsgId": 2331390497668538400, 消息ID
+ "MsgSeq": 640356119
+ }
+}
+```
+
+#### 被移除群聊通知
+- 判断此类消息的逻辑:\$.Data.MsgType=10000 并且 \$.Data.Content.string内容为移除群聊的通知内容
+```json
+{
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356153, 消息ID
+ "FromUserName":
+ {
+ "string": "39238473509@chatroom" 所在群聊的ID
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 10000,
+ "Content":
+ {
+ "string": "你被\"朝夕。\"移出群聊"
+ },
+ "Status": 4,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705045790, 消息发送时间
+ "MsgSource": "\n\tv1_f7Xny9H/\n\t\n\t\t\n\t\n\n",
+ "NewMsgId": 5759605552965664254, 消息ID
+ "MsgSeq": 640356153
+ }
+}
+```
+
+#### 踢出群聊通知
+- 判断此类消息的逻辑:\$.Data.MsgType=10002 并且 解析\$.Data.Content.string中的xml sysmsg.type=sysmsgtemplate 并且 template中的内容为“你将xxx移出了群聊”(根据手机设置的系统语言template会有调整,不同语言关键字不同)
+```json
+{
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356143, 消息ID
+ "FromUserName":
+ {
+ "string": "34757816141@chatroom" 所在群聊的ID
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 10002,
+ "Content":
+ {
+ "string": "34757816141@chatroom:\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\n\t\n\n"
+ },
+ "Status": 4,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705045666, 消息发送时间
+ "MsgSource": "\n\t\n\t\t\n\t\n\n",
+ "NewMsgId": 7100572668516374210, 消息ID
+ "MsgSeq": 640356143
+ }
+}
+```
+
+#### 解散群聊通知
+- 判断此类消息的逻辑:\$.Data.MsgType=10002 并且 解析\$.Data.Content.string中的xml sysmsg.type=sysmsgtemplate 并且 template中的内容为“群主xxx已解散该群聊”(根据手机设置的系统语言template会有调整,不同语言关键字不同)
+```json
+{
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356158, 消息ID
+ "FromUserName":
+ {
+ "string": "39238473509@chatroom" 所在群聊的ID
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 10002,
+ "Content":
+ {
+ "string": "39238473509@chatroom:\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n"
+ },
+ "Status": 4,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705045834, 消息发送时间
+ "MsgSource": "\n\t\n\t\t\n\t\n\n",
+ "NewMsgId": 6869316888754169027, 消息ID
+ "MsgSeq": 640356158
+ }
+}
+```
+
+#### 修改群名称
+- 判断此类消息的逻辑:\$.Data.MsgType=10000 并且 \$.Data.Content.string为修改群名的通知内容
+```json
+{
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356129, 消息ID
+ "FromUserName":
+ {
+ "string": "34757816141@chatroom" 所在群聊的ID
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 10000,
+ "Content":
+ {
+ "string": "你修改群名为“GeWe test1”"
+ },
+ "Status": 4,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705045517, 消息发送时间
+ "MsgSource": "\n\tv1_3uPmlxJG\n\t\n\t\t\n\t\n\n",
+ "NewMsgId": 6984814725261047392, 消息ID
+ "MsgSeq": 640356129
+ }
+}
+```
+
+#### 更换群主通知
+- 判断此类消息的逻辑:\$.Data.MsgType=10000 并且 \$.Data.Content.string为更换群主的通知内容
+```json
+ {
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356125, 消息ID
+ "FromUserName":
+ {
+ "string": "34757816141@chatroom" 所在群聊的ID
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 消息接收人的wxid
+ },
+ "MsgType": 10000,
+ "Content":
+ {
+ "string": "你已成为新群主"
+ },
+ "Status": 4,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705045441, 消息发送时间
+ "MsgSource": "\n\tv1_iqIx6JkV\n\t\n\t\t\n\t\n\n",
+ "NewMsgId": 7268255507978211143, 消息ID
+ "MsgSeq": 640356125
+ }
+ }
+```
+
+#### 群信息变更通知
+```json
+{
+ "TypeName": "ModContacts", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "UserName":
+ {
+ "string": "34757816141@chatroom" 所在群聊的ID
+ },
+ "NickName":
+ {
+ "string": "GeWe test"
+ },
+ "PyInitial":
+ {
+ "string": "GEWETEST"
+ },
+ "QuanPin":
+ {
+ "string": "GeWetest"
+ },
+ "Sex": 0,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "BitMask": 4294967295,
+ "BitVal": 2,
+ "ImgFlag": 1,
+ "Remark":
+ {},
+ "RemarkPyinitial":
+ {},
+ "RemarkQuanPin":
+ {},
+ "ContactType": 0,
+ "RoomInfoCount": 0,
+ "DomainList": [
+ {}],
+ "ChatRoomNotify": 1,
+ "AddContactScene": 0,
+ "PersonalCard": 0,
+ "HasWeiXinHdHeadImg": 0,
+ "VerifyFlag": 0,
+ "Level": 0,
+ "Source": 0,
+ "ChatRoomOwner": "wxid_0xsqb3o0tsvz22",
+ "WeiboFlag": 0,
+ "AlbumStyle": 0,
+ "AlbumFlag": 0,
+ "SnsUserInfo":
+ {
+ "SnsFlag": 0,
+ "SnsBgobjectId": 0,
+ "SnsFlagEx": 0
+ },
+ "CustomizedInfo":
+ {
+ "BrandFlag": 0
+ },
+ "AdditionalContactList":
+ {
+ "LinkedinContactItem":
+ {}
+ },
+ "ChatroomMaxCount": 700000019,
+ "DeleteFlag": 2,
+ "Description": "\b\u0004\u0012\u0017\n\u000Ewxid_phyyedw9xap220\u0001@\u0000�\u0001\u0000\u0012\u001B\n\u0012wxid_phyyedw9xap220\u0001@\u0000�\u0001\u0000\u0012\u001C\n\u0013wxid_0xsqb3o0tsvz220\u0001@\u0000�\u0001\u0000\u0012\u001D\n\u0013wxid_8pvka4jg6qzt220�\u0010@\u0000�\u0001\u0000\u0018\u0001\"\u0000(\u00008\u0000",
+ "ChatroomStatus": 27,
+ "Extflag": 0,
+ "ChatRoomBusinessType": 0
+ }
+}
+```
+
+#### 发布群公告
+- 判断此类消息的逻辑:\$.Data.MsgType=10002 并且 解析\$.Data.Content.string中的xml sysmsg.type=mmchatroombarannouncememt
+```json
+{
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356133, 消息ID
+ "FromUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22" 发布人的wxid
+ },
+ "ToUserName":
+ {
+ "string": "34757816141@chatroom" 所在群聊的ID
+ },
+ "MsgType": 10002,
+ "Content":
+ {
+ "string": "\n \n \n \n\t1705045558\n\t127\n\t1\n\t\n\t\twxid_0xsqb3o0tsvz22\n\t\t34757816141@chatroom\n\t\t7c79fed82a0037648954bba6d5ca2025\n\t\n\t\n\t\n\t\t\n\t\t\t-1\n\t\t\n\t\n\twxid_0xsqb3o0tsvz22_34757816141@chatroom_1705045558_2028281562\n\n]]>\n \n \n"
+ },
+ "Status": 3,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705045559, 消息发送时间
+ "MsgSource": "\n\t\n\t\t\n\t\n\n",
+ "NewMsgId": 8056409355261218186, 消息ID
+ "MsgSeq": 640356133
+ }
+}
+```
+
+#### 群待办
+- 判断此类消息的逻辑:\$.Data.MsgType=10002 并且 解析\$.Data.Content.string中的xml sysmsg.type=roomtoolstips
+```json
+ {
+ "TypeName": "AddMsg", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "MsgId": 1040356135, 消息ID
+ "FromUserName":
+ {
+ "string": "34757816141@chatroom" 所在群聊的ID
+ },
+ "ToUserName":
+ {
+ "string": "wxid_0xsqb3o0tsvz22"
+ },
+ "MsgType": 10002,
+ "Content":
+ {
+ "string": "34757816141@chatroom:\n\n\n 0\n\n \n \n \n \n \n \n \n \n \n 0\n \n \n \n \n\n\n \n\n\n \n \n \n \n \n \n \n\n \n\n \n\n\n"
+ },
+ "Status": 4,
+ "ImgStatus": 1,
+ "ImgBuf":
+ {
+ "iLen": 0
+ },
+ "CreateTime": 1705045591, 消息发送时间
+ "MsgSource": "\n\t\n\t\t\n\t\n\n",
+ "NewMsgId": 1765700414095721113, 消息ID
+ "MsgSeq": 640356135
+ }
+ }
+```
+
+#### 删除好友通知
+```json
+{
+ "TypeName": "DelContacts", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "UserName":
+ {
+ "string": "wxid_phyyedw9xap22" 删除的好友wxid
+ },
+ "DeleteContactScen": 0
+ }
+}
+```
+
+#### 退出群聊
+```json
+{
+ "TypeName": "DelContacts", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22", 所属微信的wxid
+ "Data":
+ {
+ "UserName":
+ {
+ "string": "34559815390@chatroom" 退出的群聊ID
+ },
+ "DeleteContactScen": 0
+ }
+}
+```
+
+#### 掉线通知
+```json
+{
+ "TypeName": "Offline", 消息类型
+ "Appid": "wx_wR_U4zPj2M_OTS3BCyoE4", 设备ID
+ "Wxid": "wxid_phyyedw9xap22" 掉线号的wxid
+}
+```
diff --git a/gewechat/client/get_token.py b/gewechat/client/get_token.py
new file mode 100644
index 0000000..b7af7d4
--- /dev/null
+++ b/gewechat/client/get_token.py
@@ -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)
\ No newline at end of file
diff --git a/gewechat/client/login.py b/gewechat/client/login.py
new file mode 100644
index 0000000..0f41096
--- /dev/null
+++ b/gewechat/client/login.py
@@ -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)
\ No newline at end of file
diff --git a/main.py b/main.py
index 83704aa..153ab83 100644
--- a/main.py
+++ b/main.py
@@ -1,35 +1,126 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
-
-import signal
+import logging
import threading
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 constants import ChatType
-from robot import Robot, __version__
-from wcferry import Wcf
+from robot import Robot
+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):
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):
- # 在退出前先关闭插件系统
- robot.plugin_manager.shutdown_plugins()
- wcf.cleanup() # 退出前清理环境
- exit(0)
+ parsed_url = urllib.parse.urlparse(callback_url)
+ host = parsed_url.hostname or "0.0.0.0"
+ port = parsed_url.port or 8999
- signal.signal(signal.SIGINT, handler)
+ # start_fastapi_server(host, port)
- robot = Robot(config, wcf, chat_type)
- robot.LOG.info(f"WeChatRobot【{__version__}】成功启动···")
+ # 创建 GewechatClient 实例
+ 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 发送新闻
robot.onEveryTime("08:30", robot.news_baidu_report_auto)
@@ -52,21 +143,22 @@ def main(chat_type: int):
# 启动Dashboard服务器
dashboard_server = None
- try:
- # 创建Dashboard服务器实例,共享robot对象
- from admin.dashboard.server import DashboardServer
- dashboard_server = DashboardServer(robot_instance=robot)
-
- # 在单独的线程中启动Dashboard服务器
- dashboard_thread = threading.Thread(target=dashboard_server.run, daemon=True)
- dashboard_thread.start()
- robot.LOG.info(f"Dashboard服务器已在 http://{dashboard_server.host}:{dashboard_server.port} 启动")
- except Exception as e:
- robot.LOG.error(f"Dashboard服务器启动失败: {e}")
+ # try:
+ # # 创建Dashboard服务器实例,共享robot对象
+ # from admin.dashboard.server import DashboardServer
+ # dashboard_server = DashboardServer(robot_instance=robot)
+ #
+ # # 在单独的线程中启动Dashboard服务器
+ # dashboard_thread = threading.Thread(target=dashboard_server.run, daemon=True)
+ # dashboard_thread.start()
+ # robot.LOG.info(f"Dashboard服务器已在 http://{dashboard_server.host}:{dashboard_server.port} 启动")
+ # except Exception as e:
+ # robot.LOG.error(f"Dashboard服务器启动失败: {e}")
# 让机器人一直跑
robot.keep_running_and_block_process()
+
if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument('-c', type=int, default=0, help=f'选择模型参数序号: {ChatType.help_hint()}')
diff --git a/message_util.py b/message_util.py
index 057f266..0b54765 100644
--- a/message_util.py
+++ b/message_util.py
@@ -1,10 +1,13 @@
# -*- coding: utf-8 -*-
import logging
+import os.path
import random
import time
from typing import Optional
-from wcferry import Wcf
+from gewechat_client import GewechatClient
+
+from utils.wechat.contact_manager import ContactManager
class MessageUtil:
@@ -13,15 +16,16 @@ class MessageUtil:
"""
# 修改 MessageUtil 类的初始化方法,接受联系人管理器而不是联系人字典
- def __init__(self, wcf, contact_manager):
- self.wcf = wcf
+ def __init__(self, app_id: str, base_url: str, client: GewechatClient, contact_manager: ContactManager):
+ self.app_id = app_id
+ self.client = client
self.contact_manager = contact_manager
self.LOG = logging.getLogger("MessageUtil")
def send_text(self, msg: str, receiver: str, at_list: str = "") -> None:
"""
发送文本消息
-
+
:param msg: 消息字符串
:param receiver: 接收人wxid或者群id
:param at_list: 要@的wxid, @所有人的wxid为:notify@all
@@ -35,22 +39,21 @@ class MessageUtil:
ats = " @所有人"
else:
wxids = at_list.split(",")
- for wxid in wxids:
- # 根据 wxid 查找群昵称
- ats += f" @{self.wcf.get_alias_in_chatroom(wxid, receiver)}"
+ if len(wxids) > 0:
+ ats += self.get_user_chatroom_nickname(receiver, wxids)
# {msg}{ats} 表示要发送的消息内容后面紧跟@,例如 北京天气情况为:xxx @张三
if ats == "":
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:
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:
"""
发送文件消息
-
+
:param file_path: 文件路径
:param receiver: 接收人wxid或者群id
"""
@@ -58,9 +61,11 @@ class MessageUtil:
time.sleep(random.uniform(0.5, 1.5))
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,13 +76,13 @@ class MessageUtil:
time.sleep(random.uniform(0.5, 1.5))
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,
receiver: str) -> int:
"""
发送富文本消息
-
+
卡片样式:
|-------------------------------------|
|title, 最长两行 |
@@ -87,7 +92,7 @@ class MessageUtil:
|digest, 最多三行,会占位 |--------|
|(account logo) name |
|-------------------------------------|
-
+
:param name: 左下显示的名字
:param account: 填公众号 id 可以显示对应的头像(gh_ 开头的)
:param title: 标题,最多两行
@@ -101,17 +106,21 @@ class MessageUtil:
time.sleep(random.uniform(0.5, 1.5))
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:
- """
- 更新联系人字典
-
- :param contacts: 联系人字典,格式为 {"wxid": "NickName"}
- """
- self.contacts.update(contacts)
+ 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)
- # 修改使用 allContacts 的地方,改为使用 contact_manager
- # 例如:
- # 原来的代码: nickname = self.allContacts.get(wxid, wxid)
- # 修改为: nickname = self.contact_manager.get_nickname(wxid)
+ def invite_member(self, group_id, sender):
+ return self.client.invite_member(self.app_id, sender, group_id, "自动加群邀请")
+
+ def get_chatroom_members(self, group_id) -> dict:
+ data = self.client.get_chatroom_member_list(self.app_id, group_id)
+ members = {member["wxid"]: member["nickName"] for member in data["data"]["memberList"]}
+ return members
+
+ def download_file_from_url(self, url: str, target_dir: str) -> str:
+ # 根据获取的文件地址,从server 下载 :http://{服务ip}:2532/download/{接口返回的文件路径}
+ return ""
diff --git a/plugins/beautyleg/main.py b/plugins/beautyleg/main.py
index 50f7dfd..786ab76 100644
--- a/plugins/beautyleg/main.py
+++ b/plugins/beautyleg/main.py
@@ -117,7 +117,7 @@ class BeautyLegPlugin(MessagePluginInterface):
# 发送图片
random_file_path = os.path.abspath(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}")
return True, "发送成功"
diff --git a/plugins/dify/main.py b/plugins/dify/main.py
index a72c609..52beb36 100644
--- a/plugins/dify/main.py
+++ b/plugins/dify/main.py
@@ -6,7 +6,6 @@ import time
import re # 添加re模块导入
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
@@ -63,7 +62,6 @@ class DifyPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象
- self.wcf = context.get("wcf")
self.event_system = context.get("event_system")
self.message_util: MessageUtil = context.get("message_util")
self.gbm = context.get("gbm")
diff --git a/plugins/douyin_parser/main.py b/plugins/douyin_parser/main.py
index ef3f399..c3790ef 100644
--- a/plugins/douyin_parser/main.py
+++ b/plugins/douyin_parser/main.py
@@ -6,8 +6,7 @@ import traceback
import requests
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.plugin_interface import PluginStatus
from utils.decorator.plugin_decorators import plugin_stats_decorator
@@ -61,9 +60,8 @@ class DouyinParserPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象
- self.wcf = context.get("wcf")
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")
# 从配置中获取参数
@@ -103,7 +101,6 @@ class DouyinParserPlugin(MessagePluginInterface):
self.LOG.info(f"插件执行: {self.name}:{content}")
sender = message.get("sender")
roomid = message.get("roomid", "")
- wcf: Wcf = message.get("wcf")
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"))
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, "发送视频文件成功"
else:
print(f"❌下载视频失败")
return False, "下载视频失败"
else:
# 发送卡片
- wcf.send_rich_text(
+ self.message_util.send_rich_text(
"BOT-PC直接查看",
"gh_11",
title[:30],
diff --git a/plugins/game_task/main.py b/plugins/game_task/main.py
index 2193f2b..1836b3f 100644
--- a/plugins/game_task/main.py
+++ b/plugins/game_task/main.py
@@ -3,8 +3,6 @@ import logging
from datetime import datetime
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.plugin_interface import PluginStatus
@@ -52,7 +50,6 @@ class GameTaskPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象
- self.wcf = context.get("wcf")
self.event_system = context.get("event_system")
self.message_util: MessageUtil = context.get("message_util")
@@ -115,7 +112,6 @@ class GameTaskPlugin(MessagePluginInterface):
command = content.split(" ")[0].lower()
sender = message.get("sender")
roomid = message.get("roomid", "")
- wcf: Wcf = message.get("wcf")
gbm: GroupBotManager = message.get("gbm")
all_contacts = message.get("all_contacts", {})
diff --git a/plugins/global_news/main.py b/plugins/global_news/main.py
index e6efe51..ec45a59 100644
--- a/plugins/global_news/main.py
+++ b/plugins/global_news/main.py
@@ -4,8 +4,7 @@ import threading
import time # 添加这一行
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.plugin_interface import PluginStatus
from utils.decorator.plugin_decorators import plugin_stats_decorator
@@ -55,12 +54,13 @@ class GlobalNewsPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象
- self.wcf = context.get("wcf")
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.command_format = self._config.get("GlobalNews", {}).get("command-format", "全球新闻 - 获取最新的全球政治经济新闻")
+ self._commands = self._config.get("GlobalNews", {}).get("command",
+ ["全球新闻", "国际新闻", "环球新闻", "政经新闻"])
+ self.command_format = self._config.get("GlobalNews", {}).get("command-format",
+ "全球新闻 - 获取最新的全球政治经济新闻")
self.enable = self._config.get("GlobalNews", {}).get("enable", True)
self.LOG.info(f"[{self.name}] 插件初始化完成,指令:{self._commands}")
@@ -96,7 +96,6 @@ class GlobalNewsPlugin(MessagePluginInterface):
self.LOG.info(f"插件执行: {self.name}:{content}")
sender = message.get("sender")
roomid = message.get("roomid", "")
- wcf: Wcf = message.get("wcf")
gbm: GroupBotManager = message.get("gbm")
# 检查权限
@@ -105,48 +104,48 @@ class GlobalNewsPlugin(MessagePluginInterface):
# 生成唯一任务ID
task_id = f"{sender}_{roomid}_{int(time.time())}"
-
+
# 发送等待消息
- wcf.send_text("🌍正在获取全球新闻,请稍候...",
- (roomid if roomid else sender), sender)
-
+ self.message_util.send_text("🌍正在获取全球新闻,请稍候...",
+ (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, "新闻获取任务已启动"
- 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(
target=self._fetch_news_thread,
- args=(task_id, sender, roomid, wcf)
+ args=(task_id, sender, roomid)
)
thread.daemon = True
thread.start()
self._news_tasks[task_id] = thread
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:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
news_result = loop.run_until_complete(self._fetch_news_async())
loop.close()
-
+
# 处理结果
if news_result:
# 发送新闻图片
receiver = roomid if roomid else sender
- wcf.send_image(news_result, receiver)
- wcf.send_text("🌍全球新闻获取完成!", receiver, sender)
+ self.message_util.send_image(news_result, receiver)
+ self.message_util.send_text("🌍全球新闻获取完成!", receiver, sender)
else:
- wcf.send_text("❌获取新闻失败,请稍后再试",
- (roomid if roomid else sender), sender)
+ self.message_util.send_text("❌获取新闻失败,请稍后再试",
+ (roomid if roomid else sender), sender)
except Exception as 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)
finally:
# 清理任务
if task_id in self._news_tasks:
@@ -163,23 +162,23 @@ class GlobalNewsPlugin(MessagePluginInterface):
self._run_in_executor(fox),
self._run_in_executor(bbc)
]
-
+
# 并行执行所有任务
results = await asyncio.gather(*tasks)
-
+
# 合并结果
news_titles = "\n".join(results)
-
+
# 使用AI分析新闻
markdown_news = await self._run_in_executor(
dify_news_title_analyze, news_titles
)
-
+
# 转换为图片
image_path = await self._run_in_executor(
convert_md_str_to_image, markdown_news, "news_output.png"
)
-
+
return image_path
except Exception as e:
self.LOG.error(f"异步获取新闻失败: {e}")
@@ -188,4 +187,4 @@ class GlobalNewsPlugin(MessagePluginInterface):
async def _run_in_executor(self, func, *args):
"""在线程池中运行同步函数"""
loop = asyncio.get_event_loop()
- return await loop.run_in_executor(None, func, *args)
\ No newline at end of file
+ return await loop.run_in_executor(None, func, *args)
diff --git a/plugins/group_add/main.py b/plugins/group_add/main.py
index 90235fb..b599cd5 100644
--- a/plugins/group_add/main.py
+++ b/plugins/group_add/main.py
@@ -3,7 +3,6 @@ import re
from datetime import datetime
from typing import Dict, Any, List, Optional, Tuple
-from wcferry import Wcf
from plugin_common.message_plugin_interface import MessagePluginInterface
from plugin_common.plugin_interface import PluginStatus
@@ -45,8 +44,6 @@ class GroupAddPlugin(MessagePluginInterface):
self.LOG = logging.getLogger(f"Plugin.{self.name}")
self.LOG.info(f"正在初始化 {self.name} 插件...")
- # 保存上下文对象
- self.wcf = context.get("wcf")
# 获取群管理器
self.gbm = context.get("gbm")
@@ -99,8 +96,7 @@ class GroupAddPlugin(MessagePluginInterface):
"""处理消息"""
content = message.get("content", "")
roomid = message.get("roomid", "")
- wcf: Wcf = message.get("wcf")
-
+
self.LOG.info(f"插件执行: {self.name}:{content}")
# 提取昵称
@@ -116,8 +112,8 @@ class GroupAddPlugin(MessagePluginInterface):
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}")
return True, f"已欢迎 {nickname}"
diff --git a/plugins/group_auto_invite/main.py b/plugins/group_auto_invite/main.py
index 66b945d..ece3a9d 100644
--- a/plugins/group_auto_invite/main.py
+++ b/plugins/group_auto_invite/main.py
@@ -3,8 +3,9 @@ import redis
import re
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.plugin_interface import PluginStatus
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.info(f"正在初始化 {self.name} 插件...")
- # 保存上下文对象
- self.wcf = context.get("wcf")
# 获取群管理器
self.gbm = context.get("gbm")
# 获取Redis连接池
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", {})
self._commands = plugin_config.get("command", ["#加群配置"])
@@ -111,27 +112,26 @@ class GroupAutoInvitePlugin(MessagePluginInterface):
sender = message.get("sender")
roomid = message.get("roomid", "")
- wcf: Wcf = message.get("wcf")
gbm: GroupBotManager = message.get("gbm")
# 处理加群配置命令
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)
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, "无法处理的消息"
- 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]]:
"""处理配置命令"""
# 检查是否为管理员
admin_list = self.gbm.get_admin_list()
if sender not in admin_list:
- wcf.send_text("⚠️ 权限不足,只有管理员才能配置群邀请功能",
+ self.message_util.send_text("⚠️ 权限不足,只有管理员才能配置群邀请功能",
(roomid if roomid else sender), sender)
return True, "权限不足"
@@ -140,10 +140,10 @@ class GroupAutoInvitePlugin(MessagePluginInterface):
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, "配置命令处理成功"
- 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]]:
"""处理加群请求"""
try:
@@ -152,29 +152,29 @@ class GroupAutoInvitePlugin(MessagePluginInterface):
# 检查是否找到群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, "未找到群聊"
# 判断是否在群里面,如果在,则不添加
con = ContactManager.get_instance()
members = con.get_group_members(group_id)
# 如果在群里面,则不添加
if sender in members:
- wcf.send_text(f"⚠️ 你已经在群聊中了,无需重复添加", sender)
+ self.message_util.send_text(f"⚠️ 你已经在群聊中了,无需重复添加", sender)
return True, "你已经在群聊中了"
# 发送邀请
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:
- wcf.send_text(f"✅ 已发送邀请,请查看群聊邀请通知", sender)
+ self.message_util.send_text(f"✅ 已发送邀请,请查看群聊邀请通知", sender)
return True, "邀请发送成功"
else:
- wcf.send_text(f"❌ 邀请发送失败,请稍后再试", sender)
+ self.message_util.send_text(f"❌ 邀请发送失败,请稍后再试", sender)
return False, "邀请发送失败"
except Exception as e:
self.LOG.error(f"处理加群请求出错: {e}")
- wcf.send_text(f"❌ 处理加群请求出错: {e}", sender)
+ self.message_util.send_text(f"❌ 处理加群请求出错: {e}", sender)
return False, f"处理出错: {e}"
def add_mapping(self, key, group_id):
diff --git a/plugins/group_member_change/main.py b/plugins/group_member_change/main.py
index df1e03c..a75ceaa 100644
--- a/plugins/group_member_change/main.py
+++ b/plugins/group_member_change/main.py
@@ -4,8 +4,6 @@ import time
from datetime import datetime
from typing import Dict, Any, List, Optional, Tuple
-from wcferry import Wcf
-
from plugin_common.message_plugin_interface import MessagePluginInterface
from plugin_common.plugin_interface import PluginStatus
from utils.robot_cmd.robot_command import Feature, PermissionStatus, GroupBotManager
@@ -51,8 +49,6 @@ class GroupMemberChangePlugin(MessagePluginInterface):
"""初始化插件"""
self.LOG.info(f"正在初始化 {self.name} 插件...")
- # 保存上下文对象
- self.wcf: Wcf = context.get("wcf")
# 创建消息工具实例message_util
self.message_util: MessageUtil = context.get("message_util")
@@ -130,7 +126,7 @@ class GroupMemberChangePlugin(MessagePluginInterface):
"""检查指定群的成员变化"""
try:
# 获取当前群成员
- current_members = self.wcf.get_chatroom_members(group_id)
+ current_members = self.message_util.get_chatroom_members(group_id)
# 添加安全检查:如果获取到的成员列表为空,可能是接口异常
if not current_members:
diff --git a/plugins/group_virtual/main.py b/plugins/group_virtual/main.py
index bf3b2e7..fd83314 100644
--- a/plugins/group_virtual/main.py
+++ b/plugins/group_virtual/main.py
@@ -1,8 +1,7 @@
import logging
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 plugin_common.message_plugin_interface import MessagePluginInterface
from plugin_common.plugin_interface import PluginStatus
@@ -51,7 +50,6 @@ class GroupVirtualPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象
- self.wcf: Wcf = context.get("wcf")
self.event_system = context.get("event_system")
self.message_util: MessageUtil = context.get("message_util")
@@ -99,9 +97,9 @@ class GroupVirtualPlugin(MessagePluginInterface):
"""处理消息"""
roomid = message.get("roomid", "")
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, "不转发自己的消息"
# 获取该群所在的所有虚拟聊天组
diff --git a/plugins/message_recall/main.py b/plugins/message_recall/main.py
index b042524..9ad9e07 100644
--- a/plugins/message_recall/main.py
+++ b/plugins/message_recall/main.py
@@ -6,7 +6,6 @@ from plugin_common.message_plugin_interface import MessagePluginInterface
from plugin_common.plugin_interface import PluginStatus
from utils.decorator.plugin_decorators import plugin_stats_decorator
from utils.robot_cmd.robot_command import GroupBotManager
-from wcferry import Wcf
class MessageRecallPlugin(MessagePluginInterface):
@@ -44,8 +43,6 @@ class MessageRecallPlugin(MessagePluginInterface):
self.LOG = logging.getLogger(f"Plugin.{self.name}")
self.LOG.info(f"正在初始化 {self.name} 插件...")
- # 保存上下文对象
- self.wcf: Wcf = context.get("wcf")
self.event_system = context.get("event_system")
self.message_util: MessageUtil = context.get("message_util")
@@ -92,48 +89,47 @@ class MessageRecallPlugin(MessagePluginInterface):
sender = message.get("sender")
roomid = message.get("roomid", "")
- wcf: Wcf = message.get("wcf")
# 检查是否是管理员
admin_list = GroupBotManager.get_admin_list()
self.LOG.info(f"admin_list={admin_list}")
# if sender not in admin_list:
- # wcf.send_text("⚠️ 权限不足,只有管理员才能撤回消息",
+ # self.message_util.send_text("⚠️ 权限不足,只有管理员才能撤回消息",
# (roomid if roomid else sender), sender)
# return True, "权限不足"
# 解析命令获取消息ID
parts = content.split(" ", 1)
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)
return True, "命令格式错误"
-
- try:
- # 从数据库里面提取可以处理的消息and StrTalker ={roomid}
- sql = (f"SELECT * FROM MSG where IsSender=1 and CreateTime > (strftime('%s', 'now') - 120) "
- f"limit {parts[1]}")
- data = wcf.query_sql('MSG0.db', sql)
- self.LOG.info(f"SQL:{sql}\n 查询到可撤回数据: {data}")
- if not data:
- wcf.send_text("❌ 没有可撤回的消息", (roomid if roomid else sender), sender)
- return True, "没有可撤回的消息"
- for item in data:
- if item["MsgSvrID"]:
- # 调用撤回消息API
- result = wcf.revoke_msg(item["MsgSvrID"])
- if result:
- wcf.send_text("✅ 消息撤回成功", (roomid if roomid else sender), sender)
- return True, "撤回成功"
- else:
- wcf.send_text("❌ 消息撤回失败,可能是消息ID无效或已超过撤回时间限制(2分钟)",
- (roomid if roomid else sender), sender)
- return True, "撤回失败"
-
- except Exception as e:
- self.LOG.error(f"撤回消息出错: {e}")
- wcf.send_text(f"❌ 撤回消息出错: {str(e)}", (roomid if roomid else sender), sender)
- return True, f"处理出错: {e}"
+ #
+ # try:
+ # # 从数据库里面提取可以处理的消息and StrTalker ={roomid}
+ # sql = (f"SELECT * FROM MSG where IsSender=1 and CreateTime > (strftime('%s', 'now') - 120) "
+ # f"limit {parts[1]}")
+ # data = self.message_util.query_sql('MSG0.db', sql)
+ # self.LOG.info(f"SQL:{sql}\n 查询到可撤回数据: {data}")
+ # if not data:
+ # self.message_util.send_text("❌ 没有可撤回的消息", (roomid if roomid else sender), sender)
+ # return True, "没有可撤回的消息"
+ # for item in data:
+ # if item["MsgSvrID"]:
+ # # 调用撤回消息API
+ # result = self.message_util.revoke_msg(item["MsgSvrID"])
+ # if result:
+ # self.message_util.send_text("✅ 消息撤回成功", (roomid if roomid else sender), sender)
+ # return True, "撤回成功"
+ # else:
+ # self.message_util.send_text("❌ 消息撤回失败,可能是消息ID无效或已超过撤回时间限制(2分钟)",
+ # (roomid if roomid else sender), sender)
+ # return True, "撤回失败"
+ #
+ # except Exception as e:
+ # self.LOG.error(f"撤回消息出错: {e}")
+ # self.message_util.send_text(f"❌ 撤回消息出错: {str(e)}", (roomid if roomid else sender), sender)
+ # return True, f"处理出错: {e}"
def get_help(self) -> str:
"""获取插件帮助信息"""
diff --git a/plugins/message_sign/main.py b/plugins/message_sign/main.py
index 01b818c..e8031f0 100644
--- a/plugins/message_sign/main.py
+++ b/plugins/message_sign/main.py
@@ -3,7 +3,6 @@ import logging
import pytz
from typing import Dict, Any, List, Optional, Tuple
-from wcferry import Wcf
from db.connection import DBConnectionManager
from message_util import MessageUtil
@@ -64,7 +63,6 @@ class MessageSignPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象
- self.wcf = context.get("wcf")
self.event_system = context.get("event_system")
self.message_util: MessageUtil = context.get("message_util")
self.gbm = context.get("gbm")
@@ -217,7 +215,6 @@ class MessageSignPlugin(MessagePluginInterface):
"""处理签到请求"""
sender = message.get("sender")
roomid = message.get("roomid", "")
- wcf: Wcf = message.get("wcf")
all_contacts = message.get("all_contacts", {})
try:
diff --git a/plugins/message_summary/main.py b/plugins/message_summary/main.py
index 2db8c03..3119881 100644
--- a/plugins/message_summary/main.py
+++ b/plugins/message_summary/main.py
@@ -5,6 +5,7 @@ from typing import Dict, Any, Tuple, Optional, List
import requests
+from message_util import MessageUtil
from utils.string_utils import remove_trailing_content
from utils.wechat.message_to_db import MessageStorage
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_url = api_config.get("api_url", "http://192.168.2.240/v1/chat-messages")
self.message_storage = MessageStorage()
+ self.message_util: MessageUtil = context.get("message_util")
+
self.LOG.info(f"初始化 {self.name} 插件成功")
return True
except Exception as e:
@@ -88,13 +91,10 @@ class MessageSummaryPlugin(MessagePluginInterface):
command = content[len(self.command_prefix):].split()[0]
if command not in self.commands:
return False, None
- wcf = message.get("wcf")
# 获取需要总结的内容
group_id = message.get("roomid")
if not group_id:
- # 直接发送消息
- if wcf:
- wcf.send_text("只支持群聊消息总结", message.get("sender"))
+ self.message_util.send_text("只支持群聊消息总结", message.get("sender"))
return False, None
# 权限判断
gbm: GroupBotManager = message.get("gbm")
@@ -110,18 +110,17 @@ class MessageSummaryPlugin(MessagePluginInterface):
# 获取群名并处理
group_name = all_contacts.get(group_id, group_id)
group_name = self._sanitize_group_name(group_name)
- if wcf:
- wcf.send_text("⏳群消息总结中… 😊", group_id)
+ self.message_util.send_text("⏳群消息总结中… 😊", group_id)
- # 创建线程异步处理总结生成和发送
- summary_thread = threading.Thread(
- target=self._async_generate_and_send_summary,
- args=(chat_content, group_name, group_id, message)
- )
- summary_thread.daemon = True # 设置为守护线程,主程序退出时线程也会退出
- summary_thread.start()
+ # 创建线程异步处理总结生成和发送
+ summary_thread = threading.Thread(
+ target=self._async_generate_and_send_summary,
+ args=(chat_content, group_name, group_id, message)
+ )
+ summary_thread.daemon = True # 设置为守护线程,主程序退出时线程也会退出
+ summary_thread.start()
- return True, "异步总结已启动"
+ return True, "异步总结已启动"
except Exception as e:
self.LOG.error(f"处理消息总结命令失败: {e}")
@@ -138,17 +137,17 @@ class MessageSummaryPlugin(MessagePluginInterface):
wcf = message.get("wcf")
if wcf:
# 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:
- wcf.send_file(image_path, group_id)
+ self.message_util.send_file(image_path, group_id)
else:
- wcf.send_text("❌ 生成总结图片失败", group_id)
+ self.message_util.send_text("❌ 生成总结图片失败", group_id)
except Exception as e:
self.LOG.error(f"异步生成总结失败: {e}")
wcf = message.get("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:
"""处理群名,去除特殊字符并限制长度"""
diff --git a/plugins/music/main.py b/plugins/music/main.py
index 0a14134..dbcaf5e 100644
--- a/plugins/music/main.py
+++ b/plugins/music/main.py
@@ -3,7 +3,6 @@ import requests
import lz4.block as lb
from typing import Dict, Any, List, Optional, Tuple
-from wcferry import Wcf
from plugin_common.message_plugin_interface import MessagePluginInterface
from plugin_common.plugin_interface import PluginStatus
@@ -48,7 +47,6 @@ class MusicPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象
- self.wcf = context.get("wcf")
self.event_system = context.get("event_system")
self.message_util = context.get("message_util")
@@ -90,12 +88,11 @@ class MusicPlugin(MessagePluginInterface):
command = content.split(" ")[0]
sender = message.get("sender")
roomid = message.get("roomid", "")
- wcf: Wcf = message.get("wcf")
gbm: GroupBotManager = message.get("gbm")
# 检查命令格式
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)
return False, "命令格式错误"
@@ -110,7 +107,7 @@ class MusicPlugin(MessagePluginInterface):
# 搜索歌曲
song_info = self._search_song(user_song_name)
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)
return False, "未找到歌曲"
@@ -204,19 +201,18 @@ class MusicPlugin(MessagePluginInterface):
"""
-
- # 修改消息数据库里面的消息content内容
- text_bytes = xml_message.encode('utf-8')
- compressed_data = lb.compress(text_bytes, store_size=False).hex()
-
- data = wcf.query_sql('MSG0.db', "SELECT * FROM MSG where type = 49 limit 1")
- wcf.query_sql('MSG0.db',
- f"""UPDATE MSG SET CompressContent = x'{compressed_data}', BytesExtra=x'', type=49, SubType=3,
- IsSender=0, TalkerId=2 WHERE MsgSvrID={data[0]['MsgSvrID']}"""
- )
-
- result = wcf.forward_msg(data[0]["MsgSvrID"], receiver)
- self.LOG.info(f"插件化:点歌发送结果: {result}")
+ # # 修改消息数据库里面的消息content内容
+ # text_bytes = xml_message.encode('utf-8')
+ # compressed_data = lb.compress(text_bytes, store_size=False).hex()
+ #
+ # data = self.message_util.query_sql('MSG0.db', "SELECT * FROM MSG where type = 49 limit 1")
+ # self.message_util.query_sql('MSG0.db',
+ # f"""UPDATE MSG SET CompressContent = x'{compressed_data}', BytesExtra=x'', type=49, SubType=3,
+ # IsSender=0, TalkerId=2 WHERE MsgSvrID={data[0]['MsgSvrID']}"""
+ # )
+ #
+ # result = self.message_util.forward_msg(data[0]["MsgSvrID"], receiver)
+ # self.LOG.info(f"插件化:点歌发送结果: {result}")
return True
except Exception as e:
diff --git a/plugins/plugin_manager/main.py b/plugins/plugin_manager/main.py
index 360ed46..206fd97 100644
--- a/plugins/plugin_manager/main.py
+++ b/plugins/plugin_manager/main.py
@@ -94,14 +94,14 @@ class PluginManagerPlugin(MessagePluginInterface):
# 检查命令格式
parts = content.split(" ")
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, "命令格式错误"
# 只有使用的时候才全局获取对象。防止在预加载的时候跟主线程冲突
self.plugin_registry = PluginRegistry()
self.plugin_manager = PluginManager().get_instance()
# 检查权限 (只允许管理员操作)
if not self._is_admin(sender, gbm):
- wcf.send_text(f"❌权限不足,只有管理员可以管理插件", target, sender)
+ self.message_util.send_text(f"❌权限不足,只有管理员可以管理插件", target, sender)
return True, "权限不足"
# 解析子命令
@@ -125,14 +125,14 @@ class PluginManagerPlugin(MessagePluginInterface):
if handler and (sub_command == "列表" or plugin_name):
return handler(wcf, sender, roomid)
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, "未知命令"
except Exception as e:
import traceback
error_trace = traceback.format_exc()
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}"
def _is_admin(self, user_id: str, gbm: GroupBotManager) -> bool:
@@ -153,7 +153,7 @@ class PluginManagerPlugin(MessagePluginInterface):
module_name = plugin.__class__.__module__.split('.')[-2]
message += f"{status}-{plugin.name} [模块: {module_name}]\n"
- wcf.send_text(message, target, sender)
+ self.message_util.send_text(message, target, sender)
return True, "列出插件成功"
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)
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}"
# 不允许操作自身(对于某些操作)
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, "不能对插件管理插件自身执行此操作"
# 执行具体操作
@@ -182,11 +182,11 @@ class PluginManagerPlugin(MessagePluginInterface):
plugin = self.plugin_registry.get_plugin(plugin_name)
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} 不存在"
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} 已经处于启用状态"
# 获取插件的模块名
@@ -194,10 +194,10 @@ class PluginManagerPlugin(MessagePluginInterface):
# 启动插件
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} 启用成功"
else:
- wcf.send_text(f"❌插件 {plugin_name} 启用失败", target, sender)
+ self.message_util.send_text(f"❌插件 {plugin_name} 启用失败", target, sender)
return False, f"插件 {plugin_name} 启用失败"
def _disable_plugin(self, plugin_name: str, wcf, sender: str, roomid: str) -> Tuple[bool, str]:
@@ -206,11 +206,11 @@ class PluginManagerPlugin(MessagePluginInterface):
plugin = self.plugin_registry.get_plugin(plugin_name)
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} 不存在"
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} 已经处于禁用状态"
# 获取插件的模块名
@@ -218,10 +218,10 @@ class PluginManagerPlugin(MessagePluginInterface):
# 停止插件
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} 禁用成功"
else:
- wcf.send_text(f"❌插件 {plugin_name} 禁用失败", target, sender)
+ self.message_util.send_text(f"❌插件 {plugin_name} 禁用失败", target, sender)
return False, f"插件 {plugin_name} 禁用失败"
def _reload_plugin(self, plugin_name: str, wcf, sender: str, roomid: str) -> Tuple[bool, str]:
@@ -230,7 +230,7 @@ class PluginManagerPlugin(MessagePluginInterface):
plugin = self.plugin_registry.get_plugin(plugin_name)
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} 不存在"
# 记录原插件状态
@@ -243,10 +243,10 @@ class PluginManagerPlugin(MessagePluginInterface):
reloaded_plugin = self.plugin_manager.reload_plugin(module_name)
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} 重载成功"
else:
- wcf.send_text(f"❌插件 {plugin_name} 重载失败", target, sender)
+ self.message_util.send_text(f"❌插件 {plugin_name} 重载失败", target, sender)
return False, f"插件 {plugin_name} 重载失败"
def _unload_plugin(self, plugin_name: str, wcf, sender: str, roomid: str) -> Tuple[bool, str]:
@@ -255,7 +255,7 @@ class PluginManagerPlugin(MessagePluginInterface):
plugin = self.plugin_registry.get_plugin(plugin_name)
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} 不存在"
# 获取插件的模块名
@@ -263,10 +263,10 @@ class PluginManagerPlugin(MessagePluginInterface):
# 卸载插件
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} 卸载成功"
else:
- wcf.send_text(f"❌插件 {plugin_name} 卸载失败", target, sender)
+ self.message_util.send_text(f"❌插件 {plugin_name} 卸载失败", target, sender)
return False, f"插件 {plugin_name} 卸载失败"
def _load_plugin(self, plugin_name: str, wcf, sender: str, roomid: str, silent: bool = False) -> Tuple[bool, str]:
@@ -276,7 +276,7 @@ class PluginManagerPlugin(MessagePluginInterface):
plugin_dir = os.path.join("plugins", plugin_name)
if not os.path.exists(plugin_dir):
if not silent:
- wcf.send_text(f"❌插件目录 {plugin_dir} 不存在",
+ self.message_util.send_text(f"❌插件目录 {plugin_dir} 不存在",
(roomid if roomid else sender), sender)
return False, f"插件目录 {plugin_dir} 不存在"
@@ -285,7 +285,7 @@ class PluginManagerPlugin(MessagePluginInterface):
existing_module_name = existing_plugin.__class__.__module__.split('.')[-2]
if existing_module_name == plugin_name:
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)
return True, f"插件 {existing_plugin.name} 已经加载"
@@ -294,18 +294,18 @@ class PluginManagerPlugin(MessagePluginInterface):
plugin = self.plugin_manager.load_plugin(plugin_name)
if plugin:
if not silent:
- wcf.send_text(f"✅插件 {plugin.name} 加载成功",
+ self.message_util.send_text(f"✅插件 {plugin.name} 加载成功",
(roomid if roomid else sender), sender)
return True, f"插件 {plugin.name} 加载成功"
else:
if not silent:
- wcf.send_text(f"❌插件 {plugin_name} 加载失败",
+ self.message_util.send_text(f"❌插件 {plugin_name} 加载失败",
(roomid if roomid else sender), sender)
return False, f"插件 {plugin_name} 加载失败"
except Exception as e:
self.LOG.error(f"加载插件 {plugin_name} 出错: {e}")
if not silent:
- wcf.send_text(f"❌加载插件出错: {str(e)}",
+ self.message_util.send_text(f"❌加载插件出错: {str(e)}",
(roomid if roomid else sender), sender)
return False, f"加载插件出错: {e}"
@@ -315,13 +315,13 @@ class PluginManagerPlugin(MessagePluginInterface):
display_name, plugin = self.plugin_manager.find_plugin_by_name(plugin_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)
return True, f"未找到插件 {plugin_name}"
plugin = self.plugin_registry.get_plugin(display_name)
if not plugin:
- wcf.send_text(f"❌插件 {display_name} 不存在",
+ self.message_util.send_text(f"❌插件 {display_name} 不存在",
(roomid if roomid else sender), sender)
return True, f"插件 {display_name} 不存在"
@@ -340,5 +340,5 @@ class PluginManagerPlugin(MessagePluginInterface):
🔑 命令:{', '.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, "查看插件详情成功"
diff --git a/plugins/point_trade/main.py b/plugins/point_trade/main.py
index ae566ad..e97354a 100644
--- a/plugins/point_trade/main.py
+++ b/plugins/point_trade/main.py
@@ -4,7 +4,6 @@ from datetime import datetime
from typing import Dict, Any, List, Optional, Tuple
import xml.etree.ElementTree as ET
-from wcferry import Wcf
from db.connection import DBConnectionManager
from db.points_db import PointsDBOperator, PointSource
@@ -54,7 +53,6 @@ class PointTradePlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象
- self.wcf = context.get("wcf")
self.event_system = context.get("event_system")
self.message_util: MessageUtil = context.get("message_util")
self.gbm = context.get("gbm")
@@ -129,7 +127,6 @@ class PointTradePlugin(MessagePluginInterface):
command = content.split(" ")
sender = message.get("sender")
roomid = message.get("roomid", "")
- wcf: Wcf = message.get("wcf")
gbm: GroupBotManager = message.get("gbm")
xml = message.get("xml", "")
@@ -159,7 +156,6 @@ class PointTradePlugin(MessagePluginInterface):
command = content.split(" ")
sender = message.get("sender")
roomid = message.get("roomid", "")
- wcf: Wcf = message.get("wcf")
xml = message.get("xml", "")
# 检查命令格式
@@ -312,7 +308,6 @@ class PointTradePlugin(MessagePluginInterface):
"""处理积分排行榜命令"""
sender = message.get("sender")
roomid = message.get("roomid", "")
- wcf: Wcf = message.get("wcf")
if not roomid:
self.message_util.send_text("❌积分排行榜仅在群聊中可用!", sender, "")
@@ -452,7 +447,6 @@ class PointTradePlugin(MessagePluginInterface):
content = str(message.get("content", "")).strip()
sender = message.get("sender")
roomid = message.get("roomid", "")
- wcf: Wcf = message.get("wcf")
xml = message.get("xml", "")
# 检查是否在群聊中
@@ -633,7 +627,6 @@ class PointTradePlugin(MessagePluginInterface):
"""处理保释命令"""
sender = message.get("sender")
roomid = message.get("roomid", "")
- wcf: Wcf = message.get("wcf")
xml = message.get("xml", "")
# 检查是否在群聊中
diff --git a/plugins/system_updater/main.py b/plugins/system_updater/main.py
index 417f2b7..654d531 100644
--- a/plugins/system_updater/main.py
+++ b/plugins/system_updater/main.py
@@ -78,7 +78,6 @@ class SystemUpdaterPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象
- self.wcf = context.get("wcf")
self.message_util = context.get("message_util")
self.config = self._config
@@ -127,12 +126,11 @@ class SystemUpdaterPlugin(MessagePluginInterface):
content = str(message.get("content", "")).strip()
sender = message.get("sender")
roomid = message.get("roomid", "")
- wcf = message.get("wcf")
gbm = message.get("gbm", None)
# 检查权限
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)
return True, "无权限"
@@ -156,12 +154,12 @@ class SystemUpdaterPlugin(MessagePluginInterface):
# 检查win_click模块是否可用
if win_click is None:
- wcf.send_text("⚠️ 无法执行更新操作,系统缺少必要的组件",
+ self.message_util.send_text("⚠️ 无法执行更新操作,系统缺少必要的组件",
(roomid if roomid else sender), sender)
return True, "缺少win_click模块"
# 发送更新通知
- wcf.send_text(f"🔄 系统即将更新并重启,等待时间设置为{wait_time}秒...",
+ self.message_util.send_text(f"🔄 系统即将更新并重启,等待时间设置为{wait_time}秒...",
(roomid if roomid else sender), sender)
# 启动更新流程
diff --git a/plugins/video/main.py b/plugins/video/main.py
index cca2dd2..9df3bc6 100644
--- a/plugins/video/main.py
+++ b/plugins/video/main.py
@@ -3,7 +3,6 @@ import os
import requests
from typing import Dict, Any, List, Optional, Tuple
-from wcferry import Wcf
from plugin_common.message_plugin_interface import MessagePluginInterface
from plugin_common.plugin_interface import PluginStatus
@@ -50,7 +49,6 @@ class VideoPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象
- self.wcf = context.get("wcf")
self.event_system = context.get("event_system")
self.message_util = context.get("message_util")
self.gbm = context.get("gbm")
@@ -96,7 +94,6 @@ class VideoPlugin(MessagePluginInterface):
self.LOG.info(f"插件执行: {self.name}:{content}")
sender = message.get("sender")
roomid = message.get("roomid", "")
- wcf: Wcf = message.get("wcf")
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)
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)
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}")
return True, "发送成功"
except Exception as 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)
return False, f"处理出错: {e}"
diff --git a/plugins/video_man/main.py b/plugins/video_man/main.py
index 85752bf..4edd954 100644
--- a/plugins/video_man/main.py
+++ b/plugins/video_man/main.py
@@ -3,8 +3,6 @@ import os
import requests
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.plugin_interface import PluginStatus
@@ -51,7 +49,6 @@ class VideoManPlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象
- self.wcf = context.get("wcf")
self.event_system = context.get("event_system")
self.message_util: MessageUtil = context.get("message_util")
self.gbm = context.get("gbm")
@@ -97,7 +94,6 @@ class VideoManPlugin(MessagePluginInterface):
self.LOG.info(f"插件执行: {self.name}:{content}")
sender = message.get("sender")
roomid = message.get("roomid", "")
- wcf: Wcf = message.get("wcf")
gbm: GroupBotManager = message.get("gbm")
# 检查权限
@@ -114,7 +110,7 @@ class VideoManPlugin(MessagePluginInterface):
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}")
return True, "发送成功"
diff --git a/plugins/xiuren_image/main.py b/plugins/xiuren_image/main.py
index 092ab93..a9cbe68 100644
--- a/plugins/xiuren_image/main.py
+++ b/plugins/xiuren_image/main.py
@@ -3,8 +3,6 @@ import os
import random
from typing import Dict, Any, List, Optional, Tuple
-from wcferry import Wcf
-
from plugin_common.message_plugin_interface import MessagePluginInterface
from plugin_common.plugin_interface import PluginStatus
from utils.decorator.plugin_decorators import plugin_stats_decorator
@@ -49,7 +47,6 @@ class XiurenImagePlugin(MessagePluginInterface):
self.LOG.info(f"正在初始化 {self.name} 插件...")
# 保存上下文对象
- self.wcf = context.get("wcf")
self.event_system = context.get("event_system")
self.message_util = context.get("message_util")
@@ -95,7 +92,6 @@ class XiurenImagePlugin(MessagePluginInterface):
self.LOG.info(f"插件执行: {self.name}:{content}")
sender = message.get("sender")
roomid = message.get("roomid", "")
- wcf: Wcf = message.get("wcf")
gbm: GroupBotManager = message.get("gbm")
# 检查权限
@@ -106,12 +102,12 @@ class XiurenImagePlugin(MessagePluginInterface):
# 获取随机图片
pic_path = self._get_random_pic()
if not pic_path:
- wcf.send_text(f"❌未找到图片资源",
+ self.message_util.send_text(f"❌未找到图片资源",
(roomid if roomid else sender), sender)
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}")
return True, "发送成功"
diff --git a/requirements.txt b/requirements.txt
index d921655..9d7d6d9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,7 +7,6 @@ requests~=2.32.3
schedule~=1.2.2
pyhandytools
sparkdesk-api==1.3.0
-wcferry==39.5.1.0
websocket~=0.2.1
pillow~=11.0.0
jupyter_client~=8.6.3
@@ -45,4 +44,7 @@ pywin32==306
opencv-python~=4.11.0.86
deepface~=0.0.93
-scikit-learn>=1.0.2
\ No newline at end of file
+scikit-learn>=1.0.2
+gewechat-client==0.1.5
+fastapi~=0.115.12
+uvicorn~=0.34.2
\ No newline at end of file
diff --git a/robot.py b/robot.py
index 5b3a4cd..24256d4 100644
--- a/robot.py
+++ b/robot.py
@@ -7,14 +7,12 @@ from queue import Empty
from threading import Thread
import random
+from gewechat_client import GewechatClient
+
from base.func_doubao import Doubao
from base.func_epic import is_friday, get_free
from base.func_zhipu import ZhiPu
-from wcferry import Wcf, WxMsg
-
-from base.func_bard import BardAssistant
-from base.func_chatglm import ChatGLM
from base.func_chatgpt import ChatGPT
from base.func_news import News
from base.func_tigerbot import TigerBot
@@ -22,6 +20,7 @@ from base.func_xinghuo_web import XinghuoWeb
from base.func_claude import Claude
from configuration import Config
from constants import ChatType
+from gewechat.call_back_message.message import WxMessage, MessageType
from utils.wechat.message_to_db import MessageStorage
from plugin_common.event_system import EventType, EventSystem
from plugin_common.message_plugin_interface import MessagePluginInterface
@@ -33,8 +32,6 @@ from job_mgmt import Job
from utils.robot_cmd.robot_command import Feature
from utils.robot_cmd.robot_command import PermissionStatus
-__version__ = "39.5.1.0"
-
from sehuatang.shehuatang import pdf_file_path
from utils.wechat.contact_manager import ContactManager
from xiuren.meitu_dl import meitu_dowload_pub_pic
@@ -48,17 +45,16 @@ class Robot(Job):
"""个性化自己的机器人
"""
- def __init__(self, config: Config, wcf: Wcf, chat_type: int) -> None:
- self.wcf = wcf
+ def __init__(self, config: Config, app_id: str, client: GewechatClient, chat_type: int) -> None:
+ self.app_id = app_id
+ self.client = client
self.config = config
self.LOG = logging.getLogger("Robot")
- self.wxid = self.wcf.get_self_wxid()
# 初始化联系人管理器并设置联系人
self.contact_manager = ContactManager.get_instance()
self.allContacts = self.get_all_contacts()
- self.all_head_img = self.get_all_head_img_url()
- self.contact_manager.set_contacts(self.allContacts, self.all_head_img, self.wcf)
+ self.contact_manager.set_contacts(self.allContacts)
self.LOG.info(f"DB+REDIS 连接池开始初始化")
# 使用单例模式获取实例
@@ -73,7 +69,7 @@ class Robot(Job):
self.redis_pool = self.db_manager.redis_pool
# 初始化消息工具类 - 使用联系人管理器
- self.message_util = MessageUtil(wcf, self.contact_manager)
+ self.message_util = MessageUtil(app_id, client, self.contact_manager)
self.groups = {} # 存储按group_id分组的消息列表,每个group_id最多保留10条消息
GroupBotManager.load_local_cache()
@@ -89,7 +85,7 @@ class Robot(Job):
# 设置插件系统上下文
self.system_context = {
"config": config,
- "wcf": wcf,
+ "client": client,
"event_system": self.event_system,
"plugin_registry": self.plugin_registry,
"db_pool": self.db_pool,
@@ -105,48 +101,7 @@ class Robot(Job):
self.LOG.info("插件系统初始化完成")
# 消息存档模块初始化,自动完成入库动作
- self.message_storage = MessageStorage(self.wcf)
- if ChatType.is_in_chat_types(chat_type):
- if chat_type == ChatType.TIGER_BOT.value and TigerBot.value_check(self.config.TIGERBOT):
- self.chat = TigerBot(self.config.TIGERBOT)
- elif chat_type == ChatType.CHATGPT.value and ChatGPT.value_check(self.config.CHATGPT):
- self.chat = ChatGPT(self.config.CHATGPT)
- elif chat_type == ChatType.XINGHUO_WEB.value and XinghuoWeb.value_check(self.config.XINGHUO_WEB):
- self.chat = XinghuoWeb(self.config.XINGHUO_WEB)
- elif chat_type == ChatType.CHATGLM.value and ChatGLM.value_check(self.config.CHATGLM):
- self.chat = ChatGLM(self.config.CHATGLM)
- elif chat_type == ChatType.BardAssistant.value and BardAssistant.value_check(self.config.BardAssistant):
- self.chat = BardAssistant(self.config.BardAssistant)
- elif chat_type == ChatType.ZhiPu.value and ZhiPu.value_check(self.config.ZhiPu):
- self.chat = ZhiPu(self.config.ZhiPu)
- elif chat_type == ChatType.CLAUDE.value and Claude.value_check(self.config.CLAUDE):
- self.chat = Claude(self.config.CLAUDE)
- elif chat_type == ChatType.DOUBAO.value and Claude.value_check(self.config.DOUBAO):
- self.chat = Doubao(self.config.DOUBAO)
- else:
- self.LOG.warning("未配置模型")
- self.chat = None
- else:
- if TigerBot.value_check(self.config.TIGERBOT):
- self.chat = TigerBot(self.config.TIGERBOT)
- elif ChatGPT.value_check(self.config.CHATGPT):
- self.chat = ChatGPT(self.config.CHATGPT)
- elif XinghuoWeb.value_check(self.config.XINGHUO_WEB):
- self.chat = XinghuoWeb(self.config.XINGHUO_WEB)
- elif ChatGLM.value_check(self.config.CHATGLM):
- self.chat = ChatGLM(self.config.CHATGLM)
- elif BardAssistant.value_check(self.config.BardAssistant):
- self.chat = BardAssistant(self.config.BardAssistant)
- elif ZhiPu.value_check(self.config.ZhiPu):
- self.chat = ZhiPu(self.config.ZhiPu)
- elif Claude.value_check(self.config.CLAUDE):
- self.chat = Claude(self.config.CLAUDE)
- elif Doubao.value_check(self.config.DOUBAO):
- self.chat = Doubao(self.config.DOUBAO)
- else:
- self.LOG.warning("未配置模型")
- self.chat = None
- self.LOG.info(f"已选择: {self.chat}")
+ self.message_storage = MessageStorage(self.client)
@staticmethod
def value_check(args: dict) -> bool:
@@ -154,11 +109,11 @@ class Robot(Job):
return all(value is not None for key, value in args.items() if key != 'proxy')
return False
- def toChitchat(self, msg: WxMsg) -> bool:
+ def toChitchat(self, msg: WxMessage) -> bool:
"""闲聊,接入 ChatGPT
"""
# 去除@的人和空格等字符
- q = re.sub(r"@.*?[\u2005|\s]", "", msg.content).replace(" ", "")
+ q = re.sub(r"@.*?[\u2005|\s]", "", msg.content.raw_content).replace(" ", "")
if q == "#今日百度新闻":
self.news_baidu_report((msg.roomid if msg.from_group() else msg.sender))
@@ -176,7 +131,7 @@ class Robot(Job):
else:
return True
- def processMsg(self, msg: WxMsg) -> None:
+ def processMsg(self, msg: WxMessage) -> None:
"""当接收到消息的时候,会调用本方法。如果不实现本方法,则打印原始消息。
此处可进行自定义发送的内容,如通过 msg.content 关键字自动获取当前天气信息,并发送到对应的群组@发送者
群号:msg.roomid 微信ID:msg.sender 消息内容:msg.content
@@ -222,7 +177,7 @@ class Robot(Job):
try:
self.message_storage.archive_message(msg)
# 单独处理图片消息
- if msg.type == 3: # 图片消息类型
+ if msg.msg_type == 3: # 图片消息类型
self.message_storage.process_image(msg)
except Exception as e:
self.LOG.error(f"archive_message error: {e}")
@@ -237,7 +192,7 @@ class Robot(Job):
rsp = self.gbm.handle_command(msg.roomid, msg.content)
# 不在群里发送,防止被骚扰
if rsp is not None:
- self.send_text_msg(rsp, msg.roomid, msg.sender)
+ self.message_util.send_text(rsp, msg.roomid, msg.sender)
return
except Exception as e:
self.LOG.error(f"revoke_receive_message error: {e}")
@@ -248,15 +203,7 @@ class Robot(Job):
if plugin_processed:
return
- # 非群聊信息,按消息类型进行处理
- if msg.type == 37: # 好友请求
- self.LOG.info(f"收到好友请求:{msg}")
- self.auto_accept_friend_request(msg)
-
- elif msg.type == 10000: # 系统信息
- self.say_hi_to_new_friend(msg)
-
- elif msg.type == 0x01: # 文本消息
+ elif msg.msg_type == MessageType.TEXT: # 文本消息
# 让配置加载更灵活,自己可以更新配置。也可以利用定时任务更新。
if msg.from_self():
if msg.content == "^更新$":
@@ -274,79 +221,15 @@ class Robot(Job):
else:
self.toChitchat(msg) # 闲聊
- def onMsg(self, msg: WxMsg) -> int:
+ def onMsg(self, msg: WxMessage) -> int:
try:
- self.LOG.debug(msg) # 打印信息
- self.processMsg(msg)
+ self.LOG.info(msg) # 打印信息
+ # self.processMsg(msg)
except Exception as e:
self.LOG.error(e)
return 0
- def enableRecvMsg(self) -> None:
- self.wcf.enable_recv_msg(self.onMsg)
-
- def enableReceivingMsg(self) -> None:
- def innerProcessMsg(wcf: Wcf):
- while wcf.is_receiving_msg():
- try:
- msg = wcf.get_msg()
- self.LOG.debug(msg)
- self.processMsg(msg)
- except Empty:
- continue # Empty message
- except Exception as e:
- self.LOG.error(f"Receiving message error: {e}")
-
- self.wcf.enable_receiving_msg()
- Thread(target=innerProcessMsg, name="GetMessage", args=(self.wcf,), daemon=True).start()
-
- def send_text_msg(self, msg: str, receiver: str, at_list: str = "") -> None:
- """ 发送消息
- :param msg: 消息字符串
- :param receiver: 接收人wxid或者群id
- :param at_list: 要@的wxid, @所有人的wxid为:notify@all
- """
- # msg 中需要有 @ 名单中一样数量的 @
-
- # 风控处理,随机延迟发送,解决群消息高频发送导致的微信风险
- time.sleep(random.uniform(0.3, 1.0))
-
- ats = ""
- if at_list:
- if at_list == "notify@all": # @所有人
- ats = " @所有人"
- else:
- wxids = at_list.split(",")
- for wxid in wxids:
- # 根据 wxid 查找群昵称
- ats += f" @{self.wcf.get_alias_in_chatroom(wxid, receiver)}"
-
- # {msg}{ats} 表示要发送的消息内容后面紧跟@,例如 北京天气情况为:xxx @张三
- if ats == "":
- self.LOG.info(f"To {receiver}: {msg}")
- self.wcf.send_text(f"{msg}", receiver, at_list)
- else:
- self.LOG.info(f"To {receiver}: {ats}\r{msg}")
- self.wcf.send_text(f"{ats}\n\n{msg}", receiver, at_list)
-
- def get_all_contacts(self) -> dict:
- """
- 获取联系人(包括好友、公众号、服务号、群成员……)
- 格式: {"wxid": "NickName"}
- """
- contacts = self.wcf.query_sql("MicroMsg.db", "SELECT UserName, NickName FROM Contact;")
- return {contact["UserName"]: contact["NickName"] for contact in contacts}
-
- def get_all_head_img_url(self) -> dict:
- try:
- head_img_urls = self.wcf.query_sql("MicroMsg.db", "SELECT usrName ,bigHeadImgUrl FROM ContactHeadImgUrl;")
- # self.LOG.info(f"head_img_urls: {head_img_urls}")
- return {contact["usrName"]: contact["bigHeadImgUrl"] for contact in head_img_urls}
- except Exception as e:
- self.LOG.error(f"get_all_head_img_url error: {e}")
- return {}
-
def keep_running_and_block_process(self) -> None:
"""
保持机器人运行,不让进程退出
@@ -355,31 +238,11 @@ class Robot(Job):
self.runPendingJobs()
time.sleep(1)
- def auto_accept_friend_request(self, msg: WxMsg) -> None:
- try:
- xml = ET.fromstring(msg.content)
- v3 = xml.attrib["encryptusername"]
- v4 = xml.attrib["ticket"]
- scene = int(xml.attrib["scene"])
- res = self.wcf.accept_new_friend(v3, v4, scene)
- self.LOG.info(f"同意好友请求:{res}")
- except Exception as e:
- self.LOG.error(f"同意好友出错:{e}")
-
- def say_hi_to_new_friend(self, msg: WxMsg) -> None:
- nickName = re.findall(r"你已添加了(.*),现在可以开始聊天了。", msg.content)
- if nickName:
- # 添加了好友,更新好友列表和联系人管理器
- self.allContacts[msg.sender] = nickName[0]
- self.contact_manager.update_contact(msg.sender, nickName[0])
- self.send_text_msg(f"Hi {nickName[0]},我自动通过了你的好友请求。", msg.sender)
-
# 添加一个方法用于刷新联系人信息
def refresh_contacts(self):
"""刷新联系人信息"""
self.allContacts = self.get_all_contacts()
- self.all_head_img = self.get_all_head_img_url()
- self.contact_manager.refresh_contacts(self.allContacts, self.all_head_img, self.wcf)
+ self.contact_manager.refresh_contacts(self.allContacts)
self.LOG.info("联系人信息已刷新")
def send_group_txt_message(self, msg: str, feature: Feature):
@@ -389,7 +252,7 @@ class Robot(Job):
return
for r in receivers:
if self.gbm.get_group_permission(r, feature) == PermissionStatus.ENABLED:
- self.send_text_msg(msg, r)
+ self.message_util.send_text(msg, r)
except Exception as e:
self.LOG.error(f"send_group_txt_message:{feature.description} error:{e}")
@@ -400,11 +263,11 @@ class Robot(Job):
return
for r in receivers:
if self.gbm.get_group_permission(r, feature) == PermissionStatus.ENABLED:
- self.wcf.send_file(path, r)
+ self.message_util.send_file(path, r)
except Exception as e:
self.LOG.error(f"send_group_file_message:{feature.description} error:{e}")
- def process_plugin_message(self, msg: WxMsg) -> bool:
+ def process_plugin_message(self, msg: WxMessage) -> bool:
"""使用插件处理消息"""
# 获取所有消息处理插件
message_plugins = self.plugin_registry.get_plugins_by_type(MessagePluginInterface)
@@ -415,16 +278,15 @@ class Robot(Job):
continue
try:
- # 转换WxMsg为插件可处理的格式
+ # 转换WxMessage为插件可处理的格式
plugin_msg = {
- "type": msg.type,
+ "type": msg.msg_type,
"content": msg.content,
"sender": msg.sender,
"roomid": msg.roomid if msg.from_group() else "",
- "xml": msg.xml,
+ "xml": msg.content.xml_content,
"is_at": msg.is_at(self.wxid), # 确保正确设置is_at标志
"timestamp": time.time(),
- "wcf": self.wcf, # 提供wcf对象,让插件可以直接发送消息
"message_util": self.message_util, # 提供消息工具类
"gbm": self.gbm, # 每次从程序变量中取,保证最新
"all_contacts": self.allContacts,
@@ -536,6 +398,87 @@ class Robot(Job):
def xiu_ren_pdf_send(self):
try:
pub_path = generate_pdf_from_images("xiuren")
- self.wcf.send_file(pub_path, "45317011307@chatroom")
+ self.message_util.send_file(pub_path, "45317011307@chatroom")
except Exception as e:
self.LOG.error(f"xiu_ren_pdf_send error:{e}")
+
+ def get_all_contacts(self) -> dict:
+ """获取所有联系人信息并保存到数据库"""
+ from db.contacts_db import ContactsDBOperator
+
+ contacts_dict = {}
+ contacts_wxids = self.client.fetch_contacts_list(self.app_id)
+
+ # 初始化联系人数据库操作类
+ contacts_db = ContactsDBOperator()
+
+ for wxid in contacts_wxids:
+ # 获取联系人详细信息
+ contact_info = self.client.get_detail_info(self.app_id, wxid)
+
+ # 将联系人信息添加到字典中
+ if contact_info and contact_info.get("ret") == 200 and "data" in contact_info:
+ contact_data = contact_info.get("data", [])
+
+ # 保存联系人信息到数据库
+ if contact_data:
+ try:
+ # 判断联系人类型
+ contact_type = "friends" # 默认为好友类型
+ if wxid.endswith("@chatroom"):
+ contact_type = "chatrooms"
+ # 如果是这个类型,则提取群成员信息
+ # 提取群成员信息
+ self.update_chatroom_member_details(wxid)
+ elif wxid.startswith("gh_"):
+ contact_type = "ghs"
+
+ # 保存到数据库
+ contacts_db.save_contacts(contact_data, contact_type)
+
+ # 添加到返回字典
+ for contact in contact_data:
+ user_name = contact.get("userName")
+ if user_name:
+ contacts_dict[user_name] = contact.get("nickName") or user_name
+ except Exception as e:
+ self.LOG.error(f"保存联系人信息到数据库失败: {e}")
+
+ self.LOG.info(f"成功获取并保存{len(contacts_dict)}个联系人信息")
+ return contacts_dict
+
+ def update_chatroom_member_details(self, chatroom_id):
+ """更新群成员详细信息"""
+ try:
+ # 首先获取群成员列表
+ members_response = self.client.get_chatroom_member_list(self.app_id, chatroom_id)
+ if members_response and members_response.get('ret') == 200:
+ member_list = members_response.get('data', {}).get('memberList', [])
+
+ # 提取成员wxid列表
+ member_wxids = [member.get('wxid') for member in member_list if member.get('wxid')]
+
+ if member_wxids:
+ # 获取成员详细信息
+ details_response = self.client.get_chatroom_member_detail(self.app_id, chatroom_id, member_wxids)
+
+ # 使用ContactsDBOperator处理响应
+ from db.contacts_db import ContactsDBOperator
+ contacts_db = ContactsDBOperator()
+ success = contacts_db.process_chatroom_member_detail_response(chatroom_id, details_response)
+
+ if success:
+ self.LOG.info(f"成功更新群聊{chatroom_id}的成员详细信息")
+ else:
+ self.LOG.error(f"更新群聊{chatroom_id}的成员详细信息失败")
+
+ return success
+ else:
+ self.LOG.warning(f"群聊{chatroom_id}没有成员")
+ return False
+ else:
+ self.LOG.error(f"获取群聊{chatroom_id}成员列表失败")
+ return False
+ except Exception as e:
+ self.LOG.error(f"更新群聊成员详细信息出错: {e}")
+ return False
diff --git a/utils/decorator/points_decorator.py b/utils/decorator/points_decorator.py
index 3d46d73..8b88709 100644
--- a/utils/decorator/points_decorator.py
+++ b/utils/decorator/points_decorator.py
@@ -143,7 +143,7 @@ def plugin_points_cost(points: int, description: str = None, feature: Feature =
# 积分不足
wcf = message.get("wcf")
if wcf:
- wcf.send_text(
+ self.message_util.send_text(
f"❌ 积分不足\n无法使用 {plugin_name} 功能\n"
f"🪙 先参与积分活动[签到,答题/t]赚取吧!\n"
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} 积分"
wcf = message.get("wcf")
if wcf:
- wcf.send_text(
+ self.message_util.send_text(
f"💰消费 {points} 积分",
(roomid if roomid else sender), sender
)
diff --git a/utils/json_converter.py b/utils/json_converter.py
new file mode 100644
index 0000000..fdbe3fd
--- /dev/null
+++ b/utils/json_converter.py
@@ -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}")
\ No newline at end of file
diff --git a/utils/robot_cmd/robot_command.py b/utils/robot_cmd/robot_command.py
index 9538257..6461ed9 100644
--- a/utils/robot_cmd/robot_command.py
+++ b/utils/robot_cmd/robot_command.py
@@ -41,7 +41,7 @@ class Feature(Enum):
VIDEO = 14, "黑丝视频 [黑丝视频, 黑丝, 来个黑丝,搞个黑丝]"
VIDEO_MAN = 15, "肌肉视频 [猛男, 肌肉, 帅哥]"
# GROUP_ADD = 16, "加群提醒"
- # DOUYIN_PARSER = 17, "抖音链接转视频"
+ DOUYIN_PARSER = 17, "抖音链接转视频"
GROUP_MEMBER_CHANGE = 18, "群成员变更提醒功能"
# KID_PHOTO_EXTRACT = 19, "儿童照片提取转发功能" # 小朋友照片提取功能
NEWS = 20, "全球政治经济新闻"
diff --git a/utils/wechat/contact_manager.py b/utils/wechat/contact_manager.py
index dbb0775..31f081a 100644
--- a/utils/wechat/contact_manager.py
+++ b/utils/wechat/contact_manager.py
@@ -5,7 +5,9 @@
import logging
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:
@@ -50,7 +52,7 @@ class ContactManager:
cls._instance = ContactManager()
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:
@@ -67,13 +69,12 @@ class ContactManager:
"gender": gender}
"""
self._contacts = contacts
- self._friends = wcf.get_friends()
- self._head_images = head_imgs
+ self._friends = 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._personal_contacts = {}
@@ -90,8 +91,6 @@ class ContactManager:
# 判断是否为群组(wxid以@chatroom结尾)
elif wxid.endswith('@chatroom'):
self._group_contacts[wxid] = nickname
- # 如果是群,这处理群列表内容
- self._group_contacts_friends[wxid] = wcf.get_chatroom_members(wxid)
# # 其他为普通好友和群成员
# else:
@@ -216,7 +215,7 @@ class ContactManager:
self._personal_contacts[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:
@@ -235,11 +234,9 @@ class ContactManager:
# "province": cnt.get("province", ""),
# "city": cnt.get("city", ""),
# "gender": gender}
- self._friends = wcf.get_friends()
- self._head_images = head_imgs
+ self._friends = self.message_util.get_friends()
self._logger.info(f"联系人信息已刷新,共 {len(new_contacts)} 个联系人")
- # 重新分类联系人
- self._classify_contacts(wcf)
+ self._classify_contacts()
def get_contact_statistics(self) -> Tuple[int, int, int, int, int]:
"""获取联系人统计信息
diff --git a/utils/wechat/message_to_db.py b/utils/wechat/message_to_db.py
index 47d2566..66b6d36 100644
--- a/utils/wechat/message_to_db.py
+++ b/utils/wechat/message_to_db.py
@@ -3,12 +3,15 @@ import xml.etree.ElementTree as ET
import logging
import concurrent.futures # 添加线程池支持
import os
-from wcferry import WxMsg, Wcf
+
+from gewechat_client import GewechatClient
from db.connection import DBConnectionManager
from db.message_storage import MessageStorageDB
# 导入积分系统
from db.points_db import PointsDBOperator, PointSource
+from gewechat.call_back_message.message import WxMessage
+
# 配置日志
logging.basicConfig(
level=logging.INFO,
@@ -19,11 +22,11 @@ logger = logging.getLogger("MessageStorage")
class MessageStorage:
- def __init__(self, wcf: Wcf = None):
+ def __init__(self, client: GewechatClient = None):
# 获取数据库连接管理器的单例
self.db_manager = DBConnectionManager.get_instance()
self.message_db = MessageStorageDB(self.db_manager)
-
+
self.points_db = PointsDBOperator(self.db_manager)
# 初始化本地缓存字典,使用 group_id 作为键
self.local_membercounts = {}
@@ -34,7 +37,7 @@ class MessageStorage:
self.pending_tasks = []
# 保存WCF实例,用于图片处理
- self.wcf = wcf
+ self.client = client
# 图片处理相关初始化
self.image_executor = concurrent.futures.ThreadPoolExecutor(max_workers=3) # 专用于图片处理的线程池
@@ -46,7 +49,7 @@ class MessageStorage:
os.makedirs(self.image_dir, exist_ok=True)
logger.info(f"图片存储目录: {self.image_dir}")
- def process_message(self, message: WxMsg):
+ def process_message(self, message: WxMessage):
# 示例message字符串
current_date = datetime.now().strftime('%Y-%m-%d')
# 生成Redis key
@@ -59,7 +62,7 @@ class MessageStorage:
redis_conn.expire(key, 86400 * 2)
# 或者使用字符串:r.incr(key) # 如果只存储一个整数值,字符串类型可能更简单
- def archive_message(self, msg: WxMsg):
+ def archive_message(self, msg: WxMessage):
"""异步存档消息,防止堵塞主线程"""
# 提交任务到线程池
future = self.executor.submit(self._archive_message_task, msg)
@@ -70,7 +73,7 @@ class MessageStorage:
# 清理已完成的任务
self._cleanup_completed_tasks()
- def _archive_message_task(self, msg: WxMsg):
+ def _archive_message_task(self, msg: WxMessage):
"""实际执行消息存档的任务函数"""
try:
# 使用 MessageStorageDB 类存档消息
@@ -80,7 +83,7 @@ class MessageStorage:
'roomid': msg.roomid,
'sender': msg.sender,
'content': msg.content, # 添加消息内容
- 'message_id': msg.id # 添加消息ID
+ 'message_id': msg.msg_id # 添加消息ID
}
except Exception as e:
logger.error(f"存档消息出错: {e}")
@@ -89,13 +92,13 @@ class MessageStorage:
'roomid': msg.roomid,
'sender': msg.sender,
'content': msg.content, # 添加消息内容
- 'message_id': msg.id, # 添加消息ID
+ 'message_id': msg.msg_id, # 添加消息ID
'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
# 提交任务到图片处理线程池
@@ -108,11 +111,11 @@ class MessageStorage:
self._cleanup_completed_tasks()
return True
- def _process_image_task(self, msg: WxMsg):
+ def _process_image_task(self, msg: WxMessage):
"""实际执行图片处理的任务函数"""
try:
# 使用wcf下载图片,确保图片存在
- if self.wcf and msg.id:
+ if self.client and msg.msg_id:
# 创建按群ID或个人wxid分割的目录
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)
# 尝试使用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:
- 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 {
'success': True,
- 'message_id': msg.id,
+ 'message_id': msg.msg_id,
'roomid': msg.roomid,
'sender': msg.sender,
'file_path': download_path
@@ -137,7 +151,7 @@ class MessageStorage:
else:
return {
'success': False,
- 'message_id': msg.id,
+ 'message_id': msg.msg_id,
'roomid': msg.roomid,
'sender': msg.sender,
'error': "图片下载失败"
@@ -145,21 +159,25 @@ class MessageStorage:
else:
return {
'success': False,
- 'message_id': msg.id,
+ 'message_id': msg.msg_id,
'roomid': msg.roomid,
'sender': msg.sender,
'error': "WCF实例不存在或消息ID无效"
}
except Exception as e:
- logger.error(f"图片处理出错: {msg.id}, 错误: {e}")
+ logger.error(f"图片处理出错: {msg.msg_id}, 错误: {e}")
return {
'success': False,
- 'message_id': msg.id,
+ 'message_id': msg.msg_id,
'roomid': msg.roomid,
'sender': msg.sender,
'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):
"""处理异步图片处理任务完成后的回调"""
try:
@@ -256,13 +274,13 @@ class MessageStorage:
# 格式化输出字符串,添加emoji和美化格式
ranking_str = f"🏆 {yesterday} 发言排行榜 🏆\n"
-
+
# 为不同名次添加不同的奖杯和样式,并发放积分
for rank, result in enumerate(results, start=1):
username = result['wx_id']
speech_count = result['speech_count']
display_name = allContacts.get(username, username)
-
+
# 根据排名发放不同数量的积分
reward_points = 0
if rank == 1:
@@ -280,14 +298,14 @@ class MessageStorage:
else:
reward_points = 5
ranking_str += f"👍 {rank}.{display_name}: {speech_count}次 +{reward_points}积分\n"
-
+
# 发放积分奖励
if reward_points > 0:
success, _ = self.points_db.add_points(
- username,
- groupId,
- reward_points,
- PointSource.OTHER,
+ username,
+ groupId,
+ reward_points,
+ PointSource.OTHER,
f"{yesterday}发言排行第{rank}名奖励"
)
if not success: