Skip to content

Commit 2da07c7

Browse files
feat: add support for editing our own application information (#1237)
Signed-off-by: arielle <[email protected]> Co-authored-by: vi <[email protected]> Co-authored-by: shiftinv <[email protected]>
1 parent 0ee7ec4 commit 2da07c7

File tree

4 files changed

+272
-15
lines changed

4 files changed

+272
-15
lines changed

changelog/1237.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for modifying application info using :meth:`AppInfo.edit`.

disnake/appinfo.py

Lines changed: 250 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
from __future__ import annotations
44

5-
from typing import TYPE_CHECKING, Dict, List, Optional, cast
5+
from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, cast
66

77
from . import utils
8-
from .asset import Asset
8+
from .asset import Asset, AssetBytes
99
from .enums import ApplicationEventWebhookStatus, try_enum
1010
from .flags import ApplicationFlags
1111
from .permissions import Permissions
12+
from .utils import MISSING
1213

1314
if TYPE_CHECKING:
1415
from .guild import Guild
@@ -17,12 +18,14 @@
1718
AppInfo as AppInfoPayload,
1819
ApplicationIntegrationType as ApplicationIntegrationTypeLiteral,
1920
ApplicationIntegrationTypeConfiguration as ApplicationIntegrationTypeConfigurationPayload,
21+
EditAppInfo as EditAppInfoPayload,
2022
InstallParams as InstallParamsPayload,
2123
PartialAppInfo as PartialAppInfoPayload,
2224
Team as TeamPayload,
2325
)
2426
from .user import User
2527

28+
2629
__all__ = (
2730
"AppInfo",
2831
"PartialAppInfo",
@@ -36,6 +39,9 @@ class InstallParams:
3639
3740
.. versionadded:: 2.5
3841
42+
.. versionchanged:: |vnext|
43+
This class can now be created by users.
44+
3945
Attributes
4046
----------
4147
scopes: List[:class:`str`]
@@ -53,27 +59,46 @@ class InstallParams:
5359

5460
def __init__(
5561
self,
62+
*,
63+
scopes: List[str],
64+
permissions: Permissions = MISSING,
65+
) -> None:
66+
self.scopes = scopes
67+
if permissions is MISSING:
68+
permissions = Permissions.none()
69+
self.permissions = permissions
70+
self._app_id: Optional[int] = None
71+
self._install_type: Optional[ApplicationIntegrationTypeLiteral] = None
72+
73+
@classmethod
74+
def _from_data(
75+
cls,
5676
data: InstallParamsPayload,
5777
parent: AppInfo,
5878
*,
5979
install_type: Optional[ApplicationIntegrationTypeLiteral] = None,
60-
) -> None:
61-
self._app_id = parent.id
62-
self._install_type: Optional[ApplicationIntegrationTypeLiteral] = install_type
63-
self.scopes = data["scopes"]
64-
self.permissions = Permissions(int(data["permissions"]))
80+
) -> InstallParams:
81+
instance = cls(permissions=Permissions(int(data["permissions"])), scopes=data["scopes"])
82+
instance._install_type = install_type
83+
instance._app_id = parent.id
84+
return instance
6585

6686
def __repr__(self) -> str:
6787
return f"<InstallParams scopes={self.scopes!r} permissions={self.permissions!r}>"
6888

6989
def to_url(self) -> str:
7090
"""Returns a string that can be used to install this application.
7191
92+
.. note:: This method can only be used on InstallParams that have been created by :meth:`.Client.application_info`
93+
7294
Returns
7395
-------
7496
:class:`str`
7597
The invite url.
7698
"""
99+
if self._app_id is None:
100+
msg = "This InstallParams instance is not linked to an application."
101+
raise ValueError(msg)
77102
return utils.oauth_url(
78103
self._app_id,
79104
scopes=self.scopes,
@@ -83,12 +108,22 @@ def to_url(self) -> str:
83108
),
84109
)
85110

111+
def to_dict(self) -> InstallParamsPayload:
112+
return {
113+
"scopes": self.scopes,
114+
"permissions": str(self.permissions.value),
115+
}
116+
86117

87118
class InstallTypeConfiguration:
88119
"""Represents the configuration for a particular application installation type.
89120
90121
.. versionadded:: 2.10
91122
123+
.. versionchanged:: |vnext|
124+
125+
This class can now be created by users.
126+
92127
Attributes
93128
----------
94129
install_params: Optional[:class:`InstallParams`]
@@ -97,19 +132,31 @@ class InstallTypeConfiguration:
97132

98133
__slots__ = ("install_params",)
99134

100-
def __init__(
101-
self,
135+
def __init__(self, *, install_params: Optional[InstallParams] = None) -> None:
136+
self.install_params: Optional[InstallParams] = install_params
137+
138+
@classmethod
139+
def _from_data(
140+
cls,
102141
data: ApplicationIntegrationTypeConfigurationPayload,
103142
*,
104143
parent: AppInfo,
105144
install_type: ApplicationIntegrationTypeLiteral,
106-
) -> None:
107-
self.install_params: Optional[InstallParams] = (
108-
InstallParams(install_params, parent=parent, install_type=install_type)
145+
) -> InstallTypeConfiguration:
146+
return cls(
147+
install_params=InstallParams._from_data(
148+
install_params, parent=parent, install_type=install_type
149+
)
109150
if (install_params := data.get("oauth2_install_params"))
110151
else None
111152
)
112153

154+
def to_dict(self) -> ApplicationIntegrationTypeConfigurationPayload:
155+
payload: ApplicationIntegrationTypeConfigurationPayload = {}
156+
if self.install_params:
157+
payload["oauth2_install_params"] = self.install_params.to_dict()
158+
return payload
159+
113160
def __repr__(self) -> str:
114161
return f"<InstallTypeConfiguration install_params={self.install_params!r}>"
115162

@@ -310,7 +357,9 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload) -> None:
310357
)
311358
self.tags: Optional[List[str]] = data.get("tags")
312359
self.install_params: Optional[InstallParams] = (
313-
InstallParams(data["install_params"], parent=self) if "install_params" in data else None
360+
InstallParams._from_data(data["install_params"], parent=self)
361+
if "install_params" in data
362+
else None
314363
)
315364
self.custom_install_url: Optional[str] = data.get("custom_install_url")
316365
self.redirect_uris: Optional[List[str]] = data.get("redirect_uris")
@@ -335,7 +384,7 @@ def __init__(self, state: ConnectionState, data: AppInfoPayload) -> None:
335384
] = {}
336385
for type_str, config in (data.get("integration_types_config") or {}).items():
337386
install_type = cast("ApplicationIntegrationTypeLiteral", int(type_str))
338-
self._install_types_config[install_type] = InstallTypeConfiguration(
387+
self._install_types_config[install_type] = InstallTypeConfiguration._from_data(
339388
config or {},
340389
parent=self,
341390
install_type=install_type,
@@ -405,6 +454,193 @@ def user_install_type_config(self) -> Optional[InstallTypeConfiguration]:
405454
"""
406455
return self._install_types_config.get(1)
407456

457+
async def edit(
458+
self,
459+
*,
460+
custom_install_url: Optional[str] = MISSING,
461+
description: Optional[str] = MISSING,
462+
role_connections_verification_url: Optional[str] = MISSING,
463+
install_params: Optional[InstallParams] = MISSING,
464+
guild_install_type_config: Optional[InstallTypeConfiguration] = MISSING,
465+
user_install_type_config: Optional[InstallTypeConfiguration] = MISSING,
466+
flags: ApplicationFlags = MISSING,
467+
icon: Optional[AssetBytes] = MISSING,
468+
cover_image: Optional[AssetBytes] = MISSING,
469+
interactions_endpoint_url: Optional[str] = MISSING,
470+
tags: Sequence[str] = MISSING,
471+
event_webhooks_url: Optional[str] = MISSING,
472+
event_webhooks_status: ApplicationEventWebhookStatus = MISSING,
473+
event_webhooks_types: Sequence[str] = MISSING,
474+
) -> AppInfo:
475+
"""|coro|
476+
477+
Edit's the application's information.
478+
479+
All parameters are optional.
480+
481+
.. versionadded:: |vnext|
482+
483+
Parameters
484+
----------
485+
custom_install_url: Optional[:class:`str`]
486+
The custom installation url for this application.
487+
description: Optional[:class:`str`]
488+
The application's description.
489+
role_connections_verification_url: Optional[:class:`str`]
490+
The application's role connection verification entry point,
491+
which when configured will render the app as a verification method
492+
in the guild role verification configuration.
493+
install_params: Optional[:class:`InstallParams`]
494+
The installation parameters for this application.
495+
496+
If provided with ``custom_install_url``, must be set to ``None``.
497+
498+
It's recommended to use :attr:`guild_install_type_config` and :attr:`user_install_type_config`
499+
instead of this parameter, as this parameter is soft-deprecated by Discord.
500+
501+
:attr:`bot_public` **must** be ``True`` if this parameter is provided.
502+
guild_install_type_config: Optional[:class:`InstallTypeConfiguration`]
503+
The guild installation type configuration for this application.
504+
If set to ``None``, guild installations will be disabled.
505+
You cannot disable both user and guild installations.
506+
507+
Note the only valid scopes for guild installations are ``applications.commands`` and ``bot``.
508+
509+
user_install_type_config: Optional[:class:`InstallTypeConfiguration`]
510+
The user installation type configuration for this application.
511+
If set to ``None``, user installations will be disabled.
512+
You cannot disable both user and guild installations.
513+
514+
Note the only valid scopes for user installations are ``applications.commands``.
515+
flags: :class:`ApplicationFlags`
516+
The application's public flags.
517+
518+
This is restricted to only affecting the limited intent flags:
519+
:attr:`~ApplicationFlags.gateway_guild_members_limited`,
520+
:attr:`~ApplicationFlags.gateway_presence_limited`, and
521+
:attr:`~ApplicationFlags.gateway_message_content_limited`.
522+
523+
.. warning::
524+
Disabling an intent that you are currently requesting during your current session
525+
will cause you to be disconnected from the gateway. Take caution when providing this parameter.
526+
527+
icon: Optional[|resource_type|]
528+
Update the application's icon asset, if any.
529+
cover_image: Optional[|resource_type|]
530+
Update the cover_image for rich presence integrations.
531+
interactions_endpoint_url: Optional[:class:`str`]
532+
The application's interactions endpoint URL.
533+
tags: List[:class:`str`]
534+
The application's tags.
535+
event_webhooks_url: Optional[:class:`str`]
536+
The application's event webhooks URL.
537+
event_webhooks_status: :class:`ApplicationEventWebhookStatus`
538+
The application's event webhooks status.
539+
event_webhooks_types: Optional[List[:class:`str`]]
540+
The application's event webhook types. See `webhook event types <https://discord.com/developers/docs/events/webhook-events#event-types>`_
541+
for a list of valid events.
542+
543+
Raises
544+
------
545+
HTTPException
546+
Editing the application information failed.
547+
548+
Returns
549+
-------
550+
:class:`.AppInfo`
551+
The new application information.
552+
553+
Examples
554+
--------
555+
556+
.. code-block:: python
557+
558+
>>> app_info = await client.application_info()
559+
>>> await app_info.edit(description="A new description!")
560+
561+
To enable user installations while using custom install URL.
562+
563+
.. code-block:: python
564+
565+
>>> from disnake import InstallTypeConfiguration
566+
>>> await app_info.edit(
567+
... user_install_type_config=InstallTypeConfiguration()
568+
... )
569+
570+
To disable user installations and guild installations.
571+
Note, both cannot be disabled simultaneously.
572+
573+
.. code-block:: python
574+
575+
>>> await app_info.edit(
576+
... custom_install_url="https://example.com/install",
577+
... # to disable user installations
578+
... user_install_type_config=None,
579+
... # to disable guild installations
580+
... guild_install_type_config=None,
581+
... )
582+
"""
583+
fields: EditAppInfoPayload = {}
584+
585+
if custom_install_url is not MISSING:
586+
fields["custom_install_url"] = custom_install_url
587+
588+
if description is not MISSING:
589+
fields["description"] = description or ""
590+
591+
if role_connections_verification_url is not MISSING:
592+
fields["role_connections_verification_url"] = role_connections_verification_url
593+
594+
if install_params is not MISSING:
595+
fields["install_params"] = install_params.to_dict() if install_params else None
596+
597+
if guild_install_type_config is not MISSING or user_install_type_config is not MISSING:
598+
integration_types_config: Dict[str, ApplicationIntegrationTypeConfigurationPayload] = {}
599+
600+
if guild_install_type_config is MISSING:
601+
guild_install_type_config = self.guild_install_type_config
602+
if guild_install_type_config:
603+
integration_types_config["0"] = guild_install_type_config.to_dict()
604+
605+
if user_install_type_config is MISSING:
606+
user_install_type_config = self.user_install_type_config
607+
if user_install_type_config:
608+
integration_types_config["1"] = user_install_type_config.to_dict()
609+
610+
fields["integration_types_config"] = integration_types_config
611+
612+
if flags is not MISSING:
613+
fields["flags"] = flags.value
614+
615+
if icon is not MISSING:
616+
fields["icon"] = await utils._assetbytes_to_base64_data(icon)
617+
618+
if cover_image is not MISSING:
619+
fields["cover_image"] = await utils._assetbytes_to_base64_data(cover_image)
620+
621+
if interactions_endpoint_url is not MISSING:
622+
fields["interactions_endpoint_url"] = interactions_endpoint_url
623+
624+
if tags is not MISSING:
625+
fields["tags"] = list(tags) if tags else None
626+
627+
if event_webhooks_url is not MISSING:
628+
fields["event_webhooks_url"] = event_webhooks_url
629+
630+
if event_webhooks_status is not MISSING:
631+
if event_webhooks_status is ApplicationEventWebhookStatus.disabled_by_discord:
632+
msg = f"cannot set 'event_webhooks_status' to {event_webhooks_status!r}"
633+
raise ValueError(msg)
634+
fields["event_webhooks_status"] = event_webhooks_status.value
635+
636+
if event_webhooks_types is not MISSING:
637+
fields["event_webhooks_types"] = (
638+
list(event_webhooks_types) if event_webhooks_types else None
639+
)
640+
641+
data = await self._state.http.edit_application_info(**fields)
642+
return AppInfo(self._state, data)
643+
408644

409645
class PartialAppInfo:
410646
"""Represents a partial AppInfo given by :func:`~disnake.abc.GuildChannel.create_invite`.

disnake/http.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3059,6 +3059,9 @@ def get_voice_regions(self) -> Response[List[voice.VoiceRegion]]:
30593059
def application_info(self) -> Response[appinfo.AppInfo]:
30603060
return self.request(Route("GET", "/oauth2/applications/@me"))
30613061

3062+
def edit_application_info(self, **fields: Any) -> Response[appinfo.AppInfo]:
3063+
return self.request(Route("PATCH", "/applications/@me"), json=fields)
3064+
30623065
def get_application_role_connection_metadata_records(
30633066
self, application_id: Snowflake
30643067
) -> Response[List[application_role_connection.ApplicationRoleConnectionMetadata]]:

0 commit comments

Comments
 (0)