liangzimixin 0.3.43 → 0.3.45
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 +172 -75
- package/dist/index.d.cts +30 -7
- package/dist/setup-entry.cjs +172 -75
- package/openclaw.plugin.json +7 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -17441,6 +17441,9 @@ function parseChannelAddress(address) {
|
|
|
17441
17441
|
const prefix = `${CHANNEL_ID}:`;
|
|
17442
17442
|
return address.startsWith(prefix) ? address.slice(prefix.length) : address;
|
|
17443
17443
|
}
|
|
17444
|
+
var GUIDE_URL = "https://www.kdocs.cn/l/ca5LX4wxpSSE";
|
|
17445
|
+
var HINT_NO_QUANTUM_ACCOUNT = "\u60A8\u672A\u914D\u7F6E\u91CF\u5B50\u52A0\u5BC6\u53C2\u6570\uFF0C\u8BF7\u53C2\u8003[\u300A\u63A5\u5165\u6307\u5357\u300B\u5B89\u88C5\u91CF\u5B50\u5BC6\u4FE1\u63D2\u4EF6](" + GUIDE_URL + ") \u4FEE\u6539\u914D\u7F6E";
|
|
17446
|
+
var HINT_PLAINTEXT_NOT_SUPPORTED = "\u5F53\u524D\u6A21\u5F0F\u4E0D\u652F\u6301\u666E\u901A\u6D88\u606F\uFF0C\u8BF7\u53C2\u8003[\u300A\u63A5\u5165\u6307\u5357\u300B\u5B89\u88C5\u91CF\u5B50\u5BC6\u4FE1\u63D2\u4EF6](" + GUIDE_URL + ") \u4FEE\u6539\u914D\u7F6E";
|
|
17444
17447
|
|
|
17445
17448
|
// src/config.ts
|
|
17446
17449
|
var AccountConfigSchema = external_exports.object({
|
|
@@ -17462,7 +17465,9 @@ var AccountConfigSchema = external_exports.object({
|
|
|
17462
17465
|
*/
|
|
17463
17466
|
botUserId: external_exports.string().optional(),
|
|
17464
17467
|
/** 🟡 部署环境 — staging=联调, production=线上 */
|
|
17465
|
-
env: external_exports.enum(["staging", "production"]).optional()
|
|
17468
|
+
env: external_exports.enum(["staging", "production"]).optional(),
|
|
17469
|
+
/** 🟡 消息加密模式 — quantum_only=仅加密, quantum_and_plain=加密+普通(默认) */
|
|
17470
|
+
encryptionMode: external_exports.enum(["quantum_only", "quantum_and_plain"]).default("quantum_and_plain")
|
|
17466
17471
|
}).transform((raw) => ({
|
|
17467
17472
|
appId: raw.appId,
|
|
17468
17473
|
appSecret: raw.appSecret,
|
|
@@ -17471,7 +17476,8 @@ var AccountConfigSchema = external_exports.object({
|
|
|
17471
17476
|
// quantumAppId: raw.quantumAppId,
|
|
17472
17477
|
// quantumAppSecret: raw.quantumAppSecret,
|
|
17473
17478
|
botUserId: raw.botUserId,
|
|
17474
|
-
env: raw.env
|
|
17479
|
+
env: raw.env,
|
|
17480
|
+
encryptionMode: raw.encryptionMode
|
|
17475
17481
|
}));
|
|
17476
17482
|
var ENV_PRESETS = {
|
|
17477
17483
|
/** 联调环境 */
|
|
@@ -18180,13 +18186,15 @@ var quantumImOutbound = {
|
|
|
18180
18186
|
}
|
|
18181
18187
|
const messagePipe = messagePipeGetter();
|
|
18182
18188
|
const chatId = parseChannelAddress(to);
|
|
18189
|
+
const encryptionMode = pluginConfigGetter?.()?.credentials?.encryptionMode;
|
|
18183
18190
|
const isEncrypted = getOutboundEncryptionStatus(chatId);
|
|
18191
|
+
const skipEncrypt = encryptionMode === "quantum_only" ? false : isEncrypted !== true;
|
|
18184
18192
|
await messagePipe.sendMessage({
|
|
18185
18193
|
chatId,
|
|
18186
18194
|
senderId: chatId,
|
|
18187
18195
|
msgType: "markdown",
|
|
18188
18196
|
content: JSON.stringify({ content: text }),
|
|
18189
|
-
skipEncrypt
|
|
18197
|
+
skipEncrypt
|
|
18190
18198
|
});
|
|
18191
18199
|
return { channel: CHANNEL_ID, messageId: "", chatId };
|
|
18192
18200
|
},
|
|
@@ -18201,7 +18209,9 @@ var quantumImOutbound = {
|
|
|
18201
18209
|
const sdkRuntime = sdkRuntimeGetter();
|
|
18202
18210
|
const chatId = parseChannelAddress(to);
|
|
18203
18211
|
const fileCfg = pluginConfigGetter().file;
|
|
18212
|
+
const encryptionMode = pluginConfigGetter?.()?.credentials?.encryptionMode;
|
|
18204
18213
|
const isEncrypted = getOutboundEncryptionStatus(chatId);
|
|
18214
|
+
const skipEncrypt = encryptionMode === "quantum_only" ? false : isEncrypted !== true;
|
|
18205
18215
|
const result = await resolveAndUploadMedia({
|
|
18206
18216
|
mediaUrl: mediaUrl ?? void 0,
|
|
18207
18217
|
tokenManager: tokenManagerGetter(),
|
|
@@ -18214,7 +18224,7 @@ var quantumImOutbound = {
|
|
|
18214
18224
|
maxFileSizeMb: fileCfg.maxFileSizeMb,
|
|
18215
18225
|
chunkSizeMb: fileCfg.chunkSizeMb,
|
|
18216
18226
|
timeoutMs: fileCfg.fetchTimeoutMs,
|
|
18217
|
-
skipEncrypt
|
|
18227
|
+
skipEncrypt
|
|
18218
18228
|
});
|
|
18219
18229
|
if (result.warning) {
|
|
18220
18230
|
log4.warn("sendMedia:degraded", { chatId, warning: result.warning });
|
|
@@ -18515,8 +18525,10 @@ function createQuantumImDeliverFn(deps) {
|
|
|
18515
18525
|
maxFileSizeMb,
|
|
18516
18526
|
chunkSizeMb,
|
|
18517
18527
|
timeoutMs,
|
|
18518
|
-
isEncrypted
|
|
18528
|
+
isEncrypted,
|
|
18529
|
+
encryptionMode
|
|
18519
18530
|
} = deps;
|
|
18531
|
+
const shouldSkipEncrypt = encryptionMode === "quantum_only" ? false : isEncrypted === false;
|
|
18520
18532
|
const deliver = async (payload) => {
|
|
18521
18533
|
const mediaUrls = [];
|
|
18522
18534
|
if (payload.mediaUrls?.length) {
|
|
@@ -18534,7 +18546,7 @@ function createQuantumImDeliverFn(deps) {
|
|
|
18534
18546
|
msgType: "markdown",
|
|
18535
18547
|
content: JSON.stringify({ content: payload.text }),
|
|
18536
18548
|
replyToMessageId,
|
|
18537
|
-
skipEncrypt:
|
|
18549
|
+
skipEncrypt: shouldSkipEncrypt
|
|
18538
18550
|
});
|
|
18539
18551
|
log11.info("\u{1F4E4} AI \u6587\u672C\u56DE\u590D\u5DF2\u53D1\u9001", {
|
|
18540
18552
|
chatId,
|
|
@@ -18557,7 +18569,7 @@ function createQuantumImDeliverFn(deps) {
|
|
|
18557
18569
|
maxFileSizeMb,
|
|
18558
18570
|
chunkSizeMb,
|
|
18559
18571
|
timeoutMs,
|
|
18560
|
-
skipEncrypt:
|
|
18572
|
+
skipEncrypt: shouldSkipEncrypt
|
|
18561
18573
|
});
|
|
18562
18574
|
if (result.warning) {
|
|
18563
18575
|
log11.warn("\u{1F4E4} \u5A92\u4F53\u53D1\u9001\u964D\u7EA7", { chatId, mediaUrl, warning: result.warning });
|
|
@@ -18576,7 +18588,7 @@ function createQuantumImDeliverFn(deps) {
|
|
|
18576
18588
|
msgType: "markdown",
|
|
18577
18589
|
content: JSON.stringify({ content: `\u{1F4CE} ${mediaUrl}` }),
|
|
18578
18590
|
replyToMessageId,
|
|
18579
|
-
skipEncrypt:
|
|
18591
|
+
skipEncrypt: shouldSkipEncrypt
|
|
18580
18592
|
});
|
|
18581
18593
|
}
|
|
18582
18594
|
}
|
|
@@ -18626,12 +18638,13 @@ var InboundPipeline = class {
|
|
|
18626
18638
|
});
|
|
18627
18639
|
return;
|
|
18628
18640
|
}
|
|
18641
|
+
const feedbackSkipEncrypt = this.deps.pluginConfig.credentials.encryptionMode === "quantum_only" ? false : true;
|
|
18629
18642
|
this.deps.messagePipe.sendMessage({
|
|
18630
18643
|
chatId: msg.chatId,
|
|
18631
18644
|
senderId: msg.senderId,
|
|
18632
18645
|
msgType: "text",
|
|
18633
18646
|
content: JSON.stringify({ content: "\u4EFB\u52A1\u5DF2\u6536\u5230\u{1F44C}\uFF0C\u6B63\u5728\u5904\u7406\u4E2D..." }),
|
|
18634
|
-
skipEncrypt:
|
|
18647
|
+
skipEncrypt: feedbackSkipEncrypt
|
|
18635
18648
|
}).catch((err) => {
|
|
18636
18649
|
log12.warn("\u26A0\uFE0F \u5373\u65F6\u53CD\u9988\u53D1\u9001\u5931\u8D25", { error: err.message });
|
|
18637
18650
|
});
|
|
@@ -18659,7 +18672,8 @@ var InboundPipeline = class {
|
|
|
18659
18672
|
allowPrivateNetwork: this.deps.pluginConfig.file.allowPrivateNetwork,
|
|
18660
18673
|
maxFileSizeMb: this.deps.pluginConfig.file.maxFileSizeMb,
|
|
18661
18674
|
timeoutMs: this.deps.pluginConfig.file.fetchTimeoutMs,
|
|
18662
|
-
isEncrypted: msg.isEncrypted
|
|
18675
|
+
isEncrypted: msg.isEncrypted,
|
|
18676
|
+
encryptionMode: this.deps.pluginConfig.credentials.encryptionMode
|
|
18663
18677
|
});
|
|
18664
18678
|
const { dispatcher, replyOptions } = core.channel.reply.createReplyDispatcherWithTyping({
|
|
18665
18679
|
deliver
|
|
@@ -18759,6 +18773,13 @@ var QUANTUM_IM_CONFIG_JSON_SCHEMA = {
|
|
|
18759
18773
|
description: "\u9009\u62E9\u5BF9\u63A5\u7684\u670D\u52A1\u5668\u73AF\u5883\u3002staging = \u8054\u8C03\u73AF\u5883\uFF0Cproduction = \u7EBF\u4E0A\u73AF\u5883",
|
|
18760
18774
|
enum: ["staging", "production"],
|
|
18761
18775
|
default: "production"
|
|
18776
|
+
},
|
|
18777
|
+
encryptionMode: {
|
|
18778
|
+
type: "string",
|
|
18779
|
+
title: "\u6D88\u606F\u52A0\u5BC6\u6A21\u5F0F",
|
|
18780
|
+
description: "quantum_only = \u4EC5\u91CF\u5B50\u52A0\u5BC6\u6D88\u606F\uFF08\u6240\u6709\u6D88\u606F\u5FC5\u987B\u52A0\u5BC6\uFF09\uFF1Bquantum_and_plain = \u540C\u65F6\u652F\u6301\u52A0\u5BC6\u548C\u666E\u901A\u6D88\u606F",
|
|
18781
|
+
enum: ["quantum_only", "quantum_and_plain"],
|
|
18782
|
+
default: "quantum_and_plain"
|
|
18762
18783
|
}
|
|
18763
18784
|
}
|
|
18764
18785
|
};
|
|
@@ -18853,33 +18874,39 @@ var quantumImOnboarding = {
|
|
|
18853
18874
|
].join("\n"),
|
|
18854
18875
|
"\u91CF\u5B50\u5BC6\u4FE1 \u2014 \u51ED\u636E\u914D\u7F6E"
|
|
18855
18876
|
);
|
|
18856
|
-
const
|
|
18857
|
-
message: "\
|
|
18858
|
-
initialValue: existing.appId ?? void 0,
|
|
18859
|
-
validate: required2
|
|
18860
|
-
});
|
|
18861
|
-
const appSecret = await prompter.text({
|
|
18862
|
-
message: "\u5E94\u7528\u5BC6\u94A5 (appSecret)",
|
|
18863
|
-
initialValue: existing.appSecret ?? void 0,
|
|
18864
|
-
validate: required2
|
|
18865
|
-
});
|
|
18866
|
-
const encryptionMode = await prompter.select({
|
|
18867
|
-
message: "\u6D88\u606F\u4F20\u8F93\u6A21\u5F0F",
|
|
18877
|
+
const encryptionModeChoice = await prompter.select({
|
|
18878
|
+
message: "\u8BF7\u9009\u62E9\u6D88\u606F\u4EA4\u4E92\u65B9\u5F0F",
|
|
18868
18879
|
options: [
|
|
18869
|
-
{ value: "
|
|
18870
|
-
{ value: "
|
|
18880
|
+
{ value: "quantum_only", label: "\u4EC5\u91CF\u5B50\u52A0\u5BC6\u6D88\u606F" },
|
|
18881
|
+
{ value: "quantum_and_plain", label: "\u91CF\u5B50\u52A0\u5BC6+\u666E\u901A\u6D88\u606F\uFF08\u82E5\u91CF\u5B50\u52A0\u5BC6\u7981\u7528\uFF0C\u5219\u7559\u7A7A\uFF09" }
|
|
18871
18882
|
],
|
|
18872
|
-
initialValue: existing.
|
|
18883
|
+
initialValue: existing.encryptionMode ?? "quantum_and_plain"
|
|
18873
18884
|
});
|
|
18874
18885
|
let quantumAccount = "";
|
|
18875
|
-
if (
|
|
18886
|
+
if (encryptionModeChoice === "quantum_only") {
|
|
18876
18887
|
quantumAccount = await prompter.text({
|
|
18877
18888
|
message: "\u91CF\u5B50\u8D26\u6237\u6807\u8BC6 (quantumAccount)",
|
|
18878
18889
|
placeholder: "\u5982: 17100001111",
|
|
18879
18890
|
initialValue: existing.quantumAccount ?? void 0,
|
|
18880
18891
|
validate: required2
|
|
18881
18892
|
});
|
|
18893
|
+
} else {
|
|
18894
|
+
quantumAccount = await prompter.text({
|
|
18895
|
+
message: "\u91CF\u5B50\u8D26\u6237\u6807\u8BC6\uFF08\u53EF\u9009\uFF0C\u4E0D\u586B\u5219\u4EC5\u652F\u6301\u660E\u6587\uFF09",
|
|
18896
|
+
placeholder: "\u5982: 17100001111",
|
|
18897
|
+
initialValue: existing.quantumAccount ?? void 0
|
|
18898
|
+
});
|
|
18882
18899
|
}
|
|
18900
|
+
const appId = await prompter.text({
|
|
18901
|
+
message: "\u5E94\u7528 ID (appId)",
|
|
18902
|
+
initialValue: existing.appId ?? void 0,
|
|
18903
|
+
validate: required2
|
|
18904
|
+
});
|
|
18905
|
+
const appSecret = await prompter.text({
|
|
18906
|
+
message: "\u5E94\u7528\u5BC6\u94A5 (appSecret)",
|
|
18907
|
+
initialValue: existing.appSecret ?? void 0,
|
|
18908
|
+
validate: required2
|
|
18909
|
+
});
|
|
18883
18910
|
const channels = cfg.channels ?? {};
|
|
18884
18911
|
const channelCfg = channels[CHANNEL_ID] ?? {};
|
|
18885
18912
|
const accounts = channelCfg.accounts ?? {};
|
|
@@ -18895,10 +18922,8 @@ var quantumImOnboarding = {
|
|
|
18895
18922
|
default: {
|
|
18896
18923
|
appId: appId.trim(),
|
|
18897
18924
|
appSecret: appSecret.trim(),
|
|
18898
|
-
|
|
18925
|
+
encryptionMode: encryptionModeChoice,
|
|
18899
18926
|
...quantumAccount.trim() ? { quantumAccount: quantumAccount.trim() } : {}
|
|
18900
|
-
// quantumAppId: quantumAppId.trim(),
|
|
18901
|
-
// quantumAppSecret: quantumAppSecret.trim(),
|
|
18902
18927
|
}
|
|
18903
18928
|
}
|
|
18904
18929
|
}
|
|
@@ -20042,6 +20067,8 @@ var MessagePipe = class {
|
|
|
20042
20067
|
messageCallback = null;
|
|
20043
20068
|
/** 量子账户标识 — 用于判断是否具备解密能力 */
|
|
20044
20069
|
quantumAccount;
|
|
20070
|
+
/** 消息加密模式 */
|
|
20071
|
+
encryptionMode;
|
|
20045
20072
|
constructor(deps) {
|
|
20046
20073
|
this.wsClient = deps.wsClient;
|
|
20047
20074
|
this.dedup = deps.dedup;
|
|
@@ -20049,6 +20076,7 @@ var MessagePipe = class {
|
|
|
20049
20076
|
this.tokenFn = deps.tokenFn;
|
|
20050
20077
|
this.messageServiceBaseUrl = deps.messageServiceBaseUrl;
|
|
20051
20078
|
this.quantumAccount = deps.quantumAccount;
|
|
20079
|
+
this.encryptionMode = deps.encryptionMode;
|
|
20052
20080
|
this.wsClient.on("message", (rawData) => {
|
|
20053
20081
|
this.handleInbound(rawData).catch((err) => {
|
|
20054
20082
|
log21.error("\u274C \u5165\u7AD9\u5904\u7406\u5F02\u5E38", { step: "handleInbound", error: err.message });
|
|
@@ -20074,32 +20102,38 @@ var MessagePipe = class {
|
|
|
20074
20102
|
* POST {messageServiceBaseUrl}/messages/v1/send
|
|
20075
20103
|
* 字段映射: senderId → receive_id, msgType → msg_type
|
|
20076
20104
|
*
|
|
20077
|
-
*
|
|
20078
|
-
*
|
|
20079
|
-
*
|
|
20080
|
-
* - 原始 content 置为空字符串 {content: ''}
|
|
20081
|
-
* - extra 序列化为 JSON 字符串
|
|
20082
|
-
*
|
|
20083
|
-
* 失败不抛异常,避免阻塞 SDK deliver 回调。
|
|
20105
|
+
* 加密行为取决于 encryptionMode:
|
|
20106
|
+
* - quantum_only: 强制加密,失败直接抛错(不降级明文)
|
|
20107
|
+
* - quantum_and_plain: 加密失败降级明文(保持兼容)
|
|
20084
20108
|
*/
|
|
20085
20109
|
async sendMessage(msg) {
|
|
20086
20110
|
let content = msg.content;
|
|
20087
20111
|
let extra = "";
|
|
20088
|
-
if (
|
|
20089
|
-
|
|
20112
|
+
if (!msg.skipEncrypt && this.quantumAccount) {
|
|
20113
|
+
if (this.encryptionMode === "quantum_only") {
|
|
20090
20114
|
const { ciphertext, keyId, iv } = await this.crypto.encrypt(msg.content);
|
|
20091
20115
|
extra = JSON.stringify({
|
|
20092
20116
|
cryptoIv: iv,
|
|
20093
|
-
// 随机生成的 IV,解密时需要回传
|
|
20094
20117
|
encryptMsg: ciphertext,
|
|
20095
20118
|
sessionId: keyId
|
|
20096
20119
|
});
|
|
20097
20120
|
content = JSON.stringify({ content: "" });
|
|
20098
|
-
log21.debug("\u{1F512} \u51FA\u7AD9\u6D88\u606F\u5DF2\u52A0\u5BC6", { sessionId: keyId });
|
|
20099
|
-
}
|
|
20100
|
-
|
|
20101
|
-
|
|
20102
|
-
|
|
20121
|
+
log21.debug("\u{1F512} \u51FA\u7AD9\u6D88\u606F\u5DF2\u52A0\u5BC6 (quantum_only)", { sessionId: keyId });
|
|
20122
|
+
} else {
|
|
20123
|
+
try {
|
|
20124
|
+
const { ciphertext, keyId, iv } = await this.crypto.encrypt(msg.content);
|
|
20125
|
+
extra = JSON.stringify({
|
|
20126
|
+
cryptoIv: iv,
|
|
20127
|
+
encryptMsg: ciphertext,
|
|
20128
|
+
sessionId: keyId
|
|
20129
|
+
});
|
|
20130
|
+
content = JSON.stringify({ content: "" });
|
|
20131
|
+
log21.debug("\u{1F512} \u51FA\u7AD9\u6D88\u606F\u5DF2\u52A0\u5BC6 (quantum_and_plain)", { sessionId: keyId });
|
|
20132
|
+
} catch (err) {
|
|
20133
|
+
log21.error("\u274C \u51FA\u7AD9\u6D88\u606F\u52A0\u5BC6\u5931\u8D25\uFF0C\u5C06\u53D1\u9001\u660E\u6587", {
|
|
20134
|
+
error: err.message
|
|
20135
|
+
});
|
|
20136
|
+
}
|
|
20103
20137
|
}
|
|
20104
20138
|
}
|
|
20105
20139
|
const body = {
|
|
@@ -20231,47 +20265,67 @@ var MessagePipe = class {
|
|
|
20231
20265
|
log21.warn("\u26A0\uFE0F CallbackData \u89E3\u6790\u5931\u8D25", { error: err.message });
|
|
20232
20266
|
return;
|
|
20233
20267
|
}
|
|
20268
|
+
log21.info("\u{1F4E9} \u5165\u7AD9\u539F\u59CB\u6570\u636E", {
|
|
20269
|
+
appId: callbackData.appId,
|
|
20270
|
+
msgUid: callbackData.msgUid,
|
|
20271
|
+
eventType: callbackData.eventType,
|
|
20272
|
+
userId: callbackData.userId,
|
|
20273
|
+
toUserId: callbackData.toUserId,
|
|
20274
|
+
groupId: callbackData.groupId,
|
|
20275
|
+
type: callbackData.type,
|
|
20276
|
+
content: callbackData.content,
|
|
20277
|
+
extra: callbackData.extra
|
|
20278
|
+
});
|
|
20234
20279
|
const extra = callbackData.extra;
|
|
20235
|
-
|
|
20280
|
+
let isEncrypted = false;
|
|
20281
|
+
if (typeof extra === "string" && extra.length > 0) {
|
|
20282
|
+
try {
|
|
20283
|
+
const parsed = JSON.parse(extra);
|
|
20284
|
+
isEncrypted = Boolean(parsed.encryptMsg);
|
|
20285
|
+
} catch {
|
|
20286
|
+
log21.debug("\u2139\uFE0F extra \u4E0D\u662F\u6709\u6548 JSON\uFF0C\u89C6\u4E3A\u660E\u6587", { msgUid: callbackData.msgUid });
|
|
20287
|
+
}
|
|
20288
|
+
}
|
|
20236
20289
|
let contentObj;
|
|
20237
|
-
if (
|
|
20290
|
+
if (this.encryptionMode === "quantum_only") {
|
|
20238
20291
|
if (!this.quantumAccount) {
|
|
20239
|
-
log21.
|
|
20240
|
-
|
|
20241
|
-
|
|
20242
|
-
|
|
20292
|
+
log21.info("\u26A0\uFE0F quantum_only: \u672A\u914D\u7F6E quantumAccount\uFF0C\u53D1\u9001\u5F15\u5BFC\u63D0\u793A", { chatId: callbackData.userId });
|
|
20293
|
+
await this.sendHintMessage(callbackData, HINT_NO_QUANTUM_ACCOUNT);
|
|
20294
|
+
return;
|
|
20295
|
+
}
|
|
20296
|
+
if (!isEncrypted) {
|
|
20297
|
+
log21.info("\u26A0\uFE0F quantum_only: \u6536\u5230\u660E\u6587\u6D88\u606F\uFF0C\u62D2\u7EDD\u5904\u7406", { msgUid: callbackData.msgUid });
|
|
20298
|
+
await this.sendHintMessage(callbackData, HINT_PLAINTEXT_NOT_SUPPORTED);
|
|
20243
20299
|
return;
|
|
20244
20300
|
}
|
|
20245
20301
|
try {
|
|
20246
|
-
|
|
20247
|
-
const encryptMsg = extraData.encryptMsg ?? "";
|
|
20248
|
-
const sessionId = extraData.sessionId ?? "";
|
|
20249
|
-
if (!encryptMsg) {
|
|
20250
|
-
log21.warn("\u26A0\uFE0F extra \u4E2D encryptMsg \u4E3A\u7A7A\uFF0C\u8DF3\u8FC7\u89E3\u5BC6", { msgUid: callbackData.msgUid });
|
|
20251
|
-
contentObj = callbackData.content;
|
|
20252
|
-
} else {
|
|
20253
|
-
const cryptoIv = extraData.cryptoIv ?? "";
|
|
20254
|
-
const decrypted = await this.crypto.decrypt(encryptMsg, sessionId, cryptoIv);
|
|
20255
|
-
try {
|
|
20256
|
-
contentObj = JSON.parse(decrypted);
|
|
20257
|
-
} catch {
|
|
20258
|
-
contentObj = { content: decrypted };
|
|
20259
|
-
}
|
|
20260
|
-
log21.debug("\u{1F513} \u6D88\u606F\u89E3\u5BC6\u6210\u529F", {
|
|
20261
|
-
msgUid: callbackData.msgUid,
|
|
20262
|
-
sessionId,
|
|
20263
|
-
hasIv: Boolean(extraData.cryptoIv)
|
|
20264
|
-
});
|
|
20265
|
-
}
|
|
20302
|
+
contentObj = await this.decryptExtra(callbackData);
|
|
20266
20303
|
} catch (err) {
|
|
20267
|
-
log21.error("\u274C \u6D88\u606F\u89E3\u5BC6\u5931\u8D25", {
|
|
20304
|
+
log21.error("\u274C quantum_only: \u6D88\u606F\u89E3\u5BC6\u5931\u8D25", {
|
|
20268
20305
|
msgUid: callbackData.msgUid,
|
|
20269
20306
|
error: err.message
|
|
20270
20307
|
});
|
|
20271
20308
|
return;
|
|
20272
20309
|
}
|
|
20273
20310
|
} else {
|
|
20274
|
-
|
|
20311
|
+
if (isEncrypted) {
|
|
20312
|
+
if (!this.quantumAccount) {
|
|
20313
|
+
log21.info("\u26A0\uFE0F quantum_and_plain: \u6536\u5230\u52A0\u5BC6\u6D88\u606F\u4F46\u672A\u914D\u7F6E quantumAccount", { msgUid: callbackData.msgUid });
|
|
20314
|
+
await this.sendHintMessage(callbackData, HINT_NO_QUANTUM_ACCOUNT);
|
|
20315
|
+
return;
|
|
20316
|
+
}
|
|
20317
|
+
try {
|
|
20318
|
+
contentObj = await this.decryptExtra(callbackData);
|
|
20319
|
+
} catch (err) {
|
|
20320
|
+
log21.error("\u274C quantum_and_plain: \u6D88\u606F\u89E3\u5BC6\u5931\u8D25", {
|
|
20321
|
+
msgUid: callbackData.msgUid,
|
|
20322
|
+
error: err.message
|
|
20323
|
+
});
|
|
20324
|
+
return;
|
|
20325
|
+
}
|
|
20326
|
+
} else {
|
|
20327
|
+
contentObj = callbackData.content;
|
|
20328
|
+
}
|
|
20275
20329
|
}
|
|
20276
20330
|
if (callbackData.eventType !== "callback:direct") {
|
|
20277
20331
|
log21.debug("\u2139\uFE0F \u5FFD\u7565\u975E\u76EE\u6807\u4E8B\u4EF6", { eventType: callbackData.eventType });
|
|
@@ -20298,6 +20352,48 @@ var MessagePipe = class {
|
|
|
20298
20352
|
log21.warn("\u26A0\uFE0F \u6D88\u606F\u56DE\u8C03\u672A\u6CE8\u518C\uFF0C\u65E0\u6CD5\u5904\u7406", { messageId: msg.messageId });
|
|
20299
20353
|
}
|
|
20300
20354
|
}
|
|
20355
|
+
// ── 私有辅助方法 ──
|
|
20356
|
+
/**
|
|
20357
|
+
* 发送系统提示消息(明文,不加密)。
|
|
20358
|
+
* 用于在模式不匹配时向用户发送引导信息。
|
|
20359
|
+
*/
|
|
20360
|
+
async sendHintMessage(callbackData, hintText) {
|
|
20361
|
+
const chatId = callbackData.groupId || callbackData.userId;
|
|
20362
|
+
const senderId = callbackData.userId;
|
|
20363
|
+
try {
|
|
20364
|
+
await this.sendMessage({
|
|
20365
|
+
chatId,
|
|
20366
|
+
senderId,
|
|
20367
|
+
msgType: "markdown",
|
|
20368
|
+
content: JSON.stringify({ content: hintText }),
|
|
20369
|
+
skipEncrypt: true
|
|
20370
|
+
});
|
|
20371
|
+
log21.info("\u{1F4A1} \u5DF2\u53D1\u9001\u6A21\u5F0F\u63D0\u793A\u6D88\u606F", { chatId });
|
|
20372
|
+
} catch (err) {
|
|
20373
|
+
log21.error("\u274C \u63D0\u793A\u6D88\u606F\u53D1\u9001\u5931\u8D25", { error: err.message });
|
|
20374
|
+
}
|
|
20375
|
+
}
|
|
20376
|
+
/**
|
|
20377
|
+
* 解密 extra 中的加密内容。
|
|
20378
|
+
* @throws 解密失败时抛出异常
|
|
20379
|
+
*/
|
|
20380
|
+
async decryptExtra(callbackData) {
|
|
20381
|
+
const extraData = JSON.parse(callbackData.extra);
|
|
20382
|
+
const encryptMsg = extraData.encryptMsg ?? "";
|
|
20383
|
+
const sessionId = extraData.sessionId ?? "";
|
|
20384
|
+
if (!encryptMsg) {
|
|
20385
|
+
log21.warn("\u26A0\uFE0F extra \u4E2D encryptMsg \u4E3A\u7A7A\uFF0C\u4F7F\u7528\u539F\u59CB content", { msgUid: callbackData.msgUid });
|
|
20386
|
+
return callbackData.content;
|
|
20387
|
+
}
|
|
20388
|
+
const cryptoIv = extraData.cryptoIv ?? "";
|
|
20389
|
+
const decrypted = await this.crypto.decrypt(encryptMsg, sessionId, cryptoIv);
|
|
20390
|
+
log21.debug("\u{1F513} \u6D88\u606F\u89E3\u5BC6\u6210\u529F", { msgUid: callbackData.msgUid, sessionId, hasIv: Boolean(cryptoIv) });
|
|
20391
|
+
try {
|
|
20392
|
+
return JSON.parse(decrypted);
|
|
20393
|
+
} catch {
|
|
20394
|
+
return { content: decrypted };
|
|
20395
|
+
}
|
|
20396
|
+
}
|
|
20301
20397
|
};
|
|
20302
20398
|
|
|
20303
20399
|
// src/transport/connection-manager.ts
|
|
@@ -20380,7 +20476,7 @@ var ConnectionManager = class {
|
|
|
20380
20476
|
this.client.on("pong", () => {
|
|
20381
20477
|
this.pongReceived = true;
|
|
20382
20478
|
this.clearPongTimeout();
|
|
20383
|
-
log22.
|
|
20479
|
+
log22.debug("heartbeat: pong received");
|
|
20384
20480
|
});
|
|
20385
20481
|
}
|
|
20386
20482
|
/** 启动心跳保活 — 定时发送 WebSocket Ping 帧 */
|
|
@@ -20401,7 +20497,7 @@ var ConnectionManager = class {
|
|
|
20401
20497
|
}
|
|
20402
20498
|
this.pongReceived = false;
|
|
20403
20499
|
this.client.ping();
|
|
20404
|
-
log22.
|
|
20500
|
+
log22.debug("heartbeat: ping sent");
|
|
20405
20501
|
this.clearPongTimeout();
|
|
20406
20502
|
this.pongTimeoutTimer = setTimeout(() => {
|
|
20407
20503
|
if (!this.pongReceived && this.running) {
|
|
@@ -20933,7 +21029,8 @@ async function startPlugin(accountConfig, internalOverrides) {
|
|
|
20933
21029
|
crypto: cryptoEngine,
|
|
20934
21030
|
tokenFn: () => tokenManager.getValidToken(),
|
|
20935
21031
|
messageServiceBaseUrl: config2.message.messageServiceBaseUrl,
|
|
20936
|
-
quantumAccount: accountConfig.quantumAccount
|
|
21032
|
+
quantumAccount: accountConfig.quantumAccount,
|
|
21033
|
+
encryptionMode: accountConfig.encryptionMode
|
|
20937
21034
|
});
|
|
20938
21035
|
const connectionManager = new ConnectionManager(wsClient, {
|
|
20939
21036
|
heartbeatIntervalMs: config2.transport.heartbeatIntervalMs,
|
package/dist/index.d.cts
CHANGED
|
@@ -23,15 +23,21 @@ declare const AccountConfigSchema: z.ZodPipe<z.ZodObject<{
|
|
|
23
23
|
staging: "staging";
|
|
24
24
|
production: "production";
|
|
25
25
|
}>>;
|
|
26
|
+
encryptionMode: z.ZodDefault<z.ZodEnum<{
|
|
27
|
+
quantum_only: "quantum_only";
|
|
28
|
+
quantum_and_plain: "quantum_and_plain";
|
|
29
|
+
}>>;
|
|
26
30
|
}, z.core.$strip>, z.ZodTransform<{
|
|
27
31
|
appId: string;
|
|
28
32
|
appSecret: string;
|
|
29
33
|
quantumAccount: string | undefined;
|
|
30
34
|
botUserId: string | undefined;
|
|
31
35
|
env: "staging" | "production" | undefined;
|
|
36
|
+
encryptionMode: "quantum_only" | "quantum_and_plain";
|
|
32
37
|
}, {
|
|
33
38
|
appId: string;
|
|
34
39
|
appSecret: string;
|
|
40
|
+
encryptionMode: "quantum_only" | "quantum_and_plain";
|
|
35
41
|
quantumAccount?: string | undefined;
|
|
36
42
|
botUserId?: string | undefined;
|
|
37
43
|
env?: "staging" | "production" | undefined;
|
|
@@ -757,6 +763,8 @@ declare class MessagePipe {
|
|
|
757
763
|
private messageCallback;
|
|
758
764
|
/** 量子账户标识 — 用于判断是否具备解密能力 */
|
|
759
765
|
private readonly quantumAccount?;
|
|
766
|
+
/** 消息加密模式 */
|
|
767
|
+
private readonly encryptionMode;
|
|
760
768
|
constructor(deps: {
|
|
761
769
|
wsClient: WSClient;
|
|
762
770
|
dedup: MessageDedup;
|
|
@@ -767,6 +775,8 @@ declare class MessagePipe {
|
|
|
767
775
|
messageServiceBaseUrl: string;
|
|
768
776
|
/** 量子账户标识 — 用于判断是否具备解密能力 */
|
|
769
777
|
quantumAccount?: string;
|
|
778
|
+
/** 消息加密模式 */
|
|
779
|
+
encryptionMode: 'quantum_only' | 'quantum_and_plain';
|
|
770
780
|
});
|
|
771
781
|
/** 注册入站消息回调 — 解密后的消息会通过此回调传给 L4 层 */
|
|
772
782
|
onMessage(callback: (msg: InboundMessage) => void): void;
|
|
@@ -781,13 +791,9 @@ declare class MessagePipe {
|
|
|
781
791
|
* POST {messageServiceBaseUrl}/messages/v1/send
|
|
782
792
|
* 字段映射: senderId → receive_id, msgType → msg_type
|
|
783
793
|
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
786
|
-
*
|
|
787
|
-
* - 原始 content 置为空字符串 {content: ''}
|
|
788
|
-
* - extra 序列化为 JSON 字符串
|
|
789
|
-
*
|
|
790
|
-
* 失败不抛异常,避免阻塞 SDK deliver 回调。
|
|
794
|
+
* 加密行为取决于 encryptionMode:
|
|
795
|
+
* - quantum_only: 强制加密,失败直接抛错(不降级明文)
|
|
796
|
+
* - quantum_and_plain: 加密失败降级明文(保持兼容)
|
|
791
797
|
*/
|
|
792
798
|
sendMessage(msg: OutboundMessage): Promise<void>;
|
|
793
799
|
/**
|
|
@@ -822,6 +828,16 @@ declare class MessagePipe {
|
|
|
822
828
|
* 7. 回调 L4
|
|
823
829
|
*/
|
|
824
830
|
private handleInbound;
|
|
831
|
+
/**
|
|
832
|
+
* 发送系统提示消息(明文,不加密)。
|
|
833
|
+
* 用于在模式不匹配时向用户发送引导信息。
|
|
834
|
+
*/
|
|
835
|
+
private sendHintMessage;
|
|
836
|
+
/**
|
|
837
|
+
* 解密 extra 中的加密内容。
|
|
838
|
+
* @throws 解密失败时抛出异常
|
|
839
|
+
*/
|
|
840
|
+
private decryptExtra;
|
|
825
841
|
}
|
|
826
842
|
|
|
827
843
|
/**
|
|
@@ -1141,6 +1157,13 @@ declare const QUANTUM_IM_CONFIG_JSON_SCHEMA: {
|
|
|
1141
1157
|
readonly enum: readonly ["staging", "production"];
|
|
1142
1158
|
readonly default: "production";
|
|
1143
1159
|
};
|
|
1160
|
+
readonly encryptionMode: {
|
|
1161
|
+
readonly type: "string";
|
|
1162
|
+
readonly title: "消息加密模式";
|
|
1163
|
+
readonly description: "quantum_only = 仅量子加密消息(所有消息必须加密);quantum_and_plain = 同时支持加密和普通消息";
|
|
1164
|
+
readonly enum: readonly ["quantum_only", "quantum_and_plain"];
|
|
1165
|
+
readonly default: "quantum_and_plain";
|
|
1166
|
+
};
|
|
1144
1167
|
};
|
|
1145
1168
|
};
|
|
1146
1169
|
|
package/dist/setup-entry.cjs
CHANGED
|
@@ -3688,6 +3688,9 @@ function parseChannelAddress(address) {
|
|
|
3688
3688
|
const prefix = `${CHANNEL_ID}:`;
|
|
3689
3689
|
return address.startsWith(prefix) ? address.slice(prefix.length) : address;
|
|
3690
3690
|
}
|
|
3691
|
+
var GUIDE_URL = "https://www.kdocs.cn/l/ca5LX4wxpSSE";
|
|
3692
|
+
var HINT_NO_QUANTUM_ACCOUNT = "\u60A8\u672A\u914D\u7F6E\u91CF\u5B50\u52A0\u5BC6\u53C2\u6570\uFF0C\u8BF7\u53C2\u8003[\u300A\u63A5\u5165\u6307\u5357\u300B\u5B89\u88C5\u91CF\u5B50\u5BC6\u4FE1\u63D2\u4EF6](" + GUIDE_URL + ") \u4FEE\u6539\u914D\u7F6E";
|
|
3693
|
+
var HINT_PLAINTEXT_NOT_SUPPORTED = "\u5F53\u524D\u6A21\u5F0F\u4E0D\u652F\u6301\u666E\u901A\u6D88\u606F\uFF0C\u8BF7\u53C2\u8003[\u300A\u63A5\u5165\u6307\u5357\u300B\u5B89\u88C5\u91CF\u5B50\u5BC6\u4FE1\u63D2\u4EF6](" + GUIDE_URL + ") \u4FEE\u6539\u914D\u7F6E";
|
|
3691
3694
|
|
|
3692
3695
|
// src/logger.ts
|
|
3693
3696
|
var LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
@@ -4275,13 +4278,15 @@ var quantumImOutbound = {
|
|
|
4275
4278
|
}
|
|
4276
4279
|
const messagePipe = messagePipeGetter();
|
|
4277
4280
|
const chatId = parseChannelAddress(to);
|
|
4281
|
+
const encryptionMode = pluginConfigGetter?.()?.credentials?.encryptionMode;
|
|
4278
4282
|
const isEncrypted = getOutboundEncryptionStatus(chatId);
|
|
4283
|
+
const skipEncrypt = encryptionMode === "quantum_only" ? false : isEncrypted !== true;
|
|
4279
4284
|
await messagePipe.sendMessage({
|
|
4280
4285
|
chatId,
|
|
4281
4286
|
senderId: chatId,
|
|
4282
4287
|
msgType: "markdown",
|
|
4283
4288
|
content: JSON.stringify({ content: text }),
|
|
4284
|
-
skipEncrypt
|
|
4289
|
+
skipEncrypt
|
|
4285
4290
|
});
|
|
4286
4291
|
return { channel: CHANNEL_ID, messageId: "", chatId };
|
|
4287
4292
|
},
|
|
@@ -4296,7 +4301,9 @@ var quantumImOutbound = {
|
|
|
4296
4301
|
const sdkRuntime = sdkRuntimeGetter();
|
|
4297
4302
|
const chatId = parseChannelAddress(to);
|
|
4298
4303
|
const fileCfg = pluginConfigGetter().file;
|
|
4304
|
+
const encryptionMode = pluginConfigGetter?.()?.credentials?.encryptionMode;
|
|
4299
4305
|
const isEncrypted = getOutboundEncryptionStatus(chatId);
|
|
4306
|
+
const skipEncrypt = encryptionMode === "quantum_only" ? false : isEncrypted !== true;
|
|
4300
4307
|
const result = await resolveAndUploadMedia({
|
|
4301
4308
|
mediaUrl: mediaUrl ?? void 0,
|
|
4302
4309
|
tokenManager: tokenManagerGetter(),
|
|
@@ -4309,7 +4316,7 @@ var quantumImOutbound = {
|
|
|
4309
4316
|
maxFileSizeMb: fileCfg.maxFileSizeMb,
|
|
4310
4317
|
chunkSizeMb: fileCfg.chunkSizeMb,
|
|
4311
4318
|
timeoutMs: fileCfg.fetchTimeoutMs,
|
|
4312
|
-
skipEncrypt
|
|
4319
|
+
skipEncrypt
|
|
4313
4320
|
});
|
|
4314
4321
|
if (result.warning) {
|
|
4315
4322
|
log4.warn("sendMedia:degraded", { chatId, warning: result.warning });
|
|
@@ -18110,7 +18117,9 @@ var AccountConfigSchema = external_exports.object({
|
|
|
18110
18117
|
*/
|
|
18111
18118
|
botUserId: external_exports.string().optional(),
|
|
18112
18119
|
/** 🟡 部署环境 — staging=联调, production=线上 */
|
|
18113
|
-
env: external_exports.enum(["staging", "production"]).optional()
|
|
18120
|
+
env: external_exports.enum(["staging", "production"]).optional(),
|
|
18121
|
+
/** 🟡 消息加密模式 — quantum_only=仅加密, quantum_and_plain=加密+普通(默认) */
|
|
18122
|
+
encryptionMode: external_exports.enum(["quantum_only", "quantum_and_plain"]).default("quantum_and_plain")
|
|
18114
18123
|
}).transform((raw) => ({
|
|
18115
18124
|
appId: raw.appId,
|
|
18116
18125
|
appSecret: raw.appSecret,
|
|
@@ -18119,7 +18128,8 @@ var AccountConfigSchema = external_exports.object({
|
|
|
18119
18128
|
// quantumAppId: raw.quantumAppId,
|
|
18120
18129
|
// quantumAppSecret: raw.quantumAppSecret,
|
|
18121
18130
|
botUserId: raw.botUserId,
|
|
18122
|
-
env: raw.env
|
|
18131
|
+
env: raw.env,
|
|
18132
|
+
encryptionMode: raw.encryptionMode
|
|
18123
18133
|
}));
|
|
18124
18134
|
var ENV_PRESETS = {
|
|
18125
18135
|
/** 联调环境 */
|
|
@@ -19101,6 +19111,8 @@ var MessagePipe = class {
|
|
|
19101
19111
|
messageCallback = null;
|
|
19102
19112
|
/** 量子账户标识 — 用于判断是否具备解密能力 */
|
|
19103
19113
|
quantumAccount;
|
|
19114
|
+
/** 消息加密模式 */
|
|
19115
|
+
encryptionMode;
|
|
19104
19116
|
constructor(deps) {
|
|
19105
19117
|
this.wsClient = deps.wsClient;
|
|
19106
19118
|
this.dedup = deps.dedup;
|
|
@@ -19108,6 +19120,7 @@ var MessagePipe = class {
|
|
|
19108
19120
|
this.tokenFn = deps.tokenFn;
|
|
19109
19121
|
this.messageServiceBaseUrl = deps.messageServiceBaseUrl;
|
|
19110
19122
|
this.quantumAccount = deps.quantumAccount;
|
|
19123
|
+
this.encryptionMode = deps.encryptionMode;
|
|
19111
19124
|
this.wsClient.on("message", (rawData) => {
|
|
19112
19125
|
this.handleInbound(rawData).catch((err) => {
|
|
19113
19126
|
log12.error("\u274C \u5165\u7AD9\u5904\u7406\u5F02\u5E38", { step: "handleInbound", error: err.message });
|
|
@@ -19133,32 +19146,38 @@ var MessagePipe = class {
|
|
|
19133
19146
|
* POST {messageServiceBaseUrl}/messages/v1/send
|
|
19134
19147
|
* 字段映射: senderId → receive_id, msgType → msg_type
|
|
19135
19148
|
*
|
|
19136
|
-
*
|
|
19137
|
-
*
|
|
19138
|
-
*
|
|
19139
|
-
* - 原始 content 置为空字符串 {content: ''}
|
|
19140
|
-
* - extra 序列化为 JSON 字符串
|
|
19141
|
-
*
|
|
19142
|
-
* 失败不抛异常,避免阻塞 SDK deliver 回调。
|
|
19149
|
+
* 加密行为取决于 encryptionMode:
|
|
19150
|
+
* - quantum_only: 强制加密,失败直接抛错(不降级明文)
|
|
19151
|
+
* - quantum_and_plain: 加密失败降级明文(保持兼容)
|
|
19143
19152
|
*/
|
|
19144
19153
|
async sendMessage(msg) {
|
|
19145
19154
|
let content = msg.content;
|
|
19146
19155
|
let extra = "";
|
|
19147
|
-
if (
|
|
19148
|
-
|
|
19156
|
+
if (!msg.skipEncrypt && this.quantumAccount) {
|
|
19157
|
+
if (this.encryptionMode === "quantum_only") {
|
|
19149
19158
|
const { ciphertext, keyId, iv } = await this.crypto.encrypt(msg.content);
|
|
19150
19159
|
extra = JSON.stringify({
|
|
19151
19160
|
cryptoIv: iv,
|
|
19152
|
-
// 随机生成的 IV,解密时需要回传
|
|
19153
19161
|
encryptMsg: ciphertext,
|
|
19154
19162
|
sessionId: keyId
|
|
19155
19163
|
});
|
|
19156
19164
|
content = JSON.stringify({ content: "" });
|
|
19157
|
-
log12.debug("\u{1F512} \u51FA\u7AD9\u6D88\u606F\u5DF2\u52A0\u5BC6", { sessionId: keyId });
|
|
19158
|
-
}
|
|
19159
|
-
|
|
19160
|
-
|
|
19161
|
-
|
|
19165
|
+
log12.debug("\u{1F512} \u51FA\u7AD9\u6D88\u606F\u5DF2\u52A0\u5BC6 (quantum_only)", { sessionId: keyId });
|
|
19166
|
+
} else {
|
|
19167
|
+
try {
|
|
19168
|
+
const { ciphertext, keyId, iv } = await this.crypto.encrypt(msg.content);
|
|
19169
|
+
extra = JSON.stringify({
|
|
19170
|
+
cryptoIv: iv,
|
|
19171
|
+
encryptMsg: ciphertext,
|
|
19172
|
+
sessionId: keyId
|
|
19173
|
+
});
|
|
19174
|
+
content = JSON.stringify({ content: "" });
|
|
19175
|
+
log12.debug("\u{1F512} \u51FA\u7AD9\u6D88\u606F\u5DF2\u52A0\u5BC6 (quantum_and_plain)", { sessionId: keyId });
|
|
19176
|
+
} catch (err) {
|
|
19177
|
+
log12.error("\u274C \u51FA\u7AD9\u6D88\u606F\u52A0\u5BC6\u5931\u8D25\uFF0C\u5C06\u53D1\u9001\u660E\u6587", {
|
|
19178
|
+
error: err.message
|
|
19179
|
+
});
|
|
19180
|
+
}
|
|
19162
19181
|
}
|
|
19163
19182
|
}
|
|
19164
19183
|
const body = {
|
|
@@ -19290,47 +19309,67 @@ var MessagePipe = class {
|
|
|
19290
19309
|
log12.warn("\u26A0\uFE0F CallbackData \u89E3\u6790\u5931\u8D25", { error: err.message });
|
|
19291
19310
|
return;
|
|
19292
19311
|
}
|
|
19312
|
+
log12.info("\u{1F4E9} \u5165\u7AD9\u539F\u59CB\u6570\u636E", {
|
|
19313
|
+
appId: callbackData.appId,
|
|
19314
|
+
msgUid: callbackData.msgUid,
|
|
19315
|
+
eventType: callbackData.eventType,
|
|
19316
|
+
userId: callbackData.userId,
|
|
19317
|
+
toUserId: callbackData.toUserId,
|
|
19318
|
+
groupId: callbackData.groupId,
|
|
19319
|
+
type: callbackData.type,
|
|
19320
|
+
content: callbackData.content,
|
|
19321
|
+
extra: callbackData.extra
|
|
19322
|
+
});
|
|
19293
19323
|
const extra = callbackData.extra;
|
|
19294
|
-
|
|
19324
|
+
let isEncrypted = false;
|
|
19325
|
+
if (typeof extra === "string" && extra.length > 0) {
|
|
19326
|
+
try {
|
|
19327
|
+
const parsed = JSON.parse(extra);
|
|
19328
|
+
isEncrypted = Boolean(parsed.encryptMsg);
|
|
19329
|
+
} catch {
|
|
19330
|
+
log12.debug("\u2139\uFE0F extra \u4E0D\u662F\u6709\u6548 JSON\uFF0C\u89C6\u4E3A\u660E\u6587", { msgUid: callbackData.msgUid });
|
|
19331
|
+
}
|
|
19332
|
+
}
|
|
19295
19333
|
let contentObj;
|
|
19296
|
-
if (
|
|
19334
|
+
if (this.encryptionMode === "quantum_only") {
|
|
19297
19335
|
if (!this.quantumAccount) {
|
|
19298
|
-
log12.
|
|
19299
|
-
|
|
19300
|
-
|
|
19301
|
-
|
|
19336
|
+
log12.info("\u26A0\uFE0F quantum_only: \u672A\u914D\u7F6E quantumAccount\uFF0C\u53D1\u9001\u5F15\u5BFC\u63D0\u793A", { chatId: callbackData.userId });
|
|
19337
|
+
await this.sendHintMessage(callbackData, HINT_NO_QUANTUM_ACCOUNT);
|
|
19338
|
+
return;
|
|
19339
|
+
}
|
|
19340
|
+
if (!isEncrypted) {
|
|
19341
|
+
log12.info("\u26A0\uFE0F quantum_only: \u6536\u5230\u660E\u6587\u6D88\u606F\uFF0C\u62D2\u7EDD\u5904\u7406", { msgUid: callbackData.msgUid });
|
|
19342
|
+
await this.sendHintMessage(callbackData, HINT_PLAINTEXT_NOT_SUPPORTED);
|
|
19302
19343
|
return;
|
|
19303
19344
|
}
|
|
19304
19345
|
try {
|
|
19305
|
-
|
|
19306
|
-
const encryptMsg = extraData.encryptMsg ?? "";
|
|
19307
|
-
const sessionId = extraData.sessionId ?? "";
|
|
19308
|
-
if (!encryptMsg) {
|
|
19309
|
-
log12.warn("\u26A0\uFE0F extra \u4E2D encryptMsg \u4E3A\u7A7A\uFF0C\u8DF3\u8FC7\u89E3\u5BC6", { msgUid: callbackData.msgUid });
|
|
19310
|
-
contentObj = callbackData.content;
|
|
19311
|
-
} else {
|
|
19312
|
-
const cryptoIv = extraData.cryptoIv ?? "";
|
|
19313
|
-
const decrypted = await this.crypto.decrypt(encryptMsg, sessionId, cryptoIv);
|
|
19314
|
-
try {
|
|
19315
|
-
contentObj = JSON.parse(decrypted);
|
|
19316
|
-
} catch {
|
|
19317
|
-
contentObj = { content: decrypted };
|
|
19318
|
-
}
|
|
19319
|
-
log12.debug("\u{1F513} \u6D88\u606F\u89E3\u5BC6\u6210\u529F", {
|
|
19320
|
-
msgUid: callbackData.msgUid,
|
|
19321
|
-
sessionId,
|
|
19322
|
-
hasIv: Boolean(extraData.cryptoIv)
|
|
19323
|
-
});
|
|
19324
|
-
}
|
|
19346
|
+
contentObj = await this.decryptExtra(callbackData);
|
|
19325
19347
|
} catch (err) {
|
|
19326
|
-
log12.error("\u274C \u6D88\u606F\u89E3\u5BC6\u5931\u8D25", {
|
|
19348
|
+
log12.error("\u274C quantum_only: \u6D88\u606F\u89E3\u5BC6\u5931\u8D25", {
|
|
19327
19349
|
msgUid: callbackData.msgUid,
|
|
19328
19350
|
error: err.message
|
|
19329
19351
|
});
|
|
19330
19352
|
return;
|
|
19331
19353
|
}
|
|
19332
19354
|
} else {
|
|
19333
|
-
|
|
19355
|
+
if (isEncrypted) {
|
|
19356
|
+
if (!this.quantumAccount) {
|
|
19357
|
+
log12.info("\u26A0\uFE0F quantum_and_plain: \u6536\u5230\u52A0\u5BC6\u6D88\u606F\u4F46\u672A\u914D\u7F6E quantumAccount", { msgUid: callbackData.msgUid });
|
|
19358
|
+
await this.sendHintMessage(callbackData, HINT_NO_QUANTUM_ACCOUNT);
|
|
19359
|
+
return;
|
|
19360
|
+
}
|
|
19361
|
+
try {
|
|
19362
|
+
contentObj = await this.decryptExtra(callbackData);
|
|
19363
|
+
} catch (err) {
|
|
19364
|
+
log12.error("\u274C quantum_and_plain: \u6D88\u606F\u89E3\u5BC6\u5931\u8D25", {
|
|
19365
|
+
msgUid: callbackData.msgUid,
|
|
19366
|
+
error: err.message
|
|
19367
|
+
});
|
|
19368
|
+
return;
|
|
19369
|
+
}
|
|
19370
|
+
} else {
|
|
19371
|
+
contentObj = callbackData.content;
|
|
19372
|
+
}
|
|
19334
19373
|
}
|
|
19335
19374
|
if (callbackData.eventType !== "callback:direct") {
|
|
19336
19375
|
log12.debug("\u2139\uFE0F \u5FFD\u7565\u975E\u76EE\u6807\u4E8B\u4EF6", { eventType: callbackData.eventType });
|
|
@@ -19357,6 +19396,48 @@ var MessagePipe = class {
|
|
|
19357
19396
|
log12.warn("\u26A0\uFE0F \u6D88\u606F\u56DE\u8C03\u672A\u6CE8\u518C\uFF0C\u65E0\u6CD5\u5904\u7406", { messageId: msg.messageId });
|
|
19358
19397
|
}
|
|
19359
19398
|
}
|
|
19399
|
+
// ── 私有辅助方法 ──
|
|
19400
|
+
/**
|
|
19401
|
+
* 发送系统提示消息(明文,不加密)。
|
|
19402
|
+
* 用于在模式不匹配时向用户发送引导信息。
|
|
19403
|
+
*/
|
|
19404
|
+
async sendHintMessage(callbackData, hintText) {
|
|
19405
|
+
const chatId = callbackData.groupId || callbackData.userId;
|
|
19406
|
+
const senderId = callbackData.userId;
|
|
19407
|
+
try {
|
|
19408
|
+
await this.sendMessage({
|
|
19409
|
+
chatId,
|
|
19410
|
+
senderId,
|
|
19411
|
+
msgType: "markdown",
|
|
19412
|
+
content: JSON.stringify({ content: hintText }),
|
|
19413
|
+
skipEncrypt: true
|
|
19414
|
+
});
|
|
19415
|
+
log12.info("\u{1F4A1} \u5DF2\u53D1\u9001\u6A21\u5F0F\u63D0\u793A\u6D88\u606F", { chatId });
|
|
19416
|
+
} catch (err) {
|
|
19417
|
+
log12.error("\u274C \u63D0\u793A\u6D88\u606F\u53D1\u9001\u5931\u8D25", { error: err.message });
|
|
19418
|
+
}
|
|
19419
|
+
}
|
|
19420
|
+
/**
|
|
19421
|
+
* 解密 extra 中的加密内容。
|
|
19422
|
+
* @throws 解密失败时抛出异常
|
|
19423
|
+
*/
|
|
19424
|
+
async decryptExtra(callbackData) {
|
|
19425
|
+
const extraData = JSON.parse(callbackData.extra);
|
|
19426
|
+
const encryptMsg = extraData.encryptMsg ?? "";
|
|
19427
|
+
const sessionId = extraData.sessionId ?? "";
|
|
19428
|
+
if (!encryptMsg) {
|
|
19429
|
+
log12.warn("\u26A0\uFE0F extra \u4E2D encryptMsg \u4E3A\u7A7A\uFF0C\u4F7F\u7528\u539F\u59CB content", { msgUid: callbackData.msgUid });
|
|
19430
|
+
return callbackData.content;
|
|
19431
|
+
}
|
|
19432
|
+
const cryptoIv = extraData.cryptoIv ?? "";
|
|
19433
|
+
const decrypted = await this.crypto.decrypt(encryptMsg, sessionId, cryptoIv);
|
|
19434
|
+
log12.debug("\u{1F513} \u6D88\u606F\u89E3\u5BC6\u6210\u529F", { msgUid: callbackData.msgUid, sessionId, hasIv: Boolean(cryptoIv) });
|
|
19435
|
+
try {
|
|
19436
|
+
return JSON.parse(decrypted);
|
|
19437
|
+
} catch {
|
|
19438
|
+
return { content: decrypted };
|
|
19439
|
+
}
|
|
19440
|
+
}
|
|
19360
19441
|
};
|
|
19361
19442
|
|
|
19362
19443
|
// src/transport/connection-manager.ts
|
|
@@ -19439,7 +19520,7 @@ var ConnectionManager = class {
|
|
|
19439
19520
|
this.client.on("pong", () => {
|
|
19440
19521
|
this.pongReceived = true;
|
|
19441
19522
|
this.clearPongTimeout();
|
|
19442
|
-
log13.
|
|
19523
|
+
log13.debug("heartbeat: pong received");
|
|
19443
19524
|
});
|
|
19444
19525
|
}
|
|
19445
19526
|
/** 启动心跳保活 — 定时发送 WebSocket Ping 帧 */
|
|
@@ -19460,7 +19541,7 @@ var ConnectionManager = class {
|
|
|
19460
19541
|
}
|
|
19461
19542
|
this.pongReceived = false;
|
|
19462
19543
|
this.client.ping();
|
|
19463
|
-
log13.
|
|
19544
|
+
log13.debug("heartbeat: ping sent");
|
|
19464
19545
|
this.clearPongTimeout();
|
|
19465
19546
|
this.pongTimeoutTimer = setTimeout(() => {
|
|
19466
19547
|
if (!this.pongReceived && this.running) {
|
|
@@ -19993,6 +20074,13 @@ var QUANTUM_IM_CONFIG_JSON_SCHEMA = {
|
|
|
19993
20074
|
description: "\u9009\u62E9\u5BF9\u63A5\u7684\u670D\u52A1\u5668\u73AF\u5883\u3002staging = \u8054\u8C03\u73AF\u5883\uFF0Cproduction = \u7EBF\u4E0A\u73AF\u5883",
|
|
19994
20075
|
enum: ["staging", "production"],
|
|
19995
20076
|
default: "production"
|
|
20077
|
+
},
|
|
20078
|
+
encryptionMode: {
|
|
20079
|
+
type: "string",
|
|
20080
|
+
title: "\u6D88\u606F\u52A0\u5BC6\u6A21\u5F0F",
|
|
20081
|
+
description: "quantum_only = \u4EC5\u91CF\u5B50\u52A0\u5BC6\u6D88\u606F\uFF08\u6240\u6709\u6D88\u606F\u5FC5\u987B\u52A0\u5BC6\uFF09\uFF1Bquantum_and_plain = \u540C\u65F6\u652F\u6301\u52A0\u5BC6\u548C\u666E\u901A\u6D88\u606F",
|
|
20082
|
+
enum: ["quantum_only", "quantum_and_plain"],
|
|
20083
|
+
default: "quantum_and_plain"
|
|
19996
20084
|
}
|
|
19997
20085
|
}
|
|
19998
20086
|
};
|
|
@@ -20062,7 +20150,8 @@ async function startPlugin(accountConfig, internalOverrides) {
|
|
|
20062
20150
|
crypto: cryptoEngine,
|
|
20063
20151
|
tokenFn: () => tokenManager.getValidToken(),
|
|
20064
20152
|
messageServiceBaseUrl: config2.message.messageServiceBaseUrl,
|
|
20065
|
-
quantumAccount: accountConfig.quantumAccount
|
|
20153
|
+
quantumAccount: accountConfig.quantumAccount,
|
|
20154
|
+
encryptionMode: accountConfig.encryptionMode
|
|
20066
20155
|
});
|
|
20067
20156
|
const connectionManager = new ConnectionManager(wsClient, {
|
|
20068
20157
|
heartbeatIntervalMs: config2.transport.heartbeatIntervalMs,
|
|
@@ -20382,8 +20471,10 @@ function createQuantumImDeliverFn(deps) {
|
|
|
20382
20471
|
maxFileSizeMb,
|
|
20383
20472
|
chunkSizeMb,
|
|
20384
20473
|
timeoutMs,
|
|
20385
|
-
isEncrypted
|
|
20474
|
+
isEncrypted,
|
|
20475
|
+
encryptionMode
|
|
20386
20476
|
} = deps;
|
|
20477
|
+
const shouldSkipEncrypt = encryptionMode === "quantum_only" ? false : isEncrypted === false;
|
|
20387
20478
|
const deliver = async (payload) => {
|
|
20388
20479
|
const mediaUrls = [];
|
|
20389
20480
|
if (payload.mediaUrls?.length) {
|
|
@@ -20401,7 +20492,7 @@ function createQuantumImDeliverFn(deps) {
|
|
|
20401
20492
|
msgType: "markdown",
|
|
20402
20493
|
content: JSON.stringify({ content: payload.text }),
|
|
20403
20494
|
replyToMessageId,
|
|
20404
|
-
skipEncrypt:
|
|
20495
|
+
skipEncrypt: shouldSkipEncrypt
|
|
20405
20496
|
});
|
|
20406
20497
|
log23.info("\u{1F4E4} AI \u6587\u672C\u56DE\u590D\u5DF2\u53D1\u9001", {
|
|
20407
20498
|
chatId,
|
|
@@ -20424,7 +20515,7 @@ function createQuantumImDeliverFn(deps) {
|
|
|
20424
20515
|
maxFileSizeMb,
|
|
20425
20516
|
chunkSizeMb,
|
|
20426
20517
|
timeoutMs,
|
|
20427
|
-
skipEncrypt:
|
|
20518
|
+
skipEncrypt: shouldSkipEncrypt
|
|
20428
20519
|
});
|
|
20429
20520
|
if (result.warning) {
|
|
20430
20521
|
log23.warn("\u{1F4E4} \u5A92\u4F53\u53D1\u9001\u964D\u7EA7", { chatId, mediaUrl, warning: result.warning });
|
|
@@ -20443,7 +20534,7 @@ function createQuantumImDeliverFn(deps) {
|
|
|
20443
20534
|
msgType: "markdown",
|
|
20444
20535
|
content: JSON.stringify({ content: `\u{1F4CE} ${mediaUrl}` }),
|
|
20445
20536
|
replyToMessageId,
|
|
20446
|
-
skipEncrypt:
|
|
20537
|
+
skipEncrypt: shouldSkipEncrypt
|
|
20447
20538
|
});
|
|
20448
20539
|
}
|
|
20449
20540
|
}
|
|
@@ -20481,12 +20572,13 @@ var InboundPipeline = class {
|
|
|
20481
20572
|
});
|
|
20482
20573
|
return;
|
|
20483
20574
|
}
|
|
20575
|
+
const feedbackSkipEncrypt = this.deps.pluginConfig.credentials.encryptionMode === "quantum_only" ? false : true;
|
|
20484
20576
|
this.deps.messagePipe.sendMessage({
|
|
20485
20577
|
chatId: msg.chatId,
|
|
20486
20578
|
senderId: msg.senderId,
|
|
20487
20579
|
msgType: "text",
|
|
20488
20580
|
content: JSON.stringify({ content: "\u4EFB\u52A1\u5DF2\u6536\u5230\u{1F44C}\uFF0C\u6B63\u5728\u5904\u7406\u4E2D..." }),
|
|
20489
|
-
skipEncrypt:
|
|
20581
|
+
skipEncrypt: feedbackSkipEncrypt
|
|
20490
20582
|
}).catch((err) => {
|
|
20491
20583
|
log24.warn("\u26A0\uFE0F \u5373\u65F6\u53CD\u9988\u53D1\u9001\u5931\u8D25", { error: err.message });
|
|
20492
20584
|
});
|
|
@@ -20514,7 +20606,8 @@ var InboundPipeline = class {
|
|
|
20514
20606
|
allowPrivateNetwork: this.deps.pluginConfig.file.allowPrivateNetwork,
|
|
20515
20607
|
maxFileSizeMb: this.deps.pluginConfig.file.maxFileSizeMb,
|
|
20516
20608
|
timeoutMs: this.deps.pluginConfig.file.fetchTimeoutMs,
|
|
20517
|
-
isEncrypted: msg.isEncrypted
|
|
20609
|
+
isEncrypted: msg.isEncrypted,
|
|
20610
|
+
encryptionMode: this.deps.pluginConfig.credentials.encryptionMode
|
|
20518
20611
|
});
|
|
20519
20612
|
const { dispatcher, replyOptions } = core.channel.reply.createReplyDispatcherWithTyping({
|
|
20520
20613
|
deliver
|
|
@@ -20647,33 +20740,39 @@ var quantumImOnboarding = {
|
|
|
20647
20740
|
].join("\n"),
|
|
20648
20741
|
"\u91CF\u5B50\u5BC6\u4FE1 \u2014 \u51ED\u636E\u914D\u7F6E"
|
|
20649
20742
|
);
|
|
20650
|
-
const
|
|
20651
|
-
message: "\
|
|
20652
|
-
initialValue: existing.appId ?? void 0,
|
|
20653
|
-
validate: required2
|
|
20654
|
-
});
|
|
20655
|
-
const appSecret = await prompter.text({
|
|
20656
|
-
message: "\u5E94\u7528\u5BC6\u94A5 (appSecret)",
|
|
20657
|
-
initialValue: existing.appSecret ?? void 0,
|
|
20658
|
-
validate: required2
|
|
20659
|
-
});
|
|
20660
|
-
const encryptionMode = await prompter.select({
|
|
20661
|
-
message: "\u6D88\u606F\u4F20\u8F93\u6A21\u5F0F",
|
|
20743
|
+
const encryptionModeChoice = await prompter.select({
|
|
20744
|
+
message: "\u8BF7\u9009\u62E9\u6D88\u606F\u4EA4\u4E92\u65B9\u5F0F",
|
|
20662
20745
|
options: [
|
|
20663
|
-
{ value: "
|
|
20664
|
-
{ value: "
|
|
20746
|
+
{ value: "quantum_only", label: "\u4EC5\u91CF\u5B50\u52A0\u5BC6\u6D88\u606F" },
|
|
20747
|
+
{ value: "quantum_and_plain", label: "\u91CF\u5B50\u52A0\u5BC6+\u666E\u901A\u6D88\u606F\uFF08\u82E5\u91CF\u5B50\u52A0\u5BC6\u7981\u7528\uFF0C\u5219\u7559\u7A7A\uFF09" }
|
|
20665
20748
|
],
|
|
20666
|
-
initialValue: existing.
|
|
20749
|
+
initialValue: existing.encryptionMode ?? "quantum_and_plain"
|
|
20667
20750
|
});
|
|
20668
20751
|
let quantumAccount = "";
|
|
20669
|
-
if (
|
|
20752
|
+
if (encryptionModeChoice === "quantum_only") {
|
|
20670
20753
|
quantumAccount = await prompter.text({
|
|
20671
20754
|
message: "\u91CF\u5B50\u8D26\u6237\u6807\u8BC6 (quantumAccount)",
|
|
20672
20755
|
placeholder: "\u5982: 17100001111",
|
|
20673
20756
|
initialValue: existing.quantumAccount ?? void 0,
|
|
20674
20757
|
validate: required2
|
|
20675
20758
|
});
|
|
20759
|
+
} else {
|
|
20760
|
+
quantumAccount = await prompter.text({
|
|
20761
|
+
message: "\u91CF\u5B50\u8D26\u6237\u6807\u8BC6\uFF08\u53EF\u9009\uFF0C\u4E0D\u586B\u5219\u4EC5\u652F\u6301\u660E\u6587\uFF09",
|
|
20762
|
+
placeholder: "\u5982: 17100001111",
|
|
20763
|
+
initialValue: existing.quantumAccount ?? void 0
|
|
20764
|
+
});
|
|
20676
20765
|
}
|
|
20766
|
+
const appId = await prompter.text({
|
|
20767
|
+
message: "\u5E94\u7528 ID (appId)",
|
|
20768
|
+
initialValue: existing.appId ?? void 0,
|
|
20769
|
+
validate: required2
|
|
20770
|
+
});
|
|
20771
|
+
const appSecret = await prompter.text({
|
|
20772
|
+
message: "\u5E94\u7528\u5BC6\u94A5 (appSecret)",
|
|
20773
|
+
initialValue: existing.appSecret ?? void 0,
|
|
20774
|
+
validate: required2
|
|
20775
|
+
});
|
|
20677
20776
|
const channels = cfg.channels ?? {};
|
|
20678
20777
|
const channelCfg = channels[CHANNEL_ID] ?? {};
|
|
20679
20778
|
const accounts = channelCfg.accounts ?? {};
|
|
@@ -20689,10 +20788,8 @@ var quantumImOnboarding = {
|
|
|
20689
20788
|
default: {
|
|
20690
20789
|
appId: appId.trim(),
|
|
20691
20790
|
appSecret: appSecret.trim(),
|
|
20692
|
-
|
|
20791
|
+
encryptionMode: encryptionModeChoice,
|
|
20693
20792
|
...quantumAccount.trim() ? { quantumAccount: quantumAccount.trim() } : {}
|
|
20694
|
-
// quantumAppId: quantumAppId.trim(),
|
|
20695
|
-
// quantumAppSecret: quantumAppSecret.trim(),
|
|
20696
20793
|
}
|
|
20697
20794
|
}
|
|
20698
20795
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -33,6 +33,13 @@
|
|
|
33
33
|
"description": "选择对接的服务器环境。staging = 联调环境,production = 线上环境",
|
|
34
34
|
"enum": ["staging", "production"],
|
|
35
35
|
"default": "production"
|
|
36
|
+
},
|
|
37
|
+
"encryptionMode": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"title": "消息加密模式",
|
|
40
|
+
"description": "quantum_only = 仅量子加密消息(所有消息必须加密);quantum_and_plain = 同时支持加密和普通消息",
|
|
41
|
+
"enum": ["quantum_only", "quantum_and_plain"],
|
|
42
|
+
"default": "quantum_and_plain"
|
|
36
43
|
}
|
|
37
44
|
}
|
|
38
45
|
}
|