fix(preview): send User-Agent on OG fetch and upload HQ thumbnail for large preview card#345
fix(preview): send User-Agent on OG fetch and upload HQ thumbnail for large preview card#345claytim wants to merge 3 commits into
Conversation
… large preview card Link previews (LinkPreview: true on /chat/send/text) failed or rendered poorly in three ways: 1. Open Graph requests were sent without a User-Agent, so Go's default "Go-http-client/2.0" was used. Bot-protected sites (e.g. Mercado Livre) answer 403 and the message went out with an empty preview. Requests now send the WhatsApp preview fetcher UA plus Accept and Accept-Language headers. 2. Only a small inline JPEGThumbnail (100px) was embedded, so clients always rendered the compact preview card. Now a high-res thumbnail (up to 600px) is also generated, uploaded to WhatsApp media servers (MediaLinkThumbnail) and referenced via ThumbnailDirectPath, ThumbnailSHA256/EncSHA256, MediaKey and ThumbnailWidth/Height, which makes clients render the large preview card. If the upload fails the message falls back to the inline thumbnail. 3. MediaKeyTimestamp is required by WhatsApp Web to download the hosted thumbnail; without it the card shows the stretched inline thumbnail and looks blurry. It is now set, and the inline thumbnail size was raised from 100px to 192px (the size official clients embed).
There was a problem hiding this comment.
Code Review
This pull request introduces support for high-resolution link preview thumbnails in WhatsApp messages by uploading the high-res image to WhatsApp media servers and configuring the message's extended text fields. It also updates HTTP headers (such as User-Agent and Accept) when fetching Open Graph data to bypass bot protection and avoid unsupported image formats. The feedback suggests using the request context (r.Context()) instead of context.Background() for the media upload to properly manage the request lifecycle, and optimizing the image resizing process by downscaling the smaller thumbnail from the already resized high-resolution thumbnail rather than resizing the original image twice.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| // Upload the high-res thumbnail so clients render the large preview | ||
| // card; without these fields only the small inline thumbnail shows. | ||
| if len(og.HQImageData) > 0 { | ||
| uploaded, upErr := clientManager.GetWhatsmeowClient(txtid).Upload(context.Background(), og.HQImageData, whatsmeow.MediaLinkThumbnail) |
There was a problem hiding this comment.
Using context.Background() for the media upload ignores the lifecycle of the incoming HTTP request. It is highly recommended to use r.Context() instead. This ensures that if the client cancels the request or disconnects, the upload operation is aborted immediately, saving server resources and network bandwidth.
| uploaded, upErr := clientManager.GetWhatsmeowClient(txtid).Upload(context.Background(), og.HQImageData, whatsmeow.MediaLinkThumbnail) | |
| uploaded, upErr := clientManager.GetWhatsmeowClient(txtid).Upload(r.Context(), og.HQImageData, whatsmeow.MediaLinkThumbnail) |
When none of the og:image/twitter:image/icon selectors matched,
imageURLStr was empty and pageURL.ResolveReference resolved it to the
page itself, so the HTML page was downloaded a second time and fed to
the image decoder ("image: unknown format"). Skip the image step
entirely in that case.
- Use r.Context() for the thumbnail upload so a cancelled request aborts the upload instead of running in the background. - Downscale the inline thumbnail from the already resized 600px thumbnail instead of running Lanczos3 twice over the original image (which can be up to 4000x4000).
Summary
Link previews (
"LinkPreview": trueonPOST /chat/send/text) had three problems, all fixed here:Bot-protected sites returned 403 and the preview silently came out empty. Open Graph requests were sent without a
User-Agent, so Go's defaultGo-http-client/2.0was used and sites like Mercado Livre, Shopee and Amazon short links refused the request. Requests now sendUser-Agent: WhatsApp/2.23.20.0(the UA WhatsApp's own preview fetcher uses, which sites allow since they want their links previewed) plusAccept/Accept-Language. TheAcceptheader intentionally does not advertiseimage/avif: CDNs (e.g.ae01.alicdn.com) would then serve AVIF, which Go'simagepackage cannot decode.Only the small compact preview card was ever rendered. The code embedded a single 100px inline
JPEGThumbnail. Official clients render the large preview card (full-width image on top) only when the message references a high-resolution thumbnail uploaded to WhatsApp's media servers. Now a thumbnail of up to 600px is also generated, uploaded viaClient.Upload(..., whatsmeow.MediaLinkThumbnail), and referenced throughThumbnailDirectPath,ThumbnailSHA256,ThumbnailEncSHA256,MediaKey,ThumbnailWidthandThumbnailHeight. If the upload fails, the message falls back to the inline thumbnail instead of failing the send.WhatsApp Web rendered the large card blurry.
MediaKeyTimestampis required by WhatsApp Web to download the hosted thumbnail; without it, Web falls back to stretching the tiny inline thumbnail. It is now set, and the inline thumbnail size was raised from 100px to 192px, which is the size official clients embed (verified against real messages received from official clients).getOpenGraphDatanow returns theopenGraphResultstruct instead of three separate values, so the handler can access both thumbnails and the dimensions.Evidence
Before the fix, sending a Mercado Livre product link with
"LinkPreview": truelogged:and the message was delivered with no preview card at all.
User-Agent check against the same URL:
User-Agent: WhatsApp/2.23.20.0User-Agent: facebookexternalhit/1.1 ...AVIF check against the AliExpress CDN (
ae01.alicdn.com/kf/....jpg):Acceptheader...,image/avif,image/webp,*/*;q=0.8image/avif→image: unknown format, thumbnail dropped...,image/webp,*/*;q=0.8(this PR)image/webp→ decoded fine (webp decoder is already imported)After the fix, all of the following were sent through the API and verified on Android and WhatsApp Web:
https://github.com— large sharp card (site without bot protection, regression check)https://produto.mercadolivre.com.br/MLB-4810647054-...— large sharp card (previously: no preview at all)https://meli.la/1ER8rwq— short link, redirect followed with the UA preserved, large sharp cardhttps://s.shopee.com.br/9fJAInl3U4— large sharp cardhttps://amzn.to/4pvvoqJ— large sharp cardhttps://s.click.aliexpress.com/e/_c4Oa6MOX— large sharp card (previously: card without image due to AVIF)Before setting
MediaKeyTimestamp, the large card rendered correctly on the phone but blurry on WhatsApp Web; with it set, both render sharp. No errors or warnings in the server logs for any of the sends above.