Vicy_Publique/commandes/panel.py
utilisateurqu 48ec3a3d89 V2.1: Plafond negatif, reserve vault, carte bancaire visuelle, panel rewrite
- db: added client_negative_floor column to banks
- bank_manager: vault check on depot, negative floor on retrait/virement
- banque: new /banque set-plafond and /banque set-reserve commands
- bank_card: new Pillow-based bank card image generator
- panel: full rewrite (no more JSON), guild name in title, new banking flow
2026-08-19 17:11:52 +02:00

1354 lines
61 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.

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))