def __init__(self, source, **kwargs): EmojiB = namedtuple("EmojiB", "emoji position explain") help_dict_emoji = {'\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f': EmojiB("<:before_fast_check:754948796139569224>", First(0), "Goes to the first page."), '\N{BLACK LEFT-POINTING TRIANGLE}\ufe0f': EmojiB("<:before_check:754948796487565332>", First(1), "Goes to the previous page."), '\N{BLACK RIGHT-POINTING TRIANGLE}\ufe0f': EmojiB("<:next_check:754948796361736213>", Last(1), "Goes to the next page."), '\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f': EmojiB("<:next_fast_check:754948796391227442>", Last(2), "Goes to the last page."), '\N{BLACK SQUARE FOR STOP}\ufe0f': EmojiB("<:stop_check:754948796365930517>", Last(0), "Remove this message."), '<:information_pp:754948796454010900>': EmojiB("<:information_pp:754948796454010900>", Last(4), "Shows this infomation message.")} super().__init__(source, dict_emoji=help_dict_emoji, **kwargs)
class MyPagesMenu(MenuPages, inherit_buttons=False): def __init__(self, source, **kwargs): super().__init__(source, clear_reactions_after=True, timeout=120.0, **kwargs) def _skip_double_triangle_buttons(self): max_pages = self._source.get_max_pages() if max_pages is None: return True return max_pages <= 2 @button(cfg['EMOJIS'].get( 'FIRST_PAGE', '\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f'), position=First(0), skip_if=_skip_double_triangle_buttons) async def go_to_first_page(self, payload): """go to the first page""" await self.show_page(0) @button(cfg['EMOJIS'].get('PREVIOUS_PAGE', '\N{BLACK LEFT-POINTING TRIANGLE}\ufe0f'), position=First(1)) async def go_to_previous_page(self, payload): """go to the previous page""" await self.show_checked_page(self.current_page - 1) @button(cfg['EMOJIS'].get('NEXT_PAGE', '\N{BLACK RIGHT-POINTING TRIANGLE}\ufe0f'), position=Last(1)) async def go_to_next_page(self, payload): """go to the next page""" await self.show_checked_page(self.current_page + 1) @button(cfg['EMOJIS'].get( 'LAST_PAGE', '\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f'), position=Last(2), skip_if=_skip_double_triangle_buttons) async def go_to_last_page(self, payload): """go to the last page""" # The call here is safe because it's guarded by skip_if await self.show_page(self._source.get_max_pages() - 1) @button(cfg['EMOJIS'].get('STOP_PAGES', '\N{BLACK SQUARE FOR STOP}\ufe0f'), position=Last(0)) async def stop_pages(self, payload): """stops the pagination session.""" self.stop()
class BaseMenu(menus.views.ViewMenuPages): def __init__(self, source, **kwargs): # to not allow spamming of certain buttons self.lock = asyncio.Lock() super().__init__(source, **kwargs) self.default_emojis = ( "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f", "\N{BLACK LEFT-POINTING TRIANGLE}\ufe0f", "\N{BLACK RIGHT-POINTING TRIANGLE}\ufe0f", "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f", "\N{BLACK SQUARE FOR STOP}\ufe0f") for emoji in self.buttons: if emoji.name not in self.default_emojis: continue self.remove_button(emoji) # buttons for pagination @button(DRIGHT_POINT, position=First(0), skip_if=menus.MenuPages._skip_double_triangle_buttons) async def go_to_first_page(self, payload): """go to the first page""" await self.show_page(0) @button(LEFT_POINT, position=First(1)) async def go_to_previous_page(self, payload): """go to the previous page""" await self.show_checked_page(self.current_page - 1) @button(RIGHT_POINT, position=Last(0)) async def go_to_next_page(self, payload): """go to the next page""" await self.show_checked_page(self.current_page + 1) @button(DLEFT_POINT, position=Last(1), skip_if=menus.MenuPages._skip_double_triangle_buttons) async def go_to_last_page(self, payload): """go to the last page""" # The call here is safe because it's guarded by skip_if await self.show_page(self._source.get_max_pages() - 1) @button(STOP, position=Last(2)) async def stop_pages(self, payload): """stops the pagination session.""" self.stop()
def __init__(self, source, dict_emoji=None, **kwargs): super().__init__(source, delete_message_after=kwargs.pop( 'delete_message_after', True), **kwargs) self.info = False # Remind me to redo this dumb code EmojiB = namedtuple("EmojiB", "emoji position explain") def_dict_emoji = { '\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f': EmojiB("<:before_fast_check:754948796139569224>", First(0), "Goes to the first page."), '\N{BLACK LEFT-POINTING TRIANGLE}\ufe0f': EmojiB("<:before_check:754948796487565332>", First(1), "Goes to the previous page."), '\N{BLACK RIGHT-POINTING TRIANGLE}\ufe0f': EmojiB("<:next_check:754948796361736213>", Last(1), "Goes to the next page."), '\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f': EmojiB("<:next_fast_check:754948796391227442>", Last(2), "Goes to the last page."), '\N{BLACK SQUARE FOR STOP}\ufe0f': EmojiB("<:stop_check:754948796365930517>", Last(0), "Remove this message.") } self.dict_emoji = dict_emoji or def_dict_emoji for emoji in self.buttons: callback = self.buttons[emoji].action if emoji.name not in self.dict_emoji: continue new_but = self.dict_emoji[emoji.name] new_button = Button(new_but.emoji, callback, position=new_but.position) del self.dict_emoji[emoji.name] self.dict_emoji[new_but.emoji] = new_but self.add_button(new_button) self.remove_button(emoji)
class Confirm(Menu): class Buttons: yes = b"\xE2\x9C\x85".decode("utf8") # ✅ :white_check_mark: no = b"\xE2\x9D\x8C".decode("utf8") # ❌ :x: def __init__(self, message, *args, **kwargs): self.message = message super().__init__(*args, **kwargs) @button(Buttons.yes, position=First(0)) async def choice_yes(self, payload: RawReactionActionEvent): pass @button(Buttons.yes, position=Last(0)) async def choice_yes(self, payload: RawReactionActionEvent): pass
class AuthorOnlyPagedMenu(BlockingPagedMenu, inherit_buttons=False): """ Menu that allows react control only for the message owner or admins. """ bot: Bot ctx: Context message: Message author: MemberLike = None def __init__(self, source, **kwargs): super().__init__(source, **kwargs) async def start(self, ctx: Context, *, channel=None, wait=False): assert (hasattr(ctx, "author") and ctx.author ), "ctx.author does not exists or is falsy, that's unexpected" self.author = ctx.author await super().start(ctx, channel=channel, wait=wait) @staticmethod def _is_add_event(payload: discord.RawReactionActionEvent) -> bool: return hasattr(payload, "event_type") and payload.event_type == "REACTION_ADD" def _is_author_or_admin(self, payload: discord.RawReactionActionEvent) -> bool: return hasattr(payload, "user_id") and (payload.user_id == self.author.id or payload.user_id in config.admin_ids) async def _try_remove_reaction(self, payload: discord.RawReactionActionEvent): if self.ctx.message.guild and self.ctx.message.guild.me.guild_permissions.manage_messages: # try removing the reaction afterwards try: await self.message.remove_reaction(payload.emoji, payload.member) except (discord.DiscordException, AttributeError): pass async def _checks(self, payload: discord.RawReactionActionEvent, remove_react_after=True) -> bool: """Checks it's reaction ADD event and message ownership. Additionally allows for the users react removal. """ if self._is_add_event(payload): if remove_react_after: asyncio.ensure_future(self._try_remove_reaction(payload), loop=self.bot.loop) if self._is_author_or_admin(payload): return True return False def _check_fast_forward(self): return self._source.get_max_pages() < 3 @button("\N{BLACK LEFT-POINTING TRIANGLE}\ufe0f", position=First(1)) async def go_to_previous_page(self, payload): """go to the previous page""" if await self._checks(payload): await self.show_checked_page(self.current_page - 1) @button("\N{BLACK RIGHT-POINTING TRIANGLE}\ufe0f", position=Last(0)) async def go_to_next_page(self, payload): """go to the next page""" if await self._checks(payload): await self.show_checked_page(self.current_page + 1) @button( "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f", position=First(0), skip_if=_check_fast_forward, ) async def go_to_first_page(self, payload): """go to the first page""" if await self._checks(payload): await self.show_page(0) @button( "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f", position=Last(1), skip_if=_check_fast_forward, ) async def go_to_last_page(self, payload): """go to the last page""" # The call here is safe because it's guarded by skip_if if await self._checks(payload): await self.show_page(self._source.get_max_pages() - 1) @button("\N{BLACK SQUARE FOR STOP}\ufe0f", position=Last(2)) async def stop_pages(self, payload): """stops the pagination session.""" if await self._checks(payload): self.stop()
class RoboPages(menus.MenuPages, inherit_buttons=False): def __init__(self, source, *args, **kwargs): super().__init__(source=source, check_embeds=True, *args, **kwargs) self.input_lock = asyncio.Lock() async def finalize(self, timed_out): try: if timed_out: await self.message.clear_reactions() else: await self.message.delete() except discord.HTTPException: pass def _skip_when(self): return self.source.get_max_pages() <= 2 def _skip_when_short(self): return self.source.get_max_pages() <= 1 # Disabling because discord-ext-menus paginate on both reaction add and remove. # async def remove_reaction(self, emoji, payload): # user = self.bot.get_user(payload.user_id) # try: # await self.message.remove_reaction(emoji, user) # except discord.Forbidden: # pass @menus.button('<:first:855373614642888714>', position=First(0), skip_if=_skip_when) async def rewind(self, payload): """Goes to first page.""" await self.show_page(0) @menus.button('<:previous:855373614299086859>', position=First(1), skip_if=_skip_when_short) async def back(self, payload): """Goes to the previous page.""" await self.show_checked_page(self.current_page - 1) @menus.button('<:stop:855373614618116097>', position=First(2)) async def stop_menu(self, payload): """Removes this message.""" self.stop() @menus.button('<:next:855373615012380692>', position=Last(0), skip_if=_skip_when_short) async def forward(self, payload): """Goes to the next page.""" await self.show_checked_page(self.current_page + 1) @menus.button('<:last:855373614907129876>', position=Last(1), skip_if=_skip_when) async def fastforward(self, payload): """Goes to the last page.""" await self.show_page(self._source.get_max_pages() - 1) @menus.button('<:info:855373614625587241>', position=Last(2)) async def show_information_page(self, payload): """Shows this message.""" embed = discord.Embed(title='Paginator Help', description=' Hello! Welcome to the Help Page.', colour=0xce0037) messages = [] for (emoji, button) in self.buttons.items(): messages.append(f'{emoji}: {button.action.__doc__}') embed.add_field(name='What are these reactions for?', value='\n'.join(messages), inline=False) embed.set_footer(text=f'We were on page {self.current_page + 1}.') await self.message.edit(content=None, embed=embed) async def go_back_to_current_page(): await asyncio.sleep(15.0) await self.show_page(self.current_page) self.bot.loop.create_task(go_back_to_current_page()) @menus.button('<:number:855414332300591146>', position=Last(3), lock=False, skip_if=_skip_when) async def numbered_page(self, payload): """Lets you go to a page by typing its number.""" if self.input_lock.locked(): return async with self.input_lock: channel = self.message.channel author_id = payload.user_id to_delete = [] to_delete.append(await channel.send('What page do you want to go?')) def check(msg: discord.Message): return msg.author.id == author_id and channel == msg.channel and msg.content.isdigit( ) try: msg = await self.bot.wait_for('message', check=check, timeout=20.0) except asyncio.TimeoutError: to_delete.append(await channel.send('Took too long.')) await asyncio.sleep(5.0) else: page = int(msg.content) to_delete.append(msg) await self.show_checked_page(page - 1) try: await channel.delete_messages(to_delete) except Exception: pass async def send_initial_message(self, ctx, channel): # Have to do this because apparently setting inherit_buttons to False # didn't do anything. reactions = { '\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f', '\N{BLACK LEFT-POINTING TRIANGLE}\ufe0f', '\N{BLACK RIGHT-POINTING TRIANGLE}\ufe0f', '\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\ufe0f', '\N{BLACK SQUARE FOR STOP}\ufe0f' } for reaction in reactions: try: self.remove_button(reaction) except discord.HTTPException: pass return await super().send_initial_message(ctx, channel)