modified: bot.py

This commit is contained in:
SimolZimol
2025-08-23 11:17:05 +02:00
parent 1718d131da
commit ca118c12fc

354
bot.py
View File

@@ -321,7 +321,7 @@ def load_user_data_from_mysql(user_id, guild_id):
} }
# Count warnings from user_warnings table # Count warnings from user_warnings table
warning_count_query = "SELECT COUNT(*) FROM user_warnings WHERE user_id = %s AND guild_id = %s" warning_count_query = "SELECT COUNT(*) FROM user_warnings WHERE user_id = %s AND guild_id = %s AND aktiv = TRUE"
cursor.execute(warning_count_query, (user_id, guild_id)) cursor.execute(warning_count_query, (user_id, guild_id))
warning_count = cursor.fetchone()[0] warning_count = cursor.fetchone()[0]
user_data["warns"] = warning_count user_data["warns"] = warning_count
@@ -2106,6 +2106,8 @@ async def modhelp(ctx):
"`/unmute <user>` - Manually unmute a user\n" "`/unmute <user>` - Manually unmute a user\n"
"`/modinfo [user]` - View comprehensive user information\n" "`/modinfo [user]` - View comprehensive user information\n"
"`/viewwarn <warning_id>` - View detailed warning information\n" "`/viewwarn <warning_id>` - View detailed warning information\n"
"`/removewarn <warning_id>` - Deactivate a warning (Level 6+)\n"
"`/restorewarn <warning_id>` - Reactivate a warning (Level 6+)\n"
"`/modstats [user]` - View moderation statistics\n" "`/modstats [user]` - View moderation statistics\n"
"`/processes <action> [type]` - Manage active processes\n" "`/processes <action> [type]` - Manage active processes\n"
"`/startgiveaway` - Create server giveaways\n" "`/startgiveaway` - Create server giveaways\n"
@@ -2793,14 +2795,14 @@ async def save_warning_to_database(user_id, guild_id, moderator_id, reason, time
insert_query = """ insert_query = """
INSERT INTO user_warnings (user_id, guild_id, moderator_id, reason, message_id, INSERT INTO user_warnings (user_id, guild_id, moderator_id, reason, message_id,
message_content, message_attachments, message_author_id, message_content, message_attachments, message_author_id,
message_channel_id, context_messages, created_at) message_channel_id, context_messages, aktiv, created_at)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""" """
cursor.execute(insert_query, ( cursor.execute(insert_query, (
user_id, guild_id, moderator_id, reason, message_id_db, user_id, guild_id, moderator_id, reason, message_id_db,
message_content, message_attachments, message_author_id, message_content, message_attachments, message_author_id,
message_channel_id, context_messages, timestamp message_channel_id, context_messages, True, timestamp
)) ))
connection.commit() connection.commit()
@@ -2839,10 +2841,12 @@ def create_warnings_table():
message_author_id BIGINT NULL, message_author_id BIGINT NULL,
message_channel_id BIGINT NULL, message_channel_id BIGINT NULL,
context_messages LONGTEXT NULL, context_messages LONGTEXT NULL,
aktiv BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_guild (user_id, guild_id), INDEX idx_user_guild (user_id, guild_id),
INDEX idx_created_at (created_at), INDEX idx_created_at (created_at),
INDEX idx_message_id (message_id) INDEX idx_message_id (message_id),
INDEX idx_aktiv (aktiv)
) )
""" """
@@ -2856,7 +2860,9 @@ def create_warnings_table():
"ALTER TABLE user_warnings ADD COLUMN message_author_id BIGINT NULL", "ALTER TABLE user_warnings ADD COLUMN message_author_id BIGINT NULL",
"ALTER TABLE user_warnings ADD COLUMN message_channel_id BIGINT NULL", "ALTER TABLE user_warnings ADD COLUMN message_channel_id BIGINT NULL",
"ALTER TABLE user_warnings ADD COLUMN context_messages LONGTEXT NULL", "ALTER TABLE user_warnings ADD COLUMN context_messages LONGTEXT NULL",
"ALTER TABLE user_warnings ADD INDEX idx_message_id (message_id)" "ALTER TABLE user_warnings ADD COLUMN aktiv BOOLEAN DEFAULT TRUE",
"ALTER TABLE user_warnings ADD INDEX idx_message_id (message_id)",
"ALTER TABLE user_warnings ADD INDEX idx_aktiv (aktiv)"
] ]
for alter_query in alter_queries: for alter_query in alter_queries:
@@ -2877,8 +2883,14 @@ def create_warnings_table():
if connection: if connection:
close_database_connection(connection) close_database_connection(connection)
async def get_user_warnings(user_id, guild_id): async def get_user_warnings(user_id, guild_id, active_only=True):
"""Retrieves all warning records for a user""" """Retrieves warning records for a user
Args:
user_id: Discord user ID
guild_id: Discord guild ID
active_only: If True, only returns active warnings. If False, returns all warnings.
"""
connection = None connection = None
cursor = None cursor = None
try: try:
@@ -2887,11 +2899,11 @@ async def get_user_warnings(user_id, guild_id):
select_query = """ select_query = """
SELECT id, moderator_id, reason, created_at, message_id, message_content, SELECT id, moderator_id, reason, created_at, message_id, message_content,
message_attachments, message_author_id, message_channel_id, context_messages message_attachments, message_author_id, message_channel_id, context_messages, aktiv
FROM user_warnings FROM user_warnings
WHERE user_id = %s AND guild_id = %s WHERE user_id = %s AND guild_id = %s {}
ORDER BY created_at DESC ORDER BY created_at DESC
""" """.format("AND aktiv = TRUE" if active_only else "")
cursor.execute(select_query, (user_id, guild_id)) cursor.execute(select_query, (user_id, guild_id))
results = cursor.fetchall() results = cursor.fetchall()
@@ -2908,7 +2920,8 @@ async def get_user_warnings(user_id, guild_id):
"message_attachments": row[6], "message_attachments": row[6],
"message_author_id": row[7], "message_author_id": row[7],
"message_channel_id": row[8], "message_channel_id": row[8],
"context_messages": row[9] "context_messages": row[9],
"aktiv": row[10]
}) })
return warnings return warnings
@@ -2922,6 +2935,66 @@ async def get_user_warnings(user_id, guild_id):
if connection: if connection:
close_database_connection(connection) close_database_connection(connection)
async def deactivate_warning(warning_id):
"""Deactivates a warning by setting aktiv to FALSE"""
connection = None
cursor = None
try:
connection = connect_to_database()
cursor = connection.cursor()
update_query = "UPDATE user_warnings SET aktiv = FALSE WHERE id = %s"
cursor.execute(update_query, (warning_id,))
connection.commit()
if cursor.rowcount > 0:
logger.info(f"Deactivated warning with ID {warning_id}")
return True
else:
logger.warning(f"No warning found with ID {warning_id}")
return False
except Exception as e:
logger.error(f"Error deactivating warning: {e}")
if connection:
connection.rollback()
return False
finally:
if cursor:
cursor.close()
if connection:
close_database_connection(connection)
async def reactivate_warning(warning_id):
"""Reactivates a warning by setting aktiv to TRUE"""
connection = None
cursor = None
try:
connection = connect_to_database()
cursor = connection.cursor()
update_query = "UPDATE user_warnings SET aktiv = TRUE WHERE id = %s"
cursor.execute(update_query, (warning_id,))
connection.commit()
if cursor.rowcount > 0:
logger.info(f"Reactivated warning with ID {warning_id}")
return True
else:
logger.warning(f"No warning found with ID {warning_id}")
return False
except Exception as e:
logger.error(f"Error reactivating warning: {e}")
if connection:
connection.rollback()
return False
finally:
if cursor:
cursor.close()
if connection:
close_database_connection(connection)
async def get_message_data(channel, message_id, context_range=3): async def get_message_data(channel, message_id, context_range=3):
"""Retrieves and processes message data for warning documentation with context messages""" """Retrieves and processes message data for warning documentation with context messages"""
try: try:
@@ -3509,8 +3582,8 @@ async def account(ctx, user: discord.User = None):
# Get guild settings for thresholds # Get guild settings for thresholds
guild_settings = get_guild_settings(ctx.guild.id) guild_settings = get_guild_settings(ctx.guild.id)
# Get detailed warning records # Get detailed warning records (active warnings only for user view)
warning_records = await get_user_warnings(target_user.id, ctx.guild.id) warning_records = await get_user_warnings(target_user.id, ctx.guild.id, active_only=True)
# Warning information with threshold # Warning information with threshold
warn_threshold = guild_settings.get("max_warn_threshold", 3) warn_threshold = guild_settings.get("max_warn_threshold", 3)
@@ -3680,8 +3753,8 @@ async def modinfo(ctx, user: discord.User = None):
guild_settings = get_guild_settings(ctx.guild.id) guild_settings = get_guild_settings(ctx.guild.id)
warn_threshold = guild_settings.get("max_warn_threshold", 3) warn_threshold = guild_settings.get("max_warn_threshold", 3)
# Get detailed warning records # Get detailed warning records (all warnings for moderator view)
warning_records = await get_user_warnings(user.id, ctx.guild.id) warning_records = await get_user_warnings(user.id, ctx.guild.id, active_only=False)
# Create main embed # Create main embed
embed = discord.Embed( embed = discord.Embed(
@@ -3778,7 +3851,9 @@ async def modinfo(ctx, user: discord.User = None):
if len(reason) > 50: if len(reason) > 50:
reason = reason[:47] + "..." reason = reason[:47] + "..."
warning_line = f"`{warning_date}` **{mod_name}**: {reason}" # Add status indicator
status_indicator = "🟢" if record.get("aktiv", True) else "🔴"
warning_line = f"`{warning_date}` {status_indicator} **{mod_name}**: {reason}"
# Add message indicator and content preview if available # Add message indicator and content preview if available
if record.get("message_id"): if record.get("message_id"):
@@ -3792,6 +3867,8 @@ async def modinfo(ctx, user: discord.User = None):
if len(warning_records) > 3: if len(warning_records) > 3:
warning_history += f"*... and {len(warning_records) - 3} more warning(s)*" warning_history += f"*... and {len(warning_records) - 3} more warning(s)*"
warning_history += f"\n🟢 = Active Warning | 🔴 = Deactivated Warning"
embed.add_field(name="📋 Recent Warnings", value=warning_history, inline=False) embed.add_field(name="📋 Recent Warnings", value=warning_history, inline=False)
# Server activity (if member) # Server activity (if member)
@@ -3899,7 +3976,7 @@ async def viewwarn(ctx, warning_id: int):
select_query = """ select_query = """
SELECT user_id, guild_id, moderator_id, reason, created_at, message_id, SELECT user_id, guild_id, moderator_id, reason, created_at, message_id,
message_content, message_attachments, message_author_id, message_channel_id, context_messages message_content, message_attachments, message_author_id, message_channel_id, context_messages, aktiv
FROM user_warnings FROM user_warnings
WHERE id = %s AND guild_id = %s WHERE id = %s AND guild_id = %s
""" """
@@ -3917,7 +3994,7 @@ async def viewwarn(ctx, warning_id: int):
return return
# Parse result # Parse result
user_id, guild_id, moderator_id, reason, created_at, message_id, message_content, message_attachments, message_author_id, message_channel_id, context_messages = result user_id, guild_id, moderator_id, reason, created_at, message_id, message_content, message_attachments, message_author_id, message_channel_id, context_messages, aktiv = result
# Get user and moderator objects # Get user and moderator objects
warned_user = await client.fetch_user(user_id) warned_user = await client.fetch_user(user_id)
@@ -3933,6 +4010,13 @@ async def viewwarn(ctx, warning_id: int):
embed.add_field(name="👤 Warned User", value=f"{warned_user.mention}\n`{warned_user.id}`", inline=True) embed.add_field(name="👤 Warned User", value=f"{warned_user.mention}\n`{warned_user.id}`", inline=True)
embed.add_field(name="👮 Moderator", value=f"{moderator.mention}\n`{moderator.id}`", inline=True) embed.add_field(name="👮 Moderator", value=f"{moderator.mention}\n`{moderator.id}`", inline=True)
embed.add_field(name="📅 Date", value=f"<t:{int(created_at.timestamp())}:F>", inline=True) embed.add_field(name="📅 Date", value=f"<t:{int(created_at.timestamp())}:F>", inline=True)
# Add status field
status_text = "🟢 **Active**" if aktiv else "🔴 **Deactivated**"
embed.add_field(name="📊 Status", value=status_text, inline=True)
embed.add_field(name="🆔 Warning ID", value=f"`{warning_id}`", inline=True)
embed.add_field(name="", value="", inline=True) # Empty field for spacing
embed.add_field(name="📝 Reason", value=reason, inline=False) embed.add_field(name="📝 Reason", value=reason, inline=False)
# Add message information if available # Add message information if available
@@ -4046,6 +4130,238 @@ async def viewwarn(ctx, warning_id: int):
) )
await send_response(embed=embed) await send_response(embed=embed)
@client.hybrid_command()
async def removewarn(ctx, warning_id: int):
"""Deactivates a warning (makes it hidden from /account but keeps data) (Requires Permission Level 6 or higher)
Usage: /removewarn 123
"""
# 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:
await ctx.followup.send(content=content, embed=embed, ephemeral=ephemeral, file=file)
else:
await ctx.send(content=content, embed=embed, file=file)
except Exception as e:
logger.error(f"Error sending response: {e}")
try:
await ctx.send(content=content, embed=embed, file=file)
except:
pass
try:
# Check permissions
user_data = await load_user_data(ctx.author.id, ctx.guild.id)
if user_data["permission"] < 6:
embed = discord.Embed(
title="❌ Insufficient Permissions",
description="You need Level 6 or higher permissions to deactivate warnings.",
color=0xff0000
)
await send_response(embed=embed, ephemeral=True)
return
# Get warning info first
connection = connect_to_database()
cursor = connection.cursor()
cursor.execute("SELECT user_id, guild_id, reason, aktiv FROM user_warnings WHERE id = %s", (warning_id,))
warning_data = cursor.fetchone()
if not warning_data:
embed = discord.Embed(
title="❌ Warning Not Found",
description=f"No warning found with ID `{warning_id}`.",
color=0xff0000
)
await send_response(embed=embed, ephemeral=True)
return
if warning_data[1] != ctx.guild.id:
embed = discord.Embed(
title="❌ Invalid Warning",
description="This warning doesn't belong to this server.",
color=0xff0000
)
await send_response(embed=embed, ephemeral=True)
return
if not warning_data[3]: # Already inactive
embed = discord.Embed(
title="⚠️ Warning Already Inactive",
description=f"Warning `{warning_id}` is already deactivated.",
color=0xffa500
)
await send_response(embed=embed, ephemeral=True)
return
# Deactivate the warning
success = await deactivate_warning(warning_id)
if success:
try:
target_user = await client.fetch_user(warning_data[0])
user_name = target_user.display_name
except:
user_name = f"User {warning_data[0]}"
embed = discord.Embed(
title="✅ Warning Deactivated",
description=f"Warning `{warning_id}` for {user_name} has been deactivated.\n\n**Reason:** {warning_data[2]}\n\nThe warning is now hidden from `/account` but data is preserved for admin review.",
color=0x00ff00
)
await send_response(embed=embed)
# Log the action
await log_moderation_action(
ctx.guild.id,
f"Warning `{warning_id}` deactivated by {ctx.author.display_name}",
ctx.author
)
else:
embed = discord.Embed(
title="❌ Error",
description="Failed to deactivate warning. Please try again.",
color=0xff0000
)
await send_response(embed=embed)
except Exception as e:
logger.error(f"Error in removewarn command: {e}")
embed = discord.Embed(
title="❌ Error",
description="An error occurred while deactivating the warning.",
color=0xff0000
)
await send_response(embed=embed)
finally:
if cursor:
cursor.close()
if connection:
close_database_connection(connection)
@client.hybrid_command()
async def restorewarn(ctx, warning_id: int):
"""Reactivates a previously deactivated warning (Requires Permission Level 6 or higher)
Usage: /restorewarn 123
"""
# 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:
await ctx.followup.send(content=content, embed=embed, ephemeral=ephemeral, file=file)
else:
await ctx.send(content=content, embed=embed, file=file)
except Exception as e:
logger.error(f"Error sending response: {e}")
try:
await ctx.send(content=content, embed=embed, file=file)
except:
pass
try:
# Check permissions
user_data = await load_user_data(ctx.author.id, ctx.guild.id)
if user_data["permission"] < 6:
embed = discord.Embed(
title="❌ Insufficient Permissions",
description="You need Level 6 or higher permissions to reactivate warnings.",
color=0xff0000
)
await send_response(embed=embed, ephemeral=True)
return
# Get warning info first
connection = connect_to_database()
cursor = connection.cursor()
cursor.execute("SELECT user_id, guild_id, reason, aktiv FROM user_warnings WHERE id = %s", (warning_id,))
warning_data = cursor.fetchone()
if not warning_data:
embed = discord.Embed(
title="❌ Warning Not Found",
description=f"No warning found with ID `{warning_id}`.",
color=0xff0000
)
await send_response(embed=embed, ephemeral=True)
return
if warning_data[1] != ctx.guild.id:
embed = discord.Embed(
title="❌ Invalid Warning",
description="This warning doesn't belong to this server.",
color=0xff0000
)
await send_response(embed=embed, ephemeral=True)
return
if warning_data[3]: # Already active
embed = discord.Embed(
title="⚠️ Warning Already Active",
description=f"Warning `{warning_id}` is already active.",
color=0xffa500
)
await send_response(embed=embed, ephemeral=True)
return
# Reactivate the warning
success = await reactivate_warning(warning_id)
if success:
try:
target_user = await client.fetch_user(warning_data[0])
user_name = target_user.display_name
except:
user_name = f"User {warning_data[0]}"
embed = discord.Embed(
title="✅ Warning Reactivated",
description=f"Warning `{warning_id}` for {user_name} has been reactivated.\n\n**Reason:** {warning_data[2]}\n\nThe warning is now visible in `/account` again.",
color=0x00ff00
)
await send_response(embed=embed)
# Log the action
await log_moderation_action(
ctx.guild.id,
f"Warning `{warning_id}` reactivated by {ctx.author.display_name}",
ctx.author
)
else:
embed = discord.Embed(
title="❌ Error",
description="Failed to reactivate warning. Please try again.",
color=0xff0000
)
await send_response(embed=embed)
except Exception as e:
logger.error(f"Error in restorewarn command: {e}")
embed = discord.Embed(
title="❌ Error",
description="An error occurred while reactivating the warning.",
color=0xff0000
)
await send_response(embed=embed)
finally:
if cursor:
cursor.close()
if 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):
"""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)