"""lg-rez / features / Commandes de gestion des salons
Création, ajout, suppression de membres
"""
import asyncio
import functools
import datetime
from discord.ext import commands
from lgrez import config
from lgrez.blocs import tools, one_command
from lgrez.bdd import Joueur, Boudoir, Bouderie
from lgrez.features.sync import transtype
[docs]def in_boudoir(callback):
"""Décorateur : commande utilisable dans un boudoir uniquement.
Lors d'une invocation de la commande décorée hors d'un boudoir
(enregistré dans :class:`.bdd.Boudoir`), affiche un message d'erreur.
Ce décorateur n'est utilisable que sur une commande définie dans un
Cog.
"""
@functools.wraps(callback)
async def new_callback(self, ctx, *args, **kwargs):
try:
Boudoir.from_channel(ctx.channel)
except ValueError:
await ctx.reply("Cette commande est invalide en dehors "
"d'un boudoir.")
else:
# if ctx.authors
return await callback(self, ctx, *args, **kwargs)
return new_callback
async def _invite(joueur, boudoir, invite_msg):
"""Invitation d'un joueur dans un boudoir (lancer comme tâche à part)"""
pc = joueur.private_chan
bc = boudoir.chan
mess = await pc.send(
f"{joueur.member.mention} {boudoir.gerant.nom} t'as invité(e) à "
f"rejoindre son boudoir : « {boudoir.nom} » !\nAcceptes-tu ?"
)
if await tools.yes_no(mess):
await boudoir.add_joueur(joueur)
info = f"{joueur.nom} a rejoint le boudoir !"
confirm = f"Tu as bien rejoint {bc.mention} !"
else:
info = f"{joueur.nom} a refusé l'invitation à rejoindre ce boudoir."
confirm = "Invitation refusée."
try:
await invite_msg.reply(info)
except discord.HTTPException:
await bc.send(info)
await mess.reply(confirm)
[docs]class GestionChans(commands.Cog):
"""Gestion des salons"""
@commands.group(aliases=["boudoirs"])
async def boudoir(self, ctx):
"""Gestion des boudoirs
Les options relatives à un boudoir précis ne peuvent être
exécutées que dans ce boudoir ; certaines sont réservées au
gérant dudit boudoir.
"""
if not ctx.invoked_subcommand:
# Pas de sous-commande (correspondante)
if ctx.subcommand_passed:
# Tentative de subcommand
raise commands.BadArgument(
f"Option '{ctx.subcommand_passed}' inconnue"
)
ctx.message.content = f"!help {ctx.invoked_with}"
with one_command.bypass(ctx):
await config.bot.process_commands(ctx.message)
@boudoir.command(aliases=["liste"])
@tools.joueurs_only
@tools.private
async def list(self, ctx):
"""Liste les boudoirs dans lesquels tu es"""
joueur = Joueur.from_member(ctx.author)
bouderies = joueur.bouderies
if not bouderies:
await ctx.reply(
"Tu n'es dans aucun boudoir pour le moment.\n"
f"{tools.code('!boudoir create')} pour en créer un."
)
return
rep = "Tu es dans les boudoirs suivants :"
for bouderie in bouderies:
rep += f"\n - {bouderie.boudoir.chan.mention}"
if bouderie.gerant:
rep += " (gérant)"
rep += "\n\nUtilise `!boudoir leave` dans un boudoir pour le quitter."
await ctx.send(rep)
@boudoir.command(aliases=["new", "creer", "créer"])
@tools.vivants_only
@tools.private
async def create(self, ctx, *, nom=None):
"""Crée un nouveau boudoir dont tu es gérant"""
member = ctx.author
joueur = Joueur.from_member(member)
if not nom:
await ctx.send("Comment veux-tu nommer ton boudoir ?\n"
+ tools.ital("(`stop` pour annuler)"))
mess = await tools.wait_for_message_here(ctx)
nom = mess.content
if len(nom) > 32:
await ctx.send("Le nom des boudoirs est limité à 32 caractères.")
return
await ctx.send("Création du boudoir...")
async with ctx.typing():
now = datetime.datetime.now()
categ = tools.channel(config.boudoirs_category_name)
if len(categ.channels) >= 50:
# Limitation Discord : 50 channels par catégorie
ok = False
N = 2
while not ok:
nom_nouv_cat = f"{config.boudoirs_category_name} {N}"
categ_new = tools.channel(nom_nouv_cat,
must_be_found=False)
if not categ_new:
categ = await categ.clone(name=nom_nouv_cat)
ok = True
elif len(categ_new.channels) < 50:
# Catégorie N pas pleine
categ = categ_new
ok = True
N += 1
chan = await config.guild.create_text_channel(
nom,
topic=f"Boudoir crée le {now:%d/%m à %H:%M}. "
f"Gérant(e) : {joueur.nom}",
category=categ,
)
boudoir = Boudoir(chan_id=chan.id, nom=nom, ts_created=now)
boudoir.add()
await boudoir.add_joueur(joueur, gerant=True)
await chan.send(
f"{member.mention}, voici ton boudoir ! "
"Tu peux maintenant y inviter des gens avec la commande "
"`!boudoir invite`."
)
await ctx.send(f"Ton boudoir a bien été créé : {chan.mention} !")
await tools.log(f"Boudoir {chan.mention} créé par {joueur.nom}.")
@boudoir.command(aliases=["add"])
@tools.joueurs_only
@in_boudoir
async def invite(self, ctx, *, cible):
"""Invite un joueur à rejoindre ce boudoir"""
boudoir = Boudoir.from_channel(ctx.channel)
gerant = Joueur.from_member(ctx.author)
if boudoir.gerant != gerant:
await ctx.reply("Seul le gérant du boudoir peut y inviter "
"de nouvelles personnes.")
return
joueur = await tools.boucle_query_joueur(
ctx, cible=cible, message="Qui souhaites-tu inviter ?"
)
if joueur in boudoir.joueurs:
await ctx.send(f"{joueur.nom} est déjà dans ce boudoir !")
return
mess = await ctx.send(f"Invitation envoyée à {joueur.nom}.")
asyncio.create_task(_invite(joueur, boudoir, mess))
# On envoie l'invitation en arrière-plan (libération du chan).
@boudoir.command(aliases=["remove"])
@tools.joueurs_only
@in_boudoir
async def expulse(self, ctx, *, cible):
"""Expulse un membre de ce boudoir"""
boudoir = Boudoir.from_channel(ctx.channel)
gerant = Joueur.from_member(ctx.author)
if boudoir.gerant != gerant:
await ctx.reply("Seul le gérant du boudoir peut en expulser "
"des membres.")
return
joueur = await tools.boucle_query_joueur(
ctx, cible=cible, message="Qui souhaites-tu expulser ?"
)
if joueur not in boudoir.joueurs:
await ctx.send(f"{joueur.nom} n'est pas membre du boudoir !")
return
await boudoir.remove_joueur(joueur)
await joueur.private_chan.send(f"Tu as été expulsé(e) du boudoir "
f"« {boudoir.nom} ».")
await ctx.send(f"{joueur.nom} a bien été expulsé de ce boudoir.")
@boudoir.command(aliases=["quit"])
@tools.joueurs_only
@in_boudoir
async def leave(self, ctx):
"""Quitte ce boudoir"""
joueur = Joueur.from_member(ctx.author)
boudoir = Boudoir.from_channel(ctx.channel)
if boudoir.gerant == joueur:
await ctx.send(
"Tu ne peux pas quitter un boudoir que tu gères. "
"Utilise `!boudoir transfer` pour passer les droits "
"de gestion ou `!boudoir delete` pour le supprimer."
)
return
mess = await ctx.reply(
"Veux-tu vraiment quitter ce boudoir ? Tu ne "
"pourras pas y retourner sans invitation."
)
if not await tools.yes_no(mess):
await ctx.send("Mission aborted.")
return
await boudoir.remove_joueur(joueur)
await ctx.send(tools.ital(f"{joueur.nom} a quitté ce boudoir."))
@boudoir.command(aliases=["transmit"])
@tools.joueurs_only
@in_boudoir
async def transfer(self, ctx, cible=None):
"""Transfère les droits de gestion de ce boudoir"""
boudoir = Boudoir.from_channel(ctx.channel)
gerant = Joueur.from_member(ctx.author)
if boudoir.gerant != gerant:
await ctx.reply("Seul le gérant du boudoir peut en transférer "
"les droits de gestion.")
return
joueur = await tools.boucle_query_joueur(
ctx, cible=cible, message=("À qui souhaites-tu confier "
"la gestion de ce boudoir ?")
)
if joueur not in boudoir.joueurs:
await ctx.send(f"{joueur.nom} n'est pas membre de ce boudoir !")
return
mess = await ctx.reply(
"Veux-tu vraiment transférer les droits de ce boudoir ? "
"Tu ne pourras pas les récupérer par toi-même."
)
if not await tools.yes_no(mess):
await ctx.send("Mission aborted.")
return
bd_gerant = next(bd for bd in boudoir.bouderies if bd.joueur == gerant)
bd_nouv = next(bd for bd in boudoir.bouderies if bd.joueur == joueur)
bd_gerant.gerant = False
bd_nouv.gerant = True
bd_nouv.ts_promu = datetime.datetime.now()
Bouderie.update()
await boudoir.chan.edit(
topic=f"Boudoir crée le {boudoir.ts_created:%d/%m à %H:%M}. "
f"Gérant(e) : {joueur.nom}"
)
await ctx.send(f"Boudoir transféré à {joueur.nom}.")
@boudoir.command()
@tools.joueurs_only
@in_boudoir
async def delete(self, ctx):
"""Supprime ce boudoir"""
boudoir = Boudoir.from_channel(ctx.channel)
gerant = Joueur.from_member(ctx.author)
if boudoir.gerant != gerant:
await ctx.reply("Seul le gérant du boudoir peut le supprimer.")
return
mess = await ctx.reply("Veux-tu vraiment supprimer ce boudoir ? "
"Cette action est irréversible.")
if not await tools.yes_no(mess):
await ctx.send("Mission aborted.")
return
await ctx.send("Suppression...")
for joueur in boudoir.joueurs:
await boudoir.remove_joueur(joueur)
await joueur.private_chan.send(
f"Le boudoir « {boudoir.nom } » a été supprimé."
)
await boudoir.chan.edit(name=f"\N{CROSS MARK} {boudoir.nom}")
await ctx.send(tools.ital(
"[Tous les joueurs ont été exclus de ce boudoir ; "
"le channel reste présent pour archive.]"
))
@boudoir.command()
@tools.joueurs_only
@in_boudoir
async def rename(self, ctx, *, nom):
"""Renomme ce boudoir"""
boudoir = Boudoir.from_channel(ctx.channel)
gerant = Joueur.from_member(ctx.author)
if boudoir.gerant != gerant:
await ctx.reply("Seul le gérant du boudoir peut le renommer.")
return
if not nom:
await ctx.send("Comment veux-tu renommer ce boudoir ?\n"
+ tools.ital("(`stop` pour annuler)"))
mess = await tools.wait_for_message_here(ctx)
nom = mess.content
if len(nom) > 32:
await ctx.send("Le nom des boudoirs est limité à 32 caractères.")
return
boudoir.nom = nom
boudoir.update()
await boudoir.chan.edit(name=nom)
await ctx.send("Boudoir renommé avec succès.")
@commands.command()
@tools.mjs_only
async def addhere(self, ctx, *joueurs):
"""Ajoute les membres au chan courant (COMMANDE MJ)
Args:
*joueurs: membres à ajouter, chacun entouré par des
guillemets si nom + prénom
Si ``*joueurs`` est un seul élément, il peut être de la forme
``<crit>=<filtre>`` tel que décrit dans l'aide de ``!send``.
"""
ts_debut = ctx.message.created_at - datetime.timedelta(microseconds=1)
if len(joueurs) == 1 and "=" in joueurs[0]:
# Si critère : on remplace joueurs
crit, _, filtre = joueurs[0].partition("=")
crit = crit.strip()
if crit in Joueur.attrs:
col = Joueur.attrs[crit]
arg = transtype(filtre.strip(), col)
joueurs = Joueur.query.filter_by(**{crit: arg}).all()
else:
raise commands.UserInputError(f"critère '{crit}' incorrect")
else:
# Sinon, si noms / mentions
joueurs = [await tools.boucle_query_joueur(ctx, cible)
for cible in joueurs]
for joueur in joueurs:
await ctx.channel.set_permissions(joueur.member,
read_messages=True)
await ctx.send(f"{joueur.nom} ajouté")
mess = await ctx.send("Fini, purge les messages ?")
if await tools.yes_no(mess):
await ctx.channel.purge(after=ts_debut)
@commands.command()
@tools.mjs_only
async def purge(self, ctx, N=None):
"""Supprime tous les messages de ce chan (COMMANDE MJ)
Args:
N: nombre de messages à supprimer (défaut : tous)
"""
if N:
mess = await ctx.send(
f"Supprimer les {N} messages les plus récents de ce chan ? "
"(sans compter le `!purge` et ce message)"
)
else:
mess = await ctx.send("Supprimer tous les messages de ce chan ?")
if await tools.yes_no(mess):
await ctx.channel.purge(limit=int(N) + 2 if N else None)