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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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()}