Compare commits
2 Commits
35414c3085
...
6d5b92ad69
Author | SHA1 | Date | |
---|---|---|---|
6d5b92ad69 | |||
60a183742a |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: '3.13'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
@ -21,7 +21,7 @@ pip install disagreement
|
|||||||
pip install -e .
|
pip install -e .
|
||||||
```
|
```
|
||||||
|
|
||||||
Requires Python 3.11 or newer.
|
Requires Python 3.10 or newer.
|
||||||
|
|
||||||
## Basic Usage
|
## Basic Usage
|
||||||
|
|
||||||
|
@ -920,12 +920,12 @@ class Client:
|
|||||||
await view._start(self)
|
await view._start(self)
|
||||||
components_payload = view.to_components_payload()
|
components_payload = view.to_components_payload()
|
||||||
elif components:
|
elif components:
|
||||||
from .models import ActionRow as ActionRowModel
|
from .models import Component as ComponentModel
|
||||||
|
|
||||||
components_payload = [
|
components_payload = [
|
||||||
comp.to_dict()
|
comp.to_dict()
|
||||||
for comp in components
|
for comp in components
|
||||||
if isinstance(comp, ActionRowModel)
|
if isinstance(comp, ComponentModel)
|
||||||
]
|
]
|
||||||
|
|
||||||
message_data = await self._http.send_message(
|
message_data = await self._http.send_message(
|
||||||
|
@ -44,3 +44,5 @@ __all__ = [
|
|||||||
"OptionMetadata",
|
"OptionMetadata",
|
||||||
"AppCommandContext", # To be defined
|
"AppCommandContext", # To be defined
|
||||||
]
|
]
|
||||||
|
|
||||||
|
from .hybrid import *
|
||||||
|
@ -1,58 +1,25 @@
|
|||||||
# disagreement/ext/app_commands/commands.py
|
# disagreement/ext/app_commands/commands.py
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
from typing import Callable, Optional, List, Dict, Any, Union, TYPE_CHECKING
|
from typing import Any, Callable, Dict, List, Optional, Union, TYPE_CHECKING
|
||||||
|
|
||||||
|
from disagreement.enums import (
|
||||||
|
ApplicationCommandType,
|
||||||
|
ApplicationCommandOptionType,
|
||||||
|
IntegrationType,
|
||||||
|
InteractionContextType,
|
||||||
|
)
|
||||||
|
from disagreement.interactions import ApplicationCommandOption, Snowflake
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from disagreement.ext.commands.core import (
|
from disagreement.ext.commands.core import Command as PrefixCommand
|
||||||
Command as PrefixCommand,
|
from disagreement.ext.commands.cog import Cog
|
||||||
) # Alias to avoid name clash
|
|
||||||
from disagreement.interactions import ApplicationCommandOption, Snowflake
|
|
||||||
from disagreement.enums import (
|
|
||||||
ApplicationCommandType,
|
|
||||||
IntegrationType,
|
|
||||||
InteractionContextType,
|
|
||||||
ApplicationCommandOptionType, # Added
|
|
||||||
)
|
|
||||||
from disagreement.ext.commands.cog import Cog # Corrected import path
|
|
||||||
|
|
||||||
# Placeholder for Cog if not using the existing one or if it needs adaptation
|
|
||||||
if not TYPE_CHECKING:
|
if not TYPE_CHECKING:
|
||||||
# This dynamic Cog = Any might not be ideal if Cog is used in runtime type checks.
|
|
||||||
# However, for type hinting purposes when TYPE_CHECKING is false, it avoids import.
|
|
||||||
# If Cog is needed at runtime by this module (it is, for AppCommand.cog type hint),
|
|
||||||
# it should be imported directly.
|
|
||||||
# For now, the TYPE_CHECKING block handles the proper import for static analysis.
|
|
||||||
# Let's ensure Cog is available at runtime if AppCommand.cog is accessed.
|
|
||||||
# A simple way is to import it outside TYPE_CHECKING too, if it doesn't cause circularity.
|
|
||||||
# Given its usage, a forward reference string 'Cog' might be better in AppCommand.cog type hint.
|
|
||||||
# Let's try importing it directly for runtime, assuming no circularity with this specific module.
|
|
||||||
try:
|
|
||||||
from disagreement.ext.commands.cog import Cog
|
|
||||||
except ImportError:
|
|
||||||
Cog = Any # Fallback if direct import fails (e.g. during partial builds/tests)
|
|
||||||
# Import PrefixCommand at runtime for HybridCommand
|
|
||||||
try:
|
try:
|
||||||
from disagreement.ext.commands.core import Command as PrefixCommand
|
from disagreement.ext.commands.core import Command as PrefixCommand
|
||||||
except Exception: # pragma: no cover - safeguard against unusual import issues
|
except ImportError:
|
||||||
PrefixCommand = Any # type: ignore
|
PrefixCommand = Any
|
||||||
# Import enums used at runtime
|
|
||||||
try:
|
|
||||||
from disagreement.enums import (
|
|
||||||
ApplicationCommandType,
|
|
||||||
IntegrationType,
|
|
||||||
InteractionContextType,
|
|
||||||
ApplicationCommandOptionType,
|
|
||||||
)
|
|
||||||
from disagreement.interactions import ApplicationCommandOption, Snowflake
|
|
||||||
except Exception: # pragma: no cover
|
|
||||||
ApplicationCommandType = ApplicationCommandOptionType = IntegrationType = (
|
|
||||||
InteractionContextType
|
|
||||||
) = Any # type: ignore
|
|
||||||
ApplicationCommandOption = Snowflake = Any # type: ignore
|
|
||||||
else: # When TYPE_CHECKING is true, Cog and PrefixCommand are already imported above.
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class AppCommand:
|
class AppCommand:
|
||||||
@ -235,59 +202,6 @@ class MessageCommand(AppCommand):
|
|||||||
super().__init__(callback, type=ApplicationCommandType.MESSAGE, **kwargs)
|
super().__init__(callback, type=ApplicationCommandType.MESSAGE, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class HybridCommand(SlashCommand, PrefixCommand): # Inherit from both
|
|
||||||
"""
|
|
||||||
Represents a command that can be invoked as both a slash command
|
|
||||||
and a traditional prefix-based command.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, callback: Callable[..., Any], **kwargs: Any):
|
|
||||||
# Initialize SlashCommand part (which calls AppCommand.__init__)
|
|
||||||
# We need to ensure 'type' is correctly passed for AppCommand
|
|
||||||
# kwargs for SlashCommand: name, description, guild_ids, default_member_permissions, nsfw, parent, cog, etc.
|
|
||||||
# kwargs for PrefixCommand: name, aliases, brief, description, cog
|
|
||||||
|
|
||||||
# Pop prefix-specific args before passing to SlashCommand constructor
|
|
||||||
prefix_aliases = kwargs.pop("aliases", [])
|
|
||||||
prefix_brief = kwargs.pop("brief", None)
|
|
||||||
# Description is used by both, AppCommand's constructor will handle it.
|
|
||||||
# Name is used by both. Cog is used by both.
|
|
||||||
|
|
||||||
# Call SlashCommand's __init__
|
|
||||||
# This will set up name, description, callback, type=CHAT_INPUT, options, etc.
|
|
||||||
super().__init__(callback, **kwargs) # This is SlashCommand.__init__
|
|
||||||
|
|
||||||
# Now, explicitly initialize the PrefixCommand parts that SlashCommand didn't cover
|
|
||||||
# or that need specific values for the prefix version.
|
|
||||||
# PrefixCommand.__init__(self, callback, name=self.name, aliases=prefix_aliases, brief=prefix_brief, description=self.description, cog=self.cog)
|
|
||||||
# However, PrefixCommand.__init__ also sets self.params, which AppCommand already did.
|
|
||||||
# We need to be careful not to re-initialize things unnecessarily or incorrectly.
|
|
||||||
# Let's manually set the distinct attributes for the PrefixCommand aspect.
|
|
||||||
|
|
||||||
# Attributes from PrefixCommand:
|
|
||||||
# self.callback is already set by AppCommand
|
|
||||||
# self.name is already set by AppCommand
|
|
||||||
self.aliases: List[str] = (
|
|
||||||
prefix_aliases # This was specific to HybridCommand before, now aligns with PrefixCommand
|
|
||||||
)
|
|
||||||
self.brief: Optional[str] = prefix_brief
|
|
||||||
# self.description is already set by AppCommand (SlashCommand ensures it exists)
|
|
||||||
# self.cog is already set by AppCommand
|
|
||||||
# self.params is already set by AppCommand
|
|
||||||
|
|
||||||
# Ensure the MRO is handled correctly. Python's MRO (C3 linearization)
|
|
||||||
# should call SlashCommand's __init__ then AppCommand's __init__.
|
|
||||||
# PrefixCommand.__init__ won't be called automatically unless we explicitly call it.
|
|
||||||
# By setting attributes directly, we avoid potential issues with multiple __init__ calls
|
|
||||||
# if their logic overlaps too much (e.g., both trying to set self.params).
|
|
||||||
|
|
||||||
# We might need to override invoke if the context or argument passing differs significantly
|
|
||||||
# between app command invocation and prefix command invocation.
|
|
||||||
# For now, SlashCommand.invoke and PrefixCommand.invoke are separate.
|
|
||||||
# The correct one will be called depending on how the command is dispatched.
|
|
||||||
# The AppCommandHandler will use AppCommand.invoke (via SlashCommand).
|
|
||||||
# The prefix CommandHandler will use PrefixCommand.invoke.
|
|
||||||
# This seems acceptable.
|
|
||||||
|
|
||||||
|
|
||||||
class AppCommandGroup:
|
class AppCommandGroup:
|
||||||
|
@ -26,8 +26,8 @@ from .commands import (
|
|||||||
MessageCommand,
|
MessageCommand,
|
||||||
AppCommand,
|
AppCommand,
|
||||||
AppCommandGroup,
|
AppCommandGroup,
|
||||||
HybridCommand,
|
|
||||||
)
|
)
|
||||||
|
from .hybrid import HybridCommand
|
||||||
from disagreement.interactions import (
|
from disagreement.interactions import (
|
||||||
ApplicationCommandOption,
|
ApplicationCommandOption,
|
||||||
ApplicationCommandOptionChoice,
|
ApplicationCommandOptionChoice,
|
||||||
|
61
disagreement/ext/app_commands/hybrid.py
Normal file
61
disagreement/ext/app_commands/hybrid.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# disagreement/ext/app_commands/hybrid.py
|
||||||
|
|
||||||
|
from typing import Any, Callable, List, Optional
|
||||||
|
|
||||||
|
from .commands import SlashCommand
|
||||||
|
from disagreement.ext.commands.core import PrefixCommand
|
||||||
|
|
||||||
|
|
||||||
|
class HybridCommand(SlashCommand, PrefixCommand): # Inherit from both
|
||||||
|
"""
|
||||||
|
Represents a command that can be invoked as both a slash command
|
||||||
|
and a traditional prefix-based command.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, callback: Callable[..., Any], **kwargs: Any):
|
||||||
|
# Initialize SlashCommand part (which calls AppCommand.__init__)
|
||||||
|
# We need to ensure 'type' is correctly passed for AppCommand
|
||||||
|
# kwargs for SlashCommand: name, description, guild_ids, default_member_permissions, nsfw, parent, cog, etc.
|
||||||
|
# kwargs for PrefixCommand: name, aliases, brief, description, cog
|
||||||
|
|
||||||
|
# Pop prefix-specific args before passing to SlashCommand constructor
|
||||||
|
prefix_aliases = kwargs.pop("aliases", [])
|
||||||
|
prefix_brief = kwargs.pop("brief", None)
|
||||||
|
# Description is used by both, AppCommand's constructor will handle it.
|
||||||
|
# Name is used by both. Cog is used by both.
|
||||||
|
|
||||||
|
# Call SlashCommand's __init__
|
||||||
|
# This will set up name, description, callback, type=CHAT_INPUT, options, etc.
|
||||||
|
super().__init__(callback, **kwargs) # This is SlashCommand.__init__
|
||||||
|
|
||||||
|
# Now, explicitly initialize the PrefixCommand parts that SlashCommand didn't cover
|
||||||
|
# or that need specific values for the prefix version.
|
||||||
|
# PrefixCommand.__init__(self, callback, name=self.name, aliases=prefix_aliases, brief=prefix_brief, description=self.description, cog=self.cog)
|
||||||
|
# However, PrefixCommand.__init__ also sets self.params, which AppCommand already did.
|
||||||
|
# We need to be careful not to re-initialize things unnecessarily or incorrectly.
|
||||||
|
# Let's manually set the distinct attributes for the PrefixCommand aspect.
|
||||||
|
|
||||||
|
# Attributes from PrefixCommand:
|
||||||
|
# self.callback is already set by AppCommand
|
||||||
|
# self.name is already set by AppCommand
|
||||||
|
self.aliases: List[str] = (
|
||||||
|
prefix_aliases # This was specific to HybridCommand before, now aligns with PrefixCommand
|
||||||
|
)
|
||||||
|
self.brief: Optional[str] = prefix_brief
|
||||||
|
# self.description is already set by AppCommand (SlashCommand ensures it exists)
|
||||||
|
# self.cog is already set by AppCommand
|
||||||
|
# self.params is already set by AppCommand
|
||||||
|
|
||||||
|
# Ensure the MRO is handled correctly. Python's MRO (C3 linearization)
|
||||||
|
# should call SlashCommand's __init__ then AppCommand's __init__.
|
||||||
|
# PrefixCommand.__init__ won't be called automatically unless we explicitly call it.
|
||||||
|
# By setting attributes directly, we avoid potential issues with multiple __init__ calls
|
||||||
|
# if their logic overlaps too much (e.g., both trying to set self.params).
|
||||||
|
|
||||||
|
# We might need to override invoke if the context or argument passing differs significantly
|
||||||
|
# between app command invocation and prefix command invocation.
|
||||||
|
# For now, SlashCommand.invoke and PrefixCommand.invoke are separate.
|
||||||
|
# The correct one will be called depending on how the command is dispatched.
|
||||||
|
# The AppCommandHandler will use AppCommand.invoke (via SlashCommand).
|
||||||
|
# The prefix CommandHandler will use PrefixCommand.invoke.
|
||||||
|
# This seems acceptable.
|
@ -1,5 +1,7 @@
|
|||||||
# disagreement/ext/commands/core.py
|
# disagreement/ext/commands/core.py
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import inspect
|
import inspect
|
||||||
from typing import (
|
from typing import (
|
||||||
@ -27,10 +29,10 @@ from .errors import (
|
|||||||
CommandInvokeError,
|
CommandInvokeError,
|
||||||
)
|
)
|
||||||
from .converters import run_converters, DEFAULT_CONVERTERS, Converter
|
from .converters import run_converters, DEFAULT_CONVERTERS, Converter
|
||||||
from .cog import Cog
|
|
||||||
from disagreement.typing import Typing
|
from disagreement.typing import Typing
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from .cog import Cog
|
||||||
from disagreement.client import Client
|
from disagreement.client import Client
|
||||||
from disagreement.models import Message, User
|
from disagreement.models import Message, User
|
||||||
|
|
||||||
@ -86,6 +88,9 @@ class Command:
|
|||||||
await self.callback(ctx, *args, **kwargs)
|
await self.callback(ctx, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
PrefixCommand = Command # Alias for clarity in hybrid commands
|
||||||
|
|
||||||
|
|
||||||
class CommandContext:
|
class CommandContext:
|
||||||
"""
|
"""
|
||||||
Represents the context in which a command is being invoked.
|
Represents the context in which a command is being invoked.
|
||||||
@ -123,7 +128,7 @@ class CommandContext:
|
|||||||
|
|
||||||
async def reply(
|
async def reply(
|
||||||
self,
|
self,
|
||||||
content: str,
|
content: Optional[str] = None,
|
||||||
*,
|
*,
|
||||||
mention_author: Optional[bool] = None,
|
mention_author: Optional[bool] = None,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
@ -235,6 +240,7 @@ class CommandHandler:
|
|||||||
return self.commands.get(name.lower())
|
return self.commands.get(name.lower())
|
||||||
|
|
||||||
def add_cog(self, cog_to_add: "Cog") -> None:
|
def add_cog(self, cog_to_add: "Cog") -> None:
|
||||||
|
from .cog import Cog
|
||||||
if not isinstance(cog_to_add, Cog):
|
if not isinstance(cog_to_add, Cog):
|
||||||
raise TypeError("Argument must be a subclass of Cog.")
|
raise TypeError("Argument must be a subclass of Cog.")
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ name = "disagreement"
|
|||||||
version = "0.1.0rc1"
|
version = "0.1.0rc1"
|
||||||
description = "A Python library for the Discord API."
|
description = "A Python library for the Discord API."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.10"
|
||||||
license = {text = "BSD 3-Clause"}
|
license = {text = "BSD 3-Clause"}
|
||||||
authors = [
|
authors = [
|
||||||
{name = "Slipstream", email = "me@slipstreamm.dev"}
|
{name = "Slipstream", email = "me@slipstreamm.dev"}
|
||||||
@ -15,6 +15,7 @@ classifiers = [
|
|||||||
"License :: OSI Approved :: BSD License",
|
"License :: OSI Approved :: BSD License",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
"Programming Language :: Python :: 3.12",
|
"Programming Language :: Python :: 3.12",
|
||||||
"Programming Language :: Python :: 3.13",
|
"Programming Language :: Python :: 3.13",
|
||||||
@ -31,10 +32,10 @@ dependencies = [
|
|||||||
test = [
|
test = [
|
||||||
"pytest>=8.0.0",
|
"pytest>=8.0.0",
|
||||||
"pytest-asyncio>=1.0.0",
|
"pytest-asyncio>=1.0.0",
|
||||||
"hypothesis>=6.89.0",
|
"hypothesis>=6.132.0",
|
||||||
]
|
]
|
||||||
dev = [
|
dev = [
|
||||||
"dotenv>=0.0.5",
|
"python-dotenv>=1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
|
@ -33,7 +33,7 @@ async def test_check_decorator_blocks(message):
|
|||||||
async def test_cooldown_per_user(message):
|
async def test_cooldown_per_user(message):
|
||||||
uses = []
|
uses = []
|
||||||
|
|
||||||
@cooldown(1, 0.05)
|
@cooldown(1, 0.1)
|
||||||
async def cb(ctx):
|
async def cb(ctx):
|
||||||
uses.append(1)
|
uses.append(1)
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ async def test_cooldown_per_user(message):
|
|||||||
with pytest.raises(CommandOnCooldown):
|
with pytest.raises(CommandOnCooldown):
|
||||||
await cmd.invoke(ctx)
|
await cmd.invoke(ctx)
|
||||||
|
|
||||||
await asyncio.sleep(0.05)
|
await asyncio.sleep(0.1)
|
||||||
await cmd.invoke(ctx)
|
await cmd.invoke(ctx)
|
||||||
assert len(uses) == 2
|
assert len(uses) == 2
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user