diff --git a/src/utils.py b/src/utils.py index d4a03f5..add5dc5 100644 --- a/src/utils.py +++ b/src/utils.py @@ -3,8 +3,10 @@ import aiohttp import logging import async_timeout +from typing import Callable -from discord import Interaction, Embed, Colour +from discord import Interaction, Embed, Colour, ButtonStyle, Button +from discord.ui import View, button log = logging.getLogger(__name__) @@ -53,6 +55,111 @@ class FollowupIcons: assigned = "https://img.icons8.com/fluency-systems-filled/48/4598DA/hashtag-large.png" +class PaginationView(View): + def __init__( + self, inter: Interaction, embed: Embed, getdata: Callable, + formatdata: Callable, maxpage: int, initpage: int=1 + ): + self.inter = inter + self.embed = embed + self.getdata = getdata + self.formatdata = formatdata + self.maxpage = maxpage + self.index = initpage + super().__init__(timeout=100) + + async def check_user_is_author(self, inter: Interaction) -> bool: + """Ensure the user is the author of the original command.""" + + if inter.user == self.inter.user: + return True + + await inter.response.defer() + await ( + Followup(None, "Only the author can interact with this.") + .error() + .send(inter, ephemeral=True) + ) + return False + + async def on_timeout(self): + """Erase the controls on timeout.""" + + message = await self.inter.original_response() + await message.edit(view=None) + + @staticmethod + def calc_total_pages(results: int, max_pagesize: int) -> int: + result = ((results - 1) // max_pagesize) + 1 + log.debug("total pages calculated: %s", result) + return result + + @button(emoji="◀️", style=ButtonStyle.blurple) + async def backward(self, inter: Interaction, button: Button): + self.index -= 1 + await inter.response.defer() + self.inter = inter + await self.navigate() + + @button(emoji="▶️", style=ButtonStyle.blurple) + async def forward(self, inter: Interaction, button: Button): + self.index += 1 + await inter.response.defer() + self.inter = inter + await self.navigate() + + @button(emoji="⏭️", style=ButtonStyle.blurple) + async def start_or_end(self, inter: Interaction, button: Button): + if self.index <= self.maxpage // 2: + self.index = self.maxpage + else: + self.index = 1 + + await inter.response.defer() + self.inter = inter + await self.navigate() + + async def navigate(self): + log.debug("navigating to page: %s", self.index) + + self.update_buttons() + paged_embed = await self.create_paged_embed() + await self.inter.edit_original_response(embed=paged_embed, view=self) + + async def create_paged_embed(self) -> Embed: + embed = self.embed.copy() + data = await self.getdata(self.index) + + for item in data: + key, value = self.formatdata(item) + embed.add_field(name=key, value=value, inline=False) + + if self.maxpage != 1: + embed.set_footer(text=f"Page {self.index}/{self.maxpage}") + + return embed + + def update_buttons(self): + if self.index >= self.maxpage: + self.children[2].emoji = "⏮️" + else: + self.children[2].emoji = "⏭️" + + self.children[0].disabled = self.index == 1 + self.children[1].disabled = self.index == self.maxpage + + async def send(self): + embed = await self.create_paged_embed() + + if self.maxpage == 1: + await self.inter.edit_original_response(embed=embed) + return + + self.update_buttons() + await self.inter.edit_original_response(embed=embed, view=self) + + + class Followup: """Wrapper for a discord embed to follow up an interaction.""" @@ -66,10 +173,10 @@ class Followup: description=description ) - async def send(self, inter: Interaction, message: str = None): + async def send(self, inter: Interaction, message: str = None, ephemeral: bool = False): """""" - await inter.followup.send(content=message, embed=self._embed) + await inter.followup.send(content=message, embed=self._embed, ephemeral=ephemeral) def fields(self, inline: bool = False, **fields: dict): """""" @@ -86,6 +193,13 @@ class Followup: return self + def footer(self, text: str, icon_url: str = None): + """""" + + self._embed.set_footer(text=text, icon_url=icon_url) + + return self + def error(self): """"""