feat(command|help): finish help command

This commit is contained in:
Romain J 2020-01-05 01:01:06 +01:00
commit 248228408d
10 changed files with 434 additions and 205 deletions

View file

@ -1,10 +1,21 @@
"""
Based on https://github.com/Rapptz/RoboDanny/blob/3ec71c4c4031f868caff3027d71aecdebc3c5cec/cogs/utils/paginator.py
Adapted by Romain J.
"""
import asyncio
import discord
from discord.ext import commands
from discord.ext.commands import Paginator as CommandPaginator
class CannotPaginate(Exception):
pass
class Pages:
"""Implements a paginator that queries the user for the
pagination interface.
@ -30,28 +41,35 @@ class Pages:
permissions: discord.Permissions
Our permissions for the channel.
"""
def __init__(self, ctx, *, entries, per_page=12, show_entry_count=True):
def __init__(self, ctx, *, entries, per_page=12, show_entry_count=True,
embed_color=discord.Color.blurple(), title=None,
thumbnail=None, footericon=None, footertext=None, author=None,
delete_after=None):
self.bot = ctx.bot
self.entries = entries
self.message = ctx.message
self.channel = ctx.channel
self.author = ctx.author
self.author = author if author else ctx.author
self.thumbnail = thumbnail
self.footericon = footericon
self.footertext = footertext
self.title = title
self.delete_after = delete_after
self.per_page = per_page
pages, left_over = divmod(len(self.entries), self.per_page)
if left_over:
pages += 1
self.maximum_pages = pages
self.embed = discord.Embed(colour=discord.Colour.blurple())
self.embed = discord.Embed(colour=embed_color)
self.paginating = len(entries) > per_page
self.show_entry_count = show_entry_count
self.reaction_emojis = [
('\N{BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', self.first_page),
('\U000023ee\U0000fe0f', self.first_page),
('\N{BLACK LEFT-POINTING TRIANGLE}', self.previous_page),
('\U000023f9', self.stop_pages),
('\N{BLACK RIGHT-POINTING TRIANGLE}', self.next_page),
('\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', self.last_page),
('\N{INPUT SYMBOL FOR NUMBERS}', self.numbered_page ),
('\N{BLACK SQUARE FOR STOP}', self.stop_pages),
('\N{INFORMATION SOURCE}', self.show_help),
('\U000023ed\U0000fe0f', self.last_page)
]
if ctx.guild is not None:
@ -60,18 +78,24 @@ class Pages:
self.permissions = self.channel.permissions_for(ctx.bot.user)
if not self.permissions.embed_links:
raise CannotPaginate('Bot does not have embed links permission.')
raise commands.BotMissingPermissions(
'I do not have permissions to : Embed links.'
)
if not self.permissions.send_messages:
raise CannotPaginate('Bot cannot send messages.')
raise commands.BotMissingPermissions('Bot cannot send messages.')
if self.paginating:
# verify we can actually use the pagination session
if not self.permissions.add_reactions:
raise CannotPaginate('Bot does not have add reactions permission.')
raise commands.BotMissingPermissions(
'I do not have permissions to : Add Reactions.'
)
if not self.permissions.read_message_history:
raise CannotPaginate('Bot does not have Read Message History permission.')
raise commands.BotMissingPermissions(
'I do not have permissions to : Read Message History.'
)
def get_page(self, page):
base = (page - 1) * self.per_page
@ -86,22 +110,25 @@ class Pages:
def prepare_embed(self, entries, page, *, first=False):
p = []
for index, entry in enumerate(entries, 1 + ((page - 1) * self.per_page)):
p.append(f'{index}. {entry}')
for index, entry in enumerate(entries,
1 + ((page - 1) * self.per_page)):
p.append(f'`{index}.` {entry}')
if self.maximum_pages > 1:
if self.show_entry_count:
text = f'Page {page}/{self.maximum_pages} ({len(self.entries)} entries)'
text = f'Showing page {page}/{self.maximum_pages} ({len(self.entries)} entries)'
else:
text = f'Page {page}/{self.maximum_pages}'
text = f'Showing page {page}/{self.maximum_pages}'
self.embed.set_footer(text=text)
if self.paginating and first:
p.append('')
p.append('Confused? React with \N{INFORMATION SOURCE} for more info.')
self.embed.description = '\n'.join(p)
self.embed.title = self.title or discord.Embed.Empty
self.embed.set_author(icon_url=self.author.avatar_url,
name=str(self.author))
async def show_page(self, page, *, first=False):
self.current_page = page
@ -153,7 +180,8 @@ class Pages:
async def numbered_page(self):
"""lets you type a page number to go to"""
to_delete = []
to_delete.append(await self.channel.send('What page do you want to go to?'))
to_delete.append(
await self.channel.send('What page do you want to go to?'))
def message_check(m):
return m.author == self.author and \
@ -161,7 +189,11 @@ class Pages:
m.content.isdigit()
try:
msg = await self.bot.wait_for('message', check=message_check, timeout=30.0)
msg = await self.bot.wait_for(
'message',
check=message_check,
timeout=30.0
)
except asyncio.TimeoutError:
to_delete.append(await self.channel.send('Took too long.'))
await asyncio.sleep(5)
@ -171,7 +203,8 @@ class Pages:
if page != 0 and page <= self.maximum_pages:
await self.show_page(page)
else:
to_delete.append(await self.channel.send(f'Invalid page given. ({page}/{self.maximum_pages})'))
to_delete.append(await self.channel.send(
f'Invalid page given. ({page}/{self.maximum_pages})'))
await asyncio.sleep(5)
try:
@ -182,8 +215,9 @@ class Pages:
async def show_help(self):
"""shows this message"""
messages = ['Welcome to the interactive paginator!\n']
messages.append('This interactively allows you to see pages of text by navigating with ' \
'reactions. They are as follows:\n')
messages.append(
'This interactively allows you to see pages of text by navigating with ' \
'reactions. They are as follows:\n')
for (emoji, func) in self.reaction_emojis:
messages.append(f'{emoji} {func.__doc__}')
@ -191,7 +225,8 @@ class Pages:
embed = self.embed.copy()
embed.clear_fields()
embed.description = '\n'.join(messages)
embed.set_footer(text=f'We were on page {self.current_page} before this message.')
embed.set_footer(
text=f'We were on page {self.current_page} before this message.')
await self.message.edit(content=None, embed=embed)
async def go_back_to_current_page():
@ -205,16 +240,15 @@ class Pages:
await self.message.delete()
self.paginating = False
def react_check(self, payload):
if payload.user_id != self.author.id:
def react_check(self, reaction, user):
if user is None or user.id != self.author.id:
return False
if payload.message_id != self.message.id:
if reaction.message.id != self.message.id:
return False
to_check = str(payload.emoji)
for (emoji, func) in self.reaction_emojis:
if to_check == emoji:
if reaction.emoji == emoji:
self.match = func
return True
return False
@ -230,52 +264,72 @@ class Pages:
while self.paginating:
try:
payload = await self.bot.wait_for('raw_reaction_add', check=self.react_check, timeout=120.0)
reaction, user = await self.bot.wait_for(
'reaction_add',
check=self.react_check,
timeout=self.delete_after
)
except asyncio.TimeoutError:
self.paginating = False
try:
await self.message.clear_reactions()
await self.message.delete()
except:
pass
finally:
break
try:
await self.message.remove_reaction(payload.emoji, discord.Object(id=payload.user_id))
await self.message.remove_reaction(reaction, user)
except:
pass # can't remove it so don't bother doing so
pass # can't remove it so don't bother doing so
await self.match()
class FieldPages(Pages):
"""Similar to Pages except entries should be a list of
tuples having (key, value) to show as embed fields instead.
"""
def __init__(self, ctx, *, entries, per_page=12, show_entry_count=True,
title, thumbnail, footericon, footertext,
embed_color=discord.Color.blurple()):
super().__init__(ctx, entries=entries, per_page=per_page,
show_entry_count=show_entry_count, title=title,
thumbnail=thumbnail, footericon=footericon,
footertext=footertext, embed_color=embed_color)
def prepare_embed(self, entries, page, *, first=False):
self.embed.clear_fields()
self.embed.description = discord.Embed.Empty
for key, value in entries:
self.embed.add_field(name=key, value=value, inline=False)
self.embed.title = self.title
if self.maximum_pages > 1:
if self.show_entry_count:
text = f'Page {page}/{self.maximum_pages} ({len(self.entries)} entries)'
text = f' [{page}/{self.maximum_pages}]'
else:
text = f'Page {page}/{self.maximum_pages}'
text = f' [{page}/{self.maximum_pages}]'
self.embed.title = self.title + text
self.embed.set_footer(icon_url=self.footericon, text=self.footertext)
self.embed.set_thumbnail(url=self.thumbnail)
self.embed.set_footer(text=text)
class TextPages(Pages):
"""Uses a commands.Paginator internally to paginate some text."""
def __init__(self, ctx, text, *, prefix='```', suffix='```', max_size=2000):
paginator = CommandPaginator(prefix=prefix, suffix=suffix, max_size=max_size - 200)
def __init__(self, ctx, text, *, prefix='```', suffix='```',
max_size=2000):
paginator = CommandPaginator(prefix=prefix, suffix=suffix,
max_size=max_size - 200)
for line in text.split('\n'):
paginator.add_line(line)
super().__init__(ctx, entries=paginator.pages, per_page=1, show_entry_count=False)
super().__init__(ctx, entries=paginator.pages, per_page=1,
show_entry_count=False)
def get_page(self, page):
return self.entries[page - 1]
@ -286,4 +340,4 @@ class TextPages(Pages):
def get_content(self, entry, page, *, first=False):
if self.maximum_pages > 1:
return f'{entry}\nPage {page}/{self.maximum_pages}'
return entry
return entry