import discord from discord.ext import commands import json import aiohttp import os from dotenv import load_dotenv from family import FamilyManager from tree import generate_tree from typing import Optional # Configuration initiale load_dotenv() TOKEN = os.getenv("DISCORD_TOKEN") with open("config.json") as f: config = json.load(f) intents = discord.Intents.default() intents.members = True intents.message_content = True bot = commands.Bot(command_prefix="!", intents=intents) family = FamilyManager("family.json") async def download_avatar(member: discord.Member) -> Optional[str]: """Télécharge et enregistre l'avatar d'un membre""" if not member.avatar: return None avatar_dir = "avatars" os.makedirs(avatar_dir, exist_ok=True) avatar_path = f"{avatar_dir}/{member.id}.png" try: async with aiohttp.ClientSession() as session: async with session.get(str(member.avatar.url)) as resp: if resp.status == 200: with open(avatar_path, "wb") as f: f.write(await resp.read()) return avatar_path except Exception as e: print(f"Erreur de téléchargement avatar {member.id}: {e}") return None async def update_member(member: discord.Member): """Met à jour les infos d'un membre dans la famille""" avatar_url = str(member.avatar.url) if member.avatar else None family.add_member( str(member.id), member.display_name, avatar_url ) await download_avatar(member) @bot.event async def on_ready(): print(f"Connecté en tant que {bot.user} (ID: {bot.user.id})") try: synced = await bot.tree.sync() print(f"Commandes synchronisées ({len(synced)}): {[c.name for c in synced]}") except Exception as e: print(f"Erreur synchronisation commandes: {e}") @bot.tree.command(name="enregistrer", description="Enregistre ou met à jour son profil") async def enregistrer(interaction: discord.Interaction): """Commande pour que les membres enregistrent leurs infos""" await update_member(interaction.user) await interaction.response.send_message( "✅ Profil mis à jour avec succès !", ephemeral=True ) @bot.tree.command(name="adopter", description="Adopter un membre") async def adopter(interaction: discord.Interaction, enfant: discord.Member): parent = interaction.user # Debug parent_gen = family.get_generation(str(parent.id)) enfant_gen = family.get_generation(str(enfant.id)) print(f"Adoption: {parent.display_name} (gen {parent_gen}) → {enfant.display_name} (gen {enfant_gen})") # Validations if parent.id == enfant.id: await interaction.response.send_message("❌ Auto-adoption impossible !", ephemeral=True) return if enfant_gen > parent_gen: await interaction.response.send_message( "❌ Impossible d'adopter quelqu'un de plus ancien que vous !", ephemeral=True ) return # Vérification relation existante existing_parents = family.get_parents(str(enfant.id)) if any(p["id"] == str(parent.id) for p in existing_parents): await interaction.response.send_message( "❌ Vous avez déjà adopté cette personne !", ephemeral=True ) return # Met à jour les infos des membres await update_member(parent) await update_member(enfant) # Crée le lien familial if not family.add_child(str(parent.id), str(enfant.id)): await interaction.response.send_message( "❌ Erreur lors de l'adoption (peut-être une relation existante ?)", ephemeral=True ) return # Message de succès message = f"✅ {parent.mention} a adopté {enfant.mention} !" # Mentionne le partenaire si existe partner_id = family.get_partner(str(parent.id)) if partner_id: partner = interaction.guild.get_member(int(partner_id)) if partner: message += f"\n👫 Partenaire : {partner.mention}" await interaction.response.send_message(message) @bot.tree.command(name="racine", description="Définir un membre comme racine") @commands.has_permissions(administrator=True) async def racine(interaction: discord.Interaction, membre: discord.Member, est_racine: bool): success = family.set_as_root(str(membre.id), est_racine) await interaction.response.send_message( f"✅ {membre.mention} est maintenant {'une racine' if est_racine else 'plus une racine'} !" if success else "❌ Erreur", ephemeral=True ) @bot.tree.command(name="couple", description="Officialiser une relation") async def couple(interaction: discord.Interaction, partenaire: discord.Member): membre = interaction.user # Validations if membre.id == partenaire.id: await interaction.response.send_message( "❌ Impossible de former un couple avec soi-même !", ephemeral=True ) return # Met à jour les infos await update_member(membre) await update_member(partenaire) if family.add_couple(str(membre.id), str(partenaire.id)): await interaction.response.send_message( f"💑 {membre.mention} et {partenaire.mention} sont maintenant partenaires !" ) else: await interaction.response.send_message( "❌ Ces membres sont déjà en couple !", ephemeral=True ) @bot.tree.command(name="renier", description="Renier un enfant") async def renier(interaction: discord.Interaction, enfant: discord.Member): parent = interaction.user # Supprime d'abord le lien parental family.remove_child(str(parent.id), str(enfant.id)) # Vérifie si l'enfant n'a plus aucun parent parents_restants = family.get_parents(str(enfant.id)) if not parents_restants: # Suppression complète if family.remove_member(str(enfant.id)): await interaction.response.send_message( f"❌ {parent.mention} a renié et supprimé {enfant.mention} de la famille !" ) else: await interaction.response.send_message( "❌ Erreur lors de la suppression du membre", ephemeral=True ) else: remaining_parents = ", ".join([f"<@{p['id']}>" for p in parents_restants]) await interaction.response.send_message( f"⚠️ {enfant.mention} a encore des parents ({remaining_parents}). " f"Seul le lien avec {parent.mention} a été rompu." ) @bot.tree.command(name="arbre", description="Afficher l'arbre généalogique") async def arbre(interaction: discord.Interaction): await interaction.response.defer() from tree import get_file_hash # Import direct de la fonction json_file = "family.json" png_file = "arbre.png" hash_file = "family.hash" try: # Vérifier si la régénération est nécessaire current_hash = get_file_hash(json_file) regenerate = True if os.path.exists(png_file): try: with open(hash_file, "r") as f: if current_hash == f.read(): regenerate = False except FileNotFoundError: pass if regenerate: # Télécharger les avatars members = family.get_all_members() for member_id in members: try: member = await interaction.guild.fetch_member(int(member_id)) await download_avatar(member) except: continue # Générer le nouvel arbre generate_tree(json_file, png_file, config["ROOT_MEMBER_ID"]) # Sauvegarder le hash with open(hash_file, "w") as f: f.write(current_hash) # Envoyer l'image await interaction.followup.send(file=discord.File(png_file)) except Exception as e: await interaction.followup.send( "❌ Erreur lors de la génération de l'arbre", ephemeral=True ) print(f"Erreur génération arbre: {e}") @bot.tree.command(name="parent", description="Voir les parents d'un membre") async def parent(interaction: discord.Interaction, membre: Optional[discord.Member] = None): target = membre or interaction.user parents = family.get_parents(str(target.id)) if not parents: await interaction.response.send_message( f"ℹ️ {target.display_name} n'a pas de parent enregistré.", ephemeral=True ) return parent_mentions = [f"<@{p['id']}>" for p in parents] await interaction.response.send_message( f"👪 Parents de {target.mention} : {' '.join(parent_mentions)}" ) @bot.tree.command(name="enfants", description="Lister ses enfants") async def enfants(interaction: discord.Interaction): """Commande pour lister ses enfants et son partenaire""" membre_id = str(interaction.user.id) # Récupération du partenaire partner_id = family.get_partner(membre_id) partner_mention = f"<@{partner_id}>" if partner_id else "Aucun" # Récupération des enfants enfants = family.get_children(membre_id) enfants_mentions = [f"<@{enfant_id}>" for enfant_id in enfants] if enfants else ["Aucun"] await interaction.response.send_message( f"👨‍👩‍👧‍👦 **Famille de {interaction.user.mention}**\n" f"• Partenaire : {partner_mention}\n" f"• Enfant(s) : {' '.join(enfants_mentions)}", ephemeral=True ) @bot.command() @commands.has_permissions(administrator=True) async def init(ctx): """Initialise tous les membres du serveur (admin seulement)""" count = 0 for member in ctx.guild.members: await update_member(member) count += 1 await ctx.send(f"✅ {count} membres initialisés !") bot.run(TOKEN)