""" 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}"