Vicy_Publique/commandes/panel.py
utilisateurqu ae451fe894 Fix retrait DB migration + emojis panel
- db.py: Ajout migration ALTER TABLE pour colonne client_negative_floor
  (correction du bug 'no such column' sur les bases existantes)
- panel.py: Tous les boutons du panel avec emojis
- panel.py: Correction TextSelect -> TextDisplay (panel crash)
2026-08-19 21:21:11 +02:00

1314 lines
62 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 hashlib
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
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 _uid(seed: str) -> str:
return hashlib.md5(seed.encode()).hexdigest()[:12]
def _btn(label, style, cid, cb, emoji=None):
b = discord.ui.Button(label=label, style=style, custom_id=cid, emoji=emoji)
b.callback = cb
return b
def _back_btn(cid, cb):
return _btn("Retour", discord.ButtonStyle.secondary, cid, cb, emoji="◀️")
async def _get_user_bank(guild, user) -> dict:
banques = await bank_manager.lister_banques(guild.id)
for b in banques:
role = guild.get_role(b["role_id"])
if role and role in user.roles:
return b
return None
# ═══════════════════════════════════════════════════════════════════
# MODALS
# ═══════════════════════════════════════════════════════════════════
class VirementModal(discord.ui.Modal, title="💸 Virement"):
rib_dest = discord.ui.TextInput(label="RIB destinataire", required=True, max_length=30)
montant = discord.ui.TextInput(label="Montant (EUR)", 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}EUR** envoye."
if result["tva_prelevee"] > 0:
msg += f"\nTVA prelevee : **{result['tva_prelevee']:.2f}EUR**"
await interaction.response.send_message(msg, ephemeral=True)
except ValueError as e:
await interaction.response.send_message(str(e), ephemeral=True)
class DepotRetraitModal(discord.ui.Modal):
montant = discord.ui.TextInput(label="Montant (EUR)", required=True, max_length=15)
def __init__(self, rib: str, operation: str):
super().__init__(title=f"{'Depot' 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}EUR**. Nouveau solde : **{result['nouveau_solde']:.2f}EUR**",
ephemeral=True,
)
except ValueError as e:
await interaction.response.send_message(str(e), ephemeral=True)
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)
def __init__(self, bank_data=None):
super().__init__()
self.bank_data = bank_data
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
bank = self.bank_data
if not bank:
bank = await _get_user_bank(interaction.guild, interaction.user)
if not bank:
await interaction.response.send_message("Aucune banque associee.", 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 ete ouvert.\n"
f"**RIB :** `{result['rib']}`"
)
except discord.Forbidden:
pass
except ValueError as e:
await interaction.response.send_message(str(e), ephemeral=True)
class CreerBanqueModal(discord.ui.Modal, title="Creer une Banque"):
nom = discord.ui.TextInput(label="Nom de la banque", required=True, max_length=50)
role_id = discord.ui.TextInput(label="ID du role 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("Reserve 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("Role introuvable.", ephemeral=True)
return
try:
result = await bank_manager.creer_banque(interaction.guild.id, self.nom.value.strip(), rid)
embed = discord.Embed(
title="Banque creee",
description=(
f"**Nom :** {result['name']}\n"
f"**Role :** {role.mention}\n"
f"**Code etablissement :** `{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(str(e), ephemeral=True)
class CreerInstitutionModal(discord.ui.Modal, title="Creer une Institution"):
nom = discord.ui.TextInput(label="Nom de l'institution", required=True, max_length=50)
role_id = discord.ui.TextInput(label="ID du role (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("Reserve 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']}** creee (ID: `{result['id']}`).", ephemeral=True,
)
except Exception as e:
await interaction.response.send_message(str(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("Reserve 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)
await interaction.response.send_message(
"Fisc configure." if ok else "Institution introuvable.", ephemeral=True,
)
class GouvVirerModal(discord.ui.Modal, title="Virement Fisc"):
rib_dest = discord.ui.TextInput(label="RIB destinataire", required=True, max_length=30)
montant = discord.ui.TextInput(label="Montant (EUR)", 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("Reserve 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 configure.", 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
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}EUR** effectue depuis le Fisc.", ephemeral=True)
except ValueError as e:
await interaction.response.send_message(str(e), ephemeral=True)
class SetPlafondModal(discord.ui.Modal, title="Plafond Negatif"):
plafond = discord.ui.TextInput(label="Plafond max (0-10000 EUR)", required=True, max_length=10)
def __init__(self, bank_data):
super().__init__()
self.bank_data = bank_data
async def on_submit(self, interaction: discord.Interaction):
try:
val = float(self.plafond.value.replace(",", ".").strip())
except ValueError:
await interaction.response.send_message("Valeur invalide.", ephemeral=True)
return
if val < 0 or val > 10000:
await interaction.response.send_message("Plafond doit etre entre 0 et 10000.", ephemeral=True)
return
ok = await bank_manager.set_negative_floor(interaction.guild.id, self.bank_data["id"], val)
await interaction.response.send_message(
f"Plafond negatif de **{self.bank_data['name']}** defini a **-{val:.2f}EUR**." if ok else "Erreur.",
ephemeral=True,
)
class SetReserveModal(discord.ui.Modal, title="Reserve Bancaire"):
montant = discord.ui.TextInput(label="Montant de la reserve (EUR)", required=True, max_length=15)
def __init__(self, bank_data):
super().__init__()
self.bank_data = bank_data
async def on_submit(self, interaction: discord.Interaction):
try:
val = float(self.montant.value.replace(",", ".").strip())
except ValueError:
await interaction.response.send_message("Valeur invalide.", ephemeral=True)
return
if val < 0:
await interaction.response.send_message("La reserve ne peut pas etre negative.", ephemeral=True)
return
ok = await bank_manager.set_vault_balance(interaction.guild.id, self.bank_data["id"], val)
await interaction.response.send_message(
f"Reserve de **{self.bank_data['name']}** definie a **{val:.2f}EUR**." if ok else "Erreur.",
ephemeral=True,
)
class CreerCarteModal(discord.ui.Modal, title="Creer une Carte"):
prenom = discord.ui.TextInput(label="Prenom", 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="Nationalite", 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("Reserve 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'identite creee.", ephemeral=True)
class AnnonceSessionModal(discord.ui.Modal, title="Annoncer une Session"):
date = discord.ui.TextInput(label="Date (ex: Vendredi 20 Juin a 20h45)", required=True, max_length=100)
reminder1 = discord.ui.TextInput(label="1er rappel (ex: Jeudi 19 Juin a 19h)", required=True, max_length=100)
reminder2 = discord.ui.TextInput(label="2e rappel (ex: Vendredi 20 Juin a 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 ete comprise.", ephemeral=True)
return
ping_role_id = get_role_id(interaction.guild.id, "citoyen")
message = (
"# <VV> - **SESSION VICTORIA'S VICE**\n\n"
f"> Rendez-vous le **{self.date.value}** pour une session officielle.\n"
f"> Ping : <@&{ping_role_id}>"
)
await interaction.response.send_message(content=message)
class ListePresenceModal(discord.ui.Modal, title="Liste des Presences"):
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 donnee 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"**Presents ({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,
)
class ConfigRoleModal(discord.ui.Modal, title="Definir un Role"):
cle = discord.ui.TextInput(label="Cle (banquier, citoyen, ems...)", required=True, max_length=30)
role_id = discord.ui.TextInput(label="ID du role 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("Role introuvable.", ephemeral=True)
return
set_config_value(interaction.guild.id, "roles", cle, rid)
await interaction.response.send_message(f"Role **{ROLE_LABELS.get(cle, cle)}** -> {role.mention}", ephemeral=True)
class ConfigChannelModal(discord.ui.Modal, title="Definir un Salon"):
cle = discord.ui.TextInput(label="Cle (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="Definir une Categorie"):
cle = discord.ui.TextInput(label="Cle (ticket)", required=True, max_length=30)
category_id = discord.ui.TextInput(label="ID de la categorie 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("Categorie introuvable.", ephemeral=True)
return
set_config_value(interaction.guild.id, "categories", cle, cid)
await interaction.response.send_message(f"Categorie **{CATEGORY_LABELS.get(cle, cle)}** -> **{cat.name}**", ephemeral=True)
class RollModal(discord.ui.Modal, title="Lancer un De"):
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="Resultat du de",
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 envoye a {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 a 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 envoye a **{count}** membres.", ephemeral=True)
# ═══════════════════════════════════════════════════════════════════
# VIEWS — COMPTES
# ═══════════════════════════════════════════════════════════════════
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"]
safe = _uid(rib)
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} EUR`".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_{safe}", self._card, emoji="💳"),
_btn("Relevé", discord.ButtonStyle.primary, f"cd_stmt_{safe}", self._statement, emoji="📄"),
_btn("Virement", discord.ButtonStyle.primary, f"cd_vir_{safe}", self._vir, emoji="💸"),
),
discord.ui.ActionRow(
_btn("Dépôt", discord.ButtonStyle.success, f"cd_dep_{safe}", self._depot, emoji="💰"),
_btn("Retrait", discord.ButtonStyle.danger, f"cd_ret_{safe}", self._retrait, emoji="🏧"),
),
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 _statement(self, interaction: discord.Interaction):
txs = await bank_manager.get_transactions(interaction.guild.id, self.compte["rib"], limit=20)
from utils.bank_statement import BankStatementGenerator
gen = BankStatementGenerator()
buf = gen.generate(
bank_name=self.bank["name"],
holder_name=interaction.user.display_name,
rib=self.compte["rib"],
solde_actuel=self.compte["solde"],
transactions=txs,
account_type=self.compte["account_type"],
)
file = discord.File(buf, filename="releve_bancaire.png")
embed = discord.Embed(title="Releve bancaire", color=C_BANK)
embed.set_image(url="attachment://releve_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):
view = ComptePanelView(interaction.user, self.bot_ref)
await view._load_accounts(interaction)
await interaction.response.edit_message(view=view)
class ComptePanelView(discord.ui.LayoutView):
def __init__(self, author, bot):
super().__init__(timeout=300)
self.author = author
self.bot_ref = bot
async def _load_accounts(self, interaction: discord.Interaction):
comptes = await bank_manager.get_comptes_user(interaction.guild.id, self.author.id)
if not comptes:
items = [
discord.ui.TextDisplay("## Mes Comptes"),
discord.ui.TextDisplay("Aucun compte bancaire. Contactez un banquier."),
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). Cliquez pour details."),
discord.ui.Separator(),
]
for c in comptes:
solde_str = f"{c['solde']:,.2f} EUR".replace(",", " ")
label = f"{c['bank_name']} — {c['account_type'].title()} — {solde_str}"
safe = _uid(c["rib"])
btn = _btn(label[:80], discord.ButtonStyle.primary, f"cp_acc_{safe}", self._make_acc_cb(c), emoji="💰")
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):
await interaction.response.edit_message(view=MainPanelView(interaction.user, self.bot_ref))
# ═══════════════════════════════════════════════════════════════════
# VIEWS — 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
items = [
discord.ui.TextDisplay("## Operations Banquier"),
discord.ui.TextDisplay("Selectionnez un membre, puis choisissez une operation."),
discord.ui.Separator(),
discord.ui.ActionRow(
discord.ui.UserSelect(
placeholder="Selectionner un membre...",
custom_id=_uid("bq_sel_" + str(author.id)),
)
),
discord.ui.Separator(),
discord.ui.ActionRow(
_btn("Ouvrir Compte", discord.ButtonStyle.success, _uid("bq_open_" + str(author.id)), self._ouvrir, emoji="➕"),
_btn("Consulter Clients", discord.ButtonStyle.secondary, _uid("bq_clients_" + str(author.id)), self._clients, emoji="👥"),
),
discord.ui.ActionRow(
_btn("Info Banque", discord.ButtonStyle.secondary, _uid("bq_info_" + str(author.id)), self._info, emoji="ℹ️"),
_btn("Transactions", discord.ButtonStyle.secondary, _uid("bq_tx_" + str(author.id)), self._tx, emoji="📊"),
),
discord.ui.ActionRow(
_btn("Set Plafond", discord.ButtonStyle.primary, _uid("bq_plaf_" + str(author.id)), self._plafond, emoji="📉"),
_btn("Set Reserve", discord.ButtonStyle.primary, _uid("bq_res_" + str(author.id)), self._reserve, emoji="🏦"),
),
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)))
for child in self.children:
if isinstance(child, discord.ui.Container):
for item in child.children:
if isinstance(item, discord.ui.ActionRow):
for sub in item.children:
if isinstance(sub, discord.ui.UserSelect):
sub.callback = self._user_selected
break
async def _user_selected(self, interaction: discord.Interaction):
self.selected_user = interaction.data["values"][0]
await interaction.response.defer()
async def _back(self, interaction):
await interaction.response.edit_message(view=MainPanelView(interaction.user, self.bot_ref))
async def _ouvrir(self, interaction):
bank = await _get_user_bank(interaction.guild, interaction.user)
await interaction.response.send_modal(OuvrirCompteModal(bank_data=bank))
async def _clients(self, interaction):
bank = await _get_user_bank(interaction.guild, interaction.user)
if not bank:
await interaction.response.send_message("Aucune banque associee.", ephemeral=True)
return
comptes = await bank_manager.get_comptes_banque(interaction.guild.id, bank["id"])
if not comptes:
await interaction.response.send_message("Aucun compte dans cette banque.", ephemeral=True)
return
embed = discord.Embed(title=f"Clients de {bank['name']}", color=discord.Color.blue())
for c in comptes:
member = interaction.guild.get_member(c["user_id"])
name = member.display_name if member else f"<@{c['user_id']}>"
tva_text = f" | TVA: {c['tva_rate']}%" if c["account_type"] == "entreprise" else ""
embed.add_field(
name=f"{name} ({c['account_type']})",
value=f"**RIB :** `{c['rib']}`\n**Solde :** `{c['solde']:.2f}EUR`{tva_text}",
inline=False,
)
embed.set_footer(text=f"{len(comptes)} compte(s)")
await interaction.response.send_message(embed=embed, ephemeral=True)
async def _info(self, interaction):
bank = await _get_user_bank(interaction.guild, interaction.user)
if not bank:
await interaction.response.send_message("Aucune banque associee.", ephemeral=True)
return
role = interaction.guild.get_role(bank["role_id"])
comptes = await bank_manager.get_comptes_banque(interaction.guild.id, bank["id"])
nb_p = sum(1 for c in comptes if c["account_type"] == "particulier")
nb_e = sum(1 for c in comptes if c["account_type"] == "entreprise")
embed = discord.Embed(title=f"{bank['name']}", color=discord.Color.blue())
embed.add_field(name="Role", value=role.mention if role else "Inconnu", inline=True)
embed.add_field(name="Code etabl.", value=f"`{bank['establishment_code']}`", inline=True)
embed.add_field(name="Reserve", value=f"`{bank['vault_balance']:.2f}EUR`", inline=True)
embed.add_field(name="Encours clients", value=f"`{bank['clients_total_balance']:.2f}EUR`", inline=True)
embed.add_field(name="Particuliers", value=str(nb_p), inline=True)
embed.add_field(name="Entreprises", value=str(nb_e), inline=True)
if bank.get("client_negative_floor", 0) > 0:
embed.add_field(name="Plafond neg.", value=f"`-{bank['client_negative_floor']:.2f}EUR`", inline=True)
await interaction.response.send_message(embed=embed, ephemeral=True)
async def _tx(self, interaction):
if not self.selected_user:
await interaction.response.send_message("Selectionnez d'abord un membre.", ephemeral=True)
return
uid = int(self.selected_user) if isinstance(self.selected_user, (int, str)) else self.selected_user.id
comptes = await bank_manager.get_comptes_user(interaction.guild.id, uid)
if not comptes:
await interaction.response.send_message("Aucun compte pour ce membre.", ephemeral=True)
return
member = interaction.guild.get_member(uid)
name = member.display_name if member else str(uid)
embed = discord.Embed(title=f"Transactions de {name}", color=discord.Color.blue())
for c in comptes:
txs = await bank_manager.get_transactions(interaction.guild.id, c["rib"], limit=5)
if txs:
lines = []
for tx in txs:
is_out = tx["emetteur_rib"] == c["rib"]
signe = "-" if is_out else "+"
lines.append(f"`{tx['date'][:16]}` **{tx['type']}** `{signe}{tx['montant']:.2f}EUR`")
embed.add_field(name=f"{c['bank_name']} ({c['account_type']})", value="\n".join(lines), inline=False)
await interaction.response.send_message(embed=embed, ephemeral=True)
async def _plafond(self, interaction):
bank = await _get_user_bank(interaction.guild, interaction.user)
if not bank:
await interaction.response.send_message("Aucune banque associee.", ephemeral=True)
return
await interaction.response.send_modal(SetPlafondModal(bank))
async def _reserve(self, interaction):
bank = await _get_user_bank(interaction.guild, interaction.user)
if not bank:
await interaction.response.send_message("Aucune banque associee.", ephemeral=True)
return
await interaction.response.send_modal(SetReserveModal(bank))
# ═══════════════════════════════════════════════════════════════════
# 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."),
discord.ui.Separator(),
discord.ui.ActionRow(
_btn("Créer Banque", discord.ButtonStyle.success, "gp_cbanque", self._cbanque, emoji="🏦"),
_btn("Créer Institution", discord.ButtonStyle.success, "gp_cinst", self._cinst, emoji="🏛️"),
),
discord.ui.ActionRow(
_btn("Config Fisc", discord.ButtonStyle.primary, "gp_fisc", self._fisc, emoji="📋"),
_btn("Virement Fisc", discord.ButtonStyle.primary, "gp_vfisc", self._vfisc, emoji="💸"),
),
discord.ui.ActionRow(
_btn("Lister Banques", discord.ButtonStyle.secondary, "gp_lbanques", self._lbanques, emoji="📃"),
_btn("Lister Institutions", discord.ButtonStyle.secondary, "gp_linst", self._linst, emoji="📃"),
),
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("Reserve 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("Reserve 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("Reserve 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("Reserve 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("Reserve 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"**Role :** {role.mention if role else 'N/A'}\n"
f"**Code :** `{b['establishment_code']}`\n"
f"**Reserve :** `{b['vault_balance']:.2f}EUR`\n"
f"**Encours :** `{b['clients_total_balance']:.2f}EUR`"
),
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("Reserve 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"**Role :** {role.mention if role else 'Non defini'}\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("## Identite"),
discord.ui.TextDisplay("Cartes d'identite, permis, visites medicales."),
discord.ui.Separator(),
discord.ui.ActionRow(
_btn("Ma Carte", discord.ButtonStyle.primary, "ip_carte", self._carte, emoji="🪪"),
),
]
if has_role(author, "gouvernement"):
items.append(discord.ui.ActionRow(
_btn("Créer Carte", discord.ButtonStyle.success, "ip_ccar", self._ccar, emoji="🪪"),
))
action_items = []
if has_role(author, "auto_ecole"):
action_items.append(_btn("Permis", discord.ButtonStyle.primary, "ip_permis", self._permis, emoji="🚗"))
if has_role(author, "ems"):
action_items.append(_btn("Visite Médicale", discord.ButtonStyle.primary, "ip_vismed", self._vismed, emoji="🏥"))
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'identite", 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_message("Module permis a venir.", ephemeral=True)
async def _vismed(self, interaction):
await interaction.response.send_message("Module visite medicale a venir.", ephemeral=True)
# ═══════════════════════════════════════════════════════════════════
# 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 prives."),
discord.ui.Separator(),
discord.ui.ActionRow(
_btn("Embed Ticket", discord.ButtonStyle.success, "tp_ticket", self._ticket, emoji="📩"),
_btn("Envoyer MP", discord.ButtonStyle.primary, "tp_mp", self._mp, emoji="✉️"),
_btn("MP à Tous", discord.ButtonStyle.danger, "tp_mptous", self._mptous, emoji="📢"),
),
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="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 envoye.", 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
# ═══════════════════════════════════════════════════════════════════
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 presences."),
discord.ui.Separator(),
discord.ui.ActionRow(
_btn("Annoncer Session", discord.ButtonStyle.success, "sp_annonce", self._annonce, emoji="📅"),
_btn("Liste Présences", discord.ButtonStyle.primary, "sp_liste", self._liste, emoji="✅"),
),
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 roles, salons et categories."),
discord.ui.Separator(),
discord.ui.ActionRow(
_btn("Définir Rôle", discord.ButtonStyle.primary, "cfg_role", self._crole, emoji="🎭"),
_btn("Définir Salon", discord.ButtonStyle.primary, "cfg_chan", self._cchan, emoji="💬"),
_btn("Définir Catégorie", discord.ButtonStyle.primary, "cfg_cat", self._ccat, emoji="📂"),
),
discord.ui.ActionRow(
_btn("Voir Config", discord.ButtonStyle.secondary, "cfg_voir", self._cvoir, emoji="👁️"),
_btn("Reset Config", discord.ButtonStyle.danger, "cfg_reset", self._creset, emoji="🗑️"),
),
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'N/A {rid}'}\n"
else:
roles_text += f"> {label} : Non defini\n"
embed.add_field(name="Roles", 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'N/A {cid}'}\n"
else:
channels_text += f"> {label} : Non defini\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} : N/A {catid}\n"
else:
cats_text += f"> {label} : Non defini\n"
embed.add_field(name="Categories", 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("Admin uniquement.", ephemeral=True)
return
save_guild_config(interaction.guild.id, {"roles": {}, "channels": {}, "categories": {}})
await interaction.response.send_message("Configuration reinitialisee.", 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 de et autres outils."),
discord.ui.Separator(),
discord.ui.ActionRow(
_btn("Lancer un Dé", discord.ButtonStyle.primary, "dp_roll", self._roll, emoji="🎲"),
),
]
if author.guild_permissions.administrator:
items.append(discord.ui.ActionRow(
_btn("Stats Bot", discord.ButtonStyle.secondary, "dp_stats", self._stats, emoji="📊"),
))
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
# ═══════════════════════════════════════════════════════════════════
class CategorySelect(discord.ui.Select):
def __init__(self, author, bot):
options = [
discord.SelectOption(label="Mes Comptes", description="Comptes, virements, releves", emoji="💰", value="compte"),
]
if has_role(author, "banquier"):
options.append(discord.SelectOption(label="Operations Banquier", description="Comptes, clients, reserve", 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="Identite", description="Cartes, permis, medicale", emoji="🪪", value="identite"))
if author.guild_permissions.administrator:
options.append(discord.SelectOption(label="Tickets & Messages", description="Tickets, MP prives", emoji="📩", value="tickets"))
options.append(discord.SelectOption(label="Sessions RP", description="Annonces, presences", emoji="📅", value="sessions"))
options.append(discord.SelectOption(label="Divers", description="De, 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="Selectionnez une categorie...", options=options, custom_id=_uid("pnl_cat_" + str(author.id)))
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-ecole")
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 roles :** {role_text}"),
discord.ui.TextDisplay("Selectionnez une catégorie ci-dessous."),
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 expire. 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))