Vicy_Publique/commandes/panel.py

1355 lines
61 KiB
Python
Raw Normal View History

import os
import json
import random
import discord
from discord import app_commands
from discord.ext import commands
from utils.guild_config import (
has_role,
get_role_id,
load_guild_config,
save_guild_config,
set_config_value,
ROLE_LABELS,
CHANNEL_LABELS,
CATEGORY_LABELS,
)
from utils.bank_manager import BankManager
from utils.rib import decoder_rib
bank_manager = BankManager()
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
C_MAIN = 0x2C2F33
C_BANK = 0x1ABC9C
C_BANQ_OP = 0x2ECC71
C_GOV = 0xD4AF37
C_ID = 0x3498DB
C_TICKET = 0xE74C3C
C_SESSION = 0x9B59B6
C_CONFIG = 0x95A5A6
C_FUN = 0xE67E22
def _load_json(name):
path = os.path.join(base_dir, "data", name)
if os.path.exists(path):
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
return {}
def _btn(label, style, cid, cb):
b = discord.ui.Button(label=label, style=style, custom_id=cid)
b.callback = cb
return b
def _back_btn(cid, cb):
return _btn("◀ Retour", discord.ButtonStyle.secondary, cid, cb)
# ═══════════════════════════════════════════════════════════════════
# USER SELECT (pour banquiers)
# ═══════════════════════════════════════════════════════════════════
class TargetUserSelect(discord.ui.UserSelect):
def __init__(self, custom_id="bp_usel"):
super().__init__(placeholder="Sélectionner un membre...", custom_id=custom_id)
self.selected_user = None
async def callback(self, interaction: discord.Interaction):
self.selected_user = self.values[0]
await interaction.response.defer()
# ═══════════════════════════════════════════════════════════════════
# MODALS — COMPTES (Mes Comptes)
# ═══════════════════════════════════════════════════════════════════
class VirementModal(discord.ui.Modal, title="💸 Virement"):
rib_dest = discord.ui.TextInput(
label="RIB du destinataire", required=True, max_length=30
)
montant = discord.ui.TextInput(
label="Montant (€)", required=True, max_length=15
)
def __init__(self, source_rib=None):
super().__init__()
self.source_rib = source_rib
async def on_submit(self, interaction: discord.Interaction):
try:
montant = float(self.montant.value.replace(",", ".").strip())
except ValueError:
await interaction.response.send_message("❌ Montant invalide.", ephemeral=True)
return
rib_dest = self.rib_dest.value.strip().replace(" ", "")
try:
result = await bank_manager.virement(
guild_id=interaction.guild.id,
rib_source=self.source_rib,
rib_dest=rib_dest,
montant=montant,
emetteur_id=interaction.user.id,
)
msg = f"✅ Virement de **{montant:.2f}€** envoyé."
if result["tva_prelevee"] > 0:
msg += f"\n💡 TVA prélevée : **{result['tva_prelevee']:.2f}€**"
await interaction.response.send_message(msg, ephemeral=True)
except ValueError as e:
await interaction.response.send_message(f"❌ {e}", ephemeral=True)
class DepotRetraitModal(discord.ui.Modal):
montant = discord.ui.TextInput(
label="Montant (€)", required=True, max_length=15
)
def __init__(self, rib: str, operation: str):
super().__init__(title=f"{'➕ Dépôt' if operation == 'depot' else '➖ Retrait'}")
self.rib = rib
self.operation = operation
async def on_submit(self, interaction: discord.Interaction):
try:
montant = float(self.montant.value.replace(",", ".").strip())
except ValueError:
await interaction.response.send_message("❌ Montant invalide.", ephemeral=True)
return
try:
if self.operation == "depot":
result = await bank_manager.depot_banquier(
interaction.guild.id, self.rib, montant, interaction.user.id
)
else:
result = await bank_manager.retrait_banquier(
interaction.guild.id, self.rib, montant, interaction.user.id
)
await interaction.response.send_message(
f"✅ {self.operation.title()} de **{montant:.2f}€**. Nouveau solde : **{result['nouveau_solde']:.2f}€**",
ephemeral=True,
)
except ValueError as e:
await interaction.response.send_message(f"❌ {e}", ephemeral=True)
# ═══════════════════════════════════════════════════════════════════
# MODALS — BANQUIER
# ═══════════════════════════════════════════════════════════════════
class OuvrirCompteModal(discord.ui.Modal, title="🏦 Ouvrir un Compte"):
membre_id = discord.ui.TextInput(
label="ID du membre", required=True, max_length=20
)
type_compte = discord.ui.TextInput(
label="Type (particulier / entreprise)", required=True, max_length=15
)
taux_tva = discord.ui.TextInput(
label="TVA % (entreprise, 0 sinon)", required=False, max_length=6
)
async def on_submit(self, interaction: discord.Interaction):
try:
membre_id = int(self.membre_id.value.strip())
except ValueError:
await interaction.response.send_message("❌ ID invalide.", ephemeral=True)
return
member = interaction.guild.get_member(membre_id)
if not member:
await interaction.response.send_message("❌ Membre introuvable.", ephemeral=True)
return
type_val = self.type_compte.value.strip().lower()
if type_val not in ("particulier", "entreprise"):
await interaction.response.send_message("❌ Type invalide.", ephemeral=True)
return
tva = 0.0
if type_val == "entreprise" and self.taux_tva.value:
try:
tva = float(self.taux_tva.value.replace(",", ".").strip())
except ValueError:
pass
banques = await bank_manager.lister_banques(interaction.guild.id)
bank = None
for b in banques:
role = interaction.guild.get_role(b["role_id"])
if role and role in interaction.user.roles:
bank = b
break
if not bank:
await interaction.response.send_message("❌ Aucune banque associée à vos rôles.", ephemeral=True)
return
try:
result = await bank_manager.ouvrir_compte(
interaction.guild.id, bank["id"], member.id, type_val, tva
)
embed = discord.Embed(
title="💳 Compte ouvert",
description=(
f"**Banque :** {bank['name']}\n"
f"**Membre :** {member.mention}\n"
f"**Type :** {type_val.title()}\n"
f"**RIB :** `{result['rib']}`"
),
color=discord.Color.green(),
)
await interaction.response.send_message(embed=embed, ephemeral=True)
try:
await member.send(
f"💳 Votre compte **{type_val}** chez **{bank['name']}** a été ouvert.\n"
f"**RIB :** `{result['rib']}`"
)
except discord.Forbidden:
pass
except ValueError as e:
await interaction.response.send_message(f"❌ {e}", ephemeral=True)
class TransactionsModal(discord.ui.Modal, title="📜 Transactions"):
rib = discord.ui.TextInput(
label="RIB du compte", required=True, max_length=30
)
async def on_submit(self, interaction: discord.Interaction):
rib = self.rib.value.strip().replace(" ", "")
txs = await bank_manager.get_transactions(interaction.guild.id, rib, limit=10)
if not txs:
await interaction.response.send_message("Aucune transaction.", ephemeral=True)
return
lines = []
for tx in txs:
lines.append(
f"`{tx['date']}` — **{tx['type']}** — {tx['montant']:.2f}€"
)
embed = discord.Embed(
title=f"📜 Transactions ({rib})",
description="\n".join(lines),
color=discord.Color.blue(),
)
await interaction.response.send_message(embed=embed, ephemeral=True)
# ═══════════════════════════════════════════════════════════════════
# MODALS — GOUVERNEMENT
# ═══════════════════════════════════════════════════════════════════
class GouvVirerModal(discord.ui.Modal, title="💸 Virement Gouvernement"):
rib_dest = discord.ui.TextInput(
label="RIB du destinataire", required=True, max_length=30
)
montant = discord.ui.TextInput(
label="Montant (€)", required=True, max_length=15
)
async def on_submit(self, interaction: discord.Interaction):
if not has_role(interaction.user, "gouvernement"):
await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True)
return
try:
montant = float(self.montant.value.replace(",", ".").strip())
except ValueError:
await interaction.response.send_message("❌ Montant invalide.", ephemeral=True)
return
rib_dest = self.rib_dest.value.strip().replace(" ", "")
try:
fisc = await bank_manager.get_fisc_config(interaction.guild.id)
if not fisc:
await interaction.response.send_message("❌ Aucun fisc configuré.", ephemeral=True)
return
source_compte = await bank_manager.get_compte_by_rib(interaction.guild.id, fisc.get("rib", ""))
if not source_compte:
await interaction.response.send_message("❌ Compte fisc introuvable.", ephemeral=True)
return
result = await bank_manager.virement(
guild_id=interaction.guild.id,
rib_source=source_compte["rib"],
rib_dest=rib_dest,
montant=montant,
emetteur_id=interaction.user.id,
)
await interaction.response.send_message(
f"✅ Virement de **{montant:.2f}€** effectué depuis le Fisc.",
ephemeral=True,
)
except ValueError as e:
await interaction.response.send_message(f"❌ {e}", ephemeral=True)
class ConfigFiscModal(discord.ui.Modal, title="🏛️ Configurer le Fisc"):
institution_id = discord.ui.TextInput(
label="ID de l'institution fisc", required=True, max_length=10
)
async def on_submit(self, interaction: discord.Interaction):
if not has_role(interaction.user, "gouvernement"):
await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True)
return
try:
iid = int(self.institution_id.value.strip())
except ValueError:
await interaction.response.send_message("❌ ID invalide.", ephemeral=True)
return
ok = await bank_manager.configurer_fisc(interaction.guild.id, iid)
if ok:
await interaction.response.send_message("✅ Fisc configuré.", ephemeral=True)
else:
await interaction.response.send_message("❌ Institution introuvable.", ephemeral=True)
class CreerBanqueModal(discord.ui.Modal, title="🏦 Créer une Banque"):
nom = discord.ui.TextInput(
label="Nom de la banque", required=True, max_length=50
)
role_id = discord.ui.TextInput(
label="ID du rôle banquier", required=True, max_length=20
)
async def on_submit(self, interaction: discord.Interaction):
if not has_role(interaction.user, "gouvernement"):
await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True)
return
try:
rid = int(self.role_id.value.strip())
except ValueError:
await interaction.response.send_message("❌ ID invalide.", ephemeral=True)
return
role = interaction.guild.get_role(rid)
if not role:
await interaction.response.send_message("❌ Rôle introuvable.", ephemeral=True)
return
try:
result = await bank_manager.creer_banque(interaction.guild.id, self.nom.value.strip(), rid)
embed = discord.Embed(
title="🏦 Banque créée",
description=(
f"**Nom :** {result['name']}\n"
f"**Rôle :** {role.mention}\n"
f"**Code établissement :** `{result['establishment_code']}`"
),
color=discord.Color.green(),
)
await interaction.response.send_message(embed=embed, ephemeral=True)
except ValueError as e:
await interaction.response.send_message(f"❌ {e}", ephemeral=True)
class CreerInstitutionModal(discord.ui.Modal, title="🏛️ Créer une Institution"):
nom = discord.ui.TextInput(
label="Nom de l'institution", required=True, max_length=50
)
role_id = discord.ui.TextInput(
label="ID du rôle (optionnel)", required=False, max_length=20
)
audit_all = discord.ui.TextInput(
label="Audit global ? (oui/non)", required=False, max_length=3
)
async def on_submit(self, interaction: discord.Interaction):
if not has_role(interaction.user, "gouvernement"):
await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True)
return
rid = None
if self.role_id.value:
try:
rid = int(self.role_id.value.strip())
except ValueError:
pass
can_audit = self.audit_all.value.strip().lower() in ("oui", "yes", "true", "1")
try:
result = await bank_manager.creer_institution(
interaction.guild.id, self.nom.value.strip(), rid, can_audit
)
await interaction.response.send_message(
f"✅ Institution **{result['name']}** créée (ID: `{result['id']}`).",
ephemeral=True,
)
except Exception as e:
await interaction.response.send_message(f"❌ {e}", ephemeral=True)
# ═══════════════════════════════════════════════════════════════════
# MODALS — IDENTITE
# ═══════════════════════════════════════════════════════════════════
class CreerCarteModal(discord.ui.Modal, title="🪪 Créer une Carte"):
prenom = discord.ui.TextInput(label="Prénom", required=True, max_length=30)
nom_famille = discord.ui.TextInput(label="Nom de famille", required=True, max_length=30)
date_naissance = discord.ui.TextInput(label="Date de naissance", required=True, max_length=20)
sexe = discord.ui.TextInput(label="Sexe (M/F)", required=True, max_length=1)
nationalite = discord.ui.TextInput(label="Nationalité", required=True, max_length=30)
async def on_submit(self, interaction: discord.Interaction):
if not has_role(interaction.user, "gouvernement"):
await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True)
return
from utils.data_manager import DataManager
dm = DataManager()
dm.save_identity(
str(interaction.user.id),
{
"prenom": self.prenom.value.strip(),
"nom": self.nom_famille.value.strip(),
"date_naissance": self.date_naissance.value.strip(),
"sexe": self.sexe.value.strip().upper(),
"nationalite": self.nationalite.value.strip(),
},
)
await interaction.response.send_message("✅ Carte d'identité créée.", ephemeral=True)
class PermisModal(discord.ui.Modal, title="🚗 Permis de Conduire"):
membre_id = discord.ui.TextInput(label="ID du membre", required=True, max_length=20)
categorie = discord.ui.TextInput(label="Catégorie (A/B/C)", required=True, max_length=2)
async def on_submit(self, interaction: discord.Interaction):
await interaction.response.send_message("✅ Permis enregistré (module à venir).", ephemeral=True)
class VisiteMedModal(discord.ui.Modal, title="🏥 Visite Médicale"):
membre_id = discord.ui.TextInput(label="ID du membre", required=True, max_length=20)
resultat = discord.ui.TextInput(label="Résultat (Apte/Inapte)", required=True, max_length=10)
async def on_submit(self, interaction: discord.Interaction):
await interaction.response.send_message("✅ Visite médicale enregistrée (module à venir).", ephemeral=True)
# ═══════════════════════════════════════════════════════════════════
# MODALS — SESSIONS
# ═══════════════════════════════════════════════════════════════════
class AnnonceSessionModal(discord.ui.Modal, title="📢 Annoncer une Session"):
date = discord.ui.TextInput(
label="Date (ex: Vendredi 20 Juin à 20h45)", required=True, max_length=100,
)
reminder1 = discord.ui.TextInput(
label="1er rappel (ex: Jeudi 19 Juin à 19h)", required=True, max_length=100,
)
reminder2 = discord.ui.TextInput(
label="2e rappel (ex: Vendredi 20 Juin à 15h)", required=True, max_length=100,
)
async def on_submit(self, interaction: discord.Interaction):
import dateparser
from datetime import timezone
session_date = dateparser.parse(
self.date.value, languages=["fr"],
settings={"TIMEZONE": "Europe/Paris", "RETURN_AS_TIMEZONE_AWARE": True},
)
r1 = dateparser.parse(
self.reminder1.value, languages=["fr"],
settings={"TIMEZONE": "Europe/Paris", "RETURN_AS_TIMEZONE_AWARE": True},
)
r2 = dateparser.parse(
self.reminder2.value, languages=["fr"],
settings={"TIMEZONE": "Europe/Paris", "RETURN_AS_TIMEZONE_AWARE": True},
)
if not session_date or not r1 or not r2:
await interaction.response.send_message("❌ Une des dates n'a pas été comprise.", ephemeral=True)
return
ping_role_id = get_role_id(interaction.guild.id, "citoyen")
message = (
"# <:VV:1339237574593609780> - **SESSION VICTORIA'S VICE**\n\n"
"```📅 - HORAIRE DE LA SESSION```\n"
f"> - Nous vous donnons rendez-vous le **{self.date.value}** "
"pour une **session officielle** sur Victoria's Vice. 🗓️\n"
"---\n"
"```🤔 - COMMENT REJOINDRE LA SESSION ?```\n"
"> - Pour lancer la Map, entrez le code sur la barre de recherche de Fortnite.\n"
"> - **`CODE DE LA MAP`** <:arrow:1297491509201080362> [0986-1836-1625]\n"
"> - **`LIEN DIRECT`** <:arrow:1297491509201080362> "
"<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 pour ce message.", ephemeral=True)
return
data = presence_data[mid]
def mentions(ids):
return ", ".join(f"<@{uid}>" for uid in ids) if ids else "Aucun"
await interaction.response.send_message(
f"**Présents ({len(data['present'])})** : {mentions(data['present'])}\n"
f"**Absents ({len(data['absent'])})** : {mentions(data['absent'])}\n"
f"**En retard ({len(data['retard'])})** : {mentions(data['retard'])}",
ephemeral=True,
)
# ═══════════════════════════════════════════════════════════════════
# MODALS — CONFIG
# ═══════════════════════════════════════════════════════════════════
class ConfigRoleModal(discord.ui.Modal, title="🎭 Définir un Rôle"):
cle = discord.ui.TextInput(label="Clé (banquier, citoyen, ems...)", required=True, max_length=30)
role_id = discord.ui.TextInput(label="ID du rôle Discord", required=True, max_length=20)
async def on_submit(self, interaction: discord.Interaction):
if not interaction.user.guild_permissions.manage_guild:
await interaction.response.send_message("❌ Permission requise.", ephemeral=True)
return
cle = self.cle.value.strip().lower()
try:
rid = int(self.role_id.value.strip())
except ValueError:
await interaction.response.send_message("❌ ID invalide.", ephemeral=True)
return
role = interaction.guild.get_role(rid)
if not role:
await interaction.response.send_message("❌ Rôle introuvable.", ephemeral=True)
return
set_config_value(interaction.guild.id, "roles", cle, rid)
await interaction.response.send_message(
f"✅ Rôle **{ROLE_LABELS.get(cle, cle)}** → {role.mention}", ephemeral=True,
)
class ConfigChannelModal(discord.ui.Modal, title="📢 Définir un Salon"):
cle = discord.ui.TextInput(label="Clé (welcome, candidature...)", required=True, max_length=30)
channel_id = discord.ui.TextInput(label="ID du salon Discord", required=True, max_length=20)
async def on_submit(self, interaction: discord.Interaction):
if not interaction.user.guild_permissions.manage_guild:
await interaction.response.send_message("❌ Permission requise.", ephemeral=True)
return
cle = self.cle.value.strip().lower()
try:
cid = int(self.channel_id.value.strip())
except ValueError:
await interaction.response.send_message("❌ ID invalide.", ephemeral=True)
return
ch = interaction.guild.get_channel(cid)
if not ch:
await interaction.response.send_message("❌ Salon introuvable.", ephemeral=True)
return
set_config_value(interaction.guild.id, "channels", cle, cid)
await interaction.response.send_message(
f"✅ Salon **{CHANNEL_LABELS.get(cle, cle)}** → {ch.mention}", ephemeral=True,
)
class ConfigCategoryModal(discord.ui.Modal, title="📁 Définir une Catégorie"):
cle = discord.ui.TextInput(label="Clé (ticket)", required=True, max_length=30)
category_id = discord.ui.TextInput(label="ID de la catégorie Discord", required=True, max_length=20)
async def on_submit(self, interaction: discord.Interaction):
if not interaction.user.guild_permissions.manage_guild:
await interaction.response.send_message("❌ Permission requise.", ephemeral=True)
return
cle = self.cle.value.strip().lower()
try:
cid = int(self.category_id.value.strip())
except ValueError:
await interaction.response.send_message("❌ ID invalide.", ephemeral=True)
return
cat = interaction.guild.get_channel(cid)
if not cat:
await interaction.response.send_message("❌ Catégorie introuvable.", ephemeral=True)
return
set_config_value(interaction.guild.id, "categories", cle, cid)
await interaction.response.send_message(
f"✅ Catégorie **{CATEGORY_LABELS.get(cle, cle)}** → **{cat.name}**", ephemeral=True,
)
class RollModal(discord.ui.Modal, title="🎲 Lancer un Dé"):
minimum = discord.ui.TextInput(label="Valeur minimale", required=True, max_length=5)
maximum = discord.ui.TextInput(label="Valeur maximale", required=True, max_length=5)
raison = discord.ui.TextInput(label="Raison", required=True, max_length=100)
async def on_submit(self, interaction: discord.Interaction):
try:
min_val = int(self.minimum.value.strip())
max_val = int(self.maximum.value.strip())
except ValueError:
await interaction.response.send_message("❌ Valeurs invalides.", ephemeral=True)
return
if min_val > max_val:
await interaction.response.send_message("❌ Min > Max.", ephemeral=True)
return
result = random.randint(min_val, max_val)
embed = discord.Embed(
title="🎲 Résultat du dé",
description=f"**{result}** (entre {min_val} et {max_val})\n*Raison : {self.raison.value.strip()}*",
color=C_FUN,
)
await interaction.response.send_message(embed=embed)
class MessageModal(discord.ui.Modal, title="✉️ Envoyer un MP"):
user_id = discord.ui.TextInput(label="ID de l'utilisateur", required=True, max_length=20)
message = discord.ui.TextInput(label="Message", required=True, style=discord.TextStyle.paragraph, max_length=2000)
async def on_submit(self, interaction: discord.Interaction):
try:
uid = int(self.user_id.value.strip())
except ValueError:
await interaction.response.send_message("❌ ID invalide.", ephemeral=True)
return
user = interaction.bot.get_user(uid)
if not user:
await interaction.response.send_message("❌ Utilisateur introuvable.", ephemeral=True)
return
try:
await user.send(self.message.value)
await interaction.response.send_message(f"✅ MP envoyé à {user.mention}.", ephemeral=True)
except discord.Forbidden:
await interaction.response.send_message("❌ Impossible d'envoyer un MP.", ephemeral=True)
class MessageTousModal(discord.ui.Modal, title="📢 MP à Tous"):
message = discord.ui.TextInput(label="Message", required=True, style=discord.TextStyle.paragraph, max_length=2000)
async def on_submit(self, interaction: discord.Interaction):
if not interaction.user.guild_permissions.administrator:
await interaction.response.send_message("❌ Admin uniquement.", ephemeral=True)
return
await interaction.response.send_message("📨 Envoi en cours...", ephemeral=True)
count = 0
for member in interaction.guild.members:
if not member.bot:
try:
await member.send(self.message.value)
count += 1
except discord.Forbidden:
pass
await interaction.followup.send(f"✅ MP envoyé à **{count}** membres.", ephemeral=True)
# ═══════════════════════════════════════════════════════════════════
# VIEWS — MES COMPTES (tout le monde)
# ═══════════════════════════════════════════════════════════════════
class CompteDetailView(discord.ui.LayoutView):
def __init__(self, author, bot, compte_data, bank_data):
super().__init__(timeout=300)
self.author = author
self.bot_ref = bot
self.compte = compte_data
self.bank = bank_data
rib = compte_data["rib"]
solde = compte_data["solde"]
acc_type = compte_data["account_type"]
tva = compte_data.get("tva_rate", 0)
bank_name = bank_data["name"]
solde_color = discord.Color.green() if solde >= 0 else discord.Color.red()
items = [
discord.ui.TextDisplay(f"## 💳 {bank_name} — {acc_type.title()}"),
discord.ui.Separator(),
discord.ui.TextDisplay(f"**RIB :** `{rib}`"),
discord.ui.TextDisplay(f"**Solde :** `{solde:,.2f}€`".replace(",", " ")),
]
if acc_type == "entreprise" and tva > 0:
items.append(discord.ui.TextDisplay(f"**TVA :** `{tva}%`"))
items.extend([
discord.ui.Separator(),
discord.ui.ActionRow(
_btn("💳 Carte", discord.ButtonStyle.primary, f"cd_card_{rib[-8:]}", self._card),
_btn("📤 Virement", discord.ButtonStyle.primary, f"cd_vir_{rib[-8:]}", self._vir),
_btn("➕ Dépôt", discord.ButtonStyle.success, f"cd_dep_{rib[-8:]}", self._depot),
_btn("➖ Retrait", discord.ButtonStyle.danger, f"cd_ret_{rib[-8:]}", self._retrait),
),
discord.ui.Separator(),
discord.ui.ActionRow(_back_btn("back_cd", self._back)),
])
self.add_item(discord.ui.Container(*items, accent_colour=solde_color))
async def _card(self, interaction: discord.Interaction):
from utils.bank_card import BankCardGenerator
gen = BankCardGenerator()
buf = gen.generate(
bank_name=self.bank["name"],
holder_name=interaction.user.display_name,
rib=self.compte["rib"],
solde=self.compte["solde"],
account_type=self.compte["account_type"],
tva_rate=self.compte.get("tva_rate", 0),
)
file = discord.File(buf, filename="carte_bancaire.png")
embed = discord.Embed(color=C_BANK)
embed.set_image(url="attachment://carte_bancaire.png")
await interaction.response.send_message(file=file, embed=embed, ephemeral=True)
async def _vir(self, interaction: discord.Interaction):
await interaction.response.send_modal(
VirementModal(source_rib=self.compte["rib"])
)
async def _depot(self, interaction: discord.Interaction):
await interaction.response.send_modal(
DepotRetraitModal(rib=self.compte["rib"], operation="depot")
)
async def _retrait(self, interaction: discord.Interaction):
await interaction.response.send_modal(
DepotRetraitModal(rib=self.compte["rib"], operation="retrait")
)
async def _back(self, interaction: discord.Interaction):
await interaction.response.edit_message(
view=ComptePanelView(interaction.user, self.bot_ref)
)
class ComptePanelView(discord.ui.LayoutView):
def __init__(self, author, bot):
super().__init__(timeout=300)
self.author = author
self.bot_ref = bot
items = [
discord.ui.TextDisplay("## 💰 Mes Comptes"),
discord.ui.TextDisplay("Cliquez sur un compte pour voir les détails et opérer."),
discord.ui.Separator(),
]
self._account_buttons = []
async def _load_accounts(self, interaction: discord.Interaction):
comptes = await bank_manager.get_comptes_user(interaction.guild.id, interaction.user.id)
if not comptes:
items = [
discord.ui.TextDisplay("## 💰 Mes Comptes"),
discord.ui.TextDisplay("Aucun compte bancaire. contactez un banquier pour en ouvrir un."),
discord.ui.Separator(),
discord.ui.ActionRow(_back_btn("back_cp", self._back)),
]
self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_BANK)))
return
items = [
discord.ui.TextDisplay("## 💰 Mes Comptes"),
discord.ui.TextDisplay(f"**{len(comptes)}** compte(s) trouvé(s). Cliquez pour details."),
discord.ui.Separator(),
]
for i, c in enumerate(comptes):
solde_str = f"{c['solde']:,.2f}€".replace(",", " ")
label = f"💳 {c['bank_name']} — {c['account_type'].title()} — {solde_str}"
btn = _btn(
label[:80],
discord.ButtonStyle.primary,
f"cp_acc_{i}",
self._make_acc_cb(c),
)
items.append(discord.ui.ActionRow(btn))
items.extend([
discord.ui.Separator(),
discord.ui.ActionRow(_back_btn("back_cp", self._back)),
])
self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_BANK)))
def _make_acc_cb(self, compte_data):
async def cb(interaction):
bank = await bank_manager.get_banque(interaction.guild.id, compte_data["bank_id"])
await interaction.response.edit_message(
view=CompteDetailView(interaction.user, self.bot_ref, compte_data, bank)
)
return cb
async def _back(self, interaction: discord.Interaction):
await interaction.response.edit_message(
view=MainPanelView(interaction.user, self.bot_ref)
)
# ═══════════════════════════════════════════════════════════════════
# VIEWS — OPERATIONS BANQUIER
# ═══════════════════════════════════════════════════════════════════
class BanquierPanelView(discord.ui.LayoutView):
def __init__(self, author, bot):
super().__init__(timeout=300)
self.author = author
self.bot_ref = bot
self.selected_user = None
self.user_select = TargetUserSelect(custom_id="bp_usel")
items = [
discord.ui.TextDisplay("## 🏦 Opérations Banquier"),
discord.ui.TextDisplay("Sélectionnez un membre puis choisissez une opération."),
discord.ui.Separator(),
discord.ui.ActionRow(self.user_select),
discord.ui.Separator(),
discord.ui.ActionRow(
_btn("🏦 Ouvrir Compte", discord.ButtonStyle.success, "bp_ouvrir", self._ouvrir),
_btn("📜 Transactions", discord.ButtonStyle.secondary, "bp_tx", self._tx),
),
discord.ui.Separator(),
discord.ui.ActionRow(_back_btn("back_bp", self._back)),
]
self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_BANQ_OP)))
def _target(self, interaction):
return self.user_select.selected_user or interaction.user
async def _back(self, interaction):
await interaction.response.edit_message(
view=MainPanelView(interaction.user, self.bot_ref)
)
async def _ouvrir(self, interaction):
await interaction.response.send_modal(OuvrirCompteModal())
async def _tx(self, interaction):
await interaction.response.send_modal(TransactionsModal())
# ═══════════════════════════════════════════════════════════════════
# VIEWS — GOUVERNEMENT
# ═══════════════════════════════════════════════════════════════════
class GouvernementPanelView(discord.ui.LayoutView):
def __init__(self, author, bot):
super().__init__(timeout=300)
self.author = author
self.bot_ref = bot
items = [
discord.ui.TextDisplay("## 🏛️ Gouvernement"),
discord.ui.TextDisplay("Institutions, fisc, banques nationales."),
discord.ui.Separator(),
discord.ui.ActionRow(
_btn("🏦 Créer Banque", discord.ButtonStyle.success, "gp_cbanque", self._cbanque),
_btn("🏛️ Créer Institution", discord.ButtonStyle.success, "gp_cinst", self._cinst),
),
discord.ui.ActionRow(
_btn("⚙️ Config Fisc", discord.ButtonStyle.primary, "gp_fisc", self._fisc),
_btn("💸 Virement Fisc", discord.ButtonStyle.primary, "gp_vfisc", self._vfisc),
),
discord.ui.ActionRow(
_btn("📋 Lister Banques", discord.ButtonStyle.secondary, "gp_lbanques", self._lbanques),
_btn("📋 Lister Institutions", discord.ButtonStyle.secondary, "gp_linst", self._linst),
),
discord.ui.Separator(),
discord.ui.ActionRow(_back_btn("back_gp", self._back)),
]
self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_GOV)))
async def _back(self, interaction):
await interaction.response.edit_message(
view=MainPanelView(interaction.user, self.bot_ref)
)
async def _cbanque(self, interaction):
if not has_role(interaction.user, "gouvernement"):
await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True)
return
await interaction.response.send_modal(CreerBanqueModal())
async def _cinst(self, interaction):
if not has_role(interaction.user, "gouvernement"):
await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True)
return
await interaction.response.send_modal(CreerInstitutionModal())
async def _fisc(self, interaction):
if not has_role(interaction.user, "gouvernement"):
await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True)
return
await interaction.response.send_modal(ConfigFiscModal())
async def _vfisc(self, interaction):
if not has_role(interaction.user, "gouvernement"):
await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True)
return
await interaction.response.send_modal(GouvVirerModal())
async def _lbanques(self, interaction):
if not has_role(interaction.user, "gouvernement"):
await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True)
return
banques = await bank_manager.lister_banques(interaction.guild.id)
if not banques:
await interaction.response.send_message("Aucune banque.", ephemeral=True)
return
embed = discord.Embed(title="🏦 Banques", color=discord.Color.gold())
for b in banques:
role = interaction.guild.get_role(b["role_id"])
embed.add_field(
name=b["name"],
value=(
f"**Rôle :** {role.mention if role else '❌'}\n"
f"**Code :** `{b['establishment_code']}`\n"
f"**Réserve :** `{b['vault_balance']:.2f}€`\n"
f"**Encours :** `{b['clients_total_balance']:.2f}€`"
),
inline=False,
)
await interaction.response.send_message(embed=embed, ephemeral=True)
async def _linst(self, interaction):
if not has_role(interaction.user, "gouvernement"):
await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True)
return
insts = await bank_manager.lister_institutions(interaction.guild.id)
if not insts:
await interaction.response.send_message("Aucune institution.", ephemeral=True)
return
embed = discord.Embed(title="🏛️ Institutions", color=discord.Color.gold())
for i in insts:
role = interaction.guild.get_role(i["role_id"]) if i["role_id"] else None
embed.add_field(
name=i["name"],
value=(
f"**ID :** `{i['id']}`\n"
f"**Rôle :** {role.mention if role else 'Non défini'}\n"
f"**Audit global :** {'Oui' if i['can_audit_all'] else 'Non'}"
),
inline=False,
)
await interaction.response.send_message(embed=embed, ephemeral=True)
# ═══════════════════════════════════════════════════════════════════
# VIEWS — IDENTITE
# ═══════════════════════════════════════════════════════════════════
class IdentitePanelView(discord.ui.LayoutView):
def __init__(self, author, bot):
super().__init__(timeout=300)
self.author = author
self.bot_ref = bot
items = [
discord.ui.TextDisplay("## 🪪 Identité"),
discord.ui.TextDisplay("Cartes d'identité, permis, visites médicales."),
discord.ui.Separator(),
discord.ui.ActionRow(
_btn("🪪 Ma Carte", discord.ButtonStyle.primary, "ip_carte", self._carte),
),
]
if has_role(author, "gouvernement"):
items.append(
discord.ui.ActionRow(
_btn("🪪 Créer Carte", discord.ButtonStyle.success, "ip_ccar", self._ccar),
)
)
action_items = []
if has_role(author, "auto_ecole"):
action_items.append(_btn("🚗 Permis", discord.ButtonStyle.primary, "ip_permis", self._permis))
if has_role(author, "ems"):
action_items.append(_btn("🏥 Visite Médicale", discord.ButtonStyle.primary, "ip_vismed", self._vismed))
if action_items:
items.append(discord.ui.ActionRow(*action_items))
items.extend([
discord.ui.Separator(),
discord.ui.ActionRow(_back_btn("back_ip", self._back)),
])
self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_ID)))
async def _back(self, interaction):
await interaction.response.edit_message(
view=MainPanelView(interaction.user, self.bot_ref)
)
async def _carte(self, interaction):
from utils.data_manager import DataManager
uid = str(interaction.user.id)
dm = DataManager()
identity = dm.identities.get(uid)
if not identity:
await interaction.response.send_message("❌ Aucune carte trouvée.", ephemeral=True)
return
try:
from utils.card_generator import CardGenerator
cg = CardGenerator()
card_buffer = await cg.create_identity_card(interaction.user, identity)
file = discord.File(card_buffer, filename="carte_identite.png")
embed = discord.Embed(title="🪪 Carte d'identité", color=C_ID)
embed.set_image(url="attachment://carte_identite.png")
await interaction.response.send_message(file=file, embed=embed)
except Exception as e:
await interaction.response.send_message(f"❌ Erreur : {e}", ephemeral=True)
async def _ccar(self, interaction):
await interaction.response.send_modal(CreerCarteModal())
async def _permis(self, interaction):
await interaction.response.send_modal(PermisModal())
async def _vismed(self, interaction):
await interaction.response.send_modal(VisiteMedModal())
# ═══════════════════════════════════════════════════════════════════
# VIEWS — TICKETS & MESSAGES
# ═══════════════════════════════════════════════════════════════════
class TicketsPanelView(discord.ui.LayoutView):
def __init__(self, author, bot):
super().__init__(timeout=300)
self.author = author
self.bot_ref = bot
items = [
discord.ui.TextDisplay("## 📩 Tickets & Messages"),
discord.ui.TextDisplay("Gestion des tickets et messages privés."),
discord.ui.Separator(),
discord.ui.ActionRow(
_btn("📑 Embed Ticket", discord.ButtonStyle.success, "tp_ticket", self._ticket),
_btn("✉️ Envoyer MP", discord.ButtonStyle.primary, "tp_mp", self._mp),
_btn("📢 MP à Tous", discord.ButtonStyle.danger, "tp_mptous", self._mptous),
),
discord.ui.Separator(),
discord.ui.ActionRow(_back_btn("back_tp", self._back)),
]
self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_TICKET)))
async def _back(self, interaction):
await interaction.response.edit_message(
view=MainPanelView(interaction.user, self.bot_ref)
)
async def _ticket(self, interaction):
if not interaction.user.guild_permissions.manage_guild:
await interaction.response.send_message("❌ Permission requise.", ephemeral=True)
return
embed = discord.Embed(
title="📩 Ticket Whitelist",
description="Pour réaliser votre whitelist, ouvrez un ticket et remplissez le formulaire.",
color=0x00FF00,
)
from commandes.ticket import TicketView
await interaction.channel.send(embed=embed, view=TicketView())
await interaction.response.send_message("✅ Embed des tickets envoyé.", ephemeral=True)
async def _mp(self, interaction):
await interaction.response.send_modal(MessageModal())
async def _mptous(self, interaction):
await interaction.response.send_modal(MessageTousModal())
# ═══════════════════════════════════════════════════════════════════
# VIEWS — SESSIONS RP
# ═══════════════════════════════════════════════════════════════════
class SessionsPanelView(discord.ui.LayoutView):
def __init__(self, author, bot):
super().__init__(timeout=300)
self.author = author
self.bot_ref = bot
items = [
discord.ui.TextDisplay("## 📅 Sessions RP"),
discord.ui.TextDisplay("Annonces de sessions et gestion des présences."),
discord.ui.Separator(),
discord.ui.ActionRow(
_btn("📢 Annoncer Session", discord.ButtonStyle.success, "sp_annonce", self._annonce),
_btn("📋 Liste Présences", discord.ButtonStyle.primary, "sp_liste", self._liste),
),
discord.ui.Separator(),
discord.ui.ActionRow(_back_btn("back_sp", self._back)),
]
self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_SESSION)))
async def _back(self, interaction):
await interaction.response.edit_message(
view=MainPanelView(interaction.user, self.bot_ref)
)
async def _annonce(self, interaction):
await interaction.response.send_modal(AnnonceSessionModal())
async def _liste(self, interaction):
await interaction.response.send_modal(ListePresenceModal())
# ═══════════════════════════════════════════════════════════════════
# VIEWS — CONFIGURATION
# ═══════════════════════════════════════════════════════════════════
class ConfigPanelView(discord.ui.LayoutView):
def __init__(self, author, bot):
super().__init__(timeout=300)
self.author = author
self.bot_ref = bot
items = [
discord.ui.TextDisplay("## ⚙️ Configuration"),
discord.ui.TextDisplay("Configurer les rôles, salons et catégories de ce serveur."),
discord.ui.Separator(),
discord.ui.ActionRow(
_btn("🎭 Définir Rôle", discord.ButtonStyle.primary, "cfg_role", self._crole),
_btn("📢 Définir Salon", discord.ButtonStyle.primary, "cfg_chan", self._cchan),
_btn("📁 Définir Catégorie", discord.ButtonStyle.primary, "cfg_cat", self._ccat),
),
discord.ui.ActionRow(
_btn("👁️ Voir Config", discord.ButtonStyle.secondary, "cfg_voir", self._cvoir),
_btn("🗑️ Reset Config", discord.ButtonStyle.danger, "cfg_reset", self._creset),
),
discord.ui.Separator(),
discord.ui.ActionRow(_back_btn("back_cfg", self._back)),
]
self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_CONFIG)))
async def _back(self, interaction):
await interaction.response.edit_message(
view=MainPanelView(interaction.user, self.bot_ref)
)
async def _crole(self, interaction):
await interaction.response.send_modal(ConfigRoleModal())
async def _cchan(self, interaction):
await interaction.response.send_modal(ConfigChannelModal())
async def _ccat(self, interaction):
await interaction.response.send_modal(ConfigCategoryModal())
async def _cvoir(self, interaction):
if not interaction.user.guild_permissions.manage_guild:
await interaction.response.send_message("❌ Permission requise.", ephemeral=True)
return
config = load_guild_config(interaction.guild.id)
embed = discord.Embed(title="⚙️ Configuration actuelle", color=C_CONFIG)
roles_text = ""
for key, label in ROLE_LABELS.items():
rid = config.get("roles", {}).get(key)
if rid:
role = interaction.guild.get_role(int(rid))
roles_text += f"> {label} : {role.mention if role else f'❌ {rid}'}\n"
else:
roles_text += f"> {label} : ⚠️ Non défini\n"
embed.add_field(name="🎭 Rôles", value=roles_text, inline=False)
channels_text = ""
for key, label in CHANNEL_LABELS.items():
cid = config.get("channels", {}).get(key)
if cid:
ch = interaction.guild.get_channel(int(cid))
channels_text += f"> {label} : {ch.mention if ch else f'❌ {cid}'}\n"
else:
channels_text += f"> {label} : ⚠️ Non défini\n"
embed.add_field(name="📢 Salons", value=channels_text, inline=False)
cats_text = ""
for key, label in CATEGORY_LABELS.items():
catid = config.get("categories", {}).get(key)
if catid:
cat = interaction.guild.get_channel(int(catid))
cats_text += f"> {label} : **{cat.name}**\n" if cat else f"> {label} : ❌ {catid}\n"
else:
cats_text += f"> {label} : ⚠️ Non défini\n"
embed.add_field(name="📁 Catégories", value=cats_text, inline=False)
await interaction.response.send_message(embed=embed, ephemeral=True)
async def _creset(self, interaction):
if not interaction.user.guild_permissions.administrator:
await interaction.response.send_message("❌ Réservé aux administrateurs.", ephemeral=True)
return
save_guild_config(interaction.guild.id, {"roles": {}, "channels": {}, "categories": {}})
await interaction.response.send_message("✅ Configuration réinitialisée.", ephemeral=True)
# ═══════════════════════════════════════════════════════════════════
# VIEWS — DIVERS
# ═══════════════════════════════════════════════════════════════════
class DiversPanelView(discord.ui.LayoutView):
def __init__(self, author, bot):
super().__init__(timeout=300)
self.author = author
self.bot_ref = bot
items = [
discord.ui.TextDisplay("## 🎲 Divers"),
discord.ui.TextDisplay("Lancer de dé et autres outils."),
discord.ui.Separator(),
discord.ui.ActionRow(
_btn("🎲 Lancer un Dé", discord.ButtonStyle.primary, "dp_roll", self._roll),
),
]
if author.guild_permissions.administrator:
items.append(
discord.ui.ActionRow(
_btn("📊 Stats Bot", discord.ButtonStyle.secondary, "dp_stats", self._stats),
)
)
items.extend([
discord.ui.Separator(),
discord.ui.ActionRow(_back_btn("back_dp", self._back)),
])
self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_FUN)))
async def _back(self, interaction):
await interaction.response.edit_message(
view=MainPanelView(interaction.user, self.bot_ref)
)
async def _roll(self, interaction):
await interaction.response.send_modal(RollModal())
async def _stats(self, interaction):
from utils.stats_manager import get_stats_detailed
stats = get_stats_detailed()
if not stats:
await interaction.response.send_message("📊 Aucune statistique.", ephemeral=True)
return
await interaction.response.send_message(
f"📊 **Statistiques du bot**\n```json\n{json.dumps(stats, indent=2, ensure_ascii=False)[:1800]}\n```",
ephemeral=True,
)
# ═══════════════════════════════════════════════════════════════════
# PANEL PRINCIPAL — MENU ADAPTATIF
# ═══════════════════════════════════════════════════════════════════
class CategorySelect(discord.ui.Select):
def __init__(self, author, bot):
options = [
discord.SelectOption(label="Mes Comptes", description="Comptes, virements, cartes", emoji="💰", value="compte"),
]
if has_role(author, "banquier"):
options.append(
discord.SelectOption(label="Opérations Banquier", description="Ouvrir comptes, opérations", emoji="🏦", value="banquier")
)
if has_role(author, "gouvernement"):
options.append(
discord.SelectOption(label="Gouvernement", description="Banques, institutions, fisc", emoji="🏛️", value="gouvernement")
)
options.append(
discord.SelectOption(label="Identité", description="Cartes d'identité, permis", emoji="🪪", value="identite")
)
if author.guild_permissions.administrator:
options.append(
discord.SelectOption(label="Tickets & Messages", description="Tickets, MP privés", emoji="📩", value="tickets")
)
options.append(
discord.SelectOption(label="Sessions RP", description="Annonces, présences", emoji="📅", value="sessions")
)
options.append(
discord.SelectOption(label="Divers", description="Dé, stats", emoji="🎲", value="divers")
)
if author.guild_permissions.manage_guild:
options.append(
discord.SelectOption(label="Configuration", description="Configurer le bot", emoji="⚙️", value="config")
)
super().__init__(placeholder="Sélectionnez une catégorie...", options=options, custom_id="pnl_catselect")
self._bot = bot
async def callback(self, interaction: discord.Interaction):
view_map = {
"compte": ComptePanelView,
"banquier": BanquierPanelView,
"gouvernement": GouvernementPanelView,
"identite": IdentitePanelView,
"tickets": TicketsPanelView,
"sessions": SessionsPanelView,
"config": ConfigPanelView,
"divers": DiversPanelView,
}
cls = view_map.get(self.values[0])
if cls:
view = cls(interaction.user, self._bot)
if isinstance(view, ComptePanelView):
await view._load_accounts(interaction)
await interaction.response.edit_message(view=view)
class MainPanelView(discord.ui.LayoutView):
def __init__(self, author, bot):
super().__init__(timeout=300)
self.author = author
self.bot_ref = bot
self.message = None
role_parts = []
if has_role(author, "banquier"):
role_parts.append("🏦 Banquier")
if has_role(author, "gouvernement"):
role_parts.append("🏛️ Gouvernement")
if has_role(author, "ems"):
role_parts.append("🏥 EMS")
if has_role(author, "auto_ecole"):
role_parts.append("🚗 Auto-école")
if author.guild_permissions.administrator:
role_parts.append("👑 Admin")
role_text = " • ".join(role_parts) if role_parts else "👤 Citoyen"
guild_name = author.guild.name if author.guild else "Victoria's Vice"
self.add_item(discord.ui.Container(
discord.ui.TextDisplay(f"## 🏦 {guild_name} — Panel de Commandes"),
discord.ui.TextDisplay(f"**Vos rôles :** {role_text}"),
discord.ui.TextDisplay("Sélectionnez une catégorie ci-dessous pour accéder aux commandes."),
discord.ui.Separator(),
discord.ui.ActionRow(CategorySelect(author, bot)),
accent_colour=discord.Colour(C_MAIN),
))
async def on_timeout(self):
if self.message:
try:
await self.message.edit(
content="⏰ Panel expiré. Utilisez `/panel` pour le rouvrir.",
view=None,
)
except discord.HTTPException:
pass
# ═══════════════════════════════════════════════════════════════════
# COG
# ═══════════════════════════════════════════════════════════════════
class Panel(commands.Cog):
def __init__(self, bot):
self.bot = bot
@app_commands.command(name="panel", description="Ouvre le panneau de commandes du bot")
async def panel(self, interaction: discord.Interaction):
view = MainPanelView(interaction.user, self.bot)
await interaction.response.send_message(view=view, ephemeral=False)
view.message = await interaction.original_response()
async def setup(bot):
await bot.add_cog(Panel(bot))