diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 034e3fba66..8ce8ea467a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ 3.8 ] + python-version: [ '3.10' ] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -17,7 +17,7 @@ jobs: - name: Install dependencies run: | python -m pip install -U pip - pip install -U sphinx sphinxcontrib-trio aiohttp sphinxcontrib-websupport myst-parser + pip install -U sphinx sphinxcontrib-trio aiohttp sphinxcontrib-websupport myst-parser typing-extensions - name: Compile to html run: | cd docs diff --git a/discord/asset.py b/discord/asset.py index 5b025ded5a..035814c405 100644 --- a/discord/asset.py +++ b/discord/asset.py @@ -268,11 +268,11 @@ def __str__(self) -> str: def __len__(self) -> int: return len(self._url) - def __repr__(self): + def __repr__(self) -> str: shorten = self._url.replace(self.BASE, '') return f'' - def __eq__(self, other): + def __eq__(self, other) -> bool: return isinstance(other, Asset) and self._url == other._url def __hash__(self): diff --git a/discord/bot.py b/discord/bot.py index 275ba9417a..8cd05cefe3 100644 --- a/discord/bot.py +++ b/discord/bot.py @@ -40,6 +40,7 @@ Type, TypeVar, Union, + Type, ) from .client import Client diff --git a/discord/commands/_types.py b/discord/commands/_types.py new file mode 100644 index 0000000000..8fb0803232 --- /dev/null +++ b/discord/commands/_types.py @@ -0,0 +1,16 @@ +from typing import Callable, TYPE_CHECKING, Union, Coroutine, Any, TypeVar + +if TYPE_CHECKING: + from .. import Cog, ApplicationContext + +T = TypeVar('T') + +Coro = Coroutine[Any, Any, T] +MaybeCoro = Union[T, Coro[T]] +CoroFunc = Callable[..., Coro[Any]] + +Check = Union[Callable[["Cog", "ApplicationContext[Any]"], MaybeCoro[bool]], + Callable[["ApplicationContext[Any]"], MaybeCoro[bool]]] +Hook = Union[Callable[["Cog", "ApplicationContext[Any]"], Coro[Any]], Callable[["ApplicationContext[Any]"], Coro[Any]]] +Error = Union[Callable[["Cog", "ApplicationContext[Any]", "CommandError"], Coro[Any]], + Callable[["ApplicationContext[Any]", "CommandError"], Coro[Any]]] diff --git a/discord/commands/context.py b/discord/commands/context.py index 2d057ab87b..5e0ea30a06 100644 --- a/discord/commands/context.py +++ b/discord/commands/context.py @@ -24,21 +24,26 @@ """ from __future__ import annotations -from typing import TYPE_CHECKING, Optional, TypeVar, Union +from typing import TYPE_CHECKING, Optional, Union, TypeVar, Generic, Callable, List, Any, Dict -import discord.abc +import discord.utils if TYPE_CHECKING: - from typing_extensions import ParamSpec - - import discord - from discord import Bot - from discord.state import ConnectionState - - from .core import ApplicationCommand, Option + from . import ApplicationCommand, Option from ..cog import Cog - from ..webhook import WebhookMessage - from typing import Callable + from ..embeds import Embed + from ..file import File + from ..guild import Guild + from ..interactions import Interaction, InteractionChannel, InteractionResponse, InteractionMessage + from ..member import Member + from ..mentions import AllowedMentions + from ..message import Message + from ..state import ConnectionState + from ..user import User + from ..ui import View + from ..voice_client import VoiceProtocol + from ..webhook import Webhook, WebhookMessage + from typing_extensions import ParamSpec from ..guild import Guild from ..interactions import Interaction, InteractionResponse @@ -58,7 +63,18 @@ __all__ = ("ApplicationContext", "AutocompleteContext") -class ApplicationContext(discord.abc.Messageable): +MISSING: Any = discord.utils.MISSING + +T = TypeVar("T") +BotT = TypeVar("BotT", bound="Union[discord.Bot, discord.AutoShardedBot]") +CogT = TypeVar("CogT", bound="Cog") + +if TYPE_CHECKING: + P = ParamSpec('P') +else: + P = TypeVar('P') + +class ApplicationContext(discord.abc.Messageable, Generic[BotT]): """Represents a Discord application command interaction context. This class is not created manually and is instead passed to application @@ -76,9 +92,9 @@ class ApplicationContext(discord.abc.Messageable): The command that this context belongs to. """ - def __init__(self, bot: Bot, interaction: Interaction): - self.bot = bot - self.interaction = interaction + def __init__(self, bot: BotT, interaction: Interaction) -> None: + self.bot: BotT = bot + self.interaction: Interaction = interaction # below attributes will be set after initialization self.command: ApplicationCommand = None # type: ignore @@ -88,7 +104,7 @@ def __init__(self, bot: Bot, interaction: Interaction): self._state: ConnectionState = self.interaction._state - async def _get_channel(self) -> discord.abc.Messageable: + async def _get_channel(self) -> Optional[InteractionChannel]: return self.channel async def invoke(self, command: ApplicationCommand[CogT, P, T], /, *args: P.args, **kwargs: P.kwargs) -> T: @@ -118,7 +134,7 @@ async def invoke(self, command: ApplicationCommand[CogT, P, T], /, *args: P.args return await command(self, *args, **kwargs) @cached_property - def channel(self): + def channel(self) -> Optional[InteractionChannel]: return self.interaction.channel @cached_property @@ -133,14 +149,6 @@ def guild(self) -> Optional[Guild]: def guild_id(self) -> Optional[int]: return self.interaction.guild_id - @cached_property - def locale(self) -> Optional[str]: - return self.interaction.locale - - @cached_property - def guild_locale(self) -> Optional[str]: - return self.interaction.guild_locale - @cached_property def me(self) -> Union[Member, User]: return self.guild.me if self.guild is not None else self.bot.user @@ -168,6 +176,14 @@ def voice_client(self): def response(self) -> InteractionResponse: return self.interaction.response + @property + def cog(self) -> Optional[Cog]: + """Optional[:class:`.Cog`]: Returns the cog associated with this context's command. ``None`` if it does not exist.""" + if self.command is None: + return None + + return self.command.cog + @property def respond(self) -> Callable[..., Union[Interaction, WebhookMessage]]: """Callable[..., Union[:class:`~.Interaction`, :class:`~.Webhook`]]: Sends either a response @@ -195,15 +211,15 @@ def send_followup(self): f"Interaction was not yet issued a response. Try using {type(self).__name__}.respond() first." ) - @property - def defer(self): - return self.interaction.response.defer + @discord.utils.copy_doc(InteractionResponse.defer) + async def defer(self, *, ephemeral: bool = False) -> None: + return await self.interaction.response.defer(ephemeral=ephemeral) @property - def followup(self): + def followup(self) -> Webhook: return self.interaction.followup - async def delete(self): + async def delete(self) -> None: """Calls :attr:`~discord.commands.ApplicationContext.respond`. If the response is done, then calls :attr:`~discord.commands.ApplicationContext.respond` first.""" if not self.response.is_done(): @@ -211,17 +227,26 @@ async def delete(self): return await self.interaction.delete_original_message() - @property - def edit(self): - return self.interaction.edit_original_message - - @property - def cog(self) -> Optional[Cog]: - """Optional[:class:`.Cog`]: Returns the cog associated with this context's command. ``None`` if it does not exist.""" - if self.command is None: - return None - - return self.command.cog + async def edit( + self, + *, + content: Optional[str] = MISSING, + embeds: List[Embed] = MISSING, + embed: Optional[Embed] = MISSING, + file: File = MISSING, + files: List[File] = MISSING, + view: Optional[View] = MISSING, + allowed_mentions: Optional[AllowedMentions] = None, + ) -> InteractionMessage: + return await self.interaction.edit_original_message( + content=content, + embeds=embeds, + embed=embed, + file=file, + files=files, + view=view, + allowed_mentions=allowed_mentions, + ) class AutocompleteContext: @@ -248,18 +273,24 @@ class AutocompleteContext: """ __slots__ = ("bot", "interaction", "command", "focused", "value", "options") - - def __init__(self, bot: Bot, interaction: Interaction) -> None: - self.bot = bot - self.interaction = interaction - - self.command: ApplicationCommand = None # type: ignore - self.focused: Option = None # type: ignore - self.value: str = None # type: ignore - self.options: dict = None # type: ignore + + def __init__( + self, + interaction: Interaction, + *, + command: ApplicationCommand, + focused: Option, + value: str, + options: Dict[str, Any], + ) -> None: + self.interaction: Interaction = interaction + self.command: ApplicationCommand = command + self.focused: Option = focused + self.value: str = value + self.options: Dict[str, Any] = options @property - def cog(self) -> Optional[Cog]: + def cog(self) -> Optional[CogT]: """Optional[:class:`.Cog`]: Returns the cog associated with this context's command. ``None`` if it does not exist.""" if self.command is None: return None diff --git a/discord/commands/core.py b/discord/commands/core.py index a029301d06..59bcae4062 100644 --- a/discord/commands/core.py +++ b/discord/commands/core.py @@ -32,8 +32,25 @@ import re import types from collections import OrderedDict -from typing import Any, Callable, Dict, Generator, Generic, List, Optional, Type, TypeVar, Union, TYPE_CHECKING +from typing_extensions import ParamSpec +from typing import ( + Any, + Callable, + Dict, + List, + Optional, + Union, + TYPE_CHECKING, + Awaitable, + overload, + TypeVar, + Generic, + Type, + Concatenate, + Generator, +) +from ..enums import SlashCommandOptionType, ChannelType from .context import ApplicationContext, AutocompleteContext from .errors import ApplicationCommandError, CheckFailure, ApplicationCommandInvokeError from .options import Option, OptionChoice @@ -41,9 +58,34 @@ from ..enums import SlashCommandOptionType, ChannelType from ..errors import ValidationError, ClientException from ..member import Member -from ..message import Message from ..user import User -from ..utils import find, get_or_fetch, async_all, utcnow +from ..message import Message +from ..utils import find, async_all, get_or_fetch, utcnow +from ..errors import ValidationError, ClientException +from .context import AutocompleteContext +from .errors import ApplicationCommandError, CheckFailure, ApplicationCommandInvokeError +from .permissions import CommandPermission + +if TYPE_CHECKING: + from ..types.interactions import ( + CreateApplicationCommand, + ApplicationCommandOption as ApplicationCommandOptionData, + ApplicationCommandOptionChoice + ) + from ._types import ( + Coro, + CoroFunc, + Hook, + Error + ) + from .context import ApplicationContext + from ..cog import Cog + from .permissions import Permission + from ..interactions import Interaction + + P = ParamSpec('P') +else: + P = TypeVar('P') __all__ = ( "_BaseCommand", @@ -60,20 +102,15 @@ "MessageCommand", ) -if TYPE_CHECKING: - from typing_extensions import ParamSpec - - from ..cog import Cog - T = TypeVar('T') -CogT = TypeVar('CogT', bound='Cog') +CogT = TypeVar("CogT", bound="Cog") +ApplicationCommandT = TypeVar("ApplicationCommandT", bound="ApplicationCommand") +ApplicationContextT = TypeVar("ApplicationContextT", bound="ApplicationContext") +HookT = TypeVar("HookT", bound="Hook") +ErrorT = TypeVar('ErrorT', bound='Error') -if TYPE_CHECKING: - P = ParamSpec('P') -else: - P = TypeVar('P') +def wrap_callback(coro): # TODO: Maybe typehint -def wrap_callback(coro): @functools.wraps(coro) async def wrapped(*args, **kwargs): try: @@ -85,9 +122,12 @@ async def wrapped(*args, **kwargs): except Exception as exc: raise ApplicationCommandInvokeError(exc) from exc return ret + return wrapped -def hooked_wrapped_callback(command, ctx, coro): + +def hooked_wrapped_callback(command: ApplicationCommandT, ctx: ApplicationContextT, coro): # TODO: Maybe Typehint coro & return type + @functools.wraps(coro) async def wrapped(arg): try: @@ -103,13 +143,26 @@ async def wrapped(arg): await command._max_concurrency.release(ctx) await command.call_after_hooks(ctx) return ret + return wrapped + class _BaseCommand: __slots__ = () +# finished class ApplicationCommand(_BaseCommand, Generic[CogT, P, T]): - cog = None + __original_kwargs__: Dict[str, Any] + cog: Optional[Cog] = None + name: str + description: str + callback: HookT + checks: List[Callable[[ApplicationContext], Awaitable[bool]]] + on_error: Callable[[ApplicationContext, Exception], Awaitable[None]] + _before_invoke: Optional[Callable[[ApplicationContext], Awaitable[None]]] + _after_invoke: Optional[Callable[[ApplicationContext], Awaitable[None]]] + default_permission: bool + permissions: List[Permission] def __init__(self, func: Callable, **kwargs) -> None: from ..ext.commands.cooldowns import CooldownMapping, BucketType, MaxConcurrency @@ -134,10 +187,10 @@ def __init__(self, func: Callable, **kwargs) -> None: self._max_concurrency: Optional[MaxConcurrency] = max_concurrency - def __repr__(self): + def __repr__(self) -> str: return f"" - def __eq__(self, other) -> bool: + def __eq__(self, other: Any) -> bool: if hasattr(self, "id") and hasattr(other, "id"): check = self.id == other.id else: @@ -151,7 +204,7 @@ def __eq__(self, other) -> bool: and check ) - async def __call__(self, ctx, *args, **kwargs): + async def __call__(self, ctx: ApplicationContext, *args: P.args, **kwargs: P.kwargs) -> T: """|coro| Calls the command's callback. @@ -159,7 +212,7 @@ async def __call__(self, ctx, *args, **kwargs): convert the arguments beforehand, so take care to pass the correct arguments in. """ - return await self.callback(ctx, *args, **kwargs) + await self.callback(ctx, *args, **kwargs) def _prepare_cooldowns(self, ctx: ApplicationContext): if self._buckets.valid: @@ -295,10 +348,10 @@ async def dispatch_error(self, ctx: ApplicationContext, error: Exception) -> Non finally: ctx.bot.dispatch('application_command_error', ctx, error) - def _get_signature_parameters(self): + def _get_signature_parameters(self) -> OrderedDict[str, inspect.Parameter]: return OrderedDict(inspect.signature(self.callback).parameters) - def error(self, coro): + def error(self, coro: ErrorT) -> ErrorT: """A decorator that registers a coroutine as a local error handler. A local error handler is an :func:`.on_command_error` event limited to @@ -327,7 +380,7 @@ def has_error_handler(self) -> bool: """ return hasattr(self, 'on_error') - def before_invoke(self, coro): + def before_invoke(self, coro: HookT) -> HookT: """A decorator that registers a coroutine as a pre-invoke hook. A pre-invoke hook is called directly before the command is called. This makes it a useful function to set up database @@ -349,7 +402,7 @@ def before_invoke(self, coro): self._before_invoke = coro return coro - def after_invoke(self, coro): + def after_invoke(self, coro: HookT) -> HookT: """A decorator that registers a coroutine as a post-invoke hook. A post-invoke hook is called directly after the command is called. This makes it a useful function to clean-up database @@ -453,6 +506,7 @@ def qualified_name(self) -> str: def _set_cog(self, cog): self.cog = cog + class SlashCommand(ApplicationCommand): r"""A class that implements the protocol for a slash command. @@ -498,16 +552,39 @@ class SlashCommand(ApplicationCommand): .. versionadded:: 2.0 """ - type = 1 + type: int = 1 - def __new__(cls, *args, **kwargs) -> SlashCommand: + def __new__(cls: Type[ApplicationCommandT], *args: Any, **kwargs: Any) -> ApplicationCommandT: self = super().__new__(cls) self.__original_kwargs__ = kwargs.copy() return self - def __init__(self, func: Callable, *args, **kwargs) -> None: + @overload + def __init__( + self, + func: Union[ + Callable[Concatenate[CogT, ApplicationContextT, P], Coro[T]], + Callable[Concatenate[ApplicationContextT, P], Coro[T]] + ], + *, + name: Optional[str] = None, + description: Optional[str] = None, + guild_ids: Optional[List[int]] = None, + parent: Optional[SlashCommandGroup] = None, + checks: Optional[List[Callable[[ApplicationContext], Awaitable[bool]]]] = None, + default_permission: bool = None, + permissions: Optional[List[Permission]] = None, + ) -> None: + ... + + def __init__( + self, + func: Callable[[ApplicationContext, ...], Awaitable[None]], + **kwargs: Any + ) -> None: super().__init__(func, **kwargs) + if not asyncio.iscoroutinefunction(func): raise TypeError("Callback must be a coroutine.") self.callback = func @@ -517,7 +594,7 @@ def __init__(self, func: Callable, *args, **kwargs) -> None: name = kwargs.get("name") or func.__name__ validate_chat_input_name(name) self.name: str = name - self.id = None + self.id = None # TODO: typehint usage? description = kwargs.get("description") or ( inspect.cleandoc(func.__doc__).splitlines()[0] @@ -526,10 +603,10 @@ def __init__(self, func: Callable, *args, **kwargs) -> None: ) validate_chat_input_description(description) self.description: str = description - self.parent = kwargs.get('parent') + self.parent: SlashCommandGroup = kwargs.get('parent') self.attached_to_group: bool = False - self.cog = None + self.cog: Optional[Cog] = None params = self._get_signature_parameters() if (kwop := kwargs.get('options', None)): @@ -554,10 +631,7 @@ def __init__(self, func: Callable, *args, **kwargs) -> None: if self.permissions and self.default_permission: self.default_permission = False - - def _parse_options(self, params) -> List[Option]: - final_options = [] - + def _parse_options(self, params: OrderedDict[str, inspect.Parameter]) -> List[Option]: if list(params.items())[0][0] == "self": temp = list(params.items()) temp.pop(0) @@ -611,7 +685,6 @@ def _parse_options(self, params) -> List[Option]: return final_options - def _match_option_param_names(self, params, options): if list(params.items())[0][0] == "self": temp = list(params.items()) @@ -657,20 +730,20 @@ def _match_option_param_names(self, params, options): return options - def _is_typing_union(self, annotation): + def _is_typing_union(self, annotation: Any) -> bool: # TODO: Typehint annotation return ( - getattr(annotation, '__origin__', None) is Union - or type(annotation) is getattr(types, "UnionType", Union) - ) # type: ignore + getattr(annotation, '__origin__', None) is Union + or type(annotation) is getattr(types, "UnionType", Union) + ) # type: ignore - def _is_typing_optional(self, annotation): + def _is_typing_optional(self, annotation: Any) -> bool: # TODO: Typehint annotation return self._is_typing_union(annotation) and type(None) in annotation.__args__ # type: ignore @property def is_subcommand(self) -> bool: return self.parent is not None - def to_dict(self) -> Dict: + def to_dict(self) -> CreateApplicationCommand: as_dict = { "name": self.name, "description": self.description, @@ -682,6 +755,9 @@ def to_dict(self) -> Dict: return as_dict + # NOTE: while checking conflicts (for master -> core/typing) + # __eq__ seems to have been removed from master?? + async def _invoke(self, ctx: ApplicationContext) -> None: # TODO: Parse the args better kwargs = {} @@ -691,9 +767,9 @@ async def _invoke(self, ctx: ApplicationContext) -> None: # Checks if input_type is user, role or channel if ( - SlashCommandOptionType.user.value - <= op.input_type.value - <= SlashCommandOptionType.role.value + SlashCommandOptionType.user.value + <= op.input_type.value + <= SlashCommandOptionType.role.value ): if ctx.guild is None and op.input_type.name == "user": _data = ctx.interaction.data["resolved"]["users"][arg] @@ -755,8 +831,7 @@ async def invoke_autocomplete_callback(self, ctx: AutocompleteContext): ][:25] return await ctx.interaction.response.send_autocomplete_result(choices=choices) - - def copy(self): + def copy(self) -> "SlashCommand": """Creates a copy of this command. Returns @@ -767,14 +842,14 @@ def copy(self): ret = self.__class__(self.callback, **self.__original_kwargs__) return self._ensure_assignment_on_copy(ret) - def _ensure_assignment_on_copy(self, other): + def _ensure_assignment_on_copy(self, other: "SlashCommand") -> "SlashCommand": other._before_invoke = self._before_invoke other._after_invoke = self._after_invoke if self.checks != other.checks: other.checks = self.checks.copy() - #if self._buckets.valid and not other._buckets.valid: + # if self._buckets.valid and not other._buckets.valid: # other._buckets = self._buckets.copy() - #if self._max_concurrency != other._max_concurrency: + # if self._max_concurrency != other._max_concurrency: # # _max_concurrency won't be None at this point # other._max_concurrency = self._max_concurrency.copy() # type: ignore @@ -784,7 +859,7 @@ def _ensure_assignment_on_copy(self, other): pass return other - def _update_copy(self, kwargs: Dict[str, Any]): + def _update_copy(self, kwargs: Dict[str, Any]) -> "SlashCommand": if kwargs: kw = kwargs.copy() kw.update(self.__original_kwargs__) @@ -794,7 +869,127 @@ def _update_copy(self, kwargs: Dict[str, Any]): return self.copy() -class SlashCommandGroup(ApplicationCommand): +channel_type_map = { + 'TextChannel': ChannelType.text, + 'VoiceChannel': ChannelType.voice, + 'StageChannel': ChannelType.stage_voice, + 'CategoryChannel': ChannelType.category +} + + +class Option: + + @overload + def __int__( + self, + input_type: Any, + /, + description: str, + *, + name: Optional[str] = None, + channel_type: Optional[ChannelType] = None, + required: bool = None, + default: Any = None, + min_value: Optional[int] = None, + max_value: Optional[int] = None, + autocomplete: Optional[Callable[[AutocompleteContext], Awaitable[Any]]] = None, + ) -> None: + ... + + def __init__( + self, input_type: Any, /, description: str = None, **kwargs + ) -> None: + self.name: Optional[str] = kwargs.pop("name", None) + self.description: str = description or "No description provided" + self._converter = None + self.channel_types: List[ChannelType] = kwargs.pop("channel_types", []) + if not isinstance(input_type, SlashCommandOptionType): + if hasattr(input_type, "convert"): + self._converter = input_type + input_type = SlashCommandOptionType.string + else: + _type = SlashCommandOptionType.from_datatype(input_type) + if _type == SlashCommandOptionType.channel: + if not isinstance(input_type, tuple): + input_type = (input_type,) + for i in input_type: + if i.__name__ == 'GuildChannel': + continue + + channel_type = channel_type_map[i.__name__] + self.channel_types.append(channel_type) + input_type = _type + self.input_type: Any = input_type + self.required: bool = kwargs.pop("required", True) + self.choices: List[OptionChoice] = [ + o if isinstance(o, OptionChoice) else OptionChoice(o) + for o in kwargs.pop("choices", list()) + ] + self.default: Any = kwargs.pop("default", None) + if self.input_type == SlashCommandOptionType.integer: + minmax_types = (int, type(None)) + elif self.input_type == SlashCommandOptionType.number: + minmax_types = (int, float, type(None)) + else: + minmax_types = (type(None),) + minmax_typehint = Optional[Union[minmax_types]] # type: ignore + + self.min_value: minmax_typehint = kwargs.pop("min_value", None) + self.max_value: minmax_typehint = kwargs.pop("max_value", None) + + if not (isinstance(self.min_value, minmax_types) or self.min_value is None): + raise TypeError(f"Expected {minmax_typehint} for min_value, got \"{type(self.min_value).__name__}\"") + if not (isinstance(self.max_value, minmax_types) or self.min_value is None): + raise TypeError(f"Expected {minmax_typehint} for max_value, got \"{type(self.max_value).__name__}\"") + + self.autocomplete: Callable[[AutocompleteContext], Awaitable[List[Union[OptionChoice, str]]]] \ + = kwargs.pop("autocomplete", None) + + def to_dict(self) -> ApplicationCommandOptionData: + as_dict = { + "type": self.input_type.value, + "name": self.name, + "description": self.description, + "required": self.required, + "choices": [c.to_dict() for c in self.choices], + "autocomplete": bool(self.autocomplete) + } + if self.channel_types: + as_dict["channel_types"] = [t.value for t in self.channel_types] + if self.min_value is not None: + as_dict["min_value"] = self.min_value + if self.max_value is not None: + as_dict["max_value"] = self.max_value + + return as_dict + + def __repr__(self) -> str: + return f"" + + +class OptionChoice: + def __init__(self, name: str, value: Optional[Union[str, int, float]] = None): + self.name: str = name + self.value: Union[str, int, float] = value or name + + def to_dict(self) -> ApplicationCommandOptionChoice: + return {"name": self.name, "value": self.value} + + +def option(name: str, option_type=None, **kwargs: Any): # TODO: Typehint everything + """A decorator that can be used instead of type hinting Option""" + + def decor(func): + nonlocal option_type + option_type = option_type or func.__annotations__.get(name, str) + func.__annotations__[name] = Option(type, **kwargs) + return func + + return decor + + +# finished +class SlashCommandGroup(ApplicationCommand, Option): r"""A class that implements the protocol for a slash command group. These can be created manually, but they should be created via the @@ -823,9 +1018,9 @@ class SlashCommandGroup(ApplicationCommand): :exc:`.CheckFailure` exception is raised to the :func:`.on_application_command_error` event. """ - type = 1 + type: int = 1 - def __new__(cls, *args, **kwargs) -> SlashCommandGroup: + def __new__(cls: ApplicationCommandT, *args: Any, **kwargs: Any) -> ApplicationCommandT: self = super().__new__(cls) self.__original_kwargs__ = kwargs.copy() @@ -847,13 +1042,26 @@ def __new__(cls, *args, **kwargs) -> SlashCommandGroup: return self + @overload def __init__( - self, - name: str, - description: str, - guild_ids: Optional[List[int]] = None, - parent: Optional[SlashCommandGroup] = None, - **kwargs + self, + name: str, + description: str, + guild_ids: Optional[List[int]] = None, + parent: Optional[SlashCommandGroup] = None, + *, + default_permission: Optional[bool] = None, + permissions: Optional[List[Permission]] = None, + ): + ... + + def __init__( + self, + name: str, + description: str, + guild_ids: Optional[List[int]] = None, + parent: Optional[SlashCommandGroup] = None, + **kwargs ) -> None: validate_chat_input_name(name) validate_chat_input_description(description) @@ -861,8 +1069,8 @@ def __init__( self.description = description self.input_type = SlashCommandOptionType.sub_command_group self.subcommands: List[Union[SlashCommand, SlashCommandGroup]] = self.__initial_commands__ - self.guild_ids = guild_ids - self.parent = parent + self.guild_ids: Optional[List[int]] = guild_ids + self.parent: Optional[SlashCommandGroup] = parent self.checks = [] self._before_invoke = None @@ -870,12 +1078,12 @@ def __init__( self.cog = None # Permissions - self.default_permission = kwargs.get("default_permission", True) + self.default_permission: bool = kwargs.get("default_permission", True) self.permissions: List[CommandPermission] = kwargs.get("permissions", []) if self.permissions and self.default_permission: self.default_permission = False - def to_dict(self) -> Dict: + def to_dict(self) -> CreateApplicationCommand: as_dict = { "name": self.name, "description": self.description, @@ -1015,6 +1223,7 @@ def _set_cog(self, cog): subcommand._set_cog(cog) +# finished class ContextMenuCommand(ApplicationCommand): r"""A class that implements the protocol for context menu commands. @@ -1052,13 +1261,14 @@ class ContextMenuCommand(ApplicationCommand): .. versionadded:: 2.0 """ - def __new__(cls, *args, **kwargs) -> ContextMenuCommand: + + def __new__(cls: ApplicationCommandT, *args: Any, **kwargs: Any) -> ApplicationCommandT: self = super().__new__(cls) self.__original_kwargs__ = kwargs.copy() return self - def __init__(self, func: Callable, *args, **kwargs) -> None: + def __init__(self, func: Callable, *args: Any, **kwargs: Any) -> None: super().__init__(func, **kwargs) if not asyncio.iscoroutinefunction(func): raise TypeError("Callback must be a coroutine.") @@ -1095,7 +1305,7 @@ def __init__(self, func: Callable, *args, **kwargs) -> None: # Context Menu commands can't have parents self.parent = None - def validate_parameters(self): + def validate_parameters(self) -> None: params = self._get_signature_parameters() if list(params.items())[0][0] == "self": temp = list(params.items()) @@ -1130,13 +1340,14 @@ def validate_parameters(self): pass @property - def qualified_name(self): + def qualified_name(self) -> str: return self.name - def to_dict(self) -> Dict[str, Union[str, int]]: + def to_dict(self) -> CreateApplicationCommand: return {"name": self.name, "description": self.description, "type": self.type, "default_permission": self.default_permission} +# finished class UserCommand(ContextMenuCommand): r"""A class that implements the protocol for user context menu commands. @@ -1163,7 +1374,7 @@ class UserCommand(ContextMenuCommand): """ type = 2 - def __new__(cls, *args, **kwargs) -> UserCommand: + def __new__(cls: Type[ApplicationCommandT], *args: Any, **kwargs: Any) -> ApplicationCommandT: self = super().__new__(cls) self.__original_kwargs__ = kwargs.copy() @@ -1197,7 +1408,7 @@ async def _invoke(self, ctx: ApplicationContext) -> None: else: await self.callback(ctx, target) - def copy(self): + def copy(self: ApplicationCommandT) -> ApplicationCommandT: """Creates a copy of this command. Returns @@ -1208,14 +1419,14 @@ def copy(self): ret = self.__class__(self.callback, **self.__original_kwargs__) return self._ensure_assignment_on_copy(ret) - def _ensure_assignment_on_copy(self, other): + def _ensure_assignment_on_copy(self, other: ApplicationCommandT) -> ApplicationCommandT: other._before_invoke = self._before_invoke other._after_invoke = self._after_invoke if self.checks != other.checks: other.checks = self.checks.copy() - #if self._buckets.valid and not other._buckets.valid: + # if self._buckets.valid and not other._buckets.valid: # other._buckets = self._buckets.copy() - #if self._max_concurrency != other._max_concurrency: + # if self._max_concurrency != other._max_concurrency: # # _max_concurrency won't be None at this point # other._max_concurrency = self._max_concurrency.copy() # type: ignore @@ -1225,7 +1436,7 @@ def _ensure_assignment_on_copy(self, other): pass return other - def _update_copy(self, kwargs: Dict[str, Any]): + def _update_copy(self: ApplicationCommandT, kwargs: Dict[str, Any]) -> ApplicationCommandT: if kwargs: kw = kwargs.copy() kw.update(self.__original_kwargs__) @@ -1235,6 +1446,7 @@ def _update_copy(self, kwargs: Dict[str, Any]): return self.copy() +# finished class MessageCommand(ContextMenuCommand): r"""A class that implements the protocol for message context menu commands. @@ -1261,13 +1473,13 @@ class MessageCommand(ContextMenuCommand): """ type = 3 - def __new__(cls, *args, **kwargs) -> MessageCommand: + def __new__(cls: Type[ApplicationCommandT], *args: Any, **kwargs: Any) -> ApplicationCommandT: self = super().__new__(cls) self.__original_kwargs__ = kwargs.copy() return self - async def _invoke(self, ctx: ApplicationContext): + async def _invoke(self, ctx: ApplicationContext) -> None: _data = ctx.interaction.data["resolved"]["messages"] for i, v in _data.items(): v["id"] = int(i) @@ -1286,7 +1498,7 @@ async def _invoke(self, ctx: ApplicationContext): else: await self.callback(ctx, target) - def copy(self): + def copy(self: ApplicationCommandT) -> ApplicationCommandT: """Creates a copy of this command. Returns @@ -1297,14 +1509,14 @@ def copy(self): ret = self.__class__(self.callback, **self.__original_kwargs__) return self._ensure_assignment_on_copy(ret) - def _ensure_assignment_on_copy(self, other): + def _ensure_assignment_on_copy(self, other: ApplicationCommandT) -> ApplicationCommandT: other._before_invoke = self._before_invoke other._after_invoke = self._after_invoke if self.checks != other.checks: other.checks = self.checks.copy() - #if self._buckets.valid and not other._buckets.valid: + # if self._buckets.valid and not other._buckets.valid: # other._buckets = self._buckets.copy() - #if self._max_concurrency != other._max_concurrency: + # if self._max_concurrency != other._max_concurrency: # # _max_concurrency won't be None at this point # other._max_concurrency = self._max_concurrency.copy() # type: ignore @@ -1314,7 +1526,7 @@ def _ensure_assignment_on_copy(self, other): pass return other - def _update_copy(self, kwargs: Dict[str, Any]): + def _update_copy(self: ApplicationCommandT, kwargs: Dict[str, Any]) -> ApplicationCommandT: if kwargs: kw = kwargs.copy() kw.update(self.__original_kwargs__) @@ -1323,7 +1535,42 @@ def _update_copy(self, kwargs: Dict[str, Any]): else: return self.copy() -def slash_command(**kwargs): + +@overload +def slash_command( + **kwargs: Any +) -> Callable[ + [ + Union[ + Callable[Concatenate[ApplicationContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ApplicationContextT, P], Coro[T]] + ] + ], ApplicationCommand[CogT, P, T]]: + ... + + +@overload +def slash_command( + **kwargs: Any +) -> Callable[ + [ + Union[ + Callable[Concatenate[ApplicationContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ApplicationContextT, P], Coro[T]] + ] + ], ApplicationCommandT]: + ... + + +def slash_command( + **kwargs: Any +) -> Callable[ + [ + Union[ + Callable[Concatenate[ApplicationContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ApplicationContextT, P], Coro[Any]] + ] + ], Union[ApplicationCommand[CogT, P, T], ApplicationCommandT]]: """Decorator for slash commands that invokes :func:`application_command`. .. versionadded:: 2.0 Returns @@ -1333,7 +1580,42 @@ def slash_command(**kwargs): """ return application_command(cls=SlashCommand, **kwargs) -def user_command(**kwargs): + +@overload +def user_command( + **kwargs: Any +) -> Callable[ + [ + Union[ + Callable[Concatenate[ApplicationContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ApplicationContextT, P], Coro[T]] + ] + ], ApplicationCommand[CogT, P, T]]: + ... + + +@overload +def user_command( + **kwargs: Any +) -> Callable[ + [ + Union[ + Callable[Concatenate[ApplicationContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ApplicationContextT, P], Coro[T]] + ] + ], ApplicationCommandT]: + ... + + +def user_command( + **kwargs: Any +) -> Callable[ + [ + Union[ + Callable[Concatenate[ApplicationContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ApplicationContextT, P], Coro[Any]] + ] + ], Union[ApplicationCommand[CogT, P, T], ApplicationCommandT]]: """Decorator for user commands that invokes :func:`application_command`. .. versionadded:: 2.0 Returns @@ -1343,7 +1625,42 @@ def user_command(**kwargs): """ return application_command(cls=UserCommand, **kwargs) -def message_command(**kwargs): + +@overload +def message_command( + **kwargs: Any +) -> Callable[ + [ + Union[ + Callable[Concatenate[ApplicationContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ApplicationContextT, P], Coro[T]] + ] + ], ApplicationCommand[CogT, P, T]]: + ... + + +@overload +def message_command( + **kwargs: Any +) -> Callable[ + [ + Union[ + Callable[Concatenate[ApplicationContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ApplicationContextT, P], Coro[T]] + ] + ], ApplicationCommandT]: + ... + + +def message_command( + **kwargs: Any +) -> Callable[ + [ + Union[ + Callable[Concatenate[ApplicationContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ApplicationContextT, P], Coro[Any]] + ] + ], Union[ApplicationCommand[CogT, P, T], ApplicationCommandT]]: """Decorator for message commands that invokes :func:`application_command`. .. versionadded:: 2.0 Returns @@ -1353,7 +1670,45 @@ def message_command(**kwargs): """ return application_command(cls=MessageCommand, **kwargs) -def application_command(cls=SlashCommand, **attrs): + +@overload +def application_command( + cls: Type[ApplicationCommand] = SlashCommand, + **attrs: Any +) -> Callable[ + [ + Union[ + Callable[Concatenate[ApplicationContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ApplicationContextT, P], Coro[Any]] + ] + ], ApplicationCommand[CogT, P, T]]: + ... + + +@overload +def application_command( + cls: Type[ApplicationCommand] = SlashCommand, + **attrs: Any +) -> Callable[ + [ + Union[ + Callable[Concatenate[ApplicationContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ApplicationContextT, P], Coro[Any]] + ] + ], ApplicationCommandT]: + ... + + +def application_command( + cls: Type[ApplicationCommand] = SlashCommand, + **attrs: Any +) -> Callable[ + [ + Union[ + Callable[Concatenate[ApplicationContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ApplicationContextT, P], Coro[Any]] + ] + ], Union[ApplicationCommand[CogT, P, T], ApplicationCommandT]]: """A decorator that transforms a function into an :class:`.ApplicationCommand`. More specifically, usually one of :class:`.SlashCommand`, :class:`.UserCommand`, or :class:`.MessageCommand`. The exact class depends on the ``cls`` parameter. @@ -1377,7 +1732,12 @@ def application_command(cls=SlashCommand, **attrs): If the function is not a coroutine or is already a command. """ - def decorator(func: Callable) -> cls: + def decorator( + func: Union[ + Callable[Concatenate[ApplicationContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ApplicationContextT, P], Coro[Any]] + ] + ) -> ApplicationCommandT: if isinstance(func, ApplicationCommand): func = func.callback elif not callable(func): @@ -1388,7 +1748,42 @@ def decorator(func: Callable) -> cls: return decorator -def command(**kwargs): + +@overload +def command( + **kwargs: Any +) -> Callable[ + [ + Union[ + Callable[Concatenate[ApplicationContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ApplicationContextT, P], Coro[T]] + ] + ], ApplicationCommand[CogT, P, T]]: + ... + + +@overload +def command( + **kwargs: Any +) -> Callable[ + [ + Union[ + Callable[Concatenate[ApplicationContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ApplicationContextT, P], Coro[T]] + ] + ], ApplicationCommandT]: + ... + + +def command( + **kwargs: Any +) -> Callable[ + [ + Union[ + Callable[Concatenate[ApplicationContextT, P], Coro[Any]], + Callable[Concatenate[CogT, ApplicationContextT, P], Coro[T]] + ] + ], Union[ApplicationCommand[CogT, P, T], ApplicationCommandT]]: """There is an alias for :meth:`application_command`. .. note:: This decorator is overridden by :func:`commands.command`. @@ -1402,10 +1797,10 @@ def command(**kwargs): docs = "https://discord.com/developers/docs" - +# NOTE: what is this? # Validation -def validate_chat_input_name(name: Any): +def validate_chat_input_name(name: Any) -> None: # Must meet the regex ^[\w-]{1,32}$ if not isinstance(name, str): raise TypeError(f"Chat input command names and options must be of type str. Received {name}") @@ -1423,7 +1818,7 @@ def validate_chat_input_name(name: Any): raise ValidationError(f"Chat input command names and options must be lowercase. Received {name}") -def validate_chat_input_description(description: Any): +def validate_chat_input_description(description: Any) -> None: if not isinstance(description, str): raise TypeError(f"Command description must be of type str. Received {description}") if not 1 <= len(description) <= 100: diff --git a/discord/commands/errors.py b/discord/commands/errors.py index 5a4b47eec8..794c34d82b 100644 --- a/discord/commands/errors.py +++ b/discord/commands/errors.py @@ -1,64 +1,64 @@ -""" -The MIT License (MIT) - -Copyright (c) 2021-present Pycord Development - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from ..errors import DiscordException - -__all__ = ( - "ApplicationCommandError", - "CheckFailure", - "ApplicationCommandInvokeError", -) - -class ApplicationCommandError(DiscordException): - r"""The base exception type for all application command related errors. - - This inherits from :exc:`discord.DiscordException`. - - This exception and exceptions inherited from it are handled - in a special way as they are caught and passed into a special event - from :class:`.Bot`\, :func:`.on_command_error`. - """ - pass - -class CheckFailure(ApplicationCommandError): - """Exception raised when the predicates in :attr:`.Command.checks` have failed. - - This inherits from :exc:`ApplicationCommandError` - """ - pass - -class ApplicationCommandInvokeError(ApplicationCommandError): - """Exception raised when the command being invoked raised an exception. - - This inherits from :exc:`ApplicationCommandError` - - Attributes - ----------- - original: :exc:`Exception` - The original exception that was raised. You can also get this via - the ``__cause__`` attribute. - """ - def __init__(self, e: Exception) -> None: - self.original: Exception = e - super().__init__(f'Application Command raised an exception: {e.__class__.__name__}: {e}') +""" +The MIT License (MIT) + +Copyright (c) 2021-present Pycord Development + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from ..errors import DiscordException + +__all__ = ( + "ApplicationCommandError", + "CheckFailure", + "ApplicationCommandInvokeError", +) + +class ApplicationCommandError(DiscordException): + r"""The base exception type for all application command related errors. + + This inherits from :exc:`discord.DiscordException`. + + This exception and exceptions inherited from it are handled + in a special way as they are caught and passed into a special event + from :class:`.Bot`\, :func:`.on_command_error`. + """ + pass + +class CheckFailure(ApplicationCommandError): + """Exception raised when the predicates in :attr:`.Command.checks` have failed. + + This inherits from :exc:`ApplicationCommandError` + """ + pass + +class ApplicationCommandInvokeError(ApplicationCommandError): + """Exception raised when the command being invoked raised an exception. + + This inherits from :exc:`ApplicationCommandError` + + Attributes + ----------- + original: :exc:`Exception` + The original exception that was raised. You can also get this via + the ``__cause__`` attribute. + """ + def __init__(self, e: Exception) -> None: + self.original: Exception = e + super().__init__(f'Application Command raised an exception: {e.__class__.__name__}: {e}') diff --git a/discord/commands/permissions.py b/discord/commands/permissions.py index 7cc4f6b4da..d143f5b02b 100644 --- a/discord/commands/permissions.py +++ b/discord/commands/permissions.py @@ -1,228 +1,248 @@ -""" -The MIT License (MIT) - -Copyright (c) 2015-2021 Rapptz -Copyright (c) 2021-present Pycord Development - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - -from typing import Union, Dict, Callable - -__all__ = ( - "CommandPermission", - "has_role", - "has_any_role", - "is_user", - "is_owner", - "permission", -) - -class CommandPermission: - """The class used in the application command decorators - to hash permission data into a dictionary using the - :meth:`to_dict` method to be sent to the discord API later on. - - .. versionadded:: 2.0 - - Attributes - ----------- - id: Union[:class:`int`, :class:`str`] - A string or integer that represents or helps get - the id of the user or role that the permission is tied to. - type: :class:`int` - An integer representing the type of the permission. - permission: :class:`bool` - A boolean representing the permission's value. - guild_id: :class:`int` - The integer which represents the id of the guild that the - permission may be tied to. - """ - def __init__(self, id: Union[int, str], type: int, permission: bool = True, guild_id: int = None): - self.id = id - self.type = type - self.permission = permission - self.guild_id = guild_id - - def to_dict(self) -> Dict[str, Union[int, bool]]: - return {"id": self.id, "type": self.type, "permission": self.permission} - -def permission(role_id: int = None, user_id: int = None, permission: bool = True, guild_id: int = None): - """The method used to specify application command permissions - for specific users or roles using their id. - - This method is meant to be used as a decorator. - - .. versionadded:: 2.0 - - Parameters - ----------- - role_id: :class:`int` - An integer which represents the id of the role that the - permission may be tied to. - user_id: :class:`int` - An integer which represents the id of the user that the - permission may be tied to. - permission: :class:`bool` - A boolean representing the permission's value. - guild_id: :class:`int` - The integer which represents the id of the guild that the - permission may be tied to. - """ - def decorator(func: Callable): - if not role_id is None: - app_cmd_perm = CommandPermission(role_id, 1, permission, guild_id) - elif not user_id is None: - app_cmd_perm = CommandPermission(user_id, 2, permission, guild_id) - else: - raise ValueError("role_id or user_id must be specified!") - - # Create __app_cmd_perms__ - if not hasattr(func, '__app_cmd_perms__'): - func.__app_cmd_perms__ = [] - - # Append - func.__app_cmd_perms__.append(app_cmd_perm) - - return func - - return decorator - -def has_role(item: Union[int, str], guild_id: int = None): - """The method used to specify application command role restrictions. - - This method is meant to be used as a decorator. - - .. versionadded:: 2.0 - - Parameters - ----------- - item: Union[:class:`int`, :class:`str`] - An integer or string that represent the id or name of the role - that the permission is tied to. - guild_id: :class:`int` - The integer which represents the id of the guild that the - permission may be tied to. - """ - def decorator(func: Callable): - # Create __app_cmd_perms__ - if not hasattr(func, '__app_cmd_perms__'): - func.__app_cmd_perms__ = [] - - # Permissions (Will Convert ID later in register_commands if needed) - app_cmd_perm = CommandPermission(item, 1, True, guild_id) #{"id": item, "type": 1, "permission": True} - - # Append - func.__app_cmd_perms__.append(app_cmd_perm) - - return func - - return decorator - -def has_any_role(*items: Union[int, str], guild_id: int = None): - """The method used to specify multiple application command role restrictions, - The application command runs if the invoker has **any** of the specified roles. - - This method is meant to be used as a decorator. - - .. versionadded:: 2.0 - - Parameters - ----------- - *items: Union[:class:`int`, :class:`str`] - The integers or strings that represent the ids or names of the roles - that the permission is tied to. - guild_id: :class:`int` - The integer which represents the id of the guild that the - permission may be tied to. - """ - def decorator(func: Callable): - # Create __app_cmd_perms__ - if not hasattr(func, '__app_cmd_perms__'): - func.__app_cmd_perms__ = [] - - # Permissions (Will Convert ID later in register_commands if needed) - for item in items: - app_cmd_perm = CommandPermission(item, 1, True, guild_id) #{"id": item, "type": 1, "permission": True} - - # Append - func.__app_cmd_perms__.append(app_cmd_perm) - - return func - - return decorator - -def is_user(user: int, guild_id: int = None): - """The method used to specify application command user restrictions. - - This method is meant to be used as a decorator. - - .. versionadded:: 2.0 - - Parameters - ----------- - user: :class:`int` - An integer that represent the id of the user that the permission is tied to. - guild_id: :class:`int` - The integer which represents the id of the guild that the - permission may be tied to. - """ - def decorator(func: Callable): - # Create __app_cmd_perms__ - if not hasattr(func, '__app_cmd_perms__'): - func.__app_cmd_perms__ = [] - - # Permissions (Will Convert ID later in register_commands if needed) - app_cmd_perm = CommandPermission(user, 2, True, guild_id) #{"id": user, "type": 2, "permission": True} - - # Append - func.__app_cmd_perms__.append(app_cmd_perm) - - return func - - return decorator - -def is_owner(guild_id: int = None): - """The method used to limit application commands exclusively - to the owner of the bot. - - This method is meant to be used as a decorator. - - .. versionadded:: 2.0 - - Parameters - ----------- - guild_id: :class:`int` - The integer which represents the id of the guild that the - permission may be tied to. - """ - def decorator(func: Callable): - # Create __app_cmd_perms__ - if not hasattr(func, '__app_cmd_perms__'): - func.__app_cmd_perms__ = [] - - # Permissions (Will Convert ID later in register_commands if needed) - app_cmd_perm = CommandPermission("owner", 2, True, guild_id) #{"id": "owner", "type": 2, "permission": True} - - # Append - func.__app_cmd_perms__.append(app_cmd_perm) - - return func - - return decorator +""" +The MIT License (MIT) + +Copyright (c) 2015-2021 Rapptz +Copyright (c) 2021-present Pycord Development + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +from __future__ import annotations + +from typing import Union, Callable, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from ..types.interactions import ( + ApplicationCommandPermissions, + ApplicationCommandPermissionType + ) + from ..types.snowflake import Snowflake + +__all__ = ( + "CommandPermission", + "has_role", + "has_any_role", + "is_user", + "is_owner", + "permission", +) + + +class CommandPermission: + """The class used in the application command decorators + to hash permission data into a dictionary using the + :meth:`to_dict` method to be sent to the discord API later on. + + .. versionadded:: 2.0 + + Attributes + ----------- + id: Union[:class:`int`, :class:`str`] + A string or integer that represents or helps get + the id of the user or role that the permission is tied to. + type: :class:`int` + An integer representing the type of the permission. + permission: :class:`bool` + A boolean representing the permission's value. + guild_id: :class:`int` + The integer which represents the id of the guild that the + permission may be tied to. + """ + def __init__(self, perm_id: Snowflake, perm_type: int, permission: bool = True, guild_id: Optional[int] = None): + self.id: Snowflake = perm_id + self.type: ApplicationCommandPermissionType = perm_type + self.permission: bool = permission + self.guild_id: Optional[int] = guild_id + + def to_dict(self) -> ApplicationCommandPermissions: + return {"id": self.id, "type": self.type, "permission": self.permission} + + +def permission(role_id: int = None, user_id: int = None, permission: bool = True, guild_id: int = None): + """The method used to specify application command permissions + for specific users or roles using their id. + + This method is meant to be used as a decorator. + + .. versionadded:: 2.0 + + Parameters + ----------- + role_id: :class:`int` + An integer which represents the id of the role that the + permission may be tied to. + user_id: :class:`int` + An integer which represents the id of the user that the + permission may be tied to. + permission: :class:`bool` + A boolean representing the permission's value. + guild_id: :class:`int` + The integer which represents the id of the guild that the + permission may be tied to. + """ + + def decorator(func: Callable): + if role_id is not None: + app_cmd_perm = CommandPermission(role_id, 1, permission, guild_id) + elif user_id is not None: + app_cmd_perm = CommandPermission(user_id, 2, permission, guild_id) + else: + raise ValueError("role_id or user_id must be specified!") + + # Create __app_cmd_perms__ + if not hasattr(func, '__app_cmd_perms__'): + func.__app_cmd_perms__ = [] + + # Append + func.__app_cmd_perms__.append(app_cmd_perm) + + return func + + return decorator + + +def has_role(item: Union[int, str], guild_id: int = None): + """The method used to specify application command role restrictions. + + This method is meant to be used as a decorator. + + .. versionadded:: 2.0 + + Parameters + ----------- + item: Union[:class:`int`, :class:`str`] + An integer or string that represent the id or name of the role + that the permission is tied to. + guild_id: :class:`int` + The integer which represents the id of the guild that the + permission may be tied to. + """ + + def decorator(func: Callable): + # Create __app_cmd_perms__ + if not hasattr(func, '__app_cmd_perms__'): + func.__app_cmd_perms__ = [] + + # Permissions (Will Convert ID later in register_commands if needed) + app_cmd_perm = CommandPermission(item, 1, True, guild_id) # {"id": item, "type": 1, "permission": True} + + # Append + func.__app_cmd_perms__.append(app_cmd_perm) + + return func + + return decorator + + +def has_any_role(*items: Union[int, str], guild_id: int = None): + """The method used to specify multiple application command role restrictions, + The application command runs if the invoker has **any** of the specified roles. + + This method is meant to be used as a decorator. + + .. versionadded:: 2.0 + + Parameters + ----------- + *items: Union[:class:`int`, :class:`str`] + The integers or strings that represent the ids or names of the roles + that the permission is tied to. + guild_id: :class:`int` + The integer which represents the id of the guild that the + permission may be tied to. + """ + + def decorator(func: Callable): + # Create __app_cmd_perms__ + if not hasattr(func, '__app_cmd_perms__'): + func.__app_cmd_perms__ = [] + + # Permissions (Will Convert ID later in register_commands if needed) + for item in items: + app_cmd_perm = CommandPermission(item, 1, True, guild_id) # {"id": item, "type": 1, "permission": True} + + # Append + func.__app_cmd_perms__.append(app_cmd_perm) + + return func + + return decorator + + +def is_user(user: int, guild_id: int = None): + """The method used to specify application command user restrictions. + + This method is meant to be used as a decorator. + + .. versionadded:: 2.0 + + Parameters + ----------- + user: :class:`int` + An integer that represent the id of the user that the permission is tied to. + guild_id: :class:`int` + The integer which represents the id of the guild that the + permission may be tied to. + """ + + def decorator(func: Callable): + # Create __app_cmd_perms__ + if not hasattr(func, '__app_cmd_perms__'): + func.__app_cmd_perms__ = [] + + # Permissions (Will Convert ID later in register_commands if needed) + app_cmd_perm = CommandPermission(user, 2, True, guild_id) # {"id": user, "type": 2, "permission": True} + + # Append + func.__app_cmd_perms__.append(app_cmd_perm) + + return func + + return decorator + + +def is_owner(guild_id: int = None): + """The method used to limit application commands exclusively + to the owner of the bot. + + This method is meant to be used as a decorator. + + .. versionadded:: 2.0 + + Parameters + ----------- + guild_id: :class:`int` + The integer which represents the id of the guild that the + permission may be tied to. + """ + + def decorator(func: Callable): + # Create __app_cmd_perms__ + if not hasattr(func, '__app_cmd_perms__'): + func.__app_cmd_perms__ = [] + + # Permissions (Will Convert ID later in register_commands if needed) + app_cmd_perm = CommandPermission("owner", 2, True, guild_id) # {"id": "owner", "type": 2, "permission": True} + + # Append + func.__app_cmd_perms__.append(app_cmd_perm) + + return func + + return decorator diff --git a/discord/ext/commands/bot.py b/discord/ext/commands/bot.py index 57d5955be8..d49fd7c14d 100644 --- a/discord/ext/commands/bot.py +++ b/discord/ext/commands/bot.py @@ -115,7 +115,7 @@ def _is_submodule(parent: str, child: str) -> bool: return parent == child or child.startswith(parent + ".") class _DefaultRepr: - def __repr__(self): + def __repr__(self) -> str: return '' _default = _DefaultRepr() diff --git a/discord/ext/commands/converter.py b/discord/ext/commands/converter.py index 754e43267a..f1ac89003c 100644 --- a/discord/ext/commands/converter.py +++ b/discord/ext/commands/converter.py @@ -988,7 +988,7 @@ async def test(ctx, numbers: Greedy[int], reason: str): def __init__(self, *, converter: T): self.converter = converter - def __repr__(self): + def __repr__(self) -> str: converter = getattr(self.converter, '__name__', repr(self.converter)) return f'Greedy[{converter}]' diff --git a/discord/ext/commands/help.py b/discord/ext/commands/help.py index 163111f534..458c0c55b8 100644 --- a/discord/ext/commands/help.py +++ b/discord/ext/commands/help.py @@ -176,7 +176,7 @@ def pages(self): self.close_page() return self._pages - def __repr__(self): + def __repr__(self) -> str: fmt = '' return fmt.format(self) diff --git a/discord/ext/commands/view.py b/discord/ext/commands/view.py index 57d6439a5d..59076ca5bc 100644 --- a/discord/ext/commands/view.py +++ b/discord/ext/commands/view.py @@ -189,5 +189,5 @@ def get_quoted_word(self): result.append(current) - def __repr__(self): + def __repr__(self) -> str: return f'' diff --git a/discord/flags.py b/discord/flags.py index 40e527ce96..10c3bedff9 100644 --- a/discord/flags.py +++ b/discord/flags.py @@ -63,7 +63,7 @@ def __get__(self, instance: Optional[BF], owner: Type[BF]) -> Any: def __set__(self, instance: BF, value: bool) -> None: instance._set_flag(self.flag, value) - def __repr__(self): + def __repr__(self) -> str: return f'' diff --git a/discord/http.py b/discord/http.py index cdefd1f5e4..99afd66986 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1693,7 +1693,9 @@ def get_global_command( ) return self.request(r) - def upsert_global_command(self, application_id: Snowflake, payload) -> Response[interactions.ApplicationCommand]: + def upsert_global_command( + self, application_id: Snowflake, payload: interactions.CreateApplicationCommand + ) -> Response[interactions.ApplicationCommand]: r = Route('POST', '/applications/{application_id}/commands', application_id=application_id) return self.request(r, json=payload) @@ -1727,7 +1729,7 @@ def delete_global_command(self, application_id: Snowflake, command_id: Snowflake return self.request(r) def bulk_upsert_global_commands( - self, application_id: Snowflake, payload + self, application_id: Snowflake, payload: List[interactions.CreateApplicationCommand] ) -> Response[List[interactions.ApplicationCommand]]: r = Route('PUT', '/applications/{application_id}/commands', application_id=application_id) return self.request(r, json=payload) @@ -1764,7 +1766,7 @@ def upsert_guild_command( self, application_id: Snowflake, guild_id: Snowflake, - payload: interactions.EditApplicationCommand, + payload: interactions.CreateApplicationCommand, ) -> Response[interactions.ApplicationCommand]: r = Route( 'POST', @@ -1815,7 +1817,7 @@ def bulk_upsert_guild_commands( self, application_id: Snowflake, guild_id: Snowflake, - payload: List[interactions.EditApplicationCommand], + payload: List[interactions.CreateApplicationCommand], ) -> Response[List[interactions.ApplicationCommand]]: r = Route( 'PUT', @@ -1829,7 +1831,7 @@ def bulk_upsert_command_permissions( self, application_id: Snowflake, guild_id: Snowflake, - payload: List[interactions.EditApplicationCommand], + payload: List[interactions.BaseGuildApplicationCommandPermissions], ) -> Response[List[interactions.ApplicationCommand]]: r = Route( 'PUT', diff --git a/discord/integrations.py b/discord/integrations.py index 6a5f0f5929..85de8df321 100644 --- a/discord/integrations.py +++ b/discord/integrations.py @@ -115,7 +115,7 @@ def __init__(self, *, data: IntegrationPayload, guild: Guild) -> None: self._state = guild._state self._from_data(data) - def __repr__(self): + def __repr__(self) -> str: return f"<{self.__class__.__name__} id={self.id} name={self.name!r}>" def _from_data(self, data: IntegrationPayload) -> None: diff --git a/discord/mentions.py b/discord/mentions.py index 81f80f9223..14156c064c 100644 --- a/discord/mentions.py +++ b/discord/mentions.py @@ -36,10 +36,10 @@ class _FakeBool: - def __repr__(self): + def __repr__(self) -> str: return 'True' - def __eq__(self, other): + def __eq__(self, other) -> bool: return other is True def __bool__(self): diff --git a/discord/partial_emoji.py b/discord/partial_emoji.py index 2eef74d54c..4310e88635 100644 --- a/discord/partial_emoji.py +++ b/discord/partial_emoji.py @@ -173,7 +173,7 @@ def __str__(self) -> str: return f'' return f'<:{self.name}:{self.id}>' - def __repr__(self): + def __repr__(self) -> str: return f'<{self.__class__.__name__} animated={self.animated} name={self.name!r} id={self.id}>' def __eq__(self, other: Any) -> bool: diff --git a/discord/types/interactions.py b/discord/types/interactions.py index 716427494e..bce2797e72 100644 --- a/discord/types/interactions.py +++ b/discord/types/interactions.py @@ -40,9 +40,12 @@ ApplicationCommandType = Literal[1, 2, 3] + class _ApplicationCommandOptional(TypedDict, total=False): - options: List[ApplicationCommandOption] type: ApplicationCommandType + guild_id: Snowflake + options: List[ApplicationCommandOption] + default_permission: bool class ApplicationCommand(_ApplicationCommandOptional): @@ -50,11 +53,17 @@ class ApplicationCommand(_ApplicationCommandOptional): application_id: Snowflake name: str description: str + version: Snowflake class _ApplicationCommandOptionOptional(TypedDict, total=False): + required: bool choices: List[ApplicationCommandOptionChoice] options: List[ApplicationCommandOption] + channel_types: List[ChannelType] + min_value: int + max_value: int + autocomplete: bool ApplicationCommandOptionType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] @@ -64,12 +73,11 @@ class ApplicationCommandOption(_ApplicationCommandOptionOptional): type: ApplicationCommandOptionType name: str description: str - required: bool class ApplicationCommandOptionChoice(TypedDict): name: str - value: Union[str, int] + value: Union[str, int, float] ApplicationCommandPermissionType = Literal[1, 2] @@ -94,7 +102,7 @@ class GuildApplicationCommandPermissions(PartialGuildApplicationCommandPermissio guild_id: Snowflake -InteractionType = Literal[1, 2, 3] +InteractionType = Literal[1, 2, 3, 4] class _ApplicationCommandInteractionDataOption(TypedDict): @@ -225,12 +233,25 @@ class MessageInteraction(TypedDict): user: User +class _CreateApplicationCommandOptional(TypedDict, total=False): + options: List[ApplicationCommandOption] + default_permission: bool + type: ApplicationCommandType + + +class CreateApplicationCommand(_CreateApplicationCommandOptional): + name: str + description: str + + class _EditApplicationCommandOptional(TypedDict, total=False): + name: str description: str - options: Optional[List[ApplicationCommandOption]] + options: List[ApplicationCommandOption] type: ApplicationCommandType + default_permission: bool class EditApplicationCommand(_EditApplicationCommandOptional): - name: str - default_permission: bool + pass + diff --git a/discord/ui/button.py b/discord/ui/button.py index 032dd33344..61efb61b27 100644 --- a/discord/ui/button.py +++ b/discord/ui/button.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import Callable, Optional, TYPE_CHECKING, Tuple, Type, TypeVar, Union +from typing import Callable, Optional, TYPE_CHECKING, Tuple, Type, TypeVar, Union, Dict import inspect import os @@ -132,7 +132,7 @@ def style(self) -> ButtonStyle: return self._underlying.style @style.setter - def style(self, value: ButtonStyle): + def style(self, value: ButtonStyle) -> None: self._underlying.style = value @property @@ -144,7 +144,7 @@ def custom_id(self) -> Optional[str]: return self._underlying.custom_id @custom_id.setter - def custom_id(self, value: Optional[str]): + def custom_id(self, value: Optional[str]) -> None: if value is not None and not isinstance(value, str): raise TypeError('custom_id must be None or str') @@ -156,7 +156,7 @@ def url(self) -> Optional[str]: return self._underlying.url @url.setter - def url(self, value: Optional[str]): + def url(self, value: Optional[str]) -> None: if value is not None and not isinstance(value, str): raise TypeError('url must be None or str') self._underlying.url = value @@ -167,7 +167,7 @@ def disabled(self) -> bool: return self._underlying.disabled @disabled.setter - def disabled(self, value: bool): + def disabled(self, value: bool) -> None: self._underlying.disabled = bool(value) @property @@ -176,7 +176,7 @@ def label(self) -> Optional[str]: return self._underlying.label @label.setter - def label(self, value: Optional[str]): + def label(self, value: Optional[str]) -> None: self._underlying.label = str(value) if value is not None else value @property @@ -185,7 +185,7 @@ def emoji(self) -> Optional[PartialEmoji]: return self._underlying.emoji @emoji.setter - def emoji(self, value: Optional[Union[str, Emoji, PartialEmoji]]): # type: ignore + def emoji(self, value: Optional[Union[str, Emoji, PartialEmoji]]) -> None: if value is not None: if isinstance(value, str): self._underlying.emoji = PartialEmoji.from_str(value) @@ -212,7 +212,8 @@ def from_component(cls: Type[B], button: ButtonComponent) -> B: def type(self) -> ComponentType: return self._underlying.type - def to_component_dict(self): + # TODO(ultrabear) What is the type signature of the dict returned? + def to_component_dict(self) -> Dict: return self._underlying.to_dict() def is_dispatchable(self) -> bool: diff --git a/discord/ui/item.py b/discord/ui/item.py index 1fb168a6f7..c4cfa2c14a 100644 --- a/discord/ui/item.py +++ b/discord/ui/item.py @@ -56,7 +56,7 @@ class Item(Generic[V]): __item_repr_attributes__: Tuple[str, ...] = ('row',) - def __init__(self): + def __init__(self) -> None: self._view: Optional[V] = None self._row: Optional[int] = None self._rendered_row: Optional[int] = None @@ -100,7 +100,7 @@ def row(self) -> Optional[int]: return self._row @row.setter - def row(self, value: Optional[int]): + def row(self, value: Optional[int]) -> None: if value is None: self._row = None elif 5 > value >= 0: @@ -117,7 +117,7 @@ def view(self) -> Optional[V]: """Optional[:class:`View`]: The underlying view for this item.""" return self._view - async def callback(self, interaction: Interaction): + async def callback(self, interaction: Interaction) -> None: """|coro| The callback associated with this UI item. diff --git a/discord/ui/select.py b/discord/ui/select.py index 0241a3ddde..7b8a3650af 100644 --- a/discord/ui/select.py +++ b/discord/ui/select.py @@ -130,7 +130,7 @@ def custom_id(self) -> str: return self._underlying.custom_id @custom_id.setter - def custom_id(self, value: str): + def custom_id(self, value: str) -> None: if not isinstance(value, str): raise TypeError('custom_id must be None or str') @@ -142,7 +142,7 @@ def placeholder(self) -> Optional[str]: return self._underlying.placeholder @placeholder.setter - def placeholder(self, value: Optional[str]): + def placeholder(self, value: Optional[str]) -> None: if value is not None and not isinstance(value, str): raise TypeError('placeholder must be None or str') @@ -154,7 +154,7 @@ def min_values(self) -> int: return self._underlying.min_values @min_values.setter - def min_values(self, value: int): + def min_values(self, value: int) -> None: self._underlying.min_values = int(value) @property @@ -163,7 +163,7 @@ def max_values(self) -> int: return self._underlying.max_values @max_values.setter - def max_values(self, value: int): + def max_values(self, value: int) -> None: self._underlying.max_values = int(value) @property @@ -172,7 +172,7 @@ def options(self) -> List[SelectOption]: return self._underlying.options @options.setter - def options(self, value: List[SelectOption]): + def options(self, value: List[SelectOption]) -> None: if not isinstance(value, list): raise TypeError('options must be a list of SelectOption') if not all(isinstance(obj, SelectOption) for obj in value): @@ -228,7 +228,7 @@ def add_option( self.append_option(option) - def append_option(self, option: SelectOption): + def append_option(self, option: SelectOption) -> None: """Appends an option to the select menu. Parameters @@ -253,7 +253,7 @@ def disabled(self) -> bool: return self._underlying.disabled @disabled.setter - def disabled(self, value: bool): + def disabled(self, value: bool) -> None: self._underlying.disabled = bool(value) @property diff --git a/discord/ui/view.py b/discord/ui/view.py index 8ec2ee46cb..0fc9ddbee6 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -79,7 +79,7 @@ class _ViewWeights: 'weights', ) - def __init__(self, children: List[Item]): + def __init__(self, children: List[Item]) -> None: self.weights: List[int] = [0, 0, 0, 0, 0] key = lambda i: sys.maxsize if i.row is None else i.row @@ -155,7 +155,7 @@ def __init_subclass__(cls) -> None: cls.__view_children_items__ = children - def __init__(self, *items: Item, timeout: Optional[float] = 180.0): + def __init__(self, *items: Item, timeout: Optional[float] = 180.0) -> None: self.timeout = timeout self.children: List[Item] = [] for func in self.__view_children_items__: @@ -366,6 +366,8 @@ async def _scheduled_task(self, item: Item, interaction: Interaction): if not interaction.response._responded: await interaction.response.defer() except Exception as e: + # TODO(ultrabear) How to define this return type as what on_error returns? + # (as on_error can be reimplemented it seems) return await self.on_error(e, item, interaction) def _start_listening_from_store(self, store: ViewStore) -> None: @@ -378,20 +380,20 @@ def _start_listening_from_store(self, store: ViewStore) -> None: self.__timeout_expiry = time.monotonic() + self.timeout self.__timeout_task = loop.create_task(self.__timeout_task_impl()) - def _dispatch_timeout(self): + def _dispatch_timeout(self) -> None: if self.__stopped.done(): return self.__stopped.set_result(True) asyncio.create_task(self.on_timeout(), name=f'discord-ui-view-timeout-{self.id}') - def _dispatch_item(self, item: Item, interaction: Interaction): + def _dispatch_item(self, item: Item, interaction: Interaction) -> None: if self.__stopped.done(): return asyncio.create_task(self._scheduled_task(item, interaction), name=f'discord-ui-view-dispatch-{self.id}') - def refresh(self, components: List[Component]): + def refresh(self, components: List[Component]) -> None: # This is pretty hacky at the moment # fmt: off old_state: Dict[Tuple[int, str], Item] = { @@ -464,7 +466,7 @@ async def wait(self) -> bool: class ViewStore: - def __init__(self, state: ConnectionState): + def __init__(self, state: ConnectionState) -> None: # (component_type, message_id, custom_id): (View, Item) self._views: Dict[Tuple[int, Optional[int], str], Tuple[View, Item]] = {} # message_id: View @@ -482,7 +484,7 @@ def persistent_views(self) -> Sequence[View]: # fmt: on return list(views.values()) - def __verify_integrity(self): + def __verify_integrity(self) -> None: to_remove: List[Tuple[int, Optional[int], str]] = [] for (k, (view, _)) in self._views.items(): if view.is_finished(): @@ -491,7 +493,7 @@ def __verify_integrity(self): for k in to_remove: del self._views[k] - def add_view(self, view: View, message_id: Optional[int] = None): + def add_view(self, view: View, message_id: Optional[int] = None) -> None: self.__verify_integrity() view._start_listening_from_store(self) @@ -502,7 +504,7 @@ def add_view(self, view: View, message_id: Optional[int] = None): if message_id is not None: self._synced_message_views[message_id] = view - def remove_view(self, view: View): + def remove_view(self, view: View) -> None: for item in view.children: if item.is_dispatchable(): self._views.pop((item.type.value, item.custom_id), None) # type: ignore @@ -512,7 +514,7 @@ def remove_view(self, view: View): del self._synced_message_views[key] break - def dispatch(self, component_type: int, custom_id: str, interaction: Interaction): + def dispatch(self, component_type: int, custom_id: str, interaction: Interaction) -> None: self.__verify_integrity() message_id: Optional[int] = interaction.message and interaction.message.id key = (component_type, message_id, custom_id) @@ -526,13 +528,13 @@ def dispatch(self, component_type: int, custom_id: str, interaction: Interaction item.refresh_state(interaction) view._dispatch_item(item, interaction) - def is_message_tracked(self, message_id: int): + def is_message_tracked(self, message_id: int) -> bool: return message_id in self._synced_message_views def remove_message_tracking(self, message_id: int) -> Optional[View]: return self._synced_message_views.pop(message_id, None) - def update_from_message(self, message_id: int, components: List[ComponentPayload]): + def update_from_message(self, message_id: int, components: List[ComponentPayload]) -> None: # pre-req: is_message_tracked == true view = self._synced_message_views[message_id] view.refresh([_component_factory(d) for d in components]) diff --git a/discord/utils.py b/discord/utils.py index cd93543431..3f07b8c8c7 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -96,13 +96,13 @@ class _MissingSentinel: - def __eq__(self, other): + def __eq__(self, other) -> bool: return False def __bool__(self): return False - def __repr__(self): + def __repr__(self) -> str: return '...' diff --git a/discord/webhook/async_.py b/discord/webhook/async_.py index 11cdf0c975..0578d619b3 100644 --- a/discord/webhook/async_.py +++ b/discord/webhook/async_.py @@ -578,7 +578,7 @@ def __init__(self, *, data): self.id = int(data['id']) self.name = data['name'] - def __repr__(self): + def __repr__(self) -> str: return f'' @@ -605,7 +605,7 @@ def __init__(self, *, data, state): self.name = data['name'] self._icon = data['icon'] - def __repr__(self): + def __repr__(self) -> str: return f'' @property @@ -993,7 +993,7 @@ def __init__(self, data: WebhookPayload, session: aiohttp.ClientSession, token: super().__init__(data, token, state) self.session = session - def __repr__(self): + def __repr__(self) -> str: return f'' @property diff --git a/discord/webhook/sync.py b/discord/webhook/sync.py index 699fae7576..2e89e08b3c 100644 --- a/discord/webhook/sync.py +++ b/discord/webhook/sync.py @@ -554,7 +554,7 @@ def __init__(self, data: WebhookPayload, session: Session, token: Optional[str] super().__init__(data, token, state) self.session = session - def __repr__(self): + def __repr__(self) -> str: return f'' @property diff --git a/discord/welcome_screen.py b/discord/welcome_screen.py index b97c6653e8..7ead156623 100644 --- a/discord/welcome_screen.py +++ b/discord/welcome_screen.py @@ -65,7 +65,7 @@ def __init__(self, channel: Snowflake, description: str, emoji: Union[Emoji, Par self.description = description self.emoji = emoji - def __repr__(self): + def __repr__(self) -> str: return f'WelcomeScreenChannel(channel={self.channel} description={self.description})' def to_dict(self) -> WelcomeScreenChannelPayload: @@ -123,7 +123,7 @@ def __init__(self, data: WelcomeScreenPayload, guild: Guild): self._guild = guild self._update(data) - def __repr__(self): + def __repr__(self) -> str: return f'=3.6.0,<3.9.0 \ No newline at end of file +aiohttp>=3.6.0,<3.9.0 +typing-extensions==4.0.1