From 4d1e265848247cdb5f62c9c2213bfdc34141e3ec Mon Sep 17 00:00:00 2001 From: Mathis Date: Fri, 25 Sep 2026 19:28:15 +0200 Subject: [PATCH] feat(ticket): add claim, add member, persistent management view - ClaimButton: staff claim/unclaim with log - AddMemberButton: UserSelect to add member to ticket channel - TicketManagementView: persistent LayoutView with all 3 buttons - claimed_by field in TicketData - Buttons registered as persistent views for bot restart survival --- tickets/cog.py | 11 ++++- tickets/data.py | 2 + tickets/ui.py | 124 +++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 135 insertions(+), 2 deletions(-) diff --git a/tickets/cog.py b/tickets/cog.py index 2884f2a..9359b87 100644 --- a/tickets/cog.py +++ b/tickets/cog.py @@ -8,7 +8,7 @@ from discord.ext import commands from tickets import storage from tickets.data import TicketCategory -from tickets.ui import PanelCreateButton +from tickets.ui import AddMemberButton, ClaimButton, CloseButton, PanelCreateButton logger = logging.getLogger("vision") @@ -130,12 +130,21 @@ class ConfigSetLogButton(discord.ui.Button): await interaction.response.send_message("Selectionnez le canal de logs :", view=view, ephemeral=True) +class TicketManagementView(discord.ui.LayoutView): + def __init__(self): + super().__init__(timeout=None) + container = discord.ui.Container() + container.add_item(discord.ui.ActionRow(ClaimButton(), AddMemberButton(), CloseButton())) + self.add_item(container) + + class TicketSystem(commands.Cog): ticket = app_commands.Group(name="ticket", description="Systeme de tickets") def __init__(self, bot: commands.Bot): self.bot = bot self.bot.add_view(PanelView()) + self.bot.add_view(TicketManagementView()) @commands.Cog.listener() async def on_ready(self): diff --git a/tickets/data.py b/tickets/data.py index 6dee8c4..cfad81c 100644 --- a/tickets/data.py +++ b/tickets/data.py @@ -53,6 +53,7 @@ class TicketData: created_at: str = "" closed_at: str | None = None closed_by: int | None = None + claimed_by: int | None = None messages: list[TicketMessage] = field(default_factory=list) reason: str = "" @@ -66,6 +67,7 @@ class TicketData: "created_at": self.created_at, "closed_at": self.closed_at, "closed_by": self.closed_by, + "claimed_by": self.claimed_by, "messages": [ { "author_id": m.author_id, diff --git a/tickets/ui.py b/tickets/ui.py index 81f44ff..cde08b8 100644 --- a/tickets/ui.py +++ b/tickets/ui.py @@ -185,7 +185,13 @@ class TicketModal(discord.ui.Modal, title="Nouveau Ticket"): ) ) container.add_item(discord.ui.Separator()) - container.add_item(discord.ui.ActionRow(CloseButton())) + container.add_item( + discord.ui.ActionRow( + ClaimButton(), + AddMemberButton(), + CloseButton(), + ) + ) view = discord.ui.LayoutView() view.add_item(container) @@ -208,6 +214,122 @@ class TicketModal(discord.ui.Modal, title="Nouveau Ticket"): await _send_log(guild, config, embed) +class ClaimButton(discord.ui.Button): + def __init__(self): + super().__init__( + label="Claim", + style=discord.ButtonStyle.success, + custom_id="ticket_claim", + emoji="Claim", + ) + + async def callback(self, interaction: discord.Interaction): + config = storage.get_config(interaction.guild_id) + ticket = storage.get_ticket(interaction.channel.id) + if not ticket: + await interaction.response.send_message("Ce n'est pas un ticket.", ephemeral=True) + return + + member = interaction.guild.get_member(interaction.user.id) + if not member or not _is_staff(member, config): + await interaction.response.send_message("Seul le staff peut claim un ticket.", ephemeral=True) + return + + if ticket.claimed_by: + if ticket.claimed_by == interaction.user.id: + ticket.claimed_by = None + storage.save_ticket(ticket) + await interaction.response.send_message( + f"{interaction.user.mention} a retire son claim.", ephemeral=True + ) + else: + await interaction.response.send_message( + f"Ce ticket est deja claim par <@{ticket.claimed_by}>.", ephemeral=True + ) + return + + ticket.claimed_by = interaction.user.id + storage.save_ticket(ticket) + + embed = discord.Embed( + title="Ticket claim", + description=f"Claim par {interaction.user.mention}", + color=discord.Color.green(), + timestamp=datetime.now(timezone.utc), + ) + await _send_log(interaction.guild, config, embed) + + container = discord.ui.Container() + container.add_item(discord.ui.TextDisplay(f"## Ticket claim\nClaim par {interaction.user.mention}")) + view = discord.ui.LayoutView() + view.add_item(container) + await interaction.response.send_message(view=view) + + +class AddMemberButton(discord.ui.Button): + def __init__(self): + super().__init__( + label="Ajouter un membre", + style=discord.ButtonStyle.secondary, + custom_id="ticket_add_member", + emoji="Ajouter", + ) + + async def callback(self, interaction: discord.Interaction): + config = storage.get_config(interaction.guild_id) + ticket = storage.get_ticket(interaction.channel.id) + if not ticket: + await interaction.response.send_message("Ce n'est pas un ticket.", ephemeral=True) + return + + member = interaction.guild.get_member(interaction.user.id) + if not member or not _is_staff(member, config): + await interaction.response.send_message("Seul le staff peut ajouter des membres.", ephemeral=True) + return + + await interaction.response.send_message( + "Selectionnez le membre a ajouter :", + view=AddMemberSelectView(), + ephemeral=True, + ) + + +class AddMemberSelect(discord.ui.UserSelect): + def __init__(self): + super().__init__( + placeholder="Selectionnez un membre...", + custom_id="ticket_add_member_select", + min_values=1, + max_values=1, + ) + + async def callback(self, interaction: discord.Interaction): + user = self.values[0] + await interaction.channel.set_permissions( + user, + view_channel=True, + send_messages=True, + read_message_history=True, + ) + await interaction.response.edit_message( + content=f"{user.mention} a ete ajoute au ticket.", + view=None, + ) + embed = discord.Embed( + title="Membre ajoute", + description=f"{user.mention} a ete ajoute par {interaction.user.mention}", + color=discord.Color.blue(), + timestamp=datetime.now(timezone.utc), + ) + await _send_log(interaction.guild, storage.get_config(interaction.guild_id), embed) + + +class AddMemberSelectView(discord.ui.View): + def __init__(self): + super().__init__(timeout=300) + self.add_item(AddMemberSelect()) + + class CloseButton(discord.ui.Button): def __init__(self): super().__init__(