Skip to content

Commit 0862399

Browse files
committed
feat(tldr): allow specifying summary style
Users can now append a style option (e.g., `detailed`, `brief`) to the `/tldr` command. This overrides the group's default summary style. Updated help text to reflect new options and introduced a new `parseTLDRArgs` helper.
1 parent bd301ad commit 0862399

File tree

1 file changed

+131
-6
lines changed

1 file changed

+131
-6
lines changed

src/bot/commands.ts

Lines changed: 131 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)