Files
buzz/docs/gif-search.md
klopez4212 8471049c43 feat(desktop): add KLIPY GIF search to composers (#5554)
## Summary

- Supersedes #1913 with a KLIPY-hosted URL implementation.
- Adds KLIPY GIF search and trending results to desktop message and
forum composers.
- Keeps selected GIFs hosted by KLIPY; Buzz stores only the external URL
and media metadata (no imeta tag, since relays only accept hash-backed
local `/media/` entries).
- Aligns the Emoji/GIF picker with Buzz's standard segmented control,
theme surfaces, and motion behavior.

## Relay-to-provider boundary

- The relay proxies KLIPY search/share so the `BUZZ_KLIPY_API_KEY` never
reaches the desktop; the key stays server-side behind a redacted `Debug`
impl.
- The dedicated GIF `reqwest` client sets `redirect::Policy::none()`.
Because the API key rides in the request path, following a provider
`3xx` could replay a key-bearing URL to an attacker-chosen host (an
SSRF/key-disclosure primitive). With redirects disabled, a `3xx` returns
as a non-success status that the handlers map to a generic `502`; the
`Location` target is never read or forwarded.
- Admission reuses the established NIP-98, membership, replay, and
per-pubkey rate-limit gates, with an upstream response-size cap and
allowlisting so KLIPY error bodies never cross the relay boundary.

## Accessibility

- Under `prefers-reduced-motion: reduce`, the picker grid renders a
static provider poster (a normalized `jpg` asset) instead of the
animated preview, or a named static placeholder when no poster is
available. It reacts to preference changes while mounted.
`no-preference` keeps the animated preview.
- Selected GIFs carry their title through `ImetaMedia`'s `displayLabel`,
so the composer thumbnail, preview dialog, editor, lightbox, and remove
control all derive one non-empty accessible name instead of an empty
`Attachment ` label. Ordinary hashed uploads keep their existing
hash-derived names.

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Signed-off-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Hayt <9e1c23a3fd83f61da34420e4e88ff1b16e45cafcc0cd9019eb07d4ecfa8ca9b0@buzz.block.builderlab.xyz>
2026-08-25 19:09:55 -04:00

1.7 KiB

Relay-proxied GIF search

Buzz relays can optionally provide GIF search without distributing a provider credential to desktop clients. An operator configures BUZZ_KLIPY_API_KEY in the relay's secret store. When present, the relay advertises this NIP-11 shape:

{
  "supported_extensions": ["buzz-gif"],
  "gif": {
    "provider": "klipy",
    "search": "/gifs/search",
    "share": "/gifs/share"
  }
}

The descriptor is provider-agnostic so a relay can advertise another provider or paths later. Clients must require buzz-gif, recognize the provider, and use only safe relay-relative search and share paths.

POST /gifs/search requires NIP-98 authentication and relay membership. Its per-pubkey, per-community Redis admission limit defaults to 30 requests per minute and can be tuned with BUZZ_RATE_LIMIT_GIF_SEARCHES_PER_MIN. The relay sends the provider credential upstream and returns only allowlisted successful result data. Provider error bodies are never returned to clients or written to logs.

POST /gifs/share uses the same authentication and membership boundary. It accepts a bounded GIF slug plus anonymous customer ID and forwards KLIPY's share signal so selected media can enter the user's provider-backed Recents. The endpoint returns no provider body.

Message and rendering boundary

Selecting a GIF sends a normal Buzz message containing a KLIPY CDN image URL. Because Buzz's imeta validator permits only hash-verified local /media/ paths, external GIFs are deliberately content-only and carry no imeta tag. Buzz does not download, cache, or store the GIF bytes. Existing image URL rendering handles the message, including pasted GIF URLs on relays that do not advertise buzz-gif. Only the picker is capability-gated.