Major update, v1.0 i guess ?
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,4 +4,5 @@ avatars/*
|
|||||||
arbre.png
|
arbre.png
|
||||||
config.json
|
config.json
|
||||||
temp_avatars/*
|
temp_avatars/*
|
||||||
|
family.hash
|
||||||
__pycache__
|
__pycache__
|
||||||
|
104
bot.py
104
bot.py
@ -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,23 +106,12 @@ 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))
|
|
||||||
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)
|
|
||||||
if role:
|
|
||||||
try:
|
|
||||||
await enfant.add_roles(role)
|
|
||||||
message = f"✅ {parent.mention} a adopté {enfant.mention} !"
|
message = f"✅ {parent.mention} a adopté {enfant.mention} !"
|
||||||
|
|
||||||
# Mentionne le partenaire si existe
|
# Mentionne le partenaire si existe
|
||||||
@ -126,16 +122,6 @@ async def adopter(interaction: discord.Interaction, enfant: discord.Member):
|
|||||||
message += f"\n👫 Partenaire : {partner.mention}"
|
message += f"\n👫 Partenaire : {partner.mention}"
|
||||||
|
|
||||||
await interaction.response.send_message(message)
|
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,7 +192,27 @@ 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
|
||||||
|
|
||||||
|
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()
|
members = family.get_all_members()
|
||||||
for member_id in members:
|
for member_id in members:
|
||||||
try:
|
try:
|
||||||
@ -214,17 +221,22 @@ async def arbre(interaction: discord.Interaction):
|
|||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Génère l'arbre
|
# Générer le nouvel arbre
|
||||||
generate_tree("family.json", "arbre.png", config["ROOT_MEMBER_ID"])
|
generate_tree(json_file, png_file, config["ROOT_MEMBER_ID"])
|
||||||
|
|
||||||
# Envoie le résultat
|
# Sauvegarder le hash
|
||||||
try:
|
with open(hash_file, "w") as f:
|
||||||
await interaction.followup.send(file=discord.File("arbre.png"))
|
f.write(current_hash)
|
||||||
except:
|
|
||||||
|
# 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):
|
||||||
|
69
family.py
69
family.py
@ -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
|
|
||||||
|
96
tree.py
96
tree.py
@ -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
|
||||||
|
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", []):
|
for couple in data.get("couples", []):
|
||||||
if all(m in data["members"] for m in couple):
|
if all(m in members for m in couple):
|
||||||
member1, member2 = couple
|
couples.append(couple)
|
||||||
|
members = [m for m in members if m not in couple]
|
||||||
|
|
||||||
# Lien conjugal
|
# Créer les sous-graphes
|
||||||
G.add_edge(member1, member2, **couple_style)
|
if members:
|
||||||
|
G.add_subgraph(members, name=f"rank{gen}", rank="same")
|
||||||
|
|
||||||
# Alignement forcé pour les couples racines
|
for couple in couples:
|
||||||
if member1 in top_level and member2 in top_level:
|
G.add_subgraph(couple, name=f"couple_{couple[0]}_{couple[1]}", rank="same")
|
||||||
G.add_subgraph(couple, name=f"couple_{member1}_{member2}", rank="same")
|
G.add_edge(couple[0], couple[1], **couple_style)
|
||||||
|
|
||||||
# 5. Liens parent-enfant
|
# 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"]:
|
||||||
|
Reference in New Issue
Block a user