diff --git a/commandes/goodbye.py b/commandes/goodbye.py index 6782fae..2453c32 100644 --- a/commandes/goodbye.py +++ b/commandes/goodbye.py @@ -28,7 +28,8 @@ class Goodbye(commands.Cog): welcome_banner_background TEXT, welcome_channel_message TEXT, goodbye_server_name TEXT, - goodbye_message TEXT + goodbye_message TEXT, + goodbye_banner_background TEXT ) ''') @@ -42,6 +43,9 @@ class Goodbye(commands.Cog): if 'goodbye_message' not in columns: cursor.execute('ALTER TABLE server_configs ADD COLUMN goodbye_message TEXT') + if 'goodbye_banner_background' not in columns: + cursor.execute('ALTER TABLE server_configs ADD COLUMN goodbye_banner_background TEXT') + conn.commit() conn.close() @@ -73,7 +77,7 @@ class Goodbye(commands.Cog): seconds = delta.seconds % 60 return f"il y a {seconds} {'seconde' if seconds <= 1 else 'secondes'}" - async def create_goodbye_image(self, username, avatar_url, server_name=None): + async def create_goodbye_image(self, username, avatar_url, server_name=None, banner_background=None): current_dir = os.path.dirname(__file__) base_dir = os.path.abspath(os.path.join(current_dir, "..")) @@ -81,7 +85,18 @@ class Goodbye(commands.Cog): async with session.get(avatar_url) as resp: avatar_bytes = await resp.read() - background = Image.open(os.path.join(base_dir, 'background_template_goodbye.png')) + # Utiliser le fond personnalisé ou celui par défaut + bg_path = None + if banner_background: + # On cherche dans le dossier banners/ + potential_path = os.path.join(base_dir, "banners", banner_background) + if os.path.exists(potential_path): + bg_path = potential_path + + if bg_path: + background = Image.open(bg_path) + else: + background = Image.open(os.path.join(base_dir, 'background_template_goodbye.png')) draw = ImageDraw.Draw(background) font_large = ImageFont.truetype(os.path.join(base_dir, 'custom_font_large.otf'), 70) @@ -110,7 +125,7 @@ class Goodbye(commands.Cog): async def on_member_remove(self, member): conn = sqlite3.connect(self.db_path) cursor = conn.cursor() - cursor.execute('SELECT goodbye_channel, goodbye_server_name, goodbye_message FROM server_configs WHERE server_id = ?', (member.guild.id,)) + cursor.execute('SELECT goodbye_channel, goodbye_server_name, goodbye_message, goodbye_banner_background FROM server_configs WHERE server_id = ?', (member.guild.id,)) result = cursor.fetchone() conn.close() @@ -118,13 +133,15 @@ class Goodbye(commands.Cog): goodbye_channel = result[0] server_name = result[1] custom_message = result[2] + banner_background = result[3] channel = member.guild.get_channel(goodbye_channel) if channel: img = await self.create_goodbye_image( member.name, member.avatar.url if member.avatar else member.default_avatar.url, - server_name=server_name + server_name=server_name, + banner_background=banner_background ) joined_message = self.humanize_time_delta(member.joined_at) @@ -138,23 +155,67 @@ class Goodbye(commands.Cog): embed.set_image(url="attachment://goodbye.png") await channel.send(embed=embed, file=discord.File(img, filename="goodbye.png")) + async def save_banner_attachment(self, attachment: discord.Attachment, guild_id: int) -> str: + """Sauvegarde l'attachment et retourne le chemin du fichier""" + base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "banners")) + os.makedirs(base_dir, exist_ok=True) + + # Vérifier que c'est une image + if not attachment.content_type or not attachment.content_type.startswith('image/'): + raise ValueError("Le fichier doit être une image.") + + # Vérifier le format PNG + if not attachment.filename.lower().endswith('.png'): + raise ValueError("Le fichier doit être au format PNG.") + + # Vérifier les dimensions + banner_path = os.path.join(base_dir, f"banner_goodbye_{guild_id}.png") + + async with aiohttp.ClientSession() as session: + async with session.get(attachment.url) as resp: + if resp.status == 200: + # Vérifier les dimensions avant de sauvegarder + img_data = await resp.read() + from io import BytesIO + with Image.open(BytesIO(img_data)) as img: + width, height = img.size + if width != 1000 or height != 300: + raise ValueError(f"Les dimensions doivent être 1000x300 pixels (votre image: {width}x{height})") + + with open(banner_path, 'wb') as f: + f.write(img_data) + + return os.path.basename(banner_path) + @app_commands.command(name="setgoodbye", description="Définit le salon d'aurevoir et les paramètres personnalisés") @app_commands.checks.has_permissions(administrator=True) - async def set_goodbye(self, interaction: discord.Interaction, channel: discord.TextChannel, server_name: str = None, message: str = None): + async def set_goodbye(self, interaction: discord.Interaction, channel: discord.TextChannel, server_name: str = None, message: str = None, banner_file: discord.Attachment = None): + banner_path = None + + # Si un fichier est uploadé, le sauvegarder + if banner_file: + try: + banner_path = await self.save_banner_attachment(banner_file, interaction.guild_id) + except Exception as e: + await interaction.response.send_message(f"❌ Erreur avec le fichier: {e}", ephemeral=True) + return + conn = sqlite3.connect(self.db_path) cursor = conn.cursor() - cursor.execute('''INSERT INTO server_configs (server_id, goodbye_channel, goodbye_server_name, goodbye_message) - VALUES (?, ?, ?, ?) ON CONFLICT(server_id) + cursor.execute('''INSERT INTO server_configs (server_id, goodbye_channel, goodbye_server_name, goodbye_message, goodbye_banner_background) + VALUES (?, ?, ?, ?, ?) ON CONFLICT(server_id) DO UPDATE SET goodbye_channel=excluded.goodbye_channel, goodbye_server_name=COALESCE(excluded.goodbye_server_name, goodbye_server_name), - goodbye_message=COALESCE(excluded.goodbye_message, goodbye_message)''', - (interaction.guild_id, channel.id, server_name, message)) + goodbye_message=COALESCE(excluded.goodbye_message, goodbye_message), + goodbye_banner_background=COALESCE(excluded.goodbye_banner_background, goodbye_banner_background)''', + (interaction.guild_id, channel.id, server_name, message, banner_path)) conn.commit() conn.close() msg = f"✅ Salon d'aurevoir défini sur {channel.mention}" if server_name: msg += f"\nNom du serveur: {server_name}" if message: msg += f"\nMessage: {message}" + if banner_path: msg += f"\nBannière personnalisée activée." await interaction.response.send_message(msg, ephemeral=True) diff --git a/commandes/ticket/__init__.py b/commandes/ticket/__init__.py index 031e34e..209b100 100644 --- a/commandes/ticket/__init__.py +++ b/commandes/ticket/__init__.py @@ -567,7 +567,16 @@ class TicketPriorityModal(discord.ui.Modal): try: category_channel = None - if self.config.default_category: + + # Check if category has a specific Discord category + if self.category.discord_category_id: + try: + category_channel = guild.get_channel(int(self.category.discord_category_id)) + except (ValueError, TypeError): + pass + + # Fallback to default config category if not found or not set + if not category_channel and self.config.default_category: try: category_channel = guild.get_channel(int(self.config.default_category)) except (ValueError, TypeError): @@ -2214,7 +2223,8 @@ class CategoryActionView(discord.ui.View): f"**Emoji:** {self.category.emoji}\n" f"**Description:** {self.category.description or 'Aucune'}\n" f"**Couleur:** `{self.category.color}`\n" - f"**Staff Spécifique:** {', '.join([f'<@&{r}>' for r in self.category.staff_roles]) if self.category.staff_roles else 'Global (Tous les staffs)'}", + f"**Staff Spécifique:** {', '.join([f'<@&{r}>' for r in self.category.staff_roles]) if self.category.staff_roles else 'Global (Tous les staffs)'}\n" + f"**Catégorie Discord:** {f'<#{self.category.discord_category_id}>' if self.category.discord_category_id else 'Par défaut'}", color=discord.Color.blue() ) return embed @@ -2236,6 +2246,17 @@ class CategoryActionView(discord.ui.View): ) await interaction.response.edit_message(embed=embed, view=view) + @discord.ui.button(label="📁 Catégorie Discord", style=discord.ButtonStyle.secondary) + async def discord_category_callback(self, interaction: discord.Interaction, button: discord.ui.Button): + view = CategoryDiscordCategoryView(self.bot, self.storage, self.config, self.category, self) + embed = discord.Embed( + title=f"Catégorie Discord: {self.category.name}", + description="Choisissez la catégorie Discord (dossier) dans laquelle les tickets de ce type seront créés.\n\n" + "Si aucune n'est sélectionnée, la catégorie par défaut du serveur sera utilisée.", + color=discord.Color.blue() + ) + await interaction.response.edit_message(embed=embed, view=view) + @discord.ui.button(label="🗑️ Supprimer", style=discord.ButtonStyle.danger) async def delete_callback(self, interaction: discord.Interaction, button: discord.ui.Button): view = DeleteCategoryConfirmationView(self.bot, self.storage, self.config, self.category, self.parent_view) @@ -2276,6 +2297,33 @@ class CategoryPermissionsView(discord.ui.View): async def back_callback(self, interaction: discord.Interaction, button: discord.ui.Button): await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view) +class CategoryDiscordCategoryView(discord.ui.View): + """Vue pour configurer la catégorie Discord d'une catégorie de ticket""" + + def __init__(self, bot, storage, config, category, parent_view): + super().__init__(timeout=None) + self.bot = bot + self.storage = storage + self.config = config + self.category = category + self.parent_view = parent_view + + @discord.ui.select(cls=discord.ui.ChannelSelect, channel_types=[discord.ChannelType.category], placeholder="Sélectionner la catégorie Discord") + async def select_callback(self, interaction: discord.Interaction, select: discord.ui.ChannelSelect): + self.category.discord_category_id = select.values[0].id + self.storage.save_config(interaction.guild_id, self.config) + await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view) + + @discord.ui.button(label="❌ Réinitialiser", style=discord.ButtonStyle.danger) + async def reset_callback(self, interaction: discord.Interaction, button: discord.ui.Button): + self.category.discord_category_id = None + self.storage.save_config(interaction.guild_id, self.config) + await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view) + + @discord.ui.button(label="⬅️ Retour", style=discord.ButtonStyle.secondary) + async def back_callback(self, interaction: discord.Interaction, button: discord.ui.Button): + await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view) + class DeleteCategoryConfirmationView(discord.ui.View): def __init__(self, bot, storage, config, category, parent_view): diff --git a/data/json-export-history.json b/data/json-export-history.json index 6d6b331..262fa2b 100644 --- a/data/json-export-history.json +++ b/data/json-export-history.json @@ -1,4 +1,13 @@ [ + { + "date": "2026-05-01T14:11:08.947818+00:00", + "trigger": "auto", + "serversCount": 1, + "membersCount": 12, + "guildName": "Omega Kube Bêta Test", + "errors": [], + "success": true + }, { "date": "2026-05-01T13:48:18.180388+00:00", "trigger": "auto", @@ -169,14 +178,5 @@ "guildName": "Omega Kube Bêta Test", "errors": [], "success": true - }, - { - "date": "2026-04-01T09:07:49.861748+00:00", - "trigger": "auto", - "serversCount": 1, - "membersCount": 12, - "guildName": "Omega Kube Bêta Test", - "errors": [], - "success": true } ] \ No newline at end of file