feat(command|help): finish help command

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

View file

@ -65,8 +65,9 @@ class Admin(commands.Cog):
########################################################################### ###########################################################################
@groupExtra(name='say', invoke_without_command=True, category='admin', @groupExtra(name='say', invoke_without_command=True, category='text',
description=Texts('admin_help').get('_say')) description=Texts('admin_help').get('_say'),
short_doc=Texts('admin_help').get('_say__short'))
async def _say(self, ctx: commands.Context, *, content: str): async def _say(self, ctx: commands.Context, *, content: str):
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
try: try:
@ -77,7 +78,8 @@ class Admin(commands.Cog):
await ctx.send(content) await ctx.send(content)
@_say.command(name='edit', @_say.command(name='edit',
description=Texts('admin_help').get('_say_edit')) description=Texts('admin_help').get('_say_edit'),
short_doc=Texts('admin_help').get('_say_edit__short'))
async def _say_edit(self, ctx: commands.Context, message_id: int, *, async def _say_edit(self, ctx: commands.Context, message_id: int, *,
content: str): content: str):
try: try:
@ -95,7 +97,8 @@ class Admin(commands.Cog):
delete_after=5) delete_after=5)
@_say.command(name='to', @_say.command(name='to',
description=Texts('admin_help').get('_say_to')) description=Texts('admin_help').get('_say_to'),
short_doc=Texts('admin_help').get('_say_to__short'))
async def _say_to(self, ctx: commands.Context, async def _say_to(self, ctx: commands.Context,
channel: Union[discord.TextChannel, discord.User], *, channel: Union[discord.TextChannel, discord.User], *,
content): content):
@ -108,8 +111,9 @@ class Admin(commands.Cog):
########################################################################### ###########################################################################
@commandExtra(name='ban', category='admin', @commandExtra(name='ban', category='administration',
description=Texts('admin_help').get('_ban')) description=Texts('admin_help').get('_ban'),
short_doc=Texts('admin_help').get('_ban__short'))
async def _ban(self, ctx: commands.Context, user: discord.Member, *, async def _ban(self, ctx: commands.Context, user: discord.Member, *,
reason=""): reason=""):
try: try:
@ -136,8 +140,9 @@ class Admin(commands.Cog):
########################################################################### ###########################################################################
@commandExtra(name='kick', category='admin', @commandExtra(name='kick', category='administration',
description=Texts('admin_help').get('_kick')) description=Texts('admin_help').get('_kick'),
short_doc=Texts('admin_help').get('_kick__short'))
async def _kick(self, ctx: commands.Context, user: discord.Member, *, async def _kick(self, ctx: commands.Context, user: discord.Member, *,
reason=""): reason=""):
try: try:
@ -164,8 +169,9 @@ class Admin(commands.Cog):
########################################################################### ###########################################################################
@commandExtra(name='clear', category='admin', @commandExtra(name='clear', category='text',
description=Texts('admin_help').get('_clear')) description=Texts('admin_help').get('_clear'),
short_doc=Texts('admin_help').get('_clear__short'))
async def _clear(self, ctx: commands.Context, count: int): async def _clear(self, ctx: commands.Context, count: int):
try: try:
await ctx.message.delete() await ctx.message.delete()
@ -175,14 +181,16 @@ class Admin(commands.Cog):
########################################################################### ###########################################################################
@groupExtra(name='react', category='admin', @groupExtra(name='react', category='text',
description=Texts('admin_help').get('_react')) description=Texts('admin_help').get('_react'),
short_doc=Texts('admin_help').get('_react__short'))
async def _react(self, ctx: commands.Context): async def _react(self, ctx: commands.Context):
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await ctx.send_help('react') await ctx.send_help('react')
@_react.command(name='add', @_react.command(name='add',
description=Texts('admin_help').get('admin._react_add')) description=Texts('admin_help').get('_react_add'),
short_doc=Texts('admin_help').get('_react_add__short'))
async def _react_add(self, ctx: commands.Context, message_id: int, *, async def _react_add(self, ctx: commands.Context, message_id: int, *,
emojis: str): emojis: str):
emojis: list = emojis.split(' ') emojis: list = emojis.split(' ')
@ -199,7 +207,8 @@ class Admin(commands.Cog):
delete_after=5) delete_after=5)
@_react.command(name='clear', @_react.command(name='clear',
description=Texts('admin_help').get('_react_remove')) description=Texts('admin_help').get('_react_remove'),
short_doc=Texts('admin_help').get('_react_remove__short'))
async def _react_remove(self, ctx: commands.Context, message_id: int): async def _react_remove(self, ctx: commands.Context, message_id: int):
try: try:
message: discord.Message = await ctx.channel.fetch_message( message: discord.Message = await ctx.channel.fetch_message(
@ -213,8 +222,9 @@ class Admin(commands.Cog):
########################################################################### ###########################################################################
@groupExtra(name='delete', invoke_without_command=True, @groupExtra(name='delete', invoke_without_command=True,
category='admin', category='text',
description=Texts('admin_help').get('_delete')) description=Texts('admin_help').get('_delete'),
short_doc=Texts('admin_help').get('_delete__short'))
async def _delete(self, ctx: commands.Context, message_id: int): async def _delete(self, ctx: commands.Context, message_id: int):
try: try:
await ctx.message.delete() await ctx.message.delete()
@ -231,7 +241,8 @@ class Admin(commands.Cog):
delete_after=5) delete_after=5)
@_delete.command(name='from', aliases=['to', 'in'], @_delete.command(name='from', aliases=['to', 'in'],
description=Texts('admin_help').get('_delete_from')) description=Texts('admin_help').get('_delete_from'),
short_doc=Texts('admin_help').get('_delete_from__short'))
async def _delete_from(self, ctx: commands.Context, async def _delete_from(self, ctx: commands.Context,
channel: discord.TextChannel, message_id: int): channel: discord.TextChannel, message_id: int):
try: try:
@ -259,7 +270,8 @@ class Admin(commands.Cog):
if member: if member:
warns = self.bot.database.session \ warns = self.bot.database.session \
.query(WarnModel) \ .query(WarnModel) \
.filter(WarnModel.user_id == member.id, WarnModel.created_at > week_ago, .filter(WarnModel.user_id == member.id,
WarnModel.created_at > week_ago,
WarnModel.server_id == ctx.guild.id) \ WarnModel.server_id == ctx.guild.id) \
.order_by(WarnModel.created_at.desc()) .order_by(WarnModel.created_at.desc())
else: else:
@ -287,14 +299,16 @@ class Admin(commands.Cog):
reason): reason):
now = datetime.datetime.now() now = datetime.datetime.now()
warn = WarnModel(server_id=ctx.guild.id, user_id=member.id, reason=reason, warn = WarnModel(server_id=ctx.guild.id, user_id=member.id,
reason=reason,
created_at=now) created_at=now)
self.bot.database.session.add(warn) self.bot.database.session.add(warn)
self.bot.database.session.commit() self.bot.database.session.commit()
@groupExtra(name='warn', aliases=['warns'], category='admin', @groupExtra(name='warn', aliases=['warns'], category='administration',
description=Texts('admin_help').get('_warn')) description=Texts('admin_help').get('_warn'),
short_doc=Texts('admin_help').get('_warn__short'))
async def _warn(self, ctx: commands.Context): async def _warn(self, ctx: commands.Context):
await ctx.trigger_typing() await ctx.trigger_typing()
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
@ -307,7 +321,8 @@ class Admin(commands.Cog):
await ctx.send(embed=e) await ctx.send(embed=e)
@_warn.command(name='add', aliases=['new'], @_warn.command(name='add', aliases=['new'],
description=Texts('admin_help').get('_warn_new')) description=Texts('admin_help').get('_warn_new'),
short_doc=Texts('admin_help').get('_warn_new__short'))
async def _warn_new(self, ctx: commands.Context, member: discord.Member, async def _warn_new(self, ctx: commands.Context, member: discord.Member,
*, reason="N/A"): *, reason="N/A"):
member = await ctx.guild.fetch_member(member.id) member = await ctx.guild.fetch_member(member.id)
@ -387,7 +402,8 @@ class Admin(commands.Cog):
) )
@_warn.command(name='remove', aliases=['revoke', 'del', 'delete'], @_warn.command(name='remove', aliases=['revoke', 'del', 'delete'],
description=Texts('admin_help').get('_warn_remove')) description=Texts('admin_help').get('_warn_remove'),
short_doc=Texts('admin_help').get('_warn_remove__short'))
async def _warn_remove(self, ctx: commands.Context, warn_id: int): async def _warn_remove(self, ctx: commands.Context, warn_id: int):
warn = self.bot.database.session \ warn = self.bot.database.session \
.query(WarnModel) \ .query(WarnModel) \
@ -400,7 +416,8 @@ class Admin(commands.Cog):
f" {Texts('admin', ctx).get('successfully removed')}") f" {Texts('admin', ctx).get('successfully removed')}")
@_warn.command(name='show', aliases=['list', 'all'], @_warn.command(name='show', aliases=['list', 'all'],
description=Texts('admin_help').get('_warn_show')) description=Texts('admin_help').get('_warn_show'),
short_doc=Texts('admin_help').get('_warn_show__short'))
async def _warn_show(self, ctx: commands.Context, member: discord.Member): async def _warn_show(self, ctx: commands.Context, member: discord.Member):
warns_list, warns = await self.get_warn(ctx, member) warns_list, warns = await self.get_warn(ctx, member)
@ -412,7 +429,8 @@ class Admin(commands.Cog):
await ctx.send(embed=e) await ctx.send(embed=e)
@_warn.command(name='edit', aliases=['change', 'modify'], @_warn.command(name='edit', aliases=['change', 'modify'],
description=Texts('admin_help').get('_warn_edit')) description=Texts('admin_help').get('_warn_edit'),
short_doc=Texts('admin_help').get('_warn_edit__short'))
async def _warn_edit(self, ctx: commands.Context, warn_id: int, *, reason): async def _warn_edit(self, ctx: commands.Context, warn_id: int, *, reason):
warn = self.bot.database.session \ warn = self.bot.database.session \
.query(WarnModel) \ .query(WarnModel) \
@ -428,8 +446,9 @@ class Admin(commands.Cog):
########################################################################### ###########################################################################
@commandExtra(name='language', aliases=['lang', 'langue', 'langage'], @commandExtra(name='language', aliases=['lang', 'langue', 'langage'],
category='admin', category='server',
description=Texts('admin_help').get('_language')) description=Texts('admin_help').get('_language'),
short_doc=Texts('admin_help').get('_language__short'))
async def _language(self, ctx: commands.Context, locale: str): async def _language(self, ctx: commands.Context, locale: str):
available = self.bot.database.session \ available = self.bot.database.session \
.query(LangModel.value) \ .query(LangModel.value) \
@ -450,7 +469,8 @@ class Admin(commands.Cog):
current.value = locale.lower() current.value = locale.lower()
self.bot.database.session.commit() self.bot.database.session.commit()
else: else:
new_row = LangModel(key=str(ctx.guild.id), value=locale.lower()) new_row = LangModel(key=str(ctx.guild.id),
value=locale.lower())
self.bot.database.session.add(new_row) self.bot.database.session.add(new_row)
self.bot.database.session.commit() self.bot.database.session.commit()
@ -459,14 +479,16 @@ class Admin(commands.Cog):
########################################################################### ###########################################################################
@groupExtra(name='prefix', aliases=['prefixes'], category='admin', @groupExtra(name='prefix', aliases=['prefixes'], category='server',
description=Texts('admin_help').get('_prefix')) description=Texts('admin_help').get('_prefix'),
short_doc=Texts('admin_help').get('_prefix_short'))
async def _prefix(self, ctx: commands.Context): async def _prefix(self, ctx: commands.Context):
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await ctx.send_help('prefix') await ctx.send_help('prefix')
@_prefix.command(name='add', aliases=['set', 'new'], @_prefix.command(name='add', aliases=['set', 'new'],
description=Texts('admin_help').get('_prefix_add')) description=Texts('admin_help').get('_prefix_add'),
short_doc=Texts('admin_help').get('_prefix_add__short'))
async def _prefix_add(self, ctx: commands.Context, prefix: str): async def _prefix_add(self, ctx: commands.Context, prefix: str):
if str(ctx.guild.id) in self.bot.prefixes: if str(ctx.guild.id) in self.bot.prefixes:
prefixes = self.bot.prefixes.get( prefixes = self.bot.prefixes.get(
@ -500,7 +522,9 @@ class Admin(commands.Cog):
) )
@_prefix.command(name='remove', aliases=['drop', 'del', 'delete'], @_prefix.command(name='remove', aliases=['drop', 'del', 'delete'],
description=Texts('admin_help').get('_prefix_remove')) description=Texts('admin_help').get('_prefix_remove'),
short_doc=Texts('admin_help').get(
'_prefix_remove__short'))
async def _prefix_remove(self, ctx: commands.Context, prefix: str): async def _prefix_remove(self, ctx: commands.Context, prefix: str):
if str(ctx.guild.id) in self.bot.prefixes: if str(ctx.guild.id) in self.bot.prefixes:
prefixes = self.bot.prefixes.get( prefixes = self.bot.prefixes.get(
@ -529,7 +553,8 @@ class Admin(commands.Cog):
) )
@_prefix.command(name='list', aliases=['show', 'all'], @_prefix.command(name='list', aliases=['show', 'all'],
description=Texts('admin_help').get('_prefix_list')) description=Texts('admin_help').get('_prefix_list'),
short_doc=Texts('admin_help').get('_prefix_list__short'))
async def _prefix_list(self, ctx: commands.Context): async def _prefix_list(self, ctx: commands.Context):
extras = ['.'] extras = ['.']
if ctx.message.guild is not None: if ctx.message.guild is not None:
@ -546,10 +571,10 @@ class Admin(commands.Cog):
prefixes.extend(extras) prefixes.extend(extras)
if len(prefixes) <= 1: if len(prefixes) <= 1:
text = Texts('admin', ctx)\ text = Texts('admin', ctx) \
.get('The only prefix for this guild is :\n') .get('The only prefix for this guild is :\n')
else: else:
text = Texts('admin', ctx)\ text = Texts('admin', ctx) \
.get('Available prefixes for this guild are :\n') .get('Available prefixes for this guild are :\n')
await ctx.send(text + "\n".join(prefixes)) await ctx.send(text + "\n".join(prefixes))

View file

@ -4,9 +4,11 @@ import logging
import discord import discord
from discord.ext import commands from discord.ext import commands
from discord import utils
from bot import TuxBot from bot import TuxBot
from utils import Texts from utils import Texts
from utils.paginator import FieldPages
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -14,8 +16,46 @@ log = logging.getLogger(__name__)
class HelpCommand(commands.HelpCommand): class HelpCommand(commands.HelpCommand):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.ignore_cogs = ["Monitoring", "Help", "Logs"] self.ignore_cogs = ["Monitoring", "Help", "Jishaku"]
self.owner_cogs = [] self.owner_cogs = ["Admin"]
def common_command_formatting(self, emb, command):
prefix = self.context.prefix if str(self.context.bot.user.id) in self.context.prefix else f"@{self.context.bot.user.name}"
emb.title = self.get_command_signature(command)
emb.description = command.description
usage = command.description + "todo: usage"
try:
usg = command.description + "todo: usage"
emb.add_field(
name=usage,
value=f"{prefix}{command.qualified_name} " + usg
)
except KeyError:
emb.add_field(
name=usage,
value=f"{prefix}{command.qualified_name}"
)
aliases = "`" + '`, `'.join(command.aliases) + "`"
if aliases == "``":
aliases = Texts(
'help', self.context
).get(
'command_help.no_aliases'
)
emb.add_field(
name=Texts(
'help', self.context
).get(
'command_help.aliases'
),
value=aliases
)
return emb
async def send_bot_help(self, mapping): async def send_bot_help(self, mapping):
owners = self.context.bot.owners owners = self.context.bot.owners
@ -23,9 +63,10 @@ class HelpCommand(commands.HelpCommand):
f"{owner.name}#{owner.discriminator}" f"{owner.name}#{owner.discriminator}"
for owner in owners for owner in owners
] ]
prefix = self.context.prefix if str(self.context.bot.user.id) not in self.context.prefix else f"@{self.context.bot.user.name} "
e = discord.Embed( e = discord.Embed(
color=discord.colour.Color.blue(), color=discord.Color.blue(),
description=Texts( description=Texts(
'help', self.context 'help', self.context
).get( ).get(
@ -34,22 +75,28 @@ class HelpCommand(commands.HelpCommand):
', '.join(owners_name[:-1]) + ' & ' + owners_name[-1] ', '.join(owners_name[:-1]) + ' & ' + owners_name[-1]
) )
) )
e.set_author( e.set_author(
icon_url=self.context.author.avatar_url_as(format='png'), icon_url=self.context.author.avatar_url_as(format='png'),
name=self.context.author name=self.context.author
) )
e.set_footer(
text=Texts(
'help', self.context
).get(
'main_page.footer'
).format(
prefix
)
)
cogs = "" cogs = ""
for extension in self.context.bot.cogs.values(): for extension in self.context.bot.cogs.values():
if self.context.author not in owners \ if self.context.author not in owners \
and extension.qualified_name in self.owner_cogs: and extension.__class__.__name__ in self.owner_cogs:
continue continue
if self.context.author in owners \ if extension.__class__.__name__ in self.ignore_cogs:
and extension.qualified_name in self.ignore_cogs:
continue
if extension.qualified_name == "Jishaku":
continue continue
cogs += f"{extension.icon} **{extension.qualified_name}**\n" cogs += f"{extension.icon} **{extension.qualified_name}**\n"
e.add_field( e.add_field(
@ -63,6 +110,100 @@ class HelpCommand(commands.HelpCommand):
await self.context.send(embed=e) await self.context.send(embed=e)
async def send_cog_help(self, cog):
pages = {}
prefix = self.context.prefix if str(self.context.bot.user.id) in self.context.prefix else f"@{self.context.bot.user.name}"
if cog.__class__.__name__ in self.owner_cogs \
and self.context.author not in self.context.bot.owners:
return self.command_not_found(cog.qualified_name)
for cmd in cog.get_commands():
if self.context.author not in self.context.bot.owners \
and (cmd.hidden or cmd.category == "Hidden"):
continue
if cmd.category not in pages:
pages[cmd.category] = "```asciidoc\n"
pages[cmd.category] \
+= f"{cmd.name}" \
+ ' ' * int(17 - len(cmd.name)) \
+ f":: {cmd.short_doc}\n"
if isinstance(cmd, commands.Group):
for group_command in cmd.commands:
pages[cmd.category] \
+= f"{group_command.name}" \
+ ' ' * int(15 - len(group_command.name)) \
+ f":: {cmd.short_doc}\n"
for e in pages:
pages[e] += "```"
formatted = []
for name, cont in pages.items():
formatted.append((name, cont))
footer_text = Texts('help', self.context) \
.get('main_page.footer') \
.format(prefix)
pages = FieldPages(
self.context,
embed_color=discord.Color.blue(),
entries=formatted,
title=cog.qualified_name.upper(),
thumbnail=cog.big_icon,
footericon=self.context.bot.user.avatar_url,
footertext=footer_text,
per_page=1
)
await pages.paginate()
async def send_group_help(self, group):
if group.cog_name in self.ignore_cogs:
return await self.send_error_message(
self.command_not_found(group.name)
)
formatted = self.common_command_formatting(
discord.Embed(color=discord.Color.blue()),
group
)
sub_cmd_list = ""
for group_command in group.commands:
sub_cmd_list += f"└> **{group_command.name}** - {group_command.description}\n"
subcommands = Texts(
'help', self.context
).get(
'command_help.subcommands'
)
formatted.add_field(name=subcommands, value=sub_cmd_list, inline=False)
await self.context.send(embed=formatted)
async def send_command_help(self, command):
if isinstance(command, commands.Group):
return await self.send_group_help(command)
if command.cog_name in self.ignore_cogs:
return await self.send_error_message(
self.command_not_found(command.name))
formatted = self.common_command_formatting(
discord.Embed(color=discord.Color.blue()),
command
)
await self.context.send(embed=formatted)
def command_not_found(self, command):
return Texts(
'help', self.context
).get(
'main_page.not_found'
).format(
command
)
class Help(commands.Cog): class Help(commands.Cog):
def __init__(self, bot: TuxBot): def __init__(self, bot: TuxBot):

View file

@ -1,7 +1,7 @@
""" """
Based on https://github.com/Rapptz/RoboDanny/blob/3d94e89ef27f702a5f57f432a9131bdfb60bb3ec/cogs/stats.py Based on https://github.com/Rapptz/RoboDanny/blob/3d94e89ef27f702a5f57f432a9131bdfb60bb3ec/cogs/stats.py
Rewrite by Romain J. Adapted by Romain J.
""" """
@ -52,6 +52,9 @@ class Logs(commands.Cog):
self._resumes = [] self._resumes = []
self._identifies = defaultdict(list) self._identifies = defaultdict(list)
self.icon = ":newspaper:"
self.big_icon = "https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/twitter/233/newspaper_1f4f0.png"
def _clear_gateway_data(self): def _clear_gateway_data(self):
one_week_ago = datetime.datetime.utcnow() - datetime.timedelta(days=7) one_week_ago = datetime.datetime.utcnow() - datetime.timedelta(days=7)
to_remove = [ to_remove = [
@ -240,8 +243,9 @@ class Logs(commands.Cog):
msg = f'{emoji} `[{dt:%Y-%m-%d %H:%M:%S}] {record.message}`' msg = f'{emoji} `[{dt:%Y-%m-%d %H:%M:%S}] {record.message}`'
await self.webhook.send(msg) await self.webhook.send(msg)
@commandExtra(name='commandstats', hidden=True, category='logs', @commandExtra(name='commandstats', hidden=True, category='misc',
description=Texts('logs_help').get('_commandstats')) description=Texts('logs_help').get('_commandstats'),
short_doc=Texts('logs_help').get('_commandstats__short'))
@commands.is_owner() @commands.is_owner()
async def _commandstats(self, ctx, limit=20): async def _commandstats(self, ctx, limit=20):
counter = self.bot.command_stats counter = self.bot.command_stats
@ -256,8 +260,9 @@ class Logs(commands.Cog):
await ctx.send(f'```\n{output}\n```') await ctx.send(f'```\n{output}\n```')
@commandExtra(name='socketstats', hidden=True, category='logs', @commandExtra(name='socketstats', hidden=True, category='misc',
description=Texts('logs_help').get('_socketstats')) description=Texts('logs_help').get('_socketstats'),
short_doc=Texts('logs_help').get('_socketstats__short'))
@commands.is_owner() @commands.is_owner()
async def _socketstats(self, ctx): async def _socketstats(self, ctx):
delta = datetime.datetime.utcnow() - self.bot.uptime delta = datetime.datetime.utcnow() - self.bot.uptime
@ -267,8 +272,9 @@ class Logs(commands.Cog):
await ctx.send( await ctx.send(
f'{total} socket events observed ({cpm:.2f}/minute):\n{self.bot.socket_stats}') f'{total} socket events observed ({cpm:.2f}/minute):\n{self.bot.socket_stats}')
@commandExtra(name='uptime', category='logs', @commandExtra(name='uptime', category='misc',
description=Texts('logs_help').get('_uptime')) description=Texts('logs_help').get('_uptime'),
short_doc=Texts('logs_help').get('_uptime__short'))
async def _uptime(self, ctx): async def _uptime(self, ctx):
"""Tells you how long the bot has been up for.""" """Tells you how long the bot has been up for."""
uptime = humanize.naturaltime( uptime = humanize.naturaltime(

View file

@ -204,15 +204,17 @@ class Polls(commands.Cog):
poll.content = json.dumps(content) poll.content = json.dumps(content)
self.bot.database.session.commit() self.bot.database.session.commit()
@groupExtra(name='sondage', aliases=['poll'], @groupExtra(name='poll', aliases=['sondage'],
category='poll', category='poll',
description=Texts('poll_help').get('_poll')) description=Texts('poll_help').get('_poll'),
short_doc=Texts('poll_help').get('_poll__short'))
async def _poll(self, ctx: commands.Context): async def _poll(self, ctx: commands.Context):
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await ctx.send_help('sondage') await ctx.send_help('sondage')
@_poll.group(name='create', aliases=['new', 'nouveau'], @_poll.group(name='create', aliases=['new', 'nouveau'],
description=Texts('poll_help').get('_poll_create')) description=Texts('poll_help').get('_poll_create'),
short_doc=Texts('poll_help').get('_poll_create__short'))
async def _poll_create(self, ctx: commands.Context, *, poll: str): async def _poll_create(self, ctx: commands.Context, *, poll: str):
is_anonymous = '--anonyme' in poll is_anonymous = '--anonyme' in poll
poll = poll.replace('--anonyme', '') poll = poll.replace('--anonyme', '')

View file

@ -36,10 +36,41 @@ class Useful(commands.Cog):
return os.popen(cmd).read().strip() return os.popen(cmd).read().strip()
@staticmethod
def fetch_info():
total_lines = 0
total_python_lines = 0
file_amount = 0
python_file_amount = 0
ENV = "env"
for path, _, files in os.walk("."):
for name in files:
file_dir = str(pathlib.PurePath(path, name))
if (
not name.endswith(".py")
and not name.endswith(".po")
and not name.endswith(".json")
) or ENV in file_dir:
continue
file_amount += 1
python_file_amount += 1 if name.endswith(".py") else 0
with open(file_dir, "r", encoding="utf-8") as file:
for line in file:
if not line.strip().startswith("#") \
or not line.strip():
total_lines += 1
total_python_lines += 1 if name.endswith(".py") \
else 0
return (file_amount, total_lines), (
python_file_amount, total_python_lines)
########################################################################### ###########################################################################
@commandExtra(name='iplocalise', category='useful', @commandExtra(name='iplocalise', category='network',
description=Texts('useful_help').get('_iplocalise')) description=Texts('useful_help').get('_iplocalise'),
short_doc=Texts('useful_help').get('_iplocalise__short'))
async def _iplocalise(self, ctx: commands.Context, addr, ip_type=''): async def _iplocalise(self, ctx: commands.Context, addr, ip_type=''):
addr = re.sub(r'http(s?)://', '', addr) addr = re.sub(r'http(s?)://', '', addr)
addr = addr[:-1] if addr.endswith('/') else addr addr = addr[:-1] if addr.endswith('/') else addr
@ -103,8 +134,9 @@ class Useful(commands.Cog):
########################################################################### ###########################################################################
@commandExtra(name='getheaders', category='useful', @commandExtra(name='getheaders', category='network',
description=Texts('useful_help').get('_getheaders')) description=Texts('useful_help').get('_getheaders'),
short_doc=Texts('useful_help').get('_getheaders__short'))
async def _getheaders(self, ctx: commands.Context, addr: str): async def _getheaders(self, ctx: commands.Context, addr: str):
if (addr.startswith('http') or addr.startswith('ftp')) is not True: if (addr.startswith('http') or addr.startswith('ftp')) is not True:
addr = f"http://{addr}" addr = f"http://{addr}"
@ -127,7 +159,7 @@ class Useful(commands.Cog):
e.add_field(name=key, value=value, inline=True) e.add_field(name=key, value=value, inline=True)
await ctx.send(embed=e) await ctx.send(embed=e)
except aiohttp.client_exceptions.ClientError: except aiohttp.ClientError:
await ctx.send( await ctx.send(
f"{Texts('useful', ctx).get('Cannot connect to host')} {addr}" f"{Texts('useful', ctx).get('Cannot connect to host')} {addr}"
) )
@ -135,8 +167,9 @@ class Useful(commands.Cog):
########################################################################### ###########################################################################
@commandExtra(name='git', aliases=['sources', 'source', 'github'], @commandExtra(name='git', aliases=['sources', 'source', 'github'],
category='useful', category='misc',
description=Texts('useful_help').get('_git')) description=Texts('useful_help').get('_git'),
short_doc=Texts('useful_help').get('_git__short'))
async def _git(self, ctx): async def _git(self, ctx):
e = discord.Embed( e = discord.Embed(
title=Texts('useful', ctx).get('git repo'), title=Texts('useful', ctx).get('git repo'),
@ -151,8 +184,9 @@ class Useful(commands.Cog):
########################################################################### ###########################################################################
@commandExtra(name='quote', category='useful', @commandExtra(name='quote', category='misc',
description=Texts('useful_help').get('_quote')) description=Texts('useful_help').get('_quote'),
short_doc=Texts('useful_help').get('_quote__short'))
async def _quote(self, ctx, message_id: discord.Message): async def _quote(self, ctx, message_id: discord.Message):
e = discord.Embed( e = discord.Embed(
colour=message_id.author.colour, colour=message_id.author.colour,
@ -174,8 +208,9 @@ class Useful(commands.Cog):
########################################################################### ###########################################################################
@commandExtra(name='ping', category='useful', @commandExtra(name='ping', category='network',
description=Texts('useful_help').get('_ping')) description=Texts('useful_help').get('_ping'),
short_doc=Texts('useful_help').get('_ping__short'))
async def _ping(self, ctx: commands.Context): async def _ping(self, ctx: commands.Context):
start = time.perf_counter() start = time.perf_counter()
await ctx.trigger_typing() await ctx.trigger_typing()
@ -193,37 +228,9 @@ class Useful(commands.Cog):
########################################################################### ###########################################################################
@staticmethod @commandExtra(name='info', aliases=['about'], category='misc',
def fetch_info(): description=Texts('useful_help').get('_info'),
total_lines = 0 short_doc=Texts('useful_help').get('_info__short'))
total_python_lines = 0
file_amount = 0
python_file_amount = 0
ENV = "env"
for path, _, files in os.walk("."):
for name in files:
file_dir = str(pathlib.PurePath(path, name))
if (
not name.endswith(".py")
and not name.endswith(".po")
and not name.endswith(".json")
) or ENV in file_dir:
continue
file_amount += 1
python_file_amount += 1 if name.endswith(".py") else 0
with open(file_dir, "r", encoding="utf-8") as file:
for line in file:
if not line.strip().startswith("#") \
or not line.strip():
total_lines += 1
total_python_lines += 1 if name.endswith(".py") \
else 0
return (file_amount, total_lines), (python_file_amount, total_python_lines)
@commandExtra(name='info', aliases=['about'], category='useful',
description=Texts('useful_help').get('_info'))
async def _info(self, ctx: commands.Context): async def _info(self, ctx: commands.Context):
proc = psutil.Process() proc = psutil.Process()
total, python = self.fetch_info() total, python = self.fetch_info()
@ -306,8 +313,9 @@ class Useful(commands.Cog):
########################################################################### ###########################################################################
@commandExtra(name='credits', aliases=['contributors', 'authors'], @commandExtra(name='credits', aliases=['contributors', 'authors'],
category='useful', category='misc',
description=Texts('useful_help').get('_credits')) description=Texts('useful_help').get('_credits'),
short_doc=Texts('useful_help').get('_credits__short'))
async def _credits(self, ctx: commands.Context): async def _credits(self, ctx: commands.Context):
e = discord.Embed( e = discord.Embed(
title=Texts('useful', ctx).get('Contributors'), title=Texts('useful', ctx).get('Contributors'),

View file

@ -19,14 +19,16 @@ class User(commands.Cog):
########################################################################### ###########################################################################
@groupExtra(name='alias', aliases=['aliases'], category='user', @groupExtra(name='alias', aliases=['aliases'], category='alias',
description=Texts('user_help').get('user._alias')) description=Texts('user_help').get('_alias'),
short_doc=Texts('user_help').get('_alias__short'))
async def _alias(self, ctx: commands.Context): async def _alias(self, ctx: commands.Context):
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await ctx.send_help('alias') await ctx.send_help('alias')
@_alias.command(name='add', aliases=['set', 'new'], @_alias.command(name='add', aliases=['set', 'new'],
description=Texts('user_help').get('_alias_add')) description=Texts('user_help').get('_alias_add'),
short_doc=Texts('user_help').get('_alias_add__short'))
async def _alias_add(self, ctx: commands.Context, *, user_alias: str): async def _alias_add(self, ctx: commands.Context, *, user_alias: str):
is_global = False is_global = False
if '--global' in user_alias: if '--global' in user_alias:
@ -54,12 +56,14 @@ class User(commands.Cog):
self.bot.database.session.commit() self.bot.database.session.commit()
@_alias.command(name='remove', aliases=['drop', 'del', 'delete'], @_alias.command(name='remove', aliases=['drop', 'del', 'delete'],
description=Texts('user_help').get('_alias_remove')) description=Texts('user_help').get('_alias_remove'),
short_doc=Texts('user_help').get('_alias_remove__short'))
async def _alias_remove(self, ctx: commands.Context, prefix: str): async def _alias_remove(self, ctx: commands.Context, prefix: str):
... ...
@_alias.command(name='list', aliases=['show', 'all'], @_alias.command(name='list', aliases=['show', 'all'],
description=Texts('user_help').get('_alias_list')) description=Texts('user_help').get('_alias_list'),
short_doc=Texts('user_help').get('_alias_list__short'))
async def _alias_list(self, ctx: commands.Context): async def _alias_list(self, ctx: commands.Context):
... ...

View file

@ -19,4 +19,16 @@ msgid 'main_page.description'
msgstr "Made by {}\nWhen using commands, <> means a required argument and [] means an optional argument.\n***(These symbols shouldn't be written)***" msgstr "Made by {}\nWhen using commands, <> means a required argument and [] means an optional argument.\n***(These symbols shouldn't be written)***"
msgid 'main_page.categories' msgid 'main_page.categories'
msgstr 'Categories' msgstr 'Categories'
msgid 'main_page.footer'
msgstr '- Send {}help <Command> to see more help about a command.'
msgid 'main_page.not_found'
msgstr 'No command called "{}" found.'
msgid 'command_help.subcommands'
msgstr 'Subcommands'
msgid 'command_help.no_aliases'
msgstr 'No aliases'

View file

@ -19,4 +19,16 @@ msgid 'main_page.description'
msgstr "Créé par {}\nLorsque vous utilisez les commandes, <> correspond à un argument obligatoire et [] à un argument optionnel.\n***(Vous ne devez pas écrire ces symboles)***" msgstr "Créé par {}\nLorsque vous utilisez les commandes, <> correspond à un argument obligatoire et [] à un argument optionnel.\n***(Vous ne devez pas écrire ces symboles)***"
msgid 'main_page.categories' msgid 'main_page.categories'
msgstr 'Catégories' msgstr 'Catégories'
msgid 'main_page.footer'
msgstr "- Envoyez {}help <Commande> pour avoir plus d'aide sur la commande."
msgid 'main_page.not_found'
msgstr 'Impossible de trouver la commande {}.'
msgid 'command_help.subcommands'
msgstr 'Sous-commandes'
msgid 'command_help.no_aliases'
msgstr 'Aucun alias'

View file

@ -1,60 +1,25 @@
[INFO ] [2020-01-04 18:47:27] discord.client: logging in using static token [INFO ] [2020-01-05 00:57:22] discord.client: logging in using static token
[INFO ] [2020-01-04 18:47:28] discord.gateway: Shard ID 0 has sent the IDENTIFY payload. [INFO ] [2020-01-05 00:57:23] discord.gateway: Shard ID 0 has sent the IDENTIFY payload.
[INFO ] [2020-01-04 18:47:28] discord.gateway: Shard ID 0 has connected to Gateway: ["gateway-prd-main-1mb3",{"micros":59339,"calls":["discord-sessions-prd-1-26",{"micros":56634,"calls":["start_session",{"micros":36442,"calls":["api-prd-main-wpx2",{"micros":33053,"calls":["get_user",{"micros":2542},"add_authorized_ip",{"micros":1944},"get_guilds",{"micros":2441},"coros_wait",{"micros":1}]}]},"guilds_connect",{"micros":7,"calls":[]},"presence_connect",{"micros":1,"calls":[]}]}]}] (Session ID: 2e6c7d848985fce96ccf1d3a03a9878b). [INFO ] [2020-01-05 00:57:24] discord.gateway: Shard ID 0 has connected to Gateway: ["gateway-prd-main-gl57",{"micros":945031,"calls":["discord-sessions-prd-1-25",{"micros":942744,"calls":["start_session",{"micros":32305,"calls":["api-prd-main-7v2n",{"micros":29524,"calls":["get_user",{"micros":1434},"add_authorized_ip",{"micros":1485},"get_guilds",{"micros":2100},"coros_wait",{"micros":1}]}]},"guilds_connect",{"micros":7,"calls":[]},"presence_connect",{"micros":877400,"calls":[]}]}]}] (Session ID: 4f9781c6360b7abd8e741bf8ebc2dfc7).
[INFO ] [2020-01-04 18:47:38] discord.state: Processed a chunk for 462 members in guild ID 280805240977227776. [INFO ] [2020-01-05 00:57:33] discord.state: Processed a chunk for 462 members in guild ID 280805240977227776.
[INFO ] [2020-01-04 18:47:38] discord.state: Processed a chunk for 794 members in guild ID 331981755177238530. [INFO ] [2020-01-05 00:57:33] discord.state: Processed a chunk for 794 members in guild ID 331981755177238530.
[INFO ] [2020-01-04 18:47:38] discord.state: Processed a chunk for 1000 members in guild ID 296698073177128962. [INFO ] [2020-01-05 00:57:34] discord.state: Processed a chunk for 1000 members in guild ID 296698073177128962.
[INFO ] [2020-01-04 18:47:39] discord.state: Processed a chunk for 1000 members in guild ID 296698073177128962. [INFO ] [2020-01-05 00:57:34] discord.state: Processed a chunk for 1000 members in guild ID 296698073177128962.
[INFO ] [2020-01-04 18:47:39] discord.state: Processed a chunk for 1000 members in guild ID 296698073177128962. [INFO ] [2020-01-05 00:57:35] discord.state: Processed a chunk for 1000 members in guild ID 296698073177128962.
[INFO ] [2020-01-04 18:47:40] discord.state: Processed a chunk for 1000 members in guild ID 296698073177128962. [INFO ] [2020-01-05 00:57:35] discord.state: Processed a chunk for 1000 members in guild ID 296698073177128962.
[INFO ] [2020-01-04 18:47:40] discord.state: Processed a chunk for 1000 members in guild ID 296698073177128962. [INFO ] [2020-01-05 00:57:36] discord.state: Processed a chunk for 1000 members in guild ID 296698073177128962.
[INFO ] [2020-01-04 18:47:41] discord.state: Processed a chunk for 1000 members in guild ID 296698073177128962. [INFO ] [2020-01-05 00:57:36] discord.state: Processed a chunk for 1000 members in guild ID 296698073177128962.
[INFO ] [2020-01-04 18:47:41] discord.state: Processed a chunk for 1000 members in guild ID 296698073177128962. [INFO ] [2020-01-05 00:57:36] discord.state: Processed a chunk for 1000 members in guild ID 296698073177128962.
[INFO ] [2020-01-04 18:47:42] discord.state: Processed a chunk for 1000 members in guild ID 296698073177128962. [INFO ] [2020-01-05 00:57:37] discord.state: Processed a chunk for 1000 members in guild ID 296698073177128962.
[INFO ] [2020-01-04 18:47:42] discord.state: Processed a chunk for 810 members in guild ID 296698073177128962. [INFO ] [2020-01-05 00:57:37] discord.state: Processed a chunk for 840 members in guild ID 296698073177128962.
[INFO ] [2020-01-04 18:47:45] cogs.Logs: 2020-01-04 17:48:03.502000: Romain#5117 in #general (Gnous): rm-dev01.info [INFO ] [2020-01-05 00:57:39] cogs.Logs: 2020-01-04 23:57:58.535000: Romain#5117 in #tuxbot-test (Gnous): <@!301062143942590465> help
[INFO ] [2020-01-04 18:49:07] cogs.Logs: 2020-01-04 17:49:25.331000: loup#8965 in #general (Gnous): rm-dev01.info [INFO ] [2020-01-05 00:58:20] cogs.Logs: 2020-01-04 23:58:38.646000: Romain#5117 in #tuxbot-test (Gnous): rm-dev01.jsk reload cogs.Help
[INFO ] [2020-01-04 18:50:52] cogs.Logs: 2020-01-04 17:51:11.819000: Romain#5117 in #bot (Gnous): rm-dev01.jsk reloac cogs.Utility [INFO ] [2020-01-05 00:58:22] cogs.Logs: 2020-01-04 23:58:41.511000: Romain#5117 in #tuxbot-test (Gnous): <@!301062143942590465> help
[INFO ] [2020-01-04 18:50:58] cogs.Logs: 2020-01-04 17:51:16.650000: loup#8965 in #bot (Gnous): rm-dev01.info [INFO ] [2020-01-05 00:58:53] cogs.Logs: 2020-01-04 23:59:12.305000: Romain#5117 in #tuxbot-test (Gnous): rm-dev01.jsk reload cogs.Help
[INFO ] [2020-01-04 18:50:58] cogs.Logs: 2020-01-04 17:51:17.057000: Romain#5117 in #bot (Gnous): rm-dev01.jsk reload cogs.Utility [INFO ] [2020-01-05 00:58:57] cogs.Logs: 2020-01-04 23:59:16.559000: Romain#5117 in #tuxbot-test (Gnous): <@!301062143942590465> help
[INFO ] [2020-01-04 18:51:09] cogs.Logs: 2020-01-04 17:51:28.317000: Romain#5117 in #bot (Gnous): rm-dev01.jsk reload cogs.Useful [INFO ] [2020-01-05 00:59:09] cogs.Logs: 2020-01-04 23:59:28.908000: Romain#5117 in #tuxbot-test (Gnous): rm-dev01.jsk reload cogs.Help
[INFO ] [2020-01-04 18:51:18] cogs.Logs: 2020-01-04 17:51:36.366000: Romain#5117 in #bot (Gnous): rm-dev01.info [INFO ] [2020-01-05 00:59:17] cogs.Logs: 2020-01-04 23:59:36.767000: Romain#5117 in #bots-spam (Mounak): <@!301062143942590465> help
[INFO ] [2020-01-04 18:52:22] cogs.Logs: 2020-01-04 17:52:41.348000: loup#8965 in #bot (Gnous): rm-dev01.jsk [INFO ] [2020-01-05 01:00:48] discord.client: Cleaning up tasks.
[INFO ] [2020-01-04 18:53:20] cogs.Logs: 2020-01-04 17:53:38.893000: Romain#5117 in #bots-spam (Mounak): rm-dev01.info [INFO ] [2020-01-05 01:00:48] discord.client: Cleaning up after 6 tasks.
[INFO ] [2020-01-04 18:57:52] cogs.Logs: 2020-01-04 17:58:10.611000: Romain#5117 in #bot (Gnous): rm-dev01.jsk [INFO ] [2020-01-05 01:00:49] discord.client: All tasks finished cancelling.
[INFO ] [2020-01-04 18:59:40] cogs.Logs: 2020-01-04 17:59:59.117000: Romain#5117 in #tuxbot-test (Gnous): rm-dev01.jsk py [INFO ] [2020-01-05 01:00:49] discord.client: Closing the event loop.
conf = _bot.config.get("permission", "Owners")
[INFO ] [2020-01-04 18:59:49] cogs.Logs: 2020-01-04 18:00:08.291000: Romain#5117 in #tuxbot-test (Gnous): rm-dev01.jsk py
conf = _bot.config.get("permissions", "Owners")
[INFO ] [2020-01-04 18:59:59] cogs.Logs: 2020-01-04 18:00:17.851000: Romain#5117 in #tuxbot-test (Gnous): rm-dev01.jsk py
conf = _bot.config.get("permissions", "Owners")
await _ctx.send(conf)
[INFO ] [2020-01-04 19:01:46] cogs.Logs: 2020-01-04 18:02:04.343000: Romain#5117 in #tuxbot-test (Gnous): rm-dev01.jsk py
conf = _bot.config.get("permissions", "Owners")
conf = conf.split(', ')
conf.append(str(329989396218773504))
conf = ', '.join(conf)
await _ctx.send(conf)
[INFO ] [2020-01-04 19:02:06] cogs.Logs: 2020-01-04 18:02:25.221000: Romain#5117 in #tuxbot-test (Gnous): rm-dev01.jsk py
conf = _bot.config.get("permissions", "Owners")
conf = conf.split(', ')
conf.append(str(a))
conf = ', '.join(conf)
_bot.config.set("permissions", "Owners", conf)
[INFO ] [2020-01-04 19:02:17] cogs.Logs: 2020-01-04 18:02:36.778000: Romain#5117 in #tuxbot-test (Gnous): rm-dev01.jsk py
conf = _bot.config.get("permissions", "Owners")
conf = conf.split(', ')
conf.append(str(123456789))
conf = ', '.join(conf)
_bot.config.set("permissions", "Owners", conf)
[INFO ] [2020-01-04 19:05:37] discord.client: Cleaning up tasks.
[INFO ] [2020-01-04 19:05:37] discord.client: Cleaning up after 6 tasks.
[INFO ] [2020-01-04 19:05:37] discord.client: All tasks finished cancelling.
[INFO ] [2020-01-04 19:05:37] discord.client: Closing the event loop.

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 asyncio
import discord import discord
from discord.ext import commands
from discord.ext.commands import Paginator as CommandPaginator from discord.ext.commands import Paginator as CommandPaginator
class CannotPaginate(Exception): class CannotPaginate(Exception):
pass pass
class Pages: class Pages:
"""Implements a paginator that queries the user for the """Implements a paginator that queries the user for the
pagination interface. pagination interface.
@ -30,28 +41,35 @@ class Pages:
permissions: discord.Permissions permissions: discord.Permissions
Our permissions for the channel. 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.bot = ctx.bot
self.entries = entries self.entries = entries
self.message = ctx.message self.message = ctx.message
self.channel = ctx.channel 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 self.per_page = per_page
pages, left_over = divmod(len(self.entries), self.per_page) pages, left_over = divmod(len(self.entries), self.per_page)
if left_over: if left_over:
pages += 1 pages += 1
self.maximum_pages = pages 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.paginating = len(entries) > per_page
self.show_entry_count = show_entry_count self.show_entry_count = show_entry_count
self.reaction_emojis = [ 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), ('\N{BLACK LEFT-POINTING TRIANGLE}', self.previous_page),
('\U000023f9', self.stop_pages),
('\N{BLACK RIGHT-POINTING TRIANGLE}', self.next_page), ('\N{BLACK RIGHT-POINTING TRIANGLE}', self.next_page),
('\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR}', self.last_page), ('\U000023ed\U0000fe0f', 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),
] ]
if ctx.guild is not None: if ctx.guild is not None:
@ -60,18 +78,24 @@ class Pages:
self.permissions = self.channel.permissions_for(ctx.bot.user) self.permissions = self.channel.permissions_for(ctx.bot.user)
if not self.permissions.embed_links: 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: if not self.permissions.send_messages:
raise CannotPaginate('Bot cannot send messages.') raise commands.BotMissingPermissions('Bot cannot send messages.')
if self.paginating: if self.paginating:
# verify we can actually use the pagination session # verify we can actually use the pagination session
if not self.permissions.add_reactions: 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: 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): def get_page(self, page):
base = (page - 1) * self.per_page base = (page - 1) * self.per_page
@ -86,22 +110,25 @@ class Pages:
def prepare_embed(self, entries, page, *, first=False): def prepare_embed(self, entries, page, *, first=False):
p = [] p = []
for index, entry in enumerate(entries, 1 + ((page - 1) * self.per_page)): for index, entry in enumerate(entries,
p.append(f'{index}. {entry}') 1 + ((page - 1) * self.per_page)):
p.append(f'`{index}.` {entry}')
if self.maximum_pages > 1: if self.maximum_pages > 1:
if self.show_entry_count: 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: else:
text = f'Page {page}/{self.maximum_pages}' text = f'Showing page {page}/{self.maximum_pages}'
self.embed.set_footer(text=text) self.embed.set_footer(text=text)
if self.paginating and first: if self.paginating and first:
p.append('') p.append('')
p.append('Confused? React with \N{INFORMATION SOURCE} for more info.')
self.embed.description = '\n'.join(p) 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): async def show_page(self, page, *, first=False):
self.current_page = page self.current_page = page
@ -153,7 +180,8 @@ class Pages:
async def numbered_page(self): async def numbered_page(self):
"""lets you type a page number to go to""" """lets you type a page number to go to"""
to_delete = [] 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): def message_check(m):
return m.author == self.author and \ return m.author == self.author and \
@ -161,7 +189,11 @@ class Pages:
m.content.isdigit() m.content.isdigit()
try: 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: except asyncio.TimeoutError:
to_delete.append(await self.channel.send('Took too long.')) to_delete.append(await self.channel.send('Took too long.'))
await asyncio.sleep(5) await asyncio.sleep(5)
@ -171,7 +203,8 @@ class Pages:
if page != 0 and page <= self.maximum_pages: if page != 0 and page <= self.maximum_pages:
await self.show_page(page) await self.show_page(page)
else: 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) await asyncio.sleep(5)
try: try:
@ -182,8 +215,9 @@ class Pages:
async def show_help(self): async def show_help(self):
"""shows this message""" """shows this message"""
messages = ['Welcome to the interactive paginator!\n'] messages = ['Welcome to the interactive paginator!\n']
messages.append('This interactively allows you to see pages of text by navigating with ' \ messages.append(
'reactions. They are as follows:\n') '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: for (emoji, func) in self.reaction_emojis:
messages.append(f'{emoji} {func.__doc__}') messages.append(f'{emoji} {func.__doc__}')
@ -191,7 +225,8 @@ class Pages:
embed = self.embed.copy() embed = self.embed.copy()
embed.clear_fields() embed.clear_fields()
embed.description = '\n'.join(messages) 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) await self.message.edit(content=None, embed=embed)
async def go_back_to_current_page(): async def go_back_to_current_page():
@ -205,16 +240,15 @@ class Pages:
await self.message.delete() await self.message.delete()
self.paginating = False self.paginating = False
def react_check(self, payload): def react_check(self, reaction, user):
if payload.user_id != self.author.id: if user is None or user.id != self.author.id:
return False return False
if payload.message_id != self.message.id: if reaction.message.id != self.message.id:
return False return False
to_check = str(payload.emoji)
for (emoji, func) in self.reaction_emojis: for (emoji, func) in self.reaction_emojis:
if to_check == emoji: if reaction.emoji == emoji:
self.match = func self.match = func
return True return True
return False return False
@ -230,52 +264,72 @@ class Pages:
while self.paginating: while self.paginating:
try: 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: except asyncio.TimeoutError:
self.paginating = False self.paginating = False
try: try:
await self.message.clear_reactions() await self.message.delete()
except: except:
pass pass
finally: finally:
break break
try: try:
await self.message.remove_reaction(payload.emoji, discord.Object(id=payload.user_id)) await self.message.remove_reaction(reaction, user)
except: 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() await self.match()
class FieldPages(Pages): class FieldPages(Pages):
"""Similar to Pages except entries should be a list of """Similar to Pages except entries should be a list of
tuples having (key, value) to show as embed fields instead. 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): def prepare_embed(self, entries, page, *, first=False):
self.embed.clear_fields() self.embed.clear_fields()
self.embed.description = discord.Embed.Empty
for key, value in entries: for key, value in entries:
self.embed.add_field(name=key, value=value, inline=False) self.embed.add_field(name=key, value=value, inline=False)
self.embed.title = self.title
if self.maximum_pages > 1: if self.maximum_pages > 1:
if self.show_entry_count: if self.show_entry_count:
text = f'Page {page}/{self.maximum_pages} ({len(self.entries)} entries)' text = f' [{page}/{self.maximum_pages}]'
else: 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): class TextPages(Pages):
"""Uses a commands.Paginator internally to paginate some text.""" """Uses a commands.Paginator internally to paginate some text."""
def __init__(self, ctx, text, *, prefix='```', suffix='```', max_size=2000): def __init__(self, ctx, text, *, prefix='```', suffix='```',
paginator = CommandPaginator(prefix=prefix, suffix=suffix, max_size=max_size - 200) max_size=2000):
paginator = CommandPaginator(prefix=prefix, suffix=suffix,
max_size=max_size - 200)
for line in text.split('\n'): for line in text.split('\n'):
paginator.add_line(line) 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): def get_page(self, page):
return self.entries[page - 1] return self.entries[page - 1]
@ -286,4 +340,4 @@ class TextPages(Pages):
def get_content(self, entry, page, *, first=False): def get_content(self, entry, page, *, first=False):
if self.maximum_pages > 1: if self.maximum_pages > 1:
return f'{entry}\nPage {page}/{self.maximum_pages}' return f'{entry}\nPage {page}/{self.maximum_pages}'
return entry return entry