@zhin.js/adapter-weixin-ilink 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/README.md +96 -0
- package/client/LoginAssistPanel.tsx +200 -0
- package/client/WeixinIlinkManagement.tsx +139 -0
- package/client/index.tsx +11 -0
- package/client/tsconfig.json +13 -0
- package/client/utils/api.ts +30 -0
- package/dist/index.js +29 -0
- package/lib/adapter.d.ts +11 -0
- package/lib/adapter.d.ts.map +1 -0
- package/lib/adapter.js +14 -0
- package/lib/adapter.js.map +1 -0
- package/lib/aes-ecb.d.ts +7 -0
- package/lib/aes-ecb.d.ts.map +1 -0
- package/lib/aes-ecb.js +19 -0
- package/lib/aes-ecb.js.map +1 -0
- package/lib/bot.d.ts +37 -0
- package/lib/bot.d.ts.map +1 -0
- package/lib/bot.js +383 -0
- package/lib/bot.js.map +1 -0
- package/lib/cdn-upload.d.ts +18 -0
- package/lib/cdn-upload.d.ts.map +1 -0
- package/lib/cdn-upload.js +73 -0
- package/lib/cdn-upload.js.map +1 -0
- package/lib/cdn-url.d.ts +14 -0
- package/lib/cdn-url.d.ts.map +1 -0
- package/lib/cdn-url.js +14 -0
- package/lib/cdn-url.js.map +1 -0
- package/lib/context-store.d.ts +2 -0
- package/lib/context-store.d.ts.map +1 -0
- package/lib/context-store.js +2 -0
- package/lib/context-store.js.map +1 -0
- package/lib/credentials.d.ts +15 -0
- package/lib/credentials.d.ts.map +1 -0
- package/lib/credentials.js +49 -0
- package/lib/credentials.js.map +1 -0
- package/lib/ilink-api.d.ts +81 -0
- package/lib/ilink-api.d.ts.map +1 -0
- package/lib/ilink-api.js +273 -0
- package/lib/ilink-api.js.map +1 -0
- package/lib/ilink-config-cache.d.ts +19 -0
- package/lib/ilink-config-cache.d.ts.map +1 -0
- package/lib/ilink-config-cache.js +64 -0
- package/lib/ilink-config-cache.js.map +1 -0
- package/lib/ilink-logger.d.ts +10 -0
- package/lib/ilink-logger.d.ts.map +1 -0
- package/lib/ilink-logger.js +27 -0
- package/lib/ilink-logger.js.map +1 -0
- package/lib/ilink-meta.d.ts +13 -0
- package/lib/ilink-meta.d.ts.map +1 -0
- package/lib/ilink-meta.js +57 -0
- package/lib/ilink-meta.js.map +1 -0
- package/lib/ilink-random.d.ts +11 -0
- package/lib/ilink-random.d.ts.map +1 -0
- package/lib/ilink-random.js +16 -0
- package/lib/ilink-random.js.map +1 -0
- package/lib/ilink-redact.d.ts +21 -0
- package/lib/ilink-redact.d.ts.map +1 -0
- package/lib/ilink-redact.js +54 -0
- package/lib/ilink-redact.js.map +1 -0
- package/lib/ilink-session-guard.d.ts +16 -0
- package/lib/ilink-session-guard.d.ts.map +1 -0
- package/lib/ilink-session-guard.js +49 -0
- package/lib/ilink-session-guard.js.map +1 -0
- package/lib/ilink-types.d.ts +233 -0
- package/lib/ilink-types.d.ts.map +1 -0
- package/lib/ilink-types.js +35 -0
- package/lib/ilink-types.js.map +1 -0
- package/lib/index.d.ts +20 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +38 -0
- package/lib/index.js.map +1 -0
- package/lib/login-assist-routes.d.ts +7 -0
- package/lib/login-assist-routes.d.ts.map +1 -0
- package/lib/login-assist-routes.js +29 -0
- package/lib/login-assist-routes.js.map +1 -0
- package/lib/login.d.ts +6 -0
- package/lib/login.d.ts.map +1 -0
- package/lib/login.js +121 -0
- package/lib/login.js.map +1 -0
- package/lib/media-download.d.ts +19 -0
- package/lib/media-download.d.ts.map +1 -0
- package/lib/media-download.js +95 -0
- package/lib/media-download.js.map +1 -0
- package/lib/mime.d.ts +9 -0
- package/lib/mime.d.ts.map +1 -0
- package/lib/mime.js +99 -0
- package/lib/mime.js.map +1 -0
- package/lib/outbound-media.d.ts +5 -0
- package/lib/outbound-media.d.ts.map +1 -0
- package/lib/outbound-media.js +122 -0
- package/lib/outbound-media.js.map +1 -0
- package/lib/pic-decrypt.d.ts +10 -0
- package/lib/pic-decrypt.d.ts.map +1 -0
- package/lib/pic-decrypt.js +89 -0
- package/lib/pic-decrypt.js.map +1 -0
- package/lib/routes.d.ts +8 -0
- package/lib/routes.d.ts.map +1 -0
- package/lib/routes.js +29 -0
- package/lib/routes.js.map +1 -0
- package/lib/silk-transcode.d.ts +9 -0
- package/lib/silk-transcode.d.ts.map +1 -0
- package/lib/silk-transcode.js +64 -0
- package/lib/silk-transcode.js.map +1 -0
- package/lib/types.d.ts +26 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -0
- package/lib/typing-indicator.d.ts +32 -0
- package/lib/typing-indicator.d.ts.map +1 -0
- package/lib/typing-indicator.js +95 -0
- package/lib/typing-indicator.js.map +1 -0
- package/lib/upload.d.ts +43 -0
- package/lib/upload.d.ts.map +1 -0
- package/lib/upload.js +106 -0
- package/lib/upload.js.map +1 -0
- package/lib/weixin-inbound.d.ts +71 -0
- package/lib/weixin-inbound.d.ts.map +1 -0
- package/lib/weixin-inbound.js +201 -0
- package/lib/weixin-inbound.js.map +1 -0
- package/lib/weixin-send-media.d.ts +22 -0
- package/lib/weixin-send-media.d.ts.map +1 -0
- package/lib/weixin-send-media.js +54 -0
- package/lib/weixin-send-media.js.map +1 -0
- package/lib/weixin-send.d.ts +65 -0
- package/lib/weixin-send.d.ts.map +1 -0
- package/lib/weixin-send.js +181 -0
- package/lib/weixin-send.js.map +1 -0
- package/package.json +91 -0
- package/src/adapter.ts +16 -0
- package/src/aes-ecb.ts +21 -0
- package/src/bot.ts +483 -0
- package/src/cdn-upload.test.ts +177 -0
- package/src/cdn-upload.ts +87 -0
- package/src/cdn-url.ts +20 -0
- package/src/context-store.ts +8 -0
- package/src/credentials.ts +64 -0
- package/src/ilink-api.ts +349 -0
- package/src/ilink-config-cache.ts +79 -0
- package/src/ilink-logger.ts +36 -0
- package/src/ilink-meta.ts +60 -0
- package/src/ilink-random.ts +17 -0
- package/src/ilink-redact.ts +54 -0
- package/src/ilink-session-guard.ts +58 -0
- package/src/ilink-types.ts +261 -0
- package/src/index.ts +60 -0
- package/src/login-assist-routes.ts +36 -0
- package/src/login.ts +166 -0
- package/src/media-download.ts +149 -0
- package/src/mime.test.ts +74 -0
- package/src/mime.ts +105 -0
- package/src/outbound-media.ts +141 -0
- package/src/pic-decrypt.ts +101 -0
- package/src/routes.ts +39 -0
- package/src/silk-transcode.test.ts +58 -0
- package/src/silk-transcode.ts +74 -0
- package/src/silk-wasm.d.ts +6 -0
- package/src/types.ts +26 -0
- package/src/typing-indicator.ts +139 -0
- package/src/upload.test.ts +305 -0
- package/src/upload.ts +157 -0
- package/src/weixin-inbound.ts +257 -0
- package/src/weixin-send-media.ts +72 -0
- package/src/weixin-send.ts +237 -0
package/lib/login.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { apiGetFetch, apiPostFetch } from "./ilink-api.js";
|
|
2
|
+
import { buildBaseInfo, DEFAULT_API_BASE_URL } from "./ilink-meta.js";
|
|
3
|
+
import { logger } from "./ilink-logger.js";
|
|
4
|
+
import { loadCredentials, saveCredentials, } from "./credentials.js";
|
|
5
|
+
const DEFAULT_ILINK_BOT_TYPE = "3";
|
|
6
|
+
const QR_LONG_POLL_TIMEOUT_MS = 35_000;
|
|
7
|
+
const LOGIN_TIMEOUT_MS = 480_000;
|
|
8
|
+
function getLocalBotTokenList() {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
async function fetchQRCode(apiBaseUrl, botType) {
|
|
12
|
+
const rawText = await apiPostFetch({
|
|
13
|
+
baseUrl: apiBaseUrl,
|
|
14
|
+
endpoint: `ilink/bot/get_bot_qrcode?bot_type=${encodeURIComponent(botType)}`,
|
|
15
|
+
body: JSON.stringify({ local_token_list: getLocalBotTokenList(), base_info: buildBaseInfo() }),
|
|
16
|
+
label: "fetchQRCode",
|
|
17
|
+
});
|
|
18
|
+
return JSON.parse(rawText);
|
|
19
|
+
}
|
|
20
|
+
async function pollQRStatus(apiBaseUrl, qrcode) {
|
|
21
|
+
try {
|
|
22
|
+
const endpoint = `ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrcode)}`;
|
|
23
|
+
const rawText = await apiGetFetch({
|
|
24
|
+
baseUrl: apiBaseUrl,
|
|
25
|
+
endpoint,
|
|
26
|
+
timeoutMs: QR_LONG_POLL_TIMEOUT_MS,
|
|
27
|
+
label: "pollQRStatus",
|
|
28
|
+
});
|
|
29
|
+
return JSON.parse(rawText);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
33
|
+
return { status: "wait" };
|
|
34
|
+
}
|
|
35
|
+
logger.warn(`pollQRStatus error: ${String(err)}`);
|
|
36
|
+
return { status: "wait" };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function resolveCredentials(plugin, config) {
|
|
40
|
+
const envToken = process.env.WEIXIN_ILINK_TOKEN?.trim() || config.botToken?.trim();
|
|
41
|
+
if (envToken) {
|
|
42
|
+
const stored = loadCredentials(config.name);
|
|
43
|
+
return {
|
|
44
|
+
botToken: envToken,
|
|
45
|
+
ilinkUserId: stored?.ilinkUserId,
|
|
46
|
+
ilinkBotId: stored?.ilinkBotId,
|
|
47
|
+
baseUrl: config.baseUrl ?? stored?.baseUrl ?? DEFAULT_API_BASE_URL,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const stored = loadCredentials(config.name);
|
|
51
|
+
if (stored?.botToken) {
|
|
52
|
+
return {
|
|
53
|
+
...stored,
|
|
54
|
+
baseUrl: config.baseUrl ?? stored.baseUrl ?? DEFAULT_API_BASE_URL,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return loginWithQr(plugin, config);
|
|
58
|
+
}
|
|
59
|
+
export async function loginWithQr(plugin, config) {
|
|
60
|
+
const apiBaseUrl = config.baseUrl ?? DEFAULT_API_BASE_URL;
|
|
61
|
+
const qr = await fetchQRCode(apiBaseUrl, DEFAULT_ILINK_BOT_TYPE);
|
|
62
|
+
const qrcodeUrl = qr.qrcode_img_content || qr.qrcode;
|
|
63
|
+
const loginAssist = plugin.inject("loginAssist");
|
|
64
|
+
if (!loginAssist) {
|
|
65
|
+
throw new Error("loginAssist 服务不可用,请确保已加载 zhin 核心服务");
|
|
66
|
+
}
|
|
67
|
+
void loginAssist.waitForInput("weixin-ilink", config.name, "qrcode", {
|
|
68
|
+
message: "请用微信扫描 ClawBot 二维码完成登录",
|
|
69
|
+
image: qrcodeUrl.startsWith("data:") ? qrcodeUrl : undefined,
|
|
70
|
+
url: qrcodeUrl.startsWith("http") ? qrcodeUrl : undefined,
|
|
71
|
+
qrcode: qr.qrcode,
|
|
72
|
+
});
|
|
73
|
+
const assistTask = loginAssist
|
|
74
|
+
.listPending()
|
|
75
|
+
.find((t) => t.adapter === "weixin-ilink" && t.botId === config.name);
|
|
76
|
+
const deadline = Date.now() + LOGIN_TIMEOUT_MS;
|
|
77
|
+
let currentBaseUrl = apiBaseUrl;
|
|
78
|
+
try {
|
|
79
|
+
while (Date.now() < deadline) {
|
|
80
|
+
const status = await pollQRStatus(currentBaseUrl, qr.qrcode);
|
|
81
|
+
if (status.status === "scaned_but_redirect" && status.redirect_host) {
|
|
82
|
+
currentBaseUrl = status.redirect_host.startsWith("http")
|
|
83
|
+
? status.redirect_host
|
|
84
|
+
: `https://${status.redirect_host}`;
|
|
85
|
+
}
|
|
86
|
+
if (status.status === "confirmed" || status.status === "binded_redirect") {
|
|
87
|
+
if (!status.bot_token && status.status !== "binded_redirect") {
|
|
88
|
+
throw new Error("扫码成功但未返回 bot_token");
|
|
89
|
+
}
|
|
90
|
+
const creds = {
|
|
91
|
+
botToken: status.bot_token ?? loadCredentials(config.name)?.botToken ?? "",
|
|
92
|
+
ilinkUserId: status.ilink_user_id,
|
|
93
|
+
ilinkBotId: status.ilink_bot_id,
|
|
94
|
+
baseUrl: status.baseurl ?? currentBaseUrl,
|
|
95
|
+
};
|
|
96
|
+
if (!creds.botToken) {
|
|
97
|
+
throw new Error("binded_redirect 但本地无有效 bot_token");
|
|
98
|
+
}
|
|
99
|
+
saveCredentials(config.name, creds);
|
|
100
|
+
if (assistTask)
|
|
101
|
+
loginAssist.submit(assistTask.id, "confirmed");
|
|
102
|
+
return creds;
|
|
103
|
+
}
|
|
104
|
+
if (status.status === "expired") {
|
|
105
|
+
if (assistTask)
|
|
106
|
+
loginAssist.cancel(assistTask.id, "expired");
|
|
107
|
+
throw new Error("二维码已过期,请重启 bot 重新登录");
|
|
108
|
+
}
|
|
109
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
110
|
+
}
|
|
111
|
+
if (assistTask)
|
|
112
|
+
loginAssist.cancel(assistTask.id, "timeout");
|
|
113
|
+
throw new Error("微信扫码登录超时");
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (assistTask)
|
|
117
|
+
loginAssist.cancel(assistTask.id, String(err));
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=login.js.map
|
package/lib/login.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { WeixinInboundMediaOpts } from "./weixin-inbound.js";
|
|
2
|
+
import type { WeixinMessage } from "./ilink-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 {};
|
|
19
|
+
//# sourceMappingURL=media-download.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-download.d.ts","sourceRoot":"","sources":["../src/media-download.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAQlE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAKtD,gEAAgE;AAChE,KAAK,WAAW,GAAG,CACjB,MAAM,EAAE,MAAM,EACd,WAAW,CAAC,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,EACjB,gBAAgB,CAAC,EAAE,MAAM,KACtB,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE/B;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,aAAa,CAAC,WAAW,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,SAAS,GAAG,CAAC,GAAG,KAAK,EAC5E,IAAI,EAAE;IACJ,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,WAAW,CAAC;IACvB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;CACf,GACA,OAAO,CAAC,sBAAsB,CAAC,CAiHjC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { logger } from "./ilink-logger.js";
|
|
2
|
+
import { getMimeFromFilename } from "./mime.js";
|
|
3
|
+
import { downloadAndDecryptBuffer, downloadPlainCdnBuffer, } from "./pic-decrypt.js";
|
|
4
|
+
import { silkToWav } from "./silk-transcode.js";
|
|
5
|
+
import { MessageItemType } from "./ilink-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-download.ts"],"names":[],"mappings":"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"}
|
package/lib/mime.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
/** Sniff common image/video MIME from magic bytes. */
|
|
6
|
+
export declare function sniffMimeFromBuffer(buf: Buffer): string | undefined;
|
|
7
|
+
/** Get file extension from Content-Type header or URL path. Returns ".bin" for unknown. */
|
|
8
|
+
export declare function getExtensionFromContentTypeOrUrl(contentType: string | null, url: string): string;
|
|
9
|
+
//# sourceMappingURL=mime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mime.d.ts","sourceRoot":"","sources":["../src/mime.ts"],"names":[],"mappings":"AAsDA,wGAAwG;AACxG,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG5D;AAED,2EAA2E;AAC3E,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED,sDAAsD;AACtD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CA0BnE;AAED,2FAA2F;AAC3F,wBAAgB,gCAAgC,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQhG"}
|
package/lib/mime.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
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
|
+
/** Sniff common image/video MIME from magic bytes. */
|
|
63
|
+
export function sniffMimeFromBuffer(buf) {
|
|
64
|
+
if (buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) {
|
|
65
|
+
return "image/jpeg";
|
|
66
|
+
}
|
|
67
|
+
if (buf.length >= 8 &&
|
|
68
|
+
buf[0] === 0x89 &&
|
|
69
|
+
buf[1] === 0x50 &&
|
|
70
|
+
buf[2] === 0x4e &&
|
|
71
|
+
buf[3] === 0x47) {
|
|
72
|
+
return "image/png";
|
|
73
|
+
}
|
|
74
|
+
if (buf.length >= 6 && buf.slice(0, 6).toString("ascii") === "GIF87a") {
|
|
75
|
+
return "image/gif";
|
|
76
|
+
}
|
|
77
|
+
if (buf.length >= 6 && buf.slice(0, 6).toString("ascii") === "GIF89a") {
|
|
78
|
+
return "image/gif";
|
|
79
|
+
}
|
|
80
|
+
if (buf.length >= 12 && buf.slice(0, 4).toString("ascii") === "RIFF" && buf.slice(8, 12).toString("ascii") === "WEBP") {
|
|
81
|
+
return "image/webp";
|
|
82
|
+
}
|
|
83
|
+
if (buf.length >= 8 && buf.slice(4, 8).toString("ascii") === "ftyp") {
|
|
84
|
+
return "video/mp4";
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
/** Get file extension from Content-Type header or URL path. Returns ".bin" for unknown. */
|
|
89
|
+
export function getExtensionFromContentTypeOrUrl(contentType, url) {
|
|
90
|
+
if (contentType) {
|
|
91
|
+
const ext = getExtensionFromMime(contentType);
|
|
92
|
+
if (ext !== ".bin")
|
|
93
|
+
return ext;
|
|
94
|
+
}
|
|
95
|
+
const ext = path.extname(new URL(url).pathname).toLowerCase();
|
|
96
|
+
const knownExts = new Set(Object.keys(EXTENSION_TO_MIME));
|
|
97
|
+
return knownExts.has(ext) ? ext : ".bin";
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=mime.js.map
|
package/lib/mime.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mime.js","sourceRoot":"","sources":["../src/mime.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { MessageSegment, SendContent } from "zhin.js";
|
|
2
|
+
export declare function segmentMediaRef(seg: MessageSegment): string | undefined;
|
|
3
|
+
/** 将 base64 / 远程图落盘,供 sendWeixinMediaFile 上传 */
|
|
4
|
+
export declare function materializeOutboundMedia(content: SendContent, outboundDir: string): Promise<SendContent>;
|
|
5
|
+
//# sourceMappingURL=outbound-media.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound-media.d.ts","sourceRoot":"","sources":["../src/outbound-media.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAkB3D,wBAAgB,eAAe,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAIvE;AAkGD,gDAAgD;AAChD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,WAAW,EACpB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,WAAW,CAAC,CAUtB"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 微信 iLink 出站媒体:base64 / 远程 URL 须落盘后再走 CDN 上传。
|
|
3
|
+
*/
|
|
4
|
+
import crypto from "node:crypto";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { downloadRemoteImageToTemp } from "./upload.js";
|
|
8
|
+
import { getExtensionFromMime } from "./mime.js";
|
|
9
|
+
const MEDIA_TYPES = new Set(["image", "video", "file", "record", "audio"]);
|
|
10
|
+
function isRemoteUrl(value) {
|
|
11
|
+
return /^https?:\/\//i.test(value);
|
|
12
|
+
}
|
|
13
|
+
function isInlineMediaRef(value) {
|
|
14
|
+
return value.startsWith("base64://") || /^data:[^/]+\/[^;]+;base64,/i.test(value);
|
|
15
|
+
}
|
|
16
|
+
function normalizeLocalPath(value) {
|
|
17
|
+
return value.replace(/^file:\/\//, "");
|
|
18
|
+
}
|
|
19
|
+
export function segmentMediaRef(seg) {
|
|
20
|
+
const data = seg.data;
|
|
21
|
+
const candidates = [data.file, data.path, data.url].filter((v) => typeof v === "string" && Boolean(v));
|
|
22
|
+
return candidates[0];
|
|
23
|
+
}
|
|
24
|
+
function extractBase64Buffer(data) {
|
|
25
|
+
const url = typeof data.url === "string" ? data.url : undefined;
|
|
26
|
+
const file = typeof data.file === "string" ? data.file : undefined;
|
|
27
|
+
const base64 = typeof data.base64 === "string" ? data.base64 : undefined;
|
|
28
|
+
if (url?.startsWith("base64://")) {
|
|
29
|
+
return Buffer.from(url.slice(9), "base64");
|
|
30
|
+
}
|
|
31
|
+
if (url && /^data:[^/]+\/[^;]+;base64,/i.test(url)) {
|
|
32
|
+
return Buffer.from(url.replace(/^data:[^/]+\/[^;]+;base64,/, ""), "base64");
|
|
33
|
+
}
|
|
34
|
+
if (file?.startsWith("base64://")) {
|
|
35
|
+
return Buffer.from(file.slice(9), "base64");
|
|
36
|
+
}
|
|
37
|
+
if (base64) {
|
|
38
|
+
const raw = base64.startsWith("base64://") ? base64.slice(9) : base64;
|
|
39
|
+
return Buffer.from(raw, "base64");
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
function resolveExt(seg, mime) {
|
|
44
|
+
const data = seg.data;
|
|
45
|
+
const name = typeof data.name === "string" ? data.name : undefined;
|
|
46
|
+
if (name) {
|
|
47
|
+
const ext = path.extname(name);
|
|
48
|
+
if (ext)
|
|
49
|
+
return ext;
|
|
50
|
+
}
|
|
51
|
+
const mimeHint = mime ?? (typeof data.mime === "string" ? data.mime : typeof data.mimeType === "string" ? data.mimeType : undefined);
|
|
52
|
+
if (mimeHint) {
|
|
53
|
+
return getExtensionFromMime(mimeHint) ?? ".bin";
|
|
54
|
+
}
|
|
55
|
+
if (seg.type === "image")
|
|
56
|
+
return ".png";
|
|
57
|
+
if (seg.type === "video")
|
|
58
|
+
return ".mp4";
|
|
59
|
+
if (seg.type === "record" || seg.type === "audio")
|
|
60
|
+
return ".mp3";
|
|
61
|
+
return ".bin";
|
|
62
|
+
}
|
|
63
|
+
function withLocalFile(seg, filePath) {
|
|
64
|
+
const data = { ...seg.data, file: filePath };
|
|
65
|
+
delete data.base64;
|
|
66
|
+
return { type: seg.type, data };
|
|
67
|
+
}
|
|
68
|
+
function writeOutboundBuffer(buffer, outboundDir, seg) {
|
|
69
|
+
fs.mkdirSync(outboundDir, { recursive: true });
|
|
70
|
+
const ext = resolveExt(seg);
|
|
71
|
+
const filePath = path.join(outboundDir, `out-${seg.type}-${Date.now()}-${crypto.randomBytes(4).toString("hex")}${ext}`);
|
|
72
|
+
fs.writeFileSync(filePath, buffer);
|
|
73
|
+
return filePath;
|
|
74
|
+
}
|
|
75
|
+
async function materializeSegment(seg, outboundDir) {
|
|
76
|
+
if (!MEDIA_TYPES.has(seg.type))
|
|
77
|
+
return seg;
|
|
78
|
+
const ref = segmentMediaRef(seg);
|
|
79
|
+
if (ref && !isInlineMediaRef(ref)) {
|
|
80
|
+
const local = normalizeLocalPath(ref);
|
|
81
|
+
if (isRemoteUrl(local)) {
|
|
82
|
+
const downloaded = await downloadRemoteImageToTemp(local, outboundDir);
|
|
83
|
+
return withLocalFile(seg, downloaded);
|
|
84
|
+
}
|
|
85
|
+
if (fs.existsSync(local)) {
|
|
86
|
+
return withLocalFile(seg, local);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const buffer = extractBase64Buffer(seg.data);
|
|
90
|
+
if (buffer) {
|
|
91
|
+
return withLocalFile(seg, writeOutboundBuffer(buffer, outboundDir, seg));
|
|
92
|
+
}
|
|
93
|
+
return seg;
|
|
94
|
+
}
|
|
95
|
+
function asSegments(content) {
|
|
96
|
+
if (typeof content === "string") {
|
|
97
|
+
return [{ type: "text", data: { text: content } }];
|
|
98
|
+
}
|
|
99
|
+
if (!Array.isArray(content)) {
|
|
100
|
+
return typeof content === "object" && content !== null && "type" in content
|
|
101
|
+
? [content]
|
|
102
|
+
: [];
|
|
103
|
+
}
|
|
104
|
+
return content.flatMap((item) => {
|
|
105
|
+
if (typeof item === "string")
|
|
106
|
+
return [{ type: "text", data: { text: item } }];
|
|
107
|
+
return typeof item === "object" && item !== null && "type" in item ? [item] : [];
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/** 将 base64 / 远程图落盘,供 sendWeixinMediaFile 上传 */
|
|
111
|
+
export async function materializeOutboundMedia(content, outboundDir) {
|
|
112
|
+
const segments = asSegments(content);
|
|
113
|
+
const out = await Promise.all(segments.map((seg) => materializeSegment(seg, outboundDir)));
|
|
114
|
+
if (typeof content === "string") {
|
|
115
|
+
return out[0]?.type === "text" ? out[0].data.text : content;
|
|
116
|
+
}
|
|
117
|
+
if (!Array.isArray(content)) {
|
|
118
|
+
return out[0] ?? content;
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=outbound-media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound-media.js","sourceRoot":"","sources":["../src/outbound-media.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Download and AES-128-ECB decrypt a CDN media file. Returns plaintext Buffer.
|
|
3
|
+
* aesKeyBase64: CDNMedia.aes_key JSON field (see parseAesKey for supported formats).
|
|
4
|
+
*/
|
|
5
|
+
export declare function downloadAndDecryptBuffer(encryptedQueryParam: string, aesKeyBase64: string, cdnBaseUrl: string, label: string, fullUrl?: string): Promise<Buffer>;
|
|
6
|
+
/**
|
|
7
|
+
* Download plain (unencrypted) bytes from the CDN. Returns the raw Buffer.
|
|
8
|
+
*/
|
|
9
|
+
export declare function downloadPlainCdnBuffer(encryptedQueryParam: string, cdnBaseUrl: string, label: string, fullUrl?: string): Promise<Buffer>;
|
|
10
|
+
//# sourceMappingURL=pic-decrypt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pic-decrypt.d.ts","sourceRoot":"","sources":["../src/pic-decrypt.ts"],"names":[],"mappings":"AAqDA;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,mBAAmB,EAAE,MAAM,EAC3B,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,mBAAmB,EAAE,MAAM,EAC3B,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAWjB"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { decryptAesEcb } from "./aes-ecb.js";
|
|
2
|
+
import { buildCdnDownloadUrl, ENABLE_CDN_URL_FALLBACK } from "./cdn-url.js";
|
|
3
|
+
import { logger } from "./ilink-logger.js";
|
|
4
|
+
/**
|
|
5
|
+
* Download raw bytes from the CDN (no decryption).
|
|
6
|
+
*/
|
|
7
|
+
async function fetchCdnBytes(url, label) {
|
|
8
|
+
let res;
|
|
9
|
+
try {
|
|
10
|
+
res = await fetch(url);
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
const cause = err.cause ?? err.code ?? "(no cause)";
|
|
14
|
+
logger.error(`${label}: fetch network error url=${url} err=${String(err)} cause=${String(cause)}`);
|
|
15
|
+
throw err;
|
|
16
|
+
}
|
|
17
|
+
logger.debug(`${label}: response status=${res.status} ok=${res.ok}`);
|
|
18
|
+
if (!res.ok) {
|
|
19
|
+
const body = await res.text().catch(() => "(unreadable)");
|
|
20
|
+
const msg = `${label}: CDN download ${res.status} ${res.statusText} body=${body}`;
|
|
21
|
+
logger.error(msg);
|
|
22
|
+
throw new Error(msg);
|
|
23
|
+
}
|
|
24
|
+
return Buffer.from(await res.arrayBuffer());
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Parse CDNMedia.aes_key into a raw 16-byte AES key.
|
|
28
|
+
*
|
|
29
|
+
* Two encodings are seen in the wild:
|
|
30
|
+
* - base64(raw 16 bytes) → images (aes_key from media field)
|
|
31
|
+
* - base64(hex string of 16 bytes) → file / voice / video
|
|
32
|
+
*
|
|
33
|
+
* In the second case, base64-decoding yields 32 ASCII hex chars which must
|
|
34
|
+
* then be parsed as hex to recover the actual 16-byte key.
|
|
35
|
+
*/
|
|
36
|
+
function parseAesKey(aesKeyBase64, label) {
|
|
37
|
+
const decoded = Buffer.from(aesKeyBase64, "base64");
|
|
38
|
+
if (decoded.length === 16) {
|
|
39
|
+
return decoded;
|
|
40
|
+
}
|
|
41
|
+
if (decoded.length === 32 && /^[0-9a-fA-F]{32}$/.test(decoded.toString("ascii"))) {
|
|
42
|
+
// hex-encoded key: base64 → hex string → raw bytes
|
|
43
|
+
return Buffer.from(decoded.toString("ascii"), "hex");
|
|
44
|
+
}
|
|
45
|
+
const msg = `${label}: aes_key must decode to 16 raw bytes or 32-char hex string, got ${decoded.length} bytes (base64="${aesKeyBase64}")`;
|
|
46
|
+
logger.error(msg);
|
|
47
|
+
throw new Error(msg);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Download and AES-128-ECB decrypt a CDN media file. Returns plaintext Buffer.
|
|
51
|
+
* aesKeyBase64: CDNMedia.aes_key JSON field (see parseAesKey for supported formats).
|
|
52
|
+
*/
|
|
53
|
+
export async function downloadAndDecryptBuffer(encryptedQueryParam, aesKeyBase64, cdnBaseUrl, label, fullUrl) {
|
|
54
|
+
const key = parseAesKey(aesKeyBase64, label);
|
|
55
|
+
let url;
|
|
56
|
+
if (fullUrl) {
|
|
57
|
+
url = fullUrl;
|
|
58
|
+
}
|
|
59
|
+
else if (ENABLE_CDN_URL_FALLBACK) {
|
|
60
|
+
url = buildCdnDownloadUrl(encryptedQueryParam, cdnBaseUrl);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
throw new Error(`${label}: fullUrl is required (CDN URL fallback is disabled)`);
|
|
64
|
+
}
|
|
65
|
+
logger.debug(`${label}: fetching url=${url}`);
|
|
66
|
+
const encrypted = await fetchCdnBytes(url, label);
|
|
67
|
+
logger.debug(`${label}: downloaded ${encrypted.byteLength} bytes, decrypting`);
|
|
68
|
+
const decrypted = decryptAesEcb(encrypted, key);
|
|
69
|
+
logger.debug(`${label}: decrypted ${decrypted.length} bytes`);
|
|
70
|
+
return decrypted;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Download plain (unencrypted) bytes from the CDN. Returns the raw Buffer.
|
|
74
|
+
*/
|
|
75
|
+
export async function downloadPlainCdnBuffer(encryptedQueryParam, cdnBaseUrl, label, fullUrl) {
|
|
76
|
+
let url;
|
|
77
|
+
if (fullUrl) {
|
|
78
|
+
url = fullUrl;
|
|
79
|
+
}
|
|
80
|
+
else if (ENABLE_CDN_URL_FALLBACK) {
|
|
81
|
+
url = buildCdnDownloadUrl(encryptedQueryParam, cdnBaseUrl);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
throw new Error(`${label}: fullUrl is required (CDN URL fallback is disabled)`);
|
|
85
|
+
}
|
|
86
|
+
logger.debug(`${label}: fetching url=${url}`);
|
|
87
|
+
return fetchCdnBytes(url, label);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=pic-decrypt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pic-decrypt.js","sourceRoot":"","sources":["../src/pic-decrypt.ts"],"names":[],"mappings":"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"}
|