Files
discordeno/packages/bot/src/transformers/interaction.ts
T
FlenyandLink 088de8c417 fix(types)!: Sort & fix interactions.ts types (#4290)
* Sort & fix interactions.ts types

* Fix type errors

* Apply suggestions from code review

Co-authored-by: Link <lts20050703@gmail.com>

---------

Co-authored-by: Link <lts20050703@gmail.com>
2025-08-11 18:33:47 +02:00

329 lines
15 KiB
TypeScript

import {
DiscordApplicationIntegrationType,
type DiscordInteraction,
type DiscordInteractionCallback,
type DiscordInteractionCallbackResponse,
type DiscordInteractionDataOption,
type DiscordInteractionDataResolved,
type DiscordInteractionResource,
InteractionResponseTypes,
InteractionTypes,
MessageFlags,
} from '@discordeno/types'
import { Collection } from '@discordeno/utils'
import type {
Bot,
DiscordChannel,
Interaction,
InteractionCallback,
InteractionCallbackResponse,
InteractionDataOption,
InteractionDataResolved,
InteractionResolvedChannel,
InteractionResource,
InternalBot,
Member,
Message,
} from '../index.js'
export const baseInteraction: InternalBot['transformers']['$inferredTypes']['interaction'] = {
// This allows typescript to still check for type errors on functions below
...(undefined as unknown as InternalBot['transformers']['$inferredTypes']['interaction']),
async respond(response, options) {
let type = InteractionResponseTypes.ChannelMessageWithSource
// If user provides a string, change it to a response object
if (typeof response === 'string') response = { content: response }
// If user provides an object, determine if it should be an autocomplete or a modal response
if (response.title) type = InteractionResponseTypes.Modal
if (this.type === InteractionTypes.ApplicationCommandAutocomplete) type = InteractionResponseTypes.ApplicationCommandAutocompleteResult
if (type === InteractionResponseTypes.ChannelMessageWithSource && options?.isPrivate) {
response.flags ??= 0
response.flags |= MessageFlags.Ephemeral
}
// Since this has already been given a response, any further responses must be followups.
if (this.acknowledged) return await this.bot.helpers.sendFollowupMessage(this.token, response)
// Modals cannot be chained
if (this.type === InteractionTypes.ModalSubmit && type === InteractionResponseTypes.Modal)
throw new Error('Cannot respond to a modal interaction with another modal.')
const result = await this.bot.helpers.sendInteractionResponse(
this.id,
this.token,
{ type, data: response },
{ withResponse: options?.withResponse },
)
this.acknowledged = true
return result
},
async edit(response, messageId, options) {
if (this.type === InteractionTypes.ApplicationCommandAutocomplete) throw new Error('Cannot edit an autocomplete interaction.')
// If user provides a string, change it to a response object
if (typeof response === 'string') response = { content: response }
if (messageId) {
return await this.bot?.helpers.editFollowupMessage(this.token, messageId, response)
}
if (!this.acknowledged) {
if (this.type !== InteractionTypes.MessageComponent && this.type !== InteractionTypes.ModalSubmit)
throw new Error("This interaction has not been responded to yet and this isn't a MessageComponent or ModalSubmit interaction.")
const result = await this.bot.helpers.sendInteractionResponse(
this.id,
this.token,
{ type: InteractionResponseTypes.UpdateMessage, data: response },
options,
)
this.acknowledged = true
return result
}
return await this.bot.helpers.editOriginalInteractionResponse(this.token, response)
},
async deferEdit(options) {
if (this.type === InteractionTypes.ApplicationCommandAutocomplete) throw new Error('Cannot edit an autocomplete interaction.')
if (this.acknowledged) throw new Error('Cannot defer an already responded interaction.')
if (this.type !== InteractionTypes.MessageComponent && this.type !== InteractionTypes.ModalSubmit)
throw new Error("Cannot defer to then edit an interaction that isn't a MessageComponent or ModalSubmit interaction.")
const result = await this.bot.helpers.sendInteractionResponse(
this.id,
this.token,
{ type: InteractionResponseTypes.DeferredUpdateMessage },
options,
)
this.acknowledged = true
return result
},
async defer(isPrivate, options) {
if (this.acknowledged) throw new Error('Cannot defer an already responded interaction.')
const result = await this.bot.helpers.sendInteractionResponse(
this.id,
this.token,
{
type: InteractionResponseTypes.DeferredChannelMessageWithSource,
data: {
flags: isPrivate ? MessageFlags.Ephemeral : undefined,
},
},
options,
)
this.acknowledged = true
return result
},
async delete(messageId) {
if (this.type === InteractionTypes.ApplicationCommandAutocomplete) throw new Error('Cannot delete an autocomplete interaction')
if (messageId) return await this.bot?.helpers.deleteFollowupMessage(this.token, messageId)
else return await this.bot?.helpers.deleteOriginalInteractionResponse(this.token)
},
}
export function transformInteraction(
bot: InternalBot,
payload: { interaction: DiscordInteraction; shardId: number },
): typeof bot.transformers.$inferredTypes.interaction {
const guildId = payload.interaction.guild_id ? bot.transformers.snowflake(payload.interaction.guild_id) : undefined
const user = bot.transformers.user(bot, payload.interaction.member?.user ?? payload.interaction.user!)
const interaction: Interaction = Object.create(baseInteraction)
const props = bot.transformers.desiredProperties.interaction
// Typescript has an hard time with this, so we need to tell him for sure this is a Bot
interaction.bot = bot as unknown as Bot
interaction.acknowledged = false
if (props.id && payload.interaction.id) interaction.id = bot.transformers.snowflake(payload.interaction.id)
if (props.applicationId && payload.interaction.application_id)
interaction.applicationId = bot.transformers.snowflake(payload.interaction.application_id)
if (props.type && payload.interaction.type) interaction.type = payload.interaction.type
if (props.token && payload.interaction.token) interaction.token = payload.interaction.token
if (props.version && payload.interaction.version) interaction.version = payload.interaction.version
if (props.locale && payload.interaction.locale) interaction.locale = payload.interaction.locale
if (props.guildLocale && payload.interaction.guild_locale) interaction.guildLocale = payload.interaction.guild_locale
if (props.guild && payload.interaction.guild)
// @ts-expect-error payload.interaction.guild is a Partial<DiscordGuild>
interaction.guild = bot.transformers.guild(bot, { guild: payload.interaction.guild, shardId: payload.shardId })
if (props.guildId && guildId) interaction.guildId = guildId
if (props.user && user) interaction.user = user
if (props.appPermissions && payload.interaction.app_permissions)
interaction.appPermissions = bot.transformers.snowflake(payload.interaction.app_permissions)
if (props.message && payload.interaction.message)
interaction.message = bot.transformers.message(bot, { message: payload.interaction.message, shardId: 0 })
if (props.channel && payload.interaction.channel)
interaction.channel = bot.transformers.channel(bot, { channel: payload.interaction.channel as DiscordChannel, guildId })
if (props.channelId && payload.interaction.channel_id) interaction.channelId = bot.transformers.snowflake(payload.interaction.channel_id)
if (props.member && guildId && payload.interaction.member)
interaction.member = bot.transformers.member(bot, payload.interaction.member, guildId, user.id)
if (props.entitlements && payload.interaction.entitlements)
interaction.entitlements = payload.interaction.entitlements.map((e) => bot.transformers.entitlement(bot, e))
if (props.authorizingIntegrationOwners && payload.interaction.authorizing_integration_owners) {
interaction.authorizingIntegrationOwners = {}
if (payload.interaction.authorizing_integration_owners['0'])
interaction.authorizingIntegrationOwners[DiscordApplicationIntegrationType.GuildInstall] = bot.transformers.snowflake(
payload.interaction.authorizing_integration_owners['0'],
)
if (payload.interaction.authorizing_integration_owners['1'])
interaction.authorizingIntegrationOwners[DiscordApplicationIntegrationType.UserInstall] = bot.transformers.snowflake(
payload.interaction.authorizing_integration_owners['1'],
)
}
if (props.context && payload.interaction.context) interaction.context = payload.interaction.context
if (props.attachmentSizeLimit && payload.interaction.attachment_size_limit)
interaction.attachmentSizeLimit = payload.interaction.attachment_size_limit
if (props.data && payload.interaction.data) {
interaction.data = {
type: payload.interaction.data.type,
componentType: payload.interaction.data.component_type,
customId: payload.interaction.data.custom_id,
components: payload.interaction.data.components?.map((component) => bot.transformers.component(bot, component)),
values: payload.interaction.data.values,
id: payload.interaction.data.id ? bot.transformers.snowflake(payload.interaction.data.id) : undefined,
name: payload.interaction.data.name,
resolved: payload.interaction.data.resolved
? bot.transformers.interactionDataResolved(bot, { resolved: payload.interaction.data.resolved, shardId: payload.shardId, guildId })
: undefined,
options: payload.interaction.data.options?.map((opt) => bot.transformers.interactionDataOptions(bot, opt)),
targetId: payload.interaction.data.target_id ? bot.transformers.snowflake(payload.interaction.data.target_id) : undefined,
}
}
// Typescript has an hard time with interaction.bot, so we need to tell him for sure this interaction is the of the correct type
return bot.transformers.customizers.interaction(bot, payload, interaction as unknown as typeof bot.transformers.$inferredTypes.interaction)
}
export function transformInteractionDataOption(bot: Bot, option: DiscordInteractionDataOption): InteractionDataOption {
const opt = {
name: option.name,
type: option.type,
value: option.value,
options: option.options,
focused: option.focused,
} as InteractionDataOption
return bot.transformers.customizers.interactionDataOptions(bot, option, opt)
}
export function transformInteractionDataResolved(
bot: InternalBot,
payload: { resolved: DiscordInteractionDataResolved; shardId: number; guildId?: bigint },
): InteractionDataResolved {
const transformed: InteractionDataResolved = {}
if (payload.resolved.messages) {
transformed.messages = new Collection(
Object.entries(payload.resolved.messages).map(([_id, value]) => {
// @ts-expect-error TODO: Deal with partials
const message: Message = bot.transformers.message(bot, { message: value, shardId: payload.shardId })
return [message.id, message]
}),
)
}
if (payload.resolved.users) {
transformed.users = new Collection(
Object.entries(payload.resolved.users).map(([_id, value]) => {
const user = bot.transformers.user(bot, value)
return [user.id, user]
}),
)
}
if (payload.guildId && payload.resolved.members) {
transformed.members = new Collection(
Object.entries(payload.resolved.members).map(([id, value]) => {
const member: Member = bot.transformers.member(bot, value, payload.guildId!, bot.transformers.snowflake(id))
return [member.id, member]
}),
)
}
if (payload.guildId && payload.resolved.roles) {
transformed.roles = new Collection(
Object.entries(payload.resolved.roles).map(([_id, value]) => {
const role = bot.transformers.role(bot, { role: value, guildId: payload.guildId! })
return [role.id, role]
}),
)
}
if (payload.resolved.channels) {
transformed.channels = new Collection(
Object.entries(payload.resolved.channels).map(([_id, value]) => {
const channel = bot.transformers.channel(bot, { channel: value }) as InteractionResolvedChannel
return [channel.id, channel]
}),
)
}
if (payload.resolved.attachments) {
transformed.attachments = new Collection(
Object.entries(payload.resolved.attachments).map(([key, value]) => {
const id = bot.transformers.snowflake(key)
return [id, bot.transformers.attachment(bot, value)]
}),
)
}
return bot.transformers.customizers.interactionDataResolved(bot, payload, transformed)
}
export function transformInteractionCallbackResponse(
bot: InternalBot,
payload: { interactionCallbackResponse: DiscordInteractionCallbackResponse; shardId: number },
): typeof bot.transformers.$inferredTypes.interactionCallbackResponse {
const props = bot.transformers.desiredProperties.interactionCallbackResponse
const response = {} as InteractionCallbackResponse
if (props.interaction && payload.interactionCallbackResponse.interaction)
response.interaction = bot.transformers.interactionCallback(bot, payload.interactionCallbackResponse.interaction)
if (props.resource && payload.interactionCallbackResponse.resource)
response.resource = bot.transformers.interactionResource(bot, {
interactionResource: payload.interactionCallbackResponse.resource,
shardId: payload.shardId,
})
return bot.transformers.customizers.interactionCallbackResponse(bot, payload.interactionCallbackResponse, response)
}
export function transformInteractionCallback(
bot: InternalBot,
payload: DiscordInteractionCallback,
): typeof bot.transformers.$inferredTypes.interactionCallback {
const props = bot.transformers.desiredProperties.interactionCallback
const callback = {} as InteractionCallback
if (props.id && payload.id) callback.id = bot.transformers.snowflake(payload.id)
if (props.type && payload.type) callback.type = payload.type
if (props.activityInstanceId && payload.activity_instance_id) callback.activityInstanceId = payload.activity_instance_id
if (props.responseMessageId && payload.response_message_id) callback.responseMessageId = bot.transformers.snowflake(payload.response_message_id)
if (props.responseMessageEphemeral && payload.response_message_ephemeral) callback.responseMessageEphemeral = payload.response_message_ephemeral
if (props.responseMessageLoading && payload.response_message_loading) callback.responseMessageLoading = payload.response_message_loading
return bot.transformers.customizers.interactionCallback(bot, payload, callback)
}
export function transformInteractionResource(
bot: InternalBot,
payload: { interactionResource: DiscordInteractionResource; shardId: number },
): typeof bot.transformers.$inferredTypes.interactionResource {
const props = bot.transformers.desiredProperties.interactionResource
const resource = {} as InteractionResource
if (props.type && payload.interactionResource.type) resource.type = payload.interactionResource.type
if (props.activityInstance && payload.interactionResource.activity_instance)
resource.activityInstance = payload.interactionResource.activity_instance
if (props.message && payload.interactionResource.message)
resource.message = bot.transformers.message(bot, { message: payload.interactionResource.message, shardId: payload.shardId })
return bot.transformers.customizers.interactionResource(bot, payload.interactionResource, resource)
}