diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 11b93e104..2a40c0045 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,6 +34,7 @@ repos: hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] + language: python - repo: https://github.com/psf/black rev: 24.2.0 hooks: @@ -48,6 +49,9 @@ repos: # name: isort Formatting # language: python # types: [file, python] + +default_language_version: + python: python3.10 ci: autoupdate_branch: "unstable" autofix_prs: true diff --git a/docs/src/API Reference/API Reference/models/Discord/index.md b/docs/src/API Reference/API Reference/models/Discord/index.md index 1b45617cd..b97fffa0e 100644 --- a/docs/src/API Reference/API Reference/models/Discord/index.md +++ b/docs/src/API Reference/API Reference/models/Discord/index.md @@ -22,6 +22,7 @@ search: - [Invite](invite) - [Message](message) - [Modals](modals) +- [Poll](poll) - [Reaction](reaction) - [Role](role) - [Scheduled event](scheduled_event) diff --git a/docs/src/API Reference/API Reference/models/Discord/poll.md b/docs/src/API Reference/API Reference/models/Discord/poll.md new file mode 100644 index 000000000..7f53c7c3d --- /dev/null +++ b/docs/src/API Reference/API Reference/models/Discord/poll.md @@ -0,0 +1 @@ +::: interactions.models.discord.poll diff --git a/docs/src/Guides/01 Getting Started.md b/docs/src/Guides/01 Getting Started.md index bda0b6058..53095ff5b 100644 --- a/docs/src/Guides/01 Getting Started.md +++ b/docs/src/Guides/01 Getting Started.md @@ -76,7 +76,7 @@ async def on_ready(): @listen() async def on_message_create(event): # This event is called when a message is sent in a channel the bot can see - print(f"message received: {event.message.content}") + print(f"message received: {event.message.jump_url}") bot.start("Put your token here") diff --git a/docs/src/Guides/03 Creating Commands.md b/docs/src/Guides/03 Creating Commands.md index 7279f798f..368df1947 100644 --- a/docs/src/Guides/03 Creating Commands.md +++ b/docs/src/Guides/03 Creating Commands.md @@ -423,18 +423,69 @@ There are two ways to define permissions. Multiple permissions are defined with the bitwise OR operator `|`. -### Blocking Commands in DMs +## Usable Contexts -You can also block commands in DMs. To do that, just set `dm_permission` to false. +You can control where slash commands (and other application commands) can be used using - in guilds, in DMs, and/or other private channels. By default, commands can be used in all contexts. -```py -@slash_command( - name="my_guild_only_command", - dm_permission=False, -) -async def my_command_function(ctx: SlashContext): - ... -``` +As with permissions, there are two ways to define the context. + +=== ":one: Decorators" + + ```python + from interactions import contexts + + @slash_command(name="my_guild_only_command") + @contexts(guild=True, bot_dm=False, private_channel=False) + async def my_command_function(ctx: SlashContext): + ... + ``` + +=== ":two: Function Definition" + + ```python + from interactions import ContextType + + @slash_command( + name="my_command", + contexts=[ContextType.GUILD], + ) + async def my_command_function(ctx: SlashContext): + ... + ``` + +## Integration Types + +Applications can be installed/integrated in different ways: +- The one you are familiar with is the *guild* integration, where the application is installed in a specific guild, and so the entire guild can use the application. +- You can also install the application to a *user*, where the application can then be used by the user anywhere they desire. + +By default, commands can only be used in guild integrations. Like many other properties, this can be changed. + +There are two ways to define this: + +=== ":one: Decorators" + + ```python + from interactions import integration_types + + @slash_command(name="my_command") + @integration_types(guild=True, user=True) + async def my_command_function(ctx: SlashContext): + ... + ``` + +=== ":two: Function Definition" + + ```python + from interactions import IntegrationType + + @slash_command( + name="my_command", + integration_types=[IntegrationType.GUILD_INSTALL, IntegrationType.USER_INSTALL], + ) + async def my_command_function(ctx: SlashContext): + ... + ``` ## Checks diff --git a/interactions/__init__.py b/interactions/__init__.py index 314e26004..e4b9cce38 100644 --- a/interactions/__init__.py +++ b/interactions/__init__.py @@ -25,6 +25,8 @@ MentionPrefix, Missing, MISSING, + POLL_MAX_ANSWERS, + POLL_MAX_DURATION_HOURS, PREMIUM_GUILD_LIMITS, SELECT_MAX_NAME_LENGTH, SELECTS_MAX_OPTIONS, @@ -37,6 +39,7 @@ smart_cache, T, T_co, + ClientT, utils, ) from .client import const @@ -83,6 +86,7 @@ BrandColors, BrandColours, Buckets, + BulkBanResponse, Button, ButtonStyle, CallbackObject, @@ -104,9 +108,11 @@ ComponentContext, ComponentType, ConsumeRest, + contexts, context_menu, ContextMenu, ContextMenuContext, + ContextType, Converter, cooldown, Cooldown, @@ -179,6 +185,8 @@ IDConverter, InputText, IntegrationExpireBehaviour, + IntegrationType, + integration_types, Intents, InteractionCommand, InteractionContext, @@ -214,6 +222,7 @@ MessageConverter, MessageFlags, MessageInteraction, + MessageInteractionMetadata, MessageReference, MessageType, MFALevel, @@ -238,6 +247,12 @@ PartialEmojiConverter, PermissionOverwrite, Permissions, + Poll, + PollAnswer, + PollAnswerCount, + PollLayoutType, + PollMedia, + PollResults, PremiumTier, PremiumType, process_allowed_mentions, @@ -403,6 +418,7 @@ "BrandColors", "BrandColours", "Buckets", + "BulkBanResponse", "Button", "ButtonStyle", "CallbackObject", @@ -415,6 +431,7 @@ "ChannelType", "check", "Client", + "ClientT", "ClientUser", "Color", "COLOR_TYPES", @@ -426,10 +443,12 @@ "ComponentType", "ConsumeRest", "const", + "contexts", "context_menu", "CONTEXT_MENU_NAME_LENGTH", "ContextMenu", "ContextMenuContext", + "ContextType", "Converter", "cooldown", "Cooldown", @@ -519,6 +538,8 @@ "IDConverter", "InputText", "IntegrationExpireBehaviour", + "IntegrationType", + "integration_types", "Intents", "InteractionCommand", "InteractionContext", @@ -558,6 +579,7 @@ "MessageConverter", "MessageFlags", "MessageInteraction", + "MessageInteractionMetadata", "MessageReference", "MessageType", "MFALevel", @@ -584,6 +606,14 @@ "PartialEmojiConverter", "PermissionOverwrite", "Permissions", + "Poll", + "PollAnswer", + "PollAnswerCount", + "PollLayoutType", + "POLL_MAX_ANSWERS", + "POLL_MAX_DURATION_HOURS", + "PollMedia", + "PollResults", "PREMIUM_GUILD_LIMITS", "PremiumTier", "PremiumType", diff --git a/interactions/api/events/__init__.py b/interactions/api/events/__init__.py index 70db34457..f208fb7c5 100644 --- a/interactions/api/events/__init__.py +++ b/interactions/api/events/__init__.py @@ -41,6 +41,8 @@ MessageCreate, MessageDelete, MessageDeleteBulk, + MessagePollVoteAdd, + MessagePollVoteRemove, MessageReactionAdd, MessageReactionRemove, MessageReactionRemoveAll, @@ -159,6 +161,8 @@ "MessageCreate", "MessageDelete", "MessageDeleteBulk", + "MessagePollVoteAdd", + "MessagePollVoteRemove", "MessageReactionAdd", "MessageReactionRemove", "MessageReactionRemoveAll", diff --git a/interactions/api/events/discord.py b/interactions/api/events/discord.py index bbd6f1f2a..181ec1eb0 100644 --- a/interactions/api/events/discord.py +++ b/interactions/api/events/discord.py @@ -72,6 +72,8 @@ async def an_event_handler(event: ChannelCreate): "MessageCreate", "MessageDelete", "MessageDeleteBulk", + "MessagePollVoteAdd", + "MessagePollVoteRemove", "MessageReactionAdd", "MessageReactionRemove", "MessageReactionRemoveAll", @@ -115,6 +117,7 @@ async def an_event_handler(event: ChannelCreate): from interactions.models.discord.entitlement import Entitlement from interactions.models.discord.guild import Guild, GuildIntegration from interactions.models.discord.message import Message + from interactions.models.discord.poll import Poll from interactions.models.discord.reaction import Reaction from interactions.models.discord.role import Role from interactions.models.discord.scheduled_event import ScheduledEvent @@ -588,6 +591,72 @@ class MessageReactionRemoveEmoji(MessageReactionRemoveAll): """The emoji that was removed""" +@attrs.define(eq=False, order=False, hash=False, kw_only=False) +class BaseMessagePollEvent(BaseEvent): + user_id: "Snowflake_Type" = attrs.field(repr=False) + """The ID of the user that voted""" + channel_id: "Snowflake_Type" = attrs.field(repr=False) + """The ID of the channel the poll is in""" + message_id: "Snowflake_Type" = attrs.field(repr=False) + """The ID of the message the poll is in""" + answer_id: int = attrs.field(repr=False) + """The ID of the answer the user voted for""" + guild_id: "Optional[Snowflake_Type]" = attrs.field(repr=False, default=None) + """The ID of the guild the poll is in""" + + def get_message(self) -> "Optional[Message]": + """Get the message object if it is cached""" + return self.client.cache.get_message(self.channel_id, self.message_id) + + def get_user(self) -> "Optional[User]": + """Get the user object if it is cached""" + return self.client.get_user(self.user_id) + + def get_channel(self) -> "Optional[TYPE_ALL_CHANNEL]": + """Get the channel object if it is cached""" + return self.client.get_channel(self.channel_id) + + def get_guild(self) -> "Optional[Guild]": + """Get the guild object if it is cached""" + return self.client.get_guild(self.guild_id) if self.guild_id is not None else None + + def get_poll(self) -> "Optional[Poll]": + """Get the poll object if it is cached""" + message = self.get_message() + return message.poll if message is not None else None + + async def fetch_message(self) -> "Message": + """Fetch the message the poll is in""" + return await self.client.cache.fetch_message(self.channel_id, self.message_id) + + async def fetch_user(self) -> "User": + """Fetch the user that voted""" + return await self.client.fetch_user(self.user_id) + + async def fetch_channel(self) -> "TYPE_ALL_CHANNEL": + """Fetch the channel the poll is in""" + return await self.client.fetch_channel(self.channel_id) + + async def fetch_guild(self) -> "Optional[Guild]": + """Fetch the guild the poll is in""" + return await self.client.fetch_guild(self.guild_id) if self.guild_id is not None else None + + async def fetch_poll(self) -> "Poll": + """Fetch the poll object""" + message = await self.fetch_message() + return message.poll + + +@attrs.define(eq=False, order=False, hash=False, kw_only=False) +class MessagePollVoteAdd(BaseMessagePollEvent): + """Dispatched when a user votes in a poll""" + + +@attrs.define(eq=False, order=False, hash=False, kw_only=False) +class MessagePollVoteRemove(BaseMessagePollEvent): + """Dispatched when a user remotes a votes in a poll""" + + @attrs.define(eq=False, order=False, hash=False, kw_only=False) class PresenceUpdate(BaseEvent): """A user's presence has changed.""" diff --git a/interactions/api/events/processors/message_events.py b/interactions/api/events/processors/message_events.py index 00314464f..74a10cbde 100644 --- a/interactions/api/events/processors/message_events.py +++ b/interactions/api/events/processors/message_events.py @@ -83,3 +83,41 @@ async def _on_raw_message_delete_bulk(self, event: "RawGatewayEvent") -> None: event.data.get("ids"), ) ) + + @Processor.define() + async def _on_raw_message_poll_vote_add(self, event: "RawGatewayEvent") -> None: + """ + Process raw message poll vote add event and dispatch a processed poll vote add event. + + Args: + event: raw poll vote add event + + """ + self.dispatch( + events.MessagePollVoteAdd( + event.data.get("guild_id", None), + event.data["channel_id"], + event.data["message_id"], + event.data["user_id"], + event.data["option"], + ) + ) + + @Processor.define() + async def _on_raw_message_poll_vote_remove(self, event: "RawGatewayEvent") -> None: + """ + Process raw message poll vote remove event and dispatch a processed poll vote remove event. + + Args: + event: raw poll vote remove event + + """ + self.dispatch( + events.MessagePollVoteRemove( + event.data.get("guild_id", None), + event.data["channel_id"], + event.data["message_id"], + event.data["user_id"], + event.data["option"], + ) + ) diff --git a/interactions/api/http/http_client.py b/interactions/api/http/http_client.py index dd3061c1f..18a386dbf 100644 --- a/interactions/api/http/http_client.py +++ b/interactions/api/http/http_client.py @@ -319,7 +319,11 @@ def _process_payload( else: payload = [dict_filter(x) if isinstance(x, dict) else x for x in payload] - if not files: + if files is None: + return payload + + if files == []: + payload["attachments"] = [] return payload if not isinstance(files, list): diff --git a/interactions/api/http/http_requests/entitlements.py b/interactions/api/http/http_requests/entitlements.py index 632a4909a..9b6796ba1 100644 --- a/interactions/api/http/http_requests/entitlements.py +++ b/interactions/api/http/http_requests/entitlements.py @@ -89,3 +89,21 @@ async def delete_test_entitlement(self, application_id: "Snowflake_Type", entitl entitlement_id=entitlement_id, ) ) + + async def consume_entitlement(self, application_id: "Snowflake_Type", entitlement_id: "Snowflake_Type") -> None: + """ + For One-Time Purchase consumable SKUs, marks a given entitlement for the user as consumed. + + Args: + application_id: The ID of the application. + entitlement_id: The ID of the entitlement. + + """ + await self.request( + Route( + "POST", + "/applications/{application_id}/entitlements/{entitlement_id}/consume", + application_id=application_id, + entitlement_id=entitlement_id, + ) + ) diff --git a/interactions/api/http/http_requests/guild.py b/interactions/api/http/http_requests/guild.py index 6e95a08fe..153f04c3e 100644 --- a/interactions/api/http/http_requests/guild.py +++ b/interactions/api/http/http_requests/guild.py @@ -299,6 +299,32 @@ async def remove_guild_ban( Route("DELETE", "/guilds/{guild_id}/bans/{user_id}", guild_id=guild_id, user_id=user_id), reason=reason ) + async def bulk_guild_ban( + self, + guild_id: "Snowflake_Type", + user_ids: "list[Snowflake_Type]", + delete_message_seconds: int = 0, + reason: str | None = None, + ) -> discord_typings.BulkBanData: + """ + Ban a list of users from the guild. + + Args: + guild_id: The ID of the guild to create the ban in + user_ids: List of user ids to ban (max 200) + delete_message_seconds: Number of seconds to delete messages for (0-604800) + reason: The reason for this action + + Returns: + Bulk ban object + + """ + payload = {"delete_message_days": delete_message_seconds, "user_ids": user_ids} + result = await self.request( + Route("POST", "/guilds/{guild_id}/bulk-ban", guild_id=guild_id), payload=payload, reason=reason + ) + return cast(discord_typings.BulkBanData, result) + async def get_guild_prune_count( self, guild_id: "Snowflake_Type", diff --git a/interactions/api/http/http_requests/messages.py b/interactions/api/http/http_requests/messages.py index a836e269f..8891df54b 100644 --- a/interactions/api/http/http_requests/messages.py +++ b/interactions/api/http/http_requests/messages.py @@ -1,8 +1,9 @@ -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, cast, TypedDict import discord_typings from interactions.models.internal.protocols import CanRequest +from interactions.client.utils.serializer import dict_filter_none from ..route import Route __all__ = ("MessageRequests",) @@ -13,6 +14,10 @@ from interactions import UPLOADABLE_TYPE +class GetAnswerVotersData(TypedDict): + users: list[discord_typings.UserData] + + class MessageRequests(CanRequest): async def create_message( self, @@ -175,3 +180,59 @@ async def crosspost_message( ) ) return cast(discord_typings.MessageData, result) + + async def get_answer_voters( + self, + channel_id: "Snowflake_Type", + message_id: "Snowflake_Type", + answer_id: int, + after: "Snowflake_Type | None" = None, + limit: int = 25, + ) -> GetAnswerVotersData: + """ + Get a list of users that voted for this specific answer. + + Args: + channel_id: Channel the message is in + message_id: The message with the poll + answer_id: The answer to get voters for + after: Get messages after this user ID + limit: The max number of users to return (default 25, max 100) + + Returns: + GetAnswerVotersData: A response that has a list of users that voted for the answer + + """ + result = await self.request( + Route( + "GET", + "/channels/{channel_id}/polls/{message_id}/answers/{answer_id}", + channel_id=channel_id, + message_id=message_id, + answer_id=answer_id, + ), + params=dict_filter_none({"after": after, "limit": limit}), + ) + return cast(GetAnswerVotersData, result) + + async def end_poll(self, channel_id: "Snowflake_Type", message_id: "Snowflake_Type") -> discord_typings.MessageData: + """ + Ends a poll. Only can end polls from the current bot. + + Args: + channel_id: Channel the message is in + message_id: The message with the poll + + Returns: + message object + + """ + result = await self.request( + Route( + "POST", + "/channels/{channel_id}/polls/{message_id}/expire", + channel_id=channel_id, + message_id=message_id, + ) + ) + return cast(discord_typings.MessageData, result) diff --git a/interactions/client/__init__.py b/interactions/client/__init__.py index 96df96f0a..2f2783052 100644 --- a/interactions/client/__init__.py +++ b/interactions/client/__init__.py @@ -29,9 +29,12 @@ MISSING, MENTION_PREFIX, PREMIUM_GUILD_LIMITS, + POLL_MAX_ANSWERS, + POLL_MAX_DURATION_HOURS, Absent, T, T_co, + ClientT, ) from .client import Client from .auto_shard_client import AutoShardedClient @@ -61,6 +64,8 @@ "EMBED_MAX_FIELDS", "EMBED_TOTAL_MAX", "EMBED_FIELD_VALUE_LENGTH", + "POLL_MAX_ANSWERS", + "POLL_MAX_DURATION_HOURS", "Singleton", "Sentinel", "GlobalScope", @@ -73,6 +78,7 @@ "Absent", "T", "T_co", + "ClientT", "Client", "AutoShardedClient", "smart_cache", diff --git a/interactions/client/auto_shard_client.py b/interactions/client/auto_shard_client.py index abfe41e17..233444994 100644 --- a/interactions/client/auto_shard_client.py +++ b/interactions/client/auto_shard_client.py @@ -173,7 +173,12 @@ async def _on_websocket_ready(self, event: events.RawGatewayEvent) -> None: # run any pending startup tasks if self.async_startup_tasks: try: - await asyncio.gather(*self.async_startup_tasks) + await asyncio.gather( + *[ + task[0](*task[1] if len(task) > 1 else [], **task[2] if len(task) == 3 else {}) + for task in self.async_startup_tasks + ] + ) except Exception as e: self.dispatch(events.Error(source="async-extension-loader", error=e)) diff --git a/interactions/client/client.py b/interactions/client/client.py index 47a631f08..fe9a5ff6f 100644 --- a/interactions/client/client.py +++ b/interactions/client/client.py @@ -12,6 +12,7 @@ import traceback from collections.abc import Iterable from datetime import datetime +from typing_extensions import Self from typing import ( TYPE_CHECKING, Any, @@ -26,6 +27,8 @@ Union, Awaitable, Tuple, + TypeVar, + overload, ) from aiohttp import BasicAuth @@ -40,6 +43,7 @@ from interactions.client import errors from interactions.client.const import ( GLOBAL_SCOPE, + Missing, MISSING, Absent, EMBED_MAX_DESC_LENGTH, @@ -122,6 +126,8 @@ if TYPE_CHECKING: from interactions.models import Snowflake_Type, TYPE_ALL_CHANNEL +EventT = TypeVar("EventT", bound=BaseEvent) + __all__ = ("Client",) # see https://discord.com/developers/docs/topics/gateway#list-of-intents @@ -362,17 +368,17 @@ def __init__( """The HTTP client to use when interacting with discord endpoints""" # context factories - self.interaction_context: Type[BaseContext] = interaction_context + self.interaction_context: Type[BaseContext[Self]] = interaction_context """The object to instantiate for Interaction Context""" - self.component_context: Type[BaseContext] = component_context + self.component_context: Type[BaseContext[Self]] = component_context """The object to instantiate for Component Context""" - self.autocomplete_context: Type[BaseContext] = autocomplete_context + self.autocomplete_context: Type[BaseContext[Self]] = autocomplete_context """The object to instantiate for Autocomplete Context""" - self.modal_context: Type[BaseContext] = modal_context + self.modal_context: Type[BaseContext[Self]] = modal_context """The object to instantiate for Modal Context""" - self.slash_context: Type[BaseContext] = slash_context + self.slash_context: Type[BaseContext[Self]] = slash_context """The object to instantiate for Slash Context""" - self.context_menu_context: Type[BaseContext] = context_menu_context + self.context_menu_context: Type[BaseContext[Self]] = context_menu_context """The object to instantiate for Context Menu Context""" self.token: str | None = token @@ -1061,12 +1067,36 @@ async def wait_until_ready(self) -> None: """Waits for the client to become ready.""" await self._ready.wait() + @overload + def wait_for( + self, + event: type[EventT], + checks: Absent[Callable[[EventT], bool] | Callable[[EventT], Awaitable[bool]]] = MISSING, + timeout: Optional[float] = None, + ) -> "Awaitable[EventT]": ... + + @overload + def wait_for( + self, + event: str, + checks: Callable[[EventT], bool] | Callable[[EventT], Awaitable[bool]], + timeout: Optional[float] = None, + ) -> "Awaitable[EventT]": ... + + @overload def wait_for( self, - event: Union[str, "BaseEvent"], - checks: Absent[Optional[Union[Callable[..., bool], Callable[..., Awaitable[bool]]]]] = MISSING, + event: str, + checks: Missing = MISSING, timeout: Optional[float] = None, - ) -> Any: + ) -> Awaitable[Any]: ... + + def wait_for( + self, + event: Union[str, "type[BaseEvent]"], + checks: Absent[Callable[[BaseEvent], bool] | Callable[[BaseEvent], Awaitable[bool]]] = MISSING, + timeout: Optional[float] = None, + ) -> Awaitable[Any]: """ Waits for a WebSocket event to be dispatched. @@ -1112,7 +1142,7 @@ async def wait_for_modal( """ author = to_snowflake(author) if author else None - def predicate(event) -> bool: + def predicate(event: events.ModalCompletion) -> bool: if modal.custom_id != event.ctx.custom_id: return False return author == to_snowflake(event.ctx.author) if author else True @@ -1120,9 +1150,60 @@ def predicate(event) -> bool: resp = await self.wait_for("modal_completion", predicate, timeout) return resp.ctx + @overload + async def wait_for_component( + self, + messages: Union[Message, int, list], + components: Union[ + List[List[Union["BaseComponent", dict]]], + List[Union["BaseComponent", dict]], + "BaseComponent", + dict, + ], + check: Optional[Callable[[events.Component], bool] | Callable[[events.Component], Awaitable[bool]]] = None, + timeout: Optional[float] = None, + ) -> "events.Component": ... + + @overload + async def wait_for_component( + self, + *, + components: Union[ + List[List[Union["BaseComponent", dict]]], + List[Union["BaseComponent", dict]], + "BaseComponent", + dict, + ], + check: Optional[Callable[[events.Component], bool] | Callable[[events.Component], Awaitable[bool]]] = None, + timeout: Optional[float] = None, + ) -> "events.Component": ... + + @overload + async def wait_for_component( + self, + messages: None, + components: Union[ + List[List[Union["BaseComponent", dict]]], + List[Union["BaseComponent", dict]], + "BaseComponent", + dict, + ], + check: Optional[Callable[[events.Component], bool] | Callable[[events.Component], Awaitable[bool]]] = None, + timeout: Optional[float] = None, + ) -> "events.Component": ... + + @overload + async def wait_for_component( + self, + messages: Union[Message, int, list], + components: None = None, + check: Optional[Callable[[events.Component], bool] | Callable[[events.Component], Awaitable[bool]]] = None, + timeout: Optional[float] = None, + ) -> "events.Component": ... + async def wait_for_component( self, - messages: Union[Message, int, list] = None, + messages: Optional[Union[Message, int, list]] = None, components: Optional[ Union[ List[List[Union["BaseComponent", dict]]], @@ -1131,7 +1212,7 @@ async def wait_for_component( dict, ] ] = None, - check: Absent[Optional[Union[Callable[..., bool], Callable[..., Awaitable[bool]]]]] | None = None, + check: Optional[Callable[[events.Component], bool] | Callable[[events.Component], Awaitable[bool]]] = None, timeout: Optional[float] = None, ) -> "events.Component": """ @@ -1746,7 +1827,7 @@ def update_command_cache(self, scope: "Snowflake_Type", command_name: str, comma command.cmd_id[scope] = command_id self._interaction_lookup[command.resolved_name] = command - async def get_context(self, data: dict) -> InteractionContext: + async def get_context(self, data: dict) -> InteractionContext[Self]: match data["type"]: case InteractionType.MESSAGE_COMPONENT: cls = self.component_context.from_dict(self, data) @@ -2572,6 +2653,16 @@ async def delete_test_entitlement(self, entitlement_id: "Snowflake_Type") -> Non """ await self.http.delete_test_entitlement(self.app.id, to_snowflake(entitlement_id)) + async def consume_entitlement(self, entitlement_id: "Snowflake_Type") -> None: + """ + For One-Time Purchase consumable SKUs, marks a given entitlement for the user as consumed. + + Args: + entitlement_id: The ID of the entitlement to consume. + + """ + await self.http.consume_entitlement(self.app.id, entitlement_id) + def mention_command(self, name: str, scope: int = 0) -> str: """ Returns a string that would mention the interaction specified. diff --git a/interactions/client/const.py b/interactions/client/const.py index d1b4f04e7..52eb709f8 100644 --- a/interactions/client/const.py +++ b/interactions/client/const.py @@ -43,7 +43,8 @@ import sys from collections import defaultdict from importlib.metadata import version as _v, PackageNotFoundError -from typing import TypeVar, Union, Callable, Coroutine, ClassVar +import typing_extensions +from typing import TypeVar, Union, Callable, Coroutine, ClassVar, TYPE_CHECKING __all__ = ( "__version__", @@ -79,11 +80,14 @@ "Absent", "T", "T_co", + "ClientT", "LIB_PATH", "RECOVERABLE_WEBSOCKET_CLOSE_CODES", "NON_RESUMABLE_WEBSOCKET_CLOSE_CODES", "CLIENT_FEATURE_FLAGS", "has_client_feature", + "POLL_MAX_ANSWERS", + "POLL_MAX_DURATION_HOURS", ) _ver_info = sys.version_info @@ -130,6 +134,9 @@ def get_logger() -> logging.Logger: EMBED_TOTAL_MAX = 6000 EMBED_FIELD_VALUE_LENGTH = 1024 +POLL_MAX_ANSWERS = 10 +POLL_MAX_DURATION_HOURS = 168 + class Singleton(type): _instances: ClassVar[dict] = {} @@ -234,6 +241,13 @@ def has_client_feature(feature: str) -> bool: Absent = Union[T, Missing] AsyncCallable = Callable[..., Coroutine] +if TYPE_CHECKING: + from interactions import Client + + ClientT = typing_extensions.TypeVar("ClientT", bound=Client, default=Client) +else: + ClientT = TypeVar("ClientT") + LIB_PATH = os.sep.join(__file__.split(os.sep)[:-2]) """The path to the library folder.""" diff --git a/interactions/client/mixins/send.py b/interactions/client/mixins/send.py index 5776c78d5..7aeadc03a 100644 --- a/interactions/client/mixins/send.py +++ b/interactions/client/mixins/send.py @@ -10,6 +10,7 @@ from interactions.models.discord.components import BaseComponent from interactions.models.discord.embed import Embed from interactions.models.discord.message import AllowedMentions, Message, MessageReference + from interactions.models.discord.poll import Poll from interactions.models.discord.sticker import Sticker from interactions.models.discord.snowflake import Snowflake_Type @@ -49,6 +50,7 @@ async def send( delete_after: Optional[float] = None, nonce: Optional[str | int] = None, enforce_nonce: bool = False, + poll: "Optional[Poll | dict]" = None, **kwargs: Any, ) -> "Message": """ @@ -73,6 +75,7 @@ async def send( enforce_nonce: If enabled and nonce is present, it will be checked for uniqueness in the past few minutes. \ If another message was created by the same author with the same nonce, that message will be returned \ and no new message will be created. + poll: A poll. Returns: New message object that was sent. @@ -115,6 +118,7 @@ async def send( flags=flags, nonce=nonce, enforce_nonce=enforce_nonce, + poll=poll, **kwargs, ) diff --git a/interactions/ext/hybrid_commands/context.py b/interactions/ext/hybrid_commands/context.py index c3a649829..51b6d2fa5 100644 --- a/interactions/ext/hybrid_commands/context.py +++ b/interactions/ext/hybrid_commands/context.py @@ -9,7 +9,6 @@ Permissions, Message, SlashContext, - Client, Typing, Embed, BaseComponent, @@ -22,7 +21,11 @@ to_snowflake, Attachment, process_message_payload, + TYPE_MESSAGEABLE_CHANNEL, + Poll, ) +from interactions.client.const import ClientT +from interactions.models.discord.enums import ContextType from interactions.client.mixins.send import SendMixin from interactions.client.errors import HTTPException from interactions.ext import prefixed_commands as prefixed @@ -35,7 +38,7 @@ class DeferTyping: - def __init__(self, ctx: "HybridContext", ephermal: bool) -> None: + def __init__(self, ctx: "SlashContext[ClientT]", ephermal: bool) -> None: self.ctx = ctx self.ephermal = ephermal @@ -46,7 +49,7 @@ async def __aexit__(self, *_) -> None: pass -class HybridContext(BaseContext, SendMixin): +class HybridContext(BaseContext[ClientT], SendMixin): prefix: str "The prefix used to invoke this command." @@ -60,6 +63,9 @@ class HybridContext(BaseContext, SendMixin): ephemeral: bool """Whether the context response is ephemeral.""" + context: Optional[ContextType] + """Context where the command was triggered from""" + _command_name: str """The command name.""" _message: Message | None @@ -71,16 +77,17 @@ class HybridContext(BaseContext, SendMixin): __attachment_index__: int - _slash_ctx: SlashContext | None - _prefixed_ctx: prefixed.PrefixedContext | None + _slash_ctx: SlashContext[ClientT] | None + _prefixed_ctx: prefixed.PrefixedContext[ClientT] | None - def __init__(self, client: Client): + def __init__(self, client: ClientT): super().__init__(client) self.prefix = "" self.app_permissions = Permissions(0) self.deferred = False self.responded = False self.ephemeral = False + self.context = None self._command_name = "" self.args = [] self.kwargs = {} @@ -90,12 +97,12 @@ def __init__(self, client: Client): self._prefixed_ctx = None @classmethod - def from_dict(cls, client: Client, payload: dict) -> None: + def from_dict(cls, client: ClientT, payload: dict) -> None: # this doesn't mean anything, so just implement it to make abc happy raise NotImplementedError @classmethod - def from_slash_context(cls, ctx: SlashContext) -> Self: + def from_slash_context(cls, ctx: SlashContext[ClientT]) -> Self: self = cls(ctx.client) self.guild_id = ctx.guild_id self.channel_id = ctx.channel_id @@ -106,6 +113,7 @@ def from_slash_context(cls, ctx: SlashContext) -> Self: self.deferred = ctx.deferred self.responded = ctx.responded self.ephemeral = ctx.ephemeral + self.context = ctx.context self._command_name = ctx._command_name self.args = ctx.args self.kwargs = ctx.kwargs @@ -113,7 +121,7 @@ def from_slash_context(cls, ctx: SlashContext) -> Self: return self @classmethod - def from_prefixed_context(cls, ctx: prefixed.PrefixedContext) -> Self: + def from_prefixed_context(cls, ctx: prefixed.PrefixedContext[ClientT]) -> Self: # this is a "best guess" on what the permissions are # this may or may not be totally accurate if hasattr(ctx.channel, "permissions_for"): @@ -121,9 +129,27 @@ def from_prefixed_context(cls, ctx: prefixed.PrefixedContext) -> Self: elif ctx.channel.type in {10, 11, 12}: # it's a thread app_permissions = ctx.channel.parent_channel.permissions_for(ctx.guild.me) # type: ignore else: - app_permissions = Permissions(0) + # likely a dm, give a sane default + app_permissions = ( + Permissions.VIEW_CHANNEL + | Permissions.SEND_MESSAGES + | Permissions.READ_MESSAGE_HISTORY + | Permissions.EMBED_LINKS + | Permissions.ATTACH_FILES + | Permissions.MENTION_EVERYONE + | Permissions.USE_EXTERNAL_EMOJIS + ) self = cls(ctx.client) + + if ctx.channel.type == 1: # dm + # note that prefixed cmds for dms cannot be used outside of bot dms + self.context = ContextType.BOT_DM + elif ctx.channel.type == 3: # group dm - technically not possible but just in case + self.context = ContextType.PRIVATE_CHANNEL + else: + self.context = ContextType.GUILD + self.guild_id = ctx.guild_id self.channel_id = ctx.channel_id self.author_id = ctx.author_id @@ -137,7 +163,7 @@ def from_prefixed_context(cls, ctx: prefixed.PrefixedContext) -> Self: return self @property - def inner_context(self) -> SlashContext | prefixed.PrefixedContext: + def inner_context(self) -> SlashContext[ClientT] | prefixed.PrefixedContext[ClientT]: """The inner context that this hybrid context is wrapping.""" return self._slash_ctx or self._prefixed_ctx # type: ignore @@ -165,6 +191,14 @@ def deferred_ephemeral(self) -> bool: """Whether the interaction has been deferred ephemerally.""" return self.deferred and self.ephemeral + @property + def channel(self) -> "TYPE_MESSAGEABLE_CHANNEL": + """The channel this context was invoked in.""" + if self._prefixed_ctx: + return self._prefixed_ctx.channel + + return self._slash_ctx.channel + @property def message(self) -> Message | None: """The message that invoked this context.""" @@ -276,6 +310,7 @@ async def send( suppress_embeds: bool = False, silent: bool = False, flags: Optional[Union[int, "MessageFlags"]] = None, + poll: "Optional[Poll | dict]" = None, delete_after: Optional[float] = None, ephemeral: bool = False, **kwargs: Any, @@ -297,6 +332,7 @@ async def send( suppress_embeds: Should embeds be suppressed on this send silent: Should this message be sent without triggering a notification. flags: Message flags to apply. + poll: A poll. delete_after: Delete message after this many seconds. ephemeral: Should this message be sent as ephemeral (hidden) - only works with interactions @@ -325,6 +361,7 @@ async def send( file=file, tts=tts, flags=flags, + poll=poll, delete_after=delete_after, pass_self_into_delete=bool(self._slash_ctx), **kwargs, diff --git a/interactions/ext/hybrid_commands/hybrid_slash.py b/interactions/ext/hybrid_commands/hybrid_slash.py index 8bab140fa..659cb1ddc 100644 --- a/interactions/ext/hybrid_commands/hybrid_slash.py +++ b/interactions/ext/hybrid_commands/hybrid_slash.py @@ -26,6 +26,8 @@ SlashCommandOption, Snowflake_Type, Permissions, + ContextType, + IntegrationType, ) from interactions.client.const import AsyncCallable, GLOBAL_SCOPE from interactions.client.utils.serializer import no_export_meta @@ -33,7 +35,6 @@ from interactions.client.errors import BadArgument from interactions.ext.prefixed_commands import PrefixedCommand, PrefixedContext from interactions.models.internal.converters import _LiteralConverter, CONSUME_REST_MARKER -from interactions.models.internal.checks import guild_only if TYPE_CHECKING: from .context import HybridContext @@ -45,6 +46,22 @@ def _values_wrapper(a_dict: dict | None) -> list: return list(a_dict.values()) if a_dict else [] +def generate_contexts_check(contexts: list[ContextType | int]) -> Callable[["HybridContext"], Awaitable[bool]]: + set_contexts = frozenset(contexts) + + async def _contexts_check(ctx: "HybridContext") -> bool: + if ctx.context: + return ctx.context in set_contexts + + if ctx.guild_id: + return ContextType.GUILD in set_contexts + if ctx.channel.type == 1 and ctx.channel.recipient.id == ctx.bot.user.id: + return ContextType.BOT_DM in set_contexts + return ContextType.PRIVATE_CHANNEL in set_contexts + + return _contexts_check # type: ignore + + def generate_permission_check(permissions: "Permissions") -> Callable[["HybridContext"], Awaitable[bool]]: async def _permission_check(ctx: "HybridContext") -> bool: return ctx.author.has_permission(*permissions) if ctx.guild_id else True # type: ignore @@ -333,10 +350,9 @@ def slash_to_prefixed(cmd: HybridSlashCommand) -> _HybridToPrefixedCommand: # n # can't be done in init due to how _binding works prefixed_cmd._binding = cmd._binding - if not cmd.dm_permission: - prefixed_cmd.add_check(guild_only()) - - if cmd.scopes != [GLOBAL_SCOPE]: + if cmd.scopes == [GLOBAL_SCOPE]: + prefixed_cmd.add_check(generate_contexts_check(cmd.contexts)) + else: prefixed_cmd.add_check(generate_scope_check(cmd.scopes)) if cmd.default_member_permissions: @@ -451,6 +467,8 @@ def hybrid_slash_command( scopes: Absent[list["Snowflake_Type"]] = MISSING, options: Optional[list[Union[SlashCommandOption, dict]]] = None, default_member_permissions: Optional["Permissions"] = None, + integration_types: Optional[List[Union[IntegrationType, int]]] = None, + contexts: Optional[List[Union[ContextType, int]]] = None, dm_permission: bool = True, sub_cmd_name: str | LocalisedName = None, group_name: str | LocalisedName = None, @@ -480,7 +498,9 @@ def hybrid_slash_command( scopes: The scope this command exists within options: The parameters for the command, max 25 default_member_permissions: What permissions members need to have by default to use this command. - dm_permission: Should this command be available in DMs. + integration_types: Installation context(s) where the slash command is available, only for globally-scoped commands. + contexts: Interaction context(s) where the command can be used, only for globally-scoped commands. + dm_permission: Should this command be available in DMs (deprecated). sub_cmd_name: 1-32 character name of the subcommand sub_cmd_description: 1-100 character description of the subcommand group_name: 1-32 character name of the group @@ -521,6 +541,8 @@ def wrapper(func: AsyncCallable) -> HybridSlashCommand: description=_description, scopes=scopes or [GLOBAL_SCOPE], default_member_permissions=perm, + integration_types=integration_types or [IntegrationType.GUILD_INSTALL], + contexts=contexts or [ContextType.GUILD, ContextType.BOT_DM, ContextType.PRIVATE_CHANNEL], dm_permission=dm_permission, callback=func, options=options, @@ -544,6 +566,8 @@ def hybrid_slash_subcommand( base_description: Optional[str | LocalisedDesc] = None, base_desc: Optional[str | LocalisedDesc] = None, base_default_member_permissions: Optional["Permissions"] = None, + base_integration_types: Optional[List[Union[IntegrationType, int]]] = None, + base_contexts: Optional[List[Union[ContextType, int]]] = None, base_dm_permission: bool = True, subcommand_group_description: Optional[str | LocalisedDesc] = None, sub_group_desc: Optional[str | LocalisedDesc] = None, @@ -564,7 +588,9 @@ def hybrid_slash_subcommand( base_description: The description of the base command base_desc: An alias of `base_description` base_default_member_permissions: What permissions members need to have by default to use this command. - base_dm_permission: Should this command be available in DMs. + base_integration_types: Installation context(s) where the slash command is available, only for globally-scoped commands. + base_contexts: Interaction context(s) where the command can be used, only for globally-scoped commands. + base_dm_permission: Should this command be available in DMs (deprecated). subcommand_group_description: Description of the subcommand group sub_group_desc: An alias for `subcommand_group_description` scopes: The scopes of which this command is available, defaults to GLOBAL_SCOPE @@ -597,6 +623,8 @@ def wrapper(func: AsyncCallable) -> HybridSlashCommand: sub_cmd_name=_name, sub_cmd_description=_description, default_member_permissions=base_default_member_permissions, + integration_types=base_integration_types or [IntegrationType.GUILD_INSTALL], + contexts=base_contexts or [ContextType.GUILD, ContextType.BOT_DM, ContextType.PRIVATE_CHANNEL], dm_permission=base_dm_permission, scopes=scopes or [GLOBAL_SCOPE], callback=func, diff --git a/interactions/ext/paginators.py b/interactions/ext/paginators.py index 034c7eb0c..9e36cdcbb 100644 --- a/interactions/ext/paginators.py +++ b/interactions/ext/paginators.py @@ -106,6 +106,8 @@ class Paginator: """Show a button which will call the `callback`""" show_select_menu: bool = attrs.field(repr=False, default=False) """Should a select menu be shown for navigation""" + hide_buttons_on_stop: bool = attrs.field(repr=False, default=False) + """Should the paginator buttons be hidden instead of disabled after stop or timeout""" first_button_emoji: Optional[Union["PartialEmoji", dict, str]] = attrs.field( repr=False, default="⏮️", metadata=export_converter(process_emoji) @@ -270,6 +272,9 @@ def create_components(self, disable: bool = False) -> List[ActionRow]: A list of ActionRows """ + if disable and self.hide_buttons_on_stop: + return [] + output = [] if self.show_select_menu: diff --git a/interactions/ext/prefixed_commands/context.py b/interactions/ext/prefixed_commands/context.py index a9d995faf..b09249665 100644 --- a/interactions/ext/prefixed_commands/context.py +++ b/interactions/ext/prefixed_commands/context.py @@ -2,7 +2,7 @@ from typing_extensions import Self -from interactions.client.client import Client +from interactions.client.const import ClientT from interactions.client.mixins.send import SendMixin from interactions.models.discord.channel import TYPE_MESSAGEABLE_CHANNEL from interactions.models.discord.embed import Embed @@ -17,7 +17,7 @@ __all__ = ("PrefixedContext",) -class PrefixedContext(BaseContext, SendMixin): +class PrefixedContext(BaseContext[ClientT], SendMixin): _message: Message prefix: str @@ -33,12 +33,12 @@ class PrefixedContext(BaseContext, SendMixin): "This is always empty, and is only here for compatibility with other types of commands." @classmethod - def from_dict(cls, client: "Client", payload: dict) -> Self: + def from_dict(cls, client: "ClientT", payload: dict) -> Self: # this doesn't mean anything, so just implement it to make abc happy raise NotImplementedError @classmethod - def from_message(cls, client: "Client", message: Message) -> Self: + def from_message(cls, client: "ClientT", message: Message) -> Self: instance = cls(client=client) # hack to work around BaseContext property diff --git a/interactions/models/__init__.py b/interactions/models/__init__.py index ab3fc621d..c258996e4 100644 --- a/interactions/models/__init__.py +++ b/interactions/models/__init__.py @@ -29,6 +29,7 @@ BaseUser, BrandColors, BrandColours, + BulkBanResponse, Button, ButtonStyle, ChannelFlags, @@ -42,6 +43,7 @@ Colour, CommandType, ComponentType, + ContextType, CustomEmoji, DefaultNotificationLevel, DefaultReaction, @@ -84,6 +86,7 @@ GuildWidgetSettings, InputText, IntegrationExpireBehaviour, + IntegrationType, Intents, InteractionPermissionTypes, InteractionType, @@ -103,6 +106,7 @@ MessageActivityType, MessageFlags, MessageInteraction, + MessageInteractionMetadata, MessageReference, MessageType, MFALevel, @@ -119,6 +123,12 @@ PartialEmoji, PermissionOverwrite, Permissions, + Poll, + PollAnswer, + PollAnswerCount, + PollLayoutType, + PollMedia, + PollResults, PremiumTier, PremiumType, process_allowed_mentions, @@ -216,6 +226,7 @@ component_callback, ComponentCommand, ComponentContext, + contexts, context_menu, user_context_menu, message_context_menu, @@ -256,6 +267,7 @@ has_id, has_role, IDConverter, + integration_types, InteractionCommand, InteractionContext, IntervalTrigger, @@ -353,6 +365,7 @@ "BrandColors", "BrandColours", "Buckets", + "BulkBanResponse", "Button", "ButtonStyle", "CallbackObject", @@ -374,9 +387,11 @@ "ComponentContext", "ComponentType", "ConsumeRest", + "contexts", "context_menu", "ContextMenu", "ContextMenuContext", + "ContextType", "Converter", "cooldown", "Cooldown", @@ -454,6 +469,8 @@ "IDConverter", "InputText", "IntegrationExpireBehaviour", + "IntegrationType", + "integration_types", "Intents", "InteractionCommand", "InteractionContext", @@ -489,6 +506,7 @@ "MessageConverter", "MessageFlags", "MessageInteraction", + "MessageInteractionMetadata", "MessageReference", "MessageType", "MFALevel", @@ -513,6 +531,12 @@ "PartialEmojiConverter", "PermissionOverwrite", "Permissions", + "Poll", + "PollAnswer", + "PollAnswerCount", + "PollLayoutType", + "PollMedia", + "PollResults", "PremiumTier", "PremiumType", "process_allowed_mentions", diff --git a/interactions/models/discord/__init__.py b/interactions/models/discord/__init__.py index fcedbec6e..21baa4d68 100644 --- a/interactions/models/discord/__init__.py +++ b/interactions/models/discord/__init__.py @@ -83,10 +83,12 @@ ChannelType, CommandType, ComponentType, + ContextType, DefaultNotificationLevel, ExplicitContentFilterLevel, ForumLayoutType, IntegrationExpireBehaviour, + IntegrationType, Intents, InteractionPermissionTypes, InteractionType, @@ -102,6 +104,7 @@ OnboardingPromptType, OverwriteType, Permissions, + PollLayoutType, PremiumTier, PremiumType, ScheduledEventPrivacyLevel, @@ -126,6 +129,7 @@ AuditLogEntry, AuditLogHistory, BaseGuild, + BulkBanResponse, Guild, GuildBan, GuildIntegration, @@ -145,6 +149,7 @@ Message, MessageActivity, MessageInteraction, + MessageInteractionMetadata, MessageReference, process_allowed_mentions, process_message_payload, @@ -152,6 +157,7 @@ ) from .modal import InputText, Modal, ParagraphText, ShortText, TextStyles from .onboarding import Onboarding, OnboardingPrompt, OnboardingPromptOption +from .poll import PollMedia, PollAnswer, PollAnswerCount, PollResults, Poll from .reaction import Reaction, ReactionUsers from .role import Role from .scheduled_event import ScheduledEvent @@ -202,6 +208,7 @@ "BaseUser", "BrandColors", "BrandColours", + "BulkBanResponse", "Button", "ButtonStyle", "ChannelFlags", @@ -215,6 +222,7 @@ "Colour", "CommandType", "ComponentType", + "ContextType", "CustomEmoji", "DefaultNotificationLevel", "DefaultReaction", @@ -258,6 +266,7 @@ "GuildWidgetSettings", "InputText", "IntegrationExpireBehaviour", + "IntegrationType", "Intents", "InteractionPermissionTypes", "InteractionType", @@ -277,6 +286,7 @@ "MessageActivityType", "MessageFlags", "MessageInteraction", + "MessageInteractionMetadata", "MessageReference", "MessageType", "MFALevel", @@ -293,6 +303,12 @@ "PartialEmoji", "PermissionOverwrite", "Permissions", + "Poll", + "PollAnswer", + "PollAnswerCount", + "PollLayoutType", + "PollMedia", + "PollResults", "PremiumTier", "PremiumType", "process_allowed_mentions", diff --git a/interactions/models/discord/application.py b/interactions/models/discord/application.py index e42730e71..f6fdee89d 100644 --- a/interactions/models/discord/application.py +++ b/interactions/models/discord/application.py @@ -62,6 +62,9 @@ class Application(DiscordObject): # todo: implement an ApplicationInstallParams object. See https://discord.com/developers/docs/resources/application#install-params-object install_params: Optional[dict] = attrs.field(repr=False, default=None) """The application's settings for in-app invitation to guilds""" + # todo: implement IntegrationTypeConfigurationObject too, see https://discord.com/developers/docs/resources/application#application-object-application-integration-type-configuration-object + integration_types_config: Optional[dict] = attrs.field(repr=False, default=None) + """Default scopes and permissions for each supported installation context. Value for each key is an integration type configuration object""" custom_install_url: Optional[str] = attrs.field(repr=False, default=None) """The application's custom authorization link for invitation to a guild""" diff --git a/interactions/models/discord/channel.py b/interactions/models/discord/channel.py index d7d0ff263..539393ff8 100644 --- a/interactions/models/discord/channel.py +++ b/interactions/models/discord/channel.py @@ -833,6 +833,7 @@ async def edit( rtc_region: Absent[Union["models.VoiceRegion", str]] = MISSING, video_quality_mode: Absent[VideoQualityMode] = MISSING, default_auto_archive_duration: Absent[AutoArchiveDuration] = MISSING, + flags: Absent[Union[int, ChannelFlags]] = MISSING, archived: Absent[bool] = MISSING, auto_archive_duration: Absent[AutoArchiveDuration] = MISSING, locked: Absent[bool] = MISSING, @@ -858,6 +859,7 @@ async def edit( rtc_region: Channel voice region id, automatic when set to None. video_quality_mode: The camera video quality mode of the voice channel default_auto_archive_duration: The default duration that the clients use (not the API) for newly created threads in the channel, in minutes, to automatically archive the thread after recent activity + flags: Channel flags combined as a bitfield. Only REQUIRE_TAG is supported for now. archived: Whether the thread is archived auto_archive_duration: Duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080 locked: Whether the thread is locked; when a thread is locked, only users with MANAGE_THREADS can unarchive it @@ -883,6 +885,7 @@ async def edit( "rtc_region": rtc_region.id if isinstance(rtc_region, models.VoiceRegion) else rtc_region, "video_quality_mode": video_quality_mode, "default_auto_archive_duration": default_auto_archive_duration, + "flags": flags, "archived": archived, "auto_archive_duration": auto_archive_duration, "locked": locked, diff --git a/interactions/models/discord/components.py b/interactions/models/discord/components.py index ffc03887f..495874832 100644 --- a/interactions/models/discord/components.py +++ b/interactions/models/discord/components.py @@ -7,7 +7,7 @@ import discord_typings import interactions.models.discord as d_models -from interactions.models.discord.snowflake import Snowflake +from interactions.models.discord.snowflake import Snowflake, Snowflake_Type from interactions.client.const import ACTION_ROW_MAX_ITEMS, MISSING from interactions.client.mixins.serialization import DictSerializationMixin from interactions.models.discord.base import DiscordObject @@ -212,6 +212,7 @@ class Button(InteractiveComponent): label optional[str]: The text that appears on the button, max 80 characters. emoji optional[Union[PartialEmoji, dict, str]]: The emoji that appears on the button. custom_id Optional[str]: A developer-defined identifier for the button, max 100 characters. + sku_id: Optional[Snowflake_Type]: Identifier for a purchasable SKU, only available when using premium-style buttons url Optional[str]: A url for link-style buttons. disabled bool: Disable the button and make it not interactable, default false. @@ -226,6 +227,7 @@ def __init__( label: str | None = None, emoji: "PartialEmoji | None | str" = None, custom_id: str | None = None, + sku_id: Snowflake_Type | None = None, url: str | None = None, disabled: bool = False, ) -> None: @@ -233,6 +235,7 @@ def __init__( self.label: str | None = label self.emoji: "PartialEmoji | None" = emoji self.custom_id: str | None = custom_id + self.sku_id: Snowflake_Type | None = sku_id self.url: str | None = url self.disabled: bool = disabled @@ -244,10 +247,17 @@ def __init__( if self.url is None: raise ValueError("URL buttons must have a url.") + elif self.style == ButtonStyle.PREMIUM: + if any(p is not None for p in (self.custom_id, self.url, self.emoji, self.label)): + raise ValueError("Premium buttons cannot have a custom_id, url, emoji, or label.") + if self.sku_id is None: + raise ValueError("Premium buttons must have a sku_id.") + elif self.custom_id is None: self.custom_id = str(uuid.uuid4()) - if not self.label and not self.emoji: - raise ValueError("Buttons must have a label or an emoji.") + + if self.style != ButtonStyle.PREMIUM and not self.label and not self.emoji: + raise ValueError("Non-premium buttons must have a label or an emoji.") if isinstance(self.emoji, str): self.emoji = PartialEmoji.from_str(self.emoji) @@ -261,12 +271,13 @@ def from_dict(cls, data: discord_typings.ButtonComponentData) -> "Button": label=data.get("label"), emoji=emoji, custom_id=data.get("custom_id"), + sku_id=data.get("sku_id"), url=data.get("url"), disabled=data.get("disabled", False), ) def __repr__(self) -> str: - return f"<{self.__class__.__name__} type={self.type} style={self.style} label={self.label} emoji={self.emoji} custom_id={self.custom_id} url={self.url} disabled={self.disabled}>" + return f"<{self.__class__.__name__} type={self.type} style={self.style} label={self.label} emoji={self.emoji} custom_id={self.custom_id} sku_id={self.sku_id} url={self.url} disabled={self.disabled}>" def to_dict(self) -> discord_typings.ButtonComponentData: emoji = self.emoji.to_dict() if self.emoji else None @@ -279,6 +290,7 @@ def to_dict(self) -> discord_typings.ButtonComponentData: "label": self.label, "emoji": emoji, "custom_id": self.custom_id, + "sku_id": self.sku_id, "url": self.url, "disabled": self.disabled, } diff --git a/interactions/models/discord/entitlement.py b/interactions/models/discord/entitlement.py index bb14ba52f..d4af90fef 100644 --- a/interactions/models/discord/entitlement.py +++ b/interactions/models/discord/entitlement.py @@ -24,8 +24,10 @@ class Entitlement(DiscordObject): """ID of the parent application.""" type: EntitlementType = attrs.field(repr=False, converter=EntitlementType) """The type of entitlement.""" - deleted: bool = attrs.field(repr=False, converter=bool) + deleted: bool = attrs.field(repr=False) """Whether the entitlement is deleted.""" + consumed: bool = attrs.field(repr=False, default=False) + """For consumable items, whether or not the entitlement has been consumed""" subscription_id: Optional[Snowflake_Type] = attrs.field(repr=False, converter=to_optional_snowflake, default=None) """The ID of the subscription plan. Not present when using test entitlements.""" starts_at: Optional[Timestamp] = attrs.field(repr=False, converter=optional_c(timestamp_converter), default=None) diff --git a/interactions/models/discord/enums.py b/interactions/models/discord/enums.py index ca5330c85..07da3d4bd 100644 --- a/interactions/models/discord/enums.py +++ b/interactions/models/discord/enums.py @@ -16,11 +16,13 @@ "ChannelType", "CommandType", "ComponentType", + "ContextType", "DefaultNotificationLevel", "ExplicitContentFilterLevel", "ForumLayoutType", "ForumSortOrder", "IntegrationExpireBehaviour", + "IntegrationType", "Intents", "InteractionPermissionTypes", "InteractionType", @@ -36,6 +38,7 @@ "OnboardingPromptType", "OverwriteType", "Permissions", + "PollLayoutType", "PremiumTier", "PremiumType", "ScheduledEventPrivacyLevel", @@ -389,6 +392,7 @@ class MessageType(CursedIntEnum): GUILD_INCIDENT_ALERT_MODE_DISABLED = 37 GUILD_INCIDENT_REPORT_RAID = 38 GUILD_INCIDENT_REPORT_FALSE_ALARM = 39 + PURCHASE_NOTIFICATION = 44 @classmethod def deletable(cls) -> Tuple["MessageType", ...]: @@ -402,6 +406,10 @@ def deletable(cls) -> Tuple["MessageType", ...]: cls.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2, cls.USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3, cls.CHANNEL_FOLLOW_ADD, + cls.GUILD_DISCOVERY_DISQUALIFIED, + cls.GUILD_DISCOVERY_REQUALIFIED, + cls.GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING, + cls.GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING, cls.THREAD_CREATED, cls.REPLY, cls.APPLICATION_COMMAND, @@ -415,6 +423,11 @@ def deletable(cls) -> Tuple["MessageType", ...]: cls.STAGE_SPEAKER, cls.STAGE_TOPIC, cls.GUILD_APPLICATION_PREMIUM_SUBSCRIPTION, + cls.GUILD_INCIDENT_ALERT_MODE_ENABLED, + cls.GUILD_INCIDENT_ALERT_MODE_DISABLED, + cls.GUILD_INCIDENT_REPORT_RAID, + cls.GUILD_INCIDENT_REPORT_FALSE_ALARM, + cls.PURCHASE_NOTIFICATION, ) @@ -578,6 +591,8 @@ class Permissions(DiscordIntFlag): # type: ignore """Allows the usage of custom sounds from other servers""" SEND_VOICE_MESSAGES = 1 << 46 """Allows for sending audio messages""" + SEND_POLLS = 1 << 49 + """Allows sending polls""" # Shortcuts/grouping/aliases REQUIRES_MFA = ( @@ -661,6 +676,21 @@ class ComponentType(CursedIntEnum): """Select menu for picking from channels""" +class IntegrationType(CursedIntEnum): + """The types of installation contexts supported by discord.""" + + GUILD_INSTALL = 0 + USER_INSTALL = 1 + + +class ContextType(CursedIntEnum): + """The context of where an interaction can be used.""" + + GUILD = 0 + BOT_DM = 1 + PRIVATE_CHANNEL = 2 + + class CommandType(CursedIntEnum): """The interaction commands supported by discord.""" @@ -704,6 +734,8 @@ class ButtonStyle(CursedIntEnum): """red""" LINK = 5 """url button""" + PREMIUM = 6 + """premium button""" # Aliases BLUE = 1 @@ -825,6 +857,8 @@ class SystemChannelFlags(DiscordIntFlag): class ChannelFlags(DiscordIntFlag): PINNED = 1 << 1 """ Thread is pinned to the top of its parent forum channel """ + REQUIRE_TAG = 1 << 4 + """Whether a tag is required to be specified when creating a thread in a Guild Forum or Media channel.""" CLYDE_THREAD = 1 << 8 """This thread was created by Clyde""" HIDE_MEDIA_DOWNLOAD_OPTIONS = 1 << 15 @@ -1099,4 +1133,25 @@ def converter(cls, value: Optional[int]) -> "ForumSortOrder": class EntitlementType(CursedIntEnum): """The type of entitlement.""" + PURCHASE = 1 + """Entitlement was purchased by user""" + PREMIUM_SUBSCRIPTION = 2 + """Entitlement for Discord Nitro subscription""" + DEVELOPER_GIFT = 3 + """Entitlement was gifted by developer""" + TEST_MODE_PURCHASE = 4 + """Entitlement was purchased by a dev in application test mode""" + FREE_PURCHASE = 5 + """Entitlement was granted when the SKU was free""" + USER_GIFT = 6 + """Entitlement was gifted by another user""" + PREMIUM_PURCHASE = 7 + """Entitlement was claimed by user for free as a Nitro Subscriber""" APPLICATION_SUBSCRIPTION = 8 + """Entitlement was purchased as an app subscription""" + + +class PollLayoutType(CursedIntEnum): + """The layout of a poll.""" + + DEFAULT = 1 diff --git a/interactions/models/discord/guild.py b/interactions/models/discord/guild.py index cce6914db..ca73a8ca9 100644 --- a/interactions/models/discord/guild.py +++ b/interactions/models/discord/guild.py @@ -61,6 +61,7 @@ __all__ = ( "GuildBan", + "BulkBanResponse", "BaseGuild", "GuildWelcome", "GuildPreview", @@ -85,6 +86,34 @@ class GuildBan: """The banned user""" +@attrs.define(eq=False, order=False, hash=False, kw_only=True) +class BulkBanResponse(ClientObject): + _banned_users: list[Snowflake_Type] = attrs.field(repr=False, converter=to_snowflake_list) + """List of user IDs that were successfully banned.""" + _failed_users: list[Snowflake_Type] = attrs.field(repr=False, converter=to_snowflake_list) + """List of user IDs that were not banned.""" + + @property + def banned_users(self) -> List["models.User | None"]: + """List of users that were successfully banned.""" + return [self.client.cache.get_user(u_id) for u_id in self._banned_users] + + @property + def failed_users(self) -> List["models.User | None"]: + """List of users that were not banned.""" + return [self.client.cache.get_user(u_id) for u_id in self._failed_users] + + @property + def failed_user_ids(self) -> List[Snowflake_Type]: + """List of user IDs that were not banned.""" + return self._failed_users + + @property + def banned_user_ids(self) -> List[Snowflake_Type]: + """List of user IDs that were successfully banned.""" + return self._banned_users + + @attrs.define(eq=False, order=False, hash=False, kw_only=True) class BaseGuild(DiscordObject): name: str = attrs.field(repr=True) @@ -1748,6 +1777,29 @@ async def ban( delete_message_seconds = delete_message_days * 3600 await self._client.http.create_guild_ban(self.id, to_snowflake(user), delete_message_seconds, reason=reason) + async def bulk_ban( + self, + users: List[Union["models.User", "models.Member", Snowflake_Type]], + delete_message_seconds: int = 0, + reason: Optional[str] = None, + ) -> BulkBanResponse: + """ + Bans a list of users from the guild. + + !!! note + You must have the `ban members` permission + + Args: + user: The users to ban + delete_message_seconds: How many seconds worth of messages to remove + reason: The reason for the ban + + """ + result = await self.client.http.bulk_guild_ban( + self.id, [to_snowflake(user) for user in users], delete_message_seconds, reason=reason + ) + return BulkBanResponse.from_dict(result, self.client) + async def fetch_ban(self, user: Union["models.User", "models.Member", Snowflake_Type]) -> Optional[GuildBan]: """ Fetches the ban information for the specified user in the guild. You must have the `ban members` permission. diff --git a/interactions/models/discord/message.py b/interactions/models/discord/message.py index 9627a1b56..56a051a6b 100644 --- a/interactions/models/discord/message.py +++ b/interactions/models/discord/message.py @@ -1,6 +1,7 @@ import asyncio import base64 import re +from collections import namedtuple from dataclasses import dataclass from typing import ( TYPE_CHECKING, @@ -28,6 +29,8 @@ from interactions.models.discord.embed import process_embeds from interactions.models.discord.emoji import process_emoji_req_format from interactions.models.discord.file import UPLOADABLE_TYPE +from interactions.models.discord.poll import Poll +from interactions.models.misc.iterator import AsyncIterator from .base import DiscordObject from .enums import ( @@ -38,8 +41,10 @@ MessageActivityType, MessageFlags, MessageType, + IntegrationType, ) from .snowflake import ( + Snowflake, Snowflake_Type, to_optional_snowflake, to_snowflake, @@ -56,6 +61,7 @@ "MessageActivity", "MessageReference", "MessageInteraction", + "MessageInteractionMetadata", "AllowedMentions", "BaseMessage", "Message", @@ -68,6 +74,35 @@ channel_mention = re.compile(r"<#(?P[0-9]{17,})>") +class PollAnswerVotersIterator(AsyncIterator): + def __init__( + self, message: "Message", answer_id: int, limit: int = 25, after: Snowflake_Type | None = None + ) -> None: + self.message: "Message" = message + self.answer_id = answer_id + self.after: Snowflake_Type | None = after + self._more: bool = True + super().__init__(limit) + + async def fetch(self) -> list["models.User"]: + if not self.last: + self.last = namedtuple("temp", "id") + self.last.id = self.after + + rcv = await self.message._client.http.get_answer_voters( + self.message._channel_id, + self.message.id, + self.answer_id, + limit=self.get_limit, + after=to_snowflake(self.last.id) if self.last.id else None, + ) + if not rcv: + raise asyncio.QueueEmpty + + users = [self.message._client.cache.place_user_data(user_data) for user_data in rcv["users"]] + return users + + @attrs.define(eq=False, order=False, hash=False, kw_only=True) class Attachment(DiscordObject): filename: str = attrs.field( @@ -186,6 +221,7 @@ class MessageInteraction(DiscordObject): _user_id: "Snowflake_Type" = attrs.field( repr=False, ) + """ID of the user who triggered the interaction""" @classmethod def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]: @@ -199,6 +235,49 @@ def user(self) -> "models.User": return self.client.get_user(self._user_id) +@attrs.define(eq=False, order=False, hash=False, kw_only=True) +class MessageInteractionMetadata(DiscordObject): + type: InteractionType = attrs.field(repr=False, converter=InteractionType) + """The type of interaction""" + authorizing_integration_owners: dict[IntegrationType, Snowflake] = attrs.field(repr=False, factory=dict) + """IDs for installation context(s) related to an interaction.""" + original_response_message_id: "Optional[Snowflake_Type]" = attrs.field( + repr=False, default=None, converter=to_optional_snowflake + ) + """ID of the original response message, present only on follow-up messages""" + interacted_message_id: "Optional[Snowflake_Type]" = attrs.field( + repr=False, default=None, converter=to_optional_snowflake + ) + """ID of the message that contained interactive component, present only on messages created from component interactions""" + triggering_interaction_metadata: "Optional[MessageInteractionMetadata]" = attrs.field(repr=False, default=None) + """Metadata for the interaction that was used to open the modal, present only on modal submit interactions""" + + _user_id: "Snowflake_Type" = attrs.field( + repr=False, + ) + """ID of the user who triggered the interaction""" + + @classmethod + def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]: + if "authorizing_integration_owners" in data: + data["authorizing_integration_owners"] = { + IntegrationType(int(integration_type)): Snowflake(owner_id) + for integration_type, owner_id in data["authorizing_integration_owners"].items() + } + if "triggering_interaction_metadata" in data: + data["triggering_interaction_metadata"] = cls.from_dict(data["triggering_interaction_metadata"], client) + + user_data = data["user"] + data["user_id"] = client.cache.place_user_data(user_data).id + + return data + + @property + def user(self) -> "models.User": + """Get the user associated with this interaction.""" + return self.client.get_user(self.user_id) + + @attrs.define(eq=False, order=False, hash=False, kw_only=False) class AllowedMentions(DictSerializationMixin): """ @@ -360,8 +439,12 @@ class Message(BaseMessage): """Data showing the source of a crosspost, channel follow add, pin, or reply message""" flags: MessageFlags = attrs.field(repr=False, default=MessageFlags.NONE, converter=MessageFlags) """Message flags combined as a bitfield""" - interaction: Optional["MessageInteraction"] = attrs.field(repr=False, default=None) + poll: Optional[Poll] = attrs.field(repr=False, default=None, converter=optional_c(Poll.from_dict)) + """A poll.""" + interaction_metadata: Optional[MessageInteractionMetadata] = attrs.field(repr=False, default=None) """Sent if the message is a response to an Interaction""" + interaction: Optional["MessageInteraction"] = attrs.field(repr=False, default=None) + """(Deprecated in favor of interaction_metadata) Sent if the message is a response to an Interaction""" components: Optional[List["models.ActionRow"]] = attrs.field(repr=False, default=None) """Sent if the message contains components like buttons, action rows, or other interactive components""" sticker_items: Optional[List["models.StickerItem"]] = attrs.field(repr=False, default=None) @@ -510,6 +593,9 @@ def _process_dict(cls, data: dict, client: "Client") -> dict: # noqa: C901 elif msg_reference := data.get("message_reference"): data["referenced_message_id"] = msg_reference.get("message_id") + if "interaction_metadata" in data: + data["interaction_metadata"] = MessageInteractionMetadata.from_dict(data["interaction_metadata"], client) + if "interaction" in data: data["interaction"] = MessageInteraction.from_dict(data["interaction"], client) @@ -592,6 +678,20 @@ def proto_url(self) -> str: """A URL like `jump_url` that uses protocols.""" return f"discord://-/channels/{self._guild_id or '@me'}/{self._channel_id}/{self.id}" + def answer_voters( + self, answer_id: int, limit: int = 0, before: Snowflake_Type | None = None + ) -> PollAnswerVotersIterator: + """ + An async iterator for getting the voters for an answer in the poll this message has. + + Args: + answer_id: The answer to get voters for + after: Get messages after this user ID + limit: The max number of users to return (default 25, max 100) + + """ + return PollAnswerVotersIterator(self, answer_id, limit, before) + async def edit( self, *, @@ -649,7 +749,7 @@ async def edit( ) message_payload = process_message_payload( content=content, - embeds=embeds or embed, + embeds=embed if embeds is None else embeds, components=components, allowed_mentions=allowed_mentions, attachments=attachments, @@ -847,6 +947,12 @@ async def publish(self) -> None: """ await self._client.http.crosspost_message(self._channel_id, self.id) + async def end_poll(self) -> "Message": + """Ends the poll contained in this message.""" + message_data = await self._client.http.end_poll(self._channel_id, self.id) + if message_data: + return self._client.cache.place_message_data(message_data) + def process_allowed_mentions(allowed_mentions: Optional[Union[AllowedMentions, dict]]) -> Optional[dict]: """ @@ -929,6 +1035,7 @@ def process_message_payload( flags: Optional[Union[int, MessageFlags]] = None, nonce: Optional[str | int] = None, enforce_nonce: bool = False, + poll: Optional[Poll | dict] = None, **kwargs, ) -> dict: """ @@ -948,6 +1055,7 @@ def process_message_payload( enforce_nonce: If enabled and nonce is present, it will be checked for uniqueness in the past few minutes. \ If another message was created by the same author with the same nonce, that message will be returned \ and no new message will be created. + poll: A poll. Returns: Dictionary @@ -965,6 +1073,9 @@ def process_message_payload( if attachments: attachments = [attachment.to_dict() for attachment in attachments] + if isinstance(poll, Poll): + poll = poll.to_dict() + return dict_filter_none( { "content": content, @@ -978,6 +1089,7 @@ def process_message_payload( "flags": flags, "nonce": nonce, "enforce_nonce": enforce_nonce, + "poll": poll, **kwargs, } ) diff --git a/interactions/models/discord/poll.py b/interactions/models/discord/poll.py new file mode 100644 index 000000000..1656d7e23 --- /dev/null +++ b/interactions/models/discord/poll.py @@ -0,0 +1,184 @@ +from typing import Optional, Union, Dict, Any +from typing_extensions import Self + +import attrs + +from interactions.client.const import MISSING, POLL_MAX_DURATION_HOURS, POLL_MAX_ANSWERS +from interactions.client.utils.attr_converters import ( + optional, + timestamp_converter, +) +from interactions.client.mixins.serialization import DictSerializationMixin +from interactions.client.utils.serializer import no_export_meta +from interactions.models.discord.emoji import PartialEmoji, process_emoji +from interactions.models.discord.enums import PollLayoutType +from interactions.models.discord.timestamp import Timestamp + +__all__ = ( + "PollMedia", + "PollAnswer", + "PollAnswerCount", + "PollResults", + "Poll", +) + + +@attrs.define(eq=False, order=False, hash=False, kw_only=True) +class PollMedia(DictSerializationMixin): + text: Optional[str] = attrs.field(repr=False, default=None) + """ + The text of the field. + + !!! warning + While `text` is *marked* as optional, it is *currently required* by Discord's API to make polls. + According to Discord, this may change to be actually optional in the future. + """ + emoji: Optional[PartialEmoji] = attrs.field(repr=False, default=None, converter=optional(PartialEmoji.from_dict)) + """The emoji of the field.""" + + @classmethod + def create(cls, *, text: Optional[str] = None, emoji: Optional[Union[PartialEmoji, dict, str]] = None) -> Self: + """ + Create a PollMedia object, used for questions and answers for polls. + + !!! warning + While `text` is *marked* as optional, it is *currently required* by Discord's API to make polls. + According to Discord, this may change to be actually optional in the future. + + Args: + text: The text of the field. + emoji: The emoji of the field. + + Returns: + A PollMedia object. + + """ + if not text and not emoji: + raise ValueError("Either text or emoji must be provided.") + + return cls(text=text, emoji=process_emoji(emoji)) + + +@attrs.define(eq=False, order=False, hash=False, kw_only=True) +class PollAnswer(DictSerializationMixin): + poll_media: PollMedia = attrs.field(repr=False, converter=PollMedia.from_dict) + """The data of the answer.""" + answer_id: Optional[int] = attrs.field(repr=False, default=None) + """The ID of the answer. This is only returned for polls that have been given by Discord's API.""" + + +@attrs.define(eq=False, order=False, hash=False, kw_only=True) +class PollAnswerCount(DictSerializationMixin): + id: int = attrs.field(repr=False) + """The answer ID of the answer.""" + count: int = attrs.field(repr=False, default=0) + """The number of votes for this answer.""" + me_voted: bool = attrs.field(repr=False, default=False) + """Whether the current user voted for this answer.""" + + +@attrs.define(eq=False, order=False, hash=False, kw_only=True) +class PollResults(DictSerializationMixin): + is_finalized: bool = attrs.field(repr=False, default=False) + """Whether the votes have been precisely counted.""" + answer_counts: list[PollAnswerCount] = attrs.field(repr=False, factory=list, converter=PollAnswerCount.from_list) + """The counts for each answer.""" + + +@attrs.define(eq=False, order=False, hash=False, kw_only=True) +class Poll(DictSerializationMixin): + question: PollMedia = attrs.field(repr=False) + """The question of the poll. Only text media is supported.""" + answers: list[PollAnswer] = attrs.field(repr=False, factory=list, converter=PollAnswer.from_list) + """Each of the answers available in the poll, up to 10.""" + expiry: Timestamp = attrs.field(repr=False, default=MISSING, converter=optional(timestamp_converter)) + """Number of hours the poll is open for, up to 7 days.""" + allow_multiselect: bool = attrs.field(repr=False, default=False, metadata=no_export_meta) + """Whether a user can select multiple answers.""" + layout_type: PollLayoutType = attrs.field(repr=False, default=PollLayoutType.DEFAULT, converter=PollLayoutType) + """The layout type of the poll.""" + results: Optional[PollResults] = attrs.field(repr=False, default=None, converter=optional(PollResults.from_dict)) + """The results of the poll, if the polls is finished.""" + + _duration: int = attrs.field(repr=False, default=0) + """How long, in hours, the poll will be open for (up to 7 days). This is only used when creating polls.""" + + @classmethod + def create( + cls, + question: str, + *, + duration: int, + allow_multiselect: bool = False, + answers: Optional[list[PollMedia | str]] = None, + ) -> Self: + """ + Create a Poll object for sending. + + Args: + question: The question of the poll. + duration: How long, in hours, the poll will be open for (up to 7 days). + allow_multiselect: Whether a user can select multiple answers. + answers: Each of the answers available in the poll, up to 10. + + Returns: + A Poll object. + + """ + if answers: + media_to_answers = [ + ( + PollAnswer(poll_media=answer) + if isinstance(answer, PollMedia) + else PollAnswer(poll_media=PollMedia.create(text=answer)) + ) + for answer in answers + ] + else: + media_to_answers = [] + + return cls( + question=PollMedia(text=question), + duration=duration, + allow_multiselect=allow_multiselect, + answers=media_to_answers, + ) + + @answers.validator + def _answers_validation(self, attribute: str, value: Any) -> None: + if len(value) > POLL_MAX_ANSWERS: + raise ValueError(f"A poll can have at most {POLL_MAX_ANSWERS} answers.") + + @_duration.validator + def _duration_validation(self, attribute: str, value: int) -> None: + if value < 0 or value > POLL_MAX_DURATION_HOURS: + raise ValueError( + f"The duration must be between 0 and {POLL_MAX_DURATION_HOURS} hours ({POLL_MAX_DURATION_HOURS // 24} days)." + ) + + def add_answer(self, text: Optional[str] = None, emoji: Optional[Union[PartialEmoji, dict, str]] = None) -> Self: + """ + Adds an answer to the poll. + + !!! warning + While `text` is *marked* as optional, it is *currently required* by Discord's API to make polls. + According to Discord, this may change to be actually optional in the future. + + Args: + text: The text of the answer. + emoji: The emoji for the answer. + + """ + if not text and not emoji: + raise ValueError("Either text or emoji must be provided") + + self.answers.append(PollAnswer(poll_media=PollMedia.create(text=text, emoji=emoji))) + self._answers_validation("answers", self.answers) + return self + + def to_dict(self) -> Dict[str, Any]: + data = super().to_dict() + + data["duration"] = self._duration + data.pop("_duration", None) + return data diff --git a/interactions/models/discord/timestamp.py b/interactions/models/discord/timestamp.py index 2a81e819a..f8d1b81fa 100644 --- a/interactions/models/discord/timestamp.py +++ b/interactions/models/discord/timestamp.py @@ -99,12 +99,15 @@ def astimezone(self, tz: tzinfo | None = None) -> "Timestamp": if self.year > 1970 or (self.year == 1970 and (self.month > 1 or self.day > 1)): return super().astimezone(tz) - if self.year < 1969 or self.month < 12 or self.day < 31: + if self.year < 1969 or (self.year == 1969 and (self.month < 12 or self.day < 31)): # windows kind of breaks down for dates before unix time # technically this is solvable, but it's not worth the effort # also, again, this is a loose bound, but it's good enough for our purposes raise ValueError("astimezone with no arguments is not supported for dates before Unix Time on Windows.") + if tz: + return self.replace(tzinfo=tz) + # to work around the issue to some extent, we'll use a timestamp with a date # that doesn't trigger the bug, and use the timezone from it to modify this # timestamp diff --git a/interactions/models/discord/webhooks.py b/interactions/models/discord/webhooks.py index 4d88f8534..548eaec9f 100644 --- a/interactions/models/discord/webhooks.py +++ b/interactions/models/discord/webhooks.py @@ -1,3 +1,4 @@ +import asyncio import re from enum import IntEnum from typing import Optional, TYPE_CHECKING, Union, Dict, Any, List @@ -5,7 +6,7 @@ import attrs from interactions.client.const import MISSING, Absent -from interactions.client.errors import ForeignWebhookException, EmptyMessageException +from interactions.client.errors import ForeignWebhookException, EmptyMessageException, NotFound from interactions.client.mixins.send import SendMixin from interactions.client.utils.serializer import to_image_data from interactions.models.discord.message import process_message_payload @@ -26,6 +27,7 @@ Message, MessageReference, ) + from interactions.models.discord.poll import Poll from interactions.models.discord.sticker import Sticker __all__ = ("WebhookTypes", "Webhook") @@ -189,6 +191,7 @@ async def send( tts: bool = False, suppress_embeds: bool = False, flags: Optional[Union[int, "MessageFlags"]] = None, + poll: "Optional[Poll | dict]" = None, username: str | None = None, avatar_url: str | None = None, wait: bool = False, @@ -211,6 +214,7 @@ async def send( tts: Should this message use Text To Speech. suppress_embeds: Should embeds be suppressed on this send flags: Message flags to apply. + poll: A poll. username: The username to use avatar_url: The url of an image to use as the avatar wait: Waits for confirmation of delivery. Set this to True if you intend to edit the message @@ -240,6 +244,7 @@ async def send( reply_to=reply_to, tts=tts, flags=flags, + poll=poll, username=username, avatar_url=avatar_url, **kwargs, @@ -256,6 +261,24 @@ async def send( if message_data: return self._client.cache.place_message_data(message_data) + async def fetch_message(self, message_id: Union["Message", "Snowflake_Type"]) -> Optional["Message"]: + """ + Returns a previously-sent webhook message from the same token. Returns a message object on success. + + Args: + message_id: ID of message to retrieve. + + Returns: + The message object fetched. If the message is not found, returns None. + + """ + message_id = to_snowflake(message_id) + try: + msg_data = await self._client.http.get_webhook_message(self.id, self.token, message_id) + except NotFound: + return None + return self._client.cache.place_message_data(msg_data) + async def edit_message( self, message: Union["Message", "Snowflake_Type"], @@ -313,3 +336,29 @@ async def edit_message( ) if msg_data: return self._client.cache.place_message_data(msg_data) + + async def delete_message( + self, + message: Union["Message", "Snowflake_Type"], + *, + delay: int = 0, + ) -> None: + """ + Delete a message as this webhook. + + Args: + message: Message to delete + delay: Seconds to wait before deleting message. + + """ + + async def _delete() -> None: + if delay: + await asyncio.sleep(delay) + + await self._client.http.delete_webhook_message(self.id, self.token, to_snowflake(message)) + + if delay: + return asyncio.create_task(_delete()) + + return await _delete() diff --git a/interactions/models/internal/__init__.py b/interactions/models/internal/__init__.py index 1764ef28f..92652dae9 100644 --- a/interactions/models/internal/__init__.py +++ b/interactions/models/internal/__init__.py @@ -18,12 +18,14 @@ CallbackType, component_callback, ComponentCommand, + contexts, context_menu, user_context_menu, message_context_menu, ContextMenu, global_autocomplete, GlobalAutoComplete, + integration_types, InteractionCommand, LocalisedDesc, LocalisedName, @@ -122,6 +124,7 @@ "component_callback", "ComponentCommand", "ComponentContext", + "contexts", "context_menu", "user_context_menu", "message_context_menu", @@ -162,6 +165,7 @@ "has_id", "has_role", "IDConverter", + "integration_types", "InteractionCommand", "InteractionContext", "IntervalTrigger", diff --git a/interactions/models/internal/application_commands.py b/interactions/models/internal/application_commands.py index c404d055f..c0f2b1b8b 100644 --- a/interactions/models/internal/application_commands.py +++ b/interactions/models/internal/application_commands.py @@ -36,7 +36,7 @@ from interactions.client.utils.attr_utils import attrs_validator, docs from interactions.client.utils.misc_utils import get_parameters, maybe_coroutine from interactions.client.utils.serializer import no_export_meta -from interactions.models.discord.enums import ChannelType, CommandType, Permissions +from interactions.models.discord.enums import ChannelType, CommandType, ContextType, IntegrationType, Permissions from interactions.models.discord.role import Role from interactions.models.discord.snowflake import to_snowflake_list, to_snowflake from interactions.models.discord.user import BaseUser @@ -57,12 +57,14 @@ "CallbackType", "component_callback", "ComponentCommand", + "contexts", "context_menu", "user_context_menu", "message_context_menu", "ContextMenu", "global_autocomplete", "GlobalAutoComplete", + "integration_types", "InteractionCommand", "LocalisedDesc", "LocalisedName", @@ -221,7 +223,7 @@ class InteractionCommand(BaseCommand): converter=LocalisedName.converter, ) scopes: List["Snowflake_Type"] = attrs.field( - default=[GLOBAL_SCOPE], + factory=lambda: [GLOBAL_SCOPE], converter=to_snowflake_list, metadata=docs("The scopes of this interaction. Global or guild ids") | no_export_meta, ) @@ -230,7 +232,9 @@ class InteractionCommand(BaseCommand): default=None, metadata=docs("What permissions members need to have by default to use this command"), ) - dm_permission: bool = attrs.field(repr=False, default=True, metadata=docs("Whether this command is enabled in DMs")) + dm_permission: bool = attrs.field( + repr=False, default=True, metadata=docs("Whether this command is enabled in DMs (deprecated)") | no_export_meta + ) cmd_id: Dict[str, "Snowflake_Type"] = attrs.field( repr=False, factory=dict, metadata=docs("The unique IDs of this commands") | no_export_meta ) # scope: cmd_id @@ -244,14 +248,43 @@ class InteractionCommand(BaseCommand): metadata=docs("A system to automatically defer this command after a set duration") | no_export_meta, ) nsfw: bool = attrs.field(repr=False, default=False, metadata=docs("This command should only work in NSFW channels")) + integration_types: list[Union[IntegrationType, int]] = attrs.field( + factory=lambda: [IntegrationType.GUILD_INSTALL], + repr=False, + metadata=docs("Installation context(s) where the command is available, only for globally-scoped commands."), + ) + contexts: list[Union[ContextType, int]] = attrs.field( + factory=lambda: [ContextType.GUILD, ContextType.BOT_DM, ContextType.PRIVATE_CHANNEL], + repr=False, + metadata=docs("Interaction context(s) where the command can be used, only for globally-scoped commands."), + ) _application_id: "Snowflake_Type" = attrs.field(repr=False, default=None, converter=optional(to_snowflake)) def __attrs_post_init__(self) -> None: - if self.callback is not None and hasattr(self.callback, "auto_defer"): - self.auto_defer = self.callback.auto_defer + if self.callback is not None: + if hasattr(self.callback, "auto_defer"): + self.auto_defer = self.callback.auto_defer + if hasattr(self.callback, "integration_types"): + self.integration_types = self.callback.integration_types + if hasattr(self.callback, "contexts"): + self.contexts = self.callback.contexts super().__attrs_post_init__() + @dm_permission.validator + def _dm_permission_validator(self, attribute: str, value: bool) -> None: + # since dm_permission is deprecated, ipy transforms it into something that isn't + if not value: + try: + self.contexts.remove(ContextType.PRIVATE_CHANNEL) + except ValueError: + pass + + try: + self.contexts.remove(ContextType.BOT_DM) + except ValueError: + pass + def to_dict(self) -> dict: data = super().to_dict() @@ -754,6 +787,8 @@ def group( group_description=description, scopes=self.scopes, default_member_permissions=self.default_member_permissions, + integration_types=self.integration_types, + contexts=self.contexts, dm_permission=self.dm_permission, checks=self.checks.copy() if inherit_checks else [], ) @@ -787,6 +822,8 @@ def wrapper(call: Callable[..., Coroutine]) -> "SlashCommand": sub_cmd_name=sub_cmd_name, sub_cmd_description=sub_cmd_description, default_member_permissions=self.default_member_permissions, + integration_types=self.integration_types, + contexts=self.contexts, dm_permission=self.dm_permission, options=options, callback=call, @@ -912,6 +949,8 @@ def slash_command( scopes: Absent[List["Snowflake_Type"]] = MISSING, options: Optional[List[Union[SlashCommandOption, Dict]]] = None, default_member_permissions: Optional["Permissions"] = None, + integration_types: Optional[List[Union[IntegrationType, int]]] = None, + contexts: Optional[List[Union[ContextType, int]]] = None, dm_permission: bool = True, sub_cmd_name: str | LocalisedName = None, group_name: str | LocalisedName = None, @@ -933,7 +972,9 @@ def slash_command( scopes: The scope this command exists within options: The parameters for the command, max 25 default_member_permissions: What permissions members need to have by default to use this command. - dm_permission: Should this command be available in DMs. + integration_types: Installation context(s) where the command is available, only for globally-scoped commands. + contexts: Interaction context(s) where the command can be used, only for globally-scoped commands. + dm_permission: Should this command be available in DMs (deprecated). sub_cmd_name: 1-32 character name of the subcommand sub_cmd_description: 1-100 character description of the subcommand group_name: 1-32 character name of the group @@ -973,6 +1014,8 @@ def wrapper(func: AsyncCallable) -> SlashCommand: description=_description, scopes=scopes or [GLOBAL_SCOPE], default_member_permissions=perm, + integration_types=integration_types or [IntegrationType.GUILD_INSTALL], + contexts=contexts or [ContextType.GUILD, ContextType.BOT_DM, ContextType.PRIVATE_CHANNEL], dm_permission=dm_permission, callback=func, options=options, @@ -993,6 +1036,8 @@ def subcommand( base_description: Optional[str | LocalisedDesc] = None, base_desc: Optional[str | LocalisedDesc] = None, base_default_member_permissions: Optional["Permissions"] = None, + base_integration_types: Optional[List[Union[IntegrationType, int]]] = None, + base_contexts: Optional[List[Union[ContextType, int]]] = None, base_dm_permission: bool = True, subcommand_group_description: Optional[str | LocalisedDesc] = None, sub_group_desc: Optional[str | LocalisedDesc] = None, @@ -1011,7 +1056,9 @@ def subcommand( base_description: The description of the base command base_desc: An alias of `base_description` base_default_member_permissions: What permissions members need to have by default to use this command. - base_dm_permission: Should this command be available in DMs. + base_integration_types: Installation context(s) where the command is available, only for globally-scoped commands. + base_contexts: Interaction context(s) where the command can be used, only for globally-scoped commands. + base_dm_permission: Should this command be available in DMs (deprecated). subcommand_group_description: Description of the subcommand group sub_group_desc: An alias for `subcommand_group_description` scopes: The scopes of which this command is available, defaults to GLOBAL_SCOPE @@ -1043,6 +1090,8 @@ def wrapper(func: AsyncCallable) -> SlashCommand: sub_cmd_name=_name, sub_cmd_description=_description, default_member_permissions=base_default_member_permissions, + integration_types=base_integration_types or [IntegrationType.GUILD_INSTALL], + contexts=base_contexts or [ContextType.GUILD, ContextType.BOT_DM, ContextType.PRIVATE_CHANNEL], dm_permission=base_dm_permission, scopes=scopes or [GLOBAL_SCOPE], callback=func, @@ -1060,6 +1109,8 @@ def context_menu( context_type: "CommandType", scopes: Absent[List["Snowflake_Type"]] = MISSING, default_member_permissions: Optional["Permissions"] = None, + integration_types: Optional[List[Union[IntegrationType, int]]] = None, + contexts: Optional[List[Union[ContextType, int]]] = None, dm_permission: bool = True, ) -> Callable[[AsyncCallable], ContextMenu]: """ @@ -1070,7 +1121,9 @@ def context_menu( context_type: The type of context menu scopes: The scope this command exists within default_member_permissions: What permissions members need to have by default to use this command. - dm_permission: Should this command be available in DMs. + integration_types: Installation context(s) where the command is available, only for globally-scoped commands. + contexts: Interaction context(s) where the command can be used, only for globally-scoped commands. + dm_permission: Should this command be available in DMs (deprecated). Returns: ContextMenu object @@ -1097,6 +1150,8 @@ def wrapper(func: AsyncCallable) -> ContextMenu: type=context_type, scopes=scopes or [GLOBAL_SCOPE], default_member_permissions=perm, + integration_types=integration_types or [IntegrationType.GUILD_INSTALL], + contexts=contexts or [ContextType.GUILD, ContextType.BOT_DM, ContextType.PRIVATE_CHANNEL], dm_permission=dm_permission, callback=func, ) @@ -1110,6 +1165,8 @@ def user_context_menu( *, scopes: Absent[List["Snowflake_Type"]] = MISSING, default_member_permissions: Optional["Permissions"] = None, + integration_types: Optional[List[Union[IntegrationType, int]]] = None, + contexts: Optional[List[Union[ContextType, int]]] = None, dm_permission: bool = True, ) -> Callable[[AsyncCallable], ContextMenu]: """ @@ -1119,7 +1176,9 @@ def user_context_menu( name: 1-32 character name of the context menu, defaults to the name of the coroutine. scopes: The scope this command exists within default_member_permissions: What permissions members need to have by default to use this command. - dm_permission: Should this command be available in DMs. + integration_types: Installation context(s) where the command is available, only for globally-scoped commands. + contexts: Interaction context(s) where the command can be used, only for globally-scoped commands. + dm_permission: Should this command be available in DMs (deprecated). Returns: ContextMenu object @@ -1130,6 +1189,8 @@ def user_context_menu( context_type=CommandType.USER, scopes=scopes, default_member_permissions=default_member_permissions, + integration_types=integration_types or [IntegrationType.GUILD_INSTALL], + contexts=contexts or [ContextType.GUILD, ContextType.BOT_DM, ContextType.PRIVATE_CHANNEL], dm_permission=dm_permission, ) @@ -1139,6 +1200,8 @@ def message_context_menu( *, scopes: Absent[List["Snowflake_Type"]] = MISSING, default_member_permissions: Optional["Permissions"] = None, + integration_types: Optional[List[Union[IntegrationType, int]]] = None, + contexts: Optional[List[Union[ContextType, int]]] = None, dm_permission: bool = True, ) -> Callable[[AsyncCallable], ContextMenu]: """ @@ -1148,7 +1211,9 @@ def message_context_menu( name: 1-32 character name of the context menu, defaults to the name of the coroutine. scopes: The scope this command exists within default_member_permissions: What permissions members need to have by default to use this command. - dm_permission: Should this command be available in DMs. + integration_types: Installation context(s) where the command is available, only for globally-scoped commands. + contexts: Interaction context(s) where the command can be used, only for globally-scoped commands. + dm_permission: Should this command be available in DMs (deprecated). Returns: ContextMenu object @@ -1159,6 +1224,8 @@ def message_context_menu( context_type=CommandType.MESSAGE, scopes=scopes, default_member_permissions=default_member_permissions, + integration_types=integration_types or [IntegrationType.GUILD_INSTALL], + contexts=contexts or [ContextType.GUILD, ContextType.BOT_DM, ContextType.PRIVATE_CHANNEL], dm_permission=dm_permission, ) @@ -1337,6 +1404,59 @@ def wrapper(func: InterCommandT) -> InterCommandT: return wrapper +def integration_types(guild: bool = True, user: bool = False) -> Callable[[InterCommandT], InterCommandT]: + """ + A decorator to set integration types for an application command. + + Args: + guild: Should the command be available for guilds + user: Should the command be available for individual users + + """ + kwargs = locals() + + def wrapper(func: InterCommandT) -> InterCommandT: + if hasattr(func, "cmd_id"): + raise ValueError("integration_types decorators must be positioned under a command decorator") + + func.integration_types = [] + for key in kwargs: + if kwargs[key]: + func.integration_types.append(IntegrationType[key.upper() + "_INSTALL"]) + + return func + + return wrapper + + +def contexts( + guild: bool = True, bot_dm: bool = True, private_channel: bool = True +) -> Callable[[InterCommandT], InterCommandT]: + """ + A decorator to set contexts where the command can be used for a application command. + + Args: + guild: Should the command be available in guilds + bot_dm: Should the command be available in bot DMs + private_channel: Should the command be available in private channels + + """ + kwargs = locals() + + def wrapper(func: InterCommandT) -> InterCommandT: + if hasattr(func, "cmd_id"): + raise ValueError("contexts decorators must be positioned under a command decorator") + + func.contexts = [] + for key in kwargs: + if kwargs[key]: + func.contexts.append(ContextType[key.upper()]) + + return func + + return wrapper + + def application_commands_to_dict( # noqa: C901 commands: Dict["Snowflake_Type", Dict[str, InteractionCommand]], client: "Client" ) -> dict: @@ -1366,7 +1486,8 @@ def squash_subcommand(subcommands: List) -> Dict: if subcommand.default_member_permissions else None ), - "dm_permission": subcommand.dm_permission, + "integration_types": subcommand.integration_types, + "contexts": subcommand.contexts, "name_localizations": subcommand.name.to_locale_dict(), "description_localizations": subcommand.description.to_locale_dict(), "nsfw": subcommand.nsfw, @@ -1419,6 +1540,10 @@ def squash_subcommand(subcommands: List) -> Dict: ) if any(c.default_member_permissions != cmd_list[0].default_member_permissions for c in cmd_list): raise ValueError(f"Conflicting `default_member_permissions` values found in `{cmd_list[0].name}`") + if any(c.contexts != cmd_list[0].contexts for c in cmd_list): + raise ValueError(f"Conflicting `contexts` values found in `{cmd_list[0].name}`") + if any(c.integration_types != cmd_list[0].integration_types for c in cmd_list): + raise ValueError(f"Conflicting `integration_types` values found in `{cmd_list[0].name}`") if any(c.dm_permission != cmd_list[0].dm_permission for c in cmd_list): raise ValueError(f"Conflicting `dm_permission` values found in `{cmd_list[0].name}`") if any(c.nsfw != nsfw for c in cmd_list): @@ -1458,13 +1583,14 @@ def _compare_commands(local_cmd: dict, remote_cmd: dict) -> bool: "name": ("name", ""), "description": ("description", ""), "default_member_permissions": ("default_member_permissions", None), - "dm_permission": ("dm_permission", True), "name_localized": ("name_localizations", None), "description_localized": ("description_localizations", None), + "integration_types": ("integration_types", [IntegrationType.GUILD_INSTALL]), + "contexts": ("contexts", [ContextType.GUILD, ContextType.BOT_DM, ContextType.PRIVATE_CHANNEL]), } if remote_cmd.get("guild_id"): # non-global command - del lookup["dm_permission"] + del lookup["contexts"] for local_name, comparison_data in lookup.items(): remote_name, default_value = comparison_data diff --git a/interactions/models/internal/context.py b/interactions/models/internal/context.py index c63d80e16..775e80f06 100644 --- a/interactions/models/internal/context.py +++ b/interactions/models/internal/context.py @@ -9,9 +9,10 @@ from aiohttp import FormData from interactions.client import const -from interactions.client.const import get_logger, MISSING +from interactions.client.const import get_logger, MISSING, ClientT from interactions.models.discord.components import BaseComponent from interactions.models.discord.file import UPLOADABLE_TYPE +from interactions.models.discord.poll import Poll from interactions.models.discord.sticker import Sticker from interactions.models.discord.user import Member, User @@ -27,6 +28,8 @@ InteractionType, ComponentType, CommandType, + ContextType, + IntegrationType, ) from interactions.models.discord.message import ( AllowedMentions, @@ -146,7 +149,7 @@ def from_dict(cls, client: "interactions.Client", data: dict, guild_id: None | S return instance -class BaseContext(metaclass=abc.ABCMeta): +class BaseContext(typing.Generic[ClientT], metaclass=abc.ABCMeta): """ Base context class for all contexts. @@ -154,9 +157,6 @@ class BaseContext(metaclass=abc.ABCMeta): """ - client: "interactions.Client" - """The client that created this context.""" - command: BaseCommand """The command this context invokes.""" @@ -170,8 +170,10 @@ class BaseContext(metaclass=abc.ABCMeta): guild_id: typing.Optional[Snowflake] """The id of the guild this context was invoked in, if any.""" - def __init__(self, client: "interactions.Client") -> None: - self.client = client + def __init__(self, client: ClientT) -> None: + self.client: ClientT = client + """The client that created this context.""" + self.author_id = MISSING self.channel_id = MISSING self.message_id = MISSING @@ -215,12 +217,12 @@ def voice_state(self) -> typing.Optional["interactions.VoiceState"]: return self.client.cache.get_bot_voice_state(self.guild_id) @property - def bot(self) -> "interactions.Client": + def bot(self) -> "ClientT": return self.client @classmethod @abc.abstractmethod - def from_dict(cls, client: "interactions.Client", payload: dict) -> Self: + def from_dict(cls, client: "ClientT", payload: dict) -> Self: """ Create a context instance from a dict. @@ -235,7 +237,7 @@ def from_dict(cls, client: "interactions.Client", payload: dict) -> Self: raise NotImplementedError -class BaseInteractionContext(BaseContext): +class BaseInteractionContext(BaseContext[ClientT]): token: str """The interaction token.""" id: Snowflake @@ -256,11 +258,16 @@ class BaseInteractionContext(BaseContext): ephemeral: bool """Whether the interaction response is ephemeral.""" + authorizing_integration_owners: dict[IntegrationType, Snowflake] + """Mapping of installation contexts that the interaction was authorized for to related user or guild IDs""" + context: typing.Optional[ContextType] + """Context where the interaction was triggered from""" + entitlements: list[Entitlement] """The entitlements of the invoking user.""" _context_type: int - """The context type of the interaction.""" + """The type of the interaction.""" command_id: Snowflake """The command ID of the interaction.""" _command_name: str @@ -273,14 +280,14 @@ class BaseInteractionContext(BaseContext): kwargs: dict[str, typing.Any] """The keyword arguments passed to the interaction.""" - def __init__(self, client: "interactions.Client") -> None: + def __init__(self, client: "ClientT") -> None: super().__init__(client) self.deferred = False self.responded = False self.ephemeral = False @classmethod - def from_dict(cls, client: "interactions.Client", payload: dict) -> Self: + def from_dict(cls, client: "ClientT", payload: dict) -> Self: instance = cls(client=client) instance.token = payload["token"] instance.id = Snowflake(payload["id"]) @@ -290,6 +297,11 @@ def from_dict(cls, client: "interactions.Client", payload: dict) -> Self: instance._context_type = payload.get("type", 0) instance.resolved = Resolved.from_dict(client, payload["data"].get("resolved", {}), payload.get("guild_id")) instance.entitlements = Entitlement.from_list(payload["entitlements"], client) + instance.context = ContextType(payload["context"]) if payload.get("context") else None + instance.authorizing_integration_owners = { + IntegrationType(int(integration_type)): Snowflake(owner_id) + for integration_type, owner_id in payload.get("authorizing_integration_owners", {}).items() + } instance.channel_id = Snowflake(payload["channel_id"]) if channel := payload.get("channel"): @@ -405,7 +417,7 @@ def gather_options(_options: list[dict[str, typing.Any]]) -> dict[str, typing.An self.args = list(self.kwargs.values()) -class InteractionContext(BaseInteractionContext, SendMixin): +class InteractionContext(BaseInteractionContext[ClientT], SendMixin): async def defer(self, *, ephemeral: bool = False, suppress_error: bool = False) -> None: """ Defer the interaction. @@ -445,6 +457,10 @@ async def send_premium_required(self) -> None: """ Send a premium required response. + !!! warn + This response has been deprecated by Discord and will be removed in the future. + Use a button with the PREMIUM type instead. + When used, the user will be prompted to subscribe to premium to use this feature. Only available for applications with monetization enabled. """ @@ -528,6 +544,7 @@ async def send( suppress_embeds: bool = False, silent: bool = False, flags: typing.Optional[typing.Union[int, "MessageFlags"]] = None, + poll: "typing.Optional[Poll | dict]" = None, delete_after: typing.Optional[float] = None, ephemeral: bool = False, **kwargs: typing.Any, @@ -549,6 +566,7 @@ async def send( suppress_embeds: Should embeds be suppressed on this send silent: Should this message be sent without triggering a notification. flags: Message flags to apply. + poll: A poll. delete_after: Delete message after this many seconds. ephemeral: Whether the response should be ephemeral @@ -577,6 +595,7 @@ async def send( file=file, tts=tts, flags=flags, + poll=poll, delete_after=delete_after, pass_self_into_delete=True, **kwargs, @@ -621,7 +640,7 @@ async def edit( ) -> "interactions.Message": message_payload = process_message_payload( content=content, - embeds=embeds or embed, + embeds=embed if embeds is None else embeds, components=components, allowed_mentions=allowed_mentions, attachments=attachments, @@ -641,13 +660,13 @@ async def edit( return self.client.cache.place_message_data(message_data) -class SlashContext(InteractionContext, ModalMixin): +class SlashContext(InteractionContext[ClientT], ModalMixin): @classmethod - def from_dict(cls, client: "interactions.Client", payload: dict) -> Self: + def from_dict(cls, client: "ClientT", payload: dict) -> Self: return super().from_dict(client, payload) -class ContextMenuContext(InteractionContext, ModalMixin): +class ContextMenuContext(InteractionContext[ClientT], ModalMixin): target_id: Snowflake """The id of the target of the context menu.""" editing_origin: bool @@ -655,12 +674,12 @@ class ContextMenuContext(InteractionContext, ModalMixin): target_type: None | CommandType """The type of the target of the context menu.""" - def __init__(self, client: "interactions.Client") -> None: + def __init__(self, client: "ClientT") -> None: super().__init__(client) self.editing_origin = False @classmethod - def from_dict(cls, client: "interactions.Client", payload: dict) -> Self: + def from_dict(cls, client: "ClientT", payload: dict) -> Self: instance = super().from_dict(client, payload) instance.target_id = Snowflake(payload["data"]["target_id"]) instance.target_type = CommandType(payload["data"]["type"]) @@ -723,7 +742,7 @@ def target(self) -> None | Message | User | Member: return self.resolved.get(self.target_id) -class ComponentContext(InteractionContext, ModalMixin): +class ComponentContext(InteractionContext[ClientT], ModalMixin): values: list[str] """The values of the SelectMenu component, if any.""" custom_id: str @@ -734,7 +753,7 @@ class ComponentContext(InteractionContext, ModalMixin): """Whether you have deferred the interaction and are editing the original response.""" @classmethod - def from_dict(cls, client: "interactions.Client", payload: dict) -> Self: + def from_dict(cls, client: "ClientT", payload: dict) -> Self: instance = super().from_dict(client, payload) instance.values = payload["data"].get("values", []) instance.custom_id = payload["data"]["custom_id"] @@ -863,7 +882,7 @@ async def edit_origin( message_payload = process_message_payload( content=content, - embeds=embeds or embed, + embeds=embed if embeds is None else embeds, components=components, allowed_mentions=allowed_mentions, tts=tts, @@ -877,18 +896,21 @@ async def edit_origin( ) message_data = await self.client.http.edit_interaction_message( - message_payload, self.client.app.id, self.token, files=files or file + message_payload, self.client.app.id, self.token, files=file if files is None else files ) self.deferred = False self.editing_origin = False else: payload = {"type": CallbackType.UPDATE_MESSAGE, "data": message_payload} - await self.client.http.post_initial_response(payload, str(self.id), self.token, files=files or file) + await self.client.http.post_initial_response( + payload, str(self.id), self.token, files=file if files is None else files + ) message_data = await self.client.http.get_interaction_message(self.client.app.id, self.token) if message_data: message = self.client.cache.place_message_data(message_data) self.message_id = message.id + self.responded = True return message @property @@ -902,7 +924,7 @@ def component(self) -> typing.Optional[BaseComponent]: return component -class ModalContext(InteractionContext): +class ModalContext(InteractionContext[ClientT]): responses: dict[str, str] """The responses of the modal. The key is the `custom_id` of the component.""" custom_id: str @@ -911,7 +933,7 @@ class ModalContext(InteractionContext): """Whether to edit the original message instead of sending a new one.""" @classmethod - def from_dict(cls, client: "interactions.Client", payload: dict) -> Self: + def from_dict(cls, client: "ClientT", payload: dict) -> Self: instance = super().from_dict(client, payload) instance.responses = { comp["components"][0]["custom_id"]: comp["components"][0]["value"] for comp in payload["data"]["components"] @@ -972,12 +994,12 @@ async def _defer(self, *, ephemeral: bool = False, edit_origin: bool = False) -> self.ephemeral = ephemeral -class AutocompleteContext(BaseInteractionContext): +class AutocompleteContext(BaseInteractionContext[ClientT]): focussed_option: SlashCommandOption # todo: option parsing """The option the user is currently filling in.""" @classmethod - def from_dict(cls, client: "interactions.Client", payload: dict) -> Self: + def from_dict(cls, client: "ClientT", payload: dict) -> Self: return super().from_dict(client, payload) @property diff --git a/interactions/models/internal/extension.py b/interactions/models/internal/extension.py index 2ac9cb1a2..ff434de77 100644 --- a/interactions/models/internal/extension.py +++ b/interactions/models/internal/extension.py @@ -2,6 +2,7 @@ import inspect import typing from typing import Awaitable, Dict, List, TYPE_CHECKING, Callable, Coroutine, Optional +import re import interactions.models.internal as models import interactions.api.events as events @@ -83,6 +84,7 @@ def __new__(cls, bot: "Client", *args, **kwargs) -> "Extension": if instance.name in bot.ext: raise ValueError(f"An extension with the name {instance.name} is already loaded!") + instance.extension_name = inspect.getmodule(instance).__name__ instance.extension_checks = [] instance.extension_prerun = [] instance.extension_postrun = [] @@ -123,7 +125,6 @@ def __new__(cls, bot: "Client", *args, **kwargs) -> "Extension": bot.add_global_autocomplete(val) bot.dispatch(events.ExtensionCommandParse(extension=instance, callables=callables)) - instance.extension_name = inspect.getmodule(instance).__name__ instance.bot.ext[instance.name] = instance if hasattr(instance, "async_start"): @@ -154,12 +155,20 @@ def drop(self) -> None: for func in self._commands: if isinstance(func, models.ModalCommand): for listener in func.listeners: - # noinspection PyProtectedMember - self.bot._modal_callbacks.pop(listener) + if isinstance(listener, re.Pattern): + # noinspection PyProtectedMember + self.bot._regex_modal_callbacks.pop(listener) + else: + # noinspection PyProtectedMember + self.bot._modal_callbacks.pop(listener) elif isinstance(func, models.ComponentCommand): for listener in func.listeners: - # noinspection PyProtectedMember - self.bot._component_callbacks.pop(listener) + if isinstance(listener, re.Pattern): + # noinspection PyProtectedMember + self.bot._regex_component_callbacks.pop(listener) + else: + # noinspection PyProtectedMember + self.bot._component_callbacks.pop(listener) elif isinstance(func, models.InteractionCommand): for scope in func.scopes: if self.bot.interactions_by_scope.get(scope): diff --git a/poetry.lock b/poetry.lock index 1327f4cf6..86a9d2f5f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -345,13 +345,13 @@ files = [ [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.6.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, + {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, + {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, ] [[package]] @@ -544,13 +544,13 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "codefind" -version = "0.1.4" +version = "0.1.6" description = "Find code objects and their referents" optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "codefind-0.1.4-py3-none-any.whl", hash = "sha256:31bd5873b29c37776b4dc9041cbccd38e673fe472719b47268b1fbd3624866cb"}, - {file = "codefind-0.1.4.tar.gz", hash = "sha256:9d7b60d231a68be9b205606d9164ee06e1e0d45b9da73101e429ebe4769ee5af"}, + {file = "codefind-0.1.6-py3-none-any.whl", hash = "sha256:0a3b8a441d881a4ba81bede611c11deb56c920a6fcee038dc4bd6e3e6869c67b"}, + {file = "codefind-0.1.6.tar.gz", hash = "sha256:2447fb7c09a57369e131f8087be1640304500d960f742e65c7d9b07ed00fff19"}, ] [[package]] @@ -566,63 +566,63 @@ files = [ [[package]] name = "coverage" -version = "7.5.1" +version = "7.5.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e"}, - {file = "coverage-7.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f"}, - {file = "coverage-7.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a"}, - {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35"}, - {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e"}, - {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223"}, - {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e"}, - {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146"}, - {file = "coverage-7.5.1-cp310-cp310-win32.whl", hash = "sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228"}, - {file = "coverage-7.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8"}, - {file = "coverage-7.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428"}, - {file = "coverage-7.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746"}, - {file = "coverage-7.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3"}, - {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2"}, - {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca"}, - {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8"}, - {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057"}, - {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987"}, - {file = "coverage-7.5.1-cp311-cp311-win32.whl", hash = "sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136"}, - {file = "coverage-7.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd"}, - {file = "coverage-7.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206"}, - {file = "coverage-7.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34"}, - {file = "coverage-7.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d"}, - {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa"}, - {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e"}, - {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572"}, - {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07"}, - {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7"}, - {file = "coverage-7.5.1-cp312-cp312-win32.whl", hash = "sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19"}, - {file = "coverage-7.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596"}, - {file = "coverage-7.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7"}, - {file = "coverage-7.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90"}, - {file = "coverage-7.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e"}, - {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5"}, - {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661"}, - {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8"}, - {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4"}, - {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d"}, - {file = "coverage-7.5.1-cp38-cp38-win32.whl", hash = "sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41"}, - {file = "coverage-7.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de"}, - {file = "coverage-7.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1"}, - {file = "coverage-7.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece"}, - {file = "coverage-7.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26"}, - {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5"}, - {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601"}, - {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be"}, - {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f"}, - {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668"}, - {file = "coverage-7.5.1-cp39-cp39-win32.whl", hash = "sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981"}, - {file = "coverage-7.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f"}, - {file = "coverage-7.5.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312"}, - {file = "coverage-7.5.1.tar.gz", hash = "sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c"}, + {file = "coverage-7.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a6519d917abb15e12380406d721e37613e2a67d166f9fb7e5a8ce0375744cd45"}, + {file = "coverage-7.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea7da970f1feccf48be7335f8b2ca64baf9b589d79e05b9397a06696ce1a1ec"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:923b7b1c717bd0f0f92d862d1ff51d9b2b55dbbd133e05680204465f454bb286"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62bda40da1e68898186f274f832ef3e759ce929da9a9fd9fcf265956de269dbc"}, + {file = "coverage-7.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8b7339180d00de83e930358223c617cc343dd08e1aa5ec7b06c3a121aec4e1d"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25a5caf742c6195e08002d3b6c2dd6947e50efc5fc2c2205f61ecb47592d2d83"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:05ac5f60faa0c704c0f7e6a5cbfd6f02101ed05e0aee4d2822637a9e672c998d"}, + {file = "coverage-7.5.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:239a4e75e09c2b12ea478d28815acf83334d32e722e7433471fbf641c606344c"}, + {file = "coverage-7.5.3-cp310-cp310-win32.whl", hash = "sha256:a5812840d1d00eafae6585aba38021f90a705a25b8216ec7f66aebe5b619fb84"}, + {file = "coverage-7.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:33ca90a0eb29225f195e30684ba4a6db05dbef03c2ccd50b9077714c48153cac"}, + {file = "coverage-7.5.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f81bc26d609bf0fbc622c7122ba6307993c83c795d2d6f6f6fd8c000a770d974"}, + {file = "coverage-7.5.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7cec2af81f9e7569280822be68bd57e51b86d42e59ea30d10ebdbb22d2cb7232"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55f689f846661e3f26efa535071775d0483388a1ccfab899df72924805e9e7cd"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50084d3516aa263791198913a17354bd1dc627d3c1639209640b9cac3fef5807"}, + {file = "coverage-7.5.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341dd8f61c26337c37988345ca5c8ccabeff33093a26953a1ac72e7d0103c4fb"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ab0b028165eea880af12f66086694768f2c3139b2c31ad5e032c8edbafca6ffc"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5bc5a8c87714b0c67cfeb4c7caa82b2d71e8864d1a46aa990b5588fa953673b8"}, + {file = "coverage-7.5.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:38a3b98dae8a7c9057bd91fbf3415c05e700a5114c5f1b5b0ea5f8f429ba6614"}, + {file = "coverage-7.5.3-cp311-cp311-win32.whl", hash = "sha256:fcf7d1d6f5da887ca04302db8e0e0cf56ce9a5e05f202720e49b3e8157ddb9a9"}, + {file = "coverage-7.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:8c836309931839cca658a78a888dab9676b5c988d0dd34ca247f5f3e679f4e7a"}, + {file = "coverage-7.5.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:296a7d9bbc598e8744c00f7a6cecf1da9b30ae9ad51c566291ff1314e6cbbed8"}, + {file = "coverage-7.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34d6d21d8795a97b14d503dcaf74226ae51eb1f2bd41015d3ef332a24d0a17b3"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e317953bb4c074c06c798a11dbdd2cf9979dbcaa8ccc0fa4701d80042d4ebf1"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:705f3d7c2b098c40f5b81790a5fedb274113373d4d1a69e65f8b68b0cc26f6db"}, + {file = "coverage-7.5.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1196e13c45e327d6cd0b6e471530a1882f1017eb83c6229fc613cd1a11b53cd"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:015eddc5ccd5364dcb902eaecf9515636806fa1e0d5bef5769d06d0f31b54523"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fd27d8b49e574e50caa65196d908f80e4dff64d7e592d0c59788b45aad7e8b35"}, + {file = "coverage-7.5.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:33fc65740267222fc02975c061eb7167185fef4cc8f2770267ee8bf7d6a42f84"}, + {file = "coverage-7.5.3-cp312-cp312-win32.whl", hash = "sha256:7b2a19e13dfb5c8e145c7a6ea959485ee8e2204699903c88c7d25283584bfc08"}, + {file = "coverage-7.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:0bbddc54bbacfc09b3edaec644d4ac90c08ee8ed4844b0f86227dcda2d428fcb"}, + {file = "coverage-7.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f78300789a708ac1f17e134593f577407d52d0417305435b134805c4fb135adb"}, + {file = "coverage-7.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b368e1aee1b9b75757942d44d7598dcd22a9dbb126affcbba82d15917f0cc155"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f836c174c3a7f639bded48ec913f348c4761cbf49de4a20a956d3431a7c9cb24"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:244f509f126dc71369393ce5fea17c0592c40ee44e607b6d855e9c4ac57aac98"}, + {file = "coverage-7.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c2872b3c91f9baa836147ca33650dc5c172e9273c808c3c3199c75490e709d"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dd4b3355b01273a56b20c219e74e7549e14370b31a4ffe42706a8cda91f19f6d"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f542287b1489c7a860d43a7d8883e27ca62ab84ca53c965d11dac1d3a1fab7ce"}, + {file = "coverage-7.5.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75e3f4e86804023e991096b29e147e635f5e2568f77883a1e6eed74512659ab0"}, + {file = "coverage-7.5.3-cp38-cp38-win32.whl", hash = "sha256:c59d2ad092dc0551d9f79d9d44d005c945ba95832a6798f98f9216ede3d5f485"}, + {file = "coverage-7.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:fa21a04112c59ad54f69d80e376f7f9d0f5f9123ab87ecd18fbb9ec3a2beed56"}, + {file = "coverage-7.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5102a92855d518b0996eb197772f5ac2a527c0ec617124ad5242a3af5e25f85"}, + {file = "coverage-7.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d1da0a2e3b37b745a2b2a678a4c796462cf753aebf94edcc87dcc6b8641eae31"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8383a6c8cefba1b7cecc0149415046b6fc38836295bc4c84e820872eb5478b3d"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9aad68c3f2566dfae84bf46295a79e79d904e1c21ccfc66de88cd446f8686341"}, + {file = "coverage-7.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bde997cac85fcac227b27d4fb2c7608a2c5f6558469b0eb704c5726ae49e1c52"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:990fb20b32990b2ce2c5f974c3e738c9358b2735bc05075d50a6f36721b8f303"}, + {file = "coverage-7.5.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3d5a67f0da401e105753d474369ab034c7bae51a4c31c77d94030d59e41df5bd"}, + {file = "coverage-7.5.3-cp39-cp39-win32.whl", hash = "sha256:e08c470c2eb01977d221fd87495b44867a56d4d594f43739a8028f8646a51e0d"}, + {file = "coverage-7.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:1d2a830ade66d3563bb61d1e3c77c8def97b30ed91e166c67d0632c018f380f0"}, + {file = "coverage-7.5.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:3538d8fb1ee9bdd2e2692b3b18c22bb1c19ffbefd06880f5ac496e42d7bb3884"}, + {file = "coverage-7.5.3.tar.gz", hash = "sha256:04aefca5190d1dc7a53a4c1a5a7f8568811306d7a8ee231c42fb69215571944f"}, ] [package.dependencies] @@ -658,13 +658,13 @@ files = [ [[package]] name = "discord-typings" -version = "0.7.0" -description = "Python typings of payloads that Discord sends" +version = "0.9.0" +description = "Python typings of all payloads that Discord sends as TypedDicts" optional = false python-versions = ">=3.7" files = [ - {file = "discord_typings-0.7.0-py3-none-any.whl", hash = "sha256:6c776da9153cde775c93cf744207297e4baec3f805b290216ebd490a86ff3140"}, - {file = "discord_typings-0.7.0.tar.gz", hash = "sha256:9debd89834d4a6b354d74fa21c527a84fb59454d2d4f0229a3f7d9f7c39f09c3"}, + {file = "discord_typings-0.9.0-py3-none-any.whl", hash = "sha256:3d8776e16aa1f908fae947225d9cfd99701bcf0c7e28ca56bc10d892d1cfcffa"}, + {file = "discord_typings-0.9.0.tar.gz", hash = "sha256:fc39321dd04b3299e2b680da34454be8f29540acff4303752d514444daeb4e6d"}, ] [package.dependencies] @@ -683,17 +683,20 @@ files = [ [[package]] name = "emoji" -version = "2.11.1" +version = "2.12.1" description = "Emoji for Python" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = ">=3.7" files = [ - {file = "emoji-2.11.1-py2.py3-none-any.whl", hash = "sha256:b7ba25299bbf520cc8727848ae66b986da32aee27dc2887eaea2bff07226ce49"}, - {file = "emoji-2.11.1.tar.gz", hash = "sha256:062ff0b3154b6219143f8b9f4b3e5c64c35bc2b146e6e2349ab5f29e218ce1ee"}, + {file = "emoji-2.12.1-py3-none-any.whl", hash = "sha256:a00d62173bdadc2510967a381810101624a2f0986145b8da0cffa42e29430235"}, + {file = "emoji-2.12.1.tar.gz", hash = "sha256:4aa0488817691aa58d83764b6c209f8a27c0b3ab3f89d1b8dceca1a62e4973eb"}, ] +[package.dependencies] +typing-extensions = ">=4.7.0" + [package.extras] -dev = ["coverage", "coveralls", "pytest"] +dev = ["coverage", "pytest (>=7.4.4)"] [[package]] name = "exceptiongroup" @@ -953,13 +956,13 @@ test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", [[package]] name = "griffe" -version = "0.44.0" +version = "0.45.2" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.8" files = [ - {file = "griffe-0.44.0-py3-none-any.whl", hash = "sha256:8a4471c469ba980b87c843f1168850ce39d0c1d0c7be140dca2480f76c8e5446"}, - {file = "griffe-0.44.0.tar.gz", hash = "sha256:34aee1571042f9bf00529bc715de4516fb6f482b164e90d030300601009e0223"}, + {file = "griffe-0.45.2-py3-none-any.whl", hash = "sha256:297ec8530d0c68e5b98ff86fb588ebc3aa3559bb5dc21f3caea8d9542a350133"}, + {file = "griffe-0.45.2.tar.gz", hash = "sha256:83ce7dcaafd8cb7f43cbf1a455155015a1eb624b1ffd93249e5e1c4a22b2fdb2"}, ] [package.dependencies] @@ -1054,13 +1057,13 @@ files = [ [[package]] name = "jurigged" -version = "0.5.7" +version = "0.5.8" description = "Live update of Python functions" optional = false -python-versions = ">=3.8,<4.0" +python-versions = "<4.0,>=3.8" files = [ - {file = "jurigged-0.5.7-py3-none-any.whl", hash = "sha256:0a13a27bd6bffe71a50aecf75f29577c6de40ed8ef94fb6977f495b8c2071514"}, - {file = "jurigged-0.5.7.tar.gz", hash = "sha256:3afbb05f51f48a83e0f426fb0ea9cb44c3afefd6c49d11a783f6985576dd18db"}, + {file = "jurigged-0.5.8-py3-none-any.whl", hash = "sha256:29c66ac2fa5425444bf77bd517d577d41a3cb8532f8dcb9b2ace7d949028fb75"}, + {file = "jurigged-0.5.8.tar.gz", hash = "sha256:abe0b425370d6f30178fb4a81a27d2b7b18798015c447673b94d958c839f43ea"}, ] [package.dependencies] @@ -1263,13 +1266,13 @@ requests = "*" [[package]] name = "mkdocs-git-revision-date-localized-plugin" -version = "1.2.5" +version = "1.2.6" description = "Mkdocs plugin that enables displaying the localized date of the last git modification of a markdown file." optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_git_revision_date_localized_plugin-1.2.5-py3-none-any.whl", hash = "sha256:d796a18b07cfcdb154c133e3ec099d2bb5f38389e4fd54d3eb516a8a736815b8"}, - {file = "mkdocs_git_revision_date_localized_plugin-1.2.5.tar.gz", hash = "sha256:0c439816d9d0dba48e027d9d074b2b9f1d7cd179f74ba46b51e4da7bb3dc4b9b"}, + {file = "mkdocs_git_revision_date_localized_plugin-1.2.6-py3-none-any.whl", hash = "sha256:f015cb0f3894a39b33447b18e270ae391c4e25275cac5a626e80b243784e2692"}, + {file = "mkdocs_git_revision_date_localized_plugin-1.2.6.tar.gz", hash = "sha256:e432942ce4ee8aa9b9f4493e993dee9d2cc08b3ea2b40a3d6b03ca0f2a4bcaa2"}, ] [package.dependencies] @@ -1280,13 +1283,13 @@ pytz = "*" [[package]] name = "mkdocs-material" -version = "9.5.21" +version = "9.5.26" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.21-py3-none-any.whl", hash = "sha256:210e1f179682cd4be17d5c641b2f4559574b9dea2f589c3f0e7c17c5bd1959bc"}, - {file = "mkdocs_material-9.5.21.tar.gz", hash = "sha256:049f82770f40559d3c2aa2259c562ea7257dbb4aaa9624323b5ef27b2d95a450"}, + {file = "mkdocs_material-9.5.26-py3-none-any.whl", hash = "sha256:5d01fb0aa1c7946a1e3ae8689aa2b11a030621ecb54894e35aabb74c21016312"}, + {file = "mkdocs_material-9.5.26.tar.gz", hash = "sha256:56aeb91d94cffa43b6296fa4fbf0eb7c840136e563eecfd12c2d9e92e50ba326"}, ] [package.dependencies] @@ -1363,18 +1366,18 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "1.10.0" +version = "1.10.3" description = "A Python handler for mkdocstrings." optional = false python-versions = ">=3.8" files = [ - {file = "mkdocstrings_python-1.10.0-py3-none-any.whl", hash = "sha256:ba833fbd9d178a4b9d5cb2553a4df06e51dc1f51e41559a4d2398c16a6f69ecc"}, - {file = "mkdocstrings_python-1.10.0.tar.gz", hash = "sha256:71678fac657d4d2bb301eed4e4d2d91499c095fd1f8a90fa76422a87a5693828"}, + {file = "mkdocstrings_python-1.10.3-py3-none-any.whl", hash = "sha256:11ff6d21d3818fb03af82c3ea6225b1534837e17f790aa5f09626524171f949b"}, + {file = "mkdocstrings_python-1.10.3.tar.gz", hash = "sha256:321cf9c732907ab2b1fedaafa28765eaa089d89320f35f7206d00ea266889d03"}, ] [package.dependencies] griffe = ">=0.44" -mkdocstrings = ">=0.24.2" +mkdocstrings = ">=0.25" [[package]] name = "multidict" @@ -1550,18 +1553,15 @@ icu = ["PyICU (>=1.0.0)"] [[package]] name = "nodeenv" -version = "1.8.0" +version = "1.9.1" description = "Node.js virtual environment builder" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, ] -[package.dependencies] -setuptools = "*" - [[package]] name = "orjson" version = "3.10.3" @@ -1662,13 +1662,13 @@ files = [ [[package]] name = "platformdirs" -version = "4.2.1" +version = "4.2.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, - {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] @@ -1846,13 +1846,13 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] name = "pytest" -version = "8.2.0" +version = "8.2.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"}, - {file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"}, + {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, + {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, ] [package.dependencies] @@ -1868,13 +1868,13 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments [[package]] name = "pytest-asyncio" -version = "0.23.6" +version = "0.23.7" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-asyncio-0.23.6.tar.gz", hash = "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"}, - {file = "pytest_asyncio-0.23.6-py3-none-any.whl", hash = "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a"}, + {file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"}, + {file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"}, ] [package.dependencies] @@ -2017,101 +2017,101 @@ pyyaml = "*" [[package]] name = "regex" -version = "2024.4.28" +version = "2024.5.15" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.8" files = [ - {file = "regex-2024.4.28-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd196d056b40af073d95a2879678585f0b74ad35190fac04ca67954c582c6b61"}, - {file = "regex-2024.4.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8bb381f777351bd534462f63e1c6afb10a7caa9fa2a421ae22c26e796fe31b1f"}, - {file = "regex-2024.4.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:47af45b6153522733aa6e92543938e97a70ce0900649ba626cf5aad290b737b6"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99d6a550425cc51c656331af0e2b1651e90eaaa23fb4acde577cf15068e2e20f"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf29304a8011feb58913c382902fde3395957a47645bf848eea695839aa101b7"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:92da587eee39a52c91aebea8b850e4e4f095fe5928d415cb7ed656b3460ae79a"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6277d426e2f31bdbacb377d17a7475e32b2d7d1f02faaecc48d8e370c6a3ff31"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28e1f28d07220c0f3da0e8fcd5a115bbb53f8b55cecf9bec0c946eb9a059a94c"}, - {file = "regex-2024.4.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aaa179975a64790c1f2701ac562b5eeb733946eeb036b5bcca05c8d928a62f10"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6f435946b7bf7a1b438b4e6b149b947c837cb23c704e780c19ba3e6855dbbdd3"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:19d6c11bf35a6ad077eb23852827f91c804eeb71ecb85db4ee1386825b9dc4db"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:fdae0120cddc839eb8e3c15faa8ad541cc6d906d3eb24d82fb041cfe2807bc1e"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e672cf9caaf669053121f1766d659a8813bd547edef6e009205378faf45c67b8"}, - {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f57515750d07e14743db55d59759893fdb21d2668f39e549a7d6cad5d70f9fea"}, - {file = "regex-2024.4.28-cp310-cp310-win32.whl", hash = "sha256:a1409c4eccb6981c7baabc8888d3550df518add6e06fe74fa1d9312c1838652d"}, - {file = "regex-2024.4.28-cp310-cp310-win_amd64.whl", hash = "sha256:1f687a28640f763f23f8a9801fe9e1b37338bb1ca5d564ddd41619458f1f22d1"}, - {file = "regex-2024.4.28-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84077821c85f222362b72fdc44f7a3a13587a013a45cf14534df1cbbdc9a6796"}, - {file = "regex-2024.4.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b45d4503de8f4f3dc02f1d28a9b039e5504a02cc18906cfe744c11def942e9eb"}, - {file = "regex-2024.4.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:457c2cd5a646dd4ed536c92b535d73548fb8e216ebee602aa9f48e068fc393f3"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b51739ddfd013c6f657b55a508de8b9ea78b56d22b236052c3a85a675102dc6"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:459226445c7d7454981c4c0ce0ad1a72e1e751c3e417f305722bbcee6697e06a"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:670fa596984b08a4a769491cbdf22350431970d0112e03d7e4eeaecaafcd0fec"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe00f4fe11c8a521b173e6324d862ee7ee3412bf7107570c9b564fe1119b56fb"}, - {file = "regex-2024.4.28-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36f392dc7763fe7924575475736bddf9ab9f7a66b920932d0ea50c2ded2f5636"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:23a412b7b1a7063f81a742463f38821097b6a37ce1e5b89dd8e871d14dbfd86b"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f1d6e4b7b2ae3a6a9df53efbf199e4bfcff0959dbdb5fd9ced34d4407348e39a"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:499334ad139557de97cbc4347ee921c0e2b5e9c0f009859e74f3f77918339257"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:0940038bec2fe9e26b203d636c44d31dd8766abc1fe66262da6484bd82461ccf"}, - {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:66372c2a01782c5fe8e04bff4a2a0121a9897e19223d9eab30c54c50b2ebeb7f"}, - {file = "regex-2024.4.28-cp311-cp311-win32.whl", hash = "sha256:c77d10ec3c1cf328b2f501ca32583625987ea0f23a0c2a49b37a39ee5c4c4630"}, - {file = "regex-2024.4.28-cp311-cp311-win_amd64.whl", hash = "sha256:fc0916c4295c64d6890a46e02d4482bb5ccf33bf1a824c0eaa9e83b148291f90"}, - {file = "regex-2024.4.28-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:08a1749f04fee2811c7617fdd46d2e46d09106fa8f475c884b65c01326eb15c5"}, - {file = "regex-2024.4.28-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b8eb28995771c087a73338f695a08c9abfdf723d185e57b97f6175c5051ff1ae"}, - {file = "regex-2024.4.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd7ef715ccb8040954d44cfeff17e6b8e9f79c8019daae2fd30a8806ef5435c0"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb0315a2b26fde4005a7c401707c5352df274460f2f85b209cf6024271373013"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2fc053228a6bd3a17a9b0a3f15c3ab3cf95727b00557e92e1cfe094b88cc662"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fe9739a686dc44733d52d6e4f7b9c77b285e49edf8570754b322bca6b85b4cc"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74fcf77d979364f9b69fcf8200849ca29a374973dc193a7317698aa37d8b01c"}, - {file = "regex-2024.4.28-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:965fd0cf4694d76f6564896b422724ec7b959ef927a7cb187fc6b3f4e4f59833"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2fef0b38c34ae675fcbb1b5db760d40c3fc3612cfa186e9e50df5782cac02bcd"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bc365ce25f6c7c5ed70e4bc674f9137f52b7dd6a125037f9132a7be52b8a252f"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ac69b394764bb857429b031d29d9604842bc4cbfd964d764b1af1868eeebc4f0"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:144a1fc54765f5c5c36d6d4b073299832aa1ec6a746a6452c3ee7b46b3d3b11d"}, - {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2630ca4e152c221072fd4a56d4622b5ada876f668ecd24d5ab62544ae6793ed6"}, - {file = "regex-2024.4.28-cp312-cp312-win32.whl", hash = "sha256:7f3502f03b4da52bbe8ba962621daa846f38489cae5c4a7b5d738f15f6443d17"}, - {file = "regex-2024.4.28-cp312-cp312-win_amd64.whl", hash = "sha256:0dd3f69098511e71880fb00f5815db9ed0ef62c05775395968299cb400aeab82"}, - {file = "regex-2024.4.28-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:374f690e1dd0dbdcddea4a5c9bdd97632cf656c69113f7cd6a361f2a67221cb6"}, - {file = "regex-2024.4.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f87ae6b96374db20f180eab083aafe419b194e96e4f282c40191e71980c666"}, - {file = "regex-2024.4.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5dbc1bcc7413eebe5f18196e22804a3be1bfdfc7e2afd415e12c068624d48247"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f85151ec5a232335f1be022b09fbbe459042ea1951d8a48fef251223fc67eee1"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57ba112e5530530fd175ed550373eb263db4ca98b5f00694d73b18b9a02e7185"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:224803b74aab56aa7be313f92a8d9911dcade37e5f167db62a738d0c85fdac4b"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a54a047b607fd2d2d52a05e6ad294602f1e0dec2291152b745870afc47c1397"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a2a512d623f1f2d01d881513af9fc6a7c46e5cfffb7dc50c38ce959f9246c94"}, - {file = "regex-2024.4.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c06bf3f38f0707592898428636cbb75d0a846651b053a1cf748763e3063a6925"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1031a5e7b048ee371ab3653aad3030ecfad6ee9ecdc85f0242c57751a05b0ac4"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d7a353ebfa7154c871a35caca7bfd8f9e18666829a1dc187115b80e35a29393e"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7e76b9cfbf5ced1aca15a0e5b6f229344d9b3123439ffce552b11faab0114a02"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5ce479ecc068bc2a74cb98dd8dba99e070d1b2f4a8371a7dfe631f85db70fe6e"}, - {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7d77b6f63f806578c604dca209280e4c54f0fa9a8128bb8d2cc5fb6f99da4150"}, - {file = "regex-2024.4.28-cp38-cp38-win32.whl", hash = "sha256:d84308f097d7a513359757c69707ad339da799e53b7393819ec2ea36bc4beb58"}, - {file = "regex-2024.4.28-cp38-cp38-win_amd64.whl", hash = "sha256:2cc1b87bba1dd1a898e664a31012725e48af826bf3971e786c53e32e02adae6c"}, - {file = "regex-2024.4.28-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7413167c507a768eafb5424413c5b2f515c606be5bb4ef8c5dee43925aa5718b"}, - {file = "regex-2024.4.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:108e2dcf0b53a7c4ab8986842a8edcb8ab2e59919a74ff51c296772e8e74d0ae"}, - {file = "regex-2024.4.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f1c5742c31ba7d72f2dedf7968998730664b45e38827637e0f04a2ac7de2f5f1"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecc6148228c9ae25ce403eade13a0961de1cb016bdb35c6eafd8e7b87ad028b1"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7d893c8cf0e2429b823ef1a1d360a25950ed11f0e2a9df2b5198821832e1947"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4290035b169578ffbbfa50d904d26bec16a94526071ebec3dadbebf67a26b25e"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44a22ae1cfd82e4ffa2066eb3390777dc79468f866f0625261a93e44cdf6482b"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd24fd140b69f0b0bcc9165c397e9b2e89ecbeda83303abf2a072609f60239e2"}, - {file = "regex-2024.4.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:39fb166d2196413bead229cd64a2ffd6ec78ebab83fff7d2701103cf9f4dfd26"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9301cc6db4d83d2c0719f7fcda37229691745168bf6ae849bea2e85fc769175d"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c3d389e8d76a49923683123730c33e9553063d9041658f23897f0b396b2386f"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:99ef6289b62042500d581170d06e17f5353b111a15aa6b25b05b91c6886df8fc"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b91d529b47798c016d4b4c1d06cc826ac40d196da54f0de3c519f5a297c5076a"}, - {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:43548ad74ea50456e1c68d3c67fff3de64c6edb85bcd511d1136f9b5376fc9d1"}, - {file = "regex-2024.4.28-cp39-cp39-win32.whl", hash = "sha256:05d9b6578a22db7dedb4df81451f360395828b04f4513980b6bd7a1412c679cc"}, - {file = "regex-2024.4.28-cp39-cp39-win_amd64.whl", hash = "sha256:3986217ec830c2109875be740531feb8ddafe0dfa49767cdcd072ed7e8927962"}, - {file = "regex-2024.4.28.tar.gz", hash = "sha256:83ab366777ea45d58f72593adf35d36ca911ea8bd838483c1823b883a121b0e4"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a81e3cfbae20378d75185171587cbf756015ccb14840702944f014e0d93ea09f"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b59138b219ffa8979013be7bc85bb60c6f7b7575df3d56dc1e403a438c7a3f6"}, + {file = "regex-2024.5.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0bd000c6e266927cb7a1bc39d55be95c4b4f65c5be53e659537537e019232b1"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eaa7ddaf517aa095fa8da0b5015c44d03da83f5bd49c87961e3c997daed0de7"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba68168daedb2c0bab7fd7e00ced5ba90aebf91024dea3c88ad5063c2a562cca"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e8d717bca3a6e2064fc3a08df5cbe366369f4b052dcd21b7416e6d71620dca1"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1337b7dbef9b2f71121cdbf1e97e40de33ff114801263b275aafd75303bd62b5"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9ebd0a36102fcad2f03696e8af4ae682793a5d30b46c647eaf280d6cfb32796"}, + {file = "regex-2024.5.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9efa1a32ad3a3ea112224897cdaeb6aa00381627f567179c0314f7b65d354c62"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1595f2d10dff3d805e054ebdc41c124753631b6a471b976963c7b28543cf13b0"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b802512f3e1f480f41ab5f2cfc0e2f761f08a1f41092d6718868082fc0d27143"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a0981022dccabca811e8171f913de05720590c915b033b7e601f35ce4ea7019f"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:19068a6a79cf99a19ccefa44610491e9ca02c2be3305c7760d3831d38a467a6f"}, + {file = "regex-2024.5.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1b5269484f6126eee5e687785e83c6b60aad7663dafe842b34691157e5083e53"}, + {file = "regex-2024.5.15-cp310-cp310-win32.whl", hash = "sha256:ada150c5adfa8fbcbf321c30c751dc67d2f12f15bd183ffe4ec7cde351d945b3"}, + {file = "regex-2024.5.15-cp310-cp310-win_amd64.whl", hash = "sha256:ac394ff680fc46b97487941f5e6ae49a9f30ea41c6c6804832063f14b2a5a145"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f5b1dff3ad008dccf18e652283f5e5339d70bf8ba7c98bf848ac33db10f7bc7a"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c6a2b494a76983df8e3d3feea9b9ffdd558b247e60b92f877f93a1ff43d26656"}, + {file = "regex-2024.5.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a32b96f15c8ab2e7d27655969a23895eb799de3665fa94349f3b2fbfd547236f"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10002e86e6068d9e1c91eae8295ef690f02f913c57db120b58fdd35a6bb1af35"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec54d5afa89c19c6dd8541a133be51ee1017a38b412b1321ccb8d6ddbeb4cf7d"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:10e4ce0dca9ae7a66e6089bb29355d4432caed736acae36fef0fdd7879f0b0cb"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e507ff1e74373c4d3038195fdd2af30d297b4f0950eeda6f515ae3d84a1770f"}, + {file = "regex-2024.5.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1f059a4d795e646e1c37665b9d06062c62d0e8cc3c511fe01315973a6542e40"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0721931ad5fe0dda45d07f9820b90b2148ccdd8e45bb9e9b42a146cb4f695649"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:833616ddc75ad595dee848ad984d067f2f31be645d603e4d158bba656bbf516c"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:287eb7f54fc81546346207c533ad3c2c51a8d61075127d7f6d79aaf96cdee890"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:19dfb1c504781a136a80ecd1fff9f16dddf5bb43cec6871778c8a907a085bb3d"}, + {file = "regex-2024.5.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:119af6e56dce35e8dfb5222573b50c89e5508d94d55713c75126b753f834de68"}, + {file = "regex-2024.5.15-cp311-cp311-win32.whl", hash = "sha256:1c1c174d6ec38d6c8a7504087358ce9213d4332f6293a94fbf5249992ba54efa"}, + {file = "regex-2024.5.15-cp311-cp311-win_amd64.whl", hash = "sha256:9e717956dcfd656f5055cc70996ee2cc82ac5149517fc8e1b60261b907740201"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:632b01153e5248c134007209b5c6348a544ce96c46005d8456de1d552455b014"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e64198f6b856d48192bf921421fdd8ad8eb35e179086e99e99f711957ffedd6e"}, + {file = "regex-2024.5.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68811ab14087b2f6e0fc0c2bae9ad689ea3584cad6917fc57be6a48bbd012c49"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ec0c2fea1e886a19c3bee0cd19d862b3aa75dcdfb42ebe8ed30708df64687a"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0c0c0003c10f54a591d220997dd27d953cd9ccc1a7294b40a4be5312be8797b"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2431b9e263af1953c55abbd3e2efca67ca80a3de8a0437cb58e2421f8184717a"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a605586358893b483976cffc1723fb0f83e526e8f14c6e6614e75919d9862cf"}, + {file = "regex-2024.5.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:391d7f7f1e409d192dba8bcd42d3e4cf9e598f3979cdaed6ab11288da88cb9f2"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9ff11639a8d98969c863d4617595eb5425fd12f7c5ef6621a4b74b71ed8726d5"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4eee78a04e6c67e8391edd4dad3279828dd66ac4b79570ec998e2155d2e59fd5"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8fe45aa3f4aa57faabbc9cb46a93363edd6197cbc43523daea044e9ff2fea83e"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d0a3d8d6acf0c78a1fff0e210d224b821081330b8524e3e2bc5a68ef6ab5803d"}, + {file = "regex-2024.5.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c486b4106066d502495b3025a0a7251bf37ea9540433940a23419461ab9f2a80"}, + {file = "regex-2024.5.15-cp312-cp312-win32.whl", hash = "sha256:c49e15eac7c149f3670b3e27f1f28a2c1ddeccd3a2812cba953e01be2ab9b5fe"}, + {file = "regex-2024.5.15-cp312-cp312-win_amd64.whl", hash = "sha256:673b5a6da4557b975c6c90198588181029c60793835ce02f497ea817ff647cb2"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:87e2a9c29e672fc65523fb47a90d429b70ef72b901b4e4b1bd42387caf0d6835"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c3bea0ba8b73b71b37ac833a7f3fd53825924165da6a924aec78c13032f20850"}, + {file = "regex-2024.5.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfc4f82cabe54f1e7f206fd3d30fda143f84a63fe7d64a81558d6e5f2e5aaba9"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5bb9425fe881d578aeca0b2b4b3d314ec88738706f66f219c194d67179337cb"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64c65783e96e563103d641760664125e91bd85d8e49566ee560ded4da0d3e704"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cf2430df4148b08fb4324b848672514b1385ae3807651f3567871f130a728cc3"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5397de3219a8b08ae9540c48f602996aa6b0b65d5a61683e233af8605c42b0f2"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:455705d34b4154a80ead722f4f185b04c4237e8e8e33f265cd0798d0e44825fa"}, + {file = "regex-2024.5.15-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2b6f1b3bb6f640c1a92be3bbfbcb18657b125b99ecf141fb3310b5282c7d4ed"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3ad070b823ca5890cab606c940522d05d3d22395d432f4aaaf9d5b1653e47ced"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:5b5467acbfc153847d5adb21e21e29847bcb5870e65c94c9206d20eb4e99a384"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e6662686aeb633ad65be2a42b4cb00178b3fbf7b91878f9446075c404ada552f"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:2b4c884767504c0e2401babe8b5b7aea9148680d2e157fa28f01529d1f7fcf67"}, + {file = "regex-2024.5.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3cd7874d57f13bf70078f1ff02b8b0aa48d5b9ed25fc48547516c6aba36f5741"}, + {file = "regex-2024.5.15-cp38-cp38-win32.whl", hash = "sha256:e4682f5ba31f475d58884045c1a97a860a007d44938c4c0895f41d64481edbc9"}, + {file = "regex-2024.5.15-cp38-cp38-win_amd64.whl", hash = "sha256:d99ceffa25ac45d150e30bd9ed14ec6039f2aad0ffa6bb87a5936f5782fc1569"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:13cdaf31bed30a1e1c2453ef6015aa0983e1366fad2667657dbcac7b02f67133"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cac27dcaa821ca271855a32188aa61d12decb6fe45ffe3e722401fe61e323cd1"}, + {file = "regex-2024.5.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7dbe2467273b875ea2de38ded4eba86cbcbc9a1a6d0aa11dcf7bd2e67859c435"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f18a9a3513a99c4bef0e3efd4c4a5b11228b48aa80743be822b71e132ae4f5"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d347a741ea871c2e278fde6c48f85136c96b8659b632fb57a7d1ce1872547600"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1878b8301ed011704aea4c806a3cadbd76f84dece1ec09cc9e4dc934cfa5d4da"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4babf07ad476aaf7830d77000874d7611704a7fcf68c9c2ad151f5d94ae4bfc4"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35cb514e137cb3488bce23352af3e12fb0dbedd1ee6e60da053c69fb1b29cc6c"}, + {file = "regex-2024.5.15-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cdd09d47c0b2efee9378679f8510ee6955d329424c659ab3c5e3a6edea696294"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:72d7a99cd6b8f958e85fc6ca5b37c4303294954eac1376535b03c2a43eb72629"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a094801d379ab20c2135529948cb84d417a2169b9bdceda2a36f5f10977ebc16"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c0c18345010870e58238790a6779a1219b4d97bd2e77e1140e8ee5d14df071aa"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:16093f563098448ff6b1fa68170e4acbef94e6b6a4e25e10eae8598bb1694b5d"}, + {file = "regex-2024.5.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e38a7d4e8f633a33b4c7350fbd8bad3b70bf81439ac67ac38916c4a86b465456"}, + {file = "regex-2024.5.15-cp39-cp39-win32.whl", hash = "sha256:71a455a3c584a88f654b64feccc1e25876066c4f5ef26cd6dd711308aa538694"}, + {file = "regex-2024.5.15-cp39-cp39-win_amd64.whl", hash = "sha256:cab12877a9bdafde5500206d1020a584355a97884dfd388af3699e9137bf7388"}, + {file = "regex-2024.5.15.tar.gz", hash = "sha256:d3ee02d9e5f482cc8309134a91eeaacbdd2261ba111b0fef3748eeb4913e6a2c"}, ] [[package]] name = "requests" -version = "2.31.0" +version = "2.32.3" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -2126,13 +2126,13 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "sentry-sdk" -version = "2.0.1" +version = "2.5.1" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = ">=3.6" files = [ - {file = "sentry_sdk-2.0.1-py2.py3-none-any.whl", hash = "sha256:b54c54a2160f509cf2757260d0cf3885b608c6192c2555a3857e3a4d0f84bdb3"}, - {file = "sentry_sdk-2.0.1.tar.gz", hash = "sha256:c278e0f523f6f0ee69dc43ad26dcdb1202dffe5ac326ae31472e012d941bee21"}, + {file = "sentry_sdk-2.5.1-py2.py3-none-any.whl", hash = "sha256:1f87acdce4a43a523ae5aa21a3fc37522d73ebd9ec04b1dbf01aa3d173852def"}, + {file = "sentry_sdk-2.5.1.tar.gz", hash = "sha256:fbc40a78a8a9c6675133031116144f0d0940376fa6e4e1acd5624c90b0aaf58b"}, ] [package.dependencies] @@ -2141,6 +2141,7 @@ urllib3 = ">=1.26.11" [package.extras] aiohttp = ["aiohttp (>=3.5)"] +anthropic = ["anthropic (>=0.16)"] arq = ["arq (>=0.23)"] asyncpg = ["asyncpg (>=0.23)"] beam = ["apache-beam (>=2.12)"] @@ -2153,9 +2154,11 @@ django = ["django (>=1.8)"] falcon = ["falcon (>=1.4)"] fastapi = ["fastapi (>=0.79.0)"] flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] -grpcio = ["grpcio (>=1.21.1)"] +grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"] httpx = ["httpx (>=0.16.0)"] huey = ["huey (>=2)"] +huggingface-hub = ["huggingface-hub (>=0.22)"] +langchain = ["langchain (>=0.0.210)"] loguru = ["loguru (>=0.5)"] openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] @@ -2171,22 +2174,6 @@ starlette = ["starlette (>=0.19.1)"] starlite = ["starlite (>=1.48)"] tornado = ["tornado (>=5)"] -[[package]] -name = "setuptools" -version = "69.5.1" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, - {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - [[package]] name = "six" version = "1.16.0" @@ -2222,31 +2209,31 @@ files = [ [[package]] name = "typeguard" -version = "4.2.1" +version = "4.3.0" description = "Run-time type checker for Python" optional = false python-versions = ">=3.8" files = [ - {file = "typeguard-4.2.1-py3-none-any.whl", hash = "sha256:7da3bd46e61f03e0852f8d251dcbdc2a336aa495d7daff01e092b55327796eb8"}, - {file = "typeguard-4.2.1.tar.gz", hash = "sha256:c556a1b95948230510070ca53fa0341fb0964611bd05d598d87fb52115d65fee"}, + {file = "typeguard-4.3.0-py3-none-any.whl", hash = "sha256:4d24c5b39a117f8a895b9da7a9b3114f04eb63bade45a4492de49b175b6f7dfa"}, + {file = "typeguard-4.3.0.tar.gz", hash = "sha256:92ee6a0aec9135181eae6067ebd617fd9de8d75d714fb548728a4933b1dea651"}, ] [package.dependencies] -typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\""} +typing-extensions = ">=4.10.0" [package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.3.0)"] test = ["coverage[toml] (>=7)", "mypy (>=1.2.0)", "pytest (>=7)"] [[package]] name = "typing-extensions" -version = "4.11.0" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, - {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] @@ -2312,13 +2299,13 @@ test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)" [[package]] name = "virtualenv" -version = "20.26.1" +version = "20.26.2" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.26.1-py3-none-any.whl", hash = "sha256:7aa9982a728ae5892558bff6a2839c00b9ed145523ece2274fad6f414690ae75"}, - {file = "virtualenv-20.26.1.tar.gz", hash = "sha256:604bfdceaeece392802e6ae48e69cec49168b9c5f4a44e483963f9242eb0e78b"}, + {file = "virtualenv-20.26.2-py3-none-any.whl", hash = "sha256:a624db5e94f01ad993d476b9ee5346fdf7b9de43ccaee0e0197012dc838a0e9b"}, + {file = "virtualenv-20.26.2.tar.gz", hash = "sha256:82bf0f4eebbb78d36ddaee0283d43fe5736b53880b8a8cdcd37390a07ac3741c"}, ] [package.dependencies] @@ -2332,40 +2319,43 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "watchdog" -version = "4.0.0" +version = "4.0.1" description = "Filesystem events monitoring" optional = false python-versions = ">=3.8" files = [ - {file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:39cb34b1f1afbf23e9562501673e7146777efe95da24fab5707b88f7fb11649b"}, - {file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c522392acc5e962bcac3b22b9592493ffd06d1fc5d755954e6be9f4990de932b"}, - {file = "watchdog-4.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c47bdd680009b11c9ac382163e05ca43baf4127954c5f6d0250e7d772d2b80c"}, - {file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8350d4055505412a426b6ad8c521bc7d367d1637a762c70fdd93a3a0d595990b"}, - {file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c17d98799f32e3f55f181f19dd2021d762eb38fdd381b4a748b9f5a36738e935"}, - {file = "watchdog-4.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4986db5e8880b0e6b7cd52ba36255d4793bf5cdc95bd6264806c233173b1ec0b"}, - {file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:11e12fafb13372e18ca1bbf12d50f593e7280646687463dd47730fd4f4d5d257"}, - {file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5369136a6474678e02426bd984466343924d1df8e2fd94a9b443cb7e3aa20d19"}, - {file = "watchdog-4.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76ad8484379695f3fe46228962017a7e1337e9acadafed67eb20aabb175df98b"}, - {file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:45cc09cc4c3b43fb10b59ef4d07318d9a3ecdbff03abd2e36e77b6dd9f9a5c85"}, - {file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eed82cdf79cd7f0232e2fdc1ad05b06a5e102a43e331f7d041e5f0e0a34a51c4"}, - {file = "watchdog-4.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba30a896166f0fee83183cec913298151b73164160d965af2e93a20bbd2ab605"}, - {file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d18d7f18a47de6863cd480734613502904611730f8def45fc52a5d97503e5101"}, - {file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2895bf0518361a9728773083908801a376743bcc37dfa252b801af8fd281b1ca"}, - {file = "watchdog-4.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87e9df830022488e235dd601478c15ad73a0389628588ba0b028cb74eb72fed8"}, - {file = "watchdog-4.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6e949a8a94186bced05b6508faa61b7adacc911115664ccb1923b9ad1f1ccf7b"}, - {file = "watchdog-4.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6a4db54edea37d1058b08947c789a2354ee02972ed5d1e0dca9b0b820f4c7f92"}, - {file = "watchdog-4.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d31481ccf4694a8416b681544c23bd271f5a123162ab603c7d7d2dd7dd901a07"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8fec441f5adcf81dd240a5fe78e3d83767999771630b5ddfc5867827a34fa3d3"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:6a9c71a0b02985b4b0b6d14b875a6c86ddea2fdbebd0c9a720a806a8bbffc69f"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:557ba04c816d23ce98a06e70af6abaa0485f6d94994ec78a42b05d1c03dcbd50"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0f9bd1fd919134d459d8abf954f63886745f4660ef66480b9d753a7c9d40927"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f9b2fdca47dc855516b2d66eef3c39f2672cbf7e7a42e7e67ad2cbfcd6ba107d"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:73c7a935e62033bd5e8f0da33a4dcb763da2361921a69a5a95aaf6c93aa03a87"}, - {file = "watchdog-4.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6a80d5cae8c265842c7419c560b9961561556c4361b297b4c431903f8c33b269"}, - {file = "watchdog-4.0.0-py3-none-win32.whl", hash = "sha256:8f9a542c979df62098ae9c58b19e03ad3df1c9d8c6895d96c0d51da17b243b1c"}, - {file = "watchdog-4.0.0-py3-none-win_amd64.whl", hash = "sha256:f970663fa4f7e80401a7b0cbeec00fa801bf0287d93d48368fc3e6fa32716245"}, - {file = "watchdog-4.0.0-py3-none-win_ia64.whl", hash = "sha256:9a03e16e55465177d416699331b0f3564138f1807ecc5f2de9d55d8f188d08c7"}, - {file = "watchdog-4.0.0.tar.gz", hash = "sha256:e3e7065cbdabe6183ab82199d7a4f6b3ba0a438c5a512a68559846ccb76a78ec"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"}, + {file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"}, + {file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"}, + {file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d4925e4bf7b9bddd1c3de13c9b8a2cdb89a468f640e66fbfabaf735bd85b3e35"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cad0bbd66cd59fc474b4a4376bc5ac3fc698723510cbb64091c2a793b18654db"}, + {file = "watchdog-4.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a3c2c317a8fb53e5b3d25790553796105501a235343f5d2bf23bb8649c2c8709"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c9904904b6564d4ee8a1ed820db76185a3c96e05560c776c79a6ce5ab71888ba"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:667f3c579e813fcbad1b784db7a1aaa96524bed53437e119f6a2f5de4db04235"}, + {file = "watchdog-4.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d10a681c9a1d5a77e75c48a3b8e1a9f2ae2928eda463e8d33660437705659682"}, + {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"}, + {file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"}, + {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7921319fe4430b11278d924ef66d4daa469fafb1da679a2e48c935fa27af193"}, + {file = "watchdog-4.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f0de0f284248ab40188f23380b03b59126d1479cd59940f2a34f8852db710625"}, + {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bca36be5707e81b9e6ce3208d92d95540d4ca244c006b61511753583c81c70dd"}, + {file = "watchdog-4.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ab998f567ebdf6b1da7dc1e5accfaa7c6992244629c0fdaef062f43249bd8dee"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"}, + {file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"}, + {file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"}, + {file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"}, + {file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"}, + {file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"}, ] [package.extras] @@ -2373,13 +2363,13 @@ watchmedo = ["PyYAML (>=3.10)"] [[package]] name = "wcmatch" -version = "8.5.1" +version = "8.5.2" description = "Wildcard/glob file name matcher." optional = false python-versions = ">=3.8" files = [ - {file = "wcmatch-8.5.1-py3-none-any.whl", hash = "sha256:24c19cedc92bc9c9e27f39db4e1824d72f95bd2cea32b254a47a45b1a1b227ed"}, - {file = "wcmatch-8.5.1.tar.gz", hash = "sha256:c0088c7f6426cf6bf27e530e2b7b734031905f7e490475fd83c7c5008ab581b3"}, + {file = "wcmatch-8.5.2-py3-none-any.whl", hash = "sha256:17d3ad3758f9d0b5b4dedc770b65420d4dac62e680229c287bf24c9db856a478"}, + {file = "wcmatch-8.5.2.tar.gz", hash = "sha256:a70222b86dea82fb382dd87b73278c10756c138bd6f8f714e2183128887b9eb2"}, ] [package.dependencies] @@ -2502,4 +2492,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.10,<4.0" -content-hash = "57341bebc704ccbabbe982a32701954ad1d7a9869a91dd1a4b0fca4788b6dd05" +content-hash = "677f01dd282ea84b1f1f1e5b4be927d42fb1675d5fc942d75148e2d42797b925" diff --git a/pyproject.toml b/pyproject.toml index 618c7ef7e..d4e2d8501 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "interactions.py" -version = "5.12.1" +version = "5.13.0" description = "Easy, simple, scalable and modular: a Python API wrapper for interactions." authors = ["LordOfPolls "] @@ -9,7 +9,7 @@ python = ">=3.10,<4.0" aiohttp = "^3.8.3" attrs = ">=22.1.0" mypy = ">0.930" -discord-typings = "^0.7.0" +discord-typings = "^0.9.0" tomli = "^2.0.1" emoji = "^2.1.0" croniter = "^2.0.2" diff --git a/requirements.txt b/requirements.txt index e0ca43cb6..293eabbec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ aiohttp attrs>=22.1 croniter -discord-typings>=0.7.0 +discord-typings>=0.9.0 emoji tomli