feat(ticket): add transfer, priority, lock/unlock, stats, message recording
All checks were successful
ci / ci (push) Successful in 44s
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:
parent
4d1e265848
commit
e46c04fe98
3 changed files with 269 additions and 2 deletions
|
|
@ -8,7 +8,17 @@ from discord.ext import commands
|
||||||
|
|
||||||
from tickets import storage
|
from tickets import storage
|
||||||
from tickets.data import TicketCategory
|
from tickets.data import TicketCategory
|
||||||
from tickets.ui import AddMemberButton, ClaimButton, CloseButton, PanelCreateButton
|
from tickets.ui import (
|
||||||
|
AddMemberButton,
|
||||||
|
ClaimButton,
|
||||||
|
CloseButton,
|
||||||
|
LockButton,
|
||||||
|
PanelCreateButton,
|
||||||
|
PriorityButton,
|
||||||
|
StatsButton,
|
||||||
|
TransferButton,
|
||||||
|
_is_staff,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger("vision")
|
logger = logging.getLogger("vision")
|
||||||
|
|
||||||
|
|
@ -134,7 +144,8 @@ class TicketManagementView(discord.ui.LayoutView):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(timeout=None)
|
super().__init__(timeout=None)
|
||||||
container = discord.ui.Container()
|
container = discord.ui.Container()
|
||||||
container.add_item(discord.ui.ActionRow(ClaimButton(), AddMemberButton(), CloseButton()))
|
container.add_item(discord.ui.ActionRow(ClaimButton(), AddMemberButton(), TransferButton()))
|
||||||
|
container.add_item(discord.ui.ActionRow(PriorityButton(), LockButton(), StatsButton(), CloseButton()))
|
||||||
self.add_item(container)
|
self.add_item(container)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -150,6 +161,37 @@ class TicketSystem(commands.Cog):
|
||||||
async def on_ready(self):
|
async def on_ready(self):
|
||||||
logger.info("TicketSystem cog charge")
|
logger.info("TicketSystem cog charge")
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_message(self, message: discord.Message):
|
||||||
|
if message.author.bot:
|
||||||
|
return
|
||||||
|
if not message.guild:
|
||||||
|
return
|
||||||
|
|
||||||
|
ticket = storage.get_ticket(message.channel.id)
|
||||||
|
if not ticket:
|
||||||
|
return
|
||||||
|
|
||||||
|
config = storage.get_config(message.guild.id)
|
||||||
|
member = message.guild.get_member(message.author.id)
|
||||||
|
if not member:
|
||||||
|
return
|
||||||
|
|
||||||
|
is_staff = _is_staff(member, config)
|
||||||
|
|
||||||
|
from tickets.data import TicketMessage
|
||||||
|
|
||||||
|
msg = TicketMessage(
|
||||||
|
author_id=message.author.id,
|
||||||
|
author_name=str(message.author),
|
||||||
|
content=message.content,
|
||||||
|
timestamp=message.created_at.isoformat(),
|
||||||
|
attachments=[a.url for a in message.attachments],
|
||||||
|
is_staff=is_staff,
|
||||||
|
)
|
||||||
|
ticket.messages.append(msg)
|
||||||
|
storage.save_ticket(ticket)
|
||||||
|
|
||||||
@ticket.command(name="setup", description="Envoyer le panneau de tickets")
|
@ticket.command(name="setup", description="Envoyer le panneau de tickets")
|
||||||
@app_commands.describe(channel="Canal pour le panneau")
|
@app_commands.describe(channel="Canal pour le panneau")
|
||||||
@app_commands.checks.has_permissions(manage_guild=True)
|
@app_commands.checks.has_permissions(manage_guild=True)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,13 @@ class TicketStatus(str, Enum):
|
||||||
CLOSED = "closed"
|
CLOSED = "closed"
|
||||||
|
|
||||||
|
|
||||||
|
class TicketPriority(str, Enum):
|
||||||
|
LOW = "low"
|
||||||
|
NORMAL = "normal"
|
||||||
|
HIGH = "high"
|
||||||
|
URGENT = "urgent"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TicketCategory:
|
class TicketCategory:
|
||||||
id: str
|
id: str
|
||||||
|
|
@ -54,6 +61,8 @@ class TicketData:
|
||||||
closed_at: str | None = None
|
closed_at: str | None = None
|
||||||
closed_by: int | None = None
|
closed_by: int | None = None
|
||||||
claimed_by: int | None = None
|
claimed_by: int | None = None
|
||||||
|
locked: bool = False
|
||||||
|
priority: TicketPriority = TicketPriority.NORMAL
|
||||||
messages: list[TicketMessage] = field(default_factory=list)
|
messages: list[TicketMessage] = field(default_factory=list)
|
||||||
reason: str = ""
|
reason: str = ""
|
||||||
|
|
||||||
|
|
@ -68,6 +77,8 @@ class TicketData:
|
||||||
"closed_at": self.closed_at,
|
"closed_at": self.closed_at,
|
||||||
"closed_by": self.closed_by,
|
"closed_by": self.closed_by,
|
||||||
"claimed_by": self.claimed_by,
|
"claimed_by": self.claimed_by,
|
||||||
|
"locked": self.locked,
|
||||||
|
"priority": self.priority.value,
|
||||||
"messages": [
|
"messages": [
|
||||||
{
|
{
|
||||||
"author_id": m.author_id,
|
"author_id": m.author_id,
|
||||||
|
|
@ -86,6 +97,7 @@ class TicketData:
|
||||||
def from_dict(cls, data: dict) -> TicketData:
|
def from_dict(cls, data: dict) -> TicketData:
|
||||||
messages = [TicketMessage(**m) for m in data.pop("messages", [])]
|
messages = [TicketMessage(**m) for m in data.pop("messages", [])]
|
||||||
data["status"] = TicketStatus(data.get("status", "open"))
|
data["status"] = TicketStatus(data.get("status", "open"))
|
||||||
|
data["priority"] = TicketPriority(data.get("priority", "normal"))
|
||||||
data.pop("transcript_path", None)
|
data.pop("transcript_path", None)
|
||||||
data.pop("rating", None)
|
data.pop("rating", None)
|
||||||
data.pop("feedback", None)
|
data.pop("feedback", None)
|
||||||
|
|
|
||||||
213
tickets/ui.py
213
tickets/ui.py
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||||
import asyncio
|
import asyncio
|
||||||
import contextlib
|
import contextlib
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
@ -189,6 +190,14 @@ class TicketModal(discord.ui.Modal, title="Nouveau Ticket"):
|
||||||
discord.ui.ActionRow(
|
discord.ui.ActionRow(
|
||||||
ClaimButton(),
|
ClaimButton(),
|
||||||
AddMemberButton(),
|
AddMemberButton(),
|
||||||
|
TransferButton(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
container.add_item(
|
||||||
|
discord.ui.ActionRow(
|
||||||
|
PriorityButton(),
|
||||||
|
LockButton(),
|
||||||
|
StatsButton(),
|
||||||
CloseButton(),
|
CloseButton(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -330,6 +339,210 @@ class AddMemberSelectView(discord.ui.View):
|
||||||
self.add_item(AddMemberSelect())
|
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):
|
class CloseButton(discord.ui.Button):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue