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
config.json
temp_avatars/*
family.hash
__pycache__

132
bot.py
View File

@ -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):

View File

@ -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

108
tree.py
View File

@ -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"]: