@pawastation/wechat-kf 0.1.1

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 (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +291 -0
  3. package/README.zh-CN.md +401 -0
  4. package/dist/index.d.ts +27 -0
  5. package/dist/index.js +24 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/src/accounts.d.ts +37 -0
  8. package/dist/src/accounts.js +205 -0
  9. package/dist/src/accounts.js.map +1 -0
  10. package/dist/src/api.d.ts +29 -0
  11. package/dist/src/api.js +172 -0
  12. package/dist/src/api.js.map +1 -0
  13. package/dist/src/bot.d.ts +35 -0
  14. package/dist/src/bot.js +379 -0
  15. package/dist/src/bot.js.map +1 -0
  16. package/dist/src/channel.d.ts +113 -0
  17. package/dist/src/channel.js +183 -0
  18. package/dist/src/channel.js.map +1 -0
  19. package/dist/src/chunk-utils.d.ts +18 -0
  20. package/dist/src/chunk-utils.js +58 -0
  21. package/dist/src/chunk-utils.js.map +1 -0
  22. package/dist/src/config-schema.d.ts +56 -0
  23. package/dist/src/config-schema.js +38 -0
  24. package/dist/src/config-schema.js.map +1 -0
  25. package/dist/src/constants.d.ts +19 -0
  26. package/dist/src/constants.js +20 -0
  27. package/dist/src/constants.js.map +1 -0
  28. package/dist/src/crypto.d.ts +18 -0
  29. package/dist/src/crypto.js +80 -0
  30. package/dist/src/crypto.js.map +1 -0
  31. package/dist/src/fs-utils.d.ts +7 -0
  32. package/dist/src/fs-utils.js +13 -0
  33. package/dist/src/fs-utils.js.map +1 -0
  34. package/dist/src/monitor.d.ts +18 -0
  35. package/dist/src/monitor.js +131 -0
  36. package/dist/src/monitor.js.map +1 -0
  37. package/dist/src/outbound.d.ts +66 -0
  38. package/dist/src/outbound.js +234 -0
  39. package/dist/src/outbound.js.map +1 -0
  40. package/dist/src/reply-dispatcher.d.ts +40 -0
  41. package/dist/src/reply-dispatcher.js +120 -0
  42. package/dist/src/reply-dispatcher.js.map +1 -0
  43. package/dist/src/runtime.d.ts +130 -0
  44. package/dist/src/runtime.js +22 -0
  45. package/dist/src/runtime.js.map +1 -0
  46. package/dist/src/send-utils.d.ts +30 -0
  47. package/dist/src/send-utils.js +89 -0
  48. package/dist/src/send-utils.js.map +1 -0
  49. package/dist/src/send.d.ts +7 -0
  50. package/dist/src/send.js +13 -0
  51. package/dist/src/send.js.map +1 -0
  52. package/dist/src/token.d.ts +8 -0
  53. package/dist/src/token.js +57 -0
  54. package/dist/src/token.js.map +1 -0
  55. package/dist/src/types.d.ts +173 -0
  56. package/dist/src/types.js +3 -0
  57. package/dist/src/types.js.map +1 -0
  58. package/dist/src/unicode-format.d.ts +26 -0
  59. package/dist/src/unicode-format.js +157 -0
  60. package/dist/src/unicode-format.js.map +1 -0
  61. package/dist/src/webhook.d.ts +22 -0
  62. package/dist/src/webhook.js +138 -0
  63. package/dist/src/webhook.js.map +1 -0
  64. package/dist/src/wechat-kf-directives.d.ts +34 -0
  65. package/dist/src/wechat-kf-directives.js +65 -0
  66. package/dist/src/wechat-kf-directives.js.map +1 -0
  67. package/index.ts +32 -0
  68. package/openclaw.plugin.json +31 -0
  69. package/package.json +91 -0
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Markdown → Unicode text formatting
3
+ *
4
+ * Converts markdown bold/italic/bold-italic to Unicode Mathematical
5
+ * Alphanumeric Symbols. Only converts ASCII letters (a-z, A-Z) and
6
+ * digits (0-9) — other characters pass through unchanged.
7
+ *
8
+ * This is meant for plain-text surfaces like WeChat KF where
9
+ * rich text / HTML isn't supported.
10
+ */
11
+ // Unicode Mathematical Alphanumeric offsets for A-Z, a-z
12
+ // Bold: U+1D400 (A) / U+1D41A (a)
13
+ // Italic: U+1D434 (A) / U+1D44E (a)
14
+ // Bold Italic: U+1D468 (A) / U+1D482 (a)
15
+ // Bold digits: U+1D7CE (0)
16
+ const BOLD_UPPER_START = 0x1d400;
17
+ const BOLD_LOWER_START = 0x1d41a;
18
+ const BOLD_DIGIT_START = 0x1d7ce;
19
+ const ITALIC_UPPER_START = 0x1d434;
20
+ const ITALIC_LOWER_START = 0x1d44e;
21
+ // Italic has no digit variant — use normal digits
22
+ const BOLD_ITALIC_UPPER_START = 0x1d468;
23
+ const BOLD_ITALIC_LOWER_START = 0x1d482;
24
+ function mapChar(ch, upperStart, lowerStart, digitStart) {
25
+ const code = ch.charCodeAt(0);
26
+ if (code >= 65 && code <= 90) {
27
+ // A-Z
28
+ return String.fromCodePoint(upperStart + (code - 65));
29
+ }
30
+ if (code >= 97 && code <= 122) {
31
+ // a-z
32
+ return String.fromCodePoint(lowerStart + (code - 97));
33
+ }
34
+ if (digitStart !== undefined && code >= 48 && code <= 57) {
35
+ // 0-9
36
+ return String.fromCodePoint(digitStart + (code - 48));
37
+ }
38
+ return ch;
39
+ }
40
+ function toBold(text) {
41
+ return [...text].map((ch) => mapChar(ch, BOLD_UPPER_START, BOLD_LOWER_START, BOLD_DIGIT_START)).join("");
42
+ }
43
+ function toItalic(text) {
44
+ return [...text]
45
+ .map((ch) => {
46
+ // Special case: italic lowercase 'h' is U+210E (ℎ) not in the block
47
+ if (ch === "h")
48
+ return "\u210E";
49
+ return mapChar(ch, ITALIC_UPPER_START, ITALIC_LOWER_START);
50
+ })
51
+ .join("");
52
+ }
53
+ function toBoldItalic(text) {
54
+ return [...text].map((ch) => mapChar(ch, BOLD_ITALIC_UPPER_START, BOLD_ITALIC_LOWER_START)).join("");
55
+ }
56
+ /** Regex matching any placeholder inserted by steps 1-4. */
57
+ const PLACEHOLDER_RE = /\x00(?:CB|IC|ES)\d+\x00/g;
58
+ /**
59
+ * Apply a Unicode transform while preserving placeholder sequences.
60
+ * Splits text on placeholder boundaries, transforms only non-placeholder
61
+ * segments, and reassembles.
62
+ */
63
+ function applyTransform(text, transform) {
64
+ let result = "";
65
+ let lastIndex = 0;
66
+ for (const match of text.matchAll(PLACEHOLDER_RE)) {
67
+ const idx = match.index;
68
+ if (idx > lastIndex) {
69
+ result += transform(text.slice(lastIndex, idx));
70
+ }
71
+ result += match[0];
72
+ lastIndex = idx + match[0].length;
73
+ }
74
+ if (lastIndex < text.length) {
75
+ result += transform(text.slice(lastIndex));
76
+ }
77
+ return result;
78
+ }
79
+ /**
80
+ * Convert markdown formatting to Unicode styled text.
81
+ *
82
+ * Handles:
83
+ * - `***text***` or `___text___` → bold italic
84
+ * - `**text**` or `__text__` → bold
85
+ * - `*text*` or `_text_` → italic
86
+ * - `` `code` `` → left as-is (backtick preserved)
87
+ * - ``` code blocks ``` → left as-is
88
+ * - `# headings` → 𝗛𝗲𝗮𝗱𝗶𝗻𝗴 (bold, # stripped)
89
+ * - `- list items` / `* list items` → • item
90
+ * - `1. numbered` → 1. (kept)
91
+ * - `[text](url)` → text (url)
92
+ * - `~~strikethrough~~` → stripped markers (no unicode strikethrough that's reliable)
93
+ */
94
+ export function markdownToUnicode(text) {
95
+ if (!text)
96
+ return text;
97
+ // 1. Preserve code blocks
98
+ const codeBlocks = [];
99
+ let result = text.replace(/```[\s\S]*?```/g, (match) => {
100
+ codeBlocks.push(match.replace(/^```\w*\n?/, "").replace(/\n?```$/, ""));
101
+ return `\x00CB${codeBlocks.length - 1}\x00`;
102
+ });
103
+ // 2. Preserve inline code
104
+ const inlineCodes = [];
105
+ result = result.replace(/`([^`\n]+)`/g, (_match, code) => {
106
+ inlineCodes.push(code);
107
+ return `\x00IC${inlineCodes.length - 1}\x00`;
108
+ });
109
+ // 3. Images: ![alt](url) → [alt](url) (must come before link handling)
110
+ result = result.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, "[$1]($2)");
111
+ // 4. Escape characters: protect \* \_ \~ \` \# \[ \] \( \) \\ \- \!
112
+ const escapes = [];
113
+ result = result.replace(/\\([*_~`#[\]()\\!-])/g, (_m, ch) => {
114
+ escapes.push(ch);
115
+ return `\x00ES${escapes.length - 1}\x00`;
116
+ });
117
+ // 5. Links: [text](url) → text (url)
118
+ result = result.replace(/\[([^\]]*)\]\(([^)]+)\)/g, "$1 ($2)");
119
+ // 6. Headings: # text → bold text (before bold/italic so inner markers are also bolded)
120
+ result = result.replace(/^(#{1,6})\s+(.+)$/gm, (_m, _hashes, content) => {
121
+ // Strip any inline bold/italic markers before applying bold
122
+ const stripped = content.replace(/\*{1,3}([^*]+)\*{1,3}/g, "$1").replace(/_{1,3}([^_]+)_{1,3}/g, "$1");
123
+ return applyTransform(stripped, toBold);
124
+ });
125
+ // 7. Bold italic: ***text*** or ___text___ (multiline with [\s\S])
126
+ result = result.replace(/\*{3}([\s\S]+?)\*{3}/g, (_m, inner) => applyTransform(inner, toBoldItalic));
127
+ result = result.replace(/_{3}([\s\S]+?)_{3}/g, (_m, inner) => applyTransform(inner, toBoldItalic));
128
+ // 8. Bold: **text** or __text__ (multiline with [\s\S])
129
+ result = result.replace(/\*{2}([\s\S]+?)\*{2}/g, (_m, inner) => applyTransform(inner, toBold));
130
+ result = result.replace(/_{2}([\s\S]+?)_{2}/g, (_m, inner) => applyTransform(inner, toBold));
131
+ // 9. Italic: *text* or _text_ (but not inside words for _)
132
+ // Use [^*\n] to avoid matching list markers like "* item *"
133
+ result = result.replace(/(?<!\w)\*([^*\n]+?)\*(?!\w)/g, (_m, inner) => applyTransform(inner, toItalic));
134
+ result = result.replace(/(?<!\w)_([^_\n]+?)_(?!\w)/g, (_m, inner) => applyTransform(inner, toItalic));
135
+ // 10. Strikethrough: ~~text~~ → just remove markers
136
+ result = result.replace(/~~([\s\S]+?)~~/g, "$1");
137
+ // 11. Task lists (must come before unordered list handling)
138
+ result = result.replace(/^([ \t]*)-\s+\[x\]\s+/gm, "$1\u2611 ");
139
+ result = result.replace(/^([ \t]*)-\s+\[ \]\s+/gm, "$1\u2610 ");
140
+ // 12. Unordered list: - item or * item → • item
141
+ result = result.replace(/^[ \t]*[-*]\s+/gm, "• ");
142
+ // 13. Blockquotes: > text → ┃ text
143
+ result = result.replace(/^>\s?/gm, "┃ ");
144
+ // 14. Horizontal rules
145
+ result = result.replace(/^[-*_]{3,}$/gm, "─────────");
146
+ // 15. Restore code blocks with visual markers
147
+ result = result.replace(/\x00CB(\d+)\x00/g, (_m, idx) => {
148
+ const code = codeBlocks[Number(idx)] ?? "";
149
+ return `\n━━━ code ━━━\n${code}\n━━━━━━━━━━━━\n`;
150
+ });
151
+ // 16. Restore inline code with backtick markers
152
+ result = result.replace(/\x00IC(\d+)\x00/g, (_m, idx) => `\`${inlineCodes[Number(idx)] ?? ""}\``);
153
+ // 17. Restore escape characters
154
+ result = result.replace(/\x00ES(\d+)\x00/g, (_m, idx) => escapes[Number(idx)] ?? "");
155
+ return result;
156
+ }
157
+ //# sourceMappingURL=unicode-format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unicode-format.js","sourceRoot":"","sources":["../../src/unicode-format.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * HTTP webhook server for WeChat KF callbacks
3
+ *
4
+ * Handles:
5
+ * - GET: URL verification (echostr decrypt)
6
+ * - POST: Event notification (decrypt XML → trigger sync_msg)
7
+ */
8
+ import { type Server } from "node:http";
9
+ export type WebhookHandler = (openKfId: string, token: string) => void | Promise<void>;
10
+ export type WebhookOptions = {
11
+ port: number;
12
+ path: string;
13
+ callbackToken: string;
14
+ encodingAESKey: string;
15
+ corpId: string;
16
+ onEvent: WebhookHandler;
17
+ log?: {
18
+ info: (...a: unknown[]) => void;
19
+ error: (...a: unknown[]) => void;
20
+ };
21
+ };
22
+ export declare function createWebhookServer(opts: WebhookOptions): Server;
@@ -0,0 +1,138 @@
1
+ /**
2
+ * HTTP webhook server for WeChat KF callbacks
3
+ *
4
+ * Handles:
5
+ * - GET: URL verification (echostr decrypt)
6
+ * - POST: Event notification (decrypt XML → trigger sync_msg)
7
+ */
8
+ import { createServer } from "node:http";
9
+ import { decrypt, verifySignature } from "./crypto.js";
10
+ function parseQuery(url) {
11
+ const idx = url.indexOf("?");
12
+ if (idx < 0)
13
+ return {};
14
+ const params = {};
15
+ for (const pair of url.slice(idx + 1).split("&")) {
16
+ const eqIdx = pair.indexOf("=");
17
+ if (eqIdx < 0)
18
+ continue;
19
+ const k = pair.slice(0, eqIdx);
20
+ const v = pair.slice(eqIdx + 1);
21
+ if (k)
22
+ params[decodeURIComponent(k)] = decodeURIComponent(v);
23
+ }
24
+ return params;
25
+ }
26
+ function readBody(req, maxSize = 64 * 1024) {
27
+ return new Promise((resolve, reject) => {
28
+ const chunks = [];
29
+ let size = 0;
30
+ let rejected = false;
31
+ req.on("data", (c) => {
32
+ if (rejected)
33
+ return;
34
+ size += c.length;
35
+ if (size > maxSize) {
36
+ rejected = true;
37
+ // Stop buffering but keep the connection alive so the server
38
+ // can still write a response (413). Resume drains remaining data.
39
+ req.removeAllListeners("data");
40
+ req.resume();
41
+ reject(new Error("[wechat-kf] request body too large"));
42
+ return;
43
+ }
44
+ chunks.push(c);
45
+ });
46
+ req.on("end", () => {
47
+ if (!rejected)
48
+ resolve(Buffer.concat(chunks).toString("utf8"));
49
+ });
50
+ req.on("error", (err) => {
51
+ if (!rejected)
52
+ reject(err);
53
+ });
54
+ });
55
+ }
56
+ /** Extract a tag value from XML string */
57
+ function xmlTag(xml, tag) {
58
+ const re = new RegExp(`<${tag}><!\\[CDATA\\[(.+?)\\]\\]></${tag}>|<${tag}>(.+?)</${tag}>`);
59
+ const m = xml.match(re);
60
+ return m?.[1] ?? m?.[2];
61
+ }
62
+ export function createWebhookServer(opts) {
63
+ const { path, callbackToken, encodingAESKey, onEvent, log } = opts;
64
+ const server = createServer(async (req, res) => {
65
+ const url = req.url ?? "/";
66
+ const pathname = url.split("?")[0];
67
+ if (pathname !== path) {
68
+ res.writeHead(404);
69
+ res.end("not found");
70
+ return;
71
+ }
72
+ const query = parseQuery(url);
73
+ try {
74
+ if (req.method === "GET") {
75
+ // URL verification
76
+ const { msg_signature, timestamp, nonce, echostr } = query;
77
+ if (!msg_signature || !timestamp || !nonce || !echostr) {
78
+ res.writeHead(400);
79
+ res.end("missing params");
80
+ return;
81
+ }
82
+ if (!verifySignature(callbackToken, timestamp, nonce, echostr, msg_signature)) {
83
+ res.writeHead(403);
84
+ res.end("signature mismatch");
85
+ return;
86
+ }
87
+ const { message } = decrypt(encodingAESKey, echostr);
88
+ res.writeHead(200, { "Content-Type": "text/plain" });
89
+ res.end(message);
90
+ return;
91
+ }
92
+ if (req.method === "POST") {
93
+ const { msg_signature, timestamp, nonce } = query;
94
+ const body = await readBody(req);
95
+ // Extract Encrypt from XML
96
+ const encryptedMsg = xmlTag(body, "Encrypt");
97
+ if (!encryptedMsg || !msg_signature || !timestamp || !nonce) {
98
+ res.writeHead(400);
99
+ res.end("bad request");
100
+ return;
101
+ }
102
+ if (!verifySignature(callbackToken, timestamp, nonce, encryptedMsg, msg_signature)) {
103
+ res.writeHead(403);
104
+ res.end("signature mismatch");
105
+ return;
106
+ }
107
+ const { message } = decrypt(encodingAESKey, encryptedMsg);
108
+ // Parse decrypted XML for Token and OpenKfId
109
+ const eventToken = xmlTag(message, "Token") ?? "";
110
+ const openKfId = xmlTag(message, "OpenKfId") ?? "";
111
+ // Respond immediately, process async
112
+ res.writeHead(200, { "Content-Type": "text/plain" });
113
+ res.end("success");
114
+ // Trigger message sync
115
+ Promise.resolve(onEvent(openKfId, eventToken)).catch((err) => {
116
+ (log?.error ?? console.error)("[wechat-kf] onEvent error:", err);
117
+ });
118
+ return;
119
+ }
120
+ res.writeHead(405);
121
+ res.end("method not allowed");
122
+ }
123
+ catch (err) {
124
+ if (err instanceof Error && err.message.includes("body too large")) {
125
+ res.writeHead(413);
126
+ res.end("payload too large");
127
+ return;
128
+ }
129
+ (log?.error ?? console.error)("[wechat-kf] webhook error:", err);
130
+ if (!res.headersSent) {
131
+ res.writeHead(500);
132
+ res.end("internal error");
133
+ }
134
+ }
135
+ });
136
+ return server;
137
+ }
138
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../src/webhook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * WeChat KF directive parser
3
+ *
4
+ * Parses [[wechat_link: title | desc | url | thumbUrl]] directives
5
+ * embedded in agent text replies. The framework doesn't recognize these
6
+ * directives, so the text arrives intact for plugin-level interception.
7
+ *
8
+ * Syntax (pipe-separated fields):
9
+ * [[wechat_link: title | url]] → 2 fields
10
+ * [[wechat_link: title | desc | url]] → 3 fields
11
+ * [[wechat_link: title | desc | url | thumbUrl]] → 4 fields
12
+ */
13
+ export type WechatLinkDirective = {
14
+ title: string;
15
+ desc?: string;
16
+ url: string;
17
+ thumbUrl?: string;
18
+ };
19
+ export type WechatDirectiveResult = {
20
+ text: string;
21
+ link?: WechatLinkDirective;
22
+ };
23
+ /**
24
+ * Quick check whether text contains a `[[wechat_link:...]]` directive.
25
+ */
26
+ export declare function hasWechatLinkDirective(text: string): boolean;
27
+ /**
28
+ * Extract the first `[[wechat_link:...]]` directive from text.
29
+ *
30
+ * Returns the remaining text (with directive stripped and trimmed)
31
+ * plus the parsed link fields. If parsing fails (e.g. invalid URL),
32
+ * returns the original text unchanged with no link.
33
+ */
34
+ export declare function parseWechatLinkDirective(text: string): WechatDirectiveResult;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * WeChat KF directive parser
3
+ *
4
+ * Parses [[wechat_link: title | desc | url | thumbUrl]] directives
5
+ * embedded in agent text replies. The framework doesn't recognize these
6
+ * directives, so the text arrives intact for plugin-level interception.
7
+ *
8
+ * Syntax (pipe-separated fields):
9
+ * [[wechat_link: title | url]] → 2 fields
10
+ * [[wechat_link: title | desc | url]] → 3 fields
11
+ * [[wechat_link: title | desc | url | thumbUrl]] → 4 fields
12
+ */
13
+ const DIRECTIVE_RE = /\[\[wechat_link:\s*([^\]]+)\]\]/i;
14
+ /**
15
+ * Quick check whether text contains a `[[wechat_link:...]]` directive.
16
+ */
17
+ export function hasWechatLinkDirective(text) {
18
+ return DIRECTIVE_RE.test(text);
19
+ }
20
+ /**
21
+ * Extract the first `[[wechat_link:...]]` directive from text.
22
+ *
23
+ * Returns the remaining text (with directive stripped and trimmed)
24
+ * plus the parsed link fields. If parsing fails (e.g. invalid URL),
25
+ * returns the original text unchanged with no link.
26
+ */
27
+ export function parseWechatLinkDirective(text) {
28
+ const match = DIRECTIVE_RE.exec(text);
29
+ if (!match) {
30
+ return { text };
31
+ }
32
+ const parts = match[1].split("|").map((s) => s.trim());
33
+ let link;
34
+ if (parts.length === 2) {
35
+ const [title, url] = parts;
36
+ if (isValidUrl(url)) {
37
+ link = { title, url };
38
+ }
39
+ }
40
+ else if (parts.length === 3) {
41
+ const [title, desc, url] = parts;
42
+ if (isValidUrl(url)) {
43
+ link = { title, desc, url };
44
+ }
45
+ }
46
+ else if (parts.length >= 4) {
47
+ const [title, desc, url, thumbUrl] = parts;
48
+ if (isValidUrl(url)) {
49
+ link = { title, desc, url, thumbUrl: thumbUrl || undefined };
50
+ }
51
+ }
52
+ if (!link) {
53
+ return { text };
54
+ }
55
+ // Strip the directive from text and clean up whitespace
56
+ const stripped = text
57
+ .replace(match[0], "")
58
+ .replace(/\n{3,}/g, "\n\n")
59
+ .trim();
60
+ return { text: stripped, link };
61
+ }
62
+ function isValidUrl(url) {
63
+ return url.startsWith("http://") || url.startsWith("https://");
64
+ }
65
+ //# sourceMappingURL=wechat-kf-directives.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wechat-kf-directives.js","sourceRoot":"","sources":["../../src/wechat-kf-directives.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAcH,MAAM,YAAY,GAAG,kCAAkC,CAAC;AAExD;;GAEG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACnD,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,IAAI,EAAE,CAAC;IAClB,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACvD,IAAI,IAAqC,CAAC;IAE1C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;QAC3B,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,IAAI,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;QACxB,CAAC;IACH,CAAC;SAAM,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;QACjC,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,IAAI,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;QAC9B,CAAC;IACH,CAAC;SAAM,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QAC7B,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,KAAK,CAAC;QAC3C,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,IAAI,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,IAAI,SAAS,EAAE,CAAC;QAC/D,CAAC;IACH,CAAC;IAED,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,IAAI,EAAE,CAAC;IAClB,CAAC;IAED,wDAAwD;IACxD,MAAM,QAAQ,GAAG,IAAI;SAClB,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;SACrB,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC;SAC1B,IAAI,EAAE,CAAC;IACV,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAClC,CAAC;AAED,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;AACjE,CAAC"}
package/index.ts ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * WeChat KF (微信客服) OpenClaw Channel Plugin
3
+ */
4
+
5
+ import { wechatKfPlugin } from "./src/channel.js";
6
+ import { type PluginRuntime, setRuntime } from "./src/runtime.js";
7
+
8
+ export { sendTextMessage, syncMessages } from "./src/api.js";
9
+ export { wechatKfPlugin } from "./src/channel.js";
10
+ export { computeSignature, decrypt, encrypt, verifySignature } from "./src/crypto.js";
11
+ export { getAccessToken } from "./src/token.js";
12
+
13
+ type OpenClawPluginApi = {
14
+ runtime: PluginRuntime;
15
+ registerChannel: (opts: { plugin: typeof wechatKfPlugin }) => void;
16
+ };
17
+
18
+ const plugin = {
19
+ id: "wechat-kf",
20
+ name: "WeChat KF",
21
+ description: "WeChat Customer Service (企业微信客服) channel plugin",
22
+ // Plugin-level config schema (not channel-level).
23
+ // Channel config is handled via openclaw.plugin.json configSchema
24
+ // and the runtime schema in channel.ts → configSchema.
25
+ configSchema: { type: "object", additionalProperties: false, properties: {} },
26
+ register(api: OpenClawPluginApi) {
27
+ setRuntime(api.runtime);
28
+ api.registerChannel({ plugin: wechatKfPlugin });
29
+ },
30
+ };
31
+
32
+ export default plugin;
@@ -0,0 +1,31 @@
1
+ {
2
+ "id": "wechat-kf",
3
+ "name": "WeChat Customer Service",
4
+ "description": "OpenClaw channel plugin for WeChat Customer Service (企业微信客服) via WeCom KF API",
5
+ "version": "0.1.0",
6
+ "channels": ["wechat-kf"],
7
+ "configSchema": {
8
+ "type": "object",
9
+ "properties": {
10
+ "enabled": { "type": "boolean" },
11
+ "corpId": { "type": "string", "description": "WeCom Corp ID (企业ID)" },
12
+ "appSecret": { "type": "string", "description": "Self-built app secret (应用密钥)" },
13
+ "token": { "type": "string", "description": "Webhook callback token" },
14
+ "encodingAESKey": { "type": "string", "description": "43-character base64 AES key", "minLength": 43, "maxLength": 43 },
15
+ "webhookPort": { "type": "integer", "minimum": 1, "maximum": 65535, "default": 9999 },
16
+ "webhookPath": { "type": "string", "default": "/wechat-kf" },
17
+ "dmPolicy": { "type": "string", "enum": ["open", "pairing", "allowlist", "disabled"], "default": "open" },
18
+ "allowFrom": { "type": "array", "items": { "type": "string" } }
19
+ },
20
+ "uiHints": {
21
+ "corpId": { "label": "Corp ID", "placeholder": "ww0123456789abcdef" },
22
+ "appSecret": { "label": "App Secret", "placeholder": "Your WeCom app secret", "sensitive": true },
23
+ "token": { "label": "Callback Token", "placeholder": "Webhook verification token", "sensitive": true },
24
+ "encodingAESKey": { "label": "Encoding AES Key", "placeholder": "43-character base64 key", "sensitive": true },
25
+ "webhookPort": { "label": "Webhook Port", "placeholder": "9999" },
26
+ "webhookPath": { "label": "Webhook Path", "placeholder": "/wechat-kf" },
27
+ "dmPolicy": { "label": "DM Policy", "placeholder": "open" },
28
+ "allowFrom": { "label": "Allowlist", "placeholder": "List of allowed external user IDs" }
29
+ }
30
+ }
31
+ }
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "@pawastation/wechat-kf",
3
+ "version": "0.1.1",
4
+ "description": "OpenClaw channel plugin for WeChat Customer Service (企业微信客服) — enables AI-powered customer service bots via WeCom KF API",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "pawaca",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/pawastation/wechat-kf.git"
11
+ },
12
+ "homepage": "https://github.com/pawastation/wechat-kf#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/pawastation/wechat-kf/issues"
15
+ },
16
+ "keywords": [
17
+ "openclaw",
18
+ "wechat",
19
+ "wecom",
20
+ "customer-service",
21
+ "chatbot",
22
+ "ai-assistant"
23
+ ],
24
+ "main": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js"
30
+ },
31
+ "./crypto": {
32
+ "types": "./dist/src/crypto.d.ts",
33
+ "import": "./dist/src/crypto.js"
34
+ },
35
+ "./api": {
36
+ "types": "./dist/src/api.d.ts",
37
+ "import": "./dist/src/api.js"
38
+ }
39
+ },
40
+ "files": [
41
+ "dist/",
42
+ "index.ts",
43
+ "openclaw.plugin.json",
44
+ "README.md",
45
+ "LICENSE"
46
+ ],
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "engines": {
51
+ "node": ">=18.0.0"
52
+ },
53
+ "devDependencies": {
54
+ "@biomejs/biome": "^2.4.2",
55
+ "@types/node": "^25.2.3",
56
+ "typescript": "^5.9.3",
57
+ "vitest": "^3.0.0"
58
+ },
59
+ "openclaw": {
60
+ "extensions": [
61
+ "./index.ts"
62
+ ],
63
+ "channel": {
64
+ "id": "wechat-kf",
65
+ "label": "WeChat KF",
66
+ "selectionLabel": "WeChat Customer Service (微信客服)",
67
+ "docsPath": "/channels/wechat-kf",
68
+ "docsLabel": "wechat-kf",
69
+ "blurb": "企业微信客服 API channel for WeChat users.",
70
+ "aliases": [
71
+ "wxkf"
72
+ ],
73
+ "order": 80
74
+ },
75
+ "install": {
76
+ "npmSpec": "@pawastation/wechat-kf",
77
+ "localPath": "extensions/wechat-kf",
78
+ "defaultChoice": "local"
79
+ }
80
+ },
81
+ "scripts": {
82
+ "build": "tsc",
83
+ "typecheck": "tsc --noEmit",
84
+ "test": "vitest run",
85
+ "test:watch": "vitest",
86
+ "lint": "biome check src/ index.ts",
87
+ "lint:fix": "biome check --write src/ index.ts",
88
+ "format": "biome format --write src/ index.ts",
89
+ "check": "biome check src/ index.ts"
90
+ }
91
+ }