modified: app.py

modified:   bot.py
	modified:   templates/server_admin_dashboard.html
	new file:   templates/server_settings.html
This commit is contained in:
SimolZimol
2025-08-19 18:35:32 +02:00
parent 59a9960155
commit 196d2ac570
4 changed files with 1298 additions and 8 deletions

98
app.py
View File

@@ -580,6 +580,104 @@ def edit_giveaway(guild_id, uuid):
return render_template("edit_giveaway.html", giveaway=giveaway, guild_id=guild_id) return render_template("edit_giveaway.html", giveaway=giveaway, guild_id=guild_id)
return redirect(url_for("landing_page")) 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>") @app.route("/user_giveaways/<int:guild_id>")
def user_giveaways(guild_id): def user_giveaways(guild_id):
"""Zeigt dem Benutzer die Giveaways an, die er auf einem bestimmten Server gewonnen hat.""" """Zeigt dem Benutzer die Giveaways an, die er auf einem bestimmten Server gewonnen hat."""

771
bot.py
View File

@@ -112,7 +112,7 @@ loop = asyncio.get_event_loop()
def close_database_connection(connection): def close_database_connection(connection):
connection.close() 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""" """Fügt neue Benutzerdaten in die Datenbank ein mit Connection Pool"""
connection = None connection = None
cursor = None cursor = None
@@ -120,11 +120,11 @@ def insert_user_data(user_id, guild_id, permission, points, ban, askmultus, filt
connection = connect_to_database() connection = connect_to_database()
cursor = connection.cursor() cursor = connection.cursor()
insert_query = """ 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) 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) 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) 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) cursor.execute(insert_query, data)
connection.commit() connection.commit()
@@ -246,7 +246,10 @@ async def create_user_data_with_member(user_id, guild_id, member=None):
"asknotes_history": [], "asknotes_history": [],
"xp": 0, "xp": 0,
"level": 1, "level": 1,
"nickname": nickname "nickname": nickname,
"ai_ban": 0,
"mutes": 0,
"warns": 0
} }
insert_user_data( 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 [], "asknotes_history": json.loads(result[9]) if result[9] else [],
"xp": int(result[10]) if result[10] is not None else 0, "xp": int(result[10]) if result[10] is not None else 0,
"level": int(result[11]) if result[11] is not None else 1, "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: else:
user_data = { user_data = {
@@ -308,7 +314,10 @@ def load_user_data_from_mysql(user_id, guild_id):
"asknotes_history": [], "asknotes_history": [],
"xp": 0, "xp": 0,
"level": 1, "level": 1,
"nickname": "" "nickname": "",
"ai_ban": 0,
"mutes": 0,
"warns": 0
} }
insert_user_data( insert_user_data(
user_data["user_id"], user_data["user_id"],
@@ -404,7 +413,10 @@ def load_user_data_sync(user_id, guild_id):
"asknotes_history": [], "asknotes_history": [],
"xp": 0, "xp": 0,
"level": 1, "level": 1,
"nickname": "" "nickname": "",
"ai_ban": 0,
"mutes": 0,
"warns": 0
} }
insert_user_data( insert_user_data(
user_data["user_id"], user_data["user_id"],
@@ -2146,6 +2158,749 @@ async def version(ctx):
"""Displays the current version of the bot.""" """Displays the current version of the bot."""
await ctx.send(f"The current version of the bot is: {__version__}") 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 <name> - Setzt den Namen für auto-erstellte Mute-Rollen
- auto_create_mute_role <true/false> - Auto-Erstellung von Mute-Rollen
- max_warn_threshold <number> - Anzahl Warnungen vor Auto-Aktion
- auto_mute_on_warns <true/false> - Auto-Mute bei zu vielen Warnungen
- auto_mute_duration <duration> - Dauer für Auto-Mutes (z.B. 1h, 30m)
- log_channel <#channel> - Kanal für Moderations-Logs
- mod_log_enabled <true/false> - 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 <setting> <value> 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-Ordner für Notizen
CACHE_DIR = "cache" CACHE_DIR = "cache"
if not os.path.exists(CACHE_DIR): if not os.path.exists(CACHE_DIR):

View File

@@ -173,6 +173,7 @@
.giveaway-icon { color: #ffd700; } .giveaway-icon { color: #ffd700; }
.user-icon { color: #4299e1; } .user-icon { color: #4299e1; }
.settings-icon { color: #9f7aea; }
/* Responsive Design */ /* Responsive Design */
@media (max-width: 768px) { @media (max-width: 768px) {
@@ -267,6 +268,44 @@
</div> </div>
</div> </div>
<!-- Server Settings Section -->
<div class="admin-card">
<div class="card-body">
<h5 class="card-title">
<i class="fas fa-cogs section-icon" style="color: #9f7aea;"></i>Server Settings
</h5>
<p class="card-text">Configure server-specific settings for moderation, logging, and automation.</p>
<div class="row">
<div class="col-md-6 mb-3">
<div style="background: rgba(159, 122, 234, 0.1); border: 1px solid rgba(159, 122, 234, 0.2); border-radius: 10px; padding: 1.5rem;">
<h6 style="color: #9f7aea; margin-bottom: 0.75rem;">
<i class="fas fa-shield-alt"></i> Moderation Settings
</h6>
<p style="color: #a0aec0; margin-bottom: 1rem; font-size: 0.9rem;">
Configure mute roles, warn thresholds, and automatic moderation actions.
</p>
<a href="{{ url_for('server_settings', guild_id=guild_id) }}" class="admin-btn">
<i class="fas fa-cog"></i> Configure Settings
</a>
</div>
</div>
<div class="col-md-6 mb-3">
<div style="background: rgba(66, 153, 225, 0.1); border: 1px solid rgba(66, 153, 225, 0.2); border-radius: 10px; padding: 1.5rem;">
<h6 style="color: #4299e1; margin-bottom: 0.75rem;">
<i class="fas fa-clipboard-list"></i> Log Configuration
</h6>
<p style="color: #a0aec0; margin-bottom: 1rem; font-size: 0.9rem;">
Set up moderation logs and monitoring channels for server activity.
</p>
<a href="{{ url_for('server_settings', guild_id=guild_id) }}#log-settings" class="admin-btn">
<i class="fas fa-list"></i> Setup Logging
</a>
</div>
</div>
</div>
</div>
</div>
<!-- User Management Section --> <!-- User Management Section -->
<div class="admin-card"> <div class="admin-card">
<div class="card-body"> <div class="card-body">

View File

@@ -0,0 +1,398 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Server-Einstellungen - {{ guild_name }}</title>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
body {
background: linear-gradient(135deg, #0c1426 0%, #1a1f2e 25%, #2d3748 75%, #0c1426 100%);
min-height: 100vh;
color: #e2e8f0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.main-container {
background: rgba(26, 31, 46, 0.8);
backdrop-filter: blur(15px);
border: 1px solid rgba(102, 126, 234, 0.2);
border-radius: 20px;
margin: 2rem auto;
max-width: 1000px;
padding: 2rem;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
}
.header-section {
text-align: center;
margin-bottom: 2rem;
padding-bottom: 1.5rem;
border-bottom: 1px solid rgba(102, 126, 234, 0.2);
}
.header-title {
color: #e2e8f0;
font-size: 2.2rem;
font-weight: 700;
margin-bottom: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
}
.header-icon {
color: #667eea;
font-size: 2.2rem;
}
.server-name {
color: #a0aec0;
font-size: 1.1rem;
}
.settings-section {
background: rgba(45, 55, 72, 0.6);
border: 1px solid rgba(102, 126, 234, 0.2);
border-radius: 15px;
padding: 2rem;
margin-bottom: 2rem;
}
.section-title {
color: #e2e8f0;
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.75rem;
}
.section-icon {
color: #667eea;
font-size: 1.5rem;
}
.form-group label {
color: #e2e8f0;
font-weight: 600;
margin-bottom: 0.5rem;
}
.form-control {
background: rgba(45, 55, 72, 0.8);
border: 1px solid rgba(102, 126, 234, 0.3);
border-radius: 8px;
color: #e2e8f0;
padding: 0.75rem;
}
.form-control:focus {
background: rgba(45, 55, 72, 0.9);
border-color: #667eea;
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
color: #e2e8f0;
}
.form-control::placeholder {
color: #a0aec0;
}
.form-check {
margin: 1rem 0;
padding: 1rem;
background: rgba(45, 55, 72, 0.4);
border-radius: 8px;
border: 1px solid rgba(102, 126, 234, 0.2);
}
.form-check-input {
margin-top: 0.3rem;
}
.form-check-label {
color: #e2e8f0;
font-weight: 500;
margin-left: 0.5rem;
}
.help-text {
color: #a0aec0;
font-size: 0.875rem;
margin-top: 0.25rem;
}
.btn-primary {
background: linear-gradient(135deg, #667eea, #764ba2);
border: none;
color: white;
padding: 0.75rem 2rem;
border-radius: 10px;
font-weight: 600;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);
}
.btn-secondary {
background: rgba(45, 55, 72, 0.6);
border: 1px solid rgba(102, 126, 234, 0.2);
color: #e2e8f0;
padding: 0.75rem 1.5rem;
border-radius: 10px;
font-weight: 600;
transition: all 0.3s ease;
text-decoration: none;
}
.btn-secondary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.1);
color: #e2e8f0;
text-decoration: none;
border-color: rgba(102, 126, 234, 0.4);
}
.alert {
border-radius: 10px;
border: none;
margin-bottom: 1.5rem;
}
.alert-success {
background: rgba(72, 187, 120, 0.1);
color: #48bb78;
border: 1px solid rgba(72, 187, 120, 0.3);
}
.alert-danger {
background: rgba(245, 101, 101, 0.1);
color: #f56565;
border: 1px solid rgba(245, 101, 101, 0.3);
}
.info-card {
background: rgba(56, 178, 172, 0.1);
border: 1px solid rgba(56, 178, 172, 0.2);
border-radius: 10px;
padding: 1rem;
margin-bottom: 1.5rem;
}
.info-card-title {
color: #38b2ac;
font-weight: 600;
margin-bottom: 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.info-card-text {
color: #a0aec0;
margin: 0;
}
@media (max-width: 768px) {
.main-container {
margin: 1rem;
padding: 1.5rem;
}
.header-title {
font-size: 1.8rem;
flex-direction: column;
gap: 0.5rem;
}
.settings-section {
padding: 1.5rem;
}
}
</style>
</head>
<body>
{% include 'navigation.html' %}
<div class="main-container">
<div class="header-section">
<h1 class="header-title">
<i class="fas fa-cogs header-icon"></i>
Server-Einstellungen
</h1>
<p class="server-name">{{ guild_name }}</p>
</div>
<!-- Flash Messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ 'success' if category == 'success' else 'danger' }}">
<i class="fas fa-{{ 'check-circle' if category == 'success' else 'exclamation-triangle' }}"></i>
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST">
<!-- Mute-Einstellungen -->
<div class="settings-section">
<h3 class="section-title">
<i class="fas fa-volume-mute section-icon"></i>
Mute-Einstellungen
</h3>
<div class="info-card">
<div class="info-card-title">
<i class="fas fa-info-circle"></i>
Hinweis zu Rollen-IDs
</div>
<p class="info-card-text">
Um eine Rollen-ID zu finden: Rechtsklick auf die Rolle → "ID kopieren" (Entwicklermodus muss aktiviert sein)
</p>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="mute_role_id">Mute-Rollen-ID</label>
<input type="text" class="form-control" id="mute_role_id" name="mute_role_id"
value="{{ settings.mute_role_id or '' }}" placeholder="z.B. 123456789012345678">
<small class="help-text">Leer lassen für automatische Suche nach Name</small>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="mute_role_name">Mute-Rollen-Name</label>
<input type="text" class="form-control" id="mute_role_name" name="mute_role_name"
value="{{ settings.mute_role_name }}" placeholder="Muted" required>
<small class="help-text">Name für automatisch erstellte Mute-Rollen</small>
</div>
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="auto_create_mute_role"
name="auto_create_mute_role" {% if settings.auto_create_mute_role %}checked{% endif %}>
<label class="form-check-label" for="auto_create_mute_role">
Mute-Rolle automatisch erstellen
</label>
<small class="help-text d-block">Erstellt automatisch eine Mute-Rolle, falls keine gefunden wird</small>
</div>
</div>
<!-- Warn-Einstellungen -->
<div class="settings-section">
<h3 class="section-title">
<i class="fas fa-exclamation-triangle section-icon"></i>
Warn-Einstellungen
</h3>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="max_warn_threshold">Maximale Warnungen</label>
<input type="number" class="form-control" id="max_warn_threshold" name="max_warn_threshold"
value="{{ settings.max_warn_threshold }}" min="1" max="10" required>
<small class="help-text">Anzahl Warnungen vor automatischen Aktionen</small>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="auto_mute_duration">Auto-Mute-Dauer</label>
<input type="text" class="form-control" id="auto_mute_duration" name="auto_mute_duration"
value="{{ settings.auto_mute_duration }}" placeholder="1h" required>
<small class="help-text">Format: 10m, 1h, 2d (Minuten, Stunden, Tage)</small>
</div>
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="auto_mute_on_warns"
name="auto_mute_on_warns" {% if settings.auto_mute_on_warns %}checked{% endif %}>
<label class="form-check-label" for="auto_mute_on_warns">
Automatischer Mute bei zu vielen Warnungen
</label>
<small class="help-text d-block">Mutet Benutzer automatisch bei Erreichen des Warn-Limits</small>
</div>
</div>
<!-- Log-Einstellungen -->
<div class="settings-section">
<h3 class="section-title">
<i class="fas fa-clipboard-list section-icon"></i>
Log-Einstellungen
</h3>
<div class="form-group">
<label for="log_channel_id">Log-Kanal-ID</label>
<input type="text" class="form-control" id="log_channel_id" name="log_channel_id"
value="{{ settings.log_channel_id or '' }}" placeholder="z.B. 123456789012345678">
<small class="help-text">Kanal für Moderations-Logs (leer lassen zum Deaktivieren)</small>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="mod_log_enabled"
name="mod_log_enabled" {% if settings.mod_log_enabled %}checked{% endif %}>
<label class="form-check-label" for="mod_log_enabled">
Moderations-Logs aktiviert
</label>
<small class="help-text d-block">Protokolliert alle Moderationsaktionen</small>
</div>
</div>
<!-- Action Buttons -->
<div class="text-center">
<button type="submit" class="btn btn-primary mr-3">
<i class="fas fa-save"></i> Einstellungen speichern
</button>
<a href="{{ url_for('server_admin_dashboard', guild_id=guild_id) }}" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> Zurück zum Dashboard
</a>
</div>
</form>
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Formular-Validierung
document.querySelector('form').addEventListener('submit', function(e) {
const muteRoleId = document.getElementById('mute_role_id').value;
const logChannelId = document.getElementById('log_channel_id').value;
const muteDuration = document.getElementById('auto_mute_duration').value;
// Validiere Rollen-ID Format (falls angegeben)
if (muteRoleId && !/^\d{17,19}$/.test(muteRoleId)) {
alert('Mute-Rollen-ID muss eine 17-19 stellige Zahl sein');
e.preventDefault();
return;
}
// Validiere Log-Kanal-ID Format (falls angegeben)
if (logChannelId && !/^\d{17,19}$/.test(logChannelId)) {
alert('Log-Kanal-ID muss eine 17-19 stellige Zahl sein');
e.preventDefault();
return;
}
// Validiere Zeitformat
if (muteDuration && !/^\d+[mhd]$/.test(muteDuration)) {
alert('Auto-Mute-Dauer muss im Format "10m", "1h" oder "2d" sein');
e.preventDefault();
return;
}
});
</script>
</body>
</html>