@openclaw/bluebubbles 2026.2.25 → 2026.3.2
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 +1 -1
- package/index.ts +0 -2
- package/package.json +1 -1
- package/src/account-resolve.ts +19 -2
- package/src/accounts.test.ts +25 -0
- package/src/accounts.ts +18 -5
- package/src/actions.ts +4 -19
- package/src/attachments.test.ts +20 -2
- package/src/attachments.ts +15 -1
- package/src/channel.ts +3 -10
- package/src/config-schema.test.ts +12 -0
- package/src/config-schema.ts +5 -3
- package/src/monitor-debounce.ts +205 -0
- package/src/monitor-processing.ts +43 -22
- package/src/monitor.test.ts +87 -717
- package/src/monitor.ts +157 -364
- package/src/monitor.webhook-auth.test.ts +862 -0
- package/src/monitor.webhook-route.test.ts +44 -0
- package/src/onboarding.secret-input.test.ts +81 -0
- package/src/onboarding.ts +8 -2
- package/src/probe.ts +5 -4
- package/src/secret-input.ts +19 -0
- package/src/send-helpers.ts +34 -22
- package/src/send.test.ts +24 -0
- package/src/send.ts +7 -2
- package/src/targets.ts +2 -5
- package/src/types.ts +2 -0
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
2
2
|
import {
|
|
3
|
+
DM_GROUP_ACCESS_REASON,
|
|
4
|
+
createScopedPairingAccess,
|
|
3
5
|
createReplyPrefixOptions,
|
|
4
6
|
evictOldHistoryKeys,
|
|
5
7
|
logAckFailure,
|
|
6
8
|
logInboundDrop,
|
|
7
9
|
logTypingFailure,
|
|
10
|
+
readStoreAllowFromForDmPolicy,
|
|
8
11
|
recordPendingHistoryEntryIfEnabled,
|
|
9
12
|
resolveAckReaction,
|
|
10
13
|
resolveDmGroupAccessWithLists,
|
|
@@ -40,6 +43,7 @@ import type {
|
|
|
40
43
|
} from "./monitor-shared.js";
|
|
41
44
|
import { isBlueBubblesPrivateApiEnabled } from "./probe.js";
|
|
42
45
|
import { normalizeBlueBubblesReactionInput, sendBlueBubblesReaction } from "./reactions.js";
|
|
46
|
+
import { normalizeSecretInputString } from "./secret-input.js";
|
|
43
47
|
import { resolveChatGuidForTarget, sendMessageBlueBubbles } from "./send.js";
|
|
44
48
|
import { formatBlueBubblesChatTarget, isAllowedBlueBubblesSender } from "./targets.js";
|
|
45
49
|
|
|
@@ -419,6 +423,11 @@ export async function processMessage(
|
|
|
419
423
|
target: WebhookTarget,
|
|
420
424
|
): Promise<void> {
|
|
421
425
|
const { account, config, runtime, core, statusSink } = target;
|
|
426
|
+
const pairing = createScopedPairingAccess({
|
|
427
|
+
core,
|
|
428
|
+
channel: "bluebubbles",
|
|
429
|
+
accountId: account.accountId,
|
|
430
|
+
});
|
|
422
431
|
const privateApiEnabled = isBlueBubblesPrivateApiEnabled(account.accountId);
|
|
423
432
|
|
|
424
433
|
const groupFlag = resolveGroupFlagFromChatGuid(message.chatGuid);
|
|
@@ -500,14 +509,18 @@ export async function processMessage(
|
|
|
500
509
|
|
|
501
510
|
const dmPolicy = account.config.dmPolicy ?? "pairing";
|
|
502
511
|
const groupPolicy = account.config.groupPolicy ?? "allowlist";
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
512
|
+
const configuredAllowFrom = (account.config.allowFrom ?? []).map((entry) => String(entry));
|
|
513
|
+
const storeAllowFrom = await readStoreAllowFromForDmPolicy({
|
|
514
|
+
provider: "bluebubbles",
|
|
515
|
+
accountId: account.accountId,
|
|
516
|
+
dmPolicy,
|
|
517
|
+
readStore: pairing.readStoreForDmPolicy,
|
|
518
|
+
});
|
|
506
519
|
const accessDecision = resolveDmGroupAccessWithLists({
|
|
507
520
|
isGroup,
|
|
508
521
|
dmPolicy,
|
|
509
522
|
groupPolicy,
|
|
510
|
-
allowFrom:
|
|
523
|
+
allowFrom: configuredAllowFrom,
|
|
511
524
|
groupAllowFrom: account.config.groupAllowFrom,
|
|
512
525
|
storeAllowFrom,
|
|
513
526
|
isSenderAllowed: (allowFrom) =>
|
|
@@ -530,7 +543,7 @@ export async function processMessage(
|
|
|
530
543
|
|
|
531
544
|
if (accessDecision.decision !== "allow") {
|
|
532
545
|
if (isGroup) {
|
|
533
|
-
if (accessDecision.
|
|
546
|
+
if (accessDecision.reasonCode === DM_GROUP_ACCESS_REASON.GROUP_POLICY_DISABLED) {
|
|
534
547
|
logVerbose(core, runtime, "Blocked BlueBubbles group message (groupPolicy=disabled)");
|
|
535
548
|
logGroupAllowlistHint({
|
|
536
549
|
runtime,
|
|
@@ -541,7 +554,7 @@ export async function processMessage(
|
|
|
541
554
|
});
|
|
542
555
|
return;
|
|
543
556
|
}
|
|
544
|
-
if (accessDecision.
|
|
557
|
+
if (accessDecision.reasonCode === DM_GROUP_ACCESS_REASON.GROUP_POLICY_EMPTY_ALLOWLIST) {
|
|
545
558
|
logVerbose(core, runtime, "Blocked BlueBubbles group message (no allowlist)");
|
|
546
559
|
logGroupAllowlistHint({
|
|
547
560
|
runtime,
|
|
@@ -552,7 +565,7 @@ export async function processMessage(
|
|
|
552
565
|
});
|
|
553
566
|
return;
|
|
554
567
|
}
|
|
555
|
-
if (accessDecision.
|
|
568
|
+
if (accessDecision.reasonCode === DM_GROUP_ACCESS_REASON.GROUP_POLICY_NOT_ALLOWLISTED) {
|
|
556
569
|
logVerbose(
|
|
557
570
|
core,
|
|
558
571
|
runtime,
|
|
@@ -575,15 +588,14 @@ export async function processMessage(
|
|
|
575
588
|
return;
|
|
576
589
|
}
|
|
577
590
|
|
|
578
|
-
if (accessDecision.
|
|
591
|
+
if (accessDecision.reasonCode === DM_GROUP_ACCESS_REASON.DM_POLICY_DISABLED) {
|
|
579
592
|
logVerbose(core, runtime, `Blocked BlueBubbles DM from ${message.senderId}`);
|
|
580
593
|
logVerbose(core, runtime, `drop: dmPolicy disabled sender=${message.senderId}`);
|
|
581
594
|
return;
|
|
582
595
|
}
|
|
583
596
|
|
|
584
597
|
if (accessDecision.decision === "pairing") {
|
|
585
|
-
const { code, created } = await
|
|
586
|
-
channel: "bluebubbles",
|
|
598
|
+
const { code, created } = await pairing.upsertPairingRequest({
|
|
587
599
|
id: message.senderId,
|
|
588
600
|
meta: { name: message.senderName },
|
|
589
601
|
});
|
|
@@ -662,10 +674,11 @@ export async function processMessage(
|
|
|
662
674
|
// Command gating (parity with iMessage/WhatsApp)
|
|
663
675
|
const useAccessGroups = config.commands?.useAccessGroups !== false;
|
|
664
676
|
const hasControlCmd = core.channel.text.hasControlCommand(messageText, config);
|
|
677
|
+
const commandDmAllowFrom = isGroup ? configuredAllowFrom : effectiveAllowFrom;
|
|
665
678
|
const ownerAllowedForCommands =
|
|
666
|
-
|
|
679
|
+
commandDmAllowFrom.length > 0
|
|
667
680
|
? isAllowedBlueBubblesSender({
|
|
668
|
-
allowFrom:
|
|
681
|
+
allowFrom: commandDmAllowFrom,
|
|
669
682
|
sender: message.senderId,
|
|
670
683
|
chatId: message.chatId ?? undefined,
|
|
671
684
|
chatGuid: message.chatGuid ?? undefined,
|
|
@@ -682,17 +695,16 @@ export async function processMessage(
|
|
|
682
695
|
chatIdentifier: message.chatIdentifier ?? undefined,
|
|
683
696
|
})
|
|
684
697
|
: false;
|
|
685
|
-
const dmAuthorized = dmPolicy === "open" || ownerAllowedForCommands;
|
|
686
698
|
const commandGate = resolveControlCommandGate({
|
|
687
699
|
useAccessGroups,
|
|
688
700
|
authorizers: [
|
|
689
|
-
{ configured:
|
|
701
|
+
{ configured: commandDmAllowFrom.length > 0, allowed: ownerAllowedForCommands },
|
|
690
702
|
{ configured: effectiveGroupAllowFrom.length > 0, allowed: groupAllowedForCommands },
|
|
691
703
|
],
|
|
692
704
|
allowTextCommands: true,
|
|
693
705
|
hasControlCommand: hasControlCmd,
|
|
694
706
|
});
|
|
695
|
-
const commandAuthorized =
|
|
707
|
+
const commandAuthorized = commandGate.commandAuthorized;
|
|
696
708
|
|
|
697
709
|
// Block control commands from unauthorized senders in groups
|
|
698
710
|
if (isGroup && commandGate.shouldBlock) {
|
|
@@ -720,8 +732,8 @@ export async function processMessage(
|
|
|
720
732
|
// surfacing dropped content (allowlist/mention/command gating).
|
|
721
733
|
cacheInboundMessage();
|
|
722
734
|
|
|
723
|
-
const baseUrl = account.config.serverUrl
|
|
724
|
-
const password = account.config.password
|
|
735
|
+
const baseUrl = normalizeSecretInputString(account.config.serverUrl);
|
|
736
|
+
const password = normalizeSecretInputString(account.config.password);
|
|
725
737
|
const maxBytes =
|
|
726
738
|
account.config.mediaMaxMb && account.config.mediaMaxMb > 0
|
|
727
739
|
? account.config.mediaMaxMb * 1024 * 1024
|
|
@@ -1087,14 +1099,15 @@ export async function processMessage(
|
|
|
1087
1099
|
});
|
|
1088
1100
|
}
|
|
1089
1101
|
}
|
|
1102
|
+
const commandBody = messageText.trim();
|
|
1090
1103
|
|
|
1091
1104
|
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
|
1092
1105
|
Body: body,
|
|
1093
1106
|
BodyForAgent: rawBody,
|
|
1094
1107
|
InboundHistory: inboundHistory,
|
|
1095
1108
|
RawBody: rawBody,
|
|
1096
|
-
CommandBody:
|
|
1097
|
-
BodyForCommands:
|
|
1109
|
+
CommandBody: commandBody,
|
|
1110
|
+
BodyForCommands: commandBody,
|
|
1098
1111
|
MediaUrl: mediaUrls[0],
|
|
1099
1112
|
MediaUrls: mediaUrls.length > 0 ? mediaUrls : undefined,
|
|
1100
1113
|
MediaPath: mediaPaths[0],
|
|
@@ -1376,15 +1389,23 @@ export async function processReaction(
|
|
|
1376
1389
|
target: WebhookTarget,
|
|
1377
1390
|
): Promise<void> {
|
|
1378
1391
|
const { account, config, runtime, core } = target;
|
|
1392
|
+
const pairing = createScopedPairingAccess({
|
|
1393
|
+
core,
|
|
1394
|
+
channel: "bluebubbles",
|
|
1395
|
+
accountId: account.accountId,
|
|
1396
|
+
});
|
|
1379
1397
|
if (reaction.fromMe) {
|
|
1380
1398
|
return;
|
|
1381
1399
|
}
|
|
1382
1400
|
|
|
1383
1401
|
const dmPolicy = account.config.dmPolicy ?? "pairing";
|
|
1384
1402
|
const groupPolicy = account.config.groupPolicy ?? "allowlist";
|
|
1385
|
-
const storeAllowFrom = await
|
|
1386
|
-
|
|
1387
|
-
.
|
|
1403
|
+
const storeAllowFrom = await readStoreAllowFromForDmPolicy({
|
|
1404
|
+
provider: "bluebubbles",
|
|
1405
|
+
accountId: account.accountId,
|
|
1406
|
+
dmPolicy,
|
|
1407
|
+
readStore: pairing.readStoreForDmPolicy,
|
|
1408
|
+
});
|
|
1388
1409
|
const accessDecision = resolveDmGroupAccessWithLists({
|
|
1389
1410
|
isGroup: reaction.isGroup,
|
|
1390
1411
|
dmPolicy,
|