disagreement 0.1.0rc1__tar.gz → 0.1.0rc3__tar.gz

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 (144) hide show
  1. disagreement-0.1.0rc3/MANIFEST.in +4 -0
  2. {disagreement-0.1.0rc1/disagreement.egg-info → disagreement-0.1.0rc3}/PKG-INFO +6 -5
  3. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/README.md +1 -1
  4. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/__init__.py +9 -5
  5. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/client.py +41 -6
  6. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/color.py +28 -0
  7. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/enums.py +5 -0
  8. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ext/app_commands/__init__.py +2 -0
  9. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ext/app_commands/commands.py +13 -99
  10. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ext/app_commands/decorators.py +1 -1
  11. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ext/app_commands/handler.py +25 -12
  12. disagreement-0.1.0rc3/disagreement/ext/app_commands/hybrid.py +61 -0
  13. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ext/commands/cog.py +15 -6
  14. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ext/commands/core.py +28 -12
  15. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ext/tasks.py +46 -0
  16. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/gateway.py +102 -63
  17. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/http.py +134 -17
  18. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/interactions.py +17 -14
  19. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/models.py +124 -9
  20. disagreement-0.1.0rc3/disagreement/py.typed +0 -0
  21. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ui/modal.py +1 -1
  22. disagreement-0.1.0rc3/disagreement/utils.py +73 -0
  23. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3/disagreement.egg-info}/PKG-INFO +6 -5
  24. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement.egg-info/SOURCES.txt +12 -0
  25. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement.egg-info/requires.txt +2 -2
  26. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/caching.md +1 -1
  27. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/converters.md +3 -0
  28. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/gateway.md +6 -0
  29. disagreement-0.1.0rc3/docs/message_history.md +16 -0
  30. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/task_loop.md +16 -0
  31. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/using_components.md +1 -1
  32. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/webhooks.md +9 -0
  33. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/examples/basic_bot.py +5 -4
  34. disagreement-0.1.0rc3/examples/extension_management.py +45 -0
  35. disagreement-0.1.0rc3/examples/message_history.py +31 -0
  36. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/examples/modal_send.py +1 -0
  37. disagreement-0.1.0rc3/examples/sample_extension.py +16 -0
  38. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/examples/sharded_bot.py +3 -0
  39. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/pyproject.toml +5 -4
  40. disagreement-0.1.0rc3/tests/test_color_acceptance.py +29 -0
  41. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_command_checks.py +2 -2
  42. disagreement-0.1.0rc3/tests/test_gateway_intent.py +7 -0
  43. disagreement-0.1.0rc3/tests/test_http_rate_limit.py +70 -0
  44. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_http_reactions.py +51 -6
  45. disagreement-0.1.0rc3/tests/test_member.py +22 -0
  46. disagreement-0.1.0rc3/tests/test_message_pager.py +37 -0
  47. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_modals.py +2 -2
  48. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_tasks_extension.py +24 -0
  49. disagreement-0.1.0rc3/tests/test_textchannel_history.py +24 -0
  50. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_webhooks.py +43 -0
  51. disagreement-0.1.0rc1/MANIFEST.in +0 -3
  52. disagreement-0.1.0rc1/disagreement/utils.py +0 -10
  53. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/LICENSE +0 -0
  54. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/audio.py +0 -0
  55. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/cache.py +0 -0
  56. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/components.py +0 -0
  57. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/error_handler.py +0 -0
  58. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/errors.py +0 -0
  59. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/event_dispatcher.py +0 -0
  60. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ext/__init__.py +0 -0
  61. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ext/app_commands/context.py +0 -0
  62. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ext/app_commands/converters.py +0 -0
  63. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ext/commands/__init__.py +0 -0
  64. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ext/commands/converters.py +0 -0
  65. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ext/commands/decorators.py +0 -0
  66. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ext/commands/errors.py +0 -0
  67. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ext/commands/help.py +0 -0
  68. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ext/commands/view.py +0 -0
  69. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ext/loader.py +0 -0
  70. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/hybrid_context.py +0 -0
  71. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/i18n.py +0 -0
  72. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/logging_config.py +0 -0
  73. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/oauth.py +0 -0
  74. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/permissions.py +0 -0
  75. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/rate_limiter.py +0 -0
  76. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/shard_manager.py +0 -0
  77. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/typing.py +0 -0
  78. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ui/__init__.py +0 -0
  79. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ui/button.py +0 -0
  80. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ui/item.py +0 -0
  81. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ui/select.py +0 -0
  82. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/ui/view.py +0 -0
  83. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement/voice_client.py +0 -0
  84. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement.egg-info/dependency_links.txt +0 -0
  85. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/disagreement.egg-info/top_level.txt +0 -0
  86. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/commands.md +0 -0
  87. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/context_menus.md +0 -0
  88. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/events.md +0 -0
  89. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/extension_loader.md +0 -0
  90. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/i18n.md +0 -0
  91. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/oauth2.md +0 -0
  92. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/permissions.md +0 -0
  93. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/presence.md +0 -0
  94. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/reactions.md +0 -0
  95. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/sharding.md +0 -0
  96. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/slash_commands.md +0 -0
  97. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/typing_indicator.md +0 -0
  98. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/voice_client.md +0 -0
  99. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/docs/voice_features.md +0 -0
  100. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/examples/component_bot.py +0 -0
  101. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/examples/context_menus.py +0 -0
  102. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/examples/hybrid_bot.py +0 -0
  103. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/examples/modal_command.py +0 -0
  104. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/examples/task_loop.py +0 -0
  105. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/examples/voice_bot.py +0 -0
  106. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/setup.cfg +0 -0
  107. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_additional_converters.py +0 -0
  108. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_cache.py +0 -0
  109. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_channel_permissions.py +0 -0
  110. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_client_context_manager.py +0 -0
  111. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_color.py +0 -0
  112. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_components_factory.py +0 -0
  113. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_context.py +0 -0
  114. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_context_menus.py +0 -0
  115. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_converter_registration.py +0 -0
  116. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_converters.py +0 -0
  117. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_error_handler.py +0 -0
  118. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_errors.py +0 -0
  119. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_event_dispatcher.py +0 -0
  120. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_event_error_hook.py +0 -0
  121. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_extension_loader.py +0 -0
  122. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_gateway_backoff.py +0 -0
  123. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_help_command.py +0 -0
  124. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_hybrid_context.py +0 -0
  125. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_i18n.py +0 -0
  126. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_interaction.py +0 -0
  127. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_logging_config.py +0 -0
  128. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_modal_send.py +0 -0
  129. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_oauth.py +0 -0
  130. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_permissions.py +0 -0
  131. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_presence_and_typing.py +0 -0
  132. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_presence_update.py +0 -0
  133. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_rate_limiter.py +0 -0
  134. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_reactions.py +0 -0
  135. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_send_files.py +0 -0
  136. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_sharding.py +0 -0
  137. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_slash_contexts.py +0 -0
  138. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_textchannel_purge.py +0 -0
  139. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_typing_indicator.py +0 -0
  140. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_ui.py +0 -0
  141. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_utils.py +0 -0
  142. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_view_layout.py +0 -0
  143. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_voice_client.py +0 -0
  144. {disagreement-0.1.0rc1 → disagreement-0.1.0rc3}/tests/test_wait_for.py +0 -0
@@ -0,0 +1,4 @@
1
+ graft docs
2
+ graft examples
3
+ include LICENSE
4
+ include disagreement/py.typed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: disagreement
3
- Version: 0.1.0rc1
3
+ Version: 0.1.0rc3
4
4
  Summary: A Python library for the Discord API.
5
5
  Author-email: Slipstream <me@slipstreamm.dev>
6
6
  License: BSD 3-Clause
@@ -12,22 +12,23 @@ Classifier: Intended Audience :: Developers
12
12
  Classifier: License :: OSI Approved :: BSD License
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
15
16
  Classifier: Programming Language :: Python :: 3.11
16
17
  Classifier: Programming Language :: Python :: 3.12
17
18
  Classifier: Programming Language :: Python :: 3.13
18
19
  Classifier: Topic :: Software Development :: Libraries
19
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
21
  Classifier: Topic :: Internet
21
- Requires-Python: >=3.11
22
+ Requires-Python: >=3.10
22
23
  Description-Content-Type: text/markdown
23
24
  License-File: LICENSE
24
25
  Requires-Dist: aiohttp<4.0.0,>=3.9.0
25
26
  Provides-Extra: test
26
27
  Requires-Dist: pytest>=8.0.0; extra == "test"
27
28
  Requires-Dist: pytest-asyncio>=1.0.0; extra == "test"
28
- Requires-Dist: hypothesis>=6.89.0; extra == "test"
29
+ Requires-Dist: hypothesis>=6.132.0; extra == "test"
29
30
  Provides-Extra: dev
30
- Requires-Dist: dotenv>=0.0.5; extra == "dev"
31
+ Requires-Dist: python-dotenv>=1.0.0; extra == "dev"
31
32
  Dynamic: license-file
32
33
 
33
34
  # Disagreement
@@ -53,7 +54,7 @@ pip install disagreement
53
54
  pip install -e .
54
55
  ```
55
56
 
56
- Requires Python 3.11 or newer.
57
+ Requires Python 3.10 or newer.
57
58
 
58
59
  ## Basic Usage
59
60
 
@@ -21,7 +21,7 @@ pip install disagreement
21
21
  pip install -e .
22
22
  ```
23
23
 
24
- Requires Python 3.11 or newer.
24
+ Requires Python 3.10 or newer.
25
25
 
26
26
  ## Basic Usage
27
27
 
@@ -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)
@@ -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:
@@ -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")
@@ -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