feat(ticket): add transfer, priority, lock/unlock, stats, message recording
All checks were successful
ci / ci (push) Successful in 44s

- TransferButton: transfer ticket to another staff member
- PriorityButton: set ticket priority (low/normal/high/urgent)
- LockButton: lock/unlock ticket (toggle channel permissions)
- StatsButton: show ticket info (category, user, claimed, priority, messages)
- on_message listener: records all messages for transcript
- TicketData: added locked, priority fields
This commit is contained in:
Mathis 2026-09-25 19:38:24 +02:00
parent 4d1e265848
commit e46c04fe98
3 changed files with 269 additions and 2 deletions

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import asyncio
import contextlib
import logging
import os
from datetime import datetime, timezone
import discord
@ -189,6 +190,14 @@ class TicketModal(discord.ui.Modal, title="Nouveau Ticket"):
discord.ui.ActionRow(
ClaimButton(),
AddMemberButton(),
TransferButton(),
)
)
container.add_item(
discord.ui.ActionRow(
PriorityButton(),
LockButton(),
StatsButton(),
CloseButton(),
)
)
@ -330,6 +339,210 @@ class AddMemberSelectView(discord.ui.View):
self.add_item(AddMemberSelect())
class TransferButton(discord.ui.Button):
def __init__(self):
super().__init__(
label="Transfer",
style=discord.ButtonStyle.secondary,
custom_id="ticket_transfer",
)
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 transferer.", ephemeral=True)
return
await interaction.response.send_message(
"Selectionnez le staff :",
view=TransferSelectView(),
ephemeral=True,
)
class TransferSelect(discord.ui.UserSelect):
def __init__(self):
super().__init__(
placeholder="Selectionnez un staff...",
custom_id="ticket_transfer_select",
min_values=1,
max_values=1,
)
async def callback(self, interaction: discord.Interaction):
user = self.values[0]
ticket = storage.get_ticket(interaction.channel.id)
if ticket:
ticket.claimed_by = user.id
storage.save_ticket(ticket)
await interaction.response.edit_message(
content=f"Ticket transfere a {user.mention}.",
view=None,
)
embed = discord.Embed(
title="Ticket transfere",
description=f"Transfere a {user.mention} 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 TransferSelectView(discord.ui.View):
def __init__(self):
super().__init__(timeout=300)
self.add_item(TransferSelect())
class PriorityButton(discord.ui.Button):
def __init__(self):
super().__init__(
label="Priorite",
style=discord.ButtonStyle.secondary,
custom_id="ticket_priority",
)
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 changer la priorite.", ephemeral=True)
return
await interaction.response.send_message(
"Selectionnez la priorite :",
view=PrioritySelectView(),
ephemeral=True,
)
class PrioritySelect(discord.ui.Select):
def __init__(self):
super().__init__(
placeholder="Selectionnez la priorite...",
custom_id="ticket_priority_select",
options=[
discord.SelectOption(label="Basse", value="low", emoji="🟢"),
discord.SelectOption(label="Normale", value="normal", emoji="🔵"),
discord.SelectOption(label="Haute", value="high", emoji="🟠"),
discord.SelectOption(label="Urgente", value="urgent", emoji="🔴"),
],
)
async def callback(self, interaction: discord.Interaction):
from tickets.data import TicketPriority
ticket = storage.get_ticket(interaction.channel.id)
if ticket:
ticket.priority = TicketPriority(self.values[0])
storage.save_ticket(ticket)
await interaction.response.edit_message(
content=f"Priorite definie sur **{self.values[0]}**.",
view=None,
)
embed = discord.Embed(
title="Priorite changee",
description=f"Priorite : **{self.values[0]}** 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 PrioritySelectView(discord.ui.View):
def __init__(self):
super().__init__(timeout=300)
self.add_item(PrioritySelect())
class LockButton(discord.ui.Button):
def __init__(self):
super().__init__(
label="Lock",
style=discord.ButtonStyle.secondary,
custom_id="ticket_lock",
)
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 lock/unlock.", ephemeral=True)
return
ticket.locked = not ticket.locked
storage.save_ticket(ticket)
overwrites = interaction.channel.overwrites_for(interaction.guild.default_role)
overwrites.send_messages = not ticket.locked
await interaction.channel.set_permissions(interaction.guild.default_role, overwrite=overwrites)
self.label = "Unlock" if ticket.locked else "Lock"
await interaction.response.send_message(
f"Ticket {'locked' if ticket.locked else 'unlocked'} par {interaction.user.mention}."
)
embed = discord.Embed(
title=f"Ticket {'locked' if ticket.locked else 'unlocked'}",
description=f"Par {interaction.user.mention}",
color=discord.Color.orange() if ticket.locked else discord.Color.green(),
timestamp=datetime.now(timezone.utc),
)
await _send_log(interaction.guild, config, embed)
class StatsButton(discord.ui.Button):
def __init__(self):
super().__init__(
label="Infos",
style=discord.ButtonStyle.secondary,
custom_id="ticket_stats",
)
async def callback(self, interaction: discord.Interaction):
ticket = storage.get_ticket(interaction.channel.id)
if not ticket:
await interaction.response.send_message("Ce n'est pas un ticket.", ephemeral=True)
return
config = storage.get_config(interaction.guild_id)
cat = next((c for c in config.categories if c.id == ticket.category_id), None)
user = interaction.guild.get_member(ticket.user_id)
claimed = interaction.guild.get_member(ticket.claimed_by) if ticket.claimed_by else None
msg_count = len(ticket.messages)
container = discord.ui.Container()
container.add_item(
discord.ui.TextDisplay(
f"## Infos Ticket\n"
f"**Categorie :** {cat.emoji} {cat.name if cat else ticket.category_id}\n"
f"**Utilisateur :** {user.mention if user else ticket.user_id}\n"
f"**Claim par :** {claimed.mention if claimed else 'Non claim'}\n"
f"**Priorite :** {ticket.priority.value}\n"
f"**Statut :** {'Lock' if ticket.locked else 'Ouvert'}\n"
f"**Messages :** {msg_count}\n"
f"**Cree :** <t:{int(os.path.fromisoformat(ticket.created_at).timestamp())}:R>"
if ticket.created_at
else ""
)
)
view = discord.ui.LayoutView()
view.add_item(container)
await interaction.response.send_message(view=view, ephemeral=True)
class CloseButton(discord.ui.Button):
def __init__(self):
super().__init__(