disagreement 0.1.0rc1__py3-none-any.whl → 0.1.0rc3__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.
disagreement/__init__.py CHANGED
@@ -14,7 +14,7 @@ __title__ = "disagreement"
14
14
  __author__ = "Slipstream"
15
15
  __license__ = "BSD 3-Clause License"
16
16
  __copyright__ = "Copyright 2025 Slipstream"
17
- __version__ = "0.1.0rc1"
17
+ __version__ = "0.1.0rc3"
18
18
 
19
19
  from .client import Client, AutoShardedClient
20
20
  from .models import Message, User, Reaction
@@ -30,12 +30,16 @@ from .errors import (
30
30
  NotFound,
31
31
  )
32
32
  from .color import Color
33
- from .utils import utcnow
33
+ from .utils import utcnow, message_pager
34
34
  from .enums import GatewayIntent, GatewayOpcode # Export enums
35
35
  from .error_handler import setup_global_error_handler
36
36
  from .hybrid_context import HybridContext
37
37
  from .ext import tasks
38
+ from .logging_config import setup_logging
38
39
 
39
- # Set up logging if desired
40
- # import logging
41
- # logging.getLogger(__name__).addHandler(logging.NullHandler())
40
+ import logging
41
+
42
+
43
+ # Configure a default logger if none has been configured yet
44
+ if not logging.getLogger().hasHandlers():
45
+ setup_logging(logging.INFO)
disagreement/client.py CHANGED
@@ -16,6 +16,7 @@ from typing import (
16
16
  List,
17
17
  Dict,
18
18
  )
19
+ from types import ModuleType
19
20
 
20
21
  from .http import HTTPClient
21
22
  from .gateway import GatewayClient
@@ -28,6 +29,7 @@ from .ext.commands.core import CommandHandler
28
29
  from .ext.commands.cog import Cog
29
30
  from .ext.app_commands.handler import AppCommandHandler
30
31
  from .ext.app_commands.context import AppCommandContext
32
+ from .ext import loader as ext_loader
31
33
  from .interactions import Interaction, Snowflake
32
34
  from .error_handler import setup_global_error_handler
33
35
  from .voice_client import VoiceClient
@@ -121,14 +123,10 @@ class Client:
121
123
 
122
124
  self._closed: bool = False
123
125
  self._ready_event: asyncio.Event = asyncio.Event()
124
- self.application_id: Optional[Snowflake] = None # For Application Commands
125
126
  self.user: Optional["User"] = (
126
127
  None # The bot's own user object, populated on READY
127
128
  )
128
129
 
129
- # Initialize AppCommandHandler
130
- self.app_command_handler: AppCommandHandler = AppCommandHandler(client=self)
131
-
132
130
  # Internal Caches
133
131
  self._guilds: Dict[Snowflake, "Guild"] = {}
134
132
  self._channels: Dict[Snowflake, "Channel"] = (
@@ -638,6 +636,23 @@ class Client:
638
636
  # import traceback
639
637
  # traceback.print_exception(type(error.original), error.original, error.original.__traceback__)
640
638
 
639
+ # --- Extension Management Methods ---
640
+
641
+ def load_extension(self, name: str) -> ModuleType:
642
+ """Load an extension by name using :mod:`disagreement.ext.loader`."""
643
+
644
+ return ext_loader.load_extension(name)
645
+
646
+ def unload_extension(self, name: str) -> None:
647
+ """Unload a previously loaded extension."""
648
+
649
+ ext_loader.unload_extension(name)
650
+
651
+ def reload_extension(self, name: str) -> ModuleType:
652
+ """Reload an extension by name."""
653
+
654
+ return ext_loader.reload_extension(name)
655
+
641
656
  # --- Model Parsing and Fetching ---
642
657
 
643
658
  def parse_user(self, data: Dict[str, Any]) -> "User":
@@ -920,12 +935,12 @@ class Client:
920
935
  await view._start(self)
921
936
  components_payload = view.to_components_payload()
922
937
  elif components:
923
- from .models import ActionRow as ActionRowModel
938
+ from .models import Component as ComponentModel
924
939
 
925
940
  components_payload = [
926
941
  comp.to_dict()
927
942
  for comp in components
928
- if isinstance(comp, ActionRowModel)
943
+ if isinstance(comp, ComponentModel)
929
944
  ]
930
945
 
931
946
  message_data = await self._http.send_message(
@@ -1016,6 +1031,26 @@ class Client:
1016
1031
  self._voice_clients[guild_id] = voice
1017
1032
  return voice
1018
1033
 
1034
+ async def add_reaction(self, channel_id: str, message_id: str, emoji: str) -> None:
1035
+ """|coro| Add a reaction to a message."""
1036
+
1037
+ await self.create_reaction(channel_id, message_id, emoji)
1038
+
1039
+ async def remove_reaction(
1040
+ self, channel_id: str, message_id: str, emoji: str
1041
+ ) -> None:
1042
+ """|coro| Remove the bot's reaction from a message."""
1043
+
1044
+ await self.delete_reaction(channel_id, message_id, emoji)
1045
+
1046
+ async def clear_reactions(self, channel_id: str, message_id: str) -> None:
1047
+ """|coro| Remove all reactions from a message."""
1048
+
1049
+ if self._closed:
1050
+ raise DisagreementException("Client is closed.")
1051
+
1052
+ await self._http.clear_reactions(channel_id, message_id)
1053
+
1019
1054
  async def create_reaction(
1020
1055
  self, channel_id: str, message_id: str, emoji: str
1021
1056
  ) -> None:
disagreement/color.py CHANGED
@@ -48,3 +48,31 @@ class Color:
48
48
 
49
49
  def to_rgb(self) -> tuple[int, int, int]:
50
50
  return ((self.value >> 16) & 0xFF, (self.value >> 8) & 0xFF, self.value & 0xFF)
51
+
52
+ @classmethod
53
+ def parse(cls, value: "Color | int | str | tuple[int, int, int] | None") -> "Color | None":
54
+ """Convert ``value`` to a :class:`Color` instance.
55
+
56
+ Parameters
57
+ ----------
58
+ value:
59
+ The value to convert. May be ``None``, an existing ``Color``, an
60
+ integer in the ``0xRRGGBB`` format, or a hex string like ``"#RRGGBB"``.
61
+
62
+ Returns
63
+ -------
64
+ Optional[Color]
65
+ A ``Color`` object if ``value`` is not ``None``.
66
+ """
67
+
68
+ if value is None:
69
+ return None
70
+ if isinstance(value, cls):
71
+ return value
72
+ if isinstance(value, int):
73
+ return cls(value)
74
+ if isinstance(value, str):
75
+ return cls.from_hex(value)
76
+ if isinstance(value, tuple) and len(value) == 3:
77
+ return cls.from_rgb(*value)
78
+ raise TypeError("Color value must be Color, int, str, tuple, or None")
disagreement/enums.py CHANGED
@@ -49,6 +49,11 @@ class GatewayIntent(IntEnum):
49
49
  AUTO_MODERATION_CONFIGURATION = 1 << 20
50
50
  AUTO_MODERATION_EXECUTION = 1 << 21
51
51
 
52
+ @classmethod
53
+ def none(cls) -> int:
54
+ """Return a bitmask representing no intents."""
55
+ return 0
56
+
52
57
  @classmethod
53
58
  def default(cls) -> int:
54
59
  """Returns default intents (excluding privileged ones like members, presences, message content)."""
@@ -44,3 +44,5 @@ __all__ = [
44
44
  "OptionMetadata",
45
45
  "AppCommandContext", # To be defined
46
46
  ]
47
+
48
+ from .hybrid import *
@@ -1,58 +1,25 @@
1
1
  # disagreement/ext/app_commands/commands.py
2
2
 
3
3
  import inspect
4
- from typing import Callable, Optional, List, Dict, Any, Union, TYPE_CHECKING
4
+ from typing import Any, Callable, Dict, List, Optional, Union, TYPE_CHECKING
5
5
 
6
+ from disagreement.enums import (
7
+ ApplicationCommandType,
8
+ ApplicationCommandOptionType,
9
+ IntegrationType,
10
+ InteractionContextType,
11
+ )
12
+ from disagreement.interactions import ApplicationCommandOption, Snowflake
6
13
 
7
14
  if TYPE_CHECKING:
8
- from disagreement.ext.commands.core import (
9
- Command as PrefixCommand,
10
- ) # Alias to avoid name clash
11
- from disagreement.interactions import ApplicationCommandOption, Snowflake
12
- from disagreement.enums import (
13
- ApplicationCommandType,
14
- IntegrationType,
15
- InteractionContextType,
16
- ApplicationCommandOptionType, # Added
17
- )
18
- from disagreement.ext.commands.cog import Cog # Corrected import path
19
-
20
- # Placeholder for Cog if not using the existing one or if it needs adaptation
15
+ from disagreement.ext.commands.core import Command as PrefixCommand
16
+ from disagreement.ext.commands.cog import Cog
17
+
21
18
  if not TYPE_CHECKING:
22
- # This dynamic Cog = Any might not be ideal if Cog is used in runtime type checks.
23
- # However, for type hinting purposes when TYPE_CHECKING is false, it avoids import.
24
- # If Cog is needed at runtime by this module (it is, for AppCommand.cog type hint),
25
- # it should be imported directly.
26
- # For now, the TYPE_CHECKING block handles the proper import for static analysis.
27
- # Let's ensure Cog is available at runtime if AppCommand.cog is accessed.
28
- # A simple way is to import it outside TYPE_CHECKING too, if it doesn't cause circularity.
29
- # Given its usage, a forward reference string 'Cog' might be better in AppCommand.cog type hint.
30
- # Let's try importing it directly for runtime, assuming no circularity with this specific module.
31
- try:
32
- from disagreement.ext.commands.cog import Cog
33
- except ImportError:
34
- Cog = Any # Fallback if direct import fails (e.g. during partial builds/tests)
35
- # Import PrefixCommand at runtime for HybridCommand
36
19
  try:
37
20
  from disagreement.ext.commands.core import Command as PrefixCommand
38
- except Exception: # pragma: no cover - safeguard against unusual import issues
39
- PrefixCommand = Any # type: ignore
40
- # Import enums used at runtime
41
- try:
42
- from disagreement.enums import (
43
- ApplicationCommandType,
44
- IntegrationType,
45
- InteractionContextType,
46
- ApplicationCommandOptionType,
47
- )
48
- from disagreement.interactions import ApplicationCommandOption, Snowflake
49
- except Exception: # pragma: no cover
50
- ApplicationCommandType = ApplicationCommandOptionType = IntegrationType = (
51
- InteractionContextType
52
- ) = Any # type: ignore
53
- ApplicationCommandOption = Snowflake = Any # type: ignore
54
- else: # When TYPE_CHECKING is true, Cog and PrefixCommand are already imported above.
55
- pass
21
+ except ImportError:
22
+ PrefixCommand = Any
56
23
 
57
24
 
58
25
  class AppCommand:
@@ -235,59 +202,6 @@ class MessageCommand(AppCommand):
235
202
  super().__init__(callback, type=ApplicationCommandType.MESSAGE, **kwargs)
236
203
 
237
204
 
238
- class HybridCommand(SlashCommand, PrefixCommand): # Inherit from both
239
- """
240
- Represents a command that can be invoked as both a slash command
241
- and a traditional prefix-based command.
242
- """
243
-
244
- def __init__(self, callback: Callable[..., Any], **kwargs: Any):
245
- # Initialize SlashCommand part (which calls AppCommand.__init__)
246
- # We need to ensure 'type' is correctly passed for AppCommand
247
- # kwargs for SlashCommand: name, description, guild_ids, default_member_permissions, nsfw, parent, cog, etc.
248
- # kwargs for PrefixCommand: name, aliases, brief, description, cog
249
-
250
- # Pop prefix-specific args before passing to SlashCommand constructor
251
- prefix_aliases = kwargs.pop("aliases", [])
252
- prefix_brief = kwargs.pop("brief", None)
253
- # Description is used by both, AppCommand's constructor will handle it.
254
- # Name is used by both. Cog is used by both.
255
-
256
- # Call SlashCommand's __init__
257
- # This will set up name, description, callback, type=CHAT_INPUT, options, etc.
258
- super().__init__(callback, **kwargs) # This is SlashCommand.__init__
259
-
260
- # Now, explicitly initialize the PrefixCommand parts that SlashCommand didn't cover
261
- # or that need specific values for the prefix version.
262
- # PrefixCommand.__init__(self, callback, name=self.name, aliases=prefix_aliases, brief=prefix_brief, description=self.description, cog=self.cog)
263
- # However, PrefixCommand.__init__ also sets self.params, which AppCommand already did.
264
- # We need to be careful not to re-initialize things unnecessarily or incorrectly.
265
- # Let's manually set the distinct attributes for the PrefixCommand aspect.
266
-
267
- # Attributes from PrefixCommand:
268
- # self.callback is already set by AppCommand
269
- # self.name is already set by AppCommand
270
- self.aliases: List[str] = (
271
- prefix_aliases # This was specific to HybridCommand before, now aligns with PrefixCommand
272
- )
273
- self.brief: Optional[str] = prefix_brief
274
- # self.description is already set by AppCommand (SlashCommand ensures it exists)
275
- # self.cog is already set by AppCommand
276
- # self.params is already set by AppCommand
277
-
278
- # Ensure the MRO is handled correctly. Python's MRO (C3 linearization)
279
- # should call SlashCommand's __init__ then AppCommand's __init__.
280
- # PrefixCommand.__init__ won't be called automatically unless we explicitly call it.
281
- # By setting attributes directly, we avoid potential issues with multiple __init__ calls
282
- # if their logic overlaps too much (e.g., both trying to set self.params).
283
-
284
- # We might need to override invoke if the context or argument passing differs significantly
285
- # between app command invocation and prefix command invocation.
286
- # For now, SlashCommand.invoke and PrefixCommand.invoke are separate.
287
- # The correct one will be called depending on how the command is dispatched.
288
- # The AppCommandHandler will use AppCommand.invoke (via SlashCommand).
289
- # The prefix CommandHandler will use PrefixCommand.invoke.
290
- # This seems acceptable.
291
205
 
292
206
 
293
207
  class AppCommandGroup:
@@ -26,8 +26,8 @@ from .commands import (
26
26
  MessageCommand,
27
27
  AppCommand,
28
28
  AppCommandGroup,
29
- HybridCommand,
30
29
  )
30
+ from .hybrid import HybridCommand
31
31
  from disagreement.interactions import (
32
32
  ApplicationCommandOption,
33
33
  ApplicationCommandOptionChoice,
@@ -1,6 +1,7 @@
1
1
  # disagreement/ext/app_commands/handler.py
2
2
 
3
3
  import inspect
4
+ import logging
4
5
  from typing import (
5
6
  TYPE_CHECKING,
6
7
  Dict,
@@ -64,6 +65,9 @@ if not TYPE_CHECKING:
64
65
  Message = Any
65
66
 
66
67
 
68
+ logger = logging.getLogger(__name__)
69
+
70
+
67
71
  class AppCommandHandler:
68
72
  """
69
73
  Manages application command registration, parsing, and dispatching.
@@ -544,7 +548,7 @@ class AppCommandHandler:
544
548
  await command.invoke(ctx, *parsed_args, **parsed_kwargs)
545
549
 
546
550
  except Exception as e:
547
- print(f"Error invoking app command '{command.name}': {e}")
551
+ logger.error("Error invoking app command '%s': %s", command.name, e)
548
552
  await self.dispatch_app_command_error(ctx, e)
549
553
  # else:
550
554
  # # Default error reply if no handler on client
@@ -594,34 +598,43 @@ class AppCommandHandler:
594
598
  payload = cmd_or_group.to_dict()
595
599
  commands_to_sync.append(payload)
596
600
  except AttributeError:
597
- print(
598
- f"Warning: Command or group '{cmd_or_group.name}' does not have a to_dict() method. Skipping."
601
+ logger.warning(
602
+ "Command or group '%s' does not have a to_dict() method. Skipping.",
603
+ cmd_or_group.name,
599
604
  )
600
605
  except Exception as e:
601
- print(
602
- f"Error converting command/group '{cmd_or_group.name}' to dict: {e}. Skipping."
606
+ logger.error(
607
+ "Error converting command/group '%s' to dict: %s. Skipping.",
608
+ cmd_or_group.name,
609
+ e,
603
610
  )
604
611
 
605
612
  if not commands_to_sync:
606
- print(
607
- f"No commands to sync for {'guild ' + str(guild_id) if guild_id else 'global'} scope."
613
+ logger.info(
614
+ "No commands to sync for %s scope.",
615
+ f"guild {guild_id}" if guild_id else "global",
608
616
  )
609
617
  return
610
618
 
611
619
  try:
612
620
  if guild_id:
613
- print(
614
- f"Syncing {len(commands_to_sync)} commands for guild {guild_id}..."
621
+ logger.info(
622
+ "Syncing %s commands for guild %s...",
623
+ len(commands_to_sync),
624
+ guild_id,
615
625
  )
616
626
  await self.client._http.bulk_overwrite_guild_application_commands(
617
627
  application_id, guild_id, commands_to_sync
618
628
  )
619
629
  else:
620
- print(f"Syncing {len(commands_to_sync)} global commands...")
630
+ logger.info(
631
+ "Syncing %s global commands...",
632
+ len(commands_to_sync),
633
+ )
621
634
  await self.client._http.bulk_overwrite_global_application_commands(
622
635
  application_id, commands_to_sync
623
636
  )
624
- print("Command sync successful.")
637
+ logger.info("Command sync successful.")
625
638
  except Exception as e:
626
- print(f"Error syncing application commands: {e}")
639
+ logger.error("Error syncing application commands: %s", e)
627
640
  # Consider re-raising or specific error handling
@@ -0,0 +1,61 @@
1
+ # disagreement/ext/app_commands/hybrid.py
2
+
3
+ from typing import Any, Callable, List, Optional
4
+
5
+ from .commands import SlashCommand
6
+ from disagreement.ext.commands.core import PrefixCommand
7
+
8
+
9
+ class HybridCommand(SlashCommand, PrefixCommand): # Inherit from both
10
+ """
11
+ Represents a command that can be invoked as both a slash command
12
+ and a traditional prefix-based command.
13
+ """
14
+
15
+ def __init__(self, callback: Callable[..., Any], **kwargs: Any):
16
+ # Initialize SlashCommand part (which calls AppCommand.__init__)
17
+ # We need to ensure 'type' is correctly passed for AppCommand
18
+ # kwargs for SlashCommand: name, description, guild_ids, default_member_permissions, nsfw, parent, cog, etc.
19
+ # kwargs for PrefixCommand: name, aliases, brief, description, cog
20
+
21
+ # Pop prefix-specific args before passing to SlashCommand constructor
22
+ prefix_aliases = kwargs.pop("aliases", [])
23
+ prefix_brief = kwargs.pop("brief", None)
24
+ # Description is used by both, AppCommand's constructor will handle it.
25
+ # Name is used by both. Cog is used by both.
26
+
27
+ # Call SlashCommand's __init__
28
+ # This will set up name, description, callback, type=CHAT_INPUT, options, etc.
29
+ super().__init__(callback, **kwargs) # This is SlashCommand.__init__
30
+
31
+ # Now, explicitly initialize the PrefixCommand parts that SlashCommand didn't cover
32
+ # or that need specific values for the prefix version.
33
+ # PrefixCommand.__init__(self, callback, name=self.name, aliases=prefix_aliases, brief=prefix_brief, description=self.description, cog=self.cog)
34
+ # However, PrefixCommand.__init__ also sets self.params, which AppCommand already did.
35
+ # We need to be careful not to re-initialize things unnecessarily or incorrectly.
36
+ # Let's manually set the distinct attributes for the PrefixCommand aspect.
37
+
38
+ # Attributes from PrefixCommand:
39
+ # self.callback is already set by AppCommand
40
+ # self.name is already set by AppCommand
41
+ self.aliases: List[str] = (
42
+ prefix_aliases # This was specific to HybridCommand before, now aligns with PrefixCommand
43
+ )
44
+ self.brief: Optional[str] = prefix_brief
45
+ # self.description is already set by AppCommand (SlashCommand ensures it exists)
46
+ # self.cog is already set by AppCommand
47
+ # self.params is already set by AppCommand
48
+
49
+ # Ensure the MRO is handled correctly. Python's MRO (C3 linearization)
50
+ # should call SlashCommand's __init__ then AppCommand's __init__.
51
+ # PrefixCommand.__init__ won't be called automatically unless we explicitly call it.
52
+ # By setting attributes directly, we avoid potential issues with multiple __init__ calls
53
+ # if their logic overlaps too much (e.g., both trying to set self.params).
54
+
55
+ # We might need to override invoke if the context or argument passing differs significantly
56
+ # between app command invocation and prefix command invocation.
57
+ # For now, SlashCommand.invoke and PrefixCommand.invoke are separate.
58
+ # The correct one will be called depending on how the command is dispatched.
59
+ # The AppCommandHandler will use AppCommand.invoke (via SlashCommand).
60
+ # The prefix CommandHandler will use PrefixCommand.invoke.
61
+ # This seems acceptable.
@@ -1,6 +1,7 @@
1
1
  # disagreement/ext/commands/cog.py
2
2
 
3
3
  import inspect
4
+ import logging
4
5
  from typing import TYPE_CHECKING, List, Tuple, Callable, Awaitable, Any, Dict, Union
5
6
 
6
7
  if TYPE_CHECKING:
@@ -16,6 +17,8 @@ else: # pragma: no cover - runtime imports for isinstance checks
16
17
  # EventDispatcher might be needed if cogs register listeners directly
17
18
  # from disagreement.event_dispatcher import EventDispatcher
18
19
 
20
+ logger = logging.getLogger(__name__)
21
+
19
22
 
20
23
  class Cog:
21
24
  """
@@ -59,8 +62,10 @@ class Cog:
59
62
  cmd.cog = self # Assign the cog instance to the command
60
63
  if cmd.name in self._commands:
61
64
  # This should ideally be caught earlier or handled by CommandHandler
62
- print(
63
- f"Warning: Duplicate command name '{cmd.name}' in cog '{self.cog_name}'. Overwriting."
65
+ logger.warning(
66
+ "Duplicate command name '%s' in cog '%s'. Overwriting.",
67
+ cmd.name,
68
+ self.cog_name,
64
69
  )
65
70
  self._commands[cmd.name.lower()] = cmd
66
71
  # Also register aliases
@@ -79,8 +84,10 @@ class Cog:
79
84
  # For AppCommandGroup, its commands will have cog set individually if they are AppCommands
80
85
  self._app_commands_and_groups.append(app_cmd_obj)
81
86
  else:
82
- print(
83
- f"Warning: Member '{member_name}' in cog '{self.cog_name}' has '__app_command_object__' but it's not an AppCommand or AppCommandGroup."
87
+ logger.warning(
88
+ "Member '%s' in cog '%s' has '__app_command_object__' but it's not an AppCommand or AppCommandGroup.",
89
+ member_name,
90
+ self.cog_name,
84
91
  )
85
92
 
86
93
  elif isinstance(member, (AppCommand, AppCommandGroup)):
@@ -92,8 +99,10 @@ class Cog:
92
99
  # This is a method decorated with @commands.Cog.listener or @commands.listener
93
100
  if not inspect.iscoroutinefunction(member):
94
101
  # Decorator should have caught this, but double check
95
- print(
96
- f"Warning: Listener '{member_name}' in cog '{self.cog_name}' is not a coroutine. Skipping."
102
+ logger.warning(
103
+ "Listener '%s' in cog '%s' is not a coroutine. Skipping.",
104
+ member_name,
105
+ self.cog_name,
97
106
  )
98
107
  continue
99
108
 
@@ -1,6 +1,9 @@
1
1
  # disagreement/ext/commands/core.py
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import asyncio
6
+ import logging
4
7
  import inspect
5
8
  from typing import (
6
9
  TYPE_CHECKING,
@@ -27,10 +30,12 @@ from .errors import (
27
30
  CommandInvokeError,
28
31
  )
29
32
  from .converters import run_converters, DEFAULT_CONVERTERS, Converter
30
- from .cog import Cog
31
33
  from disagreement.typing import Typing
32
34
 
35
+ logger = logging.getLogger(__name__)
36
+
33
37
  if TYPE_CHECKING:
38
+ from .cog import Cog
34
39
  from disagreement.client import Client
35
40
  from disagreement.models import Message, User
36
41
 
@@ -86,6 +91,9 @@ class Command:
86
91
  await self.callback(ctx, *args, **kwargs)
87
92
 
88
93
 
94
+ PrefixCommand = Command # Alias for clarity in hybrid commands
95
+
96
+
89
97
  class CommandContext:
90
98
  """
91
99
  Represents the context in which a command is being invoked.
@@ -123,7 +131,7 @@ class CommandContext:
123
131
 
124
132
  async def reply(
125
133
  self,
126
- content: str,
134
+ content: Optional[str] = None,
127
135
  *,
128
136
  mention_author: Optional[bool] = None,
129
137
  **kwargs: Any,
@@ -219,8 +227,10 @@ class CommandHandler:
219
227
  self.commands[command.name.lower()] = command
220
228
  for alias in command.aliases:
221
229
  if alias in self.commands:
222
- print(
223
- f"Warning: Alias '{alias}' for command '{command.name}' conflicts with an existing command or alias."
230
+ logger.warning(
231
+ "Alias '%s' for command '%s' conflicts with an existing command or alias.",
232
+ alias,
233
+ command.name,
224
234
  )
225
235
  self.commands[alias.lower()] = command
226
236
 
@@ -235,6 +245,8 @@ class CommandHandler:
235
245
  return self.commands.get(name.lower())
236
246
 
237
247
  def add_cog(self, cog_to_add: "Cog") -> None:
248
+ from .cog import Cog
249
+
238
250
  if not isinstance(cog_to_add, Cog):
239
251
  raise TypeError("Argument must be a subclass of Cog.")
240
252
 
@@ -252,8 +264,9 @@ class CommandHandler:
252
264
  for event_name, callback in cog_to_add.get_listeners():
253
265
  self.client._event_dispatcher.register(event_name.upper(), callback)
254
266
  else:
255
- print(
256
- f"Warning: Client does not have '_event_dispatcher'. Listeners for cog '{cog_to_add.cog_name}' not registered."
267
+ logger.warning(
268
+ "Client does not have '_event_dispatcher'. Listeners for cog '%s' not registered.",
269
+ cog_to_add.cog_name,
257
270
  )
258
271
 
259
272
  if hasattr(cog_to_add, "cog_load") and inspect.iscoroutinefunction(
@@ -261,7 +274,7 @@ class CommandHandler:
261
274
  ):
262
275
  asyncio.create_task(cog_to_add.cog_load())
263
276
 
264
- print(f"Cog '{cog_to_add.cog_name}' added.")
277
+ logger.info("Cog '%s' added.", cog_to_add.cog_name)
265
278
 
266
279
  def remove_cog(self, cog_name: str) -> Optional["Cog"]:
267
280
  cog_to_remove = self.cogs.pop(cog_name, None)
@@ -271,8 +284,11 @@ class CommandHandler:
271
284
 
272
285
  if hasattr(self.client, "_event_dispatcher"):
273
286
  for event_name, callback in cog_to_remove.get_listeners():
274
- print(
275
- f"Note: Listener '{callback.__name__}' for event '{event_name}' from cog '{cog_name}' needs manual unregistration logic in EventDispatcher."
287
+ logger.debug(
288
+ "Listener '%s' for event '%s' from cog '%s' needs manual unregistration logic in EventDispatcher.",
289
+ callback.__name__,
290
+ event_name,
291
+ cog_name,
276
292
  )
277
293
 
278
294
  if hasattr(cog_to_remove, "cog_unload") and inspect.iscoroutinefunction(
@@ -281,7 +297,7 @@ class CommandHandler:
281
297
  asyncio.create_task(cog_to_remove.cog_unload())
282
298
 
283
299
  cog_to_remove._eject()
284
- print(f"Cog '{cog_name}' removed.")
300
+ logger.info("Cog '%s' removed.", cog_name)
285
301
  return cog_to_remove
286
302
 
287
303
  async def get_prefix(self, message: "Message") -> Union[str, List[str], None]:
@@ -487,11 +503,11 @@ class CommandHandler:
487
503
  ctx.kwargs = parsed_kwargs
488
504
  await command.invoke(ctx, *parsed_args, **parsed_kwargs)
489
505
  except CommandError as e:
490
- print(f"Command error for '{command.name}': {e}")
506
+ logger.error("Command error for '%s': %s", command.name, e)
491
507
  if hasattr(self.client, "on_command_error"):
492
508
  await self.client.on_command_error(ctx, e)
493
509
  except Exception as e:
494
- print(f"Unexpected error invoking command '{command.name}': {e}")
510
+ logger.error("Unexpected error invoking command '%s': %s", command.name, e)
495
511
  exc = CommandInvokeError(e)
496
512
  if hasattr(self.client, "on_command_error"):
497
513
  await self.client.on_command_error(ctx, exc)