Skip to content

Commit

Permalink
Add missing menu base (#254)
Browse files Browse the repository at this point in the history
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
TrustyJAID and pre-commit-ci[bot] committed Apr 17, 2024
1 parent b1e8234 commit 18c4632
Showing 1 changed file with 248 additions and 0 deletions.
248 changes: 248 additions & 0 deletions leveler/menus/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
from typing import Any, List, Optional

import discord
from redbot.core import commands
from redbot.vendored.discord.ext import menus


class StopButton(discord.ui.Button):
def __init__(
self,
style: discord.ButtonStyle,
row: Optional[int],
):
super().__init__(style=style, row=row)
self.style = style
self.emoji = "\N{HEAVY MULTIPLICATION X}\N{VARIATION SELECTOR-16}"

async def callback(self, interaction: discord.Interaction):
self.view.stop()
if interaction.message.flags.ephemeral:
await interaction.response.edit_message(view=None)
return
await interaction.message.delete()


class ForwardButton(discord.ui.Button):
def __init__(
self,
style: discord.ButtonStyle,
row: Optional[int],
):
super().__init__(style=style, row=row)
self.style = style
self.emoji = "\N{BLACK RIGHT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}"

async def callback(self, interaction: discord.Interaction):
await self.view.show_checked_page(self.view.current_page + 1, interaction)


class BackButton(discord.ui.Button):
def __init__(
self,
style: discord.ButtonStyle,
row: Optional[int],
):
super().__init__(style=style, row=row)
self.style = style
self.emoji = "\N{BLACK LEFT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}"

async def callback(self, interaction: discord.Interaction):
await self.view.show_checked_page(self.view.current_page - 1, interaction)


class LastItemButton(discord.ui.Button):
def __init__(
self,
style: discord.ButtonStyle,
row: Optional[int],
):
super().__init__(style=style, row=row)
self.style = style
self.emoji = (
"\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\N{VARIATION SELECTOR-16}"
)

async def callback(self, interaction: discord.Interaction):
await self.view.show_page(self.view._source.get_max_pages() - 1, interaction)


class FirstItemButton(discord.ui.Button):
def __init__(
self,
style: discord.ButtonStyle,
row: Optional[int],
):
super().__init__(style=style, row=row)
self.style = style
self.emoji = (
"\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}\N{VARIATION SELECTOR-16}"
)

async def callback(self, interaction: discord.Interaction):
await self.view.show_page(0, interaction)


class _SelectMenu(discord.ui.Select):
def __init__(self, options: List[discord.SelectOption]):
super().__init__(placeholder="Select a Page", min_values=1, max_values=1, options=options)

async def callback(self, interaction: discord.Interaction):
index = int(self.values[0])
await self.view.show_page(index, interaction)


class BaseView(discord.ui.View):
def __init__(
self,
source: menus.PageSource,
clear_reactions_after: bool = True,
delete_message_after: bool = False,
timeout: int = 180,
message: discord.Message = None,
page_start: int = 0,
**kwargs: Any,
) -> None:
super().__init__(
timeout=timeout,
)
self._source = source
self.page_start = page_start
self.current_page = page_start
self.message = message
self.ctx = kwargs.get("ctx", None)
self.forward_button = ForwardButton(discord.ButtonStyle.grey, 0)
self.back_button = BackButton(discord.ButtonStyle.grey, 0)
self.first_item = FirstItemButton(discord.ButtonStyle.grey, 0)
self.last_item = LastItemButton(discord.ButtonStyle.grey, 0)
self.stop_button = StopButton(discord.ButtonStyle.red, 0)
self.add_item(self.stop_button)
self.add_item(self.first_item)
self.add_item(self.back_button)
self.add_item(self.forward_button)
self.add_item(self.last_item)
self.select_menu = self._get_select_menu()
self.add_item(self.select_menu)

@property
def source(self):
return self._source

async def on_timeout(self):
await self.message.edit(view=None)

async def start(self, ctx: commands.Context):
await self.send_initial_message(ctx)

def select_options(self):
return getattr(self.source, "select_options", [])

async def change_source(self, new_source: menus.PageSource):
self.current_page = 0
self._source = new_source
if not self.source.is_paginating():
self.disable_navigation()
page = await self._source.get_page(self.page_start)
return await self._get_kwargs_from_page(page)

def _get_select_menu(self):
# handles modifying the select menu if more than 25 pages are provided
# this will show the previous 12 and next 13 pages in the select menu
# based on the currently displayed page. Once you reach close to the max
# pages it will display the last 25 pages.
if len(self.select_options()) > 25:
minus_diff = None
plus_diff = 25
if 12 < self.current_page < len(self.select_options()) - 25:
minus_diff = self.current_page - 12
plus_diff = self.current_page + 13
elif self.current_page >= len(self.select_options()) - 25:
minus_diff = len(self.select_options()) - 25
plus_diff = None
options = self.select_options()[minus_diff:plus_diff]
else:
options = self.select_options()[:25]
return _SelectMenu(options)

def disable_navigation(self):
self.first_item.disabled = True
self.back_button.disabled = True
self.forward_button.disabled = True
self.last_item.disabled = True

def enable_navigation(self):
self.first_item.disabled = False
self.back_button.disabled = False
self.forward_button.disabled = False
self.last_item.disabled = False

async def send_initial_message(self, ctx: commands.Context):
"""|coro|
The default implementation of :meth:`Menu.send_initial_message`
for the interactive pagination session.
This implementation shows the first page of the source.
"""
self.ctx = ctx
if not self.source.is_paginating():
self.disable_navigation()
page = await self._source.get_page(self.page_start)
kwargs = await self._get_kwargs_from_page(page)
self.message = await ctx.send(**kwargs, view=self)
return self.message

async def _get_kwargs_from_page(self, page):
value = await discord.utils.maybe_coroutine(self._source.format_page, self, page)
if isinstance(value, dict):
return value
elif isinstance(value, str):
return {"content": value, "embed": None}
elif isinstance(value, discord.Embed):
return {"embed": value, "content": None}

async def get_page(self, page_number: int):
if not self.source.is_paginating():
self.disable_navigation()
else:
self.enable_navigation()
page = await self._source.get_page(page_number)
self.current_page = page_number
return await self._get_kwargs_from_page(page)

async def show_page(self, page_number: int, interaction: discord.Interaction):
if not self.source.is_paginating():
self.disable_navigation()
else:
self.enable_navigation()
if len(self.source.select_options) > 25 and self.source.is_paginating():
self.remove_item(self.select_menu)
self.select_menu = self._get_select_menu()
self.add_item(self.select_menu)
page = await self._source.get_page(page_number)
self.current_page = page_number
kwargs = await self._get_kwargs_from_page(page)
await interaction.response.edit_message(**kwargs, view=self)

async def show_checked_page(self, page_number: int, interaction: discord.Interaction) -> None:
max_pages = self._source.get_max_pages()
try:
if max_pages is None:
# If it doesn't give maximum pages, it cannot be checked
await self.show_page(page_number, interaction)
elif page_number >= max_pages:
await self.show_page(0, interaction)
elif page_number < 0:
await self.show_page(max_pages - 1, interaction)
elif max_pages > page_number >= 0:
await self.show_page(page_number, interaction)
except IndexError:
# An error happened that can be handled, so ignore it.
pass

async def interaction_check(self, interaction: discord.Interaction):
"""Just extends the default reaction_check to use owner_ids"""
if interaction.user.id not in (*self.ctx.bot.owner_ids, self.ctx.author.id):
await interaction.response.send_message(
content="You are not authorized to interact with this.", ephemeral=True
)
return False
return True

0 comments on commit 18c4632

Please sign in to comment.