From 1a5dc475c06464c67014f88118b1a0d9d9618aea Mon Sep 17 00:00:00 2001 From: root Date: Sun, 20 Apr 2025 22:08:44 +0200 Subject: [PATCH] Major update, v1.0 i guess ? --- .gitignore | 1 + bot.py | 132 +++++++++++++++++++++++++++++++++-------------------- family.py | 69 +++++++++++----------------- tree.py | 108 +++++++++++++++++++++++++++++-------------- 4 files changed, 182 insertions(+), 128 deletions(-) diff --git a/.gitignore b/.gitignore index 22cf556..c8e3bfb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ avatars/* arbre.png config.json temp_avatars/* +family.hash __pycache__ diff --git a/bot.py b/bot.py index 0463c35..ca99b59 100644 --- a/bot.py +++ b/bot.py @@ -69,7 +69,6 @@ async def enregistrer(interaction: discord.Interaction): "✅ 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 @@ -84,7 +83,6 @@ async def adopter(interaction: discord.Interaction, enfant: discord.Member): await interaction.response.send_message("❌ Auto-adoption impossible !", ephemeral=True) return - # Nouvelle condition plus permissive if enfant_gen > parent_gen: await interaction.response.send_message( "❌ Impossible d'adopter quelqu'un de plus ancien que vous !", @@ -92,6 +90,15 @@ async def adopter(interaction: discord.Interaction, enfant: discord.Member): ) 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) @@ -99,43 +106,22 @@ async def adopter(interaction: discord.Interaction, enfant: discord.Member): # 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", + "❌ Erreur lors de l'adoption (peut-être une relation existante ?)", ephemeral=True ) return - # Attribue le rôle approprié - generation = family.get_generation(str(enfant.id)) - role_id = config["ROLE_IDS"].get( - "ENFANT" if generation == 1 else - "PETIT_ENFANT" if generation == 2 else - "ARRIERE_PETIT_ENFANT" - ) + # Message de succès + message = f"✅ {parent.mention} a adopté {enfant.mention} !" - role = interaction.guild.get_role(role_id) - if role: - try: - await enfant.add_roles(role) - 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}" - # 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) - except discord.Forbidden: - await interaction.response.send_message( - "❌ Permission refusée pour attribuer le rôle !", - ephemeral=True - ) - else: - await interaction.response.send_message( - "❌ Rôle introuvable ! Contactez un administrateur.", - ephemeral=True - ) + await interaction.response.send_message(message) @bot.tree.command(name="racine", description="Définir un membre comme racine") @commands.has_permissions(administrator=True) @@ -173,7 +159,8 @@ async def couple(interaction: discord.Interaction, partenaire: discord.Member): ephemeral=True ) -@bot.tree.command(name="renier", description="Renier et supprimer un enfant") + +@bot.tree.command(name="renier", description="Renier un enfant") async def renier(interaction: discord.Interaction, enfant: discord.Member): parent = interaction.user @@ -191,13 +178,13 @@ async def renier(interaction: discord.Interaction, enfant: discord.Member): ) else: await interaction.response.send_message( - "❌ Erreur lors de la suppression", + "❌ Erreur lors de la suppression du membre", ephemeral=True ) else: - # Cas où l'enfant a d'autres parents + remaining_parents = ", ".join([f"<@{p['id']}>" for p in parents_restants]) await interaction.response.send_message( - f"⚠️ {enfant.mention} a encore des parents ({len(parents_restants)}). " + f"⚠️ {enfant.mention} a encore des parents ({remaining_parents}). " f"Seul le lien avec {parent.mention} a été rompu." ) @@ -205,26 +192,51 @@ async def renier(interaction: discord.Interaction, enfant: discord.Member): async def arbre(interaction: discord.Interaction): await interaction.response.defer() - # Télécharge les avatars des membres - 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 + from tree import get_file_hash # Import direct de la fonction - # Génère l'arbre - generate_tree("family.json", "arbre.png", config["ROOT_MEMBER_ID"]) + json_file = "family.json" + png_file = "arbre.png" + hash_file = "family.hash" - # Envoie le résultat try: - await interaction.followup.send(file=discord.File("arbre.png")) - except: + # 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): @@ -243,6 +255,26 @@ async def parent(interaction: discord.Interaction, membre: Optional[discord.Memb 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): diff --git a/family.py b/family.py index d47285e..d644844 100644 --- a/family.py +++ b/family.py @@ -27,23 +27,6 @@ class FamilyManager: with open(self.file_path, "w") as f: json.dump(data, f, indent=2) - def set_as_root(self, member_id: str, is_root: bool = True) -> bool: - """Définit un membre comme racine ou non""" - data = self._load() - if str(member_id) not in data["members"]: - return False - - if "roots" not in data: - data["roots"] = [] - - if is_root and str(member_id) not in data["roots"]: - data["roots"].append(str(member_id)) - elif not is_root and str(member_id) in data["roots"]: - data["roots"].remove(str(member_id)) - - self._save(data) - return True - def add_member(self, member_id: str, member_name: str, avatar_url: str = None): """ Ajoute ou met à jour un membre @@ -88,7 +71,7 @@ class FamilyManager: }) # Ajoute le partenaire comme parent secondaire si existe - partner_id = self.get_partner(str(parent_id)) + partner_id = self.get_partner(parent_id) if partner_id: partner_name = data["members"].get(partner_id, {}).get("name", "Partenaire") data["members"][str(child_id)]["parents"].append({ @@ -141,6 +124,31 @@ class FamilyManager: self._save(data) return changed + def remove_member(self, member_id: str) -> bool: + """Supprime complètement un membre de la famille""" + data = self._load() + member_id = str(member_id) + + if member_id not in data["members"]: + return False + + # Supprime des couples + data["couples"] = [c for c in data["couples"] if member_id not in c] + + # Supprime des racines + if "roots" in data and member_id in data["roots"]: + data["roots"].remove(member_id) + + # Supprime le membre + del data["members"][member_id] + + # Nettoie les références parentales + for m in data["members"].values(): + m["parents"] = [p for p in m["parents"] if p["id"] != member_id] + + self._save(data) + return True + def get_member(self, member_id: str) -> Optional[Dict]: """Récupère les informations d'un membre""" data = self._load() @@ -192,28 +200,3 @@ class FamilyManager: """Liste toutes les racines""" data = self._load() return data.get("roots", []) - -def remove_member(self, member_id: str) -> bool: - """Supprime complètement un membre de la famille""" - data = self._load() - member_id = str(member_id) - - if member_id not in data["members"]: - return False - - # Supprime des couples - data["couples"] = [c for c in data["couples"] if member_id not in c] - - # Supprime des racines - if "roots" in data and member_id in data["roots"]: - data["roots"].remove(member_id) - - # Supprime le membre - del data["members"][member_id] - - # Nettoie les références parentales - for m in data["members"].values(): - m["parents"] = [p for p in m["parents"] if p["id"] != member_id] - - self._save(data) - return True diff --git a/tree.py b/tree.py index 90a3612..7cc4af9 100644 --- a/tree.py +++ b/tree.py @@ -1,8 +1,17 @@ import pygraphviz as pgv import json import os +import hashlib from PIL import Image, ImageDraw, ImageFont, ImageOps +def get_file_hash(filename: str) -> str: + """Calcule le hash SHA-256 d'un fichier""" + hash_sha256 = hashlib.sha256() + with open(filename, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + hash_sha256.update(chunk) + return hash_sha256.hexdigest() + def create_avatar_with_name(user_id, name, avatar_path, output_path): """Crée une image combinant avatar rond et pseudo en dessous""" try: @@ -81,48 +90,77 @@ def generate_tree(input_file, output_file, root_id=None): else: G.add_node(user_id, label=info["name"], **{**node_style, "fontcolor": "white"}) - # 2. Identification des niveaux - top_level = set() + # 2. Calcul des générations avec la nouvelle logique + generations = {} - # Ajout des racines explicites + # a. Identifier les racines if root_id: - top_level.add(root_id) + root_members = [root_id] else: - for root in data.get("roots", []): - top_level.add(root) + root_members = data.get("roots", [k for k,v in data["members"].items() if not v.get("parents")]) - # Ajout des couples sans parents - for couple in data.get("couples", []): - if all(m in data["members"] and not data["members"][m].get("parents") for m in couple): - top_level.update(couple) - - # 3. Création des sous-graphes hiérarchiques - if top_level: - G.add_subgraph(top_level, name="rank_top", rank="same") - - # Niveau des enfants directs - children_level = set() - for user_id, info in data["members"].items(): - parents = info.get("parents", []) - if any(p["id"] in top_level for p in parents): - children_level.add(user_id) + # b. Première passe: membres sans parents = gen 0 + for user_id in data["members"]: + if not data["members"][user_id].get("parents"): + generations[user_id] = 0 - if children_level: - G.add_subgraph(children_level, name="rank_children", rank="same") - - # 4. Gestion des couples + # c. Deuxième passe: aligner les couples for couple in data.get("couples", []): - if all(m in data["members"] for m in couple): - member1, member2 = couple - - # Lien conjugal - G.add_edge(member1, member2, **couple_style) - - # Alignement forcé pour les couples racines - if member1 in top_level and member2 in top_level: - G.add_subgraph(couple, name=f"couple_{member1}_{member2}", rank="same") + m1, m2 = couple + if m1 in generations and m2 in generations: + gen = min(generations[m1], generations[m2]) + generations[m1] = gen + generations[m2] = gen + + # d. Troisième passe: calcul descendant + changed = True + while changed: + changed = False + for user_id in data["members"]: + if user_id in generations: + continue + + parents = data["members"][user_id].get("parents", []) + if parents: + parent_gens = [generations[p["id"]] for p in parents if p["id"] in generations] + if parent_gens: + new_gen = min(parent_gens) + 1 + generations[user_id] = new_gen + changed = True + + # Aligner avec partenaire + partner_id = next((c[0] if c[1]==user_id else c[1] for c in data.get("couples",[]) if user_id in c), None) + if partner_id and partner_id in data["members"]: + generations[partner_id] = new_gen - # 5. Liens parent-enfant + # 3. Organisation des niveaux + max_gen = max(generations.values()) if generations else 0 + + # Niveau 0: Uniquement les racines + roots = [m for m in root_members if m in data["members"]] + if roots: + G.add_subgraph(roots, name="rank0", rank="same") + + # Niveaux suivants + for gen in range(1, max_gen + 1): + members = [m for m,g in generations.items() if g == gen] + + # Séparer les couples + couples = [] + for couple in data.get("couples", []): + if all(m in members for m in couple): + couples.append(couple) + members = [m for m in members if m not in couple] + + # Créer les sous-graphes + if members: + G.add_subgraph(members, name=f"rank{gen}", rank="same") + + for couple in couples: + G.add_subgraph(couple, name=f"couple_{couple[0]}_{couple[1]}", rank="same") + G.add_edge(couple[0], couple[1], **couple_style) + + # 4. Liens parent-enfant for user_id, info in data["members"].items(): for parent in info.get("parents", []): if parent["id"] in data["members"]: