disagreement 0.1.0rc1__tar.gz → 0.1.0rc2__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.
- {disagreement-0.1.0rc1/disagreement.egg-info → disagreement-0.1.0rc2}/PKG-INFO +6 -5
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/README.md +1 -1
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/__init__.py +2 -2
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/client.py +41 -2
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/color.py +28 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/enums.py +5 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ext/app_commands/__init__.py +2 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ext/app_commands/commands.py +13 -99
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ext/app_commands/decorators.py +1 -1
- disagreement-0.1.0rc2/disagreement/ext/app_commands/hybrid.py +61 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ext/commands/core.py +8 -2
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ext/tasks.py +46 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/http.py +115 -13
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/interactions.py +17 -14
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/models.py +124 -6
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ui/modal.py +1 -1
- disagreement-0.1.0rc2/disagreement/utils.py +73 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2/disagreement.egg-info}/PKG-INFO +6 -5
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement.egg-info/SOURCES.txt +11 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement.egg-info/requires.txt +2 -2
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/caching.md +1 -1
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/converters.md +3 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/gateway.md +6 -0
- disagreement-0.1.0rc2/docs/message_history.md +16 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/task_loop.md +16 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/using_components.md +1 -1
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/webhooks.md +9 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/examples/basic_bot.py +5 -4
- disagreement-0.1.0rc2/examples/extension_management.py +45 -0
- disagreement-0.1.0rc2/examples/message_history.py +31 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/examples/modal_send.py +1 -0
- disagreement-0.1.0rc2/examples/sample_extension.py +16 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/examples/sharded_bot.py +3 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/pyproject.toml +5 -4
- disagreement-0.1.0rc2/tests/test_color_acceptance.py +29 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_command_checks.py +2 -2
- disagreement-0.1.0rc2/tests/test_gateway_intent.py +7 -0
- disagreement-0.1.0rc2/tests/test_http_rate_limit.py +70 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_http_reactions.py +51 -6
- disagreement-0.1.0rc2/tests/test_member.py +22 -0
- disagreement-0.1.0rc2/tests/test_message_pager.py +37 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_modals.py +2 -2
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_tasks_extension.py +24 -0
- disagreement-0.1.0rc2/tests/test_textchannel_history.py +24 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_webhooks.py +43 -0
- disagreement-0.1.0rc1/disagreement/utils.py +0 -10
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/LICENSE +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/MANIFEST.in +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/audio.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/cache.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/components.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/error_handler.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/errors.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/event_dispatcher.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ext/__init__.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ext/app_commands/context.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ext/app_commands/converters.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ext/app_commands/handler.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ext/commands/__init__.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ext/commands/cog.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ext/commands/converters.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ext/commands/decorators.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ext/commands/errors.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ext/commands/help.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ext/commands/view.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ext/loader.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/gateway.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/hybrid_context.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/i18n.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/logging_config.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/oauth.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/permissions.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/rate_limiter.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/shard_manager.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/typing.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ui/__init__.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ui/button.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ui/item.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ui/select.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/ui/view.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement/voice_client.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement.egg-info/dependency_links.txt +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/disagreement.egg-info/top_level.txt +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/commands.md +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/context_menus.md +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/events.md +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/extension_loader.md +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/i18n.md +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/oauth2.md +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/permissions.md +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/presence.md +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/reactions.md +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/sharding.md +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/slash_commands.md +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/typing_indicator.md +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/voice_client.md +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/docs/voice_features.md +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/examples/component_bot.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/examples/context_menus.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/examples/hybrid_bot.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/examples/modal_command.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/examples/task_loop.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/examples/voice_bot.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/setup.cfg +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_additional_converters.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_cache.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_channel_permissions.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_client_context_manager.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_color.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_components_factory.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_context.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_context_menus.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_converter_registration.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_converters.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_error_handler.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_errors.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_event_dispatcher.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_event_error_hook.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_extension_loader.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_gateway_backoff.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_help_command.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_hybrid_context.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_i18n.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_interaction.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_logging_config.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_modal_send.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_oauth.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_permissions.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_presence_and_typing.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_presence_update.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_rate_limiter.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_reactions.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_send_files.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_sharding.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_slash_contexts.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_textchannel_purge.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_typing_indicator.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_ui.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_utils.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_view_layout.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_voice_client.py +0 -0
- {disagreement-0.1.0rc1 → disagreement-0.1.0rc2}/tests/test_wait_for.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: disagreement
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.0rc2
|
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.
|
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.
|
29
|
+
Requires-Dist: hypothesis>=6.132.0; extra == "test"
|
29
30
|
Provides-Extra: dev
|
30
|
-
Requires-Dist: dotenv>=0.0
|
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.
|
57
|
+
Requires Python 3.10 or newer.
|
57
58
|
|
58
59
|
## Basic Usage
|
59
60
|
|
@@ -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.
|
17
|
+
__version__ = "0.1.0rc2"
|
18
18
|
|
19
19
|
from .client import Client, AutoShardedClient
|
20
20
|
from .models import Message, User, Reaction
|
@@ -30,7 +30,7 @@ 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
|
@@ -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
|
@@ -638,6 +640,23 @@ class Client:
|
|
638
640
|
# import traceback
|
639
641
|
# traceback.print_exception(type(error.original), error.original, error.original.__traceback__)
|
640
642
|
|
643
|
+
# --- Extension Management Methods ---
|
644
|
+
|
645
|
+
def load_extension(self, name: str) -> ModuleType:
|
646
|
+
"""Load an extension by name using :mod:`disagreement.ext.loader`."""
|
647
|
+
|
648
|
+
return ext_loader.load_extension(name)
|
649
|
+
|
650
|
+
def unload_extension(self, name: str) -> None:
|
651
|
+
"""Unload a previously loaded extension."""
|
652
|
+
|
653
|
+
ext_loader.unload_extension(name)
|
654
|
+
|
655
|
+
def reload_extension(self, name: str) -> ModuleType:
|
656
|
+
"""Reload an extension by name."""
|
657
|
+
|
658
|
+
return ext_loader.reload_extension(name)
|
659
|
+
|
641
660
|
# --- Model Parsing and Fetching ---
|
642
661
|
|
643
662
|
def parse_user(self, data: Dict[str, Any]) -> "User":
|
@@ -920,12 +939,12 @@ class Client:
|
|
920
939
|
await view._start(self)
|
921
940
|
components_payload = view.to_components_payload()
|
922
941
|
elif components:
|
923
|
-
from .models import
|
942
|
+
from .models import Component as ComponentModel
|
924
943
|
|
925
944
|
components_payload = [
|
926
945
|
comp.to_dict()
|
927
946
|
for comp in components
|
928
|
-
if isinstance(comp,
|
947
|
+
if isinstance(comp, ComponentModel)
|
929
948
|
]
|
930
949
|
|
931
950
|
message_data = await self._http.send_message(
|
@@ -1016,6 +1035,26 @@ class Client:
|
|
1016
1035
|
self._voice_clients[guild_id] = voice
|
1017
1036
|
return voice
|
1018
1037
|
|
1038
|
+
async def add_reaction(self, channel_id: str, message_id: str, emoji: str) -> None:
|
1039
|
+
"""|coro| Add a reaction to a message."""
|
1040
|
+
|
1041
|
+
await self.create_reaction(channel_id, message_id, emoji)
|
1042
|
+
|
1043
|
+
async def remove_reaction(
|
1044
|
+
self, channel_id: str, message_id: str, emoji: str
|
1045
|
+
) -> None:
|
1046
|
+
"""|coro| Remove the bot's reaction from a message."""
|
1047
|
+
|
1048
|
+
await self.delete_reaction(channel_id, message_id, emoji)
|
1049
|
+
|
1050
|
+
async def clear_reactions(self, channel_id: str, message_id: str) -> None:
|
1051
|
+
"""|coro| Remove all reactions from a message."""
|
1052
|
+
|
1053
|
+
if self._closed:
|
1054
|
+
raise DisagreementException("Client is closed.")
|
1055
|
+
|
1056
|
+
await self._http.clear_reactions(channel_id, message_id)
|
1057
|
+
|
1019
1058
|
async def create_reaction(
|
1020
1059
|
self, channel_id: str, message_id: str, emoji: str
|
1021
1060
|
) -> 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)."""
|
@@ -1,58 +1,25 @@
|
|
1
1
|
# disagreement/ext/app_commands/commands.py
|
2
2
|
|
3
3
|
import inspect
|
4
|
-
from typing import Callable,
|
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
|
-
|
10
|
-
|
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
|
39
|
-
PrefixCommand = Any
|
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:
|
@@ -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,5 +1,7 @@
|
|
1
1
|
# disagreement/ext/commands/core.py
|
2
2
|
|
3
|
+
from __future__ import annotations
|
4
|
+
|
3
5
|
import asyncio
|
4
6
|
import inspect
|
5
7
|
from typing import (
|
@@ -27,10 +29,10 @@ from .errors import (
|
|
27
29
|
CommandInvokeError,
|
28
30
|
)
|
29
31
|
from .converters import run_converters, DEFAULT_CONVERTERS, Converter
|
30
|
-
from .cog import Cog
|
31
32
|
from disagreement.typing import Typing
|
32
33
|
|
33
34
|
if TYPE_CHECKING:
|
35
|
+
from .cog import Cog
|
34
36
|
from disagreement.client import Client
|
35
37
|
from disagreement.models import Message, User
|
36
38
|
|
@@ -86,6 +88,9 @@ class Command:
|
|
86
88
|
await self.callback(ctx, *args, **kwargs)
|
87
89
|
|
88
90
|
|
91
|
+
PrefixCommand = Command # Alias for clarity in hybrid commands
|
92
|
+
|
93
|
+
|
89
94
|
class CommandContext:
|
90
95
|
"""
|
91
96
|
Represents the context in which a command is being invoked.
|
@@ -123,7 +128,7 @@ class CommandContext:
|
|
123
128
|
|
124
129
|
async def reply(
|
125
130
|
self,
|
126
|
-
content: str,
|
131
|
+
content: Optional[str] = None,
|
127
132
|
*,
|
128
133
|
mention_author: Optional[bool] = None,
|
129
134
|
**kwargs: Any,
|
@@ -235,6 +240,7 @@ class CommandHandler:
|
|
235
240
|
return self.commands.get(name.lower())
|
236
241
|
|
237
242
|
def add_cog(self, cog_to_add: "Cog") -> None:
|
243
|
+
from .cog import Cog
|
238
244
|
if not isinstance(cog_to_add, Cog):
|
239
245
|
raise TypeError("Argument must be a subclass of Cog.")
|
240
246
|
|
@@ -18,6 +18,8 @@ class Task:
|
|
18
18
|
delta: Optional[datetime.timedelta] = None,
|
19
19
|
time_of_day: Optional[datetime.time] = None,
|
20
20
|
on_error: Optional[Callable[[Exception], Awaitable[None]]] = None,
|
21
|
+
before_loop: Optional[Callable[[], Awaitable[None] | None]] = None,
|
22
|
+
after_loop: Optional[Callable[[], Awaitable[None] | None]] = None,
|
21
23
|
) -> None:
|
22
24
|
self._coro = coro
|
23
25
|
self._task: Optional[asyncio.Task[None]] = None
|
@@ -36,6 +38,8 @@ class Task:
|
|
36
38
|
self._seconds = float(interval_seconds)
|
37
39
|
self._time_of_day = time_of_day
|
38
40
|
self._on_error = on_error
|
41
|
+
self._before_loop = before_loop
|
42
|
+
self._after_loop = after_loop
|
39
43
|
|
40
44
|
def _seconds_until_time(self) -> float:
|
41
45
|
assert self._time_of_day is not None
|
@@ -47,6 +51,9 @@ class Task:
|
|
47
51
|
|
48
52
|
async def _run(self, *args: Any, **kwargs: Any) -> None:
|
49
53
|
try:
|
54
|
+
if self._before_loop is not None:
|
55
|
+
await _maybe_call_no_args(self._before_loop)
|
56
|
+
|
50
57
|
first = True
|
51
58
|
while True:
|
52
59
|
if self._time_of_day is not None:
|
@@ -65,6 +72,9 @@ class Task:
|
|
65
72
|
first = False
|
66
73
|
except asyncio.CancelledError:
|
67
74
|
pass
|
75
|
+
finally:
|
76
|
+
if self._after_loop is not None:
|
77
|
+
await _maybe_call_no_args(self._after_loop)
|
68
78
|
|
69
79
|
def start(self, *args: Any, **kwargs: Any) -> asyncio.Task[None]:
|
70
80
|
if self._task is None or self._task.done():
|
@@ -89,6 +99,12 @@ async def _maybe_call(
|
|
89
99
|
await result
|
90
100
|
|
91
101
|
|
102
|
+
async def _maybe_call_no_args(func: Callable[[], Awaitable[None] | None]) -> None:
|
103
|
+
result = func()
|
104
|
+
if asyncio.iscoroutine(result):
|
105
|
+
await result
|
106
|
+
|
107
|
+
|
92
108
|
class _Loop:
|
93
109
|
def __init__(
|
94
110
|
self,
|
@@ -110,6 +126,8 @@ class _Loop:
|
|
110
126
|
self.on_error = on_error
|
111
127
|
self._task: Optional[Task] = None
|
112
128
|
self._owner: Any = None
|
129
|
+
self._before_loop: Optional[Callable[..., Awaitable[Any]]] = None
|
130
|
+
self._after_loop: Optional[Callable[..., Awaitable[Any]]] = None
|
113
131
|
|
114
132
|
def __get__(self, obj: Any, objtype: Any) -> "_BoundLoop":
|
115
133
|
return _BoundLoop(self, obj)
|
@@ -119,7 +137,33 @@ class _Loop:
|
|
119
137
|
return self.func(*args, **kwargs)
|
120
138
|
return self.func(self._owner, *args, **kwargs)
|
121
139
|
|
140
|
+
def before_loop(
|
141
|
+
self, func: Callable[..., Awaitable[Any]]
|
142
|
+
) -> Callable[..., Awaitable[Any]]:
|
143
|
+
self._before_loop = func
|
144
|
+
return func
|
145
|
+
|
146
|
+
def after_loop(
|
147
|
+
self, func: Callable[..., Awaitable[Any]]
|
148
|
+
) -> Callable[..., Awaitable[Any]]:
|
149
|
+
self._after_loop = func
|
150
|
+
return func
|
151
|
+
|
122
152
|
def start(self, *args: Any, **kwargs: Any) -> asyncio.Task[None]:
|
153
|
+
def call_before() -> Awaitable[None] | None:
|
154
|
+
if self._before_loop is None:
|
155
|
+
return None
|
156
|
+
if self._owner is not None:
|
157
|
+
return self._before_loop(self._owner)
|
158
|
+
return self._before_loop()
|
159
|
+
|
160
|
+
def call_after() -> Awaitable[None] | None:
|
161
|
+
if self._after_loop is None:
|
162
|
+
return None
|
163
|
+
if self._owner is not None:
|
164
|
+
return self._after_loop(self._owner)
|
165
|
+
return self._after_loop()
|
166
|
+
|
123
167
|
self._task = Task(
|
124
168
|
self._coro,
|
125
169
|
seconds=self.seconds,
|
@@ -128,6 +172,8 @@ class _Loop:
|
|
128
172
|
delta=self.delta,
|
129
173
|
time_of_day=self.time_of_day,
|
130
174
|
on_error=self.on_error,
|
175
|
+
before_loop=call_before,
|
176
|
+
after_loop=call_after,
|
131
177
|
)
|
132
178
|
return self._task.start(*args, **kwargs)
|
133
179
|
|