Major update, v1.0 i guess ?

This commit is contained in:
root
2025-04-20 22:08:44 +02:00
parent 6e2c321abe
commit 1a5dc475c0
4 changed files with 182 additions and 128 deletions

1
.gitignore vendored
View File

@ -4,4 +4,5 @@ avatars/*
arbre.png arbre.png
config.json config.json
temp_avatars/* temp_avatars/*
family.hash
__pycache__ __pycache__

132
bot.py
View File

@ -69,7 +69,6 @@ async def enregistrer(interaction: discord.Interaction):
"✅ Profil mis à jour avec succès !", "✅ Profil mis à jour avec succès !",
ephemeral=True ephemeral=True
) )
@bot.tree.command(name="adopter", description="Adopter un membre") @bot.tree.command(name="adopter", description="Adopter un membre")
async def adopter(interaction: discord.Interaction, enfant: discord.Member): async def adopter(interaction: discord.Interaction, enfant: discord.Member):
parent = interaction.user 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) await interaction.response.send_message("❌ Auto-adoption impossible !", ephemeral=True)
return return
# Nouvelle condition plus permissive
if enfant_gen > parent_gen: if enfant_gen > parent_gen:
await interaction.response.send_message( await interaction.response.send_message(
"❌ Impossible d'adopter quelqu'un de plus ancien que vous !", "❌ Impossible d'adopter quelqu'un de plus ancien que vous !",
@ -92,6 +90,15 @@ async def adopter(interaction: discord.Interaction, enfant: discord.Member):
) )
return 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 # Met à jour les infos des membres
await update_member(parent) await update_member(parent)
await update_member(enfant) await update_member(enfant)
@ -99,43 +106,22 @@ async def adopter(interaction: discord.Interaction, enfant: discord.Member):
# Crée le lien familial # Crée le lien familial
if not family.add_child(str(parent.id), str(enfant.id)): if not family.add_child(str(parent.id), str(enfant.id)):
await interaction.response.send_message( await interaction.response.send_message(
"❌ Erreur lors de l'adoption", "❌ Erreur lors de l'adoption (peut-être une relation existante ?)",
ephemeral=True ephemeral=True
) )
return return
# Attribue le rôle approprié # Message de succès
generation = family.get_generation(str(enfant.id)) message = f"{parent.mention} a adopté {enfant.mention} !"
role_id = config["ROLE_IDS"].get(
"ENFANT" if generation == 1 else
"PETIT_ENFANT" if generation == 2 else
"ARRIERE_PETIT_ENFANT"
)
role = interaction.guild.get_role(role_id) # Mentionne le partenaire si existe
if role: partner_id = family.get_partner(str(parent.id))
try: if partner_id:
await enfant.add_roles(role) partner = interaction.guild.get_member(int(partner_id))
message = f"{parent.mention} a adopté {enfant.mention} !" if partner:
message += f"\n👫 Partenaire : {partner.mention}"
# Mentionne le partenaire si existe await interaction.response.send_message(message)
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
)
@bot.tree.command(name="racine", description="Définir un membre comme racine") @bot.tree.command(name="racine", description="Définir un membre comme racine")
@commands.has_permissions(administrator=True) @commands.has_permissions(administrator=True)
@ -173,7 +159,8 @@ async def couple(interaction: discord.Interaction, partenaire: discord.Member):
ephemeral=True 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): async def renier(interaction: discord.Interaction, enfant: discord.Member):
parent = interaction.user parent = interaction.user
@ -191,13 +178,13 @@ async def renier(interaction: discord.Interaction, enfant: discord.Member):
) )
else: else:
await interaction.response.send_message( await interaction.response.send_message(
"❌ Erreur lors de la suppression", "❌ Erreur lors de la suppression du membre",
ephemeral=True ephemeral=True
) )
else: 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( 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." 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): async def arbre(interaction: discord.Interaction):
await interaction.response.defer() await interaction.response.defer()
# Télécharge les avatars des membres from tree import get_file_hash # Import direct de la fonction
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ère l'arbre json_file = "family.json"
generate_tree("family.json", "arbre.png", config["ROOT_MEMBER_ID"]) png_file = "arbre.png"
hash_file = "family.hash"
# Envoie le résultat
try: try:
await interaction.followup.send(file=discord.File("arbre.png")) # Vérifier si la régénération est nécessaire
except: 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( await interaction.followup.send(
"❌ Erreur lors de la génération de l'arbre", "❌ Erreur lors de la génération de l'arbre",
ephemeral=True ephemeral=True
) )
print(f"Erreur génération arbre: {e}")
@bot.tree.command(name="parent", description="Voir les parents d'un membre") @bot.tree.command(name="parent", description="Voir les parents d'un membre")
async def parent(interaction: discord.Interaction, membre: Optional[discord.Member] = None): 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)}" 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() @bot.command()
@commands.has_permissions(administrator=True) @commands.has_permissions(administrator=True)
async def init(ctx): async def init(ctx):

View File

@ -27,23 +27,6 @@ class FamilyManager:
with open(self.file_path, "w") as f: with open(self.file_path, "w") as f:
json.dump(data, f, indent=2) 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): def add_member(self, member_id: str, member_name: str, avatar_url: str = None):
""" """
Ajoute ou met à jour un membre Ajoute ou met à jour un membre
@ -88,7 +71,7 @@ class FamilyManager:
}) })
# Ajoute le partenaire comme parent secondaire si existe # 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: if partner_id:
partner_name = data["members"].get(partner_id, {}).get("name", "Partenaire") partner_name = data["members"].get(partner_id, {}).get("name", "Partenaire")
data["members"][str(child_id)]["parents"].append({ data["members"][str(child_id)]["parents"].append({
@ -141,6 +124,31 @@ class FamilyManager:
self._save(data) self._save(data)
return changed 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]: def get_member(self, member_id: str) -> Optional[Dict]:
"""Récupère les informations d'un membre""" """Récupère les informations d'un membre"""
data = self._load() data = self._load()
@ -192,28 +200,3 @@ class FamilyManager:
"""Liste toutes les racines""" """Liste toutes les racines"""
data = self._load() data = self._load()
return data.get("roots", []) 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

98
tree.py
View File

@ -1,8 +1,17 @@
import pygraphviz as pgv import pygraphviz as pgv
import json import json
import os import os
import hashlib
from PIL import Image, ImageDraw, ImageFont, ImageOps 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): def create_avatar_with_name(user_id, name, avatar_path, output_path):
"""Crée une image combinant avatar rond et pseudo en dessous""" """Crée une image combinant avatar rond et pseudo en dessous"""
try: try:
@ -81,48 +90,77 @@ def generate_tree(input_file, output_file, root_id=None):
else: else:
G.add_node(user_id, label=info["name"], **{**node_style, "fontcolor": "white"}) G.add_node(user_id, label=info["name"], **{**node_style, "fontcolor": "white"})
# 2. Identification des niveaux # 2. Calcul des générations avec la nouvelle logique
top_level = set() generations = {}
# Ajout des racines explicites # a. Identifier les racines
if root_id: if root_id:
top_level.add(root_id) root_members = [root_id]
else: else:
for root in data.get("roots", []): root_members = data.get("roots", [k for k,v in data["members"].items() if not v.get("parents")])
top_level.add(root)
# Ajout des couples sans parents # 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
# c. Deuxième passe: aligner les couples
for couple in data.get("couples", []): for couple in data.get("couples", []):
if all(m in data["members"] and not data["members"][m].get("parents") for m in couple): m1, m2 = couple
top_level.update(couple) if m1 in generations and m2 in generations:
gen = min(generations[m1], generations[m2])
generations[m1] = gen
generations[m2] = gen
# 3. Création des sous-graphes hiérarchiques # d. Troisième passe: calcul descendant
if top_level: changed = True
G.add_subgraph(top_level, name="rank_top", rank="same") while changed:
changed = False
for user_id in data["members"]:
if user_id in generations:
continue
# Niveau des enfants directs parents = data["members"][user_id].get("parents", [])
children_level = set() if parents:
for user_id, info in data["members"].items(): parent_gens = [generations[p["id"]] for p in parents if p["id"] in generations]
parents = info.get("parents", []) if parent_gens:
if any(p["id"] in top_level for p in parents): new_gen = min(parent_gens) + 1
children_level.add(user_id) generations[user_id] = new_gen
changed = True
if children_level: # Aligner avec partenaire
G.add_subgraph(children_level, name="rank_children", rank="same") 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
# 4. Gestion des couples # 3. Organisation des niveaux
for couple in data.get("couples", []): max_gen = max(generations.values()) if generations else 0
if all(m in data["members"] for m in couple):
member1, member2 = couple
# Lien conjugal # Niveau 0: Uniquement les racines
G.add_edge(member1, member2, **couple_style) roots = [m for m in root_members if m in data["members"]]
if roots:
G.add_subgraph(roots, name="rank0", rank="same")
# Alignement forcé pour les couples racines # Niveaux suivants
if member1 in top_level and member2 in top_level: for gen in range(1, max_gen + 1):
G.add_subgraph(couple, name=f"couple_{member1}_{member2}", rank="same") members = [m for m,g in generations.items() if g == gen]
# 5. Liens parent-enfant # 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 user_id, info in data["members"].items():
for parent in info.get("parents", []): for parent in info.get("parents", []):
if parent["id"] in data["members"]: if parent["id"] in data["members"]: