""" Panel centralise — Container V2 (discord.py 2.7) Menus adaptatifs selon les roles de l'utilisateur. Banquiers : UserSelect pour choisir l'utilisateur cible. """ import discord from discord.ext import commands from discord import app_commands import json import math import random import string import os import asyncio from datetime import datetime, timedelta from utils.guild_config import ( has_role, get_guild_role, get_guild_channel, get_guild_category, load_guild_config, save_guild_config, set_config_value, ROLE_LABELS, CHANNEL_LABELS, CATEGORY_LABELS, get_role_id ) from utils.data_manager import DataManager C_MAIN = 0x2F3136 C_BANK = 0xF1C40F C_GOV = 0x1ABC9C C_ID = 0x3498DB C_TICKET = 0xE74C3C C_SESSION = 0x9B59B6 C_CONFIG = 0x95A5A6 C_FUN = 0xE67E22 C_BANQ_OP = 0x2ECC71 BANQUES = ["Victoria's Credit Bank", "NGC Bank"] def _load_json(path, default=None): if default is None: default = {} if not os.path.exists(path): return default try: with open(path, "r", encoding="utf-8") as f: return json.load(f) except (json.JSONDecodeError, IOError): return default def _save_json(path, data): with open(path, "w", encoding="utf-8") as f: json.dump(data, f, indent=4, ensure_ascii=False) def _find_account_by_rib(rib, comptes, entreprises, banque_nom=None): if banque_nom: if banque_nom in comptes: for uid, data in comptes[banque_nom].items(): if data.get("rib") == rib: return comptes, banque_nom, uid, False if banque_nom in entreprises: for nom, data in entreprises[banque_nom].items(): if data.get("rib") == rib: return entreprises, banque_nom, nom, True for bq, users in comptes.items(): for uid, data in users.items(): if data.get("rib") == rib: return comptes, bq, uid, False for bq, ents in entreprises.items(): for nom, data in ents.items(): if data.get("rib") == rib: return entreprises, bq, nom, True return None, None, None, None def _get_user_accounts(uid_str, comptes): accounts = [] for banque, users in comptes.items(): if uid_str in users: c = users[uid_str] accounts.append((banque, c)) return accounts # ═══════════════════════════════════════════════════════════════════ # USER SELECT (Banquiers uniquement) # ═══════════════════════════════════════════════════════════════════ class TargetUserSelect(discord.ui.UserSelect): def __init__(self, custom_id="pnl_usel", placeholder="Choisir un utilisateur (defaut: vous)"): super().__init__(placeholder=placeholder, custom_id=custom_id, min_values=1, max_values=1) async def callback(self, interaction: discord.Interaction): self.view.selected_user = self.values[0] await interaction.response.defer() # ═══════════════════════════════════════════════════════════════════ # MODALS — COMPTES (tout le monde) # ═══════════════════════════════════════════════════════════════════ class CompteModal(discord.ui.Modal, title="💰 Voir un Compte"): def __init__(self, target_user=None): super().__init__() self.target_user = target_user async def on_submit(self, interaction: discord.Interaction): if self.target_user: uid_str = str(self.target_user.id) member = self.target_user else: uid_str = str(interaction.user.id) member = interaction.user comptes = _load_json("comptes_bancaires.json") accounts = _get_user_accounts(uid_str, comptes) if not accounts: await interaction.response.send_message( f"❌ Aucun compte trouvé pour **{member.display_name}**.", ephemeral=True ) return embed = discord.Embed( title=f"💰 Comptes de {member.display_name}", color=C_BANK, ) for banque, c in accounts: txs = c.get("transactions", []) tx_text = "" for t in txs[-3:]: tx_text += ( f"> 📅 {t.get('date', 'N/A')} — **{t.get('type', 'N/A')}** — " f"{t.get('montant', 0)}€\n" ) if not tx_text: tx_text = "> Aucune transaction récente.\n" embed.add_field( name=f"🏦 {banque}", value=( f"**RIB** : `{c.get('rib', 'N/A')}`\n" f"**Solde** : **{c.get('solde', 0):.2f}€**\n" f"**Dernières transactions** :\n{tx_text}" ), inline=False, ) await interaction.response.send_message(embed=embed, ephemeral=True) class VirementModal(discord.ui.Modal, title="📤 Effectuer un Virement"): rib_dest = discord.ui.TextInput( label="RIB destinataire", required=True, max_length=40 ) montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) banque = discord.ui.TextInput( label="Banque (laisser vide = auto)", required=False, max_length=50 ) async def on_submit(self, interaction: discord.Interaction): try: montant_val = float(self.montant.value.strip()) except ValueError: await interaction.response.send_message( "❌ Montant invalide.", ephemeral=True ) return if montant_val <= 0: await interaction.response.send_message( "❌ Le montant doit être supérieur à 0.", ephemeral=True ) return rib_dest_val = self.rib_dest.value.strip() banque_nom = self.banque.value.strip() if self.banque.value else None comptes = _load_json("comptes_bancaires.json") entreprises = _load_json("comptes_entreprises.json") uid_str = str(interaction.user.id) source = None source_bq = None for bq, users in comptes.items(): if uid_str in users: source = users[uid_str] source_bq = bq break if not source: await interaction.response.send_message( "❌ Vous n'avez pas de compte bancaire.", ephemeral=True ) return if source["solde"] < montant_val: await interaction.response.send_message( "❌ Solde insuffisant.", ephemeral=True ) return dest_store, dest_bq, dest_id, dest_ent = _find_account_by_rib( rib_dest_val, comptes, entreprises, banque_nom ) if not dest_store: await interaction.response.send_message( "❌ RIB destinataire introuvable.", ephemeral=True ) return now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") source["solde"] -= montant_val source.setdefault("transactions", []).append( { "date": now, "type": "Virement", "source": f"Vers {rib_dest_val}", "montant": -montant_val, } ) dest_store[dest_bq][dest_id]["solde"] += montant_val dest_store[dest_bq][dest_id].setdefault("transactions", []).append( { "date": now, "type": "Virement reçu", "source": interaction.user.name, "montant": montant_val, } ) _save_json("comptes_bancaires.json", comptes) _save_json("comptes_entreprises.json", entreprises) await interaction.response.send_message( f"✅ Virement de **{montant_val:.2f}€** effectué vers `{rib_dest_val}` dans **{dest_bq}**.", ephemeral=False, ) class AbonnementModal(discord.ui.Modal, title="🔄 Créer un Abonnement"): nom = discord.ui.TextInput( label="Nom de l'abonnement", required=True, max_length=50 ) rib_emetteur = discord.ui.TextInput( label="RIB émetteur", required=True, max_length=40 ) montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) rib_receveur = discord.ui.TextInput( label="RIB receveur", required=True, max_length=40 ) banque = discord.ui.TextInput(label="Banque", required=True, max_length=50) jour = discord.ui.TextInput( label="Jour (lundi, mardi...)", required=True, max_length=10 ) async def on_submit(self, interaction: discord.Interaction): JOURS = [ "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche", ] jour_val = self.jour.value.strip().lower() if jour_val not in JOURS: await interaction.response.send_message( f"❌ Jour invalide. {', '.join(JOURS)}", ephemeral=True ) return try: montant_val = float(self.montant.value.strip()) except ValueError: await interaction.response.send_message( "❌ Montant invalide.", ephemeral=True ) return abos = _load_json("abonnements.json", []) abos.append( { "nom": self.nom.value.strip(), "rib_emetteur": self.rib_emetteur.value.strip(), "rib_receveur": self.rib_receveur.value.strip(), "montant": montant_val, "banque": self.banque.value.strip(), "jour": jour_val, } ) _save_json("abonnements.json", abos) await interaction.response.send_message( f"✅ Abonnement **{self.nom.value.strip()}** enregistré. " f"Virement automatique tous les **{jour_val}**.", ephemeral=True, ) # ═══════════════════════════════════════════════════════════════════ # MODALS — OPERATIONS BANQUIER # ═══════════════════════════════════════════════════════════════════ class OuvrirCompteModal(discord.ui.Modal, title="🏦 Ouvrir un Compte"): def __init__(self, target_user=None): super().__init__() self.target_user = target_user async def on_submit(self, interaction: discord.Interaction): if not has_role(interaction.user, "banquier"): await interaction.response.send_message( "❌ Réservé aux banquiers.", ephemeral=True ) return member = self.target_user or interaction.user uid = member.id embed = discord.Embed( title="🏦 Ouvrir un Compte", description=f"Compte pour : **{member.display_name}**", color=C_BANQ_OP, ) await interaction.response.send_message(embed=embed, ephemeral=True) async def _actual_open(self, interaction, banque_nom, uid, member): comptes = _load_json("comptes_bancaires.json") if banque_nom not in comptes: comptes[banque_nom] = {} uid_str = str(uid) if uid_str in comptes[banque_nom]: return False, f"{member.mention} a déjà un compte dans **{banque_nom}**." rib = ( f"{random.randint(10000, 99999)} {random.randint(10000, 99999)} " f"{random.randint(10000000000, 99999999999)} {random.randint(10, 99)}" ) comptes[banque_nom][uid_str] = { "rib": rib, "solde": 0.0, "transactions": [], } _save_json("comptes_bancaires.json", comptes) try: await member.send( f"🏦 Un compte **{banque_nom}** a été créé pour vous.\nRIB : `{rib}`" ) except discord.Forbidden: pass return True, f"✅ Compte créé pour {member.mention} dans **{banque_nom}**. RIB: `{rib}`" class OuvrirCompteSimpleModal(discord.ui.Modal, title="🏦 Ouvrir Compte Personnel"): banque = discord.ui.TextInput( label="Banque (V'Toria's Credit Bank / NGC Bank)", required=True, max_length=50, ) def __init__(self, target_user=None): super().__init__() self.target_user = target_user async def on_submit(self, interaction: discord.Interaction): if not has_role(interaction.user, "banquier"): await interaction.response.send_message( "❌ Réservé aux banquiers.", ephemeral=True ) return banque_nom = self.banque.value.strip() if banque_nom not in BANQUES: await interaction.response.send_message( f"❌ Banque invalide. Choisissez : {', '.join(BANQUES)}", ephemeral=True, ) return member = self.target_user or interaction.user uid = member.id uid_str = str(uid) comptes = _load_json("comptes_bancaires.json") if banque_nom not in comptes: comptes[banque_nom] = {} if uid_str in comptes[banque_nom]: await interaction.response.send_message( f"❌ {member.mention} a déjà un compte dans **{banque_nom}**.", ephemeral=True, ) return rib = ( f"{random.randint(10000, 99999)} {random.randint(10000, 99999)} " f"{random.randint(10000000000, 99999999999)} {random.randint(10, 99)}" ) comptes[banque_nom][uid_str] = { "rib": rib, "solde": 0.0, "transactions": [], } _save_json("comptes_bancaires.json", comptes) try: await member.send( f"🏦 Un compte **{banque_nom}** a été créé pour vous.\nRIB : `{rib}`" ) except discord.Forbidden: pass await interaction.response.send_message( f"✅ Compte créé pour {member.mention} dans **{banque_nom}**. RIB: `{rib}`", ephemeral=False, ) class OuvrirCompteEntModal(discord.ui.Modal, title="🏢 Ouvrir Compte Entreprise"): entreprise = discord.ui.TextInput( label="Nom de l'entreprise", required=True, max_length=50 ) banque = discord.ui.TextInput( label="Banque (V'Toria's Credit Bank / NGC Bank)", required=True, max_length=50, ) tva = discord.ui.TextInput(label="TVA (2, 5, 10 ou 20)", required=True, max_length=3) async def on_submit(self, interaction: discord.Interaction): if not has_role(interaction.user, "banquier"): await interaction.response.send_message( "❌ Réservé aux banquiers.", ephemeral=True ) return banque_nom = self.banque.value.strip() if banque_nom not in BANQUES: await interaction.response.send_message( f"❌ Banque invalide. Choisissez : {', '.join(BANQUES)}", ephemeral=True, ) return try: tva_val = int(self.tva.value.strip()) if tva_val not in (2, 5, 10, 20): raise ValueError except ValueError: await interaction.response.send_message( "❌ TVA invalide (2, 5, 10 ou 20).", ephemeral=True ) return nom_ent = self.entreprise.value.strip() entreprises = _load_json("comptes_entreprises.json") if banque_nom not in entreprises: entreprises[banque_nom] = {} if nom_ent in entreprises[banque_nom]: await interaction.response.send_message( f"❌ Un compte existe déjà pour **{nom_ent}** dans **{banque_nom}**.", ephemeral=True, ) return rib = " ".join( ["".join(random.choices(string.digits, k=5)) for _ in range(4)] ) mdp = "".join(random.choices(string.ascii_letters + string.digits, k=8)) entreprises[banque_nom][nom_ent] = { "rib": rib, "solde": 0, "mdp": mdp, "tva": tva_val, "transactions": [], } _save_json("comptes_entreprises.json", entreprises) try: await interaction.user.send( f"🏦 Compte entreprise **{nom_ent}** ouvert dans **{banque_nom}** !\n" f"💳 RIB : `{rib}`\n🔐 Mot de passe : `{mdp}`\n💼 TVA : `{tva_val}%`" ) except discord.Forbidden: pass await interaction.response.send_message( f"✅ Compte entreprise **{nom_ent}** créé dans **{banque_nom}**.", ephemeral=True, ) class VirementBanquierModal(discord.ui.Modal, title="🏦 Virement Banquier"): rib_emetteur = discord.ui.TextInput( label="RIB émetteur", required=True, max_length=40 ) rib_destinataire = discord.ui.TextInput( label="RIB destinataire", required=True, max_length=40 ) montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) banque = discord.ui.TextInput( label="Banque (V'Toria's Credit Bank / NGC Bank)", required=True, max_length=50, ) async def on_submit(self, interaction: discord.Interaction): if not has_role(interaction.user, "banquier"): await interaction.response.send_message( "❌ Réservé aux banquiers.", ephemeral=True ) return try: montant_val = float(self.montant.value.strip()) except ValueError: await interaction.response.send_message( "❌ Montant invalide.", ephemeral=True ) return banque_nom = self.banque.value.strip() comptes = _load_json("comptes_bancaires.json") if banque_nom not in comptes: await interaction.response.send_message( "❌ Banque introuvable.", ephemeral=True ) return emetteur_id = None destinataire_id = None for uid, data in comptes[banque_nom].items(): if data.get("rib") == self.rib_emetteur.value.strip(): emetteur_id = uid if data.get("rib") == self.rib_destinataire.value.strip(): destinataire_id = uid if not emetteur_id: await interaction.response.send_message( "❌ RIB émetteur introuvable.", ephemeral=True ) return if not destinataire_id: await interaction.response.send_message( "❌ RIB destinataire introuvable.", ephemeral=True ) return if comptes[banque_nom][emetteur_id]["solde"] < montant_val: await interaction.response.send_message( "❌ Solde insuffisant.", ephemeral=True ) return now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") comptes[banque_nom][emetteur_id]["solde"] -= montant_val comptes[banque_nom][emetteur_id]["transactions"].append( { "date": now, "type": "Virement banquier", "source": f"Vers {self.rib_destinataire.value}", "montant": -montant_val, } ) comptes[banque_nom][destinataire_id]["solde"] += montant_val comptes[banque_nom][destinataire_id]["transactions"].append( { "date": now, "type": "Virement reçu (banquier)", "source": f"De {self.rib_emetteur.value}", "montant": montant_val, } ) _save_json("comptes_bancaires.json", comptes) await interaction.response.send_message( f"✅ Virement banquier de **{montant_val:.2f}€** de " f"`{self.rib_emetteur.value}` vers `{self.rib_destinataire.value}` " f"dans **{banque_nom}**.", ephemeral=True, ) class VirementBanquierEntModal(discord.ui.Modal, title="🏢 Vir. Banquier → Entreprise"): banque = discord.ui.TextInput( label="Banque (V'Toria's Credit Bank / NGC Bank)", required=True, max_length=50, ) entreprise = discord.ui.TextInput( label="Nom entreprise", required=True, max_length=50 ) mdp = discord.ui.TextInput( label="Mot de passe entreprise", required=True, max_length=20 ) rib_dest = discord.ui.TextInput( label="RIB destinataire", required=True, max_length=40 ) montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) async def on_submit(self, interaction: discord.Interaction): if not has_role(interaction.user, "banquier"): await interaction.response.send_message( "❌ Réservé aux banquiers.", ephemeral=True ) return try: montant_val = float(self.montant.value.strip()) except ValueError: await interaction.response.send_message( "❌ Montant invalide.", ephemeral=True ) return banque_nom = self.banque.value.strip() entreprises = _load_json("comptes_entreprises.json") comptes = _load_json("comptes_bancaires.json") if ( banque_nom not in entreprises or self.entreprise.value.strip() not in entreprises[banque_nom] ): await interaction.response.send_message( "❌ Compte entreprise introuvable.", ephemeral=True ) return compte_ent = entreprises[banque_nom][self.entreprise.value.strip()] if compte_ent.get("mdp") != self.mdp.value.strip(): await interaction.response.send_message( "❌ Mot de passe incorrect.", ephemeral=True ) return if compte_ent["solde"] < montant_val: await interaction.response.send_message( "❌ Solde insuffisant.", ephemeral=True ) return dest_store, dest_bq, dest_id, _ = _find_account_by_rib( self.rib_dest.value.strip(), comptes, entreprises ) if not dest_store: await interaction.response.send_message( "❌ RIB destinataire introuvable.", ephemeral=True ) return now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") compte_ent["solde"] -= montant_val compte_ent.setdefault("transactions", []).append( { "date": now, "type": "Virement", "source": f"Vers {self.rib_dest.value}", "montant": -montant_val, } ) dest_store[dest_bq][dest_id]["solde"] += montant_val dest_store[dest_bq][dest_id].setdefault("transactions", []).append( { "date": now, "type": "Virement reçu", "source": self.entreprise.value.strip(), "montant": montant_val, } ) _save_json("comptes_entreprises.json", entreprises) _save_json("comptes_bancaires.json", comptes) await interaction.response.send_message( f"✅ Virement de **{montant_val:.2f}€** depuis " f"**{self.entreprise.value.strip()}** vers `{self.rib_dest.value.strip()}`.", ephemeral=True, ) class AjouterArgentModal(discord.ui.Modal, title="➕ Ajouter de l'Argent"): rib = discord.ui.TextInput(label="RIB du compte", required=True, max_length=40) montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) banque = discord.ui.TextInput( label="Banque (optionnel)", required=False, max_length=50 ) async def on_submit(self, interaction: discord.Interaction): if not has_role(interaction.user, "banquier"): await interaction.response.send_message( "❌ Réservé aux banquiers.", ephemeral=True ) return try: montant_val = float(self.montant.value.strip()) except ValueError: await interaction.response.send_message( "❌ Montant invalide.", ephemeral=True ) return if montant_val <= 0: await interaction.response.send_message( "❌ Montant doit être > 0.", ephemeral=True ) return banque_nom = self.banque.value.strip() if self.banque.value else None comptes = _load_json("comptes_bancaires.json") entreprises = _load_json("comptes_entreprises.json") store, bq, uid, is_ent = _find_account_by_rib( self.rib.value.strip(), comptes, entreprises, banque_nom ) if not store: await interaction.response.send_message( "❌ Aucun compte trouvé avec ce RIB.", ephemeral=True ) return now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") store[bq][uid]["solde"] += montant_val store[bq][uid].setdefault("transactions", []).append( { "date": now, "type": "Dépôt", "source": "Banque", "montant": montant_val, "emetteur": str(interaction.user.id), "destinataire": uid, } ) if is_ent: _save_json("comptes_entreprises.json", entreprises) else: _save_json("comptes_bancaires.json", comptes) try: user = await interaction.client.fetch_user(int(uid)) await user.send( f"💰 Dépôt de **{montant_val:.2f}€** sur votre compte **{bq}**." ) except Exception: pass await interaction.response.send_message( f"✅ **{montant_val:.2f}€** ajoutés au compte `{self.rib.value.strip()}` dans **{bq}**.", ephemeral=True, ) class RetirerArgentModal(discord.ui.Modal, title="➖ Retirer de l'Argent"): rib = discord.ui.TextInput(label="RIB du compte", required=True, max_length=40) montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) banque = discord.ui.TextInput( label="Banque (optionnel)", required=False, max_length=50 ) async def on_submit(self, interaction: discord.Interaction): if not has_role(interaction.user, "banquier"): await interaction.response.send_message( "❌ Réservé aux banquiers.", ephemeral=True ) return try: montant_val = float(self.montant.value.strip()) except ValueError: await interaction.response.send_message( "❌ Montant invalide.", ephemeral=True ) return banque_nom = self.banque.value.strip() if self.banque.value else None comptes = _load_json("comptes_bancaires.json") entreprises = _load_json("comptes_entreprises.json") store, bq, uid, is_ent = _find_account_by_rib( self.rib.value.strip(), comptes, entreprises, banque_nom ) if not store: await interaction.response.send_message( "❌ Aucun compte trouvé avec ce RIB.", ephemeral=True ) return if store[bq][uid]["solde"] < montant_val: await interaction.response.send_message( "❌ Solde insuffisant.", ephemeral=True ) return now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") store[bq][uid]["solde"] -= montant_val store[bq][uid].setdefault("transactions", []).append( { "date": now, "type": "Retrait", "source": "Banque", "montant": -montant_val, "emetteur": str(interaction.user.id), "destinataire": uid, } ) if is_ent: _save_json("comptes_entreprises.json", entreprises) else: _save_json("comptes_bancaires.json", comptes) embed = discord.Embed( title="💸 Retrait effectué", description=( f"💼 Banque : **{bq}**\n" f"RIB : `{self.rib.value.strip()}`\n" f"Montant : **{montant_val:.2f}€**" ), color=discord.Color.red(), ) await interaction.response.send_message(embed=embed, ephemeral=False) class TransactionsModal(discord.ui.Modal, title="📜 Voir Transactions"): def __init__(self, target_user=None): super().__init__() self.target_user = target_user async def on_submit(self, interaction: discord.Interaction): if not has_role(interaction.user, "banquier"): await interaction.response.send_message( "❌ Réservé aux banquiers.", ephemeral=True ) return member = self.target_user or interaction.user uid_str = str(member.id) comptes = _load_json("comptes_bancaires.json") all_txs = [] for banque, users in comptes.items(): if uid_str in users: for t in users[uid_str].get("transactions", []): all_txs.append(t) if not all_txs: await interaction.response.send_message( f"📜 {member.mention} n'a aucune transaction.", ephemeral=True ) return embed = discord.Embed( title=f"📜 Transactions de {member.display_name}", color=discord.Color.gold(), ) for t in all_txs[-10:]: embed.add_field( name=f"📅 {t.get('date', 'N/A')}", value=( f"**Type**: {t.get('type', 'N/A')}\n" f"💰 **Montant**: {t.get('montant', 0)}€\n" f"📤 **Source**: {t.get('source', 'N/A')}" ), inline=False, ) embed.set_footer(text="10 dernières transactions") await interaction.response.send_message(embed=embed, ephemeral=True) # ═══════════════════════════════════════════════════════════════════ # MODALS — GOUVERNEMENT # ═══════════════════════════════════════════════════════════════════ class GouvVirerModal(discord.ui.Modal, title="💸 Virement Gouvernement"): rib = discord.ui.TextInput( label="RIB destinataire", required=True, max_length=40 ) montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) banque = discord.ui.TextInput( label="Banque (optionnel)", required=False, max_length=50 ) 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_val = float(self.montant.value.strip()) except ValueError: await interaction.response.send_message( "❌ Montant invalide.", ephemeral=True ) return if montant_val <= 0: await interaction.response.send_message( "❌ Montant doit être > 0.", ephemeral=True ) return banque_nom = self.banque.value.strip() if self.banque.value else None comptes = _load_json("comptes_bancaires.json") entreprises = _load_json("comptes_entreprises.json") gouv = entreprises.get("Gouvernement", {}).get("GOUV") if not gouv: await interaction.response.send_message( "❌ Compte Gouvernement introuvable.", ephemeral=True ) return if gouv["solde"] < montant_val: await interaction.response.send_message( "❌ Fonds insuffisants.", ephemeral=True ) return dest_store, dest_bq, dest_id, _ = _find_account_by_rib( self.rib.value.strip(), comptes, entreprises, banque_nom ) if not dest_store: await interaction.response.send_message( "❌ RIB destinataire introuvable.", ephemeral=True ) return now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") gouv["solde"] -= montant_val gouv.setdefault("transactions", []).append( { "date": now, "type": "Virement", "source": "Gouvernement", "montant": -montant_val, "destinataire": self.rib.value.strip(), } ) dest_store[dest_bq][dest_id]["solde"] += montant_val dest_store[dest_bq][dest_id].setdefault("transactions", []).append( { "date": now, "type": "Virement reçu", "source": "Gouvernement", "montant": montant_val, } ) _save_json("comptes_entreprises.json", entreprises) _save_json("comptes_bancaires.json", comptes) embed = discord.Embed( title="🏛️ Virement Gouvernemental effectué", description=( f"RIB : `{self.rib.value.strip()}`\n" f"Montant : **{montant_val:.2f}€**\n" f"Banque : **{dest_bq}**" ), color=discord.Color.gold(), ) await interaction.response.send_message(embed=embed) class RetraitGouvModal(discord.ui.Modal, title="🏦 Retrait Gouvernement"): montant = discord.ui.TextInput(label="Montant", 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: montant_val = float(self.montant.value.strip()) except ValueError: await interaction.response.send_message( "❌ Montant invalide.", ephemeral=True ) return entreprises = _load_json("comptes_entreprises.json") gouv = entreprises.get("Gouvernement", {}).get("GOUV") if not gouv: await interaction.response.send_message( "❌ Compte Gouvernement introuvable.", ephemeral=True ) return if gouv["solde"] < montant_val: await interaction.response.send_message( "❌ Fonds insuffisants.", ephemeral=True ) return gouv["solde"] -= montant_val now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") gouv.setdefault("transactions", []).append( { "date": now, "type": "Retrait", "source": "Gouvernement", "montant": -montant_val, } ) _save_json("comptes_entreprises.json", entreprises) embed = discord.Embed( title="🏦 Retrait Gouvernement", description=( f"Montant : **{montant_val:.2f}€**\n" f"Solde restant : **{gouv['solde']:.2f}€**" ), color=discord.Color.gold(), ) await interaction.response.send_message(embed=embed) class VirementFBMModal(discord.ui.Modal, title="📤 Virement vers FBM"): montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) banque = discord.ui.TextInput( label="Banque (V'Toria's Credit Bank / NGC Bank)", required=True, max_length=50, ) async def on_submit(self, interaction: discord.Interaction): try: montant_val = float(self.montant.value.strip()) except ValueError: await interaction.response.send_message( "❌ Montant invalide.", ephemeral=True ) return if montant_val <= 0: await interaction.response.send_message( "❌ Montant doit être > 0.", ephemeral=True ) return banque_nom = self.banque.value.strip() uid_str = str(interaction.user.id) comptes = _load_json("comptes_bancaires.json") entreprises = _load_json("comptes_entreprises.json") if banque_nom not in comptes or uid_str not in comptes[banque_nom]: await interaction.response.send_message( "❌ Vous n'avez pas de compte dans cette banque.", ephemeral=True ) return compte = comptes[banque_nom][uid_str] if compte["solde"] < montant_val: await interaction.response.send_message( "❌ Solde insuffisant.", ephemeral=True ) return if "fbm" not in entreprises: entreprises["fbm"] = {} if "FBM" not in entreprises["fbm"]: entreprises["fbm"]["FBM"] = { "rib": "00000 00000 00000000000 03", "solde": 0.0, "transactions": [], } now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") compte["solde"] -= montant_val compte.setdefault("transactions", []).append( { "date": now, "type": "Virement", "source": "Vers FBM", "montant": -montant_val, } ) entreprises["fbm"]["FBM"]["solde"] += montant_val entreprises["fbm"]["FBM"].setdefault("transactions", []).append( { "date": now, "type": "Virement reçu", "source": interaction.user.name, "montant": montant_val, } ) _save_json("comptes_bancaires.json", comptes) _save_json("comptes_entreprises.json", entreprises) await interaction.response.send_message( f"✅ Virement de **{montant_val:.2f}€** envoyé à **FBM** depuis **{banque_nom}**.", ephemeral=True, ) class VirementURSAFFModal(discord.ui.Modal, title="📤 Virement vers URSAFF"): montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) banque = discord.ui.TextInput( label="Banque (V'Toria's Credit Bank / NGC Bank)", required=True, max_length=50, ) async def on_submit(self, interaction: discord.Interaction): try: montant_val = float(self.montant.value.strip()) except ValueError: await interaction.response.send_message( "❌ Montant invalide.", ephemeral=True ) return if montant_val <= 0: await interaction.response.send_message( "❌ Montant doit être > 0.", ephemeral=True ) return banque_nom = self.banque.value.strip() uid_str = str(interaction.user.id) comptes = _load_json("comptes_bancaires.json") entreprises = _load_json("comptes_entreprises.json") if banque_nom not in comptes or uid_str not in comptes[banque_nom]: await interaction.response.send_message( "❌ Vous n'avez pas de compte dans cette banque.", ephemeral=True ) return compte = comptes[banque_nom][uid_str] if compte["solde"] < montant_val: await interaction.response.send_message( "❌ Solde insuffisant.", ephemeral=True ) return if "ursaff" not in entreprises: entreprises["ursaff"] = {} if "URSAFF" not in entreprises["ursaff"]: entreprises["ursaff"]["URSAFF"] = { "rib": "00000 00000 00000000000 02", "solde": 0.0, "transactions": [], } now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") compte["solde"] -= montant_val compte.setdefault("transactions", []).append( { "date": now, "type": "Virement", "source": "Vers URSAFF", "montant": -montant_val, } ) entreprises["ursaff"]["URSAFF"]["solde"] += montant_val entreprises["ursaff"]["URSAFF"].setdefault("transactions", []).append( { "date": now, "type": "Virement reçu", "source": interaction.user.name, "montant": montant_val, } ) _save_json("comptes_bancaires.json", comptes) _save_json("comptes_entreprises.json", entreprises) await interaction.response.send_message( f"✅ Virement de **{montant_val:.2f}€** envoyé à **URSAFF** depuis **{banque_nom}**.", ephemeral=True, ) # ═══════════════════════════════════════════════════════════════════ # MODALS — IDENTITE # ═══════════════════════════════════════════════════════════════════ class CreerCarteModal(discord.ui.Modal, title="🪪 Créer une Carte d'Identité"): utilisateur = discord.ui.TextInput( label="ID Utilisateur", required=True, max_length=20 ) nom_complet = discord.ui.TextInput( label="Nom complet (Ex: Dupont Jean)", required=True, max_length=100 ) date_naissance = discord.ui.TextInput( label="Date de naissance", required=True, max_length=20 ) lieu_naissance = discord.ui.TextInput( label="Lieu de naissance", required=True, max_length=50 ) sexe = discord.ui.TextInput( label="Sexe (Homme/Femme/Autre)", required=True, max_length=6 ) 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 uid = self.utilisateur.value.strip() parts = self.nom_complet.value.strip().split(maxsplit=1) nom_val = parts[0].upper() if parts else "" prenom_val = parts[1].capitalize() if len(parts) > 1 else "" dm = DataManager() date_creation = datetime.now() date_exp = (date_creation + timedelta(days=730)).strftime("%d/%m/%Y") dm.identities[uid] = { "nom": nom_val, "prenom": prenom_val, "date_naissance": self.date_naissance.value.strip(), "lieu_naissance": self.lieu_naissance.value.strip(), "sexe": self.sexe.value.strip().capitalize(), "date_expiration": date_exp, } dm.save_data() await interaction.response.send_message( f"✅ Carte d'identité créée pour <@{uid}>.", ephemeral=False ) class VoirCarteModal(discord.ui.Modal, title="🪪 Voir une Carte d'Identité"): def __init__(self, target_user=None): super().__init__() self.target_user = target_user async def on_submit(self, interaction: discord.Interaction): if self.target_user: uid = str(self.target_user.id) member = self.target_user else: uid = str(interaction.user.id) member = interaction.user 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(member, 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 ) class PermisModal(discord.ui.Modal, title="🚗 Ajouter un Permis"): membre = discord.ui.TextInput(label="ID du membre", required=True, max_length=20) async def on_submit(self, interaction: discord.Interaction): if not has_role(interaction.user, "auto_ecole"): await interaction.response.send_message( "❌ Réservé à l'auto-école.", ephemeral=True ) return try: member = await interaction.guild.fetch_member( int(self.membre.value.strip()) ) role = get_guild_role(interaction.guild, "permis") if not role: await interaction.response.send_message( "❌ Rôle permis introuvable.", ephemeral=True ) return await member.add_roles(role) await interaction.response.send_message( f"✅ Permis attribué à {member.mention}.", ephemeral=True ) except (ValueError, discord.NotFound): await interaction.response.send_message( "❌ Membre introuvable.", ephemeral=True ) class VisiteMedModal(discord.ui.Modal, title="🏥 Valider Visite Médicale"): membre = discord.ui.TextInput(label="ID du membre", required=True, max_length=20) async def on_submit(self, interaction: discord.Interaction): if not has_role(interaction.user, "ems"): await interaction.response.send_message( "❌ Réservé aux EMS.", ephemeral=True ) return try: member = await interaction.guild.fetch_member( int(self.membre.value.strip()) ) role = get_guild_role(interaction.guild, "visite_medicale") if not role: await interaction.response.send_message( "❌ Rôle visite médicale introuvable.", ephemeral=True ) return await member.add_roles(role) fichier = "visite_médicale.json" data = _load_json(fichier) expiration = (datetime.now() + timedelta(days=30)).strftime( "%Y-%m-%d %H:%M:%S" ) data[str(member.id)] = {"role_id": role.id, "expiration": expiration} _save_json(fichier, data) await interaction.response.send_message( f"✅ Visite médicale validée pour {member.mention} jusqu'au {expiration}.", ephemeral=True, ) except (ValueError, discord.NotFound): await interaction.response.send_message( "❌ Membre introuvable.", ephemeral=True ) # ═══════════════════════════════════════════════════════════════════ # MODALS — TICKETS & MESSAGES # ═══════════════════════════════════════════════════════════════════ class MessageModal(discord.ui.Modal, title="✉️ Envoyer un MP"): membre = discord.ui.TextInput( label="ID du membre", required=True, max_length=20 ) message = discord.ui.TextInput( label="Message", style=discord.TextStyle.paragraph, required=True, max_length=2000, ) async def on_submit(self, interaction: discord.Interaction): if not interaction.user.guild_permissions.administrator: await interaction.response.send_message( "❌ Réservé aux administrateurs.", ephemeral=True ) return try: user = await interaction.client.fetch_user(int(self.membre.value.strip())) await user.send(self.message.value) await interaction.response.send_message( f"✅ MP envoyé à {user.name}.", ephemeral=True ) except (ValueError, discord.NotFound): await interaction.response.send_message( "❌ Membre introuvable.", ephemeral=True ) except discord.Forbidden: await interaction.response.send_message( "❌ Impossible d'envoyer le MP.", ephemeral=True ) class MessageTousModal(discord.ui.Modal, title="📢 MP à Tous"): message = discord.ui.TextInput( label="Message", style=discord.TextStyle.paragraph, required=True, max_length=2000, ) async def on_submit(self, interaction: discord.Interaction): if not interaction.user.guild_permissions.administrator: await interaction.response.send_message( "❌ Réservé aux administrateurs.", ephemeral=True ) return await interaction.response.defer(ephemeral=True) sent, failed = 0, 0 for member in interaction.guild.members: if not member.bot: try: await member.send(self.message.value) sent += 1 except Exception: failed += 1 await interaction.followup.send( f"✅ MP envoyés : {sent} | Échecs : {failed}", 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 session_date = session_date.astimezone(timezone.utc) r1 = r1.astimezone(timezone.utc) r2 = r2.astimezone(timezone.utc) 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 de présence 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, ) # ═══════════════════════════════════════════════════════════════════ # MODALS — UTILITAIRES # ═══════════════════════════════════════════════════════════════════ 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" f"*Raison : {self.raison.value.strip()}*" ), color=C_FUN, ) await interaction.response.send_message(embed=embed) # ═══════════════════════════════════════════════════════════════════ # PANELS — VUES # ═══════════════════════════════════════════════════════════════════ 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) # ─── MON COMPTE (tout le monde, banquiers ont UserSelect) ─────── class ComptePanelView(discord.ui.LayoutView): def __init__(self, author, bot): super().__init__(timeout=300) self.author = author self.bot_ref = bot self.selected_user = None is_banquier = has_role(author, "banquier") target_label = "👤 **Vous-même**" items = [ discord.ui.TextDisplay("## 💰 Mes Comptes"), discord.ui.TextDisplay( "Consultez vos comptes, faites des virements et gérez vos abonnements." ), discord.ui.Separator(), ] if is_banquier: items.append(discord.ui.ActionRow(TargetUserSelect())) items.append( discord.ui.TextDisplay("* Sélectionnez un utilisateur ci-dessus pour opérer sur son compte." )) items.append(discord.ui.Separator()) items.extend([ discord.ui.ActionRow( _btn("💰 Voir Compte", discord.ButtonStyle.primary, "cp_voir", self._voir), _btn("📤 Virement", discord.ButtonStyle.primary, "cp_vir", self._vir), _btn("🔄 Abonnement", discord.ButtonStyle.primary, "cp_abo", self._abo), ), discord.ui.ActionRow( _btn("🏛️ Vir. Gouvernement", discord.ButtonStyle.primary, "cp_vg", self._vg), _btn("🏢 Vir. FBM", discord.ButtonStyle.primary, "cp_vfbm", self._vfbm), _btn("🏢 Vir. URSAFF", discord.ButtonStyle.primary, "cp_vurs", self._vurs), ), 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 _target(self, interaction): return self.selected_user or interaction.user async def _back(self, interaction): await interaction.response.edit_message( view=MainPanelView(interaction.user, self.bot_ref) ) async def _voir(self, interaction): await interaction.response.send_modal( CompteModal(target_user=self._target(interaction)) ) async def _vir(self, interaction): await interaction.response.send_modal(VirementModal()) async def _abo(self, interaction): await interaction.response.send_modal(AbonnementModal()) async def _vg(self, interaction): await interaction.response.send_modal(VirementModal()) async def _vfbm(self, interaction): await interaction.response.send_modal(VirementFBMModal()) async def _vurs(self, interaction): await interaction.response.send_modal(VirementURSAFFModal()) # ─── OPERATIONS BANQUIER (banquiers uniquement) ────────────────── 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("## 🏦 Opérations Banquier"), discord.ui.TextDisplay( "Ouvrez des comptes, ajoutez/retirez de l'argent et consultez les transactions." ), discord.ui.Separator(), discord.ui.ActionRow(TargetUserSelect(custom_id="bp_usel")), discord.ui.Separator(), discord.ui.ActionRow( _btn("🏦 Ouvrir Compte", discord.ButtonStyle.success, "bp_ouvrir", self._ouvrir), _btn("🏢 Ouvrir Compte ENT", discord.ButtonStyle.success, "bp_ouvrir_ent", self._ouvrir_ent), ), discord.ui.ActionRow( _btn("➕ Ajouter Argent", discord.ButtonStyle.primary, "bp_ajouter", self._ajouter), _btn("➖ Retirer Argent", discord.ButtonStyle.danger, "bp_retirer", self._retirer), ), discord.ui.ActionRow( _btn("🏦 Vir. Banquier", discord.ButtonStyle.primary, "bp_vir_bq", self._vir_bq), _btn("🏢 Vir. Banquier ENT", discord.ButtonStyle.primary, "bp_vir_bq_ent", self._vir_bq_ent), ), discord.ui.ActionRow( _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.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( OuvrirCompteSimpleModal(target_user=self._target(interaction)) ) async def _ouvrir_ent(self, interaction): await interaction.response.send_modal(OuvrirCompteEntModal()) async def _ajouter(self, interaction): await interaction.response.send_modal(AjouterArgentModal()) async def _retirer(self, interaction): await interaction.response.send_modal(RetirerArgentModal()) async def _vir_bq(self, interaction): await interaction.response.send_modal(VirementBanquierModal()) async def _vir_bq_ent(self, interaction): await interaction.response.send_modal(VirementBanquierEntModal()) async def _tx(self, interaction): await interaction.response.send_modal( TransactionsModal(target_user=self._target(interaction)) ) # ─── GOUVERNEMENT (gouvernement uniquement) ───────────────────── 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( "Comptes étatiques, FBM, URSAFF, Banque Nationale." ), discord.ui.Separator(), discord.ui.ActionRow( _btn("🏛️ Compte Gouv", discord.ButtonStyle.primary, "gp_cg", self._cg), _btn("💸 Virement Gouv", discord.ButtonStyle.primary, "gp_vg", self._vg), _btn("🏦 Retrait Gouv", discord.ButtonStyle.danger, "gp_rg", self._rg), ), discord.ui.ActionRow( _btn("📄 Compte FBM", discord.ButtonStyle.primary, "gp_cfbm", self._cfbm), _btn("📤 Virement FBM", discord.ButtonStyle.primary, "gp_vfbm", self._vfbm), ), discord.ui.ActionRow( _btn("📄 Compte URSAFF", discord.ButtonStyle.primary, "gp_curs", self._curs), _btn("📤 Virement URSAFF", discord.ButtonStyle.primary, "gp_vurs", self._vurs), ), discord.ui.ActionRow( _btn("🏦 Banque Nationale", discord.ButtonStyle.primary, "gp_bn", self._bn), ), 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 _cg(self, interaction): if not has_role(interaction.user, "gouvernement"): await interaction.response.send_message( "❌ Réservé au gouvernement.", ephemeral=True ) return entreprises = _load_json("comptes_entreprises.json") gouv = entreprises.get("Gouvernement", {}).get("GOUV") if not gouv: await interaction.response.send_message( "❌ Compte Gouvernement introuvable.", ephemeral=True ) return solde = gouv.get("solde", 0) txs = gouv.get("transactions", []) embed = discord.Embed( title="🏛️ Compte Gouvernement", color=discord.Color.dark_gold(), ) embed.add_field(name="💰 Solde", value=f"{solde:.2f}€", inline=False) for tx in txs[-5:]: embed.add_field( name=f"📄 {tx.get('date', 'N/A')}", value=( f"**{tx.get('type', 'N/A')}** — " f"{tx.get('montant', 0)}€ — {tx.get('source', 'N/A')}" ), inline=False, ) if txs: embed.set_footer(text=f"{len(txs)} transaction(s) au total") await interaction.response.send_message(embed=embed, ephemeral=True) async def _vg(self, interaction): await interaction.response.send_modal(GouvVirerModal()) async def _rg(self, interaction): await interaction.response.send_modal(RetraitGouvModal()) async def _cfbm(self, interaction): if not has_role(interaction.user, "gouvernement"): await interaction.response.send_message( "❌ Réservé au gouvernement.", ephemeral=True ) return entreprises = _load_json("comptes_entreprises.json") fbm = entreprises.get("fbm", {}).get("FBM", {}) solde = fbm.get("solde", 0) txs = fbm.get("transactions", []) embed = discord.Embed( title="📄 Compte FBM", color=discord.Color.orange() ) embed.add_field(name="💰 Solde", value=f"{solde:.2f}€", inline=False) for tx in txs[-5:]: embed.add_field( name=f"📄 {tx.get('date', 'N/A')}", value=f"**{tx.get('type', 'N/A')}** — {tx.get('montant', 0)}€", inline=False, ) await interaction.response.send_message(embed=embed, ephemeral=True) async def _vfbm(self, interaction): await interaction.response.send_modal(VirementFBMModal()) async def _curs(self, interaction): if not has_role(interaction.user, "gouvernement"): await interaction.response.send_message( "❌ Réservé au gouvernement.", ephemeral=True ) return entreprises = _load_json("comptes_entreprises.json") urs = entreprises.get("ursaff", {}).get("URSAFF", {}) solde = urs.get("solde", 0) txs = urs.get("transactions", []) embed = discord.Embed( title="📄 Compte URSAFF", color=discord.Color.orange() ) embed.add_field(name="💰 Solde", value=f"{solde:.2f}€", inline=False) for tx in txs[-5:]: embed.add_field( name=f"📄 {tx.get('date', 'N/A')}", value=f"**{tx.get('type', 'N/A')}** — {tx.get('montant', 0)}€", inline=False, ) await interaction.response.send_message(embed=embed, ephemeral=True) async def _vurs(self, interaction): await interaction.response.send_modal(VirementURSAFFModal()) async def _bn(self, interaction): if not has_role(interaction.user, "gouvernement"): await interaction.response.send_message( "❌ Réservé au gouvernement.", ephemeral=True ) return entreprises = _load_json("comptes_entreprises.json") bn = entreprises.get("banque_nationale", {}).get("BN", {}) solde = bn.get("solde", 0) txs = bn.get("transactions", []) embed = discord.Embed( title="🏦 Banque Nationale", color=discord.Color.blue() ) embed.add_field(name="💰 Solde", value=f"{solde:.2f}€", inline=False) for tx in txs[-5:]: embed.add_field( name=f"📄 {tx.get('date', 'N/A')}", value=f"**{tx.get('type', 'N/A')}** — {tx.get('montant', 0)}€", inline=False, ) await interaction.response.send_message(embed=embed, ephemeral=True) # ─── IDENTITE (tout le monde, boutons conditionnels) ──────────── 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): 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()) # ─── TICKETS & MESSAGES (admin uniquement) ────────────────────── 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()) # ─── SESSIONS RP (tout le monde) ──────────────────────────────── 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()) # ─── CONFIGURATION (admin uniquement) ─────────────────────────── 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 ) # ─── DIVERS (tout le monde) ───────────────────────────────────── 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 import io stats = get_stats_detailed() if not stats: await interaction.response.send_message( "📊 Aucune statistique disponible.", 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, abonnements", emoji="💰", value="compte", ), ] if has_role(author, "banquier"): options.append( discord.SelectOption( label="Opérations Banquier", description="Ouvrir comptes, ajouter/retirer, transactions", emoji="🏦", value="banquier", ) ) if has_role(author, "gouvernement"): options.append( discord.SelectOption( label="Gouvernement", description="Comptes étatiques, FBM, URSAFF", emoji="🏛️", value="gouvernement", ) ) options.append( discord.SelectOption( label="Identité", description="Cartes d'identité, permis, visite médicale", 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: await interaction.response.edit_message( view=cls(interaction.user, self._bot) ) 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" self.add_item(discord.ui.Container( discord.ui.TextDisplay("## 🏦 Victoria's Vice — 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))