@invago/mixin 1.0.16 → 1.0.17
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/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/inbound-handler.ts +112 -96
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/inbound-handler.ts
CHANGED
|
@@ -29,8 +29,9 @@ export interface MixinInboundMessage {
|
|
|
29
29
|
publicKey?: string;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const processedMessages = new Set<string>();
|
|
33
|
-
const
|
|
32
|
+
const processedMessages = new Set<string>();
|
|
33
|
+
const processingMessages = new Set<string>();
|
|
34
|
+
const MAX_DEDUP_SIZE = 2000;
|
|
34
35
|
const unauthNotifiedUsers = new Map<string, number>();
|
|
35
36
|
const unauthNotifiedGroups = new Map<string, number>();
|
|
36
37
|
const loggedAllowFromAccounts = new Set<string>();
|
|
@@ -161,19 +162,28 @@ function evaluateMixinSenderGroupAccess(params: {
|
|
|
161
162
|
};
|
|
162
163
|
}
|
|
163
164
|
|
|
164
|
-
function
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
165
|
+
function markProcessing(messageId: string): boolean {
|
|
166
|
+
if (processedMessages.has(messageId) || processingMessages.has(messageId)) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
processingMessages.add(messageId);
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function markProcessed(messageId: string): void {
|
|
174
|
+
processingMessages.delete(messageId);
|
|
175
|
+
if (processedMessages.size >= MAX_DEDUP_SIZE) {
|
|
176
|
+
const first = processedMessages.values().next().value;
|
|
177
|
+
if (first) {
|
|
178
|
+
processedMessages.delete(first);
|
|
173
179
|
}
|
|
174
180
|
}
|
|
175
|
-
processedMessages.add(messageId);
|
|
176
|
-
}
|
|
181
|
+
processedMessages.add(messageId);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function clearProcessing(messageId: string): void {
|
|
185
|
+
processingMessages.delete(messageId);
|
|
186
|
+
}
|
|
177
187
|
|
|
178
188
|
function pruneUnauthNotifiedUsers(now: number): void {
|
|
179
189
|
for (const [userId, lastNotified] of unauthNotifiedUsers) {
|
|
@@ -980,76 +990,77 @@ function evaluateMixinGroupAccess(params: {
|
|
|
980
990
|
};
|
|
981
991
|
}
|
|
982
992
|
|
|
983
|
-
export async function handleMixinMessage(params: {
|
|
993
|
+
export async function handleMixinMessage(params: {
|
|
984
994
|
cfg: OpenClawConfig;
|
|
985
995
|
accountId: string;
|
|
986
996
|
msg: MixinInboundMessage;
|
|
987
997
|
isDirect: boolean;
|
|
988
998
|
log: { info: (m: string) => void; warn: (m: string) => void; error: (m: string, e?: unknown) => void };
|
|
989
999
|
}): Promise<void> {
|
|
990
|
-
const { cfg, accountId, msg, isDirect, log } = params;
|
|
991
|
-
const rt = getMixinRuntime();
|
|
992
|
-
|
|
993
|
-
if (isProcessed(msg.messageId)) {
|
|
994
|
-
return;
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
const config = getAccountConfig(cfg, accountId);
|
|
998
|
-
|
|
999
|
-
if (msg.category === "ENCRYPTED_TEXT" || msg.category === "ENCRYPTED_POST") {
|
|
1000
|
-
log.info(`[mixin] decrypting encrypted message ${msg.messageId}, category=${msg.category}`);
|
|
1001
|
-
try {
|
|
1002
|
-
const decrypted = decryptMixinMessage(
|
|
1003
|
-
msg.data,
|
|
1004
|
-
config.sessionPrivateKey!,
|
|
1005
|
-
config.sessionId!,
|
|
1006
|
-
);
|
|
1007
|
-
if (!decrypted) {
|
|
1008
|
-
log.error(`[mixin] decryption failed for ${msg.messageId}`);
|
|
1009
|
-
markProcessed(msg.messageId);
|
|
1010
|
-
return;
|
|
1011
|
-
}
|
|
1012
|
-
log.info(`[mixin] decryption successful: messageId=${msg.messageId}, length=${decrypted.length}`);
|
|
1013
|
-
msg.data = Buffer.from(decrypted).toString("base64");
|
|
1014
|
-
msg.category = "PLAIN_TEXT";
|
|
1015
|
-
} catch (err) {
|
|
1016
|
-
log.error(`[mixin] decryption exception for ${msg.messageId}`, err);
|
|
1017
|
-
markProcessed(msg.messageId);
|
|
1018
|
-
return;
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
let isTextMessage = msg.category.startsWith("PLAIN_TEXT") || msg.category.startsWith("PLAIN_POST");
|
|
1023
|
-
const isAttachmentMessage = msg.category === "PLAIN_DATA" || msg.category === "PLAIN_AUDIO";
|
|
1000
|
+
const { cfg, accountId, msg, isDirect, log } = params;
|
|
1001
|
+
const rt = getMixinRuntime();
|
|
1024
1002
|
|
|
1025
|
-
if (!
|
|
1026
|
-
const fallbackText = tryDecodeFallbackText(msg.data);
|
|
1027
|
-
if (fallbackText) {
|
|
1028
|
-
log.warn(
|
|
1029
|
-
`[mixin] treating unexpected category as text: messageId=${msg.messageId}, category=${msg.category}, fallbackLength=${fallbackText.length}`,
|
|
1030
|
-
);
|
|
1031
|
-
msg.category = "PLAIN_TEXT";
|
|
1032
|
-
msg.data = Buffer.from(fallbackText).toString("base64");
|
|
1033
|
-
isTextMessage = true;
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
if (!isTextMessage && !isAttachmentMessage) {
|
|
1038
|
-
log.info(
|
|
1039
|
-
`[mixin] skip non-text message: messageId=${msg.messageId}, category=${msg.category}, quoteMessageId=${msg.quoteMessageId ?? "none"}`,
|
|
1040
|
-
);
|
|
1003
|
+
if (!markProcessing(msg.messageId)) {
|
|
1041
1004
|
return;
|
|
1042
1005
|
}
|
|
1043
1006
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1007
|
+
try {
|
|
1008
|
+
const config = getAccountConfig(cfg, accountId);
|
|
1009
|
+
|
|
1010
|
+
if (msg.category === "ENCRYPTED_TEXT" || msg.category === "ENCRYPTED_POST") {
|
|
1011
|
+
log.info(`[mixin] decrypting encrypted message ${msg.messageId}, category=${msg.category}`);
|
|
1012
|
+
try {
|
|
1013
|
+
const decrypted = decryptMixinMessage(
|
|
1014
|
+
msg.data,
|
|
1015
|
+
config.sessionPrivateKey!,
|
|
1016
|
+
config.sessionId!,
|
|
1017
|
+
);
|
|
1018
|
+
if (!decrypted) {
|
|
1019
|
+
log.error(`[mixin] decryption failed for ${msg.messageId}`);
|
|
1020
|
+
markProcessed(msg.messageId);
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
log.info(`[mixin] decryption successful: messageId=${msg.messageId}, length=${decrypted.length}`);
|
|
1024
|
+
msg.data = Buffer.from(decrypted).toString("base64");
|
|
1025
|
+
msg.category = "PLAIN_TEXT";
|
|
1026
|
+
} catch (err) {
|
|
1027
|
+
log.error(`[mixin] decryption exception for ${msg.messageId}`, err);
|
|
1028
|
+
markProcessed(msg.messageId);
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
let isTextMessage = msg.category.startsWith("PLAIN_TEXT") || msg.category.startsWith("PLAIN_POST");
|
|
1034
|
+
const isAttachmentMessage = msg.category === "PLAIN_DATA" || msg.category === "PLAIN_AUDIO";
|
|
1035
|
+
|
|
1036
|
+
if (!isTextMessage && !isAttachmentMessage) {
|
|
1037
|
+
const fallbackText = tryDecodeFallbackText(msg.data);
|
|
1038
|
+
if (fallbackText) {
|
|
1039
|
+
log.warn(
|
|
1040
|
+
`[mixin] treating unexpected category as text: messageId=${msg.messageId}, category=${msg.category}, fallbackLength=${fallbackText.length}`,
|
|
1041
|
+
);
|
|
1042
|
+
msg.category = "PLAIN_TEXT";
|
|
1043
|
+
msg.data = Buffer.from(fallbackText).toString("base64");
|
|
1044
|
+
isTextMessage = true;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
if (!isTextMessage && !isAttachmentMessage) {
|
|
1049
|
+
log.info(
|
|
1050
|
+
`[mixin] skip non-text message: messageId=${msg.messageId}, category=${msg.category}, quoteMessageId=${msg.quoteMessageId ?? "none"}`,
|
|
1051
|
+
);
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
const decodedBody = decodeContent(msg.category, msg.data);
|
|
1056
|
+
let text = decodedBody.trim();
|
|
1057
|
+
let mediaPayload: AgentMediaPayload | undefined;
|
|
1058
|
+
if (isAttachmentMessage) {
|
|
1059
|
+
const resolved = await resolveInboundAttachment({ rt, config, msg, log });
|
|
1060
|
+
text = resolved.text.trim();
|
|
1061
|
+
mediaPayload = resolved.mediaPayload;
|
|
1062
|
+
}
|
|
1063
|
+
log.info(`[mixin] decoded text: messageId=${msg.messageId}, category=${msg.category}, length=${text.length}`);
|
|
1053
1064
|
|
|
1054
1065
|
const botIdentity = await resolveBotIdentity({
|
|
1055
1066
|
accountId,
|
|
@@ -1397,26 +1408,31 @@ export async function handleMixinMessage(params: {
|
|
|
1397
1408
|
|
|
1398
1409
|
log.info(`[mixin] dispatching ${msg.messageId} from ${msg.userId}`);
|
|
1399
1410
|
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
}
|
|
1411
|
+
await rt.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
1412
|
+
ctx,
|
|
1413
|
+
cfg,
|
|
1414
|
+
dispatcherOptions: {
|
|
1415
|
+
deliver: async (payload) => {
|
|
1416
|
+
const replyText = payload.text ?? "";
|
|
1417
|
+
if (!replyText) {
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
const recipientId = isDirect ? msg.userId : undefined;
|
|
1421
|
+
await deliverMixinReply({
|
|
1422
|
+
cfg,
|
|
1423
|
+
accountId,
|
|
1424
|
+
conversationId: msg.conversationId,
|
|
1425
|
+
recipientId,
|
|
1426
|
+
creatorId: msg.userId,
|
|
1427
|
+
text: replyText,
|
|
1428
|
+
log,
|
|
1429
|
+
});
|
|
1430
|
+
},
|
|
1431
|
+
},
|
|
1432
|
+
});
|
|
1433
|
+
} finally {
|
|
1434
|
+
if (!processedMessages.has(msg.messageId)) {
|
|
1435
|
+
clearProcessing(msg.messageId);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|