feat:初版
This commit is contained in:
19
WechatHook/__init__.py
Normal file
19
WechatHook/__init__.py
Normal file
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
WechatHook - 微信 Hook API 封装层
|
||||
|
||||
基于个微大客户版 DLL 实现的微信 Hook 接口封装
|
||||
"""
|
||||
|
||||
from .loader import NoveLoader
|
||||
from .client import WechatHookClient
|
||||
from .message_types import MessageType, MESSAGE_TYPE_MAP, normalize_message
|
||||
|
||||
__all__ = [
|
||||
'NoveLoader',
|
||||
'WechatHookClient',
|
||||
'MessageType',
|
||||
'MESSAGE_TYPE_MAP',
|
||||
'normalize_message',
|
||||
]
|
||||
|
||||
__version__ = '1.0.0'
|
||||
192
WechatHook/callbacks.py
Normal file
192
WechatHook/callbacks.py
Normal file
@@ -0,0 +1,192 @@
|
||||
"""
|
||||
回调处理器
|
||||
|
||||
实现 Socket 回调的装饰器和处理机制
|
||||
"""
|
||||
|
||||
import copy
|
||||
import ctypes
|
||||
from ctypes import WINFUNCTYPE
|
||||
from functools import wraps
|
||||
from typing import Callable, List
|
||||
from loguru import logger
|
||||
|
||||
|
||||
# 全局回调列表
|
||||
_GLOBAL_CONNECT_CALLBACK_LIST: List[Callable] = []
|
||||
_GLOBAL_RECV_CALLBACK_LIST: List[Callable] = []
|
||||
_GLOBAL_CLOSE_CALLBACK_LIST: List[Callable] = []
|
||||
|
||||
|
||||
def CONNECT_CALLBACK(in_class: bool = False):
|
||||
"""
|
||||
连接回调装饰器
|
||||
|
||||
Args:
|
||||
in_class: 是否是类方法
|
||||
|
||||
Usage:
|
||||
@CONNECT_CALLBACK()
|
||||
def on_connect(client_id):
|
||||
pass
|
||||
|
||||
@CONNECT_CALLBACK(in_class=True)
|
||||
def on_connect(self, client_id):
|
||||
pass
|
||||
"""
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
return f(*args, **kwargs)
|
||||
|
||||
if in_class:
|
||||
wrapper._wx_connect_handled = True
|
||||
else:
|
||||
_GLOBAL_CONNECT_CALLBACK_LIST.append(wrapper)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
def RECV_CALLBACK(in_class: bool = False):
|
||||
"""
|
||||
接收消息回调装饰器
|
||||
|
||||
Args:
|
||||
in_class: 是否是类方法
|
||||
|
||||
Usage:
|
||||
@RECV_CALLBACK()
|
||||
def on_receive(client_id, message_type, data):
|
||||
pass
|
||||
|
||||
@RECV_CALLBACK(in_class=True)
|
||||
def on_receive(self, client_id, message_type, data):
|
||||
pass
|
||||
"""
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
return f(*args, **kwargs)
|
||||
|
||||
if in_class:
|
||||
wrapper._wx_recv_handled = True
|
||||
else:
|
||||
_GLOBAL_RECV_CALLBACK_LIST.append(wrapper)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
def CLOSE_CALLBACK(in_class: bool = False):
|
||||
"""
|
||||
断开连接回调装饰器
|
||||
|
||||
Args:
|
||||
in_class: 是否是类方法
|
||||
|
||||
Usage:
|
||||
@CLOSE_CALLBACK()
|
||||
def on_close(client_id):
|
||||
pass
|
||||
|
||||
@CLOSE_CALLBACK(in_class=True)
|
||||
def on_close(self, client_id):
|
||||
pass
|
||||
"""
|
||||
def decorator(f):
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
return f(*args, **kwargs)
|
||||
|
||||
if in_class:
|
||||
wrapper._wx_close_handled = True
|
||||
else:
|
||||
_GLOBAL_CLOSE_CALLBACK_LIST.append(wrapper)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
def add_callback_handler(callback_handler):
|
||||
"""
|
||||
添加回调处理器实例
|
||||
|
||||
Args:
|
||||
callback_handler: 包含回调方法的对象
|
||||
"""
|
||||
import inspect
|
||||
|
||||
for name, method in inspect.getmembers(callback_handler, callable):
|
||||
if hasattr(method, '_wx_connect_handled'):
|
||||
_GLOBAL_CONNECT_CALLBACK_LIST.append(method)
|
||||
logger.debug(f"注册连接回调: {name}")
|
||||
elif hasattr(method, '_wx_recv_handled'):
|
||||
_GLOBAL_RECV_CALLBACK_LIST.append(method)
|
||||
logger.debug(f"注册接收回调: {name}")
|
||||
elif hasattr(method, '_wx_close_handled'):
|
||||
_GLOBAL_CLOSE_CALLBACK_LIST.append(method)
|
||||
logger.debug(f"注册断开回调: {name}")
|
||||
|
||||
|
||||
@WINFUNCTYPE(None, ctypes.c_void_p)
|
||||
def wechat_connect_callback(client_id):
|
||||
"""
|
||||
微信连接回调(C 函数)
|
||||
|
||||
Args:
|
||||
client_id: 客户端 ID
|
||||
"""
|
||||
logger.info(f"[回调] 客户端连接: {client_id}")
|
||||
for func in _GLOBAL_CONNECT_CALLBACK_LIST:
|
||||
try:
|
||||
func(client_id)
|
||||
except Exception as e:
|
||||
logger.error(f"连接回调执行失败: {e}")
|
||||
|
||||
|
||||
@WINFUNCTYPE(None, ctypes.c_long, ctypes.c_char_p, ctypes.c_ulong)
|
||||
def wechat_recv_callback(client_id, data, length):
|
||||
"""
|
||||
微信接收消息回调(C 函数)
|
||||
|
||||
Args:
|
||||
client_id: 客户端 ID
|
||||
data: 消息数据(JSON 字符串)
|
||||
length: 数据长度
|
||||
"""
|
||||
try:
|
||||
import json
|
||||
|
||||
# 深拷贝数据
|
||||
data = copy.deepcopy(data)
|
||||
json_data = data.decode('utf-8')
|
||||
dict_data = json.loads(json_data)
|
||||
|
||||
msg_type = dict_data.get('type')
|
||||
msg_data = dict_data.get('data', {})
|
||||
|
||||
logger.info(f"[回调] 收到消息: type={msg_type}, data={msg_data}")
|
||||
|
||||
# 调用所有注册的回调
|
||||
for func in _GLOBAL_RECV_CALLBACK_LIST:
|
||||
try:
|
||||
func(client_id, msg_type, msg_data)
|
||||
except Exception as e:
|
||||
logger.error(f"接收回调执行失败: {e}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"解析消息失败: {e}")
|
||||
|
||||
|
||||
@WINFUNCTYPE(None, ctypes.c_ulong)
|
||||
def wechat_close_callback(client_id):
|
||||
"""
|
||||
微信断开连接回调(C 函数)
|
||||
|
||||
Args:
|
||||
client_id: 客户端 ID
|
||||
"""
|
||||
logger.warning(f"[回调] 客户端断开: {client_id}")
|
||||
for func in _GLOBAL_CLOSE_CALLBACK_LIST:
|
||||
try:
|
||||
func(client_id)
|
||||
except Exception as e:
|
||||
logger.error(f"断开回调执行失败: {e}")
|
||||
1331
WechatHook/client.py
Normal file
1331
WechatHook/client.py
Normal file
File diff suppressed because it is too large
Load Diff
311
WechatHook/loader.py
Normal file
311
WechatHook/loader.py
Normal file
@@ -0,0 +1,311 @@
|
||||
"""
|
||||
NoveLoader - DLL 加载器和函数封装
|
||||
|
||||
基于个微大客户版 Loader.dll 的 Python 封装
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
import os
|
||||
from ctypes import WinDLL, create_string_buffer, WINFUNCTYPE
|
||||
from typing import Callable
|
||||
from loguru import logger
|
||||
|
||||
|
||||
def c_string(data: str) -> ctypes.c_char_p:
|
||||
"""将 Python 字符串转换为 C 字符串"""
|
||||
return ctypes.c_char_p(data.encode('utf-8'))
|
||||
|
||||
|
||||
def create_shared_memory():
|
||||
"""创建共享内存(用于DLL通信)"""
|
||||
try:
|
||||
kernel32 = ctypes.WinDLL('kernel32')
|
||||
|
||||
# 创建文件映射
|
||||
file_handle = kernel32.CreateFileMappingA(
|
||||
-1,
|
||||
None,
|
||||
4, # PAGE_READWRITE
|
||||
0,
|
||||
33,
|
||||
"windows_shell_global__".encode('utf-8')
|
||||
)
|
||||
|
||||
if not file_handle:
|
||||
logger.warning("创建共享内存失败")
|
||||
return None, None
|
||||
|
||||
# 映射到内存
|
||||
data_address = kernel32.MapViewOfFile(
|
||||
file_handle,
|
||||
983071, # FILE_MAP_ALL_ACCESS
|
||||
0,
|
||||
0,
|
||||
0
|
||||
)
|
||||
|
||||
if not data_address:
|
||||
logger.warning("映射共享内存失败")
|
||||
kernel32.CloseHandle(file_handle)
|
||||
return None, None
|
||||
|
||||
# 写入Key数据
|
||||
key = "3101b223dca7715b0154924f0eeeee20".encode('utf-8')
|
||||
kernel32.RtlMoveMemory(data_address, key, len(key))
|
||||
|
||||
logger.success("共享内存创建成功")
|
||||
return file_handle, data_address
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"创建共享内存异常: {e}")
|
||||
return None, None
|
||||
|
||||
|
||||
class NoveLoader:
|
||||
"""
|
||||
Loader.dll 封装类
|
||||
|
||||
通过内存偏移调用 DLL 中的未导出函数
|
||||
"""
|
||||
|
||||
# 加载器模块基址
|
||||
loader_module_base: int = 0
|
||||
|
||||
# 函数偏移地址(基于 Loader.dll)
|
||||
_InitWeChatSocket: int = 0xB080
|
||||
_GetUserWeChatVersion: int = 0xCB80
|
||||
_InjectWeChat: int = 0xCC10
|
||||
_SendWeChatData: int = 0xAF90
|
||||
_DestroyWeChat: int = 0xC540
|
||||
_UseUtf8: int = 0xC680
|
||||
_InjectWeChat2: int = 0x14D7
|
||||
_InjectWeChatPid: int = 0xB750
|
||||
_InjectWeChatMultiOpen: int = 0x33B2
|
||||
|
||||
def __init__(self, loader_path: str):
|
||||
"""
|
||||
初始化 Loader
|
||||
|
||||
Args:
|
||||
loader_path: Loader.dll 的路径
|
||||
"""
|
||||
loader_path = os.path.realpath(loader_path)
|
||||
if not os.path.exists(loader_path):
|
||||
logger.error(f'Loader.dll 不存在: {loader_path}')
|
||||
raise FileNotFoundError(f'Loader.dll 不存在: {loader_path}')
|
||||
|
||||
logger.info(f"加载 Loader.dll: {loader_path}")
|
||||
loader_module = WinDLL(loader_path)
|
||||
self.loader_module_base = loader_module._handle
|
||||
|
||||
# 使用 UTF-8 编码
|
||||
self.UseUtf8()
|
||||
logger.success("Loader.dll 加载成功")
|
||||
|
||||
# 初始化回调
|
||||
from WechatHook.callbacks import (
|
||||
wechat_connect_callback,
|
||||
wechat_recv_callback,
|
||||
wechat_close_callback
|
||||
)
|
||||
self.InitWeChatSocket(
|
||||
wechat_connect_callback,
|
||||
wechat_recv_callback,
|
||||
wechat_close_callback
|
||||
)
|
||||
|
||||
def __get_non_exported_func(self, offset: int, arg_types, return_type):
|
||||
"""
|
||||
通过内存偏移获取未导出的函数
|
||||
|
||||
Args:
|
||||
offset: 函数相对于模块基址的偏移
|
||||
arg_types: 参数类型列表
|
||||
return_type: 返回值类型
|
||||
|
||||
Returns:
|
||||
可调用的函数对象
|
||||
"""
|
||||
func_addr = self.loader_module_base + offset
|
||||
if arg_types:
|
||||
func_type = ctypes.WINFUNCTYPE(return_type, *arg_types)
|
||||
else:
|
||||
func_type = ctypes.WINFUNCTYPE(return_type)
|
||||
return func_type(func_addr)
|
||||
|
||||
def InitWeChatSocket(
|
||||
self,
|
||||
connect_callback: Callable,
|
||||
recv_callback: Callable,
|
||||
close_callback: Callable
|
||||
) -> bool:
|
||||
"""
|
||||
初始化微信 Socket 回调
|
||||
|
||||
Args:
|
||||
connect_callback: 连接回调函数
|
||||
recv_callback: 接收消息回调函数
|
||||
close_callback: 断开连接回调函数
|
||||
|
||||
Returns:
|
||||
是否初始化成功
|
||||
"""
|
||||
func = self.__get_non_exported_func(
|
||||
self._InitWeChatSocket,
|
||||
[ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p],
|
||||
ctypes.c_bool
|
||||
)
|
||||
result = func(connect_callback, recv_callback, close_callback)
|
||||
logger.info(f"InitWeChatSocket: {result}")
|
||||
return result
|
||||
|
||||
def GetUserWeChatVersion(self) -> str:
|
||||
"""
|
||||
获取用户电脑上安装的微信版本
|
||||
|
||||
Returns:
|
||||
微信版本号,如 "3.9.10.19"
|
||||
"""
|
||||
func = self.__get_non_exported_func(
|
||||
self._GetUserWeChatVersion,
|
||||
[ctypes.c_char_p],
|
||||
ctypes.c_bool
|
||||
)
|
||||
out = create_string_buffer(30)
|
||||
if func(out):
|
||||
version = out.value.decode('utf-8')
|
||||
logger.info(f"微信版本: {version}")
|
||||
return version
|
||||
else:
|
||||
logger.warning("获取微信版本失败")
|
||||
return ''
|
||||
|
||||
def InjectWeChat(self, dll_path: str) -> int:
|
||||
"""
|
||||
注入微信(智能多开)
|
||||
|
||||
Args:
|
||||
dll_path: Helper.dll 的路径
|
||||
|
||||
Returns:
|
||||
客户端 ID(进程 ID),失败返回 0
|
||||
"""
|
||||
dll_path = os.path.realpath(dll_path)
|
||||
if not os.path.exists(dll_path):
|
||||
logger.error(f'Helper.dll 不存在: {dll_path}')
|
||||
return 0
|
||||
|
||||
func = self.__get_non_exported_func(
|
||||
self._InjectWeChat,
|
||||
[ctypes.c_char_p],
|
||||
ctypes.c_uint32
|
||||
)
|
||||
client_id = func(c_string(dll_path))
|
||||
if client_id:
|
||||
logger.success(f"注入微信成功,客户端 ID: {client_id}")
|
||||
else:
|
||||
logger.error("注入微信失败")
|
||||
return client_id
|
||||
|
||||
def SendWeChatData(self, client_id: int, message: str) -> bool:
|
||||
"""
|
||||
向微信发送数据
|
||||
|
||||
Args:
|
||||
client_id: 客户端 ID
|
||||
message: JSON 格式的消息
|
||||
|
||||
Returns:
|
||||
是否发送成功
|
||||
"""
|
||||
func = self.__get_non_exported_func(
|
||||
self._SendWeChatData,
|
||||
[ctypes.c_uint32, ctypes.c_char_p],
|
||||
ctypes.c_bool
|
||||
)
|
||||
result = func(client_id, c_string(message))
|
||||
return result
|
||||
|
||||
def DestroyWeChat(self) -> bool:
|
||||
"""
|
||||
销毁微信连接
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
func = self.__get_non_exported_func(
|
||||
self._DestroyWeChat,
|
||||
None,
|
||||
ctypes.c_bool
|
||||
)
|
||||
result = func()
|
||||
logger.info(f"DestroyWeChat: {result}")
|
||||
return result
|
||||
|
||||
def UseUtf8(self) -> bool:
|
||||
"""
|
||||
设置使用 UTF-8 编码
|
||||
|
||||
Returns:
|
||||
是否成功
|
||||
"""
|
||||
func = self.__get_non_exported_func(
|
||||
self._UseUtf8,
|
||||
None,
|
||||
ctypes.c_bool
|
||||
)
|
||||
return func()
|
||||
|
||||
def InjectWeChat2(self, dll_path: str, exe_path: str) -> int:
|
||||
"""
|
||||
注入微信(指定微信路径)
|
||||
|
||||
Args:
|
||||
dll_path: Helper.dll 的路径
|
||||
exe_path: WeChat.exe 的路径
|
||||
|
||||
Returns:
|
||||
客户端 ID,失败返回 0
|
||||
"""
|
||||
func = self.__get_non_exported_func(
|
||||
self._InjectWeChat2,
|
||||
[ctypes.c_char_p, ctypes.c_char_p],
|
||||
ctypes.c_uint32
|
||||
)
|
||||
return func(c_string(dll_path), c_string(exe_path))
|
||||
|
||||
def InjectWeChatPid(self, pid: int, dll_path: str) -> int:
|
||||
"""
|
||||
注入指定的微信进程
|
||||
|
||||
Args:
|
||||
pid: 微信进程 ID
|
||||
dll_path: Helper.dll 的路径
|
||||
|
||||
Returns:
|
||||
客户端 ID,失败返回 0
|
||||
"""
|
||||
func = self.__get_non_exported_func(
|
||||
self._InjectWeChatPid,
|
||||
[ctypes.c_uint32, ctypes.c_char_p],
|
||||
ctypes.c_uint32
|
||||
)
|
||||
return func(pid, c_string(dll_path))
|
||||
|
||||
def InjectWeChatMultiOpen(self, dll_path: str, exe_path: str = "") -> int:
|
||||
"""
|
||||
多开一个新的微信进程并注入
|
||||
|
||||
Args:
|
||||
dll_path: Helper.dll 的路径
|
||||
exe_path: WeChat.exe 的路径(可选)
|
||||
|
||||
Returns:
|
||||
客户端 ID,失败返回 0
|
||||
"""
|
||||
func = self.__get_non_exported_func(
|
||||
self._InjectWeChatMultiOpen,
|
||||
[ctypes.c_char_p, ctypes.c_char_p],
|
||||
ctypes.c_uint32
|
||||
)
|
||||
return func(c_string(dll_path), c_string(exe_path) if exe_path else c_string(""))
|
||||
213
WechatHook/message_types.py
Normal file
213
WechatHook/message_types.py
Normal file
@@ -0,0 +1,213 @@
|
||||
"""
|
||||
消息类型定义和映射
|
||||
|
||||
定义个微 API 的消息类型常量,以及到内部事件的映射关系
|
||||
"""
|
||||
|
||||
|
||||
class MessageType:
|
||||
"""消息类型常量(基于实际测试)"""
|
||||
# 系统消息类型
|
||||
MT_DEBUG_LOG = 11024 # 调试日志
|
||||
MT_USER_LOGIN = 11025 # 用户登录
|
||||
MT_USER_LOGOUT = 11026 # 用户登出
|
||||
MT_GET_LOGIN_INFO = 11028 # 获取登录信息
|
||||
|
||||
# 消息通知类型(基于实际测试修正)
|
||||
MT_TEXT = 11046 # 文本消息
|
||||
MT_IMAGE = 11047 # 图片消息
|
||||
MT_VOICE = 11048 # 语音消息
|
||||
MT_VIDEO = 11051 # 视频消息
|
||||
MT_EMOJI = 11052 # 表情消息
|
||||
MT_REVOKE = 11057 # 撤回消息
|
||||
MT_SYSTEM = 11058 # 系统消息
|
||||
MT_QUOTE = 11061 # 引用消息
|
||||
MT_FRIEND_REQUEST = 11056 # 好友请求
|
||||
|
||||
# 实际测试得出的正确映射
|
||||
MT_LOCATION = 11053 # 位置消息(实际)
|
||||
MT_LINK = 11054 # 链接消息(实际)
|
||||
MT_FILE = 11055 # 文件消息(实际)
|
||||
|
||||
# 兼容性定义
|
||||
MT_CARD = 11055 # 名片消息(临时兼容,实际可能是文件类型)
|
||||
MT_MINIAPP = 11054 # 小程序消息(临时兼容,实际可能是链接类型)
|
||||
|
||||
# 群聊通知类型
|
||||
MT_CHATROOM_MEMBER_ADD = 11098 # 群成员新增
|
||||
MT_CHATROOM_MEMBER_REMOVE = 11099 # 群成员删除
|
||||
MT_CHATROOM_INFO_CHANGE = 11100 # 群信息变化(成员数量变化等)
|
||||
|
||||
# 发送消息类型
|
||||
MT_SEND_TEXT = 11036 # 发送文本
|
||||
|
||||
|
||||
# 消息类型到事件名称的映射
|
||||
MESSAGE_TYPE_MAP = {
|
||||
MessageType.MT_TEXT: "text_message",
|
||||
MessageType.MT_IMAGE: "image_message",
|
||||
MessageType.MT_VOICE: "voice_message",
|
||||
MessageType.MT_VIDEO: "video_message",
|
||||
MessageType.MT_EMOJI: "emoji_message",
|
||||
MessageType.MT_REVOKE: "revoke_message",
|
||||
MessageType.MT_SYSTEM: "system_message",
|
||||
MessageType.MT_FRIEND_REQUEST: "friend_request",
|
||||
MessageType.MT_QUOTE: "quote_message",
|
||||
MessageType.MT_CHATROOM_MEMBER_ADD: "chatroom_member_add",
|
||||
MessageType.MT_CHATROOM_MEMBER_REMOVE: "chatroom_member_remove",
|
||||
MessageType.MT_CHATROOM_INFO_CHANGE: "chatroom_info_change",
|
||||
|
||||
# 修正后的映射(基于实际测试)
|
||||
MessageType.MT_LOCATION: "location_message", # 11053 -> 位置消息
|
||||
MessageType.MT_LINK: "link_message", # 11054 -> 链接消息
|
||||
MessageType.MT_FILE: "file_message", # 11055 -> 文件消息
|
||||
}
|
||||
|
||||
|
||||
def normalize_message(msg_type: int, data: dict) -> dict:
|
||||
"""
|
||||
将个微 API 的消息格式转换为统一的内部格式(兼容 XYBot)
|
||||
|
||||
Args:
|
||||
msg_type: 消息类型
|
||||
data: 原始消息数据
|
||||
|
||||
Returns:
|
||||
标准化的消息字典
|
||||
"""
|
||||
# 基础消息结构
|
||||
message = {
|
||||
"MsgType": msg_type,
|
||||
"FromWxid": data.get("from_wxid", ""),
|
||||
"ToWxid": data.get("to_wxid", ""),
|
||||
"Content": data.get("msg", data.get("content", data.get("raw_msg", ""))), # 系统消息使用 raw_msg
|
||||
"CreateTime": data.get("timestamp", data.get("create_time", 0)),
|
||||
"IsGroup": False,
|
||||
"SenderWxid": data.get("from_wxid", ""),
|
||||
}
|
||||
|
||||
# 判断是否是群聊消息(room_wxid 不为空)
|
||||
room_wxid = data.get("room_wxid", "")
|
||||
if room_wxid:
|
||||
message["IsGroup"] = True
|
||||
message["FromWxid"] = room_wxid
|
||||
message["SenderWxid"] = data.get("from_wxid", "")
|
||||
|
||||
# @ 消息处理
|
||||
if "at_user_list" in data:
|
||||
message["Ats"] = data["at_user_list"]
|
||||
elif "at_list" in data:
|
||||
message["Ats"] = data["at_list"]
|
||||
|
||||
# 图片消息
|
||||
if msg_type == MessageType.MT_IMAGE:
|
||||
message["ImagePath"] = data.get("image_path", "")
|
||||
|
||||
# 文件消息(实际类型11055)
|
||||
if msg_type == MessageType.MT_FILE:
|
||||
message["Filename"] = data.get("filename", "")
|
||||
message["FileExtend"] = data.get("file_extend", "")
|
||||
message["File"] = data.get("file_data", "")
|
||||
|
||||
# 语音消息
|
||||
if msg_type == MessageType.MT_VOICE:
|
||||
message["ImgBuf"] = {"buffer": data.get("voice_data", "")}
|
||||
|
||||
# 视频消息
|
||||
if msg_type == MessageType.MT_VIDEO:
|
||||
message["Video"] = data.get("video_data", "")
|
||||
|
||||
# 引用消息
|
||||
if "quote" in data:
|
||||
message["Quote"] = data["quote"]
|
||||
|
||||
# 引用消息的 @ 提取(从 XML 中解析)
|
||||
if msg_type == MessageType.MT_QUOTE:
|
||||
try:
|
||||
import xml.etree.ElementTree as ET
|
||||
content = message.get("Content", "")
|
||||
if content:
|
||||
root = ET.fromstring(content)
|
||||
title = root.find(".//title")
|
||||
if title is not None and title.text:
|
||||
title_text = title.text
|
||||
# 检查 title 中是否包含 @
|
||||
if "@" in title_text:
|
||||
# 从 main_config.toml 读取机器人昵称
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
config_path = Path("main_config.toml")
|
||||
if config_path.exists():
|
||||
with open(config_path, "rb") as f:
|
||||
main_config = tomllib.load(f)
|
||||
bot_nickname = main_config.get("Bot", {}).get("nickname", "")
|
||||
bot_wxid = main_config.get("Bot", {}).get("wxid", "")
|
||||
|
||||
# 检查是否 @ 了机器人
|
||||
if bot_nickname and f"@{bot_nickname}" in title_text:
|
||||
message["Ats"] = [bot_wxid] if bot_wxid else []
|
||||
except Exception:
|
||||
pass # 解析失败则忽略
|
||||
|
||||
# 位置消息(实际类型11053)
|
||||
if msg_type == MessageType.MT_LOCATION:
|
||||
message["Latitude"] = data.get("latitude", 0)
|
||||
message["Longitude"] = data.get("longitude", 0)
|
||||
message["LocationTitle"] = data.get("title", "")
|
||||
message["LocationAddress"] = data.get("address", "")
|
||||
|
||||
# 链接消息(实际类型11054)
|
||||
if msg_type == MessageType.MT_LINK:
|
||||
message["LinkTitle"] = data.get("title", "")
|
||||
message["LinkDesc"] = data.get("desc", "")
|
||||
message["LinkUrl"] = data.get("url", "")
|
||||
message["LinkThumb"] = data.get("thumb_url", "")
|
||||
message["MiniappPagePath"] = data.get("page_path", "")
|
||||
|
||||
# 好友请求
|
||||
if msg_type == MessageType.MT_FRIEND_REQUEST:
|
||||
message["V3"] = data.get("v3", "")
|
||||
message["V4"] = data.get("v4", "")
|
||||
message["Scene"] = data.get("scene", 0)
|
||||
|
||||
# 群成员新增 (type=11098)
|
||||
if msg_type == MessageType.MT_CHATROOM_MEMBER_ADD:
|
||||
message["FromWxid"] = data.get("room_wxid", "")
|
||||
message["IsGroup"] = True
|
||||
message["RoomWxid"] = data.get("room_wxid", "")
|
||||
message["RoomNickname"] = data.get("nickname", "")
|
||||
message["MemberList"] = data.get("member_list", [])
|
||||
message["TotalMember"] = data.get("total_member", 0)
|
||||
message["ManagerWxid"] = data.get("manager_wxid", "")
|
||||
|
||||
# 群成员删除 (type=11099)
|
||||
if msg_type == MessageType.MT_CHATROOM_MEMBER_REMOVE:
|
||||
message["FromWxid"] = data.get("room_wxid", "")
|
||||
message["IsGroup"] = True
|
||||
message["RoomWxid"] = data.get("room_wxid", "")
|
||||
message["RoomNickname"] = data.get("nickname", "")
|
||||
message["MemberList"] = data.get("member_list", [])
|
||||
message["TotalMember"] = data.get("total_member", 0)
|
||||
message["ManagerWxid"] = data.get("manager_wxid", "")
|
||||
|
||||
# 系统消息 (type=11058)
|
||||
if msg_type == MessageType.MT_SYSTEM:
|
||||
# 系统消息的内容在 raw_msg 字段
|
||||
message["Content"] = data.get("raw_msg", "")
|
||||
# 系统消息也可能是群聊消息
|
||||
if room_wxid:
|
||||
message["IsGroup"] = True
|
||||
message["FromWxid"] = room_wxid
|
||||
|
||||
# 群信息变化 (type=11100)
|
||||
if msg_type == MessageType.MT_CHATROOM_INFO_CHANGE:
|
||||
message["FromWxid"] = data.get("room_wxid", "")
|
||||
message["IsGroup"] = True
|
||||
message["RoomWxid"] = data.get("room_wxid", "")
|
||||
message["RoomNickname"] = data.get("nickname", "")
|
||||
message["TotalMember"] = data.get("total_member", 0)
|
||||
message["ManagerWxid"] = data.get("manager_wxid", "")
|
||||
# member_list 可能存在也可能不存在
|
||||
message["MemberList"] = data.get("member_list", [])
|
||||
|
||||
return message
|
||||
Reference in New Issue
Block a user