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