Rearrange some stuff and add flag support
This commit is contained in:
parent
17096629cd
commit
2f3d59e625
@ -64,6 +64,7 @@ from .core import GroupMixin
|
|||||||
from .converter import Greedy
|
from .converter import Greedy
|
||||||
from .view import StringView, supported_quotes
|
from .view import StringView, supported_quotes
|
||||||
from .context import Context
|
from .context import Context
|
||||||
|
from .flags import FlagConverter
|
||||||
from . import errors
|
from . import errors
|
||||||
from .help import HelpCommand, DefaultHelpCommand
|
from .help import HelpCommand, DefaultHelpCommand
|
||||||
from .cog import Cog
|
from .cog import Cog
|
||||||
@ -272,9 +273,9 @@ class BotBase(GroupMixin):
|
|||||||
for guild in guilds:
|
for guild in guilds:
|
||||||
commands[guild].append(payload)
|
commands[guild].append(payload)
|
||||||
|
|
||||||
http: HTTPClient = self.http # type: ignore
|
http: HTTPClient = self.http # type: ignore
|
||||||
global_commands = commands.pop(None, None)
|
global_commands = commands.pop(None, None)
|
||||||
application_id = self.application_id or (await self.application_info()).id # type: ignore
|
application_id = self.application_id or (await self.application_info()).id # type: ignore
|
||||||
if global_commands is not None:
|
if global_commands is not None:
|
||||||
if self.slash_command_guilds is None:
|
if self.slash_command_guilds is None:
|
||||||
await http.bulk_upsert_global_commands(
|
await http.bulk_upsert_global_commands(
|
||||||
@ -1271,7 +1272,20 @@ class BotBase(GroupMixin):
|
|||||||
ignore_params: List[inspect.Parameter] = []
|
ignore_params: List[inspect.Parameter] = []
|
||||||
message.content = f"{prefix}{command_name} "
|
message.content = f"{prefix}{command_name} "
|
||||||
for name, param in command.clean_params.items():
|
for name, param in command.clean_params.items():
|
||||||
option = next((o for o in command_options if o["name"] == name), None) # type: ignore
|
if inspect.isclass(param.annotation) and issubclass(param.annotation, FlagConverter):
|
||||||
|
for name, flag in param.annotation.get_flags().items():
|
||||||
|
option = next((o for o in command_options if o["name"] == name), None)
|
||||||
|
|
||||||
|
if option is None:
|
||||||
|
if flag.required:
|
||||||
|
raise errors.MissingRequiredFlag(flag)
|
||||||
|
else:
|
||||||
|
prefix = param.annotation.__commands_flag_prefix__
|
||||||
|
delimiter = param.annotation.__commands_flag_delimiter__
|
||||||
|
message.content += f"{prefix}{name} {option['value']}{delimiter}" # type: ignore
|
||||||
|
continue
|
||||||
|
|
||||||
|
option = next((o for o in command_options if o["name"] == name), None)
|
||||||
if option is None:
|
if option is None:
|
||||||
if param.default is param.empty and not command._is_typing_optional(param.annotation):
|
if param.default is param.empty and not command._is_typing_optional(param.annotation):
|
||||||
raise errors.MissingRequiredArgument(param)
|
raise errors.MissingRequiredArgument(param)
|
||||||
@ -1280,8 +1294,7 @@ class BotBase(GroupMixin):
|
|||||||
elif option["type"] == 3 and param.kind != param.KEYWORD_ONLY and not isinstance(param.annotation, Greedy):
|
elif option["type"] == 3 and param.kind != param.KEYWORD_ONLY and not isinstance(param.annotation, Greedy):
|
||||||
# String with space in without "consume rest"
|
# String with space in without "consume rest"
|
||||||
option = cast(_ApplicationCommandInteractionDataOptionString, option)
|
option = cast(_ApplicationCommandInteractionDataOptionString, option)
|
||||||
quoted_string = _quote_string_safe(option["value"])
|
message.content += f"{_quote_string_safe(option['value'])} "
|
||||||
message.content += f"{quoted_string} "
|
|
||||||
else:
|
else:
|
||||||
message.content += f'{option.get("value", "")} '
|
message.content += f'{option.get("value", "")} '
|
||||||
|
|
||||||
|
@ -58,13 +58,14 @@ from .converter import CONVERTER_MAPPING, Converter, run_converters, get_convert
|
|||||||
from ._types import _BaseCommand
|
from ._types import _BaseCommand
|
||||||
from .cog import Cog
|
from .cog import Cog
|
||||||
from .context import Context
|
from .context import Context
|
||||||
|
from .flags import FlagConverter
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing_extensions import Concatenate, ParamSpec, TypeGuard
|
from typing_extensions import Concatenate, ParamSpec, TypeGuard
|
||||||
|
|
||||||
from discord.message import Message
|
from discord.message import Message
|
||||||
from discord.types.interactions import EditApplicationCommand
|
from discord.types.interactions import EditApplicationCommand, ApplicationCommandInteractionDataOption
|
||||||
|
|
||||||
from ._types import (
|
from ._types import (
|
||||||
Coro,
|
Coro,
|
||||||
@ -1202,6 +1203,65 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
|||||||
finally:
|
finally:
|
||||||
ctx.command = original
|
ctx.command = original
|
||||||
|
|
||||||
|
def _param_to_options(
|
||||||
|
self, name: str, annotation: Any, required: bool, varadic: bool
|
||||||
|
) -> List[Optional[ApplicationCommandInteractionDataOption]]:
|
||||||
|
|
||||||
|
origin = getattr(annotation, "__origin__", None)
|
||||||
|
if inspect.isclass(annotation) and issubclass(annotation, FlagConverter):
|
||||||
|
return [
|
||||||
|
param
|
||||||
|
for name, flag in annotation.get_flags().items()
|
||||||
|
for param in self._param_to_options(
|
||||||
|
name, flag.annotation, required=flag.required, varadic=flag.annotation is tuple
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
if varadic:
|
||||||
|
annotation = str
|
||||||
|
origin = None
|
||||||
|
|
||||||
|
if not required and origin is not None and len(annotation.__args__) == 2:
|
||||||
|
# Unpack Optional[T] (Union[T, None]) into just T
|
||||||
|
annotation, origin = annotation.__args__[0], None
|
||||||
|
|
||||||
|
option: Dict[str, Any] = {
|
||||||
|
"name": name,
|
||||||
|
"required": required,
|
||||||
|
"description": self.option_descriptions[name],
|
||||||
|
}
|
||||||
|
|
||||||
|
if origin is None:
|
||||||
|
if not inspect.isclass(annotation):
|
||||||
|
annotation = type(annotation)
|
||||||
|
|
||||||
|
if issubclass(annotation, Converter):
|
||||||
|
# If this is a converter, we want to check if it is a native
|
||||||
|
# one, in which we can get the original type, eg, (MemberConverter -> Member)
|
||||||
|
annotation = REVERSED_CONVERTER_MAPPING.get(annotation, annotation)
|
||||||
|
|
||||||
|
option["type"] = 3
|
||||||
|
for python_type, discord_type in application_option_type_lookup.items():
|
||||||
|
if issubclass(annotation, python_type):
|
||||||
|
option["type"] = discord_type
|
||||||
|
break
|
||||||
|
|
||||||
|
elif origin is Literal:
|
||||||
|
literal_values = annotation.__args__
|
||||||
|
python_type = type(literal_values[0])
|
||||||
|
if (
|
||||||
|
all(type(value) == python_type for value in literal_values)
|
||||||
|
and python_type in application_option_type_lookup.keys()
|
||||||
|
):
|
||||||
|
|
||||||
|
option["type"] = application_option_type_lookup[python_type]
|
||||||
|
option["choices"] = [
|
||||||
|
{"name": literal_value, "value": literal_value} for literal_value in annotation.__args__
|
||||||
|
]
|
||||||
|
|
||||||
|
option.setdefault("type", 3) # STRING
|
||||||
|
return [option] # type: ignore
|
||||||
|
|
||||||
def to_application_command(self, nested: int = 0) -> Optional[EditApplicationCommand]:
|
def to_application_command(self, nested: int = 0) -> Optional[EditApplicationCommand]:
|
||||||
if self.slash_command is False:
|
if self.slash_command is False:
|
||||||
return
|
return
|
||||||
@ -1213,55 +1273,15 @@ class Command(_BaseCommand, Generic[CogT, P, T]):
|
|||||||
payload["type"] = 1
|
payload["type"] = 1
|
||||||
|
|
||||||
for name, param in self.clean_params.items():
|
for name, param in self.clean_params.items():
|
||||||
annotation: Type[Any] = param.annotation if param.annotation is not param.empty else str
|
options = self._param_to_options(
|
||||||
origin = getattr(param.annotation, "__origin__", None)
|
name,
|
||||||
|
param.annotation if param.annotation is not param.empty else str,
|
||||||
if origin is None and isinstance(annotation, Greedy):
|
varadic=param.kind == param.KEYWORD_ONLY or isinstance(param.annotation, Greedy),
|
||||||
annotation = annotation.converter
|
required=(param.default is param.empty and not self._is_typing_optional(param.annotation))
|
||||||
origin = Greedy
|
|
||||||
|
|
||||||
option: Dict[str, Any] = {
|
|
||||||
"name": name,
|
|
||||||
"description": self.option_descriptions[name],
|
|
||||||
"required": (param.default is param.empty and not self._is_typing_optional(annotation))
|
|
||||||
or param.kind == param.VAR_POSITIONAL,
|
or param.kind == param.VAR_POSITIONAL,
|
||||||
}
|
)
|
||||||
|
if options is not None:
|
||||||
annotation = cast(Any, annotation)
|
payload["options"].extend(option for option in options if option is not None)
|
||||||
if not option["required"] and origin is not None and len(annotation.__args__) == 2:
|
|
||||||
# Unpack Optional[T] (Union[T, None]) into just T
|
|
||||||
annotation, origin = annotation.__args__[0], None
|
|
||||||
|
|
||||||
if origin is None:
|
|
||||||
if not inspect.isclass(annotation):
|
|
||||||
annotation = type(annotation)
|
|
||||||
|
|
||||||
if issubclass(annotation, Converter):
|
|
||||||
# If this is a converter, we want to check if it is a native
|
|
||||||
# one, in which we can get the original type, eg, (MemberConverter -> Member)
|
|
||||||
annotation = REVERSED_CONVERTER_MAPPING.get(annotation, annotation)
|
|
||||||
|
|
||||||
option["type"] = 3
|
|
||||||
for python_type, discord_type in application_option_type_lookup.items():
|
|
||||||
if issubclass(annotation, python_type):
|
|
||||||
option["type"] = discord_type
|
|
||||||
break
|
|
||||||
|
|
||||||
elif origin is Literal:
|
|
||||||
literal_values = annotation.__args__
|
|
||||||
python_type = type(literal_values[0])
|
|
||||||
if (
|
|
||||||
all(type(value) == python_type for value in literal_values)
|
|
||||||
and python_type in application_option_type_lookup.keys()
|
|
||||||
):
|
|
||||||
|
|
||||||
option["type"] = application_option_type_lookup[python_type]
|
|
||||||
option["choices"] = [
|
|
||||||
{"name": literal_value, "value": literal_value} for literal_value in annotation.__args__
|
|
||||||
]
|
|
||||||
|
|
||||||
option.setdefault("type", 3) # STRING
|
|
||||||
payload["options"].append(option)
|
|
||||||
|
|
||||||
# Now we have all options, make sure required is before optional.
|
# Now we have all options, make sure required is before optional.
|
||||||
payload["options"] = sorted(payload["options"], key=itemgetter("required"), reverse=True)
|
payload["options"] = sorted(payload["options"], key=itemgetter("required"), reverse=True)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user