661 lines
24 KiB
Python
661 lines
24 KiB
Python
"""
|
||
Modular Ticket System
|
||
=====================
|
||
A complete, beautiful, and modular ticket system for Discord bots.
|
||
|
||
Features:
|
||
- Multiple ticket categories with custom settings
|
||
- Priority levels (Low/Normal/High/Urgent)
|
||
- Claim system for staff
|
||
- Transcript generation (TXT, JSON, HTML)
|
||
- Satisfaction surveys
|
||
- Statistics and reporting
|
||
- Bulk management
|
||
- Auto-close inactive tickets
|
||
- Rich embeds and beautiful UI
|
||
"""
|
||
import os
|
||
import sys
|
||
|
||
# Add parent directory to path for imports
|
||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||
|
||
import discord
|
||
from discord.ext import commands, tasks
|
||
from datetime import datetime
|
||
from typing import Optional
|
||
|
||
from .data.storage import get_storage
|
||
from .data.models import GuildTicketConfig, TicketCategory, TicketStatus, Priority
|
||
from .utils.formatters import TicketEmbedFormatter
|
||
from .utils.transcript import get_transcript_generator
|
||
from .views.creation import (
|
||
TicketCreationView,
|
||
TicketManagementView,
|
||
SurveyView,
|
||
)
|
||
from .views.admin import (
|
||
AdminPanelView,
|
||
ConfigPanelView,
|
||
RolesManagementView,
|
||
)
|
||
|
||
|
||
class Ticket(commands.Cog):
|
||
"""
|
||
Main ticket cog with all functionality.
|
||
"""
|
||
|
||
def __init__(self, bot):
|
||
self.bot = bot
|
||
self.storage = get_storage()
|
||
self._register_views.start()
|
||
self._auto_close_check.start()
|
||
|
||
def cog_unload(self):
|
||
self._register_views.cancel()
|
||
self._auto_close_check.cancel()
|
||
|
||
async def cog_load(self):
|
||
"""Register persistent views when cog is loaded"""
|
||
await self._register_all_views()
|
||
|
||
async def _register_all_views(self):
|
||
"""Register all persistent views for all configured guilds"""
|
||
try:
|
||
for guild_id in self.storage.get_all_guild_ids():
|
||
try:
|
||
config = self.storage.load_config(guild_id)
|
||
if config and config.enabled:
|
||
# Register creation view (with categories)
|
||
creation_view = TicketCreationView(self.bot, config)
|
||
self.bot.add_view(creation_view)
|
||
|
||
# Register management view (no config needed for persistence)
|
||
management_view = TicketManagementView(self.bot, self.storage, config)
|
||
self.bot.add_view(management_view)
|
||
|
||
# Register admin views
|
||
admin_view = AdminPanelView(self.bot, config)
|
||
self.bot.add_view(admin_view)
|
||
|
||
config_view = ConfigPanelView(self.bot, config)
|
||
self.bot.add_view(config_view)
|
||
|
||
roles_view = RolesManagementView(self.bot, config)
|
||
self.bot.add_view(roles_view)
|
||
|
||
print(f"[Ticket] Views registered for guild {guild_id}")
|
||
except Exception as e:
|
||
print(f"[Ticket] Error registering views for guild {guild_id}: {e}")
|
||
except Exception as e:
|
||
print(f"[Ticket] Error in _register_all_views: {e}")
|
||
|
||
@tasks.loop(seconds=30)
|
||
async def _register_views(self):
|
||
"""Register persistent views periodically"""
|
||
await self._register_all_views()
|
||
|
||
@tasks.loop(minutes=5)
|
||
async def _auto_close_check(self):
|
||
"""Check for tickets to auto-close"""
|
||
try:
|
||
for guild_id in self.storage.get_all_guild_ids():
|
||
config = self.storage.load_config(guild_id)
|
||
if not config.auto_close_enabled:
|
||
continue
|
||
|
||
auto_close_days = config.auto_close_days or 7
|
||
auto_close_threshold = datetime.now().timestamp() - (auto_close_days * 86400)
|
||
|
||
tickets = self.storage.get_guild_tickets(guild_id, TicketStatus.OPEN)
|
||
for ticket in tickets:
|
||
if ticket.created_at.timestamp() < auto_close_threshold:
|
||
# Auto-close ticket
|
||
ticket.status = TicketStatus.CLOSED
|
||
ticket.closed_at = datetime.now()
|
||
self.storage.save_ticket(ticket)
|
||
|
||
# Log if configured
|
||
if config.log_channel_id:
|
||
log_channel = self.bot.get_guild(guild_id).get_channel(config.log_channel_id)
|
||
if log_channel:
|
||
await log_channel.send(
|
||
f"🔒 Ticket auto-fermé (inactif depuis {auto_close_days} jours)"
|
||
)
|
||
except Exception as e:
|
||
print(f"[Ticket] Auto-close error: {e}")
|
||
|
||
@commands.Cog.listener()
|
||
async def on_ready(self):
|
||
print("🎫 Module Ticket chargé avec succès!")
|
||
|
||
# --- Main Commands ---
|
||
|
||
@commands.hybrid_group(name="ticket", aliases=["tickets", "ticketing"])
|
||
async def ticket_group(self, ctx):
|
||
"""Système de tickets complet"""
|
||
if ctx.invoked_subcommand is None:
|
||
await ctx.send_help(ctx.command)
|
||
|
||
# --- Setup Commands ---
|
||
|
||
@ticket_group.command(name="setup", aliases=["configurer"])
|
||
@commands.has_permissions(administrator=True)
|
||
async def ticket_setup(self, ctx):
|
||
"""Configurer le système de tickets"""
|
||
await ctx.defer(ephemeral=True)
|
||
|
||
config = self.storage.load_config(ctx.guild.id)
|
||
|
||
# Send setup panel
|
||
embed = discord.Embed(
|
||
title="⚙️ Configuration des Tickets",
|
||
description="Bienvenue dans le panneau de configuration des tickets!",
|
||
color=discord.Color.blue()
|
||
)
|
||
embed.add_field(name="📁 Catégories", value=str(len(config.categories)), inline=True)
|
||
embed.add_field(name="👥 Rôles Staff", value=str(len(config.staff_roles)), inline=True)
|
||
embed.add_field(name="📊 Canal Logs", value=f"<#{config.log_channel_id}>" if config.log_channel_id else "Non configuré", inline=True)
|
||
|
||
view = TicketSetupView(self.bot, self.storage, config)
|
||
await ctx.send(embed=embed, view=view, ephemeral=True)
|
||
|
||
@ticket_group.command(name="panel", aliases=["panneau"])
|
||
async def ticket_panel(self, ctx, channel: discord.TextChannel = None):
|
||
"""Créer un panneau de création de tickets"""
|
||
await ctx.defer(ephemeral=True)
|
||
|
||
config = self.storage.load_config(ctx.guild.id)
|
||
|
||
if not config.enabled:
|
||
await ctx.send("Le système de tickets est désactivé.", ephemeral=True)
|
||
return
|
||
|
||
if not config.categories:
|
||
await ctx.send("Aucune catégorie configurée. Utilisez `/ticket setup` d'abord.", ephemeral=True)
|
||
return
|
||
|
||
target = channel or ctx.channel
|
||
|
||
embed = TicketEmbedFormatter.panel_embed(config, ctx.guild.name)
|
||
view = TicketCreationView(self.bot, config)
|
||
|
||
await target.send(embed=embed, view=view)
|
||
await ctx.send(f"Panneau envoyé dans {target.mention}", ephemeral=True)
|
||
|
||
# --- Info Commands ---
|
||
|
||
@ticket_group.command(name="info", aliases=["informations"])
|
||
async def ticket_info(self, ctx, channel: discord.TextChannel = None):
|
||
"""Voir les informations d'un ticket"""
|
||
await ctx.defer(ephemeral=True)
|
||
|
||
target = channel or ctx.channel
|
||
|
||
storage = get_storage()
|
||
ticket = storage.load_ticket(target.id)
|
||
|
||
if not ticket:
|
||
await ctx.send("Ce salon n'est pas un ticket.", ephemeral=True)
|
||
return
|
||
|
||
user = self.bot.get_user(ticket.user_id)
|
||
category = storage.load_config(ctx.guild.id).get_category(ticket.category_id)
|
||
|
||
embed = TicketEmbedFormatter.ticket_info_embed(ticket, user, category)
|
||
|
||
await ctx.send(embed=embed, ephemeral=True)
|
||
|
||
@ticket_group.command(name="list", aliases=["liste"])
|
||
async def ticket_list(self, ctx, user: discord.Member = None):
|
||
"""Liste des tickets"""
|
||
await ctx.defer(ephemeral=True)
|
||
|
||
storage = get_storage()
|
||
|
||
if user:
|
||
tickets = storage.get_user_tickets(ctx.guild.id, user.id, open_only=False)
|
||
else:
|
||
tickets = storage.get_guild_tickets(ctx.guild.id)
|
||
|
||
if not tickets:
|
||
await ctx.send("Aucun ticket trouvé.", ephemeral=True)
|
||
return
|
||
|
||
embed = TicketEmbedFormatter.ticket_list_embed(tickets, user)
|
||
|
||
await ctx.send(embed=embed, ephemeral=True)
|
||
|
||
# --- Stats Command ---
|
||
|
||
@ticket_group.command(name="stats", aliases=["statistiques"])
|
||
async def ticket_stats(self, ctx):
|
||
"""Voir les statistiques des tickets"""
|
||
await ctx.defer(ephemeral=True)
|
||
|
||
config = self.storage.load_config(ctx.guild.id)
|
||
|
||
stats = self._calculate_stats(ctx.guild.id)
|
||
embed = TicketEmbedFormatter.stats_embed(stats, ctx.guild.name)
|
||
|
||
await ctx.send(embed=embed, ephemeral=True)
|
||
|
||
def _calculate_stats(self, guild_id):
|
||
"""Calculate ticket statistics"""
|
||
from .data.models import TicketStats
|
||
|
||
storage = get_storage()
|
||
tickets = storage.get_guild_tickets(guild_id)
|
||
|
||
stats = TicketStats()
|
||
stats.total_tickets = len(tickets)
|
||
|
||
total_duration = 0
|
||
closed_count = 0
|
||
rating_sum = 0
|
||
rating_count = 0
|
||
|
||
for ticket in tickets:
|
||
if ticket.status == TicketStatus.OPEN:
|
||
stats.open_tickets += 1
|
||
elif ticket.status == TicketStatus.CLAIMED:
|
||
stats.claimed_tickets += 1
|
||
stats.open_tickets += 1
|
||
elif ticket.status == TicketStatus.CLOSED:
|
||
stats.closed_tickets += 1
|
||
closed_count += 1
|
||
|
||
stats.tickets_by_category[ticket.category_id] = \
|
||
stats.tickets_by_category.get(ticket.category_id, 0) + 1
|
||
|
||
stats.tickets_by_priority[ticket.priority.value] = \
|
||
stats.tickets_by_priority.get(ticket.priority.value, 0) + 1
|
||
|
||
if ticket.duration_minutes:
|
||
total_duration += ticket.duration_minutes
|
||
|
||
if ticket.rating:
|
||
rating_sum += ticket.rating
|
||
rating_count += 1
|
||
|
||
if closed_count > 0:
|
||
stats.average_duration_minutes = total_duration / closed_count
|
||
|
||
if rating_count > 0:
|
||
stats.average_rating = rating_sum / rating_count
|
||
|
||
return stats
|
||
|
||
# --- Admin Commands ---
|
||
|
||
@ticket_group.command(name="admin", aliases=["administration"])
|
||
@commands.has_permissions(administrator=True)
|
||
async def ticket_admin(self, ctx):
|
||
"""Panneau d'administration des tickets"""
|
||
await ctx.defer(ephemeral=True)
|
||
|
||
config = self.storage.load_config(ctx.guild.id)
|
||
view = AdminPanelView(self.bot, config)
|
||
|
||
embed = discord.Embed(
|
||
title="🎛️ Administration des Tickets",
|
||
description="Gérez votre système de tickets ici:",
|
||
color=discord.Color.blue()
|
||
)
|
||
embed.add_field(name="📊 Statistiques", value="Voir les stats", inline=True)
|
||
embed.add_field(name="🔴 Fermeture Massive", value="Fermer plusieurs tickets", inline=True)
|
||
embed.add_field(name="⚙️ Configuration", value="Configurer le système", inline=True)
|
||
embed.add_field(name="🧹 Nettoyer", value="Supprimer les vieux tickets", inline=True)
|
||
|
||
await ctx.send(embed=embed, view=view, ephemeral=True)
|
||
|
||
@ticket_group.command(name="transcript", aliases=["transcription"])
|
||
async def ticket_transcript(self, ctx, channel: discord.TextChannel = None):
|
||
"""Générer un transcript pour un ticket"""
|
||
await ctx.defer(ephemeral=True)
|
||
|
||
target = channel or ctx.channel
|
||
|
||
storage = get_storage()
|
||
ticket = storage.load_ticket(target.id)
|
||
|
||
if not ticket:
|
||
await ctx.send("Ce salon n'est pas un ticket.", ephemeral=True)
|
||
return
|
||
|
||
# Collect messages
|
||
messages = []
|
||
async for msg in target.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(),
|
||
"attachments": [a.url for a in msg.attachments],
|
||
"is_staff": self._is_staff(msg.author, ctx.guild)
|
||
})
|
||
|
||
# Generate transcript
|
||
transcript_gen = get_transcript_generator()
|
||
filepath = transcript_gen.save_transcript(ticket, messages, format="html")
|
||
|
||
if filepath:
|
||
await ctx.send(f"✅ Transcript sauvegardé: `{filepath}`", ephemeral=True)
|
||
else:
|
||
await ctx.send("❌ Erreur lors de la génération.", ephemeral=True)
|
||
|
||
def _is_staff(self, member, guild) -> bool:
|
||
"""Check if member is staff"""
|
||
config = self.storage.load_config(guild.id)
|
||
for role_id in config.staff_roles:
|
||
role = guild.get_role(int(role_id))
|
||
if role and role in member.roles:
|
||
return True
|
||
return False
|
||
|
||
# --- Category Management ---
|
||
|
||
@ticket_group.command(name="category", aliases=["categorie"])
|
||
@commands.has_permissions(administrator=True)
|
||
async def ticket_category(self, ctx, action: str, category_name: str = None):
|
||
"""Gérer les catégories de tickets (add/remove/list)"""
|
||
await ctx.defer(ephemeral=True)
|
||
|
||
config = self.storage.load_config(ctx.guild.id)
|
||
|
||
if action == "list":
|
||
if not config.categories:
|
||
await ctx.send("Aucune catégorie configurée.", ephemeral=True)
|
||
return
|
||
|
||
embed = discord.Embed(
|
||
title="📁 Catégories de Tickets",
|
||
color=discord.Color.blue()
|
||
)
|
||
|
||
for cat in config.categories:
|
||
embed.add_field(
|
||
name=f"{cat.emoji} {cat.name}",
|
||
value=f"ID: `{cat.id}`\n{cat.description or 'Aucune description'}",
|
||
inline=False
|
||
)
|
||
|
||
await ctx.send(embed=embed, ephemeral=True)
|
||
|
||
elif action == "add":
|
||
if not category_name:
|
||
await ctx.send("Nom de catégorie requis.", ephemeral=True)
|
||
return
|
||
|
||
import uuid
|
||
cat_id = str(uuid.uuid4())[:8]
|
||
|
||
category = TicketCategory(
|
||
id=cat_id,
|
||
name=category_name,
|
||
description="Nouvelle catégorie",
|
||
emoji="🎫"
|
||
)
|
||
|
||
config.categories.append(category)
|
||
self.storage.save_config(ctx.guild.id, config)
|
||
|
||
await ctx.send(f"Catégorie **{category_name}** créée!", ephemeral=True)
|
||
|
||
elif action == "remove":
|
||
if not category_name:
|
||
await ctx.send("Nom de catégorie requis.", ephemeral=True)
|
||
return
|
||
|
||
config.categories = [c for c in config.categories if c.name.lower() != category_name.lower()]
|
||
self.storage.save_config(ctx.guild.id, config)
|
||
|
||
await ctx.send(f"Catégorie **{category_name}** supprimée.", ephemeral=True)
|
||
|
||
else:
|
||
await ctx.send("Action invalide. Utilisez: list, add, remove", ephemeral=True)
|
||
|
||
# --- Close/Reopen ---
|
||
|
||
@ticket_group.command(name="close", aliases=["fermer"])
|
||
async def ticket_close(self, ctx, channel: discord.TextChannel = None, *, reason: str = None):
|
||
"""Fermer un ticket"""
|
||
await ctx.defer(ephemeral=True)
|
||
|
||
target = channel or ctx.channel
|
||
|
||
storage = get_storage()
|
||
ticket = storage.load_ticket(target.id)
|
||
|
||
if not ticket:
|
||
await ctx.send("Ce salon n'est pas un ticket.", ephemeral=True)
|
||
return
|
||
|
||
# Check permissions
|
||
can_close = (
|
||
ctx.author.id == ticket.user_id or
|
||
self._is_staff(ctx.author, ctx.guild)
|
||
)
|
||
|
||
if not can_close:
|
||
await ctx.send("Vous ne pouvez pas fermer ce ticket.", ephemeral=True)
|
||
return
|
||
|
||
# Close ticket
|
||
ticket.status = TicketStatus.CLOSED
|
||
ticket.closed_at = datetime.now()
|
||
storage.save_ticket(ticket)
|
||
|
||
# Update channel
|
||
overwrites = target.overwrites.copy()
|
||
for target_perm, perms in overwrites.items():
|
||
if isinstance(target_perm, discord.Member) and target_perm.id != ctx.guild.me.id:
|
||
overwrites[target_perm] = discord.PermissionOverwrite(
|
||
read_messages=True,
|
||
send_messages=False
|
||
)
|
||
|
||
await target.edit(
|
||
name=f"closed-{target.name}",
|
||
overwrites=overwrites
|
||
)
|
||
|
||
embed = TicketEmbedFormatter.ticket_closed(ticket, ctx.author, ticket.duration_minutes, reason)
|
||
await target.send(embed=embed)
|
||
|
||
await ctx.send("Ticket fermé!", ephemeral=True)
|
||
|
||
@ticket_group.command(name="reopen", aliases=["rouvrir"])
|
||
async def ticket_reopen(self, ctx, channel: discord.TextChannel = None):
|
||
"""Rouvrir un ticket fermé"""
|
||
await ctx.defer(ephemeral=True)
|
||
|
||
target = channel or ctx.channel
|
||
|
||
storage = get_storage()
|
||
ticket = storage.load_ticket(target.id)
|
||
|
||
if not ticket:
|
||
await ctx.send("Ce salon n'est pas un ticket.", ephemeral=True)
|
||
return
|
||
|
||
if ticket.status != TicketStatus.CLOSED:
|
||
await ctx.send("Ce ticket n'est pas fermé.", ephemeral=True)
|
||
return
|
||
|
||
# Check permissions
|
||
can_reopen = (
|
||
ctx.author.id == ticket.user_id or
|
||
self._is_staff(ctx.author, ctx.guild)
|
||
)
|
||
|
||
if not can_reopen:
|
||
await ctx.send("Vous ne pouvez pas rouvrir ce ticket.", ephemeral=True)
|
||
return
|
||
|
||
# Reopen ticket
|
||
ticket.status = TicketStatus.OPEN
|
||
storage.save_ticket(ticket)
|
||
|
||
# Update channel name
|
||
new_name = target.name.replace("closed-", "")
|
||
await target.edit(name=new_name)
|
||
|
||
embed = TicketEmbedFormatter.ticket_reopened(ticket, ctx.author)
|
||
await target.send(embed=embed)
|
||
|
||
await ctx.send("Ticket rouvert!", ephemeral=True)
|
||
|
||
|
||
class TicketSetupView(discord.ui.View):
|
||
"""View for initial ticket setup"""
|
||
|
||
def __init__(self, bot, storage, config: GuildTicketConfig):
|
||
super().__init__(timeout=None) # Persistent view
|
||
self.bot = bot
|
||
self.storage = storage
|
||
self.config = config
|
||
|
||
@discord.ui.button(label="➕ Ajouter Catégorie", style=discord.ButtonStyle.blurple, custom_id="ticket_setup_add_category")
|
||
async def add_category(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||
modal = AddCategoryModal(self.bot, self.storage, self.config)
|
||
await interaction.response.send_modal(modal)
|
||
|
||
@discord.ui.button(label="👥 Configurer Rôles Staff", style=discord.ButtonStyle.secondary, custom_id="ticket_setup_config_roles")
|
||
async def config_roles(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||
from .views.admin import RolesManagementView
|
||
|
||
view = RolesManagementView(self.bot, self.config)
|
||
|
||
role_mentions = []
|
||
for role_id in self.config.staff_roles:
|
||
role = interaction.guild.get_role(int(role_id))
|
||
if role:
|
||
role_mentions.append(role.mention)
|
||
|
||
embed = discord.Embed(
|
||
title="👥 Gestion des Rôles Staff",
|
||
description="Sélectionnez les rôles qui auront accès aux tickets:",
|
||
color=discord.Color.blue()
|
||
)
|
||
embed.add_field(
|
||
name="Rôles actuels",
|
||
value=", ".join(role_mentions) if role_mentions else "Aucun",
|
||
inline=False
|
||
)
|
||
|
||
await interaction.response.send_message(embed=embed, view=view, ephemeral=True)
|
||
|
||
@discord.ui.button(label="📊 Définir Canal Logs", style=discord.ButtonStyle.secondary, custom_id="ticket_setup_config_logs")
|
||
async def config_logs(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||
modal = LogChannelModal(self.storage, self.config)
|
||
await interaction.response.send_modal(modal)
|
||
|
||
@discord.ui.button(label="✅ Système Activé", style=discord.ButtonStyle.green, custom_id="ticket_setup_toggle_system")
|
||
async def enable_system(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||
self.config.enabled = not self.config.enabled
|
||
self.storage.save_config(interaction.guild.id, self.config)
|
||
|
||
if self.config.enabled:
|
||
button.style = discord.ButtonStyle.green
|
||
button.label = "✅ Système Activé"
|
||
status_msg = "Système de tickets activé!"
|
||
else:
|
||
button.style = discord.ButtonStyle.red
|
||
button.label = "❌ Système Désactivé"
|
||
status_msg = "Système de tickets désactivé!"
|
||
|
||
await interaction.response.edit_message(view=self)
|
||
await interaction.followup.send(status_msg, ephemeral=True)
|
||
|
||
|
||
class AddCategoryModal(discord.ui.Modal):
|
||
"""Modal for adding a category"""
|
||
|
||
def __init__(self, bot, storage, config: GuildTicketConfig):
|
||
super().__init__(title="➕ Nouvelle Catégorie")
|
||
self.bot = bot
|
||
self.storage = storage
|
||
self.config = config
|
||
|
||
self.name = discord.ui.TextInput(
|
||
label="Nom",
|
||
placeholder="Ex: Support",
|
||
required=True
|
||
)
|
||
self.add_item(self.name)
|
||
|
||
self.description = discord.ui.TextInput(
|
||
label="Description",
|
||
placeholder="Description de la catégorie...",
|
||
required=False,
|
||
style=discord.TextStyle.paragraph
|
||
)
|
||
self.add_item(self.description)
|
||
|
||
self.emoji = discord.ui.TextInput(
|
||
label="Emoji",
|
||
placeholder="🎫",
|
||
required=False,
|
||
max_length=5
|
||
)
|
||
self.add_item(self.emoji)
|
||
|
||
async def on_submit(self, interaction: discord.Interaction):
|
||
import uuid
|
||
|
||
cat_id = str(uuid.uuid4())[:8]
|
||
|
||
category = TicketCategory(
|
||
id=cat_id,
|
||
name=self.name.value,
|
||
description=self.description.value or "",
|
||
emoji=self.emoji.value or "🎫"
|
||
)
|
||
|
||
self.config.categories.append(category)
|
||
|
||
if not self.config.default_category:
|
||
self.config.default_category = cat_id
|
||
|
||
self.storage.save_config(interaction.guild.id, self.config)
|
||
|
||
await interaction.response.send_message(
|
||
f"Catégorie **{self.name.value}** créée avec succès!",
|
||
ephemeral=True
|
||
)
|
||
|
||
|
||
class LogChannelModal(discord.ui.Modal):
|
||
"""Modal for setting log channel"""
|
||
|
||
def __init__(self, storage, config: GuildTicketConfig):
|
||
super().__init__(title="📊 Canal Logs")
|
||
self.storage = storage
|
||
self.config = config
|
||
|
||
self.channel_id = discord.ui.TextInput(
|
||
label="ID du Canal",
|
||
placeholder="Ex: 123456789012345678",
|
||
required=True
|
||
)
|
||
self.add_item(self.channel_id)
|
||
|
||
async def on_submit(self, interaction: discord.Interaction):
|
||
try:
|
||
channel_id = int(self.channel_id.value)
|
||
self.config.log_channel_id = channel_id
|
||
self.storage.save_config(interaction.guild.id, self.config)
|
||
await interaction.response.send_message(f"Canal logs défini: <#{channel_id}>", ephemeral=True)
|
||
except ValueError:
|
||
await interaction.response.send_message("ID de canal invalide.", ephemeral=True)
|
||
|
||
|
||
async def setup(bot):
|
||
"""Setup function for the cog"""
|
||
await bot.add_cog(Ticket(bot))
|
||
|