agent-shell-chat 1.2.2
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 +199 -0
- package/dist/bin/agent-shell.d.ts +15 -0
- package/dist/bin/agent-shell.d.ts.map +1 -0
- package/dist/bin/agent-shell.js +816 -0
- package/dist/bin/agent-shell.js.map +1 -0
- package/dist/package.json +54 -0
- package/dist/src/acp/agent-manager.d.ts +22 -0
- package/dist/src/acp/agent-manager.d.ts.map +1 -0
- package/dist/src/acp/agent-manager.js +79 -0
- package/dist/src/acp/agent-manager.js.map +1 -0
- package/dist/src/acp/client.d.ts +64 -0
- package/dist/src/acp/client.d.ts.map +1 -0
- package/dist/src/acp/client.js +265 -0
- package/dist/src/acp/client.js.map +1 -0
- package/dist/src/acp/session.d.ts +81 -0
- package/dist/src/acp/session.d.ts.map +1 -0
- package/dist/src/acp/session.js +339 -0
- package/dist/src/acp/session.js.map +1 -0
- package/dist/src/adapter/inbound.d.ts +39 -0
- package/dist/src/adapter/inbound.d.ts.map +1 -0
- package/dist/src/adapter/inbound.js +264 -0
- package/dist/src/adapter/inbound.js.map +1 -0
- package/dist/src/bridge.d.ts +115 -0
- package/dist/src/bridge.d.ts.map +1 -0
- package/dist/src/bridge.js +969 -0
- package/dist/src/bridge.js.map +1 -0
- package/dist/src/config.d.ts +155 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +265 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +7 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/inject/monitor.d.ts +24 -0
- package/dist/src/inject/monitor.d.ts.map +1 -0
- package/dist/src/inject/monitor.js +149 -0
- package/dist/src/inject/monitor.js.map +1 -0
- package/dist/src/inject/queue.d.ts +13 -0
- package/dist/src/inject/queue.d.ts.map +1 -0
- package/dist/src/inject/queue.js +35 -0
- package/dist/src/inject/queue.js.map +1 -0
- package/dist/src/inject/types.d.ts +10 -0
- package/dist/src/inject/types.d.ts.map +1 -0
- package/dist/src/inject/types.js +2 -0
- package/dist/src/inject/types.js.map +1 -0
- package/dist/src/storage/accounts.d.ts +43 -0
- package/dist/src/storage/accounts.d.ts.map +1 -0
- package/dist/src/storage/accounts.js +289 -0
- package/dist/src/storage/accounts.js.map +1 -0
- package/dist/src/storage/runtime.d.ts +23 -0
- package/dist/src/storage/runtime.d.ts.map +1 -0
- package/dist/src/storage/runtime.js +104 -0
- package/dist/src/storage/runtime.js.map +1 -0
- package/dist/src/storage/state.d.ts +17 -0
- package/dist/src/storage/state.d.ts.map +1 -0
- package/dist/src/storage/state.js +78 -0
- package/dist/src/storage/state.js.map +1 -0
- package/dist/src/telemetry/index.d.ts +33 -0
- package/dist/src/telemetry/index.d.ts.map +1 -0
- package/dist/src/telemetry/index.js +167 -0
- package/dist/src/telemetry/index.js.map +1 -0
- package/dist/src/weixin/api.d.ts +50 -0
- package/dist/src/weixin/api.d.ts.map +1 -0
- package/dist/src/weixin/api.js +90 -0
- package/dist/src/weixin/api.js.map +1 -0
- package/dist/src/weixin/auth.d.ts +26 -0
- package/dist/src/weixin/auth.d.ts.map +1 -0
- package/dist/src/weixin/auth.js +103 -0
- package/dist/src/weixin/auth.js.map +1 -0
- package/dist/src/weixin/media.d.ts +24 -0
- package/dist/src/weixin/media.d.ts.map +1 -0
- package/dist/src/weixin/media.js +64 -0
- package/dist/src/weixin/media.js.map +1 -0
- package/dist/src/weixin/monitor.d.ts +16 -0
- package/dist/src/weixin/monitor.d.ts.map +1 -0
- package/dist/src/weixin/monitor.js +113 -0
- package/dist/src/weixin/monitor.js.map +1 -0
- package/dist/src/weixin/send.d.ts +28 -0
- package/dist/src/weixin/send.d.ts.map +1 -0
- package/dist/src/weixin/send.js +162 -0
- package/dist/src/weixin/send.js.map +1 -0
- package/dist/src/weixin/types.d.ts +149 -0
- package/dist/src/weixin/types.d.ts.map +1 -0
- package/dist/src/weixin/types.js +33 -0
- package/dist/src/weixin/types.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeChat QR login flow + token persistence.
|
|
3
|
+
*/
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { getBotQrcode, getQrcodeStatus } from "./api.js";
|
|
7
|
+
function getTokenPath(storageDir) {
|
|
8
|
+
return path.join(storageDir, "token.json");
|
|
9
|
+
}
|
|
10
|
+
export function loadToken(storageDir) {
|
|
11
|
+
const tokenPath = getTokenPath(storageDir);
|
|
12
|
+
if (!fs.existsSync(tokenPath))
|
|
13
|
+
return null;
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(fs.readFileSync(tokenPath, "utf-8"));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function saveToken(storageDir, data) {
|
|
22
|
+
fs.mkdirSync(storageDir, { recursive: true, mode: 0o700 });
|
|
23
|
+
const tokenPath = getTokenPath(storageDir);
|
|
24
|
+
const tmpPath = path.join(storageDir, `.token-${process.pid}-${Date.now()}.tmp`);
|
|
25
|
+
fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2) + "\n", {
|
|
26
|
+
encoding: "utf-8",
|
|
27
|
+
mode: 0o600,
|
|
28
|
+
});
|
|
29
|
+
try {
|
|
30
|
+
fs.renameSync(tmpPath, tokenPath);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
const code = err.code;
|
|
34
|
+
if (process.platform !== "win32" || (code !== "EEXIST" && code !== "EPERM")) {
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
fs.rmSync(tokenPath, { force: true });
|
|
38
|
+
fs.renameSync(tmpPath, tokenPath);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export async function login(params) {
|
|
42
|
+
const { baseUrl, botType, storageDir, persist = true, log, renderQrUrl } = params;
|
|
43
|
+
log("Starting WeChat QR login...");
|
|
44
|
+
const qrResp = await getBotQrcode({ baseUrl, botType });
|
|
45
|
+
const qrcodeUrl = qrResp.qrcode_img_content;
|
|
46
|
+
log("Please scan the QR code with WeChat:");
|
|
47
|
+
if (renderQrUrl) {
|
|
48
|
+
renderQrUrl(qrcodeUrl);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
log(`QR URL: ${qrcodeUrl}`);
|
|
52
|
+
}
|
|
53
|
+
const deadline = Date.now() + 5 * 60_000;
|
|
54
|
+
let currentQrcode = qrResp.qrcode;
|
|
55
|
+
let refreshCount = 0;
|
|
56
|
+
while (Date.now() < deadline) {
|
|
57
|
+
const statusResp = await getQrcodeStatus({ baseUrl, qrcode: currentQrcode });
|
|
58
|
+
switch (statusResp.status) {
|
|
59
|
+
case "wait":
|
|
60
|
+
break;
|
|
61
|
+
case "scaned":
|
|
62
|
+
log("QR scanned, please confirm in WeChat...");
|
|
63
|
+
break;
|
|
64
|
+
case "expired": {
|
|
65
|
+
refreshCount++;
|
|
66
|
+
if (refreshCount > 3) {
|
|
67
|
+
throw new Error("QR code expired multiple times, please retry");
|
|
68
|
+
}
|
|
69
|
+
log(`QR expired, refreshing (${refreshCount}/3)...`);
|
|
70
|
+
const newQr = await getBotQrcode({ baseUrl, botType });
|
|
71
|
+
currentQrcode = newQr.qrcode;
|
|
72
|
+
if (renderQrUrl) {
|
|
73
|
+
renderQrUrl(newQr.qrcode_img_content);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
log(`New QR URL: ${newQr.qrcode_img_content}`);
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case "confirmed": {
|
|
81
|
+
log("Login successful!");
|
|
82
|
+
const tokenData = {
|
|
83
|
+
token: statusResp.bot_token,
|
|
84
|
+
baseUrl: statusResp.baseurl || baseUrl,
|
|
85
|
+
accountId: statusResp.ilink_bot_id,
|
|
86
|
+
userId: statusResp.ilink_user_id,
|
|
87
|
+
savedAt: new Date().toISOString(),
|
|
88
|
+
};
|
|
89
|
+
if (persist) {
|
|
90
|
+
saveToken(storageDir, tokenData);
|
|
91
|
+
}
|
|
92
|
+
log(`Bot ID: ${tokenData.accountId}`);
|
|
93
|
+
if (persist) {
|
|
94
|
+
log(`Token saved to ${getTokenPath(storageDir)}`);
|
|
95
|
+
}
|
|
96
|
+
return tokenData;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
100
|
+
}
|
|
101
|
+
throw new Error("Login timeout (5 minutes)");
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/weixin/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AES-128-ECB encrypt/decrypt for WeChat CDN media.
|
|
3
|
+
* Adapted from @tencent-weixin/openclaw-weixin cdn/aes-ecb.ts
|
|
4
|
+
*/
|
|
5
|
+
import type { CDNMedia } from "./types.js";
|
|
6
|
+
export declare function encryptAesEcb(plaintext: Buffer, key: Buffer): Buffer;
|
|
7
|
+
export declare function decryptAesEcb(ciphertext: Buffer, key: Buffer): Buffer;
|
|
8
|
+
/**
|
|
9
|
+
* Parse the AES key from CDN media reference.
|
|
10
|
+
* The key can be either:
|
|
11
|
+
* - base64 → 16 raw bytes (use directly)
|
|
12
|
+
* - base64 → 32 hex chars → parse hex → 16 bytes
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseAesKey(media: CDNMedia): Buffer | null;
|
|
15
|
+
export declare function downloadAndDecrypt(encryptQueryParam: string, aesKey: Buffer, cdnBaseUrl: string): Promise<Buffer>;
|
|
16
|
+
export declare function uploadToCdn(params: {
|
|
17
|
+
buffer: Buffer;
|
|
18
|
+
uploadParam?: string;
|
|
19
|
+
uploadFullUrl?: string;
|
|
20
|
+
aesKey: Buffer;
|
|
21
|
+
filekey: string;
|
|
22
|
+
cdnBaseUrl: string;
|
|
23
|
+
}): Promise<string>;
|
|
24
|
+
//# sourceMappingURL=media.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../../src/weixin/media.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGrE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAa1D;AAED,wBAAsB,kBAAkB,CACtC,iBAAiB,EAAE,MAAM,EACzB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAMjB;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBlB"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AES-128-ECB encrypt/decrypt for WeChat CDN media.
|
|
3
|
+
* Adapted from @tencent-weixin/openclaw-weixin cdn/aes-ecb.ts
|
|
4
|
+
*/
|
|
5
|
+
import crypto from "node:crypto";
|
|
6
|
+
export function encryptAesEcb(plaintext, key) {
|
|
7
|
+
const cipher = crypto.createCipheriv("aes-128-ecb", key, null);
|
|
8
|
+
return Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
9
|
+
}
|
|
10
|
+
export function decryptAesEcb(ciphertext, key) {
|
|
11
|
+
const decipher = crypto.createDecipheriv("aes-128-ecb", key, null);
|
|
12
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Parse the AES key from CDN media reference.
|
|
16
|
+
* The key can be either:
|
|
17
|
+
* - base64 → 16 raw bytes (use directly)
|
|
18
|
+
* - base64 → 32 hex chars → parse hex → 16 bytes
|
|
19
|
+
*/
|
|
20
|
+
export function parseAesKey(media) {
|
|
21
|
+
const raw = media.aes_key;
|
|
22
|
+
if (!raw)
|
|
23
|
+
return null;
|
|
24
|
+
const decoded = Buffer.from(raw, "base64");
|
|
25
|
+
if (decoded.length === 16)
|
|
26
|
+
return decoded;
|
|
27
|
+
if (decoded.length === 32) {
|
|
28
|
+
const hexStr = decoded.toString("ascii");
|
|
29
|
+
if (/^[0-9a-fA-F]{32}$/.test(hexStr)) {
|
|
30
|
+
return Buffer.from(hexStr, "hex");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return decoded.subarray(0, 16);
|
|
34
|
+
}
|
|
35
|
+
export async function downloadAndDecrypt(encryptQueryParam, aesKey, cdnBaseUrl) {
|
|
36
|
+
const url = `${cdnBaseUrl}/download?encrypted_query_param=${encodeURIComponent(encryptQueryParam)}`;
|
|
37
|
+
const res = await fetch(url);
|
|
38
|
+
if (!res.ok)
|
|
39
|
+
throw new Error(`CDN download failed: HTTP ${res.status}`);
|
|
40
|
+
const ciphertext = Buffer.from(await res.arrayBuffer());
|
|
41
|
+
return decryptAesEcb(ciphertext, aesKey);
|
|
42
|
+
}
|
|
43
|
+
export async function uploadToCdn(params) {
|
|
44
|
+
const encrypted = encryptAesEcb(params.buffer, params.aesKey);
|
|
45
|
+
const url = params.uploadFullUrl
|
|
46
|
+
?? (params.uploadParam
|
|
47
|
+
? `${params.cdnBaseUrl}/upload?encrypted_query_param=${encodeURIComponent(params.uploadParam)}&filekey=${encodeURIComponent(params.filekey)}`
|
|
48
|
+
: null);
|
|
49
|
+
if (!url) {
|
|
50
|
+
throw new Error("CDN upload: uploadParam or uploadFullUrl is required");
|
|
51
|
+
}
|
|
52
|
+
const res = await fetch(url, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
55
|
+
body: encrypted,
|
|
56
|
+
});
|
|
57
|
+
if (!res.ok)
|
|
58
|
+
throw new Error(`CDN upload failed: HTTP ${res.status}`);
|
|
59
|
+
const downloadParam = res.headers.get("x-encrypted-param");
|
|
60
|
+
if (!downloadParam)
|
|
61
|
+
throw new Error("CDN upload: missing x-encrypted-param header");
|
|
62
|
+
return downloadParam;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.js","sourceRoot":"","sources":["../../../src/weixin/media.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,MAAM,MAAM,aAAa,CAAC;AAGjC,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,GAAW;IAC1D,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,aAAa,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAC/D,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,UAAkB,EAAE,GAAW;IAC3D,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC,aAAa,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IACnE,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;AACxE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAAe;IACzC,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC;IAC1B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAEtB,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC3C,IAAI,OAAO,CAAC,MAAM,KAAK,EAAE;QAAE,OAAO,OAAO,CAAC;IAC1C,IAAI,OAAO,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACzC,IAAI,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACrC,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,iBAAyB,EACzB,MAAc,EACd,UAAkB;IAElB,MAAM,GAAG,GAAG,GAAG,UAAU,mCAAmC,kBAAkB,CAAC,iBAAiB,CAAC,EAAE,CAAC;IACpG,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IACxE,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;IACxD,OAAO,aAAa,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAOjC;IACC,MAAM,SAAS,GAAG,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9D,MAAM,GAAG,GAAG,MAAM,CAAC,aAAa;WAC3B,CACD,MAAM,CAAC,WAAW;YAChB,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,iCAAiC,kBAAkB,CAAC,MAAM,CAAC,WAAW,CAAC,YAAY,kBAAkB,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE;YAC7I,CAAC,CAAC,IAAI,CACT,CAAC;IACJ,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;IAC1E,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC3B,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE;QACvD,IAAI,EAAE,SAAS;KAChB,CAAC,CAAC;IAEH,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IACtE,MAAM,aAAa,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAC3D,IAAI,CAAC,aAAa;QAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IACpF,OAAO,aAAa,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeChat long-poll monitor loop.
|
|
3
|
+
* Polls getUpdates, dispatches messages via callback.
|
|
4
|
+
*/
|
|
5
|
+
import type { WeixinMessage } from "./types.js";
|
|
6
|
+
export interface MonitorOpts {
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
token?: string;
|
|
9
|
+
storageDir: string;
|
|
10
|
+
abortSignal?: AbortSignal;
|
|
11
|
+
longPollTimeoutMs?: number;
|
|
12
|
+
log: (msg: string) => void;
|
|
13
|
+
onMessage: (msg: WeixinMessage) => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function startMonitor(opts: MonitorOpts): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=monitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monitor.d.ts","sourceRoot":"","sources":["../../../src/weixin/monitor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAkB,MAAM,YAAY,CAAC;AAShE,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,SAAS,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,IAAI,CAAC;CACzC;AA8BD,wBAAsB,YAAY,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAgFnE"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeChat long-poll monitor loop.
|
|
3
|
+
* Polls getUpdates, dispatches messages via callback.
|
|
4
|
+
*/
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { getUpdates } from "./api.js";
|
|
8
|
+
import { trackException } from "../telemetry/index.js";
|
|
9
|
+
const DEFAULT_LONG_POLL_TIMEOUT_MS = 35_000;
|
|
10
|
+
const MAX_CONSECUTIVE_FAILURES = 3;
|
|
11
|
+
const BACKOFF_DELAY_MS = 30_000;
|
|
12
|
+
const RETRY_DELAY_MS = 2_000;
|
|
13
|
+
const SESSION_EXPIRED_ERRCODE = -14;
|
|
14
|
+
function getSyncBufPath(storageDir) {
|
|
15
|
+
return path.join(storageDir, "sync-buf.json");
|
|
16
|
+
}
|
|
17
|
+
function loadSyncBuf(storageDir) {
|
|
18
|
+
const p = getSyncBufPath(storageDir);
|
|
19
|
+
if (!fs.existsSync(p))
|
|
20
|
+
return "";
|
|
21
|
+
try {
|
|
22
|
+
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
23
|
+
return data.get_updates_buf ?? "";
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function saveSyncBuf(storageDir, buf) {
|
|
30
|
+
fs.mkdirSync(storageDir, { recursive: true });
|
|
31
|
+
fs.writeFileSync(getSyncBufPath(storageDir), JSON.stringify({ get_updates_buf: buf }), "utf-8");
|
|
32
|
+
}
|
|
33
|
+
function sleep(ms, signal) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
if (signal?.aborted) {
|
|
36
|
+
reject(new Error("aborted"));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const t = setTimeout(resolve, ms);
|
|
40
|
+
signal?.addEventListener("abort", () => { clearTimeout(t); reject(new Error("aborted")); }, { once: true });
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export async function startMonitor(opts) {
|
|
44
|
+
const { baseUrl, token, storageDir, abortSignal, log, onMessage } = opts;
|
|
45
|
+
let getUpdatesBuf = loadSyncBuf(storageDir);
|
|
46
|
+
if (getUpdatesBuf) {
|
|
47
|
+
log(`Resuming from previous sync buf (${getUpdatesBuf.length} bytes)`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
log("No previous sync buf, starting fresh");
|
|
51
|
+
}
|
|
52
|
+
let nextTimeoutMs = opts.longPollTimeoutMs ?? DEFAULT_LONG_POLL_TIMEOUT_MS;
|
|
53
|
+
let consecutiveFailures = 0;
|
|
54
|
+
while (!abortSignal?.aborted) {
|
|
55
|
+
try {
|
|
56
|
+
const resp = await getUpdates({
|
|
57
|
+
baseUrl,
|
|
58
|
+
token,
|
|
59
|
+
get_updates_buf: getUpdatesBuf,
|
|
60
|
+
timeoutMs: nextTimeoutMs,
|
|
61
|
+
});
|
|
62
|
+
if (resp.longpolling_timeout_ms != null && resp.longpolling_timeout_ms > 0) {
|
|
63
|
+
nextTimeoutMs = resp.longpolling_timeout_ms;
|
|
64
|
+
}
|
|
65
|
+
const isApiError = (resp.ret !== undefined && resp.ret !== 0) ||
|
|
66
|
+
(resp.errcode !== undefined && resp.errcode !== 0);
|
|
67
|
+
if (isApiError) {
|
|
68
|
+
const isSessionExpired = resp.errcode === SESSION_EXPIRED_ERRCODE || resp.ret === SESSION_EXPIRED_ERRCODE;
|
|
69
|
+
if (isSessionExpired) {
|
|
70
|
+
log(`Session expired (errcode ${SESSION_EXPIRED_ERRCODE}), pausing 1 hour...`);
|
|
71
|
+
consecutiveFailures = 0;
|
|
72
|
+
await sleep(60 * 60_000, abortSignal);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
consecutiveFailures++;
|
|
76
|
+
log(`getUpdates failed: ret=${resp.ret} errcode=${resp.errcode} errmsg=${resp.errmsg ?? ""} (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES})`);
|
|
77
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
78
|
+
log(`${MAX_CONSECUTIVE_FAILURES} consecutive failures, backing off ${BACKOFF_DELAY_MS / 1000}s`);
|
|
79
|
+
consecutiveFailures = 0;
|
|
80
|
+
await sleep(BACKOFF_DELAY_MS, abortSignal);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
await sleep(RETRY_DELAY_MS, abortSignal);
|
|
84
|
+
}
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
consecutiveFailures = 0;
|
|
88
|
+
if (resp.get_updates_buf != null && resp.get_updates_buf !== "") {
|
|
89
|
+
saveSyncBuf(storageDir, resp.get_updates_buf);
|
|
90
|
+
getUpdatesBuf = resp.get_updates_buf;
|
|
91
|
+
}
|
|
92
|
+
for (const msg of resp.msgs ?? []) {
|
|
93
|
+
onMessage(msg);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
if (abortSignal?.aborted)
|
|
98
|
+
return;
|
|
99
|
+
consecutiveFailures++;
|
|
100
|
+
log(`getUpdates error (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES}): ${String(err)}`);
|
|
101
|
+
trackException(err, "monitor");
|
|
102
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
103
|
+
log(`${MAX_CONSECUTIVE_FAILURES} consecutive failures, backing off ${BACKOFF_DELAY_MS / 1000}s`);
|
|
104
|
+
consecutiveFailures = 0;
|
|
105
|
+
await sleep(BACKOFF_DELAY_MS, abortSignal);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
await sleep(RETRY_DELAY_MS, abortSignal);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=monitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monitor.js","sourceRoot":"","sources":["../../../src/weixin/monitor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Send messages via WeChat iLink API.
|
|
3
|
+
*/
|
|
4
|
+
import { sendMessage } from "./api.js";
|
|
5
|
+
export interface WeixinSendOpts {
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
token?: string;
|
|
8
|
+
contextToken?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface WeixinMediaSendOpts extends WeixinSendOpts {
|
|
11
|
+
cdnBaseUrl: string;
|
|
12
|
+
}
|
|
13
|
+
export interface UploadedWeixinMedia {
|
|
14
|
+
filekey: string;
|
|
15
|
+
downloadEncryptedQueryParam: string;
|
|
16
|
+
aesKeyHex: string;
|
|
17
|
+
md5: string;
|
|
18
|
+
rawSize: number;
|
|
19
|
+
ciphertextSize: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function sendTextMessage(to: string, text: string, opts: WeixinSendOpts, clientId?: string, sendFn?: typeof sendMessage): Promise<string>;
|
|
22
|
+
export declare function sendImageMessage(to: string, image: Buffer, opts: WeixinMediaSendOpts, clientId?: string): Promise<string>;
|
|
23
|
+
export declare function sendFileMessage(to: string, fileName: string, file: Buffer, opts: WeixinMediaSendOpts, clientId?: string): Promise<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Split text into segments of max length, respecting line breaks where possible.
|
|
26
|
+
*/
|
|
27
|
+
export declare function splitText(text: string, maxLen: number): string[];
|
|
28
|
+
//# sourceMappingURL=send.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../../src/weixin/send.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAgB,WAAW,EAAE,MAAM,UAAU,CAAC;AAUrD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAoB,SAAQ,cAAc;IACzD,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,2BAA2B,EAAE,MAAM,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AAyFD,wBAAsB,eAAe,CACnC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,cAAc,EACpB,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,GAAE,OAAO,WAAyB,GACvC,OAAO,CAAC,MAAM,CAAC,CAyBjB;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,mBAAmB,EACzB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,CAwBjB;AAED,wBAAsB,eAAe,CACnC,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,mBAAmB,EACzB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,CA0BjB;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAqBhE"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Send messages via WeChat iLink API.
|
|
3
|
+
*/
|
|
4
|
+
import crypto from "node:crypto";
|
|
5
|
+
import { getUploadUrl, sendMessage } from "./api.js";
|
|
6
|
+
import { uploadToCdn } from "./media.js";
|
|
7
|
+
import { MessageItemType, MessageState, MessageType, UploadMediaType, } from "./types.js";
|
|
8
|
+
function aesEcbPaddedSize(size) {
|
|
9
|
+
return Math.ceil((size + 1) / 16) * 16;
|
|
10
|
+
}
|
|
11
|
+
function createCdnMedia(uploaded) {
|
|
12
|
+
return {
|
|
13
|
+
encrypt_query_param: uploaded.downloadEncryptedQueryParam,
|
|
14
|
+
aes_key: Buffer.from(uploaded.aesKeyHex).toString("base64"),
|
|
15
|
+
encrypt_type: 1,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
async function uploadMediaBuffer(params) {
|
|
19
|
+
const aesKey = crypto.randomBytes(16);
|
|
20
|
+
const filekey = crypto.randomBytes(16).toString("hex");
|
|
21
|
+
const md5 = crypto.createHash("md5").update(params.buffer).digest("hex");
|
|
22
|
+
const rawSize = params.buffer.length;
|
|
23
|
+
const ciphertextSize = aesEcbPaddedSize(rawSize);
|
|
24
|
+
const uploadUrl = await getUploadUrl({
|
|
25
|
+
baseUrl: params.opts.baseUrl,
|
|
26
|
+
token: params.opts.token,
|
|
27
|
+
body: {
|
|
28
|
+
filekey,
|
|
29
|
+
media_type: params.mediaType,
|
|
30
|
+
to_user_id: params.to,
|
|
31
|
+
rawsize: rawSize,
|
|
32
|
+
rawfilemd5: md5,
|
|
33
|
+
filesize: ciphertextSize,
|
|
34
|
+
no_need_thumb: true,
|
|
35
|
+
aeskey: aesKey.toString("hex"),
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
const downloadEncryptedQueryParam = await uploadToCdn({
|
|
39
|
+
buffer: params.buffer,
|
|
40
|
+
uploadParam: uploadUrl.upload_param,
|
|
41
|
+
uploadFullUrl: uploadUrl.upload_full_url,
|
|
42
|
+
aesKey,
|
|
43
|
+
filekey,
|
|
44
|
+
cdnBaseUrl: params.opts.cdnBaseUrl,
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
filekey,
|
|
48
|
+
downloadEncryptedQueryParam,
|
|
49
|
+
aesKeyHex: aesKey.toString("hex"),
|
|
50
|
+
md5,
|
|
51
|
+
rawSize,
|
|
52
|
+
ciphertextSize,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async function sendMediaMessage(to, item, opts, clientId) {
|
|
56
|
+
if (!opts.contextToken) {
|
|
57
|
+
throw new Error("contextToken is required to send a message");
|
|
58
|
+
}
|
|
59
|
+
const id = clientId ?? `agent-shell-${crypto.randomUUID()}`;
|
|
60
|
+
await sendMessage({
|
|
61
|
+
baseUrl: opts.baseUrl,
|
|
62
|
+
token: opts.token,
|
|
63
|
+
body: {
|
|
64
|
+
msg: {
|
|
65
|
+
from_user_id: "",
|
|
66
|
+
to_user_id: to,
|
|
67
|
+
client_id: id,
|
|
68
|
+
message_type: MessageType.BOT,
|
|
69
|
+
message_state: MessageState.FINISH,
|
|
70
|
+
context_token: opts.contextToken,
|
|
71
|
+
item_list: [item],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
return id;
|
|
76
|
+
}
|
|
77
|
+
export async function sendTextMessage(to, text, opts, clientId, sendFn = sendMessage) {
|
|
78
|
+
if (!opts.contextToken) {
|
|
79
|
+
throw new Error("contextToken is required to send a message");
|
|
80
|
+
}
|
|
81
|
+
// Generate a stable idempotency key for this logical send. Callers that
|
|
82
|
+
// retry should pass the same clientId so the iLink gateway de-duplicates
|
|
83
|
+
// repeated deliveries of the same message segment.
|
|
84
|
+
const id = clientId ?? `agent-shell-${crypto.randomUUID()}`;
|
|
85
|
+
await sendFn({
|
|
86
|
+
baseUrl: opts.baseUrl,
|
|
87
|
+
token: opts.token,
|
|
88
|
+
body: {
|
|
89
|
+
msg: {
|
|
90
|
+
from_user_id: "",
|
|
91
|
+
to_user_id: to,
|
|
92
|
+
client_id: id,
|
|
93
|
+
message_type: MessageType.BOT,
|
|
94
|
+
message_state: MessageState.FINISH,
|
|
95
|
+
context_token: opts.contextToken,
|
|
96
|
+
item_list: [{ type: 1, text_item: { text } }],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
return id;
|
|
101
|
+
}
|
|
102
|
+
export async function sendImageMessage(to, image, opts, clientId) {
|
|
103
|
+
if (!opts.contextToken) {
|
|
104
|
+
throw new Error("contextToken is required to send a message");
|
|
105
|
+
}
|
|
106
|
+
const uploaded = await uploadMediaBuffer({
|
|
107
|
+
to,
|
|
108
|
+
buffer: image,
|
|
109
|
+
mediaType: UploadMediaType.IMAGE,
|
|
110
|
+
opts,
|
|
111
|
+
});
|
|
112
|
+
return sendMediaMessage(to, {
|
|
113
|
+
type: MessageItemType.IMAGE,
|
|
114
|
+
image_item: {
|
|
115
|
+
media: createCdnMedia(uploaded),
|
|
116
|
+
mid_size: uploaded.ciphertextSize,
|
|
117
|
+
},
|
|
118
|
+
}, opts, clientId);
|
|
119
|
+
}
|
|
120
|
+
export async function sendFileMessage(to, fileName, file, opts, clientId) {
|
|
121
|
+
if (!opts.contextToken) {
|
|
122
|
+
throw new Error("contextToken is required to send a message");
|
|
123
|
+
}
|
|
124
|
+
const uploaded = await uploadMediaBuffer({
|
|
125
|
+
to,
|
|
126
|
+
buffer: file,
|
|
127
|
+
mediaType: UploadMediaType.FILE,
|
|
128
|
+
opts,
|
|
129
|
+
});
|
|
130
|
+
return sendMediaMessage(to, {
|
|
131
|
+
type: MessageItemType.FILE,
|
|
132
|
+
file_item: {
|
|
133
|
+
media: createCdnMedia(uploaded),
|
|
134
|
+
file_name: fileName,
|
|
135
|
+
md5: uploaded.md5,
|
|
136
|
+
len: String(uploaded.rawSize),
|
|
137
|
+
},
|
|
138
|
+
}, opts, clientId);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Split text into segments of max length, respecting line breaks where possible.
|
|
142
|
+
*/
|
|
143
|
+
export function splitText(text, maxLen) {
|
|
144
|
+
if (text.length <= maxLen)
|
|
145
|
+
return [text];
|
|
146
|
+
const segments = [];
|
|
147
|
+
let remaining = text;
|
|
148
|
+
while (remaining.length > 0) {
|
|
149
|
+
if (remaining.length <= maxLen) {
|
|
150
|
+
segments.push(remaining);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
// Try to break at a newline
|
|
154
|
+
let breakAt = remaining.lastIndexOf("\n", maxLen);
|
|
155
|
+
if (breakAt <= 0)
|
|
156
|
+
breakAt = maxLen;
|
|
157
|
+
segments.push(remaining.substring(0, breakAt));
|
|
158
|
+
remaining = remaining.substring(breakAt).replace(/^\n/, "");
|
|
159
|
+
}
|
|
160
|
+
return segments;
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=send.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send.js","sourceRoot":"","sources":["../../../src/weixin/send.ts"],"names":[],"mappings":"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"}
|