Ajout du système de convocation
This commit is contained in:
parent
6372014c99
commit
cc94e85105
4 changed files with 264 additions and 66 deletions
|
|
@ -1,104 +1,285 @@
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
from discord import app_commands
|
||||
import json
|
||||
import os
|
||||
from typing import List, Optional, Dict
|
||||
import datetime
|
||||
|
||||
# --- CONFIGURATION ---
|
||||
CONV_SETTINGS_FILE = "data/convocation_settings.json"
|
||||
CONV_BACKUPS_FILE = "data/convocation_backups.json"
|
||||
|
||||
def load_json(filepath: str) -> Dict:
|
||||
if not os.path.exists(filepath):
|
||||
return {}
|
||||
try:
|
||||
with open(filepath, "r", encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
return {}
|
||||
|
||||
def save_json(filepath: str, data: Dict):
|
||||
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
||||
with open(filepath, "w", encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
|
||||
class Convocation(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.settings = load_json(CONV_SETTINGS_FILE)
|
||||
self.backups = load_json(CONV_BACKUPS_FILE)
|
||||
|
||||
@app_commands.command(
|
||||
name="convocation",
|
||||
description="Convocation d’un membre"
|
||||
)
|
||||
def get_guild_settings(self, guild_id: int) -> Dict:
|
||||
return self.settings.get(str(guild_id), {})
|
||||
|
||||
def save_guild_settings(self, guild_id: int, guild_settings: Dict):
|
||||
self.settings[str(guild_id)] = guild_settings
|
||||
save_json(CONV_SETTINGS_FILE, self.settings)
|
||||
|
||||
def get_backup(self, guild_id: int, user_id: int) -> Optional[Dict]:
|
||||
key = f"{guild_id}_{user_id}"
|
||||
return self.backups.get(key)
|
||||
|
||||
def save_backup(self, guild_id: int, user_id: int, backup_data: Dict):
|
||||
key = f"{guild_id}_{user_id}"
|
||||
self.backups[key] = backup_data
|
||||
save_json(CONV_BACKUPS_FILE, self.backups)
|
||||
|
||||
def clear_backup(self, guild_id: int, user_id: int):
|
||||
key = f"{guild_id}_{user_id}"
|
||||
if key in self.backups:
|
||||
del self.backups[key]
|
||||
save_json(CONV_BACKUPS_FILE, self.backups)
|
||||
|
||||
@commands.hybrid_group(name="convocation", description="Gère les convocations de membres")
|
||||
@commands.has_permissions(manage_roles=True)
|
||||
async def convocation_group(self, ctx):
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send_help(ctx.command)
|
||||
|
||||
@convocation_group.command(name="convoquer", description="Convoque un membre et verrouille ses accès")
|
||||
@app_commands.describe(
|
||||
membre="Le membre à convoquer",
|
||||
raison="La raison de la convocation",
|
||||
heure="L’heure de la convocation"
|
||||
heure="L'heure de la convocation (ex: 14:30)"
|
||||
)
|
||||
@app_commands.checks.has_permissions(manage_roles=True)
|
||||
async def convocation(
|
||||
self,
|
||||
interaction: discord.Interaction,
|
||||
membre: discord.Member,
|
||||
raison: str,
|
||||
heure: str
|
||||
):
|
||||
role_convocation_id = 1388607106558726204
|
||||
log_user_ids = [
|
||||
1195440742307467377,
|
||||
689550504963080328,
|
||||
1359253046055534775
|
||||
]
|
||||
log_channel_id = 1389747212837322852
|
||||
async def convoquer(self, ctx, membre: discord.Member, raison: str, heure: str):
|
||||
guild_id = ctx.guild.id
|
||||
settings = self.get_guild_settings(guild_id)
|
||||
role_conv_id = settings.get("role_id")
|
||||
log_channel_id = settings.get("log_channel_id")
|
||||
attente_id = settings.get("attente_id") # Salon d'attente à ignorer
|
||||
admin_ids = settings.get("admin_ids", [])
|
||||
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
if not role_conv_id:
|
||||
await ctx.send("❌ Le rôle de convocation n'est pas configuré. Utilisez `/convocation config`.", ephemeral=True)
|
||||
return
|
||||
|
||||
await ctx.defer(ephemeral=True)
|
||||
|
||||
try:
|
||||
roles_to_remove = [
|
||||
r for r in membre.roles if r.id != interaction.guild.id
|
||||
]
|
||||
await membre.remove_roles(*roles_to_remove)
|
||||
role_convocation = interaction.guild.get_role(role_convocation_id)
|
||||
if role_convocation:
|
||||
await membre.add_roles(role_convocation)
|
||||
# --- BACKUP ---
|
||||
bot_top_role = ctx.guild.me.top_role
|
||||
backup_data = {
|
||||
"roles": [r.id for r in membre.roles if not r.is_default() and r.id != role_conv_id and r.position < bot_top_role.position],
|
||||
"overrides": {}
|
||||
}
|
||||
|
||||
embed_convocation = discord.Embed(
|
||||
# 1. Backup and Remove current roles
|
||||
current_roles = [ctx.guild.get_role(rid) for rid in backup_data["roles"] if ctx.guild.get_role(rid)]
|
||||
if current_roles:
|
||||
try:
|
||||
await membre.remove_roles(*current_roles, reason=f"Convocation par {ctx.author}")
|
||||
except discord.Forbidden:
|
||||
await ctx.send("⚠️ Je n'ai pas pu retirer certains rôles (Permission refusée).", ephemeral=True)
|
||||
|
||||
# 2. Dynamic Lockdown: Loop through ALL channels
|
||||
lockdown_count = 0
|
||||
for channel in ctx.guild.channels:
|
||||
# On ignore le salon d'attente et ses parents s'il y en a
|
||||
if attente_id and (channel.id == attente_id or (hasattr(channel, 'category') and channel.category and channel.category.id == attente_id)):
|
||||
continue
|
||||
|
||||
# Check existing override for the member
|
||||
overwrites = channel.overwrites_for(membre)
|
||||
if not overwrites.is_empty():
|
||||
# Save existing override (as dict of pair of permission name and boolean/None)
|
||||
backup_data["overrides"][str(channel.id)] = {k: v for k, v in dict(overwrites).items() if v is not None}
|
||||
|
||||
# Apply Lockdown override for the member
|
||||
try:
|
||||
# Specific override for this member: View Channel = False
|
||||
await channel.set_permissions(membre, view_channel=False, reason="Convocation Lockdown")
|
||||
lockdown_count += 1
|
||||
except discord.Forbidden:
|
||||
pass # Skip if we can't touch this channel
|
||||
|
||||
self.save_backup(guild_id, membre.id, backup_data)
|
||||
|
||||
# 3. Add Convocation role
|
||||
role_conv = ctx.guild.get_role(role_conv_id)
|
||||
if role_conv:
|
||||
if role_conv.position >= bot_top_role.position:
|
||||
await ctx.send(f"⚠️ Je ne peux pas donner le rôle {role_conv.mention} car il est plus haut que moi !", ephemeral=True)
|
||||
else:
|
||||
try:
|
||||
await membre.add_roles(role_conv, reason="Début de convocation")
|
||||
except discord.Forbidden:
|
||||
await ctx.send(f"⚠️ Impossible de donner le rôle {role_conv.name} (Permission refusée).", ephemeral=True)
|
||||
|
||||
# 4. Embeds & Logs (Inchangés mais inclus pour la cohérence)
|
||||
embed_conv = discord.Embed(
|
||||
title="CONVOCATION | 🔖",
|
||||
description=f"> Bonjour <@{membre.id}>, vous êtes **convoqué** par <@{interaction.user.id}>. Voici les **détails** :",
|
||||
color=0x0099ff
|
||||
description=f"> Bonjour {membre.mention}, vous êtes **convoqué** par {ctx.author.mention}. Voici les **détails** :",
|
||||
color=0x0099ff,
|
||||
timestamp=discord.utils.utcnow()
|
||||
)
|
||||
embed_convocation.add_field(name="**Raison de la convocation :**", value=f"➜ {raison}", inline=False)
|
||||
embed_convocation.add_field(name="**Heure de la convocation :**", value=f"➜ {heure}", inline=False)
|
||||
embed_convocation.add_field(name="\u200B", value=(
|
||||
"> Merci de vous présenter dans le vocal attente moov **5 minutes** avant l’heure. "
|
||||
"Soyez **professionnel**, et en cas d’absence, merci de contacter la personne vous ayant convoqué.\n\n"
|
||||
embed_conv.add_field(name="**Raison de la convocation :**", value=f"➜ {raison}", inline=False)
|
||||
embed_conv.add_field(name="**Heure de la convocation :**", value=f"➜ {heure}", inline=False)
|
||||
embed_conv.add_field(name="\u200B", value=(
|
||||
"> Merci de vous présenter dans le vocal attente **5 minutes** avant l'heure.\n\n"
|
||||
"> La **levée** de suspension se fera uniquement à la **fin** de la convocation."
|
||||
), inline=False)
|
||||
embed_convocation.add_field(name="**__Bien cordialement__**", value="_La Gestion de Alésia | 📑_", inline=False)
|
||||
embed_conv.set_footer(text=f"Gestion de {ctx.guild.name}")
|
||||
|
||||
embed_log = discord.Embed(
|
||||
title="📋 CONVOCATION ENVOYÉE",
|
||||
color=0xff9900
|
||||
color=0xff9900,
|
||||
timestamp=discord.utils.utcnow()
|
||||
)
|
||||
embed_log.add_field(name="👤 Membre convoqué", value=f"<@{membre.id}> ({membre.name}#{membre.discriminator})", inline=True)
|
||||
embed_log.add_field(name="👮 Convoqué par", value=f"<@{interaction.user.id}> ({interaction.user.name}#{interaction.user.discriminator})", inline=True)
|
||||
embed_log.add_field(name="👤 Membre convoqué", value=f"{membre.mention} ({membre.id})", inline=True)
|
||||
embed_log.add_field(name="👮 Convoqué par", value=f"{ctx.author.mention}", inline=True)
|
||||
embed_log.add_field(name="📄 Raison", value=raison, inline=False)
|
||||
embed_log.add_field(name="🕒 Heure", value=heure, inline=False)
|
||||
embed_log.add_field(name="📆 Date", value=f"<t:{int(discord.utils.utcnow().timestamp())}:F>", inline=False)
|
||||
embed_log.set_footer(text="Gestion des convocations", icon_url=self.bot.user.display_avatar.url)
|
||||
|
||||
# MP au membre
|
||||
# DM
|
||||
dm_sent = False
|
||||
try:
|
||||
await membre.send(embed=embed_convocation)
|
||||
await membre.send(embed=embed_conv)
|
||||
dm_sent = True
|
||||
except:
|
||||
except discord.Forbidden:
|
||||
dm_sent = False
|
||||
|
||||
await interaction.edit_original_response(content=(
|
||||
f"✅ Convocation envoyée à {membre.mention}." if dm_sent else
|
||||
f"⚠️ Convocation enregistrée, mais impossible d'envoyer un MP à {membre.mention}."
|
||||
))
|
||||
|
||||
# Log aux utilisateurs
|
||||
for user_id in log_user_ids:
|
||||
# Admin & Channel Logs
|
||||
for admin_id in admin_ids:
|
||||
try:
|
||||
user = await self.bot.fetch_user(user_id)
|
||||
await user.send(embed=embed_log)
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de l'envoi du log à {user_id} : {e}")
|
||||
admin = await self.bot.fetch_user(admin_id)
|
||||
await admin.send(embed=embed_log)
|
||||
except: pass
|
||||
|
||||
# Log dans le salon
|
||||
try:
|
||||
log_channel = await self.bot.fetch_channel(log_channel_id)
|
||||
if isinstance(log_channel, discord.TextChannel):
|
||||
await log_channel.send(embed=embed_log)
|
||||
except Exception as e:
|
||||
print(f"Erreur lors de l'envoi du log dans le salon : {e}")
|
||||
if log_channel_id:
|
||||
chan = ctx.guild.get_channel(log_channel_id)
|
||||
if chan: await chan.send(embed=embed_log)
|
||||
|
||||
await ctx.send(f"✅ Convocation envoyée à {membre.mention}. ({lockdown_count} salons verrouillés) " +
|
||||
("" if dm_sent else "(⚠️ DM impossible)"), ephemeral=True)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Erreur dans la commande /convocation : {e}")
|
||||
await interaction.edit_original_response(content="❌ Une erreur est survenue.")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
await ctx.send(f"❌ Une erreur critique est survenue : {e}", ephemeral=True)
|
||||
|
||||
@convocation_group.command(name="lever", description="Lève la convocation d'un membre et restaure ses accès")
|
||||
@app_commands.describe(membre="Le membre à libérer")
|
||||
async def lever(self, ctx, membre: discord.Member):
|
||||
guild_id = ctx.guild.id
|
||||
settings = self.get_guild_settings(guild_id)
|
||||
role_conv_id = settings.get("role_id")
|
||||
|
||||
await ctx.defer(ephemeral=True)
|
||||
|
||||
backup_data = self.get_backup(guild_id, membre.id)
|
||||
if not backup_data:
|
||||
await ctx.send("⚠️ Aucune donnée de convocation trouvée pour ce membre.", ephemeral=True)
|
||||
return
|
||||
|
||||
# 1. Remove Convocation role
|
||||
if role_conv_id:
|
||||
role_conv = ctx.guild.get_role(role_conv_id)
|
||||
if role_conv and role_conv in membre.roles:
|
||||
await membre.remove_roles(role_conv, reason="Fin de convocation")
|
||||
|
||||
# 2. Restore channel overrides
|
||||
restore_count = 0
|
||||
overrides = backup_data.get("overrides", {})
|
||||
for channel in ctx.guild.channels:
|
||||
chan_id_str = str(channel.id)
|
||||
try:
|
||||
if chan_id_str in overrides:
|
||||
# Restore previous override
|
||||
ov_data = overrides[chan_id_str]
|
||||
overwrite = discord.PermissionOverwrite(**ov_data)
|
||||
await channel.set_permissions(membre, overwrite=overwrite, reason="Restauration fin de convocation")
|
||||
restore_count += 1
|
||||
else:
|
||||
# Clear our lockdown override
|
||||
# On ne vide que si un override membre existait (sinon on risque de supprimer des perms rôles ?)
|
||||
# En fait set_permissions(member, overwrite=None) supprime l'entrée PERSONNELLE du membre.
|
||||
await channel.set_permissions(membre, overwrite=None, reason="Fin de convocation - Cleanup")
|
||||
except discord.Forbidden:
|
||||
pass
|
||||
|
||||
# 3. Restore roles
|
||||
role_ids = backup_data.get("roles", [])
|
||||
restored_roles = []
|
||||
if role_ids:
|
||||
for rid in role_ids:
|
||||
role = ctx.guild.get_role(rid)
|
||||
if role and role.position < ctx.guild.me.top_role.position:
|
||||
restored_roles.append(role)
|
||||
|
||||
if restored_roles:
|
||||
await membre.add_roles(*restored_roles, reason="Restauration fin de convocation")
|
||||
|
||||
self.clear_backup(guild_id, membre.id)
|
||||
|
||||
# 4. Notification
|
||||
embed = discord.Embed(
|
||||
title="🔓 CONVOCATION TERMINÉE",
|
||||
description=f"Votre convocation sur **{ctx.guild.name}** est terminée. Vos accès ont été restaurés.",
|
||||
color=discord.Color.green()
|
||||
)
|
||||
try:
|
||||
await membre.send(embed=embed)
|
||||
except: pass
|
||||
|
||||
await ctx.send(f"✅ Convocation levée pour {membre.mention}. Access restaurés.", ephemeral=True)
|
||||
|
||||
@convocation_group.command(name="config", description="Configure le système de convocation")
|
||||
@app_commands.describe(
|
||||
role="Le rôle de convocation (Muet/Convocation)",
|
||||
channel="Le salon de logs",
|
||||
attente="Salon (ou catégorie) d'attente à laisser visible",
|
||||
admins="Liste d'IDs d'admins à notifier (séparés par des espaces)"
|
||||
)
|
||||
@commands.has_permissions(administrator=True)
|
||||
async def config(self, ctx, role: discord.Role, channel: discord.TextChannel = None, attente: discord.abc.GuildChannel = None, admins: str = None):
|
||||
guild_id = ctx.guild.id
|
||||
settings = self.get_guild_settings(guild_id)
|
||||
|
||||
settings["role_id"] = role.id
|
||||
if channel:
|
||||
settings["log_channel_id"] = channel.id
|
||||
if attente:
|
||||
settings["attente_id"] = attente.id
|
||||
|
||||
if admins:
|
||||
admin_list = []
|
||||
for uid in admins.split():
|
||||
if uid.isdigit():
|
||||
admin_list.append(int(uid))
|
||||
settings["admin_ids"] = admin_list
|
||||
|
||||
self.save_guild_settings(guild_id, settings)
|
||||
|
||||
embed = discord.Embed(title="⚙️ Configuration Convocation Mise à jour", color=discord.Color.blue())
|
||||
embed.add_field(name="Rôle", value=role.mention)
|
||||
embed.add_field(name="Salon Logs", value=channel.mention if channel else "Non défini")
|
||||
embed.add_field(name="Salon/Cat d'attente", value=attente.mention if attente else "Non défini")
|
||||
embed.add_field(name="Admins notifiés", value=str(len(settings.get("admin_ids", []))))
|
||||
|
||||
await ctx.send(embed=embed, ephemeral=True)
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(Convocation(bot))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue