Skip to content

fix(preview): send User-Agent on OG fetch and upload HQ thumbnail for large preview card#345

Open
claytim wants to merge 3 commits into
asternic:mainfrom
claytim:fix/link-preview-ua-and-hq-thumbnail
Open

fix(preview): send User-Agent on OG fetch and upload HQ thumbnail for large preview card#345
claytim wants to merge 3 commits into
asternic:mainfrom
claytim:fix/link-preview-ua-and-hq-thumbnail

Conversation

@claytim

@claytim claytim commented Jul 9, 2026

Copy link
Copy Markdown

Summary

Link previews ("LinkPreview": true on POST /chat/send/text) had three problems, all fixed here:

  1. Bot-protected sites returned 403 and the preview silently came out empty. Open Graph requests were sent without a User-Agent, so Go's default Go-http-client/2.0 was used and sites like Mercado Livre, Shopee and Amazon short links refused the request. Requests now send User-Agent: WhatsApp/2.23.20.0 (the UA WhatsApp's own preview fetcher uses, which sites allow since they want their links previewed) plus Accept/Accept-Language. The Accept header intentionally does not advertise image/avif: CDNs (e.g. ae01.alicdn.com) would then serve AVIF, which Go's image package cannot decode.

  2. 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 via Client.Upload(..., whatsmeow.MediaLinkThumbnail), and referenced through ThumbnailDirectPath, ThumbnailSHA256, ThumbnailEncSHA256, MediaKey, ThumbnailWidth and ThumbnailHeight. If the upload fails, the message falls back to the inline thumbnail instead of failing the send.

  3. WhatsApp Web rendered the large card blurry. MediaKeyTimestamp is 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).

getOpenGraphData now returns the openGraphResult struct 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": true logged:

{"level":"warn","error":"unexpected status code 403","url":"https://produto.mercadolivre.com.br/MLB-4810647054-...","message":"Failed to fetch URL for Open Graph data"}

and the message was delivered with no preview card at all.

User-Agent check against the same URL:

Request Result
no User-Agent (Go default) 403 Forbidden
User-Agent: WhatsApp/2.23.20.0 200 OK
User-Agent: facebookexternalhit/1.1 ... 200 OK

AVIF check against the AliExpress CDN (ae01.alicdn.com/kf/....jpg):

Accept header Served content
...,image/avif,image/webp,*/*;q=0.8 image/avifimage: 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 card
  • https://s.shopee.com.br/9fJAInl3U4 — large sharp card
  • https://amzn.to/4pvvoqJ — large sharp card
  • https://s.click.aliexpress.com/e/_c4Oa6MOX — large sharp card (previously: card without image due to AVIF)
  • Message containing three links — preview generated for the first URL, matching official client behavior

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.

image

… 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).

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread handlers.go Outdated
// 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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
uploaded, upErr := clientManager.GetWhatsmeowClient(txtid).Upload(context.Background(), og.HQImageData, whatsmeow.MediaLinkThumbnail)
uploaded, upErr := clientManager.GetWhatsmeowClient(txtid).Upload(r.Context(), og.HQImageData, whatsmeow.MediaLinkThumbnail)

Comment thread helpers.go Outdated
claytim added 2 commits July 9, 2026 11:29
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant