Skip to content

refactor: Continue dynamic moderation refactoring timeout arguments #977

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 60 commits into
base: refactor/moderation
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
8472700
Add reason and silent options to moderation command configs
cursoragent Jul 26, 2025
98e6d67
Implement dynamic moderation command system with unified executor
cursoragent Jul 26, 2025
72f2c87
Refactor moderation command argument validation with new helper method
cursoragent Jul 26, 2025
6c8bc84
Remove moderation command cogs from project
cursoragent Jul 26, 2025
cb6c178
Refactor dynamic moderation command parameter type annotation
cursoragent Jul 26, 2025
0addfaa
Disable app command for dynamic moderation command
cursoragent Jul 26, 2025
c98d658
Refactor dynamic moderation cog for slash command compatibility
cursoragent Jul 26, 2025
aa79e80
Restrict dynamic moderation command target to discord.User
cursoragent Jul 26, 2025
0564785
Add MemberOrUser transformer for consistent member/user resolution
cursoragent Jul 26, 2025
cadbe31
Fix dynamic moderation command closure with captured self reference
cursoragent Jul 26, 2025
70887b7
Improve member fetching with fallback to fetch_member method
cursoragent Jul 26, 2025
977c487
Fix AppCommandOptionType import for compatibility with older discord.py
cursoragent Jul 26, 2025
098b3ec
Add GuildInit cog to ensure guild database rows on startup/join
cursoragent Jul 26, 2025
6e04c0d
Add automatic database reconnection on connection loss
cursoragent Jul 26, 2025
144c856
Improve database connection error logging with specific error handling
cursoragent Jul 26, 2025
41335fd
Checkpoint before follow-up message
cursoragent Jul 26, 2025
5564a1d
Add help text to dynamic moderation command configuration
cursoragent Jul 26, 2025
b515017
Fix dynamic moderation command help text placement
cursoragent Jul 26, 2025
a2fdf5a
Add help text and description to dynamic moderation command config
cursoragent Jul 26, 2025
1d271a2
Link dynamic moderation command back to cog instance
cursoragent Jul 26, 2025
7ef0772
Refactor dynamic command registration with in-place decorators
cursoragent Jul 26, 2025
fae0d50
Remove unnecessary short_doc attribute from dynamic moderation command
cursoragent Jul 26, 2025
13c2898
Register dynamic commands with cog's command list for help discovery
cursoragent Jul 26, 2025
04c17f8
Fix dynamic command registration to preserve existing commands
cursoragent Jul 26, 2025
1a36ef5
Fix command registration by removing existing commands before adding
cursoragent Jul 26, 2025
500e595
Simplify dynamic command registration and cog loading process
cursoragent Jul 26, 2025
0e5c886
Remove conflicting moderation commands before registering new ones
cursoragent Jul 26, 2025
bedfd81
Prevent premature command registration in dynamic moderation cog
cursoragent Jul 26, 2025
85c1c2c
Add dynamic flag injection for moderation commands
cursoragent Jul 26, 2025
6725fca
Fix flag annotation for help command parsing in dynamic moderation
cursoragent Jul 26, 2025
17a68da
Refactor dynamic moderation command generation with improved flag han…
cursoragent Jul 26, 2025
8e4e536
Expose dynamically created flags class in module globals
cursoragent Jul 26, 2025
cf0611e
Refactor moderation commands to use flag-based dynamic executor
cursoragent Jul 26, 2025
399c683
Refactor dynamic moderation flags using new flag_factory utility
cursoragent Jul 26, 2025
670d546
Fix help introspection for dynamic moderation flags class
cursoragent Jul 26, 2025
c62d7c6
Fix dynamic moderation typing import to support help introspection
cursoragent Jul 26, 2025
bd3c7a3
Add Dict type import to builtins for runtime compatibility
cursoragent Jul 26, 2025
11229e8
Add metaclass for declarative moderation command creation
cursoragent Jul 26, 2025
71cbaee
Refactor moderation command meta and add ban command implementation
cursoragent Jul 26, 2025
69b8c49
Add dynamic module import for moderation commands
cursoragent Jul 26, 2025
250f097
Remove dynamic moderation command system implementation
cursoragent Jul 26, 2025
7646c80
Fix eval_annotation resolution for command flags in moderation cog
cursoragent Jul 26, 2025
91e7004
Add global alias for FlagsCls to support eval string resolution
cursoragent Jul 26, 2025
df1d35d
Fix type hint for moderation command callback context parameter
cursoragent Jul 26, 2025
25cddd0
Add setup function for ban command extension
cursoragent Jul 26, 2025
f25ed67
Fix command registration in moderation cog
cursoragent Jul 26, 2025
9622292
Refactor moderation commands to separate text and slash command imple…
cursoragent Jul 26, 2025
f2cfeda
Fix potential None error when adding flags to slash command globals
cursoragent Jul 26, 2025
48894f9
Fix optional flags handling in moderation command methods
cursoragent Jul 26, 2025
dde3fae
Add global reference to FlagsCls for annotation resolution
cursoragent Jul 26, 2025
bfc0979
Fix FlagsCls global resolution for command metadata evaluation
cursoragent Jul 26, 2025
e8c0571
Fix slash command callback to retrieve cog instance dynamically
cursoragent Jul 26, 2025
9c74ab0
Refactor slash command to use explicit options and SimpleNamespace
cursoragent Jul 26, 2025
81d4607
Improve flag parsing for complex type hints in help command
cursoragent Jul 26, 2025
b8bb138
Set custom usage string for moderation text commands
cursoragent Jul 26, 2025
522915c
Add moderation action mixin and command infrastructure
cursoragent Jul 26, 2025
32dfff4
Add no-op setup functions to moderation utility modules
cursoragent Jul 26, 2025
3f15270
Remove ban command implementation
cursoragent Jul 26, 2025
34c8756
Remove old ban command cleanup code before registering new commands
cursoragent Jul 26, 2025
94f3bfc
Add flag support for ban command with purge and silent options
cursoragent Jul 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"source.organizeImports.ruff": "explicit"
}
},
"python.languageServer": "Pylance",
"python.languageServer": "None",
"python.analysis.typeCheckingMode": "strict",
"python.analysis.autoFormatStrings": true,
"python.analysis.completeFunctionParens": true,
Expand Down
181 changes: 181 additions & 0 deletions tux/cogs/moderation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,3 +604,184 @@ async def is_jailed(self, guild_id: int, user_id: int) -> bool:
active_restriction_type=CaseType.JAIL,
inactive_restriction_type=CaseType.UNJAIL,
)

# ------------------------------------------------------------------
# Unified mixed-argument executor (dynamic moderation system)
# ------------------------------------------------------------------
async def execute_mixed_mod_action(
self,
ctx: commands.Context[Tux],
config: "ModerationCommandConfig", # quoted to avoid circular import at runtime
user: discord.Member | discord.User,
mixed_args: str,
) -> None:
"""Parse *mixed_args* according to *config* and execute the moderation flow.

This serves as the single entry-point for all dynamically generated
moderation commands. It handles:
1. Mixed-argument parsing (positional + flags).
2. Validation based on *config* (duration required?, purge range?, etc.).
3. Permission / sanity checks via *check_conditions*.
4. Building the *actions* list and delegating to :py:meth:`execute_mod_action`.
"""

from tux.utils.mixed_args import parse_mixed_arguments # local import to avoid heavy top-level deps
from tux.utils.constants import CONST # default reason constant

assert ctx.guild, "This command can only be used in guild context." # noqa: S101

parsed = parse_mixed_arguments(mixed_args or "")
ok, validated = self._validate_args(config, parsed)
if not ok:
await ctx.send(validated["error"], ephemeral=True) # type: ignore[index]
return

duration = validated["duration"] # type: ignore[assignment]
purge = validated["purge"] # type: ignore[assignment]
reason = (validated.get("reason") or CONST.DEFAULT_REASON) # type: ignore[assignment]
silent = validated["silent"] # type: ignore[assignment]

# ------------------------------------------------------------------
# Permission / sanity checks
# ------------------------------------------------------------------
if not await self.check_conditions(ctx, user, ctx.author, config.name):
return

# ------------------------------------------------------------------
# Build Discord action coroutine(s)
# ------------------------------------------------------------------
# Prepare arg bundle for the lambda / callable
arg_bundle: dict[str, Any] = {
"duration": duration,
"purge": purge,
"silent": silent,
}

coroutine_or_none = config.discord_action(ctx.guild, user, reason, arg_bundle)
actions: list[tuple[Any, type[Any]]] = []
if coroutine_or_none is not None:
actions.append((coroutine_or_none, type(None)))

# ------------------------------------------------------------------
# Delegate to existing helper that handles DM, case creation, etc.
# ------------------------------------------------------------------
await self.execute_mod_action(
ctx=ctx,
case_type=config.case_type,
user=user,
reason=reason,
silent=silent,
dm_action=config.dm_action,
actions=actions,
duration=duration,
)

# ------------------------------------------------------------------
# Validation helper for dynamic moderation commands
# ------------------------------------------------------------------
def _validate_args(self, config: "ModerationCommandConfig", parsed: dict[str, Any]) -> tuple[bool, dict[str, Any]]:
"""Validate *parsed* arguments against *config* rules.

Returns (is_valid, validated_dict). On failure sends the error message
via the ctx stored in validated_dict["ctx"] and returns False.
"""
validated: dict[str, Any] = {}

# Duration
duration = parsed.get("duration")
if config.supports_duration:
if not duration:
return False, {"error": "Duration required (e.g. `14d`)."}
from tux.utils.functions import parse_time_string
try:
parse_time_string(duration) # ensure valid
except Exception:
return False, {"error": "Invalid duration format."}
validated["duration"] = duration
else:
validated["duration"] = None

# Purge
purge_raw = parsed.get("purge")
if config.supports_purge:
try:
purge_val = int(purge_raw or 0)
except ValueError:
return False, {"error": "Purge must be an integer 0-7."}
if not 0 <= purge_val <= 7:
return False, {"error": "Purge must be between 0 and 7."}
validated["purge"] = purge_val
else:
validated["purge"] = 0

validated["reason"] = parsed.get("reason")
validated["silent"] = bool(parsed.get("silent", False))
return True, validated

# ------------------------------------------------------------------
# New flag-based dynamic executor (replacing mixed-args approach)
# ------------------------------------------------------------------
async def execute_flag_mod_action(
self,
ctx: commands.Context[Tux],
config: "ModerationCommandConfig",
user: discord.Member | discord.User,
flags: Any,
reason: str,
) -> None:
"""Execute moderation flow based on *flags* parsed by FlagConverter.

This is the preferred pathway for dynamically generated moderation
commands that rely on discord.py's native FlagConverter parsing.
"""

from tux.utils.constants import CONST

assert ctx.guild, "Command must run in a guild context." # noqa: S101

duration = getattr(flags, "duration", None) if flags is not None else None
purge = getattr(flags, "purge", 0) if flags is not None else 0
silent = getattr(flags, "silent", False) if flags is not None else False

# Validation based on config
if config.supports_duration and not duration:
await ctx.send("Duration required (e.g. 14d).", ephemeral=True)
return
if not config.supports_duration:
duration = None

if not config.supports_purge:
purge = 0
else:
if not isinstance(purge, int) or not 0 <= purge <= 7:
await ctx.send("Purge must be between 0 and 7.", ephemeral=True)
return

reason_final = reason or CONST.DEFAULT_REASON

# Permission / sanity checks
if not await self.check_conditions(ctx, user, ctx.author, config.name):
return

# Build arg bundle for discord_action
arg_bundle: dict[str, Any] = {
"duration": duration,
"purge": purge,
"silent": silent,
}

coroutine_or_none = config.discord_action(ctx.guild, user, reason_final, arg_bundle)
actions: list[tuple[Any, type[Any]]] = []
if coroutine_or_none is not None:
actions.append((coroutine_or_none, type(None)))

await self.execute_mod_action(
ctx=ctx,
case_type=config.case_type,
user=user,
reason=reason_final,
silent=silent,
dm_action=config.dm_action,
actions=actions,
duration=duration,
)
75 changes: 75 additions & 0 deletions tux/cogs/moderation/action_mixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from __future__ import annotations

from collections.abc import Awaitable, Callable
from typing import Any

import discord
from discord.ext import commands
from prisma.enums import CaseType

from tux.cogs.moderation import ModerationCogBase
from tux.utils.constants import CONST
from tux.utils.transformers import MemberOrUser

__all__ = ["ModerationActionMixin"]


class ModerationActionMixin(ModerationCogBase):
"""Shared execution helper for moderation actions.

This mixin builds the *actions* list and delegates to
:py:meth:`ModerationCogBase.execute_mod_action` so concrete commands only
need to supply metadata and the Discord API coroutine.
"""

async def _run_action( # noqa: WPS211 (many params is ok for clarity)
self,
ctx: commands.Context, # noqa: D401
*,
member: MemberOrUser,
reason: str,
duration: str | None,
purge: int,
silent: bool,
case_type: CaseType,
dm_verb: str,
discord_action: Callable[[], Awaitable[Any]] | None = None,
) -> None:
"""Validate and execute the moderation workflow."""

assert ctx.guild is not None, "Guild-only command" # noqa: S101

# Permission / sanity checks
if not await self.check_conditions(ctx, member, ctx.author, case_type.name.lower()):
return

if purge and not (0 <= purge <= 7):
await ctx.send("Purge must be between 0 and 7 days.", ephemeral=True)
return

final_reason = reason or CONST.DEFAULT_REASON

actions: list[tuple[Awaitable[Any], type[Any]]] = []
if discord_action is not None:
actions.append((discord_action(), type(None)))

await self.execute_mod_action(
ctx=ctx,
case_type=case_type,
user=member,
reason=final_reason,
silent=silent,
dm_action=dm_verb,
actions=actions,
duration=duration,
)


# ---------------------------------------------------------------------------
# No-op setup so the cog loader skips this util module
# ---------------------------------------------------------------------------


async def setup(bot): # type: ignore[unused-argument]
"""Utility module – nothing to load."""
return
69 changes: 0 additions & 69 deletions tux/cogs/moderation/ban.py

This file was deleted.

Loading