@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.
- package/LICENSE +21 -0
- package/README.md +291 -0
- package/README.zh-CN.md +401 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/src/accounts.d.ts +37 -0
- package/dist/src/accounts.js +205 -0
- package/dist/src/accounts.js.map +1 -0
- package/dist/src/api.d.ts +29 -0
- package/dist/src/api.js +172 -0
- package/dist/src/api.js.map +1 -0
- package/dist/src/bot.d.ts +35 -0
- package/dist/src/bot.js +379 -0
- package/dist/src/bot.js.map +1 -0
- package/dist/src/channel.d.ts +113 -0
- package/dist/src/channel.js +183 -0
- package/dist/src/channel.js.map +1 -0
- package/dist/src/chunk-utils.d.ts +18 -0
- package/dist/src/chunk-utils.js +58 -0
- package/dist/src/chunk-utils.js.map +1 -0
- package/dist/src/config-schema.d.ts +56 -0
- package/dist/src/config-schema.js +38 -0
- package/dist/src/config-schema.js.map +1 -0
- package/dist/src/constants.d.ts +19 -0
- package/dist/src/constants.js +20 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/crypto.d.ts +18 -0
- package/dist/src/crypto.js +80 -0
- package/dist/src/crypto.js.map +1 -0
- package/dist/src/fs-utils.d.ts +7 -0
- package/dist/src/fs-utils.js +13 -0
- package/dist/src/fs-utils.js.map +1 -0
- package/dist/src/monitor.d.ts +18 -0
- package/dist/src/monitor.js +131 -0
- package/dist/src/monitor.js.map +1 -0
- package/dist/src/outbound.d.ts +66 -0
- package/dist/src/outbound.js +234 -0
- package/dist/src/outbound.js.map +1 -0
- package/dist/src/reply-dispatcher.d.ts +40 -0
- package/dist/src/reply-dispatcher.js +120 -0
- package/dist/src/reply-dispatcher.js.map +1 -0
- package/dist/src/runtime.d.ts +130 -0
- package/dist/src/runtime.js +22 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/send-utils.d.ts +30 -0
- package/dist/src/send-utils.js +89 -0
- package/dist/src/send-utils.js.map +1 -0
- package/dist/src/send.d.ts +7 -0
- package/dist/src/send.js +13 -0
- package/dist/src/send.js.map +1 -0
- package/dist/src/token.d.ts +8 -0
- package/dist/src/token.js +57 -0
- package/dist/src/token.js.map +1 -0
- package/dist/src/types.d.ts +173 -0
- package/dist/src/types.js +3 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/unicode-format.d.ts +26 -0
- package/dist/src/unicode-format.js +157 -0
- package/dist/src/unicode-format.js.map +1 -0
- package/dist/src/webhook.d.ts +22 -0
- package/dist/src/webhook.js +138 -0
- package/dist/src/webhook.js.map +1 -0
- package/dist/src/wechat-kf-directives.d.ts +34 -0
- package/dist/src/wechat-kf-directives.js +65 -0
- package/dist/src/wechat-kf-directives.js.map +1 -0
- package/index.ts +32 -0
- package/openclaw.plugin.json +31 -0
- package/package.json +91 -0
|
@@ -0,0 +1,205 @@
|
|
|
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 { mkdir, readFile } from "node:fs/promises";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { atomicWriteFile } from "./fs-utils.js";
|
|
11
|
+
const DEFAULT_PORT = 9999;
|
|
12
|
+
const DEFAULT_PATH = "/wechat-kf";
|
|
13
|
+
/** In-memory set of discovered kfids */
|
|
14
|
+
const discoveredKfIds = new Set();
|
|
15
|
+
/** In-memory set of disabled kfids (persisted to disk) */
|
|
16
|
+
const disabledKfIds = new Set();
|
|
17
|
+
let stateDir = null;
|
|
18
|
+
export function setStateDir(dir) {
|
|
19
|
+
stateDir = dir;
|
|
20
|
+
}
|
|
21
|
+
export function getChannelConfig(cfg) {
|
|
22
|
+
return (cfg.channels?.["wechat-kf"] ?? {});
|
|
23
|
+
}
|
|
24
|
+
/** Register a dynamically discovered kfid */
|
|
25
|
+
export async function registerKfId(kfId) {
|
|
26
|
+
if (!kfId || discoveredKfIds.has(kfId))
|
|
27
|
+
return;
|
|
28
|
+
discoveredKfIds.add(kfId);
|
|
29
|
+
await persistKfIds();
|
|
30
|
+
}
|
|
31
|
+
/** Get all known kfids */
|
|
32
|
+
export function getKnownKfIds() {
|
|
33
|
+
return Array.from(discoveredKfIds);
|
|
34
|
+
}
|
|
35
|
+
/** Get all known kfids that are currently enabled */
|
|
36
|
+
export function getEnabledKfIds() {
|
|
37
|
+
return Array.from(discoveredKfIds).filter((id) => !disabledKfIds.has(id));
|
|
38
|
+
}
|
|
39
|
+
/** Check whether a kfid is enabled (not in the disabled set) */
|
|
40
|
+
export function isKfIdEnabled(kfId) {
|
|
41
|
+
return !disabledKfIds.has(resolveKfId(kfId));
|
|
42
|
+
}
|
|
43
|
+
/** Disable a kfid (add to disabled set). Returns true if the state changed. */
|
|
44
|
+
export async function disableKfId(kfId) {
|
|
45
|
+
if (!kfId)
|
|
46
|
+
return false;
|
|
47
|
+
const resolved = resolveKfId(kfId);
|
|
48
|
+
if (disabledKfIds.has(resolved))
|
|
49
|
+
return false;
|
|
50
|
+
disabledKfIds.add(resolved);
|
|
51
|
+
await persistDisabledKfIds();
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
/** Enable a previously disabled kfid. Returns true if the state changed. */
|
|
55
|
+
export async function enableKfId(kfId) {
|
|
56
|
+
if (!kfId)
|
|
57
|
+
return false;
|
|
58
|
+
const resolved = resolveKfId(kfId);
|
|
59
|
+
if (!disabledKfIds.has(resolved))
|
|
60
|
+
return false;
|
|
61
|
+
disabledKfIds.delete(resolved);
|
|
62
|
+
await persistDisabledKfIds();
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Delete a kfid entirely — removes from discovered set and adds to disabled set
|
|
67
|
+
* so it won't be re-activated if the webhook delivers it again before restart.
|
|
68
|
+
* Returns true if the kfid was known (and thus actually removed).
|
|
69
|
+
*/
|
|
70
|
+
export async function deleteKfId(kfId) {
|
|
71
|
+
if (!kfId)
|
|
72
|
+
return false;
|
|
73
|
+
const resolved = resolveKfId(kfId);
|
|
74
|
+
const wasKnown = discoveredKfIds.has(resolved);
|
|
75
|
+
discoveredKfIds.delete(resolved);
|
|
76
|
+
disabledKfIds.add(resolved);
|
|
77
|
+
await persistKfIds();
|
|
78
|
+
await persistDisabledKfIds();
|
|
79
|
+
return wasKnown;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Resolve a potentially lowercased kfId to its original-case form.
|
|
83
|
+
* Falls back to the input if no match is found in the discovered set.
|
|
84
|
+
*/
|
|
85
|
+
function resolveKfId(kfId) {
|
|
86
|
+
// Direct match — fast path
|
|
87
|
+
if (discoveredKfIds.has(kfId) || disabledKfIds.has(kfId))
|
|
88
|
+
return kfId;
|
|
89
|
+
// Case-insensitive lookup in discovered set
|
|
90
|
+
for (const id of discoveredKfIds) {
|
|
91
|
+
if (id.toLowerCase() === kfId.toLowerCase())
|
|
92
|
+
return id;
|
|
93
|
+
}
|
|
94
|
+
// Case-insensitive lookup in disabled set
|
|
95
|
+
for (const id of disabledKfIds) {
|
|
96
|
+
if (id.toLowerCase() === kfId.toLowerCase())
|
|
97
|
+
return id;
|
|
98
|
+
}
|
|
99
|
+
return kfId;
|
|
100
|
+
}
|
|
101
|
+
/** Load persisted kfids from state dir */
|
|
102
|
+
export async function loadKfIds(dir) {
|
|
103
|
+
stateDir = dir;
|
|
104
|
+
try {
|
|
105
|
+
const data = await readFile(join(dir, "wechat-kf-kfids.json"), "utf8");
|
|
106
|
+
const ids = JSON.parse(data);
|
|
107
|
+
if (Array.isArray(ids)) {
|
|
108
|
+
for (const id of ids)
|
|
109
|
+
discoveredKfIds.add(id);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// No persisted state yet, that's fine
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const data = await readFile(join(dir, "wechat-kf-disabled-kfids.json"), "utf8");
|
|
117
|
+
const ids = JSON.parse(data);
|
|
118
|
+
if (Array.isArray(ids)) {
|
|
119
|
+
for (const id of ids)
|
|
120
|
+
disabledKfIds.add(id);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// No persisted disabled state yet, that's fine
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Persist kfids to state dir */
|
|
128
|
+
async function persistKfIds() {
|
|
129
|
+
if (!stateDir)
|
|
130
|
+
return;
|
|
131
|
+
try {
|
|
132
|
+
await mkdir(stateDir, { recursive: true });
|
|
133
|
+
await atomicWriteFile(join(stateDir, "wechat-kf-kfids.json"), JSON.stringify(Array.from(discoveredKfIds)));
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// Best effort
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Persist disabled kfids to state dir */
|
|
140
|
+
async function persistDisabledKfIds() {
|
|
141
|
+
if (!stateDir)
|
|
142
|
+
return;
|
|
143
|
+
try {
|
|
144
|
+
await mkdir(stateDir, { recursive: true });
|
|
145
|
+
await atomicWriteFile(join(stateDir, "wechat-kf-disabled-kfids.json"), JSON.stringify(Array.from(disabledKfIds)));
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Best effort
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export function listAccountIds(_cfg) {
|
|
152
|
+
// Return discovered kfids as account ids, excluding disabled ones
|
|
153
|
+
const ids = getEnabledKfIds();
|
|
154
|
+
return ids.length > 0 ? ids : ["default"];
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Recover the original case-sensitive kfId from the normalized (lowercased) accountId.
|
|
158
|
+
* OpenClaw core normalizes accountIds to lowercase, but WeChat KF API requires
|
|
159
|
+
* the original case-sensitive openKfId.
|
|
160
|
+
*/
|
|
161
|
+
function recoverOriginalKfId(normalizedId) {
|
|
162
|
+
if (normalizedId === "default")
|
|
163
|
+
return undefined;
|
|
164
|
+
// Look up the original-case kfId from our discovered set
|
|
165
|
+
for (const kfId of discoveredKfIds) {
|
|
166
|
+
if (kfId.toLowerCase() === normalizedId.toLowerCase())
|
|
167
|
+
return kfId;
|
|
168
|
+
}
|
|
169
|
+
// Fallback: return as-is (may fail if case matters)
|
|
170
|
+
return normalizedId;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Reset all module-level mutable state.
|
|
174
|
+
* @internal Exposed for testing only — allows test isolation between runs.
|
|
175
|
+
*/
|
|
176
|
+
export function _reset() {
|
|
177
|
+
discoveredKfIds.clear();
|
|
178
|
+
disabledKfIds.clear();
|
|
179
|
+
stateDir = null;
|
|
180
|
+
}
|
|
181
|
+
export function resolveAccount(cfg, accountId) {
|
|
182
|
+
const config = getChannelConfig(cfg);
|
|
183
|
+
const id = accountId ?? "default";
|
|
184
|
+
const corpId = config.corpId;
|
|
185
|
+
const appSecret = config.appSecret;
|
|
186
|
+
const token = config.token;
|
|
187
|
+
const encodingAESKey = config.encodingAESKey;
|
|
188
|
+
const kfIdDisabled = id !== "default" && !isKfIdEnabled(id);
|
|
189
|
+
const enabled = kfIdDisabled ? false : (config.enabled ?? false);
|
|
190
|
+
const configured = !!(corpId && appSecret && token && encodingAESKey);
|
|
191
|
+
return {
|
|
192
|
+
accountId: id,
|
|
193
|
+
enabled,
|
|
194
|
+
configured,
|
|
195
|
+
corpId,
|
|
196
|
+
appSecret,
|
|
197
|
+
token,
|
|
198
|
+
encodingAESKey,
|
|
199
|
+
openKfId: recoverOriginalKfId(id),
|
|
200
|
+
webhookPort: config.webhookPort ?? DEFAULT_PORT,
|
|
201
|
+
webhookPath: config.webhookPath ?? DEFAULT_PATH,
|
|
202
|
+
config,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=accounts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accounts.js","sourceRoot":"","sources":["../../src/accounts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeCom Customer Service API wrapper
|
|
3
|
+
*/
|
|
4
|
+
import type { 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<Buffer>;
|
|
11
|
+
type WechatMediaType = "image" | "voice" | "video" | "file";
|
|
12
|
+
/** Upload media file to WeChat */
|
|
13
|
+
export declare function uploadMedia(corpId: string, appSecret: string, type: WechatMediaType, buffer: Buffer, filename: string): Promise<WechatMediaUploadResponse>;
|
|
14
|
+
/** Send an image message to a WeChat user */
|
|
15
|
+
export declare function sendImageMessage(corpId: string, appSecret: string, toUser: string, openKfId: string, mediaId: string): Promise<WechatKfSendMsgResponse>;
|
|
16
|
+
/** Send a voice message to a WeChat user */
|
|
17
|
+
export declare function sendVoiceMessage(corpId: string, appSecret: string, toUser: string, openKfId: string, mediaId: string): Promise<WechatKfSendMsgResponse>;
|
|
18
|
+
/** Send a video message to a WeChat user */
|
|
19
|
+
export declare function sendVideoMessage(corpId: string, appSecret: string, toUser: string, openKfId: string, mediaId: string): Promise<WechatKfSendMsgResponse>;
|
|
20
|
+
/** Send a file message to a WeChat user */
|
|
21
|
+
export declare function sendFileMessage(corpId: string, appSecret: string, toUser: string, openKfId: string, mediaId: string): Promise<WechatKfSendMsgResponse>;
|
|
22
|
+
/** Send a link message to a WeChat user */
|
|
23
|
+
export declare function sendLinkMessage(corpId: string, appSecret: string, toUser: string, openKfId: string, link: {
|
|
24
|
+
title: string;
|
|
25
|
+
desc?: string;
|
|
26
|
+
url: string;
|
|
27
|
+
thumb_media_id: string;
|
|
28
|
+
}): Promise<WechatKfSendMsgResponse>;
|
|
29
|
+
export {};
|
package/dist/src/api.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeCom Customer Service API wrapper
|
|
3
|
+
*/
|
|
4
|
+
import { extname } from "node:path";
|
|
5
|
+
import { API_POST_TIMEOUT_MS, MEDIA_TIMEOUT_MS, TOKEN_EXPIRED_CODES } from "./constants.js";
|
|
6
|
+
import { clearAccessToken, getAccessToken } from "./token.js";
|
|
7
|
+
const MIME_MAP = {
|
|
8
|
+
".jpg": "image/jpeg",
|
|
9
|
+
".jpeg": "image/jpeg",
|
|
10
|
+
".png": "image/png",
|
|
11
|
+
".gif": "image/gif",
|
|
12
|
+
".bmp": "image/bmp",
|
|
13
|
+
".webp": "image/webp",
|
|
14
|
+
".amr": "audio/amr",
|
|
15
|
+
".mp3": "audio/mpeg",
|
|
16
|
+
".wav": "audio/wav",
|
|
17
|
+
".ogg": "audio/ogg",
|
|
18
|
+
".silk": "audio/silk",
|
|
19
|
+
".m4a": "audio/mp4",
|
|
20
|
+
".aac": "audio/aac",
|
|
21
|
+
".mp4": "video/mp4",
|
|
22
|
+
".avi": "video/x-msvideo",
|
|
23
|
+
".mov": "video/quicktime",
|
|
24
|
+
};
|
|
25
|
+
const BASE = "https://qyapi.weixin.qq.com/cgi-bin";
|
|
26
|
+
/**
|
|
27
|
+
* Check whether a WeCom API response indicates a business error.
|
|
28
|
+
* Successful responses have errcode === 0 or omit errcode entirely (undefined).
|
|
29
|
+
* Using a truthy check so that both 0 and undefined are treated as success.
|
|
30
|
+
*/
|
|
31
|
+
function hasApiError(errcode) {
|
|
32
|
+
return !!errcode;
|
|
33
|
+
}
|
|
34
|
+
async function apiPost(path, token, body) {
|
|
35
|
+
const resp = await fetch(`${BASE}${path}?access_token=${token}`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: { "Content-Type": "application/json" },
|
|
38
|
+
body: JSON.stringify(body),
|
|
39
|
+
signal: AbortSignal.timeout(API_POST_TIMEOUT_MS),
|
|
40
|
+
});
|
|
41
|
+
if (!resp.ok) {
|
|
42
|
+
const text = await resp.text().catch(() => "");
|
|
43
|
+
throw new Error(`[wechat-kf] API ${path} HTTP ${resp.status}: ${text.slice(0, 200)}`);
|
|
44
|
+
}
|
|
45
|
+
return (await resp.json());
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Call apiPost with automatic token refresh on token-expired errors.
|
|
49
|
+
* Detects errcode 40014/42001/40001, clears the cached token, fetches a
|
|
50
|
+
* new one and retries exactly once.
|
|
51
|
+
*/
|
|
52
|
+
async function apiPostWithTokenRetry(path, corpId, appSecret, body) {
|
|
53
|
+
let token = await getAccessToken(corpId, appSecret);
|
|
54
|
+
const data = await apiPost(path, token, body);
|
|
55
|
+
const result = data;
|
|
56
|
+
if (typeof result.errcode === "number" && TOKEN_EXPIRED_CODES.has(result.errcode)) {
|
|
57
|
+
clearAccessToken(corpId, appSecret);
|
|
58
|
+
token = await getAccessToken(corpId, appSecret);
|
|
59
|
+
return apiPost(path, token, body);
|
|
60
|
+
}
|
|
61
|
+
return data;
|
|
62
|
+
}
|
|
63
|
+
/** Pull messages from WeChat KF */
|
|
64
|
+
export async function syncMessages(corpId, appSecret, params) {
|
|
65
|
+
const data = await apiPostWithTokenRetry("/kf/sync_msg", corpId, appSecret, params);
|
|
66
|
+
if (hasApiError(data.errcode)) {
|
|
67
|
+
throw new Error(`[wechat-kf] sync_msg failed: ${data.errcode} ${data.errmsg}`);
|
|
68
|
+
}
|
|
69
|
+
return data;
|
|
70
|
+
}
|
|
71
|
+
async function sendMessage(corpId, appSecret, toUser, openKfId, msgtype, payload) {
|
|
72
|
+
const body = {
|
|
73
|
+
touser: toUser,
|
|
74
|
+
open_kfid: openKfId,
|
|
75
|
+
msgtype,
|
|
76
|
+
...payload,
|
|
77
|
+
};
|
|
78
|
+
const data = await apiPostWithTokenRetry("/kf/send_msg", corpId, appSecret, body);
|
|
79
|
+
if (hasApiError(data.errcode)) {
|
|
80
|
+
throw new Error(`[wechat-kf] send_msg failed: ${data.errcode} ${data.errmsg}`);
|
|
81
|
+
}
|
|
82
|
+
return data;
|
|
83
|
+
}
|
|
84
|
+
/** Send a text message to a WeChat user */
|
|
85
|
+
export function sendTextMessage(corpId, appSecret, toUser, openKfId, content) {
|
|
86
|
+
return sendMessage(corpId, appSecret, toUser, openKfId, "text", { text: { content } });
|
|
87
|
+
}
|
|
88
|
+
/** Download media file from WeChat */
|
|
89
|
+
export async function downloadMedia(corpId, appSecret, mediaId) {
|
|
90
|
+
const attemptDownload = async (token) => {
|
|
91
|
+
const resp = await fetch(`${BASE}/media/get?access_token=${token}&media_id=${mediaId}`, {
|
|
92
|
+
signal: AbortSignal.timeout(MEDIA_TIMEOUT_MS),
|
|
93
|
+
});
|
|
94
|
+
if (!resp.ok) {
|
|
95
|
+
throw new Error(`[wechat-kf] download media failed: ${resp.status} ${resp.statusText}`);
|
|
96
|
+
}
|
|
97
|
+
const contentType = resp.headers.get("content-type") ?? "";
|
|
98
|
+
if (contentType.includes("application/json")) {
|
|
99
|
+
const data = (await resp.json());
|
|
100
|
+
return { buffer: Buffer.alloc(0), errcode: data.errcode, errmsg: data.errmsg };
|
|
101
|
+
}
|
|
102
|
+
return { buffer: Buffer.from(await resp.arrayBuffer()) };
|
|
103
|
+
};
|
|
104
|
+
let token = await getAccessToken(corpId, appSecret);
|
|
105
|
+
const result = await attemptDownload(token);
|
|
106
|
+
if (result.errcode !== undefined) {
|
|
107
|
+
// Token-expired error: clear and retry once
|
|
108
|
+
if (TOKEN_EXPIRED_CODES.has(result.errcode)) {
|
|
109
|
+
clearAccessToken(corpId, appSecret);
|
|
110
|
+
token = await getAccessToken(corpId, appSecret);
|
|
111
|
+
const retry = await attemptDownload(token);
|
|
112
|
+
if (retry.errcode !== undefined) {
|
|
113
|
+
throw new Error(`[wechat-kf] download media failed: ${retry.errcode} ${retry.errmsg}`);
|
|
114
|
+
}
|
|
115
|
+
return retry.buffer;
|
|
116
|
+
}
|
|
117
|
+
throw new Error(`[wechat-kf] download media failed: ${result.errcode} ${result.errmsg}`);
|
|
118
|
+
}
|
|
119
|
+
return result.buffer;
|
|
120
|
+
}
|
|
121
|
+
/** Upload media file to WeChat */
|
|
122
|
+
export async function uploadMedia(corpId, appSecret, type, buffer, filename) {
|
|
123
|
+
const doUpload = async (token) => {
|
|
124
|
+
const formData = new FormData();
|
|
125
|
+
const ext = extname(filename).toLowerCase();
|
|
126
|
+
const mime = MIME_MAP[ext] ?? "application/octet-stream";
|
|
127
|
+
const blob = new Blob([new Uint8Array(buffer)], { type: mime });
|
|
128
|
+
formData.append("media", blob, filename);
|
|
129
|
+
const resp = await fetch(`${BASE}/media/upload?access_token=${token}&type=${type}`, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
body: formData,
|
|
132
|
+
signal: AbortSignal.timeout(MEDIA_TIMEOUT_MS),
|
|
133
|
+
});
|
|
134
|
+
if (!resp.ok) {
|
|
135
|
+
const text = await resp.text().catch(() => "");
|
|
136
|
+
throw new Error(`[wechat-kf] upload media HTTP ${resp.status}: ${text.slice(0, 200)}`);
|
|
137
|
+
}
|
|
138
|
+
return (await resp.json());
|
|
139
|
+
};
|
|
140
|
+
let token = await getAccessToken(corpId, appSecret);
|
|
141
|
+
let data = await doUpload(token);
|
|
142
|
+
if (TOKEN_EXPIRED_CODES.has(data.errcode)) {
|
|
143
|
+
clearAccessToken(corpId, appSecret);
|
|
144
|
+
token = await getAccessToken(corpId, appSecret);
|
|
145
|
+
data = await doUpload(token);
|
|
146
|
+
}
|
|
147
|
+
if (hasApiError(data.errcode)) {
|
|
148
|
+
throw new Error(`[wechat-kf] upload media failed: ${data.errcode} ${data.errmsg}`);
|
|
149
|
+
}
|
|
150
|
+
return data;
|
|
151
|
+
}
|
|
152
|
+
/** Send an image message to a WeChat user */
|
|
153
|
+
export function sendImageMessage(corpId, appSecret, toUser, openKfId, mediaId) {
|
|
154
|
+
return sendMessage(corpId, appSecret, toUser, openKfId, "image", { image: { media_id: mediaId } });
|
|
155
|
+
}
|
|
156
|
+
/** Send a voice message to a WeChat user */
|
|
157
|
+
export function sendVoiceMessage(corpId, appSecret, toUser, openKfId, mediaId) {
|
|
158
|
+
return sendMessage(corpId, appSecret, toUser, openKfId, "voice", { voice: { media_id: mediaId } });
|
|
159
|
+
}
|
|
160
|
+
/** Send a video message to a WeChat user */
|
|
161
|
+
export function sendVideoMessage(corpId, appSecret, toUser, openKfId, mediaId) {
|
|
162
|
+
return sendMessage(corpId, appSecret, toUser, openKfId, "video", { video: { media_id: mediaId } });
|
|
163
|
+
}
|
|
164
|
+
/** Send a file message to a WeChat user */
|
|
165
|
+
export function sendFileMessage(corpId, appSecret, toUser, openKfId, mediaId) {
|
|
166
|
+
return sendMessage(corpId, appSecret, toUser, openKfId, "file", { file: { media_id: mediaId } });
|
|
167
|
+
}
|
|
168
|
+
/** Send a link message to a WeChat user */
|
|
169
|
+
export function sendLinkMessage(corpId, appSecret, toUser, openKfId, link) {
|
|
170
|
+
return sendMessage(corpId, appSecret, toUser, openKfId, "link", { link });
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
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 PluginRuntime } from "./runtime.js";
|
|
11
|
+
import type { OpenClawConfig, ResolvedWechatKfAccount, WechatKfMessage } from "./types.js";
|
|
12
|
+
export type Logger = {
|
|
13
|
+
info: (...args: unknown[]) => void;
|
|
14
|
+
error: (...args: unknown[]) => void;
|
|
15
|
+
warn?: (...args: unknown[]) => void;
|
|
16
|
+
};
|
|
17
|
+
export type BotContext = {
|
|
18
|
+
cfg: OpenClawConfig;
|
|
19
|
+
runtime?: PluginRuntime;
|
|
20
|
+
stateDir: string;
|
|
21
|
+
log?: Logger;
|
|
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
|
+
resetState(): void;
|
|
32
|
+
};
|
|
33
|
+
declare function handleEvent(ctx: BotContext, _account: ResolvedWechatKfAccount, msg: WechatKfMessage): Promise<void>;
|
|
34
|
+
export declare function handleWebhookEvent(ctx: BotContext, openKfId: string, syncToken: string): Promise<void>;
|
|
35
|
+
export {};
|