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)
75 lines
2.6 KiB
Python
75 lines
2.6 KiB
Python
"""Discord UI modals for Wormhole (e.g. report form)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from typing import TYPE_CHECKING
|
|
|
|
import discord
|
|
|
|
from ..utils import ok_embed, warn_embed
|
|
|
|
if TYPE_CHECKING:
|
|
from ..core import Wormhole
|
|
|
|
|
|
class ReportModal(discord.ui.Modal, title="Report Message"):
|
|
reason = discord.ui.TextInput(
|
|
label="Reason",
|
|
style=discord.TextStyle.paragraph,
|
|
placeholder="Describe why you're reporting this message...",
|
|
required=True,
|
|
max_length=1000,
|
|
)
|
|
|
|
def __init__(self, cog: Wormhole, net_name: str, message: discord.Message) -> None:
|
|
super().__init__()
|
|
self.cog = cog
|
|
self.net_name = net_name
|
|
self.message = message
|
|
|
|
async def on_submit(self, interaction: discord.Interaction) -> None:
|
|
nets = await self.cog.config.networks()
|
|
nd = nets.get(self.net_name)
|
|
if not nd:
|
|
await interaction.response.send_message("Network not found.", ephemeral=True)
|
|
return
|
|
|
|
report_id = nd.get("report_counter", 0) + 1
|
|
report = {
|
|
"id": report_id,
|
|
"reporter_id": interaction.user.id,
|
|
"author_id": self.message.author.id,
|
|
"channel_id": self.message.channel.id,
|
|
"guild_id": self.message.guild.id if self.message.guild else None,
|
|
"message_content_hash": str(hash(self.message.content or "")),
|
|
"reason": self.reason.value,
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
"resolved": False,
|
|
"resolved_by": None,
|
|
}
|
|
|
|
async with self.cog.config.networks() as ns:
|
|
if self.net_name in ns:
|
|
ns[self.net_name].setdefault("reports", []).append(report)
|
|
ns[self.net_name]["report_counter"] = report_id
|
|
|
|
owner = self.cog.bot.get_user(nd.get("owner_id"))
|
|
if owner:
|
|
try:
|
|
await owner.send(
|
|
embed=warn_embed(
|
|
f"**Report #{report_id}** in `{self.net_name}`\n"
|
|
f"Reporter: {interaction.user} (`{interaction.user.id}`)\n"
|
|
f"Author: {self.message.author} (`{self.message.author.id}`)\n"
|
|
f"Reason: {self.reason.value}",
|
|
title="🚩 New Report",
|
|
)
|
|
)
|
|
except discord.Forbidden:
|
|
pass
|
|
|
|
await interaction.response.send_message(
|
|
embed=ok_embed(f"Report #{report_id} submitted. Staff will review it."),
|
|
ephemeral=True,
|
|
)
|