Keeping the 4,782-line v3.4.6 monolith as wormhole.py.old for:
- Historical reference
- Rollback safety
- Migration verification
This file is NOT loaded by the cog - it's purely archival.
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
- Mirror channels now receive relayed messages (was blocked from sending but never sent to)
- Server allowlist enforced in relay (allowlist_servers gate check)
- Silent mode suppresses status broadcasts (_status respects 'silent' flag)
- Welcome message sent when channel joins via 'wh open'
Commands now show as 'wh create', 'wh staff add', 'wh set relaymode'
instead of 'wh-create', 'wh-staff add', 'wh-set relaymode'.
- Added commands/_base.py with root 'wh' group and all 20 subgroup stubs
- All 13 command mixins inherit from WormholeBase
- Decorators changed: @commands.hybrid_command(name='wh-X') → @WormholeBase.wh.command(name='X')
- Subgroup commands: @wh_X.command() → @WormholeBase.wh_X.command()
- Removed duplicate 'wh' group from core.py (now in _base.py)
- Fixed alias: 'wh-color' → 'color' for colour command
The hybrid_command decorator on mixin classes already auto-registers
slash commands. The manual registrations in _register_context_menus
clashed, causing 'wh-list already registered' on load. Removed all
manual slash commands — only context menus remain.
Embed mode was missing foreign emoji support. Now all three relay
modes (webhook, embed, compact) show unknown emojis as embed
thumbnails via attachment:// scheme.
File attachments render as massive images in Discord regardless of
source size. Switch to embed thumbnails via the attachment:// scheme:
1. Download emoji from CDN at 48px
2. Attach as a File to the message
3. Reference via attachment://name.png in a minimal embed's thumbnail
4. Thumbnails render at ~80×80 px — much closer to emoji size
Fresh File/Embed objects are built per channel (consumed on send).
Bumps to v3.4.6.
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.
Custom emojis the bot can't see (bot.get_emoji returns None) are now:
1. Replaced with :name: in message text
2. Downloaded from Discord CDN at 64px
3. Attached as small image files to the relay message
Known emojis (from any server the bot is in) keep the <:name:id>
syntax and render normally. Up to 5 emoji images per message.
Works in webhook and compact relay modes. Bumps to v3.4.4.
The forward map only stores relay targets — the source channel (where
the original message was typed) was never included. When someone in
Server B replied to a relayed message, the button for Server A fell
back to the fallback URL pointing at Server B instead of Server A.
Fix: after building the relay-target URLs, identify the source channel
by finding network channels not present in the forward map, and map
them to the original message ID.
Passing view=None to wh.send() on every message caused all relays
to fail. Now uses **_view_kw dict unpacking so the view parameter
is only included when there is actually a reply-jump button to show.
When a reply is relayed across servers, each copy now includes a URL
button underneath the quoted reply text:
- If the referenced message has a relayed copy in the target channel,
the button links to that local copy (so users stay in their own server).
- Otherwise, it falls back to the original message URL.
Works across all relay modes (webhook, embed, compact) and leverages
the existing msg_map to resolve per-channel message IDs.
Bumps version to 3.4.3.
- Migration now uses a _mention_migrated flag so it only forces mentions
off once. After that, admins can freely toggle per-network settings
with 'wh mentions set <network> <type> <true/false>'
- New mention_optout_users list per network: users can opt out of being
@pinged through the relay even when user mentions are enabled
network-wide. Their <@ID> mentions are stripped; everyone else's pass
through. Default: opted in.
- New commands:
• wh mentions optout <network> — stop receiving relay pings
• wh mentions optin <network> — start receiving relay pings again
• wh mentions mystatus <network> — check your current ping status
- apply_mention_policy() now accepts optout_users set and selectively
strips only opted-out users' mentions when user mentions are globally
allowed
- Updated 'wh mentions status' to show opt-out count and correct
defaults
- On cog load, all existing networks are migrated to disable every
mention type (user, role, everyone, here) — changing the default
only affected new networks, not existing ones
- Reverted the v3.4.2 'and ctx.prefix' guard in the command filter —
the real relay bug was the regex crash, not the empty prefix.
ctx.valid alone correctly identifies registered commands even with
an empty-string prefix
- Default mention_policy now disables all mention types including user
mentions (previously allow_user_mentions defaulted to True)
- Blocked mentions are now removed entirely instead of replaced with
ZWSP placeholders — '@mention HI!' becomes 'HI!' on the other side
- Trailing whitespace from removed mentions is cleaned up
- Updated both apply_mention_policy() and sanitise_mentions()
Root cause: apply_mention_policy() in utils.py used raw strings for
re.sub replacement arguments:
re.sub(r'<@&(\d+)>', r'@\u200brole', content)
The r-prefix makes \u200b literal backslash-u instead of Unicode ZWSP.
Python's re.sub always validates the replacement template, so \u at
position 1 raises re.error on EVERY message — even without matches.
Since _DEFAULT_NETWORK sets mention_policy.allow_role_mentions=False,
this code path runs for all networks, causing _do_relay to throw on
every single message. The exception was caught by on_message's
try/except, silently killing relay with total_messages stuck at 0.
Fix: remove r-prefix from replacement strings so \u200b is correctly
interpreted as the Unicode zero-width space character.
Added three diagnostic tools to help debug relay issues:
- wh relaydebug: static check of all on_message gates against the
current channel — reports which checks pass/fail
- wh testsend [text]: bypasses on_message entirely and sends a test
message directly through the webhook to verify connectivity
- wh tracemode: toggles live tracing for the channel — when enabled,
every message produces a diagnostic embed showing exactly which
gate-check blocked it or that the relay succeeded
Also added trace checkpoints at every early-return in on_message so
tracemode can pinpoint the exact check that blocks a message.
The v3.4.1 command filter used bot.get_context() to detect commands,
but an empty-string prefix ('') matched EVERY message, causing
ctx.valid to return True for any message whose first word happened
to be a registered command. With enough built-in commands (help,
ping, info, set, etc.) this silently dropped most or all relay
traffic (total_messages stayed at 0).
Fix: require ctx.prefix to be truthy (non-empty) before skipping
a message as a command. Empty-prefix matches are now ignored,
so only messages with a real prefix (!, @bot, etc.) are filtered.
Also re-added the empty-prefix warning to 'wh debug' output so
users know to clean up their bot prefix configuration.
Discord enforces a max of 25 subcommands per slash command group.
Wormhole has 50 children on the main group and 39 on wh set, making
hybrid_group impossible.
Fix: wh is now a regular prefix group, with 7 standalone slash commands
registered for the most common user-facing operations:
/wormhole-list, /wormhole-info, /wormhole-discover,
/wormhole-accept, /wormhole-agree, /wormhole-report,
/wormhole-profile
All 4 context menus still work (Report, Bookmark, Delete, Profile).
All 85+ prefix commands still work via [p]wh.
get_relayed() now checks reverse map as fallback, so passing any
relayed copy's message ID resolves to the full mapping.
mod edit and mod nuke both call get_original() first, so staff can
right-click or copy ANY copy of a message and it deletes/edits the
entire chain across all servers.
Phase 5 — Security, Privacy & Power Update:
HYBRID COMMANDS
- Main command group converted to hybrid_group (slash + prefix)
- All commands work as both /wh and [p]wh
CONTEXT MENU ACTIONS (right-click)
- Report to Wormhole — opens modal for report reason
- Wormhole Bookmark — saves message to DM
- Wormhole Delete — staff can delete from entire network
- Wormhole Profile — view user stats across networks
GRANULAR MENTION POLICY
- Per-network control: allow/block @user, @role, @everyone, @here
- Per-server overrides (different rules per guild)
- Per-user exemptions (trusted users bypass policy)
- Replaces legacy strip-* with proper policy engine
- wh mentions set/server-set/exempt/unexempt/status
TERMS OF SERVICE / RULES ACCEPTANCE GATE
- wh tos enable — require acceptance before messaging
- Built-in legal template (6 sections, prosecution-ready)
- Consent tracking with timestamps
- wh tos set (custom text), template (reset), accepted (view), reset (force re-accept)
- wh accept / wh agree — user-facing acceptance flow
- Blocks relay for non-accepted users with helpful reminder
MOD EDIT/DELETE ACROSS NETWORK
- wh mod edit <name> <msg_id> <new_content> — edits all relayed copies
- wh mod nuke <name> <msg_id> — deletes from every server + original
- Both audit-logged
REPORT SYSTEM
- wh report message — report via reply or message ID
- Context menu report with modal (reason input)
- Anti-abuse cooldown (60s between reports)
- Content hashing for audit trails
- Reports sent to log channel + network owner DM
- wh report list/resolve/action (ban/mute/warn/dismiss)
- Capped at 200 reports per network
PRIVACY & SECURITY
- _ReportModal class for context menu reporting
- Content hash stored instead of plaintext in report records
- Report cooldowns prevent abuse
- ToS acceptance with ISO timestamps for legal compliance
4,196 lines in wormhole.py, 419 in utils.py — 85+ commands total