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
}