mirror of
https://github.com/clawdbot/clawdbot.git
synced 2026-02-01 03:47:45 +01:00
feat: enhance BlueBubbles media and message handling by adding reply context support and improving outbound message ID tracking
This commit is contained in:
committed by
Peter Steinberger
parent
c331bdc27d
commit
d029ceab1c
@@ -170,6 +170,8 @@ export async function sendBlueBubblesAttachment(params: {
|
||||
// Add optional caption
|
||||
if (caption) {
|
||||
addField("message", caption);
|
||||
addField("text", caption);
|
||||
addField("caption", caption);
|
||||
}
|
||||
|
||||
// Close the multipart body
|
||||
|
||||
@@ -229,7 +229,7 @@ export const bluebubblesPlugin: ChannelPlugin<ResolvedBlueBubblesAccount> = {
|
||||
return { channel: "bluebubbles", ...result };
|
||||
},
|
||||
sendMedia: async (ctx) => {
|
||||
const { cfg, to, text, mediaUrl, accountId } = ctx;
|
||||
const { cfg, to, text, mediaUrl, accountId, replyToId } = ctx;
|
||||
const { mediaPath, mediaBuffer, contentType, filename, caption } = ctx as {
|
||||
mediaPath?: string;
|
||||
mediaBuffer?: Uint8Array;
|
||||
@@ -247,6 +247,7 @@ export const bluebubblesPlugin: ChannelPlugin<ResolvedBlueBubblesAccount> = {
|
||||
contentType,
|
||||
filename,
|
||||
caption: resolvedCaption ?? undefined,
|
||||
replyToId: replyToId ?? null,
|
||||
accountId: accountId ?? undefined,
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { fileURLToPath } from "node:url";
|
||||
import type { ClawdbotConfig } from "clawdbot/plugin-sdk";
|
||||
|
||||
import { sendBlueBubblesAttachment } from "./attachments.js";
|
||||
import { sendMessageBlueBubbles } from "./send.js";
|
||||
import { getBlueBubblesRuntime } from "./runtime.js";
|
||||
|
||||
const HTTP_URL_RE = /^https?:\/\//i;
|
||||
@@ -46,6 +47,7 @@ export async function sendBlueBubblesMedia(params: {
|
||||
contentType?: string;
|
||||
filename?: string;
|
||||
caption?: string;
|
||||
replyToId?: string | null;
|
||||
accountId?: string;
|
||||
}) {
|
||||
const {
|
||||
@@ -57,6 +59,7 @@ export async function sendBlueBubblesMedia(params: {
|
||||
contentType,
|
||||
filename,
|
||||
caption,
|
||||
replyToId,
|
||||
accountId,
|
||||
} = params;
|
||||
const core = getBlueBubblesRuntime();
|
||||
@@ -106,15 +109,25 @@ export async function sendBlueBubblesMedia(params: {
|
||||
}
|
||||
}
|
||||
|
||||
return sendBlueBubblesAttachment({
|
||||
const attachmentResult = await sendBlueBubblesAttachment({
|
||||
to,
|
||||
buffer,
|
||||
filename: resolvedFilename ?? "attachment",
|
||||
contentType: resolvedContentType ?? undefined,
|
||||
caption: caption ?? undefined,
|
||||
opts: {
|
||||
cfg,
|
||||
accountId,
|
||||
},
|
||||
});
|
||||
|
||||
const trimmedCaption = caption?.trim();
|
||||
if (trimmedCaption) {
|
||||
await sendMessageBlueBubbles(to, trimmedCaption, {
|
||||
cfg,
|
||||
accountId,
|
||||
replyToMessageGuid: replyToId?.trim() || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
return attachmentResult;
|
||||
}
|
||||
|
||||
@@ -1396,6 +1396,55 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("outbound message ids", () => {
|
||||
it("enqueues system event for outbound message id", async () => {
|
||||
mockEnqueueSystemEvent.mockClear();
|
||||
|
||||
mockDispatchReplyWithBufferedBlockDispatcher.mockImplementationOnce(async (params) => {
|
||||
await params.dispatcherOptions.deliver({ text: "replying now" }, { kind: "final" });
|
||||
});
|
||||
|
||||
const account = createMockAccount();
|
||||
const config: ClawdbotConfig = {};
|
||||
const core = createMockRuntime();
|
||||
setBlueBubblesRuntime(core);
|
||||
|
||||
unregister = registerBlueBubblesWebhookTarget({
|
||||
account,
|
||||
config,
|
||||
runtime: { log: vi.fn(), error: vi.fn() },
|
||||
core,
|
||||
path: "/bluebubbles-webhook",
|
||||
});
|
||||
|
||||
const payload = {
|
||||
type: "new-message",
|
||||
data: {
|
||||
text: "hello",
|
||||
handle: { address: "+15551234567" },
|
||||
isGroup: false,
|
||||
isFromMe: false,
|
||||
guid: "msg-1",
|
||||
chatGuid: "iMessage;-;+15551234567",
|
||||
date: Date.now(),
|
||||
},
|
||||
};
|
||||
|
||||
const req = createMockRequest("POST", "/bluebubbles-webhook", payload);
|
||||
const res = createMockResponse();
|
||||
|
||||
await handleBlueBubblesWebhookRequest(req, res);
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
expect(mockEnqueueSystemEvent).toHaveBeenCalledWith(
|
||||
"BlueBubbles sent message id: msg-123",
|
||||
expect.objectContaining({
|
||||
sessionKey: "agent:main:bluebubbles:dm:+15551234567",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("reaction events", () => {
|
||||
it("enqueues system event for reaction added", async () => {
|
||||
mockEnqueueSystemEvent.mockClear();
|
||||
|
||||
@@ -1316,6 +1316,15 @@ async function processMessage(
|
||||
? formatBlueBubblesChatTarget({ chatGuid: chatGuidForActions })
|
||||
: message.senderId;
|
||||
|
||||
const maybeEnqueueOutboundMessageId = (messageId?: string) => {
|
||||
const trimmed = messageId?.trim();
|
||||
if (!trimmed || trimmed === "ok" || trimmed === "unknown") return;
|
||||
core.system.enqueueSystemEvent(`BlueBubbles sent message id: ${trimmed}`, {
|
||||
sessionKey: route.sessionKey,
|
||||
contextKey: `bluebubbles:outbound:${outboundTarget}:${trimmed}`,
|
||||
});
|
||||
};
|
||||
|
||||
const ctxPayload = {
|
||||
Body: body,
|
||||
BodyForAgent: body,
|
||||
@@ -1368,13 +1377,15 @@ async function processMessage(
|
||||
for (const mediaUrl of mediaList) {
|
||||
const caption = first ? payload.text : undefined;
|
||||
first = false;
|
||||
await sendBlueBubblesMedia({
|
||||
const result = await sendBlueBubblesMedia({
|
||||
cfg: config,
|
||||
to: outboundTarget,
|
||||
mediaUrl,
|
||||
caption: caption ?? undefined,
|
||||
replyToId: payload.replyToId ?? null,
|
||||
accountId: account.accountId,
|
||||
});
|
||||
maybeEnqueueOutboundMessageId(result.messageId);
|
||||
sentMessage = true;
|
||||
statusSink?.({ lastOutboundAt: Date.now() });
|
||||
}
|
||||
@@ -1391,11 +1402,12 @@ async function processMessage(
|
||||
for (const chunk of chunks) {
|
||||
const replyToMessageGuid =
|
||||
typeof payload.replyToId === "string" ? payload.replyToId.trim() : "";
|
||||
await sendMessageBlueBubbles(outboundTarget, chunk, {
|
||||
const result = await sendMessageBlueBubbles(outboundTarget, chunk, {
|
||||
cfg: config,
|
||||
accountId: account.accountId,
|
||||
replyToMessageGuid: replyToMessageGuid || undefined,
|
||||
});
|
||||
maybeEnqueueOutboundMessageId(result.messageId);
|
||||
sentMessage = true;
|
||||
statusSink?.({ lastOutboundAt: Date.now() });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user