Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
293 changes: 282 additions & 11 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,32 @@ 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:`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 +172,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 +194,7 @@ class AppInfo:
"bot_public",
"bot_require_code_grant",
"owner",
"bot",
"_icon",
"_summary",
"verify_key",
Expand All @@ -173,9 +207,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 +228,18 @@ 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 = 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

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 +254,29 @@ 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: 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] | None = data.get("tags", [])
self.tags: list[str] = data.get("tags", [])
Comment on lines -228 to +275
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it was None because it could have a value of null? Should it maybe do or [] to be sure?

Copy link
Contributor Author

@Lumabots Lumabots Nov 11, 2025

Choose a reason for hiding this comment

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

it cant be None, the typing was just shit (ig, considering this issue is in a LOT of place inside the library)
Screenshot 2025-11-11 at 17 28 30

Copy link
Member

Choose a reason for hiding this comment

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

flags vs tags ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Seems that I was really tired, gonna check for tags after

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it cant be None either, it can be missing but if mssing then [] make more sense
Screenshot 2025-11-14 at 12 20 22

Copy link
Member

Choose a reason for hiding this comment

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

Is this from discord.food ? In any case I think adding an or [] insttead of the default of .get can'tn hurt

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no this is form the official discord.
I dont really get the point of using or [] as it can be None according to the discord docs

Copy link
Member

Choose a reason for hiding this comment

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

Oh no I mean removing the None is correct, I meant using self.tags: list[str] = data.get("tags") or [] to be extra safe.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah i understood this, its more that tags will never be None, according to docs AND to every library that i checked about this (i checked 5 of them)

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 +285,136 @@ 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: bool = 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[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.

.. versionadded:: 2.7
"""
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 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 +458,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 +551,83 @@ 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

@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:
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