On branch DiscordProfile
Initial commit
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
"""
|
||||
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 .context import *
|
||||
from .core import *
|
||||
from .options import *
|
||||
from .permissions import *
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,476 @@
|
||||
"""
|
||||
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 TYPE_CHECKING, Any, TypeVar, overload
|
||||
|
||||
import discord.abc
|
||||
from discord.interactions import Interaction, InteractionMessage, InteractionResponse
|
||||
from discord.webhook.async_ import Webhook
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Awaitable, Callable
|
||||
|
||||
from typing_extensions import ParamSpec
|
||||
|
||||
import discord
|
||||
|
||||
from .. import AllowedMentions, Bot
|
||||
from ..client import ClientUser
|
||||
from ..cog import Cog
|
||||
from ..embeds import Embed
|
||||
from ..file import File
|
||||
from ..guild import Guild
|
||||
from ..interactions import InteractionChannel
|
||||
from ..member import Member
|
||||
from ..message import Message
|
||||
from ..permissions import Permissions
|
||||
from ..poll import Poll
|
||||
from ..state import ConnectionState
|
||||
from ..ui import BaseView
|
||||
from ..user import User
|
||||
from ..voice import VoiceClient
|
||||
from ..webhook import WebhookMessage
|
||||
from .core import ApplicationCommand, Option
|
||||
|
||||
T = TypeVar("T")
|
||||
CogT = TypeVar("CogT", bound="Cog")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
P = ParamSpec("P")
|
||||
else:
|
||||
P = TypeVar("P")
|
||||
|
||||
__all__ = ("ApplicationContext", "AutocompleteContext")
|
||||
|
||||
|
||||
class ApplicationContext(discord.abc.Messageable):
|
||||
"""Represents a Discord application command interaction context.
|
||||
|
||||
This class is not created manually and is instead passed to application
|
||||
commands as the first parameter.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Attributes
|
||||
----------
|
||||
bot: :class:`.Bot`
|
||||
The bot that the command belongs to.
|
||||
interaction: :class:`.Interaction`
|
||||
The interaction object that invoked the command.
|
||||
"""
|
||||
|
||||
def __init__(self, bot: Bot, interaction: Interaction):
|
||||
self.bot = bot
|
||||
self.interaction = interaction
|
||||
|
||||
# below attributes will be set after initialization
|
||||
self.focused: Option = None # type: ignore
|
||||
self.value: str = None # type: ignore
|
||||
self.options: dict = None # type: ignore
|
||||
|
||||
self._state: ConnectionState = self.interaction._state
|
||||
|
||||
async def _get_channel(self) -> InteractionChannel | None:
|
||||
return self.interaction.channel
|
||||
|
||||
async def invoke(
|
||||
self,
|
||||
command: ApplicationCommand[CogT, P, T],
|
||||
/,
|
||||
*args: P.args,
|
||||
**kwargs: P.kwargs,
|
||||
) -> T:
|
||||
r"""|coro|
|
||||
|
||||
Calls a command with the arguments given.
|
||||
This is useful if you want to just call the callback that a
|
||||
:class:`.ApplicationCommand` holds internally.
|
||||
|
||||
.. note::
|
||||
|
||||
This does not handle converters, checks, cooldowns, pre-invoke,
|
||||
or after-invoke hooks in any matter. It calls the internal callback
|
||||
directly as-if it was a regular function.
|
||||
You must take care in passing the proper arguments when
|
||||
using this function.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
command: :class:`.ApplicationCommand`
|
||||
The command that is going to be called.
|
||||
\*args
|
||||
The arguments to use.
|
||||
\*\*kwargs
|
||||
The keyword arguments to use.
|
||||
|
||||
Raises
|
||||
-------
|
||||
TypeError
|
||||
The command argument to invoke is missing.
|
||||
"""
|
||||
return await command(self, *args, **kwargs)
|
||||
|
||||
@property
|
||||
def command(self) -> ApplicationCommand | None:
|
||||
"""The command that this context belongs to."""
|
||||
return self.interaction.command
|
||||
|
||||
@command.setter
|
||||
def command(self, value: ApplicationCommand | None) -> None:
|
||||
self.interaction.command = value
|
||||
|
||||
@property
|
||||
def channel(self) -> InteractionChannel | None:
|
||||
"""Union[:class:`abc.GuildChannel`, :class:`PartialMessageable`, :class:`Thread`]:
|
||||
Returns the channel associated with this context's command. Shorthand for :attr:`.Interaction.channel`.
|
||||
"""
|
||||
return self.interaction.channel
|
||||
|
||||
@property
|
||||
def channel_id(self) -> int | None:
|
||||
"""Returns the ID of the channel associated with this context's command.
|
||||
Shorthand for :attr:`.Interaction.channel_id`.
|
||||
"""
|
||||
return self.interaction.channel_id
|
||||
|
||||
@property
|
||||
def guild(self) -> Guild | None:
|
||||
"""Returns the guild associated with this context's command.
|
||||
Shorthand for :attr:`.Interaction.guild`.
|
||||
"""
|
||||
return self.interaction.guild
|
||||
|
||||
@property
|
||||
def guild_id(self) -> int | None:
|
||||
"""Returns the ID of the guild associated with this context's command.
|
||||
Shorthand for :attr:`.Interaction.guild_id`.
|
||||
"""
|
||||
return self.interaction.guild_id
|
||||
|
||||
@property
|
||||
def locale(self) -> str | None:
|
||||
"""Returns the locale of the guild associated with this context's command.
|
||||
Shorthand for :attr:`.Interaction.locale`.
|
||||
"""
|
||||
return self.interaction.locale
|
||||
|
||||
@property
|
||||
def guild_locale(self) -> str | None:
|
||||
"""Returns the locale of the guild associated with this context's command.
|
||||
Shorthand for :attr:`.Interaction.guild_locale`.
|
||||
"""
|
||||
return self.interaction.guild_locale
|
||||
|
||||
@property
|
||||
def app_permissions(self) -> Permissions:
|
||||
return self.interaction.app_permissions
|
||||
|
||||
@property
|
||||
def me(self) -> Member | ClientUser | None:
|
||||
"""Union[:class:`.Member`, :class:`.ClientUser`]:
|
||||
Similar to :attr:`.Guild.me` except it may return the :class:`.ClientUser` in private message
|
||||
message contexts, or when :meth:`Intents.guilds` is absent.
|
||||
"""
|
||||
return (
|
||||
self.interaction.guild.me
|
||||
if self.interaction.guild is not None
|
||||
else self.bot.user
|
||||
)
|
||||
|
||||
@property
|
||||
def message(self) -> Message | None:
|
||||
"""Returns the message sent with this context's command.
|
||||
Shorthand for :attr:`.Interaction.message`, if applicable.
|
||||
"""
|
||||
return self.interaction.message
|
||||
|
||||
@property
|
||||
def user(self) -> Member | User:
|
||||
"""Returns the user that sent this context's command.
|
||||
Shorthand for :attr:`.Interaction.user`.
|
||||
"""
|
||||
return self.interaction.user # type: ignore # command user will never be None
|
||||
|
||||
author: Member | User = user
|
||||
|
||||
@property
|
||||
def voice_client(self) -> VoiceClient | None:
|
||||
"""Returns the voice client associated with this context's command.
|
||||
Shorthand for :attr:`Interaction.guild.voice_client<~discord.Guild.voice_client>`, if applicable.
|
||||
"""
|
||||
if self.interaction.guild is None:
|
||||
return None
|
||||
|
||||
return self.interaction.guild.voice_client
|
||||
|
||||
@property
|
||||
def response(self) -> InteractionResponse:
|
||||
"""Returns the response object associated with this context's command.
|
||||
Shorthand for :attr:`.Interaction.response`.
|
||||
"""
|
||||
return self.interaction.response
|
||||
|
||||
@property
|
||||
def selected_options(self) -> list[dict[str, Any]] | None:
|
||||
"""The options and values that were selected by the user when sending the command.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Optional[List[Dict[:class:`str`, Any]]]
|
||||
A dictionary containing the options and values that were selected by the user when the command
|
||||
was processed, if applicable. Returns ``None`` if the command has not yet been invoked,
|
||||
or if there are no options defined for that command.
|
||||
"""
|
||||
return self.interaction.data.get("options", None)
|
||||
|
||||
@property
|
||||
def unselected_options(self) -> list[Option] | None:
|
||||
"""The options that were not provided by the user when sending the command.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Optional[List[:class:`.Option`]]
|
||||
A list of Option objects (if any) that were not selected by the user when the command was processed.
|
||||
Returns ``None`` if there are no options defined for that command.
|
||||
"""
|
||||
if self.command.options is not None: # type: ignore
|
||||
if self.selected_options:
|
||||
return [
|
||||
option
|
||||
for option in self.command.options # type: ignore
|
||||
if option.to_dict()["name"]
|
||||
not in [opt["name"] for opt in self.selected_options]
|
||||
]
|
||||
else:
|
||||
return self.command.options # type: ignore
|
||||
return None
|
||||
|
||||
@property
|
||||
def attachment_size_limit(self) -> int:
|
||||
"""Returns the attachment size limit associated with this context's interaction.
|
||||
Shorthand for :attr:`.Interaction.attachment_size_limit`.
|
||||
"""
|
||||
return self.interaction.attachment_size_limit
|
||||
|
||||
@property
|
||||
@discord.utils.copy_doc(InteractionResponse.send_modal)
|
||||
def send_modal(self) -> Callable[..., Awaitable[Interaction]]:
|
||||
return self.interaction.response.send_modal
|
||||
|
||||
@overload
|
||||
async def respond(
|
||||
self,
|
||||
content: Any | None = None,
|
||||
embed: Embed | None = None,
|
||||
view: BaseView | None = None,
|
||||
tts: bool = False,
|
||||
ephemeral: bool = False,
|
||||
allowed_mentions: AllowedMentions | None = None,
|
||||
file: File | None = None,
|
||||
files: list[File] | None = None,
|
||||
poll: Poll | None = None,
|
||||
delete_after: float | None = None,
|
||||
silent: bool = False,
|
||||
suppress_embeds: bool = False,
|
||||
) -> Interaction | WebhookMessage: ...
|
||||
|
||||
@overload
|
||||
async def respond(
|
||||
self,
|
||||
content: Any | None = None,
|
||||
embeds: list[Embed] | None = None,
|
||||
view: BaseView | None = None,
|
||||
tts: bool = False,
|
||||
ephemeral: bool = False,
|
||||
allowed_mentions: AllowedMentions | None = None,
|
||||
file: File | None = None,
|
||||
files: list[File] | None = None,
|
||||
poll: Poll | None = None,
|
||||
delete_after: float | None = None,
|
||||
silent: bool = False,
|
||||
suppress_embeds: bool = False,
|
||||
) -> Interaction | WebhookMessage: ...
|
||||
|
||||
@discord.utils.copy_doc(Interaction.respond)
|
||||
async def respond(self, *args, **kwargs) -> Interaction | WebhookMessage:
|
||||
return await self.interaction.respond(*args, **kwargs)
|
||||
|
||||
@property
|
||||
@discord.utils.copy_doc(InteractionResponse.send_message)
|
||||
def send_response(self) -> Callable[..., Awaitable[Interaction]]:
|
||||
if not self.interaction.response.is_done():
|
||||
return self.interaction.response.send_message
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"Interaction was already issued a response. Try using"
|
||||
f" {type(self).__name__}.send_followup() instead."
|
||||
)
|
||||
|
||||
@property
|
||||
@discord.utils.copy_doc(Webhook.send)
|
||||
def send_followup(self) -> Callable[..., Awaitable[WebhookMessage]]:
|
||||
if self.interaction.response.is_done():
|
||||
return self.followup.send
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"Interaction was not yet issued a response. Try using"
|
||||
f" {type(self).__name__}.respond() first."
|
||||
)
|
||||
|
||||
@property
|
||||
@discord.utils.copy_doc(InteractionResponse.defer)
|
||||
def defer(self) -> Callable[..., Awaitable[None]]:
|
||||
return self.interaction.response.defer
|
||||
|
||||
@property
|
||||
def followup(self) -> Webhook:
|
||||
"""Returns the followup webhook for followup interactions."""
|
||||
return self.interaction.followup
|
||||
|
||||
async def delete(self, *, delay: float | None = None) -> None:
|
||||
"""|coro|
|
||||
|
||||
Deletes the original interaction response message.
|
||||
|
||||
This is a higher level interface to :meth:`Interaction.delete_original_response`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
delay: Optional[:class:`float`]
|
||||
If provided, the number of seconds to wait before deleting the message.
|
||||
|
||||
Raises
|
||||
------
|
||||
HTTPException
|
||||
Deleting the message failed.
|
||||
Forbidden
|
||||
You do not have proper permissions to delete the message.
|
||||
"""
|
||||
if not self.interaction.response.is_done():
|
||||
await self.defer()
|
||||
|
||||
return await self.interaction.delete_original_response(delay=delay)
|
||||
|
||||
@property
|
||||
@discord.utils.copy_doc(Interaction.edit_original_response)
|
||||
def edit(self) -> Callable[..., Awaitable[InteractionMessage]]:
|
||||
return self.interaction.edit_original_response
|
||||
|
||||
@property
|
||||
def cog(self) -> Cog | None:
|
||||
"""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
|
||||
|
||||
def is_guild_authorised(self) -> bool:
|
||||
""":class:`bool`: Checks if the invoked command is guild-installed.
|
||||
This is a shortcut for :meth:`Interaction.is_guild_authorised`.
|
||||
|
||||
There is an alias for this called :meth:`.is_guild_authorized`.
|
||||
|
||||
.. versionadded:: 2.7
|
||||
"""
|
||||
return self.interaction.is_guild_authorised()
|
||||
|
||||
def is_user_authorised(self) -> bool:
|
||||
""":class:`bool`: Checks if the invoked command is user-installed.
|
||||
This is a shortcut for :meth:`Interaction.is_user_authorised`.
|
||||
|
||||
There is an alias for this called :meth:`.is_user_authorized`.
|
||||
|
||||
.. versionadded:: 2.7
|
||||
"""
|
||||
return self.interaction.is_user_authorised()
|
||||
|
||||
def is_guild_authorized(self) -> bool:
|
||||
""":class:`bool`: An alias for :meth:`.is_guild_authorised`.
|
||||
|
||||
.. versionadded:: 2.7
|
||||
"""
|
||||
return self.is_guild_authorised()
|
||||
|
||||
def is_user_authorized(self) -> bool:
|
||||
""":class:`bool`: An alias for :meth:`.is_user_authorised`.
|
||||
|
||||
.. versionadded:: 2.7
|
||||
"""
|
||||
return self.is_user_authorised()
|
||||
|
||||
|
||||
class AutocompleteContext:
|
||||
"""Represents context for a slash command's option autocomplete.
|
||||
|
||||
This class is not created manually and is instead passed to an :class:`.Option`'s autocomplete callback.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Attributes
|
||||
----------
|
||||
bot: :class:`.Bot`
|
||||
The bot that the command belongs to.
|
||||
interaction: :class:`.Interaction`
|
||||
The interaction object that invoked the autocomplete.
|
||||
focused: :class:`.Option`
|
||||
The option the user is currently typing.
|
||||
value: :class:`.str`
|
||||
The content of the focused option.
|
||||
options: Dict[:class:`str`, Any]
|
||||
A name to value mapping of the options that the user has selected before this option.
|
||||
"""
|
||||
|
||||
__slots__ = ("bot", "interaction", "focused", "value", "options")
|
||||
|
||||
def __init__(self, bot: Bot, interaction: Interaction):
|
||||
self.bot = bot
|
||||
self.interaction = interaction
|
||||
|
||||
self.focused: Option = None # type: ignore
|
||||
self.value: str = None # type: ignore
|
||||
self.options: dict = None # type: ignore
|
||||
|
||||
@property
|
||||
def cog(self) -> Cog | None:
|
||||
"""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 command(self) -> ApplicationCommand | None:
|
||||
"""The command that this context belongs to."""
|
||||
return self.interaction.command
|
||||
|
||||
@command.setter
|
||||
def command(self, value: ApplicationCommand | None) -> None:
|
||||
self.interaction.command = value
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,557 @@
|
||||
"""
|
||||
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 __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import logging
|
||||
import sys
|
||||
import types
|
||||
from collections.abc import Awaitable, Callable, Iterable
|
||||
from enum import Enum
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Literal,
|
||||
Optional,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
get_args,
|
||||
)
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
from typing import TypeAliasType
|
||||
else:
|
||||
from typing_extensions import TypeAliasType
|
||||
|
||||
from ..abc import GuildChannel, Mentionable
|
||||
from ..channel import (
|
||||
CategoryChannel,
|
||||
DMChannel,
|
||||
ForumChannel,
|
||||
MediaChannel,
|
||||
StageChannel,
|
||||
TextChannel,
|
||||
Thread,
|
||||
VoiceChannel,
|
||||
)
|
||||
from ..commands import ApplicationContext, AutocompleteContext
|
||||
from ..enums import ChannelType
|
||||
from ..enums import Enum as DiscordEnum
|
||||
from ..enums import SlashCommandOptionType
|
||||
from ..utils import MISSING, basic_autocomplete
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..cog import Cog
|
||||
from ..ext.commands import Converter
|
||||
from ..member import Member
|
||||
from ..message import Attachment
|
||||
from ..role import Role
|
||||
from ..user import User
|
||||
|
||||
InputType = Union[
|
||||
Type[str],
|
||||
Type[bool],
|
||||
Type[int],
|
||||
Type[float],
|
||||
Type[GuildChannel],
|
||||
Type[Thread],
|
||||
Type[Member],
|
||||
Type[User],
|
||||
Type[Attachment],
|
||||
Type[Role],
|
||||
Type[Mentionable],
|
||||
SlashCommandOptionType,
|
||||
Converter,
|
||||
Type[Converter],
|
||||
Type[Enum],
|
||||
Type[DiscordEnum],
|
||||
]
|
||||
|
||||
AutocompleteReturnType = Union[
|
||||
Iterable["OptionChoice"], Iterable[str], Iterable[int], Iterable[float]
|
||||
]
|
||||
T = TypeVar("T", bound=AutocompleteReturnType)
|
||||
MaybeAwaitable = Union[T, Awaitable[T]]
|
||||
AutocompleteFunction = Union[
|
||||
Callable[[AutocompleteContext], MaybeAwaitable[AutocompleteReturnType]],
|
||||
Callable[[Cog, AutocompleteContext], MaybeAwaitable[AutocompleteReturnType]],
|
||||
Callable[
|
||||
[AutocompleteContext, Any], # pyright: ignore [reportExplicitAny]
|
||||
MaybeAwaitable[AutocompleteReturnType],
|
||||
],
|
||||
Callable[
|
||||
[Cog, AutocompleteContext, Any], # pyright: ignore [reportExplicitAny]
|
||||
MaybeAwaitable[AutocompleteReturnType],
|
||||
],
|
||||
]
|
||||
|
||||
|
||||
__all__ = (
|
||||
"ThreadOption",
|
||||
"Option",
|
||||
"OptionChoice",
|
||||
"option",
|
||||
)
|
||||
|
||||
CHANNEL_TYPE_MAP = {
|
||||
TextChannel: ChannelType.text,
|
||||
VoiceChannel: ChannelType.voice,
|
||||
StageChannel: ChannelType.stage_voice,
|
||||
CategoryChannel: ChannelType.category,
|
||||
Thread: ChannelType.public_thread,
|
||||
ForumChannel: ChannelType.forum,
|
||||
MediaChannel: ChannelType.media,
|
||||
DMChannel: ChannelType.private,
|
||||
}
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ThreadOption:
|
||||
"""Represents a class that can be passed as the ``input_type`` for an :class:`Option` class.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Parameters
|
||||
----------
|
||||
thread_type: Literal["public", "private", "news"]
|
||||
The thread type to expect for this options input.
|
||||
"""
|
||||
|
||||
def __init__(self, thread_type: Literal["public", "private", "news"]):
|
||||
type_map = {
|
||||
"public": ChannelType.public_thread,
|
||||
"private": ChannelType.private_thread,
|
||||
"news": ChannelType.news_thread,
|
||||
}
|
||||
self._type = type_map[thread_type]
|
||||
|
||||
|
||||
class Option:
|
||||
"""Represents a selectable option for a slash command.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
input_type: Union[Type[:class:`str`], Type[:class:`bool`], Type[:class:`int`], Type[:class:`float`], Type[:class:`.abc.GuildChannel`], Type[:class:`Thread`], Type[:class:`Member`], Type[:class:`User`], Type[:class:`Attachment`], Type[:class:`Role`], Type[:class:`.abc.Mentionable`], :class:`SlashCommandOptionType`, Type[:class:`.ext.commands.Converter`], Type[:class:`enums.Enum`], Type[:class:`Enum`]]
|
||||
The type of input that is expected for this option. This can be a :class:`SlashCommandOptionType`,
|
||||
an associated class, a channel type, a :class:`Converter`, a converter class or an :class:`enum.Enum`.
|
||||
If a :class:`enum.Enum` is used and it has up to 25 values, :attr:`choices` will be automatically filled. If the :class:`enum.Enum` has more than 25 values, :attr:`autocomplete` will be implemented with :func:`discord.utils.basic_autocomplete` instead.
|
||||
name: :class:`str`
|
||||
The name of this option visible in the UI.
|
||||
Inherits from the variable name if not provided as a parameter.
|
||||
description: Optional[:class:`str`]
|
||||
The description of this option.
|
||||
Must be 100 characters or fewer. If :attr:`input_type` is a :class:`enum.Enum` and :attr:`description` is not specified, :attr:`input_type`'s docstring will be used.
|
||||
choices: Optional[List[Union[:class:`Any`, :class:`OptionChoice`]]]
|
||||
The list of available choices for this option.
|
||||
Can be a list of values or :class:`OptionChoice` objects (which represent a name:value pair).
|
||||
If provided, the input from the user must match one of the choices in the list.
|
||||
required: Optional[:class:`bool`]
|
||||
Whether this option is required.
|
||||
default: Optional[:class:`Any`]
|
||||
The default value for this option. If provided, ``required`` will be considered ``False``.
|
||||
min_value: Optional[:class:`int`]
|
||||
The minimum value that can be entered.
|
||||
Only applies to Options with an :attr:`.input_type` of :class:`int` or :class:`float`.
|
||||
max_value: Optional[:class:`int`]
|
||||
The maximum value that can be entered.
|
||||
Only applies to Options with an :attr:`.input_type` of :class:`int` or :class:`float`.
|
||||
min_length: Optional[:class:`int`]
|
||||
The minimum length of the string that can be entered. Must be between 0 and 6000 (inclusive).
|
||||
Only applies to Options with an :attr:`input_type` of :class:`str`.
|
||||
max_length: Optional[:class:`int`]
|
||||
The maximum length of the string that can be entered. Must be between 1 and 6000 (inclusive).
|
||||
Only applies to Options with an :attr:`input_type` of :class:`str`.
|
||||
channel_types: list[:class:`discord.ChannelType`] | None
|
||||
A list of channel types that can be selected in this option.
|
||||
Only applies to Options with an :attr:`input_type` of :class:`discord.SlashCommandOptionType.channel`.
|
||||
If this argument is used, :attr:`input_type` will be ignored.
|
||||
name_localizations: Dict[:class:`str`, :class:`str`]
|
||||
The name localizations for this option. The values of this should be ``"locale": "name"``.
|
||||
See `here <https://docs.discord.com/developers/reference#locales>`_ for a list of valid locales.
|
||||
description_localizations: Dict[:class:`str`, :class:`str`]
|
||||
The description localizations for this option. The values of this should be ``"locale": "description"``.
|
||||
See `here <https://docs.discord.com/developers/reference#locales>`_ for a list of valid locales.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Basic usage: ::
|
||||
|
||||
@bot.slash_command(guild_ids=[...])
|
||||
async def hello(
|
||||
ctx: discord.ApplicationContext,
|
||||
name: Option(str, "Enter your name"),
|
||||
age: Option(int, "Enter your age", min_value=1, max_value=99, default=18)
|
||||
# passing the default value makes an argument optional
|
||||
# you also can create optional argument using:
|
||||
# age: Option(int, "Enter your age") = 18
|
||||
):
|
||||
await ctx.respond(f"Hello! Your name is {name} and you are {age} years old.")
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
|
||||
input_type: SlashCommandOptionType
|
||||
converter: Converter | type[Converter] | None = None
|
||||
|
||||
def __init__(
|
||||
self, input_type: InputType = str, /, description: str | None = None, **kwargs
|
||||
) -> None:
|
||||
self.name: str | None = kwargs.pop("name", None)
|
||||
if self.name is not None:
|
||||
self.name = str(self.name)
|
||||
self._parameter_name = self.name # default
|
||||
input_type = self._parse_type_alias(input_type)
|
||||
input_type = self._strip_none_type(input_type)
|
||||
self._raw_type: InputType | tuple = input_type
|
||||
|
||||
enum_choices = []
|
||||
input_type_is_class = isinstance(input_type, type)
|
||||
if input_type_is_class and issubclass(input_type, (Enum, DiscordEnum)):
|
||||
if description is None and input_type.__doc__ is not None:
|
||||
description = inspect.cleandoc(input_type.__doc__)
|
||||
if description and len(description) > 100:
|
||||
description = description[:97] + "..."
|
||||
_log.warning(
|
||||
"Option %s's description was truncated due to Enum %s's docstring exceeding 100 characters.",
|
||||
self.name,
|
||||
input_type,
|
||||
)
|
||||
enum_choices = [OptionChoice(e.name, e.value) for e in input_type]
|
||||
value_class = enum_choices[0].value.__class__
|
||||
if value_class in SlashCommandOptionType.__members__ and all(
|
||||
isinstance(elem.value, value_class) for elem in enum_choices
|
||||
):
|
||||
input_type = SlashCommandOptionType.from_datatype(
|
||||
enum_choices[0].value.__class__
|
||||
)
|
||||
else:
|
||||
enum_choices = [OptionChoice(e.name, str(e.value)) for e in input_type]
|
||||
input_type = SlashCommandOptionType.string
|
||||
|
||||
self.description = description or "No description provided"
|
||||
self.channel_types: list[ChannelType] = kwargs.pop("channel_types", [])
|
||||
|
||||
if self.channel_types:
|
||||
self.input_type = SlashCommandOptionType.channel
|
||||
elif isinstance(input_type, SlashCommandOptionType):
|
||||
self.input_type = input_type
|
||||
else:
|
||||
from ..ext.commands import Converter
|
||||
|
||||
if isinstance(input_type, tuple) and any(
|
||||
issubclass(op, ApplicationContext) for op in input_type
|
||||
):
|
||||
input_type = next(
|
||||
op for op in input_type if issubclass(op, ApplicationContext)
|
||||
)
|
||||
|
||||
if (
|
||||
isinstance(input_type, Converter)
|
||||
or input_type_is_class
|
||||
and issubclass(input_type, Converter)
|
||||
):
|
||||
self.converter = input_type
|
||||
self._raw_type = str
|
||||
self.input_type = SlashCommandOptionType.string
|
||||
else:
|
||||
try:
|
||||
self.input_type = SlashCommandOptionType.from_datatype(input_type)
|
||||
except TypeError as exc:
|
||||
from ..ext.commands.converter import CONVERTER_MAPPING
|
||||
|
||||
if input_type not in CONVERTER_MAPPING:
|
||||
raise exc
|
||||
self.converter = CONVERTER_MAPPING[input_type]
|
||||
self._raw_type = str
|
||||
self.input_type = SlashCommandOptionType.string
|
||||
else:
|
||||
if self.input_type == SlashCommandOptionType.channel:
|
||||
if not isinstance(self._raw_type, tuple):
|
||||
if hasattr(input_type, "__args__"):
|
||||
self._raw_type = input_type.__args__ # type: ignore # Union.__args__
|
||||
else:
|
||||
self._raw_type = (input_type,)
|
||||
if not self.channel_types:
|
||||
self.channel_types = [
|
||||
CHANNEL_TYPE_MAP[t]
|
||||
for t in self._raw_type
|
||||
if t is not GuildChannel
|
||||
]
|
||||
self.required: bool = (
|
||||
kwargs.pop("required", True) if "default" not in kwargs else False
|
||||
)
|
||||
self.default = kwargs.pop("default", None)
|
||||
|
||||
self._autocomplete: AutocompleteFunction | None = None
|
||||
self._autocomplete_is_instance_method: bool = False
|
||||
self.autocomplete = kwargs.pop("autocomplete", None)
|
||||
if len(enum_choices) > 25:
|
||||
self.choices: list[OptionChoice] = []
|
||||
for e in enum_choices:
|
||||
e.value = str(e.value)
|
||||
self.autocomplete = basic_autocomplete(enum_choices)
|
||||
self.input_type = SlashCommandOptionType.string
|
||||
else:
|
||||
self.choices: list[OptionChoice] = enum_choices or [
|
||||
o if isinstance(o, OptionChoice) else OptionChoice(o)
|
||||
for o in kwargs.pop("choices", [])
|
||||
]
|
||||
|
||||
if self.input_type == SlashCommandOptionType.integer:
|
||||
minmax_types = (int, type(None))
|
||||
minmax_typehint = Optional[int]
|
||||
elif self.input_type == SlashCommandOptionType.number:
|
||||
minmax_types = (int, float, type(None))
|
||||
minmax_typehint = Optional[Union[int, float]]
|
||||
else:
|
||||
minmax_types = (type(None),)
|
||||
minmax_typehint = type(None)
|
||||
|
||||
if self.input_type == SlashCommandOptionType.string:
|
||||
minmax_length_types = (int, type(None))
|
||||
minmax_length_typehint = Optional[int]
|
||||
else:
|
||||
minmax_length_types = (type(None),)
|
||||
minmax_length_typehint = type(None)
|
||||
|
||||
self.min_value: int | float | None = kwargs.pop("min_value", None)
|
||||
self.max_value: int | float | None = kwargs.pop("max_value", None)
|
||||
self.min_length: int | None = kwargs.pop("min_length", None)
|
||||
self.max_length: int | None = kwargs.pop("max_length", None)
|
||||
|
||||
if (
|
||||
self.input_type != SlashCommandOptionType.integer
|
||||
and self.input_type != SlashCommandOptionType.number
|
||||
and (self.min_value or self.max_value)
|
||||
):
|
||||
raise AttributeError(
|
||||
"Option does not take min_value or max_value if not of type "
|
||||
"SlashCommandOptionType.integer or SlashCommandOptionType.number"
|
||||
)
|
||||
if self.input_type != SlashCommandOptionType.string and (
|
||||
self.min_length or self.max_length
|
||||
):
|
||||
raise AttributeError(
|
||||
"Option does not take min_length or max_length if not of type str"
|
||||
)
|
||||
|
||||
if self.min_value is not None and not isinstance(self.min_value, minmax_types):
|
||||
raise TypeError(
|
||||
f"Expected {minmax_typehint} for min_value, got"
|
||||
f' "{type(self.min_value).__name__}"'
|
||||
)
|
||||
if self.max_value is not None and not isinstance(self.max_value, minmax_types):
|
||||
raise TypeError(
|
||||
f"Expected {minmax_typehint} for max_value, got"
|
||||
f' "{type(self.max_value).__name__}"'
|
||||
)
|
||||
|
||||
if self.min_length is not None:
|
||||
if not isinstance(self.min_length, minmax_length_types):
|
||||
raise TypeError(
|
||||
f"Expected {minmax_length_typehint} for min_length,"
|
||||
f' got "{type(self.min_length).__name__}"'
|
||||
)
|
||||
if self.min_length < 0 or self.min_length > 6000:
|
||||
raise AttributeError(
|
||||
"min_length must be between 0 and 6000 (inclusive)"
|
||||
)
|
||||
if self.max_length is not None:
|
||||
if not isinstance(self.max_length, minmax_length_types):
|
||||
raise TypeError(
|
||||
f"Expected {minmax_length_typehint} for max_length,"
|
||||
f' got "{type(self.max_length).__name__}"'
|
||||
)
|
||||
if self.max_length < 1 or self.max_length > 6000:
|
||||
raise AttributeError("max_length must between 1 and 6000 (inclusive)")
|
||||
|
||||
self.name_localizations = kwargs.pop("name_localizations", MISSING)
|
||||
self.description_localizations = kwargs.pop(
|
||||
"description_localizations", MISSING
|
||||
)
|
||||
|
||||
if input_type is None:
|
||||
raise TypeError("input_type cannot be NoneType.")
|
||||
|
||||
@staticmethod
|
||||
def _parse_type_alias(input_type: InputType) -> InputType:
|
||||
if isinstance(input_type, TypeAliasType):
|
||||
return input_type.__value__
|
||||
return input_type
|
||||
|
||||
@staticmethod
|
||||
def _strip_none_type(input_type):
|
||||
if isinstance(input_type, SlashCommandOptionType):
|
||||
return input_type
|
||||
|
||||
if input_type is type(None):
|
||||
raise TypeError("Option type cannot be only NoneType")
|
||||
|
||||
args = ()
|
||||
if isinstance(input_type, types.UnionType):
|
||||
args = get_args(input_type)
|
||||
elif getattr(input_type, "__origin__", None) is Union:
|
||||
args = get_args(input_type)
|
||||
elif isinstance(input_type, tuple):
|
||||
args = input_type
|
||||
|
||||
if args:
|
||||
filtered = tuple(t for t in args if t is not type(None))
|
||||
if not filtered:
|
||||
raise TypeError("Option type cannot be only NoneType")
|
||||
if len(filtered) == 1:
|
||||
return filtered[0]
|
||||
|
||||
return filtered
|
||||
|
||||
return input_type
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
as_dict = {
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"type": self.input_type.value,
|
||||
"required": self.required,
|
||||
"choices": [c.to_dict() for c in self.choices],
|
||||
"autocomplete": bool(self.autocomplete),
|
||||
}
|
||||
if self.name_localizations is not MISSING:
|
||||
as_dict["name_localizations"] = self.name_localizations
|
||||
if self.description_localizations is not MISSING:
|
||||
as_dict["description_localizations"] = self.description_localizations
|
||||
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
|
||||
if self.min_length is not None:
|
||||
as_dict["min_length"] = self.min_length
|
||||
if self.max_length is not None:
|
||||
as_dict["max_length"] = self.max_length
|
||||
|
||||
return as_dict
|
||||
|
||||
def __repr__(self):
|
||||
return f"<discord.commands.{self.__class__.__name__} name={self.name}>"
|
||||
|
||||
@property
|
||||
def autocomplete(self) -> AutocompleteFunction | None:
|
||||
"""
|
||||
The autocomplete handler for the option. Accepts a callable (sync or async)
|
||||
that takes a single required argument of :class:`AutocompleteContext` or two arguments
|
||||
of :class:`discord.Cog` (being the command's cog) and :class:`AutocompleteContext`.
|
||||
The callable must return an iterable of :class:`str` or :class:`OptionChoice`.
|
||||
Alternatively, :func:`discord.utils.basic_autocomplete` may be used in place of the callable.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Optional[AutocompleteFunction]
|
||||
|
||||
.. versionchanged:: 2.7
|
||||
|
||||
.. note::
|
||||
Does not validate the input value against the autocomplete results.
|
||||
"""
|
||||
return self._autocomplete
|
||||
|
||||
@autocomplete.setter
|
||||
def autocomplete(self, value: AutocompleteFunction | None) -> None:
|
||||
self._autocomplete = value
|
||||
# this is done here so it does not have to be computed every time the autocomplete is invoked
|
||||
if self._autocomplete is not None:
|
||||
self._autocomplete_is_instance_method = (
|
||||
sum(
|
||||
1
|
||||
for param in inspect.signature(
|
||||
self._autocomplete
|
||||
).parameters.values()
|
||||
if param.default == param.empty
|
||||
and param.kind not in (param.VAR_POSITIONAL, param.VAR_KEYWORD)
|
||||
)
|
||||
== 2
|
||||
)
|
||||
|
||||
|
||||
class OptionChoice:
|
||||
"""
|
||||
Represents a name:value pairing for a selected :class:`.Option`.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Attributes
|
||||
----------
|
||||
name: :class:`str`
|
||||
The name of the choice. Shown in the UI when selecting an option.
|
||||
value: Optional[Union[:class:`str`, :class:`int`, :class:`float`]]
|
||||
The value of the choice. If not provided, will use the value of ``name``.
|
||||
name_localizations: Dict[:class:`str`, :class:`str`]
|
||||
The name localizations for this choice. The values of this should be ``"locale": "name"``.
|
||||
See `here <https://docs.discord.com/developers/reference#locales>`_ for a list of valid locales.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
value: str | int | float | None = None,
|
||||
name_localizations: dict[str, str] = MISSING,
|
||||
):
|
||||
self.name = str(name)
|
||||
self.value = value if value is not None else name
|
||||
self.name_localizations = name_localizations
|
||||
|
||||
def to_dict(self) -> dict[str, str | int | float]:
|
||||
as_dict = {"name": self.name, "value": self.value}
|
||||
if self.name_localizations is not MISSING:
|
||||
as_dict["name_localizations"] = self.name_localizations
|
||||
|
||||
return as_dict
|
||||
|
||||
|
||||
def option(name, input_type=None, **kwargs):
|
||||
"""A decorator that can be used instead of typehinting :class:`.Option`.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Attributes
|
||||
----------
|
||||
parameter_name: :class:`str`
|
||||
The name of the target function parameter this option is mapped to.
|
||||
This allows you to have a separate UI ``name`` and parameter name.
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
resolved_name = kwargs.pop("parameter_name", None) or name
|
||||
itype = (
|
||||
kwargs.pop("type", None)
|
||||
or input_type
|
||||
or func.__annotations__.get(resolved_name, str)
|
||||
)
|
||||
func.__annotations__[resolved_name] = Option(itype, name=name, **kwargs)
|
||||
return func
|
||||
|
||||
return decorator
|
||||
@@ -0,0 +1,139 @@
|
||||
"""
|
||||
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 Callable
|
||||
|
||||
from ..enums import InteractionContextType
|
||||
from ..permissions import Permissions
|
||||
from .core import ApplicationCommand
|
||||
|
||||
__all__ = ("default_permissions", "guild_only", "is_nsfw")
|
||||
|
||||
|
||||
def default_permissions(**perms: bool) -> Callable:
|
||||
"""A decorator that limits the usage of an application command to members with certain
|
||||
permissions.
|
||||
|
||||
The permissions passed in must be exactly like the properties shown under
|
||||
:class:`.discord.Permissions`.
|
||||
|
||||
.. note::
|
||||
These permissions can be updated by server administrators per-guild. As such, these are only "defaults", as the
|
||||
name suggests. If you want to make sure that a user **always** has the specified permissions regardless, you
|
||||
should use an internal check such as :func:`~.ext.commands.has_permissions`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
**perms: Dict[:class:`str`, :class:`bool`]
|
||||
An argument list of permissions to check for.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
from discord import default_permissions
|
||||
|
||||
@bot.slash_command()
|
||||
@default_permissions(manage_messages=True)
|
||||
async def test(ctx):
|
||||
await ctx.respond('You can manage messages.')
|
||||
"""
|
||||
|
||||
invalid = set(perms) - set(Permissions.VALID_FLAGS)
|
||||
if invalid:
|
||||
raise TypeError(f"Invalid permission(s): {', '.join(invalid)}")
|
||||
|
||||
def inner(command: Callable):
|
||||
if isinstance(command, ApplicationCommand):
|
||||
if command.parent is not None:
|
||||
raise RuntimeError(
|
||||
"Permission restrictions can only be set on top-level commands"
|
||||
)
|
||||
command.default_member_permissions = Permissions(**perms)
|
||||
else:
|
||||
command.__default_member_permissions__ = Permissions(**perms)
|
||||
return command
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
def guild_only() -> Callable:
|
||||
"""A decorator that limits the usage of an application command to guild contexts.
|
||||
The command won't be able to be used in private message channels.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
from discord import guild_only
|
||||
|
||||
@bot.slash_command()
|
||||
@guild_only()
|
||||
async def test(ctx):
|
||||
await ctx.respond("You're in a guild.")
|
||||
"""
|
||||
|
||||
def inner(command: Callable):
|
||||
if isinstance(command, ApplicationCommand):
|
||||
command.contexts = {InteractionContextType.guild}
|
||||
else:
|
||||
command.__contexts__ = {InteractionContextType.guild}
|
||||
|
||||
return command
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
def is_nsfw() -> Callable:
|
||||
"""A decorator that limits the usage of an application command to 18+ channels and users.
|
||||
In guilds, the command will only be able to be used in channels marked as NSFW.
|
||||
In DMs, users must have opted into age-restricted commands via privacy settings.
|
||||
|
||||
Note that apps intending to be listed in the App Directory cannot have NSFW commands.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
from discord import is_nsfw
|
||||
|
||||
@bot.slash_command()
|
||||
@is_nsfw()
|
||||
async def test(ctx):
|
||||
await ctx.respond("This command is age restricted.")
|
||||
"""
|
||||
|
||||
def inner(command: Callable):
|
||||
if isinstance(command, ApplicationCommand):
|
||||
command.nsfw = True
|
||||
else:
|
||||
command.__nsfw__ = True
|
||||
|
||||
return command
|
||||
|
||||
return inner
|
||||
Reference in New Issue
Block a user