@ecat/weixin-bot-cli 0.1.0
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 +27 -0
- package/README.md +77 -0
- package/dist/api/api.d.ts +47 -0
- package/dist/api/api.js +233 -0
- package/dist/api/api.js.map +1 -0
- package/dist/api/config-cache.d.ts +18 -0
- package/dist/api/config-cache.js +64 -0
- package/dist/api/config-cache.js.map +1 -0
- package/dist/api/session-guard.d.ts +15 -0
- package/dist/api/session-guard.js +49 -0
- package/dist/api/session-guard.js.map +1 -0
- package/dist/api/types.d.ts +201 -0
- package/dist/api/types.js +35 -0
- package/dist/api/types.js.map +1 -0
- package/dist/auth/accounts.d.ts +30 -0
- package/dist/auth/accounts.js +158 -0
- package/dist/auth/accounts.js.map +1 -0
- package/dist/auth/login-qr.d.ts +31 -0
- package/dist/auth/login-qr.js +235 -0
- package/dist/auth/login-qr.js.map +1 -0
- package/dist/cdn/aes-ecb.d.ts +6 -0
- package/dist/cdn/aes-ecb.js +19 -0
- package/dist/cdn/aes-ecb.js.map +1 -0
- package/dist/cdn/cdn-upload.d.ts +17 -0
- package/dist/cdn/cdn-upload.js +73 -0
- package/dist/cdn/cdn-upload.js.map +1 -0
- package/dist/cdn/cdn-url.d.ts +13 -0
- package/dist/cdn/cdn-url.js +14 -0
- package/dist/cdn/cdn-url.js.map +1 -0
- package/dist/cdn/pic-decrypt.d.ts +9 -0
- package/dist/cdn/pic-decrypt.js +89 -0
- package/dist/cdn/pic-decrypt.js.map +1 -0
- package/dist/cdn/upload.d.ts +42 -0
- package/dist/cdn/upload.js +106 -0
- package/dist/cdn/upload.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +127 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/config-schema.d.ts +16 -0
- package/dist/config/config-schema.js +17 -0
- package/dist/config/config-schema.js.map +1 -0
- package/dist/media/media-download.d.ts +18 -0
- package/dist/media/media-download.js +95 -0
- package/dist/media/media-download.js.map +1 -0
- package/dist/media/mime.d.ts +6 -0
- package/dist/media/mime.js +73 -0
- package/dist/media/mime.js.map +1 -0
- package/dist/media/silk-transcode.d.ts +8 -0
- package/dist/media/silk-transcode.js +64 -0
- package/dist/media/silk-transcode.js.map +1 -0
- package/dist/messaging/debug-mode.d.ts +9 -0
- package/dist/messaging/debug-mode.js +63 -0
- package/dist/messaging/debug-mode.js.map +1 -0
- package/dist/messaging/inbound.d.ts +69 -0
- package/dist/messaging/inbound.js +201 -0
- package/dist/messaging/inbound.js.map +1 -0
- package/dist/messaging/send-media.d.ts +21 -0
- package/dist/messaging/send-media.js +54 -0
- package/dist/messaging/send-media.js.map +1 -0
- package/dist/messaging/send.d.ts +70 -0
- package/dist/messaging/send.js +203 -0
- package/dist/messaging/send.js.map +1 -0
- package/dist/monitor/monitor.d.ts +12 -0
- package/dist/monitor/monitor.js +145 -0
- package/dist/monitor/monitor.js.map +1 -0
- package/dist/storage/state-dir.d.ts +2 -0
- package/dist/storage/state-dir.js +8 -0
- package/dist/storage/state-dir.js.map +1 -0
- package/dist/storage/sync-buf.d.ts +20 -0
- package/dist/storage/sync-buf.js +64 -0
- package/dist/storage/sync-buf.js.map +1 -0
- package/dist/util/logger.d.ts +14 -0
- package/dist/util/logger.js +119 -0
- package/dist/util/logger.js.map +1 -0
- package/dist/util/random.d.ts +10 -0
- package/dist/util/random.js +16 -0
- package/dist/util/random.js.map +1 -0
- package/dist/util/redact.d.ts +20 -0
- package/dist/util/redact.js +54 -0
- package/dist/util/redact.js.map +1 -0
- package/package.json +38 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { getUploadUrl } from "../api/api.js";
|
|
5
|
+
import { aesEcbPaddedSize } from "./aes-ecb.js";
|
|
6
|
+
import { uploadBufferToCdn } from "./cdn-upload.js";
|
|
7
|
+
import { logger } from "../util/logger.js";
|
|
8
|
+
import { getExtensionFromContentTypeOrUrl } from "../media/mime.js";
|
|
9
|
+
import { tempFileName } from "../util/random.js";
|
|
10
|
+
import { UploadMediaType } from "../api/types.js";
|
|
11
|
+
/**
|
|
12
|
+
* Download a remote media URL (image, video, file) to a local temp file in destDir.
|
|
13
|
+
* Returns the local file path; extension is inferred from Content-Type / URL.
|
|
14
|
+
*/
|
|
15
|
+
export async function downloadRemoteImageToTemp(url, destDir) {
|
|
16
|
+
logger.debug(`downloadRemoteImageToTemp: fetching url=${url}`);
|
|
17
|
+
const res = await fetch(url);
|
|
18
|
+
if (!res.ok) {
|
|
19
|
+
const msg = `remote media download failed: ${res.status} ${res.statusText} url=${url}`;
|
|
20
|
+
logger.error(`downloadRemoteImageToTemp: ${msg}`);
|
|
21
|
+
throw new Error(msg);
|
|
22
|
+
}
|
|
23
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
24
|
+
logger.debug(`downloadRemoteImageToTemp: downloaded ${buf.length} bytes`);
|
|
25
|
+
await fs.mkdir(destDir, { recursive: true });
|
|
26
|
+
const ext = getExtensionFromContentTypeOrUrl(res.headers.get("content-type"), url);
|
|
27
|
+
const name = tempFileName("weixin-remote", ext);
|
|
28
|
+
const filePath = path.join(destDir, name);
|
|
29
|
+
await fs.writeFile(filePath, buf);
|
|
30
|
+
logger.debug(`downloadRemoteImageToTemp: saved to ${filePath} ext=${ext}`);
|
|
31
|
+
return filePath;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Common upload pipeline: read file → hash → gen aeskey → getUploadUrl → uploadBufferToCdn → return info.
|
|
35
|
+
*/
|
|
36
|
+
async function uploadMediaToCdn(params) {
|
|
37
|
+
const { filePath, toUserId, opts, cdnBaseUrl, mediaType, label } = params;
|
|
38
|
+
const plaintext = await fs.readFile(filePath);
|
|
39
|
+
const rawsize = plaintext.length;
|
|
40
|
+
const rawfilemd5 = crypto.createHash("md5").update(plaintext).digest("hex");
|
|
41
|
+
const filesize = aesEcbPaddedSize(rawsize);
|
|
42
|
+
const filekey = crypto.randomBytes(16).toString("hex");
|
|
43
|
+
const aeskey = crypto.randomBytes(16);
|
|
44
|
+
logger.debug(`${label}: file=${filePath} rawsize=${rawsize} filesize=${filesize} md5=${rawfilemd5} filekey=${filekey}`);
|
|
45
|
+
const uploadUrlResp = await getUploadUrl({
|
|
46
|
+
...opts,
|
|
47
|
+
filekey,
|
|
48
|
+
media_type: mediaType,
|
|
49
|
+
to_user_id: toUserId,
|
|
50
|
+
rawsize,
|
|
51
|
+
rawfilemd5,
|
|
52
|
+
filesize,
|
|
53
|
+
no_need_thumb: true,
|
|
54
|
+
aeskey: aeskey.toString("hex"),
|
|
55
|
+
});
|
|
56
|
+
const uploadFullUrl = uploadUrlResp.upload_full_url?.trim();
|
|
57
|
+
const uploadParam = uploadUrlResp.upload_param;
|
|
58
|
+
if (!uploadFullUrl && !uploadParam) {
|
|
59
|
+
logger.error(`${label}: getUploadUrl returned no upload URL (need upload_full_url or upload_param), resp=${JSON.stringify(uploadUrlResp)}`);
|
|
60
|
+
throw new Error(`${label}: getUploadUrl returned no upload URL`);
|
|
61
|
+
}
|
|
62
|
+
const { downloadParam: downloadEncryptedQueryParam } = await uploadBufferToCdn({
|
|
63
|
+
buf: plaintext,
|
|
64
|
+
uploadFullUrl: uploadFullUrl || undefined,
|
|
65
|
+
uploadParam: uploadParam ?? undefined,
|
|
66
|
+
filekey,
|
|
67
|
+
cdnBaseUrl,
|
|
68
|
+
aeskey,
|
|
69
|
+
label: `${label}[orig filekey=${filekey}]`,
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
filekey,
|
|
73
|
+
downloadEncryptedQueryParam,
|
|
74
|
+
aeskey: aeskey.toString("hex"),
|
|
75
|
+
fileSize: rawsize,
|
|
76
|
+
fileSizeCiphertext: filesize,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** Upload a local image file to the Weixin CDN with AES-128-ECB encryption. */
|
|
80
|
+
export async function uploadFileToWeixin(params) {
|
|
81
|
+
return uploadMediaToCdn({
|
|
82
|
+
...params,
|
|
83
|
+
mediaType: UploadMediaType.IMAGE,
|
|
84
|
+
label: "uploadFileToWeixin",
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/** Upload a local video file to the Weixin CDN. */
|
|
88
|
+
export async function uploadVideoToWeixin(params) {
|
|
89
|
+
return uploadMediaToCdn({
|
|
90
|
+
...params,
|
|
91
|
+
mediaType: UploadMediaType.VIDEO,
|
|
92
|
+
label: "uploadVideoToWeixin",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Upload a local file attachment (non-image, non-video) to the Weixin CDN.
|
|
97
|
+
* Uses media_type=FILE; no thumbnail required.
|
|
98
|
+
*/
|
|
99
|
+
export async function uploadFileAttachmentToWeixin(params) {
|
|
100
|
+
return uploadMediaToCdn({
|
|
101
|
+
...params,
|
|
102
|
+
mediaType: UploadMediaType.FILE,
|
|
103
|
+
label: "uploadFileAttachmentToWeixin",
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=upload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.js","sourceRoot":"","sources":["../../src/cdn/upload.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { startWeixinLoginWithQr, waitForWeixinLogin } from "./auth/login-qr.js";
|
|
4
|
+
import { saveWeixinAccount, registerWeixinAccountId, clearStaleAccountsForUserId, normalizeAccountId, resolveWeixinAccount, listIndexedWeixinAccountIds, DEFAULT_BASE_URL, } from "./auth/accounts.js";
|
|
5
|
+
import { monitorWeixinProvider } from "./monitor/monitor.js";
|
|
6
|
+
import { sendMessageWeixin } from "./messaging/send.js";
|
|
7
|
+
import { logger } from "./util/logger.js";
|
|
8
|
+
const program = new Command();
|
|
9
|
+
program
|
|
10
|
+
.name("weixin-bot-cli")
|
|
11
|
+
.description("Weixin Bot CLI for receiving messages")
|
|
12
|
+
.version("1.0.0")
|
|
13
|
+
.option("-d, --dir <path>", "Home directory for data storage (overrides ~/.weixin-bot-cli)")
|
|
14
|
+
.option("-h, --home <path>", "Alias for --dir")
|
|
15
|
+
.hook("preAction", (thisCommand) => {
|
|
16
|
+
const opts = thisCommand.opts();
|
|
17
|
+
const homeDir = opts.home || opts.dir;
|
|
18
|
+
if (homeDir) {
|
|
19
|
+
process.env.WEIXIN_BOT_HOME = homeDir;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
program
|
|
23
|
+
.command("login")
|
|
24
|
+
.description("Login to WeChat via QR code")
|
|
25
|
+
.action(async () => {
|
|
26
|
+
try {
|
|
27
|
+
console.log("🚀 开始获取登录二维码...");
|
|
28
|
+
const startResult = await startWeixinLoginWithQr({
|
|
29
|
+
apiBaseUrl: DEFAULT_BASE_URL,
|
|
30
|
+
verbose: true,
|
|
31
|
+
});
|
|
32
|
+
if (!startResult.qrcodeUrl) {
|
|
33
|
+
console.error("❌ 获取二维码失败:", startResult.message);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
console.log("\n⏳ 等待微信扫码并确认...");
|
|
37
|
+
const waitResult = await waitForWeixinLogin({
|
|
38
|
+
sessionKey: startResult.sessionKey,
|
|
39
|
+
apiBaseUrl: DEFAULT_BASE_URL,
|
|
40
|
+
verbose: true,
|
|
41
|
+
});
|
|
42
|
+
if (waitResult.connected && waitResult.botToken && waitResult.accountId) {
|
|
43
|
+
try {
|
|
44
|
+
const normalizedId = normalizeAccountId(waitResult.accountId);
|
|
45
|
+
saveWeixinAccount(normalizedId, {
|
|
46
|
+
token: waitResult.botToken,
|
|
47
|
+
baseUrl: waitResult.baseUrl || DEFAULT_BASE_URL,
|
|
48
|
+
userId: waitResult.userId,
|
|
49
|
+
});
|
|
50
|
+
registerWeixinAccountId(normalizedId);
|
|
51
|
+
if (waitResult.userId) {
|
|
52
|
+
clearStaleAccountsForUserId(normalizedId, waitResult.userId);
|
|
53
|
+
}
|
|
54
|
+
console.log(`\n✅ 登录成功!已保存账号凭证 (Account ID: ${normalizedId})`);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
logger.error(`保存账号数据失败 accountId=${waitResult.accountId} err=${String(err)}`);
|
|
58
|
+
console.error(`⚠️ 保存账号数据失败: ${String(err)}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
console.error("❌ 登录未成功:", waitResult.message);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
console.error("❌ 登录过程中发生错误:", err);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
program
|
|
70
|
+
.command("start")
|
|
71
|
+
.description("Start polling for incoming messages")
|
|
72
|
+
.action(async () => {
|
|
73
|
+
const ids = listIndexedWeixinAccountIds();
|
|
74
|
+
if (ids.length === 0) {
|
|
75
|
+
console.error("❌ 未找到已登录的账号,请先运行 `weixin-bot-cli login`。");
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
const accountId = ids[0];
|
|
79
|
+
const accountInfo = resolveWeixinAccount(accountId);
|
|
80
|
+
if (!accountInfo.token) {
|
|
81
|
+
console.error(`❌ 账号 ${accountId} 凭证无效或未配置。`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
await monitorWeixinProvider({
|
|
86
|
+
baseUrl: accountInfo.baseUrl,
|
|
87
|
+
token: accountInfo.token,
|
|
88
|
+
accountId: accountInfo.accountId,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
console.error("❌ 监听发生错误:", err);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
program
|
|
97
|
+
.command("send <to> <text>")
|
|
98
|
+
.description("Send a text message to a specific WeChat user")
|
|
99
|
+
.action(async (to, text) => {
|
|
100
|
+
const ids = listIndexedWeixinAccountIds();
|
|
101
|
+
if (ids.length === 0) {
|
|
102
|
+
console.error("❌ 未找到已登录的账号,请先运行 `weixin-bot-cli login`。");
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
const accountInfo = resolveWeixinAccount(ids[0]);
|
|
106
|
+
if (!accountInfo.token) {
|
|
107
|
+
console.error("❌ 账号凭证无效。");
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
await sendMessageWeixin({
|
|
112
|
+
to,
|
|
113
|
+
text,
|
|
114
|
+
opts: {
|
|
115
|
+
baseUrl: accountInfo.baseUrl,
|
|
116
|
+
token: accountInfo.token,
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
console.log(`✅ 消息已成功发送给: ${to}`);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
console.error("❌ 消息发送失败:", err);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
program.parse(process.argv);
|
|
127
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Top-level weixin config schema (token is stored in credentials file, not config). */
|
|
3
|
+
export declare const WeixinConfigSchema: z.ZodObject<{
|
|
4
|
+
name: z.ZodOptional<z.ZodString>;
|
|
5
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
6
|
+
baseUrl: z.ZodDefault<z.ZodString>;
|
|
7
|
+
cdnBaseUrl: z.ZodDefault<z.ZodString>;
|
|
8
|
+
routeTag: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
accounts: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
10
|
+
name: z.ZodOptional<z.ZodString>;
|
|
11
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
12
|
+
baseUrl: z.ZodDefault<z.ZodString>;
|
|
13
|
+
cdnBaseUrl: z.ZodDefault<z.ZodString>;
|
|
14
|
+
routeTag: z.ZodOptional<z.ZodNumber>;
|
|
15
|
+
}, z.core.$strip>>>;
|
|
16
|
+
}, z.core.$strip>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CDN_BASE_URL, DEFAULT_BASE_URL } from "../auth/accounts.js";
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Zod config schema
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
const weixinAccountSchema = z.object({
|
|
7
|
+
name: z.string().optional(),
|
|
8
|
+
enabled: z.boolean().optional(),
|
|
9
|
+
baseUrl: z.string().default(DEFAULT_BASE_URL),
|
|
10
|
+
cdnBaseUrl: z.string().default(CDN_BASE_URL),
|
|
11
|
+
routeTag: z.number().optional(),
|
|
12
|
+
});
|
|
13
|
+
/** Top-level weixin config schema (token is stored in credentials file, not config). */
|
|
14
|
+
export const WeixinConfigSchema = weixinAccountSchema.extend({
|
|
15
|
+
accounts: z.record(z.string(), weixinAccountSchema).optional(),
|
|
16
|
+
});
|
|
17
|
+
//# sourceMappingURL=config-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-schema.js","sourceRoot":"","sources":["../../src/config/config-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAErE,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC/B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,gBAAgB,CAAC;IAC7C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC;IAC5C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAChC,CAAC,CAAC;AAEH,wFAAwF;AACxF,MAAM,CAAC,MAAM,kBAAkB,GAAG,mBAAmB,CAAC,MAAM,CAAC;IAC3D,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,mBAAmB,CAAC,CAAC,QAAQ,EAAE;CAC/D,CAAC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { WeixinInboundMediaOpts } from "../messaging/inbound.js";
|
|
2
|
+
import type { WeixinMessage } from "../api/types.js";
|
|
3
|
+
/** Persist a buffer via the framework's unified media store. */
|
|
4
|
+
type SaveMediaFn = (buffer: Buffer, contentType?: string, subdir?: string, maxBytes?: number, originalFilename?: string) => Promise<{
|
|
5
|
+
path: string;
|
|
6
|
+
}>;
|
|
7
|
+
/**
|
|
8
|
+
* Download and decrypt media from a single MessageItem.
|
|
9
|
+
* Returns the populated WeixinInboundMediaOpts fields; empty object on unsupported type or failure.
|
|
10
|
+
*/
|
|
11
|
+
export declare function downloadMediaFromItem(item: WeixinMessage["item_list"] extends (infer T)[] | undefined ? T : never, deps: {
|
|
12
|
+
cdnBaseUrl: string;
|
|
13
|
+
saveMedia: SaveMediaFn;
|
|
14
|
+
log: (msg: string) => void;
|
|
15
|
+
errLog: (msg: string) => void;
|
|
16
|
+
label: string;
|
|
17
|
+
}): Promise<WeixinInboundMediaOpts>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { logger } from "../util/logger.js";
|
|
2
|
+
import { getMimeFromFilename } from "./mime.js";
|
|
3
|
+
import { downloadAndDecryptBuffer, downloadPlainCdnBuffer, } from "../cdn/pic-decrypt.js";
|
|
4
|
+
import { silkToWav } from "./silk-transcode.js";
|
|
5
|
+
import { MessageItemType } from "../api/types.js";
|
|
6
|
+
const WEIXIN_MEDIA_MAX_BYTES = 100 * 1024 * 1024;
|
|
7
|
+
/**
|
|
8
|
+
* Download and decrypt media from a single MessageItem.
|
|
9
|
+
* Returns the populated WeixinInboundMediaOpts fields; empty object on unsupported type or failure.
|
|
10
|
+
*/
|
|
11
|
+
export async function downloadMediaFromItem(item, deps) {
|
|
12
|
+
const { cdnBaseUrl, saveMedia, log, errLog, label } = deps;
|
|
13
|
+
const result = {};
|
|
14
|
+
if (item.type === MessageItemType.IMAGE) {
|
|
15
|
+
const img = item.image_item;
|
|
16
|
+
if (!img?.media?.encrypt_query_param && !img?.media?.full_url)
|
|
17
|
+
return result;
|
|
18
|
+
const aesKeyBase64 = img.aeskey
|
|
19
|
+
? Buffer.from(img.aeskey, "hex").toString("base64")
|
|
20
|
+
: img.media.aes_key;
|
|
21
|
+
logger.debug(`${label} image: encrypt_query_param=${(img.media.encrypt_query_param ?? "").slice(0, 40)}... hasAesKey=${Boolean(aesKeyBase64)} aeskeySource=${img.aeskey ? "image_item.aeskey" : "media.aes_key"} full_url=${Boolean(img.media.full_url)}`);
|
|
22
|
+
try {
|
|
23
|
+
const buf = aesKeyBase64
|
|
24
|
+
? await downloadAndDecryptBuffer(img.media.encrypt_query_param ?? "", aesKeyBase64, cdnBaseUrl, `${label} image`, img.media.full_url)
|
|
25
|
+
: await downloadPlainCdnBuffer(img.media.encrypt_query_param ?? "", cdnBaseUrl, `${label} image-plain`, img.media.full_url);
|
|
26
|
+
const saved = await saveMedia(buf, undefined, "inbound", WEIXIN_MEDIA_MAX_BYTES);
|
|
27
|
+
result.decryptedPicPath = saved.path;
|
|
28
|
+
logger.debug(`${label} image saved: ${saved.path}`);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
logger.error(`${label} image download/decrypt failed: ${String(err)}`);
|
|
32
|
+
errLog(`weixin ${label} image download/decrypt failed: ${String(err)}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (item.type === MessageItemType.VOICE) {
|
|
36
|
+
const voice = item.voice_item;
|
|
37
|
+
if ((!voice?.media?.encrypt_query_param && !voice?.media?.full_url) || !voice?.media?.aes_key)
|
|
38
|
+
return result;
|
|
39
|
+
try {
|
|
40
|
+
const silkBuf = await downloadAndDecryptBuffer(voice.media.encrypt_query_param ?? "", voice.media.aes_key, cdnBaseUrl, `${label} voice`, voice.media.full_url);
|
|
41
|
+
logger.debug(`${label} voice: decrypted ${silkBuf.length} bytes, attempting silk transcode`);
|
|
42
|
+
const wavBuf = await silkToWav(silkBuf);
|
|
43
|
+
if (wavBuf) {
|
|
44
|
+
const saved = await saveMedia(wavBuf, "audio/wav", "inbound", WEIXIN_MEDIA_MAX_BYTES);
|
|
45
|
+
result.decryptedVoicePath = saved.path;
|
|
46
|
+
result.voiceMediaType = "audio/wav";
|
|
47
|
+
logger.debug(`${label} voice: saved WAV to ${saved.path}`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
const saved = await saveMedia(silkBuf, "audio/silk", "inbound", WEIXIN_MEDIA_MAX_BYTES);
|
|
51
|
+
result.decryptedVoicePath = saved.path;
|
|
52
|
+
result.voiceMediaType = "audio/silk";
|
|
53
|
+
logger.debug(`${label} voice: silk transcode unavailable, saved raw SILK to ${saved.path}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
logger.error(`${label} voice download/transcode failed: ${String(err)}`);
|
|
58
|
+
errLog(`weixin ${label} voice download/transcode failed: ${String(err)}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else if (item.type === MessageItemType.FILE) {
|
|
62
|
+
const fileItem = item.file_item;
|
|
63
|
+
if ((!fileItem?.media?.encrypt_query_param && !fileItem?.media?.full_url) || !fileItem?.media?.aes_key)
|
|
64
|
+
return result;
|
|
65
|
+
try {
|
|
66
|
+
const buf = await downloadAndDecryptBuffer(fileItem.media.encrypt_query_param ?? "", fileItem.media.aes_key, cdnBaseUrl, `${label} file`, fileItem.media.full_url);
|
|
67
|
+
const mime = getMimeFromFilename(fileItem.file_name ?? "file.bin");
|
|
68
|
+
const saved = await saveMedia(buf, mime, "inbound", WEIXIN_MEDIA_MAX_BYTES, fileItem.file_name ?? undefined);
|
|
69
|
+
result.decryptedFilePath = saved.path;
|
|
70
|
+
result.fileMediaType = mime;
|
|
71
|
+
logger.debug(`${label} file: saved to ${saved.path} mime=${mime}`);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
logger.error(`${label} file download failed: ${String(err)}`);
|
|
75
|
+
errLog(`weixin ${label} file download failed: ${String(err)}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else if (item.type === MessageItemType.VIDEO) {
|
|
79
|
+
const videoItem = item.video_item;
|
|
80
|
+
if ((!videoItem?.media?.encrypt_query_param && !videoItem?.media?.full_url) || !videoItem?.media?.aes_key)
|
|
81
|
+
return result;
|
|
82
|
+
try {
|
|
83
|
+
const buf = await downloadAndDecryptBuffer(videoItem.media.encrypt_query_param ?? "", videoItem.media.aes_key, cdnBaseUrl, `${label} video`, videoItem.media.full_url);
|
|
84
|
+
const saved = await saveMedia(buf, "video/mp4", "inbound", WEIXIN_MEDIA_MAX_BYTES);
|
|
85
|
+
result.decryptedVideoPath = saved.path;
|
|
86
|
+
logger.debug(`${label} video: saved to ${saved.path}`);
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
logger.error(`${label} video download failed: ${String(err)}`);
|
|
90
|
+
errLog(`weixin ${label} video download failed: ${String(err)}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=media-download.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-download.js","sourceRoot":"","sources":["../../src/media/media-download.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Get MIME type from filename extension. Returns "application/octet-stream" for unknown extensions. */
|
|
2
|
+
export declare function getMimeFromFilename(filename: string): string;
|
|
3
|
+
/** Get file extension from MIME type. Returns ".bin" for unknown types. */
|
|
4
|
+
export declare function getExtensionFromMime(mimeType: string): string;
|
|
5
|
+
/** Get file extension from Content-Type header or URL path. Returns ".bin" for unknown. */
|
|
6
|
+
export declare function getExtensionFromContentTypeOrUrl(contentType: string | null, url: string): string;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
const EXTENSION_TO_MIME = {
|
|
3
|
+
".pdf": "application/pdf",
|
|
4
|
+
".doc": "application/msword",
|
|
5
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
6
|
+
".xls": "application/vnd.ms-excel",
|
|
7
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
8
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
9
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
10
|
+
".txt": "text/plain",
|
|
11
|
+
".csv": "text/csv",
|
|
12
|
+
".zip": "application/zip",
|
|
13
|
+
".tar": "application/x-tar",
|
|
14
|
+
".gz": "application/gzip",
|
|
15
|
+
".mp3": "audio/mpeg",
|
|
16
|
+
".ogg": "audio/ogg",
|
|
17
|
+
".wav": "audio/wav",
|
|
18
|
+
".mp4": "video/mp4",
|
|
19
|
+
".mov": "video/quicktime",
|
|
20
|
+
".webm": "video/webm",
|
|
21
|
+
".mkv": "video/x-matroska",
|
|
22
|
+
".avi": "video/x-msvideo",
|
|
23
|
+
".png": "image/png",
|
|
24
|
+
".jpg": "image/jpeg",
|
|
25
|
+
".jpeg": "image/jpeg",
|
|
26
|
+
".gif": "image/gif",
|
|
27
|
+
".webp": "image/webp",
|
|
28
|
+
".bmp": "image/bmp",
|
|
29
|
+
};
|
|
30
|
+
const MIME_TO_EXTENSION = {
|
|
31
|
+
"image/jpeg": ".jpg",
|
|
32
|
+
"image/jpg": ".jpg",
|
|
33
|
+
"image/png": ".png",
|
|
34
|
+
"image/gif": ".gif",
|
|
35
|
+
"image/webp": ".webp",
|
|
36
|
+
"image/bmp": ".bmp",
|
|
37
|
+
"video/mp4": ".mp4",
|
|
38
|
+
"video/quicktime": ".mov",
|
|
39
|
+
"video/webm": ".webm",
|
|
40
|
+
"video/x-matroska": ".mkv",
|
|
41
|
+
"video/x-msvideo": ".avi",
|
|
42
|
+
"audio/mpeg": ".mp3",
|
|
43
|
+
"audio/ogg": ".ogg",
|
|
44
|
+
"audio/wav": ".wav",
|
|
45
|
+
"application/pdf": ".pdf",
|
|
46
|
+
"application/zip": ".zip",
|
|
47
|
+
"application/x-tar": ".tar",
|
|
48
|
+
"application/gzip": ".gz",
|
|
49
|
+
"text/plain": ".txt",
|
|
50
|
+
"text/csv": ".csv",
|
|
51
|
+
};
|
|
52
|
+
/** Get MIME type from filename extension. Returns "application/octet-stream" for unknown extensions. */
|
|
53
|
+
export function getMimeFromFilename(filename) {
|
|
54
|
+
const ext = path.extname(filename).toLowerCase();
|
|
55
|
+
return EXTENSION_TO_MIME[ext] ?? "application/octet-stream";
|
|
56
|
+
}
|
|
57
|
+
/** Get file extension from MIME type. Returns ".bin" for unknown types. */
|
|
58
|
+
export function getExtensionFromMime(mimeType) {
|
|
59
|
+
const ct = mimeType.split(";")[0].trim().toLowerCase();
|
|
60
|
+
return MIME_TO_EXTENSION[ct] ?? ".bin";
|
|
61
|
+
}
|
|
62
|
+
/** Get file extension from Content-Type header or URL path. Returns ".bin" for unknown. */
|
|
63
|
+
export function getExtensionFromContentTypeOrUrl(contentType, url) {
|
|
64
|
+
if (contentType) {
|
|
65
|
+
const ext = getExtensionFromMime(contentType);
|
|
66
|
+
if (ext !== ".bin")
|
|
67
|
+
return ext;
|
|
68
|
+
}
|
|
69
|
+
const ext = path.extname(new URL(url).pathname).toLowerCase();
|
|
70
|
+
const knownExts = new Set(Object.keys(EXTENSION_TO_MIME));
|
|
71
|
+
return knownExts.has(ext) ? ext : ".bin";
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=mime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mime.js","sourceRoot":"","sources":["../../src/media/mime.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,MAAM,iBAAiB,GAA2B;IAChD,MAAM,EAAE,iBAAiB;IACzB,MAAM,EAAE,oBAAoB;IAC5B,OAAO,EAAE,yEAAyE;IAClF,MAAM,EAAE,0BAA0B;IAClC,OAAO,EAAE,mEAAmE;IAC5E,MAAM,EAAE,+BAA+B;IACvC,OAAO,EAAE,2EAA2E;IACpF,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,UAAU;IAClB,MAAM,EAAE,iBAAiB;IACzB,MAAM,EAAE,mBAAmB;IAC3B,KAAK,EAAE,kBAAkB;IACzB,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,iBAAiB;IACzB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,kBAAkB;IAC1B,MAAM,EAAE,iBAAiB;IACzB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;CACpB,CAAC;AAEF,MAAM,iBAAiB,GAA2B;IAChD,YAAY,EAAE,MAAM;IACpB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,YAAY,EAAE,OAAO;IACrB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,iBAAiB,EAAE,MAAM;IACzB,YAAY,EAAE,OAAO;IACrB,kBAAkB,EAAE,MAAM;IAC1B,iBAAiB,EAAE,MAAM;IACzB,YAAY,EAAE,MAAM;IACpB,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,iBAAiB,EAAE,MAAM;IACzB,iBAAiB,EAAE,MAAM;IACzB,mBAAmB,EAAE,MAAM;IAC3B,kBAAkB,EAAE,KAAK;IACzB,YAAY,EAAE,MAAM;IACpB,UAAU,EAAE,MAAM;CACnB,CAAC;AAEF,wGAAwG;AACxG,MAAM,UAAU,mBAAmB,CAAC,QAAgB;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IACjD,OAAO,iBAAiB,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC;AAC9D,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACnD,MAAM,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACvD,OAAO,iBAAiB,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC;AACzC,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,gCAAgC,CAAC,WAA0B,EAAE,GAAW;IACtF,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,GAAG,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC;QAC9C,IAAI,GAAG,KAAK,MAAM;YAAE,OAAO,GAAG,CAAC;IACjC,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9D,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAC1D,OAAO,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Try to transcode a SILK audio buffer to WAV using silk-wasm.
|
|
3
|
+
* silk-wasm's decode() returns { data: Uint8Array (pcm_s16le), duration: number }.
|
|
4
|
+
*
|
|
5
|
+
* Returns a WAV Buffer on success, or null if silk-wasm is unavailable or decoding fails.
|
|
6
|
+
* Callers should fall back to passing the raw SILK file when null is returned.
|
|
7
|
+
*/
|
|
8
|
+
export declare function silkToWav(silkBuf: Buffer): Promise<Buffer | null>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { logger } from "../util/logger.js";
|
|
2
|
+
/** Default sample rate for Weixin voice messages. */
|
|
3
|
+
const SILK_SAMPLE_RATE = 24_000;
|
|
4
|
+
/**
|
|
5
|
+
* Wrap raw pcm_s16le bytes in a WAV container.
|
|
6
|
+
* Mono channel, 16-bit signed little-endian.
|
|
7
|
+
*/
|
|
8
|
+
function pcmBytesToWav(pcm, sampleRate) {
|
|
9
|
+
const pcmBytes = pcm.byteLength;
|
|
10
|
+
const totalSize = 44 + pcmBytes;
|
|
11
|
+
const buf = Buffer.allocUnsafe(totalSize);
|
|
12
|
+
let offset = 0;
|
|
13
|
+
buf.write("RIFF", offset);
|
|
14
|
+
offset += 4;
|
|
15
|
+
buf.writeUInt32LE(totalSize - 8, offset);
|
|
16
|
+
offset += 4;
|
|
17
|
+
buf.write("WAVE", offset);
|
|
18
|
+
offset += 4;
|
|
19
|
+
buf.write("fmt ", offset);
|
|
20
|
+
offset += 4;
|
|
21
|
+
buf.writeUInt32LE(16, offset);
|
|
22
|
+
offset += 4; // fmt chunk size
|
|
23
|
+
buf.writeUInt16LE(1, offset);
|
|
24
|
+
offset += 2; // PCM format
|
|
25
|
+
buf.writeUInt16LE(1, offset);
|
|
26
|
+
offset += 2; // mono
|
|
27
|
+
buf.writeUInt32LE(sampleRate, offset);
|
|
28
|
+
offset += 4;
|
|
29
|
+
buf.writeUInt32LE(sampleRate * 2, offset);
|
|
30
|
+
offset += 4; // byte rate (mono 16-bit)
|
|
31
|
+
buf.writeUInt16LE(2, offset);
|
|
32
|
+
offset += 2; // block align
|
|
33
|
+
buf.writeUInt16LE(16, offset);
|
|
34
|
+
offset += 2; // bits per sample
|
|
35
|
+
buf.write("data", offset);
|
|
36
|
+
offset += 4;
|
|
37
|
+
buf.writeUInt32LE(pcmBytes, offset);
|
|
38
|
+
offset += 4;
|
|
39
|
+
Buffer.from(pcm.buffer, pcm.byteOffset, pcm.byteLength).copy(buf, offset);
|
|
40
|
+
return buf;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Try to transcode a SILK audio buffer to WAV using silk-wasm.
|
|
44
|
+
* silk-wasm's decode() returns { data: Uint8Array (pcm_s16le), duration: number }.
|
|
45
|
+
*
|
|
46
|
+
* Returns a WAV Buffer on success, or null if silk-wasm is unavailable or decoding fails.
|
|
47
|
+
* Callers should fall back to passing the raw SILK file when null is returned.
|
|
48
|
+
*/
|
|
49
|
+
export async function silkToWav(silkBuf) {
|
|
50
|
+
try {
|
|
51
|
+
const { decode } = await import("silk-wasm");
|
|
52
|
+
logger.debug(`silkToWav: decoding ${silkBuf.length} bytes of SILK`);
|
|
53
|
+
const result = await decode(silkBuf, SILK_SAMPLE_RATE);
|
|
54
|
+
logger.debug(`silkToWav: decoded duration=${result.duration}ms pcmBytes=${result.data.byteLength}`);
|
|
55
|
+
const wav = pcmBytesToWav(result.data, SILK_SAMPLE_RATE);
|
|
56
|
+
logger.debug(`silkToWav: WAV size=${wav.length}`);
|
|
57
|
+
return wav;
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
logger.warn(`silkToWav: transcode failed, will use raw silk err=${String(err)}`);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=silk-transcode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"silk-transcode.js","sourceRoot":"","sources":["../../src/media/silk-transcode.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Toggle debug mode for a bot account. Returns the new state. */
|
|
2
|
+
export declare function toggleDebugMode(accountId: string): boolean;
|
|
3
|
+
/** Check whether debug mode is active for a bot account. */
|
|
4
|
+
export declare function isDebugMode(accountId: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Reset internal state — only for tests.
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export declare function _resetForTest(): void;
|