Files
Discord-ai-chatbot/bot.py
2025-08-18 09:50:06 +02:00

1759 lines
66 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
__version__ = "dev-0.9.4"
__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
import hashlib
from urllib.parse import urlparse
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))),
"summarize": bool(int(os.getenv("SUMMARIZE_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 (OLD - now using connection pool)
# db_connection = mysql.connector.connect(
# host=DB_HOST,
# port=DB_PORT,
# user=DB_USER,
# password=DB_PASSWORD,
# database=DB_DATABASE
# )
# Cursor erstellen (OLD - now using connection pool)
# 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):
"""Fügt neue Benutzerdaten in die Datenbank ein mit Connection Pool"""
connection = None
cursor = None
try:
connection = connect_to_database()
cursor = connection.cursor()
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)
cursor.execute(insert_query, data)
connection.commit()
logger.info("User data inserted successfully.")
except Exception as e:
logger.error(f"Error inserting user data: {e}")
if connection:
connection.rollback()
raise e
finally:
if cursor:
cursor.close()
if connection:
close_database_connection(connection)
def update_user_data(user_id, guild_id, field, value):
"""Aktualisiert Benutzerdaten in der Datenbank mit Connection Pool"""
connection = None
cursor = None
try:
connection = connect_to_database()
cursor = connection.cursor()
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)
cursor.execute(update_query, (serialized_chat_history, user_id, guild_id))
else:
cursor.execute(update_query, (value, user_id, guild_id))
connection.commit()
logger.debug(f"Successfully updated {field} for user {user_id} in guild {guild_id}")
except mysql.connector.Error as err:
logger.error(f"Database error: {err}")
if connection:
connection.rollback()
raise err
finally:
if cursor:
cursor.close()
if connection:
close_database_connection(connection)
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")
# Removed get_database_cursor() - now using connection pool directly
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()
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 ""
# 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 = {
"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": 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"],
nickname,
profile_picture,
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):
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)]
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)]
# 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 Member-Informationen
if not user_data or user_data.get("user_id") is None:
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
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_sync(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_sync(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 <t:{unix_end_time}:R>",
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):
"""Starts the live chat in the current channel."""
channel_id = ctx.channel.id
if channel_id in live_chats and live_chats[channel_id]["active"]:
await ctx.send("Live chat is already active.")
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 started. Messages will be processed.")
@client.hybrid_command()
async def stoplivechat(ctx):
"""Stops the live chat in the current channel."""
channel_id = ctx.channel.id
if channel_id in live_chats:
live_chats[channel_id]["active"] = False
await ctx.send("Live chat has been stopped.")
else:
await ctx.send("No active live chat in this channel.")
@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_sync(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):
"""Calculates the XP needed for the next 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, member=None):
"""Adds XP to a user and checks if they level up. Also updates user data."""
# Lade Benutzerdaten (XP, Level, etc.) - mit member-Objekt für neue User
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)
# 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
level_up = False
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
level_up = True
# Berechne die neuen XP-Anforderungen für das nächste Level
xp_needed = calculate_xp_needed_for_level(user_data["level"])
# Aktualisiere Benutzerdaten wenn member-Objekt verfügbar ist
if member:
try:
# Aktualisiere Nickname
new_nickname = member.display_name
if user_data.get("nickname") != new_nickname:
try:
update_user_data(user_id, guild_id, "nickname", new_nickname)
user_data["nickname"] = new_nickname
except Exception as e:
logger.error(f"Failed to update nickname for user {user_id}: {e}")
# Aktualisiere Profilbild - mit lokalem Download und Hash-Vergleich
if member.display_avatar:
try:
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
except Exception as e:
logger.error(f"Failed to update profile picture for user {user_id}: {e}")
else:
# Kein Profilbild vorhanden, nutze Default
try:
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
except Exception as e:
logger.error(f"Failed to set default profile picture for user {user_id}: {e}")
# Aktualisiere Join-Datum - IMMER wenn member.joined_at verfügbar ist
if member.joined_at:
try:
join_date = member.joined_at.date()
# Aktualisiere Join-Datum auch wenn es bereits existiert (für den Fall, dass es falsch war)
update_user_data(user_id, guild_id, "join_date", join_date)
user_data["join_date"] = join_date
except Exception as e:
logger.error(f"Failed to update join date for user {user_id}: {e}")
logger.info(f"Updated user data for {member.display_name} (ID: {user_id}) - Nickname: {new_nickname}, Join Date: {join_date if member.joined_at else 'N/A'}")
except Exception as e:
logger.error(f"Error updating user data during XP gain: {e}")
# Speichere die aktualisierten XP und Level in der Datenbank
try:
update_user_data(user_id, guild_id, "xp", user_data["xp"])
update_user_data(user_id, guild_id, "level", user_data["level"])
except Exception as e:
logger.error(f"Failed to update XP/Level for user {user_id}: {e}")
return level_up # Gibt zurück, ob ein Level-Up stattgefunden hat
@client.hybrid_command()
async def level(ctx, user: discord.User = None):
"""Shows the current level and XP of the user or another person."""
guild_id = ctx.guild.id
# Wenn kein User angegeben wurde, zeige das eigene Level
if user is None:
target_user = ctx.author
user_id = ctx.author.id
else:
target_user = user
user_id = user.id
# Lade die Benutzerdaten (XP und Level) aus der Datenbank
user_data = load_user_data_sync(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 {target_user.display_name}",
description=f"Level: {current_level}\nXP: {current_xp}/{xp_needed}",
color=0x00ff00
)
# Füge das Profilbild des Benutzers hinzu
embed.set_thumbnail(url=target_user.display_avatar.url)
await ctx.send(embed=embed)
@client.hybrid_command()
async def leaderboard(ctx):
"""Shows the top users in the XP leaderboard."""
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
# Überprüfe, ob die Nachricht in einem Server gesendet wurde
if message.guild is None:
await client.process_commands(message)
return
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)
cooldown_key = (user_id, guild_id)
current_time = time.time()
if cooldown_key in xp_cooldowns:
time_since_last_xp = current_time - xp_cooldowns[cooldown_key]
if time_since_last_xp < 60: # 60 Sekunden Cooldown
await client.process_commands(message)
return
# Cooldown aktualisieren
xp_cooldowns[cooldown_key] = current_time
xp_gained = random.randint(2, 25) # Zufällige XP zwischen 2 und 25 vergeben
# XP vergeben und Benutzerdaten aktualisieren
level_up = await add_xp_to_user(user_id, guild_id, xp_gained, member)
# Optional: Level-Up Benachrichtigung senden
if level_up:
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! 🎉")
except:
# Falls das Senden fehlschlägt, einfach überspringen
pass
# 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_sync(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 authorization 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_sync(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_sync(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_sync(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_sync(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_sync(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):
"""Shuts down the bot (Admin only)."""
user_perms = load_user_data_sync(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):
"""Syncs the bot's slash commands (Owner only)."""
try:
user_perms = load_user_data_sync(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_sync(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_sync(user_id, guild_id)
try:
user_history = user_data.get(user_history_field, [])
# Ensure user_history is a list, not a string
if isinstance(user_history, str):
try:
user_history = json.loads(user_history)
except (json.JSONDecodeError, ValueError):
user_history = []
elif not isinstance(user_history, list):
user_history = []
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, 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": "Whats 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 (Owner only)."""
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 (Admin only)."""
if not features["summarize"]:
await ctx.send("Sorry, the summarize feature is currently disabled.")
return
guild_id = ctx.guild.id
user_perms = load_user_data_sync(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):
"""Makes the bot join 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):
"""Makes the bot leave 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 bot features."""
guild_id = ctx.guild.id
user_id = ctx.author.id
user_data = load_user_data_sync(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)
# 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}")
# Use a session with timeout for better connection handling
session = requests.Session()
session.timeout = (10, 15) # (connection timeout, read timeout)
response = session.get(discord_url, timeout=15)
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. Use 'txt' for text files or 'url' for website URLs."""
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 your 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()