2026-05-06 17:07:09 +02:00
import disnake
2026-03-29 19:29:17 +02:00
import re
2026-05-06 17:07:09 +02:00
from disnake . ext import commands
from disnake import ui
2026-03-14 15:40:19 +01:00
import json
import os
2026-03-28 21:58:44 +01:00
import asyncio
2026-03-14 15:40:19 +01:00
from typing import Optional
2026-03-28 21:58:44 +01:00
from src . logger import kuby_logger
2026-04-02 19:06:50 +02:00
import time
# Dictionnaire global pour suivre les renommages de salons
# Format: {channel_id: [timestamp1, timestamp2, ...]}
channel_rename_timestamps = { }
def check_channel_rename_rate_limit ( channel_id : int ) - > int :
""" Vérifie si le renommage va atteindre la limite de Discord (2 edits / 10 min). """
now = time . time ( )
if channel_id not in channel_rename_timestamps :
channel_rename_timestamps [ channel_id ] = [ ]
timestamps = [ ts for ts in channel_rename_timestamps [ channel_id ] if now - ts < 600 ]
channel_rename_timestamps [ channel_id ] = timestamps
if len ( timestamps ) > = 2 :
return max ( 0 , int ( 600 - ( now - timestamps [ 0 ] ) ) )
return 0
def log_channel_rename ( channel_id : int ) :
""" Enregistre un renommage de salon. """
now = time . time ( )
if channel_id not in channel_rename_timestamps :
channel_rename_timestamps [ channel_id ] = [ ]
timestamps = [ ts for ts in channel_rename_timestamps [ channel_id ] if now - ts < 600 ]
timestamps . append ( now )
channel_rename_timestamps [ channel_id ] = timestamps
2026-03-14 15:40:19 +01:00
TICKET_WHITELIST_SETTINGS_FILE = " data/ticket_whitelist_settings.json "
def load_settings ( guild_id : int ) - > dict :
""" Charge les paramètres de ticket pour un serveur """
if not os . path . exists ( TICKET_WHITELIST_SETTINGS_FILE ) :
return { }
try :
with open ( TICKET_WHITELIST_SETTINGS_FILE , " r " , encoding = ' utf-8 ' ) as f :
return json . load ( f ) . get ( str ( guild_id ) , { } )
except ( json . JSONDecodeError , FileNotFoundError ) :
return { }
def save_settings ( guild_id : int , settings : dict ) - > None :
""" Sauvegarde les paramètres de ticket """
data = { }
if os . path . exists ( TICKET_WHITELIST_SETTINGS_FILE ) :
try :
with open ( TICKET_WHITELIST_SETTINGS_FILE , " r " , encoding = ' utf-8 ' ) as f :
data = json . load ( f )
except ( json . JSONDecodeError , FileNotFoundError ) :
pass
data [ str ( guild_id ) ] = settings
os . makedirs ( os . path . dirname ( TICKET_WHITELIST_SETTINGS_FILE ) , exist_ok = True )
with open ( TICKET_WHITELIST_SETTINGS_FILE , " w " , encoding = ' utf-8 ' ) as f :
json . dump ( data , f , indent = 4 , ensure_ascii = False )
2026-05-06 17:07:09 +02:00
async def get_ticket_user_id ( channel : disnake . TextChannel ) - > Optional [ int ] :
2026-04-02 19:06:50 +02:00
""" 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 )
2026-05-06 17:07:09 +02:00
# Tentative d'utilisation du cache (plus rapide)
2026-04-02 19:06:50 +02:00
if topic :
match = re . search ( r " ( \ d { 17,20}) " , topic )
if match :
return int ( match . group ( 1 ) )
# Si le topic est vide ou ID introuvable, on tente de forcer un refresh via l'API
try :
# On utilise fetch_channel pour avoir un objet tout neuf avec le topic à jour
refreshed_channel = await channel . guild . fetch_channel ( channel . id )
topic = getattr ( refreshed_channel , " topic " , None )
if topic :
match = re . search ( r " ( \ d { 17,20}) " , topic )
if match :
return int ( match . group ( 1 ) )
except :
pass
return None
2026-05-06 17:07:09 +02:00
class TicketWhitelistConfigView ( disnake . ui . View ) :
2026-03-14 15:40:19 +01:00
def __init__ ( self , guild_id : int ) :
super ( ) . __init__ ( timeout = 60 )
self . guild_id = guild_id
2026-05-06 17:07:09 +02:00
async def prompt_for_id ( self , interaction : disnake . Interaction , prompt_text : str , is_category : bool = False ) :
2026-03-14 15:40:19 +01:00
await interaction . response . send_message ( prompt_text , 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 )
target = None
if is_category :
if msg . content . isdigit ( ) :
2026-05-06 17:07:09 +02:00
target = disnake . utils . get ( interaction . guild . categories , id = int ( msg . content ) )
2026-03-14 15:40:19 +01:00
if not target :
await interaction . followup . send ( " ❌ Catégorie introuvable avec cet ID. " , ephemeral = True )
return None
else :
await interaction . followup . send ( " ❌ Veuillez envoyer l ' ID (les chiffres) de la catégorie. " , ephemeral = True )
return None
else :
if msg . role_mentions :
target = msg . role_mentions [ 0 ]
elif msg . content . isdigit ( ) :
target = interaction . guild . get_role ( int ( msg . content ) )
if not target :
await interaction . followup . send ( " ❌ ID ou mention invalide. " , ephemeral = True )
return None
try : await msg . delete ( )
except : pass
return target
except __import__ ( " asyncio " ) . TimeoutError :
await interaction . followup . send ( " ❌ Temps écoulé. " , ephemeral = True )
return None
2026-05-06 17:07:09 +02:00
@ui.button ( label = " Catégorie Tickets " , style = disnake . ButtonStyle . primary , row = 0 , emoji = " 📁 " )
async def set_category ( self , interaction : disnake . Interaction , button : ui . Button ) :
2026-03-14 15:40:19 +01:00
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 )
settings [ " ticket_category_id " ] = category . id
save_settings ( self . guild_id , settings )
await interaction . followup . send ( f " ✅ Catégorie configurée sur ** { category . name } ** " , ephemeral = True )
2026-05-06 17:07:09 +02:00
@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 ) :
2026-03-14 15:40:19 +01:00
role = await self . prompt_for_id ( interaction , " Mentionne le **rôle Staff** ou envoie son ID : " )
if role :
settings = load_settings ( self . guild_id )
settings [ " staff_role_id " ] = role . id
save_settings ( self . guild_id , settings )
await interaction . followup . send ( f " ✅ Rôle Staff configuré sur ** { role . name } ** " , ephemeral = True )
2026-05-06 17:07:09 +02:00
@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 ) :
2026-03-14 15:40:19 +01:00
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 )
settings [ " team_staff_role_id " ] = role . id
save_settings ( self . guild_id , settings )
await interaction . followup . send ( f " ✅ Rôle Équipe Staff configuré sur ** { role . name } ** " , ephemeral = True )
2026-05-06 17:07:09 +02:00
@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 ) :
2026-03-14 15:40:19 +01:00
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 )
settings [ " attente_oral_role_id " ] = role . id
save_settings ( self . guild_id , settings )
await interaction . followup . send ( f " ✅ Rôle Attente Oral configuré sur ** { role . name } ** " , ephemeral = True )
2026-05-06 17:07:09 +02:00
@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 ) :
2026-03-14 15:40:19 +01:00
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 )
settings [ " candidature_a_faire_role_id " ] = role . id
save_settings ( self . guild_id , settings )
await interaction . followup . send ( f " ✅ Rôle Candidature à faire configuré sur ** { role . name } ** " , ephemeral = True )
2026-05-06 17:07:09 +02:00
@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 ) :
2026-03-14 15:40:19 +01:00
role = await self . prompt_for_id ( interaction , " Mentionne le rôle **Citoyen** ou envoie son ID : " )
if role :
settings = load_settings ( self . guild_id )
settings [ " citoyen_role_id " ] = role . id
save_settings ( self . guild_id , settings )
await interaction . followup . send ( f " ✅ Rôle Citoyen configuré sur ** { role . name } ** " , ephemeral = True )
2026-02-07 14:42:39 +01:00
2026-05-06 17:07:09 +02:00
class ContinueFormView ( disnake . ui . View ) :
2026-02-07 14:42:39 +01:00
""" 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 ) :
super ( ) . __init__ ( timeout = 180.0 ) # Définir un timeout pour la vue
self . current_step = current_step
self . step1_data = step1_data
self . step2_part1_data = step2_part1_data
self . step2_part2_data = step2_part2_data
2026-05-06 17:07:09 +02:00
@disnake.ui.button ( label = " 📋 Continuer le formulaire " , style = disnake . ButtonStyle . green , custom_id = " continue_form " )
2026-05-09 18:42:42 +02:00
async def continue_form ( self , button : disnake . ui . Button , interaction : disnake . Interaction ) :
2026-02-07 14:42:39 +01:00
""" Passe à l ' étape suivante du formulaire """
if self . current_step == 1 :
await interaction . response . send_modal ( TicketStep2Part1 ( self . step1_data ) )
elif self . current_step == 2 :
await interaction . response . send_modal ( TicketStep2Part2 ( self . step1_data , self . step2_part1_data ) )
elif self . current_step == 3 :
await interaction . response . send_modal ( TicketStep3 ( self . step1_data , self . step2_part1_data , self . step2_part2_data ) )
# Vérifier si le message existe toujours avant de le modifier
try :
await interaction . message . edit ( view = self )
2026-05-06 17:07:09 +02:00
except disnake . errors . NotFound :
2026-02-07 14:42:39 +01:00
# Si le message n'existe plus, nous ne faisons rien
pass
2026-05-06 17:07:09 +02:00
class TicketStep1 ( disnake . ui . Modal ) :
2026-02-07 14:42:39 +01:00
""" Étape 1 du formulaire - Infos personnelles """
2026-05-06 17:07:09 +02:00
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 ) ,
]
)
2026-02-07 14:42:39 +01:00
2026-05-06 17:07:09 +02:00
async def callback ( self , interaction : disnake . ModalInteraction ) :
2026-02-07 14:42:39 +01:00
""" Envoie un bouton pour passer à l ' étape 2 """
2026-05-06 17:07:09 +02:00
view = ContinueFormView ( current_step = 1 , step1_data = interaction . text_values )
2026-02-07 14:42:39 +01:00
await interaction . response . send_message ( " ✅ Première étape terminée ! Cliquez ci-dessous pour continuer. " , view = view , ephemeral = True )
2026-05-06 17:07:09 +02:00
class TicketStep2Part1 ( disnake . ui . Modal ) :
2026-02-07 14:42:39 +01:00
""" Étape 2 du formulaire - Infos RP (Partie 1) """
def __init__ ( self , step1_data ) :
self . step1_data = step1_data
2026-05-06 17:07:09 +02:00
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 ) ,
]
)
2026-02-07 14:42:39 +01:00
2026-05-06 17:07:09 +02:00
async def callback ( self , interaction : disnake . ModalInteraction ) :
2026-02-07 14:42:39 +01:00
""" Envoie un bouton pour passer à l ' étape suivante """
2026-05-06 17:07:09 +02:00
view = ContinueFormView ( current_step = 2 , step1_data = self . step1_data , step2_part1_data = interaction . text_values )
2026-02-07 14:42:39 +01:00
await interaction . response . send_message ( " ✅ Deuxième partie terminée ! Cliquez ci-dessous pour continuer. " , view = view , ephemeral = True )
2026-05-06 17:07:09 +02:00
class TicketStep2Part2 ( disnake . ui . Modal ) :
2026-02-07 14:42:39 +01:00
""" Étape 2 du formulaire - Infos RP (Partie 2) """
def __init__ ( self , step1_data , step2_part1_data ) :
self . step1_data = step1_data
self . step2_part1_data = step2_part1_data
2026-05-06 17:07:09 +02:00
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 ) ,
]
)
2026-02-07 14:42:39 +01:00
2026-05-06 17:07:09 +02:00
async def callback ( self , interaction : disnake . ModalInteraction ) :
2026-02-07 14:42:39 +01:00
""" Envoie un bouton pour passer à l ' étape 3 """
2026-05-06 17:07:09 +02:00
view = ContinueFormView ( current_step = 3 , step1_data = self . step1_data , step2_part1_data = self . step2_part1_data , step2_part2_data = interaction . text_values )
2026-02-07 14:42:39 +01:00
await interaction . response . send_message ( " ✅ Deuxième étape terminée ! Cliquez ci-dessous pour continuer. " , view = view , ephemeral = True )
2026-05-06 17:07:09 +02:00
class TicketStep3 ( disnake . ui . Modal ) :
2026-02-07 14:42:39 +01:00
""" Étape 3 du formulaire - Histoire du personnage """
def __init__ ( self , step1_data , step2_part1_data , step2_part2_data ) :
self . step1_data = step1_data
self . step2_part1_data = step2_part1_data
self . step2_part2_data = step2_part2_data
2026-05-06 17:07:09 +02:00
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 ) ,
]
)
2026-02-07 14:42:39 +01:00
2026-05-06 17:07:09 +02:00
async def callback ( self , interaction : disnake . ModalInteraction ) :
2026-02-07 14:42:39 +01:00
""" Création du ticket après validation """
guild = interaction . guild
user = interaction . user
2026-03-14 15:40:19 +01:00
settings = load_settings ( guild . id )
category_id = settings . get ( " ticket_category_id " )
staff_role_id = settings . get ( " staff_role_id " )
team_staff_role_id = settings . get ( " team_staff_role_id " )
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 )
2026-02-07 14:42:39 +01:00
2026-05-06 17:07:09 +02:00
category = disnake . utils . get ( guild . categories , id = int ( category_id ) )
2026-02-07 14:42:39 +01:00
if category is None :
2026-03-14 15:40:19 +01:00
return await interaction . response . send_message ( " ❌ Erreur : La catégorie des tickets configurée n ' existe plus ! " , ephemeral = True )
2026-02-07 14:42:39 +01:00
2026-05-06 17:07:09 +02:00
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 ) )
2026-03-14 15:40:19 +01:00
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 )
2026-02-07 14:42:39 +01:00
# Vérifier si l'utilisateur a déjà un ticket ouvert
for channel in category . text_channels :
if channel . topic == f " Ticket de { user . id } " :
2026-03-14 15:40:19 +01:00
return await interaction . response . send_message ( f " ❌ Vous avez déjà un ticket ouvert : { channel . mention } " , ephemeral = True )
2026-02-07 14:42:39 +01:00
# Définir les permissions du salon du ticket
overwrites = {
2026-05-06 17:07:09 +02:00
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 )
2026-02-07 14:42:39 +01:00
}
2026-03-14 15:40:19 +01:00
# Créer un salon de ticket avec le nom [pseudo]-écrit
2026-02-07 14:42:39 +01:00
ticket_channel = await category . create_text_channel (
name = f " { user . name } -écrit " ,
topic = f " Ticket de { user . id } " ,
overwrites = overwrites
)
# Création de l'embed avec les infos du formulaire
2026-05-06 17:07:09 +02:00
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
2026-02-07 14:42:39 +01:00
embed . add_field ( name = " 📋 - Informations Personnelles " , value = (
2026-05-06 17:07:09 +02:00
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 ' ] } "
2026-02-07 14:42:39 +01:00
) , inline = False )
embed . add_field ( name = " 👤 - Informations RP " , value = (
2026-05-06 17:07:09 +02:00
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 ' ] } "
2026-02-07 14:42:39 +01:00
) , inline = False )
embed . add_field ( name = " 📋 - Développement du Personnage " , value = (
2026-05-06 17:07:09 +02:00
f " > - Histoire : { s3 [ ' histoire ' ] } \n "
f " > - Objectifs : { s3 [ ' objectifs ' ] } "
2026-02-07 14:42:39 +01:00
) , inline = False )
# Envoyer l'embed avec les boutons d'actions pour les staff
2026-03-14 15:40:19 +01:00
await ticket_channel . send ( embed = embed , view = TicketManagementView ( ) )
2026-02-07 14:42:39 +01:00
# Envoyer un message pour mentionner l'équipe staff
await ticket_channel . send ( f " { team_staff_role . mention } - Nouveau ticket créé ! " )
# Mettre à jour le pseudo du joueur si le bot a les permissions nécessaires
if guild . me . guild_permissions . manage_nicknames :
2026-05-06 17:07:09 +02:00
new_nickname = f " { s2p1 [ ' prenom ' ] } { s2p1 [ ' nom ' ] } | { s1 [ ' pseudo ' ] } "
2026-02-07 14:42:39 +01:00
if len ( new_nickname ) > 32 :
new_nickname = new_nickname [ : 32 ]
2026-03-14 15:40:19 +01:00
try : await user . edit ( nick = new_nickname )
2026-05-06 17:07:09 +02:00
except disnake . Forbidden : pass
2026-03-14 15:40:19 +01:00
2026-02-07 14:42:39 +01:00
# Envoyer un message de confirmation
try :
await interaction . response . send_message ( f " ✅ Ticket ouvert : { ticket_channel . mention } " , ephemeral = True )
2026-05-06 17:07:09 +02:00
except disnake . errors . NotFound :
2026-02-07 14:42:39 +01:00
await interaction . followup . send ( f " ✅ Ticket ouvert : { ticket_channel . mention } " , ephemeral = True )
2026-05-06 17:07:09 +02:00
class TicketManagementView ( disnake . ui . View ) :
2026-02-07 14:42:39 +01:00
""" Vue pour gérer le ticket avec les boutons Fermer, Réouvrir, Claim et Supprimer """
2026-03-14 15:40:19 +01:00
def __init__ ( self ) :
2026-02-07 14:42:39 +01:00
super ( ) . __init__ ( timeout = None )
2026-03-28 21:58:44 +01:00
2026-05-06 17:07:09 +02:00
async def get_ticket_owner ( self , interaction : disnake . Interaction ) :
2026-04-02 19:06:50 +02:00
""" Récupère le propriétaire du ticket via le helper robuste """
owner_id = await get_ticket_user_id ( interaction . channel )
if owner_id :
2026-03-28 21:58:44 +01:00
try :
member = interaction . guild . get_member ( owner_id )
if not member :
2026-03-29 19:29:17 +02:00
member = await interaction . guild . fetch_member ( owner_id )
2026-03-28 21:58:44 +01:00
return member
2026-03-29 19:29:17 +02:00
except :
2026-03-28 21:58:44 +01:00
return None
return None
2026-02-07 14:42:39 +01:00
2026-05-06 17:07:09 +02:00
@disnake.ui.button ( label = " 🔒 Fermer le Ticket " , style = disnake . ButtonStyle . red , custom_id = " close_ticket " )
2026-05-09 18:42:42 +02:00
async def close_ticket ( self , button : disnake . ui . Button , interaction : disnake . Interaction ) :
2026-02-07 14:42:39 +01:00
""" Ferme le ticket en désactivant l ' envoi de messages pour le membre """
2026-03-28 22:17:17 +01:00
await interaction . response . defer ( ephemeral = True )
2026-03-14 15:40:19 +01:00
settings = load_settings ( interaction . guild . id )
staff_role_id = settings . get ( " staff_role_id " )
2026-03-28 22:17:17 +01:00
is_staff = any ( r . id == int ( staff_role_id ) for r in interaction . user . roles ) if staff_role_id else False
if not is_staff and not interaction . user . guild_permissions . administrator :
return await interaction . followup . send ( " ❌ Seuls les staffs peuvent fermer un ticket. " , ephemeral = True )
2026-03-14 15:40:19 +01:00
2026-03-28 21:58:44 +01:00
owner = await self . get_ticket_owner ( interaction )
if not owner :
2026-03-28 22:17:17 +01:00
return await interaction . followup . send ( " ❌ Impossible de trouver le propriétaire du ticket à fermer. " , ephemeral = True )
2026-02-07 14:42:39 +01:00
2026-03-28 22:17:17 +01:00
try :
await interaction . channel . set_permissions ( owner , read_messages = False , send_messages = False )
2026-04-02 19:06:50 +02:00
log_channel_rename ( interaction . channel . id )
asyncio . create_task ( interaction . channel . edit ( name = f " fermé- { owner . display_name } " . replace ( " " , " - " ) . lower ( ) [ : 100 ] ) )
2026-03-28 22:17:17 +01:00
# Envoi du message de fermeture dans le salon
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 )
2026-05-06 17:07:09 +02:00
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 )
2026-03-28 22:17:17 +01:00
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 )
2026-05-06 17:07:09 +02:00
@disnake.ui.button ( label = " 👤 Claim le Ticket " , style = disnake . ButtonStyle . blurple , custom_id = " claim_ticket " )
2026-05-09 18:42:42 +02:00
async def claim_ticket ( self , button : disnake . ui . Button , interaction : disnake . Interaction ) :
2026-02-07 14:42:39 +01:00
""" Un staff prend en charge le ticket """
2026-03-28 22:17:17 +01:00
await interaction . response . defer ( ephemeral = False )
2026-03-14 15:40:19 +01:00
settings = load_settings ( interaction . guild . id )
staff_role_id = settings . get ( " staff_role_id " )
2026-03-28 22:17:17 +01:00
is_staff = any ( r . id == int ( staff_role_id ) for r in interaction . user . roles ) if staff_role_id else False
if not is_staff and not interaction . user . guild_permissions . administrator :
return await interaction . followup . send ( " ❌ Seuls les staffs peuvent claim un ticket. " )
2026-02-07 14:42:39 +01:00
2026-03-28 22:17:17 +01:00
try :
staff_role = interaction . guild . get_role ( int ( staff_role_id ) ) if staff_role_id else None
# Retrait des permissions du rôle staff pour forcer le claimer à parler seul
if staff_role :
await interaction . channel . set_permissions ( staff_role , send_messages = False , read_messages = True )
# Donner les permissions au claimer
await interaction . channel . set_permissions ( interaction . user , read_messages = True , send_messages = True )
await interaction . followup . send ( f " 👥 { interaction . user . mention } a pris en charge ce ticket. " )
except Exception as e :
kuby_logger . error ( f " Erreur lors du claim du ticket: { e } " )
await interaction . followup . send ( f " ❌ Erreur lors du claim : { e } " )
2026-05-06 17:07:09 +02:00
class ClosedTicketView ( disnake . ui . View ) :
2026-02-07 14:42:39 +01:00
""" Vue pour les tickets fermés avec les boutons Réouvrir et Supprimer """
2026-03-14 15:40:19 +01:00
def __init__ ( self ) :
2026-02-07 14:42:39 +01:00
super ( ) . __init__ ( timeout = None )
2026-05-06 17:07:09 +02:00
async def get_ticket_owner ( self , interaction : disnake . Interaction ) :
2026-04-02 19:06:50 +02:00
""" Récupère le propriétaire du ticket via le helper robuste """
owner_id = await get_ticket_user_id ( interaction . channel )
if owner_id :
2026-03-28 21:58:44 +01:00
try :
member = interaction . guild . get_member ( owner_id )
if not member :
2026-03-29 19:29:17 +02:00
member = await interaction . guild . fetch_member ( owner_id )
2026-03-28 21:58:44 +01:00
return member
2026-03-29 19:29:17 +02:00
except :
2026-03-28 21:58:44 +01:00
return None
return None
2026-05-06 17:07:09 +02:00
@disnake.ui.button ( label = " ♻ Réouvrir le Ticket " , style = disnake . ButtonStyle . green , custom_id = " reopen_ticket " )
2026-05-09 18:42:42 +02:00
async def reopen_ticket ( self , button : disnake . ui . Button , interaction : disnake . Interaction ) :
2026-02-07 14:42:39 +01:00
""" Réouvre le ticket pour le membre """
2026-03-28 22:17:17 +01:00
await interaction . response . defer ( ephemeral = True )
2026-03-14 15:40:19 +01:00
settings = load_settings ( interaction . guild . id )
staff_role_id = settings . get ( " staff_role_id " )
2026-03-28 22:17:17 +01:00
is_staff = any ( r . id == int ( staff_role_id ) for r in interaction . user . roles ) if staff_role_id else False
if not is_staff and not interaction . user . guild_permissions . administrator :
return await interaction . followup . send ( " ❌ Seuls les staffs peuvent réouvrir un ticket. " , ephemeral = True )
2026-03-14 15:40:19 +01:00
2026-03-28 21:58:44 +01:00
owner = await self . get_ticket_owner ( interaction )
if not owner :
2026-03-28 22:17:17 +01:00
return await interaction . followup . send ( " ❌ Impossible de trouver le propriétaire du ticket à réouvrir. " , ephemeral = True )
2026-03-14 15:40:19 +01:00
2026-03-28 22:17:17 +01:00
try :
2026-03-28 22:23:23 +01:00
# On demande au staff quel statut remettre
2026-05-06 17:07:09 +02:00
embed = disnake . Embed (
2026-03-28 22:23:23 +01:00
title = " ♻️ Réouverture du Ticket " ,
description = f " Choisissez le statut à appliquer pour ** { owner . display_name } **. " ,
2026-05-06 17:07:09 +02:00
color = disnake . Color . blue ( )
2026-03-28 22:23:23 +01:00
)
view = ReopenStatusView ( owner )
await interaction . channel . send ( embed = embed , view = view )
await interaction . followup . send ( " ✅ Choisissez le statut ci-dessous. " , ephemeral = True )
except Exception as e :
kuby_logger . error ( f " Erreur lors de la préparation de réouverture: { e } " )
await interaction . followup . send ( f " ❌ Erreur : { e } " , ephemeral = True )
2026-05-06 17:07:09 +02:00
class ReopenStatusView ( disnake . ui . View ) :
2026-03-28 22:23:23 +01:00
""" Vue pour choisir le statut lors d ' une réouverture """
2026-05-06 17:07:09 +02:00
def __init__ ( self , owner : disnake . Member ) :
2026-03-28 22:23:23 +01:00
super ( ) . __init__ ( timeout = 180 )
self . owner = owner
2026-05-06 17:07:09 +02:00
@disnake.ui.button ( label = " Écrit (Ouvert) " , style = disnake . ButtonStyle . secondary )
2026-05-09 18:42:42 +02:00
async def set_written ( self , button : disnake . ui . Button , interaction : disnake . Interaction ) :
2026-03-28 22:23:23 +01:00
await self . apply_status ( interaction , " ouvert " )
2026-05-06 17:07:09 +02:00
@disnake.ui.button ( label = " Oral (À faire) " , style = disnake . ButtonStyle . primary )
2026-05-09 18:42:42 +02:00
async def set_oral ( self , button : disnake . ui . Button , interaction : disnake . Interaction ) :
2026-03-28 22:23:23 +01:00
await self . apply_status ( interaction , " oral-à-faire " )
2026-05-06 17:07:09 +02:00
@disnake.ui.button ( label = " Accepté " , style = disnake . ButtonStyle . success )
2026-05-09 18:42:42 +02:00
async def set_accepted ( self , button : disnake . ui . Button , interaction : disnake . Interaction ) :
2026-03-28 22:23:23 +01:00
await self . apply_status ( interaction , " accepté " )
2026-05-06 17:07:09 +02:00
async def apply_status ( self , interaction : disnake . Interaction , prefix : str ) :
2026-03-28 22:23:23 +01:00
await interaction . response . defer ( ephemeral = True )
try :
2026-03-29 19:29:17 +02:00
settings = load_settings ( interaction . guild . id )
candidature_a_faire_role_id = settings . get ( " candidature_a_faire_role_id " )
attente_oral_role_id = settings . get ( " attente_oral_role_id " )
citoyen_role_id = settings . get ( " citoyen_role_id " )
# On récupère les objets rôles
role_écrit = interaction . guild . get_role ( int ( candidature_a_faire_role_id ) ) if candidature_a_faire_role_id else None
role_oral = interaction . guild . get_role ( int ( attente_oral_role_id ) ) if attente_oral_role_id else None
role_citoyen = interaction . guild . get_role ( int ( citoyen_role_id ) ) if citoyen_role_id else None
# Synchronisation des RÔLES en fonction du statut choisi
roles_to_remove = [ ]
role_to_add = None
if prefix == " ouvert " :
role_to_add = role_écrit
roles_to_remove = [ r for r in [ role_oral , role_citoyen ] if r ]
elif prefix == " oral-à-faire " :
role_to_add = role_oral
roles_to_remove = [ r for r in [ role_écrit , role_citoyen ] if r ]
elif prefix == " accepté " :
role_to_add = role_citoyen
roles_to_remove = [ r for r in [ role_écrit , role_oral ] if r ]
# Mise à jour des rôles sur le membre
if roles_to_remove :
await self . owner . remove_roles ( * roles_to_remove , reason = " Réouverture du ticket - Maj Statut " )
if role_to_add :
await self . owner . add_roles ( role_to_add , reason = " Réouverture du ticket - Maj Statut " )
# Mise à jour du SALON
2026-03-28 22:23:23 +01:00
await interaction . channel . set_permissions ( self . owner , read_messages = True , send_messages = True )
new_name = f " { prefix } - { self . owner . display_name } " . replace ( " " , " - " ) . lower ( ) [ : 100 ]
2026-04-02 19:06:50 +02:00
log_channel_rename ( interaction . channel . id )
asyncio . create_task ( interaction . channel . edit ( name = new_name , topic = f " Ticket de { self . owner . id } " ) )
2026-03-28 22:17:17 +01:00
new_view = TicketManagementView ( )
2026-03-29 19:29:17 +02:00
await interaction . channel . send ( f " ✅ Ticket réouvert par { interaction . user . mention } (Statut: ** { prefix } **). " , view = new_view )
await interaction . followup . send ( f " ✅ Statut ** { prefix } ** appliqué et rôles synchronisés. " , ephemeral = True )
2026-03-28 22:23:23 +01:00
self . stop ( )
2026-03-28 22:17:17 +01:00
except Exception as e :
2026-03-29 19:29:17 +02:00
kuby_logger . error ( f " Erreur lors de la réouverture/synchro: { e } " )
await interaction . followup . send ( f " ❌ Erreur système : { e } " , ephemeral = True )
2026-02-07 14:42:39 +01:00
2026-05-06 17:07:09 +02:00
@disnake.ui.button ( label = " ❌ Supprimer le Ticket " , style = disnake . ButtonStyle . danger , custom_id = " delete_ticket " )
2026-05-09 18:42:42 +02:00
async def delete_ticket ( self , button : disnake . ui . Button , interaction : disnake . Interaction ) :
2026-02-07 14:42:39 +01:00
""" Supprime le salon du ticket """
2026-03-28 22:17:17 +01:00
await interaction . response . defer ( ephemeral = True )
2026-03-14 15:40:19 +01:00
settings = load_settings ( interaction . guild . id )
staff_role_id = settings . get ( " staff_role_id " )
2026-03-28 22:17:17 +01:00
is_staff = any ( r . id == int ( staff_role_id ) for r in interaction . user . roles ) if staff_role_id else False
if not is_staff and not interaction . user . guild_permissions . administrator :
return await interaction . followup . send ( " ❌ Seuls les staffs peuvent supprimer un ticket. " , ephemeral = True )
2026-03-14 15:40:19 +01:00
2026-03-28 22:17:17 +01:00
try :
await interaction . followup . send ( " ❌ Suppression du ticket... " , ephemeral = True )
await interaction . channel . delete ( )
except Exception as e :
kuby_logger . error ( f " Erreur lors de la suppression du ticket: { e } " )
try : await interaction . followup . send ( f " ❌ Erreur lors de la suppression : { e } " , ephemeral = True )
except : pass
2026-02-07 14:42:39 +01:00
2026-05-06 17:07:09 +02:00
class TicketView ( disnake . ui . View ) :
2026-02-07 14:42:39 +01:00
""" Vue avec le bouton pour ouvrir le ticket """
def __init__ ( self ) :
super ( ) . __init__ ( timeout = None )
2026-05-06 17:07:09 +02:00
@disnake.ui.button ( label = " 📑 Ouvrir un Ticket Whitelist " , style = disnake . ButtonStyle . red , custom_id = " open_ticket " )
2026-05-09 18:42:42 +02:00
async def open_ticket ( self , button : disnake . ui . Button , interaction : disnake . Interaction ) :
2026-02-07 14:42:39 +01:00
""" Affiche le premier formulaire """
await interaction . response . send_modal ( TicketStep1 ( ) )
class Ticket ( commands . Cog ) :
""" Commande pour envoyer l ' embed de ticket """
def __init__ ( self , bot ) :
self . bot = bot
2026-03-28 21:58:44 +01:00
async def cog_load ( self ) :
""" Enregistre les vues persistantes """
self . bot . add_view ( TicketView ( ) )
self . bot . add_view ( TicketManagementView ( ) )
self . bot . add_view ( ClosedTicketView ( ) )
2026-05-06 17:07:09 +02:00
@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 ) :
2026-02-07 14:42:39 +01:00
""" Envoie l ' embed avec le bouton d ' ouverture de ticket """
2026-05-06 17:07:09 +02:00
embed = disnake . Embed (
2026-02-07 14:42:39 +01:00
title = " 📩 Ticket Whitelist " ,
description = " Pour réaliser votre whitelist, ouvrez un ticket et remplissez le formulaire. " ,
color = 0x00ff00
)
view = TicketView ( )
await interaction . channel . send ( embed = embed , view = view )
await interaction . response . send_message ( " ✅ Embed des tickets envoyé. " , ephemeral = True )
2026-05-06 17:07:09 +02:00
@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 ) :
2026-03-14 15:40:19 +01:00
""" Interface de configuration des tickets """
settings = load_settings ( interaction . guild . id )
2026-05-06 17:07:09 +02:00
embed = disnake . Embed (
2026-03-14 15:40:19 +01:00
title = " ⚙️ Configuration Tickets Whitelist " ,
description = " Utilisez les boutons ci-dessous pour lier la catégorie et les rôles. " ,
2026-05-06 17:07:09 +02:00
color = disnake . Color . blue ( )
2026-03-14 15:40:19 +01:00
)
cat_id = settings . get ( " ticket_category_id " )
s_role_id = settings . get ( " staff_role_id " )
ts_role_id = settings . get ( " team_staff_role_id " )
ao_role_id = settings . get ( " attente_oral_role_id " )
caf_role_id = settings . get ( " candidature_a_faire_role_id " )
c_role_id = settings . get ( " citoyen_role_id " )
embed . add_field ( name = " 📁 Catégorie Tickets " , value = f " Id: { cat_id } " if cat_id else " ❌ Non configuré " , inline = True )
embed . add_field ( name = " 🛡️ Staff " , value = f " <@& { s_role_id } > " if s_role_id else " ❌ Non configuré " , inline = True )
embed . add_field ( name = " 👥 Équipe Staff " , value = f " <@& { ts_role_id } > " if ts_role_id else " ❌ Non configuré " , inline = True )
embed . add_field ( name = " 🎤 Attente Oral " , value = f " <@& { ao_role_id } > " if ao_role_id else " ❌ Non configuré " , inline = True )
embed . add_field ( name = " 📝 Candidature à faire " , value = f " <@& { caf_role_id } > " if caf_role_id else " ❌ Non configuré " , inline = True )
embed . add_field ( name = " 🏙️ Citoyen " , value = f " <@& { c_role_id } > " if c_role_id else " ❌ Non configuré " , inline = True )
await interaction . response . send_message ( embed = embed , view = TicketWhitelistConfigView ( interaction . guild . id ) , ephemeral = True )
2026-02-07 14:42:39 +01:00
@commands.Cog.listener ( )
async def on_message ( self , message ) :
2026-03-28 22:12:36 +01:00
""" Surveille les messages des staffs dans les tickets pour validation automatique """
2026-02-07 14:42:39 +01:00
if message . guild is None or message . author . bot :
return
2026-03-28 22:12:36 +01:00
# On ne traite que si le message contient "validé"
if " validé " not in message . content . lower ( ) :
return
2026-04-02 19:06:50 +02:00
# Détection du format du topic du ticket via helper (robuste)
user_id = await get_ticket_user_id ( message . channel )
if not user_id :
await message . channel . send ( " ❌ Debug: Impossible de récupérer l ' ID utilisateur via le topic (même après refresh API). " )
return
2026-03-14 15:40:19 +01:00
settings = load_settings ( message . guild . id )
staff_role_id = settings . get ( " staff_role_id " )
2026-03-28 22:12:36 +01:00
# Vérification si l'auteur est staff ou admin
is_staff = False
if staff_role_id :
is_staff = any ( r . id == int ( staff_role_id ) for r in message . author . roles )
if not is_staff and not message . author . guild_permissions . administrator :
2026-03-31 22:09:00 +02:00
if " validé " in message . content . lower ( ) :
await message . channel . send ( " ❌ Debug: Permission refusée. Vous n ' êtes ni Staff ni Admin. " )
2026-03-14 15:40:19 +01:00
return
2026-03-28 22:12:36 +01:00
try :
2026-04-02 19:06:50 +02:00
# L'ID est déjà trouvé par le helper ci-dessus
user_id = user_id
2026-02-07 14:42:39 +01:00
try :
2026-03-29 19:29:17 +02:00
user = await message . guild . fetch_member ( user_id )
2026-05-06 17:07:09 +02:00
except ( disnake . NotFound , disnake . HTTPException ) :
2026-03-29 19:29:17 +02:00
await message . channel . send ( " ❌ Utilisateur introuvable. A-t-il quitté le serveur ? " , delete_after = 10 )
2026-03-28 22:12:36 +01:00
return
attente_oral_role_id = settings . get ( " attente_oral_role_id " )
candidature_a_faire_role_id = settings . get ( " candidature_a_faire_role_id " )
citoyen_role_id = settings . get ( " citoyen_role_id " )
if not attente_oral_role_id or not candidature_a_faire_role_id or not citoyen_role_id :
await message . channel . send ( " ❌ Erreur de configuration : Impossible d ' attribuer les rôles, ils ne sont pas tous définis dans `/ticket_whitelist_config`. " , delete_after = 10 )
return
attente_oral_role = message . guild . get_role ( int ( attente_oral_role_id ) )
candidature_a_faire_role = message . guild . get_role ( int ( candidature_a_faire_role_id ) )
citoyen_role = message . guild . get_role ( int ( citoyen_role_id ) )
2026-03-29 19:29:17 +02:00
if ( attente_oral_role and attente_oral_role in user . roles ) or " oral-à-faire " in message . channel . name . lower ( ) :
2026-03-28 22:12:36 +01:00
# DEUXIÈME VALIDATION -> ACCEPTÉ / CITOYEN
if attente_oral_role : await user . remove_roles ( attente_oral_role )
if citoyen_role : await user . add_roles ( citoyen_role )
# Mise à jour du nom du salon
new_name = f " accepté- { user . display_name } " . replace ( " " , " - " ) . lower ( ) [ : 100 ]
2026-04-02 19:06:50 +02:00
wait_time = check_channel_rename_rate_limit ( message . channel . id )
log_channel_rename ( message . channel . id )
asyncio . create_task ( message . channel . edit ( name = new_name ) )
2026-03-28 22:12:36 +01:00
# Confirmation dans le salon (Feedback visuel)
2026-05-06 17:07:09 +02:00
embed_val = disnake . Embed (
2026-03-28 22:12:36 +01:00
title = " 🎉 Whitelist Validée ! " ,
description = f " Félicitations { user . mention } , ton entretien oral a été validé par { message . author . mention } ! \n Tu es désormais **Citoyen**. " ,
2026-05-06 17:07:09 +02:00
color = disnake . Color . green ( )
2026-03-28 22:12:36 +01:00
)
await message . channel . send ( embed = embed_val )
2026-03-28 22:23:23 +01:00
# DM au joueur (RESTAURATION DESIGN CAPTURE)
2026-03-28 22:12:36 +01:00
try :
2026-05-06 17:07:09 +02:00
embed_dm = disnake . Embed (
2026-03-28 22:12:36 +01:00
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 ! " ,
2026-05-06 17:07:09 +02:00
color = disnake . Color . green ( )
2026-03-28 22:12:36 +01:00
)
embed_dm . add_field ( name = " 🏙️ Nouveau Statut " , value = " Citoyen " , inline = True )
embed_dm . add_field ( name = " 📍 Serveur " , value = message . guild . name , inline = True )
2026-03-28 22:23:23 +01:00
embed_dm . add_field ( name = " 📋 Prochaine étape " , value = " Tu peux désormais te connecter au serveur et commencer ton aventure RP. N ' oublie pas de consulter les salons d ' informations ! " , inline = False )
2026-04-02 19:06:50 +02:00
if wait_time > 0 :
m , s = divmod ( wait_time , 60 )
embed_dm . add_field ( name = " ⏳ Nom du ticket " , value = f " Le nom de ton ticket sera actualisé d ' ici { m } m et { s } s environ (limite de sécurité Discord). " , inline = False )
2026-03-28 22:23:23 +01:00
embed_dm . add_field ( name = " 🔗 Lien vers ton ticket " , value = f " [Clique ici pour voir ton ticket]( { message . channel . jump_url } ) " , inline = False )
2026-03-28 22:12:36 +01:00
embed_dm . set_footer ( text = f " L ' équipe { message . guild . name } " )
2026-03-28 22:23:23 +01:00
if message . guild . icon : embed_dm . set_thumbnail ( url = message . guild . icon . url )
2026-03-28 22:12:36 +01:00
await user . send ( embed = embed_dm )
except : pass
2026-03-15 16:53:38 +01:00
2026-03-28 22:12:36 +01:00
else :
# PREMIÈRE VALIDATION -> ORAL À FAIRE
if candidature_a_faire_role : await user . remove_roles ( candidature_a_faire_role )
if attente_oral_role : await user . add_roles ( attente_oral_role )
# Mise à jour du nom du salon
new_name = f " oral-à-faire- { user . display_name } " . replace ( " " , " - " ) . lower ( ) [ : 100 ]
2026-04-02 19:06:50 +02:00
wait_time = check_channel_rename_rate_limit ( message . channel . id )
log_channel_rename ( message . channel . id )
asyncio . create_task ( message . channel . edit ( name = new_name ) )
2026-03-28 22:12:36 +01:00
# Confirmation dans le salon
2026-05-06 17:07:09 +02:00
embed_val = disnake . Embed (
2026-03-28 22:12:36 +01:00
title = " 📝 Étape Écrite Validée ! " ,
description = f " Ta candidature écrite a été validée par { message . author . mention } . \n Tu passes désormais en **Attente Oral**. " ,
2026-05-06 17:07:09 +02:00
color = disnake . Color . orange ( )
2026-03-28 22:12:36 +01:00
)
await message . channel . send ( embed = embed_val )
2026-03-28 22:23:23 +01:00
# DM au joueur (RESTAURATION DESIGN CAPTURE)
2026-03-28 22:12:36 +01:00
try :
2026-05-06 17:07:09 +02:00
embed_dm = disnake . Embed (
2026-03-28 22:12:36 +01:00
title = " 📝 Validation Étape Écrite " ,
2026-03-28 22:23:23 +01:00
description = f " Ta candidature écrite sur ** { message . guild . name } ** a été examinée et validée par notre équipe ! " ,
2026-05-06 17:07:09 +02:00
color = disnake . Color . orange ( )
2026-03-28 22:12:36 +01:00
)
2026-03-28 22:23:23 +01:00
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 )
2026-04-02 19:06:50 +02:00
if wait_time > 0 :
m , s = divmod ( wait_time , 60 )
embed_dm . add_field ( name = " ⏳ Nom du ticket " , value = f " Le nom de ton ticket sera actualisé d ' ici { m } m et { s } s environ (limite de sécurité Discord). " , inline = False )
2026-03-28 22:23:23 +01:00
embed_dm . add_field ( name = " 🔗 Lien vers ton ticket " , value = f " [Clique ici pour voir ton ticket]( { message . channel . jump_url } ) " , inline = False )
embed_dm . set_footer ( text = f " L ' équipe { message . guild . name } " )
if message . guild . icon : embed_dm . set_thumbnail ( url = message . guild . icon . url )
2026-03-28 22:12:36 +01:00
await user . send ( embed = embed_dm )
except : pass
except Exception as e :
kuby_logger . error ( f " Erreur lors de la validation du ticket: { e } " , exc_info = True )
await message . channel . send ( f " ❌ Une erreur système est survenue : { e } " )
2026-02-07 14:42:39 +01:00
2026-05-09 18:42:42 +02:00
def setup ( bot ) :
bot . add_cog ( Ticket ( bot ) )
2026-03-14 15:40:19 +01:00
bot . add_view ( TicketView ( ) )
bot . add_view ( TicketManagementView ( ) )
bot . add_view ( ClosedTicketView ( ) )