Files
Discord-ai-chatbot/app.py
SimolZimol 215d4bd11e modified: app.py
modified:   templates/footer.html
2025-08-24 23:17:17 +02:00

1047 lines
42 KiB
Python

__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/<int:guild_id>")
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/<int:guild_id>")
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/<int:guild_id>/<int:user_id>", 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/<int:guild_id>/<int:user_id>")
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/<int:guild_id>/<int:user_id>", 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/<int:guild_id>/<int:user_id>")
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/<int:guild_id>/<int:user_id>", 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/<int:guild_id>")
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/<int:guild_id>/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/<int:guild_id>")
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/<int:guild_id>/<string:uuid>", 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/<int:guild_id>", 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/<int:guild_id>")
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/<int:guild_id>/<string:uuid>", 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:** <t:{int(time.time())}:F>
**From:** Multus Bot Web Panel"""
# Discord DM via Bot senden
import requests
# 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 have received your request and will get back to you as soon as possible.", "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 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)