__version__ = "dev-0.8.3" __all__ = ["Discordbot-chatai-webpanel (Discord)"] __author__ = "SimolZimol" from flask import Flask, render_template, redirect, url_for, request, session, jsonify, send_file, flash, g from requests_oauthlib import OAuth2Session import os import subprocess import psutil import time import mysql.connector import mysql.connector.pooling from datetime import datetime, timedelta from flask_session import Session import logging app = Flask(__name__) app.secret_key = os.getenv("FLASK_SECRET_KEY") # Disable Flask/Werkzeug logging for static files and 304 responses logging.getLogger('werkzeug').setLevel(logging.ERROR) app.logger.setLevel(logging.ERROR) # Configure custom logging to only show important messages log = logging.getLogger('werkzeug') log.setLevel(logging.ERROR) # Override Flask's logger to filter out static file requests class NoStaticFilter(logging.Filter): def filter(self, record): # Filter out static file requests and 304 responses if hasattr(record, 'msg'): msg = str(record.msg) if '/static/' in msg and ('304' in msg or '200' in msg): return False if 'GET /static/' in msg: return False return True # Apply filter to werkzeug logger werkzeug_logger = logging.getLogger('werkzeug') werkzeug_logger.addFilter(NoStaticFilter()) LOG_FILE_PATH = os.path.join("logs", f"{datetime.now().strftime('%Y-%m-%d')}.log") app.config["SESSION_TYPE"] = "filesystem" # Oder 'redis' für Redis-basierte Speicherung Session(app) print(f"Session Type: {app.config['SESSION_TYPE']}") DB_HOST = os.getenv("DB_HOST") DB_PORT = os.getenv("DB_PORT") DB_USER = os.getenv("DB_USER") DB_PASS = os.getenv("DB_PASSWORD") DB_NAME = os.getenv("DB_DATABASE") DISCORD_CLIENT_ID = os.getenv("DISCORD_CLIENT_ID") DISCORD_CLIENT_SECRET = os.getenv("DISCORD_CLIENT_SECRET") DISCORD_REDIRECT_URI = os.getenv("DISCORD_REDIRECT_URI") DISCORD_OAUTH2_URL = "https://discord.com/api/oauth2/authorize" DISCORD_TOKEN_URL = "https://discord.com/api/oauth2/token" DISCORD_API_URL = "https://discord.com/api/users/@me" os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' bot_process = None def bot_status(): """Überprüft, ob der Bot läuft.""" global bot_process if bot_process is None: return False return bot_process.poll() is None # None bedeutet, dass der Prozess noch läuft def start_bot(): """Startet den Bot.""" global bot_process if not bot_status(): bot_process = subprocess.Popen(["python", "bot.py"], cwd=os.path.dirname(os.path.abspath(__file__))) else: print("Bot läuft bereits.") def stop_bot(): """Stoppt den Bot.""" global bot_process if bot_process and bot_status(): bot_process.terminate() bot_process.wait() # Warten, bis der Prozess beendet ist bot_process = None else: print("Bot läuft nicht.") # Database Connection Pool für bessere Verbindungsverwaltung app_pool = mysql.connector.pooling.MySQLConnectionPool( pool_name="app_pool", pool_size=8, # Stark reduziert von 15 auf 8 (Bot: 25, App: 8 = 33 total) pool_reset_session=True, host=DB_HOST, port=DB_PORT, user=DB_USER, password=DB_PASS, database=DB_NAME, # Zusätzliche Pool-Optimierungen use_pure=True, # Verwende Pure Python Connector für bessere Stabilität connect_timeout=10, # Timeout für Verbindungsaufbau autocommit=True, # Für bessere Pool-Performance ) def get_db_connection(): """Stellt eine Verbindung zur MySQL-Datenbank über Connection Pool her.""" try: connection = app_pool.get_connection() return connection except mysql.connector.PoolError as e: print(f"Pool error in app.py: {e}") # Besserer Fallback mit Retry-Mechanismus for attempt in range(3): try: print(f"Attempting direct connection (attempt {attempt + 1}/3)") return mysql.connector.connect( host=DB_HOST, port=DB_PORT, user=DB_USER, password=DB_PASS, database=DB_NAME, connect_timeout=5, autocommit=True ) except Exception as retry_error: print(f"Direct connection attempt {attempt + 1} failed: {retry_error}") if attempt == 2: # Letzter Versuch raise retry_error time.sleep(1) # Kurze Pause zwischen Versuchen from contextlib import contextmanager @contextmanager def get_db_cursor(): """Context Manager für sichere Datenbankverbindungen mit automatischer Bereinigung.""" connection = None cursor = None try: connection = get_db_connection() cursor = connection.cursor(dictionary=True) yield cursor, connection except Exception as e: if connection: connection.rollback() raise e finally: if cursor: cursor.close() if connection and connection.is_connected(): connection.close() def get_pool_status(): """Gibt Pool-Status zurück für Monitoring""" try: return { "pool_size": app_pool.pool_size, "connections_in_use": len(app_pool._cnx_queue._queue) if hasattr(app_pool, '_cnx_queue') else 'Unknown' } except Exception as e: return {"error": str(e)} def token_updater(token): session['oauth_token'] = token def make_discord_session(token=None, state=None): return OAuth2Session( DISCORD_CLIENT_ID, token=token or session.get("oauth_token"), state=state, redirect_uri=DISCORD_REDIRECT_URI, scope=["identify", "guilds"], auto_refresh_kwargs={ 'client_id': DISCORD_CLIENT_ID, 'client_secret': DISCORD_CLIENT_SECRET, }, auto_refresh_url=DISCORD_TOKEN_URL, token_updater=token_updater ) @app.route("/logs") def view_logs(): """Zeigt die Logs des Bots im Admin-Panel an.""" if is_bot_admin(): return render_template("logs.html") return redirect(url_for("landing_page")) @app.route("/get_logs") def get_logs(): """Liest den Inhalt der Log-Datei und gibt ihn zurück.""" if is_bot_admin(): try: with open(LOG_FILE_PATH, 'r', encoding='utf-8') as file: logs = file.read() return jsonify({"logs": logs}) except FileNotFoundError: return jsonify({"logs": "Log file not found."}) return redirect(url_for("landing_page")) @app.route("/download_logs") def download_logs(): """Bietet die Log-Datei zum Download an.""" if is_bot_admin(): return send_file(LOG_FILE_PATH, as_attachment=True) return redirect(url_for("landing_page")) def is_bot_admin(): """Überprüft, ob der Benutzer globale Admin-Rechte hat.""" if "discord_user" in session: user_info = session["discord_user"] user_id = user_info["id"] try: with get_db_cursor() as (cursor, connection): cursor.execute("SELECT global_permission FROM bot_data WHERE user_id = %s", (user_id,)) user_data = cursor.fetchone() return user_data and user_data["global_permission"] >= 8 except Exception as e: print(f"Database error in is_bot_admin: {e}") return False return False def is_server_admin(guild_id): """Überprüft, ob der Benutzer Admin-Rechte auf einem bestimmten Server (Guild) hat.""" if "discord_user" in session: user_info = session["discord_user"] user_id = user_info["id"] try: with get_db_cursor() as (cursor, connection): cursor.execute("SELECT permission FROM user_data WHERE user_id = %s AND guild_id = %s", (user_id, guild_id)) user_data = cursor.fetchone() return user_data and user_data["permission"] >= 8 except Exception as e: print(f"Database error in is_server_admin: {e}") return False return False @app.route("/") def landing_page(): """Landing Page""" return render_template("landing.html") @app.route("/db_status") def db_status(): """Debug-Route für DB-Pool Status""" try: status = get_pool_status() with get_db_cursor() as (cursor, connection): cursor.execute("SELECT 1") db_ok = True except Exception as e: status = {"error": str(e)} db_ok = False return jsonify({ "pool_status": status, "database_ok": db_ok, "timestamp": datetime.now().isoformat() }) @app.route("/about") def about(): """Öffentliche Über-uns-Seite""" return render_template("about.html") @app.route("/contact") def contact(): """Öffentliche Kontaktseite""" return render_template("contact.html") @app.route("/faq") def faq(): """Öffentliche FAQ-Seite""" return render_template("faq.html") @app.route("/help") def help_page(): """Öffentliche Hilfeseite""" return render_template("help.html") @app.route("/login") def login(): """Startet den Discord-OAuth2-Flow.""" discord = make_discord_session() authorization_url, state = discord.authorization_url(DISCORD_OAUTH2_URL) session['oauth_state'] = state return redirect(authorization_url) @app.before_request def load_user_data(): """Lädt Benutzerdaten vor jeder Anfrage für geschützte Routen.""" if "discord_user" in session: g.user_info = session["discord_user"] g.is_admin = session.get("is_admin", False) g.bot_running = bot_status() # Lädt den Bot-Status in g # Lade die Gilden des Nutzers aus der Session (ohne DB-Zugriff) user_guilds = session.get("discord_guilds", []) # Nur DB-Zugriff wenn Gilden-Daten nicht im Cache sind guild_cache_key = f"guild_cache_{g.user_info['id']}" if guild_cache_key not in session or not session[guild_cache_key]: try: with get_db_cursor() as (cursor, connection): user_guild_ids = [guild["id"] for guild in user_guilds] if user_guild_ids: # Nur wenn Gilden existieren # Finde nur die Gilden, die auch in der Datenbank existieren placeholders = ','.join(['%s'] * len(user_guild_ids)) cursor.execute(f"SELECT guild_id FROM guilds WHERE guild_id IN ({placeholders})", user_guild_ids) existing_guilds = cursor.fetchall() existing_guild_ids = {g["guild_id"] for g in existing_guilds} # Filtere die Gilden des Nutzers basierend auf der Existenz in der Datenbank filtered_guilds = [guild for guild in user_guilds if int(guild["id"]) in existing_guild_ids] # Cache die gefilterten Gilden für 5 Minuten session[guild_cache_key] = filtered_guilds session[f"{guild_cache_key}_time"] = time.time() g.guilds = filtered_guilds else: g.guilds = [] except Exception as e: print(f"Error loading guild data: {e}") # Fallback: Verwende alle Gilden aus der Session g.guilds = user_guilds # Setze einen Flag für DB-Probleme g.db_error = True else: # Prüfe Cache-Alter (5 Minuten TTL) cache_time = session.get(f"{guild_cache_key}_time", 0) if time.time() - cache_time > 300: # 5 Minuten # Cache ist abgelaufen, lösche ihn session.pop(guild_cache_key, None) session.pop(f"{guild_cache_key}_time", None) g.guilds = user_guilds # Fallback else: # Verwende gecachte Daten g.guilds = session[guild_cache_key] else: # Falls der Benutzer nicht eingeloggt ist, keine Daten setzen g.user_info = None g.is_admin = False g.guilds = [] g.bot_running = False g.db_error = False @app.route("/callback") def callback(): """Verarbeitet den OAuth2-Rückruf von Discord.""" try: discord = make_discord_session(state=session.get("oauth_state")) token = discord.fetch_token( DISCORD_TOKEN_URL, client_secret=DISCORD_CLIENT_SECRET, authorization_response=request.url, ) session['oauth_token'] = token # Abrufen der Benutzerinformationen von Discord user_info = discord.get(DISCORD_API_URL).json() session['discord_user'] = user_info # Hole die Gilden (Server), auf denen der Benutzer ist guilds_response = discord.get('https://discord.com/api/users/@me/guilds') if guilds_response.status_code != 200: flash("Fehler beim Abrufen der Gilden.", "danger") return redirect(url_for("landing_page")) guilds = guilds_response.json() session['discord_guilds'] = guilds # Speichere die Gilden in der Session # Prüfe die Admin-Berechtigungen in der bot_data Tabelle connection = get_db_connection() cursor = connection.cursor(dictionary=True) cursor.execute("SELECT global_permission FROM bot_data WHERE user_id = %s", (user_info["id"],)) bot_admin_data = cursor.fetchone() # Speichere Admin-Rechte in der Session session['is_admin'] = bool(bot_admin_data and bot_admin_data['global_permission'] >= 8) cursor.close() connection.close() # Leite zur User-Landing-Page weiter return redirect(url_for("user_landing_page")) except Exception as e: print(f"Error in OAuth2 callback: {e}") flash("Ein Fehler ist beim Authentifizierungsprozess aufgetreten.", "danger") return redirect(url_for("landing_page")) @app.route("/user_server_data/") def user_server_data(guild_id): """Zeigt die serverbezogenen Nutzerdaten für den ausgewählten Server an.""" if "discord_user" in session: user_info = session["discord_user"] user_id = user_info["id"] connection = get_db_connection() cursor = connection.cursor(dictionary=True) # Hole die serverbezogenen Nutzerdaten cursor.execute("SELECT * FROM user_data WHERE user_id = %s AND guild_id = %s", (user_id, guild_id)) user_data = cursor.fetchone() cursor.close() connection.close() if user_data: return render_template("user_server_data.html", user_info=user_info, user_data=user_data, guild_id=guild_id) else: flash("Keine Daten für diesen Server gefunden.", "warning") return redirect(url_for("user_landing_page")) return redirect(url_for("landing_page")) @app.route("/server_admin_dashboard/") def server_admin_dashboard(guild_id): """Serverbasiertes Admin-Dashboard für server-spezifische Admin-Rechte.""" if is_server_admin(guild_id): connection = get_db_connection() cursor = connection.cursor(dictionary=True) # Giveaways für den Server abrufen cursor.execute("SELECT * FROM giveaway_data WHERE guild_id = %s", (guild_id,)) giveaways = cursor.fetchall() # Benutzer auf dem Server abrufen cursor.execute("SELECT * FROM user_data WHERE guild_id = %s", (guild_id,)) server_users = cursor.fetchall() # Servername aus der guilds-Tabelle holen 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_admin_dashboard.html", giveaways=giveaways, server_users=server_users, guild_id=guild_id, guild_name=guild_name) flash("You do not have permission to access this server's admin dashboard.", "danger") return redirect(url_for("user_landing_page")) @app.route("/edit_user//", methods=["GET", "POST"]) def edit_user(guild_id, user_id): """Bearbeitet die Daten eines spezifischen Benutzers auf einem bestimmten Server.""" if is_server_admin(guild_id): connection = get_db_connection() cursor = connection.cursor(dictionary=True) # Hole die Berechtigungen des aktuellen Admins admin_user_id = session["discord_user"]["id"] cursor.execute("SELECT permission FROM user_data WHERE guild_id = %s AND user_id = %s", (guild_id, admin_user_id)) admin_data = cursor.fetchone() max_permission = admin_data["permission"] if admin_data else 0 if request.method == "POST": points = int(request.form.get("points", 0)) level = int(request.form.get("level", 1)) ban = int(request.form.get("ban", 0)) permission = int(request.form.get("permission", 0)) askmultus = int(request.form.get("askmultus", 0)) filter_value = int(request.form.get("filter_value", 0)) rank = request.form.get("rank", "") xp = int(request.form.get("xp", 0)) # Validierung der Berechtigungen if permission > max_permission: flash("You cannot assign a permission level higher than your own.", "danger") return redirect(url_for("edit_user", guild_id=guild_id, user_id=user_id)) # Update der Benutzerdaten cursor.execute(""" UPDATE user_data SET points = %s, level = %s, ban = %s, permission = %s, askmultus = %s, filter_value = %s, rank = %s, xp = %s WHERE guild_id = %s AND user_id = %s """, (points, level, ban, permission, askmultus, filter_value, rank, xp, guild_id, user_id)) connection.commit() flash("User data updated successfully!", "success") return redirect(url_for("server_admin_dashboard", guild_id=guild_id)) # Daten des spezifischen Benutzers laden cursor.execute("SELECT * FROM user_data WHERE guild_id = %s AND user_id = %s", (guild_id, user_id)) user_data = cursor.fetchone() cursor.close() connection.close() return render_template("edit_user.html", user_data=user_data, guild_id=guild_id, max_permission=max_permission) return redirect(url_for("landing_page")) @app.route("/ban_user//") def ban_user(guild_id, user_id): """Banned einen Benutzer auf einem spezifischen Server.""" if is_server_admin(guild_id): connection = get_db_connection() cursor = connection.cursor() try: cursor.execute("UPDATE user_data SET ban = 1 WHERE user_id = %s AND guild_id = %s", (user_id, guild_id)) connection.commit() return redirect(url_for("server_admin_dashboard", guild_id=guild_id)) except Exception as e: print(f"Error banning user: {e}") connection.rollback() return redirect(url_for("server_admin_dashboard", guild_id=guild_id)) finally: cursor.close() connection.close() return redirect(url_for("landing_page")) @app.route("/update_points//", methods=["POST"]) def update_points(guild_id, user_id): """Aktualisiert die Punkte eines Benutzers auf einem spezifischen Server.""" if is_server_admin(guild_id): points_change = int(request.form["points_change"]) connection = get_db_connection() cursor = connection.cursor() try: cursor.execute("UPDATE user_data SET points = points + %s WHERE user_id = %s AND guild_id = %s", (points_change, user_id, guild_id)) connection.commit() return redirect(url_for("server_admin_dashboard", guild_id=guild_id)) except Exception as e: print(f"Error updating points: {e}") connection.rollback() return redirect(url_for("server_admin_dashboard", guild_id=guild_id)) finally: cursor.close() connection.close() return redirect(url_for("landing_page")) @app.route("/unban_user//") def unban_user(guild_id, user_id): """Entbannt einen Benutzer auf einem spezifischen Server.""" if is_server_admin(guild_id): connection = get_db_connection() cursor = connection.cursor() try: cursor.execute("UPDATE user_data SET ban = 0 WHERE user_id = %s AND guild_id = %s", (user_id, guild_id)) connection.commit() return redirect(url_for("server_admin_dashboard", guild_id=guild_id)) except Exception as e: print(f"Error unbanning user: {e}") connection.rollback() return redirect(url_for("server_admin_dashboard", guild_id=guild_id)) finally: cursor.close() connection.close() return redirect(url_for("landing_page")) @app.route("/update_role//", methods=["POST"]) def update_role(guild_id, user_id): """Aktualisiert die Rolle (Berechtigung) eines Benutzers auf einem spezifischen Server.""" if is_server_admin(guild_id): new_permission = request.form["permission"] connection = get_db_connection() cursor = connection.cursor() try: cursor.execute("UPDATE user_data SET permission = %s WHERE user_id = %s AND guild_id = %s", (new_permission, user_id, guild_id)) connection.commit() return redirect(url_for("server_admin_dashboard", guild_id=guild_id)) except Exception as e: print(f"Error updating role: {e}") connection.rollback() return redirect(url_for("server_admin_dashboard", guild_id=guild_id)) finally: cursor.close() connection.close() return redirect(url_for("landing_page")) @app.route("/user_dashboard/") def user_dashboard(guild_id): """Serverbasiertes User-Dashboard""" if g.user_info: user_id = g.user_info["id"] # Debugging-Ausgaben print(f"Accessing user_dashboard for user_id: {user_id}, guild_id: {guild_id}") # Hole die serverbezogenen Nutzerdaten connection = get_db_connection() cursor = connection.cursor(dictionary=True) # Überprüfe, ob der Benutzer Mitglied des Servers (Gilde) ist cursor.execute("SELECT * FROM user_data WHERE user_id = %s AND guild_id = %s", (user_id, guild_id)) user_data = cursor.fetchone() # Debugging-Ausgabe für user_data print(f"user_data for user_id {user_id} on guild_id {guild_id}: {user_data}") cursor.close() connection.close() if user_data: # Falls `user_data` vorhanden ist, setze `g.guild_id` und `g.user_data` g.guild_id = guild_id g.user_data = user_data print("Access granted to user_dashboard.") return render_template("user_dashboard.html") else: # Debugging-Ausgabe für Fehlerfall print(f"No access for user_id {user_id} on guild_id {guild_id}") flash("You do not have access to this server.", "danger") return redirect(url_for("user_landing_page")) # Falls der Benutzer nicht eingeloggt ist print("User not logged in, redirecting to landing page.") flash("Please log in to view your dashboard.", "danger") return redirect(url_for("landing_page")) @app.route("/user_dashboard//leaderboard") def leaderboard(guild_id): """Zeigt das Level Leaderboard für einen bestimmten Server an.""" if "discord_user" in session: try: with get_db_cursor() as (cursor, connection): current_date = datetime.now() one_month_ago = current_date - timedelta(days=30) # Hole die Leaderboard-Daten cursor.execute(""" SELECT nickname, profile_picture, level, xp, join_date FROM user_data WHERE guild_id = %s AND ban = 0 AND (leave_date IS NULL OR leave_date > %s) ORDER BY level DESC, xp DESC """, (guild_id, one_month_ago)) leaderboard_data = cursor.fetchall() # Hole den Server-Namen aus der guilds-Tabelle 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 f"Server {guild_id}" # Übergabe von enumerate und guild_name an das Template return render_template("leaderboard.html", leaderboard=leaderboard_data, guild_id=guild_id, guild_name=guild_name, enumerate=enumerate) except Exception as e: print(f"Database error in leaderboard: {e}") return "Database connection error", 500 return redirect(url_for("landing_page")) @app.route("/server_giveaways/") def server_giveaways(guild_id): """Serverbasiertes Giveaway-Management.""" if is_server_admin(guild_id): connection = get_db_connection() cursor = connection.cursor(dictionary=True) # Hole die Giveaways für den spezifischen Server cursor.execute("SELECT * FROM giveaway_data WHERE guild_id = %s", (guild_id,)) giveaways = cursor.fetchall() cursor.close() connection.close() return render_template("server_giveaways.html", giveaways=giveaways, guild_id=guild_id) return redirect(url_for("landing_page")) @app.route("/edit_giveaway//", methods=["GET", "POST"]) def edit_giveaway(guild_id, uuid): """Bearbeitet ein spezifisches Giveaway für einen bestimmten Server.""" if is_server_admin(guild_id): connection = get_db_connection() cursor = connection.cursor(dictionary=True) if request.method == "POST": platform = request.form.get("platform") name = request.form.get("name") game_key = request.form.get("game_key") winner_dc_id = request.form.get("winner_dc_id") aktiv = bool(request.form.get("aktiv")) # Update der Giveaways-Daten cursor.execute(""" UPDATE giveaway_data SET platform = %s, name = %s, game_key = %s, winner_dc_id = %s, aktiv = %s WHERE guild_id = %s AND uuid = %s """, (platform, name, game_key, winner_dc_id, aktiv, guild_id, uuid)) connection.commit() flash("Giveaway updated successfully!", "success") # Nach dem Speichern zum server_admin_dashboard weiterleiten return redirect(url_for("server_admin_dashboard", guild_id=guild_id)) # Daten des spezifischen Giveaways laden cursor.execute("SELECT * FROM giveaway_data WHERE guild_id = %s AND uuid = %s", (guild_id, uuid)) giveaway = cursor.fetchone() cursor.close() connection.close() 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.""" if "discord_user" in session: user_info = session["discord_user"] user_id = user_info["id"] connection = get_db_connection() cursor = connection.cursor(dictionary=True) # Suche nach Giveaways, bei denen der eingeloggte Benutzer der Gewinner ist cursor.execute(""" SELECT * FROM giveaway_data WHERE winner_dc_id = %s AND guild_id = %s """, (user_id, guild_id)) won_giveaways = cursor.fetchall() # Hole den Servernamen aus der guilds-Tabelle 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 f"Server {guild_id}" cursor.close() connection.close() return render_template("user_giveaways.html", won_giveaways=won_giveaways, guild_id=guild_id, guild_name=guild_name) return redirect(url_for("landing_page")) @app.route("/redeem_giveaway//", methods=["GET", "POST"]) def redeem_giveaway(guild_id, uuid): """Erlaubt dem Benutzer, einen gewonnenen Giveaway-Code für einen bestimmten Server einzulösen und erneut anzuzeigen, falls bereits eingelöst.""" if "discord_user" in session: user_info = session["discord_user"] user_id = user_info["id"] connection = get_db_connection() cursor = connection.cursor(dictionary=True) # Überprüfen, ob der eingeloggte Benutzer der Gewinner ist cursor.execute(""" SELECT * FROM giveaway_data WHERE guild_id = %s AND uuid = %s AND winner_dc_id = %s """, (guild_id, uuid, user_id)) giveaway = cursor.fetchone() if giveaway: if request.method == "POST" and not giveaway["aktiv"]: # Wenn der Benutzer den Key einlöst, setze `aktiv` auf TRUE cursor.execute("UPDATE giveaway_data SET aktiv = TRUE WHERE guild_id = %s AND uuid = %s", (guild_id, uuid)) connection.commit() giveaway["aktiv"] = True # Aktualisiere den Status in `giveaway` für die Anzeige flash("Giveaway redeemed successfully!", "success") # Zeige den Game Key an, ob er eingelöst ist oder gerade eingelöst wurde return render_template("redeem_giveaway.html", giveaway=giveaway, key=giveaway["game_key"] if giveaway["aktiv"] else None) else: flash("You are not the winner of this giveaway or the giveaway is no longer available.", "danger") cursor.close() connection.close() return redirect(url_for("user_giveaways", guild_id=guild_id)) return redirect(url_for("landing_page")) @app.route("/user_landing_page") def user_landing_page(): """Zeigt die globale Benutzerdaten und die Liste der Server an.""" if g.user_info and "discord_guilds" in session: guilds = session["discord_guilds"] return render_template("user_landing_page.html", user_info=g.user_info, guilds=guilds) return redirect(url_for("landing_page")) @app.route("/global_admin_dashboard") def global_admin_dashboard(): """Globales Admin-Dashboard nur für globale Admins""" if g.is_admin: bot_running = bot_status() # Funktion, die den Status des Bots prüft return render_template("global_admin_dashboard.html", user_info=g.user_info, bot_running=bot_running) return redirect(url_for("user_landing_page")) @app.route("/terms-of-service") def terms_of_service(): """Zeigt die Terms of Service Seite an.""" return render_template("terms_of_service.html") @app.route("/user-contact", methods=["GET", "POST"]) def user_contact(): """Kontaktformular für eingeloggte Benutzer mit automatischer Discord DM""" if not g.user_info: flash("Please log in to access the contact form.", "warning") return redirect(url_for("login")) if request.method == "POST": try: # Form data sammeln subject = request.form.get("subject", "").strip() category = request.form.get("category", "").strip() priority = request.form.get("priority", "").strip() message = request.form.get("message", "").strip() server_context = request.form.get("server_context", "").strip() # Validierung if not all([subject, category, priority, message]): flash("Please fill in all required fields.", "danger") return render_template("user_contact.html", user_info=g.user_info) # Discord User Info user_info = g.user_info # Priority emoji mapping priority_emojis = { "low": "🟢", "medium": "🟡", "high": "🟠", "urgent": "🔴" } # Category emoji mapping category_emojis = { "bug_report": "🐛", "feature_request": "💡", "account_issue": "👤", "moderation": "🛡️", "giveaway": "🎁", "privacy": "🔒", "technical": "⚙️", "other": "❓" } # Discord Message erstellen embed_content = f"""**📨 New Contact Form Submission** **User Information:** • **Name:** {user_info.get('global_name', user_info.get('username', 'Unknown'))} • **Username:** {user_info.get('username', 'Unknown')}#{user_info.get('discriminator', '0000')} • **Discord ID:** `{user_info.get('id', 'Unknown')}` • **Avatar:** {user_info.get('avatar_url', 'No avatar')} **Message Details:** • **Subject:** {subject} • **Category:** {category_emojis.get(category, '❓')} {category.replace('_', ' ').title()} • **Priority:** {priority_emojis.get(priority, '⚪')} {priority.upper()} {f"• **Server Context:** {server_context}" if server_context else ""} **Message:** ``` {message} ``` **Submitted at:** **From:** Multus Bot Web Panel""" # Discord DM via Bot senden import requests import time # Ihre Discord User ID hier eintragen YOUR_DISCORD_ID = "253922739709018114" # Ersetzen Sie dies mit Ihrer Discord ID try: # Versuche die Nachricht über den Bot zu senden connection = get_db_connection() cursor = connection.cursor() # In Datenbank speichern cursor.execute(""" INSERT INTO contact_messages ( user_id, username, subject, category, priority, message, server_context, submitted_at, status ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) """, ( user_info.get('id'), f"{user_info.get('username')}#{user_info.get('discriminator')}", subject, category, priority, message, server_context, int(time.time()), 'pending' )) connection.commit() flash("Your message has been submitted successfully! We'll process it and get back to you within 15 minutes via Discord DM.", "success") cursor.close() connection.close() except Exception as e: print(f"Error saving contact message: {e}") flash("There was an error submitting your message. Please try again or contact us directly on Discord.", "danger") except Exception as e: print(f"Error saving contact message: {e}") flash("There was an error submitting your message. Please try again or contact us directly on Discord.", "danger") except Exception as e: print(f"Error processing contact form: {e}") flash("An unexpected error occurred. Please try again.", "danger") return render_template("user_contact.html", user_info=g.user_info) @app.route("/privacy-policy") def privacy_policy(): """Zeigt die Privacy Policy Seite an.""" return render_template("privacy_policy.html") @app.route("/logout") def logout(): """Meldet den Benutzer ab.""" session.clear() return redirect(url_for("landing_page")) # Bot Management Routes @app.route("/start_bot") def start(): if g.is_admin: start_bot() return redirect(url_for("global_admin_dashboard")) return redirect(url_for("landing_page")) @app.route("/stop_bot") def stop(): if g.is_admin: stop_bot() return redirect(url_for("global_admin_dashboard")) return redirect(url_for("landing_page")) if __name__ == "__main__": # Disable default Flask logging for static files app.logger.disabled = True log = logging.getLogger('werkzeug') log.disabled = True # Start app with minimal logging app.run(host="0.0.0.0", port=5000, debug=True)