platforms.emoji_resolver module

Resolve custom emojis from Discord and Matrix into image Attachments.

Custom emojis are platform-specific rich content that arrives as text tokens (Discord <:name:id>) or HTML <img> tags (Matrix). This module extracts them from message text, downloads the images, and returns Attachment objects so the LLM can see the emojis as inline PNG images.

Both Discord and Matrix adapters call into these shared utilities from their on_message handlers.

class platforms.emoji_resolver.DiscordEmojiMatch(name, emoji_id, animated, full_match)[source]

Bases: object

An immutable record of one custom emoji parsed from Discord text.

Captures everything needed to both download a custom emoji’s image and rewrite the original <:name:id> (or animated <a:name:id>) token in the message body. Instances are produced by extract_discord_emojis() (which deduplicates by id) and consumed by rewrite_discord_emoji_text() and _download_one_discord_emoji(); being frozen, they are safe to share across the parallel download fan-out.

Parameters:
name: str

The emoji’s short name (e.g. "blobwave").

emoji_id: str

The numeric Discord emoji id used to build the CDN URL.

animated: bool

True for animated emojis (<a:...>), selecting a .gif rather than .png download.

full_match: str

The original token text as it appeared in the message, used for the in-place text replacement.

platforms.emoji_resolver.extract_discord_emojis(text)[source]

Find the unique custom emojis in a message, in first-seen order.

Scans the text for Discord custom-emoji tokens (<:name:id> and the animated <a:name:id> form) using DISCORD_EMOJI_RE and returns one DiscordEmojiMatch per distinct emoji id, so a repeated emoji is downloaded and rewritten only once. This is the entry point of the resolve-emojis-as-images flow, telling the caller both what to fetch and what to rewrite. Pure string processing with no I/O.

Called by the Discord bot adapter (platforms/discord.py) and the selfbot adapter (platforms/discord_self.py) in their on_message paths when resolve_emojis_as_images is enabled; also exercised by tests/test_emoji_resolver.py.

Parameters:

text (str) – The raw message body to scan for custom-emoji tokens.

Returns:

One match per unique emoji id, ordered by first appearance; empty when the text contains no custom emojis.

Return type:

list[DiscordEmojiMatch]

platforms.emoji_resolver.rewrite_discord_emoji_text(text, matches)[source]

Swap raw custom-emoji tokens for readable [emoji: name] placeholders.

Rewrites the message body so that the opaque <:name:id> tokens become human- and LLM-legible [emoji: name] markers, which is paired with attaching the emoji images: the model reads the name in context while also seeing the actual picture as an inline attachment. Replaces by exact token text (full_match) so only the emojis the caller chose to resolve are touched. Pure string processing with no I/O.

Called by the Discord bot adapter (platforms/discord.py) and the selfbot adapter (platforms/discord_self.py) right after their emoji images are downloaded; also covered by tests/test_emoji_resolver.py.

Parameters:
  • text (str) – The message body to rewrite.

  • matches (list[DiscordEmojiMatch]) – The emojis to replace (typically the subset that was actually downloaded as images).

Returns:

The text with each matched token replaced by [emoji: name].

Return type:

str

async platforms.emoji_resolver.download_discord_emojis(matches, *, max_emojis=5, media_cache=None)[source]

Download up to max_emojis Discord custom emojis concurrently.

Caps the work at max_emojis (to bound how many images a single message can pull) and fetches the chosen emojis in parallel, returning only the ones that succeeded so a few bad emojis never sink the batch. This is the download half of the resolve-emojis-as-images flow whose matches come from extract_discord_emojis().

Fans out one _download_one_discord_emoji() task per emoji via asyncio.gather() (with return_exceptions=True so a failure is isolated, not raised), threading the optional media_cache through to each so cached emoji bytes are reused. Called by the Discord bot adapter (platforms/discord.py) and the selfbot adapter (platforms/discord_self.py) when resolve_emojis_as_images is enabled; no internal callers.

Parameters:
  • matches (list[DiscordEmojiMatch]) – Candidate emojis to download (usually from extract_discord_emojis()).

  • max_emojis (int) – Maximum number of emojis to fetch from the front of matches (default 5).

  • media_cache (Any | None) – Optional cache exposing get_or_download to dedupe repeated downloads.

Returns:

The successfully downloaded emoji attachments; failures and the over-the-limit tail are omitted.

Return type:

list[Attachment]

class platforms.emoji_resolver.MatrixEmojiMatch(alt_text, mxc_url, full_tag)[source]

Bases: object

An immutable record of one custom emoji parsed from Matrix HTML.

Captures what is needed to both download a Matrix custom emoji and rewrite its shortcode in the plain-text body. Instances are produced by extract_matrix_emojis() (deduplicated by mxc:// URL) and consumed by rewrite_matrix_emoji_text() and download_matrix_emojis(); being frozen, they are safe to share across the parallel download fan-out.

Parameters:
  • alt_text (str)

  • mxc_url (str)

  • full_tag (str)

alt_text: str

The alt text of the source <img> tag (often a :shortcode:), used both as the rewrite label and to derive a filename.

mxc_url: str

The mxc:// content URI used to download the image from the Matrix homeserver.

full_tag: str

The complete original <img ...> tag as it appeared in the formatted body.

platforms.emoji_resolver.extract_matrix_emojis(formatted_body)[source]

Find the unique custom emojis embedded in a Matrix HTML body.

Scans a Matrix message’s formatted_body for inline emoji <img> tags whose src is an mxc:// URI (using _MATRIX_EMOJI_IMG_RE), pulling the alt text from each (falling back to "emoji") and deduplicating by URI so each emoji is fetched and rewritten once. This is the Matrix-side entry point of the resolve-emojis-as-images flow. Pure string processing with no I/O.

Called by the Matrix adapter (platforms/matrix.py) in its message path when emoji resolution is enabled; also exercised by tests/test_emoji_resolver.py.

Parameters:

formatted_body (str) – The Matrix HTML formatted_body to scan.

Returns:

One match per unique mxc:// URL, ordered by first appearance; empty when no emoji images are present.

Return type:

list[MatrixEmojiMatch]

platforms.emoji_resolver.rewrite_matrix_emoji_text(body, matches)[source]

Swap Matrix emoji shortcodes in the body for [emoji: name] markers.

Rewrites the plain-text body so the legible emoji name survives next to the inline image attachment shown to the LLM. Matrix clients typically put the shortcode (e.g. :wave:) in the plain-text body, so this strips the surrounding colons from each emoji’s alt text and wraps it in descriptive bracket notation, replacing only the first occurrence of each shortcode. Pure string processing with no I/O.

Called by the Matrix adapter (platforms/matrix.py) after its emoji images are downloaded; also covered by tests/test_emoji_resolver.py.

Parameters:
  • body (str) – The plain-text message body to rewrite.

  • matches (list[MatrixEmojiMatch]) – The emojis whose shortcodes should be replaced.

Returns:

The body with each matched shortcode replaced by [emoji: name]; unchanged when no shortcode is present.

Return type:

str

async platforms.emoji_resolver.download_matrix_emojis(matches, matrix_client, *, max_emojis=5, media_cache=None)[source]

Download up to max_emojis Matrix custom emojis concurrently.

Caps the work at max_emojis and fetches the chosen emojis in parallel from the Matrix homeserver, returning only those that succeeded so a few failures never sink the batch. This is the download half of the Matrix resolve-emojis-as-images flow whose matches come from extract_matrix_emojis().

Fans out the inner _download_one coroutine per emoji via asyncio.gather() (with return_exceptions=True), where each task fetches media through the matrix-nio AsyncClient.download() method against the homeserver’s mxc:// URIs and wraps the bytes in an Attachment. The optional media_cache is threaded through so previously fetched emoji bytes are reused on a hit. Imports nio.responses.DownloadError to detect download failures. Called by the Matrix adapter (platforms/matrix.py) when emoji resolution is enabled; no internal callers.

Parameters:
  • matches (list[MatrixEmojiMatch]) – Candidate emojis to download (usually from extract_matrix_emojis()).

  • matrix_client (Any) – The matrix-nio AsyncClient used to fetch media from the homeserver.

  • max_emojis (int) – Maximum number of emojis to fetch from the front of matches (default 5).

  • media_cache (Any | None) – Optional cache exposing get_or_download to dedupe repeated downloads.

Returns:

The successfully downloaded emoji attachments; failures and the over-the-limit tail are omitted.

Return type:

list[Attachment]