11import { proto , type AnyMessageContent } from 'baileys'
22import { ZaileysBuilderError } from '../errors.js'
3- import type { ButtonDef , InteractiveButton , MediaSource } from '../types.js'
3+ import type { BottomSheetOptions , ButtonDef , InteractiveButton , LimitedTimeOfferOptions , MediaSource } from '../types.js'
44
55const MAX_BUTTONS = 10
66
@@ -21,26 +21,37 @@ export type HeaderMedia = { kind: 'image' | 'video'; src: MediaSource }
2121/** Content shape carrying a pre-built proto message (+ optional header media) for the relay send path. */
2222export type RelayContent = { [ RELAY_CONTENT_KEY ] : proto . IMessage ; [ RELAY_MEDIA_KEY ] ?: HeaderMedia }
2323
24- /** Optional decoration for {@link buildButtonsContent}: body text, footer, and a text/media header. */
24+ /** Optional decoration for {@link buildButtonsContent}: body text, footer, a text/media header, and nativeFlow params . */
2525export type ButtonsContentOptions = {
2626 text ?: string
2727 footer ?: string
2828 title ?: string
2929 subtitle ?: string
3030 image ?: MediaSource
3131 video ?: MediaSource
32+ bottomSheet ?: BottomSheetOptions
33+ limitedTimeOffer ?: LimitedTimeOfferOptions
3234}
3335
3436type NativeButton = { name : string ; buttonParamsJson : string }
3537
3638const nonEmpty = ( value : unknown ) : value is string => typeof value === 'string' && value . length > 0
3739
38- const toNativeButton = ( button : ButtonDef | InteractiveButton , seen : Set < string > ) : NativeButton => {
40+ const requireText = ( button : ButtonDef | InteractiveButton ) : string => {
3941 const text = ( button as { text ?: unknown } ) . text
4042 if ( ! nonEmpty ( text ) || text . trim ( ) . length === 0 ) {
4143 throw new ZaileysBuilderError ( 'INVALID_OPTIONS' , 'button text must be a non-empty string' )
4244 }
45+ return text
46+ }
47+
48+ const toNativeButton = ( button : ButtonDef | InteractiveButton , seen : Set < string > ) : NativeButton => {
4349 const type = ( button as InteractiveButton ) . type ?? 'reply'
50+ if ( type === 'location' ) {
51+ const text = ( button as { text ?: unknown } ) . text
52+ return { name : 'send_location' , buttonParamsJson : JSON . stringify ( nonEmpty ( text ) ? { display_text : text } : { } ) }
53+ }
54+ const text = requireText ( button )
4455 if ( type === 'reply' ) {
4556 const id = ( button as ButtonDef ) . id
4657 if ( ! nonEmpty ( id ) ) {
@@ -77,9 +88,42 @@ const toNativeButton = (button: ButtonDef | InteractiveButton, seen: Set<string>
7788 }
7889 return { name : 'cta_call' , buttonParamsJson : JSON . stringify ( { display_text : text , id : phone , phone_number : phone } ) }
7990 }
91+ if ( type === 'reminder' || type === 'cancel-reminder' ) {
92+ const id = ( button as { id ?: unknown } ) . id
93+ const name = type === 'reminder' ? 'cta_reminder' : 'cta_cancel_reminder'
94+ return { name, buttonParamsJson : JSON . stringify ( { display_text : text , id : nonEmpty ( id ) ? id : text } ) }
95+ }
96+ if ( type === 'address' ) {
97+ const id = ( button as { id ?: unknown } ) . id
98+ return { name : 'address_message' , buttonParamsJson : JSON . stringify ( { display_text : text , id : nonEmpty ( id ) ? id : text } ) }
99+ }
80100 throw new ZaileysBuilderError ( 'INVALID_OPTIONS' , `unknown button type: ${ String ( type ) } ` )
81101}
82102
103+ /** Serialize {@link BottomSheetOptions}/{@link LimitedTimeOfferOptions } into the nativeFlow `messageParamsJson` string. */
104+ export const buildMessageParamsJson = ( opts ?: ButtonsContentOptions ) : string => {
105+ const params : Record < string , unknown > = { }
106+ if ( opts ?. bottomSheet ) {
107+ const b = opts . bottomSheet
108+ params [ 'bottom_sheet' ] = {
109+ ...( b . buttonsLimit !== undefined ? { in_thread_buttons_limit : b . buttonsLimit } : { } ) ,
110+ ...( b . dividers !== undefined ? { divider_indices : b . dividers } : { } ) ,
111+ ...( b . listTitle !== undefined ? { list_title : b . listTitle } : { } ) ,
112+ ...( b . buttonTitle !== undefined ? { button_title : b . buttonTitle } : { } ) ,
113+ }
114+ }
115+ if ( opts ?. limitedTimeOffer ) {
116+ const o = opts . limitedTimeOffer
117+ params [ 'limited_time_offer' ] = {
118+ ...( o . text !== undefined ? { text : o . text } : { } ) ,
119+ ...( o . url !== undefined ? { url : o . url } : { } ) ,
120+ ...( o . copyCode !== undefined ? { copy_code : o . copyCode } : { } ) ,
121+ ...( o . expiresAt !== undefined ? { expiration_time : o . expiresAt } : { } ) ,
122+ }
123+ }
124+ return Object . keys ( params ) . length > 0 ? JSON . stringify ( params ) : ''
125+ }
126+
83127/**
84128 * Map declarative buttons to nativeFlow buttons, validating each. Reusable by
85129 * {@link buildButtonsContent} and the carousel card builder.
@@ -120,7 +164,7 @@ export const buildButtonsContent = (
120164
121165 const interactiveMessage : proto . Message . IInteractiveMessage = {
122166 body : { text : opts ?. text && opts . text . length > 0 ? opts . text : ' ' } ,
123- nativeFlowMessage : { buttons : nativeButtons , messageParamsJson : '' } ,
167+ nativeFlowMessage : { buttons : nativeButtons , messageParamsJson : buildMessageParamsJson ( opts ) } ,
124168 }
125169 if ( opts ?. footer && opts . footer . length > 0 ) {
126170 interactiveMessage . footer = { text : opts . footer }
0 commit comments