from discord.ext import commands, tasks from discord import app_commands, ui import discord import json import asyncio import os from datetime import datetime ABSENCE_CHANNEL_ID = 1369669999677145099 ABSENCE_FILE = "absences.json" ABSENCE_FILE_TMP = "absences.json.tmp" file_lock = asyncio.Lock() async def load_absences(): async with file_lock: try: with open(ABSENCE_FILE, "r") as f: return json.load(f) except FileNotFoundError: return {} async def save_absences(data): async with file_lock: with open(ABSENCE_FILE_TMP, "w") as f: json.dump(data, f, indent=4) os.replace(ABSENCE_FILE_TMP, ABSENCE_FILE) class Absence(commands.Cog): def __init__(self, bot): self.bot = bot self.check_absences.start() @commands.hybrid_command(name="absence", with_app_command=True) async def absence(self, ctx, motif: str, date_debut: str, date_fin: str, superieur: discord.Member = None): try: debut = datetime.strptime(date_debut, "%d/%m/%Y") fin = datetime.strptime(date_fin, "%d/%m/%Y") except ValueError: return await ctx.send("❌ Format de date invalide. Utilise JJ/MM/AAAA.", ephemeral=True) if debut > fin: return await ctx.send("❌ La date de début doit précéder la date de fin.", ephemeral=True) abs_data = await load_absences() if str(ctx.author.id) in abs_data: return await ctx.send("❌ Tu as déjà déclaré une absence. Utilise `/fin_absence` pour la terminer.", ephemeral=True) duree = (fin - debut).days + 1 etat = "à venir" if debut > datetime.today() else "en cours" abs_data[str(ctx.author.id)] = { "pseudo": ctx.author.name, "debut": date_debut, "fin": date_fin, "motif": motif, "superieur": superieur.mention if superieur else "Aucun", "message_id": None, "etat": etat } await save_absences(abs_data) channel = self.bot.get_channel(ABSENCE_CHANNEL_ID) if not channel: return await ctx.send("❌ Salon d'absence introuvable. Vérifie l'ID.", ephemeral=True) absence_message = await channel.send( f"# ABSENCE STAFF :\n" f"- **Pseudo Discord** : {ctx.author.mention}\n" f"- **Durée** : {duree} jours\n" f"- **Débute le** : {date_debut}\n" f"- **Termine le** : {date_fin}\n" f"- **Motif** : {motif}\n" f"- **Mention du supérieur hiérarchique** : {superieur.mention if superieur else 'Aucun'}" ) abs_data[str(ctx.author.id)]["message_id"] = absence_message.id await save_absences(abs_data) await ctx.send("✅ Absence enregistrée avec succès !", ephemeral=True) @commands.hybrid_command(name="fin_absence", with_app_command=True) async def fin_absence(self, ctx): abs_data = await load_absences() user_id = str(ctx.author.id) if user_id not in abs_data: return await ctx.send("❌ Tu n'as pas d'absence en cours.", ephemeral=True) message_id = abs_data[user_id].get("message_id") channel = self.bot.get_channel(ABSENCE_CHANNEL_ID) if message_id and channel: try: msg = await channel.fetch_message(message_id) await msg.delete() except Exception: pass del abs_data[user_id] await save_absences(abs_data) await ctx.send("✅ Ton absence a été retirée.", ephemeral=True) @commands.hybrid_command(name="liste_absences", with_app_command=True) async def liste_absences(self, ctx): abs_data = await load_absences() absences = [ (user_id, info) for user_id, info in abs_data.items() if info.get("etat") == "en cours" ] if not absences: return await ctx.send("Aucune absence staff en cours.", ephemeral=True) per_page = 3 pages = [absences[i:i + per_page] for i in range(0, len(absences), per_page)] def create_embed(page_index: int) -> discord.Embed: embed = discord.Embed( title=f"📋 Absences staff en cours (Page {page_index + 1}/{len(pages)})", color=discord.Color.orange() ) for _, info in pages[page_index]: embed.add_field( name=info["pseudo"], value=( f"**Début** : {info['debut']}\n" f"**Fin** : {info['fin']}\n" f"**Motif** : {info['motif']}\n" f"**Supérieur** : {info['superieur']}" ), inline=False ) embed.set_footer(text="Gestion des absences du staff") return embed class PaginatorView(ui.View): def __init__(self): super().__init__(timeout=60) self.page = 0 @ui.button(label="⬅️ Précédent", style=discord.ButtonStyle.secondary, disabled=True) async def previous(self, interaction: discord.Interaction, button: discord.ui.Button): self.page -= 1 await interaction.response.edit_message(embed=create_embed(self.page), view=self) self.update_buttons() @ui.button(label="➡️ Suivant", style=discord.ButtonStyle.secondary, disabled=len(pages) == 1) async def next(self, interaction: discord.Interaction, button: discord.ui.Button): self.page += 1 await interaction.response.edit_message(embed=create_embed(self.page), view=self) self.update_buttons() def update_buttons(self): self.children[0].disabled = self.page == 0 self.children[1].disabled = self.page >= len(pages) - 1 view = PaginatorView() await ctx.send(embed=create_embed(0), view=view, ephemeral=True) @tasks.loop(minutes=1) async def check_absences(self): abs_data = await load_absences() today = datetime.today() to_remove = [] for user_id, info in abs_data.items(): debut = datetime.strptime(info["debut"], "%d/%m/%Y") fin = datetime.strptime(info["fin"], "%d/%m/%Y") etat = info.get("etat", "en cours") if etat == "à venir" and debut <= today: for guild in self.bot.guilds: member = guild.get_member(int(user_id)) if member: await self.deactivate_mentions(member) abs_data[user_id]["etat"] = "en cours" if etat == "en cours" and fin < today: for guild in self.bot.guilds: member = guild.get_member(int(user_id)) if member: await self.activate_mentions(member) to_remove.append(user_id) for user_id in to_remove: abs_data.pop(user_id, None) await save_absences(abs_data) async def deactivate_mentions(self, member): role = discord.utils.get(member.guild.roles, name="Absence") if not role: role = await member.guild.create_role(name="Absence", reason="Gestion des absences staff") await member.add_roles(role, reason="Début d'absence staff") async def activate_mentions(self, member): role = discord.utils.get(member.guild.roles, name="Absence") if role: await member.remove_roles(role, reason="Fin d'absence staff") @commands.Cog.listener() async def on_message(self, message): if message.author.bot: return abs_data = await load_absences() absent_ids = {k for k, v in abs_data.items() if v.get("etat") == "en cours"} if str(message.author.id) in absent_ids: await message.channel.send( f"{message.author.mention}, tu es actuellement déclaré absent. Merci de prévenir si tu es de retour !", delete_after=30 ) for user_id in absent_ids: if f"<@{user_id}>" in message.content or f"<@!{user_id}>" in message.content: msg_id = abs_data[user_id].get("message_id") absence_url = f"https://discord.com/channels/{message.guild.id}/{ABSENCE_CHANNEL_ID}/{msg_id}" if msg_id else "" await message.reply( f"{message.author.mention}, cet utilisateur est absent. Plus d'infos : {absence_url}", delete_after=60 ) async def setup(bot): await bot.add_cog(Absence(bot))