quick commit
Some checks failed
CI/CD Pipeline / Code Quality Checks (push) Failing after 6m9s
CI/CD Pipeline / Security Scanning (push) Successful in 26s
CI/CD Pipeline / Tests (3.11) (push) Failing after 5m24s
CI/CD Pipeline / Tests (3.12) (push) Failing after 5m23s
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Deploy to Staging (push) Has been skipped
CI/CD Pipeline / Deploy to Production (push) Has been skipped
CI/CD Pipeline / Notification (push) Successful in 1s

This commit is contained in:
2026-01-17 20:24:43 +01:00
parent 95cc3cdb8f
commit 831eed8dbc
82 changed files with 8860 additions and 167 deletions

View File

@@ -1,7 +1,6 @@
"""Moderation commands and automod features."""
import logging
import re
from datetime import datetime, timedelta, timezone
import discord
@@ -10,36 +9,43 @@ from sqlalchemy import func, select
from guardden.bot import GuardDen
from guardden.models import ModerationLog, Strike
from guardden.utils import parse_duration
from guardden.utils.ratelimit import RateLimitExceeded
logger = logging.getLogger(__name__)
def parse_duration(duration_str: str) -> timedelta | None:
"""Parse a duration string like '1h', '30m', '7d' into a timedelta."""
match = re.match(r"^(\d+)([smhdw])$", duration_str.lower())
if not match:
return None
amount = int(match.group(1))
unit = match.group(2)
units = {
"s": timedelta(seconds=amount),
"m": timedelta(minutes=amount),
"h": timedelta(hours=amount),
"d": timedelta(days=amount),
"w": timedelta(weeks=amount),
}
return units.get(unit)
class Moderation(commands.Cog):
"""Moderation commands for server management."""
def __init__(self, bot: GuardDen) -> None:
self.bot = bot
def cog_check(self, ctx: commands.Context) -> bool:
if not ctx.guild:
return False
if not self.bot.is_owner_allowed(ctx.author.id):
return False
return True
async def cog_before_invoke(self, ctx: commands.Context) -> None:
if not ctx.command:
return
result = self.bot.rate_limiter.acquire_command(
ctx.command.qualified_name,
user_id=ctx.author.id,
guild_id=ctx.guild.id if ctx.guild else None,
channel_id=ctx.channel.id,
)
if result.is_limited:
raise RateLimitExceeded(result.reset_after)
async def cog_command_error(self, ctx: commands.Context, error: Exception) -> None:
if isinstance(error, RateLimitExceeded):
await ctx.send(
f"You're being rate limited. Try again in {error.retry_after:.1f} seconds."
)
async def _log_action(
self,
guild: discord.Guild,
@@ -334,7 +340,15 @@ class Moderation(commands.Cog):
except discord.Forbidden:
pass
await member.kick(reason=f"{ctx.author}: {reason}")
try:
await member.kick(reason=f"{ctx.author}: {reason}")
except discord.Forbidden:
await ctx.send("❌ I don't have permission to kick this member.")
return
except discord.HTTPException as e:
await ctx.send(f"❌ Failed to kick member: {e}")
return
await self._log_action(ctx.guild, member, ctx.author, "kick", reason)
embed = discord.Embed(
@@ -346,7 +360,10 @@ class Moderation(commands.Cog):
embed.add_field(name="Reason", value=reason, inline=False)
embed.set_footer(text=f"Moderator: {ctx.author}")
await ctx.send(embed=embed)
try:
await ctx.send(embed=embed)
except discord.HTTPException:
await ctx.send(f"{member} has been kicked from the server.")
@commands.command(name="ban")
@commands.has_permissions(ban_members=True)
@@ -376,7 +393,15 @@ class Moderation(commands.Cog):
except discord.Forbidden:
pass
await ctx.guild.ban(member, reason=f"{ctx.author}: {reason}", delete_message_days=0)
try:
await ctx.guild.ban(member, reason=f"{ctx.author}: {reason}", delete_message_days=0)
except discord.Forbidden:
await ctx.send("❌ I don't have permission to ban this member.")
return
except discord.HTTPException as e:
await ctx.send(f"❌ Failed to ban member: {e}")
return
await self._log_action(ctx.guild, member, ctx.author, "ban", reason)
embed = discord.Embed(
@@ -388,7 +413,10 @@ class Moderation(commands.Cog):
embed.add_field(name="Reason", value=reason, inline=False)
embed.set_footer(text=f"Moderator: {ctx.author}")
await ctx.send(embed=embed)
try:
await ctx.send(embed=embed)
except discord.HTTPException:
await ctx.send(f"{member} has been banned from the server.")
@commands.command(name="unban")
@commands.has_permissions(ban_members=True)