@@ -920,6 +920,77 @@ async function registerBotCommands(token: string): Promise<void> {
920920 }
921921}
922922
923+ // --- Debounce buffer ---
924+
925+ interface DebouncedEntry {
926+ messages : TelegramMessage [ ] ;
927+ timer : ReturnType < typeof setTimeout > ;
928+ }
929+
930+ const debounceBuffer = new Map < number , DebouncedEntry > ( ) ;
931+
932+ function flushDebounced ( chatId : number ) : void {
933+ const entry = debounceBuffer . get ( chatId ) ;
934+ if ( ! entry || entry . messages . length === 0 ) {
935+ debounceBuffer . delete ( chatId ) ;
936+ return ;
937+ }
938+ debounceBuffer . delete ( chatId ) ;
939+
940+ if ( entry . messages . length === 1 ) {
941+ handleMessage ( entry . messages [ 0 ] ) . catch ( ( err ) => {
942+ console . error ( `[Telegram] Unhandled: ${ err } ` ) ;
943+ } ) ;
944+ return ;
945+ }
946+
947+ const first = entry . messages [ 0 ] ;
948+ const merged : TelegramMessage = {
949+ message_id : first . message_id ,
950+ from : first . from ,
951+ reply_to_message : first . reply_to_message ,
952+ chat : first . chat ,
953+ message_thread_id : first . message_thread_id ,
954+ text : entry . messages
955+ . map ( ( m ) => {
956+ const { text } = getMessageTextAndEntities ( m ) ;
957+ return text ;
958+ } )
959+ . filter ( Boolean )
960+ . join ( "\n" ) ,
961+ entities : first . entities ,
962+ } ;
963+
964+ for ( const m of entry . messages ) {
965+ if ( m . photo && m . photo . length > 0 && ! merged . photo ) merged . photo = m . photo ;
966+ if ( m . voice && ! merged . voice ) merged . voice = m . voice ;
967+ if ( m . audio && ! merged . audio ) merged . audio = m . audio ;
968+ if ( m . document && ! merged . document ) merged . document = m . document ;
969+ }
970+
971+ const count = entry . messages . length ;
972+ debugLog ( `Debounce flush: chat=${ chatId } merged=${ count } messages` ) ;
973+ console . log ( `[Telegram] Debounced ${ count } messages from chat ${ chatId } ` ) ;
974+
975+ handleMessage ( merged ) . catch ( ( err ) => {
976+ console . error ( `[Telegram] Unhandled: ${ err } ` ) ;
977+ } ) ;
978+ }
979+
980+ function enqueueDebounced ( message : TelegramMessage , debounceMs : number ) : void {
981+ const chatId = message . chat . id ;
982+ const existing = debounceBuffer . get ( chatId ) ;
983+
984+ if ( existing ) {
985+ clearTimeout ( existing . timer ) ;
986+ existing . messages . push ( message ) ;
987+ existing . timer = setTimeout ( ( ) => flushDebounced ( chatId ) , debounceMs ) ;
988+ } else {
989+ const timer = setTimeout ( ( ) => flushDebounced ( chatId ) , debounceMs ) ;
990+ debounceBuffer . set ( chatId , { messages : [ message ] , timer } ) ;
991+ }
992+ }
993+
923994// --- Polling loop ---
924995
925996let running = true ;
@@ -941,6 +1012,7 @@ async function poll(): Promise<void> {
9411012
9421013 console . log ( "Telegram bot started (long polling)" ) ;
9431014 console . log ( ` Allowed users: ${ config . allowedUserIds . length === 0 ? "all" : config . allowedUserIds . join ( ", " ) } ` ) ;
1015+ if ( config . debounceMs > 0 ) console . log ( ` Debounce: ${ config . debounceMs } ms` ) ;
9441016 if ( telegramDebug ) console . log ( " Debug: enabled" ) ;
9451017
9461018 // Register available skills as bot command menu (non-blocking)
@@ -968,9 +1040,13 @@ async function poll(): Promise<void> {
9681040 update . edited_channel_post ,
9691041 ] . filter ( ( m ) : m is TelegramMessage => Boolean ( m ) ) ;
9701042 for ( const incoming of incomingMessages ) {
971- handleMessage ( incoming ) . catch ( ( err ) => {
972- console . error ( `[Telegram] Unhandled: ${ err } ` ) ;
973- } ) ;
1043+ if ( config . debounceMs > 0 ) {
1044+ enqueueDebounced ( incoming , config . debounceMs ) ;
1045+ } else {
1046+ handleMessage ( incoming ) . catch ( ( err ) => {
1047+ console . error ( `[Telegram] Unhandled: ${ err } ` ) ;
1048+ } ) ;
1049+ }
9741050 }
9751051 if ( update . my_chat_member ) {
9761052 handleMyChatMember ( update . my_chat_member ) . catch ( ( err ) => {
0 commit comments