@yaoqi10012/wechat-kf 0.3.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 (57) hide show
  1. package/LICENSE +21 -0
  2. package/dist/accounts.d.ts +38 -0
  3. package/dist/accounts.js +247 -0
  4. package/dist/accounts.js.map +1 -0
  5. package/dist/api.d.ts +58 -0
  6. package/dist/api.js +200 -0
  7. package/dist/api.js.map +1 -0
  8. package/dist/bot.d.ts +37 -0
  9. package/dist/bot.js +672 -0
  10. package/dist/bot.js.map +1 -0
  11. package/dist/channel.d.ts +14 -0
  12. package/dist/channel.js +320 -0
  13. package/dist/channel.js.map +1 -0
  14. package/dist/config-schema.d.ts +56 -0
  15. package/dist/config-schema.js +39 -0
  16. package/dist/config-schema.js.map +1 -0
  17. package/dist/constants.d.ts +41 -0
  18. package/dist/constants.js +51 -0
  19. package/dist/constants.js.map +1 -0
  20. package/dist/crypto.d.ts +18 -0
  21. package/dist/crypto.js +81 -0
  22. package/dist/crypto.js.map +1 -0
  23. package/dist/fs-utils.d.ts +7 -0
  24. package/dist/fs-utils.js +13 -0
  25. package/dist/fs-utils.js.map +1 -0
  26. package/dist/monitor.d.ts +31 -0
  27. package/dist/monitor.js +80 -0
  28. package/dist/monitor.js.map +1 -0
  29. package/dist/outbound.d.ts +32 -0
  30. package/dist/outbound.js +411 -0
  31. package/dist/outbound.js.map +1 -0
  32. package/dist/reply-dispatcher.d.ts +36 -0
  33. package/dist/reply-dispatcher.js +216 -0
  34. package/dist/reply-dispatcher.js.map +1 -0
  35. package/dist/runtime.d.ts +12 -0
  36. package/dist/runtime.js +23 -0
  37. package/dist/runtime.js.map +1 -0
  38. package/dist/send-utils.d.ts +52 -0
  39. package/dist/send-utils.js +217 -0
  40. package/dist/send-utils.js.map +1 -0
  41. package/dist/token.d.ts +8 -0
  42. package/dist/token.js +61 -0
  43. package/dist/token.js.map +1 -0
  44. package/dist/types.d.ts +236 -0
  45. package/dist/types.js +3 -0
  46. package/dist/types.js.map +1 -0
  47. package/dist/unicode-format.d.ts +26 -0
  48. package/dist/unicode-format.js +157 -0
  49. package/dist/unicode-format.js.map +1 -0
  50. package/dist/webhook.d.ts +22 -0
  51. package/dist/webhook.js +148 -0
  52. package/dist/webhook.js.map +1 -0
  53. package/dist/wechat-kf-directives.d.ts +157 -0
  54. package/dist/wechat-kf-directives.js +576 -0
  55. package/dist/wechat-kf-directives.js.map +1 -0
  56. package/openclaw.plugin.json +31 -0
  57. package/package.json +92 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 pawaca
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Account resolution for WeChat KF
3
+ *
4
+ * Accounts are dynamically discovered from webhook callbacks.
5
+ * Each openKfId becomes an independent accountId (like Telegram chat groups).
6
+ * Enterprise credentials (corpId, appSecret, token, encodingAESKey) are shared.
7
+ */
8
+ import type { OpenClawConfig } from "openclaw/plugin-sdk";
9
+ import type { ResolvedWechatKfAccount, WechatKfConfig } from "./types.js";
10
+ export declare function setStateDir(dir: string): void;
11
+ export declare function getChannelConfig(cfg: OpenClawConfig): WechatKfConfig;
12
+ /** Register a dynamically discovered kfid */
13
+ export declare function registerKfId(kfId: string): Promise<void>;
14
+ /** Get all known kfids */
15
+ export declare function getKnownKfIds(): string[];
16
+ /** Get all known kfids that are currently enabled */
17
+ export declare function getEnabledKfIds(): string[];
18
+ /** Check whether a kfid is enabled (not in the disabled set) */
19
+ export declare function isKfIdEnabled(kfId: string): boolean;
20
+ /** Disable a kfid (add to disabled set). Returns true if the state changed. */
21
+ export declare function disableKfId(kfId: string): Promise<boolean>;
22
+ /** Enable a previously disabled kfid. Returns true if the state changed. */
23
+ export declare function enableKfId(kfId: string): Promise<boolean>;
24
+ /**
25
+ * Delete a kfid entirely — removes from discovered set and adds to disabled set
26
+ * so it won't be re-activated if the webhook delivers it again before restart.
27
+ * Returns true if the kfid was known (and thus actually removed).
28
+ */
29
+ export declare function deleteKfId(kfId: string): Promise<boolean>;
30
+ /** Load persisted kfids from state dir */
31
+ export declare function loadKfIds(dir: string): Promise<void>;
32
+ export declare function listAccountIds(_cfg: OpenClawConfig): string[];
33
+ /**
34
+ * Reset all module-level mutable state.
35
+ * @internal Exposed for testing only — allows test isolation between runs.
36
+ */
37
+ export declare function _reset(): void;
38
+ export declare function resolveAccount(cfg: OpenClawConfig, accountId?: string): ResolvedWechatKfAccount;
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Account resolution for WeChat KF
3
+ *
4
+ * Accounts are dynamically discovered from webhook callbacks.
5
+ * Each openKfId becomes an independent accountId (like Telegram chat groups).
6
+ * Enterprise credentials (corpId, appSecret, token, encodingAESKey) are shared.
7
+ */
8
+ import { readFileSync } from "node:fs";
9
+ import { mkdir, readFile } from "node:fs/promises";
10
+ import { join } from "node:path";
11
+ import { CHANNEL_ID, DEFAULT_WEBHOOK_PATH, DISABLED_KFIDS_FILE, defaultStateDir, formatError, KFIDS_FILE, logTag, } from "./constants.js";
12
+ import { atomicWriteFile } from "./fs-utils.js";
13
+ import { getSharedContext } from "./monitor.js";
14
+ /** In-memory set of discovered kfids */
15
+ const discoveredKfIds = new Set();
16
+ /** In-memory set of disabled kfids (persisted to disk) */
17
+ const disabledKfIds = new Set();
18
+ let stateDir = null;
19
+ let kfIdsPreloaded = false;
20
+ /** Synchronously preload persisted kfIds so listAccountIds returns them before loadKfIds runs */
21
+ function preloadKfIdsSync() {
22
+ if (kfIdsPreloaded)
23
+ return;
24
+ kfIdsPreloaded = true;
25
+ const dir = stateDir ?? defaultStateDir();
26
+ try {
27
+ const data = readFileSync(join(dir, KFIDS_FILE), "utf8");
28
+ const ids = JSON.parse(data);
29
+ if (Array.isArray(ids)) {
30
+ for (const id of ids)
31
+ discoveredKfIds.add(id);
32
+ }
33
+ }
34
+ catch (err) {
35
+ if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) {
36
+ console.warn(`${logTag()} failed to preload kfids: ${formatError(err)}`);
37
+ }
38
+ }
39
+ try {
40
+ const data = readFileSync(join(dir, DISABLED_KFIDS_FILE), "utf8");
41
+ const ids = JSON.parse(data);
42
+ if (Array.isArray(ids)) {
43
+ for (const id of ids)
44
+ disabledKfIds.add(id);
45
+ }
46
+ }
47
+ catch (err) {
48
+ if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) {
49
+ console.warn(`${logTag()} failed to preload disabled kfids: ${formatError(err)}`);
50
+ }
51
+ }
52
+ }
53
+ export function setStateDir(dir) {
54
+ stateDir = dir;
55
+ }
56
+ export function getChannelConfig(cfg) {
57
+ return (cfg.channels?.[CHANNEL_ID] ?? {});
58
+ }
59
+ /** Register a dynamically discovered kfid */
60
+ export async function registerKfId(kfId) {
61
+ if (!kfId || discoveredKfIds.has(kfId))
62
+ return;
63
+ discoveredKfIds.add(kfId);
64
+ await persistKfIds();
65
+ }
66
+ /** Get all known kfids */
67
+ export function getKnownKfIds() {
68
+ return Array.from(discoveredKfIds);
69
+ }
70
+ /** Get all known kfids that are currently enabled */
71
+ export function getEnabledKfIds() {
72
+ return Array.from(discoveredKfIds).filter((id) => !disabledKfIds.has(id));
73
+ }
74
+ /** Check whether a kfid is enabled (not in the disabled set) */
75
+ export function isKfIdEnabled(kfId) {
76
+ return !disabledKfIds.has(resolveKfId(kfId));
77
+ }
78
+ /** Disable a kfid (add to disabled set). Returns true if the state changed. */
79
+ export async function disableKfId(kfId) {
80
+ if (!kfId)
81
+ return false;
82
+ const resolved = resolveKfId(kfId);
83
+ if (disabledKfIds.has(resolved))
84
+ return false;
85
+ disabledKfIds.add(resolved);
86
+ await persistDisabledKfIds();
87
+ return true;
88
+ }
89
+ /** Enable a previously disabled kfid. Returns true if the state changed. */
90
+ export async function enableKfId(kfId) {
91
+ if (!kfId)
92
+ return false;
93
+ const resolved = resolveKfId(kfId);
94
+ if (!disabledKfIds.has(resolved))
95
+ return false;
96
+ disabledKfIds.delete(resolved);
97
+ await persistDisabledKfIds();
98
+ return true;
99
+ }
100
+ /**
101
+ * Delete a kfid entirely — removes from discovered set and adds to disabled set
102
+ * so it won't be re-activated if the webhook delivers it again before restart.
103
+ * Returns true if the kfid was known (and thus actually removed).
104
+ */
105
+ export async function deleteKfId(kfId) {
106
+ if (!kfId)
107
+ return false;
108
+ const resolved = resolveKfId(kfId);
109
+ const wasKnown = discoveredKfIds.has(resolved);
110
+ discoveredKfIds.delete(resolved);
111
+ disabledKfIds.add(resolved);
112
+ await persistKfIds();
113
+ await persistDisabledKfIds();
114
+ return wasKnown;
115
+ }
116
+ /**
117
+ * Resolve a potentially lowercased kfId to its original-case form.
118
+ * Falls back to the input if no match is found in the discovered set.
119
+ */
120
+ function resolveKfId(kfId) {
121
+ // Direct match — fast path
122
+ if (discoveredKfIds.has(kfId) || disabledKfIds.has(kfId))
123
+ return kfId;
124
+ // Case-insensitive lookup in discovered set
125
+ for (const id of discoveredKfIds) {
126
+ if (id.toLowerCase() === kfId.toLowerCase())
127
+ return id;
128
+ }
129
+ // Case-insensitive lookup in disabled set
130
+ for (const id of disabledKfIds) {
131
+ if (id.toLowerCase() === kfId.toLowerCase())
132
+ return id;
133
+ }
134
+ return kfId;
135
+ }
136
+ /** Load persisted kfids from state dir */
137
+ export async function loadKfIds(dir) {
138
+ stateDir = dir;
139
+ kfIdsPreloaded = true;
140
+ try {
141
+ const data = await readFile(join(dir, KFIDS_FILE), "utf8");
142
+ const ids = JSON.parse(data);
143
+ if (Array.isArray(ids)) {
144
+ for (const id of ids)
145
+ discoveredKfIds.add(id);
146
+ }
147
+ }
148
+ catch (err) {
149
+ if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) {
150
+ console.warn(`${logTag()} failed to load kfids: ${formatError(err)}`);
151
+ }
152
+ }
153
+ try {
154
+ const data = await readFile(join(dir, DISABLED_KFIDS_FILE), "utf8");
155
+ const ids = JSON.parse(data);
156
+ if (Array.isArray(ids)) {
157
+ for (const id of ids)
158
+ disabledKfIds.add(id);
159
+ }
160
+ }
161
+ catch (err) {
162
+ if (!(err instanceof Error && "code" in err && err.code === "ENOENT")) {
163
+ console.warn(`${logTag()} failed to load disabled kfids: ${formatError(err)}`);
164
+ }
165
+ }
166
+ }
167
+ /** Persist kfids to state dir */
168
+ async function persistKfIds() {
169
+ if (!stateDir)
170
+ return;
171
+ try {
172
+ await mkdir(stateDir, { recursive: true });
173
+ await atomicWriteFile(join(stateDir, KFIDS_FILE), JSON.stringify(Array.from(discoveredKfIds)));
174
+ }
175
+ catch (err) {
176
+ getSharedContext()?.botCtx.log?.warn(`${logTag()} failed to persist kfids: ${formatError(err)}`);
177
+ }
178
+ }
179
+ /** Persist disabled kfids to state dir */
180
+ async function persistDisabledKfIds() {
181
+ if (!stateDir)
182
+ return;
183
+ try {
184
+ await mkdir(stateDir, { recursive: true });
185
+ await atomicWriteFile(join(stateDir, DISABLED_KFIDS_FILE), JSON.stringify(Array.from(disabledKfIds)));
186
+ }
187
+ catch (err) {
188
+ getSharedContext()?.botCtx.log?.warn(`${logTag()} failed to persist disabled kfids: ${formatError(err)}`);
189
+ }
190
+ }
191
+ export function listAccountIds(_cfg) {
192
+ // "default" is always first — represents enterprise-level shared infrastructure.
193
+ // Real kfIds follow. When no kfIds are discovered yet, returns ["default"].
194
+ preloadKfIdsSync();
195
+ const ids = getEnabledKfIds();
196
+ return ["default", ...ids];
197
+ }
198
+ /**
199
+ * Recover the original case-sensitive kfId from the normalized (lowercased) accountId.
200
+ * OpenClaw core normalizes accountIds to lowercase, but WeChat KF API requires
201
+ * the original case-sensitive openKfId.
202
+ */
203
+ function recoverOriginalKfId(normalizedId) {
204
+ if (normalizedId === "default")
205
+ return undefined;
206
+ // Look up the original-case kfId from our discovered set
207
+ for (const kfId of discoveredKfIds) {
208
+ if (kfId.toLowerCase() === normalizedId.toLowerCase())
209
+ return kfId;
210
+ }
211
+ // Fallback: return as-is (may fail if case matters)
212
+ return normalizedId;
213
+ }
214
+ /**
215
+ * Reset all module-level mutable state.
216
+ * @internal Exposed for testing only — allows test isolation between runs.
217
+ */
218
+ export function _reset() {
219
+ discoveredKfIds.clear();
220
+ disabledKfIds.clear();
221
+ stateDir = null;
222
+ kfIdsPreloaded = false;
223
+ }
224
+ export function resolveAccount(cfg, accountId) {
225
+ const config = getChannelConfig(cfg);
226
+ const id = accountId ?? "default";
227
+ const corpId = config.corpId;
228
+ const appSecret = config.appSecret;
229
+ const token = config.token;
230
+ const encodingAESKey = config.encodingAESKey;
231
+ const kfIdDisabled = id !== "default" && !isKfIdEnabled(id);
232
+ const enabled = kfIdDisabled ? false : (config.enabled ?? false);
233
+ const configured = !!(corpId && appSecret && token && encodingAESKey);
234
+ return {
235
+ accountId: id,
236
+ enabled,
237
+ configured,
238
+ corpId,
239
+ appSecret,
240
+ token,
241
+ encodingAESKey,
242
+ openKfId: recoverOriginalKfId(id),
243
+ webhookPath: config.webhookPath ?? DEFAULT_WEBHOOK_PATH,
244
+ config,
245
+ };
246
+ }
247
+ //# sourceMappingURL=accounts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accounts.js","sourceRoot":"","sources":["../src/accounts.ts"],"names":[],"mappings":"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"}
package/dist/api.d.ts ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * WeCom Customer Service API wrapper
3
+ */
4
+ import type { WechatKfSendMsgRequest, WechatKfSendMsgResponse, WechatKfSyncMsgRequest, WechatKfSyncMsgResponse, WechatMediaUploadResponse } from "./types.js";
5
+ /** Pull messages from WeChat KF */
6
+ export declare function syncMessages(corpId: string, appSecret: string, params: WechatKfSyncMsgRequest): Promise<WechatKfSyncMsgResponse>;
7
+ /** Send a text message to a WeChat user */
8
+ export declare function sendTextMessage(corpId: string, appSecret: string, toUser: string, openKfId: string, content: string): Promise<WechatKfSendMsgResponse>;
9
+ /** Download media file from WeChat */
10
+ export declare function downloadMedia(corpId: string, appSecret: string, mediaId: string): Promise<{
11
+ buffer: Buffer;
12
+ contentType: string;
13
+ }>;
14
+ type WechatMediaType = "image" | "voice" | "video" | "file";
15
+ /** Upload media file to WeChat */
16
+ export declare function uploadMedia(corpId: string, appSecret: string, type: WechatMediaType, buffer: Buffer, filename: string): Promise<WechatMediaUploadResponse>;
17
+ /** Send an image message to a WeChat user */
18
+ export declare function sendImageMessage(corpId: string, appSecret: string, toUser: string, openKfId: string, mediaId: string): Promise<WechatKfSendMsgResponse>;
19
+ /** Send a voice message to a WeChat user */
20
+ export declare function sendVoiceMessage(corpId: string, appSecret: string, toUser: string, openKfId: string, mediaId: string): Promise<WechatKfSendMsgResponse>;
21
+ /** Send a video message to a WeChat user */
22
+ export declare function sendVideoMessage(corpId: string, appSecret: string, toUser: string, openKfId: string, mediaId: string): Promise<WechatKfSendMsgResponse>;
23
+ /** Send a file message to a WeChat user */
24
+ export declare function sendFileMessage(corpId: string, appSecret: string, toUser: string, openKfId: string, mediaId: string): Promise<WechatKfSendMsgResponse>;
25
+ /** Send a link message to a WeChat user */
26
+ export declare function sendLinkMessage(corpId: string, appSecret: string, toUser: string, openKfId: string, link: {
27
+ title: string;
28
+ desc?: string;
29
+ url: string;
30
+ thumb_media_id: string;
31
+ }): Promise<WechatKfSendMsgResponse>;
32
+ /** Send a location message to a WeChat user */
33
+ export declare function sendLocationMessage(corpId: string, appSecret: string, toUser: string, openKfId: string, location: {
34
+ name?: string;
35
+ address?: string;
36
+ latitude: number;
37
+ longitude: number;
38
+ }): Promise<WechatKfSendMsgResponse>;
39
+ /** Send a miniprogram message to a WeChat user */
40
+ export declare function sendMiniprogramMessage(corpId: string, appSecret: string, toUser: string, openKfId: string, miniprogram: {
41
+ appid: string;
42
+ title?: string;
43
+ thumb_media_id: string;
44
+ pagepath: string;
45
+ }): Promise<WechatKfSendMsgResponse>;
46
+ /** Send a menu message to a WeChat user */
47
+ export declare function sendMsgMenuMessage(corpId: string, appSecret: string, toUser: string, openKfId: string, msgmenu: WechatKfSendMsgRequest["msgmenu"]): Promise<WechatKfSendMsgResponse>;
48
+ /** Send a business card message to a WeChat user */
49
+ export declare function sendBusinessCardMessage(corpId: string, appSecret: string, toUser: string, openKfId: string, businessCard: {
50
+ userid: string;
51
+ }): Promise<WechatKfSendMsgResponse>;
52
+ /** Send an acquisition link (获客链接) message to a WeChat user */
53
+ export declare function sendCaLinkMessage(corpId: string, appSecret: string, toUser: string, openKfId: string, caLink: {
54
+ link_url: string;
55
+ }): Promise<WechatKfSendMsgResponse>;
56
+ /** Send a raw message with arbitrary msgtype — for testing undocumented message types. */
57
+ export declare function sendRawMessage(corpId: string, appSecret: string, toUser: string, openKfId: string, msgtype: string, payload: Record<string, unknown>): Promise<WechatKfSendMsgResponse>;
58
+ export {};
package/dist/api.js ADDED
@@ -0,0 +1,200 @@
1
+ /**
2
+ * WeCom Customer Service API wrapper
3
+ */
4
+ import { extname } from "node:path";
5
+ import { API_POST_TIMEOUT_MS, logTag, MEDIA_TIMEOUT_MS, TOKEN_EXPIRED_CODES } from "./constants.js";
6
+ import { getSharedContext } from "./monitor.js";
7
+ import { clearAccessToken, getAccessToken } from "./token.js";
8
+ const MIME_MAP = {
9
+ ".jpg": "image/jpeg",
10
+ ".jpeg": "image/jpeg",
11
+ ".png": "image/png",
12
+ ".gif": "image/gif",
13
+ ".bmp": "image/bmp",
14
+ ".webp": "image/webp",
15
+ ".amr": "audio/amr",
16
+ ".mp3": "audio/mpeg",
17
+ ".wav": "audio/wav",
18
+ ".ogg": "audio/ogg",
19
+ ".silk": "audio/silk",
20
+ ".m4a": "audio/mp4",
21
+ ".aac": "audio/aac",
22
+ ".mp4": "video/mp4",
23
+ ".avi": "video/x-msvideo",
24
+ ".mov": "video/quicktime",
25
+ };
26
+ const BASE = "https://qyapi.weixin.qq.com/cgi-bin";
27
+ /**
28
+ * Check whether a WeCom API response indicates a business error.
29
+ * Successful responses have errcode === 0 or omit errcode entirely (undefined).
30
+ * Using a truthy check so that both 0 and undefined are treated as success.
31
+ */
32
+ function hasApiError(errcode) {
33
+ return !!errcode;
34
+ }
35
+ async function apiPost(path, token, body) {
36
+ const resp = await fetch(`${BASE}${path}?access_token=${token}`, {
37
+ method: "POST",
38
+ headers: { "Content-Type": "application/json" },
39
+ body: JSON.stringify(body),
40
+ signal: AbortSignal.timeout(API_POST_TIMEOUT_MS),
41
+ });
42
+ if (!resp.ok) {
43
+ const text = await resp.text().catch(() => "");
44
+ throw new Error(`${logTag()} API ${path} HTTP ${resp.status}: ${text.slice(0, 200)}`);
45
+ }
46
+ return (await resp.json());
47
+ }
48
+ /**
49
+ * Call apiPost with automatic token refresh on token-expired errors.
50
+ * Detects errcode 40014/42001/40001, clears the cached token, fetches a
51
+ * new one and retries exactly once.
52
+ */
53
+ async function apiPostWithTokenRetry(path, corpId, appSecret, body) {
54
+ let token = await getAccessToken(corpId, appSecret);
55
+ const data = await apiPost(path, token, body);
56
+ const result = data;
57
+ if (typeof result.errcode === "number" && TOKEN_EXPIRED_CODES.has(result.errcode)) {
58
+ getSharedContext()?.botCtx.log?.info(`${logTag()} token expired (errcode=${result.errcode}), refreshing and retrying ${path}`);
59
+ clearAccessToken(corpId, appSecret);
60
+ token = await getAccessToken(corpId, appSecret);
61
+ return apiPost(path, token, body);
62
+ }
63
+ return data;
64
+ }
65
+ /** Pull messages from WeChat KF */
66
+ export async function syncMessages(corpId, appSecret, params) {
67
+ const data = await apiPostWithTokenRetry("/kf/sync_msg", corpId, appSecret, params);
68
+ if (hasApiError(data.errcode)) {
69
+ throw new Error(`${logTag()} sync_msg failed: ${data.errcode} ${data.errmsg}`);
70
+ }
71
+ return data;
72
+ }
73
+ async function sendMessage(corpId, appSecret, toUser, openKfId, msgtype, payload) {
74
+ const body = {
75
+ touser: toUser,
76
+ open_kfid: openKfId,
77
+ msgtype,
78
+ ...payload,
79
+ };
80
+ const data = await apiPostWithTokenRetry("/kf/send_msg", corpId, appSecret, body);
81
+ if (hasApiError(data.errcode)) {
82
+ throw new Error(`${logTag()} send_msg failed: ${data.errcode} ${data.errmsg}`);
83
+ }
84
+ return data;
85
+ }
86
+ /** Send a text message to a WeChat user */
87
+ export function sendTextMessage(corpId, appSecret, toUser, openKfId, content) {
88
+ return sendMessage(corpId, appSecret, toUser, openKfId, "text", { text: { content } });
89
+ }
90
+ /** Download media file from WeChat */
91
+ export async function downloadMedia(corpId, appSecret, mediaId) {
92
+ const attemptDownload = async (token) => {
93
+ const resp = await fetch(`${BASE}/media/get?access_token=${token}&media_id=${mediaId}`, {
94
+ signal: AbortSignal.timeout(MEDIA_TIMEOUT_MS),
95
+ });
96
+ if (!resp.ok) {
97
+ throw new Error(`${logTag()} download media failed: ${resp.status} ${resp.statusText}`);
98
+ }
99
+ const contentType = resp.headers.get("content-type") ?? "";
100
+ if (contentType.includes("application/json")) {
101
+ const data = (await resp.json());
102
+ return { buffer: Buffer.alloc(0), contentType, errcode: data.errcode, errmsg: data.errmsg };
103
+ }
104
+ return { buffer: Buffer.from(await resp.arrayBuffer()), contentType };
105
+ };
106
+ let token = await getAccessToken(corpId, appSecret);
107
+ const result = await attemptDownload(token);
108
+ if (result.errcode !== undefined) {
109
+ // Token-expired error: clear and retry once
110
+ if (TOKEN_EXPIRED_CODES.has(result.errcode)) {
111
+ getSharedContext()?.botCtx.log?.info(`${logTag()} token expired (errcode=${result.errcode}), refreshing and retrying media download`);
112
+ clearAccessToken(corpId, appSecret);
113
+ token = await getAccessToken(corpId, appSecret);
114
+ const retry = await attemptDownload(token);
115
+ if (retry.errcode !== undefined) {
116
+ throw new Error(`${logTag()} download media failed: ${retry.errcode} ${retry.errmsg}`);
117
+ }
118
+ return { buffer: retry.buffer, contentType: retry.contentType };
119
+ }
120
+ throw new Error(`${logTag()} download media failed: ${result.errcode} ${result.errmsg}`);
121
+ }
122
+ return { buffer: result.buffer, contentType: result.contentType };
123
+ }
124
+ /** Upload media file to WeChat */
125
+ export async function uploadMedia(corpId, appSecret, type, buffer, filename) {
126
+ const doUpload = async (token) => {
127
+ const formData = new FormData();
128
+ const ext = extname(filename).toLowerCase();
129
+ const mime = MIME_MAP[ext] ?? "application/octet-stream";
130
+ const blob = new Blob([new Uint8Array(buffer)], { type: mime });
131
+ formData.append("media", blob, filename);
132
+ const resp = await fetch(`${BASE}/media/upload?access_token=${token}&type=${type}`, {
133
+ method: "POST",
134
+ body: formData,
135
+ signal: AbortSignal.timeout(MEDIA_TIMEOUT_MS),
136
+ });
137
+ if (!resp.ok) {
138
+ const text = await resp.text().catch(() => "");
139
+ throw new Error(`${logTag()} upload media HTTP ${resp.status}: ${text.slice(0, 200)}`);
140
+ }
141
+ return (await resp.json());
142
+ };
143
+ let token = await getAccessToken(corpId, appSecret);
144
+ let data = await doUpload(token);
145
+ if (TOKEN_EXPIRED_CODES.has(data.errcode)) {
146
+ getSharedContext()?.botCtx.log?.info(`${logTag()} token expired (errcode=${data.errcode}), refreshing and retrying media upload`);
147
+ clearAccessToken(corpId, appSecret);
148
+ token = await getAccessToken(corpId, appSecret);
149
+ data = await doUpload(token);
150
+ }
151
+ if (hasApiError(data.errcode)) {
152
+ throw new Error(`${logTag()} upload media failed: ${data.errcode} ${data.errmsg}`);
153
+ }
154
+ return data;
155
+ }
156
+ /** Send an image message to a WeChat user */
157
+ export function sendImageMessage(corpId, appSecret, toUser, openKfId, mediaId) {
158
+ return sendMessage(corpId, appSecret, toUser, openKfId, "image", { image: { media_id: mediaId } });
159
+ }
160
+ /** Send a voice message to a WeChat user */
161
+ export function sendVoiceMessage(corpId, appSecret, toUser, openKfId, mediaId) {
162
+ return sendMessage(corpId, appSecret, toUser, openKfId, "voice", { voice: { media_id: mediaId } });
163
+ }
164
+ /** Send a video message to a WeChat user */
165
+ export function sendVideoMessage(corpId, appSecret, toUser, openKfId, mediaId) {
166
+ return sendMessage(corpId, appSecret, toUser, openKfId, "video", { video: { media_id: mediaId } });
167
+ }
168
+ /** Send a file message to a WeChat user */
169
+ export function sendFileMessage(corpId, appSecret, toUser, openKfId, mediaId) {
170
+ return sendMessage(corpId, appSecret, toUser, openKfId, "file", { file: { media_id: mediaId } });
171
+ }
172
+ /** Send a link message to a WeChat user */
173
+ export function sendLinkMessage(corpId, appSecret, toUser, openKfId, link) {
174
+ return sendMessage(corpId, appSecret, toUser, openKfId, "link", { link });
175
+ }
176
+ /** Send a location message to a WeChat user */
177
+ export function sendLocationMessage(corpId, appSecret, toUser, openKfId, location) {
178
+ return sendMessage(corpId, appSecret, toUser, openKfId, "location", { location });
179
+ }
180
+ /** Send a miniprogram message to a WeChat user */
181
+ export function sendMiniprogramMessage(corpId, appSecret, toUser, openKfId, miniprogram) {
182
+ return sendMessage(corpId, appSecret, toUser, openKfId, "miniprogram", { miniprogram });
183
+ }
184
+ /** Send a menu message to a WeChat user */
185
+ export function sendMsgMenuMessage(corpId, appSecret, toUser, openKfId, msgmenu) {
186
+ return sendMessage(corpId, appSecret, toUser, openKfId, "msgmenu", { msgmenu });
187
+ }
188
+ /** Send a business card message to a WeChat user */
189
+ export function sendBusinessCardMessage(corpId, appSecret, toUser, openKfId, businessCard) {
190
+ return sendMessage(corpId, appSecret, toUser, openKfId, "business_card", { business_card: businessCard });
191
+ }
192
+ /** Send an acquisition link (获客链接) message to a WeChat user */
193
+ export function sendCaLinkMessage(corpId, appSecret, toUser, openKfId, caLink) {
194
+ return sendMessage(corpId, appSecret, toUser, openKfId, "ca_link", { ca_link: caLink });
195
+ }
196
+ /** Send a raw message with arbitrary msgtype — for testing undocumented message types. */
197
+ export function sendRawMessage(corpId, appSecret, toUser, openKfId, msgtype, payload) {
198
+ return sendMessage(corpId, appSecret, toUser, openKfId, msgtype, payload);
199
+ }
200
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACpG,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAS9D,MAAM,QAAQ,GAAG;IACf,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,iBAAiB;IACzB,MAAM,EAAE,iBAAiB;CACgB,CAAC;AAE5C,MAAM,IAAI,GAAG,qCAAqC,CAAC;AAEnD;;;;GAIG;AACH,SAAS,WAAW,CAAC,OAA2B;IAC9C,OAAO,CAAC,CAAC,OAAO,CAAC;AACnB,CAAC;AAED,KAAK,UAAU,OAAO,CAAI,IAAY,EAAE,KAAa,EAAE,IAAa;IAClE,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,GAAG,IAAI,iBAAiB,KAAK,EAAE,EAAE;QAC/D,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;QAC1B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,mBAAmB,CAAC;KACjD,CAAC,CAAC;IACH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,IAAI,SAAS,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAM,CAAC;AAClC,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,qBAAqB,CAAI,IAAY,EAAE,MAAc,EAAE,SAAiB,EAAE,IAAa;IACpG,IAAI,KAAK,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAI,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAEjD,MAAM,MAAM,GAAG,IAA+B,CAAC;IAC/C,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,mBAAmB,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAClF,gBAAgB,EAAE,EAAE,MAAM,CAAC,GAAG,EAAE,IAAI,CAClC,GAAG,MAAM,EAAE,2BAA2B,MAAM,CAAC,OAAO,8BAA8B,IAAI,EAAE,CACzF,CAAC;QACF,gBAAgB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QACpC,KAAK,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAChD,OAAO,OAAO,CAAI,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,mCAAmC;AACnC,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAc,EACd,SAAiB,EACjB,MAA8B;IAE9B,MAAM,IAAI,GAAG,MAAM,qBAAqB,CAA0B,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IAC7G,IAAI,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,EAAE,qBAAqB,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAiBD,KAAK,UAAU,WAAW,CACxB,MAAc,EACd,SAAiB,EACjB,MAAc,EACd,QAAgB,EAChB,OAAsB,EACtB,OAAgC;IAEhC,MAAM,IAAI,GAA2B;QACnC,MAAM,EAAE,MAAM;QACd,SAAS,EAAE,QAAQ;QACnB,OAAO;QACP,GAAG,OAAO;KACX,CAAC;IACF,MAAM,IAAI,GAAG,MAAM,qBAAqB,CAA0B,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IAC3G,IAAI,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,EAAE,qBAAqB,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,eAAe,CAC7B,MAAc,EACd,SAAiB,EACjB,MAAc,EACd,QAAgB,EAChB,OAAe;IAEf,OAAO,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;AACzF,CAAC;AAED,sCAAsC;AACtC,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAc,EACd,SAAiB,EACjB,OAAe;IAEf,MAAM,eAAe,GAAG,KAAK,EAC3B,KAAa,EACwE,EAAE;QACvF,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,2BAA2B,KAAK,aAAa,OAAO,EAAE,EAAE;YACtF,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,gBAAgB,CAAC;SAC9C,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,EAAE,2BAA2B,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QAC1F,CAAC;QACD,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;QAC3D,IAAI,WAAW,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;YAC7C,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAwC,CAAC;YACxE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;QAC9F,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC;IACxE,CAAC,CAAC;IAEF,IAAI,KAAK,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,KAAK,CAAC,CAAC;IAE5C,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACjC,4CAA4C;QAC5C,IAAI,mBAAmB,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5C,gBAAgB,EAAE,EAAE,MAAM,CAAC,GAAG,EAAE,IAAI,CAClC,GAAG,MAAM,EAAE,2BAA2B,MAAM,CAAC,OAAO,2CAA2C,CAChG,CAAC;YACF,gBAAgB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;YACpC,KAAK,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;YAChD,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,KAAK,CAAC,CAAC;YAC3C,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;gBAChC,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,EAAE,2BAA2B,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;YACzF,CAAC;YACD,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;QAClE,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,EAAE,2BAA2B,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3F,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC;AACpE,CAAC;AAMD,kCAAkC;AAClC,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAc,EACd,SAAiB,EACjB,IAAqB,EACrB,MAAc,EACd,QAAgB;IAEhB,MAAM,QAAQ,GAAG,KAAK,EAAE,KAAa,EAAsC,EAAE;QAC3E,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;QAChC,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAA2B,CAAC;QACrE,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC;QACzD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAChE,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QAEzC,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,8BAA8B,KAAK,SAAS,IAAI,EAAE,EAAE;YAClF,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,gBAAgB,CAAC;SAC9C,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAC/C,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,EAAE,sBAAsB,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QACzF,CAAC;QACD,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAA8B,CAAC;IAC1D,CAAC,CAAC;IAEF,IAAI,KAAK,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACpD,IAAI,IAAI,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAC;IAEjC,IAAI,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1C,gBAAgB,EAAE,EAAE,MAAM,CAAC,GAAG,EAAE,IAAI,CAClC,GAAG,MAAM,EAAE,2BAA2B,IAAI,CAAC,OAAO,yCAAyC,CAC5F,CAAC;QACF,gBAAgB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QACpC,KAAK,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAChD,IAAI,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,EAAE,yBAAyB,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACrF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,gBAAgB,CAC9B,MAAc,EACd,SAAiB,EACjB,MAAc,EACd,QAAgB,EAChB,OAAe;IAEf,OAAO,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;AACrG,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,gBAAgB,CAC9B,MAAc,EACd,SAAiB,EACjB,MAAc,EACd,QAAgB,EAChB,OAAe;IAEf,OAAO,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;AACrG,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,gBAAgB,CAC9B,MAAc,EACd,SAAiB,EACjB,MAAc,EACd,QAAgB,EAChB,OAAe;IAEf,OAAO,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;AACrG,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,eAAe,CAC7B,MAAc,EACd,SAAiB,EACjB,MAAc,EACd,QAAgB,EAChB,OAAe;IAEf,OAAO,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;AACnG,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,eAAe,CAC7B,MAAc,EACd,SAAiB,EACjB,MAAc,EACd,QAAgB,EAChB,IAA2E;IAE3E,OAAO,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;AAC5E,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,mBAAmB,CACjC,MAAc,EACd,SAAiB,EACjB,MAAc,EACd,QAAgB,EAChB,QAAkF;IAElF,OAAO,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;AACpF,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,sBAAsB,CACpC,MAAc,EACd,SAAiB,EACjB,MAAc,EACd,QAAgB,EAChB,WAAwF;IAExF,OAAO,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC;AAC1F,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,kBAAkB,CAChC,MAAc,EACd,SAAiB,EACjB,MAAc,EACd,QAAgB,EAChB,OAA0C;IAE1C,OAAO,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;AAClF,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,uBAAuB,CACrC,MAAc,EACd,SAAiB,EACjB,MAAc,EACd,QAAgB,EAChB,YAAgC;IAEhC,OAAO,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC,CAAC;AAC5G,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,iBAAiB,CAC/B,MAAc,EACd,SAAiB,EACjB,MAAc,EACd,QAAgB,EAChB,MAA4B;IAE5B,OAAO,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;AAC1F,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,cAAc,CAC5B,MAAc,EACd,SAAiB,EACjB,MAAc,EACd,QAAgB,EAChB,OAAe,EACf,OAAgC;IAEhC,OAAO,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAwB,EAAE,OAAO,CAAC,CAAC;AAC7F,CAAC"}
package/dist/bot.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Message processing — pull messages via sync_msg and dispatch to OpenClaw agent.
3
+ *
4
+ * Architecture:
5
+ * - Each openKfId is an independent account with its own cursor and session
6
+ * - sync_msg is called with open_kfid filter to only pull messages for that kf account
7
+ * - Plugin layer: download media, save via MediaPaths/MediaTypes
8
+ * - OpenClaw runner: handles media understanding (transcription, vision, etc.)
9
+ */
10
+ import type { ChannelLogSink, OpenClawConfig } from "openclaw/plugin-sdk";
11
+ import type { ResolvedWechatKfAccount, WechatKfMessage } from "./types.js";
12
+ /** Minimal runtime shape used only for error logging in the reply dispatcher. */
13
+ export type RuntimeErrorLogger = {
14
+ error?: (...args: unknown[]) => void;
15
+ [key: string]: unknown;
16
+ };
17
+ export type BotContext = {
18
+ cfg: OpenClawConfig;
19
+ runtime?: RuntimeErrorLogger;
20
+ stateDir: string;
21
+ log?: ChannelLogSink;
22
+ };
23
+ declare function isDuplicate(msgid: string): boolean;
24
+ /** Exposed for testing only — do not use in production code. */
25
+ export declare const _testing: {
26
+ kfLocks: Map<string, Promise<void>>;
27
+ processedMsgIds: Set<string>;
28
+ isDuplicate: typeof isDuplicate;
29
+ DEDUP_MAX_SIZE: number;
30
+ handleEvent: typeof handleEvent;
31
+ drainToLatestCursor: typeof drainToLatestCursor;
32
+ resetState(): void;
33
+ };
34
+ declare function handleEvent(ctx: BotContext, _account: ResolvedWechatKfAccount, msg: WechatKfMessage): Promise<void>;
35
+ declare function drainToLatestCursor(corpId: string, appSecret: string, openKfId: string, syncToken: string, stateDir: string, log?: ChannelLogSink): Promise<void>;
36
+ export declare function handleWebhookEvent(ctx: BotContext, openKfId: string, syncToken: string): Promise<void>;
37
+ export {};