feat(interactions): slashcommands base

This commit is contained in:
grey-cat-1908 2022-07-30 15:49:46 +03:00
parent b4128f8c80
commit 9b1d710bce
5 changed files with 228 additions and 148 deletions

View file

@ -70,9 +70,9 @@ class HTTPClient:
*,
_ttl: int = None,
headers: Optional[Dict[str, Any]] = None,
params: Optional[Dict] = None,
params: Optional[Dict[str, Any]] = None,
**kwargs,
) -> Optional[Dict]:
) -> Optional[Dict[str, Any]]:
"""Send an API request to the Discord API."""
ttl = _ttl or self.max_ttl
@ -113,7 +113,7 @@ class HTTPClient:
*,
_ttl: int = None,
**kwargs,
) -> Optional[Dict]:
) -> Optional[Dict[str, Any]]:
"""Handle responses from the Discord API."""
_logger.debug(f"Received response for the {endpoint} ({await res.text()})")
@ -165,7 +165,7 @@ class HTTPClient:
return await self.__send(method, endpoint, _ttl=_ttl - 1, **kwargs)
async def get(self, route: str, *, params: Optional[Dict] = None) -> Optional[Dict]:
async def get(self, route: str, *, params: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]:
"""|coro|
Sends a GET request to a Discord REST API endpoint.
@ -187,22 +187,22 @@ class HTTPClient:
self,
route: str,
*,
headers: dict = None,
json: Optional[Dict] = None,
data=None,
) -> Optional[Dict]:
headers: Dict[str, Any] = None,
json: Optional[Dict[str, Any]] = None,
data: Optional[Dict[str, Any]] = None,
) -> Optional[Dict[str, Any]]:
"""|coro|
Sends a POST request to a Discord REST API endpoint.
Parameters
----------
route : :class:`str`
route: :class:`str`
The endpoint to send the request to.
json : Dict
json: Dict[str, Any]
Json data to post
data : Any
data: Any
Data to post
headers : :class:`dict`
headers: Dict[str, Any]
Custom request headers
Returns
@ -212,7 +212,7 @@ class HTTPClient:
"""
return await self.__send("POST", route, json=json, data=data, headers=headers)
async def delete(self, route: str, *, headers: dict = None) -> Optional[Dict]:
async def delete(self, route: str, *, headers: Dict[str, Any] = None) -> Optional[Dict[str, Any]]:
"""|coro|
Sends a DELETE request to a Discord REST API endpoint.
@ -234,10 +234,10 @@ class HTTPClient:
self,
route: str,
*,
headers: dict = None,
json: Optional[Dict] = None,
data=None,
) -> Optional[Dict]:
headers: Dict[str, Any] = None,
json: Optional[Dict[str, Any]] = None,
data: Optional[Dict[str, Any]] = None,
) -> Optional[Dict[str, Any]]:
"""|coro|
Sends a PATCH request to a Discord REST API endpoint.
@ -260,8 +260,8 @@ class HTTPClient:
return await self.__send("PATCH", route, json=json, data=data, headers=headers)
async def put(
self, route: str, *, headers: dict = None, data: Optional[Dict] = None
) -> Optional[Dict]:
self, route: str, *, headers: Dict[str, Any] = None, data: Optional[Dict[str, Any]] = None
) -> Optional[Dict[str, Any]]:
"""|coro|
Sends a PUT request to a Discord REST API endpoint.

View file

@ -1,7 +1,7 @@
# Copyright MelisaDev 2022 - Present
# Full MIT License can be found in `LICENSE.txt` at the project root.
from .commands import ApplicationCommandTypes
from .commands import ApplicationCommandType
__all__ = "ApplicationCommandTypes"

View file

@ -6,14 +6,15 @@ from __future__ import annotations
from enum import IntEnum
from typing import Optional, Dict, Union, Any, List
from .i18n import LocalizedField
from ..guild.channel import ChannelType
from ...utils.snowflake import Snowflake
from ...utils.conversion import try_enum
from ...utils.api_model import APIModelBase
class ApplicationCommandTypes(IntEnum):
"""Application Command Types
class ApplicationCommandType(IntEnum):
"""Application Command Type
Attributes
----------
@ -33,8 +34,8 @@ class ApplicationCommandTypes(IntEnum):
return self.value
class ApplicationCommandOptionTypes(IntEnum):
"""Application Command Option Types
class SlashCommandOptionType(IntEnum):
"""Application Command Option Type
Attributes
----------
@ -78,43 +79,27 @@ class ApplicationCommandOptionTypes(IntEnum):
return self.value
class ApplicationCommand(APIModelBase):
"""Application Command
class PartialApplicationCommand(APIModelBase):
"""Represents Partial Application Command
Attributes
----------
id: :class:`~melisa.utils.snowflake.Snowflake`
Unique ID of command
type: Optional[:class:`~melisa.interactions.commands.ApplicationCommandTypes`]
type: Optional[:class:`~melisa.interactions.commands.SlashCommandType`]
Type of command, defaults to ``1``
application_id: :class:`~melisa.utils.snowflake.Snowflake`
ID of the parent application
guild_id: Optional[:class:`~melisa.utils.snowflake.Snowflake`]
guild id of the command, if not global
name: str
Name of command, 1-32 characters
name_localizations: Optional[Dict[str, str]]
Localization dictionary for ``name`` field.
Values follow the same restrictions as ``name``
description: str
Description for ``CHAT_INPUT`` commands, 1-100 characters.
Empty string for ``USER`` and ``MESSAGE`` commands
description_localizations: Optional[Dict[str, str]]
Localization dictionary for ``description`` field.
Values follow the same restrictions as ``description``
options: Optional[List[:class:`~melisa.models.interactions.commands.ApplicationCommandOption`]]
Parameters for the command, max of 25.
Only available for ``CHAT_INPUT`` command type.
default_member_permissions: Optional[str]
name: :class:`~melisa.models.interactions.i18n.LocalizedField`
Name of the command
default_member_permissions: str
Set of permissions represented as a bit set
dm_permission: Optional[bool]
dm_permission: bool
Indicates whether the command is available
in DMs with the app, only for globally-scoped commands.
By default, commands are visible.
default_permission: Optional[bool]
Not recommended for use as field will soon be deprecated.
Indicates whether the command is enabled by default
when the app is added to a guild, defaults to true
version: :class:`~melisa.utils.snowflake.Snowflake`
Autoincrementing version identifier updated during substantial record changes
"""
@ -122,17 +107,12 @@ class ApplicationCommand(APIModelBase):
# ToDo: Better Permissions
id: Snowflake = None
type: Optional[ApplicationCommandTypes] = 1
type: Optional[ApplicationCommandType] = 1
application_id: Snowflake = None
guild_id: Optional[Snowflake] = None
name: str = None
name_localizations: Optional[Dict[str, str]] = None
description: str = None
description_localizations: Optional[Dict[str, str]] = None
options: Optional[List[ApplicationCommandOption]] = None
default_member_permissions: Optional[str] = None
dm_permission: Optional[bool] = True
default_permission: Optional[bool] = True
name: LocalizedField = None
default_member_permissions: str = None
dm_permission: bool = True
version: Snowflake = None
@classmethod
@ -142,32 +122,76 @@ class ApplicationCommand(APIModelBase):
Parameters
----------
data: :class:`dict`
The dictionary to convert into a ApplicationCommand.
The dictionary to convert into a PartialApplicationCommand.
"""
self: ApplicationCommand = super().__new__(cls)
self: PartialApplicationCommand = super().__new__(cls)
self.id = Snowflake(data.get("id", 0))
self.type = data.get("type", 1)
self.type = try_enum(ApplicationCommandType, data.get("type", 1))
self.application_id = Snowflake(data.get("application_id"))
self.guild_id = (
Snowflake(data["guild_id"]) if data.get("guild_id") is not None else None
)
name = data.get("name")
name_localizations = data.get("name_localizations")
self.name = LocalizedField(name, name_localizations)
self.default_member_permissions = data.get("default_member_permissions", "")
self.dm_permission = data.get("dm_permission", True)
self.version = Snowflake(data.get("version", 0))
return self
class SlashCommand(PartialApplicationCommand):
"""Represents SlashCommand
Attributes
----------
description: :class:`~melisa.models.interactions.i18n.LocalizedField`
Description of command
"""
description: LocalizedField = None
@classmethod
def from_dict(cls, data: Dict[str, Any]):
"""Generate a SlashCommand from the given data.
Parameters
----------
data: :class:`dict`
The dictionary to convert into a SlashCommand.
"""
self: SlashCommand = super().__new__(cls)
self.id = Snowflake(data.get("id", 0))
self.type = try_enum(ApplicationCommandType, data.get("type", 1))
self.application_id = Snowflake(data.get("application_id"))
self.guild_id = (
Snowflake(data["guild_id"]) if data.get("guild_id") is not None else None
)
self.name = data.get("name")
self.name_localizations = data.get("name_localizations")
self.description = data.get("description")
self.description_localizations = data.get("description_localizations")
description = data.get("description")
description_localizations = data.get("description_localizations")
self.description = LocalizedField(description, description_localizations)
self.options = [
ApplicationCommandOption.from_dict(x) for x in data.get("options", [])
SlashCommandOption.from_dict(x) for x in data.get("options", [])
]
self.default_member_permissions = data.get("default_member_permissions")
self.default_member_permissions = data.get("default_member_permissions", "")
self.dm_permission = data.get("dm_permission", True)
self.default_permission = data.get("default_permission", True)
self.version = Snowflake(data.get("version", 0))
return self
class ApplicationCommandOption(APIModelBase):
class SlashCommandOption(APIModelBase):
"""Application Command Option
.. warning::
@ -176,7 +200,7 @@ class ApplicationCommandOption(APIModelBase):
Attributes
----------
type: :class:`~melisa.models.interactions.commands.ApplicationCommandOptionTypes`
type: :class:`~melisa.models.interactions.commands.SlashCommandOptionType`
Type of option
name: str
1-32 character name
@ -190,10 +214,10 @@ class ApplicationCommandOption(APIModelBase):
Values follow the same restrictions as ``description``
required: Optional[bool]
If the parameter is required or optional--default false
choices: Optional[List[:class:`~melisa.models.interactions.commands.ApplicationCommandOptionChoice`]]
choices: Optional[List[:class:`~melisa.models.interactions.commands.SlashCommandOptionChoice`]]
Choices for ``STRING``, ``INTEGER``, and ``NUMBER``
types for the user to pick from, max 25
options: Optional[List[ApplicationCommandOption]]
options: Optional[List[SlashCommandOption]]
If the option is a subcommand or subcommand group type,
these nested options will be the parameters
channel_types: Optional[List[:class:`~melisa.models.guild.channel.ChannelType`]]
@ -210,14 +234,14 @@ class ApplicationCommandOption(APIModelBase):
``int``, or ``float`` type option
"""
type: ApplicationCommandOptionTypes = None
type: SlashCommandOptionType = None
name: str = None
name_localizations: Dict[str, str] = None
description: str
description_localizations: Dict[str, str] = None
required: Optional[bool] = False
choices: Optional[List[ApplicationCommandOptionChoice]] = None
options: Optional[List[ApplicationCommandOption]] = None
choices: Optional[List[SlashCommandOptionChoice]] = None
options: Optional[List[SlashCommandOption]] = None
channel_types: Optional[List[ChannelType]] = None
min_value: Optional[int, float] = None
max_value: Optional[int, float] = None
@ -225,26 +249,26 @@ class ApplicationCommandOption(APIModelBase):
@classmethod
def from_dict(cls, data: Dict[str, Any]):
"""Generate a ApplicationCommandOption from the given data.
"""Generate a SlashCommandOption from the given data.
Parameters
----------
data: :class:`dict`
The dictionary to convert into a ApplicationCommandOption.
The dictionary to convert into a SlashCommandOption.
"""
self: ApplicationCommandOption = super().__new__(cls)
self: SlashCommandOption = super().__new__(cls)
self.type = try_enum(ApplicationCommandOptionTypes, data.get("type", 0))
self.type = try_enum(SlashCommandOptionType, data.get("type", 0))
self.name = data.get("name")
self.name_localizations = data.get("name_localizations")
self.description = data.get("description")
self.description_localizations = data.get("description_localizations")
self.required = data.get("required", False)
self.choices = [
try_enum(ApplicationCommandOptionChoice, x) for x in data.get("choices", [])
try_enum(SlashCommandOptionChoice, x) for x in data.get("choices", [])
]
self.options = [
ApplicationCommandOption.from_dict(x) for x in data.get("options", [])
SlashCommandOption.from_dict(x) for x in data.get("options", [])
]
self.channel_types = [
try_enum(ChannelType, x) for x in data.get("channel_types", [])
@ -256,7 +280,7 @@ class ApplicationCommandOption(APIModelBase):
return self
class ApplicationCommandOptionChoice(APIModelBase):
class SlashCommandOptionChoice(APIModelBase):
"""Application Command Option Choice
If you specify ``choices`` for an option,
@ -283,14 +307,14 @@ class ApplicationCommandOptionChoice(APIModelBase):
@classmethod
def from_dict(cls, data: Dict[str, Any]):
"""Generate a ApplicationCommandOptionChoice from the given data.
"""Generate a SlashCommandOptionChoice from the given data.
Parameters
----------
data: :class:`dict`
The dictionary to convert into a ApplicationCommandOptionChoice.
The dictionary to convert into a SlashCommandOptionChoice.
"""
self: ApplicationCommandOptionChoice = super().__new__(cls)
self: SlashCommandOptionChoice = super().__new__(cls)
self.name = data.get("name")
self.name_localizations = data.get("name_localizations")
@ -299,8 +323,8 @@ class ApplicationCommandOptionChoice(APIModelBase):
return self
class ApplicationCommandInteractionDataOption(APIModelBase):
"""Application Command Interaction Data Option
class SlashCommandInteractionDataOption(APIModelBase):
"""Slash Command Interaction Data Option
All options have names, and an option can either be a parameter
and input value--in which case ``value`` will be set--or it
@ -313,40 +337,53 @@ class ApplicationCommandInteractionDataOption(APIModelBase):
----------
name: :class:`str`
Name of the parameter
type: :class:`~melisa.models.interactions.commands.ApplicationCommandOptionTypes`
Value of :class:`~melisa.models.interactions.commands.ApplicationCommandOptionTypes`
type: :class:`~melisa.models.interactions.commands.SlashCommandOptionType`
Value of :class:`~melisa.models.interactions.commands.SlashCommandOptionType`
value: Optional[Union[str, int, float]]
Value of the option resulting from user input
options: Optional[List[ApplicationCommandInteractionDataOption]]
options: Optional[List[SlashCommandInteractionDataOption]]
Present if this option is a group or subcommand
focused: Optional[bool]
``true`` if this option is the currently focused option for autocomplete
"""
name: str = None
type: ApplicationCommandOptionTypes = None
type: SlashCommandOptionType = None
value: Optional[Union[str, int, float]] = None
options: Optional[List[ApplicationCommandInteractionDataOption]] = None
focused: Optional[bool] = None
options: Optional[List[SlashCommandInteractionDataOption]] = None
focused: bool = None
@classmethod
def from_dict(cls, data: Dict[str, Any]):
"""Generate a ApplicationCommandInteractionDataOption from the given data.
"""Generate a SlashCommandInteractionDataOption from the given data.
Parameters
----------
data: :class:`dict`
The dictionary to convert into a ApplicationCommandInteractionDataOption.
The dictionary to convert into a SlashCommandInteractionDataOption.
"""
self: ApplicationCommandInteractionDataOption = super().__new__(cls)
self: SlashCommandInteractionDataOption = super().__new__(cls)
self.name = data.get("name")
self.type = try_enum(ApplicationCommandOptionTypes, data.get("type", 0))
self.type = try_enum(SlashCommandOptionType, data.get("type", 0))
self.value = data.get("value")
self.options = [
ApplicationCommandInteractionDataOption.from_dict(x)
SlashCommandInteractionDataOption.from_dict(x)
for x in data.get("options", [])
]
self.focused = data.get("focused")
self.focused = data.get("focused", False)
return self
# noinspection PyTypeChecker
command_types_for_converting: Dict[ApplicationCommandType, PartialApplicationCommand] = {
ApplicationCommandType.CHAT_INPUT: SlashCommand
}
def _choose_command_type(data):
data.update({"type": ApplicationCommandType(data.pop("type"))})
command_cls = command_types_for_converting.get(data["type"], PartialApplicationCommand)
return command_cls.from_dict(data)

View file

@ -0,0 +1,57 @@
# Copyright MelisaDev 2022 - Present
# Full MIT License can be found in `LICENSE.txt` at the project root.
from __future__ import annotations
from typing import Dict
class LocalizedField:
original: str
localizations: str
def __init__(
self,
original: str = None,
localizations: Dict[str, str] = None,
):
self.original: str = original
self.localizations: Dict[str, str] = localizations
def insert(self, locale: str, value: str) -> LocalizedField:
self.localizations[locale] = value
return self
def remove(self, locale: str) -> LocalizedField:
self.localizations.pop(locale, None)
return self
def __repr__(self):
return f"<LocalizedField original={self.original} localizations={self.localizations}>"
def __eq__(self, other):
return self.original == other.original and self.localizations == other.localizations
def __hash__(self):
return hash((self.original, self.localizations))
def __getitem__(self, key):
return self.localizations[key]
def __setitem__(self, key, value):
self.localizations[key] = value
def __delitem__(self, key):
self.localizations.pop(key, None)
def __contains__(self, key):
return key in self.localizations
def __iter__(self):
return iter(self.localizations)
def __len__(self):
return len(self.localizations)
def __str__(self):
return self.original

View file

@ -6,8 +6,10 @@ from typing import Union, Optional, List, Dict, Any, AsyncIterator
from aiohttp import FormData
from .models.interactions import ApplicationCommandTypes
from .models.interactions.commands import ApplicationCommandOption, ApplicationCommand
from .models.interactions import ApplicationCommandType
from .models.interactions.commands import SlashCommandOption, SlashCommand, PartialApplicationCommand, \
_choose_command_type
from .models.interactions.i18n import LocalizedField
from .models.message import Embed, File, AllowedMentions, Message
from .exceptions import EmbedFieldError
from .core.http import HTTPClient
@ -856,7 +858,7 @@ class RESTApp:
application_id: Union[int, str, Snowflake],
*,
with_localizations: Optional[bool] = False,
) -> List[ApplicationCommand]:
) -> List[PartialApplicationCommand]:
"""|coro|
[**REST API**] Fetch all of the global commands for your application.
@ -882,7 +884,7 @@ class RESTApp:
"""
return [
ApplicationCommand.from_dict(x)
_choose_command_type(x)
for x in await self._http.get(
f"/applications/{application_id}/commands?with_localizations={with_localizations}"
)
@ -891,17 +893,15 @@ class RESTApp:
async def create_global_application_command(
self,
application_id: Union[int, str, Snowflake],
command_type: ApplicationCommandTypes,
name: str,
description: str = None,
command_type: ApplicationCommandType,
name: LocalizedField,
description: LocalizedField = None,
*,
name_localizations: Optional[Dict[str, str]] = None,
description_localizations: Optional[Dict[str, str]] = None,
options: Optional[List[ApplicationCommandOption]] = None,
options: Optional[List[SlashCommandOption]] = None,
default_member_permissions: Optional[str] = None,
dm_permission: Optional[bool] = None,
default_permission: Optional[bool] = None,
) -> ApplicationCommand:
) -> PartialApplicationCommand:
"""|coro|
[**REST API**] Create a new global command.
@ -910,20 +910,14 @@ class RESTApp:
----------
application_id: :class:`~melisa.utils.snowflake.Snowflake`
ID of the parent application
command_type: Optional[:class:`~melisa.interactions.commands.ApplicationCommandTypes`]
command_type: Optional[:class:`~melisa.interactions.commands.ApplicationCommandType`]
Type of command, defaults to ``1``
name: str
name: :class:`~melisa.models.interactions.i18n.LocalizedField`
Name of command, 1-32 characters
description: str
description: Optional[:class:`~melisa.models.interactions.i18n.LocalizedField`]
Description for ``CHAT_INPUT`` commands, 1-100 characters.
Empty string for ``USER`` and ``MESSAGE`` commands
name_localizations: Optional[Dict[str, str]]
Localization dictionary for ``name`` field.
Values follow the same restrictions as ``name``
description_localizations: Optional[Dict[str, str]]
Localization dictionary for ``description`` field.
Values follow the same restrictions as ``description``
options: Optional[List[:class:`~melisa.models.interactions.commands.ApplicationCommandOption`]]
options: Optional[List[:class:`~melisa.models.interactions.commands.SlashCommandOption`]]
Parameters for the command, max of 25.
Only available for ``CHAT_INPUT`` command type.
default_member_permissions: Optional[str]
@ -948,16 +942,16 @@ class RESTApp:
"""
data = {
"name": name,
"description": description,
"name": name.original,
"description": description.original,
"type": int(command_type),
}
if name_localizations is not None:
data["name_localizations"] = name_localizations
if name.localizations is not None:
data["name_localizations"] = name.localizations
if description_localizations is not None:
data["description_localizations"] = description_localizations
if description.localizations is not None:
data["description_localizations"] = description.localizations
if default_member_permissions is not None:
data["default_member_permissions"] = default_member_permissions
@ -971,7 +965,7 @@ class RESTApp:
if default_permission is not None:
data["default_permission"] = default_permission
return ApplicationCommand.from_dict(
return _choose_command_type(
await self._http.post(f"/applications/{application_id}/commands", json=data)
)
@ -979,7 +973,7 @@ class RESTApp:
self,
application_id: Union[int, str, Snowflake],
command_id: Union[int, str, Snowflake],
) -> ApplicationCommand:
) -> PartialApplicationCommand:
"""|coro|
[**REST API**] Fetch a global command for your application.
@ -1001,7 +995,7 @@ class RESTApp:
You provided a wrong arguments
"""
return ApplicationCommand.from_dict(
return _choose_command_type(
await self._http.get(
f"/applications/{application_id}/commands/{command_id}"
)
@ -1012,15 +1006,13 @@ class RESTApp:
application_id: Union[int, str, Snowflake],
command_id: Union[int, str, Snowflake],
*,
name: Optional[str] = None,
description: Optional[str] = None,
name_localizations: Optional[Dict[str, str]] = None,
description_localizations: Optional[Dict[str, str]] = None,
options: Optional[List[ApplicationCommandOption]] = None,
name: Optional[LocalizedField] = None,
description: Optional[LocalizedField] = None,
options: Optional[List[SlashCommandOption]] = None,
default_member_permissions: Optional[str] = None,
dm_permission: Optional[bool] = None,
default_permission: Optional[bool] = None,
) -> ApplicationCommand:
) -> PartialApplicationCommand:
"""|coro|
All parameters are optional, but any parameters
@ -1034,17 +1026,11 @@ class RESTApp:
ID of the parent application
command_id: Optional[bool]
ID of command to edit.
name: Optional[str]
name: Optional[:class:`~melisa.models.interactions.i18n.LocalizedField`]
Name of command, 1-32 characters
description: Optional[str]
description: Optional[:class:`~melisa.models.interactions.i18n.LocalizedField`]
Description for ``CHAT_INPUT`` commands, 1-100 characters.
Empty string for ``USER`` and ``MESSAGE`` commands
name_localizations: Optional[Dict[str, str]]
Localization dictionary for ``name`` field.
Values follow the same restrictions as ``name``
description_localizations: Optional[Dict[str, str]]
Localization dictionary for ``description`` field.
Values follow the same restrictions as ``description``
options: Optional[List[:class:`~melisa.models.interactions.commands.ApplicationCommandOption`]]
Parameters for the command, max of 25.
Only available for ``CHAT_INPUT`` command type.
@ -1071,17 +1057,17 @@ class RESTApp:
data = {}
if name is not None:
data["name"] = name
if name.original is not None:
data["name"] = name.original
if description is not None:
data["description"] = description
if description.original is not None:
data["description"] = description.original
if name_localizations is not None:
data["name_localizations"] = name_localizations
if name.localizations is not None:
data["name_localizations"] = name.localizations
if description_localizations is not None:
data["description_localizations"] = description_localizations
if description.localizations is not None:
data["description_localizations"] = description.localizations
if default_member_permissions is not None:
data["default_member_permissions"] = default_member_permissions
@ -1095,7 +1081,7 @@ class RESTApp:
if default_permission is not None:
data["default_permission"] = default_permission
return ApplicationCommand.from_dict(
return _choose_command_type(
await self._http.patch(
f"/applications/{application_id}/commands/{command_id}", json=data
)