chore: sync current WechatHookBot workspace
This commit is contained in:
234
docs/AIChat函数调用框架与开发指南.md
Normal file
234
docs/AIChat函数调用框架与开发指南.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# AIChat 函数调用框架与开发指南
|
||||
|
||||
> 适用范围:`WechatHookBot/plugins/AIChat/main.py` 及所有通过 `ToolRegistry` 注册的插件工具。
|
||||
> 更新时间:2026-02-09
|
||||
|
||||
## 1. 目标与设计原则
|
||||
|
||||
当前 AIChat 的函数调用框架,核心目标是:
|
||||
|
||||
1. **少误触发**:只在用户明确有工具意图时暴露工具。
|
||||
2. **少泄露中间态**:不向用户输出半截 `function_call/tool_calls` 或 JSON。
|
||||
3. **可扩展**:插件只需实现 `get_llm_tools()` + `execute_llm_tool()`。
|
||||
4. **可控输出**:工具执行后,默认由 AI 再生成一条自然语言总结(Eridanus 风格)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 总体架构
|
||||
|
||||
### 2.1 核心模块
|
||||
|
||||
- AI 编排入口:`WechatHookBot/plugins/AIChat/main.py`
|
||||
- 工具注册中心:`WechatHookBot/utils/tool_registry.py`
|
||||
- 工具执行器:`WechatHookBot/utils/tool_executor.py`
|
||||
- 工具结果标准化:`WechatHookBot/utils/llm_tooling.py`
|
||||
|
||||
### 2.2 数据流(简化)
|
||||
|
||||
1. 接收消息(文本/图片)
|
||||
2. AIChat 按规则筛选本轮可用工具
|
||||
3. 预处理工具 schema(补全描述、约束参数)
|
||||
4. 注入函数调用规则到 system prompt
|
||||
5. 请求模型(流式或非流式)
|
||||
6. 提取 `tool_calls`(兼容旧 `function_call`)
|
||||
7. 通过 `ToolExecutor` 并发/串行执行工具
|
||||
8. 工具结果回填上下文,二次请求 AI 输出最终自然语言
|
||||
|
||||
---
|
||||
|
||||
## 3. 关键流程(按代码路径)
|
||||
|
||||
### 3.1 主入口
|
||||
|
||||
- 文本入口:`_call_ai_api(...)`
|
||||
- 图片入口:`_call_ai_api_with_image(...)`
|
||||
|
||||
两条链路逻辑保持一致:都支持工具选择、工具执行、二次总结回复。
|
||||
|
||||
### 3.2 工具选择(Smart Select)
|
||||
|
||||
函数:`_select_tools_for_message(...)`
|
||||
|
||||
特点:
|
||||
|
||||
- 当 `tools.smart_select=true` 时,按意图正则筛选工具。
|
||||
- 无明显工具意图时,返回空工具列表(严格模式)。
|
||||
- 对“自拍/生图”口语增加专门识别:`_looks_like_image_generation_request(...)`。
|
||||
|
||||
### 3.3 工具声明标准化
|
||||
|
||||
函数:
|
||||
|
||||
- `_normalize_tool_schema_for_llm(...)`
|
||||
- `_prepare_tools_for_llm(...)`
|
||||
|
||||
标准化动作:
|
||||
|
||||
- 补函数描述(缺失时自动补)
|
||||
- 补参数类型与描述(缺失时自动补)
|
||||
- 统一 `parameters.additionalProperties = false`
|
||||
|
||||
### 3.4 规则注入(System Prompt)
|
||||
|
||||
函数:`_build_tool_rules_prompt(...)`
|
||||
|
||||
注入规则包括:
|
||||
|
||||
- 仅基于【当前消息】决定是否调用工具
|
||||
- 参数不全先追问
|
||||
- 禁止输出工具调用 JSON 片段
|
||||
- 工具后输出自然语言总结
|
||||
|
||||
开关:`tools.rule_prompt_enabled`
|
||||
|
||||
### 3.5 工具调用提取
|
||||
|
||||
函数:`_extract_tool_calls_data(...)`
|
||||
|
||||
支持:
|
||||
|
||||
- 标准 `tool_calls`
|
||||
- 旧格式 `function_call` 兼容转换
|
||||
|
||||
并且在流式链路中组装增量 `tool_calls`,避免丢参数。
|
||||
|
||||
### 3.6 工具执行与回传
|
||||
|
||||
函数:
|
||||
|
||||
- `_execute_tools_async(...)`
|
||||
- `_execute_tools_async_with_image(...)`
|
||||
- `_continue_with_tool_results(...)`
|
||||
|
||||
行为:
|
||||
|
||||
- 通过 `ToolExecutor.execute_batch(...)` 统一执行。
|
||||
- 根据返回标志决定是否直接发送、是否二次 AI 总结。
|
||||
- 默认启用二次 AI 总结(`tools.followup_ai_reply=true`)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 关键配置项(`plugins/AIChat/config.toml`)
|
||||
|
||||
`[tools]` 相关:
|
||||
|
||||
- `smart_select`: 是否启用意图筛选
|
||||
- `loose_image_tool`: 文本形态绘图调用的宽松映射
|
||||
- `async_execute`: 工具是否后台执行
|
||||
- `followup_ai_reply`: 工具后是否二次 AI 总结
|
||||
- `rule_prompt_enabled`: 是否注入函数调用规则提示
|
||||
|
||||
建议线上默认:
|
||||
|
||||
- `smart_select = true`
|
||||
- `followup_ai_reply = true`
|
||||
- `rule_prompt_enabled = true`
|
||||
|
||||
---
|
||||
|
||||
## 5. 插件开发规范
|
||||
|
||||
## 5.1 工具定义接口
|
||||
|
||||
插件实现:
|
||||
|
||||
- `get_llm_tools() -> List[dict]`
|
||||
- `execute_llm_tool(tool_name, arguments, bot, from_wxid) -> dict`
|
||||
|
||||
`get_llm_tools()` 建议:
|
||||
|
||||
1. `description` 里明确:**何时调用**、**何时不要调用**。
|
||||
2. 参数写清语义、可选值、默认行为。
|
||||
3. `required` 只保留真正必需参数。
|
||||
4. 加 `additionalProperties: false`,减少幻觉参数。
|
||||
|
||||
### 5.2 工具返回结构约定
|
||||
|
||||
基础字段:
|
||||
|
||||
- `success: bool`
|
||||
- `message: str`
|
||||
|
||||
可选控制字段(由 `ToolResult/ToolCallResult` 消费):
|
||||
|
||||
- `need_ai_reply`: 强制进入二次 AI 总结
|
||||
- `already_sent`: 工具内部已发送过消息
|
||||
- `send_result_text`: 是否直接发送 `message`
|
||||
- `no_reply`: 本轮不需要任何回复
|
||||
- `save_to_memory`: 是否保存到上下文记忆
|
||||
|
||||
---
|
||||
|
||||
## 6. 推荐模板
|
||||
|
||||
### 6.1 工具声明模板
|
||||
|
||||
```python
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "search_xxx",
|
||||
"description": "仅当用户明确要求检索XXX时调用;普通闲聊不要调用。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "检索关键词,尽量简洁明确。"
|
||||
}
|
||||
},
|
||||
"required": ["query"],
|
||||
"additionalProperties": False
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 工具执行返回模板
|
||||
|
||||
```python
|
||||
return {
|
||||
"success": True,
|
||||
"message": "已完成检索",
|
||||
"need_ai_reply": True
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 常见问题排查
|
||||
|
||||
### 7.1 用户说“看看自拍”却不触发生图
|
||||
|
||||
先检查:
|
||||
|
||||
1. `tools.smart_select` 是否开启
|
||||
2. 生图插件工具是否在白名单中
|
||||
3. 日志里“本次启用工具”是否包含生图工具名
|
||||
|
||||
当前已加入自拍口语识别(`_looks_like_image_generation_request`),如仍异常优先看意图文本是否被预处理掉关键字。
|
||||
|
||||
### 7.2 出现半截 JSON / function_call 泄露
|
||||
|
||||
检查:
|
||||
|
||||
1. `rule_prompt_enabled` 是否开启
|
||||
2. 流式链路是否提前发送 preview(应禁用)
|
||||
3. 是否落入旧模型 `function_call` 兼容分支但未成功转工具
|
||||
|
||||
### 7.3 工具执行后无最终文本
|
||||
|
||||
检查:
|
||||
|
||||
1. `followup_ai_reply` 是否开启
|
||||
2. 工具返回是否误设 `no_reply=true`
|
||||
3. `_continue_with_tool_results` 是否收到有效 `tool_results`
|
||||
|
||||
---
|
||||
|
||||
## 8. 后续建议
|
||||
|
||||
1. 继续统一所有插件工具描述风格(触发边界 + 禁用条件)。
|
||||
2. 在日志中增加“本轮 allow 工具集合”调试行,提升定位速度。
|
||||
3. 为高频工具加回归用例(最少做 py_compile + simulate 场景脚本)。
|
||||
|
||||
694
docs/API文档.md
694
docs/API文档.md
@@ -1,694 +0,0 @@
|
||||
# WechatHookBot API 文档
|
||||
|
||||
## WechatHookClient API 参考
|
||||
|
||||
基于个微大客户版 Hook API 封装的 Python 客户端。
|
||||
|
||||
## 消息发送
|
||||
|
||||
### send_text - 发送文本消息
|
||||
|
||||
```python
|
||||
await client.send_text(to_wxid: str, content: str) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `to_wxid`: 接收者 wxid(个人或群聊)
|
||||
- `content`: 文本内容
|
||||
|
||||
**返回:** 是否发送成功
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.send_text("wxid_xxx", "你好,世界")
|
||||
await client.send_text("123@chatroom", "群聊消息")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### send_image - 发送图片
|
||||
|
||||
```python
|
||||
await client.send_image(to_wxid: str, image_path: str) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `to_wxid`: 接收者 wxid
|
||||
- `image_path`: 图片文件路径
|
||||
|
||||
**支持格式:** jpg, png, gif
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.send_image("wxid_xxx", "D:/images/photo.jpg")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### send_file - 发送文件
|
||||
|
||||
```python
|
||||
await client.send_file(to_wxid: str, file_path: str) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `to_wxid`: 接收者 wxid
|
||||
- `file_path`: 文件路径
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.send_file("wxid_xxx", "D:/documents/report.pdf")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### send_video - 发送视频
|
||||
|
||||
```python
|
||||
await client.send_video(to_wxid: str, video_path: str) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `to_wxid`: 接收者 wxid
|
||||
- `video_path`: 视频文件路径
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.send_video("wxid_xxx", "D:/videos/demo.mp4")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### send_card - 发送名片
|
||||
|
||||
```python
|
||||
await client.send_card(to_wxid: str, card_wxid: str, card_nickname: str) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `to_wxid`: 接收者 wxid
|
||||
- `card_wxid`: 名片的 wxid
|
||||
- `card_nickname`: 名片昵称
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.send_card("wxid_xxx", "wxid_yyy", "张三")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### send_location - 发送位置
|
||||
|
||||
```python
|
||||
await client.send_location(
|
||||
to_wxid: str,
|
||||
latitude: float,
|
||||
longitude: float,
|
||||
title: str,
|
||||
address: str
|
||||
) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `to_wxid`: 接收者 wxid
|
||||
- `latitude`: 纬度
|
||||
- `longitude`: 经度
|
||||
- `title`: 位置标题
|
||||
- `address`: 详细地址
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.send_location(
|
||||
"wxid_xxx",
|
||||
39.9042,
|
||||
116.4074,
|
||||
"天安门",
|
||||
"北京市东城区"
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### send_link - 发送链接
|
||||
|
||||
```python
|
||||
await client.send_link(
|
||||
to_wxid: str,
|
||||
title: str,
|
||||
desc: str,
|
||||
url: str,
|
||||
thumb_url: str = ""
|
||||
) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `to_wxid`: 接收者 wxid
|
||||
- `title`: 链接标题
|
||||
- `desc`: 链接描述
|
||||
- `url`: 链接地址
|
||||
- `thumb_url`: 缩略图 URL(可选)
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.send_link(
|
||||
"wxid_xxx",
|
||||
"新闻标题",
|
||||
"新闻摘要",
|
||||
"https://example.com/news",
|
||||
"https://example.com/thumb.jpg"
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### send_miniapp - 发送小程序
|
||||
|
||||
```python
|
||||
await client.send_miniapp(
|
||||
to_wxid: str,
|
||||
appid: str,
|
||||
title: str,
|
||||
page_path: str,
|
||||
thumb_url: str = ""
|
||||
) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `to_wxid`: 接收者 wxid
|
||||
- `appid`: 小程序 appid
|
||||
- `title`: 小程序标题
|
||||
- `page_path`: 小程序页面路径
|
||||
- `thumb_url`: 缩略图 URL(可选)
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.send_miniapp(
|
||||
"wxid_xxx",
|
||||
"wx1234567890",
|
||||
"小程序标题",
|
||||
"pages/index/index"
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### send_at_message - 发送群聊@消息
|
||||
|
||||
```python
|
||||
await client.send_at_message(
|
||||
chatroom_id: str,
|
||||
content: str,
|
||||
at_list: list[str]
|
||||
) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `chatroom_id`: 群聊 ID
|
||||
- `content`: 消息内容
|
||||
- `at_list`: 要@的 wxid 列表
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
# @指定用户
|
||||
await client.send_at_message(
|
||||
"123@chatroom",
|
||||
"大家好",
|
||||
["wxid_aaa", "wxid_bbb"]
|
||||
)
|
||||
|
||||
# @所有人
|
||||
await client.send_at_message(
|
||||
"123@chatroom",
|
||||
"重要通知",
|
||||
["notify@all"]
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### revoke_message - 撤回消息
|
||||
|
||||
```python
|
||||
await client.revoke_message(msg_id: str) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `msg_id`: 消息 ID
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.revoke_message("1234567890")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 好友管理
|
||||
|
||||
### get_friend_list - 获取好友列表
|
||||
|
||||
```python
|
||||
await client.get_friend_list() -> list[dict]
|
||||
```
|
||||
|
||||
**返回:** 好友列表
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
friends = await client.get_friend_list()
|
||||
for friend in friends:
|
||||
print(friend["wxid"], friend["nickname"])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### get_friend_info - 获取好友信息
|
||||
|
||||
```python
|
||||
await client.get_friend_info(wxid: str) -> dict
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `wxid`: 好友 wxid
|
||||
|
||||
**返回:** 好友详细信息
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
info = await client.get_friend_info("wxid_xxx")
|
||||
print(info["nickname"], info["remark"])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### search_user - 搜索用户
|
||||
|
||||
```python
|
||||
await client.search_user(keyword: str) -> dict
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `keyword`: 搜索关键词(wxid、手机号、微信号)
|
||||
|
||||
**返回:** 用户信息
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
result = await client.search_user("wxid_xxx")
|
||||
if result:
|
||||
print(result["nickname"])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### add_friend - 添加好友
|
||||
|
||||
```python
|
||||
await client.add_friend(
|
||||
wxid: str,
|
||||
verify_msg: str = "",
|
||||
scene: int = 3
|
||||
) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `wxid`: 要添加的 wxid
|
||||
- `verify_msg`: 验证消息
|
||||
- `scene`: 添加场景(3=搜索,15=名片)
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.add_friend("wxid_xxx", "你好,我是...")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### accept_friend - 同意好友请求
|
||||
|
||||
```python
|
||||
await client.accept_friend(v3: str, v4: str, scene: int) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `v3`: 好友请求的 v3 参数
|
||||
- `v4`: 好友请求的 v4 参数
|
||||
- `scene`: 场景值
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
# 从好友请求消息中获取参数
|
||||
await client.accept_friend(v3, v4, scene)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### delete_friend - 删除好友
|
||||
|
||||
```python
|
||||
await client.delete_friend(wxid: str) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `wxid`: 要删除的好友 wxid
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.delete_friend("wxid_xxx")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### set_friend_remark - 修改好友备注
|
||||
|
||||
```python
|
||||
await client.set_friend_remark(wxid: str, remark: str) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `wxid`: 好友 wxid
|
||||
- `remark`: 新备注
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.set_friend_remark("wxid_xxx", "张三")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### check_friend_status - 检测好友状态
|
||||
|
||||
```python
|
||||
await client.check_friend_status(wxid: str) -> dict
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `wxid`: 好友 wxid
|
||||
|
||||
**返回:** 好友状态信息
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
status = await client.check_friend_status("wxid_xxx")
|
||||
print(status["is_friend"]) # 是否是好友
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 群聊管理
|
||||
|
||||
### get_chatroom_list - 获取群聊列表
|
||||
|
||||
```python
|
||||
await client.get_chatroom_list() -> list[dict]
|
||||
```
|
||||
|
||||
**返回:** 群聊列表
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
chatrooms = await client.get_chatroom_list()
|
||||
for room in chatrooms:
|
||||
print(room["chatroom_id"], room["name"])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### get_chatroom_members - 获取群成员
|
||||
|
||||
```python
|
||||
await client.get_chatroom_members(chatroom_id: str) -> list[dict]
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `chatroom_id`: 群聊 ID
|
||||
|
||||
**返回:** 群成员列表
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
members = await client.get_chatroom_members("123@chatroom")
|
||||
for member in members:
|
||||
print(member["wxid"], member["nickname"])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### get_chatroom_info - 获取群信息
|
||||
|
||||
```python
|
||||
await client.get_chatroom_info(chatroom_id: str) -> dict
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `chatroom_id`: 群聊 ID
|
||||
|
||||
**返回:** 群聊详细信息
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
info = await client.get_chatroom_info("123@chatroom")
|
||||
print(info["name"], info["member_count"])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### create_chatroom - 创建群聊
|
||||
|
||||
```python
|
||||
await client.create_chatroom(member_list: list[str]) -> str
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `member_list`: 成员 wxid 列表(至少2人)
|
||||
|
||||
**返回:** 新群聊的 chatroom_id
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
chatroom_id = await client.create_chatroom(["wxid_aaa", "wxid_bbb"])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### invite_to_chatroom - 邀请进群
|
||||
|
||||
```python
|
||||
await client.invite_to_chatroom(
|
||||
chatroom_id: str,
|
||||
wxid_list: list[str]
|
||||
) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `chatroom_id`: 群聊 ID
|
||||
- `wxid_list`: 要邀请的 wxid 列表
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.invite_to_chatroom("123@chatroom", ["wxid_xxx", "wxid_yyy"])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### remove_chatroom_member - 踢出群成员
|
||||
|
||||
```python
|
||||
await client.remove_chatroom_member(
|
||||
chatroom_id: str,
|
||||
wxid_list: list[str]
|
||||
) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `chatroom_id`: 群聊 ID
|
||||
- `wxid_list`: 要踢出的 wxid 列表
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.remove_chatroom_member("123@chatroom", ["wxid_xxx"])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### quit_chatroom - 退出群聊
|
||||
|
||||
```python
|
||||
await client.quit_chatroom(chatroom_id: str) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `chatroom_id`: 群聊 ID
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.quit_chatroom("123@chatroom")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### set_chatroom_name - 修改群名称
|
||||
|
||||
```python
|
||||
await client.set_chatroom_name(chatroom_id: str, name: str) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `chatroom_id`: 群聊 ID
|
||||
- `name`: 新群名称
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.set_chatroom_name("123@chatroom", "新群名")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### set_chatroom_announcement - 修改群公告
|
||||
|
||||
```python
|
||||
await client.set_chatroom_announcement(
|
||||
chatroom_id: str,
|
||||
announcement: str
|
||||
) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `chatroom_id`: 群聊 ID
|
||||
- `announcement`: 群公告内容
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.set_chatroom_announcement("123@chatroom", "群公告内容")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### set_my_chatroom_nickname - 修改我的群昵称
|
||||
|
||||
```python
|
||||
await client.set_my_chatroom_nickname(
|
||||
chatroom_id: str,
|
||||
nickname: str
|
||||
) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `chatroom_id`: 群聊 ID
|
||||
- `nickname`: 新昵称
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.set_my_chatroom_nickname("123@chatroom", "我的群昵称")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 登录信息
|
||||
|
||||
### get_login_info - 获取当前登录信息
|
||||
|
||||
```python
|
||||
await client.get_login_info() -> dict
|
||||
```
|
||||
|
||||
**返回:** 登录账号信息
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
info = await client.get_login_info()
|
||||
print(info["wxid"], info["nickname"])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CDN 功能
|
||||
|
||||
### cdn_upload - CDN 上传
|
||||
|
||||
```python
|
||||
await client.cdn_upload(file_path: str) -> dict
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `file_path`: 文件路径
|
||||
|
||||
**返回:** CDN 信息(包含 aes_key, file_id 等)
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
cdn_info = await client.cdn_upload("D:/files/image.jpg")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### cdn_download - CDN 下载
|
||||
|
||||
```python
|
||||
await client.cdn_download(
|
||||
aes_key: str,
|
||||
file_id: str,
|
||||
save_path: str
|
||||
) -> bool
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `aes_key`: AES 密钥
|
||||
- `file_id`: 文件 ID
|
||||
- `save_path`: 保存路径
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
await client.cdn_download(aes_key, file_id, "D:/downloads/file.jpg")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 企业微信
|
||||
|
||||
### get_work_chatroom_list - 获取企业群列表
|
||||
|
||||
```python
|
||||
await client.get_work_chatroom_list() -> list[dict]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### get_work_friend_list - 获取企业好友列表
|
||||
|
||||
```python
|
||||
await client.get_work_friend_list() -> list[dict]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### get_work_chatroom_members - 获取企业群成员
|
||||
|
||||
```python
|
||||
await client.get_work_chatroom_members(chatroom_id: str) -> list[dict]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 错误处理
|
||||
|
||||
所有 API 调用失败时会抛出异常或返回 False/None,建议使用 try-except 处理:
|
||||
|
||||
```python
|
||||
try:
|
||||
result = await client.send_text("wxid_xxx", "消息")
|
||||
if result:
|
||||
logger.info("发送成功")
|
||||
else:
|
||||
logger.error("发送失败")
|
||||
except Exception as e:
|
||||
logger.error(f"发送异常: {e}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 所有 API 都是异步函数,必须使用 `await` 调用
|
||||
2. wxid 格式:个人为 `wxid_xxx`,群聊为 `xxx@chatroom`
|
||||
3. 文件路径建议使用绝对路径
|
||||
4. 部分 API 需要特定权限(如群主才能踢人)
|
||||
5. API 调用频率不宜过高,避免风控
|
||||
176
docs/CDN图片下载接口实现说明.md
Normal file
176
docs/CDN图片下载接口实现说明.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# CDN 图片下载接口实现说明(WechatHookBot)
|
||||
|
||||
本文档说明当前项目中“通过 CDN 参数下载图片”的真实实现路径、参数来源、容错策略与缓存行为,便于排障和二次开发。
|
||||
|
||||
## 1. 结论概览
|
||||
|
||||
- 当前项目的核心 CDN 下载接口是 `HttpClient.cdn_download_image(...)`。
|
||||
- 实际请求的 Hook API 端点是 `POST /api/cdn_download`。
|
||||
- 下载成功判定条件是响应中 `errCode == 1`。
|
||||
- 上层(`WechatHookClient`)提供了统一入口 `download_wechat_media(...)`,并在图片场景支持:
|
||||
- 从消息 XML 自动提取 `fileid/aeskey`;
|
||||
- 已知 `file_id + aes_key` 直接下载;
|
||||
- 原图失败后回退缩略图。
|
||||
|
||||
---
|
||||
|
||||
## 2. 关键代码位置
|
||||
|
||||
- 低层 HTTP 下载实现:`WechatHookBot/WechatHook/http_client.py:752`
|
||||
- 统一媒体下载入口:`WechatHookBot/WechatHook/client.py:1232`
|
||||
- XML 解析 + CDN 下载(原图/缩略图回退):`WechatHookBot/WechatHook/client.py:1352`
|
||||
- 直接 CDN 参数下载(`file_id + aes_key`):`WechatHookBot/WechatHook/client.py:1452`
|
||||
- 图片处理器封装(下载后转 base64):`WechatHookBot/utils/image_processor.py:255`
|
||||
|
||||
---
|
||||
|
||||
## 3. 调用链(从插件到 Hook API)
|
||||
|
||||
常见链路如下:
|
||||
|
||||
1. 插件层(如 `AIChat`、`GrokVideo`)拿到图片消息或引用消息里的 CDN 参数。
|
||||
2. 调用 `ImageProcessor.download_image(...)` 或 `ImageProcessor.download_image_by_cdn(...)`。
|
||||
3. `ImageProcessor` 调 `bot.download_wechat_media("image", ...)`(`bot` 即 `WechatHookClient`)。
|
||||
4. `WechatHookClient` 根据参数分发到:
|
||||
- `download_image(message, save_path)`(从 XML 提取参数)
|
||||
- 或 `download_image_by_cdn(file_id, aes_key, save_path)`
|
||||
5. 最终都进入 `HttpClient.cdn_download_image(...)`。
|
||||
6. `HttpClient` 发起 `POST /api/cdn_download` 到 Hook 端。
|
||||
|
||||
---
|
||||
|
||||
## 4. `POST /api/cdn_download` 请求细节
|
||||
|
||||
`HttpClient.cdn_download_image(...)` 组装的请求体(关键字段):
|
||||
|
||||
- `fileid`: CDN 文件标识
|
||||
- `asekey`: AES 密钥
|
||||
- `imgType`: 图片类型(`1=原图`, `2=缩略图`)
|
||||
- `out`: 本地保存路径
|
||||
|
||||
注意:
|
||||
|
||||
- 字段名是 `asekey`(不是 `aeskey`),这是按 Hook API 的实际参数约定来的。
|
||||
- 代码位置:`WechatHookBot/WechatHook/http_client.py:773`。
|
||||
|
||||
成功判定:
|
||||
|
||||
- 当响应存在且 `errCode == 1` 时判定成功并返回 `save_path`。
|
||||
- 代码位置:`WechatHookBot/WechatHook/http_client.py:801`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 参数来源与提取策略
|
||||
|
||||
### 5.1 从图片消息 XML 自动提取
|
||||
|
||||
`WechatHookClient.download_image(...)` 会解析 `message["Content"]` 的 XML:
|
||||
|
||||
- 原图 fileid 候选顺序:
|
||||
- `cdnbigimgurl`
|
||||
- `cdnmidimgurl`
|
||||
- `cdnhdimgurl`
|
||||
- `fileid`
|
||||
- aeskey:`aeskey`
|
||||
- 缩略图参数:
|
||||
- `cdnthumburl`
|
||||
- `cdnthumbaeskey`(缺失时回退 `aeskey`)
|
||||
|
||||
代码位置:`WechatHookBot/WechatHook/client.py:1386`、`WechatHookBot/WechatHook/client.py:1393`。
|
||||
|
||||
### 5.2 已知 CDN 参数直接下载
|
||||
|
||||
调用 `download_image_by_cdn(file_id, aes_key, save_path, ...)` 时,不解析 XML,直接走 CDN 下载。
|
||||
|
||||
代码位置:`WechatHookBot/WechatHook/client.py:1452`。
|
||||
|
||||
---
|
||||
|
||||
## 6. 失败回退与重试机制
|
||||
|
||||
### 6.1 上层回退(原图 -> 缩略图)
|
||||
|
||||
- 在 `download_image(...)` 中,先尝试 `imgType=1` 原图。
|
||||
- 失败后尝试 `imgType=2` 缩略图。
|
||||
- 代码位置:`WechatHookBot/WechatHook/client.py:1405`、`WechatHookBot/WechatHook/client.py:1424`。
|
||||
|
||||
### 6.2 网络重试
|
||||
|
||||
- `HttpClient.cdn_download_image(...)` 对 `httpx.ConnectError` 最多重试 2 次(总计最多 3 次尝试)。
|
||||
- 重试间隔为 `0.2 * (attempt + 1)` 秒。
|
||||
- 代码位置:`WechatHookBot/WechatHook/http_client.py:787`、`WechatHookBot/WechatHook/http_client.py:806`。
|
||||
|
||||
### 6.3 下载完成确认
|
||||
|
||||
- 上层会轮询文件是否存在且大小 `> 0`,避免“接口返回成功但文件尚未落盘”的时序问题。
|
||||
- 轮询次数 20 次、每次 0.5 秒。
|
||||
- 代码位置:`WechatHookBot/WechatHook/client.py:1416`、`WechatHookBot/WechatHook/client.py:1489`。
|
||||
|
||||
---
|
||||
|
||||
## 7. 缓存策略(两层)
|
||||
|
||||
### 7.1 WechatHookClient 文件缓存(磁盘)
|
||||
|
||||
- 路径:`WechatHookBot/temp/wechat_media_cache/`
|
||||
- TTL:3600 秒(1 小时)
|
||||
- 缓存 key 包含媒体类型 + `msg_id` 或 `cdn:file_id:aes_key`,再做 SHA1。
|
||||
- 同 key 使用 `asyncio.Lock` 防并发重复下载。
|
||||
|
||||
代码位置:
|
||||
|
||||
- 初始化:`WechatHookBot/WechatHook/client.py:52`
|
||||
- key 构建:`WechatHookBot/WechatHook/client.py:1151`
|
||||
- TTL 校验:`WechatHookBot/WechatHook/client.py:1187`
|
||||
|
||||
### 7.2 ImageProcessor 的 Redis base64 缓存(可选)
|
||||
|
||||
- `download_image(...)`(消息图)默认 `use_cache=True`,可用 `image:{msgId}` 读写缓存。
|
||||
- `download_image_by_cdn(...)` 默认 `use_cache=False`,只有显式开启才会用 `image:cdn:{file_id}`。
|
||||
- Redis 媒体缓存默认 TTL 常见为 900 秒(调用处指定)。
|
||||
|
||||
代码位置:
|
||||
|
||||
- 消息图缓存:`WechatHookBot/utils/image_processor.py:188`
|
||||
- CDN 图缓存 key:`WechatHookBot/utils/image_processor.py:281`
|
||||
- Redis 媒体缓存接口:`WechatHookBot/utils/redis_cache.py:668`
|
||||
|
||||
---
|
||||
|
||||
## 8. 并发与节流行为
|
||||
|
||||
`HttpClient` 对 Hook API 使用全局串行信号量:
|
||||
|
||||
- `self._hook_request_semaphore = asyncio.Semaphore(1)`
|
||||
- 这意味着同一时刻只有一个 Hook HTTP 请求在飞行中(包括 CDN 下载)。
|
||||
|
||||
代码位置:`WechatHookBot/WechatHook/http_client.py:37`。
|
||||
|
||||
影响:
|
||||
|
||||
- 高并发场景下更稳定,但单次吞吐会受限。
|
||||
- 日志可能出现“Hook API 排队中,等待串行执行”。
|
||||
|
||||
---
|
||||
|
||||
## 9. 兼容接口说明
|
||||
|
||||
`WechatHookClient` 仍保留了旧风格 `cdn_init/cdn_download/cdn_upload` 兼容方法,但在新协议里不推荐使用:
|
||||
|
||||
- `cdn_init()`:直接返回成功(无需初始化)
|
||||
- `cdn_download()`:提示不可用,建议改用 `download_image/download_video`
|
||||
|
||||
代码位置:`WechatHookBot/WechatHook/client.py:430`。
|
||||
|
||||
---
|
||||
|
||||
## 10. 实际开发建议
|
||||
|
||||
- 插件中优先使用 `bot.download_wechat_media("image", ...)` 作为统一入口,不要直接拼 `/api/cdn_download`。
|
||||
- 如果已有完整消息对象(含 XML),优先传 `message`,让框架自动处理原图/缩略图回退。
|
||||
- 如果只有 `file_id + aes_key`,调用 `download_image_by_cdn(...)`。
|
||||
- 排障时先看三项:
|
||||
- XML 中是否真的有 `cdnbigimgurl/aeskey`
|
||||
- Hook 返回是否 `errCode == 1`
|
||||
- 本地 `out` 指向路径是否可写、文件是否落盘
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
# WechatHookBot 项目概述
|
||||
|
||||
## 项目信息
|
||||
|
||||
- **项目名称**: WechatHookBot
|
||||
- **版本**: v1.0.0
|
||||
- **创建时间**: 2025-11-12
|
||||
- **项目类型**: 微信机器人框架
|
||||
- **技术栈**: Python 3.11 (32位), asyncio, ctypes
|
||||
|
||||
## 项目简介
|
||||
|
||||
基于个微大客户版 Hook API 的微信机器人框架,通过 DLL 注入方式实现微信消息的接收和发送。
|
||||
|
||||
## 核心特性
|
||||
|
||||
1. **DLL Hook 技术**
|
||||
- 使用 Loader.dll 和 Helper.dll 注入微信进程
|
||||
- 通过 Socket 回调接收消息
|
||||
- 支持微信版本: 2.84.18.17
|
||||
|
||||
2. **插件系统**
|
||||
- 基于事件驱动的插件架构
|
||||
- 支持热重载(无需重启)
|
||||
- 装饰器风格的事件处理
|
||||
- 插件启用/禁用管理
|
||||
|
||||
3. **消息处理**
|
||||
- 支持文本、图片、视频、文件等多种消息类型
|
||||
- 群聊和私聊消息处理
|
||||
- 消息过滤(白名单/黑名单)
|
||||
- @消息识别
|
||||
|
||||
4. **AI 集成**
|
||||
- 支持自定义 AI API
|
||||
- 可切换人设(通过 txt 文件)
|
||||
- 多种触发模式(全部/提及/@/关键词)
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
WechatHookBot/
|
||||
├── bot.py # 主入口
|
||||
├── main_config.toml # 主配置文件
|
||||
├── requirements.txt # 依赖列表
|
||||
├── libs/ # DLL 文件
|
||||
│ ├── Loader.dll
|
||||
│ └── Helper.dll
|
||||
├── WechatHook/ # Hook API 封装
|
||||
│ ├── loader.py # DLL 加载器
|
||||
│ ├── client.py # API 客户端
|
||||
│ ├── callbacks.py # 回调处理
|
||||
│ └── message_types.py # 消息类型定义
|
||||
├── utils/ # 工具模块
|
||||
│ ├── hookbot.py # 机器人核心
|
||||
│ ├── plugin_manager.py # 插件管理器
|
||||
│ ├── event_manager.py # 事件管理器
|
||||
│ └── decorators.py # 装饰器
|
||||
├── plugins/ # 插件目录
|
||||
│ ├── AIChat/ # AI 聊天插件
|
||||
│ ├── ManagePlugin/ # 插件管理插件
|
||||
│ └── ExamplePlugin/ # 示例插件
|
||||
└── docs/ # 文档目录
|
||||
└── MemoryBank/ # 内存银行
|
||||
```
|
||||
|
||||
## 当前状态
|
||||
|
||||
✅ **已完成**:
|
||||
- DLL Hook 集成
|
||||
- 消息接收和发送
|
||||
- 插件系统框架
|
||||
- AI 聊天插件
|
||||
- 插件管理功能
|
||||
- 热重载支持
|
||||
|
||||
🚧 **进行中**:
|
||||
- 测试和调试
|
||||
- 文档完善
|
||||
|
||||
📋 **待开发**:
|
||||
- 更多插件功能
|
||||
- Web 管理界面
|
||||
- 数据统计功能
|
||||
@@ -1,137 +0,0 @@
|
||||
# 开发日志
|
||||
|
||||
## 2025-11-12
|
||||
|
||||
### 项目初始化
|
||||
- ✅ 创建 WechatHookBot 项目结构
|
||||
- ✅ 从 XYBot 复制 utils 和 database 模块
|
||||
- ✅ 设计技术架构文档
|
||||
|
||||
### Hook API 集成
|
||||
- ✅ 封装 NoveLoader (Loader.dll)
|
||||
- ✅ 封装 WechatHookClient (API 客户端)
|
||||
- ✅ 实现消息类型映射和格式转换
|
||||
- ✅ 实现回调处理机制
|
||||
|
||||
### 启动调试
|
||||
**问题**: 回调未触发,无法接收消息
|
||||
- ❌ 尝试1: 调整回调注册顺序 - 失败
|
||||
- ❌ 尝试2: 修改事件循环处理 - 失败
|
||||
- ✅ 解决: 添加共享内存创建 (`create_shared_memory`)
|
||||
- 关键发现: DLL 需要共享内存进行通信
|
||||
- 参考官方 Demo 第 357-365 行
|
||||
|
||||
**问题**: Socket 客户端 ID 混淆
|
||||
- ❌ 使用进程 ID 发送消息 - 失败
|
||||
- ✅ 解决: 区分进程 ID 和 Socket 客户端 ID
|
||||
- `InjectWeChat` 返回进程 ID
|
||||
- 回调中的 `client_id` 是 Socket ID (通常为 1)
|
||||
|
||||
**问题**: 登录信息获取失败
|
||||
- ❌ 使用 type=11028 - 错误
|
||||
- ✅ 解决: 实际类型是 type=11025
|
||||
- 登录信息在注入后自动推送
|
||||
- 包含 wxid, nickname, account, avatar 等字段
|
||||
|
||||
**问题**: 消息类型映射错误
|
||||
- ❌ 使用 10001-10013 - 错误
|
||||
- ✅ 解决: 实际类型是 11046-11061
|
||||
- 文本消息: 11046
|
||||
- 图片消息: 11047
|
||||
- 其他类型依次递增
|
||||
|
||||
**问题**: 群聊消息处理失败
|
||||
- ❌ 使用 `from_wxid` 判断群聊 - 错误
|
||||
- ✅ 解决: 使用 `room_wxid` 字段判断
|
||||
- 群聊消息: `room_wxid` 不为空
|
||||
- 私聊消息: `room_wxid` 为空
|
||||
- 消息内容字段: `msg` (不是 `content`)
|
||||
|
||||
### 插件开发
|
||||
|
||||
#### AIChat 插件
|
||||
- ✅ 支持自定义 API 配置
|
||||
- ✅ 支持人设切换 (txt 文件)
|
||||
- ✅ 三种触发模式: all/mention/keyword
|
||||
- ✅ 群聊/私聊分别控制
|
||||
|
||||
**问题**: 插件配置未加载
|
||||
- ❌ 使用 `on_load` 方法 - 失败
|
||||
- ✅ 解决: 使用 `async_init` 方法
|
||||
- 插件基类只支持 `async_init`
|
||||
- `on_enable` 用于定时任务注册
|
||||
|
||||
#### ManagePlugin 插件
|
||||
- ✅ 插件列表查看
|
||||
- ✅ 热重载功能
|
||||
- ✅ 启用/禁用插件
|
||||
- ✅ 权限控制(管理员)
|
||||
|
||||
**命令列表**:
|
||||
- `/插件列表` - 查看所有插件状态
|
||||
- `/重载插件 <名称>` - 热重载指定插件
|
||||
- `/启用插件 <名称>` - 启用插件
|
||||
- `/禁用插件 <名称>` - 禁用插件
|
||||
|
||||
### 依赖管理
|
||||
**最终依赖**:
|
||||
```
|
||||
loguru==0.7.3
|
||||
APScheduler==3.11.0
|
||||
aiohttp==3.9.1
|
||||
```
|
||||
|
||||
**移除的依赖**:
|
||||
- SQLAlchemy (需要 C++ 编译)
|
||||
- eventlet (msgspec 不支持 32 位)
|
||||
- Flask (不需要 WebUI)
|
||||
|
||||
### 关键技术点
|
||||
|
||||
1. **共享内存创建**
|
||||
```python
|
||||
def create_shared_memory():
|
||||
kernel32 = ctypes.WinDLL('kernel32')
|
||||
file_handle = kernel32.CreateFileMappingA(-1, None, 4, 0, 33,
|
||||
"windows_shell_global__".encode('utf-8'))
|
||||
data_address = kernel32.MapViewOfFile(file_handle, 983071, 0, 0, 0)
|
||||
key = "3101b223dca7715b0154924f0eeeee20".encode('utf-8')
|
||||
kernel32.RtlMoveMemory(data_address, key, len(key))
|
||||
```
|
||||
|
||||
2. **异步回调处理**
|
||||
```python
|
||||
# 在回调线程中使用事件循环
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
self.hookbot.process_message(msg_type, data),
|
||||
self.event_loop
|
||||
)
|
||||
```
|
||||
|
||||
3. **消息格式转换**
|
||||
```python
|
||||
# 群聊判断
|
||||
room_wxid = data.get("room_wxid", "")
|
||||
if room_wxid:
|
||||
message["IsGroup"] = True
|
||||
message["FromWxid"] = room_wxid
|
||||
message["SenderWxid"] = data.get("from_wxid", "")
|
||||
```
|
||||
|
||||
### 测试结果
|
||||
|
||||
✅ **成功测试**:
|
||||
- 机器人启动和注入
|
||||
- 登录信息获取
|
||||
- 私聊消息接收
|
||||
- 群聊消息接收
|
||||
- ping-pong 测试
|
||||
- 插件管理命令
|
||||
- AI 聊天功能
|
||||
|
||||
### 下一步计划
|
||||
|
||||
1. 完善插件功能
|
||||
2. 添加更多消息类型支持
|
||||
3. 优化错误处理
|
||||
4. 编写使用文档
|
||||
@@ -1,234 +0,0 @@
|
||||
# 技术要点
|
||||
|
||||
## 核心架构
|
||||
|
||||
### 1. DLL Hook 机制
|
||||
|
||||
**工作流程**:
|
||||
```
|
||||
1. 加载 Loader.dll
|
||||
2. 创建共享内存 (关键!)
|
||||
3. 注册 Socket 回调
|
||||
4. 调用 InjectWeChat 注入 Helper.dll
|
||||
5. 等待 Socket 连接回调
|
||||
6. 使用 Socket Client ID 发送 API 请求
|
||||
```
|
||||
|
||||
**关键代码**:
|
||||
```python
|
||||
# 共享内存创建 (必须在 Loader 之前)
|
||||
create_shared_memory()
|
||||
|
||||
# 注册回调
|
||||
add_callback_handler(self)
|
||||
loader = NoveLoader(loader_dll)
|
||||
|
||||
# 注入微信
|
||||
process_id = loader.InjectWeChat(helper_dll)
|
||||
|
||||
# 等待 Socket 连接
|
||||
# socket_client_id 从回调中获取 (通常为 1)
|
||||
|
||||
# 使用 Socket ID 发送请求
|
||||
client = WechatHookClient(loader, socket_client_id)
|
||||
```
|
||||
|
||||
### 2. 消息类型映射
|
||||
|
||||
**实际测试的消息类型**:
|
||||
```python
|
||||
MT_DEBUG_LOG = 11024 # 调试日志
|
||||
MT_USER_LOGIN = 11025 # 用户登录 (自动推送)
|
||||
MT_TEXT = 11046 # 文本消息
|
||||
MT_IMAGE = 11047 # 图片消息
|
||||
MT_VOICE = 11048 # 语音消息
|
||||
MT_VIDEO = 11049 # 视频消息
|
||||
MT_EMOJI = 11050 # 表情消息
|
||||
MT_LOCATION = 11051 # 位置消息
|
||||
MT_LINK = 11052 # 链接消息
|
||||
MT_FILE = 11053 # 文件消息
|
||||
MT_MINIAPP = 11054 # 小程序消息
|
||||
MT_CARD = 11055 # 名片消息
|
||||
MT_FRIEND_REQUEST = 11056 # 好友请求
|
||||
MT_REVOKE = 11057 # 撤回消息
|
||||
MT_SYSTEM = 11058 # 系统消息
|
||||
```
|
||||
|
||||
### 3. 消息数据结构
|
||||
|
||||
**文本消息** (type=11046):
|
||||
```json
|
||||
{
|
||||
"at_user_list": [],
|
||||
"from_wxid": "wxid_xxx",
|
||||
"is_pc": 0,
|
||||
"msg": "消息内容",
|
||||
"msgid": "123456789",
|
||||
"room_wxid": "",
|
||||
"timestamp": 1762940000,
|
||||
"to_wxid": "wxid_yyy",
|
||||
"wx_type": 1
|
||||
}
|
||||
```
|
||||
|
||||
**登录信息** (type=11025):
|
||||
```json
|
||||
{
|
||||
"account": "账号",
|
||||
"avatar": "http://...",
|
||||
"device_id": "设备ID",
|
||||
"nickname": "昵称",
|
||||
"phone": "手机号",
|
||||
"wxid": "wxid_xxx",
|
||||
"wx_user_dir": "C:\\..."
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 群聊消息判断
|
||||
|
||||
**关键字段**:
|
||||
- `room_wxid`: 群聊 ID,私聊时为空
|
||||
- `from_wxid`: 发送者 wxid
|
||||
- `to_wxid`: 接收者 (群聊时是群 ID)
|
||||
|
||||
**判断逻辑**:
|
||||
```python
|
||||
room_wxid = data.get("room_wxid", "")
|
||||
if room_wxid:
|
||||
# 群聊消息
|
||||
message["IsGroup"] = True
|
||||
message["FromWxid"] = room_wxid
|
||||
message["SenderWxid"] = data.get("from_wxid", "")
|
||||
else:
|
||||
# 私聊消息
|
||||
message["IsGroup"] = False
|
||||
message["FromWxid"] = data.get("from_wxid", "")
|
||||
```
|
||||
|
||||
### 5. 异步回调处理
|
||||
|
||||
**问题**: 回调在同步线程中执行,但需要调用异步方法
|
||||
|
||||
**解决方案**:
|
||||
```python
|
||||
# 在初始化时保存事件循环
|
||||
self.event_loop = asyncio.get_event_loop()
|
||||
|
||||
# 在回调中使用
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
self.hookbot.process_message(msg_type, data),
|
||||
self.event_loop
|
||||
)
|
||||
```
|
||||
|
||||
### 6. 插件系统
|
||||
|
||||
**插件生命周期**:
|
||||
```python
|
||||
class MyPlugin(PluginBase):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# 初始化成员变量
|
||||
|
||||
async def async_init(self):
|
||||
# 异步初始化 (加载配置等)
|
||||
pass
|
||||
|
||||
async def on_enable(self, bot=None):
|
||||
# 启用时调用 (注册定时任务)
|
||||
pass
|
||||
|
||||
async def on_disable(self):
|
||||
# 禁用时调用 (清理资源)
|
||||
pass
|
||||
```
|
||||
|
||||
**事件处理**:
|
||||
```python
|
||||
@on_text_message()
|
||||
async def handle_message(self, bot, message: dict):
|
||||
content = message.get("Content", "")
|
||||
from_wxid = message.get("FromWxid", "")
|
||||
|
||||
# 处理消息
|
||||
await bot.send_text(from_wxid, "回复内容")
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 为什么必须使用 32 位 Python?
|
||||
**A**: Loader.dll 和 Helper.dll 是 32 位编译的,只能在 32 位 Python 中加载。
|
||||
|
||||
### Q2: 为什么需要共享内存?
|
||||
**A**: DLL 之间通过共享内存进行通信,必须在加载 Loader.dll 之前创建。
|
||||
|
||||
### Q3: 为什么 Socket Client ID 和进程 ID 不同?
|
||||
**A**:
|
||||
- `InjectWeChat` 返回的是微信进程 ID
|
||||
- 回调中的 `client_id` 是 Socket 连接 ID (从 1 开始)
|
||||
- 发送 API 请求时使用 Socket ID
|
||||
|
||||
### Q4: 如何判断是群聊还是私聊?
|
||||
**A**: 检查 `room_wxid` 字段,不为空则是群聊。
|
||||
|
||||
### Q5: 插件配置为什么没有加载?
|
||||
**A**: 使用 `async_init` 方法而不是 `on_load`。
|
||||
|
||||
### Q6: 如何热重载插件?
|
||||
**A**: 发送 `/重载插件 插件名` 命令。
|
||||
|
||||
### Q7: 为什么收不到消息?
|
||||
**A**: 检查以下几点:
|
||||
1. 共享内存是否创建成功
|
||||
2. Socket 客户端是否连接
|
||||
3. 消息类型是否在 MESSAGE_TYPE_MAP 中
|
||||
4. 消息是否被过滤 (白名单/黑名单)
|
||||
|
||||
### Q8: 如何添加新的消息类型?
|
||||
**A**:
|
||||
1. 在 `message_types.py` 中添加常量
|
||||
2. 在 `MESSAGE_TYPE_MAP` 中添加映射
|
||||
3. 在 `normalize_message` 中处理特殊字段
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 1. 消息处理
|
||||
- 使用 `asyncio.run_coroutine_threadsafe` 避免阻塞回调线程
|
||||
- 深拷贝消息数据避免并发问题
|
||||
|
||||
### 2. 插件管理
|
||||
- 按优先级排序事件处理器
|
||||
- 支持插件返回 False 中断处理链
|
||||
|
||||
### 3. 错误处理
|
||||
- 每个处理器独立 try-except
|
||||
- 一个插件出错不影响其他插件
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 1. 查看回调数据
|
||||
```python
|
||||
logger.info(f"[回调] 收到消息: type={msg_type}, data={msg_data}")
|
||||
```
|
||||
|
||||
### 2. 查看消息处理
|
||||
```python
|
||||
logger.info(f"收到消息: type={event_type}, from={from_wxid}, content={content}")
|
||||
```
|
||||
|
||||
### 3. 查看插件加载
|
||||
```python
|
||||
logger.info(f"插件 {plugin_name} 已加载")
|
||||
```
|
||||
|
||||
### 4. 使用调试日志
|
||||
```python
|
||||
logger.debug(f"详细信息: {variable}")
|
||||
```
|
||||
|
||||
## 安全注意事项
|
||||
|
||||
1. **API 密钥**: 不要将 API 密钥提交到版本控制
|
||||
2. **管理员权限**: 只有管理员可以执行插件管理命令
|
||||
3. **消息过滤**: 使用白名单/黑名单控制消息处理
|
||||
4. **错误处理**: 捕获所有异常避免崩溃
|
||||
61
docs/内部接口清单.md
Normal file
61
docs/内部接口清单.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# 内部接口清单(HttpClient)
|
||||
|
||||
本文档整理当前项目内 `HttpClient` 封装的低层接口(直接对应 HTTP API)。
|
||||
一般插件请优先使用 `WechatHookClient`,仅在需要更底层控制时再使用 `HttpClient`。
|
||||
|
||||
说明:
|
||||
- 这些方法会直接向 Hook HTTP API 发请求。
|
||||
- 返回值大多为 `bool` 或 `dict`(详见方法注释与实现)。
|
||||
|
||||
## 基础
|
||||
|
||||
- `await http_client.post(endpoint, data=None)` 发送 POST 请求(低层)
|
||||
- `await http_client.get(endpoint, params=None)` 发送 GET 请求(低层)
|
||||
- `await http_client.close()` 关闭客户端
|
||||
- `await http_client.sqlite_exec(db_name, sql_fmt)` 执行 SQLite 查询(新接口)
|
||||
- `await http_client.get_db_handle()` 获取数据库句柄列表(新接口)
|
||||
|
||||
## 消息发送
|
||||
|
||||
- `await http_client.send_text(wxid, msg)` 发送文本
|
||||
- `await http_client.send_image(wxid, image_path)` 发送图片
|
||||
- `await http_client.send_file(wxid, file_path)` 发送文件
|
||||
- `await http_client.send_voice(wxid, voice_path)` 发送语音(新接口为 silkPath)
|
||||
- `await http_client.send_at_text(room_id, msg, wxids)` 群聊 @ 文本
|
||||
- `await http_client.send_card(wxid, card_wxid)` 发送名片
|
||||
- `await http_client.send_xml(wxid, xml)` 发送 XML
|
||||
- `await http_client.send_app_msg(wxid, appmsg_content, msg_type)` 发送 appmsg(如链接卡片)
|
||||
- `await http_client.revoke_message(new_msg_id)` 撤回消息
|
||||
|
||||
## 好友管理
|
||||
|
||||
- `await http_client.get_friend_list()` 获取好友列表
|
||||
- `await http_client.get_friend_info(wxid)` 获取好友资料(网络)
|
||||
- `await http_client.get_friend_info_cache(wxid)` 获取好友资料(缓存)
|
||||
- `await http_client.add_friend(wxid, verify_msg="", scene=3)` 添加好友
|
||||
- `await http_client.accept_friend(v3, v4, scene)` 同意好友请求
|
||||
- `await http_client.delete_friend(wxid)` 删除好友
|
||||
- `await http_client.set_friend_remark(wxid, remark)` 修改好友备注
|
||||
|
||||
## 群聊管理
|
||||
|
||||
- `await http_client.get_chatroom_members(room_id)` 获取群成员列表
|
||||
- `await http_client.get_chatroom_info(room_id)` 获取群信息
|
||||
- `await http_client.get_group_member_contact(room_id, member_wxid)` 获取群成员详细信息(含头像)
|
||||
- `await http_client.create_chatroom(wxid_list)` 创建群聊
|
||||
- `await http_client.invite_to_chatroom(room_id, wxid_list)` 邀请进群
|
||||
- `await http_client.remove_chatroom_member(room_id, wxid_list)` 踢出群成员
|
||||
- `await http_client.quit_chatroom(room_id)` 退出群聊
|
||||
- `await http_client.set_chatroom_announcement(room_id, announcement)` 修改群公告
|
||||
|
||||
## 媒体下载
|
||||
|
||||
- `await http_client.cdn_download_image(fileid, aeskey, save_path, img_type=1, timeout=60.0)` CDN 下载图片
|
||||
- `await http_client.download_image(to_user, from_user, msg_id, total_len, save_path)` 下载图片
|
||||
- `await http_client.download_video(msg_id, new_msg_id, total_len, save_path)` 下载视频
|
||||
|
||||
## 初始化与个人信息
|
||||
|
||||
- `await http_client.wechat_init()` 微信初始化好友/群列表缓存
|
||||
- `await http_client.get_self_info()` 获取本人信息(缓存)
|
||||
- `await http_client.set_nickname(nickname)` 修改自己昵称
|
||||
152
docs/原始CDN下载实现指南_给第三方框架.md
Normal file
152
docs/原始CDN下载实现指南_给第三方框架.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# 原始 CDN 下载实现指南(给第三方框架)
|
||||
|
||||
这份文档只讲“原始 Hook 协议怎么下图”,不依赖本项目的 `WechatHookClient` 或 `ImageProcessor` 封装。
|
||||
|
||||
## 1) 接口结论(按本仓库实际代码验证)
|
||||
|
||||
- 接口:`POST /api/cdn_download`
|
||||
- 请求体字段:
|
||||
- `fileid`:CDN 文件标识
|
||||
- `asekey`:AES 密钥(注意字段名是 `asekey`,不是 `aeskey`)
|
||||
- `imgType`:`1` 原图,`2` 缩略图
|
||||
- `out`:本地保存路径(Hook 所在机器可写路径)
|
||||
- 成功判定:响应 JSON 中 `errCode == 1`
|
||||
|
||||
已在代码中看到的依据:
|
||||
|
||||
- `WechatHookBot/WechatHook/http_client.py:752`
|
||||
- `WechatHookBot/WechatHook/http_client.py:775`
|
||||
- `WechatHookBot/WechatHook/http_client.py:793`
|
||||
- `WechatHookBot/WechatHook/http_client.py:801`
|
||||
|
||||
---
|
||||
|
||||
## 2) 参数从哪里来(消息 XML)
|
||||
|
||||
图片消息里一般在 `<img ... />` 标签:
|
||||
|
||||
- 原图 fileid 候选:`cdnbigimgurl` / `cdnmidimgurl` / `cdnhdimgurl` / `fileid`
|
||||
- 原图 key:`aeskey`
|
||||
- 缩略图 fileid:`cdnthumburl`
|
||||
- 缩略图 key:`cdnthumbaeskey`(若没有可回退 `aeskey`)
|
||||
|
||||
本仓库提取逻辑:
|
||||
|
||||
- `WechatHookBot/WechatHook/client.py:1386`
|
||||
- `WechatHookBot/WechatHook/client.py:1393`
|
||||
|
||||
---
|
||||
|
||||
## 3) 你朋友可直接照抄的实现流程
|
||||
|
||||
1. 解析微信消息 XML,拿到:
|
||||
- 原图:`fileid = cdnbigimgurl(或候选)`,`aeskey`
|
||||
- 缩略图:`thumb_fileid = cdnthumburl`,`thumb_key = cdnthumbaeskey or aeskey`
|
||||
2. 先调用一次原图:`imgType=1`
|
||||
3. 如果失败,再调用缩略图:`imgType=2`
|
||||
4. 成功后检查 `out` 文件存在且大小 `> 0`
|
||||
|
||||
推荐这么做的原因:有些消息原图拉不到,但缩略图能拉到。
|
||||
|
||||
---
|
||||
|
||||
## 4) 最小请求示例(curl)
|
||||
|
||||
```bash
|
||||
curl -X POST "http://127.0.0.1:8888/api/cdn_download" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"fileid": "<cdnbigimgurl>",
|
||||
"asekey": "<aeskey>",
|
||||
"imgType": 1,
|
||||
"out": "D:/temp/wx_img_001.jpg"
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5) Python 最小实现(可直接给朋友)
|
||||
|
||||
```python
|
||||
import os
|
||||
import requests
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def parse_img_xml(xml_text: str):
|
||||
root = ET.fromstring(xml_text)
|
||||
img = root.find(".//img")
|
||||
if img is None:
|
||||
raise ValueError("xml里没有<img>标签")
|
||||
|
||||
fileid = (
|
||||
img.get("cdnbigimgurl", "")
|
||||
or img.get("cdnmidimgurl", "")
|
||||
or img.get("cdnhdimgurl", "")
|
||||
or img.get("fileid", "")
|
||||
)
|
||||
aeskey = img.get("aeskey", "")
|
||||
|
||||
thumb_fileid = img.get("cdnthumburl", "")
|
||||
thumb_key = img.get("cdnthumbaeskey", "") or aeskey
|
||||
|
||||
return fileid, aeskey, thumb_fileid, thumb_key
|
||||
|
||||
|
||||
def cdn_download(base_url: str, fileid: str, aeskey: str, out_path: str, img_type: int = 1, timeout: int = 60):
|
||||
payload = {
|
||||
"fileid": fileid,
|
||||
"asekey": aeskey, # 注意这里是 asekey
|
||||
"imgType": img_type,
|
||||
"out": out_path,
|
||||
}
|
||||
resp = requests.post(f"{base_url}/api/cdn_download", json=payload, timeout=timeout)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
ok = isinstance(data, dict) and data.get("errCode") == 1
|
||||
if not ok:
|
||||
return False, data
|
||||
if not os.path.exists(out_path) or os.path.getsize(out_path) <= 0:
|
||||
return False, {"error": "hook返回成功但文件未落盘", "resp": data}
|
||||
return True, data
|
||||
|
||||
|
||||
def download_image_with_fallback(base_url: str, xml_text: str, out_path: str):
|
||||
fileid, aeskey, thumb_fileid, thumb_key = parse_img_xml(xml_text)
|
||||
|
||||
if fileid and aeskey:
|
||||
ok, data = cdn_download(base_url, fileid, aeskey, out_path, img_type=1)
|
||||
if ok:
|
||||
return out_path
|
||||
|
||||
if thumb_fileid and thumb_key:
|
||||
ok, data = cdn_download(base_url, thumb_fileid, thumb_key, out_path, img_type=2)
|
||||
if ok:
|
||||
return out_path
|
||||
|
||||
raise RuntimeError("原图/缩略图都下载失败")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6) 常见坑(你教朋友时重点强调)
|
||||
|
||||
- `asekey` 字段名拼错:写成 `aeskey` 会直接失败。
|
||||
- `fileid` 取错:优先 `cdnbigimgurl`,不要只盯一个字段。
|
||||
- 只试原图不试缩略图:很多“偶发失败”是这么来的。
|
||||
- 路径不可写:`out` 必须是 Hook 进程有权限写入的本机路径。
|
||||
- 仅看 HTTP 200:必须再看 `errCode` 和文件是否真正写出来。
|
||||
|
||||
---
|
||||
|
||||
## 7) 与 `download_img` 的区别(避免混淆)
|
||||
|
||||
- `/api/cdn_download`:走 `fileid + asekey` 这条 CDN 参数下载链路(你现在要的)。
|
||||
- `/api/download_img`:走 `MsgId/to_user/from_user/total_len...` 这条“按消息参数”下载链路。
|
||||
|
||||
`/api/download_img` 的官方文档在:
|
||||
|
||||
- `新接口/下载图片.md:1`
|
||||
|
||||
如果你朋友框架已经能拿到 `<img>` 里的 `cdnbigimgurl/aeskey`,优先实现 `/api/cdn_download` 即可。
|
||||
|
||||
83
docs/封装接口清单.md
Normal file
83
docs/封装接口清单.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# 封装接口清单(WechatHookClient)
|
||||
|
||||
本文档整理当前项目内已封装的对外接口,插件开发请优先使用 `WechatHookClient`,避免直接调用 HTTP API。
|
||||
|
||||
说明:
|
||||
- 插件回调里通常会拿到 `bot` 参数,它就是 `WechatHookClient` 实例。
|
||||
- 统一发送:`send_message` / `send_media`。
|
||||
- 统一下载:`download_wechat_media`(带锁与缓存,TTL=60分钟)。
|
||||
- 群成员信息建议优先使用 `utils.member_info_service`(MemberSync 数据库)。
|
||||
|
||||
## 基础信息
|
||||
|
||||
- `bot.wxid` 获取当前登录 wxid
|
||||
- `bot.nickname` 获取当前登录昵称
|
||||
- `await bot.update_profile(wxid, nickname)` 手动更新缓存信息
|
||||
|
||||
## 初始化与登录
|
||||
|
||||
- `await bot.wechat_init()` 初始化好友/群列表缓存
|
||||
- `await bot.get_login_info()` 获取当前登录信息(并更新 `bot.wxid`/`bot.nickname`)
|
||||
|
||||
## 消息发送
|
||||
|
||||
- `await bot.send_message(to_wxid, msg_type, content)` 统一发送入口(text/image/video/file/xml)
|
||||
- `await bot.send_text(to_wxid, content)` 发送文本
|
||||
- `await bot.send_image(to_wxid, image_path)` 发送图片
|
||||
- `await bot.send_video(to_wxid, video_path)` 发送视频
|
||||
- `await bot.send_file(to_wxid, file_path)` 发送文件
|
||||
- `await bot.send_media(to_wxid, file_path, media_type="")` 统一媒体发送(支持按扩展名自动判断)
|
||||
- `await bot.send_at_message(chatroom_id, content, at_list)` 群聊 @ 消息
|
||||
- `await bot.send_xml(to_wxid, xml)` 发送 XML 消息
|
||||
- `await bot.send_card(to_wxid, card_wxid, card_nickname="")` 发送名片
|
||||
- `await bot.send_link(to_wxid, title, desc, url, thumb_url="")` 发送链接
|
||||
- `await bot.send_link_card(to_wxid, title, desc, url, image_url="")` 发送链接卡片
|
||||
- `await bot.revoke_message(msg_id)` 撤回消息
|
||||
|
||||
## 群聊管理
|
||||
|
||||
- `await bot.get_chatroom_list(force_refresh=False)` 获取群聊列表(好友列表 + 本地数据库兜底)
|
||||
- `await bot.get_chatroom_members(chatroom_id)` 获取群成员列表(优先全量名单,不足时数据库兜底)
|
||||
- 返回字段:`wxid` / `nickname` / `display_name` / `avatar`
|
||||
- `await bot.get_chatroom_info(chatroom_id)` 获取群信息
|
||||
- `await bot.create_chatroom(member_list)` 创建群聊
|
||||
- `await bot.invite_to_chatroom(chatroom_id, wxid_list)` 邀请进群
|
||||
- `await bot.remove_chatroom_member(chatroom_id, wxid_list)` 踢出群成员
|
||||
- `await bot.quit_chatroom(chatroom_id)` 退出群聊
|
||||
- `await bot.set_chatroom_announcement(chatroom_id, announcement)` 修改群公告
|
||||
- `await bot.set_chatroom_name(chatroom_id, name)` 修改群名称(新协议可能不可用)
|
||||
- `await bot.set_my_chatroom_nickname(chatroom_id, nickname)` 修改我的群昵称(新协议可能不可用)
|
||||
|
||||
## 群成员信息
|
||||
|
||||
- `await bot.get_group_member_contact(room_id, member_wxid)` 获取群成员详细信息(含头像)
|
||||
- `await bot.get_user_info_in_chatroom(chatroom_id, user_wxid, max_retries=1)` 从缓存/群成员列表获取信息
|
||||
- `bot.update_chatroom_members_cache(chatroom_id, members)` 更新群成员缓存
|
||||
- `bot.get_cached_member_info(chatroom_id, user_wxid)` 从缓存获取群成员信息
|
||||
|
||||
## 好友管理
|
||||
|
||||
- `await bot.get_friend_list()` 获取好友列表(失败会自动触发初始化/全量更新)
|
||||
- `await bot.get_friend_info(wxid)` 获取好友资料(网络)
|
||||
- `await bot.add_friend(wxid, verify_msg="", scene=3)` 添加好友
|
||||
- `await bot.accept_friend(v3, v4, scene)` 同意好友请求
|
||||
- `await bot.delete_friend(wxid)` 删除好友
|
||||
- `await bot.set_friend_remark(wxid, remark)` 修改好友备注
|
||||
|
||||
## 微信消息媒体下载(图片/视频)
|
||||
|
||||
- `await bot.download_wechat_media(media_type, save_path, message=None, msg_id=None, total_len=0, to_user="", from_user="", file_id="", aes_key="", prefer_original=True, timeout=60.0)`
|
||||
- 统一入口:带缓存与锁,避免重复下载
|
||||
|
||||
- `await bot.download_image(message, save_path)` 从消息 XML 下载图片
|
||||
- `await bot.download_image_by_id(msg_id, total_len, save_path, to_user="", from_user="")` 引用消息图片下载
|
||||
- `await bot.download_image_by_cdn(file_id, aes_key, save_path, prefer_original=True, timeout=60.0)` CDN 参数下载图片
|
||||
- `await bot.download_video(message, save_path)` 从消息 XML 下载视频
|
||||
- `await bot.download_video_by_id(msg_id, total_len, save_path)` 引用消息视频下载
|
||||
|
||||
## 兼容/保留接口(新协议一般不需要)
|
||||
|
||||
- `await bot.cdn_init()` CDN 初始化(新协议无需)
|
||||
- `await bot.cdn_download(...)` CDN 下载(新协议不推荐)
|
||||
- `await bot.cdn_upload(...)` CDN 上传(新协议不推荐)
|
||||
- `await bot.send_cdn_image(to_wxid, file_path)` 发送图片(兼容接口)
|
||||
200
docs/快速开始.md
200
docs/快速开始.md
@@ -1,200 +0,0 @@
|
||||
# 快速开始
|
||||
|
||||
## 环境准备
|
||||
|
||||
### 1. 系统要求
|
||||
|
||||
- ✅ Windows 系统(Win10/Win11)
|
||||
- ✅ Python 3.x **32位版本**(重要!)
|
||||
- ✅ 微信客户端已安装并登录
|
||||
|
||||
### 2. 检查 Python 版本
|
||||
|
||||
```bash
|
||||
python --version
|
||||
# 应显示 Python 3.x.x
|
||||
|
||||
# 检查是否为 32位
|
||||
python -c "import sys; print(sys.maxsize > 2**32)"
|
||||
# 应显示 False(表示 32位)
|
||||
```
|
||||
|
||||
如果是 64位 Python,需要下载安装 32位版本:
|
||||
https://www.python.org/downloads/
|
||||
|
||||
## 安装步骤
|
||||
|
||||
### 1. 克隆或下载项目
|
||||
|
||||
```bash
|
||||
cd D:\project\shrobot
|
||||
# 项目已在 WechatHookBot 目录
|
||||
```
|
||||
|
||||
### 2. 安装依赖
|
||||
|
||||
```bash
|
||||
cd WechatHookBot
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 3. 准备 DLL 文件
|
||||
|
||||
将个微大客户版的 DLL 文件放到 `libs/` 目录:
|
||||
|
||||
```
|
||||
WechatHookBot/
|
||||
libs/
|
||||
Loader.dll
|
||||
Helper.dll
|
||||
```
|
||||
|
||||
### 4. 配置文件
|
||||
|
||||
复制配置模板并修改:
|
||||
|
||||
```bash
|
||||
# main_config.toml 已存在,修改以下内容:
|
||||
```
|
||||
|
||||
```toml
|
||||
[Bot]
|
||||
admins = ["your_wxid"] # 改为你的 wxid
|
||||
```
|
||||
|
||||
## 运行
|
||||
|
||||
### 方式一:简单启动(无 WebUI)
|
||||
|
||||
```bash
|
||||
python bot.py
|
||||
```
|
||||
|
||||
### 方式二:完整启动(带 WebUI)
|
||||
|
||||
```bash
|
||||
python app.py
|
||||
```
|
||||
|
||||
然后访问:http://localhost:9999
|
||||
|
||||
默认账号:admin / admin123
|
||||
|
||||
## 第一次运行
|
||||
|
||||
1. **启动微信**:确保微信客户端已登录
|
||||
2. **运行程序**:执行 `python bot.py`
|
||||
3. **观察日志**:查看是否成功注入微信
|
||||
4. **测试消息**:给机器人发送消息测试
|
||||
|
||||
## 测试插件
|
||||
|
||||
### 创建测试插件
|
||||
|
||||
在 `plugins/` 目录创建 `TestPlugin/` 文件夹:
|
||||
|
||||
```
|
||||
plugins/
|
||||
TestPlugin/
|
||||
__init__.py # 空文件
|
||||
main.py
|
||||
```
|
||||
|
||||
`main.py` 内容:
|
||||
|
||||
```python
|
||||
from utils.plugin_base import PluginBase
|
||||
from utils.decorators import *
|
||||
from WechatHook import WechatHookClient
|
||||
from loguru import logger
|
||||
|
||||
class TestPlugin(PluginBase):
|
||||
description = "测试插件"
|
||||
author = "Your Name"
|
||||
version = "1.0.0"
|
||||
|
||||
@on_text_message
|
||||
async def handle_text(self, client: WechatHookClient, message: dict):
|
||||
content = message.get("Content", "")
|
||||
from_wxid = message.get("FromWxid", "")
|
||||
|
||||
if content == "ping":
|
||||
await client.send_text(from_wxid, "pong")
|
||||
logger.info("收到 ping,回复 pong")
|
||||
```
|
||||
|
||||
重启程序,给机器人发送 "ping",应该会收到 "pong" 回复。
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 1. DLL 注入失败
|
||||
|
||||
**现象:** 提示 "注入微信失败"
|
||||
|
||||
**解决:**
|
||||
- 确认使用 32位 Python
|
||||
- 确认微信已登录
|
||||
- 关闭杀毒软件或添加信任
|
||||
- 以管理员身份运行
|
||||
|
||||
### 2. 找不到 DLL 文件
|
||||
|
||||
**现象:** "Loader DLL 文件不存在"
|
||||
|
||||
**解决:**
|
||||
- 检查 `libs/` 目录是否有 DLL 文件
|
||||
- 检查 `main_config.toml` 中的路径配置
|
||||
|
||||
### 3. 收不到消息
|
||||
|
||||
**现象:** 程序运行正常但收不到消息
|
||||
|
||||
**解决:**
|
||||
- 检查是否启用了白名单/黑名单过滤
|
||||
- 查看日志是否有错误信息
|
||||
- 确认插件已正确加载
|
||||
|
||||
### 4. 发送消息失败
|
||||
|
||||
**现象:** 调用 send_text 返回 False
|
||||
|
||||
**解决:**
|
||||
- 检查 wxid 是否正确
|
||||
- 检查是否是好友/群成员
|
||||
- 查看日志中的详细错误信息
|
||||
|
||||
## 获取 wxid
|
||||
|
||||
### 方法一:通过日志
|
||||
|
||||
运行程序后,给机器人发送消息,在日志中可以看到:
|
||||
|
||||
```
|
||||
收到消息: FromWxid=wxid_xxx, Content=...
|
||||
```
|
||||
|
||||
### 方法二:通过 API
|
||||
|
||||
创建临时插件打印所有消息:
|
||||
|
||||
```python
|
||||
@on_text_message
|
||||
async def handle_text(self, client, message):
|
||||
logger.info(f"消息详情: {message}")
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
- 📖 阅读 [插件开发指南](插件开发.md)
|
||||
- 🏗️ 了解 [架构设计](架构设计.md)
|
||||
- 📚 查看 [API 文档](API文档.md)
|
||||
- 🔌 开发自己的插件
|
||||
|
||||
## 获取帮助
|
||||
|
||||
如遇到问题,请:
|
||||
|
||||
1. 查看日志文件 `logs/hookbot.log`
|
||||
2. 检查配置文件是否正确
|
||||
3. 确认环境要求是否满足
|
||||
4. 查看项目文档
|
||||
@@ -1,90 +0,0 @@
|
||||
# WechatHookBot 控制台卡顿优化方案
|
||||
|
||||
## 问题判断
|
||||
- 控制台卡顿主要来源于高频日志输出、消息处理缺乏背压、插件耗时任务并发过高、异步调度开销叠加。
|
||||
- 项目当前在回调入口与路由层大量打印日志,并为每条消息创建异步任务,易导致 I/O 堆积与事件循环压力。
|
||||
- 关键日志位置:
|
||||
- 控制台与文件日志初始化:`WechatHookBot/bot.py:236-249`
|
||||
- 回调层日志:`WechatHookBot/bot.py:49-78`
|
||||
- 路由层日志:`WechatHookBot/utils/hookbot.py:80-123`
|
||||
|
||||
## 优化方案
|
||||
|
||||
### 日志优化
|
||||
- 将控制台日志级别降至 `WARNING` 或禁用控制台,仅保留文件日志;关闭 `colorize` 降低 Windows 终端渲染开销(参考 `WechatHookBot/bot.py:236-241`)。
|
||||
- 合并重复日志为一条结构化日志,减少每消息的 `debug/info` 输出;对系统与群成员变动事件保留必要日志,其余按采样输出(参考 `WechatHookBot/bot.py:49-78`、`WechatHookBot/utils/hookbot.py:80-123`)。
|
||||
- 使用异步日志队列/缓冲写入降低主流程的 I/O 开销;缩短文件日志保留周期与大小(参考 `WechatHookBot/bot.py:242-249`)。
|
||||
|
||||
### 背压与队列
|
||||
- 在接收回调引入有界 `asyncio.Queue(maxsize=...)`:`on_receive` 只负责快速入队,不对每条消息直接 `run_coroutine_threadsafe`(参考 `WechatHookBot/bot.py:68-78`)。
|
||||
- 由独立消费者协程从队列拉取并串行或受限并行处理;队列满时采用以下策略之一:
|
||||
- 丢弃最旧消息(防止饥饿)
|
||||
- 采样处理(降低负载)
|
||||
- 降级处理(仅计数、不分发事件)
|
||||
- 为高频消息类型设置优先级;在高水位时先处理高优先级(系统通知等),低优先级直接丢弃或延后。
|
||||
|
||||
### 并发与耗时任务
|
||||
- 使用 `asyncio.Semaphore(n)` 限制插件执行并发度,避免大量并行 I/O 或 CPU 任务压垮事件循环(事件分发位置:`WechatHookBot/utils/hookbot.py:126-132`)。
|
||||
- 为插件任务设置超时与熔断:单条消息处理超过阈值自动取消或降级;对连续失败触发短期熔断减少系统压力。
|
||||
|
||||
### 过滤策略
|
||||
- 启用/加强 `ignore-mode`:在群聊压力大时切换为 `Whitelist` 仅处理白名单来源;或用 `Blacklist` 屏蔽噪声(读取配置位置:`WechatHookBot/utils/hookbot.py:33-50`,过滤逻辑:`WechatHookBot/utils/hookbot.py:160-171`)。
|
||||
- 对非关键消息类型(除 `11058` 系统通知)在高负载状态下动态降级为统计计数,不逐条分发。
|
||||
|
||||
### 定时任务与调度
|
||||
- APScheduler 设置:
|
||||
- `coalesce=True` 合并触发
|
||||
- `max_instances=1` 防止并发堆积
|
||||
- 合理 `misfire_grace_time`
|
||||
- 将 APScheduler 相关日志降至 `WARNING`,避免定时任务导致控制台刷屏(启动点:`WechatHookBot/bot.py:185-187`)。
|
||||
|
||||
### 运行习惯与配置
|
||||
- 长期运行时仅启用文件日志或在初始化阶段保留控制台输出,稳定后自动关闭控制台 Sink。
|
||||
- 将日志采样率、队列容量、并发上限、忽略模式等参数放入 `main_config.toml`,支持运行期按需调整(配置读取:`WechatHookBot/utils/hookbot.py:33-50`)。
|
||||
|
||||
## 实施优先级
|
||||
1. 低风险配置级
|
||||
- 降低或关闭控制台日志、压低日志级别、关闭彩色输出、减少路由层细粒度日志。
|
||||
2. 架构级
|
||||
- 引入有界消息队列与单/多消费者;设置并发上限与任务超时;高水位时对低优先级消息降级或采样。
|
||||
3. 策略级
|
||||
- 启用白名单模式;扩充过滤类型;对高频事件采用采样与聚合日志。
|
||||
|
||||
## 验证要点
|
||||
- CPU 与终端响应度显著改善,`logs/hookbot.log` 保持必要信息且写入量合理。
|
||||
- 峰值时队列长度稳定在上限附近但不无限增长,平均处理时延可控。
|
||||
- 插件执行受限在设定并发与超时范围内,无明显阻塞主循环。
|
||||
|
||||
## 可配置建议示例(不直接改代码)
|
||||
```toml
|
||||
# main_config.toml 中建议新增
|
||||
[Performance]
|
||||
log_console_enabled = false # 禁用控制台日志
|
||||
log_level_file = "INFO" # 文件日志级别
|
||||
log_colorize = false # 关闭彩色输出
|
||||
log_sampling_rate = 0.1 # 日志采样比例(10%)
|
||||
|
||||
[Queue]
|
||||
max_size = 1000 # 消息队列容量
|
||||
overflow_strategy = "drop_oldest" # 溢出策略:丢弃最旧/采样/degrade
|
||||
|
||||
[Concurrency]
|
||||
plugin_max_concurrency = 8 # 插件并发上限
|
||||
plugin_task_timeout_seconds = 5 # 单任务超时
|
||||
|
||||
[Filter]
|
||||
ignore_mode = "Whitelist" # None/Whitelist/Blacklist
|
||||
whitelist = ["room_wxid_xxx", "wxid_xxx"]
|
||||
blacklist = []
|
||||
|
||||
[Scheduler]
|
||||
coalesce = true
|
||||
max_instances = 1
|
||||
misfire_grace_time = 30
|
||||
```
|
||||
|
||||
## 关联文件位置(便于落地)
|
||||
- 控制台与文件日志初始化:`WechatHookBot/bot.py:236-249`
|
||||
- 回调注册与消息接收:`WechatHookBot/bot.py:42-83`
|
||||
- 事件分发与过滤:`WechatHookBot/utils/hookbot.py:68-132`、`WechatHookBot/utils/hookbot.py:133-172`
|
||||
- 定时任务启动:`WechatHookBot/bot.py:185-187`
|
||||
465
docs/插件开发.md
465
docs/插件开发.md
@@ -1,465 +0,0 @@
|
||||
# WechatHookBot 插件开发指南
|
||||
|
||||
## 插件系统
|
||||
|
||||
WechatHookBot 的插件系统完全兼容 XYBotV2,所有 XYBot 插件可以直接使用。
|
||||
|
||||
## 插件结构
|
||||
|
||||
```
|
||||
plugins/
|
||||
PluginName/
|
||||
__init__.py # 可选,可为空
|
||||
main.py # 必需,包含插件类
|
||||
config.toml # 可选,插件配置
|
||||
README.md # 可选,插件说明
|
||||
```
|
||||
|
||||
## 基本插件模板
|
||||
|
||||
```python
|
||||
from utils.plugin_base import PluginBase
|
||||
from utils.decorators import *
|
||||
from WechatHook import WechatHookClient
|
||||
|
||||
class MyPlugin(PluginBase):
|
||||
description = "插件描述"
|
||||
author = "作者名"
|
||||
version = "1.0.0"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# 同步初始化
|
||||
self.data = {}
|
||||
|
||||
async def async_init(self):
|
||||
# 异步初始化
|
||||
pass
|
||||
|
||||
@on_text_message(priority=50)
|
||||
async def handle_text(self, client: WechatHookClient, message: dict):
|
||||
"""处理文本消息"""
|
||||
content = message.get("Content", "")
|
||||
from_wxid = message.get("FromWxid", "")
|
||||
|
||||
if content == "你好":
|
||||
await client.send_text(from_wxid, "你好!我是机器人")
|
||||
return False # 阻止后续处理
|
||||
|
||||
return True # 继续执行后续处理器
|
||||
```
|
||||
|
||||
## 事件装饰器
|
||||
|
||||
### 消息事件
|
||||
|
||||
| 装饰器 | 触发条件 | 参数 |
|
||||
|--------|----------|------|
|
||||
| `@on_text_message` | 文本消息 | priority (0-99) |
|
||||
| `@on_image_message` | 图片消息 | priority |
|
||||
| `@on_voice_message` | 语音消息 | priority |
|
||||
| `@on_video_message` | 视频消息 | priority |
|
||||
| `@on_file_message` | 文件消息 | priority |
|
||||
| `@on_at_message` | @消息 | priority |
|
||||
| `@on_quote_message` | 引用消息 | priority |
|
||||
| `@on_card_message` | 名片消息 | priority |
|
||||
| `@on_location_message` | 位置消息 | priority |
|
||||
| `@on_link_message` | 链接消息 | priority |
|
||||
| `@on_miniapp_message` | 小程序消息 | priority |
|
||||
| `@on_emoji_message` | 表情消息 | priority |
|
||||
| `@on_revoke_message` | 撤回消息 | priority |
|
||||
| `@on_friend_request` | 好友请求 | priority |
|
||||
|
||||
### 优先级机制
|
||||
|
||||
- 优先级范围:0-99
|
||||
- 数值越大,优先级越高
|
||||
- 默认优先级:50
|
||||
- 按优先级从高到低执行
|
||||
|
||||
```python
|
||||
@on_text_message(priority=80) # 高优先级
|
||||
async def handle_important(self, client, message):
|
||||
pass
|
||||
|
||||
@on_text_message(priority=20) # 低优先级
|
||||
async def handle_normal(self, client, message):
|
||||
pass
|
||||
```
|
||||
|
||||
### 阻塞机制
|
||||
|
||||
- 返回 `False`:阻止后续处理器执行
|
||||
- 返回 `True` 或不返回:继续执行
|
||||
|
||||
```python
|
||||
@on_text_message
|
||||
async def handle_sensitive(self, client, message):
|
||||
if "敏感词" in message["Content"]:
|
||||
await client.send_text(message["FromWxid"], "检测到敏感内容")
|
||||
return False # 阻止后续执行
|
||||
return True # 继续执行
|
||||
```
|
||||
|
||||
## 定时任务
|
||||
|
||||
### interval - 间隔触发
|
||||
|
||||
```python
|
||||
@schedule('interval', seconds=30)
|
||||
async def periodic_task(self, client: WechatHookClient):
|
||||
"""每30秒执行一次"""
|
||||
pass
|
||||
|
||||
@schedule('interval', minutes=5)
|
||||
async def five_minutes_task(self, client: WechatHookClient):
|
||||
"""每5分钟执行一次"""
|
||||
pass
|
||||
```
|
||||
|
||||
### cron - 定时触发
|
||||
|
||||
```python
|
||||
@schedule('cron', hour=8, minute=30)
|
||||
async def morning_task(self, client: WechatHookClient):
|
||||
"""每天早上8:30执行"""
|
||||
await client.send_text("wxid_xxx", "早安!")
|
||||
|
||||
@schedule('cron', day_of_week='mon-fri', hour='9-17')
|
||||
async def work_time_task(self, client: WechatHookClient):
|
||||
"""工作日9-17点每小时执行"""
|
||||
pass
|
||||
```
|
||||
|
||||
### date - 指定时间触发
|
||||
|
||||
```python
|
||||
@schedule('date', run_date='2024-12-31 23:59:59')
|
||||
async def new_year_task(self, client: WechatHookClient):
|
||||
"""在指定时间执行一次"""
|
||||
pass
|
||||
```
|
||||
|
||||
## 消息对象结构
|
||||
|
||||
### 文本消息
|
||||
|
||||
```python
|
||||
{
|
||||
"FromWxid": "wxid_xxx", # 发送者 wxid
|
||||
"ToWxid": "wxid_yyy", # 接收者 wxid
|
||||
"Content": "消息内容", # 文本内容
|
||||
"MsgType": 1, # 消息类型
|
||||
"IsGroup": False, # 是否群聊
|
||||
"SenderWxid": "wxid_xxx", # 实际发送者(群聊时不同)
|
||||
"CreateTime": 1234567890, # 创建时间戳
|
||||
}
|
||||
```
|
||||
|
||||
### 群聊消息
|
||||
|
||||
```python
|
||||
{
|
||||
"FromWxid": "123@chatroom", # 群聊 ID
|
||||
"ToWxid": "wxid_bot", # 机器人 wxid
|
||||
"Content": "消息内容",
|
||||
"IsGroup": True, # 是否群聊
|
||||
"SenderWxid": "wxid_xxx", # 实际发送者
|
||||
"Ats": ["wxid_bot"], # 被@的用户列表
|
||||
}
|
||||
```
|
||||
|
||||
### 图片消息
|
||||
|
||||
```python
|
||||
{
|
||||
"FromWxid": "wxid_xxx",
|
||||
"Content": "base64_image_data", # 图片 base64 数据
|
||||
"MsgType": 3,
|
||||
"ImagePath": "/path/to/image", # 图片路径(如果有)
|
||||
}
|
||||
```
|
||||
|
||||
## WechatHookClient API
|
||||
|
||||
### 发送消息
|
||||
|
||||
```python
|
||||
# 发送文本
|
||||
await client.send_text(wxid, "消息内容")
|
||||
|
||||
# 发送图片
|
||||
await client.send_image(wxid, "/path/to/image.jpg")
|
||||
|
||||
# 发送文件
|
||||
await client.send_file(wxid, "/path/to/file.pdf")
|
||||
|
||||
# 发送视频
|
||||
await client.send_video(wxid, "/path/to/video.mp4")
|
||||
|
||||
# 发送名片
|
||||
await client.send_card(wxid, card_wxid, card_nickname)
|
||||
|
||||
# 发送位置
|
||||
await client.send_location(wxid, lat, lng, title, address)
|
||||
|
||||
# 发送链接
|
||||
await client.send_link(wxid, title, desc, url, thumb_url)
|
||||
|
||||
# 发送小程序
|
||||
await client.send_miniapp(wxid, appid, title, page_path, thumb_url)
|
||||
|
||||
# 群聊@消息
|
||||
await client.send_at_message(chatroom_id, content, at_list)
|
||||
|
||||
# 撤回消息
|
||||
await client.revoke_message(msg_id)
|
||||
```
|
||||
|
||||
### 好友管理
|
||||
|
||||
```python
|
||||
# 获取好友列表
|
||||
friends = await client.get_friend_list()
|
||||
|
||||
# 获取好友信息
|
||||
info = await client.get_friend_info(wxid)
|
||||
|
||||
# 搜索用户
|
||||
result = await client.search_user(keyword)
|
||||
|
||||
# 添加好友
|
||||
await client.add_friend(wxid, verify_msg)
|
||||
|
||||
# 同意好友请求
|
||||
await client.accept_friend(v3, v4, scene)
|
||||
|
||||
# 删除好友
|
||||
await client.delete_friend(wxid)
|
||||
|
||||
# 修改备注
|
||||
await client.set_friend_remark(wxid, remark)
|
||||
|
||||
# 检测好友状态
|
||||
status = await client.check_friend_status(wxid)
|
||||
```
|
||||
|
||||
### 群聊管理
|
||||
|
||||
```python
|
||||
# 获取群聊列表
|
||||
chatrooms = await client.get_chatroom_list()
|
||||
|
||||
# 获取群成员
|
||||
members = await client.get_chatroom_members(chatroom_id)
|
||||
|
||||
# 获取群信息
|
||||
info = await client.get_chatroom_info(chatroom_id)
|
||||
|
||||
# 创建群聊
|
||||
chatroom_id = await client.create_chatroom(member_list)
|
||||
|
||||
# 邀请进群
|
||||
await client.invite_to_chatroom(chatroom_id, wxid_list)
|
||||
|
||||
# 踢出群成员
|
||||
await client.remove_chatroom_member(chatroom_id, wxid_list)
|
||||
|
||||
# 退出群聊
|
||||
await client.quit_chatroom(chatroom_id)
|
||||
|
||||
# 修改群名称
|
||||
await client.set_chatroom_name(chatroom_id, name)
|
||||
|
||||
# 修改群公告
|
||||
await client.set_chatroom_announcement(chatroom_id, announcement)
|
||||
|
||||
# 修改我的群昵称
|
||||
await client.set_my_chatroom_nickname(chatroom_id, nickname)
|
||||
```
|
||||
|
||||
## 数据库使用
|
||||
|
||||
### KeyvalDB - 键值存储
|
||||
|
||||
```python
|
||||
from database.keyvalDB import KeyvalDB
|
||||
|
||||
keyval_db = KeyvalDB()
|
||||
|
||||
# 设置值
|
||||
await keyval_db.set("key", "value")
|
||||
|
||||
# 获取值
|
||||
value = await keyval_db.get("key")
|
||||
|
||||
# 删除值
|
||||
await keyval_db.delete("key")
|
||||
```
|
||||
|
||||
### XYBotDB - 业务数据
|
||||
|
||||
```python
|
||||
from database.XYBotDB import XYBotDB
|
||||
|
||||
db = XYBotDB()
|
||||
|
||||
# 使用 SQLAlchemy 操作数据库
|
||||
# 参考 XYBot 的数据库使用方式
|
||||
```
|
||||
|
||||
## 配置文件
|
||||
|
||||
### 插件配置 (config.toml)
|
||||
|
||||
```toml
|
||||
[basic]
|
||||
enable = true
|
||||
|
||||
[settings]
|
||||
api_key = "your_api_key"
|
||||
timeout = 30
|
||||
```
|
||||
|
||||
### 读取配置
|
||||
|
||||
```python
|
||||
import tomllib
|
||||
import os
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
config_path = os.path.join(os.path.dirname(__file__), "config.toml")
|
||||
with open(config_path, "rb") as f:
|
||||
config = tomllib.load(f)
|
||||
|
||||
self.enable = config.get("basic", {}).get("enable", False)
|
||||
self.api_key = config.get("settings", {}).get("api_key", "")
|
||||
```
|
||||
|
||||
## 日志系统
|
||||
|
||||
使用 loguru 记录日志:
|
||||
|
||||
```python
|
||||
from loguru import logger
|
||||
|
||||
# 不同级别的日志
|
||||
logger.debug("调试信息")
|
||||
logger.info("普通信息")
|
||||
logger.success("成功信息")
|
||||
logger.warning("警告信息")
|
||||
logger.error("错误信息")
|
||||
|
||||
# 带参数的日志
|
||||
logger.info("收到消息: {}", message)
|
||||
```
|
||||
|
||||
## 异步编程
|
||||
|
||||
所有插件函数必须是异步函数:
|
||||
|
||||
```python
|
||||
# ✅ 正确
|
||||
@on_text_message
|
||||
async def handle_text(self, client, message):
|
||||
await client.send_text(...)
|
||||
|
||||
# ❌ 错误
|
||||
@on_text_message
|
||||
def handle_text(self, client, message): # 缺少 async
|
||||
client.send_text(...) # 缺少 await
|
||||
```
|
||||
|
||||
### 使用阻塞函数
|
||||
|
||||
如需使用阻塞函数,使用 `asyncio.run_in_executor`:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
@on_text_message
|
||||
async def handle_text(self, client, message):
|
||||
# 在线程池中运行阻塞函数
|
||||
result = await asyncio.to_thread(blocking_function, arg1, arg2)
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
```python
|
||||
from utils.plugin_base import PluginBase
|
||||
from utils.decorators import *
|
||||
from WechatHook import WechatHookClient
|
||||
from loguru import logger
|
||||
import tomllib
|
||||
import os
|
||||
|
||||
class ExamplePlugin(PluginBase):
|
||||
description = "示例插件"
|
||||
author = "Your Name"
|
||||
version = "1.0.0"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# 读取配置
|
||||
config_path = os.path.join(os.path.dirname(__file__), "config.toml")
|
||||
with open(config_path, "rb") as f:
|
||||
config = tomllib.load(f)
|
||||
|
||||
self.enable = config.get("basic", {}).get("enable", False)
|
||||
self.keyword = config.get("settings", {}).get("keyword", "你好")
|
||||
|
||||
async def async_init(self):
|
||||
logger.info("ExamplePlugin 初始化完成")
|
||||
|
||||
@on_text_message(priority=50)
|
||||
async def handle_text(self, client: WechatHookClient, message: dict):
|
||||
if not self.enable:
|
||||
return
|
||||
|
||||
content = message.get("Content", "")
|
||||
from_wxid = message.get("FromWxid", "")
|
||||
|
||||
if self.keyword in content:
|
||||
await client.send_text(from_wxid, f"你说了关键词:{self.keyword}")
|
||||
logger.info(f"触发关键词回复: {from_wxid}")
|
||||
|
||||
@on_at_message(priority=60)
|
||||
async def handle_at(self, client: WechatHookClient, message: dict):
|
||||
if not self.enable:
|
||||
return
|
||||
|
||||
content = message.get("Content", "")
|
||||
from_wxid = message.get("FromWxid", "")
|
||||
|
||||
await client.send_text(from_wxid, "你@了我!")
|
||||
|
||||
@schedule('cron', hour=9, minute=0)
|
||||
async def morning_greeting(self, client: WechatHookClient):
|
||||
if not self.enable:
|
||||
return
|
||||
|
||||
# 每天早上9点发送问候
|
||||
await client.send_text("wxid_xxx", "早安!新的一天开始了")
|
||||
```
|
||||
|
||||
## 插件管理
|
||||
|
||||
### 禁用插件
|
||||
|
||||
在 `main_config.toml` 中添加:
|
||||
|
||||
```toml
|
||||
[Bot]
|
||||
disabled-plugins = ["ExamplePlugin", "AnotherPlugin"]
|
||||
```
|
||||
|
||||
### 删除插件
|
||||
|
||||
直接删除 `plugins/` 下对应的文件夹
|
||||
|
||||
### 热重载
|
||||
|
||||
通过 WebUI 或 ManagePlugin 插件实现热重载
|
||||
253
docs/架构设计.md
253
docs/架构设计.md
@@ -1,253 +0,0 @@
|
||||
# WechatHookBot 架构设计
|
||||
|
||||
## 技术架构
|
||||
|
||||
### 四层架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 第四层:WebUI 层(可选) │
|
||||
│ Flask + SocketIO + Bootstrap │
|
||||
│ - 插件管理、消息监控、配置管理 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↕
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 第三层:插件层(完全复用 XYBot) │
|
||||
│ PluginManager → PluginBase → 具体插件 │
|
||||
│ 装饰器:@on_text_message, @on_image_message, @schedule │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↕
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 第二层:Bot 核心层 │
|
||||
│ HookBot - 消息预处理、路由、类型映射 │
|
||||
│ EventManager - 事件分发、优先级、阻塞机制 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↕
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 第一层:WechatHook 层 │
|
||||
│ WechatHookClient - API 封装 │
|
||||
│ NoveLoader - DLL 调用 │
|
||||
│ 回调处理器 - Socket 回调 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↕
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 底层:DLL 层 │
|
||||
│ Loader.dll ←→ Helper.dll (注入微信进程) ←→ 微信客户端 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 消息流转
|
||||
|
||||
### 接收消息流程
|
||||
|
||||
```
|
||||
微信消息
|
||||
→ Helper.dll (注入微信进程)
|
||||
→ Socket 回调
|
||||
→ on_recv_callback(client_id, msg_type, data)
|
||||
→ HookBot.process_message()
|
||||
→ 消息类型映射 (type → event)
|
||||
→ EventManager.emit(event_type, client, message)
|
||||
→ 插件事件处理器(按优先级执行)
|
||||
```
|
||||
|
||||
### 发送消息流程
|
||||
|
||||
```
|
||||
插件调用
|
||||
→ client.send_text(wxid, content)
|
||||
→ WechatHookClient 封装
|
||||
→ 构造 JSON payload {"type": 11036, "data": {...}}
|
||||
→ NoveLoader.SendWeChatData()
|
||||
→ Loader.dll
|
||||
→ Helper.dll
|
||||
→ 微信发送
|
||||
```
|
||||
|
||||
## 核心模块
|
||||
|
||||
### 1. WechatHook 层
|
||||
|
||||
**NoveLoader** - DLL 函数封装
|
||||
```python
|
||||
class NoveLoader:
|
||||
- InitWeChatSocket() # 初始化回调
|
||||
- InjectWeChat() # 注入微信
|
||||
- SendWeChatData() # 发送数据
|
||||
- DestroyWeChat() # 销毁连接
|
||||
```
|
||||
|
||||
**WechatHookClient** - API 封装
|
||||
```python
|
||||
class WechatHookClient:
|
||||
- send_text() # 发送文本
|
||||
- send_image() # 发送图片
|
||||
- send_file() # 发送文件
|
||||
- get_friend_list() # 获取好友列表
|
||||
- get_chatroom_list() # 获取群聊列表
|
||||
- add_friend() # 添加好友
|
||||
# ... 更多 API
|
||||
```
|
||||
|
||||
### 2. Bot 核心层
|
||||
|
||||
**HookBot** - 消息处理核心
|
||||
```python
|
||||
class HookBot:
|
||||
- process_message() # 处理消息
|
||||
- _normalize_message() # 统一消息格式
|
||||
- _filter_message() # 白名单/黑名单过滤
|
||||
```
|
||||
|
||||
**EventManager** - 事件管理(复用 XYBot)
|
||||
```python
|
||||
class EventManager:
|
||||
- bind_instance() # 绑定插件实例
|
||||
- emit() # 触发事件
|
||||
- unbind_instance() # 解绑实例
|
||||
```
|
||||
|
||||
### 3. 插件层
|
||||
|
||||
**PluginBase** - 插件基类(复用 XYBot)
|
||||
```python
|
||||
class PluginBase:
|
||||
description: str
|
||||
author: str
|
||||
version: str
|
||||
|
||||
async def on_enable() # 启用时调用
|
||||
async def on_disable() # 禁用时调用
|
||||
async def async_init() # 异步初始化
|
||||
```
|
||||
|
||||
**装饰器系统**(复用 XYBot)
|
||||
- `@on_text_message` - 文本消息
|
||||
- `@on_image_message` - 图片消息
|
||||
- `@on_voice_message` - 语音消息
|
||||
- `@on_at_message` - @消息
|
||||
- `@schedule` - 定时任务
|
||||
|
||||
## 消息类型映射
|
||||
|
||||
从个微 API 的 type 值映射到内部事件类型:
|
||||
|
||||
```python
|
||||
MESSAGE_TYPE_MAP = {
|
||||
# 需要根据实际 API 文档补充
|
||||
10001: "text_message",
|
||||
10002: "image_message",
|
||||
10003: "voice_message",
|
||||
10004: "video_message",
|
||||
10005: "file_message",
|
||||
10006: "card_message",
|
||||
10007: "location_message",
|
||||
10008: "link_message",
|
||||
10009: "miniapp_message",
|
||||
10010: "emoji_message",
|
||||
10011: "revoke_message",
|
||||
10012: "system_message",
|
||||
10013: "friend_request",
|
||||
# 群聊通知
|
||||
10020: "chatroom_member_add",
|
||||
10021: "chatroom_member_remove",
|
||||
}
|
||||
```
|
||||
|
||||
## 数据库设计
|
||||
|
||||
复用 XYBot 的数据库架构:
|
||||
|
||||
### XYBotDB (SQLite)
|
||||
- 用户表 - 用户信息、积分
|
||||
- 签到表 - 签到记录
|
||||
- 其他业务表
|
||||
|
||||
### MessageDB (SQLite + aiosqlite)
|
||||
- 消息记录表(可选)
|
||||
|
||||
### KeyvalDB (SQLite + aiosqlite)
|
||||
- 键值存储表
|
||||
- 用于配置、状态管理
|
||||
|
||||
## 配置系统
|
||||
|
||||
### main_config.toml
|
||||
|
||||
```toml
|
||||
[WechatHook]
|
||||
loader-dll = "libs/Loader.dll"
|
||||
helper-dll = "libs/Helper.dll"
|
||||
|
||||
[Bot]
|
||||
version = "v1.0.0"
|
||||
admins = ["admin_wxid"]
|
||||
disabled-plugins = []
|
||||
timezone = "Asia/Shanghai"
|
||||
|
||||
# 消息过滤
|
||||
ignore-mode = "None" # None/Whitelist/Blacklist
|
||||
whitelist = []
|
||||
blacklist = []
|
||||
|
||||
[Database]
|
||||
xybot-db = "sqlite:///database/hookbot.db"
|
||||
message-db = "sqlite+aiosqlite:///database/message.db"
|
||||
keyval-db = "sqlite+aiosqlite:///database/keyval.db"
|
||||
|
||||
[WebUI]
|
||||
admin-username = "admin"
|
||||
admin-password = "admin123"
|
||||
session-timeout = 30
|
||||
```
|
||||
|
||||
## 与 XYBot 的对比
|
||||
|
||||
| 特性 | XYBot | WechatHookBot |
|
||||
|------|-------|---------------|
|
||||
| 底层技术 | 协议实现 | DLL Hook |
|
||||
| 登录方式 | 二维码/唤醒 | 无需登录 |
|
||||
| 依赖 | Redis | 无 |
|
||||
| 消息接收 | sync_message 轮询 | Socket 回调 |
|
||||
| 插件系统 | ✅ | ✅ 完全兼容 |
|
||||
| 风控风险 | 中 | 高 |
|
||||
| 多开支持 | 需多实例 | 原生支持 |
|
||||
| Python 版本 | 3.11 | 3.x (32位) |
|
||||
|
||||
## 优势
|
||||
|
||||
1. **架构更简单**:无需 Redis,减少依赖
|
||||
2. **无需登录**:Hook 已登录微信,省去登录流程
|
||||
3. **实时性更好**:Socket 回调,无需轮询
|
||||
4. **代码复用**:80% 代码可复用 XYBot
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **32位限制**:DLL 是 32位,必须使用 32位 Python
|
||||
2. **风控风险**:Hook 方式风控风险较高
|
||||
3. **依赖微信**:必须有微信客户端在运行
|
||||
4. **杀软拦截**:DLL 可能被杀毒软件拦截
|
||||
|
||||
## 开发路线
|
||||
|
||||
### 第一阶段:基础框架
|
||||
- [ ] WechatHook 层实现
|
||||
- [ ] HookBot 核心类
|
||||
- [ ] 消息类型映射
|
||||
- [ ] 基础 API 封装
|
||||
|
||||
### 第二阶段:插件系统
|
||||
- [ ] 复用 XYBot 插件系统
|
||||
- [ ] 适配消息格式
|
||||
- [ ] 测试插件兼容性
|
||||
|
||||
### 第三阶段:完善功能
|
||||
- [ ] 数据库集成
|
||||
- [ ] 配置系统
|
||||
- [ ] 日志系统
|
||||
- [ ] 错误处理
|
||||
|
||||
### 第四阶段:WebUI(可选)
|
||||
- [ ] 复用 XYBot WebUI
|
||||
- [ ] 适配 Hook API
|
||||
- [ ] 实时监控界面
|
||||
236
docs/项目概览.md
236
docs/项目概览.md
@@ -1,236 +0,0 @@
|
||||
# WechatHookBot 项目概览
|
||||
|
||||
## 📁 当前项目结构
|
||||
|
||||
```
|
||||
WechatHookBot/
|
||||
├── docs/ # 📚 文档目录
|
||||
│ ├── 项目概览.md # 本文件
|
||||
│ ├── 快速开始.md # 安装和运行指南
|
||||
│ ├── 架构设计.md # 技术架构文档
|
||||
│ ├── 插件开发.md # 插件开发指南
|
||||
│ └── API文档.md # API 接口文档
|
||||
├── main_config.toml # ⚙️ 主配置文件
|
||||
├── requirements.txt # 📦 Python 依赖
|
||||
├── .gitignore # 🚫 Git 忽略文件
|
||||
└── README.md # 📖 项目说明
|
||||
```
|
||||
|
||||
## 📋 已完成的工作
|
||||
|
||||
### ✅ 文档系统
|
||||
- [x] README.md - 项目介绍和快速开始
|
||||
- [x] 架构设计.md - 完整的技术架构设计
|
||||
- [x] 插件开发.md - 详细的插件开发指南
|
||||
- [x] API文档.md - 完整的 API 接口文档
|
||||
- [x] 快速开始.md - 安装和运行教程
|
||||
- [x] 项目概览.md - 项目总览(本文件)
|
||||
|
||||
### ✅ 配置文件
|
||||
- [x] main_config.toml - 主配置文件模板
|
||||
- [x] requirements.txt - Python 依赖列表
|
||||
- [x] .gitignore - Git 忽略规则
|
||||
|
||||
## 🚧 待开发模块
|
||||
|
||||
### 第一阶段:核心框架(优先级:高)
|
||||
|
||||
```
|
||||
WechatHook/ # Hook 层实现
|
||||
├── __init__.py
|
||||
├── loader.py # NoveLoader DLL 封装
|
||||
├── client.py # WechatHookClient API 封装
|
||||
├── message_types.py # 消息类型映射
|
||||
└── callbacks.py # 回调处理器
|
||||
```
|
||||
|
||||
**任务清单:**
|
||||
- [ ] 实现 NoveLoader 类(基于 python_demo.py)
|
||||
- [ ] 实现 WechatHookClient 类(封装所有 API)
|
||||
- [ ] 定义消息类型映射表
|
||||
- [ ] 实现回调处理系统
|
||||
|
||||
### 第二阶段:Bot 核心(优先级:高)
|
||||
|
||||
```
|
||||
utils/ # 工具类(复用 XYBot)
|
||||
├── __init__.py
|
||||
├── plugin_base.py # ✅ 从 XYBot 复制
|
||||
├── plugin_manager.py # ✅ 从 XYBot 复制
|
||||
├── event_manager.py # ✅ 从 XYBot 复制
|
||||
├── decorators.py # ✅ 从 XYBot 复制
|
||||
├── singleton.py # ✅ 从 XYBot 复制
|
||||
└── hookbot.py # ⚠️ 新实现(类似 xybot.py)
|
||||
|
||||
bot.py # 主入口(简化版)
|
||||
```
|
||||
|
||||
**任务清单:**
|
||||
- [ ] 从 XYBot 复制 utils/ 下的文件
|
||||
- [ ] 实现 HookBot 类(消息处理核心)
|
||||
- [ ] 实现 bot.py 主入口
|
||||
- [ ] 适配消息格式(统一为 XYBot 格式)
|
||||
|
||||
### 第三阶段:数据库(优先级:中)
|
||||
|
||||
```
|
||||
database/ # 数据库(复用 XYBot)
|
||||
├── __init__.py
|
||||
├── XYBotDB.py # ✅ 从 XYBot 复制
|
||||
├── keyvalDB.py # ✅ 从 XYBot 复制
|
||||
└── messsagDB.py # ✅ 从 XYBot 复制
|
||||
```
|
||||
|
||||
**任务清单:**
|
||||
- [ ] 从 XYBot 复制数据库文件
|
||||
- [ ] 修改数据库名称(xybot.db → hookbot.db)
|
||||
- [ ] 测试数据库功能
|
||||
|
||||
### 第四阶段:插件系统(优先级:中)
|
||||
|
||||
```
|
||||
plugins/ # 插件目录
|
||||
├── ExamplePlugin/ # 示例插件
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py
|
||||
│ └── config.toml
|
||||
└── Menu/ # 菜单插件(从 XYBot 移植)
|
||||
├── __init__.py
|
||||
├── main.py
|
||||
└── config.toml
|
||||
```
|
||||
|
||||
**任务清单:**
|
||||
- [ ] 创建 ExamplePlugin 示例插件
|
||||
- [ ] 从 XYBot 移植 Menu 插件
|
||||
- [ ] 测试插件兼容性
|
||||
- [ ] 根据需要移植更多插件
|
||||
|
||||
### 第五阶段:WebUI(优先级:低,可选)
|
||||
|
||||
```
|
||||
WebUI/ # Web 管理界面(复用 XYBot)
|
||||
├── __init__.py
|
||||
├── config.py
|
||||
├── routes/ # 路由
|
||||
├── services/ # 服务层
|
||||
├── templates/ # 模板
|
||||
└── static/ # 静态资源
|
||||
|
||||
app.py # WebUI 入口
|
||||
```
|
||||
|
||||
**任务清单:**
|
||||
- [ ] 从 XYBot 复制 WebUI 代码
|
||||
- [ ] 适配 WechatHook API
|
||||
- [ ] 实现 app.py 入口
|
||||
- [ ] 测试 WebUI 功能
|
||||
|
||||
### 第六阶段:辅助功能(优先级:低)
|
||||
|
||||
```
|
||||
libs/ # DLL 文件目录
|
||||
├── Loader.dll # 从个微大客户版复制
|
||||
└── Helper.dll # 从个微大客户版复制
|
||||
|
||||
logs/ # 日志目录(自动创建)
|
||||
resources/ # 资源目录(自动创建)
|
||||
```
|
||||
|
||||
**任务清单:**
|
||||
- [ ] 准备 DLL 文件
|
||||
- [ ] 实现日志系统
|
||||
- [ ] 实现资源管理
|
||||
|
||||
## 📊 开发进度
|
||||
|
||||
| 阶段 | 模块 | 状态 | 进度 |
|
||||
|------|------|------|------|
|
||||
| 0 | 文档系统 | ✅ 完成 | 100% |
|
||||
| 1 | WechatHook 层 | 🚧 待开发 | 0% |
|
||||
| 2 | Bot 核心层 | 🚧 待开发 | 0% |
|
||||
| 3 | 数据库 | 🚧 待开发 | 0% |
|
||||
| 4 | 插件系统 | 🚧 待开发 | 0% |
|
||||
| 5 | WebUI | 🚧 待开发 | 0% |
|
||||
| 6 | 辅助功能 | 🚧 待开发 | 0% |
|
||||
|
||||
**总体进度:** 15% (文档完成)
|
||||
|
||||
## 🎯 下一步行动
|
||||
|
||||
### 立即开始(推荐顺序)
|
||||
|
||||
1. **准备 DLL 文件**
|
||||
- 从个微大客户版复制 `Loader.dll` 和 `Helper.dll` 到 `libs/` 目录
|
||||
|
||||
2. **实现 WechatHook 层**
|
||||
- 参考 `个微大客户版/python4.1.2.17Demo/python_demo.py`
|
||||
- 实现 `WechatHook/loader.py`(NoveLoader 类)
|
||||
- 实现 `WechatHook/client.py`(WechatHookClient 类)
|
||||
|
||||
3. **复用 XYBot 代码**
|
||||
- 复制 `XYBotV2/utils/` 到 `WechatHookBot/utils/`
|
||||
- 复制 `XYBotV2/database/` 到 `WechatHookBot/database/`
|
||||
|
||||
4. **实现 Bot 核心**
|
||||
- 参考 `XYBotV2/utils/xybot.py` 实现 `utils/hookbot.py`
|
||||
- 实现 `bot.py` 主入口
|
||||
|
||||
5. **测试基础功能**
|
||||
- 创建简单的测试插件
|
||||
- 测试消息收发
|
||||
- 验证插件系统
|
||||
|
||||
## 📚 参考资料
|
||||
|
||||
### 内部文档
|
||||
- [快速开始](快速开始.md) - 环境准备和安装
|
||||
- [架构设计](架构设计.md) - 技术架构详解
|
||||
- [插件开发](插件开发.md) - 插件开发指南
|
||||
- [API文档](API文档.md) - API 接口参考
|
||||
|
||||
### 参考项目
|
||||
- `XYBotV2/` - 插件系统、事件管理、数据库
|
||||
- `个微大客户版/python4.1.2.17Demo/python_demo.py` - DLL 调用示例
|
||||
- `个微大客户版/API/` - API 接口文档
|
||||
|
||||
## 💡 开发建议
|
||||
|
||||
### 代码复用策略
|
||||
- ✅ **完全复用**:utils/(插件系统、事件管理、装饰器)
|
||||
- ✅ **完全复用**:database/(数据库层)
|
||||
- ⚠️ **参考实现**:hookbot.py(参考 xybot.py,但需适配)
|
||||
- 🆕 **全新实现**:WechatHook/(Hook 层封装)
|
||||
|
||||
### 开发优先级
|
||||
1. **核心功能优先**:先实现消息收发和插件系统
|
||||
2. **逐步完善**:基础功能稳定后再添加 WebUI
|
||||
3. **测试驱动**:每个模块完成后立即测试
|
||||
|
||||
### 注意事项
|
||||
- 使用 32位 Python 开发和测试
|
||||
- 保持与 XYBot 插件的兼容性
|
||||
- 注意消息格式的统一转换
|
||||
- 做好错误处理和日志记录
|
||||
|
||||
## 🔗 相关链接
|
||||
|
||||
- **XYBotV2 项目**:`D:\project\shrobot\XYBotV2\`
|
||||
- **个微大客户版**:`D:\project\shrobot\个微大客户版\`
|
||||
- **当前项目**:`D:\project\shrobot\WechatHookBot\`
|
||||
|
||||
## 📝 更新日志
|
||||
|
||||
### 2025-01-XX
|
||||
- ✅ 创建项目目录结构
|
||||
- ✅ 完成所有文档编写
|
||||
- ✅ 创建配置文件模板
|
||||
- 🚧 准备开始代码实现
|
||||
|
||||
---
|
||||
|
||||
**项目状态:** 🚧 文档完成,代码开发中
|
||||
|
||||
**预计完成时间:** 2-3 天(基础框架)
|
||||
|
||||
**当前任务:** 实现 WechatHook 层
|
||||
Reference in New Issue
Block a user