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.
1. CRITICAL: Permission overwrites now properly merge instead of replace.
set_permissions with keyword args REPLACES the entire overwrite,
so Lock→Hide would undo the Lock. Now uses overwrites_for() to
fetch existing state and modify only the target field.
Affects: Lock, Unlock, Hide, Unhide, Ghost, Reveal (buttons & commands)
2. CRITICAL: Removed broken async generator in any() for game rename check.
await in generator expression creates async generator that any() can't
consume. Game rename loop now always starts and checks config per-guild.
3. Minor: Fixed double-write in _save_temp_channels.
4. Minor: Removed unused imports (datetime, Set).
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
- CV2 HelpPaginatorView: Close button in its own ActionRow,
category Select in a separate ActionRow (Discord requires
selects to be alone in their row)
- Cap overview embed to 20 category fields to stay under
Discord's 25-field limit
Major rewrite of help rendering:
- Embed mode (default): Rich embeds with interactive View buttons
(⏮◀️▶️⏭) + category select dropdown + close button
- CV2 mode (when `cv2 toggle` is on): Components V2 containers
with the same navigation
- Shared data-gathering layer: both modes use the same logic to
gather commands, filter permissions, handle categories
- All Red help features: command filtering, permission checks,
aliases, subcommands, cooldowns, format_help_for_context
- Text fallback for DMs
- Help command now always tries CV2 in guilds (that's its purpose)
with text fallback only on error or in DMs
- cv2 group invoke no longer calls ctx.send_help() which relied
on Red's internal formatter — sends usage text directly
- Context.send calls Messageable.send internally, both patched —
use asyncio task ID set to prevent re-entry loop
- Text fallback for help now paginates at 1900 chars to stay
under Discord's 2000-char limit
The classic Red/discord.py pattern: bot.remove_command('help') in
__init__.py's setup() BEFORE bot.add_cog(), so the cog's own
@commands.command(name='help') decorator doesn't clash with Red's
built-in help.
Original help command stored on cog instance and restored in cog_unload.
- Removes Red's built-in help command on cog load, restores on unload
- Our help command fully replaces it (CV2 when enabled, text fallback for DMs)
- Monkey-patches both Context.send AND Messageable.send so ALL bot embeds
everywhere (channels, threads, DMs) get auto-converted to CV2 containers
- Per-cog overrides still respected for Context sends
- Clean restore of everything on cog unload
Replaces embed-based help with full Components V2 system:
- converter.py: Embed → Container conversion engine
- formatter.py: CV2 help page builder + paginator views
- newhelpmenu.py: Main cog with settings, monkey-patching, public API
- 1,747 lines across 4 files, default off