@gloablehive/ipad-wechat-plugin 1.0.23 → 1.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +228 -14
- package/dist/src/channel.js +10 -0
- package/index.ts +235 -18
- package/package.json +2 -2
- package/src/channel.ts +12 -0
package/dist/index.js
CHANGED
|
@@ -7,38 +7,252 @@
|
|
|
7
7
|
* Multi-account support:
|
|
8
8
|
* - cfg is passed to handleInboundMessage for cache isolation
|
|
9
9
|
* - Each account's guid can have a dedicated webhook path
|
|
10
|
+
*
|
|
11
|
+
* Webhook strategy:
|
|
12
|
+
* - registerHttpRoute kept for forward-compatibility
|
|
13
|
+
* - Standalone HTTP server on port 18790 as primary receiver (bypasses OpenClaw routing bug)
|
|
14
|
+
* - JuHeBot notify_type → WebhookPayload transformer included
|
|
10
15
|
*/
|
|
11
|
-
import {
|
|
16
|
+
import { createServer } from "http";
|
|
17
|
+
import { readFileSync } from "fs";
|
|
18
|
+
import { join } from "path";
|
|
19
|
+
import { homedir } from "os";
|
|
20
|
+
import { randomUUID } from "crypto";
|
|
21
|
+
import WebSocket from "ws";
|
|
22
|
+
import { defineChannelPluginEntry, buildChannelOutboundSessionRoute } from "openclaw/plugin-sdk/core";
|
|
12
23
|
import { ipadWeChatPlugin, handleInboundMessage, getCacheManagerReady } from "./src/channel.js";
|
|
24
|
+
/** Read OpenClaw main config from disk so standalone server has full cfg */
|
|
25
|
+
let _cfgCache = null;
|
|
26
|
+
function loadOpenClawConfig() {
|
|
27
|
+
if (_cfgCache)
|
|
28
|
+
return _cfgCache;
|
|
29
|
+
try {
|
|
30
|
+
const p = join(homedir(), ".openclaw", "openclaw.json");
|
|
31
|
+
_cfgCache = JSON.parse(readFileSync(p, "utf-8"));
|
|
32
|
+
return _cfgCache;
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
console.error("[iPad WeChat] Failed to load openclaw.json:", err);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// ── Gateway WebSocket dispatch ──
|
|
40
|
+
const GATEWAY_PORT = parseInt(process.env.OPENCLAW_GATEWAY_PORT || "18789", 10);
|
|
41
|
+
let _gwReqId = 0;
|
|
42
|
+
function dispatchViaGateway(params) {
|
|
43
|
+
const cfg = loadOpenClawConfig();
|
|
44
|
+
const token = cfg?.gateway?.controlUi?.auth?.token || cfg?.gateway?.auth?.token || "";
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
const url = `ws://127.0.0.1:${GATEWAY_PORT}`;
|
|
47
|
+
const ws = new WebSocket(url, { headers: { Origin: `http://127.0.0.1:${GATEWAY_PORT}` } });
|
|
48
|
+
const timeout = setTimeout(() => { ws.close(); reject(new Error("Gateway dispatch timeout")); }, 30000);
|
|
49
|
+
let connected = false;
|
|
50
|
+
let sendId = null;
|
|
51
|
+
ws.on("open", () => {
|
|
52
|
+
const connectId = `ipad-wechat-${++_gwReqId}`;
|
|
53
|
+
ws.send(JSON.stringify({
|
|
54
|
+
type: "req", id: connectId, method: "connect",
|
|
55
|
+
params: {
|
|
56
|
+
minProtocol: 3, maxProtocol: 3,
|
|
57
|
+
client: { id: "openclaw-control-ui", version: "1.0.0", mode: "webchat", platform: "node" },
|
|
58
|
+
scopes: ["operator.admin", "operator.read", "operator.write"],
|
|
59
|
+
...(token ? { auth: { token } } : {}),
|
|
60
|
+
},
|
|
61
|
+
}));
|
|
62
|
+
});
|
|
63
|
+
ws.on("message", (data) => {
|
|
64
|
+
try {
|
|
65
|
+
const frame = JSON.parse(data.toString());
|
|
66
|
+
if (frame.type === "res" && !connected) {
|
|
67
|
+
connected = true;
|
|
68
|
+
if (!frame.ok) {
|
|
69
|
+
clearTimeout(timeout);
|
|
70
|
+
ws.close();
|
|
71
|
+
reject(new Error("Gateway connect failed: " + (frame.error?.message || "unknown")));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
sendId = `ipad-wechat-${++_gwReqId}`;
|
|
75
|
+
ws.send(JSON.stringify({
|
|
76
|
+
type: "req", id: sendId, method: "chat.send",
|
|
77
|
+
params: {
|
|
78
|
+
idempotencyKey: randomUUID(),
|
|
79
|
+
sessionKey: params.sessionKey,
|
|
80
|
+
message: params.message,
|
|
81
|
+
deliver: true,
|
|
82
|
+
originatingChannel: "ipad-wechat",
|
|
83
|
+
originatingTo: params.from,
|
|
84
|
+
originatingAccountId: params.accountId || "default",
|
|
85
|
+
},
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
else if (frame.type === "res" && frame.id === sendId) {
|
|
89
|
+
clearTimeout(timeout);
|
|
90
|
+
ws.close();
|
|
91
|
+
if (frame.ok) {
|
|
92
|
+
resolve();
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
reject(new Error("chat.send failed: " + (frame.error?.message || "unknown")));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch { /* ignore parse errors */ }
|
|
100
|
+
});
|
|
101
|
+
ws.on("error", (err) => { clearTimeout(timeout); reject(err); });
|
|
102
|
+
ws.on("close", () => { clearTimeout(timeout); if (!connected)
|
|
103
|
+
reject(new Error("Gateway closed before connect")); });
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
// JuHeBot notify_type constants (from doc-6966894.md)
|
|
107
|
+
const NOTIFY_NEW_MSG = 1010;
|
|
108
|
+
const NOTIFY_BATCH_NEW_MSG = 1011;
|
|
109
|
+
/**
|
|
110
|
+
* Transform JuHeBot callback payload → WebhookPayload
|
|
111
|
+
* Handles both JuHeBot native format and our own test format.
|
|
112
|
+
*/
|
|
113
|
+
function transformPayload(raw) {
|
|
114
|
+
// Already in our expected format
|
|
115
|
+
if (raw.event && raw.message) {
|
|
116
|
+
return raw;
|
|
117
|
+
}
|
|
118
|
+
const notifyType = raw.notify_type ?? raw.notifyType ?? raw.type;
|
|
119
|
+
if (notifyType === NOTIFY_NEW_MSG || notifyType === NOTIFY_BATCH_NEW_MSG) {
|
|
120
|
+
const data = raw.data || raw;
|
|
121
|
+
const msgs = notifyType === NOTIFY_BATCH_NEW_MSG
|
|
122
|
+
? (data.msgs || data.messages || [data])
|
|
123
|
+
: [data];
|
|
124
|
+
if (!msgs || msgs.length === 0)
|
|
125
|
+
return null;
|
|
126
|
+
const m = msgs[0];
|
|
127
|
+
return {
|
|
128
|
+
event: "message",
|
|
129
|
+
message: {
|
|
130
|
+
messageId: String(m.msg_id ?? m.msgId ?? m.new_msg_id ?? m.newMsgId ?? `msg_${Date.now()}`),
|
|
131
|
+
fromUser: m.from_username ?? m.fromUsername ?? m.from_user ?? m.fromUser ?? "",
|
|
132
|
+
toUser: m.to_username ?? m.toUsername ?? m.to_user ?? m.toUser ?? "",
|
|
133
|
+
content: m.content ?? m.msg_content ?? m.msgContent ?? "",
|
|
134
|
+
type: m.msg_type ?? m.msgType ?? m.type ?? 1,
|
|
135
|
+
timestamp: m.timestamp ?? m.create_time ?? m.createTime ?? Date.now(),
|
|
136
|
+
isSelf: m.is_self === 1 || m.is_self === true || m.isSelf === true,
|
|
137
|
+
roomId: m.room_username ?? m.roomUsername ?? m.roomId ?? undefined,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
// Non-message events – log for future handling
|
|
142
|
+
console.log(`[iPad WeChat] notify_type=${notifyType} (non-message)`, JSON.stringify(raw).slice(0, 300));
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const WEBHOOK_PORT = parseInt(process.env.IPAD_WECHAT_WEBHOOK_PORT || "18790", 10);
|
|
146
|
+
let _webhookServerStarted = false;
|
|
13
147
|
export default defineChannelPluginEntry({
|
|
14
148
|
id: "ipad-wechat",
|
|
15
149
|
name: "iPad WeChat",
|
|
16
150
|
description: "Connect OpenClaw to iPad WeChat protocol for sending and receiving WeChat messages",
|
|
17
151
|
plugin: ipadWeChatPlugin,
|
|
18
152
|
registerFull(api) {
|
|
19
|
-
console.log("[iPad WeChat] registerFull called
|
|
20
|
-
//
|
|
153
|
+
console.log("[iPad WeChat] registerFull called");
|
|
154
|
+
// ── 1. Standard OpenClaw route (kept for forward-compat) ──
|
|
21
155
|
api.registerHttpRoute({
|
|
22
|
-
path: "/
|
|
23
|
-
auth: "plugin",
|
|
24
|
-
match: "exact",
|
|
156
|
+
path: "/ipad-wechat/webhook",
|
|
157
|
+
auth: "plugin",
|
|
25
158
|
handler: async (req, res) => {
|
|
26
|
-
console.log("[iPad WeChat]
|
|
159
|
+
console.log("[iPad WeChat] OpenClaw route hit:", req.url);
|
|
27
160
|
const cfg = req.cfg;
|
|
28
161
|
const ready = getCacheManagerReady();
|
|
29
|
-
if (ready)
|
|
162
|
+
if (ready)
|
|
30
163
|
await ready;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
console.log("[iPad WeChat] Received webhook:", payload);
|
|
34
|
-
if (payload) {
|
|
164
|
+
const payload = transformPayload(req.body || {});
|
|
165
|
+
if (payload)
|
|
35
166
|
await handleInboundMessage(api, payload, cfg);
|
|
36
|
-
}
|
|
37
167
|
res.statusCode = 200;
|
|
38
168
|
res.end("ok");
|
|
39
169
|
return true;
|
|
40
170
|
},
|
|
41
171
|
});
|
|
42
|
-
|
|
172
|
+
// ── 2. Standalone HTTP server (primary – bypasses OpenClaw routing bug) ──
|
|
173
|
+
if (_webhookServerStarted) {
|
|
174
|
+
console.log("[iPad WeChat] Webhook server already running, skipping duplicate start");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
_webhookServerStarted = true;
|
|
178
|
+
const server = createServer((req, res) => {
|
|
179
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
180
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
|
|
181
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
182
|
+
if (req.method === "OPTIONS") {
|
|
183
|
+
res.writeHead(204);
|
|
184
|
+
res.end();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (req.method === "GET") {
|
|
188
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
189
|
+
res.end(JSON.stringify({ status: "ok", service: "ipad-wechat-webhook", port: WEBHOOK_PORT }));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (req.method !== "POST") {
|
|
193
|
+
res.writeHead(405);
|
|
194
|
+
res.end("Method not allowed");
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
let body = "";
|
|
198
|
+
req.on("data", (chunk) => { body += chunk; });
|
|
199
|
+
req.on("end", async () => {
|
|
200
|
+
try {
|
|
201
|
+
const raw = JSON.parse(body);
|
|
202
|
+
console.log("[iPad WeChat] Webhook received:", JSON.stringify(raw).slice(0, 500));
|
|
203
|
+
const payload = transformPayload(raw);
|
|
204
|
+
if (payload && payload.message) {
|
|
205
|
+
const cfg = loadOpenClawConfig();
|
|
206
|
+
const msg = payload.message;
|
|
207
|
+
const isChatroom = !!msg.roomId;
|
|
208
|
+
const peerId = isChatroom ? msg.roomId : (msg.fromUser || msg.toUser || "");
|
|
209
|
+
const peerKind = isChatroom ? "group" : "direct";
|
|
210
|
+
let sessionKey = `agent:main:ipad-wechat:${peerKind}:${peerId}`;
|
|
211
|
+
try {
|
|
212
|
+
const route = buildChannelOutboundSessionRoute({
|
|
213
|
+
cfg, agentId: "main", channel: "ipad-wechat",
|
|
214
|
+
peer: { kind: peerKind, id: peerId },
|
|
215
|
+
chatType: peerKind,
|
|
216
|
+
from: msg.toUser || "", to: msg.fromUser || "",
|
|
217
|
+
});
|
|
218
|
+
sessionKey = route.sessionKey;
|
|
219
|
+
}
|
|
220
|
+
catch { /* fallback to manual key */ }
|
|
221
|
+
try {
|
|
222
|
+
await dispatchViaGateway({
|
|
223
|
+
sessionKey,
|
|
224
|
+
message: msg.content || "",
|
|
225
|
+
from: msg.fromUser || "",
|
|
226
|
+
to: msg.toUser || "",
|
|
227
|
+
});
|
|
228
|
+
console.log(`[iPad WeChat] Message dispatched → session=${sessionKey}`);
|
|
229
|
+
}
|
|
230
|
+
catch (dispatchErr) {
|
|
231
|
+
console.error("[iPad WeChat] Gateway dispatch error:", dispatchErr);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
235
|
+
res.end("ok");
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
console.error("[iPad WeChat] Webhook error:", err);
|
|
239
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
240
|
+
res.end("error");
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
server.on("error", (err) => {
|
|
245
|
+
if (err.code === "EADDRINUSE") {
|
|
246
|
+
console.error(`[iPad WeChat] Port ${WEBHOOK_PORT} in use. Set IPAD_WECHAT_WEBHOOK_PORT to change.`);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
console.error("[iPad WeChat] Server error:", err);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
server.listen(WEBHOOK_PORT, "0.0.0.0", () => {
|
|
253
|
+
console.log(`[iPad WeChat] ✅ Webhook server listening on 0.0.0.0:${WEBHOOK_PORT}`);
|
|
254
|
+
console.log(`[iPad WeChat] Set JuHeBot notifyUrl → http://<public-ip>:${WEBHOOK_PORT}/`);
|
|
255
|
+
});
|
|
256
|
+
console.log("[iPad WeChat] registerFull complete");
|
|
43
257
|
},
|
|
44
258
|
});
|
package/dist/src/channel.js
CHANGED
|
@@ -79,9 +79,19 @@ function getCacheManager(cfg) {
|
|
|
79
79
|
// Initialize registry with accounts
|
|
80
80
|
initializeRegistry(cfg);
|
|
81
81
|
const basePath = section?.cachePath || "./cache/wechat-ipad";
|
|
82
|
+
// Build knowledge config if provided in section
|
|
83
|
+
let knowledgeConfig;
|
|
84
|
+
if (section?.knowledge) {
|
|
85
|
+
knowledgeConfig = {
|
|
86
|
+
...section.knowledge,
|
|
87
|
+
nodeId: section.knowledge.nodeId || 'ipad-wechat-node',
|
|
88
|
+
agentId: section.knowledge.agentId || section.agentId || 'default-agent',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
82
91
|
cacheManager = createCacheManager({
|
|
83
92
|
basePath,
|
|
84
93
|
accounts,
|
|
94
|
+
knowledgeConfig,
|
|
85
95
|
});
|
|
86
96
|
// Initialize cache manager - await for ready state
|
|
87
97
|
cacheManagerReady = cacheManager.init().catch(err => {
|
package/index.ts
CHANGED
|
@@ -7,10 +7,151 @@
|
|
|
7
7
|
* Multi-account support:
|
|
8
8
|
* - cfg is passed to handleInboundMessage for cache isolation
|
|
9
9
|
* - Each account's guid can have a dedicated webhook path
|
|
10
|
+
*
|
|
11
|
+
* Webhook strategy:
|
|
12
|
+
* - registerHttpRoute kept for forward-compatibility
|
|
13
|
+
* - Standalone HTTP server on port 18790 as primary receiver (bypasses OpenClaw routing bug)
|
|
14
|
+
* - JuHeBot notify_type → WebhookPayload transformer included
|
|
10
15
|
*/
|
|
11
16
|
|
|
12
|
-
import {
|
|
17
|
+
import { createServer } from "http";
|
|
18
|
+
import { readFileSync } from "fs";
|
|
19
|
+
import { join } from "path";
|
|
20
|
+
import { homedir } from "os";
|
|
21
|
+
import { randomUUID } from "crypto";
|
|
22
|
+
import WebSocket from "ws";
|
|
23
|
+
import { defineChannelPluginEntry, buildChannelOutboundSessionRoute } from "openclaw/plugin-sdk/core";
|
|
13
24
|
import { ipadWeChatPlugin, handleInboundMessage, getCacheManagerReady } from "./src/channel.js";
|
|
25
|
+
import type { WebhookPayload } from "./src/client.js";
|
|
26
|
+
|
|
27
|
+
/** Read OpenClaw main config from disk so standalone server has full cfg */
|
|
28
|
+
let _cfgCache: any = null;
|
|
29
|
+
function loadOpenClawConfig(): any {
|
|
30
|
+
if (_cfgCache) return _cfgCache;
|
|
31
|
+
try {
|
|
32
|
+
const p = join(homedir(), ".openclaw", "openclaw.json");
|
|
33
|
+
_cfgCache = JSON.parse(readFileSync(p, "utf-8"));
|
|
34
|
+
return _cfgCache;
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error("[iPad WeChat] Failed to load openclaw.json:", err);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Gateway WebSocket dispatch ──
|
|
42
|
+
const GATEWAY_PORT = parseInt(process.env.OPENCLAW_GATEWAY_PORT || "18789", 10);
|
|
43
|
+
let _gwReqId = 0;
|
|
44
|
+
|
|
45
|
+
function dispatchViaGateway(params: {
|
|
46
|
+
sessionKey: string;
|
|
47
|
+
message: string;
|
|
48
|
+
from: string;
|
|
49
|
+
to: string;
|
|
50
|
+
accountId?: string;
|
|
51
|
+
}): Promise<void> {
|
|
52
|
+
const cfg = loadOpenClawConfig();
|
|
53
|
+
const token = cfg?.gateway?.controlUi?.auth?.token || cfg?.gateway?.auth?.token || "";
|
|
54
|
+
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
const url = `ws://127.0.0.1:${GATEWAY_PORT}`;
|
|
57
|
+
const ws = new WebSocket(url, { headers: { Origin: `http://127.0.0.1:${GATEWAY_PORT}` } });
|
|
58
|
+
const timeout = setTimeout(() => { ws.close(); reject(new Error("Gateway dispatch timeout")); }, 30000);
|
|
59
|
+
|
|
60
|
+
let connected = false;
|
|
61
|
+
let sendId: string | null = null;
|
|
62
|
+
|
|
63
|
+
ws.on("open", () => {
|
|
64
|
+
const connectId = `ipad-wechat-${++_gwReqId}`;
|
|
65
|
+
ws.send(JSON.stringify({
|
|
66
|
+
type: "req", id: connectId, method: "connect",
|
|
67
|
+
params: {
|
|
68
|
+
minProtocol: 3, maxProtocol: 3,
|
|
69
|
+
client: { id: "openclaw-control-ui", version: "1.0.0", mode: "webchat", platform: "node" },
|
|
70
|
+
scopes: ["operator.admin", "operator.read", "operator.write"],
|
|
71
|
+
...(token ? { auth: { token } } : {}),
|
|
72
|
+
},
|
|
73
|
+
}));
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
ws.on("message", (data: Buffer) => {
|
|
77
|
+
try {
|
|
78
|
+
const frame = JSON.parse(data.toString());
|
|
79
|
+
if (frame.type === "res" && !connected) {
|
|
80
|
+
connected = true;
|
|
81
|
+
if (!frame.ok) { clearTimeout(timeout); ws.close(); reject(new Error("Gateway connect failed: " + (frame.error?.message || "unknown"))); return; }
|
|
82
|
+
sendId = `ipad-wechat-${++_gwReqId}`;
|
|
83
|
+
ws.send(JSON.stringify({
|
|
84
|
+
type: "req", id: sendId, method: "chat.send",
|
|
85
|
+
params: {
|
|
86
|
+
idempotencyKey: randomUUID(),
|
|
87
|
+
sessionKey: params.sessionKey,
|
|
88
|
+
message: params.message,
|
|
89
|
+
deliver: true,
|
|
90
|
+
originatingChannel: "ipad-wechat",
|
|
91
|
+
originatingTo: params.from,
|
|
92
|
+
originatingAccountId: params.accountId || "default",
|
|
93
|
+
},
|
|
94
|
+
}));
|
|
95
|
+
} else if (frame.type === "res" && frame.id === sendId) {
|
|
96
|
+
clearTimeout(timeout);
|
|
97
|
+
ws.close();
|
|
98
|
+
if (frame.ok) { resolve(); } else { reject(new Error("chat.send failed: " + (frame.error?.message || "unknown"))); }
|
|
99
|
+
}
|
|
100
|
+
} catch { /* ignore parse errors */ }
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
ws.on("error", (err) => { clearTimeout(timeout); reject(err); });
|
|
104
|
+
ws.on("close", () => { clearTimeout(timeout); if (!connected) reject(new Error("Gateway closed before connect")); });
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// JuHeBot notify_type constants (from doc-6966894.md)
|
|
109
|
+
const NOTIFY_NEW_MSG = 1010;
|
|
110
|
+
const NOTIFY_BATCH_NEW_MSG = 1011;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Transform JuHeBot callback payload → WebhookPayload
|
|
114
|
+
* Handles both JuHeBot native format and our own test format.
|
|
115
|
+
*/
|
|
116
|
+
function transformPayload(raw: any): WebhookPayload | null {
|
|
117
|
+
// Already in our expected format
|
|
118
|
+
if (raw.event && raw.message) {
|
|
119
|
+
return raw as WebhookPayload;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const notifyType = raw.notify_type ?? raw.notifyType ?? raw.type;
|
|
123
|
+
|
|
124
|
+
if (notifyType === NOTIFY_NEW_MSG || notifyType === NOTIFY_BATCH_NEW_MSG) {
|
|
125
|
+
const data = raw.data || raw;
|
|
126
|
+
const msgs = notifyType === NOTIFY_BATCH_NEW_MSG
|
|
127
|
+
? (data.msgs || data.messages || [data])
|
|
128
|
+
: [data];
|
|
129
|
+
|
|
130
|
+
if (!msgs || msgs.length === 0) return null;
|
|
131
|
+
const m = msgs[0];
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
event: "message",
|
|
135
|
+
message: {
|
|
136
|
+
messageId: String(m.msg_id ?? m.msgId ?? m.new_msg_id ?? m.newMsgId ?? `msg_${Date.now()}`),
|
|
137
|
+
fromUser: m.from_username ?? m.fromUsername ?? m.from_user ?? m.fromUser ?? "",
|
|
138
|
+
toUser: m.to_username ?? m.toUsername ?? m.to_user ?? m.toUser ?? "",
|
|
139
|
+
content: m.content ?? m.msg_content ?? m.msgContent ?? "",
|
|
140
|
+
type: m.msg_type ?? m.msgType ?? m.type ?? 1,
|
|
141
|
+
timestamp: m.timestamp ?? m.create_time ?? m.createTime ?? Date.now(),
|
|
142
|
+
isSelf: m.is_self === 1 || m.is_self === true || m.isSelf === true,
|
|
143
|
+
roomId: m.room_username ?? m.roomUsername ?? m.roomId ?? undefined,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Non-message events – log for future handling
|
|
149
|
+
console.log(`[iPad WeChat] notify_type=${notifyType} (non-message)`, JSON.stringify(raw).slice(0, 300));
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const WEBHOOK_PORT = parseInt(process.env.IPAD_WECHAT_WEBHOOK_PORT || "18790", 10);
|
|
154
|
+
let _webhookServerStarted = false;
|
|
14
155
|
|
|
15
156
|
export default defineChannelPluginEntry({
|
|
16
157
|
id: "ipad-wechat",
|
|
@@ -19,28 +160,20 @@ export default defineChannelPluginEntry({
|
|
|
19
160
|
plugin: ipadWeChatPlugin,
|
|
20
161
|
|
|
21
162
|
registerFull(api) {
|
|
22
|
-
console.log("[iPad WeChat] registerFull called
|
|
163
|
+
console.log("[iPad WeChat] registerFull called");
|
|
23
164
|
|
|
24
|
-
//
|
|
165
|
+
// ── 1. Standard OpenClaw route (kept for forward-compat) ──
|
|
25
166
|
api.registerHttpRoute({
|
|
26
|
-
path: "/
|
|
27
|
-
auth: "plugin",
|
|
28
|
-
match: "exact",
|
|
167
|
+
path: "/ipad-wechat/webhook",
|
|
168
|
+
auth: "plugin",
|
|
29
169
|
handler: async (req, res) => {
|
|
30
|
-
console.log("[iPad WeChat]
|
|
31
|
-
|
|
170
|
+
console.log("[iPad WeChat] OpenClaw route hit:", (req as any).url);
|
|
32
171
|
const cfg = (req as any).cfg;
|
|
33
172
|
const ready = getCacheManagerReady();
|
|
34
|
-
if (ready)
|
|
35
|
-
await ready;
|
|
36
|
-
}
|
|
173
|
+
if (ready) await ready;
|
|
37
174
|
|
|
38
|
-
const payload = (req as any).body;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (payload) {
|
|
42
|
-
await handleInboundMessage(api, payload, cfg);
|
|
43
|
-
}
|
|
175
|
+
const payload = transformPayload((req as any).body || {});
|
|
176
|
+
if (payload) await handleInboundMessage(api, payload, cfg);
|
|
44
177
|
|
|
45
178
|
res.statusCode = 200;
|
|
46
179
|
res.end("ok");
|
|
@@ -48,6 +181,90 @@ export default defineChannelPluginEntry({
|
|
|
48
181
|
},
|
|
49
182
|
});
|
|
50
183
|
|
|
51
|
-
|
|
184
|
+
// ── 2. Standalone HTTP server (primary – bypasses OpenClaw routing bug) ──
|
|
185
|
+
if (_webhookServerStarted) {
|
|
186
|
+
console.log("[iPad WeChat] Webhook server already running, skipping duplicate start");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
_webhookServerStarted = true;
|
|
190
|
+
|
|
191
|
+
const server = createServer((req, res) => {
|
|
192
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
193
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
|
|
194
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
195
|
+
|
|
196
|
+
if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; }
|
|
197
|
+
|
|
198
|
+
if (req.method === "GET") {
|
|
199
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
200
|
+
res.end(JSON.stringify({ status: "ok", service: "ipad-wechat-webhook", port: WEBHOOK_PORT }));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (req.method !== "POST") { res.writeHead(405); res.end("Method not allowed"); return; }
|
|
205
|
+
|
|
206
|
+
let body = "";
|
|
207
|
+
req.on("data", (chunk: Buffer) => { body += chunk; });
|
|
208
|
+
req.on("end", async () => {
|
|
209
|
+
try {
|
|
210
|
+
const raw = JSON.parse(body);
|
|
211
|
+
console.log("[iPad WeChat] Webhook received:", JSON.stringify(raw).slice(0, 500));
|
|
212
|
+
|
|
213
|
+
const payload = transformPayload(raw);
|
|
214
|
+
if (payload && payload.message) {
|
|
215
|
+
const cfg = loadOpenClawConfig();
|
|
216
|
+
const msg = payload.message;
|
|
217
|
+
const isChatroom = !!(msg as any).roomId;
|
|
218
|
+
const peerId = isChatroom ? (msg as any).roomId : (msg.fromUser || msg.toUser || "");
|
|
219
|
+
const peerKind = isChatroom ? "group" : "direct";
|
|
220
|
+
|
|
221
|
+
let sessionKey = `agent:main:ipad-wechat:${peerKind}:${peerId}`;
|
|
222
|
+
try {
|
|
223
|
+
const route = buildChannelOutboundSessionRoute({
|
|
224
|
+
cfg, agentId: "main", channel: "ipad-wechat",
|
|
225
|
+
peer: { kind: peerKind as any, id: peerId },
|
|
226
|
+
chatType: peerKind as any,
|
|
227
|
+
from: msg.toUser || "", to: msg.fromUser || "",
|
|
228
|
+
});
|
|
229
|
+
sessionKey = route.sessionKey;
|
|
230
|
+
} catch { /* fallback to manual key */ }
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
await dispatchViaGateway({
|
|
234
|
+
sessionKey,
|
|
235
|
+
message: msg.content || "",
|
|
236
|
+
from: msg.fromUser || "",
|
|
237
|
+
to: msg.toUser || "",
|
|
238
|
+
});
|
|
239
|
+
console.log(`[iPad WeChat] Message dispatched → session=${sessionKey}`);
|
|
240
|
+
} catch (dispatchErr) {
|
|
241
|
+
console.error("[iPad WeChat] Gateway dispatch error:", dispatchErr);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
246
|
+
res.end("ok");
|
|
247
|
+
} catch (err) {
|
|
248
|
+
console.error("[iPad WeChat] Webhook error:", err);
|
|
249
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
250
|
+
res.end("error");
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
server.on("error", (err: NodeJS.ErrnoException) => {
|
|
256
|
+
if (err.code === "EADDRINUSE") {
|
|
257
|
+
console.error(`[iPad WeChat] Port ${WEBHOOK_PORT} in use. Set IPAD_WECHAT_WEBHOOK_PORT to change.`);
|
|
258
|
+
} else {
|
|
259
|
+
console.error("[iPad WeChat] Server error:", err);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
server.listen(WEBHOOK_PORT, "0.0.0.0", () => {
|
|
264
|
+
console.log(`[iPad WeChat] ✅ Webhook server listening on 0.0.0.0:${WEBHOOK_PORT}`);
|
|
265
|
+
console.log(`[iPad WeChat] Set JuHeBot notifyUrl → http://<public-ip>:${WEBHOOK_PORT}/`);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
console.log("[iPad WeChat] registerFull complete");
|
|
52
269
|
},
|
|
53
270
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gloablehive/ipad-wechat-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.25",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw channel plugin for iPad WeChat protocol - enables sending/receiving WeChat messages through iPad protocol",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@gloablehive/wechat-cache": "
|
|
23
|
+
"@gloablehive/wechat-cache": "file:../wechat-cache",
|
|
24
24
|
"openclaw": ">=1.0.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
package/src/channel.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
AccountRegistry,
|
|
27
27
|
createAccountRegistry,
|
|
28
28
|
RegisteredAccount,
|
|
29
|
+
KnowledgeConfig,
|
|
29
30
|
} from "@gloablehive/wechat-cache";
|
|
30
31
|
|
|
31
32
|
// Import client pool
|
|
@@ -107,9 +108,20 @@ function getCacheManager(cfg: OpenClawConfig): CacheManager {
|
|
|
107
108
|
|
|
108
109
|
const basePath = section?.cachePath || "./cache/wechat-ipad";
|
|
109
110
|
|
|
111
|
+
// Build knowledge config if provided in section
|
|
112
|
+
let knowledgeConfig: (Partial<KnowledgeConfig> & { nodeId: string; agentId: string }) | undefined;
|
|
113
|
+
if (section?.knowledge) {
|
|
114
|
+
knowledgeConfig = {
|
|
115
|
+
...section.knowledge,
|
|
116
|
+
nodeId: section.knowledge.nodeId || 'ipad-wechat-node',
|
|
117
|
+
agentId: section.knowledge.agentId || section.agentId || 'default-agent',
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
110
121
|
cacheManager = createCacheManager({
|
|
111
122
|
basePath,
|
|
112
123
|
accounts,
|
|
124
|
+
knowledgeConfig,
|
|
113
125
|
});
|
|
114
126
|
|
|
115
127
|
// Initialize cache manager - await for ready state
|