Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 56 additions & 44 deletions discord/app/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,33 +27,35 @@
import asyncio
import inspect
from collections import OrderedDict
from typing import Callable, Dict, List, Optional, Union

from ..enums import SlashCommandOptionType
from .context import InteractionContext
from ..interactions import Interaction
from ..member import Member
from ..user import User
from .context import InteractionContext


class SlashCommand:
type = 1

def __new__(cls, *args, **kwargs):
def __new__(cls, *args, **kwargs) -> SlashCommand:
self = super().__new__(cls)

self.__original_kwargs__ = kwargs.copy()
return self

def __init__(self, func, *args, **kwargs):
def __init__(self, func: Callable, *args, **kwargs) -> None:
if not asyncio.iscoroutinefunction(func):
raise TypeError("Callback must be a coroutine.")
self.callback = func

self.guild_ids = kwargs.get("guild_ids", None)
self.guild_ids: Optional[List[int]] = kwargs.get("guild_ids", None)

name = kwargs.get("name") or func.__name__
if not isinstance(name, str):
raise TypeError("Name of a command must be a string.")
self.name = name
self.name: str = name

description = kwargs.get("description") or (
inspect.cleandoc(func.__doc__) if func.__doc__ is not None else None
Expand All @@ -65,7 +67,7 @@ def __init__(self, func, *args, **kwargs):

if not isinstance(description, str):
raise TypeError("Description of a command must be a string.")
self.description = description
self.description: str = description

options = OrderedDict(inspect.signature(func).parameters)
options.pop(list(options)[0])
Expand All @@ -75,9 +77,9 @@ def __init__(self, func, *args, **kwargs):
if o.name is None:
o.name = a
self.options.append(o)
self.is_subcommand = False
self.is_subcommand = False

def to_dict(self):
def to_dict(self) -> Dict:
as_dict = {
"name": self.name,
"description": self.description,
Expand All @@ -88,32 +90,35 @@ def to_dict(self):

return as_dict

def __eq__(self, other):
def __eq__(self, other) -> bool:
return (
isinstance(other, SlashCommand)
and other.name == self.name
and other.description == self.description
)

async def invoke(self, interaction):
args = (o['value'] for o in interaction.data.get('options', []))
async def invoke(self, interaction) -> None:
args = (o["value"] for o in interaction.data.get("options", []))
ctx = InteractionContext(interaction)
await self.callback(ctx, *args)


class Option:
def __init__(self, input_type, /, description, **kwargs):
self.name = kwargs.pop("name", None)
def __init__(
self, input_type: SlashCommandOptionType, /, description: str, **kwargs
) -> None:
self.name: Optional[str] = kwargs.pop("name", None)
self.description = description
if not isinstance(input_type, SlashCommandOptionType):
input_type = SlashCommandOptionType.from_datatype(input_type)
self.input_type = input_type
self.required = kwargs.pop("required", True)
self.choices = [
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())
]

def to_dict(self):
def to_dict(self) -> Dict:
return {
"name": self.name,
"description": self.description,
Expand All @@ -122,20 +127,28 @@ def to_dict(self):
"choices": [c.to_dict() for c in self.choices],
}


class SubCommandGroup(Option):
type = 1
def __init__(self, name, description, guild_ids = None, parent_group=None):

def __init__(
self,
name: str,
description: str,
guild_ids: Optional[List[int]] = None,
parent_group: Optional[SubCommandGroup] = None,
) -> None:
super().__init__(
SlashCommandOptionType.sub_command_group,
SlashCommandOptionType.sub_command_group,
name=name,
description=description,
)
self.subcommands = []
self.subcommands: List[SlashCommand] = []
self.guild_ids = guild_ids
self.parent_group = parent_group

def to_dict(self):
as_dict = {
def to_dict(self) -> Dict:
as_dict = {
"name": self.name,
"description": self.description,
"options": [c.to_dict() for c in self.subcommands],
Expand All @@ -146,7 +159,7 @@ def to_dict(self):

return as_dict

def command(self, **kwargs):
def command(self, **kwargs) -> SlashCommand:
def wrap(func) -> SlashCommand:
command = SlashCommand(func, **kwargs)
command.is_subcommand = True
Expand All @@ -155,60 +168,55 @@ def wrap(func) -> SlashCommand:

return wrap

def command_group(self, name, description):
def command_group(self, name, description) -> SubCommandGroup:
if self.parent_group is not None:
raise Exception('Subcommands can only be nested once') # TODO: Improve this
raise Exception("Subcommands can only be nested once") # TODO: Improve this

sub_command_group = SubCommandGroup(name, description, parent_group=self)
self.subcommands.append(sub_command_group)
return sub_command_group
async def invoke(self, interaction):

async def invoke(self, interaction: Interaction) -> None:
option = interaction.data["options"][0]
# command can be SubCommandGroup or SlashCommand
# but we don't need to worry about that
command = list(filter(lambda x: x.name == option["name"], self.subcommands))[0]
interaction.data = option
return await command.invoke(interaction)

await command.invoke(interaction)


class OptionChoice:
def __init__(self, name, value=None):
def __init__(self, name: str, value: Optional[str] = None):
self.name = name
self.value = value or name

def to_dict(self):
def to_dict(self) -> Dict[str, str]:
return {"name": self.name, "value": self.value}


class UserCommand:
type = 2

def __new__(cls, *args, **kwargs):
def __new__(cls, *args, **kwargs) -> UserCommand:
self = super().__new__(cls)

self.__original_kwargs__ = kwargs.copy()
return self

def __init__(self, func, *args, **kwargs):
def __init__(self, func: Callable, *args, **kwargs) -> None:
if not asyncio.iscoroutinefunction(func):
raise TypeError("Callback must be a coroutine.")
self.callback = func

self.guild_ids = kwargs.get("guild_ids", None)
self.guild_ids: Optional[List[int]] = kwargs.get("guild_ids", None)

self.description = ""
self.name = kwargs.pop("name", func.__name__)
self.name: str = kwargs.pop("name", func.__name__)
if not isinstance(self.name, str):
raise TypeError("Name of a command must be a string.")

def to_dict(self):
return {
"name":self.name,
"description":self.description,
"type":self.type
}
def to_dict(self) -> Dict[str, Union[str, int]]:
return {"name": self.name, "description": self.description, "type": self.type}

async def invoke(self, interaction):
async def invoke(self, interaction: Interaction) -> None:
if "members" not in interaction.data["resolved"]:
_data = interaction.data["resolved"]["users"]
for i, v in _data.items():
Expand All @@ -225,7 +233,11 @@ async def invoke(self, interaction):
v["id"] = int(i)
user = v
member["user"] = user
target = Member(data=member, guild=interaction._state._get_guild(interaction.guild_id), state=interaction._state)
target = Member(
data=member,
guild=interaction._state._get_guild(interaction.guild_id),
state=interaction._state,
)
ctx = InteractionContext(interaction)
await self.callback(ctx, target)

Expand Down