diff --git a/wormhole/commands/__init__.py b/wormhole/commands/__init__.py index 940dc99..e66b775 100644 --- a/wormhole/commands/__init__.py +++ b/wormhole/commands/__init__.py @@ -1,5 +1,6 @@ """Command modules — each is a mixin class inherited by the main Wormhole cog.""" +from ._base import WormholeBase from .advanced import AdvancedCommands from .bridge import BridgeCommands from .debug import DebugCommands @@ -15,6 +16,7 @@ from .staff import StaffCommands from .tos import ToSCommands __all__ = [ + "WormholeBase", "AdvancedCommands", "BridgeCommands", "DebugCommands", diff --git a/wormhole/commands/_base.py b/wormhole/commands/_base.py new file mode 100644 index 0000000..ee04fe9 --- /dev/null +++ b/wormhole/commands/_base.py @@ -0,0 +1,128 @@ +""" +Root ``wh`` command group and subgroup stubs. + +Every command mixin inherits from :class:`WormholeBase` so that +all subcommands end up registered under the same ``wh`` parent. +""" + +from __future__ import annotations + +from redbot.core import commands + + +class WormholeBase: + """Defines the ``wh`` group and every subgroup stub. + + Subgroups are defined here (with ``invoke_without_command=True``) so + each mixin can reference them as ``@WormholeBase.wh_xxx.command(…)``. + """ + + # ── Root ─────────────────────────────────────────────────────────────── + + @commands.group(name="wh", aliases=["wormhole"], invoke_without_command=True) + async def wh(self, ctx: commands.Context) -> None: + """Wormhole — the ultimate cross-server relay.""" + if ctx.invoked_subcommand is None: + await ctx.send_help(ctx.command) + + # ── Subgroups (alphabetical) ─────────────────────────────────────────── + + @wh.group(name="ar", aliases=["autoreply"], invoke_without_command=True) + async def wh_ar(self, ctx: commands.Context) -> None: + """Auto-response triggers.""" + await ctx.send_help(ctx.command) + + @wh.group(name="blackout", invoke_without_command=True) + async def wh_blackout(self, ctx: commands.Context) -> None: + """Scheduled freeze windows.""" + await ctx.send_help(ctx.command) + + @wh.group(name="bm", aliases=["bookmark"], invoke_without_command=True) + async def wh_bm(self, ctx: commands.Context) -> None: + """Message bookmarks.""" + await ctx.send_help(ctx.command) + + @wh.group(name="bridge", invoke_without_command=True) + async def wh_bridge(self, ctx: commands.Context) -> None: + """Cross-network bridging.""" + await ctx.send_help(ctx.command) + + @wh.group(name="debug", invoke_without_command=True) + async def wh_debug(self, ctx: commands.Context) -> None: + """Debug and diagnostic tools.""" + await ctx.send_help(ctx.command) + + @wh.group(name="dm", invoke_without_command=True) + async def wh_dm(self, ctx: commands.Context) -> None: + """DM relay — receive network messages in your DMs.""" + await ctx.send_help(ctx.command) + + @wh.group(name="filter", invoke_without_command=True) + async def wh_filter(self, ctx: commands.Context) -> None: + """Manage content filters and automod.""" + await ctx.send_help(ctx.command) + + @wh.group(name="hl", aliases=["highlight"], invoke_without_command=True) + async def wh_hl(self, ctx: commands.Context) -> None: + """Keyword highlight notifications.""" + await ctx.send_help(ctx.command) + + @wh.group(name="invite", invoke_without_command=True) + async def wh_invite(self, ctx: commands.Context) -> None: + """Network invite codes.""" + await ctx.send_help(ctx.command) + + @wh.group(name="karma", invoke_without_command=True) + async def wh_karma(self, ctx: commands.Context) -> None: + """Karma system commands.""" + await ctx.send_help(ctx.command) + + @wh.group(name="mention", aliases=["mentions"], invoke_without_command=True) + async def wh_mention(self, ctx: commands.Context) -> None: + """Configure mention behaviour.""" + await ctx.send_help(ctx.command) + + @wh.group(name="mirror", invoke_without_command=True) + async def wh_mirror(self, ctx: commands.Context) -> None: + """Mirror channels — exact copy of relay to an extra channel.""" + await ctx.send_help(ctx.command) + + @wh.group(name="mod", invoke_without_command=True) + async def wh_mod(self, ctx: commands.Context) -> None: + """Network moderation tools.""" + await ctx.send_help(ctx.command) + + @wh.group(name="poll", invoke_without_command=True) + async def wh_poll(self, ctx: commands.Context) -> None: + """Network-wide polls.""" + await ctx.send_help(ctx.command) + + @wh.group(name="report", invoke_without_command=True) + async def wh_report(self, ctx: commands.Context) -> None: + """Network report system.""" + await ctx.send_help(ctx.command) + + @wh.group(name="rules", aliases=["tos", "terms"], invoke_without_command=True) + async def wh_rules(self, ctx: commands.Context) -> None: + """Network rules / Terms of Service.""" + await ctx.send_help(ctx.command) + + @wh.group(name="schedule", invoke_without_command=True) + async def wh_schedule(self, ctx: commands.Context) -> None: + """Scheduled messages.""" + await ctx.send_help(ctx.command) + + @wh.group(name="set", invoke_without_command=True) + async def wh_set(self, ctx: commands.Context) -> None: + """Configure network settings.""" + await ctx.send_help(ctx.command) + + @wh.group(name="staff", invoke_without_command=True) + async def wh_staff(self, ctx: commands.Context) -> None: + """Manage network staff — add, remove, list, promote, demote.""" + await ctx.send_help(ctx.command) + + @wh.group(name="star", aliases=["starboard"], invoke_without_command=True) + async def wh_star(self, ctx: commands.Context) -> None: + """Starboard commands.""" + await ctx.send_help(ctx.command) diff --git a/wormhole/commands/advanced.py b/wormhole/commands/advanced.py index 4e858f0..1f4ceef 100644 --- a/wormhole/commands/advanced.py +++ b/wormhole/commands/advanced.py @@ -15,19 +15,15 @@ from ..utils import ( ok_embed, err_embed, info_embed, warn_embed, announce_embed, truncate, generate_invite_code, human_timedelta, COLOUR_INFO, ) +from ._base import WormholeBase -class AdvancedCommands: +class AdvancedCommands(WormholeBase): """Mixin — advanced features.""" # ── Polls ────────────────────────────────────────────────────────────── - @commands.group(name="wh-poll", aliases=["whpoll"], invoke_without_command=True) - async def wh_poll(self, ctx: commands.Context) -> None: - """Network-wide polls.""" - await ctx.send_help(ctx.command) - - @wh_poll.command(name="create") + @WormholeBase.wh_poll.command(name="create") @requires_role(Role.MODERATOR) async def wh_poll_create(self, ctx: commands.Context, name: str, duration_minutes: int, question: str, *, options: str) -> None: """Create a poll. Options separated by ``|``. @@ -66,7 +62,7 @@ class AdvancedCommands: pass await ctx.send(embed=ok_embed(f"Poll `{poll_id}` created in `{name}`.")) - @wh_poll.command(name="vote") + @WormholeBase.wh_poll.command(name="vote") async def wh_poll_vote(self, ctx: commands.Context, name: str, poll_id: str, choice: int) -> None: """Vote in a poll (choice = option number, 1-based).""" nd = await self._net(name) @@ -88,7 +84,7 @@ class AdvancedCommands: p.setdefault("votes", {}).setdefault(idx, []).append(uid) await ctx.send(embed=ok_embed(f"Voted for **{poll['options'][choice-1]}**."), delete_after=5) - @wh_poll.command(name="results") + @WormholeBase.wh_poll.command(name="results") async def wh_poll_results(self, ctx: commands.Context, name: str, poll_id: str) -> None: """View current poll results.""" nd = await self._net(name) @@ -100,7 +96,7 @@ class AdvancedCommands: results = self._format_poll_results(poll) await ctx.send(embed=info_embed(results, title=f"📊 {poll['question']}")) - @wh_poll.command(name="end") + @WormholeBase.wh_poll.command(name="end") @requires_role(Role.MODERATOR) async def wh_poll_end(self, ctx: commands.Context, name: str, poll_id: str) -> None: """End a poll early.""" @@ -124,7 +120,7 @@ class AdvancedCommands: # ── AFK ──────────────────────────────────────────────────────────────── - @commands.hybrid_command(name="wh-afk") + @WormholeBase.wh.command(name="afk") async def wh_afk(self, ctx: commands.Context, name: str, *, reason: str = "AFK") -> None: """Set your AFK status in a network.""" nd = await self._net(name) @@ -139,12 +135,7 @@ class AdvancedCommands: # ── Auto-responses ───────────────────────────────────────────────────── - @commands.group(name="wh-ar", aliases=["whar"], invoke_without_command=True) - async def wh_ar(self, ctx: commands.Context) -> None: - """Auto-response triggers.""" - await ctx.send_help(ctx.command) - - @wh_ar.command(name="add") + @WormholeBase.wh_ar.command(name="add") @requires_role(Role.ADMIN) async def wh_ar_add(self, ctx: commands.Context, name: str, trigger: str, *, reply: str) -> None: """Add an auto-response. Use ``regex:pattern`` for regex triggers.""" @@ -165,7 +156,7 @@ class AdvancedCommands: } await ctx.send(embed=ok_embed(f"Auto-response added for `{trigger}`.")) - @wh_ar.command(name="remove", aliases=["rm"]) + @WormholeBase.wh_ar.command(name="remove", aliases=["rm"]) @requires_role(Role.ADMIN) async def wh_ar_rm(self, ctx: commands.Context, name: str, *, trigger: str) -> None: """Remove an auto-response.""" @@ -173,7 +164,7 @@ class AdvancedCommands: ns[name].get("auto_responses", {}).pop(trigger, None) await ctx.send(embed=ok_embed(f"Auto-response removed for `{trigger}`.")) - @wh_ar.command(name="list") + @WormholeBase.wh_ar.command(name="list") @requires_role(Role.HELPER) async def wh_ar_list(self, ctx: commands.Context, name: str) -> None: """List auto-responses.""" @@ -191,12 +182,7 @@ class AdvancedCommands: # ── Scheduled messages ───────────────────────────────────────────────── - @commands.group(name="wh-schedule", aliases=["whschedule"], invoke_without_command=True) - async def wh_schedule(self, ctx: commands.Context) -> None: - """Scheduled messages.""" - await ctx.send_help(ctx.command) - - @wh_schedule.command(name="add") + @WormholeBase.wh_schedule.command(name="add") @requires_role(Role.ADMIN) async def wh_schedule_add(self, ctx: commands.Context, name: str, minutes_from_now: int, *, content: str) -> None: """Schedule a message to be sent in N minutes.""" @@ -209,7 +195,7 @@ class AdvancedCommands: }) await ctx.send(embed=ok_embed(f"Message scheduled for {minutes_from_now} minutes from now.")) - @wh_schedule.command(name="list") + @WormholeBase.wh_schedule.command(name="list") @requires_role(Role.HELPER) async def wh_schedule_list(self, ctx: commands.Context, name: str) -> None: """List scheduled messages.""" @@ -224,7 +210,7 @@ class AdvancedCommands: lines.append(f"**{i}.** {sm.get('send_at_iso', '?')[:16]} — {truncate(sm.get('content', ''), 60)}") await ctx.send(embed=info_embed("\n".join(lines), title=f"📅 Scheduled — {name}")) - @wh_schedule.command(name="clear") + @WormholeBase.wh_schedule.command(name="clear") @requires_role(Role.ADMIN) async def wh_schedule_clear(self, ctx: commands.Context, name: str) -> None: """Clear all scheduled messages.""" @@ -234,7 +220,7 @@ class AdvancedCommands: # ── Analytics ────────────────────────────────────────────────────────── - @commands.hybrid_command(name="wh-analytics") + @WormholeBase.wh.command(name="analytics") @requires_role(Role.HELPER) async def wh_analytics(self, ctx: commands.Context, name: str) -> None: """View network analytics dashboard.""" @@ -269,7 +255,7 @@ class AdvancedCommands: # ── Quiet hours ──────────────────────────────────────────────────────── - @commands.hybrid_command(name="wh-quiet") + @WormholeBase.wh.command(name="quiet") async def wh_quiet(self, ctx: commands.Context, name: str, start_hour: int, end_hour: int, tz_offset: int = 0) -> None: """Set quiet hours — DM relay pauses during this window. @@ -285,7 +271,7 @@ class AdvancedCommands: } await ctx.send(embed=ok_embed(f"Quiet hours set: {start_hour}:00–{end_hour}:00 (UTC{tz_offset:+d})")) - @commands.hybrid_command(name="wh-unquiet") + @WormholeBase.wh.command(name="unquiet") async def wh_unquiet(self, ctx: commands.Context, name: str) -> None: """Remove your quiet hours.""" async with self.config.networks() as ns: @@ -294,7 +280,7 @@ class AdvancedCommands: # ── User colours ─────────────────────────────────────────────────────── - @commands.hybrid_command(name="wh-colour", aliases=["wh-color"]) + @WormholeBase.wh.command(name="colour", aliases=["color"]) async def wh_colour(self, ctx: commands.Context, name: str, hex_code: str = None) -> None: """Set your name colour in embed mode (hex, e.g. ``#ff5733``).""" if hex_code: @@ -311,12 +297,7 @@ class AdvancedCommands: # ── Invites ──────────────────────────────────────────────────────────── - @commands.group(name="wh-invite", aliases=["whinvite"], invoke_without_command=True) - async def wh_invite(self, ctx: commands.Context) -> None: - """Network invite codes.""" - await ctx.send_help(ctx.command) - - @wh_invite.command(name="create") + @WormholeBase.wh_invite.command(name="create") @requires_role(Role.ADMIN) async def wh_invite_create(self, ctx: commands.Context, name: str, max_uses: int = 0) -> None: """Create an invite code (``max_uses`` 0 = unlimited).""" @@ -330,7 +311,7 @@ class AdvancedCommands: } await ctx.send(embed=ok_embed(f"Invite code: `{code}`\nMax uses: {'unlimited' if max_uses == 0 else max_uses}")) - @wh_invite.command(name="vanity") + @WormholeBase.wh_invite.command(name="vanity") @requires_role(Role.OWNER) async def wh_invite_vanity(self, ctx: commands.Context, name: str, code: str) -> None: """Set a vanity invite code.""" @@ -340,7 +321,7 @@ class AdvancedCommands: ns[name]["vanity_invite"] = code await ctx.send(embed=ok_embed(f"Vanity invite set: `{code}`")) - @wh_invite.command(name="list") + @WormholeBase.wh_invite.command(name="list") @requires_role(Role.ADMIN) async def wh_invite_list(self, ctx: commands.Context, name: str) -> None: """List all invite codes.""" @@ -360,7 +341,7 @@ class AdvancedCommands: lines.append(f"\n✨ Vanity: `{vanity}`") await ctx.send(embed=info_embed("\n".join(lines), title=f"🔗 Invites — {name}")) - @wh_invite.command(name="revoke") + @WormholeBase.wh_invite.command(name="revoke") @requires_role(Role.ADMIN) async def wh_invite_revoke(self, ctx: commands.Context, name: str, code: str) -> None: """Revoke an invite code.""" @@ -370,7 +351,7 @@ class AdvancedCommands: # ── Announcements ────────────────────────────────────────────────────── - @commands.hybrid_command(name="wh-announce") + @WormholeBase.wh.command(name="announce") @requires_role(Role.ADMIN) async def wh_announce(self, ctx: commands.Context, name: str, *, message: str) -> None: """Broadcast an announcement to all channels in the network.""" @@ -392,12 +373,7 @@ class AdvancedCommands: # ── Blackout schedules ───────────────────────────────────────────────── - @commands.group(name="wh-blackout", aliases=["whblackout"], invoke_without_command=True) - async def wh_blackout(self, ctx: commands.Context) -> None: - """Scheduled freeze windows.""" - await ctx.send_help(ctx.command) - - @wh_blackout.command(name="add") + @WormholeBase.wh_blackout.command(name="add") @requires_role(Role.ADMIN) async def wh_blackout_add(self, ctx: commands.Context, name: str, start_hour: int, end_hour: int, *, days: str = "0,1,2,3,4,5,6") -> None: """Add a blackout schedule. Days: 0=Mon … 6=Sun.""" @@ -410,7 +386,7 @@ class AdvancedCommands: }) await ctx.send(embed=ok_embed(f"Blackout added: {start_hour}:00–{end_hour}:00 UTC on days {day_list}")) - @wh_blackout.command(name="clear") + @WormholeBase.wh_blackout.command(name="clear") @requires_role(Role.ADMIN) async def wh_blackout_clear(self, ctx: commands.Context, name: str) -> None: """Clear all blackout schedules.""" @@ -420,7 +396,7 @@ class AdvancedCommands: # ── Portal embed ─────────────────────────────────────────────────────── - @commands.hybrid_command(name="wh-portal") + @WormholeBase.wh.command(name="portal") @requires_role(Role.ADMIN) async def wh_portal(self, ctx: commands.Context, name: str) -> None: """Post a self-updating portal embed in this channel.""" diff --git a/wormhole/commands/bridge.py b/wormhole/commands/bridge.py index 6cb8f09..231b19e 100644 --- a/wormhole/commands/bridge.py +++ b/wormhole/commands/bridge.py @@ -7,17 +7,13 @@ from redbot.core import commands from ..models.permissions import Role, requires_role from ..utils import ok_embed, err_embed, info_embed, COLOUR_INFO +from ._base import WormholeBase -class BridgeCommands: +class BridgeCommands(WormholeBase): """Mixin — network bridging and mirror channels.""" - @commands.group(name="wh-bridge", aliases=["whbridge"], invoke_without_command=True) - async def wh_bridge(self, ctx: commands.Context) -> None: - """Cross-network bridging.""" - await ctx.send_help(ctx.command) - - @wh_bridge.command(name="link") + @WormholeBase.wh_bridge.command(name="link") @requires_role(Role.OWNER) async def wh_bridge_link(self, ctx: commands.Context, source: str, target: str) -> None: """Bridge messages from one network to another (one-way).""" @@ -37,7 +33,7 @@ class BridgeCommands: await ctx.send(embed=ok_embed(f"Bridge: `{source}` → `{target}` (one-way).")) await self._audit(source, "bridge_link", str(ctx.author), target) - @wh_bridge.command(name="unlink") + @WormholeBase.wh_bridge.command(name="unlink") @requires_role(Role.OWNER) async def wh_bridge_unlink(self, ctx: commands.Context, source: str, target: str) -> None: """Remove a bridge.""" @@ -53,7 +49,7 @@ class BridgeCommands: await ctx.send(embed=ok_embed(f"Bridge `{source}` → `{target}` removed.")) await self._audit(source, "bridge_unlink", str(ctx.author), target) - @wh_bridge.command(name="list") + @WormholeBase.wh_bridge.command(name="list") async def wh_bridge_list(self, ctx: commands.Context, name: str) -> None: """List bridges for a network.""" nd = await self._net(name) @@ -70,12 +66,7 @@ class BridgeCommands: # ── Mirror channels ──────────────────────────────────────────────────── - @commands.group(name="wh-mirror", aliases=["whmirror"], invoke_without_command=True) - async def wh_mirror(self, ctx: commands.Context) -> None: - """Mirror channels — exact copy of relay to an extra channel.""" - await ctx.send_help(ctx.command) - - @wh_mirror.command(name="add") + @WormholeBase.wh_mirror.command(name="add") @requires_role(Role.ADMIN) async def wh_mirror_add(self, ctx: commands.Context, name: str, channel: discord.TextChannel) -> None: """Add a mirror channel.""" @@ -85,7 +76,7 @@ class BridgeCommands: mirrors.append(channel.id) await ctx.send(embed=ok_embed(f"{channel.mention} added as mirror for `{name}`.")) - @wh_mirror.command(name="remove", aliases=["rm"]) + @WormholeBase.wh_mirror.command(name="remove", aliases=["rm"]) @requires_role(Role.ADMIN) async def wh_mirror_rm(self, ctx: commands.Context, name: str, channel: discord.TextChannel) -> None: """Remove a mirror channel.""" @@ -95,7 +86,7 @@ class BridgeCommands: mirrors.remove(channel.id) await ctx.send(embed=ok_embed(f"{channel.mention} removed from mirrors for `{name}`.")) - @wh_mirror.command(name="list") + @WormholeBase.wh_mirror.command(name="list") async def wh_mirror_list(self, ctx: commands.Context, name: str) -> None: """List mirror channels.""" nd = await self._net(name) diff --git a/wormhole/commands/debug.py b/wormhole/commands/debug.py index 497016b..6a816e0 100644 --- a/wormhole/commands/debug.py +++ b/wormhole/commands/debug.py @@ -12,17 +12,13 @@ from redbot.core import checks, commands from ..models.permissions import Role, has_role, requires_role from ..utils import ok_embed, err_embed, info_embed, warn_embed, COLOUR_INFO, human_timedelta +from ._base import WormholeBase -class DebugCommands: +class DebugCommands(WormholeBase): """Mixin — debug, diagnostics, backup/restore.""" - @commands.group(name="wh-debug", aliases=["whdebug"], invoke_without_command=True) - async def wh_debug(self, ctx: commands.Context) -> None: - """Debug and diagnostic tools.""" - await ctx.send_help(ctx.command) - - @wh_debug.command(name="health") + @WormholeBase.wh_debug.command(name="health") @requires_role(Role.HELPER) async def wh_debug_health(self, ctx: commands.Context, name: str) -> None: """Check network health — permissions, webhooks, channel status.""" @@ -62,7 +58,7 @@ class DebugCommands: em.set_footer(text=f"{healthy}/{len(channels)} healthy · Last check: {nd.get('last_health_check', 'never')[:16]}") await ctx.send(embed=em) - @wh_debug.command(name="trace") + @WormholeBase.wh_debug.command(name="trace") @requires_role(Role.ADMIN) async def wh_debug_trace(self, ctx: commands.Context) -> None: """Toggle trace mode for this channel — logs all relay decisions.""" @@ -73,7 +69,7 @@ class DebugCommands: self._trace_channels.add(ctx.channel.id) await ctx.send(embed=ok_embed("Trace mode **enabled** — relay decisions will be logged here.")) - @wh_debug.command(name="testsend") + @WormholeBase.wh_debug.command(name="testsend") @requires_role(Role.ADMIN) async def wh_debug_testsend(self, ctx: commands.Context, name: str) -> None: """Send a test message through the relay.""" @@ -97,7 +93,7 @@ class DebugCommands: await ctx.send(embed=warn_embed(f"Failed to send to {ch.mention}: {exc}")) await ctx.send(embed=ok_embed(f"Test message sent to {sent} channels.")) - @wh_debug.command(name="relaydebug") + @WormholeBase.wh_debug.command(name="relaydebug") @requires_role(Role.ADMIN) async def wh_debug_relaydebug(self, ctx: commands.Context, name: str) -> None: """Show detailed relay pipeline state for diagnostics.""" @@ -120,7 +116,7 @@ class DebugCommands: # ── Backup / Restore ─────────────────────────────────────────────────── - @wh_debug.command(name="backup") + @WormholeBase.wh_debug.command(name="backup") @requires_role(Role.OWNER) async def wh_debug_backup(self, ctx: commands.Context, name: str) -> None: """Export network config as a JSON file.""" @@ -139,7 +135,7 @@ class DebugCommands: file=discord.File(buf, filename=f"wormhole_{name}_backup.json"), ) - @wh_debug.command(name="restore") + @WormholeBase.wh_debug.command(name="restore") @checks.is_owner() async def wh_debug_restore(self, ctx: commands.Context, name: str) -> None: """Restore a network from a JSON backup (attach file to message).""" @@ -159,7 +155,7 @@ class DebugCommands: # ── Version ──────────────────────────────────────────────────────────── - @commands.hybrid_command(name="wh-version") + @WormholeBase.wh.command(name="version") async def wh_version(self, ctx: commands.Context) -> None: """Show Wormhole cog version.""" nets = await self.config.networks() diff --git a/wormhole/commands/dm.py b/wormhole/commands/dm.py index 2fbde3e..c630e5f 100644 --- a/wormhole/commands/dm.py +++ b/wormhole/commands/dm.py @@ -7,17 +7,13 @@ from redbot.core import commands from ..models.permissions import Role, requires_role from ..utils import ok_embed, err_embed, info_embed, dm_embed, truncate +from ._base import WormholeBase -class DMCommands: +class DMCommands(WormholeBase): """Mixin — DM relay management.""" - @commands.group(name="wh-dm", aliases=["whdm"], invoke_without_command=True) - async def wh_dm(self, ctx: commands.Context) -> None: - """DM relay — receive network messages in your DMs.""" - await ctx.send_help(ctx.command) - - @wh_dm.command(name="enable") + @WormholeBase.wh_dm.command(name="enable") @requires_role(Role.ADMIN) async def wh_dm_enable(self, ctx: commands.Context, name: str, enabled: bool = True) -> None: """Enable or disable DM relay for a network.""" @@ -25,7 +21,7 @@ class DMCommands: ns[name]["dm_enabled"] = enabled await ctx.send(embed=ok_embed(f"DM relay {'enabled' if enabled else 'disabled'} for `{name}`.")) - @wh_dm.command(name="subscribe", aliases=["sub"]) + @WormholeBase.wh_dm.command(name="subscribe", aliases=["sub"]) async def wh_dm_sub(self, ctx: commands.Context, name: str) -> None: """Subscribe to receive network messages via DM.""" nd = await self._net(name) @@ -40,7 +36,7 @@ class DMCommands: subs.append(ctx.author.id) await ctx.send(embed=ok_embed(f"Subscribed to DM relay for `{name}`.")) - @wh_dm.command(name="unsubscribe", aliases=["unsub"]) + @WormholeBase.wh_dm.command(name="unsubscribe", aliases=["unsub"]) async def wh_dm_unsub(self, ctx: commands.Context, name: str) -> None: """Unsubscribe from DM relay.""" async with self.config.networks() as ns: @@ -49,7 +45,7 @@ class DMCommands: subs.remove(ctx.author.id) await ctx.send(embed=ok_embed(f"Unsubscribed from DM relay for `{name}`.")) - @wh_dm.command(name="mode") + @WormholeBase.wh_dm.command(name="mode") @requires_role(Role.ADMIN) async def wh_dm_mode(self, ctx: commands.Context, name: str, mode: str) -> None: """Set DM relay mode: ``embed``, ``compact``, or ``plain``.""" @@ -59,7 +55,7 @@ class DMCommands: ns[name]["dm_relay_mode"] = mode await ctx.send(embed=ok_embed(f"DM relay mode set to **{mode}** for `{name}`.")) - @wh_dm.command(name="send") + @WormholeBase.wh_dm.command(name="send") async def wh_dm_send(self, ctx: commands.Context, name: str, *, message: str) -> None: """Send a message to the network from your DMs.""" nd = await self._net(name) @@ -83,7 +79,7 @@ class DMCommands: pass await ctx.send(embed=ok_embed("Message sent to the network.")) - @wh_dm.command(name="ignore") + @WormholeBase.wh_dm.command(name="ignore") async def wh_dm_ignore(self, ctx: commands.Context, name: str, user: discord.User) -> None: """Ignore a user in DM relay (you won't see their messages).""" async with self.config.networks() as ns: @@ -94,7 +90,7 @@ class DMCommands: user_ignores.append(user.id) await ctx.send(embed=ok_embed(f"{user.display_name} ignored in DM relay for `{name}`.")) - @wh_dm.command(name="unignore") + @WormholeBase.wh_dm.command(name="unignore") async def wh_dm_unignore(self, ctx: commands.Context, name: str, user: discord.User) -> None: """Stop ignoring a user in DM relay.""" async with self.config.networks() as ns: diff --git a/wormhole/commands/filters.py b/wormhole/commands/filters.py index 430e8aa..603ce66 100644 --- a/wormhole/commands/filters.py +++ b/wormhole/commands/filters.py @@ -7,19 +7,15 @@ from redbot.core import commands from ..models.permissions import Role, requires_role from ..utils import ok_embed, err_embed, info_embed, COLOUR_INFO +from ._base import WormholeBase -class FilterCommands: +class FilterCommands(WormholeBase): """Mixin — content filters and automod configuration.""" - @commands.group(name="wh-filter", aliases=["whfilter"], invoke_without_command=True) - async def wh_filter(self, ctx: commands.Context) -> None: - """Manage content filters and automod.""" - await ctx.send_help(ctx.command) - # ── Word filters ─────────────────────────────────────────────────────── - @wh_filter.command(name="addword") + @WormholeBase.wh_filter.command(name="addword") @requires_role(Role.MODERATOR) async def wh_filter_addword(self, ctx: commands.Context, name: str, *, word: str) -> None: """Add a word to the filter list.""" @@ -30,7 +26,7 @@ class FilterCommands: await ctx.send(embed=ok_embed(f"Word `{word}` added to filters for `{name}`.")) await self._audit(name, "filter_add", str(ctx.author), word) - @wh_filter.command(name="rmword") + @WormholeBase.wh_filter.command(name="rmword") @requires_role(Role.MODERATOR) async def wh_filter_rmword(self, ctx: commands.Context, name: str, *, word: str) -> None: """Remove a word from the filter list.""" @@ -39,7 +35,7 @@ class FilterCommands: ns[name]["word_filters"] = [w for w in wf if w.lower() != word.lower()] await ctx.send(embed=ok_embed(f"Word `{word}` removed from filters for `{name}`.")) - @wh_filter.command(name="addregex") + @WormholeBase.wh_filter.command(name="addregex") @requires_role(Role.ADMIN) async def wh_filter_addregex(self, ctx: commands.Context, name: str, *, pattern: str) -> None: """Add a regex pattern to the filter list.""" @@ -55,7 +51,7 @@ class FilterCommands: await ctx.send(embed=ok_embed(f"Regex `{pattern}` added to filters for `{name}`.")) await self._audit(name, "regex_add", str(ctx.author), pattern) - @wh_filter.command(name="rmregex") + @WormholeBase.wh_filter.command(name="rmregex") @requires_role(Role.ADMIN) async def wh_filter_rmregex(self, ctx: commands.Context, name: str, *, pattern: str) -> None: """Remove a regex pattern from the filter list.""" @@ -65,7 +61,7 @@ class FilterCommands: rf.remove(pattern) await ctx.send(embed=ok_embed(f"Regex removed from filters for `{name}`.")) - @wh_filter.command(name="list") + @WormholeBase.wh_filter.command(name="list") @requires_role(Role.HELPER) async def wh_filter_list(self, ctx: commands.Context, name: str) -> None: """List all active filters.""" @@ -89,7 +85,7 @@ class FilterCommands: # ── Automod ──────────────────────────────────────────────────────────── - @wh_filter.command(name="automod") + @WormholeBase.wh_filter.command(name="automod") @requires_role(Role.ADMIN) async def wh_filter_automod(self, ctx: commands.Context, name: str, feature: str, enabled: bool) -> None: """Toggle automod features. @@ -131,7 +127,7 @@ class FilterCommands: else: self.raid_detectors.pop(name, None) - @wh_filter.command(name="automod-set") + @WormholeBase.wh_filter.command(name="automod-set") @requires_role(Role.ADMIN) async def wh_filter_automod_set(self, ctx: commands.Context, name: str, key: str, value: str) -> None: """Set an automod parameter. @@ -155,7 +151,7 @@ class FilterCommands: # ── Attachment filters ───────────────────────────────────────────────── - @wh_filter.command(name="blockext") + @WormholeBase.wh_filter.command(name="blockext") @requires_role(Role.ADMIN) async def wh_filter_blockext(self, ctx: commands.Context, name: str, ext: str) -> None: """Block a file extension (e.g. ``.exe``).""" @@ -167,7 +163,7 @@ class FilterCommands: exts.append(ext) await ctx.send(embed=ok_embed(f"Extension `{ext}` blocked for `{name}`.")) - @wh_filter.command(name="unblockext") + @WormholeBase.wh_filter.command(name="unblockext") @requires_role(Role.ADMIN) async def wh_filter_unblockext(self, ctx: commands.Context, name: str, ext: str) -> None: """Unblock a file extension.""" @@ -179,7 +175,7 @@ class FilterCommands: exts.remove(ext) await ctx.send(embed=ok_embed(f"Extension `{ext}` unblocked for `{name}`.")) - @wh_filter.command(name="maxfilesize") + @WormholeBase.wh_filter.command(name="maxfilesize") @requires_role(Role.ADMIN) async def wh_filter_maxfilesize(self, ctx: commands.Context, name: str, mb: float = 0) -> None: """Set max file size in MB (0 to disable).""" diff --git a/wormhole/commands/mentions.py b/wormhole/commands/mentions.py index 1fdb623..32180de 100644 --- a/wormhole/commands/mentions.py +++ b/wormhole/commands/mentions.py @@ -7,17 +7,13 @@ from redbot.core import commands from ..models.permissions import Role, requires_role from ..utils import ok_embed, err_embed, info_embed, COLOUR_INFO +from ._base import WormholeBase -class MentionCommands: +class MentionCommands(WormholeBase): """Mixin — mention policy management.""" - @commands.group(name="wh-mention", aliases=["whmention"], invoke_without_command=True) - async def wh_mention(self, ctx: commands.Context) -> None: - """Configure mention behaviour.""" - await ctx.send_help(ctx.command) - - @wh_mention.command(name="set") + @WormholeBase.wh_mention.command(name="set") @requires_role(Role.ADMIN) async def wh_mention_set(self, ctx: commands.Context, name: str, kind: str, allowed: bool) -> None: """Set a mention policy. @@ -38,7 +34,7 @@ class MentionCommands: await ctx.send(embed=ok_embed(f"`{kind}` mentions {'allowed' if allowed else 'blocked'} in `{name}`.")) await self._audit(name, f"mention_{kind}", str(ctx.author), details=str(allowed)) - @wh_mention.command(name="exempt") + @WormholeBase.wh_mention.command(name="exempt") @requires_role(Role.ADMIN) async def wh_mention_exempt(self, ctx: commands.Context, name: str, user: discord.User) -> None: """Exempt a user from mention restrictions.""" @@ -48,7 +44,7 @@ class MentionCommands: exempt.append(user.id) await ctx.send(embed=ok_embed(f"{user.mention} exempted from mention restrictions in `{name}`.")) - @wh_mention.command(name="unexempt") + @WormholeBase.wh_mention.command(name="unexempt") @requires_role(Role.ADMIN) async def wh_mention_unexempt(self, ctx: commands.Context, name: str, user: discord.User) -> None: """Remove a user's mention exemption.""" @@ -58,7 +54,7 @@ class MentionCommands: exempt.remove(user.id) await ctx.send(embed=ok_embed(f"{user.mention} exemption removed in `{name}`.")) - @wh_mention.command(name="optout") + @WormholeBase.wh_mention.command(name="optout") async def wh_mention_optout(self, ctx: commands.Context, name: str) -> None: """Opt out of being pinged via relay.""" async with self.config.networks() as ns: @@ -67,7 +63,7 @@ class MentionCommands: optout.append(ctx.author.id) await ctx.send(embed=ok_embed("You've opted out of relay pings.")) - @wh_mention.command(name="optin") + @WormholeBase.wh_mention.command(name="optin") async def wh_mention_optin(self, ctx: commands.Context, name: str) -> None: """Opt back in to relay pings.""" async with self.config.networks() as ns: @@ -76,7 +72,7 @@ class MentionCommands: optout.remove(ctx.author.id) await ctx.send(embed=ok_embed("You've opted back in to relay pings.")) - @wh_mention.command(name="serveroverride") + @WormholeBase.wh_mention.command(name="serveroverride") @requires_role(Role.ADMIN) async def wh_mention_serveroverride(self, ctx: commands.Context, name: str, guild_id: int, kind: str, allowed: bool) -> None: """Set per-server mention override.""" @@ -94,7 +90,7 @@ class MentionCommands: so.setdefault(gid_str, {})[key_map[kind]] = allowed await ctx.send(embed=ok_embed(f"Server override set for `{guild_id}` in `{name}`.")) - @wh_mention.command(name="show") + @WormholeBase.wh_mention.command(name="show") @requires_role(Role.HELPER) async def wh_mention_show(self, ctx: commands.Context, name: str) -> None: """Show current mention policy.""" diff --git a/wormhole/commands/moderation.py b/wormhole/commands/moderation.py index c201246..78c8171 100644 --- a/wormhole/commands/moderation.py +++ b/wormhole/commands/moderation.py @@ -10,19 +10,15 @@ from redbot.core import commands from ..models.permissions import Role, has_role, requires_role, role_name, get_role from ..utils import ok_embed, err_embed, info_embed, warn_embed, truncate, COLOUR_INFO +from ._base import WormholeBase -class ModerationCommands: +class ModerationCommands(WormholeBase): """Mixin — moderation tools (per-network, role-gated).""" - @commands.group(name="wh-mod", aliases=["whmod"], invoke_without_command=True) - async def wh_mod(self, ctx: commands.Context) -> None: - """Network moderation tools.""" - await ctx.send_help(ctx.command) - # ── User bans ────────────────────────────────────────────────────────── - @wh_mod.command(name="ban") + @WormholeBase.wh_mod.command(name="ban") @requires_role(Role.MODERATOR) async def wh_mod_ban(self, ctx: commands.Context, name: str, user: discord.User, *, reason: str = "No reason") -> None: """Ban a user from the network.""" @@ -41,7 +37,7 @@ class ModerationCommands: await self._audit(name, "ban", str(ctx.author), str(user), reason) await self._log(nd, warn_embed(f"🔨 **{user}** banned by {ctx.author}\nReason: {reason}")) - @wh_mod.command(name="unban") + @WormholeBase.wh_mod.command(name="unban") @requires_role(Role.MODERATOR) async def wh_mod_unban(self, ctx: commands.Context, name: str, user: discord.User) -> None: """Unban a user from the network.""" @@ -54,7 +50,7 @@ class ModerationCommands: # ── User mutes ───────────────────────────────────────────────────────── - @wh_mod.command(name="mute") + @WormholeBase.wh_mod.command(name="mute") @requires_role(Role.MODERATOR) async def wh_mod_mute(self, ctx: commands.Context, name: str, user: discord.User, *, reason: str = "No reason") -> None: """Mute a user in the network (messages won't relay).""" @@ -71,7 +67,7 @@ class ModerationCommands: await self._audit(name, "mute", str(ctx.author), str(user), reason) await self._log(nd, warn_embed(f"🔇 **{user}** muted by {ctx.author}\nReason: {reason}")) - @wh_mod.command(name="unmute") + @WormholeBase.wh_mod.command(name="unmute") @requires_role(Role.MODERATOR) async def wh_mod_unmute(self, ctx: commands.Context, name: str, user: discord.User) -> None: """Unmute a user in the network.""" @@ -84,7 +80,7 @@ class ModerationCommands: # ── Server bans/mutes ────────────────────────────────────────────────── - @wh_mod.command(name="banserver") + @WormholeBase.wh_mod.command(name="banserver") @requires_role(Role.ADMIN) async def wh_mod_banserver(self, ctx: commands.Context, name: str, guild_id: int) -> None: """Ban a server from the network.""" @@ -95,7 +91,7 @@ class ModerationCommands: await ctx.send(embed=ok_embed(f"Server `{guild_id}` banned from `{name}`.")) await self._audit(name, "banserver", str(ctx.author), str(guild_id)) - @wh_mod.command(name="unbanserver") + @WormholeBase.wh_mod.command(name="unbanserver") @requires_role(Role.ADMIN) async def wh_mod_unbanserver(self, ctx: commands.Context, name: str, guild_id: int) -> None: """Unban a server from the network.""" @@ -106,7 +102,7 @@ class ModerationCommands: await ctx.send(embed=ok_embed(f"Server `{guild_id}` unbanned from `{name}`.")) await self._audit(name, "unbanserver", str(ctx.author), str(guild_id)) - @wh_mod.command(name="muteserver") + @WormholeBase.wh_mod.command(name="muteserver") @requires_role(Role.ADMIN) async def wh_mod_muteserver(self, ctx: commands.Context, name: str, guild_id: int) -> None: """Mute a server in the network.""" @@ -117,7 +113,7 @@ class ModerationCommands: await ctx.send(embed=ok_embed(f"Server `{guild_id}` muted in `{name}`.")) await self._audit(name, "muteserver", str(ctx.author), str(guild_id)) - @wh_mod.command(name="unmuteserver") + @WormholeBase.wh_mod.command(name="unmuteserver") @requires_role(Role.ADMIN) async def wh_mod_unmuteserver(self, ctx: commands.Context, name: str, guild_id: int) -> None: """Unmute a server in the network.""" @@ -130,7 +126,7 @@ class ModerationCommands: # ── Allowlist ────────────────────────────────────────────────────────── - @wh_mod.command(name="allowserver") + @WormholeBase.wh_mod.command(name="allowserver") @requires_role(Role.ADMIN) async def wh_mod_allowserver(self, ctx: commands.Context, name: str, guild_id: int) -> None: """Add a server to the allowlist.""" @@ -140,7 +136,7 @@ class ModerationCommands: allow.append(guild_id) await ctx.send(embed=ok_embed(f"Server `{guild_id}` added to allowlist for `{name}`.")) - @wh_mod.command(name="removeallow") + @WormholeBase.wh_mod.command(name="removeallow") @requires_role(Role.ADMIN) async def wh_mod_removeallow(self, ctx: commands.Context, name: str, guild_id: int) -> None: """Remove a server from the allowlist.""" @@ -152,7 +148,7 @@ class ModerationCommands: # ── Purge ────────────────────────────────────────────────────────────── - @wh_mod.command(name="purge") + @WormholeBase.wh_mod.command(name="purge") @requires_role(Role.MODERATOR) async def wh_mod_purge(self, ctx: commands.Context, name: str, count: int = 10) -> None: """Purge recent relayed messages across all channels (max 50).""" @@ -180,7 +176,7 @@ class ModerationCommands: # ── Cross-network mod edit/delete ────────────────────────────────────── - @wh_mod.command(name="edit") + @WormholeBase.wh_mod.command(name="edit") @requires_role(Role.MODERATOR) async def wh_mod_edit(self, ctx: commands.Context, name: str, message_id: int, *, new_content: str) -> None: """Edit a relayed message across all channels.""" @@ -209,7 +205,7 @@ class ModerationCommands: await ctx.send(embed=ok_embed(f"Edited {edited} relayed copies.")) await self._audit(name, "mod_edit", str(ctx.author), str(orig_id), truncate(new_content, 100)) - @wh_mod.command(name="nuke") + @WormholeBase.wh_mod.command(name="nuke") @requires_role(Role.MODERATOR) async def wh_mod_nuke(self, ctx: commands.Context, name: str, message_id: int) -> None: """Delete a message and all its relayed copies across the network.""" @@ -243,7 +239,7 @@ class ModerationCommands: # ── Audit log ────────────────────────────────────────────────────────── - @wh_mod.command(name="audit") + @WormholeBase.wh_mod.command(name="audit") @requires_role(Role.HELPER) async def wh_mod_audit(self, ctx: commands.Context, name: str, count: int = 20) -> None: """View the network audit log.""" @@ -275,7 +271,7 @@ class ModerationCommands: # ── Ban/mute lists ───────────────────────────────────────────────────── - @wh_mod.command(name="bans") + @WormholeBase.wh_mod.command(name="bans") @requires_role(Role.HELPER) async def wh_mod_bans(self, ctx: commands.Context, name: str) -> None: """List banned users.""" @@ -291,7 +287,7 @@ class ModerationCommands: lines.append(f"• {user or uid}") await ctx.send(embed=info_embed("\n".join(lines), title=f"🔨 Banned Users — {name}")) - @wh_mod.command(name="mutes") + @WormholeBase.wh_mod.command(name="mutes") @requires_role(Role.HELPER) async def wh_mod_mutes(self, ctx: commands.Context, name: str) -> None: """List muted users.""" diff --git a/wormhole/commands/network.py b/wormhole/commands/network.py index 405b643..cbc4e5f 100644 --- a/wormhole/commands/network.py +++ b/wormhole/commands/network.py @@ -12,12 +12,13 @@ from redbot.core import commands from ..models.config import DEFAULT_NETWORK from ..models.permissions import Role, has_role, requires_role, role_name, get_role, list_staff from ..utils import ok_embed, err_embed, info_embed, warn_embed, COLOUR_INFO, COLOUR_OK, truncate +from ._base import WormholeBase -class NetworkCommands: +class NetworkCommands(WormholeBase): """Mixin — network lifecycle commands.""" - @commands.hybrid_command(name="wh-create") + @WormholeBase.wh.command(name="create") async def wh_create(self, ctx: commands.Context, name: str, *, description: str = "") -> None: """Create a new wormhole network.""" nets = await self.config.networks() @@ -40,7 +41,7 @@ class NetworkCommands: )) await self._audit(name, "create", str(ctx.author)) - @commands.hybrid_command(name="wh-delete") + @WormholeBase.wh.command(name="delete") async def wh_delete(self, ctx: commands.Context, name: str) -> None: """Delete a network (owner or bot owner only).""" nd = await self._net(name) @@ -54,7 +55,7 @@ class NetworkCommands: self.cooldowns.pop(name, None) await ctx.send(embed=ok_embed(f"Network `{name}` deleted.")) - @commands.hybrid_command(name="wh-open") + @WormholeBase.wh.command(name="open") @requires_role(Role.ADMIN) async def wh_open(self, ctx: commands.Context, name: str) -> None: """Add the current channel to a network.""" @@ -63,7 +64,6 @@ class NetworkCommands: return await ctx.send(embed=err_embed(f"Network `{name}` not found.")) if ctx.channel.id in nd.get("channels", []): return await ctx.send(embed=err_embed("This channel is already in the network.")) - # Check not in another network existing = await self._net_for_ch(ctx.channel.id) if existing: return await ctx.send(embed=err_embed(f"This channel is already in network `{existing}`.")) @@ -72,7 +72,7 @@ class NetworkCommands: await ctx.send(embed=ok_embed(f"Channel linked to `{name}`. Messages will now relay.")) await self._audit(name, "open", str(ctx.author), str(ctx.channel.id)) - @commands.hybrid_command(name="wh-close") + @WormholeBase.wh.command(name="close") async def wh_close(self, ctx: commands.Context, name: str = None) -> None: """Remove the current channel from a network.""" if not name: @@ -91,7 +91,7 @@ class NetworkCommands: await ctx.send(embed=ok_embed(f"Channel removed from `{name}`.")) await self._audit(name, "close", str(ctx.author), str(ctx.channel.id)) - @commands.hybrid_command(name="wh-list") + @WormholeBase.wh.command(name="list") async def wh_list(self, ctx: commands.Context) -> None: """List all networks you have access to.""" nets = await self.config.networks() @@ -107,7 +107,7 @@ class NetworkCommands: em = discord.Embed(title="🌀 Wormhole Networks", description="\n".join(lines), colour=COLOUR_INFO) await ctx.send(embed=em) - @commands.hybrid_command(name="wh-discover") + @WormholeBase.wh.command(name="discover") async def wh_discover(self, ctx: commands.Context) -> None: """Browse public networks.""" nets = await self.config.networks() @@ -122,7 +122,7 @@ class NetworkCommands: em = discord.Embed(title="🔍 Public Networks", description="\n".join(lines), colour=COLOUR_INFO) await ctx.send(embed=em) - @commands.hybrid_command(name="wh-info") + @WormholeBase.wh.command(name="info") async def wh_info(self, ctx: commands.Context, name: str) -> None: """Show detailed info about a network.""" nd = await self._net(name) @@ -137,7 +137,6 @@ class NetworkCommands: em.add_field(name="Status", value=status, inline=True) em.add_field(name="Owner", value=str(owner or nd["owner_id"]), inline=True) - # Staff count by role staff = list_staff(nd) staff_summary = [] for role in (Role.ADMIN, Role.MODERATOR, Role.HELPER): diff --git a/wormhole/commands/reports.py b/wormhole/commands/reports.py index 3896553..76fd587 100644 --- a/wormhole/commands/reports.py +++ b/wormhole/commands/reports.py @@ -9,17 +9,13 @@ from redbot.core import commands from ..models.permissions import Role, requires_role, has_role from ..utils import ok_embed, err_embed, info_embed, warn_embed, truncate, COLOUR_INFO +from ._base import WormholeBase -class ReportCommands: +class ReportCommands(WormholeBase): """Mixin — user report system (per-network).""" - @commands.group(name="wh-report", aliases=["whreport"], invoke_without_command=True) - async def wh_report(self, ctx: commands.Context) -> None: - """Network report system.""" - await ctx.send_help(ctx.command) - - @wh_report.command(name="msg") + @WormholeBase.wh_report.command(name="msg") async def wh_report_msg(self, ctx: commands.Context, message_id: int, *, reason: str = "No reason provided") -> None: """Report a message by ID.""" net_name = await self._net_for_ch(ctx.channel.id) @@ -51,7 +47,7 @@ class ReportCommands: title="New Report", )) - @wh_report.command(name="list") + @WormholeBase.wh_report.command(name="list") @requires_role(Role.HELPER) async def wh_report_list(self, ctx: commands.Context, name: str, show_resolved: bool = False) -> None: """List reports for a network.""" @@ -74,7 +70,7 @@ class ReportCommands: em = discord.Embed(title=f"🚩 Reports — {name}", description="\n".join(lines), colour=COLOUR_INFO) await ctx.send(embed=em) - @wh_report.command(name="view") + @WormholeBase.wh_report.command(name="view") @requires_role(Role.HELPER) async def wh_report_view(self, ctx: commands.Context, name: str, report_id: int) -> None: """View details of a specific report.""" @@ -103,7 +99,7 @@ class ReportCommands: em.add_field(name="Resolution", value=report["resolution"], inline=False) await ctx.send(embed=em) - @wh_report.command(name="resolve") + @WormholeBase.wh_report.command(name="resolve") @requires_role(Role.MODERATOR) async def wh_report_resolve(self, ctx: commands.Context, name: str, report_id: int, *, resolution: str = "Resolved") -> None: """Resolve a report.""" @@ -120,7 +116,7 @@ class ReportCommands: await ctx.send(embed=ok_embed(f"Report #{report_id} resolved.")) await self._audit(name, "report_resolve", str(ctx.author), str(report_id), resolution) - @wh_report.command(name="dismiss") + @WormholeBase.wh_report.command(name="dismiss") @requires_role(Role.MODERATOR) async def wh_report_dismiss(self, ctx: commands.Context, name: str, report_id: int, *, reason: str = "Dismissed") -> None: """Dismiss a report.""" diff --git a/wormhole/commands/settings.py b/wormhole/commands/settings.py index dc661f7..889cd96 100644 --- a/wormhole/commands/settings.py +++ b/wormhole/commands/settings.py @@ -9,19 +9,15 @@ from redbot.core import commands from ..models.permissions import Role, requires_role from ..utils import ok_embed, err_embed, info_embed, COLOUR_INFO, COLOUR_OK +from ._base import WormholeBase -class SettingsCommands: +class SettingsCommands(WormholeBase): """Mixin — network settings management.""" - @commands.group(name="wh-set", aliases=["whset"], invoke_without_command=True) - async def wh_set(self, ctx: commands.Context) -> None: - """Configure network settings.""" - await ctx.send_help(ctx.command) - # ── Relay mode ───────────────────────────────────────────────────────── - @wh_set.command(name="relaymode") + @WormholeBase.wh_set.command(name="relaymode") @requires_role(Role.ADMIN) async def wh_set_relaymode(self, ctx: commands.Context, name: str, mode: str) -> None: """Set relay mode: ``webhook``, ``embed``, or ``compact``.""" @@ -34,7 +30,7 @@ class SettingsCommands: # ── Webhooks ─────────────────────────────────────────────────────────── - @wh_set.command(name="webhooks") + @WormholeBase.wh_set.command(name="webhooks") @requires_role(Role.ADMIN) async def wh_set_webhooks(self, ctx: commands.Context, name: str, enabled: bool) -> None: """Enable or disable webhook relay.""" @@ -45,7 +41,7 @@ class SettingsCommands: # ── Sync toggles ────────────────────────────────────────────────────── - @wh_set.command(name="sync") + @WormholeBase.wh_set.command(name="sync") @requires_role(Role.ADMIN) async def wh_set_sync(self, ctx: commands.Context, name: str, feature: str, enabled: bool) -> None: """Toggle sync features: ``edits``, ``deletes``, ``reactions``, ``replies``, ``stickers``, ``pins``, ``threads``, ``typing``.""" @@ -64,7 +60,7 @@ class SettingsCommands: # ── Identity ─────────────────────────────────────────────────────────── - @wh_set.command(name="imagemode") + @WormholeBase.wh_set.command(name="imagemode") @requires_role(Role.ADMIN) async def wh_set_imagemode(self, ctx: commands.Context, name: str, mode: str) -> None: """Set avatar mode: ``user``, ``server``, ``custom``.""" @@ -74,7 +70,7 @@ class SettingsCommands: ns[name]["image_mode"] = mode await ctx.send(embed=ok_embed(f"Image mode for `{name}` set to **{mode}**.")) - @wh_set.command(name="namemode") + @WormholeBase.wh_set.command(name="namemode") @requires_role(Role.ADMIN) async def wh_set_namemode(self, ctx: commands.Context, name: str, mode: str) -> None: """Set name mode: ``user``, ``server``, ``both``, ``custom``.""" @@ -84,7 +80,7 @@ class SettingsCommands: ns[name]["name_mode"] = mode await ctx.send(embed=ok_embed(f"Name mode for `{name}` set to **{mode}**.")) - @wh_set.command(name="icon") + @WormholeBase.wh_set.command(name="icon") @requires_role(Role.ADMIN) async def wh_set_icon(self, ctx: commands.Context, name: str, url: str = None) -> None: """Set a custom network icon URL.""" @@ -92,7 +88,7 @@ class SettingsCommands: ns[name]["custom_icon"] = url await ctx.send(embed=ok_embed(f"Custom icon {'set' if url else 'cleared'} for `{name}`.")) - @wh_set.command(name="customname") + @WormholeBase.wh_set.command(name="customname") @requires_role(Role.ADMIN) async def wh_set_customname(self, ctx: commands.Context, name: str, *, value: str = None) -> None: """Set a custom display name for relayed messages.""" @@ -100,7 +96,7 @@ class SettingsCommands: ns[name]["custom_name"] = value await ctx.send(embed=ok_embed(f"Custom name {'set' if value else 'cleared'} for `{name}`.")) - @wh_set.command(name="colour", aliases=["color"]) + @WormholeBase.wh_set.command(name="colour", aliases=["color"]) @requires_role(Role.ADMIN) async def wh_set_colour(self, ctx: commands.Context, name: str, hex_code: str = None) -> None: """Set the network embed colour (hex, e.g. ``#ff5733``).""" @@ -117,7 +113,7 @@ class SettingsCommands: # ── Description / MOTD / rules ───────────────────────────────────────── - @wh_set.command(name="description", aliases=["desc"]) + @WormholeBase.wh_set.command(name="description", aliases=["desc"]) @requires_role(Role.ADMIN) async def wh_set_desc(self, ctx: commands.Context, name: str, *, text: str) -> None: """Set the network description.""" @@ -125,7 +121,7 @@ class SettingsCommands: ns[name]["description"] = text await ctx.send(embed=ok_embed(f"Description updated for `{name}`.")) - @wh_set.command(name="motd") + @WormholeBase.wh_set.command(name="motd") @requires_role(Role.ADMIN) async def wh_set_motd(self, ctx: commands.Context, name: str, *, text: str = "") -> None: """Set or clear the Message of the Day.""" @@ -133,7 +129,7 @@ class SettingsCommands: ns[name]["motd"] = text await ctx.send(embed=ok_embed(f"MOTD {'set' if text else 'cleared'} for `{name}`.")) - @wh_set.command(name="welcome") + @WormholeBase.wh_set.command(name="welcome") @requires_role(Role.ADMIN) async def wh_set_welcome(self, ctx: commands.Context, name: str, *, text: str = "") -> None: """Set or clear the welcome message for new servers.""" @@ -143,7 +139,7 @@ class SettingsCommands: # ── Network behaviour ────────────────────────────────────────────────── - @wh_set.command(name="freeze") + @WormholeBase.wh_set.command(name="freeze") @requires_role(Role.ADMIN) async def wh_set_freeze(self, ctx: commands.Context, name: str) -> None: """Freeze the network — relay paused.""" @@ -152,7 +148,7 @@ class SettingsCommands: await ctx.send(embed=ok_embed(f"`{name}` is now ❄️ frozen. Messages won't relay.")) await self._audit(name, "freeze", str(ctx.author)) - @wh_set.command(name="unfreeze") + @WormholeBase.wh_set.command(name="unfreeze") @requires_role(Role.ADMIN) async def wh_set_unfreeze(self, ctx: commands.Context, name: str) -> None: """Unfreeze the network — resume relay.""" @@ -161,7 +157,7 @@ class SettingsCommands: await ctx.send(embed=ok_embed(f"`{name}` is now 🟢 active.")) await self._audit(name, "unfreeze", str(ctx.author)) - @wh_set.command(name="anonymous", aliases=["anon"]) + @WormholeBase.wh_set.command(name="anonymous", aliases=["anon"]) @requires_role(Role.ADMIN) async def wh_set_anonymous(self, ctx: commands.Context, name: str, enabled: bool) -> None: """Toggle anonymous mode.""" @@ -172,7 +168,7 @@ class SettingsCommands: ns[name]["anon_salt"] = secrets.token_hex(8) await ctx.send(embed=ok_embed(f"Anonymous mode {'enabled' if enabled else 'disabled'} for `{name}`.")) - @wh_set.command(name="nsfw") + @WormholeBase.wh_set.command(name="nsfw") @requires_role(Role.ADMIN) async def wh_set_nsfw(self, ctx: commands.Context, name: str, gate: bool) -> None: """Toggle NSFW gate (only relay to NSFW channels if source is NSFW).""" @@ -180,7 +176,7 @@ class SettingsCommands: ns[name]["nsfw_gate"] = gate await ctx.send(embed=ok_embed(f"NSFW gate {'enabled' if gate else 'disabled'} for `{name}`.")) - @wh_set.command(name="mediaonly") + @WormholeBase.wh_set.command(name="mediaonly") @requires_role(Role.ADMIN) async def wh_set_mediaonly(self, ctx: commands.Context, name: str, enabled: bool) -> None: """Toggle media-only mode (only relay messages with attachments).""" @@ -188,7 +184,7 @@ class SettingsCommands: ns[name]["media_only"] = enabled await ctx.send(embed=ok_embed(f"Media-only mode {'enabled' if enabled else 'disabled'} for `{name}`.")) - @wh_set.command(name="silent") + @WormholeBase.wh_set.command(name="silent") @requires_role(Role.ADMIN) async def wh_set_silent(self, ctx: commands.Context, name: str, enabled: bool) -> None: """Toggle silent mode (suppress status messages).""" @@ -196,7 +192,7 @@ class SettingsCommands: ns[name]["silent"] = enabled await ctx.send(embed=ok_embed(f"Silent mode {'enabled' if enabled else 'disabled'} for `{name}`.")) - @wh_set.command(name="embeds") + @WormholeBase.wh_set.command(name="embeds") @requires_role(Role.ADMIN) async def wh_set_embeds(self, ctx: commands.Context, name: str, enabled: bool) -> None: """Toggle forwarding embeds from relayed messages.""" @@ -206,7 +202,7 @@ class SettingsCommands: # ── Rate limiting ────────────────────────────────────────────────────── - @wh_set.command(name="ratelimit") + @WormholeBase.wh_set.command(name="ratelimit") @requires_role(Role.ADMIN) async def wh_set_ratelimit(self, ctx: commands.Context, name: str, rate: int = 5, per: float = 10.0) -> None: """Set rate limit: *rate* messages per *per* seconds.""" @@ -220,7 +216,7 @@ class SettingsCommands: self.cooldowns[name] = CooldownBucket(rate, per) await ctx.send(embed=ok_embed(f"Rate limit: {rate} msgs per {per}s for `{name}`.")) - @wh_set.command(name="slowmode") + @WormholeBase.wh_set.command(name="slowmode") @requires_role(Role.ADMIN) async def wh_set_slowmode(self, ctx: commands.Context, name: str, seconds: int = 0) -> None: """Set slowmode (seconds between messages per user, 0 to disable).""" @@ -230,7 +226,7 @@ class SettingsCommands: # ── Log channel ──────────────────────────────────────────────────────── - @wh_set.command(name="logchannel", aliases=["log"]) + @WormholeBase.wh_set.command(name="logchannel", aliases=["log"]) @requires_role(Role.ADMIN) async def wh_set_logchannel(self, ctx: commands.Context, name: str, channel: discord.TextChannel = None) -> None: """Set (or clear) the mod log channel.""" @@ -240,7 +236,7 @@ class SettingsCommands: # ── Server nicknames ─────────────────────────────────────────────────── - @wh_set.command(name="servernick") + @WormholeBase.wh_set.command(name="servernick") @requires_role(Role.ADMIN) async def wh_set_servernick(self, ctx: commands.Context, name: str, guild_id: int, *, nick: str = None) -> None: """Set or clear a nickname for a server in relay messages.""" @@ -254,7 +250,7 @@ class SettingsCommands: # ── Relay delay ──────────────────────────────────────────────────────── - @wh_set.command(name="delay") + @WormholeBase.wh_set.command(name="delay") @requires_role(Role.ADMIN) async def wh_set_delay(self, ctx: commands.Context, name: str, seconds: int = 0) -> None: """Set relay delay (seconds before messages are relayed, 0 to disable).""" @@ -264,7 +260,7 @@ class SettingsCommands: # ── Ephemeral auto-delete ────────────────────────────────────────────── - @wh_set.command(name="ephemeral") + @WormholeBase.wh_set.command(name="ephemeral") @requires_role(Role.ADMIN) async def wh_set_ephemeral(self, ctx: commands.Context, name: str, seconds: int = 0) -> None: """Auto-delete relayed messages after N seconds (0 to disable).""" @@ -274,7 +270,7 @@ class SettingsCommands: # ── Discovery ────────────────────────────────────────────────────────── - @wh_set.command(name="public") + @WormholeBase.wh_set.command(name="public") @requires_role(Role.OWNER) async def wh_set_public(self, ctx: commands.Context, name: str, enabled: bool) -> None: """Make network publicly discoverable.""" @@ -282,7 +278,7 @@ class SettingsCommands: ns[name]["public"] = enabled await ctx.send(embed=ok_embed(f"`{name}` is now {'public' if enabled else 'private'}.")) - @wh_set.command(name="tags") + @WormholeBase.wh_set.command(name="tags") @requires_role(Role.ADMIN) async def wh_set_tags(self, ctx: commands.Context, name: str, *, tags: str) -> None: """Set discovery tags (comma-separated).""" @@ -293,7 +289,7 @@ class SettingsCommands: # ── Channel overrides ────────────────────────────────────────────────── - @wh_set.command(name="override") + @WormholeBase.wh_set.command(name="override") @requires_role(Role.ADMIN) async def wh_set_override(self, ctx: commands.Context, name: str, key: str, value: str) -> None: """Set a per-channel config override for the current channel.""" @@ -321,7 +317,7 @@ class SettingsCommands: # ── View all settings ────────────────────────────────────────────────── - @wh_set.command(name="show") + @WormholeBase.wh_set.command(name="show") @requires_role(Role.HELPER) async def wh_set_show(self, ctx: commands.Context, name: str) -> None: """Show current settings for a network.""" diff --git a/wormhole/commands/social.py b/wormhole/commands/social.py index b8f48ab..6bc2e16 100644 --- a/wormhole/commands/social.py +++ b/wormhole/commands/social.py @@ -10,14 +10,15 @@ from redbot.core import commands from ..models.permissions import Role, has_role, requires_role, get_role, role_name from ..utils import ok_embed, err_embed, info_embed, star_embed, warn_embed, truncate, COLOUR_INFO, COLOUR_STAR +from ._base import WormholeBase -class SocialCommands: +class SocialCommands(WormholeBase): """Mixin — social features.""" # ── Profiles ─────────────────────────────────────────────────────────── - @commands.hybrid_command(name="wh-profile") + @WormholeBase.wh.command(name="profile") async def wh_profile(self, ctx: commands.Context, name: str, user: discord.User = None) -> None: """View a user's wormhole profile in a network.""" nd = await self._net(name) @@ -46,12 +47,7 @@ class SocialCommands: # ── Karma ────────────────────────────────────────────────────────────── - @commands.group(name="wh-karma", aliases=["whkarma"], invoke_without_command=True) - async def wh_karma(self, ctx: commands.Context) -> None: - """Karma system commands.""" - await ctx.send_help(ctx.command) - - @wh_karma.command(name="enable") + @WormholeBase.wh_karma.command(name="enable") @requires_role(Role.ADMIN) async def wh_karma_enable(self, ctx: commands.Context, name: str, enabled: bool = True) -> None: """Enable or disable the karma system.""" @@ -59,7 +55,7 @@ class SocialCommands: ns[name]["karma_enabled"] = enabled await ctx.send(embed=ok_embed(f"Karma {'enabled' if enabled else 'disabled'} for `{name}`.")) - @wh_karma.command(name="emoji") + @WormholeBase.wh_karma.command(name="emoji") @requires_role(Role.ADMIN) async def wh_karma_emoji(self, ctx: commands.Context, name: str, emoji: str = "👍") -> None: """Set the karma reaction emoji.""" @@ -67,7 +63,7 @@ class SocialCommands: ns[name]["karma_emoji"] = emoji await ctx.send(embed=ok_embed(f"Karma emoji set to {emoji} for `{name}`.")) - @wh_karma.command(name="top") + @WormholeBase.wh_karma.command(name="top") async def wh_karma_top(self, ctx: commands.Context, name: str, count: int = 10) -> None: """Show karma leaderboard.""" nd = await self._net(name) @@ -88,12 +84,7 @@ class SocialCommands: # ── Starboard ────────────────────────────────────────────────────────── - @commands.group(name="wh-star", aliases=["whstar"], invoke_without_command=True) - async def wh_star(self, ctx: commands.Context) -> None: - """Starboard commands.""" - await ctx.send_help(ctx.command) - - @wh_star.command(name="enable") + @WormholeBase.wh_star.command(name="enable") @requires_role(Role.ADMIN) async def wh_star_enable(self, ctx: commands.Context, name: str, enabled: bool = True) -> None: """Enable or disable starboard.""" @@ -101,7 +92,7 @@ class SocialCommands: ns[name]["starboard_enabled"] = enabled await ctx.send(embed=ok_embed(f"Starboard {'enabled' if enabled else 'disabled'} for `{name}`.")) - @wh_star.command(name="channel") + @WormholeBase.wh_star.command(name="channel") @requires_role(Role.ADMIN) async def wh_star_channel(self, ctx: commands.Context, name: str, channel: discord.TextChannel = None) -> None: """Set the starboard channel.""" @@ -109,7 +100,7 @@ class SocialCommands: ns[name]["starboard_channel"] = channel.id if channel else None await ctx.send(embed=ok_embed(f"Starboard channel {'set' if channel else 'cleared'} for `{name}`.")) - @wh_star.command(name="threshold") + @WormholeBase.wh_star.command(name="threshold") @requires_role(Role.ADMIN) async def wh_star_threshold(self, ctx: commands.Context, name: str, threshold: int = 3) -> None: """Set minimum stars to reach the starboard.""" @@ -119,12 +110,7 @@ class SocialCommands: # ── Highlights ───────────────────────────────────────────────────────── - @commands.group(name="wh-hl", aliases=["whhl"], invoke_without_command=True) - async def wh_hl(self, ctx: commands.Context) -> None: - """Keyword highlight notifications.""" - await ctx.send_help(ctx.command) - - @wh_hl.command(name="add") + @WormholeBase.wh_hl.command(name="add") async def wh_hl_add(self, ctx: commands.Context, name: str, *, keyword: str) -> None: """Add a keyword to highlight.""" async with self.config.networks() as ns: @@ -138,7 +124,7 @@ class SocialCommands: user_hl.append(keyword) await ctx.send(embed=ok_embed(f"Highlight `{keyword}` added for `{name}`.")) - @wh_hl.command(name="remove", aliases=["rm"]) + @WormholeBase.wh_hl.command(name="remove", aliases=["rm"]) async def wh_hl_remove(self, ctx: commands.Context, name: str, *, keyword: str) -> None: """Remove a highlight keyword.""" async with self.config.networks() as ns: @@ -147,7 +133,7 @@ class SocialCommands: ns[name]["highlights"][str(ctx.author.id)] = [k for k in user_hl if k.lower() != keyword.lower()] await ctx.send(embed=ok_embed(f"Highlight `{keyword}` removed.")) - @wh_hl.command(name="list") + @WormholeBase.wh_hl.command(name="list") async def wh_hl_list(self, ctx: commands.Context, name: str) -> None: """List your highlights.""" nd = await self._net(name) @@ -160,12 +146,7 @@ class SocialCommands: # ── Bookmarks ────────────────────────────────────────────────────────── - @commands.group(name="wh-bm", aliases=["whbm"], invoke_without_command=True) - async def wh_bm(self, ctx: commands.Context) -> None: - """Message bookmarks.""" - await ctx.send_help(ctx.command) - - @wh_bm.command(name="list") + @WormholeBase.wh_bm.command(name="list") async def wh_bm_list(self, ctx: commands.Context) -> None: """List your bookmarks.""" bm = await self.config.bookmarks() @@ -181,7 +162,7 @@ class SocialCommands: em = discord.Embed(title="📌 Bookmarks", description="\n".join(lines), colour=COLOUR_INFO) await ctx.send(embed=em) - @wh_bm.command(name="clear") + @WormholeBase.wh_bm.command(name="clear") async def wh_bm_clear(self, ctx: commands.Context) -> None: """Clear all your bookmarks.""" async with self.config.bookmarks() as bm: diff --git a/wormhole/commands/staff.py b/wormhole/commands/staff.py index 5986c72..0c187f4 100644 --- a/wormhole/commands/staff.py +++ b/wormhole/commands/staff.py @@ -18,17 +18,13 @@ from ..models.permissions import ( remove_staff, ) from ..utils import ok_embed, err_embed, info_embed, COLOUR_INFO +from ._base import WormholeBase -class StaffCommands: +class StaffCommands(WormholeBase): """Mixin — staff management (per-network hierarchy).""" - @commands.group(name="wh-staff", aliases=["whstaff"], invoke_without_command=True) - async def wh_staff(self, ctx: commands.Context) -> None: - """Manage network staff — add, remove, list, promote, demote.""" - await ctx.send_help(ctx.command) - - @wh_staff.command(name="add") + @WormholeBase.wh_staff.command(name="add") async def wh_staff_add(self, ctx: commands.Context, name: str, user: discord.User, role_str: str = "moderator") -> None: """Assign a staff role to a user. @@ -57,7 +53,7 @@ class StaffCommands: )) await self._audit(name, "staff_add", str(ctx.author), str(user), f"role={role_name(target_role)}") - @wh_staff.command(name="remove", aliases=["rm"]) + @WormholeBase.wh_staff.command(name="remove", aliases=["rm"]) async def wh_staff_rm(self, ctx: commands.Context, name: str, user: discord.User) -> None: """Remove a user from network staff.""" nd = await self._net(name) @@ -81,7 +77,7 @@ class StaffCommands: await ctx.send(embed=ok_embed(f"{user.mention} removed from staff in `{name}`.")) await self._audit(name, "staff_rm", str(ctx.author), str(user)) - @wh_staff.command(name="promote") + @WormholeBase.wh_staff.command(name="promote") async def wh_staff_promote(self, ctx: commands.Context, name: str, user: discord.User) -> None: """Promote a staff member one tier up.""" nd = await self._net(name) @@ -110,7 +106,7 @@ class StaffCommands: )) await self._audit(name, "promote", str(ctx.author), str(user), f"{role_name(current)} → {role_name(new_role)}") - @wh_staff.command(name="demote") + @WormholeBase.wh_staff.command(name="demote") async def wh_staff_demote(self, ctx: commands.Context, name: str, user: discord.User) -> None: """Demote a staff member one tier down.""" nd = await self._net(name) @@ -144,7 +140,7 @@ class StaffCommands: )) await self._audit(name, "demote", str(ctx.author), str(user), f"{role_name(current)} → {label}") - @wh_staff.command(name="list", aliases=["ls"]) + @WormholeBase.wh_staff.command(name="list", aliases=["ls"]) async def wh_staff_ls(self, ctx: commands.Context, name: str) -> None: """List all staff in a network with their roles.""" nd = await self._net(name) @@ -169,7 +165,7 @@ class StaffCommands: ) await ctx.send(embed=em) - @commands.hybrid_command(name="wh-transfer") + @WormholeBase.wh.command(name="transfer") async def wh_transfer(self, ctx: commands.Context, name: str, new_owner: discord.User) -> None: """Transfer network ownership (owner or bot owner only).""" nd = await self._net(name) diff --git a/wormhole/commands/tos.py b/wormhole/commands/tos.py index 8e4b7c3..3600602 100644 --- a/wormhole/commands/tos.py +++ b/wormhole/commands/tos.py @@ -9,17 +9,13 @@ from redbot.core import commands from ..models.permissions import Role, requires_role from ..utils import ok_embed, err_embed, info_embed, COLOUR_INFO +from ._base import WormholeBase -class ToSCommands: +class ToSCommands(WormholeBase): """Mixin — Terms of Service / rules acceptance gate.""" - @commands.group(name="wh-rules", aliases=["whrules"], invoke_without_command=True) - async def wh_rules(self, ctx: commands.Context) -> None: - """Network rules / Terms of Service.""" - await ctx.send_help(ctx.command) - - @wh_rules.command(name="set") + @WormholeBase.wh_rules.command(name="set") @requires_role(Role.ADMIN) async def wh_rules_set(self, ctx: commands.Context, name: str, *, text: str) -> None: """Set the rules / ToS text.""" @@ -27,7 +23,7 @@ class ToSCommands: ns[name]["rules_text"] = text await ctx.send(embed=ok_embed(f"Rules updated for `{name}`.")) - @wh_rules.command(name="require") + @WormholeBase.wh_rules.command(name="require") @requires_role(Role.ADMIN) async def wh_rules_require(self, ctx: commands.Context, name: str, enabled: bool = True) -> None: """Toggle mandatory rules acceptance before using the network.""" @@ -37,7 +33,7 @@ class ToSCommands: f"Rules acceptance {'required' if enabled else 'not required'} for `{name}`." )) - @wh_rules.command(name="view") + @WormholeBase.wh_rules.command(name="view") async def wh_rules_view(self, ctx: commands.Context, name: str) -> None: """View the network rules.""" nd = await self._net(name) @@ -56,7 +52,7 @@ class ToSCommands: em.set_footer(text="⚠️ You haven't accepted these rules yet. Use `wh accept` or `wh agree`.") await ctx.send(embed=em) - @commands.hybrid_command(name="wh-accept") + @WormholeBase.wh.command(name="accept") async def wh_accept(self, ctx: commands.Context, name: str) -> None: """Accept the network rules / ToS.""" nd = await self._net(name) @@ -68,12 +64,12 @@ class ToSCommands: ns[name].setdefault("rules_accepted", {})[str(ctx.author.id)] = datetime.now(timezone.utc).isoformat() await ctx.send(embed=ok_embed(f"You've accepted the rules for `{name}`. You can now use the relay.")) - @commands.hybrid_command(name="wh-agree") + @WormholeBase.wh.command(name="agree") async def wh_agree(self, ctx: commands.Context, name: str) -> None: """Alias for ``wh accept``.""" await self.wh_accept(ctx, name) - @wh_rules.command(name="reset") + @WormholeBase.wh_rules.command(name="reset") @requires_role(Role.ADMIN) async def wh_rules_reset(self, ctx: commands.Context, name: str) -> None: """Reset all acceptances (force everyone to re-accept).""" diff --git a/wormhole/core.py b/wormhole/core.py index f50fee0..984d1e2 100644 --- a/wormhole/core.py +++ b/wormhole/core.py @@ -726,9 +726,4 @@ class Wormhole( ) await interaction.response.send_message(embed=em, ephemeral=True) - # ── Root command group ───────────────────────────────────────────────── - - @commands.hybrid_group(name="wh", aliases=["wormhole"], invoke_without_command=True) - async def wh(self, ctx: commands.Context) -> None: - """🌀 Wormhole — the ultimate cross-server relay.""" - await ctx.send_help(ctx.command) + # ── Root command group is defined in commands/_base.py ────────────────