Source code for discord.ext.paginator.paginator

# Base class for defining paginators
from __future__ import annotations
from discord.ext.commands import Context
from discord.ui import View
from discord import Embed, Interaction

from typing import Any, List, Dict, Callable, Type, Union, Optional


[docs]class PaginatorView(View): _paginator: Paginator
[docs] async def on_timeout(self) -> None: await self._paginator.on_end() self.stop() return await super().on_timeout()
[docs]class DefaultView(PaginatorView): def __init__(self, ctx: Context, paginator: Paginator, *, timeout: Optional[float] = 180): self._ctx = ctx self._paginator = paginator super().__init__(timeout=timeout)
[docs]class Paginator(object): """ Base class for defining paginators Parameters ---------- view: Type[:class:`ModifiedView`] View instance which handles editing messages, for built-in paginators such as :class:`ButtonsPaginator`, this arg is invalid. pages: List[Dict[:class:`str`, Any]] An array of pages, i.e messages to send as page. .. note:: **This MUST be a dictionary** .. code-block:: diff # Page content is sent using kwargs # Where page = pages[page_index] + Context.send(**page) embeds: List[Union[:class:`Embed`, List[:class:`Embed`]]] An array of embeds or a 2d array of embeds to send as pages, if pages are provided then these are appended to these values. messages: List[:class:`str`] An array of message contents to send as pages, if pages are provided this overwrite previous content cyclical: :class:`bool` Whether you can traverse from start to end and vise versa once limit reached, if not end/start page are continually returned allow_fast_traverse: :class:`bool` Whether to allow direct traversing from start to end and vise versa current_page: :class:`int` Index of page paginator starts on, uses zero indexing start_page: :class:`int` Index of starting page for paginator, to use this value instead of current page, set current page to an invalid value e.g. ``-1``. This is also used when traversing to start directly. timeout: :class:`int` Timeout for paginator view, defaults to ``180`` author_only: :class:`bool` Whether only the person who invoked the pagination is only allowed to respond edit: :class:`bool` Whether to edit existing view message or generate a new one. """ ctx: Union[Context, Interaction] def __init__( self, view: Type[DefaultView], *, pages: List[Dict[str, Any]] = [], embeds: List[Union[Embed, List[Embed]]] = [], messages: List[str] = [], cyclical: bool = True, allow_fast_traverse: bool = False, ## original_message: Message = None, current_page: int = 0, start_page: int = 0, timeout: Optional[int] = 180, author_only: bool = True, edit: bool = True, ) -> None: if not issubclass(view, PaginatorView): raise ValueError('Invalid view class provided') self.view_cls = view self.allow_fast_traverse = allow_fast_traverse ## self.original_message = original_message self.pages = pages self.current_page = current_page self.start_page = start_page self.cyclical = cyclical self.ctx = None self.timeout = timeout self.author_only = author_only self.edit = edit self._can_traverse = True for i, v in embeds: if i > (len(self.pages) - 1): if not isinstance(v, list): self.pages.append({'embeds': [v]}) else: self.pages.append({'embeds': v}) else: c = self.pages[i].pop('embed', None) if isinstance(v, list): v.append(c) else: v = [c, v] if embeds := self.pages.get('embeds', []): embeds.extend(v) self.pages[i]['embeds'] = embeds else: self.pages['embeds'] = v for i, v in messages: if i > (len(self.pages) - 1): self.pages.append({'content': v}) else: self.pages[i]['content'] = v self.max_page = len(self.pages) - 1 if self.current_page < 0 or self.current_page > self.max_page: self.current_page = self.start_page
[docs] async def on_traverse_forward(self): """ An event called right before forward traversed page is returned """
[docs] async def on_traverse_back(self): """ An event called right before backward traversed page is returned """
[docs] async def on_traverse_start(self): """ An event called right before moving to Paginator.start_page """
[docs] async def on_traverse_end(self): """ An event called right before moving to Paginator.max_page """
[docs] async def on_traverse_to(self): """ An event called before a page is traversed to using :meth:`Paginator.traverse_to` """
[docs] async def on_start(self): """ An event called when pagination starts, directly before message is sent """
[docs] async def on_end(self): """ An event called when pagination ends, directly before message is sent """
[docs] async def traverse_forward(self) -> Dict[str, Any]: """ Moves forward, i.e. next page """ if not self._can_traverse: raise ValueError('Pagination ended') if (self.current_page >= self.max_page and not self.cyclical): return self.pages[self.current_page] elif self.current_page >= self.max_page: self.current_page = 0 else: self.current_page += 1 await self.on_traverse_forward() return self.pages[self.current_page]
[docs] async def traverse_back(self) -> Dict[str, Any]: """ Moves backward, i.e previous page """ if not self._can_traverse: raise ValueError('Pagination ended') if (self.current_page <= 0 and not self.cyclical): return self.pages[self.current_page] elif self.current_page <= 0: self.current_page = self.max_page else: self.current_page -= 1 await self.on_traverse_back() return self.pages[self.current_page]
[docs] async def traverse_start(self) -> Dict[str, Any]: """ Moves to the start, Paginator.start_page """ if not self._can_traverse: raise ValueError('Pagination ended') if not self.allow_fast_traverse: return self.pages[self.current_page] self.current_page = self.start_page await self.on_traverse_start() return self.pages[self.current_page]
[docs] async def traverse_end(self) -> Dict[str, Any]: """ Moves to the end, Paginator.max_page """ if not self._can_traverse: raise ValueError('Pagination ended') if not self.allow_fast_traverse: return self.pages[self.current_page] self.current_page = self.max_page await self.on_traverse_end() return self.pages[self.current_page]
[docs] async def traverse_to(self, page: int) -> Dict[str, Any]: """ Moves to a specific page Parameters ---------- page: :class:`int` Page to traverse to """ if not self._can_traverse: raise ValueError('Pagination ended') if not self.allow_fast_traverse: return self.pages[self.current_page] if page < 0 or page > self.max_page: return self.pages[self.current_page] self.current_page = page await self.on_traverse_to() return self.pages[self.current_page]
[docs] async def end(self) -> None: """ Ends pagination """ if not self._can_traverse: raise ValueError('Pagination already ended') self._can_traverse = False await self.on_end() self.ctx = None
[docs] async def start(self, ctx: Union[Context, Interaction], *, timeout: int = ..., call: Callable[..., Any] = None) -> None: """Starts pagination Parameters ---------- ctx: :class:`Context` Context to pass to view timeout: Optional[:class:`int`] Default timeout call: Callable[..., Any] Function to call rather then :meth:`ctx.send` .. admonition:: Example .. code-block:: py await Paginator.context_start(Context, call=Context.reply) """ assert issubclass(self.view_cls, DefaultView), 'Invalid view cls provided for context_start' self._can_traverse = True self.ctx = ctx if isinstance(ctx, Context): func = call or ctx.send else: func = call or ctx.response.send_message view = self.view_cls(ctx, self, timeout=timeout if timeout != ... else self.timeout) page = self.pages[self.current_page] page['view'] = view await self.on_start() await func(**page)