EveCogs/wormhole/MODULE_MAP.md
root 44d7f79c13 fix(wormhole): fix persistence bug and finalize v4.0.0 modular architecture
BREAKING: Removed obsolete monolithic wormhole.py (4,782 lines)
The v4.0.0 modular architecture was already implemented but the old
file was still present causing confusion.

Fixes:
- Fix context menu cleanup in cog_unload (add type parameter)
- Remove obsolete wormhole.py monolith (archived as .old)
- Context menus now properly unregister on reload

Architecture:
- core.py (731 lines) - main cog class
- commands/ (14 modules, 2,645 lines) - command mixins
- listeners/ (3 modules) - event handlers
- models/ (3 modules) - data structures
- services/ (1 module) - business logic
- ui/ (2 modules) - Discord UI components

Documentation:
+ ARCHITECTURE.md - design overview and history
+ MODULE_MAP.md - developer quick reference

This fixes the "already loaded" phantom state bug where wormhole
wouldn't persist through restarts and required double-loading.

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
2026-05-26 16:26:03 +00:00

7.8 KiB

Wormhole Module Map

Quick Reference: Where to Find Code

Core & Entry

File Purpose Key Contents
__init__.py Red entry point setup() function
core.py Main cog class Wormhole class, __init__(), _init(), helper methods
utils.py Shared utilities Embed builders, formatters, detectors

Commands (all in commands/)

Module Commands Description
_base.py wh + subgroups Root command group definitions
network.py create, delete, info, list Network CRUD
settings.py set, mode, rate-limit, slowmode Network configuration
moderation.py freeze, ban, kick, mute, purge Moderation tools
staff.py promote, demote, staff-list, invite-create Staff management
filters.py filter add/remove, automod enable/disable Content filtering
social.py starboard, karma, profile, highlight Social features
dm.py dm-relay, dm-send DM relay
mentions.py mentions set/status/exempt Mention policy
tos.py tos enable/set/accepted Terms of Service
reports.py report message/list/resolve User reports
bridge.py bridge add/remove/list Network bridging
advanced.py poll, afk, autoreply, bookmark Advanced features
debug.py debug, trace, health, analytics Debugging tools

Listeners (all in listeners/)

Module Events Description
relay.py on_message, on_message_delete Main relay pipeline
sync.py on_message_edit, on_reaction_add/remove Edit/delete/reaction sync
misc.py on_guild_join, on_member_join Miscellaneous events

Models (all in models/)

Module Contents Purpose
config.py DEFAULT_GLOBAL, DEFAULT_NETWORK Config schemas
message_map.py MessageMap class Message ID tracking
permissions.py Role enum, role helpers Staff hierarchy

Services (all in services/)

Module Functions Purpose
emoji.py resolve_foreign_emojis(), build_emoji_embeds_and_files() Emoji relay

UI (all in ui/)

Module Classes Purpose
modals.py ReportModal Report submission form
views.py reply_jump_view() Reply jump buttons

Command Flow Example

User runs: [p]wh create mynetwork

  1. Red routes to wh command group (defined in commands/_base.py)
  2. Red dispatches to create subcommand
  3. NetworkCommands.wh_create() in commands/network.py executes
  4. Method accesses self.config (initialized in core.py.__init__())
  5. Calls self._log() helper (defined in core.py)
  6. Sends response embed using ok_embed() from utils.py

Relay Flow Example

User sends message in linked channel:

  1. RelayListener.on_message() in listeners/relay.py fires
  2. Checks self._ready.wait() (initialized in core.py)
  3. Fetches network data via self._net() (defined in core.py)
  4. Checks filters, automod, cooldowns (state in core.py)
  5. Resolves foreign emojis via services/emoji.py
  6. Builds relay embed with utils.py helpers
  7. Gets webhook from self.webhook_cache (in core.py)
  8. Sends to all linked channels
  9. Updates self.msg_map (MessageMap from models/message_map.py)

Inheritance Hierarchy

commands.Cog
    ↑
Wormhole (core.py)
    ↑ inherits from all:
    ├── RelayListener (listeners/relay.py)
    ├── SyncListener (listeners/sync.py)
    ├── MiscListener (listeners/misc.py)
    ├── NetworkCommands (commands/network.py)
    ├── SettingsCommands (commands/settings.py)
    ├── ModerationCommands (commands/moderation.py)
    ├── StaffCommands (commands/staff.py)
    ├── FilterCommands (commands/filters.py)
    ├── SocialCommands (commands/social.py)
    ├── DMCommands (commands/dm.py)
    ├── AdvancedCommands (commands/advanced.py)
    ├── MentionCommands (commands/mentions.py)
    ├── ToSCommands (commands/tos.py)
    ├── ReportCommands (commands/reports.py)
    ├── BridgeCommands (commands/bridge.py)
    └── DebugCommands (commands/debug.py)
         ↑ all inherit from
         WormholeBase (commands/_base.py)

State Management

All mutable state lives in core.py:

class Wormhole(...):
    def __init__(self, bot):
        self.bot = bot
        self.config = Config.get_conf(...)
        
        # Caches
        self.webhook_cache: Dict[int, discord.Webhook] = {}
        
        # Rate limiting
        self.cooldowns: Dict[str, CooldownBucket] = {}
        self.slowmode_tracker: Dict[str, Dict[int, float]] = {}
        
        # Anti-abuse
        self.dup_detectors: Dict[str, DuplicateDetector] = {}
        self.raid_detectors: Dict[str, RaidDetector] = {}
        
        # Message tracking
        self.msg_map = MessageMap()  # from models/message_map.py
        
        # Background tasks
        self._ready = asyncio.Event()
        self._bg_tasks: List[asyncio.Task] = []
        self._ctx_menus: List[app_commands.ContextMenu] = []

All command/listener mixins access this state via self.<attribute>.


Adding New Features

New Command Category

  1. Create commands/newcategory.py:
from ._base import WormholeBase

class NewCategoryCommands(WormholeBase):
    @WormholeBase.wh.group(name="newcat", invoke_without_command=True)
    async def wh_newcat(self, ctx):
        """New category commands."""
        await ctx.send_help(ctx.command)
    
    @wh_newcat.command(name="something")
    async def wh_newcat_something(self, ctx):
        """Do something."""
        await ctx.send("Done!")
  1. Export in commands/__init__.py:
from .newcategory import NewCategoryCommands
__all__ = [..., "NewCategoryCommands"]
  1. Inherit in core.py:
class Wormhole(
    ...,
    NewCategoryCommands,  # ← add here
    commands.Cog,
):

New Listener

  1. Add to existing listeners/*.py or create new:
class NewListener:
    @commands.Cog.listener()
    async def on_some_event(self, ...):
        """Handle event."""
        ...
  1. Export in listeners/__init__.py
  2. Inherit in core.py

New Service

  1. Create services/myservice.py:
async def do_something(param: str) -> str:
    """Service logic."""
    return result
  1. Import in modules that need it:
from ..services.myservice import do_something

File Size Reference

Total: ~145 KB of code (excluding old monolith)

Largest modules:
  core.py:           32 KB  (731 lines)
  listeners/relay.py: 23 KB  (main relay logic)
  commands/settings.py: 19 KB  (352 lines)
  commands/advanced.py: 21 KB  (411 lines)
  utils.py:          18 KB  (shared utilities)

Average command module: 8-12 KB (100-200 lines)
Average listener: 2-23 KB (50-600 lines)
Average model: 2-12 KB (50-300 lines)

Checklist: Before Committing Changes

  • Run python3 -m py_compile wormhole/**/*.py (no syntax errors)
  • Check imports in __init__.py files are up-to-date
  • Verify core.py inherits from all mixins
  • Test the changed command/listener in Discord
  • Update this map if you added/removed modules
  • Update ARCHITECTURE.md if structure changed

Quick Grep Patterns

# Find all commands
grep -r "@.*\.command" wormhole/commands/

# Find all listeners
grep -r "@commands.Cog.listener" wormhole/listeners/

# Find where a helper method is defined
grep -rn "def _method_name" wormhole/

# Find all uses of config
grep -r "self.config" wormhole/ | grep -v ".pyc"

# Find all webhook operations
grep -r "webhook" wormhole/ --include="*.py"

# Find message relay code
grep -r "relay" wormhole/listeners/