Reliably embed failure screenshots in Nextcloud Talk
Screenshots often fell back to "too large to embed" text instead of the
actual proof image. A single fixed 800px PNG encode exceeds Nextcloud
Talk's 32000-char message limit once base64-expanded (~74KB).
Encode as JPEG (much smaller for UI screenshots) and step the width down
through a list of candidates until the embedded message fits, keeping the
largest size that does. Typical search-failure screenshots now embed at
full 800px width; even a 336KB screenshot embeds at 520px.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Change-Id: I32a71646c3e6c57a18e1230080c53abf952b3dd7
diff --git a/lib/utils.js b/lib/utils.js
index 145a689..5d1c20e 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -18,29 +18,41 @@
let fullMessage = message;
const MAX_MESSAGE_LENGTH = 32000; // Nextcloud Talk message size limit
- // If a screenshot path is provided, try to embed it
+ // If a screenshot path is provided, try to embed it. The data URI is
+ // base64 (~33% overhead), so a PNG quickly blows past the message size
+ // limit. Use JPEG (much smaller for UI screenshots) and step the width
+ // down until the embedded message fits, keeping the largest that does.
if (screenshotPath && fs.existsSync(screenshotPath)) {
try {
- // First, try to resize the image to reduce size
- const resizedBuffer = await sharp(screenshotPath)
- .resize(800, null, { // Resize to max width of 800px, maintain aspect ratio
- withoutEnlargement: true,
- fit: 'inside'
- })
- .png({ quality: 80, compressionLevel: 9 })
- .toBuffer();
-
- const base64Image = resizedBuffer.toString('base64');
- const dataUri = `data:image/png;base64,${base64Image}`;
- const messageWithImage = `${message}\n\n`;
-
- // Check if the message with image fits within the limit
- if (messageWithImage.length <= MAX_MESSAGE_LENGTH) {
- fullMessage = messageWithImage;
- console.log(`Screenshot will be embedded (message size: ${messageWithImage.length} chars)`);
+ const candidateWidths = [800, 640, 520, 420, 320, 240];
+ let embedded = null;
+ let lastSize = 0;
+
+ for (const width of candidateWidths) {
+ const resizedBuffer = await sharp(screenshotPath)
+ .resize(width, null, { // maintain aspect ratio
+ withoutEnlargement: true,
+ fit: 'inside'
+ })
+ .jpeg({ quality: 72, mozjpeg: true })
+ .toBuffer();
+
+ const dataUri = `data:image/jpeg;base64,${resizedBuffer.toString('base64')}`;
+ const messageWithImage = `${message}\n\n`;
+ lastSize = messageWithImage.length;
+
+ if (messageWithImage.length <= MAX_MESSAGE_LENGTH) {
+ embedded = messageWithImage;
+ console.log(`Screenshot embedded at width ${width}px (message size: ${messageWithImage.length} chars)`);
+ break;
+ }
+ }
+
+ if (embedded) {
+ fullMessage = embedded;
} else {
- console.log(`Screenshot too large (${messageWithImage.length} chars), sending text-only notification`);
- fullMessage = `${message}\n\n_Screenshot available locally but too large to embed (${Math.round(messageWithImage.length / 1024)}KB)_`;
+ console.log(`Screenshot too large to embed even at smallest size (${lastSize} chars), sending text-only notification`);
+ fullMessage = `${message}\n\n_Screenshot available locally but too large to embed_`;
}
} catch (imageError) {
console.error('Failed to process screenshot for Nextcloud Talk:', imageError.message);