aaaaa
This commit is contained in:
parent
80d9f71962
commit
27807e89bb
@ -39,7 +39,8 @@ CREATE TABLE IF NOT EXISTS mod_application_settings (
|
||||
required_role_id BIGINT NULL,
|
||||
reviewer_role_id BIGINT NULL,
|
||||
custom_questions JSONB NULL,
|
||||
cooldown_days INTEGER NOT NULL DEFAULT 30
|
||||
cooldown_days INTEGER NOT NULL DEFAULT 30,
|
||||
log_new_applications BOOLEAN NOT NULL DEFAULT FALSE
|
||||
);
|
||||
"""
|
||||
|
||||
@ -370,6 +371,18 @@ class ModApplicationCog(commands.Cog):
|
||||
)(settings_cooldown_command)
|
||||
self.modapp_group.add_command(settings_cooldown_command)
|
||||
|
||||
# --- Toggle Log New Applications Command ---
|
||||
settings_log_new_apps_command = app_commands.Command(
|
||||
name="settings_lognewapps",
|
||||
description="Toggle whether new applications are automatically logged in the log channel",
|
||||
callback=self.toggle_log_new_applications_callback,
|
||||
parent=self.modapp_group # Direct child of modapp
|
||||
)
|
||||
app_commands.describe(
|
||||
enabled="Whether new applications should be logged automatically"
|
||||
)(settings_log_new_apps_command)
|
||||
self.modapp_group.add_command(settings_log_new_apps_command)
|
||||
|
||||
# --- Command Callbacks ---
|
||||
|
||||
async def apply_callback(self, interaction: discord.Interaction):
|
||||
@ -703,6 +716,50 @@ class ModApplicationCog(commands.Cog):
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
async def toggle_log_new_applications_callback(self, interaction: discord.Interaction, enabled: bool):
|
||||
"""Handle the /modapp settings lognewapps command"""
|
||||
# Check if user has permission to manage applications
|
||||
if not await self.check_admin_permission(interaction.guild_id, interaction.user.id):
|
||||
await interaction.response.send_message(
|
||||
"❌ You don't have permission to manage application settings.",
|
||||
ephemeral=True
|
||||
)
|
||||
return
|
||||
|
||||
# Get current settings to check if log channel is set
|
||||
settings = await self.get_application_settings(interaction.guild_id)
|
||||
log_channel_id = settings.get("log_channel_id")
|
||||
|
||||
if enabled and not log_channel_id:
|
||||
await interaction.response.send_message(
|
||||
"❌ You need to set a log channel first using `/modapp settings_logchannel` before enabling this feature.",
|
||||
ephemeral=True
|
||||
)
|
||||
return
|
||||
|
||||
# Update setting in database
|
||||
success = await self.update_application_setting(interaction.guild_id, "log_new_applications", enabled)
|
||||
|
||||
if success:
|
||||
status = "enabled" if enabled else "disabled"
|
||||
if enabled:
|
||||
log_channel = interaction.guild.get_channel(log_channel_id)
|
||||
channel_mention = log_channel.mention if log_channel else "the configured log channel"
|
||||
await interaction.response.send_message(
|
||||
f"✅ New applications will now be automatically logged in {channel_mention}.",
|
||||
ephemeral=True
|
||||
)
|
||||
else:
|
||||
await interaction.response.send_message(
|
||||
"✅ New applications will no longer be automatically logged.",
|
||||
ephemeral=True
|
||||
)
|
||||
else:
|
||||
await interaction.response.send_message(
|
||||
"❌ Failed to update application settings.",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
# --- Database Helper Methods ---
|
||||
|
||||
async def submit_application(self, guild_id: int, user_id: int, form_data: Dict[str, str]) -> bool:
|
||||
@ -846,7 +903,8 @@ class ModApplicationCog(commands.Cog):
|
||||
"required_role_id": None,
|
||||
"reviewer_role_id": None,
|
||||
"custom_questions": None,
|
||||
"cooldown_days": 30
|
||||
"cooldown_days": 30,
|
||||
"log_new_applications": False
|
||||
}
|
||||
|
||||
# Convert row to dictionary and parse custom_questions JSON if it exists
|
||||
@ -1012,6 +1070,8 @@ class ModApplicationCog(commands.Cog):
|
||||
# Get application settings
|
||||
settings = await self.get_application_settings(guild.id)
|
||||
review_channel_id = settings.get("review_channel_id")
|
||||
log_channel_id = settings.get("log_channel_id")
|
||||
log_new_applications = settings.get("log_new_applications", False)
|
||||
|
||||
if not review_channel_id:
|
||||
return
|
||||
@ -1080,7 +1140,7 @@ class ModApplicationCog(commands.Cog):
|
||||
custom_id=f"view_application_{application['application_id']}"
|
||||
))
|
||||
|
||||
# Send the notification
|
||||
# Send the notification to the review channel
|
||||
try:
|
||||
await review_channel.send(
|
||||
content=f"📝 New moderator application from {user.mention}",
|
||||
@ -1088,7 +1148,28 @@ class ModApplicationCog(commands.Cog):
|
||||
view=view
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending application notification: {e}")
|
||||
logger.error(f"Error sending application notification to review channel: {e}")
|
||||
|
||||
# If log_new_applications is enabled and log_channel_id is set, also log to the log channel
|
||||
if log_new_applications and log_channel_id:
|
||||
log_channel = guild.get_channel(log_channel_id)
|
||||
if log_channel:
|
||||
try:
|
||||
# Create a simpler embed for the log channel
|
||||
log_embed = discord.Embed(
|
||||
title="New Moderator Application Submitted",
|
||||
description=f"A new moderator application has been submitted by {user.mention}.",
|
||||
color=discord.Color.blue(),
|
||||
timestamp=datetime.datetime.now()
|
||||
)
|
||||
log_embed.set_author(name=f"{user.name}", icon_url=user.display_avatar.url)
|
||||
log_embed.add_field(name="Application ID", value=application["application_id"], inline=True)
|
||||
log_embed.add_field(name="Status", value="PENDING", inline=True)
|
||||
log_embed.add_field(name="Submission Time", value=discord.utils.format_dt(datetime.datetime.now()), inline=True)
|
||||
|
||||
await log_channel.send(embed=log_embed)
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending application notification to log channel: {e}")
|
||||
|
||||
async def notify_application_status_change(self, guild: discord.Guild, user_id: int, status: APPLICATION_STATUS, reason: Optional[str] = None) -> None:
|
||||
"""Notify the applicant about a status change"""
|
||||
|
@ -7,6 +7,7 @@ from typing import Optional, Union, List
|
||||
|
||||
# Use absolute import for ModLogCog
|
||||
from discordbot.cogs.mod_log_cog import ModLogCog
|
||||
from discordbot.db import mod_log_db # Import the database functions
|
||||
|
||||
# Configure logging
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -138,6 +139,31 @@ class ModerationCog(commands.Cog):
|
||||
)(dm_banned_command)
|
||||
self.moderate_group.add_command(dm_banned_command)
|
||||
|
||||
# --- View Infractions Command ---
|
||||
view_infractions_command = app_commands.Command(
|
||||
name="infractions",
|
||||
description="View moderation infractions for a user",
|
||||
callback=self.moderate_view_infractions_callback,
|
||||
parent=self.moderate_group
|
||||
)
|
||||
app_commands.describe(
|
||||
member="The member whose infractions to view"
|
||||
)(view_infractions_command)
|
||||
self.moderate_group.add_command(view_infractions_command)
|
||||
|
||||
# --- Remove Infraction Command ---
|
||||
remove_infraction_command = app_commands.Command(
|
||||
name="removeinfraction",
|
||||
description="Remove a specific infraction by its case ID",
|
||||
callback=self.moderate_remove_infraction_callback,
|
||||
parent=self.moderate_group
|
||||
)
|
||||
app_commands.describe(
|
||||
case_id="The case ID of the infraction to remove",
|
||||
reason="The reason for removing the infraction"
|
||||
)(remove_infraction_command)
|
||||
self.moderate_group.add_command(remove_infraction_command)
|
||||
|
||||
# Helper method for parsing duration strings
|
||||
def _parse_duration(self, duration_str: str) -> Optional[datetime.timedelta]:
|
||||
"""Parse a duration string like '1d', '2h', '30m' into a timedelta."""
|
||||
@ -712,6 +738,95 @@ class ModerationCog(commands.Cog):
|
||||
logger.error(f"Error sending DM to banned user {banned_user} (ID: {banned_user.id}): {e}")
|
||||
await interaction.response.send_message(f"❌ An unexpected error occurred: {e}", ephemeral=True)
|
||||
|
||||
async def moderate_view_infractions_callback(self, interaction: discord.Interaction, member: discord.Member):
|
||||
"""View moderation infractions for a user."""
|
||||
if not interaction.user.guild_permissions.kick_members: # Using kick_members as a general mod permission
|
||||
await interaction.response.send_message("❌ You don't have permission to view infractions.", ephemeral=True)
|
||||
return
|
||||
|
||||
if not self.bot.pg_pool:
|
||||
await interaction.response.send_message("❌ Database connection is not available.", ephemeral=True)
|
||||
logger.error("Cannot view infractions: pg_pool is None.")
|
||||
return
|
||||
|
||||
infractions = await mod_log_db.get_user_mod_logs(self.bot.pg_pool, interaction.guild.id, member.id)
|
||||
|
||||
if not infractions:
|
||||
await interaction.response.send_message(f"No infractions found for {member.mention}.", ephemeral=True)
|
||||
return
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"Infractions for {member.display_name}",
|
||||
color=discord.Color.orange()
|
||||
)
|
||||
embed.set_thumbnail(url=member.display_avatar.url)
|
||||
|
||||
for infraction in infractions[:25]: # Display up to 25 infractions
|
||||
action_type = infraction['action_type']
|
||||
reason = infraction['reason'] or "No reason provided"
|
||||
moderator_id = infraction['moderator_id']
|
||||
timestamp = infraction['timestamp']
|
||||
case_id = infraction['case_id']
|
||||
duration_seconds = infraction['duration_seconds']
|
||||
|
||||
moderator = interaction.guild.get_member(moderator_id) or f"ID: {moderator_id}"
|
||||
|
||||
value = f"**Case ID:** {case_id}\n"
|
||||
value += f"**Action:** {action_type}\n"
|
||||
value += f"**Moderator:** {moderator}\n"
|
||||
if duration_seconds:
|
||||
duration_str = str(datetime.timedelta(seconds=duration_seconds))
|
||||
value += f"**Duration:** {duration_str}\n"
|
||||
value += f"**Reason:** {reason}\n"
|
||||
value += f"**Date:** {discord.utils.format_dt(timestamp, style='f')}"
|
||||
|
||||
embed.add_field(name=f"Infraction #{case_id}", value=value, inline=False)
|
||||
|
||||
if len(infractions) > 25:
|
||||
embed.set_footer(text=f"Showing 25 of {len(infractions)} infractions.")
|
||||
|
||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||
|
||||
async def moderate_remove_infraction_callback(self, interaction: discord.Interaction, case_id: int, reason: str = None):
|
||||
"""Remove a specific infraction by its case ID."""
|
||||
if not interaction.user.guild_permissions.ban_members: # Higher permission for removing infractions
|
||||
await interaction.response.send_message("❌ You don't have permission to remove infractions.", ephemeral=True)
|
||||
return
|
||||
|
||||
if not self.bot.pg_pool:
|
||||
await interaction.response.send_message("❌ Database connection is not available.", ephemeral=True)
|
||||
logger.error("Cannot remove infraction: pg_pool is None.")
|
||||
return
|
||||
|
||||
# Fetch the infraction to ensure it exists and to log details
|
||||
infraction_to_remove = await mod_log_db.get_mod_log(self.bot.pg_pool, case_id)
|
||||
if not infraction_to_remove or infraction_to_remove['guild_id'] != interaction.guild.id:
|
||||
await interaction.response.send_message(f"❌ Infraction with Case ID {case_id} not found in this server.", ephemeral=True)
|
||||
return
|
||||
|
||||
deleted = await mod_log_db.delete_mod_log(self.bot.pg_pool, case_id, interaction.guild.id)
|
||||
|
||||
if deleted:
|
||||
logger.info(f"Infraction (Case ID: {case_id}) removed by {interaction.user} (ID: {interaction.user.id}) in guild {interaction.guild.id}. Reason: {reason}")
|
||||
|
||||
# Log the removal action itself
|
||||
mod_log_cog: ModLogCog = self.bot.get_cog('ModLogCog')
|
||||
if mod_log_cog:
|
||||
target_user_id = infraction_to_remove['target_user_id']
|
||||
target_user = await self.bot.fetch_user(target_user_id) # Fetch user for logging
|
||||
|
||||
await mod_log_cog.log_action(
|
||||
guild=interaction.guild,
|
||||
moderator=interaction.user,
|
||||
target=target_user if target_user else Object(id=target_user_id),
|
||||
action_type="REMOVE_INFRACTION",
|
||||
reason=f"Removed Case ID {case_id}. Original reason: {infraction_to_remove['reason']}. Removal reason: {reason or 'Not specified'}",
|
||||
duration=None
|
||||
)
|
||||
await interaction.response.send_message(f"✅ Infraction with Case ID {case_id} has been removed. Reason: {reason or 'Not specified'}", ephemeral=True)
|
||||
else:
|
||||
await interaction.response.send_message(f"❌ Failed to remove infraction with Case ID {case_id}. It might have already been removed or an error occurred.", ephemeral=True)
|
||||
|
||||
# --- Legacy Command Handlers (for prefix commands) ---
|
||||
|
||||
@commands.command(name="timeout")
|
||||
|
@ -459,3 +459,32 @@ async def log_action_safe(bot_instance, guild_id: int, target_user_id: int, acti
|
||||
|
||||
# Otherwise, use the helper function to run in the bot's loop
|
||||
return run_in_bot_loop(bot_instance, _log_action_coro)
|
||||
|
||||
async def delete_mod_log(pool: asyncpg.Pool, case_id: int, guild_id: int) -> bool:
|
||||
"""Deletes a specific moderation log entry by case_id, ensuring it belongs to the guild."""
|
||||
query = """
|
||||
DELETE FROM moderation_logs
|
||||
WHERE case_id = $1 AND guild_id = $2;
|
||||
"""
|
||||
connection, success = await create_connection_with_retry(pool)
|
||||
if not success or not connection:
|
||||
log.error(f"Failed to acquire database connection for deleting mod log for case_id {case_id} in guild {guild_id}")
|
||||
return False
|
||||
|
||||
try:
|
||||
async with connection.transaction():
|
||||
result = await connection.execute(query, case_id, guild_id)
|
||||
if result == "DELETE 1":
|
||||
log.info(f"Deleted mod log entry for case_id {case_id} in guild {guild_id}")
|
||||
return True
|
||||
else:
|
||||
log.warning(f"Could not delete mod log entry for case_id {case_id} in guild {guild_id}. Case might not exist or not belong to this guild.")
|
||||
return False
|
||||
except Exception as e:
|
||||
log.exception(f"Error deleting mod log entry for case_id {case_id} in guild {guild_id}: {e}")
|
||||
return False
|
||||
finally:
|
||||
try:
|
||||
await pool.release(connection)
|
||||
except Exception as e:
|
||||
log.warning(f"Error releasing connection back to pool after deleting mod log: {e}")
|
||||
|
Loading…
x
Reference in New Issue
Block a user