feat: Add send_dm option to ban command and update modal for user notification preferences

This commit is contained in:
Slipstream 2025-05-18 14:52:28 -06:00
parent 777e206d07
commit cdad3244f8
Signed by: slipstream
GPG Key ID: 13E498CE010AC6FD

View File

@ -43,8 +43,19 @@ class ModerationCog(commands.Cog):
app_commands.describe(
member="The member to ban",
reason="The reason for the ban",
delete_days="Number of days of messages to delete (0-7)"
delete_days="Number of days of messages to delete (0-7)",
send_dm="Whether to send a DM notification to the user (default: True)"
)(ban_command)
# Add the send_dm parameter with a default value of True
send_dm_param = app_commands.Parameter(
name="send_dm",
description="Whether to send a DM notification to the user",
type=bool,
default=True
)
ban_command.parameters.append(send_dm_param)
self.moderate_group.add_command(ban_command)
# --- Unban Command ---
@ -203,7 +214,7 @@ class ModerationCog(commands.Cog):
# --- Command Callbacks ---
async def moderate_ban_callback(self, interaction: discord.Interaction, member: discord.Member, reason: str = None, delete_days: int = 0):
async def moderate_ban_callback(self, interaction: discord.Interaction, member: discord.Member, reason: str = None, delete_days: int = 0, send_dm: bool = True):
"""Ban a member from the server."""
# Check if the user has permission to ban members
if not interaction.user.guild_permissions.ban_members:
@ -238,25 +249,26 @@ class ModerationCog(commands.Cog):
# Ensure delete_days is within valid range (0-7)
delete_days = max(0, min(7, delete_days))
# Try to send a DM to the user before banning them
# Try to send a DM to the user before banning them (if send_dm is True)
dm_sent = False
try:
embed = discord.Embed(
title="Ban Notice",
description=f"You have been banned from **{interaction.guild.name}**",
color=discord.Color.red()
)
embed.add_field(name="Reason", value=reason or "No reason provided", inline=False)
embed.add_field(name="Moderator", value=interaction.user.name, inline=False)
embed.set_footer(text=f"Server ID: {interaction.guild.id}{discord.utils.utcnow().strftime('%Y-%m-%d %H:%M:%S')} UTC")
if send_dm:
try:
embed = discord.Embed(
title="Ban Notice",
description=f"You have been banned from **{interaction.guild.name}**",
color=discord.Color.red()
)
embed.add_field(name="Reason", value=reason or "No reason provided", inline=False)
embed.add_field(name="Moderator", value=interaction.user.name, inline=False)
embed.set_footer(text=f"Server ID: {interaction.guild.id}{discord.utils.utcnow().strftime('%Y-%m-%d %H:%M:%S')} UTC")
await member.send(embed=embed)
dm_sent = True
except discord.Forbidden:
# User has DMs closed, ignore
pass
except Exception as e:
logger.error(f"Error sending ban DM to {member} (ID: {member.id}): {e}")
await member.send(embed=embed)
dm_sent = True
except discord.Forbidden:
# User has DMs closed, ignore
pass
except Exception as e:
logger.error(f"Error sending ban DM to {member} (ID: {member.id}): {e}")
# Perform the ban
try:
@ -280,8 +292,11 @@ class ModerationCog(commands.Cog):
# -------------------------
# Send confirmation message with DM status
dm_status = "✅ DM notification sent" if dm_sent else "❌ Could not send DM notification (user may have DMs disabled)"
await interaction.response.send_message(f"🔨 **Banned {member.mention}**! Reason: {reason or 'No reason provided'}\n{dm_status}")
if send_dm:
dm_status = "✅ DM notification sent" if dm_sent else "❌ Could not send DM notification (user may have DMs disabled)"
await interaction.response.send_message(f"🔨 **Banned {member.mention}**! Reason: {reason or 'No reason provided'}\n{dm_status}")
else:
await interaction.response.send_message(f"🔨 **Banned {member.mention}**! Reason: {reason or 'No reason provided'}\n⚠️ DM notification was disabled")
except discord.Forbidden:
await interaction.response.send_message("❌ I don't have permission to ban this member.", ephemeral=True)
except discord.HTTPException as e:
@ -798,7 +813,7 @@ class ModerationCog(commands.Cog):
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"
@ -807,12 +822,12 @@ class ModerationCog(commands.Cog):
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):
@ -836,20 +851,20 @@ class ModerationCog(commands.Cog):
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
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:
@ -881,7 +896,7 @@ class ModerationCog(commands.Cog):
if deleted_count > 0:
logger.info(f"{deleted_count} infractions for user {member} (ID: {member.id}) cleared by {interaction.user} (ID: {interaction.user.id}) in guild {interaction.guild.id}. Reason: {reason}")
# Log the clear all action
mod_log_cog: ModLogCog = self.bot.get_cog('ModLogCog')
if mod_log_cog:
@ -1132,6 +1147,7 @@ class BanModal(discord.ui.Modal, title="Ban User"):
def __init__(self, member: discord.Member):
super().__init__()
self.member = member
self.send_dm = True # Default to True
reason = discord.ui.TextInput(
label="Reason",
@ -1156,7 +1172,7 @@ class BanModal(discord.ui.Modal, title="Ban User"):
reason = self.reason.value or "No reason provided"
delete_days = int(self.delete_days.value) if self.delete_days.value and self.delete_days.value.isdigit() else 0
# Call the existing ban callback
await cog.moderate_ban_callback(interaction, self.member, reason=reason, delete_days=delete_days)
await cog.moderate_ban_callback(interaction, self.member, reason=reason, delete_days=delete_days, send_dm=self.send_dm)
else:
await interaction.followup.send("Error: Moderation cog not found.", ephemeral=True)
@ -1243,6 +1259,33 @@ class RemoveTimeoutModal(discord.ui.Modal, title="Remove Timeout"):
# Context menu commands must be defined at module level
class BanOptionsView(discord.ui.View):
def __init__(self, member: discord.Member):
super().__init__(timeout=60) # 60 second timeout
self.member = member
self.send_dm = True # Default to True
self.update_button_label()
def update_button_label(self):
self.toggle_dm_button.label = f"Send DM: {'Yes' if self.send_dm else 'No'}"
self.toggle_dm_button.style = discord.ButtonStyle.green if self.send_dm else discord.ButtonStyle.red
@discord.ui.button(label="Send DM: Yes", style=discord.ButtonStyle.green, custom_id="toggle_dm")
async def toggle_dm_button(self, interaction: discord.Interaction, _: discord.ui.Button):
# Toggle the send_dm value
self.send_dm = not self.send_dm
self.update_button_label()
await interaction.response.edit_message(view=self)
@discord.ui.button(label="Continue to Ban", style=discord.ButtonStyle.danger, custom_id="continue_ban")
async def continue_button(self, interaction: discord.Interaction, _: discord.ui.Button):
# Create and show the modal
modal = BanModal(self.member)
modal.send_dm = self.send_dm # Pass the send_dm setting to the modal
await interaction.response.send_modal(modal)
# Stop listening for interactions on this view
self.stop()
@app_commands.context_menu(name="Ban User")
async def ban_user_context_menu(interaction: discord.Interaction, member: discord.Member):
"""Bans the selected user via a modal."""
@ -1266,8 +1309,13 @@ async def ban_user_context_menu(interaction: discord.Interaction, member: discor
await interaction.response.send_message("❌ I cannot ban myself.", ephemeral=True)
return
modal = BanModal(member)
await interaction.response.send_modal(modal)
# Show options view first
view = BanOptionsView(member)
await interaction.response.send_message(
f"⚠️ You are about to ban **{member.display_name}** ({member.id}).\nPlease select your options:",
view=view,
ephemeral=True
)
@app_commands.context_menu(name="Kick User")
async def kick_user_context_menu(interaction: discord.Interaction, member: discord.Member):