224 lines
8.6 KiB
Python
224 lines
8.6 KiB
Python
|
|
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))
|