@ -14,7 +14,7 @@ from .bot import Bot, AutoShardedBot, when_mentioned, when_mentioned_or
|
||||
from .context import Context
|
||||
from .core import *
|
||||
from .errors import *
|
||||
from .formatter import HelpFormatter, Paginator
|
||||
from .help import *
|
||||
from .converter import *
|
||||
from .cooldowns import *
|
||||
from .cog import *
|
||||
|
@ -38,7 +38,7 @@ from .core import GroupMixin, Command
|
||||
from .view import StringView
|
||||
from .context import Context
|
||||
from .errors import CommandNotFound, CommandError
|
||||
from .formatter import HelpFormatter
|
||||
from .help import HelpCommand, DefaultHelpCommand
|
||||
from .cog import Cog
|
||||
|
||||
def when_mentioned(bot, msg):
|
||||
@ -84,71 +84,17 @@ def when_mentioned_or(*prefixes):
|
||||
|
||||
return inner
|
||||
|
||||
_mentions_transforms = {
|
||||
'@everyone': '@\u200beveryone',
|
||||
'@here': '@\u200bhere'
|
||||
}
|
||||
|
||||
_mention_pattern = re.compile('|'.join(_mentions_transforms.keys()))
|
||||
|
||||
def _is_submodule(parent, child):
|
||||
return parent == child or child.startswith(parent + ".")
|
||||
|
||||
async def _default_help_command(ctx, *commands : str):
|
||||
"""Shows this message."""
|
||||
bot = ctx.bot
|
||||
destination = ctx.message.author if bot.pm_help else ctx.message.channel
|
||||
class _DefaultRepr:
|
||||
def __repr__(self):
|
||||
return '<default-help-command>'
|
||||
|
||||
def repl(obj):
|
||||
return _mentions_transforms.get(obj.group(0), '')
|
||||
|
||||
# help by itself just lists our own commands.
|
||||
if len(commands) == 0:
|
||||
pages = await bot.formatter.format_help_for(ctx, bot)
|
||||
elif len(commands) == 1:
|
||||
# try to see if it is a cog name
|
||||
name = _mention_pattern.sub(repl, commands[0])
|
||||
command = None
|
||||
if name in bot.cogs:
|
||||
command = bot.cogs[name]
|
||||
else:
|
||||
command = bot.all_commands.get(name)
|
||||
if command is None:
|
||||
await destination.send(bot.command_not_found.format(name))
|
||||
return
|
||||
|
||||
pages = await bot.formatter.format_help_for(ctx, command)
|
||||
else:
|
||||
name = _mention_pattern.sub(repl, commands[0])
|
||||
command = bot.all_commands.get(name)
|
||||
if command is None:
|
||||
await destination.send(bot.command_not_found.format(name))
|
||||
return
|
||||
|
||||
for key in commands[1:]:
|
||||
try:
|
||||
key = _mention_pattern.sub(repl, key)
|
||||
command = command.all_commands.get(key)
|
||||
if command is None:
|
||||
await destination.send(bot.command_not_found.format(key))
|
||||
return
|
||||
except AttributeError:
|
||||
await destination.send(bot.command_has_no_subcommands.format(command, key))
|
||||
return
|
||||
|
||||
pages = await bot.formatter.format_help_for(ctx, command)
|
||||
|
||||
if bot.pm_help is None:
|
||||
characters = sum(map(len, pages))
|
||||
# modify destination based on length of pages.
|
||||
if characters > 1000:
|
||||
destination = ctx.message.author
|
||||
|
||||
for page in pages:
|
||||
await destination.send(page)
|
||||
_default = _DefaultRepr()
|
||||
|
||||
class BotBase(GroupMixin):
|
||||
def __init__(self, command_prefix, formatter=None, description=None, pm_help=False, **options):
|
||||
def __init__(self, command_prefix, help_command=_default, description=None, **options):
|
||||
super().__init__(**options)
|
||||
self.command_prefix = command_prefix
|
||||
self.extra_events = {}
|
||||
@ -158,32 +104,19 @@ class BotBase(GroupMixin):
|
||||
self._check_once = []
|
||||
self._before_invoke = None
|
||||
self._after_invoke = None
|
||||
self._help_command = None
|
||||
self.description = inspect.cleandoc(description) if description else ''
|
||||
self.pm_help = pm_help
|
||||
self.owner_id = options.get('owner_id')
|
||||
self.command_not_found = options.pop('command_not_found', 'No command called "{}" found.')
|
||||
self.command_has_no_subcommands = options.pop('command_has_no_subcommands', 'Command {0.name} has no subcommands.')
|
||||
|
||||
if options.pop('self_bot', False):
|
||||
self._skip_check = lambda x, y: x != y
|
||||
else:
|
||||
self._skip_check = lambda x, y: x == y
|
||||
|
||||
self.help_attrs = options.pop('help_attrs', {})
|
||||
|
||||
if 'name' not in self.help_attrs:
|
||||
self.help_attrs['name'] = 'help'
|
||||
|
||||
if formatter is not None:
|
||||
if not isinstance(formatter, HelpFormatter):
|
||||
raise discord.ClientException('Formatter must be a subclass of HelpFormatter')
|
||||
self.formatter = formatter
|
||||
if help_command is _default:
|
||||
self.help_command = DefaultHelpCommand()
|
||||
else:
|
||||
self.formatter = HelpFormatter()
|
||||
|
||||
# pay no mind to this ugliness.
|
||||
help_cmd = Command(_default_help_command, **self.help_attrs)
|
||||
self.add_command(help_cmd)
|
||||
self.help_command = help_command
|
||||
|
||||
# internal helpers
|
||||
|
||||
@ -577,6 +510,9 @@ class BotBase(GroupMixin):
|
||||
if cog is None:
|
||||
return
|
||||
|
||||
help_command = self._help_command
|
||||
if help_command and help_command.cog is cog:
|
||||
help_command.cog = None
|
||||
cog._eject(self)
|
||||
|
||||
# extensions
|
||||
@ -685,6 +621,27 @@ class BotBase(GroupMixin):
|
||||
if _is_submodule(lib_name, module):
|
||||
del sys.modules[module]
|
||||
|
||||
# help command stuff
|
||||
|
||||
@property
|
||||
def help_command(self):
|
||||
return self._help_command
|
||||
|
||||
@help_command.setter
|
||||
def help_command(self, value):
|
||||
if value is not None:
|
||||
if not isinstance(value, HelpCommand):
|
||||
raise discord.ClientException('help_command must be a subclass of HelpCommand')
|
||||
if self._help_command is not None:
|
||||
self._help_command._remove_from_bot(self)
|
||||
self._help_command = value
|
||||
value._add_to_bot(self)
|
||||
elif self._help_command is not None:
|
||||
self._help_command._remove_from_bot(self)
|
||||
self._help_command = None
|
||||
else:
|
||||
self._help_command = None
|
||||
|
||||
# command processing
|
||||
|
||||
async def get_prefix(self, message):
|
||||
@ -899,40 +856,16 @@ class Bot(BotBase, discord.Client):
|
||||
Whether the commands should be case insensitive. Defaults to ``False``. This
|
||||
attribute does not carry over to groups. You must set it to every group if
|
||||
you require group commands to be case insensitive as well.
|
||||
description : :class:`str`
|
||||
description: :class:`str`
|
||||
The content prefixed into the default help message.
|
||||
self_bot : :class:`bool`
|
||||
self_bot: :class:`bool`
|
||||
If ``True``, the bot will only listen to commands invoked by itself rather
|
||||
than ignoring itself. If ``False`` (the default) then the bot will ignore
|
||||
itself. This cannot be changed once initialised.
|
||||
formatter : :class:`.HelpFormatter`
|
||||
The formatter used to format the help message. By default, it uses
|
||||
the :class:`.HelpFormatter`. Check it for more info on how to override it.
|
||||
If you want to change the help command completely (add aliases, etc) then
|
||||
a call to :meth:`~.Bot.remove_command` with 'help' as the argument would do the
|
||||
trick.
|
||||
pm_help : Optional[:class:`bool`]
|
||||
A tribool that indicates if the help command should PM the user instead of
|
||||
sending it to the channel it received it from. If the boolean is set to
|
||||
``True``, then all help output is PM'd. If ``False``, none of the help
|
||||
output is PM'd. If ``None``, then the bot will only PM when the help
|
||||
message becomes too long (dictated by more than 1000 characters).
|
||||
Defaults to ``False``.
|
||||
help_attrs : :class:`dict`
|
||||
A dictionary of options to pass in for the construction of the help command.
|
||||
This allows you to change the command behaviour without actually changing
|
||||
the implementation of the command. The attributes will be the same as the
|
||||
ones passed in the :class:`.Command` constructor. Note that ``pass_context``
|
||||
will always be set to ``True`` regardless of what you pass in.
|
||||
command_not_found : :class:`str`
|
||||
The format string used when the help command is invoked with a command that
|
||||
is not found. Useful for i18n. Defaults to ``"No command called {} found."``.
|
||||
The only format argument is the name of the command passed.
|
||||
command_has_no_subcommands : :class:`str`
|
||||
The format string used when the help command is invoked with requests for a
|
||||
subcommand but the command does not have any subcommands. Defaults to
|
||||
``"Command {0.name} has no subcommands."``. The first format argument is the
|
||||
:class:`.Command` attempted to get a subcommand and the second is the name.
|
||||
help_command: Optional[:class:`.HelpCommand`]
|
||||
The help command implementation to use. This can be dynamically
|
||||
set at runtime. To remove the help command pass ``None``. For more
|
||||
information on implementing a help command, see :ref:`ext_commands_help_command`.
|
||||
owner_id: Optional[:class:`int`]
|
||||
The ID that owns the bot. If this is not set and is then queried via
|
||||
:meth:`.is_owner` then it is fetched automatically using
|
||||
|
@ -810,27 +810,15 @@ class Command(_BaseCommand):
|
||||
@property
|
||||
def signature(self):
|
||||
"""Returns a POSIX-like signature useful for help command output."""
|
||||
result = []
|
||||
parent = self.full_parent_name
|
||||
|
||||
if len(self.aliases) > 0:
|
||||
aliases = '|'.join(self.aliases)
|
||||
fmt = '[%s|%s]' % (self.name, aliases)
|
||||
if parent:
|
||||
fmt = parent + ' ' + fmt
|
||||
result.append(fmt)
|
||||
else:
|
||||
name = self.name if not parent else parent + ' ' + self.name
|
||||
result.append(name)
|
||||
|
||||
if self.usage is not None:
|
||||
result.append(self.usage)
|
||||
return ' '.join(result)
|
||||
return self.usage
|
||||
|
||||
|
||||
params = self.clean_params
|
||||
if not params:
|
||||
return ' '.join(result)
|
||||
return ''
|
||||
|
||||
result = []
|
||||
for name, param in params.items():
|
||||
greedy = isinstance(param.annotation, converters._Greedy)
|
||||
|
||||
|
@ -1,352 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2019 Rapptz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import inspect
|
||||
import discord.utils
|
||||
|
||||
from .core import GroupMixin, Command
|
||||
from .errors import CommandError
|
||||
# from discord.iterators import _FilteredAsyncIterator
|
||||
|
||||
# help -> shows info of bot on top/bottom and lists subcommands
|
||||
# help command -> shows detailed info of command
|
||||
# help command <subcommand chain> -> same as above
|
||||
|
||||
# <description>
|
||||
|
||||
# <command signature with aliases>
|
||||
|
||||
# <long doc>
|
||||
|
||||
# Cog:
|
||||
# <command> <shortdoc>
|
||||
# <command> <shortdoc>
|
||||
# Other Cog:
|
||||
# <command> <shortdoc>
|
||||
# No Category:
|
||||
# <command> <shortdoc>
|
||||
|
||||
# Type <prefix>help command for more info on a command.
|
||||
# You can also type <prefix>help category for more info on a category.
|
||||
|
||||
class Paginator:
|
||||
"""A class that aids in paginating code blocks for Discord messages.
|
||||
|
||||
Attributes
|
||||
-----------
|
||||
prefix: :class:`str`
|
||||
The prefix inserted to every page. e.g. three backticks.
|
||||
suffix: :class:`str`
|
||||
The suffix appended at the end of every page. e.g. three backticks.
|
||||
max_size: :class:`int`
|
||||
The maximum amount of codepoints allowed in a page.
|
||||
"""
|
||||
def __init__(self, prefix='```', suffix='```', max_size=2000):
|
||||
self.prefix = prefix
|
||||
self.suffix = suffix
|
||||
self.max_size = max_size - len(suffix)
|
||||
self._current_page = [prefix]
|
||||
self._count = len(prefix) + 1 # prefix + newline
|
||||
self._pages = []
|
||||
|
||||
def add_line(self, line='', *, empty=False):
|
||||
"""Adds a line to the current page.
|
||||
|
||||
If the line exceeds the :attr:`max_size` then an exception
|
||||
is raised.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
line: str
|
||||
The line to add.
|
||||
empty: bool
|
||||
Indicates if another empty line should be added.
|
||||
|
||||
Raises
|
||||
------
|
||||
RuntimeError
|
||||
The line was too big for the current :attr:`max_size`.
|
||||
"""
|
||||
if len(line) > self.max_size - len(self.prefix) - 2:
|
||||
raise RuntimeError('Line exceeds maximum page size %s' % (self.max_size - len(self.prefix) - 2))
|
||||
|
||||
if self._count + len(line) + 1 > self.max_size:
|
||||
self.close_page()
|
||||
|
||||
self._count += len(line) + 1
|
||||
self._current_page.append(line)
|
||||
|
||||
if empty:
|
||||
self._current_page.append('')
|
||||
self._count += 1
|
||||
|
||||
def close_page(self):
|
||||
"""Prematurely terminate a page."""
|
||||
self._current_page.append(self.suffix)
|
||||
self._pages.append('\n'.join(self._current_page))
|
||||
self._current_page = [self.prefix]
|
||||
self._count = len(self.prefix) + 1 # prefix + newline
|
||||
|
||||
@property
|
||||
def pages(self):
|
||||
"""Returns the rendered list of pages."""
|
||||
# we have more than just the prefix in our current page
|
||||
if len(self._current_page) > 1:
|
||||
self.close_page()
|
||||
return self._pages
|
||||
|
||||
def __repr__(self):
|
||||
fmt = '<Paginator prefix: {0.prefix} suffix: {0.suffix} max_size: {0.max_size} count: {0._count}>'
|
||||
return fmt.format(self)
|
||||
|
||||
class HelpFormatter:
|
||||
"""The default base implementation that handles formatting of the help
|
||||
command.
|
||||
|
||||
To override the behaviour of the formatter, :meth:`~.HelpFormatter.format`
|
||||
should be overridden. A number of utility functions are provided for use
|
||||
inside that method.
|
||||
|
||||
Attributes
|
||||
-----------
|
||||
show_hidden: :class:`bool`
|
||||
Dictates if hidden commands should be shown in the output.
|
||||
Defaults to ``False``.
|
||||
show_check_failure: :class:`bool`
|
||||
Dictates if commands that have their :attr:`.Command.checks` failed
|
||||
shown. Defaults to ``False``.
|
||||
width: :class:`int`
|
||||
The maximum number of characters that fit in a line.
|
||||
Defaults to 80.
|
||||
commands_heading: :class:`str`
|
||||
The command list's heading string used when the help command is invoked with a category name.
|
||||
Useful for i18n. Defaults to ``"Commands:"``
|
||||
no_category: :class:`str`
|
||||
The string used when there is a command which does not belong to any category(cog).
|
||||
Useful for i18n. Defaults to ``"No Category"``
|
||||
"""
|
||||
def __init__(self, show_hidden=False, show_check_failure=False, width=80,
|
||||
commands_heading="Commands:", no_category="No Category"):
|
||||
self.width = width
|
||||
self.show_hidden = show_hidden
|
||||
self.show_check_failure = show_check_failure
|
||||
self.commands_heading = commands_heading
|
||||
self.no_category = no_category
|
||||
|
||||
def has_subcommands(self):
|
||||
""":class:`bool`: Specifies if the command has subcommands."""
|
||||
return isinstance(self.command, GroupMixin)
|
||||
|
||||
def is_bot(self):
|
||||
""":class:`bool`: Specifies if the command being formatted is the bot itself."""
|
||||
return self.command is self.context.bot
|
||||
|
||||
def is_cog(self):
|
||||
""":class:`bool`: Specifies if the command being formatted is actually a cog."""
|
||||
return not self.is_bot() and not isinstance(self.command, Command)
|
||||
|
||||
def shorten(self, text):
|
||||
"""Shortens text to fit into the :attr:`width`."""
|
||||
if len(text) > self.width:
|
||||
return text[:self.width - 3] + '...'
|
||||
return text
|
||||
|
||||
@property
|
||||
def max_name_size(self):
|
||||
""":class:`int`: Returns the largest name length of a command or if it has subcommands
|
||||
the largest subcommand name."""
|
||||
try:
|
||||
commands = self.command.all_commands if not self.is_cog() else self.context.bot.all_commands
|
||||
if commands:
|
||||
return max(map(lambda c: discord.utils._string_width(c.name) if self.show_hidden or not c.hidden else 0, commands.values()))
|
||||
return 0
|
||||
except AttributeError:
|
||||
return len(self.command.name)
|
||||
|
||||
@property
|
||||
def clean_prefix(self):
|
||||
"""The cleaned up invoke prefix. i.e. mentions are ``@name`` instead of ``<@id>``."""
|
||||
user = self.context.guild.me if self.context.guild else self.context.bot.user
|
||||
# this breaks if the prefix mention is not the bot itself but I
|
||||
# consider this to be an *incredibly* strange use case. I'd rather go
|
||||
# for this common use case rather than waste performance for the
|
||||
# odd one.
|
||||
return self.context.prefix.replace(user.mention, '@' + user.display_name)
|
||||
|
||||
def get_command_signature(self):
|
||||
"""Retrieves the signature portion of the help page."""
|
||||
prefix = self.clean_prefix
|
||||
cmd = self.command
|
||||
return prefix + cmd.signature
|
||||
|
||||
def get_ending_note(self):
|
||||
"""Returns help command's ending note. This is mainly useful to override for i18n purposes."""
|
||||
command_name = self.context.invoked_with
|
||||
return "Type {0}{1} command for more info on a command.\n" \
|
||||
"You can also type {0}{1} category for more info on a category.".format(self.clean_prefix, command_name)
|
||||
|
||||
async def filter_command_list(self):
|
||||
"""Returns a filtered list of commands based on the two attributes
|
||||
provided, :attr:`show_check_failure` and :attr:`show_hidden`.
|
||||
Also filters based on if :meth:`~.HelpFormatter.is_cog` is valid.
|
||||
|
||||
Returns
|
||||
--------
|
||||
iterable
|
||||
An iterable with the filter being applied. The resulting value is
|
||||
a (key, value) :class:`tuple` of the command name and the command itself.
|
||||
"""
|
||||
|
||||
def sane_no_suspension_point_predicate(tup):
|
||||
cmd = tup[1]
|
||||
if self.is_cog():
|
||||
# filter commands that don't exist to this cog.
|
||||
if cmd.cog is not self.command:
|
||||
return False
|
||||
|
||||
if cmd.hidden and not self.show_hidden:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
async def predicate(tup):
|
||||
if sane_no_suspension_point_predicate(tup) is False:
|
||||
return False
|
||||
|
||||
cmd = tup[1]
|
||||
try:
|
||||
return await cmd.can_run(self.context)
|
||||
except CommandError:
|
||||
return False
|
||||
|
||||
iterator = self.command.all_commands.items() if not self.is_cog() else self.context.bot.all_commands.items()
|
||||
if self.show_check_failure:
|
||||
return filter(sane_no_suspension_point_predicate, iterator)
|
||||
|
||||
# Gotta run every check and verify it
|
||||
ret = []
|
||||
for elem in iterator:
|
||||
valid = await predicate(elem)
|
||||
if valid:
|
||||
ret.append(elem)
|
||||
|
||||
return ret
|
||||
|
||||
def _add_subcommands_to_page(self, max_width, commands):
|
||||
for name, command in commands:
|
||||
if name in command.aliases:
|
||||
# skip aliases
|
||||
continue
|
||||
width_gap = discord.utils._string_width(name) - len(name)
|
||||
entry = ' {0:<{width}} {1}'.format(name, command.short_doc, width=max_width-width_gap)
|
||||
shortened = self.shorten(entry)
|
||||
self._paginator.add_line(shortened)
|
||||
|
||||
async def format_help_for(self, context, command_or_bot):
|
||||
"""Formats the help page and handles the actual heavy lifting of how
|
||||
the help command looks like. To change the behaviour, override the
|
||||
:meth:`~.HelpFormatter.format` method.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
context: :class:`.Context`
|
||||
The context of the invoked help command.
|
||||
command_or_bot: :class:`.Command` or :class:`.Bot`
|
||||
The bot or command that we are getting the help of.
|
||||
|
||||
Returns
|
||||
--------
|
||||
list
|
||||
A paginated output of the help command.
|
||||
"""
|
||||
self.context = context
|
||||
self.command = command_or_bot
|
||||
return await self.format()
|
||||
|
||||
async def format(self):
|
||||
"""Handles the actual behaviour involved with formatting.
|
||||
|
||||
To change the behaviour, this method should be overridden.
|
||||
|
||||
Returns
|
||||
--------
|
||||
list
|
||||
A paginated output of the help command.
|
||||
"""
|
||||
self._paginator = Paginator()
|
||||
|
||||
# we need a padding of ~80 or so
|
||||
|
||||
description = self.command.description if not self.is_cog() else inspect.getdoc(self.command)
|
||||
|
||||
if description:
|
||||
# <description> portion
|
||||
self._paginator.add_line(description, empty=True)
|
||||
|
||||
if isinstance(self.command, Command):
|
||||
# <signature portion>
|
||||
signature = self.get_command_signature()
|
||||
self._paginator.add_line(signature, empty=True)
|
||||
|
||||
# <long doc> section
|
||||
if self.command.help:
|
||||
self._paginator.add_line(self.command.help, empty=True)
|
||||
|
||||
# end it here if it's just a regular command
|
||||
if not self.has_subcommands():
|
||||
self._paginator.close_page()
|
||||
return self._paginator.pages
|
||||
|
||||
max_width = self.max_name_size
|
||||
|
||||
def category(tup):
|
||||
cog = tup[1].cog_name
|
||||
# we insert the zero width space there to give it approximate
|
||||
# last place sorting position.
|
||||
return cog + ':' if cog is not None else '\u200b' + self.no_category + ':'
|
||||
|
||||
filtered = await self.filter_command_list()
|
||||
if self.is_bot():
|
||||
data = sorted(filtered, key=category)
|
||||
for category, commands in itertools.groupby(data, key=category):
|
||||
# there simply is no prettier way of doing this.
|
||||
commands = sorted(commands)
|
||||
if len(commands) > 0:
|
||||
self._paginator.add_line(category)
|
||||
|
||||
self._add_subcommands_to_page(max_width, commands)
|
||||
else:
|
||||
filtered = sorted(filtered)
|
||||
if filtered:
|
||||
self._paginator.add_line(self.commands_heading)
|
||||
self._add_subcommands_to_page(max_width, filtered)
|
||||
|
||||
# add the ending note
|
||||
self._paginator.add_line()
|
||||
ending_note = self.get_ending_note()
|
||||
self._paginator.add_line(ending_note)
|
||||
return self._paginator.pages
|
1144
discord/ext/commands/help.py
Normal file
1144
discord/ext/commands/help.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user