2021-07-11 03:03:32 +00:00
|
|
|
# © lambda#0987 <lambda@lambda.dance>
|
|
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
2020-05-12 23:55:08 +00:00
|
|
|
|
2018-07-30 05:54:13 +00:00
|
|
|
import asyncio
|
|
|
|
import contextlib
|
|
|
|
import typing
|
|
|
|
|
|
|
|
import discord
|
|
|
|
from discord.ext.commands import Context
|
|
|
|
|
|
|
|
# Copyright © 2016-2017 Pandentia and contributors
|
|
|
|
# https://github.com/Thessia/Liara/blob/75fa11948b8b2ea27842d8815a32e51ef280a999/cogs/utils/paginator.py
|
|
|
|
|
|
|
|
class Paginator:
|
2018-08-11 18:43:50 +00:00
|
|
|
def __init__(self, ctx: Context, pages: typing.Iterable, *, timeout=300, delete_message=False,
|
2018-07-30 05:54:13 +00:00
|
|
|
delete_message_on_timeout=False, text_message=None):
|
|
|
|
|
|
|
|
self.pages = list(pages)
|
|
|
|
self.timeout = timeout
|
2018-08-11 18:43:50 +00:00
|
|
|
self.author = ctx.author
|
2018-07-30 05:54:13 +00:00
|
|
|
self.target = ctx.channel
|
|
|
|
self.delete_msg = delete_message
|
|
|
|
self.delete_msg_timeout = delete_message_on_timeout
|
|
|
|
self.text_message = text_message
|
|
|
|
|
|
|
|
self._stopped = None # we use this later
|
|
|
|
self._embed = None
|
|
|
|
self._message = None
|
|
|
|
self._client = ctx.bot
|
|
|
|
|
|
|
|
self.footer = 'Page {} of {}'
|
|
|
|
self.navigation = {
|
|
|
|
'\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}': self.first_page,
|
|
|
|
'\N{BLACK LEFT-POINTING TRIANGLE}': self.previous_page,
|
|
|
|
'\N{BLACK RIGHT-POINTING TRIANGLE}': self.next_page,
|
|
|
|
'\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}': self.last_page,
|
|
|
|
'\N{BLACK SQUARE FOR STOP}': self.stop
|
|
|
|
}
|
|
|
|
|
|
|
|
self._page = None
|
|
|
|
|
2020-10-01 06:06:43 +00:00
|
|
|
def react_check(self, reaction: discord.RawReactionActionEvent):
|
|
|
|
if reaction.user_id != self.author.id:
|
2018-08-11 18:43:50 +00:00
|
|
|
return False
|
|
|
|
|
2020-10-01 06:06:43 +00:00
|
|
|
if reaction.message_id != self._message.id:
|
2018-08-11 18:43:50 +00:00
|
|
|
return False
|
|
|
|
|
2020-10-01 06:06:43 +00:00
|
|
|
target_emoji = str(reaction.emoji)
|
|
|
|
return bool(discord.utils.find(lambda emoji: target_emoji == emoji, self.navigation))
|
2018-08-11 18:43:50 +00:00
|
|
|
|
2018-07-30 05:54:13 +00:00
|
|
|
async def begin(self):
|
|
|
|
"""Starts pagination"""
|
|
|
|
self._stopped = False
|
|
|
|
self._embed = discord.Embed()
|
|
|
|
await self.first_page()
|
|
|
|
for button in self.navigation:
|
|
|
|
await self._message.add_reaction(button)
|
|
|
|
while not self._stopped:
|
|
|
|
try:
|
2020-10-01 06:06:43 +00:00
|
|
|
reaction: RawReactionActionEvent = await self._client.wait_for(
|
|
|
|
'raw_reaction_add',
|
2018-08-11 18:43:50 +00:00
|
|
|
check=self.react_check,
|
|
|
|
timeout=self.timeout)
|
2018-07-30 05:54:13 +00:00
|
|
|
except asyncio.TimeoutError:
|
|
|
|
await self.stop(delete=self.delete_msg_timeout)
|
|
|
|
continue
|
|
|
|
|
2020-10-01 06:06:43 +00:00
|
|
|
await self.navigation[str(reaction.emoji)]()
|
2018-07-30 05:54:13 +00:00
|
|
|
|
|
|
|
await asyncio.sleep(0.2)
|
|
|
|
with contextlib.suppress(discord.HTTPException):
|
2020-10-01 06:06:43 +00:00
|
|
|
await self._message.remove_reaction(reaction.emoji, discord.Object(reaction.user_id))
|
2018-07-30 05:54:13 +00:00
|
|
|
|
|
|
|
async def stop(self, *, delete=None):
|
|
|
|
"""Aborts pagination."""
|
|
|
|
if delete is None:
|
|
|
|
delete = self.delete_msg
|
|
|
|
|
|
|
|
if delete:
|
2019-08-05 15:39:42 +00:00
|
|
|
with contextlib.suppress(discord.HTTPException):
|
|
|
|
await self._message.delete()
|
2018-07-30 05:54:13 +00:00
|
|
|
else:
|
|
|
|
await self._clear_reactions()
|
|
|
|
self._stopped = True
|
|
|
|
|
|
|
|
async def _clear_reactions(self):
|
|
|
|
try:
|
|
|
|
await self._message.clear_reactions()
|
|
|
|
except discord.Forbidden:
|
|
|
|
for button in self.navigation:
|
2019-08-05 15:39:42 +00:00
|
|
|
with contextlib.suppress(discord.HTTPException):
|
|
|
|
await self._message.remove_reaction(button, self._message.author)
|
|
|
|
except discord.HTTPException:
|
|
|
|
pass
|
2018-07-30 05:54:13 +00:00
|
|
|
|
|
|
|
async def format_page(self):
|
|
|
|
self._embed.description = self.pages[self._page]
|
|
|
|
self._embed.set_footer(text=self.footer.format(self._page + 1, len(self.pages)))
|
|
|
|
|
|
|
|
kwargs = {'embed': self._embed}
|
|
|
|
if self.text_message:
|
|
|
|
kwargs['content'] = self.text_message
|
|
|
|
|
|
|
|
if self._message:
|
|
|
|
await self._message.edit(**kwargs)
|
|
|
|
else:
|
|
|
|
self._message = await self.target.send(**kwargs)
|
|
|
|
|
|
|
|
async def first_page(self):
|
|
|
|
self._page = 0
|
|
|
|
await self.format_page()
|
|
|
|
|
|
|
|
async def next_page(self):
|
|
|
|
self._page += 1
|
|
|
|
if self._page == len(self.pages): # avoid the inevitable IndexError
|
|
|
|
self._page = 0
|
|
|
|
await self.format_page()
|
|
|
|
|
|
|
|
async def previous_page(self):
|
|
|
|
self._page -= 1
|
|
|
|
if self._page < 0: # ditto
|
|
|
|
self._page = len(self.pages) - 1
|
|
|
|
await self.format_page()
|
|
|
|
|
|
|
|
async def last_page(self):
|
|
|
|
self._page = len(self.pages) - 1
|
|
|
|
await self.format_page()
|
|
|
|
|
|
|
|
class ListPaginator(Paginator):
|
|
|
|
def __init__(self, ctx, _list: list, per_page=10, **kwargs):
|
|
|
|
pages = []
|
|
|
|
page = ''
|
|
|
|
c = 0
|
|
|
|
l = len(_list)
|
|
|
|
for i in _list:
|
|
|
|
if c > l:
|
|
|
|
break
|
|
|
|
if c % per_page == 0 and page:
|
|
|
|
pages.append(page.strip())
|
|
|
|
page = ''
|
|
|
|
page += '{}. {}\n'.format(c+1, i)
|
|
|
|
|
|
|
|
c += 1
|
|
|
|
pages.append(page.strip())
|
|
|
|
# shut up, IDEA
|
|
|
|
# noinspection PyArgumentList
|
|
|
|
super().__init__(ctx, pages, **kwargs)
|
|
|
|
self.footer += ' ({} entries)'.format(l)
|