Compare commits
10 Commits
02e9de031d
...
test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99a04b9a6b | ||
|
|
9bc222659d | ||
|
|
acb5216bc5 | ||
|
|
c59d6d888f | ||
|
|
1417320164 | ||
|
|
338b77ddfd | ||
|
|
618819707c | ||
|
|
8ba7a1bf98 | ||
|
|
cb91f4884a | ||
|
|
4d422068b6 |
141
bot.py
141
bot.py
@@ -3255,8 +3255,8 @@ async def modhelp(ctx):
|
|||||||
embed.add_field(
|
embed.add_field(
|
||||||
name="👮 Moderator Commands (Level 5+)",
|
name="👮 Moderator Commands (Level 5+)",
|
||||||
value=(
|
value=(
|
||||||
"`/warn <user> <reason> [message_id]` - Warn a user (with optional message reference)\n"
|
"`/warn <user> <reason> [message_id] [silent:True]` - Warn a user (with optional silent mode)\n"
|
||||||
"`/mute <user> <duration> [reason] [message_id]` - Mute a user temporarily\n"
|
"`/mute <user> <duration> [reason] [message_id] [silent:True]` - Mute a user temporarily (with optional silent mode)\n"
|
||||||
"`/unmute <user>` - Manually unmute a user (deactivates all active mutes)\n"
|
"`/unmute <user>` - Manually unmute a user (deactivates all active mutes)\n"
|
||||||
"`/viewmute <id_or_uuid>` - View detailed mute information by ID or UUID\n"
|
"`/viewmute <id_or_uuid>` - View detailed mute information by ID or UUID\n"
|
||||||
"`/listmutes [status]` - List mutes (active, completed, expired, cancelled, all)\n"
|
"`/listmutes [status]` - List mutes (active, completed, expired, cancelled, all)\n"
|
||||||
@@ -3316,7 +3316,22 @@ async def modhelp(ctx):
|
|||||||
"• **Mute IDs**: Easy-to-use numeric IDs for tracking (e.g. `/viewmute 123`)\n"
|
"• **Mute IDs**: Easy-to-use numeric IDs for tracking (e.g. `/viewmute 123`)\n"
|
||||||
"• **Multiple Mutes**: Can handle multiple active mutes per user\n"
|
"• **Multiple Mutes**: Can handle multiple active mutes per user\n"
|
||||||
"• **Smart Notifications**: Auto-unmute alerts sent to mod log channel\n"
|
"• **Smart Notifications**: Auto-unmute alerts sent to mod log channel\n"
|
||||||
"• **Role Restoration**: Automatically restores previous roles after mute"
|
"• **Role Restoration**: Automatically restores previous roles after mute\n"
|
||||||
|
"• **Silent Mode**: Optional discrete moderation (ephemeral responses only)"
|
||||||
|
),
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Silent Mode Features
|
||||||
|
embed.add_field(
|
||||||
|
name="🔇 Silent Mode Features",
|
||||||
|
value=(
|
||||||
|
"• **Discrete Moderation**: Use `silent:True` parameter with warn/mute commands\n"
|
||||||
|
"• **Ephemeral Responses**: Only moderator sees the action confirmation\n"
|
||||||
|
"• **User Notifications**: Target user still receives DM about action\n"
|
||||||
|
"• **Full Logging**: Mod log channel records all actions normally\n"
|
||||||
|
"• **No Public Messages**: Regular users don't see moderation announcements\n"
|
||||||
|
"• **Perfect for**: Handling sensitive issues without public drama"
|
||||||
),
|
),
|
||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
@@ -4723,26 +4738,32 @@ async def restore_user_roles(user, guild):
|
|||||||
close_database_connection(connection)
|
close_database_connection(connection)
|
||||||
|
|
||||||
@client.hybrid_command()
|
@client.hybrid_command()
|
||||||
async def warn(ctx, user: discord.User, reason: str = "No reason provided", message_id: str = None, context_range: int = 3):
|
async def warn(ctx, user: discord.User, reason: str = "No reason provided", message_id: str = None, context_range: int = 3, silent: bool = False):
|
||||||
"""Warns a user (Requires Permission Level 5 or higher)
|
"""Warns a user (Requires Permission Level 5 or higher)
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
/warn @user "Inappropriate behavior"
|
/warn @user "Inappropriate behavior"
|
||||||
/warn @user "Bad language" 1407754702564884622
|
/warn @user "Bad language" 1407754702564884622
|
||||||
/warn @user "Spam" 1407754702564884622 15
|
/warn @user "Spam" 1407754702564884622 15
|
||||||
|
/warn @user "Bad behavior" silent:True (silent mode - no public announcement)
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- user: The user to warn
|
- user: The user to warn
|
||||||
- reason: Reason for the warning
|
- reason: Reason for the warning
|
||||||
- message_id: Optional message ID to reference (required for context_range)
|
- message_id: Optional message ID to reference (required for context_range)
|
||||||
- context_range: Number of messages before/after to archive (only works with message_id)
|
- context_range: Number of messages before/after to archive (only works with message_id)
|
||||||
|
- silent: If True, only send ephemeral response to mod (no public message)
|
||||||
|
|
||||||
Note: context_range parameter only works when message_id is also provided!
|
Note: context_range parameter only works when message_id is also provided!
|
||||||
"""
|
"""
|
||||||
# Check if it's a slash command and defer if needed
|
# Check if it's a slash command and defer if needed
|
||||||
is_slash_command = hasattr(ctx, 'interaction') and ctx.interaction
|
is_slash_command = hasattr(ctx, 'interaction') and ctx.interaction
|
||||||
|
if silent:
|
||||||
|
logger.info(f"Silent warn command - is_slash_command: {is_slash_command}, has interaction: {hasattr(ctx, 'interaction')}, interaction value: {getattr(ctx, 'interaction', None)}")
|
||||||
|
|
||||||
|
# For slash commands, always defer to ensure we have a response method
|
||||||
if is_slash_command:
|
if is_slash_command:
|
||||||
await ctx.defer()
|
await ctx.defer(ephemeral=silent) # Defer as ephemeral if silent mode
|
||||||
|
|
||||||
# Helper function for sending responses
|
# Helper function for sending responses
|
||||||
async def send_response(content=None, embed=None, ephemeral=False, file=None):
|
async def send_response(content=None, embed=None, ephemeral=False, file=None):
|
||||||
@@ -4991,6 +5012,40 @@ async def warn(ctx, user: discord.User, reason: str = "No reason provided", mess
|
|||||||
inline=False
|
inline=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Send response based on silent mode
|
||||||
|
if silent:
|
||||||
|
# Silent mode: create special silent embed and send as ephemeral
|
||||||
|
silent_embed = discord.Embed(
|
||||||
|
title="🔇 Silent Warning Issued",
|
||||||
|
description=f"{user.mention} has been warned silently.",
|
||||||
|
color=0xff9500,
|
||||||
|
timestamp=datetime.now()
|
||||||
|
)
|
||||||
|
silent_embed.add_field(name="📝 Reason", value=reason, inline=False)
|
||||||
|
silent_embed.add_field(name="⚠️ Warning Count", value=f"{target_data['warns']}/{warn_threshold}", inline=True)
|
||||||
|
if warning_id:
|
||||||
|
silent_embed.add_field(name="🆔 Warning ID", value=str(warning_id), inline=True)
|
||||||
|
|
||||||
|
silent_embed.add_field(name="🔔 Actions Taken",
|
||||||
|
value="• User received DM notification\n• Mod log entry created\n• No public announcement",
|
||||||
|
inline=False)
|
||||||
|
silent_embed.set_footer(text=f"Silent Mode • User ID: {user.id}")
|
||||||
|
silent_embed.set_thumbnail(url=user.display_avatar.url)
|
||||||
|
|
||||||
|
# Send as ephemeral
|
||||||
|
if is_slash_command:
|
||||||
|
if hasattr(ctx, 'followup') and ctx.followup is not None:
|
||||||
|
await ctx.followup.send(embed=silent_embed, ephemeral=True)
|
||||||
|
logger.info(f"Silent warning sent via ctx.followup.send (ephemeral)")
|
||||||
|
elif hasattr(ctx, 'interaction') and ctx.interaction:
|
||||||
|
await ctx.interaction.followup.send(embed=silent_embed, ephemeral=True)
|
||||||
|
logger.info(f"Silent warning sent via ctx.interaction.followup.send (ephemeral)")
|
||||||
|
else:
|
||||||
|
logger.error(f"Silent warning failed: No followup available")
|
||||||
|
else:
|
||||||
|
logger.error(f"Silent warning attempted with prefix command - not supported")
|
||||||
|
else:
|
||||||
|
# Normal mode: send public response
|
||||||
await send_response(embed=embed)
|
await send_response(embed=embed)
|
||||||
|
|
||||||
# Log the warning action
|
# Log the warning action
|
||||||
@@ -6232,13 +6287,14 @@ async def restorewarn(ctx, warning_id: int):
|
|||||||
close_database_connection(connection)
|
close_database_connection(connection)
|
||||||
|
|
||||||
@client.hybrid_command()
|
@client.hybrid_command()
|
||||||
async def mute(ctx, user: discord.User, duration: str, reason: str = "No reason provided", message_id: str = None, context_range: int = 3):
|
async def mute(ctx, user: discord.User, duration: str, reason: str = "No reason provided", message_id: str = None, context_range: int = 3, silent: bool = False):
|
||||||
"""Mutes a user for a specified duration (Requires Permission Level 5 or higher)
|
"""Mutes a user for a specified duration (Requires Permission Level 5 or higher)
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
/mute @user 10m "Inappropriate behavior"
|
/mute @user 10m "Inappropriate behavior"
|
||||||
/mute @user 1h "Bad language" 1407754702564884622
|
/mute @user 1h "Bad language" 1407754702564884622
|
||||||
/mute @user 1h "Bad language" 1407754702564884622 15
|
/mute @user 1h "Bad language" 1407754702564884622 15
|
||||||
|
/mute @user 30m "Spamming" silent:True (silent mode - no public announcement)
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- user: The user to mute
|
- user: The user to mute
|
||||||
@@ -6246,6 +6302,7 @@ async def mute(ctx, user: discord.User, duration: str, reason: str = "No reason
|
|||||||
- reason: Reason for the mute (can include message ID and context range)
|
- reason: Reason for the mute (can include message ID and context range)
|
||||||
- message_id: Optional message ID to reference (required for context_range)
|
- message_id: Optional message ID to reference (required for context_range)
|
||||||
- context_range: Number of context messages to archive (only works with message_id)
|
- context_range: Number of context messages to archive (only works with message_id)
|
||||||
|
- silent: If True, only send ephemeral response to mod (no public message)
|
||||||
|
|
||||||
Duration examples:
|
Duration examples:
|
||||||
- 10m = 10 minutes
|
- 10m = 10 minutes
|
||||||
@@ -6259,8 +6316,10 @@ async def mute(ctx, user: discord.User, duration: str, reason: str = "No reason
|
|||||||
"""
|
"""
|
||||||
# Check if it's a slash command and defer if needed
|
# Check if it's a slash command and defer if needed
|
||||||
is_slash_command = hasattr(ctx, 'interaction') and ctx.interaction
|
is_slash_command = hasattr(ctx, 'interaction') and ctx.interaction
|
||||||
|
|
||||||
|
# For slash commands, always defer to ensure we have a response method
|
||||||
if is_slash_command:
|
if is_slash_command:
|
||||||
await ctx.defer()
|
await ctx.defer(ephemeral=silent) # Defer as ephemeral if silent mode
|
||||||
|
|
||||||
# Helper function for sending responses
|
# Helper function for sending responses
|
||||||
async def send_response(content=None, embed=None, ephemeral=False, file=None):
|
async def send_response(content=None, embed=None, ephemeral=False, file=None):
|
||||||
@@ -6524,6 +6583,74 @@ async def mute(ctx, user: discord.User, duration: str, reason: str = "No reason
|
|||||||
embed.set_footer(text=f"User ID: {user.id} | Process ID: {str(process_uuid)[:8]} | Use /viewmute {mute_id} for details")
|
embed.set_footer(text=f"User ID: {user.id} | Process ID: {str(process_uuid)[:8]} | Use /viewmute {mute_id} for details")
|
||||||
embed.set_thumbnail(url=user.display_avatar.url)
|
embed.set_thumbnail(url=user.display_avatar.url)
|
||||||
|
|
||||||
|
# Send response based on silent mode
|
||||||
|
if silent:
|
||||||
|
# Silent mode: ephemeral response to moderator only
|
||||||
|
silent_embed = discord.Embed(
|
||||||
|
title="🔇 Silent Mute Applied",
|
||||||
|
description=f"{user.mention} has been muted silently.",
|
||||||
|
color=0xff0000,
|
||||||
|
timestamp=datetime.now()
|
||||||
|
)
|
||||||
|
silent_embed.add_field(name="⏱️ Duration", value=duration, inline=True)
|
||||||
|
silent_embed.add_field(name="⏰ Ends At", value=f"<t:{int(end_time.timestamp())}:F>", inline=True)
|
||||||
|
silent_embed.add_field(name="📝 Reason", value=reason, inline=False)
|
||||||
|
silent_embed.add_field(name="🔇 Mute Count", value=f"{user_data['mutes']}", inline=True)
|
||||||
|
silent_embed.add_field(name="🆔 Mute Record ID", value=f"`{mute_id}`", inline=True)
|
||||||
|
|
||||||
|
silent_embed.add_field(name="🔔 Actions Taken",
|
||||||
|
value="• User muted with timeout role\n• User received DM notification\n• Mod log entry created\n• No public announcement",
|
||||||
|
inline=False)
|
||||||
|
silent_embed.set_footer(text=f"Silent Mode • User ID: {user.id} | Use /viewmute {mute_id} for details")
|
||||||
|
silent_embed.set_thumbnail(url=user.display_avatar.url)
|
||||||
|
|
||||||
|
# Send ephemeral response - use followup since we deferred
|
||||||
|
try:
|
||||||
|
if is_slash_command:
|
||||||
|
# Since we deferred with ephemeral=silent, use followup
|
||||||
|
# Make sure followup is available and properly initialized
|
||||||
|
if hasattr(ctx, 'followup') and ctx.followup is not None:
|
||||||
|
await ctx.followup.send(embed=silent_embed, ephemeral=True)
|
||||||
|
logger.info(f"Silent mute sent via ctx.followup.send (ephemeral)")
|
||||||
|
elif hasattr(ctx, 'interaction') and ctx.interaction:
|
||||||
|
# Direct interaction followup as fallback
|
||||||
|
await ctx.interaction.followup.send(embed=silent_embed, ephemeral=True)
|
||||||
|
logger.info(f"Silent mute sent via ctx.interaction.followup.send (ephemeral)")
|
||||||
|
else:
|
||||||
|
logger.error(f"Silent mute failed: No followup available - ctx.followup: {getattr(ctx, 'followup', None)}")
|
||||||
|
raise Exception("No followup available after defer")
|
||||||
|
else:
|
||||||
|
# For prefix commands, we can't do true ephemeral, so log error instead
|
||||||
|
logger.error(f"Silent mute attempted with prefix command - not supported")
|
||||||
|
raise Exception("Silent mode only works with slash commands")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error sending silent mute response: {e}")
|
||||||
|
# Send error to mod log instead of fallback message
|
||||||
|
try:
|
||||||
|
await log_moderation_action(
|
||||||
|
guild=ctx.guild,
|
||||||
|
action_type="mute_error",
|
||||||
|
moderator=ctx.author,
|
||||||
|
target_user=user,
|
||||||
|
reason=f"Silent mute issued but ephemeral response failed: {str(e)}",
|
||||||
|
duration=duration,
|
||||||
|
additional_info={
|
||||||
|
"Original Reason": reason,
|
||||||
|
"Mute ID": str(mute_id) if mute_id else "N/A",
|
||||||
|
"Duration": duration,
|
||||||
|
"Error Details": str(e),
|
||||||
|
"Command Type": "Slash" if is_slash_command else "Prefix",
|
||||||
|
"Fallback": "User received DM notification normally"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except Exception as log_error:
|
||||||
|
logger.error(f"Failed to log silent mute error: {log_error}")
|
||||||
|
|
||||||
|
# Silent mode is complete - exit here to prevent normal logging/responses
|
||||||
|
return
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Normal mode: public response
|
||||||
await send_response(embed=embed)
|
await send_response(embed=embed)
|
||||||
|
|
||||||
# Log the mute action
|
# Log the mute action
|
||||||
|
|||||||
Reference in New Issue
Block a user