From 00e00460c196cce12b0a99355b976baee8e6a095 Mon Sep 17 00:00:00 2001 From: Tyler Yust Date: Sun, 25 Jan 2026 00:19:45 -0800 Subject: [PATCH] fix(bluebubbles): hide internal routing metadata in cross-context markers When sending cross-context messages via BlueBubbles, the origin marker was exposing internal chat_guid routing info like '[from bluebubbles:chat_guid:any;-;+19257864429]'. This adds a formatTargetDisplay() function to the BlueBubbles plugin that: - Extracts phone numbers from chat_guid formats (iMessage;-;+1234567890 -> +1234567890) - Normalizes handles for clean display - Avoids returning raw chat_guid formats containing internal routing metadata Now cross-context markers show clean identifiers like '[from +19257864429]' instead of exposing internal routing details to recipients. --- extensions/bluebubbles/src/channel.ts | 43 +++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/extensions/bluebubbles/src/channel.ts b/extensions/bluebubbles/src/channel.ts index 126a73131..9be055908 100644 --- a/extensions/bluebubbles/src/channel.ts +++ b/extensions/bluebubbles/src/channel.ts @@ -25,9 +25,11 @@ import { resolveBlueBubblesMessageId } from "./monitor.js"; import { probeBlueBubbles, type BlueBubblesProbe } from "./probe.js"; import { sendMessageBlueBubbles } from "./send.js"; import { + extractHandleFromChatGuid, looksLikeBlueBubblesTargetId, normalizeBlueBubblesHandle, normalizeBlueBubblesMessagingTarget, + parseBlueBubblesTarget, } from "./targets.js"; import { bluebubblesMessageActions } from "./actions.js"; import { monitorBlueBubblesProvider, resolveWebhookPathFromConfig } from "./monitor.js"; @@ -148,6 +150,47 @@ export const bluebubblesPlugin: ChannelPlugin = { looksLikeId: looksLikeBlueBubblesTargetId, hint: "", }, + formatTargetDisplay: ({ target, display }) => { + // Helper to extract a clean handle from any BlueBubbles target format + const extractCleanDisplay = (value: string | undefined): string | null => { + const trimmed = value?.trim(); + if (!trimmed) return null; + try { + const parsed = parseBlueBubblesTarget(trimmed); + if (parsed.kind === "chat_guid") { + const handle = extractHandleFromChatGuid(parsed.chatGuid); + if (handle) return handle; + } + if (parsed.kind === "handle") { + return normalizeBlueBubblesHandle(parsed.to); + } + } catch { + // Fall through + } + // Strip common prefixes and try raw extraction + const stripped = trimmed + .replace(/^bluebubbles:/i, "") + .replace(/^chat_guid:/i, "") + .replace(/^chat_id:/i, "") + .replace(/^chat_identifier:/i, ""); + const handle = extractHandleFromChatGuid(stripped); + if (handle) return handle; + // Don't return raw chat_guid formats - they contain internal routing info + if (stripped.includes(";-;") || stripped.includes(";+;")) return null; + return stripped; + }; + + // Try to get a clean display from the display parameter first + const cleanDisplay = extractCleanDisplay(display); + if (cleanDisplay) return cleanDisplay; + + // Fall back to extracting from target + const cleanTarget = extractCleanDisplay(target); + if (cleanTarget) return cleanTarget; + + // Last resort: return display or target as-is + return display?.trim() || target?.trim() || ""; + }, }, setup: { resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),