Ajout: Syncronisation automatique des pp discord des agent O.K sur le site via Kuby
This commit is contained in:
parent
886bfa4b1e
commit
5a9e9b52d6
2 changed files with 180 additions and 1 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -37,3 +37,6 @@ logs/
|
|||
.idea/
|
||||
.DS_Store
|
||||
.roo/
|
||||
.continue/mcpServers/new-mcp-server.yaml
|
||||
.continue/mcpServers/new-mcp-server-1.yaml
|
||||
.gitignore
|
||||
|
|
|
|||
176
commandes/team_json.py
Normal file
176
commandes/team_json.py
Normal file
|
|
@ -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))
|
||||
Loading…
Add table
Add a link
Reference in a new issue