disagreement 0.0.1__py3-none-any.whl → 0.0.2__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.
@@ -0,0 +1,627 @@
1
+ # disagreement/ext/app_commands/handler.py
2
+
3
+ import inspect
4
+ from typing import (
5
+ TYPE_CHECKING,
6
+ Dict,
7
+ Optional,
8
+ List,
9
+ Any,
10
+ Tuple,
11
+ Union,
12
+ get_origin,
13
+ get_args,
14
+ Literal,
15
+ )
16
+
17
+ if TYPE_CHECKING:
18
+ from disagreement.client import Client
19
+ from disagreement.interactions import Interaction, ResolvedData, Snowflake
20
+ from disagreement.enums import (
21
+ ApplicationCommandType,
22
+ ApplicationCommandOptionType,
23
+ InteractionType,
24
+ )
25
+ from .commands import (
26
+ AppCommand,
27
+ SlashCommand,
28
+ UserCommand,
29
+ MessageCommand,
30
+ AppCommandGroup,
31
+ )
32
+ from .context import AppCommandContext
33
+ from disagreement.models import (
34
+ User,
35
+ Member,
36
+ Role,
37
+ Attachment,
38
+ Message,
39
+ ) # For resolved data
40
+
41
+ # Channel models would also go here
42
+
43
+ # Placeholder for models not yet fully defined or imported
44
+ if not TYPE_CHECKING:
45
+ from disagreement.enums import (
46
+ ApplicationCommandType,
47
+ ApplicationCommandOptionType,
48
+ InteractionType,
49
+ )
50
+ from .commands import (
51
+ AppCommand,
52
+ SlashCommand,
53
+ UserCommand,
54
+ MessageCommand,
55
+ AppCommandGroup,
56
+ )
57
+ from .context import AppCommandContext
58
+
59
+ User = Any
60
+ Member = Any
61
+ Role = Any
62
+ Attachment = Any
63
+ Channel = Any
64
+ Message = Any
65
+
66
+
67
+ class AppCommandHandler:
68
+ """
69
+ Manages application command registration, parsing, and dispatching.
70
+ """
71
+
72
+ def __init__(self, client: "Client"):
73
+ self.client: "Client" = client
74
+ # Store commands: key could be (name, type) for global, or (name, type, guild_id) for guild-specific
75
+ # For simplicity, let's start with a flat structure and refine if needed for guild commands.
76
+ # A more robust system might have separate dicts for global and guild commands.
77
+ self._slash_commands: Dict[str, SlashCommand] = {}
78
+ self._user_commands: Dict[str, UserCommand] = {}
79
+ self._message_commands: Dict[str, MessageCommand] = {}
80
+ self._app_command_groups: Dict[str, AppCommandGroup] = {}
81
+ self._converter_registry: Dict[type, type] = {}
82
+
83
+ def add_command(self, command: Union["AppCommand", "AppCommandGroup"]) -> None:
84
+ """Adds an application command or a command group to the handler."""
85
+ if isinstance(command, AppCommandGroup):
86
+ if command.name in self._app_command_groups:
87
+ raise ValueError(
88
+ f"AppCommandGroup '{command.name}' is already registered."
89
+ )
90
+ self._app_command_groups[command.name] = command
91
+ return
92
+
93
+ if isinstance(command, SlashCommand):
94
+ if command.name in self._slash_commands:
95
+ raise ValueError(
96
+ f"SlashCommand '{command.name}' is already registered."
97
+ )
98
+ self._slash_commands[command.name] = command
99
+ return
100
+
101
+ if isinstance(command, UserCommand):
102
+ if command.name in self._user_commands:
103
+ raise ValueError(f"UserCommand '{command.name}' is already registered.")
104
+ self._user_commands[command.name] = command
105
+ return
106
+
107
+ if isinstance(command, MessageCommand):
108
+ if command.name in self._message_commands:
109
+ raise ValueError(
110
+ f"MessageCommand '{command.name}' is already registered."
111
+ )
112
+ self._message_commands[command.name] = command
113
+ return
114
+
115
+ if isinstance(command, AppCommand):
116
+ # Fallback for plain AppCommand objects
117
+ if command.type == ApplicationCommandType.CHAT_INPUT:
118
+ if command.name in self._slash_commands:
119
+ raise ValueError(
120
+ f"SlashCommand '{command.name}' is already registered."
121
+ )
122
+ self._slash_commands[command.name] = command # type: ignore
123
+ elif command.type == ApplicationCommandType.USER:
124
+ if command.name in self._user_commands:
125
+ raise ValueError(
126
+ f"UserCommand '{command.name}' is already registered."
127
+ )
128
+ self._user_commands[command.name] = command # type: ignore
129
+ elif command.type == ApplicationCommandType.MESSAGE:
130
+ if command.name in self._message_commands:
131
+ raise ValueError(
132
+ f"MessageCommand '{command.name}' is already registered."
133
+ )
134
+ self._message_commands[command.name] = command # type: ignore
135
+ else:
136
+ raise TypeError(
137
+ f"Unsupported command type: {command.type} for '{command.name}'"
138
+ )
139
+ else:
140
+ raise TypeError("Can only add AppCommand or AppCommandGroup instances.")
141
+
142
+ def remove_command(
143
+ self, name: str
144
+ ) -> Optional[Union["AppCommand", "AppCommandGroup"]]:
145
+ """Removes an application command or group by name."""
146
+ if name in self._slash_commands:
147
+ return self._slash_commands.pop(name)
148
+ if name in self._user_commands:
149
+ return self._user_commands.pop(name)
150
+ if name in self._message_commands:
151
+ return self._message_commands.pop(name)
152
+ if name in self._app_command_groups:
153
+ return self._app_command_groups.pop(name)
154
+ return None
155
+
156
+ def register_converter(self, annotation: type, converter_cls: type) -> None:
157
+ """Register a custom converter class for a type annotation."""
158
+ self._converter_registry[annotation] = converter_cls
159
+
160
+ def get_converter(self, annotation: type) -> Optional[type]:
161
+ """Retrieve a registered converter class for a type annotation."""
162
+ return self._converter_registry.get(annotation)
163
+
164
+ def get_command(
165
+ self,
166
+ name: str,
167
+ command_type: "ApplicationCommandType",
168
+ interaction_options: Optional[List[Dict[str, Any]]] = None,
169
+ ) -> Optional["AppCommand"]:
170
+ """Retrieves a command of a specific type."""
171
+ if command_type == ApplicationCommandType.CHAT_INPUT:
172
+ if not interaction_options:
173
+ return self._slash_commands.get(name)
174
+
175
+ # Handle subcommands/groups
176
+ current_options = interaction_options
177
+ target_command_or_group: Optional[Union[AppCommand, AppCommandGroup]] = (
178
+ self._app_command_groups.get(name)
179
+ )
180
+
181
+ if not target_command_or_group:
182
+ return self._slash_commands.get(name)
183
+
184
+ final_command: Optional[AppCommand] = None
185
+
186
+ while current_options:
187
+ opt_data = current_options[0]
188
+ opt_name = opt_data.get("name")
189
+ opt_type = (
190
+ ApplicationCommandOptionType(opt_data["type"])
191
+ if opt_data.get("type")
192
+ else None
193
+ )
194
+
195
+ if not opt_name or not isinstance(
196
+ target_command_or_group, AppCommandGroup
197
+ ):
198
+ break
199
+
200
+ next_target = target_command_or_group.get_command(opt_name)
201
+
202
+ if isinstance(next_target, AppCommand) and (
203
+ opt_type == ApplicationCommandOptionType.SUB_COMMAND
204
+ or not opt_data.get("options")
205
+ ):
206
+ final_command = next_target
207
+ break
208
+ elif (
209
+ isinstance(next_target, AppCommandGroup)
210
+ and opt_type == ApplicationCommandOptionType.SUB_COMMAND_GROUP
211
+ ):
212
+ target_command_or_group = next_target
213
+ current_options = opt_data.get("options", [])
214
+ if not current_options:
215
+ break
216
+ else:
217
+ break
218
+
219
+ return final_command
220
+
221
+ if command_type == ApplicationCommandType.USER:
222
+ return self._user_commands.get(name)
223
+
224
+ if command_type == ApplicationCommandType.MESSAGE:
225
+ return self._message_commands.get(name)
226
+
227
+ return None
228
+
229
+ async def _resolve_option_value(
230
+ self,
231
+ value: Any,
232
+ expected_type: Any,
233
+ resolved_data: Optional["ResolvedData"],
234
+ guild_id: Optional["Snowflake"],
235
+ ) -> Any:
236
+ """
237
+ Resolves an option value to the expected Python type using resolved_data.
238
+ """
239
+ converter_cls = self.get_converter(expected_type)
240
+ if converter_cls:
241
+ try:
242
+ init_params = inspect.signature(converter_cls.__init__).parameters
243
+ if "client" in init_params:
244
+ converter_instance = converter_cls(client=self.client) # type: ignore[arg-type]
245
+ else:
246
+ converter_instance = converter_cls()
247
+ return await converter_instance.convert(None, value) # type: ignore[arg-type]
248
+ except Exception:
249
+ pass
250
+
251
+ # This is a simplified resolver. A more robust one would use converters.
252
+ if resolved_data:
253
+ if expected_type is User or expected_type.__name__ == "User":
254
+ return resolved_data.users.get(value) if resolved_data.users else None
255
+
256
+ if expected_type is Member or expected_type.__name__ == "Member":
257
+ member_obj = (
258
+ resolved_data.members.get(value) if resolved_data.members else None
259
+ )
260
+ if member_obj:
261
+ if (
262
+ hasattr(member_obj, "username")
263
+ and not member_obj.username
264
+ and resolved_data.users
265
+ ):
266
+ user_obj = resolved_data.users.get(value)
267
+ if user_obj:
268
+ member_obj.username = user_obj.username
269
+ member_obj.discriminator = user_obj.discriminator
270
+ member_obj.avatar = user_obj.avatar
271
+ member_obj.bot = user_obj.bot
272
+ member_obj.user = user_obj # type: ignore[attr-defined]
273
+ return member_obj
274
+ return None
275
+ if expected_type is Role or expected_type.__name__ == "Role":
276
+ return resolved_data.roles.get(value) if resolved_data.roles else None
277
+ if expected_type is Attachment or expected_type.__name__ == "Attachment":
278
+ return (
279
+ resolved_data.attachments.get(value)
280
+ if resolved_data.attachments
281
+ else None
282
+ )
283
+ if expected_type is Message or expected_type.__name__ == "Message":
284
+ return (
285
+ resolved_data.messages.get(value)
286
+ if resolved_data.messages
287
+ else None
288
+ )
289
+ if "Channel" in expected_type.__name__:
290
+ return (
291
+ resolved_data.channels.get(value)
292
+ if resolved_data.channels
293
+ else None
294
+ )
295
+
296
+ # For basic types, Discord already sends them correctly (string, int, bool, float)
297
+ if isinstance(value, expected_type):
298
+ return value
299
+ try: # Attempt direct conversion for basic types if Discord sent string for int/float/bool
300
+ if expected_type is int:
301
+ return int(value)
302
+ if expected_type is float:
303
+ return float(value)
304
+ if expected_type is bool: # Discord sends true/false
305
+ if isinstance(value, str):
306
+ return value.lower() == "true"
307
+ return bool(value)
308
+ except (ValueError, TypeError):
309
+ pass # Conversion failed
310
+ return value # Return as is if no specific resolution or conversion applied
311
+
312
+ async def _resolve_value(
313
+ self,
314
+ value: Any,
315
+ expected_type: Any,
316
+ resolved_data: Optional["ResolvedData"],
317
+ guild_id: Optional["Snowflake"],
318
+ ) -> Any:
319
+ """Public wrapper around ``_resolve_option_value`` used by tests."""
320
+
321
+ return await self._resolve_option_value(
322
+ value=value,
323
+ expected_type=expected_type,
324
+ resolved_data=resolved_data,
325
+ guild_id=guild_id,
326
+ )
327
+
328
+ async def _parse_interaction_options(
329
+ self,
330
+ command_params: Dict[str, inspect.Parameter], # From command.params
331
+ interaction_options: Optional[List[Dict[str, Any]]],
332
+ resolved_data: Optional["ResolvedData"],
333
+ guild_id: Optional["Snowflake"],
334
+ ) -> Tuple[List[Any], Dict[str, Any]]:
335
+ """
336
+ Parses options from an interaction payload and maps them to command function arguments.
337
+ """
338
+ args_list: List[Any] = []
339
+ kwargs_dict: Dict[str, Any] = {}
340
+
341
+ if not interaction_options: # No options provided in interaction
342
+ # Check if command has required params without defaults
343
+ for name, param in command_params.items():
344
+ if param.default == inspect.Parameter.empty:
345
+ # This should ideally be caught by Discord if option is marked required
346
+ raise ValueError(f"Missing required option: {name}")
347
+ return args_list, kwargs_dict
348
+
349
+ # Create a dictionary of provided options by name for easier lookup
350
+ provided_options: Dict[str, Any] = {
351
+ opt["name"]: opt["value"] for opt in interaction_options if "value" in opt
352
+ }
353
+
354
+ for name, param in command_params.items():
355
+ if name in provided_options:
356
+ raw_value = provided_options[name]
357
+ expected_type = (
358
+ param.annotation
359
+ if param.annotation != inspect.Parameter.empty
360
+ else str
361
+ )
362
+
363
+ # Handle Optional[T]
364
+ origin_type = get_origin(expected_type)
365
+ if origin_type is Union:
366
+ union_args = get_args(expected_type)
367
+ # Assuming Optional[T] is Union[T, NoneType]
368
+ non_none_types = [t for t in union_args if t is not type(None)]
369
+ if len(non_none_types) == 1:
370
+ expected_type = non_none_types[0]
371
+ # Else, complex Union, might need more sophisticated handling or default to raw_value/str
372
+ elif origin_type is Literal:
373
+ literal_args = get_args(expected_type)
374
+ if literal_args:
375
+ expected_type = type(literal_args[0])
376
+ else:
377
+ expected_type = str
378
+
379
+ resolved_value = await self._resolve_option_value(
380
+ raw_value, expected_type, resolved_data, guild_id
381
+ )
382
+
383
+ if (
384
+ param.kind == inspect.Parameter.KEYWORD_ONLY
385
+ or param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
386
+ ):
387
+ kwargs_dict[name] = resolved_value
388
+ # Note: Slash commands don't map directly to *args. All options are named.
389
+ # So, we'll primarily use kwargs_dict and then construct args_list based on param order if needed,
390
+ # but Discord sends named options, so direct kwarg usage is more natural.
391
+ elif param.default != inspect.Parameter.empty:
392
+ kwargs_dict[name] = param.default
393
+ else:
394
+ # Required parameter not provided by Discord - this implies an issue with command definition
395
+ # or Discord's validation, as Discord should enforce required options.
396
+ raise ValueError(
397
+ f"Required option '{name}' not found in interaction payload."
398
+ )
399
+
400
+ # Populate args_list based on the order in command_params for positional arguments
401
+ # This assumes that all args that are not keyword-only are passed positionally if present in kwargs_dict
402
+ for name, param in command_params.items():
403
+ if param.kind == inspect.Parameter.POSITIONAL_ONLY or (
404
+ param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
405
+ and name in kwargs_dict
406
+ ):
407
+ if name in kwargs_dict: # Ensure it was resolved or had a default
408
+ args_list.append(kwargs_dict[name])
409
+ # If it was POSITIONAL_ONLY and not in kwargs_dict, it's an error (already raised)
410
+ elif param.kind == inspect.Parameter.VAR_POSITIONAL: # *args
411
+ # Slash commands don't map to *args well. This would be empty.
412
+ pass
413
+
414
+ # Filter kwargs_dict to only include actual KEYWORD_ONLY or POSITIONAL_OR_KEYWORD params
415
+ # that were not used for args_list (if strict positional/keyword separation is desired).
416
+ # For slash commands, it's simpler to pass all resolved named options as kwargs.
417
+ final_kwargs = {
418
+ k: v
419
+ for k, v in kwargs_dict.items()
420
+ if k in command_params
421
+ and command_params[k].kind != inspect.Parameter.POSITIONAL_ONLY
422
+ }
423
+
424
+ # For simplicity with slash commands, let's assume all resolved options are passed via kwargs
425
+ # and the command signature is primarily (self, ctx, **options) or (ctx, **options)
426
+ # or (self, ctx, option1, option2) where names match.
427
+ # The AppCommand.invoke will handle passing them.
428
+ # The current args_list and final_kwargs might be redundant if invoke just uses **final_kwargs.
429
+ # Let's return kwargs_dict directly for now, and AppCommand.invoke can map them.
430
+
431
+ return [], kwargs_dict # Return empty args, all in kwargs for now.
432
+
433
+ async def dispatch_app_command_error(
434
+ self, context: "AppCommandContext", error: Exception
435
+ ) -> None:
436
+ """Dispatches an app command error to the client if implemented."""
437
+ if hasattr(self.client, "on_app_command_error"):
438
+ await self.client.on_app_command_error(context, error)
439
+
440
+ async def process_interaction(self, interaction: "Interaction") -> None:
441
+ """Processes an incoming interaction."""
442
+ if interaction.type == InteractionType.MODAL_SUBMIT:
443
+ callback = getattr(self.client, "on_modal_submit", None)
444
+ if callback is not None:
445
+ from typing import Awaitable, Callable, cast
446
+
447
+ await cast(Callable[["Interaction"], Awaitable[None]], callback)(
448
+ interaction
449
+ )
450
+ return
451
+
452
+ if interaction.type == InteractionType.APPLICATION_COMMAND_AUTOCOMPLETE:
453
+ callback = getattr(self.client, "on_autocomplete", None)
454
+ if callback is not None:
455
+ from typing import Awaitable, Callable, cast
456
+
457
+ await cast(Callable[["Interaction"], Awaitable[None]], callback)(
458
+ interaction
459
+ )
460
+ return
461
+
462
+ if interaction.type != InteractionType.APPLICATION_COMMAND:
463
+ return
464
+
465
+ if not interaction.data or not interaction.data.name:
466
+ from .context import AppCommandContext
467
+
468
+ ctx = AppCommandContext(
469
+ bot=self.client, interaction=interaction, command=None
470
+ )
471
+ await ctx.send("Command not found.", ephemeral=True)
472
+ return
473
+
474
+ command_name = interaction.data.name
475
+ command_type = interaction.data.type or ApplicationCommandType.CHAT_INPUT
476
+ command = self.get_command(
477
+ command_name,
478
+ command_type,
479
+ interaction.data.options if interaction.data else None,
480
+ )
481
+
482
+ if not command:
483
+ from .context import AppCommandContext
484
+
485
+ ctx = AppCommandContext(
486
+ bot=self.client, interaction=interaction, command=None
487
+ )
488
+ await ctx.send(f"Command '{command_name}' not found.", ephemeral=True)
489
+ return
490
+
491
+ # Create context
492
+ from .context import AppCommandContext # Ensure AppCommandContext is available
493
+
494
+ ctx = AppCommandContext(
495
+ bot=self.client, interaction=interaction, command=command
496
+ )
497
+
498
+ try:
499
+ # Prepare arguments for the command callback
500
+ # Skip 'self' and 'ctx' from command.params for parsing interaction options
501
+ params_to_parse = {
502
+ name: param
503
+ for name, param in command.params.items()
504
+ if name not in ("self", "ctx")
505
+ }
506
+
507
+ if command.type in (
508
+ ApplicationCommandType.USER,
509
+ ApplicationCommandType.MESSAGE,
510
+ ):
511
+ # Context menu commands provide a target_id. Resolve and pass it
512
+ args = []
513
+ kwargs = {}
514
+ if params_to_parse and interaction.data and interaction.data.target_id:
515
+ first_param = next(iter(params_to_parse.values()))
516
+ expected = (
517
+ first_param.annotation
518
+ if first_param.annotation != inspect.Parameter.empty
519
+ else str
520
+ )
521
+ resolved = await self._resolve_option_value(
522
+ interaction.data.target_id,
523
+ expected,
524
+ interaction.data.resolved,
525
+ interaction.guild_id,
526
+ )
527
+ if first_param.kind in (
528
+ inspect.Parameter.POSITIONAL_ONLY,
529
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
530
+ ):
531
+ args.append(resolved)
532
+ else:
533
+ kwargs[first_param.name] = resolved
534
+
535
+ await command.invoke(ctx, *args, **kwargs)
536
+ else:
537
+ parsed_args, parsed_kwargs = await self._parse_interaction_options(
538
+ command_params=params_to_parse,
539
+ interaction_options=interaction.data.options,
540
+ resolved_data=interaction.data.resolved,
541
+ guild_id=interaction.guild_id,
542
+ )
543
+
544
+ await command.invoke(ctx, *parsed_args, **parsed_kwargs)
545
+
546
+ except Exception as e:
547
+ print(f"Error invoking app command '{command.name}': {e}")
548
+ await self.dispatch_app_command_error(ctx, e)
549
+ # else:
550
+ # # Default error reply if no handler on client
551
+ # try:
552
+ # await ctx.send(f"An error occurred: {e}", ephemeral=True)
553
+ # except Exception as send_e:
554
+ # print(f"Failed to send error message for app command: {send_e}")
555
+
556
+ async def sync_commands(
557
+ self, application_id: "Snowflake", guild_id: Optional["Snowflake"] = None
558
+ ) -> None:
559
+ """
560
+ Synchronizes (registers/updates) all application commands with Discord.
561
+ If guild_id is provided, syncs commands for that guild. Otherwise, syncs global commands.
562
+ """
563
+ commands_to_sync: List[Dict[str, Any]] = []
564
+
565
+ # Collect commands based on scope (global or specific guild)
566
+ # This needs to be more sophisticated to handle guild_ids on commands/groups
567
+
568
+ source_commands = (
569
+ list(self._slash_commands.values())
570
+ + list(self._user_commands.values())
571
+ + list(self._message_commands.values())
572
+ + list(self._app_command_groups.values())
573
+ )
574
+
575
+ for cmd_or_group in source_commands:
576
+ # Determine if this command/group should be synced for the current scope
577
+ is_guild_specific_command = (
578
+ cmd_or_group.guild_ids is not None and len(cmd_or_group.guild_ids) > 0
579
+ )
580
+
581
+ if guild_id: # Syncing for a specific guild
582
+ # Skip if not a guild-specific command OR if it's for a different guild
583
+ if not is_guild_specific_command or (
584
+ cmd_or_group.guild_ids is not None
585
+ and guild_id not in cmd_or_group.guild_ids
586
+ ):
587
+ continue
588
+ else: # Syncing global commands
589
+ if is_guild_specific_command:
590
+ continue # Skip guild-specific commands when syncing global
591
+
592
+ # Use the to_dict() method from AppCommand or AppCommandGroup
593
+ try:
594
+ payload = cmd_or_group.to_dict()
595
+ commands_to_sync.append(payload)
596
+ except AttributeError:
597
+ print(
598
+ f"Warning: Command or group '{cmd_or_group.name}' does not have a to_dict() method. Skipping."
599
+ )
600
+ except Exception as e:
601
+ print(
602
+ f"Error converting command/group '{cmd_or_group.name}' to dict: {e}. Skipping."
603
+ )
604
+
605
+ if not commands_to_sync:
606
+ print(
607
+ f"No commands to sync for {'guild ' + str(guild_id) if guild_id else 'global'} scope."
608
+ )
609
+ return
610
+
611
+ try:
612
+ if guild_id:
613
+ print(
614
+ f"Syncing {len(commands_to_sync)} commands for guild {guild_id}..."
615
+ )
616
+ await self.client._http.bulk_overwrite_guild_application_commands(
617
+ application_id, guild_id, commands_to_sync
618
+ )
619
+ else:
620
+ print(f"Syncing {len(commands_to_sync)} global commands...")
621
+ await self.client._http.bulk_overwrite_global_application_commands(
622
+ application_id, commands_to_sync
623
+ )
624
+ print("Command sync successful.")
625
+ except Exception as e:
626
+ print(f"Error syncing application commands: {e}")
627
+ # Consider re-raising or specific error handling
@@ -0,0 +1,49 @@
1
+ # disagreement/ext/commands/__init__.py
2
+
3
+ """
4
+ disagreement.ext.commands - A command framework extension for the Disagreement library.
5
+ """
6
+
7
+ from .cog import Cog
8
+ from .core import (
9
+ Command,
10
+ CommandContext,
11
+ CommandHandler,
12
+ ) # CommandHandler might be internal
13
+ from .decorators import command, listener, check, check_any, cooldown
14
+ from .errors import (
15
+ CommandError,
16
+ CommandNotFound,
17
+ BadArgument,
18
+ MissingRequiredArgument,
19
+ ArgumentParsingError,
20
+ CheckFailure,
21
+ CheckAnyFailure,
22
+ CommandOnCooldown,
23
+ CommandInvokeError,
24
+ )
25
+
26
+ __all__ = [
27
+ # Cog
28
+ "Cog",
29
+ # Core
30
+ "Command",
31
+ "CommandContext",
32
+ # "CommandHandler", # Usually not part of public API for direct use by bot devs
33
+ # Decorators
34
+ "command",
35
+ "listener",
36
+ "check",
37
+ "check_any",
38
+ "cooldown",
39
+ # Errors
40
+ "CommandError",
41
+ "CommandNotFound",
42
+ "BadArgument",
43
+ "MissingRequiredArgument",
44
+ "ArgumentParsingError",
45
+ "CheckFailure",
46
+ "CheckAnyFailure",
47
+ "CommandOnCooldown",
48
+ "CommandInvokeError",
49
+ ]