refactor(bluebubbles): share send helpers

This commit is contained in:
Peter Steinberger
2026-02-15 01:10:45 +00:00
parent 5e205030ed
commit 811e0c5797
3 changed files with 61 additions and 108 deletions

View File

@@ -3,8 +3,8 @@ import crypto from "node:crypto";
import path from "node:path";
import { resolveBlueBubblesAccount } from "./accounts.js";
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
import { extractBlueBubblesMessageId, resolveBlueBubblesSendTarget } from "./send-helpers.js";
import { resolveChatGuidForTarget } from "./send.js";
import { parseBlueBubblesTarget, normalizeBlueBubblesHandle } from "./targets.js";
import {
blueBubblesFetchWithTimeout,
buildBlueBubblesApiUrl,
@@ -102,52 +102,6 @@ export type SendBlueBubblesAttachmentResult = {
messageId: string;
};
function resolveSendTarget(raw: string): BlueBubblesSendTarget {
const parsed = parseBlueBubblesTarget(raw);
if (parsed.kind === "handle") {
return {
kind: "handle",
address: normalizeBlueBubblesHandle(parsed.to),
service: parsed.service,
};
}
if (parsed.kind === "chat_id") {
return { kind: "chat_id", chatId: parsed.chatId };
}
if (parsed.kind === "chat_guid") {
return { kind: "chat_guid", chatGuid: parsed.chatGuid };
}
return { kind: "chat_identifier", chatIdentifier: parsed.chatIdentifier };
}
function extractMessageId(payload: unknown): string {
if (!payload || typeof payload !== "object") {
return "unknown";
}
const record = payload as Record<string, unknown>;
const data =
record.data && typeof record.data === "object"
? (record.data as Record<string, unknown>)
: null;
const candidates = [
record.messageId,
record.guid,
record.id,
data?.messageId,
data?.guid,
data?.id,
];
for (const candidate of candidates) {
if (typeof candidate === "string" && candidate.trim()) {
return candidate.trim();
}
if (typeof candidate === "number" && Number.isFinite(candidate)) {
return String(candidate);
}
}
return "unknown";
}
/**
* Send an attachment via BlueBubbles API.
* Supports sending media files (images, videos, audio, documents) to a chat.
@@ -193,7 +147,7 @@ export async function sendBlueBubblesAttachment(params: {
}
}
const target = resolveSendTarget(to);
const target = resolveBlueBubblesSendTarget(to);
const chatGuid = await resolveChatGuidForTarget({
baseUrl,
password,
@@ -299,7 +253,7 @@ export async function sendBlueBubblesAttachment(params: {
}
try {
const parsed = JSON.parse(responseBody) as unknown;
return { messageId: extractMessageId(parsed) };
return { messageId: extractBlueBubblesMessageId(parsed) };
} catch {
return { messageId: "ok" };
}

View File

@@ -0,0 +1,53 @@
import type { BlueBubblesSendTarget } from "./types.js";
import { normalizeBlueBubblesHandle, parseBlueBubblesTarget } from "./targets.js";
export function resolveBlueBubblesSendTarget(raw: string): BlueBubblesSendTarget {
const parsed = parseBlueBubblesTarget(raw);
if (parsed.kind === "handle") {
return {
kind: "handle",
address: normalizeBlueBubblesHandle(parsed.to),
service: parsed.service,
};
}
if (parsed.kind === "chat_id") {
return { kind: "chat_id", chatId: parsed.chatId };
}
if (parsed.kind === "chat_guid") {
return { kind: "chat_guid", chatGuid: parsed.chatGuid };
}
return { kind: "chat_identifier", chatIdentifier: parsed.chatIdentifier };
}
export function extractBlueBubblesMessageId(payload: unknown): string {
if (!payload || typeof payload !== "object") {
return "unknown";
}
const record = payload as Record<string, unknown>;
const data =
record.data && typeof record.data === "object"
? (record.data as Record<string, unknown>)
: null;
const candidates = [
record.messageId,
record.messageGuid,
record.message_guid,
record.guid,
record.id,
data?.messageId,
data?.messageGuid,
data?.message_guid,
data?.message_id,
data?.guid,
data?.id,
];
for (const candidate of candidates) {
if (typeof candidate === "string" && candidate.trim()) {
return candidate.trim();
}
if (typeof candidate === "number" && Number.isFinite(candidate)) {
return String(candidate);
}
}
return "unknown";
}

View File

@@ -3,11 +3,8 @@ import crypto from "node:crypto";
import { stripMarkdown } from "openclaw/plugin-sdk";
import { resolveBlueBubblesAccount } from "./accounts.js";
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
import {
extractHandleFromChatGuid,
normalizeBlueBubblesHandle,
parseBlueBubblesTarget,
} from "./targets.js";
import { extractBlueBubblesMessageId, resolveBlueBubblesSendTarget } from "./send-helpers.js";
import { extractHandleFromChatGuid, normalizeBlueBubblesHandle } from "./targets.js";
import {
blueBubblesFetchWithTimeout,
buildBlueBubblesApiUrl,
@@ -74,57 +71,6 @@ function resolveEffectId(raw?: string): string | undefined {
return raw;
}
function resolveSendTarget(raw: string): BlueBubblesSendTarget {
const parsed = parseBlueBubblesTarget(raw);
if (parsed.kind === "handle") {
return {
kind: "handle",
address: normalizeBlueBubblesHandle(parsed.to),
service: parsed.service,
};
}
if (parsed.kind === "chat_id") {
return { kind: "chat_id", chatId: parsed.chatId };
}
if (parsed.kind === "chat_guid") {
return { kind: "chat_guid", chatGuid: parsed.chatGuid };
}
return { kind: "chat_identifier", chatIdentifier: parsed.chatIdentifier };
}
function extractMessageId(payload: unknown): string {
if (!payload || typeof payload !== "object") {
return "unknown";
}
const record = payload as Record<string, unknown>;
const data =
record.data && typeof record.data === "object"
? (record.data as Record<string, unknown>)
: null;
const candidates = [
record.messageId,
record.messageGuid,
record.message_guid,
record.guid,
record.id,
data?.messageId,
data?.messageGuid,
data?.message_guid,
data?.message_id,
data?.guid,
data?.id,
];
for (const candidate of candidates) {
if (typeof candidate === "string" && candidate.trim()) {
return candidate.trim();
}
if (typeof candidate === "number" && Number.isFinite(candidate)) {
return String(candidate);
}
}
return "unknown";
}
type BlueBubblesChatRecord = Record<string, unknown>;
function extractChatGuid(chat: BlueBubblesChatRecord): string | null {
@@ -365,7 +311,7 @@ async function createNewChatWithMessage(params: {
}
try {
const parsed = JSON.parse(body) as unknown;
return { messageId: extractMessageId(parsed) };
return { messageId: extractBlueBubblesMessageId(parsed) };
} catch {
return { messageId: "ok" };
}
@@ -400,7 +346,7 @@ export async function sendMessageBlueBubbles(
}
const privateApiStatus = getCachedBlueBubblesPrivateApiStatus(account.accountId);
const target = resolveSendTarget(to);
const target = resolveBlueBubblesSendTarget(to);
const chatGuid = await resolveChatGuidForTarget({
baseUrl,
password,
@@ -477,7 +423,7 @@ export async function sendMessageBlueBubbles(
}
try {
const parsed = JSON.parse(body) as unknown;
return { messageId: extractMessageId(parsed) };
return { messageId: extractBlueBubblesMessageId(parsed) };
} catch {
return { messageId: "ok" };
}