init: WeChat Hermes Gateway - wxhook + Hermes AI auto-reply bot
This commit is contained in:
+16
@@ -0,0 +1,16 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.venv/
|
||||||
|
*.egg-info/
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
# 🤖 WeChat Hermes Gateway
|
||||||
|
|
||||||
|
Windows 微信机器人 ↔ Linux Hermes AI,全自动双向聊天。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 最终架构
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────┐
|
||||||
|
│ Windows 192.168.0.111 │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────┐ ┌───────────────────────────┐ │
|
||||||
|
│ │ 微信 3.9.5.81 x64 │ │ 日常微信 WeChatAppEx 4.x │ │
|
||||||
|
│ │ 机器人号modachen │ │ 老爸日常使用,互不干扰 │ │
|
||||||
|
│ │ wxhook Bot类TCP │ └───────────────────────────┘ │
|
||||||
|
│ └────────┬─────────┘ │
|
||||||
|
│ │ wxhook DLL 收消息 │
|
||||||
|
│ ┌────────▼────────────────┐ │
|
||||||
|
│ │ wechat_agent.py │ ← 常驻进程 │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Bot类接收微信事件(TCP) │ │
|
||||||
|
│ │ → POST Hermes API :8642 │ ← 带session: sisyphus │
|
||||||
|
│ │ ← 收回复 → wxhook发回 │ │
|
||||||
|
│ │ :5801 ←Hermes找小小莫 │ ← 双向通道 │
|
||||||
|
│ │ 看门狗防 wxhook 挂掉 │ │
|
||||||
|
│ └────────┬────────────────┘ │
|
||||||
|
└───────────┼─────────────────────────────────────────────────┘
|
||||||
|
│ HTTP (局域网)
|
||||||
|
┌───────────▼─────────────────────────────────────────────────┐
|
||||||
|
│ Linux 192.168.0.103 │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────┐ │
|
||||||
|
│ │ Hermes Gateway │ │
|
||||||
|
│ │ - API Server :8642 │ ← OpenAI兼容API │
|
||||||
|
│ │ - session自动重置: 已关闭 │ ← sisyphus永不清 │
|
||||||
|
│ │ - 健康检查 /health │ │
|
||||||
|
│ └──────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ hermes CLI — AI 处理引擎 │
|
||||||
|
│ 莫荷/莫小荷 — 老爸专属称呼 │
|
||||||
|
└──────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据流
|
||||||
|
|
||||||
|
### 文字消息
|
||||||
|
|
||||||
|
```
|
||||||
|
老爸发微信
|
||||||
|
→ WeChat 3.9.5.81 收到
|
||||||
|
→ wxhook DLL TCP → Bot 类 → on_msg 处理器
|
||||||
|
→ wechat_agent.py POST Hermes API (:8642)
|
||||||
|
→ X-Hermes-Session-Id: sisyphus (固定)
|
||||||
|
→ Hermes 处理 → 返回回复
|
||||||
|
→ wechat_agent.py 收回复 → wxhook API 发回
|
||||||
|
→ 老爸手机收到
|
||||||
|
```
|
||||||
|
|
||||||
|
### 图片消息
|
||||||
|
|
||||||
|
```
|
||||||
|
老爸发图片
|
||||||
|
→ WeChat 收到 → wxhook IMAGE_MESSAGE 事件
|
||||||
|
→ wechat_agent.py 保存图片 → 调豆包OCR (VolcEngine)
|
||||||
|
→ OCR 文字结果 + 通知 → POST Hermes API
|
||||||
|
→ Hermes 知道图片内容 → 回复老爸
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hermes 找小小莫(双向)
|
||||||
|
|
||||||
|
```
|
||||||
|
Hermes → POST http://192.168.0.111:5801/hermes-msg
|
||||||
|
→ wechat_agent.py 写入日志和 inbox 文件
|
||||||
|
```
|
||||||
|
|
||||||
|
## 人物 / ID
|
||||||
|
|
||||||
|
| 角色 | 微信名 | wxid | 说明 |
|
||||||
|
|------|--------|------|------|
|
||||||
|
| 老爸 | 莫语不语 | `wxid_c0a6izmwd78y22` | 用户,主人 |
|
||||||
|
| 莫荷/莫小荷 | modachenchen | `wxid_7onnerpx2s2l22` | Hermes AI,老爸专属称呼"莫小荷" |
|
||||||
|
| 小小莫 | — | — | Sisyphus,Windows 运维,通过 API 与 Hermes 通信 |
|
||||||
|
|
||||||
|
## 关键端口
|
||||||
|
|
||||||
|
| 端口 | 用途 | 所在 |
|
||||||
|
|------|------|------|
|
||||||
|
| 19001 | wxhook HTTP API | Windows |
|
||||||
|
| 5801 | Hermes→小小莫 消息入口 | Windows |
|
||||||
|
| 8642 | Hermes API Server (OpenAI兼容) | Linux |
|
||||||
|
| 5800 | bridge.py (旧,不再使用) | Linux |
|
||||||
|
|
||||||
|
## 组件
|
||||||
|
|
||||||
|
### Windows 端(wechat_agent.py)
|
||||||
|
- **wxhook Bot 类** — DLL 注入 + TCP 收消息
|
||||||
|
- **Hermes API 调用** — 直接 POST :8642,session 固定 `sisyphus`
|
||||||
|
- **回复服务** — 5801 端口收 Hermes 消息
|
||||||
|
- **看门狗** — 2 分钟无消息自动刷新 webhook;API 挂了才重注入 DLL
|
||||||
|
- **昵称缓存** — 从 wxhook getContactList 获取联系人昵称
|
||||||
|
|
||||||
|
### Linux 端(Hermes Gateway)
|
||||||
|
- **API Server** — 0.0.0.0:8642,Bearer auth
|
||||||
|
- **session 管理** — `api_server` 平台关闭自动重置,`sisyphus` 永不清上下文
|
||||||
|
- **配置位置** — `/home/hmo/.hermes/config.yaml`
|
||||||
|
- **Provider** — `ocg-new` → `https://opencode.ai/zen/go/v1`
|
||||||
|
|
||||||
|
## 启动步骤
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
```batch
|
||||||
|
cd D:\F\NewI\opencode\daily-workspace\projects\wechat-hermes-gateway
|
||||||
|
scripts\start_bridge.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
或直接:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:PYTHONHOME=''
|
||||||
|
Start-Process -WindowStyle Hidden python.exe scripts\wechat_agent.py
|
||||||
|
```
|
||||||
|
|
||||||
|
启动后需用修复过低工具扫码登录微信。
|
||||||
|
|
||||||
|
### Linux(如重启后)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /home/hmo/hermes-agent/.venv/bin/activate
|
||||||
|
hermes gateway restart
|
||||||
|
```
|
||||||
|
|
||||||
|
验证:
|
||||||
|
```bash
|
||||||
|
ss -tlnp | grep 8642
|
||||||
|
curl http://127.0.0.1:8642/v1/models
|
||||||
|
```
|
||||||
|
|
||||||
|
## 通信方式
|
||||||
|
|
||||||
|
| 方向 | 方式 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| 小小莫 → Hermes | POST :8642/v1/chat/completions | 带 `X-Hermes-Session-Id: sisyphus` |
|
||||||
|
| Hermes → 小小莫 | POST :5801/hermes-msg | 写入 `C:\Users\hmo\Desktop\hermes_inbox.txt` |
|
||||||
|
| 老爸 ↔ Hermes | 微信聊天 | 自动通过 wechat_agent.py 桥接 |
|
||||||
|
|
||||||
|
## 项目文件
|
||||||
|
|
||||||
|
```
|
||||||
|
wechat-hermes-gateway/
|
||||||
|
├── README.md # 本文档
|
||||||
|
├── scripts/
|
||||||
|
│ ├── wechat_agent.py # 主力:微信机器人代理
|
||||||
|
│ └── start_bridge.bat # 一键启动脚本
|
||||||
|
└── temp/ # 废弃/临时脚本
|
||||||
|
```
|
||||||
|
|
||||||
|
## 历史决策
|
||||||
|
|
||||||
|
1. **wxhook HTTP webhook 不可靠** → 改用 Bot 类 TCP 收消息
|
||||||
|
2. **Bot 类偶尔停发事件** → 加看门狗自动刷新
|
||||||
|
3. `hermes -z` **无上下文** → 改用 Hermes API Server (:8642) + session
|
||||||
|
4. **session 自动重置** → 关闭 api_server 平台的重置策略
|
||||||
|
5. **群聊不认人** → session 固定 `sisyphus`,所有消息共享上下文
|
||||||
|
6. **Linux bridge 常挂** → 去掉 bridge.py,Windows 直接调 Hermes API
|
||||||
|
|
||||||
|
## 已实现的功能
|
||||||
|
|
||||||
|
| 功能 | 状态 |
|
||||||
|
|------|------|
|
||||||
|
| 文字消息收发(个人聊天) | ✅ 双向,session 上下文连贯 |
|
||||||
|
| 文字消息收发(群聊) | ✅ 同 session,认识老爸 |
|
||||||
|
| 图片消息接收 + OCR 分析 | ✅ 自动保存 → 豆包 OCR → 结果给 Hermes |
|
||||||
|
| Hermes 身份认知 | ✅ 知道自己是莫荷/莫小荷,知道老爸 |
|
||||||
|
| 会话上下文持续 | ✅ session `sisyphus`,自动重置已关闭 |
|
||||||
|
| 小小莫 ↔ Hermes 双向通信 | ✅ API (:8642) + HTTP (:5801/hermes-msg) |
|
||||||
|
| wxhook 看门狗自愈 | ✅ 2分钟无消息刷新 webhook |
|
||||||
|
| 昵称识别 | ✅ 从 getContactList 获取 |
|
||||||
|
| 联系人列表查询 | ✅ wxhook /api/getContactList |
|
||||||
|
|
||||||
|
## 未实现 / 不可行
|
||||||
|
|
||||||
|
| 功能 | 原因 |
|
||||||
|
|------|------|
|
||||||
|
| 语音消息 | ⏳ 已能检测并下载,转文字(STT)待接入 |
|
||||||
|
| 发送图片 | wxhook 有 send_image API 但未接入回复链路 |
|
||||||
|
| 文件收发 | 同上,未接入 |
|
||||||
|
| 换头像/改资料 | wxhook 无相关 API |
|
||||||
|
| 群管理(拉人踢人) | wxhook 无群管理 API |
|
||||||
|
| iLink 官方 bot 接口 | 限制太多,弃用 |
|
||||||
|
| 多人独立会话 | 目前全部共享 `sisyphus` 单会话 |
|
||||||
|
|
||||||
|
## 灾难恢复流程
|
||||||
|
|
||||||
|
### 场景:Windows 重启
|
||||||
|
|
||||||
|
需要 3 步,**顺序不能错**:
|
||||||
|
|
||||||
|
```
|
||||||
|
第1步:双击 start_bridge.bat
|
||||||
|
→ 自动杀旧微信 → Bot 类启动新微信 + 注入 DLL
|
||||||
|
→ 等待微信窗口出现
|
||||||
|
|
||||||
|
第2步:运行修复过低工具
|
||||||
|
→ 选择修复过低6.0\低版通用杀器.sp.exe
|
||||||
|
→ 自动扫描到已运行的微信 → 打补丁 → 弹出登录二维码
|
||||||
|
|
||||||
|
第3步:手机扫码登录
|
||||||
|
→ 登录后 wechat_agent.py 自动检测到登录状态
|
||||||
|
→ 开始转发消息
|
||||||
|
→ 给 filehelper 发 "[Bridge] online" 确认
|
||||||
|
```
|
||||||
|
|
||||||
|
验证:手机发条消息给 modachenchen,看能否收到自动回复。
|
||||||
|
|
||||||
|
### 场景:Linux 重启
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 启动 Hermes gateway(自动恢复 session)
|
||||||
|
source /home/hmo/hermes-agent/.venv/bin/activate
|
||||||
|
hermes gateway restart
|
||||||
|
|
||||||
|
# 2. 验证
|
||||||
|
ss -tlnp | grep 8642 # 确认 API 端口
|
||||||
|
curl http://127.0.0.1:8642/v1/models # 确认 API 响应
|
||||||
|
|
||||||
|
# 3. 确认 Windows 能连上
|
||||||
|
# 从 Windows 运行:
|
||||||
|
curl http://192.168.0.103:8642/v1/models -H "Authorization: Bearer hermes123"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景:两边都重启了
|
||||||
|
|
||||||
|
1. Linux 先:`hermes gateway restart` + 验证 8642 监听
|
||||||
|
2. Windows 后:`start_bridge.bat` → 修复工具登录 → 完成
|
||||||
|
|
||||||
|
### 场景:Hermes 不认人了(session 丢了)
|
||||||
|
|
||||||
|
不用慌,system prompt 里已经写死了她的身份和你的身份。
|
||||||
|
发条消息她就会看到:
|
||||||
|
> "你是莫荷,女生。你的主人是老爸(微信名:莫语不语)"
|
||||||
|
|
||||||
|
如果连这都不奏效 → 告诉 Hermes "去找小小莫" → 它会 POST 到 :5801/hermes-msg → 我来处理。
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- wxhook DLL 仅支持 x64 微信 3.9.5.81
|
||||||
|
- 每次 WeChat 重启需重新登录(修复过低工具)
|
||||||
|
- **start_bridge.bat 必须第 1 步执行**,修复工具第 2 步
|
||||||
|
- Hermes API 首次调用可能较慢(大模型冷启动)
|
||||||
|
- 看门狗只刷新 webhook,不会误伤正常消息处理
|
||||||
|
- 如果微信登录后没反应,等 1-2 分钟看门狗会自动刷新
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
"""
|
||||||
|
WeChat Hermes Bridge — with webhook keepalive
|
||||||
|
"""
|
||||||
|
import pymem, pymem.process, requests, time, json, threading
|
||||||
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
|
|
||||||
|
DLL = r"C:\Users\hmo\AppData\Local\Programs\Python\Python310\Lib\site-packages\wxhook\tools\wxhook.dll"
|
||||||
|
WX = "http://127.0.0.1:19088"
|
||||||
|
HERMES = "http://192.168.0.103:5800/callback"
|
||||||
|
LOG = r"C:\Users\hmo\Desktop\bridge.log"
|
||||||
|
BOT = "wxid_7onnerpx2s2l22"
|
||||||
|
PORT = 5801
|
||||||
|
|
||||||
|
def log(m):
|
||||||
|
with open(LOG, "a", encoding="utf-8") as f:
|
||||||
|
f.write(f"{time.strftime('%H:%M:%S')} {m}\n")
|
||||||
|
|
||||||
|
# Inject DLL
|
||||||
|
try:
|
||||||
|
requests.post(f"{WX}/api/checkLogin", json={}, timeout=3)
|
||||||
|
log("DLL OK")
|
||||||
|
except:
|
||||||
|
pm = pymem.Pymem("WeChat.exe")
|
||||||
|
pymem.process.inject_dll(pm.process_handle, DLL.encode())
|
||||||
|
time.sleep(2)
|
||||||
|
log("DLL injected")
|
||||||
|
|
||||||
|
log(f"login: {requests.post(f'{WX}/api/checkLogin', json={}, timeout=5).json()}")
|
||||||
|
|
||||||
|
# Periodic webhook refresh (every 30 seconds)
|
||||||
|
def webhook_keepalive():
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
requests.post(f"{WX}/api/hookSyncMsg", json={
|
||||||
|
"port": PORT, "ip": "0.0.0.0", "enableHttp": 1,
|
||||||
|
"url": f"http://127.0.0.1:{PORT}", "timeout": 300
|
||||||
|
}, timeout=5)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
time.sleep(30)
|
||||||
|
|
||||||
|
threading.Thread(target=webhook_keepalive, daemon=True).start()
|
||||||
|
log("keepalive started (30s)")
|
||||||
|
|
||||||
|
# Test message
|
||||||
|
requests.post(f"{WX}/api/sendTextMsg",
|
||||||
|
json={"wxid": "filehelper", "msg": "[Bridge] online"}, timeout=5)
|
||||||
|
|
||||||
|
class H(BaseHTTPRequestHandler):
|
||||||
|
def do_POST(self):
|
||||||
|
body = self.rfile.read(int(self.headers.get("Content-Length", 0)))
|
||||||
|
try:
|
||||||
|
d = json.loads(body)
|
||||||
|
fu = d.get("fromUser", "")
|
||||||
|
ct = d.get("content", "")
|
||||||
|
if fu and ct and fu != BOT:
|
||||||
|
log(f"MSG {fu}: {ct[:60]}")
|
||||||
|
threading.Thread(target=lambda: requests.post(HERMES, json=[{"id": int(time.time()*1000), "type": 1, "content": ct, "sender": fu, "roomid": "", "ts": time.time()}], timeout=30), daemon=True).start()
|
||||||
|
self.send_response(200); self.end_headers(); return
|
||||||
|
to = d.get("to", "") or d.get("wxid", "")
|
||||||
|
msg = d.get("message", "") or d.get("content", "")
|
||||||
|
if to and msg:
|
||||||
|
r = requests.post(f"{WX}/api/sendTextMsg", json={"wxid": to, "msg": msg}, timeout=5)
|
||||||
|
log(f"SEND {to}: {r.get('msg','')}")
|
||||||
|
self.send_response(200); self.end_headers(); return
|
||||||
|
except Exception as e:
|
||||||
|
log(f"ERR: {str(e)[:80]}")
|
||||||
|
self.send_response(200); self.end_headers()
|
||||||
|
def do_GET(self):
|
||||||
|
self.send_response(200); self.end_headers(); self.wfile.write(b'{"ok":true}')
|
||||||
|
def log_message(self, *a): pass
|
||||||
|
|
||||||
|
log(f"ready on :{PORT}")
|
||||||
|
HTTPServer(("0.0.0.0", PORT), H).serve_forever()
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
@echo off
|
||||||
|
title WeChat Hermes Bridge
|
||||||
|
cd /d "%~dp0.."
|
||||||
|
|
||||||
|
set PYTHON=C:\Users\hmo\AppData\Local\Programs\Python\Python310\python.exe
|
||||||
|
set AGENT=scripts\wechat_agent.py
|
||||||
|
|
||||||
|
echo ========================================
|
||||||
|
echo WeChat Hermes Bridge
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
echo [1/3] 启动微信 + 注入 DLL...
|
||||||
|
echo.
|
||||||
|
set PYTHONHOME=
|
||||||
|
set WXHOOK_LOG_LEVEL=ERROR
|
||||||
|
|
||||||
|
%PYTHON% %AGENT%
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ========================================
|
||||||
|
echo 微信已启动!
|
||||||
|
echo.
|
||||||
|
echo 第 2 步:运行修复过低工具 ^> 扫码登录
|
||||||
|
echo 路径:D:\Program Files (x86)\低版本修复工具\
|
||||||
|
echo 低版本修复工具\修复过低6.0\低版通用杀器.sp.exe
|
||||||
|
echo.
|
||||||
|
echo 第 3 步:手机扫码 → 自动开始转发
|
||||||
|
echo.
|
||||||
|
echo 按 Ctrl+C 停止桥接
|
||||||
|
echo ========================================
|
||||||
|
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
pause
|
||||||
|
)
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
"""
|
||||||
|
WeChat Agent - wxhook + Hermes API (:8642)
|
||||||
|
"""
|
||||||
|
import sys, os, json, time, threading, requests, re
|
||||||
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
|
|
||||||
|
sys.path.insert(0, r"C:\Users\hmo\AppData\Local\Programs\Python\Python310\Lib\site-packages")
|
||||||
|
os.environ["WXHOOK_LOG_LEVEL"] = "ERROR"
|
||||||
|
|
||||||
|
from wxhook import Bot
|
||||||
|
from wxhook.events import TEXT_MESSAGE, IMAGE_MESSAGE, VOICE_MESSAGE
|
||||||
|
import pymem, pymem.process
|
||||||
|
|
||||||
|
BOT_WXID = "wxid_7onnerpx2s2l22"
|
||||||
|
WX_API = ""
|
||||||
|
LOG_FILE = r"C:\Users\hmo\Desktop\wechat_agent.log"
|
||||||
|
DLL = r"C:\Users\hmo\AppData\Local\Programs\Python\Python310\Lib\site-packages\wxhook\tools\wxhook.dll"
|
||||||
|
last_msg_time = time.time()
|
||||||
|
nickname_cache = {}
|
||||||
|
|
||||||
|
HERMES_API = "http://192.168.0.103:8642/v1/chat/completions"
|
||||||
|
HERMES_KEY = "hermes123"
|
||||||
|
|
||||||
|
def log(m):
|
||||||
|
with open(LOG_FILE, "a", encoding="utf-8") as f:
|
||||||
|
f.write(f"{time.strftime('%H:%M:%S')} {m}\n")
|
||||||
|
|
||||||
|
def send_wx(wxid, msg):
|
||||||
|
try:
|
||||||
|
requests.post(WX_API + "/api/sendTextMsg", json={"wxid": wxid, "msg": msg}, timeout=5)
|
||||||
|
log(f"SEND {wxid}: ok")
|
||||||
|
except Exception as e:
|
||||||
|
log(f"SEND ERR: {e}")
|
||||||
|
|
||||||
|
def get_nickname(wxid):
|
||||||
|
if wxid in nickname_cache:
|
||||||
|
return nickname_cache[wxid]
|
||||||
|
try:
|
||||||
|
r = requests.post(WX_API + "/api/getContactList", json={}, timeout=5)
|
||||||
|
for c in r.json().get("data", []):
|
||||||
|
if c.get("wxid") == wxid:
|
||||||
|
nick = c.get("nickname") or c.get("customAccount") or wxid
|
||||||
|
nickname_cache[wxid] = nick
|
||||||
|
return nick
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
nickname_cache[wxid] = wxid
|
||||||
|
return wxid
|
||||||
|
|
||||||
|
def call_hermes(wxid, content):
|
||||||
|
nickname = get_nickname(wxid)
|
||||||
|
headers = {"Authorization": f"Bearer {HERMES_KEY}", "X-Hermes-Session-Id": "sisyphus", "Content-Type": "application/json"}
|
||||||
|
sys_prompt = f"你是莫荷,女生。你主人是老爸({nickname})。回复简短像聊天。发图用 [IMG]URL[/IMG]。"
|
||||||
|
body = {"model": "hermes-agent", "messages": [{"role": "system", "content": sys_prompt}, {"role": "user", "content": content}]}
|
||||||
|
try:
|
||||||
|
r = requests.post(HERMES_API, json=body, headers=headers, timeout=120, proxies={"http": None, "https": None})
|
||||||
|
if r.status_code == 200:
|
||||||
|
return r.json()["choices"][0]["message"]["content"]
|
||||||
|
except Exception as e:
|
||||||
|
log(f"API ERR: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def watchdog():
|
||||||
|
global last_msg_time
|
||||||
|
while True:
|
||||||
|
idle = time.time() - last_msg_time
|
||||||
|
if idle > 120 and WX_API:
|
||||||
|
try:
|
||||||
|
r = requests.post(WX_API + "/api/checkLogin", json={}, timeout=5)
|
||||||
|
if r.json().get("code") == 1:
|
||||||
|
requests.post(WX_API + "/api/hookSyncMsg", json={"ip": "127.0.0.1", "port": 19001, "enableHttp": 1, "url": "", "timeout": 300}, timeout=5)
|
||||||
|
log(f"WATCHDOG: refreshed ({int(idle)}s)")
|
||||||
|
else:
|
||||||
|
log("WATCHDOG: re-injecting...")
|
||||||
|
pymem.process.inject_dll(pymem.Pymem("WeChat.exe").process_handle, DLL.encode())
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
last_msg_time = time.time()
|
||||||
|
time.sleep(30)
|
||||||
|
|
||||||
|
threading.Thread(target=watchdog, daemon=True).start()
|
||||||
|
|
||||||
|
class RH(BaseHTTPRequestHandler):
|
||||||
|
def do_POST(self):
|
||||||
|
global last_msg_time
|
||||||
|
last_msg_time = time.time()
|
||||||
|
body = self.rfile.read(int(self.headers.get("Content-Length", 0)))
|
||||||
|
try:
|
||||||
|
d = json.loads(body)
|
||||||
|
if self.path == "/hermes-msg":
|
||||||
|
msg = d.get("message", "") or d.get("content", "") or str(d)[:200]
|
||||||
|
log("<<< HERMES: " + msg[:100])
|
||||||
|
with open(r"C:\Users\hmo\Desktop\hermes_inbox.txt", "a", encoding="utf-8") as f:
|
||||||
|
f.write(f"{time.strftime('%H:%M:%S')} {msg}\n")
|
||||||
|
self.send_response(200); self.end_headers(); return
|
||||||
|
to = d.get("to", "") or d.get("wxid", "")
|
||||||
|
msg = d.get("message", "") or d.get("content", "")
|
||||||
|
if to and msg:
|
||||||
|
log(f"REPLY {to}: {msg[:50]}")
|
||||||
|
send_wx(to, msg)
|
||||||
|
except Exception as e:
|
||||||
|
log(f"RH ERR: {e}")
|
||||||
|
self.send_response(200); self.end_headers()
|
||||||
|
def do_GET(self):
|
||||||
|
self.send_response(200); self.end_headers(); self.wfile.write(b'{"ok":true}')
|
||||||
|
def log_message(self, *a): pass
|
||||||
|
|
||||||
|
threading.Thread(target=lambda: HTTPServer(("0.0.0.0", 5801), RH).serve_forever(), daemon=True).start()
|
||||||
|
log("HTTP :5801")
|
||||||
|
|
||||||
|
log("Creating Bot...")
|
||||||
|
b = Bot()
|
||||||
|
WX_API = b.BASE_URL
|
||||||
|
log("Bot ready, API=" + WX_API)
|
||||||
|
|
||||||
|
@b.handle([TEXT_MESSAGE, IMAGE_MESSAGE, VOICE_MESSAGE])
|
||||||
|
def on_msg(_bot, event):
|
||||||
|
global last_msg_time
|
||||||
|
last_msg_time = time.time()
|
||||||
|
fu = event.fromUser or ""
|
||||||
|
if not fu or fu == BOT_WXID:
|
||||||
|
return
|
||||||
|
if event.type == VOICE_MESSAGE:
|
||||||
|
log(f"<- {fu}: [voice]")
|
||||||
|
reply = call_hermes(fu, "[voice message]")
|
||||||
|
if reply: send_wx(fu, reply)
|
||||||
|
return
|
||||||
|
if event.type == IMAGE_MESSAGE:
|
||||||
|
log(f"<- {fu}: [image]")
|
||||||
|
b64 = event.base64Img or ""
|
||||||
|
ocr_text = ""
|
||||||
|
if b64:
|
||||||
|
try:
|
||||||
|
import base64
|
||||||
|
os.makedirs(r"C:\Users\hmo\Desktop\wechat_images", exist_ok=True)
|
||||||
|
fname = os.path.join(r"C:\Users\hmo\Desktop\wechat_images", str(int(time.time())) + ".jpg")
|
||||||
|
with open(fname, "wb") as f:
|
||||||
|
f.write(base64.b64decode(b64))
|
||||||
|
api_key = os.environ.get("VOLCENGINE_API_KEY", "b0359bed-09f2-49e2-a53c-32ba057412e3")
|
||||||
|
with open(fname, "rb") as f:
|
||||||
|
img_b64 = base64.b64encode(f.read()).decode()
|
||||||
|
r = requests.post("https://ark.cn-beijing.volces.com/api/coding/v3/chat/completions",
|
||||||
|
json={"model": "doubao-seed-code", "messages": [{"role": "user", "content": [{"type": "text", "text": "描述图片"}, {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64," + img_b64}}]}], "max_tokens": 500},
|
||||||
|
headers={"Authorization": "Bearer " + api_key, "Content-Type": "application/json"}, timeout=60)
|
||||||
|
ocr_text = r.json()["choices"][0]["message"]["content"]
|
||||||
|
except Exception as e:
|
||||||
|
log(f"OCR err: {e}")
|
||||||
|
msg = "[image]" + ("\n" + ocr_text if ocr_text else "")
|
||||||
|
reply = call_hermes(fu, msg)
|
||||||
|
if reply: send_wx(fu, reply)
|
||||||
|
return
|
||||||
|
content = event.content or ""
|
||||||
|
if not content:
|
||||||
|
return
|
||||||
|
log(f"<- {fu}: {content[:50]}")
|
||||||
|
reply = call_hermes(fu, content)
|
||||||
|
if reply:
|
||||||
|
log(f"-> {fu}: {reply[:50]}")
|
||||||
|
img_match = re.search(r'\[IMG\](.*?)\[/IMG\]', reply)
|
||||||
|
if img_match:
|
||||||
|
img_cmd = img_match.group(1).strip()
|
||||||
|
clean = re.sub(r'\s*\[IMG\].*?\[/IMG\]\s*', '', reply).strip()
|
||||||
|
if clean:
|
||||||
|
send_wx(fu, clean)
|
||||||
|
try:
|
||||||
|
ir = requests.get(img_cmd, timeout=30, proxies={"http": None, "https": None})
|
||||||
|
if ir.status_code == 200:
|
||||||
|
ext = ".jpg"
|
||||||
|
if "png" in ir.headers.get("content-type", ""): ext = ".png"
|
||||||
|
tmp = os.path.join(r"C:\Users\hmo\Desktop", f"send_img_{int(time.time())}{ext}")
|
||||||
|
with open(tmp, "wb") as f:
|
||||||
|
f.write(ir.content)
|
||||||
|
try:
|
||||||
|
_bot.send_image(fu, tmp)
|
||||||
|
except:
|
||||||
|
requests.post(WX_API + "/api/sendImagesMsg", json={"wxid": fu, "imagePath": tmp}, timeout=10)
|
||||||
|
os.remove(tmp)
|
||||||
|
except Exception as e:
|
||||||
|
log(f"IMG err: {e}")
|
||||||
|
else:
|
||||||
|
send_wx(fu, reply)
|
||||||
|
else:
|
||||||
|
log(f"-> {fu}: no reply")
|
||||||
|
|
||||||
|
print("[Agent] Hermes API: " + HERMES_API)
|
||||||
|
log("Ready")
|
||||||
|
b.run()
|
||||||
Reference in New Issue
Block a user