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/context/base.py
CHANGED
|
@@ -10,7 +10,9 @@ from contextlib import suppress
|
|
|
10
10
|
import attr
|
|
11
11
|
import hikari
|
|
12
12
|
|
|
13
|
+
from arc.abc.option import OptionType # noqa: TCH001 Needed for tests
|
|
13
14
|
from arc.errors import NoResponseIssuedError, ResponseAlreadyIssuedError
|
|
15
|
+
from arc.internal.options import OPTIONTYPE_TO_TYPE, resolve_snowflake_value
|
|
14
16
|
from arc.internal.types import ClientT, ResponseBuilderT
|
|
15
17
|
from arc.locale import CustomLocaleRequest
|
|
16
18
|
|
|
@@ -252,6 +254,7 @@ class Context(t.Generic[ClientT]):
|
|
|
252
254
|
"_created_at",
|
|
253
255
|
"_autodefer_task",
|
|
254
256
|
"_has_command_failed",
|
|
257
|
+
"_options",
|
|
255
258
|
)
|
|
256
259
|
|
|
257
260
|
def __init__(
|
|
@@ -260,6 +263,7 @@ class Context(t.Generic[ClientT]):
|
|
|
260
263
|
self._client = client
|
|
261
264
|
self._command = command
|
|
262
265
|
self._interaction: hikari.CommandInteraction = interaction
|
|
266
|
+
self._options: t.Sequence[hikari.CommandInteractionOption] | None = None
|
|
263
267
|
self._responses: t.MutableSequence[InteractionResponse] = []
|
|
264
268
|
self._resp_builder: asyncio.Future[ResponseBuilderT] = asyncio.Future()
|
|
265
269
|
self._issued_response: bool = False
|
|
@@ -272,11 +276,11 @@ class Context(t.Generic[ClientT]):
|
|
|
272
276
|
def interaction(self) -> hikari.CommandInteraction:
|
|
273
277
|
"""The underlying interaction object.
|
|
274
278
|
|
|
275
|
-
|
|
279
|
+
!!! warning
|
|
276
280
|
This should not be used directly in most cases, and is only exposed for advanced use cases.
|
|
277
281
|
|
|
278
|
-
If you use the interaction to create a response
|
|
279
|
-
you should disable the autodefer feature in your
|
|
282
|
+
If you use the interaction to create a response,
|
|
283
|
+
you should disable the autodefer feature in your command.
|
|
280
284
|
"""
|
|
281
285
|
return self._interaction
|
|
282
286
|
|
|
@@ -404,6 +408,82 @@ class Context(t.Generic[ClientT]):
|
|
|
404
408
|
"""Gets the channel this context represents, None if in a DM. Requires application cache."""
|
|
405
409
|
return self._interaction.get_channel()
|
|
406
410
|
|
|
411
|
+
@t.overload
|
|
412
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.ATTACHMENT]) -> hikari.Attachment | None:
|
|
413
|
+
...
|
|
414
|
+
|
|
415
|
+
@t.overload
|
|
416
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.MENTIONABLE]) -> hikari.User | hikari.Role | None:
|
|
417
|
+
...
|
|
418
|
+
|
|
419
|
+
@t.overload
|
|
420
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.USER]) -> hikari.User | None:
|
|
421
|
+
...
|
|
422
|
+
|
|
423
|
+
@t.overload
|
|
424
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.ROLE]) -> hikari.Role | None:
|
|
425
|
+
...
|
|
426
|
+
|
|
427
|
+
@t.overload
|
|
428
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.CHANNEL]) -> hikari.PartialChannel | None:
|
|
429
|
+
...
|
|
430
|
+
|
|
431
|
+
@t.overload
|
|
432
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.STRING]) -> str | None:
|
|
433
|
+
...
|
|
434
|
+
|
|
435
|
+
@t.overload
|
|
436
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.BOOLEAN]) -> bool | None:
|
|
437
|
+
...
|
|
438
|
+
|
|
439
|
+
@t.overload
|
|
440
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.FLOAT]) -> float | None:
|
|
441
|
+
...
|
|
442
|
+
|
|
443
|
+
@t.overload
|
|
444
|
+
def get_option(self, name: str, opt_type: t.Literal[OptionType.INTEGER]) -> int | None:
|
|
445
|
+
...
|
|
446
|
+
|
|
447
|
+
def get_option(self, name: str, opt_type: OptionType) -> t.Any | None:
|
|
448
|
+
"""Get the value of an option by name.
|
|
449
|
+
|
|
450
|
+
Parameters
|
|
451
|
+
----------
|
|
452
|
+
name : str
|
|
453
|
+
The name of the option.
|
|
454
|
+
opt_type : hikari.OptionType
|
|
455
|
+
The type of the option.
|
|
456
|
+
|
|
457
|
+
Returns
|
|
458
|
+
-------
|
|
459
|
+
ValueT | None
|
|
460
|
+
The value of the option, or None if it does not exist, or is not of the correct type.
|
|
461
|
+
|
|
462
|
+
Usage
|
|
463
|
+
-----
|
|
464
|
+
```py
|
|
465
|
+
value = ctx.get_option("name", arc.OptionType.STRING)
|
|
466
|
+
if value is None:
|
|
467
|
+
# Option does not exist or is not a string
|
|
468
|
+
|
|
469
|
+
# Do something with the value
|
|
470
|
+
print(value)
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
"""
|
|
474
|
+
if self._options is None:
|
|
475
|
+
return None
|
|
476
|
+
|
|
477
|
+
value = next((x.value for x in self._options if x.name == name), None)
|
|
478
|
+
|
|
479
|
+
if value is hikari.Snowflake and self._interaction.resolved is not None:
|
|
480
|
+
value = resolve_snowflake_value(value, opt_type, self._interaction.resolved)
|
|
481
|
+
|
|
482
|
+
if not isinstance(value, OPTIONTYPE_TO_TYPE[opt_type]):
|
|
483
|
+
return None
|
|
484
|
+
|
|
485
|
+
return value
|
|
486
|
+
|
|
407
487
|
def loc(self, key: str, use_guild: bool = True, force_locale: hikari.Locale | None = None, **kwargs: t.Any) -> str:
|
|
408
488
|
"""Get a localized string using the interaction's locale.
|
|
409
489
|
|
|
@@ -578,7 +658,8 @@ class Context(t.Generic[ClientT]):
|
|
|
578
658
|
...
|
|
579
659
|
|
|
580
660
|
async def respond_with_builder(self, builder: ResponseBuilderT) -> InteractionResponse | None:
|
|
581
|
-
"""Respond to the interaction with a builder.
|
|
661
|
+
"""Respond to the interaction with a builder. This method will try to turn the builder into a valid
|
|
662
|
+
response or followup, depending on the builder type and interaction state.
|
|
582
663
|
|
|
583
664
|
Parameters
|
|
584
665
|
----------
|
|
@@ -593,13 +674,13 @@ class Context(t.Generic[ClientT]):
|
|
|
593
674
|
Raises
|
|
594
675
|
------
|
|
595
676
|
RuntimeError
|
|
596
|
-
The interaction was already issued an initial response.
|
|
677
|
+
The interaction was already issued an initial response and the builder can only be used for initial responses.
|
|
597
678
|
"""
|
|
598
679
|
async with self._response_lock:
|
|
599
|
-
if self._issued_response:
|
|
680
|
+
if self._issued_response and not isinstance(builder, hikari.api.InteractionMessageBuilder):
|
|
600
681
|
raise RuntimeError("This interaction was already issued an initial response.")
|
|
601
682
|
|
|
602
|
-
if self.client.is_rest:
|
|
683
|
+
if self.client.is_rest and not self._issued_response:
|
|
603
684
|
self._resp_builder.set_result(builder)
|
|
604
685
|
self._issued_response = True
|
|
605
686
|
if not isinstance(builder, hikari.api.InteractionModalBuilder):
|
|
@@ -608,25 +689,38 @@ class Context(t.Generic[ClientT]):
|
|
|
608
689
|
return
|
|
609
690
|
|
|
610
691
|
if isinstance(builder, hikari.api.InteractionMessageBuilder):
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
692
|
+
if not self._issued_response:
|
|
693
|
+
await self.interaction.create_initial_response(
|
|
694
|
+
response_type=hikari.ResponseType.MESSAGE_CREATE,
|
|
695
|
+
content=builder.content,
|
|
696
|
+
tts=builder.is_tts,
|
|
697
|
+
components=builder.components,
|
|
698
|
+
attachments=builder.attachments,
|
|
699
|
+
embeds=builder.embeds,
|
|
700
|
+
mentions_everyone=builder.mentions_everyone,
|
|
701
|
+
user_mentions=builder.user_mentions,
|
|
702
|
+
role_mentions=builder.role_mentions,
|
|
703
|
+
flags=builder.flags,
|
|
704
|
+
)
|
|
705
|
+
else:
|
|
706
|
+
await self.interaction.execute(
|
|
707
|
+
content=builder.content,
|
|
708
|
+
tts=builder.is_tts,
|
|
709
|
+
components=builder.components or hikari.UNDEFINED,
|
|
710
|
+
attachments=builder.attachments or hikari.UNDEFINED,
|
|
711
|
+
embeds=builder.embeds or hikari.UNDEFINED,
|
|
712
|
+
mentions_everyone=builder.mentions_everyone,
|
|
713
|
+
user_mentions=builder.user_mentions,
|
|
714
|
+
role_mentions=builder.role_mentions,
|
|
715
|
+
flags=builder.flags,
|
|
716
|
+
)
|
|
623
717
|
elif isinstance(builder, hikari.api.InteractionDeferredBuilder):
|
|
624
718
|
await self.interaction.create_initial_response(
|
|
625
719
|
response_type=hikari.ResponseType.DEFERRED_MESSAGE_CREATE, flags=builder.flags
|
|
626
720
|
)
|
|
627
721
|
else:
|
|
628
722
|
await self.interaction.create_modal_response(
|
|
629
|
-
title=builder.title, custom_id=builder.custom_id, components=builder.components
|
|
723
|
+
title=builder.title, custom_id=builder.custom_id, components=builder.components or hikari.UNDEFINED
|
|
630
724
|
)
|
|
631
725
|
|
|
632
726
|
self._issued_response = True
|
arc/errors.py
CHANGED
|
@@ -5,18 +5,9 @@ import typing as t
|
|
|
5
5
|
if t.TYPE_CHECKING:
|
|
6
6
|
import hikari
|
|
7
7
|
|
|
8
|
+
from arc.abc.command import CommandProto
|
|
8
9
|
from arc.abc.limiter import LimiterProto
|
|
9
10
|
|
|
10
|
-
__all__ = (
|
|
11
|
-
"ArcError",
|
|
12
|
-
"AutocompleteError",
|
|
13
|
-
"CommandInvokeError",
|
|
14
|
-
"ExtensionError",
|
|
15
|
-
"ExtensionLoadError",
|
|
16
|
-
"ExtensionUnloadError",
|
|
17
|
-
"NoResponseIssuedError",
|
|
18
|
-
)
|
|
19
|
-
|
|
20
11
|
|
|
21
12
|
class ArcError(Exception):
|
|
22
13
|
"""Base exception for all Arc errors."""
|
|
@@ -120,10 +111,10 @@ class UnderCooldownError(ArcError):
|
|
|
120
111
|
|
|
121
112
|
Attributes
|
|
122
113
|
----------
|
|
123
|
-
retry_after : float
|
|
124
|
-
The amount of time in seconds until the command is off cooldown.
|
|
125
114
|
limiter : arc.abc.limiter.LimiterProto
|
|
126
115
|
The limiter that was rate limited.
|
|
116
|
+
retry_after : float
|
|
117
|
+
The amount of time in seconds until the command is off cooldown.
|
|
127
118
|
"""
|
|
128
119
|
|
|
129
120
|
def __init__(self, limiter: LimiterProto[t.Any], retry_after: float, *args: t.Any) -> None:
|
|
@@ -132,6 +123,34 @@ class UnderCooldownError(ArcError):
|
|
|
132
123
|
super().__init__(*args)
|
|
133
124
|
|
|
134
125
|
|
|
126
|
+
class CommandPublishFailedError(ArcError):
|
|
127
|
+
"""Raised when a command could not be published to Discord.
|
|
128
|
+
|
|
129
|
+
Attributes
|
|
130
|
+
----------
|
|
131
|
+
command : arc.abc.command.CommandProto
|
|
132
|
+
The command that failed to publish.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
def __init__(self, command: CommandProto, *args: t.Any) -> None:
|
|
136
|
+
self.command = command
|
|
137
|
+
super().__init__(*args)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class GuildCommandPublishFailedError(ArcError):
|
|
141
|
+
"""Raised when a set of commands could not be published to a guild.
|
|
142
|
+
|
|
143
|
+
Attributes
|
|
144
|
+
----------
|
|
145
|
+
guild_id : hikari.Snowflake
|
|
146
|
+
The guild that commands could not be published to.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def __init__(self, guild_id: hikari.Snowflake, *args: t.Any) -> None:
|
|
150
|
+
self.guild_id = guild_id
|
|
151
|
+
super().__init__(*args)
|
|
152
|
+
|
|
153
|
+
|
|
135
154
|
# MIT License
|
|
136
155
|
#
|
|
137
156
|
# Copyright (c) 2023-present hypergonial
|
arc/events.py
CHANGED
|
@@ -17,7 +17,11 @@ class ArcEvent(hikari.Event):
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class CommandErrorEvent(ArcEvent, t.Generic[GatewayClientT]):
|
|
20
|
-
"""Event dispatched when a command raises an exception that is not handled by any error handlers.
|
|
20
|
+
"""Event dispatched when a command raises an exception that is not handled by any error handlers.
|
|
21
|
+
|
|
22
|
+
!!! warning
|
|
23
|
+
Creating any listeners for this event will disable the client error handler completely.
|
|
24
|
+
"""
|
|
21
25
|
|
|
22
26
|
def __init__(self, client: GatewayClientT, context: Context[GatewayClientT], exception: Exception) -> None:
|
|
23
27
|
self._context = context
|
arc/internal/about.py
CHANGED
|
@@ -5,7 +5,7 @@ __author_email__: t.Final[str] = "46067571+hypergonial@users.noreply.github.com"
|
|
|
5
5
|
__maintainer__: t.Final[str] = "hypergonial"
|
|
6
6
|
__license__: t.Final[str] = "MIT"
|
|
7
7
|
__url__: t.Final[str] = "https://github.com/hypergonial/hikari-arc"
|
|
8
|
-
__version__: t.Final[str] = "0.
|
|
8
|
+
__version__: t.Final[str] = "0.6.0"
|
|
9
9
|
|
|
10
10
|
# MIT License
|
|
11
11
|
#
|
arc/internal/options.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
|
|
5
|
+
import hikari
|
|
6
|
+
|
|
7
|
+
from arc.abc.option import OptionType
|
|
8
|
+
|
|
9
|
+
if t.TYPE_CHECKING:
|
|
10
|
+
from arc.abc.option import CommandOptionBase
|
|
11
|
+
from arc.internal.types import ClientT
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
OPTIONTYPE_TO_TYPE: dict[OptionType, type[t.Any]] = {
|
|
15
|
+
OptionType.STRING: str,
|
|
16
|
+
OptionType.INTEGER: int,
|
|
17
|
+
OptionType.BOOLEAN: bool,
|
|
18
|
+
OptionType.USER: hikari.PartialUser,
|
|
19
|
+
OptionType.CHANNEL: hikari.PartialChannel,
|
|
20
|
+
OptionType.ROLE: hikari.Role,
|
|
21
|
+
OptionType.MENTIONABLE: hikari.Unique,
|
|
22
|
+
OptionType.FLOAT: float,
|
|
23
|
+
OptionType.ATTACHMENT: hikari.Attachment,
|
|
24
|
+
}
|
|
25
|
+
"""Used for runtime type checking in Context.get_option, not much else at the moment."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def resolve_snowflake_value(
|
|
29
|
+
value: hikari.Snowflake, opt_type: hikari.OptionType | int, resolved: hikari.ResolvedOptionData
|
|
30
|
+
) -> t.Any:
|
|
31
|
+
"""Resolve a snowflake value into a resolved option.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
value : hikari.Snowflake
|
|
36
|
+
The snowflake value to resolve.
|
|
37
|
+
opt_type : hikari.OptionType | int
|
|
38
|
+
The type of the option.
|
|
39
|
+
resolved : hikari.ResolvedOptionData
|
|
40
|
+
The resolved option data of the interaction.
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
Any
|
|
45
|
+
The resolved snowflake value.
|
|
46
|
+
|
|
47
|
+
Raises
|
|
48
|
+
------
|
|
49
|
+
ValueError
|
|
50
|
+
If the option type is not a valid option type.
|
|
51
|
+
"""
|
|
52
|
+
match opt_type:
|
|
53
|
+
case hikari.OptionType.USER:
|
|
54
|
+
out = resolved.members.get(value) or resolved.users[value]
|
|
55
|
+
case hikari.OptionType.ATTACHMENT:
|
|
56
|
+
out = resolved.attachments[value]
|
|
57
|
+
case hikari.OptionType.CHANNEL:
|
|
58
|
+
out = resolved.channels[value]
|
|
59
|
+
case hikari.OptionType.ROLE:
|
|
60
|
+
out = resolved.roles[value]
|
|
61
|
+
case hikari.OptionType.MENTIONABLE:
|
|
62
|
+
out = resolved.members.get(value) or resolved.users.get(value) or resolved.roles[value]
|
|
63
|
+
case _:
|
|
64
|
+
raise ValueError(f"Unexpected option type '{opt_type}.'")
|
|
65
|
+
|
|
66
|
+
return out
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def resolve_options(
|
|
70
|
+
local_options: t.MutableMapping[str, CommandOptionBase[ClientT, t.Any, t.Any]],
|
|
71
|
+
incoming_options: t.Sequence[hikari.CommandInteractionOption],
|
|
72
|
+
resolved: hikari.ResolvedOptionData | None,
|
|
73
|
+
) -> dict[str, t.Any]:
|
|
74
|
+
"""Resolve the options into kwargs for the callback.
|
|
75
|
+
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
local_options : t.MutableMapping[str, Option[t.Any, t.Any]]
|
|
79
|
+
The options of the locally stored command.
|
|
80
|
+
incoming_options : t.Sequence[hikari.CommandInteractionOption]
|
|
81
|
+
The options of the interaction.
|
|
82
|
+
resolved : hikari.ResolvedOptionData
|
|
83
|
+
The resolved option data of the interaction.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
dict[str, Any]
|
|
88
|
+
The resolved options as kwargs, ready to be passed to the callback.
|
|
89
|
+
"""
|
|
90
|
+
option_kwargs: dict[str, t.Any] = {}
|
|
91
|
+
|
|
92
|
+
for arg_name, opt in local_options.items():
|
|
93
|
+
inter_opt = next((o for o in incoming_options if o.name == opt.name), None)
|
|
94
|
+
|
|
95
|
+
if inter_opt is None:
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
if isinstance(inter_opt.value, hikari.Snowflake) and resolved:
|
|
99
|
+
option_kwargs[arg_name] = resolve_snowflake_value(inter_opt.value, inter_opt.type, resolved)
|
|
100
|
+
|
|
101
|
+
elif isinstance(inter_opt.value, hikari.Snowflake):
|
|
102
|
+
raise ValueError(f"Missing resolved option data for '{inter_opt.name}'.")
|
|
103
|
+
else:
|
|
104
|
+
option_kwargs[arg_name] = inter_opt.value
|
|
105
|
+
|
|
106
|
+
return option_kwargs
|
arc/internal/sigparse.py
CHANGED
|
@@ -41,6 +41,7 @@ TYPE_TO_OPTION_MAPPING: dict[t.Type[t.Any], t.Type[CommandOptionBase[t.Any, t.An
|
|
|
41
41
|
int: IntOption,
|
|
42
42
|
str: StrOption,
|
|
43
43
|
float: FloatOption,
|
|
44
|
+
hikari.Role: RoleOption,
|
|
44
45
|
hikari.User | hikari.Role: MentionableOption,
|
|
45
46
|
t.Union[hikari.User, hikari.Role]: MentionableOption,
|
|
46
47
|
hikari.Attachment: AttachmentOption,
|
|
@@ -51,11 +52,14 @@ TYPE_TO_OPTION_MAPPING: dict[t.Type[t.Any], t.Type[CommandOptionBase[t.Any, t.An
|
|
|
51
52
|
hikari.GuildNewsChannel: ChannelOption,
|
|
52
53
|
hikari.GuildPrivateThread: ChannelOption,
|
|
53
54
|
hikari.GuildPublicThread: ChannelOption,
|
|
55
|
+
hikari.GuildNewsThread: ChannelOption,
|
|
54
56
|
hikari.GuildForumChannel: ChannelOption,
|
|
57
|
+
hikari.GuildThreadChannel: ChannelOption,
|
|
55
58
|
hikari.DMChannel: ChannelOption,
|
|
56
59
|
hikari.GroupDMChannel: ChannelOption,
|
|
57
60
|
hikari.GuildStageChannel: ChannelOption,
|
|
58
61
|
hikari.PartialChannel: ChannelOption,
|
|
62
|
+
hikari.InteractionChannel: ChannelOption,
|
|
59
63
|
hikari.TextableChannel: ChannelOption,
|
|
60
64
|
hikari.GuildChannel: ChannelOption,
|
|
61
65
|
hikari.PrivateChannel: ChannelOption,
|
|
@@ -83,6 +87,7 @@ CHANNEL_TYPES_MAPPING: dict[t.Type[hikari.PartialChannel], hikari.ChannelType |
|
|
|
83
87
|
hikari.GuildNewsChannel: hikari.ChannelType.GUILD_NEWS,
|
|
84
88
|
hikari.GuildPrivateThread: hikari.ChannelType.GUILD_PRIVATE_THREAD,
|
|
85
89
|
hikari.GuildPublicThread: hikari.ChannelType.GUILD_PUBLIC_THREAD,
|
|
90
|
+
hikari.GuildNewsThread: hikari.ChannelType.GUILD_NEWS_THREAD,
|
|
86
91
|
hikari.GuildForumChannel: hikari.ChannelType.GUILD_FORUM,
|
|
87
92
|
hikari.DMChannel: hikari.ChannelType.DM,
|
|
88
93
|
hikari.GroupDMChannel: hikari.ChannelType.GROUP_DM,
|
|
@@ -105,6 +110,19 @@ CHANNEL_TYPES_MAPPING: dict[t.Type[hikari.PartialChannel], hikari.ChannelType |
|
|
|
105
110
|
hikari.ChannelType.GROUP_DM,
|
|
106
111
|
hikari.ChannelType.GUILD_STAGE,
|
|
107
112
|
},
|
|
113
|
+
hikari.InteractionChannel: {
|
|
114
|
+
hikari.ChannelType.GUILD_TEXT,
|
|
115
|
+
hikari.ChannelType.GUILD_VOICE,
|
|
116
|
+
hikari.ChannelType.GUILD_CATEGORY,
|
|
117
|
+
hikari.ChannelType.GUILD_NEWS,
|
|
118
|
+
hikari.ChannelType.GUILD_FORUM,
|
|
119
|
+
hikari.ChannelType.GUILD_NEWS_THREAD,
|
|
120
|
+
hikari.ChannelType.GUILD_PUBLIC_THREAD,
|
|
121
|
+
hikari.ChannelType.GUILD_PRIVATE_THREAD,
|
|
122
|
+
hikari.ChannelType.DM,
|
|
123
|
+
hikari.ChannelType.GROUP_DM,
|
|
124
|
+
hikari.ChannelType.GUILD_STAGE,
|
|
125
|
+
},
|
|
108
126
|
hikari.TextableChannel: {
|
|
109
127
|
hikari.ChannelType.GUILD_TEXT,
|
|
110
128
|
hikari.ChannelType.GUILD_VOICE,
|
|
@@ -366,7 +384,7 @@ def parse_command_signature( # noqa: C901
|
|
|
366
384
|
return options
|
|
367
385
|
|
|
368
386
|
|
|
369
|
-
def parse_event_signature(func: t.Callable[
|
|
387
|
+
def parse_event_signature(func: t.Callable[[EventT], t.Awaitable[None]]) -> list[type[EventT]]:
|
|
370
388
|
"""Parse an event callback function's signature and return the event type, ignore other type hints."""
|
|
371
389
|
hints = t.get_type_hints(func)
|
|
372
390
|
|
arc/internal/sync.py
CHANGED
|
@@ -8,6 +8,8 @@ from contextlib import suppress
|
|
|
8
8
|
|
|
9
9
|
import hikari
|
|
10
10
|
|
|
11
|
+
from arc.errors import GuildCommandPublishFailedError
|
|
12
|
+
|
|
11
13
|
if t.TYPE_CHECKING:
|
|
12
14
|
from arc.abc.client import Client
|
|
13
15
|
from arc.abc.command import CommandBase
|
|
@@ -153,7 +155,7 @@ def _get_all_commands(
|
|
|
153
155
|
] = defaultdict(lambda: defaultdict(lambda: defaultdict(dict))) # type: ignore
|
|
154
156
|
|
|
155
157
|
for command in itertools.chain(
|
|
156
|
-
client.
|
|
158
|
+
client._slash_commands.values(), client._message_commands.values(), client._user_commands.values()
|
|
157
159
|
):
|
|
158
160
|
if command.guilds:
|
|
159
161
|
for guild_id in command.guilds:
|
|
@@ -293,7 +295,10 @@ async def _sync_commands_for_guild(
|
|
|
293
295
|
builders.append(existing._build())
|
|
294
296
|
created += 1
|
|
295
297
|
|
|
296
|
-
|
|
298
|
+
try:
|
|
299
|
+
created = await client.app.rest.set_application_commands(client.application, builders, guild)
|
|
300
|
+
except Exception as e:
|
|
301
|
+
raise GuildCommandPublishFailedError(guild_id, e, f"Failed to register commands in guild {guild_id}.") from e
|
|
297
302
|
|
|
298
303
|
for existing in created:
|
|
299
304
|
with suppress(KeyError):
|
arc/internal/types.py
CHANGED
|
@@ -24,19 +24,14 @@ EventT = t.TypeVar("EventT", bound="hikari.Event")
|
|
|
24
24
|
BuilderT = t.TypeVar("BuilderT", bound="hikari.api.SlashCommandBuilder | hikari.api.ContextMenuCommandBuilder")
|
|
25
25
|
ParamsT = t.TypeVar("ParamsT", bound="OptionParams[t.Any]")
|
|
26
26
|
HookableT = t.TypeVar("HookableT", bound="Hookable[t.Any]")
|
|
27
|
-
P = t.ParamSpec("P")
|
|
28
27
|
|
|
29
28
|
# Type aliases
|
|
30
|
-
EventCallbackT: t.TypeAlias = "t.Callable[
|
|
31
|
-
ErrorHandlerCallbackT: t.TypeAlias =
|
|
32
|
-
"t.Callable[t.Concatenate[Context[ClientT], Exception, ...], t.Coroutine[t.Any, t.Any, None]]"
|
|
33
|
-
)
|
|
29
|
+
EventCallbackT: t.TypeAlias = "t.Callable[[EventT], t.Awaitable[None]]"
|
|
30
|
+
ErrorHandlerCallbackT: t.TypeAlias = "t.Callable[[Context[ClientT], Exception], t.Awaitable[None]]"
|
|
34
31
|
SlashCommandLike: t.TypeAlias = "SlashCommand[ClientT] | SlashGroup[ClientT]"
|
|
35
32
|
CommandCallbackT: t.TypeAlias = "t.Callable[t.Concatenate[Context[ClientT], ...], t.Awaitable[None]]"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
)
|
|
39
|
-
UserContextCallbackT: t.TypeAlias = "t.Callable[t.Concatenate[Context[ClientT], hikari.User, ...], t.Awaitable[None]]"
|
|
33
|
+
MessageCommandCallbackT: t.TypeAlias = "t.Callable[[Context[ClientT], hikari.Message], t.Awaitable[None]]"
|
|
34
|
+
UserCommandCallbackT: t.TypeAlias = "t.Callable[[Context[ClientT], hikari.User], t.Awaitable[None]]"
|
|
40
35
|
AutocompleteCallbackT: t.TypeAlias = "t.Callable[[AutocompleteData[ClientT, ChoiceT]], t.Awaitable[t.Sequence[ChoiceT]]] | t.Callable[[AutocompleteData[ClientT, ChoiceT]], t.Awaitable[t.Sequence[hikari.api.AutocompleteChoiceBuilder]]]"
|
|
41
36
|
ResponseBuilderT: t.TypeAlias = (
|
|
42
37
|
"hikari.api.InteractionMessageBuilder | hikari.api.InteractionDeferredBuilder | hikari.api.InteractionModalBuilder"
|
|
@@ -44,9 +39,9 @@ ResponseBuilderT: t.TypeAlias = (
|
|
|
44
39
|
HookT: t.TypeAlias = "t.Callable[[Context[ClientT]], t.Awaitable[HookResult]] | t.Callable[[Context[ClientT]], HookResult] | t.Callable[[Context[ClientT]], None] | t.Callable[[Context[ClientT]], t.Awaitable[None]]"
|
|
45
40
|
PostHookT: t.TypeAlias = "t.Callable[[Context[ClientT]], None] | t.Callable[[Context[ClientT]], t.Awaitable[None]]"
|
|
46
41
|
LifeCycleHookT: t.TypeAlias = "t.Callable[[ClientT], t.Awaitable[None]]"
|
|
47
|
-
CommandLocaleRequestT: t.TypeAlias = "t.Callable[
|
|
48
|
-
OptionLocaleRequestT: t.TypeAlias = "t.Callable[
|
|
49
|
-
CustomLocaleRequestT: t.TypeAlias = "t.Callable[
|
|
42
|
+
CommandLocaleRequestT: t.TypeAlias = "t.Callable[[CommandLocaleRequest], LocaleResponse]"
|
|
43
|
+
OptionLocaleRequestT: t.TypeAlias = "t.Callable[[OptionLocaleRequest], LocaleResponse]"
|
|
44
|
+
CustomLocaleRequestT: t.TypeAlias = "t.Callable[[CustomLocaleRequest], str]"
|
|
50
45
|
|
|
51
46
|
# MIT License
|
|
52
47
|
#
|
arc/plugin.py
CHANGED
|
@@ -140,7 +140,7 @@ class GatewayPluginBase(PluginBase[GatewayClientT]):
|
|
|
140
140
|
----------
|
|
141
141
|
event : type[hikari.Event]
|
|
142
142
|
The event to subscribe to.
|
|
143
|
-
callback : Callable[[EventT], Awaitable[None]]
|
|
143
|
+
callback : t.Callable[[EventT], t.Awaitable[None]]
|
|
144
144
|
The callback to call when the event is dispatched.
|
|
145
145
|
"""
|
|
146
146
|
if event not in self.listeners:
|
|
@@ -158,7 +158,7 @@ class GatewayPluginBase(PluginBase[GatewayClientT]):
|
|
|
158
158
|
----------
|
|
159
159
|
event : type[hikari.Event]
|
|
160
160
|
The event to unsubscribe from.
|
|
161
|
-
callback : Callable[[EventT], Awaitable[None]]
|
|
161
|
+
callback : t.Callable[[EventT], t.Awaitable[None]]
|
|
162
162
|
The callback to unsubscribe.
|
|
163
163
|
"""
|
|
164
164
|
if event not in self.listeners:
|
arc/utils/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from .hooks import (
|
|
2
|
-
|
|
2
|
+
LimiterHook,
|
|
3
3
|
bot_has_permissions,
|
|
4
4
|
channel_limiter,
|
|
5
5
|
custom_limiter,
|
|
@@ -12,6 +12,7 @@ from .hooks import (
|
|
|
12
12
|
owner_only,
|
|
13
13
|
user_limiter,
|
|
14
14
|
)
|
|
15
|
+
from .ratelimiter import RateLimiter, RateLimiterExhaustedError
|
|
15
16
|
|
|
16
17
|
__all__ = (
|
|
17
18
|
"guild_only",
|
|
@@ -25,7 +26,9 @@ __all__ = (
|
|
|
25
26
|
"member_limiter",
|
|
26
27
|
"channel_limiter",
|
|
27
28
|
"custom_limiter",
|
|
29
|
+
"LimiterHook",
|
|
28
30
|
"RateLimiter",
|
|
31
|
+
"RateLimiterExhaustedError",
|
|
29
32
|
)
|
|
30
33
|
|
|
31
34
|
# MIT License
|
arc/utils/hooks/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from .basic import bot_has_permissions, dm_only, guild_only, has_permissions, owner_only
|
|
2
2
|
from .limiters import (
|
|
3
|
-
|
|
3
|
+
LimiterHook,
|
|
4
4
|
channel_limiter,
|
|
5
5
|
custom_limiter,
|
|
6
6
|
global_limiter,
|
|
@@ -21,5 +21,5 @@ __all__ = (
|
|
|
21
21
|
"user_limiter",
|
|
22
22
|
"member_limiter",
|
|
23
23
|
"custom_limiter",
|
|
24
|
-
"
|
|
24
|
+
"LimiterHook",
|
|
25
25
|
)
|