Major update, v1.0 i guess ?
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,4 +4,5 @@ avatars/*
|
||||
arbre.png
|
||||
config.json
|
||||
temp_avatars/*
|
||||
family.hash
|
||||
__pycache__
|
||||
|
132
bot.py
132
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):
|
||||
|
69
family.py
69
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
|
||||
|
108
tree.py
108
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"]:
|
||||
|
Reference in New Issue
Block a user