@gloablehive/ipad-wechat-plugin 1.0.24 → 1.0.26

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 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 { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
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, registering webhook route");
20
- // Test with a simple different path
153
+ console.log("[iPad WeChat] registerFull called");
154
+ // ── 1. Standard OpenClaw route (kept for forward-compat) ──
21
155
  api.registerHttpRoute({
22
- path: "/ipad-test-webhook",
156
+ path: "/ipad-wechat/webhook",
23
157
  auth: "plugin",
24
- match: "exact",
25
158
  handler: async (req, res) => {
26
- console.log("[iPad WeChat] Handler called, url:", req.url);
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
- const payload = req.body;
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
- console.log("[iPad WeChat] Webhook route registered");
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/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 { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
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, registering webhook route");
163
+ console.log("[iPad WeChat] registerFull called");
23
164
 
24
- // Test with a simple different path
165
+ // ── 1. Standard OpenClaw route (kept for forward-compat) ──
25
166
  api.registerHttpRoute({
26
- path: "/ipad-test-webhook",
167
+ path: "/ipad-wechat/webhook",
27
168
  auth: "plugin",
28
- match: "exact",
29
169
  handler: async (req, res) => {
30
- console.log("[iPad WeChat] Handler called, url:", (req as any).url);
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
- console.log("[iPad WeChat] Received webhook:", payload);
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
- console.log("[iPad WeChat] Webhook route registered");
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
  });
@@ -91,7 +91,7 @@
91
91
  "channels": ["ipad-wechat"],
92
92
  "http": {
93
93
  "outbound": ["*"],
94
- "inbound": ["/api/channels/ipad-wechat/webhook"]
94
+ "inbound": ["/ipad-wechat/webhook"]
95
95
  }
96
96
  },
97
97
  "apiVersion": "1.0.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gloablehive/ipad-wechat-plugin",
3
- "version": "1.0.24",
3
+ "version": "1.0.26",
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": "file:../wechat-cache",
23
+ "@gloablehive/wechat-cache": "^1.2.0",
24
24
  "openclaw": ">=1.0.0"
25
25
  },
26
26
  "devDependencies": {