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.
Files changed (46) hide show
  1. arc/__init__.py +55 -5
  2. arc/abc/__init__.py +32 -1
  3. arc/abc/client.py +207 -67
  4. arc/abc/command.py +245 -34
  5. arc/abc/error_handler.py +33 -2
  6. arc/abc/hookable.py +24 -0
  7. arc/abc/limiter.py +61 -0
  8. arc/abc/option.py +73 -5
  9. arc/abc/plugin.py +185 -29
  10. arc/client.py +103 -33
  11. arc/command/message.py +21 -18
  12. arc/command/option/attachment.py +9 -5
  13. arc/command/option/bool.py +9 -6
  14. arc/command/option/channel.py +9 -5
  15. arc/command/option/float.py +11 -7
  16. arc/command/option/int.py +11 -7
  17. arc/command/option/mentionable.py +9 -5
  18. arc/command/option/role.py +9 -5
  19. arc/command/option/str.py +11 -7
  20. arc/command/option/user.py +9 -5
  21. arc/command/slash.py +222 -197
  22. arc/command/user.py +20 -17
  23. arc/context/autocomplete.py +1 -0
  24. arc/context/base.py +216 -105
  25. arc/errors.py +52 -10
  26. arc/events.py +5 -1
  27. arc/extension.py +23 -0
  28. arc/internal/about.py +1 -1
  29. arc/internal/deprecation.py +3 -4
  30. arc/internal/options.py +106 -0
  31. arc/internal/sigparse.py +19 -1
  32. arc/internal/sync.py +13 -10
  33. arc/internal/types.py +34 -15
  34. arc/locale.py +28 -0
  35. arc/plugin.py +56 -5
  36. arc/utils/__init__.py +53 -2
  37. arc/utils/hooks/__init__.py +25 -0
  38. arc/utils/{hooks.py → hooks/basic.py} +28 -1
  39. arc/utils/hooks/limiters.py +217 -0
  40. arc/utils/ratelimiter.py +243 -0
  41. {hikari_arc-0.4.0.dist-info → hikari_arc-0.6.0.dist-info}/METADATA +13 -8
  42. hikari_arc-0.6.0.dist-info/RECORD +52 -0
  43. hikari_arc-0.4.0.dist-info/RECORD +0 -47
  44. {hikari_arc-0.4.0.dist-info → hikari_arc-0.6.0.dist-info}/LICENSE +0 -0
  45. {hikari_arc-0.4.0.dist-info → hikari_arc-0.6.0.dist-info}/WHEEL +0 -0
  46. {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
- import hikari
5
+ if t.TYPE_CHECKING:
6
+ import hikari
4
7
 
5
- __all__ = (
6
- "ArcError",
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.4.0"
8
+ __version__: t.Final[str] = "0.6.0"
9
9
 
10
10
  # MIT License
11
11
  #
@@ -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: t.Optional[str] = None) -> None:
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 : Optional[str], optional
21
- The object's name that should be used instead, by default None
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(
@@ -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[t.Concatenate[EventT, ...], t.Awaitable[None]]) -> list[type[EventT]]:
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.slash_commands.values(), client.message_commands.values(), client.user_commands.values()
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
- created = await client.app.rest.set_application_commands(client.application, builders, guild)
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 GatewayClient, RESTClient
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
- GatewayClientT = t.TypeVar("GatewayClientT", bound="GatewayClient")
20
- RESTClientT = t.TypeVar("RESTClientT", bound="RESTClient")
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[t.Concatenate[EventT, ...], t.Awaitable[None]]"
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
- MessageContextCallbackT: t.TypeAlias = (
35
- "t.Callable[t.Concatenate[Context[ClientT], hikari.Message, ...], t.Awaitable[None]]"
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[t.Concatenate[CommandLocaleRequest, ...], LocaleResponse]"
46
- OptionLocaleRequestT: t.TypeAlias = "t.Callable[t.Concatenate[OptionLocaleRequest, ...], LocaleResponse]"
47
- CustomLocaleRequestT: t.TypeAlias = "t.Callable[t.Concatenate[CustomLocaleRequest, ...], str]"
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 hikari
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__(self, name: str) -> None:
74
- super().__init__(name)
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: