mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-21 10:17:59 -05:00
Discord.py dep update 3.1 (#2587)
* Dependency update discord.py==1.0.1 websockets<7 [style] black==19.3b0 [Docs] jinja==2.10.1 urllib3==1.24.2 Changes related to breaking changes from discord.py have also been made to match As of this commit, help formatter is back to discord.py's default
This commit is contained in:
@@ -14,7 +14,7 @@ from discord.ext.commands import when_mentioned_or
|
||||
|
||||
from . import Config, i18n, commands, errors
|
||||
from .cog_manager import CogManager
|
||||
from .help_formatter import Help, help as help_
|
||||
|
||||
from .rpc import RPCMixin
|
||||
from .utils import common_filters
|
||||
|
||||
@@ -29,10 +29,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
||||
"""Mixin for the main bot class.
|
||||
|
||||
This exists because `Red` inherits from `discord.AutoShardedClient`, which
|
||||
is something other bot classes (namely selfbots) may not want to have as
|
||||
a parent class.
|
||||
|
||||
Selfbots should inherit from this mixin along with `discord.Client`.
|
||||
is something other bot classes may not want to have as a parent class.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, cli_flags=None, bot_dir: Path = Path.cwd(), **kwargs):
|
||||
@@ -118,11 +115,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
||||
|
||||
self.cog_mgr = CogManager()
|
||||
|
||||
super().__init__(*args, formatter=Help(), **kwargs)
|
||||
|
||||
self.remove_command("help")
|
||||
|
||||
self.add_command(help_)
|
||||
super().__init__(*args, help_command=commands.DefaultHelpCommand(), **kwargs)
|
||||
|
||||
self._permissions_hooks: List[commands.CheckPredicate] = []
|
||||
|
||||
@@ -216,6 +209,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
||||
curr_pkgs.remove(pkg_name)
|
||||
|
||||
async def load_extension(self, spec: ModuleSpec):
|
||||
# NB: this completely bypasses `discord.ext.commands.Bot._load_from_module_spec`
|
||||
name = spec.name.split(".")[-1]
|
||||
if name in self.extensions:
|
||||
raise errors.PackageAlreadyLoaded(spec)
|
||||
@@ -225,12 +219,17 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
||||
del lib
|
||||
raise discord.ClientException(f"extension {name} does not have a setup function")
|
||||
|
||||
if asyncio.iscoroutinefunction(lib.setup):
|
||||
await lib.setup(self)
|
||||
try:
|
||||
if asyncio.iscoroutinefunction(lib.setup):
|
||||
await lib.setup(self)
|
||||
else:
|
||||
lib.setup(self)
|
||||
except Exception as e:
|
||||
self._remove_module_references(lib.__name__)
|
||||
self._call_module_finalizers(lib, key)
|
||||
raise errors.ExtensionFailed(key, e) from e
|
||||
else:
|
||||
lib.setup(self)
|
||||
|
||||
self.extensions[name] = lib
|
||||
self._BotBase__extensions[name] = lib
|
||||
|
||||
def remove_cog(self, cogname: str):
|
||||
cog = self.get_cog(cogname)
|
||||
@@ -250,62 +249,6 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
||||
for meth in self.rpc_handlers.pop(cogname.upper(), ()):
|
||||
self.unregister_rpc_handler(meth)
|
||||
|
||||
def unload_extension(self, name):
|
||||
lib = self.extensions.get(name)
|
||||
|
||||
if lib is None:
|
||||
return
|
||||
|
||||
lib_name = lib.__name__ # Thank you
|
||||
|
||||
# find all references to the module
|
||||
|
||||
# remove the cogs registered from the module
|
||||
for cogname, cog in self.cogs.copy().items():
|
||||
if cog.__module__ and _is_submodule(lib_name, cog.__module__):
|
||||
self.remove_cog(cogname)
|
||||
|
||||
# first remove all the commands from the module
|
||||
for cmd in self.all_commands.copy().values():
|
||||
if cmd.module and _is_submodule(lib_name, cmd.module):
|
||||
if isinstance(cmd, discord.ext.commands.GroupMixin):
|
||||
cmd.recursively_remove_all_commands()
|
||||
|
||||
self.remove_command(cmd.name)
|
||||
|
||||
# then remove all the listeners from the module
|
||||
for event_list in self.extra_events.copy().values():
|
||||
remove = []
|
||||
|
||||
for index, event in enumerate(event_list):
|
||||
if event.__module__ and _is_submodule(lib_name, event.__module__):
|
||||
remove.append(index)
|
||||
|
||||
for index in reversed(remove):
|
||||
del event_list[index]
|
||||
|
||||
try:
|
||||
func = getattr(lib, "teardown")
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
func(self)
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
# finally remove the import..
|
||||
pkg_name = lib.__package__
|
||||
del lib
|
||||
del self.extensions[name]
|
||||
|
||||
for module in list(sys.modules):
|
||||
if _is_submodule(lib_name, module):
|
||||
del sys.modules[module]
|
||||
|
||||
if pkg_name.startswith("redbot.cogs."):
|
||||
del sys.modules["redbot.cogs"].__dict__[name]
|
||||
|
||||
async def is_automod_immune(
|
||||
self, to_check: Union[discord.Message, commands.Context, discord.abc.User, discord.Role]
|
||||
) -> bool:
|
||||
@@ -399,11 +342,9 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
||||
else:
|
||||
self.add_permissions_hook(hook)
|
||||
|
||||
for attr in dir(cog):
|
||||
_attr = getattr(cog, attr)
|
||||
if isinstance(_attr, discord.ext.commands.Command) and not isinstance(
|
||||
_attr, commands.Command
|
||||
):
|
||||
for command in cog.__cog_commands__:
|
||||
|
||||
if not isinstance(command, commands.Command):
|
||||
raise RuntimeError(
|
||||
f"The {cog.__class__.__name__} cog in the {cog.__module__} package,"
|
||||
" is not using Red's command module, and cannot be added. "
|
||||
@@ -414,13 +355,8 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
||||
)
|
||||
super().add_cog(cog)
|
||||
self.dispatch("cog_add", cog)
|
||||
|
||||
def add_command(self, command: commands.Command):
|
||||
if not isinstance(command, commands.Command):
|
||||
raise TypeError("Command objects must derive from redbot.core.commands.Command")
|
||||
|
||||
super().add_command(command)
|
||||
self.dispatch("command_add", command)
|
||||
for command in cog.__cog_commands__:
|
||||
self.dispatch("command_add", command)
|
||||
|
||||
def clear_permission_rules(self, guild_id: Optional[int]) -> None:
|
||||
"""Clear all permission overrides in a scope.
|
||||
|
||||
@@ -4,3 +4,4 @@ from .context import *
|
||||
from .converter import *
|
||||
from .errors import *
|
||||
from .requires import *
|
||||
from .help import *
|
||||
|
||||
@@ -20,6 +20,7 @@ if TYPE_CHECKING:
|
||||
|
||||
__all__ = [
|
||||
"Cog",
|
||||
"CogMixin",
|
||||
"CogCommandMixin",
|
||||
"CogGroupMixin",
|
||||
"Command",
|
||||
@@ -241,9 +242,9 @@ class Command(CogCommandMixin, commands.Command):
|
||||
if result is False:
|
||||
return False
|
||||
|
||||
if self.parent is None and self.instance is not None:
|
||||
if self.parent is None and self.cog is not None:
|
||||
# For top-level commands, we need to check the cog's requires too
|
||||
ret = await self.instance.requires.verify(ctx)
|
||||
ret = await self.cog.requires.verify(ctx)
|
||||
if ret is False:
|
||||
return False
|
||||
|
||||
@@ -374,8 +375,8 @@ class Command(CogCommandMixin, commands.Command):
|
||||
def allow_for(self, model_id: Union[int, str], guild_id: int) -> None:
|
||||
super().allow_for(model_id, guild_id=guild_id)
|
||||
parents = self.parents
|
||||
if self.instance is not None:
|
||||
parents.append(self.instance)
|
||||
if self.cog is not None:
|
||||
parents.append(self.cog)
|
||||
for parent in parents:
|
||||
cur_rule = parent.requires.get_rule(model_id, guild_id=guild_id)
|
||||
if cur_rule is PermState.NORMAL:
|
||||
@@ -389,8 +390,8 @@ class Command(CogCommandMixin, commands.Command):
|
||||
old_rule, new_rule = super().clear_rule_for(model_id, guild_id=guild_id)
|
||||
if old_rule is PermState.ACTIVE_ALLOW:
|
||||
parents = self.parents
|
||||
if self.instance is not None:
|
||||
parents.append(self.instance)
|
||||
if self.cog is not None:
|
||||
parents.append(self.cog)
|
||||
for parent in parents:
|
||||
should_continue = parent.reevaluate_rules_for(model_id, guild_id=guild_id)[1]
|
||||
if not should_continue:
|
||||
@@ -445,10 +446,11 @@ class GroupMixin(discord.ext.commands.GroupMixin):
|
||||
|
||||
def command(self, *args, **kwargs):
|
||||
"""A shortcut decorator that invokes :func:`.command` and adds it to
|
||||
the internal command list.
|
||||
the internal command list via :meth:`~.GroupMixin.add_command`.
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
kwargs.setdefault("parent", self)
|
||||
result = command(*args, **kwargs)(func)
|
||||
self.add_command(result)
|
||||
return result
|
||||
@@ -457,10 +459,11 @@ class GroupMixin(discord.ext.commands.GroupMixin):
|
||||
|
||||
def group(self, *args, **kwargs):
|
||||
"""A shortcut decorator that invokes :func:`.group` and adds it to
|
||||
the internal command list.
|
||||
the internal command list via :meth:`~.GroupMixin.add_command`.
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
kwargs.setdefault("parent", self)
|
||||
result = group(*args, **kwargs)(func)
|
||||
self.add_command(result)
|
||||
return result
|
||||
@@ -551,12 +554,24 @@ class Group(GroupMixin, Command, CogGroupMixin, commands.Group):
|
||||
await super().invoke(ctx)
|
||||
|
||||
|
||||
class Cog(CogCommandMixin, CogGroupMixin):
|
||||
"""Base class for a cog."""
|
||||
class CogMixin(CogGroupMixin, CogCommandMixin):
|
||||
"""Mixin class for a cog, intended for use with discord.py's cog class"""
|
||||
|
||||
@property
|
||||
def all_commands(self) -> Dict[str, Command]:
|
||||
return {cmd.name: cmd for cmd in self.__dict__.values() if isinstance(cmd, Command)}
|
||||
return {cmd.name: cmd for cmd in self.__cog_commands__}
|
||||
|
||||
|
||||
class Cog(CogMixin, commands.Cog):
|
||||
"""
|
||||
Red's Cog base class
|
||||
|
||||
This includes a metaclass from discord.py
|
||||
"""
|
||||
|
||||
# NB: Do not move the inheritcance of this. Keeping the mix of that metaclass
|
||||
# seperate gives us more freedoms in several places.
|
||||
pass
|
||||
|
||||
|
||||
def command(name=None, cls=Command, **attrs):
|
||||
|
||||
@@ -63,44 +63,9 @@ class Context(commands.Context):
|
||||
return await super().send(content=content, **kwargs)
|
||||
|
||||
async def send_help(self) -> List[discord.Message]:
|
||||
"""Send the command help message.
|
||||
|
||||
Returns
|
||||
-------
|
||||
`list` of `discord.Message`
|
||||
A list of help messages which were sent to the user.
|
||||
|
||||
"""
|
||||
""" Send the command help message. """
|
||||
command = self.invoked_subcommand or self.command
|
||||
embed_wanted = await self.bot.embed_requested(
|
||||
self.channel, self.author, command=self.bot.get_command("help")
|
||||
)
|
||||
if self.guild and not self.channel.permissions_for(self.guild.me).embed_links:
|
||||
embed_wanted = False
|
||||
|
||||
ret = []
|
||||
destination = self
|
||||
if embed_wanted:
|
||||
embeds = await self.bot.formatter.format_help_for(self, command)
|
||||
for embed in embeds:
|
||||
try:
|
||||
m = await destination.send(embed=embed)
|
||||
except discord.HTTPException:
|
||||
destination = self.author
|
||||
m = await destination.send(embed=embed)
|
||||
ret.append(m)
|
||||
else:
|
||||
f = commands.HelpFormatter()
|
||||
msgs = await f.format_help_for(self, command)
|
||||
for msg in msgs:
|
||||
try:
|
||||
m = await destination.send(msg)
|
||||
except discord.HTTPException:
|
||||
destination = self.author
|
||||
m = await destination.send(msg)
|
||||
ret.append(m)
|
||||
|
||||
return ret
|
||||
await super().send_help(command)
|
||||
|
||||
async def tick(self) -> bool:
|
||||
"""Add a tick reaction to the command message.
|
||||
|
||||
23
redbot/core/commands/help.py
Normal file
23
redbot/core/commands/help.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from discord.ext import commands
|
||||
from .commands import Command
|
||||
|
||||
__all__ = ["HelpCommand", "DefaultHelpCommand", "MinimalHelpCommand"]
|
||||
|
||||
|
||||
class _HelpCommandImpl(Command, commands.help._HelpCommandImpl):
|
||||
pass
|
||||
|
||||
|
||||
class HelpCommand(commands.help.HelpCommand):
|
||||
def _add_to_bot(self, bot):
|
||||
command = _HelpCommandImpl(self, self.command_callback, **self.command_attrs)
|
||||
bot.add_command(command)
|
||||
self._command_impl = command
|
||||
|
||||
|
||||
class DefaultHelpCommand(HelpCommand, commands.help.DefaultHelpCommand):
|
||||
pass
|
||||
|
||||
|
||||
class MinimalHelpCommand(HelpCommand, commands.help.MinimalHelpCommand):
|
||||
pass
|
||||
@@ -119,7 +119,7 @@ def init_events(bot, cli_flags):
|
||||
"Outdated version! {} is available "
|
||||
"but you're using {}".format(data["info"]["version"], red_version)
|
||||
)
|
||||
owner = await bot.get_user_info(bot.owner_id)
|
||||
owner = await bot.fetch_user(bot.owner_id)
|
||||
await owner.send(
|
||||
"Your Red instance is out of date! {} is the current "
|
||||
"version, however you are using {}!".format(
|
||||
@@ -168,8 +168,9 @@ def init_events(bot, cli_flags):
|
||||
if hasattr(ctx.command, "on_error"):
|
||||
return
|
||||
|
||||
if ctx.cog and hasattr(ctx.cog, f"_{ctx.cog.__class__.__name__}__error"):
|
||||
return
|
||||
if ctx.cog:
|
||||
if commands.Cog._get_overridden_method(ctx.cog.cog_command_error) is not None:
|
||||
return
|
||||
|
||||
if isinstance(error, commands.MissingRequiredArgument):
|
||||
await ctx.send_help()
|
||||
|
||||
@@ -1,403 +0,0 @@
|
||||
"""Overrides the built-in help formatter.
|
||||
|
||||
All help messages will be embed and pretty.
|
||||
|
||||
Most of the code stolen from
|
||||
discord.ext.commands.formatter.py and
|
||||
converted into embeds instead of codeblocks.
|
||||
|
||||
Docstr on cog class becomes category.
|
||||
Docstr on command definition becomes command
|
||||
summary and usage.
|
||||
Use [p] in command docstr for bot prefix.
|
||||
|
||||
See [p]help here for example.
|
||||
|
||||
await bot.formatter.format_help_for(ctx, command)
|
||||
to send help page for command. Optionally pass a
|
||||
string as third arg to add a more descriptive
|
||||
message to help page.
|
||||
e.g. format_help_for(ctx, ctx.command, "Missing required arguments")
|
||||
|
||||
discord.py 1.0.0a
|
||||
|
||||
This help formatter contains work by Rapptz (Danny) and SirThane#1780.
|
||||
"""
|
||||
import contextlib
|
||||
from collections import namedtuple
|
||||
from typing import List, Optional, Union
|
||||
|
||||
import discord
|
||||
from discord.ext.commands import formatter as dpy_formatter
|
||||
import inspect
|
||||
import itertools
|
||||
import re
|
||||
|
||||
from . import commands
|
||||
from .i18n import Translator
|
||||
from .utils.chat_formatting import pagify
|
||||
from .utils import fuzzy_command_search, format_fuzzy_results
|
||||
|
||||
_ = Translator("Help", __file__)
|
||||
|
||||
EMPTY_STRING = "\u200b"
|
||||
|
||||
_mentions_transforms = {"@everyone": "@\u200beveryone", "@here": "@\u200bhere"}
|
||||
|
||||
_mention_pattern = re.compile("|".join(_mentions_transforms.keys()))
|
||||
|
||||
EmbedField = namedtuple("EmbedField", "name value inline")
|
||||
|
||||
|
||||
class Help(dpy_formatter.HelpFormatter):
|
||||
"""Formats help for commands."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.context = None
|
||||
self.command = None
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def pm_check(ctx):
|
||||
return isinstance(ctx.channel, discord.DMChannel)
|
||||
|
||||
@property
|
||||
def me(self):
|
||||
return self.context.me
|
||||
|
||||
@property
|
||||
def bot_all_commands(self):
|
||||
return self.context.bot.all_commands
|
||||
|
||||
@property
|
||||
def avatar(self):
|
||||
return self.context.bot.user.avatar_url_as(format="png")
|
||||
|
||||
async def color(self):
|
||||
if self.pm_check(self.context):
|
||||
return self.context.bot.color
|
||||
else:
|
||||
return await self.context.embed_colour()
|
||||
|
||||
colour = color
|
||||
|
||||
@property
|
||||
def destination(self):
|
||||
if self.context.bot.pm_help:
|
||||
return self.context.author
|
||||
return self.context
|
||||
|
||||
# All the other shit
|
||||
|
||||
@property
|
||||
def author(self):
|
||||
# Get author dict with username if PM and display name in guild
|
||||
if self.pm_check(self.context):
|
||||
name = self.context.bot.user.name
|
||||
else:
|
||||
name = self.me.display_name if not "" else self.context.bot.user.name
|
||||
author = {"name": "{0} Help Manual".format(name), "icon_url": self.avatar}
|
||||
return author
|
||||
|
||||
def _add_subcommands(self, cmds):
|
||||
entries = ""
|
||||
for name, command in cmds:
|
||||
if name in command.aliases:
|
||||
# skip aliases
|
||||
continue
|
||||
|
||||
if self.is_cog() or self.is_bot():
|
||||
name = "{0}{1}".format(self.context.clean_prefix, name)
|
||||
|
||||
entries += "**{0}** {1}\n".format(name, command.short_doc)
|
||||
return entries
|
||||
|
||||
def get_ending_note(self):
|
||||
# command_name = self.context.invoked_with
|
||||
return (
|
||||
"Type {0}help <command> for more info on a command. "
|
||||
"You can also type {0}help <category> for more info on a category.".format(
|
||||
self.context.clean_prefix
|
||||
)
|
||||
)
|
||||
|
||||
async def format(self) -> dict:
|
||||
"""Formats command for output.
|
||||
|
||||
Returns a dict used to build embed"""
|
||||
emb = {"embed": {"title": "", "description": ""}, "footer": {"text": ""}, "fields": []}
|
||||
|
||||
if self.is_cog():
|
||||
translator = getattr(self.command, "__translator__", lambda s: s)
|
||||
description = (
|
||||
inspect.cleandoc(translator(self.command.__doc__))
|
||||
if self.command.__doc__
|
||||
else EMPTY_STRING
|
||||
)
|
||||
else:
|
||||
description = self.command.description
|
||||
|
||||
if not description == "" and description is not None:
|
||||
description = "*{0}*".format(description)
|
||||
|
||||
if description:
|
||||
# <description> portion
|
||||
emb["embed"]["description"] = description[:2046]
|
||||
|
||||
tagline = await self.context.bot.db.help.tagline()
|
||||
if tagline:
|
||||
footer = tagline
|
||||
else:
|
||||
footer = self.get_ending_note()
|
||||
emb["footer"]["text"] = footer
|
||||
|
||||
if isinstance(self.command, discord.ext.commands.core.Command):
|
||||
# <signature portion>
|
||||
emb["embed"]["title"] = emb["embed"]["description"]
|
||||
emb["embed"]["description"] = "`Syntax: {0}`".format(self.get_command_signature())
|
||||
|
||||
# <long doc> section
|
||||
if self.command.help:
|
||||
splitted = self.command.help.split("\n\n")
|
||||
name = "__{0}__".format(splitted[0])
|
||||
value = "\n\n".join(splitted[1:]).replace("[p]", self.context.clean_prefix)
|
||||
if value == "":
|
||||
value = EMPTY_STRING
|
||||
field = EmbedField(name[:252], value[:1024], False)
|
||||
emb["fields"].append(field)
|
||||
|
||||
# end it here if it's just a regular command
|
||||
if not self.has_subcommands():
|
||||
return emb
|
||||
|
||||
def category(tup):
|
||||
# Turn get cog (Category) name from cog/list tuples
|
||||
cog = tup[1].cog_name
|
||||
return "**__{0}:__**".format(cog) if cog is not None else "**__\u200bNo Category:__**"
|
||||
|
||||
# Get subcommands for bot or category
|
||||
filtered = await self.filter_command_list()
|
||||
|
||||
if self.is_bot():
|
||||
# Get list of non-hidden commands for bot.
|
||||
data = sorted(filtered, key=category)
|
||||
for category, commands_ in itertools.groupby(data, key=category):
|
||||
commands_ = sorted(commands_)
|
||||
if len(commands_) > 0:
|
||||
for i, page in enumerate(
|
||||
pagify(self._add_subcommands(commands_), page_length=1000)
|
||||
):
|
||||
title = category if i < 1 else f"{category} (continued)"
|
||||
field = EmbedField(title, page, False)
|
||||
emb["fields"].append(field)
|
||||
|
||||
else:
|
||||
# Get list of commands for category
|
||||
filtered = sorted(filtered)
|
||||
if filtered:
|
||||
for i, page in enumerate(
|
||||
pagify(self._add_subcommands(filtered), page_length=1000)
|
||||
):
|
||||
title = (
|
||||
"**__Commands:__**"
|
||||
if not self.is_bot() and self.is_cog()
|
||||
else "**__Subcommands:__**"
|
||||
)
|
||||
if i > 0:
|
||||
title += " (continued)"
|
||||
field = EmbedField(title, page, False)
|
||||
emb["fields"].append(field)
|
||||
|
||||
return emb
|
||||
|
||||
@staticmethod
|
||||
def group_fields(fields: List[EmbedField], max_chars=1000):
|
||||
curr_group = []
|
||||
ret = []
|
||||
for f in fields:
|
||||
if sum(len(f2.value) for f2 in curr_group) + len(f.value) > max_chars and curr_group:
|
||||
ret.append(curr_group)
|
||||
curr_group = []
|
||||
curr_group.append(f)
|
||||
|
||||
if len(curr_group) > 0:
|
||||
ret.append(curr_group)
|
||||
|
||||
return ret
|
||||
|
||||
async def format_help_for(self, ctx, command_or_bot, reason: str = ""):
|
||||
"""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
|
||||
-----------
|
||||
ctx: :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.
|
||||
reason : str
|
||||
|
||||
Returns
|
||||
--------
|
||||
list
|
||||
A paginated output of the help command.
|
||||
"""
|
||||
self.context = ctx
|
||||
self.command = command_or_bot
|
||||
|
||||
# We want the permission state to be set as if the author had run the command he is
|
||||
# requesting help for. This is so the subcommands shown in the help menu correctly reflect
|
||||
# any permission rules set.
|
||||
if isinstance(self.command, commands.Command):
|
||||
with contextlib.suppress(commands.CommandError):
|
||||
await self.command.can_run(
|
||||
self.context, check_all_parents=True, change_permission_state=True
|
||||
)
|
||||
elif isinstance(self.command, commands.Cog):
|
||||
with contextlib.suppress(commands.CommandError):
|
||||
# Cog's don't have a `can_run` method, so we use the `Requires` object directly.
|
||||
await self.command.requires.verify(self.context)
|
||||
|
||||
emb = await self.format()
|
||||
|
||||
if reason:
|
||||
emb["embed"]["title"] = reason
|
||||
|
||||
ret = []
|
||||
|
||||
page_char_limit = await ctx.bot.db.help.page_char_limit()
|
||||
field_groups = self.group_fields(emb["fields"], page_char_limit)
|
||||
|
||||
for i, group in enumerate(field_groups, 1):
|
||||
embed = discord.Embed(color=await self.color(), **emb["embed"])
|
||||
|
||||
if len(field_groups) > 1:
|
||||
description = "{} *- Page {} of {}*".format(
|
||||
embed.description, i, len(field_groups)
|
||||
)
|
||||
embed.description = description
|
||||
|
||||
embed.set_author(**self.author)
|
||||
|
||||
for field in group:
|
||||
embed.add_field(**field._asdict())
|
||||
|
||||
embed.set_footer(**emb["footer"])
|
||||
|
||||
ret.append(embed)
|
||||
|
||||
return ret
|
||||
|
||||
async def format_command_not_found(
|
||||
self, ctx: commands.Context, command_name: str
|
||||
) -> Optional[Union[str, discord.Message]]:
|
||||
"""Get the response for a user calling help on a missing command."""
|
||||
self.context = ctx
|
||||
return await default_command_not_found(
|
||||
ctx,
|
||||
command_name,
|
||||
use_embeds=True,
|
||||
colour=await self.colour(),
|
||||
author=self.author,
|
||||
footer={"text": self.get_ending_note()},
|
||||
)
|
||||
|
||||
|
||||
@commands.command(hidden=True)
|
||||
async def help(ctx: commands.Context, *, command_name: str = ""):
|
||||
"""Show help documentation.
|
||||
|
||||
- `[p]help`: Show the help manual.
|
||||
- `[p]help command`: Show help for a command.
|
||||
- `[p]help Category`: Show commands and description for a category,
|
||||
"""
|
||||
bot = ctx.bot
|
||||
if bot.pm_help:
|
||||
destination = ctx.author
|
||||
else:
|
||||
destination = ctx.channel
|
||||
|
||||
use_embeds = await ctx.embed_requested()
|
||||
if use_embeds:
|
||||
formatter = bot.formatter
|
||||
else:
|
||||
formatter = dpy_formatter.HelpFormatter()
|
||||
|
||||
if not command_name:
|
||||
# help by itself just lists our own commands.
|
||||
pages = await formatter.format_help_for(ctx, bot)
|
||||
else:
|
||||
# First check if it's a cog
|
||||
command = bot.get_cog(command_name)
|
||||
if command is None:
|
||||
command = bot.get_command(command_name)
|
||||
if command is None:
|
||||
if hasattr(formatter, "format_command_not_found"):
|
||||
msg = await formatter.format_command_not_found(ctx, command_name)
|
||||
else:
|
||||
msg = await default_command_not_found(ctx, command_name, use_embeds=use_embeds)
|
||||
pages = [msg]
|
||||
else:
|
||||
pages = await formatter.format_help_for(ctx, command)
|
||||
|
||||
max_pages_in_guild = await ctx.bot.db.help.max_pages_in_guild()
|
||||
if len(pages) > max_pages_in_guild:
|
||||
destination = ctx.author
|
||||
if ctx.guild and not ctx.guild.me.permissions_in(ctx.channel).send_messages:
|
||||
destination = ctx.author
|
||||
try:
|
||||
for page in pages:
|
||||
if isinstance(page, discord.Embed):
|
||||
await destination.send(embed=page)
|
||||
else:
|
||||
await destination.send(page)
|
||||
except discord.Forbidden:
|
||||
await ctx.channel.send(
|
||||
_(
|
||||
"I couldn't send the help message to you in DM. Either you blocked me or you "
|
||||
"disabled DMs in this server."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def default_command_not_found(
|
||||
ctx: commands.Context, command_name: str, *, use_embeds: bool, **embed_options
|
||||
) -> Optional[Union[str, discord.Embed]]:
|
||||
"""Default function for formatting the response to a missing command."""
|
||||
ret = None
|
||||
cmds = command_name.split()
|
||||
prev_command = None
|
||||
for invoked in itertools.accumulate(cmds, lambda *args: " ".join(args)):
|
||||
command = ctx.bot.get_command(invoked)
|
||||
if command is None:
|
||||
if prev_command is not None and not isinstance(prev_command, commands.Group):
|
||||
ret = _("Command *{command_name}* has no subcommands.").format(
|
||||
command_name=prev_command.qualified_name
|
||||
)
|
||||
break
|
||||
elif not await command.can_see(ctx):
|
||||
return
|
||||
prev_command = command
|
||||
|
||||
if ret is None:
|
||||
fuzzy_commands = await fuzzy_command_search(ctx, command_name, min_score=75)
|
||||
if fuzzy_commands:
|
||||
ret = await format_fuzzy_results(ctx, fuzzy_commands, embed=use_embeds)
|
||||
else:
|
||||
ret = _("Command *{command_name}* not found.").format(command_name=command_name)
|
||||
|
||||
if use_embeds:
|
||||
if isinstance(ret, str):
|
||||
ret = discord.Embed(title=ret)
|
||||
if "colour" in embed_options:
|
||||
ret.colour = embed_options.pop("colour")
|
||||
elif "color" in embed_options:
|
||||
ret.colour = embed_options.pop("color")
|
||||
|
||||
if "author" in embed_options:
|
||||
ret.set_author(**embed_options.pop("author"))
|
||||
if "footer" in embed_options:
|
||||
ret.set_footer(**embed_options.pop("footer"))
|
||||
|
||||
return ret
|
||||
@@ -249,10 +249,10 @@ class Case:
|
||||
guild = mod_channel.guild
|
||||
if data["message"]:
|
||||
try:
|
||||
message = await mod_channel.get_message(data["message"])
|
||||
message = await mod_channel.fetch_message(data["message"])
|
||||
except discord.NotFound:
|
||||
message = None
|
||||
user = await bot.get_user_info(data["user"])
|
||||
user = await bot.fetch_user(data["user"])
|
||||
moderator = guild.get_member(data["moderator"])
|
||||
channel = guild.get_channel(data["channel"])
|
||||
amended_by = guild.get_member(data["amended_by"])
|
||||
@@ -489,7 +489,7 @@ async def get_cases_for_member(
|
||||
if not member:
|
||||
member = guild.get_member(member_id)
|
||||
if not member:
|
||||
member = await bot.get_user_info(member_id)
|
||||
member = await bot.fetch_user(member_id)
|
||||
|
||||
try:
|
||||
mod_channel = await get_modlog_channel(guild)
|
||||
@@ -501,7 +501,7 @@ async def get_cases_for_member(
|
||||
message = None
|
||||
if data["message"] and mod_channel:
|
||||
try:
|
||||
message = await mod_channel.get_message(data["message"])
|
||||
message = await mod_channel.fetch_message(data["message"])
|
||||
except discord.NotFound:
|
||||
pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user