refactor: share bluebubbles response and tapback helpers

This commit is contained in:
Peter Steinberger 2026-03-13 21:17:59 +00:00
parent 867dc6a185
commit 6756e376f3
2 changed files with 39 additions and 34 deletions

View File

@ -26,6 +26,14 @@ function assertPrivateApiEnabled(accountId: string, feature: string): void {
}
}
async function assertBlueBubblesActionOk(response: Response, action: string): Promise<void> {
if (response.ok) {
return;
}
const errorText = await response.text().catch(() => "");
throw new Error(`BlueBubbles ${action} failed (${response.status}): ${errorText || "unknown"}`);
}
function resolvePartIndex(partIndex: number | undefined): number {
return typeof partIndex === "number" ? partIndex : 0;
}
@ -55,12 +63,7 @@ async function sendBlueBubblesChatEndpointRequest(params: {
{ method: params.method },
params.opts.timeoutMs,
);
if (!res.ok) {
const errorText = await res.text().catch(() => "");
throw new Error(
`BlueBubbles ${params.action} failed (${res.status}): ${errorText || "unknown"}`,
);
}
await assertBlueBubblesActionOk(res, params.action);
}
async function sendPrivateApiJsonRequest(params: {
@ -86,12 +89,7 @@ async function sendPrivateApiJsonRequest(params: {
}
const res = await blueBubblesFetchWithTimeout(url, request, params.opts.timeoutMs);
if (!res.ok) {
const errorText = await res.text().catch(() => "");
throw new Error(
`BlueBubbles ${params.action} failed (${res.status}): ${errorText || "unknown"}`,
);
}
await assertBlueBubblesActionOk(res, params.action);
}
export async function markBlueBubblesChatRead(

View File

@ -582,6 +582,29 @@ export function parseTapbackText(params: {
return null;
}
const parseLeadingReactionAction = (
prefix: "reacted" | "removed",
defaultAction: "added" | "removed",
) => {
if (!lower.startsWith(prefix)) {
return null;
}
const emoji = extractFirstEmoji(trimmed) ?? params.emojiHint;
if (!emoji) {
return null;
}
const quotedText = extractQuotedTapbackText(trimmed);
if (params.requireQuoted && !quotedText) {
return null;
}
const fallback = trimmed.slice(prefix.length).trim();
return {
emoji,
action: params.actionHint ?? defaultAction,
quotedText: quotedText ?? fallback,
};
};
for (const [pattern, { emoji, action }] of TAPBACK_TEXT_MAP) {
if (lower.startsWith(pattern)) {
// Extract quoted text if present (e.g., 'Loved "hello"' -> "hello")
@ -599,30 +622,14 @@ export function parseTapbackText(params: {
}
}
if (lower.startsWith("reacted")) {
const emoji = extractFirstEmoji(trimmed) ?? params.emojiHint;
if (!emoji) {
return null;
}
const quotedText = extractQuotedTapbackText(trimmed);
if (params.requireQuoted && !quotedText) {
return null;
}
const fallback = trimmed.slice("reacted".length).trim();
return { emoji, action: params.actionHint ?? "added", quotedText: quotedText ?? fallback };
const reacted = parseLeadingReactionAction("reacted", "added");
if (reacted) {
return reacted;
}
if (lower.startsWith("removed")) {
const emoji = extractFirstEmoji(trimmed) ?? params.emojiHint;
if (!emoji) {
return null;
}
const quotedText = extractQuotedTapbackText(trimmed);
if (params.requireQuoted && !quotedText) {
return null;
}
const fallback = trimmed.slice("removed".length).trim();
return { emoji, action: params.actionHint ?? "removed", quotedText: quotedText ?? fallback };
const removed = parseLeadingReactionAction("removed", "removed");
if (removed) {
return removed;
}
return null;
}