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,
|
required_role_id BIGINT NULL,
|
||||||
reviewer_role_id BIGINT NULL,
|
reviewer_role_id BIGINT NULL,
|
||||||
custom_questions JSONB 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)
|
)(settings_cooldown_command)
|
||||||
self.modapp_group.add_command(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 ---
|
# --- Command Callbacks ---
|
||||||
|
|
||||||
async def apply_callback(self, interaction: discord.Interaction):
|
async def apply_callback(self, interaction: discord.Interaction):
|
||||||
@ -703,6 +716,50 @@ class ModApplicationCog(commands.Cog):
|
|||||||
ephemeral=True
|
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 ---
|
# --- Database Helper Methods ---
|
||||||
|
|
||||||
async def submit_application(self, guild_id: int, user_id: int, form_data: Dict[str, str]) -> bool:
|
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,
|
"required_role_id": None,
|
||||||
"reviewer_role_id": None,
|
"reviewer_role_id": None,
|
||||||
"custom_questions": 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
|
# Convert row to dictionary and parse custom_questions JSON if it exists
|
||||||
@ -1012,6 +1070,8 @@ class ModApplicationCog(commands.Cog):
|
|||||||
# Get application settings
|
# Get application settings
|
||||||
settings = await self.get_application_settings(guild.id)
|
settings = await self.get_application_settings(guild.id)
|
||||||
review_channel_id = settings.get("review_channel_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:
|
if not review_channel_id:
|
||||||
return
|
return
|
||||||
@ -1080,7 +1140,7 @@ class ModApplicationCog(commands.Cog):
|
|||||||
custom_id=f"view_application_{application['application_id']}"
|
custom_id=f"view_application_{application['application_id']}"
|
||||||
))
|
))
|
||||||
|
|
||||||
# Send the notification
|
# Send the notification to the review channel
|
||||||
try:
|
try:
|
||||||
await review_channel.send(
|
await review_channel.send(
|
||||||
content=f"📝 New moderator application from {user.mention}",
|
content=f"📝 New moderator application from {user.mention}",
|
||||||
@ -1088,7 +1148,28 @@ class ModApplicationCog(commands.Cog):
|
|||||||
view=view
|
view=view
|
||||||
)
|
)
|
||||||
except Exception as e:
|
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:
|
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"""
|
"""Notify the applicant about a status change"""
|
||||||
|
@ -7,6 +7,7 @@ from typing import Optional, Union, List
|
|||||||
|
|
||||||
# Use absolute import for ModLogCog
|
# Use absolute import for ModLogCog
|
||||||
from discordbot.cogs.mod_log_cog import ModLogCog
|
from discordbot.cogs.mod_log_cog import ModLogCog
|
||||||
|
from discordbot.db import mod_log_db # Import the database functions
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -138,6 +139,31 @@ class ModerationCog(commands.Cog):
|
|||||||
)(dm_banned_command)
|
)(dm_banned_command)
|
||||||
self.moderate_group.add_command(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
|
# Helper method for parsing duration strings
|
||||||
def _parse_duration(self, duration_str: str) -> Optional[datetime.timedelta]:
|
def _parse_duration(self, duration_str: str) -> Optional[datetime.timedelta]:
|
||||||
"""Parse a duration string like '1d', '2h', '30m' into a 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}")
|
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)
|
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) ---
|
# --- Legacy Command Handlers (for prefix commands) ---
|
||||||
|
|
||||||
@commands.command(name="timeout")
|
@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
|
# Otherwise, use the helper function to run in the bot's loop
|
||||||
return run_in_bot_loop(bot_instance, _log_action_coro)
|
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