@@ -1093,11 +1093,13 @@ export class Commands {
10931093 // Convert markdown to HTML
10941094 const formattedSummary = this . markdownToHtml ( summary ) ;
10951095
1096- await ctx . api . editMessageText (
1096+ // Send summary, splitting into multiple messages if too long
1097+ await this . sendSummaryMessage (
1098+ ctx ,
10971099 chat . id ,
10981100 loadingMsg . message_id ,
1099- `📝 <b>TLDR Summary</b> (${ summaryLabel } )\n\n ${ formattedSummary } ` ,
1100- { parse_mode : 'HTML' }
1101+ `📝 <b>TLDR Summary</b> (${ summaryLabel } )` ,
1102+ formattedSummary
11011103 ) ;
11021104 } catch ( error : any ) {
11031105 console . error ( 'Error generating TLDR:' , error ) ;
@@ -1198,11 +1200,13 @@ export class Commands {
11981200 // Convert markdown to HTML
11991201 const formattedSummary = this . markdownToHtml ( summary ) ;
12001202
1201- await ctx . api . editMessageText (
1203+ // Send summary, splitting into multiple messages if too long
1204+ await this . sendSummaryMessage (
1205+ ctx ,
12021206 chat . id ,
12031207 loadingMsg . message_id ,
1204- `📝 <b>TLDR Summary</b> (from message)\n\n ${ formattedSummary } ` ,
1205- { parse_mode : 'HTML' }
1208+ `📝 <b>TLDR Summary</b> (from message)` ,
1209+ formattedSummary
12061210 ) ;
12071211 } catch ( error : any ) {
12081212 console . error ( 'Error generating TLDR from message:' , error ) ;
@@ -1917,6 +1921,127 @@ export class Commands {
19171921 return html ;
19181922 }
19191923
1924+ /**
1925+ * Send summary message, splitting into multiple parts if too long
1926+ * Telegram has a 4096 character limit per message
1927+ */
1928+ private async sendSummaryMessage (
1929+ ctx : MyContext ,
1930+ chatId : number ,
1931+ loadingMsgId : number ,
1932+ header : string ,
1933+ summary : string
1934+ ) : Promise < void > {
1935+ const MAX_MESSAGE_LENGTH = 4096 ;
1936+ const headerLength = header . length + 2 ; // +2 for \n\n
1937+
1938+ // Calculate available length for summary (leave some buffer for safety)
1939+ const maxSummaryLength = MAX_MESSAGE_LENGTH - headerLength - 100 ; // 100 char buffer
1940+
1941+ // If summary fits in one message, send it normally
1942+ if ( summary . length <= maxSummaryLength ) {
1943+ try {
1944+ await ctx . api . editMessageText (
1945+ chatId ,
1946+ loadingMsgId ,
1947+ `${ header } \n\n${ summary } ` ,
1948+ { parse_mode : 'HTML' }
1949+ ) ;
1950+ return ;
1951+ } catch ( error : any ) {
1952+ // If edit fails due to message length, fall through to splitting logic
1953+ if ( ! error . message ?. includes ( 'MESSAGE_TOO_LONG' ) ) {
1954+ throw error ;
1955+ }
1956+ }
1957+ }
1958+
1959+ // Split summary into chunks
1960+ const chunks = this . splitMessage ( summary , maxSummaryLength ) ;
1961+
1962+ // Edit loading message with first chunk
1963+ try {
1964+ await ctx . api . editMessageText (
1965+ chatId ,
1966+ loadingMsgId ,
1967+ `${ header } (1/${ chunks . length } )\n\n${ chunks [ 0 ] } ` ,
1968+ { parse_mode : 'HTML' }
1969+ ) ;
1970+ } catch ( error : any ) {
1971+ // If even the header + first chunk is too long, send just the first chunk and continue
1972+ if ( error . message ?. includes ( 'MESSAGE_TOO_LONG' ) ) {
1973+ await ctx . api . editMessageText (
1974+ chatId ,
1975+ loadingMsgId ,
1976+ chunks [ 0 ] ,
1977+ { parse_mode : 'HTML' }
1978+ ) ;
1979+ } else {
1980+ throw error ;
1981+ }
1982+ }
1983+
1984+ // Send remaining chunks as new messages
1985+ for ( let i = 1 ; i < chunks . length ; i ++ ) {
1986+ await ctx . reply (
1987+ `${ header } (${ i + 1 } /${ chunks . length } )\n\n${ chunks [ i ] } ` ,
1988+ { parse_mode : 'HTML' }
1989+ ) ;
1990+ }
1991+ }
1992+
1993+ /**
1994+ * Split a long message into chunks that fit within Telegram's message length limit
1995+ * Tries to split at paragraph boundaries (double newlines) when possible
1996+ */
1997+ private splitMessage ( text : string , maxLength : number ) : string [ ] {
1998+ if ( text . length <= maxLength ) {
1999+ return [ text ] ;
2000+ }
2001+
2002+ const chunks : string [ ] = [ ] ;
2003+ let remaining = text ;
2004+
2005+ while ( remaining . length > 0 ) {
2006+ if ( remaining . length <= maxLength ) {
2007+ chunks . push ( remaining ) ;
2008+ break ;
2009+ }
2010+
2011+ // Try to find a good split point (preferably at paragraph break)
2012+ let splitPoint = maxLength ;
2013+
2014+ // Look for paragraph break (double newline) near the max length
2015+ const paragraphBreak = remaining . lastIndexOf ( '\n\n' , maxLength ) ;
2016+ if ( paragraphBreak > maxLength * 0.7 ) {
2017+ // Use paragraph break if it's not too early
2018+ splitPoint = paragraphBreak + 2 ; // +2 to include \n\n
2019+ } else {
2020+ // Look for single newline
2021+ const lineBreak = remaining . lastIndexOf ( '\n' , maxLength ) ;
2022+ if ( lineBreak > maxLength * 0.8 ) {
2023+ splitPoint = lineBreak + 1 ; // +1 to include \n
2024+ } else {
2025+ // Look for sentence end
2026+ const sentenceEnd = remaining . lastIndexOf ( '. ' , maxLength ) ;
2027+ if ( sentenceEnd > maxLength * 0.7 ) {
2028+ splitPoint = sentenceEnd + 2 ; // +2 to include '. '
2029+ } else {
2030+ // Force split at max length
2031+ splitPoint = maxLength ;
2032+ }
2033+ }
2034+ }
2035+
2036+ // Extract chunk and add continuation indicator if not the last chunk
2037+ const chunk = remaining . substring ( 0 , splitPoint ) ;
2038+ chunks . push ( chunk ) ;
2039+ remaining = remaining . substring ( splitPoint ) . trim ( ) ;
2040+ }
2041+
2042+ return chunks ;
2043+ }
2044+
19202045 /**
19212046 * Parse TLDR command arguments to extract timeframe/count and optional style preference
19222047 * Examples: "1h detailed", "300 brief", "1 day bullet", "default"
0 commit comments