hikari-arc 0.5.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 +8 -2
- arc/abc/__init__.py +10 -1
- arc/abc/client.py +116 -30
- arc/abc/command.py +54 -10
- arc/abc/error_handler.py +10 -2
- arc/abc/limiter.py +5 -16
- arc/abc/option.py +71 -3
- arc/abc/plugin.py +92 -0
- arc/client.py +3 -3
- arc/command/message.py +4 -4
- arc/command/option/attachment.py +3 -3
- arc/command/option/bool.py +3 -4
- arc/command/option/channel.py +3 -3
- arc/command/option/float.py +4 -4
- arc/command/option/int.py +4 -4
- arc/command/option/mentionable.py +3 -3
- arc/command/option/role.py +3 -3
- arc/command/option/str.py +4 -4
- arc/command/option/user.py +3 -3
- arc/command/slash.py +116 -115
- arc/command/user.py +3 -3
- arc/context/base.py +114 -20
- arc/errors.py +31 -12
- arc/events.py +5 -1
- arc/internal/about.py +1 -1
- arc/internal/options.py +106 -0
- arc/internal/sigparse.py +19 -1
- arc/internal/sync.py +7 -2
- arc/internal/types.py +7 -12
- arc/plugin.py +2 -2
- arc/utils/__init__.py +4 -1
- arc/utils/hooks/__init__.py +2 -2
- arc/utils/hooks/limiters.py +70 -202
- arc/utils/ratelimiter.py +243 -0
- {hikari_arc-0.5.0.dist-info → hikari_arc-0.6.0.dist-info}/METADATA +11 -6
- hikari_arc-0.6.0.dist-info/RECORD +52 -0
- hikari_arc-0.5.0.dist-info/RECORD +0 -50
- {hikari_arc-0.5.0.dist-info → hikari_arc-0.6.0.dist-info}/LICENSE +0 -0
- {hikari_arc-0.5.0.dist-info → hikari_arc-0.6.0.dist-info}/WHEEL +0 -0
- {hikari_arc-0.5.0.dist-info → hikari_arc-0.6.0.dist-info}/top_level.txt +0 -0
arc/__init__.py
CHANGED
|
@@ -11,9 +11,9 @@ https://arc.hypergonial.com
|
|
|
11
11
|
from alluka import Client as Injector
|
|
12
12
|
from alluka import inject
|
|
13
13
|
|
|
14
|
-
from arc import abc, command
|
|
14
|
+
from arc import abc, command, utils
|
|
15
15
|
|
|
16
|
-
from .abc import HookResult, Option, with_hook, with_post_hook
|
|
16
|
+
from .abc import HookResult, Option, OptionType, with_hook, with_post_hook
|
|
17
17
|
from .client import (
|
|
18
18
|
Client,
|
|
19
19
|
GatewayClient,
|
|
@@ -51,7 +51,9 @@ from .errors import (
|
|
|
51
51
|
ArcError,
|
|
52
52
|
AutocompleteError,
|
|
53
53
|
CommandInvokeError,
|
|
54
|
+
CommandPublishFailedError,
|
|
54
55
|
DMOnlyError,
|
|
56
|
+
GuildCommandPublishFailedError,
|
|
55
57
|
GuildOnlyError,
|
|
56
58
|
InvokerMissingPermissionsError,
|
|
57
59
|
NotOwnerError,
|
|
@@ -121,6 +123,7 @@ __all__ = (
|
|
|
121
123
|
"RESTClientBase",
|
|
122
124
|
"GatewayClient",
|
|
123
125
|
"RESTClient",
|
|
126
|
+
"OptionType",
|
|
124
127
|
"ArcError",
|
|
125
128
|
"AutocompleteError",
|
|
126
129
|
"UnderCooldownError",
|
|
@@ -129,6 +132,8 @@ __all__ = (
|
|
|
129
132
|
"NotOwnerError",
|
|
130
133
|
"DMOnlyError",
|
|
131
134
|
"CommandInvokeError",
|
|
135
|
+
"GuildCommandPublishFailedError",
|
|
136
|
+
"CommandPublishFailedError",
|
|
132
137
|
"PluginBase",
|
|
133
138
|
"RESTPluginBase",
|
|
134
139
|
"GatewayPluginBase",
|
|
@@ -149,6 +154,7 @@ __all__ = (
|
|
|
149
154
|
"CommandLocaleRequest",
|
|
150
155
|
"OptionLocaleRequest",
|
|
151
156
|
"abc",
|
|
157
|
+
"utils",
|
|
152
158
|
"command",
|
|
153
159
|
"with_hook",
|
|
154
160
|
"with_post_hook",
|
arc/abc/__init__.py
CHANGED
|
@@ -2,7 +2,15 @@ from .client import Client
|
|
|
2
2
|
from .command import CallableCommandBase, CallableCommandProto, CommandBase, CommandProto
|
|
3
3
|
from .error_handler import HasErrorHandler
|
|
4
4
|
from .hookable import Hookable, HookResult, with_hook, with_post_hook
|
|
5
|
-
from .option import
|
|
5
|
+
from .option import (
|
|
6
|
+
CommandOptionBase,
|
|
7
|
+
Option,
|
|
8
|
+
OptionBase,
|
|
9
|
+
OptionParams,
|
|
10
|
+
OptionType,
|
|
11
|
+
OptionWithChoices,
|
|
12
|
+
OptionWithChoicesParams,
|
|
13
|
+
)
|
|
6
14
|
from .plugin import PluginBase
|
|
7
15
|
|
|
8
16
|
__all__ = (
|
|
@@ -13,6 +21,7 @@ __all__ = (
|
|
|
13
21
|
"CallableCommandBase",
|
|
14
22
|
"Option",
|
|
15
23
|
"OptionBase",
|
|
24
|
+
"OptionType",
|
|
16
25
|
"CommandOptionBase",
|
|
17
26
|
"OptionParams",
|
|
18
27
|
"OptionWithChoices",
|
arc/abc/client.py
CHANGED
|
@@ -18,7 +18,7 @@ import hikari
|
|
|
18
18
|
from arc.abc.command import _CommandSettings
|
|
19
19
|
from arc.abc.plugin import PluginBase
|
|
20
20
|
from arc.command.message import MessageCommand
|
|
21
|
-
from arc.command.slash import SlashCommand, SlashGroup
|
|
21
|
+
from arc.command.slash import SlashCommand, SlashGroup, SlashSubCommand, SlashSubGroup
|
|
22
22
|
from arc.command.user import UserCommand
|
|
23
23
|
from arc.context import AutodeferMode, Context
|
|
24
24
|
from arc.errors import ExtensionLoadError, ExtensionUnloadError
|
|
@@ -156,6 +156,20 @@ class Client(t.Generic[AppT], abc.ABC):
|
|
|
156
156
|
will return interaction response builders to be sent back to Discord, otherwise they will return None.
|
|
157
157
|
"""
|
|
158
158
|
|
|
159
|
+
@property
|
|
160
|
+
def _commands(self) -> t.Mapping[hikari.CommandType, t.Mapping[str, CommandBase[te.Self, t.Any]]]:
|
|
161
|
+
"""All top-level commands added to this client, categorized by command type.
|
|
162
|
+
|
|
163
|
+
!!! note
|
|
164
|
+
This does not include subcommands & subgroups due to implementation details, therefore
|
|
165
|
+
you should use [`Client.walk_commands()`][arc.abc.client.Client.walk_commands] instead.
|
|
166
|
+
"""
|
|
167
|
+
return {
|
|
168
|
+
hikari.CommandType.SLASH: self._slash_commands,
|
|
169
|
+
hikari.CommandType.MESSAGE: self._message_commands,
|
|
170
|
+
hikari.CommandType.USER: self._user_commands,
|
|
171
|
+
}
|
|
172
|
+
|
|
159
173
|
@property
|
|
160
174
|
def app(self) -> AppT:
|
|
161
175
|
"""The application this client is for."""
|
|
@@ -181,30 +195,6 @@ class Client(t.Generic[AppT], abc.ABC):
|
|
|
181
195
|
"""The guilds that slash commands will be registered in by default."""
|
|
182
196
|
return self._default_enabled_guilds
|
|
183
197
|
|
|
184
|
-
@property
|
|
185
|
-
def commands(self) -> t.Mapping[hikari.CommandType, t.Mapping[str, CommandBase[te.Self, t.Any]]]:
|
|
186
|
-
"""All commands added to this client, categorized by command type."""
|
|
187
|
-
return {
|
|
188
|
-
hikari.CommandType.SLASH: self.slash_commands,
|
|
189
|
-
hikari.CommandType.MESSAGE: self._message_commands,
|
|
190
|
-
hikari.CommandType.USER: self._user_commands,
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
@property
|
|
194
|
-
def slash_commands(self) -> t.Mapping[str, SlashCommandLike[te.Self]]:
|
|
195
|
-
"""The slash commands added to this client. This only includes top-level commands and groups."""
|
|
196
|
-
return self._slash_commands
|
|
197
|
-
|
|
198
|
-
@property
|
|
199
|
-
def message_commands(self) -> t.Mapping[str, MessageCommand[te.Self]]:
|
|
200
|
-
"""The message commands added to this client."""
|
|
201
|
-
return self._message_commands
|
|
202
|
-
|
|
203
|
-
@property
|
|
204
|
-
def user_commands(self) -> t.Mapping[str, UserCommand[te.Self]]:
|
|
205
|
-
"""The user commands added to this client."""
|
|
206
|
-
return self._user_commands
|
|
207
|
-
|
|
208
198
|
@property
|
|
209
199
|
def plugins(self) -> t.Mapping[str, PluginBase[te.Self]]:
|
|
210
200
|
"""The plugins added to this client."""
|
|
@@ -318,11 +308,11 @@ class Client(t.Generic[AppT], abc.ABC):
|
|
|
318
308
|
|
|
319
309
|
match interaction.command_type:
|
|
320
310
|
case hikari.CommandType.SLASH:
|
|
321
|
-
command = self.
|
|
311
|
+
command = self._slash_commands.get(interaction.command_name)
|
|
322
312
|
case hikari.CommandType.MESSAGE:
|
|
323
|
-
command = self.
|
|
313
|
+
command = self._message_commands.get(interaction.command_name)
|
|
324
314
|
case hikari.CommandType.USER:
|
|
325
|
-
command = self.
|
|
315
|
+
command = self._user_commands.get(interaction.command_name)
|
|
326
316
|
case _:
|
|
327
317
|
pass
|
|
328
318
|
|
|
@@ -372,7 +362,7 @@ class Client(t.Generic[AppT], abc.ABC):
|
|
|
372
362
|
hikari.api.InteractionAutocompleteBuilder | None
|
|
373
363
|
The autocomplete builder to send back to Discord, if using a REST client.
|
|
374
364
|
"""
|
|
375
|
-
command = self.
|
|
365
|
+
command = self._slash_commands.get(interaction.command_name)
|
|
376
366
|
|
|
377
367
|
if command is None:
|
|
378
368
|
logger.warning(f"Received autocomplete interaction for unknown command '{interaction.command_name}'.")
|
|
@@ -380,6 +370,98 @@ class Client(t.Generic[AppT], abc.ABC):
|
|
|
380
370
|
|
|
381
371
|
return await command._on_autocomplete(interaction)
|
|
382
372
|
|
|
373
|
+
@t.overload
|
|
374
|
+
def walk_commands(
|
|
375
|
+
self, command_type: t.Literal[hikari.CommandType.USER], *, callable_only: bool = False
|
|
376
|
+
) -> t.Iterator[UserCommand[te.Self]]:
|
|
377
|
+
...
|
|
378
|
+
|
|
379
|
+
@t.overload
|
|
380
|
+
def walk_commands(
|
|
381
|
+
self, command_type: t.Literal[hikari.CommandType.MESSAGE], *, callable_only: bool = False
|
|
382
|
+
) -> t.Iterator[MessageCommand[te.Self]]:
|
|
383
|
+
...
|
|
384
|
+
|
|
385
|
+
@t.overload
|
|
386
|
+
def walk_commands(
|
|
387
|
+
self, command_type: t.Literal[hikari.CommandType.SLASH], *, callable_only: t.Literal[False] = False
|
|
388
|
+
) -> t.Iterator[SlashCommand[te.Self] | SlashSubCommand[te.Self] | SlashGroup[te.Self] | SlashSubGroup[te.Self]]:
|
|
389
|
+
...
|
|
390
|
+
|
|
391
|
+
@t.overload
|
|
392
|
+
def walk_commands(
|
|
393
|
+
self, command_type: t.Literal[hikari.CommandType.SLASH], *, callable_only: t.Literal[True] = True
|
|
394
|
+
) -> t.Iterator[SlashCommand[te.Self] | SlashSubCommand[te.Self]]:
|
|
395
|
+
...
|
|
396
|
+
|
|
397
|
+
def walk_commands( # noqa: C901
|
|
398
|
+
self, command_type: hikari.CommandType, *, callable_only: bool = False
|
|
399
|
+
) -> t.Iterator[t.Any]:
|
|
400
|
+
"""Iterate over all commands of a certain type added to this plugin.
|
|
401
|
+
|
|
402
|
+
Parameters
|
|
403
|
+
----------
|
|
404
|
+
command_type : hikari.CommandType
|
|
405
|
+
The type of commands to return.
|
|
406
|
+
callable_only : bool
|
|
407
|
+
Whether to only return commands that are directly callable.
|
|
408
|
+
If True, command groups and subgroups will be skipped.
|
|
409
|
+
This is only applicable to slash commands.
|
|
410
|
+
|
|
411
|
+
Yields
|
|
412
|
+
------
|
|
413
|
+
CommandT[te.Self]
|
|
414
|
+
The next command that matches the given criteria.
|
|
415
|
+
|
|
416
|
+
Usage
|
|
417
|
+
-----
|
|
418
|
+
```py
|
|
419
|
+
for cmd in plugin.walk_commands(hikari.CommandType.SLASH):
|
|
420
|
+
print(cmd.name)
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
!!! tip
|
|
424
|
+
To iterate over all types of commands, you may use [`itertools.chain()`][itertools.chain]:
|
|
425
|
+
|
|
426
|
+
```py
|
|
427
|
+
import itertools
|
|
428
|
+
|
|
429
|
+
for cmd in itertools.chain(
|
|
430
|
+
plugin.walk_commands(hikari.CommandType.SLASH),
|
|
431
|
+
plugin.walk_commands(hikari.CommandType.MESSAGE),
|
|
432
|
+
plugin.walk_commands(hikari.CommandType.USER),
|
|
433
|
+
):
|
|
434
|
+
print(cmd.name)
|
|
435
|
+
```
|
|
436
|
+
"""
|
|
437
|
+
if hikari.CommandType.SLASH is command_type:
|
|
438
|
+
for command in self._slash_commands.values():
|
|
439
|
+
if isinstance(command, SlashCommand):
|
|
440
|
+
yield command
|
|
441
|
+
continue
|
|
442
|
+
|
|
443
|
+
if not callable_only:
|
|
444
|
+
yield command
|
|
445
|
+
|
|
446
|
+
for sub in command.children.values():
|
|
447
|
+
if isinstance(sub, SlashSubCommand):
|
|
448
|
+
yield sub
|
|
449
|
+
continue
|
|
450
|
+
|
|
451
|
+
if not callable_only:
|
|
452
|
+
yield sub
|
|
453
|
+
|
|
454
|
+
for subsub in sub.children.values():
|
|
455
|
+
yield subsub
|
|
456
|
+
|
|
457
|
+
elif hikari.CommandType.MESSAGE is command_type:
|
|
458
|
+
for command in self._message_commands.values():
|
|
459
|
+
yield command
|
|
460
|
+
|
|
461
|
+
elif hikari.CommandType.USER is command_type:
|
|
462
|
+
for command in self._user_commands.values():
|
|
463
|
+
yield command
|
|
464
|
+
|
|
383
465
|
@t.overload
|
|
384
466
|
def include(self) -> t.Callable[[CommandBase[te.Self, BuilderT]], CommandBase[te.Self, BuilderT]]:
|
|
385
467
|
...
|
|
@@ -423,7 +505,7 @@ class Client(t.Generic[AppT], abc.ABC):
|
|
|
423
505
|
f"\nYou should use '{type(self).__name__}.add_plugin()' to add the entire plugin to the client."
|
|
424
506
|
)
|
|
425
507
|
|
|
426
|
-
if existing := self.
|
|
508
|
+
if existing := self._commands[command.command_type].get(command.name):
|
|
427
509
|
logger.warning(
|
|
428
510
|
f"Shadowing already registered command '{command.name}'. Did you define multiple commands with the same name?"
|
|
429
511
|
)
|
|
@@ -663,6 +745,10 @@ class Client(t.Generic[AppT], abc.ABC):
|
|
|
663
745
|
await ctx.respond(f"❌ Something went wrong: {exception}")
|
|
664
746
|
```
|
|
665
747
|
|
|
748
|
+
!!! warning
|
|
749
|
+
Errors that cannot be handled by the error handler should be re-raised.
|
|
750
|
+
Otherwise tracebacks will not be printed to stderr.
|
|
751
|
+
|
|
666
752
|
Or, as a function:
|
|
667
753
|
|
|
668
754
|
```py
|
arc/abc/command.py
CHANGED
|
@@ -13,6 +13,7 @@ from arc.abc.hookable import Hookable, HookResult
|
|
|
13
13
|
from arc.abc.limiter import LimiterProto
|
|
14
14
|
from arc.abc.option import OptionBase
|
|
15
15
|
from arc.context import AutodeferMode
|
|
16
|
+
from arc.errors import CommandPublishFailedError
|
|
16
17
|
from arc.internal.types import (
|
|
17
18
|
BuilderT,
|
|
18
19
|
ClientT,
|
|
@@ -49,6 +50,15 @@ class CommandProto(t.Protocol):
|
|
|
49
50
|
def qualified_name(self) -> t.Sequence[str]:
|
|
50
51
|
"""The fully qualified name of this command."""
|
|
51
52
|
|
|
53
|
+
@property
|
|
54
|
+
@abc.abstractmethod
|
|
55
|
+
def display_name(self) -> str:
|
|
56
|
+
"""The display name of this command. This is what is shown in the Discord client.
|
|
57
|
+
|
|
58
|
+
!!! note
|
|
59
|
+
Slash commands can also be mentioned, see [SlashCommand.make_mention][arc.command.SlashCommand.make_mention].
|
|
60
|
+
"""
|
|
61
|
+
|
|
52
62
|
|
|
53
63
|
class CallableCommandProto(CommandProto, t.Protocol[ClientT]):
|
|
54
64
|
"""A protocol for any command-like object that can be called directly.
|
|
@@ -74,6 +84,15 @@ class CallableCommandProto(CommandProto, t.Protocol[ClientT]):
|
|
|
74
84
|
def qualified_name(self) -> t.Sequence[str]:
|
|
75
85
|
"""The fully qualified name of this command."""
|
|
76
86
|
|
|
87
|
+
@property
|
|
88
|
+
@abc.abstractmethod
|
|
89
|
+
def display_name(self) -> str:
|
|
90
|
+
"""The display name of this command. This is what is shown in the Discord client.
|
|
91
|
+
|
|
92
|
+
!!! note
|
|
93
|
+
Slash commands can also be mentioned, see [SlashCommand.make_mention][arc.command.SlashCommand.make_mention].
|
|
94
|
+
"""
|
|
95
|
+
|
|
77
96
|
@property
|
|
78
97
|
@abc.abstractmethod
|
|
79
98
|
def hooks(self) -> t.MutableSequence[HookT[ClientT]]:
|
|
@@ -260,7 +279,7 @@ class CommandBase(HasErrorHandler[ClientT], Hookable[ClientT], t.Generic[ClientT
|
|
|
260
279
|
"""The client that is handling this command."""
|
|
261
280
|
if self._client is None:
|
|
262
281
|
raise RuntimeError(
|
|
263
|
-
f"Command '{self.
|
|
282
|
+
f"Command '{self.display_name}' was not included in a client, '{type(self).__name__}.client' cannot be accessed until it is included in a client."
|
|
264
283
|
)
|
|
265
284
|
return self._client
|
|
266
285
|
|
|
@@ -292,6 +311,20 @@ class CommandBase(HasErrorHandler[ClientT], Hookable[ClientT], t.Generic[ClientT
|
|
|
292
311
|
settings = self._resolve_settings()
|
|
293
312
|
return settings.is_nsfw if settings.is_nsfw is not hikari.UNDEFINED else False
|
|
294
313
|
|
|
314
|
+
@property
|
|
315
|
+
def instances(self) -> t.Mapping[hikari.Snowflake | None, hikari.PartialCommand]:
|
|
316
|
+
"""A mapping of guild IDs to command instances. None corresponds to the global instance, if any."""
|
|
317
|
+
return self._instances
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
def display_name(self) -> str:
|
|
321
|
+
"""The display name of this command. This is what is shown in the Discord client.
|
|
322
|
+
|
|
323
|
+
!!! note
|
|
324
|
+
Slash commands can also be mentioned, see [SlashCommand.make_mention][arc.command.SlashCommand.make_mention].
|
|
325
|
+
"""
|
|
326
|
+
return self.name
|
|
327
|
+
|
|
295
328
|
def _register_instance(
|
|
296
329
|
self, instance: hikari.PartialCommand, guild: hikari.SnowflakeishOr[hikari.PartialGuild] | None = None
|
|
297
330
|
) -> None:
|
|
@@ -352,13 +385,15 @@ class CommandBase(HasErrorHandler[ClientT], Hookable[ClientT], t.Generic[ClientT
|
|
|
352
385
|
raise RuntimeError("Cannot publish command without a client.")
|
|
353
386
|
|
|
354
387
|
kwargs = self._to_dict()
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
388
|
+
try:
|
|
389
|
+
if self.command_type is hikari.CommandType.SLASH:
|
|
390
|
+
created = await self.client.app.rest.create_slash_command(self.client.application, **kwargs)
|
|
391
|
+
else:
|
|
392
|
+
created = await self.client.app.rest.create_context_menu_command(
|
|
393
|
+
self.client.application, type=self.command_type, **kwargs
|
|
394
|
+
)
|
|
395
|
+
except Exception as e:
|
|
396
|
+
raise CommandPublishFailedError(self, f"Failed to publish command '{self.display_name}'") from e
|
|
362
397
|
|
|
363
398
|
self._instances[hikari.Snowflake(guild) if guild else None] = created
|
|
364
399
|
|
|
@@ -427,8 +462,8 @@ class CommandBase(HasErrorHandler[ClientT], Hookable[ClientT], t.Generic[ClientT
|
|
|
427
462
|
|
|
428
463
|
def _client_remove_hook(self, client: ClientT) -> None:
|
|
429
464
|
"""Called when the client requests the command be removed from it."""
|
|
430
|
-
self._client = None
|
|
431
465
|
self.client._remove_command(self)
|
|
466
|
+
self._client = None
|
|
432
467
|
|
|
433
468
|
def _plugin_include_hook(self, plugin: PluginBase[ClientT]) -> None:
|
|
434
469
|
"""Called when the plugin requests the command be added to it."""
|
|
@@ -517,7 +552,7 @@ class CallableCommandBase(CommandBase[ClientT, BuilderT], CallableCommandProto[C
|
|
|
517
552
|
callback: CommandCallbackT[ClientT]
|
|
518
553
|
"""The callback to invoke when this command is called."""
|
|
519
554
|
|
|
520
|
-
_invoke_task: asyncio.Task[t.Any] | None = attr.field(init=False, default=None)
|
|
555
|
+
_invoke_task: asyncio.Task[t.Any] | None = attr.field(init=False, default=None, repr=False)
|
|
521
556
|
|
|
522
557
|
def reset_all_limiters(self, context: Context[ClientT]) -> None:
|
|
523
558
|
"""Reset all limiters for this command.
|
|
@@ -563,6 +598,15 @@ class SubCommandBase(OptionBase[ClientT], HasErrorHandler[ClientT], Hookable[Cli
|
|
|
563
598
|
_parent: ParentT | None = attr.field(default=None, init=False, alias="parent")
|
|
564
599
|
"""The parent of this subcommand or subgroup."""
|
|
565
600
|
|
|
601
|
+
@property
|
|
602
|
+
@abc.abstractmethod
|
|
603
|
+
def display_name(self) -> str:
|
|
604
|
+
"""The display name of this command. This is what is shown in the Discord client.
|
|
605
|
+
|
|
606
|
+
!!! note
|
|
607
|
+
Slash commands can also be mentioned, see [SlashCommand.make_mention][arc.command.SlashCommand.make_mention].
|
|
608
|
+
"""
|
|
609
|
+
|
|
566
610
|
@property
|
|
567
611
|
def error_handler(self) -> ErrorHandlerCallbackT[ClientT] | None:
|
|
568
612
|
"""The error handler for this object."""
|
arc/abc/error_handler.py
CHANGED
|
@@ -45,12 +45,20 @@ class HasErrorHandler(abc.ABC, t.Generic[ClientT]):
|
|
|
45
45
|
@client.include
|
|
46
46
|
@arc.slash_command("foo", "Foo command description")
|
|
47
47
|
async def foo(ctx: arc.GatewayContext) -> None:
|
|
48
|
-
raise
|
|
48
|
+
raise RuntimeError("foo")
|
|
49
49
|
|
|
50
50
|
@foo.set_error_handler
|
|
51
51
|
async def foo_error_handler(ctx: arc.GatewayContext, exc: Exception) -> None:
|
|
52
|
-
|
|
52
|
+
if isinstance(exc, RuntimeError):
|
|
53
|
+
await ctx.respond("foo failed")
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
raise exc
|
|
53
57
|
```
|
|
58
|
+
|
|
59
|
+
!!! warning
|
|
60
|
+
Errors that cannot be handled by the error handler should be re-raised.
|
|
61
|
+
Otherwise they will not propagate to the next error handler.
|
|
54
62
|
"""
|
|
55
63
|
|
|
56
64
|
def decorator(func: ErrorHandlerCallbackT[ClientT]) -> ErrorHandlerCallbackT[ClientT]:
|
arc/abc/limiter.py
CHANGED
|
@@ -12,12 +12,15 @@ if t.TYPE_CHECKING:
|
|
|
12
12
|
|
|
13
13
|
@t.runtime_checkable
|
|
14
14
|
class LimiterProto(t.Protocol, t.Generic[ClientT]):
|
|
15
|
-
"""A protocol that all
|
|
15
|
+
"""A protocol that all limiter hooks should implement.
|
|
16
16
|
A limiter is simply a special type of hook with added methods.
|
|
17
17
|
|
|
18
18
|
If you're looking to integrate your own ratelimiter implementation,
|
|
19
19
|
you should make sure to implement all methods defined here.
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
!!! tip
|
|
22
|
+
An easy (but not necessary) way to ensure you've implemented all methods
|
|
23
|
+
is to inherit from this protocol.
|
|
21
24
|
"""
|
|
22
25
|
|
|
23
26
|
@abc.abstractmethod
|
|
@@ -32,20 +35,6 @@ class LimiterProto(t.Protocol, t.Generic[ClientT]):
|
|
|
32
35
|
The context to evaluate the ratelimit under.
|
|
33
36
|
"""
|
|
34
37
|
|
|
35
|
-
@abc.abstractmethod
|
|
36
|
-
async def acquire(self, ctx: Context[ClientT], *, wait: bool = True) -> None:
|
|
37
|
-
"""Acquire the limiter with the given context.
|
|
38
|
-
Implementations should raise an exception if the limiter is ratelimited
|
|
39
|
-
and wait is False.
|
|
40
|
-
|
|
41
|
-
Parameters
|
|
42
|
-
----------
|
|
43
|
-
ctx : Context
|
|
44
|
-
The context to evaluate the ratelimit under.
|
|
45
|
-
wait : bool
|
|
46
|
-
Whether or not to block until the limiter is available.
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
38
|
@abc.abstractmethod
|
|
50
39
|
def reset(self, ctx: Context[ClientT]) -> None:
|
|
51
40
|
"""Reset the limiter for the given context.
|
arc/abc/option.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
|
+
import enum
|
|
4
5
|
import typing as t
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
@@ -16,7 +17,15 @@ if t.TYPE_CHECKING:
|
|
|
16
17
|
from arc.abc.client import Client
|
|
17
18
|
from arc.abc.command import CommandProto
|
|
18
19
|
|
|
19
|
-
__all__ = (
|
|
20
|
+
__all__ = (
|
|
21
|
+
"Option",
|
|
22
|
+
"OptionParams",
|
|
23
|
+
"OptionWithChoices",
|
|
24
|
+
"OptionWithChoicesParams",
|
|
25
|
+
"OptionBase",
|
|
26
|
+
"CommandOptionBase",
|
|
27
|
+
"OptionType",
|
|
28
|
+
)
|
|
20
29
|
|
|
21
30
|
T = t.TypeVar("T")
|
|
22
31
|
|
|
@@ -37,6 +46,65 @@ arc.Option[int, arc.IntParams(...)]
|
|
|
37
46
|
"""
|
|
38
47
|
|
|
39
48
|
|
|
49
|
+
class OptionType(enum.IntEnum):
|
|
50
|
+
"""The type of a command option.
|
|
51
|
+
|
|
52
|
+
This is practically identical to `hikari.OptionType` at the moment.
|
|
53
|
+
It may however be used in the future to define custom option types.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
SUB_COMMAND = 1
|
|
57
|
+
"""Denotes a command option where the value will be a sub command."""
|
|
58
|
+
|
|
59
|
+
SUB_COMMAND_GROUP = 2
|
|
60
|
+
"""Denotes a command option where the value will be a sub command group."""
|
|
61
|
+
|
|
62
|
+
STRING = 3
|
|
63
|
+
"""Denotes a command option where the value will be a string."""
|
|
64
|
+
|
|
65
|
+
INTEGER = 4
|
|
66
|
+
"""Denotes a command option where the value will be a int.
|
|
67
|
+
|
|
68
|
+
This is range limited between -2^53 and 2^53.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
BOOLEAN = 5
|
|
72
|
+
"""Denotes a command option where the value will be a bool."""
|
|
73
|
+
|
|
74
|
+
USER = 6
|
|
75
|
+
"""Denotes a command option where the value will be resolved to a user."""
|
|
76
|
+
|
|
77
|
+
CHANNEL = 7
|
|
78
|
+
"""Denotes a command option where the value will be resolved to a channel."""
|
|
79
|
+
|
|
80
|
+
ROLE = 8
|
|
81
|
+
"""Denotes a command option where the value will be resolved to a role."""
|
|
82
|
+
|
|
83
|
+
MENTIONABLE = 9
|
|
84
|
+
"""Denotes a command option where the value will be a snowflake ID."""
|
|
85
|
+
|
|
86
|
+
FLOAT = 10
|
|
87
|
+
"""Denotes a command option where the value will be a float.
|
|
88
|
+
|
|
89
|
+
This is range limited between -2^53 and 2^53.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
ATTACHMENT = 11
|
|
93
|
+
"""Denotes a command option where the value will be an attachment."""
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def from_hikari(cls, option_type: hikari.OptionType) -> OptionType:
|
|
97
|
+
"""Convert a hikari.OptionType to an OptionType."""
|
|
98
|
+
return cls(option_type.value)
|
|
99
|
+
|
|
100
|
+
def to_hikari(self) -> hikari.OptionType:
|
|
101
|
+
"""Convert an OptionType to a hikari.OptionType."""
|
|
102
|
+
# TODO: Map custom option types to their respective hikari.OptionType
|
|
103
|
+
return hikari.OptionType(self.value)
|
|
104
|
+
|
|
105
|
+
# TODO: When adding custom convertible option types, add them with an offset of 1000 or so
|
|
106
|
+
|
|
107
|
+
|
|
40
108
|
class OptionParams(t.Generic[T]):
|
|
41
109
|
"""The base class for all option parameters objects.
|
|
42
110
|
|
|
@@ -153,13 +221,13 @@ class OptionBase(abc.ABC, t.Generic[T]):
|
|
|
153
221
|
|
|
154
222
|
@property
|
|
155
223
|
@abc.abstractmethod
|
|
156
|
-
def option_type(self) ->
|
|
224
|
+
def option_type(self) -> OptionType:
|
|
157
225
|
"""The type of the option. Used to register the command."""
|
|
158
226
|
|
|
159
227
|
def _to_dict(self) -> dict[str, t.Any]:
|
|
160
228
|
"""Convert the option to a dictionary of kwargs that can be passed to hikari.CommandOption."""
|
|
161
229
|
return {
|
|
162
|
-
"type": self.option_type,
|
|
230
|
+
"type": self.option_type.to_hikari(),
|
|
163
231
|
"name": self.name,
|
|
164
232
|
"description": self.description,
|
|
165
233
|
"autocomplete": False,
|
arc/abc/plugin.py
CHANGED
|
@@ -12,6 +12,7 @@ from arc.abc.command import _CommandSettings
|
|
|
12
12
|
from arc.abc.error_handler import HasErrorHandler
|
|
13
13
|
from arc.abc.hookable import Hookable
|
|
14
14
|
from arc.command import MessageCommand, SlashCommand, SlashGroup, UserCommand
|
|
15
|
+
from arc.command.slash import SlashSubCommand, SlashSubGroup
|
|
15
16
|
from arc.context import AutodeferMode, Context
|
|
16
17
|
from arc.internal.types import BuilderT, ClientT, ErrorHandlerCallbackT, HookT, PostHookT, SlashCommandLike
|
|
17
18
|
|
|
@@ -368,6 +369,97 @@ class PluginBase(HasErrorHandler[ClientT], Hookable[ClientT]):
|
|
|
368
369
|
|
|
369
370
|
return decorator
|
|
370
371
|
|
|
372
|
+
@t.overload
|
|
373
|
+
def walk_commands(
|
|
374
|
+
self, command_type: t.Literal[hikari.CommandType.USER], *, callable_only: bool = False
|
|
375
|
+
) -> t.Iterator[UserCommand[ClientT]]:
|
|
376
|
+
...
|
|
377
|
+
|
|
378
|
+
@t.overload
|
|
379
|
+
def walk_commands(
|
|
380
|
+
self, command_type: t.Literal[hikari.CommandType.MESSAGE], *, callable_only: bool = False
|
|
381
|
+
) -> t.Iterator[MessageCommand[ClientT]]:
|
|
382
|
+
...
|
|
383
|
+
|
|
384
|
+
@t.overload
|
|
385
|
+
def walk_commands(
|
|
386
|
+
self, command_type: t.Literal[hikari.CommandType.SLASH], *, callable_only: t.Literal[False]
|
|
387
|
+
) -> t.Iterator[SlashCommand[ClientT] | SlashSubCommand[ClientT] | SlashGroup[ClientT] | SlashSubGroup[ClientT]]:
|
|
388
|
+
...
|
|
389
|
+
|
|
390
|
+
@t.overload
|
|
391
|
+
def walk_commands(
|
|
392
|
+
self, command_type: t.Literal[hikari.CommandType.SLASH], *, callable_only: t.Literal[True]
|
|
393
|
+
) -> t.Iterator[SlashCommand[ClientT] | SlashSubCommand[ClientT]]:
|
|
394
|
+
...
|
|
395
|
+
|
|
396
|
+
def walk_commands( # noqa: C901
|
|
397
|
+
self, command_type: hikari.CommandType, *, callable_only: bool = False
|
|
398
|
+
) -> t.Iterator[t.Any]:
|
|
399
|
+
"""Iterate over all commands of a certain type added to this plugin.
|
|
400
|
+
|
|
401
|
+
Parameters
|
|
402
|
+
----------
|
|
403
|
+
command_type : hikari.CommandType
|
|
404
|
+
The type of commands to return.
|
|
405
|
+
callable_only : bool
|
|
406
|
+
Whether to only return commands that are directly callable.
|
|
407
|
+
If True, command groups and subgroups will be skipped.
|
|
408
|
+
|
|
409
|
+
Yields
|
|
410
|
+
------
|
|
411
|
+
CommandT[ClientT]
|
|
412
|
+
The next command that matches the given criteria.
|
|
413
|
+
|
|
414
|
+
Usage
|
|
415
|
+
-----
|
|
416
|
+
```py
|
|
417
|
+
for cmd in plugin.walk_commands(hikari.CommandType.SLASH):
|
|
418
|
+
print(cmd.name)
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
!!! tip
|
|
422
|
+
To iterate over all types of commands, you may use [`itertools.chain()`][itertools.chain]:
|
|
423
|
+
|
|
424
|
+
```py
|
|
425
|
+
import itertools
|
|
426
|
+
|
|
427
|
+
for cmd in itertools.chain(
|
|
428
|
+
plugin.walk_commands(hikari.CommandType.SLASH),
|
|
429
|
+
plugin.walk_commands(hikari.CommandType.MESSAGE),
|
|
430
|
+
plugin.walk_commands(hikari.CommandType.USER),
|
|
431
|
+
):
|
|
432
|
+
print(cmd.name)
|
|
433
|
+
```
|
|
434
|
+
"""
|
|
435
|
+
if hikari.CommandType.SLASH is command_type:
|
|
436
|
+
for command in self._slash_commands.values():
|
|
437
|
+
if isinstance(command, SlashCommand):
|
|
438
|
+
yield command
|
|
439
|
+
continue
|
|
440
|
+
|
|
441
|
+
if not callable_only:
|
|
442
|
+
yield command
|
|
443
|
+
|
|
444
|
+
for sub in command.children.values():
|
|
445
|
+
if isinstance(sub, SlashSubCommand):
|
|
446
|
+
yield sub
|
|
447
|
+
continue
|
|
448
|
+
|
|
449
|
+
if not callable_only:
|
|
450
|
+
yield sub
|
|
451
|
+
|
|
452
|
+
for subsub in sub.children.values():
|
|
453
|
+
yield subsub
|
|
454
|
+
|
|
455
|
+
elif hikari.CommandType.MESSAGE is command_type:
|
|
456
|
+
for command in self._message_commands.values():
|
|
457
|
+
yield command
|
|
458
|
+
|
|
459
|
+
elif hikari.CommandType.USER is command_type:
|
|
460
|
+
for command in self._user_commands.values():
|
|
461
|
+
yield command
|
|
462
|
+
|
|
371
463
|
|
|
372
464
|
# MIT License
|
|
373
465
|
#
|