import { InlineKeyboardButton as TelegramInlineKeyboardButton } from '@grammyjs/types' ; import { ConstOrContextPathFunc , ContextPathFunc , filterNonNullable } from './generic-types' ; import { combinePath } from './path' ; export interface CallbackButtonTemplate { readonly text : string ; readonly relativePath : string ; } export type InlineKeyboardButton = Readonly < TelegramInlineKeyboardButton > ; export type InlineKeyboard = ReadonlyArray < readonly InlineKeyboardButton [ ] > ; export type ButtonTemplate = CallbackButtonTemplate | InlineKeyboardButton ; export type ButtonTemplateRow = readonly ButtonTemplate [ ] ; type UncreatedTemplate < Context > = ConstOrContextPathFunc < Context , ButtonTemplate | undefined > ; type RowOfUncreatedTemplates < Context > = Array < UncreatedTemplate < Context > > ; type ButtonTemplateRowGenerator < Context > = ContextPathFunc < Context , ButtonTemplateRow [ ] > ; type KeyboardTemplateEntry < Context > = RowOfUncreatedTemplates < Context > | ButtonTemplateRowGenerator < Context > ; function isRow < Context > ( entry ) { return Array . isArray ( entry ) ; } function isCallbackButtonTemplate ( kindOfButton ) { return 'text' in kindOfButton && 'relativePath' in kindOfButton ; } export class Keyboard < Context > { private readonly _entries : Array < KeyboardTemplateEntry < Context > > = [ ] ; addCreator ( creator ) { this . _entries . push ( creator ) ; } add ( joinLastRow , ... buttons ) { const lastEntry = this . _entries . slice ( - 1 ) [ 0 ] ; if ( joinLastRow && isRow ( lastEntry ) ) { lastEntry . push ( ... buttons ) ; return ; } this . _entries . push ( [ ... buttons ] ) ; } async render ( context , path ) { const arrayOfRowArrays = await Promise . all ( this . _entries . map ( async ( o ) => entryToRows ( o , context , path ) ) ) ; const rows = arrayOfRowArrays . flat ( 1 ) . map ( row => renderRow ( row , path ) ) . filter ( o => o . length > 0 ) ; return rows ; } } async function entryToRows < Context > ( entry , context , path ) { if ( isRow ( entry ) ) { const buttonsInRow = await Promise . all ( entry . map ( async ( button ) => typeof button === 'function' ? button ( context , path ) : button ) ) ; const filtered = buttonsInRow . filter ( filterNonNullable ( ) ) ; return [ filtered ] ; } return entry ( context , path ) ; } function renderRow ( templates , path ) { return templates . map ( template => isCallbackButtonTemplate ( template ) ? renderCallbackButtonTemplate ( template , path ) : template ) ; } function renderCallbackButtonTemplate ( template , path ) { const absolutePath = combinePath ( path , template . relativePath ) ; if ( absolutePath . length > 64 ) { throw new Error ( `callback_data only supports 1-64 bytes. With this button (${ template . relativePath }) it would get too long (${ absolutePath . length }). Full path: ${ absolutePath }` ) ; } return { text : template . text , callback_data : absolutePath , } ; }