modified: bot.py
This commit is contained in:
709
bot.py
709
bot.py
@@ -947,6 +947,15 @@ async def handle_expired_mute(process_uuid, data):
|
||||
except (discord.Forbidden, discord.NotFound):
|
||||
pass # User has DMs disabled or doesn't exist
|
||||
|
||||
# Update user_mutes table to mark as auto-unmuted
|
||||
try:
|
||||
mute_record = await get_mute_by_process_uuid(process_uuid)
|
||||
if mute_record:
|
||||
await deactivate_mute(mute_record['id'], auto_unmuted=True)
|
||||
logger.info(f"Updated mute record {mute_record['id']} as auto-unmuted")
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating user_mutes table for process {process_uuid}: {e}")
|
||||
|
||||
logger.info(f"Successfully unmuted user {user_id} in guild {guild_id}")
|
||||
update_process_status(process_uuid, "completed")
|
||||
|
||||
@@ -3004,6 +3013,248 @@ def create_contact_messages_table():
|
||||
if connection:
|
||||
close_database_connection(connection)
|
||||
|
||||
def create_mutes_table():
|
||||
"""Creates the user_mutes table if it doesn't exist"""
|
||||
connection = None
|
||||
cursor = None
|
||||
try:
|
||||
connection = connect_to_database()
|
||||
cursor = connection.cursor()
|
||||
|
||||
create_table_query = """
|
||||
CREATE TABLE IF NOT EXISTS user_mutes (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id VARCHAR(50) NOT NULL,
|
||||
guild_id VARCHAR(50) NOT NULL,
|
||||
moderator_id VARCHAR(50) NOT NULL,
|
||||
reason TEXT NOT NULL,
|
||||
duration VARCHAR(20) NOT NULL,
|
||||
start_time TIMESTAMP NOT NULL,
|
||||
end_time TIMESTAMP NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
status VARCHAR(20) DEFAULT 'active',
|
||||
process_uuid VARCHAR(36),
|
||||
channel_id VARCHAR(50),
|
||||
mute_role_id VARCHAR(50),
|
||||
message_id BIGINT,
|
||||
message_content TEXT,
|
||||
message_attachments JSON,
|
||||
message_author_id VARCHAR(50),
|
||||
message_channel_id VARCHAR(50),
|
||||
context_messages JSON,
|
||||
aktiv BOOLEAN DEFAULT TRUE,
|
||||
unmuted_at TIMESTAMP NULL,
|
||||
unmuted_by VARCHAR(50) NULL,
|
||||
auto_unmuted BOOLEAN DEFAULT FALSE,
|
||||
INDEX idx_user_guild (user_id, guild_id),
|
||||
INDEX idx_guild (guild_id),
|
||||
INDEX idx_moderator (moderator_id),
|
||||
INDEX idx_process_uuid (process_uuid),
|
||||
INDEX idx_aktiv (aktiv),
|
||||
INDEX idx_status (status)
|
||||
)
|
||||
"""
|
||||
|
||||
cursor.execute(create_table_query)
|
||||
connection.commit()
|
||||
logger.info("User mutes table created or already exists")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating user mutes table: {e}")
|
||||
finally:
|
||||
if cursor:
|
||||
cursor.close()
|
||||
if connection:
|
||||
close_database_connection(connection)
|
||||
|
||||
async def save_mute_to_database(user_id, guild_id, moderator_id, reason, duration, start_time, end_time,
|
||||
process_uuid=None, channel_id=None, mute_role_id=None, message_data=None, message_id=None):
|
||||
"""Saves individual mute records to the database with optional message data and context"""
|
||||
connection = None
|
||||
cursor = None
|
||||
try:
|
||||
connection = connect_to_database()
|
||||
cursor = connection.cursor()
|
||||
|
||||
# Extract message data if provided
|
||||
message_content = None
|
||||
message_attachments = None
|
||||
message_author_id = None
|
||||
message_channel_id = None
|
||||
context_messages = None
|
||||
|
||||
if message_data:
|
||||
if isinstance(message_data, dict) and "main_message" in message_data:
|
||||
# New format with context
|
||||
main_msg = message_data.get("main_message", {})
|
||||
message_content = main_msg.get("content")
|
||||
message_attachments = main_msg.get("attachments")
|
||||
message_author_id = main_msg.get("author_id")
|
||||
message_channel_id = main_msg.get("channel_id")
|
||||
context_messages = json.dumps(message_data.get("context_messages", []))
|
||||
else:
|
||||
# Old format or simple dict
|
||||
message_content = message_data.get("content")
|
||||
message_attachments = message_data.get("attachments")
|
||||
message_author_id = message_data.get("author_id")
|
||||
message_channel_id = message_data.get("channel_id")
|
||||
|
||||
# Convert JSON fields
|
||||
if message_attachments and isinstance(message_attachments, str):
|
||||
# Already JSON string
|
||||
pass
|
||||
elif message_attachments:
|
||||
# Convert to JSON string
|
||||
message_attachments = json.dumps(message_attachments)
|
||||
|
||||
insert_query = """
|
||||
INSERT INTO user_mutes (
|
||||
user_id, guild_id, moderator_id, reason, duration, start_time, end_time,
|
||||
process_uuid, channel_id, mute_role_id, message_id, message_content,
|
||||
message_attachments, message_author_id, message_channel_id, context_messages
|
||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
|
||||
cursor.execute(insert_query, (
|
||||
user_id, guild_id, moderator_id, reason, duration, start_time, end_time,
|
||||
str(process_uuid) if process_uuid else None, channel_id, mute_role_id,
|
||||
message_id, message_content, message_attachments, message_author_id,
|
||||
message_channel_id, context_messages
|
||||
))
|
||||
|
||||
mute_id = cursor.lastrowid
|
||||
connection.commit()
|
||||
|
||||
logger.info(f"Mute record saved to database: ID={mute_id}, User={user_id}, Guild={guild_id}")
|
||||
return mute_id
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error saving mute to database: {e}")
|
||||
if connection:
|
||||
connection.rollback()
|
||||
return None
|
||||
finally:
|
||||
if cursor:
|
||||
cursor.close()
|
||||
if connection:
|
||||
close_database_connection(connection)
|
||||
|
||||
async def get_user_mutes(user_id, guild_id, active_only=True):
|
||||
"""Retrieves mute records for a user
|
||||
|
||||
Args:
|
||||
user_id: Discord user ID
|
||||
guild_id: Discord guild ID
|
||||
active_only: If True, only returns active mutes. If False, returns all mutes.
|
||||
"""
|
||||
connection = None
|
||||
cursor = None
|
||||
try:
|
||||
connection = connect_to_database()
|
||||
cursor = connection.cursor()
|
||||
|
||||
if active_only:
|
||||
select_query = """
|
||||
SELECT * FROM user_mutes
|
||||
WHERE user_id = %s AND guild_id = %s AND aktiv = TRUE
|
||||
ORDER BY created_at DESC
|
||||
"""
|
||||
else:
|
||||
select_query = """
|
||||
SELECT * FROM user_mutes
|
||||
WHERE user_id = %s AND guild_id = %s
|
||||
ORDER BY created_at DESC
|
||||
"""
|
||||
|
||||
cursor.execute(select_query, (user_id, guild_id))
|
||||
results = cursor.fetchall()
|
||||
|
||||
# Convert results to list of dictionaries
|
||||
mutes = []
|
||||
columns = [desc[0] for desc in cursor.description]
|
||||
for row in results:
|
||||
mute_dict = dict(zip(columns, row))
|
||||
mutes.append(mute_dict)
|
||||
|
||||
return mutes
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error retrieving user mutes: {e}")
|
||||
return []
|
||||
finally:
|
||||
if cursor:
|
||||
cursor.close()
|
||||
if connection:
|
||||
close_database_connection(connection)
|
||||
|
||||
async def deactivate_mute(mute_id, unmuted_by=None, auto_unmuted=False):
|
||||
"""Deactivates a mute by setting aktiv to FALSE and recording unmute info"""
|
||||
connection = None
|
||||
cursor = None
|
||||
try:
|
||||
connection = connect_to_database()
|
||||
cursor = connection.cursor()
|
||||
|
||||
update_query = """
|
||||
UPDATE user_mutes
|
||||
SET aktiv = FALSE, unmuted_at = NOW(), unmuted_by = %s, auto_unmuted = %s, status = 'completed'
|
||||
WHERE id = %s
|
||||
"""
|
||||
|
||||
cursor.execute(update_query, (unmuted_by, auto_unmuted, mute_id))
|
||||
connection.commit()
|
||||
|
||||
if cursor.rowcount > 0:
|
||||
logger.info(f"Mute {mute_id} deactivated successfully")
|
||||
return True
|
||||
else:
|
||||
logger.warning(f"No mute found with ID {mute_id}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error deactivating mute: {e}")
|
||||
if connection:
|
||||
connection.rollback()
|
||||
return False
|
||||
finally:
|
||||
if cursor:
|
||||
cursor.close()
|
||||
if connection:
|
||||
close_database_connection(connection)
|
||||
|
||||
async def get_mute_by_process_uuid(process_uuid):
|
||||
"""Gets mute record by process UUID"""
|
||||
connection = None
|
||||
cursor = None
|
||||
try:
|
||||
connection = connect_to_database()
|
||||
cursor = connection.cursor()
|
||||
|
||||
select_query = """
|
||||
SELECT * FROM user_mutes
|
||||
WHERE process_uuid = %s
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
"""
|
||||
|
||||
cursor.execute(select_query, (str(process_uuid),))
|
||||
result = cursor.fetchone()
|
||||
|
||||
if result:
|
||||
columns = [desc[0] for desc in cursor.description]
|
||||
mute_dict = dict(zip(columns, result))
|
||||
return mute_dict
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting mute by process UUID: {e}")
|
||||
return None
|
||||
finally:
|
||||
if cursor:
|
||||
cursor.close()
|
||||
if connection:
|
||||
close_database_connection(connection)
|
||||
|
||||
async def send_contact_message_to_admin(message_data):
|
||||
"""Sends a contact message to the admin via Discord DM"""
|
||||
try:
|
||||
@@ -4373,6 +4624,263 @@ async def viewwarn(ctx, warning_id: int):
|
||||
)
|
||||
await send_response(embed=embed)
|
||||
|
||||
@client.hybrid_command()
|
||||
async def viewmute(ctx, process_uuid: str):
|
||||
"""View detailed information about a specific mute (Requires Permission Level 5 or higher)"""
|
||||
# Check if it's a slash command and defer if needed
|
||||
is_slash_command = hasattr(ctx, 'interaction') and ctx.interaction
|
||||
if is_slash_command:
|
||||
await ctx.defer()
|
||||
|
||||
# Helper function for sending responses
|
||||
async def send_response(content=None, embed=None, ephemeral=False, file=None):
|
||||
try:
|
||||
if is_slash_command:
|
||||
if hasattr(ctx, 'followup') and ctx.followup:
|
||||
await ctx.followup.send(content=content, embed=embed, ephemeral=ephemeral, file=file)
|
||||
elif hasattr(ctx, 'response') and not ctx.response.is_done():
|
||||
await ctx.response.send_message(content=content, embed=embed, ephemeral=ephemeral, file=file)
|
||||
else:
|
||||
await ctx.send(content=content, embed=embed, file=file)
|
||||
else:
|
||||
await ctx.send(content=content, embed=embed, file=file)
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending response in viewmute command: {e}")
|
||||
# Final fallback - try basic send
|
||||
try:
|
||||
if embed:
|
||||
await ctx.send(embed=embed)
|
||||
elif content:
|
||||
await ctx.send(content=content)
|
||||
except Exception as fallback_error:
|
||||
logger.error(f"Fallback send also failed: {fallback_error}")
|
||||
|
||||
try:
|
||||
# Load moderator data
|
||||
mod_data = await load_user_data(ctx.author.id, ctx.guild.id)
|
||||
|
||||
# Check moderation rights
|
||||
if not check_moderation_permission(mod_data["permission"]):
|
||||
embed = discord.Embed(
|
||||
title="❌ Insufficient Permissions",
|
||||
description="You need moderation permissions (Level 5 or higher) to use this command.",
|
||||
color=0xff0000
|
||||
)
|
||||
await send_response(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
# Get mute details from user_mutes database (preferred) or active_processes as fallback
|
||||
connection = None
|
||||
cursor = None
|
||||
try:
|
||||
connection = connect_to_database()
|
||||
cursor = connection.cursor()
|
||||
|
||||
# First try to find mute in user_mutes table by process_uuid
|
||||
select_query = """
|
||||
SELECT * FROM user_mutes
|
||||
WHERE process_uuid = %s AND guild_id = %s
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
"""
|
||||
|
||||
cursor.execute(select_query, (process_uuid, ctx.guild.id))
|
||||
mute_result = cursor.fetchone()
|
||||
|
||||
if mute_result:
|
||||
# Found in user_mutes table
|
||||
columns = [desc[0] for desc in cursor.description]
|
||||
mute_data = dict(zip(columns, mute_result))
|
||||
|
||||
# Get user and moderator objects
|
||||
muted_user = await client.fetch_user(int(mute_data['user_id']))
|
||||
moderator = await client.fetch_user(int(mute_data['moderator_id']))
|
||||
|
||||
# Get channel
|
||||
channel = ctx.guild.get_channel(int(mute_data['channel_id'])) if mute_data['channel_id'] else None
|
||||
|
||||
# Create detailed embed
|
||||
embed = discord.Embed(
|
||||
title=f"🔇 Mute Details - ID: {mute_data['id']}",
|
||||
color=0xff0000,
|
||||
timestamp=mute_data['created_at']
|
||||
)
|
||||
|
||||
embed.add_field(name="👤 Muted User", value=f"{muted_user.mention}\n`{muted_user.id}`", inline=True)
|
||||
embed.add_field(name="👮 Moderator", value=f"{moderator.mention}\n`{moderator.id}`", inline=True)
|
||||
embed.add_field(name="📅 Muted At", value=f"<t:{int(mute_data['start_time'].timestamp())}:F>", inline=True)
|
||||
|
||||
# Add status information
|
||||
status_emoji = {"active": "🟢", "completed": "✅", "expired": "⏰", "cancelled": "❌"}.get(mute_data['status'], "❓")
|
||||
aktiv_status = "🟢 Active" if mute_data['aktiv'] else "🔴 Inactive"
|
||||
status_text = f"{status_emoji} **{mute_data['status'].title()}** ({aktiv_status})"
|
||||
embed.add_field(name="📊 Status", value=status_text, inline=True)
|
||||
|
||||
# End time and duration info
|
||||
if mute_data['end_time']:
|
||||
if mute_data['aktiv'] and mute_data['status'] == 'active':
|
||||
embed.add_field(name="⏰ Ends At", value=f"<t:{int(mute_data['end_time'].timestamp())}:F>\n<t:{int(mute_data['end_time'].timestamp())}:R>", inline=True)
|
||||
else:
|
||||
embed.add_field(name="⏰ Ended At", value=f"<t:{int(mute_data['end_time'].timestamp())}:F>", inline=True)
|
||||
|
||||
embed.add_field(name="⏱️ Duration", value=mute_data['duration'], inline=True)
|
||||
|
||||
# Add reason
|
||||
embed.add_field(name="📝 Reason", value=mute_data['reason'], inline=False)
|
||||
|
||||
# Add channel information
|
||||
if channel:
|
||||
embed.add_field(name="📍 Channel", value=f"{channel.mention}\n`{channel.id}`", inline=True)
|
||||
|
||||
# Add mute role information
|
||||
if mute_data['mute_role_id']:
|
||||
mute_role = ctx.guild.get_role(int(mute_data['mute_role_id']))
|
||||
if mute_role:
|
||||
embed.add_field(name="🎭 Mute Role", value=f"{mute_role.mention}\n`{mute_role.id}`", inline=True)
|
||||
else:
|
||||
embed.add_field(name="🎭 Mute Role", value=f"❌ Deleted Role\n`{mute_data['mute_role_id']}`", inline=True)
|
||||
|
||||
# Unmute information
|
||||
if not mute_data['aktiv'] and mute_data['unmuted_at']:
|
||||
unmute_info = f"<t:{int(mute_data['unmuted_at'].timestamp())}:F>"
|
||||
if mute_data['unmuted_by']:
|
||||
unmuter = await client.fetch_user(int(mute_data['unmuted_by']))
|
||||
unmute_info += f"\nBy: {unmuter.mention}"
|
||||
if mute_data['auto_unmuted']:
|
||||
unmute_info += "\n🤖 Automatic unmute"
|
||||
embed.add_field(name="🔓 Unmuted At", value=unmute_info, inline=True)
|
||||
|
||||
# Message reference if available
|
||||
if mute_data['message_id'] and mute_data['message_content']:
|
||||
content_preview = mute_data['message_content'][:100] + "..." if len(mute_data['message_content']) > 100 else mute_data['message_content']
|
||||
embed.add_field(name="📄 Referenced Message", value=f"ID: `{mute_data['message_id']}`\nContent: {content_preview}", inline=False)
|
||||
|
||||
embed.add_field(name="🆔 Process UUID", value=f"`{process_uuid}`", inline=False)
|
||||
embed.add_field(name="🆔 Mute Record ID", value=f"`{mute_data['id']}`", inline=True)
|
||||
|
||||
embed.set_thumbnail(url=muted_user.display_avatar.url)
|
||||
embed.set_footer(text=f"Mute Record from Database | Server: {ctx.guild.name}")
|
||||
|
||||
await send_response(embed=embed)
|
||||
return
|
||||
|
||||
else:
|
||||
# Fallback to active_processes table
|
||||
select_query = """
|
||||
SELECT uuid, process_type, guild_id, channel_id, user_id, target_id,
|
||||
created_at, end_time, status, data
|
||||
FROM active_processes
|
||||
WHERE uuid = %s AND guild_id = %s AND process_type = 'mute'
|
||||
"""
|
||||
|
||||
cursor.execute(select_query, (process_uuid, ctx.guild.id))
|
||||
result = cursor.fetchone()
|
||||
|
||||
if not result:
|
||||
embed = discord.Embed(
|
||||
title="❌ Mute Not Found",
|
||||
description=f"No mute with UUID `{process_uuid}` found in this server.",
|
||||
color=0xff0000
|
||||
)
|
||||
await send_response(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
# Parse result (fallback to old format)
|
||||
uuid, process_type, guild_id, channel_id, user_id, target_id, created_at, end_time, status, data = result
|
||||
|
||||
# Parse data JSON
|
||||
import json
|
||||
proc_data = json.loads(data) if data else {}
|
||||
|
||||
# Get user and moderator objects
|
||||
muted_user = await client.fetch_user(target_id)
|
||||
moderator_id = proc_data.get('moderator_id', user_id)
|
||||
moderator = await client.fetch_user(moderator_id)
|
||||
|
||||
# Get channel
|
||||
channel = ctx.guild.get_channel(channel_id) if channel_id else None
|
||||
|
||||
# Create detailed embed
|
||||
embed = discord.Embed(
|
||||
title=f"🔇 Mute Details - ID: {uuid[:8]}",
|
||||
color=0xff0000,
|
||||
timestamp=created_at
|
||||
)
|
||||
|
||||
embed.add_field(name="👤 Muted User", value=f"{muted_user.mention}\n`{muted_user.id}`", inline=True)
|
||||
embed.add_field(name="👮 Moderator", value=f"{moderator.mention}\n`{moderator_id}`", inline=True)
|
||||
embed.add_field(name="📅 Muted At", value=f"<t:{int(created_at.timestamp())}:F>", inline=True)
|
||||
|
||||
# Add status and duration information
|
||||
status_emoji = {"active": "🟢", "completed": "✅", "expired": "⏰", "cancelled": "❌"}.get(status, "❓")
|
||||
status_text = f"{status_emoji} **{status.title()}**"
|
||||
embed.add_field(name="📊 Status", value=status_text, inline=True)
|
||||
|
||||
if end_time:
|
||||
if status == "active":
|
||||
embed.add_field(name="⏰ Ends At", value=f"<t:{int(end_time.timestamp())}:F>\n<t:{int(end_time.timestamp())}:R>", inline=True)
|
||||
else:
|
||||
embed.add_field(name="⏰ Ended At", value=f"<t:{int(end_time.timestamp())}:F>", inline=True)
|
||||
|
||||
embed.add_field(name="🆔 Process UUID", value=f"`{uuid}`", inline=True)
|
||||
|
||||
# Add reason
|
||||
reason = mute_data.get('reason', 'No reason provided')
|
||||
embed.add_field(name="📝 Reason", value=reason, inline=False)
|
||||
|
||||
# Add channel information
|
||||
if channel:
|
||||
embed.add_field(name="📍 Channel", value=f"{channel.mention}\n`{channel.id}`", inline=True)
|
||||
|
||||
# Add mute role information
|
||||
mute_role_id = mute_data.get('mute_role_id')
|
||||
if mute_role_id:
|
||||
mute_role = ctx.guild.get_role(mute_role_id)
|
||||
if mute_role:
|
||||
embed.add_field(name="🎭 Mute Role", value=f"{mute_role.mention}\n`{mute_role.id}`", inline=True)
|
||||
else:
|
||||
embed.add_field(name="🎭 Mute Role", value=f"❌ Deleted Role\n`{mute_role_id}`", inline=True)
|
||||
|
||||
# Add duration calculation if still active
|
||||
if status == "active" and end_time:
|
||||
from datetime import datetime
|
||||
now = datetime.now()
|
||||
if end_time > now:
|
||||
duration_left = end_time - now
|
||||
days = duration_left.days
|
||||
hours, remainder = divmod(duration_left.seconds, 3600)
|
||||
minutes, _ = divmod(remainder, 60)
|
||||
|
||||
duration_text = []
|
||||
if days > 0:
|
||||
duration_text.append(f"{days}d")
|
||||
if hours > 0:
|
||||
duration_text.append(f"{hours}h")
|
||||
if minutes > 0:
|
||||
duration_text.append(f"{minutes}m")
|
||||
|
||||
embed.add_field(name="⏳ Time Remaining", value=" ".join(duration_text) if duration_text else "Less than 1 minute", inline=True)
|
||||
|
||||
embed.set_thumbnail(url=muted_user.display_avatar.url)
|
||||
embed.set_footer(text=f"Process Type: {process_type.title()} | Server: {ctx.guild.name}")
|
||||
|
||||
await send_response(embed=embed)
|
||||
|
||||
finally:
|
||||
if cursor:
|
||||
cursor.close()
|
||||
if connection:
|
||||
close_database_connection(connection)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in viewmute command: {e}")
|
||||
embed = discord.Embed(
|
||||
title="❌ Error",
|
||||
description="An error occurred while retrieving mute details. Please try again.",
|
||||
color=0xff0000
|
||||
)
|
||||
await send_response(embed=embed)
|
||||
|
||||
@client.hybrid_command()
|
||||
async def removewarn(ctx, warning_id: int):
|
||||
"""Deactivates a warning (hides from /account but keeps data) - Level 6+ required"""
|
||||
@@ -4824,6 +5332,22 @@ async def mute(ctx, user: discord.User, duration: str, reason: str = "No reason
|
||||
data=process_data
|
||||
)
|
||||
|
||||
# Save detailed mute record to database
|
||||
mute_id = await save_mute_to_database(
|
||||
user_id=user.id,
|
||||
guild_id=ctx.guild.id,
|
||||
moderator_id=ctx.author.id,
|
||||
reason=reason,
|
||||
duration=duration,
|
||||
start_time=datetime.now(),
|
||||
end_time=end_time,
|
||||
process_uuid=process_uuid,
|
||||
channel_id=ctx.channel.id,
|
||||
mute_role_id=mute_role.id,
|
||||
message_data=message_data,
|
||||
message_id=int(message_id) if message_id else None
|
||||
)
|
||||
|
||||
# Create embed
|
||||
embed = discord.Embed(
|
||||
title="🔇 User Muted",
|
||||
@@ -5004,6 +5528,190 @@ async def unmute(ctx, user: discord.User):
|
||||
logger.error(f"Error in unmute command: {e}")
|
||||
await ctx.send("❌ Ein Fehler ist aufgetreten beim Entmuten des Benutzers.")
|
||||
|
||||
@client.hybrid_command()
|
||||
async def listmutes(ctx, status: str = "active"):
|
||||
"""List all mutes in the server (Requires Permission Level 5 or higher)
|
||||
|
||||
Parameters:
|
||||
- status: Filter by status (active, completed, expired, all) - Default: active
|
||||
"""
|
||||
# Check if it's a slash command and defer if needed
|
||||
is_slash_command = hasattr(ctx, 'interaction') and ctx.interaction
|
||||
if is_slash_command:
|
||||
await ctx.defer()
|
||||
|
||||
# Helper function for sending responses
|
||||
async def send_response(content=None, embed=None, ephemeral=False, file=None):
|
||||
try:
|
||||
if is_slash_command:
|
||||
if hasattr(ctx, 'followup') and ctx.followup:
|
||||
await ctx.followup.send(content=content, embed=embed, ephemeral=ephemeral, file=file)
|
||||
elif hasattr(ctx, 'response') and not ctx.response.is_done():
|
||||
await ctx.response.send_message(content=content, embed=embed, ephemeral=ephemeral, file=file)
|
||||
else:
|
||||
await ctx.send(content=content, embed=embed, file=file)
|
||||
else:
|
||||
await ctx.send(content=content, embed=embed, file=file)
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending response in listmutes command: {e}")
|
||||
# Final fallback
|
||||
try:
|
||||
if embed:
|
||||
await ctx.send(embed=embed)
|
||||
elif content:
|
||||
await ctx.send(content=content)
|
||||
except Exception as fallback_error:
|
||||
logger.error(f"Fallback send also failed: {fallback_error}")
|
||||
|
||||
try:
|
||||
# Load moderator data
|
||||
mod_data = await load_user_data(ctx.author.id, ctx.guild.id)
|
||||
|
||||
# Check moderation rights
|
||||
if not check_moderation_permission(mod_data["permission"]):
|
||||
embed = discord.Embed(
|
||||
title="❌ Insufficient Permissions",
|
||||
description="You need moderation permissions (Level 5 or higher) to use this command.",
|
||||
color=0xff0000
|
||||
)
|
||||
await send_response(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
# Validate status parameter
|
||||
valid_statuses = ["active", "completed", "expired", "cancelled", "all"]
|
||||
if status.lower() not in valid_statuses:
|
||||
embed = discord.Embed(
|
||||
title="❌ Invalid Status",
|
||||
description=f"Invalid status `{status}`. Valid options: {', '.join(valid_statuses)}",
|
||||
color=0xff0000
|
||||
)
|
||||
await send_response(embed=embed, ephemeral=True)
|
||||
return
|
||||
|
||||
# Get mutes from user_mutes database
|
||||
connection = None
|
||||
cursor = None
|
||||
try:
|
||||
connection = connect_to_database()
|
||||
cursor = connection.cursor()
|
||||
|
||||
if status.lower() == "all":
|
||||
select_query = """
|
||||
SELECT process_uuid, user_id, created_at, end_time, status, reason,
|
||||
duration, aktiv, moderator_id, auto_unmuted, unmuted_at
|
||||
FROM user_mutes
|
||||
WHERE guild_id = %s
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 50
|
||||
"""
|
||||
cursor.execute(select_query, (ctx.guild.id,))
|
||||
else:
|
||||
# Map status filters
|
||||
if status.lower() == "active":
|
||||
where_condition = "aktiv = 1 AND status = 'active'"
|
||||
elif status.lower() == "completed":
|
||||
where_condition = "aktiv = 0 OR status = 'completed'"
|
||||
elif status.lower() == "expired":
|
||||
where_condition = "status = 'expired' OR (end_time < NOW() AND aktiv = 0)"
|
||||
elif status.lower() == "cancelled":
|
||||
where_condition = "status = 'cancelled'"
|
||||
|
||||
select_query = f"""
|
||||
SELECT process_uuid, user_id, created_at, end_time, status, reason,
|
||||
duration, aktiv, moderator_id, auto_unmuted, unmuted_at
|
||||
FROM user_mutes
|
||||
WHERE guild_id = %s AND ({where_condition})
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 50
|
||||
"""
|
||||
cursor.execute(select_query, (ctx.guild.id,))
|
||||
|
||||
results = cursor.fetchall()
|
||||
|
||||
if not results:
|
||||
embed = discord.Embed(
|
||||
title="📋 No Mutes Found",
|
||||
description=f"No mutes with status `{status}` found in this server.",
|
||||
color=0x3498db
|
||||
)
|
||||
await send_response(embed=embed)
|
||||
return
|
||||
|
||||
# Create embed with mute list
|
||||
embed = discord.Embed(
|
||||
title=f"🔇 Server Mutes - {status.title()}",
|
||||
description=f"Found {len(results)} mute(s) in {ctx.guild.name}",
|
||||
color=0xff0000,
|
||||
timestamp=datetime.now()
|
||||
)
|
||||
|
||||
mute_list = []
|
||||
for result in results[:15]: # Limit to 15 to avoid embed limits
|
||||
process_uuid, user_id, created_at, end_time, mute_status, reason, duration, aktiv, moderator_id, auto_unmuted, unmuted_at = result
|
||||
|
||||
try:
|
||||
# Get user
|
||||
user = ctx.guild.get_member(int(user_id))
|
||||
user_display = user.display_name if user else f"Unknown User ({user_id})"
|
||||
|
||||
# Format reason
|
||||
reason_display = reason[:40] + ("..." if len(reason) > 40 else "") if reason else "No reason"
|
||||
|
||||
# Status emoji based on aktiv status and mute_status
|
||||
if aktiv and mute_status == "active":
|
||||
status_emoji = "🟢"
|
||||
display_status = "Active"
|
||||
elif not aktiv and auto_unmuted:
|
||||
status_emoji = "⏰"
|
||||
display_status = "Auto-Expired"
|
||||
elif not aktiv and unmuted_at:
|
||||
status_emoji = "✅"
|
||||
display_status = "Manually Unmuted"
|
||||
elif mute_status == "cancelled":
|
||||
status_emoji = "❌"
|
||||
display_status = "Cancelled"
|
||||
else:
|
||||
status_emoji = "❓"
|
||||
display_status = mute_status.title()
|
||||
|
||||
# Time info
|
||||
if aktiv and mute_status == "active" and end_time:
|
||||
time_info = f"ends <t:{int(end_time.timestamp())}:R>"
|
||||
elif unmuted_at:
|
||||
time_info = f"unmuted <t:{int(unmuted_at.timestamp())}:d>"
|
||||
else:
|
||||
time_info = f"<t:{int(created_at.timestamp())}:d>"
|
||||
|
||||
mute_entry = f"{status_emoji} **{user_display}** - {reason_display}\n`{process_uuid[:8]}` • {duration} • {time_info}"
|
||||
mute_list.append(mute_entry)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing mute entry: {e}")
|
||||
continue
|
||||
|
||||
if mute_list:
|
||||
mute_text = "\n\n".join(mute_list)
|
||||
embed.add_field(name="📋 Mutes", value=mute_text, inline=False)
|
||||
|
||||
embed.set_footer(text=f"Use /viewmute <uuid> for detailed info • Showing max 15 results")
|
||||
|
||||
await send_response(embed=embed)
|
||||
|
||||
finally:
|
||||
if cursor:
|
||||
cursor.close()
|
||||
if connection:
|
||||
close_database_connection(connection)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in listmutes command: {e}")
|
||||
embed = discord.Embed(
|
||||
title="❌ Error",
|
||||
description="An error occurred while retrieving mute list. Please try again.",
|
||||
color=0xff0000
|
||||
)
|
||||
await send_response(embed=embed)
|
||||
|
||||
@client.hybrid_command()
|
||||
async def modstats(ctx, user: discord.User = None):
|
||||
"""Zeigt Moderationsstatistiken für einen Benutzer an"""
|
||||
@@ -5705,6 +6413,7 @@ async def contact_status(ctx):
|
||||
try:
|
||||
# Initialize database tables
|
||||
create_warnings_table()
|
||||
create_mutes_table()
|
||||
create_contact_messages_table()
|
||||
logger.info("Database tables initialized successfully")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user