# macltok request, URL previews via the homeserver

## Problem

Malice's link cards only show the URL's host and path. It wants the page's title, description and image, the way other Matrix clients do. The client can't scrape pages itself. Browsers block it with CORS, and fetching from the malice server would leak its IP to every linked site.

Matrix already solves this with `GET /_matrix/client/v1/media/preview_url?url=...&ts=...` (ruma `authenticated_media::get_media_preview`). The homeserver fetches the page, parses OpenGraph, re-hosts `og:image` as an mxc, and returns the `og:*` keys as a loose JSON map. Knowing that endpoint, which keys matter, and that `og:image` is an mxc is protocol work every consumer would repeat, so it belongs in macltok. Nothing in `ClientHandle` reaches it today.

## Request

```rust
impl ClientHandle {
    /// None when the server has no preview for the url (404, empty body, or no usable keys)
    pub async fn url_preview(&self, url: &str) -> Result<Option<UrlPreview>, Error>;
}

pub struct UrlPreview {
    pub url: String,
    pub title: Option<String>,
    pub description: Option<String>,
    pub site_name: Option<String>,
    pub image: Option<UrlPreviewImage>,
}

pub struct UrlPreviewImage {
    /// og:image, already re-hosted by the server, usable with `media` / `media_source`
    pub media_id: MediaId,
    pub width: Option<u32>,
    pub height: Option<u32>,
    pub mimetype: Option<String>,
    pub size: Option<u64>,
}
```

Things that would help beyond the bare call.

1. Fall back to the legacy `/_matrix/media/v3/preview_url` when the server lacks authenticated media, the same way `media` does.
2. Cache results per url for a while, including negative ones. Synapse rate-limits this endpoint and a busy timeline repeats links.
3. Clamp string lengths, since the server passes page text through unfiltered. Something like 256 chars for title and site name, 1024 for description.
4. Treat `og:image` that isn't an `mxc://` uri as absent.
5. Map 429 and 5xx to a distinct error, so hosts can tell "try later" from "no preview".

## Optional, room level opt-out

Element honours `org.matrix.room.preview_urls` room state (`{"disable": true}`, set by room admins) and the per-user `org.matrix.preview_urls` room account data. If macltok exposed those on `RoomSummary`, or as `url_previews_allowed(room_id) -> bool`, malice wouldn't need to know the event types.

## Malice side, for context

Malice will call this only in unencrypted rooms by default, with a user setting to also allow it in encrypted ones, because the homeserver sees every previewed url. The image goes through malice's existing bounded media endpoint by `media_id`.
