__version__ = "dev-0.9.3" __all__ = ["Discordbot-chatai (Discord)"] __author__ = "SimolZimol" import discord import uuid import os, sys from openai import OpenAI from discord.ext import commands, tasks from discord.ui import Button, View import requests import asyncio import base64 import mysql.connector import json import logging from datetime import datetime, timedelta import concurrent.futures from gtts import gTTS import shutil from bs4 import BeautifulSoup from dotenv import load_dotenv import random import time load_dotenv() DB_HOST = os.getenv("DB_HOST") DB_PORT = os.getenv("DB_PORT") DB_USER = os.getenv("DB_USER") DB_PASSWORD = os.getenv("DB_PASSWORD") DB_DATABASE = os.getenv("DB_DATABASE") OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL") OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") OWNER_ID = int(os.getenv("OWNER_ID")) GIVEAWAY_WEBSITE_URL = os.getenv("GIVEAWAY_WEBSITE_URL") features = { "askmultus": bool(int(os.getenv("ASKMULTUS_ENABLED", 0))), "vision": bool(int(os.getenv("VISION_ENABLED", 0))) } giveaways = {} LOGS_DIR = "logs" if not os.path.exists(LOGS_DIR): os.makedirs(LOGS_DIR) logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S') logger = logging.getLogger("discord_bot") logger.setLevel(logging.INFO) log_file = os.path.join(LOGS_DIR, f"{datetime.now().strftime('%Y-%m-%d')}.log") if os.path.exists(log_file): try: timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S') renamed_log_file = os.path.join(LOGS_DIR, f"{datetime.now().strftime('%Y-%m-%d')}_{timestamp}.log") os.rename(log_file, renamed_log_file) except PermissionError: print(f"Unable to rename log file {log_file}. It may be in use by another process.") file_handler = logging.FileHandler(log_file) file_handler.setLevel(logging.INFO) file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S') file_handler.setFormatter(file_formatter) logger.addHandler(file_handler) #to do: # permissions system, Filter, mysql for user data, fix vision, embeds, info cmd (Server Info, user info), logs, points redo, ticket system, levels, mc ranks integration, add image gen, reaction system, dm system, better ready, resource management, bot action (aka playing) # mysql = userid / permission / points / ban / askmultus-int / Filter-int / rank / chat-history / guild_id # 10 filter = acc under review = nicht ok = ban add timestamp = 2 bans = unendlicher ban #perms || 10 = Owner || 8 = Admin || 5 = Mod openai_instance = OpenAI(base_url=OPENAI_BASE_URL, api_key=OPENAI_API_KEY) TOKEN = os.getenv("DISCORD_TOKEN") intents = discord.Intents.default() intents.message_content = True intents.members = True intents.reactions = True python = sys.executable client = commands.Bot(command_prefix='-', intents=intents, owner_id = OWNER_ID) askmultus_queue = asyncio.Queue() loop = asyncio.get_event_loop() # Verbindung zur MySQL-Datenbank herstellen db_connection = mysql.connector.connect( host=DB_HOST, port=DB_PORT, user=DB_USER, password=DB_PASSWORD, database=DB_DATABASE ) # Cursor erstellen db_cursor = db_connection.cursor() def close_database_connection(connection): 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): 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) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """ 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) def execute_insert(): cursor = get_database_cursor() cursor.execute(insert_query, data) db_connection.commit() try: retry_query(execute_insert) print("User data inserted successfully.") except Exception as e: print(f"Error inserting user data after retries: {e}") def update_user_data(user_id, guild_id, field, value): global db_connection, db_cursor # global-Deklaration muss vor dem Zugriff erfolgen try: update_query = f"UPDATE user_data SET {field} = %s WHERE user_id = %s AND guild_id = %s" # Überprüfen, ob das Feld 'chat_history' aktualisiert wird if field == 'chat_history': serialized_chat_history = json.dumps(value) db_cursor.execute(update_query, (serialized_chat_history, user_id, guild_id)) else: db_cursor.execute(update_query, (value, user_id, guild_id)) db_connection.commit() except mysql.connector.Error as err: logger.error(f"Database error: {err}") if db_connection.is_connected(): db_cursor.close() db_connection.close() # Verbindung neu aufbauen db_connection = connect_to_database() db_cursor = db_connection.cursor() # Wiederhole die Abfrage nach dem erneuten Verbinden update_user_data(user_id, guild_id, field, value) def connect_to_database(): connection = mysql.connector.connect( host=DB_HOST, port=DB_PORT, user=DB_USER, password=DB_PASSWORD, database=DB_DATABASE ) connection.autocommit = True # Automatisches Commit für stabilere Abfragen return connection def retry_query(func, *args, retries=3, delay=5): for _ in range(retries): try: return func(*args) except mysql.connector.Error as err: print(f"Retrying due to error: {err}") time.sleep(delay) raise RuntimeError("Max retries exceeded") def get_database_cursor(): if not db_connection.is_connected(): db_connection.reconnect(attempts=3, delay=5) return db_connection.cursor() pool = mysql.connector.pooling.MySQLConnectionPool( pool_name="mypool", pool_size=30, # Erhöht von 10 auf 30 pool_reset_session=True, autocommit=True, host=DB_HOST, port=DB_PORT, user=DB_USER, password=DB_PASSWORD, database=DB_DATABASE ) def connect_to_database(): """Holt eine Verbindung aus dem Pool""" try: connection = pool.get_connection() return connection except mysql.connector.PoolError as e: logger.error(f"Pool error: {e}") raise e def close_database_connection(connection): """Gibt eine Verbindung an den Pool zurück""" if connection and connection.is_connected(): connection.close() def load_user_data_from_mysql(user_id, guild_id): connection = None cursor = None try: connection = connect_to_database() cursor = connection.cursor() select_query = "SELECT * FROM user_data WHERE user_id = %s AND guild_id = %s" cursor.execute(select_query, (user_id, guild_id)) result = cursor.fetchone() if result: user_data = { "user_id": result[0], "guild_id": result[1], "permission": result[2], "points": int(result[3]), "ban": result[4], "askmultus": result[5], "filter_value": result[6], "rank": result[7], "chat_history": json.loads(result[8]) if result[8] else [], "asknotes_history": json.loads(result[9]) if result[9] else [], "xp": int(result[10]) if result[10] is not None else 0, "level": int(result[11]) if result[11] is not None else 1, "nickname": result[12] } else: 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"] ) return user_data except Exception as e: logger.error(f"Error loading user data from MySQL: {e}") # Return default user data in case of error return { "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": "" } finally: if cursor: cursor.close() if connection: close_database_connection(connection) cached_user_data = {} pending_deletion = {} async def cache_user_data(user_id, guild_id, data): cached_user_data[(user_id, guild_id)] = data # Setze die Daten nach 30 Sekunden auf die Löschliste if (user_id, guild_id) not in pending_deletion: pending_deletion[(user_id, guild_id)] = asyncio.get_event_loop().call_later(30, lambda: remove_user_data_from_cache(user_id, guild_id)) def remove_user_data_from_cache(user_id, guild_id): # Entferne den Benutzer aus dem Cache und der Löschliste if (user_id, guild_id) in cached_user_data: del cached_user_data[(user_id, guild_id)] if (user_id, guild_id) in pending_deletion: pending_deletion[(user_id, guild_id)].cancel() del pending_deletion[(user_id, guild_id)] def load_user_data(user_id, guild_id): 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) asyncio.ensure_future(cache_user_data(user_id, guild_id, user_data)) return user_data def get_global_permission(user_id): connection = None cursor = None try: connection = connect_to_database() cursor = connection.cursor() select_query = "SELECT global_permission FROM bot_data WHERE user_id = %s" cursor.execute(select_query, (user_id,)) result = cursor.fetchone() return result[0] if result else None except Exception as e: logger.error(f"Error getting global permission: {e}") return None finally: if cursor: cursor.close() if connection: close_database_connection(connection) def save_global_permission(user_id, permission_level): connection = None cursor = None try: connection = connect_to_database() cursor = connection.cursor() insert_query = """ INSERT INTO bot_data (user_id, global_permission) VALUES (%s, %s) ON DUPLICATE KEY UPDATE global_permission = %s """ cursor.execute(insert_query, (user_id, permission_level, permission_level)) connection.commit() logger.info(f"Successfully saved global permission for user {user_id}: {permission_level}") except Exception as e: logger.error(f"Error saving global permission: {e}") if connection: connection.rollback() raise e finally: if cursor: cursor.close() if connection: close_database_connection(connection) #----------------------------------------------------------------------------------------------------------- async def update_all_users(batch_size=20, pause_duration=1): connection = connect_to_database() cursor = connection.cursor() cursor.execute("SELECT DISTINCT guild_id FROM guilds") guilds = cursor.fetchall() cursor.close() close_database_connection(connection) for guild_id_tuple in guilds: guild_id = guild_id_tuple[0] guild = client.get_guild(int(guild_id)) if guild: members = guild.members total_members = len(members) for i in range(0, total_members, batch_size): batch = members[i:i + batch_size] for member in batch: user_id = member.id user_data = load_user_data(user_id, guild_id) # Daten aktualisieren nickname = member.display_name profile_picture = str(member.display_avatar.url) if member.display_avatar else None join_date = member.joined_at.date() if member.joined_at else None leave_date = None if member in guild.members else datetime.now().date() update_user_data(user_id, guild_id, "nickname", nickname) update_user_data(user_id, guild_id, "profile_picture", profile_picture) update_user_data(user_id, guild_id, "join_date", join_date) update_user_data(user_id, guild_id, "leave_date", leave_date) # Pause nach jeder Charge await asyncio.sleep(pause_duration) def save_giveaway_to_db(guild_id, platform, name, prize_uuid, game_key): connection = None cursor = None try: connection = connect_to_database() cursor = connection.cursor() insert_query = """ INSERT INTO giveaway_data (guild_id, uuid, platform, name, game_key) VALUES (%s, %s, %s, %s, %s) """ data = (guild_id, str(prize_uuid), platform, name, game_key) cursor.execute(insert_query, data) connection.commit() logger.info(f"Successfully saved giveaway to database: UUID={prize_uuid}") except Exception as e: logger.error(f"Error saving giveaway to database: {e}") if connection: connection.rollback() raise e finally: if cursor: cursor.close() if connection: close_database_connection(connection) def create_winner_slots_in_db(guild_id, platform, name, num_winners, game_key="PREDEFINED_GAME_KEY"): """Erstellt vorab Datenbank-Einträge für alle möglichen Gewinner mit eigenen UUIDs""" winner_uuids = [] for i in range(num_winners): connection = None cursor = None try: connection = connect_to_database() cursor = connection.cursor() winner_uuid = uuid.uuid4() insert_query = """ INSERT INTO giveaway_data (guild_id, uuid, platform, name, game_key, winner_dc_id) VALUES (%s, %s, %s, %s, %s, %s) """ # winner_dc_id ist zunächst NULL, wird später beim Gewinn gesetzt data = (guild_id, str(winner_uuid), platform, name, game_key, None) cursor.execute(insert_query, data) connection.commit() winner_uuids.append(winner_uuid) logger.info(f"Created winner slot {i+1}/{num_winners} with UUID: {winner_uuid}") except Exception as e: logger.error(f"Error creating winner slot {i+1}: {e}") if connection: connection.rollback() raise e finally: if cursor: cursor.close() if connection: close_database_connection(connection) return winner_uuids def save_winner_to_db(guild_id, platform, name, winner_dc_id, game_key="PREDEFINED_GAME_KEY"): """Erstellt einen eigenen Datenbankeintrag für jeden Gewinner mit eigener UUID""" connection = None cursor = None try: connection = connect_to_database() cursor = connection.cursor() winner_uuid = uuid.uuid4() insert_query = """ INSERT INTO giveaway_data (guild_id, uuid, platform, name, game_key, winner_dc_id) VALUES (%s, %s, %s, %s, %s, %s) """ data = (guild_id, str(winner_uuid), platform, name, game_key, winner_dc_id) cursor.execute(insert_query, data) connection.commit() logger.info(f"Successfully saved winner to database: UUID={winner_uuid}, winner_dc_id={winner_dc_id}") return winner_uuid except Exception as e: logger.error(f"Error saving winner to database: {e}") if connection: connection.rollback() raise e finally: if cursor: cursor.close() if connection: close_database_connection(connection) def assign_winner_to_uuid(winner_uuid, winner_dc_id): """Verknüpft eine bereits existierende UUID mit einem tatsächlichen Gewinner""" connection = None cursor = None try: connection = connect_to_database() cursor = connection.cursor() update_query = """ UPDATE giveaway_data SET winner_dc_id = %s WHERE uuid = %s """ data = (winner_dc_id, str(winner_uuid)) cursor.execute(update_query, data) connection.commit() logger.info(f"Successfully assigned winner {winner_dc_id} to UUID: {winner_uuid}") return True except Exception as e: logger.error(f"Error assigning winner to UUID: {e}") if connection: connection.rollback() raise e finally: if cursor: cursor.close() if connection: close_database_connection(connection) def update_winner_in_db(guild_id, prize_uuid, winner_dc_id): connection = None cursor = None try: connection = connect_to_database() cursor = connection.cursor() update_query = """ UPDATE giveaway_data SET winner_dc_id = %s WHERE uuid = %s AND guild_id = %s """ data = (winner_dc_id, str(prize_uuid), guild_id) cursor.execute(update_query, data) connection.commit() logger.info(f"Successfully updated winner in database: UUID={prize_uuid}, winner_dc_id={winner_dc_id}") except Exception as e: logger.error(f"Error updating winner in database: {e}") if connection: connection.rollback() raise e finally: if cursor: cursor.close() if connection: close_database_connection(connection) class Giveaway: def __init__(self, ctx, platform, prize, num_winners, title, subtitle, duration, end_time): self.ctx = ctx self.guild_id = ctx.guild.id # Speichern der guild_id self.platform = platform self.prize = prize self.num_winners = num_winners self.title = title self.subtitle = subtitle self.duration = duration self.end_time = end_time self.participants = [] self.prize_uuid = uuid.uuid4() # Generiert eine eindeutige UUID für das Gewinnspiel self.game_key = f"PREDEFINED_GAME_KEY" # Platzhalter für den tatsächlichen Game-Key # Erstelle nur die Gewinner-Einträge, KEINEN Haupt-Eintrag self.winner_uuids = create_winner_slots_in_db(self.guild_id, self.platform, self.title, self.num_winners, self.game_key) logger.info(f"Created giveaway '{self.title}' with {len(self.winner_uuids)} winner slots: {[str(uuid) for uuid in self.winner_uuids]}") def add_participant(self, user): if user not in self.participants: self.participants.append(user) return True return False def is_finished(self): return datetime.now() >= self.end_time def pick_winners(self): available_participants = len(self.participants) winners_to_pick = min(self.num_winners, available_participants) logger.info(f"Picking winners: requested={self.num_winners}, available_participants={available_participants}, winners_to_pick={winners_to_pick}") if winners_to_pick == 0: return [] winners = random.sample(self.participants, winners_to_pick) logger.info(f"Selected {len(winners)} winners: {[winner.name for winner in winners]}") return winners @client.hybrid_command() 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) if user_data["permission"] < 5: await ctx.send("You don't have permission to create a giveaway.") return if duration.endswith("m"): minutes = int(duration[:-1]) end_time = datetime.now() + timedelta(minutes=minutes) elif duration.endswith("d"): days = int(duration[:-1]) end_time = datetime.now() + timedelta(days=days) else: await ctx.send("Invalid duration. Please use 'm' for minutes or 'd' for days.") return # Create new giveaway giveaway = Giveaway(ctx, platform, prize, num_winners, title, subtitle, duration, end_time) giveaway_id = len(giveaways) + 1 giveaways[giveaway_id] = giveaway button = Button(label="Participate", style=discord.ButtonStyle.green, custom_id=f"giveaway_{giveaway_id}") view = View() view.add_item(button) unix_end_time = int(time.mktime(end_time.timetuple())) embed = discord.Embed( title=title, description=f"{subtitle}\n\nPrize: {prize}\nPlatform: {platform}\nNumber of winners: {num_winners}\nEnds ", color=0x00ff00 ) embed.set_footer(text=f"Giveaway ends at {end_time.strftime('%Y-%m-%d %H:%M:%S')}") await ctx.send(embed=embed, view=view) check_giveaway.start(giveaway_id) # ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- live_chats = {} live_chat_queue = asyncio.Queue() def read_file(filename): try: with open(filename, "r", encoding="utf-8") as file: return file.read() except FileNotFoundError: return "Du bist ein hilfreicher Assistent, der Fragen beantwortet." def load_chat_history(channel_id): """Lädt die Chat-Historie für einen bestimmten Kanal.""" history_file = os.path.join(CACHE_DIR, f"chat_{channel_id}.json") if os.path.exists(history_file): with open(history_file, "r", encoding="utf-8") as file: return json.load(file) return [] def save_chat_history(channel_id, messages): """Speichert die Chat-Historie für einen bestimmten Kanal.""" history_file = os.path.join(CACHE_DIR, f"chat_{channel_id}.json") with open(history_file, "w", encoding="utf-8") as file: json.dump(messages, file, indent=4) @client.hybrid_command() async def startlivechat(ctx): """Startet den Live-Chat im aktuellen Kanal.""" channel_id = ctx.channel.id if channel_id in live_chats and live_chats[channel_id]["active"]: await ctx.send("Live-Chat ist bereits aktiv.") return # Lade oder initialisiere die Chat-Historie history = load_chat_history(channel_id) live_chats[channel_id] = {"messages": history, "active": True} await ctx.send("Live-Chat gestartet. Nachrichten werden verarbeitet.") @client.hybrid_command() async def stoplivechat(ctx): """Beendet den Live-Chat im aktuellen Kanal.""" channel_id = ctx.channel.id if channel_id in live_chats: live_chats[channel_id]["active"] = False await ctx.send("Live-Chat wurde beendet.") else: await ctx.send("Kein aktiver Live-Chat in diesem Kanal.") @client.event async def on_message(message): if message.author.bot: # Bots ignorieren return channel_id = message.channel.id if channel_id in live_chats and live_chats[channel_id]["active"]: # Alle benötigten Daten aus dem message-Objekt extrahieren msg_id = str(message.id) user_id = str(message.author.id) nickname = message.author.display_name content = message.content # Füge die Nachricht zur Warteschlange hinzu await live_chat_queue.put((message, msg_id, user_id, nickname, content)) await client.process_commands(message) async def process_live_chat_queue(): while True: loop = asyncio.get_running_loop() try: if not askmultus_queue.empty(): message, msg_id, user_id, nickname, content = await live_chat_queue.get() live_introduction = read_file("live_introduction.txt") live_background_data = read_file("live_background_data.txt") message_data = live_introduction + " background data:" + live_background_data chat_history = load_chat_history(message.channel.id) timestamp = int(time.time()) # Unix-Timestamp content = timestamp + "/" + msg_id + "/" + user_id + "/" + nickname + ":" + content chat_history.append({"role": "user", "content": f"{content}"}) messages = [ {"role": "system", "content": message_data}, *chat_history ] ai_answer = await loop.run_in_executor(executor, lambda: openai_instance.chat.completions.create( model="model", messages=messages, temperature=0.8, timeout=15, # Limit waiting time for response )) ai_message = ai_answer.choices[0].message.content chat_history.append({"role": "assistant", "content": ai_message}) save_chat_history(message.channel.id, chat_history) channel = message.channel if ai_message.strip() != "::null::": if channel: await channel.send(f"**AI:** {ai_message}") live_chat_queue.task_done() except asyncio.CancelledError: break except Exception as e: logger.error(f"Error processing live chat queue: {e}") await asyncio.sleep(5) # ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @client.hybrid_command() async def setlocalpermission(ctx, permission_level: int): """Allows an admin or higher to set their own local permission level.""" user_id = ctx.author.id guild_id = ctx.guild.id # Globale Berechtigungen abrufen global_perms = get_global_permission(user_id) # Wenn der Benutzer mindestens Admin ist, kann er die Berechtigungen setzen if global_perms is not None and global_perms >= 8: # Admin-Level ist 8 oder höher # Lokale Berechtigungen setzen update_user_data(user_id, guild_id, "permission", permission_level) await ctx.send(f"Your local permission level has been set to {permission_level}.") else: await ctx.send("You do not have permission to set local permissions.") @tasks.loop(minutes=1) async def check_giveaway(giveaway_id): giveaway = giveaways.get(giveaway_id) if giveaway and giveaway.is_finished(): check_giveaway.stop() winners = giveaway.pick_winners() if winners: winner_mentions = ", ".join([winner.mention for winner in winners]) await giveaway.ctx.send(f"🎉 Congratulations to the winners of the giveaway '{giveaway.title}'! The winners are: {winner_mentions}") # Verwende die bereits erstellten UUIDs und weise sie den tatsächlichen Gewinnern zu logger.info(f"Processing {len(winners)} winners for giveaway '{giveaway.title}'") for i, winner in enumerate(winners): try: user_data = load_user_data(winner.id, giveaway.guild_id) # Verwende die bereits beim Erstellen generierte UUID winner_uuid = giveaway.winner_uuids[i] # i-te UUID für i-ten Gewinner logger.info(f"Assigning winner {i+1}/{len(winners)}: {winner.name} (ID: {winner.id}) to existing UUID: {winner_uuid}") # Verknüpfe die bereits vorhandene UUID mit dem tatsächlichen Gewinner assign_winner_to_uuid(winner_uuid, winner.id) await winner.send(f"🎁 Congratulations! You won the giveaway '{giveaway.title}'!\n" f"Please claim your prize using the following link: {GIVEAWAY_WEBSITE_URL}{giveaway.guild_id}/{winner_uuid}") logger.info(f"Sent winner notification with UUID: {winner_uuid}") except Exception as e: logger.error(f"Error processing winner {winner.name}: {e}") else: await giveaway.ctx.send(f"The giveaway '{giveaway.title}' has ended, but there were no participants.") del giveaways[giveaway_id] @client.event async def on_interaction(interaction): """Processes participation in a giveaway.""" # Nur Button-Interaktionen für Giveaways verarbeiten if interaction.type == discord.InteractionType.component and "custom_id" in interaction.data: if interaction.data["custom_id"].startswith("giveaway_"): giveaway_id = int(interaction.data["custom_id"].split("_")[1]) giveaway = giveaways.get(giveaway_id) if giveaway: if giveaway.is_finished(): await interaction.response.send_message("This giveaway has already ended.", ephemeral=True) else: added = giveaway.add_participant(interaction.user) if added: await interaction.response.send_message("You have successfully entered the giveaway!", ephemeral=True) else: await interaction.response.send_message("You're already participating in this giveaway.", ephemeral=True) # Slash Commands und andere Interaktionen werden automatisch vom Framework verarbeitet def read_introduction(): try: with open("introduction.txt", "r", encoding="utf-8") as file: introduction = file.read() return introduction except FileNotFoundError: return "" def read_askintroduction(): try: with open("asknotesintro.txt", "r", encoding="utf-8") as file: introduction = file.read() return introduction except FileNotFoundError: return "" def read_background_data(filename): try: with open(filename, "r", encoding="utf-8") as file: data = file.read() return data except FileNotFoundError: return "" def get_current_datetime(): return datetime.now().strftime("%Y-%m-%d %H:%M:%S") def calculate_xp_needed_for_level(level): """Berechnet die benötigten XP für das nächste Level.""" xp_need = 5 * (int(level) ** 2) + 50 * int(level) + 100 return int(xp_need) async def add_xp_to_user(user_id, guild_id, xp_gained): """Fügt einem Benutzer XP hinzu und überprüft, ob er ein Level aufsteigt.""" # Lade Benutzerdaten (XP, Level, etc.) user_data = load_user_data(user_id, guild_id) # Initialisiere XP, falls es None ist user_data["xp"] = user_data.get("xp", 0) # Füge die gewonnenen XP hinzu user_data["xp"] += xp_gained # Berechne die benötigten XP für das aktuelle Level level = user_data["level"] xp_needed = calculate_xp_needed_for_level(level) # Überprüfe, ob der Benutzer aufgestiegen ist while user_data["xp"] >= xp_needed: # Reduziere die überschüssigen XP und erhöhe das Level user_data["xp"] -= xp_needed user_data["level"] += 1 # Berechne die neuen XP-Anforderungen für das nächste Level xp_needed = calculate_xp_needed_for_level(user_data["level"]) # Speichere die aktualisierten Benutzerdaten in der Datenbank update_user_data(user_id, guild_id, "xp", user_data["xp"]) update_user_data(user_id, guild_id, "level", user_data["level"]) @client.hybrid_command() async def level(ctx): """Zeigt den aktuellen Level und XP des Benutzers an.""" user_id = ctx.author.id guild_id = ctx.guild.id # Lade die Benutzerdaten (XP und Level) aus der Datenbank user_data = load_user_data(user_id, guild_id) # Berechne die für das nächste Level benötigten XP current_level = user_data["level"] current_xp = user_data["xp"] xp_needed = calculate_xp_needed_for_level(current_level) # Erstelle eine Antwort mit den aktuellen Level-Informationen embed = discord.Embed( title=f"Level Information for {ctx.author.name}", description=f"Level: {current_level}\nXP: {current_xp}/{xp_needed}", color=0x00ff00 ) await ctx.send(embed=embed) @client.hybrid_command() async def leaderboard(ctx): """Zeigt die besten Benutzer im XP-Leaderboard an.""" guild_id = ctx.guild.id connection = connect_to_database() cursor = connection.cursor() # Abfrage, um die Benutzer basierend auf der XP zu sortieren select_query = """ SELECT user_id, xp, level FROM user_data WHERE guild_id = %s ORDER BY level DESC, xp DESC LIMIT 10 """ cursor.execute(select_query, (guild_id,)) result = cursor.fetchall() # Liste, um die Benutzer und ihre XP zu speichern leaderboard_entries = [] # Benutzernamen über die user_id abrufen und in die Liste einfügen for row in result: user_id = row[0] xp = row[1] level = row[2] # Benutzername mit der user_id abrufen user = await client.fetch_user(user_id) username = user.name leaderboard_entries.append(f"{username}: Level {level}, XP {xp}") cursor.close() close_database_connection(connection) # Erstelle die Nachricht für das Leaderboard leaderboard_message = "\n".join(leaderboard_entries) embed = discord.Embed( title="Leaderboard", description=leaderboard_message, color=0x00ff00 ) await ctx.send(embed=embed) xp_cooldowns = {} @client.event async def on_message(message): """Event-Handler, der XP vergibt, wenn Nachrichten gesendet werden.""" if message.author.bot: return # Ignoriere Nachrichten von Bots user_id = message.author.id guild_id = message.guild.id xp_gained = random.randint(2, 25) # Zufällige XP zwischen 15 und 25 vergeben await add_xp_to_user(user_id, guild_id, xp_gained) # Weiterleiten der Nachricht an andere Event-Handler await client.process_commands(message) # Verwenden Sie die Funktion, um Hintergrunddaten zu laden background_data = read_background_data("background_data.txt") @client.event async def on_ready(): client.loop.create_task(process_ai_queue()) client.loop.create_task(process_live_chat_queue()) # Starte die Queue-Verarbeitung logger.info("Bot is ready!") logger.info(f"Logged in as: {client.user.name}") logger.info(f"Client ID: {client.user.id}") logger.info('------') # Version check version_url = "https://simolzimol.eu/version_chat.txt" current_version = __version__ try: response = requests.get(version_url) if response.status_code == 200: latest_version = response.text.strip() if latest_version != current_version: logger.info(f"New version available: {latest_version}") else: logger.info("Bot is up to date.") else: logger.info("Unable to retrieve version information.") except requests.exceptions.RequestException: logger.info("Failed to connect to version server.") @client.event async def on_command_error(ctx, error): logger.error(f"An error occurred while executing the command: {error}") @client.event async def on_command(ctx): command = ctx.command logger.info(f"Command '{command.name}' was executed by '{ctx.author.name}' in '{ctx.guild.name}'.") @client.hybrid_command() async def points(ctx): """Shows how many points you have.""" user_id = ctx.author.id guild_id = ctx.guild.id # Lade Benutzerdaten aus der MySQL-Datenbank user_data = load_user_data(user_id, guild_id) points = user_data["points"] embed = discord.Embed( title="Points", description=f"You have {points} points.", color=0x3498db ) await ctx.send(embed=embed) @client.hybrid_command() async def permissionlevel(ctx): """Displays the authorisation level and rank of the user.""" user_id = ctx.author.id guild_id = ctx.guild.id # Load user data from the MySQL database user_data = load_user_data(user_id, guild_id) permission_level = user_data["permission"] rank = "" if permission_level == 10: rank = "Owner" elif permission_level == 8: rank = "Admin" elif permission_level == 5: rank = "Mod" else: rank = "User" embed = discord.Embed( title="Permission Level", description=f"Your permission level is: {permission_level}. Your rank is: {rank}.", color=0x3498db ) await ctx.send(embed=embed) @client.hybrid_command() async def addpoints(ctx, user: discord.User, amount: int): """Adds a certain number of points to a user.""" user_perms = load_user_data(ctx.author.id, ctx.guild.id) if 5 <= user_perms["permission"]: user_id = user.id guild_id = ctx.guild.id # Lade Benutzerdaten aus der MySQL-Datenbank user_data = load_user_data(user_id, guild_id) # Füge die Punkte hinzu user_data["points"] += amount # Speichere die aktualisierten Benutzerdaten in der MySQL-Datenbank update_user_data(user_data["user_id"], guild_id, "points", user_data["points"]) embed = discord.Embed( title="Points Added", description=f"Added {amount} points to {user.display_name}.", color=0x2ecc71 ) await ctx.send(embed=embed) else: await ctx.send("You don't have permissions.") @client.hybrid_command() async def resetpoints(ctx, user: discord.User): """Resets a user's points to 0.""" user_perms = load_user_data(ctx.author.id, ctx.guild.id) if 5 <= user_perms["permission"]: user_id = user.id guild_id = ctx.guild.id # Lade Benutzerdaten aus der MySQL-Datenbank user_data = load_user_data(user_id, guild_id) # Setze die Punkte auf 0 zurück user_data["points"] = 0 # Speichere die aktualisierten Benutzerdaten in der MySQL-Datenbank update_user_data(user_data["user_id"], guild_id, "points", user_data["points"]) embed = discord.Embed( title="Points Reset", description=f"Reset points for {user.display_name}.", color=0x2ecc71 ) await ctx.send(embed=embed) else: await ctx.send("You don't have permissions.") @client.hybrid_command() async def shutdown_(ctx): user_perms = load_user_data(ctx.author.id, ctx.guild.id) if 8 <= user_perms["permission"]: await ctx.send("Shutting down the bot...") await client.logout() exit() else: await ctx.send("You don't have the necessary permissions to use this command.") @client.hybrid_command() async def owner_command(ctx): try: user_perms = load_user_data(ctx.author.id, ctx.guild.id) if 10 <= user_perms["permission"]: await client.tree.sync() await ctx.send("reloaded !") else: await ctx.send("You don't have the necessary permissions to use this command.") except Exception as e: await ctx.send(f"An error occurred while executing the command: {e}") @client.hybrid_command() async def askmultus(ctx, *, prompt: str): """Submits a prompt to Multus for assistance or information. (5 Points)""" if not features["askmultus"]: await ctx.send("Sorry, the askmultus feature is currently disabled.") return user_id = ctx.author.id guild_id = ctx.guild.id # Lade Benutzerdaten aus der MySQL-Datenbank user_data = load_user_data(user_id, guild_id) if user_data["points"] >= 5: user_data["points"] -= 5 # Speichere die aktualisierten Benutzerdaten in der MySQL-Datenbank update_user_data(user_data["user_id"], guild_id, "points", user_data["points"]) # Define the full data and user history field for askmultus introduction = read_introduction() background_data = read_background_data("background_data.txt") current_datetime = get_current_datetime() full_data = introduction + f"\nCurrent Date and Time: {current_datetime}" + background_data user_history_field = "chat_history" # Füge die Anfrage zur Warteschlange hinzu await askmultus_queue.put((ctx, user_data["user_id"], ctx.author.name, prompt, ctx.channel.id, full_data, user_history_field, "local-model")) # Erstelle ein Embed für die Bestätigungsnachricht embed = discord.Embed(title="Multus Assistance Request", color=0x00ff00) embed.add_field(name="Request Received", value=f"Your request has been added to the queue. Position in queue: {askmultus_queue.qsize()}") await ctx.send(embed=embed) else: await ctx.send("You don't have enough points to use this command.") executor = concurrent.futures.ThreadPoolExecutor() async def process_ai_queue(): loop = asyncio.get_running_loop() while True: try: if not askmultus_queue.empty(): ctx, user_id, user_name, prompt, channel_id, full_data, user_history_field, model = await askmultus_queue.get() guild_id = ctx.guild.id user_data = load_user_data(user_id, guild_id) try: user_history = user_data.get(user_history_field, []) user_history.append({"role": "user", "content": f"{user_name}: {prompt}"}) messages = [ {"role": "system", "content": full_data}, *user_history ] completion = await loop.run_in_executor(executor, lambda: openai_instance.chat.completions.create( model=model, messages=messages, temperature=0.8, timeout=15, # Limit waiting time for response )) assistant_message = completion.choices[0].message.content channel = client.get_channel(channel_id) # Prepare the embed with split fields if necessary embed = discord.Embed(title="AI Response", color=0x00ff00) embed.add_field(name="Prompt", value=prompt, inline=False) if len(assistant_message) <= 1024: embed.add_field(name="Response", value=assistant_message, inline=False) else: # Split the response into multiple fields if it exceeds 1024 characters parts = [assistant_message[i:i+1024] for i in range(0, len(assistant_message), 1024)] for i, part in enumerate(parts): embed.add_field(name=f"Response Part {i+1}", value=part, inline=False) await channel.send(embed=embed) if ctx.voice_client: # If bot is in a voice channel tts = gTTS(assistant_message, lang="en") tts.save("response.mp3") ctx.voice_client.play(discord.FFmpegPCMAudio("response.mp3")) user_history.append({"role": "assistant", "content": assistant_message}) # Update the relevant user history field update_user_data(user_data["user_id"], guild_id, user_history_field, json.dumps(user_history)) except Exception as e: logger.error(f"Processing errors: {e}") finally: askmultus_queue.task_done() except asyncio.CancelledError: break except Exception as e: logger.error(f"Error in process_ai_queue: {e}") await asyncio.sleep(5) @client.hybrid_command() async def vision(ctx, image_url: str): """Analyzes the content of an image.""" if not features["vision"]: await ctx.send("Sorry, the vision feature is currently disabled.") return try: # Read the image and encode it to base64 response = requests.get(image_url) if response.status_code == 200: base64_image = base64.b64encode(response.content).decode("utf-8") else: await ctx.send(f"Failed to retrieve the image from {image_url}.") return # Process the request using OpenAI's Vision model completion = openai_instance.chat.completions.create( model="local-model", messages=[ { "role": "system", "content": "This is a chat between a user and an assistant. The assistant is helping the user to describe an image.", }, { "role": "user", "content": [ {"type": "text", "text": "What’s in this image?"}, {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}}, ], }, ], max_tokens=1000, stream=True, ) # Send the response to the Discord channel chunks = [] for chunk in completion: if chunk.choices[0].delta.content: chunks.append(chunk.choices[0].delta.content) result = "".join(chunks) await ctx.send(result) except Exception as e: await ctx.send(f"Error analyzing the image: {e}") @client.hybrid_command() async def addbackgrounddata(ctx, *, data: str): """Adds additional background data to the file.""" if commands.is_owner(): try: with open("background_data.txt", "a", encoding="utf-8") as file: file.write("\n" + data) await ctx.send("Background data added successfully.") except Exception as e: await ctx.send(f"Error adding background data: {e}") else: await ctx.send("You don't have the necessary permissions to use this command.") @client.hybrid_command() async def summarize(ctx, number: int): """Summarizes the last x messages in the chat.""" guild_id = ctx.guild.id user_perms = load_user_data(ctx.author.id, guild_id) if 5 < user_perms["permission"]: try: # Fetch the last 10 messages in the channel messages = [] async for message in ctx.channel.history(limit=number): messages.append(message) # Extract the content of each message message_contents = [message.content for message in messages] # Join the message contents into a single string messages_combined = "\n".join(message_contents) introduction = read_introduction() full_data = introduction + background_data # Process the combined messages using OpenAI's summarization model completion = openai_instance.chat.completions.create( model="text-davinci-003", # Choose an appropriate summarization model messages=[ {"role": "system", "content": "Summarizing the last x messages in the chat: "}, {"role": "user", "content": messages_combined}, ], max_tokens=1000, stream=False, ) # Extract the summarized text from the completion summary = completion.choices[0].message.content # Send the summarized text to the Discord channel await ctx.send(summary) except Exception as e: await ctx.send(f"An error occurred while summarizing the messages: {e}") else: await ctx.send("You don't have the necessary permissions to use this command.") @client.hybrid_command() async def join(ctx): """Bot joins a voice channel.""" if ctx.author.voice: channel = ctx.author.voice.channel await channel.connect() await ctx.send(f"Joined {channel}") else: await ctx.send("You are not connected to a voice channel.") @client.hybrid_command() async def leave(ctx): """Bot leaves the voice channel.""" if ctx.voice_client: await ctx.voice_client.disconnect() await ctx.send("Left the voice channel.") else: await ctx.send("I am not in a voice channel.") @client.hybrid_command() async def toggle_feature(ctx, feature: str, state: str): """Allows admin to enable or disable features.""" guild_id = ctx.guild.id user_id = ctx.author.id user_data = load_user_data(user_id, guild_id) user_perms = user_data["permission"] if user_perms < 8: # Nur Admins (permission level >= 8) können Funktionen aktivieren/deaktivieren await ctx.send("You do not have the necessary permissions to toggle features.") return global features if feature.lower() not in features: await ctx.send(f"Feature {feature} not found.") return if state.lower() == "on": features[feature.lower()] = True await ctx.send(f"Feature {feature} enabled.") elif state.lower() == "off": features[feature.lower()] = False await ctx.send(f"Feature {feature} disabled.") else: await ctx.send("Please specify 'on' or 'off'.") await ctx.send("Please specify 'on' or 'off'.") @client.hybrid_command() async def version(ctx): """Displays the current version of the bot.""" await ctx.send(f"The current version of the bot is: {__version__}") # Cache-Ordner für Notizen CACHE_DIR = "cache" if not os.path.exists(CACHE_DIR): os.makedirs(CACHE_DIR) @client.hybrid_command() async def addnotes(ctx, type: str, *, source: str): """Adds a note that can be consulted later.""" await ctx.defer() # Signalisiert, dass die Bearbeitung des Befehls begonnen hat user_id = ctx.author.id guild_id = ctx.guild.id user_cache_dir = os.path.join(CACHE_DIR, f"{str(guild_id)}_{str(user_id)}") if not os.path.exists(user_cache_dir): os.makedirs(user_cache_dir) note_file = os.path.join(user_cache_dir, "notes.txt") if type.lower() == "txt": if ctx.message.attachments: attachment = ctx.message.attachments[0] await attachment.save(note_file) await ctx.send(f"Text file added as notes for user {ctx.author.name}.") else: await ctx.send("No text file attached.") elif type.lower() == "url": try: response = requests.get(source) if response.status_code == 200: # HTML-Parsen und nur Text extrahieren soup = BeautifulSoup(response.text, 'html.parser') # Entfernen von Header- und Footer-Elementen for element in soup(['header', 'footer', 'nav', 'aside']): element.decompose() text = soup.get_text() # Entfernen von überflüssigen Leerzeilen cleaned_text = "\n".join([line.strip() for line in text.splitlines() if line.strip()]) with open(note_file, "a", encoding="utf-8") as file: file.write(cleaned_text + "\n") await ctx.send(f"Website content added as notes for user {ctx.author.name}.") else: await ctx.send(f"Failed to retrieve the website from {source}.") except Exception as e: await ctx.send(f"Error fetching website: {e}") else: await ctx.send("Invalid type. Use 'txt' for text files or 'url' for website URLs.") @client.hybrid_command() async def asknotes(ctx, *, question: str): """Asks a question about the saved notes.""" await ctx.defer() user_id = ctx.author.id guild_id = ctx.guild.id user_cache_dir = os.path.join(CACHE_DIR, f"{str(guild_id)}_{str(user_id)}") note_file = os.path.join(user_cache_dir, "notes.txt") asknotesintroduction = read_askintroduction() if not os.path.exists(note_file): await ctx.send(f"No notes found for user {ctx.author.name}.") return with open(note_file, "r", encoding="utf-8") as file: notes = file.read() # Define the full data and user history field for asknotes full_data = asknotesintroduction user_history_field = "asknotes_history" # Füge die Anfrage zur Warteschlange hinzu await askmultus_queue.put((ctx, user_id, ctx.author.name, question, ctx.channel.id, full_data, user_history_field, "text-davinci-003")) # Erstelle ein Embed für die Bestätigungsnachricht embed = discord.Embed(title="Notes Query", color=0x00ff00) embed.add_field(name="Request Received", value="Your request has been added to the queue. Processing it now...") await ctx.send(embed=embed) @client.hybrid_command() async def delnotes(ctx): """Deletes all saved notes and the asknotes history for the user.""" user_id = ctx.author.id guild_id = ctx.guild.id user_cache_dir = os.path.join(CACHE_DIR, f"{str(guild_id)}_{str(user_id)}") if os.path.exists(user_cache_dir): # Lösche die gespeicherten Notizen im Cache-Ordner shutil.rmtree(user_cache_dir) # Setze die asknotes-Historie in der Datenbank zurück try: update_user_data(user_id, guild_id, "asknotes_history", None) await ctx.send(f"All notes and asknotes history deleted for user {ctx.author.name}.") except Exception as e: await ctx.send(f"Error deleting asknotes history: {e}") else: await ctx.send(f"No notes found for user {ctx.author.name}.") try: loop.run_until_complete(client.start(TOKEN)) except KeyboardInterrupt: loop.run_until_complete(client.logout()) finally: loop.close()