Merge branch 'dev' into 'main'
Dev See merge request Omega_Kube/kuby!36
This commit is contained in:
commit
d5167448df
6 changed files with 1412 additions and 202 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -37,3 +37,6 @@ logs/
|
||||||
.idea/
|
.idea/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.roo/
|
.roo/
|
||||||
|
.continue/mcpServers/new-mcp-server.yaml
|
||||||
|
.continue/mcpServers/new-mcp-server-1.yaml
|
||||||
|
.gitignore
|
||||||
|
|
|
||||||
0
TODO.md
0
TODO.md
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))
|
||||||
|
|
@ -127,18 +127,21 @@ class RatingSelect(disnake.ui.Select):
|
||||||
await interaction.response.defer()
|
await interaction.response.defer()
|
||||||
|
|
||||||
class FeedbackModal(disnake.ui.Modal):
|
class FeedbackModal(disnake.ui.Modal):
|
||||||
def __init__(self, view):
|
"""Modal that collects a free‑form comment for a ticket feedback."""
|
||||||
super().__init__(components=[], title="Votre avis")
|
def __init__(self, view: FeedbackView):
|
||||||
self.view = view
|
self.view = view
|
||||||
self.comment = disnake.ui.TextInput(
|
self.comment = disnake.ui.TextInput(
|
||||||
label="Commentaire",
|
label="Commentaire",
|
||||||
style=disnake.TextInputStyle.paragraph,
|
style=disnake.TextInputStyle.paragraph,
|
||||||
placeholder="Dites-nous ce que vous avez pensé du support...",
|
placeholder="Dites-nous ce que vous avez pensé du support...",
|
||||||
required=True,
|
required=True,
|
||||||
max_length=1000
|
max_length=1000,
|
||||||
)
|
)
|
||||||
self.add_item(self.comment)
|
super().__init__(title="Votre avis", components=[self.comment])
|
||||||
|
|
||||||
async def callback(self, interaction: disnake.Interaction):
|
async def callback(self, interaction: disnake.Interaction):
|
||||||
self.view.comment = self.comment.value
|
self.view.comment = self.comment.value
|
||||||
await interaction.response.send_message("Commentaire enregistré ! N'oubliez pas de valider avec 'Envoyer'.", ephemeral=True)
|
await interaction.response.send_message(
|
||||||
|
"Commentaire enregistré ! N'oubliez pas de valider avec « Envoyer ».",
|
||||||
|
ephemeral=True,
|
||||||
|
)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
9
config/whitelist_questions.json
Normal file
9
config/whitelist_questions.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"questions": [
|
||||||
|
{"label": "Pseudo Epic (Fortnite)", "custom_id": "pseudo", "placeholder": "JohnDoe", "required": true, "style": "short", "max_length": 16},
|
||||||
|
{"label": "Âge", "custom_id": "age", "placeholder": "18", "required": true, "style": "short", "max_length": 2},
|
||||||
|
{"label": "Expérience RP ?", "custom_id": "experience", "placeholder": "Oui/Non", "required": true, "style": "paragraph", "max_length": 50},
|
||||||
|
{"label": "Comment as‑tu découvert le serveur ?", "custom_id": "decouverte", "placeholder": "", "required": true, "style": "short", "max_length": 20},
|
||||||
|
{"label": "As‑tu un micro de qualité ?", "custom_id": "micro", "placeholder": "", "required": true, "style": "short", "max_length": 3}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue