Maintenance de routine
This commit is contained in:
parent
c330b9a1cc
commit
99ab16098f
62 changed files with 52172 additions and 144 deletions
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
139
commandes/ticket/feedback_view.py
Normal file
139
commandes/ticket/feedback_view.py
Normal 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)
|
||||
329
commandes/ticket_whitelist.py
Normal file
329
commandes/ticket_whitelist.py
Normal 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))
|
||||
Loading…
Add table
Add a link
Reference in a new issue