diff --git a/bot.py b/bot.py index b44843e..2e53cbc 100644 --- a/bot.py +++ b/bot.py @@ -1,45 +1,102 @@ import discord -from discord.ext import commands, tasks +from discord.ext import commands from discord import app_commands +import datetime +import json import os +import re import logging -from dotenv import load_dotenv -from data_manager import DataManager -from utils import extract_number_and_sum, setup_logging -from datetime import datetime, timedelta +import asyncio +from datetime import datetime # Configuration du logging -setup_logging() - -logger = logging.getLogger('Le69iste') +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s [%(levelname)s] %(message)s', + handlers=[ + logging.StreamHandler() + ] +) +logger = logging.getLogger('DiscordBot') # Chargement des variables d'environnement +from dotenv import load_dotenv load_dotenv() +DATA_FILE = 'data.json' +START_TIME = datetime.now() + # Initialisation des intentions et du bot intents = discord.Intents.default() intents.messages = True intents.message_content = True bot = commands.Bot(command_prefix='!', intents=intents) -# Initialisation du gestionnaire de données -data_manager = DataManager('data.json') +def get_uptime(): + now = datetime.now() + delta = now - START_TIME + days, seconds = delta.days, delta.seconds + hours = seconds // 3600 + minutes = (seconds % 3600) // 60 + seconds = seconds % 60 + return f"{days} jours, {hours} heures, {minutes} minutes et {seconds} secondes" -# Variable pour stocker le temps de démarrage du bot -start_time = datetime.now() +def load_data(): + try: + with open(DATA_FILE, 'r') as f: + content = f.read().strip() + if not content: + logger.warning("Fichier de données vide, création d'une nouvelle structure") + return {"config": {}, "stats": {}} + return json.loads(content) + except FileNotFoundError: + logger.warning(f"Fichier {DATA_FILE} non trouvé, création d'une nouvelle structure") + return {"config": {}, "stats": {}} + except json.JSONDecodeError: + logger.error(f"Erreur de décodage JSON dans {DATA_FILE}. Voulez vous créer une nouvelle structure ? Toutes les données du bot serons perdues (O/N)") + response = input() + if response.lower() == 'o' or response.lower() == 'oui' or response.lower() == 'y' or response.lower() == 'yes': + return {"config": {}, "stats": {}} + else: + logger.error("Arrêt du bot") + exit(1) + +def save_data(data): + try: + with open(DATA_FILE, 'w') as f: + json.dump(data, f, indent=4) + logger.debug("Données sauvegardées avec succès") + except Exception as e: + logger.error(f"Erreur lors de la sauvegarde des données: {e}") + +def get_guild_config(guild_id: str, data: dict) -> dict: + if guild_id not in data['config']: + data['config'][guild_id] = { + "send_public": True, + "send_message": True, + "enable_reactions": True + } + save_data(data) + return data['config'][guild_id] + +def extract_number_and_sum(message): + numbers = list(map(int, re.findall(r'\b\d+\b', message))) + total = sum(numbers) + return numbers, total -@tasks.loop(hours=1) async def update_presence(): - await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name=f"les nombres rigolos sur {len(bot.guilds)} serveurs.")) + while True: + await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name=f"les nombres rigolos sur {len(bot.guilds)} serveurs.")) + await asyncio.sleep(3600) @bot.event async def on_ready(): await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name=f"les nombres rigolos sur {len(bot.guilds)} serveurs.")) - update_presence.start() + asyncio.create_task(update_presence()) logger.info(f'Bot {bot.user} connecté à Discord') try: synced = await bot.tree.sync() - logger.info(f'{len(synced)} commandes slash synchronisées') + logger.info(f'Synchronisé {len(synced)} commandes slash') except Exception as e: logger.error(f'Erreur lors de la synchronisation des commandes slash: {e}') @@ -50,13 +107,19 @@ async def on_message(message): numbers, total = extract_number_and_sum(message.content) if total == 69: - data = await data_manager.load_data() + data = load_data() guild_id = str(message.guild.id) user_id = str(message.author.id) - config = await data_manager.get_guild_config(guild_id) + config = get_guild_config(guild_id, data) # Mettre à jour les statistiques - data_manager.update_stats(guild_id, user_id, 'count_69') + if guild_id not in data['stats']: + data['stats'][guild_id] = {} + if user_id not in data['stats'][guild_id]: + data['stats'][guild_id][user_id] = {'count_69': 0} + + data['stats'][guild_id][user_id]['count_69'] += 1 + save_data(data) logger.info(f"69 trouvé dans le message de {message.author.id} sur {message.guild.id}") @@ -85,16 +148,113 @@ async def on_message(message): await bot.process_commands(message) -# Ajout de la commande /info -@bot.tree.command(name="info", description="Affiche l'uptime du bot, le cluster et la shard") +@bot.command(name="ping", help="Renvoie la latence du bot.") +async def ping(ctx): + logger.info(f"Commande ping utilisée sur {ctx.guild.id}") + await ctx.send(f"Pong ! Latence: {round(bot.latency * 1000)}ms") + +@bot.tree.command(name="info", description="Affiche le cluster, la shard et l'uptime du bot.") async def info(interaction: discord.Interaction): - uptime = datetime.now() - start_time - data = await data_manager.load_data() + logger.info(f"Commande info utilisée par {interaction.user}") + data = load_data() + cluster = data.get("info", {}).get("cluster", "N/A") shard = data.get("info", {}).get("shard", "N/A") - await interaction.response.send_message( - f"Uptime: {str(timedelta(seconds=uptime.seconds))}\nCluster: {cluster}\nShard: {shard}" - ) + uptime = get_uptime() + + embed = discord.Embed(title="Informations du Bot", color=discord.Color.green()) + embed.add_field(name="Cluster", value=cluster, inline=True) + embed.add_field(name="Shard", value=shard, inline=True) + embed.add_field(name="Uptime", value=uptime, inline=True) + + await interaction.response.send_message(embed=embed) + +@bot.tree.command(name="leaderboard_server", description="Affiche le classement des utilisateurs avec le plus de 69 sur ce serveur.") +async def leaderboard_server(interaction: discord.Interaction): + logger.info(f"Commande leaderboard_server utilisée sur {interaction.guild.id}") + data = load_data() + guild_id = str(interaction.guild.id) + + if guild_id not in data['stats']: + logger.warning(f"Aucune donnée pour le serveur {guild_id}") + await interaction.response.send_message("Aucune donnée disponible pour ce serveur.") + return + + leaderboard = sorted(data['stats'][guild_id].items(), key=lambda x: x[1]['count_69'], reverse=True) + embed = discord.Embed(title="Classement des utilisateurs avec le plus de 69 sur ce serveur", color=discord.Color.blue()) + + for user_id, stats in leaderboard[:10]: + user = await bot.fetch_user(int(user_id)) + embed.add_field(name=user.display_name, value=f"{stats['count_69']} fois", inline=False) + + await interaction.response.send_message(embed=embed) + +@bot.tree.command(name="leaderboard_global", description="Affiche le classement global des utilisateurs avec le plus de 69.") +async def leaderboard_global(interaction: discord.Interaction): + logger.info(f"Commande leaderboard_global utilisée par {interaction.user}") + data = load_data() + global_stats = {} + + for guild_stats in data['stats'].values(): + for user_id, stats in guild_stats.items(): + if user_id not in global_stats: + global_stats[user_id] = 0 + global_stats[user_id] += stats['count_69'] + + leaderboard = sorted(global_stats.items(), key=lambda x: x[1], reverse=True) + embed = discord.Embed(title="Classement global des utilisateurs avec le plus de 69", color=discord.Color.gold()) + + for user_id, count in leaderboard[:10]: + user = await bot.fetch_user(int(user_id)) + embed.add_field(name=user.display_name, value=f"{count} fois", inline=False) + + await interaction.response.send_message(embed=embed) + +@bot.tree.command(name="config", description="Affiche ou configure les paramètres du bot.") +async def config(interaction: discord.Interaction): + logger.info(f"Commande config utilisée sur {interaction.guild.id}") + data = load_data() + guild_id = str(interaction.guild.id) + config = get_guild_config(guild_id, data) + + embed = discord.Embed(title="Configuration du Bot", color=discord.Color.blue()) + embed.add_field(name="Envoi des messages", value="Public" if config['send_public'] else "Privé", inline=True) + embed.add_field(name="Envoi des messages activé", value="Oui" if config['send_message'] else "Non", inline=True) + embed.add_field(name="Réactions activées", value="Oui" if config['enable_reactions'] else "Non", inline=True) + await interaction.response.send_message(embed=embed) + +@bot.tree.command(name="set_send_public", description="Configure si les messages doivent être envoyés en public ou en privé.") +@app_commands.describe(send_public="Définir si les messages sont envoyés en public (True) ou en privé (False).") +async def set_send_public(interaction: discord.Interaction, send_public: bool): + logger.info(f"Modification de send_public à {send_public} sur {interaction.guild.id}") + data = load_data() + guild_id = str(interaction.guild.id) + config = get_guild_config(guild_id, data) + config['send_public'] = send_public + save_data(data) + await interaction.response.send_message(f"Envoi des messages {'public' if send_public else 'privé'} configuré.") + +@bot.tree.command(name="set_send_message", description="Active ou désactive l'envoi des messages.") +@app_commands.describe(send_message="Définir si l'envoi des messages est activé (True) ou désactivé (False).") +async def set_send_message(interaction: discord.Interaction, send_message: bool): + logger.info(f"Modification de send_message à {send_message} sur {interaction.guild.id}") + data = load_data() + guild_id = str(interaction.guild.id) + config = get_guild_config(guild_id, data) + config['send_message'] = send_message + save_data(data) + await interaction.response.send_message(f"Envoi des messages {'activé' if send_message else 'désactivé'}.") + +@bot.tree.command(name="set_enable_reactions", description="Active ou désactive les réactions.") +@app_commands.describe(enable_reactions="Activer ou désactiver les réactions (True ou False).") +async def set_enable_reactions(interaction: discord.Interaction, enable_reactions: bool): + logger.info(f"Modification de enable_reactions à {enable_reactions} sur {interaction.guild.id}") + data = load_data() + guild_id = str(interaction.guild.id) + config = get_guild_config(guild_id, data) + config['enable_reactions'] = enable_reactions + save_data(data) + await interaction.response.send_message(f"Réactions {'activées' if enable_reactions else 'désactivées'}.") @bot.event async def on_command_error(ctx, error):