hikari-arc 0.4.0__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- arc/__init__.py +55 -5
- arc/abc/__init__.py +32 -1
- arc/abc/client.py +207 -67
- arc/abc/command.py +245 -34
- arc/abc/error_handler.py +33 -2
- arc/abc/hookable.py +24 -0
- arc/abc/limiter.py +61 -0
- arc/abc/option.py +73 -5
- arc/abc/plugin.py +185 -29
- arc/client.py +103 -33
- arc/command/message.py +21 -18
- arc/command/option/attachment.py +9 -5
- arc/command/option/bool.py +9 -6
- arc/command/option/channel.py +9 -5
- arc/command/option/float.py +11 -7
- arc/command/option/int.py +11 -7
- arc/command/option/mentionable.py +9 -5
- arc/command/option/role.py +9 -5
- arc/command/option/str.py +11 -7
- arc/command/option/user.py +9 -5
- arc/command/slash.py +222 -197
- arc/command/user.py +20 -17
- arc/context/autocomplete.py +1 -0
- arc/context/base.py +216 -105
- arc/errors.py +52 -10
- arc/events.py +5 -1
- arc/extension.py +23 -0
- arc/internal/about.py +1 -1
- arc/internal/deprecation.py +3 -4
- arc/internal/options.py +106 -0
- arc/internal/sigparse.py +19 -1
- arc/internal/sync.py +13 -10
- arc/internal/types.py +34 -15
- arc/locale.py +28 -0
- arc/plugin.py +56 -5
- arc/utils/__init__.py +53 -2
- arc/utils/hooks/__init__.py +25 -0
- arc/utils/{hooks.py → hooks/basic.py} +28 -1
- arc/utils/hooks/limiters.py +217 -0
- arc/utils/ratelimiter.py +243 -0
- {hikari_arc-0.4.0.dist-info → hikari_arc-0.6.0.dist-info}/METADATA +13 -8
- hikari_arc-0.6.0.dist-info/RECORD +52 -0
- hikari_arc-0.4.0.dist-info/RECORD +0 -47
- {hikari_arc-0.4.0.dist-info → hikari_arc-0.6.0.dist-info}/LICENSE +0 -0
- {hikari_arc-0.4.0.dist-info → hikari_arc-0.6.0.dist-info}/WHEEL +0 -0
- {hikari_arc-0.4.0.dist-info → hikari_arc-0.6.0.dist-info}/top_level.txt +0 -0
arc/command/slash.py
CHANGED
|
@@ -6,10 +6,11 @@ import typing as t
|
|
|
6
6
|
import attr
|
|
7
7
|
import hikari
|
|
8
8
|
|
|
9
|
-
from arc.abc.command import CallableCommandBase, CommandBase, SubCommandBase
|
|
10
|
-
from arc.abc.option import OptionWithChoices
|
|
9
|
+
from arc.abc.command import CallableCommandBase, CallableCommandProto, CommandBase, SubCommandBase, _CommandSettings
|
|
10
|
+
from arc.abc.option import OptionType, OptionWithChoices
|
|
11
11
|
from arc.context import AutocompleteData, AutodeferMode, Context
|
|
12
12
|
from arc.errors import AutocompleteError, CommandInvokeError
|
|
13
|
+
from arc.internal.options import resolve_options
|
|
13
14
|
from arc.internal.sigparse import parse_command_signature
|
|
14
15
|
from arc.internal.types import ClientT, CommandCallbackT, HookT, PostHookT, ResponseBuilderT, SlashCommandLike
|
|
15
16
|
from arc.locale import CommandLocaleRequest, LocaleResponse
|
|
@@ -18,7 +19,7 @@ if t.TYPE_CHECKING:
|
|
|
18
19
|
from asyncio.futures import Future
|
|
19
20
|
|
|
20
21
|
from arc.abc.client import Client
|
|
21
|
-
from arc.abc.command import
|
|
22
|
+
from arc.abc.command import CommandProto
|
|
22
23
|
from arc.abc.option import CommandOptionBase
|
|
23
24
|
from arc.abc.plugin import PluginBase
|
|
24
25
|
|
|
@@ -33,64 +34,6 @@ __all__ = (
|
|
|
33
34
|
)
|
|
34
35
|
|
|
35
36
|
|
|
36
|
-
def _resolve_options(
|
|
37
|
-
local_options: t.MutableMapping[str, CommandOptionBase[ClientT, t.Any, t.Any]],
|
|
38
|
-
incoming_options: t.Sequence[hikari.CommandInteractionOption],
|
|
39
|
-
resolved: hikari.ResolvedOptionData | None,
|
|
40
|
-
) -> dict[str, t.Any]:
|
|
41
|
-
"""Resolve the options into kwargs for the callback.
|
|
42
|
-
|
|
43
|
-
Parameters
|
|
44
|
-
----------
|
|
45
|
-
local_options : t.MutableMapping[str, Option[t.Any, t.Any]]
|
|
46
|
-
The options of the locally stored command.
|
|
47
|
-
incoming_options : t.Sequence[hikari.CommandInteractionOption]
|
|
48
|
-
The options of the interaction.
|
|
49
|
-
resolved : hikari.ResolvedOptionData
|
|
50
|
-
The resolved option data of the interaction.
|
|
51
|
-
|
|
52
|
-
Returns
|
|
53
|
-
-------
|
|
54
|
-
dict[str, Any]
|
|
55
|
-
The resolved options as kwargs, ready to be passed to the callback.
|
|
56
|
-
"""
|
|
57
|
-
option_kwargs: dict[str, t.Any] = {}
|
|
58
|
-
|
|
59
|
-
for arg_name, opt in local_options.items():
|
|
60
|
-
inter_opt = next((o for o in incoming_options if o.name == opt.name), None)
|
|
61
|
-
|
|
62
|
-
if inter_opt is None:
|
|
63
|
-
continue
|
|
64
|
-
|
|
65
|
-
if isinstance(inter_opt.value, hikari.Snowflake) and resolved:
|
|
66
|
-
match inter_opt.type:
|
|
67
|
-
case hikari.OptionType.USER:
|
|
68
|
-
value = resolved.members.get(inter_opt.value) or resolved.users[inter_opt.value]
|
|
69
|
-
case hikari.OptionType.ATTACHMENT:
|
|
70
|
-
value = resolved.attachments[inter_opt.value]
|
|
71
|
-
case hikari.OptionType.CHANNEL:
|
|
72
|
-
value = resolved.channels[inter_opt.value]
|
|
73
|
-
case hikari.OptionType.ROLE:
|
|
74
|
-
value = resolved.roles[inter_opt.value]
|
|
75
|
-
case hikari.OptionType.MENTIONABLE:
|
|
76
|
-
value = (
|
|
77
|
-
resolved.members.get(inter_opt.value)
|
|
78
|
-
or resolved.users.get(inter_opt.value)
|
|
79
|
-
or resolved.roles[inter_opt.value]
|
|
80
|
-
)
|
|
81
|
-
case _:
|
|
82
|
-
raise ValueError(f"Unexpected option type '{inter_opt.type}.'")
|
|
83
|
-
|
|
84
|
-
option_kwargs[arg_name] = value
|
|
85
|
-
|
|
86
|
-
elif isinstance(inter_opt.value, hikari.Snowflake):
|
|
87
|
-
raise ValueError(f"Missing resolved option data for '{inter_opt.name}'.")
|
|
88
|
-
else:
|
|
89
|
-
option_kwargs[arg_name] = inter_opt.value
|
|
90
|
-
|
|
91
|
-
return option_kwargs
|
|
92
|
-
|
|
93
|
-
|
|
94
37
|
def _choices_to_builders(
|
|
95
38
|
choices: t.Sequence[hikari.api.AutocompleteChoiceBuilder] | t.Sequence[t.Any],
|
|
96
39
|
) -> t.Sequence[hikari.api.AutocompleteChoiceBuilder]:
|
|
@@ -126,6 +69,11 @@ class SlashCommand(CallableCommandBase[ClientT, hikari.api.SlashCommandBuilder])
|
|
|
126
69
|
def qualified_name(self) -> t.Sequence[str]:
|
|
127
70
|
return (self.name,)
|
|
128
71
|
|
|
72
|
+
@property
|
|
73
|
+
def display_name(self) -> str:
|
|
74
|
+
"""The display name of this command."""
|
|
75
|
+
return f"/{self.name}"
|
|
76
|
+
|
|
129
77
|
def _get_context(
|
|
130
78
|
self, interaction: hikari.CommandInteraction, command: CallableCommandProto[ClientT]
|
|
131
79
|
) -> Context[ClientT]:
|
|
@@ -134,7 +82,9 @@ class SlashCommand(CallableCommandBase[ClientT, hikari.api.SlashCommandBuilder])
|
|
|
134
82
|
if interaction.command_type is not hikari.CommandType.SLASH:
|
|
135
83
|
raise ValueError(f"Expected slash command, got {interaction.command_type}")
|
|
136
84
|
|
|
137
|
-
|
|
85
|
+
ctx = Context(self.client, command, interaction)
|
|
86
|
+
ctx._options = interaction.options
|
|
87
|
+
return ctx
|
|
138
88
|
|
|
139
89
|
def _to_dict(self) -> dict[str, t.Any]:
|
|
140
90
|
sorted_options = sorted(self.options.values(), key=lambda option: option.is_required, reverse=True)
|
|
@@ -164,11 +114,36 @@ class SlashCommand(CallableCommandBase[ClientT, hikari.api.SlashCommandBuilder])
|
|
|
164
114
|
return await super().invoke(
|
|
165
115
|
interaction,
|
|
166
116
|
*args,
|
|
167
|
-
**{**kwargs, **
|
|
117
|
+
**{**kwargs, **resolve_options(self.options, interaction.options, interaction.resolved)},
|
|
168
118
|
)
|
|
169
119
|
else:
|
|
170
120
|
return await super().invoke(interaction, *args, **kwargs)
|
|
171
121
|
|
|
122
|
+
def make_mention(self, *, guild: hikari.Snowflakeish | hikari.PartialGuild | None = None) -> str:
|
|
123
|
+
"""Make a slash mention for this command.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
guild : hikari.SnowflakeishOr[hikari.PartialGuild] | None
|
|
128
|
+
The guild the command is registered in. If None, the global command's ID is used.
|
|
129
|
+
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
str
|
|
133
|
+
The slash command mention.
|
|
134
|
+
|
|
135
|
+
Raises
|
|
136
|
+
------
|
|
137
|
+
KeyError
|
|
138
|
+
If the command has not been published in the given guild or globally.
|
|
139
|
+
"""
|
|
140
|
+
instance = self._instances.get(hikari.Snowflake(guild) if guild else None)
|
|
141
|
+
|
|
142
|
+
if instance is None:
|
|
143
|
+
raise KeyError(f"Command '{self.display_name}' has not been published in the given scope.")
|
|
144
|
+
|
|
145
|
+
return f"</{self.name}:{instance.id}>"
|
|
146
|
+
|
|
172
147
|
async def _on_autocomplete(
|
|
173
148
|
self, interaction: hikari.AutocompleteInteraction
|
|
174
149
|
) -> hikari.api.InteractionAutocompleteBuilder | None:
|
|
@@ -230,7 +205,7 @@ class SlashCommand(CallableCommandBase[ClientT, hikari.api.SlashCommandBuilder])
|
|
|
230
205
|
class SlashGroup(CommandBase[ClientT, hikari.api.SlashCommandBuilder]):
|
|
231
206
|
"""A group for slash subcommands and subgroups."""
|
|
232
207
|
|
|
233
|
-
children: dict[str, SlashSubCommand[ClientT] | SlashSubGroup[ClientT]] = attr.field(factory=dict)
|
|
208
|
+
children: dict[str, SlashSubCommand[ClientT] | SlashSubGroup[ClientT]] = attr.field(factory=dict, init=False)
|
|
234
209
|
"""Subcommands and subgroups that belong to this group."""
|
|
235
210
|
|
|
236
211
|
description: str = "No description provided."
|
|
@@ -249,6 +224,11 @@ class SlashGroup(CommandBase[ClientT, hikari.api.SlashCommandBuilder]):
|
|
|
249
224
|
def qualified_name(self) -> t.Sequence[str]:
|
|
250
225
|
return (self.name,)
|
|
251
226
|
|
|
227
|
+
@property
|
|
228
|
+
def display_name(self) -> str:
|
|
229
|
+
"""The display name of this command."""
|
|
230
|
+
return f"/{self.name}"
|
|
231
|
+
|
|
252
232
|
def _to_dict(self) -> dict[str, t.Any]:
|
|
253
233
|
return {
|
|
254
234
|
**super()._to_dict(),
|
|
@@ -283,13 +263,15 @@ class SlashGroup(CommandBase[ClientT, hikari.api.SlashCommandBuilder]):
|
|
|
283
263
|
self,
|
|
284
264
|
subcommand: SlashSubCommand[ClientT],
|
|
285
265
|
interaction: hikari.CommandInteraction,
|
|
266
|
+
options: t.Sequence[hikari.CommandInteractionOption] | None = None,
|
|
286
267
|
*args: t.Any,
|
|
287
268
|
**kwargs: t.Any,
|
|
288
269
|
) -> asyncio.Future[ResponseBuilderT] | None:
|
|
289
270
|
"""Invoke a subcommand."""
|
|
290
271
|
ctx = self._get_context(interaction, subcommand)
|
|
272
|
+
ctx._options = options
|
|
291
273
|
|
|
292
|
-
if (autodefer := subcommand.
|
|
274
|
+
if (autodefer := subcommand.autodefer) and autodefer.should_autodefer:
|
|
293
275
|
ctx._start_autodefer(autodefer)
|
|
294
276
|
|
|
295
277
|
self._invoke_task = asyncio.create_task(self._handle_callback(subcommand, ctx, *args, **kwargs))
|
|
@@ -314,12 +296,12 @@ class SlashGroup(CommandBase[ClientT, hikari.api.SlashCommandBuilder]):
|
|
|
314
296
|
if sub.options is None:
|
|
315
297
|
if not isinstance(subcmd, SlashSubCommand):
|
|
316
298
|
raise CommandInvokeError(f"Slash group got subgroup without options: '{subcmd.name}'.")
|
|
317
|
-
return await self._invoke_subcmd(subcmd, interaction, *args, **kwargs)
|
|
299
|
+
return await self._invoke_subcmd(subcmd, interaction, None, *args, **kwargs)
|
|
318
300
|
|
|
319
301
|
# Resolve options and invoke if it does
|
|
320
302
|
if isinstance(subcmd, SlashSubCommand):
|
|
321
|
-
res =
|
|
322
|
-
return await self._invoke_subcmd(subcmd, interaction, *args, **{**kwargs, **res})
|
|
303
|
+
res = resolve_options(subcmd.options, sub.options, interaction.resolved)
|
|
304
|
+
return await self._invoke_subcmd(subcmd, interaction, sub.options, *args, **{**kwargs, **res})
|
|
323
305
|
|
|
324
306
|
# Get second-order subcommand
|
|
325
307
|
subsub = next((o for o in sub.options if o.name in subcmd.children), None)
|
|
@@ -331,11 +313,11 @@ class SlashGroup(CommandBase[ClientT, hikari.api.SlashCommandBuilder]):
|
|
|
331
313
|
|
|
332
314
|
# Invoke it if it has no options
|
|
333
315
|
if subsub.options is None:
|
|
334
|
-
return await self._invoke_subcmd(subsubcmd, interaction, *args, **kwargs)
|
|
316
|
+
return await self._invoke_subcmd(subsubcmd, interaction, None, *args, **kwargs)
|
|
335
317
|
|
|
336
318
|
# Resolve options and invoke if it does
|
|
337
|
-
res =
|
|
338
|
-
return await self._invoke_subcmd(subsubcmd, interaction, *args, **{**kwargs, **res})
|
|
319
|
+
res = resolve_options(subsubcmd.options, subsub.options, interaction.resolved)
|
|
320
|
+
return await self._invoke_subcmd(subsubcmd, interaction, subsub.options, *args, **{**kwargs, **res})
|
|
339
321
|
|
|
340
322
|
async def _on_autocomplete(
|
|
341
323
|
self, interaction: hikari.AutocompleteInteraction
|
|
@@ -427,7 +409,7 @@ class SlashGroup(CommandBase[ClientT, hikari.api.SlashCommandBuilder]):
|
|
|
427
409
|
"""Decorator to add a subcommand to this group."""
|
|
428
410
|
|
|
429
411
|
def decorator(command: SlashSubCommand[ClientT]) -> SlashSubCommand[ClientT]:
|
|
430
|
-
command.
|
|
412
|
+
command._parent = self
|
|
431
413
|
self.children[command.name] = command
|
|
432
414
|
return command
|
|
433
415
|
|
|
@@ -442,8 +424,8 @@ class SlashGroup(CommandBase[ClientT, hikari.api.SlashCommandBuilder]):
|
|
|
442
424
|
description: str = "No description provided.",
|
|
443
425
|
*,
|
|
444
426
|
autodefer: AutodeferMode | bool | hikari.UndefinedType = hikari.UNDEFINED,
|
|
445
|
-
name_localizations:
|
|
446
|
-
description_localizations:
|
|
427
|
+
name_localizations: t.Mapping[hikari.Locale, str] | None = None,
|
|
428
|
+
description_localizations: t.Mapping[hikari.Locale, str] | None = None,
|
|
447
429
|
) -> SlashSubGroup[ClientT]:
|
|
448
430
|
"""Create a subgroup and add it to this group.
|
|
449
431
|
|
|
@@ -451,24 +433,24 @@ class SlashGroup(CommandBase[ClientT, hikari.api.SlashCommandBuilder]):
|
|
|
451
433
|
----------
|
|
452
434
|
name : str
|
|
453
435
|
The name of the subgroup.
|
|
454
|
-
description : str
|
|
455
|
-
The description of the subgroup
|
|
456
|
-
autodefer : bool | AutodeferMode | hikari.UndefinedType
|
|
436
|
+
description : str
|
|
437
|
+
The description of the subgroup
|
|
438
|
+
autodefer : bool | AutodeferMode | hikari.UndefinedType
|
|
457
439
|
If True, all commands in this subgroup will automatically defer if it is taking longer than 2 seconds to respond.
|
|
458
440
|
If not provided, then this setting will be inherited from the parent.
|
|
459
|
-
name_localizations : dict[hikari.Locale, str] | None
|
|
460
|
-
Localizations for the name of the subgroup
|
|
461
|
-
description_localizations : dict[hikari.Locale, str] | None
|
|
462
|
-
Localizations for the description of the subgroup
|
|
441
|
+
name_localizations : dict[hikari.Locale, str] | None
|
|
442
|
+
Localizations for the name of the subgroup
|
|
443
|
+
description_localizations : dict[hikari.Locale, str] | None
|
|
444
|
+
Localizations for the description of the subgroup
|
|
463
445
|
"""
|
|
464
446
|
group: SlashSubGroup[ClientT] = SlashSubGroup(
|
|
465
447
|
name=name,
|
|
466
448
|
description=description,
|
|
467
|
-
autodefer=AutodeferMode(autodefer) if autodefer else
|
|
449
|
+
autodefer=AutodeferMode(autodefer) if isinstance(autodefer, bool) else autodefer,
|
|
468
450
|
name_localizations=name_localizations or {},
|
|
469
451
|
description_localizations=description_localizations or {},
|
|
470
452
|
)
|
|
471
|
-
group.
|
|
453
|
+
group._parent = self
|
|
472
454
|
self.children[name] = group
|
|
473
455
|
return group
|
|
474
456
|
|
|
@@ -477,19 +459,17 @@ class SlashGroup(CommandBase[ClientT, hikari.api.SlashCommandBuilder]):
|
|
|
477
459
|
class SlashSubGroup(SubCommandBase[ClientT, SlashGroup[ClientT]]):
|
|
478
460
|
"""A subgroup of a slash command group."""
|
|
479
461
|
|
|
480
|
-
children: dict[str, SlashSubCommand[ClientT]] = attr.field(factory=dict)
|
|
462
|
+
children: dict[str, SlashSubCommand[ClientT]] = attr.field(factory=dict, init=False)
|
|
481
463
|
"""Subcommands that belong to this subgroup."""
|
|
482
464
|
|
|
483
|
-
|
|
465
|
+
_autodefer: AutodeferMode | hikari.UndefinedType = attr.field(default=hikari.UNDEFINED, alias="autodefer")
|
|
484
466
|
"""If True, this subcommand will automatically defer if it is taking longer than 2 seconds to respond.
|
|
485
467
|
If undefined, then it will be inherited from the parent.
|
|
486
468
|
"""
|
|
487
469
|
|
|
488
|
-
_plugin: PluginBase[ClientT] | None = attr.field(default=None, init=False)
|
|
489
|
-
|
|
490
470
|
@property
|
|
491
|
-
def option_type(self) ->
|
|
492
|
-
return
|
|
471
|
+
def option_type(self) -> OptionType:
|
|
472
|
+
return OptionType.SUB_COMMAND_GROUP
|
|
493
473
|
|
|
494
474
|
@property
|
|
495
475
|
def command_type(self) -> hikari.CommandType:
|
|
@@ -497,23 +477,28 @@ class SlashSubGroup(SubCommandBase[ClientT, SlashGroup[ClientT]]):
|
|
|
497
477
|
|
|
498
478
|
@property
|
|
499
479
|
def qualified_name(self) -> t.Sequence[str]:
|
|
500
|
-
if self.parent is None:
|
|
501
|
-
raise ValueError("Cannot get qualified name of subgroup without parent.")
|
|
502
|
-
|
|
503
480
|
return (self.parent.name, self.name)
|
|
504
481
|
|
|
482
|
+
@property
|
|
483
|
+
def display_name(self) -> str:
|
|
484
|
+
return "/" + " ".join(self.qualified_name)
|
|
485
|
+
|
|
505
486
|
@property
|
|
506
487
|
def client(self) -> ClientT:
|
|
507
488
|
"""The client that includes this subgroup."""
|
|
508
|
-
if self.parent is None:
|
|
509
|
-
raise ValueError("Cannot get client of subgroup without parent.")
|
|
510
|
-
|
|
511
489
|
return self.parent.client
|
|
512
490
|
|
|
513
491
|
@property
|
|
514
492
|
def plugin(self) -> PluginBase[ClientT] | None:
|
|
515
493
|
"""The plugin that includes this subgroup."""
|
|
516
|
-
return self.
|
|
494
|
+
return self.parent.plugin
|
|
495
|
+
|
|
496
|
+
@property
|
|
497
|
+
def autodefer(self) -> AutodeferMode:
|
|
498
|
+
"""The resolved autodefer configuration for this subcommand."""
|
|
499
|
+
autodefer = self._resolve_settings().autodefer
|
|
500
|
+
assert autodefer is not hikari.UNDEFINED
|
|
501
|
+
return autodefer
|
|
517
502
|
|
|
518
503
|
def _to_dict(self) -> dict[str, t.Any]:
|
|
519
504
|
return {
|
|
@@ -521,13 +506,25 @@ class SlashSubGroup(SubCommandBase[ClientT, SlashGroup[ClientT]]):
|
|
|
521
506
|
"options": [subcommand.to_command_option() for subcommand in self.children.values()],
|
|
522
507
|
}
|
|
523
508
|
|
|
509
|
+
def _resolve_settings(self) -> _CommandSettings:
|
|
510
|
+
settings = self._parent._resolve_settings() if self._parent else _CommandSettings.default()
|
|
511
|
+
|
|
512
|
+
return settings.apply(
|
|
513
|
+
_CommandSettings(
|
|
514
|
+
autodefer=self.autodefer,
|
|
515
|
+
default_permissions=hikari.UNDEFINED,
|
|
516
|
+
is_nsfw=hikari.UNDEFINED,
|
|
517
|
+
is_dm_enabled=hikari.UNDEFINED,
|
|
518
|
+
)
|
|
519
|
+
)
|
|
520
|
+
|
|
524
521
|
def _resolve_hooks(self) -> list[HookT[ClientT]]:
|
|
525
|
-
assert self.
|
|
526
|
-
return self.
|
|
522
|
+
assert self._parent is not None
|
|
523
|
+
return self._parent._resolve_hooks() + self._hooks
|
|
527
524
|
|
|
528
525
|
def _resolve_post_hooks(self) -> list[PostHookT[ClientT]]:
|
|
529
|
-
assert self.
|
|
530
|
-
return self.
|
|
526
|
+
assert self._parent is not None
|
|
527
|
+
return self._parent._resolve_post_hooks() + self._post_hooks
|
|
531
528
|
|
|
532
529
|
async def _handle_exception(self, ctx: Context[ClientT], exc: Exception) -> None:
|
|
533
530
|
try:
|
|
@@ -536,8 +533,8 @@ class SlashSubGroup(SubCommandBase[ClientT, SlashGroup[ClientT]]):
|
|
|
536
533
|
else:
|
|
537
534
|
raise exc
|
|
538
535
|
except Exception as e:
|
|
539
|
-
assert self.
|
|
540
|
-
await self.
|
|
536
|
+
assert self._parent is not None
|
|
537
|
+
await self._parent._handle_exception(ctx, e)
|
|
541
538
|
|
|
542
539
|
def _request_option_locale(self, client: Client[t.Any], command: CommandProto) -> None:
|
|
543
540
|
super()._request_option_locale(client, command)
|
|
@@ -559,7 +556,7 @@ class SlashSubGroup(SubCommandBase[ClientT, SlashGroup[ClientT]]):
|
|
|
559
556
|
"""First-order decorator to add a subcommand to this group."""
|
|
560
557
|
|
|
561
558
|
def decorator(command: SlashSubCommand[ClientT]) -> SlashSubCommand[ClientT]:
|
|
562
|
-
command.
|
|
559
|
+
command._parent = self
|
|
563
560
|
self.children[command.name] = command
|
|
564
561
|
return command
|
|
565
562
|
|
|
@@ -570,7 +567,9 @@ class SlashSubGroup(SubCommandBase[ClientT, SlashGroup[ClientT]]):
|
|
|
570
567
|
|
|
571
568
|
|
|
572
569
|
@attr.define(slots=True, kw_only=True)
|
|
573
|
-
class SlashSubCommand(
|
|
570
|
+
class SlashSubCommand(
|
|
571
|
+
SubCommandBase[ClientT, SlashGroup[ClientT] | SlashSubGroup[ClientT]], CallableCommandProto[ClientT]
|
|
572
|
+
):
|
|
574
573
|
"""A subcommand of a slash command group."""
|
|
575
574
|
|
|
576
575
|
callback: CommandCallbackT[ClientT]
|
|
@@ -579,65 +578,48 @@ class SlashSubCommand(SubCommandBase[ClientT, SlashGroup[ClientT] | SlashSubGrou
|
|
|
579
578
|
options: t.MutableMapping[str, CommandOptionBase[ClientT, t.Any, t.Any]] = attr.field(factory=dict)
|
|
580
579
|
"""The options of this subcommand."""
|
|
581
580
|
|
|
582
|
-
|
|
581
|
+
_autodefer: AutodeferMode | hikari.UndefinedType = attr.field(default=hikari.UNDEFINED, alias="autodefer")
|
|
583
582
|
"""If True, this subcommand will automatically defer if it is taking longer than 2 seconds to respond.
|
|
584
583
|
If undefined, then it will be inherited from the parent.
|
|
585
584
|
"""
|
|
586
585
|
|
|
587
586
|
_invoke_task: asyncio.Task[t.Any] | None = attr.field(default=None, init=False)
|
|
588
587
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
588
|
+
@property
|
|
589
|
+
def root(self) -> SlashGroup[ClientT]:
|
|
590
|
+
"""The root group of this subcommand."""
|
|
591
|
+
if self._parent is None:
|
|
592
|
+
raise ValueError("Cannot get root of subcommand without parent.")
|
|
592
593
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
594
|
+
if isinstance(self._parent, SlashSubGroup):
|
|
595
|
+
if self._parent._parent is None:
|
|
596
|
+
raise ValueError("Cannot get root of subcommand without parent.")
|
|
596
597
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
await self.error_handler(ctx, exc)
|
|
601
|
-
else:
|
|
602
|
-
raise exc
|
|
603
|
-
except Exception as e:
|
|
604
|
-
assert self.parent is not None
|
|
605
|
-
await self.parent._handle_exception(ctx, e)
|
|
598
|
+
return self._parent._parent
|
|
599
|
+
|
|
600
|
+
return self._parent
|
|
606
601
|
|
|
607
602
|
@property
|
|
608
603
|
def qualified_name(self) -> t.Sequence[str]:
|
|
609
|
-
if self.
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
if isinstance(self.parent, SlashSubGroup):
|
|
613
|
-
if self.parent.parent is None:
|
|
614
|
-
raise ValueError("Cannot get qualified name of subcommand without parent.")
|
|
615
|
-
|
|
616
|
-
return (self.parent.parent.name, self.parent.name, self.name)
|
|
604
|
+
if isinstance(self._parent, SlashSubGroup):
|
|
605
|
+
return (self.root.name, self.parent.name, self.name)
|
|
617
606
|
|
|
618
607
|
return (self.parent.name, self.name)
|
|
619
608
|
|
|
620
609
|
@property
|
|
621
|
-
def
|
|
622
|
-
"""The
|
|
623
|
-
if self.
|
|
624
|
-
raise ValueError("Cannot get
|
|
625
|
-
|
|
626
|
-
if isinstance(self.parent, SlashSubGroup):
|
|
627
|
-
if self.parent.parent is None:
|
|
628
|
-
raise ValueError("Cannot get root of subcommand without parent.")
|
|
629
|
-
|
|
630
|
-
return self.parent.parent
|
|
631
|
-
|
|
632
|
-
return self.parent
|
|
610
|
+
def parent(self) -> SlashGroup[ClientT] | SlashSubGroup[ClientT]:
|
|
611
|
+
"""The parent of this subcommand."""
|
|
612
|
+
if self._parent is None:
|
|
613
|
+
raise ValueError("Cannot get parent of subcommand without parent.")
|
|
614
|
+
return self._parent
|
|
633
615
|
|
|
634
616
|
@property
|
|
635
617
|
def command_type(self) -> hikari.CommandType:
|
|
636
618
|
return hikari.CommandType.SLASH
|
|
637
619
|
|
|
638
620
|
@property
|
|
639
|
-
def option_type(self) ->
|
|
640
|
-
return
|
|
621
|
+
def option_type(self) -> OptionType:
|
|
622
|
+
return OptionType.SUB_COMMAND
|
|
641
623
|
|
|
642
624
|
@property
|
|
643
625
|
def client(self) -> ClientT:
|
|
@@ -649,30 +631,67 @@ class SlashSubCommand(SubCommandBase[ClientT, SlashGroup[ClientT] | SlashSubGrou
|
|
|
649
631
|
"""The plugin that includes this subcommand."""
|
|
650
632
|
return self.root.plugin
|
|
651
633
|
|
|
652
|
-
|
|
653
|
-
|
|
634
|
+
@property
|
|
635
|
+
def autodefer(self) -> AutodeferMode:
|
|
636
|
+
"""The resolved autodefer configuration for this subcommand."""
|
|
637
|
+
autodefer = self._resolve_settings().autodefer
|
|
638
|
+
assert autodefer is not hikari.UNDEFINED
|
|
639
|
+
return autodefer
|
|
654
640
|
|
|
655
|
-
|
|
656
|
-
|
|
641
|
+
@property
|
|
642
|
+
def display_name(self) -> str:
|
|
643
|
+
return "/" + " ".join(self.qualified_name)
|
|
644
|
+
|
|
645
|
+
def make_mention(self, guild: hikari.Snowflakeish | hikari.PartialGuild | None = None) -> str:
|
|
646
|
+
"""Make a slash mention for this command.
|
|
647
|
+
|
|
648
|
+
Returns
|
|
649
|
+
-------
|
|
650
|
+
str
|
|
651
|
+
The slash command mention.
|
|
652
|
+
"""
|
|
653
|
+
instance = self.root._instances.get(hikari.Snowflake(guild) if guild else None)
|
|
657
654
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
if self.autodefer is not hikari.UNDEFINED:
|
|
661
|
-
return self.autodefer
|
|
655
|
+
if instance is None:
|
|
656
|
+
raise KeyError(f"Command '{self.display_name}' has not been published in the given scope.")
|
|
662
657
|
|
|
663
|
-
|
|
664
|
-
return AutodeferMode.OFF
|
|
658
|
+
return f"</{' '.join(self.qualified_name)}:{instance.id}>"
|
|
665
659
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
return self.parent.autodefer
|
|
660
|
+
def _resolve_settings(self) -> _CommandSettings:
|
|
661
|
+
settings = self._parent._resolve_settings() if self._parent else _CommandSettings.default()
|
|
669
662
|
|
|
670
|
-
|
|
671
|
-
|
|
663
|
+
return settings.apply(
|
|
664
|
+
_CommandSettings(
|
|
665
|
+
autodefer=self._autodefer,
|
|
666
|
+
default_permissions=hikari.UNDEFINED,
|
|
667
|
+
is_nsfw=hikari.UNDEFINED,
|
|
668
|
+
is_dm_enabled=hikari.UNDEFINED,
|
|
669
|
+
)
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
def _resolve_hooks(self) -> list[HookT[ClientT]]:
|
|
673
|
+
assert self._parent is not None
|
|
674
|
+
return self._parent._resolve_hooks() + self._hooks
|
|
675
|
+
|
|
676
|
+
def _resolve_post_hooks(self) -> list[PostHookT[ClientT]]:
|
|
677
|
+
assert self._parent is not None
|
|
678
|
+
return self._parent._resolve_post_hooks() + self._post_hooks
|
|
679
|
+
|
|
680
|
+
async def _handle_exception(self, ctx: Context[ClientT], exc: Exception) -> None:
|
|
681
|
+
try:
|
|
682
|
+
if self.error_handler:
|
|
683
|
+
await self.error_handler(ctx, exc)
|
|
684
|
+
else:
|
|
685
|
+
raise exc
|
|
686
|
+
except Exception as e:
|
|
687
|
+
assert self._parent is not None
|
|
688
|
+
await self._parent._handle_exception(ctx, e)
|
|
672
689
|
|
|
673
|
-
|
|
690
|
+
def _request_option_locale(self, client: Client[t.Any], command: CommandProto) -> None:
|
|
691
|
+
super()._request_option_locale(client, command)
|
|
674
692
|
|
|
675
|
-
|
|
693
|
+
for option in self.options.values():
|
|
694
|
+
option._request_option_locale(client, command)
|
|
676
695
|
|
|
677
696
|
async def __call__(self, ctx: Context[ClientT], *args: t.Any, **kwargs: t.Any) -> None:
|
|
678
697
|
"""Invoke this subcommand with the given context.
|
|
@@ -702,13 +721,13 @@ def slash_command(
|
|
|
702
721
|
name: str,
|
|
703
722
|
description: str = "No description provided.",
|
|
704
723
|
*,
|
|
705
|
-
guilds: t.Sequence[hikari.
|
|
706
|
-
is_dm_enabled: bool =
|
|
707
|
-
is_nsfw: bool =
|
|
708
|
-
autodefer: bool | AutodeferMode =
|
|
709
|
-
default_permissions: hikari.
|
|
710
|
-
name_localizations:
|
|
711
|
-
description_localizations:
|
|
724
|
+
guilds: t.Sequence[hikari.PartialGuild | hikari.Snowflakeish] | hikari.UndefinedType = hikari.UNDEFINED,
|
|
725
|
+
is_dm_enabled: bool | hikari.UndefinedType = hikari.UNDEFINED,
|
|
726
|
+
is_nsfw: bool | hikari.UndefinedType = hikari.UNDEFINED,
|
|
727
|
+
autodefer: bool | AutodeferMode | hikari.UndefinedType = hikari.UNDEFINED,
|
|
728
|
+
default_permissions: hikari.Permissions | hikari.UndefinedType = hikari.UNDEFINED,
|
|
729
|
+
name_localizations: t.Mapping[hikari.Locale, str] | None = None,
|
|
730
|
+
description_localizations: t.Mapping[hikari.Locale, str] | None = None,
|
|
712
731
|
) -> t.Callable[[t.Callable[t.Concatenate[Context[ClientT], ...], t.Awaitable[None]]], SlashCommand[ClientT]]:
|
|
713
732
|
"""A decorator that creates a slash command.
|
|
714
733
|
|
|
@@ -716,49 +735,52 @@ def slash_command(
|
|
|
716
735
|
----------
|
|
717
736
|
name : str
|
|
718
737
|
The name of the slash command.
|
|
719
|
-
description : str
|
|
720
|
-
The description of the command
|
|
721
|
-
guilds : t.Sequence[hikari.
|
|
722
|
-
The guilds this command should be enabled in, if left as
|
|
723
|
-
is_dm_enabled : bool
|
|
724
|
-
If True, the command is usable in direct messages
|
|
725
|
-
is_nsfw : bool
|
|
726
|
-
If True, the command is only usable in NSFW channels
|
|
727
|
-
autodefer : bool | AutodeferMode
|
|
728
|
-
If True, this command will be automatically deferred if it takes longer than 2 seconds to respond
|
|
729
|
-
default_permissions : hikari.
|
|
730
|
-
The default permissions required to use this command, these can be overriden by guild admins
|
|
731
|
-
name_localizations : dict[hikari.Locale, str] | None
|
|
732
|
-
Localizations for the name of this command
|
|
733
|
-
description_localizations : dict[hikari.Locale, str] | None
|
|
734
|
-
Localizations for the description of this command
|
|
738
|
+
description : str
|
|
739
|
+
The description of the command
|
|
740
|
+
guilds : t.Sequence[hikari.PartialGuild | hikari.Snowflakeish] | hikari.UndefinedType
|
|
741
|
+
The guilds this command should be enabled in, if left as undefined, the command is global
|
|
742
|
+
is_dm_enabled : bool | hikari.UndefinedType
|
|
743
|
+
If True, the command is usable in direct messages
|
|
744
|
+
is_nsfw : bool | hikari.UndefinedType
|
|
745
|
+
If True, the command is only usable in NSFW channels
|
|
746
|
+
autodefer : bool | AutodeferMode | hikari.UndefinedType
|
|
747
|
+
If True, this command will be automatically deferred if it takes longer than 2 seconds to respond
|
|
748
|
+
default_permissions : hikari.Permissions | hikari.UndefinedType
|
|
749
|
+
The default permissions required to use this command, these can be overriden by guild admins
|
|
750
|
+
name_localizations : dict[hikari.Locale, str] | None
|
|
751
|
+
Localizations for the name of this command
|
|
752
|
+
description_localizations : dict[hikari.Locale, str] | None
|
|
753
|
+
Localizations for the description of this command
|
|
735
754
|
|
|
736
755
|
Returns
|
|
737
756
|
-------
|
|
738
757
|
t.Callable[[t.Callable[t.Concatenate[Context[ClientT], ...], t.Awaitable[None]]], SlashCommand[ClientT]]
|
|
739
758
|
The decorated slash command.
|
|
740
759
|
|
|
760
|
+
!!! note
|
|
761
|
+
Parameters left as `hikari.UNDEFINED` will be inherited from the parent plugin or client.
|
|
762
|
+
|
|
741
763
|
Usage
|
|
742
764
|
-----
|
|
743
765
|
```py
|
|
744
766
|
@client.include
|
|
745
|
-
@arc.slash_command(
|
|
767
|
+
@arc.slash_command("hi", "Say hi!")
|
|
746
768
|
async def hi_slash(
|
|
747
769
|
ctx: arc.GatewayContext,
|
|
748
|
-
user: arc.Option[hikari.User, arc.UserParams(
|
|
770
|
+
user: arc.Option[hikari.User, arc.UserParams("The user to say hi to.")]
|
|
749
771
|
) -> None:
|
|
750
772
|
await ctx.respond(f"Hey {user.mention}!")
|
|
751
773
|
```
|
|
752
774
|
"""
|
|
753
775
|
|
|
754
776
|
def decorator(func: t.Callable[t.Concatenate[Context[ClientT], ...], t.Awaitable[None]]) -> SlashCommand[ClientT]:
|
|
755
|
-
guild_ids =
|
|
777
|
+
guild_ids = tuple(hikari.Snowflake(i) for i in guilds) if guilds is not hikari.UNDEFINED else hikari.UNDEFINED
|
|
756
778
|
options = parse_command_signature(func)
|
|
757
779
|
|
|
758
780
|
return SlashCommand(
|
|
759
781
|
callback=func,
|
|
760
782
|
options=options,
|
|
761
|
-
autodefer=AutodeferMode(autodefer),
|
|
783
|
+
autodefer=AutodeferMode(autodefer) if isinstance(autodefer, bool) else autodefer,
|
|
762
784
|
name=name,
|
|
763
785
|
description=description,
|
|
764
786
|
name_localizations=name_localizations or {},
|
|
@@ -777,8 +799,8 @@ def slash_subcommand(
|
|
|
777
799
|
description: str = "No description provided.",
|
|
778
800
|
*,
|
|
779
801
|
autodefer: bool | AutodeferMode | hikari.UndefinedType = hikari.UNDEFINED,
|
|
780
|
-
name_localizations:
|
|
781
|
-
description_localizations:
|
|
802
|
+
name_localizations: t.Mapping[hikari.Locale, str] | None = None,
|
|
803
|
+
description_localizations: t.Mapping[hikari.Locale, str] | None = None,
|
|
782
804
|
) -> t.Callable[[t.Callable[t.Concatenate[Context[ClientT], ...], t.Awaitable[None]]], SlashSubCommand[ClientT]]:
|
|
783
805
|
"""A decorator that creates a slash sub command. It should be included in a slash command group.
|
|
784
806
|
|
|
@@ -786,21 +808,24 @@ def slash_subcommand(
|
|
|
786
808
|
----------
|
|
787
809
|
name : str
|
|
788
810
|
The name of the slash command.
|
|
789
|
-
description : str
|
|
790
|
-
The description of the command
|
|
791
|
-
autodefer : bool | AutodeferMode | hikari.UndefinedType
|
|
792
|
-
If True, this command will be automatically deferred if it takes longer than 2 seconds to respond
|
|
811
|
+
description : str
|
|
812
|
+
The description of the command
|
|
813
|
+
autodefer : bool | AutodeferMode | hikari.UndefinedType
|
|
814
|
+
If True, this command will be automatically deferred if it takes longer than 2 seconds to respond
|
|
793
815
|
If undefined, then this setting will be be inherited from the parent
|
|
794
|
-
name_localizations : dict[hikari.Locale, str] | None
|
|
795
|
-
Localizations for the name of this command
|
|
796
|
-
description_localizations : dict[hikari.Locale, str] | None
|
|
797
|
-
Localizations for the description of this command
|
|
816
|
+
name_localizations : dict[hikari.Locale, str] | None
|
|
817
|
+
Localizations for the name of this command
|
|
818
|
+
description_localizations : dict[hikari.Locale, str] | None
|
|
819
|
+
Localizations for the description of this command
|
|
798
820
|
|
|
799
821
|
Returns
|
|
800
822
|
-------
|
|
801
823
|
t.Callable[[t.Callable[t.Concatenate[Context[ClientT], ...], t.Awaitable[None]]], SlashCommand[ClientT]]
|
|
802
824
|
The decorated slash command.
|
|
803
825
|
|
|
826
|
+
!!! note
|
|
827
|
+
Parameters left as `hikari.UNDEFINED` will be inherited from the parent group, plugin or client.
|
|
828
|
+
|
|
804
829
|
Usage
|
|
805
830
|
-----
|
|
806
831
|
```py
|
|
@@ -825,7 +850,7 @@ def slash_subcommand(
|
|
|
825
850
|
callback=func,
|
|
826
851
|
options=options,
|
|
827
852
|
name=name,
|
|
828
|
-
autodefer=AutodeferMode(autodefer) if autodefer else
|
|
853
|
+
autodefer=AutodeferMode(autodefer) if isinstance(autodefer, bool) else autodefer,
|
|
829
854
|
description=description,
|
|
830
855
|
name_localizations=name_localizations or {},
|
|
831
856
|
description_localizations=description_localizations or {},
|