From d0a3954f0a3a576a393d808e27e617cedf60e083 Mon Sep 17 00:00:00 2001 From: SimolZimol <70102430+SimolZimol@users.noreply.github.com> Date: Mon, 15 Jun 2026 21:10:09 +0200 Subject: [PATCH] modified: app.py modified: bot.py modified: templates/server_settings.html --- app.py | 65 ++++++++-- bot.py | 224 +++++++++++++++++++++++++++++++-- templates/server_settings.html | 129 +++++++++++++++++++ 3 files changed, 400 insertions(+), 18 deletions(-) diff --git a/app.py b/app.py index 7b6929a..a9c882f 100644 --- a/app.py +++ b/app.py @@ -46,6 +46,17 @@ app.config["SESSION_TYPE"] = "filesystem" # Oder 'redis' für Redis-basierte Sp Session(app) print(f"Session Type: {app.config['SESSION_TYPE']}") +import json as _json + +@app.template_filter("from_json_ids") +def from_json_ids(value): + """Converts a JSON list of role ID strings to a space-separated string for display.""" + try: + ids = _json.loads(value) + return " ".join(str(i) for i in ids) + except Exception: + return value or "" + DB_HOST = os.getenv("DB_HOST") DB_PORT = os.getenv("DB_PORT") DB_USER = os.getenv("DB_USER") @@ -735,7 +746,23 @@ def server_settings(guild_id): 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")) - + + # Honeypot fields + honeypot_enabled = bool(request.form.get("honeypot_enabled")) + honeypot_channel_id = request.form.get("honeypot_channel_id") + honeypot_preserve_old_accounts = bool(request.form.get("honeypot_preserve_old_accounts")) + honeypot_get_role = request.form.get("honeypot_get_role") or None + honeypot_log_channel_id = request.form.get("honeypot_log_channel_id") + honeypot_ignore_roles_raw = request.form.get("honeypot_ignore_roles", "").strip() + honeypot_acc_age_min = int(request.form.get("honeypot_acc_age_min", 30)) + + # Build ignore roles JSON + import json as _json + honeypot_ignore_roles = None + if honeypot_ignore_roles_raw: + ids = [r.strip() for r in honeypot_ignore_roles_raw.replace(",", " ").split() if r.strip().isdigit()] + honeypot_ignore_roles = _json.dumps(ids) if ids else None + # Validierung if max_warn_threshold < 1 or max_warn_threshold > 10: flash("Warn-Limit muss zwischen 1 und 10 liegen.", "danger") @@ -750,25 +777,38 @@ def server_settings(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 - + honeypot_channel_id = int(honeypot_channel_id) if honeypot_channel_id and honeypot_channel_id.isdigit() else None + honeypot_log_channel_id = int(honeypot_log_channel_id) if honeypot_log_channel_id and honeypot_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 + log_channel_id = %s, mod_log_enabled = %s, + honeypot_enabled = %s, honeypot_channel_id = %s, + honeypot_preserve_old_accounts = %s, honeypot_get_role = %s, + honeypot_log_channel_id = %s, honeypot_ignore_roles = %s, + honeypot_acc_age_min = %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)) + """, (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, + honeypot_enabled, honeypot_channel_id, honeypot_preserve_old_accounts, + honeypot_get_role, honeypot_log_channel_id, honeypot_ignore_roles, + honeypot_acc_age_min, 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) + auto_mute_on_warns, auto_mute_duration, log_channel_id, mod_log_enabled, + honeypot_enabled, honeypot_channel_id, honeypot_preserve_old_accounts, + honeypot_get_role, honeypot_log_channel_id, honeypot_ignore_roles, honeypot_acc_age_min) + VALUES (%s, %s, %s, %s, %s, %s, %s, %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)) + auto_mute_on_warns, auto_mute_duration, log_channel_id, mod_log_enabled, + honeypot_enabled, honeypot_channel_id, honeypot_preserve_old_accounts, + honeypot_get_role, honeypot_log_channel_id, honeypot_ignore_roles, honeypot_acc_age_min)) connection.commit() flash("Server-Einstellungen erfolgreich gespeichert!", "success") @@ -794,7 +834,14 @@ def server_settings(guild_id): "auto_mute_on_warns": False, "auto_mute_duration": "1h", "log_channel_id": None, - "mod_log_enabled": True + "mod_log_enabled": True, + "honeypot_enabled": False, + "honeypot_channel_id": None, + "honeypot_preserve_old_accounts": False, + "honeypot_get_role": None, + "honeypot_log_channel_id": None, + "honeypot_ignore_roles": None, + "honeypot_acc_age_min": 30 } # Hole Servername diff --git a/bot.py b/bot.py index 6e8394d..9de4e17 100644 --- a/bot.py +++ b/bot.py @@ -2873,8 +2873,140 @@ async def on_message(message): user_id = message.author.id guild_id = message.guild.id member = message.author # Das Member-Objekt für Datenaktualisierung - - # XP-Cooldown überprüfen (60 Sekunden) + +# ── Honeypot check ────────────────────────────────────────────────────────── +guild_settings = get_guild_settings(guild_id) +if guild_settings.get("honeypot_enabled") and guild_settings.get("honeypot_channel_id"): + if message.channel.id == int(guild_settings["honeypot_channel_id"]): + # Build ignore-role set + ignore_role_ids = set() + raw_ignore = guild_settings.get("honeypot_ignore_roles") + if raw_ignore: + try: + ignore_role_ids = {int(r) for r in json.loads(raw_ignore)} + except Exception: + pass + + member_role_ids = {role.id for role in member.roles} + if not (ignore_role_ids & member_role_ids): + # Delete the honeypot message + try: + await message.delete() + except Exception: + pass + + action_taken = None + acc_age_min = int(guild_settings.get("honeypot_acc_age_min") or 30) + preserve = guild_settings.get("honeypot_preserve_old_accounts", False) + + # Determine: old-account mute vs ban + if preserve and member.joined_at: + now_aware = datetime.now(member.joined_at.tzinfo) + days_on_server = (now_aware - member.joined_at).days + is_old_account = days_on_server >= acc_age_min + else: + is_old_account = False + + if is_old_account: + # Apply 1-year (365 days) mute via the mute system + honeypot_role = None + hp_role_id = guild_settings.get("honeypot_get_role") + if hp_role_id: + try: + honeypot_role = message.guild.get_role(int(hp_role_id)) + except Exception: + pass + if honeypot_role is None: + honeypot_role = await get_or_create_mute_role(message.guild, guild_settings) + + if honeypot_role: + try: + await member.add_roles( + honeypot_role, + reason="Honeypot: wrote in honeypot channel (account protected as old member)" + ) + # Persist mute record for 365 days + start_time = datetime.now() + end_time = start_time + timedelta(days=365) + process_data = { + "user_id": member.id, + "guild_id": guild_id, + "channel_id": message.channel.id, + "reason": "Honeypot trigger", + "moderator_id": client.user.id, + "mute_role_id": honeypot_role.id + } + process_uuid = create_active_process( + process_type="mute", + guild_id=guild_id, + channel_id=message.channel.id, + user_id=member.id, + target_id=member.id, + end_time=end_time, + data=process_data + ) + await save_mute_to_database( + user_id=member.id, + guild_id=guild_id, + moderator_id=client.user.id, + reason="Honeypot: wrote in honeypot channel (account protected as old member)", + duration="365d", + start_time=start_time, + end_time=end_time, + process_uuid=process_uuid, + channel_id=message.channel.id, + mute_role_id=honeypot_role.id + ) + action_taken = "mute" + except discord.Forbidden: + logger.warning(f"Honeypot: no permission to mute {member.id} in guild {guild_id}") + else: + try: + await message.guild.ban( + member, + reason="Honeypot: wrote in honeypot channel", + delete_message_days=1 + ) + action_taken = "ban" + except discord.Forbidden: + logger.warning(f"Honeypot: no permission to ban {member.id} in guild {guild_id}") + + # Send log embed + hp_log_ch_id = guild_settings.get("honeypot_log_channel_id") + if action_taken and hp_log_ch_id: + try: + log_ch = message.guild.get_channel(int(hp_log_ch_id)) + if log_ch: + color = 0x8b0000 if action_taken == "ban" else 0xff9500 + action_label = "🔨 Banned" if action_taken == "ban" else "🔇 Muted (1 year)" + embed = discord.Embed( + title="🍯 Honeypot triggered", + description=f"{member.mention} wrote in the honeypot channel.", + color=color, + timestamp=datetime.now() + ) + embed.add_field(name="Action", value=action_label, inline=True) + embed.add_field(name="User", value=f"{member} (`{member.id}`)", inline=True) + embed.add_field(name="Channel", value=f"<#{message.channel.id}>", inline=True) + if preserve: + embed.add_field( + name="Account age on server", + value=f"{days_on_server if is_old_account else 'N/A'} days (Minimum: {acc_age_min}d)", + inline=False + ) + if message.content: + embed.add_field( + name="Message content", + value=message.content[:500], + inline=False + ) + embed.set_thumbnail(url=member.display_avatar.url) + await log_ch.send(embed=embed) + except Exception as e: + logger.error(f"Honeypot: error sending log: {e}") + + return # Never process further for honeypot channel messages +# ── End honeypot check ────────────────────────────────────────────────────── cooldown_key = (user_id, guild_id) current_time = time.time() @@ -3747,7 +3879,14 @@ def get_guild_settings(guild_id): "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 + "mod_log_enabled": bool(result[8]) if result[8] is not None else True, + "honeypot_enabled": bool(result[9]) if len(result) > 9 and result[9] is not None else False, + "honeypot_channel_id": result[10] if len(result) > 10 else None, + "honeypot_preserve_old_accounts": bool(result[11]) if len(result) > 11 and result[11] is not None else False, + "honeypot_get_role": result[12] if len(result) > 12 else None, + "honeypot_log_channel_id": result[13] if len(result) > 13 else None, + "honeypot_ignore_roles": result[14] if len(result) > 14 else None, + "honeypot_acc_age_min": result[15] if len(result) > 15 and result[15] is not None else 30 } else: # Erstelle Default-Einstellungen @@ -3760,7 +3899,14 @@ def get_guild_settings(guild_id): "auto_mute_on_warns": False, "auto_mute_duration": "1h", "log_channel_id": None, - "mod_log_enabled": True + "mod_log_enabled": True, + "honeypot_enabled": False, + "honeypot_channel_id": None, + "honeypot_preserve_old_accounts": False, + "honeypot_get_role": None, + "honeypot_log_channel_id": None, + "honeypot_ignore_roles": None, + "honeypot_acc_age_min": 30 } save_guild_settings(guild_id, default_settings) return default_settings @@ -3777,7 +3923,14 @@ def get_guild_settings(guild_id): "auto_mute_on_warns": False, "auto_mute_duration": "1h", "log_channel_id": None, - "mod_log_enabled": True + "mod_log_enabled": True, + "honeypot_enabled": False, + "honeypot_channel_id": None, + "honeypot_preserve_old_accounts": False, + "honeypot_get_role": None, + "honeypot_log_channel_id": None, + "honeypot_ignore_roles": None, + "honeypot_acc_age_min": 30 } finally: if cursor: @@ -3785,6 +3938,41 @@ def get_guild_settings(guild_id): if connection: close_database_connection(connection) +def migrate_guild_settings_table(): + """Adds new honeypot columns to guild_settings if they don't already exist""" + connection = None + cursor = None + try: + connection = connect_to_database() + cursor = connection.cursor() + + alter_queries = [ + "ALTER TABLE guild_settings ADD COLUMN honeypot_enabled BOOLEAN DEFAULT FALSE", + "ALTER TABLE guild_settings ADD COLUMN honeypot_channel_id BIGINT NULL", + "ALTER TABLE guild_settings ADD COLUMN honeypot_preserve_old_accounts BOOLEAN DEFAULT FALSE", + "ALTER TABLE guild_settings ADD COLUMN honeypot_get_role VARCHAR(255) NULL", + "ALTER TABLE guild_settings ADD COLUMN honeypot_log_channel_id BIGINT NULL", + "ALTER TABLE guild_settings ADD COLUMN honeypot_ignore_roles TEXT NULL", + "ALTER TABLE guild_settings ADD COLUMN honeypot_acc_age_min INT DEFAULT 30", + ] + + for query in alter_queries: + try: + cursor.execute(query) + except Exception: + pass # Column already exists + + connection.commit() + logger.info("guild_settings table migration completed") + + except Exception as e: + logger.error(f"Error migrating guild_settings table: {e}") + 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 @@ -3796,8 +3984,11 @@ def save_guild_settings(guild_id, settings): 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) + log_channel_id, mod_log_enabled, + honeypot_enabled, honeypot_channel_id, honeypot_preserve_old_accounts, + honeypot_get_role, honeypot_log_channel_id, honeypot_ignore_roles, + honeypot_acc_age_min) + VALUES (%s, %s, %s, %s, %s, %s, %s, %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), @@ -3806,7 +3997,14 @@ def save_guild_settings(guild_id, settings): 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) + mod_log_enabled = VALUES(mod_log_enabled), + honeypot_enabled = VALUES(honeypot_enabled), + honeypot_channel_id = VALUES(honeypot_channel_id), + honeypot_preserve_old_accounts = VALUES(honeypot_preserve_old_accounts), + honeypot_get_role = VALUES(honeypot_get_role), + honeypot_log_channel_id = VALUES(honeypot_log_channel_id), + honeypot_ignore_roles = VALUES(honeypot_ignore_roles), + honeypot_acc_age_min = VALUES(honeypot_acc_age_min) """ cursor.execute(insert_query, ( @@ -3818,7 +4016,14 @@ def save_guild_settings(guild_id, settings): settings.get("auto_mute_on_warns", False), settings.get("auto_mute_duration", "1h"), settings.get("log_channel_id"), - settings.get("mod_log_enabled", True) + settings.get("mod_log_enabled", True), + settings.get("honeypot_enabled", False), + settings.get("honeypot_channel_id"), + settings.get("honeypot_preserve_old_accounts", False), + settings.get("honeypot_get_role"), + settings.get("honeypot_log_channel_id"), + settings.get("honeypot_ignore_roles"), + settings.get("honeypot_acc_age_min", 30) )) connection.commit() @@ -7823,6 +8028,7 @@ try: create_warnings_table() create_mutes_table() create_contact_messages_table() + migrate_guild_settings_table() logger.info("Database tables initialized successfully") loop.run_until_complete(client.start(TOKEN)) diff --git a/templates/server_settings.html b/templates/server_settings.html index bb7ea4d..e005d31 100644 --- a/templates/server_settings.html +++ b/templates/server_settings.html @@ -350,6 +350,110 @@ + +
+

+ + Honeypot System +

+ +
+
+ + What is a Honeypot? +
+

+ A honeypot channel is a hidden channel that attracts bots and spam accounts. + Any user who writes there will be automatically banned — or if old account protection + is enabled, muted for 1 year. +

+
+ +
+ + + Enables automatic detection in the honeypot channel +
+ +
+
+
+ + + Channel where messages trigger a ban +
+
+
+
+ + + Channel for honeypot logs (empty = disabled) +
+
+
+ +
+ + + + Users who have been on the server longer than the minimum age will not be banned, + but only muted for 1 year (for possibly hacked accounts) + +
+ +
+
+
+ + + + Users must be on the server for at least this many days + to be considered an "old account" + +
+
+
+
+ + + + Role for muted old accounts. Empty = use normal mute role + +
+
+
+ +
+ + + + Space- or comma-separated role IDs that are exempt from honeypot + (e.g., mod roles) + +
+
+