@invago/mixin 1.0.16 → 1.0.18
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 +10 -9
- package/README.zh-CN.md +11 -10
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/channel.ts +68 -16
- package/src/inbound-handler.ts +513 -187
- package/src/message-dedup.ts +357 -0
package/README.md
CHANGED
|
@@ -10,11 +10,12 @@ Connect [Mixin Messenger](https://mixin.one/messenger) to [OpenClaw](https://ope
|
|
|
10
10
|
|
|
11
11
|
MixinClaw is an OpenClaw channel plugin. It runs in the same process as the OpenClaw Gateway, receives inbound messages from Mixin Blaze WebSocket, and delivers outbound messages over the Mixin HTTP API.
|
|
12
12
|
|
|
13
|
-
Important:
|
|
14
|
-
|
|
15
|
-
- Install the plugin on the same machine where the OpenClaw Gateway runs.
|
|
16
|
-
- OpenClaw config files use JSON5, so comments and trailing commas are allowed.
|
|
17
|
-
- The proxy configured by this plugin only affects this plugin.
|
|
13
|
+
Important:
|
|
14
|
+
|
|
15
|
+
- Install the plugin on the same machine where the OpenClaw Gateway runs.
|
|
16
|
+
- OpenClaw config files use JSON5, so comments and trailing commas are allowed.
|
|
17
|
+
- The proxy configured by this plugin only affects this plugin.
|
|
18
|
+
- Per confirmed Mixin platform behavior, user messages in group chats are delivered to the bot only when the bot is explicitly mentioned.
|
|
18
19
|
|
|
19
20
|
## Recommended Install
|
|
20
21
|
|
|
@@ -325,11 +326,11 @@ Mixin now supports formal group access controls in addition to direct-message `d
|
|
|
325
326
|
|
|
326
327
|
Important delivery boundary:
|
|
327
328
|
|
|
328
|
-
-
|
|
329
|
-
-
|
|
329
|
+
- Per confirmed Mixin platform behavior, group messages from users are delivered to the bot only when the bot is explicitly mentioned.
|
|
330
|
+
- Use `@<identity_number> your message`, for example `@7000103034 hello`.
|
|
330
331
|
- `requireMentionInGroup: false` only disables this plugin's own post-delivery filtering.
|
|
331
|
-
- It does not
|
|
332
|
-
- If a non-mention
|
|
332
|
+
- It does not cause Mixin to forward non-mention user messages in groups to the bot.
|
|
333
|
+
- If a non-mention user message produces no read receipt and no inbound log, the message was not delivered to the plugin by Mixin in the first place.
|
|
333
334
|
- Group quote/reply interactions are currently not treated as a reliable bot trigger, because Mixin may not deliver those events to the bot over Blaze consistently.
|
|
334
335
|
|
|
335
336
|
Example:
|
package/README.zh-CN.md
CHANGED
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
|
|
11
11
|
MixinClaw 是一个 OpenClaw 频道插件。它运行在 OpenClaw Gateway 同一进程中,使用 Mixin Blaze WebSocket 接收入站消息,并通过 Mixin HTTP API 发送出站消息。
|
|
12
12
|
|
|
13
|
-
重要说明:
|
|
14
|
-
|
|
15
|
-
- 插件需要安装在 OpenClaw Gateway 所在的机器上。
|
|
16
|
-
- OpenClaw 配置文件使用 JSON5,支持注释和尾逗号。
|
|
17
|
-
- 这里配置的代理只作用于这个插件,不影响其他插件。
|
|
13
|
+
重要说明:
|
|
14
|
+
|
|
15
|
+
- 插件需要安装在 OpenClaw Gateway 所在的机器上。
|
|
16
|
+
- OpenClaw 配置文件使用 JSON5,支持注释和尾逗号。
|
|
17
|
+
- 这里配置的代理只作用于这个插件,不影响其他插件。
|
|
18
|
+
- 根据 Mixin 官方确认,群内用户消息只有在显式 `@` 机器人的情况下才会投递给 bot。
|
|
18
19
|
|
|
19
20
|
## 推荐安装方式
|
|
20
21
|
|
|
@@ -317,12 +318,12 @@ OpenClaw 支持通过 `bindings[].match.accountId` 把不同的频道账号直
|
|
|
317
318
|
|
|
318
319
|
关于群消息投递边界:
|
|
319
320
|
|
|
320
|
-
-
|
|
321
|
-
-
|
|
321
|
+
- 根据 Mixin 官方确认,群内用户消息只有在显式 `@bot` 时才会投递给机器人。
|
|
322
|
+
- 推荐写法是 `@<identity_number> + 文本`,例如 `@7000103034 你好`。
|
|
322
323
|
- `requireMentionInGroup: false` 只表示关闭插件自身的群消息二次过滤。
|
|
323
|
-
-
|
|
324
|
-
-
|
|
325
|
-
-
|
|
324
|
+
- 它不会让 Mixin 把未 `@` 的群内用户消息投递给 bot。
|
|
325
|
+
- 如果一条未 `@` 的群内用户消息既没有已读,也没有任何入站日志,那不是插件过滤了它,而是 Mixin 根本没有把这条消息送到插件。
|
|
326
|
+
- 目前群内“引用回复”也不应被当成稳定触发方式,因为 Mixin 不一定会把这类事件稳定投递给 bot。
|
|
326
327
|
|
|
327
328
|
示例:
|
|
328
329
|
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { buildClient, sleep } from "./shared.js";
|
|
|
10
10
|
import { MixinConfigSchema } from "./config-schema.js";
|
|
11
11
|
import { describeAccount, isConfigured, listAccountIds, resolveAccount, resolveDefaultAccountId, resolveMediaMaxMb } from "./config.js";
|
|
12
12
|
import type { MixinAccountConfig } from "./config-schema.js";
|
|
13
|
-
import { handleMixinMessage, type MixinInboundMessage } from "./inbound-handler.js";
|
|
13
|
+
import { handleMixinMessage, handleMixinSystemConversation, type MixinInboundMessage } from "./inbound-handler.js";
|
|
14
14
|
import { getMixpayStatusSnapshot, startMixpayWorker } from "./mixpay-worker.js";
|
|
15
15
|
import { mixinOnboardingAdapter } from "./onboarding.js";
|
|
16
16
|
import { buildMixinOutboundPlanFromReplyPayload, executeMixinOutboundPlan } from "./outbound-plan.js";
|
|
@@ -459,8 +459,50 @@ export const mixinPlugin = {
|
|
|
459
459
|
onSenderReady: (sender) => {
|
|
460
460
|
setMixinBlazeSender(accountId, sender);
|
|
461
461
|
},
|
|
462
|
-
handler: {
|
|
463
|
-
|
|
462
|
+
handler: {
|
|
463
|
+
onConversation: async (rawMsg: any) => {
|
|
464
|
+
if (stopped) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (!rawMsg || !rawMsg.message_id) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const isDirect = await resolveIsDirectMessage({
|
|
472
|
+
config,
|
|
473
|
+
conversationId: rawMsg.conversation_id,
|
|
474
|
+
userId: rawMsg.user_id,
|
|
475
|
+
log,
|
|
476
|
+
});
|
|
477
|
+
const quoteMessageId = extractQuoteMessageId(rawMsg);
|
|
478
|
+
const rawCategory = typeof rawMsg.category === "string" ? rawMsg.category : "SYSTEM_CONVERSATION";
|
|
479
|
+
const rawData = typeof rawMsg.data_base64 === "string"
|
|
480
|
+
? rawMsg.data_base64
|
|
481
|
+
: typeof rawMsg.data === "string"
|
|
482
|
+
? rawMsg.data
|
|
483
|
+
: "";
|
|
484
|
+
|
|
485
|
+
log.info(
|
|
486
|
+
`[mixin] blaze inbound: messageId=${rawMsg.message_id}, conversationId=${rawMsg.conversation_id ?? ""}, userId=${rawMsg.user_id ?? ""}, category=${rawCategory}, isDirect=${isDirect}, quoteMessageId=${quoteMessageId ?? "none"}, dataLength=${rawData.length}`,
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
const msg: MixinInboundMessage = {
|
|
490
|
+
conversationId: rawMsg.conversation_id ?? "",
|
|
491
|
+
userId: rawMsg.user_id ?? "",
|
|
492
|
+
messageId: rawMsg.message_id,
|
|
493
|
+
category: rawCategory,
|
|
494
|
+
data: rawData,
|
|
495
|
+
createdAt: rawMsg.created_at ?? new Date().toISOString(),
|
|
496
|
+
quoteMessageId,
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
try {
|
|
500
|
+
await handleMixinSystemConversation({ cfg, accountId, msg, log });
|
|
501
|
+
} catch (err) {
|
|
502
|
+
log.error(`error handling system conversation ${msg.messageId}`, err);
|
|
503
|
+
}
|
|
504
|
+
},
|
|
505
|
+
onMessage: async (rawMsg: any) => {
|
|
464
506
|
if (stopped) {
|
|
465
507
|
return;
|
|
466
508
|
}
|
|
@@ -471,24 +513,34 @@ export const mixinPlugin = {
|
|
|
471
513
|
return;
|
|
472
514
|
}
|
|
473
515
|
|
|
474
|
-
const isDirect = await resolveIsDirectMessage({
|
|
475
|
-
config,
|
|
476
|
-
conversationId: rawMsg.conversation_id,
|
|
477
|
-
userId: rawMsg.user_id,
|
|
478
|
-
log,
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
516
|
+
const isDirect = await resolveIsDirectMessage({
|
|
517
|
+
config,
|
|
518
|
+
conversationId: rawMsg.conversation_id,
|
|
519
|
+
userId: rawMsg.user_id,
|
|
520
|
+
log,
|
|
521
|
+
});
|
|
522
|
+
const quoteMessageId = extractQuoteMessageId(rawMsg);
|
|
523
|
+
const rawCategory = typeof rawMsg.category === "string" ? rawMsg.category : "PLAIN_TEXT";
|
|
524
|
+
const rawData = typeof rawMsg.data_base64 === "string"
|
|
525
|
+
? rawMsg.data_base64
|
|
526
|
+
: typeof rawMsg.data === "string"
|
|
527
|
+
? rawMsg.data
|
|
528
|
+
: "";
|
|
529
|
+
log.info(
|
|
530
|
+
`[mixin] blaze inbound: messageId=${rawMsg.message_id}, conversationId=${rawMsg.conversation_id ?? ""}, userId=${rawMsg.user_id}, category=${rawCategory}, isDirect=${isDirect}, quoteMessageId=${quoteMessageId ?? "none"}, dataLength=${rawData.length}`,
|
|
531
|
+
);
|
|
532
|
+
log.info(
|
|
533
|
+
`[mixin] inbound route context: messageId=${rawMsg.message_id}, conversationId=${rawMsg.conversation_id ?? ""}, userId=${rawMsg.user_id}, isDirect=${isDirect}`,
|
|
534
|
+
);
|
|
535
|
+
|
|
484
536
|
const msg: MixinInboundMessage = {
|
|
485
537
|
conversationId: rawMsg.conversation_id ?? "",
|
|
486
538
|
userId: rawMsg.user_id,
|
|
487
539
|
messageId: rawMsg.message_id,
|
|
488
|
-
category:
|
|
489
|
-
data:
|
|
540
|
+
category: rawCategory,
|
|
541
|
+
data: rawData,
|
|
490
542
|
createdAt: rawMsg.created_at ?? new Date().toISOString(),
|
|
491
|
-
quoteMessageId
|
|
543
|
+
quoteMessageId,
|
|
492
544
|
};
|
|
493
545
|
|
|
494
546
|
try {
|