diff --git a/bot.py b/bot.py index c52f775..18a841e 100644 --- a/bot.py +++ b/bot.py @@ -22,6 +22,8 @@ from bs4 import BeautifulSoup from dotenv import load_dotenv import random import time +import hashlib +from urllib.parse import urlparse load_dotenv() @@ -204,10 +206,17 @@ def close_database_connection(connection): if connection and connection.is_connected(): connection.close() -def create_user_data_with_member(user_id, guild_id, member=None): +async def create_user_data_with_member(user_id, guild_id, member=None): """Erstellt neue User-Daten mit korrekten Informationen vom Member-Objekt""" nickname = member.display_name if member else "" - profile_picture = str(member.display_avatar.url) if member and member.display_avatar else None + + # Profilbild herunterladen und lokal speichern + if member and member.display_avatar: + discord_avatar_url = str(member.display_avatar.url) + profile_picture = await download_and_save_profile_image(user_id, discord_avatar_url) + else: + profile_picture = "/static/default_profile.png" + join_date = member.joined_at.date() if member and member.joined_at else None user_data = { @@ -242,7 +251,7 @@ def create_user_data_with_member(user_id, guild_id, member=None): join_date ) - logger.info(f"Created new user data for {nickname} (ID: {user_id}) with join_date: {join_date}") + logger.info(f"Created new user data for {nickname} (ID: {user_id}) with join_date: {join_date} and profile_picture: {profile_picture}") return user_data def load_user_data_from_mysql(user_id, guild_id): @@ -344,7 +353,7 @@ def remove_user_data_from_cache(user_id, guild_id): pending_deletion[(user_id, guild_id)].cancel() del pending_deletion[(user_id, guild_id)] -def load_user_data(user_id, guild_id, member=None): +async def load_user_data(user_id, guild_id, member=None): if (user_id, guild_id) in cached_user_data: return cached_user_data[(user_id, guild_id)] @@ -353,7 +362,50 @@ def load_user_data(user_id, guild_id, member=None): # Wenn keine User-Daten existieren, erstelle neue mit Member-Informationen if not user_data or user_data.get("user_id") is None: - user_data = create_user_data_with_member(user_id, guild_id, member) + user_data = await create_user_data_with_member(user_id, guild_id, member) + + asyncio.ensure_future(cache_user_data(user_id, guild_id, user_data)) + return user_data + +def load_user_data_sync(user_id, guild_id): + """Synchrone Version von load_user_data für bestehende Commands""" + if (user_id, guild_id) in cached_user_data: + return cached_user_data[(user_id, guild_id)] + + # Daten aus der Datenbank laden oder neu anlegen + user_data = load_user_data_from_mysql(user_id, guild_id) + + # Wenn keine User-Daten existieren, erstelle neue mit Default-Werten + if not user_data or user_data.get("user_id") is None: + user_data = { + "user_id": user_id, + "guild_id": guild_id, + "permission": 0, + "points": 0, + "ban": 0, + "askmultus": 0, + "filter_value": 0, + "rank": 0, + "chat_history": [], + "asknotes_history": [], + "xp": 0, + "level": 1, + "nickname": "" + } + insert_user_data( + user_data["user_id"], + user_data["guild_id"], + user_data["permission"], + user_data["points"], + user_data["ban"], + user_data["askmultus"], + user_data["filter_value"], + user_data["chat_history"], + user_data["xp"], + user_data["level"], + user_data["nickname"], + "/static/default_profile.png" + ) asyncio.ensure_future(cache_user_data(user_id, guild_id, user_data)) return user_data @@ -619,7 +671,7 @@ class Giveaway: async def startgiveaway(ctx, platform: str, prize: str, num_winners: int, title: str, subtitle: str, duration: str): """Creates a new giveaway, only available for admins.""" guild_id = ctx.guild.id - user_data = load_user_data(ctx.author.id, guild_id) + user_data = load_user_data_sync(ctx.author.id, guild_id) if user_data["permission"] < 5: await ctx.send("You don't have permission to create a giveaway.") return @@ -879,7 +931,7 @@ def calculate_xp_needed_for_level(level): async def add_xp_to_user(user_id, guild_id, xp_gained, member=None): """Fügt einem Benutzer XP hinzu und überprüft, ob er ein Level aufsteigt. Aktualisiert auch Benutzerdaten.""" # Lade Benutzerdaten (XP, Level, etc.) - mit member-Objekt für neue User - user_data = load_user_data(user_id, guild_id, member) + user_data = await load_user_data(user_id, guild_id, member) # Initialisiere XP, falls es None ist user_data["xp"] = user_data.get("xp", 0) @@ -911,11 +963,22 @@ async def add_xp_to_user(user_id, guild_id, xp_gained, member=None): update_user_data(user_id, guild_id, "nickname", new_nickname) user_data["nickname"] = new_nickname - # Aktualisiere Profilbild - new_profile_picture = str(member.display_avatar.url) if member.display_avatar else None - if user_data.get("profile_picture") != new_profile_picture: - update_user_data(user_id, guild_id, "profile_picture", new_profile_picture) - user_data["profile_picture"] = new_profile_picture + # Aktualisiere Profilbild - mit lokalem Download und Hash-Vergleich + if member.display_avatar: + discord_avatar_url = str(member.display_avatar.url) + # Download und speichere das Profilbild lokal + local_profile_path = await download_and_save_profile_image(user_id, discord_avatar_url) + + # Speichere den lokalen Pfad in der Datenbank statt der Discord URL + if user_data.get("profile_picture") != local_profile_path: + update_user_data(user_id, guild_id, "profile_picture", local_profile_path) + user_data["profile_picture"] = local_profile_path + else: + # Kein Profilbild vorhanden, nutze Default + default_path = "/static/default_profile.png" + if user_data.get("profile_picture") != default_path: + update_user_data(user_id, guild_id, "profile_picture", default_path) + user_data["profile_picture"] = default_path # Aktualisiere Join-Datum - IMMER wenn member.joined_at verfügbar ist if member.joined_at: @@ -1047,7 +1110,7 @@ async def on_message(message): # Optional: Level-Up Benachrichtigung senden if level_up: - user_data = load_user_data(user_id, guild_id) + user_data = await load_user_data(user_id, guild_id, member) new_level = user_data["level"] try: await message.channel.send(f"🎉 {member.mention} has reached **Level {new_level}**! Congratulations! 🎉") @@ -1479,6 +1542,71 @@ CACHE_DIR = "cache" if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR) +# Profilbild-Ordner erstellen +PROFILE_IMAGES_DIR = "static/profile_images" +if not os.path.exists(PROFILE_IMAGES_DIR): + os.makedirs(PROFILE_IMAGES_DIR) + +def get_url_hash(url): + """Erstellt einen Hash aus der URL für Vergleichszwecke""" + if not url: + return None + return hashlib.md5(url.encode('utf-8')).hexdigest() + +def get_local_profile_path(user_id): + """Gibt den lokalen Pfad für das Profilbild eines Users zurück""" + return os.path.join(PROFILE_IMAGES_DIR, f"user_{user_id}.png") + +def get_web_profile_path(user_id): + """Gibt den Web-Pfad für das Profilbild eines Users zurück""" + return f"/static/profile_images/user_{user_id}.png" + +async def download_and_save_profile_image(user_id, discord_url): + """Lädt ein Profilbild herunter und speichert es lokal""" + if not discord_url: + return "/static/default_profile.png" + + try: + local_path = get_local_profile_path(user_id) + web_path = get_web_profile_path(user_id) + + # Überprüfe, ob das Bild bereits existiert und der Hash gleich ist + hash_file = local_path + ".hash" + current_hash = get_url_hash(discord_url) + + if os.path.exists(local_path) and os.path.exists(hash_file): + with open(hash_file, 'r') as f: + stored_hash = f.read().strip() + + if stored_hash == current_hash: + logger.info(f"Profile image for user {user_id} is up to date, skipping download") + return web_path + + # Download das Bild + logger.info(f"Downloading profile image for user {user_id} from {discord_url}") + response = requests.get(discord_url, timeout=10) + + if response.status_code == 200: + # Speichere das Bild + with open(local_path, 'wb') as f: + f.write(response.content) + + # Speichere den Hash + with open(hash_file, 'w') as f: + f.write(current_hash) + + logger.info(f"Successfully downloaded and saved profile image for user {user_id}") + return web_path + else: + logger.warning(f"Failed to download profile image for user {user_id}: HTTP {response.status_code}") + return "/static/default_profile.png" + + except Exception as e: + logger.error(f"Error downloading profile image for user {user_id}: {e}") + return "/static/default_profile.png" + +# Cache-Ordner für Notizen + @client.hybrid_command() async def addnotes(ctx, type: str, *, source: str): """Adds a note that can be consulted later."""