@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.
@@ -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 storeAllowFrom = await core.channel.pairing
504
- .readAllowFromStore("bluebubbles")
505
- .catch(() => []);
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: account.config.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.reason === "groupPolicy=disabled") {
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.reason === "groupPolicy=allowlist (empty allowlist)") {
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.reason === "groupPolicy=allowlist (not allowlisted)") {
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.reason === "dmPolicy=disabled") {
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 core.channel.pairing.upsertPairingRequest({
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
- effectiveAllowFrom.length > 0
679
+ commandDmAllowFrom.length > 0
667
680
  ? isAllowedBlueBubblesSender({
668
- allowFrom: effectiveAllowFrom,
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: effectiveAllowFrom.length > 0, allowed: ownerAllowedForCommands },
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 = isGroup ? commandGate.commandAuthorized : dmAuthorized;
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?.trim();
724
- const password = account.config.password?.trim();
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: rawBody,
1097
- BodyForCommands: rawBody,
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 core.channel.pairing
1386
- .readAllowFromStore("bluebubbles")
1387
- .catch(() => []);
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,