Skip to main content

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

ProviderEnum ValuePlatformsDescription
TwitchtwitchTwitchNative Twitch emotes (global + channel)
7TV7tvTwitch, YouTube, KickThird-party emote service
FrankerFaceZffzTwitch, YouTubeThird-party emote extension
BetterTTVbttvTwitch, YouTube, KickThird-party emote extension
DiscorddiscordDiscordGuild custom emojis
YouTubeyoutubeYouTubeYouTube native chat emojis (~100 standard shortcodes such as :yt:, :hand-pink-waving:)
KickkickKickKick platform emotes
TrovotrovoTrovoTrovo platform emotes

Per-Platform Fetch Strategy

PlatformProviders Fetched
Twitch7TV (channel), FFZ (channel), BTTV (channel), FFZ (global)
YouTubeYouTube (native standard emojis), 7TV (channel), BTTV (channel), FFZ (channel + global)
Kick7TV (channel), BTTV (channel), FFZ (global)
TrovoTrovo (native, requires client_id), 7TV (global), BTTV (global), FFZ (global)
DiscordDiscord guild emojis (requires bot token)

Empty sets are filtered out -- only providers with at least one emote are included in the response.

Provider Channel-Lookup URLs

ProviderTwitchYouTube
7TVhttps://7tv.io/v3/users/twitch/{channel_id}https://7tv.io/v3/users/youtube/{channel_id}
BTTVhttps://api.betterttv.net/3/cached/users/twitch/{channel_id}https://api.betterttv.net/3/cached/users/youtube/{channel_id}
FFZhttps://api.frankerfacez.com/v1/room/id/{channel_id}https://api.frankerfacez.com/v1/room/yt/{channel_id}

Note on FFZ: while 7TV/BTTV use a /users/... URL pattern, FFZ uses /room/.... Both are equivalent in semantics — they return the channel-active emote set. FFZ's /user/... endpoint returns user-profile data (uploaded sets, badges) and is not what the chat renders.

API

REST Endpoints

MethodPathDescriptionAuth
GET/v1/emotes/{platform}/{channel_id}Fetch channel emotesAuthenticated
GET/v1/emotes/userFetch user's Twitch subscriber emotesAuthenticated

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 of EmoteSet with the user's available emotes
  • owners -- 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 (Kappa matches, kappa does not, KappaRoss does not)
  • Consecutive text words are merged into a single EmotePart::Text
  • Returns Vec<EmotePart> where each part is either Text { text } or Emote { id, name, url, provider }

YouTube Standard Emojis

YouTube Live Chat ships with ~100 platform-native standard emojis (e.g. :yt:, :hand-pink-waving:, :dothefive:). YouTube does not expose an API for the catalogue, so the list is curated manually in crates/lo-chat/src/youtube_emotes.rs and embedded in the REST/GraphQL response for platform = "youtube" as an additional EmoteSet with provider = "youtube". The set is pushed first in the response so the frontend's first-write-wins emote map gives YouTube native emojis priority over any 7TV/BTTV/FFZ entry that happens to share a shortcode.

Sending: YouTube Live Chat renders :shortcode: tokens server-side, so chat messages sent through Lumio with YouTube shortcodes are forwarded as plain text in messageText and YouTube turns them into emoji images on delivery. No client-side conversion is required.

Rendering: Incoming YouTube messages keep the raw shortcode in message_text. The MessageContent renderer (apps/web/src/app/(app)/dashboard/chat/message-content.tsx) and the lighter EmoteText component split each whitespace-token by the shortcode regex :[a-zA-Z0-9_-]+: and replace runs of back-to-back shortcodes (e.g. :hand-pink-waving::hand-pink-waving:) with separate <img> elements separated by visible spaces.

Adding new emojis: Append a (shortcode, cdn_url) tuple to YOUTUBE_STANDARD_EMOTES in crates/lo-chat/src/youtube_emotes.rs and rebuild the API binary.

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

FilePurpose
apps/api/src/routes/emotes.rsREST endpoints for channel and user emotes
apps/api/src/services/emotes.rsProvider fetch functions, caching, owner resolution
crates/lo-chat/src/emotes.rsCore types (Emote, EmoteSet, EmoteProvider, EmotePart) and parse_emotes()
crates/lo-chat/src/youtube_emotes.rsStatic catalogue of YouTube native standard-emoji shortcodes
apps/web/src/app/(app)/dashboard/chat/message-content.tsxMultichat renderer with shortcode-splitting + colon-fallback
apps/web/src/app/(app)/dashboard/chat/emote-library.tsxEmote picker with platform tabs and the youtube-global section