176 lines
7.1 KiB
Python
176 lines
7.1 KiB
Python
# -*- 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))
|