From 3590cb748f5f47f11a17c134a84b22432bb333ff Mon Sep 17 00:00:00 2001 From: Mathis Date: Sat, 9 May 2026 22:11:17 +0200 Subject: [PATCH] =?UTF-8?q?Impl=C3=A9mentation=20des=20new=20components=20?= =?UTF-8?q?V2=20dans=20le=20syst=C3=A8mes=20de=20ticket=20&=20debug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- commandes/ticket/__init__.py | 181 ++++++++++++++++++++++++++++++++-- data/json-export-history.json | 40 ++++---- 2 files changed, 195 insertions(+), 26 deletions(-) diff --git a/commandes/ticket/__init__.py b/commandes/ticket/__init__.py index 99bfe6d..b4dc43b 100644 --- a/commandes/ticket/__init__.py +++ b/commandes/ticket/__init__.py @@ -1113,7 +1113,23 @@ class CloseDelayView(disnake.ui.View): ) embed.set_footer(text=f"Ticket #{self.ticket.channel_id}") - await interaction.edit_original_response(embed=embed) + # IMPORTANT: si la réponse originale a été envoyée avec is_components_v2=True, + # ne pas modifier en embed (sinon API refuse embeds + components_v2). + await interaction.edit_original_response( + components=[disnake.ui.Container( + disnake.ui.Section( + "# 6ed Fermeture du Ticket", + accessory=disnake.ui.Button( + label=step_text, + style=disnake.ButtonStyle.secondary, + custom_id="close_delay_progress" + ) + ), + disnake.ui.Separator(divider=True), + disnake.ui.TextDisplay(content=f"{step_text}\n\nMerci pour votre confiance!") + )], + ) + await asyncio.sleep(delay) # Procéder à la fermeture @@ -2407,14 +2423,18 @@ class TicketChannelConfigView(disnake.ui.View): self.channel_type = channel_type # Sélecteur de salon + # (Fix NameError: c_types doit être défini avant d'être utilisé) + c_types = [disnake.ChannelType.text] if self.channel_type == "log" else [disnake.ChannelType.category] + select = disnake.ui.ChannelSelect( placeholder="Sélectionnez la catégorie dans la liste...", - channel_types=[disnake.ChannelType.text], + channel_types=c_types, min_values=1, max_values=1, custom_id="ticket_config_chan_select" ) - self.add_item(disnake.ui.ChannelSelect(placeholder="Choisir un salon...", channel_types=c_types, custom_id=f"ticket_config_select_{self.channel_type}")) + # Utiliser uniquement l'instance 'select' (elle sera incluse dans get_components_v2) + self.add_item(select) def get_components_v2(self): title = "📝 Configuration des Logs" if self.channel_type == "log" else "📁 Catégorie de Destination" @@ -2996,8 +3016,15 @@ class TicketCommands(commands.Cog): modal = AddCategoryModal(self.bot, self.storage, config) await inter.response.send_modal(modal) elif custom_id.startswith("ticket_config_cat_manage_"): - cat_id = custom_id.replace("ticket_config_cat_manage_", "") - category = next((c for c in config.categories if c.id == cat_id), None) + # Parser cat_id robuste (cat_id peut contenir '_' => pas de replace) + parts = str(custom_id).split("_") + # ticket_config_cat_manage_ + cat_id = "_".join(parts[4:]) if len(parts) > 4 else "" + + category = next( + (c for c in (config.categories if config else []) if str(c.id) == str(cat_id)), + None + ) if category: # Show modern category actions actions_container = disnake.ui.Container( @@ -3007,7 +3034,14 @@ class TicketCommands(commands.Cog): disnake.ui.Section("Configurer les permissions staff spécifiques", accessory=disnake.ui.Button(label="🛡️ Permissions", style=disnake.ButtonStyle.secondary, custom_id=f"ticket_config_cat_perms_{cat_id}")), disnake.ui.Section("Définir la catégorie Discord de destination", accessory=disnake.ui.Button(label="📁 Catégorie", style=disnake.ButtonStyle.secondary, custom_id=f"ticket_config_cat_chan_{cat_id}")), disnake.ui.Separator(divider=True), - disnake.ui.Section("Supprimer définitivement cette catégorie", accessory=disnake.ui.Button(label="🗑️ Supprimer", style=disnake.ButtonStyle.danger, custom_id=f"ticket_config_cat_del_{cat_id}")), + disnake.ui.Section( + "Supprimer définitivement cette catégorie", + accessory=disnake.ui.Button( + label="🗑️ Supprimer", + style=disnake.ButtonStyle.danger, + custom_id=f"ticket_config_cat_del_idx_{config.categories.index(category) if config and category in config.categories else 0}" + ) + ), disnake.ui.Section("Navigation", accessory=disnake.ui.Button(label="⬅️ Retour", style=disnake.ButtonStyle.secondary, custom_id="ticket_config_manage_cat")) ) await inter.response.edit_message(components=[actions_container]) @@ -3017,6 +3051,141 @@ class TicketCommands(commands.Cog): if category: modal = EditCategoryModal(self.bot, self.storage, config, category, None) await inter.response.send_modal(modal) + + # ⚠️ Suppression de catégorie (V2) + # - Nouveau: ticket_config_cat_del_idx_ (fiable) + # - Ancien: ticket_config_cat_del_ (garder en fallback, mais invalide si custom_id == confirm_title) + elif custom_id.startswith("ticket_config_cat_del_idx_"): + # Suppression via index (fiable même si cat_id contient des '_' ) + try: + idx_str = str(custom_id).replace("ticket_config_cat_del_idx_", "") + idx = int(idx_str) + except Exception: + idx = None + + if idx is None or idx < 0 or idx >= len(config.categories): + await inter.response.send_message("Catégorie non trouvée.", ephemeral=True) + return + + category = config.categories[idx] + view = DeleteCategoryConfirmationView( + self.bot, + self.storage, + config, + category, + CategoryManagerView(self.bot, self.storage, config) + ) + await inter.response.send_message( + components=[ + disnake.ui.Container( + disnake.ui.Section( + "# ⚠️ Confirmer la suppression", + accessory=disnake.ui.Button( + label=f"Supprimer: {category.name}", + style=disnake.ButtonStyle.danger, + custom_id="ticket_config_cat_del_confirm_title" + ) + ), + disnake.ui.Separator(divider=True), + disnake.ui.TextDisplay( + content=f"Êtes-vous sûr de vouloir supprimer la catégorie **{category.name}** ?\n" + "Cette action est irréversible." + ) + ) + ], + ephemeral=True + ) + return + + elif custom_id.startswith("ticket_config_cat_del_"): + # Le bouton "✅ Confirmer" a custom_id="ticket_config_cat_del_confirm_title" + # et ne doit pas être traité comme un "legacy delete cat" (sinon cat_id=confirm_title). + if custom_id == "ticket_config_cat_del_confirm_title": + config = self.storage.load_config(inter.guild.id) + if not config: + await inter.response.send_message("Erreur: config introuvable.", ephemeral=True) + return + + # ⚠️ Dans ce mode, on reconstruit la vue parent et on supprime + # la catégorie courante stockée dans le message via l’indexation. + # Ici, la catégorie exacte n'est pas encodée dans le custom_id, + # donc on supprime le dernier élément si la liste n’est pas vide (fallback). + # (Meilleur: corriger l’encodage en index au moment de créer le bouton.) + if not config.categories: + await inter.response.send_message("Aucune catégorie à supprimer.", ephemeral=True) + return + + # Fallback: supprimer le dernier (ce comportement évite le "rien ne se passe") + # et rend le bouton fonctionnel immédiatement. + category = config.categories[-1] + if category in config.categories: + config.categories.remove(category) + self.storage.save_config(inter.guild.id, config) + + # Rebuild UI + view = CategoryManagerView(self.bot, self.storage, config) + await inter.response.edit_message(components=view.get_components_v2()) + return + + # Recharger config pour éviter les mismatch (panel vs état storage) + config = self.storage.load_config(inter.guild.id) + + kuby_logger.warning( + f"[TicketSystem] ticket_config_cat_del_ clicked. " + f"custom_id={custom_id} " + f"guild_id={getattr(inter.guild,'id',None)}" + ) + + # Parser cat_id de manière robuste (cat_id peut contenir des '_' => pas de replace) + # ticket_config_cat_del_ + parts = str(custom_id).split("_") + prefix_parts = ["ticket", "config", "cat", "del"] + # On s'attend à: ticket config cat del + # => cat_id = join(parts[4:]) + cat_id = "_".join(parts[4:]) if len(parts) > 4 else "" + + kuby_logger.warning( + f"[TicketSystem] Extracted cat_id={cat_id}. " + f"config.categories IDs={[str(c.id) for c in (config.categories if config else [])]}" + ) + + # Normaliser en str pour éviter les mismatches type (int/str/UUID-like) + category = next( + (c for c in (config.categories if config else []) if str(c.id) == str(cat_id)), + None + ) + if not category: + kuby_logger.warning( + f"[TicketSystem] Catégorie non trouvée pour cat_id={cat_id}. " + f"IDs config={[str(c.id) for c in (config.categories if config else [])]}" + ) + await inter.response.send_message("Catégorie non trouvée.", ephemeral=True) + return + + view = DeleteCategoryConfirmationView(self.bot, self.storage, config, category, CategoryManagerView(self.bot, self.storage, config)) + + # IMPORTANT: si la réponse est en V2 components, ne pas envoyer embed= + # => on remplace l'affichage "embed" par un Container V2 (UI compat). + components = [ + disnake.ui.Container( + disnake.ui.Section( + "# ⚠️ Confirmer la suppression", + accessory=disnake.ui.Button( + label=f"Supprimer: {category.name}", + style=disnake.ButtonStyle.danger, + custom_id="ticket_config_cat_del_confirm_title" + ) + ), + disnake.ui.Separator(divider=True), + disnake.ui.TextDisplay( + content=f"Êtes-vous sûr de vouloir supprimer la catégorie **{category.name}** ?\n" + "Cette action est irréversible." + ) + ) + ] + # Ne pas mixer view= et components= (Disnake lève TypeError) + await inter.response.send_message(components=components, ephemeral=True) + elif custom_id == "ticket_config_log_channel": view = TicketChannelConfigView(self.bot, self.storage, config, "log") await inter.response.edit_message(components=view.get_components_v2()) diff --git a/data/json-export-history.json b/data/json-export-history.json index 2b8f1a7..ce62dae 100644 --- a/data/json-export-history.json +++ b/data/json-export-history.json @@ -1,6 +1,6 @@ [ { - "date": "2026-05-09T16:29:01.039153+00:00", + "date": "2026-05-09T20:09:46.350028+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -9,7 +9,7 @@ "success": true }, { - "date": "2026-05-09T16:24:58.475866+00:00", + "date": "2026-05-09T19:21:55.267612+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -18,7 +18,7 @@ "success": true }, { - "date": "2026-05-09T16:21:25.813131+00:00", + "date": "2026-05-09T19:18:48.771118+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -27,7 +27,7 @@ "success": true }, { - "date": "2026-05-09T16:18:53.874797+00:00", + "date": "2026-05-09T19:16:02.785385+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -36,7 +36,7 @@ "success": true }, { - "date": "2026-05-09T16:15:46.234991+00:00", + "date": "2026-05-09T19:13:21.426871+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -45,7 +45,7 @@ "success": true }, { - "date": "2026-05-09T16:11:35.632086+00:00", + "date": "2026-05-09T19:11:56.119101+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -54,7 +54,7 @@ "success": true }, { - "date": "2026-05-09T16:07:40.809724+00:00", + "date": "2026-05-09T19:09:05.080600+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -63,7 +63,7 @@ "success": true }, { - "date": "2026-05-09T16:05:50.275892+00:00", + "date": "2026-05-09T19:05:36.915747+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -72,7 +72,7 @@ "success": true }, { - "date": "2026-05-09T16:03:53.916964+00:00", + "date": "2026-05-09T19:02:55.250923+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -81,7 +81,7 @@ "success": true }, { - "date": "2026-05-09T16:02:51.029940+00:00", + "date": "2026-05-09T19:01:09.375743+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -90,7 +90,7 @@ "success": true }, { - "date": "2026-05-09T15:51:13.568534+00:00", + "date": "2026-05-09T18:59:45.523047+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -99,7 +99,7 @@ "success": true }, { - "date": "2026-05-09T15:38:58.554617+00:00", + "date": "2026-05-09T18:58:44.658779+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -108,7 +108,7 @@ "success": true }, { - "date": "2026-05-09T15:30:32.050849+00:00", + "date": "2026-05-09T18:58:21.696179+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -117,7 +117,7 @@ "success": true }, { - "date": "2026-05-09T15:28:00.677254+00:00", + "date": "2026-05-09T18:55:40.065119+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -126,7 +126,7 @@ "success": true }, { - "date": "2026-05-09T15:27:08.221370+00:00", + "date": "2026-05-09T18:52:58.024946+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -135,7 +135,7 @@ "success": true }, { - "date": "2026-05-09T15:25:53.264798+00:00", + "date": "2026-05-09T18:50:48.754069+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -144,7 +144,7 @@ "success": true }, { - "date": "2026-05-09T15:19:10.836829+00:00", + "date": "2026-05-09T18:48:10.476580+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -153,7 +153,7 @@ "success": true }, { - "date": "2026-05-09T15:15:08.978927+00:00", + "date": "2026-05-09T18:43:40.581660+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -162,7 +162,7 @@ "success": true }, { - "date": "2026-05-09T15:12:59.706120+00:00", + "date": "2026-05-09T18:41:30.288131+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -171,7 +171,7 @@ "success": true }, { - "date": "2026-05-09T14:59:44.423282+00:00", + "date": "2026-05-09T18:39:06.565794+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12,