import os import json import random import hashlib import discord from discord import app_commands from discord.ext import commands from utils.guild_config import ( has_role, get_role_id, load_guild_config, save_guild_config, set_config_value, ROLE_LABELS, CHANNEL_LABELS, CATEGORY_LABELS, ) from utils.bank_manager import BankManager bank_manager = BankManager() base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) C_MAIN = 0x2C2F33 C_BANK = 0x1ABC9C C_BANQ_OP = 0x2ECC71 C_GOV = 0xD4AF37 C_ID = 0x3498DB C_TICKET = 0xE74C3C C_SESSION = 0x9B59B6 C_CONFIG = 0x95A5A6 C_FUN = 0xE67E22 def _uid(seed: str) -> str: return hashlib.md5(seed.encode()).hexdigest()[:12] def _btn(label, style, cid, cb, emoji=None): b = discord.ui.Button(label=label, style=style, custom_id=cid, emoji=emoji) b.callback = cb return b def _back_btn(cid, cb): return _btn("Retour", discord.ButtonStyle.secondary, cid, cb, emoji="◀️") async def _get_user_bank(guild, user) -> dict: banques = await bank_manager.lister_banques(guild.id) for b in banques: role = guild.get_role(b["role_id"]) if role and role in user.roles: return b return None # ═══════════════════════════════════════════════════════════════════ # MODALS # ═══════════════════════════════════════════════════════════════════ class VirementModal(discord.ui.Modal, title="💸 Virement"): rib_dest = discord.ui.TextInput(label="RIB destinataire", required=True, max_length=30) montant = discord.ui.TextInput(label="Montant (EUR)", required=True, max_length=15) def __init__(self, source_rib=None): super().__init__() self.source_rib = source_rib async def on_submit(self, interaction: discord.Interaction): try: montant = float(self.montant.value.replace(",", ".").strip()) except ValueError: await interaction.response.send_message("Montant invalide.", ephemeral=True) return rib_dest = self.rib_dest.value.strip().replace(" ", "") try: result = await bank_manager.virement( guild_id=interaction.guild.id, rib_source=self.source_rib, rib_dest=rib_dest, montant=montant, emetteur_id=interaction.user.id, ) msg = f"Virement de **{montant:.2f}EUR** envoye." if result["tva_prelevee"] > 0: msg += f"\nTVA prelevee : **{result['tva_prelevee']:.2f}EUR**" await interaction.response.send_message(msg, ephemeral=True) except ValueError as e: await interaction.response.send_message(str(e), ephemeral=True) class DepotRetraitModal(discord.ui.Modal): montant = discord.ui.TextInput(label="Montant (EUR)", required=True, max_length=15) def __init__(self, rib: str, operation: str): super().__init__(title=f"{'Depot' if operation == 'depot' else 'Retrait'}") self.rib = rib self.operation = operation async def on_submit(self, interaction: discord.Interaction): try: montant = float(self.montant.value.replace(",", ".").strip()) except ValueError: await interaction.response.send_message("Montant invalide.", ephemeral=True) return try: if self.operation == "depot": result = await bank_manager.depot_banquier(interaction.guild.id, self.rib, montant, interaction.user.id) else: result = await bank_manager.retrait_banquier(interaction.guild.id, self.rib, montant, interaction.user.id) await interaction.response.send_message( f"{self.operation.title()} de **{montant:.2f}EUR**. Nouveau solde : **{result['nouveau_solde']:.2f}EUR**", ephemeral=True, ) except ValueError as e: await interaction.response.send_message(str(e), ephemeral=True) class OuvrirCompteModal(discord.ui.Modal, title="Ouvrir un Compte"): membre_id = discord.ui.TextInput(label="ID du membre", required=True, max_length=20) type_compte = discord.ui.TextInput(label="Type (particulier / entreprise)", required=True, max_length=15) taux_tva = discord.ui.TextInput(label="TVA % (entreprise, 0 sinon)", required=False, max_length=6) def __init__(self, bank_data=None): super().__init__() self.bank_data = bank_data async def on_submit(self, interaction: discord.Interaction): try: membre_id = int(self.membre_id.value.strip()) except ValueError: await interaction.response.send_message("ID invalide.", ephemeral=True) return member = interaction.guild.get_member(membre_id) if not member: await interaction.response.send_message("Membre introuvable.", ephemeral=True) return type_val = self.type_compte.value.strip().lower() if type_val not in ("particulier", "entreprise"): await interaction.response.send_message("Type invalide.", ephemeral=True) return tva = 0.0 if type_val == "entreprise" and self.taux_tva.value: try: tva = float(self.taux_tva.value.replace(",", ".").strip()) except ValueError: pass bank = self.bank_data if not bank: bank = await _get_user_bank(interaction.guild, interaction.user) if not bank: await interaction.response.send_message("Aucune banque associee.", ephemeral=True) return try: result = await bank_manager.ouvrir_compte(interaction.guild.id, bank["id"], member.id, type_val, tva) embed = discord.Embed( title="Compte ouvert", description=( f"**Banque :** {bank['name']}\n" f"**Membre :** {member.mention}\n" f"**Type :** {type_val.title()}\n" f"**RIB :** `{result['rib']}`" ), color=discord.Color.green(), ) await interaction.response.send_message(embed=embed, ephemeral=True) try: await member.send( f"Votre compte **{type_val}** chez **{bank['name']}** a ete ouvert.\n" f"**RIB :** `{result['rib']}`" ) except discord.Forbidden: pass except ValueError as e: await interaction.response.send_message(str(e), ephemeral=True) class CreerBanqueModal(discord.ui.Modal, title="Creer une Banque"): nom = discord.ui.TextInput(label="Nom de la banque", required=True, max_length=50) role_id = discord.ui.TextInput(label="ID du role banquier", required=True, max_length=20) async def on_submit(self, interaction: discord.Interaction): if not has_role(interaction.user, "gouvernement"): await interaction.response.send_message("Reserve au gouvernement.", ephemeral=True) return try: rid = int(self.role_id.value.strip()) except ValueError: await interaction.response.send_message("ID invalide.", ephemeral=True) return role = interaction.guild.get_role(rid) if not role: await interaction.response.send_message("Role introuvable.", ephemeral=True) return try: result = await bank_manager.creer_banque(interaction.guild.id, self.nom.value.strip(), rid) embed = discord.Embed( title="Banque creee", description=( f"**Nom :** {result['name']}\n" f"**Role :** {role.mention}\n" f"**Code etablissement :** `{result['establishment_code']}`" ), color=discord.Color.green(), ) await interaction.response.send_message(embed=embed, ephemeral=True) except ValueError as e: await interaction.response.send_message(str(e), ephemeral=True) class CreerInstitutionModal(discord.ui.Modal, title="Creer une Institution"): nom = discord.ui.TextInput(label="Nom de l'institution", required=True, max_length=50) role_id = discord.ui.TextInput(label="ID du role (optionnel)", required=False, max_length=20) audit_all = discord.ui.TextInput(label="Audit global ? (oui/non)", required=False, max_length=3) async def on_submit(self, interaction: discord.Interaction): if not has_role(interaction.user, "gouvernement"): await interaction.response.send_message("Reserve au gouvernement.", ephemeral=True) return rid = None if self.role_id.value: try: rid = int(self.role_id.value.strip()) except ValueError: pass can_audit = self.audit_all.value.strip().lower() in ("oui", "yes", "true", "1") try: result = await bank_manager.creer_institution(interaction.guild.id, self.nom.value.strip(), rid, can_audit) await interaction.response.send_message( f"Institution **{result['name']}** creee (ID: `{result['id']}`).", ephemeral=True, ) except Exception as e: await interaction.response.send_message(str(e), ephemeral=True) class ConfigFiscModal(discord.ui.Modal, title="Configurer le Fisc"): institution_id = discord.ui.TextInput(label="ID de l'institution fisc", required=True, max_length=10) async def on_submit(self, interaction: discord.Interaction): if not has_role(interaction.user, "gouvernement"): await interaction.response.send_message("Reserve au gouvernement.", ephemeral=True) return try: iid = int(self.institution_id.value.strip()) except ValueError: await interaction.response.send_message("ID invalide.", ephemeral=True) return ok = await bank_manager.configurer_fisc(interaction.guild.id, iid) await interaction.response.send_message( "Fisc configure." if ok else "Institution introuvable.", ephemeral=True, ) class GouvVirerModal(discord.ui.Modal, title="Virement Fisc"): rib_dest = discord.ui.TextInput(label="RIB destinataire", required=True, max_length=30) montant = discord.ui.TextInput(label="Montant (EUR)", required=True, max_length=15) async def on_submit(self, interaction: discord.Interaction): if not has_role(interaction.user, "gouvernement"): await interaction.response.send_message("Reserve au gouvernement.", ephemeral=True) return try: montant = float(self.montant.value.replace(",", ".").strip()) except ValueError: await interaction.response.send_message("Montant invalide.", ephemeral=True) return rib_dest = self.rib_dest.value.strip().replace(" ", "") try: fisc = await bank_manager.get_fisc_config(interaction.guild.id) if not fisc: await interaction.response.send_message("Aucun fisc configure.", ephemeral=True) return source_compte = await bank_manager.get_compte_by_rib(interaction.guild.id, fisc.get("rib", "")) if not source_compte: await interaction.response.send_message("Compte fisc introuvable.", ephemeral=True) return await bank_manager.virement( guild_id=interaction.guild.id, rib_source=source_compte["rib"], rib_dest=rib_dest, montant=montant, emetteur_id=interaction.user.id, ) await interaction.response.send_message(f"Virement de **{montant:.2f}EUR** effectue depuis le Fisc.", ephemeral=True) except ValueError as e: await interaction.response.send_message(str(e), ephemeral=True) class SetPlafondModal(discord.ui.Modal, title="Plafond Negatif"): plafond = discord.ui.TextInput(label="Plafond max (0-10000 EUR)", required=True, max_length=10) def __init__(self, bank_data): super().__init__() self.bank_data = bank_data async def on_submit(self, interaction: discord.Interaction): try: val = float(self.plafond.value.replace(",", ".").strip()) except ValueError: await interaction.response.send_message("Valeur invalide.", ephemeral=True) return if val < 0 or val > 10000: await interaction.response.send_message("Plafond doit etre entre 0 et 10000.", ephemeral=True) return ok = await bank_manager.set_negative_floor(interaction.guild.id, self.bank_data["id"], val) await interaction.response.send_message( f"Plafond negatif de **{self.bank_data['name']}** defini a **-{val:.2f}EUR**." if ok else "Erreur.", ephemeral=True, ) class SetReserveModal(discord.ui.Modal, title="Reserve Bancaire"): montant = discord.ui.TextInput(label="Montant de la reserve (EUR)", required=True, max_length=15) def __init__(self, bank_data): super().__init__() self.bank_data = bank_data async def on_submit(self, interaction: discord.Interaction): try: val = float(self.montant.value.replace(",", ".").strip()) except ValueError: await interaction.response.send_message("Valeur invalide.", ephemeral=True) return if val < 0: await interaction.response.send_message("La reserve ne peut pas etre negative.", ephemeral=True) return ok = await bank_manager.set_vault_balance(interaction.guild.id, self.bank_data["id"], val) await interaction.response.send_message( f"Reserve de **{self.bank_data['name']}** definie a **{val:.2f}EUR**." if ok else "Erreur.", ephemeral=True, ) class CreerCarteModal(discord.ui.Modal, title="Creer une Carte"): prenom = discord.ui.TextInput(label="Prenom", required=True, max_length=30) nom_famille = discord.ui.TextInput(label="Nom de famille", required=True, max_length=30) date_naissance = discord.ui.TextInput(label="Date de naissance", required=True, max_length=20) sexe = discord.ui.TextInput(label="Sexe (M/F)", required=True, max_length=1) nationalite = discord.ui.TextInput(label="Nationalite", required=True, max_length=30) async def on_submit(self, interaction: discord.Interaction): if not has_role(interaction.user, "gouvernement"): await interaction.response.send_message("Reserve au gouvernement.", ephemeral=True) return from utils.data_manager import DataManager dm = DataManager() dm.save_identity( str(interaction.user.id), { "prenom": self.prenom.value.strip(), "nom": self.nom_famille.value.strip(), "date_naissance": self.date_naissance.value.strip(), "sexe": self.sexe.value.strip().upper(), "nationalite": self.nationalite.value.strip(), }, ) await interaction.response.send_message("Carte d'identite creee.", ephemeral=True) class AnnonceSessionModal(discord.ui.Modal, title="Annoncer une Session"): date = discord.ui.TextInput(label="Date (ex: Vendredi 20 Juin a 20h45)", required=True, max_length=100) reminder1 = discord.ui.TextInput(label="1er rappel (ex: Jeudi 19 Juin a 19h)", required=True, max_length=100) reminder2 = discord.ui.TextInput(label="2e rappel (ex: Vendredi 20 Juin a 15h)", required=True, max_length=100) async def on_submit(self, interaction: discord.Interaction): import dateparser from datetime import timezone session_date = dateparser.parse(self.date.value, languages=["fr"], settings={"TIMEZONE": "Europe/Paris", "RETURN_AS_TIMEZONE_AWARE": True}) r1 = dateparser.parse(self.reminder1.value, languages=["fr"], settings={"TIMEZONE": "Europe/Paris", "RETURN_AS_TIMEZONE_AWARE": True}) r2 = dateparser.parse(self.reminder2.value, languages=["fr"], settings={"TIMEZONE": "Europe/Paris", "RETURN_AS_TIMEZONE_AWARE": True}) if not session_date or not r1 or not r2: await interaction.response.send_message("Une des dates n'a pas ete comprise.", ephemeral=True) return ping_role_id = get_role_id(interaction.guild.id, "citoyen") message = ( "# - **SESSION VICTORIA'S VICE**\n\n" f"> Rendez-vous le **{self.date.value}** pour une session officielle.\n" f"> Ping : <@&{ping_role_id}>" ) await interaction.response.send_message(content=message) class ListePresenceModal(discord.ui.Modal, title="Liste des Presences"): message_id = discord.ui.TextInput(label="ID du message d'annonce", required=True, max_length=20) async def on_submit(self, interaction: discord.Interaction): from commandes.annonces_sessions import presence_data try: mid = int(self.message_id.value.strip()) except ValueError: await interaction.response.send_message("ID invalide.", ephemeral=True) return if mid not in presence_data: await interaction.response.send_message("Aucune donnee pour ce message.", ephemeral=True) return data = presence_data[mid] def mentions(ids): return ", ".join(f"<@{uid}>" for uid in ids) if ids else "Aucun" await interaction.response.send_message( f"**Presents ({len(data['present'])})** : {mentions(data['present'])}\n" f"**Absents ({len(data['absent'])})** : {mentions(data['absent'])}\n" f"**En retard ({len(data['retard'])})** : {mentions(data['retard'])}", ephemeral=True, ) class ConfigRoleModal(discord.ui.Modal, title="Definir un Role"): cle = discord.ui.TextInput(label="Cle (banquier, citoyen, ems...)", required=True, max_length=30) role_id = discord.ui.TextInput(label="ID du role Discord", required=True, max_length=20) async def on_submit(self, interaction: discord.Interaction): if not interaction.user.guild_permissions.manage_guild: await interaction.response.send_message("Permission requise.", ephemeral=True) return cle = self.cle.value.strip().lower() try: rid = int(self.role_id.value.strip()) except ValueError: await interaction.response.send_message("ID invalide.", ephemeral=True) return role = interaction.guild.get_role(rid) if not role: await interaction.response.send_message("Role introuvable.", ephemeral=True) return set_config_value(interaction.guild.id, "roles", cle, rid) await interaction.response.send_message(f"Role **{ROLE_LABELS.get(cle, cle)}** -> {role.mention}", ephemeral=True) class ConfigChannelModal(discord.ui.Modal, title="Definir un Salon"): cle = discord.ui.TextInput(label="Cle (welcome, candidature...)", required=True, max_length=30) channel_id = discord.ui.TextInput(label="ID du salon Discord", required=True, max_length=20) async def on_submit(self, interaction: discord.Interaction): if not interaction.user.guild_permissions.manage_guild: await interaction.response.send_message("Permission requise.", ephemeral=True) return cle = self.cle.value.strip().lower() try: cid = int(self.channel_id.value.strip()) except ValueError: await interaction.response.send_message("ID invalide.", ephemeral=True) return ch = interaction.guild.get_channel(cid) if not ch: await interaction.response.send_message("Salon introuvable.", ephemeral=True) return set_config_value(interaction.guild.id, "channels", cle, cid) await interaction.response.send_message(f"Salon **{CHANNEL_LABELS.get(cle, cle)}** -> {ch.mention}", ephemeral=True) class ConfigCategoryModal(discord.ui.Modal, title="Definir une Categorie"): cle = discord.ui.TextInput(label="Cle (ticket)", required=True, max_length=30) category_id = discord.ui.TextInput(label="ID de la categorie Discord", required=True, max_length=20) async def on_submit(self, interaction: discord.Interaction): if not interaction.user.guild_permissions.manage_guild: await interaction.response.send_message("Permission requise.", ephemeral=True) return cle = self.cle.value.strip().lower() try: cid = int(self.category_id.value.strip()) except ValueError: await interaction.response.send_message("ID invalide.", ephemeral=True) return cat = interaction.guild.get_channel(cid) if not cat: await interaction.response.send_message("Categorie introuvable.", ephemeral=True) return set_config_value(interaction.guild.id, "categories", cle, cid) await interaction.response.send_message(f"Categorie **{CATEGORY_LABELS.get(cle, cle)}** -> **{cat.name}**", ephemeral=True) class RollModal(discord.ui.Modal, title="Lancer un De"): minimum = discord.ui.TextInput(label="Valeur minimale", required=True, max_length=5) maximum = discord.ui.TextInput(label="Valeur maximale", required=True, max_length=5) raison = discord.ui.TextInput(label="Raison", required=True, max_length=100) async def on_submit(self, interaction: discord.Interaction): try: min_val = int(self.minimum.value.strip()) max_val = int(self.maximum.value.strip()) except ValueError: await interaction.response.send_message("Valeurs invalides.", ephemeral=True) return if min_val > max_val: await interaction.response.send_message("Min > Max.", ephemeral=True) return result = random.randint(min_val, max_val) embed = discord.Embed( title="Resultat du de", description=f"**{result}** (entre {min_val} et {max_val})\n*Raison : {self.raison.value.strip()}*", color=C_FUN, ) await interaction.response.send_message(embed=embed) class MessageModal(discord.ui.Modal, title="Envoyer un MP"): user_id = discord.ui.TextInput(label="ID de l'utilisateur", required=True, max_length=20) message = discord.ui.TextInput(label="Message", required=True, style=discord.TextStyle.paragraph, max_length=2000) async def on_submit(self, interaction: discord.Interaction): try: uid = int(self.user_id.value.strip()) except ValueError: await interaction.response.send_message("ID invalide.", ephemeral=True) return user = interaction.bot.get_user(uid) if not user: await interaction.response.send_message("Utilisateur introuvable.", ephemeral=True) return try: await user.send(self.message.value) await interaction.response.send_message(f"MP envoye a {user.mention}.", ephemeral=True) except discord.Forbidden: await interaction.response.send_message("Impossible d'envoyer un MP.", ephemeral=True) class MessageTousModal(discord.ui.Modal, title="MP a Tous"): message = discord.ui.TextInput(label="Message", required=True, style=discord.TextStyle.paragraph, max_length=2000) async def on_submit(self, interaction: discord.Interaction): if not interaction.user.guild_permissions.administrator: await interaction.response.send_message("Admin uniquement.", ephemeral=True) return await interaction.response.send_message("Envoi en cours...", ephemeral=True) count = 0 for member in interaction.guild.members: if not member.bot: try: await member.send(self.message.value) count += 1 except discord.Forbidden: pass await interaction.followup.send(f"MP envoye a **{count}** membres.", ephemeral=True) # ═══════════════════════════════════════════════════════════════════ # VIEWS — COMPTES # ═══════════════════════════════════════════════════════════════════ class CompteDetailView(discord.ui.LayoutView): def __init__(self, author, bot, compte_data, bank_data): super().__init__(timeout=300) self.author = author self.bot_ref = bot self.compte = compte_data self.bank = bank_data rib = compte_data["rib"] solde = compte_data["solde"] acc_type = compte_data["account_type"] tva = compte_data.get("tva_rate", 0) bank_name = bank_data["name"] safe = _uid(rib) solde_color = discord.Color.green() if solde >= 0 else discord.Color.red() items = [ discord.ui.TextDisplay(f"## {bank_name} — {acc_type.title()}"), discord.ui.Separator(), discord.ui.TextDisplay(f"**RIB :** `{rib}`"), discord.ui.TextDisplay(f"**Solde :** `{solde:,.2f} EUR`".replace(",", " ")), ] if acc_type == "entreprise" and tva > 0: items.append(discord.ui.TextDisplay(f"**TVA :** `{tva}%`")) items.extend([ discord.ui.Separator(), discord.ui.ActionRow( _btn("Carte", discord.ButtonStyle.primary, f"cd_card_{safe}", self._card, emoji="💳"), _btn("Relevé", discord.ButtonStyle.primary, f"cd_stmt_{safe}", self._statement, emoji="📄"), _btn("Virement", discord.ButtonStyle.primary, f"cd_vir_{safe}", self._vir, emoji="💸"), ), discord.ui.ActionRow( _btn("Dépôt", discord.ButtonStyle.success, f"cd_dep_{safe}", self._depot, emoji="💰"), _btn("Retrait", discord.ButtonStyle.danger, f"cd_ret_{safe}", self._retrait, emoji="🏧"), ), discord.ui.Separator(), discord.ui.ActionRow(_back_btn("back_cd", self._back)), ]) self.add_item(discord.ui.Container(*items, accent_colour=solde_color)) async def _card(self, interaction: discord.Interaction): from utils.bank_card import BankCardGenerator gen = BankCardGenerator() buf = gen.generate( bank_name=self.bank["name"], holder_name=interaction.user.display_name, rib=self.compte["rib"], solde=self.compte["solde"], account_type=self.compte["account_type"], tva_rate=self.compte.get("tva_rate", 0), ) file = discord.File(buf, filename="carte_bancaire.png") embed = discord.Embed(color=C_BANK) embed.set_image(url="attachment://carte_bancaire.png") await interaction.response.send_message(file=file, embed=embed, ephemeral=True) async def _statement(self, interaction: discord.Interaction): txs = await bank_manager.get_transactions(interaction.guild.id, self.compte["rib"], limit=20) from utils.bank_statement import BankStatementGenerator gen = BankStatementGenerator() buf = gen.generate( bank_name=self.bank["name"], holder_name=interaction.user.display_name, rib=self.compte["rib"], solde_actuel=self.compte["solde"], transactions=txs, account_type=self.compte["account_type"], ) file = discord.File(buf, filename="releve_bancaire.png") embed = discord.Embed(title="Releve bancaire", color=C_BANK) embed.set_image(url="attachment://releve_bancaire.png") await interaction.response.send_message(file=file, embed=embed, ephemeral=True) async def _vir(self, interaction: discord.Interaction): await interaction.response.send_modal(VirementModal(source_rib=self.compte["rib"])) async def _depot(self, interaction: discord.Interaction): await interaction.response.send_modal(DepotRetraitModal(rib=self.compte["rib"], operation="depot")) async def _retrait(self, interaction: discord.Interaction): await interaction.response.send_modal(DepotRetraitModal(rib=self.compte["rib"], operation="retrait")) async def _back(self, interaction: discord.Interaction): view = ComptePanelView(interaction.user, self.bot_ref) await view._load_accounts(interaction) await interaction.response.edit_message(view=view) class ComptePanelView(discord.ui.LayoutView): def __init__(self, author, bot): super().__init__(timeout=300) self.author = author self.bot_ref = bot async def _load_accounts(self, interaction: discord.Interaction): comptes = await bank_manager.get_comptes_user(interaction.guild.id, self.author.id) if not comptes: items = [ discord.ui.TextDisplay("## Mes Comptes"), discord.ui.TextDisplay("Aucun compte bancaire. Contactez un banquier."), discord.ui.Separator(), discord.ui.ActionRow(_back_btn("back_cp", self._back)), ] self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_BANK))) return items = [ discord.ui.TextDisplay("## Mes Comptes"), discord.ui.TextDisplay(f"**{len(comptes)}** compte(s). Cliquez pour details."), discord.ui.Separator(), ] for c in comptes: solde_str = f"{c['solde']:,.2f} EUR".replace(",", " ") label = f"{c['bank_name']} — {c['account_type'].title()} — {solde_str}" safe = _uid(c["rib"]) btn = _btn(label[:80], discord.ButtonStyle.primary, f"cp_acc_{safe}", self._make_acc_cb(c), emoji="💰") items.append(discord.ui.ActionRow(btn)) items.extend([ discord.ui.Separator(), discord.ui.ActionRow(_back_btn("back_cp", self._back)), ]) self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_BANK))) def _make_acc_cb(self, compte_data): async def cb(interaction): bank = await bank_manager.get_banque(interaction.guild.id, compte_data["bank_id"]) await interaction.response.edit_message( view=CompteDetailView(interaction.user, self.bot_ref, compte_data, bank) ) return cb async def _back(self, interaction): await interaction.response.edit_message(view=MainPanelView(interaction.user, self.bot_ref)) # ═══════════════════════════════════════════════════════════════════ # VIEWS — BANQUIER # ═══════════════════════════════════════════════════════════════════ class BanquierPanelView(discord.ui.LayoutView): def __init__(self, author, bot): super().__init__(timeout=300) self.author = author self.bot_ref = bot self.selected_user = None items = [ discord.ui.TextDisplay("## Operations Banquier"), discord.ui.TextDisplay("Selectionnez un membre, puis choisissez une operation."), discord.ui.Separator(), discord.ui.ActionRow( discord.ui.UserSelect( placeholder="Selectionner un membre...", custom_id=_uid("bq_sel_" + str(author.id)), ) ), discord.ui.Separator(), discord.ui.ActionRow( _btn("Ouvrir Compte", discord.ButtonStyle.success, _uid("bq_open_" + str(author.id)), self._ouvrir, emoji="➕"), _btn("Consulter Clients", discord.ButtonStyle.secondary, _uid("bq_clients_" + str(author.id)), self._clients, emoji="👥"), ), discord.ui.ActionRow( _btn("Info Banque", discord.ButtonStyle.secondary, _uid("bq_info_" + str(author.id)), self._info, emoji="ℹ️"), _btn("Transactions", discord.ButtonStyle.secondary, _uid("bq_tx_" + str(author.id)), self._tx, emoji="📊"), ), discord.ui.ActionRow( _btn("Set Plafond", discord.ButtonStyle.primary, _uid("bq_plaf_" + str(author.id)), self._plafond, emoji="📉"), _btn("Set Reserve", discord.ButtonStyle.primary, _uid("bq_res_" + str(author.id)), self._reserve, emoji="🏦"), ), discord.ui.Separator(), discord.ui.ActionRow(_back_btn("back_bp", self._back)), ] self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_BANQ_OP))) for child in self.children: if isinstance(child, discord.ui.Container): for item in child.children: if isinstance(item, discord.ui.ActionRow): for sub in item.children: if isinstance(sub, discord.ui.UserSelect): sub.callback = self._user_selected break async def _user_selected(self, interaction: discord.Interaction): self.selected_user = interaction.data["values"][0] await interaction.response.defer() async def _back(self, interaction): await interaction.response.edit_message(view=MainPanelView(interaction.user, self.bot_ref)) async def _ouvrir(self, interaction): bank = await _get_user_bank(interaction.guild, interaction.user) await interaction.response.send_modal(OuvrirCompteModal(bank_data=bank)) async def _clients(self, interaction): bank = await _get_user_bank(interaction.guild, interaction.user) if not bank: await interaction.response.send_message("Aucune banque associee.", ephemeral=True) return comptes = await bank_manager.get_comptes_banque(interaction.guild.id, bank["id"]) if not comptes: await interaction.response.send_message("Aucun compte dans cette banque.", ephemeral=True) return embed = discord.Embed(title=f"Clients de {bank['name']}", color=discord.Color.blue()) for c in comptes: member = interaction.guild.get_member(c["user_id"]) name = member.display_name if member else f"<@{c['user_id']}>" tva_text = f" | TVA: {c['tva_rate']}%" if c["account_type"] == "entreprise" else "" embed.add_field( name=f"{name} ({c['account_type']})", value=f"**RIB :** `{c['rib']}`\n**Solde :** `{c['solde']:.2f}EUR`{tva_text}", inline=False, ) embed.set_footer(text=f"{len(comptes)} compte(s)") await interaction.response.send_message(embed=embed, ephemeral=True) async def _info(self, interaction): bank = await _get_user_bank(interaction.guild, interaction.user) if not bank: await interaction.response.send_message("Aucune banque associee.", ephemeral=True) return role = interaction.guild.get_role(bank["role_id"]) comptes = await bank_manager.get_comptes_banque(interaction.guild.id, bank["id"]) nb_p = sum(1 for c in comptes if c["account_type"] == "particulier") nb_e = sum(1 for c in comptes if c["account_type"] == "entreprise") embed = discord.Embed(title=f"{bank['name']}", color=discord.Color.blue()) embed.add_field(name="Role", value=role.mention if role else "Inconnu", inline=True) embed.add_field(name="Code etabl.", value=f"`{bank['establishment_code']}`", inline=True) embed.add_field(name="Reserve", value=f"`{bank['vault_balance']:.2f}EUR`", inline=True) embed.add_field(name="Encours clients", value=f"`{bank['clients_total_balance']:.2f}EUR`", inline=True) embed.add_field(name="Particuliers", value=str(nb_p), inline=True) embed.add_field(name="Entreprises", value=str(nb_e), inline=True) if bank.get("client_negative_floor", 0) > 0: embed.add_field(name="Plafond neg.", value=f"`-{bank['client_negative_floor']:.2f}EUR`", inline=True) await interaction.response.send_message(embed=embed, ephemeral=True) async def _tx(self, interaction): if not self.selected_user: await interaction.response.send_message("Selectionnez d'abord un membre.", ephemeral=True) return uid = int(self.selected_user) if isinstance(self.selected_user, (int, str)) else self.selected_user.id comptes = await bank_manager.get_comptes_user(interaction.guild.id, uid) if not comptes: await interaction.response.send_message("Aucun compte pour ce membre.", ephemeral=True) return member = interaction.guild.get_member(uid) name = member.display_name if member else str(uid) embed = discord.Embed(title=f"Transactions de {name}", color=discord.Color.blue()) for c in comptes: txs = await bank_manager.get_transactions(interaction.guild.id, c["rib"], limit=5) if txs: lines = [] for tx in txs: is_out = tx["emetteur_rib"] == c["rib"] signe = "-" if is_out else "+" lines.append(f"`{tx['date'][:16]}` **{tx['type']}** `{signe}{tx['montant']:.2f}EUR`") embed.add_field(name=f"{c['bank_name']} ({c['account_type']})", value="\n".join(lines), inline=False) await interaction.response.send_message(embed=embed, ephemeral=True) async def _plafond(self, interaction): bank = await _get_user_bank(interaction.guild, interaction.user) if not bank: await interaction.response.send_message("Aucune banque associee.", ephemeral=True) return await interaction.response.send_modal(SetPlafondModal(bank)) async def _reserve(self, interaction): bank = await _get_user_bank(interaction.guild, interaction.user) if not bank: await interaction.response.send_message("Aucune banque associee.", ephemeral=True) return await interaction.response.send_modal(SetReserveModal(bank)) # ═══════════════════════════════════════════════════════════════════ # VIEWS — GOUVERNEMENT # ═══════════════════════════════════════════════════════════════════ class GouvernementPanelView(discord.ui.LayoutView): def __init__(self, author, bot): super().__init__(timeout=300) self.author = author self.bot_ref = bot items = [ discord.ui.TextDisplay("## Gouvernement"), discord.ui.TextDisplay("Institutions, fisc, banques."), discord.ui.Separator(), discord.ui.ActionRow( _btn("Créer Banque", discord.ButtonStyle.success, "gp_cbanque", self._cbanque, emoji="🏦"), _btn("Créer Institution", discord.ButtonStyle.success, "gp_cinst", self._cinst, emoji="🏛️"), ), discord.ui.ActionRow( _btn("Config Fisc", discord.ButtonStyle.primary, "gp_fisc", self._fisc, emoji="📋"), _btn("Virement Fisc", discord.ButtonStyle.primary, "gp_vfisc", self._vfisc, emoji="💸"), ), discord.ui.ActionRow( _btn("Lister Banques", discord.ButtonStyle.secondary, "gp_lbanques", self._lbanques, emoji="📃"), _btn("Lister Institutions", discord.ButtonStyle.secondary, "gp_linst", self._linst, emoji="📃"), ), discord.ui.Separator(), discord.ui.ActionRow(_back_btn("back_gp", self._back)), ] self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_GOV))) async def _back(self, interaction): await interaction.response.edit_message(view=MainPanelView(interaction.user, self.bot_ref)) async def _cbanque(self, interaction): if not has_role(interaction.user, "gouvernement"): await interaction.response.send_message("Reserve au gouvernement.", ephemeral=True) return await interaction.response.send_modal(CreerBanqueModal()) async def _cinst(self, interaction): if not has_role(interaction.user, "gouvernement"): await interaction.response.send_message("Reserve au gouvernement.", ephemeral=True) return await interaction.response.send_modal(CreerInstitutionModal()) async def _fisc(self, interaction): if not has_role(interaction.user, "gouvernement"): await interaction.response.send_message("Reserve au gouvernement.", ephemeral=True) return await interaction.response.send_modal(ConfigFiscModal()) async def _vfisc(self, interaction): if not has_role(interaction.user, "gouvernement"): await interaction.response.send_message("Reserve au gouvernement.", ephemeral=True) return await interaction.response.send_modal(GouvVirerModal()) async def _lbanques(self, interaction): if not has_role(interaction.user, "gouvernement"): await interaction.response.send_message("Reserve au gouvernement.", ephemeral=True) return banques = await bank_manager.lister_banques(interaction.guild.id) if not banques: await interaction.response.send_message("Aucune banque.", ephemeral=True) return embed = discord.Embed(title="Banques", color=discord.Color.gold()) for b in banques: role = interaction.guild.get_role(b["role_id"]) embed.add_field( name=b["name"], value=( f"**Role :** {role.mention if role else 'N/A'}\n" f"**Code :** `{b['establishment_code']}`\n" f"**Reserve :** `{b['vault_balance']:.2f}EUR`\n" f"**Encours :** `{b['clients_total_balance']:.2f}EUR`" ), inline=False, ) await interaction.response.send_message(embed=embed, ephemeral=True) async def _linst(self, interaction): if not has_role(interaction.user, "gouvernement"): await interaction.response.send_message("Reserve au gouvernement.", ephemeral=True) return insts = await bank_manager.lister_institutions(interaction.guild.id) if not insts: await interaction.response.send_message("Aucune institution.", ephemeral=True) return embed = discord.Embed(title="Institutions", color=discord.Color.gold()) for i in insts: role = interaction.guild.get_role(i["role_id"]) if i["role_id"] else None embed.add_field( name=i["name"], value=( f"**ID :** `{i['id']}`\n" f"**Role :** {role.mention if role else 'Non defini'}\n" f"**Audit global :** {'Oui' if i['can_audit_all'] else 'Non'}" ), inline=False, ) await interaction.response.send_message(embed=embed, ephemeral=True) # ═══════════════════════════════════════════════════════════════════ # VIEWS — IDENTITE # ═══════════════════════════════════════════════════════════════════ class IdentitePanelView(discord.ui.LayoutView): def __init__(self, author, bot): super().__init__(timeout=300) self.author = author self.bot_ref = bot items = [ discord.ui.TextDisplay("## Identite"), discord.ui.TextDisplay("Cartes d'identite, permis, visites medicales."), discord.ui.Separator(), discord.ui.ActionRow( _btn("Ma Carte", discord.ButtonStyle.primary, "ip_carte", self._carte, emoji="🪪"), ), ] if has_role(author, "gouvernement"): items.append(discord.ui.ActionRow( _btn("Créer Carte", discord.ButtonStyle.success, "ip_ccar", self._ccar, emoji="🪪"), )) action_items = [] if has_role(author, "auto_ecole"): action_items.append(_btn("Permis", discord.ButtonStyle.primary, "ip_permis", self._permis, emoji="🚗")) if has_role(author, "ems"): action_items.append(_btn("Visite Médicale", discord.ButtonStyle.primary, "ip_vismed", self._vismed, emoji="🏥")) if action_items: items.append(discord.ui.ActionRow(*action_items)) items.extend([ discord.ui.Separator(), discord.ui.ActionRow(_back_btn("back_ip", self._back)), ]) self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_ID))) async def _back(self, interaction): await interaction.response.edit_message(view=MainPanelView(interaction.user, self.bot_ref)) async def _carte(self, interaction): from utils.data_manager import DataManager uid = str(interaction.user.id) dm = DataManager() identity = dm.identities.get(uid) if not identity: await interaction.response.send_message("Aucune carte trouvée.", ephemeral=True) return try: from utils.card_generator import CardGenerator cg = CardGenerator() card_buffer = await cg.create_identity_card(interaction.user, identity) file = discord.File(card_buffer, filename="carte_identite.png") embed = discord.Embed(title="Carte d'identite", color=C_ID) embed.set_image(url="attachment://carte_identite.png") await interaction.response.send_message(file=file, embed=embed) except Exception as e: await interaction.response.send_message(f"Erreur : {e}", ephemeral=True) async def _ccar(self, interaction): await interaction.response.send_modal(CreerCarteModal()) async def _permis(self, interaction): await interaction.response.send_message("Module permis a venir.", ephemeral=True) async def _vismed(self, interaction): await interaction.response.send_message("Module visite medicale a venir.", ephemeral=True) # ═══════════════════════════════════════════════════════════════════ # VIEWS — TICKETS & MESSAGES # ═══════════════════════════════════════════════════════════════════ class TicketsPanelView(discord.ui.LayoutView): def __init__(self, author, bot): super().__init__(timeout=300) self.author = author self.bot_ref = bot items = [ discord.ui.TextDisplay("## Tickets & Messages"), discord.ui.TextDisplay("Gestion des tickets et messages prives."), discord.ui.Separator(), discord.ui.ActionRow( _btn("Embed Ticket", discord.ButtonStyle.success, "tp_ticket", self._ticket, emoji="📩"), _btn("Envoyer MP", discord.ButtonStyle.primary, "tp_mp", self._mp, emoji="✉️"), _btn("MP à Tous", discord.ButtonStyle.danger, "tp_mptous", self._mptous, emoji="📢"), ), discord.ui.Separator(), discord.ui.ActionRow(_back_btn("back_tp", self._back)), ] self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_TICKET))) async def _back(self, interaction): await interaction.response.edit_message(view=MainPanelView(interaction.user, self.bot_ref)) async def _ticket(self, interaction): if not interaction.user.guild_permissions.manage_guild: await interaction.response.send_message("Permission requise.", ephemeral=True) return embed = discord.Embed( title="Ticket Whitelist", description="Ouvrez un ticket et remplissez le formulaire.", color=0x00FF00, ) from commandes.ticket import TicketView await interaction.channel.send(embed=embed, view=TicketView()) await interaction.response.send_message("Embed des tickets envoye.", ephemeral=True) async def _mp(self, interaction): await interaction.response.send_modal(MessageModal()) async def _mptous(self, interaction): await interaction.response.send_modal(MessageTousModal()) # ═══════════════════════════════════════════════════════════════════ # VIEWS — SESSIONS # ═══════════════════════════════════════════════════════════════════ class SessionsPanelView(discord.ui.LayoutView): def __init__(self, author, bot): super().__init__(timeout=300) self.author = author self.bot_ref = bot items = [ discord.ui.TextDisplay("## Sessions RP"), discord.ui.TextDisplay("Annonces de sessions et gestion des presences."), discord.ui.Separator(), discord.ui.ActionRow( _btn("Annoncer Session", discord.ButtonStyle.success, "sp_annonce", self._annonce, emoji="📅"), _btn("Liste Présences", discord.ButtonStyle.primary, "sp_liste", self._liste, emoji="✅"), ), discord.ui.Separator(), discord.ui.ActionRow(_back_btn("back_sp", self._back)), ] self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_SESSION))) async def _back(self, interaction): await interaction.response.edit_message(view=MainPanelView(interaction.user, self.bot_ref)) async def _annonce(self, interaction): await interaction.response.send_modal(AnnonceSessionModal()) async def _liste(self, interaction): await interaction.response.send_modal(ListePresenceModal()) # ═══════════════════════════════════════════════════════════════════ # VIEWS — CONFIGURATION # ═══════════════════════════════════════════════════════════════════ class ConfigPanelView(discord.ui.LayoutView): def __init__(self, author, bot): super().__init__(timeout=300) self.author = author self.bot_ref = bot items = [ discord.ui.TextDisplay("## Configuration"), discord.ui.TextDisplay("Configurer les roles, salons et categories."), discord.ui.Separator(), discord.ui.ActionRow( _btn("Définir Rôle", discord.ButtonStyle.primary, "cfg_role", self._crole, emoji="🎭"), _btn("Définir Salon", discord.ButtonStyle.primary, "cfg_chan", self._cchan, emoji="💬"), _btn("Définir Catégorie", discord.ButtonStyle.primary, "cfg_cat", self._ccat, emoji="📂"), ), discord.ui.ActionRow( _btn("Voir Config", discord.ButtonStyle.secondary, "cfg_voir", self._cvoir, emoji="👁️"), _btn("Reset Config", discord.ButtonStyle.danger, "cfg_reset", self._creset, emoji="🗑️"), ), discord.ui.Separator(), discord.ui.ActionRow(_back_btn("back_cfg", self._back)), ] self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_CONFIG))) async def _back(self, interaction): await interaction.response.edit_message(view=MainPanelView(interaction.user, self.bot_ref)) async def _crole(self, interaction): await interaction.response.send_modal(ConfigRoleModal()) async def _cchan(self, interaction): await interaction.response.send_modal(ConfigChannelModal()) async def _ccat(self, interaction): await interaction.response.send_modal(ConfigCategoryModal()) async def _cvoir(self, interaction): if not interaction.user.guild_permissions.manage_guild: await interaction.response.send_message("Permission requise.", ephemeral=True) return config = load_guild_config(interaction.guild.id) embed = discord.Embed(title="Configuration actuelle", color=C_CONFIG) roles_text = "" for key, label in ROLE_LABELS.items(): rid = config.get("roles", {}).get(key) if rid: role = interaction.guild.get_role(int(rid)) roles_text += f"> {label} : {role.mention if role else f'N/A {rid}'}\n" else: roles_text += f"> {label} : Non defini\n" embed.add_field(name="Roles", value=roles_text, inline=False) channels_text = "" for key, label in CHANNEL_LABELS.items(): cid = config.get("channels", {}).get(key) if cid: ch = interaction.guild.get_channel(int(cid)) channels_text += f"> {label} : {ch.mention if ch else f'N/A {cid}'}\n" else: channels_text += f"> {label} : Non defini\n" embed.add_field(name="Salons", value=channels_text, inline=False) cats_text = "" for key, label in CATEGORY_LABELS.items(): catid = config.get("categories", {}).get(key) if catid: cat = interaction.guild.get_channel(int(catid)) cats_text += f"> {label} : **{cat.name}**\n" if cat else f"> {label} : N/A {catid}\n" else: cats_text += f"> {label} : Non defini\n" embed.add_field(name="Categories", value=cats_text, inline=False) await interaction.response.send_message(embed=embed, ephemeral=True) async def _creset(self, interaction): if not interaction.user.guild_permissions.administrator: await interaction.response.send_message("Admin uniquement.", ephemeral=True) return save_guild_config(interaction.guild.id, {"roles": {}, "channels": {}, "categories": {}}) await interaction.response.send_message("Configuration reinitialisee.", ephemeral=True) # ═══════════════════════════════════════════════════════════════════ # VIEWS — DIVERS # ═══════════════════════════════════════════════════════════════════ class DiversPanelView(discord.ui.LayoutView): def __init__(self, author, bot): super().__init__(timeout=300) self.author = author self.bot_ref = bot items = [ discord.ui.TextDisplay("## Divers"), discord.ui.TextDisplay("Lancer de de et autres outils."), discord.ui.Separator(), discord.ui.ActionRow( _btn("Lancer un Dé", discord.ButtonStyle.primary, "dp_roll", self._roll, emoji="🎲"), ), ] if author.guild_permissions.administrator: items.append(discord.ui.ActionRow( _btn("Stats Bot", discord.ButtonStyle.secondary, "dp_stats", self._stats, emoji="📊"), )) items.extend([ discord.ui.Separator(), discord.ui.ActionRow(_back_btn("back_dp", self._back)), ]) self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_FUN))) async def _back(self, interaction): await interaction.response.edit_message(view=MainPanelView(interaction.user, self.bot_ref)) async def _roll(self, interaction): await interaction.response.send_modal(RollModal()) async def _stats(self, interaction): from utils.stats_manager import get_stats_detailed stats = get_stats_detailed() if not stats: await interaction.response.send_message("Aucune statistique.", ephemeral=True) return await interaction.response.send_message( f"**Statistiques du bot**\n```json\n{json.dumps(stats, indent=2, ensure_ascii=False)[:1800]}\n```", ephemeral=True, ) # ═══════════════════════════════════════════════════════════════════ # PANEL PRINCIPAL # ═══════════════════════════════════════════════════════════════════ class CategorySelect(discord.ui.Select): def __init__(self, author, bot): options = [ discord.SelectOption(label="Mes Comptes", description="Comptes, virements, releves", emoji="💰", value="compte"), ] if has_role(author, "banquier"): options.append(discord.SelectOption(label="Operations Banquier", description="Comptes, clients, reserve", emoji="🏦", value="banquier")) if has_role(author, "gouvernement"): options.append(discord.SelectOption(label="Gouvernement", description="Banques, institutions, fisc", emoji="🏛️", value="gouvernement")) options.append(discord.SelectOption(label="Identite", description="Cartes, permis, medicale", emoji="🪪", value="identite")) if author.guild_permissions.administrator: options.append(discord.SelectOption(label="Tickets & Messages", description="Tickets, MP prives", emoji="📩", value="tickets")) options.append(discord.SelectOption(label="Sessions RP", description="Annonces, presences", emoji="📅", value="sessions")) options.append(discord.SelectOption(label="Divers", description="De, stats", emoji="🎲", value="divers")) if author.guild_permissions.manage_guild: options.append(discord.SelectOption(label="Configuration", description="Configurer le bot", emoji="⚙️", value="config")) super().__init__(placeholder="Selectionnez une categorie...", options=options, custom_id=_uid("pnl_cat_" + str(author.id))) self._bot = bot async def callback(self, interaction: discord.Interaction): view_map = { "compte": ComptePanelView, "banquier": BanquierPanelView, "gouvernement": GouvernementPanelView, "identite": IdentitePanelView, "tickets": TicketsPanelView, "sessions": SessionsPanelView, "config": ConfigPanelView, "divers": DiversPanelView, } cls = view_map.get(self.values[0]) if cls: view = cls(interaction.user, self._bot) if isinstance(view, ComptePanelView): await view._load_accounts(interaction) await interaction.response.edit_message(view=view) class MainPanelView(discord.ui.LayoutView): def __init__(self, author, bot): super().__init__(timeout=300) self.author = author self.bot_ref = bot self.message = None role_parts = [] if has_role(author, "banquier"): role_parts.append("Banquier") if has_role(author, "gouvernement"): role_parts.append("Gouvernement") if has_role(author, "ems"): role_parts.append("EMS") if has_role(author, "auto_ecole"): role_parts.append("Auto-ecole") if author.guild_permissions.administrator: role_parts.append("Admin") role_text = " | ".join(role_parts) if role_parts else "Citoyen" guild_name = author.guild.name if author.guild else "Victoria's Vice" self.add_item(discord.ui.Container( discord.ui.TextDisplay(f"## {guild_name} — Panel de Commandes"), discord.ui.TextDisplay(f"**Vos roles :** {role_text}"), discord.ui.TextDisplay("Selectionnez une catégorie ci-dessous."), discord.ui.Separator(), discord.ui.ActionRow(CategorySelect(author, bot)), accent_colour=discord.Colour(C_MAIN), )) async def on_timeout(self): if self.message: try: await self.message.edit(content="Panel expire. Utilisez `/panel` pour le rouvrir.", view=None) except discord.HTTPException: pass # ═══════════════════════════════════════════════════════════════════ # COG # ═══════════════════════════════════════════════════════════════════ class Panel(commands.Cog): def __init__(self, bot): self.bot = bot @app_commands.command(name="panel", description="Ouvre le panneau de commandes du bot") async def panel(self, interaction: discord.Interaction): view = MainPanelView(interaction.user, self.bot) await interaction.response.send_message(view=view, ephemeral=False) view.message = await interaction.original_response() async def setup(bot): await bot.add_cog(Panel(bot))