From 5a9e9b52d6cd48e0ef56b44dc418ec695b4d3c7e Mon Sep 17 00:00:00 2001 From: Mathis Date: Thu, 18 Jun 2026 22:12:01 +0200 Subject: [PATCH] Ajout: Syncronisation automatique des pp discord des agent O.K sur le site via Kuby --- .gitignore | 5 +- commandes/team_json.py | 176 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 commandes/team_json.py diff --git a/.gitignore b/.gitignore index 21803b9..649fd36 100755 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,7 @@ logs/ .vscode/ .idea/ .DS_Store -.roo/ \ No newline at end of file +.roo/ +.continue/mcpServers/new-mcp-server.yaml +.continue/mcpServers/new-mcp-server-1.yaml +.gitignore diff --git a/commandes/team_json.py b/commandes/team_json.py new file mode 100644 index 0000000..0ee9bbb --- /dev/null +++ b/commandes/team_json.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +""" +Team JSON Generator +=================== +Ce module parcourt les membres d'un serveur Discord, filtre ceux qui possèdent +un ou plusieurs rôles spécifiés, télécharge leurs avatars et génère un fichier +JSON compatible avec le site de production. + +Le JSON attendu ressemble à : +[ + { + "id": "123456789012345678", + "name": "Nom affiché", + "role": "Nom du rôle filtré", + "avatar": "123456789012345678.png", + "bio": "", + "stats": [], + "skills": [], + "social": {"discord":"","twitter":"",...} + }, + ... +] + +Le champ ``avatar`` correspond au nom de fichier stocké dans ``website/avatars/``. +""" + +import os +import json +import aiohttp +from pathlib import Path +from typing import List +import disnake +from disnake.ext import commands, tasks + +# ---------------------------------------------------------------------- +# Configuration – variables d'environnement (dans le .env du projet) +# ---------------------------------------------------------------------- +TARGET_GUILD_ID = os.getenv("TARGET_GUILD_ID") # ID du serveur contenant l'équipe +# Liste d'IDs de rôles séparés par des virgules (ex. "123,456,789") +TEAM_ROLE_IDS = os.getenv("TEAM_ROLE_IDS", "").split(",") if os.getenv("TEAM_ROLE_IDS") else [] + +# Dossiers où seront stockés les avatars et le JSON final +AVATAR_ROOT = Path(__file__).parents[2] / "website" / "avatars" +AVATAR_ROOT.mkdir(parents=True, exist_ok=True) + +JSON_OUTPUT = Path(__file__).parents[2] / "website" / "team.json" + +# ---------------------------------------------------------------------- +class TeamJSON(commands.Cog): + """Cog responsable de la génération du JSON d'équipe et du téléchargement des avatars.""" + + def __init__(self, bot: commands.Bot): + self.bot = bot + # Démarre la tâche quotidienne (synchronisation 24h/24) + self.sync_daily.start() + + # ------------------------------------------------------------------ + @tasks.loop(hours=24) + async def sync_daily(self): + """Tâche planifiée exécutée chaque jour (cron).""" + await self._generate_team(trigger="cron") + + @sync_daily.before_loop + async def before_sync(self): + await self.bot.wait_until_ready() + + # ------------------------------------------------------------------ + async def _download_avatar(self, session: aiohttp.ClientSession, member: disnake.Member) -> str: + """Télécharge l'avatar du *member* et le sauvegarde dans ``AVATAR_ROOT``. + Retourne le nom de fichier (ex. ``"123456789.png"``).""" + avatar_url = str(member.display_avatar.url) + filename = f"{member.id}.png" + dest_path = AVATAR_ROOT / filename + try: + async with session.get(avatar_url) as resp: + if resp.status == 200: + dest_path.write_bytes(await resp.read()) + else: + self.bot.logger.warning( + f"[TeamJSON] Avatar download failed for {member.id} – HTTP {resp.status}" + ) + except Exception as exc: + self.bot.logger.error(f"[TeamJSON] Exception while downloading avatar {member.id}: {exc}") + return filename + + # ------------------------------------------------------------------ + async def _generate_team(self, *, trigger: str = "manual") -> None: + """Construit le JSON de l'équipe et télécharge les avatars. + + Le JSON produit suit exactement le format attendu par le site de production. + Les champs ``stats``, ``skills`` et ``social`` sont laissés vides car ils ne + sont pas disponibles via l'API bot. + """ + if not TARGET_GUILD_ID: + self.bot.logger.error("[TeamJSON] TARGET_GUILD_ID non défini – arrêt du générateur.") + return + if not TEAM_ROLE_IDS: + self.bot.logger.error("[TeamJSON] TEAM_ROLE_IDS non défini – arrêt du générateur.") + return + + # Récupération de la guilde + guild = self.bot.get_guild(int(TARGET_GUILD_ID)) + if not guild: + try: + guild = await self.bot.fetch_guild(int(TARGET_GUILD_ID)) + except Exception as exc: + self.bot.logger.error(f"[TeamJSON] Impossible de récupérer la guilde {TARGET_GUILD_ID}: {exc}") + return + + # Convertir les IDs de rôle en entiers pour la comparaison + role_ids = {int(r) for r in TEAM_ROLE_IDS if r.strip()} + members_data: List[dict] = [] + + async with aiohttp.ClientSession() as session: + async for member in guild.fetch_members(limit=None): + if member.bot: + continue + # Intersection entre les rôles du membre et les rôles ciblés + member_role_ids = {r.id for r in member.roles} + intersect = member_role_ids.intersection(role_ids) + if not intersect: + continue # le membre ne possède aucun des rôles recherchés + + # On prend le premier rôle correspondant comme "role" dans le JSON + role_name = next((r.name for r in member.roles if r.id in intersect), "") + avatar_file = await self._download_avatar(session, member) + + entry = { + "id": str(member.id), + "name": member.display_name, + "role": role_name, + "avatar": avatar_file, + "bio": "", # non récupérable via l'API bot + "stats": [], + "skills": [], + "social": { + "discord": "", + "twitter": "", + "instagram": "", + "youtube": "", + "tiktok": "", + "github": "", + }, + } + members_data.append(entry) + + # Écriture du JSON final + try: + JSON_OUTPUT.parent.mkdir(parents=True, exist_ok=True) + JSON_OUTPUT.write_text( + json.dumps(members_data, indent=2, ensure_ascii=False), + encoding="utf-8", + ) + self.bot.logger.info( + f"[TeamJSON] JSON généré ({len(members_data)} membres) → {JSON_OUTPUT}" + ) + except Exception as exc: + self.bot.logger.error(f"[TeamJSON] Échec de l'écriture du JSON : {exc}") + + # ------------------------------------------------------------------ + @commands.slash_command( + name="generate_team_json", + description="Génère le JSON de l'équipe (admin only)" + ) + @commands.has_permissions(administrator=True) + async def generate_team_json(self, inter: disnake.ApplicationCommandInteraction): + """Commande manuelle pour (re)générer le JSON et les avatars.""" + await inter.response.defer(ephemeral=True) + await self._generate_team(trigger="manual") + await inter.edit_original_response( + content="✅ JSON de l'équipe généré et avatars synchronisés." + ) + +# ---------------------------------------------------------------------- +def setup(bot: commands.Bot): + bot.add_cog(TeamJSON(bot))