BREAKING: Complete cog restructure from monolithic wormhole.py into modular architecture. ## Architecture - models/permissions.py — Role IntEnum (MEMBER→OWNER), requires_role() decorator - models/config.py — DEFAULT_NETWORK/DEFAULT_GLOBAL with 80+ config keys - models/message_map.py — MessageMap class (extracted from _MessageMap) - services/emoji.py — Foreign emoji resolution and relay - ui/views.py — Reply jump button view - ui/modals.py — Report modal - core.py — Main Wormhole class, helpers, background loops, context menus - commands/ — 13 command modules as mixin classes - listeners/ — relay pipeline, edit/delete/reaction sync, typing/pin sync ## Staff System (new) Per-network hierarchical roles replacing flat owner/staff: Bot Owner (Red built-in) — global override └─ Network Owner → Admin → Moderator → Helper → Member Each role assigned per-network. Higher roles can assign lower. Legacy staff_ids auto-migrated to ADMIN role. ## Enhanced Moderation - Server-level ban/mute (not just user-level) - Allowlist mode (only whitelisted servers can send) - Mod edit and nuke (delete N messages by user) - Anti-raid detector with auto-freeze - Expanded automod: anti-zalgo, anti-emote-spam, anti-newline-spam ## Commands (100+) network, settings, staff, moderation, filters, social, dm, mentions, tos, reports, bridge, advanced (polls/afk/auto-responses/scheduled/ analytics/quiet-hours/colours/invites/announcements/blackout/portal), debug (health/trace/testsend/relaydebug/backup/restore/version) ## Files - 30 Python files, 5,274 lines total (was 4,782 in monolith) - Original wormhole.py preserved for reference - utils.py unchanged (all helpers reused)
54 lines
2 KiB
Python
54 lines
2 KiB
Python
"""
|
|
In-memory bidirectional message map for tracking relayed messages.
|
|
|
|
Maps original message IDs ↔ relayed copies across channels within a network.
|
|
Capped at MAP_LIMIT entries per direction to bound memory usage.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections import OrderedDict
|
|
from typing import Dict, List, Optional
|
|
|
|
from .config import MAP_LIMIT
|
|
|
|
|
|
class MessageMap:
|
|
"""Bidirectional map: original_id ↔ {channel_id: relayed_msg_id}.
|
|
|
|
``forward[net][original_id] = {ch_id: relayed_id, ...}``
|
|
``reverse[net][relayed_id] = original_id``
|
|
"""
|
|
|
|
def __init__(self) -> None:
|
|
self.forward: Dict[str, OrderedDict[int, Dict[int, int]]] = {}
|
|
self.reverse: Dict[str, OrderedDict[int, int]] = {}
|
|
|
|
def add(self, network: str, original_id: int, mapping: Dict[int, int]) -> None:
|
|
"""Record that *original_id* was relayed to *mapping* channels."""
|
|
fwd = self.forward.setdefault(network, OrderedDict())
|
|
rev = self.reverse.setdefault(network, OrderedDict())
|
|
|
|
fwd[original_id] = mapping
|
|
for relayed_id in mapping.values():
|
|
rev[relayed_id] = original_id
|
|
|
|
# Prune oldest entries
|
|
while len(fwd) > MAP_LIMIT:
|
|
_old_orig, old_map = fwd.popitem(last=False)
|
|
for rid in old_map.values():
|
|
rev.pop(rid, None)
|
|
while len(rev) > MAP_LIMIT * 2:
|
|
rev.popitem(last=False)
|
|
|
|
def get_relayed(self, network: str, original_id: int) -> Dict[int, int]:
|
|
"""Get ``{ch_id: relayed_id}`` for an original message."""
|
|
return self.forward.get(network, {}).get(original_id, {})
|
|
|
|
def get_original(self, network: str, relayed_id: int) -> Optional[int]:
|
|
"""Get the original message ID from a relayed copy ID."""
|
|
return self.reverse.get(network, {}).get(relayed_id)
|
|
|
|
def get_all_relayed_ids(self, network: str, original_id: int) -> List[int]:
|
|
"""Get all relayed message IDs for an original."""
|
|
return list(self.get_relayed(network, original_id).values())
|