docs: weixin ClawBot bridge protocol and examples

This commit is contained in:
AskaEth committed 2026-08-21 18:15:20 +08:00
commit 4ea3957da9
13 files changed
+2158

No files matched your search

+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
# ilink_bridge.py — 微信 ClawBot iLink 最小示例(Python)
# 流程: 扫码登录 → 长轮询收消息 → 回声回复
# 依赖: pip install requests
import json, os, time, uuid, base64, random, requests
BASE_URL = "https://ilinkai.weixin.qq.com"
STATE_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bridge-state.json")
# ---------- 状态持久化: bot_token / 游标 / context_token ----------
state = {"bot_token": None, "cursor": "", "contexts": {}}
if os.path.exists(STATE_FILE):
state.update(json.load(open(STATE_FILE)))
def save_state():
with open(STATE_FILE, "w") as f:
json.dump(state, f, indent=2)
os.chmod(STATE_FILE, 0o600)
# ---------- 通用请求头: 鉴权 + 防重放 X-WECHAT-UIN ----------
def headers():
h = {
"Content-Type": "application/json",
"AuthorizationType": "ilink_bot_token",
"X-WECHAT-UIN": base64.b64encode(str(random.getrandbits(32)).encode()).decode(),
}
if state["bot_token"]:
h["Authorization"] = f"Bearer {state['bot_token']}"
return h
def api_post(path, body, timeout=45):
return requests.post(BASE_URL + path, headers=headers(), json=body, timeout=timeout).json()
# ---------- 接口1: GET /ilink/bot/get_bot_qrcode 获取登录二维码 ----------
# ---------- 接口2: GET /ilink/bot/get_qrcode_status 轮询扫码状态 ----------
def login():
qr = requests.get(f"{BASE_URL}/ilink/bot/get_bot_qrcode?bot_type=3").json()
print("请用微信扫码:", qr["qrcode_img_content"])
deadline = time.time() + 8 * 60
while time.time() < deadline:
s = requests.get(f"{BASE_URL}/ilink/bot/get_qrcode_status",
params={"qrcode": qr["qrcode"]},
headers={"iLink-App-ClientVersion": "1"}).json()
if s.get("status") == "confirmed": # 状态流转: wait → scaned → confirmed
state.update(bot_token=s["bot_token"], bot_id=s["ilink_bot_id"],
owner_id=s["ilink_user_id"])
save_state()
print("登录成功, bot:", s["ilink_bot_id"])
return
time.sleep(1)
raise RuntimeError("扫码超时")
# ---------- 你的 AI 后端: 替换成自己项目的调用 ----------
def ask_your_ai(peer_id: str, text: str) -> str:
# 例: requests.post("http://127.0.0.1:8000/chat",
# json={"session_id": peer_id, "text": text}).json()["reply"]
return f"你说的是:{text}"
# ---------- 接口4: POST /ilink/bot/sendmessage 发送消息 ----------
def send_text(to_user_id, text, context_token):
for i in range(0, max(len(text), 1), 1500): # 长文本分片
api_post("/ilink/bot/sendmessage", {
"msg": {
"to_user_id": to_user_id,
"client_id": str(uuid.uuid4()), # 去重 ID
"message_type": 2, # 2 = bot 消息
"message_state": 2, # 2 = FINISH
"context_token": context_token, # 必填! 来自入站消息
"item_list": [{"type": 1, "text_item": {"text": text[i:i+1500]}}],
}
})
# ---------- 接口3: POST /ilink/bot/getupdates 长轮询收消息 ----------
def poll_loop():
failures = 0
while True:
try:
resp = api_post("/ilink/bot/getupdates", {
"get_updates_buf": state["cursor"], # 同步游标
"base_info": {"channel_version": "1.0.2"},
})
if resp.get("errcode") == -14: # 会话过期
print("会话过期(errcode=-14),请删除 bridge-state.json 重新扫码")
raise SystemExit(2)
if resp.get("ret", 0) != 0:
raise RuntimeError(f"getupdates 错误: {resp}")
if resp.get("get_updates_buf"): # 游标立即落盘
state["cursor"] = resp["get_updates_buf"]
save_state()
for msg in resp.get("msgs") or []:
if msg.get("message_type") != 1: # 只处理用户消息
continue
text = "\n".join(
it.get("text_item", {}).get("text", "")
for it in msg.get("item_list", []) if it.get("type") == 1
).strip()
if not text:
continue
state["contexts"][msg["from_user_id"]] = msg["context_token"]
save_state()
try:
send_text(msg["from_user_id"],
ask_your_ai(msg["from_user_id"], text),
msg["context_token"])
except Exception as e:
print("处理消息失败:", e)
failures = 0
except requests.exceptions.Timeout:
continue # 长轮询正常超时
except Exception as e:
failures += 1
wait = 2 if failures <= 3 else 30 # 退避: 2s ×3 → 30s
print(f"轮询异常({failures}次),{wait}s 后重试:", e)
time.sleep(wait)
if __name__ == "__main__":
if not state["bot_token"]:
login()
poll_loop()