重大版本调整:gewechat兼容。
This commit is contained in:
@@ -178,12 +178,12 @@ class DashboardServer:
|
|||||||
return {"success": False, "message": "WCF实例不可用"}
|
return {"success": False, "message": "WCF实例不可用"}
|
||||||
|
|
||||||
# 获取当前登录的微信ID
|
# 获取当前登录的微信ID
|
||||||
wx_id = self.wcf.get_self_wxid()
|
wx_id = self.self.message_util.get_self_wxid()
|
||||||
if not wx_id:
|
if not wx_id:
|
||||||
return {"success": False, "message": "未获取到微信ID"}
|
return {"success": False, "message": "未获取到微信ID"}
|
||||||
|
|
||||||
# 获取用户详细信息
|
# 获取用户详细信息
|
||||||
user_info = self.wcf.get_user_info()
|
user_info = self.self.message_util.get_user_info()
|
||||||
self.logger.info(f"获取用户信息:{user_info}")
|
self.logger.info(f"获取用户信息:{user_info}")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -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. 启动后,发送 #帮助 可以查看 模式和常用指令
|
|
||||||
@@ -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)
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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]
|
|
||||||
@@ -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()
|
|
||||||
@@ -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())
|
|
||||||
@@ -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)
|
|
||||||
@@ -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)
|
|
||||||
@@ -140,3 +140,12 @@ redis_config:
|
|||||||
port: 6379
|
port: 6379
|
||||||
db: 0
|
db: 0
|
||||||
decode_responses: true
|
decode_responses: true
|
||||||
|
|
||||||
|
|
||||||
|
#gewechat 配置
|
||||||
|
|
||||||
|
gewechat:
|
||||||
|
base_url: "http://192.168.2.240:2531/v2/api"
|
||||||
|
gewechat_token: "cb43f52db27e4a56bb6ec7da54373582"
|
||||||
|
app_id: "wx_3BC6eSHGE5xEm_hH3__7c"
|
||||||
|
callback_url : "http://192.168.2.192:8999/gewechat/callback"
|
||||||
|
|||||||
@@ -44,3 +44,42 @@ class Config(object):
|
|||||||
# DB config
|
# DB config
|
||||||
self.mariadb = yconfig.get("db_config", {})
|
self.mariadb = yconfig.get("db_config", {})
|
||||||
self.redis = yconfig.get("redis_config", {})
|
self.redis = yconfig.get("redis_config", {})
|
||||||
|
|
||||||
|
#gewechat config
|
||||||
|
gewechat_config = yconfig['gewechat']
|
||||||
|
self.BASE_URL = gewechat_config.get("base_url", "")
|
||||||
|
self.GEWECHAT_TOKEN = gewechat_config.get("gewechat_token", "")
|
||||||
|
self.APP_ID = gewechat_config.get("app_id", "")
|
||||||
|
self.CALLBACK_URL = gewechat_config.get("callback_url", "")
|
||||||
|
|
||||||
|
def update_config(self, section, key, value):
|
||||||
|
"""更新配置文件中指定部分的键值
|
||||||
|
|
||||||
|
Args:
|
||||||
|
section: 配置部分名称,如 'gewechat'
|
||||||
|
key: 键名,如 'app_id'
|
||||||
|
value: 要设置的值
|
||||||
|
"""
|
||||||
|
import yaml
|
||||||
|
import os
|
||||||
|
|
||||||
|
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.yaml')
|
||||||
|
|
||||||
|
# 读取当前配置
|
||||||
|
with open(config_path, 'r', encoding='utf-8') as f:
|
||||||
|
config_data = yaml.safe_load(f)
|
||||||
|
|
||||||
|
# 更新配置
|
||||||
|
if section in config_data:
|
||||||
|
config_data[section][key] = value
|
||||||
|
|
||||||
|
# 写回配置文件
|
||||||
|
with open(config_path, 'w', encoding='utf-8') as f:
|
||||||
|
yaml.dump(config_data, f, default_flow_style=False, allow_unicode=True)
|
||||||
|
|
||||||
|
# 更新当前实例的属性
|
||||||
|
if hasattr(self, key.upper()):
|
||||||
|
setattr(self, key.upper(), value)
|
||||||
|
|
||||||
|
return True
|
||||||
|
return False
|
||||||
458
db/contacts_db.py
Normal file
458
db/contacts_db.py
Normal file
@@ -0,0 +1,458 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
微信联系人数据库操作类
|
||||||
|
用于管理微信联系人信息的存储和查询
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
from typing import List, Dict, Optional, Union, Any
|
||||||
|
|
||||||
|
from db.connection import DBConnectionManager
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class ContactsDBOperator:
|
||||||
|
"""微信联系人数据库操作类"""
|
||||||
|
|
||||||
|
def __init__(self, db_manager: Optional[DBConnectionManager] = None):
|
||||||
|
"""初始化联系人数据库操作类
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db_manager: 数据库连接管理器,如果为None则自动获取单例
|
||||||
|
"""
|
||||||
|
self.db_manager = db_manager or DBConnectionManager.get_instance()
|
||||||
|
self._ensure_table_exists()
|
||||||
|
|
||||||
|
def _ensure_table_exists(self):
|
||||||
|
"""确保联系人表存在"""
|
||||||
|
try:
|
||||||
|
# 创建联系人表
|
||||||
|
sql = """
|
||||||
|
CREATE TABLE IF NOT EXISTS t_wechat_contacts (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_name VARCHAR(64) NOT NULL COMMENT '微信ID',
|
||||||
|
nick_name VARCHAR(128) COMMENT '昵称',
|
||||||
|
py_initial VARCHAR(128) COMMENT '拼音首字母',
|
||||||
|
quan_pin VARCHAR(256) COMMENT '全拼',
|
||||||
|
sex TINYINT COMMENT '性别:1男,2女,0未知',
|
||||||
|
remark VARCHAR(128) COMMENT '备注',
|
||||||
|
remark_py_initial VARCHAR(128) COMMENT '备注拼音首字母',
|
||||||
|
remark_quan_pin VARCHAR(256) COMMENT '备注全拼',
|
||||||
|
signature TEXT COMMENT '个性签名',
|
||||||
|
alias VARCHAR(128) COMMENT '微信号',
|
||||||
|
sns_bg_img TEXT COMMENT '朋友圈背景图',
|
||||||
|
country VARCHAR(64) COMMENT '国家',
|
||||||
|
province VARCHAR(64) COMMENT '省份',
|
||||||
|
city VARCHAR(64) COMMENT '城市',
|
||||||
|
big_head_img_url TEXT COMMENT '大头像URL',
|
||||||
|
small_head_img_url TEXT COMMENT '小头像URL',
|
||||||
|
description TEXT COMMENT '描述',
|
||||||
|
card_img_url TEXT COMMENT '名片图片URL',
|
||||||
|
label_list TEXT COMMENT '标签列表',
|
||||||
|
phone_num_list TEXT COMMENT '电话号码列表',
|
||||||
|
type ENUM('friends', 'chatrooms', 'ghs') NOT NULL COMMENT '联系人类型:好友、群聊、公众号',
|
||||||
|
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
UNIQUE KEY `idx_user_name` (`user_name`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信联系人信息表';
|
||||||
|
"""
|
||||||
|
conn = self.db_manager.get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute(sql)
|
||||||
|
|
||||||
|
# 创建群成员表 - 增加了更多字段以支持详细信息
|
||||||
|
sql_chatroom_member = """
|
||||||
|
CREATE TABLE IF NOT EXISTS t_chatroom_member (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
chatroom_id VARCHAR(64) NOT NULL COMMENT '群聊ID',
|
||||||
|
wxid VARCHAR(64) NOT NULL COMMENT '成员微信ID',
|
||||||
|
nick_name VARCHAR(128) COMMENT '成员昵称',
|
||||||
|
display_name VARCHAR(128) COMMENT '群内显示名称',
|
||||||
|
inviter_user_name VARCHAR(64) COMMENT '邀请人微信ID',
|
||||||
|
member_flag INT COMMENT '成员标志,2049表示管理员',
|
||||||
|
big_head_img_url TEXT COMMENT '大头像URL',
|
||||||
|
small_head_img_url TEXT COMMENT '小头像URL',
|
||||||
|
is_owner TINYINT(1) DEFAULT 0 COMMENT '是否群主:0否,1是',
|
||||||
|
is_admin TINYINT(1) DEFAULT 0 COMMENT '是否管理员:0否,1是',
|
||||||
|
sex TINYINT COMMENT '性别:1男,2女,0未知',
|
||||||
|
signature TEXT COMMENT '个性签名',
|
||||||
|
alias VARCHAR(128) COMMENT '微信号',
|
||||||
|
country VARCHAR(64) COMMENT '国家',
|
||||||
|
province VARCHAR(64) COMMENT '省份',
|
||||||
|
city VARCHAR(64) COMMENT '城市',
|
||||||
|
label_list TEXT COMMENT '标签列表',
|
||||||
|
phone_num_list TEXT COMMENT '电话号码列表',
|
||||||
|
py_initial VARCHAR(128) COMMENT '拼音首字母',
|
||||||
|
quan_pin VARCHAR(256) COMMENT '全拼',
|
||||||
|
remark_py_initial VARCHAR(128) COMMENT '备注拼音首字母',
|
||||||
|
remark_quan_pin VARCHAR(256) COMMENT '备注全拼',
|
||||||
|
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
UNIQUE KEY `idx_chatroom_member` (`chatroom_id`, `wxid`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信群成员信息表';
|
||||||
|
"""
|
||||||
|
cursor.execute(sql_chatroom_member)
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
logger.info("成功创建或确认微信联系人表和群成员表存在")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"创建微信联系人表或群成员表失败: {e}")
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
if 'cursor' in locals():
|
||||||
|
cursor.close()
|
||||||
|
if 'conn' in locals():
|
||||||
|
self.db_manager.release_connection(conn)
|
||||||
|
|
||||||
|
def save_contacts(self, contacts_data: List[Dict], contact_type: str) -> bool:
|
||||||
|
"""保存联系人信息到数据库
|
||||||
|
|
||||||
|
Args:
|
||||||
|
contacts_data: 联系人数据列表
|
||||||
|
contact_type: 联系人类型,可选值:'friends', 'chatrooms', 'ghs'
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功保存
|
||||||
|
"""
|
||||||
|
if not contacts_data:
|
||||||
|
logger.warning(f"没有{contact_type}类型的联系人数据需要保存")
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = self.db_manager.get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
for contact in contacts_data:
|
||||||
|
# 将驼峰命名转换为下划线命名
|
||||||
|
data = {
|
||||||
|
'user_name': contact.get('userName', ''),
|
||||||
|
'nick_name': contact.get('nickName', ''),
|
||||||
|
'py_initial': contact.get('pyInitial', ''),
|
||||||
|
'quan_pin': contact.get('quanPin', ''),
|
||||||
|
'sex': contact.get('sex', 0),
|
||||||
|
'remark': contact.get('remark', ''),
|
||||||
|
'remark_py_initial': contact.get('remarkPyInitial', ''),
|
||||||
|
'remark_quan_pin': contact.get('remarkQuanPin', ''),
|
||||||
|
'signature': contact.get('signature', ''),
|
||||||
|
'alias': contact.get('alias', ''),
|
||||||
|
'sns_bg_img': contact.get('snsBgImg', ''),
|
||||||
|
'country': contact.get('country', ''),
|
||||||
|
'province': contact.get('province', ''),
|
||||||
|
'city': contact.get('city', ''),
|
||||||
|
'big_head_img_url': contact.get('bigHeadImgUrl', ''),
|
||||||
|
'small_head_img_url': contact.get('smallHeadImgUrl', ''),
|
||||||
|
'description': contact.get('description', ''),
|
||||||
|
'card_img_url': contact.get('cardImgUrl', ''),
|
||||||
|
'label_list': contact.get('labelList', ''),
|
||||||
|
'phone_num_list': json.dumps(contact.get('phoneNumList', [])) if contact.get('phoneNumList') else '',
|
||||||
|
'type': contact_type
|
||||||
|
}
|
||||||
|
|
||||||
|
# 构建SQL语句
|
||||||
|
fields = ', '.join(data.keys())
|
||||||
|
placeholders = ', '.join(['%s'] * len(data))
|
||||||
|
values = tuple(data.values())
|
||||||
|
|
||||||
|
# 使用INSERT ... ON DUPLICATE KEY UPDATE语法
|
||||||
|
update_clause = ', '.join([f"{k}=VALUES({k})" for k in data.keys() if k != 'user_name'])
|
||||||
|
|
||||||
|
sql = f"""
|
||||||
|
INSERT INTO t_wechat_contacts ({fields})
|
||||||
|
VALUES ({placeholders})
|
||||||
|
ON DUPLICATE KEY UPDATE {update_clause}
|
||||||
|
"""
|
||||||
|
|
||||||
|
cursor.execute(sql, values)
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
logger.info(f"成功保存{len(contacts_data)}个{contact_type}类型的联系人")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"保存{contact_type}类型的联系人失败: {e}")
|
||||||
|
if 'conn' in locals():
|
||||||
|
conn.rollback()
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
if 'cursor' in locals():
|
||||||
|
cursor.close()
|
||||||
|
if 'conn' in locals():
|
||||||
|
self.db_manager.release_connection(conn)
|
||||||
|
|
||||||
|
def save_simple_contacts(self, contact_list: List[str], contact_type: str) -> bool:
|
||||||
|
"""保存简单联系人列表(只有user_name)到数据库
|
||||||
|
|
||||||
|
Args:
|
||||||
|
contact_list: 联系人ID列表
|
||||||
|
contact_type: 联系人类型,可选值:'friends', 'chatrooms', 'ghs'
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功保存
|
||||||
|
"""
|
||||||
|
if not contact_list:
|
||||||
|
logger.warning(f"没有{contact_type}类型的联系人数据需要保存")
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = self.db_manager.get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
for user_name in contact_list:
|
||||||
|
# 构建SQL语句
|
||||||
|
sql = """
|
||||||
|
INSERT INTO t_wechat_contacts (user_name, type)
|
||||||
|
VALUES (%s, %s)
|
||||||
|
ON DUPLICATE KEY UPDATE type = VALUES(type), update_time = CURRENT_TIMESTAMP
|
||||||
|
"""
|
||||||
|
|
||||||
|
cursor.execute(sql, (user_name, contact_type))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
logger.info(f"成功保存{len(contact_list)}个{contact_type}类型的简单联系人")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"保存{contact_type}类型的简单联系人失败: {e}")
|
||||||
|
if 'conn' in locals():
|
||||||
|
conn.rollback()
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
if 'cursor' in locals():
|
||||||
|
cursor.close()
|
||||||
|
if 'conn' in locals():
|
||||||
|
self.db_manager.release_connection(conn)
|
||||||
|
|
||||||
|
def get_contacts_by_type(self, contact_type: str) -> List[Dict]:
|
||||||
|
"""根据类型获取联系人列表
|
||||||
|
|
||||||
|
Args:
|
||||||
|
contact_type: 联系人类型,可选值:'friends', 'chatrooms', 'ghs'
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict]: 联系人列表
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
conn = self.db_manager.get_connection()
|
||||||
|
cursor = conn.cursor(dictionary=True)
|
||||||
|
|
||||||
|
sql = """
|
||||||
|
SELECT * FROM t_wechat_contacts
|
||||||
|
WHERE type = %s
|
||||||
|
ORDER BY nick_name
|
||||||
|
"""
|
||||||
|
|
||||||
|
cursor.execute(sql, (contact_type,))
|
||||||
|
results = cursor.fetchall()
|
||||||
|
|
||||||
|
return results
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取{contact_type}类型的联系人失败: {e}")
|
||||||
|
return []
|
||||||
|
finally:
|
||||||
|
if 'cursor' in locals():
|
||||||
|
cursor.close()
|
||||||
|
if 'conn' in locals():
|
||||||
|
self.db_manager.release_connection(conn)
|
||||||
|
|
||||||
|
def get_contact_by_user_name(self, user_name: str) -> Optional[Dict]:
|
||||||
|
"""根据user_name获取联系人信息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_name: 联系人ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[Dict]: 联系人信息,如果不存在则返回None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
conn = self.db_manager.get_connection()
|
||||||
|
cursor = conn.cursor(dictionary=True)
|
||||||
|
|
||||||
|
sql = """
|
||||||
|
SELECT * FROM t_wechat_contacts
|
||||||
|
WHERE user_name = %s
|
||||||
|
LIMIT 1
|
||||||
|
"""
|
||||||
|
|
||||||
|
cursor.execute(sql, (user_name,))
|
||||||
|
result = cursor.fetchone()
|
||||||
|
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取联系人{user_name}失败: {e}")
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
if 'cursor' in locals():
|
||||||
|
cursor.close()
|
||||||
|
if 'conn' in locals():
|
||||||
|
self.db_manager.release_connection(conn)
|
||||||
|
|
||||||
|
def get_display_name(self, user_name: str) -> str:
|
||||||
|
"""获取联系人的显示名称(优先使用备注,其次是昵称,最后是微信ID)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_name: 联系人ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 显示名称
|
||||||
|
"""
|
||||||
|
contact = self.get_contact_by_user_name(user_name)
|
||||||
|
if not contact:
|
||||||
|
return user_name
|
||||||
|
|
||||||
|
return contact.get('remark') or contact.get('nick_name') or user_name
|
||||||
|
|
||||||
|
def get_all_contacts_name_map(self) -> Dict[str, str]:
|
||||||
|
"""获取所有联系人的ID到显示名称的映射
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, str]: 联系人ID到显示名称的映射
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
conn = self.db_manager.get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
sql = """
|
||||||
|
SELECT user_name, remark, nick_name FROM t_wechat_contacts
|
||||||
|
"""
|
||||||
|
|
||||||
|
cursor.execute(sql)
|
||||||
|
results = cursor.fetchall()
|
||||||
|
|
||||||
|
name_map = {}
|
||||||
|
for user_name, remark, nick_name in results:
|
||||||
|
display_name = remark or nick_name or user_name
|
||||||
|
name_map[user_name] = display_name
|
||||||
|
|
||||||
|
return name_map
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取所有联系人名称映射失败: {e}")
|
||||||
|
return {}
|
||||||
|
finally:
|
||||||
|
if 'cursor' in locals():
|
||||||
|
cursor.close()
|
||||||
|
if 'conn' in locals():
|
||||||
|
self.db_manager.release_connection(conn)
|
||||||
|
|
||||||
|
def save_chatroom_member_detail(self, chatroom_id: str, member_details: List[Dict]) -> bool:
|
||||||
|
"""保存群成员详细信息到数据库
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chatroom_id: 群聊ID
|
||||||
|
member_details: 群成员详细信息列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功保存
|
||||||
|
"""
|
||||||
|
if not member_details or not chatroom_id:
|
||||||
|
logger.warning(f"没有群聊{chatroom_id}的成员详细信息需要保存")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = self.db_manager.get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# 获取现有的群成员信息,以便更新而不是替换
|
||||||
|
existing_members_sql = """
|
||||||
|
SELECT wxid, is_owner, is_admin FROM t_chatroom_member
|
||||||
|
WHERE chatroom_id = %s
|
||||||
|
"""
|
||||||
|
cursor.execute(existing_members_sql, (chatroom_id,))
|
||||||
|
existing_members = {row[0]: (row[1], row[2]) for row in cursor.fetchall()}
|
||||||
|
|
||||||
|
for member in member_details:
|
||||||
|
wxid = member.get('userName', '')
|
||||||
|
if not wxid:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 保留现有的群主和管理员标识
|
||||||
|
is_owner, is_admin = 0, 0
|
||||||
|
if wxid in existing_members:
|
||||||
|
is_owner, is_admin = existing_members[wxid]
|
||||||
|
|
||||||
|
# 处理电话号码列表
|
||||||
|
phone_num_list = member.get('phoneNumList', [])
|
||||||
|
if phone_num_list:
|
||||||
|
phone_num_str = json.dumps(phone_num_list)
|
||||||
|
else:
|
||||||
|
phone_num_str = ''
|
||||||
|
|
||||||
|
# 构建数据
|
||||||
|
data = {
|
||||||
|
'chatroom_id': chatroom_id,
|
||||||
|
'wxid': wxid,
|
||||||
|
'nick_name': member.get('nickName', ''),
|
||||||
|
'display_name': member.get('remark', ''), # 使用备注作为群内显示名称
|
||||||
|
'inviter_user_name': member.get('inviterUserName', ''),
|
||||||
|
'member_flag': member.get('memberFlag', 0),
|
||||||
|
'big_head_img_url': member.get('bigHeadImgUrl', ''),
|
||||||
|
'small_head_img_url': member.get('smallHeadImgUrl', ''),
|
||||||
|
'is_owner': is_owner,
|
||||||
|
'is_admin': is_admin,
|
||||||
|
# 额外的详细信息字段
|
||||||
|
'sex': member.get('sex', 0),
|
||||||
|
'signature': member.get('signature', ''),
|
||||||
|
'alias': member.get('alias', ''),
|
||||||
|
'country': member.get('country', ''),
|
||||||
|
'province': member.get('province', ''),
|
||||||
|
'city': member.get('city', ''),
|
||||||
|
'label_list': member.get('labelList', ''),
|
||||||
|
'phone_num_list': phone_num_str,
|
||||||
|
'py_initial': member.get('pyInitial', ''),
|
||||||
|
'quan_pin': member.get('quanPin', ''),
|
||||||
|
'remark_py_initial': member.get('remarkPyInitial', ''),
|
||||||
|
'remark_quan_pin': member.get('remarkQuanPin', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
# 构建SQL语句 - 使用REPLACE INTO确保更新现有记录
|
||||||
|
fields = ', '.join(data.keys())
|
||||||
|
placeholders = ', '.join(['%s'] * len(data))
|
||||||
|
values = tuple(data.values())
|
||||||
|
|
||||||
|
sql = f"""
|
||||||
|
REPLACE INTO t_chatroom_member ({fields})
|
||||||
|
VALUES ({placeholders})
|
||||||
|
"""
|
||||||
|
|
||||||
|
cursor.execute(sql, values)
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
logger.info(f"成功保存群聊{chatroom_id}的{len(member_details)}个成员详细信息")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"保存群聊{chatroom_id}的成员详细信息失败: {e}")
|
||||||
|
if 'conn' in locals():
|
||||||
|
conn.rollback()
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
if 'cursor' in locals():
|
||||||
|
cursor.close()
|
||||||
|
if 'conn' in locals():
|
||||||
|
self.db_manager.release_connection(conn)
|
||||||
|
|
||||||
|
def process_chatroom_member_detail_response(self, chatroom_id: str, response: Dict) -> bool:
|
||||||
|
"""处理获取群成员详情的API响应
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chatroom_id: 群聊ID
|
||||||
|
response: API响应数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功处理
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if response.get('ret') != 200:
|
||||||
|
logger.error(f"获取群聊{chatroom_id}成员详情失败: {response.get('msg')}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
data = response.get('data', [])
|
||||||
|
if not data:
|
||||||
|
logger.warning(f"群聊{chatroom_id}成员详情数据为空")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.save_chatroom_member_detail(chatroom_id, data)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"处理群聊{chatroom_id}成员详情数据失败: {e}")
|
||||||
|
return False
|
||||||
@@ -3,10 +3,9 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from wcferry import WxMsg
|
|
||||||
|
|
||||||
from db.base import BaseDBOperator
|
from db.base import BaseDBOperator
|
||||||
from db.connection import DBConnectionManager
|
from db.connection import DBConnectionManager
|
||||||
|
from gewechat.call_back_message.message import WxMessage
|
||||||
|
|
||||||
|
|
||||||
class MessageStorageDB(BaseDBOperator):
|
class MessageStorageDB(BaseDBOperator):
|
||||||
@@ -15,14 +14,15 @@ class MessageStorageDB(BaseDBOperator):
|
|||||||
def __init__(self, db_manager: DBConnectionManager):
|
def __init__(self, db_manager: DBConnectionManager):
|
||||||
super().__init__(db_manager)
|
super().__init__(db_manager)
|
||||||
|
|
||||||
def archive_message(self, msg: WxMsg) -> bool:
|
def archive_message(self, msg: WxMessage) -> bool:
|
||||||
"""存档消息"""
|
"""存档消息"""
|
||||||
now_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
now_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
sql = """
|
sql = """
|
||||||
INSERT INTO messages (group_id, timestamp, sender, content, message_type, attachment_url, message_id, message_xml, message_thumb)
|
INSERT INTO messages (group_id, timestamp, sender, content, message_type, attachment_url, message_id, message_xml, message_thumb)
|
||||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
"""
|
"""
|
||||||
params = (msg.roomid, now_time, msg.sender, msg.content, msg.type, msg.extra, msg.id, msg.xml, msg.thumb)
|
params = (
|
||||||
|
msg.roomid, now_time, msg.sender, msg.content, msg.msg_type, msg.content, msg.msg_id, msg.msg_source, "")
|
||||||
result = self.execute_update(sql, params)
|
result = self.execute_update(sql, params)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -51,7 +51,6 @@ class MessageStorageDB(BaseDBOperator):
|
|||||||
"""
|
"""
|
||||||
return self.execute_query(sql, (date,)) or []
|
return self.execute_query(sql, (date,)) or []
|
||||||
|
|
||||||
|
|
||||||
def get_speech_ranking(self, date: str, group_id: str, limit: int = 20) -> List[Dict]:
|
def get_speech_ranking(self, date: str, group_id: str, limit: int = 20) -> List[Dict]:
|
||||||
"""获取指定日期和群组的发言排名"""
|
"""获取指定日期和群组的发言排名"""
|
||||||
sql = """
|
sql = """
|
||||||
@@ -87,7 +86,6 @@ class MessageStorageDB(BaseDBOperator):
|
|||||||
params = (group_id, wx_id, date, count)
|
params = (group_id, wx_id, date, count)
|
||||||
return self.execute_update(sql, params)
|
return self.execute_update(sql, params)
|
||||||
|
|
||||||
|
|
||||||
def get_message_trend(self, group_id: str, days: int = 7) -> List[Dict]:
|
def get_message_trend(self, group_id: str, days: int = 7) -> List[Dict]:
|
||||||
"""获取指定群组的消息趋势数据
|
"""获取指定群组的消息趋势数据
|
||||||
|
|
||||||
@@ -111,7 +109,7 @@ class MessageStorageDB(BaseDBOperator):
|
|||||||
return self.execute_query(sql, (group_id, days)) or []
|
return self.execute_query(sql, (group_id, days)) or []
|
||||||
|
|
||||||
def get_messages_by_filter(self, group_id=None, start_date=None, end_date=None,
|
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:
|
Args:
|
||||||
|
|||||||
0
gewechat/__init__.py
Normal file
0
gewechat/__init__.py
Normal file
122
gewechat/api/callback.py
Normal file
122
gewechat/api/callback.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
from fastapi import APIRouter, Request
|
||||||
|
from gewechat.call_back_message.message import WxMessage, MessageType, AppMessageType
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from robot import Robot
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# 存储Robot实例的字典,以appid为键
|
||||||
|
robot_instances = {}
|
||||||
|
|
||||||
|
|
||||||
|
def register_robot(appid, robot_instance):
|
||||||
|
"""注册Robot实例"""
|
||||||
|
robot_instances[appid] = robot_instance
|
||||||
|
logger.info(f"已注册appid为{appid}的Robot实例")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/gewechat/callback")
|
||||||
|
async def callback(request: Request):
|
||||||
|
"""接收微信消息回调"""
|
||||||
|
try:
|
||||||
|
# 获取原始JSON数据
|
||||||
|
json_data = await request.json()
|
||||||
|
|
||||||
|
# 创建消息对象
|
||||||
|
msg = WxMessage.from_json(json_data)
|
||||||
|
|
||||||
|
# 根据消息类型处理
|
||||||
|
if msg.type_name == "AddMsg":
|
||||||
|
await handle_add_message(msg)
|
||||||
|
elif msg.type_name == "ModContacts":
|
||||||
|
await handle_mod_contacts(msg)
|
||||||
|
elif msg.type_name == "DelContacts":
|
||||||
|
await handle_del_contacts(msg)
|
||||||
|
elif msg.type_name == "Offline":
|
||||||
|
await handle_offline(msg)
|
||||||
|
|
||||||
|
return {"code": 0, "message": "success"}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"处理回调消息失败: {str(e)}", exc_info=True)
|
||||||
|
return {"code": -1, "message": f"处理失败: {str(e)}"}
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_add_message(msg: WxMessage):
|
||||||
|
"""处理新消息"""
|
||||||
|
try:
|
||||||
|
# 获取对应的Robot实例
|
||||||
|
robot: Robot = robot_instances.get(msg.appid)
|
||||||
|
if robot:
|
||||||
|
# 调用Robot的onMsg方法处理消息
|
||||||
|
robot.onMsg(msg)
|
||||||
|
else:
|
||||||
|
logger.warning(f"未找到appid为{msg.appid}的Robot实例")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"处理新消息失败: {str(e)}", exc_info=True)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_mod_contacts(msg: WxMessage):
|
||||||
|
"""处理联系人变更"""
|
||||||
|
logger.info(f"联系人信息变更: {msg.raw_data}")
|
||||||
|
# 获取对应的Robot实例并刷新联系人
|
||||||
|
robot = robot_instances.get(msg.appid)
|
||||||
|
if robot:
|
||||||
|
robot.refresh_contacts()
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_del_contacts(msg: WxMessage):
|
||||||
|
"""处理联系人删除"""
|
||||||
|
logger.info(f"联系人被删除: {msg.raw_data}")
|
||||||
|
# 获取对应的Robot实例并刷新联系人
|
||||||
|
robot = robot_instances.get(msg.appid)
|
||||||
|
if robot:
|
||||||
|
robot.refresh_contacts()
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_offline(msg: WxMessage):
|
||||||
|
"""处理离线通知"""
|
||||||
|
logger.info(f"账号离线: {msg.wxid}")
|
||||||
|
# 可以在这里处理账号离线逻辑
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_text_message(msg: WxMessage):
|
||||||
|
"""处理文本消息"""
|
||||||
|
logger.info(f"收到文本消息: {msg.content.raw_content}")
|
||||||
|
# TODO: 实现文本消息处理逻辑
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_image_message(msg: WxMessage):
|
||||||
|
"""处理图片消息"""
|
||||||
|
image_content = msg.get_image_content()
|
||||||
|
if image_content:
|
||||||
|
logger.info(f"收到图片消息: {image_content.url}")
|
||||||
|
# TODO: 实现图片消息处理逻辑
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_app_message(msg: WxMessage):
|
||||||
|
"""处理应用消息"""
|
||||||
|
app_type = msg.get_app_message_type()
|
||||||
|
if app_type == AppMessageType.LINK:
|
||||||
|
logger.info("收到链接消息")
|
||||||
|
elif app_type == AppMessageType.FILE:
|
||||||
|
logger.info("收到文件消息")
|
||||||
|
elif app_type == AppMessageType.MINIPROGRAM:
|
||||||
|
logger.info("收到小程序消息")
|
||||||
|
# TODO: 实现应用消息处理逻辑
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_system_message(msg: WxMessage):
|
||||||
|
"""处理系统消息"""
|
||||||
|
logger.info(f"收到系统消息: {msg.content.raw_content}")
|
||||||
|
# TODO: 实现系统消息处理逻辑
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_system_notify(msg: WxMessage):
|
||||||
|
"""处理系统通知"""
|
||||||
|
logger.info(f"收到系统通知: {msg.content.raw_content}")
|
||||||
|
# TODO: 实现系统通知处理逻辑
|
||||||
85
gewechat/api/start_server.py
Normal file
85
gewechat/api/start_server.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import threading
|
||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
import uvicorn
|
||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
from gewechat.api.callback import router as callback_router
|
||||||
|
|
||||||
|
# 配置日志
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def is_port_in_use(port, host='0.0.0.0'):
|
||||||
|
"""检查端口是否被占用"""
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||||
|
try:
|
||||||
|
s.bind((host, port))
|
||||||
|
return False
|
||||||
|
except socket.error:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def start_fastapi_server(host="0.0.0.0", port=8999):
|
||||||
|
"""启动FastAPI服务器"""
|
||||||
|
# 检查端口是否被占用
|
||||||
|
if is_port_in_use(port, host):
|
||||||
|
logger.warning(f"端口 {port} 已被占用,尝试使用其他端口")
|
||||||
|
# 尝试其他端口
|
||||||
|
for test_port in range(9000, 9100):
|
||||||
|
if not is_port_in_use(test_port, host):
|
||||||
|
port = test_port
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
logger.error("无法找到可用端口,服务器启动失败")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
app = FastAPI()
|
||||||
|
app.include_router(callback_router)
|
||||||
|
|
||||||
|
# 添加健康检查路由
|
||||||
|
@app.get("/health")
|
||||||
|
async def health_check():
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
logger.info(f"正在启动FastAPI服务器,地址: http://{host}:{port}")
|
||||||
|
|
||||||
|
# 使用线程启动uvicorn服务器
|
||||||
|
server_thread = threading.Thread(
|
||||||
|
target=uvicorn.run,
|
||||||
|
args=(app,),
|
||||||
|
kwargs={"host": host, "port": port, "log_level": "info"},
|
||||||
|
daemon=True
|
||||||
|
)
|
||||||
|
server_thread.start()
|
||||||
|
logger.info(f"FastAPI 服务已在 http://{host}:{port} 启动")
|
||||||
|
logger.info(f"回调URL: http://{host}:{port}/gewechat/callback")
|
||||||
|
|
||||||
|
# 返回启动的端口,以便调用者知道实际使用的端口
|
||||||
|
return port
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"启动FastAPI服务器失败: {e}", exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# 配置日志
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
|
||||||
|
# 启动服务器
|
||||||
|
port = start_fastapi_server()
|
||||||
|
if port:
|
||||||
|
print(f"服务器启动成功,端口: {port}")
|
||||||
|
print(f"回调URL: http://localhost:{port}/gewechat/callback")
|
||||||
|
print(f"健康检查URL: http://localhost:{port}/health")
|
||||||
|
|
||||||
|
# 保持主线程运行
|
||||||
|
try:
|
||||||
|
import time
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("服务器已停止")
|
||||||
|
else:
|
||||||
|
print("服务器启动失败")
|
||||||
299
gewechat/call_back_message/message.py
Normal file
299
gewechat/call_back_message/message.py
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
from enum import Enum
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class MessageType(Enum):
|
||||||
|
"""消息类型枚举"""
|
||||||
|
TEXT = 1 # 文本消息
|
||||||
|
IMAGE = 3 # 图片消息
|
||||||
|
VOICE = 34 # 语音消息
|
||||||
|
VIDEO = 43 # 视频消息
|
||||||
|
EMOJI = 47 # emoji表情
|
||||||
|
LOCATION = 48 # 地理位置
|
||||||
|
APP = 49 # 应用消息(链接、文件、小程序等)
|
||||||
|
SYSTEM = 10000 # 系统消息
|
||||||
|
SYSTEM_NOTIFY = 10002 # 系统通知
|
||||||
|
|
||||||
|
|
||||||
|
class AppMessageType(Enum):
|
||||||
|
"""应用消息类型枚举"""
|
||||||
|
LINK = 5 # 链接消息
|
||||||
|
FILE = 6 # 文件消息
|
||||||
|
FILE_NOTICE = 74 # 文件上传通知
|
||||||
|
MINIPROGRAM = 33 # 小程序消息
|
||||||
|
QUOTE = 57 # 引用消息
|
||||||
|
TRANSFER = 2000 # 转账消息
|
||||||
|
RED_PACKET = 2001 # 红包消息
|
||||||
|
CHANNELS = 51 # 视频号消息
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MessageContent:
|
||||||
|
"""消息内容"""
|
||||||
|
raw_content: str # 原始内容
|
||||||
|
xml_content: Optional[ET.Element] = None # XML内容(如果有)
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
"""处理XML内容"""
|
||||||
|
if self.raw_content.startswith('<?xml') or self.raw_content.startswith('<msg'):
|
||||||
|
try:
|
||||||
|
self.xml_content = ET.fromstring(self.raw_content)
|
||||||
|
except ET.ParseError:
|
||||||
|
self.xml_content = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ImageContent:
|
||||||
|
"""图片消息特定内容"""
|
||||||
|
aes_key: str
|
||||||
|
url: str
|
||||||
|
length: int
|
||||||
|
md5: str
|
||||||
|
thumb_base64: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class VoiceContent:
|
||||||
|
"""语音消息特定内容"""
|
||||||
|
voice_length: int
|
||||||
|
aes_key: str
|
||||||
|
url: str
|
||||||
|
voice_base64: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class VideoContent:
|
||||||
|
"""视频消息特定内容"""
|
||||||
|
aes_key: str
|
||||||
|
video_url: str
|
||||||
|
thumb_url: str
|
||||||
|
length: int
|
||||||
|
play_length: int
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LocationContent:
|
||||||
|
"""地理位置特定内容"""
|
||||||
|
x: float # 纬度
|
||||||
|
y: float # 经度
|
||||||
|
label: str # 地址标签
|
||||||
|
poi_name: Optional[str] = None # 地点名称
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class WxMessage:
|
||||||
|
"""消息基础类"""
|
||||||
|
type_name: str
|
||||||
|
appid: str
|
||||||
|
wxid: str
|
||||||
|
msg_id: int
|
||||||
|
sender: str
|
||||||
|
to_user: str
|
||||||
|
roomid: str # 新增room_id属性
|
||||||
|
msg_type: MessageType
|
||||||
|
content: MessageContent
|
||||||
|
create_time: int
|
||||||
|
push_content: Optional[str]
|
||||||
|
new_msg_id: int
|
||||||
|
msg_seq: int
|
||||||
|
msg_source: str
|
||||||
|
raw_data: Dict[str, Any] # 原始JSON数据
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(cls, json_data: Dict[str, Any]) -> 'WxMessage':
|
||||||
|
"""从JSON数据创建消息对象"""
|
||||||
|
data = json_data.get("Data", {})
|
||||||
|
to_user = data.get("ToUserName", {}).get("string", "")
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
type_name=json_data.get("TypeName", ""),
|
||||||
|
appid=json_data.get("Appid", ""),
|
||||||
|
wxid=json_data.get("Wxid", ""),
|
||||||
|
msg_id=data.get("MsgId", 0),
|
||||||
|
sender=data.get("FromUserName", {}).get("string", ""),
|
||||||
|
to_user=to_user,
|
||||||
|
roomid=to_user if to_user.endswith("@chatroom") else "", # 设置room_id
|
||||||
|
msg_type=MessageType(data.get("MsgType", 0)),
|
||||||
|
content=MessageContent(data.get("Content", {}).get("string", "")),
|
||||||
|
create_time=data.get("CreateTime", 0),
|
||||||
|
push_content=data.get("PushContent"),
|
||||||
|
new_msg_id=data.get("NewMsgId", 0),
|
||||||
|
msg_seq=data.get("MsgSeq", 0),
|
||||||
|
msg_source=data.get("MsgSource", ""),
|
||||||
|
raw_data=json_data
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
"""返回消息的字符串表示,用于打印和日志"""
|
||||||
|
# 获取消息类型的名称
|
||||||
|
msg_type_name = self.msg_type.name if self.msg_type else "UNKNOWN"
|
||||||
|
|
||||||
|
# 处理不同类型的消息内容
|
||||||
|
content_str = ""
|
||||||
|
if self.msg_type == MessageType.TEXT:
|
||||||
|
# 文本消息直接显示内容
|
||||||
|
content_str = self.content.raw_content
|
||||||
|
elif self.msg_type == MessageType.IMAGE:
|
||||||
|
# 图片消息显示图片信息
|
||||||
|
img_content = self.get_image_content()
|
||||||
|
if img_content:
|
||||||
|
content_str = f"[图片] 大小: {img_content.length}字节, MD5: {img_content.md5}"
|
||||||
|
else:
|
||||||
|
content_str = "[图片]"
|
||||||
|
elif self.msg_type == MessageType.VOICE:
|
||||||
|
# 语音消息显示语音信息
|
||||||
|
voice_content = self.get_voice_content()
|
||||||
|
if voice_content:
|
||||||
|
content_str = f"[语音] 长度: {voice_content.voice_length}ms"
|
||||||
|
else:
|
||||||
|
content_str = "[语音]"
|
||||||
|
elif self.msg_type == MessageType.VIDEO:
|
||||||
|
# 视频消息显示视频信息
|
||||||
|
video_content = self.get_video_content()
|
||||||
|
if video_content:
|
||||||
|
content_str = f"[视频] 长度: {video_content.play_length}ms, 大小: {video_content.length}字节"
|
||||||
|
else:
|
||||||
|
content_str = "[视频]"
|
||||||
|
elif self.msg_type == MessageType.LOCATION:
|
||||||
|
# 位置消息显示位置信息
|
||||||
|
location_content = self.get_location_content()
|
||||||
|
if location_content:
|
||||||
|
content_str = f"[位置] {location_content.label}"
|
||||||
|
else:
|
||||||
|
content_str = "[位置]"
|
||||||
|
elif self.msg_type == MessageType.APP:
|
||||||
|
# 应用消息显示应用类型
|
||||||
|
app_type = self.get_app_message_type()
|
||||||
|
if app_type:
|
||||||
|
content_str = f"[应用消息] 类型: {app_type.name}"
|
||||||
|
else:
|
||||||
|
content_str = "[应用消息]"
|
||||||
|
elif self.msg_type == MessageType.EMOJI:
|
||||||
|
content_str = "[表情]"
|
||||||
|
elif self.msg_type == MessageType.SYSTEM:
|
||||||
|
content_str = f"[系统消息] {self.content.raw_content}"
|
||||||
|
elif self.msg_type == MessageType.SYSTEM_NOTIFY:
|
||||||
|
content_str = f"[系统通知] {self.content.raw_content}"
|
||||||
|
else:
|
||||||
|
# 其他类型消息
|
||||||
|
content_str = f"[未知类型消息] {self.content.raw_content[:30]}..."
|
||||||
|
|
||||||
|
# 限制内容长度,避免过长
|
||||||
|
if len(content_str) > 100:
|
||||||
|
content_str = content_str[:97] + "..."
|
||||||
|
|
||||||
|
# 构建基本信息
|
||||||
|
from_info = f"发送者: {self.sender}"
|
||||||
|
to_info = f"接收者: {self.to_user}"
|
||||||
|
|
||||||
|
# 如果是群消息,添加群信息
|
||||||
|
group_info = ""
|
||||||
|
if self.from_group():
|
||||||
|
group_info = f"群聊: {self.roomid}, "
|
||||||
|
|
||||||
|
# 构建完整的消息字符串
|
||||||
|
return (f"WxMessage[ID: {self.msg_id}, 类型: {msg_type_name}, "
|
||||||
|
f"{group_info}{from_info}, {to_info}, "
|
||||||
|
f"内容: {content_str}]")
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
"""返回消息的详细表示,用于调试"""
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
def from_self(self) -> bool:
|
||||||
|
"""判断是否是自己发送的消息"""
|
||||||
|
return self.sender == self.wxid
|
||||||
|
|
||||||
|
def from_group(self) -> bool:
|
||||||
|
return self.to_user.endswith("@chatroom")
|
||||||
|
|
||||||
|
def get_app_message_type(self) -> Optional[AppMessageType]:
|
||||||
|
"""获取应用消息类型"""
|
||||||
|
if self.msg_type != MessageType.APP or not self.content.xml_content:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
appmsg = self.content.xml_content.find('.//appmsg')
|
||||||
|
if appmsg is not None:
|
||||||
|
type_value = int(appmsg.find('type').text)
|
||||||
|
return AppMessageType(type_value)
|
||||||
|
except (AttributeError, ValueError):
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_image_content(self) -> Optional[ImageContent]:
|
||||||
|
"""获取图片消息内容"""
|
||||||
|
if self.msg_type != MessageType.IMAGE or not self.content.xml_content:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
img = self.content.xml_content.find('img')
|
||||||
|
if img is not None:
|
||||||
|
return ImageContent(
|
||||||
|
aes_key=img.get('aeskey', ''),
|
||||||
|
url=img.get('cdnthumburl', ''),
|
||||||
|
length=int(img.get('length', 0)),
|
||||||
|
md5=img.get('md5', ''),
|
||||||
|
thumb_base64=self.raw_data.get("Data", {}).get("ImgBuf", {}).get("buffer")
|
||||||
|
)
|
||||||
|
except (AttributeError, ValueError):
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_voice_content(self) -> Optional[VoiceContent]:
|
||||||
|
"""获取语音消息内容"""
|
||||||
|
if self.msg_type != MessageType.VOICE or not self.content.xml_content:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
voice = self.content.xml_content.find('.//voicemsg')
|
||||||
|
if voice is not None:
|
||||||
|
return VoiceContent(
|
||||||
|
voice_length=int(voice.get('voicelength', 0)),
|
||||||
|
aes_key=voice.get('aeskey', ''),
|
||||||
|
url=voice.get('voiceurl', ''),
|
||||||
|
voice_base64=self.raw_data.get("Data", {}).get("ImgBuf", {}).get("buffer")
|
||||||
|
)
|
||||||
|
except (AttributeError, ValueError):
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_video_content(self) -> Optional[VideoContent]:
|
||||||
|
"""获取视频消息内容"""
|
||||||
|
if self.msg_type != MessageType.VIDEO or not self.content.xml_content:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
video = self.content.xml_content.find('.//videomsg')
|
||||||
|
if video is not None:
|
||||||
|
return VideoContent(
|
||||||
|
aes_key=video.get('aeskey', ''),
|
||||||
|
video_url=video.get('cdnvideourl', ''),
|
||||||
|
thumb_url=video.get('cdnthumburl', ''),
|
||||||
|
length=int(video.get('length', 0)),
|
||||||
|
play_length=int(video.get('playlength', 0))
|
||||||
|
)
|
||||||
|
except (AttributeError, ValueError):
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_location_content(self) -> Optional[LocationContent]:
|
||||||
|
"""获取地理位置内容"""
|
||||||
|
if self.msg_type != MessageType.LOCATION or not self.content.xml_content:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
location = self.content.xml_content.find('location')
|
||||||
|
if location is not None:
|
||||||
|
return LocationContent(
|
||||||
|
x=float(location.get('x', 0)),
|
||||||
|
y=float(location.get('y', 0)),
|
||||||
|
label=location.get('label', ''),
|
||||||
|
poi_name=location.get('poiname')
|
||||||
|
)
|
||||||
|
except (AttributeError, ValueError):
|
||||||
|
pass
|
||||||
|
return None
|
||||||
1200
gewechat/call_back_message/model.md
Normal file
1200
gewechat/call_back_message/model.md
Normal file
File diff suppressed because it is too large
Load Diff
10
gewechat/client/get_token.py
Normal file
10
gewechat/client/get_token.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
url = "/tools/getTokenId"
|
||||||
|
|
||||||
|
payload={}
|
||||||
|
headers = {}
|
||||||
|
base_url="http://192.168.2.240:2531/v2/api"
|
||||||
|
response = requests.request("POST", base_url+url, headers=headers, data=payload)
|
||||||
|
|
||||||
|
print(response.text)
|
||||||
12
gewechat/client/login.py
Normal file
12
gewechat/client/login.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
import requests
|
||||||
|
def login():
|
||||||
|
|
||||||
|
url = "/tools/getTokenId"
|
||||||
|
|
||||||
|
payload = {}
|
||||||
|
headers = {}
|
||||||
|
base_url = "http://192.168.2.240:2531/v2/api"
|
||||||
|
response = requests.request("POST", base_url + url, headers=headers, data=payload)
|
||||||
|
|
||||||
|
print(response.text)
|
||||||
146
main.py
146
main.py
@@ -1,35 +1,126 @@
|
|||||||
#! /usr/bin/env python3
|
#! /usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
import logging
|
||||||
import signal
|
|
||||||
import threading
|
import threading
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
import uvicorn
|
||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
from gewechat_client import GewechatClient
|
||||||
|
|
||||||
|
import socket
|
||||||
|
# 启动FastAPI服务器
|
||||||
|
# 从callback_url中提取主机和端口
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
from configuration import Config
|
from configuration import Config
|
||||||
from constants import ChatType
|
from constants import ChatType
|
||||||
from robot import Robot, __version__
|
from robot import Robot
|
||||||
from wcferry import Wcf
|
from gewechat.api.callback import router as callback_router
|
||||||
|
|
||||||
|
# 配置日志
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def is_port_in_use(port, host='0.0.0.0'):
|
||||||
|
"""检查端口是否被占用"""
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||||
|
try:
|
||||||
|
s.bind((host, port))
|
||||||
|
return False
|
||||||
|
except socket.error:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def start_fastapi_server(host="0.0.0.0", port=8999):
|
||||||
|
"""启动FastAPI服务器"""
|
||||||
|
# 检查端口是否被占用
|
||||||
|
if is_port_in_use(port, host):
|
||||||
|
logger.warning(f"端口 {port} 已被占用,尝试使用其他端口")
|
||||||
|
# 尝试其他端口
|
||||||
|
for test_port in range(9000, 9100):
|
||||||
|
if not is_port_in_use(test_port, host):
|
||||||
|
port = test_port
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
logger.error("无法找到可用端口,服务器启动失败")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
app = FastAPI()
|
||||||
|
app.include_router(callback_router)
|
||||||
|
|
||||||
|
# 添加健康检查路由
|
||||||
|
@app.get("/health")
|
||||||
|
async def health_check():
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
logger.info(f"正在启动FastAPI服务器,地址: http://{host}:{port}")
|
||||||
|
|
||||||
|
# 使用线程启动uvicorn服务器
|
||||||
|
server_thread = threading.Thread(
|
||||||
|
target=uvicorn.run,
|
||||||
|
args=(app,),
|
||||||
|
kwargs={"host": host, "port": port, "log_level": "info"},
|
||||||
|
daemon=True
|
||||||
|
)
|
||||||
|
server_thread.start()
|
||||||
|
logger.info(f"FastAPI 服务已在 http://{host}:{port} 启动")
|
||||||
|
logger.info(f"回调URL: http://{host}:{port}/gewechat/callback")
|
||||||
|
|
||||||
|
# 返回启动的端口,以便调用者知道实际使用的端口
|
||||||
|
return port
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"启动FastAPI服务器失败: {e}", exc_info=True)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def main(chat_type: int):
|
def main(chat_type: int):
|
||||||
config = Config()
|
config = Config()
|
||||||
wcf = Wcf(debug=True)
|
base_url = config.BASE_URL
|
||||||
|
token = config.GEWECHAT_TOKEN
|
||||||
|
app_id = config.APP_ID
|
||||||
|
callback_url = config.CALLBACK_URL
|
||||||
|
send_msg_wxid = "filehelper" # 要发送消息的好友昵称
|
||||||
|
|
||||||
def handler(sig, frame):
|
parsed_url = urllib.parse.urlparse(callback_url)
|
||||||
# 在退出前先关闭插件系统
|
host = parsed_url.hostname or "0.0.0.0"
|
||||||
robot.plugin_manager.shutdown_plugins()
|
port = parsed_url.port or 8999
|
||||||
wcf.cleanup() # 退出前清理环境
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, handler)
|
# start_fastapi_server(host, port)
|
||||||
|
|
||||||
robot = Robot(config, wcf, chat_type)
|
# 创建 GewechatClient 实例
|
||||||
robot.LOG.info(f"WeChatRobot【{__version__}】成功启动···")
|
client = GewechatClient(base_url, token)
|
||||||
|
|
||||||
|
# 登录, 自动创建二维码,扫码后自动登录
|
||||||
|
app_id, error_msg = client.login(app_id=app_id)
|
||||||
|
|
||||||
|
if error_msg:
|
||||||
|
print("登录失败")
|
||||||
|
return
|
||||||
|
|
||||||
|
resp = client.set_callback(token, callback_url)
|
||||||
|
print(f"set_callback:{resp}")
|
||||||
|
|
||||||
|
# 如果启动时,配置文件中的app_id为空,那么将app_id写入配置文件
|
||||||
|
if not config.APP_ID:
|
||||||
|
# 更新配置文件中的APP_ID
|
||||||
|
config.update_config('gewechat', 'app_id', app_id)
|
||||||
|
print(f"已将新的APP_ID: {app_id} 写入配置文件")
|
||||||
|
# 同时更新当前配置对象中的APP_ID
|
||||||
|
config.APP_ID = app_id
|
||||||
|
|
||||||
|
# 创建机器人实例
|
||||||
|
robot = Robot(config, app_id, client, chat_type)
|
||||||
|
robot.LOG.info(f"WeChatRobot gewechat 成功启动···")
|
||||||
|
|
||||||
|
# # 注册Robot实例到callback模块
|
||||||
|
# from gewechat.api.callback import register_robot
|
||||||
|
# register_robot(app_id, robot)
|
||||||
|
|
||||||
# 机器人启动发送测试消息
|
# 机器人启动发送测试消息
|
||||||
robot.send_text_msg("启动成功!", "filehelper")
|
client.post_text(app_id, send_msg_wxid, "gewechat client 启动成功!")
|
||||||
|
|
||||||
# 接收消息
|
|
||||||
robot.enableReceivingMsg() # 加队列
|
|
||||||
# 每天 8:30 发送新闻
|
# 每天 8:30 发送新闻
|
||||||
robot.onEveryTime("08:30", robot.news_baidu_report_auto)
|
robot.onEveryTime("08:30", robot.news_baidu_report_auto)
|
||||||
|
|
||||||
@@ -52,21 +143,22 @@ def main(chat_type: int):
|
|||||||
|
|
||||||
# 启动Dashboard服务器
|
# 启动Dashboard服务器
|
||||||
dashboard_server = None
|
dashboard_server = None
|
||||||
try:
|
# try:
|
||||||
# 创建Dashboard服务器实例,共享robot对象
|
# # 创建Dashboard服务器实例,共享robot对象
|
||||||
from admin.dashboard.server import DashboardServer
|
# from admin.dashboard.server import DashboardServer
|
||||||
dashboard_server = DashboardServer(robot_instance=robot)
|
# dashboard_server = DashboardServer(robot_instance=robot)
|
||||||
|
#
|
||||||
# 在单独的线程中启动Dashboard服务器
|
# # 在单独的线程中启动Dashboard服务器
|
||||||
dashboard_thread = threading.Thread(target=dashboard_server.run, daemon=True)
|
# dashboard_thread = threading.Thread(target=dashboard_server.run, daemon=True)
|
||||||
dashboard_thread.start()
|
# dashboard_thread.start()
|
||||||
robot.LOG.info(f"Dashboard服务器已在 http://{dashboard_server.host}:{dashboard_server.port} 启动")
|
# robot.LOG.info(f"Dashboard服务器已在 http://{dashboard_server.host}:{dashboard_server.port} 启动")
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
robot.LOG.error(f"Dashboard服务器启动失败: {e}")
|
# robot.LOG.error(f"Dashboard服务器启动失败: {e}")
|
||||||
|
|
||||||
# 让机器人一直跑
|
# 让机器人一直跑
|
||||||
robot.keep_running_and_block_process()
|
robot.keep_running_and_block_process()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = ArgumentParser()
|
parser = ArgumentParser()
|
||||||
parser.add_argument('-c', type=int, default=0, help=f'选择模型参数序号: {ChatType.help_hint()}')
|
parser.add_argument('-c', type=int, default=0, help=f'选择模型参数序号: {ChatType.help_hint()}')
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import logging
|
import logging
|
||||||
|
import os.path
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from wcferry import Wcf
|
from gewechat_client import GewechatClient
|
||||||
|
|
||||||
|
from utils.wechat.contact_manager import ContactManager
|
||||||
|
|
||||||
|
|
||||||
class MessageUtil:
|
class MessageUtil:
|
||||||
@@ -13,8 +16,9 @@ class MessageUtil:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# 修改 MessageUtil 类的初始化方法,接受联系人管理器而不是联系人字典
|
# 修改 MessageUtil 类的初始化方法,接受联系人管理器而不是联系人字典
|
||||||
def __init__(self, wcf, contact_manager):
|
def __init__(self, app_id: str, base_url: str, client: GewechatClient, contact_manager: ContactManager):
|
||||||
self.wcf = wcf
|
self.app_id = app_id
|
||||||
|
self.client = client
|
||||||
self.contact_manager = contact_manager
|
self.contact_manager = contact_manager
|
||||||
self.LOG = logging.getLogger("MessageUtil")
|
self.LOG = logging.getLogger("MessageUtil")
|
||||||
|
|
||||||
@@ -35,19 +39,18 @@ class MessageUtil:
|
|||||||
ats = " @所有人"
|
ats = " @所有人"
|
||||||
else:
|
else:
|
||||||
wxids = at_list.split(",")
|
wxids = at_list.split(",")
|
||||||
for wxid in wxids:
|
if len(wxids) > 0:
|
||||||
# 根据 wxid 查找群昵称
|
ats += self.get_user_chatroom_nickname(receiver, wxids)
|
||||||
ats += f" @{self.wcf.get_alias_in_chatroom(wxid, receiver)}"
|
|
||||||
|
|
||||||
# {msg}{ats} 表示要发送的消息内容后面紧跟@,例如 北京天气情况为:xxx @张三
|
# {msg}{ats} 表示要发送的消息内容后面紧跟@,例如 北京天气情况为:xxx @张三
|
||||||
if ats == "":
|
if ats == "":
|
||||||
self.LOG.info(f"To {receiver}: {msg}")
|
self.LOG.info(f"To {receiver}: {msg}")
|
||||||
self.wcf.send_text(f"{msg}", receiver, at_list)
|
self.client.post_text(self.app_id, receiver, "{msg}", "")
|
||||||
else:
|
else:
|
||||||
self.LOG.info(f"To {receiver}: {ats}\r{msg}")
|
self.LOG.info(f"To {receiver}: {ats}\r{msg}")
|
||||||
self.wcf.send_text(f"{ats}\n{msg}", receiver, at_list)
|
self.client.post_text(self.app_id, receiver, f"{ats}\n{msg}", at_list)
|
||||||
|
|
||||||
def send_file(self, file_path: str, receiver: str) -> None:
|
def send_file(self, file_path: str, receiver: str) -> str:
|
||||||
"""
|
"""
|
||||||
发送文件消息
|
发送文件消息
|
||||||
|
|
||||||
@@ -58,9 +61,11 @@ class MessageUtil:
|
|||||||
time.sleep(random.uniform(0.5, 1.5))
|
time.sleep(random.uniform(0.5, 1.5))
|
||||||
|
|
||||||
self.LOG.info(f"Sending file to {receiver}: {file_path}")
|
self.LOG.info(f"Sending file to {receiver}: {file_path}")
|
||||||
self.wcf.send_file(file_path, receiver)
|
(path, filename) = os.path.split(file_path)
|
||||||
|
self.LOG.info(f"Sending file to {path}: {filename}")
|
||||||
|
return self.client.post_file(self.app_id, receiver, file_path, filename)
|
||||||
|
|
||||||
def send_image(self, image_path: str, receiver: str) -> None:
|
def send_image(self, image_path: str, receiver: str) -> str:
|
||||||
"""
|
"""
|
||||||
发送文件消息
|
发送文件消息
|
||||||
|
|
||||||
@@ -71,7 +76,7 @@ class MessageUtil:
|
|||||||
time.sleep(random.uniform(0.5, 1.5))
|
time.sleep(random.uniform(0.5, 1.5))
|
||||||
|
|
||||||
self.LOG.info(f"Sending file to {receiver}: {image_path}")
|
self.LOG.info(f"Sending file to {receiver}: {image_path}")
|
||||||
self.wcf.send_image(image_path, receiver)
|
return self.client.post_image(self.app_id, receiver, image_path)
|
||||||
|
|
||||||
def send_rich_text(self, name: str, account: str, title: str, digest: str, url: str, thumburl: str,
|
def send_rich_text(self, name: str, account: str, title: str, digest: str, url: str, thumburl: str,
|
||||||
receiver: str) -> int:
|
receiver: str) -> int:
|
||||||
@@ -101,17 +106,21 @@ class MessageUtil:
|
|||||||
time.sleep(random.uniform(0.5, 1.5))
|
time.sleep(random.uniform(0.5, 1.5))
|
||||||
|
|
||||||
self.LOG.info(f"Sending rich text to {receiver}: {title}")
|
self.LOG.info(f"Sending rich text to {receiver}: {title}")
|
||||||
return self.wcf.send_rich_text(name, account, title, digest, url, thumburl, receiver)
|
return self.client.post_link(self.app_id, receiver, title, digest, url, thumburl)
|
||||||
|
|
||||||
def update_contacts(self, contacts: dict) -> None:
|
def get_user_chatroom_nickname(self, chatroom_id: str, member_wxids: list[str]) -> str:
|
||||||
"""
|
data = self.client.get_chatroom_member_detail(self.app_id, chatroom_id, member_wxids)
|
||||||
更新联系人字典
|
nicknames_with_at = [" @" + member["nickName"] for member in data["data"] if member.get("nickName")]
|
||||||
|
return " ".join(nicknames_with_at)
|
||||||
|
|
||||||
:param contacts: 联系人字典,格式为 {"wxid": "NickName"}
|
def invite_member(self, group_id, sender):
|
||||||
"""
|
return self.client.invite_member(self.app_id, sender, group_id, "自动加群邀请")
|
||||||
self.contacts.update(contacts)
|
|
||||||
|
|
||||||
# 修改使用 allContacts 的地方,改为使用 contact_manager
|
def get_chatroom_members(self, group_id) -> dict:
|
||||||
# 例如:
|
data = self.client.get_chatroom_member_list(self.app_id, group_id)
|
||||||
# 原来的代码: nickname = self.allContacts.get(wxid, wxid)
|
members = {member["wxid"]: member["nickName"] for member in data["data"]["memberList"]}
|
||||||
# 修改为: nickname = self.contact_manager.get_nickname(wxid)
|
return members
|
||||||
|
|
||||||
|
def download_file_from_url(self, url: str, target_dir: str) -> str:
|
||||||
|
# 根据获取的文件地址,从server 下载 :http://{服务ip}:2532/download/{接口返回的文件路径}
|
||||||
|
return ""
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ class BeautyLegPlugin(MessagePluginInterface):
|
|||||||
# 发送图片
|
# 发送图片
|
||||||
random_file_path = os.path.abspath(random_file_path)
|
random_file_path = os.path.abspath(random_file_path)
|
||||||
self.LOG.info(f"BeautyLeg.random_file_path: {random_file_path}")
|
self.LOG.info(f"BeautyLeg.random_file_path: {random_file_path}")
|
||||||
result = wcf.send_file(random_file_path, (roomid if roomid else sender))
|
result = self.message_util.send_file(random_file_path, (roomid if roomid else sender))
|
||||||
self.LOG.info(f"发送图片结果: {result}")
|
self.LOG.info(f"发送图片结果: {result}")
|
||||||
return True, "发送成功"
|
return True, "发送成功"
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import time
|
|||||||
import re # 添加re模块导入
|
import re # 添加re模块导入
|
||||||
from typing import Dict, Any, List, Optional, Tuple
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
|
||||||
from wcferry import Wcf
|
|
||||||
|
|
||||||
from message_util import MessageUtil
|
from message_util import MessageUtil
|
||||||
from plugin_common.message_plugin_interface import MessagePluginInterface
|
from plugin_common.message_plugin_interface import MessagePluginInterface
|
||||||
@@ -63,7 +62,6 @@ class DifyPlugin(MessagePluginInterface):
|
|||||||
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
||||||
|
|
||||||
# 保存上下文对象
|
# 保存上下文对象
|
||||||
self.wcf = context.get("wcf")
|
|
||||||
self.event_system = context.get("event_system")
|
self.event_system = context.get("event_system")
|
||||||
self.message_util: MessageUtil = context.get("message_util")
|
self.message_util: MessageUtil = context.get("message_util")
|
||||||
self.gbm = context.get("gbm")
|
self.gbm = context.get("gbm")
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ import traceback
|
|||||||
import requests
|
import requests
|
||||||
from typing import Dict, Any, List, Optional, Tuple
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
|
||||||
from wcferry import Wcf
|
from message_util import MessageUtil
|
||||||
|
|
||||||
from plugin_common.message_plugin_interface import MessagePluginInterface
|
from plugin_common.message_plugin_interface import MessagePluginInterface
|
||||||
from plugin_common.plugin_interface import PluginStatus
|
from plugin_common.plugin_interface import PluginStatus
|
||||||
from utils.decorator.plugin_decorators import plugin_stats_decorator
|
from utils.decorator.plugin_decorators import plugin_stats_decorator
|
||||||
@@ -61,9 +60,8 @@ class DouyinParserPlugin(MessagePluginInterface):
|
|||||||
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
||||||
|
|
||||||
# 保存上下文对象
|
# 保存上下文对象
|
||||||
self.wcf = context.get("wcf")
|
|
||||||
self.event_system = context.get("event_system")
|
self.event_system = context.get("event_system")
|
||||||
self.message_util = context.get("message_util")
|
self.message_util:MessageUtil = context.get("message_util")
|
||||||
self.gbm = context.get("gbm")
|
self.gbm = context.get("gbm")
|
||||||
|
|
||||||
# 从配置中获取参数
|
# 从配置中获取参数
|
||||||
@@ -103,7 +101,6 @@ class DouyinParserPlugin(MessagePluginInterface):
|
|||||||
self.LOG.info(f"插件执行: {self.name}:{content}")
|
self.LOG.info(f"插件执行: {self.name}:{content}")
|
||||||
sender = message.get("sender")
|
sender = message.get("sender")
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
wcf: Wcf = message.get("wcf")
|
|
||||||
gbm: GroupBotManager = message.get("gbm")
|
gbm: GroupBotManager = message.get("gbm")
|
||||||
|
|
||||||
# 检查权限
|
# 检查权限
|
||||||
@@ -138,14 +135,14 @@ class DouyinParserPlugin(MessagePluginInterface):
|
|||||||
# 下载并发送文件
|
# 下载并发送文件
|
||||||
mp4_path = self._download_stream(video_url, os.path.join(self.download_dir, "douyin.mp4"))
|
mp4_path = self._download_stream(video_url, os.path.join(self.download_dir, "douyin.mp4"))
|
||||||
if mp4_path:
|
if mp4_path:
|
||||||
wcf.send_file(mp4_path, (roomid if roomid else sender))
|
self.message_util.send_file(mp4_path, (roomid if roomid else sender))
|
||||||
return True, "发送视频文件成功"
|
return True, "发送视频文件成功"
|
||||||
else:
|
else:
|
||||||
print(f"❌下载视频失败")
|
print(f"❌下载视频失败")
|
||||||
return False, "下载视频失败"
|
return False, "下载视频失败"
|
||||||
else:
|
else:
|
||||||
# 发送卡片
|
# 发送卡片
|
||||||
wcf.send_rich_text(
|
self.message_util.send_rich_text(
|
||||||
"BOT-PC直接查看",
|
"BOT-PC直接查看",
|
||||||
"gh_11",
|
"gh_11",
|
||||||
title[:30],
|
title[:30],
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import logging
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, Any, List, Optional, Tuple
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
|
||||||
from wcferry import Wcf
|
|
||||||
|
|
||||||
from message_util import MessageUtil
|
from message_util import MessageUtil
|
||||||
from plugin_common.message_plugin_interface import MessagePluginInterface
|
from plugin_common.message_plugin_interface import MessagePluginInterface
|
||||||
from plugin_common.plugin_interface import PluginStatus
|
from plugin_common.plugin_interface import PluginStatus
|
||||||
@@ -52,7 +50,6 @@ class GameTaskPlugin(MessagePluginInterface):
|
|||||||
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
||||||
|
|
||||||
# 保存上下文对象
|
# 保存上下文对象
|
||||||
self.wcf = context.get("wcf")
|
|
||||||
self.event_system = context.get("event_system")
|
self.event_system = context.get("event_system")
|
||||||
self.message_util: MessageUtil = context.get("message_util")
|
self.message_util: MessageUtil = context.get("message_util")
|
||||||
|
|
||||||
@@ -115,7 +112,6 @@ class GameTaskPlugin(MessagePluginInterface):
|
|||||||
command = content.split(" ")[0].lower()
|
command = content.split(" ")[0].lower()
|
||||||
sender = message.get("sender")
|
sender = message.get("sender")
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
wcf: Wcf = message.get("wcf")
|
|
||||||
gbm: GroupBotManager = message.get("gbm")
|
gbm: GroupBotManager = message.get("gbm")
|
||||||
all_contacts = message.get("all_contacts", {})
|
all_contacts = message.get("all_contacts", {})
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import threading
|
|||||||
import time # 添加这一行
|
import time # 添加这一行
|
||||||
from typing import Dict, Any, List, Optional, Tuple
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
|
||||||
from wcferry import Wcf
|
from message_util import MessageUtil
|
||||||
|
|
||||||
from plugin_common.message_plugin_interface import MessagePluginInterface
|
from plugin_common.message_plugin_interface import MessagePluginInterface
|
||||||
from plugin_common.plugin_interface import PluginStatus
|
from plugin_common.plugin_interface import PluginStatus
|
||||||
from utils.decorator.plugin_decorators import plugin_stats_decorator
|
from utils.decorator.plugin_decorators import plugin_stats_decorator
|
||||||
@@ -55,12 +54,13 @@ class GlobalNewsPlugin(MessagePluginInterface):
|
|||||||
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
||||||
|
|
||||||
# 保存上下文对象
|
# 保存上下文对象
|
||||||
self.wcf = context.get("wcf")
|
|
||||||
self.event_system = context.get("event_system")
|
self.event_system = context.get("event_system")
|
||||||
self.message_util = context.get("message_util")
|
self.message_util: MessageUtil = context.get("message_util")
|
||||||
|
|
||||||
self._commands = self._config.get("GlobalNews", {}).get("command", ["全球新闻", "国际新闻", "环球新闻", "政经新闻"])
|
self._commands = self._config.get("GlobalNews", {}).get("command",
|
||||||
self.command_format = self._config.get("GlobalNews", {}).get("command-format", "全球新闻 - 获取最新的全球政治经济新闻")
|
["全球新闻", "国际新闻", "环球新闻", "政经新闻"])
|
||||||
|
self.command_format = self._config.get("GlobalNews", {}).get("command-format",
|
||||||
|
"全球新闻 - 获取最新的全球政治经济新闻")
|
||||||
self.enable = self._config.get("GlobalNews", {}).get("enable", True)
|
self.enable = self._config.get("GlobalNews", {}).get("enable", True)
|
||||||
|
|
||||||
self.LOG.info(f"[{self.name}] 插件初始化完成,指令:{self._commands}")
|
self.LOG.info(f"[{self.name}] 插件初始化完成,指令:{self._commands}")
|
||||||
@@ -96,7 +96,6 @@ class GlobalNewsPlugin(MessagePluginInterface):
|
|||||||
self.LOG.info(f"插件执行: {self.name}:{content}")
|
self.LOG.info(f"插件执行: {self.name}:{content}")
|
||||||
sender = message.get("sender")
|
sender = message.get("sender")
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
wcf: Wcf = message.get("wcf")
|
|
||||||
gbm: GroupBotManager = message.get("gbm")
|
gbm: GroupBotManager = message.get("gbm")
|
||||||
|
|
||||||
# 检查权限
|
# 检查权限
|
||||||
@@ -107,26 +106,26 @@ class GlobalNewsPlugin(MessagePluginInterface):
|
|||||||
task_id = f"{sender}_{roomid}_{int(time.time())}"
|
task_id = f"{sender}_{roomid}_{int(time.time())}"
|
||||||
|
|
||||||
# 发送等待消息
|
# 发送等待消息
|
||||||
wcf.send_text("🌍正在获取全球新闻,请稍候...",
|
self.message_util.send_text("🌍正在获取全球新闻,请稍候...",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
|
|
||||||
# 启动异步任务
|
# 启动异步任务
|
||||||
self._start_news_task(task_id, sender, roomid, wcf)
|
self._start_news_task(task_id, sender, roomid)
|
||||||
|
|
||||||
return True, "新闻获取任务已启动"
|
return True, "新闻获取任务已启动"
|
||||||
|
|
||||||
def _start_news_task(self, task_id: str, sender: str, roomid: str, wcf: Wcf):
|
def _start_news_task(self, task_id: str, sender: str, roomid: str):
|
||||||
"""启动异步新闻获取任务"""
|
"""启动异步新闻获取任务"""
|
||||||
thread = threading.Thread(
|
thread = threading.Thread(
|
||||||
target=self._fetch_news_thread,
|
target=self._fetch_news_thread,
|
||||||
args=(task_id, sender, roomid, wcf)
|
args=(task_id, sender, roomid)
|
||||||
)
|
)
|
||||||
thread.daemon = True
|
thread.daemon = True
|
||||||
thread.start()
|
thread.start()
|
||||||
self._news_tasks[task_id] = thread
|
self._news_tasks[task_id] = thread
|
||||||
self.LOG.info(f"启动新闻获取任务: {task_id}")
|
self.LOG.info(f"启动新闻获取任务: {task_id}")
|
||||||
|
|
||||||
def _fetch_news_thread(self, task_id: str, sender: str, roomid: str, wcf: Wcf):
|
def _fetch_news_thread(self, task_id: str, sender: str, roomid: str):
|
||||||
"""在单独的线程中运行异步新闻获取任务"""
|
"""在单独的线程中运行异步新闻获取任务"""
|
||||||
try:
|
try:
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop()
|
||||||
@@ -138,15 +137,15 @@ class GlobalNewsPlugin(MessagePluginInterface):
|
|||||||
if news_result:
|
if news_result:
|
||||||
# 发送新闻图片
|
# 发送新闻图片
|
||||||
receiver = roomid if roomid else sender
|
receiver = roomid if roomid else sender
|
||||||
wcf.send_image(news_result, receiver)
|
self.message_util.send_image(news_result, receiver)
|
||||||
wcf.send_text("🌍全球新闻获取完成!", receiver, sender)
|
self.message_util.send_text("🌍全球新闻获取完成!", receiver, sender)
|
||||||
else:
|
else:
|
||||||
wcf.send_text("❌获取新闻失败,请稍后再试",
|
self.message_util.send_text("❌获取新闻失败,请稍后再试",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.LOG.error(f"新闻获取任务出错: {e}")
|
self.LOG.error(f"新闻获取任务出错: {e}")
|
||||||
wcf.send_text(f"❌获取新闻出错: {str(e)}",
|
self.message_util.send_text(f"❌获取新闻出错: {str(e)}",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
finally:
|
finally:
|
||||||
# 清理任务
|
# 清理任务
|
||||||
if task_id in self._news_tasks:
|
if task_id in self._news_tasks:
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import re
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, Any, List, Optional, Tuple
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
|
||||||
from wcferry import Wcf
|
|
||||||
|
|
||||||
from plugin_common.message_plugin_interface import MessagePluginInterface
|
from plugin_common.message_plugin_interface import MessagePluginInterface
|
||||||
from plugin_common.plugin_interface import PluginStatus
|
from plugin_common.plugin_interface import PluginStatus
|
||||||
@@ -45,8 +44,6 @@ class GroupAddPlugin(MessagePluginInterface):
|
|||||||
self.LOG = logging.getLogger(f"Plugin.{self.name}")
|
self.LOG = logging.getLogger(f"Plugin.{self.name}")
|
||||||
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
||||||
|
|
||||||
# 保存上下文对象
|
|
||||||
self.wcf = context.get("wcf")
|
|
||||||
# 获取群管理器
|
# 获取群管理器
|
||||||
self.gbm = context.get("gbm")
|
self.gbm = context.get("gbm")
|
||||||
|
|
||||||
@@ -99,7 +96,6 @@ class GroupAddPlugin(MessagePluginInterface):
|
|||||||
"""处理消息"""
|
"""处理消息"""
|
||||||
content = message.get("content", "")
|
content = message.get("content", "")
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
wcf: Wcf = message.get("wcf")
|
|
||||||
|
|
||||||
self.LOG.info(f"插件执行: {self.name}:{content}")
|
self.LOG.info(f"插件执行: {self.name}:{content}")
|
||||||
|
|
||||||
@@ -116,7 +112,7 @@ class GroupAddPlugin(MessagePluginInterface):
|
|||||||
welcome_message = f"🎉 欢迎 【{nickname}】 加入群聊👋 \n 🕒 {now_time} 🕒 !"
|
welcome_message = f"🎉 欢迎 【{nickname}】 加入群聊👋 \n 🕒 {now_time} 🕒 !"
|
||||||
|
|
||||||
# 发送欢迎消息
|
# 发送欢迎消息
|
||||||
wcf.send_text(welcome_message, roomid)
|
# self.me.send_text(welcome_message, roomid)
|
||||||
|
|
||||||
self.LOG.info(f"已发送欢迎消息给新成员 {nickname} 在群 {roomid}")
|
self.LOG.info(f"已发送欢迎消息给新成员 {nickname} 在群 {roomid}")
|
||||||
return True, f"已欢迎 {nickname}"
|
return True, f"已欢迎 {nickname}"
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import redis
|
|||||||
import re
|
import re
|
||||||
from typing import Dict, Any, List, Optional, Tuple
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
|
||||||
from wcferry import Wcf
|
from gewechat_client import GewechatClient
|
||||||
|
|
||||||
|
from message_util import MessageUtil
|
||||||
from plugin_common.message_plugin_interface import MessagePluginInterface
|
from plugin_common.message_plugin_interface import MessagePluginInterface
|
||||||
from plugin_common.plugin_interface import PluginStatus
|
from plugin_common.plugin_interface import PluginStatus
|
||||||
from utils.decorator.plugin_decorators import plugin_stats_decorator
|
from utils.decorator.plugin_decorators import plugin_stats_decorator
|
||||||
@@ -50,13 +51,13 @@ class GroupAutoInvitePlugin(MessagePluginInterface):
|
|||||||
self.LOG = logging.getLogger(f"Plugin.{self.name}")
|
self.LOG = logging.getLogger(f"Plugin.{self.name}")
|
||||||
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
||||||
|
|
||||||
# 保存上下文对象
|
|
||||||
self.wcf = context.get("wcf")
|
|
||||||
# 获取群管理器
|
# 获取群管理器
|
||||||
self.gbm = context.get("gbm")
|
self.gbm = context.get("gbm")
|
||||||
# 获取Redis连接池
|
# 获取Redis连接池
|
||||||
self.redis_pool = context.get("redis_pool")
|
self.redis_pool = context.get("redis_pool")
|
||||||
|
|
||||||
|
self.clent:GewechatClient = context.get("clent")
|
||||||
|
self.message_util: MessageUtil = context.get("message_util")
|
||||||
# 从配置中获取命令和启用状态
|
# 从配置中获取命令和启用状态
|
||||||
plugin_config = self._config.get("GroupAutoInvite", {})
|
plugin_config = self._config.get("GroupAutoInvite", {})
|
||||||
self._commands = plugin_config.get("command", ["#加群配置"])
|
self._commands = plugin_config.get("command", ["#加群配置"])
|
||||||
@@ -111,27 +112,26 @@ class GroupAutoInvitePlugin(MessagePluginInterface):
|
|||||||
|
|
||||||
sender = message.get("sender")
|
sender = message.get("sender")
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
wcf: Wcf = message.get("wcf")
|
|
||||||
gbm: GroupBotManager = message.get("gbm")
|
gbm: GroupBotManager = message.get("gbm")
|
||||||
|
|
||||||
# 处理加群配置命令
|
# 处理加群配置命令
|
||||||
if content.startswith("#加群配置|"):
|
if content.startswith("#加群配置|"):
|
||||||
return self._handle_config_command(content, sender, roomid, wcf, gbm)
|
return self._handle_config_command(content, sender, roomid, gbm)
|
||||||
|
|
||||||
# 处理加群请求
|
# 处理加群请求
|
||||||
match = re.search(r"^#加群\s+(\w+)$", content)
|
match = re.search(r"^#加群\s+(\w+)$", content)
|
||||||
if match:
|
if match:
|
||||||
return self._handle_join_request(match.group(1), sender, roomid, wcf, gbm)
|
return self._handle_join_request(match.group(1), sender, roomid, gbm)
|
||||||
|
|
||||||
return False, "无法处理的消息"
|
return False, "无法处理的消息"
|
||||||
|
|
||||||
def _handle_config_command(self, content: str, sender: str, roomid: str, wcf: Wcf, gbm: GroupBotManager) -> Tuple[
|
def _handle_config_command(self, content: str, sender: str, roomid: str, gbm: GroupBotManager) -> Tuple[
|
||||||
bool, Optional[str]]:
|
bool, Optional[str]]:
|
||||||
"""处理配置命令"""
|
"""处理配置命令"""
|
||||||
# 检查是否为管理员
|
# 检查是否为管理员
|
||||||
admin_list = self.gbm.get_admin_list()
|
admin_list = self.gbm.get_admin_list()
|
||||||
if sender not in admin_list:
|
if sender not in admin_list:
|
||||||
wcf.send_text("⚠️ 权限不足,只有管理员才能配置群邀请功能",
|
self.message_util.send_text("⚠️ 权限不足,只有管理员才能配置群邀请功能",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
return True, "权限不足"
|
return True, "权限不足"
|
||||||
|
|
||||||
@@ -140,10 +140,10 @@ class GroupAutoInvitePlugin(MessagePluginInterface):
|
|||||||
result = self.process_command(command)
|
result = self.process_command(command)
|
||||||
|
|
||||||
# 发送结果
|
# 发送结果
|
||||||
wcf.send_text(result, (roomid if roomid else sender), sender)
|
self.message_util.send_text(result, (roomid if roomid else sender), sender)
|
||||||
return True, "配置命令处理成功"
|
return True, "配置命令处理成功"
|
||||||
|
|
||||||
def _handle_join_request(self, key: str, sender: str, roomid: str, wcf: Wcf, gbm: GroupBotManager) -> Tuple[
|
def _handle_join_request(self, key: str, sender: str, roomid: str, gbm: GroupBotManager) -> Tuple[
|
||||||
bool, Optional[str]]:
|
bool, Optional[str]]:
|
||||||
"""处理加群请求"""
|
"""处理加群请求"""
|
||||||
try:
|
try:
|
||||||
@@ -152,29 +152,29 @@ class GroupAutoInvitePlugin(MessagePluginInterface):
|
|||||||
|
|
||||||
# 检查是否找到群ID
|
# 检查是否找到群ID
|
||||||
if isinstance(group_id, str) and "没有关联的群ID" in group_id:
|
if isinstance(group_id, str) and "没有关联的群ID" in group_id:
|
||||||
wcf.send_text(f"⚠️ 未找到关键词 '{key}' 对应的群聊", sender)
|
self.message_util.send_text(f"⚠️ 未找到关键词 '{key}' 对应的群聊", sender)
|
||||||
return True, "未找到群聊"
|
return True, "未找到群聊"
|
||||||
# 判断是否在群里面,如果在,则不添加
|
# 判断是否在群里面,如果在,则不添加
|
||||||
con = ContactManager.get_instance()
|
con = ContactManager.get_instance()
|
||||||
members = con.get_group_members(group_id)
|
members = con.get_group_members(group_id)
|
||||||
# 如果在群里面,则不添加
|
# 如果在群里面,则不添加
|
||||||
if sender in members:
|
if sender in members:
|
||||||
wcf.send_text(f"⚠️ 你已经在群聊中了,无需重复添加", sender)
|
self.message_util.send_text(f"⚠️ 你已经在群聊中了,无需重复添加", sender)
|
||||||
return True, "你已经在群聊中了"
|
return True, "你已经在群聊中了"
|
||||||
# 发送邀请
|
# 发送邀请
|
||||||
self.LOG.info(f"邀请用户 {sender} 加入群 {group_id}")
|
self.LOG.info(f"邀请用户 {sender} 加入群 {group_id}")
|
||||||
result = wcf.invite_chatroom_members(group_id, sender)
|
result = self.message_util.invite_member(group_id, sender)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
wcf.send_text(f"✅ 已发送邀请,请查看群聊邀请通知", sender)
|
self.message_util.send_text(f"✅ 已发送邀请,请查看群聊邀请通知", sender)
|
||||||
return True, "邀请发送成功"
|
return True, "邀请发送成功"
|
||||||
else:
|
else:
|
||||||
wcf.send_text(f"❌ 邀请发送失败,请稍后再试", sender)
|
self.message_util.send_text(f"❌ 邀请发送失败,请稍后再试", sender)
|
||||||
return False, "邀请发送失败"
|
return False, "邀请发送失败"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.LOG.error(f"处理加群请求出错: {e}")
|
self.LOG.error(f"处理加群请求出错: {e}")
|
||||||
wcf.send_text(f"❌ 处理加群请求出错: {e}", sender)
|
self.message_util.send_text(f"❌ 处理加群请求出错: {e}", sender)
|
||||||
return False, f"处理出错: {e}"
|
return False, f"处理出错: {e}"
|
||||||
|
|
||||||
def add_mapping(self, key, group_id):
|
def add_mapping(self, key, group_id):
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import time
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, Any, List, Optional, Tuple
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
|
||||||
from wcferry import Wcf
|
|
||||||
|
|
||||||
from plugin_common.message_plugin_interface import MessagePluginInterface
|
from plugin_common.message_plugin_interface import MessagePluginInterface
|
||||||
from plugin_common.plugin_interface import PluginStatus
|
from plugin_common.plugin_interface import PluginStatus
|
||||||
from utils.robot_cmd.robot_command import Feature, PermissionStatus, GroupBotManager
|
from utils.robot_cmd.robot_command import Feature, PermissionStatus, GroupBotManager
|
||||||
@@ -51,8 +49,6 @@ class GroupMemberChangePlugin(MessagePluginInterface):
|
|||||||
"""初始化插件"""
|
"""初始化插件"""
|
||||||
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
||||||
|
|
||||||
# 保存上下文对象
|
|
||||||
self.wcf: Wcf = context.get("wcf")
|
|
||||||
# 创建消息工具实例message_util
|
# 创建消息工具实例message_util
|
||||||
self.message_util: MessageUtil = context.get("message_util")
|
self.message_util: MessageUtil = context.get("message_util")
|
||||||
|
|
||||||
@@ -130,7 +126,7 @@ class GroupMemberChangePlugin(MessagePluginInterface):
|
|||||||
"""检查指定群的成员变化"""
|
"""检查指定群的成员变化"""
|
||||||
try:
|
try:
|
||||||
# 获取当前群成员
|
# 获取当前群成员
|
||||||
current_members = self.wcf.get_chatroom_members(group_id)
|
current_members = self.message_util.get_chatroom_members(group_id)
|
||||||
|
|
||||||
# 添加安全检查:如果获取到的成员列表为空,可能是接口异常
|
# 添加安全检查:如果获取到的成员列表为空,可能是接口异常
|
||||||
if not current_members:
|
if not current_members:
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Dict, Any, List, Optional, Tuple
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
|
||||||
from wcferry import Wcf
|
from gewechat.call_back_message.message import WxMessage
|
||||||
|
|
||||||
from message_util import MessageUtil
|
from message_util import MessageUtil
|
||||||
from plugin_common.message_plugin_interface import MessagePluginInterface
|
from plugin_common.message_plugin_interface import MessagePluginInterface
|
||||||
from plugin_common.plugin_interface import PluginStatus
|
from plugin_common.plugin_interface import PluginStatus
|
||||||
@@ -51,7 +50,6 @@ class GroupVirtualPlugin(MessagePluginInterface):
|
|||||||
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
||||||
|
|
||||||
# 保存上下文对象
|
# 保存上下文对象
|
||||||
self.wcf: Wcf = context.get("wcf")
|
|
||||||
self.event_system = context.get("event_system")
|
self.event_system = context.get("event_system")
|
||||||
self.message_util: MessageUtil = context.get("message_util")
|
self.message_util: MessageUtil = context.get("message_util")
|
||||||
|
|
||||||
@@ -99,9 +97,9 @@ class GroupVirtualPlugin(MessagePluginInterface):
|
|||||||
"""处理消息"""
|
"""处理消息"""
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
sender = message.get("sender", "")
|
sender = message.get("sender", "")
|
||||||
|
full_wx_msg: WxMessage = message.get("full_wx_msg", "")
|
||||||
# 检查是否是机器人自己发送的消息
|
# 检查是否是机器人自己发送的消息
|
||||||
if sender == self.wcf.self_wxid:
|
if full_wx_msg.from_self():
|
||||||
return False, "不转发自己的消息"
|
return False, "不转发自己的消息"
|
||||||
|
|
||||||
# 获取该群所在的所有虚拟聊天组
|
# 获取该群所在的所有虚拟聊天组
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from plugin_common.message_plugin_interface import MessagePluginInterface
|
|||||||
from plugin_common.plugin_interface import PluginStatus
|
from plugin_common.plugin_interface import PluginStatus
|
||||||
from utils.decorator.plugin_decorators import plugin_stats_decorator
|
from utils.decorator.plugin_decorators import plugin_stats_decorator
|
||||||
from utils.robot_cmd.robot_command import GroupBotManager
|
from utils.robot_cmd.robot_command import GroupBotManager
|
||||||
from wcferry import Wcf
|
|
||||||
|
|
||||||
|
|
||||||
class MessageRecallPlugin(MessagePluginInterface):
|
class MessageRecallPlugin(MessagePluginInterface):
|
||||||
@@ -44,8 +43,6 @@ class MessageRecallPlugin(MessagePluginInterface):
|
|||||||
self.LOG = logging.getLogger(f"Plugin.{self.name}")
|
self.LOG = logging.getLogger(f"Plugin.{self.name}")
|
||||||
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
||||||
|
|
||||||
# 保存上下文对象
|
|
||||||
self.wcf: Wcf = context.get("wcf")
|
|
||||||
self.event_system = context.get("event_system")
|
self.event_system = context.get("event_system")
|
||||||
self.message_util: MessageUtil = context.get("message_util")
|
self.message_util: MessageUtil = context.get("message_util")
|
||||||
|
|
||||||
@@ -92,48 +89,47 @@ class MessageRecallPlugin(MessagePluginInterface):
|
|||||||
|
|
||||||
sender = message.get("sender")
|
sender = message.get("sender")
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
wcf: Wcf = message.get("wcf")
|
|
||||||
|
|
||||||
# 检查是否是管理员
|
# 检查是否是管理员
|
||||||
admin_list = GroupBotManager.get_admin_list()
|
admin_list = GroupBotManager.get_admin_list()
|
||||||
self.LOG.info(f"admin_list={admin_list}")
|
self.LOG.info(f"admin_list={admin_list}")
|
||||||
# if sender not in admin_list:
|
# if sender not in admin_list:
|
||||||
# wcf.send_text("⚠️ 权限不足,只有管理员才能撤回消息",
|
# self.message_util.send_text("⚠️ 权限不足,只有管理员才能撤回消息",
|
||||||
# (roomid if roomid else sender), sender)
|
# (roomid if roomid else sender), sender)
|
||||||
# return True, "权限不足"
|
# return True, "权限不足"
|
||||||
|
|
||||||
# 解析命令获取消息ID
|
# 解析命令获取消息ID
|
||||||
parts = content.split(" ", 1)
|
parts = content.split(" ", 1)
|
||||||
if len(parts) < 2:
|
if len(parts) < 2:
|
||||||
wcf.send_text(f"❌命令格式错误!\n{self.command_format}",
|
self.message_util.send_text(f"❌命令格式错误!\n{self.command_format}",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
return True, "命令格式错误"
|
return True, "命令格式错误"
|
||||||
|
#
|
||||||
try:
|
# try:
|
||||||
# 从数据库里面提取可以处理的消息and StrTalker ={roomid}
|
# # 从数据库里面提取可以处理的消息and StrTalker ={roomid}
|
||||||
sql = (f"SELECT * FROM MSG where IsSender=1 and CreateTime > (strftime('%s', 'now') - 120) "
|
# sql = (f"SELECT * FROM MSG where IsSender=1 and CreateTime > (strftime('%s', 'now') - 120) "
|
||||||
f"limit {parts[1]}")
|
# f"limit {parts[1]}")
|
||||||
data = wcf.query_sql('MSG0.db', sql)
|
# data = self.message_util.query_sql('MSG0.db', sql)
|
||||||
self.LOG.info(f"SQL:{sql}\n 查询到可撤回数据: {data}")
|
# self.LOG.info(f"SQL:{sql}\n 查询到可撤回数据: {data}")
|
||||||
if not data:
|
# if not data:
|
||||||
wcf.send_text("❌ 没有可撤回的消息", (roomid if roomid else sender), sender)
|
# self.message_util.send_text("❌ 没有可撤回的消息", (roomid if roomid else sender), sender)
|
||||||
return True, "没有可撤回的消息"
|
# return True, "没有可撤回的消息"
|
||||||
for item in data:
|
# for item in data:
|
||||||
if item["MsgSvrID"]:
|
# if item["MsgSvrID"]:
|
||||||
# 调用撤回消息API
|
# # 调用撤回消息API
|
||||||
result = wcf.revoke_msg(item["MsgSvrID"])
|
# result = self.message_util.revoke_msg(item["MsgSvrID"])
|
||||||
if result:
|
# if result:
|
||||||
wcf.send_text("✅ 消息撤回成功", (roomid if roomid else sender), sender)
|
# self.message_util.send_text("✅ 消息撤回成功", (roomid if roomid else sender), sender)
|
||||||
return True, "撤回成功"
|
# return True, "撤回成功"
|
||||||
else:
|
# else:
|
||||||
wcf.send_text("❌ 消息撤回失败,可能是消息ID无效或已超过撤回时间限制(2分钟)",
|
# self.message_util.send_text("❌ 消息撤回失败,可能是消息ID无效或已超过撤回时间限制(2分钟)",
|
||||||
(roomid if roomid else sender), sender)
|
# (roomid if roomid else sender), sender)
|
||||||
return True, "撤回失败"
|
# return True, "撤回失败"
|
||||||
|
#
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
self.LOG.error(f"撤回消息出错: {e}")
|
# self.LOG.error(f"撤回消息出错: {e}")
|
||||||
wcf.send_text(f"❌ 撤回消息出错: {str(e)}", (roomid if roomid else sender), sender)
|
# self.message_util.send_text(f"❌ 撤回消息出错: {str(e)}", (roomid if roomid else sender), sender)
|
||||||
return True, f"处理出错: {e}"
|
# return True, f"处理出错: {e}"
|
||||||
|
|
||||||
def get_help(self) -> str:
|
def get_help(self) -> str:
|
||||||
"""获取插件帮助信息"""
|
"""获取插件帮助信息"""
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import logging
|
|||||||
import pytz
|
import pytz
|
||||||
from typing import Dict, Any, List, Optional, Tuple
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
|
||||||
from wcferry import Wcf
|
|
||||||
|
|
||||||
from db.connection import DBConnectionManager
|
from db.connection import DBConnectionManager
|
||||||
from message_util import MessageUtil
|
from message_util import MessageUtil
|
||||||
@@ -64,7 +63,6 @@ class MessageSignPlugin(MessagePluginInterface):
|
|||||||
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
||||||
|
|
||||||
# 保存上下文对象
|
# 保存上下文对象
|
||||||
self.wcf = context.get("wcf")
|
|
||||||
self.event_system = context.get("event_system")
|
self.event_system = context.get("event_system")
|
||||||
self.message_util: MessageUtil = context.get("message_util")
|
self.message_util: MessageUtil = context.get("message_util")
|
||||||
self.gbm = context.get("gbm")
|
self.gbm = context.get("gbm")
|
||||||
@@ -217,7 +215,6 @@ class MessageSignPlugin(MessagePluginInterface):
|
|||||||
"""处理签到请求"""
|
"""处理签到请求"""
|
||||||
sender = message.get("sender")
|
sender = message.get("sender")
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
wcf: Wcf = message.get("wcf")
|
|
||||||
all_contacts = message.get("all_contacts", {})
|
all_contacts = message.get("all_contacts", {})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from typing import Dict, Any, Tuple, Optional, List
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
from message_util import MessageUtil
|
||||||
from utils.string_utils import remove_trailing_content
|
from utils.string_utils import remove_trailing_content
|
||||||
from utils.wechat.message_to_db import MessageStorage
|
from utils.wechat.message_to_db import MessageStorage
|
||||||
from utils.compress_chat_data import compress_chat_data
|
from utils.compress_chat_data import compress_chat_data
|
||||||
@@ -54,6 +55,8 @@ class MessageSummaryPlugin(MessagePluginInterface):
|
|||||||
self._api_key = api_config.get("api_key", "app-McGLzBhBjeBCSEi7n83MtuTo")
|
self._api_key = api_config.get("api_key", "app-McGLzBhBjeBCSEi7n83MtuTo")
|
||||||
self._api_url = api_config.get("api_url", "http://192.168.2.240/v1/chat-messages")
|
self._api_url = api_config.get("api_url", "http://192.168.2.240/v1/chat-messages")
|
||||||
self.message_storage = MessageStorage()
|
self.message_storage = MessageStorage()
|
||||||
|
self.message_util: MessageUtil = context.get("message_util")
|
||||||
|
|
||||||
self.LOG.info(f"初始化 {self.name} 插件成功")
|
self.LOG.info(f"初始化 {self.name} 插件成功")
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -88,13 +91,10 @@ class MessageSummaryPlugin(MessagePluginInterface):
|
|||||||
command = content[len(self.command_prefix):].split()[0]
|
command = content[len(self.command_prefix):].split()[0]
|
||||||
if command not in self.commands:
|
if command not in self.commands:
|
||||||
return False, None
|
return False, None
|
||||||
wcf = message.get("wcf")
|
|
||||||
# 获取需要总结的内容
|
# 获取需要总结的内容
|
||||||
group_id = message.get("roomid")
|
group_id = message.get("roomid")
|
||||||
if not group_id:
|
if not group_id:
|
||||||
# 直接发送消息
|
self.message_util.send_text("只支持群聊消息总结", message.get("sender"))
|
||||||
if wcf:
|
|
||||||
wcf.send_text("只支持群聊消息总结", message.get("sender"))
|
|
||||||
return False, None
|
return False, None
|
||||||
# 权限判断
|
# 权限判断
|
||||||
gbm: GroupBotManager = message.get("gbm")
|
gbm: GroupBotManager = message.get("gbm")
|
||||||
@@ -110,18 +110,17 @@ class MessageSummaryPlugin(MessagePluginInterface):
|
|||||||
# 获取群名并处理
|
# 获取群名并处理
|
||||||
group_name = all_contacts.get(group_id, group_id)
|
group_name = all_contacts.get(group_id, group_id)
|
||||||
group_name = self._sanitize_group_name(group_name)
|
group_name = self._sanitize_group_name(group_name)
|
||||||
if wcf:
|
self.message_util.send_text("⏳群消息总结中… 😊", group_id)
|
||||||
wcf.send_text("⏳群消息总结中… 😊", group_id)
|
|
||||||
|
|
||||||
# 创建线程异步处理总结生成和发送
|
# 创建线程异步处理总结生成和发送
|
||||||
summary_thread = threading.Thread(
|
summary_thread = threading.Thread(
|
||||||
target=self._async_generate_and_send_summary,
|
target=self._async_generate_and_send_summary,
|
||||||
args=(chat_content, group_name, group_id, message)
|
args=(chat_content, group_name, group_id, message)
|
||||||
)
|
)
|
||||||
summary_thread.daemon = True # 设置为守护线程,主程序退出时线程也会退出
|
summary_thread.daemon = True # 设置为守护线程,主程序退出时线程也会退出
|
||||||
summary_thread.start()
|
summary_thread.start()
|
||||||
|
|
||||||
return True, "异步总结已启动"
|
return True, "异步总结已启动"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.LOG.error(f"处理消息总结命令失败: {e}")
|
self.LOG.error(f"处理消息总结命令失败: {e}")
|
||||||
@@ -138,17 +137,17 @@ class MessageSummaryPlugin(MessagePluginInterface):
|
|||||||
wcf = message.get("wcf")
|
wcf = message.get("wcf")
|
||||||
if wcf:
|
if wcf:
|
||||||
# if summary:
|
# if summary:
|
||||||
# wcf.send_text(f"总结已生成:\n{summary}", group_id, message.get("sender"))
|
# self.message_util.send_text(f"总结已生成:\n{summary}", group_id, message.get("sender"))
|
||||||
|
|
||||||
if image_path:
|
if image_path:
|
||||||
wcf.send_file(image_path, group_id)
|
self.message_util.send_file(image_path, group_id)
|
||||||
else:
|
else:
|
||||||
wcf.send_text("❌ 生成总结图片失败", group_id)
|
self.message_util.send_text("❌ 生成总结图片失败", group_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.LOG.error(f"异步生成总结失败: {e}")
|
self.LOG.error(f"异步生成总结失败: {e}")
|
||||||
wcf = message.get("wcf")
|
wcf = message.get("wcf")
|
||||||
if wcf:
|
if wcf:
|
||||||
wcf.send_text(f"❌ 生成总结失败: {str(e)}", group_id)
|
self.message_util.send_text(f"❌ 生成总结失败: {str(e)}", group_id)
|
||||||
|
|
||||||
def _sanitize_group_name(self, group_name: str) -> str:
|
def _sanitize_group_name(self, group_name: str) -> str:
|
||||||
"""处理群名,去除特殊字符并限制长度"""
|
"""处理群名,去除特殊字符并限制长度"""
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import requests
|
|||||||
import lz4.block as lb
|
import lz4.block as lb
|
||||||
from typing import Dict, Any, List, Optional, Tuple
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
|
||||||
from wcferry import Wcf
|
|
||||||
|
|
||||||
from plugin_common.message_plugin_interface import MessagePluginInterface
|
from plugin_common.message_plugin_interface import MessagePluginInterface
|
||||||
from plugin_common.plugin_interface import PluginStatus
|
from plugin_common.plugin_interface import PluginStatus
|
||||||
@@ -48,7 +47,6 @@ class MusicPlugin(MessagePluginInterface):
|
|||||||
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
||||||
|
|
||||||
# 保存上下文对象
|
# 保存上下文对象
|
||||||
self.wcf = context.get("wcf")
|
|
||||||
self.event_system = context.get("event_system")
|
self.event_system = context.get("event_system")
|
||||||
self.message_util = context.get("message_util")
|
self.message_util = context.get("message_util")
|
||||||
|
|
||||||
@@ -90,12 +88,11 @@ class MusicPlugin(MessagePluginInterface):
|
|||||||
command = content.split(" ")[0]
|
command = content.split(" ")[0]
|
||||||
sender = message.get("sender")
|
sender = message.get("sender")
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
wcf: Wcf = message.get("wcf")
|
|
||||||
gbm: GroupBotManager = message.get("gbm")
|
gbm: GroupBotManager = message.get("gbm")
|
||||||
|
|
||||||
# 检查命令格式
|
# 检查命令格式
|
||||||
if len(content.split(" ")) == 1:
|
if len(content.split(" ")) == 1:
|
||||||
wcf.send_text(f"❌命令格式错误!\n{self.command_format}",
|
self.message_util.send_text(f"❌命令格式错误!\n{self.command_format}",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
return False, "命令格式错误"
|
return False, "命令格式错误"
|
||||||
|
|
||||||
@@ -110,7 +107,7 @@ class MusicPlugin(MessagePluginInterface):
|
|||||||
# 搜索歌曲
|
# 搜索歌曲
|
||||||
song_info = self._search_song(user_song_name)
|
song_info = self._search_song(user_song_name)
|
||||||
if not song_info or not song_info.get("play_url"):
|
if not song_info or not song_info.get("play_url"):
|
||||||
wcf.send_text(f"❌未找到歌曲:{user_song_name}",
|
self.message_util.send_text(f"❌未找到歌曲:{user_song_name}",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
return False, "未找到歌曲"
|
return False, "未找到歌曲"
|
||||||
|
|
||||||
@@ -204,19 +201,18 @@ class MusicPlugin(MessagePluginInterface):
|
|||||||
</appinfo>
|
</appinfo>
|
||||||
<commenturl />
|
<commenturl />
|
||||||
</msg>"""
|
</msg>"""
|
||||||
|
# # 修改消息数据库里面的消息content内容
|
||||||
# 修改消息数据库里面的消息content内容
|
# text_bytes = xml_message.encode('utf-8')
|
||||||
text_bytes = xml_message.encode('utf-8')
|
# compressed_data = lb.compress(text_bytes, store_size=False).hex()
|
||||||
compressed_data = lb.compress(text_bytes, store_size=False).hex()
|
#
|
||||||
|
# data = self.message_util.query_sql('MSG0.db', "SELECT * FROM MSG where type = 49 limit 1")
|
||||||
data = wcf.query_sql('MSG0.db', "SELECT * FROM MSG where type = 49 limit 1")
|
# self.message_util.query_sql('MSG0.db',
|
||||||
wcf.query_sql('MSG0.db',
|
# f"""UPDATE MSG SET CompressContent = x'{compressed_data}', BytesExtra=x'', type=49, SubType=3,
|
||||||
f"""UPDATE MSG SET CompressContent = x'{compressed_data}', BytesExtra=x'', type=49, SubType=3,
|
# IsSender=0, TalkerId=2 WHERE MsgSvrID={data[0]['MsgSvrID']}"""
|
||||||
IsSender=0, TalkerId=2 WHERE MsgSvrID={data[0]['MsgSvrID']}"""
|
# )
|
||||||
)
|
#
|
||||||
|
# result = self.message_util.forward_msg(data[0]["MsgSvrID"], receiver)
|
||||||
result = wcf.forward_msg(data[0]["MsgSvrID"], receiver)
|
# self.LOG.info(f"插件化:点歌发送结果: {result}")
|
||||||
self.LOG.info(f"插件化:点歌发送结果: {result}")
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -94,14 +94,14 @@ class PluginManagerPlugin(MessagePluginInterface):
|
|||||||
# 检查命令格式
|
# 检查命令格式
|
||||||
parts = content.split(" ")
|
parts = content.split(" ")
|
||||||
if len(parts) == 1:
|
if len(parts) == 1:
|
||||||
wcf.send_text(f"❌命令格式错误!\n{self.command_format}", target, sender)
|
self.message_util.send_text(f"❌命令格式错误!\n{self.command_format}", target, sender)
|
||||||
return True, "命令格式错误"
|
return True, "命令格式错误"
|
||||||
# 只有使用的时候才全局获取对象。防止在预加载的时候跟主线程冲突
|
# 只有使用的时候才全局获取对象。防止在预加载的时候跟主线程冲突
|
||||||
self.plugin_registry = PluginRegistry()
|
self.plugin_registry = PluginRegistry()
|
||||||
self.plugin_manager = PluginManager().get_instance()
|
self.plugin_manager = PluginManager().get_instance()
|
||||||
# 检查权限 (只允许管理员操作)
|
# 检查权限 (只允许管理员操作)
|
||||||
if not self._is_admin(sender, gbm):
|
if not self._is_admin(sender, gbm):
|
||||||
wcf.send_text(f"❌权限不足,只有管理员可以管理插件", target, sender)
|
self.message_util.send_text(f"❌权限不足,只有管理员可以管理插件", target, sender)
|
||||||
return True, "权限不足"
|
return True, "权限不足"
|
||||||
|
|
||||||
# 解析子命令
|
# 解析子命令
|
||||||
@@ -125,14 +125,14 @@ class PluginManagerPlugin(MessagePluginInterface):
|
|||||||
if handler and (sub_command == "列表" or plugin_name):
|
if handler and (sub_command == "列表" or plugin_name):
|
||||||
return handler(wcf, sender, roomid)
|
return handler(wcf, sender, roomid)
|
||||||
else:
|
else:
|
||||||
wcf.send_text(f"❌未知命令或缺少参数!\n{self.command_format}", target, sender)
|
self.message_util.send_text(f"❌未知命令或缺少参数!\n{self.command_format}", target, sender)
|
||||||
return True, "未知命令"
|
return True, "未知命令"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
error_trace = traceback.format_exc()
|
error_trace = traceback.format_exc()
|
||||||
self.LOG.error(f"处理插件管理请求出错: {e}\n{error_trace}")
|
self.LOG.error(f"处理插件管理请求出错: {e}\n{error_trace}")
|
||||||
wcf.send_text(f"❌操作失败: {str(e)}", target, sender)
|
self.message_util.send_text(f"❌操作失败: {str(e)}", target, sender)
|
||||||
return True, f"处理出错: {e}"
|
return True, f"处理出错: {e}"
|
||||||
|
|
||||||
def _is_admin(self, user_id: str, gbm: GroupBotManager) -> bool:
|
def _is_admin(self, user_id: str, gbm: GroupBotManager) -> bool:
|
||||||
@@ -153,7 +153,7 @@ class PluginManagerPlugin(MessagePluginInterface):
|
|||||||
module_name = plugin.__class__.__module__.split('.')[-2]
|
module_name = plugin.__class__.__module__.split('.')[-2]
|
||||||
message += f"{status}-{plugin.name} [模块: {module_name}]\n"
|
message += f"{status}-{plugin.name} [模块: {module_name}]\n"
|
||||||
|
|
||||||
wcf.send_text(message, target, sender)
|
self.message_util.send_text(message, target, sender)
|
||||||
return True, "列出插件成功"
|
return True, "列出插件成功"
|
||||||
|
|
||||||
def _operate_plugin(self, plugin_name: str, wcf, sender: str, roomid: str,
|
def _operate_plugin(self, plugin_name: str, wcf, sender: str, roomid: str,
|
||||||
@@ -165,12 +165,12 @@ class PluginManagerPlugin(MessagePluginInterface):
|
|||||||
display_name, plugin = self.plugin_manager.find_plugin_by_name(plugin_name)
|
display_name, plugin = self.plugin_manager.find_plugin_by_name(plugin_name)
|
||||||
|
|
||||||
if not display_name:
|
if not display_name:
|
||||||
wcf.send_text(f"❌未找到插件 {plugin_name},请检查名称是否正确", target, sender)
|
self.message_util.send_text(f"❌未找到插件 {plugin_name},请检查名称是否正确", target, sender)
|
||||||
return True, f"未找到插件 {plugin_name}"
|
return True, f"未找到插件 {plugin_name}"
|
||||||
|
|
||||||
# 不允许操作自身(对于某些操作)
|
# 不允许操作自身(对于某些操作)
|
||||||
if display_name == self.name and operation_func in [self._unload_plugin, self._disable_plugin]:
|
if display_name == self.name and operation_func in [self._unload_plugin, self._disable_plugin]:
|
||||||
wcf.send_text(f"⚠️不能对插件管理插件自身执行此操作", target, sender)
|
self.message_util.send_text(f"⚠️不能对插件管理插件自身执行此操作", target, sender)
|
||||||
return True, "不能对插件管理插件自身执行此操作"
|
return True, "不能对插件管理插件自身执行此操作"
|
||||||
|
|
||||||
# 执行具体操作
|
# 执行具体操作
|
||||||
@@ -182,11 +182,11 @@ class PluginManagerPlugin(MessagePluginInterface):
|
|||||||
plugin = self.plugin_registry.get_plugin(plugin_name)
|
plugin = self.plugin_registry.get_plugin(plugin_name)
|
||||||
|
|
||||||
if not plugin:
|
if not plugin:
|
||||||
wcf.send_text(f"❌插件 {plugin_name} 不存在", target, sender)
|
self.message_util.send_text(f"❌插件 {plugin_name} 不存在", target, sender)
|
||||||
return True, f"插件 {plugin_name} 不存在"
|
return True, f"插件 {plugin_name} 不存在"
|
||||||
|
|
||||||
if plugin.status == PluginStatus.RUNNING:
|
if plugin.status == PluginStatus.RUNNING:
|
||||||
wcf.send_text(f"⚠️插件 {plugin_name} 已经处于启用状态", target, sender)
|
self.message_util.send_text(f"⚠️插件 {plugin_name} 已经处于启用状态", target, sender)
|
||||||
return True, f"插件 {plugin_name} 已经处于启用状态"
|
return True, f"插件 {plugin_name} 已经处于启用状态"
|
||||||
|
|
||||||
# 获取插件的模块名
|
# 获取插件的模块名
|
||||||
@@ -194,10 +194,10 @@ class PluginManagerPlugin(MessagePluginInterface):
|
|||||||
|
|
||||||
# 启动插件
|
# 启动插件
|
||||||
if self.plugin_manager.start_plugin(module_name):
|
if self.plugin_manager.start_plugin(module_name):
|
||||||
wcf.send_text(f"✅插件 {plugin_name} 启用成功", target, sender)
|
self.message_util.send_text(f"✅插件 {plugin_name} 启用成功", target, sender)
|
||||||
return True, f"插件 {plugin_name} 启用成功"
|
return True, f"插件 {plugin_name} 启用成功"
|
||||||
else:
|
else:
|
||||||
wcf.send_text(f"❌插件 {plugin_name} 启用失败", target, sender)
|
self.message_util.send_text(f"❌插件 {plugin_name} 启用失败", target, sender)
|
||||||
return False, f"插件 {plugin_name} 启用失败"
|
return False, f"插件 {plugin_name} 启用失败"
|
||||||
|
|
||||||
def _disable_plugin(self, plugin_name: str, wcf, sender: str, roomid: str) -> Tuple[bool, str]:
|
def _disable_plugin(self, plugin_name: str, wcf, sender: str, roomid: str) -> Tuple[bool, str]:
|
||||||
@@ -206,11 +206,11 @@ class PluginManagerPlugin(MessagePluginInterface):
|
|||||||
plugin = self.plugin_registry.get_plugin(plugin_name)
|
plugin = self.plugin_registry.get_plugin(plugin_name)
|
||||||
|
|
||||||
if not plugin:
|
if not plugin:
|
||||||
wcf.send_text(f"❌插件 {plugin_name} 不存在", target, sender)
|
self.message_util.send_text(f"❌插件 {plugin_name} 不存在", target, sender)
|
||||||
return True, f"插件 {plugin_name} 不存在"
|
return True, f"插件 {plugin_name} 不存在"
|
||||||
|
|
||||||
if plugin.status != PluginStatus.RUNNING:
|
if plugin.status != PluginStatus.RUNNING:
|
||||||
wcf.send_text(f"⚠️插件 {plugin_name} 已经处于禁用状态", target, sender)
|
self.message_util.send_text(f"⚠️插件 {plugin_name} 已经处于禁用状态", target, sender)
|
||||||
return True, f"插件 {plugin_name} 已经处于禁用状态"
|
return True, f"插件 {plugin_name} 已经处于禁用状态"
|
||||||
|
|
||||||
# 获取插件的模块名
|
# 获取插件的模块名
|
||||||
@@ -218,10 +218,10 @@ class PluginManagerPlugin(MessagePluginInterface):
|
|||||||
|
|
||||||
# 停止插件
|
# 停止插件
|
||||||
if self.plugin_manager.stop_plugin(module_name):
|
if self.plugin_manager.stop_plugin(module_name):
|
||||||
wcf.send_text(f"✅插件 {plugin_name} 禁用成功", target, sender)
|
self.message_util.send_text(f"✅插件 {plugin_name} 禁用成功", target, sender)
|
||||||
return True, f"插件 {plugin_name} 禁用成功"
|
return True, f"插件 {plugin_name} 禁用成功"
|
||||||
else:
|
else:
|
||||||
wcf.send_text(f"❌插件 {plugin_name} 禁用失败", target, sender)
|
self.message_util.send_text(f"❌插件 {plugin_name} 禁用失败", target, sender)
|
||||||
return False, f"插件 {plugin_name} 禁用失败"
|
return False, f"插件 {plugin_name} 禁用失败"
|
||||||
|
|
||||||
def _reload_plugin(self, plugin_name: str, wcf, sender: str, roomid: str) -> Tuple[bool, str]:
|
def _reload_plugin(self, plugin_name: str, wcf, sender: str, roomid: str) -> Tuple[bool, str]:
|
||||||
@@ -230,7 +230,7 @@ class PluginManagerPlugin(MessagePluginInterface):
|
|||||||
plugin = self.plugin_registry.get_plugin(plugin_name)
|
plugin = self.plugin_registry.get_plugin(plugin_name)
|
||||||
|
|
||||||
if not plugin:
|
if not plugin:
|
||||||
wcf.send_text(f"❌插件 {plugin_name} 不存在", target, sender)
|
self.message_util.send_text(f"❌插件 {plugin_name} 不存在", target, sender)
|
||||||
return True, f"插件 {plugin_name} 不存在"
|
return True, f"插件 {plugin_name} 不存在"
|
||||||
|
|
||||||
# 记录原插件状态
|
# 记录原插件状态
|
||||||
@@ -243,10 +243,10 @@ class PluginManagerPlugin(MessagePluginInterface):
|
|||||||
reloaded_plugin = self.plugin_manager.reload_plugin(module_name)
|
reloaded_plugin = self.plugin_manager.reload_plugin(module_name)
|
||||||
|
|
||||||
if reloaded_plugin:
|
if reloaded_plugin:
|
||||||
wcf.send_text(f"✅插件 {plugin_name} 重载成功", target, sender)
|
self.message_util.send_text(f"✅插件 {plugin_name} 重载成功", target, sender)
|
||||||
return True, f"插件 {plugin_name} 重载成功"
|
return True, f"插件 {plugin_name} 重载成功"
|
||||||
else:
|
else:
|
||||||
wcf.send_text(f"❌插件 {plugin_name} 重载失败", target, sender)
|
self.message_util.send_text(f"❌插件 {plugin_name} 重载失败", target, sender)
|
||||||
return False, f"插件 {plugin_name} 重载失败"
|
return False, f"插件 {plugin_name} 重载失败"
|
||||||
|
|
||||||
def _unload_plugin(self, plugin_name: str, wcf, sender: str, roomid: str) -> Tuple[bool, str]:
|
def _unload_plugin(self, plugin_name: str, wcf, sender: str, roomid: str) -> Tuple[bool, str]:
|
||||||
@@ -255,7 +255,7 @@ class PluginManagerPlugin(MessagePluginInterface):
|
|||||||
plugin = self.plugin_registry.get_plugin(plugin_name)
|
plugin = self.plugin_registry.get_plugin(plugin_name)
|
||||||
|
|
||||||
if not plugin:
|
if not plugin:
|
||||||
wcf.send_text(f"❌插件 {plugin_name} 不存在", target, sender)
|
self.message_util.send_text(f"❌插件 {plugin_name} 不存在", target, sender)
|
||||||
return True, f"插件 {plugin_name} 不存在"
|
return True, f"插件 {plugin_name} 不存在"
|
||||||
|
|
||||||
# 获取插件的模块名
|
# 获取插件的模块名
|
||||||
@@ -263,10 +263,10 @@ class PluginManagerPlugin(MessagePluginInterface):
|
|||||||
|
|
||||||
# 卸载插件
|
# 卸载插件
|
||||||
if self.plugin_manager.unload_plugin(module_name):
|
if self.plugin_manager.unload_plugin(module_name):
|
||||||
wcf.send_text(f"✅插件 {plugin_name} 卸载成功", target, sender)
|
self.message_util.send_text(f"✅插件 {plugin_name} 卸载成功", target, sender)
|
||||||
return True, f"插件 {plugin_name} 卸载成功"
|
return True, f"插件 {plugin_name} 卸载成功"
|
||||||
else:
|
else:
|
||||||
wcf.send_text(f"❌插件 {plugin_name} 卸载失败", target, sender)
|
self.message_util.send_text(f"❌插件 {plugin_name} 卸载失败", target, sender)
|
||||||
return False, f"插件 {plugin_name} 卸载失败"
|
return False, f"插件 {plugin_name} 卸载失败"
|
||||||
|
|
||||||
def _load_plugin(self, plugin_name: str, wcf, sender: str, roomid: str, silent: bool = False) -> Tuple[bool, str]:
|
def _load_plugin(self, plugin_name: str, wcf, sender: str, roomid: str, silent: bool = False) -> Tuple[bool, str]:
|
||||||
@@ -276,7 +276,7 @@ class PluginManagerPlugin(MessagePluginInterface):
|
|||||||
plugin_dir = os.path.join("plugins", plugin_name)
|
plugin_dir = os.path.join("plugins", plugin_name)
|
||||||
if not os.path.exists(plugin_dir):
|
if not os.path.exists(plugin_dir):
|
||||||
if not silent:
|
if not silent:
|
||||||
wcf.send_text(f"❌插件目录 {plugin_dir} 不存在",
|
self.message_util.send_text(f"❌插件目录 {plugin_dir} 不存在",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
return False, f"插件目录 {plugin_dir} 不存在"
|
return False, f"插件目录 {plugin_dir} 不存在"
|
||||||
|
|
||||||
@@ -285,7 +285,7 @@ class PluginManagerPlugin(MessagePluginInterface):
|
|||||||
existing_module_name = existing_plugin.__class__.__module__.split('.')[-2]
|
existing_module_name = existing_plugin.__class__.__module__.split('.')[-2]
|
||||||
if existing_module_name == plugin_name:
|
if existing_module_name == plugin_name:
|
||||||
if not silent:
|
if not silent:
|
||||||
wcf.send_text(f"⚠️插件 {existing_plugin.name} (模块名: {plugin_name}) 已经加载",
|
self.message_util.send_text(f"⚠️插件 {existing_plugin.name} (模块名: {plugin_name}) 已经加载",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
return True, f"插件 {existing_plugin.name} 已经加载"
|
return True, f"插件 {existing_plugin.name} 已经加载"
|
||||||
|
|
||||||
@@ -294,18 +294,18 @@ class PluginManagerPlugin(MessagePluginInterface):
|
|||||||
plugin = self.plugin_manager.load_plugin(plugin_name)
|
plugin = self.plugin_manager.load_plugin(plugin_name)
|
||||||
if plugin:
|
if plugin:
|
||||||
if not silent:
|
if not silent:
|
||||||
wcf.send_text(f"✅插件 {plugin.name} 加载成功",
|
self.message_util.send_text(f"✅插件 {plugin.name} 加载成功",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
return True, f"插件 {plugin.name} 加载成功"
|
return True, f"插件 {plugin.name} 加载成功"
|
||||||
else:
|
else:
|
||||||
if not silent:
|
if not silent:
|
||||||
wcf.send_text(f"❌插件 {plugin_name} 加载失败",
|
self.message_util.send_text(f"❌插件 {plugin_name} 加载失败",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
return False, f"插件 {plugin_name} 加载失败"
|
return False, f"插件 {plugin_name} 加载失败"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.LOG.error(f"加载插件 {plugin_name} 出错: {e}")
|
self.LOG.error(f"加载插件 {plugin_name} 出错: {e}")
|
||||||
if not silent:
|
if not silent:
|
||||||
wcf.send_text(f"❌加载插件出错: {str(e)}",
|
self.message_util.send_text(f"❌加载插件出错: {str(e)}",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
return False, f"加载插件出错: {e}"
|
return False, f"加载插件出错: {e}"
|
||||||
|
|
||||||
@@ -315,13 +315,13 @@ class PluginManagerPlugin(MessagePluginInterface):
|
|||||||
display_name, plugin = self.plugin_manager.find_plugin_by_name(plugin_name)
|
display_name, plugin = self.plugin_manager.find_plugin_by_name(plugin_name)
|
||||||
|
|
||||||
if not display_name:
|
if not display_name:
|
||||||
wcf.send_text(f"❌未找到插件 {plugin_name},请检查名称是否正确",
|
self.message_util.send_text(f"❌未找到插件 {plugin_name},请检查名称是否正确",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
return True, f"未找到插件 {plugin_name}"
|
return True, f"未找到插件 {plugin_name}"
|
||||||
|
|
||||||
plugin = self.plugin_registry.get_plugin(display_name)
|
plugin = self.plugin_registry.get_plugin(display_name)
|
||||||
if not plugin:
|
if not plugin:
|
||||||
wcf.send_text(f"❌插件 {display_name} 不存在",
|
self.message_util.send_text(f"❌插件 {display_name} 不存在",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
return True, f"插件 {display_name} 不存在"
|
return True, f"插件 {display_name} 不存在"
|
||||||
|
|
||||||
@@ -340,5 +340,5 @@ class PluginManagerPlugin(MessagePluginInterface):
|
|||||||
🔑 命令:{', '.join(plugin.commands) if hasattr(plugin, 'commands') else '无'}
|
🔑 命令:{', '.join(plugin.commands) if hasattr(plugin, 'commands') else '无'}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
wcf.send_text(message, (roomid if roomid else sender), sender)
|
self.message_util.send_text(message, (roomid if roomid else sender), sender)
|
||||||
return True, "查看插件详情成功"
|
return True, "查看插件详情成功"
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from datetime import datetime
|
|||||||
from typing import Dict, Any, List, Optional, Tuple
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
from wcferry import Wcf
|
|
||||||
|
|
||||||
from db.connection import DBConnectionManager
|
from db.connection import DBConnectionManager
|
||||||
from db.points_db import PointsDBOperator, PointSource
|
from db.points_db import PointsDBOperator, PointSource
|
||||||
@@ -54,7 +53,6 @@ class PointTradePlugin(MessagePluginInterface):
|
|||||||
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
||||||
|
|
||||||
# 保存上下文对象
|
# 保存上下文对象
|
||||||
self.wcf = context.get("wcf")
|
|
||||||
self.event_system = context.get("event_system")
|
self.event_system = context.get("event_system")
|
||||||
self.message_util: MessageUtil = context.get("message_util")
|
self.message_util: MessageUtil = context.get("message_util")
|
||||||
self.gbm = context.get("gbm")
|
self.gbm = context.get("gbm")
|
||||||
@@ -129,7 +127,6 @@ class PointTradePlugin(MessagePluginInterface):
|
|||||||
command = content.split(" ")
|
command = content.split(" ")
|
||||||
sender = message.get("sender")
|
sender = message.get("sender")
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
wcf: Wcf = message.get("wcf")
|
|
||||||
gbm: GroupBotManager = message.get("gbm")
|
gbm: GroupBotManager = message.get("gbm")
|
||||||
xml = message.get("xml", "")
|
xml = message.get("xml", "")
|
||||||
|
|
||||||
@@ -159,7 +156,6 @@ class PointTradePlugin(MessagePluginInterface):
|
|||||||
command = content.split(" ")
|
command = content.split(" ")
|
||||||
sender = message.get("sender")
|
sender = message.get("sender")
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
wcf: Wcf = message.get("wcf")
|
|
||||||
xml = message.get("xml", "")
|
xml = message.get("xml", "")
|
||||||
|
|
||||||
# 检查命令格式
|
# 检查命令格式
|
||||||
@@ -312,7 +308,6 @@ class PointTradePlugin(MessagePluginInterface):
|
|||||||
"""处理积分排行榜命令"""
|
"""处理积分排行榜命令"""
|
||||||
sender = message.get("sender")
|
sender = message.get("sender")
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
wcf: Wcf = message.get("wcf")
|
|
||||||
|
|
||||||
if not roomid:
|
if not roomid:
|
||||||
self.message_util.send_text("❌积分排行榜仅在群聊中可用!", sender, "")
|
self.message_util.send_text("❌积分排行榜仅在群聊中可用!", sender, "")
|
||||||
@@ -452,7 +447,6 @@ class PointTradePlugin(MessagePluginInterface):
|
|||||||
content = str(message.get("content", "")).strip()
|
content = str(message.get("content", "")).strip()
|
||||||
sender = message.get("sender")
|
sender = message.get("sender")
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
wcf: Wcf = message.get("wcf")
|
|
||||||
xml = message.get("xml", "")
|
xml = message.get("xml", "")
|
||||||
|
|
||||||
# 检查是否在群聊中
|
# 检查是否在群聊中
|
||||||
@@ -633,7 +627,6 @@ class PointTradePlugin(MessagePluginInterface):
|
|||||||
"""处理保释命令"""
|
"""处理保释命令"""
|
||||||
sender = message.get("sender")
|
sender = message.get("sender")
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
wcf: Wcf = message.get("wcf")
|
|
||||||
xml = message.get("xml", "")
|
xml = message.get("xml", "")
|
||||||
|
|
||||||
# 检查是否在群聊中
|
# 检查是否在群聊中
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ class SystemUpdaterPlugin(MessagePluginInterface):
|
|||||||
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
||||||
|
|
||||||
# 保存上下文对象
|
# 保存上下文对象
|
||||||
self.wcf = context.get("wcf")
|
|
||||||
self.message_util = context.get("message_util")
|
self.message_util = context.get("message_util")
|
||||||
self.config = self._config
|
self.config = self._config
|
||||||
|
|
||||||
@@ -127,12 +126,11 @@ class SystemUpdaterPlugin(MessagePluginInterface):
|
|||||||
content = str(message.get("content", "")).strip()
|
content = str(message.get("content", "")).strip()
|
||||||
sender = message.get("sender")
|
sender = message.get("sender")
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
wcf = message.get("wcf")
|
|
||||||
gbm = message.get("gbm", None)
|
gbm = message.get("gbm", None)
|
||||||
|
|
||||||
# 检查权限
|
# 检查权限
|
||||||
if self.admin_wxids and sender not in self.admin_wxids:
|
if self.admin_wxids and sender not in self.admin_wxids:
|
||||||
wcf.send_text("⚠️ 您没有执行此操作的权限",
|
self.message_util.send_text("⚠️ 您没有执行此操作的权限",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
return True, "无权限"
|
return True, "无权限"
|
||||||
|
|
||||||
@@ -156,12 +154,12 @@ class SystemUpdaterPlugin(MessagePluginInterface):
|
|||||||
|
|
||||||
# 检查win_click模块是否可用
|
# 检查win_click模块是否可用
|
||||||
if win_click is None:
|
if win_click is None:
|
||||||
wcf.send_text("⚠️ 无法执行更新操作,系统缺少必要的组件",
|
self.message_util.send_text("⚠️ 无法执行更新操作,系统缺少必要的组件",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
return True, "缺少win_click模块"
|
return True, "缺少win_click模块"
|
||||||
|
|
||||||
# 发送更新通知
|
# 发送更新通知
|
||||||
wcf.send_text(f"🔄 系统即将更新并重启,等待时间设置为{wait_time}秒...",
|
self.message_util.send_text(f"🔄 系统即将更新并重启,等待时间设置为{wait_time}秒...",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
|
|
||||||
# 启动更新流程
|
# 启动更新流程
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import os
|
|||||||
import requests
|
import requests
|
||||||
from typing import Dict, Any, List, Optional, Tuple
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
|
||||||
from wcferry import Wcf
|
|
||||||
|
|
||||||
from plugin_common.message_plugin_interface import MessagePluginInterface
|
from plugin_common.message_plugin_interface import MessagePluginInterface
|
||||||
from plugin_common.plugin_interface import PluginStatus
|
from plugin_common.plugin_interface import PluginStatus
|
||||||
@@ -50,7 +49,6 @@ class VideoPlugin(MessagePluginInterface):
|
|||||||
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
||||||
|
|
||||||
# 保存上下文对象
|
# 保存上下文对象
|
||||||
self.wcf = context.get("wcf")
|
|
||||||
self.event_system = context.get("event_system")
|
self.event_system = context.get("event_system")
|
||||||
self.message_util = context.get("message_util")
|
self.message_util = context.get("message_util")
|
||||||
self.gbm = context.get("gbm")
|
self.gbm = context.get("gbm")
|
||||||
@@ -96,7 +94,6 @@ class VideoPlugin(MessagePluginInterface):
|
|||||||
self.LOG.info(f"插件执行: {self.name}:{content}")
|
self.LOG.info(f"插件执行: {self.name}:{content}")
|
||||||
sender = message.get("sender")
|
sender = message.get("sender")
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
wcf: Wcf = message.get("wcf")
|
|
||||||
gbm: GroupBotManager = message.get("gbm")
|
gbm: GroupBotManager = message.get("gbm")
|
||||||
|
|
||||||
# 检查权限
|
# 检查权限
|
||||||
@@ -109,18 +106,18 @@ class VideoPlugin(MessagePluginInterface):
|
|||||||
file_abspath = self._download_stream("https://api.guiguiya.com/api/hook/heisis", save_path)
|
file_abspath = self._download_stream("https://api.guiguiya.com/api/hook/heisis", save_path)
|
||||||
|
|
||||||
if not file_abspath or not file_abspath.endswith("mp4"):
|
if not file_abspath or not file_abspath.endswith("mp4"):
|
||||||
wcf.send_text(f"\n❌视频下载失败,请稍后再试",
|
self.message_util.send_text(f"\n❌视频下载失败,请稍后再试",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
return False, "视频下载失败"
|
return False, "视频下载失败"
|
||||||
|
|
||||||
# 发送视频
|
# 发送视频
|
||||||
result = wcf.send_file(file_abspath, (roomid if roomid else sender))
|
result = self.message_util.send_file(file_abspath, (roomid if roomid else sender))
|
||||||
self.LOG.info(f"发送视频结果: {result}")
|
self.LOG.info(f"发送视频结果: {result}")
|
||||||
return True, "发送成功"
|
return True, "发送成功"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.LOG.error(f"处理视频请求出错: {e}")
|
self.LOG.error(f"处理视频请求出错: {e}")
|
||||||
wcf.send_text(f"\n❌请求出错:{e}",
|
self.message_util.send_text(f"\n❌请求出错:{e}",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
return False, f"处理出错: {e}"
|
return False, f"处理出错: {e}"
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import os
|
|||||||
import requests
|
import requests
|
||||||
from typing import Dict, Any, List, Optional, Tuple
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
|
||||||
from wcferry import Wcf
|
|
||||||
|
|
||||||
from message_util import MessageUtil
|
from message_util import MessageUtil
|
||||||
from plugin_common.message_plugin_interface import MessagePluginInterface
|
from plugin_common.message_plugin_interface import MessagePluginInterface
|
||||||
from plugin_common.plugin_interface import PluginStatus
|
from plugin_common.plugin_interface import PluginStatus
|
||||||
@@ -51,7 +49,6 @@ class VideoManPlugin(MessagePluginInterface):
|
|||||||
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
||||||
|
|
||||||
# 保存上下文对象
|
# 保存上下文对象
|
||||||
self.wcf = context.get("wcf")
|
|
||||||
self.event_system = context.get("event_system")
|
self.event_system = context.get("event_system")
|
||||||
self.message_util: MessageUtil = context.get("message_util")
|
self.message_util: MessageUtil = context.get("message_util")
|
||||||
self.gbm = context.get("gbm")
|
self.gbm = context.get("gbm")
|
||||||
@@ -97,7 +94,6 @@ class VideoManPlugin(MessagePluginInterface):
|
|||||||
self.LOG.info(f"插件执行: {self.name}:{content}")
|
self.LOG.info(f"插件执行: {self.name}:{content}")
|
||||||
sender = message.get("sender")
|
sender = message.get("sender")
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
wcf: Wcf = message.get("wcf")
|
|
||||||
gbm: GroupBotManager = message.get("gbm")
|
gbm: GroupBotManager = message.get("gbm")
|
||||||
|
|
||||||
# 检查权限
|
# 检查权限
|
||||||
@@ -114,7 +110,7 @@ class VideoManPlugin(MessagePluginInterface):
|
|||||||
return False, "视频下载失败"
|
return False, "视频下载失败"
|
||||||
|
|
||||||
# 发送视频
|
# 发送视频
|
||||||
result = wcf.send_file(file_abspath, (roomid if roomid else sender))
|
result = self.message_util.send_file(file_abspath, (roomid if roomid else sender))
|
||||||
self.LOG.info(f"发送视频结果: {result}")
|
self.LOG.info(f"发送视频结果: {result}")
|
||||||
return True, "发送成功"
|
return True, "发送成功"
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import os
|
|||||||
import random
|
import random
|
||||||
from typing import Dict, Any, List, Optional, Tuple
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
|
|
||||||
from wcferry import Wcf
|
|
||||||
|
|
||||||
from plugin_common.message_plugin_interface import MessagePluginInterface
|
from plugin_common.message_plugin_interface import MessagePluginInterface
|
||||||
from plugin_common.plugin_interface import PluginStatus
|
from plugin_common.plugin_interface import PluginStatus
|
||||||
from utils.decorator.plugin_decorators import plugin_stats_decorator
|
from utils.decorator.plugin_decorators import plugin_stats_decorator
|
||||||
@@ -49,7 +47,6 @@ class XiurenImagePlugin(MessagePluginInterface):
|
|||||||
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
self.LOG.info(f"正在初始化 {self.name} 插件...")
|
||||||
|
|
||||||
# 保存上下文对象
|
# 保存上下文对象
|
||||||
self.wcf = context.get("wcf")
|
|
||||||
self.event_system = context.get("event_system")
|
self.event_system = context.get("event_system")
|
||||||
self.message_util = context.get("message_util")
|
self.message_util = context.get("message_util")
|
||||||
|
|
||||||
@@ -95,7 +92,6 @@ class XiurenImagePlugin(MessagePluginInterface):
|
|||||||
self.LOG.info(f"插件执行: {self.name}:{content}")
|
self.LOG.info(f"插件执行: {self.name}:{content}")
|
||||||
sender = message.get("sender")
|
sender = message.get("sender")
|
||||||
roomid = message.get("roomid", "")
|
roomid = message.get("roomid", "")
|
||||||
wcf: Wcf = message.get("wcf")
|
|
||||||
gbm: GroupBotManager = message.get("gbm")
|
gbm: GroupBotManager = message.get("gbm")
|
||||||
|
|
||||||
# 检查权限
|
# 检查权限
|
||||||
@@ -106,12 +102,12 @@ class XiurenImagePlugin(MessagePluginInterface):
|
|||||||
# 获取随机图片
|
# 获取随机图片
|
||||||
pic_path = self._get_random_pic()
|
pic_path = self._get_random_pic()
|
||||||
if not pic_path:
|
if not pic_path:
|
||||||
wcf.send_text(f"❌未找到图片资源",
|
self.message_util.send_text(f"❌未找到图片资源",
|
||||||
(roomid if roomid else sender), sender)
|
(roomid if roomid else sender), sender)
|
||||||
return False, "未找到图片资源"
|
return False, "未找到图片资源"
|
||||||
|
|
||||||
# 发送图片
|
# 发送图片
|
||||||
result = wcf.send_file(pic_path, (roomid if roomid else sender))
|
result = self.message_util.send_file(pic_path, (roomid if roomid else sender))
|
||||||
self.LOG.info(f"发送图片结果: {result}")
|
self.LOG.info(f"发送图片结果: {result}")
|
||||||
return True, "发送成功"
|
return True, "发送成功"
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ requests~=2.32.3
|
|||||||
schedule~=1.2.2
|
schedule~=1.2.2
|
||||||
pyhandytools
|
pyhandytools
|
||||||
sparkdesk-api==1.3.0
|
sparkdesk-api==1.3.0
|
||||||
wcferry==39.5.1.0
|
|
||||||
websocket~=0.2.1
|
websocket~=0.2.1
|
||||||
pillow~=11.0.0
|
pillow~=11.0.0
|
||||||
jupyter_client~=8.6.3
|
jupyter_client~=8.6.3
|
||||||
@@ -46,3 +45,6 @@ pywin32==306
|
|||||||
opencv-python~=4.11.0.86
|
opencv-python~=4.11.0.86
|
||||||
deepface~=0.0.93
|
deepface~=0.0.93
|
||||||
scikit-learn>=1.0.2
|
scikit-learn>=1.0.2
|
||||||
|
gewechat-client==0.1.5
|
||||||
|
fastapi~=0.115.12
|
||||||
|
uvicorn~=0.34.2
|
||||||
273
robot.py
273
robot.py
@@ -7,14 +7,12 @@ from queue import Empty
|
|||||||
from threading import Thread
|
from threading import Thread
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
from gewechat_client import GewechatClient
|
||||||
|
|
||||||
from base.func_doubao import Doubao
|
from base.func_doubao import Doubao
|
||||||
from base.func_epic import is_friday, get_free
|
from base.func_epic import is_friday, get_free
|
||||||
from base.func_zhipu import ZhiPu
|
from base.func_zhipu import ZhiPu
|
||||||
|
|
||||||
from wcferry import Wcf, WxMsg
|
|
||||||
|
|
||||||
from base.func_bard import BardAssistant
|
|
||||||
from base.func_chatglm import ChatGLM
|
|
||||||
from base.func_chatgpt import ChatGPT
|
from base.func_chatgpt import ChatGPT
|
||||||
from base.func_news import News
|
from base.func_news import News
|
||||||
from base.func_tigerbot import TigerBot
|
from base.func_tigerbot import TigerBot
|
||||||
@@ -22,6 +20,7 @@ from base.func_xinghuo_web import XinghuoWeb
|
|||||||
from base.func_claude import Claude
|
from base.func_claude import Claude
|
||||||
from configuration import Config
|
from configuration import Config
|
||||||
from constants import ChatType
|
from constants import ChatType
|
||||||
|
from gewechat.call_back_message.message import WxMessage, MessageType
|
||||||
from utils.wechat.message_to_db import MessageStorage
|
from utils.wechat.message_to_db import MessageStorage
|
||||||
from plugin_common.event_system import EventType, EventSystem
|
from plugin_common.event_system import EventType, EventSystem
|
||||||
from plugin_common.message_plugin_interface import MessagePluginInterface
|
from plugin_common.message_plugin_interface import MessagePluginInterface
|
||||||
@@ -33,8 +32,6 @@ from job_mgmt import Job
|
|||||||
from utils.robot_cmd.robot_command import Feature
|
from utils.robot_cmd.robot_command import Feature
|
||||||
from utils.robot_cmd.robot_command import PermissionStatus
|
from utils.robot_cmd.robot_command import PermissionStatus
|
||||||
|
|
||||||
__version__ = "39.5.1.0"
|
|
||||||
|
|
||||||
from sehuatang.shehuatang import pdf_file_path
|
from sehuatang.shehuatang import pdf_file_path
|
||||||
from utils.wechat.contact_manager import ContactManager
|
from utils.wechat.contact_manager import ContactManager
|
||||||
from xiuren.meitu_dl import meitu_dowload_pub_pic
|
from xiuren.meitu_dl import meitu_dowload_pub_pic
|
||||||
@@ -48,17 +45,16 @@ class Robot(Job):
|
|||||||
"""个性化自己的机器人
|
"""个性化自己的机器人
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config: Config, wcf: Wcf, chat_type: int) -> None:
|
def __init__(self, config: Config, app_id: str, client: GewechatClient, chat_type: int) -> None:
|
||||||
self.wcf = wcf
|
self.app_id = app_id
|
||||||
|
self.client = client
|
||||||
self.config = config
|
self.config = config
|
||||||
self.LOG = logging.getLogger("Robot")
|
self.LOG = logging.getLogger("Robot")
|
||||||
|
|
||||||
self.wxid = self.wcf.get_self_wxid()
|
|
||||||
# 初始化联系人管理器并设置联系人
|
# 初始化联系人管理器并设置联系人
|
||||||
self.contact_manager = ContactManager.get_instance()
|
self.contact_manager = ContactManager.get_instance()
|
||||||
self.allContacts = self.get_all_contacts()
|
self.allContacts = self.get_all_contacts()
|
||||||
self.all_head_img = self.get_all_head_img_url()
|
self.contact_manager.set_contacts(self.allContacts)
|
||||||
self.contact_manager.set_contacts(self.allContacts, self.all_head_img, self.wcf)
|
|
||||||
|
|
||||||
self.LOG.info(f"DB+REDIS 连接池开始初始化")
|
self.LOG.info(f"DB+REDIS 连接池开始初始化")
|
||||||
# 使用单例模式获取实例
|
# 使用单例模式获取实例
|
||||||
@@ -73,7 +69,7 @@ class Robot(Job):
|
|||||||
self.redis_pool = self.db_manager.redis_pool
|
self.redis_pool = self.db_manager.redis_pool
|
||||||
|
|
||||||
# 初始化消息工具类 - 使用联系人管理器
|
# 初始化消息工具类 - 使用联系人管理器
|
||||||
self.message_util = MessageUtil(wcf, self.contact_manager)
|
self.message_util = MessageUtil(app_id, client, self.contact_manager)
|
||||||
self.groups = {} # 存储按group_id分组的消息列表,每个group_id最多保留10条消息
|
self.groups = {} # 存储按group_id分组的消息列表,每个group_id最多保留10条消息
|
||||||
GroupBotManager.load_local_cache()
|
GroupBotManager.load_local_cache()
|
||||||
|
|
||||||
@@ -89,7 +85,7 @@ class Robot(Job):
|
|||||||
# 设置插件系统上下文
|
# 设置插件系统上下文
|
||||||
self.system_context = {
|
self.system_context = {
|
||||||
"config": config,
|
"config": config,
|
||||||
"wcf": wcf,
|
"client": client,
|
||||||
"event_system": self.event_system,
|
"event_system": self.event_system,
|
||||||
"plugin_registry": self.plugin_registry,
|
"plugin_registry": self.plugin_registry,
|
||||||
"db_pool": self.db_pool,
|
"db_pool": self.db_pool,
|
||||||
@@ -105,48 +101,7 @@ class Robot(Job):
|
|||||||
self.LOG.info("插件系统初始化完成")
|
self.LOG.info("插件系统初始化完成")
|
||||||
|
|
||||||
# 消息存档模块初始化,自动完成入库动作
|
# 消息存档模块初始化,自动完成入库动作
|
||||||
self.message_storage = MessageStorage(self.wcf)
|
self.message_storage = MessageStorage(self.client)
|
||||||
if ChatType.is_in_chat_types(chat_type):
|
|
||||||
if chat_type == ChatType.TIGER_BOT.value and TigerBot.value_check(self.config.TIGERBOT):
|
|
||||||
self.chat = TigerBot(self.config.TIGERBOT)
|
|
||||||
elif chat_type == ChatType.CHATGPT.value and ChatGPT.value_check(self.config.CHATGPT):
|
|
||||||
self.chat = ChatGPT(self.config.CHATGPT)
|
|
||||||
elif chat_type == ChatType.XINGHUO_WEB.value and XinghuoWeb.value_check(self.config.XINGHUO_WEB):
|
|
||||||
self.chat = XinghuoWeb(self.config.XINGHUO_WEB)
|
|
||||||
elif chat_type == ChatType.CHATGLM.value and ChatGLM.value_check(self.config.CHATGLM):
|
|
||||||
self.chat = ChatGLM(self.config.CHATGLM)
|
|
||||||
elif chat_type == ChatType.BardAssistant.value and BardAssistant.value_check(self.config.BardAssistant):
|
|
||||||
self.chat = BardAssistant(self.config.BardAssistant)
|
|
||||||
elif chat_type == ChatType.ZhiPu.value and ZhiPu.value_check(self.config.ZhiPu):
|
|
||||||
self.chat = ZhiPu(self.config.ZhiPu)
|
|
||||||
elif chat_type == ChatType.CLAUDE.value and Claude.value_check(self.config.CLAUDE):
|
|
||||||
self.chat = Claude(self.config.CLAUDE)
|
|
||||||
elif chat_type == ChatType.DOUBAO.value and Claude.value_check(self.config.DOUBAO):
|
|
||||||
self.chat = Doubao(self.config.DOUBAO)
|
|
||||||
else:
|
|
||||||
self.LOG.warning("未配置模型")
|
|
||||||
self.chat = None
|
|
||||||
else:
|
|
||||||
if TigerBot.value_check(self.config.TIGERBOT):
|
|
||||||
self.chat = TigerBot(self.config.TIGERBOT)
|
|
||||||
elif ChatGPT.value_check(self.config.CHATGPT):
|
|
||||||
self.chat = ChatGPT(self.config.CHATGPT)
|
|
||||||
elif XinghuoWeb.value_check(self.config.XINGHUO_WEB):
|
|
||||||
self.chat = XinghuoWeb(self.config.XINGHUO_WEB)
|
|
||||||
elif ChatGLM.value_check(self.config.CHATGLM):
|
|
||||||
self.chat = ChatGLM(self.config.CHATGLM)
|
|
||||||
elif BardAssistant.value_check(self.config.BardAssistant):
|
|
||||||
self.chat = BardAssistant(self.config.BardAssistant)
|
|
||||||
elif ZhiPu.value_check(self.config.ZhiPu):
|
|
||||||
self.chat = ZhiPu(self.config.ZhiPu)
|
|
||||||
elif Claude.value_check(self.config.CLAUDE):
|
|
||||||
self.chat = Claude(self.config.CLAUDE)
|
|
||||||
elif Doubao.value_check(self.config.DOUBAO):
|
|
||||||
self.chat = Doubao(self.config.DOUBAO)
|
|
||||||
else:
|
|
||||||
self.LOG.warning("未配置模型")
|
|
||||||
self.chat = None
|
|
||||||
self.LOG.info(f"已选择: {self.chat}")
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def value_check(args: dict) -> bool:
|
def value_check(args: dict) -> bool:
|
||||||
@@ -154,11 +109,11 @@ class Robot(Job):
|
|||||||
return all(value is not None for key, value in args.items() if key != 'proxy')
|
return all(value is not None for key, value in args.items() if key != 'proxy')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def toChitchat(self, msg: WxMsg) -> bool:
|
def toChitchat(self, msg: WxMessage) -> bool:
|
||||||
"""闲聊,接入 ChatGPT
|
"""闲聊,接入 ChatGPT
|
||||||
"""
|
"""
|
||||||
# 去除@的人和空格等字符
|
# 去除@的人和空格等字符
|
||||||
q = re.sub(r"@.*?[\u2005|\s]", "", msg.content).replace(" ", "")
|
q = re.sub(r"@.*?[\u2005|\s]", "", msg.content.raw_content).replace(" ", "")
|
||||||
|
|
||||||
if q == "#今日百度新闻":
|
if q == "#今日百度新闻":
|
||||||
self.news_baidu_report((msg.roomid if msg.from_group() else msg.sender))
|
self.news_baidu_report((msg.roomid if msg.from_group() else msg.sender))
|
||||||
@@ -176,7 +131,7 @@ class Robot(Job):
|
|||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def processMsg(self, msg: WxMsg) -> None:
|
def processMsg(self, msg: WxMessage) -> None:
|
||||||
"""当接收到消息的时候,会调用本方法。如果不实现本方法,则打印原始消息。
|
"""当接收到消息的时候,会调用本方法。如果不实现本方法,则打印原始消息。
|
||||||
此处可进行自定义发送的内容,如通过 msg.content 关键字自动获取当前天气信息,并发送到对应的群组@发送者
|
此处可进行自定义发送的内容,如通过 msg.content 关键字自动获取当前天气信息,并发送到对应的群组@发送者
|
||||||
群号:msg.roomid 微信ID:msg.sender 消息内容:msg.content
|
群号:msg.roomid 微信ID:msg.sender 消息内容:msg.content
|
||||||
@@ -222,7 +177,7 @@ class Robot(Job):
|
|||||||
try:
|
try:
|
||||||
self.message_storage.archive_message(msg)
|
self.message_storage.archive_message(msg)
|
||||||
# 单独处理图片消息
|
# 单独处理图片消息
|
||||||
if msg.type == 3: # 图片消息类型
|
if msg.msg_type == 3: # 图片消息类型
|
||||||
self.message_storage.process_image(msg)
|
self.message_storage.process_image(msg)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.LOG.error(f"archive_message error: {e}")
|
self.LOG.error(f"archive_message error: {e}")
|
||||||
@@ -237,7 +192,7 @@ class Robot(Job):
|
|||||||
rsp = self.gbm.handle_command(msg.roomid, msg.content)
|
rsp = self.gbm.handle_command(msg.roomid, msg.content)
|
||||||
# 不在群里发送,防止被骚扰
|
# 不在群里发送,防止被骚扰
|
||||||
if rsp is not None:
|
if rsp is not None:
|
||||||
self.send_text_msg(rsp, msg.roomid, msg.sender)
|
self.message_util.send_text(rsp, msg.roomid, msg.sender)
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.LOG.error(f"revoke_receive_message error: {e}")
|
self.LOG.error(f"revoke_receive_message error: {e}")
|
||||||
@@ -248,15 +203,7 @@ class Robot(Job):
|
|||||||
if plugin_processed:
|
if plugin_processed:
|
||||||
return
|
return
|
||||||
|
|
||||||
# 非群聊信息,按消息类型进行处理
|
elif msg.msg_type == MessageType.TEXT: # 文本消息
|
||||||
if msg.type == 37: # 好友请求
|
|
||||||
self.LOG.info(f"收到好友请求:{msg}")
|
|
||||||
self.auto_accept_friend_request(msg)
|
|
||||||
|
|
||||||
elif msg.type == 10000: # 系统信息
|
|
||||||
self.say_hi_to_new_friend(msg)
|
|
||||||
|
|
||||||
elif msg.type == 0x01: # 文本消息
|
|
||||||
# 让配置加载更灵活,自己可以更新配置。也可以利用定时任务更新。
|
# 让配置加载更灵活,自己可以更新配置。也可以利用定时任务更新。
|
||||||
if msg.from_self():
|
if msg.from_self():
|
||||||
if msg.content == "^更新$":
|
if msg.content == "^更新$":
|
||||||
@@ -274,79 +221,15 @@ class Robot(Job):
|
|||||||
else:
|
else:
|
||||||
self.toChitchat(msg) # 闲聊
|
self.toChitchat(msg) # 闲聊
|
||||||
|
|
||||||
def onMsg(self, msg: WxMsg) -> int:
|
def onMsg(self, msg: WxMessage) -> int:
|
||||||
try:
|
try:
|
||||||
self.LOG.debug(msg) # 打印信息
|
self.LOG.info(msg) # 打印信息
|
||||||
self.processMsg(msg)
|
# self.processMsg(msg)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.LOG.error(e)
|
self.LOG.error(e)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def enableRecvMsg(self) -> None:
|
|
||||||
self.wcf.enable_recv_msg(self.onMsg)
|
|
||||||
|
|
||||||
def enableReceivingMsg(self) -> None:
|
|
||||||
def innerProcessMsg(wcf: Wcf):
|
|
||||||
while wcf.is_receiving_msg():
|
|
||||||
try:
|
|
||||||
msg = wcf.get_msg()
|
|
||||||
self.LOG.debug(msg)
|
|
||||||
self.processMsg(msg)
|
|
||||||
except Empty:
|
|
||||||
continue # Empty message
|
|
||||||
except Exception as e:
|
|
||||||
self.LOG.error(f"Receiving message error: {e}")
|
|
||||||
|
|
||||||
self.wcf.enable_receiving_msg()
|
|
||||||
Thread(target=innerProcessMsg, name="GetMessage", args=(self.wcf,), daemon=True).start()
|
|
||||||
|
|
||||||
def send_text_msg(self, msg: str, receiver: str, at_list: str = "") -> None:
|
|
||||||
""" 发送消息
|
|
||||||
:param msg: 消息字符串
|
|
||||||
:param receiver: 接收人wxid或者群id
|
|
||||||
:param at_list: 要@的wxid, @所有人的wxid为:notify@all
|
|
||||||
"""
|
|
||||||
# msg 中需要有 @ 名单中一样数量的 @
|
|
||||||
|
|
||||||
# 风控处理,随机延迟发送,解决群消息高频发送导致的微信风险
|
|
||||||
time.sleep(random.uniform(0.3, 1.0))
|
|
||||||
|
|
||||||
ats = ""
|
|
||||||
if at_list:
|
|
||||||
if at_list == "notify@all": # @所有人
|
|
||||||
ats = " @所有人"
|
|
||||||
else:
|
|
||||||
wxids = at_list.split(",")
|
|
||||||
for wxid in wxids:
|
|
||||||
# 根据 wxid 查找群昵称
|
|
||||||
ats += f" @{self.wcf.get_alias_in_chatroom(wxid, receiver)}"
|
|
||||||
|
|
||||||
# {msg}{ats} 表示要发送的消息内容后面紧跟@,例如 北京天气情况为:xxx @张三
|
|
||||||
if ats == "":
|
|
||||||
self.LOG.info(f"To {receiver}: {msg}")
|
|
||||||
self.wcf.send_text(f"{msg}", receiver, at_list)
|
|
||||||
else:
|
|
||||||
self.LOG.info(f"To {receiver}: {ats}\r{msg}")
|
|
||||||
self.wcf.send_text(f"{ats}\n\n{msg}", receiver, at_list)
|
|
||||||
|
|
||||||
def get_all_contacts(self) -> dict:
|
|
||||||
"""
|
|
||||||
获取联系人(包括好友、公众号、服务号、群成员……)
|
|
||||||
格式: {"wxid": "NickName"}
|
|
||||||
"""
|
|
||||||
contacts = self.wcf.query_sql("MicroMsg.db", "SELECT UserName, NickName FROM Contact;")
|
|
||||||
return {contact["UserName"]: contact["NickName"] for contact in contacts}
|
|
||||||
|
|
||||||
def get_all_head_img_url(self) -> dict:
|
|
||||||
try:
|
|
||||||
head_img_urls = self.wcf.query_sql("MicroMsg.db", "SELECT usrName ,bigHeadImgUrl FROM ContactHeadImgUrl;")
|
|
||||||
# self.LOG.info(f"head_img_urls: {head_img_urls}")
|
|
||||||
return {contact["usrName"]: contact["bigHeadImgUrl"] for contact in head_img_urls}
|
|
||||||
except Exception as e:
|
|
||||||
self.LOG.error(f"get_all_head_img_url error: {e}")
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def keep_running_and_block_process(self) -> None:
|
def keep_running_and_block_process(self) -> None:
|
||||||
"""
|
"""
|
||||||
保持机器人运行,不让进程退出
|
保持机器人运行,不让进程退出
|
||||||
@@ -355,31 +238,11 @@ class Robot(Job):
|
|||||||
self.runPendingJobs()
|
self.runPendingJobs()
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
def auto_accept_friend_request(self, msg: WxMsg) -> None:
|
|
||||||
try:
|
|
||||||
xml = ET.fromstring(msg.content)
|
|
||||||
v3 = xml.attrib["encryptusername"]
|
|
||||||
v4 = xml.attrib["ticket"]
|
|
||||||
scene = int(xml.attrib["scene"])
|
|
||||||
res = self.wcf.accept_new_friend(v3, v4, scene)
|
|
||||||
self.LOG.info(f"同意好友请求:{res}")
|
|
||||||
except Exception as e:
|
|
||||||
self.LOG.error(f"同意好友出错:{e}")
|
|
||||||
|
|
||||||
def say_hi_to_new_friend(self, msg: WxMsg) -> None:
|
|
||||||
nickName = re.findall(r"你已添加了(.*),现在可以开始聊天了。", msg.content)
|
|
||||||
if nickName:
|
|
||||||
# 添加了好友,更新好友列表和联系人管理器
|
|
||||||
self.allContacts[msg.sender] = nickName[0]
|
|
||||||
self.contact_manager.update_contact(msg.sender, nickName[0])
|
|
||||||
self.send_text_msg(f"Hi {nickName[0]},我自动通过了你的好友请求。", msg.sender)
|
|
||||||
|
|
||||||
# 添加一个方法用于刷新联系人信息
|
# 添加一个方法用于刷新联系人信息
|
||||||
def refresh_contacts(self):
|
def refresh_contacts(self):
|
||||||
"""刷新联系人信息"""
|
"""刷新联系人信息"""
|
||||||
self.allContacts = self.get_all_contacts()
|
self.allContacts = self.get_all_contacts()
|
||||||
self.all_head_img = self.get_all_head_img_url()
|
self.contact_manager.refresh_contacts(self.allContacts)
|
||||||
self.contact_manager.refresh_contacts(self.allContacts, self.all_head_img, self.wcf)
|
|
||||||
self.LOG.info("联系人信息已刷新")
|
self.LOG.info("联系人信息已刷新")
|
||||||
|
|
||||||
def send_group_txt_message(self, msg: str, feature: Feature):
|
def send_group_txt_message(self, msg: str, feature: Feature):
|
||||||
@@ -389,7 +252,7 @@ class Robot(Job):
|
|||||||
return
|
return
|
||||||
for r in receivers:
|
for r in receivers:
|
||||||
if self.gbm.get_group_permission(r, feature) == PermissionStatus.ENABLED:
|
if self.gbm.get_group_permission(r, feature) == PermissionStatus.ENABLED:
|
||||||
self.send_text_msg(msg, r)
|
self.message_util.send_text(msg, r)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.LOG.error(f"send_group_txt_message:{feature.description} error:{e}")
|
self.LOG.error(f"send_group_txt_message:{feature.description} error:{e}")
|
||||||
|
|
||||||
@@ -400,11 +263,11 @@ class Robot(Job):
|
|||||||
return
|
return
|
||||||
for r in receivers:
|
for r in receivers:
|
||||||
if self.gbm.get_group_permission(r, feature) == PermissionStatus.ENABLED:
|
if self.gbm.get_group_permission(r, feature) == PermissionStatus.ENABLED:
|
||||||
self.wcf.send_file(path, r)
|
self.message_util.send_file(path, r)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.LOG.error(f"send_group_file_message:{feature.description} error:{e}")
|
self.LOG.error(f"send_group_file_message:{feature.description} error:{e}")
|
||||||
|
|
||||||
def process_plugin_message(self, msg: WxMsg) -> bool:
|
def process_plugin_message(self, msg: WxMessage) -> bool:
|
||||||
"""使用插件处理消息"""
|
"""使用插件处理消息"""
|
||||||
# 获取所有消息处理插件
|
# 获取所有消息处理插件
|
||||||
message_plugins = self.plugin_registry.get_plugins_by_type(MessagePluginInterface)
|
message_plugins = self.plugin_registry.get_plugins_by_type(MessagePluginInterface)
|
||||||
@@ -415,16 +278,15 @@ class Robot(Job):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 转换WxMsg为插件可处理的格式
|
# 转换WxMessage为插件可处理的格式
|
||||||
plugin_msg = {
|
plugin_msg = {
|
||||||
"type": msg.type,
|
"type": msg.msg_type,
|
||||||
"content": msg.content,
|
"content": msg.content,
|
||||||
"sender": msg.sender,
|
"sender": msg.sender,
|
||||||
"roomid": msg.roomid if msg.from_group() else "",
|
"roomid": msg.roomid if msg.from_group() else "",
|
||||||
"xml": msg.xml,
|
"xml": msg.content.xml_content,
|
||||||
"is_at": msg.is_at(self.wxid), # 确保正确设置is_at标志
|
"is_at": msg.is_at(self.wxid), # 确保正确设置is_at标志
|
||||||
"timestamp": time.time(),
|
"timestamp": time.time(),
|
||||||
"wcf": self.wcf, # 提供wcf对象,让插件可以直接发送消息
|
|
||||||
"message_util": self.message_util, # 提供消息工具类
|
"message_util": self.message_util, # 提供消息工具类
|
||||||
"gbm": self.gbm, # 每次从程序变量中取,保证最新
|
"gbm": self.gbm, # 每次从程序变量中取,保证最新
|
||||||
"all_contacts": self.allContacts,
|
"all_contacts": self.allContacts,
|
||||||
@@ -536,6 +398,87 @@ class Robot(Job):
|
|||||||
def xiu_ren_pdf_send(self):
|
def xiu_ren_pdf_send(self):
|
||||||
try:
|
try:
|
||||||
pub_path = generate_pdf_from_images("xiuren")
|
pub_path = generate_pdf_from_images("xiuren")
|
||||||
self.wcf.send_file(pub_path, "45317011307@chatroom")
|
self.message_util.send_file(pub_path, "45317011307@chatroom")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.LOG.error(f"xiu_ren_pdf_send error:{e}")
|
self.LOG.error(f"xiu_ren_pdf_send error:{e}")
|
||||||
|
|
||||||
|
def get_all_contacts(self) -> dict:
|
||||||
|
"""获取所有联系人信息并保存到数据库"""
|
||||||
|
from db.contacts_db import ContactsDBOperator
|
||||||
|
|
||||||
|
contacts_dict = {}
|
||||||
|
contacts_wxids = self.client.fetch_contacts_list(self.app_id)
|
||||||
|
|
||||||
|
# 初始化联系人数据库操作类
|
||||||
|
contacts_db = ContactsDBOperator()
|
||||||
|
|
||||||
|
for wxid in contacts_wxids:
|
||||||
|
# 获取联系人详细信息
|
||||||
|
contact_info = self.client.get_detail_info(self.app_id, wxid)
|
||||||
|
|
||||||
|
# 将联系人信息添加到字典中
|
||||||
|
if contact_info and contact_info.get("ret") == 200 and "data" in contact_info:
|
||||||
|
contact_data = contact_info.get("data", [])
|
||||||
|
|
||||||
|
# 保存联系人信息到数据库
|
||||||
|
if contact_data:
|
||||||
|
try:
|
||||||
|
# 判断联系人类型
|
||||||
|
contact_type = "friends" # 默认为好友类型
|
||||||
|
if wxid.endswith("@chatroom"):
|
||||||
|
contact_type = "chatrooms"
|
||||||
|
# 如果是这个类型,则提取群成员信息
|
||||||
|
# 提取群成员信息
|
||||||
|
self.update_chatroom_member_details(wxid)
|
||||||
|
elif wxid.startswith("gh_"):
|
||||||
|
contact_type = "ghs"
|
||||||
|
|
||||||
|
# 保存到数据库
|
||||||
|
contacts_db.save_contacts(contact_data, contact_type)
|
||||||
|
|
||||||
|
# 添加到返回字典
|
||||||
|
for contact in contact_data:
|
||||||
|
user_name = contact.get("userName")
|
||||||
|
if user_name:
|
||||||
|
contacts_dict[user_name] = contact.get("nickName") or user_name
|
||||||
|
except Exception as e:
|
||||||
|
self.LOG.error(f"保存联系人信息到数据库失败: {e}")
|
||||||
|
|
||||||
|
self.LOG.info(f"成功获取并保存{len(contacts_dict)}个联系人信息")
|
||||||
|
return contacts_dict
|
||||||
|
|
||||||
|
def update_chatroom_member_details(self, chatroom_id):
|
||||||
|
"""更新群成员详细信息"""
|
||||||
|
try:
|
||||||
|
# 首先获取群成员列表
|
||||||
|
members_response = self.client.get_chatroom_member_list(self.app_id, chatroom_id)
|
||||||
|
if members_response and members_response.get('ret') == 200:
|
||||||
|
member_list = members_response.get('data', {}).get('memberList', [])
|
||||||
|
|
||||||
|
# 提取成员wxid列表
|
||||||
|
member_wxids = [member.get('wxid') for member in member_list if member.get('wxid')]
|
||||||
|
|
||||||
|
if member_wxids:
|
||||||
|
# 获取成员详细信息
|
||||||
|
details_response = self.client.get_chatroom_member_detail(self.app_id, chatroom_id, member_wxids)
|
||||||
|
|
||||||
|
# 使用ContactsDBOperator处理响应
|
||||||
|
from db.contacts_db import ContactsDBOperator
|
||||||
|
contacts_db = ContactsDBOperator()
|
||||||
|
success = contacts_db.process_chatroom_member_detail_response(chatroom_id, details_response)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.LOG.info(f"成功更新群聊{chatroom_id}的成员详细信息")
|
||||||
|
else:
|
||||||
|
self.LOG.error(f"更新群聊{chatroom_id}的成员详细信息失败")
|
||||||
|
|
||||||
|
return success
|
||||||
|
else:
|
||||||
|
self.LOG.warning(f"群聊{chatroom_id}没有成员")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self.LOG.error(f"获取群聊{chatroom_id}成员列表失败")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
self.LOG.error(f"更新群聊成员详细信息出错: {e}")
|
||||||
|
return False
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ def plugin_points_cost(points: int, description: str = None, feature: Feature =
|
|||||||
# 积分不足
|
# 积分不足
|
||||||
wcf = message.get("wcf")
|
wcf = message.get("wcf")
|
||||||
if wcf:
|
if wcf:
|
||||||
wcf.send_text(
|
self.message_util.send_text(
|
||||||
f"❌ 积分不足\n无法使用 {plugin_name} 功能\n"
|
f"❌ 积分不足\n无法使用 {plugin_name} 功能\n"
|
||||||
f"🪙 先参与积分活动[签到,答题/t]赚取吧!\n"
|
f"🪙 先参与积分活动[签到,答题/t]赚取吧!\n"
|
||||||
f"💰 有: {user_points['total_points']} | 需: {points} |差: {points - user_points['total_points']} ",
|
f"💰 有: {user_points['total_points']} | 需: {points} |差: {points - user_points['total_points']} ",
|
||||||
@@ -170,7 +170,7 @@ def plugin_points_cost(points: int, description: str = None, feature: Feature =
|
|||||||
response += f"\n\n💰 已消费 {points} 积分"
|
response += f"\n\n💰 已消费 {points} 积分"
|
||||||
wcf = message.get("wcf")
|
wcf = message.get("wcf")
|
||||||
if wcf:
|
if wcf:
|
||||||
wcf.send_text(
|
self.message_util.send_text(
|
||||||
f"💰消费 {points} 积分",
|
f"💰消费 {points} 积分",
|
||||||
(roomid if roomid else sender), sender
|
(roomid if roomid else sender), sender
|
||||||
)
|
)
|
||||||
|
|||||||
131
utils/json_converter.py
Normal file
131
utils/json_converter.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
JSON数据转换工具
|
||||||
|
用于将JSON数据转换为Python对象
|
||||||
|
"""
|
||||||
|
|
||||||
|
class DictObject:
|
||||||
|
"""将字典转换为对象,使得可以通过属性访问"""
|
||||||
|
def __init__(self, data):
|
||||||
|
for key, value in data.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
setattr(self, key, DictObject(value))
|
||||||
|
elif isinstance(value, list):
|
||||||
|
setattr(self, key, [
|
||||||
|
DictObject(item) if isinstance(item, dict) else item
|
||||||
|
for item in value
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""打印对象时的表示形式"""
|
||||||
|
attrs = ', '.join(f"{key}={repr(value)}" for key, value in self.__dict__.items())
|
||||||
|
return f"{self.__class__.__name__}({attrs})"
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"""将对象转换回字典"""
|
||||||
|
result = {}
|
||||||
|
for key, value in self.__dict__.items():
|
||||||
|
if isinstance(value, DictObject):
|
||||||
|
result[key] = value.to_dict()
|
||||||
|
elif isinstance(value, list):
|
||||||
|
result[key] = [
|
||||||
|
item.to_dict() if isinstance(item, DictObject) else item
|
||||||
|
for item in value
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
result[key] = value
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def json_to_object(json_data):
|
||||||
|
"""
|
||||||
|
将JSON数据转换为Python对象
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_data: JSON字符串或字典
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DictObject: 转换后的Python对象
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
# 如果输入是字符串,则解析为字典
|
||||||
|
if isinstance(json_data, str):
|
||||||
|
data = json.loads(json_data)
|
||||||
|
else:
|
||||||
|
data = json_data
|
||||||
|
|
||||||
|
# 转换为对象
|
||||||
|
return DictObject(data)
|
||||||
|
|
||||||
|
|
||||||
|
# 使用示例
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 示例JSON数据
|
||||||
|
example_json = {
|
||||||
|
"ret": 200,
|
||||||
|
"msg": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"friends": [
|
||||||
|
"tmessage",
|
||||||
|
"medianote",
|
||||||
|
"qmessage",
|
||||||
|
"qqmail",
|
||||||
|
"wxid_910acevfm2nb21",
|
||||||
|
"qqsafe",
|
||||||
|
"wxid_9299552988412",
|
||||||
|
"weixin",
|
||||||
|
"exmail_tool",
|
||||||
|
"wxid_mp05xmje0ctn22",
|
||||||
|
"wxid_09oq4f4j4wg912",
|
||||||
|
"wxid_6bfguz79h8n122",
|
||||||
|
"wxid_lyuq4hr4lrjq22",
|
||||||
|
"wxid_a1zqyljsrsdu12",
|
||||||
|
"wxid_lv3pb3zhna3522",
|
||||||
|
"wxid_k2biq6fuinsr22",
|
||||||
|
"wxid_ujredjhxz9y712",
|
||||||
|
"wxid_uwb7989u0jea12",
|
||||||
|
"wxid_in46ey732vxu12",
|
||||||
|
"wxid_3rvervwohj6921",
|
||||||
|
"wxid_4wkls7tu62ua12",
|
||||||
|
"wxid_g0bdknnotx2f12",
|
||||||
|
"wxid_ce5fgp0icb3y21",
|
||||||
|
"wxid_1482424825211",
|
||||||
|
"wxid_vw3p4f6jy7bm12",
|
||||||
|
"wxid_o2m8xm71c23522",
|
||||||
|
"wxid_bclqpc2ho6o412",
|
||||||
|
"wxid_98pjjzpiisi721",
|
||||||
|
"wxid_noq2wsn5c8h222"
|
||||||
|
],
|
||||||
|
"chatrooms": [
|
||||||
|
"2180313478@chatroom",
|
||||||
|
"14358945067@chatroom",
|
||||||
|
"17362526147@chatroom",
|
||||||
|
"11685224357@chatroom",
|
||||||
|
"17522822550@chatroom"
|
||||||
|
],
|
||||||
|
"ghs": [
|
||||||
|
"gh_7aac992b0363",
|
||||||
|
"gh_d7293b5f14f4",
|
||||||
|
"gh_f51ce3ef83a4",
|
||||||
|
"gh_7d20df86e26b",
|
||||||
|
"gh_69bfb92a3e43"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 转换为对象
|
||||||
|
obj = json_to_object(example_json)
|
||||||
|
|
||||||
|
# 通过属性访问
|
||||||
|
print(f"返回码: {obj.ret}")
|
||||||
|
print(f"消息: {obj.msg}")
|
||||||
|
print(f"好友数量: {len(obj.data.friends)}")
|
||||||
|
print(f"第一个好友: {obj.data.friends[0]}")
|
||||||
|
print(f"第一个群聊: {obj.data.chatrooms[0]}")
|
||||||
|
|
||||||
|
# 转换回字典
|
||||||
|
dict_data = obj.to_dict()
|
||||||
|
print(f"转换回字典: {dict_data}")
|
||||||
@@ -41,7 +41,7 @@ class Feature(Enum):
|
|||||||
VIDEO = 14, "黑丝视频 [黑丝视频, 黑丝, 来个黑丝,搞个黑丝]"
|
VIDEO = 14, "黑丝视频 [黑丝视频, 黑丝, 来个黑丝,搞个黑丝]"
|
||||||
VIDEO_MAN = 15, "肌肉视频 [猛男, 肌肉, 帅哥]"
|
VIDEO_MAN = 15, "肌肉视频 [猛男, 肌肉, 帅哥]"
|
||||||
# GROUP_ADD = 16, "加群提醒"
|
# GROUP_ADD = 16, "加群提醒"
|
||||||
# DOUYIN_PARSER = 17, "抖音链接转视频"
|
DOUYIN_PARSER = 17, "抖音链接转视频"
|
||||||
GROUP_MEMBER_CHANGE = 18, "群成员变更提醒功能"
|
GROUP_MEMBER_CHANGE = 18, "群成员变更提醒功能"
|
||||||
# KID_PHOTO_EXTRACT = 19, "儿童照片提取转发功能" # 小朋友照片提取功能
|
# KID_PHOTO_EXTRACT = 19, "儿童照片提取转发功能" # 小朋友照片提取功能
|
||||||
NEWS = 20, "全球政治经济新闻"
|
NEWS = 20, "全球政治经济新闻"
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Dict, Optional, List, Tuple
|
from typing import Dict, Optional, List, Tuple
|
||||||
|
|
||||||
from wcferry import Wcf
|
from gewechat_client import GewechatClient
|
||||||
|
|
||||||
|
from utils.json_converter import json_to_object
|
||||||
|
|
||||||
|
|
||||||
class ContactManager:
|
class ContactManager:
|
||||||
@@ -50,7 +52,7 @@ class ContactManager:
|
|||||||
cls._instance = ContactManager()
|
cls._instance = ContactManager()
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def set_contacts(self, contacts: Dict[str, str], head_imgs: Dict[str, str], wcf: Wcf) -> None:
|
def set_contacts(self, contacts: Dict[str, str]) -> None:
|
||||||
"""设置联系人字典
|
"""设置联系人字典
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -67,13 +69,12 @@ class ContactManager:
|
|||||||
"gender": gender}
|
"gender": gender}
|
||||||
"""
|
"""
|
||||||
self._contacts = contacts
|
self._contacts = contacts
|
||||||
self._friends = wcf.get_friends()
|
self._friends = contacts
|
||||||
self._head_images = head_imgs
|
|
||||||
self._logger.info(f"联系人信息已更新,共 {len(contacts)} 个联系人")
|
self._logger.info(f"联系人信息已更新,共 {len(contacts)} 个联系人")
|
||||||
# 分类联系人
|
# 分类联系人
|
||||||
self._classify_contacts(wcf)
|
self._classify_contacts()
|
||||||
|
|
||||||
def _classify_contacts(self, wcf: Wcf) -> None:
|
def _classify_contacts(self) -> None:
|
||||||
"""将联系人分类为群组、个人联系人、公共好友和公众号"""
|
"""将联系人分类为群组、个人联系人、公共好友和公众号"""
|
||||||
self._group_contacts = {}
|
self._group_contacts = {}
|
||||||
self._personal_contacts = {}
|
self._personal_contacts = {}
|
||||||
@@ -90,8 +91,6 @@ class ContactManager:
|
|||||||
# 判断是否为群组(wxid以@chatroom结尾)
|
# 判断是否为群组(wxid以@chatroom结尾)
|
||||||
elif wxid.endswith('@chatroom'):
|
elif wxid.endswith('@chatroom'):
|
||||||
self._group_contacts[wxid] = nickname
|
self._group_contacts[wxid] = nickname
|
||||||
# 如果是群,这处理群列表内容
|
|
||||||
self._group_contacts_friends[wxid] = wcf.get_chatroom_members(wxid)
|
|
||||||
|
|
||||||
# # 其他为普通好友和群成员
|
# # 其他为普通好友和群成员
|
||||||
# else:
|
# else:
|
||||||
@@ -216,7 +215,7 @@ class ContactManager:
|
|||||||
self._personal_contacts[wxid] = nickname
|
self._personal_contacts[wxid] = nickname
|
||||||
self._logger.debug(f"已更新联系人: {wxid} -> {nickname}")
|
self._logger.debug(f"已更新联系人: {wxid} -> {nickname}")
|
||||||
|
|
||||||
def refresh_contacts(self, new_contacts: Dict[str, str], head_imgs: Dict[str, str], wcf: Wcf) -> None:
|
def refresh_contacts(self, new_contacts: Dict[str, str]) -> None:
|
||||||
"""刷新联系人信息
|
"""刷新联系人信息
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -235,11 +234,9 @@ class ContactManager:
|
|||||||
# "province": cnt.get("province", ""),
|
# "province": cnt.get("province", ""),
|
||||||
# "city": cnt.get("city", ""),
|
# "city": cnt.get("city", ""),
|
||||||
# "gender": gender}
|
# "gender": gender}
|
||||||
self._friends = wcf.get_friends()
|
self._friends = self.message_util.get_friends()
|
||||||
self._head_images = head_imgs
|
|
||||||
self._logger.info(f"联系人信息已刷新,共 {len(new_contacts)} 个联系人")
|
self._logger.info(f"联系人信息已刷新,共 {len(new_contacts)} 个联系人")
|
||||||
# 重新分类联系人
|
self._classify_contacts()
|
||||||
self._classify_contacts(wcf)
|
|
||||||
|
|
||||||
def get_contact_statistics(self) -> Tuple[int, int, int, int, int]:
|
def get_contact_statistics(self) -> Tuple[int, int, int, int, int]:
|
||||||
"""获取联系人统计信息
|
"""获取联系人统计信息
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ import xml.etree.ElementTree as ET
|
|||||||
import logging
|
import logging
|
||||||
import concurrent.futures # 添加线程池支持
|
import concurrent.futures # 添加线程池支持
|
||||||
import os
|
import os
|
||||||
from wcferry import WxMsg, Wcf
|
|
||||||
|
from gewechat_client import GewechatClient
|
||||||
|
|
||||||
from db.connection import DBConnectionManager
|
from db.connection import DBConnectionManager
|
||||||
from db.message_storage import MessageStorageDB
|
from db.message_storage import MessageStorageDB
|
||||||
# 导入积分系统
|
# 导入积分系统
|
||||||
from db.points_db import PointsDBOperator, PointSource
|
from db.points_db import PointsDBOperator, PointSource
|
||||||
|
from gewechat.call_back_message.message import WxMessage
|
||||||
|
|
||||||
# 配置日志
|
# 配置日志
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
@@ -19,7 +22,7 @@ logger = logging.getLogger("MessageStorage")
|
|||||||
|
|
||||||
class MessageStorage:
|
class MessageStorage:
|
||||||
|
|
||||||
def __init__(self, wcf: Wcf = None):
|
def __init__(self, client: GewechatClient = None):
|
||||||
# 获取数据库连接管理器的单例
|
# 获取数据库连接管理器的单例
|
||||||
self.db_manager = DBConnectionManager.get_instance()
|
self.db_manager = DBConnectionManager.get_instance()
|
||||||
self.message_db = MessageStorageDB(self.db_manager)
|
self.message_db = MessageStorageDB(self.db_manager)
|
||||||
@@ -34,7 +37,7 @@ class MessageStorage:
|
|||||||
self.pending_tasks = []
|
self.pending_tasks = []
|
||||||
|
|
||||||
# 保存WCF实例,用于图片处理
|
# 保存WCF实例,用于图片处理
|
||||||
self.wcf = wcf
|
self.client = client
|
||||||
|
|
||||||
# 图片处理相关初始化
|
# 图片处理相关初始化
|
||||||
self.image_executor = concurrent.futures.ThreadPoolExecutor(max_workers=3) # 专用于图片处理的线程池
|
self.image_executor = concurrent.futures.ThreadPoolExecutor(max_workers=3) # 专用于图片处理的线程池
|
||||||
@@ -46,7 +49,7 @@ class MessageStorage:
|
|||||||
os.makedirs(self.image_dir, exist_ok=True)
|
os.makedirs(self.image_dir, exist_ok=True)
|
||||||
logger.info(f"图片存储目录: {self.image_dir}")
|
logger.info(f"图片存储目录: {self.image_dir}")
|
||||||
|
|
||||||
def process_message(self, message: WxMsg):
|
def process_message(self, message: WxMessage):
|
||||||
# 示例message字符串
|
# 示例message字符串
|
||||||
current_date = datetime.now().strftime('%Y-%m-%d')
|
current_date = datetime.now().strftime('%Y-%m-%d')
|
||||||
# 生成Redis key
|
# 生成Redis key
|
||||||
@@ -59,7 +62,7 @@ class MessageStorage:
|
|||||||
redis_conn.expire(key, 86400 * 2)
|
redis_conn.expire(key, 86400 * 2)
|
||||||
# 或者使用字符串:r.incr(key) # 如果只存储一个整数值,字符串类型可能更简单
|
# 或者使用字符串:r.incr(key) # 如果只存储一个整数值,字符串类型可能更简单
|
||||||
|
|
||||||
def archive_message(self, msg: WxMsg):
|
def archive_message(self, msg: WxMessage):
|
||||||
"""异步存档消息,防止堵塞主线程"""
|
"""异步存档消息,防止堵塞主线程"""
|
||||||
# 提交任务到线程池
|
# 提交任务到线程池
|
||||||
future = self.executor.submit(self._archive_message_task, msg)
|
future = self.executor.submit(self._archive_message_task, msg)
|
||||||
@@ -70,7 +73,7 @@ class MessageStorage:
|
|||||||
# 清理已完成的任务
|
# 清理已完成的任务
|
||||||
self._cleanup_completed_tasks()
|
self._cleanup_completed_tasks()
|
||||||
|
|
||||||
def _archive_message_task(self, msg: WxMsg):
|
def _archive_message_task(self, msg: WxMessage):
|
||||||
"""实际执行消息存档的任务函数"""
|
"""实际执行消息存档的任务函数"""
|
||||||
try:
|
try:
|
||||||
# 使用 MessageStorageDB 类存档消息
|
# 使用 MessageStorageDB 类存档消息
|
||||||
@@ -80,7 +83,7 @@ class MessageStorage:
|
|||||||
'roomid': msg.roomid,
|
'roomid': msg.roomid,
|
||||||
'sender': msg.sender,
|
'sender': msg.sender,
|
||||||
'content': msg.content, # 添加消息内容
|
'content': msg.content, # 添加消息内容
|
||||||
'message_id': msg.id # 添加消息ID
|
'message_id': msg.msg_id # 添加消息ID
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"存档消息出错: {e}")
|
logger.error(f"存档消息出错: {e}")
|
||||||
@@ -89,13 +92,13 @@ class MessageStorage:
|
|||||||
'roomid': msg.roomid,
|
'roomid': msg.roomid,
|
||||||
'sender': msg.sender,
|
'sender': msg.sender,
|
||||||
'content': msg.content, # 添加消息内容
|
'content': msg.content, # 添加消息内容
|
||||||
'message_id': msg.id, # 添加消息ID
|
'message_id': msg.msg_id, # 添加消息ID
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
def process_image(self, msg: WxMsg):
|
def process_image(self, msg: WxMessage):
|
||||||
"""异步处理图片消息,与消息存档分离"""
|
"""异步处理图片消息,与消息存档分离"""
|
||||||
if msg.type != 3 or not self.wcf: # 不是图片消息或没有WCF实例
|
if msg.msg_type != 3 or not self.client: # 不是图片消息或没有WCF实例
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 提交任务到图片处理线程池
|
# 提交任务到图片处理线程池
|
||||||
@@ -108,11 +111,11 @@ class MessageStorage:
|
|||||||
self._cleanup_completed_tasks()
|
self._cleanup_completed_tasks()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _process_image_task(self, msg: WxMsg):
|
def _process_image_task(self, msg: WxMessage):
|
||||||
"""实际执行图片处理的任务函数"""
|
"""实际执行图片处理的任务函数"""
|
||||||
try:
|
try:
|
||||||
# 使用wcf下载图片,确保图片存在
|
# 使用wcf下载图片,确保图片存在
|
||||||
if self.wcf and msg.id:
|
if self.client and msg.msg_id:
|
||||||
# 创建按群ID或个人wxid分割的目录
|
# 创建按群ID或个人wxid分割的目录
|
||||||
target_dir = os.path.join(self.image_dir, msg.roomid if msg.roomid else msg.sender)
|
target_dir = os.path.join(self.image_dir, msg.roomid if msg.roomid else msg.sender)
|
||||||
# 确保目标目录存在
|
# 确保目标目录存在
|
||||||
@@ -120,16 +123,27 @@ class MessageStorage:
|
|||||||
os.makedirs(target_dir, exist_ok=True)
|
os.makedirs(target_dir, exist_ok=True)
|
||||||
|
|
||||||
# 尝试使用wcf下载图片到分组后的目录
|
# 尝试使用wcf下载图片到分组后的目录
|
||||||
download_path = self.wcf.download_image(msg.id, msg.extra, target_dir)
|
json = self.client.download_image(msg.msg_id, msg.content.xml_content, 1)
|
||||||
|
# {
|
||||||
|
# "ret": 200,
|
||||||
|
# "msg": "操作成功",
|
||||||
|
# "data": {
|
||||||
|
# "fileUrl": "/download/20240720/wx_BTVoJ_o_r6DpxNCNiycFE/0ca5b675-8e2c-4dc1-b288-3c44a40086ec4"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# 解析JSON
|
||||||
|
if json and json.get('data') and json['data'].get('fileUrl'):
|
||||||
|
file_url = json['data']['fileUrl']
|
||||||
|
download_path = self.download_file_from_url(file_url, target_dir)
|
||||||
if download_path:
|
if download_path:
|
||||||
logger.info(f"使用wcf下载图片成功: {msg.id} -> {download_path}")
|
logger.info(f"使用wcf下载图片成功: {msg.msg_id} -> {download_path}")
|
||||||
|
|
||||||
# 直接使用下载后的路径更新数据库
|
# 直接使用下载后的路径更新数据库
|
||||||
self.message_db.update_message_image_path(msg.id, download_path)
|
self.message_db.update_message_image_path(msg.msg_id, download_path)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
'message_id': msg.id,
|
'message_id': msg.msg_id,
|
||||||
'roomid': msg.roomid,
|
'roomid': msg.roomid,
|
||||||
'sender': msg.sender,
|
'sender': msg.sender,
|
||||||
'file_path': download_path
|
'file_path': download_path
|
||||||
@@ -137,7 +151,7 @@ class MessageStorage:
|
|||||||
else:
|
else:
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message_id': msg.id,
|
'message_id': msg.msg_id,
|
||||||
'roomid': msg.roomid,
|
'roomid': msg.roomid,
|
||||||
'sender': msg.sender,
|
'sender': msg.sender,
|
||||||
'error': "图片下载失败"
|
'error': "图片下载失败"
|
||||||
@@ -145,21 +159,25 @@ class MessageStorage:
|
|||||||
else:
|
else:
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message_id': msg.id,
|
'message_id': msg.msg_id,
|
||||||
'roomid': msg.roomid,
|
'roomid': msg.roomid,
|
||||||
'sender': msg.sender,
|
'sender': msg.sender,
|
||||||
'error': "WCF实例不存在或消息ID无效"
|
'error': "WCF实例不存在或消息ID无效"
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"图片处理出错: {msg.id}, 错误: {e}")
|
logger.error(f"图片处理出错: {msg.msg_id}, 错误: {e}")
|
||||||
return {
|
return {
|
||||||
'success': False,
|
'success': False,
|
||||||
'message_id': msg.id,
|
'message_id': msg.msg_id,
|
||||||
'roomid': msg.roomid,
|
'roomid': msg.roomid,
|
||||||
'sender': msg.sender,
|
'sender': msg.sender,
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def download_file_from_url(self, url: str, target_dir: str) -> str:
|
||||||
|
# TODO 根据获取的文件地址,从server 下载 :http://{服务ip}:2532/download/{接口返回的文件路径}
|
||||||
|
return ""
|
||||||
|
|
||||||
def _process_image_callback(self, future):
|
def _process_image_callback(self, future):
|
||||||
"""处理异步图片处理任务完成后的回调"""
|
"""处理异步图片处理任务完成后的回调"""
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user