Maintenance de routine

This commit is contained in:
Mathis 2026-02-07 14:42:39 +01:00
parent c330b9a1cc
commit 99ab16098f
62 changed files with 52172 additions and 144 deletions

View file

@ -100,9 +100,9 @@ class TicketPanelView(discord.ui.View):
# Reload config to ensure we have latest permissions/categories
self.config = self.storage.load_config(interaction.guild_id)
if not self._is_whitelisted(interaction):
await interaction.response.send_message("❌ Vous n'êtes pas autorisé à utiliser cette fonctionnalité. Veuillez demander à être ajouté à la whitelist.", ephemeral=True)
return
# Reload config to ensure we have latest permissions/categories
self.config = self.storage.load_config(interaction.guild_id)
if not self.config.enabled:
await interaction.response.send_message("Le système de tickets est désactivé.", ephemeral=True)
@ -135,9 +135,9 @@ class TicketPanelView(discord.ui.View):
# Reload config
self.config = self.storage.load_config(interaction.guild_id)
if not self._is_whitelisted(interaction):
await interaction.response.send_message("❌ Vous n'êtes pas autorisé à utiliser cette fonctionnalité. Veuillez demander à être ajouté à la whitelist.", ephemeral=True)
return
# Reload config
self.config = self.storage.load_config(interaction.guild_id)
storage = get_storage()
tickets = storage.get_user_tickets(interaction.guild_id, interaction.user.id, open_only=True)
@ -356,9 +356,7 @@ class CategorySelectionView(discord.ui.View):
async def _on_category_select(self, interaction: discord.Interaction, category_id: str):
"""Handle category selection"""
try:
if not self._is_whitelisted(interaction):
await interaction.response.send_message("❌ Vous n'êtes pas autorisé à utiliser cette fonctionnalité. Veuillez demander à être ajouté à la whitelist.", ephemeral=True)
return
category = self.config.get_category(category_id)
if not category:
@ -536,6 +534,43 @@ class TicketPriorityModal(discord.ui.Modal):
await interaction.response.send_message(f"Ticket créé: {ticket_channel.mention}", ephemeral=True)
class SetCategoryModal(discord.ui.Modal):
"""Modal pour définir la catégorie Discord où les tickets seront créés"""
def __init__(self, bot, storage, config: GuildTicketConfig):
super().__init__(title="Définir la Catégorie Discord")
self.bot = bot
self.storage = storage
self.config = config
self.category = discord.ui.TextInput(
label="Catégorie Discord",
placeholder="Entrez l'ID de la catégorie (clic droit -> Copier l'ID)",
required=True,
max_length=20
)
self.add_item(self.category)
async def on_submit(self, interaction: discord.Interaction):
cat_id = self.category.value.strip()
# Verify it's a valid ID
if not cat_id.isdigit():
await interaction.response.send_message("❌ Veuillez entrer un ID de catégorie valide (que des chiffres).", ephemeral=True)
return
# Check if category exists
category = interaction.guild.get_channel(int(cat_id))
if not category or not isinstance(category, discord.CategoryChannel):
await interaction.response.send_message("❌ Catégorie introuvable ou ce n'est pas une catégorie.", ephemeral=True)
return
self.config.default_category = cat_id
self.storage.save_config(interaction.guild_id, self.config)
await interaction.response.send_message(f"📁 Les tickets seront désormais créés dans la catégorie **{category.name}**!", ephemeral=True)
class TicketManagementView(discord.ui.View):
"""Gestion du ticket (dans le salon du ticket)"""
@ -883,6 +918,27 @@ class CloseDelayView(discord.ui.View):
duration = self.ticket.duration_minutes
# Send Feedback Request to User via DM
try:
user = interaction.guild.get_member(self.ticket.user_id)
if user:
from .feedback_view import FeedbackView
dm_embed = discord.Embed(
title="📝 Votre avis nous intéresse !",
description=f"Votre ticket **#{self.ticket.channel_id}** a été fermé.\nMerci de prendre un moment pour noter la qualité du support.",
color=discord.Color.green(),
timestamp=datetime.now()
)
dm_embed.add_field(name="Durée du ticket", value=f"{duration:.1f} minutes", inline=True)
if self.close_reason:
dm_embed.add_field(name="Raison de fermeture", value=self.close_reason, inline=False)
view = FeedbackView(self.bot, self.storage, self.ticket)
await user.send(embed=dm_embed, view=view)
except Exception as e:
print(f"Failed to send feedback DM: {e}")
# Send transcript to log channel
if self.config.log_channel_id and transcript_file and os.path.exists(transcript_file):
try:
@ -1642,14 +1698,23 @@ class ConfigPanelView(discord.ui.View):
toggle_btn.callback = self._toggle_callback
self.add_item(toggle_btn)
# Default category
self.category_btn = discord.ui.Button(
label="📁 Catégorie par défaut",
style=discord.ButtonStyle.secondary,
custom_id="config_category"
)
self.category_btn.callback = self._category_callback
self.add_item(self.category_btn)
# Ajouter catégorie
add_cat_btn = discord.ui.Button(
self.add_cat_btn = discord.ui.Button(
style=discord.ButtonStyle.blurple,
label="Ajouter Catégorie",
custom_id="config_add_cat"
)
add_cat_btn.callback = self._add_cat_callback
self.add_item(add_cat_btn)
self.add_cat_btn.callback = self._add_cat_callback
self.add_item(self.add_cat_btn)
# Staff permissions
staff_btn = discord.ui.Button(
@ -1737,6 +1802,10 @@ class ConfigPanelView(discord.ui.View):
embed.add_field(name="Rôles", value=", ".join(role_mentions) if role_mentions else "Aucun", inline=False)
embed.add_field(name="Utilisateurs", value=", ".join(user_mentions) if user_mentions else "Aucun", inline=False)
async def _category_callback(self, interaction: discord.Interaction):
modal = SetCategoryModal(self.bot, self.storage, self.config)
await interaction.response.send_modal(modal)
async def _log_channel_callback(self, interaction: discord.Interaction):
modal = SetChannelModal(self.bot, self.storage, self.config, "log")
await interaction.response.send_modal(modal)
@ -1944,6 +2013,7 @@ class PermissionConfigView(discord.ui.View):
# Add components
self._add_role_select()
self._add_user_select()
self._add_remove_select()
def _add_role_select(self):
select = discord.ui.RoleSelect(
@ -1966,118 +2036,102 @@ class PermissionConfigView(discord.ui.View):
self.add_item(select)
async def _role_callback(self, interaction: discord.Interaction):
# Determine which list to update
if self.config_type == "staff":
target_list = self.config.staff_roles
else:
target_list = self.config.config_roles
# Get new selected IDs
# Note: RoleSelect/UserSelect returns the objects, not IDs directly in values sometimes depending on lib version,
# but interaction.data['values'] is reliable for IDs if we want raw.
# However, interaction.values which is bound to callback by library usually contains the objects.
# Let's use the view item's values.
# Actually standard lib behavior: interaction.data['values'] contains IDs.
# But let's use the argument 'interaction' and the component.
# Better: use the values from the specific component that triggered this
# But since we have separate callbacks we can just read interaction.data
selected_ids = [str(r.id) for r in interaction.data.get('values', [])] # But wait, select menu interactions return objects in the view item's values property if mapped? NO, interaction.values is for string selects.
# For Entity Select Menus (Role/User), interaction.data['values'] contains IDs.
# BUT, discord.py 2.0 uses select.values for string selects.
# For RoleSelect, values are the list of Role objects.
# Let's try to trust the interaction object's resolution.
# Wait, standard discord.py RoleSelect callback receives interaction.
# The select object itself will have 'values' populated with Role objects after processing.
# But we need to reference the select item.
# Since we added it dynamically, we can't easily reference 'self.role_select'.
# Workaround: Re-read from interaction.data['values'] which are IDs.
values = interaction.data.get('values', [])
# WE NEED TO MERGE with existing? No, select menu usually replaces selection if it shows current selection.
# BUT Discord UI Selects don't show "current selection" natively pre-filled unless we set defaults.
# AND we can only set defaults for StringSelect, NOT for RoleSelect/UserSelect (API Limitation).
# So RoleSelect/UserSelect always start empty.
# This implies we can only ADD/REMOVE or we have to handle it as "Add these", "Remove these"?
# OR we treat valid input as "Append these to existing" or "Replace existing"?
# Given we can't show pre-selected items, "Replace" is dangerous because user can't see what was there.
# "Append" is safer but how to remove?
# Modification: Make it "Toggle" or just "Add/Remove" via specific actions?
# A common pattern is: The select menu creates a list. We Add them.
# But how to remove?
# Maybe we should stick to STRING SELECT for removal if we want to show lists.
# But the user issue is "Can't see all roles".
# Better approach for Entity Selects:
# They are usually used for "Add these items".
# To remove, we might need a separate "Remove" workflow or a StringSelect of *current* items to remove.
# Let's implement: "Add selected roles/users to permission".
# And provide a way to "Clear/Remove" via a StringSelect of *existing* configured items.
# So:
# 1. RoleSelect (Add)
# 2. UserSelect (Add)
# 3. StringSelect (Remove) - Populated with currently configured items.
new_ids = interaction.data.get('values', [])
added_count = 0
for new_id in new_ids:
if new_id not in target_list:
target_list.append(new_id)
added_count += 1
try:
# Determine which list to update
if self.config_type == "staff":
target_list = self.config.staff_roles
else:
target_list = self.config.config_roles
self.storage.save_config(interaction.guild_id, self.config)
await interaction.response.send_message(
f"✅ **{added_count}** rôle(s) ajouté(s) à la configuration {self.config_type}.",
ephemeral=True
)
# Refresh view to update Remove dropdown if we add it
await self._refresh_view(interaction)
# Get new selected IDs
new_ids = interaction.data.get('values', [])
added_count = 0
for new_id in new_ids:
if new_id not in target_list:
target_list.append(new_id)
added_count += 1
self.storage.save_config(interaction.guild_id, self.config)
await self._refresh_view(interaction)
except Exception as e:
# Assuming kuby_logger is defined elsewhere, if not, replace with print or logging.error
# import kuby_logger # Uncomment if kuby_logger is not globally available
# kuby_logger.error(f"Error in _role_callback: {e}")
import traceback
traceback.print_exc()
await interaction.response.send_message("Une erreur est survenue lors de la mise à jour des rôles.", ephemeral=True)
async def _user_callback(self, interaction: discord.Interaction):
if self.config_type == "staff":
target_list = self.config.staff_users
else:
target_list = self.config.config_users
new_ids = [int(uid) for uid in interaction.data.get('values', [])]
added_count = 0
for new_id in new_ids:
if new_id not in target_list:
target_list.append(new_id)
added_count += 1
try:
if self.config_type == "staff":
target_list = self.config.staff_users
else:
target_list = self.config.config_users
self.storage.save_config(interaction.guild_id, self.config)
await interaction.response.send_message(
f"✅ **{added_count}** utilisateur(s) ajouté(s) à la configuration {self.config_type}.",
ephemeral=True
)
await self._refresh_view(interaction)
new_ids = [int(uid) for uid in interaction.data.get('values', [])]
added_count = 0
for new_id in new_ids:
if new_id not in target_list:
target_list.append(new_id)
added_count += 1
self.storage.save_config(interaction.guild_id, self.config)
await self._refresh_view(interaction)
except Exception as e:
# Assuming kuby_logger is defined elsewhere
# import kuby_logger
# kuby_logger.error(f"Error in _user_callback: {e}")
import traceback
traceback.print_exc()
await interaction.response.send_message("Une erreur est survenue lors de la mise à jour des utilisateurs.", ephemeral=True)
async def _refresh_view(self, interaction):
# Re-render the view?
# We can't easily edit the ephemeral message with a new view from here without 'response.edit_message'
# But we already responded to interaction.
# So we can followup edit?
# Actually we just want to update the "Remove" select menu.
# Let's add the Remove Select Menu.
self.clear_items()
self._add_role_select()
self._add_user_select()
self._add_remove_select()
await interaction.message.edit(view=self)
# Rebuild Embed
if self.config_type == "staff":
title = "Permissions Staff"
description = "Configurez qui peut gérer les tickets (répondre, fermer, etc.)"
color = discord.Color.blue()
target_roles = self.config.staff_roles
target_users = self.config.staff_users
else:
title = "Permissions Configuration"
description = "Configurez qui peut modifier les paramètres du bot (/ticketconfig)"
color = discord.Color.orange()
target_roles = self.config.config_roles
target_users = self.config.config_users
embed = discord.Embed(title=title, description=description, color=color)
# Add fields logic (inline reuse)
role_mentions = []
for role_id in target_roles:
role = self.guild.get_role(int(role_id))
if role:
role_mentions.append(role.mention)
user_mentions = []
for user_id in target_users:
user = self.guild.get_member(user_id)
if user:
user_mentions.append(user.mention)
else:
user_mentions.append(f"<@{user_id}>")
embed.add_field(name="Rôles", value=", ".join(role_mentions) if role_mentions else "Aucun", inline=False)
embed.add_field(name="Utilisateurs", value=", ".join(user_mentions) if user_mentions else "Aucun", inline=False)
await interaction.response.edit_message(embed=embed, view=self)
def _add_remove_select(self):
# Build options from current config
@ -2131,31 +2185,36 @@ class PermissionConfigView(discord.ui.View):
self.add_item(select)
async def _remove_callback(self, interaction: discord.Interaction):
values = interaction.data.get('values', [])
removed_count = 0
for val in values:
prefix, id_str = val.split('_')
id_val = int(id_str)
try:
values = interaction.data.get('values', [])
if prefix == 'r': # Role
target_list = self.config.staff_roles if self.config_type == "staff" else self.config.config_roles
if str(id_val) in target_list:
target_list.remove(str(id_val))
removed_count += 1
elif prefix == 'u': # User
target_list = self.config.staff_users if self.config_type == "staff" else self.config.config_users
if id_val in target_list:
target_list.remove(id_val)
removed_count += 1
self.storage.save_config(interaction.guild_id, self.config)
await interaction.response.send_message(
f"🗑️ **{removed_count}** permission(s) retirée(s).",
ephemeral=True
)
await self._refresh_view(interaction)
removed_count = 0
for val in values:
prefix, id_str = val.split('_')
id_val = int(id_str)
if prefix == 'r': # Role
target_list = self.config.staff_roles if self.config_type == "staff" else self.config.config_roles
if str(id_val) in target_list:
target_list.remove(str(id_val))
removed_count += 1
elif prefix == 'u': # User
target_list = self.config.staff_users if self.config_type == "staff" else self.config.config_users
if id_val in target_list:
target_list.remove(id_val)
removed_count += 1
self.storage.save_config(interaction.guild_id, self.config)
await self._refresh_view(interaction)
except Exception as e:
# Assuming kuby_logger is defined elsewhere
# import kuby_logger
# kuby_logger.error(f"Error in _remove_callback: {e}")
import traceback
traceback.print_exc()
await interaction.response.send_message("Une erreur est survenue lors de la suppression des permissions.", ephemeral=True)
class TicketCommands(commands.Cog):
@ -2289,7 +2348,7 @@ class TicketCommands(commands.Cog):
embed.set_footer(text="Utilisez les boutons ci-dessous pour configurer le système")
view = TicketSetupView(self.bot, self.storage, config)
view = ConfigPanelView(self.bot, self.storage, config)
await ctx.send(embed=embed, view=view, ephemeral=True)

View file

@ -0,0 +1,139 @@
import discord
from .data.models import TicketData
class FeedbackView(discord.ui.View):
def __init__(self, bot, storage, ticket: TicketData):
super().__init__(timeout=86400) # 24h timeout
self.bot = bot
self.storage = storage
self.ticket = ticket
self.rating = 0
self.comment = ""
# 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):
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):
if self.rating == 0:
await interaction.response.send_message("❌ Veuillez sélectionner une note avant d'envoyer.", ephemeral=True)
return
# Save feedback
self.ticket.rating = self.rating
self.ticket.feedback = self.comment if self.comment else "Aucun commentaire"
self.storage.save_ticket(self.ticket)
# Disable view
for child in self.children:
child.disabled = True
await interaction.response.edit_message(content="✅ Merci pour votre retour !", view=self, embed=None)
# Log feedback if configured
try:
# Reload ticket to ensure we have latest data (especially claimed_by)
# We need to use the channel_id to find the ticket
reloaded_ticket = self.storage.load_ticket(self.ticket.channel_id)
if reloaded_ticket:
self.ticket = reloaded_ticket
print(f"DEBUG: Reloaded ticket {self.ticket.channel_id}. Claimed by: {self.ticket.claimed_by}", flush=True)
else:
print(f"DEBUG: Could not reload ticket {self.ticket.channel_id}", flush=True)
print(f"DEBUG: Feedback submitted for {self.ticket.channel_id}", flush=True)
if self.ticket.claimed_by:
staff_user = self.bot.get_user(self.ticket.claimed_by)
print(f"DEBUG: Staff user from cache: {staff_user}", flush=True)
if not staff_user:
# Try fetching if not in cache
try:
staff_user = await self.bot.fetch_user(self.ticket.claimed_by)
print(f"DEBUG: Staff user fetched: {staff_user}", flush=True)
except Exception as e:
print(f"DEBUG: Failed to fetch staff user: {e}", flush=True)
pass
if staff_user:
# Create feedback embed
embed = discord.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()
)
embed.add_field(
name="Note",
value=f"{'' * self.rating} ({self.rating}/5)",
inline=True
)
# Add user info if possible
user = self.bot.get_user(self.ticket.user_id)
user_name = f"{user.name} ({user.id})" if user else f"Utilisateur {self.ticket.user_id}"
embed.add_field(
name="Utilisateur",
value=user_name,
inline=True
)
feedback_text = self.comment if self.comment else "Aucun commentaire écrit."
embed.add_field(
name="Commentaire",
value=feedback_text,
inline=False
)
try:
await staff_user.send(embed=embed)
print(f"DEBUG: DM sent successfully to {staff_user}", flush=True)
except discord.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)
else:
print("DEBUG: Staff user object is None", flush=True)
else:
print("DEBUG: Ticket has no claimed_by value", flush=True)
except Exception as e:
print(f"Error in feedback notification: {e}", flush=True)
class RatingSelect(discord.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")
]
super().__init__(placeholder="Notez le service...", min_values=1, max_values=1, options=options, row=0)
async def callback(self, interaction: discord.Interaction):
self.view.rating = int(self.values[0])
await interaction.response.defer()
class FeedbackModal(discord.ui.Modal):
def __init__(self, view):
super().__init__(title="Votre avis")
self.view = view
self.comment = discord.ui.TextInput(
label="Commentaire",
style=discord.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):
self.view.comment = self.comment.value
await interaction.response.send_message("Commentaire enregistré ! N'oubliez pas de valider avec 'Envoyer'.", ephemeral=True)

View file

@ -0,0 +1,329 @@
import discord
from discord.ext import commands
from discord import app_commands
TICKET_CATEGORY_ID = 1450001640127205388 # ID de la catégorie des tickets
STAFF_ROLE_ID = 1454042474141192316 # ID du rôle staff
TEAM_STAFF_ROLE_ID = 1454042474141192316 # ID du rôle équipe staff
ATTENTE_ORAL_ROLE_ID = 11464424211442307279 # ID du rôle "attente oral"
CANDIDATURE_A_FAIRE_ROLE_ID = 1464428034906919038 # ID du rôle "candidature à faire"
CITOYEN_ROLE_ID = 1453708386674806835 # ID du rôle "citoyen"
class ContinueFormView(discord.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):
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
@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):
"""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)
except discord.errors.NotFound:
# Si le message n'existe plus, nous ne faisons rien
pass
class TicketStep1(discord.ui.Modal, title="📋 Informations Personnelles"):
"""É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)
async def on_submit(self, interaction: discord.Interaction):
"""Envoie un bouton pour passer à l'étape 2"""
view = ContinueFormView(current_step=1, step1_data=self)
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)"):
"""Étape 2 du formulaire - Infos RP (Partie 1)"""
def __init__(self, step1_data):
super().__init__()
self.step1_data = step1_data
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):
"""Envoie un bouton pour passer à l'étape suivante"""
view = ContinueFormView(current_step=2, step1_data=self.step1_data, step2_part1_data=self)
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)"):
"""É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
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):
"""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)
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"):
"""É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
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):
"""Création du ticket après validation"""
guild = interaction.guild
user = interaction.user
category = discord.utils.get(guild.categories, id=TICKET_CATEGORY_ID)
# Vérifier si la catégorie existe
if category is None:
await interaction.response.send_message("❌ Erreur : La catégorie des tickets n'existe pas !", ephemeral=True)
return
# Vérifier si le rôle staff existe
staff_role = discord.utils.get(guild.roles, id=STAFF_ROLE_ID)
if staff_role is None:
await interaction.response.send_message("❌ Erreur : Le rôle staff n'existe pas !", ephemeral=True)
return
# 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}":
await interaction.response.send_message(f"❌ Vous avez déjà un ticket ouvert : {channel.mention}", ephemeral=True)
return
# 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),
discord.utils.get(guild.roles, id=TEAM_STAFF_ROLE_ID): discord.PermissionOverwrite(read_messages=True, send_messages=False)
}
# Créer un salon de ticket avec le nom [pseudo_du_membre]-écrit
ticket_channel = await category.create_text_channel(
name=f"{user.name}-écrit",
topic=f"Ticket de {user.id}",
overwrites=overwrites
)
# Récupérer le rôle "équipe staff"
team_staff_role = discord.utils.get(guild.roles, id=TEAM_STAFF_ROLE_ID)
# Création de l'embed avec les infos du formulaire
embed = discord.Embed(title="📩 Ticket Whitelist", color=0x00ff00)
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}"
), 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}"
), inline=False)
embed.add_field(name="📋 - Développement du Personnage", value=(
f"> - Histoire : {self.histoire.value}\n"
f"> - Objectifs : {self.objectifs.value}"
), inline=False)
# Envoyer l'embed avec les boutons d'actions pour les staff
await ticket_channel.send(embed=embed, view=TicketManagementView(user))
# 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:
new_nickname = f"{self.step2_part1_data.prenom.value} {self.step2_part1_data.nom.value} | {self.step1_data.pseudo.value}"
if len(new_nickname) > 32:
new_nickname = new_nickname[:32]
await user.edit(nick=new_nickname)
else:
await interaction.followup.send("❌ Le bot n'a pas les permissions nécessaires pour modifier le pseudo.", ephemeral=True)
# Envoyer un message de confirmation
try:
await interaction.response.send_message(f"✅ Ticket ouvert : {ticket_channel.mention}", ephemeral=True)
except discord.errors.NotFound:
await interaction.followup.send(f"✅ Ticket ouvert : {ticket_channel.mention}", ephemeral=True)
class TicketManagementView(discord.ui.View):
"""Vue pour gérer le ticket avec les boutons Fermer, Réouvrir, Claim et Supprimer"""
def __init__(self, ticket_owner):
super().__init__(timeout=None)
self.ticket_owner = ticket_owner
self.claimed_by = None # Stocke le staff qui claim le ticket
@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):
"""Ferme le ticket en désactivant l'envoi de messages pour le membre"""
if not discord.utils.get(interaction.user.roles, id=STAFF_ROLE_ID):
await interaction.response.send_message("❌ Seuls les staffs peuvent fermer un ticket.", ephemeral=True)
return
await interaction.channel.set_permissions(self.ticket_owner, read_messages=False, send_messages=False)
await interaction.channel.edit(name="ticket-fermé")
await interaction.response.send_message("🔒 Le ticket a été fermé. Vous ne pouvez plus le voir.", ephemeral=True)
# Mettre à jour la vue pour afficher uniquement les boutons Réouvrir et Supprimer
new_view = ClosedTicketView(self.ticket_owner)
await interaction.channel.send("Le ticket a été fermé.", view=new_view)
@discord.ui.button(label="👤 Prendre en charge le Ticket", style=discord.ButtonStyle.blurple, custom_id="claim_ticket")
async def claim_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
"""Un staff prend en charge le ticket"""
if not discord.utils.get(interaction.user.roles, id=STAFF_ROLE_ID):
await interaction.response.send_message("❌ Seuls les staffs peuvent claim un ticket.", ephemeral=True)
return
self.claimed_by = interaction.user
await interaction.channel.set_permissions(discord.utils.get(interaction.guild.roles, id=STAFF_ROLE_ID), send_messages=False, read_messages=True)
await interaction.channel.set_permissions(self.claimed_by, read_messages=True, send_messages=True)
await interaction.response.send_message(f"👤 {self.claimed_by.mention} a claim ce ticket.", ephemeral=False)
class ClosedTicketView(discord.ui.View):
"""Vue pour les tickets fermés avec les boutons Réouvrir et Supprimer"""
def __init__(self, ticket_owner):
super().__init__(timeout=None)
self.ticket_owner = ticket_owner
@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):
"""Réouvre le ticket pour le membre"""
if not discord.utils.get(interaction.user.roles, id=STAFF_ROLE_ID):
await interaction.response.send_message("❌ Seuls les staffs peuvent réouvrir un ticket.", ephemeral=True)
return
await interaction.channel.set_permissions(self.ticket_owner, read_messages=True, send_messages=True)
await interaction.response.send_message("✅ Le ticket a été réouvert.", ephemeral=True)
# Mettre à jour la vue pour afficher tous les boutons
new_view = TicketManagementView(self.ticket_owner)
await interaction.channel.send("Le ticket a été réouvert.", view=new_view)
@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):
"""Supprime le salon du ticket"""
if not discord.utils.get(interaction.user.roles, id=STAFF_ROLE_ID):
await interaction.response.send_message("❌ Seuls les staffs peuvent supprimer un ticket.", ephemeral=True)
return
await interaction.response.send_message("❌ Suppression du ticket...", ephemeral=True)
await interaction.channel.delete()
class TicketView(discord.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):
"""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
@app_commands.command(name="ticket_whitelist", description="Envoie l'embed pour ouvrir un ticket de whitelist.")
async def ticket_whitelist(self, interaction: discord.Interaction):
"""Envoie l'embed avec le bouton d'ouverture de ticket"""
embed = discord.Embed(
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)
@commands.Cog.listener()
async def on_message(self, message):
"""Surveille les messages des staffs dans les tickets"""
if message.guild is None or message.author.bot:
return
if not message.channel.topic or not message.channel.topic.startswith("Ticket de"):
return
staff_role = discord.utils.get(message.guild.roles, id=STAFF_ROLE_ID)
if staff_role in message.author.roles and "validé" in message.content.lower():
try:
user_id = message.channel.topic.split("Ticket de ")[1]
user = await message.guild.fetch_member(user_id)
# Vérifier si le rôle "attente oral" est déjà attribué
attente_oral_role = discord.utils.get(message.guild.roles, id=ATTENTE_ORAL_ROLE_ID)
candidature_a_faire_role = discord.utils.get(message.guild.roles, id=CANDIDATURE_A_FAIRE_ROLE_ID)
citoyen_role = discord.utils.get(message.guild.roles, id=CITOYEN_ROLE_ID)
if attente_oral_role in user.roles:
# Deuxième "validé" détecté
await user.remove_roles(attente_oral_role)
await user.add_roles(citoyen_role)
await message.channel.edit(name=f"{user.name}-citoyen")
else:
# Premier "validé" détecté
await user.remove_roles(candidature_a_faire_role)
await user.add_roles(attente_oral_role)
await message.channel.edit(name=f"{user.name}-oral")
# Mettre à jour le pseudo du joueur si le bot a les permissions nécessaires
if message.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}"
if len(new_nickname) > 32:
new_nickname = new_nickname[:32]
await user.edit(nick=new_nickname)
else:
await message.channel.send("❌ Le bot n'a pas les permissions nécessaires pour modifier le pseudo.", delete_after=10)
except Exception as e:
print(f"Erreur lors du renommage du ticket: {e}")
async def setup(bot):
await bot.add_cog(Ticket(bot))