feat(bot,rest,types,utils): Support User-Installed apps, Interaction contexts and install types (#3502)

* Support integrationTypesConfig in Application

* Add support for commands with integrationTypes and contexts

And deprecate the dmPermission boolean, it is being replaced by contexts

* Update contexts comment to make it more clear

* User-Installed apps

* Add integrationType oauth parameter

fixes #3517

* Add null to contexts

closes #3523

* Mark oauth2 install params as nullable

closes #3525

* Fix typescript errors

* Add preview notices

---------

Co-authored-by: Matt Hatcher <3768988+MatthewSH@users.noreply.github.com>
This commit is contained in:
Fleny
2024-04-29 15:36:29 +00:00
committed by GitHub
co-authored by Matt Hatcher
parent c411076261
commit 687c29dd7a
12 changed files with 279 additions and 12 deletions
+30 -1
View File
@@ -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<Transformers>, 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<Transformers>, 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<Transformers>, 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<Transformers>, 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,
@@ -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<Record<DiscordApplicationIntegrationType, ApplicationIntegrationTypeConfiguration>>
}
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
}
}
+25 -1
View File
@@ -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<Record<DiscordApplicationIntegrationType, bigint>>
/** 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,
+62 -2
View File
@@ -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<Message['interaction']>
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<Record<DiscordApplicationIntegrationType, bigint>>
/** 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
}
+1 -1
View File
@@ -974,7 +974,7 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
},
async editApplicationInfo(body) {
return await rest.patch<DiscordApplication>(rest.routes.oauth2.application(), {
return await rest.patch<DiscordApplication>(rest.routes.application(), {
body,
})
},
+4
View File
@@ -628,6 +628,10 @@ export function createRoutes(): RestRoutes {
return `/users/${userId}`
},
application() {
return '/applications/@me'
},
currentUser() {
return '/users/@me'
},
+3 -1
View File
@@ -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. */
+2
View File
@@ -15,6 +15,7 @@ import type {
DiscordApplicationCommandOption,
DiscordApplicationCommandOptionChoice,
DiscordApplicationCommandPermissions,
DiscordApplicationIntegrationTypeConfiguration,
DiscordApplicationRoleConnection,
DiscordApplicationWebhook,
DiscordArchivedThreads,
@@ -180,6 +181,7 @@ export interface CamelizedDiscordGuildIntegrationsUpdate extends Camelize<Discor
export interface CamelizedDiscordTypingStart extends Camelize<DiscordTypingStart> {}
export interface CamelizedDiscordMember extends Camelize<DiscordMember> {}
export interface CamelizedDiscordApplication extends Camelize<DiscordApplication> {}
export interface CamelizedDiscordApplicationIntegrationTypeConfiguration extends Camelize<DiscordApplicationIntegrationTypeConfiguration> {}
export interface CamelizedDiscordApplicationRoleConnection extends Camelize<DiscordApplicationRoleConnection> {}
export type CamelizedDiscordTokenExchange = Camelize<DiscordTokenExchange>
export interface CamelizedDiscordTokenExchangeAuthorizationCode extends Camelize<DiscordTokenExchangeAuthorizationCode> {}
+87 -2
View File
@@ -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<Record<`${DiscordApplicationIntegrationType}`, DiscordApplicationIntegrationTypeConfiguration>>
/** 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<DiscordChannel, 'member'> & { member: DiscordThreadMember }
@@ -1550,6 +1585,24 @@ export interface DiscordMessageInteraction {
member?: Partial<DiscordMember>
}
/** 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<Record<DiscordApplicationIntegrationType, string>>
/** 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<Record<DiscordApplicationIntegrationType, string>>
/** 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 */
+18 -1
View File
@@ -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<DiscordApplicationCommandOption[]>
/** 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
*
+1 -1
View File
@@ -1038,7 +1038,7 @@ export type Localization = Partial<Record<Locales, string>>
export type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U]
export type CamelCase<S extends string> = S extends `${infer T}_${infer U}` ? `${T}${Capitalize<CamelCase<U>>}` : S
export type SnakeCase<S extends string> = S extends `${infer T}${infer U}` ? `${T extends Capitalize<T> ? '_' : ''}${Lowercase<T>}${SnakeCase<U>}` : S
export type SnakeCase<S extends string> = S extends `${infer T}${infer U}` ? `${T extends Lowercase<T> ? '' : '_'}${Lowercase<T>}${SnakeCase<U>}` : S
export type Camelize<T> = T extends any[]
? T extends Array<Record<any, any>>
+13 -2
View File
@@ -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
}