diff --git a/packages/bot/src/transformers.ts b/packages/bot/src/transformers.ts index 25f4e1b39..2ece767d7 100644 --- a/packages/bot/src/transformers.ts +++ b/packages/bot/src/transformers.ts @@ -31,6 +31,7 @@ import type { DiscordInviteStageInstance, DiscordMember, DiscordMessage, + DiscordMessageInteractionMetadata, DiscordPoll, DiscordPollMedia, DiscordPresenceUpdate, @@ -85,7 +86,7 @@ import { transformIntegration, type Integration } from './transformers/integrati import { transformInteraction, transformInteractionDataOption, type Interaction, type InteractionDataOption } from './transformers/interaction.js' import { transformInvite, type Invite } from './transformers/invite.js' import { transformMember, type Member } from './transformers/member.js' -import { transformMessage, type Message } from './transformers/message.js' +import { transformMessage, type Message, type MessageInteractionMetadata, transformMessageInteractionMetadata } from './transformers/message.js' import { transformGuildOnboarding, type GuildOnboarding } from './transformers/onboarding.js' import { transformPoll, transformPollMedia, type Poll, type PollMedia } from './transformers/poll.js' import { transformPresence, type PresenceUpdate } from './transformers/presence.js' @@ -120,6 +121,7 @@ export interface Transformers { channel: (bot: Bot, payload: DiscordChannel, channel: Channel) => any interaction: (bot: Bot, payload: DiscordInteraction, interaction: Interaction) => any message: (bot: Bot, payload: DiscordMessage, message: Message) => any + messageInteractionMetadata: (bot: Bot, payload: DiscordMessageInteractionMetadata, metadata: MessageInteractionMetadata) => any user: (bot: Bot, payload: DiscordUser, user: User) => any member: (bot: Bot, payload: DiscordMember, member: Member) => any role: (bot: Bot, payload: DiscordRole, role: Role) => any @@ -297,6 +299,8 @@ export interface Transformers { locale: boolean guildLocale: boolean appPermissions: boolean + authorizingIntegrationOwners: boolean + context: boolean } invite: { channelId: boolean @@ -345,6 +349,15 @@ export interface Transformers { embeds: boolean guildId: boolean id: boolean + interactionMetadata: { + id: boolean + type: boolean + userId: boolean + authorizingIntegrationOwners: boolean + originalResponseMessageId: boolean + interactedMessageId: boolean + triggeringInteractionMetadata: boolean + } interaction: { id: boolean member: boolean @@ -560,6 +573,7 @@ export interface Transformers { user: (bot: Bot, payload: DiscordUser) => User member: (bot: Bot, payload: DiscordMember, guildId: BigString, userId: BigString) => Member message: (bot: Bot, payload: DiscordMessage) => Message + messageInteractionMetadata: (bot: Bot, payload: DiscordMessageInteractionMetadata) => MessageInteractionMetadata role: (bot: Bot, payload: { role: DiscordRole } & { guildId: BigString }) => Role voiceState: (bot: Bot, payload: { voiceState: DiscordVoiceState } & { guildId: bigint }) => VoiceState interaction: (bot: Bot, payload: DiscordInteraction) => Interaction @@ -631,6 +645,9 @@ export function createTransformers(options: Partial, opts?: Create message(bot, payload, message) { return message }, + messageInteractionMetadata(bot, payload, metadata) { + return metadata + }, role(bot, payload, role) { return role }, @@ -881,6 +898,8 @@ export function createTransformers(options: Partial, opts?: Create locale: opts?.defaultDesiredPropertiesValue ?? false, guildLocale: opts?.defaultDesiredPropertiesValue ?? false, appPermissions: opts?.defaultDesiredPropertiesValue ?? false, + authorizingIntegrationOwners: opts?.defaultDesiredPropertiesValue ?? false, + context: opts?.defaultDesiredPropertiesValue ?? false, }, invite: { channelId: opts?.defaultDesiredPropertiesValue ?? false, @@ -929,6 +948,15 @@ export function createTransformers(options: Partial, opts?: Create embeds: opts?.defaultDesiredPropertiesValue ?? false, guildId: opts?.defaultDesiredPropertiesValue ?? false, id: opts?.defaultDesiredPropertiesValue ?? false, + interactionMetadata: { + id: opts?.defaultDesiredPropertiesValue ?? false, + type: opts?.defaultDesiredPropertiesValue ?? false, + userId: opts?.defaultDesiredPropertiesValue ?? false, + authorizingIntegrationOwners: opts?.defaultDesiredPropertiesValue ?? false, + originalResponseMessageId: opts?.defaultDesiredPropertiesValue ?? false, + interactedMessageId: opts?.defaultDesiredPropertiesValue ?? false, + triggeringInteractionMetadata: opts?.defaultDesiredPropertiesValue ?? false, + }, interaction: { id: opts?.defaultDesiredPropertiesValue ?? false, member: opts?.defaultDesiredPropertiesValue ?? false, @@ -1152,6 +1180,7 @@ export function createTransformers(options: Partial, opts?: Create invite: options.invite ?? transformInvite, member: options.member ?? transformMember, message: options.message ?? transformMessage, + messageInteractionMetadata: options.messageInteractionMetadata ?? transformMessageInteractionMetadata, presence: options.presence ?? transformPresence, role: options.role ?? transformRole, user: options.user ?? transformUser, diff --git a/packages/bot/src/transformers/application.ts b/packages/bot/src/transformers/application.ts index 4d8421d82..42bc2e805 100644 --- a/packages/bot/src/transformers/application.ts +++ b/packages/bot/src/transformers/application.ts @@ -1,10 +1,12 @@ import { + DiscordApplicationIntegrationType, iconHashToBigInt, type ApplicationFlags, type Bot, type DiscordApplication, type DiscordUser, type Guild, + type OAuth2Scope, type Team, type User, } from '../index.js' @@ -38,6 +40,26 @@ export function transformApplication(bot: Bot, payload: { application: DiscordAp bot: payload.application.bot ? bot.transformers.user(bot, payload.application.bot as DiscordUser) : undefined, interactionsEndpointUrl: payload.application.interactions_endpoint_url, redirectUris: payload.application.redirect_uris, + integrationTypesConfig: payload.application.integration_types_config + ? { + [DiscordApplicationIntegrationType.GuildInstall]: payload.application.integration_types_config['0']?.oauth2_install_params + ? { + oauth2InstallParams: { + scopes: payload.application.integration_types_config['0'].oauth2_install_params.scopes, + permissions: bot.transformers.snowflake(payload.application.integration_types_config['0'].oauth2_install_params.permissions), + }, + } + : undefined, + [DiscordApplicationIntegrationType.UserInstall]: payload.application.integration_types_config['1']?.oauth2_install_params + ? { + oauth2InstallParams: { + scopes: payload.application.integration_types_config['1'].oauth2_install_params.scopes, + permissions: bot.transformers.snowflake(payload.application.integration_types_config['1'].oauth2_install_params.permissions), + }, + } + : undefined, + } + : undefined, } as Application return bot.transformers.customizers.application(bot, payload.application, application) @@ -66,4 +88,15 @@ export interface Application { bot?: User redirectUris?: string[] interactionsEndpointUrl?: string + integrationTypesConfig?: Partial> +} + +export interface ApplicationIntegrationTypeConfiguration { + /** Install params for each installation context's default in-app authorization link */ + oauth2InstallParams?: { + /** Scopes to add the application to the server with */ + scopes: OAuth2Scope[] + /** Permissions to request for the bot role */ + permissions: bigint + } } diff --git a/packages/bot/src/transformers/interaction.ts b/packages/bot/src/transformers/interaction.ts index c7e2c1069..79fae018e 100644 --- a/packages/bot/src/transformers/interaction.ts +++ b/packages/bot/src/transformers/interaction.ts @@ -11,7 +11,14 @@ import { type MessageComponentTypes, } from '@discordeno/types' import { Collection } from '@discordeno/utils' -import type { Bot, Channel, Component, DiscordChannel } from '../index.js' +import { + type Bot, + type Channel, + type Component, + DiscordApplicationIntegrationType, + type DiscordChannel, + type DiscordInteractionContextType, +} from '../index.js' import { MessageFlags, type DiscordInteractionDataResolved } from '../typings.js' import type { Attachment } from './attachment.js' import type { Member } from './member.js' @@ -71,6 +78,10 @@ export interface Interaction extends BaseInteraction { guildLocale?: string /** The computed permissions for a bot or app in the context of a specific interaction (including channel overwrites) */ appPermissions: bigint + /** Mapping of installation contexts that the interaction was authorized for to related user or guild IDs. */ + authorizingIntegrationOwners: Partial> + /** Context where the interaction was triggered from */ + context?: DiscordInteractionContextType } export interface BaseInteraction { @@ -232,6 +243,19 @@ export function transformInteraction(bot: Bot, payload: DiscordInteraction): Int if (props.channel && payload.channel) interaction.channel = bot.transformers.channel(bot, { channel: payload.channel as DiscordChannel, guildId }) if (props.channelId && payload.channel_id) interaction.channelId = bot.transformers.snowflake(payload.channel_id) if (props.member && guildId && payload.member) interaction.member = bot.transformers.member(bot, payload.member, guildId, user.id) + if (props.authorizingIntegrationOwners && payload.authorizing_integration_owners) { + interaction.authorizingIntegrationOwners = {} + + if (payload.authorizing_integration_owners['0']) + interaction.authorizingIntegrationOwners[DiscordApplicationIntegrationType.GuildInstall] = bot.transformers.snowflake( + payload.authorizing_integration_owners['0'], + ) + if (payload.authorizing_integration_owners['1']) + interaction.authorizingIntegrationOwners[DiscordApplicationIntegrationType.UserInstall] = bot.transformers.snowflake( + payload.authorizing_integration_owners['1'], + ) + } + if (props.context && payload.context) interaction.context = payload.context if (props.data && payload.data) { interaction.data = { type: payload.data.type, diff --git a/packages/bot/src/transformers/message.ts b/packages/bot/src/transformers/message.ts index 2cd1437c3..c892d9b4d 100644 --- a/packages/bot/src/transformers/message.ts +++ b/packages/bot/src/transformers/message.ts @@ -1,4 +1,12 @@ -import type { DiscordMessage, InteractionTypes, MessageActivityTypes, MessageTypes, StickerFormatTypes } from '@discordeno/types' +import { + DiscordApplicationIntegrationType, + type DiscordMessage, + type DiscordMessageInteractionMetadata, + type InteractionTypes, + type MessageActivityTypes, + type MessageTypes, + type StickerFormatTypes, +} from '@discordeno/types' import { CHANNEL_MENTION_REGEX } from '../constants.js' import { snowflakeToTimestamp, type Bot } from '../index.js' import { MessageFlags } from '../typings.js' @@ -189,7 +197,13 @@ export interface Message extends MessageBase { guildId?: bigint /** id of the message */ id: bigint - /** Sent if the message is a response to an Interaction */ + /** sent if the message is sent as a result of an interaction */ + interactionMetadata?: MessageInteractionMetadata + /** + * Sent if the message is a response to an Interaction + * + * @deprecated Deprecated in favor of {@link interactionMetadata} + */ interaction?: { /** Id of the interaction */ id: bigint @@ -281,6 +295,7 @@ export function transformMessage(bot: Bot, payload: DiscordMessage): Message { if (props.embeds && payload.embeds?.length) message.embeds = payload.embeds.map((embed) => bot.transformers.embed(bot, embed)) if (props.guildId && guildId) message.guildId = guildId if (props.id && payload.id) message.id = bot.transformers.snowflake(payload.id) + if (payload.interaction_metadata) message.interactionMetadata = transformMessageInteractionMetadata(bot, payload.interaction_metadata) if (payload.interaction) { const interaction = {} as NonNullable let edited = false @@ -371,3 +386,48 @@ export function transformMessage(bot: Bot, payload: DiscordMessage): Message { return bot.transformers.customizers.message(bot, payload, message) } + +export function transformMessageInteractionMetadata(bot: Bot, payload: DiscordMessageInteractionMetadata): MessageInteractionMetadata { + const props = bot.transformers.desiredProperties.message.interactionMetadata + const metadata = {} as MessageInteractionMetadata + + if (props.id) metadata.id = bot.transformers.snowflake(payload.id) + if (props.authorizingIntegrationOwners) { + metadata.authorizingIntegrationOwners = {} + if (payload.authorizing_integration_owners['0']) + metadata.authorizingIntegrationOwners[DiscordApplicationIntegrationType.GuildInstall] = bot.transformers.snowflake( + payload.authorizing_integration_owners['0'], + ) + if (payload.authorizing_integration_owners['1']) + metadata.authorizingIntegrationOwners[DiscordApplicationIntegrationType.UserInstall] = bot.transformers.snowflake( + payload.authorizing_integration_owners['1'], + ) + } + if (props.interactedMessageId && payload.interacted_message_id) + metadata.interactedMessageId = bot.transformers.snowflake(payload.interacted_message_id) + if (props.originalResponseMessageId && payload.original_response_message_id) + metadata.originalResponseMessageId = bot.transformers.snowflake(payload.original_response_message_id) + if (props.triggeringInteractionMetadata && payload.triggering_interaction_metadata) + metadata.triggeringInteractionMetadata = transformMessageInteractionMetadata(bot, payload.triggering_interaction_metadata) + if (props.type) metadata.type = payload.type + if (props.userId) metadata.userId = bot.transformers.snowflake(payload.user_id) + + return bot.transformers.customizers.messageInteractionMetadata(bot, payload, metadata) +} + +export interface MessageInteractionMetadata { + /** Id of the interaction */ + id: bigint + /** The type of interaction */ + type: InteractionTypes + /** ID of the user who triggered the interaction */ + userId: bigint + /** IDs for installation context(s) related to an interaction */ + authorizingIntegrationOwners: Partial> + /** ID of the original response message, present only on follow-up messages */ + originalResponseMessageId?: bigint + /** ID of the message that contained interactive component, present only on messages created from component interactions */ + interactedMessageId?: bigint + /** Metadata for the interaction that was used to open the modal, present only on modal submit interactions */ + triggeringInteractionMetadata?: MessageInteractionMetadata +} diff --git a/packages/rest/src/manager.ts b/packages/rest/src/manager.ts index d56428fb2..f192e7d1e 100644 --- a/packages/rest/src/manager.ts +++ b/packages/rest/src/manager.ts @@ -974,7 +974,7 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage }, async editApplicationInfo(body) { - return await rest.patch(rest.routes.oauth2.application(), { + return await rest.patch(rest.routes.application(), { body, }) }, diff --git a/packages/rest/src/routes.ts b/packages/rest/src/routes.ts index eb942cf1f..23a5b23ae 100644 --- a/packages/rest/src/routes.ts +++ b/packages/rest/src/routes.ts @@ -628,6 +628,10 @@ export function createRoutes(): RestRoutes { return `/users/${userId}` }, + application() { + return '/applications/@me' + }, + currentUser() { return '/users/@me' }, diff --git a/packages/rest/src/typings/routes.ts b/packages/rest/src/typings/routes.ts index 57679ef5e..d637c8929 100644 --- a/packages/rest/src/typings/routes.ts +++ b/packages/rest/src/typings/routes.ts @@ -255,7 +255,7 @@ export interface RestRoutes { tokenRevoke: () => string /** Route to get information about the current authorization. Requires an access token */ currentAuthorization: () => string - /** Route to get information about the current application. Requires an access token */ + /** Route to get information about the current application. */ application: () => string /** Route to get the connection the user has. Requires the `connections` OAuth2 scope */ connections: () => string @@ -273,6 +273,8 @@ export interface RestRoutes { } /** Get information about the current OAuth2 user / bot user. If used with a OAuth2 token requires the `identify` OAuth2 scope */ currentUser: () => string + /** Route to get and edit information about the current application. */ + application: () => string /** Route for handling a sticker. */ sticker: (stickerId: BigString) => string /** Route for handling all voice regions. */ diff --git a/packages/types/src/camel.ts b/packages/types/src/camel.ts index 46cf91159..56d1bec39 100644 --- a/packages/types/src/camel.ts +++ b/packages/types/src/camel.ts @@ -15,6 +15,7 @@ import type { DiscordApplicationCommandOption, DiscordApplicationCommandOptionChoice, DiscordApplicationCommandPermissions, + DiscordApplicationIntegrationTypeConfiguration, DiscordApplicationRoleConnection, DiscordApplicationWebhook, DiscordArchivedThreads, @@ -180,6 +181,7 @@ export interface CamelizedDiscordGuildIntegrationsUpdate extends Camelize {} export interface CamelizedDiscordMember extends Camelize {} export interface CamelizedDiscordApplication extends Camelize {} +export interface CamelizedDiscordApplicationIntegrationTypeConfiguration extends Camelize {} export interface CamelizedDiscordApplicationRoleConnection extends Camelize {} export type CamelizedDiscordTokenExchange = Camelize export interface CamelizedDiscordTokenExchangeAuthorizationCode extends Camelize {} diff --git a/packages/types/src/discord.ts b/packages/types/src/discord.ts index a16b5e7ca..84424d6f2 100644 --- a/packages/types/src/discord.ts +++ b/packages/types/src/discord.ts @@ -373,6 +373,13 @@ export interface DiscordApplication { tags?: string[] /** settings for the application's default in-app authorization link, if enabled */ install_params?: DiscordInstallParams + /** + * Default scopes and permissions for each supported installation context. + * + * @remarks + * This is currently in preview. + */ + integration_types_config?: Partial> /** the application's default custom authorization link, if enabled */ custom_install_url?: string /** the application's role connection verification entry point, which when configured will render the app as a verification method in the guild role verification configuration */ @@ -387,6 +394,28 @@ export interface DiscordApplication { interactions_endpoint_url?: string } +/** https://discord.com/developers/docs/resources/application#application-object-application-integration-type-configuration-object */ +export interface DiscordApplicationIntegrationTypeConfiguration { + /** + * Install params for each installation context's default in-app authorization link + * + * https://discord.com/developers/docs/resources/application#install-params-object-install-params-structure + */ + oauth2_install_params?: { + /** Scopes to add the application to the server with */ + scopes: OAuth2Scope[] + /** Permissions to request for the bot role */ + permissions: string + } +} + +export enum DiscordApplicationIntegrationType { + /** App is installable to servers */ + GuildInstall = 0, + /** App is installable to users */ + UserInstall = 1, +} + export type DiscordTokenExchange = DiscordTokenExchangeAuthorizationCode | DiscordTokenExchangeRefreshToken | DiscordTokenExchangeClientCredentials export interface DiscordTokenExchangeAuthorizationCode { @@ -1321,7 +1350,13 @@ export interface DiscordMessage { * Note: This field is only returned for messages with a `type` of `19` (REPLY). If the message is a reply but the `referenced_message` field is not present, the backend did not attempt to fetch the message that was being replied to, so its state is unknown. If the field exists but is null, the referenced message was deleted. */ referenced_message?: DiscordMessage - /** Sent if the message is a response to an Interaction */ + /** sent if the message is sent as a result of an interaction */ + interaction_metadata?: DiscordMessageInteractionMetadata + /** + * Sent if the message is a response to an Interaction + * + * @deprecated Deprecated in favor of {@link interaction_metadata} + */ interaction?: DiscordMessageInteraction /** The thread that was started from this message, includes thread member object */ thread?: Omit & { member: DiscordThreadMember } @@ -1550,6 +1585,24 @@ export interface DiscordMessageInteraction { member?: Partial } +/** https://discord.com/developers/docs/resources/channel#message-interaction-metadata-object-message-interaction-metadata-structure */ +export interface DiscordMessageInteractionMetadata { + /** Id of the interaction */ + id: string + /** The type of interaction */ + type: InteractionTypes + /** ID of the user who triggered the interaction */ + user_id: string + /** IDs for installation context(s) related to an interaction */ + authorizing_integration_owners: Partial> + /** ID of the original response message, present only on follow-up messages */ + original_response_message_id?: string + /** ID of the message that contained interactive component, present only on messages created from component interactions */ + interacted_message_id?: string + /** Metadata for the interaction that was used to open the modal, present only on modal submit interactions */ + triggering_interaction_metadata?: DiscordMessageInteractionMetadata +} + export type DiscordMessageComponents = DiscordActionRow[] /** https://discord.com/developers/docs/interactions/message-components#actionrow */ @@ -1718,6 +1771,10 @@ export interface DiscordInteraction { app_permissions: string /** For monetized apps, any entitlements for the invoking user, representing access to premium SKUs */ entitlements: DiscordEntitlement[] + /** Mapping of installation contexts that the interaction was authorized for to related user or guild IDs. */ + authorizing_integration_owners: Partial> + /** Context where the interaction was triggered from */ + context?: DiscordInteractionContextType } /** https://discord.com/developers/docs/resources/guild#guild-member-object */ @@ -2272,7 +2329,25 @@ export interface DiscordCreateApplicationCommand { options?: DiscordApplicationCommandOption[] /** Set of permissions represented as a bit set */ default_member_permissions?: string | null - /** Indicates whether the command is available in DMs with the app, only for globally-scoped commands. By default, commands are visible. */ + /** + * Installation context(s) where the command is available + * + * @remarks + * This is currently in preview. + */ + integration_types?: DiscordApplicationIntegrationType[] + /** + * Interaction context(s) where the command can be used, only for globally-scoped commands. By default, all interaction context types included. + * + * @remarks + * This is currently in preview. + */ + contexts?: DiscordInteractionContextType[] | null + /** + * Indicates whether the command is available in DMs with the app, only for globally-scoped commands. By default, commands are visible. + * + * @deprecated use {@link contexts} instead + */ dm_permission?: boolean /** Indicates whether the command is age-restricted, defaults to false */ nsfw?: boolean @@ -3248,6 +3323,16 @@ export enum DiscordMessageFlag { IsVoiceMessage = 1 << 13, } +/** https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-context-types */ +export enum DiscordInteractionContextType { + /** Interaction can be used within servers */ + Guild = 0, + /** Interaction can be used within DMs with the app's bot user */ + BotDm = 1, + /** Interaction can be used within Group DMs and DMs other than the app's bot user */ + PrivateChannel = 2, +} + /** https://discord.com/developers/docs/resources/guild#bulk-guild-ban */ export interface DiscordBulkBan { /** list of user ids, that were successfully banned */ diff --git a/packages/types/src/discordeno.ts b/packages/types/src/discordeno.ts index 1e8c9bc14..ed5d65ab9 100644 --- a/packages/types/src/discordeno.ts +++ b/packages/types/src/discordeno.ts @@ -4,6 +4,7 @@ import type { AutoModerationTriggerTypes, DiscordApplicationCommandOption, DiscordApplicationCommandOptionChoice, + DiscordApplicationIntegrationType, DiscordAttachment, DiscordAutoModerationRuleTriggerMetadataPresets, DiscordChannel, @@ -11,6 +12,7 @@ import type { DiscordGuildOnboardingMode, DiscordGuildOnboardingPrompt, DiscordInstallParams, + DiscordInteractionContextType, DiscordMessageFlag, DiscordPollAnswer, DiscordPollLayoutType, @@ -459,7 +461,15 @@ export interface CreateSlashApplicationCommand { options?: Camelize /** Set of permissions represented as a bit set */ defaultMemberPermissions?: PermissionStrings[] - /** Indicates whether the command is available in DMs with the app, only for globally-scoped commands. By default, commands are visible. */ + /** Installation context(s) where the command is available */ + integrationTypes?: DiscordApplicationIntegrationType[] + /** Interaction context(s) where the command can be used, only for globally-scoped commands. By default, all interaction context types included for new commands. */ + contexts?: DiscordInteractionContextType[] + /** + * Indicates whether the command is available in DMs with the app, only for globally-scoped commands. By default, commands are visible. + * + * @deprecated use {@link contexts} instead + */ dmPermission?: boolean /** Indicates whether the command is age-restricted, defaults to `false` */ nsfw?: boolean @@ -1294,6 +1304,13 @@ export interface EditApplication { roleConnectionsVerificationUrl?: string /** Settings for the app's default in-app authorization link, if enabled */ installParams?: DiscordInstallParams + /** + * Default scopes and permissions for each supported installation context. + * + * @remarks + * This is currently in preview. + */ + integrationTypesConfig?: DiscordApplicationIntegrationType /** * App's public flags * diff --git a/packages/types/src/shared.ts b/packages/types/src/shared.ts index 9ecf1f73a..69bd94044 100644 --- a/packages/types/src/shared.ts +++ b/packages/types/src/shared.ts @@ -1038,7 +1038,7 @@ export type Localization = Partial> export type AtLeastOne }> = Partial & U[keyof U] export type CamelCase = S extends `${infer T}_${infer U}` ? `${T}${Capitalize>}` : S -export type SnakeCase = S extends `${infer T}${infer U}` ? `${T extends Capitalize ? '_' : ''}${Lowercase}${SnakeCase}` : S +export type SnakeCase = S extends `${infer T}${infer U}` ? `${T extends Lowercase ? '' : '_'}${Lowercase}${SnakeCase}` : S export type Camelize = T extends any[] ? T extends Array> diff --git a/packages/utils/src/oauth2.ts b/packages/utils/src/oauth2.ts index e8bad1445..cdcdaa7eb 100644 --- a/packages/utils/src/oauth2.ts +++ b/packages/utils/src/oauth2.ts @@ -1,4 +1,4 @@ -import type { BigString, OAuth2Scope, PermissionStrings } from '@discordeno/types' +import type { BigString, DiscordApplicationIntegrationType, OAuth2Scope, PermissionStrings } from '@discordeno/types' import { calculateBits } from './permissions.js' export function createOAuth2Link(options: CreateOAuth2LinkOptions): string { @@ -12,8 +12,8 @@ export function createOAuth2Link(options: CreateOAuth2LinkOptions): string { if (options.prompt) url += `&prompt=${options.prompt}` if (options.permissions) url += `&permissions=${Array.isArray(options.permissions) ? calculateBits(options.permissions) : options.permissions}` if (options.guildId) url += `&guild_id=${options.guildId}` - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions if (options.disableGuildSelect !== undefined) url += `&disable_guild_select=${options.disableGuildSelect}` + if (options.integrationType) url += `&integration_type=${options.integrationType}` return url } @@ -72,4 +72,15 @@ export interface CreateOAuth2LinkOptions { * Should be defined only in a [bot authorization flow](https://discord.com/developers/docs/topics/oauth2#bot-authorization-flow), with [advanced bot authorization](https://discord.com/developers/docs/topics/oauth2#advanced-bot-authorization) or with the `webhook.incoming` scope */ disableGuildSelect?: boolean + /** + * Specifies the installation context for the authorization + * + * @remarks + * Should be defined only when {@link scopes} includes {@link OAuth2Scope.ApplicationsCommands}. + * + * When set to GuildInstall (0) the application will be authorized for installation to a server, and when set to UserInstall (1) the application will be authorized for installation to a user. + * + * The application must be configured in the Developer Portal to support the provided `integrationType`. + */ + integrationType?: DiscordApplicationIntegrationType }