Première version non terminer du nouveau système de ticket § ajout du système de boutons persistant (non tester)

This commit is contained in:
Mathis 2026-01-04 00:01:15 +01:00
parent 10173132a2
commit 93e2f9bb44
15 changed files with 3788 additions and 209 deletions

View file

@ -0,0 +1,453 @@
"""
Ticket Formatters
=================
Beautiful embed and message formatters for the ticket system.
"""
from datetime import datetime
from typing import Optional, List, Dict, Any
from discord import Embed, Color, User, Member, Role
from ..data.models import (
TicketData,
TicketCategory,
TicketStats,
GuildTicketConfig,
TicketStatus,
Priority
)
class TicketEmbedFormatter:
"""
Creates beautiful, consistent embeds for the ticket system.
Features:
- Color-coded by priority
- Consistent styling
- Rich information display
- Translatable text
"""
# Priority colors
PRIORITY_COLORS = {
Priority.LOW: Color.green(),
Priority.NORMAL: Color.blue(),
Priority.HIGH: Color.orange(),
Priority.URGENT: Color.red()
}
# Status emojis
STATUS_EMOJIS = {
TicketStatus.OPEN: "🟢",
TicketStatus.CLAIMED: "🟡",
TicketStatus.CLOSED: "🔴",
TicketStatus.ARCHIVED: ""
}
# Priority emojis
PRIORITY_EMOJIS = {
Priority.LOW: "🟢",
Priority.NORMAL: "🔵",
Priority.HIGH: "🟠",
Priority.URGENT: "🔴"
}
@classmethod
def get_priority_color(cls, priority: Priority) -> Color:
"""Get embed color based on priority"""
return cls.PRIORITY_COLORS.get(priority, Color.blue())
@classmethod
def format_status(cls, status: TicketStatus) -> str:
"""Get formatted status with emoji"""
emoji = cls.STATUS_EMOJIS.get(status, "")
status_names = {
TicketStatus.OPEN: "Ouvert",
TicketStatus.CLAIMED: "Réclamé",
TicketStatus.CLOSED: "Fermé",
TicketStatus.ARCHIVED: "Archivé"
}
return f"{emoji} {status_names.get(status, status.value)}"
@classmethod
def format_priority(cls, priority: Priority) -> str:
"""Get formatted priority with emoji"""
emoji = cls.PRIORITY_EMOJIS.get(priority, "")
priority_names = {
Priority.LOW: "Basse",
Priority.NORMAL: "Normale",
Priority.HIGH: "Haute",
Priority.URGENT: "Urgente"
}
return f"{emoji} {priority_names.get(priority, priority.value)}"
# --- Ticket Creation Embeds ---
@classmethod
def ticket_created(
cls,
ticket: TicketData,
user: User,
category: Optional[TicketCategory] = None,
guild_name: str = "Serveur"
) -> Embed:
"""Embed for when a ticket is created"""
embed = Embed(
title="🎫 Nouveau Ticket Créé",
description=category.welcome_message if category else "Merci d'avoir créé un ticket. Notre équipe va vous répondre sous peu.",
color=cls.get_priority_color(ticket.priority),
timestamp=datetime.now()
)
embed.set_author(name=user.display_name, icon_url=user.display_avatar.url)
# Add fields
embed.add_field(name="📌 Ticket ID", value=f"`{ticket.channel_id}`", inline=True)
embed.add_field(name="👤 Utilisateur", value=user.mention, inline=True)
embed.add_field(name="📁 Catégorie", value=category.name if category else ticket.category_id, inline=True)
embed.add_field(name="⚡ Priorité", value=cls.format_priority(ticket.priority), inline=True)
if ticket.reason:
embed.add_field(name="📝 Raison", value=ticket.reason, inline=False)
embed.set_footer(text=f"ID: {ticket.channel_id} | {guild_name}")
return embed
@classmethod
def ticket_closed(
cls,
ticket: TicketData,
closed_by: User,
duration_minutes: float,
reason: Optional[str] = None
) -> Embed:
"""Embed for when a ticket is closed"""
embed = Embed(
title="🔴 Ticket Fermé",
description=f"Ce ticket a été fermé par {closed_by.mention}",
color=Color.red(),
timestamp=datetime.now()
)
embed.add_field(name="👤 Fermé par", value=closed_by.mention, inline=True)
embed.add_field(name="⏱️ Durée", value=cls._format_duration(duration_minutes), inline=True)
embed.add_field(name="📊 Statut", value=cls.format_status(ticket.status), inline=True)
if reason:
embed.add_field(name="📝 Raison de fermeture", value=reason, inline=False)
return embed
@classmethod
def ticket_reopened(cls, ticket: TicketData, reopened_by: User) -> Embed:
"""Embed for when a ticket is reopened"""
embed = Embed(
title="🟢 Ticket Rouvert",
description=f"Ce ticket a été rouvert par {reopened_by.mention}",
color=Color.green(),
timestamp=datetime.now()
)
embed.add_field(name="👤 Rouvert par", value=reopened_by.mention, inline=True)
embed.add_field(name="📊 Statut", value=cls.format_status(ticket.status), inline=True)
return embed
@classmethod
def ticket_claimed(cls, ticket: TicketData, claimed_by: User) -> Embed:
"""Embed for when a ticket is claimed"""
embed = Embed(
title="🟡 Ticket Réclamé",
description=f"Ce ticket a été réclamé par {claimed_by.mention}",
color=Color.gold(),
timestamp=datetime.now()
)
embed.add_field(name="👤 Réclamé par", value=claimed_by.mention, inline=True)
embed.add_field(name="📊 Statut", value=cls.format_status(ticket.status), inline=True)
return embed
@classmethod
def ticket_unclaimed(cls, ticket: TicketData, unclaimed_by: User) -> Embed:
"""Embed for when a ticket claim is removed"""
embed = Embed(
title="🟠 Ticket Non Réclamé",
description=f"Le ticket n'est plus réclamé par {unclaimed_by.mention}",
color=Color.orange(),
timestamp=datetime.now()
)
return embed
# --- Panel Embeds ---
@classmethod
def panel_embed(
cls,
config: GuildTicketConfig,
guild_name: str
) -> Embed:
"""Embed for the ticket creation panel"""
embed = Embed(
title="🎫 Support - Créer un Ticket",
description="Cliquez sur le bouton ci-dessous pour créer un ticket. Notre équipe vous répondra dans les plus brefs délais.",
color=Color.blue(),
timestamp=datetime.now()
)
# Add categories
if config.categories:
categories_text = "\n".join(
f"{cat.emoji} **{cat.name}**\n{cat.description}"
for cat in config.categories
)
embed.add_field(name="📁 Catégories disponibles", value=categories_text, inline=False)
embed.set_footer(text=f"ID: {config.guild_id} | {guild_name}")
return embed
@classmethod
def category_select_embed(cls, categories: List[TicketCategory]) -> Embed:
"""Embed shown when selecting a category"""
embed = Embed(
title="📁 Choisir une catégorie",
description="Sélectionnez la catégorie qui correspond le mieux à votre demande:",
color=Color.blue(),
timestamp=datetime.now()
)
for cat in categories:
embed.add_field(
name=f"{cat.emoji} {cat.name}",
value=cat.description or "Aucune description",
inline=False
)
return embed
# --- Admin Embeds ---
@classmethod
def ticket_list_embed(
cls,
tickets: List[TicketData],
user: Optional[User] = None
) -> Embed:
"""Embed showing a list of tickets"""
embed = Embed(
title="📋 Liste des Tickets",
description=f"Nombre de tickets: **{len(tickets)}**",
color=Color.blue()
)
if user:
embed.set_author(name=f"Tickets de {user.display_name}", icon_url=user.display_avatar.url)
# Group by status
by_status = {}
for ticket in tickets:
status = cls.format_status(ticket.status)
by_status.setdefault(status, []).append(ticket)
for status, status_tickets in by_status.items():
ticket_list = "\n".join(
f"`{t.channel_id}` - {t.category_id} ({cls.format_priority(t.priority)})"
for t in status_tickets[:10] # Limit to 10 per status
)
if len(status_tickets) > 10:
ticket_list += f"\n... et {len(status_tickets) - 10} autres"
embed.add_field(name=status, value=ticket_list or "Aucun", inline=False)
return embed
@classmethod
def stats_embed(cls, stats: TicketStats, guild_name: str) -> Embed:
"""Embed showing ticket statistics"""
embed = Embed(
title="📊 Statistiques des Tickets",
color=Color.blue(),
timestamp=datetime.now()
)
embed.set_author(name=guild_name)
# Main stats
embed.add_field(name="📝 Total", value=str(stats.total_tickets), inline=True)
embed.add_field(name="🟢 Ouverts", value=str(stats.open_tickets), inline=True)
embed.add_field(name="🔴 Fermés", value=str(stats.closed_tickets), inline=True)
embed.add_field(name="⏱️ Durée Moyenne", value=cls._format_duration(stats.average_duration_minutes), inline=True)
embed.add_field(name="⭐ Note Moyenne", value=f"{stats.average_rating:.1f}/5" if stats.average_rating else "N/A", inline=True)
# By category
if stats.tickets_by_category:
cat_text = "\n".join(f"{cat}: **{count}**" for cat, count in stats.tickets_by_category.items())
embed.add_field(name="📁 Par Catégorie", value=cat_text, inline=False)
# By priority
if stats.tickets_by_priority:
priority_text = "\n".join(f"{prio}: **{count}**" for prio, count in stats.tickets_by_priority.items())
embed.add_field(name="⚡ Par Priorité", value=priority_text, inline=False)
return embed
@classmethod
def ticket_info_embed(
cls,
ticket: TicketData,
user: Optional[User] = None,
category: Optional[TicketCategory] = None
) -> Embed:
"""Embed showing detailed ticket information"""
embed = Embed(
title=f"🎫 Informations du Ticket",
color=cls.get_priority_color(ticket.priority),
timestamp=datetime.now()
)
if user:
embed.set_author(name=user.display_name, icon_url=user.display_avatar.url)
embed.add_field(name="📌 ID", value=f"`{ticket.channel_id}`", inline=True)
embed.add_field(name="📊 Statut", value=cls.format_status(ticket.status), inline=True)
embed.add_field(name="⚡ Priorité", value=cls.format_priority(ticket.priority), inline=True)
embed.add_field(name="📁 Catégorie", value=category.name if category else ticket.category_id, inline=True)
embed.add_field(name="📅 Créé le", value=ticket.created_at.strftime("%d/%m/%Y à %H:%M"), inline=True)
if ticket.closed_at:
embed.add_field(name="🔴 Fermé le", value=ticket.closed_at.strftime("%d/%m/%Y à %H:%M"), inline=True)
if ticket.claimed_by:
embed.add_field(name="👤 Réclamé par", value=f"<@{ticket.claimed_by}>", inline=True)
if ticket.reason:
embed.add_field(name="📝 Raison", value=ticket.reason, inline=False)
if ticket.duration_minutes:
embed.add_field(name="⏱️ Durée", value=cls._format_duration(ticket.duration_minutes), inline=True)
if ticket.rating:
stars = "" * ticket.rating
embed.add_field(name="⭐ Note", value=f"{stars} ({ticket.rating}/5)", inline=True)
return embed
# --- Survey Embeds ---
@classmethod
def survey_embed(cls) -> Embed:
"""Embed for satisfaction survey"""
embed = Embed(
title="⭐ Évaluation du Support",
description="Merci d'avoir utilisé notre système de tickets! "
"Pour améliorer nos services, merci de noter votre expérience.",
color=Color.gold(),
timestamp=datetime.now()
)
embed.add_field(name="1 ⭐", value="Très insatisfait", inline=True)
embed.add_field(name="3 ⭐", value="Neutre", inline=True)
embed.add_field(name="5 ⭐", value="Très satisfait", inline=True)
return embed
@classmethod
def survey_thanks_embed(cls, rating: int) -> Embed:
"""Embed shown after submitting a survey"""
stars = "" * rating
embed = Embed(
title="Merci pour votre évaluation!",
description=f"Vous avez attribué: {stars}",
color=Color.green()
)
return embed
# --- Helper Methods ---
@staticmethod
def _format_duration(minutes: float) -> str:
"""Format duration in minutes to human-readable string"""
if minutes < 1:
return f"{minutes * 60:.0f}s"
elif minutes < 60:
return f"{minutes:.0f}min"
elif minutes < 1440:
hours = minutes / 60
return f"{hours:.1f}h"
else:
days = minutes / 1440
return f"{days:.1f}j"
class TicketMessageFormatter:
"""Formats regular messages for the ticket system"""
@staticmethod
def welcome_message(
user: User,
category: TicketCategory
) -> str:
"""Welcome message when entering a ticket"""
return (
f"👋 Bienvenue {user.mention} dans votre ticket **{category.name}**!\n\n"
f"{category.welcome_message}\n\n"
f"Un membre de notre équipe va vous répondre sous peu."
)
@staticmethod
def staff_notification(
claimed_by: User,
ticket_channel_mention: str
) -> str:
"""Notification when a ticket is claimed"""
return (
f"🟡 Le ticket {ticket_channel_mention} a été réclamé par {claimed_by.mention}"
)
@staticmethod
def ticket_closed_notification(
closed_by: User,
reason: Optional[str] = None
) -> str:
"""Notification when a ticket is closed"""
msg = f"🔴 Ce ticket a été fermé par {closed_by.mention}"
if reason:
msg += f"\n📝 Raison: {reason}"
return msg
@staticmethod
def auto_close_warning(
days_remaining: int,
ticket_channel_mention: str
) -> str:
"""Warning before auto-closing a ticket"""
return (
f"⚠️ Ce ticket {ticket_channel_mention} sera fermé automatiquement "
f"dans **{days_remaining} jour(s)** en raison de son inactivity."
)
@staticmethod
def transcript_saved(filepath: str) -> str:
"""Message when transcript is saved"""
return f"📄 Transcript sauvegardé: `{filepath}`"
@staticmethod
def no_permission(action: str) -> str:
"""Message when user has no permission"""
return f"❌ Vous n'avez pas la permission de {action}"
@staticmethod
def ticket_limit_reached(limit: int) -> str:
"""Message when user has reached ticket limit"""
return f"❌ Vous avez atteint la limite de {limit} tickets ouverts"
@staticmethod
def already_has_ticket(channel_mention: str) -> str:
"""Message when user already has a ticket"""
return f"❌ Vous avez déjà un ticket ouvert: {channel_mention}"