Fix du système de permissions
This commit is contained in:
parent
dfec6b2418
commit
c330b9a1cc
48 changed files with 4229 additions and 243 deletions
16
bot.py
16
bot.py
|
|
@ -1,4 +1,6 @@
|
||||||
import discord
|
import discord
|
||||||
|
from commandes.ticket.utils.permissions import can_access_config
|
||||||
|
from commandes.ticket.data.storage import get_storage
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
import os
|
import os
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
@ -78,6 +80,19 @@ class MyBot(commands.Bot):
|
||||||
if await self.is_owner(ctx.author):
|
if await self.is_owner(ctx.author):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# 1b. Bypass pour les commandes de config tickets
|
||||||
|
# Si la commande est une commande ticket et l'utilisateur a les perms ticket
|
||||||
|
if ctx.command and (ctx.command.name in ["ticketconfig", "ticketsetup", "configticket"]):
|
||||||
|
try:
|
||||||
|
storage = get_storage()
|
||||||
|
config = storage.load_config(ctx.guild.id)
|
||||||
|
if can_access_config(ctx, config):
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
kuby_logger.error(f"Error checking ticket permissions in global whitelist: {e}")
|
||||||
|
# Fallthrough to normal whitelist check if error
|
||||||
|
|
||||||
|
|
||||||
# 2. Récupération du Cog
|
# 2. Récupération du Cog
|
||||||
whitelist_cog = self.get_cog("WhitelistMonitor")
|
whitelist_cog = self.get_cog("WhitelistMonitor")
|
||||||
if not whitelist_cog:
|
if not whitelist_cog:
|
||||||
|
|
@ -190,4 +205,3 @@ class MyBot(commands.Bot):
|
||||||
kuby_logger.error(f"Command error in {ctx.command}: {str(error)}", exc_info=True)
|
kuby_logger.error(f"Command error in {ctx.command}: {str(error)}", exc_info=True)
|
||||||
|
|
||||||
bot = MyBot()
|
bot = MyBot()
|
||||||
bot.run(os.getenv("DISCORD_TOKEN"))
|
|
||||||
67
commandes/ticket/TODO_CONFIRM_DELAY.md
Normal file
67
commandes/ticket/TODO_CONFIRM_DELAY.md
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
# Ticket Close Confirmation and Delay - Implementation Plan
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
Add confirmation dialog and waiting delay before ticket closure.
|
||||||
|
|
||||||
|
## Changes to make
|
||||||
|
|
||||||
|
### 1. Create `CloseConfirmationView` class
|
||||||
|
- Yes/No confirmation buttons
|
||||||
|
- Cancel option
|
||||||
|
- Shows before closing
|
||||||
|
|
||||||
|
### 2. Create `CloseDelayView` class
|
||||||
|
- Countdown display (5 seconds)
|
||||||
|
- Progress steps:
|
||||||
|
- 📄 Génération du transcript...
|
||||||
|
- 📝 Sauvegarde des données...
|
||||||
|
- 🗑️ Suppression du canal...
|
||||||
|
- Cancel option during delay
|
||||||
|
|
||||||
|
### 3. Modify `TicketManagementView._close_callback`
|
||||||
|
- Show confirmation view first
|
||||||
|
- After Yes, show delay view with countdown
|
||||||
|
- After delay completes, proceed with closing
|
||||||
|
|
||||||
|
## Files to modify
|
||||||
|
- `commandes/ticket/__init__.py`
|
||||||
|
|
||||||
|
## Status
|
||||||
|
- [x] Add CloseConfirmationView class ✅
|
||||||
|
- [x] Add CloseDelayView class ✅
|
||||||
|
- [x] Modify TicketManagementView._close_callback ✅
|
||||||
|
- [x] Add cancel button to CloseDelayView ✅
|
||||||
|
- [x] Test the flow ✅
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### CloseConfirmationView
|
||||||
|
```python
|
||||||
|
class CloseConfirmationView(discord.ui.View):
|
||||||
|
"""Confirmation dialog before closing ticket"""
|
||||||
|
|
||||||
|
def __init__(self, bot, storage, config, ticket):
|
||||||
|
super().__init__(timeout=60)
|
||||||
|
# Yes button (green)
|
||||||
|
# No button (red) - cancels and removes message
|
||||||
|
```
|
||||||
|
|
||||||
|
### CloseDelayView
|
||||||
|
```python
|
||||||
|
class CloseDelayView(discord.ui.View):
|
||||||
|
"""Countdown and progress before ticket deletion"""
|
||||||
|
|
||||||
|
def __init__(self, bot, storage, config, ticket, close_reason):
|
||||||
|
super().__init__(timeout=None)
|
||||||
|
# Disable all buttons during countdown
|
||||||
|
# Update embed with countdown
|
||||||
|
# After delay, call actual close function
|
||||||
|
```
|
||||||
|
|
||||||
|
## Flow
|
||||||
|
1. User clicks "Fermer"
|
||||||
|
2. Show confirmation embed with Yes/No buttons
|
||||||
|
3. If Yes → Show delay view with countdown
|
||||||
|
4. After delay → Close ticket and delete channel
|
||||||
|
5. If No or timeout → Remove confirmation message
|
||||||
|
|
||||||
23
commandes/ticket/TODO_FIX_WHITELIST.md
Normal file
23
commandes/ticket/TODO_FIX_WHITELIST.md
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# TODO - Correction du système de tickets pour les rôles staff/config
|
||||||
|
|
||||||
|
## Problème
|
||||||
|
Les utilisateurs avec les rôles staff et config ne peuvent pas interagir avec le système de tickets.
|
||||||
|
|
||||||
|
## Cause
|
||||||
|
La méthode `_is_whitelisted` retourne `False` quand le cog WhitelistMonitor n'est pas disponible, les utilisateurs bloquant même staff/config.
|
||||||
|
|
||||||
|
## Plan de correction
|
||||||
|
|
||||||
|
### Étape 1: Corriger TicketPanelView._is_whitelisted()
|
||||||
|
- [ ] Modifier pour que les rôles staff/config soient toujours autorisés
|
||||||
|
|
||||||
|
### Étape 2: Corriger CategorySelectionView._is_whitelisted()
|
||||||
|
- [ ] Modifier pour que les rôles staff/config soient toujours autorisés
|
||||||
|
|
||||||
|
### Étape 3: Vérifier les autres vues
|
||||||
|
- [ ] Vérifier TicketManagementView._is_staff()
|
||||||
|
|
||||||
|
## État d'avancement
|
||||||
|
- [x] TicketPanelView._is_whitelisted() corrigé
|
||||||
|
- [x] CategorySelectionView._is_whitelisted() corrigé
|
||||||
|
- [x] Tests de vérification
|
||||||
|
|
@ -12,6 +12,7 @@ Fonctionnalités:
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import io
|
import io
|
||||||
|
import asyncio
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
@ -21,6 +22,7 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspa
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands, tasks
|
from discord.ext import commands, tasks
|
||||||
|
import traceback
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
from .data.storage import get_storage
|
from .data.storage import get_storage
|
||||||
|
|
@ -34,6 +36,8 @@ from .data.models import (
|
||||||
)
|
)
|
||||||
from .utils.formatters import TicketEmbedFormatter
|
from .utils.formatters import TicketEmbedFormatter
|
||||||
from .staff_analytics import StaffAnalytics, StaffStatsView
|
from .staff_analytics import StaffAnalytics, StaffStatsView
|
||||||
|
from .utils.permissions import is_staff, is_whitelisted, can_access_config
|
||||||
|
from src.logger import kuby_logger
|
||||||
|
|
||||||
|
|
||||||
class TicketPanelView(discord.ui.View):
|
class TicketPanelView(discord.ui.View):
|
||||||
|
|
@ -93,6 +97,9 @@ class TicketPanelView(discord.ui.View):
|
||||||
|
|
||||||
async def _create_callback(self, interaction: discord.Interaction):
|
async def _create_callback(self, interaction: discord.Interaction):
|
||||||
"""Affiche les catégories pour créer un ticket"""
|
"""Affiche les catégories pour créer un ticket"""
|
||||||
|
# Reload config to ensure we have latest permissions/categories
|
||||||
|
self.config = self.storage.load_config(interaction.guild_id)
|
||||||
|
|
||||||
if not self._is_whitelisted(interaction):
|
if not self._is_whitelisted(interaction):
|
||||||
await interaction.response.send_message("❌ Vous n'êtes pas autorisé à utiliser cette fonctionnalité. Veuillez demander à être ajouté à la whitelist.", ephemeral=True)
|
await interaction.response.send_message("❌ Vous n'êtes pas autorisé à utiliser cette fonctionnalité. Veuillez demander à être ajouté à la whitelist.", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
@ -125,6 +132,9 @@ class TicketPanelView(discord.ui.View):
|
||||||
|
|
||||||
async def _my_tickets_callback(self, interaction: discord.Interaction):
|
async def _my_tickets_callback(self, interaction: discord.Interaction):
|
||||||
"""Affiche les tickets de l'utilisateur"""
|
"""Affiche les tickets de l'utilisateur"""
|
||||||
|
# Reload config
|
||||||
|
self.config = self.storage.load_config(interaction.guild_id)
|
||||||
|
|
||||||
if not self._is_whitelisted(interaction):
|
if not self._is_whitelisted(interaction):
|
||||||
await interaction.response.send_message("❌ Vous n'êtes pas autorisé à utiliser cette fonctionnalité. Veuillez demander à être ajouté à la whitelist.", ephemeral=True)
|
await interaction.response.send_message("❌ Vous n'êtes pas autorisé à utiliser cette fonctionnalité. Veuillez demander à être ajouté à la whitelist.", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
@ -155,6 +165,9 @@ class TicketPanelView(discord.ui.View):
|
||||||
|
|
||||||
async def _analytics_callback(self, interaction: discord.Interaction):
|
async def _analytics_callback(self, interaction: discord.Interaction):
|
||||||
"""Affiche les analytics (staff uniquement)"""
|
"""Affiche les analytics (staff uniquement)"""
|
||||||
|
# Reload config
|
||||||
|
self.config = self.storage.load_config(interaction.guild_id)
|
||||||
|
|
||||||
if not self._is_staff(interaction):
|
if not self._is_staff(interaction):
|
||||||
await interaction.response.send_message("❌ Cette fonctionnalité est réservée au staff.", ephemeral=True)
|
await interaction.response.send_message("❌ Cette fonctionnalité est réservée au staff.", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
@ -233,6 +246,9 @@ class TicketPanelView(discord.ui.View):
|
||||||
|
|
||||||
async def _config_callback(self, interaction: discord.Interaction):
|
async def _config_callback(self, interaction: discord.Interaction):
|
||||||
"""Ouvre le panel de configuration (admin ou rôles config uniquement)"""
|
"""Ouvre le panel de configuration (admin ou rôles config uniquement)"""
|
||||||
|
# Reload config
|
||||||
|
self.config = self.storage.load_config(interaction.guild_id)
|
||||||
|
|
||||||
if not self._is_whitelisted(interaction):
|
if not self._is_whitelisted(interaction):
|
||||||
await interaction.response.send_message("❌ Vous n'êtes pas autorisé à utiliser cette fonctionnalité. Veuillez demander à être ajouté à la whitelist.", ephemeral=True)
|
await interaction.response.send_message("❌ Vous n'êtes pas autorisé à utiliser cette fonctionnalité. Veuillez demander à être ajouté à la whitelist.", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
@ -253,53 +269,13 @@ class TicketPanelView(discord.ui.View):
|
||||||
|
|
||||||
|
|
||||||
def _is_staff(self, interaction: discord.Interaction) -> bool:
|
def _is_staff(self, interaction: discord.Interaction) -> bool:
|
||||||
"""Vérifie si l'utilisateur est staff"""
|
return is_staff(interaction, self.config)
|
||||||
# Admin peut toujours accéder
|
|
||||||
if interaction.user.guild_permissions.administrator:
|
|
||||||
return True
|
|
||||||
|
|
||||||
guild = interaction.guild
|
|
||||||
user = interaction.user
|
|
||||||
|
|
||||||
for role_id in self.config.staff_roles:
|
|
||||||
role = guild.get_role(int(role_id))
|
|
||||||
if role and role in user.roles:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _is_admin(self, interaction: discord.Interaction) -> bool:
|
|
||||||
"""Vérifie si l'utilisateur est administrateur"""
|
|
||||||
return interaction.user.guild_permissions.administrator
|
|
||||||
|
|
||||||
def _can_access_config(self, interaction: discord.Interaction) -> bool:
|
def _can_access_config(self, interaction: discord.Interaction) -> bool:
|
||||||
"""Vérifie si l'utilisateur peut accéder à la configuration"""
|
return can_access_config(interaction, self.config)
|
||||||
# Admin peut toujours accéder
|
|
||||||
if interaction.user.guild_permissions.administrator:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Vérifier les rôles staff
|
|
||||||
guild = interaction.guild
|
|
||||||
user = interaction.user
|
|
||||||
for role_id in self.config.staff_roles:
|
|
||||||
role = guild.get_role(int(role_id))
|
|
||||||
if role and role in user.roles:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _is_whitelisted(self, interaction: discord.Interaction) -> bool:
|
def _is_whitelisted(self, interaction: discord.Interaction) -> bool:
|
||||||
"""Vérifie si l'utilisateur est whitelisté ou a les permissions staff/config"""
|
return is_whitelisted(interaction, self.config, self.bot)
|
||||||
# Staff members and config roles can always access
|
|
||||||
if self._is_staff(interaction) or self._can_access_config(interaction):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Get whitelist monitor cog
|
|
||||||
whitelist_cog = self.bot.get_cog("WhitelistMonitor")
|
|
||||||
if not whitelist_cog:
|
|
||||||
return False # Deny if whitelist system not available
|
|
||||||
|
|
||||||
return whitelist_cog.is_whitelisted(interaction.guild.id, interaction.user.id)
|
|
||||||
|
|
||||||
def _calculate_stats(self, guild_id: int) -> TicketStats:
|
def _calculate_stats(self, guild_id: int) -> TicketStats:
|
||||||
"""Calcule les statistiques"""
|
"""Calcule les statistiques"""
|
||||||
|
|
@ -340,121 +316,46 @@ class TicketPanelView(discord.ui.View):
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryButton(discord.ui.Button):
|
||||||
|
def __init__(self, category, view_instance):
|
||||||
|
super().__init__(
|
||||||
|
style=discord.ButtonStyle.blurple,
|
||||||
|
label=f"{category.emoji} {category.name}",
|
||||||
|
custom_id=f"cat_{category.id}_{view_instance.id_gen}"
|
||||||
|
)
|
||||||
|
self.category = category
|
||||||
|
self.view_instance = view_instance
|
||||||
|
# We need unique custom_ids even for ephemeral to avoid any weird collisions/caching
|
||||||
|
|
||||||
|
async def callback(self, interaction: discord.Interaction):
|
||||||
|
await self.view_instance._on_category_select(interaction, self.category.id)
|
||||||
|
|
||||||
|
|
||||||
class CategorySelectionView(discord.ui.View):
|
class CategorySelectionView(discord.ui.View):
|
||||||
"""Sélection de catégorie pour créer un ticket"""
|
"""Sélection de catégorie pour créer un ticket"""
|
||||||
|
|
||||||
def __init__(self, bot, config: GuildTicketConfig):
|
def __init__(self, bot, config: GuildTicketConfig):
|
||||||
super().__init__(timeout=None)
|
super().__init__(timeout=300) # 5 minutes timeout for ephemeral menu
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.config = config
|
self.config = config
|
||||||
|
self.id_gen = int(datetime.now().timestamp())
|
||||||
|
|
||||||
# Ajouter boutons de catégories
|
# Ajouter boutons de catégories
|
||||||
for category in config.categories[:5]:
|
for category in config.categories[:5]:
|
||||||
btn = discord.ui.Button(
|
self.add_item(CategoryButton(category, self))
|
||||||
style=discord.ButtonStyle.blurple,
|
|
||||||
label=f"{category.emoji} {category.name}",
|
|
||||||
custom_id=f"cat_{category.id}"
|
|
||||||
)
|
|
||||||
btn.callback = self._category_callback(category.id)
|
|
||||||
self.add_item(btn)
|
|
||||||
|
|
||||||
def _is_staff(self, interaction: discord.Interaction) -> bool:
|
def _is_staff(self, interaction: discord.Interaction) -> bool:
|
||||||
"""Vérifie si l'utilisateur est staff"""
|
return is_staff(interaction, self.config)
|
||||||
# Admin peut toujours accéder
|
|
||||||
if interaction.user.guild_permissions.administrator:
|
|
||||||
return True
|
|
||||||
|
|
||||||
guild = interaction.guild
|
|
||||||
user = interaction.user
|
|
||||||
|
|
||||||
for role_id in self.config.staff_roles:
|
|
||||||
role = guild.get_role(int(role_id))
|
|
||||||
if role and role in user.roles:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _can_access_config(self, interaction: discord.Interaction) -> bool:
|
def _can_access_config(self, interaction: discord.Interaction) -> bool:
|
||||||
"""Vérifie si l'utilisateur peut accéder à la configuration"""
|
return can_access_config(interaction, self.config)
|
||||||
# Admin peut toujours accéder
|
|
||||||
if interaction.user.guild_permissions.administrator:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Vérifier les rôles staff
|
|
||||||
guild = interaction.guild
|
|
||||||
user = interaction.user
|
|
||||||
for role_id in self.config.staff_roles:
|
|
||||||
role = guild.get_role(int(role_id))
|
|
||||||
if role and role in user.roles:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _is_whitelisted(self, interaction: discord.Interaction) -> bool:
|
def _is_whitelisted(self, interaction: discord.Interaction) -> bool:
|
||||||
"""Vérifie si l'utilisateur est whitelisté ou a les permissions staff/config"""
|
return is_whitelisted(interaction, self.config, self.bot)
|
||||||
# Staff members and config roles can always access
|
|
||||||
if self._is_staff(interaction) or self._can_access_config(interaction):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Get whitelist monitor cog
|
|
||||||
whitelist_cog = self.bot.get_cog("WhitelistMonitor")
|
|
||||||
if not whitelist_cog:
|
|
||||||
return False # Deny if whitelist system not available
|
|
||||||
|
|
||||||
return whitelist_cog.is_whitelisted(interaction.guild.id, interaction.user.id)
|
|
||||||
|
|
||||||
def _is_staff(self, interaction: discord.Interaction) -> bool:
|
|
||||||
"""Vérifie si l'utilisateur est staff"""
|
|
||||||
# Admin peut toujours accéder
|
|
||||||
if interaction.user.guild_permissions.administrator:
|
|
||||||
return True
|
|
||||||
|
|
||||||
guild = interaction.guild
|
|
||||||
user = interaction.user
|
|
||||||
|
|
||||||
for role_id in self.config.staff_roles:
|
|
||||||
role = guild.get_role(int(role_id))
|
|
||||||
if role and role in user.roles:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _can_access_config(self, interaction: discord.Interaction) -> bool:
|
|
||||||
"""Vérifie si l'utilisateur peut accéder à la configuration"""
|
|
||||||
# Admin peut toujours accéder
|
|
||||||
if interaction.user.guild_permissions.administrator:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Vérifier les rôles staff
|
|
||||||
guild = interaction.guild
|
|
||||||
user = interaction.user
|
|
||||||
for role_id in self.config.staff_roles:
|
|
||||||
role = guild.get_role(int(role_id))
|
|
||||||
if role and role in user.roles:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _is_whitelisted(self, interaction: discord.Interaction) -> bool:
|
|
||||||
"""Vérifie si l'utilisateur est whitelisté ou a les permissions staff/config"""
|
|
||||||
# Staff members and config roles can always access
|
|
||||||
if self._is_staff(interaction) or self._can_access_config(interaction):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Get whitelist monitor cog
|
|
||||||
whitelist_cog = self.bot.get_cog("WhitelistMonitor")
|
|
||||||
if not whitelist_cog:
|
|
||||||
return False # Deny if whitelist system not available
|
|
||||||
|
|
||||||
return whitelist_cog.is_whitelisted(interaction.guild.id, interaction.user.id)
|
|
||||||
|
|
||||||
def _category_callback(self, category_id: str):
|
|
||||||
async def callback(interaction: discord.Interaction):
|
|
||||||
await self._on_category_select(interaction, category_id)
|
|
||||||
return callback
|
|
||||||
|
|
||||||
async def _on_category_select(self, interaction: discord.Interaction, category_id: str):
|
async def _on_category_select(self, interaction: discord.Interaction, category_id: str):
|
||||||
"""Handle category selection"""
|
"""Handle category selection"""
|
||||||
|
try:
|
||||||
if not self._is_whitelisted(interaction):
|
if not self._is_whitelisted(interaction):
|
||||||
await interaction.response.send_message("❌ Vous n'êtes pas autorisé à utiliser cette fonctionnalité. Veuillez demander à être ajouté à la whitelist.", ephemeral=True)
|
await interaction.response.send_message("❌ Vous n'êtes pas autorisé à utiliser cette fonctionnalité. Veuillez demander à être ajouté à la whitelist.", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
@ -479,6 +380,13 @@ class CategorySelectionView(discord.ui.View):
|
||||||
# Show priority modal
|
# Show priority modal
|
||||||
modal = TicketPriorityModal(self.bot, self.config, category)
|
modal = TicketPriorityModal(self.bot, self.config, category)
|
||||||
await interaction.response.send_modal(modal)
|
await interaction.response.send_modal(modal)
|
||||||
|
except Exception as e:
|
||||||
|
kuby_logger.error(f"[CategorySelection] Error: {e}", exc_info=True)
|
||||||
|
try:
|
||||||
|
if not interaction.response.is_done():
|
||||||
|
await interaction.response.send_message(f"❌ Erreur interne: {e}", ephemeral=True)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TicketPriorityModal(discord.ui.Modal):
|
class TicketPriorityModal(discord.ui.Modal):
|
||||||
|
|
@ -598,7 +506,18 @@ class TicketPriorityModal(discord.ui.Modal):
|
||||||
|
|
||||||
# Add management buttons
|
# Add management buttons
|
||||||
view = TicketManagementView(self.bot, storage, self.config, ticket_data)
|
view = TicketManagementView(self.bot, storage, self.config, ticket_data)
|
||||||
await ticket_channel.send(content=user.mention, embed=embed, view=view)
|
# Prepare mentions
|
||||||
|
mentions = [user.mention]
|
||||||
|
for role_id in self.config.staff_roles:
|
||||||
|
mentions.append(f"<@&{role_id}>")
|
||||||
|
for user_id in self.config.staff_users:
|
||||||
|
mentions.append(f"<@{user_id}>")
|
||||||
|
|
||||||
|
mention_content = " ".join(mentions)
|
||||||
|
|
||||||
|
# Add management buttons
|
||||||
|
view = TicketManagementView(self.bot, storage, self.config, ticket_data)
|
||||||
|
await ticket_channel.send(content=mention_content, embed=embed, view=view)
|
||||||
|
|
||||||
# Log
|
# Log
|
||||||
if self.config.log_channel_id:
|
if self.config.log_channel_id:
|
||||||
|
|
@ -667,9 +586,38 @@ class TicketManagementView(discord.ui.View):
|
||||||
self.add_item(reopen_btn)
|
self.add_item(reopen_btn)
|
||||||
|
|
||||||
async def _close_callback(self, interaction: discord.Interaction):
|
async def _close_callback(self, interaction: discord.Interaction):
|
||||||
"""Ferme le ticket"""
|
"""Demande confirmation avant de fermer le ticket"""
|
||||||
modal = CloseTicketModal(self.bot, self.storage, self.config, self.ticket)
|
# Vérifier si l'utilisateur peut fermer le ticket
|
||||||
await interaction.response.send_modal(modal)
|
can_close = (
|
||||||
|
interaction.user.id == self.ticket.user_id or
|
||||||
|
self._is_staff(interaction)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not can_close:
|
||||||
|
await interaction.response.send_message("Vous ne pouvez pas fermer ce ticket.", ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Créer l'embed de confirmation
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="🔒 Confirmer la Fermeture",
|
||||||
|
description="Êtes-vous sûr de vouloir fermer ce ticket?\n\n"
|
||||||
|
"Cette action est irréversible et le salon sera supprimé.",
|
||||||
|
color=discord.Color.orange()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ajouter les informations du ticket
|
||||||
|
category = self.config.get_category(self.ticket.category_id)
|
||||||
|
duration = self.ticket.duration_minutes
|
||||||
|
|
||||||
|
embed.add_field(name="📁 Catégorie", value=category.name if category else "N/A", inline=True)
|
||||||
|
embed.add_field(name="⏱️ Durée", value=f"{duration:.1f} min" if duration else "N/A", inline=True)
|
||||||
|
embed.add_field(name="📊 Statut", value=self.ticket.status.value.capitalize(), inline=True)
|
||||||
|
|
||||||
|
embed.set_footer(text="Cliquez sur Confirmer pour fermer ou Annuler pour annuler l'opération")
|
||||||
|
|
||||||
|
# Afficher la vue de confirmation
|
||||||
|
view = CloseConfirmationView(self.bot, self.storage, self.config, self.ticket)
|
||||||
|
await interaction.response.send_message(embed=embed, view=view, ephemeral=True)
|
||||||
|
|
||||||
async def _transcript_callback(self, interaction: discord.Interaction):
|
async def _transcript_callback(self, interaction: discord.Interaction):
|
||||||
"""Génère le transcript"""
|
"""Génère le transcript"""
|
||||||
|
|
@ -778,6 +726,215 @@ class TicketManagementView(discord.ui.View):
|
||||||
await interaction.channel.send(embed=embed)
|
await interaction.channel.send(embed=embed)
|
||||||
await interaction.response.send_message("Ticket rouvert!", ephemeral=True)
|
await interaction.response.send_message("Ticket rouvert!", ephemeral=True)
|
||||||
|
|
||||||
|
def _is_staff(self, interaction: discord.Interaction) -> bool:
|
||||||
|
return is_staff(interaction, self.config)
|
||||||
|
|
||||||
|
def _is_staff_member(self, member) -> bool:
|
||||||
|
# Create a fake interaction-like check or manual check
|
||||||
|
if member.guild_permissions.administrator:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if member.id in self.config.staff_users:
|
||||||
|
return True
|
||||||
|
|
||||||
|
for role_id in self.config.staff_roles:
|
||||||
|
role = member.guild.get_role(int(role_id))
|
||||||
|
if role and role in member.roles:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class CloseConfirmationView(discord.ui.View):
|
||||||
|
"""Demande de confirmation avant la fermeture du ticket"""
|
||||||
|
|
||||||
|
def __init__(self, bot, storage, config: GuildTicketConfig, ticket: TicketData):
|
||||||
|
super().__init__(timeout=60)
|
||||||
|
self.bot = bot
|
||||||
|
self.storage = storage
|
||||||
|
self.config = config
|
||||||
|
self.ticket = ticket
|
||||||
|
|
||||||
|
@discord.ui.button(label="✅ Confirmer", style=discord.ButtonStyle.green, custom_id="close_confirm")
|
||||||
|
async def confirm_callback(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
"""L'utilisateur confirme la fermeture"""
|
||||||
|
# Vérifier les permissions
|
||||||
|
can_close = (
|
||||||
|
interaction.user.id == self.ticket.user_id or
|
||||||
|
self._is_staff(interaction)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not can_close:
|
||||||
|
await interaction.response.send_message("Vous ne pouvez pas fermer ce ticket.", ephemeral=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Show the delay view with countdown
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="⏳ Fermeture du Ticket",
|
||||||
|
description="Le ticket va être fermé dans quelques secondes...\n\n"
|
||||||
|
"📄 Génération du transcript en cours...\n"
|
||||||
|
"📝 Envoi aux logs...\n"
|
||||||
|
"🗑️ Suppression du canal...",
|
||||||
|
color=discord.Color.orange()
|
||||||
|
)
|
||||||
|
|
||||||
|
view = CloseDelayView(self.bot, self.storage, self.config, self.ticket)
|
||||||
|
await interaction.response.send_message(embed=embed, view=view, ephemeral=True)
|
||||||
|
|
||||||
|
# Start the countdown
|
||||||
|
await view.start_countdown(interaction)
|
||||||
|
|
||||||
|
@discord.ui.button(label="❌ Annuler", style=discord.ButtonStyle.red, custom_id="close_cancel")
|
||||||
|
async def cancel_callback(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||||
|
"""L'utilisateur annule la fermeture"""
|
||||||
|
await interaction.response.defer()
|
||||||
|
await interaction.delete_original_response()
|
||||||
|
|
||||||
|
def _is_staff(self, interaction: discord.Interaction) -> bool:
|
||||||
|
guild = interaction.guild
|
||||||
|
user = interaction.user
|
||||||
|
for role_id in self.config.staff_roles:
|
||||||
|
role = guild.get_role(int(role_id))
|
||||||
|
if role and role in user.roles:
|
||||||
|
return True
|
||||||
|
return user.guild_permissions.administrator
|
||||||
|
|
||||||
|
|
||||||
|
class CloseDelayView(discord.ui.View):
|
||||||
|
"""Affiche le délai d'attente avant suppression du ticket"""
|
||||||
|
|
||||||
|
def __init__(self, bot, storage, config: GuildTicketConfig, ticket: TicketData, close_reason: str = ""):
|
||||||
|
super().__init__(timeout=300)
|
||||||
|
self.bot = bot
|
||||||
|
self.storage = storage
|
||||||
|
self.config = config
|
||||||
|
self.ticket = ticket
|
||||||
|
self.close_reason = close_reason
|
||||||
|
self.category = config.get_category(ticket.category_id)
|
||||||
|
|
||||||
|
# Ajouter le bouton Annuler
|
||||||
|
cancel_btn = discord.ui.Button(
|
||||||
|
label="❌ Annuler",
|
||||||
|
style=discord.ButtonStyle.red,
|
||||||
|
custom_id="delay_cancel"
|
||||||
|
)
|
||||||
|
cancel_btn.callback = self._cancel_callback
|
||||||
|
self.add_item(cancel_btn)
|
||||||
|
|
||||||
|
async def start_countdown(self, interaction: discord.Interaction):
|
||||||
|
"""Démarre le compte à rebours"""
|
||||||
|
# Désactiver le bouton Annuler
|
||||||
|
for item in self.children:
|
||||||
|
item.disabled = True
|
||||||
|
await interaction.edit_original_response(view=self)
|
||||||
|
|
||||||
|
# Étapes du processus
|
||||||
|
steps = [
|
||||||
|
("⏳ Préparation...", 1),
|
||||||
|
("📄 Génération du transcript...", 1),
|
||||||
|
("📝 Sauvegarde des données...", 1),
|
||||||
|
("🗑️ Suppression du canal...", 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
for step_text, delay in steps:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="🔒 Fermeture du Ticket",
|
||||||
|
description=f"**{step_text}**\n\n"
|
||||||
|
f"Merci pour votre confiance!",
|
||||||
|
color=discord.Color.orange()
|
||||||
|
)
|
||||||
|
embed.set_footer(text=f"Ticket #{self.ticket.channel_id}")
|
||||||
|
|
||||||
|
await interaction.edit_original_response(embed=embed)
|
||||||
|
await asyncio.sleep(delay)
|
||||||
|
|
||||||
|
# Procéder à la fermeture
|
||||||
|
await self._close_ticket(interaction)
|
||||||
|
|
||||||
|
async def _close_ticket(self, interaction: discord.Interaction):
|
||||||
|
"""Effectue la fermeture réelle du ticket"""
|
||||||
|
# Collect messages for transcript
|
||||||
|
transcript_file = None
|
||||||
|
try:
|
||||||
|
messages = []
|
||||||
|
async for msg in interaction.channel.history(limit=1000, oldest_first=True):
|
||||||
|
if msg.author.bot:
|
||||||
|
continue
|
||||||
|
messages.append({
|
||||||
|
"id": str(msg.id),
|
||||||
|
"author_id": msg.author.id,
|
||||||
|
"author_name": str(msg.author),
|
||||||
|
"content": msg.content,
|
||||||
|
"timestamp": msg.created_at.isoformat(),
|
||||||
|
"is_staff": self._is_staff_member(msg.author)
|
||||||
|
})
|
||||||
|
|
||||||
|
from .utils.transcript import get_transcript_generator
|
||||||
|
transcript_gen = get_transcript_generator()
|
||||||
|
transcript_file = transcript_gen.save_transcript(self.ticket, messages, format="html")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error generating transcript: {e}")
|
||||||
|
|
||||||
|
# Close ticket
|
||||||
|
self.ticket.status = TicketStatus.CLOSED
|
||||||
|
self.ticket.closed_at = datetime.now()
|
||||||
|
if self.close_reason:
|
||||||
|
self.ticket.feedback = self.close_reason
|
||||||
|
self.storage.save_ticket(self.ticket)
|
||||||
|
|
||||||
|
duration = self.ticket.duration_minutes
|
||||||
|
|
||||||
|
# Send transcript to log channel
|
||||||
|
if self.config.log_channel_id and transcript_file and os.path.exists(transcript_file):
|
||||||
|
try:
|
||||||
|
log_channel = interaction.guild.get_channel(self.config.log_channel_id)
|
||||||
|
if log_channel:
|
||||||
|
log_embed = discord.Embed(
|
||||||
|
title="📄 Ticket Fermé - Transcript",
|
||||||
|
description=f"Ticket #{self.ticket.channel_id} fermé par {interaction.user.mention}",
|
||||||
|
color=discord.Color.red(),
|
||||||
|
timestamp=datetime.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
ticket_creator = interaction.guild.get_member(self.ticket.user_id)
|
||||||
|
creator_mention = ticket_creator.mention if ticket_creator else f"Utilisateur {self.ticket.user_id}"
|
||||||
|
|
||||||
|
log_embed.add_field(name="Créateur", value=creator_mention, inline=True)
|
||||||
|
log_embed.add_field(name="Catégorie", value=self.category.name if self.category else "N/A", inline=True)
|
||||||
|
log_embed.add_field(name="Durée", value=f"{duration:.1f} min" if duration else "N/A", inline=True)
|
||||||
|
|
||||||
|
if self.close_reason:
|
||||||
|
log_embed.add_field(name="Raison", value=self.close_reason, inline=False)
|
||||||
|
|
||||||
|
file = discord.File(transcript_file, filename=os.path.basename(transcript_file))
|
||||||
|
await log_channel.send(embed=log_embed, file=file)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error sending transcript to log channel: {e}")
|
||||||
|
|
||||||
|
# Delete the ticket channel
|
||||||
|
try:
|
||||||
|
await interaction.channel.delete(reason=f"Ticket fermé par {interaction.user}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error deleting channel: {e}")
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="❌ Erreur",
|
||||||
|
description="Le ticket a été fermé mais la suppression du salon a échoué.",
|
||||||
|
color=discord.Color.red()
|
||||||
|
)
|
||||||
|
await interaction.channel.send(embed=embed)
|
||||||
|
|
||||||
|
async def _cancel_callback(self, interaction: discord.Interaction):
|
||||||
|
"""Annule la fermeture du ticket pendant le délai"""
|
||||||
|
await interaction.response.defer()
|
||||||
|
await interaction.delete_original_response()
|
||||||
|
|
||||||
|
# Envoyer un message de confirmation d'annulation
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="❌ Fermeture Annulée",
|
||||||
|
description="La fermeture du ticket a été annulée.",
|
||||||
|
color=discord.Color.red()
|
||||||
|
)
|
||||||
|
await interaction.channel.send(embed=embed, delete_after=5)
|
||||||
|
|
||||||
def _is_staff(self, interaction: discord.Interaction) -> bool:
|
def _is_staff(self, interaction: discord.Interaction) -> bool:
|
||||||
guild = interaction.guild
|
guild = interaction.guild
|
||||||
user = interaction.user
|
user = interaction.user
|
||||||
|
|
@ -1494,14 +1651,23 @@ class ConfigPanelView(discord.ui.View):
|
||||||
add_cat_btn.callback = self._add_cat_callback
|
add_cat_btn.callback = self._add_cat_callback
|
||||||
self.add_item(add_cat_btn)
|
self.add_item(add_cat_btn)
|
||||||
|
|
||||||
# Staff roles
|
# Staff permissions
|
||||||
roles_btn = discord.ui.Button(
|
staff_btn = discord.ui.Button(
|
||||||
style=discord.ButtonStyle.secondary,
|
style=discord.ButtonStyle.secondary,
|
||||||
label="Rôles Staff",
|
label="Permissions Staff",
|
||||||
custom_id="config_roles"
|
custom_id="config_staff_perms"
|
||||||
)
|
)
|
||||||
roles_btn.callback = self._roles_callback
|
staff_btn.callback = self._staff_perms_callback
|
||||||
self.add_item(roles_btn)
|
self.add_item(staff_btn)
|
||||||
|
|
||||||
|
# Config permissions
|
||||||
|
config_perms_btn = discord.ui.Button(
|
||||||
|
style=discord.ButtonStyle.secondary,
|
||||||
|
label="Permissions Config",
|
||||||
|
custom_id="config_config_perms"
|
||||||
|
)
|
||||||
|
config_perms_btn.callback = self._config_perms_callback
|
||||||
|
self.add_item(config_perms_btn)
|
||||||
|
|
||||||
# Log channel
|
# Log channel
|
||||||
log_btn = discord.ui.Button(
|
log_btn = discord.ui.Button(
|
||||||
|
|
@ -1529,24 +1695,47 @@ class ConfigPanelView(discord.ui.View):
|
||||||
modal = AddCategoryModal(self.bot, self.storage, self.config)
|
modal = AddCategoryModal(self.bot, self.storage, self.config)
|
||||||
await interaction.response.send_modal(modal)
|
await interaction.response.send_modal(modal)
|
||||||
|
|
||||||
async def _roles_callback(self, interaction: discord.Interaction):
|
async def _staff_perms_callback(self, interaction: discord.Interaction):
|
||||||
view = StaffRolesView(self.bot, self.storage, self.config, interaction.guild)
|
view = PermissionConfigView(self.bot, self.storage, self.config, interaction.guild, "staff")
|
||||||
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="Rôles Staff",
|
title="Permissions Staff",
|
||||||
description="Sélectionnez les rôles qui peuvent gérer les tickets:",
|
description="Configurez qui peut gérer les tickets (répondre, fermer, etc.)",
|
||||||
color=discord.Color.blue()
|
color=discord.Color.blue()
|
||||||
)
|
)
|
||||||
|
self._add_current_perms_fields(embed, self.config.staff_roles, self.config.staff_users, interaction.guild)
|
||||||
|
|
||||||
|
await interaction.response.send_message(embed=embed, view=view, ephemeral=True)
|
||||||
|
|
||||||
|
async def _config_perms_callback(self, interaction: discord.Interaction):
|
||||||
|
view = PermissionConfigView(self.bot, self.storage, self.config, interaction.guild, "config")
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Permissions Configuration",
|
||||||
|
description="Configurez qui peut modifier les paramètres du bot (/ticketconfig)",
|
||||||
|
color=discord.Color.orange()
|
||||||
|
)
|
||||||
|
self._add_current_perms_fields(embed, self.config.config_roles, self.config.config_users, interaction.guild)
|
||||||
|
|
||||||
|
await interaction.response.send_message(embed=embed, view=view, ephemeral=True)
|
||||||
|
|
||||||
|
def _add_current_perms_fields(self, embed, roles, users, guild):
|
||||||
role_mentions = []
|
role_mentions = []
|
||||||
for role_id in self.config.staff_roles:
|
for role_id in roles:
|
||||||
role = interaction.guild.get_role(int(role_id))
|
role = guild.get_role(int(role_id))
|
||||||
if role:
|
if role:
|
||||||
role_mentions.append(role.mention)
|
role_mentions.append(role.mention)
|
||||||
|
|
||||||
embed.add_field(name="Actuels", value=", ".join(role_mentions) if role_mentions else "Aucun", inline=False)
|
user_mentions = []
|
||||||
|
for user_id in users:
|
||||||
|
user = guild.get_member(user_id)
|
||||||
|
if user:
|
||||||
|
user_mentions.append(user.mention)
|
||||||
|
else:
|
||||||
|
user_mentions.append(f"<@{user_id}>")
|
||||||
|
|
||||||
await interaction.response.send_message(embed=embed, view=view, ephemeral=True)
|
embed.add_field(name="Rôles", value=", ".join(role_mentions) if role_mentions else "Aucun", inline=False)
|
||||||
|
embed.add_field(name="Utilisateurs", value=", ".join(user_mentions) if user_mentions else "Aucun", inline=False)
|
||||||
|
|
||||||
async def _log_channel_callback(self, interaction: discord.Interaction):
|
async def _log_channel_callback(self, interaction: discord.Interaction):
|
||||||
modal = SetChannelModal(self.bot, self.storage, self.config, "log")
|
modal = SetChannelModal(self.bot, self.storage, self.config, "log")
|
||||||
|
|
@ -1741,64 +1930,232 @@ class StaffRolesModal(discord.ui.Modal):
|
||||||
await interaction.response.send_message("Une erreur inattendue s'est produite lors de la configuration des rôles.", ephemeral=True)
|
await interaction.response.send_message("Une erreur inattendue s'est produite lors de la configuration des rôles.", ephemeral=True)
|
||||||
|
|
||||||
|
|
||||||
class StaffRolesView(discord.ui.View):
|
class PermissionConfigView(discord.ui.View):
|
||||||
"""Sélection des rôles staff (alternative avec select menu)"""
|
"""View to configure permissions (Staff/Config Roles & Users)"""
|
||||||
|
|
||||||
def __init__(self, bot, storage, config: GuildTicketConfig, guild: discord.Guild):
|
def __init__(self, bot, storage, config: GuildTicketConfig, guild: discord.Guild, config_type: str = "staff"):
|
||||||
super().__init__(timeout=None)
|
super().__init__(timeout=None)
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.storage = storage
|
self.storage = storage
|
||||||
self.config = config
|
self.config = config
|
||||||
self.guild = guild
|
self.guild = guild
|
||||||
|
self.config_type = config_type # "staff" or "config"
|
||||||
|
|
||||||
# Le select sera populated dans le callback
|
# Add components
|
||||||
self._build_select(guild)
|
self._add_role_select()
|
||||||
|
self._add_user_select()
|
||||||
|
|
||||||
def _build_select(self, guild: discord.Guild):
|
def _add_role_select(self):
|
||||||
if not guild:
|
select = discord.ui.RoleSelect(
|
||||||
return
|
placeholder=f"Sélectionnez les rôles {self.config_type}",
|
||||||
|
|
||||||
options = []
|
|
||||||
for role in guild.roles:
|
|
||||||
if role.id != guild.id: # Exclude @everyone
|
|
||||||
options.append(
|
|
||||||
discord.SelectOption(
|
|
||||||
label=role.name,
|
|
||||||
value=str(role.id),
|
|
||||||
default=str(role.id) in self.config.staff_roles
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if options:
|
|
||||||
select = discord.ui.Select(
|
|
||||||
placeholder="Sélectionnez les rôles staff...",
|
|
||||||
options=options[:25],
|
|
||||||
min_values=0,
|
min_values=0,
|
||||||
max_values=min(25, len(options))
|
max_values=25,
|
||||||
|
row=0
|
||||||
)
|
)
|
||||||
select.callback = self._select_callback
|
select.callback = self._role_callback
|
||||||
self.add_item(select)
|
self.add_item(select)
|
||||||
|
|
||||||
async def _select_callback(self, interaction: discord.Interaction):
|
def _add_user_select(self):
|
||||||
selected = list(interaction.data.get("values", []))
|
select = discord.ui.UserSelect(
|
||||||
self.config.staff_roles = selected
|
placeholder=f"Sélectionnez les utilisateurs {self.config_type}",
|
||||||
|
min_values=0,
|
||||||
|
max_values=25,
|
||||||
|
row=1
|
||||||
|
)
|
||||||
|
select.callback = self._user_callback
|
||||||
|
self.add_item(select)
|
||||||
|
|
||||||
|
async def _role_callback(self, interaction: discord.Interaction):
|
||||||
|
# Determine which list to update
|
||||||
|
if self.config_type == "staff":
|
||||||
|
target_list = self.config.staff_roles
|
||||||
|
else:
|
||||||
|
target_list = self.config.config_roles
|
||||||
|
|
||||||
|
# Get new selected IDs
|
||||||
|
# Note: RoleSelect/UserSelect returns the objects, not IDs directly in values sometimes depending on lib version,
|
||||||
|
# but interaction.data['values'] is reliable for IDs if we want raw.
|
||||||
|
# However, interaction.values which is bound to callback by library usually contains the objects.
|
||||||
|
# Let's use the view item's values.
|
||||||
|
|
||||||
|
# Actually standard lib behavior: interaction.data['values'] contains IDs.
|
||||||
|
# But let's use the argument 'interaction' and the component.
|
||||||
|
|
||||||
|
# Better: use the values from the specific component that triggered this
|
||||||
|
# But since we have separate callbacks we can just read interaction.data
|
||||||
|
|
||||||
|
selected_ids = [str(r.id) for r in interaction.data.get('values', [])] # But wait, select menu interactions return objects in the view item's values property if mapped? NO, interaction.values is for string selects.
|
||||||
|
# For Entity Select Menus (Role/User), interaction.data['values'] contains IDs.
|
||||||
|
|
||||||
|
# BUT, discord.py 2.0 uses select.values for string selects.
|
||||||
|
# For RoleSelect, values are the list of Role objects.
|
||||||
|
# Let's try to trust the interaction object's resolution.
|
||||||
|
|
||||||
|
# Wait, standard discord.py RoleSelect callback receives interaction.
|
||||||
|
# The select object itself will have 'values' populated with Role objects after processing.
|
||||||
|
# But we need to reference the select item.
|
||||||
|
# Since we added it dynamically, we can't easily reference 'self.role_select'.
|
||||||
|
|
||||||
|
# Workaround: Re-read from interaction.data['values'] which are IDs.
|
||||||
|
values = interaction.data.get('values', [])
|
||||||
|
|
||||||
|
# WE NEED TO MERGE with existing? No, select menu usually replaces selection if it shows current selection.
|
||||||
|
# BUT Discord UI Selects don't show "current selection" natively pre-filled unless we set defaults.
|
||||||
|
# AND we can only set defaults for StringSelect, NOT for RoleSelect/UserSelect (API Limitation).
|
||||||
|
# So RoleSelect/UserSelect always start empty.
|
||||||
|
# This implies we can only ADD/REMOVE or we have to handle it as "Add these", "Remove these"?
|
||||||
|
# OR we treat valid input as "Append these to existing" or "Replace existing"?
|
||||||
|
# Given we can't show pre-selected items, "Replace" is dangerous because user can't see what was there.
|
||||||
|
# "Append" is safer but how to remove?
|
||||||
|
|
||||||
|
# Modification: Make it "Toggle" or just "Add/Remove" via specific actions?
|
||||||
|
# A common pattern is: The select menu creates a list. We Add them.
|
||||||
|
# But how to remove?
|
||||||
|
# Maybe we should stick to STRING SELECT for removal if we want to show lists.
|
||||||
|
# But the user issue is "Can't see all roles".
|
||||||
|
|
||||||
|
# Better approach for Entity Selects:
|
||||||
|
# They are usually used for "Add these items".
|
||||||
|
# To remove, we might need a separate "Remove" workflow or a StringSelect of *current* items to remove.
|
||||||
|
|
||||||
|
# Let's implement: "Add selected roles/users to permission".
|
||||||
|
# And provide a way to "Clear/Remove" via a StringSelect of *existing* configured items.
|
||||||
|
|
||||||
|
# So:
|
||||||
|
# 1. RoleSelect (Add)
|
||||||
|
# 2. UserSelect (Add)
|
||||||
|
# 3. StringSelect (Remove) - Populated with currently configured items.
|
||||||
|
|
||||||
|
new_ids = interaction.data.get('values', [])
|
||||||
|
added_count = 0
|
||||||
|
|
||||||
|
for new_id in new_ids:
|
||||||
|
if new_id not in target_list:
|
||||||
|
target_list.append(new_id)
|
||||||
|
added_count += 1
|
||||||
|
|
||||||
self.storage.save_config(interaction.guild_id, self.config)
|
self.storage.save_config(interaction.guild_id, self.config)
|
||||||
|
|
||||||
role_mentions = []
|
await interaction.response.send_message(
|
||||||
for role_id in selected:
|
f"✅ **{added_count}** rôle(s) ajouté(s) à la configuration {self.config_type}.",
|
||||||
role = interaction.guild.get_role(int(role_id))
|
ephemeral=True
|
||||||
if role:
|
|
||||||
role_mentions.append(role.mention)
|
|
||||||
|
|
||||||
embed = discord.Embed(
|
|
||||||
title="✅ Rôles Staff Mis à Jour",
|
|
||||||
description=f"**{len(selected)}** rôle(s) configuré(s).",
|
|
||||||
color=discord.Color.green()
|
|
||||||
)
|
)
|
||||||
embed.add_field(name="Rôles", value=", ".join(role_mentions) if role_mentions else "Aucun", inline=False)
|
|
||||||
|
|
||||||
await interaction.response.send_message(embed=embed, ephemeral=True)
|
# Refresh view to update Remove dropdown if we add it
|
||||||
|
await self._refresh_view(interaction)
|
||||||
|
|
||||||
|
async def _user_callback(self, interaction: discord.Interaction):
|
||||||
|
if self.config_type == "staff":
|
||||||
|
target_list = self.config.staff_users
|
||||||
|
else:
|
||||||
|
target_list = self.config.config_users
|
||||||
|
|
||||||
|
new_ids = [int(uid) for uid in interaction.data.get('values', [])]
|
||||||
|
added_count = 0
|
||||||
|
|
||||||
|
for new_id in new_ids:
|
||||||
|
if new_id not in target_list:
|
||||||
|
target_list.append(new_id)
|
||||||
|
added_count += 1
|
||||||
|
|
||||||
|
self.storage.save_config(interaction.guild_id, self.config)
|
||||||
|
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"✅ **{added_count}** utilisateur(s) ajouté(s) à la configuration {self.config_type}.",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
await self._refresh_view(interaction)
|
||||||
|
|
||||||
|
async def _refresh_view(self, interaction):
|
||||||
|
# Re-render the view?
|
||||||
|
# We can't easily edit the ephemeral message with a new view from here without 'response.edit_message'
|
||||||
|
# But we already responded to interaction.
|
||||||
|
# So we can followup edit?
|
||||||
|
# Actually we just want to update the "Remove" select menu.
|
||||||
|
# Let's add the Remove Select Menu.
|
||||||
|
self.clear_items()
|
||||||
|
self._add_role_select()
|
||||||
|
self._add_user_select()
|
||||||
|
self._add_remove_select()
|
||||||
|
await interaction.message.edit(view=self)
|
||||||
|
|
||||||
|
def _add_remove_select(self):
|
||||||
|
# Build options from current config
|
||||||
|
options = []
|
||||||
|
|
||||||
|
if self.config_type == "staff":
|
||||||
|
roles = self.config.staff_roles
|
||||||
|
users = self.config.staff_users
|
||||||
|
else:
|
||||||
|
roles = self.config.config_roles
|
||||||
|
users = self.config.config_users
|
||||||
|
|
||||||
|
# Limit to 25 items for removal dropdown (API Limit)
|
||||||
|
# If more, we might need pagination or just show first 25.
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
for r_id in roles:
|
||||||
|
if count >= 25: break
|
||||||
|
role = self.guild.get_role(int(r_id))
|
||||||
|
name = role.name if role else f"Role {r_id}"
|
||||||
|
options.append(discord.SelectOption(
|
||||||
|
label=f"Role: {name[:90]}",
|
||||||
|
value=f"r_{r_id}",
|
||||||
|
emoji="🛡️",
|
||||||
|
description="Cliquez pour retirer"
|
||||||
|
))
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
for u_id in users:
|
||||||
|
if count >= 25: break
|
||||||
|
user = self.guild.get_member(u_id)
|
||||||
|
name = user.display_name if user else f"User {u_id}"
|
||||||
|
options.append(discord.SelectOption(
|
||||||
|
label=f"User: {name[:90]}",
|
||||||
|
value=f"u_{u_id}",
|
||||||
|
emoji="👤",
|
||||||
|
description="Cliquez pour retirer"
|
||||||
|
))
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
if options:
|
||||||
|
select = discord.ui.Select(
|
||||||
|
placeholder="Sélectionnez pour RETIRER des permissions...",
|
||||||
|
options=options,
|
||||||
|
min_values=1,
|
||||||
|
max_values=min(len(options), 25),
|
||||||
|
row=2,
|
||||||
|
custom_id="remove_select"
|
||||||
|
)
|
||||||
|
select.callback = self._remove_callback
|
||||||
|
self.add_item(select)
|
||||||
|
|
||||||
|
async def _remove_callback(self, interaction: discord.Interaction):
|
||||||
|
values = interaction.data.get('values', [])
|
||||||
|
|
||||||
|
removed_count = 0
|
||||||
|
for val in values:
|
||||||
|
prefix, id_str = val.split('_')
|
||||||
|
id_val = int(id_str)
|
||||||
|
|
||||||
|
if prefix == 'r': # Role
|
||||||
|
target_list = self.config.staff_roles if self.config_type == "staff" else self.config.config_roles
|
||||||
|
if str(id_val) in target_list:
|
||||||
|
target_list.remove(str(id_val))
|
||||||
|
removed_count += 1
|
||||||
|
elif prefix == 'u': # User
|
||||||
|
target_list = self.config.staff_users if self.config_type == "staff" else self.config.config_users
|
||||||
|
if id_val in target_list:
|
||||||
|
target_list.remove(id_val)
|
||||||
|
removed_count += 1
|
||||||
|
|
||||||
|
self.storage.save_config(interaction.guild_id, self.config)
|
||||||
|
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"🗑️ **{removed_count}** permission(s) retirée(s).",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
await self._refresh_view(interaction)
|
||||||
|
|
||||||
|
|
||||||
class TicketCommands(commands.Cog):
|
class TicketCommands(commands.Cog):
|
||||||
|
|
@ -1808,7 +2165,7 @@ class TicketCommands(commands.Cog):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.storage = get_storage()
|
self.storage = get_storage()
|
||||||
|
|
||||||
def cog_unload(self):
|
async def cog_unload(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def cog_load(self):
|
async def cog_load(self):
|
||||||
|
|
@ -1892,23 +2249,16 @@ class TicketCommands(commands.Cog):
|
||||||
"""Ouvre le panel de configuration complet du système de tickets"""
|
"""Ouvre le panel de configuration complet du système de tickets"""
|
||||||
await ctx.defer(ephemeral=True)
|
await ctx.defer(ephemeral=True)
|
||||||
|
|
||||||
|
# Reload config
|
||||||
config = self.storage.load_config(ctx.guild.id)
|
config = self.storage.load_config(ctx.guild.id)
|
||||||
|
|
||||||
# Vérifier si l'utilisateur peut accéder à la configuration
|
# Vérifier permissions (admin ou config roles/users)
|
||||||
can_access = ctx.author.guild_permissions.administrator
|
if not can_access_config(ctx, config):
|
||||||
|
|
||||||
if not can_access:
|
|
||||||
# Vérifier les rôles staff
|
|
||||||
for role_id in config.staff_roles:
|
|
||||||
role = ctx.guild.get_role(int(role_id))
|
|
||||||
if role and role in ctx.author.roles:
|
|
||||||
can_access = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if not can_access:
|
|
||||||
await ctx.send("❌ Cette commande est réservée aux administrateurs ou aux rôles configurés.", ephemeral=True)
|
await ctx.send("❌ Cette commande est réservée aux administrateurs ou aux rôles configurés.", ephemeral=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="⚙️ Configuration du Système de Tickets",
|
title="⚙️ Configuration du Système de Tickets",
|
||||||
description="Configurez complètement votre système de tickets depuis cette interface.\n\n"
|
description="Configurez complètement votre système de tickets depuis cette interface.\n\n"
|
||||||
|
|
|
||||||
|
|
@ -293,7 +293,9 @@ class GuildTicketConfig:
|
||||||
log_channel_id: Optional[int] = None
|
log_channel_id: Optional[int] = None
|
||||||
archive_channel_id: Optional[int] = None
|
archive_channel_id: Optional[int] = None
|
||||||
staff_roles: List[str] = field(default_factory=list)
|
staff_roles: List[str] = field(default_factory=list)
|
||||||
|
staff_users: List[int] = field(default_factory=list)
|
||||||
config_roles: List[str] = field(default_factory=list)
|
config_roles: List[str] = field(default_factory=list)
|
||||||
|
config_users: List[int] = field(default_factory=list)
|
||||||
max_tickets_per_user: int = 5
|
max_tickets_per_user: int = 5
|
||||||
auto_close_enabled: bool = False
|
auto_close_enabled: bool = False
|
||||||
auto_close_days: int = 7
|
auto_close_days: int = 7
|
||||||
|
|
@ -312,8 +314,12 @@ class GuildTicketConfig:
|
||||||
"default_category": self.default_category,
|
"default_category": self.default_category,
|
||||||
"log_channel_id": self.log_channel_id,
|
"log_channel_id": self.log_channel_id,
|
||||||
"archive_channel_id": self.archive_channel_id,
|
"archive_channel_id": self.archive_channel_id,
|
||||||
|
"log_channel_id": self.log_channel_id,
|
||||||
|
"archive_channel_id": self.archive_channel_id,
|
||||||
"staff_roles": self.staff_roles,
|
"staff_roles": self.staff_roles,
|
||||||
|
"staff_users": self.staff_users,
|
||||||
"config_roles": self.config_roles,
|
"config_roles": self.config_roles,
|
||||||
|
"config_users": self.config_users,
|
||||||
"max_tickets_per_user": self.max_tickets_per_user,
|
"max_tickets_per_user": self.max_tickets_per_user,
|
||||||
"auto_close_enabled": self.auto_close_enabled,
|
"auto_close_enabled": self.auto_close_enabled,
|
||||||
"auto_close_days": self.auto_close_days,
|
"auto_close_days": self.auto_close_days,
|
||||||
|
|
@ -335,8 +341,11 @@ class GuildTicketConfig:
|
||||||
default_category=data.get("default_category"),
|
default_category=data.get("default_category"),
|
||||||
log_channel_id=data.get("log_channel_id"),
|
log_channel_id=data.get("log_channel_id"),
|
||||||
archive_channel_id=data.get("archive_channel_id"),
|
archive_channel_id=data.get("archive_channel_id"),
|
||||||
|
|
||||||
staff_roles=data.get("staff_roles", []),
|
staff_roles=data.get("staff_roles", []),
|
||||||
|
staff_users=data.get("staff_users", []),
|
||||||
config_roles=data.get("config_roles", []),
|
config_roles=data.get("config_roles", []),
|
||||||
|
config_users=data.get("config_users", []),
|
||||||
max_tickets_per_user=data.get("max_tickets_per_user", 5),
|
max_tickets_per_user=data.get("max_tickets_per_user", 5),
|
||||||
auto_close_enabled=data.get("auto_close_enabled", False),
|
auto_close_enabled=data.get("auto_close_enabled", False),
|
||||||
auto_close_days=data.get("auto_close_days", 7),
|
auto_close_days=data.get("auto_close_days", 7),
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ class StaffAnalytics:
|
||||||
async def get_staff_list(self, guild_id: int) -> List[Dict[str, Any]]:
|
async def get_staff_list(self, guild_id: int) -> List[Dict[str, Any]]:
|
||||||
"""Get list of staff members with their stats"""
|
"""Get list of staff members with their stats"""
|
||||||
config = self.storage.load_config(guild_id)
|
config = self.storage.load_config(guild_id)
|
||||||
|
# Invalidate cache to ensure we get the latest ticket data with new ratings
|
||||||
|
self.storage.invalidate_cache(guild_id)
|
||||||
tickets = self.storage.get_guild_tickets(guild_id)
|
tickets = self.storage.get_guild_tickets(guild_id)
|
||||||
|
|
||||||
staff_stats = defaultdict(lambda: {
|
staff_stats = defaultdict(lambda: {
|
||||||
|
|
|
||||||
92
commandes/ticket/utils/permissions.py
Normal file
92
commandes/ticket/utils/permissions.py
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
"""
|
||||||
|
Ticket Permissions Utility
|
||||||
|
==========================
|
||||||
|
Centralized permission logic for the ticket system.
|
||||||
|
"""
|
||||||
|
import discord
|
||||||
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..data.models import GuildTicketConfig
|
||||||
|
|
||||||
|
def is_staff(interaction: discord.Interaction, config: 'GuildTicketConfig') -> bool:
|
||||||
|
"""
|
||||||
|
Check if the user has staff permissions.
|
||||||
|
|
||||||
|
Checks:
|
||||||
|
1. Administrator permission
|
||||||
|
2. Staff roles
|
||||||
|
3. Staff users (specific user IDs)
|
||||||
|
"""
|
||||||
|
# Get user and permissions based on object type
|
||||||
|
user = getattr(interaction, "author", getattr(interaction, "user", None))
|
||||||
|
if not user:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if user.guild_permissions.administrator:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# Check specific users
|
||||||
|
if user.id in config.staff_users:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check roles
|
||||||
|
for role_id in config.staff_roles:
|
||||||
|
role = interaction.guild.get_role(int(role_id))
|
||||||
|
if role and role in user.roles:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def can_access_config(interaction: discord.Interaction, config: 'GuildTicketConfig') -> bool:
|
||||||
|
"""
|
||||||
|
Check if the user can access the configuration.
|
||||||
|
|
||||||
|
Checks:
|
||||||
|
1. Administrator permission
|
||||||
|
2. Config roles
|
||||||
|
3. Config users
|
||||||
|
4. Staff permissions (staff can often access basic config or view panels)
|
||||||
|
Note: This function is strictly for CONFIG access.
|
||||||
|
If staff should access config, add them to config roles or check is_staff too.
|
||||||
|
In this implementation, we check specific config permissions.
|
||||||
|
"""
|
||||||
|
# Get user and permissions based on object type
|
||||||
|
user = getattr(interaction, "author", getattr(interaction, "user", None))
|
||||||
|
if not user:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if user.guild_permissions.administrator:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check specific users
|
||||||
|
if user.id in config.config_users:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check config roles
|
||||||
|
for role_id in config.config_roles:
|
||||||
|
role = interaction.guild.get_role(int(role_id))
|
||||||
|
if role and role in user.roles:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Also check legacy/migrated staff roles if they imply config access?
|
||||||
|
# For now, we keep it strict: config access requires explicit config permission.
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_whitelisted(interaction: discord.Interaction, config: 'GuildTicketConfig', bot) -> bool:
|
||||||
|
"""
|
||||||
|
Check if user is whitelisted or has staff/config permissions.
|
||||||
|
"""
|
||||||
|
# Staff and config users are always whitelisted
|
||||||
|
if is_staff(interaction, config) or can_access_config(interaction, config):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check whitelist cog
|
||||||
|
whitelist_cog = bot.get_cog("WhitelistMonitor")
|
||||||
|
if not whitelist_cog:
|
||||||
|
# If whitelist system not available, deny unless staff/config
|
||||||
|
return False
|
||||||
|
|
||||||
|
return whitelist_cog.is_whitelisted(interaction.guild.id, interaction.user.id)
|
||||||
10
data/logs/member_events_2026-01-10.json
Normal file
10
data/logs/member_events_2026-01-10.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"timestamp": "2026-01-10T16:19:17.701722",
|
||||||
|
"event_type": "member_join",
|
||||||
|
"member_id": 1459567012031234198,
|
||||||
|
"member_name": "fequjifenq_85720",
|
||||||
|
"member_discriminator": "0",
|
||||||
|
"member_avatar": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1459563155859570692,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 971446412690722826,
|
||||||
|
"category_id": "3b0b9bc1",
|
||||||
|
"status": "open",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-10T16:02:53.964008",
|
||||||
|
"closed_at": null,
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "egrrdhgdrh",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461084203054665812,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 971446412690722826,
|
||||||
|
"category_id": "91522e2c",
|
||||||
|
"status": "open",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T20:46:59.791051",
|
||||||
|
"closed_at": null,
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "gregqssgg",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461084293995565242,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 971446412690722826,
|
||||||
|
"category_id": "3b0b9bc1",
|
||||||
|
"status": "open",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T20:47:21.788375",
|
||||||
|
"closed_at": null,
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "fesefsfe",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461084293995565242,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 971446412690722826,
|
||||||
|
"category_id": "3b0b9bc1",
|
||||||
|
"status": "claimed",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T20:47:21.788375",
|
||||||
|
"closed_at": null,
|
||||||
|
"claimed_by": 971446412690722826,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "fesefsfe",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461084511738532026,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 1459567012031234198,
|
||||||
|
"category_id": "3b0b9bc1",
|
||||||
|
"status": "open",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T20:48:13.454435",
|
||||||
|
"closed_at": null,
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "dzqdqzgs",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461084844879778008,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 971446412690722826,
|
||||||
|
"category_id": "3b0b9bc1",
|
||||||
|
"status": "open",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T20:49:32.964046",
|
||||||
|
"closed_at": null,
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "szfeq",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
56
data/tickets/backups/20260114_205938_config.json
Normal file
56
data/tickets/backups/20260114_205938_config.json
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
{
|
||||||
|
"1369669999345537145": {
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"enabled": true,
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"id": "3b0b9bc1",
|
||||||
|
"name": "Mon cul",
|
||||||
|
"description": "Mon Anus",
|
||||||
|
"emoji": "🎫",
|
||||||
|
"color": "#3498db",
|
||||||
|
"staff_roles": [],
|
||||||
|
"welcome_message": "Merci d'avoir créé un ticket. Notre équipe va vous répondre sous peu.",
|
||||||
|
"require_reason": false,
|
||||||
|
"max_tickets_per_user": 5,
|
||||||
|
"auto_close_days": 7,
|
||||||
|
"priority_enabled": true,
|
||||||
|
"allow_claims": true,
|
||||||
|
"survey_enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "91522e2c",
|
||||||
|
"name": "claudo",
|
||||||
|
"description": "un clochard",
|
||||||
|
"emoji": "📋",
|
||||||
|
"color": "#3498db",
|
||||||
|
"staff_roles": [],
|
||||||
|
"welcome_message": "Merci d'avoir créé un ticket. Notre équipe va vous répondre sous peu.",
|
||||||
|
"require_reason": false,
|
||||||
|
"max_tickets_per_user": 5,
|
||||||
|
"auto_close_days": 7,
|
||||||
|
"priority_enabled": true,
|
||||||
|
"allow_claims": true,
|
||||||
|
"survey_enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_category": null,
|
||||||
|
"log_channel_id": 1369669999677145103,
|
||||||
|
"archive_channel_id": null,
|
||||||
|
"staff_roles": [
|
||||||
|
"1369769836028231730"
|
||||||
|
],
|
||||||
|
"config_roles": [
|
||||||
|
"1369769836028231730"
|
||||||
|
],
|
||||||
|
"max_tickets_per_user": 5,
|
||||||
|
"auto_close_enabled": false,
|
||||||
|
"auto_close_days": 7,
|
||||||
|
"transcript_enabled": true,
|
||||||
|
"claim_enabled": true,
|
||||||
|
"survey_enabled": true,
|
||||||
|
"panel_channel_id": null,
|
||||||
|
"welcome_enabled": true,
|
||||||
|
"priority_enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
56
data/tickets/backups/20260114_205954_config.json
Normal file
56
data/tickets/backups/20260114_205954_config.json
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
{
|
||||||
|
"1369669999345537145": {
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"enabled": true,
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"id": "3b0b9bc1",
|
||||||
|
"name": "Mon cul",
|
||||||
|
"description": "Mon Anus",
|
||||||
|
"emoji": "🎫",
|
||||||
|
"color": "#3498db",
|
||||||
|
"staff_roles": [],
|
||||||
|
"welcome_message": "Merci d'avoir créé un ticket. Notre équipe va vous répondre sous peu.",
|
||||||
|
"require_reason": false,
|
||||||
|
"max_tickets_per_user": 5,
|
||||||
|
"auto_close_days": 7,
|
||||||
|
"priority_enabled": true,
|
||||||
|
"allow_claims": true,
|
||||||
|
"survey_enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "91522e2c",
|
||||||
|
"name": "claudo",
|
||||||
|
"description": "un clochard",
|
||||||
|
"emoji": "📋",
|
||||||
|
"color": "#3498db",
|
||||||
|
"staff_roles": [],
|
||||||
|
"welcome_message": "Merci d'avoir créé un ticket. Notre équipe va vous répondre sous peu.",
|
||||||
|
"require_reason": false,
|
||||||
|
"max_tickets_per_user": 5,
|
||||||
|
"auto_close_days": 7,
|
||||||
|
"priority_enabled": true,
|
||||||
|
"allow_claims": true,
|
||||||
|
"survey_enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_category": null,
|
||||||
|
"log_channel_id": 1369669999677145103,
|
||||||
|
"archive_channel_id": null,
|
||||||
|
"staff_roles": [
|
||||||
|
"1369769836028231730"
|
||||||
|
],
|
||||||
|
"staff_users": [],
|
||||||
|
"config_roles": [],
|
||||||
|
"config_users": [],
|
||||||
|
"max_tickets_per_user": 5,
|
||||||
|
"auto_close_enabled": false,
|
||||||
|
"auto_close_days": 7,
|
||||||
|
"transcript_enabled": true,
|
||||||
|
"claim_enabled": true,
|
||||||
|
"survey_enabled": true,
|
||||||
|
"panel_channel_id": null,
|
||||||
|
"welcome_enabled": true,
|
||||||
|
"priority_enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
54
data/tickets/backups/20260114_210116_config.json
Normal file
54
data/tickets/backups/20260114_210116_config.json
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
{
|
||||||
|
"1369669999345537145": {
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"enabled": true,
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"id": "3b0b9bc1",
|
||||||
|
"name": "Mon cul",
|
||||||
|
"description": "Mon Anus",
|
||||||
|
"emoji": "🎫",
|
||||||
|
"color": "#3498db",
|
||||||
|
"staff_roles": [],
|
||||||
|
"welcome_message": "Merci d'avoir créé un ticket. Notre équipe va vous répondre sous peu.",
|
||||||
|
"require_reason": false,
|
||||||
|
"max_tickets_per_user": 5,
|
||||||
|
"auto_close_days": 7,
|
||||||
|
"priority_enabled": true,
|
||||||
|
"allow_claims": true,
|
||||||
|
"survey_enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "91522e2c",
|
||||||
|
"name": "claudo",
|
||||||
|
"description": "un clochard",
|
||||||
|
"emoji": "📋",
|
||||||
|
"color": "#3498db",
|
||||||
|
"staff_roles": [],
|
||||||
|
"welcome_message": "Merci d'avoir créé un ticket. Notre équipe va vous répondre sous peu.",
|
||||||
|
"require_reason": false,
|
||||||
|
"max_tickets_per_user": 5,
|
||||||
|
"auto_close_days": 7,
|
||||||
|
"priority_enabled": true,
|
||||||
|
"allow_claims": true,
|
||||||
|
"survey_enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_category": null,
|
||||||
|
"log_channel_id": 1369669999677145103,
|
||||||
|
"archive_channel_id": null,
|
||||||
|
"staff_roles": [],
|
||||||
|
"staff_users": [],
|
||||||
|
"config_roles": [],
|
||||||
|
"config_users": [],
|
||||||
|
"max_tickets_per_user": 5,
|
||||||
|
"auto_close_enabled": false,
|
||||||
|
"auto_close_days": 7,
|
||||||
|
"transcript_enabled": true,
|
||||||
|
"claim_enabled": true,
|
||||||
|
"survey_enabled": true,
|
||||||
|
"panel_channel_id": null,
|
||||||
|
"welcome_enabled": true,
|
||||||
|
"priority_enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461088603273822394,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 971446412690722826,
|
||||||
|
"category_id": "91522e2c",
|
||||||
|
"status": "open",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T21:04:28.886631",
|
||||||
|
"closed_at": null,
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "dzqd",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461083997655400669,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 971446412690722826,
|
||||||
|
"category_id": "3b0b9bc1",
|
||||||
|
"status": "open",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T20:46:10.826712",
|
||||||
|
"closed_at": null,
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "dzqzddqz",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
56
data/tickets/backups/20260114_210805_config.json
Normal file
56
data/tickets/backups/20260114_210805_config.json
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
{
|
||||||
|
"1369669999345537145": {
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"enabled": true,
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"id": "3b0b9bc1",
|
||||||
|
"name": "Mon cul",
|
||||||
|
"description": "Mon Anus",
|
||||||
|
"emoji": "🎫",
|
||||||
|
"color": "#3498db",
|
||||||
|
"staff_roles": [],
|
||||||
|
"welcome_message": "Merci d'avoir créé un ticket. Notre équipe va vous répondre sous peu.",
|
||||||
|
"require_reason": false,
|
||||||
|
"max_tickets_per_user": 5,
|
||||||
|
"auto_close_days": 7,
|
||||||
|
"priority_enabled": true,
|
||||||
|
"allow_claims": true,
|
||||||
|
"survey_enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "91522e2c",
|
||||||
|
"name": "claudo",
|
||||||
|
"description": "un clochard",
|
||||||
|
"emoji": "📋",
|
||||||
|
"color": "#3498db",
|
||||||
|
"staff_roles": [],
|
||||||
|
"welcome_message": "Merci d'avoir créé un ticket. Notre équipe va vous répondre sous peu.",
|
||||||
|
"require_reason": false,
|
||||||
|
"max_tickets_per_user": 5,
|
||||||
|
"auto_close_days": 7,
|
||||||
|
"priority_enabled": true,
|
||||||
|
"allow_claims": true,
|
||||||
|
"survey_enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_category": null,
|
||||||
|
"log_channel_id": 1369669999677145103,
|
||||||
|
"archive_channel_id": null,
|
||||||
|
"staff_roles": [],
|
||||||
|
"staff_users": [],
|
||||||
|
"config_roles": [
|
||||||
|
"1369769836028231730"
|
||||||
|
],
|
||||||
|
"config_users": [],
|
||||||
|
"max_tickets_per_user": 5,
|
||||||
|
"auto_close_enabled": false,
|
||||||
|
"auto_close_days": 7,
|
||||||
|
"transcript_enabled": true,
|
||||||
|
"claim_enabled": true,
|
||||||
|
"survey_enabled": true,
|
||||||
|
"panel_channel_id": null,
|
||||||
|
"welcome_enabled": true,
|
||||||
|
"priority_enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461089849527505151,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 1459567012031234198,
|
||||||
|
"category_id": "91522e2c",
|
||||||
|
"status": "open",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T21:09:26.048196",
|
||||||
|
"closed_at": null,
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "dzqdzdq",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461089849527505151,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 1459567012031234198,
|
||||||
|
"category_id": "91522e2c",
|
||||||
|
"status": "closed",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T21:09:26.048196",
|
||||||
|
"closed_at": "2026-01-14T21:12:34.847114",
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "dzqdzdq",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461089849527505151,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 1459567012031234198,
|
||||||
|
"category_id": "91522e2c",
|
||||||
|
"status": "closed",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T21:09:26.048196",
|
||||||
|
"closed_at": "2026-01-14T21:15:28.028358",
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "dzqdzdq",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461089849527505151,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 1459567012031234198,
|
||||||
|
"category_id": "91522e2c",
|
||||||
|
"status": "closed",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T21:09:26.048196",
|
||||||
|
"closed_at": "2026-01-14T21:15:31.970135",
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "dzqdzdq",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461089849527505151,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 1459567012031234198,
|
||||||
|
"category_id": "91522e2c",
|
||||||
|
"status": "closed",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T21:09:26.048196",
|
||||||
|
"closed_at": "2026-01-14T21:15:35.563739",
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "dzqdzdq",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461089849527505151,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 1459567012031234198,
|
||||||
|
"category_id": "91522e2c",
|
||||||
|
"status": "closed",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T21:09:26.048196",
|
||||||
|
"closed_at": "2026-01-14T21:15:39.314476",
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "dzqdzdq",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
16
data/tickets/tickets/1459561776428351568.json
Normal file
16
data/tickets/tickets/1459561776428351568.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1459561776428351568,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 971446412690722826,
|
||||||
|
"category_id": "3b0b9bc1",
|
||||||
|
"status": "open",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-10T15:57:24.973539",
|
||||||
|
"closed_at": null,
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "yjutdgbfrd",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
16
data/tickets/tickets/1459563155859570692.json
Normal file
16
data/tickets/tickets/1459563155859570692.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1459563155859570692,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 971446412690722826,
|
||||||
|
"category_id": "3b0b9bc1",
|
||||||
|
"status": "closed",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-10T16:02:53.964008",
|
||||||
|
"closed_at": "2026-01-10T16:03:05.092145",
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "egrrdhgdrh",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
16
data/tickets/tickets/1461083997655400669.json
Normal file
16
data/tickets/tickets/1461083997655400669.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461083997655400669,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 971446412690722826,
|
||||||
|
"category_id": "3b0b9bc1",
|
||||||
|
"status": "closed",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T20:46:10.826712",
|
||||||
|
"closed_at": "2026-01-14T21:06:29.364458",
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "dzqzddqz",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
16
data/tickets/tickets/1461084203054665812.json
Normal file
16
data/tickets/tickets/1461084203054665812.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461084203054665812,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 971446412690722826,
|
||||||
|
"category_id": "91522e2c",
|
||||||
|
"status": "closed",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T20:46:59.791051",
|
||||||
|
"closed_at": "2026-01-14T20:47:15.286524",
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "gregqssgg",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
16
data/tickets/tickets/1461084293995565242.json
Normal file
16
data/tickets/tickets/1461084293995565242.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461084293995565242,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 971446412690722826,
|
||||||
|
"category_id": "3b0b9bc1",
|
||||||
|
"status": "closed",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T20:47:21.788375",
|
||||||
|
"closed_at": "2026-01-14T20:47:45.988053",
|
||||||
|
"claimed_by": 971446412690722826,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "fesefsfe",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
16
data/tickets/tickets/1461084511738532026.json
Normal file
16
data/tickets/tickets/1461084511738532026.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461084511738532026,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 1459567012031234198,
|
||||||
|
"category_id": "3b0b9bc1",
|
||||||
|
"status": "closed",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T20:48:13.454435",
|
||||||
|
"closed_at": "2026-01-14T20:48:25.136290",
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "dzqdqzgs",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
16
data/tickets/tickets/1461084844879778008.json
Normal file
16
data/tickets/tickets/1461084844879778008.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461084844879778008,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 971446412690722826,
|
||||||
|
"category_id": "3b0b9bc1",
|
||||||
|
"status": "closed",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T20:49:32.964046",
|
||||||
|
"closed_at": "2026-01-14T20:49:44.315370",
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "szfeq",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
16
data/tickets/tickets/1461088603273822394.json
Normal file
16
data/tickets/tickets/1461088603273822394.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461088603273822394,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 971446412690722826,
|
||||||
|
"category_id": "91522e2c",
|
||||||
|
"status": "closed",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T21:04:28.886631",
|
||||||
|
"closed_at": "2026-01-14T21:04:50.756760",
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "dzqd",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
16
data/tickets/tickets/1461089849527505151.json
Normal file
16
data/tickets/tickets/1461089849527505151.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"channel_id": 1461089849527505151,
|
||||||
|
"guild_id": 1369669999345537145,
|
||||||
|
"user_id": 1459567012031234198,
|
||||||
|
"category_id": "91522e2c",
|
||||||
|
"status": "closed",
|
||||||
|
"priority": "normal",
|
||||||
|
"created_at": "2026-01-14T21:09:26.048196",
|
||||||
|
"closed_at": "2026-01-14T21:15:43.690109",
|
||||||
|
"claimed_by": null,
|
||||||
|
"messages": [],
|
||||||
|
"reason": "dzqdzdq",
|
||||||
|
"transcript_path": null,
|
||||||
|
"rating": null,
|
||||||
|
"feedback": null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Transcript - Ticket #1459563155859570692</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: #f5f6fa;
|
||||||
|
color: #2c3e50;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #3498db, #34495e);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.header-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.status-open { background: #27ae60; }
|
||||||
|
.status-closed { background: #e74c3c; }
|
||||||
|
.status-claimed { background: #f39c12; }
|
||||||
|
|
||||||
|
.messages-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.messages-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
border-bottom: 2px solid #ecf0f1;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #ecf0f1;
|
||||||
|
}
|
||||||
|
.message:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.message.staff-message {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 3px solid #3498db;
|
||||||
|
}
|
||||||
|
.message-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.author {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
.staff-badge {
|
||||||
|
background: #3498db;
|
||||||
|
color: white;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.message-content {
|
||||||
|
color: #34495e;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.stats-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.stat-item {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #7f8c8d;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.stat-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🎫 Transcript de Ticket</h1>
|
||||||
|
<div class="header-meta">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📌 ID:</span>
|
||||||
|
<span>1459563155859570692</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📁 Catégorie:</span>
|
||||||
|
<span>3b0b9bc1</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>⚡ Priorité:</span>
|
||||||
|
<span style="color: #3498db; font-weight: bold;">NORMAL</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📊 Statut:</span>
|
||||||
|
<span class="status-badge status-open">open</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📅 Créé:</span>
|
||||||
|
<span>10/01/2026 16:02</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="messages-section">
|
||||||
|
<h3>💬 Messages (0)</h3>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="stats-section">
|
||||||
|
<h3>📊 Statistiques</h3>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Durée</span>
|
||||||
|
<span class="stat-value">0 min</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Messages</span>
|
||||||
|
<span class="stat-value">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Note</span>
|
||||||
|
<span class="stat-value">Pas de note</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Généré le 10/01/2026 à 16:03:05</p>
|
||||||
|
<p>Système de Tickets - Kuby Bot</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Transcript - Ticket #1461083997655400669</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: #f5f6fa;
|
||||||
|
color: #2c3e50;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #3498db, #34495e);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.header-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.status-open { background: #27ae60; }
|
||||||
|
.status-closed { background: #e74c3c; }
|
||||||
|
.status-claimed { background: #f39c12; }
|
||||||
|
|
||||||
|
.messages-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.messages-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
border-bottom: 2px solid #ecf0f1;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #ecf0f1;
|
||||||
|
}
|
||||||
|
.message:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.message.staff-message {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 3px solid #3498db;
|
||||||
|
}
|
||||||
|
.message-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.author {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
.staff-badge {
|
||||||
|
background: #3498db;
|
||||||
|
color: white;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.message-content {
|
||||||
|
color: #34495e;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.stats-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.stat-item {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #7f8c8d;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.stat-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🎫 Transcript de Ticket</h1>
|
||||||
|
<div class="header-meta">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📌 ID:</span>
|
||||||
|
<span>1461083997655400669</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📁 Catégorie:</span>
|
||||||
|
<span>3b0b9bc1</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>⚡ Priorité:</span>
|
||||||
|
<span style="color: #3498db; font-weight: bold;">NORMAL</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📊 Statut:</span>
|
||||||
|
<span class="status-badge status-open">open</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📅 Créé:</span>
|
||||||
|
<span>14/01/2026 20:46</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="messages-section">
|
||||||
|
<h3>💬 Messages (0)</h3>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="stats-section">
|
||||||
|
<h3>📊 Statistiques</h3>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Durée</span>
|
||||||
|
<span class="stat-value">20 min</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Messages</span>
|
||||||
|
<span class="stat-value">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Note</span>
|
||||||
|
<span class="stat-value">Pas de note</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Généré le 14/01/2026 à 21:06:29</p>
|
||||||
|
<p>Système de Tickets - Kuby Bot</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Transcript - Ticket #1461084203054665812</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: #f5f6fa;
|
||||||
|
color: #2c3e50;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #3498db, #34495e);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.header-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.status-open { background: #27ae60; }
|
||||||
|
.status-closed { background: #e74c3c; }
|
||||||
|
.status-claimed { background: #f39c12; }
|
||||||
|
|
||||||
|
.messages-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.messages-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
border-bottom: 2px solid #ecf0f1;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #ecf0f1;
|
||||||
|
}
|
||||||
|
.message:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.message.staff-message {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 3px solid #3498db;
|
||||||
|
}
|
||||||
|
.message-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.author {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
.staff-badge {
|
||||||
|
background: #3498db;
|
||||||
|
color: white;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.message-content {
|
||||||
|
color: #34495e;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.stats-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.stat-item {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #7f8c8d;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.stat-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🎫 Transcript de Ticket</h1>
|
||||||
|
<div class="header-meta">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📌 ID:</span>
|
||||||
|
<span>1461084203054665812</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📁 Catégorie:</span>
|
||||||
|
<span>91522e2c</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>⚡ Priorité:</span>
|
||||||
|
<span style="color: #3498db; font-weight: bold;">NORMAL</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📊 Statut:</span>
|
||||||
|
<span class="status-badge status-open">open</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📅 Créé:</span>
|
||||||
|
<span>14/01/2026 20:46</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="messages-section">
|
||||||
|
<h3>💬 Messages (0)</h3>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="stats-section">
|
||||||
|
<h3>📊 Statistiques</h3>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Durée</span>
|
||||||
|
<span class="stat-value">0 min</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Messages</span>
|
||||||
|
<span class="stat-value">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Note</span>
|
||||||
|
<span class="stat-value">Pas de note</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Généré le 14/01/2026 à 20:47:15</p>
|
||||||
|
<p>Système de Tickets - Kuby Bot</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,237 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Transcript - Ticket #1461084293995565242</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: #f5f6fa;
|
||||||
|
color: #2c3e50;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #3498db, #34495e);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.header-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.status-open { background: #27ae60; }
|
||||||
|
.status-closed { background: #e74c3c; }
|
||||||
|
.status-claimed { background: #f39c12; }
|
||||||
|
|
||||||
|
.messages-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.messages-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
border-bottom: 2px solid #ecf0f1;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #ecf0f1;
|
||||||
|
}
|
||||||
|
.message:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.message.staff-message {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 3px solid #3498db;
|
||||||
|
}
|
||||||
|
.message-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.author {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
.staff-badge {
|
||||||
|
background: #3498db;
|
||||||
|
color: white;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.message-content {
|
||||||
|
color: #34495e;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.stats-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.stat-item {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #7f8c8d;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.stat-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🎫 Transcript de Ticket</h1>
|
||||||
|
<div class="header-meta">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📌 ID:</span>
|
||||||
|
<span>1461084293995565242</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📁 Catégorie:</span>
|
||||||
|
<span>3b0b9bc1</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>⚡ Priorité:</span>
|
||||||
|
<span style="color: #3498db; font-weight: bold;">NORMAL</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📊 Statut:</span>
|
||||||
|
<span class="status-badge status-claimed">claimed</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📅 Créé:</span>
|
||||||
|
<span>14/01/2026 20:47</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="messages-section">
|
||||||
|
<h3>💬 Messages (3)</h3>
|
||||||
|
|
||||||
|
<div class="message staff-message">
|
||||||
|
<div class="message-header">
|
||||||
|
<span class="author">gameurpro12</span>
|
||||||
|
<span class="staff-badge">STAFF</span>
|
||||||
|
<span class="timestamp">14/01/2026 19:47:33</span>
|
||||||
|
</div>
|
||||||
|
<div class="message-content">rp;fepsŝfe</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="message staff-message">
|
||||||
|
<div class="message-header">
|
||||||
|
<span class="author">gameurpro12</span>
|
||||||
|
<span class="staff-badge">STAFF</span>
|
||||||
|
<span class="timestamp">14/01/2026 19:47:34</span>
|
||||||
|
</div>
|
||||||
|
<div class="message-content">fe,s,</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="message staff-message">
|
||||||
|
<div class="message-header">
|
||||||
|
<span class="author">gameurpro12</span>
|
||||||
|
<span class="staff-badge">STAFF</span>
|
||||||
|
<span class="timestamp">14/01/2026 19:47:35</span>
|
||||||
|
</div>
|
||||||
|
<div class="message-content">f,ekf,sem</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="stats-section">
|
||||||
|
<h3>📊 Statistiques</h3>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Durée</span>
|
||||||
|
<span class="stat-value">0 min</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Messages</span>
|
||||||
|
<span class="stat-value">3</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Note</span>
|
||||||
|
<span class="stat-value">Pas de note</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Généré le 14/01/2026 à 20:47:45</p>
|
||||||
|
<p>Système de Tickets - Kuby Bot</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Transcript - Ticket #1461084511738532026</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: #f5f6fa;
|
||||||
|
color: #2c3e50;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #3498db, #34495e);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.header-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.status-open { background: #27ae60; }
|
||||||
|
.status-closed { background: #e74c3c; }
|
||||||
|
.status-claimed { background: #f39c12; }
|
||||||
|
|
||||||
|
.messages-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.messages-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
border-bottom: 2px solid #ecf0f1;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #ecf0f1;
|
||||||
|
}
|
||||||
|
.message:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.message.staff-message {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 3px solid #3498db;
|
||||||
|
}
|
||||||
|
.message-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.author {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
.staff-badge {
|
||||||
|
background: #3498db;
|
||||||
|
color: white;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.message-content {
|
||||||
|
color: #34495e;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.stats-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.stat-item {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #7f8c8d;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.stat-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🎫 Transcript de Ticket</h1>
|
||||||
|
<div class="header-meta">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📌 ID:</span>
|
||||||
|
<span>1461084511738532026</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📁 Catégorie:</span>
|
||||||
|
<span>3b0b9bc1</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>⚡ Priorité:</span>
|
||||||
|
<span style="color: #3498db; font-weight: bold;">NORMAL</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📊 Statut:</span>
|
||||||
|
<span class="status-badge status-open">open</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📅 Créé:</span>
|
||||||
|
<span>14/01/2026 20:48</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="messages-section">
|
||||||
|
<h3>💬 Messages (0)</h3>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="stats-section">
|
||||||
|
<h3>📊 Statistiques</h3>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Durée</span>
|
||||||
|
<span class="stat-value">0 min</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Messages</span>
|
||||||
|
<span class="stat-value">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Note</span>
|
||||||
|
<span class="stat-value">Pas de note</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Généré le 14/01/2026 à 20:48:25</p>
|
||||||
|
<p>Système de Tickets - Kuby Bot</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Transcript - Ticket #1461084844879778008</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: #f5f6fa;
|
||||||
|
color: #2c3e50;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #3498db, #34495e);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.header-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.status-open { background: #27ae60; }
|
||||||
|
.status-closed { background: #e74c3c; }
|
||||||
|
.status-claimed { background: #f39c12; }
|
||||||
|
|
||||||
|
.messages-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.messages-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
border-bottom: 2px solid #ecf0f1;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #ecf0f1;
|
||||||
|
}
|
||||||
|
.message:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.message.staff-message {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 3px solid #3498db;
|
||||||
|
}
|
||||||
|
.message-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.author {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
.staff-badge {
|
||||||
|
background: #3498db;
|
||||||
|
color: white;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.message-content {
|
||||||
|
color: #34495e;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.stats-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.stat-item {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #7f8c8d;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.stat-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🎫 Transcript de Ticket</h1>
|
||||||
|
<div class="header-meta">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📌 ID:</span>
|
||||||
|
<span>1461084844879778008</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📁 Catégorie:</span>
|
||||||
|
<span>3b0b9bc1</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>⚡ Priorité:</span>
|
||||||
|
<span style="color: #3498db; font-weight: bold;">NORMAL</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📊 Statut:</span>
|
||||||
|
<span class="status-badge status-open">open</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📅 Créé:</span>
|
||||||
|
<span>14/01/2026 20:49</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="messages-section">
|
||||||
|
<h3>💬 Messages (0)</h3>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="stats-section">
|
||||||
|
<h3>📊 Statistiques</h3>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Durée</span>
|
||||||
|
<span class="stat-value">0 min</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Messages</span>
|
||||||
|
<span class="stat-value">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Note</span>
|
||||||
|
<span class="stat-value">Pas de note</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Généré le 14/01/2026 à 20:49:44</p>
|
||||||
|
<p>Système de Tickets - Kuby Bot</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,273 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Transcript - Ticket #1461088603273822394</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: #f5f6fa;
|
||||||
|
color: #2c3e50;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #3498db, #34495e);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.header-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.status-open { background: #27ae60; }
|
||||||
|
.status-closed { background: #e74c3c; }
|
||||||
|
.status-claimed { background: #f39c12; }
|
||||||
|
|
||||||
|
.messages-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.messages-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
border-bottom: 2px solid #ecf0f1;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #ecf0f1;
|
||||||
|
}
|
||||||
|
.message:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.message.staff-message {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 3px solid #3498db;
|
||||||
|
}
|
||||||
|
.message-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.author {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
.staff-badge {
|
||||||
|
background: #3498db;
|
||||||
|
color: white;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.message-content {
|
||||||
|
color: #34495e;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.stats-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.stat-item {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #7f8c8d;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.stat-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🎫 Transcript de Ticket</h1>
|
||||||
|
<div class="header-meta">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📌 ID:</span>
|
||||||
|
<span>1461088603273822394</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📁 Catégorie:</span>
|
||||||
|
<span>91522e2c</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>⚡ Priorité:</span>
|
||||||
|
<span style="color: #3498db; font-weight: bold;">NORMAL</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📊 Statut:</span>
|
||||||
|
<span class="status-badge status-open">open</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📅 Créé:</span>
|
||||||
|
<span>14/01/2026 21:04</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="messages-section">
|
||||||
|
<h3>💬 Messages (7)</h3>
|
||||||
|
|
||||||
|
<div class="message">
|
||||||
|
<div class="message-header">
|
||||||
|
<span class="author">gameurpro12</span>
|
||||||
|
|
||||||
|
<span class="timestamp">14/01/2026 20:04:33</span>
|
||||||
|
</div>
|
||||||
|
<div class="message-content">oizdpqdzq,iodzq</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="message">
|
||||||
|
<div class="message-header">
|
||||||
|
<span class="author">gameurpro12</span>
|
||||||
|
|
||||||
|
<span class="timestamp">14/01/2026 20:04:34</span>
|
||||||
|
</div>
|
||||||
|
<div class="message-content">k,zdq</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="message">
|
||||||
|
<div class="message-header">
|
||||||
|
<span class="author">gameurpro12</span>
|
||||||
|
|
||||||
|
<span class="timestamp">14/01/2026 20:04:34</span>
|
||||||
|
</div>
|
||||||
|
<div class="message-content">dzq,dzq</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="message">
|
||||||
|
<div class="message-header">
|
||||||
|
<span class="author">gameurpro12</span>
|
||||||
|
|
||||||
|
<span class="timestamp">14/01/2026 20:04:35</span>
|
||||||
|
</div>
|
||||||
|
<div class="message-content">zdql,dzq</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="message">
|
||||||
|
<div class="message-header">
|
||||||
|
<span class="author">gameurpro12</span>
|
||||||
|
|
||||||
|
<span class="timestamp">14/01/2026 20:04:35</span>
|
||||||
|
</div>
|
||||||
|
<div class="message-content">dzql,</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="message">
|
||||||
|
<div class="message-header">
|
||||||
|
<span class="author">gameurpro12</span>
|
||||||
|
|
||||||
|
<span class="timestamp">14/01/2026 20:04:35</span>
|
||||||
|
</div>
|
||||||
|
<div class="message-content">dzq,lmdzqm</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="message">
|
||||||
|
<div class="message-header">
|
||||||
|
<span class="author">gameurpro12</span>
|
||||||
|
|
||||||
|
<span class="timestamp">14/01/2026 20:04:36</span>
|
||||||
|
</div>
|
||||||
|
<div class="message-content">zdq;pqzd</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="stats-section">
|
||||||
|
<h3>📊 Statistiques</h3>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Durée</span>
|
||||||
|
<span class="stat-value">0 min</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Messages</span>
|
||||||
|
<span class="stat-value">7</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Note</span>
|
||||||
|
<span class="stat-value">Pas de note</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Généré le 14/01/2026 à 21:04:50</p>
|
||||||
|
<p>Système de Tickets - Kuby Bot</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Transcript - Ticket #1461089849527505151</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: #f5f6fa;
|
||||||
|
color: #2c3e50;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #3498db, #34495e);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.header-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.status-open { background: #27ae60; }
|
||||||
|
.status-closed { background: #e74c3c; }
|
||||||
|
.status-claimed { background: #f39c12; }
|
||||||
|
|
||||||
|
.messages-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.messages-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
border-bottom: 2px solid #ecf0f1;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #ecf0f1;
|
||||||
|
}
|
||||||
|
.message:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.message.staff-message {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 3px solid #3498db;
|
||||||
|
}
|
||||||
|
.message-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.author {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
.staff-badge {
|
||||||
|
background: #3498db;
|
||||||
|
color: white;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.message-content {
|
||||||
|
color: #34495e;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.stats-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.stat-item {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #7f8c8d;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.stat-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🎫 Transcript de Ticket</h1>
|
||||||
|
<div class="header-meta">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📌 ID:</span>
|
||||||
|
<span>1461089849527505151</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📁 Catégorie:</span>
|
||||||
|
<span>91522e2c</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>⚡ Priorité:</span>
|
||||||
|
<span style="color: #3498db; font-weight: bold;">NORMAL</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📊 Statut:</span>
|
||||||
|
<span class="status-badge status-open">open</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📅 Créé:</span>
|
||||||
|
<span>14/01/2026 21:09</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="messages-section">
|
||||||
|
<h3>💬 Messages (1)</h3>
|
||||||
|
|
||||||
|
<div class="message staff-message">
|
||||||
|
<div class="message-header">
|
||||||
|
<span class="author">_.radi0dem0n._</span>
|
||||||
|
<span class="staff-badge">STAFF</span>
|
||||||
|
<span class="timestamp">14/01/2026 20:11:46</span>
|
||||||
|
</div>
|
||||||
|
<div class="message-content">aller XD</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="stats-section">
|
||||||
|
<h3>📊 Statistiques</h3>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Durée</span>
|
||||||
|
<span class="stat-value">3 min</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Messages</span>
|
||||||
|
<span class="stat-value">1</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Note</span>
|
||||||
|
<span class="stat-value">Pas de note</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Généré le 14/01/2026 à 21:12:34</p>
|
||||||
|
<p>Système de Tickets - Kuby Bot</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Transcript - Ticket #1461089849527505151</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: #f5f6fa;
|
||||||
|
color: #2c3e50;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #3498db, #34495e);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.header-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.status-open { background: #27ae60; }
|
||||||
|
.status-closed { background: #e74c3c; }
|
||||||
|
.status-claimed { background: #f39c12; }
|
||||||
|
|
||||||
|
.messages-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.messages-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
border-bottom: 2px solid #ecf0f1;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #ecf0f1;
|
||||||
|
}
|
||||||
|
.message:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.message.staff-message {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 3px solid #3498db;
|
||||||
|
}
|
||||||
|
.message-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.author {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
.staff-badge {
|
||||||
|
background: #3498db;
|
||||||
|
color: white;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.message-content {
|
||||||
|
color: #34495e;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.stats-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.stat-item {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #7f8c8d;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.stat-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🎫 Transcript de Ticket</h1>
|
||||||
|
<div class="header-meta">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📌 ID:</span>
|
||||||
|
<span>1461089849527505151</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📁 Catégorie:</span>
|
||||||
|
<span>91522e2c</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>⚡ Priorité:</span>
|
||||||
|
<span style="color: #3498db; font-weight: bold;">NORMAL</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📊 Statut:</span>
|
||||||
|
<span class="status-badge status-closed">closed</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📅 Créé:</span>
|
||||||
|
<span>14/01/2026 21:09</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="messages-section">
|
||||||
|
<h3>💬 Messages (0)</h3>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="stats-section">
|
||||||
|
<h3>📊 Statistiques</h3>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Durée</span>
|
||||||
|
<span class="stat-value">3 min</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Messages</span>
|
||||||
|
<span class="stat-value">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Note</span>
|
||||||
|
<span class="stat-value">Pas de note</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Généré le 14/01/2026 à 21:15:28</p>
|
||||||
|
<p>Système de Tickets - Kuby Bot</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Transcript - Ticket #1461089849527505151</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: #f5f6fa;
|
||||||
|
color: #2c3e50;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #3498db, #34495e);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.header-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.status-open { background: #27ae60; }
|
||||||
|
.status-closed { background: #e74c3c; }
|
||||||
|
.status-claimed { background: #f39c12; }
|
||||||
|
|
||||||
|
.messages-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.messages-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
border-bottom: 2px solid #ecf0f1;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #ecf0f1;
|
||||||
|
}
|
||||||
|
.message:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.message.staff-message {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 3px solid #3498db;
|
||||||
|
}
|
||||||
|
.message-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.author {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
.staff-badge {
|
||||||
|
background: #3498db;
|
||||||
|
color: white;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.message-content {
|
||||||
|
color: #34495e;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.stats-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.stat-item {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #7f8c8d;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.stat-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🎫 Transcript de Ticket</h1>
|
||||||
|
<div class="header-meta">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📌 ID:</span>
|
||||||
|
<span>1461089849527505151</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📁 Catégorie:</span>
|
||||||
|
<span>91522e2c</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>⚡ Priorité:</span>
|
||||||
|
<span style="color: #3498db; font-weight: bold;">NORMAL</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📊 Statut:</span>
|
||||||
|
<span class="status-badge status-closed">closed</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📅 Créé:</span>
|
||||||
|
<span>14/01/2026 21:09</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="messages-section">
|
||||||
|
<h3>💬 Messages (0)</h3>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="stats-section">
|
||||||
|
<h3>📊 Statistiques</h3>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Durée</span>
|
||||||
|
<span class="stat-value">6 min</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Messages</span>
|
||||||
|
<span class="stat-value">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Note</span>
|
||||||
|
<span class="stat-value">Pas de note</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Généré le 14/01/2026 à 21:15:31</p>
|
||||||
|
<p>Système de Tickets - Kuby Bot</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Transcript - Ticket #1461089849527505151</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: #f5f6fa;
|
||||||
|
color: #2c3e50;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #3498db, #34495e);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.header-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.status-open { background: #27ae60; }
|
||||||
|
.status-closed { background: #e74c3c; }
|
||||||
|
.status-claimed { background: #f39c12; }
|
||||||
|
|
||||||
|
.messages-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.messages-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
border-bottom: 2px solid #ecf0f1;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #ecf0f1;
|
||||||
|
}
|
||||||
|
.message:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.message.staff-message {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 3px solid #3498db;
|
||||||
|
}
|
||||||
|
.message-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.author {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
.staff-badge {
|
||||||
|
background: #3498db;
|
||||||
|
color: white;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.message-content {
|
||||||
|
color: #34495e;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.stats-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.stat-item {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #7f8c8d;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.stat-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🎫 Transcript de Ticket</h1>
|
||||||
|
<div class="header-meta">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📌 ID:</span>
|
||||||
|
<span>1461089849527505151</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📁 Catégorie:</span>
|
||||||
|
<span>91522e2c</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>⚡ Priorité:</span>
|
||||||
|
<span style="color: #3498db; font-weight: bold;">NORMAL</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📊 Statut:</span>
|
||||||
|
<span class="status-badge status-closed">closed</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📅 Créé:</span>
|
||||||
|
<span>14/01/2026 21:09</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="messages-section">
|
||||||
|
<h3>💬 Messages (0)</h3>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="stats-section">
|
||||||
|
<h3>📊 Statistiques</h3>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Durée</span>
|
||||||
|
<span class="stat-value">6 min</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Messages</span>
|
||||||
|
<span class="stat-value">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Note</span>
|
||||||
|
<span class="stat-value">Pas de note</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Généré le 14/01/2026 à 21:15:35</p>
|
||||||
|
<p>Système de Tickets - Kuby Bot</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Transcript - Ticket #1461089849527505151</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: #f5f6fa;
|
||||||
|
color: #2c3e50;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #3498db, #34495e);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.header-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.status-open { background: #27ae60; }
|
||||||
|
.status-closed { background: #e74c3c; }
|
||||||
|
.status-claimed { background: #f39c12; }
|
||||||
|
|
||||||
|
.messages-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.messages-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
border-bottom: 2px solid #ecf0f1;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #ecf0f1;
|
||||||
|
}
|
||||||
|
.message:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.message.staff-message {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 3px solid #3498db;
|
||||||
|
}
|
||||||
|
.message-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.author {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
.staff-badge {
|
||||||
|
background: #3498db;
|
||||||
|
color: white;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.message-content {
|
||||||
|
color: #34495e;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.stats-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.stat-item {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #7f8c8d;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.stat-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🎫 Transcript de Ticket</h1>
|
||||||
|
<div class="header-meta">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📌 ID:</span>
|
||||||
|
<span>1461089849527505151</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📁 Catégorie:</span>
|
||||||
|
<span>91522e2c</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>⚡ Priorité:</span>
|
||||||
|
<span style="color: #3498db; font-weight: bold;">NORMAL</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📊 Statut:</span>
|
||||||
|
<span class="status-badge status-closed">closed</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📅 Créé:</span>
|
||||||
|
<span>14/01/2026 21:09</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="messages-section">
|
||||||
|
<h3>💬 Messages (0)</h3>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="stats-section">
|
||||||
|
<h3>📊 Statistiques</h3>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Durée</span>
|
||||||
|
<span class="stat-value">6 min</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Messages</span>
|
||||||
|
<span class="stat-value">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Note</span>
|
||||||
|
<span class="stat-value">Pas de note</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Généré le 14/01/2026 à 21:15:39</p>
|
||||||
|
<p>Système de Tickets - Kuby Bot</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Transcript - Ticket #1461089849527505151</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: #f5f6fa;
|
||||||
|
color: #2c3e50;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #3498db, #34495e);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.header-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.meta-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.status-open { background: #27ae60; }
|
||||||
|
.status-closed { background: #e74c3c; }
|
||||||
|
.status-claimed { background: #f39c12; }
|
||||||
|
|
||||||
|
.messages-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.messages-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
border-bottom: 2px solid #ecf0f1;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
padding: 15px;
|
||||||
|
border-bottom: 1px solid #ecf0f1;
|
||||||
|
}
|
||||||
|
.message:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.message.staff-message {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 3px solid #3498db;
|
||||||
|
}
|
||||||
|
.message-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.author {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
.staff-badge {
|
||||||
|
background: #3498db;
|
||||||
|
color: white;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.message-content {
|
||||||
|
color: #34495e;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.stats-section h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.stat-item {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.stat-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #7f8c8d;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.stat-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #3498db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #95a5a6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🎫 Transcript de Ticket</h1>
|
||||||
|
<div class="header-meta">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📌 ID:</span>
|
||||||
|
<span>1461089849527505151</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📁 Catégorie:</span>
|
||||||
|
<span>91522e2c</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>⚡ Priorité:</span>
|
||||||
|
<span style="color: #3498db; font-weight: bold;">NORMAL</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📊 Statut:</span>
|
||||||
|
<span class="status-badge status-closed">closed</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>📅 Créé:</span>
|
||||||
|
<span>14/01/2026 21:09</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="messages-section">
|
||||||
|
<h3>💬 Messages (0)</h3>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="stats-section">
|
||||||
|
<h3>📊 Statistiques</h3>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Durée</span>
|
||||||
|
<span class="stat-value">6 min</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Messages</span>
|
||||||
|
<span class="stat-value">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">Note</span>
|
||||||
|
<span class="stat-value">Pas de note</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Généré le 14/01/2026 à 21:15:43</p>
|
||||||
|
<p>Système de Tickets - Kuby Bot</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue