liangzimixin 0.3.73 → 0.3.74
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/dist/index.cjs +68 -6
- package/dist/index.d.cts +17 -0
- package/dist/setup-entry.cjs +68 -6
- package/package.json +1 -1
- package/scripts/liangzimixin_install.bat +1 -1
- package/scripts/liangzimixin_install.sh +1 -1
package/dist/index.cjs
CHANGED
|
@@ -18972,7 +18972,9 @@ async function resolveMedia(params) {
|
|
|
18972
18972
|
sdkRuntime,
|
|
18973
18973
|
maxBytes = 30 * 1024 * 1024,
|
|
18974
18974
|
allowPrivateNetwork = false,
|
|
18975
|
-
timeoutMs
|
|
18975
|
+
timeoutMs,
|
|
18976
|
+
messagePipe,
|
|
18977
|
+
fileEncryptionMeta
|
|
18976
18978
|
} = params;
|
|
18977
18979
|
const client = createHttpClient({ baseUrl: serverUrl, tokenManager, timeoutMs });
|
|
18978
18980
|
log11.info("download:getUrl", { fileId });
|
|
@@ -18989,9 +18991,15 @@ async function resolveMedia(params) {
|
|
|
18989
18991
|
url: downloadInfo.fileUrl,
|
|
18990
18992
|
ssrfPolicy: { allowPrivateNetwork }
|
|
18991
18993
|
});
|
|
18994
|
+
let fileBuffer = Buffer.from(fetched.buffer);
|
|
18995
|
+
if (fileEncryptionMeta && messagePipe) {
|
|
18996
|
+
log11.info("download:decrypting", { fileId, keyId: fileEncryptionMeta.keyId, encryptedSize: fileBuffer.length });
|
|
18997
|
+
fileBuffer = Buffer.from(await messagePipe.decryptFile(fileBuffer, fileEncryptionMeta.keyId, fileEncryptionMeta.iv));
|
|
18998
|
+
log11.info("download:decrypted", { fileId, encryptedSize: fetched.buffer.length, decryptedSize: fileBuffer.length });
|
|
18999
|
+
}
|
|
18992
19000
|
const contentType = fetched.contentType || downloadInfo.mimeType;
|
|
18993
19001
|
const saved = await sdkRuntime.channel.media.saveMediaBuffer(
|
|
18994
|
-
|
|
19002
|
+
fileBuffer,
|
|
18995
19003
|
contentType,
|
|
18996
19004
|
"inbound",
|
|
18997
19005
|
maxBytes,
|
|
@@ -19035,7 +19043,9 @@ async function resolveContent(context, deps) {
|
|
|
19035
19043
|
sdkRuntime: deps.sdkRuntime,
|
|
19036
19044
|
maxBytes: deps.maxBytes,
|
|
19037
19045
|
allowPrivateNetwork: deps.allowPrivateNetwork,
|
|
19038
|
-
timeoutMs: deps.timeoutMs
|
|
19046
|
+
timeoutMs: deps.timeoutMs,
|
|
19047
|
+
messagePipe: deps.messagePipe,
|
|
19048
|
+
fileEncryptionMeta: deps.fileEncryptionMeta
|
|
19039
19049
|
});
|
|
19040
19050
|
log12.info("resolveContent:downloaded", {
|
|
19041
19051
|
fileId: context.fileId,
|
|
@@ -19394,7 +19404,9 @@ var InboundPipeline = class {
|
|
|
19394
19404
|
sdkRuntime: core,
|
|
19395
19405
|
maxBytes: this.deps.pluginConfig.file.maxFileSizeMb * 1024 * 1024,
|
|
19396
19406
|
allowPrivateNetwork: this.deps.pluginConfig.file.allowPrivateNetwork,
|
|
19397
|
-
timeoutMs: this.deps.pluginConfig.file.fetchTimeoutMs
|
|
19407
|
+
timeoutMs: this.deps.pluginConfig.file.fetchTimeoutMs,
|
|
19408
|
+
messagePipe: this.deps.messagePipe,
|
|
19409
|
+
fileEncryptionMeta: msg.fileEncryptionMeta
|
|
19398
19410
|
});
|
|
19399
19411
|
const payload = buildInboundPayload(msg, resolvedContent, this.deps.pluginConfig);
|
|
19400
19412
|
const { sdkConfig } = this.deps;
|
|
@@ -21072,6 +21084,48 @@ var MessagePipe = class _MessagePipe {
|
|
|
21072
21084
|
});
|
|
21073
21085
|
return { encryptedBuffer, keyId, iv };
|
|
21074
21086
|
}
|
|
21087
|
+
/**
|
|
21088
|
+
* 解密文件 Buffer — 按分片解密,返回解密后的 Buffer。
|
|
21089
|
+
* 用于入站加密文件消息的下载后解密。
|
|
21090
|
+
*
|
|
21091
|
+
* 如果 CryptoEngine 为透传模式,直接返回原始 buffer。
|
|
21092
|
+
*
|
|
21093
|
+
* @param buffer - 加密的文件 Buffer
|
|
21094
|
+
* @param keyId - 加密时的密钥标识 (来自 extraContent.sessionId)
|
|
21095
|
+
* @param iv - 初始化向量 (来自 extraContent.cryptoIv)
|
|
21096
|
+
* @returns 解密后的 Buffer
|
|
21097
|
+
*/
|
|
21098
|
+
async decryptFile(buffer, keyId, iv) {
|
|
21099
|
+
const chunkSize = 10 * 1024 * 1024 + 16;
|
|
21100
|
+
const totalChunks = Math.ceil(buffer.length / chunkSize);
|
|
21101
|
+
let sessionKey = "";
|
|
21102
|
+
let fillKey = "";
|
|
21103
|
+
const decryptedChunks = [];
|
|
21104
|
+
for (let i = 0; i < totalChunks; i++) {
|
|
21105
|
+
const start = i * chunkSize;
|
|
21106
|
+
const end = Math.min(start + chunkSize, buffer.length);
|
|
21107
|
+
const chunk = buffer.subarray(start, end);
|
|
21108
|
+
const options = sessionKey ? { sessionKey, fillKey } : void 0;
|
|
21109
|
+
const result = await this.crypto.decryptFileChunk(chunk, keyId, iv, options);
|
|
21110
|
+
decryptedChunks.push(result.fileBuffer);
|
|
21111
|
+
sessionKey = result.sessionKey;
|
|
21112
|
+
fillKey = result.fillKey;
|
|
21113
|
+
log25.debug("\u{1F513} \u6587\u4EF6\u5206\u7247\u89E3\u5BC6", {
|
|
21114
|
+
chunk: `${i + 1}/${totalChunks}`,
|
|
21115
|
+
encryptedSize: chunk.length,
|
|
21116
|
+
decryptedSize: result.fileBuffer.length,
|
|
21117
|
+
keyId
|
|
21118
|
+
});
|
|
21119
|
+
}
|
|
21120
|
+
const decryptedBuffer = Buffer.concat(decryptedChunks);
|
|
21121
|
+
log25.info("\u{1F513} \u6587\u4EF6\u89E3\u5BC6\u5B8C\u6210", {
|
|
21122
|
+
encryptedSize: buffer.length,
|
|
21123
|
+
decryptedSize: decryptedBuffer.length,
|
|
21124
|
+
totalChunks,
|
|
21125
|
+
keyId
|
|
21126
|
+
});
|
|
21127
|
+
return decryptedBuffer;
|
|
21128
|
+
}
|
|
21075
21129
|
/** 文件类消息类型集合 — 这些类型的加密消息保留原始 content(文件元数据) */
|
|
21076
21130
|
static FILE_MSG_TYPES = /* @__PURE__ */ new Set(["image", "file", "voice", "video"]);
|
|
21077
21131
|
/**
|
|
@@ -21384,10 +21438,17 @@ var MessagePipe = class _MessagePipe {
|
|
|
21384
21438
|
});
|
|
21385
21439
|
const extraContent = callbackData.extraContent;
|
|
21386
21440
|
let isEncrypted = false;
|
|
21441
|
+
let fileEncryptionMeta;
|
|
21387
21442
|
if (typeof extraContent === "string" && extraContent.length > 0) {
|
|
21388
21443
|
try {
|
|
21389
21444
|
const parsed = JSON.parse(extraContent);
|
|
21390
|
-
|
|
21445
|
+
if (parsed.encryptMsg) {
|
|
21446
|
+
isEncrypted = true;
|
|
21447
|
+
} else if (parsed.sessionId) {
|
|
21448
|
+
isEncrypted = true;
|
|
21449
|
+
fileEncryptionMeta = { keyId: parsed.sessionId, iv: parsed.cryptoIv ?? "" };
|
|
21450
|
+
log25.info("\u{1F512} \u5165\u7AD9\u6587\u4EF6\u6D88\u606F\u5DF2\u6807\u8BB0\u52A0\u5BC6", { msgUid: callbackData.msgUid, keyId: parsed.sessionId });
|
|
21451
|
+
}
|
|
21391
21452
|
} catch {
|
|
21392
21453
|
log25.debug("\u2139\uFE0F extraContent \u4E0D\u662F\u6709\u6548 JSON\uFF0C\u89C6\u4E3A\u660E\u6587", { msgUid: callbackData.msgUid });
|
|
21393
21454
|
}
|
|
@@ -21468,7 +21529,8 @@ var MessagePipe = class _MessagePipe {
|
|
|
21468
21529
|
msgType: callbackData.type,
|
|
21469
21530
|
content: JSON.stringify(contentObj),
|
|
21470
21531
|
timestamp: Date.now(),
|
|
21471
|
-
isEncrypted
|
|
21532
|
+
isEncrypted,
|
|
21533
|
+
fileEncryptionMeta
|
|
21472
21534
|
};
|
|
21473
21535
|
log25.debug("\u{1F4E8} \u89E3\u6790 WS \u5E27", { messageId: msg.messageId, eventType: callbackData.eventType });
|
|
21474
21536
|
if (this.dedup.isDuplicate(msg.messageId)) {
|
package/dist/index.d.cts
CHANGED
|
@@ -195,6 +195,11 @@ interface InboundMessage {
|
|
|
195
195
|
replyToMessageId?: string;
|
|
196
196
|
/** 入站消息是否经过加密 — 用于决定回复是否也需要加密 */
|
|
197
197
|
isEncrypted?: boolean;
|
|
198
|
+
/** 文件加密元数据 — 入站文件消息解密所需的 keyId/iv (来自 extraContent.sessionId + cryptoIv) */
|
|
199
|
+
fileEncryptionMeta?: {
|
|
200
|
+
keyId: string;
|
|
201
|
+
iv: string;
|
|
202
|
+
};
|
|
198
203
|
}
|
|
199
204
|
/** 出站消息 — 插件向 IM 服务器发送的消息 */
|
|
200
205
|
interface OutboundMessage {
|
|
@@ -922,6 +927,18 @@ declare class MessagePipe {
|
|
|
922
927
|
keyId: string;
|
|
923
928
|
iv: string;
|
|
924
929
|
}>;
|
|
930
|
+
/**
|
|
931
|
+
* 解密文件 Buffer — 按分片解密,返回解密后的 Buffer。
|
|
932
|
+
* 用于入站加密文件消息的下载后解密。
|
|
933
|
+
*
|
|
934
|
+
* 如果 CryptoEngine 为透传模式,直接返回原始 buffer。
|
|
935
|
+
*
|
|
936
|
+
* @param buffer - 加密的文件 Buffer
|
|
937
|
+
* @param keyId - 加密时的密钥标识 (来自 extraContent.sessionId)
|
|
938
|
+
* @param iv - 初始化向量 (来自 extraContent.cryptoIv)
|
|
939
|
+
* @returns 解密后的 Buffer
|
|
940
|
+
*/
|
|
941
|
+
decryptFile(buffer: Buffer, keyId: string, iv: string): Promise<Buffer>;
|
|
925
942
|
/** 文件类消息类型集合 — 这些类型的加密消息保留原始 content(文件元数据) */
|
|
926
943
|
private static readonly FILE_MSG_TYPES;
|
|
927
944
|
/**
|
package/dist/setup-entry.cjs
CHANGED
|
@@ -19970,6 +19970,48 @@ var MessagePipe = class _MessagePipe {
|
|
|
19970
19970
|
});
|
|
19971
19971
|
return { encryptedBuffer, keyId, iv };
|
|
19972
19972
|
}
|
|
19973
|
+
/**
|
|
19974
|
+
* 解密文件 Buffer — 按分片解密,返回解密后的 Buffer。
|
|
19975
|
+
* 用于入站加密文件消息的下载后解密。
|
|
19976
|
+
*
|
|
19977
|
+
* 如果 CryptoEngine 为透传模式,直接返回原始 buffer。
|
|
19978
|
+
*
|
|
19979
|
+
* @param buffer - 加密的文件 Buffer
|
|
19980
|
+
* @param keyId - 加密时的密钥标识 (来自 extraContent.sessionId)
|
|
19981
|
+
* @param iv - 初始化向量 (来自 extraContent.cryptoIv)
|
|
19982
|
+
* @returns 解密后的 Buffer
|
|
19983
|
+
*/
|
|
19984
|
+
async decryptFile(buffer, keyId, iv) {
|
|
19985
|
+
const chunkSize = 10 * 1024 * 1024 + 16;
|
|
19986
|
+
const totalChunks = Math.ceil(buffer.length / chunkSize);
|
|
19987
|
+
let sessionKey = "";
|
|
19988
|
+
let fillKey = "";
|
|
19989
|
+
const decryptedChunks = [];
|
|
19990
|
+
for (let i = 0; i < totalChunks; i++) {
|
|
19991
|
+
const start = i * chunkSize;
|
|
19992
|
+
const end = Math.min(start + chunkSize, buffer.length);
|
|
19993
|
+
const chunk = buffer.subarray(start, end);
|
|
19994
|
+
const options = sessionKey ? { sessionKey, fillKey } : void 0;
|
|
19995
|
+
const result = await this.crypto.decryptFileChunk(chunk, keyId, iv, options);
|
|
19996
|
+
decryptedChunks.push(result.fileBuffer);
|
|
19997
|
+
sessionKey = result.sessionKey;
|
|
19998
|
+
fillKey = result.fillKey;
|
|
19999
|
+
log16.debug("\u{1F513} \u6587\u4EF6\u5206\u7247\u89E3\u5BC6", {
|
|
20000
|
+
chunk: `${i + 1}/${totalChunks}`,
|
|
20001
|
+
encryptedSize: chunk.length,
|
|
20002
|
+
decryptedSize: result.fileBuffer.length,
|
|
20003
|
+
keyId
|
|
20004
|
+
});
|
|
20005
|
+
}
|
|
20006
|
+
const decryptedBuffer = Buffer.concat(decryptedChunks);
|
|
20007
|
+
log16.info("\u{1F513} \u6587\u4EF6\u89E3\u5BC6\u5B8C\u6210", {
|
|
20008
|
+
encryptedSize: buffer.length,
|
|
20009
|
+
decryptedSize: decryptedBuffer.length,
|
|
20010
|
+
totalChunks,
|
|
20011
|
+
keyId
|
|
20012
|
+
});
|
|
20013
|
+
return decryptedBuffer;
|
|
20014
|
+
}
|
|
19973
20015
|
/** 文件类消息类型集合 — 这些类型的加密消息保留原始 content(文件元数据) */
|
|
19974
20016
|
static FILE_MSG_TYPES = /* @__PURE__ */ new Set(["image", "file", "voice", "video"]);
|
|
19975
20017
|
/**
|
|
@@ -20282,10 +20324,17 @@ var MessagePipe = class _MessagePipe {
|
|
|
20282
20324
|
});
|
|
20283
20325
|
const extraContent = callbackData.extraContent;
|
|
20284
20326
|
let isEncrypted = false;
|
|
20327
|
+
let fileEncryptionMeta;
|
|
20285
20328
|
if (typeof extraContent === "string" && extraContent.length > 0) {
|
|
20286
20329
|
try {
|
|
20287
20330
|
const parsed = JSON.parse(extraContent);
|
|
20288
|
-
|
|
20331
|
+
if (parsed.encryptMsg) {
|
|
20332
|
+
isEncrypted = true;
|
|
20333
|
+
} else if (parsed.sessionId) {
|
|
20334
|
+
isEncrypted = true;
|
|
20335
|
+
fileEncryptionMeta = { keyId: parsed.sessionId, iv: parsed.cryptoIv ?? "" };
|
|
20336
|
+
log16.info("\u{1F512} \u5165\u7AD9\u6587\u4EF6\u6D88\u606F\u5DF2\u6807\u8BB0\u52A0\u5BC6", { msgUid: callbackData.msgUid, keyId: parsed.sessionId });
|
|
20337
|
+
}
|
|
20289
20338
|
} catch {
|
|
20290
20339
|
log16.debug("\u2139\uFE0F extraContent \u4E0D\u662F\u6709\u6548 JSON\uFF0C\u89C6\u4E3A\u660E\u6587", { msgUid: callbackData.msgUid });
|
|
20291
20340
|
}
|
|
@@ -20366,7 +20415,8 @@ var MessagePipe = class _MessagePipe {
|
|
|
20366
20415
|
msgType: callbackData.type,
|
|
20367
20416
|
content: JSON.stringify(contentObj),
|
|
20368
20417
|
timestamp: Date.now(),
|
|
20369
|
-
isEncrypted
|
|
20418
|
+
isEncrypted,
|
|
20419
|
+
fileEncryptionMeta
|
|
20370
20420
|
};
|
|
20371
20421
|
log16.debug("\u{1F4E8} \u89E3\u6790 WS \u5E27", { messageId: msg.messageId, eventType: callbackData.eventType });
|
|
20372
20422
|
if (this.dedup.isDuplicate(msg.messageId)) {
|
|
@@ -21360,7 +21410,9 @@ async function resolveMedia(params) {
|
|
|
21360
21410
|
sdkRuntime,
|
|
21361
21411
|
maxBytes = 30 * 1024 * 1024,
|
|
21362
21412
|
allowPrivateNetwork = false,
|
|
21363
|
-
timeoutMs
|
|
21413
|
+
timeoutMs,
|
|
21414
|
+
messagePipe,
|
|
21415
|
+
fileEncryptionMeta
|
|
21364
21416
|
} = params;
|
|
21365
21417
|
const client = createHttpClient({ baseUrl: serverUrl, tokenManager, timeoutMs });
|
|
21366
21418
|
log24.info("download:getUrl", { fileId });
|
|
@@ -21377,9 +21429,15 @@ async function resolveMedia(params) {
|
|
|
21377
21429
|
url: downloadInfo.fileUrl,
|
|
21378
21430
|
ssrfPolicy: { allowPrivateNetwork }
|
|
21379
21431
|
});
|
|
21432
|
+
let fileBuffer = Buffer.from(fetched.buffer);
|
|
21433
|
+
if (fileEncryptionMeta && messagePipe) {
|
|
21434
|
+
log24.info("download:decrypting", { fileId, keyId: fileEncryptionMeta.keyId, encryptedSize: fileBuffer.length });
|
|
21435
|
+
fileBuffer = Buffer.from(await messagePipe.decryptFile(fileBuffer, fileEncryptionMeta.keyId, fileEncryptionMeta.iv));
|
|
21436
|
+
log24.info("download:decrypted", { fileId, encryptedSize: fetched.buffer.length, decryptedSize: fileBuffer.length });
|
|
21437
|
+
}
|
|
21380
21438
|
const contentType = fetched.contentType || downloadInfo.mimeType;
|
|
21381
21439
|
const saved = await sdkRuntime.channel.media.saveMediaBuffer(
|
|
21382
|
-
|
|
21440
|
+
fileBuffer,
|
|
21383
21441
|
contentType,
|
|
21384
21442
|
"inbound",
|
|
21385
21443
|
maxBytes,
|
|
@@ -21423,7 +21481,9 @@ async function resolveContent(context, deps) {
|
|
|
21423
21481
|
sdkRuntime: deps.sdkRuntime,
|
|
21424
21482
|
maxBytes: deps.maxBytes,
|
|
21425
21483
|
allowPrivateNetwork: deps.allowPrivateNetwork,
|
|
21426
|
-
timeoutMs: deps.timeoutMs
|
|
21484
|
+
timeoutMs: deps.timeoutMs,
|
|
21485
|
+
messagePipe: deps.messagePipe,
|
|
21486
|
+
fileEncryptionMeta: deps.fileEncryptionMeta
|
|
21427
21487
|
});
|
|
21428
21488
|
log25.info("resolveContent:downloaded", {
|
|
21429
21489
|
fileId: context.fileId,
|
|
@@ -21770,7 +21830,9 @@ var InboundPipeline = class {
|
|
|
21770
21830
|
sdkRuntime: core,
|
|
21771
21831
|
maxBytes: this.deps.pluginConfig.file.maxFileSizeMb * 1024 * 1024,
|
|
21772
21832
|
allowPrivateNetwork: this.deps.pluginConfig.file.allowPrivateNetwork,
|
|
21773
|
-
timeoutMs: this.deps.pluginConfig.file.fetchTimeoutMs
|
|
21833
|
+
timeoutMs: this.deps.pluginConfig.file.fetchTimeoutMs,
|
|
21834
|
+
messagePipe: this.deps.messagePipe,
|
|
21835
|
+
fileEncryptionMeta: msg.fileEncryptionMeta
|
|
21774
21836
|
});
|
|
21775
21837
|
const payload = buildInboundPayload(msg, resolvedContent, this.deps.pluginConfig);
|
|
21776
21838
|
const { sdkConfig } = this.deps;
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@ REM liangzimixin install script (Windows)
|
|
|
7
7
|
REM Usage: liangzimixin_install.bat <appId> <appSecret> [quantumAccount]
|
|
8
8
|
REM ============================================================
|
|
9
9
|
|
|
10
|
-
set "SCRIPT_VERSION=0.3.
|
|
10
|
+
set "SCRIPT_VERSION=0.3.74"
|
|
11
11
|
set "NPM_PACKAGE=liangzimixin"
|
|
12
12
|
|
|
13
13
|
set "SKIP_SELF_UPDATE=0"
|
|
@@ -6,7 +6,7 @@ set -euo pipefail
|
|
|
6
6
|
# 用法: ./liangzimixin_install.sh <appId> <appSecret> [quantumAccount]
|
|
7
7
|
# ============================================================
|
|
8
8
|
|
|
9
|
-
SCRIPT_VERSION="0.3.
|
|
9
|
+
SCRIPT_VERSION="0.3.74"
|
|
10
10
|
NPM_PACKAGE="liangzimixin"
|
|
11
11
|
|
|
12
12
|
# ── 颜色 ──────────────────────────────────────────────────────
|