Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6cabb6f
Add support for editing application info and new fields
Lumabots Nov 7, 2025
2f54fe7
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 7, 2025
fab1478
Add AppInfo.edit() and missing fields support
Lumabots Nov 7, 2025
bfa2940
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 7, 2025
9893239
Update discord/appinfo.py
Lumabots Nov 7, 2025
884cd6c
Update discord/appinfo.py
Lumabots Nov 7, 2025
41f12a2
Update discord/appinfo.py
Lumabots Nov 7, 2025
714c68a
Update discord/appinfo.py
Lumabots Nov 7, 2025
734b5ce
Update discord/appinfo.py
Lumabots Nov 7, 2025
49c90b9
Update discord/appinfo.py
Lumabots Nov 7, 2025
76eb883
Restrict icon and cover_image types to bytes or None
Lumabots Nov 7, 2025
bd91789
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 7, 2025
7e5ff54
feat(emoji): add mention property to BaseEmoji for easier emoji refer…
Lumabots Nov 7, 2025
6fc48da
Merge branch 'appinf' of https://github.com/Lumabots/pycord into appinf
Lumabots Nov 7, 2025
bde7ec3
Merge branch 'master' into appinf
Lumabots Nov 11, 2025
89d3011
refactor(AppInfo): improve integration_types_config handling and simp…
Lumabots Nov 11, 2025
dfb7907
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 11, 2025
9eecbfa
refactor(AppInfo): remove deprecated flags attribute from AppInfo class
Lumabots Nov 14, 2025
ab000ac
Merge branch 'master' into appinf
Paillat-dev Nov 16, 2025
8c0dcd7
Merge branch 'master' into appinf
Paillat-dev Nov 23, 2025
03a46e8
refactor(AppInfo): rename event_webhooks_status to _event_webhooks_st…
Lumabots Nov 25, 2025
0b603f5
refactor(AppInfo): remove setter for event_webhooks_enabled and assoc…
Lumabots Nov 25, 2025
3c398cc
Update CHANGELOG.md
Lumabots Nov 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
251 changes: 249 additions & 2 deletions discord/appinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

from . import utils
from .asset import Asset
from .flags import ApplicationFlags
from .permissions import Permissions

if TYPE_CHECKING:
Expand All @@ -44,6 +45,7 @@
"AppInfo",
"PartialAppInfo",
"AppInstallParams",
"IntegrationTypesConfig",
)


Expand Down Expand Up @@ -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.

Expand All @@ -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
"""

Expand All @@ -161,6 +195,7 @@ class AppInfo:
"bot_public",
"bot_require_code_grant",
"owner",
"bot",
"_icon",
"_summary",
"verify_key",
Expand All @@ -173,9 +208,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",
Expand All @@ -189,7 +230,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"])
Expand All @@ -199,6 +240,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")

Expand All @@ -213,20 +255,31 @@ 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"
)
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"
)
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 = (
AppInstallParams(install_params) if install_params else None
)
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 (
Expand All @@ -235,6 +288,134 @@ 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,
interactions_endpoint_url: str | None = utils.MISSING,
role_connections_verification_url: str | None = utils.MISSING,
install_params: AppInstallParams | None = utils.MISSING,
custom_install_url: str | 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:
"""|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.
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. Pass ``None`` to clear.
privacy_policy_url: Optional[:class:`str`]
The application's Privacy Policy URL. Pass ``None`` to clear.
interactions_endpoint_url: Optional[:class:`str`]
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. 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
-------
:class:`.AppInfo`
The updated application information.
"""
payload: dict[str, object] = {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
payload: dict[str, object] = {}
payload: dict[str, Any] = {}

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 install_params is not utils.MISSING:
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)

@property
def icon(self) -> Asset | None:
"""Retrieves the application's icon asset, if any."""
Expand Down Expand Up @@ -278,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`
Expand Down Expand Up @@ -360,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]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def to_payload(self) -> dict[str, object]:
def _to_payload(self) -> dict[str, object]:

Change in other places accordingly, else this would be documented and have to be maintained

"""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]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def to_payload(self) -> dict[int, dict[str, object] | None]:
def _to_payload(self) -> dict[int, dict[str, object] | None]:

Idem as above, less important here

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
Loading
Loading