Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 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
1b0017c
refactor: update method names and types in AppInfo and HTTPClient
Lumabots Dec 2, 2025
20cd5ed
Merge branch 'master' into appinf
Lumabots Dec 2, 2025
ef0cb75
feat: add ApplicationEventWebhookStatus enum and update AppInfo to us…
Lumabots Dec 5, 2025
5f939dd
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 5, 2025
27b5b01
Merge branch 'master' into appinf
Lumabots Dec 8, 2025
ae03d85
Update discord/appinfo.py
Lumabots Dec 8, 2025
f2d461d
refactor: simplify icon and cover_image type annotations in AppInfo
Lumabots Dec 8, 2025
7afeed1
feat: implement FlagCommand for enhanced command argument parsing
Lumabots Dec 9, 2025
630bdcd
Merge branch 'master' into appinf
Lumabots Dec 9, 2025
03700ad
Update discord/appinfo.py
Paillat-dev Dec 9, 2025
File filter

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 `AppInfo` fields.
([#2994](https://github.com/Pycord-Development/pycord/pull/2994))

### Changed

Expand Down
297 changes: 286 additions & 11 deletions discord/appinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@

from __future__ import annotations

from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

from . import utils
from .asset import Asset
from .enums import ApplicationEventWebhookStatus, try_enum
from .flags import ApplicationFlags
from .permissions import Permissions

if TYPE_CHECKING:
Expand All @@ -44,6 +46,8 @@
"AppInfo",
"PartialAppInfo",
"AppInstallParams",
"IntegrationTypesConfig",
"ApplicationEventWebhookStatus",
)


Expand Down Expand Up @@ -134,11 +138,31 @@ 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[:class:`IntegrationTypesConfig`]
Per-installation context configuration for guild (``0``) and user (``1``) contexts.

.. 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:`ApplicationEventWebhookStatus`]
The status of event webhooks for the application, if set.

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

bot: Optional[:class:`User`]
The bot user associated with this application, if any.

.. 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 @@ -188,17 +229,24 @@ 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.rpc_origins: list[str] = data["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._icon: str | None = data.get("icon")
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)
self.owner: User | None = (
state.create_user(owner)
if (owner := data.get("owner")) is not None
else None
)

team: TeamPayload | None = data.get("team")
self.team: Team | None = Team(state, team) if team else None

self._summary: str = data["summary"]
self.verify_key: str = data["verify_key"]
self.bot: User | None = (
state.create_user(bot) if (bot := data.get("bot")) is not None else None
)

self.guild_id: int | None = utils._get_as_snowflake(data, "guild_id")

Expand All @@ -213,20 +261,35 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload):
self.approximate_user_install_count: int | None = data.get(
"approximate_user_install_count"
)
self.redirect_uris: list[str] | None = data.get("redirect_uris", [])
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] = 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: ApplicationEventWebhookStatus | None = (
try_enum(ApplicationEventWebhookStatus, status)
if (status := data.get("event_webhooks_status")) is not None
else None
)
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
AppInstallParams(install_params)
if (install_params := data.get("install_params")) is not None
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 = IntegrationTypesConfig.from_payload(
data.get("integration_types_config")
)

def __repr__(self) -> str:
return (
Expand All @@ -235,6 +298,135 @@ 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.

.. versionadded:: 2.7
"""
if self._flags is None:
return None
return ApplicationFlags._from_value(self._flags)

async def edit(
self,
*,
description: 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,
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: ApplicationEventWebhookStatus = utils.MISSING,
event_webhooks_types: list[str] | None = utils.MISSING,
) -> AppInfo:
"""|coro|

Edit the current application's settings.

Parameters
----------
description: Optional[:class:`str`]
The new application description or ``None`` to clear.
icon: Optional[:class:`bytes`]
New icon image. If ``bytes`` is given it will be base64 encoded automatically. Pass ``None`` to clear.
cover_image: Optional[:class:`bytes`]
New cover image for the store embed. If ``bytes`` is given it will be base64 encoded automatically. 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:`ApplicationEventWebhookStatus`
The desired webhook status. Pass an :class:`ApplicationEventWebhookStatus` value to set
a specific status (``DISABLED``=1, ``ENABLED``=2, ``DISABLED_BY_DISCORD``=3).
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.

.. versionadded:: 2.7
"""
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"] = event_webhooks_status.value
if event_webhooks_types is not utils.MISSING:
payload["event_webhooks_types"] = event_webhooks_types

data = await self._state.http.edit_current_application_info(payload)
return AppInfo(self._state, data)

@property
def icon(self) -> Asset | None:
"""Retrieves the application's icon asset, if any."""
Expand Down Expand Up @@ -360,3 +552,86 @@ 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(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

@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
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(
self, value: AppInstallParams | None
) -> dict[str, object] | 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
Loading