Rapptz / discord-ext-menus

MIT License
234 stars 87 forks source link

Add support for buttons checks #15

Closed anokhovd closed 4 years ago

anokhovd commented 4 years ago

This PR adds support for buttons checks. Usage of checks is same as in discord.ext.commands except that predicate take in 2 parameters: menu and payload. Now everyone can use menu session by default. If you need old functionality, you need to add check menus.check_any(menus.is_owner(), menus.is_author()) to all menu buttons.

Some examples:

    @commands.command()
    async def survey(self, ctx, *, question: str):
        """ Command that creates a simple survey with 2 choices. """
        await Survey().promt(ctx, question)

def not_voted():  # checks if user already voted in poll
    def predicate(menu, payload):
        user = menu.bot.get_user(payload.user_id)
        if user.id not in menu.voted:
            menu.voted.append(payload.user_id)
            return True
        return False

    return menus.check(predicate)

class Survey(menus.Menu):
    def __init__(self):
        super().__init__()
        self.voted, self.agree, self.disagree = [], [], []
        self.question = None

    async def send_initial_message(self, ctx, channel):
        return await ctx.send(self.question)

    @menus.button(emoji='\N{THUMBS UP SIGN}')
    @not_voted()
    async def do_agree(self, payload):
        self.agree.append(payload.member or self.bot.get_user(payload.user_id))

    @menus.button(emoji='\N{THUMBS DOWN SIGN}')
    @not_voted()
    async def do_disagree(self, payload):
        self.disagree.append(payload.member or self.bot.get_user(payload.user_id))

    async def promt(self, ctx, question):
        self.question = question
        await self.start(ctx, wait=True)
        return await ctx.send(
            f'{ctx.author.mention} poll results: \n'
            f'Agree: {" ".join(i.mention for i in self.agree) if self.agree else "-"}\n'
            f'Disagree: {" ".join(j.mention for j in self.disagree) if self.disagree else "-"}'
        )
    @commands.command()
    async def form(self, ctx, *, description):
        """ Command that creates the server access form. Users can close the form by themselves """
        acceptor_role = "GET_ACCEPTOR_ROLE_HERE"
        result = await UserForm(acceptor_role).promt(ctx, description)
        if result:
            # If form was accepted 

def has_acceptor_role():  # checks if user has role to accept forms
    def predicate(menu, payload):
        if not payload.member:
            return False
        return menu.acceptor_role in payload.member.roles

    return menus.check(predicate)

class UserForm(menus.Menu):
    def __init__(self, acceptor_role):
        super().__init__()
        self.acceptor_role = acceptor_role
        self.user_description = self.result = None

    async def send_initial_message(self, ctx, channel):
        return await ctx.send(
            self.acceptor_role.mention,
            embed=discord.Embed(
                color=discord.Color.gold(),
                description=f'**{ctx.author.mention} wants to get to the server. Accept or decline?**\n'
                            f'User description: {self.user_description}'
            )
        )

    @menus.button(emoji='\N{THUMBS UP SIGN}')
    @has_acceptor_role()
    async def accept(self, payload):
        self.result = True
        self.stop()

    @menus.button(emoji='\N{THUMBS DOWN SIGN}')
    async def decline(self, payload):
        self.stop()

    async def promt(self, ctx, description):
        self.user_description = description
        await self.start(ctx, wait=True)
        return self.result
Rapptz commented 4 years ago

This feature exists already, albeit with a different interface, called skip_if. This was deliberate because it freed the implementation of complications from checks, asynchronicity, etc when most of the time it's unnecessary (and indeed, I have yet to see anyone ask for it).