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

@ -1,6 +1,5 @@
import discord
from discord.ext import commands, tasks
from discord import app_commands, ui
import disnake
from disnake.ext import commands, tasks
import json
import asyncio
import os
@ -82,22 +81,26 @@ def parse_date(date_str: str):
# --- MODALS & VIEWS ---
class AbsenceModal(ui.Modal, title="Déclarer une Absence Staff"):
motif = ui.TextInput(
class AbsenceModal(disnake.ui.Modal):
def __init__(self, superieur: Optional[disnake.Member] = None):
super().__init__(title="Déclarer une Absence Staff")
self.superieur = superieur
motif = disnake.ui.TextInput(
label="Motif de l'absence",
style=discord.TextStyle.paragraph,
style=disnake.TextStyle.paragraph,
placeholder="Ex: Vacances, Raisons personnelles, Travail...",
required=True,
max_length=500
)
date_debut = ui.TextInput(
date_debut = disnake.ui.TextInput(
label="Début (JJ/MM/AAAA HH:MM)",
placeholder="Ex: 15/05/2024 08:00",
min_length=16,
max_length=16,
required=True
)
date_fin = ui.TextInput(
date_fin = disnake.ui.TextInput(
label="Fin (JJ/MM/AAAA HH:MM)",
placeholder="Ex: 20/05/2024 18:00",
min_length=16,
@ -105,11 +108,7 @@ class AbsenceModal(ui.Modal, title="Déclarer une Absence Staff"):
required=True
)
def __init__(self, superieur: Optional[discord.Member] = None):
super().__init__()
self.superieur = superieur
async def on_submit(self, interaction: discord.Interaction):
async def callback(self, interaction: disnake.Interaction):
try:
debut = parse_date(self.date_debut.value)
fin = parse_date(self.date_fin.value)
@ -182,20 +181,20 @@ class AbsenceModal(ui.Modal, title="Déclarer une Absence Staff"):
await interaction.response.send_message("✅ Absence enregistrée avec succès !", ephemeral=True)
class AbsenceConfigView(ui.View):
class AbsenceConfigView(disnake.ui.View):
def __init__(self, guild_id: int):
super().__init__(timeout=60)
self.guild_id = guild_id
@ui.button(label="Définir le Salon", style=discord.ButtonStyle.primary, emoji="📁")
async def set_channel(self, interaction: discord.Interaction, button: ui.Button):
@disnake.ui.button(label="Définir le Salon", style=disnake.ButtonStyle.primary, emoji="📁")
async def set_channel(self, interaction: disnake.Interaction, button: disnake.ui.Button):
await interaction.response.send_message("Mentionne le salon ici (ou envoie son ID) :", ephemeral=True)
def check(m):
return m.author == interaction.user and m.channel == interaction.channel
try:
msg = await interaction.client.wait_for('message', check=check, timeout=30)
msg = await interaction.bot.wait_for('message', check=check, timeout=30)
channel = None
if msg.channel_mentions:
channel = msg.channel_mentions[0]
@ -214,15 +213,15 @@ class AbsenceConfigView(ui.View):
except asyncio.TimeoutError:
await interaction.followup.send("❌ Temps écoulé.", ephemeral=True)
@ui.button(label="Définir le Rôle", style=discord.ButtonStyle.primary, emoji="🎭")
async def set_role(self, interaction: discord.Interaction, button: ui.Button):
@disnake.ui.button(label="Définir le Rôle", style=disnake.ButtonStyle.primary, emoji="🎭")
async def set_role(self, interaction: disnake.Interaction, button: disnake.ui.Button):
await interaction.response.send_message("Mentionne le rôle ici (ou envoie son ID) :", ephemeral=True)
def check(m):
return m.author == interaction.user and m.channel == interaction.channel
try:
msg = await interaction.client.wait_for('message', check=check, timeout=30)
msg = await interaction.bot.wait_for('message', check=check, timeout=30)
role = None
if msg.role_mentions:
role = msg.role_mentions[0]
@ -246,22 +245,22 @@ class Absence(commands.Cog):
self.bot = bot
self.check_absences.start()
@app_commands.command(name="absence", description="Déclarer une absence staff")
@app_commands.describe(superieur="Le supérieur hiérarchique à prévenir")
async def absence(self, interaction: discord.Interaction, superieur: Optional[discord.Member] = None):
@commands.slash_command(name="absence", description="Déclarer une absence staff")
async def absence(self, interaction: disnake.ApplicationCommandInteraction,
superieur: Optional[disnake.Member] = commands.Param(None, description="Le supérieur hiérarchique à prévenir")):
"""Déclare une absence via un formulaire"""
await interaction.response.send_modal(AbsenceModal(superieur))
@app_commands.command(name="absence_config", description="Configurer le système d'absence (Admin)")
@app_commands.checks.has_permissions(administrator=True)
async def absence_config(self, interaction: discord.Interaction):
@commands.slash_command(name="absence_config", description="Configurer le système d'absence (Admin)")
@commands.has_permissions(administrator=True)
async def absence_config(self, interaction: disnake.ApplicationCommandInteraction):
"""Ouvre le panneau de configuration des absences"""
settings = load_settings(interaction.guild.id)
embed = discord.Embed(
embed = disnake.Embed(
title="⚙️ Configuration des Absences Staff",
description="Utilisez les boutons ci-dessous pour configurer le système.",
color=discord.Color.blue()
color=disnake.Color.blue()
)
channel_id = settings.get("absence_channel_id")
@ -280,7 +279,7 @@ class Absence(commands.Cog):
await interaction.response.send_message(embed=embed, view=AbsenceConfigView(interaction.guild.id), ephemeral=True)
async def strike_absence_message(self, guild: discord.Guild, channel_id: int, message_id: int):
async def strike_absence_message(self, guild: disnake.Guild, channel_id: int, message_id: int):
"""Modifie le message d'absence pour le barrer au lieu de le supprimer"""
channel = guild.get_channel(channel_id)
if not channel:
@ -311,8 +310,8 @@ class Absence(commands.Cog):
except Exception as e:
print(f"Erreur lors du barrage du message d'absence: {e}")
@app_commands.command(name="fin_absence", description="Terminer son absence staff")
async def fin_absence(self, interaction: discord.Interaction):
@commands.slash_command(name="fin_absence", description="Terminer son absence staff")
async def fin_absence(self, interaction: disnake.ApplicationCommandInteraction):
"""Met fin à l'absence en cours"""
all_abs = await load_absences()
guild_abs = get_guild_absences(all_abs, interaction.guild.id)
@ -343,8 +342,8 @@ class Absence(commands.Cog):
await save_absences(all_abs)
await interaction.response.send_message("✅ Ton absence a été retirée.", ephemeral=True)
@app_commands.command(name="liste_absences", description="Afficher la liste des absences staff en cours")
async def liste_absences(self, interaction: discord.Interaction):
@commands.slash_command(name="liste_absences", description="Afficher la liste des absences staff en cours")
async def liste_absences(self, interaction: disnake.ApplicationCommandInteraction):
"""Affiche les absences en cours sur le serveur"""
all_abs = await load_absences()
guild_abs = get_guild_absences(all_abs, interaction.guild.id)
@ -360,10 +359,10 @@ class Absence(commands.Cog):
per_page = 5
pages = [absences[i:i + per_page] for i in range(0, len(absences), per_page)]
def create_embed(page_index: int) -> discord.Embed:
embed = discord.Embed(
def create_embed(page_index: int) -> disnake.Embed:
embed = disnake.Embed(
title=f"📋 Absences staff - {interaction.guild.name} ({page_index + 1}/{len(pages)})",
color=discord.Color.orange()
color=disnake.Color.orange()
)
for user_id, info in pages[page_index]:
embed.add_field(
@ -378,19 +377,19 @@ class Absence(commands.Cog):
embed.set_footer(text="Gestion des absences du staff")
return embed
class PaginatorView(ui.View):
class PaginatorView(disnake.ui.View):
def __init__(self):
super().__init__(timeout=60)
self.page = 0
@ui.button(label="⬅️ Précédent", style=discord.ButtonStyle.secondary, disabled=True)
async def previous(self, interaction_btn: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="⬅️ Précédent", style=disnake.ButtonStyle.secondary, disabled=True)
async def previous(self, interaction_btn: disnake.Interaction, button: disnake.ui.Button):
self.page -= 1
self.update_buttons()
await interaction_btn.response.edit_message(embed=create_embed(self.page), view=self)
@ui.button(label="➡️ Suivant", style=discord.ButtonStyle.secondary, disabled=len(pages) == 1)
async def next(self, interaction_btn: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="➡️ Suivant", style=disnake.ButtonStyle.secondary, disabled=len(pages) == 1)
async def next(self, interaction_btn: disnake.Interaction, button: disnake.ui.Button):
self.page += 1
self.update_buttons()
await interaction_btn.response.edit_message(embed=create_embed(self.page), view=self)

View file

@ -7,9 +7,8 @@ import asyncio
from datetime import datetime
from typing import Dict, Any, List
import discord
from discord.ext import commands
from discord import app_commands
import disnake
from disnake.ext import commands
BACKUPS_ROOT = "data/security_backups" # dossier de sortie des sauvegardes
@ -46,10 +45,10 @@ def estimate_duration_seconds(
return int(meta_cost + msg_cost + base_overhead)
def serialize_overwrites(channel: discord.abc.GuildChannel) -> List[Dict[str, Any]]:
def serialize_overwrites(channel: disnake.abc.GuildChannel) -> List[Dict[str, Any]]:
serialized: List[Dict[str, Any]] = []
for target, perms in channel.overwrites.items():
target_type = "role" if isinstance(target, discord.Role) else "member"
target_type = "role" if isinstance(target, disnake.Role) else "member"
serialized.append({
"target_type": target_type,
"target_id": target.id,
@ -64,20 +63,17 @@ class Backup(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
backup_group = app_commands.Group(name="backup", description="Outils de sauvegarde du serveur")
@commands.slash_command(name="backup", description="Outils de sauvegarde du serveur")
async def backup_group(self, interaction: disnake.ApplicationCommandInteraction):
pass
@backup_group.command(name="create", description="Créer une sauvegarde du serveur")
@app_commands.describe(
save_messages="Inclure l'export des messages",
privacy_notice="Afficher un avertissement public si messages sauvegardés",
message_limit_per_channel="Nombre max de messages à exporter par salon (0 = tous)"
)
@backup_group.sub_command(name="create", description="Créer une sauvegarde du serveur")
async def backup_create(
self,
interaction: discord.Interaction,
save_messages: bool = True,
privacy_notice: bool = True,
message_limit_per_channel: int = 10000
interaction: disnake.ApplicationCommandInteraction,
save_messages: bool = commands.Param(True, description="Inclure l'export des messages"),
privacy_notice: bool = commands.Param(True, description="Afficher un avertissement public si messages sauvegardés"),
message_limit_per_channel: int = commands.Param(10000, description="Nombre max de messages à exporter par salon (0 = tous)")
):
guild = interaction.guild
assert guild is not None, "Cette commande doit être utilisée dans un serveur."
@ -93,20 +89,20 @@ class Backup(commands.Cog):
)
return
# Déferre l'interaction
await interaction.response.defer(ephemeral=True, thinking=True)
# Deferre l'interaction
await interaction.response.defer(ephemeral=True)
# Avertissement de confidentialité
if privacy_notice and save_messages:
try:
await interaction.channel.send(
embed=discord.Embed(
embed=disnake.Embed(
title="📢 Information de confidentialité (transparence)",
description=(
"Une **sauvegarde du serveur** est en cours et **inclus les messages**.\n"
"Vous êtes dans l'obligation de prévenir les membres."
),
color=discord.Color.orange()
color=disnake.Color.orange()
)
)
except Exception:
@ -132,14 +128,14 @@ class Backup(commands.Cog):
message_limit_per_channel if message_limit_per_channel else 100000
)
prog_embed = discord.Embed(
prog_embed = disnake.Embed(
title="🗄️ Sauvegarde en cours",
description=(
f"**Serveur :** {guild.name} (`{guild.id}`)\n"
f"**Estimation :** ~{estimation_s} secondes\n"
f"**Options** : messages={'oui' if save_messages else 'non'} • limite/chan={message_limit_per_channel or 'tous'}"
),
color=discord.Color.blurple()
color=disnake.Color.blurple()
)
prog_embed.add_field(name="Progression", value=progress_bar(0.0), inline=False)
progress_msg = await interaction.followup.send(embed=prog_embed, ephemeral=False)
@ -173,11 +169,11 @@ class Backup(commands.Cog):
chans_dump=[]
for ch in guild.channels:
base={"id":ch.id,"name":ch.name,"type":str(ch.type),"position":ch.position,"category_id":ch.category.id if getattr(ch,"category",None) else None,"overwrites":serialize_overwrites(ch)}
if isinstance(ch,discord.TextChannel):
if isinstance(ch,disnake.TextChannel):
base.update({"topic":ch.topic,"nsfw":ch.nsfw,"slowmode_delay":ch.slowmode_delay,"default_auto_archive_duration":ch.default_auto_archive_duration})
elif isinstance(ch,discord.VoiceChannel):
elif isinstance(ch,disnake.VoiceChannel):
base.update({"bitrate":ch.bitrate,"user_limit":ch.user_limit,"rtc_region":str(ch.rtc_region) if ch.rtc_region else None})
elif isinstance(ch,discord.ForumChannel):
elif isinstance(ch,disnake.ForumChannel):
base.update({"nsfw":ch.nsfw,"default_auto_archive_duration":ch.default_auto_archive_duration})
chans_dump.append(base)
with open(os.path.join(base_dir,"channels.json"),"w",encoding="utf-8") as f:
@ -208,7 +204,7 @@ class Backup(commands.Cog):
data={"id":msg.id,"channel_id":msg.channel.id,"author_id":msg.author.id,"author_bot":msg.author.bot,"created_at":msg.created_at.isoformat(),"content":msg.content,"attachments":[a.url for a in msg.attachments],"embeds":[e.to_dict() for e in msg.embeds],"reference":{"message_id":getattr(msg.reference,"message_id",None),"channel_id":getattr(msg.reference,"channel_id",None)} if msg.reference else None}
f.write(json.dumps(data,ensure_ascii=False)+"\n")
count+=1
except discord.Forbidden: pass
except disnake.Forbidden: pass
except Exception: pass
exported_total+=count
done+=per_chan_weight
@ -226,7 +222,7 @@ class Backup(commands.Cog):
rel=os.path.relpath(full,base_dir)
zf.write(full,rel)
except Exception as exc:
prog_embed.color=discord.Color.red()
prog_embed.color=disnake.Color.red()
prog_embed.set_field_at(0,name="Progression",value=progress_bar(1.0),inline=False)
prog_embed.set_footer(text=f"Erreur lors de la création du ZIP : {exc}")
await progress_msg.edit(embed=prog_embed)
@ -234,15 +230,15 @@ class Backup(commands.Cog):
return
# Finalisation
prog_embed.color=discord.Color.green()
prog_embed.color=disnake.Color.green()
prog_embed.set_field_at(0,name="Progression",value=progress_bar(1.0),inline=False)
prog_embed.add_field(name="Archive",value=f"`{zip_path}`",inline=False)
prog_embed.set_footer(text="Sauvegarde terminée ✅")
await progress_msg.edit(embed=prog_embed)
await interaction.followup.send("🎉 Sauvegarde terminée. Un message de progression avec le chemin de larchive a été publié.", ephemeral=True)
@backup_group.command(name="list", description="Lister les archives de sauvegarde disponibles")
async def backup_list(self, interaction: discord.Interaction):
@backup_group.sub_command(name="list", description="Lister les archives de sauvegarde disponibles")
async def backup_list(self, interaction: disnake.ApplicationCommandInteraction):
guild = interaction.guild
assert guild is not None, "Cette commande doit être utilisée dans un serveur."
@ -256,7 +252,7 @@ class Backup(commands.Cog):
)
return
await interaction.response.defer(ephemeral=True, thinking=True)
await interaction.response.defer(ephemeral=True)
ensure_dir(BACKUPS_ROOT)
files = [f for f in os.listdir(BACKUPS_ROOT) if f.endswith(".zip")]
files.sort(reverse=True)
@ -266,7 +262,7 @@ class Backup(commands.Cog):
return
desc="\n".join(f"• `{name}`" for name in files[:20])
embed=discord.Embed(title="📦 Archives disponibles",description=desc,color=discord.Color.blurple())
embed=disnake.Embed(title="📦 Archives disponibles",description=desc,color=disnake.Color.blurple())
if len(files)>20:
embed.set_footer(text=f"... et {len(files)-20} de plus")
await interaction.followup.send(embed=embed,ephemeral=True)

View file

@ -5,9 +5,8 @@ import zipfile
import tempfile
import shutil
from typing import List
import discord
from discord import app_commands
from discord.ext import commands
import disnake
from disnake.ext import commands
BACKUPS_ROOT = "data/security_backups"
@ -43,7 +42,7 @@ class BackupRestore(commands.Cog):
elements.append(base)
return elements
async def element_autocomplete(self, interaction: discord.Interaction, current: str):
async def element_autocomplete(self, interaction: disnake.ApplicationCommandInteraction, current: str):
"""Complétion automatique pour l'option 'element'."""
guild = interaction.guild
if guild is None:
@ -60,9 +59,9 @@ class BackupRestore(commands.Cog):
backup_path = os.path.join(BACKUPS_ROOT, last_backup)
elements = await self.list_backup_elements(backup_path)
return [app_commands.Choice(name=e, value=e) for e in elements if current.lower() in e.lower()][:25]
return [e for e in elements if current.lower() in e.lower()][:25]
async def restore_roles(self, guild: discord.Guild, backup_path: str):
async def restore_roles(self, guild: disnake.Guild, backup_path: str):
"""Restaure les rôles à partir du backup."""
roles_file = os.path.join(backup_path, "roles.json")
if os.path.isfile(roles_file):
@ -72,16 +71,16 @@ class BackupRestore(commands.Cog):
try:
await guild.create_role(
name=r["name"],
permissions=discord.Permissions(r["permissions"]),
color=discord.Color(r.get("color", 0)),
permissions=disnake.Permissions(r["permissions"]),
color=disnake.Color(r.get("color", 0)),
hoist=r.get("hoist", False),
mentionable=r.get("mentionable", False),
reason="Restauration backup"
)
except discord.Forbidden:
except disnake.Forbidden:
continue
async def restore_channels(self, guild: discord.Guild, backup_path: str):
async def restore_channels(self, guild: disnake.Guild, backup_path: str):
"""Restaure les channels à partir du backup."""
channels_file = os.path.join(backup_path, "channels.json")
if os.path.isfile(channels_file):
@ -105,10 +104,10 @@ class BackupRestore(commands.Cog):
user_limit=c.get("user_limit", 0),
reason="Restauration backup"
)
except discord.Forbidden:
except disnake.Forbidden:
continue
async def restore_messages(self, channel: discord.TextChannel, backup_path: str):
async def restore_messages(self, channel: disnake.TextChannel, backup_path: str):
"""Restaure les messages à partir du backup (solution ZIP incluse)."""
if backup_path.endswith(".zip"):
temp_dir = tempfile.mkdtemp()
@ -133,17 +132,12 @@ class BackupRestore(commands.Cog):
for msg in messages:
await channel.send(msg["content"])
@app_commands.command(name="granulaire", description="Restaure un élément précis d'une sauvegarde")
@app_commands.describe(
backup="Nom ou ID de la sauvegarde",
element="Élément à restaurer (roles, channels, messages...)"
)
@app_commands.autocomplete(element=element_autocomplete)
@commands.slash_command(name="granulaire", description="Restaure un élément précis d'une sauvegarde")
async def restore_granulaire(
self,
interaction: discord.Interaction,
backup: str,
element: str
interaction: disnake.ApplicationCommandInteraction,
backup: str = commands.Param(description="Nom ou ID de la sauvegarde"),
element: str = commands.Param(description="Élément à restaurer (roles, channels, messages...)")
):
guild = interaction.guild
if guild is None:
@ -162,7 +156,7 @@ class BackupRestore(commands.Cog):
await interaction.response.send_message(f"❌ La sauvegarde `{backup}` est introuvable.", ephemeral=True)
return
await interaction.response.defer(ephemeral=True, thinking=True)
await interaction.response.defer(ephemeral=True)
available_elements = await self.list_backup_elements(backup_path)
if element not in available_elements:
@ -200,5 +194,9 @@ class BackupRestore(commands.Cog):
ephemeral=True
)
@restore_granulaire.autocomplete("element")
async def element_autocomplete_decorator(self, interaction: disnake.ApplicationCommandInteraction, current: str):
return await self.element_autocomplete(interaction, current)
async def setup(bot: commands.Bot):
await bot.add_cog(BackupRestore(bot))

View file

@ -1,6 +1,5 @@
import discord
from discord.ext import commands, tasks
from discord import app_commands
import disnake
from disnake.ext import commands, tasks
import json
import os
import asyncio
@ -57,8 +56,8 @@ class Blacklist(commands.Cog):
agents = load_json(AGENTS_FILE, {"agents": []})
return str(user_id) in agents["agents"]
@app_commands.command(name="blacklist", description="Blacklist un utilisateur")
async def blacklist(self, interaction: discord.Interaction, user: discord.User, reason: str = "Aucune raison fournie"):
@commands.slash_command(name="blacklist", description="Blacklist un utilisateur")
async def blacklist(self, interaction: disnake.ApplicationCommandInteraction, user: disnake.User, reason: str = "Aucune raison fournie"):
if not self.is_agent(interaction.user.id):
return await interaction.response.send_message("⛔ Tu nas pas la permission.", ephemeral=True)
@ -70,7 +69,7 @@ class Blacklist(commands.Cog):
save_json(BLACKLIST_FILE, blacklist)
# Préparer l'embed pro
embed = discord.Embed(
embed = disnake.Embed(
title="🚫 Mise en blacklist - Réseau Omega Kube",
description=(
f"Bonjour {user.mention},\n\n"
@ -82,7 +81,7 @@ class Blacklist(commands.Cog):
"📩 Vous disposez de **7 jours** pour contester cette décision en ouvrant un ticket via le bouton ci-dessous.\n\n"
"⏳ Passé ce délai, vous serez **définitivement banni de tout le réseau**, sans autre recours possible."
),
color=discord.Color.red()
color=disnake.Color.red()
)
embed.set_footer(text="Omega Kube - Nixfix06 & Mgstudios")
@ -122,8 +121,8 @@ class Blacklist(commands.Cog):
ephemeral=True
)
@app_commands.command(name="unblacklist", description="Retirer un utilisateur de la blacklist")
async def unblacklist(self, interaction: discord.Interaction, user: discord.User):
@commands.slash_command(name="unblacklist", description="Retirer un utilisateur de la blacklist")
async def unblacklist(self, interaction: disnake.ApplicationCommandInteraction, user: disnake.User):
if not self.is_agent(interaction.user.id):
return await interaction.response.send_message("⛔ Tu nas pas la permission.", ephemeral=True)
@ -163,21 +162,21 @@ class Blacklist(commands.Cog):
save_json(BLACKLIST_FILE, blacklist)
@commands.Cog.listener()
async def on_message(self, message: discord.Message):
async def on_message(self, message: disnake.Message):
if message.author.bot:
return
tickets = load_json(TICKETS_FILE, {})
if isinstance(message.channel, discord.DMChannel):
if isinstance(message.channel, disnake.DMChannel):
user_id = str(message.author.id)
if user_id in tickets:
guild = self.bot.get_guild(MAIN_GUILD_ID)
ticket_channel = guild.get_channel(tickets[user_id]["channel_id"])
if ticket_channel:
embed = discord.Embed(
embed = disnake.Embed(
description=message.content,
color=discord.Color.blue()
color=disnake.Color.blue()
).set_author(name=message.author, icon_url=message.author.display_avatar.url)
await ticket_channel.send(embed=embed)
@ -186,22 +185,22 @@ class Blacklist(commands.Cog):
if message.channel.id == data["channel_id"] and self.is_agent(message.author.id):
user = await self.bot.fetch_user(int(user_id))
try:
embed = discord.Embed(
embed = disnake.Embed(
description=message.content,
color=discord.Color.green()
color=disnake.Color.green()
).set_author(name=f"Agent {message.author}", icon_url=message.author.display_avatar.url)
await user.send(embed=embed)
except:
pass
# --------- Boutons ---------
class TicketButton(discord.ui.View):
class TicketButton(disnake.ui.View):
def __init__(self, user_id: int):
super().__init__(timeout=None)
self.user_id = user_id
@discord.ui.button(label="📩 Ouvrir une demande de révision", style=discord.ButtonStyle.primary, custom_id="blacklist_open_ticket")
async def open_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="📩 Ouvrir une demande de révision", style=disnake.ButtonStyle.primary, custom_id="blacklist_open_ticket")
async def open_ticket(self, interaction: disnake.Interaction, button: disnake.ui.Button):
if interaction.user.id != self.user_id:
return await interaction.response.send_message("❌ Ce bouton ne tappartient pas.", ephemeral=True)
@ -209,7 +208,7 @@ class TicketButton(discord.ui.View):
if str(self.user_id) in tickets:
return await interaction.response.send_message("❌ Tu as déjà un ticket ouvert.", ephemeral=True)
guild = interaction.client.get_guild(MAIN_GUILD_ID)
guild = interaction.bot.get_guild(MAIN_GUILD_ID)
category = guild.get_channel(TICKET_CATEGORY_ID)
ticket_channel = await guild.create_text_channel(
@ -222,10 +221,10 @@ class TicketButton(discord.ui.View):
save_json(TICKETS_FILE, tickets)
view_close = CloseTicketView(self.user_id)
embed = discord.Embed(
embed = disnake.Embed(
title="📩 Ticket ouvert",
description=f"Un utilisateur blacklisté (<@{self.user_id}>) a ouvert une demande de révision.\n\nUn agent Omega Kube prendra en charge ce dossier.",
color=discord.Color.blurple()
color=disnake.Color.blurple()
)
await ticket_channel.send(embed=embed, view=view_close)
@ -234,27 +233,27 @@ class TicketButton(discord.ui.View):
button.disabled = True
self.stop()
class CloseTicketView(discord.ui.View):
class CloseTicketView(disnake.ui.View):
def __init__(self, user_id: int):
super().__init__(timeout=None)
self.user_id = user_id
@discord.ui.button(label="❌ Fermer le ticket", style=discord.ButtonStyle.danger, custom_id="blacklist_close_ticket")
async def close_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
if not interaction.client.get_cog("Blacklist").is_agent(interaction.user.id):
@disnake.ui.button(label="❌ Fermer le ticket", style=disnake.ButtonStyle.danger, custom_id="blacklist_close_ticket")
async def close_ticket(self, interaction: disnake.Interaction, button: disnake.ui.Button):
if not interaction.bot.get_cog("Blacklist").is_agent(interaction.user.id):
return await interaction.response.send_message("⛔ Seuls les agents peuvent fermer ce ticket.", ephemeral=True)
view = ConfirmCloseView(self.user_id)
await interaction.response.send_message("⚠️ Veux-tu vraiment fermer ce ticket ?", view=view, ephemeral=True)
class ConfirmCloseView(discord.ui.View):
class ConfirmCloseView(disnake.ui.View):
def __init__(self, user_id: int):
super().__init__(timeout=30)
self.user_id = user_id
@discord.ui.button(label="✅ Oui", style=discord.ButtonStyle.success, custom_id="blacklist_confirm_close")
async def confirm(self, interaction: discord.Interaction, button: discord.ui.Button):
if not interaction.client.get_cog("Blacklist").is_agent(interaction.user.id):
@disnake.ui.button(label="✅ Oui", style=disnake.ButtonStyle.success, custom_id="blacklist_confirm_close")
async def confirm(self, interaction: disnake.Interaction, button: disnake.ui.Button):
if not interaction.bot.get_cog("Blacklist").is_agent(interaction.user.id):
return await interaction.response.send_message("⛔ Tu nas pas la permission.", ephemeral=True)
tickets = load_json(TICKETS_FILE, {})
@ -268,19 +267,19 @@ class ConfirmCloseView(discord.ui.View):
del tickets[str(self.user_id)]
save_json(TICKETS_FILE, tickets)
user = await interaction.client.fetch_user(int(self.user_id))
user = await interaction.bot.fetch_user(int(self.user_id))
try:
embed = discord.Embed(
embed = disnake.Embed(
title="🔒 Ticket fermé",
description="Ton ticket a été examiné et fermé.\n\n⚠️ Après analyse, la sanction est confirmée et tu restes blacklisté de lensemble du réseau.",
color=discord.Color.orange()
color=disnake.Color.orange()
)
await user.send(embed=embed)
await asyncio.sleep(2)
except:
pass
for guild in interaction.client.guilds:
for guild in interaction.bot.guilds:
member = guild.get_member(self.user_id)
if member:
try:
@ -290,8 +289,8 @@ class ConfirmCloseView(discord.ui.View):
await interaction.response.send_message("✅ Ticket fermé avec succès.", ephemeral=True)
@discord.ui.button(label="❌ Non", style=discord.ButtonStyle.secondary, custom_id="blacklist_cancel_close")
async def cancel(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="❌ Non", style=disnake.ButtonStyle.secondary, custom_id="blacklist_cancel_close")
async def cancel(self, interaction: disnake.Interaction, button: disnake.ui.Button):
await interaction.response.send_message("❌ Fermeture annulée.", ephemeral=True)
# --------- Setup ---------

View file

@ -1,6 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
import disnake
from disnake.ext import commands
from utils.gitlab_client import gitlab_client
from aiohttp import web
import logging
@ -33,27 +32,27 @@ def save_report(issue_iid, user_id):
except Exception as e:
kuby_logger.error(f"Error saving report to JSON: {e}")
class BugReportModal(discord.ui.Modal, title="Signaler un Bug"):
class BugReportModal(disnake.ui.Modal):
def __init__(self, priority_choice):
super().__init__()
super().__init__(title="Signaler un Bug")
self.priority_choice = priority_choice
bug_title = discord.ui.TextInput(
bug_title = disnake.ui.TextInput(
label="Titre du bug",
placeholder="Décrivez brièvement le problème...",
required=True,
max_length=100
)
description = discord.ui.TextInput(
description = disnake.ui.TextInput(
label="Description détaillée",
style=discord.TextStyle.paragraph,
style=disnake.TextStyle.paragraph,
placeholder="Que s'est-il passé ? Comment reproduire le bug ?",
required=True,
max_length=1000
)
async def on_submit(self, interaction: discord.Interaction):
async def callback(self, interaction: disnake.Interaction):
await interaction.response.send_message("Envoi de votre rapport de bug à GitLab...", ephemeral=True)
description_text = f"**Rapporté par:** {interaction.user} ({interaction.user.id})\n"
@ -76,23 +75,26 @@ class BugReportModal(discord.ui.Modal, title="Signaler un Bug"):
else:
await interaction.edit_original_response(content="❌ Une erreur est survenue lors de l'envoi du rapport à GitLab. Veuillez contacter un administrateur.")
class FeatureSuggestionModal(discord.ui.Modal, title="Suggérer une fonctionnalité"):
suggestion_title = discord.ui.TextInput(
class FeatureSuggestionModal(disnake.ui.Modal):
def __init__(self):
super().__init__(title="Suggérer une fonctionnalité")
suggestion_title = disnake.ui.TextInput(
label="Titre de la suggestion",
placeholder="Que voulez-vous ajouter ?",
required=True,
max_length=100
)
description = discord.ui.TextInput(
description = disnake.ui.TextInput(
label="Détails de la fonctionnalité",
style=discord.TextStyle.paragraph,
style=disnake.TextStyle.paragraph,
placeholder="Décrivez comment cela devrait fonctionner...",
required=True,
max_length=1000
)
async def on_submit(self, interaction: discord.Interaction):
async def callback(self, interaction: disnake.Interaction):
await interaction.response.send_message("Envoi de votre suggestion à GitLab...", ephemeral=True)
description_text = f"**Suggéré par:** {interaction.user} ({interaction.user.id})\n\n"
@ -231,10 +233,10 @@ class BugReport(commands.Cog):
try:
# Création de l'embed pour la mise à jour de statut
embed = discord.Embed(
embed = disnake.Embed(
title="🛠️ Mise à jour de votre signalement !",
description=f"Le statut de votre rapport **{issue_title}** a été mis à jour par l'équipe.",
color=discord.Color.blue()
color=disnake.Color.blue()
)
embed.add_field(name="Nouveaux Labels / Statuts", value=current_labels_str)
@ -244,7 +246,7 @@ class BugReport(commands.Cog):
kuby_logger.info(f"Notification de label envoyée à {user} ({user.id}) pour l'issue #{issue_iid}")
if dev and dev.id != user.id:
await dev.send(f"✅ [SUIVI] L'utilisateur {user} ({user.id}) a bien reçu la notification de statut **{current_labels_str}** pour l'issue #{issue_iid} ({issue_title}).")
except discord.Forbidden:
except disnake.Forbidden:
kuby_logger.warning(f"Impossible d'envoyer un MP à {user} ({user.id}) - MPs désactivés.")
if dev and dev.id != user.id:
await dev.send(f"⚠️ [DM BLOQUÉ] {user} ({user.id}) a ses MPs fermés. Impossible de notifier pour l'issue #{issue_iid}.")
@ -259,10 +261,10 @@ class BugReport(commands.Cog):
if is_closing_now:
try:
embed = discord.Embed(
embed = disnake.Embed(
title="🛠️ Bug / Suggestion Terminé(e) !",
description=f"Bonne nouvelle ! Votre signalement **{issue_title}** a été marqué comme terminé ou résolu.",
color=discord.Color.green()
color=disnake.Color.green()
)
embed.add_field(name="Prochaine étape", value="La mise à jour arrive prochainement (ou est déjà là) !")
embed.set_footer(text="Merci pour votre aide !")
@ -272,7 +274,7 @@ class BugReport(commands.Cog):
kuby_logger.info(f"Notification de clôture envoyée à {user} ({user.id}) pour l'issue #{issue_iid}")
if dev and dev.id != user.id:
await dev.send(f"✅ [SUIVI] L'utilisateur {user} ({user.id}) a bien reçu la notification de CLÔTURE pour l'issue #{issue_iid}.")
except discord.Forbidden:
except disnake.Forbidden:
kuby_logger.warning(f"Impossible d'envoyer le MP de clôture à {user} ({user.id}).")
if dev and dev.id != user.id:
await dev.send(f"⚠️ [DM BLOQUÉ] Impossible d'annoncer la clôture à {user} ({user.id}) pour l'issue #{issue_iid} (DMs désactivés).")
@ -290,10 +292,10 @@ class BugReport(commands.Cog):
author_name = payload.get("user", {}).get("name", "Développeur")
try:
embed = discord.Embed(
embed = disnake.Embed(
title="💬 Nouveau commentaire sur votre signalement",
description=f"Le développeur a répondu à votre rapport **{issue_title}** :\n\n>>> {note_body}",
color=discord.Color.orange()
color=disnake.Color.orange()
)
embed.set_footer(text=f"Par {author_name}")
@ -302,7 +304,7 @@ class BugReport(commands.Cog):
kuby_logger.info(f"Commentaire GitLab envoyé à {user} ({user.id}) pour l'issue #{issue_iid}")
if dev and dev.id != user.id:
await dev.send(f"✅ [SUIVI] L'utilisateur {user} ({user.id}) a bien reçu votre commentaire pour l'issue #{issue_iid}.")
except discord.Forbidden:
except disnake.Forbidden:
kuby_logger.warning(f"Impossible d'envoyer le commentaire en MP à {user} ({user.id}).")
if dev and dev.id != user.id:
await dev.send(f"⚠️ [DM BLOQUÉ] Impossible d'envoyer votre commentaire à {user} ({user.id}) pour l'issue #{issue_iid} (DMs désactivés).")
@ -317,18 +319,19 @@ class BugReport(commands.Cog):
kuby_logger.error(f"Error handling bot event: {e}")
return web.json_response({"status": "error", "message": str(e)}, status=500)
@app_commands.command(name="signaler_bug", description="Signaler un bug aux développeurs")
@app_commands.choices(priority=[
app_commands.Choice(name="Basse", value="Low"),
app_commands.Choice(name="Normale", value="Normal"),
app_commands.Choice(name="Haute", value="High"),
app_commands.Choice(name="Urgente", value="Urgent"),
])
async def signaler_bug(self, interaction: discord.Interaction, priority: app_commands.Choice[str]):
await interaction.response.send_modal(BugReportModal(priority))
@commands.slash_command(name="signaler_bug", description="Signaler un bug aux développeurs")
async def signaler_bug(self, interaction: disnake.ApplicationCommandInteraction,
priority: str = commands.Param(description="Priorité du bug", choices={"Basse": "Low", "Normale": "Normal", "Haute": "High", "Urgente": "Urgent"})):
# Simulate app_commands.Choice behavior for existing code
from collections import namedtuple
Choice = namedtuple('Choice', ['name', 'value'])
# find the name from the value
p_name = [k for k, v in {"Basse": "Low", "Normale": "Normal", "Haute": "High", "Urgente": "Urgent"}.items() if v == priority][0]
choice = Choice(name=p_name, value=priority)
await interaction.response.send_modal(BugReportModal(choice))
@app_commands.command(name="suggerer_fonctionnalite", description="Proposer une nouvelle fonctionnalité pour le bot")
async def suggerer_fonctionnalite(self, interaction: discord.Interaction):
@commands.slash_command(name="suggerer_fonctionnalite", description="Proposer une nouvelle fonctionnalité pour le bot")
async def suggerer_fonctionnalite(self, interaction: disnake.ApplicationCommandInteraction):
await interaction.response.send_modal(FeatureSuggestionModal())
async def setup(bot):

View file

@ -1,6 +1,5 @@
import discord
from discord.ext import commands
from discord import app_commands
import disnake
from disnake.ext import commands
import json
import os
from typing import List, Optional, Dict
@ -52,20 +51,17 @@ class Convocation(commands.Cog):
del self.backups[key]
save_json(CONV_BACKUPS_FILE, self.backups)
@commands.hybrid_group(name="convocation", description="Gère les convocations de membres")
@commands.slash_command(name="convocation", description="Gère les convocations de membres")
@commands.has_permissions(manage_roles=True)
async def convocation_group(self, ctx):
if ctx.invoked_subcommand is None:
await ctx.send_help(ctx.command)
async def convocation_group(self, interaction: disnake.ApplicationCommandInteraction):
pass
@convocation_group.command(name="convoquer", description="Convoque un membre et verrouille ses accès")
@app_commands.describe(
membre="Le membre à convoquer",
raison="La raison de la convocation",
heure="L'heure de la convocation (ex: 14:30)"
)
async def convoquer(self, ctx, membre: discord.Member, raison: str, heure: str):
guild_id = ctx.guild.id
@convocation_group.sub_command(name="convoquer", description="Convoque un membre et verrouille ses accès")
async def convoquer(self, interaction: disnake.ApplicationCommandInteraction,
membre: disnake.Member = commands.Param(description="Le membre à convoquer"),
raison: str = commands.Param(description="La raison de la convocation"),
heure: str = commands.Param(description="L'heure de la convocation (ex: 14:30)")):
guild_id = interaction.guild.id
settings = self.get_guild_settings(guild_id)
role_conv_id = settings.get("role_id")
log_channel_id = settings.get("log_channel_id")
@ -73,30 +69,30 @@ class Convocation(commands.Cog):
admin_ids = settings.get("admin_ids", [])
if not role_conv_id:
await ctx.send("❌ Le rôle de convocation n'est pas configuré. Utilisez `/convocation config`.", ephemeral=True)
await interaction.response.send_message("❌ Le rôle de convocation n'est pas configuré. Utilisez `/convocation config`.", ephemeral=True)
return
await ctx.defer(ephemeral=True)
await interaction.response.defer(ephemeral=True)
try:
# --- BACKUP ---
bot_top_role = ctx.guild.me.top_role
bot_top_role = interaction.guild.me.top_role
backup_data = {
"roles": [r.id for r in membre.roles if not r.is_default() and r.id != role_conv_id and r.position < bot_top_role.position],
"overrides": {}
}
# 1. Backup and Remove current roles
current_roles = [ctx.guild.get_role(rid) for rid in backup_data["roles"] if ctx.guild.get_role(rid)]
current_roles = [interaction.guild.get_role(rid) for rid in backup_data["roles"] if interaction.guild.get_role(rid)]
if current_roles:
try:
await membre.remove_roles(*current_roles, reason=f"Convocation par {ctx.author}")
except discord.Forbidden:
await ctx.send("⚠️ Je n'ai pas pu retirer certains rôles (Permission refusée).", ephemeral=True)
await membre.remove_roles(*current_roles, reason=f"Convocation par {interaction.author}")
except disnake.Forbidden:
await interaction.followup.send("⚠️ Je n'ai pas pu retirer certains rôles (Permission refusée).", ephemeral=True)
# 2. Dynamic Lockdown: Loop through ALL channels
lockdown_count = 0
for channel in ctx.guild.channels:
for channel in interaction.guild.channels:
# On ignore le salon d'attente et ses parents s'il y en a
if attente_id and (channel.id == attente_id or (hasattr(channel, 'category') and channel.category and channel.category.id == attente_id)):
continue
@ -112,28 +108,28 @@ class Convocation(commands.Cog):
# Specific override for this member: View Channel = False
await channel.set_permissions(membre, view_channel=False, reason="Convocation Lockdown")
lockdown_count += 1
except discord.Forbidden:
except disnake.Forbidden:
pass # Skip if we can't touch this channel
self.save_backup(guild_id, membre.id, backup_data)
# 3. Add Convocation role
role_conv = ctx.guild.get_role(role_conv_id)
role_conv = interaction.guild.get_role(role_conv_id)
if role_conv:
if role_conv.position >= bot_top_role.position:
await ctx.send(f"⚠️ Je ne peux pas donner le rôle {role_conv.mention} car il est plus haut que moi !", ephemeral=True)
await interaction.followup.send(f"⚠️ Je ne peux pas donner le rôle {role_conv.mention} car il est plus haut que moi !", ephemeral=True)
else:
try:
await membre.add_roles(role_conv, reason="Début de convocation")
except discord.Forbidden:
await ctx.send(f"⚠️ Impossible de donner le rôle {role_conv.name} (Permission refusée).", ephemeral=True)
except disnake.Forbidden:
await interaction.followup.send(f"⚠️ Impossible de donner le rôle {role_conv.name} (Permission refusée).", ephemeral=True)
# 4. Embeds & Logs (Inchangés mais inclus pour la cohérence)
embed_conv = discord.Embed(
embed_conv = disnake.Embed(
title="CONVOCATION | 🔖",
description=f"> Bonjour {membre.mention}, vous êtes **convoqué** par {ctx.author.mention}. Voici les **détails** :",
description=f"> Bonjour {membre.mention}, vous êtes **convoqué** par {interaction.author.mention}. Voici les **détails** :",
color=0x0099ff,
timestamp=discord.utils.utcnow()
timestamp=disnake.utils.utcnow()
)
embed_conv.add_field(name="**Raison de la convocation :**", value=f"{raison}", inline=False)
embed_conv.add_field(name="**Heure de la convocation :**", value=f"{heure}", inline=False)
@ -141,15 +137,15 @@ class Convocation(commands.Cog):
"> Merci de vous présenter dans le vocal attente **5 minutes** avant l'heure.\n\n"
"> La **levée** de suspension se fera uniquement à la **fin** de la convocation."
), inline=False)
embed_conv.set_footer(text=f"Gestion de {ctx.guild.name}")
embed_conv.set_footer(text=f"Gestion de {interaction.guild.name}")
embed_log = discord.Embed(
embed_log = disnake.Embed(
title="📋 CONVOCATION ENVOYÉE",
color=0xff9900,
timestamp=discord.utils.utcnow()
timestamp=disnake.utils.utcnow()
)
embed_log.add_field(name="👤 Membre convoqué", value=f"{membre.mention} ({membre.id})", inline=True)
embed_log.add_field(name="👮 Convoqué par", value=f"{ctx.author.mention}", inline=True)
embed_log.add_field(name="👮 Convoqué par", value=f"{interaction.author.mention}", inline=True)
embed_log.add_field(name="📄 Raison", value=raison, inline=False)
embed_log.add_field(name="🕒 Heure", value=heure, inline=False)
@ -158,7 +154,7 @@ class Convocation(commands.Cog):
try:
await membre.send(embed=embed_conv)
dm_sent = True
except discord.Forbidden:
except disnake.Forbidden:
dm_sent = False
# Admin & Channel Logs
@ -169,47 +165,47 @@ class Convocation(commands.Cog):
except: pass
if log_channel_id:
chan = ctx.guild.get_channel(log_channel_id)
chan = interaction.guild.get_channel(log_channel_id)
if chan: await chan.send(embed=embed_log)
await ctx.send(f"✅ Convocation envoyée à {membre.mention}. ({lockdown_count} salons verrouillés) " +
await interaction.followup.send(f"✅ Convocation envoyée à {membre.mention}. ({lockdown_count} salons verrouillés) " +
("" if dm_sent else "(⚠️ DM impossible)"), ephemeral=True)
except Exception as e:
import traceback
traceback.print_exc()
await ctx.send(f"❌ Une erreur critique est survenue : {e}", ephemeral=True)
await interaction.followup.send(f"❌ Une erreur critique est survenue : {e}", ephemeral=True)
@convocation_group.command(name="lever", description="Lève la convocation d'un membre et restaure ses accès")
@app_commands.describe(membre="Le membre à libérer")
async def lever(self, ctx, membre: discord.Member):
guild_id = ctx.guild.id
@convocation_group.sub_command(name="lever", description="Lève la convocation d'un membre et restaure ses accès")
async def lever(self, interaction: disnake.ApplicationCommandInteraction,
membre: disnake.Member = commands.Param(description="Le membre à libérer")):
guild_id = interaction.guild.id
settings = self.get_guild_settings(guild_id)
role_conv_id = settings.get("role_id")
await ctx.defer(ephemeral=True)
await interaction.response.defer(ephemeral=True)
backup_data = self.get_backup(guild_id, membre.id)
if not backup_data:
await ctx.send("⚠️ Aucune donnée de convocation trouvée pour ce membre.", ephemeral=True)
await interaction.followup.send("⚠️ Aucune donnée de convocation trouvée pour ce membre.", ephemeral=True)
return
# 1. Remove Convocation role
if role_conv_id:
role_conv = ctx.guild.get_role(role_conv_id)
role_conv = interaction.guild.get_role(role_conv_id)
if role_conv and role_conv in membre.roles:
await membre.remove_roles(role_conv, reason="Fin de convocation")
# 2. Restore channel overrides
restore_count = 0
overrides = backup_data.get("overrides", {})
for channel in ctx.guild.channels:
for channel in interaction.guild.channels:
chan_id_str = str(channel.id)
try:
if chan_id_str in overrides:
# Restore previous override
ov_data = overrides[chan_id_str]
overwrite = discord.PermissionOverwrite(**ov_data)
overwrite = disnake.PermissionOverwrite(**ov_data)
await channel.set_permissions(membre, overwrite=overwrite, reason="Restauration fin de convocation")
restore_count += 1
else:
@ -217,7 +213,7 @@ class Convocation(commands.Cog):
# On ne vide que si un override membre existait (sinon on risque de supprimer des perms rôles ?)
# En fait set_permissions(member, overwrite=None) supprime l'entrée PERSONNELLE du membre.
await channel.set_permissions(membre, overwrite=None, reason="Fin de convocation - Cleanup")
except discord.Forbidden:
except disnake.Forbidden:
pass
# 3. Restore roles
@ -225,8 +221,8 @@ class Convocation(commands.Cog):
restored_roles = []
if role_ids:
for rid in role_ids:
role = ctx.guild.get_role(rid)
if role and role.position < ctx.guild.me.top_role.position:
role = interaction.guild.get_role(rid)
if role and role.position < interaction.guild.me.top_role.position:
restored_roles.append(role)
if restored_roles:
@ -235,27 +231,24 @@ class Convocation(commands.Cog):
self.clear_backup(guild_id, membre.id)
# 4. Notification
embed = discord.Embed(
embed = disnake.Embed(
title="🔓 CONVOCATION TERMINÉE",
description=f"Votre convocation sur **{ctx.guild.name}** est terminée. Vos accès ont été restaurés.",
color=discord.Color.green()
description=f"Votre convocation sur **{interaction.guild.name}** est terminée. Vos accès ont été restaurés.",
color=disnake.Color.green()
)
try:
await membre.send(embed=embed)
except: pass
await ctx.send(f"✅ Convocation levée pour {membre.mention}. Access restaurés.", ephemeral=True)
await interaction.followup.send(f"✅ Convocation levée pour {membre.mention}. Access restaurés.", ephemeral=True)
@convocation_group.command(name="config", description="Configure le système de convocation")
@app_commands.describe(
role="Le rôle de convocation (Muet/Convocation)",
channel="Le salon de logs",
attente="Salon (ou catégorie) d'attente à laisser visible",
admins="Liste d'IDs d'admins à notifier (séparés par des espaces)"
)
@commands.has_permissions(administrator=True)
async def config(self, ctx, role: discord.Role, channel: discord.TextChannel = None, attente: discord.abc.GuildChannel = None, admins: str = None):
guild_id = ctx.guild.id
@convocation_group.sub_command(name="config", description="Configure le système de convocation")
async def config(self, interaction: disnake.ApplicationCommandInteraction,
role: disnake.Role = commands.Param(description="Le rôle de convocation (Muet/Convocation)"),
channel: disnake.TextChannel = commands.Param(None, description="Le salon de logs"),
attente: disnake.abc.GuildChannel = commands.Param(None, description="Salon (ou catégorie) d'attente à laisser visible"),
admins: str = commands.Param(None, description="Liste d'IDs d'admins à notifier (séparés par des espaces)")):
guild_id = interaction.guild.id
settings = self.get_guild_settings(guild_id)
settings["role_id"] = role.id
@ -273,13 +266,13 @@ class Convocation(commands.Cog):
self.save_guild_settings(guild_id, settings)
embed = discord.Embed(title="⚙️ Configuration Convocation Mise à jour", color=discord.Color.blue())
embed = disnake.Embed(title="⚙️ Configuration Convocation Mise à jour", color=disnake.Color.blue())
embed.add_field(name="Rôle", value=role.mention)
embed.add_field(name="Salon Logs", value=channel.mention if channel else "Non défini")
embed.add_field(name="Salon/Cat d'attente", value=attente.mention if attente else "Non défini")
embed.add_field(name="Admins notifiés", value=str(len(settings.get("admin_ids", []))))
await ctx.send(embed=embed, ephemeral=True)
await interaction.response.send_message(embed=embed, ephemeral=True)
async def setup(bot):
await bot.add_cog(Convocation(bot))

View file

@ -1,6 +1,5 @@
import discord
from discord.ext import commands
from discord import app_commands
import disnake
from disnake.ext import commands
import sqlite3
import aiohttp
import io
@ -148,14 +147,14 @@ class Goodbye(commands.Cog):
# Utiliser le message personnalisé ou un par défaut
description = custom_message if custom_message else "Nous espérons vous revoir bientôt <a:rain:1317973615714504806>"
embed = discord.Embed(title=f"{member} viens de nous quitter.",
embed = disnake.Embed(title=f"{member} viens de nous quitter.",
description=description,
color=discord.Color.dark_magenta())
color=disnake.Color.dark_magenta())
embed.set_footer(text=f"Avait rejoint {joined_message}")
embed.set_image(url="attachment://goodbye.png")
await channel.send(embed=embed, file=discord.File(img, filename="goodbye.png"))
await channel.send(embed=embed, file=disnake.File(img, filename="goodbye.png"))
async def save_banner_attachment(self, attachment: discord.Attachment, guild_id: int) -> str:
async def save_banner_attachment(self, attachment: disnake.Attachment, guild_id: int) -> str:
"""Sauvegarde l'attachment et retourne le chemin du fichier"""
base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "banners"))
os.makedirs(base_dir, exist_ok=True)
@ -187,9 +186,9 @@ class Goodbye(commands.Cog):
return os.path.basename(banner_path)
@app_commands.command(name="setgoodbye", description="Définit le salon d'aurevoir et les paramètres personnalisés")
@app_commands.checks.has_permissions(administrator=True)
async def set_goodbye(self, interaction: discord.Interaction, channel: discord.TextChannel, server_name: str = None, message: str = None, banner_file: discord.Attachment = None):
@commands.slash_command(name="setgoodbye", description="Définit le salon d'aurevoir et les paramètres personnalisés")
@commands.has_permissions(administrator=True)
async def set_goodbye(self, interaction: disnake.ApplicationCommandInteraction, channel: disnake.TextChannel, server_name: str = None, message: str = None, banner_file: disnake.Attachment = None):
banner_path = None
# Si un fichier est uploadé, le sauvegarder
@ -208,7 +207,7 @@ class Goodbye(commands.Cog):
goodbye_server_name=COALESCE(excluded.goodbye_server_name, goodbye_server_name),
goodbye_message=COALESCE(excluded.goodbye_message, goodbye_message),
goodbye_banner_background=COALESCE(excluded.goodbye_banner_background, goodbye_banner_background)''',
(interaction.guild_id, channel.id, server_name, message, banner_path))
(interaction.guild_id, channel.id, server_name, message, banner_path))
conn.commit()
conn.close()

View file

@ -1,18 +1,17 @@
import discord
from discord.ext import commands
from discord import app_commands
import disnake
from disnake.ext import commands
import sqlite3
import os
from datetime import datetime, timedelta
import re
class ModReasonModal(discord.ui.Modal):
class ModReasonModal(disnake.ui.Modal):
def __init__(self, parent_view, action_type):
super().__init__(title=f"Raison pour {action_type}")
self.parent_view = parent_view
self.action_type = action_type
self.reason = discord.ui.TextInput(
self.reason = disnake.ui.TextInput(
label="Raison",
placeholder="Entrez la raison ici...",
required=True,
@ -21,7 +20,7 @@ class ModReasonModal(discord.ui.Modal):
self.add_item(self.reason)
if action_type == "TIMEOUT":
self.duration = discord.ui.TextInput(
self.duration = disnake.ui.TextInput(
label="Durée (ex: 1h, 30m, 1d)",
placeholder="1h",
required=True,
@ -29,11 +28,11 @@ class ModReasonModal(discord.ui.Modal):
)
self.add_item(self.duration)
async def on_submit(self, interaction: discord.Interaction):
async def callback(self, interaction: disnake.Interaction):
reason = self.reason.value
member = self.parent_view.member
cog = interaction.client.get_cog("Moderation")
cog = interaction.bot.get_cog("Moderation")
if not cog:
return await interaction.response.send_message("❌ Erreur interne.", ephemeral=True)
@ -48,24 +47,24 @@ class ModReasonModal(discord.ui.Modal):
await self.parent_view.update_panel(interaction)
class ModPanelView(discord.ui.View):
class ModPanelView(disnake.ui.View):
def __init__(self, member, moderator, db_path):
super().__init__(timeout=120)
self.member = member
self.moderator = moderator
self.db_path = db_path
async def update_panel(self, interaction: discord.Interaction):
async def update_panel(self, interaction: disnake.Interaction):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('SELECT COUNT(*) FROM sanctions WHERE guild_id = ? AND user_id = ? AND type = "WARN" AND status = "ACTIVE"', (interaction.guild_id, self.member.id))
warn_count = cursor.fetchone()[0]
conn.close()
embed = discord.Embed(
embed = disnake.Embed(
title=f"🛡️ Panel de Modération - {self.member.name}",
description=f"Gestion de l'utilisateur {self.member.mention}",
color=discord.Color.blue(),
color=disnake.Color.blue(),
timestamp=datetime.now()
)
embed.set_thumbnail(url=self.member.display_avatar.url)
@ -78,38 +77,38 @@ class ModPanelView(discord.ui.View):
else:
await interaction.edit_original_response(embed=embed, view=self)
@discord.ui.button(label="Avertir", style=discord.ButtonStyle.secondary, emoji="⚠️")
async def warn_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="Avertir", style=disnake.ButtonStyle.secondary, emoji="⚠️")
async def warn_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button):
await interaction.response.send_modal(ModReasonModal(self, "WARN"))
@discord.ui.button(label="Timeout", style=discord.ButtonStyle.secondary, emoji="")
async def timeout_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="Timeout", style=disnake.ButtonStyle.secondary, emoji="")
async def timeout_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button):
await interaction.response.send_modal(ModReasonModal(self, "TIMEOUT"))
@discord.ui.button(label="Expulser", style=discord.ButtonStyle.secondary, emoji="👢")
async def kick_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="Expulser", style=disnake.ButtonStyle.secondary, emoji="👢")
async def kick_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button):
await interaction.response.send_modal(ModReasonModal(self, "KICK"))
@discord.ui.button(label="Bannir", style=discord.ButtonStyle.danger, emoji="🔨")
async def ban_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="Bannir", style=disnake.ButtonStyle.danger, emoji="🔨")
async def ban_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button):
await interaction.response.send_modal(ModReasonModal(self, "BAN"))
@discord.ui.button(label="Historique", style=discord.ButtonStyle.primary, emoji="📜")
async def history_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
cog = interaction.client.get_cog("Moderation")
@disnake.ui.button(label="Historique", style=disnake.ButtonStyle.primary, emoji="📜")
async def history_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button):
cog = interaction.bot.get_cog("Moderation")
await cog.history(interaction, self.member)
@discord.ui.button(label="Clear Warns", style=discord.ButtonStyle.danger, emoji="🧹")
async def clear_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
cog = interaction.client.get_cog("Moderation")
@disnake.ui.button(label="Clear Warns", style=disnake.ButtonStyle.danger, emoji="🧹")
async def clear_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button):
cog = interaction.bot.get_cog("Moderation")
await cog.clearwarns(interaction, self.member, reason="Nettoyage via Panel")
await self.update_panel(interaction)
class ModConfigModal(discord.ui.Modal):
class ModConfigModal(disnake.ui.Modal):
def __init__(self, field_name, current_value):
super().__init__(title=f"Modifier {field_name}")
self.field_name = field_name
self.input = discord.ui.TextInput(
self.input = disnake.ui.TextInput(
label=field_name,
default=str(current_value),
placeholder="Entrez une valeur numérique...",
@ -117,9 +116,9 @@ class ModConfigModal(discord.ui.Modal):
)
self.add_item(self.input)
async def on_submit(self, interaction: discord.Interaction):
async def callback(self, interaction: disnake.Interaction):
value = self.input.value
cog = interaction.client.get_cog("Moderation")
cog = interaction.bot.get_cog("Moderation")
mapping = {
"Limite Timeout": "warn_limit_timeout",
@ -140,21 +139,21 @@ class ModConfigModal(discord.ui.Modal):
await interaction.response.send_message(f"{self.field_name} mis à jour sur **{value}**.", ephemeral=True)
class ModConfigView(discord.ui.View):
class ModConfigView(disnake.ui.View):
def __init__(self, guild_id, db_path):
super().__init__(timeout=60)
self.guild_id = guild_id
self.db_path = db_path
@discord.ui.button(label="Log Channel", style=discord.ButtonStyle.primary, emoji="📁")
async def log_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
view = discord.ui.View()
select = discord.ui.ChannelSelect(
@disnake.ui.button(label="Log Channel", style=disnake.ButtonStyle.primary, emoji="📁")
async def log_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button):
view = disnake.ui.View()
select = disnake.ui.ChannelSelect(
placeholder="Sélectionnez le salon de logs...",
channel_types=[discord.ChannelType.text]
channel_types=[disnake.ChannelType.text]
)
async def select_callback(interaction: discord.Interaction):
async def select_callback(interaction: disnake.Interaction):
channel = select.values[0]
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
@ -166,29 +165,29 @@ class ModConfigView(discord.ui.View):
view.add_item(select)
await interaction.response.send_message("Choisissez le salon de logs :", view=view, ephemeral=True)
@discord.ui.button(label="Paliers Warns", style=discord.ButtonStyle.secondary, emoji="⚖️")
async def limits_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="Paliers Warns", style=disnake.ButtonStyle.secondary, emoji="⚖️")
async def limits_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT warn_limit_timeout, warn_limit_kick, warn_limit_ban FROM mod_config WHERE guild_id = ?", (self.guild_id,))
res = cursor.fetchone(); conn.close()
view = discord.ui.View()
view = disnake.ui.View()
btn_t = discord.ui.Button(label=f"Timeout ({res[0]})", style=discord.ButtonStyle.secondary)
btn_t = disnake.ui.Button(label=f"Timeout ({res[0]})", style=disnake.ButtonStyle.secondary)
btn_t.callback = lambda i: i.response.send_modal(ModConfigModal("Limite Timeout", res[0]))
btn_k = discord.ui.Button(label=f"Kick ({res[1]})", style=discord.ButtonStyle.secondary)
btn_k = disnake.ui.Button(label=f"Kick ({res[1]})", style=disnake.ButtonStyle.secondary)
btn_k.callback = lambda i: i.response.send_modal(ModConfigModal("Limite Kick", res[1]))
btn_b = discord.ui.Button(label=f"Ban ({res[2]})", style=discord.ButtonStyle.secondary)
btn_b = disnake.ui.Button(label=f"Ban ({res[2]})", style=disnake.ButtonStyle.secondary)
btn_b.callback = lambda i: i.response.send_modal(ModConfigModal("Limite Ban", res[2]))
view.add_item(btn_t); view.add_item(btn_k); view.add_item(btn_b)
await interaction.response.send_message("Quelle limite modifier ?", view=view, ephemeral=True)
@discord.ui.button(label="Durée Timeout", style=discord.ButtonStyle.secondary, emoji="⏱️")
async def duration_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="Durée Timeout", style=disnake.ButtonStyle.secondary, emoji="⏱️")
async def duration_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT timeout_duration FROM mod_config WHERE guild_id = ?", (self.guild_id,))
@ -225,38 +224,38 @@ class Moderation(commands.Cog):
log_channel = guild.get_channel(result[0])
if log_channel: await log_channel.send(embed=embed)
@app_commands.command(name="modpanel", description="Ouvre le panel de modération pour un membre")
@app_commands.checks.has_permissions(moderate_members=True)
async def modpanel(self, interaction: discord.Interaction, member: discord.Member):
@commands.slash_command(name="modpanel", description="Ouvre le panel de modération pour un membre")
@commands.has_permissions(moderate_members=True)
async def modpanel(self, interaction: disnake.ApplicationCommandInteraction, member: disnake.Member):
if member.top_role >= interaction.user.top_role and interaction.user.id != interaction.guild.owner_id:
return await interaction.response.send_message("❌ Impossible de gérer ce membre.", ephemeral=True)
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('SELECT COUNT(*) FROM sanctions WHERE guild_id = ? AND user_id = ? AND type = "WARN" AND status = "ACTIVE"', (interaction.guild_id, member.id))
warn_count = cursor.fetchone()[0]; conn.close()
embed = discord.Embed(title=f"🛡️ Panel de Modération - {member.name}", description=f"Gestion de {member.mention}", color=discord.Color.blue(), timestamp=datetime.now())
embed = disnake.Embed(title=f"🛡️ Panel de Modération - {member.name}", description=f"Gestion de {member.mention}", color=disnake.Color.blue(), timestamp=datetime.now())
embed.set_thumbnail(url=member.display_avatar.url)
embed.add_field(name="👤 Utilisateur", value=f"ID: `{member.id}`", inline=True)
embed.add_field(name="⚠️ Avertissements", value=f"Actifs: **{warn_count}**", inline=True)
view = ModPanelView(member, interaction.user, self.db_path)
await interaction.response.send_message(embed=embed, view=view, ephemeral=True)
@app_commands.command(name="modconfig", description="Configuration de la modération")
@app_commands.checks.has_permissions(administrator=True)
async def modconfig(self, interaction: discord.Interaction):
@commands.slash_command(name="modconfig", description="Configuration de la modération")
@commands.has_permissions(administrator=True)
async def modconfig(self, interaction: disnake.ApplicationCommandInteraction):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('INSERT OR IGNORE INTO mod_config (guild_id) VALUES (?)', (interaction.guild_id,))
cursor.execute('SELECT log_channel_id, warn_limit_timeout, warn_limit_kick, warn_limit_ban, timeout_duration FROM mod_config WHERE guild_id = ?', (interaction.guild_id,))
res = cursor.fetchone(); conn.commit(); conn.close()
embed = discord.Embed(title="⚙️ Configuration Modération", description="Modifiez les paramètres ci-dessous.", color=discord.Color.blue())
embed = disnake.Embed(title="⚙️ Configuration Modération", description="Modifiez les paramètres ci-dessous.", color=disnake.Color.blue())
embed.add_field(name="📁 Logs", value=f"<#{res[0]}>" if res[0] else "Non défini", inline=False)
embed.add_field(name="⚖️ Paliers", value=f"Timeout: **{res[1]}**, Kick: **{res[2]}**, Ban: **{res[3]}**", inline=False)
embed.add_field(name="⏱️ Durée Timeout Auto", value=f"**{res[4]}s**", inline=False)
view = ModConfigView(interaction.guild_id, self.db_path)
await interaction.response.send_message(embed=embed, view=view, ephemeral=True)
async def warn(self, interaction: discord.Interaction, member: discord.Member, reason: str):
async def warn(self, interaction: disnake.Interaction, member: disnake.Member, reason: str):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('INSERT INTO sanctions (guild_id, user_id, moderator_id, type, reason) VALUES (?, ?, ?, ?, ?)', (interaction.guild_id, member.id, interaction.user.id, 'WARN', reason))
@ -266,26 +265,26 @@ class Moderation(commands.Cog):
config = cursor.fetchone(); conn.commit(); conn.close()
if not interaction.response.is_done(): await interaction.response.send_message(f"{member.mention} averti ({warn_count}).", ephemeral=True)
else: await interaction.followup.send(f"{member.mention} averti ({warn_count}).", ephemeral=True)
await self.send_mod_log(interaction.guild, discord.Embed(title="⚠️ Avertissement", description=f"Membre: {member.mention}\nRaison: {reason}\nTotal: {warn_count}", color=discord.Color.orange()))
await self.send_mod_log(interaction.guild, disnake.Embed(title="⚠️ Avertissement", description=f"Membre: {member.mention}\nRaison: {reason}\nTotal: {warn_count}", color=disnake.Color.orange()))
try: await member.send(f"⚠️ Avertissement sur **{interaction.guild.name}**\nRaison: {reason}")
except: pass
if config:
l_t, l_k, l_b, dur = config
if warn_count >= l_b: await member.ban(reason=f"Auto: {warn_count} warns")
elif warn_count >= l_k: await member.kick(reason=f"Auto: {warn_count} warns")
elif warn_count >= l_t: await member.timeout(timedelta(seconds=dur), reason=f"Auto: {warn_count} warns")
elif warn_count >= l_t: await member.timeout(duration=timedelta(seconds=dur), reason=f"Auto: {warn_count} warns")
async def timeout(self, interaction: discord.Interaction, member: discord.Member, duration_str: str, reason: str):
async def timeout(self, interaction: disnake.Interaction, member: disnake.Member, duration_str: str, reason: str):
secs = self.parse_duration(duration_str)
if secs <= 0: return await interaction.response.send_message("❌ Durée invalide.", ephemeral=True)
await member.timeout(timedelta(seconds=secs), reason=reason)
await member.timeout(duration=timedelta(seconds=secs), reason=reason)
conn = sqlite3.connect(self.db_path); cursor = conn.cursor()
cursor.execute('INSERT INTO sanctions (guild_id, user_id, moderator_id, type, reason, duration) VALUES (?, ?, ?, ?, ?, ?)', (interaction.guild_id, member.id, interaction.user.id, 'TIMEOUT', reason, secs))
conn.commit(); conn.close()
if not interaction.response.is_done(): await interaction.response.send_message(f"{member.mention} mis en sourdine.", ephemeral=True)
else: await interaction.followup.send(f"{member.mention} mis en sourdine.", ephemeral=True)
async def kick(self, interaction: discord.Interaction, member: discord.Member, reason: str):
async def kick(self, interaction: disnake.Interaction, member: disnake.Member, reason: str):
await member.kick(reason=reason)
conn = sqlite3.connect(self.db_path); cursor = conn.cursor()
cursor.execute('INSERT INTO sanctions (guild_id, user_id, moderator_id, type, reason) VALUES (?, ?, ?, ?, ?)', (interaction.guild_id, member.id, interaction.user.id, 'KICK', reason))
@ -293,7 +292,7 @@ class Moderation(commands.Cog):
if not interaction.response.is_done(): await interaction.response.send_message(f"{member.name} expulsé.", ephemeral=True)
else: await interaction.followup.send(f"{member.name} expulsé.", ephemeral=True)
async def ban(self, interaction: discord.Interaction, user: discord.User, reason: str):
async def ban(self, interaction: disnake.Interaction, user: disnake.User, reason: str):
await interaction.guild.ban(user, reason=reason)
conn = sqlite3.connect(self.db_path); cursor = conn.cursor()
cursor.execute('INSERT INTO sanctions (guild_id, user_id, moderator_id, type, reason) VALUES (?, ?, ?, ?, ?)', (interaction.guild_id, user.id, interaction.user.id, 'BAN', reason))
@ -301,20 +300,20 @@ class Moderation(commands.Cog):
if not interaction.response.is_done(): await interaction.response.send_message(f"{user.name} banni.", ephemeral=True)
else: await interaction.followup.send(f"{user.name} banni.", ephemeral=True)
async def history(self, interaction: discord.Interaction, user: discord.User):
async def history(self, interaction: disnake.Interaction, user: disnake.User):
conn = sqlite3.connect(self.db_path); cursor = conn.cursor()
cursor.execute('SELECT id, type, reason, timestamp FROM sanctions WHERE guild_id = ? AND user_id = ? ORDER BY timestamp DESC', (interaction.guild_id, user.id))
results = cursor.fetchall(); conn.close()
if not results:
if not interaction.response.is_done(): return await interaction.response.send_message(" Aucun historique.", ephemeral=True)
else: return await interaction.followup.send(" Aucun historique.", ephemeral=True)
embed = discord.Embed(title=f"📜 Historique de {user.name}", color=discord.Color.blue())
embed = disnake.Embed(title=f"📜 Historique de {user.name}", color=disnake.Color.blue())
for s_id, s_type, reason, ts in results[:10]:
embed.add_field(name=f"#{s_id} - {s_type}", value=f"**Date:** {ts}\n**Raison:** {reason}", inline=False)
if not interaction.response.is_done(): await interaction.response.send_message(embed=embed, ephemeral=True)
else: await interaction.followup.send(embed=embed, ephemeral=True)
async def clearwarns(self, interaction: discord.Interaction, member: discord.Member, reason: str):
async def clearwarns(self, interaction: disnake.Interaction, member: disnake.Member, reason: str):
conn = sqlite3.connect(self.db_path); cursor = conn.cursor()
cursor.execute('UPDATE sanctions SET status = "REVOKED" WHERE guild_id = ? AND user_id = ? AND type = "WARN"', (interaction.guild_id, member.id))
conn.commit(); conn.close()

View file

@ -1,5 +1,5 @@
import discord
from discord.ext import commands
import disnake
from disnake.ext import commands
import json
import os
from datetime import datetime
@ -53,7 +53,7 @@ class AntiRaid(commands.Cog):
if log_channel_id:
channel = guild.get_channel(int(log_channel_id))
if channel:
embed = discord.Embed(title="🚨 ALERTE RAID", description=f"Vague de {len(self.join_cache[guild.id])} membres !", color=0xFF0000)
embed = disnake.Embed(title="🚨 ALERTE RAID", description=f"Vague de {len(self.join_cache[guild.id])} membres !", color=0xFF0000)
await channel.send(embed=embed)
async def setup(bot):

View file

@ -1,5 +1,5 @@
import discord
from discord.ext import commands
import disnake
from disnake.ext import commands
import json
import os
from collections import deque
@ -28,7 +28,7 @@ class AntiSpam(commands.Cog):
def _get_cached_settings(self, guild_id):
"""Récupère les paramètres avec cache (TTL de 5 secondes)"""
now = discord.utils.utcnow().timestamp()
now = disnake.utils.utcnow().timestamp()
cached = self._settings_cache.get(guild_id)
if cached:
@ -52,7 +52,7 @@ class AntiSpam(commands.Cog):
def _get_cached_whitelist(self, guild_id):
"""Récupère la whitelist avec cache (TTL de 5 secondes)"""
now = discord.utils.utcnow().timestamp()
now = disnake.utils.utcnow().timestamp()
cached = self._whitelist_cache.get(guild_id)
if cached:
@ -117,7 +117,7 @@ class AntiSpam(commands.Cog):
# --- TEST ANTI-SPAM (deque pour O(1)) ---
if settings.get("anti_spam") is True:
now = discord.utils.utcnow().timestamp()
now = disnake.utils.utcnow().timestamp()
# Initialiser ou récupérer le deque pour cet utilisateur
if user_id not in self.message_counts:
@ -156,7 +156,7 @@ class AntiSpam(commands.Cog):
self.message_counts[user_id] = deque(maxlen=10)
if current_warns <= 3:
embed = discord.Embed(
embed = disnake.Embed(
title="🛡️ Sécurité Kuby",
description=f"{message.author.mention}, {reason}.\n**Avertissement : {current_warns}/3**",
color=0xFFA500
@ -169,10 +169,10 @@ class AntiSpam(commands.Cog):
async def apply_mute(self, message, reason):
try:
until = discord.utils.utcnow() + timedelta(minutes=5)
until = disnake.utils.utcnow() + timedelta(minutes=5)
await message.author.timeout(until, reason=f"Kuby Security: {reason} (3 Warns)")
embed = discord.Embed(
embed = disnake.Embed(
title="⛔ Exclusion Temporaire",
description=f"**Membre :** {message.author.mention}\n**Durée :** 5 minutes\n**Raison :** {reason} répétitif.",
color=0xFF0000

View file

@ -1,5 +1,5 @@
import discord
from discord.ext import commands
import disnake
from disnake.ext import commands
import json
import os
from datetime import datetime
@ -23,11 +23,11 @@ class LogsManager(commands.Cog):
log_chan = get_log_channel(message.guild, settings)
if log_chan:
embed = discord.Embed(
embed = disnake.Embed(
title="🗑️ Message Supprimé",
description=f"**Auteur :** {message.author.mention}\n**Salon :** {message.channel.mention}",
color=0xFFA500,
timestamp=datetime.utcnow()
timestamp=disnake.utils.utcnow()
)
embed.add_field(name="Contenu", value=message.content or "*(Image ou Embed)*")
await log_chan.send(embed=embed)
@ -37,11 +37,11 @@ class LogsManager(commands.Cog):
settings = dict(json.load(open(SETTINGS_FILE, "r"))).get(str(member.guild.id), {})
log_chan = get_log_channel(member.guild, settings)
if log_chan:
embed = discord.Embed(
embed = disnake.Embed(
title="📤 Départ d'un membre",
description=f"{member.mention} ({member.id}) a quitté le serveur.",
color=0xFF0000,
timestamp=datetime.utcnow()
timestamp=disnake.utils.utcnow()
)
await log_chan.send(embed=embed)
@ -50,11 +50,11 @@ class LogsManager(commands.Cog):
settings = dict(json.load(open(SETTINGS_FILE, "r"))).get(str(guild.id), {})
log_chan = get_log_channel(guild, settings)
if log_chan:
embed = discord.Embed(
embed = disnake.Embed(
title="🔨 Bannissement",
description=f"**Utilisateur :** {user.mention} ({user.id}) a été banni.",
color=0x000000,
timestamp=datetime.utcnow()
timestamp=disnake.utils.utcnow()
)
await log_chan.send(embed=embed)

View file

@ -1,6 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
import disnake
from disnake.ext import commands
import json
import os
from typing import Optional
@ -39,9 +38,9 @@ def save_settings(guild_id: int, settings: dict) -> None:
# --- MODALS DE CONFIGURATION DU RÈGLEMENT (BACK-END) ---
class RulesTitleModal(discord.ui.Modal, title="📜 Titre du Règlement"):
class RulesTitleModal(disnake.ui.Modal, title="📜 Titre du Règlement"):
"""Modal pour configurer le titre du règlement"""
title_input = discord.ui.TextInput(
title_input = disnake.ui.TextInput(
label="Titre du règlement",
placeholder="Ex: 📜 Règlement de [Nom du Serveur]",
min_length=1,
@ -55,7 +54,7 @@ class RulesTitleModal(discord.ui.Modal, title="📜 Titre du Règlement"):
self.settings = settings
self.on_update_callback = on_update_callback
async def on_submit(self, interaction: discord.Interaction):
async def on_submit(self, interaction: disnake.Interaction):
self.settings["rules_title"] = self.title_input.value
save_settings(self.guild_id, self.settings)
@ -78,12 +77,12 @@ class RulesTitleModal(discord.ui.Modal, title="📜 Titre du Règlement"):
return view
class RulesContentModal(discord.ui.Modal, title="📝 Contenu du Règlement"):
class RulesContentModal(disnake.ui.Modal, title="📝 Contenu du Règlement"):
"""Modal pour configurer le contenu du règlement"""
content_input = discord.ui.TextInput(
content_input = disnake.ui.TextInput(
label="Contenu du règlement",
placeholder="Entrez les règles de votre serveur...",
style=discord.TextStyle.paragraph,
style=disnake.TextStyle.paragraph,
min_length=10,
max_length=4000,
required=True
@ -95,7 +94,7 @@ class RulesContentModal(discord.ui.Modal, title="📝 Contenu du Règlement"):
self.settings = settings
self.on_update_callback = on_update_callback
async def on_submit(self, interaction: discord.Interaction):
async def on_submit(self, interaction: disnake.Interaction):
self.settings["rules_content"] = self.content_input.value
save_settings(self.guild_id, self.settings)
@ -118,9 +117,9 @@ class RulesContentModal(discord.ui.Modal, title="📝 Contenu du Règlement"):
return view
class RulesChannelModal(discord.ui.Modal, title="📌 Salon du Règlement"):
class RulesChannelModal(disnake.ui.Modal, title="📌 Salon du Règlement"):
"""Modal pour sélectionner le salon du règlement"""
channel_input = discord.ui.TextInput(
channel_input = disnake.ui.TextInput(
label="ID du salon",
placeholder="Entrez l'ID du salon où envoyer le règlement",
min_length=15,
@ -134,7 +133,7 @@ class RulesChannelModal(discord.ui.Modal, title="📌 Salon du Règlement"):
self.settings = settings
self.on_update_callback = on_update_callback
async def on_submit(self, interaction: discord.Interaction):
async def on_submit(self, interaction: disnake.Interaction):
if not self.channel_input.value.isdigit():
return await interaction.response.send_message(
"❌ Erreur : ID invalide.",
@ -172,9 +171,9 @@ class RulesChannelModal(discord.ui.Modal, title="📌 Salon du Règlement"):
return view
class RulesRoleModal(discord.ui.Modal, title="✅ Rôle d'Acceptation"):
class RulesRoleModal(disnake.ui.Modal, title="✅ Rôle d'Acceptation"):
"""Modal pour configurer le rôle attribué à l'acceptation"""
role_input = discord.ui.TextInput(
role_input = disnake.ui.TextInput(
label="ID du rôle",
placeholder="Entrez l'ID du rôle à attribuer",
min_length=15,
@ -188,7 +187,7 @@ class RulesRoleModal(discord.ui.Modal, title="✅ Rôle d'Acceptation"):
self.settings = settings
self.on_update_callback = on_update_callback
async def on_submit(self, interaction: discord.Interaction):
async def on_submit(self, interaction: disnake.Interaction):
if not self.role_input.value.isdigit():
return await interaction.response.send_message(
"❌ Erreur : ID invalide.",
@ -226,9 +225,9 @@ class RulesRoleModal(discord.ui.Modal, title="✅ Rôle d'Acceptation"):
return view
class RulesMessageTypeModal(discord.ui.Modal, title="📝 Type de Message"):
class RulesMessageTypeModal(disnake.ui.Modal, title="📝 Type de Message"):
"""Modal pour choisir le type de message du règlement"""
message_type = discord.ui.TextInput(
message_type = disnake.ui.TextInput(
label="Type de message",
placeholder="Tapez 'embed' pour embed ou 'simple' pour message simple",
min_length=5,
@ -242,7 +241,7 @@ class RulesMessageTypeModal(discord.ui.Modal, title="📝 Type de Message"):
self.settings = settings
self.on_update_callback = on_update_callback
async def on_submit(self, interaction: discord.Interaction):
async def on_submit(self, interaction: disnake.Interaction):
msg_type = self.message_type.value.lower().strip()
if msg_type not in ["embed", "simple"]:
@ -274,11 +273,11 @@ class RulesMessageTypeModal(discord.ui.Modal, title="📝 Type de Message"):
return view
class ToggleRulesButton(discord.ui.Button):
class ToggleRulesButton(disnake.ui.Button):
"""Bouton de bascule pour activer/désactiver le règlement"""
def __init__(self, is_enabled: bool):
style = discord.ButtonStyle.green if is_enabled else discord.ButtonStyle.red
style = disnake.ButtonStyle.green if is_enabled else disnake.ButtonStyle.red
status = "" if is_enabled else ""
super().__init__(
label=f"Règlement [{status}]",
@ -287,7 +286,7 @@ class ToggleRulesButton(discord.ui.Button):
emoji="📜"
)
async def callback(self, interaction: discord.Interaction):
async def callback(self, interaction: disnake.Interaction):
view = self.view
settings = view.settings
guild = interaction.guild
@ -316,7 +315,7 @@ class ToggleRulesButton(discord.ui.Button):
await interaction.response.edit_message(embed=embed, view=new_view)
class RulesAcceptButton(discord.ui.View):
class RulesAcceptButton(disnake.ui.View):
"""Bouton persistant pour accepter le règlement"""
def __init__(self, guild_id: int, accept_role_id: int):
@ -327,10 +326,10 @@ class RulesAcceptButton(discord.ui.View):
@discord.ui.button(
label="J'accepte le règlement",
emoji="",
style=discord.ButtonStyle.green,
style=disnake.ButtonStyle.green,
custom_id="rules_accept_button"
)
async def accept_rules(self, interaction: discord.Interaction, button: discord.ui.Button):
async def accept_rules(self, interaction: disnake.Interaction, button: disnake.ui.Button):
"""Gère le clic sur le bouton d'acceptation"""
guild = interaction.guild
@ -368,7 +367,7 @@ class RulesAcceptButton(discord.ui.View):
f"Le rôle **{role.name}** vous a été attribué.",
ephemeral=True
)
except discord.Forbidden:
except disnake.Forbidden:
await interaction.response.send_message(
"❌ Je n'ai pas les permissions pour attribuer ce rôle.",
ephemeral=True
@ -421,7 +420,7 @@ class RulesCog(commands.Cog):
"""Appelé quand le bot est prêt"""
print("🎫 Module Règles chargé avec succès!")
async def send_rules_message(self, channel: discord.TextChannel, settings: dict) -> Optional[discord.Message]:
async def send_rules_message(self, channel: disnake.TextChannel, settings: dict) -> Optional[disnake.Message]:
"""Envoie le message du règlement dans un salon"""
title = settings.get("rules_title", "📜 Règlement du Serveur")
content = settings.get("rules_content", "")
@ -457,7 +456,7 @@ class RulesCog(commands.Cog):
else: # embed
# Créer l'embed
embed = discord.Embed(
embed = disnake.Embed(
title=title,
description=content,
color=0x2b2d31

View file

@ -1,7 +1,7 @@
# utils/no_link.py
import re
import discord
from discord.ext import commands
import disnake
from disnake.ext import commands
import json
import os
@ -26,7 +26,7 @@ class NoLink(commands.Cog):
self.bot = bot
@commands.Cog.listener()
async def on_message(self, message: discord.Message) -> None:
async def on_message(self, message: disnake.Message) -> None:
if message.author.bot or not message.guild:
return
@ -41,7 +41,7 @@ class NoLink(commands.Cog):
f"{message.author.mention} ⚠️ Les liens sont interdits sur ce serveur.",
delete_after=5
)
except discord.Forbidden:
except disnake.Forbidden:
print(f"Impossible de supprimer le message de {message.author} (permissions manquantes).")
except Exception as exc:
print(f"Erreur lors de la suppression du message : {exc}")

View file

@ -1,7 +1,6 @@
# scan.py
import discord
from discord import app_commands
from discord.ext import commands
import disnake
from disnake.ext import commands
import hashlib
import os
import json
@ -59,8 +58,8 @@ class Scan(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
@app_commands.command(name="scan", description="Scanner un fichier pour détecter un malware.")
async def scan(self, interaction: discord.Interaction, file: discord.Attachment):
@commands.slash_command(name="scan", description="Scanner un fichier pour détecter un malware.")
async def scan(self, interaction: disnake.ApplicationCommandInteraction, file: disnake.Attachment):
await interaction.response.defer() # Permet de prendre un peu de temps pour le scan
# Téléchargement temporaire du fichier

View file

@ -1,6 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
import disnake
from disnake.ext import commands
import json
import os
from typing import Optional
@ -70,9 +69,9 @@ def is_immune(guild, user) -> bool:
# --- MODALS DE CONFIGURATION ---
class SpamThresholdModal(discord.ui.Modal, title="🛡️ Seuil Anti-Spam"):
class SpamThresholdModal(disnake.ui.Modal, title="🛡️ Seuil Anti-Spam"):
"""Modal pour configurer le seuil anti-spam"""
threshold_input = discord.ui.TextInput(
threshold_input = disnake.ui.TextInput(
label="Messages max autorisés",
placeholder="Entre 3 et 10 (défaut: 5)",
min_length=1,
@ -86,7 +85,7 @@ class SpamThresholdModal(discord.ui.Modal, title="🛡️ Seuil Anti-Spam"):
self.settings = settings
self.parent_view = view
async def on_submit(self, interaction: discord.Interaction):
async def on_submit(self, interaction: disnake.Interaction):
if not self.threshold_input.value.isdigit():
return await interaction.response.send_message(
"❌ Erreur : Veuillez entrer un nombre entier.",
@ -111,9 +110,9 @@ class SpamThresholdModal(discord.ui.Modal, title="🛡️ Seuil Anti-Spam"):
ephemeral=True
)
class RaidThresholdModal(discord.ui.Modal, title="⚔️ Seuil Anti-Raid"):
class RaidThresholdModal(disnake.ui.Modal, title="⚔️ Seuil Anti-Raid"):
"""Modal pour configurer le seuil anti-raid"""
threshold_input = discord.ui.TextInput(
threshold_input = disnake.ui.TextInput(
label="Joins max en 10 secondes",
placeholder="Entre 2 et 10 (défaut: 3)",
min_length=1,
@ -127,7 +126,7 @@ class RaidThresholdModal(discord.ui.Modal, title="⚔️ Seuil Anti-Raid"):
self.settings = settings
self.parent_view = view
async def on_submit(self, interaction: discord.Interaction):
async def on_submit(self, interaction: disnake.Interaction):
if not self.threshold_input.value.isdigit():
return await interaction.response.send_message(
"❌ Erreur : Veuillez entrer un nombre entier.",
@ -154,9 +153,9 @@ class RaidThresholdModal(discord.ui.Modal, title="⚔️ Seuil Anti-Raid"):
# --- MODALS DE CONFIGURATION WHITELIST ---
class WhitelistModal(discord.ui.Modal, title="👤 Ajouter à la Whitelist"):
class WhitelistModal(disnake.ui.Modal, title="👤 Ajouter à la Whitelist"):
"""Modal pour ajouter un utilisateur à la whitelist"""
user_input = discord.ui.TextInput(
user_input = disnake.ui.TextInput(
label="ID de l'utilisateur",
placeholder="Entrez l'ID Discord de l'utilisateur",
min_length=15,
@ -169,7 +168,7 @@ class WhitelistModal(discord.ui.Modal, title="👤 Ajouter à la Whitelist"):
self.guild_id = guild_id
self.parent_view = view
async def on_submit(self, interaction: discord.Interaction):
async def on_submit(self, interaction: disnake.Interaction):
if not self.user_input.value.isdigit():
return await interaction.response.send_message(
"❌ Erreur : ID invalide.",
@ -213,10 +212,10 @@ class WhitelistModal(discord.ui.Modal, title="👤 Ajouter à la Whitelist"):
# --- VUES DE L'INTERFACE ---
class SecurityView(discord.ui.View):
class SecurityView(disnake.ui.View):
"""Vue principale du panneau de sécurité"""
def __init__(self, interaction: discord.Interaction, settings: dict, category: Optional[str] = None, bot=None):
def __init__(self, interaction: disnake.Interaction, settings: dict, category: Optional[str] = None, bot=None):
super().__init__(timeout=300) # 5 minutes timeout
self.interaction = interaction
self.guild = interaction.guild
@ -270,18 +269,18 @@ class SecurityView(discord.ui.View):
# Bouton de configuration du seuil
threshold = self.settings.get("spam_threshold", 5)
btn_threshold = discord.ui.Button(
btn_threshold = disnake.ui.Button(
label=f"🔢 Seuil: {threshold} msg/5s",
style=discord.ButtonStyle.blurple,
style=disnake.ButtonStyle.blurple,
custom_id="spam_threshold"
)
btn_threshold.callback = self.open_spam_threshold_modal
self.add_item(btn_threshold)
# Bouton retour
btn_back = discord.ui.Button(
btn_back = disnake.ui.Button(
label="Retour",
style=discord.ButtonStyle.gray,
style=disnake.ButtonStyle.gray,
emoji="⬅️"
)
btn_back.callback = self.back_to_main
@ -311,18 +310,18 @@ class SecurityView(discord.ui.View):
# Bouton de configuration du seuil
threshold = self.settings.get("raid_threshold", 3)
btn_threshold = discord.ui.Button(
btn_threshold = disnake.ui.Button(
label=f"🔢 Seuil: {threshold} join/10s",
style=discord.ButtonStyle.blurple,
style=disnake.ButtonStyle.blurple,
custom_id="raid_threshold"
)
btn_threshold.callback = self.open_raid_threshold_modal
self.add_item(btn_threshold)
# Bouton retour
btn_back = discord.ui.Button(
btn_back = disnake.ui.Button(
label="Retour",
style=discord.ButtonStyle.gray,
style=disnake.ButtonStyle.gray,
emoji="⬅️"
)
btn_back.callback = self.back_to_main
@ -351,9 +350,9 @@ class SecurityView(discord.ui.View):
self.add_item(btn_role)
# Bouton retour
btn_back = discord.ui.Button(
btn_back = disnake.ui.Button(
label="Retour",
style=discord.ButtonStyle.gray,
style=disnake.ButtonStyle.gray,
emoji="⬅️"
)
btn_back.callback = self.back_to_main
@ -376,23 +375,23 @@ class SecurityView(discord.ui.View):
if log_channel_id:
channel = self.guild.get_channel(int(log_channel_id))
channel_mention = channel.mention if channel else f"`{log_channel_id}`"
btn_channel = discord.ui.Button(
btn_channel = disnake.ui.Button(
label=f"📌 Channel: {channel.name if channel else 'Invalide'}",
style=discord.ButtonStyle.green,
style=disnake.ButtonStyle.green,
disabled=True
)
else:
btn_channel = discord.ui.Button(
btn_channel = disnake.ui.Button(
label="📌 Aucun channel configuré",
style=discord.ButtonStyle.red,
style=disnake.ButtonStyle.red,
disabled=True
)
self.add_item(btn_channel)
# Bouton retour
btn_back = discord.ui.Button(
btn_back = disnake.ui.Button(
label="Retour",
style=discord.ButtonStyle.gray,
style=disnake.ButtonStyle.gray,
emoji="⬅️"
)
btn_back.callback = self.back_to_main
@ -401,9 +400,9 @@ class SecurityView(discord.ui.View):
def build_whitelist_view(self):
"""Construit la vue Whitelist"""
# Affichage du nombre avec style
btn_count = discord.ui.Button(
btn_count = disnake.ui.Button(
label=f"👥 Whitelist: {self.whitelist_count} utilisateur(s)",
style=discord.ButtonStyle.blurple,
style=disnake.ButtonStyle.blurple,
disabled=True
)
self.add_item(btn_count)
@ -413,17 +412,17 @@ class SecurityView(discord.ui.View):
self.add_item(WhitelistSelect(self.guild, self))
else:
# Message si whitelist vide
btn_empty = discord.ui.Button(
btn_empty = disnake.ui.Button(
label="📭 Aucun utilisateur dans la whitelist",
style=discord.ButtonStyle.gray,
style=disnake.ButtonStyle.gray,
disabled=True
)
self.add_item(btn_empty)
# Bouton ajouter
btn_add = discord.ui.Button(
btn_add = disnake.ui.Button(
label=" Ajouter",
style=discord.ButtonStyle.green,
style=disnake.ButtonStyle.green,
emoji="",
custom_id="whitelist_add"
)
@ -431,9 +430,9 @@ class SecurityView(discord.ui.View):
self.add_item(btn_add)
# Bouton retour
btn_back = discord.ui.Button(
btn_back = disnake.ui.Button(
label="Retour",
style=discord.ButtonStyle.gray,
style=disnake.ButtonStyle.gray,
emoji="⬅️"
)
btn_back.callback = self.back_to_main
@ -458,18 +457,18 @@ class SecurityView(discord.ui.View):
# Bouton pour modifier le titre
btn_title = discord.ui.Button(
btn_title = disnake.ui.Button(
label="Éditer le titre",
style=discord.ButtonStyle.blurple,
style=disnake.ButtonStyle.blurple,
emoji="📝"
)
btn_title.callback = self.open_rules_title_modal
self.add_item(btn_title)
# Bouton pour modifier le contenu
btn_content = discord.ui.Button(
btn_content = disnake.ui.Button(
label="Éditer le contenu",
style=discord.ButtonStyle.blurple,
style=disnake.ButtonStyle.blurple,
emoji="📄"
)
btn_content.callback = self.open_rules_content_modal
@ -479,15 +478,15 @@ class SecurityView(discord.ui.View):
rules_channel_id = self.settings.get("rules_channel_id")
if rules_channel_id:
channel = self.guild.get_channel(int(rules_channel_id))
btn_channel = discord.ui.Button(
btn_channel = disnake.ui.Button(
label=f"Salon: {channel.name if channel else 'Invalide'}",
style=discord.ButtonStyle.green,
style=disnake.ButtonStyle.green,
custom_id="rules_channel"
)
else:
btn_channel = discord.ui.Button(
btn_channel = disnake.ui.Button(
label="Aucun salon configuré",
style=discord.ButtonStyle.red,
style=disnake.ButtonStyle.red,
custom_id="rules_channel"
)
btn_channel.callback = self.open_rules_channel_modal
@ -497,15 +496,15 @@ class SecurityView(discord.ui.View):
rules_role_id = self.settings.get("rules_accept_role_id")
if rules_role_id:
role = self.guild.get_role(int(rules_role_id))
btn_role = discord.ui.Button(
btn_role = disnake.ui.Button(
label=f"Rôle: {role.name if role else 'Invalide'}",
style=discord.ButtonStyle.green,
style=disnake.ButtonStyle.green,
custom_id="rules_role"
)
else:
btn_role = discord.ui.Button(
btn_role = disnake.ui.Button(
label="Aucun rôle configuré",
style=discord.ButtonStyle.red,
style=disnake.ButtonStyle.red,
custom_id="rules_role"
)
btn_role.callback = self.open_rules_role_modal
@ -514,9 +513,9 @@ class SecurityView(discord.ui.View):
# Bouton pour choisir le type de message
message_type = self.settings.get("rules_message_type", "embed")
type_label = "Embed" if message_type == "embed" else "Message simple"
btn_message_type = discord.ui.Button(
btn_message_type = disnake.ui.Button(
label=f"Type: {type_label}",
style=discord.ButtonStyle.blurple,
style=disnake.ButtonStyle.blurple,
emoji="📝",
custom_id="rules_message_type"
)
@ -524,84 +523,84 @@ class SecurityView(discord.ui.View):
self.add_item(btn_message_type)
# Bouton pour envoyer le règlement
btn_send = discord.ui.Button(
btn_send = disnake.ui.Button(
label="Envoyer le règlement",
style=discord.ButtonStyle.green,
style=disnake.ButtonStyle.green,
emoji="🚀"
)
btn_send.callback = self.send_rules
self.add_item(btn_send)
# Bouton retour
btn_back = discord.ui.Button(
btn_back = disnake.ui.Button(
label="Retour",
style=discord.ButtonStyle.gray,
style=disnake.ButtonStyle.gray,
emoji="⬅️"
)
btn_back.callback = self.back_to_main
self.add_item(btn_back)
async def toggle_setting(self, interaction: discord.Interaction, key: str):
async def toggle_setting(self, interaction: disnake.Interaction, key: str):
"""Bascule un paramètre"""
self.settings[key] = not self.settings.get(key, False)
save_settings(self.guild.id, self.settings)
self.build_view()
await interaction.response.edit_message(view=self)
async def open_spam_threshold_modal(self, interaction: discord.Interaction):
async def open_spam_threshold_modal(self, interaction: disnake.Interaction):
"""Ouvre le modal de configuration du seuil spam"""
await interaction.response.send_modal(
SpamThresholdModal(self.guild.id, self.settings, self)
)
async def open_raid_threshold_modal(self, interaction: discord.Interaction):
async def open_raid_threshold_modal(self, interaction: disnake.Interaction):
"""Ouvre le modal de configuration du seuil raid"""
await interaction.response.send_modal(
RaidThresholdModal(self.guild.id, self.settings, self)
)
async def open_whitelist_modal(self, interaction: discord.Interaction):
async def open_whitelist_modal(self, interaction: disnake.Interaction):
"""Ouvre le modal d'ajout à la whitelist"""
await interaction.response.send_modal(
WhitelistModal(self.guild.id, self)
)
async def open_rules_title_modal(self, interaction: discord.Interaction):
async def open_rules_title_modal(self, interaction: disnake.Interaction):
"""Ouvre le modal de configuration du titre du règlement"""
from .modules_security.rules import RulesTitleModal
await interaction.response.send_modal(
RulesTitleModal(self.guild.id, self.settings)
)
async def open_rules_content_modal(self, interaction: discord.Interaction):
async def open_rules_content_modal(self, interaction: disnake.Interaction):
"""Ouvre le modal de configuration du contenu du règlement"""
from .modules_security.rules import RulesContentModal
await interaction.response.send_modal(
RulesContentModal(self.guild.id, self.settings)
)
async def open_rules_channel_modal(self, interaction: discord.Interaction):
async def open_rules_channel_modal(self, interaction: disnake.Interaction):
"""Ouvre le modal de sélection du salon du règlement"""
from .modules_security.rules import RulesChannelModal
await interaction.response.send_modal(
RulesChannelModal(self.guild.id, self.settings)
)
async def open_rules_role_modal(self, interaction: discord.Interaction):
async def open_rules_role_modal(self, interaction: disnake.Interaction):
"""Ouvre le modal de configuration du rôle d'acceptation"""
from .modules_security.rules import RulesRoleModal
await interaction.response.send_modal(
RulesRoleModal(self.guild.id, self.settings)
)
async def open_rules_message_type_modal(self, interaction: discord.Interaction):
async def open_rules_message_type_modal(self, interaction: disnake.Interaction):
"""Ouvre le modal de sélection du type de message"""
from .modules_security.rules import RulesMessageTypeModal
await interaction.response.send_modal(
RulesMessageTypeModal(self.guild.id, self.settings)
)
async def send_rules(self, interaction: discord.Interaction):
async def send_rules(self, interaction: disnake.Interaction):
"""Envoie le règlement dans le salon configuré"""
from commandes.modules_security.rules import RulesCog
@ -670,16 +669,16 @@ class SecurityView(discord.ui.View):
ephemeral=True
)
async def back_to_main(self, interaction: discord.Interaction):
async def back_to_main(self, interaction: disnake.Interaction):
"""Retour au menu principal"""
self.category = None
self.build_view()
embed = self.create_main_embed(interaction.user)
await interaction.response.edit_message(embed=embed, view=self)
def create_main_embed(self, user: discord.Member = None) -> discord.Embed:
def create_main_embed(self, user: disnake.Member = None) -> disnake.Embed:
"""Crée l'embed du menu principal"""
embed = discord.Embed(
embed = disnake.Embed(
title="🛡️ Centre de Sécurité Kuby",
description="Gérez la protection de votre serveur",
color=0x7289DA
@ -726,9 +725,9 @@ class SecurityView(discord.ui.View):
return embed
def create_category_embed(self, title: str, description: str) -> discord.Embed:
def create_category_embed(self, title: str, description: str) -> disnake.Embed:
"""Crée un embed pour une catégorie"""
embed = discord.Embed(
embed = disnake.Embed(
title=title,
description=description,
color=0x7289DA
@ -740,11 +739,11 @@ class SecurityView(discord.ui.View):
return embed
class ToggleButton(discord.ui.Button):
class ToggleButton(disnake.ui.Button):
"""Bouton de bascule avec indicateur de statut"""
def __init__(self, label: str, emoji: str, is_enabled: bool, custom_id: str):
style = discord.ButtonStyle.green if is_enabled else discord.ButtonStyle.red
style = disnake.ButtonStyle.green if is_enabled else disnake.ButtonStyle.red
status = "" if is_enabled else ""
super().__init__(
label=f"{label} [{status}]",
@ -753,7 +752,7 @@ class ToggleButton(discord.ui.Button):
emoji=emoji
)
async def callback(self, interaction: discord.Interaction):
async def callback(self, interaction: disnake.Interaction):
view: SecurityView = self.view
key = interaction.data['custom_id']
@ -771,42 +770,42 @@ class ToggleButton(discord.ui.Button):
await interaction.response.edit_message(view=view)
class MainCategorySelect(discord.ui.Select):
class MainCategorySelect(disnake.ui.Select):
"""Menu de sélection des catégories"""
def __init__(self):
options = [
discord.SelectOption(
disnake.SelectOption(
label="Anti-Spam",
emoji="🛡️",
value="spam",
description="Protection contre le spam et les liens"
),
discord.SelectOption(
disnake.SelectOption(
label="Anti-Raid",
emoji="⚔️",
value="raid",
description="Protection contre les raids"
),
discord.SelectOption(
disnake.SelectOption(
label="Anti-Nuke",
emoji="☢️",
value="nuke",
description="Protection contre la destruction du serveur"
),
discord.SelectOption(
disnake.SelectOption(
label="Logs",
emoji="📋",
value="logs",
description="Journalisation des événements"
),
discord.SelectOption(
disnake.SelectOption(
label="Règlement",
emoji="📜",
value="rules",
description="Créer et envoyer un règlement"
),
discord.SelectOption(
disnake.SelectOption(
label="Whitelist",
emoji="👥",
value="whitelist",
@ -820,7 +819,7 @@ class MainCategorySelect(discord.ui.Select):
max_values=1
)
async def callback(self, interaction: discord.Interaction):
async def callback(self, interaction: disnake.Interaction):
view: SecurityView = self.view
view.category = self.values[0]
view.build_view()
@ -871,7 +870,7 @@ class MainCategorySelect(discord.ui.Select):
await interaction.response.edit_message(embed=embed, view=view)
class WhitelistSelect(discord.ui.Select):
class WhitelistSelect(disnake.ui.Select):
"""Menu dropdown pour voir et gérer les utilisateurs whitelistés"""
def __init__(self, guild: discord.Guild, view: 'SecurityView'):
@ -890,7 +889,7 @@ class WhitelistSelect(discord.ui.Select):
label = f"ID: {user_id} (non trouvé)"
emoji = ""
options.append(
discord.SelectOption(
disnake.SelectOption(
label=label[:100], # Discord limite à 100 chars
value=str(user_id),
emoji=emoji,
@ -905,7 +904,7 @@ class WhitelistSelect(discord.ui.Select):
max_values=1
)
async def callback(self, interaction: discord.Interaction):
async def callback(self, interaction: disnake.Interaction):
"""Gère la sélection d'un utilisateur à retirer"""
user_id = int(self.values[0])
@ -955,7 +954,7 @@ class Security(commands.Cog):
name="security",
description="🛡️ Ouvrir le panneau de sécurité Kuby"
)
async def security(self, interaction: discord.Interaction):
async def security(self, interaction: disnake.Interaction):
"""
Commande principale pour ouvrir l'interface de sécurité.
Accessible uniquement aux administrateurs et propriétaires whitelistés.
@ -990,7 +989,7 @@ class Security(commands.Cog):
)
@security.error
async def security_error(self, interaction: discord.Interaction, error: app_commands.AppCommandError):
async def security_error(self, interaction: disnake.Interaction, error: app_commands.AppCommandError):
"""Gestion des erreurs de la commande security"""
# Utilisation de followup si l'interaction est déjà différée ou répondue
send_method = interaction.followup.send if interaction.response.is_done() else interaction.response.send_message

View file

@ -1,17 +1,14 @@
import discord
from discord import app_commands
from discord.ext import commands
import disnake
from disnake.ext import commands
class SentBotMessages(commands.Cog):
def __init__(self, bot):
self.bot = bot
@app_commands.command(name="sentbotmessages", description="Le bot envoie un message dans un salon choisi.")
@app_commands.describe(
message="Le message à envoyer",
salon="Le salon où envoyer le message"
)
async def sentbotmessages(self, interaction: discord.Interaction, message: str, salon: discord.TextChannel):
@commands.slash_command(name="sentbotmessages", description="Le bot envoie un message dans un salon choisi.")
async def sentbotmessages(self, interaction: disnake.ApplicationCommandInteraction,
message: str = commands.Param(description="Le message à envoyer"),
salon: disnake.TextChannel = commands.Param(description="Le salon où envoyer le message")):
# Vérifie si l'utilisateur est admin
if not interaction.user.guild_permissions.administrator:
await interaction.response.send_message("⛔ Tu n'as pas la permission d'utiliser cette commande.", ephemeral=True)

View file

@ -1,6 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
import disnake
from disnake.ext import commands
from datetime import datetime
from collections import deque
@ -34,9 +33,9 @@ class SnipeCommands(commands.Cog):
self.deleted_messages[channel_id].appendleft(message_data)
@app_commands.command(name="snipe", description="Voir les derniers messages supprimés d'un salon")
@app_commands.describe(nombre="Nombre de messages à afficher (1-10, par défaut: 1)")
async def snipe(self, interaction: discord.Interaction, nombre: int = 1):
@commands.slash_command(name="snipe", description="Voir les derniers messages supprimés d'un salon")
async def snipe(self, interaction: disnake.ApplicationCommandInteraction,
nombre: int = commands.Param(1, description="Nombre de messages à afficher (1-10, par défaut: 1)")):
"""Retrieve deleted messages from the current channel"""
channel_id = interaction.channel.id
@ -60,9 +59,9 @@ class SnipeCommands(commands.Cog):
messages = list(self.deleted_messages[channel_id])[:nombre]
# Create embed for displaying deleted messages
embed = discord.Embed(
embed = disnake.Embed(
title=f"🗑️ Messages supprimés ({len(messages)} sur {len(self.deleted_messages[channel_id])} disponibles)",
color=discord.Color.red()
color=disnake.Color.red()
)
for i, msg_data in enumerate(messages, 1):
@ -93,8 +92,8 @@ class SnipeCommands(commands.Cog):
await interaction.response.send_message(embed=embed)
@app_commands.command(name="snipeclear", description="Effacer l'historique des messages supprimés pour ce salon")
async def snipeclear(self, interaction: discord.Interaction):
@commands.slash_command(name="snipeclear", description="Effacer l'historique des messages supprimés pour ce salon")
async def snipeclear(self, interaction: disnake.ApplicationCommandInteraction):
"""Clear the deleted messages history for the current channel"""
channel_id = interaction.channel.id

View file

@ -1,6 +1,5 @@
import discord
from discord.ext import commands
from discord import app_commands
import disnake
from disnake.ext import commands
from datetime import datetime
from commandes.ticket.data.storage import get_storage
@ -11,8 +10,8 @@ class StaffLeaderboard(commands.Cog):
def __init__(self, bot):
self.bot = bot
@app_commands.command(name="staff-leaderboard", description="Affiche le classement du staff")
async def staff_leaderboard(self, interaction: discord.Interaction):
@commands.slash_command(name="staff-leaderboard", description="Affiche le classement du staff")
async def staff_leaderboard(self, interaction: disnake.ApplicationCommandInteraction):
"""Commande pour afficher le classement du staff"""
await interaction.response.defer(ephemeral=False)
@ -31,10 +30,10 @@ class StaffLeaderboard(commands.Cog):
staff_list.sort(key=lambda x: x['average_rating'], reverse=True)
# Create embed
embed = discord.Embed(
embed = disnake.Embed(
title="🏆 Classement du Staff",
description="Classement des meilleurs membres du staff",
color=discord.Color.gold(),
color=disnake.Color.gold(),
timestamp=datetime.now()
)

View file

@ -1,6 +1,5 @@
import discord
from discord.ext import commands
from discord import app_commands
import disnake
from disnake.ext import commands
import matplotlib.pyplot as plt
import plotly.graph_objects as go
import plotly.express as px
@ -19,9 +18,9 @@ class StaffRatings(commands.Cog):
def __init__(self, bot):
self.bot = bot
@app_commands.command(name="staff-ratings", description="Affiche les évaluations du staff")
@app_commands.describe(team="Filtrer par équipe (optionnel)")
async def staff_ratings(self, interaction: discord.Interaction, team: str = None):
@commands.slash_command(name="staff-ratings", description="Affiche les évaluations du staff")
async def staff_ratings(self, interaction: disnake.ApplicationCommandInteraction,
team: str = commands.Param(None, description="Filtrer par équipe (optionnel)")):
"""Commande pour afficher les évaluations du staff"""
await interaction.response.defer(ephemeral=False)
@ -46,10 +45,10 @@ class StaffRatings(commands.Cog):
chart_buffer = self.create_staff_rating_chart_matplotlib(staff_list)
# Create embed
embed = discord.Embed(
embed = disnake.Embed(
title="📊 Évaluations du Staff",
description="Performance globale du staff",
color=discord.Color.blue(),
color=disnake.Color.blue(),
timestamp=datetime.now()
)
@ -66,7 +65,7 @@ class StaffRatings(commands.Cog):
)
# Send chart
file = discord.File(chart_buffer, filename="staff_ratings.png")
file = disnake.File(chart_buffer, filename="staff_ratings.png")
await interaction.followup.send(embed=embed, file=file)
except Exception as e:

File diff suppressed because it is too large Load diff

View file

@ -16,9 +16,8 @@ from PIL import Image, ImageDraw, ImageFont
# Add parent directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
import discord
from discord.ext import commands
from discord import app_commands
import disnake
from disnake.ext import commands
from .data.storage import get_storage
from .data.models import TicketData, TicketStatus, GuildTicketConfig
@ -31,7 +30,7 @@ class TicketAnalytics(commands.Cog):
self.bot = bot
self.storage = get_storage()
def _is_staff(self, interaction: discord.Interaction) -> bool:
def _is_staff(self, interaction: disnake.Interaction) -> bool:
"""Check if user is staff"""
guild = interaction.guild
user = interaction.user
@ -363,9 +362,8 @@ class TicketAnalytics(commands.Cog):
buf.seek(0)
return buf
@app_commands.command(name="ticket_analytics", description="Affiche les statistiques détaillées des tickets (Staff uniquement)")
@app_commands.describe(period="Période d'analyse (7d, 30d, 90d, all)")
async def ticket_analytics(self, interaction: discord.Interaction, period: str = "30d"):
@commands.slash_command(name="ticket_analytics", description="Affiche les statistiques détaillées des tickets (Staff uniquement)")
async def ticket_analytics(self, interaction: disnake.Interaction, period: str = commands.Param(default="30d", choices=["7d", "30d", "90d", "all"], description="Période d'analyse")):
"""Display comprehensive ticket analytics for staff"""
await interaction.response.defer(ephemeral=True)
@ -405,10 +403,10 @@ class TicketAnalytics(commands.Cog):
t.created_at >= cutoff_date for t in filtered_tickets if hasattr(t, 'response_time') and t.response_time == rt)]
# Create main embed
embed = discord.Embed(
embed = disnake.Embed(
title="📊 Analytics des Tickets",
description=f"Statistiques pour la période: **{period}**",
color=discord.Color.blue(),
color=disnake.Color.blue(),
timestamp=datetime.now()
)
@ -444,7 +442,7 @@ class TicketAnalytics(commands.Cog):
rating_chart = self._create_rating_chart(stats['ratings'])
await interaction.followup.send(
"📊 **Distribution des Notes Clients**",
file=discord.File(rating_chart, 'rating_distribution.png'),
file=disnake.File(rating_chart, 'rating_distribution.png'),
ephemeral=True
)
@ -453,7 +451,7 @@ class TicketAnalytics(commands.Cog):
response_chart = self._create_response_time_chart(stats['response_times'])
await interaction.followup.send(
"⏱️ **Temps de Réponse**",
file=discord.File(response_chart, 'response_times.png'),
file=disnake.File(response_chart, 'response_times.png'),
ephemeral=True
)
@ -462,7 +460,7 @@ class TicketAnalytics(commands.Cog):
trend_chart = self._create_trend_chart(stats['tickets_by_day'])
await interaction.followup.send(
"📈 **Évolution des Tickets**",
file=discord.File(trend_chart, 'ticket_trends.png'),
file=disnake.File(trend_chart, 'ticket_trends.png'),
ephemeral=True
)
@ -471,10 +469,10 @@ class TicketAnalytics(commands.Cog):
# Send feedback summary if available
if stats['feedbacks']:
feedback_embed = discord.Embed(
feedback_embed = disnake.Embed(
title="💬 Commentaires Clients",
description=f"Derniers commentaires ({min(5, len(stats['feedbacks']))} affichés)",
color=discord.Color.green()
color=disnake.Color.green()
)
# Sort by rating (lowest first to show areas for improvement)

View file

@ -1,7 +1,7 @@
import discord
import disnake
from .data.models import TicketData
class FeedbackView(discord.ui.View):
class FeedbackView(disnake.ui.View):
def __init__(self, bot, storage, ticket: TicketData, staff_rating_only: bool = False):
super().__init__(timeout=86400) # 24h timeout
self.bot = bot
@ -14,12 +14,12 @@ class FeedbackView(discord.ui.View):
# Add Select Menu for Rating
self.add_item(RatingSelect())
@discord.ui.button(label="📝 Laisser un commentaire", style=discord.ButtonStyle.secondary, custom_id="feedback_comment", row=1)
async def comment_button(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="📝 Laisser un commentaire", style=disnake.ButtonStyle.secondary, custom_id="feedback_comment", row=1)
async def comment_button(self, interaction: disnake.Interaction, button: disnake.ui.Button):
await interaction.response.send_modal(FeedbackModal(self))
@discord.ui.button(label="✅ Envoyer", style=discord.ButtonStyle.green, custom_id="feedback_submit", row=1)
async def submit_button(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="✅ Envoyer", style=disnake.ButtonStyle.green, custom_id="feedback_submit", row=1)
async def submit_button(self, interaction: disnake.Interaction, button: disnake.ui.Button):
if self.rating == 0:
await interaction.response.send_message("❌ Veuillez sélectionner une note avant d'envoyer.", ephemeral=True)
return
@ -66,11 +66,11 @@ class FeedbackView(discord.ui.View):
if staff_user:
# Create feedback embed
embed = discord.Embed(
embed = disnake.Embed(
title="📝 Nouveau Commentaire Reçu",
description=f"Un utilisateur a laissé un avis sur le ticket #{self.ticket.channel_id}",
color=discord.Color.gold() if self.rating < 3 else discord.Color.green(),
timestamp=discord.utils.utcnow()
color=disnake.Color.gold() if self.rating < 3 else disnake.Color.green(),
timestamp=disnake.utils.utcnow()
)
embed.add_field(
@ -99,7 +99,7 @@ class FeedbackView(discord.ui.View):
try:
await staff_user.send(embed=embed)
print(f"DEBUG: DM sent successfully to {staff_user}", flush=True)
except discord.Forbidden:
except disnake.Forbidden:
print(f"DEBUG: DM failed - Forbidden (DMs closed)", flush=True)
except Exception as e:
print(f"DEBUG: Error sending feedback DM: {e}", flush=True)
@ -111,34 +111,34 @@ class FeedbackView(discord.ui.View):
except Exception as e:
print(f"Error in feedback notification: {e}", flush=True)
class RatingSelect(discord.ui.Select):
class RatingSelect(disnake.ui.Select):
def __init__(self):
options = [
discord.SelectOption(label="⭐⭐⭐⭐⭐ (Excellent)", value="5", description="Service parfait !"),
discord.SelectOption(label="⭐⭐⭐⭐ (Très bien)", value="4", description="Très bon service"),
discord.SelectOption(label="⭐⭐⭐ (Bien)", value="3", description="Correct"),
discord.SelectOption(label="⭐⭐ (Moyen)", value="2", description="Peut mieux faire"),
discord.SelectOption(label="⭐ (Mauvais)", value="1", description="Insatisfaisant")
disnake.SelectOption(label="⭐⭐⭐⭐⭐ (Excellent)", value="5", description="Service parfait !"),
disnake.SelectOption(label="⭐⭐⭐⭐ (Très bien)", value="4", description="Très bon service"),
disnake.SelectOption(label="⭐⭐⭐ (Bien)", value="3", description="Correct"),
disnake.SelectOption(label="⭐⭐ (Moyen)", value="2", description="Peut mieux faire"),
disnake.SelectOption(label="⭐ (Mauvais)", value="1", description="Insatisfaisant")
]
super().__init__(placeholder="Notez le service...", min_values=1, max_values=1, options=options, row=0)
async def callback(self, interaction: discord.Interaction):
async def callback(self, interaction: disnake.Interaction):
self.view.rating = int(self.values[0])
await interaction.response.defer()
class FeedbackModal(discord.ui.Modal):
class FeedbackModal(disnake.ui.Modal):
def __init__(self, view):
super().__init__(title="Votre avis")
self.view = view
self.comment = discord.ui.TextInput(
self.comment = disnake.ui.TextInput(
label="Commentaire",
style=discord.TextStyle.paragraph,
style=disnake.TextStyle.paragraph,
placeholder="Dites-nous ce que vous avez pensé du support...",
required=True,
max_length=1000
)
self.add_item(self.comment)
async def on_submit(self, interaction: discord.Interaction):
async def on_submit(self, interaction: disnake.Interaction):
self.view.comment = self.comment.value
await interaction.response.send_message("Commentaire enregistré ! N'oubliez pas de valider avec 'Envoyer'.", ephemeral=True)

View file

@ -8,7 +8,7 @@ from datetime import datetime, timedelta
from collections import defaultdict
from typing import List, Dict, Any, Optional
import discord
import disnake
import matplotlib.pyplot as plt
import plotly.graph_objects as go
import plotly.express as px
@ -254,7 +254,7 @@ class StaffAnalytics:
return buf
class StaffStatsView(discord.ui.View):
class StaffStatsView(disnake.ui.View):
"""View for selecting individual staff member stats"""
def __init__(self, bot, staff_list: List[Dict[str, Any]], analytics: StaffAnalytics):
@ -272,7 +272,7 @@ class StaffStatsView(discord.ui.View):
description = f"Note: {staff['average_rating']:.1f}" if staff['total_ratings'] > 0 else "Aucune évaluation"
options.append(
discord.SelectOption(
disnake.SelectOption(
label=label,
value=str(staff['user_id']),
description=description[:50]
@ -280,14 +280,14 @@ class StaffStatsView(discord.ui.View):
)
if options:
select = discord.ui.Select(
select = disnake.ui.Select(
placeholder="Sélectionnez un membre du staff...",
options=options
)
select.callback = self._staff_selected
self.add_item(select)
async def _staff_selected(self, interaction: discord.Interaction):
async def _staff_selected(self, interaction: disnake.Interaction):
"""Handle staff selection"""
staff_id = int(interaction.data["values"][0])
staff_data = next((s for s in self.staff_list if s['user_id'] == staff_id), None)
@ -301,10 +301,10 @@ class StaffStatsView(discord.ui.View):
# Create individual chart
chart = self.analytics.create_individual_staff_chart(staff_data)
embed = discord.Embed(
embed = disnake.Embed(
title=f"📊 Statistiques de {staff_data['name']}",
description="Performances détaillées du membre du staff",
color=discord.Color.blue(),
color=disnake.Color.blue(),
timestamp=datetime.now()
)
@ -331,4 +331,4 @@ class StaffStatsView(discord.ui.View):
inline=True
)
await interaction.followup.send(embed=embed, file=discord.File(chart, 'staff_stats.png'), ephemeral=True)
await interaction.followup.send(embed=embed, file=disnake.File(chart, 'staff_stats.png'), ephemeral=True)

View file

@ -5,7 +5,7 @@ Beautiful embed and message formatters for the ticket system.
"""
from datetime import datetime
from typing import Optional, List, Dict, Any
from discord import Embed, Color, User, Member, Role
from disnake import Embed, Color, User, Member, Role
from ..data.models import (
TicketData,

View file

@ -3,13 +3,13 @@ Ticket Permissions Utility
==========================
Centralized permission logic for the ticket system.
"""
import discord
import disnake
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from ..data.models import GuildTicketConfig, TicketCategory
def is_staff(interaction: discord.Interaction, config: 'GuildTicketConfig', category: Optional['TicketCategory'] = None) -> bool:
def is_staff(interaction: disnake.Interaction, config: 'GuildTicketConfig', category: Optional['TicketCategory'] = None) -> bool:
"""
Check if the user has staff permissions.
@ -51,7 +51,7 @@ def is_staff(interaction: discord.Interaction, config: 'GuildTicketConfig', cate
return False
def can_access_config(interaction: discord.Interaction, config: 'GuildTicketConfig') -> bool:
def can_access_config(interaction: disnake.Interaction, config: 'GuildTicketConfig') -> bool:
"""
Check if the user can access the configuration.
@ -87,7 +87,7 @@ def can_access_config(interaction: discord.Interaction, config: 'GuildTicketConf
return False
def is_whitelisted(interaction: discord.Interaction, config: 'GuildTicketConfig', bot) -> bool:
def is_whitelisted(interaction: disnake.Interaction, config: 'GuildTicketConfig', bot) -> bool:
"""
Check if user is whitelisted or has staff/config permissions.
"""

View file

@ -7,7 +7,7 @@ import json
import os
from datetime import datetime
from typing import Optional, List, Dict, Any
from discord import User, Member
from disnake import User, Member
from ..data.models import TicketData, TicketMessage

View file

@ -1,7 +1,7 @@
import discord
import disnake
import re
from discord.ext import commands
from discord import app_commands, ui
from disnake.ext import commands
from disnake import ui
import json
import os
import asyncio
@ -63,12 +63,12 @@ def save_settings(guild_id: int, settings: dict) -> None:
with open(TICKET_WHITELIST_SETTINGS_FILE, "w", encoding='utf-8') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
async def get_ticket_user_id(channel: discord.TextChannel) -> Optional[int]:
async def get_ticket_user_id(channel: disnake.TextChannel) -> Optional[int]:
"""Récupère l'ID du propriétaire du ticket de manière robuste (gère le lag de cache)"""
import re
topic = getattr(channel, "topic", None)
# Tentative d'utilisation du cache d.py (plus rapide)
# Tentative d'utilisation du cache (plus rapide)
if topic:
match = re.search(r"(\d{17,20})", topic)
if match:
@ -88,12 +88,12 @@ async def get_ticket_user_id(channel: discord.TextChannel) -> Optional[int]:
return None
class TicketWhitelistConfigView(ui.View):
class TicketWhitelistConfigView(disnake.ui.View):
def __init__(self, guild_id: int):
super().__init__(timeout=60)
self.guild_id = guild_id
async def prompt_for_id(self, interaction: discord.Interaction, prompt_text: str, is_category: bool = False):
async def prompt_for_id(self, interaction: disnake.Interaction, prompt_text: str, is_category: bool = False):
await interaction.response.send_message(prompt_text, ephemeral=True)
def check(m):
@ -104,7 +104,7 @@ class TicketWhitelistConfigView(ui.View):
target = None
if is_category:
if msg.content.isdigit():
target = discord.utils.get(interaction.guild.categories, id=int(msg.content))
target = disnake.utils.get(interaction.guild.categories, id=int(msg.content))
if not target:
await interaction.followup.send("❌ Catégorie introuvable avec cet ID.", ephemeral=True)
return None
@ -129,8 +129,8 @@ class TicketWhitelistConfigView(ui.View):
await interaction.followup.send("❌ Temps écoulé.", ephemeral=True)
return None
@ui.button(label="Catégorie Tickets", style=discord.ButtonStyle.primary, row=0, emoji="📁")
async def set_category(self, interaction: discord.Interaction, button: ui.Button):
@ui.button(label="Catégorie Tickets", style=disnake.ButtonStyle.primary, row=0, emoji="📁")
async def set_category(self, interaction: disnake.Interaction, button: ui.Button):
category = await self.prompt_for_id(interaction, "Envoie l'ID de la **catégorie** où créer les tickets :", is_category=True)
if category:
settings = load_settings(self.guild_id)
@ -138,8 +138,8 @@ class TicketWhitelistConfigView(ui.View):
save_settings(self.guild_id, settings)
await interaction.followup.send(f"✅ Catégorie configurée sur **{category.name}**", ephemeral=True)
@ui.button(label="Rôle Staff", style=discord.ButtonStyle.primary, row=0, emoji="🛡️")
async def set_staff_role(self, interaction: discord.Interaction, button: ui.Button):
@ui.button(label="Rôle Staff", style=disnake.ButtonStyle.primary, row=0, emoji="🛡️")
async def set_staff_role(self, interaction: disnake.Interaction, button: ui.Button):
role = await self.prompt_for_id(interaction, "Mentionne le **rôle Staff** ou envoie son ID :")
if role:
settings = load_settings(self.guild_id)
@ -147,8 +147,8 @@ class TicketWhitelistConfigView(ui.View):
save_settings(self.guild_id, settings)
await interaction.followup.send(f"✅ Rôle Staff configuré sur **{role.name}**", ephemeral=True)
@ui.button(label="Rôle Équipe Staff", style=discord.ButtonStyle.primary, row=0, emoji="👥")
async def set_team_staff_role(self, interaction: discord.Interaction, button: ui.Button):
@ui.button(label="Rôle Équipe Staff", style=disnake.ButtonStyle.primary, row=0, emoji="👥")
async def set_team_staff_role(self, interaction: disnake.Interaction, button: ui.Button):
role = await self.prompt_for_id(interaction, "Mentionne le **rôle Équipe Staff** (à ping pour les nouveaux tickets) ou envoie son ID :")
if role:
settings = load_settings(self.guild_id)
@ -156,8 +156,8 @@ class TicketWhitelistConfigView(ui.View):
save_settings(self.guild_id, settings)
await interaction.followup.send(f"✅ Rôle Équipe Staff configuré sur **{role.name}**", ephemeral=True)
@ui.button(label="Rôle Attente Oral", style=discord.ButtonStyle.secondary, row=1, emoji="🎤")
async def set_attente_oral_role(self, interaction: discord.Interaction, button: ui.Button):
@ui.button(label="Rôle Attente Oral", style=disnake.ButtonStyle.secondary, row=1, emoji="🎤")
async def set_attente_oral_role(self, interaction: disnake.Interaction, button: ui.Button):
role = await self.prompt_for_id(interaction, "Mentionne le rôle **Attente Oral** ou envoie son ID :")
if role:
settings = load_settings(self.guild_id)
@ -165,8 +165,8 @@ class TicketWhitelistConfigView(ui.View):
save_settings(self.guild_id, settings)
await interaction.followup.send(f"✅ Rôle Attente Oral configuré sur **{role.name}**", ephemeral=True)
@ui.button(label="Rôle Candidature à faire", style=discord.ButtonStyle.secondary, row=1, emoji="📝")
async def set_candidature_a_faire_role(self, interaction: discord.Interaction, button: ui.Button):
@ui.button(label="Rôle Candidature à faire", style=disnake.ButtonStyle.secondary, row=1, emoji="📝")
async def set_candidature_a_faire_role(self, interaction: disnake.Interaction, button: ui.Button):
role = await self.prompt_for_id(interaction, "Mentionne le rôle **Candidature à faire** ou envoie son ID :")
if role:
settings = load_settings(self.guild_id)
@ -174,8 +174,8 @@ class TicketWhitelistConfigView(ui.View):
save_settings(self.guild_id, settings)
await interaction.followup.send(f"✅ Rôle Candidature à faire configuré sur **{role.name}**", ephemeral=True)
@ui.button(label="Rôle Citoyen", style=discord.ButtonStyle.success, row=1, emoji="🏙️")
async def set_citoyen_role(self, interaction: discord.Interaction, button: ui.Button):
@ui.button(label="Rôle Citoyen", style=disnake.ButtonStyle.success, row=1, emoji="🏙️")
async def set_citoyen_role(self, interaction: disnake.Interaction, button: ui.Button):
role = await self.prompt_for_id(interaction, "Mentionne le rôle **Citoyen** ou envoie son ID :")
if role:
settings = load_settings(self.guild_id)
@ -184,7 +184,7 @@ class TicketWhitelistConfigView(ui.View):
await interaction.followup.send(f"✅ Rôle Citoyen configuré sur **{role.name}**", ephemeral=True)
class ContinueFormView(discord.ui.View):
class ContinueFormView(disnake.ui.View):
"""Vue avec un bouton pour continuer à l'étape suivante"""
def __init__(self, current_step, step1_data=None, step2_part1_data=None, step2_part2_data=None):
@ -194,8 +194,8 @@ class ContinueFormView(discord.ui.View):
self.step2_part1_data = step2_part1_data
self.step2_part2_data = step2_part2_data
@discord.ui.button(label="📋 Continuer le formulaire", style=discord.ButtonStyle.green, custom_id="continue_form")
async def continue_form(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="📋 Continuer le formulaire", style=disnake.ButtonStyle.green, custom_id="continue_form")
async def continue_form(self, interaction: disnake.Interaction, button: disnake.ui.Button):
"""Passe à l'étape suivante du formulaire"""
if self.current_step == 1:
await interaction.response.send_modal(TicketStep2Part1(self.step1_data))
@ -207,79 +207,99 @@ class ContinueFormView(discord.ui.View):
# Vérifier si le message existe toujours avant de le modifier
try:
await interaction.message.edit(view=self)
except discord.errors.NotFound:
except disnake.errors.NotFound:
# Si le message n'existe plus, nous ne faisons rien
pass
class TicketStep1(discord.ui.Modal, title="📋 Informations Personnelles"):
class TicketStep1(disnake.ui.Modal):
"""Étape 1 du formulaire - Infos personnelles"""
pseudo = discord.ui.TextInput(label="Pseudo Epic (Fortnite)", required=True, max_length=16)
age = discord.ui.TextInput(label="Âge", required=True, max_length=2)
experience = discord.ui.TextInput(label="Expérience RP ?", style=discord.TextStyle.paragraph, required=True, max_length=50)
decouverte = discord.ui.TextInput(label="Comment as-tu découvert le serveur ?", required=True, max_length=20)
micro = discord.ui.TextInput(label="As-tu un micro de qualité ?", required=True, max_length=3)
def __init__(self):
super().__init__(
title="📋 Informations Personnelles",
custom_id="ticket_step1",
components=[
disnake.ui.TextInput(label="Pseudo Epic (Fortnite)", custom_id="pseudo", required=True, max_length=16),
disnake.ui.TextInput(label="Âge", custom_id="age", required=True, max_length=2),
disnake.ui.TextInput(label="Expérience RP ?", custom_id="experience", style=disnake.TextInputStyle.paragraph, required=True, max_length=50),
disnake.ui.TextInput(label="Comment as-tu découvert le serveur ?", custom_id="decouverte", required=True, max_length=20),
disnake.ui.TextInput(label="As-tu un micro de qualité ?", custom_id="micro", required=True, max_length=3),
]
)
async def on_submit(self, interaction: discord.Interaction):
async def callback(self, interaction: disnake.ModalInteraction):
"""Envoie un bouton pour passer à l'étape 2"""
view = ContinueFormView(current_step=1, step1_data=self)
view = ContinueFormView(current_step=1, step1_data=interaction.text_values)
await interaction.response.send_message("✅ Première étape terminée ! Cliquez ci-dessous pour continuer.", view=view, ephemeral=True)
class TicketStep2Part1(discord.ui.Modal, title="👤 Infos sur ton personnage (Partie 1)"):
class TicketStep2Part1(disnake.ui.Modal):
"""Étape 2 du formulaire - Infos RP (Partie 1)"""
def __init__(self, step1_data):
super().__init__()
self.step1_data = step1_data
super().__init__(
title="👤 Infos sur ton personnage (Partie 1)",
custom_id="ticket_step2_part1",
components=[
disnake.ui.TextInput(label="Prénom", custom_id="prenom", required=True),
disnake.ui.TextInput(label="Nom", custom_id="nom", required=True),
disnake.ui.TextInput(label="Âge du personnage", custom_id="age_perso", required=True),
disnake.ui.TextInput(label="Genre", custom_id="genre", required=True, max_length=5),
disnake.ui.TextInput(label="Date et lieu de naissance", custom_id="naissance", required=True),
]
)
prenom = discord.ui.TextInput(label="Prénom", required=True)
nom = discord.ui.TextInput(label="Nom", required=True)
age_perso = discord.ui.TextInput(label="Âge du personnage", required=True)
genre = discord.ui.TextInput(label="Genre", required=True, max_length=5)
naissance = discord.ui.TextInput(label="Date et lieu de naissance", required=True)
async def on_submit(self, interaction: discord.Interaction):
async def callback(self, interaction: disnake.ModalInteraction):
"""Envoie un bouton pour passer à l'étape suivante"""
view = ContinueFormView(current_step=2, step1_data=self.step1_data, step2_part1_data=self)
view = ContinueFormView(current_step=2, step1_data=self.step1_data, step2_part1_data=interaction.text_values)
await interaction.response.send_message("✅ Deuxième partie terminée ! Cliquez ci-dessous pour continuer.", view=view, ephemeral=True)
class TicketStep2Part2(discord.ui.Modal, title="👤 Infos sur ton personnage (Partie 2)"):
class TicketStep2Part2(disnake.ui.Modal):
"""Étape 2 du formulaire - Infos RP (Partie 2)"""
def __init__(self, step1_data, step2_part1_data):
super().__init__()
self.step1_data = step1_data
self.step2_part1_data = step2_part1_data
super().__init__(
title="👤 Infos sur ton personnage (Partie 2)",
custom_id="ticket_step2_part2",
components=[
disnake.ui.TextInput(label="Traits de caractère", custom_id="traits", required=True),
disnake.ui.TextInput(label="Nationalité", custom_id="nationalite", required=True),
disnake.ui.TextInput(label="Skin RP", custom_id="skin_rp", required=True),
disnake.ui.TextInput(label="Métier souhaité", custom_id="metier_souhaite", required=True),
]
)
traits = discord.ui.TextInput(label="Traits de caractère", required=True)
nationalite = discord.ui.TextInput(label="Nationalité", required=True)
skin_rp = discord.ui.TextInput(label="Skin RP", required=True)
metier_souhaite = discord.ui.TextInput(label="Métier souhaité", required=True)
async def on_submit(self, interaction: discord.Interaction):
async def callback(self, interaction: disnake.ModalInteraction):
"""Envoie un bouton pour passer à l'étape 3"""
view = ContinueFormView(current_step=3, step1_data=self.step1_data, step2_part1_data=self.step2_part1_data, step2_part2_data=self)
view = ContinueFormView(current_step=3, step1_data=self.step1_data, step2_part1_data=self.step2_part1_data, step2_part2_data=interaction.text_values)
await interaction.response.send_message("✅ Deuxième étape terminée ! Cliquez ci-dessous pour continuer.", view=view, ephemeral=True)
class TicketStep3(discord.ui.Modal, title="📋 Développement du Personnage"):
class TicketStep3(disnake.ui.Modal):
"""Étape 3 du formulaire - Histoire du personnage"""
def __init__(self, step1_data, step2_part1_data, step2_part2_data):
super().__init__()
self.step1_data = step1_data
self.step2_part1_data = step2_part1_data
self.step2_part2_data = step2_part2_data
super().__init__(
title="📋 Développement du Personnage",
custom_id="ticket_step3",
components=[
disnake.ui.TextInput(
label="Histoire du personnage",
custom_id="histoire",
style=disnake.TextInputStyle.paragraph,
required=True,
min_length=425,
max_length=500
),
disnake.ui.TextInput(label="Objectifs RP (court et long terme)", custom_id="objectifs", style=disnake.TextInputStyle.paragraph, required=True, max_length=100),
]
)
histoire = discord.ui.TextInput(
label="Histoire du personnage",
style=discord.TextStyle.paragraph,
required=True,
min_length=425,
max_length=500 # Limite de caractères maximum
)
objectifs = discord.ui.TextInput(label="Objectifs RP (court et long terme)", style=discord.TextStyle.paragraph, required=True, max_length=100)
async def on_submit(self, interaction: discord.Interaction):
async def callback(self, interaction: disnake.ModalInteraction):
"""Création du ticket après validation"""
guild = interaction.guild
user = interaction.user
@ -292,12 +312,12 @@ class TicketStep3(discord.ui.Modal, title="📋 Développement du Personnage"):
if not category_id or not staff_role_id or not team_staff_role_id:
return await interaction.response.send_message("❌ Erreur : Le système de tickets n'est pas entièrement configuré sur ce serveur (Catégorie, Rôle Staff ou Rôle Équipe Staff manquant).", ephemeral=True)
category = discord.utils.get(guild.categories, id=int(category_id))
category = disnake.utils.get(guild.categories, id=int(category_id))
if category is None:
return await interaction.response.send_message("❌ Erreur : La catégorie des tickets configurée n'existe plus !", ephemeral=True)
staff_role = discord.utils.get(guild.roles, id=int(staff_role_id))
team_staff_role = discord.utils.get(guild.roles, id=int(team_staff_role_id))
staff_role = disnake.utils.get(guild.roles, id=int(staff_role_id))
team_staff_role = disnake.utils.get(guild.roles, id=int(team_staff_role_id))
if staff_role is None or team_staff_role is None:
return await interaction.response.send_message("❌ Erreur : L'un des rôles staff configurés n'existe plus !", ephemeral=True)
@ -309,10 +329,10 @@ class TicketStep3(discord.ui.Modal, title="📋 Développement du Personnage"):
# Définir les permissions du salon du ticket
overwrites = {
guild.default_role: discord.PermissionOverwrite(read_messages=False),
user: discord.PermissionOverwrite(read_messages=True, send_messages=True),
staff_role: discord.PermissionOverwrite(read_messages=True, send_messages=True),
team_staff_role: discord.PermissionOverwrite(read_messages=True, send_messages=False)
guild.default_role: disnake.PermissionOverwrite(read_messages=False),
user: disnake.PermissionOverwrite(read_messages=True, send_messages=True),
staff_role: disnake.PermissionOverwrite(read_messages=True, send_messages=True),
team_staff_role: disnake.PermissionOverwrite(read_messages=True, send_messages=False)
}
# Créer un salon de ticket avec le nom [pseudo]-écrit
@ -323,28 +343,35 @@ class TicketStep3(discord.ui.Modal, title="📋 Développement du Personnage"):
)
# Création de l'embed avec les infos du formulaire
embed = discord.Embed(title="📩 Ticket Whitelist", color=0x00ff00)
embed = disnake.Embed(title="📩 Ticket Whitelist", color=0x00ff00)
# Helper to get value from data
s1 = self.step1_data
s2p1 = self.step2_part1_data
s2p2 = self.step2_part2_data
s3 = interaction.text_values
embed.add_field(name="📋 - Informations Personnelles", value=(
f"> - Pseudo Epic : {self.step1_data.pseudo.value}\n"
f"> - Âge : {self.step1_data.age.value}\n"
f"> - Expérience RP : {self.step1_data.experience.value}\n"
f"> - Découverte du serveur : {self.step1_data.decouverte.value}\n"
f"> - Micro de qualité : {self.step1_data.micro.value}"
f"> - Pseudo Epic : {s1['pseudo']}\n"
f"> - Âge : {s1['age']}\n"
f"> - Expérience RP : {s1['experience']}\n"
f"> - Découverte du serveur : {s1['decouverte']}\n"
f"> - Micro de qualité : {s1['micro']}"
), inline=False)
embed.add_field(name="👤 - Informations RP", value=(
f"> - Prénom : {self.step2_part1_data.prenom.value}\n"
f"> - Nom : {self.step2_part1_data.nom.value}\n"
f"> - Âge du personnage : {self.step2_part1_data.age_perso.value}\n"
f"> - Genre : {self.step2_part1_data.genre.value}\n"
f"> - Naissance : {self.step2_part1_data.naissance.value}\n"
f"> - Traits de caractère : {self.step2_part2_data.traits.value}\n"
f"> - Nationalité : {self.step2_part2_data.nationalite.value}\n"
f"> - Skin RP : {self.step2_part2_data.skin_rp.value}\n"
f"> - Métier souhaité : {self.step2_part2_data.metier_souhaite.value}"
f"> - Prénom : {s2p1['prenom']}\n"
f"> - Nom : {s2p1['nom']}\n"
f"> - Âge du personnage : {s2p1['age_perso']}\n"
f"> - Genre : {s2p1['genre']}\n"
f"> - Naissance : {s2p1['naissance']}\n"
f"> - Traits de caractère : {s2p2['traits']}\n"
f"> - Nationalité : {s2p2['nationalite']}\n"
f"> - Skin RP : {s2p2['skin_rp']}\n"
f"> - Métier souhaité : {s2p2['metier_souhaite']}"
), inline=False)
embed.add_field(name="📋 - Développement du Personnage", value=(
f"> - Histoire : {self.histoire.value}\n"
f"> - Objectifs : {self.objectifs.value}"
f"> - Histoire : {s3['histoire']}\n"
f"> - Objectifs : {s3['objectifs']}"
), inline=False)
# Envoyer l'embed avec les boutons d'actions pour les staff
@ -355,25 +382,25 @@ class TicketStep3(discord.ui.Modal, title="📋 Développement du Personnage"):
# Mettre à jour le pseudo du joueur si le bot a les permissions nécessaires
if guild.me.guild_permissions.manage_nicknames:
new_nickname = f"{self.step2_part1_data.prenom.value} {self.step2_part1_data.nom.value} | {self.step1_data.pseudo.value}"
new_nickname = f"{s2p1['prenom']} {s2p1['nom']} | {s1['pseudo']}"
if len(new_nickname) > 32:
new_nickname = new_nickname[:32]
try: await user.edit(nick=new_nickname)
except discord.Forbidden: pass
except disnake.Forbidden: pass
# Envoyer un message de confirmation
try:
await interaction.response.send_message(f"✅ Ticket ouvert : {ticket_channel.mention}", ephemeral=True)
except discord.errors.NotFound:
except disnake.errors.NotFound:
await interaction.followup.send(f"✅ Ticket ouvert : {ticket_channel.mention}", ephemeral=True)
class TicketManagementView(discord.ui.View):
class TicketManagementView(disnake.ui.View):
"""Vue pour gérer le ticket avec les boutons Fermer, Réouvrir, Claim et Supprimer"""
def __init__(self):
super().__init__(timeout=None)
async def get_ticket_owner(self, interaction: discord.Interaction):
async def get_ticket_owner(self, interaction: disnake.Interaction):
"""Récupère le propriétaire du ticket via le helper robuste"""
owner_id = await get_ticket_user_id(interaction.channel)
if owner_id:
@ -386,8 +413,8 @@ class TicketManagementView(discord.ui.View):
return None
return None
@discord.ui.button(label="🔒 Fermer le Ticket", style=discord.ButtonStyle.red, custom_id="close_ticket")
async def close_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="🔒 Fermer le Ticket", style=disnake.ButtonStyle.red, custom_id="close_ticket")
async def close_ticket(self, interaction: disnake.Interaction, button: disnake.ui.Button):
"""Ferme le ticket en désactivant l'envoi de messages pour le membre"""
await interaction.response.defer(ephemeral=True)
@ -411,12 +438,14 @@ class TicketManagementView(discord.ui.View):
new_view = ClosedTicketView()
await interaction.channel.send(f"🔒 Ce ticket a été fermé par {interaction.user.mention}.", view=new_view)
await interaction.followup.send("✅ Ticket fermé avec succès.", ephemeral=True)
except disnake.errors.RateLimited as e:
await interaction.followup.send(f"⚠️ Limite de débit Discord : Réessayez dans {e.retry_after:.1f}s.", ephemeral=True)
except Exception as e:
kuby_logger.error(f"Erreur lors de la fermeture du ticket: {e}")
await interaction.followup.send(f"❌ Erreur lors de la fermeture : {e}", ephemeral=True)
@discord.ui.button(label="👤 Claim le Ticket", style=discord.ButtonStyle.blurple, custom_id="claim_ticket")
async def claim_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="👤 Claim le Ticket", style=disnake.ButtonStyle.blurple, custom_id="claim_ticket")
async def claim_ticket(self, interaction: disnake.Interaction, button: disnake.ui.Button):
"""Un staff prend en charge le ticket"""
await interaction.response.defer(ephemeral=False)
@ -441,13 +470,13 @@ class TicketManagementView(discord.ui.View):
kuby_logger.error(f"Erreur lors du claim du ticket: {e}")
await interaction.followup.send(f"❌ Erreur lors du claim : {e}")
class ClosedTicketView(discord.ui.View):
class ClosedTicketView(disnake.ui.View):
"""Vue pour les tickets fermés avec les boutons Réouvrir et Supprimer"""
def __init__(self):
super().__init__(timeout=None)
async def get_ticket_owner(self, interaction: discord.Interaction):
async def get_ticket_owner(self, interaction: disnake.Interaction):
"""Récupère le propriétaire du ticket via le helper robuste"""
owner_id = await get_ticket_user_id(interaction.channel)
if owner_id:
@ -460,8 +489,8 @@ class ClosedTicketView(discord.ui.View):
return None
return None
@discord.ui.button(label="♻ Réouvrir le Ticket", style=discord.ButtonStyle.green, custom_id="reopen_ticket")
async def reopen_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="♻ Réouvrir le Ticket", style=disnake.ButtonStyle.green, custom_id="reopen_ticket")
async def reopen_ticket(self, interaction: disnake.Interaction, button: disnake.ui.Button):
"""Réouvre le ticket pour le membre"""
await interaction.response.defer(ephemeral=True)
@ -478,10 +507,10 @@ class ClosedTicketView(discord.ui.View):
try:
# On demande au staff quel statut remettre
embed = discord.Embed(
embed = disnake.Embed(
title="♻️ Réouverture du Ticket",
description=f"Choisissez le statut à appliquer pour **{owner.display_name}**.",
color=discord.Color.blue()
color=disnake.Color.blue()
)
view = ReopenStatusView(owner)
await interaction.channel.send(embed=embed, view=view)
@ -490,25 +519,25 @@ class ClosedTicketView(discord.ui.View):
kuby_logger.error(f"Erreur lors de la préparation de réouverture: {e}")
await interaction.followup.send(f"❌ Erreur : {e}", ephemeral=True)
class ReopenStatusView(discord.ui.View):
class ReopenStatusView(disnake.ui.View):
"""Vue pour choisir le statut lors d'une réouverture"""
def __init__(self, owner: discord.Member):
def __init__(self, owner: disnake.Member):
super().__init__(timeout=180)
self.owner = owner
@discord.ui.button(label="Écrit (Ouvert)", style=discord.ButtonStyle.secondary)
async def set_written(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="Écrit (Ouvert)", style=disnake.ButtonStyle.secondary)
async def set_written(self, interaction: disnake.Interaction, button: disnake.ui.Button):
await self.apply_status(interaction, "ouvert")
@discord.ui.button(label="Oral (À faire)", style=discord.ButtonStyle.primary)
async def set_oral(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="Oral (À faire)", style=disnake.ButtonStyle.primary)
async def set_oral(self, interaction: disnake.Interaction, button: disnake.ui.Button):
await self.apply_status(interaction, "oral-à-faire")
@discord.ui.button(label="Accepté", style=discord.ButtonStyle.success)
async def set_accepted(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="Accepté", style=disnake.ButtonStyle.success)
async def set_accepted(self, interaction: disnake.Interaction, button: disnake.ui.Button):
await self.apply_status(interaction, "accepté")
async def apply_status(self, interaction: discord.Interaction, prefix: str):
async def apply_status(self, interaction: disnake.Interaction, prefix: str):
await interaction.response.defer(ephemeral=True)
try:
settings = load_settings(interaction.guild.id)
@ -555,8 +584,8 @@ class ReopenStatusView(discord.ui.View):
kuby_logger.error(f"Erreur lors de la réouverture/synchro: {e}")
await interaction.followup.send(f"❌ Erreur système : {e}", ephemeral=True)
@discord.ui.button(label="❌ Supprimer le Ticket", style=discord.ButtonStyle.danger, custom_id="delete_ticket")
async def delete_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="❌ Supprimer le Ticket", style=disnake.ButtonStyle.danger, custom_id="delete_ticket")
async def delete_ticket(self, interaction: disnake.Interaction, button: disnake.ui.Button):
"""Supprime le salon du ticket"""
await interaction.response.defer(ephemeral=True)
@ -575,14 +604,14 @@ class ReopenStatusView(discord.ui.View):
try: await interaction.followup.send(f"❌ Erreur lors de la suppression : {e}", ephemeral=True)
except: pass
class TicketView(discord.ui.View):
class TicketView(disnake.ui.View):
"""Vue avec le bouton pour ouvrir le ticket"""
def __init__(self):
super().__init__(timeout=None)
@discord.ui.button(label="📑 Ouvrir un Ticket Whitelist", style=discord.ButtonStyle.red, custom_id="open_ticket")
async def open_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
@disnake.ui.button(label="📑 Ouvrir un Ticket Whitelist", style=disnake.ButtonStyle.red, custom_id="open_ticket")
async def open_ticket(self, interaction: disnake.Interaction, button: disnake.ui.Button):
"""Affiche le premier formulaire"""
await interaction.response.send_modal(TicketStep1())
@ -598,11 +627,11 @@ class Ticket(commands.Cog):
self.bot.add_view(TicketManagementView())
self.bot.add_view(ClosedTicketView())
@app_commands.command(name="ticket_whitelist", description="Envoie l'embed pour ouvrir un ticket de whitelist.")
@app_commands.checks.has_permissions(administrator=True)
async def ticket_whitelist(self, interaction: discord.Interaction):
@commands.slash_command(name="ticket_whitelist", description="Envoie l'embed pour ouvrir un ticket de whitelist.")
@commands.has_permissions(administrator=True)
async def ticket_whitelist(self, interaction: disnake.ApplicationCommandInteraction):
"""Envoie l'embed avec le bouton d'ouverture de ticket"""
embed = discord.Embed(
embed = disnake.Embed(
title="📩 Ticket Whitelist",
description="Pour réaliser votre whitelist, ouvrez un ticket et remplissez le formulaire.",
color=0x00ff00
@ -611,16 +640,16 @@ class Ticket(commands.Cog):
await interaction.channel.send(embed=embed, view=view)
await interaction.response.send_message("✅ Embed des tickets envoyé.", ephemeral=True)
@app_commands.command(name="ticket_whitelist_config", description="Configurer le système de tickets whitelist (Admin)")
@app_commands.checks.has_permissions(administrator=True)
async def ticket_whitelist_config(self, interaction: discord.Interaction):
@commands.slash_command(name="ticket_whitelist_config", description="Configurer le système de tickets whitelist (Admin)")
@commands.has_permissions(administrator=True)
async def ticket_whitelist_config(self, interaction: disnake.ApplicationCommandInteraction):
"""Interface de configuration des tickets"""
settings = load_settings(interaction.guild.id)
embed = discord.Embed(
embed = disnake.Embed(
title="⚙️ Configuration Tickets Whitelist",
description="Utilisez les boutons ci-dessous pour lier la catégorie et les rôles.",
color=discord.Color.blue()
color=disnake.Color.blue()
)
cat_id = settings.get("ticket_category_id")
@ -673,7 +702,7 @@ class Ticket(commands.Cog):
user_id = user_id
try:
user = await message.guild.fetch_member(user_id)
except (discord.NotFound, discord.HTTPException):
except (disnake.NotFound, disnake.HTTPException):
await message.channel.send("❌ Utilisateur introuvable. A-t-il quitté le serveur ?", delete_after=10)
return
@ -701,19 +730,19 @@ class Ticket(commands.Cog):
asyncio.create_task(message.channel.edit(name=new_name))
# Confirmation dans le salon (Feedback visuel)
embed_val = discord.Embed(
embed_val = disnake.Embed(
title="🎉 Whitelist Validée !",
description=f"Félicitations {user.mention}, ton entretien oral a été validé par {message.author.mention} !\nTu es désormais **Citoyen**.",
color=discord.Color.green()
color=disnake.Color.green()
)
await message.channel.send(embed=embed_val)
# DM au joueur (RESTAURATION DESIGN CAPTURE)
try:
embed_dm = discord.Embed(
embed_dm = disnake.Embed(
title="🎉 Félicitations - Citoyenneté Validée !",
description=f"Nous avons le plaisir de t'annoncer que ton passage sur **{message.guild.name}** a été validé avec succès !",
color=discord.Color.green()
color=disnake.Color.green()
)
embed_dm.add_field(name="🏙️ Nouveau Statut", value="Citoyen", inline=True)
embed_dm.add_field(name="📍 Serveur", value=message.guild.name, inline=True)
@ -740,19 +769,19 @@ class Ticket(commands.Cog):
asyncio.create_task(message.channel.edit(name=new_name))
# Confirmation dans le salon
embed_val = discord.Embed(
embed_val = disnake.Embed(
title="📝 Étape Écrite Validée !",
description=f"Ta candidature écrite a été validée par {message.author.mention}.\nTu passes désormais en **Attente Oral**.",
color=discord.Color.orange()
color=disnake.Color.orange()
)
await message.channel.send(embed=embed_val)
# DM au joueur (RESTAURATION DESIGN CAPTURE)
try:
embed_dm = discord.Embed(
embed_dm = disnake.Embed(
title="📝 Validation Étape Écrite",
description=f"Ta candidature écrite sur **{message.guild.name}** a été examinée et validée par notre équipe !",
color=discord.Color.orange()
color=disnake.Color.orange()
)
embed_dm.add_field(name="🎤 Statut Actuel", value="Attente Oral", inline=True)
embed_dm.add_field(name="🛠️ Action Requise", value="Merci de te rendre dans le salon vocal 'Attente Oral' dès que tu es disponible pour passer ton entretien.", inline=False)

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é.")

View file

@ -1,6 +1,5 @@
import discord
from discord.ext import commands
from discord import app_commands
import disnake
from disnake.ext import commands
import sqlite3
import aiohttp
import io
@ -145,9 +144,9 @@ class Welcome(commands.Cog):
gif_url = "https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNXV1NW02NTk0bGF2ZHcyMGFna2F6amRrbGpobGpsMHowOW5xa2E5eCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/ZtFuW4rsUbfrRzPauJ/giphy.gif"
await member.send(f"{formatted_message}\n\n{gif_url}")
kuby_logger.info(f"✅ [Welcome] DM envoyé avec succès à {member.name} (envoi groupé)")
except discord.Forbidden:
except disnake.Forbidden:
kuby_logger.warning(f"🚫 [Welcome] Impossible d'envoyer le DM à {member.name} (DMs fermés)")
except discord.HTTPException as e:
except disnake.HTTPException as e:
if e.code == 40003: # Rate limited (Opening DMs too fast)
kuby_logger.warning(f"⏳ [Welcome] Limite de débit Discord atteinte pour les DMs : {member.name} n'a pas reçu son message.")
else:
@ -177,13 +176,13 @@ class Welcome(commands.Cog):
member_count=member.guild.member_count
) if welcome_channel_message else "Bienvenue dans le serveur !\nSi vous cherchez des RolePlay de qualité,\nVous êtes au bon endroit. <a:sip:1316891821858619452>"
embed = discord.Embed(title=f"Merci d'accueillir {member} 🤝",
embed = disnake.Embed(title=f"Merci d'accueillir {member} 🤝",
description=channel_description,
color=discord.Color.dark_purple())
color=disnake.Color.dark_purple())
embed.set_image(url="attachment://welcome.png")
await channel.send(content=member.mention, embed=embed, file=discord.File(img, filename="welcome.png"))
await channel.send(content=member.mention, embed=embed, file=disnake.File(img, filename="welcome.png"))
async def save_banner_attachment(self, attachment: discord.Attachment, guild_id: int) -> str:
async def save_banner_attachment(self, attachment: disnake.Attachment, guild_id: int) -> str:
"""Sauvegarde l'attachment et retourne le chemin du fichier"""
base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "banners"))
os.makedirs(base_dir, exist_ok=True)
@ -215,9 +214,9 @@ class Welcome(commands.Cog):
return os.path.basename(banner_path)
@app_commands.command(name="setwelcome", description="Définit le salon, les messages de bienvenue et le fond de la bannière")
@app_commands.checks.has_permissions(administrator=True)
async def set_welcome(self, interaction: discord.Interaction, channel: discord.TextChannel, message_dm: str, message_salon: str, server_name: str, banner_file: discord.Attachment = None):
@commands.slash_command(name="setwelcome", description="Définit le salon, les messages de bienvenue et le fond de la bannière")
@commands.has_permissions(administrator=True)
async def set_welcome(self, interaction: disnake.ApplicationCommandInteraction, channel: disnake.TextChannel, message_dm: str, message_salon: str, server_name: str, banner_file: disnake.Attachment = None):
banner_path = None
# Si un fichier est uploadé, le sauvegarder
@ -237,7 +236,7 @@ class Welcome(commands.Cog):
welcome_server_name=excluded.welcome_server_name,
welcome_banner_background=COALESCE(excluded.welcome_banner_background, welcome_banner_background),
welcome_channel_message=excluded.welcome_channel_message''',
(interaction.guild_id, channel.id, message_dm, server_name, banner_path, message_salon))
(interaction.guild_id, channel.id, message_dm, server_name, banner_path, message_salon))
conn.commit()
conn.close()

View file

@ -1,6 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
import disnake
from disnake.ext import commands
import json
import os
from typing import List
@ -32,95 +31,60 @@ class Whitelist(commands.Cog):
self.whitelist[str(guild_id)] = users
self.save_whitelist()
@commands.hybrid_group(name="whitelist", aliases=["wl"], description="Gère la whitelist du serveur")
async def whitelist_group(self, ctx):
@commands.slash_command(name="whitelist", description="Gère la whitelist du serveur")
async def whitelist_group(self, interaction: disnake.ApplicationCommandInteraction):
"""Groupe de commandes pour gérer la whitelist"""
if ctx.invoked_subcommand is None:
await ctx.send_help(ctx.command)
pass
# ✅ Ajouter un utilisateur
@whitelist_group.command(name="ajouter", description="Ajoute un utilisateur à la whitelist")
@app_commands.describe(utilisateur="L'utilisateur à ajouter")
async def ajouter(self, ctx, utilisateur: discord.User):
interaction = ctx.interaction
guild_id = ctx.guild.id
@whitelist_group.sub_command(name="ajouter", description="Ajoute un utilisateur à la whitelist")
async def ajouter(self, interaction: disnake.ApplicationCommandInteraction, utilisateur: disnake.User):
guild_id = interaction.guild.id
users = self._get_guild_whitelist(guild_id)
user_id = str(utilisateur.id)
if user_id in users:
msg = f"🔒 {utilisateur.mention} est déjà dans la whitelist."
if interaction:
await interaction.response.send_message(msg, ephemeral=True)
else:
await ctx.send(msg)
return
return await interaction.response.send_message(f"🔒 {utilisateur.mention} est déjà dans la whitelist.", ephemeral=True)
users.append(user_id)
self._set_guild_whitelist(guild_id, users)
msg = f"{utilisateur.mention} a été ajouté à la whitelist."
if interaction:
await interaction.response.send_message(msg, ephemeral=True)
else:
await ctx.send(msg)
await interaction.response.send_message(f"{utilisateur.mention} a été ajouté à la whitelist.", ephemeral=True)
# ❌ Retirer un utilisateur
@whitelist_group.command(name="retirer", description="Retire un utilisateur de la whitelist")
@app_commands.describe(utilisateur="L'utilisateur à retirer")
async def retirer(self, ctx, utilisateur: discord.User):
interaction = ctx.interaction
guild_id = ctx.guild.id
@whitelist_group.sub_command(name="retirer", description="Retire un utilisateur de la whitelist")
async def retirer(self, interaction: disnake.ApplicationCommandInteraction, utilisateur: disnake.User):
guild_id = interaction.guild.id
users = self._get_guild_whitelist(guild_id)
user_id = str(utilisateur.id)
if user_id not in users:
msg = f"⚠️ {utilisateur.mention} n'est pas dans la whitelist."
if interaction:
await interaction.response.send_message(msg, ephemeral=True)
else:
await ctx.send(msg)
return
return await interaction.response.send_message(f"⚠️ {utilisateur.mention} n'est pas dans la whitelist.", ephemeral=True)
users.remove(user_id)
self._set_guild_whitelist(guild_id, users)
msg = f"{utilisateur.mention} a été retiré de la whitelist."
if interaction:
await interaction.response.send_message(msg, ephemeral=True)
else:
await ctx.send(msg)
await interaction.response.send_message(f"{utilisateur.mention} a été retiré de la whitelist.", ephemeral=True)
# 📋 Voir la whitelist
@whitelist_group.command(name="liste", description="Affiche les utilisateurs de la whitelist")
async def liste(self, ctx):
interaction = ctx.interaction
guild_id = ctx.guild.id
@whitelist_group.sub_command(name="liste", description="Affiche les utilisateurs de la whitelist")
async def liste(self, interaction: disnake.ApplicationCommandInteraction):
guild_id = interaction.guild.id
users = self._get_guild_whitelist(guild_id)
if not users:
msg = "📭 La whitelist est vide pour ce serveur."
if interaction:
await interaction.response.send_message(msg, ephemeral=True)
else:
await ctx.send(msg)
return
# Defer if interaction because fetch_user might take time
if interaction:
await interaction.response.defer(ephemeral=True)
return await interaction.response.send_message("📭 La whitelist est vide pour ce serveur.", ephemeral=True)
await interaction.response.defer(ephemeral=True)
membres = []
for user_id in users:
try:
utilisateur = await self.bot.fetch_user(int(user_id))
membres.append(f"- {utilisateur.mention} ({utilisateur.name})")
except discord.NotFound:
except disnake.NotFound:
membres.append(f"- <@{user_id}> (Utilisateur introuvable)")
texte = "\n".join(membres)
msg = f"📃 **Utilisateurs whitelistés pour ce serveur :**\n{texte}"
if interaction:
await interaction.followup.send(msg, ephemeral=True)
else:
await ctx.send(msg)
await interaction.edit_original_response(content=f"📃 **Utilisateurs whitelistés pour ce serveur :**\n{texte}")
def is_whitelisted(self, guild_id: int, user_id: int) -> bool:
"""Check if a user is whitelisted for a specific guild."""

View file

@ -1,6 +1,6 @@
import discord
from discord.ext import commands
from discord.ui import View, Button
import disnake
from disnake.ext import commands
from disnake.ui import View, Button
import json
import os
from datetime import datetime
@ -14,18 +14,18 @@ class WhitelistMonitor(commands.Cog):
self.whitelist_file = "data/whitelist.json"
self.persistent_views_added = False
self.big_permissions = [
discord.Permissions.administrator,
discord.Permissions.manage_guild,
discord.Permissions.manage_roles,
discord.Permissions.manage_channels,
discord.Permissions.kick_members,
discord.Permissions.ban_members,
discord.Permissions.manage_messages,
discord.Permissions.moderate_members,
discord.Permissions.manage_nicknames,
discord.Permissions.manage_emojis,
discord.Permissions.manage_webhooks,
discord.Permissions.view_audit_log
disnake.Permissions.administrator,
disnake.Permissions.manage_guild,
disnake.Permissions.manage_roles,
disnake.Permissions.manage_channels,
disnake.Permissions.kick_members,
disnake.Permissions.ban_members,
disnake.Permissions.manage_messages,
disnake.Permissions.moderate_members,
disnake.Permissions.manage_nicknames,
disnake.Permissions.manage_emojis,
disnake.Permissions.manage_webhooks,
disnake.Permissions.view_audit_log
]
def load_whitelist(self) -> Dict[str, List[str]]:
@ -70,7 +70,7 @@ class WhitelistMonitor(commands.Cog):
return True
return False
def has_big_permissions(self, role: discord.Role) -> bool:
def has_big_permissions(self, role: disnake.Role) -> bool:
"""Check if role has significant permissions."""
permissions = role.permissions
for perm in self.big_permissions:
@ -78,7 +78,7 @@ class WhitelistMonitor(commands.Cog):
return True
return False
async def send_whitelist_notification(self, member: discord.Member, removed_roles: List[discord.Role]):
async def send_whitelist_notification(self, member: disnake.Member, removed_roles: List[disnake.Role]):
"""Send DM notification to user about whitelist member losing roles."""
try:
# Find guild administrators to notify
@ -87,10 +87,10 @@ class WhitelistMonitor(commands.Cog):
return
# Create embed
embed = discord.Embed(
embed = disnake.Embed(
title="⚠️ Whitelist Alert",
description=f"Un membre whitelisté a perdu des rôles importants dans **{member.guild.name}**.",
color=discord.Color.orange(),
color=disnake.Color.orange(),
timestamp=datetime.now()
)
@ -121,7 +121,7 @@ class WhitelistMonitor(commands.Cog):
for admin in admins:
try:
await admin.send(embed=embed, view=view)
except discord.Forbidden:
except disnake.Forbidden:
continue # User has DMs disabled
except Exception as e:
@ -155,7 +155,7 @@ class WhitelistMonitor(commands.Cog):
print(f"[WhitelistMonitor] Error registering views: {e}")
@commands.Cog.listener()
async def on_member_update(self, before: discord.Member, after: discord.Member):
async def on_member_update(self, before: disnake.Member, after: disnake.Member):
"""Monitor role changes for whitelist members."""
if before.roles == after.roles:
return
@ -173,7 +173,7 @@ class WhitelistMonitor(commands.Cog):
await self.send_whitelist_notification(after, important_removed)
@commands.Cog.listener()
async def on_member_remove(self, member: discord.Member):
async def on_member_remove(self, member: disnake.Member):
"""Auto-remove from whitelist when member leaves server."""
if self.is_whitelisted(member.guild.id, member.id):
removed = self.remove_from_whitelist(member.guild.id, member.id)
@ -184,10 +184,10 @@ class WhitelistMonitor(commands.Cog):
if log_channel_id:
log_channel = member.guild.get_channel(log_channel_id)
if log_channel:
embed = discord.Embed(
embed = disnake.Embed(
title="🗑️ Auto-Whitelist Removal",
description=f"{member.mention} a été retiré automatiquement de la whitelist après avoir quitté le serveur.",
color=discord.Color.red(),
color=disnake.Color.red(),
timestamp=datetime.now()
)
await log_channel.send(embed=embed)
@ -214,15 +214,15 @@ class WhitelistRemovalView(View):
self.user_id = user_id
self.guild_id = guild_id
@discord.ui.button(
@disnake.ui.button(
label="Retirer de la whitelist",
style=discord.ButtonStyle.red,
style=disnake.ButtonStyle.red,
emoji="🗑️",
custom_id="whitelist_remove_button"
)
async def remove_whitelist(
self,
interaction: discord.Interaction,
interaction: disnake.Interaction,
button: Button
):
"""Remove user from whitelist when button is clicked."""
@ -250,15 +250,15 @@ class WhitelistRemovalView(View):
# Update button
button.disabled = True
button.label = "✅ Retiré"
button.style = discord.ButtonStyle.green
button.style = disnake.ButtonStyle.green
await interaction.response.edit_message(view=self)
# Send confirmation
embed = discord.Embed(
embed = disnake.Embed(
title="✅ Retrait effectué",
description=f"{user_mention} a été retiré de la whitelist.",
color=discord.Color.green()
color=disnake.Color.green()
)
await interaction.followup.send(embed=embed, ephemeral=True)
@ -270,10 +270,10 @@ class WhitelistRemovalView(View):
if log_channel_id:
log_channel = guild.get_channel(log_channel_id)
if log_channel:
embed = discord.Embed(
embed = disnake.Embed(
title="🗑️ Whitelist Manual Removal",
description=f"{user_mention} a été retiré manuellement de la whitelist.",
color=discord.Color.green(),
color=disnake.Color.green(),
timestamp=datetime.now()
)
embed.set_footer(text=f"Retiré par {interaction.user.display_name}")