# macltok request, custom emote and sticker packs (MSC2545)

## Problem

Malice is adding an emoji picker for reactions that should also offer the user's custom emotes and stickers. Those live in MSC2545 image packs, spread over three places.

1. `im.ponies.user_emotes` global account data, the user's personal pack
2. `im.ponies.room_emotes` room state, one pack per state key, in the current room
3. `im.ponies.emote_rooms` global account data, which names other rooms (and state keys) whose packs the user enabled everywhere

Merging those, resolving pack vs image `usage`, and following `emote_rooms` into rooms the client may not have state for is protocol work every consumer would repeat, so it belongs in macltok. Malice can't do it today anyway. `global_account_data` covers 1 and 3, but there is no room state getter for 2.

ruma-events 0.34 already has the types behind `unstable-msc2545` (`image_pack.rs`, `RoomImagePackEventContent`, `AccountImagePackEventContent`, `ImagePackRoomsEventContent`, `PackUsage`), including the stable `m.image_pack` aliases.

## Request

A normalised query for the packs usable in a given room.

```rust
impl ClientHandle {
    /// user pack, then the room's own packs, then emote_rooms packs, deduplicated
    pub async fn image_packs(&self, room_id: Option<OwnedRoomId>) -> Result<Vec<ImagePack>, Error>;
}

pub struct ImagePack {
    pub id: String,
    pub display_name: String,
    pub avatar_url: Option<OwnedMxcUri>,
    pub source: ImagePackSource,
    pub images: Vec<PackImage>,
}

pub enum ImagePackSource {
    User,
    Room { room_id: OwnedRoomId, state_key: String },
}

pub struct PackImage {
    pub shortcode: String,
    pub url: OwnedMxcUri,
    pub body: Option<String>,
    /// image usage if set, else the pack's, else both
    pub usage: Vec<PackUsage>,
}
```

`id` only needs to be stable for a pack across calls, e.g. `user` or `room:<room_id>:<state_key>`. Serialised field names should match the rest of macltok's normalised types. Malice's web client already has the matching TypeScript shape in `web/src/lib/types.ts` (`ImagePack`, `PackImage`).

It would also help to get a change event when any of the three sources updates, so the picker doesn't go stale, either a dedicated event or folding it into the existing room/account mirror.

## Related, reactions with custom emotes (MSC4027)

A custom emote reaction uses the image's mxc URI as the `m.annotation` key, with the shortcode in `com.beeper.reaction.shortcode` so clients without the pack can show a label.

- `toggle_reaction` only takes a key. An optional shortcode would let malice send the label.
- `normalise::timeline::reactions` drops the shortcode on receipt. `Reaction` could carry `shortcode: Option<String>`, taken from any sender's annotation for that key.

Malice relaxed its own 32-char key limit for `mxc://` keys, so sending already works without the shortcode.
