import type { Message , MessageComponentInteraction } from "discord.js" ; import type { DiscordInteraction } from "../internal/types" ; import type { CommandInstance } from "./command/command" ; import type { ButtonComponent } from "./component/button-component" ; import type { RenderReplyFn , RenderResult , TopLevelComponent , } from "./component/reply-component" ; import { createInteractionReplyOptions , flattenRenderResult , } from "./component/reply-component" ; import type { SelectMenuComponent } from "./component/select-menu-component" ; import { createInteractionContext } from "./interaction-context" ; type ReplyInstanceEvents = { onDelete : ( instance : ReplyInstance ) => void ; } ; type InteractionSubject = ButtonComponent | SelectMenuComponent ; export type ReplyInstance = { createMessage ( interaction ) : Promise < void > ; deleteMessage ( ) : Promise < void > ; refreshMessage ( ) : Promise < void > ; getMessage ( ) : Message | undefined ; findInteractionSubject ( interaction ) : InteractionSubject | undefined ; updateMessageFromComponentInteraction ( interaction , subject , commandInstance ) : Promise < void > ; } ; export class PublicReplyInstance implements ReplyInstance { private readonly render : RenderReplyFn ; private readonly events : ReplyInstanceEvents ; private renderResult : TopLevelComponent [ ] = [ ] ; private message ? : Message ; private isDeleted = false ; constructor ( render , events ) { this . render = render ; this . events = events ; } getMessage ( ) { return this . message ; } async createMessage ( interaction ) { this . renderResult = flattenRenderResult ( this . render ( ) ) ; if ( this . renderResult . length === 0 ) { await this . deleteMessage ( ) ; return ; } const options = createInteractionReplyOptions ( this . renderResult ) ; if ( interaction . deferred && interaction . ephemeral ) { this . message = await interaction . channel ?. send ( options ) ; return ; } if ( interaction . deferred ) { this . message = ( await interaction . editReply ( options ) ) as Message ; return ; } if ( interaction . replied ) { this . message = ( await interaction . followUp ( options ) ) as Message ; return ; } this . message = ( await interaction . reply ( { ... options , fetchReply : true , } ) ) as Message ; } async deleteMessage ( ) { const message = this . message ; this . message = undefined ; this . isDeleted = true ; this . events . onDelete ( this ) ; await message ?. delete ( ) ; } async refreshMessage ( ) { if ( this . isDeleted ) return ; this . renderResult = flattenRenderResult ( this . render ( ) ) ; if ( this . renderResult . length === 0 ) { await this . deleteMessage ( ) ; return ; } await this . message ?. edit ( createInteractionReplyOptions ( this . renderResult ) ) ; } findInteractionSubject ( interaction ) { return getInteractiveComponents ( this . renderResult ) . find ( ( it ) => it . customId === interaction . customId ) ; } async updateMessageFromComponentInteraction ( interaction ) { if ( this . isDeleted ) return ; this . renderResult = flattenRenderResult ( this . render ( ) ) ; if ( this . renderResult . length === 0 ) { await this . deleteMessage ( ) ; return ; } if ( interaction . deferred || interaction . replied ) { await this . refreshMessage ( ) ; return ; } await interaction . update ( createInteractionReplyOptions ( this . renderResult ) ) ; } } export class EphemeralReplyInstance implements ReplyInstance { private render : RenderReplyFn ; private renderResult : TopLevelComponent [ ] = [ ] ; constructor ( render ) { this . render = render ; } getMessage ( ) { return undefined ; } async createMessage ( interaction ) { this . renderResult = flattenRenderResult ( this . render ( ) ) ; if ( this . renderResult . length === 0 ) return ; const options = createInteractionReplyOptions ( this . renderResult ) ; if ( interaction . replied || interaction . deferred ) { await interaction . followUp ( { ... options , ephemeral : true } ) ; return ; } await interaction . reply ( { ... options , ephemeral : true } ) ; } async deleteMessage ( ) { } async refreshMessage ( ) { } findInteractionSubject ( interaction ) { return getInteractiveComponents ( this . renderResult ) . find ( ( it ) => it . customId === interaction . customId ) ; } async updateMessageFromComponentInteraction ( interaction ) { this . renderResult = flattenRenderResult ( this . render ( ) ) ; if ( this . renderResult . length === 0 ) return ; const options = createInteractionReplyOptions ( this . renderResult ) ; if ( interaction . deferred ) { await interaction . followUp ( options ) ; return ; } await interaction . update ( options ) ; } } export async function callInteractionSubject ( interaction , subject , command ) { const message = interaction . message as Message ; if ( interaction . isButton ( ) && subject . type === "button" ) { await subject . onClick ( { ... createInteractionContext ( { interaction , command } ) , message , } ) ; } if ( interaction . isSelectMenu ( ) && subject . type === "selectMenu" ) { await subject . onSelect ( { ... createInteractionContext ( { interaction , command } ) , message , values : interaction . values , } ) ; } } function getInteractiveComponents ( result ) { return flattenRenderResult ( result ) . flatMap ( ( component ) => { return component . type === "actionRow" ? component . children : [ ] ; } ) . filter ( ( component ) : component is ButtonComponent | SelectMenuComponent => { return component . type === "button" || component . type === "selectMenu" ; } ) ; }