Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: user-installable apps #10227

Merged
merged 54 commits into from
Sep 1, 2024
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
d5c6961
feat: inital user-installable apps support
Syjalo Apr 24, 2024
5088fad
docs: add deprecation warnings
Syjalo Apr 25, 2024
b580bd3
Merge branch 'main' into feat/user-installable-app
Syjalo May 13, 2024
f10a3fe
Merge branch 'main' into feat/user-installable-app
Jiralite May 13, 2024
548de0f
Merge branch 'main' into feat/user-installable-app
Syjalo Jun 6, 2024
b198465
feat: add equality checks
Syjalo Jun 19, 2024
1df40d0
fix: possibly `null` cases
Syjalo Jun 20, 2024
e8f97ac
docs: tweaks
Jiralite Jul 9, 2024
0a2dacb
Merge branch 'main' into feat/user-installable-app
Jiralite Jul 9, 2024
f6a823f
docs: add deprecations
Jiralite Jul 9, 2024
c50fe30
fix(ApplicationCommandManager): amend transform command
Jiralite Jul 9, 2024
92b7fea
feat: properly support `integration_types_config`
Jiralite Jul 9, 2024
876686c
docs: add .
Jiralite Jul 9, 2024
5c13949
docs: minor changes
Jiralite Jul 9, 2024
51f7078
featBaseApplicationCommandData): update type
Jiralite Jul 9, 2024
92c5546
style: prettier
Jiralite Jul 9, 2024
685412c
chore: fix issues
Jiralite Jul 9, 2024
8253458
Merge branch 'main' into feat/user-installable-app
Jiralite Jul 9, 2024
e1d9350
fix: correct casing
Jiralite Jul 21, 2024
2f20eeb
refactor: remove console log
Syjalo Aug 10, 2024
9ea527c
fix: use case that satisfies `/core` and the API
Syjalo Aug 10, 2024
c99752a
fix: `oauth2InstallParams` property is not nullable
Syjalo Aug 10, 2024
b68015d
fix: do not convert keys into strings
Syjalo Aug 10, 2024
a256136
feat: update transforer to return the full map
Syjalo Aug 10, 2024
50c0309
feat: update transformers
Syjalo Aug 10, 2024
50f03fa
feat: add `PartialGroupDMMessageManager `
Syjalo Aug 14, 2024
a62472f
docs: fix type
Syjalo Aug 15, 2024
4509116
feat: add approximate count of users property
Syjalo Aug 15, 2024
4670ca2
fix: messageCreate doesn't emit in PartialGroupDMChannel
Syjalo Aug 17, 2024
bfabc23
fix: add GroupDM to TextBasedChannelTypes
Syjalo Aug 17, 2024
2cc3bba
feat: add NonPartialGroupDMChannel helper
Syjalo Aug 18, 2024
12eab6a
fix: expect PartialGroupDMChannel
Syjalo Aug 18, 2024
b0e3f33
feat: narrow generic type
Syjalo Aug 18, 2024
d5465df
test: exclude PartialGroupDMChannel
Syjalo Aug 18, 2024
bfadec0
feat: use structure's channel type
Syjalo Aug 18, 2024
0043728
docs: narrow type
Syjalo Aug 20, 2024
5400e18
feat: remove transformer
Syjalo Aug 20, 2024
50319a5
refactor: remove unnecessary parse
Syjalo Aug 20, 2024
21c86d8
feat: add APIAutoModerationAction transformer
Syjalo Aug 20, 2024
22b46d7
Merge branch 'main' into feat/user-installable-app
Syjalo Aug 21, 2024
49c6fd5
fix: use the right transformer during recursive parsing of interactio…
vladfrangu Aug 21, 2024
86cd370
docs: add external types
Syjalo Aug 28, 2024
c5d87e3
docs: add `Message#interactionMetadata` property docs
Syjalo Aug 28, 2024
e6e8231
docs: make nullable
Syjalo Aug 28, 2024
708dad3
Merge branch 'main' into feat/user-installable-app
Syjalo Aug 28, 2024
e4006a5
docs: add d-docs link
Syjalo Aug 28, 2024
557be0d
docs: use optional
Syjalo Aug 28, 2024
19c3222
fix: make `oauth2InstallParams` nullable
Syjalo Aug 29, 2024
1f516a4
types: update `IntegrationTypesConfiguration`
Syjalo Aug 29, 2024
933aa20
docs: update `IntegrationTypesConfigurationParameters`
Syjalo Aug 29, 2024
72f9251
types: update `IntegrationTypesConfigurationParameters`
Syjalo Aug 29, 2024
2b25024
refactor: improve readability
Syjalo Aug 29, 2024
9d61a22
docs: mark integrationTypesConfig nullable
Syjalo Aug 31, 2024
b2e0d7d
refactor: requested changes
almeidx Sep 1, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ export class ContextMenuCommandBuilder {
*
* @remarks
* By default, commands are visible. This property is only for global commands.
* @deprecated
* Use {@link ContextMenuCommandBuilder.contexts} instead.
*/
public readonly dm_permission: boolean | undefined = undefined;

Expand Down Expand Up @@ -167,6 +169,7 @@ export class ContextMenuCommandBuilder {
* By default, commands are visible. This method is only for global commands.
* @param enabled - Whether the command should be enabled in direct messages
* @see {@link https://discord.com/developers/docs/interactions/application-commands#permissions}
* @deprecated Use {@link ContextMenuCommandBuilder.setContexts} instead.
*/
public setDMPermission(enabled: boolean | null | undefined) {
// Assert the value matches the conditions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ export class SlashCommandBuilder {
*
* @remarks
* By default, commands are visible. This property is only for global commands.
* @deprecated
* Use {@link SlashCommandBuilder.contexts} instead.
*/
public readonly dm_permission: boolean | undefined = undefined;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export class SharedSlashCommand {

public readonly default_member_permissions: Permissions | null | undefined = undefined;

/**
* @deprecated Use {@link SharedSlashCommand.contexts} instead.
*/
public readonly dm_permission: boolean | undefined = undefined;

public readonly integration_types?: ApplicationIntegrationType[];
Expand Down Expand Up @@ -112,6 +115,8 @@ export class SharedSlashCommand {
* By default, commands are visible. This method is only for global commands.
* @param enabled - Whether the command should be enabled in direct messages
* @see {@link https://discord.com/developers/docs/interactions/application-commands#permissions}
* @deprecated
* Use {@link SharedSlashCommand.setContexts} instead.
*/
public setDMPermission(enabled: boolean | null | undefined) {
// Assert the value matches the conditions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ class ApplicationCommandManager extends CachedManager {
options: command.options?.map(option => ApplicationCommand.transformOption(option)),
default_member_permissions,
dm_permission: command.dmPermission ?? command.dm_permission,
integration_types: command.integrationTypes ?? command.integration_types,
contexts: command.contexts,
};
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict';

const MessageManager = require('./MessageManager');

/**
* Manages API methods for messages in group direct message channels and holds their cache.
* @extends {MessageManager}
*/
class PartialGroupDMMessageManager extends MessageManager {
/**
* The channel that the messages belong to
* @name PartialGroupDMMessageManager#channel
* @type {PartialGroupDMChannel}
*/
}

module.exports = PartialGroupDMMessageManager;
27 changes: 26 additions & 1 deletion packages/discord.js/src/structures/ApplicationCommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,35 @@ class ApplicationCommand extends Base {
* Whether the command can be used in DMs
* <info>This property is always `null` on guild commands</info>
* @type {?boolean}
* @deprecated Use {@link ApplicationCommand#contexts} instead.
*/
this.dmPermission = data.dm_permission;
} else {
this.dmPermission ??= null;
}

if ('integration_types' in data) {
/**
* Installation context(s) where the command is available
* <info>Only for globally-scoped commands</info>
* @type {?ApplicationIntegrationType[]}
Syjalo marked this conversation as resolved.
Show resolved Hide resolved
*/
this.integrationTypes = data.integration_types;
} else {
this.integrationTypes ??= null;
}

if ('contexts' in data) {
/**
* Interaction context(s) where the command can be used
* <info>Only for globally-scoped commands</info>
* @type {?InteractionContextType[]}
Syjalo marked this conversation as resolved.
Show resolved Hide resolved
*/
this.contexts = data.contexts;
} else {
this.contexts ??= null;
}

if ('version' in data) {
/**
* Autoincrementing version identifier updated during substantial record changes
Expand Down Expand Up @@ -394,7 +417,9 @@ class ApplicationCommand extends Base {
!isEqual(
command.descriptionLocalizations ?? command.description_localizations ?? {},
this.descriptionLocalizations ?? {},
)
) ||
!isEqual(command.integrationTypes ?? command.integration_types ?? [], this.integrationTypes ?? {}) ||
!isEqual(command.contexts ?? [], this.contexts ?? [])
) {
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/discord.js/src/structures/BaseInteraction.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ class BaseInteraction extends Base {

/**
* Set of permissions the application or bot has within the channel the interaction was sent from
* @type {?Readonly<PermissionsBitField>}
* @type {Readonly<PermissionsBitField>}
*/
this.appPermissions = data.app_permissions ? new PermissionsBitField(data.app_permissions).freeze() : null;
this.appPermissions = new PermissionsBitField(data.app_permissions).freeze();

/**
* The permissions of the member, if one exists, in the channel this interaction was executed in
Expand Down
60 changes: 58 additions & 2 deletions packages/discord.js/src/structures/ClientApplication.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const PermissionsBitField = require('../util/PermissionsBitField');

/**
* @typedef {Object} ClientApplicationInstallParams
* @property {OAuth2Scopes[]} scopes The scopes to add the application to the server with
* @property {Readonly<PermissionsBitField>} permissions The permissions this bot will request upon joining
* @property {OAuth2Scopes[]} scopes Scopes that will be set upon adding this application
* @property {Readonly<PermissionsBitField>} permissions Permissions that will be requested for the integrated role
*/

/**
Expand Down Expand Up @@ -61,6 +61,52 @@ class ClientApplication extends Application {
this.installParams ??= null;
}

/**
* OAuth2 installation paremeters.
almeidx marked this conversation as resolved.
Show resolved Hide resolved
* @typedef {Object} IntegrationTypesConfigurationParameters
* @property {?string[]} scopes Scopes that will be set upon adding this application
Syjalo marked this conversation as resolved.
Show resolved Hide resolved
* @property {?Readonly<PermissionsBitField>} permissions Permissions that will be requested for the integrated role
*/

/**
* The application's supported installation context data.
* @typedef {Object} IntegrationTypesConfigurationContext
* @property {?IntegrationTypesConfigurationParameters} oauth2InstallParams
Syjalo marked this conversation as resolved.
Show resolved Hide resolved
* Scopes and permissions regarding the installation context
*/

/**
* The application's supported installation context data.
* @typedef {Object} IntegrationTypesConfiguration
* @property {?IntegrationTypesConfigurationContext} 0 Scopes and permissions
* regarding the guild-installation context
* @property {?IntegrationTypesConfigurationContext} 1 Scopes and permissions
* regarding the user-installation context
*/

if ('integration_types_config' in data) {
/**
* Default scopes and permissions for each supported installation context.
* The keys are stringified variants of {@link ApplicationIntegrationType}.
* @type {IntegrationTypesConfiguration}
Syjalo marked this conversation as resolved.
Show resolved Hide resolved
*/
this.integrationTypesConfig = Object.fromEntries(
Object.entries(data.integration_types_config).map(([key, config]) => {
const context = {
oauth2InstallParams: {
scopes: config.oauth2_install_params?.scopes ?? null,
permissions: config.oauth2_install_params
? new PermissionsBitField(config.oauth2_install_params.permissions).freeze()
: null,
},
Syjalo marked this conversation as resolved.
Show resolved Hide resolved
};
return [parseInt(key), context];
}),
);
} else {
this.integrationTypesConfig ??= null;
}

if ('custom_install_url' in data) {
/**
* This application's custom installation URL
Expand Down Expand Up @@ -89,6 +135,16 @@ class ClientApplication extends Application {
this.approximateGuildCount ??= null;
}

if ('approximate_user_install_count' in data) {
/**
* An approximate amount of users that have installed this application.
* @type {?number}
*/
this.approximateUserInstallCount = data.approximate_user_install_count;
} else {
this.approximateUserInstallCount ??= null;
}

if ('guild_id' in data) {
/**
* The id of the guild associated with this application.
Expand Down
20 changes: 20 additions & 0 deletions packages/discord.js/src/structures/CommandInteraction.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const Attachment = require('./Attachment');
const BaseInteraction = require('./BaseInteraction');
const InteractionWebhook = require('./InteractionWebhook');
const InteractionResponses = require('./interfaces/InteractionResponses');
const { _transformAPIAuthorizingIntegrationOwners } = require('../util/Transformers');

/**
* Represents a command interaction.
Expand Down Expand Up @@ -45,6 +46,25 @@ class CommandInteraction extends BaseInteraction {
*/
this.commandGuildId = data.data.guild_id ?? null;

/**
* Owner IDs the integration is installed on.
* @typedef {Object} AuthorizingIntegrationOwners
* @property {?Snowflake} 0 The guild id the integration is installed on
* @property {?Snowflake} 1 The user id the integration is installed on
*/

/**
* Mapping of installation contexts that the interaction was authorized for the related user or guild ids
* @type {AuthorizingIntegrationOwners}
*/
this.authorizingIntegrationOwners = _transformAPIAuthorizingIntegrationOwners(data.authorizingIntegrationOwners);
Syjalo marked this conversation as resolved.
Show resolved Hide resolved

/**
* Context where the interaction was triggered from
* @type {?InteractionContextType}
*/
this.context = data.context ?? null;

/**
* Whether the reply to this interaction has been deferred
* @type {boolean}
Expand Down
34 changes: 34 additions & 0 deletions packages/discord.js/src/structures/Message.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const { createComponent } = require('../util/Components');
const { NonSystemMessageTypes, MaxBulkDeletableMessageAge, UndeletableMessageTypes } = require('../util/Constants');
const MessageFlagsBitField = require('../util/MessageFlagsBitField');
const PermissionsBitField = require('../util/PermissionsBitField');
const { _transformAPIAuthorizingIntegrationOwners } = require('../util/Transformers.js');
const { cleanContent, resolvePartialEmoji, transformResolved } = require('../util/Util');

/**
Expand Down Expand Up @@ -383,6 +384,38 @@ class Message extends Base {
this.channel?.messages._add({ guild_id: data.message_reference?.guild_id, ...data.referenced_message });
}

if (data.interaction_metadata) {
/**
* Partial data of the interaction that this message is a result of
* @typedef {Object} MessageInteractionMetadata
* @property {Snowflake} id The interaction's id
* @property {InteractionType} type The type of the interaction
* @property {User} user The user that invoked the interaction
* @property {AuthorizingIntegrationOwners} authorizingIntegrationOwners
* Ids for installation context(s) related to an interaction
* @property {?Snowflake} originalResponseMessageId
* Id of the original response message. Present only on follow-up messages
* @property {?Snowflake} interactedMessageId
* Id of the message that contained interactive component.
* Present only on messages created from component interactions
* @property {?MessageInteractionMetadata} triggeringInteractionMetadata
* Metadata for the interaction that was used to open the modal. Present only on modal submit interactions
*/
this.interactionMetadata = {
id: data.interaction_metadata.id,
type: data.interaction_metadata.type,
user: this.client.users._add(data.interaction_metadata.user),
authorizingIntegrationOwners: _transformAPIAuthorizingIntegrationOwners(
data.interaction_metadata.authorizing_integration_owners,
),
originalResponseMessageId: data.interaction_metadata.original_response_message_id ?? null,
interactedMessageId: data.interaction_metadata.interacted_message_id ?? null,
triggeringInteractionMetadata: data.interaction_metadata.triggering_interaction_metadata ?? null,
Syjalo marked this conversation as resolved.
Show resolved Hide resolved
};
} else {
this.interactionMetadata ??= null;
}

/**
* Partial data of the interaction that a message is a reply to
* @typedef {Object} MessageInteraction
Expand All @@ -391,6 +424,7 @@ class Message extends Base {
* @property {string} commandName The name of the interaction's application command,
* as well as the subcommand and subcommand group, where applicable
* @property {User} user The user that invoked the interaction
* @deprecated Use {@link Message#interactionMetadata} instead.
*/

if (data.interaction) {
Expand Down
7 changes: 7 additions & 0 deletions packages/discord.js/src/structures/PartialGroupDMChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const { BaseChannel } = require('./BaseChannel');
const { DiscordjsError, ErrorCodes } = require('../errors');
const PartialGroupDMMessageManager = require('../managers/PartialGroupDMMessageManager');

/**
* Represents a Partial Group DM Channel on Discord.
Expand Down Expand Up @@ -37,6 +38,12 @@ class PartialGroupDMChannel extends BaseChannel {
* @type {PartialRecipient[]}
*/
this.recipients = data.recipients;

/**
* A manager of the messages belonging to this channel
* @type {PartialGroupDMMessageManager}
*/
this.messages = new PartialGroupDMMessageManager(this);
}

/**
Expand Down
12 changes: 11 additions & 1 deletion packages/discord.js/src/util/Transformers.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,14 @@ function _transformAPIAutoModerationAction(autoModerationAction) {
};
}

module.exports = { toSnakeCase, _transformAPIAutoModerationAction };
/**
* Transforms an API authorizing integration owners map to keyed by enum.
* @param {APIAuthorizingIntegrationOwnersMap} authorizingIntegrationOwners The data to transform
* @returns {AuthorizingIntegrationOwners}
* @ignore
*/
function _transformAPIAuthorizingIntegrationOwners(authorizingIntegrationOwners) {
return Object.fromEntries(Object.entries(authorizingIntegrationOwners).map(([key, owner]) => [parseInt(key), owner]));
Syjalo marked this conversation as resolved.
Show resolved Hide resolved
}

module.exports = { toSnakeCase, _transformAPIAutoModerationAction, _transformAPIAuthorizingIntegrationOwners };
Loading
Loading