1149 lines
42 KiB
Python
1149 lines
42 KiB
Python
__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_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.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, nickname, profile_picture, join_date, xp=0, level=1):
|
||
insert_query = """
|
||
INSERT INTO user_data (user_id, guild_id, permission, points, ban, askmultus, filter_value, rank, chat_history, nickname, profile_picture, join_date, xp, level, 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, nickname, profile_picture, join_date, xp, level, None)
|
||
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, 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():
|
||
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, guild_id):
|
||
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()
|
||
|
||
cursor.close()
|
||
close_database_connection(connection)
|
||
|
||
if result:
|
||
user_data = {
|
||
"user_id": result[0],
|
||
"guild_id": result[1],
|
||
"permission": result[2],
|
||
"points": 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 [],
|
||
"nickname": result[9],
|
||
"profile_picture": result[10],
|
||
"join_date": result[11],
|
||
"xp": result[12],
|
||
"level": result[13],
|
||
"leave_date": result[14]
|
||
}
|
||
else:
|
||
# Falls keine Benutzerdaten vorhanden sind, neue Daten einfügen
|
||
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": [],
|
||
"nickname": None, # Bei Bedarf setzen
|
||
"profile_picture": None, # Bei Bedarf setzen
|
||
"join_date": datetime.now().date(),
|
||
"xp": 0,
|
||
"level": 1,
|
||
"leave_date": None
|
||
}
|
||
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["nickname"],
|
||
user_data["profile_picture"],
|
||
user_data["join_date"],
|
||
user_data["xp"],
|
||
user_data["level"]
|
||
)
|
||
|
||
return user_data
|
||
|
||
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 = 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()
|
||
cursor.close()
|
||
connection.close()
|
||
|
||
return result[0] if result else None
|
||
|
||
def save_global_permission(user_id, permission_level):
|
||
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()
|
||
cursor.close()
|
||
connection.close()
|
||
|
||
#-----------------------------------------------------------------------------------------------------------
|
||
|
||
async def update_all_users():
|
||
connection = connect_to_database()
|
||
cursor = connection.cursor()
|
||
|
||
# Hole alle Guild-IDs aus der guilds-Datenbank
|
||
cursor.execute("SELECT guild_id FROM guilds")
|
||
guilds = cursor.fetchall()
|
||
|
||
for (guild_id,) in guilds:
|
||
guild = client.get_guild(int(guild_id))
|
||
if not guild:
|
||
continue # Falls der Bot auf diesem Server nicht mehr aktiv ist, überspringen
|
||
|
||
# Aktualisiere Benutzerdaten
|
||
for member in guild.members:
|
||
user_id = member.id
|
||
user_data = load_user_data(user_id, guild_id)
|
||
|
||
# Setze Nickname und Profilbild
|
||
update_user_data(user_id, guild_id, "nickname", member.display_name)
|
||
update_user_data(user_id, guild_id, "profile_picture", str(member.display_avatar.url))
|
||
|
||
# Falls leave_date gesetzt ist und der Nutzer wieder auf dem Server ist, entferne leave_date
|
||
if user_data.get("leave_date"):
|
||
update_user_data(user_id, guild_id, "leave_date", None)
|
||
|
||
# Überprüfe alle User in `user_data`, ob sie noch Mitglied im Server sind
|
||
cursor.execute("SELECT user_id FROM user_data WHERE guild_id = %s", (guild_id,))
|
||
all_users = cursor.fetchall()
|
||
|
||
for (user_id,) in all_users:
|
||
member = guild.get_member(int(user_id))
|
||
if member is None: # Benutzer hat den Server verlassen
|
||
# Setze leave_date, wenn der Nutzer nicht mehr auf dem Server ist
|
||
leave_date = datetime.today().date()
|
||
update_user_data(user_id, guild_id, "leave_date", leave_date)
|
||
|
||
cursor.close()
|
||
connection.close()
|
||
|
||
def save_giveaway_to_db(guild_id, platform, name, prize_uuid, game_key):
|
||
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()
|
||
cursor.close()
|
||
connection.close()
|
||
|
||
def update_winner_in_db(guild_id, prize_uuid, winner_dc_id):
|
||
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()
|
||
cursor.close()
|
||
connection.close()
|
||
|
||
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
|
||
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.guild_id, 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 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 <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)
|
||
|
||
@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}")
|
||
|
||
# Notify and update the winners
|
||
for winner in winners:
|
||
user_data = load_user_data(winner.id, giveaway.guild_id)
|
||
update_winner_in_db(giveaway.guild_id, giveaway.prize_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}/{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_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():
|
||
update_user_data_task.start()
|
||
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.")
|
||
|
||
await update_all_users()
|
||
|
||
@tasks.loop(hours=24) # Läuft alle 24 Stunden
|
||
async def update_user_data_task():
|
||
await update_all_users()
|
||
|
||
@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()
|