fix: group all commands under 'wh' parent — no more flat wh-xxx commands

Commands now show as 'wh create', 'wh staff add', 'wh set relaymode'
instead of 'wh-create', 'wh-staff add', 'wh-set relaymode'.

- Added commands/_base.py with root 'wh' group and all 20 subgroup stubs
- All 13 command mixins inherit from WormholeBase
- Decorators changed: @commands.hybrid_command(name='wh-X') → @WormholeBase.wh.command(name='X')
- Subgroup commands: @wh_X.command() → @WormholeBase.wh_X.command()
- Removed duplicate 'wh' group from core.py (now in _base.py)
- Fixed alias: 'wh-color' → 'color' for colour command
This commit is contained in:
Viktor AI 2026-05-14 19:24:37 +00:00
parent 5a4421fb6c
commit 4bc143b26f
16 changed files with 297 additions and 261 deletions

View file

@ -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",

128
wormhole/commands/_base.py Normal file
View file

@ -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)

View file

@ -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."""

View file

@ -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)

View file

@ -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()

View file

@ -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:

View file

@ -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)."""

View file

@ -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."""

View file

@ -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."""

View file

@ -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):

View file

@ -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."""

View file

@ -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."""

View file

@ -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:

View file

@ -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)

View file

@ -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)."""

View file

@ -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 ────────────────