Vicy_Publique/commandes/panel.py
utilisateurqu c9ad155bf6 V2.0: Refonte système bancaire - DB SQLite, RIB FR, TVA auto, multi-banques
- Remplace JSON par SQLite (aiosqlite) pour atomicité et intégrité
- Nouveau format RIB français avec checksum modulo 97
- Chaque banque a son propre rôle Discord (isolation par serveur)
- TVA automatique Particulier -> Entreprise, redirection au Fisc
- Institutions gouvernementales avec droit d'audit optionnel
- Commandes: /gouv, /banque, /audit, /mes-ribs
- Supprime: 16 anciens fichiers (entreprises, FBM, URSAFF, etc.)
- Reset complet des données, repart de zéro
2026-08-18 17:42:03 +02:00

2389 lines
92 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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> "
"<https://play.fn.gg/island/0986-1836-1625>\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))