import discord from discord.ext import commands, tasks from discord.ui import View, Button, Modal, TextInput import json import os from datetime import datetime import asyncio # --- Fonctions de gestion de données --- def load_config(): """Charge la configuration des tickets""" path = "data/tickets/config.json" if not os.path.exists("data/tickets"): os.makedirs("data/tickets") if not os.path.exists(path): with open(path, "w") as f: json.dump({}, f, indent=4) with open(path, "r") as f: return json.load(f) def save_config(config): """Sauvegarde la configuration des tickets""" path = "data/tickets/config.json" with open(path, "w", encoding="utf-8") as f: json.dump(config, f, indent=4, ensure_ascii=False) # --- Vues (Interactions) --- class TicketView(View): def __init__(self, bot, config): super().__init__(timeout=None) self.bot = bot self.config = config @discord.ui.button(label="Créer un ticket", style=discord.ButtonStyle.blurple, custom_id="create_ticket") async def create_ticket(self, interaction: discord.Interaction, button: Button): await interaction.response.defer(ephemeral=True) guild_id = str(interaction.guild_id) if guild_id not in self.config: return await interaction.followup.send("Le système n'est pas configuré.", ephemeral=True) guild_config = self.config[guild_id] # Vérifier si l'utilisateur a déjà un ticket for channel in interaction.guild.text_channels: if channel.name.startswith(f"ticket-{interaction.user.id}"): return await interaction.followup.send(f"Vous avez déjà un ticket : {channel.mention}", ephemeral=True) # Configuration des permissions category = interaction.guild.get_channel(int(guild_config["category_id"])) overwrites = { interaction.guild.default_role: discord.PermissionOverwrite(read_messages=False), interaction.user: discord.PermissionOverwrite(read_messages=True, send_messages=True, attach_files=True), interaction.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True) } for role_id in guild_config["staff_roles"]: role = interaction.guild.get_role(int(role_id)) if role: overwrites[role] = discord.PermissionOverwrite(read_messages=True, send_messages=True) # Création du salon channel_name = f"ticket-{interaction.user.id}-{datetime.now().strftime('%m%d%H%M')}" ticket_channel = await interaction.guild.create_text_channel( name=channel_name, category=category, overwrites=overwrites, topic=f"Ticket de {interaction.user} ({interaction.user.id})" ) embed = discord.Embed( title="Nouveau Ticket", description=guild_config["ticket_message"], color=discord.Color.blue(), timestamp=datetime.now() ) embed.set_author(name=interaction.user.display_name, icon_url=interaction.user.display_avatar.url) await ticket_channel.send(embed=embed, view=TicketCloseView(self.bot, self.config)) # Logs if guild_config.get("log_channel"): log_ch = self.bot.get_channel(int(guild_config["log_channel"])) if log_ch: log_embed = discord.Embed(title="Ticket Créé", color=discord.Color.green()) log_embed.add_field(name="Auteur", value=interaction.user.mention) log_embed.add_field(name="Salon", value=ticket_channel.mention) await log_ch.send(embed=log_embed) await interaction.followup.send(f"Ticket créé : {ticket_channel.mention}", ephemeral=True) class TicketCloseView(View): def __init__(self, bot, config): super().__init__(timeout=None) self.bot = bot self.config = config @discord.ui.button(label="Fermer le ticket", style=discord.ButtonStyle.red, custom_id="close_ticket") async def close_ticket(self, interaction: discord.Interaction, button: Button): await interaction.response.defer(ephemeral=True) guild_config = self.config.get(str(interaction.guild_id)) # Modifier permissions (bloquer l'écriture) overwrites = interaction.channel.overwrites for target in overwrites: if isinstance(target, discord.Member) and target != interaction.guild.me: overwrites[target] = discord.PermissionOverwrite(read_messages=True, send_messages=False) await interaction.channel.edit(name=f"closed-{interaction.channel.name}", overwrites=overwrites) embed = discord.Embed(title="Ticket Fermé", description=f"Fermé par {interaction.user.mention}", color=discord.Color.red()) await interaction.channel.send(embed=embed, view=TicketReopenView(self.bot, self.config)) await interaction.followup.send("Ticket fermé.", ephemeral=True) class TicketReopenView(View): def __init__(self, bot, config): super().__init__(timeout=None) self.bot = bot self.config = config @discord.ui.button(label="Rouvrir", style=discord.ButtonStyle.green, custom_id="reopen_ticket") async def reopen(self, interaction: discord.Interaction, button: Button): await interaction.response.defer(ephemeral=True) new_name = interaction.channel.name.replace("closed-", "") await interaction.channel.edit(name=new_name) await interaction.channel.send(f"Ticket rouvert par {interaction.user.mention}", view=TicketCloseView(self.bot, self.config)) @discord.ui.button(label="Supprimer", style=discord.ButtonStyle.grey, custom_id="delete_ticket") async def delete(self, interaction: discord.Interaction, button: Button): await interaction.response.send_message("Suppression dans 3s...") await asyncio.sleep(3) await interaction.channel.delete() # --- Modale de Configuration --- class TicketConfigModal(Modal): def __init__(self, bot, config, guild_id): super().__init__(title="Configuration Tickets") self.bot = bot self.config = config self.guild_id = str(guild_id) self.category_id = TextInput(label="ID Catégorie", placeholder="Ex: 123456789", required=True) self.staff_roles = TextInput(label="IDs Rôles Staff (virgule)", placeholder="ID1, ID2", required=True) self.log_channel = TextInput(label="ID Salon Logs", required=False) self.ticket_msg = TextInput(label="Message Ticket", style=discord.TextStyle.paragraph, required=True) self.welcome_msg = TextInput(label="Message Panel", required=True) for item in [self.category_id, self.staff_roles, self.log_channel, self.ticket_msg, self.welcome_msg]: self.add_item(item) async def on_submit(self, interaction: discord.Interaction): role_ids = [r.strip() for r in self.staff_roles.value.split(",")] self.config[self.guild_id] = { "category_id": self.category_id.value, "staff_roles": role_ids, "log_channel": self.log_channel.value, "ticket_message": self.ticket_msg.value, "welcome_message": self.welcome_msg.value } save_config(self.config) await interaction.response.send_message("Configuration sauvegardée !", ephemeral=True) # --- Le Cog Principal --- class Ticket(commands.Cog): def __init__(self, bot): self.bot = bot self.config = load_config() @commands.Cog.listener() async def on_ready(self): # Enregistrer les vues persistantes (pour qu'elles marchent après reboot) self.bot.add_view(TicketView(self.bot, self.config)) self.bot.add_view(TicketCloseView(self.bot, self.config)) self.bot.add_view(TicketReopenView(self.bot, self.config)) print("Module Ticket prêt") @commands.hybrid_group(name="ticket") @commands.has_permissions(administrator=True) async def ticket(self, ctx): if ctx.invoked_subcommand is None: await ctx.send("Utilisez `/ticket setup` ou `/ticket panel`", ephemeral=True) @ticket.command(name="setup") async def setup_cmd(self, ctx): await ctx.interaction.response.send_modal(TicketConfigModal(self.bot, self.config, ctx.guild.id)) @ticket.command(name="panel") async def panel_cmd(self, ctx, channel: discord.TextChannel = None): guild_id = str(ctx.guild.id) if guild_id not in self.config: return await ctx.send("Configurez d'abord le système via `/ticket setup`.") target = channel or ctx.channel embed = discord.Embed( title="Support", description=self.config[guild_id]["welcome_message"], color=discord.Color.blue() ) await target.send(embed=embed, view=TicketView(self.bot, self.config)) await ctx.send("Panel envoyé !", ephemeral=True) async def setup(bot): await bot.add_cog(Ticket(bot))