Emotes
Overview
The emotes module fetches, caches, and parses chat emotes from multiple providers across platforms. It supports both channel-specific emotes (fetched per platform + channel ID) and user-specific emotes (Twitch subscriber emotes for the authenticated user). Emotes are used in chat message rendering and overlay alerts.
Architecture
Dashboard UI / Overlay
|
v
Next.js API Proxy (/api/emotes)
|
v
REST API (GET /v1/emotes/{platform}/{channel_id}, GET /v1/emotes/user)
|
+--> Redis cache check
| Hit? --> Return cached EmoteSet[]
|
+--> Fetch from provider APIs (7TV, FFZ, BTTV, Twitch, etc.)
|
+--> Cache result in Redis
|
v
EmoteSet[] response
Emote Providers
| Provider | Enum Value | Platforms | Description |
|---|---|---|---|
| Twitch | twitch | Twitch | Native Twitch emotes (global + channel) |
| 7TV | 7tv | Twitch, YouTube, Kick | Third-party emote service |
| FrankerFaceZ | ffz | Twitch | Third-party emote extension |
| BetterTTV | bttv | Twitch, YouTube, Kick | Third-party emote extension |
| Discord | discord | Discord | Guild custom emojis |
| YouTube | youtube | YouTube | YouTube emojis from chat |
| Kick | kick | Kick | Kick platform emotes |
| Trovo | trovo | Trovo | Trovo platform emotes |
Per-Platform Fetch Strategy
| Platform | Providers Fetched |
|---|---|
| Twitch | 7TV (channel), FFZ (channel), BTTV (channel), FFZ (global) |
| YouTube | 7TV (channel), BTTV (channel), FFZ (global) |
| Kick | 7TV (channel), BTTV (channel), FFZ (global) |
| Trovo | Trovo (native, requires client_id), 7TV (global), BTTV (global), FFZ (global) |
| Discord | Discord guild emojis (requires bot token) |
Empty sets are filtered out -- only providers with at least one emote are included in the response.
API
REST Endpoints
| Method | Path | Description | Auth |
|---|---|---|---|
GET | /v1/emotes/{platform}/{channel_id} | Fetch channel emotes | Authenticated |
GET | /v1/emotes/user | Fetch user's Twitch subscriber emotes | Authenticated |
GET /v1/emotes/{platform}/{channel_id}
Fetches all available emote sets for a given platform and channel. Results are cached in Redis.
Response:
{
"data": {
"sets": [
{
"provider": "7tv",
"emotes": [
{
"id": "60ae958e...",
"name": "LULW",
"url": "https://cdn.7tv.app/emote/.../1x.webp",
"provider": "7tv",
"animated": false
}
]
}
],
"owners": null
}
}
GET /v1/emotes/user
Fetches all Twitch emotes available to the authenticated user (subscriber emotes from all channels). Requires a Twitch login connection with user:read:emotes scope. Automatically refreshes the token if expired.
Also resolves emote owner information (channel names and avatars) via batch Twitch user lookup.
Response includes:
sets-- Array ofEmoteSetwith the user's available emotesowners-- Map of owner IDs to{ displayName, profileImageUrl }for grouping emotes by channel
Types
EmoteSet
struct EmoteSet {
provider: EmoteProvider, // twitch, 7tv, ffz, bttv, discord, youtube, kick, trovo
emotes: Vec<Emote>,
}
Emote
struct Emote {
id: String, // Provider-specific emote ID
name: String, // Emote name (e.g., "Kappa", "LULW")
url: String, // CDN URL for the emote image
provider: EmoteProvider, // Source provider
animated: bool, // Whether the emote is animated (GIF/WEBP)
owner_id: Option<String>, // Owner user/channel ID (Twitch-specific)
}
Message Parsing
The parse_emotes() function splits a chat message into segments of text and emotes:
fn parse_emotes(message: &str, emote_sets: &[EmoteSet]) -> Vec<EmotePart>
Behavior:
- Builds a name-to-emote lookup from all provided sets
- Splits message on whitespace boundaries
- Exact, case-sensitive matching only (
Kappamatches,kappadoes not,KappaRossdoes not) - Consecutive text words are merged into a single
EmotePart::Text - Returns
Vec<EmotePart>where each part is eitherText { text }orEmote { id, name, url, provider }
Caching
Emote sets are cached in Redis per platform + channel ID. The cache is checked before making any external API calls. Cache keys follow the pattern used by emote_service::get_cached_emotes / cache_emotes.
User emotes are cached separately under twitch-user:user:{user_id} with owner data cached under lumio:emotes:owners:{user_id} (1 hour TTL).
Key Files
| File | Purpose |
|---|---|
apps/api/src/routes/emotes.rs | REST endpoints for channel and user emotes |
apps/api/src/services/emotes.rs | Provider fetch functions, caching, owner resolution |
crates/lo-chat/src/emotes.rs | Core types (Emote, EmoteSet, EmoteProvider, EmotePart) and parse_emotes() |