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/errors.py
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import typing as t
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
if t.TYPE_CHECKING:
|
|
6
|
+
import hikari
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"AutocompleteError",
|
|
8
|
-
"CommandInvokeError",
|
|
9
|
-
"ExtensionError",
|
|
10
|
-
"ExtensionLoadError",
|
|
11
|
-
"ExtensionUnloadError",
|
|
12
|
-
"NoResponseIssuedError",
|
|
13
|
-
)
|
|
8
|
+
from arc.abc.command import CommandProto
|
|
9
|
+
from arc.abc.limiter import LimiterProto
|
|
14
10
|
|
|
15
11
|
|
|
16
12
|
class ArcError(Exception):
|
|
@@ -109,6 +105,52 @@ class ResponseAlreadyIssuedError(InteractionResponseError):
|
|
|
109
105
|
"""
|
|
110
106
|
|
|
111
107
|
|
|
108
|
+
class UnderCooldownError(ArcError):
|
|
109
|
+
"""Raised when a built-in ratelimiter is acquired while it is exhausted, and the
|
|
110
|
+
`wait` parameter is set to `False`.
|
|
111
|
+
|
|
112
|
+
Attributes
|
|
113
|
+
----------
|
|
114
|
+
limiter : arc.abc.limiter.LimiterProto
|
|
115
|
+
The limiter that was rate limited.
|
|
116
|
+
retry_after : float
|
|
117
|
+
The amount of time in seconds until the command is off cooldown.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
def __init__(self, limiter: LimiterProto[t.Any], retry_after: float, *args: t.Any) -> None:
|
|
121
|
+
self.retry_after = retry_after
|
|
122
|
+
self.limiter = limiter
|
|
123
|
+
super().__init__(*args)
|
|
124
|
+
|
|
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
|
+
|
|
112
154
|
# MIT License
|
|
113
155
|
#
|
|
114
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/extension.py
CHANGED
|
@@ -91,3 +91,26 @@ def unloader(
|
|
|
91
91
|
return decorator(callback)
|
|
92
92
|
|
|
93
93
|
return decorator
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# MIT License
|
|
97
|
+
#
|
|
98
|
+
# Copyright (c) 2023-present hypergonial
|
|
99
|
+
#
|
|
100
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
101
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
102
|
+
# in the Software without restriction, including without limitation the rights
|
|
103
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
104
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
105
|
+
# furnished to do so, subject to the following conditions:
|
|
106
|
+
#
|
|
107
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
108
|
+
# copies or substantial portions of the Software.
|
|
109
|
+
#
|
|
110
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
111
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
112
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
113
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
114
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
115
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
116
|
+
# SOFTWARE.
|
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/deprecation.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import typing as t
|
|
4
3
|
from warnings import warn
|
|
5
4
|
|
|
6
5
|
from arc.internal.version import CURRENT_VERSION, Version
|
|
@@ -8,7 +7,7 @@ from arc.internal.version import CURRENT_VERSION, Version
|
|
|
8
7
|
__all__ = ("warn_deprecate",)
|
|
9
8
|
|
|
10
9
|
|
|
11
|
-
def warn_deprecate(*, what: str, when: Version, use_instead:
|
|
10
|
+
def warn_deprecate(*, what: str, when: Version, use_instead: str | None = None) -> None:
|
|
12
11
|
"""Warn about a deprecation.
|
|
13
12
|
|
|
14
13
|
Parameters
|
|
@@ -17,8 +16,8 @@ def warn_deprecate(*, what: str, when: Version, use_instead: t.Optional[str] = N
|
|
|
17
16
|
The name of the object that is being deprecated.
|
|
18
17
|
when : Version
|
|
19
18
|
The version in which the object will be removed.
|
|
20
|
-
use_instead :
|
|
21
|
-
The object's name that should be used instead
|
|
19
|
+
use_instead : str | None
|
|
20
|
+
The object's name that should be used instead
|
|
22
21
|
"""
|
|
23
22
|
if when < CURRENT_VERSION:
|
|
24
23
|
raise DeprecationWarning(
|
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
|
@@ -2,13 +2,14 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import itertools
|
|
4
4
|
import logging
|
|
5
|
-
import pprint
|
|
6
5
|
import typing as t
|
|
7
6
|
from collections import defaultdict
|
|
8
7
|
from contextlib import suppress
|
|
9
8
|
|
|
10
9
|
import hikari
|
|
11
10
|
|
|
11
|
+
from arc.errors import GuildCommandPublishFailedError
|
|
12
|
+
|
|
12
13
|
if t.TYPE_CHECKING:
|
|
13
14
|
from arc.abc.client import Client
|
|
14
15
|
from arc.abc.command import CommandBase
|
|
@@ -154,7 +155,7 @@ def _get_all_commands(
|
|
|
154
155
|
] = defaultdict(lambda: defaultdict(lambda: defaultdict(dict))) # type: ignore
|
|
155
156
|
|
|
156
157
|
for command in itertools.chain(
|
|
157
|
-
client.
|
|
158
|
+
client._slash_commands.values(), client._message_commands.values(), client._user_commands.values()
|
|
158
159
|
):
|
|
159
160
|
if command.guilds:
|
|
160
161
|
for guild_id in command.guilds:
|
|
@@ -204,7 +205,7 @@ async def _sync_global_commands(client: Client[AppT], commands: CommandMapping)
|
|
|
204
205
|
|
|
205
206
|
upstream = await client.app.rest.fetch_application_commands(client.application)
|
|
206
207
|
|
|
207
|
-
published: set[str] = set
|
|
208
|
+
published: dict[hikari.CommandType, set[str]] = defaultdict(set)
|
|
208
209
|
|
|
209
210
|
for existing in upstream:
|
|
210
211
|
# Ignore unsupported command types
|
|
@@ -224,11 +225,11 @@ async def _sync_global_commands(client: Client[AppT], commands: CommandMapping)
|
|
|
224
225
|
commands[existing.type][existing.name]._register_instance(existing)
|
|
225
226
|
unchanged += 1
|
|
226
227
|
|
|
227
|
-
published.add(existing.name)
|
|
228
|
+
published[existing.type].add(existing.name)
|
|
228
229
|
|
|
229
230
|
for mapping in commands.values():
|
|
230
231
|
for existing in mapping.values():
|
|
231
|
-
if existing.name in published:
|
|
232
|
+
if existing.name in published[existing.command_type]:
|
|
232
233
|
continue
|
|
233
234
|
|
|
234
235
|
await existing.publish()
|
|
@@ -263,7 +264,7 @@ async def _sync_commands_for_guild(
|
|
|
263
264
|
|
|
264
265
|
upstream = await client.app.rest.fetch_application_commands(client.application, guild)
|
|
265
266
|
|
|
266
|
-
built: set[str] = set
|
|
267
|
+
built: dict[hikari.CommandType, set[str]] = defaultdict(set)
|
|
267
268
|
|
|
268
269
|
builders: list[hikari.api.CommandBuilder] = []
|
|
269
270
|
|
|
@@ -285,16 +286,19 @@ async def _sync_commands_for_guild(
|
|
|
285
286
|
builders.append(_rebuild_hikari_command(existing))
|
|
286
287
|
unchanged += 1
|
|
287
288
|
|
|
288
|
-
built.add(existing.name)
|
|
289
|
+
built[existing.type].add(existing.name)
|
|
289
290
|
|
|
290
291
|
for mapping in commands.values():
|
|
291
292
|
for existing in mapping.values():
|
|
292
|
-
if existing.name in built:
|
|
293
|
+
if existing.name in built[existing.command_type]:
|
|
293
294
|
continue
|
|
294
295
|
builders.append(existing._build())
|
|
295
296
|
created += 1
|
|
296
297
|
|
|
297
|
-
|
|
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
|
|
298
302
|
|
|
299
303
|
for existing in created:
|
|
300
304
|
with suppress(KeyError):
|
|
@@ -325,7 +329,6 @@ async def _sync_commands(client: Client[AppT]) -> None:
|
|
|
325
329
|
|
|
326
330
|
commands = _get_all_commands(client)
|
|
327
331
|
_process_localizations(client, commands)
|
|
328
|
-
pprint.PrettyPrinter(indent=4).pprint(commands)
|
|
329
332
|
|
|
330
333
|
global_commands = commands.pop(None, None)
|
|
331
334
|
|
arc/internal/types.py
CHANGED
|
@@ -6,7 +6,7 @@ if t.TYPE_CHECKING:
|
|
|
6
6
|
import hikari
|
|
7
7
|
|
|
8
8
|
from arc.abc import Client, Hookable, HookResult, OptionParams
|
|
9
|
-
from arc.client import
|
|
9
|
+
from arc.client import GatewayClientBase, RESTClientBase
|
|
10
10
|
from arc.command import SlashCommand, SlashGroup
|
|
11
11
|
from arc.context import AutocompleteData, Context
|
|
12
12
|
from arc.locale import CommandLocaleRequest, CustomLocaleRequest, LocaleResponse, OptionLocaleRequest
|
|
@@ -16,25 +16,22 @@ if t.TYPE_CHECKING:
|
|
|
16
16
|
AppT = t.TypeVar("AppT", bound="hikari.RESTAware")
|
|
17
17
|
ChoiceT = t.TypeVar("ChoiceT", bound="int | float | str")
|
|
18
18
|
ClientT = t.TypeVar("ClientT", bound="Client[t.Any]")
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
GatewayBotT = t.TypeVar("GatewayBotT", bound="hikari.GatewayBotAware")
|
|
20
|
+
RESTBotT = t.TypeVar("RESTBotT", bound="hikari.RESTBotAware")
|
|
21
|
+
GatewayClientT = t.TypeVar("GatewayClientT", bound="GatewayClientBase[t.Any]")
|
|
22
|
+
RESTClientT = t.TypeVar("RESTClientT", bound="RESTClientBase[t.Any]")
|
|
21
23
|
EventT = t.TypeVar("EventT", bound="hikari.Event")
|
|
22
24
|
BuilderT = t.TypeVar("BuilderT", bound="hikari.api.SlashCommandBuilder | hikari.api.ContextMenuCommandBuilder")
|
|
23
25
|
ParamsT = t.TypeVar("ParamsT", bound="OptionParams[t.Any]")
|
|
24
26
|
HookableT = t.TypeVar("HookableT", bound="Hookable[t.Any]")
|
|
25
|
-
P = t.ParamSpec("P")
|
|
26
27
|
|
|
27
28
|
# Type aliases
|
|
28
|
-
EventCallbackT: t.TypeAlias = "t.Callable[
|
|
29
|
-
ErrorHandlerCallbackT: t.TypeAlias =
|
|
30
|
-
"t.Callable[t.Concatenate[Context[ClientT], Exception, ...], t.Coroutine[t.Any, t.Any, None]]"
|
|
31
|
-
)
|
|
29
|
+
EventCallbackT: t.TypeAlias = "t.Callable[[EventT], t.Awaitable[None]]"
|
|
30
|
+
ErrorHandlerCallbackT: t.TypeAlias = "t.Callable[[Context[ClientT], Exception], t.Awaitable[None]]"
|
|
32
31
|
SlashCommandLike: t.TypeAlias = "SlashCommand[ClientT] | SlashGroup[ClientT]"
|
|
33
32
|
CommandCallbackT: t.TypeAlias = "t.Callable[t.Concatenate[Context[ClientT], ...], t.Awaitable[None]]"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
)
|
|
37
|
-
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]]"
|
|
38
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]]]"
|
|
39
36
|
ResponseBuilderT: t.TypeAlias = (
|
|
40
37
|
"hikari.api.InteractionMessageBuilder | hikari.api.InteractionDeferredBuilder | hikari.api.InteractionModalBuilder"
|
|
@@ -42,6 +39,28 @@ ResponseBuilderT: t.TypeAlias = (
|
|
|
42
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]]"
|
|
43
40
|
PostHookT: t.TypeAlias = "t.Callable[[Context[ClientT]], None] | t.Callable[[Context[ClientT]], t.Awaitable[None]]"
|
|
44
41
|
LifeCycleHookT: t.TypeAlias = "t.Callable[[ClientT], t.Awaitable[None]]"
|
|
45
|
-
CommandLocaleRequestT: t.TypeAlias = "t.Callable[
|
|
46
|
-
OptionLocaleRequestT: t.TypeAlias = "t.Callable[
|
|
47
|
-
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]"
|
|
45
|
+
|
|
46
|
+
# MIT License
|
|
47
|
+
#
|
|
48
|
+
# Copyright (c) 2023-present hypergonial
|
|
49
|
+
#
|
|
50
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
51
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
52
|
+
# in the Software without restriction, including without limitation the rights
|
|
53
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
54
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
55
|
+
# furnished to do so, subject to the following conditions:
|
|
56
|
+
#
|
|
57
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
58
|
+
# copies or substantial portions of the Software.
|
|
59
|
+
#
|
|
60
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
61
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
62
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
63
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
64
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
65
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
66
|
+
# SOFTWARE.
|
arc/locale.py
CHANGED
|
@@ -23,6 +23,7 @@ __all__ = (
|
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
@t.final
|
|
26
27
|
class LocaleRequestType(enum.IntEnum):
|
|
27
28
|
"""The type of locale request."""
|
|
28
29
|
|
|
@@ -64,6 +65,7 @@ class LocaleRequest(abc.ABC):
|
|
|
64
65
|
return self._command.qualified_name
|
|
65
66
|
|
|
66
67
|
|
|
68
|
+
@t.final
|
|
67
69
|
@attr.define(slots=True)
|
|
68
70
|
class LocaleResponse:
|
|
69
71
|
"""The response to a command or option locale request."""
|
|
@@ -75,6 +77,7 @@ class LocaleResponse:
|
|
|
75
77
|
"""The localized description of the command or option."""
|
|
76
78
|
|
|
77
79
|
|
|
80
|
+
@t.final
|
|
78
81
|
@attr.define(slots=True)
|
|
79
82
|
class CommandLocaleRequest(LocaleRequest):
|
|
80
83
|
"""A request to localize a command."""
|
|
@@ -98,6 +101,7 @@ class CommandLocaleRequest(LocaleRequest):
|
|
|
98
101
|
return self._description
|
|
99
102
|
|
|
100
103
|
|
|
104
|
+
@t.final
|
|
101
105
|
@attr.define(slots=True)
|
|
102
106
|
class OptionLocaleRequest(LocaleRequest):
|
|
103
107
|
"""A request to localize a command option."""
|
|
@@ -127,6 +131,7 @@ class OptionLocaleRequest(LocaleRequest):
|
|
|
127
131
|
return self._description
|
|
128
132
|
|
|
129
133
|
|
|
134
|
+
@t.final
|
|
130
135
|
@attr.define(slots=True)
|
|
131
136
|
class CustomLocaleRequest(LocaleRequest):
|
|
132
137
|
"""A custom locale request made by the user."""
|
|
@@ -148,3 +153,26 @@ class CustomLocaleRequest(LocaleRequest):
|
|
|
148
153
|
def key(self) -> str:
|
|
149
154
|
"""The key that is to be localized."""
|
|
150
155
|
return self._key
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# MIT License
|
|
159
|
+
#
|
|
160
|
+
# Copyright (c) 2023-present hypergonial
|
|
161
|
+
#
|
|
162
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
163
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
164
|
+
# in the Software without restriction, including without limitation the rights
|
|
165
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
166
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
167
|
+
# furnished to do so, subject to the following conditions:
|
|
168
|
+
#
|
|
169
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
170
|
+
# copies or substantial portions of the Software.
|
|
171
|
+
#
|
|
172
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
173
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
174
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
175
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
176
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
177
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
178
|
+
# SOFTWARE.
|
arc/plugin.py
CHANGED
|
@@ -2,12 +2,14 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import typing as t
|
|
4
4
|
|
|
5
|
+
import hikari
|
|
6
|
+
|
|
5
7
|
from arc.abc.plugin import PluginBase
|
|
6
8
|
from arc.internal.sigparse import parse_event_signature
|
|
7
9
|
from arc.internal.types import EventCallbackT, EventT, GatewayClientT, RESTClientT
|
|
8
10
|
|
|
9
11
|
if t.TYPE_CHECKING:
|
|
10
|
-
import
|
|
12
|
+
from arc.context.base import AutodeferMode
|
|
11
13
|
|
|
12
14
|
__all__ = ("RESTPluginBase", "GatewayPluginBase")
|
|
13
15
|
|
|
@@ -23,6 +25,22 @@ class RESTPluginBase(PluginBase[RESTClientT]):
|
|
|
23
25
|
----------
|
|
24
26
|
name : str
|
|
25
27
|
The name of this plugin. This must be unique across all plugins.
|
|
28
|
+
default_enabled_guilds : t.Sequence[hikari.Snowflake] | hikari.UndefinedType
|
|
29
|
+
The default guilds to enable commands in
|
|
30
|
+
autodefer : bool | AutodeferMode
|
|
31
|
+
If True, all commands in this plugin will automatically defer if it is taking longer than 2 seconds to respond.
|
|
32
|
+
This can be overridden on a per-command basis.
|
|
33
|
+
is_dm_enabled : bool | hikari.UndefinedType
|
|
34
|
+
Whether commands in this plugin are enabled in DMs
|
|
35
|
+
This can be overridden on a per-command basis.
|
|
36
|
+
default_permissions : hikari.Permissions | hikari.UndefinedType
|
|
37
|
+
The default permissions for this plugin
|
|
38
|
+
This can be overridden on a per-command basis.
|
|
39
|
+
is_nsfw : bool | hikari.UndefinedType
|
|
40
|
+
Whether this plugin is only usable in NSFW channels
|
|
41
|
+
|
|
42
|
+
!!! note
|
|
43
|
+
Parameters left as `hikari.UNDEFINED` will be inherited from the parent client.
|
|
26
44
|
|
|
27
45
|
Usage
|
|
28
46
|
-----
|
|
@@ -53,6 +71,22 @@ class GatewayPluginBase(PluginBase[GatewayClientT]):
|
|
|
53
71
|
----------
|
|
54
72
|
name : str
|
|
55
73
|
The name of this plugin. This must be unique across all plugins.
|
|
74
|
+
default_enabled_guilds : t.Sequence[hikari.Snowflake] | hikari.UndefinedType
|
|
75
|
+
The default guilds to enable commands in
|
|
76
|
+
autodefer : bool | AutodeferMode
|
|
77
|
+
If True, all commands in this plugin will automatically defer if it is taking longer than 2 seconds to respond.
|
|
78
|
+
This can be overridden on a per-command basis.
|
|
79
|
+
is_dm_enabled : bool | hikari.UndefinedType
|
|
80
|
+
Whether commands in this plugin are enabled in DMs
|
|
81
|
+
This can be overridden on a per-command basis.
|
|
82
|
+
default_permissions : hikari.Permissions | hikari.UndefinedType
|
|
83
|
+
The default permissions for this plugin
|
|
84
|
+
This can be overridden on a per-command basis.
|
|
85
|
+
is_nsfw : bool | hikari.UndefinedType
|
|
86
|
+
Whether this plugin is only usable in NSFW channels
|
|
87
|
+
|
|
88
|
+
!!! note
|
|
89
|
+
Parameters left as `hikari.UNDEFINED` will be inherited from the parent client.
|
|
56
90
|
|
|
57
91
|
Usage
|
|
58
92
|
-----
|
|
@@ -70,8 +104,25 @@ class GatewayPluginBase(PluginBase[GatewayClientT]):
|
|
|
70
104
|
```
|
|
71
105
|
"""
|
|
72
106
|
|
|
73
|
-
def __init__(
|
|
74
|
-
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
name: str,
|
|
110
|
+
*,
|
|
111
|
+
default_enabled_guilds: t.Sequence[hikari.Snowflakeish | hikari.PartialGuild]
|
|
112
|
+
| hikari.UndefinedType = hikari.UNDEFINED,
|
|
113
|
+
autodefer: bool | AutodeferMode | hikari.UndefinedType = hikari.UNDEFINED,
|
|
114
|
+
is_dm_enabled: bool | hikari.UndefinedType = hikari.UNDEFINED,
|
|
115
|
+
default_permissions: hikari.Permissions | hikari.UndefinedType = hikari.UNDEFINED,
|
|
116
|
+
is_nsfw: bool | hikari.UndefinedType = hikari.UNDEFINED,
|
|
117
|
+
) -> None:
|
|
118
|
+
super().__init__(
|
|
119
|
+
name=name,
|
|
120
|
+
default_enabled_guilds=default_enabled_guilds,
|
|
121
|
+
autodefer=autodefer,
|
|
122
|
+
is_dm_enabled=is_dm_enabled,
|
|
123
|
+
default_permissions=default_permissions,
|
|
124
|
+
is_nsfw=is_nsfw,
|
|
125
|
+
)
|
|
75
126
|
self._listeners: dict[type[hikari.Event], set[EventCallbackT[t.Any]]] = {}
|
|
76
127
|
|
|
77
128
|
@property
|
|
@@ -89,7 +140,7 @@ class GatewayPluginBase(PluginBase[GatewayClientT]):
|
|
|
89
140
|
----------
|
|
90
141
|
event : type[hikari.Event]
|
|
91
142
|
The event to subscribe to.
|
|
92
|
-
callback : Callable[[EventT], Awaitable[None]]
|
|
143
|
+
callback : t.Callable[[EventT], t.Awaitable[None]]
|
|
93
144
|
The callback to call when the event is dispatched.
|
|
94
145
|
"""
|
|
95
146
|
if event not in self.listeners:
|
|
@@ -107,7 +158,7 @@ class GatewayPluginBase(PluginBase[GatewayClientT]):
|
|
|
107
158
|
----------
|
|
108
159
|
event : type[hikari.Event]
|
|
109
160
|
The event to unsubscribe from.
|
|
110
|
-
callback : Callable[[EventT], Awaitable[None]]
|
|
161
|
+
callback : t.Callable[[EventT], t.Awaitable[None]]
|
|
111
162
|
The callback to unsubscribe.
|
|
112
163
|
"""
|
|
113
164
|
if event not in self.listeners:
|