Migration de Kuby vers disnake
This commit is contained in:
parent
dc6e235f27
commit
c8c579eefe
36 changed files with 1205 additions and 1253 deletions
16
bot.py
16
bot.py
|
|
@ -4,10 +4,10 @@ from dotenv import load_dotenv
|
|||
# Charger les variables d'environnement AVANT tout le reste
|
||||
load_dotenv()
|
||||
|
||||
import discord
|
||||
import disnake
|
||||
from commandes.ticket.utils.permissions import can_access_config
|
||||
from commandes.ticket.data.storage import get_storage
|
||||
from discord.ext import commands
|
||||
from disnake.ext import commands
|
||||
import asyncio
|
||||
from src.logger import kuby_logger, log_performance
|
||||
import logging
|
||||
|
|
@ -25,12 +25,12 @@ except ValueError:
|
|||
kuby_logger.error(f"❌ APPLICATION_ID invalide : {application_id_raw}")
|
||||
application_id = None
|
||||
|
||||
# Configuration des intents Discord
|
||||
intents = discord.Intents.default()
|
||||
# Configuration des intents Disnake
|
||||
intents = disnake.Intents.default()
|
||||
intents.message_content = True
|
||||
intents.members = True
|
||||
intents.voice_states = True
|
||||
kuby_logger.debug(f"Discord intents configured: {intents}")
|
||||
kuby_logger.debug(f"Disnake intents configured: {intents}")
|
||||
|
||||
class MyBot(commands.Bot):
|
||||
def __init__(self):
|
||||
|
|
@ -135,7 +135,7 @@ class MyBot(commands.Bot):
|
|||
# Réponse adaptée (Slash vs Message)
|
||||
msg = "❌ Vous n'êtes pas autorisé à utiliser cette commande. Veuillez demander à être ajouté à la whitelist."
|
||||
if ctx.interaction:
|
||||
await ctx.interaction.response.send_message(msg, ephemeral=True)
|
||||
await ctx.send(msg, ephemeral=True)
|
||||
else:
|
||||
await ctx.send(msg)
|
||||
return False
|
||||
|
|
@ -147,8 +147,8 @@ class MyBot(commands.Bot):
|
|||
|
||||
# Sync slash commands
|
||||
try:
|
||||
await self.tree.sync()
|
||||
kuby_logger.info("✅ Commandes slash synchronisées avec Discord")
|
||||
# Disnake handles syncing automatically by default
|
||||
kuby_logger.info("✅ Commandes slash prêtes avec Disnake")
|
||||
except Exception as e:
|
||||
kuby_logger.error(f"❌ Erreur lors de la synchronisation des commandes slash : {e}", exc_info=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 l’archive 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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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 n’as 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 n’as 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 t’appartient 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 n’as 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 l’ensemble 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 ---------
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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é.")
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
6
kuby.py
6
kuby.py
|
|
@ -28,10 +28,8 @@ kuby_logger.info("✅ Token Discord chargé avec succès")
|
|||
async def main():
|
||||
kuby_logger.info("🔄 Démarrage de la connexion Discord...")
|
||||
try:
|
||||
kuby_logger.debug("Starting async context manager")
|
||||
async with bot:
|
||||
kuby_logger.info("🔗 Connexion établie avec Discord...")
|
||||
await bot.start(TOKEN)
|
||||
kuby_logger.info("🔗 Connexion établie avec Discord...")
|
||||
await bot.start(TOKEN)
|
||||
except KeyboardInterrupt:
|
||||
kuby_logger.info("🛑 Bot shutdown requested by user")
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
discord.py>=2.3.0
|
||||
disnake>=2.11.0
|
||||
python-dotenv>=1.0.0
|
||||
Pillow>=10.0.0
|
||||
aiohttp>=3.8.0
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import discord
|
||||
import disnake
|
||||
import json
|
||||
import os
|
||||
import asyncio
|
||||
|
|
@ -18,7 +18,7 @@ class AdvancedLogger:
|
|||
os.makedirs(self.logs_dir, exist_ok=True)
|
||||
os.makedirs(self.member_logs_dir, exist_ok=True)
|
||||
|
||||
def log_message_event(self, message: discord.Message, event_type: str, details: Dict = None):
|
||||
def log_message_event(self, message: disnake.Message, event_type: str, details: Dict = None):
|
||||
"""Log message events (sent, edited, deleted)"""
|
||||
log_data = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
|
|
@ -37,7 +37,7 @@ class AdvancedLogger:
|
|||
|
||||
asyncio.create_task(self._write_log_async("messages", log_data))
|
||||
|
||||
def log_role_event(self, member: discord.Member, role: discord.Role, event_type: str):
|
||||
def log_role_event(self, member: disnake.Member, role: disnake.Role, event_type: str):
|
||||
"""Log role changes (added/removed)"""
|
||||
log_data = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
|
|
@ -52,7 +52,7 @@ class AdvancedLogger:
|
|||
|
||||
asyncio.create_task(self._write_log_async("roles", log_data))
|
||||
|
||||
def log_voice_event(self, member: discord.Member, channel: discord.VoiceChannel, event_type: str):
|
||||
def log_voice_event(self, member: disnake.Member, channel: disnake.VoiceChannel, event_type: str):
|
||||
"""Log voice channel events (join/leave)"""
|
||||
log_data = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
|
|
@ -66,7 +66,7 @@ class AdvancedLogger:
|
|||
|
||||
asyncio.create_task(self._write_log_async("voice", log_data))
|
||||
|
||||
def log_member_leave(self, member: discord.Member):
|
||||
def log_member_leave(self, member: disnake.Member):
|
||||
"""Log when a member leaves the server"""
|
||||
roles_data = {
|
||||
"role_ids": [role.id for role in member.roles if role.name != "@everyone"],
|
||||
|
|
@ -91,7 +91,7 @@ class AdvancedLogger:
|
|||
# Also save to general logs
|
||||
asyncio.create_task(self._write_log_async("member_events", log_data))
|
||||
|
||||
def log_member_join(self, member: discord.Member):
|
||||
def log_member_join(self, member: disnake.Member):
|
||||
"""Log when a member joins the server"""
|
||||
log_data = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
|
|
@ -162,12 +162,12 @@ class AdvancedLogger:
|
|||
except Exception as e:
|
||||
print(f"CRITICAL ERROR in AdvancedLogger._write_member_log_sync: {e}")
|
||||
|
||||
async def send_leave_notification(self, member: discord.Member, channel: discord.TextChannel):
|
||||
async def send_leave_notification(self, member: disnake.Member, channel: disnake.TextChannel):
|
||||
"""Send notification when member leaves"""
|
||||
embed = discord.Embed(
|
||||
embed = disnake.Embed(
|
||||
title="👋 Membre parti",
|
||||
description=f"{member.mention} ({member.name}#{member.discriminator}) a quitté le serveur.",
|
||||
color=discord.Color.red(),
|
||||
description=f"{member.mention} ({member.name}) a quitté le serveur.",
|
||||
color=disnake.Color.red(),
|
||||
timestamp=datetime.now()
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from datetime import datetime, timezone
|
|||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import discord
|
||||
import disnake
|
||||
|
||||
logger = logging.getLogger("kuby.json_export")
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ def get_history() -> list:
|
|||
|
||||
# ─── Génération servers.json ─────────────────────────────────────────────────
|
||||
|
||||
async def generate_servers_json(bot: discord.Client) -> dict:
|
||||
async def generate_servers_json(bot: disnake.Client) -> dict:
|
||||
config = load_config()
|
||||
fields = config["serverFields"]
|
||||
founder_keys = [r.lower() for r in config.get("founderRoles", ["fondateur", "founder"])]
|
||||
|
|
@ -249,7 +249,7 @@ async def generate_servers_json(bot: discord.Client) -> dict:
|
|||
|
||||
# ─── Génération members.json ─────────────────────────────────────────────────
|
||||
|
||||
async def generate_members_json(bot: discord.Client, guild_id: Optional[int] = None) -> dict:
|
||||
async def generate_members_json(bot: disnake.Client, guild_id: Optional[int] = None) -> dict:
|
||||
config = load_config()
|
||||
fields = config["memberFields"]
|
||||
target_id = guild_id or (int(config["targetGuildId"]) if config["targetGuildId"] else None)
|
||||
|
|
@ -324,7 +324,7 @@ async def generate_members_json(bot: discord.Client, guild_id: Optional[int] = N
|
|||
|
||||
# ─── Génération complète ─────────────────────────────────────────────────────
|
||||
|
||||
async def generate_all(bot: discord.Client, trigger: str = "auto") -> dict:
|
||||
async def generate_all(bot: disnake.Client, trigger: str = "auto") -> dict:
|
||||
results = {"servers": None, "members": None, "errors": [], "trigger": trigger}
|
||||
try:
|
||||
results["servers"] = await generate_servers_json(bot)
|
||||
|
|
@ -343,7 +343,7 @@ async def generate_all(bot: discord.Client, trigger: str = "auto") -> dict:
|
|||
|
||||
# ─── Prévisualisation (sans écriture fichier) ────────────────────────────────
|
||||
|
||||
async def preview_servers(bot: discord.Client, limit: int = 3) -> list:
|
||||
async def preview_servers(bot: disnake.Client, limit: int = 3) -> list:
|
||||
"""Retourne un aperçu de N serveurs sans écrire le fichier."""
|
||||
config = load_config()
|
||||
fields = config["serverFields"]
|
||||
|
|
@ -366,7 +366,7 @@ async def preview_servers(bot: discord.Client, limit: int = 3) -> list:
|
|||
return results
|
||||
|
||||
|
||||
async def preview_members(bot: discord.Client, limit: int = 5) -> dict:
|
||||
async def preview_members(bot: disnake.Client, limit: int = 5) -> dict:
|
||||
"""Retourne un aperçu de N membres sans écrire le fichier."""
|
||||
config = load_config()
|
||||
target_id = int(config["targetGuildId"]) if config["targetGuildId"] else None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue