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)