mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-09-07 10:22:59 +00:00
Fix typing issues and improve typing completeness across the library
Co-authored-by: Danny <Rapptz@users.noreply.github.com> Co-authored-by: Josh <josh.ja.butt@gmail.com>
This commit is contained in:
@ -23,21 +23,35 @@ DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
|
||||
from typing import Any, Callable, Coroutine, TYPE_CHECKING, TypeVar, Union
|
||||
from typing import Any, Callable, Coroutine, TYPE_CHECKING, TypeVar, Union, Tuple
|
||||
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
from .bot import Bot, AutoShardedBot
|
||||
from .context import Context
|
||||
from .cog import Cog
|
||||
from .errors import CommandError
|
||||
|
||||
T = TypeVar('T')
|
||||
P = ParamSpec('P')
|
||||
MaybeCoroFunc = Union[
|
||||
Callable[P, 'Coro[T]'],
|
||||
Callable[P, T],
|
||||
]
|
||||
else:
|
||||
P = TypeVar('P')
|
||||
MaybeCoroFunc = Tuple[P, T]
|
||||
|
||||
Coro = Coroutine[Any, Any, T]
|
||||
MaybeCoro = Union[T, Coro[T]]
|
||||
CoroFunc = Callable[..., Coro[Any]]
|
||||
|
||||
ContextT = TypeVar('ContextT', bound='Context')
|
||||
_Bot = Union['Bot', 'AutoShardedBot']
|
||||
BotT = TypeVar('BotT', bound=_Bot)
|
||||
|
||||
Check = Union[Callable[["Cog", "ContextT"], MaybeCoro[bool]], Callable[["ContextT"], MaybeCoro[bool]]]
|
||||
Hook = Union[Callable[["Cog", "ContextT"], Coro[Any]], Callable[["ContextT"], Coro[Any]]]
|
||||
|
@ -33,7 +33,21 @@ import importlib.util
|
||||
import sys
|
||||
import traceback
|
||||
import types
|
||||
from typing import Any, Callable, Mapping, List, Dict, TYPE_CHECKING, Optional, TypeVar, Type, Union, overload
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
Mapping,
|
||||
List,
|
||||
Dict,
|
||||
TYPE_CHECKING,
|
||||
Optional,
|
||||
TypeVar,
|
||||
Type,
|
||||
Union,
|
||||
Iterable,
|
||||
Collection,
|
||||
overload,
|
||||
)
|
||||
|
||||
import discord
|
||||
from discord import app_commands
|
||||
@ -55,10 +69,18 @@ if TYPE_CHECKING:
|
||||
from discord.message import Message
|
||||
from discord.abc import User, Snowflake
|
||||
from ._types import (
|
||||
_Bot,
|
||||
BotT,
|
||||
Check,
|
||||
CoroFunc,
|
||||
ContextT,
|
||||
MaybeCoroFunc,
|
||||
)
|
||||
|
||||
_Prefix = Union[Iterable[str], str]
|
||||
_PrefixCallable = MaybeCoroFunc[[BotT, Message], _Prefix]
|
||||
PrefixType = Union[_Prefix, _PrefixCallable[BotT]]
|
||||
|
||||
__all__ = (
|
||||
'when_mentioned',
|
||||
'when_mentioned_or',
|
||||
@ -68,11 +90,9 @@ __all__ = (
|
||||
|
||||
T = TypeVar('T')
|
||||
CFT = TypeVar('CFT', bound='CoroFunc')
|
||||
CXT = TypeVar('CXT', bound='Context')
|
||||
BT = TypeVar('BT', bound='Union[Bot, AutoShardedBot]')
|
||||
|
||||
|
||||
def when_mentioned(bot: Union[Bot, AutoShardedBot], msg: Message) -> List[str]:
|
||||
def when_mentioned(bot: _Bot, msg: Message) -> List[str]:
|
||||
"""A callable that implements a command prefix equivalent to being mentioned.
|
||||
|
||||
These are meant to be passed into the :attr:`.Bot.command_prefix` attribute.
|
||||
@ -81,7 +101,7 @@ def when_mentioned(bot: Union[Bot, AutoShardedBot], msg: Message) -> List[str]:
|
||||
return [f'<@{bot.user.id}> ', f'<@!{bot.user.id}> '] # type: ignore
|
||||
|
||||
|
||||
def when_mentioned_or(*prefixes: str) -> Callable[[Union[Bot, AutoShardedBot], Message], List[str]]:
|
||||
def when_mentioned_or(*prefixes: str) -> Callable[[_Bot, Message], List[str]]:
|
||||
"""A callable that implements when mentioned or other prefixes provided.
|
||||
|
||||
These are meant to be passed into the :attr:`.Bot.command_prefix` attribute.
|
||||
@ -124,27 +144,33 @@ class _DefaultRepr:
|
||||
return '<default-help-command>'
|
||||
|
||||
|
||||
_default = _DefaultRepr()
|
||||
_default: Any = _DefaultRepr()
|
||||
|
||||
|
||||
class BotBase(GroupMixin):
|
||||
def __init__(self, command_prefix, help_command=_default, description=None, **options):
|
||||
class BotBase(GroupMixin[None]):
|
||||
def __init__(
|
||||
self,
|
||||
command_prefix: PrefixType[BotT],
|
||||
help_command: HelpCommand = _default,
|
||||
description: Optional[str] = None,
|
||||
**options: Any,
|
||||
) -> None:
|
||||
super().__init__(**options)
|
||||
self.command_prefix = command_prefix
|
||||
self.command_prefix: PrefixType[BotT] = command_prefix
|
||||
self.extra_events: Dict[str, List[CoroFunc]] = {}
|
||||
# Self doesn't have the ClientT bound, but since this is a mixin it technically does
|
||||
self.__tree: app_commands.CommandTree[Self] = app_commands.CommandTree(self) # type: ignore
|
||||
self.__cogs: Dict[str, Cog] = {}
|
||||
self.__extensions: Dict[str, types.ModuleType] = {}
|
||||
self._checks: List[Check] = []
|
||||
self._check_once = []
|
||||
self._before_invoke = None
|
||||
self._after_invoke = None
|
||||
self._help_command = None
|
||||
self.description = inspect.cleandoc(description) if description else ''
|
||||
self.owner_id = options.get('owner_id')
|
||||
self.owner_ids = options.get('owner_ids', set())
|
||||
self.strip_after_prefix = options.get('strip_after_prefix', False)
|
||||
self._check_once: List[Check] = []
|
||||
self._before_invoke: Optional[CoroFunc] = None
|
||||
self._after_invoke: Optional[CoroFunc] = None
|
||||
self._help_command: Optional[HelpCommand] = None
|
||||
self.description: str = inspect.cleandoc(description) if description else ''
|
||||
self.owner_id: Optional[int] = options.get('owner_id')
|
||||
self.owner_ids: Optional[Collection[int]] = options.get('owner_ids', set())
|
||||
self.strip_after_prefix: bool = options.get('strip_after_prefix', False)
|
||||
|
||||
if self.owner_id and self.owner_ids:
|
||||
raise TypeError('Both owner_id and owner_ids are set.')
|
||||
@ -182,7 +208,7 @@ class BotBase(GroupMixin):
|
||||
|
||||
await super().close() # type: ignore
|
||||
|
||||
async def on_command_error(self, context: Context, exception: errors.CommandError) -> None:
|
||||
async def on_command_error(self, context: Context[BotT], exception: errors.CommandError) -> None:
|
||||
"""|coro|
|
||||
|
||||
The default command error handler provided by the bot.
|
||||
@ -237,7 +263,7 @@ class BotBase(GroupMixin):
|
||||
self.add_check(func) # type: ignore
|
||||
return func
|
||||
|
||||
def add_check(self, func: Check, /, *, call_once: bool = False) -> None:
|
||||
def add_check(self, func: Check[ContextT], /, *, call_once: bool = False) -> None:
|
||||
"""Adds a global check to the bot.
|
||||
|
||||
This is the non-decorator interface to :meth:`.check`
|
||||
@ -261,7 +287,7 @@ class BotBase(GroupMixin):
|
||||
else:
|
||||
self._checks.append(func)
|
||||
|
||||
def remove_check(self, func: Check, /, *, call_once: bool = False) -> None:
|
||||
def remove_check(self, func: Check[ContextT], /, *, call_once: bool = False) -> None:
|
||||
"""Removes a global check from the bot.
|
||||
|
||||
This function is idempotent and will not raise an exception
|
||||
@ -324,7 +350,7 @@ class BotBase(GroupMixin):
|
||||
self.add_check(func, call_once=True)
|
||||
return func
|
||||
|
||||
async def can_run(self, ctx: Context, *, call_once: bool = False) -> bool:
|
||||
async def can_run(self, ctx: Context[BotT], *, call_once: bool = False) -> bool:
|
||||
data = self._check_once if call_once else self._checks
|
||||
|
||||
if len(data) == 0:
|
||||
@ -947,7 +973,7 @@ class BotBase(GroupMixin):
|
||||
# if the load failed, the remnants should have been
|
||||
# cleaned from the load_extension function call
|
||||
# so let's load it from our old compiled library.
|
||||
await lib.setup(self) # type: ignore
|
||||
await lib.setup(self)
|
||||
self.__extensions[name] = lib
|
||||
|
||||
# revert sys.modules back to normal and raise back to caller
|
||||
@ -1015,11 +1041,12 @@ class BotBase(GroupMixin):
|
||||
"""
|
||||
prefix = ret = self.command_prefix
|
||||
if callable(prefix):
|
||||
ret = await discord.utils.maybe_coroutine(prefix, self, message)
|
||||
# self will be a Bot or AutoShardedBot
|
||||
ret = await discord.utils.maybe_coroutine(prefix, self, message) # type: ignore
|
||||
|
||||
if not isinstance(ret, str):
|
||||
try:
|
||||
ret = list(ret)
|
||||
ret = list(ret) # type: ignore
|
||||
except TypeError:
|
||||
# It's possible that a generator raised this exception. Don't
|
||||
# replace it with our own error if that's the case.
|
||||
@ -1048,15 +1075,15 @@ class BotBase(GroupMixin):
|
||||
self,
|
||||
message: Message,
|
||||
*,
|
||||
cls: Type[CXT] = ...,
|
||||
) -> CXT: # type: ignore
|
||||
cls: Type[ContextT] = ...,
|
||||
) -> ContextT:
|
||||
...
|
||||
|
||||
async def get_context(
|
||||
self,
|
||||
message: Message,
|
||||
*,
|
||||
cls: Type[CXT] = MISSING,
|
||||
cls: Type[ContextT] = MISSING,
|
||||
) -> Any:
|
||||
r"""|coro|
|
||||
|
||||
@ -1137,7 +1164,7 @@ class BotBase(GroupMixin):
|
||||
ctx.command = self.all_commands.get(invoker)
|
||||
return ctx
|
||||
|
||||
async def invoke(self, ctx: Context) -> None:
|
||||
async def invoke(self, ctx: Context[BotT]) -> None:
|
||||
"""|coro|
|
||||
|
||||
Invokes the command given under the invocation context and
|
||||
@ -1189,9 +1216,10 @@ class BotBase(GroupMixin):
|
||||
return
|
||||
|
||||
ctx = await self.get_context(message)
|
||||
await self.invoke(ctx)
|
||||
# the type of the invocation context's bot attribute will be correct
|
||||
await self.invoke(ctx) # type: ignore
|
||||
|
||||
async def on_message(self, message):
|
||||
async def on_message(self, message: Message) -> None:
|
||||
await self.process_commands(message)
|
||||
|
||||
|
||||
|
@ -30,7 +30,7 @@ from discord.utils import maybe_coroutine
|
||||
|
||||
from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, TYPE_CHECKING, Tuple, TypeVar, Union
|
||||
|
||||
from ._types import _BaseCommand
|
||||
from ._types import _BaseCommand, BotT
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
@ -112,7 +112,7 @@ class CogMeta(type):
|
||||
|
||||
__cog_name__: str
|
||||
__cog_settings__: Dict[str, Any]
|
||||
__cog_commands__: List[Command]
|
||||
__cog_commands__: List[Command[Any, ..., Any]]
|
||||
__cog_is_app_commands_group__: bool
|
||||
__cog_app_commands__: List[Union[app_commands.Group, app_commands.Command[Any, ..., Any]]]
|
||||
__cog_listeners__: List[Tuple[str, str]]
|
||||
@ -406,7 +406,7 @@ class Cog(metaclass=CogMeta):
|
||||
pass
|
||||
|
||||
@_cog_special_method
|
||||
def bot_check_once(self, ctx: Context) -> bool:
|
||||
def bot_check_once(self, ctx: Context[BotT]) -> bool:
|
||||
"""A special method that registers as a :meth:`.Bot.check_once`
|
||||
check.
|
||||
|
||||
@ -416,7 +416,7 @@ class Cog(metaclass=CogMeta):
|
||||
return True
|
||||
|
||||
@_cog_special_method
|
||||
def bot_check(self, ctx: Context) -> bool:
|
||||
def bot_check(self, ctx: Context[BotT]) -> bool:
|
||||
"""A special method that registers as a :meth:`.Bot.check`
|
||||
check.
|
||||
|
||||
@ -426,7 +426,7 @@ class Cog(metaclass=CogMeta):
|
||||
return True
|
||||
|
||||
@_cog_special_method
|
||||
def cog_check(self, ctx: Context) -> bool:
|
||||
def cog_check(self, ctx: Context[BotT]) -> bool:
|
||||
"""A special method that registers as a :func:`~discord.ext.commands.check`
|
||||
for every command and subcommand in this cog.
|
||||
|
||||
@ -436,7 +436,7 @@ class Cog(metaclass=CogMeta):
|
||||
return True
|
||||
|
||||
@_cog_special_method
|
||||
async def cog_command_error(self, ctx: Context, error: Exception) -> None:
|
||||
async def cog_command_error(self, ctx: Context[BotT], error: Exception) -> None:
|
||||
"""A special method that is called whenever an error
|
||||
is dispatched inside this cog.
|
||||
|
||||
@ -455,7 +455,7 @@ class Cog(metaclass=CogMeta):
|
||||
pass
|
||||
|
||||
@_cog_special_method
|
||||
async def cog_before_invoke(self, ctx: Context) -> None:
|
||||
async def cog_before_invoke(self, ctx: Context[BotT]) -> None:
|
||||
"""A special method that acts as a cog local pre-invoke hook.
|
||||
|
||||
This is similar to :meth:`.Command.before_invoke`.
|
||||
@ -470,7 +470,7 @@ class Cog(metaclass=CogMeta):
|
||||
pass
|
||||
|
||||
@_cog_special_method
|
||||
async def cog_after_invoke(self, ctx: Context) -> None:
|
||||
async def cog_after_invoke(self, ctx: Context[BotT]) -> None:
|
||||
"""A special method that acts as a cog local post-invoke hook.
|
||||
|
||||
This is similar to :meth:`.Command.after_invoke`.
|
||||
|
@ -28,6 +28,8 @@ import re
|
||||
|
||||
from typing import Any, Dict, Generic, List, Optional, TYPE_CHECKING, TypeVar, Union
|
||||
|
||||
from ._types import BotT
|
||||
|
||||
import discord.abc
|
||||
import discord.utils
|
||||
|
||||
@ -59,7 +61,6 @@ MISSING: Any = discord.utils.MISSING
|
||||
|
||||
|
||||
T = TypeVar('T')
|
||||
BotT = TypeVar('BotT', bound="Union[Bot, AutoShardedBot]")
|
||||
CogT = TypeVar('CogT', bound="Cog")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -133,10 +134,10 @@ class Context(discord.abc.Messageable, Generic[BotT]):
|
||||
args: List[Any] = MISSING,
|
||||
kwargs: Dict[str, Any] = MISSING,
|
||||
prefix: Optional[str] = None,
|
||||
command: Optional[Command] = None,
|
||||
command: Optional[Command[Any, ..., Any]] = None,
|
||||
invoked_with: Optional[str] = None,
|
||||
invoked_parents: List[str] = MISSING,
|
||||
invoked_subcommand: Optional[Command] = None,
|
||||
invoked_subcommand: Optional[Command[Any, ..., Any]] = None,
|
||||
subcommand_passed: Optional[str] = None,
|
||||
command_failed: bool = False,
|
||||
current_parameter: Optional[inspect.Parameter] = None,
|
||||
@ -146,11 +147,11 @@ class Context(discord.abc.Messageable, Generic[BotT]):
|
||||
self.args: List[Any] = args or []
|
||||
self.kwargs: Dict[str, Any] = kwargs or {}
|
||||
self.prefix: Optional[str] = prefix
|
||||
self.command: Optional[Command] = command
|
||||
self.command: Optional[Command[Any, ..., Any]] = command
|
||||
self.view: StringView = view
|
||||
self.invoked_with: Optional[str] = invoked_with
|
||||
self.invoked_parents: List[str] = invoked_parents or []
|
||||
self.invoked_subcommand: Optional[Command] = invoked_subcommand
|
||||
self.invoked_subcommand: Optional[Command[Any, ..., Any]] = invoked_subcommand
|
||||
self.subcommand_passed: Optional[str] = subcommand_passed
|
||||
self.command_failed: bool = command_failed
|
||||
self.current_parameter: Optional[inspect.Parameter] = current_parameter
|
||||
@ -361,7 +362,7 @@ class Context(discord.abc.Messageable, Generic[BotT]):
|
||||
return None
|
||||
|
||||
cmd = cmd.copy()
|
||||
cmd.context = self
|
||||
cmd.context = self # type: ignore
|
||||
if len(args) == 0:
|
||||
await cmd.prepare_help_command(self, None)
|
||||
mapping = cmd.get_bot_mapping()
|
||||
@ -390,7 +391,7 @@ class Context(discord.abc.Messageable, Generic[BotT]):
|
||||
try:
|
||||
if hasattr(entity, '__cog_commands__'):
|
||||
injected = wrap_callback(cmd.send_cog_help)
|
||||
return await injected(entity)
|
||||
return await injected(entity) # type: ignore
|
||||
elif isinstance(entity, Group):
|
||||
injected = wrap_callback(cmd.send_group_help)
|
||||
return await injected(entity)
|
||||
|
@ -41,7 +41,6 @@ from typing import (
|
||||
Tuple,
|
||||
Union,
|
||||
runtime_checkable,
|
||||
overload,
|
||||
)
|
||||
|
||||
import discord
|
||||
@ -51,9 +50,8 @@ if TYPE_CHECKING:
|
||||
from .context import Context
|
||||
from discord.state import Channel
|
||||
from discord.threads import Thread
|
||||
from .bot import Bot, AutoShardedBot
|
||||
|
||||
_Bot = TypeVar('_Bot', bound=Union[Bot, AutoShardedBot])
|
||||
from ._types import BotT, _Bot
|
||||
|
||||
|
||||
__all__ = (
|
||||
@ -87,7 +85,7 @@ __all__ = (
|
||||
)
|
||||
|
||||
|
||||
def _get_from_guilds(bot, getter, argument):
|
||||
def _get_from_guilds(bot: _Bot, getter: str, argument: Any) -> Any:
|
||||
result = None
|
||||
for guild in bot.guilds:
|
||||
result = getattr(guild, getter)(argument)
|
||||
@ -115,7 +113,7 @@ class Converter(Protocol[T_co]):
|
||||
method to do its conversion logic. This method must be a :ref:`coroutine <coroutine>`.
|
||||
"""
|
||||
|
||||
async def convert(self, ctx: Context, argument: str) -> T_co:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> T_co:
|
||||
"""|coro|
|
||||
|
||||
The method to override to do conversion logic.
|
||||
@ -163,7 +161,7 @@ class ObjectConverter(IDConverter[discord.Object]):
|
||||
2. Lookup by member, role, or channel mention.
|
||||
"""
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.Object:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.Object:
|
||||
match = self._get_id_match(argument) or re.match(r'<(?:@(?:!|&)?|#)([0-9]{15,20})>$', argument)
|
||||
|
||||
if match is None:
|
||||
@ -196,7 +194,7 @@ class MemberConverter(IDConverter[discord.Member]):
|
||||
optionally caching the result if :attr:`.MemberCacheFlags.joined` is enabled.
|
||||
"""
|
||||
|
||||
async def query_member_named(self, guild, argument):
|
||||
async def query_member_named(self, guild: discord.Guild, argument: str) -> Optional[discord.Member]:
|
||||
cache = guild._state.member_cache_flags.joined
|
||||
if len(argument) > 5 and argument[-5] == '#':
|
||||
username, _, discriminator = argument.rpartition('#')
|
||||
@ -206,7 +204,7 @@ class MemberConverter(IDConverter[discord.Member]):
|
||||
members = await guild.query_members(argument, limit=100, cache=cache)
|
||||
return discord.utils.find(lambda m: m.name == argument or m.nick == argument, members)
|
||||
|
||||
async def query_member_by_id(self, bot, guild, user_id):
|
||||
async def query_member_by_id(self, bot: _Bot, guild: discord.Guild, user_id: int) -> Optional[discord.Member]:
|
||||
ws = bot._get_websocket(shard_id=guild.shard_id)
|
||||
cache = guild._state.member_cache_flags.joined
|
||||
if ws.is_ratelimited():
|
||||
@ -227,7 +225,7 @@ class MemberConverter(IDConverter[discord.Member]):
|
||||
return None
|
||||
return members[0]
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.Member:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.Member:
|
||||
bot = ctx.bot
|
||||
match = self._get_id_match(argument) or re.match(r'<@!?([0-9]{15,20})>$', argument)
|
||||
guild = ctx.guild
|
||||
@ -281,7 +279,7 @@ class UserConverter(IDConverter[discord.User]):
|
||||
and it's not available in cache.
|
||||
"""
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.User:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.User:
|
||||
match = self._get_id_match(argument) or re.match(r'<@!?([0-9]{15,20})>$', argument)
|
||||
result = None
|
||||
state = ctx._state
|
||||
@ -359,7 +357,7 @@ class PartialMessageConverter(Converter[discord.PartialMessage]):
|
||||
|
||||
@staticmethod
|
||||
def _resolve_channel(
|
||||
ctx: Context[_Bot], guild_id: Optional[int], channel_id: Optional[int]
|
||||
ctx: Context[BotT], guild_id: Optional[int], channel_id: Optional[int]
|
||||
) -> Optional[Union[Channel, Thread]]:
|
||||
if channel_id is None:
|
||||
# we were passed just a message id so we can assume the channel is the current context channel
|
||||
@ -373,7 +371,7 @@ class PartialMessageConverter(Converter[discord.PartialMessage]):
|
||||
|
||||
return ctx.bot.get_channel(channel_id)
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.PartialMessage:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.PartialMessage:
|
||||
guild_id, message_id, channel_id = self._get_id_matches(ctx, argument)
|
||||
channel = self._resolve_channel(ctx, guild_id, channel_id)
|
||||
if not channel or not isinstance(channel, discord.abc.Messageable):
|
||||
@ -396,7 +394,7 @@ class MessageConverter(IDConverter[discord.Message]):
|
||||
Raise :exc:`.ChannelNotFound`, :exc:`.MessageNotFound` or :exc:`.ChannelNotReadable` instead of generic :exc:`.BadArgument`
|
||||
"""
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.Message:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.Message:
|
||||
guild_id, message_id, channel_id = PartialMessageConverter._get_id_matches(ctx, argument)
|
||||
message = ctx.bot._connection._get_message(message_id)
|
||||
if message:
|
||||
@ -427,11 +425,11 @@ class GuildChannelConverter(IDConverter[discord.abc.GuildChannel]):
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.abc.GuildChannel:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.abc.GuildChannel:
|
||||
return self._resolve_channel(ctx, argument, 'channels', discord.abc.GuildChannel)
|
||||
|
||||
@staticmethod
|
||||
def _resolve_channel(ctx: Context, argument: str, attribute: str, type: Type[CT]) -> CT:
|
||||
def _resolve_channel(ctx: Context[BotT], argument: str, attribute: str, type: Type[CT]) -> CT:
|
||||
bot = ctx.bot
|
||||
|
||||
match = IDConverter._get_id_match(argument) or re.match(r'<#([0-9]{15,20})>$', argument)
|
||||
@ -448,7 +446,7 @@ class GuildChannelConverter(IDConverter[discord.abc.GuildChannel]):
|
||||
def check(c):
|
||||
return isinstance(c, type) and c.name == argument
|
||||
|
||||
result = discord.utils.find(check, bot.get_all_channels())
|
||||
result = discord.utils.find(check, bot.get_all_channels()) # type: ignore
|
||||
else:
|
||||
channel_id = int(match.group(1))
|
||||
if guild:
|
||||
@ -463,7 +461,7 @@ class GuildChannelConverter(IDConverter[discord.abc.GuildChannel]):
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def _resolve_thread(ctx: Context, argument: str, attribute: str, type: Type[TT]) -> TT:
|
||||
def _resolve_thread(ctx: Context[BotT], argument: str, attribute: str, type: Type[TT]) -> TT:
|
||||
bot = ctx.bot
|
||||
|
||||
match = IDConverter._get_id_match(argument) or re.match(r'<#([0-9]{15,20})>$', argument)
|
||||
@ -502,7 +500,7 @@ class TextChannelConverter(IDConverter[discord.TextChannel]):
|
||||
Raise :exc:`.ChannelNotFound` instead of generic :exc:`.BadArgument`
|
||||
"""
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.TextChannel:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.TextChannel:
|
||||
return GuildChannelConverter._resolve_channel(ctx, argument, 'text_channels', discord.TextChannel)
|
||||
|
||||
|
||||
@ -522,7 +520,7 @@ class VoiceChannelConverter(IDConverter[discord.VoiceChannel]):
|
||||
Raise :exc:`.ChannelNotFound` instead of generic :exc:`.BadArgument`
|
||||
"""
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.VoiceChannel:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.VoiceChannel:
|
||||
return GuildChannelConverter._resolve_channel(ctx, argument, 'voice_channels', discord.VoiceChannel)
|
||||
|
||||
|
||||
@ -541,7 +539,7 @@ class StageChannelConverter(IDConverter[discord.StageChannel]):
|
||||
3. Lookup by name
|
||||
"""
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.StageChannel:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.StageChannel:
|
||||
return GuildChannelConverter._resolve_channel(ctx, argument, 'stage_channels', discord.StageChannel)
|
||||
|
||||
|
||||
@ -561,7 +559,7 @@ class CategoryChannelConverter(IDConverter[discord.CategoryChannel]):
|
||||
Raise :exc:`.ChannelNotFound` instead of generic :exc:`.BadArgument`
|
||||
"""
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.CategoryChannel:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.CategoryChannel:
|
||||
return GuildChannelConverter._resolve_channel(ctx, argument, 'categories', discord.CategoryChannel)
|
||||
|
||||
|
||||
@ -580,7 +578,7 @@ class StoreChannelConverter(IDConverter[discord.StoreChannel]):
|
||||
.. versionadded:: 1.7
|
||||
"""
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.StoreChannel:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.StoreChannel:
|
||||
return GuildChannelConverter._resolve_channel(ctx, argument, 'channels', discord.StoreChannel)
|
||||
|
||||
|
||||
@ -598,7 +596,7 @@ class ThreadConverter(IDConverter[discord.Thread]):
|
||||
.. versionadded: 2.0
|
||||
"""
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.Thread:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.Thread:
|
||||
return GuildChannelConverter._resolve_thread(ctx, argument, 'threads', discord.Thread)
|
||||
|
||||
|
||||
@ -630,7 +628,7 @@ class ColourConverter(Converter[discord.Colour]):
|
||||
|
||||
RGB_REGEX = re.compile(r'rgb\s*\((?P<r>[0-9]{1,3}%?)\s*,\s*(?P<g>[0-9]{1,3}%?)\s*,\s*(?P<b>[0-9]{1,3}%?)\s*\)')
|
||||
|
||||
def parse_hex_number(self, argument):
|
||||
def parse_hex_number(self, argument: str) -> discord.Colour:
|
||||
arg = ''.join(i * 2 for i in argument) if len(argument) == 3 else argument
|
||||
try:
|
||||
value = int(arg, base=16)
|
||||
@ -641,7 +639,7 @@ class ColourConverter(Converter[discord.Colour]):
|
||||
else:
|
||||
return discord.Color(value=value)
|
||||
|
||||
def parse_rgb_number(self, argument, number):
|
||||
def parse_rgb_number(self, argument: str, number: str) -> int:
|
||||
if number[-1] == '%':
|
||||
value = int(number[:-1])
|
||||
if not (0 <= value <= 100):
|
||||
@ -653,7 +651,7 @@ class ColourConverter(Converter[discord.Colour]):
|
||||
raise BadColourArgument(argument)
|
||||
return value
|
||||
|
||||
def parse_rgb(self, argument, *, regex=RGB_REGEX):
|
||||
def parse_rgb(self, argument: str, *, regex: re.Pattern[str] = RGB_REGEX) -> discord.Colour:
|
||||
match = regex.match(argument)
|
||||
if match is None:
|
||||
raise BadColourArgument(argument)
|
||||
@ -663,7 +661,7 @@ class ColourConverter(Converter[discord.Colour]):
|
||||
blue = self.parse_rgb_number(argument, match.group('b'))
|
||||
return discord.Color.from_rgb(red, green, blue)
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.Colour:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.Colour:
|
||||
if argument[0] == '#':
|
||||
return self.parse_hex_number(argument[1:])
|
||||
|
||||
@ -704,7 +702,7 @@ class RoleConverter(IDConverter[discord.Role]):
|
||||
Raise :exc:`.RoleNotFound` instead of generic :exc:`.BadArgument`
|
||||
"""
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.Role:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.Role:
|
||||
guild = ctx.guild
|
||||
if not guild:
|
||||
raise NoPrivateMessage()
|
||||
@ -723,7 +721,7 @@ class RoleConverter(IDConverter[discord.Role]):
|
||||
class GameConverter(Converter[discord.Game]):
|
||||
"""Converts to :class:`~discord.Game`."""
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.Game:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.Game:
|
||||
return discord.Game(name=argument)
|
||||
|
||||
|
||||
@ -736,7 +734,7 @@ class InviteConverter(Converter[discord.Invite]):
|
||||
Raise :exc:`.BadInviteArgument` instead of generic :exc:`.BadArgument`
|
||||
"""
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.Invite:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.Invite:
|
||||
try:
|
||||
invite = await ctx.bot.fetch_invite(argument)
|
||||
return invite
|
||||
@ -755,7 +753,7 @@ class GuildConverter(IDConverter[discord.Guild]):
|
||||
.. versionadded:: 1.7
|
||||
"""
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.Guild:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.Guild:
|
||||
match = self._get_id_match(argument)
|
||||
result = None
|
||||
|
||||
@ -787,7 +785,7 @@ class EmojiConverter(IDConverter[discord.Emoji]):
|
||||
Raise :exc:`.EmojiNotFound` instead of generic :exc:`.BadArgument`
|
||||
"""
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.Emoji:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.Emoji:
|
||||
match = self._get_id_match(argument) or re.match(r'<a?:[a-zA-Z0-9\_]{1,32}:([0-9]{15,20})>$', argument)
|
||||
result = None
|
||||
bot = ctx.bot
|
||||
@ -821,7 +819,7 @@ class PartialEmojiConverter(Converter[discord.PartialEmoji]):
|
||||
Raise :exc:`.PartialEmojiConversionFailure` instead of generic :exc:`.BadArgument`
|
||||
"""
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.PartialEmoji:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.PartialEmoji:
|
||||
match = re.match(r'<(a?):([a-zA-Z0-9\_]{1,32}):([0-9]{15,20})>$', argument)
|
||||
|
||||
if match:
|
||||
@ -850,7 +848,7 @@ class GuildStickerConverter(IDConverter[discord.GuildSticker]):
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.GuildSticker:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.GuildSticker:
|
||||
match = self._get_id_match(argument)
|
||||
result = None
|
||||
bot = ctx.bot
|
||||
@ -890,7 +888,7 @@ class ScheduledEventConverter(IDConverter[discord.ScheduledEvent]):
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> discord.ScheduledEvent:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> discord.ScheduledEvent:
|
||||
guild = ctx.guild
|
||||
match = self._get_id_match(argument)
|
||||
result = None
|
||||
@ -967,7 +965,7 @@ class clean_content(Converter[str]):
|
||||
self.escape_markdown = escape_markdown
|
||||
self.remove_markdown = remove_markdown
|
||||
|
||||
async def convert(self, ctx: Context[_Bot], argument: str) -> str:
|
||||
async def convert(self, ctx: Context[BotT], argument: str) -> str:
|
||||
msg = ctx.message
|
||||
|
||||
if ctx.guild:
|
||||
@ -1047,10 +1045,10 @@ class Greedy(List[T]):
|
||||
|
||||
__slots__ = ('converter',)
|
||||
|
||||
def __init__(self, *, converter: T):
|
||||
self.converter = converter
|
||||
def __init__(self, *, converter: T) -> None:
|
||||
self.converter: T = converter
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
converter = getattr(self.converter, '__name__', repr(self.converter))
|
||||
return f'Greedy[{converter}]'
|
||||
|
||||
@ -1099,11 +1097,11 @@ def get_converter(param: inspect.Parameter) -> Any:
|
||||
_GenericAlias = type(List[T])
|
||||
|
||||
|
||||
def is_generic_type(tp: Any, *, _GenericAlias: Type = _GenericAlias) -> bool:
|
||||
return isinstance(tp, type) and issubclass(tp, Generic) or isinstance(tp, _GenericAlias) # type: ignore
|
||||
def is_generic_type(tp: Any, *, _GenericAlias: type = _GenericAlias) -> bool:
|
||||
return isinstance(tp, type) and issubclass(tp, Generic) or isinstance(tp, _GenericAlias)
|
||||
|
||||
|
||||
CONVERTER_MAPPING: Dict[Type[Any], Any] = {
|
||||
CONVERTER_MAPPING: Dict[type, Any] = {
|
||||
discord.Object: ObjectConverter,
|
||||
discord.Member: MemberConverter,
|
||||
discord.User: UserConverter,
|
||||
@ -1128,7 +1126,7 @@ CONVERTER_MAPPING: Dict[Type[Any], Any] = {
|
||||
}
|
||||
|
||||
|
||||
async def _actual_conversion(ctx: Context, converter, argument: str, param: inspect.Parameter):
|
||||
async def _actual_conversion(ctx: Context[BotT], converter, argument: str, param: inspect.Parameter):
|
||||
if converter is bool:
|
||||
return _convert_to_bool(argument)
|
||||
|
||||
@ -1166,7 +1164,7 @@ async def _actual_conversion(ctx: Context, converter, argument: str, param: insp
|
||||
raise BadArgument(f'Converting to "{name}" failed for parameter "{param.name}".') from exc
|
||||
|
||||
|
||||
async def run_converters(ctx: Context, converter, argument: str, param: inspect.Parameter):
|
||||
async def run_converters(ctx: Context[BotT], converter: Any, argument: str, param: inspect.Parameter) -> Any:
|
||||
"""|coro|
|
||||
|
||||
Runs converters for a given converter, argument, and parameter.
|
||||
|
@ -220,7 +220,7 @@ class CooldownMapping:
|
||||
return self._type
|
||||
|
||||
@classmethod
|
||||
def from_cooldown(cls, rate, per, type) -> Self:
|
||||
def from_cooldown(cls, rate: float, per: float, type: Callable[[Message], Any]) -> Self:
|
||||
return cls(Cooldown(rate, per), type)
|
||||
|
||||
def _bucket_key(self, msg: Message) -> Any:
|
||||
|
@ -61,6 +61,8 @@ if TYPE_CHECKING:
|
||||
from discord.message import Message
|
||||
|
||||
from ._types import (
|
||||
BotT,
|
||||
ContextT,
|
||||
Coro,
|
||||
CoroFunc,
|
||||
Check,
|
||||
@ -101,7 +103,6 @@ MISSING: Any = discord.utils.MISSING
|
||||
T = TypeVar('T')
|
||||
CogT = TypeVar('CogT', bound='Optional[Cog]')
|
||||
CommandT = TypeVar('CommandT', bound='Command')
|
||||
ContextT = TypeVar('ContextT', bound='Context')
|
||||
# CHT = TypeVar('CHT', bound='Check')
|
||||
GroupT = TypeVar('GroupT', bound='Group')
|
||||
FuncT = TypeVar('FuncT', bound=Callable[..., Any])
|
||||
@ -159,9 +160,9 @@ def get_signature_parameters(
|
||||
return params
|
||||
|
||||
|
||||
def wrap_callback(coro):
|
||||
def wrap_callback(coro: Callable[P, Coro[T]]) -> Callable[P, Coro[Optional[T]]]:
|
||||
@functools.wraps(coro)
|
||||
async def wrapped(*args, **kwargs):
|
||||
async def wrapped(*args: P.args, **kwargs: P.kwargs) -> Optional[T]:
|
||||
try:
|
||||
ret = await coro(*args, **kwargs)
|
||||
except CommandError:
|
||||
@ -175,9 +176,11 @@ def wrap_callback(coro):
|
||||
return wrapped
|
||||
|
||||
|
||||
def hooked_wrapped_callback(command, ctx, coro):
|
||||
def hooked_wrapped_callback(
|
||||
command: Command[Any, ..., Any], ctx: Context[BotT], coro: Callable[P, Coro[T]]
|
||||
) -> Callable[P, Coro[Optional[T]]]:
|
||||
@functools.wraps(coro)
|
||||
async def wrapped(*args, **kwargs):
|
||||
async def wrapped(*args: P.args, **kwargs: P.kwargs) -> Optional[T]:
|
||||
try:
|
||||
ret = await coro(*args, **kwargs)
|
||||
except CommandError:
|
||||
@ -191,7 +194,7 @@ def hooked_wrapped_callback(command, ctx, coro):
|
||||
raise CommandInvokeError(exc) from exc
|
||||
finally:
|
||||
if command._max_concurrency is not None:
|
||||
await command._max_concurrency.release(ctx)
|
||||
await command._max_concurrency.release(ctx.message)
|
||||
|
||||
await command.call_after_hooks(ctx)
|
||||
return ret
|
||||
@ -359,7 +362,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
except AttributeError:
|
||||
checks = kwargs.get('checks', [])
|
||||
|
||||
self.checks: List[Check] = checks
|
||||
self.checks: List[Check[ContextT]] = checks
|
||||
|
||||
try:
|
||||
cooldown = func.__commands_cooldown__
|
||||
@ -387,8 +390,8 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
self.cog: CogT = None
|
||||
|
||||
# bandaid for the fact that sometimes parent can be the bot instance
|
||||
parent = kwargs.get('parent')
|
||||
self.parent: Optional[GroupMixin] = parent if isinstance(parent, _BaseCommand) else None # type: ignore
|
||||
parent: Optional[GroupMixin[Any]] = kwargs.get('parent')
|
||||
self.parent: Optional[GroupMixin[Any]] = parent if isinstance(parent, _BaseCommand) else None
|
||||
|
||||
self._before_invoke: Optional[Hook] = None
|
||||
try:
|
||||
@ -422,16 +425,16 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
) -> None:
|
||||
self._callback = function
|
||||
unwrap = unwrap_function(function)
|
||||
self.module = unwrap.__module__
|
||||
self.module: str = unwrap.__module__
|
||||
|
||||
try:
|
||||
globalns = unwrap.__globals__
|
||||
except AttributeError:
|
||||
globalns = {}
|
||||
|
||||
self.params = get_signature_parameters(function, globalns)
|
||||
self.params: Dict[str, inspect.Parameter] = get_signature_parameters(function, globalns)
|
||||
|
||||
def add_check(self, func: Check, /) -> None:
|
||||
def add_check(self, func: Check[ContextT], /) -> None:
|
||||
"""Adds a check to the command.
|
||||
|
||||
This is the non-decorator interface to :func:`.check`.
|
||||
@ -450,7 +453,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
|
||||
self.checks.append(func)
|
||||
|
||||
def remove_check(self, func: Check, /) -> None:
|
||||
def remove_check(self, func: Check[ContextT], /) -> None:
|
||||
"""Removes a check from the command.
|
||||
|
||||
This function is idempotent and will not raise an exception
|
||||
@ -484,7 +487,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
self.__init__(self.callback, **dict(self.__original_kwargs__, **kwargs))
|
||||
self.cog = cog
|
||||
|
||||
async def __call__(self, context: Context, *args: P.args, **kwargs: P.kwargs) -> T:
|
||||
async def __call__(self, context: Context[BotT], *args: P.args, **kwargs: P.kwargs) -> T:
|
||||
"""|coro|
|
||||
|
||||
Calls the internal callback that the command holds.
|
||||
@ -539,7 +542,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
else:
|
||||
return self.copy()
|
||||
|
||||
async def dispatch_error(self, ctx: Context, error: Exception) -> None:
|
||||
async def dispatch_error(self, ctx: Context[BotT], error: CommandError) -> None:
|
||||
ctx.command_failed = True
|
||||
cog = self.cog
|
||||
try:
|
||||
@ -549,7 +552,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
else:
|
||||
injected = wrap_callback(coro)
|
||||
if cog is not None:
|
||||
await injected(cog, ctx, error)
|
||||
await injected(cog, ctx, error) # type: ignore
|
||||
else:
|
||||
await injected(ctx, error)
|
||||
|
||||
@ -562,7 +565,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
finally:
|
||||
ctx.bot.dispatch('command_error', ctx, error)
|
||||
|
||||
async def transform(self, ctx: Context, param: inspect.Parameter) -> Any:
|
||||
async def transform(self, ctx: Context[BotT], param: inspect.Parameter) -> Any:
|
||||
required = param.default is param.empty
|
||||
converter = get_converter(param)
|
||||
consume_rest_is_special = param.kind == param.KEYWORD_ONLY and not self.rest_is_raw
|
||||
@ -610,7 +613,9 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
# type-checker fails to narrow argument
|
||||
return await run_converters(ctx, converter, argument, param) # type: ignore
|
||||
|
||||
async def _transform_greedy_pos(self, ctx: Context, param: inspect.Parameter, required: bool, converter: Any) -> Any:
|
||||
async def _transform_greedy_pos(
|
||||
self, ctx: Context[BotT], param: inspect.Parameter, required: bool, converter: Any
|
||||
) -> Any:
|
||||
view = ctx.view
|
||||
result = []
|
||||
while not view.eof:
|
||||
@ -631,7 +636,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
return param.default
|
||||
return result
|
||||
|
||||
async def _transform_greedy_var_pos(self, ctx: Context, param: inspect.Parameter, converter: Any) -> Any:
|
||||
async def _transform_greedy_var_pos(self, ctx: Context[BotT], param: inspect.Parameter, converter: Any) -> Any:
|
||||
view = ctx.view
|
||||
previous = view.index
|
||||
try:
|
||||
@ -669,7 +674,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
return ' '.join(reversed(entries))
|
||||
|
||||
@property
|
||||
def parents(self) -> List[Group]:
|
||||
def parents(self) -> List[Group[Any, ..., Any]]:
|
||||
"""List[:class:`Group`]: Retrieves the parents of this command.
|
||||
|
||||
If the command has no parents then it returns an empty :class:`list`.
|
||||
@ -687,7 +692,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
return entries
|
||||
|
||||
@property
|
||||
def root_parent(self) -> Optional[Group]:
|
||||
def root_parent(self) -> Optional[Group[Any, ..., Any]]:
|
||||
"""Optional[:class:`Group`]: Retrieves the root parent of this command.
|
||||
|
||||
If the command has no parents then it returns ``None``.
|
||||
@ -716,7 +721,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
def __str__(self) -> str:
|
||||
return self.qualified_name
|
||||
|
||||
async def _parse_arguments(self, ctx: Context) -> None:
|
||||
async def _parse_arguments(self, ctx: Context[BotT]) -> None:
|
||||
ctx.args = [ctx] if self.cog is None else [self.cog, ctx]
|
||||
ctx.kwargs = {}
|
||||
args = ctx.args
|
||||
@ -752,7 +757,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
if not self.ignore_extra and not view.eof:
|
||||
raise TooManyArguments('Too many arguments passed to ' + self.qualified_name)
|
||||
|
||||
async def call_before_hooks(self, ctx: Context) -> None:
|
||||
async def call_before_hooks(self, ctx: Context[BotT]) -> None:
|
||||
# now that we're done preparing we can call the pre-command hooks
|
||||
# first, call the command local hook:
|
||||
cog = self.cog
|
||||
@ -777,7 +782,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
if hook is not None:
|
||||
await hook(ctx)
|
||||
|
||||
async def call_after_hooks(self, ctx: Context) -> None:
|
||||
async def call_after_hooks(self, ctx: Context[BotT]) -> None:
|
||||
cog = self.cog
|
||||
if self._after_invoke is not None:
|
||||
instance = getattr(self._after_invoke, '__self__', cog)
|
||||
@ -796,7 +801,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
if hook is not None:
|
||||
await hook(ctx)
|
||||
|
||||
def _prepare_cooldowns(self, ctx: Context) -> None:
|
||||
def _prepare_cooldowns(self, ctx: Context[BotT]) -> None:
|
||||
if self._buckets.valid:
|
||||
dt = ctx.message.edited_at or ctx.message.created_at
|
||||
current = dt.replace(tzinfo=datetime.timezone.utc).timestamp()
|
||||
@ -806,7 +811,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
if retry_after:
|
||||
raise CommandOnCooldown(bucket, retry_after, self._buckets.type) # type: ignore
|
||||
|
||||
async def prepare(self, ctx: Context) -> None:
|
||||
async def prepare(self, ctx: Context[BotT]) -> None:
|
||||
ctx.command = self
|
||||
|
||||
if not await self.can_run(ctx):
|
||||
@ -830,7 +835,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
await self._max_concurrency.release(ctx) # type: ignore
|
||||
raise
|
||||
|
||||
def is_on_cooldown(self, ctx: Context) -> bool:
|
||||
def is_on_cooldown(self, ctx: Context[BotT]) -> bool:
|
||||
"""Checks whether the command is currently on cooldown.
|
||||
|
||||
Parameters
|
||||
@ -851,7 +856,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
current = dt.replace(tzinfo=datetime.timezone.utc).timestamp()
|
||||
return bucket.get_tokens(current) == 0
|
||||
|
||||
def reset_cooldown(self, ctx: Context) -> None:
|
||||
def reset_cooldown(self, ctx: Context[BotT]) -> None:
|
||||
"""Resets the cooldown on this command.
|
||||
|
||||
Parameters
|
||||
@ -863,7 +868,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
bucket = self._buckets.get_bucket(ctx.message)
|
||||
bucket.reset()
|
||||
|
||||
def get_cooldown_retry_after(self, ctx: Context) -> float:
|
||||
def get_cooldown_retry_after(self, ctx: Context[BotT]) -> float:
|
||||
"""Retrieves the amount of seconds before this command can be tried again.
|
||||
|
||||
.. versionadded:: 1.4
|
||||
@ -887,7 +892,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
|
||||
return 0.0
|
||||
|
||||
async def invoke(self, ctx: Context) -> None:
|
||||
async def invoke(self, ctx: Context[BotT]) -> None:
|
||||
await self.prepare(ctx)
|
||||
|
||||
# terminate the invoked_subcommand chain.
|
||||
@ -896,9 +901,9 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
ctx.invoked_subcommand = None
|
||||
ctx.subcommand_passed = None
|
||||
injected = hooked_wrapped_callback(self, ctx, self.callback)
|
||||
await injected(*ctx.args, **ctx.kwargs)
|
||||
await injected(*ctx.args, **ctx.kwargs) # type: ignore
|
||||
|
||||
async def reinvoke(self, ctx: Context, *, call_hooks: bool = False) -> None:
|
||||
async def reinvoke(self, ctx: Context[BotT], *, call_hooks: bool = False) -> None:
|
||||
ctx.command = self
|
||||
await self._parse_arguments(ctx)
|
||||
|
||||
@ -936,7 +941,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
if not asyncio.iscoroutinefunction(coro):
|
||||
raise TypeError('The error handler must be a coroutine.')
|
||||
|
||||
self.on_error: Error = coro
|
||||
self.on_error: Error[Any] = coro
|
||||
return coro
|
||||
|
||||
def has_error_handler(self) -> bool:
|
||||
@ -1075,7 +1080,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
||||
|
||||
return ' '.join(result)
|
||||
|
||||
async def can_run(self, ctx: Context) -> bool:
|
||||
async def can_run(self, ctx: Context[BotT]) -> bool:
|
||||
"""|coro|
|
||||
|
||||
Checks if the command can be executed by checking all the predicates
|
||||
@ -1341,7 +1346,7 @@ class GroupMixin(Generic[CogT]):
|
||||
def command(
|
||||
self,
|
||||
name: str = MISSING,
|
||||
cls: Type[Command] = MISSING,
|
||||
cls: Type[Command[Any, ..., Any]] = MISSING,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
@ -1401,7 +1406,7 @@ class GroupMixin(Generic[CogT]):
|
||||
def group(
|
||||
self,
|
||||
name: str = MISSING,
|
||||
cls: Type[Group] = MISSING,
|
||||
cls: Type[Group[Any, ..., Any]] = MISSING,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
@ -1461,9 +1466,9 @@ class Group(GroupMixin[CogT], Command[CogT, P, T]):
|
||||
ret = super().copy()
|
||||
for cmd in self.commands:
|
||||
ret.add_command(cmd.copy())
|
||||
return ret # type: ignore
|
||||
return ret
|
||||
|
||||
async def invoke(self, ctx: Context) -> None:
|
||||
async def invoke(self, ctx: Context[BotT]) -> None:
|
||||
ctx.invoked_subcommand = None
|
||||
ctx.subcommand_passed = None
|
||||
early_invoke = not self.invoke_without_command
|
||||
@ -1481,7 +1486,7 @@ class Group(GroupMixin[CogT], Command[CogT, P, T]):
|
||||
|
||||
if early_invoke:
|
||||
injected = hooked_wrapped_callback(self, ctx, self.callback)
|
||||
await injected(*ctx.args, **ctx.kwargs)
|
||||
await injected(*ctx.args, **ctx.kwargs) # type: ignore
|
||||
|
||||
ctx.invoked_parents.append(ctx.invoked_with) # type: ignore
|
||||
|
||||
@ -1494,7 +1499,7 @@ class Group(GroupMixin[CogT], Command[CogT, P, T]):
|
||||
view.previous = previous
|
||||
await super().invoke(ctx)
|
||||
|
||||
async def reinvoke(self, ctx: Context, *, call_hooks: bool = False) -> None:
|
||||
async def reinvoke(self, ctx: Context[BotT], *, call_hooks: bool = False) -> None:
|
||||
ctx.invoked_subcommand = None
|
||||
early_invoke = not self.invoke_without_command
|
||||
if early_invoke:
|
||||
@ -1592,7 +1597,7 @@ def command(
|
||||
|
||||
def command(
|
||||
name: str = MISSING,
|
||||
cls: Type[Command] = MISSING,
|
||||
cls: Type[Command[Any, ..., Any]] = MISSING,
|
||||
**attrs: Any,
|
||||
) -> Any:
|
||||
"""A decorator that transforms a function into a :class:`.Command`
|
||||
@ -1662,7 +1667,7 @@ def group(
|
||||
|
||||
def group(
|
||||
name: str = MISSING,
|
||||
cls: Type[Group] = MISSING,
|
||||
cls: Type[Group[Any, ..., Any]] = MISSING,
|
||||
**attrs: Any,
|
||||
) -> Any:
|
||||
"""A decorator that transforms a function into a :class:`.Group`.
|
||||
@ -1679,7 +1684,7 @@ def group(
|
||||
return command(name=name, cls=cls, **attrs)
|
||||
|
||||
|
||||
def check(predicate: Check) -> Callable[[T], T]:
|
||||
def check(predicate: Check[ContextT]) -> Callable[[T], T]:
|
||||
r"""A decorator that adds a check to the :class:`.Command` or its
|
||||
subclasses. These checks could be accessed via :attr:`.Command.checks`.
|
||||
|
||||
@ -1774,7 +1779,7 @@ def check(predicate: Check) -> Callable[[T], T]:
|
||||
return decorator # type: ignore
|
||||
|
||||
|
||||
def check_any(*checks: Check) -> Callable[[T], T]:
|
||||
def check_any(*checks: Check[ContextT]) -> Callable[[T], T]:
|
||||
r"""A :func:`check` that is added that checks if any of the checks passed
|
||||
will pass, i.e. using logical OR.
|
||||
|
||||
@ -1827,7 +1832,7 @@ def check_any(*checks: Check) -> Callable[[T], T]:
|
||||
else:
|
||||
unwrapped.append(pred)
|
||||
|
||||
async def predicate(ctx: Context) -> bool:
|
||||
async def predicate(ctx: Context[BotT]) -> bool:
|
||||
errors = []
|
||||
for func in unwrapped:
|
||||
try:
|
||||
@ -1870,7 +1875,7 @@ def has_role(item: Union[int, str]) -> Callable[[T], T]:
|
||||
The name or ID of the role to check.
|
||||
"""
|
||||
|
||||
def predicate(ctx: Context) -> bool:
|
||||
def predicate(ctx: Context[BotT]) -> bool:
|
||||
if ctx.guild is None:
|
||||
raise NoPrivateMessage()
|
||||
|
||||
@ -1923,7 +1928,7 @@ def has_any_role(*items: Union[int, str]) -> Callable[[T], T]:
|
||||
raise NoPrivateMessage()
|
||||
|
||||
# ctx.guild is None doesn't narrow ctx.author to Member
|
||||
getter = functools.partial(discord.utils.get, ctx.author.roles) # type: ignore
|
||||
getter = functools.partial(discord.utils.get, ctx.author.roles)
|
||||
if any(getter(id=item) is not None if isinstance(item, int) else getter(name=item) is not None for item in items):
|
||||
return True
|
||||
raise MissingAnyRole(list(items))
|
||||
@ -2022,7 +2027,7 @@ def has_permissions(**perms: bool) -> Callable[[T], T]:
|
||||
if invalid:
|
||||
raise TypeError(f"Invalid permission(s): {', '.join(invalid)}")
|
||||
|
||||
def predicate(ctx: Context) -> bool:
|
||||
def predicate(ctx: Context[BotT]) -> bool:
|
||||
ch = ctx.channel
|
||||
permissions = ch.permissions_for(ctx.author) # type: ignore
|
||||
|
||||
@ -2048,7 +2053,7 @@ def bot_has_permissions(**perms: bool) -> Callable[[T], T]:
|
||||
if invalid:
|
||||
raise TypeError(f"Invalid permission(s): {', '.join(invalid)}")
|
||||
|
||||
def predicate(ctx: Context) -> bool:
|
||||
def predicate(ctx: Context[BotT]) -> bool:
|
||||
guild = ctx.guild
|
||||
me = guild.me if guild is not None else ctx.bot.user
|
||||
permissions = ctx.channel.permissions_for(me) # type: ignore
|
||||
@ -2077,7 +2082,7 @@ def has_guild_permissions(**perms: bool) -> Callable[[T], T]:
|
||||
if invalid:
|
||||
raise TypeError(f"Invalid permission(s): {', '.join(invalid)}")
|
||||
|
||||
def predicate(ctx: Context) -> bool:
|
||||
def predicate(ctx: Context[BotT]) -> bool:
|
||||
if not ctx.guild:
|
||||
raise NoPrivateMessage
|
||||
|
||||
@ -2103,7 +2108,7 @@ def bot_has_guild_permissions(**perms: bool) -> Callable[[T], T]:
|
||||
if invalid:
|
||||
raise TypeError(f"Invalid permission(s): {', '.join(invalid)}")
|
||||
|
||||
def predicate(ctx: Context) -> bool:
|
||||
def predicate(ctx: Context[BotT]) -> bool:
|
||||
if not ctx.guild:
|
||||
raise NoPrivateMessage
|
||||
|
||||
@ -2129,7 +2134,7 @@ def dm_only() -> Callable[[T], T]:
|
||||
.. versionadded:: 1.1
|
||||
"""
|
||||
|
||||
def predicate(ctx: Context) -> bool:
|
||||
def predicate(ctx: Context[BotT]) -> bool:
|
||||
if ctx.guild is not None:
|
||||
raise PrivateMessageOnly()
|
||||
return True
|
||||
@ -2146,7 +2151,7 @@ def guild_only() -> Callable[[T], T]:
|
||||
that is inherited from :exc:`.CheckFailure`.
|
||||
"""
|
||||
|
||||
def predicate(ctx: Context) -> bool:
|
||||
def predicate(ctx: Context[BotT]) -> bool:
|
||||
if ctx.guild is None:
|
||||
raise NoPrivateMessage()
|
||||
return True
|
||||
@ -2164,7 +2169,7 @@ def is_owner() -> Callable[[T], T]:
|
||||
from :exc:`.CheckFailure`.
|
||||
"""
|
||||
|
||||
async def predicate(ctx: Context) -> bool:
|
||||
async def predicate(ctx: Context[BotT]) -> bool:
|
||||
if not await ctx.bot.is_owner(ctx.author):
|
||||
raise NotOwner('You do not own this bot.')
|
||||
return True
|
||||
@ -2184,7 +2189,7 @@ def is_nsfw() -> Callable[[T], T]:
|
||||
DM channels will also now pass this check.
|
||||
"""
|
||||
|
||||
def pred(ctx: Context) -> bool:
|
||||
def pred(ctx: Context[BotT]) -> bool:
|
||||
ch = ctx.channel
|
||||
if ctx.guild is None or (isinstance(ch, (discord.TextChannel, discord.Thread)) and ch.is_nsfw()):
|
||||
return True
|
||||
|
@ -39,6 +39,8 @@ if TYPE_CHECKING:
|
||||
from discord.threads import Thread
|
||||
from discord.types.snowflake import Snowflake, SnowflakeList
|
||||
|
||||
from ._types import BotT
|
||||
|
||||
|
||||
__all__ = (
|
||||
'CommandError',
|
||||
@ -135,8 +137,8 @@ class ConversionError(CommandError):
|
||||
the ``__cause__`` attribute.
|
||||
"""
|
||||
|
||||
def __init__(self, converter: Converter, original: Exception) -> None:
|
||||
self.converter: Converter = converter
|
||||
def __init__(self, converter: Converter[Any], original: Exception) -> None:
|
||||
self.converter: Converter[Any] = converter
|
||||
self.original: Exception = original
|
||||
|
||||
|
||||
@ -224,9 +226,9 @@ class CheckAnyFailure(CheckFailure):
|
||||
A list of check predicates that failed.
|
||||
"""
|
||||
|
||||
def __init__(self, checks: List[CheckFailure], errors: List[Callable[[Context], bool]]) -> None:
|
||||
def __init__(self, checks: List[CheckFailure], errors: List[Callable[[Context[BotT]], bool]]) -> None:
|
||||
self.checks: List[CheckFailure] = checks
|
||||
self.errors: List[Callable[[Context], bool]] = errors
|
||||
self.errors: List[Callable[[Context[BotT]], bool]] = errors
|
||||
super().__init__('You do not have permission to run this command.')
|
||||
|
||||
|
||||
@ -807,9 +809,9 @@ class BadUnionArgument(UserInputError):
|
||||
A list of errors that were caught from failing the conversion.
|
||||
"""
|
||||
|
||||
def __init__(self, param: Parameter, converters: Tuple[Type, ...], errors: List[CommandError]) -> None:
|
||||
def __init__(self, param: Parameter, converters: Tuple[type, ...], errors: List[CommandError]) -> None:
|
||||
self.param: Parameter = param
|
||||
self.converters: Tuple[Type, ...] = converters
|
||||
self.converters: Tuple[type, ...] = converters
|
||||
self.errors: List[CommandError] = errors
|
||||
|
||||
def _get_name(x):
|
||||
|
@ -49,8 +49,6 @@ from typing import (
|
||||
Tuple,
|
||||
List,
|
||||
Any,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
@ -70,6 +68,8 @@ if TYPE_CHECKING:
|
||||
|
||||
from .context import Context
|
||||
|
||||
from ._types import BotT
|
||||
|
||||
|
||||
@dataclass
|
||||
class Flag:
|
||||
@ -148,7 +148,7 @@ def flag(
|
||||
return Flag(name=name, aliases=aliases, default=default, max_args=max_args, override=override)
|
||||
|
||||
|
||||
def validate_flag_name(name: str, forbidden: Set[str]):
|
||||
def validate_flag_name(name: str, forbidden: Set[str]) -> None:
|
||||
if not name:
|
||||
raise ValueError('flag names should not be empty')
|
||||
|
||||
@ -348,7 +348,7 @@ class FlagsMeta(type):
|
||||
return type.__new__(cls, name, bases, attrs)
|
||||
|
||||
|
||||
async def tuple_convert_all(ctx: Context, argument: str, flag: Flag, converter: Any) -> Tuple[Any, ...]:
|
||||
async def tuple_convert_all(ctx: Context[BotT], argument: str, flag: Flag, converter: Any) -> Tuple[Any, ...]:
|
||||
view = StringView(argument)
|
||||
results = []
|
||||
param: inspect.Parameter = ctx.current_parameter # type: ignore
|
||||
@ -373,7 +373,7 @@ async def tuple_convert_all(ctx: Context, argument: str, flag: Flag, converter:
|
||||
return tuple(results)
|
||||
|
||||
|
||||
async def tuple_convert_flag(ctx: Context, argument: str, flag: Flag, converters: Any) -> Tuple[Any, ...]:
|
||||
async def tuple_convert_flag(ctx: Context[BotT], argument: str, flag: Flag, converters: Any) -> Tuple[Any, ...]:
|
||||
view = StringView(argument)
|
||||
results = []
|
||||
param: inspect.Parameter = ctx.current_parameter # type: ignore
|
||||
@ -401,7 +401,7 @@ async def tuple_convert_flag(ctx: Context, argument: str, flag: Flag, converters
|
||||
return tuple(results)
|
||||
|
||||
|
||||
async def convert_flag(ctx, argument: str, flag: Flag, annotation: Any = None) -> Any:
|
||||
async def convert_flag(ctx: Context[BotT], argument: str, flag: Flag, annotation: Any = None) -> Any:
|
||||
param: inspect.Parameter = ctx.current_parameter # type: ignore
|
||||
annotation = annotation or flag.annotation
|
||||
try:
|
||||
@ -480,7 +480,7 @@ class FlagConverter(metaclass=FlagsMeta):
|
||||
yield (flag.name, getattr(self, flag.attribute))
|
||||
|
||||
@classmethod
|
||||
async def _construct_default(cls, ctx: Context) -> Self:
|
||||
async def _construct_default(cls, ctx: Context[BotT]) -> Self:
|
||||
self = cls.__new__(cls)
|
||||
flags = cls.__commands_flags__
|
||||
for flag in flags.values():
|
||||
@ -546,7 +546,7 @@ class FlagConverter(metaclass=FlagsMeta):
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
async def convert(cls, ctx: Context, argument: str) -> Self:
|
||||
async def convert(cls, ctx: Context[BotT], argument: str) -> Self:
|
||||
"""|coro|
|
||||
|
||||
The method that actually converters an argument to the flag mapping.
|
||||
@ -610,7 +610,7 @@ class FlagConverter(metaclass=FlagsMeta):
|
||||
values = [await convert_flag(ctx, value, flag) for value in values]
|
||||
|
||||
if flag.cast_to_dict:
|
||||
values = dict(values) # type: ignore
|
||||
values = dict(values)
|
||||
|
||||
setattr(self, flag.attribute, values)
|
||||
|
||||
|
@ -22,13 +22,27 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import copy
|
||||
import functools
|
||||
import inspect
|
||||
import re
|
||||
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Optional,
|
||||
Generator,
|
||||
List,
|
||||
TypeVar,
|
||||
Callable,
|
||||
Any,
|
||||
Dict,
|
||||
Tuple,
|
||||
Iterable,
|
||||
Sequence,
|
||||
Mapping,
|
||||
)
|
||||
|
||||
import discord.utils
|
||||
|
||||
@ -36,7 +50,21 @@ from .core import Group, Command, get_signature_parameters
|
||||
from .errors import CommandError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
import inspect
|
||||
|
||||
import discord.abc
|
||||
|
||||
from .bot import BotBase
|
||||
from .context import Context
|
||||
from .cog import Cog
|
||||
|
||||
from ._types import (
|
||||
Check,
|
||||
ContextT,
|
||||
BotT,
|
||||
_Bot,
|
||||
)
|
||||
|
||||
__all__ = (
|
||||
'Paginator',
|
||||
@ -45,7 +73,9 @@ __all__ = (
|
||||
'MinimalHelpCommand',
|
||||
)
|
||||
|
||||
MISSING = discord.utils.MISSING
|
||||
FuncT = TypeVar('FuncT', bound=Callable[..., Any])
|
||||
|
||||
MISSING: Any = discord.utils.MISSING
|
||||
|
||||
# help -> shows info of bot on top/bottom and lists subcommands
|
||||
# help command -> shows detailed info of command
|
||||
@ -80,10 +110,10 @@ class Paginator:
|
||||
|
||||
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.
|
||||
prefix: Optional[:class:`str`]
|
||||
The prefix inserted to every page. e.g. three backticks, if any.
|
||||
suffix: Optional[:class:`str`]
|
||||
The suffix appended at the end of every page. e.g. three backticks, if any.
|
||||
max_size: :class:`int`
|
||||
The maximum amount of codepoints allowed in a page.
|
||||
linesep: :class:`str`
|
||||
@ -91,36 +121,38 @@ class Paginator:
|
||||
.. versionadded:: 1.7
|
||||
"""
|
||||
|
||||
def __init__(self, prefix='```', suffix='```', max_size=2000, linesep='\n'):
|
||||
self.prefix = prefix
|
||||
self.suffix = suffix
|
||||
self.max_size = max_size
|
||||
self.linesep = linesep
|
||||
def __init__(
|
||||
self, prefix: Optional[str] = '```', suffix: Optional[str] = '```', max_size: int = 2000, linesep: str = '\n'
|
||||
) -> None:
|
||||
self.prefix: Optional[str] = prefix
|
||||
self.suffix: Optional[str] = suffix
|
||||
self.max_size: int = max_size
|
||||
self.linesep: str = linesep
|
||||
self.clear()
|
||||
|
||||
def clear(self):
|
||||
def clear(self) -> None:
|
||||
"""Clears the paginator to have no pages."""
|
||||
if self.prefix is not None:
|
||||
self._current_page = [self.prefix]
|
||||
self._count = len(self.prefix) + self._linesep_len # prefix + newline
|
||||
self._current_page: List[str] = [self.prefix]
|
||||
self._count: int = len(self.prefix) + self._linesep_len # prefix + newline
|
||||
else:
|
||||
self._current_page = []
|
||||
self._count = 0
|
||||
self._pages = []
|
||||
self._pages: List[str] = []
|
||||
|
||||
@property
|
||||
def _prefix_len(self):
|
||||
def _prefix_len(self) -> int:
|
||||
return len(self.prefix) if self.prefix else 0
|
||||
|
||||
@property
|
||||
def _suffix_len(self):
|
||||
def _suffix_len(self) -> int:
|
||||
return len(self.suffix) if self.suffix else 0
|
||||
|
||||
@property
|
||||
def _linesep_len(self):
|
||||
def _linesep_len(self) -> int:
|
||||
return len(self.linesep)
|
||||
|
||||
def add_line(self, line='', *, empty=False):
|
||||
def add_line(self, line: str = '', *, empty: bool = False) -> None:
|
||||
"""Adds a line to the current page.
|
||||
|
||||
If the line exceeds the :attr:`max_size` then an exception
|
||||
@ -152,7 +184,7 @@ class Paginator:
|
||||
self._current_page.append('')
|
||||
self._count += self._linesep_len
|
||||
|
||||
def close_page(self):
|
||||
def close_page(self) -> None:
|
||||
"""Prematurely terminate a page."""
|
||||
if self.suffix is not None:
|
||||
self._current_page.append(self.suffix)
|
||||
@ -165,36 +197,38 @@ class Paginator:
|
||||
self._current_page = []
|
||||
self._count = 0
|
||||
|
||||
def __len__(self):
|
||||
def __len__(self) -> int:
|
||||
total = sum(len(p) for p in self._pages)
|
||||
return total + self._count
|
||||
|
||||
@property
|
||||
def pages(self):
|
||||
def pages(self) -> List[str]:
|
||||
"""List[:class:`str`]: Returns the rendered list of pages."""
|
||||
# we have more than just the prefix in our current page
|
||||
if len(self._current_page) > (0 if self.prefix is None else 1):
|
||||
self.close_page()
|
||||
return self._pages
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
fmt = '<Paginator prefix: {0.prefix!r} suffix: {0.suffix!r} linesep: {0.linesep!r} max_size: {0.max_size} count: {0._count}>'
|
||||
return fmt.format(self)
|
||||
|
||||
|
||||
def _not_overridden(f):
|
||||
def _not_overridden(f: FuncT) -> FuncT:
|
||||
f.__help_command_not_overridden__ = True
|
||||
return f
|
||||
|
||||
|
||||
class _HelpCommandImpl(Command):
|
||||
def __init__(self, inject, *args, **kwargs):
|
||||
def __init__(self, inject: HelpCommand, *args: Any, **kwargs: Any) -> None:
|
||||
super().__init__(inject.command_callback, *args, **kwargs)
|
||||
self._original = inject
|
||||
self._injected = inject
|
||||
self.params = get_signature_parameters(inject.command_callback, globals(), skip_parameters=1)
|
||||
self._original: HelpCommand = inject
|
||||
self._injected: HelpCommand = inject
|
||||
self.params: Dict[str, inspect.Parameter] = get_signature_parameters(
|
||||
inject.command_callback, globals(), skip_parameters=1
|
||||
)
|
||||
|
||||
async def prepare(self, ctx):
|
||||
async def prepare(self, ctx: Context[Any]) -> None:
|
||||
self._injected = injected = self._original.copy()
|
||||
injected.context = ctx
|
||||
self.callback = injected.command_callback
|
||||
@ -209,7 +243,7 @@ class _HelpCommandImpl(Command):
|
||||
|
||||
await super().prepare(ctx)
|
||||
|
||||
async def _parse_arguments(self, ctx):
|
||||
async def _parse_arguments(self, ctx: Context[BotT]) -> None:
|
||||
# Make the parser think we don't have a cog so it doesn't
|
||||
# inject the parameter into `ctx.args`.
|
||||
original_cog = self.cog
|
||||
@ -219,22 +253,26 @@ class _HelpCommandImpl(Command):
|
||||
finally:
|
||||
self.cog = original_cog
|
||||
|
||||
async def _on_error_cog_implementation(self, dummy, ctx, error):
|
||||
async def _on_error_cog_implementation(self, _, ctx: Context[BotT], error: CommandError) -> None:
|
||||
await self._injected.on_help_command_error(ctx, error)
|
||||
|
||||
def _inject_into_cog(self, cog):
|
||||
def _inject_into_cog(self, cog: Cog) -> None:
|
||||
# Warning: hacky
|
||||
|
||||
# Make the cog think that get_commands returns this command
|
||||
# as well if we inject it without modifying __cog_commands__
|
||||
# since that's used for the injection and ejection of cogs.
|
||||
def wrapped_get_commands(*, _original=cog.get_commands):
|
||||
def wrapped_get_commands(
|
||||
*, _original: Callable[[], List[Command[Any, ..., Any]]] = cog.get_commands
|
||||
) -> List[Command[Any, ..., Any]]:
|
||||
ret = _original()
|
||||
ret.append(self)
|
||||
return ret
|
||||
|
||||
# Ditto here
|
||||
def wrapped_walk_commands(*, _original=cog.walk_commands):
|
||||
def wrapped_walk_commands(
|
||||
*, _original: Callable[[], Generator[Command[Any, ..., Any], None, None]] = cog.walk_commands
|
||||
):
|
||||
yield from _original()
|
||||
yield self
|
||||
|
||||
@ -244,7 +282,7 @@ class _HelpCommandImpl(Command):
|
||||
cog.walk_commands = wrapped_walk_commands
|
||||
self.cog = cog
|
||||
|
||||
def _eject_cog(self):
|
||||
def _eject_cog(self) -> None:
|
||||
if self.cog is None:
|
||||
return
|
||||
|
||||
@ -298,7 +336,11 @@ class HelpCommand:
|
||||
|
||||
MENTION_PATTERN = re.compile('|'.join(MENTION_TRANSFORMS.keys()))
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if TYPE_CHECKING:
|
||||
__original_kwargs__: Dict[str, Any]
|
||||
__original_args__: Tuple[Any, ...]
|
||||
|
||||
def __new__(cls, *args: Any, **kwargs: Any) -> Self:
|
||||
# To prevent race conditions of a single instance while also allowing
|
||||
# for settings to be passed the original arguments passed must be assigned
|
||||
# to allow for easier copies (which will be made when the help command is actually called)
|
||||
@ -314,30 +356,31 @@ class HelpCommand:
|
||||
self.__original_args__ = deepcopy(args)
|
||||
return self
|
||||
|
||||
def __init__(self, **options):
|
||||
self.show_hidden = options.pop('show_hidden', False)
|
||||
self.verify_checks = options.pop('verify_checks', True)
|
||||
def __init__(self, **options: Any) -> None:
|
||||
self.show_hidden: bool = options.pop('show_hidden', False)
|
||||
self.verify_checks: bool = options.pop('verify_checks', True)
|
||||
self.command_attrs: Dict[str, Any]
|
||||
self.command_attrs = attrs = options.pop('command_attrs', {})
|
||||
attrs.setdefault('name', 'help')
|
||||
attrs.setdefault('help', 'Shows this message')
|
||||
self.context: Context = MISSING
|
||||
self.context: Context[_Bot] = MISSING
|
||||
self._command_impl = _HelpCommandImpl(self, **self.command_attrs)
|
||||
|
||||
def copy(self):
|
||||
def copy(self) -> Self:
|
||||
obj = self.__class__(*self.__original_args__, **self.__original_kwargs__)
|
||||
obj._command_impl = self._command_impl
|
||||
return obj
|
||||
|
||||
def _add_to_bot(self, bot):
|
||||
def _add_to_bot(self, bot: BotBase) -> None:
|
||||
command = _HelpCommandImpl(self, **self.command_attrs)
|
||||
bot.add_command(command)
|
||||
self._command_impl = command
|
||||
|
||||
def _remove_from_bot(self, bot):
|
||||
def _remove_from_bot(self, bot: BotBase) -> None:
|
||||
bot.remove_command(self._command_impl.name)
|
||||
self._command_impl._eject_cog()
|
||||
|
||||
def add_check(self, func, /):
|
||||
def add_check(self, func: Check[ContextT], /) -> None:
|
||||
"""
|
||||
Adds a check to the help command.
|
||||
|
||||
@ -355,7 +398,7 @@ class HelpCommand:
|
||||
|
||||
self._command_impl.add_check(func)
|
||||
|
||||
def remove_check(self, func, /):
|
||||
def remove_check(self, func: Check[ContextT], /) -> None:
|
||||
"""
|
||||
Removes a check from the help command.
|
||||
|
||||
@ -376,15 +419,15 @@ class HelpCommand:
|
||||
|
||||
self._command_impl.remove_check(func)
|
||||
|
||||
def get_bot_mapping(self):
|
||||
def get_bot_mapping(self) -> Dict[Optional[Cog], List[Command[Any, ..., Any]]]:
|
||||
"""Retrieves the bot mapping passed to :meth:`send_bot_help`."""
|
||||
bot = self.context.bot
|
||||
mapping = {cog: cog.get_commands() for cog in bot.cogs.values()}
|
||||
mapping: Dict[Optional[Cog], List[Command[Any, ..., Any]]] = {cog: cog.get_commands() for cog in bot.cogs.values()}
|
||||
mapping[None] = [c for c in bot.commands if c.cog is None]
|
||||
return mapping
|
||||
|
||||
@property
|
||||
def invoked_with(self):
|
||||
def invoked_with(self) -> Optional[str]:
|
||||
"""Similar to :attr:`Context.invoked_with` except properly handles
|
||||
the case where :meth:`Context.send_help` is used.
|
||||
|
||||
@ -395,7 +438,7 @@ class HelpCommand:
|
||||
|
||||
Returns
|
||||
---------
|
||||
:class:`str`
|
||||
Optional[:class:`str`]
|
||||
The command name that triggered this invocation.
|
||||
"""
|
||||
command_name = self._command_impl.name
|
||||
@ -404,7 +447,7 @@ class HelpCommand:
|
||||
return command_name
|
||||
return ctx.invoked_with
|
||||
|
||||
def get_command_signature(self, command):
|
||||
def get_command_signature(self, command: Command[Any, ..., Any]) -> str:
|
||||
"""Retrieves the signature portion of the help page.
|
||||
|
||||
Parameters
|
||||
@ -418,14 +461,14 @@ class HelpCommand:
|
||||
The signature for the command.
|
||||
"""
|
||||
|
||||
parent = command.parent
|
||||
parent: Optional[Group[Any, ..., Any]] = command.parent # type: ignore - the parent will be a Group
|
||||
entries = []
|
||||
while parent is not None:
|
||||
if not parent.signature or parent.invoke_without_command:
|
||||
entries.append(parent.name)
|
||||
else:
|
||||
entries.append(parent.name + ' ' + parent.signature)
|
||||
parent = parent.parent
|
||||
parent = parent.parent # type: ignore
|
||||
parent_sig = ' '.join(reversed(entries))
|
||||
|
||||
if len(command.aliases) > 0:
|
||||
@ -439,7 +482,7 @@ class HelpCommand:
|
||||
|
||||
return f'{self.context.clean_prefix}{alias} {command.signature}'
|
||||
|
||||
def remove_mentions(self, string):
|
||||
def remove_mentions(self, string: str) -> str:
|
||||
"""Removes mentions from the string to prevent abuse.
|
||||
|
||||
This includes ``@everyone``, ``@here``, member mentions and role mentions.
|
||||
@ -450,13 +493,13 @@ class HelpCommand:
|
||||
The string with mentions removed.
|
||||
"""
|
||||
|
||||
def replace(obj, *, transforms=self.MENTION_TRANSFORMS):
|
||||
def replace(obj: re.Match, *, transforms: Dict[str, str] = self.MENTION_TRANSFORMS) -> str:
|
||||
return transforms.get(obj.group(0), '@invalid')
|
||||
|
||||
return self.MENTION_PATTERN.sub(replace, string)
|
||||
|
||||
@property
|
||||
def cog(self):
|
||||
def cog(self) -> Optional[Cog]:
|
||||
"""A property for retrieving or setting the cog for the help command.
|
||||
|
||||
When a cog is set for the help command, it is as-if the help command
|
||||
@ -473,7 +516,7 @@ class HelpCommand:
|
||||
return self._command_impl.cog
|
||||
|
||||
@cog.setter
|
||||
def cog(self, cog):
|
||||
def cog(self, cog: Optional[Cog]) -> None:
|
||||
# Remove whatever cog is currently valid, if any
|
||||
self._command_impl._eject_cog()
|
||||
|
||||
@ -481,7 +524,7 @@ class HelpCommand:
|
||||
if cog is not None:
|
||||
self._command_impl._inject_into_cog(cog)
|
||||
|
||||
def command_not_found(self, string):
|
||||
def command_not_found(self, string: str) -> str:
|
||||
"""|maybecoro|
|
||||
|
||||
A method called when a command is not found in the help command.
|
||||
@ -502,7 +545,7 @@ class HelpCommand:
|
||||
"""
|
||||
return f'No command called "{string}" found.'
|
||||
|
||||
def subcommand_not_found(self, command, string):
|
||||
def subcommand_not_found(self, command: Command[Any, ..., Any], string: str) -> str:
|
||||
"""|maybecoro|
|
||||
|
||||
A method called when a command did not have a subcommand requested in the help command.
|
||||
@ -532,7 +575,13 @@ class HelpCommand:
|
||||
return f'Command "{command.qualified_name}" has no subcommand named {string}'
|
||||
return f'Command "{command.qualified_name}" has no subcommands.'
|
||||
|
||||
async def filter_commands(self, commands, *, sort=False, key=None):
|
||||
async def filter_commands(
|
||||
self,
|
||||
commands: Iterable[Command[Any, ..., Any]],
|
||||
*,
|
||||
sort: bool = False,
|
||||
key: Optional[Callable[[Command[Any, ..., Any]], Any]] = None,
|
||||
) -> List[Command[Any, ..., Any]]:
|
||||
"""|coro|
|
||||
|
||||
Returns a filtered list of commands and optionally sorts them.
|
||||
@ -546,7 +595,7 @@ class HelpCommand:
|
||||
An iterable of commands that are getting filtered.
|
||||
sort: :class:`bool`
|
||||
Whether to sort the result.
|
||||
key: Optional[Callable[:class:`Command`, Any]]
|
||||
key: Optional[Callable[[:class:`Command`], Any]]
|
||||
An optional key function to pass to :func:`py:sorted` that
|
||||
takes a :class:`Command` as its sole parameter. If ``sort`` is
|
||||
passed as ``True`` then this will default as the command name.
|
||||
@ -565,14 +614,14 @@ class HelpCommand:
|
||||
if self.verify_checks is False:
|
||||
# if we do not need to verify the checks then we can just
|
||||
# run it straight through normally without using await.
|
||||
return sorted(iterator, key=key) if sort else list(iterator)
|
||||
return sorted(iterator, key=key) if sort else list(iterator) # type: ignore - the key shouldn't be None
|
||||
|
||||
if self.verify_checks is None and not self.context.guild:
|
||||
# if verify_checks is None and we're in a DM, don't verify
|
||||
return sorted(iterator, key=key) if sort else list(iterator)
|
||||
return sorted(iterator, key=key) if sort else list(iterator) # type: ignore
|
||||
|
||||
# if we're here then we need to check every command if it can run
|
||||
async def predicate(cmd):
|
||||
async def predicate(cmd: Command[Any, ..., Any]) -> bool:
|
||||
try:
|
||||
return await cmd.can_run(self.context)
|
||||
except CommandError:
|
||||
@ -588,7 +637,7 @@ class HelpCommand:
|
||||
ret.sort(key=key)
|
||||
return ret
|
||||
|
||||
def get_max_size(self, commands):
|
||||
def get_max_size(self, commands: Sequence[Command[Any, ..., Any]]) -> int:
|
||||
"""Returns the largest name length of the specified command list.
|
||||
|
||||
Parameters
|
||||
@ -605,7 +654,7 @@ class HelpCommand:
|
||||
as_lengths = (discord.utils._string_width(c.name) for c in commands)
|
||||
return max(as_lengths, default=0)
|
||||
|
||||
def get_destination(self):
|
||||
def get_destination(self) -> discord.abc.MessageableChannel:
|
||||
"""Returns the :class:`~discord.abc.Messageable` where the help command will be output.
|
||||
|
||||
You can override this method to customise the behaviour.
|
||||
@ -619,7 +668,7 @@ class HelpCommand:
|
||||
"""
|
||||
return self.context.channel
|
||||
|
||||
async def send_error_message(self, error):
|
||||
async def send_error_message(self, error: str) -> None:
|
||||
"""|coro|
|
||||
|
||||
Handles the implementation when an error happens in the help command.
|
||||
@ -644,7 +693,7 @@ class HelpCommand:
|
||||
await destination.send(error)
|
||||
|
||||
@_not_overridden
|
||||
async def on_help_command_error(self, ctx, error):
|
||||
async def on_help_command_error(self, ctx: Context[BotT], error: CommandError) -> None:
|
||||
"""|coro|
|
||||
|
||||
The help command's error handler, as specified by :ref:`ext_commands_error_handler`.
|
||||
@ -664,7 +713,7 @@ class HelpCommand:
|
||||
"""
|
||||
pass
|
||||
|
||||
async def send_bot_help(self, mapping):
|
||||
async def send_bot_help(self, mapping: Mapping[Optional[Cog], List[Command[Any, ..., Any]]]) -> None:
|
||||
"""|coro|
|
||||
|
||||
Handles the implementation of the bot command page in the help command.
|
||||
@ -693,7 +742,7 @@ class HelpCommand:
|
||||
"""
|
||||
return None
|
||||
|
||||
async def send_cog_help(self, cog):
|
||||
async def send_cog_help(self, cog: Cog) -> None:
|
||||
"""|coro|
|
||||
|
||||
Handles the implementation of the cog page in the help command.
|
||||
@ -721,7 +770,7 @@ class HelpCommand:
|
||||
"""
|
||||
return None
|
||||
|
||||
async def send_group_help(self, group):
|
||||
async def send_group_help(self, group: Group[Any, ..., Any]) -> None:
|
||||
"""|coro|
|
||||
|
||||
Handles the implementation of the group page in the help command.
|
||||
@ -749,7 +798,7 @@ class HelpCommand:
|
||||
"""
|
||||
return None
|
||||
|
||||
async def send_command_help(self, command):
|
||||
async def send_command_help(self, command: Command[Any, ..., Any]) -> None:
|
||||
"""|coro|
|
||||
|
||||
Handles the implementation of the single command page in the help command.
|
||||
@ -787,7 +836,7 @@ class HelpCommand:
|
||||
"""
|
||||
return None
|
||||
|
||||
async def prepare_help_command(self, ctx, command=None):
|
||||
async def prepare_help_command(self, ctx: Context[BotT], command: Optional[str] = None) -> None:
|
||||
"""|coro|
|
||||
|
||||
A low level method that can be used to prepare the help command
|
||||
@ -811,7 +860,7 @@ class HelpCommand:
|
||||
"""
|
||||
pass
|
||||
|
||||
async def command_callback(self, ctx, *, command=None):
|
||||
async def command_callback(self, ctx: Context[BotT], *, command: Optional[str] = None) -> None:
|
||||
"""|coro|
|
||||
|
||||
The actual implementation of the help command.
|
||||
@ -856,7 +905,7 @@ class HelpCommand:
|
||||
|
||||
for key in keys[1:]:
|
||||
try:
|
||||
found = cmd.all_commands.get(key)
|
||||
found = cmd.all_commands.get(key) # type: ignore
|
||||
except AttributeError:
|
||||
string = await maybe_coro(self.subcommand_not_found, cmd, self.remove_mentions(key))
|
||||
return await self.send_error_message(string)
|
||||
@ -908,28 +957,28 @@ class DefaultHelpCommand(HelpCommand):
|
||||
The paginator used to paginate the help command output.
|
||||
"""
|
||||
|
||||
def __init__(self, **options):
|
||||
self.width = options.pop('width', 80)
|
||||
self.indent = options.pop('indent', 2)
|
||||
self.sort_commands = options.pop('sort_commands', True)
|
||||
self.dm_help = options.pop('dm_help', False)
|
||||
self.dm_help_threshold = options.pop('dm_help_threshold', 1000)
|
||||
self.commands_heading = options.pop('commands_heading', "Commands:")
|
||||
self.no_category = options.pop('no_category', 'No Category')
|
||||
self.paginator = options.pop('paginator', None)
|
||||
def __init__(self, **options: Any) -> None:
|
||||
self.width: int = options.pop('width', 80)
|
||||
self.indent: int = options.pop('indent', 2)
|
||||
self.sort_commands: bool = options.pop('sort_commands', True)
|
||||
self.dm_help: bool = options.pop('dm_help', False)
|
||||
self.dm_help_threshold: int = options.pop('dm_help_threshold', 1000)
|
||||
self.commands_heading: str = options.pop('commands_heading', "Commands:")
|
||||
self.no_category: str = options.pop('no_category', 'No Category')
|
||||
self.paginator: Paginator = options.pop('paginator', None)
|
||||
|
||||
if self.paginator is None:
|
||||
self.paginator = Paginator()
|
||||
self.paginator: Paginator = Paginator()
|
||||
|
||||
super().__init__(**options)
|
||||
|
||||
def shorten_text(self, text):
|
||||
def shorten_text(self, text: str) -> str:
|
||||
""":class:`str`: Shortens text to fit into the :attr:`width`."""
|
||||
if len(text) > self.width:
|
||||
return text[: self.width - 3].rstrip() + '...'
|
||||
return text
|
||||
|
||||
def get_ending_note(self):
|
||||
def get_ending_note(self) -> str:
|
||||
""":class:`str`: Returns help command's ending note. This is mainly useful to override for i18n purposes."""
|
||||
command_name = self.invoked_with
|
||||
return (
|
||||
@ -937,7 +986,9 @@ class DefaultHelpCommand(HelpCommand):
|
||||
f"You can also type {self.context.clean_prefix}{command_name} category for more info on a category."
|
||||
)
|
||||
|
||||
def add_indented_commands(self, commands, *, heading, max_size=None):
|
||||
def add_indented_commands(
|
||||
self, commands: Sequence[Command[Any, ..., Any]], *, heading: str, max_size: Optional[int] = None
|
||||
) -> None:
|
||||
"""Indents a list of commands after the specified heading.
|
||||
|
||||
The formatting is added to the :attr:`paginator`.
|
||||
@ -973,13 +1024,13 @@ class DefaultHelpCommand(HelpCommand):
|
||||
entry = f'{self.indent * " "}{name:<{width}} {command.short_doc}'
|
||||
self.paginator.add_line(self.shorten_text(entry))
|
||||
|
||||
async def send_pages(self):
|
||||
async def send_pages(self) -> None:
|
||||
"""A helper utility to send the page output from :attr:`paginator` to the destination."""
|
||||
destination = self.get_destination()
|
||||
for page in self.paginator.pages:
|
||||
await destination.send(page)
|
||||
|
||||
def add_command_formatting(self, command):
|
||||
def add_command_formatting(self, command: Command[Any, ..., Any]) -> None:
|
||||
"""A utility function to format the non-indented block of commands and groups.
|
||||
|
||||
Parameters
|
||||
@ -1002,7 +1053,7 @@ class DefaultHelpCommand(HelpCommand):
|
||||
self.paginator.add_line(line)
|
||||
self.paginator.add_line()
|
||||
|
||||
def get_destination(self):
|
||||
def get_destination(self) -> discord.abc.Messageable:
|
||||
ctx = self.context
|
||||
if self.dm_help is True:
|
||||
return ctx.author
|
||||
@ -1011,11 +1062,11 @@ class DefaultHelpCommand(HelpCommand):
|
||||
else:
|
||||
return ctx.channel
|
||||
|
||||
async def prepare_help_command(self, ctx, command):
|
||||
async def prepare_help_command(self, ctx: Context[BotT], command: str) -> None:
|
||||
self.paginator.clear()
|
||||
await super().prepare_help_command(ctx, command)
|
||||
|
||||
async def send_bot_help(self, mapping):
|
||||
async def send_bot_help(self, mapping: Mapping[Optional[Cog], List[Command[Any, ..., Any]]]) -> None:
|
||||
ctx = self.context
|
||||
bot = ctx.bot
|
||||
|
||||
@ -1045,12 +1096,12 @@ class DefaultHelpCommand(HelpCommand):
|
||||
|
||||
await self.send_pages()
|
||||
|
||||
async def send_command_help(self, command):
|
||||
async def send_command_help(self, command: Command[Any, ..., Any]) -> None:
|
||||
self.add_command_formatting(command)
|
||||
self.paginator.close_page()
|
||||
await self.send_pages()
|
||||
|
||||
async def send_group_help(self, group):
|
||||
async def send_group_help(self, group: Group[Any, ..., Any]) -> None:
|
||||
self.add_command_formatting(group)
|
||||
|
||||
filtered = await self.filter_commands(group.commands, sort=self.sort_commands)
|
||||
@ -1064,7 +1115,7 @@ class DefaultHelpCommand(HelpCommand):
|
||||
|
||||
await self.send_pages()
|
||||
|
||||
async def send_cog_help(self, cog):
|
||||
async def send_cog_help(self, cog: Cog) -> None:
|
||||
if cog.description:
|
||||
self.paginator.add_line(cog.description, empty=True)
|
||||
|
||||
@ -1111,27 +1162,27 @@ class MinimalHelpCommand(HelpCommand):
|
||||
The paginator used to paginate the help command output.
|
||||
"""
|
||||
|
||||
def __init__(self, **options):
|
||||
self.sort_commands = options.pop('sort_commands', True)
|
||||
self.commands_heading = options.pop('commands_heading', "Commands")
|
||||
self.dm_help = options.pop('dm_help', False)
|
||||
self.dm_help_threshold = options.pop('dm_help_threshold', 1000)
|
||||
self.aliases_heading = options.pop('aliases_heading', "Aliases:")
|
||||
self.no_category = options.pop('no_category', 'No Category')
|
||||
self.paginator = options.pop('paginator', None)
|
||||
def __init__(self, **options: Any) -> None:
|
||||
self.sort_commands: bool = options.pop('sort_commands', True)
|
||||
self.commands_heading: str = options.pop('commands_heading', "Commands")
|
||||
self.dm_help: bool = options.pop('dm_help', False)
|
||||
self.dm_help_threshold: int = options.pop('dm_help_threshold', 1000)
|
||||
self.aliases_heading: str = options.pop('aliases_heading', "Aliases:")
|
||||
self.no_category: str = options.pop('no_category', 'No Category')
|
||||
self.paginator: Paginator = options.pop('paginator', None)
|
||||
|
||||
if self.paginator is None:
|
||||
self.paginator = Paginator(suffix=None, prefix=None)
|
||||
self.paginator: Paginator = Paginator(suffix=None, prefix=None)
|
||||
|
||||
super().__init__(**options)
|
||||
|
||||
async def send_pages(self):
|
||||
async def send_pages(self) -> None:
|
||||
"""A helper utility to send the page output from :attr:`paginator` to the destination."""
|
||||
destination = self.get_destination()
|
||||
for page in self.paginator.pages:
|
||||
await destination.send(page)
|
||||
|
||||
def get_opening_note(self):
|
||||
def get_opening_note(self) -> str:
|
||||
"""Returns help command's opening note. This is mainly useful to override for i18n purposes.
|
||||
|
||||
The default implementation returns ::
|
||||
@ -1150,10 +1201,10 @@ class MinimalHelpCommand(HelpCommand):
|
||||
f"You can also use `{self.context.clean_prefix}{command_name} [category]` for more info on a category."
|
||||
)
|
||||
|
||||
def get_command_signature(self, command):
|
||||
def get_command_signature(self, command: Command[Any, ..., Any]) -> str:
|
||||
return f'{self.context.clean_prefix}{command.qualified_name} {command.signature}'
|
||||
|
||||
def get_ending_note(self):
|
||||
def get_ending_note(self) -> str:
|
||||
"""Return the help command's ending note. This is mainly useful to override for i18n purposes.
|
||||
|
||||
The default implementation does nothing.
|
||||
@ -1163,9 +1214,9 @@ class MinimalHelpCommand(HelpCommand):
|
||||
:class:`str`
|
||||
The help command ending note.
|
||||
"""
|
||||
return None
|
||||
return ''
|
||||
|
||||
def add_bot_commands_formatting(self, commands, heading):
|
||||
def add_bot_commands_formatting(self, commands: Sequence[Command[Any, ..., Any]], heading: str) -> None:
|
||||
"""Adds the minified bot heading with commands to the output.
|
||||
|
||||
The formatting should be added to the :attr:`paginator`.
|
||||
@ -1186,7 +1237,7 @@ class MinimalHelpCommand(HelpCommand):
|
||||
self.paginator.add_line(f'__**{heading}**__')
|
||||
self.paginator.add_line(joined)
|
||||
|
||||
def add_subcommand_formatting(self, command):
|
||||
def add_subcommand_formatting(self, command: Command[Any, ..., Any]) -> None:
|
||||
"""Adds formatting information on a subcommand.
|
||||
|
||||
The formatting should be added to the :attr:`paginator`.
|
||||
@ -1202,7 +1253,7 @@ class MinimalHelpCommand(HelpCommand):
|
||||
fmt = '{0}{1} \N{EN DASH} {2}' if command.short_doc else '{0}{1}'
|
||||
self.paginator.add_line(fmt.format(self.context.clean_prefix, command.qualified_name, command.short_doc))
|
||||
|
||||
def add_aliases_formatting(self, aliases):
|
||||
def add_aliases_formatting(self, aliases: Sequence[str]) -> None:
|
||||
"""Adds the formatting information on a command's aliases.
|
||||
|
||||
The formatting should be added to the :attr:`paginator`.
|
||||
@ -1219,7 +1270,7 @@ class MinimalHelpCommand(HelpCommand):
|
||||
"""
|
||||
self.paginator.add_line(f'**{self.aliases_heading}** {", ".join(aliases)}', empty=True)
|
||||
|
||||
def add_command_formatting(self, command):
|
||||
def add_command_formatting(self, command: Command[Any, ..., Any]) -> None:
|
||||
"""A utility function to format commands and groups.
|
||||
|
||||
Parameters
|
||||
@ -1246,7 +1297,7 @@ class MinimalHelpCommand(HelpCommand):
|
||||
self.paginator.add_line(line)
|
||||
self.paginator.add_line()
|
||||
|
||||
def get_destination(self):
|
||||
def get_destination(self) -> discord.abc.Messageable:
|
||||
ctx = self.context
|
||||
if self.dm_help is True:
|
||||
return ctx.author
|
||||
@ -1255,11 +1306,11 @@ class MinimalHelpCommand(HelpCommand):
|
||||
else:
|
||||
return ctx.channel
|
||||
|
||||
async def prepare_help_command(self, ctx, command):
|
||||
async def prepare_help_command(self, ctx: Context[BotT], command: str) -> None:
|
||||
self.paginator.clear()
|
||||
await super().prepare_help_command(ctx, command)
|
||||
|
||||
async def send_bot_help(self, mapping):
|
||||
async def send_bot_help(self, mapping: Mapping[Optional[Cog], List[Command[Any, ..., Any]]]) -> None:
|
||||
ctx = self.context
|
||||
bot = ctx.bot
|
||||
|
||||
@ -1272,7 +1323,7 @@ class MinimalHelpCommand(HelpCommand):
|
||||
|
||||
no_category = f'\u200b{self.no_category}'
|
||||
|
||||
def get_category(command, *, no_category=no_category):
|
||||
def get_category(command: Command[Any, ..., Any], *, no_category: str = no_category) -> str:
|
||||
cog = command.cog
|
||||
return cog.qualified_name if cog is not None else no_category
|
||||
|
||||
@ -1290,7 +1341,7 @@ class MinimalHelpCommand(HelpCommand):
|
||||
|
||||
await self.send_pages()
|
||||
|
||||
async def send_cog_help(self, cog):
|
||||
async def send_cog_help(self, cog: Cog) -> None:
|
||||
bot = self.context.bot
|
||||
if bot.description:
|
||||
self.paginator.add_line(bot.description, empty=True)
|
||||
@ -1315,7 +1366,7 @@ class MinimalHelpCommand(HelpCommand):
|
||||
|
||||
await self.send_pages()
|
||||
|
||||
async def send_group_help(self, group):
|
||||
async def send_group_help(self, group: Group[Any, ..., Any]) -> None:
|
||||
self.add_command_formatting(group)
|
||||
|
||||
filtered = await self.filter_commands(group.commands, sort=self.sort_commands)
|
||||
@ -1335,7 +1386,7 @@ class MinimalHelpCommand(HelpCommand):
|
||||
|
||||
await self.send_pages()
|
||||
|
||||
async def send_command_help(self, command):
|
||||
async def send_command_help(self, command: Command[Any, ..., Any]) -> None:
|
||||
self.add_command_formatting(command)
|
||||
self.paginator.close_page()
|
||||
await self.send_pages()
|
||||
|
@ -21,6 +21,11 @@ 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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from .errors import UnexpectedQuoteError, InvalidEndOfQuotedStringError, ExpectedClosingQuoteError
|
||||
|
||||
# map from opening quotes to closing quotes
|
||||
@ -47,24 +52,24 @@ _all_quotes = set(_quotes.keys()) | set(_quotes.values())
|
||||
|
||||
|
||||
class StringView:
|
||||
def __init__(self, buffer):
|
||||
self.index = 0
|
||||
self.buffer = buffer
|
||||
self.end = len(buffer)
|
||||
def __init__(self, buffer: str) -> None:
|
||||
self.index: int = 0
|
||||
self.buffer: str = buffer
|
||||
self.end: int = len(buffer)
|
||||
self.previous = 0
|
||||
|
||||
@property
|
||||
def current(self):
|
||||
def current(self) -> Optional[str]:
|
||||
return None if self.eof else self.buffer[self.index]
|
||||
|
||||
@property
|
||||
def eof(self):
|
||||
def eof(self) -> bool:
|
||||
return self.index >= self.end
|
||||
|
||||
def undo(self):
|
||||
def undo(self) -> None:
|
||||
self.index = self.previous
|
||||
|
||||
def skip_ws(self):
|
||||
def skip_ws(self) -> bool:
|
||||
pos = 0
|
||||
while not self.eof:
|
||||
try:
|
||||
@ -79,7 +84,7 @@ class StringView:
|
||||
self.index += pos
|
||||
return self.previous != self.index
|
||||
|
||||
def skip_string(self, string):
|
||||
def skip_string(self, string: str) -> bool:
|
||||
strlen = len(string)
|
||||
if self.buffer[self.index : self.index + strlen] == string:
|
||||
self.previous = self.index
|
||||
@ -87,19 +92,19 @@ class StringView:
|
||||
return True
|
||||
return False
|
||||
|
||||
def read_rest(self):
|
||||
def read_rest(self) -> str:
|
||||
result = self.buffer[self.index :]
|
||||
self.previous = self.index
|
||||
self.index = self.end
|
||||
return result
|
||||
|
||||
def read(self, n):
|
||||
def read(self, n: int) -> str:
|
||||
result = self.buffer[self.index : self.index + n]
|
||||
self.previous = self.index
|
||||
self.index += n
|
||||
return result
|
||||
|
||||
def get(self):
|
||||
def get(self) -> Optional[str]:
|
||||
try:
|
||||
result = self.buffer[self.index + 1]
|
||||
except IndexError:
|
||||
@ -109,7 +114,7 @@ class StringView:
|
||||
self.index += 1
|
||||
return result
|
||||
|
||||
def get_word(self):
|
||||
def get_word(self) -> str:
|
||||
pos = 0
|
||||
while not self.eof:
|
||||
try:
|
||||
@ -119,12 +124,12 @@ class StringView:
|
||||
pos += 1
|
||||
except IndexError:
|
||||
break
|
||||
self.previous = self.index
|
||||
self.previous: int = self.index
|
||||
result = self.buffer[self.index : self.index + pos]
|
||||
self.index += pos
|
||||
return result
|
||||
|
||||
def get_quoted_word(self):
|
||||
def get_quoted_word(self) -> Optional[str]:
|
||||
current = self.current
|
||||
if current is None:
|
||||
return None
|
||||
@ -187,5 +192,5 @@ class StringView:
|
||||
|
||||
result.append(current)
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
return f'<StringView pos: {self.index} prev: {self.previous} end: {self.end} eof: {self.eof}>'
|
||||
|
@ -110,15 +110,15 @@ class SleepHandle:
|
||||
__slots__ = ('future', 'loop', 'handle')
|
||||
|
||||
def __init__(self, dt: datetime.datetime, *, loop: asyncio.AbstractEventLoop) -> None:
|
||||
self.loop = loop
|
||||
self.future = future = loop.create_future()
|
||||
self.loop: asyncio.AbstractEventLoop = loop
|
||||
self.future: asyncio.Future[None] = loop.create_future()
|
||||
relative_delta = discord.utils.compute_timedelta(dt)
|
||||
self.handle = loop.call_later(relative_delta, future.set_result, True)
|
||||
self.handle = loop.call_later(relative_delta, self.future.set_result, True)
|
||||
|
||||
def recalculate(self, dt: datetime.datetime) -> None:
|
||||
self.handle.cancel()
|
||||
relative_delta = discord.utils.compute_timedelta(dt)
|
||||
self.handle = self.loop.call_later(relative_delta, self.future.set_result, True)
|
||||
self.handle: asyncio.TimerHandle = self.loop.call_later(relative_delta, self.future.set_result, True)
|
||||
|
||||
def wait(self) -> asyncio.Future[Any]:
|
||||
return self.future
|
||||
|
Reference in New Issue
Block a user