Code source de lgrez.features.taches

"""lg-rez / features / Tâches planifiées

Planification, liste, annulation, exécution de tâches planifiées

"""

import datetime

from discord.ext import commands

from lgrez.blocs import tools
from lgrez.bdd import Tache, Action


[docs]class GestionTaches(commands.Cog): """Commandes de planification, exécution, annulation de tâches""" @commands.command() @tools.mjs_only async def taches(self, ctx): """Liste les tâches en attente (COMMANDE MJ) Affiche les commandes en attente d'exécution (dans la table :class:`.bdd.Tache`) et le timestamp d'exécution associé. Lorsque la tâche est liée à une action, affiche le nom de l'action et du joueur concerné. """ async with ctx.typing(): lst = Tache.query.order_by(Tache.timestamp).all() LT = "" for tache in lst: LT += ( f"\n{str(tache.id).ljust(5)} " f"{tache.timestamp.strftime('%d/%m/%Y %H:%M:%S')} " f"{tache.commande.ljust(25)} ") if (action := tache.action): LT += f"{action.base.slug.ljust(20)} {action.joueur.nom}" if LT: mess = ( "Tâches en attente : \n\nID Timestamp " "Commande Action Joueur" f"\n{'-'*105}{LT}\n\n" "Utilisez !cancel <ID> pour annuler une tâche." ) else: mess = "Aucune tâche en attente." await tools.send_code_blocs(ctx, mess) @commands.command(aliases=["doat"]) @tools.mjs_only async def planif(self, ctx, quand, *, commande): """Planifie une tâche au moment voulu (COMMANDE MJ) Args: quand: format ``[<J>/<M>[/<AAAA>]-]<H>:<M>[:<S>]``, avec ``<J>``, ``<M>``, ``<AAAA>``, ``<H>`` et ``<M>`` des entiers et ``<S>`` un entier/flottant optionnel. La date est optionnelle (défaut : date du jour). Si elle est précisée, elle doit être **séparée de l'heure par un tiret** et l'année peut être omise ; commande: commande à exécuter (commençant par un ``!``). La commande sera exécutée PAR UN WEBHOOK dans LE CHAN ``#logs`` : toutes les commandes qui sont liées au joueur ou réservées au chan privé sont à proscrire (ou doivent a minima être précédées de ``!doas cible``) Cette commande repose sur l'architecture en base de données, ce qui garantit l'exécution de la tâche même si le bot plante entre temps. Si le bot est down à l'heure d'exécution prévue, la commande sera exécutée dès le bot de retour en ligne. Si la date spécifiée est dans le passé, la commande est planifiée pour le lendemain. Examples: - ``!planif 18:00 !close maire`` - ``!planif 13/06-10:00 !open maire`` - ``!planif 13/06/2020-10:00 !open maire`` - ``!planif 23:25:12 !close maire`` """ now = datetime.datetime.now() if "/" in quand: # Date précisée date, time = quand.split("-") J, MA = date.split("/", maxsplit=1) day = int(J) if "/" in MA: # Année précisée M, A = MA.split("/") month = int(M) year = int(A) else: month = int(MA) year = now.year date = datetime.date(year=year, month=month, day=day) else: date = now.date() time = quand H, MS = time.split(":", maxsplit=1) hour = int(H) if ":" in MS: # Secondes précisées M, S = MS.split(":") minute = int(M) second = int(S) else: minute = int(MS) second = 0 time = datetime.time(hour=hour, minute=minute, second=second) ts = datetime.datetime.combine(date, time) if ts < datetime.datetime.now(): ts = datetime.datetime.combine( datetime.date.today() + datetime.timedelta(days=1), time ) await ctx.send("Date dans le passé, décalée à demain") action_id = None # ID de l'action associée à la tâche le cas échéant try: quoi, id = commande.split() if quoi in ["!open", "!close", "!remind"]: action_id = int(id) except ValueError: pass tache = Tache(timestamp=ts, commande=commande, action=Action.query.get(action_id)) tache.add() # Planifie la tâche await ctx.send( f"Commande {tools.code(commande)} planifiée pour le " f"{tools.code(ts.strftime('%d/%m/%Y %H:%M:%S'))}.\n" f"{tools.code(f'!cancel {tache.id}')} pour annuler." ) @commands.command(aliases=["retard", "doin"]) @tools.mjs_only async def delay(self, ctx, duree, *, commande): """Exécute une commande après XhYmZs (COMMANDE MJ) Args: quand: format ``[<X>h][<Y>m][<Z>s]``, avec ``<X>`` (heures) et ``<Y>`` (minutes) des entiers et ``<Z>`` (secondes) un entier ou un flottant. Chacune des trois composantes est optionnelle, mais au moins une doit être présente ; commande: commande à exécuter (commençant par un ``!``). La commande sera exécutée PAR UN WEBHOOK dans LE CHAN ``#logs`` : toutes les commandes qui sont liées au joueur ou réservées au chan privé sont à proscrire (ou doivent être précédées d'un ``!doas <cible>``). Cette commande repose sur l'architecture en base de données, ce qui garantit l'exécution de la commande même si le bot plante entre temps. Si le bot est down à l'heure d'exécution prévue, la commande sera exécutée dès le bot de retour en ligne. Examples: - ``!delay 2h !close maire`` - ``!delay 1h30m !doas @moi !vote Yacine Oussar`` """ secondes = 0 try: if "h" in duree.lower(): h, duree = duree.split("h") secondes += 3600*int(h) if "m" in duree.lower(): m, duree = duree.split("m") secondes += 60*int(m) if "s" in duree.lower(): s, duree = duree.split("s") secondes += float(s) except Exception as e: raise commands.BadArgument("<duree>") from e if duree or not secondes: raise commands.BadArgument("<duree>") ts = datetime.datetime.now() + datetime.timedelta(seconds=secondes) action_id = None # ID de l'action associée à la tâche le cas échéant try: quoi, id = commande.split(" ") if quoi in ["!open", "!close", "!remind"]: action_id = int(id) except ValueError: pass tache = Tache(timestamp=ts, commande=commande, action=Action.query.get(action_id)) tache.add() # Planifie la tâche await ctx.send( f"Commande {tools.code(commande)} planifiée pour le " f"{tools.code(ts.strftime('%d/%m/%Y %H:%M:%S'))}\n" f"{tools.code(f'!cancel {tache.id}')} pour annuler." ) @commands.command() @tools.mjs_only async def cancel(self, ctx, *ids): """Annule une ou plusieurs tâche(s) planifiée(s) (COMMANDE MJ) Args: *ids: IDs des tâches à annuler, séparées par des espaces. Utiliser ``!taches`` pour voir la liste des IDs. """ taches = [] for id in ids: if id.isdigit() and (tache := Tache.query.get(int(id))): taches.append(tache) if not taches: await ctx.send("Aucune tâche trouvée.") return Tache.delete(*taches) # Annule les tâches await ctx.send("Tâche(s) annulée(s) :\n" + "\n".join([ tools.code_bloc( f"!planif {tache.timestamp:%d/%m/%Y-%X} {tache.commande}`" ) for tache in taches ]))