From 93a56799d686c7dc3b54329d02d192d69b568edb Mon Sep 17 00:00:00 2001 From: dolfies Date: Wed, 15 Jan 2025 02:47:46 -0500 Subject: [PATCH] Split detectable applications, add new application fields --- discord/application.py | 377 +++++++++++++++++++++++++++++------ discord/client.py | 25 ++- discord/flags.py | 119 ++++++++--- discord/http.py | 2 +- discord/profile.py | 6 + discord/types/application.py | 27 ++- discord/types/profile.py | 1 + discord/utils.py | 7 +- docs/api.rst | 11 + 9 files changed, 462 insertions(+), 113 deletions(-) diff --git a/discord/application.py b/discord/application.py index 54a01cb11..cae9d98fb 100644 --- a/discord/application.py +++ b/discord/application.py @@ -50,7 +50,7 @@ from .enums import ( UserFlags, try_enum, ) -from .flags import ApplicationDiscoveryFlags, ApplicationFlags +from .flags import ApplicationDiscoveryFlags, ApplicationFlags, OverlayMethodFlags from .mixins import Hashable from .object import OLDEST_OBJECT, Object from .permissions import Permissions @@ -83,6 +83,7 @@ if TYPE_CHECKING: Branch as BranchPayload, Build as BuildPayload, Company as CompanyPayload, + DetectableApplication as DetectableApplicationPayload, EmbeddedActivityConfig as EmbeddedActivityConfigPayload, EmbeddedActivityPlatform as EmbeddedActivityPlatformValues, EmbeddedActivityPlatformConfig as EmbeddedActivityPlatformConfigPayload, @@ -682,13 +683,13 @@ class ApplicationBot(User): Attributes ----------- - application: :class:`Application` + application: :class:`PartialApplication` The application that the bot is attached to. """ __slots__ = ('application',) - def __init__(self, *, data: PartialUserPayload, state: ConnectionState, application: Application): + def __init__(self, *, data: PartialUserPayload, state: ConnectionState, application: PartialApplication): super().__init__(state=state, data=data) self.application = application @@ -852,7 +853,7 @@ class ApplicationExecutable: The type of this attribute has changed to :class:`OperatingSystem`. launcher: :class:`bool` Whether the executable is a launcher or not. - application: :class:`PartialApplication` + application: Union[:class:`PartialApplication`, :class:`DetectableApplication`] The application that the executable is for. """ @@ -863,7 +864,7 @@ class ApplicationExecutable: 'application', ) - def __init__(self, *, data: ApplicationExecutablePayload, application: PartialApplication): + def __init__(self, *, data: ApplicationExecutablePayload, application: _BaseApplication): self.name: str = data['name'] self.os: OperatingSystem = OperatingSystem.from_string(data['os']) self.launcher: bool = data['is_launcher'] @@ -1736,8 +1737,137 @@ class ApplicationTester(User): await self._state.http.delete_app_whitelist(self.application.id, self.id) -class PartialApplication(Hashable): - """Represents a partial Application. +class _BaseApplication(Hashable): + __slots__ = ( + '_state', + 'id', + 'name', + 'aliases', + 'executables', + 'hook', + 'overlay', + 'overlay_warn', + 'overlay_compatibility_hook', + '_overlay_methods', + ) + + def __init__(self, *, state: ConnectionState, data: DetectableApplicationPayload): + self._state: ConnectionState = state + self._update(data) + + def _update(self, data: DetectableApplicationPayload) -> None: + self.id: int = int(data['id']) + self.name: str = data['name'] + self.aliases: List[str] = data.get('aliases', []) + self.executables: List[ApplicationExecutable] = [ + ApplicationExecutable(data=e, application=self) for e in data.get('executables', []) + ] + self.hook: bool = data.get('hook', False) + self.overlay: bool = data.get('overlay', False) + self.overlay_warn: bool = data.get('overlay_warn', False) + self.overlay_compatibility_hook: bool = data.get('overlay_compatibility_hook', False) + self._overlay_methods: int = data.get('overlay_methods', 0) + + async def ticket(self) -> str: + """|coro| + + Retrieves the license ticket for this application. + + Raises + ------- + HTTPException + Retrieving the ticket failed. + + Returns + -------- + :class:`str` + The ticket retrieved. + """ + state = self._state + data = await state.http.get_app_ticket(self.id) + return data['ticket'] + + async def entitlement_ticket(self) -> str: + """|coro| + + Retrieves the entitlement ticket for this application. + + Raises + ------- + HTTPException + Retrieving the ticket failed. + + Returns + -------- + :class:`str` + The ticket retrieved. + """ + state = self._state + data = await state.http.get_app_entitlement_ticket(self.id) + return data['ticket'] + + +class DetectableApplication(_BaseApplication): + """Represents a detectable application. + + .. container:: operations + + .. describe:: x == y + + Checks if two applications are equal. + + .. describe:: x != y + + Checks if two applications are not equal. + + .. describe:: hash(x) + + Return the application's hash. + + .. describe:: str(x) + + Returns the application's name. + + .. versionadded:: 2.1 + + Attributes + ------------- + id: :class:`int` + The application ID. + name: :class:`str` + The application name. + hook: :class:`bool` + Whether the Discord client can hook into the application directly. + overlay: :class:`bool` + Whether the application supports the `Discord overlay `_. + overlay_warn: :class:`bool` + Whether the Discord overlay is known to be problematic with the application. + overlay_compatibility_hook: :class:`bool` + Whether to use the compatibility hook for the Discord overlay. + aliases: List[:class:`str`] + A list of aliases that can be used to identify the application. + executables: List[:class:`ApplicationExecutable`] + A list of executables that are the application's. + """ + + __slots__ = () + + def __repr__(self) -> str: + return f'' + + @property + def created_at(self) -> datetime: + """:class:`datetime.datetime`: Returns the application's creation time in UTC.""" + return utils.snowflake_time(self.id) + + @property + def overlay_methods(self) -> OverlayMethodFlags: + """:class:`OverlayMethodFlags`: The overlay methods that the application supports.""" + return OverlayMethodFlags._from_value(self._overlay_methods) + + +class PartialApplication(_BaseApplication): + """Represents a partial application. .. container:: operations @@ -1797,8 +1927,30 @@ class PartialApplication(Hashable): The type of application. tags: List[:class:`str`] A list of tags that describe the application. + hook: :class:`bool` + Whether the Discord client can hook into the application directly. overlay: :class:`bool` - Whether the application has a Discord overlay or not. + Whether the application supports the `Discord overlay `_. + overlay_warn: :class:`bool` + Whether the Discord overlay is known to be problematic with the application. + overlay_compatibility_hook: :class:`bool` + Whether to use the compatibility hook for the Discord overlay. + verified: :class:`bool` + Whether the application is verified. + + .. versionadded:: 2.1 + discoverable: :class:`bool` + Whether the application is discoverable in the application directory. + + .. versionadded:: 2.1 + monetized: :class:`bool` + Whether the application has monetization enabled. + + .. versionadded:: 2.1 + storefront_available: :class:`bool` + Whether the application has public subscriptions or products available for purchase. + + .. versionadded:: 2.1 guild_id: Optional[:class:`int`] The ID of the guild the application is attached to, if any. primary_sku_id: Optional[:class:`int`] @@ -1827,6 +1979,10 @@ class PartialApplication(Hashable): The parameters to use for authorizing the application, if specified. embedded_activity_config: Optional[:class:`EmbeddedActivityConfig`] The application's embedded activity configuration, if any. + bot: Optional[:class:`User`] + The bot attached to the application, if any. + + .. versionadded:: 2.1 owner: Optional[:class:`User`] The application owner. This may be a team user account. @@ -1838,7 +1994,7 @@ class PartialApplication(Hashable): .. note:: - In almost all cases, this is not available. + In almost all cases, this is not available. See :attr:`owner` instead. """ __slots__ = ( @@ -1864,6 +2020,10 @@ class PartialApplication(Hashable): 'overlay', 'overlay_warn', 'overlay_compatibility_hook', + 'verified', + 'discoverable', + 'monetized', + 'storefront_available', 'aliases', 'developers', 'publishers', @@ -1877,6 +2037,7 @@ class PartialApplication(Hashable): 'store_listing_sku_id', 'slug', 'eula_id', + 'bot', 'owner', 'team', '_guild', @@ -1887,23 +2048,17 @@ class PartialApplication(Hashable): owner: Optional[User] team: Optional[Team] - def __init__(self, *, state: ConnectionState, data: PartialApplicationPayload): - self._state: ConnectionState = state - self._update(data) - def __str__(self) -> str: return self.name def _update(self, data: PartialApplicationPayload) -> None: + super()._update(data) state = self._state - self.id: int = int(data['id']) - self.name: str = data['name'] self.description: str = data['description'] self.rpc_origins: Optional[List[str]] = data.get('rpc_origins') self.verify_key: str = data['verify_key'] - self.aliases: List[str] = data.get('aliases', []) self.developers: List[Company] = [Company(data=d) for d in data.get('developers', [])] self.publishers: List[Company] = [Company(data=d) for d in data.get('publishers', [])] self.executables: List[ApplicationExecutable] = [ @@ -1928,6 +2083,10 @@ class PartialApplication(Hashable): self.overlay: bool = data.get('overlay', False) self.overlay_warn: bool = data.get('overlay_warn', False) self.overlay_compatibility_hook: bool = data.get('overlay_compatibility_hook', False) + self.verified: bool = data.get('is_verified', False) + self.discoverable: bool = data.get('is_discoverable', False) + self.monetized: bool = data.get('is_monetized', False) + self.storefront_available: bool = data.get('storefront_available', False) self.guild_id: Optional[int] = utils._get_as_snowflake(data, 'guild_id') self.primary_sku_id: Optional[int] = utils._get_as_snowflake(data, 'primary_sku_id') self.store_listing_sku_id: Optional[int] = utils._get_as_snowflake(data, 'store_listing_sku_id') @@ -1952,16 +2111,20 @@ class PartialApplication(Hashable): # Hacky, but I want these to be persisted + existing = getattr(self, 'bot', None) + bot = data.get('bot') + self.bot: Optional[User] = state.create_user(bot) if bot else existing + existing = getattr(self, 'owner', None) owner = data.get('owner') - self.owner = state.create_user(owner) if owner else existing + self.owner: Optional[User] = state.create_user(owner) if owner else existing existing = getattr(self, 'team', None) team = data.get('team') if existing and team: existing._update(team) else: - self.team = Team(state=state, data=team) if team else existing + self.team: Optional[Team] = Team(state=state, data=team) if team else existing if self.team and not self.owner: # We can create a team user from the team data @@ -2038,12 +2201,20 @@ class PartialApplication(Hashable): """Optional[:class:`Guild`]: The guild linked to the application, if any and available.""" return self._state._get_guild(self.guild_id) or self._guild + @property + def overlay_methods(self) -> OverlayMethodFlags: + """:class:`OverlayMethodFlags`: The overlay methods that the application supports. + + .. versionadded:: 2.1 + """ + return OverlayMethodFlags._from_value(self._overlay_methods) + def has_bot(self) -> bool: """:class:`bool`: Whether the application has an attached bot. .. versionadded:: 2.1 """ - return self._has_bot + return self._has_bot or self.bot is not None def is_rpc_enabled(self) -> bool: """:class:`bool`: Whether the application has the ability to access the client RPC server. @@ -2199,44 +2370,6 @@ class PartialApplication(Hashable): data = await state.http.get_eula(self.eula_id) return EULA(data=data) - async def ticket(self) -> str: - """|coro| - - Retrieves the license ticket for this application. - - Raises - ------- - HTTPException - Retrieving the ticket failed. - - Returns - -------- - :class:`str` - The ticket retrieved. - """ - state = self._state - data = await state.http.get_app_ticket(self.id) - return data['ticket'] - - async def entitlement_ticket(self) -> str: - """|coro| - - Retrieves the entitlement ticket for this application. - - Raises - ------- - HTTPException - Retrieving the ticket failed. - - Returns - -------- - :class:`str` - The ticket retrieved. - """ - state = self._state - data = await state.http.get_app_entitlement_ticket(self.id) - return data['ticket'] - async def activity_statistics(self) -> List[ApplicationActivityStatistics]: """|coro| @@ -2283,10 +2416,104 @@ class Application(PartialApplication): Attributes ------------- - owner: :class:`User` - The application owner. This may be a team user account. + id: :class:`int` + The application ID. + name: :class:`str` + The application name. + description: :class:`str` + The application description. + rpc_origins: Optional[List[:class:`str`]] + A list of RPC origin URLs, if RPC is enabled. + + .. versionchanged:: 2.1 + + The type of this attribute has changed to Optional[List[:class:`str`]]. + verify_key: :class:`str` + The hex encoded key for verification in interactions and the + GameSDK's :ddocs:`GetTicket `_. + overlay_warn: :class:`bool` + Whether the Discord overlay is known to be problematic with the application. + overlay_compatibility_hook: :class:`bool` + Whether to use the compatibility hook for the Discord overlay. + verified: :class:`bool` + Whether the application is verified. + + .. versionadded:: 2.1 + discoverable: :class:`bool` + Whether the application is discoverable in the application directory. + + .. versionadded:: 2.1 + monetized: :class:`bool` + Whether the application has monetization enabled. + + .. versionadded:: 2.1 + storefront_available: :class:`bool` + Whether the application has public subscriptions or products available for purchase. + + .. versionadded:: 2.1 + guild_id: Optional[:class:`int`] + The ID of the guild the application is attached to, if any. + primary_sku_id: Optional[:class:`int`] + The application's primary SKU ID, if any. + This can be an application's game SKU, subscription SKU, etc. + store_listing_sku_id: Optional[:class:`int`] + The application's store listing SKU ID, if any. + If exists, this SKU ID should be used for checks. + slug: Optional[:class:`str`] + The slug for the application's primary SKU, if any. + eula_id: Optional[:class:`int`] + The ID of the EULA for the application, if any. + aliases: List[:class:`str`] + A list of aliases that can be used to identify the application. + developers: List[:class:`Company`] + A list of developers that developed the application. + publishers: List[:class:`Company`] + A list of publishers that published the application. + executables: List[:class:`ApplicationExecutable`] + A list of executables that are the application's. + third_party_skus: List[:class:`ThirdPartySKU`] + A list of third party platforms the SKU is available at. + custom_install_url: Optional[:class:`str`] + The custom URL to use for authorizing the application, if specified. + install_params: Optional[:class:`ApplicationInstallParams`] + The parameters to use for authorizing the application, if specified. + embedded_activity_config: Optional[:class:`EmbeddedActivityConfig`] + The application's embedded activity configuration, if any. bot: Optional[:class:`ApplicationBot`] The bot attached to the application, if any. + owner: :class:`User` + The application owner. This may be a team user account. + team: Optional[:class:`Team`] + The team that owns the application. + + .. note:: + + In almost all cases, this is not available. See :attr:`owner` instead. disabled: :class:`bool` Whether the bot attached to this application is disabled by Discord. @@ -2334,7 +2561,6 @@ class Application(PartialApplication): 'interactions_version', 'interactions_event_types', 'role_connections_verification_url', - 'bot', 'disabled', 'quarantined', 'verification_state', @@ -2371,16 +2597,15 @@ class Application(PartialApplication): self.approximate_guild_count: int = data.get('approximate_guild_count', 0) state = self._state + self.owner = self.owner or state.user # Hacky, but I want these to be persisted existing = getattr(self, 'bot', None) bot = data.get('bot') - if existing is not None: + if existing and bot: existing._update(bot) else: - self.bot: Optional[ApplicationBot] = ApplicationBot(data=bot, state=state, application=self) if bot else None - - self.owner = self.owner or state.user + self.bot: Optional[ApplicationBot] = ApplicationBot(data=bot, state=state, application=self) if bot else existing def __repr__(self) -> str: return ( @@ -3692,6 +3917,22 @@ class IntegrationApplication(Hashable): .. versionadded:: 2.1 type: Optional[:class:`ApplicationType`] The type of application. + verified: :class:`bool` + Whether the application is verified. + + .. versionadded:: 2.1 + discoverable: :class:`bool` + Whether the application is discoverable in the application directory. + + .. versionadded:: 2.1 + monetized: :class:`bool` + Whether the application has monetization enabled. + + .. versionadded:: 2.1 + storefront_available: :class:`bool` + Whether the application has public subscriptions or products available for purchase. + + .. versionadded:: 2.1 primary_sku_id: Optional[:class:`int`] The application's primary SKU ID, if any. This can be an application's game SKU, subscription SKU, etc. @@ -3712,6 +3953,10 @@ class IntegrationApplication(Hashable): 'description', 'deeplink_uri', 'type', + 'verified', + 'discoverable', + 'monetized', + 'storefront_available', 'primary_sku_id', 'role_connections_verification_url', 'third_party_skus', @@ -3733,6 +3978,10 @@ class IntegrationApplication(Hashable): self.description: str = data.get('description') or '' self.deeplink_uri: Optional[str] = data.get('deeplink_uri') self.type: Optional[ApplicationType] = try_enum(ApplicationType, data['type']) if 'type' in data else None + self.verified: bool = data.get('is_verified', False) + self.discoverable: bool = data.get('is_discoverable', False) + self.monetized: bool = data.get('is_monetized', False) + self.storefront_available: bool = data.get('storefront_available', False) self._icon: Optional[str] = data.get('icon') self._cover_image: Optional[str] = data.get('cover_image') diff --git a/discord/client.py b/discord/client.py index ad7fb890c..f07bc21ba 100644 --- a/discord/client.py +++ b/discord/client.py @@ -72,7 +72,15 @@ from .utils import MISSING from .object import Object, OLDEST_OBJECT from .backoff import ExponentialBackoff from .webhook import Webhook -from .application import Application, ApplicationActivityStatistics, Company, EULA, PartialApplication, UnverifiedApplication +from .application import ( + Application, + ApplicationActivityStatistics, + Company, + EULA, + DetectableApplication, + PartialApplication, + UnverifiedApplication, +) from .stage_instance import StageInstance from .threads import Thread from .sticker import GuildSticker, StandardSticker, StickerPack, _sticker_factory @@ -369,7 +377,9 @@ class Client: if status is None: status = getattr(state.settings, 'status', None) or Status.unknown _log.debug('Setting initial presence to %s %s', status, activities) - self.loop.create_task(self.change_presence(activities=activities, status=status, edit_settings=self._sync_presences)) + self.loop.create_task( + self.change_presence(activities=activities, status=status, edit_settings=self._sync_presences) + ) @property def latency(self) -> float: @@ -3254,13 +3264,18 @@ class Client: data = await state.http.get_my_applications(with_team_applications=with_team_applications) return [Application(state=state, data=d) for d in data] - async def detectable_applications(self) -> List[PartialApplication]: + async def detectable_applications(self) -> List[DetectableApplication]: """|coro| Retrieves the list of applications detectable by the Discord client. .. versionadded:: 2.0 + .. versionchanged:: 2.1 + + The method now returns a list of :class:`.DetectableApplication` + instead of :class:`.PartialApplication` due to an API change. + Raises ------- HTTPException @@ -3268,12 +3283,12 @@ class Client: Returns ------- - List[:class:`.PartialApplication`] + List[:class:`.DetectableApplication`] The applications detectable by the Discord client. """ state = self._connection data = await state.http.get_detectable_applications() - return [PartialApplication(state=state, data=d) for d in data] + return [DetectableApplication(state=state, data=d) for d in data] async def fetch_application(self, application_id: int, /) -> Application: """|coro| diff --git a/discord/flags.py b/discord/flags.py index 4af1ed087..1ec9e5574 100644 --- a/discord/flags.py +++ b/discord/flags.py @@ -66,6 +66,7 @@ __all__ = ( 'GiftFlags', 'LibraryApplicationFlags', 'ApplicationDiscoveryFlags', + 'OverlayMethodFlags', 'FriendSourceFlags', 'FriendDiscoveryFlags', 'HubProgressFlags', @@ -1269,15 +1270,8 @@ class ApplicationFlags(BaseFlags): rather than using this raw value. """ - # Commented-out flags are no longer used; they are kept here for historical purposes - __slots__ = () - # @flag_value - # def embedded_released(self): - # """:class:`bool`: Returns ``True`` if the embedded application is released to the public.""" - # return 1 << 1 - @flag_value def managed_emoji(self): """:class:`bool`: Returns ``True`` if the application has the ability to create managed emoji.""" @@ -1293,11 +1287,6 @@ class ApplicationFlags(BaseFlags): """:class:`bool`: Returns ``True`` if the application has the ability to create group DMs without limit.""" return 1 << 4 - # @flag_value - # def rpc_private_beta(self): - # """:class:`bool`: Returns ``True`` if the application has the ability to access the client RPC server.""" - # return 1 << 5 - @flag_value def automod_badge(self): """:class:`bool`: Returns ``True`` if the application has created at least 100 automod rules across all guilds. @@ -1306,25 +1295,21 @@ class ApplicationFlags(BaseFlags): """ return 1 << 6 - # @flag_value - # def allow_assets(self): - # """:class:`bool`: Returns ``True`` if the application has the ability to use activity assets.""" - # return 1 << 8 + @flag_value + def game_profile_disabled(self): + """:class:`bool`: Returns ``True`` if the application has its game profile page disabled. - # @flag_value - # def allow_activity_action_spectate(self): - # """:class:`bool`: Returns ``True`` if the application has the ability to enable spectating activities.""" - # return 1 << 9 + .. versionadded:: 2.1 + """ + return 1 << 7 - # @flag_value - # def allow_activity_action_join_request(self): - # """:class:`bool`: Returns ``True`` if the application has the ability to enable activity join requests.""" - # return 1 << 10 + @flag_value + def public_oauth2_client(self): + """:class:`bool`: Returns ``True`` if the application's OAuth2 credentials are public. - # @flag_value - # def rpc_has_connected(self): - # """:class:`bool`: Returns ``True`` if the application has accessed the client RPC server before.""" - # return 1 << 11 + .. versionadded:: 2.1 + """ + return 1 << 8 @flag_value def gateway_presence(self): @@ -1395,6 +1380,26 @@ class ApplicationFlags(BaseFlags): """ return 1 << 24 + @flag_value + def iframe_modal(self): + """:class:`bool`: Returns ``True`` if the application can use iframes within modals.""" + return 1 << 26 + + @flag_value + def social_layer_integration(self): + """:class:`bool`: Returns ``True`` if the application can use the social layer SDK.""" + return 1 << 27 + + @flag_value + def promoted(self): + """:class:`bool`: Returns ``True`` if the application is promoted by Discord.""" + return 1 << 29 + + @flag_value + def partner(self): + """:class:`bool`: Returns ``True`` if the application is a Discord partner.""" + return 1 << 30 + @fill_with_flags() class ChannelFlags(BaseFlags): @@ -2088,10 +2093,62 @@ class ApplicationDiscoveryFlags(BaseFlags): """:class:`bool`: Returns ``True`` if the application's role connections metadata is safe for work.""" return 1 << 15 + +@fill_with_flags() +class OverlayMethodFlags(BaseFlags): + r"""Wraps up the Discord application overlay method flags. + + .. container:: operations + + .. describe:: x == y + + Checks if two OverlayMethodFlags are equal. + .. describe:: x != y + + Checks if two OverlayMethodFlags are not equal. + .. describe:: x | y, x |= y + + Returns a OverlayMethodFlags instance with all enabled flags from + both x and y. + .. describe:: x & y, x &= y + + Returns a OverlayMethodFlags instance with only flags enabled on + both x and y. + .. describe:: x ^ y, x ^= y + + Returns a OverlayMethodFlags instance with only flags enabled on + only one of x or y, not on both. + .. describe:: ~x + + Returns a OverlayMethodFlags instance with all flags inverted from x. + .. describe:: hash(x) + + Return the flag's hash. + .. describe:: iter(x) + + Returns an iterator of ``(name, value)`` pairs. This allows it + to be, for example, constructed as a dict or a list of pairs. + Note that aliases are not shown. + .. describe:: bool(b) + + Returns whether any flag is set to ``True``. + + .. versionadded:: 2.1 + + Attributes + ----------- + value: :class:`int` + The raw value. This value is a bit array field of a 53-bit integer + representing the currently available flags. You should query + flags via the properties rather than using this raw value. + """ + + __slots__ = () + @flag_value - def eligible(self): - """:class:`bool`: Returns ``True`` if the application has met all the above criteria and is eligible for discovery.""" - return 1 << 16 + def out_of_process(self): + """:class:`bool`: Returns ``True`` if the overlay can be rendered out of process for this application.""" + return 1 << 0 @fill_with_flags() diff --git a/discord/http.py b/discord/http.py index 0543204b6..acf0205da 100644 --- a/discord/http.py +++ b/discord/http.py @@ -3357,7 +3357,7 @@ class HTTPClient: def reset_bot_token(self, app_id: Snowflake) -> Response[application.Token]: return self.request(Route('POST', '/applications/{app_id}/bot/reset', app_id=app_id)) - def get_detectable_applications(self) -> Response[List[application.PartialApplication]]: + def get_detectable_applications(self) -> Response[List[application.DetectableApplication]]: return self.request(Route('GET', '/applications/detectable')) def get_guild_applications( diff --git a/discord/profile.py b/discord/profile.py index 87b6bc483..768898fa2 100644 --- a/discord/profile.py +++ b/discord/profile.py @@ -302,6 +302,10 @@ class ApplicationProfile(Hashable): The application's ID. verified: :class:`bool` Indicates if the application is verified. + storefront_available: :class:`bool` + Indicates if the application has public subscriptions or products available for purchase. + + .. versionadded:: 2.1 popular_application_command_ids: List[:class:`int`] A list of the IDs of the application's popular commands. primary_sku_id: Optional[:class:`int`] @@ -316,6 +320,7 @@ class ApplicationProfile(Hashable): __slots__ = ( 'id', 'verified', + 'storefront_available', 'popular_application_command_ids', 'primary_sku_id', '_flags', @@ -326,6 +331,7 @@ class ApplicationProfile(Hashable): def __init__(self, data: ProfileApplicationPayload) -> None: self.id: int = int(data['id']) self.verified: bool = data.get('verified', False) + self.storefront_available: bool = data.get('storefront_available', False) self.popular_application_command_ids: List[int] = [int(id) for id in data.get('popular_application_command_ids', [])] self.primary_sku_id: Optional[int] = utils._get_as_snowflake(data, 'primary_sku_id') self._flags: int = data.get('flags', 0) diff --git a/discord/types/application.py b/discord/types/application.py index f2863e531..31fe36568 100644 --- a/discord/types/application.py +++ b/discord/types/application.py @@ -47,7 +47,7 @@ class Secret(TypedDict): secret: str -class _BaseApplication(TypedDict): +class BaseApplication(TypedDict): id: Snowflake name: str description: str @@ -59,26 +59,35 @@ class _BaseApplication(TypedDict): summary: NotRequired[Literal['']] deeplink_uri: NotRequired[str] third_party_skus: NotRequired[List[ThirdPartySKU]] + bot: NotRequired[PartialUser] + is_verified: bool + is_discoverable: bool + is_monetized: bool + storefront_available: bool -class BaseApplication(_BaseApplication): - bot: NotRequired[PartialUser] +class DetectableApplication(TypedDict): + id: Snowflake + name: str + hook: bool + overlay: NotRequired[bool] + overlay_methods: NotRequired[int] + overlay_warn: NotRequired[bool] + overlay_compatibility_hook: NotRequired[bool] + aliases: NotRequired[List[str]] + executables: NotRequired[List[ApplicationExecutable]] class IntegrationApplication(BaseApplication): role_connections_verification_url: NotRequired[Optional[str]] -class PartialApplication(_BaseApplication): +class PartialApplication(BaseApplication, DetectableApplication): owner: NotRequired[APIUser] # Not actually ever present in partial app team: NotRequired[Team] verify_key: str flags: NotRequired[int] rpc_origins: NotRequired[List[str]] - hook: NotRequired[bool] - overlay: NotRequired[bool] - overlay_warn: NotRequired[bool] - overlay_compatibility_hook: NotRequired[bool] terms_of_service_url: NotRequired[str] privacy_policy_url: NotRequired[str] max_participants: NotRequired[Optional[int]] @@ -91,13 +100,11 @@ class PartialApplication(_BaseApplication): slug: NotRequired[str] developers: NotRequired[List[Company]] publishers: NotRequired[List[Company]] - aliases: NotRequired[List[str]] eula_id: NotRequired[Snowflake] embedded_activity_config: NotRequired[EmbeddedActivityConfig] guild: NotRequired[PartialGuild] install_params: NotRequired[ApplicationInstallParams] store_listing_sku_id: NotRequired[Snowflake] - executables: NotRequired[List[ApplicationExecutable]] class ApplicationDiscoverability(TypedDict): diff --git a/discord/types/profile.py b/discord/types/profile.py index 1f4c39ec8..3c1ad5fee 100644 --- a/discord/types/profile.py +++ b/discord/types/profile.py @@ -61,6 +61,7 @@ class MutualGuild(TypedDict): class ProfileApplication(TypedDict): id: Snowflake verified: bool + storefront_available: bool popular_application_command_ids: NotRequired[List[Snowflake]] primary_sku_id: NotRequired[Snowflake] flags: int diff --git a/discord/utils.py b/discord/utils.py index 944725c3c..46c38b1b3 100644 --- a/discord/utils.py +++ b/discord/utils.py @@ -443,9 +443,10 @@ def oauth_url( url += f'&{urlencode({"state": state})}' return url + def snowflake_worker_id(id: int, /) -> int: """Returns the worker ID of the given snowflake - + .. versionadded:: 2.1 Parameters @@ -460,9 +461,10 @@ def snowflake_worker_id(id: int, /) -> int: """ return (id >> 17) & 0x1F + def snowflake_process_id(id: int, /) -> int: """Returns the process ID of the given snowflake - + .. versionadded:: 2.1 Parameters @@ -477,6 +479,7 @@ def snowflake_process_id(id: int, /) -> int: """ return (id >> 12) & 0x1F + def snowflake_increment(id: int, /) -> int: """Returns the increment of the given snowflake. For every generated ID on that process, this number is incremented. diff --git a/docs/api.rst b/docs/api.rst index 63ee3a802..85dcf3ae6 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -7169,6 +7169,7 @@ Application .. autoclass:: PartialApplication() :members: + :inherited-members: .. attributetable:: ApplicationProfile @@ -7216,6 +7217,11 @@ Application .. autoclass:: EmbeddedActivityPlatformConfig() :members: +.. attributetable:: DetectableApplication + +.. autoclass:: DetectableApplication() + :members: + .. attributetable:: UnverifiedApplication .. autoclass:: UnverifiedApplication() @@ -8399,6 +8405,11 @@ Flags .. autoclass:: OnboardingProgressFlags() :members: +.. attributetable:: OverlayMethodFlags + +.. autoclass:: OverlayMethodFlags() + :members: + .. attributetable:: PaymentFlags .. autoclass:: PaymentFlags()