feat(gif): Tenor GIF picker [operator-action] (LC-488) #469
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/LC-488-gif-picker"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
GIF picker (LC-488): a composer GIF button opens a Tenor search modal; picking one posts it as a normal same-origin attachment. Gated on an operator-set
LETS_CHAT_TENOR_API_KEY(hidden when unset).How
gif::GifConfig::from_env(env-only, read on demand - no AppState client, so zero AppState-constructor churn).LETS_CHAT_TENOR_API_KEY(+ optional_CLIENT_KEY,_CONTENT_FILTERdefaultmedium).GET /api/gifs?q=proxies Tenor v2 (featured when empty),parse_searchextracts preview + full URLs, renders a result grid fragment.POST /room/{id}/gifvalidates the URL is on the Tenor CDN (is_tenor_media_url) on top of thehttp_clientpublic-IP SSRF filter, streams the GIF with a hard byte cap, magic-byte-sniffs (must beimage/gif), re-encodes through the uploads pipeline (LC-206 decode limits), stores a content-addressedfile_uploadsrow, and posts it as an image-only message viafinalize_message_send. GIFs are never hotlinked into messages - always fetched + served same-origin.gif_availableon RoomPage/DmPage/ComposerFragment) + a singleton search modal (debounced hx-get grid, click-to-post), Tenor attribution. i18n en + es.Safety / content
[operator-action]: optional new env; search terms + picks are sent to Tenor (third-party content/usage), documented in the CLAUDE.md env table. Content filter defaults tomedium.Tests
Unit tests for
parse_search(fallbacks + skipping incomplete results) andis_tenor_media_url(incl. look-alike host rejection). The outbound HTTP path is not mockable in-process (concrete reqwest, by design), so the fetch/search HTTP is not covered by an in-process test - the pure parsing + allowlist are.just check,just test,just test-saasgreen.Notes
/gifslash-command variant the issue also mentions was not added (button is sufficient; easy follow-up).Verify
just verifybuilds the release binary clean; boot fails only on the missing SSO env (environmental, as in prior PRs).GET /api/gifs proxies Tenor search (featured when empty) and renders the result grid. POST /room/{id}/gif validates the URL is on the Tenor CDN, streams it with a hard byte cap, sniffs + re-encodes through the uploads pipeline (LC-206 decode limits), stores a content-addressed file_uploads row, and posts it as an image-only message attachment via finalize_message_send. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>