import os import json import random 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 from utils.rib import decoder_rib 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 _load_json(name): path = os.path.join(base_dir, "data", name) if os.path.exists(path): with open(path, "r", encoding="utf-8") as f: return json.load(f) return {} def _btn(label, style, cid, cb): b = discord.ui.Button(label=label, style=style, custom_id=cid) b.callback = cb return b def _back_btn(cid, cb): return _btn("◀ Retour", discord.ButtonStyle.secondary, cid, cb) # ═══════════════════════════════════════════════════════════════════ # USER SELECT (pour banquiers) # ═══════════════════════════════════════════════════════════════════ class TargetUserSelect(discord.ui.UserSelect): def __init__(self, custom_id="bp_usel"): super().__init__(placeholder="Sélectionner un membre...", custom_id=custom_id) self.selected_user = None async def callback(self, interaction: discord.Interaction): self.selected_user = self.values[0] await interaction.response.defer() # ═══════════════════════════════════════════════════════════════════ # MODALS — COMPTES (Mes Comptes) # ═══════════════════════════════════════════════════════════════════ class VirementModal(discord.ui.Modal, title="💸 Virement"): rib_dest = discord.ui.TextInput( label="RIB du destinataire", required=True, max_length=30 ) montant = discord.ui.TextInput( label="Montant (€)", 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}€** envoyé." if result["tva_prelevee"] > 0: msg += f"\n💡 TVA prélevée : **{result['tva_prelevee']:.2f}€**" await interaction.response.send_message(msg, ephemeral=True) except ValueError as e: await interaction.response.send_message(f"❌ {e}", ephemeral=True) class DepotRetraitModal(discord.ui.Modal): montant = discord.ui.TextInput( label="Montant (€)", required=True, max_length=15 ) def __init__(self, rib: str, operation: str): super().__init__(title=f"{'➕ Dépôt' 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}€**. Nouveau solde : **{result['nouveau_solde']:.2f}€**", ephemeral=True, ) except ValueError as e: await interaction.response.send_message(f"❌ {e}", ephemeral=True) # ═══════════════════════════════════════════════════════════════════ # MODALS — BANQUIER # ═══════════════════════════════════════════════════════════════════ 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 ) 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 banques = await bank_manager.lister_banques(interaction.guild.id) bank = None for b in banques: role = interaction.guild.get_role(b["role_id"]) if role and role in interaction.user.roles: bank = b break if not bank: await interaction.response.send_message("❌ Aucune banque associée à vos rôles.", 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 été ouvert.\n" f"**RIB :** `{result['rib']}`" ) except discord.Forbidden: pass except ValueError as e: await interaction.response.send_message(f"❌ {e}", ephemeral=True) class TransactionsModal(discord.ui.Modal, title="📜 Transactions"): rib = discord.ui.TextInput( label="RIB du compte", required=True, max_length=30 ) async def on_submit(self, interaction: discord.Interaction): rib = self.rib.value.strip().replace(" ", "") txs = await bank_manager.get_transactions(interaction.guild.id, rib, limit=10) if not txs: await interaction.response.send_message("Aucune transaction.", ephemeral=True) return lines = [] for tx in txs: lines.append( f"`{tx['date']}` — **{tx['type']}** — {tx['montant']:.2f}€" ) embed = discord.Embed( title=f"📜 Transactions ({rib})", description="\n".join(lines), color=discord.Color.blue(), ) await interaction.response.send_message(embed=embed, ephemeral=True) # ═══════════════════════════════════════════════════════════════════ # MODALS — GOUVERNEMENT # ═══════════════════════════════════════════════════════════════════ class GouvVirerModal(discord.ui.Modal, title="💸 Virement Gouvernement"): rib_dest = discord.ui.TextInput( label="RIB du destinataire", required=True, max_length=30 ) montant = discord.ui.TextInput( label="Montant (€)", 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("❌ Réservé 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 configuré.", 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 result = 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}€** effectué depuis le Fisc.", ephemeral=True, ) except ValueError as e: await interaction.response.send_message(f"❌ {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("❌ Réservé 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) if ok: await interaction.response.send_message("✅ Fisc configuré.", ephemeral=True) else: await interaction.response.send_message("❌ Institution introuvable.", ephemeral=True) class CreerBanqueModal(discord.ui.Modal, title="🏦 Créer une Banque"): nom = discord.ui.TextInput( label="Nom de la banque", required=True, max_length=50 ) role_id = discord.ui.TextInput( label="ID du rôle 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("❌ Réservé 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("❌ Rôle introuvable.", ephemeral=True) return try: result = await bank_manager.creer_banque(interaction.guild.id, self.nom.value.strip(), rid) embed = discord.Embed( title="🏦 Banque créée", description=( f"**Nom :** {result['name']}\n" f"**Rôle :** {role.mention}\n" f"**Code établissement :** `{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(f"❌ {e}", ephemeral=True) class CreerInstitutionModal(discord.ui.Modal, title="🏛️ Créer une Institution"): nom = discord.ui.TextInput( label="Nom de l'institution", required=True, max_length=50 ) role_id = discord.ui.TextInput( label="ID du rôle (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("❌ Réservé 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']}** créée (ID: `{result['id']}`).", ephemeral=True, ) except Exception as e: await interaction.response.send_message(f"❌ {e}", ephemeral=True) # ═══════════════════════════════════════════════════════════════════ # MODALS — IDENTITE # ═══════════════════════════════════════════════════════════════════ class CreerCarteModal(discord.ui.Modal, title="🪪 Créer une Carte"): prenom = discord.ui.TextInput(label="Prénom", 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="Nationalité", 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("❌ Réservé 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'identité créée.", ephemeral=True) class PermisModal(discord.ui.Modal, title="🚗 Permis de Conduire"): membre_id = discord.ui.TextInput(label="ID du membre", required=True, max_length=20) categorie = discord.ui.TextInput(label="Catégorie (A/B/C)", required=True, max_length=2) async def on_submit(self, interaction: discord.Interaction): await interaction.response.send_message("✅ Permis enregistré (module à venir).", ephemeral=True) class VisiteMedModal(discord.ui.Modal, title="🏥 Visite Médicale"): membre_id = discord.ui.TextInput(label="ID du membre", required=True, max_length=20) resultat = discord.ui.TextInput(label="Résultat (Apte/Inapte)", required=True, max_length=10) async def on_submit(self, interaction: discord.Interaction): await interaction.response.send_message("✅ Visite médicale enregistrée (module à venir).", ephemeral=True) # ═══════════════════════════════════════════════════════════════════ # MODALS — SESSIONS # ═══════════════════════════════════════════════════════════════════ class AnnonceSessionModal(discord.ui.Modal, title="📢 Annoncer une Session"): date = discord.ui.TextInput( label="Date (ex: Vendredi 20 Juin à 20h45)", required=True, max_length=100, ) reminder1 = discord.ui.TextInput( label="1er rappel (ex: Jeudi 19 Juin à 19h)", required=True, max_length=100, ) reminder2 = discord.ui.TextInput( label="2e rappel (ex: Vendredi 20 Juin à 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 été comprise.", ephemeral=True) return ping_role_id = get_role_id(interaction.guild.id, "citoyen") message = ( "# <:VV:1339237574593609780> - **SESSION VICTORIA'S VICE**\n\n" "```📅 - HORAIRE DE LA SESSION```\n" f"> - Nous vous donnons rendez-vous le **{self.date.value}** " "pour une **session officielle** sur Victoria's Vice. 🗓️\n" "---\n" "```🤔 - COMMENT REJOINDRE LA SESSION ?```\n" "> - Pour lancer la Map, entrez le code sur la barre de recherche de Fortnite.\n" "> - **`CODE DE LA MAP`** <:arrow:1297491509201080362> [0986-1836-1625]\n" "> - **`LIEN DIRECT`** <:arrow:1297491509201080362> " "\n" "---\n" "```🖐 - SEREZ-VOUS PRÉSENT(E) ?```\n" "> - Je serai présent : <:check:1297296859206778911> **(0)**\n" "> - Je serai absent : <:uncheck:1297296882778902598> **(0)**\n" "> - Je serai en retard : ⏰ **(0)**\n" "---\n" f"> - **`PING`** : <@&{ping_role_id}>" ) await interaction.response.send_message(content=message) msg = await interaction.original_response() from commandes.annonces_sessions import PresenceView await msg.edit(view=PresenceView(msg.id)) class ListePresenceModal(discord.ui.Modal, title="📋 Liste des Présences"): 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 donnée 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"**Présents ({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, ) # ═══════════════════════════════════════════════════════════════════ # MODALS — CONFIG # ═══════════════════════════════════════════════════════════════════ class ConfigRoleModal(discord.ui.Modal, title="🎭 Définir un Rôle"): cle = discord.ui.TextInput(label="Clé (banquier, citoyen, ems...)", required=True, max_length=30) role_id = discord.ui.TextInput(label="ID du rôle 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("❌ Rôle introuvable.", ephemeral=True) return set_config_value(interaction.guild.id, "roles", cle, rid) await interaction.response.send_message( f"✅ Rôle **{ROLE_LABELS.get(cle, cle)}** → {role.mention}", ephemeral=True, ) class ConfigChannelModal(discord.ui.Modal, title="📢 Définir un Salon"): cle = discord.ui.TextInput(label="Clé (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="📁 Définir une Catégorie"): cle = discord.ui.TextInput(label="Clé (ticket)", required=True, max_length=30) category_id = discord.ui.TextInput(label="ID de la catégorie 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("❌ Catégorie introuvable.", ephemeral=True) return set_config_value(interaction.guild.id, "categories", cle, cid) await interaction.response.send_message( f"✅ Catégorie **{CATEGORY_LABELS.get(cle, cle)}** → **{cat.name}**", ephemeral=True, ) class RollModal(discord.ui.Modal, title="🎲 Lancer un Dé"): 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="🎲 Résultat du dé", 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 envoyé à {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 à 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 envoyé à **{count}** membres.", ephemeral=True) # ═══════════════════════════════════════════════════════════════════ # VIEWS — MES COMPTES (tout le monde) # ═══════════════════════════════════════════════════════════════════ 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"] 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}€`".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_{rib[-8:]}", self._card), _btn("📤 Virement", discord.ButtonStyle.primary, f"cd_vir_{rib[-8:]}", self._vir), _btn("➕ Dépôt", discord.ButtonStyle.success, f"cd_dep_{rib[-8:]}", self._depot), _btn("➖ Retrait", discord.ButtonStyle.danger, f"cd_ret_{rib[-8:]}", self._retrait), ), 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 _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): await interaction.response.edit_message( view=ComptePanelView(interaction.user, self.bot_ref) ) class ComptePanelView(discord.ui.LayoutView): def __init__(self, author, bot): super().__init__(timeout=300) self.author = author self.bot_ref = bot items = [ discord.ui.TextDisplay("## 💰 Mes Comptes"), discord.ui.TextDisplay("Cliquez sur un compte pour voir les détails et opérer."), discord.ui.Separator(), ] self._account_buttons = [] async def _load_accounts(self, interaction: discord.Interaction): comptes = await bank_manager.get_comptes_user(interaction.guild.id, interaction.user.id) if not comptes: items = [ discord.ui.TextDisplay("## 💰 Mes Comptes"), discord.ui.TextDisplay("Aucun compte bancaire. contactez un banquier pour en ouvrir un."), 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) trouvé(s). Cliquez pour details."), discord.ui.Separator(), ] for i, c in enumerate(comptes): solde_str = f"{c['solde']:,.2f}€".replace(",", " ") label = f"💳 {c['bank_name']} — {c['account_type'].title()} — {solde_str}" btn = _btn( label[:80], discord.ButtonStyle.primary, f"cp_acc_{i}", self._make_acc_cb(c), ) 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: discord.Interaction): await interaction.response.edit_message( view=MainPanelView(interaction.user, self.bot_ref) ) # ═══════════════════════════════════════════════════════════════════ # VIEWS — OPERATIONS 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 self.user_select = TargetUserSelect(custom_id="bp_usel") items = [ discord.ui.TextDisplay("## 🏦 Opérations Banquier"), discord.ui.TextDisplay("Sélectionnez un membre puis choisissez une opération."), discord.ui.Separator(), discord.ui.ActionRow(self.user_select), discord.ui.Separator(), discord.ui.ActionRow( _btn("🏦 Ouvrir Compte", discord.ButtonStyle.success, "bp_ouvrir", self._ouvrir), _btn("📜 Transactions", discord.ButtonStyle.secondary, "bp_tx", self._tx), ), 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))) def _target(self, interaction): return self.user_select.selected_user or interaction.user async def _back(self, interaction): await interaction.response.edit_message( view=MainPanelView(interaction.user, self.bot_ref) ) async def _ouvrir(self, interaction): await interaction.response.send_modal(OuvrirCompteModal()) async def _tx(self, interaction): await interaction.response.send_modal(TransactionsModal()) # ═══════════════════════════════════════════════════════════════════ # 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 nationales."), discord.ui.Separator(), discord.ui.ActionRow( _btn("🏦 Créer Banque", discord.ButtonStyle.success, "gp_cbanque", self._cbanque), _btn("🏛️ Créer Institution", discord.ButtonStyle.success, "gp_cinst", self._cinst), ), discord.ui.ActionRow( _btn("⚙️ Config Fisc", discord.ButtonStyle.primary, "gp_fisc", self._fisc), _btn("💸 Virement Fisc", discord.ButtonStyle.primary, "gp_vfisc", self._vfisc), ), discord.ui.ActionRow( _btn("📋 Lister Banques", discord.ButtonStyle.secondary, "gp_lbanques", self._lbanques), _btn("📋 Lister Institutions", discord.ButtonStyle.secondary, "gp_linst", self._linst), ), 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("❌ Réservé 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("❌ Réservé 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("❌ Réservé 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("❌ Réservé 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("❌ Réservé 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"**Rôle :** {role.mention if role else '❌'}\n" f"**Code :** `{b['establishment_code']}`\n" f"**Réserve :** `{b['vault_balance']:.2f}€`\n" f"**Encours :** `{b['clients_total_balance']:.2f}€`" ), 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("❌ Réservé 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"**Rôle :** {role.mention if role else 'Non défini'}\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("## 🪪 Identité"), discord.ui.TextDisplay("Cartes d'identité, permis, visites médicales."), discord.ui.Separator(), discord.ui.ActionRow( _btn("🪪 Ma Carte", discord.ButtonStyle.primary, "ip_carte", self._carte), ), ] if has_role(author, "gouvernement"): items.append( discord.ui.ActionRow( _btn("🪪 Créer Carte", discord.ButtonStyle.success, "ip_ccar", self._ccar), ) ) action_items = [] if has_role(author, "auto_ecole"): action_items.append(_btn("🚗 Permis", discord.ButtonStyle.primary, "ip_permis", self._permis)) if has_role(author, "ems"): action_items.append(_btn("🏥 Visite Médicale", discord.ButtonStyle.primary, "ip_vismed", self._vismed)) 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'identité", 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_modal(PermisModal()) async def _vismed(self, interaction): await interaction.response.send_modal(VisiteMedModal()) # ═══════════════════════════════════════════════════════════════════ # 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 privés."), discord.ui.Separator(), discord.ui.ActionRow( _btn("📑 Embed Ticket", discord.ButtonStyle.success, "tp_ticket", self._ticket), _btn("✉️ Envoyer MP", discord.ButtonStyle.primary, "tp_mp", self._mp), _btn("📢 MP à Tous", discord.ButtonStyle.danger, "tp_mptous", self._mptous), ), 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="Pour réaliser votre whitelist, 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 envoyé.", 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 RP # ═══════════════════════════════════════════════════════════════════ 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 présences."), discord.ui.Separator(), discord.ui.ActionRow( _btn("📢 Annoncer Session", discord.ButtonStyle.success, "sp_annonce", self._annonce), _btn("📋 Liste Présences", discord.ButtonStyle.primary, "sp_liste", self._liste), ), 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 rôles, salons et catégories de ce serveur."), discord.ui.Separator(), discord.ui.ActionRow( _btn("🎭 Définir Rôle", discord.ButtonStyle.primary, "cfg_role", self._crole), _btn("📢 Définir Salon", discord.ButtonStyle.primary, "cfg_chan", self._cchan), _btn("📁 Définir Catégorie", discord.ButtonStyle.primary, "cfg_cat", self._ccat), ), discord.ui.ActionRow( _btn("👁️ Voir Config", discord.ButtonStyle.secondary, "cfg_voir", self._cvoir), _btn("🗑️ Reset Config", discord.ButtonStyle.danger, "cfg_reset", self._creset), ), 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'❌ {rid}'}\n" else: roles_text += f"> {label} : ⚠️ Non défini\n" embed.add_field(name="🎭 Rôles", 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'❌ {cid}'}\n" else: channels_text += f"> {label} : ⚠️ Non défini\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} : ❌ {catid}\n" else: cats_text += f"> {label} : ⚠️ Non défini\n" embed.add_field(name="📁 Catégories", 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("❌ Réservé aux administrateurs.", ephemeral=True) return save_guild_config(interaction.guild.id, {"roles": {}, "channels": {}, "categories": {}}) await interaction.response.send_message("✅ Configuration réinitialisée.", 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 dé et autres outils."), discord.ui.Separator(), discord.ui.ActionRow( _btn("🎲 Lancer un Dé", discord.ButtonStyle.primary, "dp_roll", self._roll), ), ] if author.guild_permissions.administrator: items.append( discord.ui.ActionRow( _btn("📊 Stats Bot", discord.ButtonStyle.secondary, "dp_stats", self._stats), ) ) 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 — MENU ADAPTATIF # ═══════════════════════════════════════════════════════════════════ class CategorySelect(discord.ui.Select): def __init__(self, author, bot): options = [ discord.SelectOption(label="Mes Comptes", description="Comptes, virements, cartes", emoji="💰", value="compte"), ] if has_role(author, "banquier"): options.append( discord.SelectOption(label="Opérations Banquier", description="Ouvrir comptes, opérations", 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="Identité", description="Cartes d'identité, permis", emoji="🪪", value="identite") ) if author.guild_permissions.administrator: options.append( discord.SelectOption(label="Tickets & Messages", description="Tickets, MP privés", emoji="📩", value="tickets") ) options.append( discord.SelectOption(label="Sessions RP", description="Annonces, présences", emoji="📅", value="sessions") ) options.append( discord.SelectOption(label="Divers", description="Dé, 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="Sélectionnez une catégorie...", options=options, custom_id="pnl_catselect") 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-école") 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 rôles :** {role_text}"), discord.ui.TextDisplay("Sélectionnez une catégorie ci-dessous pour accéder aux commandes."), 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 expiré. 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))