Files
Discord-ai-chatbot/bot.py
2024-09-15 10:25:35 +02:00

1084 lines
38 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.0"
__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_DB_HOST = os.getenv("GIVEAWAY_DB_HOST")
GIVEAWAY_DB_PORT = os.getenv("GIVEAWAY_DB_PORT")
GIVEAWAY_DB_USER = os.getenv("GIVEAWAY_DB_USER")
GIVEAWAY_DB_PASSWORD = os.getenv("GIVEAWAY_DB_PASSWORD")
GIVEAWAY_DB_DATABASE = os.getenv("GIVEAWAY_DB_DATABASE")
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)))
}
def connect_to_giveaway_db():
return mysql.connector.connect(
host=GIVEAWAY_DB_HOST,
port=GIVEAWAY_DB_PORT,
user=GIVEAWAY_DB_USER,
password=GIVEAWAY_DB_PASSWORD,
database=GIVEAWAY_DB_DATABASE
)
def create_giveaway_table():
connection = connect_to_giveaway_db()
cursor = connection.cursor()
create_table_query = """
CREATE TABLE IF NOT EXISTS giveaways (
id INT AUTO_INCREMENT PRIMARY KEY,
platform VARCHAR(255),
name VARCHAR(255),
uuid CHAR(36) NOT NULL UNIQUE,
game_key VARCHAR(255),
winner_dc_id BIGINT,
aktiv BOOLEAN DEFAULT FALSE
);
"""
cursor.execute(create_table_query)
connection.commit()
cursor.close()
connection.close()
create_giveaway_table()
giveaways = {}
# Erstelle einen Ordner für die Logs, wenn er noch nicht existiert
LOGS_DIR = "logs"
if not os.path.exists(LOGS_DIR):
os.makedirs(LOGS_DIR)
# Konfiguriere das Log-Format
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
# Erstelle einen Logger für den Bot
logger = logging.getLogger("discord_bot")
logger.setLevel(logging.INFO)
# Überprüfe, ob bereits eine Log-Datei für den aktuellen Tag vorhanden ist
log_file = os.path.join(LOGS_DIR, f"{datetime.now().strftime('%Y-%m-%d')}.log")
if os.path.exists(log_file):
try:
# Umbenennen der vorhandenen Log-Datei, um sie vor dem Überschreiben zu schützen
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.")
# Erstelle einen Handler, um Logs in eine Datei zu schreiben
file_handler = logging.FileHandler(log_file)
file_handler.setLevel(logging.INFO)
# Definiere das Format für die Datei-Logs
file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
file_handler.setFormatter(file_formatter)
# Füge den Handler zum Logger hinzu
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
# 10 filter = acc under review = nicht ok = ban add timestamp = 2 bans = unendlicher ban
#perms || 10 = Owner || 8 = Admin || 5 = Mod
# Point to the local server
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.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()
# SQL-Befehl für die Erstellung der Tabelle, falls sie noch nicht existiert
create_table_query = """
CREATE TABLE IF NOT EXISTS user_data (
user_id BIGINT PRIMARY KEY,
permission INT,
points INT,
ban INT,
askmultus INT,
filter_value INT,
rank INT,
chat_history JSON,
xp INT,
level INT
);
"""
db_cursor.execute(create_table_query)
db_connection.commit()
def insert_user_data(user_id, permission, points, ban, askmultus, filter_value, chat_history):
insert_query = """
INSERT INTO user_data (user_id, permission, points, ban, askmultus, filter_value, rank, chat_history, xp, level)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
serialized_chat_history = json.dumps(chat_history)
data = (user_id, permission, points, ban, askmultus, filter_value, 0, serialized_chat_history, 0, 1) # Initialisiere XP auf 0 und Level auf 1
try:
db_cursor.execute(insert_query, data)
db_connection.commit()
print("User data inserted successfully.")
except Exception as e:
print(f"Error inserting user data: {e}")
db_connection.rollback()
def update_user_data(user_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"
# Überprüfen, ob das Feld 'chat_history' aktualisiert wird
if field == 'chat_history':
# Serialize the chat history list to a JSON string
serialized_chat_history = json.dumps(value)
db_cursor.execute(update_query, (serialized_chat_history, user_id))
else:
db_cursor.execute(update_query, (value, user_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, field, value)
def connect_to_database():
return mysql.connector.connect(
host=DB_HOST,
port=DB_PORT,
user=DB_USER,
password=DB_PASSWORD,
database=DB_DATABASE
)
def close_database_connection(connection):
connection.close()
def load_user_data_from_mysql(user_id):
connection = connect_to_database()
cursor = connection.cursor()
select_query = "SELECT * FROM user_data WHERE user_id = %s"
cursor.execute(select_query, (user_id,))
result = cursor.fetchone()
cursor.close()
close_database_connection(connection)
if result:
# Wenn das Level und die XP nicht vorhanden sind, initialisieren
user_data = {
"user_id": result[0],
"permission": result[1],
"points": result[2],
"ban": result[3],
"askmultus": result[4],
"filter_value": result[5],
"rank": result[6],
"chat_history": json.loads(result[7]) if result[7] else [],
"level": result[8] if result[8] is not None else 1, # Initialisiere Level mit 1
"xp": result[9] if result[9] is not None else 0 # Initialisiere XP mit 0
}
else:
# Falls keine Benutzerdaten gefunden werden, initialisiere sie neu
user_data = {
"user_id": user_id,
"permission": 0, # Standardberechtigung
"points": 0, # Standardpunkte
"ban": 0, # Standardbannstatus
"askmultus": 0, # Standardwert für askmultus
"filter_value": 0, # Standardwert für Filter
"rank": 0, # Standardrang
"chat_history": [], # Leerer Chatverlauf
"level": 1, # Standardlevel
"xp": 0 # Standard-XP
}
# Fügen Sie die neuen Benutzerdaten zur Datenbank hinzu
insert_user_data(
user_data["user_id"],
user_data["permission"],
user_data["points"],
user_data["ban"],
user_data["askmultus"],
user_data["filter_value"],
user_data["chat_history"]
)
update_user_data(user_data["user_id"], "level", 1)
update_user_data(user_data["user_id"], "xp", 0)
return user_data
cached_user_data = {}
pending_deletion = {}
async def cache_user_data(user_id, data):
cached_user_data[user_id] = data
# Setze die Daten nach 30 Sekunden auf die Löschliste
if user_id not in pending_deletion:
pending_deletion[user_id] = asyncio.get_event_loop().call_later(30, lambda: remove_user_data_from_cache(user_id))
def remove_user_data_from_cache(user_id):
# Entferne den Benutzer aus dem Cache und der Löschliste
if user_id in cached_user_data:
del cached_user_data[user_id]
if user_id in pending_deletion:
pending_deletion[user_id].cancel()
del pending_deletion[user_id]
def load_user_data(user_id):
if user_id in cached_user_data:
return cached_user_data[user_id]
# Daten aus der Datenbank laden oder einfügen
user_data = load_user_data_from_mysql(user_id)
asyncio.ensure_future(cache_user_data(user_id, user_data))
return user_data
#-----------------------------------------------------------------------------------------------------------
def save_giveaway_to_db(platform, name, prize_uuid, game_key, winner_dc_id=None):
connection = connect_to_giveaway_db()
cursor = connection.cursor()
insert_query = """
INSERT INTO giveaways (platform, name, uuid, game_key, winner_dc_id, aktiv)
VALUES (%s, %s, %s, %s, %s, %s)
"""
data = (platform, name, str(prize_uuid), game_key, winner_dc_id, False)
cursor.execute(insert_query, data)
connection.commit()
cursor.close()
connection.close()
def update_winner_in_db(prize_uuid, winner_dc_id):
connection = connect_to_giveaway_db()
cursor = connection.cursor()
update_query = """
UPDATE giveaways SET winner_dc_id = %s WHERE uuid = %s
"""
data = (winner_dc_id, str(prize_uuid))
cursor.execute(update_query, data)
connection.commit()
cursor.close()
connection.close()
class Giveaway:
def __init__(self, ctx, platform, prize, num_winners, title, subtitle, duration, end_time):
self.ctx = ctx
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
self.game_key = f"PREDEFINED_GAME_KEY" # Platzhalter für den tatsächlichen Game-Key
# Speichern des Giveaways in der Datenbank
save_giveaway_to_db(self.platform, self.title, self.prize_uuid, self.game_key)
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):
return random.sample(self.participants, min(self.num_winners, len(self.participants)))
@client.hybrid_command()
async def startgiveaway(ctx, platform: str, prize: str, num_winners: int, title: str, subtitle: str, duration: str):
"""Creates a giveaway. Only available for mods and higher."""
user_data = load_user_data(ctx.author.id)
if user_data["permission"] < 5:
await ctx.send("You do not 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
# Neues Giveaway erstellen
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 endet at {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
await ctx.send(embed=embed, view=view)
check_giveaway.start(giveaway_id)
@tasks.loop(minutes=1)
async def check_giveaway(giveaway_id):
"""Checks every 1 minute to see if the giveaway has ended."""
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}")
# Jeden Gewinner benachrichtigen und zur Webseite schicken
for winner in winners:
user_data = load_user_data(winner.id)
# Gewinner in der Datenbank speichern (winner_dc_id)
update_winner_in_db(giveaway.prize_uuid, winner.id)
# Nachricht an den Gewinner senden
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.prize_uuid}")
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."""
# Überprüfen, ob es sich um eine Button-Interaktion handelt und ein custom_id vorhanden ist
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)
else:
# Logge Interaktionen, die nicht den erwarteten Typ haben
logger.error(f"Unbekannte Interaktion: {interaction.type}, Daten: {interaction.data}")
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_for_level(level):
return 5 * (level ** 2) + 50 * level + 100
def calculate_level(xp):
"""Berechnet das Level basierend auf den Erfahrungspunkten (XP)."""
l = 1
while 5 * (l ** 2) + 50 * l + 100 <= xp:
l += 1
return l
async def add_xp_to_user(user_id, xp_gained):
"""Fügt XP zu einem Benutzer hinzu und aktualisiert das Level, falls nötig."""
user_data = load_user_data(user_id)
# Füge die neuen XP hinzu
user_data["xp"] += xp_gained
# Berechne das neue Level basierend auf den aktualisierten XP
new_level = calculate_level(user_data["xp"])
user_data["level"] = new_level
# Aktualisiere die Benutzerdaten in der Datenbank
update_user_data(user_id, "xp", user_data["xp"])
update_user_data(user_id, "level", user_data["level"])
async def notify_level_up(user_id, new_level):
user = await client.fetch_user(user_id)
await user.send(f"Congratulations! You have leveled up to level {new_level}.")
@client.hybrid_command()
async def level(ctx):
"""Zeigt dein aktuelles Level und XP an."""
user_id = ctx.author.id
user_data = load_user_data(user_id)
level = user_data.get("level", 1) # Verwende 1 als Standardlevel, wenn nicht vorhanden
xp = user_data.get("xp", 0) # Verwende 0 als Standard-XP, wenn nicht vorhanden
required_xp = calculate_xp_for_level(level)
embed = discord.Embed(
title="Level",
description=f"You are at level {level}.\nYou have {xp}/{required_xp} XP.",
color=0x3498db
)
await ctx.send(embed=embed)
@client.hybrid_command()
async def leaderboard(ctx):
"""Zeigt die Top 10 Benutzer nach Level und XP an."""
connection = connect_to_database()
cursor = connection.cursor(dictionary=True)
select_query = "SELECT user_id, level, xp FROM user_data ORDER BY level DESC, xp DESC LIMIT 10"
cursor.execute(select_query)
top_users = cursor.fetchall()
cursor.close()
connection.close()
embed = discord.Embed(title="🏆 **Leaderboard** 🏆", color=0x3498db)
for idx, user_data in enumerate(top_users, start=1):
user = client.get_user(user_data["user_id"])
username = user.display_name if user else f"User ID: {user_data['user_id']}"
embed.add_field(name=f"{idx}. {username}", value=f"Level {user_data['level']} - {user_data['xp']} XP", inline=False)
await ctx.send(embed=embed)
xp_cooldowns = {}
@client.event
async def on_message(message):
if message.author.bot:
return
user_id = message.author.id
if user_id in xp_cooldowns and time.time() < xp_cooldowns[user_id] + 60:
return # Der Benutzer hat kürzlich XP erhalten, also überspringen wir das
xp_gained = random.randint(5, 15)
await add_xp_to_user(user_id, xp_gained)
xp_cooldowns[user_id] = time.time()
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())
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
# Lade Benutzerdaten aus der MySQL-Datenbank
user_data = load_user_data(user_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
# Load user data from the MySQL database
user_data = load_user_data(user_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)
if 5 <= user_perms["permission"]:
user_id = user.id
# Lade Benutzerdaten aus der MySQL-Datenbank
user_data = load_user_data(user_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"], "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)
if 5 <= user_perms["permission"]:
user_id = user.id
# Lade Benutzerdaten aus der MySQL-Datenbank
user_data = load_user_data(user_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"], "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)
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)
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
# Lade Benutzerdaten aus der MySQL-Datenbank
user_data = load_user_data(user_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"], "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()
user_data = load_user_data(user_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"], 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": "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."""
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."""
user_perms = load_user_data(ctx.author.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."""
user_id = ctx.author.id
user_data = load_user_data(user_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
user_cache_dir = os.path.join(CACHE_DIR, 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
user_cache_dir = os.path.join(CACHE_DIR, 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
user_cache_dir = os.path.join(CACHE_DIR, 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, "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()