Migration de Kuby vers disnake

This commit is contained in:
Mathis 2026-05-06 17:07:09 +02:00
parent dc6e235f27
commit c8c579eefe
36 changed files with 1205 additions and 1253 deletions

View file

@ -25,9 +25,8 @@ from datetime import datetime, timezone, timedelta
from pathlib import Path
from typing import Optional
import discord
from discord import app_commands
from discord.ext import commands, tasks
import disnake
from disnake.ext import commands, tasks
from src.json_export_service import (
load_config, save_config, generate_all,
@ -81,8 +80,8 @@ class WebsiteSync(commands.Cog, name="Website Sync"):
# ── Helper : embed de réponse ─────────────────────────────────────────────
@staticmethod
def _embed(title: str, color: discord.Color, description: str = "") -> discord.Embed:
e = discord.Embed(title=title, color=color, timestamp=datetime.now(timezone.utc))
def _embed(title: str, color: disnake.Color, description: str = "") -> disnake.Embed:
e = disnake.Embed(title=title, color=color, timestamp=datetime.now(timezone.utc))
if description:
e.description = description
return e
@ -90,31 +89,30 @@ class WebsiteSync(commands.Cog, name="Website Sync"):
# ══════════════════════════════════════════════════════════════════════════
# /json-generate
# ══════════════════════════════════════════════════════════════════════════
@app_commands.command(name="json-generate", description="Génère manuellement les fichiers JSON du site web")
@app_commands.describe(cible="Quoi générer ? (défaut : tout)")
@app_commands.choices(cible=[
app_commands.Choice(name="Tout (servers + members)", value="all"),
app_commands.Choice(name="Serveurs uniquement", value="servers"),
app_commands.Choice(name="Membres uniquement", value="members"),
])
@app_commands.default_permissions(administrator=True)
async def json_generate(self, interaction: discord.Interaction, cible: str = "all"):
@commands.slash_command(name="json-generate", description="Génère manuellement les fichiers JSON du site web")
@commands.has_permissions(administrator=True)
async def json_generate(self, interaction: disnake.ApplicationCommandInteraction,
cible: str = commands.Param("all", description="Quoi générer ?", choices={
"Tout (servers + members)": "all",
"Serveurs uniquement": "servers",
"Membres uniquement": "members"
})):
await interaction.response.defer(ephemeral=True)
start = datetime.now()
embed = self._embed("⏳ Génération en cours...", discord.Color.yellow())
await interaction.followup.send(embed=embed, ephemeral=True)
embed = self._embed("⏳ Génération en cours...", disnake.Color.yellow())
await interaction.followup.send(embed=embed)
try:
if cible == "servers":
res = await generate_servers_json(self.bot)
embed = self._embed("✅ servers.json généré", discord.Color.green())
embed = self._embed("✅ servers.json généré", disnake.Color.green())
embed.add_field(name="Serveurs", value=str(res["count"]), inline=True)
embed.add_field(name="Durée", value=f"{(datetime.now()-start).total_seconds():.1f}s", inline=True)
embed.add_field(name="Fichier", value=f"`{SERVERS_FILE}`", inline=False)
elif cible == "members":
res = await generate_members_json(self.bot)
embed = self._embed("✅ members.json généré", discord.Color.green())
embed = self._embed("✅ members.json généré", disnake.Color.green())
embed.add_field(name="Membres", value=str(res["count"]), inline=True)
embed.add_field(name="Serveur", value=res["guildName"], inline=True)
embed.add_field(name="Durée", value=f"{(datetime.now()-start).total_seconds():.1f}s", inline=True)
@ -125,7 +123,7 @@ class WebsiteSync(commands.Cog, name="Website Sync"):
has_errors = bool(res["errors"])
embed = self._embed(
"⚠️ Génération partielle" if has_errors else "✅ Génération complète",
discord.Color.orange() if has_errors else discord.Color.green(),
disnake.Color.orange() if has_errors else disnake.Color.green(),
)
if res["servers"]:
embed.add_field(name="📋 Serveurs", value=f"{res['servers']['count']} serveurs", inline=True)
@ -137,19 +135,19 @@ class WebsiteSync(commands.Cog, name="Website Sync"):
embed.add_field(name="📁 Dossier", value=f"`{OUTPUT_DIR}`", inline=False)
except Exception as e:
embed = self._embed("❌ Erreur", discord.Color.red(), f"```{e}```")
embed = self._embed("❌ Erreur", disnake.Color.red(), f"```{e}```")
await interaction.edit_original_response(embed=embed)
# ══════════════════════════════════════════════════════════════════════════
# /json-status
# ══════════════════════════════════════════════════════════════════════════
@app_commands.command(name="json-status", description="État des fichiers JSON et prochaine génération auto")
@app_commands.default_permissions(administrator=True)
async def json_status(self, interaction: discord.Interaction):
@commands.slash_command(name="json-status", description="État des fichiers JSON et prochaine génération auto")
@commands.has_permissions(administrator=True)
async def json_status(self, interaction: disnake.ApplicationCommandInteraction):
await interaction.response.defer(ephemeral=True)
config = load_config()
embed = self._embed("📊 Statut JSON Export", discord.Color.blurple())
embed = self._embed("📊 Statut JSON Export", disnake.Color.blurple())
# servers.json
if SERVERS_FILE.exists():
@ -203,15 +201,14 @@ class WebsiteSync(commands.Cog, name="Website Sync"):
# ══════════════════════════════════════════════════════════════════════════
# /json-config (groupe)
# ══════════════════════════════════════════════════════════════════════════
json_config_group = app_commands.Group(
name="json-config",
description="Configure la génération automatique des JSON",
default_permissions=discord.Permissions(administrator=True),
)
@commands.slash_command(name="json-config", description="Configure la génération automatique des JSON")
@commands.has_permissions(administrator=True)
async def json_config_group(self, interaction: disnake.ApplicationCommandInteraction):
pass
# ── show ──────────────────────────────────────────────────────────────────
@json_config_group.command(name="show", description="Affiche la configuration actuelle")
async def config_show(self, interaction: discord.Interaction):
@json_config_group.sub_command(name="show", description="Affiche la configuration actuelle")
async def config_show(self, interaction: disnake.ApplicationCommandInteraction):
await interaction.response.defer(ephemeral=True)
config = load_config()
@ -220,7 +217,7 @@ class WebsiteSync(commands.Cog, name="Website Sync"):
mbr_on = ", ".join(k for k, v in config["memberFields"].items() if v) or "_Aucun_"
mbr_off = ", ".join(k for k, v in config["memberFields"].items() if not v) or "_Aucun_"
embed = self._embed("⚙️ Configuration JSON Export", discord.Color.blurple())
embed = self._embed("⚙️ Configuration JSON Export", disnake.Color.blurple())
embed.add_field(name="🕐 Génération auto", value=f"{config['autoHour']:02d}:{config['autoMinute']:02d} (heure serveur)", inline=True)
embed.add_field(name="🎯 Serveur cible (members)", value=config["targetGuildId"] or "_Non défini_", inline=True)
embed.add_field(name="\u200b", value="\u200b", inline=False)
@ -233,9 +230,8 @@ class WebsiteSync(commands.Cog, name="Website Sync"):
await interaction.followup.send(embed=embed, ephemeral=True)
# ── set-guild ────────────────────────────────────────────────────────────
@json_config_group.command(name="set-guild", description="Définit l'ID du serveur cible pour members.json")
@app_commands.describe(guild_id="ID du serveur Discord")
async def config_set_guild(self, interaction: discord.Interaction, guild_id: str):
@json_config_group.sub_command(name="set-guild", description="Définit l'ID du serveur cible pour members.json")
async def config_set_guild(self, interaction: disnake.ApplicationCommandInteraction, guild_id: str = commands.Param(description="ID du serveur Discord")):
await interaction.response.defer(ephemeral=True)
try:
guild = self.bot.get_guild(int(guild_id)) or await self.bot.fetch_guild(int(guild_id))
@ -243,20 +239,21 @@ class WebsiteSync(commands.Cog, name="Website Sync"):
config["targetGuildId"] = guild_id
save_config(config)
embed = self._embed(
"✅ Serveur cible défini", discord.Color.green(),
"✅ Serveur cible défini", disnake.Color.green(),
f"**{guild.name}** (`{guild_id}`) sera utilisé pour members.json.",
)
except Exception:
embed = self._embed(
"❌ Serveur introuvable", discord.Color.red(),
"❌ Serveur introuvable", disnake.Color.red(),
f"Le bot n'est pas dans le serveur `{guild_id}` ou l'ID est invalide.",
)
await interaction.followup.send(embed=embed, ephemeral=True)
# ── set-schedule ─────────────────────────────────────────────────────────
@json_config_group.command(name="set-schedule", description="Définit l'heure de génération automatique quotidienne")
@app_commands.describe(heure="Heure (0-23)", minute="Minute (0-59, défaut: 0)")
async def config_set_schedule(self, interaction: discord.Interaction, heure: int, minute: int = 0):
@json_config_group.sub_command(name="set-schedule", description="Définit l'heure de génération automatique quotidienne")
async def config_set_schedule(self, interaction: disnake.ApplicationCommandInteraction,
heure: int = commands.Param(description="Heure (0-23)"),
minute: int = commands.Param(0, description="Minute (0-59, défaut: 0)")):
await interaction.response.defer(ephemeral=True)
if not (0 <= heure <= 23) or not (0 <= minute <= 59):
await interaction.followup.send("❌ Heure invalide.", ephemeral=True)
@ -266,42 +263,42 @@ class WebsiteSync(commands.Cog, name="Website Sync"):
config["autoMinute"] = minute
save_config(config)
embed = self._embed(
"✅ Horaire mis à jour", discord.Color.green(),
"✅ Horaire mis à jour", disnake.Color.green(),
f"Prochaine génération auto : **{heure:02d}:{minute:02d}** chaque jour.",
)
await interaction.followup.send(embed=embed, ephemeral=True)
await interaction.followup.send(embed=embed)
# ── server-field ─────────────────────────────────────────────────────────
@json_config_group.command(name="server-field", description="Active/désactive un champ dans servers.json")
@app_commands.describe(champ="Champ à modifier", actif="true = inclus, false = exclu")
@app_commands.choices(champ=[app_commands.Choice(name=f, value=f) for f in SERVER_FIELDS])
async def config_server_field(self, interaction: discord.Interaction, champ: str, actif: bool):
@json_config_group.sub_command(name="server-field", description="Active/désactive un champ dans servers.json")
async def config_server_field(self, interaction: disnake.ApplicationCommandInteraction,
champ: str = commands.Param(description="Champ à modifier", choices=SERVER_FIELDS),
actif: bool = commands.Param(description="true = inclus, false = exclu")):
await interaction.response.defer(ephemeral=True)
config = load_config()
config["serverFields"][champ] = actif
save_config(config)
state = "inclus ✅" if actif else "exclu ❌"
embed = self._embed("✅ Champ servers.json mis à jour", discord.Color.green(),
embed = self._embed("✅ Champ servers.json mis à jour", disnake.Color.green(),
f"`{champ}` est maintenant **{state}** de servers.json.")
await interaction.followup.send(embed=embed, ephemeral=True)
await interaction.followup.send(embed=embed)
# ── member-field ─────────────────────────────────────────────────────────
@json_config_group.command(name="member-field", description="Active/désactive un champ dans members.json")
@app_commands.describe(champ="Champ à modifier", actif="true = inclus, false = exclu")
@app_commands.choices(champ=[app_commands.Choice(name=f, value=f) for f in MEMBER_FIELDS])
async def config_member_field(self, interaction: discord.Interaction, champ: str, actif: bool):
@json_config_group.sub_command(name="member-field", description="Active/désactive un champ dans members.json")
async def config_member_field(self, interaction: disnake.ApplicationCommandInteraction,
champ: str = commands.Param(description="Champ à modifier", choices=MEMBER_FIELDS),
actif: bool = commands.Param(description="true = inclus, false = exclu")):
await interaction.response.defer(ephemeral=True)
config = load_config()
config["memberFields"][champ] = actif
save_config(config)
state = "inclus ✅" if actif else "exclu ❌"
embed = self._embed("✅ Champ members.json mis à jour", discord.Color.green(),
embed = self._embed("✅ Champ members.json mis à jour", disnake.Color.green(),
f"`{champ}` est maintenant **{state}** de members.json.")
await interaction.followup.send(embed=embed, ephemeral=True)
await interaction.followup.send(embed=embed)
# ── reset ─────────────────────────────────────────────────────────────────
@json_config_group.command(name="reset", description="Remet la configuration par défaut")
async def config_reset(self, interaction: discord.Interaction):
@json_config_group.sub_command(name="reset", description="Remet la configuration par défaut")
async def config_reset(self, interaction: disnake.ApplicationCommandInteraction):
await interaction.response.defer(ephemeral=True)
from src.json_export_service import CONFIG_FILE
try:
@ -309,14 +306,14 @@ class WebsiteSync(commands.Cog, name="Website Sync"):
except Exception:
pass
load_config() # recrée depuis DEFAULT_CONFIG
embed = self._embed("✅ Configuration réinitialisée", discord.Color.green(),
embed = self._embed("✅ Configuration réinitialisée", disnake.Color.green(),
"Tous les paramètres ont été remis à leurs valeurs par défaut.")
await interaction.followup.send(embed=embed, ephemeral=True)
await interaction.followup.send(embed=embed)
# ── set-output-dir ────────────────────────────────────────────────────────
@json_config_group.command(name="set-output-dir", description="Change le dossier de sortie des fichiers JSON")
@app_commands.describe(chemin="Chemin absolu ou relatif vers le dossier (ex: /var/www/site/data)")
async def config_set_output_dir(self, interaction: discord.Interaction, chemin: str):
@json_config_group.sub_command(name="set-output-dir", description="Change le dossier de sortie des fichiers JSON")
async def config_set_output_dir(self, interaction: disnake.ApplicationCommandInteraction,
chemin: str = commands.Param(description="Chemin absolu ou relatif vers le dossier (ex: /var/www/site/data)")):
await interaction.response.defer(ephemeral=True)
from pathlib import Path as _Path
p = _Path(chemin)
@ -326,75 +323,69 @@ class WebsiteSync(commands.Cog, name="Website Sync"):
config["outputDir"] = str(p)
save_config(config)
embed = self._embed(
"✅ Dossier de sortie mis à jour", discord.Color.green(),
"✅ Dossier de sortie mis à jour", disnake.Color.green(),
f"Les JSON seront désormais écrits dans :\n`{p.resolve()}`",
)
except Exception as e:
embed = self._embed("❌ Erreur", discord.Color.red(), f"Impossible d'utiliser ce chemin :\n```{e}```")
await interaction.followup.send(embed=embed, ephemeral=True)
embed = self._embed("❌ Erreur", disnake.Color.red(), f"Impossible d'utiliser ce chemin :\n```{e}```")
await interaction.followup.send(embed=embed)
# ── add-founder-role ──────────────────────────────────────────────────────
@json_config_group.command(name="add-founder-role", description="Ajoute un nom de rôle reconnu comme 'fondateur'")
@app_commands.describe(nom="Nom du rôle (insensible à la casse, correspondance partielle)")
async def config_add_founder_role(self, interaction: discord.Interaction, nom: str):
@json_config_group.sub_command(name="add-founder-role", description="Ajoute un nom de rôle reconnu comme 'fondateur'")
async def config_add_founder_role(self, interaction: disnake.ApplicationCommandInteraction,
nom: str = commands.Param(description="Nom du rôle (insensible à la casse, correspondance partielle)")):
await interaction.response.defer(ephemeral=True)
config = load_config()
roles = config.get("founderRoles", [])
nom_l = nom.lower().strip()
if nom_l in [r.lower() for r in roles]:
embed = self._embed(" Déjà présent", discord.Color.yellow(),
embed = self._embed(" Déjà présent", disnake.Color.yellow(),
f"`{nom}` est déjà dans la liste des rôles fondateurs.")
else:
roles.append(nom_l)
config["founderRoles"] = roles
save_config(config)
embed = self._embed(
"✅ Rôle fondateur ajouté", discord.Color.green(),
"✅ Rôle fondateur ajouté", disnake.Color.green(),
f"`{nom_l}` ajouté. Liste actuelle : {', '.join(f'`{r}`' for r in roles)}",
)
await interaction.followup.send(embed=embed, ephemeral=True)
await interaction.followup.send(embed=embed)
# ── remove-founder-role ───────────────────────────────────────────────────
@json_config_group.command(name="remove-founder-role", description="Retire un nom de rôle fondateur")
@app_commands.describe(nom="Nom du rôle à retirer")
async def config_remove_founder_role(self, interaction: discord.Interaction, nom: str):
@json_config_group.sub_command(name="remove-founder-role", description="Retire un nom de rôle fondateur")
async def config_remove_founder_role(self, interaction: disnake.ApplicationCommandInteraction,
nom: str = commands.Param(description="Nom du rôle à retirer")):
await interaction.response.defer(ephemeral=True)
config = load_config()
roles = config.get("founderRoles", [])
nom_l = nom.lower().strip()
updated = [r for r in roles if r.lower() != nom_l]
if len(updated) == len(roles):
embed = self._embed(" Introuvable", discord.Color.yellow(),
embed = self._embed(" Introuvable", disnake.Color.yellow(),
f"`{nom}` n'est pas dans la liste des rôles fondateurs.")
else:
config["founderRoles"] = updated
save_config(config)
reste = ', '.join(f'`{r}`' for r in updated) or "_Aucun_"
embed = self._embed(
"✅ Rôle fondateur retiré", discord.Color.green(),
"✅ Rôle fondateur retiré", disnake.Color.green(),
f"`{nom_l}` retiré. Liste actuelle : {reste}",
)
await interaction.followup.send(embed=embed, ephemeral=True)
await interaction.followup.send(embed=embed)
# ══════════════════════════════════════════════════════════════════════════
# /json-preview
# ══════════════════════════════════════════════════════════════════════════
@app_commands.command(name="json-preview", description="Aperçu des données sans écrire les fichiers")
@app_commands.describe(
cible="Quoi prévisualiser ?",
limite="Nombre d'éléments à afficher (défaut: 3)",
)
@app_commands.choices(cible=[
app_commands.Choice(name="Serveurs", value="servers"),
app_commands.Choice(name="Membres", value="members"),
])
@app_commands.default_permissions(administrator=True)
async def json_preview(self, interaction: discord.Interaction, cible: str = "servers", limite: int = 3):
@commands.slash_command(name="json-preview", description="Aperçu des données sans écrire les fichiers")
@commands.has_permissions(administrator=True)
async def json_preview(self, interaction: disnake.ApplicationCommandInteraction,
cible: str = commands.Param("servers", description="Quoi prévisualiser ?", choices={"Serveurs": "servers", "Membres": "members"}),
limite: int = commands.Param(3, description="Nombre d'éléments à afficher (1-10, défaut: 3)")):
await interaction.response.defer(ephemeral=True)
limite = max(1, min(limite, 10)) # entre 1 et 10
try:
embed = self._embed(f"🔍 Aperçu — {cible}", discord.Color.blurple())
embed = self._embed(f"🔍 Aperçu — {cible}", disnake.Color.blurple())
embed.set_footer(text="Aperçu uniquement — aucun fichier écrit")
if cible == "servers":
@ -421,22 +412,22 @@ class WebsiteSync(commands.Cog, name="Website Sync"):
)
except Exception as e:
embed = self._embed("❌ Erreur", discord.Color.red(), f"```{e}```")
embed = self._embed("❌ Erreur", disnake.Color.red(), f"```{e}```")
await interaction.followup.send(embed=embed, ephemeral=True)
# ══════════════════════════════════════════════════════════════════════════
# /json-history
# ══════════════════════════════════════════════════════════════════════════
@app_commands.command(name="json-history", description="Historique des dernières générations automatiques et manuelles")
@app_commands.describe(limite="Nombre d'entrées à afficher (défaut: 5, max: 10)")
@app_commands.default_permissions(administrator=True)
async def json_history(self, interaction: discord.Interaction, limite: int = 5):
@commands.slash_command(name="json-history", description="Historique des dernières générations automatiques et manuelles")
@commands.has_permissions(administrator=True)
async def json_history(self, interaction: disnake.ApplicationCommandInteraction,
limite: int = commands.Param(5, description="Nombre d'entrées à afficher (défaut: 5, max: 10)")):
await interaction.response.defer(ephemeral=True)
limite = max(1, min(limite, 10))
history = get_history()
embed = self._embed("📜 Historique des générations JSON", discord.Color.blurple())
embed = self._embed("📜 Historique des générations JSON", disnake.Color.blurple())
if not history:
embed.description = "_Aucune génération enregistrée pour l'instant._"
@ -465,9 +456,5 @@ class WebsiteSync(commands.Cog, name="Website Sync"):
# ─── Setup ───────────────────────────────────────────────────────────────────
async def setup(bot: commands.Bot):
cog = WebsiteSync(bot)
await bot.add_cog(cog)
# Enregistre le groupe de commandes json-config s'il n'est pas déjà présent
if not bot.tree.get_command("json-config"):
bot.tree.add_command(cog.json_config_group)
await bot.add_cog(WebsiteSync(bot))
logger.info("Cog WebsiteSync chargé.")