加入了openclaw插件,用于与openclaw对接
This commit is contained in:
126
plugins/openclaw/webhook.MD
Normal file
126
plugins/openclaw/webhook.MD
Normal file
@@ -0,0 +1,126 @@
|
||||
你要的 IM实现逻辑清单(按这个做就能稳定接):
|
||||
|
||||
入站推送到 OpenClaw
|
||||
调用:POST /wechat_abot/webhook
|
||||
Body:直接发你现在 message.py 的原始 JSON
|
||||
Header:X-Wechat-Abot-Signature: sha256=<hmac_sha256(raw_body, webhookSecret)>
|
||||
超时建议:3~5s;失败重试(指数退避)
|
||||
签名实现(必须)
|
||||
算法:HMAC-SHA256
|
||||
输入:HTTP原始body字节(不要重排JSON)
|
||||
secret:和 channels.wechat_abot.webhookSecret 一致
|
||||
防重放:建议带时间戳并限制窗口(可选增强)
|
||||
出站发送接口(你IM提供)
|
||||
POST <apiBase>/messages/send
|
||||
Header:Authorization: Bearer <botToken>
|
||||
入参:{ to, text, replyToMessageId? }
|
||||
返回:{ messageId }
|
||||
错误码要清晰(401/403/429/5xx)
|
||||
幂等与去重
|
||||
入站按 MsgId/NewMsgId 去重(你侧做一次,插件侧也做一次更稳)
|
||||
出站按你生成的 requestId/messageId 防重复发送
|
||||
群聊处理
|
||||
保持原始 Content.string(sender:\n正文)格式即可
|
||||
插件会自动提取真实发送者并清理前缀
|
||||
群里是否触发可在你侧先做过滤(如必须@机器人)
|
||||
可靠性
|
||||
出站接口支持重试(网络错误/502/503)
|
||||
429 限流要返回 retry_after
|
||||
全链路打日志:eventId、sender、conversationId、status、耗时
|
||||
安全
|
||||
webhook 只允许你的 IM 源IP(可选)
|
||||
token/secret 不写死代码,走环境变量
|
||||
关闭调试日志里的敏感字段(token、签名原文)
|
||||
|
||||
|
||||
|
||||
|
||||
可以,给你一份 Python 直连接口参考逻辑(入站推 OpenClaw + 出站发送接口),你按自己项目替换发送实现就行。
|
||||
|
||||
import os
|
||||
import json
|
||||
import hmac
|
||||
import hashlib
|
||||
import requests
|
||||
from flask import Flask, request, jsonify
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# ===== 配置 =====
|
||||
OPENCLAW_WEBHOOK_URL = os.getenv("OPENCLAW_WEBHOOK_URL", "http://127.0.0.1:18789/wechat_abot/webhook")
|
||||
OPENCLAW_WEBHOOK_SECRET = os.getenv("OPENCLAW_WEBHOOK_SECRET", "change_me")
|
||||
|
||||
IM_BOT_TOKEN = os.getenv("IM_BOT_TOKEN", "change_me") # 给 OpenClaw 插件鉴权用
|
||||
# 你自己的 IM SDK/client 在这里初始化
|
||||
# im_client = YourIMClient(...)
|
||||
|
||||
def sign_sha256(raw_body: bytes, secret: str) -> str:
|
||||
return hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
|
||||
|
||||
def verify_bearer(auth_header: str, expected_token: str) -> bool:
|
||||
if not auth_header or not auth_header.startswith("Bearer "):
|
||||
return False
|
||||
token = auth_header[7:].strip()
|
||||
return hmac.compare_digest(token, expected_token)
|
||||
|
||||
# 1) 你 IM 收到消息后的入口(示例)
|
||||
@app.post("/im/inbound")
|
||||
def im_inbound():
|
||||
# 这里 body 就是你 message.py 能解析的原始 JSON
|
||||
raw = request.get_data() # 原始字节,签名必须用它
|
||||
if not raw:
|
||||
return jsonify({"error": "empty body"}), 400
|
||||
|
||||
# 直接转发给 OpenClaw 插件 webhook
|
||||
sig = sign_sha256(raw, OPENCLAW_WEBHOOK_SECRET)
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Wechat-Abot-Signature": f"sha256={sig}",
|
||||
}
|
||||
|
||||
try:
|
||||
resp = requests.post(
|
||||
OPENCLAW_WEBHOOK_URL,
|
||||
data=raw,
|
||||
headers=headers,
|
||||
timeout=5
|
||||
)
|
||||
# 建议你自己加重试策略(429/5xx)
|
||||
return jsonify({
|
||||
"ok": resp.ok,
|
||||
"status": resp.status_code,
|
||||
"body": resp.json() if "application/json" in resp.headers.get("Content-Type", "") else resp.text
|
||||
}), 200
|
||||
except requests.RequestException as e:
|
||||
return jsonify({"ok": False, "error": str(e)}), 502
|
||||
|
||||
# 2) 给 OpenClaw 插件调用的发送接口
|
||||
@app.post("/api/messages/send")
|
||||
def api_messages_send():
|
||||
# 鉴权:对应 channels.wechat_abot.botToken
|
||||
auth = request.headers.get("Authorization", "")
|
||||
if not verify_bearer(auth, IM_BOT_TOKEN):
|
||||
return jsonify({"error": "unauthorized"}), 401
|
||||
|
||||
data = request.get_json(silent=True) or {}
|
||||
to = data.get("to")
|
||||
text = data.get("text", "")
|
||||
reply_to = data.get("replyToMessageId")
|
||||
|
||||
if not to or not text:
|
||||
return jsonify({"error": "missing to/text"}), 400
|
||||
|
||||
# TODO: 这里换成你真实 IM 发送逻辑
|
||||
# result = im_client.send_text(to=to, text=text, reply_to=reply_to)
|
||||
# message_id = result["message_id"]
|
||||
|
||||
message_id = f"out_{hashlib.md5((to + text).encode()).hexdigest()[:12]}" # demo
|
||||
return jsonify({"messageId": message_id}), 200
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8088, debug=False)
|
||||
你只需要改两处:
|
||||
|
||||
/im/inbound:挂到你现有消息接收流程里
|
||||
/api/messages/send:替换成你真实 IM 发消息函数
|
||||
Reference in New Issue
Block a user