From 8ea7f9b439df962316373ceb74fc92ce222f8159 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 25 Nov 2025 06:02:41 +0100 Subject: [PATCH] fix: keep MEDIA tokens with punctuation and log web media failures --- src/media/parse.ts | 3 ++- src/provider-web.ts | 62 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/media/parse.ts b/src/media/parse.ts index d2bfe8664..b4e2491a2 100644 --- a/src/media/parse.ts +++ b/src/media/parse.ts @@ -1,7 +1,8 @@ // Shared helpers for parsing MEDIA tokens from command/stdout text. export const MEDIA_LINE_RE = /\bMEDIA:/i; -export const MEDIA_TOKEN_RE = /\bMEDIA:\s*([^\s]+)/i; +// Allow optional wrapping backticks and punctuation after the token; capture the core token. +export const MEDIA_TOKEN_RE = /\bMEDIA:\s*`?([^\s`]+)`?/i; export function normalizeMediaSource(src: string) { if (src.startsWith("file://")) return src.replace("file://", ""); diff --git a/src/provider-web.ts b/src/provider-web.ts index e313f63a2..73d1fb165 100644 --- a/src/provider-web.ts +++ b/src/provider-web.ts @@ -367,18 +367,47 @@ export async function monitorWebProvider( onReplyStart: msg.sendComposing, }, ); - if (!replyResult || (!replyResult.text && !replyResult.mediaUrl)) return; + if (!replyResult || (!replyResult.text && !replyResult.mediaUrl)) { + logVerbose( + "Skipping auto-reply: no text/media returned from resolver", + ); + return; + } try { if (replyResult.mediaUrl) { logVerbose( `Web auto-reply media detected: ${replyResult.mediaUrl}`, ); - const media = await loadWebMedia(replyResult.mediaUrl); - await msg.sendMedia({ - image: media.buffer, - caption: replyResult.text || undefined, - mimetype: media.contentType, - }); + try { + const media = await loadWebMedia(replyResult.mediaUrl); + if (isVerbose()) { + logVerbose( + `Web auto-reply media size: ${(media.buffer.length / (1024 * 1024)).toFixed(2)}MB`, + ); + } + await msg.sendMedia({ + image: media.buffer, + caption: replyResult.text || undefined, + mimetype: media.contentType, + }); + logInfo( + `✅ Sent web media reply to ${msg.from} (${(media.buffer.length / (1024 * 1024)).toFixed(2)}MB)`, + runtime, + ); + } catch (err) { + console.error( + danger( + `Failed sending web media to ${msg.from}: ${String(err)}`, + ), + ); + if (replyResult.text) { + await msg.reply(replyResult.text); + logInfo( + `⚠️ Media skipped; sent text-only to ${msg.from}`, + runtime, + ); + } + } } else { await msg.reply(replyResult.text ?? ""); } @@ -523,6 +552,7 @@ async function downloadInboundMedia( async function loadWebMedia( mediaUrl: string, ): Promise<{ buffer: Buffer; contentType?: string }> { + const MAX_WEB_BYTES = 16 * 1024 * 1024; // 16MB: web provider can handle larger than Twilio if (mediaUrl.startsWith("file://")) { mediaUrl = mediaUrl.replace("file://", ""); } @@ -532,15 +562,25 @@ async function loadWebMedia( throw new Error(`Failed to fetch media: HTTP ${res.status}`); } const array = Buffer.from(await res.arrayBuffer()); - if (array.length > 5 * 1024 * 1024) { - throw new Error("Media exceeds 5MB limit"); + if (array.length > MAX_WEB_BYTES) { + throw new Error( + `Media exceeds ${Math.floor(MAX_WEB_BYTES / (1024 * 1024))}MB limit (got ${( + array.length / + (1024 * 1024) + ).toFixed(1)}MB)`, + ); } return { buffer: array, contentType: res.headers.get("content-type") ?? undefined }; } // Local path const data = await fs.readFile(mediaUrl); - if (data.length > 5 * 1024 * 1024) { - throw new Error("Media exceeds 5MB limit"); + if (data.length > MAX_WEB_BYTES) { + throw new Error( + `Media exceeds ${Math.floor(MAX_WEB_BYTES / (1024 * 1024))}MB limit (got ${( + data.length / + (1024 * 1024) + ).toFixed(1)}MB)`, + ); } return { buffer: data }; }