fix(wormhole): use inline links for foreign emojis instead of file attachments

File attachments render as massive images in Discord. Replace the
download-and-attach approach with clickable [:name:](cdn_url) inline
links — keeps foreign emojis compact and inline with the message text.

Removes aiohttp dependency and simplifies the helper to synchronous.
Bumps to v3.4.5.
This commit is contained in:
Viktor 2026-05-14 15:53:01 +00:00
parent 424403cf4f
commit f74e9b7897

View file

@ -34,8 +34,9 @@ Phase 5.3: Enhanced reply buttons — relayed replies now include a
copy of the referenced message (if available in the target
server) or falls back to the original message URL.
Phase 5.4: Foreign emoji relay — custom emojis unknown to the bot are
replaced with :name: and their 64 px image is attached as a
file so users still see the emoji visually.
replaced with clickable [:name:](cdn_url) inline links so
they stay compact instead of rendering as massive file
attachments.
"""
from __future__ import annotations
@ -290,43 +291,25 @@ def _reply_jump_view(url: str) -> discord.ui.View:
return view
_CUSTOM_EMOJI_RE = re.compile(r'<(a?):(\w+):(\d+)>')
_EMOJI_IMAGE_LIMIT = 5 # max emoji images to attach per message
async def _resolve_foreign_emojis(
bot, content: str
) -> tuple[str, list[tuple[str, bytes]]]:
"""Replace custom emojis the bot can't see with :name: and fetch images.
def _resolve_foreign_emojis(bot, content: str) -> str:
"""Replace custom emojis the bot can't see with clickable :name: links.
Returns (new_content, [(filename, image_bytes), ...]).
Known emojis (from any server the bot is in) are left untouched.
Unknown emojis become [:name:](cdn_url) — a clickable inline link
that lets users view the emoji image without bloating the message.
"""
unknown: list[tuple[str, int, str]] = [] # (name, id, ext)
def _check(m: re.Match) -> str:
animated, name, eid = m.group(1), m.group(2), int(m.group(3))
if bot.get_emoji(eid):
return m.group(0) # known emoji — keep original syntax
ext = "gif" if animated else "png"
unknown.append((name, eid, ext))
return f":{name}:"
url = f"https://cdn.discordapp.com/emojis/{eid}.{ext}?size=48"
return f"[:{name}:]({url})"
new_content = _CUSTOM_EMOJI_RE.sub(_check, content)
# Download images from Discord CDN (already public URLs, no auth needed)
emoji_data: list[tuple[str, bytes]] = []
if unknown:
import aiohttp
async with aiohttp.ClientSession() as session:
for name, eid, ext in unknown[:_EMOJI_IMAGE_LIMIT]:
try:
url = f"https://cdn.discordapp.com/emojis/{eid}.{ext}?size=64"
async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as resp:
if resp.status == 200:
emoji_data.append((f"{name}.{ext}", await resp.read()))
except Exception:
pass # best-effort — skip if CDN unreachable
return new_content, emoji_data
return _CUSTOM_EMOJI_RE.sub(_check, content)
_AUDIT_LIMIT = 500
@ -437,7 +420,7 @@ class _ReportModal(discord.ui.Modal, title="Report Message"):
class Wormhole(commands.Cog):
"""The ultimate cross-server relay: networks, DMs, starboard, auto-mod, invites, portals & more."""
__version__ = "3.4.4"
__version__ = "3.4.5"
def __init__(self, bot: Red):
self.bot = bot
@ -4312,12 +4295,10 @@ class Wormhole(commands.Cog):
extra_embeds = [e for e in message.embeds if e.type == "rich"]
# ── Resolve foreign emojis ─────────────────────────────────────────
# If any custom emojis in the content are unknown to the bot, replace
# them with :name: and download the image from Discord CDN so it can
# be attached as a small file.
emoji_file_data: list[tuple[str, bytes]] = []
# Unknown custom emojis become clickable [:name:](cdn_url) links so
# they stay inline and don't bloat the message as large file attachments.
if content:
content, emoji_file_data = await _resolve_foreign_emojis(self.bot, content)
content = _resolve_foreign_emojis(self.bot, content)
# ── Relay to channels ──────────────────────────────────────────────
mapping: Dict[int, int] = {}
@ -4348,8 +4329,6 @@ class Wormhole(commands.Cog):
for a in message.attachments:
try: files.append(await a.to_file())
except: pass
for fname, fdata in emoji_file_data:
files.append(discord.File(io.BytesIO(fdata), filename=fname))
send_content = content if content else None
if not send_content and not files and not extra_embeds:
send_content = "*[empty message]*"
@ -4370,8 +4349,6 @@ class Wormhole(commands.Cog):
for a in message.attachments:
try: files2.append(await a.to_file())
except: pass
for fname, fdata in emoji_file_data:
files2.append(discord.File(io.BytesIO(fdata), filename=fname))
send_content = content if content else None
if not send_content and not files2 and not extra_embeds:
send_content = "*[empty message]*"
@ -4406,8 +4383,6 @@ class Wormhole(commands.Cog):
for a in message.attachments:
try: files.append(await a.to_file())
except: pass
for fname, fdata in emoji_file_data:
files.append(discord.File(io.BytesIO(fdata), filename=fname))
sent_msg = await ch.send(content=compact_format(g, display, content), files=files or None, **_view_kw)
mapping[ch_id] = sent_msg.id