Beispiel #1
0
    async def infos(self, ctx):
        """Affiche tes informations de rôle / actions

        Toutes les actions liées à ton rôle (et parfois d'autres) sont
        indiquées, même celles que tu ne peux pas utiliser pour
        l'instant (plus de charges, déclenchées automatiquement...)
        """
        member = ctx.author
        joueur = Joueur.from_member(member)
        r = ""

        r += f"Ton rôle actuel : {tools.bold(joueur.role.nom_complet)}\n"
        r += tools.ital(f"({tools.code(f'!roles {joueur.role.slug}')} "
                        "pour tout savoir sur ce rôle)")

        if joueur.actions_actives:
            r += "\n\nActions :"
            r += tools.code_bloc("\n".join((
                f" - {action.base.slug.ljust(20)} "
                + (f"Cooldown : {action.cooldown}" if action.cooldown
                   else action.base.temporalite).ljust(22)
                + (f"   {action.charges} charge(s)"
                    + (" pour cette semaine"
                        if (action.refill and "weekends" in action.refill)
                        else "")
                    if isinstance(action.charges, int) else "Illimitée")
            ) for action in joueur.actions_actives))
            # Vraiment désolé pour cette immondice j'ai la flemme
        else:
            r += "\n\nAucune action disponible."

        await ctx.send(
            f"{r}\n{tools.code('!menu')} pour voir les votes et "
            f"actions en cours, {tools.code('@MJ')} en cas de problème"
        )
Beispiel #2
0
    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}.")
Beispiel #3
0
    async def akinator(self, ctx):
        """J'ai glissé chef

        Implémentation directe de https://pypi.org/project/akinator.py

        Warning:
            Commande en bêta, non couverte par les tests unitaires.
        """
        # Un jour mettre ça dans des embeds avec les https://fr.akinator.com/
        # bundles/elokencesite/images/akitudes_670x1096/<akitude>.png croppées,
        # <akitude> in ["defi", "serein", "inspiration_legere",
        # "inspiration_forte", "confiant", "mobile", "leger_decouragement",
        # "vrai_decouragement", "deception", "triomphe"]
        await ctx.send("Vous avez demandé à être mis en relation avec " +
                       tools.ital("Akinator : Le Génie du web") +
                       ".\nVeuillez patienter...")
        async with ctx.typing():
            # Connexion
            aki = Akinator()
            question = await aki.start_game(language="fr")

        exit = False
        while not exit and aki.progression <= 80:
            mess = await ctx.send(f"({aki.step + 1}) {question}")
            reponse = await tools.wait_for_react_clic(mess, {
                "👍": "yes",
                "🤷": "idk",
                "👎": "no",
                "⏭️": "stop"
            })
            if reponse == "stop":
                exit = True
            else:
                async with ctx.typing():
                    question = await aki.answer(reponse)

        async with ctx.typing():
            await aki.win()

        mess = await ctx.send(
            f"Tu penses à {tools.bold(aki.first_guess['name'])} "
            f"({tools.ital(aki.first_guess['description'])}) !\n"
            f"J'ai bon ?\n{aki.first_guess['absolute_picture_path']}")
        if await tools.yes_no(mess):
            await ctx.send("Yay\nhttps://fr.akinator.com/bundles/elokencesite"
                           "/images/akitudes_670x1096/triomphe.png")
        else:
            await ctx.send("Oof\nhttps://fr.akinator.com/bundles/elokencesite"
                           "/images/akitudes_670x1096/deception.png")
Beispiel #4
0
    async def roles(self, ctx, *, filtre=None):
        """Affiche la liste des rôles / des informations sur un rôle

        Args:
            filtre: peut être

                - Le nom d'un camp pour les rôles le composant ;
                - Le nom d'un rôle pour les informations sur ce rôle.

        Sans argument liste tous les rôles existants.
        """
        if filtre:
            filtre = tools.remove_accents(filtre.lower())
            filtre = filtre.strip("<>[](){}")

        if not filtre:
            roles = Role.query.filter_by(actif=True).order_by(Role.nom).all()
        else:
            camps = Camp.find_nearest(
                filtre, col=Camp.nom,
                sensi=0.6, filtre=Camp.public.is_(True)
            )

            if camps:
                roles = camps[0][0].roles
            else:
                roles = Role.find_nearest(filtre, col=Role.nom)
                if not roles:
                    await ctx.send(f"Rôle / camp \"{filtre}\" non trouvé.")
                    return

                await ctx.send(embed=roles[0][0].embed)
                return

        await tools.send_blocs(
            ctx,
            "Rôles trouvés :\n"
            + "\n".join([
                str(role.camp.discord_emoji_or_none or "")
                + tools.code(f"{role.nom.ljust(25)} {role.description_courte}")
                for role in roles if not role.nom.startswith("(")
            ])
            + "\n" + tools.ital(f"({tools.code('!roles <role>')} "
                                "pour plus d'informations sur un rôle.)")
        )
Beispiel #5
0
    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."))
Beispiel #6
0
    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.")
Beispiel #7
0
    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.]"))
Beispiel #8
0
async def open_action(action):
    """Ouvre l'action.

    Args:
        action (.bdd.Action): l'action à ouvir

    Opérations réalisées :
        - Vérification des conditions (cooldown, charges...) et
          reprogrammation si nécessaire ;
        - Gestion des tâches planifiées (planifie remind/close si
          applicable) ;
        - Information du joueur.
    """
    joueur = action.joueur
    chan = joueur.private_chan

    # Vérification base
    if not action.base:
        await tools.log(f"{action} : pas de base, exit")
        return

    # Vérification active
    if not action.active:
        await tools.log(f"{action} : inactive, exit (pas de reprogrammation)")
        return

    # Vérification cooldown
    if action.cooldown > 0:  # Action en cooldown
        action.cooldown = action.cooldown - 1
        config.session.commit()
        await tools.log(f"{action} : en cooldown, exit "
                        "(reprogrammation si temporel).")
        if action.base.trigger_debut == ActionTrigger.temporel:
            # Programmation action du lendemain
            ts = tools.next_occurence(action.base.heure_debut)
            Tache(timestamp=ts, commande=f"!open {action.id}",
                  action=action).add()
        return

    # Vérification role_actif
    if not joueur.role_actif:
        # role_actif == False : on reprogramme la tâche au lendemain tanpis
        await tools.log(f"{action} : role_actif == False, exit "
                        "(reprogrammation si temporel).")
        if action.base.trigger_debut == ActionTrigger.temporel:
            ts = tools.next_occurence(action.base.heure_debut)
            Tache(timestamp=ts, commande=f"!open {action.id}",
                  action=action).add()
        return

    # Vérification charges
    if action.charges == 0:
        # Plus de charges, mais action maintenue en base car refill / ...
        await tools.log(f"{action} : plus de charges, exit "
                        "(reprogrammation si temporel).")
        return

    # Action "automatiques" (passives : notaire...) :
    # lance la procédure de clôture / résolution
    if action.base.trigger_fin == ActionTrigger.auto:
        if action.base.trigger_debut == ActionTrigger.temporel:
            await tools.log(
                f"Action {action.base.slug} pour {joueur.nom} pas vraiment "
                f"automatique, {config.Role.mj.mention} VENEZ M'AIDER "
                "JE PANIQUE 😱 (comme je suis vraiment sympa je vous "
                f"file son chan, {chan.mention})")
        else:
            await tools.log(
                f"{action} : automatique, appel processus de clôture")

        await close_action(action)
        return

    # Tous tests préliminaires n'ont pas return ==> Vraie action à lancer

    # Calcul heure de fin (si applicable)
    heure_fin = None
    if action.base.trigger_fin == ActionTrigger.temporel:
        heure_fin = action.base.heure_fin
        ts = tools.next_occurence(heure_fin)
    elif action.base.trigger_fin == ActionTrigger.delta:
        # Si delta, on calcule la vraie heure de fin (pas modifié en base)
        delta = action.base.heure_fin
        ts = (datetime.datetime.now() + datetime.timedelta(
            hours=delta.hour, minutes=delta.minute, seconds=delta.second))
        heure_fin = ts.time()

    # Programmation remind / close
    if action.base.trigger_fin in [
            ActionTrigger.temporel, ActionTrigger.delta
    ]:
        Tache(timestamp=ts - datetime.timedelta(minutes=30),
              commande=f"!remind {action.id}",
              action=action).add()
        Tache(timestamp=ts, commande=f"!close {action.id}",
              action=action).add()
    elif action.base.trigger_fin == ActionTrigger.perma:
        # Action permanente : fermer pour le WE
        # ou rappel / réinitialisation chaque jour
        ts_matin = tools.next_occurence(datetime.time(hour=7))
        ts_pause = tools.debut_pause()
        if ts_matin < ts_pause:
            # Réopen le lendamain
            Tache(timestamp=ts_matin,
                  commande=f"!open {action.id}",
                  action=action).add()
        else:
            # Sauf si pause d'ici là
            Tache(timestamp=ts_pause,
                  commande=f"!close {action.id}",
                  action=action).add()

    # Information du joueur
    if action.is_open:  # déjà ouverte
        message = await chan.send(
            f"{tools.montre()}  Rappel : tu peux utiliser quand tu le "
            f"souhaites ton action {tools.code(action.base.slug)} ! "
            f" {config.Emoji.action} \n" +
            (f"Tu as jusqu'à {heure_fin} pour le faire. \n"
             if heure_fin else "") +
            tools.ital(f"Tape {tools.code('!action (ce que tu veux faire)')}"
                       " ou utilise la réaction pour agir."))
    else:
        # on ouvre !
        util = Utilisation(action=action)
        util.add()
        util.open()
        message = await chan.send(
            f"{tools.montre()}  Tu peux maintenant utiliser ton action "
            f"{tools.code(action.base.slug)} !  {config.Emoji.action} \n" +
            (f"Tu as jusqu'à {heure_fin} pour le faire. \n"
             if heure_fin else "") +
            tools.ital(f"Tape {tools.code('!action (ce que tu veux faire)')}"
                       " ou utilise la réaction pour agir."))

    await message.add_reaction(config.Emoji.action)

    config.session.commit()
Beispiel #9
0
    async def lore(self, ctx, doc_id):
        """Récupère et poste un lore depuis un Google Docs (COMMANDE MJ)

        Convertit les formats et éléments suivants vers Discord :
            - Gras, italique, souligné, barré;
            - Petites majuscules (-> majuscules);
            - Polices à chasse fixe (Consolas / Courier New) (-> code);
            - Liens hypertextes;
            - Listes à puces;
            - Mentions de joueurs, sous la forme ``@Prénom Nom``;
            - Emojis, sous la forme ``:nom:``.

        Permet soit de poster directement dans #annonces, soit de
        récupérer la version formatée du texte (pour copier-coller).

        Args:
            doc_id: ID ou URL du document (doit être public ou dans le
                Drive partagé avec le compte de service)
        """
        if len(doc_id) < 44:
            raise commands.BadArgument("'doc_id' doit être l'ID ou l'URL "
                                       "d'un document Google Docs")
        elif len(doc_id) > 44:  # URL fournie (pas que l'ID)
            mtch = re.search(r"/d/(\w{44})(\W|$)", doc_id)
            if mtch:
                doc_id = mtch.group(1)
            else:
                raise commands.BadArgument("'doc_id' doit être l'ID ou l'URL "
                                           "d'un document Google Docs")

        await ctx.send("Récupération du document...")
        async with ctx.typing():
            content = gsheets.get_doc_content(doc_id)

        formatted_text = ""
        for (_text, style) in content:
            _text = _text.replace("\v", "\n").replace("\f", "\n")
            # Espaces/newlines à la fin de _text ==> à part
            text = _text.rstrip()
            len_rest = len(_text) - len(text)
            rest = _text[-len_rest:] if len_rest else ""

            if not text:  # espcaes/newlines uniquement
                formatted_text += rest
                continue

            # Remplacement des mentions
            text = re.sub(r"@([\w-]+ [\w-]+)", _mention_repl, text)
            text = re.sub(r":(\w+):", _emoji_repl, text)

            if style.get("bold"):
                text = tools.bold(text)
            if style.get("italic"):
                text = tools.ital(text)
            if style.get("strikethrough"):
                input(style.get("strikethrough"))
                text = tools.strike(text)
            if style.get("smallCaps"):
                text = text.upper()
            if (wff := style.get("weightedFontFamily")):
                if wff["fontFamily"] in ["Consolas", "Courier New"]:
                    text = tools.code(text)
            if (link := style.get("link")):
                if (url := link.get("url")):
                    if not "://" in text:
                        text = text + f" (<{url}>)"
Beispiel #10
0
    async def open(self, ctx, qui, heure=None, heure_chain=None):
        """Lance un vote / des actions de rôle (COMMANDE BOT / MJ)

        Args:
            qui:
                ===========     ===========
                ``cond``        pour le vote du condamné
                ``maire``       pour le vote du maire
                ``loups``       pour le vote des loups
                ``action``      pour les actions commençant à ``heure``
                ``{id}``        pour une action spécifique
                ===========     ===========

            heure:
                - si ``qui == "cond"``, ``"maire"`` ou ``"loup"``,
                  programme en plus la fermeture à ``heure``
                  (et un rappel 30 minutes avant) ;
                - si ``qui == "action"``, il est obligatoire : heure des
                  actions à lancer (cf plus haut). Pour les actions, la
                  fermeture est de toute façon programmée le cas échéant
                  (``trigger_fin`` ``temporel`` ou ``delta``).

                Dans tous les cas, format ``HHh`` ou ``HHhMM``.

            heure_chain:
                permet de chaîner des votes : lance le vote immédiatement
                et programme sa fermeture à ``heure``, en appellant ``!close``
                de sorte à programmer une nouvelle ouverture le lendemain à
                ``heure_chain``, et ainsi de suite.
                Format ``HHh`` ou ``HHhMM``.

        Une sécurité empêche de lancer un vote ou une action déjà en cours.

        Cette commande a pour vocation première d'être exécutée
        automatiquement par des tâches planifiées.
        Elle peut être utilisée à la main, mais attention à ne pas
        faire n'importe quoi (penser à envoyer / planifier la fermeture
        des votes, par exemple).

        Examples:
            - ``!open maire`` :        lance un vote condamné maintenant
            - ``!open cond 19h`` :     lance un vote condamné maintenant
              et programme sa fermeture à 19h00 (ex. Juge Bègue)
            - ``!open cond 18h 10h`` : lance un vote condamné maintenant,
              programme sa fermeture à 18h00, et une prochaine ouverture
              à 10h qui se fermera à 18h, et ainsi de suite
            - ``!open action 19h`` :   lance toutes les actions
              commençant à 19h00
            - ``!open 122`` :          lance l'action d'ID 122

        """
        try:
            qui = Vote[qui.lower()]  # cond / maire / loups
        except KeyError:
            pass
        joueurs = await recup_joueurs("open", qui, heure)
        # Liste de joueurs (votes) ou dictionnaire joueur : action

        str_joueurs = "\n - ".join([joueur.nom for joueur in joueurs])
        await tools.send_code_blocs(
            ctx,
            f"Utilisateur(s) répondant aux critères ({len(joueurs)}) : \n" +
            str_joueurs)

        # Création utilisations & envoi messages
        for joueur in joueurs:
            chan = joueur.private_chan

            if isinstance(qui, Vote):
                action = joueur.action_vote(qui)
                util = Utilisation(action=action)
                util.add()
                util.open()

            if qui == Vote.cond:
                message = await chan.send(
                    f"{tools.montre()}  Le vote pour le condamné du "
                    f"jour est ouvert !  {config.Emoji.bucher} \n" +
                    (f"Tu as jusqu'à {heure} pour voter. \n" if heure else ""
                     ) +
                    tools.ital(f"Tape {tools.code('!vote (nom du joueur)')}"
                               " ou utilise la réaction pour voter."))
                await message.add_reaction(config.Emoji.bucher)

            elif qui == Vote.maire:
                message = await chan.send(
                    f"{tools.montre()}  Le vote pour l'élection du "
                    f"maire est ouvert !  {config.Emoji.maire} \n" +
                    (f"Tu as jusqu'à {heure} pour voter. \n" if heure else ""
                     ) + tools.ital(
                         f"Tape {tools.code('!votemaire (nom du joueur)')} "
                         "ou utilise la réaction pour voter."))
                await message.add_reaction(config.Emoji.maire)

            elif qui == Vote.loups:
                message = await chan.send(
                    f"{tools.montre()}  Le vote pour la victime de "
                    f"cette nuit est ouvert !  {config.Emoji.lune} \n" +
                    (f"Tu as jusqu'à {heure} pour voter. \n" if heure else ""
                     ) + tools.ital(
                         f"Tape {tools.code('!voteloups (nom du joueur)')} "
                         "ou utilise la réaction pour voter."))
                await message.add_reaction(config.Emoji.lune)

            else:  # Action
                for action in joueurs[joueur]:
                    await gestion_actions.open_action(action)

        config.session.commit()

        # Actions déclenchées par ouverture
        if isinstance(qui, Vote):
            for action in Action.query.filter(
                    Action.base.has(
                        BaseAction.trigger_debut == ActionTrigger.open(qui))):
                await gestion_actions.open_action(action)

            for action in Action.query.filter(
                    Action.base.has(
                        BaseAction.trigger_fin == ActionTrigger.open(qui))):
                await gestion_actions.close_action(action)

        # Réinitialise haros/candids
        items = []
        if qui == Vote.cond:
            items = CandidHaro.query.filter_by(type=CandidHaroType.haro).all()
        elif qui == Vote.maire:
            items = CandidHaro.query.filter_by(
                type=CandidHaroType.candidature).all()
        if items:
            CandidHaro.delete(*items)
            await tools.log(f"!open {qui.name} : haros/candids wiped")
            await config.Channel.haros.send(
                f"{config.Emoji.void}\n" * 30 +
                "Nouveau vote, nouveaux haros !\n" + tools.ital(
                    "Les posts ci-dessus sont invalides pour le vote actuel. "
                    f"Utilisez {tools.code('!haro')} pour en relancer."))

        # Programme fermeture
        if isinstance(qui, Vote) and heure:
            ts = tools.next_occurence(tools.heure_to_time(heure))
            Tache(timestamp=ts - datetime.timedelta(minutes=30),
                  commande=f"!remind {qui.name}").add()
            if heure_chain:
                Tache(
                    timestamp=ts,
                    commande=f"!close {qui.name} {heure_chain} {heure}").add()
                # Programmera prochaine ouverture
            else:
                Tache(timestamp=ts, commande=f"!close {qui.name}").add()
Beispiel #11
0
    async def votemaire(self, ctx, *, cible=None):
        """Vote pour le nouveau maire

        Args:
            cible: nom du joueur pour lequel tu souhaites voter.

        Cette commande n'est utilisable que lorsqu'une élection pour le
        maire est en cours, pour les joueurs ayant le droit de voter.

        Le bot t'enverra un message à l'ouverture de chaque vote.

        La commande peut être utilisée autant que voulu pour changer de
        cible tant que le vote est en cours.
        """
        joueur = Joueur.from_member(ctx.author)
        try:
            vaction = joueur.action_vote(Vote.maire)
        except RuntimeError:
            await ctx.send("Minute papillon, le jeu n'est pas encore lancé !")
            return

        # Vérification vote en cours
        if not joueur.votant_village:
            await ctx.send("Tu n'as pas le droit de participer à ce vote.")
            return
        if not vaction.is_open:
            await ctx.send("Pas de vote pour le nouveau maire en cours !")
            return

        util = vaction.derniere_utilisation

        # Choix de la cible
        candids = CandidHaro.query.filter_by(
            type=CandidHaroType.candidature).all()
        candidats = [candid.joueur.nom for candid in candids]
        pseudo_bc = _BaseCiblageForVote(
            "Pour qui veux-tu voter ? (vote actuel : "
            f"{tools.bold(util.cible.nom if util.cible else 'aucun')})\n"
            f"(candidats : {', '.join(candidats) or 'aucun :pensive:'})")
        cible = await get_cible(ctx, vaction, pseudo_bc, cible)

        # Test si la cible s'est présentée
        cible_ds_candid = CandidHaro.query.filter_by(
            joueur=cible, type=CandidHaroType.candidature).all()
        if not cible_ds_candid:
            mess = await ctx.send(
                f"{cible.nom} ne s'est pas (encore) présenté(e) ! "
                "Si c'est toujours le cas à la fin de l'élection, ton vote "
                "sera compté comme blanc... \n Veux-tu continuer ?")
            if not await tools.yes_no(mess):
                await ctx.send("Compris, mission aborted.")
                return

        if not vaction.is_open:
            # On revérifie, si ça a fermé entre temps !!
            await ctx.send("Le vote pour le nouveau maire a fermé "
                           "entre temps, pas de chance !")
            return

        # Modification en base
        if util.ciblages:  # ancien ciblage
            Ciblage.delete(*util.ciblages)
        Ciblage(utilisation=util, joueur=cible).add()
        util.ts_decision = datetime.datetime.now()
        util.etat = UtilEtat.remplie
        util.update()

        async with ctx.typing():
            # Écriture dans sheet Données brutes
            export_vote(Vote.maire, util)

        await ctx.send(
            f"Vote pour {tools.bold(cible.nom)} bien pris en compte.\n" +
            tools.ital("Tu peux modifier ton vote autant "
                       "que nécessaire avant sa fermeture."))
Beispiel #12
0
                return

        # Modification en base
        if util.ciblages:  # ancien ciblages
            Ciblage.delete(*util.ciblages)
        for bc, cible in cibles.items():
            cib = Ciblage(utilisation=util, base=bc)
            cib.valeur = cible  # affecte le bon attribut selon le bc.type
        util.ts_decision = datetime.datetime.now()
        util.etat = UtilEtat.remplie
        util.update()

        async with ctx.typing():
            # Écriture dans sheet Données brutes
            export_vote(None, util)

        # Conséquences si action instantanée
        if action.base.instant:
            await gestion_actions.close_action(action)

            await ctx.send(
                tools.ital(f"[Allô {config.Role.mj.mention}, "
                           "conséquence instantanée ici !]"))

        else:
            await ctx.send(
                f"Action « {tools.bold(action.decision)} » bien prise "
                f"en compte pour {tools.code(action.base.slug)}.\n" +
                tools.ital("Tu peux modifier ta décision autant que "
                           "nécessaire avant la fin du créneau."))
Beispiel #13
0
    await tools.sleep(chan, 5)

    await chan.send(
        "Avant toute chose, finalisons ton inscription !\nD'abord, un "
        "point règles nécessaire :\n\n" + tools.quote_bloc(
            "En t'inscrivant au Loup-Garou de la Rez, tu garantis vouloir "
            "participer à cette édition et t'engages à respecter les "
            "règles du jeu. Si tu venais à entraver le bon déroulement de "
            "la partie pour une raison ou une autre, les MJ "
            "s'autorisent à te mute ou t'expulser du Discord sans préavis."))

    await tools.sleep(chan, 5)

    message = await chan.send("C'est bon pour toi ?\n" + tools.ital(
        "(Le bot te demandera souvent confirmation, en t'affichant "
        "deux réactions comme ceci. Clique sur ✅ si ça te va, sur "
        "❎ sinon. Tu peux aussi répondre (oui, non, ...) par écrit.)"))
    if not await tools.yes_no(message):
        await chan.send("Pas de soucis. Si tu changes d'avis ou que c'est un "
                        f"missclick, appelle un MJ aled ({tools.code('@MJ')})."
                        )
        return

    await chan.send(
        "Parfait. Je vais d'abord avoir besoin de ton (vrai) prénom, "
        "celui par lequel on t'appelle au quotidien. Attention, tout "
        "troll sera foudracanonné™ !")

    def check_chan(m):
        # Message de l'utilisateur dans son channel perso
        return m.channel == chan and m.author != config.bot.user
Beispiel #14
0
async def _on_command_error(bot, ctx, exc):
    if ctx.guild != config.guild:  # Mauvais serveur
        return

    if isinstance(exc, commands.CommandInvokeError):
        if isinstance(exc.original, tools.CommandExit):  # STOP envoyé
            await ctx.send(str(exc.original) or "Mission aborted.")
            return

        if isinstance(
                exc.original,  # Erreur BDD
            (bdd.SQLAlchemyError, bdd.DriverOperationalError)):
            try:
                config.session.rollback()  # On rollback la session
                await tools.log("Rollback session")
            except ready_check.NotReadyError:
                pass

        # Dans tous les cas (sauf STOP), si erreur à l'exécution
        prefixe = ("Oups ! Un problème est survenu à l'exécution de "
                   "la commande  :grimacing: :")

        if ctx.message.webhook_id or ctx.author.top_role >= config.Role.mj:
            # MJ / webhook : affiche le traceback complet
            e = traceback.format_exception(type(exc.original), exc.original,
                                           exc.original.__traceback__)
            await tools.send_code_blocs(ctx, "".join(e), prefixe=prefixe)
        else:
            # Pas MJ : exception seulement
            await ctx.send(f"{prefixe}\n{tools.mention_MJ(ctx)} ALED – " +
                           tools.ital(_showexc(exc.original)))

    elif isinstance(exc, commands.CommandNotFound):
        await ctx.send(
            f"Hum, je ne connais pas cette commande  :thinking:\n"
            f"Utilise {tools.code('!help')} pour voir la liste des commandes.")

    elif isinstance(exc, commands.DisabledCommand):
        await ctx.send("Cette commande est désactivée. Pas de chance !")

    elif isinstance(exc, (commands.ConversionError, commands.UserInputError)):
        await ctx.send(
            f"Hmm, ce n'est pas comme ça qu'on utilise cette commande ! "
            f"({tools.code(_showexc(exc))})\n*Tape "
            f"`!help {ctx.invoked_with}` pour plus d'informations.*")
        # ctx.message.content = f"!help {ctx.command.name}"
        # ctx = await bot.get_context(ctx.message)
        # await ctx.reinvoke()

    elif isinstance(exc, commands.CheckAnyFailure):
        # Normalement raise que par @tools.mjs_only
        await ctx.send(
            "Hé ho toi, cette commande est réservée aux MJs !  :angry:")

    elif isinstance(exc, commands.MissingAnyRole):
        # Normalement raise que par @tools.joueurs_only
        await ctx.send("Cette commande est réservée aux joueurs ! "
                       "(parce qu'ils doivent être inscrits en base, toussa) "
                       f"({tools.code('!doas')} est là en cas de besoin)")

    elif isinstance(exc, commands.MissingRole):
        # Normalement raise que par @tools.vivants_only
        await ctx.send(
            "Désolé, cette commande est réservée aux joueurs en vie !")

    elif isinstance(exc, one_command.AlreadyInCommand):
        if ctx.command.name in ["addIA", "modifIA"]:
            # addIA / modifIA : droit d'enregistrer les commandes, donc chut
            return
        await ctx.send(f"Impossible d'utiliser une commande pendant "
                       "un processus ! (vote...)\n"
                       f"Envoie {tools.code(config.stop_keywords[0])} "
                       "pour arrêter le processus.")

    elif isinstance(exc, commands.CheckFailure):
        # Autre check non vérifié
        await ctx.send(f"Tiens, il semblerait que cette commande ne puisse "
                       f"pas être exécutée ! {tools.mention_MJ(ctx)} ?\n"
                       f"({tools.ital(_showexc(exc))})")

    else:
        await ctx.send(
            f"Oups ! Une erreur inattendue est survenue  :grimacing:\n"
            f"{tools.mention_MJ(ctx)} ALED – {tools.ital(_showexc(exc))}")
Beispiel #15
0
async def modif_joueur(joueur_id, modifs, silent=False):
    """Attribue les modifications demandées au joueur

    Args:
        joueur_id (int): id Discord du joueur concerné.
        modifs (list[.TDBModif]): liste des modifications à apporter.
        silent (bool): si ``True``, ne notifie pas le joueur des
            modifications.

    Returns:
        (list[.TDBModif], str): La liste des modifications appliquées
        et le changelog textuel associé (pour log global).

    Raises:
        ValueError: pas de joueur d'ID ``joueur_id`` en base

    Pour chaque modification dans ``modifs``, applique les conséquences
    adéquates (rôles, nouvelles actions, tâches planifiées...) et
    informe le joueur si ``silent`` vaut ``False``.
    """
    joueur = Joueur.query.get(joueur_id)
    if not joueur:
        raise ValueError(f"!sync : joueur d'ID {joueur_id} introuvable")

    member = joueur.member
    chan = joueur.private_chan

    changelog = (f"\n- {member.display_name} "
                 f"(@{member.name}#{member.discriminator}) :\n")
    notif = ""
    af = ":arrow_forward:"  # Flèche introduisant chaque modif

    if not modifs:
        changelog += f"    [NO MODIFS]\n"
        return [], changelog

    done = []
    for modif in modifs:
        changelog += f"    - {modif.col} : {modif.val}\n"

        if modif.col == "nom":  # Renommage joueur
            await member.edit(nick=modif.val)
            await chan.edit(name=f"{config.private_chan_prefix}{modif.val}")
            if not silent:
                notif += (f"{af} Tu t'appelles maintenant "
                          f"{tools.bold(modif.val)}.\n")

        elif modif.col == "chambre" and not silent:  # Modification chambre
            notif += (f"{af} Tu habites maintenant "
                      f"en chambre {tools.bold(modif.val)}.\n")

        elif modif.col == "statut":
            if modif.val == Statut.vivant:  # Statut = vivant
                await member.add_roles(config.Role.joueur_en_vie)
                await member.remove_roles(config.Role.joueur_mort)
                if not silent:
                    notif += f"{af} Tu es maintenant en vie. EN VIE !!!\n"

            elif modif.val == Statut.mort:  # Statut = mort
                await member.add_roles(config.Role.joueur_mort)
                await member.remove_roles(config.Role.joueur_en_vie)
                if not silent:
                    notif += (f"{af} Tu es malheureusement décédé(e) :cry:\n"
                              "Ça arrive même aux meilleurs, en espérant "
                              "que ta mort ait été belle !\n")
                # Actions à la mort
                for action in joueur.actions_actives:
                    if action.base.trigger_debut == ActionTrigger.mort:
                        await gestion_actions.open_action(action)

            elif modif.val == Statut.MV:  # Statut = MV
                await member.add_roles(config.Role.joueur_en_vie)
                await member.remove_roles(config.Role.joueur_mort)
                if not silent:
                    notif += (
                        f"{af} Oh ! Tu viens d'être réduit(e) au statut de "
                        "mort-vivant... Un MJ viendra te voir très vite, "
                        "si ce n'est déjà fait, mais retient que la partie "
                        "n'est pas finie pour toi !\n")

            elif not silent:  # Statut = autre
                notif += f"{af} Nouveau statut : {tools.bold(modif.val)} !\n"

        elif modif.col == "role":  # Modification rôle
            new_role = modif.val
            for action in joueur.actions_actives:
                if action.base in joueur.role.base_actions:
                    # Suppression anciennes actions de rôle
                    gestion_actions.delete_action(action)

            for base in new_role.base_actions:
                # Ajout et création des tâches si trigger temporel
                action = Action(joueur=joueur,
                                base=base,
                                cooldown=0,
                                charges=base.base_charges)
                gestion_actions.add_action(action)

            if not silent:
                notif += (
                    f"{af} Ton nouveau rôle, si tu l'acceptes : "
                    f"{tools.bold(new_role.nom_complet)} !\nQue ce soit pour "
                    "un jour ou pour le reste de la partie, renseigne-toi en "
                    f"tapant {tools.code(f'!roles {new_role.nom}')}.\n")

        elif modif.col == "camp" and not silent:  # Modification camp
            notif += (f"{af} Tu fais maintenant partie "
                      f"du camp « {tools.bold(modif.val.nom)} ».\n")

        elif modif.col == "votant_village" and not silent:
            if modif.val:  # votant_village = True
                notif += (f"{af} Tu peux maintenant participer "
                          "aux votes du village !\n")
            else:  # votant_village = False
                notif += (f"{af} Tu ne peux maintenant plus participer "
                          "aux votes du village.\n")

        elif modif.col == "votant_loups" and not silent:
            if modif.val:  # votant_loups = True
                notif += (f"{af} Tu peux maintenant participer "
                          "aux votes des loups ! Amuse-toi bien :wolf:\n")
            else:  # votant_loups = False
                notif += (f"{af} Tu ne peux maintenant plus participer "
                          "aux votes des loups.\n")

        elif modif.col == "role_actif" and not silent:
            if modif.val:  # role_actif = True
                notif += (f"{af} Tu peux maintenant utiliser tes pouvoirs !\n")
            else:  # role_actif = False
                notif += (f"{af} Tu ne peux maintenant plus utiliser "
                          "aucun pouvoir.\n")

        setattr(joueur, modif.col, modif.val)
        # Dans tous les cas, on modifie en base
        # (après, pour pouvoir accéder aux vieux attribus plus haut)
        done.append(modif)

    if not silent:
        await chan.send(f":zap: {member.mention} Une action divine vient "
                        f"de modifier ton existence ! :zap:\n\n{notif}\n" +
                        tools.ital(":warning: Si tu penses qu'il y a erreur, "
                                   "appelle un MJ au plus vite !"))

    return done, changelog