diff --git a/app.py b/app.py index 1b68e93..32f0e70 100644 --- a/app.py +++ b/app.py @@ -580,6 +580,104 @@ def edit_giveaway(guild_id, uuid): return render_template("edit_giveaway.html", giveaway=giveaway, guild_id=guild_id) return redirect(url_for("landing_page")) +@app.route("/server_settings/", methods=["GET", "POST"]) +def server_settings(guild_id): + """Serverbasierte Einstellungen für Moderation und andere Features.""" + if is_server_admin(guild_id): + connection = get_db_connection() + cursor = connection.cursor(dictionary=True) + + if request.method == "POST": + # Verarbeite Formular-Daten + try: + # Hole aktuelle Einstellungen + cursor.execute("SELECT * FROM guild_settings WHERE guild_id = %s", (guild_id,)) + current_settings = cursor.fetchone() + + # Parse Formular-Daten + mute_role_id = request.form.get("mute_role_id") + mute_role_name = request.form.get("mute_role_name", "Muted") + auto_create_mute_role = bool(request.form.get("auto_create_mute_role")) + max_warn_threshold = int(request.form.get("max_warn_threshold", 3)) + auto_mute_on_warns = bool(request.form.get("auto_mute_on_warns")) + auto_mute_duration = request.form.get("auto_mute_duration", "1h") + log_channel_id = request.form.get("log_channel_id") + mod_log_enabled = bool(request.form.get("mod_log_enabled")) + + # Validierung + if max_warn_threshold < 1 or max_warn_threshold > 10: + flash("Warn-Limit muss zwischen 1 und 10 liegen.", "danger") + return redirect(url_for("server_settings", guild_id=guild_id)) + + # Zeitformat validieren + time_units = {'m': 60, 'h': 3600, 'd': 86400} + if auto_mute_duration and (not auto_mute_duration[-1] in time_units or not auto_mute_duration[:-1].isdigit()): + flash("Ungültiges Zeitformat für Auto-Mute-Dauer. Verwende: 10m, 1h, 2d", "danger") + return redirect(url_for("server_settings", guild_id=guild_id)) + + # Konvertiere leere Strings zu NULL + mute_role_id = int(mute_role_id) if mute_role_id and mute_role_id.isdigit() else None + log_channel_id = int(log_channel_id) if log_channel_id and log_channel_id.isdigit() else None + + # Update oder Insert Einstellungen + if current_settings: + cursor.execute(""" + UPDATE guild_settings + SET mute_role_id = %s, mute_role_name = %s, auto_create_mute_role = %s, + max_warn_threshold = %s, auto_mute_on_warns = %s, auto_mute_duration = %s, + log_channel_id = %s, mod_log_enabled = %s + WHERE guild_id = %s + """, (mute_role_id, mute_role_name, auto_create_mute_role, max_warn_threshold, + auto_mute_on_warns, auto_mute_duration, log_channel_id, mod_log_enabled, guild_id)) + else: + cursor.execute(""" + INSERT INTO guild_settings + (guild_id, mute_role_id, mute_role_name, auto_create_mute_role, max_warn_threshold, + auto_mute_on_warns, auto_mute_duration, log_channel_id, mod_log_enabled) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) + """, (guild_id, mute_role_id, mute_role_name, auto_create_mute_role, max_warn_threshold, + auto_mute_on_warns, auto_mute_duration, log_channel_id, mod_log_enabled)) + + connection.commit() + flash("Server-Einstellungen erfolgreich gespeichert!", "success") + + except ValueError as e: + flash(f"Fehler bei der Eingabe: {str(e)}", "danger") + except Exception as e: + flash(f"Fehler beim Speichern der Einstellungen: {str(e)}", "danger") + connection.rollback() + + # Lade aktuelle Einstellungen + cursor.execute("SELECT * FROM guild_settings WHERE guild_id = %s", (guild_id,)) + settings = cursor.fetchone() + + # Falls keine Einstellungen existieren, erstelle Default-Werte + if not settings: + settings = { + "guild_id": guild_id, + "mute_role_id": None, + "mute_role_name": "Muted", + "auto_create_mute_role": True, + "max_warn_threshold": 3, + "auto_mute_on_warns": False, + "auto_mute_duration": "1h", + "log_channel_id": None, + "mod_log_enabled": True + } + + # Hole Servername + cursor.execute("SELECT name FROM guilds WHERE guild_id = %s", (guild_id,)) + guild_name_result = cursor.fetchone() + guild_name = guild_name_result["name"] if guild_name_result else "Unknown Guild" + + cursor.close() + connection.close() + + return render_template("server_settings.html", settings=settings, guild_id=guild_id, guild_name=guild_name) + + flash("Du hast keine Berechtigung, auf die Server-Einstellungen zuzugreifen.", "danger") + return redirect(url_for("user_landing_page")) + @app.route("/user_giveaways/") def user_giveaways(guild_id): """Zeigt dem Benutzer die Giveaways an, die er auf einem bestimmten Server gewonnen hat.""" diff --git a/bot.py b/bot.py index 960d7e6..01585e6 100644 --- a/bot.py +++ b/bot.py @@ -112,7 +112,7 @@ loop = asyncio.get_event_loop() def close_database_connection(connection): connection.close() -def insert_user_data(user_id, guild_id, permission, points, ban, askmultus, filter_value, chat_history, xp=0, level=1, nickname="", profile_picture="", join_date=None, leave_date=None): +def insert_user_data(user_id, guild_id, permission, points, ban, askmultus, filter_value, chat_history, xp=0, level=1, nickname="", profile_picture="", join_date=None, leave_date=None, ai_ban=0, mutes=0, warns=0): """Fügt neue Benutzerdaten in die Datenbank ein mit Connection Pool""" connection = None cursor = None @@ -120,11 +120,11 @@ def insert_user_data(user_id, guild_id, permission, points, ban, askmultus, filt connection = connect_to_database() cursor = connection.cursor() insert_query = """ - INSERT INTO user_data (user_id, guild_id, permission, points, ban, askmultus, filter_value, rank, chat_history, xp, level, nickname, profile_picture, join_date, leave_date) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + INSERT INTO user_data (user_id, guild_id, permission, points, ban, askmultus, filter_value, rank, chat_history, xp, level, nickname, profile_picture, join_date, leave_date, ai_ban, mutes, warns) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """ serialized_chat_history = json.dumps(chat_history) - data = (user_id, guild_id, permission, points, ban, askmultus, filter_value, 0, serialized_chat_history, xp, level, nickname, profile_picture, join_date, leave_date) + data = (user_id, guild_id, permission, points, ban, askmultus, filter_value, 0, serialized_chat_history, xp, level, nickname, profile_picture, join_date, leave_date, ai_ban, mutes, warns) cursor.execute(insert_query, data) connection.commit() @@ -246,7 +246,10 @@ async def create_user_data_with_member(user_id, guild_id, member=None): "asknotes_history": [], "xp": 0, "level": 1, - "nickname": nickname + "nickname": nickname, + "ai_ban": 0, + "mutes": 0, + "warns": 0 } insert_user_data( @@ -292,7 +295,10 @@ def load_user_data_from_mysql(user_id, guild_id): "asknotes_history": json.loads(result[9]) if result[9] else [], "xp": int(result[10]) if result[10] is not None else 0, "level": int(result[11]) if result[11] is not None else 1, - "nickname": result[12] + "nickname": result[12], + "ai_ban": int(result[15]) if len(result) > 15 and result[15] is not None else 0, + "mutes": int(result[16]) if len(result) > 16 and result[16] is not None else 0, + "warns": int(result[17]) if len(result) > 17 and result[17] is not None else 0 } else: user_data = { @@ -308,7 +314,10 @@ def load_user_data_from_mysql(user_id, guild_id): "asknotes_history": [], "xp": 0, "level": 1, - "nickname": "" + "nickname": "", + "ai_ban": 0, + "mutes": 0, + "warns": 0 } insert_user_data( user_data["user_id"], @@ -404,7 +413,10 @@ def load_user_data_sync(user_id, guild_id): "asknotes_history": [], "xp": 0, "level": 1, - "nickname": "" + "nickname": "", + "ai_ban": 0, + "mutes": 0, + "warns": 0 } insert_user_data( user_data["user_id"], @@ -2146,6 +2158,749 @@ async def version(ctx): """Displays the current version of the bot.""" await ctx.send(f"The current version of the bot is: {__version__}") +# ================================ GUILD SETTINGS SYSTEM ================================ + +def get_guild_settings(guild_id): + """Lädt die Guild-Einstellungen aus der Datenbank""" + connection = None + cursor = None + try: + connection = connect_to_database() + cursor = connection.cursor() + + select_query = "SELECT * FROM guild_settings WHERE guild_id = %s" + cursor.execute(select_query, (guild_id,)) + result = cursor.fetchone() + + if result: + return { + "guild_id": result[0], + "mute_role_id": result[1], + "mute_role_name": result[2] or "Muted", + "auto_create_mute_role": bool(result[3]) if result[3] is not None else True, + "max_warn_threshold": result[4] or 3, + "auto_mute_on_warns": bool(result[5]) if result[5] is not None else False, + "auto_mute_duration": result[6] or "1h", + "log_channel_id": result[7], + "mod_log_enabled": bool(result[8]) if result[8] is not None else True + } + else: + # Erstelle Default-Einstellungen + default_settings = { + "guild_id": guild_id, + "mute_role_id": None, + "mute_role_name": "Muted", + "auto_create_mute_role": True, + "max_warn_threshold": 3, + "auto_mute_on_warns": False, + "auto_mute_duration": "1h", + "log_channel_id": None, + "mod_log_enabled": True + } + save_guild_settings(guild_id, default_settings) + return default_settings + + except Exception as e: + logger.error(f"Error loading guild settings: {e}") + # Return default settings on error + return { + "guild_id": guild_id, + "mute_role_id": None, + "mute_role_name": "Muted", + "auto_create_mute_role": True, + "max_warn_threshold": 3, + "auto_mute_on_warns": False, + "auto_mute_duration": "1h", + "log_channel_id": None, + "mod_log_enabled": True + } + finally: + if cursor: + cursor.close() + if connection: + close_database_connection(connection) + +def save_guild_settings(guild_id, settings): + """Speichert Guild-Einstellungen in der Datenbank""" + connection = None + cursor = None + try: + connection = connect_to_database() + cursor = connection.cursor() + + insert_query = """ + INSERT INTO guild_settings (guild_id, mute_role_id, mute_role_name, auto_create_mute_role, + max_warn_threshold, auto_mute_on_warns, auto_mute_duration, + log_channel_id, mod_log_enabled) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) + ON DUPLICATE KEY UPDATE + mute_role_id = VALUES(mute_role_id), + mute_role_name = VALUES(mute_role_name), + auto_create_mute_role = VALUES(auto_create_mute_role), + max_warn_threshold = VALUES(max_warn_threshold), + auto_mute_on_warns = VALUES(auto_mute_on_warns), + auto_mute_duration = VALUES(auto_mute_duration), + log_channel_id = VALUES(log_channel_id), + mod_log_enabled = VALUES(mod_log_enabled) + """ + + cursor.execute(insert_query, ( + guild_id, + settings.get("mute_role_id"), + settings.get("mute_role_name", "Muted"), + settings.get("auto_create_mute_role", True), + settings.get("max_warn_threshold", 3), + settings.get("auto_mute_on_warns", False), + settings.get("auto_mute_duration", "1h"), + settings.get("log_channel_id"), + settings.get("mod_log_enabled", True) + )) + connection.commit() + + logger.info(f"Guild settings saved for guild {guild_id}") + + except Exception as e: + logger.error(f"Error saving guild settings: {e}") + if connection: + connection.rollback() + finally: + if cursor: + cursor.close() + if connection: + close_database_connection(connection) + +async def get_or_create_mute_role(guild, settings): + """Holt oder erstellt die Mute-Rolle basierend auf Guild-Einstellungen""" + mute_role = None + + # Versuche zuerst über ID zu finden + if settings["mute_role_id"]: + mute_role = guild.get_role(settings["mute_role_id"]) + if mute_role: + return mute_role + + # Versuche über Name zu finden + mute_role = discord.utils.get(guild.roles, name=settings["mute_role_name"]) + if mute_role: + # Update die ID in den Einstellungen + settings["mute_role_id"] = mute_role.id + save_guild_settings(guild.id, settings) + return mute_role + + # Erstelle neue Rolle, falls auto_create_mute_role aktiviert ist + if settings["auto_create_mute_role"]: + try: + mute_role = await guild.create_role( + name=settings["mute_role_name"], + color=discord.Color.dark_gray(), + reason="Auto-created mute role for moderation system" + ) + + # Konfiguriere Berechtigungen für alle Kanäle + for channel in guild.channels: + try: + await channel.set_permissions( + mute_role, + send_messages=False, + speak=False, + add_reactions=False, + create_private_threads=False, + create_public_threads=False, + send_messages_in_threads=False + ) + except discord.Forbidden: + logger.warning(f"Could not set permissions for {channel.name} in guild {guild.id}") + continue + + # Speichere die neue Rolle-ID + settings["mute_role_id"] = mute_role.id + save_guild_settings(guild.id, settings) + + logger.info(f"Created mute role '{settings['mute_role_name']}' for guild {guild.id}") + return mute_role + + except discord.Forbidden: + logger.error(f"No permission to create mute role in guild {guild.id}") + return None + + return None + +# ================================ MODERATION SYSTEM ================================ + +# Moderation Helper Functions +def check_moderation_permission(user_permission): + """Überprüft, ob der Nutzer Moderationsrechte hat (Permission 5 oder höher)""" + return user_permission >= 5 + +async def save_user_roles(user_id, guild_id, roles): + """Speichert die Rollen eines Users vor einem Mute""" + connection = None + cursor = None + try: + connection = connect_to_database() + cursor = connection.cursor() + + # Serialisiere die Rollen-IDs + role_ids = [str(role.id) for role in roles if not role.is_default()] + serialized_roles = json.dumps(role_ids) + + insert_query = """ + INSERT INTO user_saved_roles (user_id, guild_id, roles, saved_at) + VALUES (%s, %s, %s, %s) + ON DUPLICATE KEY UPDATE roles = %s, saved_at = %s + """ + current_time = datetime.now() + cursor.execute(insert_query, (user_id, guild_id, serialized_roles, current_time, serialized_roles, current_time)) + connection.commit() + + logger.info(f"Saved roles for user {user_id} in guild {guild_id}") + + except Exception as e: + logger.error(f"Error saving user roles: {e}") + if connection: + connection.rollback() + finally: + if cursor: + cursor.close() + if connection: + close_database_connection(connection) + +async def restore_user_roles(user, guild): + """Stellt die gespeicherten Rollen eines Users wieder her""" + connection = None + cursor = None + try: + connection = connect_to_database() + cursor = connection.cursor() + + select_query = "SELECT roles FROM user_saved_roles WHERE user_id = %s AND guild_id = %s" + cursor.execute(select_query, (user.id, guild.id)) + result = cursor.fetchone() + + if result: + role_ids = json.loads(result[0]) + roles_to_add = [] + + for role_id in role_ids: + role = guild.get_role(int(role_id)) + if role and role < guild.me.top_role: # Überprüfe, ob Bot die Rolle vergeben kann + roles_to_add.append(role) + + if roles_to_add: + await user.add_roles(*roles_to_add, reason="Mute expired - restoring roles") + logger.info(f"Restored {len(roles_to_add)} roles for user {user.id}") + + # Lösche die gespeicherten Rollen + delete_query = "DELETE FROM user_saved_roles WHERE user_id = %s AND guild_id = %s" + cursor.execute(delete_query, (user.id, guild.id)) + connection.commit() + + except Exception as e: + logger.error(f"Error restoring user roles: {e}") + if connection: + connection.rollback() + finally: + if cursor: + cursor.close() + if connection: + close_database_connection(connection) + +@client.hybrid_command() +async def warn(ctx, user: discord.User, *, reason: str = "Keine Begründung angegeben"): + """Warnt einen Benutzer (Benötigt Permission Level 5 oder höher)""" + try: + # Lade Moderator-Daten + mod_data = await load_user_data(ctx.author.id, ctx.guild.id) + + # Überprüfe Moderationsrechte + if not check_moderation_permission(mod_data["permission"]): + await ctx.send("❌ Du hast keine Berechtigung, diesen Befehl zu verwenden. (Benötigt Permission Level 5 oder höher)") + return + + # Lade User-Daten + user_data = await load_user_data(user.id, ctx.guild.id) + + # Erhöhe Warn-Count + user_data["warns"] += 1 + update_user_data(user.id, ctx.guild.id, "warns", user_data["warns"]) + + # Erstelle Embed + embed = discord.Embed( + title="⚠️ Warnung erhalten", + description=f"{user.mention} wurde gewarnt.", + color=0xff9500, + timestamp=datetime.now() + ) + embed.add_field(name="Grund", value=reason, inline=False) + embed.add_field(name="Moderator", value=ctx.author.mention, inline=True) + embed.add_field(name="Warn-Count", value=f"{user_data['warns']}", inline=True) + embed.set_footer(text=f"User ID: {user.id}") + + await ctx.send(embed=embed) + + # Log the action + logger.info(f"User {user.id} warned by {ctx.author.id} in guild {ctx.guild.id}. Reason: {reason}") + + # Auto-Aktionen basierend auf Warn-Count + if user_data["warns"] >= 3: + embed_auto = discord.Embed( + title="🚨 Auto-Aktion ausgelöst", + description=f"{user.mention} hat {user_data['warns']} Warnungen erreicht!", + color=0xff0000 + ) + embed_auto.add_field(name="Empfehlung", value="Erwäge weitere Moderationsmaßnahmen", inline=False) + await ctx.send(embed=embed_auto) + + except Exception as e: + logger.error(f"Error in warn command: {e}") + await ctx.send("❌ Ein Fehler ist aufgetreten beim Warnen des Benutzers.") + +@client.hybrid_command() +async def mute(ctx, user: discord.User, duration: str, *, reason: str = "Keine Begründung angegeben"): + """Mutet einen Benutzer für eine bestimmte Dauer (Benötigt Permission Level 5 oder höher) + + Beispiele für Dauer: + - 10m = 10 Minuten + - 1h = 1 Stunde + - 2d = 2 Tage + """ + try: + # Lade Moderator-Daten + mod_data = await load_user_data(ctx.author.id, ctx.guild.id) + + # Überprüfe Moderationsrechte + if not check_moderation_permission(mod_data["permission"]): + await ctx.send("❌ Du hast keine Berechtigung, diesen Befehl zu verwenden. (Benötigt Permission Level 5 oder höher)") + return + + # Parse Dauer + time_units = {'m': 60, 'h': 3600, 'd': 86400} + if not duration[-1] in time_units or not duration[:-1].isdigit(): + await ctx.send("❌ Ungültiges Zeitformat. Verwende: 10m, 1h, 2d") + return + + duration_seconds = int(duration[:-1]) * time_units[duration[-1]] + end_time = datetime.now() + timedelta(seconds=duration_seconds) + + # Hole Member-Objekt + member = ctx.guild.get_member(user.id) + if not member: + await ctx.send("❌ Benutzer nicht auf diesem Server gefunden.") + return + + # Lade Guild-Einstellungen + guild_settings = get_guild_settings(ctx.guild.id) + + # Speichere aktuelle Rollen + await save_user_roles(user.id, ctx.guild.id, member.roles) + + # Entferne alle Rollen außer @everyone + roles_to_remove = [role for role in member.roles if not role.is_default()] + if roles_to_remove: + await member.remove_roles(*roles_to_remove, reason=f"Muted by {ctx.author}") + + # Hole oder erstelle Mute-Rolle basierend auf Einstellungen + mute_role = await get_or_create_mute_role(ctx.guild, guild_settings) + if not mute_role: + await ctx.send("❌ Konnte Mute-Rolle nicht finden oder erstellen. Überprüfe die Server-Einstellungen.") + return + + # Vergebe Mute-Rolle + await member.add_roles(mute_role, reason=f"Muted by {ctx.author} for {duration}") + + # Update User-Daten + user_data = await load_user_data(user.id, ctx.guild.id) + user_data["mutes"] += 1 + update_user_data(user.id, ctx.guild.id, "mutes", user_data["mutes"]) + + # Erstelle Active Process für Auto-Unmute + process_data = { + "user_id": user.id, + "guild_id": ctx.guild.id, + "channel_id": ctx.channel.id, + "reason": reason, + "moderator_id": ctx.author.id, + "mute_role_id": mute_role.id + } + + process_uuid = create_active_process( + process_type="mute", + guild_id=ctx.guild.id, + channel_id=ctx.channel.id, + user_id=user.id, + target_id=user.id, + end_time=end_time, + data=process_data + ) + + # Erstelle Embed + embed = discord.Embed( + title="🔇 Benutzer gemutet", + description=f"{user.mention} wurde gemutet.", + color=0xff0000, + timestamp=datetime.now() + ) + embed.add_field(name="Dauer", value=duration, inline=True) + embed.add_field(name="Endet am", value=end_time.strftime("%Y-%m-%d %H:%M:%S"), inline=True) + embed.add_field(name="Grund", value=reason, inline=False) + embed.add_field(name="Moderator", value=ctx.author.mention, inline=True) + embed.add_field(name="Mute-Count", value=f"{user_data['mutes']}", inline=True) + embed.set_footer(text=f"User ID: {user.id} | Process ID: {str(process_uuid)[:8]}") + + await ctx.send(embed=embed) + + # Log the action + logger.info(f"User {user.id} muted by {ctx.author.id} in guild {ctx.guild.id} for {duration}. Reason: {reason}") + + except Exception as e: + logger.error(f"Error in mute command: {e}") + await ctx.send("❌ Ein Fehler ist aufgetreten beim Muten des Benutzers.") + +@client.hybrid_command() +async def unmute(ctx, user: discord.User): + """Entmutet einen Benutzer manuell (Benötigt Permission Level 5 oder höher)""" + try: + # Lade Moderator-Daten + mod_data = await load_user_data(ctx.author.id, ctx.guild.id) + + # Überprüfe Moderationsrechte + if not check_moderation_permission(mod_data["permission"]): + await ctx.send("❌ Du hast keine Berechtigung, diesen Befehl zu verwenden. (Benötigt Permission Level 5 oder höher)") + return + + # Hole Member-Objekt + member = ctx.guild.get_member(user.id) + if not member: + await ctx.send("❌ Benutzer nicht auf diesem Server gefunden.") + return + + # Lade Guild-Einstellungen + guild_settings = get_guild_settings(ctx.guild.id) + + # Finde Mute-Rolle basierend auf Einstellungen + mute_role = None + if guild_settings["mute_role_id"]: + mute_role = ctx.guild.get_role(guild_settings["mute_role_id"]) + + if not mute_role: + mute_role = discord.utils.get(ctx.guild.roles, name=guild_settings["mute_role_name"]) + + if not mute_role or mute_role not in member.roles: + await ctx.send("❌ Benutzer ist nicht gemutet.") + return + + # Entferne Mute-Rolle + await member.remove_roles(mute_role, reason=f"Unmuted by {ctx.author}") + + # Stelle Rollen wieder her + await restore_user_roles(member, ctx.guild) + + # Finde und aktualisiere aktiven Mute-Prozess + active_processes = get_active_processes(process_type="mute", guild_id=ctx.guild.id) + for process in active_processes: + if process["target_id"] == user.id: + update_process_status(process["uuid"], "cancelled_manual") + break + + # Erstelle Embed + embed = discord.Embed( + title="🔊 Benutzer entmutet", + description=f"{user.mention} wurde entmutet.", + color=0x00ff00, + timestamp=datetime.now() + ) + embed.add_field(name="Moderator", value=ctx.author.mention, inline=True) + embed.set_footer(text=f"User ID: {user.id}") + + await ctx.send(embed=embed) + + # Log the action + logger.info(f"User {user.id} unmuted by {ctx.author.id} in guild {ctx.guild.id}") + + except Exception as e: + logger.error(f"Error in unmute command: {e}") + await ctx.send("❌ Ein Fehler ist aufgetreten beim Entmuten des Benutzers.") + +@client.hybrid_command() +async def modstats(ctx, user: discord.User = None): + """Zeigt Moderationsstatistiken für einen Benutzer an""" + try: + # Falls kein User angegeben, zeige eigene Stats + target_user = user or ctx.author + + # Lade User-Daten + user_data = await load_user_data(target_user.id, ctx.guild.id) + + # Erstelle Embed + embed = discord.Embed( + title=f"📊 Moderationsstatistiken", + description=f"Statistiken für {target_user.mention}", + color=0x3498db, + timestamp=datetime.now() + ) + + embed.add_field(name="🤖 AI Bans", value=user_data.get("ai_ban", 0), inline=True) + embed.add_field(name="🔇 Mutes", value=user_data.get("mutes", 0), inline=True) + embed.add_field(name="⚠️ Warnungen", value=user_data.get("warns", 0), inline=True) + embed.add_field(name="🛡️ Permission Level", value=user_data.get("permission", 0), inline=True) + embed.add_field(name="⭐ Level", value=user_data.get("level", 1), inline=True) + embed.add_field(name="💰 Punkte", value=user_data.get("points", 0), inline=True) + + embed.set_thumbnail(url=target_user.display_avatar.url) + embed.set_footer(text=f"User ID: {target_user.id}") + + await ctx.send(embed=embed) + + except Exception as e: + logger.error(f"Error in modstats command: {e}") + await ctx.send("❌ Ein Fehler ist aufgetreten beim Laden der Moderationsstatistiken.") + +@client.hybrid_command() +async def modconfig(ctx, setting: str = None, *, value: str = None): + """Konfiguriert Moderationseinstellungen für den Server (Benötigt Permission Level 8 oder höher) + + Verfügbare Einstellungen: + - mute_role <@role/role_name> - Setzt die Mute-Rolle + - mute_role_name - Setzt den Namen für auto-erstellte Mute-Rollen + - auto_create_mute_role - Auto-Erstellung von Mute-Rollen + - max_warn_threshold - Anzahl Warnungen vor Auto-Aktion + - auto_mute_on_warns - Auto-Mute bei zu vielen Warnungen + - auto_mute_duration - Dauer für Auto-Mutes (z.B. 1h, 30m) + - log_channel <#channel> - Kanal für Moderations-Logs + - mod_log_enabled - Aktiviert/Deaktiviert Moderations-Logs + """ + try: + # Lade Moderator-Daten + mod_data = await load_user_data(ctx.author.id, ctx.guild.id) + + # Überprüfe Admin-Rechte (Level 8+) + if mod_data["permission"] < 8: + await ctx.send("❌ Du hast keine Berechtigung, Moderationseinstellungen zu ändern. (Benötigt Permission Level 8 oder höher)") + return + + # Lade aktuelle Einstellungen + guild_settings = get_guild_settings(ctx.guild.id) + + # Zeige Einstellungen an, falls keine Parameter + if not setting: + embed = discord.Embed( + title="🛠️ Moderationseinstellungen", + description=f"Aktuelle Einstellungen für **{ctx.guild.name}**", + color=0x3498db, + timestamp=datetime.now() + ) + + # Mute-Rolle Info + mute_role_info = "Nicht gesetzt" + if guild_settings["mute_role_id"]: + role = ctx.guild.get_role(guild_settings["mute_role_id"]) + mute_role_info = role.mention if role else f"❌ Rolle nicht gefunden (ID: {guild_settings['mute_role_id']})" + + embed.add_field(name="🔇 Mute-Rolle", value=mute_role_info, inline=False) + embed.add_field(name="📝 Mute-Rollen-Name", value=guild_settings["mute_role_name"], inline=True) + embed.add_field(name="🔧 Auto-Erstellen", value="✅" if guild_settings["auto_create_mute_role"] else "❌", inline=True) + embed.add_field(name="⚠️ Warn-Limit", value=guild_settings["max_warn_threshold"], inline=True) + embed.add_field(name="🔄 Auto-Mute bei Warns", value="✅" if guild_settings["auto_mute_on_warns"] else "❌", inline=True) + embed.add_field(name="⏱️ Auto-Mute-Dauer", value=guild_settings["auto_mute_duration"], inline=True) + + # Log-Kanal Info + log_info = "Nicht gesetzt" + if guild_settings["log_channel_id"]: + channel = ctx.guild.get_channel(guild_settings["log_channel_id"]) + log_info = channel.mention if channel else f"❌ Kanal nicht gefunden (ID: {guild_settings['log_channel_id']})" + + embed.add_field(name="📊 Log-Kanal", value=log_info, inline=True) + embed.add_field(name="📝 Logs Aktiviert", value="✅" if guild_settings["mod_log_enabled"] else "❌", inline=True) + + embed.set_footer(text="Verwende -modconfig zum Ändern") + await ctx.send(embed=embed) + return + + # Ändere Einstellungen + setting = setting.lower() + + if setting == "mute_role": + if not value: + await ctx.send("❌ Bitte gib eine Rolle an: `-modconfig mute_role @MuteRole`") + return + + # Parse Rolle + role = None + if value.startswith("<@&") and value.endswith(">"): + role_id = int(value[3:-1]) + role = ctx.guild.get_role(role_id) + else: + role = discord.utils.get(ctx.guild.roles, name=value) + + if not role: + await ctx.send("❌ Rolle nicht gefunden.") + return + + guild_settings["mute_role_id"] = role.id + guild_settings["mute_role_name"] = role.name + save_guild_settings(ctx.guild.id, guild_settings) + + await ctx.send(f"✅ Mute-Rolle auf {role.mention} gesetzt.") + + elif setting == "mute_role_name": + if not value: + await ctx.send("❌ Bitte gib einen Namen an: `-modconfig mute_role_name Stumm`") + return + + guild_settings["mute_role_name"] = value + save_guild_settings(ctx.guild.id, guild_settings) + + await ctx.send(f"✅ Mute-Rollen-Name auf `{value}` gesetzt.") + + elif setting == "auto_create_mute_role": + if value.lower() in ["true", "1", "ja", "yes", "on"]: + guild_settings["auto_create_mute_role"] = True + await ctx.send("✅ Auto-Erstellung von Mute-Rollen aktiviert.") + elif value.lower() in ["false", "0", "nein", "no", "off"]: + guild_settings["auto_create_mute_role"] = False + await ctx.send("✅ Auto-Erstellung von Mute-Rollen deaktiviert.") + else: + await ctx.send("❌ Ungültiger Wert. Verwende: true/false") + return + + save_guild_settings(ctx.guild.id, guild_settings) + + elif setting == "max_warn_threshold": + try: + threshold = int(value) + if threshold < 1 or threshold > 10: + await ctx.send("❌ Warn-Limit muss zwischen 1 und 10 liegen.") + return + + guild_settings["max_warn_threshold"] = threshold + save_guild_settings(ctx.guild.id, guild_settings) + + await ctx.send(f"✅ Warn-Limit auf {threshold} gesetzt.") + + except ValueError: + await ctx.send("❌ Ungültiger Wert. Verwende eine Nummer zwischen 1 und 10.") + return + + elif setting == "auto_mute_on_warns": + if value.lower() in ["true", "1", "ja", "yes", "on"]: + guild_settings["auto_mute_on_warns"] = True + await ctx.send("✅ Auto-Mute bei zu vielen Warnungen aktiviert.") + elif value.lower() in ["false", "0", "nein", "no", "off"]: + guild_settings["auto_mute_on_warns"] = False + await ctx.send("✅ Auto-Mute bei zu vielen Warnungen deaktiviert.") + else: + await ctx.send("❌ Ungültiger Wert. Verwende: true/false") + return + + save_guild_settings(ctx.guild.id, guild_settings) + + elif setting == "auto_mute_duration": + # Validiere Dauer-Format + time_units = {'m': 60, 'h': 3600, 'd': 86400} + if not value or not value[-1] in time_units or not value[:-1].isdigit(): + await ctx.send("❌ Ungültiges Zeitformat. Verwende: 10m, 1h, 2d") + return + + guild_settings["auto_mute_duration"] = value + save_guild_settings(ctx.guild.id, guild_settings) + + await ctx.send(f"✅ Auto-Mute-Dauer auf {value} gesetzt.") + + elif setting == "log_channel": + if not value: + await ctx.send("❌ Bitte gib einen Kanal an: `-modconfig log_channel #mod-log`") + return + + # Parse Kanal + channel = None + if value.startswith("<#") and value.endswith(">"): + channel_id = int(value[2:-1]) + channel = ctx.guild.get_channel(channel_id) + else: + channel = discord.utils.get(ctx.guild.channels, name=value.replace("#", "")) + + if not channel: + await ctx.send("❌ Kanal nicht gefunden.") + return + + guild_settings["log_channel_id"] = channel.id + save_guild_settings(ctx.guild.id, guild_settings) + + await ctx.send(f"✅ Log-Kanal auf {channel.mention} gesetzt.") + + elif setting == "mod_log_enabled": + if value.lower() in ["true", "1", "ja", "yes", "on"]: + guild_settings["mod_log_enabled"] = True + await ctx.send("✅ Moderations-Logs aktiviert.") + elif value.lower() in ["false", "0", "nein", "no", "off"]: + guild_settings["mod_log_enabled"] = False + await ctx.send("✅ Moderations-Logs deaktiviert.") + else: + await ctx.send("❌ Ungültiger Wert. Verwende: true/false") + return + + save_guild_settings(ctx.guild.id, guild_settings) + + else: + await ctx.send("❌ Unbekannte Einstellung. Verwende `-modconfig` ohne Parameter für eine Liste aller Einstellungen.") + + except Exception as e: + logger.error(f"Error in modconfig command: {e}") + await ctx.send("❌ Ein Fehler ist aufgetreten beim Konfigurieren der Moderationseinstellungen.") + +# Erweitere handle_expired_mute Funktion +async def handle_expired_mute(process_uuid, data): + """Handles expired mute processes""" + try: + guild_id = data.get("guild_id") + user_id = data.get("user_id") + mute_role_id = data.get("mute_role_id") + + if not all([guild_id, user_id]): + logger.error(f"Missing data in mute process {process_uuid}") + update_process_status(process_uuid, "failed") + return + + # Hole Guild und Member + guild = client.get_guild(guild_id) + if not guild: + logger.error(f"Guild {guild_id} not found for mute process {process_uuid}") + update_process_status(process_uuid, "failed") + return + + member = guild.get_member(user_id) + if not member: + logger.warning(f"Member {user_id} not found in guild {guild_id} for mute process {process_uuid}") + update_process_status(process_uuid, "completed") + return + + # Finde Mute-Rolle basierend auf Guild-Einstellungen + guild_settings = get_guild_settings(guild_id) + mute_role = None + + if mute_role_id: + mute_role = guild.get_role(mute_role_id) + + if not mute_role and guild_settings["mute_role_id"]: + mute_role = guild.get_role(guild_settings["mute_role_id"]) + + if not mute_role: + mute_role = discord.utils.get(guild.roles, name=guild_settings["mute_role_name"]) + + # Entferne Mute-Rolle falls vorhanden + if mute_role and mute_role in member.roles: + await member.remove_roles(mute_role, reason="Mute expired automatically") + + # Stelle Rollen wieder her + await restore_user_roles(member, guild) + + update_process_status(process_uuid, "completed") + logger.info(f"Successfully unmuted user {user_id} in guild {guild_id} (expired mute)") + + except Exception as e: + logger.error(f"Error handling expired mute {process_uuid}: {e}") + update_process_status(process_uuid, "failed") + # Cache-Ordner für Notizen CACHE_DIR = "cache" if not os.path.exists(CACHE_DIR): diff --git a/templates/server_admin_dashboard.html b/templates/server_admin_dashboard.html index 1ca22ef..70900fa 100644 --- a/templates/server_admin_dashboard.html +++ b/templates/server_admin_dashboard.html @@ -173,6 +173,7 @@ .giveaway-icon { color: #ffd700; } .user-icon { color: #4299e1; } + .settings-icon { color: #9f7aea; } /* Responsive Design */ @media (max-width: 768px) { @@ -267,6 +268,44 @@ + +
+
+
+ Server Settings +
+

Configure server-specific settings for moderation, logging, and automation.

+
+
+
+
+ Moderation Settings +
+

+ Configure mute roles, warn thresholds, and automatic moderation actions. +

+ + Configure Settings + +
+
+
+
+
+ Log Configuration +
+

+ Set up moderation logs and monitoring channels for server activity. +

+ + Setup Logging + +
+
+
+
+
+
diff --git a/templates/server_settings.html b/templates/server_settings.html new file mode 100644 index 0000000..c339b7a --- /dev/null +++ b/templates/server_settings.html @@ -0,0 +1,398 @@ + + + + + + Server-Einstellungen - {{ guild_name }} + + + + + + {% include 'navigation.html' %} + +
+
+

+ + Server-Einstellungen +

+

{{ guild_name }}

+
+ + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
+ + {{ message }} +
+ {% endfor %} + {% endif %} + {% endwith %} + +
+ +
+

+ + Mute-Einstellungen +

+ +
+
+ + Hinweis zu Rollen-IDs +
+

+ Um eine Rollen-ID zu finden: Rechtsklick auf die Rolle → "ID kopieren" (Entwicklermodus muss aktiviert sein) +

+
+ +
+
+
+ + + Leer lassen für automatische Suche nach Name +
+
+
+
+ + + Name für automatisch erstellte Mute-Rollen +
+
+
+ +
+ + + Erstellt automatisch eine Mute-Rolle, falls keine gefunden wird +
+
+ + +
+

+ + Warn-Einstellungen +

+ +
+
+
+ + + Anzahl Warnungen vor automatischen Aktionen +
+
+
+
+ + + Format: 10m, 1h, 2d (Minuten, Stunden, Tage) +
+
+
+ +
+ + + Mutet Benutzer automatisch bei Erreichen des Warn-Limits +
+
+ + +
+

+ + Log-Einstellungen +

+ +
+ + + Kanal für Moderations-Logs (leer lassen zum Deaktivieren) +
+ +
+ + + Protokolliert alle Moderationsaktionen +
+
+ + +
+ + + Zurück zum Dashboard + +
+
+
+ + + + + + +