Browse Source

Merge 63e799d401 into 26855160f8

pull/10189/merge
Soheab 6 days ago
committed by GitHub
parent
commit
b388571010
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 7
      discord/abc.py
  2. 7
      discord/app_commands/checks.py
  3. 5
      discord/app_commands/commands.py
  4. 55
      discord/channel.py
  5. 27
      discord/client.py
  6. 46
      discord/ext/commands/bot.py
  7. 18
      discord/ext/commands/cog.py
  8. 81
      discord/ext/commands/core.py
  9. 51
      discord/ext/commands/help.py
  10. 63
      discord/ext/commands/hybrid.py
  11. 46
      discord/flags.py
  12. 84
      discord/permissions.py
  13. 8
      discord/shard.py

7
discord/abc.py

@ -73,7 +73,7 @@ __all__ = (
T = TypeVar('T', bound=VoiceProtocol) T = TypeVar('T', bound=VoiceProtocol)
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import Self from typing_extensions import Self, Unpack
from .client import Client from .client import Client
from .user import ClientUser from .user import ClientUser
@ -108,6 +108,7 @@ if TYPE_CHECKING:
from .types.snowflake import ( from .types.snowflake import (
SnowflakeList, SnowflakeList,
) )
from .permissions import _PermissionOverwriteKwargs
PartialMessageableChannel = Union[TextChannel, VoiceChannel, StageChannel, Thread, DMChannel, PartialMessageable] PartialMessageableChannel = Union[TextChannel, VoiceChannel, StageChannel, Thread, DMChannel, PartialMessageable]
MessageableChannel = Union[PartialMessageableChannel, GroupChannel] MessageableChannel = Union[PartialMessageableChannel, GroupChannel]
@ -886,7 +887,7 @@ class GuildChannel:
target: Union[Member, Role], target: Union[Member, Role],
*, *,
reason: Optional[str] = ..., reason: Optional[str] = ...,
**permissions: Optional[bool], **permissions: Unpack[_PermissionOverwriteKwargs],
) -> None: ) -> None:
... ...
@ -896,7 +897,7 @@ class GuildChannel:
*, *,
overwrite: Any = _undefined, overwrite: Any = _undefined,
reason: Optional[str] = None, reason: Optional[str] = None,
**permissions: Optional[bool], **permissions: Unpack[_PermissionOverwriteKwargs],
) -> None: ) -> None:
r"""|coro| r"""|coro|

7
discord/app_commands/checks.py

@ -55,8 +55,9 @@ from ..utils import get as utils_get, MISSING, maybe_coroutine
T = TypeVar('T') T = TypeVar('T')
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import Self from typing_extensions import Self, Unpack
from ..interactions import Interaction from ..interactions import Interaction
from ..permissions import _PermissionsKwargs
CooldownFunction = Union[ CooldownFunction = Union[
Callable[[Interaction[Any]], Coroutine[Any, Any, T]], Callable[[Interaction[Any]], Coroutine[Any, Any, T]],
@ -286,7 +287,7 @@ def has_any_role(*items: Union[int, str]) -> Callable[[T], T]:
return check(predicate) return check(predicate)
def has_permissions(**perms: bool) -> Callable[[T], T]: def has_permissions(**perms: Unpack[_PermissionsKwargs]) -> Callable[[T], T]:
r"""A :func:`~discord.app_commands.check` that is added that checks if the member r"""A :func:`~discord.app_commands.check` that is added that checks if the member
has all of the permissions necessary. has all of the permissions necessary.
@ -341,7 +342,7 @@ def has_permissions(**perms: bool) -> Callable[[T], T]:
return check(predicate) return check(predicate)
def bot_has_permissions(**perms: bool) -> Callable[[T], T]: def bot_has_permissions(**perms: Unpack[_PermissionsKwargs]) -> Callable[[T], T]:
"""Similar to :func:`has_permissions` except checks if the bot itself has """Similar to :func:`has_permissions` except checks if the bot itself has
the permissions listed. This relies on :attr:`discord.Interaction.app_permissions`. the permissions listed. This relies on :attr:`discord.Interaction.app_permissions`.

5
discord/app_commands/commands.py

@ -61,7 +61,7 @@ from ..permissions import Permissions
from ..utils import resolve_annotation, MISSING, is_inside_class, maybe_coroutine, async_all, _shorten, _to_kebab_case from ..utils import resolve_annotation, MISSING, is_inside_class, maybe_coroutine, async_all, _shorten, _to_kebab_case
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import ParamSpec, Concatenate from typing_extensions import ParamSpec, Concatenate, Unpack
from ..interactions import Interaction from ..interactions import Interaction
from ..abc import Snowflake from ..abc import Snowflake
from .namespace import Namespace from .namespace import Namespace
@ -73,6 +73,7 @@ if TYPE_CHECKING:
# However, for type hinting purposes it's unfortunately necessary for one to # However, for type hinting purposes it's unfortunately necessary for one to
# reference the other to prevent type checking errors in callbacks # reference the other to prevent type checking errors in callbacks
from discord.ext import commands from discord.ext import commands
from discord.permissions import _PermissionsKwargs
ErrorFunc = Callable[[Interaction, AppCommandError], Coroutine[Any, Any, None]] ErrorFunc = Callable[[Interaction, AppCommandError], Coroutine[Any, Any, None]]
@ -2821,7 +2822,7 @@ def allowed_installs(
return inner return inner
def default_permissions(perms_obj: Optional[Permissions] = None, /, **perms: bool) -> Callable[[T], T]: def default_permissions(perms_obj: Optional[Permissions] = None, /, **perms: Unpack[_PermissionsKwargs]) -> Callable[[T], T]:
r"""A decorator that sets the default permissions needed to execute this command. r"""A decorator that sets the default permissions needed to execute this command.
When this decorator is used, by default users must have these permissions to execute the command. When this decorator is used, by default users must have these permissions to execute the command.

55
discord/channel.py

@ -39,6 +39,7 @@ from typing import (
Sequence, Sequence,
Tuple, Tuple,
TypeVar, TypeVar,
TypedDict,
Union, Union,
overload, overload,
) )
@ -85,7 +86,7 @@ __all__ = (
) )
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import Self from typing_extensions import Self, Unpack
from .types.threads import ThreadArchiveDuration from .types.threads import ThreadArchiveDuration
from .role import Role from .role import Role
@ -120,6 +121,50 @@ if TYPE_CHECKING:
OverwriteKeyT = TypeVar('OverwriteKeyT', Role, BaseUser, Object, Union[Role, Member, Object]) OverwriteKeyT = TypeVar('OverwriteKeyT', Role, BaseUser, Object, Union[Role, Member, Object])
class _CreateChannelWithCategory(TypedDict, total=False):
category: Optional[CategoryChannel]
class _CreateNewsChannel(TypedDict, total=False):
news: bool
class _BaseCreateChannelOptions(TypedDict, total=False):
reason: Optional[str]
position: int
class _CreateTextChannelOptions(_BaseCreateChannelOptions, total=False):
topic: str
slowmode_delay: int
nsfw: bool
overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite]
default_auto_archive_duration: int
default_thread_slowmode_delay: int
class _CreateVoiceChannelOptions(_BaseCreateChannelOptions, total=False):
bitrate: int
user_limit: int
rtc_region: Optional[str]
video_quality_mode: VideoQualityMode
overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite]
class _CreateStageChannelOptions(_CreateVoiceChannelOptions, total=False):
bitrate: int
user_limit: int
rtc_region: Optional[str]
video_quality_mode: VideoQualityMode
overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite]
class _CreateForumChannelOptions(_CreateTextChannelOptions, total=False):
topic: str
slowmode_delay: int
nsfw: bool
overwrites: Mapping[Union[Role, Member, Object], PermissionOverwrite]
default_auto_archive_duration: int
default_thread_slowmode_delay: int
default_sort_order: ForumOrderType
default_reaction_emoji: EmojiInputType
default_layout: ForumLayoutType
available_tags: Sequence[ForumTag]
class ThreadWithMessage(NamedTuple): class ThreadWithMessage(NamedTuple):
thread: Thread thread: Thread
@ -2194,7 +2239,7 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
r.sort(key=lambda c: (c.position, c.id)) r.sort(key=lambda c: (c.position, c.id))
return r return r
async def create_text_channel(self, name: str, **options: Any) -> TextChannel: async def create_text_channel(self, name: str, **options: Unpack[_CreateTextChannelOptions]) -> TextChannel:
"""|coro| """|coro|
A shortcut method to :meth:`Guild.create_text_channel` to create a :class:`TextChannel` in the category. A shortcut method to :meth:`Guild.create_text_channel` to create a :class:`TextChannel` in the category.
@ -2206,7 +2251,7 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
""" """
return await self.guild.create_text_channel(name, category=self, **options) return await self.guild.create_text_channel(name, category=self, **options)
async def create_voice_channel(self, name: str, **options: Any) -> VoiceChannel: async def create_voice_channel(self, name: str, **options: Unpack[_CreateVoiceChannelOptions]) -> VoiceChannel:
"""|coro| """|coro|
A shortcut method to :meth:`Guild.create_voice_channel` to create a :class:`VoiceChannel` in the category. A shortcut method to :meth:`Guild.create_voice_channel` to create a :class:`VoiceChannel` in the category.
@ -2218,7 +2263,7 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
""" """
return await self.guild.create_voice_channel(name, category=self, **options) return await self.guild.create_voice_channel(name, category=self, **options)
async def create_stage_channel(self, name: str, **options: Any) -> StageChannel: async def create_stage_channel(self, name: str, **options: Unpack[_CreateStageChannelOptions]) -> StageChannel:
"""|coro| """|coro|
A shortcut method to :meth:`Guild.create_stage_channel` to create a :class:`StageChannel` in the category. A shortcut method to :meth:`Guild.create_stage_channel` to create a :class:`StageChannel` in the category.
@ -2232,7 +2277,7 @@ class CategoryChannel(discord.abc.GuildChannel, Hashable):
""" """
return await self.guild.create_stage_channel(name, category=self, **options) return await self.guild.create_stage_channel(name, category=self, **options)
async def create_forum(self, name: str, **options: Any) -> ForumChannel: async def create_forum(self, name: str, **options: Unpack[_CreateForumChannelOptions]) -> ForumChannel:
"""|coro| """|coro|
A shortcut method to :meth:`Guild.create_forum` to create a :class:`ForumChannel` in the category. A shortcut method to :meth:`Guild.create_forum` to create a :class:`ForumChannel` in the category.

27
discord/client.py

@ -42,6 +42,7 @@ from typing import (
Tuple, Tuple,
Type, Type,
TypeVar, TypeVar,
TypedDict,
Union, Union,
overload, overload,
) )
@ -82,7 +83,7 @@ from .soundboard import SoundboardDefaultSound, SoundboardSound
if TYPE_CHECKING: if TYPE_CHECKING:
from types import TracebackType from types import TracebackType
from typing_extensions import Self from typing_extensions import Self, Unpack
from .abc import Messageable, PrivateChannel, Snowflake, SnowflakeTime from .abc import Messageable, PrivateChannel, Snowflake, SnowflakeTime
from .app_commands import Command, ContextMenu from .app_commands import Command, ContextMenu
@ -120,6 +121,28 @@ if TYPE_CHECKING:
from .audit_logs import AuditLogEntry from .audit_logs import AuditLogEntry
from .poll import PollAnswer from .poll import PollAnswer
from .subscription import Subscription from .subscription import Subscription
from .flags import MemberCacheFlags
class _ClientOptions(TypedDict, total=False):
max_messages: int
proxy: str
proxy_auth: aiohttp.BasicAuth
shard_id: int
shard_count: int
application_id: int
member_cache_flags: MemberCacheFlags
chunk_guilds_at_startup: bool
status: Status
activity: BaseActivity
allowed_mentions: AllowedMentions
heartbeat_timeout: float
guild_ready_timeout: float
assume_unsync_clock: bool
enable_debug_events: bool
enable_raw_presences: bool
http_trace: aiohttp.TraceConfig
max_ratelimit_timeout: float
connector: aiohttp.BaseConnector
# fmt: off # fmt: off
@ -272,7 +295,7 @@ class Client:
The websocket gateway the client is currently connected to. Could be ``None``. The websocket gateway the client is currently connected to. Could be ``None``.
""" """
def __init__(self, *, intents: Intents, **options: Any) -> None: def __init__(self, *, intents: Intents, **options: Unpack[_ClientOptions]) -> None:
self.loop: asyncio.AbstractEventLoop = _loop self.loop: asyncio.AbstractEventLoop = _loop
# self.ws is set in the connect method # self.ws is set in the connect method
self.ws: DiscordWebSocket = None # type: ignore self.ws: DiscordWebSocket = None # type: ignore

46
discord/ext/commands/bot.py

@ -64,7 +64,7 @@ from .cog import Cog
from .hybrid import hybrid_command, hybrid_group, HybridCommand, HybridGroup from .hybrid import hybrid_command, hybrid_group, HybridCommand, HybridGroup
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import Self from typing_extensions import Self, Unpack
import importlib.machinery import importlib.machinery
@ -80,12 +80,24 @@ if TYPE_CHECKING:
MaybeAwaitableFunc, MaybeAwaitableFunc,
) )
from .core import Command from .core import Command
from .hybrid import CommandCallback, ContextT, P from .hybrid import CommandCallback, ContextT, P, _HybridCommandDecoratorKwargs, _HybridGroupDecoratorKwargs
from discord.client import _ClientOptions
from discord.shard import _AutoShardedClientOptions
_Prefix = Union[Iterable[str], str] _Prefix = Union[Iterable[str], str]
_PrefixCallable = MaybeAwaitableFunc[[BotT, Message], _Prefix] _PrefixCallable = MaybeAwaitableFunc[[BotT, Message], _Prefix]
PrefixType = Union[_Prefix, _PrefixCallable[BotT]] PrefixType = Union[_Prefix, _PrefixCallable[BotT]]
class _BotOptions(_ClientOptions, total=False):
owner_id: int
owner_ids: Collection[int]
strip_after_prefix: bool
case_insensitive: bool
class _AutoShardedBotOptions(_AutoShardedClientOptions, _BotOptions):
...
__all__ = ( __all__ = (
'when_mentioned', 'when_mentioned',
'when_mentioned_or', 'when_mentioned_or',
@ -169,7 +181,7 @@ class BotBase(GroupMixin[None]):
allowed_contexts: app_commands.AppCommandContext = MISSING, allowed_contexts: app_commands.AppCommandContext = MISSING,
allowed_installs: app_commands.AppInstallationType = MISSING, allowed_installs: app_commands.AppInstallationType = MISSING,
intents: discord.Intents, intents: discord.Intents,
**options: Any, **options: Unpack[_BotOptions],
) -> None: ) -> None:
super().__init__(intents=intents, **options) super().__init__(intents=intents, **options)
self.command_prefix: PrefixType[BotT] = command_prefix # type: ignore self.command_prefix: PrefixType[BotT] = command_prefix # type: ignore
@ -281,7 +293,7 @@ class BotBase(GroupMixin[None]):
name: Union[str, app_commands.locale_str] = MISSING, name: Union[str, app_commands.locale_str] = MISSING,
with_app_command: bool = True, with_app_command: bool = True,
*args: Any, *args: Any,
**kwargs: Any, **kwargs: Unpack[_HybridCommandDecoratorKwargs], # type: ignore # name, with_app_command
) -> Callable[[CommandCallback[Any, ContextT, P, T]], HybridCommand[Any, P, T]]: ) -> Callable[[CommandCallback[Any, ContextT, P, T]], HybridCommand[Any, P, T]]:
"""A shortcut decorator that invokes :func:`~discord.ext.commands.hybrid_command` and adds it to """A shortcut decorator that invokes :func:`~discord.ext.commands.hybrid_command` and adds it to
the internal command list via :meth:`add_command`. the internal command list via :meth:`add_command`.
@ -293,8 +305,8 @@ class BotBase(GroupMixin[None]):
""" """
def decorator(func: CommandCallback[Any, ContextT, P, T]): def decorator(func: CommandCallback[Any, ContextT, P, T]):
kwargs.setdefault('parent', self) kwargs.setdefault('parent', self) # type: ignore # parent is not for the user to set
result = hybrid_command(name=name, *args, with_app_command=with_app_command, **kwargs)(func) result = hybrid_command(name=name, *args, with_app_command=with_app_command, **kwargs)(func) # type: ignore # name, with_app_command
self.add_command(result) self.add_command(result)
return result return result
@ -305,7 +317,7 @@ class BotBase(GroupMixin[None]):
name: Union[str, app_commands.locale_str] = MISSING, name: Union[str, app_commands.locale_str] = MISSING,
with_app_command: bool = True, with_app_command: bool = True,
*args: Any, *args: Any,
**kwargs: Any, **kwargs: Unpack[_HybridGroupDecoratorKwargs], # type: ignore # name, with_app_command
) -> Callable[[CommandCallback[Any, ContextT, P, T]], HybridGroup[Any, P, T]]: ) -> Callable[[CommandCallback[Any, ContextT, P, T]], HybridGroup[Any, P, T]]:
"""A shortcut decorator that invokes :func:`~discord.ext.commands.hybrid_group` and adds it to """A shortcut decorator that invokes :func:`~discord.ext.commands.hybrid_group` and adds it to
the internal command list via :meth:`add_command`. the internal command list via :meth:`add_command`.
@ -317,8 +329,8 @@ class BotBase(GroupMixin[None]):
""" """
def decorator(func: CommandCallback[Any, ContextT, P, T]): def decorator(func: CommandCallback[Any, ContextT, P, T]):
kwargs.setdefault('parent', self) kwargs.setdefault('parent', self) # type: ignore # parent is not for the user to set
result = hybrid_group(name=name, *args, with_app_command=with_app_command, **kwargs)(func) result = hybrid_group(name=name, *args, with_app_command=with_app_command, **kwargs)(func) # type: ignore # name, with_app_command
self.add_command(result) self.add_command(result)
return result return result
@ -1527,4 +1539,18 @@ class AutoShardedBot(BotBase, discord.AutoShardedClient):
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
pass if TYPE_CHECKING:
def __init__(
self,
command_prefix: PrefixType[BotT],
*,
help_command: Optional[HelpCommand] = _default,
tree_cls: Type[app_commands.CommandTree[Any]] = app_commands.CommandTree,
description: Optional[str] = None,
allowed_contexts: app_commands.AppCommandContext = MISSING,
allowed_installs: app_commands.AppInstallationType = MISSING,
intents: discord.Intents,
**kwargs: Unpack[_AutoShardedBotOptions],
) -> None:
...

18
discord/ext/commands/cog.py

@ -44,18 +44,30 @@ from typing import (
Tuple, Tuple,
TypeVar, TypeVar,
Union, Union,
TypedDict,
) )
from ._types import _BaseCommand, BotT from ._types import _BaseCommand, BotT
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import Self from typing_extensions import Self, Unpack
from discord.abc import Snowflake from discord.abc import Snowflake
from discord._types import ClientT from discord._types import ClientT
from .bot import BotBase from .bot import BotBase
from .context import Context from .context import Context
from .core import Command from .core import Command, _CommandDecoratorKwargs
class _CogKwargs(TypedDict, total=False):
name: str
group_name: Union[str, app_commands.locale_str]
description: str
group_description: Union[str, app_commands.locale_str]
group_nsfw: bool
group_auto_locale_strings: bool
group_extras: Dict[Any, Any]
command_attrs: _CommandDecoratorKwargs
__all__ = ( __all__ = (
'CogMeta', 'CogMeta',
@ -169,7 +181,7 @@ class CogMeta(type):
__cog_app_commands__: List[Union[app_commands.Group, app_commands.Command[Any, ..., Any]]] __cog_app_commands__: List[Union[app_commands.Group, app_commands.Command[Any, ..., Any]]]
__cog_listeners__: List[Tuple[str, str]] __cog_listeners__: List[Tuple[str, str]]
def __new__(cls, *args: Any, **kwargs: Any) -> CogMeta: def __new__(cls, *args: Any, **kwargs: Unpack[_CogKwargs]) -> CogMeta:
name, bases, attrs = args name, bases, attrs = args
if any(issubclass(base, app_commands.Group) for base in bases): if any(issubclass(base, app_commands.Group) for base in bases):
raise TypeError( raise TypeError(

81
discord/ext/commands/core.py

@ -43,6 +43,7 @@ from typing import (
TypeVar, TypeVar,
Union, Union,
overload, overload,
TypedDict,
) )
import re import re
@ -58,10 +59,39 @@ from .parameters import Parameter, Signature
from discord.app_commands.commands import NUMPY_DOCSTRING_ARG_REGEX from discord.app_commands.commands import NUMPY_DOCSTRING_ARG_REGEX
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import Concatenate, ParamSpec, Self from typing_extensions import Concatenate, ParamSpec, Self, Unpack
from ._types import BotT, Check, ContextT, Coro, CoroFunc, Error, Hook, UserCheck from ._types import BotT, Check, ContextT, Coro, CoroFunc, Error, Hook, UserCheck
from discord.permissions import _PermissionsKwargs
class _CommandDecoratorKwargs(TypedDict, total=False):
enabled: bool
help: str
brief: str
usage: str
rest_is_raw: bool
aliases: List[str]
description: str
hidden: bool
checks: List[UserCheck[Context[Any]]]
cooldown: CooldownMapping[Context[Any]]
max_concurrency: MaxConcurrency
require_var_positional: bool
cooldown_after_parsing: bool
ignore_extra: bool
extras: Dict[Any, Any]
class _CommandKwargs(_CommandDecoratorKwargs, total=False):
name: str
class _GroupDecoratorKwargs(_CommandDecoratorKwargs, total=False):
invoke_without_command: bool
case_insensitive: bool
class _GroupKwargs(_GroupDecoratorKwargs, total=False):
name: str
__all__ = ( __all__ = (
'Command', 'Command',
@ -368,9 +398,9 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
__original_kwargs__: Dict[str, Any] __original_kwargs__: _CommandKwargs
def __new__(cls, *args: Any, **kwargs: Any) -> Self: def __new__(cls, *args: Any, **kwargs: Unpack[_CommandKwargs]) -> Self:
# if you're wondering why this is done, it's because we need to ensure # if you're wondering why this is done, it's because we need to ensure
# we have a complete original copy of **kwargs even for classes that # we have a complete original copy of **kwargs even for classes that
# mess with it by popping before delegating to the subclass __init__. # mess with it by popping before delegating to the subclass __init__.
@ -393,7 +423,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
Callable[Concatenate[Context[Any], P], Coro[T]], Callable[Concatenate[Context[Any], P], Coro[T]],
], ],
/, /,
**kwargs: Any, **kwargs: Unpack[_CommandKwargs],
) -> None: ) -> None:
if not asyncio.iscoroutinefunction(func): if not asyncio.iscoroutinefunction(func):
raise TypeError('Callback must be a coroutine.') raise TypeError('Callback must be a coroutine.')
@ -556,7 +586,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
except ValueError: except ValueError:
pass pass
def update(self, **kwargs: Any) -> None: def update(self, **kwargs: Unpack[_CommandKwargs]) -> None:
"""Updates :class:`Command` instance with updated attribute. """Updates :class:`Command` instance with updated attribute.
This works similarly to the :func:`~discord.ext.commands.command` decorator in terms This works similarly to the :func:`~discord.ext.commands.command` decorator in terms
@ -564,7 +594,7 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
subclass constructors, sans the name and callback. subclass constructors, sans the name and callback.
""" """
cog = self.cog cog = self.cog
self.__init__(self.callback, **dict(self.__original_kwargs__, **kwargs)) self.__init__(self.callback, **dict(self.__original_kwargs__, **kwargs)) # type: ignore # it's a typeddict
self.cog = cog self.cog = cog
async def __call__(self, context: Context[BotT], /, *args: P.args, **kwargs: P.kwargs) -> T: async def __call__(self, context: Context[BotT], /, *args: P.args, **kwargs: P.kwargs) -> T:
@ -1468,7 +1498,7 @@ class GroupMixin(Generic[CogT]):
self: GroupMixin[CogT], self: GroupMixin[CogT],
name: str = ..., name: str = ...,
*args: Any, *args: Any,
**kwargs: Any, **kwargs: Unpack[_CommandDecoratorKwargs],
) -> Callable[ ) -> Callable[
[ [
Union[ Union[
@ -1486,7 +1516,7 @@ class GroupMixin(Generic[CogT]):
name: str = ..., name: str = ...,
cls: Type[CommandT] = ..., # type: ignore # previous overload handles case where cls is not set cls: Type[CommandT] = ..., # type: ignore # previous overload handles case where cls is not set
*args: Any, *args: Any,
**kwargs: Any, **kwargs: Unpack[_CommandDecoratorKwargs],
) -> Callable[ ) -> Callable[
[ [
Union[ Union[
@ -1503,7 +1533,7 @@ class GroupMixin(Generic[CogT]):
name: str = MISSING, name: str = MISSING,
cls: Type[Command[Any, ..., Any]] = MISSING, cls: Type[Command[Any, ..., Any]] = MISSING,
*args: Any, *args: Any,
**kwargs: Any, **kwargs: Unpack[_CommandDecoratorKwargs],
) -> Any: ) -> Any:
"""A shortcut decorator that invokes :func:`~discord.ext.commands.command` and adds it to """A shortcut decorator that invokes :func:`~discord.ext.commands.command` and adds it to
the internal command list via :meth:`~.GroupMixin.add_command`. the internal command list via :meth:`~.GroupMixin.add_command`.
@ -1515,8 +1545,7 @@ class GroupMixin(Generic[CogT]):
""" """
def decorator(func): def decorator(func):
kwargs.setdefault('parent', self) # type: ignore # the parent kwarg is not for users to set.
kwargs.setdefault('parent', self)
result = command(name=name, cls=cls, *args, **kwargs)(func) result = command(name=name, cls=cls, *args, **kwargs)(func)
self.add_command(result) self.add_command(result)
return result return result
@ -1528,7 +1557,7 @@ class GroupMixin(Generic[CogT]):
self: GroupMixin[CogT], self: GroupMixin[CogT],
name: str = ..., name: str = ...,
*args: Any, *args: Any,
**kwargs: Any, **kwargs: Unpack[_GroupDecoratorKwargs],
) -> Callable[ ) -> Callable[
[ [
Union[ Union[
@ -1546,7 +1575,7 @@ class GroupMixin(Generic[CogT]):
name: str = ..., name: str = ...,
cls: Type[GroupT] = ..., # type: ignore # previous overload handles case where cls is not set cls: Type[GroupT] = ..., # type: ignore # previous overload handles case where cls is not set
*args: Any, *args: Any,
**kwargs: Any, **kwargs: Unpack[_GroupDecoratorKwargs],
) -> Callable[ ) -> Callable[
[ [
Union[ Union[
@ -1563,7 +1592,7 @@ class GroupMixin(Generic[CogT]):
name: str = MISSING, name: str = MISSING,
cls: Type[Group[Any, ..., Any]] = MISSING, cls: Type[Group[Any, ..., Any]] = MISSING,
*args: Any, *args: Any,
**kwargs: Any, **kwargs: Unpack[_GroupDecoratorKwargs],
) -> Any: ) -> Any:
"""A shortcut decorator that invokes :func:`.group` and adds it to """A shortcut decorator that invokes :func:`.group` and adds it to
the internal command list via :meth:`~.GroupMixin.add_command`. the internal command list via :meth:`~.GroupMixin.add_command`.
@ -1575,7 +1604,7 @@ class GroupMixin(Generic[CogT]):
""" """
def decorator(func): def decorator(func):
kwargs.setdefault('parent', self) kwargs.setdefault('parent', self) # type: ignore # the parent kwarg is not for users to set.
result = group(name=name, cls=cls, *args, **kwargs)(func) result = group(name=name, cls=cls, *args, **kwargs)(func)
self.add_command(result) self.add_command(result)
return result return result
@ -1606,7 +1635,7 @@ class Group(GroupMixin[CogT], Command[CogT, P, T]):
Defaults to ``False``. Defaults to ``False``.
""" """
def __init__(self, *args: Any, **attrs: Any) -> None: def __init__(self, *args: Any, **attrs: Unpack[_GroupKwargs]) -> None:
self.invoke_without_command: bool = attrs.pop('invoke_without_command', False) self.invoke_without_command: bool = attrs.pop('invoke_without_command', False)
super().__init__(*args, **attrs) super().__init__(*args, **attrs)
@ -1728,7 +1757,7 @@ if TYPE_CHECKING:
@overload @overload
def command( def command(
name: str = ..., name: str = ...,
**attrs: Any, **attrs: Unpack[_CommandDecoratorKwargs],
) -> _CommandDecorator: ) -> _CommandDecorator:
... ...
@ -1737,7 +1766,7 @@ def command(
def command( def command(
name: str = ..., name: str = ...,
cls: Type[CommandT] = ..., # type: ignore # previous overload handles case where cls is not set cls: Type[CommandT] = ..., # type: ignore # previous overload handles case where cls is not set
**attrs: Any, **attrs: Unpack[_CommandDecoratorKwargs],
) -> Callable[ ) -> Callable[
[ [
Union[ Union[
@ -1753,7 +1782,7 @@ def command(
def command( def command(
name: str = MISSING, name: str = MISSING,
cls: Type[Command[Any, ..., Any]] = MISSING, cls: Type[Command[Any, ..., Any]] = MISSING,
**attrs: Any, **attrs: Unpack[_CommandDecoratorKwargs],
) -> Any: ) -> Any:
"""A decorator that transforms a function into a :class:`.Command` """A decorator that transforms a function into a :class:`.Command`
or if called with :func:`.group`, :class:`.Group`. or if called with :func:`.group`, :class:`.Group`.
@ -1798,7 +1827,7 @@ def command(
@overload @overload
def group( def group(
name: str = ..., name: str = ...,
**attrs: Any, **attrs: Unpack[_GroupDecoratorKwargs],
) -> _GroupDecorator: ) -> _GroupDecorator:
... ...
@ -1807,7 +1836,7 @@ def group(
def group( def group(
name: str = ..., name: str = ...,
cls: Type[GroupT] = ..., # type: ignore # previous overload handles case where cls is not set cls: Type[GroupT] = ..., # type: ignore # previous overload handles case where cls is not set
**attrs: Any, **attrs: Unpack[_GroupDecoratorKwargs],
) -> Callable[ ) -> Callable[
[ [
Union[ Union[
@ -1823,7 +1852,7 @@ def group(
def group( def group(
name: str = MISSING, name: str = MISSING,
cls: Type[Group[Any, ..., Any]] = MISSING, cls: Type[Group[Any, ..., Any]] = MISSING,
**attrs: Any, **attrs: Unpack[_GroupDecoratorKwargs],
) -> Any: ) -> Any:
"""A decorator that transforms a function into a :class:`.Group`. """A decorator that transforms a function into a :class:`.Group`.
@ -2165,7 +2194,7 @@ def bot_has_any_role(*items: int) -> Callable[[T], T]:
return check(predicate) return check(predicate)
def has_permissions(**perms: bool) -> Check[Any]: def has_permissions(**perms: Unpack[_PermissionsKwargs]) -> Check[Any]:
"""A :func:`.check` that is added that checks if the member has all of """A :func:`.check` that is added that checks if the member has all of
the permissions necessary. the permissions necessary.
@ -2212,7 +2241,7 @@ def has_permissions(**perms: bool) -> Check[Any]:
return check(predicate) return check(predicate)
def bot_has_permissions(**perms: bool) -> Check[Any]: def bot_has_permissions(**perms: Unpack[_PermissionsKwargs]) -> Check[Any]:
"""Similar to :func:`.has_permissions` except checks if the bot itself has """Similar to :func:`.has_permissions` except checks if the bot itself has
the permissions listed. the permissions listed.
@ -2237,7 +2266,7 @@ def bot_has_permissions(**perms: bool) -> Check[Any]:
return check(predicate) return check(predicate)
def has_guild_permissions(**perms: bool) -> Check[Any]: def has_guild_permissions(**perms: Unpack[_PermissionsKwargs]) -> Check[Any]:
"""Similar to :func:`.has_permissions`, but operates on guild wide """Similar to :func:`.has_permissions`, but operates on guild wide
permissions instead of the current channel permissions. permissions instead of the current channel permissions.
@ -2266,7 +2295,7 @@ def has_guild_permissions(**perms: bool) -> Check[Any]:
return check(predicate) return check(predicate)
def bot_has_guild_permissions(**perms: bool) -> Check[Any]: def bot_has_guild_permissions(**perms: Unpack[_PermissionsKwargs]) -> Check[Any]:
"""Similar to :func:`.has_guild_permissions`, but checks the bot """Similar to :func:`.has_guild_permissions`, but checks the bot
members guild permissions. members guild permissions.

51
discord/ext/commands/help.py

@ -42,6 +42,7 @@ from typing import (
Iterable, Iterable,
Sequence, Sequence,
Mapping, Mapping,
TypedDict,
) )
import discord.utils import discord.utils
@ -50,7 +51,7 @@ from .core import Group, Command, get_signature_parameters
from .errors import CommandError from .errors import CommandError
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import Self from typing_extensions import Self, Unpack
import discord.abc import discord.abc
@ -58,6 +59,7 @@ if TYPE_CHECKING:
from .context import Context from .context import Context
from .cog import Cog from .cog import Cog
from .parameters import Parameter from .parameters import Parameter
from .core import _CommandKwargs
from ._types import ( from ._types import (
UserCheck, UserCheck,
@ -65,6 +67,30 @@ if TYPE_CHECKING:
_Bot, _Bot,
) )
class _HelpCommandOptions(TypedDict, total=False):
show_hidden: bool
verify_checks: bool
command_attrs: _CommandKwargs
class _BaseHelpCommandOptions(_HelpCommandOptions, total=False):
sort_commands: bool
dm_help: bool
dm_help_threshold: int
no_category: str
paginator: Paginator
commands_heading: str
class _DefaultHelpCommandOptions(_BaseHelpCommandOptions, total=False):
width: int
indent: int
arguments_heading: str
default_argument_description: str
show_parameter_descriptions: bool
class _MinimalHelpCommandOptions(_BaseHelpCommandOptions, total=False):
aliases_heading: str
__all__ = ( __all__ = (
'Paginator', 'Paginator',
'HelpCommand', 'HelpCommand',
@ -224,7 +250,7 @@ def _not_overridden(f: FuncT) -> FuncT:
class _HelpCommandImpl(Command): class _HelpCommandImpl(Command):
def __init__(self, inject: HelpCommand, *args: Any, **kwargs: Any) -> None: def __init__(self, inject: HelpCommand, *args: Any, **kwargs: Unpack[_CommandKwargs]) -> None:
super().__init__(inject.command_callback, *args, **kwargs) super().__init__(inject.command_callback, *args, **kwargs)
self._original: HelpCommand = inject self._original: HelpCommand = inject
self._injected: HelpCommand = inject self._injected: HelpCommand = inject
@ -299,7 +325,7 @@ class _HelpCommandImpl(Command):
def update(self, **kwargs: Any) -> None: def update(self, **kwargs: Any) -> None:
cog = self.cog cog = self.cog
self.__init__(self._original, **dict(self.__original_kwargs__, **kwargs)) self.__init__(self._original, **dict(self.__original_kwargs__, **kwargs)) # type: ignore
self.cog = cog self.cog = cog
@ -366,10 +392,9 @@ class HelpCommand:
self.__original_args__ = deepcopy(args) self.__original_args__ = deepcopy(args)
return self return self
def __init__(self, **options: Any) -> None: def __init__(self, **options: Unpack[_HelpCommandOptions]) -> None:
self.show_hidden: bool = options.pop('show_hidden', False) self.show_hidden: bool = options.pop('show_hidden', False)
self.verify_checks: bool = options.pop('verify_checks', True) self.verify_checks: bool = options.pop('verify_checks', True)
self.command_attrs: Dict[str, Any]
self.command_attrs = attrs = options.pop('command_attrs', {}) self.command_attrs = attrs = options.pop('command_attrs', {})
attrs.setdefault('name', 'help') attrs.setdefault('name', 'help')
attrs.setdefault('help', 'Shows this message') attrs.setdefault('help', 'Shows this message')
@ -1041,7 +1066,7 @@ class DefaultHelpCommand(HelpCommand):
The paginator used to paginate the help command output. The paginator used to paginate the help command output.
""" """
def __init__(self, **options: Any) -> None: def __init__(self, **options: Unpack[_DefaultHelpCommandOptions]) -> None:
self.width: int = options.pop('width', 80) self.width: int = options.pop('width', 80)
self.indent: int = options.pop('indent', 2) self.indent: int = options.pop('indent', 2)
self.sort_commands: bool = options.pop('sort_commands', True) self.sort_commands: bool = options.pop('sort_commands', True)
@ -1051,11 +1076,13 @@ class DefaultHelpCommand(HelpCommand):
self.commands_heading: str = options.pop('commands_heading', 'Commands:') self.commands_heading: str = options.pop('commands_heading', 'Commands:')
self.default_argument_description: str = options.pop('default_argument_description', 'No description given') self.default_argument_description: str = options.pop('default_argument_description', 'No description given')
self.no_category: str = options.pop('no_category', 'No Category') self.no_category: str = options.pop('no_category', 'No Category')
self.paginator: Paginator = options.pop('paginator', None)
self.show_parameter_descriptions: bool = options.pop('show_parameter_descriptions', True) self.show_parameter_descriptions: bool = options.pop('show_parameter_descriptions', True)
if self.paginator is None: paginator = options.pop('paginator', None)
if paginator is None:
self.paginator: Paginator = Paginator() self.paginator: Paginator = Paginator()
else:
self.paginator: Paginator = paginator
super().__init__(**options) super().__init__(**options)
@ -1334,17 +1361,19 @@ class MinimalHelpCommand(HelpCommand):
The paginator used to paginate the help command output. The paginator used to paginate the help command output.
""" """
def __init__(self, **options: Any) -> None: def __init__(self, **options: Unpack[_MinimalHelpCommandOptions]) -> None:
self.sort_commands: bool = options.pop('sort_commands', True) self.sort_commands: bool = options.pop('sort_commands', True)
self.commands_heading: str = options.pop('commands_heading', 'Commands') self.commands_heading: str = options.pop('commands_heading', 'Commands')
self.dm_help: bool = options.pop('dm_help', False) self.dm_help: bool = options.pop('dm_help', False)
self.dm_help_threshold: int = options.pop('dm_help_threshold', 1000) self.dm_help_threshold: int = options.pop('dm_help_threshold', 1000)
self.aliases_heading: str = options.pop('aliases_heading', 'Aliases:') self.aliases_heading: str = options.pop('aliases_heading', 'Aliases:')
self.no_category: str = options.pop('no_category', 'No Category') self.no_category: str = options.pop('no_category', 'No Category')
self.paginator: Paginator = options.pop('paginator', None)
if self.paginator is None: paginator = options.pop('paginator', None)
if paginator is None:
self.paginator: Paginator = Paginator(suffix=None, prefix=None) self.paginator: Paginator = Paginator(suffix=None, prefix=None)
else:
self.paginator: Paginator = paginator
super().__init__(**options) super().__init__(**options)

63
discord/ext/commands/hybrid.py

@ -24,19 +24,7 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
from typing import ( from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, List, Tuple, Type, TypeVar, Union, Optional
TYPE_CHECKING,
Any,
Callable,
ClassVar,
Dict,
List,
Tuple,
Type,
TypeVar,
Union,
Optional,
)
import discord import discord
import inspect import inspect
@ -51,7 +39,7 @@ from .cog import Cog
from .view import StringView from .view import StringView
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import Self, ParamSpec, Concatenate from typing_extensions import Self, ParamSpec, Concatenate, Unpack
from ._types import ContextT, Coro, BotT from ._types import ContextT, Coro, BotT
from .bot import Bot from .bot import Bot
from .context import Context from .context import Context
@ -60,6 +48,29 @@ if TYPE_CHECKING:
AutocompleteCallback, AutocompleteCallback,
ChoiceT, ChoiceT,
) )
from .core import _CommandKwargs
class _HybridCommandKwargs(_CommandKwargs, total=False):
guild_ids: list[int]
guild_only: bool
default_permissions: bool
nsfw: bool
with_app_command: bool
class _HybridCommandDecoratorKwargs(_HybridCommandKwargs, total=False):
description: Union[str, app_commands.locale_str]
class _HybridGroupKwargs(_HybridCommandDecoratorKwargs, total=False):
with_app_command: bool
guild_ids: list[int]
guild_only: bool
default_permissions: bool
nsfw: bool
description: str
class _HybridGroupDecoratorKwargs(_HybridGroupKwargs, total=False):
description: Union[str, app_commands.locale_str]
fallback: Union[str, app_commands.locale_str]
__all__ = ( __all__ = (
@ -501,7 +512,7 @@ class HybridCommand(Command[CogT, P, T]):
*, *,
name: Union[str, app_commands.locale_str] = MISSING, name: Union[str, app_commands.locale_str] = MISSING,
description: Union[str, app_commands.locale_str] = MISSING, description: Union[str, app_commands.locale_str] = MISSING,
**kwargs: Any, **kwargs: Unpack[_HybridCommandKwargs], # type: ignore # name, description
) -> None: ) -> None:
name, name_locale = (name.message, name) if isinstance(name, app_commands.locale_str) else (name, None) name, name_locale = (name.message, name) if isinstance(name, app_commands.locale_str) else (name, None)
if name is not MISSING: if name is not MISSING:
@ -621,7 +632,7 @@ class HybridGroup(Group[CogT, P, T]):
name: Union[str, app_commands.locale_str] = MISSING, name: Union[str, app_commands.locale_str] = MISSING,
description: Union[str, app_commands.locale_str] = MISSING, description: Union[str, app_commands.locale_str] = MISSING,
fallback: Optional[Union[str, app_commands.locale_str]] = None, fallback: Optional[Union[str, app_commands.locale_str]] = None,
**attrs: Any, **attrs: Unpack[_HybridGroupKwargs], # type: ignore # name, description
) -> None: ) -> None:
name, name_locale = (name.message, name) if isinstance(name, app_commands.locale_str) else (name, None) name, name_locale = (name.message, name) if isinstance(name, app_commands.locale_str) else (name, None)
if name is not MISSING: if name is not MISSING:
@ -825,7 +836,7 @@ class HybridGroup(Group[CogT, P, T]):
name: Union[str, app_commands.locale_str] = MISSING, name: Union[str, app_commands.locale_str] = MISSING,
*args: Any, *args: Any,
with_app_command: bool = True, with_app_command: bool = True,
**kwargs: Any, **kwargs: Unpack[_HybridCommandDecoratorKwargs], # type: ignore # name, with_app_command
) -> Callable[[CommandCallback[CogT, ContextT, P2, U]], HybridCommand[CogT, P2, U]]: ) -> Callable[[CommandCallback[CogT, ContextT, P2, U]], HybridCommand[CogT, P2, U]]:
"""A shortcut decorator that invokes :func:`~discord.ext.commands.hybrid_command` and adds it to """A shortcut decorator that invokes :func:`~discord.ext.commands.hybrid_command` and adds it to
the internal command list via :meth:`add_command`. the internal command list via :meth:`add_command`.
@ -837,8 +848,8 @@ class HybridGroup(Group[CogT, P, T]):
""" """
def decorator(func: CommandCallback[CogT, ContextT, P2, U]): def decorator(func: CommandCallback[CogT, ContextT, P2, U]):
kwargs.setdefault('parent', self) kwargs.setdefault('parent', self) # type: ignore # parent is not for users to set
result = hybrid_command(name=name, *args, with_app_command=with_app_command, **kwargs)(func) result = hybrid_command(name=name, *args, with_app_command=with_app_command, **kwargs)(func) # type: ignore # name, with_app_command
self.add_command(result) self.add_command(result)
return result return result
@ -849,7 +860,7 @@ class HybridGroup(Group[CogT, P, T]):
name: Union[str, app_commands.locale_str] = MISSING, name: Union[str, app_commands.locale_str] = MISSING,
*args: Any, *args: Any,
with_app_command: bool = True, with_app_command: bool = True,
**kwargs: Any, **kwargs: Unpack[_HybridGroupDecoratorKwargs], # type: ignore # name, with_app_command
) -> Callable[[CommandCallback[CogT, ContextT, P2, U]], HybridGroup[CogT, P2, U]]: ) -> Callable[[CommandCallback[CogT, ContextT, P2, U]], HybridGroup[CogT, P2, U]]:
"""A shortcut decorator that invokes :func:`~discord.ext.commands.hybrid_group` and adds it to """A shortcut decorator that invokes :func:`~discord.ext.commands.hybrid_group` and adds it to
the internal command list via :meth:`~.GroupMixin.add_command`. the internal command list via :meth:`~.GroupMixin.add_command`.
@ -861,8 +872,8 @@ class HybridGroup(Group[CogT, P, T]):
""" """
def decorator(func: CommandCallback[CogT, ContextT, P2, U]): def decorator(func: CommandCallback[CogT, ContextT, P2, U]):
kwargs.setdefault('parent', self) kwargs.setdefault('parent', self) # type: ignore # parent is not for users to set
result = hybrid_group(name=name, *args, with_app_command=with_app_command, **kwargs)(func) result = hybrid_group(name=name, *args, with_app_command=with_app_command, **kwargs)(func) # type: ignore # name, with_app_command
self.add_command(result) self.add_command(result)
return result return result
@ -873,7 +884,7 @@ def hybrid_command(
name: Union[str, app_commands.locale_str] = MISSING, name: Union[str, app_commands.locale_str] = MISSING,
*, *,
with_app_command: bool = True, with_app_command: bool = True,
**attrs: Any, **attrs: Unpack[_HybridCommandDecoratorKwargs], # type: ignore # name, with_app_command
) -> Callable[[CommandCallback[CogT, ContextT, P, T]], HybridCommand[CogT, P, T]]: ) -> Callable[[CommandCallback[CogT, ContextT, P, T]], HybridCommand[CogT, P, T]]:
r"""A decorator that transforms a function into a :class:`.HybridCommand`. r"""A decorator that transforms a function into a :class:`.HybridCommand`.
@ -916,7 +927,7 @@ def hybrid_command(
if isinstance(func, Command): if isinstance(func, Command):
raise TypeError('Callback is already a command.') raise TypeError('Callback is already a command.')
# Pyright does not allow Command[Any] to be assigned to Command[CogT] despite it being okay here # Pyright does not allow Command[Any] to be assigned to Command[CogT] despite it being okay here
return HybridCommand(func, name=name, with_app_command=with_app_command, **attrs) # type: ignore return HybridCommand(func, name=name, with_app_command=with_app_command, **attrs) # type: ignore # name, with_app_command
return decorator return decorator
@ -925,7 +936,7 @@ def hybrid_group(
name: Union[str, app_commands.locale_str] = MISSING, name: Union[str, app_commands.locale_str] = MISSING,
*, *,
with_app_command: bool = True, with_app_command: bool = True,
**attrs: Any, **attrs: Unpack[_HybridGroupDecoratorKwargs], # type: ignore # name, with_app_command
) -> Callable[[CommandCallback[CogT, ContextT, P, T]], HybridGroup[CogT, P, T]]: ) -> Callable[[CommandCallback[CogT, ContextT, P, T]], HybridGroup[CogT, P, T]]:
"""A decorator that transforms a function into a :class:`.HybridGroup`. """A decorator that transforms a function into a :class:`.HybridGroup`.
@ -949,6 +960,6 @@ def hybrid_group(
def decorator(func: CommandCallback[CogT, ContextT, P, T]) -> HybridGroup[CogT, P, T]: def decorator(func: CommandCallback[CogT, ContextT, P, T]) -> HybridGroup[CogT, P, T]:
if isinstance(func, Command): if isinstance(func, Command):
raise TypeError('Callback is already a command.') raise TypeError('Callback is already a command.')
return HybridGroup(func, name=name, with_app_command=with_app_command, **attrs) return HybridGroup(func, name=name, with_app_command=with_app_command, **attrs) # type: ignore # name, with_app_command
return decorator return decorator

46
discord/flags.py

@ -40,12 +40,48 @@ from typing import (
Type, Type,
TypeVar, TypeVar,
overload, overload,
TypedDict,
) )
from .enums import UserFlags from .enums import UserFlags
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import Self from typing_extensions import Self, Unpack
class _IntentsFlagsKwargs(TypedDict, total=False):
guilds: bool
members: bool
moderation: bool
bans: bool
emojis: bool
emojis_and_stickers: bool
expressions: bool
integrations: bool
webhooks: bool
invites: bool
voice_states: bool
presences: bool
messages: bool
guild_messages: bool
dm_messages: bool
reactions: bool
guild_reactions: bool
dm_reactions: bool
typing: bool
guild_typing: bool
dm_typing: bool
message_content: bool
guild_scheduled_events: bool
auto_moderation: bool
auto_moderation_configuration: bool
auto_moderation_execution: bool
polls: bool
guild_polls: bool
dm_polls: bool
class _MemberCacheFlagsKwargs(TypedDict, total=False):
voice: bool
joined: bool
__all__ = ( __all__ = (
@ -754,12 +790,12 @@ class Intents(BaseFlags):
__slots__ = () __slots__ = ()
def __init__(self, value: int = 0, **kwargs: bool) -> None: def __init__(self, value: int = 0, **kwargs: Unpack[_IntentsFlagsKwargs]) -> None:
self.value: int = value self.value: int = value
for key, value in kwargs.items(): for key, kwvalue in kwargs.items():
if key not in self.VALID_FLAGS: if key not in self.VALID_FLAGS:
raise TypeError(f'{key!r} is not a valid flag name.') raise TypeError(f'{key!r} is not a valid flag name.')
setattr(self, key, value) setattr(self, key, kwvalue)
@classmethod @classmethod
def all(cls: Type[Intents]) -> Intents: def all(cls: Type[Intents]) -> Intents:
@ -1415,7 +1451,7 @@ class MemberCacheFlags(BaseFlags):
__slots__ = () __slots__ = ()
def __init__(self, **kwargs: bool): def __init__(self, **kwargs: Unpack[_MemberCacheFlagsKwargs]) -> None:
bits = max(self.VALID_FLAGS.values()).bit_length() bits = max(self.VALID_FLAGS.values()).bit_length()
self.value: int = (1 << bits) - 1 self.value: int = (1 << bits) - 1
for key, value in kwargs.items(): for key, value in kwargs.items():

84
discord/permissions.py

@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
from typing import Callable, Any, ClassVar, Dict, Iterator, Set, TYPE_CHECKING, Tuple, Optional from typing import Callable, Any, ClassVar, Dict, Iterator, Set, TYPE_CHECKING, Tuple, Optional, TypedDict, Generic, TypeVar
from .flags import BaseFlags, flag_value, fill_with_flags, alias_flag_value from .flags import BaseFlags, flag_value, fill_with_flags, alias_flag_value
__all__ = ( __all__ = (
@ -33,7 +33,73 @@ __all__ = (
) )
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import Self from typing_extensions import Self, Unpack
BoolOrNoneT = TypeVar('BoolOrNoneT', bound=Optional[bool])
class _BasePermissionsKwargs(Generic[BoolOrNoneT], TypedDict, total=False):
create_instant_invite: BoolOrNoneT
kick_members: BoolOrNoneT
ban_members: BoolOrNoneT
administrator: BoolOrNoneT
manage_channels: BoolOrNoneT
manage_guild: BoolOrNoneT
add_reactions: BoolOrNoneT
view_audit_log: BoolOrNoneT
priority_speaker: BoolOrNoneT
stream: BoolOrNoneT
read_messages: BoolOrNoneT
view_channel: BoolOrNoneT
send_messages: BoolOrNoneT
send_tts_messages: BoolOrNoneT
manage_messages: BoolOrNoneT
embed_links: BoolOrNoneT
attach_files: BoolOrNoneT
read_message_history: BoolOrNoneT
mention_everyone: BoolOrNoneT
external_emojis: BoolOrNoneT
use_external_emojis: BoolOrNoneT
view_guild_insights: BoolOrNoneT
connect: BoolOrNoneT
speak: BoolOrNoneT
mute_members: BoolOrNoneT
deafen_members: BoolOrNoneT
move_members: BoolOrNoneT
use_voice_activation: BoolOrNoneT
change_nickname: BoolOrNoneT
manage_nicknames: BoolOrNoneT
manage_roles: BoolOrNoneT
manage_permissions: BoolOrNoneT
manage_webhooks: BoolOrNoneT
manage_expressions: BoolOrNoneT
manage_emojis: BoolOrNoneT
manage_emojis_and_stickers: BoolOrNoneT
use_application_commands: BoolOrNoneT
request_to_speak: BoolOrNoneT
manage_events: BoolOrNoneT
manage_threads: BoolOrNoneT
create_public_threads: BoolOrNoneT
create_private_threads: BoolOrNoneT
send_messages_in_threads: BoolOrNoneT
external_stickers: BoolOrNoneT
use_external_stickers: BoolOrNoneT
use_embedded_activities: BoolOrNoneT
moderate_members: BoolOrNoneT
use_soundboard: BoolOrNoneT
use_external_sounds: BoolOrNoneT
send_voice_messages: BoolOrNoneT
create_expressions: BoolOrNoneT
create_events: BoolOrNoneT
send_polls: BoolOrNoneT
create_polls: BoolOrNoneT
use_external_apps: BoolOrNoneT
class _PermissionsKwargs(_BasePermissionsKwargs[bool]):
...
class _PermissionOverwriteKwargs(_BasePermissionsKwargs[Optional[bool]]):
...
# A permission alias works like a regular flag but is marked # A permission alias works like a regular flag but is marked
# So the PermissionOverwrite knows to work with it # So the PermissionOverwrite knows to work with it
@ -135,18 +201,18 @@ class Permissions(BaseFlags):
__slots__ = () __slots__ = ()
def __init__(self, permissions: int = 0, **kwargs: bool): def __init__(self, permissions: int = 0, **kwargs: Unpack[_PermissionsKwargs]):
if not isinstance(permissions, int): if not isinstance(permissions, int):
raise TypeError(f'Expected int parameter, received {permissions.__class__.__name__} instead.') raise TypeError(f'Expected int parameter, received {permissions.__class__.__name__} instead.')
self.value = permissions self.value = permissions
for key, value in kwargs.items(): for key, kwvalue in kwargs.items():
try: try:
flag = self.VALID_FLAGS[key] flag = self.VALID_FLAGS[key]
except KeyError: except KeyError:
raise TypeError(f'{key!r} is not a valid permission name.') from None raise TypeError(f'{key!r} is not a valid permission name.') from None
else: else:
self._set_flag(flag, value) self._set_flag(flag, kwvalue) # type: ignore
def is_subset(self, other: Permissions) -> bool: def is_subset(self, other: Permissions) -> bool:
"""Returns ``True`` if self has the same or fewer permissions as other.""" """Returns ``True`` if self has the same or fewer permissions as other."""
@ -381,7 +447,7 @@ class Permissions(BaseFlags):
""" """
return cls(1 << 3) return cls(1 << 3)
def update(self, **kwargs: bool) -> None: def update(self, **kwargs: Unpack[_PermissionsKwargs]) -> None:
r"""Bulk updates this permission object. r"""Bulk updates this permission object.
Allows you to set multiple attributes by using keyword Allows you to set multiple attributes by using keyword
@ -396,7 +462,7 @@ class Permissions(BaseFlags):
for key, value in kwargs.items(): for key, value in kwargs.items():
flag = self.VALID_FLAGS.get(key) flag = self.VALID_FLAGS.get(key)
if flag is not None: if flag is not None:
self._set_flag(flag, value) self._set_flag(flag, value) # type: ignore
def handle_overwrite(self, allow: int, deny: int) -> None: def handle_overwrite(self, allow: int, deny: int) -> None:
# Basically this is what's happening here. # Basically this is what's happening here.
@ -908,7 +974,7 @@ class PermissionOverwrite:
create_polls: Optional[bool] create_polls: Optional[bool]
use_external_apps: Optional[bool] use_external_apps: Optional[bool]
def __init__(self, **kwargs: Optional[bool]): def __init__(self, **kwargs: Unpack[_PermissionOverwriteKwargs]) -> None:
self._values: Dict[str, Optional[bool]] = {} self._values: Dict[str, Optional[bool]] = {}
for key, value in kwargs.items(): for key, value in kwargs.items():
@ -970,7 +1036,7 @@ class PermissionOverwrite:
""" """
return len(self._values) == 0 return len(self._values) == 0
def update(self, **kwargs: Optional[bool]) -> None: def update(self, **kwargs: Unpack[_PermissionOverwriteKwargs]) -> None:
r"""Bulk updates this permission overwrite object. r"""Bulk updates this permission overwrite object.
Allows you to set multiple attributes by using keyword Allows you to set multiple attributes by using keyword

8
discord/shard.py

@ -52,6 +52,12 @@ if TYPE_CHECKING:
from .activity import BaseActivity from .activity import BaseActivity
from .flags import Intents from .flags import Intents
from .types.gateway import SessionStartLimit from .types.gateway import SessionStartLimit
from .client import _ClientOptions
class _AutoShardedClientOptions(_ClientOptions, total=False):
shard_ids: List[int]
shard_connect_timeout: Optional[float]
__all__ = ( __all__ = (
'AutoShardedClient', 'AutoShardedClient',
@ -365,7 +371,7 @@ class AutoShardedClient(Client):
if TYPE_CHECKING: if TYPE_CHECKING:
_connection: AutoShardedConnectionState _connection: AutoShardedConnectionState
def __init__(self, *args: Any, intents: Intents, **kwargs: Any) -> None: def __init__(self, *args: Any, intents: Intents, **kwargs: Unpack[_AutoShardedClientOptions]) -> None:
kwargs.pop('shard_id', None) kwargs.pop('shard_id', None)
self.shard_ids: Optional[List[int]] = kwargs.pop('shard_ids', None) self.shard_ids: Optional[List[int]] = kwargs.pop('shard_ids', None)
self.shard_connect_timeout: Optional[float] = kwargs.pop('shard_connect_timeout', 180.0) self.shard_connect_timeout: Optional[float] = kwargs.pop('shard_connect_timeout', 180.0)

Loading…
Cancel
Save