From 6cabb6fb32e5201279c333ee034c8abe89c9489f Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Fri, 7 Nov 2025 17:15:57 +0200 Subject: [PATCH 01/38] Add support for editing application info and new fields Introduces the AppInfo.edit coroutine to allow editing application settings. Updates AppInfo and related types to support new fields such as bot, flags, event webhooks, integration_types_config, and approximate_user_authorization_count. Also refactors type hints and improves handling of optional fields for better API compatibility. --- discord/appinfo.py | 85 +++++++++++++++++++++++++++++++++++++++- discord/client.py | 19 +++------ discord/http.py | 5 +++ discord/types/appinfo.py | 78 ++++++++++++++++++++++++------------ 4 files changed, 149 insertions(+), 38 deletions(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index 034b1bb158..441e72e3aa 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -161,6 +161,7 @@ class AppInfo: "bot_public", "bot_require_code_grant", "owner", + "bot", "_icon", "_summary", "verify_key", @@ -173,9 +174,15 @@ class AppInfo: "privacy_policy_url", "approximate_guild_count", "approximate_user_install_count", + "approximate_user_authorization_count", + "flags", "redirect_uris", "interactions_endpoint_url", "role_connections_verification_url", + "event_webhooks_url", + "event_webhooks_status", + "event_webhooks_types", + "integration_types_config", "install_params", "tags", "custom_install_url", @@ -189,7 +196,7 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload): self.name: str = data["name"] self.description: str = data["description"] self._icon: str | None = data["icon"] - self.rpc_origins: list[str] = data["rpc_origins"] + self.rpc_origins: list[str] | None = data.get("rpc_origins") self.bot_public: bool = data["bot_public"] self.bot_require_code_grant: bool = data["bot_require_code_grant"] self.owner: User = state.create_user(data["owner"]) @@ -199,6 +206,7 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload): self._summary: str = data["summary"] self.verify_key: str = data["verify_key"] + self.bot: User | None = data.get("bot") and state.create_user(data["bot"]) self.guild_id: int | None = utils._get_as_snowflake(data, "guild_id") @@ -213,6 +221,10 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload): self.approximate_user_install_count: int | None = data.get( "approximate_user_install_count" ) + self.approximate_user_authorization_count: int | None = data.get( + "approximate_user_authorization_count" + ) + self.flags: int | None = data.get("flags") self.redirect_uris: list[str] | None = data.get("redirect_uris", []) self.interactions_endpoint_url: str | None = data.get( "interactions_endpoint_url" @@ -220,6 +232,9 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload): self.role_connections_verification_url: str | None = data.get( "role_connections_verification_url" ) + self.event_webhooks_url: str | None = data.get("event_webhooks_url") + self.event_webhooks_status: int | None = data.get("event_webhooks_status") + self.event_webhooks_types: list[str] | None = data.get("event_webhooks_types") install_params = data.get("install_params") self.install_params: AppInstallParams | None = ( @@ -227,6 +242,9 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload): ) self.tags: list[str] | None = data.get("tags", []) self.custom_install_url: str | None = data.get("custom_install_url") + self.integration_types_config: dict[int, dict[str, object] | None] | None = ( + data.get("integration_types_config") + ) def __repr__(self) -> str: return ( @@ -235,6 +253,71 @@ def __repr__(self) -> str: f"owner={self.owner!r}>" ) + async def edit( + self, + *, + description: str | None = utils.MISSING, + terms_of_service_url: str | None = utils.MISSING, + privacy_policy_url: str | None = utils.MISSING, + role_connections_verification_url: str | None = utils.MISSING, + interactions_endpoint_url: str | None = utils.MISSING, + tags: list[str] | None = utils.MISSING, + install_params: dict | None = utils.MISSING, + custom_install_url: str | None = utils.MISSING, + ) -> "AppInfo": + """|coro| + + Edit the current application's settings. + + This method wraps the Edit Current Application endpoint and returns the updated application info. + + Parameters + ---------- + description: Optional[:class:`str`] + The new application description. Pass ``None`` to clear. + terms_of_service_url: Optional[:class:`str`] + The application's Terms of Service URL. + privacy_policy_url: Optional[:class:`str`] + The application's Privacy Policy URL. + role_connections_verification_url: Optional[:class:`str`] + The role connection verification URL for the application. + interactions_endpoint_url: Optional[:class:`str`] + The interactions endpoint callback URL. + tags: Optional[List[:class:`str`]] + List of tags for the application (max 5). + install_params: Optional[:class:`dict`] + Dict with keys ``scopes`` (list[str]) and ``permissions`` (str) used for default install link. + custom_install_url: Optional[:class:`str`] + The default custom authorization URL for the application. + + Returns + ------- + :class:`.AppInfo` + The updated application information. + """ + payload: dict[str, object] = {} + if description is not utils.MISSING: + payload["description"] = description + if terms_of_service_url is not utils.MISSING: + payload["terms_of_service_url"] = terms_of_service_url + if privacy_policy_url is not utils.MISSING: + payload["privacy_policy_url"] = privacy_policy_url + if role_connections_verification_url is not utils.MISSING: + payload["role_connections_verification_url"] = ( + role_connections_verification_url + ) + if interactions_endpoint_url is not utils.MISSING: + payload["interactions_endpoint_url"] = interactions_endpoint_url + if tags is not utils.MISSING: + payload["tags"] = tags + if install_params is not utils.MISSING: + payload["install_params"] = install_params + if custom_install_url is not utils.MISSING: + payload["custom_install_url"] = custom_install_url + + data = await self._state.http.edit_current_application(payload) + return AppInfo(self._state, data) + @property def icon(self) -> Asset | None: """Retrieves the application's icon asset, if any.""" diff --git a/discord/client.py b/discord/client.py index f339cbfe92..d1b693045f 100644 --- a/discord/client.py +++ b/discord/client.py @@ -59,7 +59,7 @@ from .invite import Invite from .iterators import EntitlementIterator, GuildIterator from .mentions import AllowedMentions -from .monetization import SKU, Entitlement +from .monetization import SKU from .object import Object from .soundboard import SoundboardSound from .stage_instance import StageInstance @@ -77,20 +77,15 @@ if TYPE_CHECKING: from .abc import GuildChannel, PrivateChannel, Snowflake, SnowflakeTime from .channel import ( - CategoryChannel, DMChannel, - ForumChannel, - StageChannel, - TextChannel, - VoiceChannel, ) from .interactions import Interaction from .member import Member from .message import Message from .poll import Poll from .soundboard import SoundboardSound - from .threads import Thread, ThreadMember - from .ui.item import Item, ViewItem + from .threads import Thread + from .ui.item import ViewItem from .voice_client import VoiceProtocol __all__ = ("Client",) @@ -249,9 +244,9 @@ def __init__( self.loop: asyncio.AbstractEventLoop = ( asyncio.get_event_loop() if loop is None else loop ) - self._listeners: dict[str, list[tuple[asyncio.Future, Callable[..., bool]]]] = ( - {} - ) + self._listeners: dict[ + str, list[tuple[asyncio.Future, Callable[..., bool]]] + ] = {} self.shard_id: int | None = options.get("shard_id") self.shard_count: int | None = options.get("shard_count") @@ -1922,8 +1917,6 @@ async def application_info(self) -> AppInfo: Retrieving the information failed somehow. """ data = await self.http.application_info() - if "rpc_origins" not in data: - data["rpc_origins"] = None return AppInfo(self._connection, data) async def fetch_user(self, user_id: int, /) -> User: diff --git a/discord/http.py b/discord/http.py index ae64703ba6..74cf19c541 100644 --- a/discord/http.py +++ b/discord/http.py @@ -3213,6 +3213,11 @@ def get_answer_voters( def application_info(self) -> Response[appinfo.AppInfo]: return self.request(Route("GET", "/oauth2/applications/@me")) + def edit_current_application( + self, payload: dict[str, Any] + ) -> Response[appinfo.AppInfo]: + return self.request(Route("PATCH", "/applications/@me"), json=payload) + def get_application( self, application_id: Snowflake, / ) -> Response[appinfo.PartialAppInfo]: diff --git a/discord/types/appinfo.py b/discord/types/appinfo.py index c22f665745..a5b3c6311e 100644 --- a/discord/types/appinfo.py +++ b/discord/types/appinfo.py @@ -25,43 +25,73 @@ from __future__ import annotations +from typing import Literal from typing_extensions import NotRequired, TypedDict from .snowflake import Snowflake from .team import Team -from .user import User +from .guild import Guild +from .user import PartialUser + + +ApplicationIntegrationType = Literal[0, 1] +ApplicationEventWebhookStatus = Literal[1, 2, 3] + + +class AppInstallParams(TypedDict): + scopes: list[str] + permissions: str + + +class ApplicationIntegrationTypeConfiguration(TypedDict, total=False): + oauth2_install_params: AppInstallParams class BaseAppInfo(TypedDict): id: Snowflake name: str - verify_key: str - icon: str | None - summary: str description: str - terms_of_service_url: NotRequired[str] - privacy_policy_url: NotRequired[str] - hook: NotRequired[bool] - max_participants: NotRequired[int] - + verify_key: str + # Deprecated by Discord but still present in some payloads; prefer 'description'. + summary: NotRequired[str] -class AppInfo(BaseAppInfo): - team: NotRequired[Team] + icon: NotRequired[str | None] + cover_image: NotRequired[str] guild_id: NotRequired[Snowflake] - primary_sku_id: NotRequired[Snowflake] - slug: NotRequired[str] - rpc_origins: list[str] - owner: User - bot_public: bool - bot_require_code_grant: bool + guild: NotRequired[Guild] + bot: NotRequired[PartialUser] + owner: NotRequired[PartialUser] + team: NotRequired[Team | None] + rpc_origins: NotRequired[list[str]] + bot_public: NotRequired[bool] + bot_require_code_grant: NotRequired[bool] + terms_of_service_url: NotRequired[str | None] + privacy_policy_url: NotRequired[str | None] + tags: NotRequired[list[str]] + install_params: NotRequired[AppInstallParams] + custom_install_url: NotRequired[str] + integration_types_config: NotRequired[ + dict[ + ApplicationIntegrationType, + ApplicationIntegrationTypeConfiguration | None, + ] + ] -class PartialAppInfo(BaseAppInfo): - rpc_origins: NotRequired[list[str]] - cover_image: NotRequired[str] - flags: NotRequired[int] +class AppInfo(BaseAppInfo, total=False): + primary_sku_id: Snowflake + slug: str + flags: int + approximate_guild_count: int + approximate_user_install_count: int + approximate_user_authorization_count: int + redirect_uris: list[str] + interactions_endpoint_url: str | None + role_connections_verification_url: str | None + event_webhooks_url: str | None + event_webhooks_status: ApplicationEventWebhookStatus + event_webhooks_types: list[str] -class AppInstallParams(TypedDict): - scopes: list[str] - permissions: str +class PartialAppInfo(BaseAppInfo, total=False): + pass From 2f54fe792708859bb8d62ee0ec615b6edcaa6d6a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 15:17:42 +0000 Subject: [PATCH 02/38] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/appinfo.py | 2 +- discord/client.py | 6 +++--- discord/types/appinfo.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index 441e72e3aa..f61f7a3c5b 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -264,7 +264,7 @@ async def edit( tags: list[str] | None = utils.MISSING, install_params: dict | None = utils.MISSING, custom_install_url: str | None = utils.MISSING, - ) -> "AppInfo": + ) -> AppInfo: """|coro| Edit the current application's settings. diff --git a/discord/client.py b/discord/client.py index d1b693045f..9b949a2781 100644 --- a/discord/client.py +++ b/discord/client.py @@ -244,9 +244,9 @@ def __init__( self.loop: asyncio.AbstractEventLoop = ( asyncio.get_event_loop() if loop is None else loop ) - self._listeners: dict[ - str, list[tuple[asyncio.Future, Callable[..., bool]]] - ] = {} + self._listeners: dict[str, list[tuple[asyncio.Future, Callable[..., bool]]]] = ( + {} + ) self.shard_id: int | None = options.get("shard_id") self.shard_count: int | None = options.get("shard_count") diff --git a/discord/types/appinfo.py b/discord/types/appinfo.py index a5b3c6311e..afb95dcc0b 100644 --- a/discord/types/appinfo.py +++ b/discord/types/appinfo.py @@ -26,14 +26,14 @@ from __future__ import annotations from typing import Literal + from typing_extensions import NotRequired, TypedDict +from .guild import Guild from .snowflake import Snowflake from .team import Team -from .guild import Guild from .user import PartialUser - ApplicationIntegrationType = Literal[0, 1] ApplicationEventWebhookStatus = Literal[1, 2, 3] From fab14785db0053ef72b166c5f478fa54af0b2974 Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Fri, 7 Nov 2025 20:00:21 +0200 Subject: [PATCH 03/38] Add AppInfo.edit() and missing fields support Introduces the AppInfo.edit() method to allow editing application settings, including new and previously missing fields such as icon, cover_image, tags, install_params, integration_types_config, flags, event_webhooks_url, event_webhooks_status, and event_webhooks_types. Also adds related helper classes and properties for handling these fields and updates the CHANGELOG accordingly. --- CHANGELOG.md | 2 + discord/appinfo.py | 208 ++++++++++++++++++++++++++++++++++----- discord/types/appinfo.py | 2 - 3 files changed, 188 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35321c2858..ae6887be2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ These changes are available on the `master` branch, but have not yet been releas - Added `Attachment.read_chunked` and added optional `chunksize` argument to `Attachment.save` for retrieving attachments in chunks. ([#2956](https://github.com/Pycord-Development/pycord/pull/2956)) +- Added `AppInfo.edit()` method and missing fields. + ([#2994](https://github.com/Pycord-Development/pycord/pull/2994)) ### Changed diff --git a/discord/appinfo.py b/discord/appinfo.py index f61f7a3c5b..9f3272ebb3 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -30,6 +30,7 @@ from . import utils from .asset import Asset from .permissions import Permissions +from .flags import ApplicationFlags if TYPE_CHECKING: from .guild import Guild @@ -44,6 +45,7 @@ "AppInfo", "PartialAppInfo", "AppInstallParams", + "IntegrationTypesConfig", ) @@ -134,11 +136,33 @@ class AppInfo: .. versionadded:: 2.7 - install_params: Optional[List[:class:`AppInstallParams`]] + install_params: Optional[:class:`AppInstallParams`] The settings for the application's default in-app authorization link, if set. .. versionadded:: 2.7 + integration_types_config: Optional[Dict[:class:`int`, Optional[Dict[:class:`str`, Any]]]] + Per-installation context configuration. Keys are ``0`` (guild) and ``1`` (user) mapping to an object containing + ``oauth2_install_params`` or ``None`` if cleared. + + .. versionadded:: 2.7 + + event_webhooks_url: Optional[:class:`str`] + The URL used to receive application event webhooks, if set. + + .. versionadded:: 2.7 + + event_webhooks_status: Optional[:class:`int`] + The raw event webhooks status integer from the API (``2`` enabled, ``1`` disabled) if present. + Prefer :attr:`event_webhooks_enabled` for a boolean form. + + .. versionadded:: 2.7 + + event_webhooks_types: Optional[List[:class:`str`]] + List of event webhook types subscribed to, if set. + + .. versionadded:: 2.7 + tags: Optional[List[:class:`str`]] The list of tags describing the content and functionality of the app, if set. @@ -149,6 +173,16 @@ class AppInfo: custom_install_url: Optional[:class:`str`] The default custom authorization URL for the application, if set. + .. versionadded:: 2.7 + + approximate_user_authorization_count: Optional[:class:`int`] + The approximate count of users who have authorized the application, if any. + + .. versionadded:: 2.7 + + flags: Optional[:class:`ApplicationFlags`] + The public application flags, if set. + .. versionadded:: 2.7 """ @@ -175,7 +209,7 @@ class AppInfo: "approximate_guild_count", "approximate_user_install_count", "approximate_user_authorization_count", - "flags", + "_flags", "redirect_uris", "interactions_endpoint_url", "role_connections_verification_url", @@ -224,7 +258,8 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload): self.approximate_user_authorization_count: int | None = data.get( "approximate_user_authorization_count" ) - self.flags: int | None = data.get("flags") + raw_flags = data.get("flags") + self._flags: int | None = raw_flags if isinstance(raw_flags, int) else None self.redirect_uris: list[str] | None = data.get("redirect_uris", []) self.interactions_endpoint_url: str | None = data.get( "interactions_endpoint_url" @@ -253,18 +288,35 @@ def __repr__(self) -> str: f"owner={self.owner!r}>" ) + @property + def flags(self) -> ApplicationFlags | None: + """The public application flags, if set. + + Returns an :class:`ApplicationFlags` instance or ``None`` when not present. + """ + if self._flags is None: + return None + return ApplicationFlags._from_value(self._flags) + async def edit( self, *, description: str | None = utils.MISSING, + icon: bytes | str | None = utils.MISSING, + cover_image: bytes | str | None = utils.MISSING, + tags: list[str] | None = utils.MISSING, terms_of_service_url: str | None = utils.MISSING, privacy_policy_url: str | None = utils.MISSING, - role_connections_verification_url: str | None = utils.MISSING, interactions_endpoint_url: str | None = utils.MISSING, - tags: list[str] | None = utils.MISSING, - install_params: dict | None = utils.MISSING, + role_connections_verification_url: str | None = utils.MISSING, + install_params: "AppInstallParams | None" = utils.MISSING, custom_install_url: str | None = utils.MISSING, - ) -> AppInfo: + integration_types_config: "IntegrationTypesConfig | None" = utils.MISSING, + flags: ApplicationFlags | None = utils.MISSING, + event_webhooks_url: str | None = utils.MISSING, + event_webhooks_status: bool = utils.MISSING, + event_webhooks_types: list[str] | None = utils.MISSING, + ) -> "AppInfo": """|coro| Edit the current application's settings. @@ -275,20 +327,38 @@ async def edit( ---------- description: Optional[:class:`str`] The new application description. Pass ``None`` to clear. + icon: Optional[Union[:class:`bytes`, :class:`str`]] + New icon image. If ``bytes`` is given it will be base64 encoded automatically. If a ``str`` is given it is assumed + to be a pre-encoded base64 data URI or hash and sent as-is. Pass ``None`` to clear. + cover_image: Optional[Union[:class:`bytes`, :class:`str`]] + New cover image for the store embed. If ``bytes`` is given it will be base64 encoded automatically. If a ``str`` is given it is assumed + to be a pre-encoded base64 data URI or hash and sent as-is. Pass ``None`` to clear. + tags: Optional[List[:class:`str`]] + List of tags for the application (max 5). Pass ``None`` to clear. terms_of_service_url: Optional[:class:`str`] - The application's Terms of Service URL. + The application's Terms of Service URL. Pass ``None`` to clear. privacy_policy_url: Optional[:class:`str`] - The application's Privacy Policy URL. - role_connections_verification_url: Optional[:class:`str`] - The role connection verification URL for the application. + The application's Privacy Policy URL. Pass ``None`` to clear. interactions_endpoint_url: Optional[:class:`str`] - The interactions endpoint callback URL. - tags: Optional[List[:class:`str`]] - List of tags for the application (max 5). - install_params: Optional[:class:`dict`] - Dict with keys ``scopes`` (list[str]) and ``permissions`` (str) used for default install link. + The interactions endpoint callback URL. Pass ``None`` to clear. + role_connections_verification_url: Optional[:class:`str`] + The role connection verification URL for the application. Pass ``None`` to clear. + install_params: Optional[:class:`AppInstallParams`] + Settings for the application's default in-app authorization link. Pass ``None`` to clear. Omit entirely to leave unchanged. custom_install_url: Optional[:class:`str`] - The default custom authorization URL for the application. + The default custom authorization URL for the application. Pass ``None`` to clear. + integration_types_config: Optional[:class:`IntegrationTypesConfig`] + Object specifying per-installation context configuration (guild and/or user). You may set contexts individually + and omit others to leave them unchanged. Pass the object with a context explicitly set to ``None`` to clear just that + context, or pass ``None`` to clear the entire integration types configuration. + flags: Optional[:class:`ApplicationFlags`] + Application public flags. Pass ``None`` to clear (not typical). + event_webhooks_url: Optional[:class:`str`] + Event webhooks callback URL for receiving application webhook events. Pass ``None`` to clear. + event_webhooks_status: :class:`bool` + Whether webhook events are enabled. ``True`` maps to API value ``2`` (enabled), ``False`` maps to ``1`` (disabled). + event_webhooks_types: Optional[List[:class:`str`]] + List of webhook event types to subscribe to. Pass ``None`` to clear. Returns ------- @@ -298,22 +368,50 @@ async def edit( payload: dict[str, object] = {} if description is not utils.MISSING: payload["description"] = description + if icon is not utils.MISSING: + if icon is None: + payload["icon"] = None + else: + payload["icon"] = utils._bytes_to_base64_data(icon) + if cover_image is not utils.MISSING: + if cover_image is None: + payload["cover_image"] = None + else: + payload["cover_image"] = utils._bytes_to_base64_data(cover_image) + if tags is not utils.MISSING: + payload["tags"] = tags if terms_of_service_url is not utils.MISSING: payload["terms_of_service_url"] = terms_of_service_url if privacy_policy_url is not utils.MISSING: payload["privacy_policy_url"] = privacy_policy_url + if interactions_endpoint_url is not utils.MISSING: + payload["interactions_endpoint_url"] = interactions_endpoint_url if role_connections_verification_url is not utils.MISSING: payload["role_connections_verification_url"] = ( role_connections_verification_url ) - if interactions_endpoint_url is not utils.MISSING: - payload["interactions_endpoint_url"] = interactions_endpoint_url - if tags is not utils.MISSING: - payload["tags"] = tags if install_params is not utils.MISSING: - payload["install_params"] = install_params + if install_params is None: + payload["install_params"] = None + else: + payload["install_params"] = install_params.to_payload() if custom_install_url is not utils.MISSING: payload["custom_install_url"] = custom_install_url + if integration_types_config is not utils.MISSING: + if integration_types_config is None: + payload["integration_types_config"] = None + else: + payload["integration_types_config"] = ( + integration_types_config.to_payload() + ) + if flags is not utils.MISSING: + payload["flags"] = None if flags is None else flags.value + if event_webhooks_url is not utils.MISSING: + payload["event_webhooks_url"] = event_webhooks_url + if event_webhooks_status is not utils.MISSING: + payload["event_webhooks_status"] = 2 if event_webhooks_status else 1 + if event_webhooks_types is not utils.MISSING: + payload["event_webhooks_types"] = event_webhooks_types data = await self._state.http.edit_current_application(payload) return AppInfo(self._state, data) @@ -361,6 +459,17 @@ def summary(self) -> str | None: ) return self._summary + @property + def event_webhooks_enabled(self) -> bool | None: + """Returns whether event webhooks are enabled. + + This is a convenience around :attr:`event_webhooks_status` where ``True`` means enabled and ``False`` means disabled. + ``None`` indicates the status is not present. + """ + if self.event_webhooks_status is None: + return None + return self.event_webhooks_status == 2 + class PartialAppInfo: """Represents a partial AppInfo given by :func:`~discord.abc.GuildChannel.create_invite` @@ -443,3 +552,58 @@ class AppInstallParams: def __init__(self, data: AppInstallParamsPayload) -> None: self.scopes: list[str] = data.get("scopes", []) self.permissions: Permissions = Permissions(int(data["permissions"])) + + def to_payload(self) -> dict[str, object]: + """Serialize this object into an application install params payload. + + Returns + ------- + Dict[str, Any] + A dict with ``scopes`` and ``permissions`` (string form) suitable for the API. + """ + if self.permissions.value > 0 and "bot" not in self.scopes: + raise ValueError( + "'bot' must be in install_params.scopes if permissions are requested" + ) + return { + "scopes": list(self.scopes), + "permissions": str(int(self.permissions.value)), + } + + +class IntegrationTypesConfig: + """Represents per-installation context configuration for an application. + + This object is used to build the payload for the ``integration_types_config`` field when editing an application. + + Parameters + ---------- + guild: Optional[:class:`AppInstallParams`] + The configuration for the guild installation context. Omit to leave unchanged; pass ``None`` to clear. + user: Optional[:class:`AppInstallParams`] + The configuration for the user installation context. Omit to leave unchanged; pass ``None`` to clear. + """ + + __slots__ = ("guild", "user") + + def __init__( + self, + *, + guild: AppInstallParams | None = utils.MISSING, + user: AppInstallParams | None = utils.MISSING, + ) -> None: + self.guild = guild + self.user = user + + def _encode_install_params(self, value: AppInstallParams | None) -> dict | None: + if value is None: + return None + return {"oauth2_install_params": value.to_payload()} + + def to_payload(self) -> dict[int, dict[str, object] | None]: + payload: dict[int, dict[str, object] | None] = {} + if self.guild is not utils.MISSING: + payload[0] = self._encode_install_params(self.guild) + if self.user is not utils.MISSING: + payload[1] = self._encode_install_params(self.user) + return payload diff --git a/discord/types/appinfo.py b/discord/types/appinfo.py index afb95dcc0b..8fdccf35f2 100644 --- a/discord/types/appinfo.py +++ b/discord/types/appinfo.py @@ -52,8 +52,6 @@ class BaseAppInfo(TypedDict): name: str description: str verify_key: str - # Deprecated by Discord but still present in some payloads; prefer 'description'. - summary: NotRequired[str] icon: NotRequired[str | None] cover_image: NotRequired[str] From bfa2940e55989549fe927cfd5f8aadb3be7f72e4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 18:00:56 +0000 Subject: [PATCH 04/38] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/appinfo.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index 9f3272ebb3..c65d878e8e 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -29,8 +29,8 @@ from . import utils from .asset import Asset -from .permissions import Permissions from .flags import ApplicationFlags +from .permissions import Permissions if TYPE_CHECKING: from .guild import Guild @@ -309,14 +309,14 @@ async def edit( privacy_policy_url: str | None = utils.MISSING, interactions_endpoint_url: str | None = utils.MISSING, role_connections_verification_url: str | None = utils.MISSING, - install_params: "AppInstallParams | None" = utils.MISSING, + install_params: AppInstallParams | None = utils.MISSING, custom_install_url: str | None = utils.MISSING, - integration_types_config: "IntegrationTypesConfig | None" = utils.MISSING, + integration_types_config: IntegrationTypesConfig | None = utils.MISSING, flags: ApplicationFlags | None = utils.MISSING, event_webhooks_url: str | None = utils.MISSING, event_webhooks_status: bool = utils.MISSING, event_webhooks_types: list[str] | None = utils.MISSING, - ) -> "AppInfo": + ) -> AppInfo: """|coro| Edit the current application's settings. From 9893239a108696e0a615db3bd498a7cbd481aae2 Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Fri, 7 Nov 2025 19:08:13 +0100 Subject: [PATCH 05/38] Update discord/appinfo.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Lumouille <144063653+Lumabots@users.noreply.github.com> --- discord/appinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index c65d878e8e..cc755e4858 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -229,7 +229,7 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload): self.id: int = int(data["id"]) self.name: str = data["name"] self.description: str = data["description"] - self._icon: str | None = data["icon"] + self._icon: str | None = data.get("icon") self.rpc_origins: list[str] | None = data.get("rpc_origins") self.bot_public: bool = data["bot_public"] self.bot_require_code_grant: bool = data["bot_require_code_grant"] From 884cd6cd849fca9d0ba3f11b0a5218d847d4d800 Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Fri, 7 Nov 2025 19:10:09 +0100 Subject: [PATCH 06/38] Update discord/appinfo.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Lumouille <144063653+Lumabots@users.noreply.github.com> --- discord/appinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index cc755e4858..f45bc251f9 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -275,7 +275,7 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload): self.install_params: AppInstallParams | None = ( AppInstallParams(install_params) if install_params else None ) - self.tags: list[str] | None = data.get("tags", []) + self.tags: list[str] = data.get("tags", []) self.custom_install_url: str | None = data.get("custom_install_url") self.integration_types_config: dict[int, dict[str, object] | None] | None = ( data.get("integration_types_config") From 41f12a2fee232498fd9ff9e63134a4245c0512dc Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Fri, 7 Nov 2025 19:10:31 +0100 Subject: [PATCH 07/38] Update discord/appinfo.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Lumouille <144063653+Lumabots@users.noreply.github.com> --- discord/appinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index f45bc251f9..2b586c1ad7 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -260,7 +260,7 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload): ) raw_flags = data.get("flags") self._flags: int | None = raw_flags if isinstance(raw_flags, int) else None - self.redirect_uris: list[str] | None = data.get("redirect_uris", []) + self.redirect_uris: list[str] = data.get("redirect_uris", []) self.interactions_endpoint_url: str | None = data.get( "interactions_endpoint_url" ) From 714c68aa02480d8d85c065e04ba58f9297bb1da9 Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Fri, 7 Nov 2025 19:10:43 +0100 Subject: [PATCH 08/38] Update discord/appinfo.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Lumouille <144063653+Lumabots@users.noreply.github.com> --- discord/appinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index 2b586c1ad7..0c9a5e7501 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -595,7 +595,7 @@ def __init__( self.guild = guild self.user = user - def _encode_install_params(self, value: AppInstallParams | None) -> dict | None: + def _encode_install_params(self, value: AppInstallParams | None) -> dict[str, object] | None: if value is None: return None return {"oauth2_install_params": value.to_payload()} From 734b5ceca89b29c08158fe4d798d531bedb626c4 Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Fri, 7 Nov 2025 19:10:58 +0100 Subject: [PATCH 09/38] Update discord/appinfo.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Lumouille <144063653+Lumabots@users.noreply.github.com> --- discord/appinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index 0c9a5e7501..20a5a6ce90 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -567,7 +567,7 @@ def to_payload(self) -> dict[str, object]: ) return { "scopes": list(self.scopes), - "permissions": str(int(self.permissions.value)), + "permissions": str(self.permissions.value), } From 49c90b9f4d73c321c15e6690687153e529911294 Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Fri, 7 Nov 2025 19:11:20 +0100 Subject: [PATCH 10/38] Update discord/appinfo.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Lumouille <144063653+Lumabots@users.noreply.github.com> --- discord/appinfo.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index 20a5a6ce90..7bb0ea6e87 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -231,9 +231,10 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload): self.description: str = data["description"] self._icon: str | None = data.get("icon") self.rpc_origins: list[str] | None = data.get("rpc_origins") - self.bot_public: bool = data["bot_public"] - self.bot_require_code_grant: bool = data["bot_require_code_grant"] - self.owner: User = state.create_user(data["owner"]) + self.bot_public: bool = data.get("bot_public", False) + self.bot_require_code_grant: bool = data.get("bot_require_code_grant", False) + owner_data = data.get("owner") + self.owner: User | None = state.create_user(owner_data) if owner_data is not None else None team: TeamPayload | None = data.get("team") self.team: Team | None = Team(state, team) if team else None From 76eb8838ccae9832a554f2b31ff008c6129738a4 Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Fri, 7 Nov 2025 20:12:22 +0200 Subject: [PATCH 11/38] Restrict icon and cover_image types to bytes or None Updated the AppInfo class to only accept bytes or None for the icon and cover_image parameters, removing support for str. This change clarifies the expected types and may prevent type-related errors. --- discord/appinfo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index 7bb0ea6e87..0bfafc0722 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -303,8 +303,8 @@ async def edit( self, *, description: str | None = utils.MISSING, - icon: bytes | str | None = utils.MISSING, - cover_image: bytes | str | None = utils.MISSING, + icon: bytes | None = utils.MISSING, + cover_image: bytes | None = utils.MISSING, tags: list[str] | None = utils.MISSING, terms_of_service_url: str | None = utils.MISSING, privacy_policy_url: str | None = utils.MISSING, From bd917890dc2828216b25d9b9c0f8c5da892bed34 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 18:12:20 +0000 Subject: [PATCH 12/38] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/appinfo.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index 7bb0ea6e87..aec596b053 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -234,7 +234,9 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload): self.bot_public: bool = data.get("bot_public", False) self.bot_require_code_grant: bool = data.get("bot_require_code_grant", False) owner_data = data.get("owner") - self.owner: User | None = state.create_user(owner_data) if owner_data is not None else None + self.owner: User | None = ( + state.create_user(owner_data) if owner_data is not None else None + ) team: TeamPayload | None = data.get("team") self.team: Team | None = Team(state, team) if team else None @@ -596,7 +598,9 @@ def __init__( self.guild = guild self.user = user - def _encode_install_params(self, value: AppInstallParams | None) -> dict[str, object] | None: + def _encode_install_params( + self, value: AppInstallParams | None + ) -> dict[str, object] | None: if value is None: return None return {"oauth2_install_params": value.to_payload()} From 7e5ff542e9fb8906178249daca72cc3a7ecc145a Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Fri, 7 Nov 2025 20:28:43 +0200 Subject: [PATCH 13/38] feat(emoji): add mention property to BaseEmoji for easier emoji referencing --- discord/appinfo.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index 0bfafc0722..9183716726 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -183,6 +183,11 @@ class AppInfo: flags: Optional[:class:`ApplicationFlags`] The public application flags, if set. + .. versionadded:: 2.7 + + bot: Optional[:class:`User`] + The bot user associated with this application, if any. + .. versionadded:: 2.7 """ @@ -234,7 +239,9 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload): self.bot_public: bool = data.get("bot_public", False) self.bot_require_code_grant: bool = data.get("bot_require_code_grant", False) owner_data = data.get("owner") - self.owner: User | None = state.create_user(owner_data) if owner_data is not None else None + self.owner: User | None = ( + state.create_user(owner_data) if owner_data is not None else None + ) team: TeamPayload | None = data.get("team") self.team: Team | None = Team(state, team) if team else None @@ -596,7 +603,9 @@ def __init__( self.guild = guild self.user = user - def _encode_install_params(self, value: AppInstallParams | None) -> dict[str, object] | None: + def _encode_install_params( + self, value: AppInstallParams | None + ) -> dict[str, object] | None: if value is None: return None return {"oauth2_install_params": value.to_payload()} From 89d30110079dba616ee49d8b48952e5f042a6359 Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Tue, 11 Nov 2025 17:57:45 +0200 Subject: [PATCH 14/38] refactor(AppInfo): improve integration_types_config handling and simplify owner assignment --- discord/appinfo.py | 51 +++++++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index 9183716726..eed4f0fd18 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -141,9 +141,8 @@ class AppInfo: .. versionadded:: 2.7 - integration_types_config: Optional[Dict[:class:`int`, Optional[Dict[:class:`str`, Any]]]] - Per-installation context configuration. Keys are ``0`` (guild) and ``1`` (user) mapping to an object containing - ``oauth2_install_params`` or ``None`` if cleared. + integration_types_config: Optional[:class:`IntegrationTypesConfig`] + Per-installation context configuration for guild (``0``) and user (``1``) contexts. .. versionadded:: 2.7 @@ -238,10 +237,7 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload): self.rpc_origins: list[str] | None = data.get("rpc_origins") self.bot_public: bool = data.get("bot_public", False) self.bot_require_code_grant: bool = data.get("bot_require_code_grant", False) - owner_data = data.get("owner") - self.owner: User | None = ( - state.create_user(owner_data) if owner_data is not None else None - ) + self.owner: User | None = data.get("owner") and state.create_user(data["owner"]) team: TeamPayload | None = data.get("team") self.team: Team | None = Team(state, team) if team else None @@ -266,8 +262,7 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload): self.approximate_user_authorization_count: int | None = data.get( "approximate_user_authorization_count" ) - raw_flags = data.get("flags") - self._flags: int | None = raw_flags if isinstance(raw_flags, int) else None + self._flags: int | None = data.get("flags") self.redirect_uris: list[str] = data.get("redirect_uris", []) self.interactions_endpoint_url: str | None = data.get( "interactions_endpoint_url" @@ -279,13 +274,12 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload): self.event_webhooks_status: int | None = data.get("event_webhooks_status") self.event_webhooks_types: list[str] | None = data.get("event_webhooks_types") - install_params = data.get("install_params") - self.install_params: AppInstallParams | None = ( - AppInstallParams(install_params) if install_params else None + self.install_params: AppInstallParams | None = data.get("install_params") and ( + AppInstallParams(data["install_params"]) ) self.tags: list[str] = data.get("tags", []) self.custom_install_url: str | None = data.get("custom_install_url") - self.integration_types_config: dict[int, dict[str, object] | None] | None = ( + self.integration_types_config = IntegrationTypesConfig.from_payload( data.get("integration_types_config") ) @@ -301,6 +295,8 @@ def flags(self) -> ApplicationFlags | None: """The public application flags, if set. Returns an :class:`ApplicationFlags` instance or ``None`` when not present. + + .. versionadded:: 2.7 """ if self._flags is None: return None @@ -329,12 +325,10 @@ async def edit( Edit the current application's settings. - This method wraps the Edit Current Application endpoint and returns the updated application info. - Parameters ---------- description: Optional[:class:`str`] - The new application description. Pass ``None`` to clear. + The new application description or ``None`` to clear. icon: Optional[Union[:class:`bytes`, :class:`str`]] New icon image. If ``bytes`` is given it will be base64 encoded automatically. If a ``str`` is given it is assumed to be a pre-encoded base64 data URI or hash and sent as-is. Pass ``None`` to clear. @@ -372,6 +366,8 @@ async def edit( ------- :class:`.AppInfo` The updated application information. + + .. versionadded:: 2.7 """ payload: dict[str, object] = {} if description is not utils.MISSING: @@ -603,6 +599,29 @@ def __init__( self.guild = guild self.user = user + @classmethod + def from_payload(cls, data: dict | None) -> "IntegrationTypesConfig | None": + if data is None: + return None + + def _get_ctx(raw: dict, key: int): + if key in raw: + return raw[key] + skey = str(key) + return raw.get(skey) + + def _decode_ctx(value: dict | None) -> AppInstallParams | None: + if value is None: + return None + params = value.get("oauth2_install_params") + if not params: + return None + return AppInstallParams(params) + + guild_ctx = _decode_ctx(_get_ctx(data, 0)) + user_ctx = _decode_ctx(_get_ctx(data, 1)) + return cls(guild=guild_ctx, user=user_ctx) + def _encode_install_params( self, value: AppInstallParams | None ) -> dict[str, object] | None: From dfb79074d19bf0d1af8b89ff57bd38f0ca6f55b2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 15:58:25 +0000 Subject: [PATCH 15/38] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/appinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index eed4f0fd18..5b8fded5d0 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -600,7 +600,7 @@ def __init__( self.user = user @classmethod - def from_payload(cls, data: dict | None) -> "IntegrationTypesConfig | None": + def from_payload(cls, data: dict | None) -> IntegrationTypesConfig | None: if data is None: return None From 9eecbfa73a41def68e076b2cb0d15597cf72b068 Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:22:00 +0200 Subject: [PATCH 16/38] refactor(AppInfo): remove deprecated flags attribute from AppInfo class --- discord/appinfo.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index 5b8fded5d0..7c787261f1 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -179,11 +179,6 @@ class AppInfo: .. versionadded:: 2.7 - flags: Optional[:class:`ApplicationFlags`] - The public application flags, if set. - - .. versionadded:: 2.7 - bot: Optional[:class:`User`] The bot user associated with this application, if any. From 03a46e8bf54f1b5cc6e10e37ed207366a592c169 Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:06:58 +0200 Subject: [PATCH 17/38] refactor(AppInfo): rename event_webhooks_status to _event_webhooks_status and add setter for event_webhooks_enabled --- discord/appinfo.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index 7c787261f1..f12a6f7c5e 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -151,12 +151,6 @@ class AppInfo: .. versionadded:: 2.7 - event_webhooks_status: Optional[:class:`int`] - The raw event webhooks status integer from the API (``2`` enabled, ``1`` disabled) if present. - Prefer :attr:`event_webhooks_enabled` for a boolean form. - - .. versionadded:: 2.7 - event_webhooks_types: Optional[List[:class:`str`]] List of event webhook types subscribed to, if set. @@ -213,7 +207,7 @@ class AppInfo: "interactions_endpoint_url", "role_connections_verification_url", "event_webhooks_url", - "event_webhooks_status", + "_event_webhooks_status", "event_webhooks_types", "integration_types_config", "install_params", @@ -266,7 +260,7 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload): "role_connections_verification_url" ) self.event_webhooks_url: str | None = data.get("event_webhooks_url") - self.event_webhooks_status: int | None = data.get("event_webhooks_status") + self._event_webhooks_status: int | None = data.get("event_webhooks_status") self.event_webhooks_types: list[str] | None = data.get("event_webhooks_types") self.install_params: AppInstallParams | None = data.get("install_params") and ( @@ -461,13 +455,24 @@ def summary(self) -> str | None: @property def event_webhooks_enabled(self) -> bool | None: """Returns whether event webhooks are enabled. - - This is a convenience around :attr:`event_webhooks_status` where ``True`` means enabled and ``False`` means disabled. + This is a convenience around the raw API status integer where ``True`` means enabled and ``False`` means disabled. ``None`` indicates the status is not present. """ - if self.event_webhooks_status is None: + if self._event_webhooks_status is None: return None - return self.event_webhooks_status == 2 + return self._event_webhooks_status == 2 + + @event_webhooks_enabled.setter + def event_webhooks_enabled(self, value: bool | None) -> None: + """Set whether event webhooks are enabled. + + Setting to ``True`` maps to the API value ``2`` (enabled), ``False`` maps to ``1`` (disabled), + and ``None`` clears the status (sets it to ``None``). + """ + if value is None: + self._event_webhooks_status = None + return + self._event_webhooks_status = 2 if value else 1 class PartialAppInfo: From 0b603f5d7cbecbb9154d0630901141740b6b4132 Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:15:28 +0200 Subject: [PATCH 18/38] refactor(AppInfo): remove setter for event_webhooks_enabled and associated docstring --- discord/appinfo.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index f12a6f7c5e..ec33429581 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -462,18 +462,6 @@ def event_webhooks_enabled(self) -> bool | None: return None return self._event_webhooks_status == 2 - @event_webhooks_enabled.setter - def event_webhooks_enabled(self, value: bool | None) -> None: - """Set whether event webhooks are enabled. - - Setting to ``True`` maps to the API value ``2`` (enabled), ``False`` maps to ``1`` (disabled), - and ``None`` clears the status (sets it to ``None``). - """ - if value is None: - self._event_webhooks_status = None - return - self._event_webhooks_status = 2 if value else 1 - class PartialAppInfo: """Represents a partial AppInfo given by :func:`~discord.abc.GuildChannel.create_invite` From 3c398ccf0c23f827f68dd8fabb9a0d0c9d4ea02a Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Sat, 29 Nov 2025 07:33:30 +0100 Subject: [PATCH 19/38] Update CHANGELOG.md Co-authored-by: Paillat Signed-off-by: Lumouille <144063653+Lumabots@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cc07928a3..69cbdf0f98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ These changes are available on the `master` branch, but have not yet been releas - Added `Attachment.read_chunked` and added optional `chunksize` argument to `Attachment.save` for retrieving attachments in chunks. ([#2956](https://github.com/Pycord-Development/pycord/pull/2956)) -- Added `AppInfo.edit()` method and missing fields. +- Added `AppInfo.edit()` method and missing `AppInfo` fields. ([#2994](https://github.com/Pycord-Development/pycord/pull/2994)) ### Changed From 1b0017c97a6d938c05500cec2c4aba8052dc9ca2 Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Tue, 2 Dec 2025 19:53:07 +0200 Subject: [PATCH 20/38] refactor: update method names and types in AppInfo and HTTPClient --- discord/appinfo.py | 53 ++++++++++++++++++++++++---------------------- discord/http.py | 2 +- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/discord/appinfo.py b/discord/appinfo.py index ec33429581..a6476f3c57 100644 --- a/discord/appinfo.py +++ b/discord/appinfo.py @@ -25,7 +25,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from . import utils from .asset import Asset @@ -358,7 +358,7 @@ async def edit( .. versionadded:: 2.7 """ - payload: dict[str, object] = {} + payload: dict[str, Any] = {} if description is not utils.MISSING: payload["description"] = description if icon is not utils.MISSING: @@ -387,7 +387,7 @@ async def edit( if install_params is None: payload["install_params"] = None else: - payload["install_params"] = install_params.to_payload() + payload["install_params"] = install_params._to_payload() if custom_install_url is not utils.MISSING: payload["custom_install_url"] = custom_install_url if integration_types_config is not utils.MISSING: @@ -395,7 +395,7 @@ async def edit( payload["integration_types_config"] = None else: payload["integration_types_config"] = ( - integration_types_config.to_payload() + integration_types_config._to_payload() ) if flags is not utils.MISSING: payload["flags"] = None if flags is None else flags.value @@ -406,7 +406,7 @@ async def edit( if event_webhooks_types is not utils.MISSING: payload["event_webhooks_types"] = event_webhooks_types - data = await self._state.http.edit_current_application(payload) + data = await self._state.http.edit_current_application_info(payload) return AppInfo(self._state, data) @property @@ -545,7 +545,7 @@ def __init__(self, data: AppInstallParamsPayload) -> None: self.scopes: list[str] = data.get("scopes", []) self.permissions: Permissions = Permissions(int(data["permissions"])) - def to_payload(self) -> dict[str, object]: + def _to_payload(self) -> dict[str, object]: """Serialize this object into an application install params payload. Returns @@ -587,27 +587,30 @@ def __init__( self.guild = guild self.user = user + @staticmethod + def _get_ctx(raw: dict | None, key: int): + if raw is None: + return None + if key in raw: + return raw[key] + skey = str(key) + return raw.get(skey) + + @staticmethod + def _decode_ctx(value: dict | None) -> AppInstallParams | None: + if value is None: + return None + params = value.get("oauth2_install_params") + if not params: + return None + return AppInstallParams(params) + @classmethod def from_payload(cls, data: dict | None) -> IntegrationTypesConfig | None: if data is None: return None - - def _get_ctx(raw: dict, key: int): - if key in raw: - return raw[key] - skey = str(key) - return raw.get(skey) - - def _decode_ctx(value: dict | None) -> AppInstallParams | None: - if value is None: - return None - params = value.get("oauth2_install_params") - if not params: - return None - return AppInstallParams(params) - - guild_ctx = _decode_ctx(_get_ctx(data, 0)) - user_ctx = _decode_ctx(_get_ctx(data, 1)) + guild_ctx = cls._decode_ctx(cls._get_ctx(data, 0)) + user_ctx = cls._decode_ctx(cls._get_ctx(data, 1)) return cls(guild=guild_ctx, user=user_ctx) def _encode_install_params( @@ -615,9 +618,9 @@ def _encode_install_params( ) -> dict[str, object] | None: if value is None: return None - return {"oauth2_install_params": value.to_payload()} + return {"oauth2_install_params": value._to_payload()} - def to_payload(self) -> dict[int, dict[str, object] | None]: + def _to_payload(self) -> dict[int, dict[str, object] | None]: payload: dict[int, dict[str, object] | None] = {} if self.guild is not utils.MISSING: payload[0] = self._encode_install_params(self.guild) diff --git a/discord/http.py b/discord/http.py index 74cf19c541..b575b416f1 100644 --- a/discord/http.py +++ b/discord/http.py @@ -3213,7 +3213,7 @@ def get_answer_voters( def application_info(self) -> Response[appinfo.AppInfo]: return self.request(Route("GET", "/oauth2/applications/@me")) - def edit_current_application( + def edit_current_application_info( self, payload: dict[str, Any] ) -> Response[appinfo.AppInfo]: return self.request(Route("PATCH", "/applications/@me"), json=payload) From 0181f8d75dc0ac318dffa63a2037694f0e9d89f5 Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Thu, 4 Dec 2025 10:08:04 +0200 Subject: [PATCH 21/38] refactor: improve stupid error impossible to handle --- discord/ext/pages/pagination.py | 26 +++++++++++++----------- discord/ui/view.py | 35 +++++++++++++++++++-------------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/discord/ext/pages/pagination.py b/discord/ext/pages/pagination.py index 8dbf1c0f43..59a1158472 100644 --- a/discord/ext/pages/pagination.py +++ b/discord/ext/pages/pagination.py @@ -24,6 +24,7 @@ from __future__ import annotations +import contextlib from typing import List import discord @@ -456,7 +457,8 @@ def __init__( async def update( self, - pages: None | ( + pages: None + | ( list[PageGroup] | list[Page] | list[str] @@ -527,7 +529,7 @@ async def update( | list[str] | list[Page] | list[list[discord.Embed] | discord.Embed] - ) = (pages if pages is not None else self.pages) + ) = pages if pages is not None else self.pages self.show_menu = show_menu if show_menu is not None else self.show_menu if pages is not None and all(isinstance(pg, PageGroup) for pg in pages): self.page_groups = self.pages if self.show_menu else None @@ -597,11 +599,12 @@ async def on_timeout(self) -> None: page = self.pages[self.current_page] page = self.get_page_content(page) files = page.update_files() - await self.message.edit( - view=self, - files=files or [], - attachments=[], - ) + async with contextlib.suppress(discord.HTTPException): + await self.message.edit( + view=self, + files=files or [], + attachments=[], + ) async def disable( self, @@ -709,7 +712,9 @@ async def goto_page( try: if interaction: - await interaction.response.defer() # needed to force webhook message edit route for files kwarg support + await ( + interaction.response.defer() + ) # needed to force webhook message edit route for files kwarg support await interaction.followup.edit_message( message_id=self.message.id, content=page.content, @@ -952,9 +957,8 @@ async def send( ctx: Context, target: discord.abc.Messageable | None = None, target_message: str | None = None, - reference: None | ( - discord.Message | discord.MessageReference | discord.PartialMessage - ) = None, + reference: None + | (discord.Message | discord.MessageReference | discord.PartialMessage) = None, allowed_mentions: discord.AllowedMentions | None = None, mention_author: bool | None = None, delete_after: float | None = None, diff --git a/discord/ui/view.py b/discord/ui/view.py index 9d03339d09..4e9b64a0af 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -26,6 +26,7 @@ from __future__ import annotations import asyncio +import contextlib import os import sys import time @@ -42,11 +43,12 @@ from typing_extensions import Self +import discord + from ..components import ActionRow as ActionRowComponent from ..components import Button as ButtonComponent -from ..components import Component +from ..components import Component, FileComponent, _component_factory from ..components import Container as ContainerComponent -from ..components import FileComponent from ..components import Label as LabelComponent from ..components import MediaGallery as MediaGalleryComponent from ..components import Section as SectionComponent @@ -54,8 +56,6 @@ from ..components import Separator as SeparatorComponent from ..components import TextDisplay as TextDisplayComponent from ..components import Thumbnail as ThumbnailComponent -from ..components import _component_factory -from ..utils import find from .core import ItemInterface from .item import ItemCallbackType, ViewItem @@ -96,7 +96,6 @@ def _walk_all_components_v2(components: list[Component]) -> Iterator[Component]: def _component_to_item(component: Component) -> ViewItem[V]: - if isinstance(component, ButtonComponent): from .button import Button @@ -309,9 +308,10 @@ async def on_timeout(self) -> None: message = self.message if message: - m = await message.edit(view=self) - if m: - self._message = m + async with contextlib.suppress(discord.HTTPException): + m = await message.edit(view=self) + if m: + self._message = m async def on_check_failure(self, interaction: Interaction) -> None: """|coro| @@ -681,7 +681,7 @@ def add_item(self, item: ViewItem[V]) -> Self: if item._underlying.is_v2(): raise ValueError( - f"cannot use V2 components in View. Use DesignerView instead." + "cannot use V2 components in View. Use DesignerView instead." ) if isinstance(item._underlying, ActionRowComponent): for i in item.children: @@ -718,7 +718,9 @@ def clear_items(self) -> None: def refresh(self, components: list[Component]): # This is pretty hacky at the moment old_state: dict[tuple[int, str], ViewItem[V]] = { - (item.type.value, item.custom_id): item for item in self.children if item.is_dispatchable() # type: ignore + (item.type.value, item.custom_id): item + for item in self.children + if item.is_dispatchable() # type: ignore } children: list[ViewItem[V]] = [ item for item in self.children if not item.is_dispatchable() @@ -878,7 +880,7 @@ def add_item(self, item: ViewItem[V]) -> Self: if isinstance(item._underlying, (SelectComponent, ButtonComponent)): raise ValueError( - f"cannot add Select or Button to DesignerView directly. Use ActionRow instead." + "cannot add Select or Button to DesignerView directly. Use ActionRow instead." ) super().add_item(item) @@ -909,9 +911,9 @@ def is_components_v2(self) -> bool: class ViewStore: def __init__(self, state: ConnectionState): # (component_type, message_id, custom_id): (BaseView, ViewItem) - self._views: dict[tuple[int, int | None, str], tuple[BaseView, ViewItem[V]]] = ( - {} - ) + self._views: dict[ + tuple[int, int | None, str], tuple[BaseView, ViewItem[V]] + ] = {} # message_id: View self._synced_message_views: dict[int, BaseView] = {} self._state: ConnectionState = state @@ -942,7 +944,10 @@ def add_view(self, view: BaseView, message_id: int | None = None): view._start_listening_from_store(self) for item in view.walk_children(): if item.is_storable(): - self._views[(item.type.value, message_id, item.custom_id)] = (view, item) # type: ignore + self._views[(item.type.value, message_id, item.custom_id)] = ( + view, + item, + ) # type: ignore if message_id is not None: self._synced_message_views[message_id] = view From a46535263d4ca5f5b6e472a06135e67df53bd30b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 08:10:47 +0000 Subject: [PATCH 22/38] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/ext/pages/pagination.py | 10 +++++----- discord/ui/view.py | 10 ++++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/discord/ext/pages/pagination.py b/discord/ext/pages/pagination.py index 59a1158472..5db5c80fa4 100644 --- a/discord/ext/pages/pagination.py +++ b/discord/ext/pages/pagination.py @@ -457,8 +457,7 @@ def __init__( async def update( self, - pages: None - | ( + pages: None | ( list[PageGroup] | list[Page] | list[str] @@ -529,7 +528,7 @@ async def update( | list[str] | list[Page] | list[list[discord.Embed] | discord.Embed] - ) = pages if pages is not None else self.pages + ) = (pages if pages is not None else self.pages) self.show_menu = show_menu if show_menu is not None else self.show_menu if pages is not None and all(isinstance(pg, PageGroup) for pg in pages): self.page_groups = self.pages if self.show_menu else None @@ -957,8 +956,9 @@ async def send( ctx: Context, target: discord.abc.Messageable | None = None, target_message: str | None = None, - reference: None - | (discord.Message | discord.MessageReference | discord.PartialMessage) = None, + reference: None | ( + discord.Message | discord.MessageReference | discord.PartialMessage + ) = None, allowed_mentions: discord.AllowedMentions | None = None, mention_author: bool | None = None, delete_after: float | None = None, diff --git a/discord/ui/view.py b/discord/ui/view.py index 4e9b64a0af..6aae692456 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -47,8 +47,9 @@ from ..components import ActionRow as ActionRowComponent from ..components import Button as ButtonComponent -from ..components import Component, FileComponent, _component_factory +from ..components import Component from ..components import Container as ContainerComponent +from ..components import FileComponent from ..components import Label as LabelComponent from ..components import MediaGallery as MediaGalleryComponent from ..components import Section as SectionComponent @@ -56,6 +57,7 @@ from ..components import Separator as SeparatorComponent from ..components import TextDisplay as TextDisplayComponent from ..components import Thumbnail as ThumbnailComponent +from ..components import _component_factory from .core import ItemInterface from .item import ItemCallbackType, ViewItem @@ -911,9 +913,9 @@ def is_components_v2(self) -> bool: class ViewStore: def __init__(self, state: ConnectionState): # (component_type, message_id, custom_id): (BaseView, ViewItem) - self._views: dict[ - tuple[int, int | None, str], tuple[BaseView, ViewItem[V]] - ] = {} + self._views: dict[tuple[int, int | None, str], tuple[BaseView, ViewItem[V]]] = ( + {} + ) # message_id: View self._synced_message_views: dict[int, BaseView] = {} self._state: ConnectionState = state From d8a0aa2d13675a9223d69ba2bf0e7303251df1f7 Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Thu, 4 Dec 2025 10:13:19 +0200 Subject: [PATCH 23/38] docs: update CHANGELOG.md to reflect changes in BaseView.on_timeout behavior --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69cbdf0f98..ac1cb98c65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ These changes are available on the `master` branch, but have not yet been releas ([#2994](https://github.com/Pycord-Development/pycord/pull/2994)) ### Changed + - Changed `BaseView.on_timeout` behavior: views no longer raise errors on timeout. + ([#3018](https://github.com/Pycord-Development/pycord/pull/3018)) ### Fixed From 6fa92482e315a5a9f5cb6d9d20d6d65d8ac80a0e Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Thu, 4 Dec 2025 10:14:21 +0200 Subject: [PATCH 24/38] Update changelog for Paginator.on_timeout behavior Added mention of Paginator.on_timeout to the changelog entry about changed timeout behavior, clarifying that both BaseView and Paginator views no longer raise errors on timeout. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac1cb98c65..e56fd329c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ These changes are available on the `master` branch, but have not yet been releas ([#2994](https://github.com/Pycord-Development/pycord/pull/2994)) ### Changed - - Changed `BaseView.on_timeout` behavior: views no longer raise errors on timeout. + - Changed `BaseView.on_timeout` and `Paginator.on_timeout` behavior: views no longer raise errors on timeout. ([#3018](https://github.com/Pycord-Development/pycord/pull/3018)) ### Fixed From 4dbc13c943f2aff017f413f964e2631d6c1009a3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 08:15:02 +0000 Subject: [PATCH 25/38] style(pre-commit): auto fixes from pre-commit.com hooks --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e56fd329c2..809d68bc75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,9 @@ These changes are available on the `master` branch, but have not yet been releas ([#2994](https://github.com/Pycord-Development/pycord/pull/2994)) ### Changed - - Changed `BaseView.on_timeout` and `Paginator.on_timeout` behavior: views no longer raise errors on timeout. + +- Changed `BaseView.on_timeout` and `Paginator.on_timeout` behavior: views no longer + raise errors on timeout. ([#3018](https://github.com/Pycord-Development/pycord/pull/3018)) ### Fixed From a442507aa83f00c069f707f2f393de58804238e6 Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Sat, 3 May 2025 00:37:49 +0300 Subject: [PATCH 26/38] Update CHANGELOG.md Signed-off-by: Lumouille <144063653+Lumabots@users.noreply.github.com> --- CHANGELOG.md | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 809d68bc75..0f6ba06073 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -277,31 +277,7 @@ These changes are available on the `master` branch, but have not yet been releas - Fixed `TypeError` when specifying `thread_name` in `Webhook.send`. ([#2761](https://github.com/Pycord-Development/pycord/pull/2761)) - Updated `valid_locales` to support `in` and `es-419`. - ([#2767](https://github.com/Pycord-Development/pycord/pull/2767)) -- Added support for emoji aliases like `:smile:` in PartialEmoji.from_str. Also applied - the same logic in PartialEmojiConverter. - ([#2815](https://github.com/Pycord-Development/pycord/pull/2815)) -- Fixed `Webhook.edit` not working with `attachments=[]`. - ([#2779](https://github.com/Pycord-Development/pycord/pull/2779)) -- Fixed GIF-based `Sticker` returning the wrong `url`. - ([#2781](https://github.com/Pycord-Development/pycord/pull/2781)) -- Fixed `VoiceClient` crashing randomly while receiving audio - ([#2800](https://github.com/Pycord-Development/pycord/pull/2800)) -- Fixed `VoiceClient.connect` failing to do initial connection. - ([#2812](https://github.com/Pycord-Development/pycord/pull/2812)) -- Fixed `AttributeError` when printing a File component's `__repr__`. - ([#2843](https://github.com/Pycord-Development/pycord/pull/2843)) -- Fixed `TypeError` when using `@option` with certain annotations and along with - `channel_types`. ([#2835](https://github.com/Pycord-Development/pycord/pull/2835)) -- Fixed `TypeError` when using `Optional[...]` or `... | None` in command option type. - ([#2852](https://github.com/Pycord-Development/pycord/pull/2852)) -- Fixed type-hinting for `PermissionOverwrite.update`. - ([#2878](https://github.com/Pycord-Development/pycord/pull/2878)) -- Fixed `AttributeError` when accessing `AuditLogEntry.changes` more than once. - ([#2882])(https://github.com/Pycord-Development/pycord/pull/2882)) -- Fixed type hint for argument `start_time` and `end_time` of - `Guild.create_scheduled_event` - ([#2879](https://github.com/Pycord-Development/pycord/pull/2879)) + ([#2767])(https://github.com/Pycord-Development/pycord/pull/2767) ### Changed From b8bf65024c7bcf27a2acb7d16b154e184b84b593 Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Wed, 21 May 2025 11:14:41 +0300 Subject: [PATCH 27/38] sync with pycord --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f6ba06073..9c7f32e2f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -277,7 +277,11 @@ These changes are available on the `master` branch, but have not yet been releas - Fixed `TypeError` when specifying `thread_name` in `Webhook.send`. ([#2761](https://github.com/Pycord-Development/pycord/pull/2761)) - Updated `valid_locales` to support `in` and `es-419`. - ([#2767])(https://github.com/Pycord-Development/pycord/pull/2767) + ([#2767](https://github.com/Pycord-Development/pycord/pull/2767)) +- Fixed `Webhook.edit` not working with `attachments=[]`. + ([#2779](https://github.com/Pycord-Development/pycord/pull/2779)) +- Fixed GIF-based `Sticker` returning the wrong `url`. + ([#2781](https://github.com/Pycord-Development/pycord/pull/2781)) ### Changed From 2f194c75a80b7e93f6c7e25be3ed63689b53959c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:47:56 +0100 Subject: [PATCH 28/38] chore(deps): upgrade dependency msgspec to ~=0.20.0 (#3009) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- requirements/speed.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/speed.txt b/requirements/speed.txt index 62fa81b491..81437cf78d 100644 --- a/requirements/speed.txt +++ b/requirements/speed.txt @@ -1,2 +1,2 @@ -msgspec~=0.19.0 +msgspec~=0.20.0 aiohttp[speedups] From 15e40174d86fe051c63e6570105dbee13b88e5a1 Mon Sep 17 00:00:00 2001 From: Paillat Date: Wed, 26 Nov 2025 18:49:19 +0100 Subject: [PATCH 29/38] docs: :memo: Fix versionadded for bypass slowmode permission to 2.7 (#3010) :memo: Fix versionadded for bypass slowmode permission to 2.7 --- discord/permissions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/permissions.py b/discord/permissions.py index d2e3f2525f..fad9cdf1f7 100644 --- a/discord/permissions.py +++ b/discord/permissions.py @@ -684,7 +684,7 @@ def pin_messages(self) -> int: def bypass_slowmode(self) -> int: """:class:`bool`: Returns ``True`` if a user can bypass slowmode. - .. versionadded:: tbd + .. versionadded:: 2.7 """ return 1 << 52 From 1e914ae8bb1fbbbb2fe53776b7a3026805caa93f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 09:53:54 +0100 Subject: [PATCH 30/38] chore(deps): upgrade actions/checkout action to v6 (#3007) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 2 +- .github/workflows/docs-checks.yml | 2 +- .github/workflows/docs-json-export.yml | 2 +- .github/workflows/docs-localization-download.yml | 4 ++-- .github/workflows/docs-localization-upload.yml | 2 +- .github/workflows/lib-checks.yml | 10 +++++----- .github/workflows/release.yml | 6 +++--- .github/workflows/todo-checks.yml | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 828a31eda7..18162ef9a4 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -26,7 +26,7 @@ jobs: language: ["python"] steps: - name: "Checkout repository" - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: "Initialize CodeQL" uses: github/codeql-action/init@v4 with: diff --git a/.github/workflows/docs-checks.yml b/.github/workflows/docs-checks.yml index fa224a9525..8a6eb10717 100644 --- a/.github/workflows/docs-checks.yml +++ b/.github/workflows/docs-checks.yml @@ -36,7 +36,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout Repository" - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: "Setup Python" uses: actions/setup-python@v6 with: diff --git a/.github/workflows/docs-json-export.yml b/.github/workflows/docs-json-export.yml index 869b1774d7..4497065278 100644 --- a/.github/workflows/docs-json-export.yml +++ b/.github/workflows/docs-json-export.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout repository id: checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v6 id: setup-python diff --git a/.github/workflows/docs-localization-download.yml b/.github/workflows/docs-localization-download.yml index 90d025e3ff..65665f43c8 100644 --- a/.github/workflows/docs-localization-download.yml +++ b/.github/workflows/docs-localization-download.yml @@ -15,7 +15,7 @@ jobs: pr_id: ${{ steps.convert_outputs.outputs.pr_id }} steps: - name: "Checkout Repository" - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-tags: true - name: "Install Python" @@ -74,7 +74,7 @@ jobs: environment: translations steps: - name: "Checkout Repository" - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: "Refresh Pull" run: | git fetch --all diff --git a/.github/workflows/docs-localization-upload.yml b/.github/workflows/docs-localization-upload.yml index c79fff65eb..2afc84148e 100644 --- a/.github/workflows/docs-localization-upload.yml +++ b/.github/workflows/docs-localization-upload.yml @@ -20,7 +20,7 @@ jobs: environment: translations steps: - name: "Checkout Repository" - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-tags: true - name: "Install Python" diff --git a/.github/workflows/lib-checks.yml b/.github/workflows/lib-checks.yml index 47dec36218..54f373cc66 100644 --- a/.github/workflows/lib-checks.yml +++ b/.github/workflows/lib-checks.yml @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout Repository" - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: "Setup Python" uses: actions/setup-python@v6 with: @@ -52,7 +52,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout Repository" - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: "Setup Python" uses: actions/setup-python@v6 with: @@ -70,7 +70,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout Repository" - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: "Setup Python" uses: actions/setup-python@v6 with: @@ -94,7 +94,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout Repository" - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: "Setup Python" uses: actions/setup-python@v6 with: @@ -127,7 +127,7 @@ jobs: PYTHON: ${{ matrix.python-version }} steps: - name: "Checkout Repository" - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: "Setup Python" uses: actions/setup-python@v6 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6620353b71..02ee8fa686 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout Repository" - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 fetch-tags: true @@ -108,7 +108,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.ADMIN_GITHUB_TOKEN }} steps: - name: "Checkout Repository" - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 fetch-tags: true @@ -330,7 +330,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: "Checkout Repository" - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 fetch-tags: true diff --git a/.github/workflows/todo-checks.yml b/.github/workflows/todo-checks.yml index dbba5f59cb..b43afe88cc 100644 --- a/.github/workflows/todo-checks.yml +++ b/.github/workflows/todo-checks.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout Repository" - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: "Track TODO Action" uses: ribtoks/tdg-github-action@v0.4.15-beta with: From 6f4b4a08cc05a9f7b76c3987f6a4478dc152e55a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 09:54:10 +0100 Subject: [PATCH 31/38] chore(deps): upgrade dependency pre-commit to v4.5.0 (#3008) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- requirements/dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 0757f62afb..c15af38de9 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -5,7 +5,7 @@ pytest-asyncio~=1.3.0 # pytest-order~=1.0.1 mypy~=1.18.1 coverage~=7.10 -pre-commit==4.4.0 +pre-commit==4.5.0 codespell==2.4.1 bandit==1.8.6 flake8==7.3.0 From 9e2b4a9e5541049bdf84d4f7b46a22b2fee6e030 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 09:54:47 +0100 Subject: [PATCH 32/38] chore(deps): upgrade dependency bandit to v1.9.2 (#3005) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- requirements/dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index c15af38de9..6e06deae9d 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -7,5 +7,5 @@ mypy~=1.18.1 coverage~=7.10 pre-commit==4.5.0 codespell==2.4.1 -bandit==1.8.6 +bandit==1.9.2 flake8==7.3.0 From 00676fae565db32c22f74fe79ce43afeb5cf02d1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 09:57:30 +0100 Subject: [PATCH 33/38] chore(deps): upgrade dependency pytest to v9 (#2995) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- requirements/dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 6e06deae9d..f64b52f1d0 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,6 +1,6 @@ -r _.txt pylint~=3.3.8 -pytest~=8.4.1 +pytest~=9.0.1 pytest-asyncio~=1.3.0 # pytest-order~=1.0.1 mypy~=1.18.1 From c8da1d0205d0a9eabd12ee1175a7aac32e0fda1e Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Sat, 3 May 2025 00:37:49 +0300 Subject: [PATCH 34/38] Update CHANGELOG.md Signed-off-by: Lumouille <144063653+Lumabots@users.noreply.github.com> --- CHANGELOG.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c7f32e2f7..e0dabfe55c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -277,11 +277,10 @@ These changes are available on the `master` branch, but have not yet been releas - Fixed `TypeError` when specifying `thread_name` in `Webhook.send`. ([#2761](https://github.com/Pycord-Development/pycord/pull/2761)) - Updated `valid_locales` to support `in` and `es-419`. - ([#2767](https://github.com/Pycord-Development/pycord/pull/2767)) -- Fixed `Webhook.edit` not working with `attachments=[]`. - ([#2779](https://github.com/Pycord-Development/pycord/pull/2779)) -- Fixed GIF-based `Sticker` returning the wrong `url`. - ([#2781](https://github.com/Pycord-Development/pycord/pull/2781)) + ([#2767])(https://github.com/Pycord-Development/pycord/pull/2767) +- Fixed support emoji aliases like `:smile:` in PartialEmoji.from_str + ([#2774](https://github.com/Pycord-Development/pycord/pull/2774)) + ### Changed From fa6dc19c04a31bc93b07c1a347b309b87b40bd2c Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Wed, 21 May 2025 11:14:41 +0300 Subject: [PATCH 35/38] sync with pycord --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0dabfe55c..5051d9b732 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -281,6 +281,11 @@ These changes are available on the `master` branch, but have not yet been releas - Fixed support emoji aliases like `:smile:` in PartialEmoji.from_str ([#2774](https://github.com/Pycord-Development/pycord/pull/2774)) + ([#2767](https://github.com/Pycord-Development/pycord/pull/2767)) +- Fixed `Webhook.edit` not working with `attachments=[]`. + ([#2779](https://github.com/Pycord-Development/pycord/pull/2779)) +- Fixed GIF-based `Sticker` returning the wrong `url`. + ([#2781](https://github.com/Pycord-Development/pycord/pull/2781)) ### Changed From d77f0b37a718f161ce0895be4f817f52f555dd60 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 08:21:38 +0000 Subject: [PATCH 36/38] style(pre-commit): auto fixes from pre-commit.com hooks --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5051d9b732..0fbdf2205a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -282,6 +282,7 @@ These changes are available on the `master` branch, but have not yet been releas ([#2774](https://github.com/Pycord-Development/pycord/pull/2774)) ([#2767](https://github.com/Pycord-Development/pycord/pull/2767)) + - Fixed `Webhook.edit` not working with `attachments=[]`. ([#2779](https://github.com/Pycord-Development/pycord/pull/2779)) - Fixed GIF-based `Sticker` returning the wrong `url`. From 13eaf0c2184a783369a54f44fbfe9fd38393ad7f Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Wed, 21 May 2025 11:14:41 +0300 Subject: [PATCH 37/38] sync with pycord --- CHANGELOG.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fbdf2205a..f5e00ce1f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -282,11 +282,36 @@ These changes are available on the `master` branch, but have not yet been releas ([#2774](https://github.com/Pycord-Development/pycord/pull/2774)) ([#2767](https://github.com/Pycord-Development/pycord/pull/2767)) - +<<<<<<< HEAD +- Added support for emoji aliases like `:smile:` in PartialEmoji.from_str. Also applied + the same logic in PartialEmojiConverter. + ([#2815](https://github.com/Pycord-Development/pycord/pull/2815)) +======= +>>>>>>> f0b1cf96 (sync with pycord) - Fixed `Webhook.edit` not working with `attachments=[]`. ([#2779](https://github.com/Pycord-Development/pycord/pull/2779)) - Fixed GIF-based `Sticker` returning the wrong `url`. ([#2781](https://github.com/Pycord-Development/pycord/pull/2781)) +<<<<<<< HEAD +- Fixed `VoiceClient` crashing randomly while receiving audio + ([#2800](https://github.com/Pycord-Development/pycord/pull/2800)) +- Fixed `VoiceClient.connect` failing to do initial connection. + ([#2812](https://github.com/Pycord-Development/pycord/pull/2812)) +- Fixed `AttributeError` when printing a File component's `__repr__`. + ([#2843](https://github.com/Pycord-Development/pycord/pull/2843)) +- Fixed `TypeError` when using `@option` with certain annotations and along with + `channel_types`. ([#2835](https://github.com/Pycord-Development/pycord/pull/2835)) +- Fixed `TypeError` when using `Optional[...]` or `... | None` in command option type. + ([#2852](https://github.com/Pycord-Development/pycord/pull/2852)) +- Fixed type-hinting for `PermissionOverwrite.update`. + ([#2878](https://github.com/Pycord-Development/pycord/pull/2878)) +- Fixed `AttributeError` when accessing `AuditLogEntry.changes` more than once. + ([#2882])(https://github.com/Pycord-Development/pycord/pull/2882)) +- Fixed type hint for argument `start_time` and `end_time` of + `Guild.create_scheduled_event` + ([#2879](https://github.com/Pycord-Development/pycord/pull/2879)) +======= +>>>>>>> f0b1cf96 (sync with pycord) ### Changed From 5d170086050048a097ce02c625ef68c82974467e Mon Sep 17 00:00:00 2001 From: Lumouille <144063653+Lumabots@users.noreply.github.com> Date: Sat, 3 May 2025 00:37:49 +0300 Subject: [PATCH 38/38] Update CHANGELOG.md Signed-off-by: Lumouille <144063653+Lumabots@users.noreply.github.com> --- CHANGELOG.md | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5e00ce1f8..0f6ba06073 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -278,40 +278,6 @@ These changes are available on the `master` branch, but have not yet been releas ([#2761](https://github.com/Pycord-Development/pycord/pull/2761)) - Updated `valid_locales` to support `in` and `es-419`. ([#2767])(https://github.com/Pycord-Development/pycord/pull/2767) -- Fixed support emoji aliases like `:smile:` in PartialEmoji.from_str - ([#2774](https://github.com/Pycord-Development/pycord/pull/2774)) - - ([#2767](https://github.com/Pycord-Development/pycord/pull/2767)) -<<<<<<< HEAD -- Added support for emoji aliases like `:smile:` in PartialEmoji.from_str. Also applied - the same logic in PartialEmojiConverter. - ([#2815](https://github.com/Pycord-Development/pycord/pull/2815)) -======= ->>>>>>> f0b1cf96 (sync with pycord) -- Fixed `Webhook.edit` not working with `attachments=[]`. - ([#2779](https://github.com/Pycord-Development/pycord/pull/2779)) -- Fixed GIF-based `Sticker` returning the wrong `url`. - ([#2781](https://github.com/Pycord-Development/pycord/pull/2781)) -<<<<<<< HEAD -- Fixed `VoiceClient` crashing randomly while receiving audio - ([#2800](https://github.com/Pycord-Development/pycord/pull/2800)) -- Fixed `VoiceClient.connect` failing to do initial connection. - ([#2812](https://github.com/Pycord-Development/pycord/pull/2812)) -- Fixed `AttributeError` when printing a File component's `__repr__`. - ([#2843](https://github.com/Pycord-Development/pycord/pull/2843)) -- Fixed `TypeError` when using `@option` with certain annotations and along with - `channel_types`. ([#2835](https://github.com/Pycord-Development/pycord/pull/2835)) -- Fixed `TypeError` when using `Optional[...]` or `... | None` in command option type. - ([#2852](https://github.com/Pycord-Development/pycord/pull/2852)) -- Fixed type-hinting for `PermissionOverwrite.update`. - ([#2878](https://github.com/Pycord-Development/pycord/pull/2878)) -- Fixed `AttributeError` when accessing `AuditLogEntry.changes` more than once. - ([#2882])(https://github.com/Pycord-Development/pycord/pull/2882)) -- Fixed type hint for argument `start_time` and `end_time` of - `Guild.create_scheduled_event` - ([#2879](https://github.com/Pycord-Development/pycord/pull/2879)) -======= ->>>>>>> f0b1cf96 (sync with pycord) ### Changed