Source code for acord.ext.application_commands.slash

from __future__ import annotations
import inspect
from subprocess import call
import pydantic
from typing import List, Optional

from .option import SlashOption
from .types import ApplicationCommandType
from .base import UDAppCommand
from .model_based import EXTENDED_CALLS

from acord.errors import SlashCommandError
from acord.bases import _C


VALID_ATTR_NAMES = (
    "name",
    "description",
    "options",
    "default_permission",
    "guild_ids",
    "extend",
    "overwrite",
)


def get_methods(cls):
    for i in dir(cls):
        try:
            j = getattr(cls, i)
        except Exception:
            continue
        else:
            if not callable(j):
                continue
        
        yield j

[docs]class SlashBase(UDAppCommand): """Base class for creating slash commands. .. rubric:: Guidance When creating slash commands you have 2 options, initialise this class normally (ex1). Or subclass it and add your attrs through: * class variables * direct call to super().__init__ .. note:: All args can be passed through the init subclass apart from, ``overwrite`` and ``extend``, read below for further guidance. .. note:: * Max 25 options * Entire slash commands values must be less then 4k characters, (Dont panic this is handled for you!) * Name must be under 32 characters * Description must be under 100 characters For a more clearer example make sure to check out the examples in the github repo. .. rubric:: Valid on_call names on_call's can be registered directly from creating the class or :meth:`SlashBase.set_call`. Below they are represented as a list, were each element is the function signiture callback: [:class:`Interaction`, **options] Called when command is used, must be provided on_error: [:class:`Interaction`, exc_info] Called when an error occurs during handling of command. Parameters ---------- All values from attributes are considered arguments, and will be used to create the model. extend: :class:`bool` Whether to extend attributes if they were provided twice, doesn't check if they are the same! Defaults to ``True``. overwrite: :class:`bool` Whether to overwrite this command if it already exists, defaults to ``False``. When using args whilst subclassing, ``extend`` becomes ``extendable`` and ``overwrite`` becomes ``overwritable``. """ name: str """ name of command """ description: str """ description of the command """ options: Optional[List[SlashOption]] = [] """ array of options (your parameters) """ default_permission: Optional[bool] = True """ whether the command is enabled by default when the app is added to a guild """ guild_ids: Optional[List[int]] = None """ list of guilds to restrict command to """ overwrite: bool = False extend: bool = True __pre_calls__: dict = {} def dict(self, **kwds) -> dict: """:meta private:""" d = super(SlashBase, self).dict(**kwds) to_pop = ["guild_ids", "overwrite", "extend", "__pre_calls__"] to_pop.extend(getattr(self, "__ignore__", [])) for key in to_pop: d.pop(key, None) d["type"] = ApplicationCommandType.CHAT_INPUT return d def __new__(cls, *args, **kwds): # Generates new slash command on call # Adds pre-existing args from cls to kwds and calls init # returning generated slash command extend = kwds.get("extend", False) or getattr(cls, "extend", False) for attr in VALID_ATTR_NAMES: a_ = getattr(cls, attr, None) if attr in kwds and extend: if hasattr(a_, "extend"): a_.extend(kwds[attr]) if not a_: continue kwds.update({attr: a_}) return super(SlashBase, cls).__new__(cls) def __init__(self, **kwds) -> None: __pre_calls__ = {**self.__pre_calls__, **kwds.pop("calls", {})} for call_identifier in EXTENDED_CALLS: if call_identifier in kwds: __pre_calls__[call_identifier] = kwds[call_identifier] kwds.pop(call_identifier) if "callback" not in __pre_calls__: raise SlashCommandError("Slash command missing callback") super().__init__(**kwds) if not all(i for i in self.options if type(i) == SlashOption): raise SlashCommandError( "Options in a slash command must all be of type SlashOption" ) # Cache all autocompleters so we dont need to call during interaction create self.auto_complete_handlers() def __init_subclass__(cls, **kwds) -> None: # kwds is validated in the second for loop extend = kwds.pop("extendable", True) overwrite = kwds.pop("overwritable", False) cls.extend = extend cls.overwrite = overwrite cls.__annotations__.update({"extend": bool, "overwrite": bool}) for attr in kwds: if attr in VALID_ATTR_NAMES: cls.__annotations__.update({attr: SlashBase.__annotations__[attr]}) setattr(cls, attr, kwds[attr]) for attr in VALID_ATTR_NAMES: n_attr = getattr(cls, attr, None) field = SlashBase.__fields__[attr] field.validate(n_attr, {}, loc=field.alias) setattr(cls, "__pre_calls__", {}) for attr in EXTENDED_CALLS: if hasattr(cls, attr): cls.__pre_calls__[attr] = getattr(cls, attr) return cls def _total_chars(self): total = 0 total += len(self.name) + len(self.description) for attr in self.__annotations__: attr_value = getattr(self, attr) if hasattr(attr_value, "_total_chars"): total += attr_value._total_chars() else: total += len(attr_value) return total def set_call(self, name: str, function: _C) -> None: f"""|coro| Registers a function to be called on a certain condition, class must have "callback" registered and is done when intiating the class. Parameters ---------- name: :class:`str` Name of call, any from: {", ".join(EXTENDED_CALLS)} function: Callable[..., Coroutine] A coroutine function to be called on dispatch """ if not inspect.iscoroutinefunction(function): raise TypeError("Function must be a coroutine function") self.__pre_calls__.update({name: function}) def add_option(self, option: SlashOption) -> None: """Adds a specified option to the command, this method is preferred to be called as it handles slash command limits! Parameters ---------- option: :class:`SlashOption` new option to be added to slash command """ if (self._total_chars() + option._total_chars()) > 4000: raise SlashCommandError("Slash command exceeded 4k characters") if (len(self.options) + 1) > 25: raise SlashCommandError("Slash command must have less then 25 options") self.options.append(option) async def dispatcher(self, interaction, future, **kwds) -> int: """|coro| Default dispatch handler for when the slash command is used, handles callback and on_error calls. .. rubric:: Return Codes 0: Dispatched without error 1: Dispatched but an error occurred, on_error was called if accessible Exception of any type: Dispatched but an error occurred with both callback and error handler """ # 0 => Dispatched without error # 1 => Dispatched but an error occurred # Exception => Dispatched but an error occurred with both callback and error handler callback = self.__pre_calls__.get("callback") on_error = self.__pre_calls__.get("on_error") # Weird behaviour during testing, but oh well try: await callback(self, interaction, **kwds) except Exception as exc: if on_error is not None: try: await on_error( self, interaction, (type(exc), exc, exc.__traceback__) ) except Exception as e_exc: return future.set_result(e_exc) else: return future.set_result(exc) else: return future.set_result(0) return future.set_result(1) def auto_complete_handlers(self, *, cache: bool = True): """Fetches all autocomplete handlers within the class, returns a mapping of option: List[Callable] """ hndlrs = {} for i in get_methods(self): if not hasattr(i, "__autocomplete__"): continue options, *_ = i.__autocomplete__ for option in options: hndlrs[option] = i # if option not in hndlrs: # hndlrs.update({option: []}) # # hndlrs[option].append(i) if cache: self.__pre_calls__["__autocompleters__"] = hndlrs return hndlrs @classmethod def from_function(cls, function: _C, **kwds) -> None: """Generates slash command from a function, taking same kwargs and options as intiating the command normally. Parameters ---------- function: Callable[..., Coroutine] An async function which acts like the callback **kwds: Additional kwargs such as "name", "description". """ kwds.update({"callback": function}) sc_ff = cls(**kwds) return sc_ff @property def type(self): """Returns the type of command, always ``1``""" return ApplicationCommandType.CHAT_INPUT