@netcat-ai/openclaw-weixin 2.4.7

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.
Files changed (116) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/CHANGELOG.zh_CN.md +156 -0
  3. package/LICENSE +27 -0
  4. package/README.md +355 -0
  5. package/README.zh_CN.md +351 -0
  6. package/dist/index.js +16 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/src/api/api.js +484 -0
  9. package/dist/src/api/api.js.map +1 -0
  10. package/dist/src/api/config-cache.js +64 -0
  11. package/dist/src/api/config-cache.js.map +1 -0
  12. package/dist/src/api/session-guard.js +49 -0
  13. package/dist/src/api/session-guard.js.map +1 -0
  14. package/dist/src/api/types.js +37 -0
  15. package/dist/src/api/types.js.map +1 -0
  16. package/dist/src/auth/accounts.js +328 -0
  17. package/dist/src/auth/accounts.js.map +1 -0
  18. package/dist/src/auth/login-qr.js +331 -0
  19. package/dist/src/auth/login-qr.js.map +1 -0
  20. package/dist/src/auth/pairing.js +104 -0
  21. package/dist/src/auth/pairing.js.map +1 -0
  22. package/dist/src/cdn/aes-ecb.js +19 -0
  23. package/dist/src/cdn/aes-ecb.js.map +1 -0
  24. package/dist/src/cdn/cdn-upload.js +74 -0
  25. package/dist/src/cdn/cdn-upload.js.map +1 -0
  26. package/dist/src/cdn/cdn-url.js +14 -0
  27. package/dist/src/cdn/cdn-url.js.map +1 -0
  28. package/dist/src/cdn/pic-decrypt.js +89 -0
  29. package/dist/src/cdn/pic-decrypt.js.map +1 -0
  30. package/dist/src/cdn/upload.js +115 -0
  31. package/dist/src/cdn/upload.js.map +1 -0
  32. package/dist/src/channel.js +478 -0
  33. package/dist/src/channel.js.map +1 -0
  34. package/dist/src/compat.js +67 -0
  35. package/dist/src/compat.js.map +1 -0
  36. package/dist/src/config/config-schema.js +20 -0
  37. package/dist/src/config/config-schema.js.map +1 -0
  38. package/dist/src/config/reply-progress.js +5 -0
  39. package/dist/src/config/reply-progress.js.map +1 -0
  40. package/dist/src/media/media-download.js +95 -0
  41. package/dist/src/media/media-download.js.map +1 -0
  42. package/dist/src/media/mime.js +73 -0
  43. package/dist/src/media/mime.js.map +1 -0
  44. package/dist/src/media/silk-transcode.js +64 -0
  45. package/dist/src/media/silk-transcode.js.map +1 -0
  46. package/dist/src/messaging/debug-mode.js +63 -0
  47. package/dist/src/messaging/debug-mode.js.map +1 -0
  48. package/dist/src/messaging/error-notice.js +25 -0
  49. package/dist/src/messaging/error-notice.js.map +1 -0
  50. package/dist/src/messaging/inbound.js +201 -0
  51. package/dist/src/messaging/inbound.js.map +1 -0
  52. package/dist/src/messaging/markdown-filter.js +368 -0
  53. package/dist/src/messaging/markdown-filter.js.map +1 -0
  54. package/dist/src/messaging/outbound-hooks.js +58 -0
  55. package/dist/src/messaging/outbound-hooks.js.map +1 -0
  56. package/dist/src/messaging/process-message.js +406 -0
  57. package/dist/src/messaging/process-message.js.map +1 -0
  58. package/dist/src/messaging/reply-progress-sender.js +93 -0
  59. package/dist/src/messaging/reply-progress-sender.js.map +1 -0
  60. package/dist/src/messaging/send-media.js +54 -0
  61. package/dist/src/messaging/send-media.js.map +1 -0
  62. package/dist/src/messaging/send.js +220 -0
  63. package/dist/src/messaging/send.js.map +1 -0
  64. package/dist/src/messaging/slash-commands.js +70 -0
  65. package/dist/src/messaging/slash-commands.js.map +1 -0
  66. package/dist/src/monitor/monitor.js +146 -0
  67. package/dist/src/monitor/monitor.js.map +1 -0
  68. package/dist/src/storage/state-dir.js +9 -0
  69. package/dist/src/storage/state-dir.js.map +1 -0
  70. package/dist/src/storage/sync-buf.js +64 -0
  71. package/dist/src/storage/sync-buf.js.map +1 -0
  72. package/dist/src/util/logger.js +120 -0
  73. package/dist/src/util/logger.js.map +1 -0
  74. package/dist/src/util/random.js +16 -0
  75. package/dist/src/util/random.js.map +1 -0
  76. package/dist/src/util/redact.js +54 -0
  77. package/dist/src/util/redact.js.map +1 -0
  78. package/index.ts +19 -0
  79. package/openclaw.plugin.json +22 -0
  80. package/package.json +75 -0
  81. package/src/api/api.ts +586 -0
  82. package/src/api/config-cache.ts +79 -0
  83. package/src/api/session-guard.ts +58 -0
  84. package/src/api/types.ts +278 -0
  85. package/src/auth/accounts.ts +394 -0
  86. package/src/auth/login-qr.ts +458 -0
  87. package/src/auth/pairing.ts +120 -0
  88. package/src/cdn/aes-ecb.ts +21 -0
  89. package/src/cdn/cdn-upload.ts +93 -0
  90. package/src/cdn/cdn-url.ts +20 -0
  91. package/src/cdn/pic-decrypt.ts +101 -0
  92. package/src/cdn/upload.ts +168 -0
  93. package/src/channel.ts +548 -0
  94. package/src/compat.ts +77 -0
  95. package/src/config/config-schema.ts +23 -0
  96. package/src/config/reply-progress.ts +10 -0
  97. package/src/media/media-download.ts +149 -0
  98. package/src/media/mime.ts +76 -0
  99. package/src/media/silk-transcode.ts +74 -0
  100. package/src/messaging/debug-mode.ts +69 -0
  101. package/src/messaging/error-notice.ts +32 -0
  102. package/src/messaging/inbound.ts +262 -0
  103. package/src/messaging/markdown-filter.ts +361 -0
  104. package/src/messaging/outbound-hooks.ts +88 -0
  105. package/src/messaging/process-message.ts +525 -0
  106. package/src/messaging/reply-progress-sender.ts +122 -0
  107. package/src/messaging/send-media.ts +72 -0
  108. package/src/messaging/send.ts +294 -0
  109. package/src/messaging/slash-commands.ts +110 -0
  110. package/src/monitor/monitor.ts +224 -0
  111. package/src/storage/state-dir.ts +11 -0
  112. package/src/storage/sync-buf.ts +81 -0
  113. package/src/util/logger.ts +145 -0
  114. package/src/util/random.ts +17 -0
  115. package/src/util/redact.ts +54 -0
  116. package/src/vendor.d.ts +25 -0
@@ -0,0 +1,361 @@
1
+ /**
2
+ * Streaming markdown filter — character-level state machine that strips
3
+ * unsupported markdown syntax on-the-fly.
4
+ *
5
+ * Outputs as much filtered text as possible on each `feed()` call, only
6
+ * holding back the minimum characters needed for pattern disambiguation
7
+ * (e.g. a trailing `*` that might become `***`).
8
+ *
9
+ * Constructs passed through (not filtered):
10
+ * - Code fences (```)
11
+ * - Inline code (`)
12
+ * - Tables (|...|)
13
+ * - Horizontal rules (---, ***, ___)
14
+ * - Bold (**)
15
+ * - Italic/bold-italic wrapping non-CJK content
16
+ *
17
+ * Constructs filtered (markers stripped, content kept):
18
+ * - Italic/bold-italic wrapping CJK content
19
+ * - Headings H5/H6 (#####, ######)
20
+ * - Images (![alt](url)) — removed entirely
21
+ *
22
+ * States:
23
+ * - **sol** (start-of-line): checks for line-start patterns (```, >, #####, indent)
24
+ * - **body**: scans for inline patterns (![, ~~, ***) and outputs safe chars
25
+ * - **fence**: inside a fenced code block, passes through until closing ```
26
+ * - **inline**: accumulating content inside an inline marker pair
27
+ */
28
+ export class StreamingMarkdownFilter {
29
+ private buf = "";
30
+ private fence = false;
31
+ private sol = true;
32
+ private inl: { type: "image" | "bold3" | "italic" | "ubold3" | "uitalic"; acc: string } | null = null;
33
+
34
+ feed(delta: string): string {
35
+ this.buf += delta;
36
+ return this.pump(false);
37
+ }
38
+
39
+ flush(): string {
40
+ return this.pump(true);
41
+ }
42
+
43
+ private pump(eof: boolean): string {
44
+ let out = "";
45
+ while (this.buf) {
46
+ const sLen = this.buf.length;
47
+ const sSol = this.sol;
48
+ const sFence = this.fence;
49
+ const sInl = this.inl;
50
+
51
+ if (this.fence) out += this.pumpFence(eof);
52
+ else if (this.inl) out += this.pumpInline(eof);
53
+ else if (this.sol) out += this.pumpSOL(eof);
54
+ else out += this.pumpBody(eof);
55
+
56
+ if (this.buf.length === sLen && this.sol === sSol &&
57
+ this.fence === sFence && this.inl === sInl) break;
58
+ }
59
+
60
+ if (eof && this.inl) {
61
+ const markers: Record<string, string> = { image: "![", bold3: "***", italic: "*", ubold3: "___", uitalic: "_" };
62
+ out += (markers[this.inl.type] ?? "") + this.inl.acc;
63
+ this.inl = null;
64
+ }
65
+ return out;
66
+ }
67
+
68
+ /** Inside a code fence: pass content and markers through verbatim. */
69
+ private pumpFence(eof: boolean): string {
70
+ if (this.sol) {
71
+ if (this.buf.length < 3 && !eof) return "";
72
+ if (this.buf.startsWith("```")) {
73
+ const nl = this.buf.indexOf("\n", 3);
74
+ if (nl !== -1) {
75
+ this.fence = false;
76
+ const line = this.buf.slice(0, nl + 1);
77
+ this.buf = this.buf.slice(nl + 1);
78
+ this.sol = true;
79
+ return line;
80
+ }
81
+ if (eof) {
82
+ this.fence = false;
83
+ const line = this.buf;
84
+ this.buf = "";
85
+ return line;
86
+ }
87
+ return "";
88
+ }
89
+ this.sol = false;
90
+ }
91
+ const nl = this.buf.indexOf("\n");
92
+ if (nl !== -1) {
93
+ const chunk = this.buf.slice(0, nl + 1);
94
+ this.buf = this.buf.slice(nl + 1);
95
+ this.sol = true;
96
+ return chunk;
97
+ }
98
+ const chunk = this.buf;
99
+ this.buf = "";
100
+ return chunk;
101
+ }
102
+
103
+ /** At start of line: detect and consume line-start patterns, then transition to body. */
104
+ private pumpSOL(eof: boolean): string {
105
+ const b = this.buf;
106
+
107
+ if (b[0] === "\n") {
108
+ this.buf = b.slice(1);
109
+ return "\n";
110
+ }
111
+
112
+ if (b[0] === "`") {
113
+ if (b.length < 3 && !eof) return "";
114
+ if (b.startsWith("```")) {
115
+ const nl = b.indexOf("\n", 3);
116
+ if (nl !== -1) {
117
+ this.fence = true;
118
+ const line = b.slice(0, nl + 1);
119
+ this.buf = b.slice(nl + 1);
120
+ this.sol = true;
121
+ return line;
122
+ }
123
+ if (eof) {
124
+ this.buf = "";
125
+ return b;
126
+ }
127
+ return "";
128
+ }
129
+ this.sol = false;
130
+ return "";
131
+ }
132
+
133
+ if (b[0] === ">") {
134
+ this.sol = false;
135
+ return "";
136
+ }
137
+
138
+ if (b[0] === "#") {
139
+ let n = 0;
140
+ while (n < b.length && b[n] === "#") n++;
141
+ if (n === b.length && !eof) return "";
142
+ if (n >= 5 && n <= 6 && n < b.length && b[n] === " ") {
143
+ this.buf = b.slice(n + 1);
144
+ this.sol = false;
145
+ return "";
146
+ }
147
+ this.sol = false;
148
+ return "";
149
+ }
150
+
151
+ if (b[0] === " " || b[0] === "\t") {
152
+ if (b.search(/[^ \t]/) === -1 && !eof) return "";
153
+ this.sol = false;
154
+ return "";
155
+ }
156
+
157
+ if (b[0] === "-" || b[0] === "*" || b[0] === "_") {
158
+ const ch = b[0];
159
+ let j = 0;
160
+ while (j < b.length && (b[j] === ch || b[j] === " ")) j++;
161
+ if (j === b.length && !eof) return "";
162
+ if (j === b.length || b[j] === "\n") {
163
+ let count = 0;
164
+ for (let k = 0; k < j; k++) if (b[k] === ch) count++;
165
+ if (count >= 3) {
166
+ if (j < b.length) {
167
+ this.buf = b.slice(j + 1);
168
+ this.sol = true;
169
+ return b.slice(0, j + 1);
170
+ }
171
+ this.buf = "";
172
+ return b;
173
+ }
174
+ }
175
+ this.sol = false;
176
+ return "";
177
+ }
178
+
179
+ this.sol = false;
180
+ return "";
181
+ }
182
+
183
+ /** Scan line body for inline pattern triggers; output safe chars eagerly. */
184
+ private pumpBody(eof: boolean): string {
185
+ let out = "";
186
+ let i = 0;
187
+ while (i < this.buf.length) {
188
+ const c = this.buf[i];
189
+ if (c === "\n") {
190
+ out += this.buf.slice(0, i + 1);
191
+ this.buf = this.buf.slice(i + 1);
192
+ this.sol = true;
193
+ return out;
194
+ }
195
+ if (c === "!" && i + 1 < this.buf.length && this.buf[i + 1] === "[") {
196
+ out += this.buf.slice(0, i);
197
+ this.buf = this.buf.slice(i + 2);
198
+ this.inl = { type: "image", acc: "" };
199
+ return out;
200
+ }
201
+ if (c === "~") {
202
+ i++;
203
+ continue;
204
+ }
205
+ if (c === "*") {
206
+ if (i + 2 < this.buf.length && this.buf[i + 1] === "*" && this.buf[i + 2] === "*") {
207
+ out += this.buf.slice(0, i);
208
+ this.buf = this.buf.slice(i + 3);
209
+ this.inl = { type: "bold3", acc: "" };
210
+ return out;
211
+ }
212
+ if (i + 1 < this.buf.length && this.buf[i + 1] === "*") {
213
+ i += 2;
214
+ continue;
215
+ }
216
+ if (i + 1 < this.buf.length && this.buf[i + 1] !== " " && this.buf[i + 1] !== "\n") {
217
+ out += this.buf.slice(0, i);
218
+ this.buf = this.buf.slice(i + 1);
219
+ this.inl = { type: "italic", acc: "" };
220
+ return out;
221
+ }
222
+ i++;
223
+ continue;
224
+ }
225
+ if (c === "_") {
226
+ if (i + 2 < this.buf.length && this.buf[i + 1] === "_" && this.buf[i + 2] === "_") {
227
+ out += this.buf.slice(0, i);
228
+ this.buf = this.buf.slice(i + 3);
229
+ this.inl = { type: "ubold3", acc: "" };
230
+ return out;
231
+ }
232
+ if (i + 1 < this.buf.length && this.buf[i + 1] === "_") {
233
+ i += 2;
234
+ continue;
235
+ }
236
+ if (i + 1 < this.buf.length && this.buf[i + 1] !== " " && this.buf[i + 1] !== "\n") {
237
+ out += this.buf.slice(0, i);
238
+ this.buf = this.buf.slice(i + 1);
239
+ this.inl = { type: "uitalic", acc: "" };
240
+ return out;
241
+ }
242
+ i++;
243
+ continue;
244
+ }
245
+ i++;
246
+ }
247
+
248
+ let hold = 0;
249
+ if (!eof) {
250
+ if (this.buf.endsWith("**")) hold = 2;
251
+ else if (this.buf.endsWith("__")) hold = 2;
252
+ else if (this.buf.endsWith("*")) hold = 1;
253
+ else if (this.buf.endsWith("_")) hold = 1;
254
+ else if (this.buf.endsWith("!")) hold = 1;
255
+ }
256
+ out += this.buf.slice(0, this.buf.length - hold);
257
+ this.buf = hold > 0 ? this.buf.slice(-hold) : "";
258
+ return out;
259
+ }
260
+
261
+ /** Accumulate inline content until closing marker is found. */
262
+ private pumpInline(_eof: boolean): string {
263
+ if (!this.inl) return "";
264
+ this.inl.acc += this.buf;
265
+ this.buf = "";
266
+
267
+ switch (this.inl.type) {
268
+ case "bold3": {
269
+ const idx = this.inl.acc.indexOf("***");
270
+ if (idx !== -1) {
271
+ const content = this.inl.acc.slice(0, idx);
272
+ this.buf = this.inl.acc.slice(idx + 3);
273
+ this.inl = null;
274
+ if (StreamingMarkdownFilter.containsCJK(content)) return content;
275
+ return `***${content}***`;
276
+ }
277
+ return "";
278
+ }
279
+ case "ubold3": {
280
+ const idx = this.inl.acc.indexOf("___");
281
+ if (idx !== -1) {
282
+ const content = this.inl.acc.slice(0, idx);
283
+ this.buf = this.inl.acc.slice(idx + 3);
284
+ this.inl = null;
285
+ if (StreamingMarkdownFilter.containsCJK(content)) return content;
286
+ return `___${content}___`;
287
+ }
288
+ return "";
289
+ }
290
+ case "italic": {
291
+ for (let j = 0; j < this.inl.acc.length; j++) {
292
+ if (this.inl.acc[j] === "\n") {
293
+ const r = "*" + this.inl.acc.slice(0, j + 1);
294
+ this.buf = this.inl.acc.slice(j + 1);
295
+ this.inl = null;
296
+ this.sol = true;
297
+ return r;
298
+ }
299
+ if (this.inl.acc[j] === "*") {
300
+ if (j + 1 < this.inl.acc.length && this.inl.acc[j + 1] === "*") {
301
+ j++;
302
+ continue;
303
+ }
304
+ const content = this.inl.acc.slice(0, j);
305
+ this.buf = this.inl.acc.slice(j + 1);
306
+ this.inl = null;
307
+ if (StreamingMarkdownFilter.containsCJK(content)) return content;
308
+ return `*${content}*`;
309
+ }
310
+ }
311
+ return "";
312
+ }
313
+ case "uitalic": {
314
+ for (let j = 0; j < this.inl.acc.length; j++) {
315
+ if (this.inl.acc[j] === "\n") {
316
+ const r = "_" + this.inl.acc.slice(0, j + 1);
317
+ this.buf = this.inl.acc.slice(j + 1);
318
+ this.inl = null;
319
+ this.sol = true;
320
+ return r;
321
+ }
322
+ if (this.inl.acc[j] === "_") {
323
+ if (j + 1 < this.inl.acc.length && this.inl.acc[j + 1] === "_") {
324
+ j++;
325
+ continue;
326
+ }
327
+ const content = this.inl.acc.slice(0, j);
328
+ this.buf = this.inl.acc.slice(j + 1);
329
+ this.inl = null;
330
+ if (StreamingMarkdownFilter.containsCJK(content)) return content;
331
+ return `_${content}_`;
332
+ }
333
+ }
334
+ return "";
335
+ }
336
+ case "image": {
337
+ const cb = this.inl.acc.indexOf("]");
338
+ if (cb === -1) return "";
339
+ if (cb + 1 >= this.inl.acc.length) return "";
340
+ if (this.inl.acc[cb + 1] !== "(") {
341
+ const r = "![" + this.inl.acc.slice(0, cb + 1);
342
+ this.buf = this.inl.acc.slice(cb + 1);
343
+ this.inl = null;
344
+ return r;
345
+ }
346
+ const cp = this.inl.acc.indexOf(")", cb + 2);
347
+ if (cp !== -1) {
348
+ this.buf = this.inl.acc.slice(cp + 1);
349
+ this.inl = null;
350
+ return "";
351
+ }
352
+ return "";
353
+ }
354
+ }
355
+ return "";
356
+ }
357
+
358
+ private static containsCJK(text: string): boolean {
359
+ return /[\u2E80-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF]/.test(text);
360
+ }
361
+ }
@@ -0,0 +1,88 @@
1
+ import {
2
+ fireAndForgetHook,
3
+ buildCanonicalSentMessageHookContext,
4
+ toPluginMessageContext,
5
+ toPluginMessageSentEvent,
6
+ } from "openclaw/plugin-sdk/hook-runtime";
7
+ import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
8
+
9
+ import { logger } from "../util/logger.js";
10
+
11
+ const CHANNEL_ID = "openclaw-weixin";
12
+
13
+ /**
14
+ * Run message_sending hook before sending.
15
+ * Returns the (possibly modified) text content plus a cancelled flag.
16
+ * Hook errors are caught and logged — sending proceeds regardless.
17
+ */
18
+ export async function applyWeixinMessageSendingHook(params: {
19
+ to: string;
20
+ text: string;
21
+ accountId?: string;
22
+ mediaUrl?: string;
23
+ runId?: string;
24
+ }): Promise<{ cancelled: boolean; text: string }> {
25
+ const hookRunner = getGlobalHookRunner();
26
+ if (!hookRunner?.hasHooks("message_sending")) {
27
+ return { cancelled: false, text: params.text };
28
+ }
29
+ try {
30
+ const hookResult = await hookRunner.runMessageSending(
31
+ {
32
+ to: params.to,
33
+ content: params.text,
34
+ metadata: {
35
+ channel: CHANNEL_ID,
36
+ accountId: params.accountId,
37
+ runId: params.runId,
38
+ ...(params.mediaUrl ? { mediaUrls: [params.mediaUrl] } : {}),
39
+ },
40
+ },
41
+ { channelId: CHANNEL_ID, accountId: params.accountId },
42
+ );
43
+ if (hookResult?.cancel) {
44
+ return { cancelled: true, text: params.text };
45
+ }
46
+ return {
47
+ cancelled: false,
48
+ text: hookResult?.content ?? params.text,
49
+ };
50
+ } catch (err) {
51
+ logger.warn(`message_sending hook error, proceeding with send: ${String(err)}`);
52
+ return { cancelled: false, text: params.text };
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Fire message_sent hook (fire-and-forget) after a send attempt.
58
+ */
59
+ export function emitWeixinMessageSent(params: {
60
+ to: string;
61
+ content: string;
62
+ success: boolean;
63
+ error?: string;
64
+ accountId?: string;
65
+ runId?: string;
66
+ }): void {
67
+ const hookRunner = getGlobalHookRunner();
68
+ if (!hookRunner?.hasHooks("message_sent")) return;
69
+ const canonical = buildCanonicalSentMessageHookContext({
70
+ to: params.to,
71
+ content: params.content,
72
+ success: params.success,
73
+ error: params.error,
74
+ channelId: CHANNEL_ID,
75
+ accountId: params.accountId,
76
+ conversationId: params.to,
77
+ runId: params.runId,
78
+ });
79
+ fireAndForgetHook(
80
+ Promise.resolve(
81
+ hookRunner!.runMessageSent(
82
+ toPluginMessageSentEvent(canonical),
83
+ toPluginMessageContext(canonical),
84
+ ),
85
+ ),
86
+ "weixin: message_sent plugin hook failed",
87
+ );
88
+ }