Conversation
📝 WalkthroughWalkthroughAdds sticker handling across media processing: Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant IncomingJob
participant Transformer
participant StickerUtil
participant Sharp
Client->>IncomingJob: Upload/send media (type: sticker)
IncomingJob->>IncomingJob: fetch media buffer & determine mimetype/size
alt size ≤ MAX_STICKER_BYTES and type is sticker
IncomingJob->>StickerUtil: convertToWebpSticker(buffer, {animated?})
StickerUtil->>Sharp: resize & encode WebP
Sharp-->>StickerUtil: WebP buffer
StickerUtil-->>IncomingJob: WebP buffer
IncomingJob->>IncomingJob: set mimetype = image/webp, replace buffer
end
IncomingJob->>Transformer: pass media payload (sticker as media)
Transformer->>Client: produce Baileys message content with sticker media
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/services/transformer.ts (1)
467-487:⚠️ Potential issue | 🟠 MajorPre-existing fallthrough to
contactscase whenlinkis falsy.Biome correctly flags this: when
payload[type].linkis falsy (Line 469), thebreakon Line 484 is inside theifblock, so execution falls through intocase 'contacts':. This silently produces a malformed contacts response instead of throwing an error. This affects all media types including the newly addedsticker.While this is pre-existing, sticker payloads may be more likely to hit this path (e.g., if a sticker is sent as base64 data rather than a link).
Proposed fix
response[type] = { url: link } break } + break case 'contacts':Alternatively, throw an error for missing links:
response[type] = { url: link } - break } + else { + throw new Error(`Missing link for ${type} message`) + } + break case 'contacts':
9f0b006 to
faa44a8
Compare
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/services/transformer.ts (1)
467-485:⚠️ Potential issue | 🟡 MinorPre-existing fallthrough when
linkis falsy applies to stickers too.The static analyzer flags that if
payload[type].linkis falsy, execution silently falls through into the'contacts'case (line 487). This isn't new to your change — it affects all media types — but it now also applies to'sticker'. A sticker payload without alinkwould be (mis)handled as a contact.Consider adding an explicit
breakorthrowafter theif (link)block so invalid media payloads fail fast rather than silently falling through.Proposed fix
response[type] = { url: link } break } + throw new Error(`Missing link for media type ${type}`) case 'contacts':
🧹 Nitpick comments (1)
src/utils/sticker_convert.ts (1)
7-14:MAX_STICKER_BYTESis defined but the output is never validated against it.WhatsApp enforces sticker size limits. The constant suggests the intent to guard against oversized output, but
convertToWebpStickerdoesn't check the result. Lossless WebP for high-res static images can easily exceed 8 MB.Consider adding a post-conversion size check or a retry with lossy encoding:
Sketch
export const convertToWebpSticker = async (input: Buffer, opts: StickerConvertOptions = {}) => { const image = sharp(input, { animated: !!opts.animated }) - return image + let buf = await image .resize(512, 512, { fit: 'inside', withoutEnlargement: true }) .webp({ lossless: !opts.animated, quality: 80, effort: 4 }) .toBuffer() + if (buf.byteLength > MAX_STICKER_BYTES) { + buf = await sharp(input, { animated: !!opts.animated }) + .resize(512, 512, { fit: 'inside', withoutEnlargement: true }) + .webp({ lossless: false, quality: 60, effort: 4 }) + .toBuffer() + } + if (buf.byteLength > MAX_STICKER_BYTES) { + throw new Error(`Sticker exceeds maximum size of ${MAX_STICKER_BYTES} bytes`) + } + return buf }
faa44a8 to
1010aee
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/services/transformer.ts (1)
463-485:⚠️ Potential issue | 🟡 MinorPre-existing fallthrough: when
linkis falsy, media cases fall through tocontacts.The static analysis tool correctly flags that if
linkis undefined/empty, thebreakon line 484 is skipped and execution falls through to thecontactscase. This isn't new to this PR (it affects all media types), but addingstickerextends the surface area. A sticker payload without alinkwould silently fall into contact handling.Consider adding an explicit
breakorthrowafter theif (link)block to prevent this for all media types.💡 Suggested fix
response[type] = { url: link } break } + break case 'contacts':
🤖 Fix all issues with AI agents
In `@src/jobs/incoming.ts`:
- Around line 69-79: The file is saved using fileName computed from the original
mimetype, so after converting stickers to webp the saved file keeps the wrong
extension; update the logic in the incoming handler (where getMimetype,
mime.extension and fileName are used) to recompute the extension and fileName
after convertToWebpSticker when payload.type === 'sticker' (set mimetype =
'image/webp' already done — ensure you also set a new finalFileName using
mime.extension(mimetype) before calling mediaStore.saveMediaBuffer), and make
sure any downstream reference such as messagePayload uses that finalFileName
instead of the original fileName.
🧹 Nitpick comments (1)
src/jobs/incoming.ts (1)
74-78: No error handling aroundconvertToWebpSticker.If
sharpthrows (e.g. corrupt input, unsupported format), the entire job will fail unhandled. Consider wrapping the conversion in a try/catch and falling back to the original buffer so sticker delivery isn't blocked by a conversion failure.💡 Suggested resilient fallback
if (payload.type === 'sticker' && buffer.byteLength <= MAX_STICKER_BYTES) { - const animated = extension == 'gif' - buffer = await convertToWebpSticker(buffer, { animated }) - mimetype = 'image/webp' + try { + const animated = extension == 'gif' + buffer = await convertToWebpSticker(buffer, { animated }) + mimetype = 'image/webp' + } catch (e) { + logger.warn('Failed to convert sticker to webp, using original', e) + } }
| let mimetype = getMimetype(payload) | ||
| const extension = mime.extension(mimetype) | ||
| const fileName = `${mediaKey}.${extension}` | ||
| const response: Response = await fetch(link, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), method: 'GET' }) | ||
| const buffer = toBuffer(await response.arrayBuffer()) | ||
| let buffer: Buffer = toBuffer(await response.arrayBuffer()) | ||
| if (payload.type === 'sticker' && buffer.byteLength <= MAX_STICKER_BYTES) { | ||
| const animated = extension == 'gif' | ||
| buffer = await convertToWebpSticker(buffer, { animated }) | ||
| mimetype = 'image/webp' | ||
| } | ||
| await mediaStore.saveMediaBuffer(fileName, buffer) |
There was a problem hiding this comment.
Bug: file saved with original extension after webp conversion.
fileName is computed on line 71 using the original mimetype's extension (e.g. .gif, .png), but after the sticker-to-webp conversion the buffer contains webp data. The file is then saved at line 79 with the mismatched extension, which will cause issues when the media is later served or consumed.
Recompute fileName after conversion so the extension reflects the actual content.
🐛 Proposed fix
let buffer: Buffer = toBuffer(await response.arrayBuffer())
if (payload.type === 'sticker' && buffer.byteLength <= MAX_STICKER_BYTES) {
const animated = extension == 'gif'
buffer = await convertToWebpSticker(buffer, { animated })
mimetype = 'image/webp'
}
- await mediaStore.saveMediaBuffer(fileName, buffer)
+ const finalExtension = mime.extension(mimetype) || extension
+ const finalFileName = `${mediaKey}.${finalExtension}`
+ await mediaStore.saveMediaBuffer(finalFileName, buffer)Also update messagePayload to use finalFileName if the filename is referenced downstream.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let mimetype = getMimetype(payload) | |
| const extension = mime.extension(mimetype) | |
| const fileName = `${mediaKey}.${extension}` | |
| const response: Response = await fetch(link, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), method: 'GET' }) | |
| const buffer = toBuffer(await response.arrayBuffer()) | |
| let buffer: Buffer = toBuffer(await response.arrayBuffer()) | |
| if (payload.type === 'sticker' && buffer.byteLength <= MAX_STICKER_BYTES) { | |
| const animated = extension == 'gif' | |
| buffer = await convertToWebpSticker(buffer, { animated }) | |
| mimetype = 'image/webp' | |
| } | |
| await mediaStore.saveMediaBuffer(fileName, buffer) | |
| let mimetype = getMimetype(payload) | |
| const extension = mime.extension(mimetype) | |
| const fileName = `${mediaKey}.${extension}` | |
| const response: Response = await fetch(link, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS), method: 'GET' }) | |
| let buffer: Buffer = toBuffer(await response.arrayBuffer()) | |
| if (payload.type === 'sticker' && buffer.byteLength <= MAX_STICKER_BYTES) { | |
| const animated = extension == 'gif' | |
| buffer = await convertToWebpSticker(buffer, { animated }) | |
| mimetype = 'image/webp' | |
| } | |
| const finalExtension = mime.extension(mimetype) || extension | |
| const finalFileName = `${mediaKey}.${finalExtension}` | |
| await mediaStore.saveMediaBuffer(finalFileName, buffer) |
🤖 Prompt for AI Agents
In `@src/jobs/incoming.ts` around lines 69 - 79, The file is saved using fileName
computed from the original mimetype, so after converting stickers to webp the
saved file keeps the wrong extension; update the logic in the incoming handler
(where getMimetype, mime.extension and fileName are used) to recompute the
extension and fileName after convertToWebpSticker when payload.type ===
'sticker' (set mimetype = 'image/webp' already done — ensure you also set a new
finalFileName using mime.extension(mimetype) before calling
mediaStore.saveMediaBuffer), and make sure any downstream reference such as
messagePayload uses that finalFileName instead of the original fileName.
Summary by CodeRabbit