-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Add chunking on Telegram Msgs #476
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,7 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package utils | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import "strings" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Truncate returns a truncated version of s with at most maxLen runes. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handles multi-byte Unicode characters properly. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // If the string is truncated, "..." is appended to indicate truncation. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -14,3 +16,151 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return string(runes[:maxLen-3]) + "..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // SplitMessage splits long messages into chunks, preserving code block integrity where possible. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Logic is inspired by the Discord channel implementation and is channel-agnostic. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // This allocates a slice to hold all chunks; for streaming use SplitMessageIter. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func SplitMessage(content string, limit int) []string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Check failure on line 23 in pkg/utils/string.go
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var messages []string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _ = SplitMessageIter(content, limit, func(chunk string) error { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| messages = append(messages, chunk) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return messages | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // SplitMessageIter splits content into chunks and calls cb for each chunk. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // This avoids allocating a slice to hold all chunks and is more memory-efficient for very large messages. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func SplitMessageIter(content string, limit int, cb func(chunk string) error) error { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| content = strings.TrimSpace(content) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for len(content) > 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if len(content) <= limit { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if content != "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := cb(content); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return err | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| msgEnd := limit | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Find natural split point within the limit | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| msgEnd = findLastNewline(content[:limit], 200) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if msgEnd <= 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| msgEnd = findLastSpace(content[:limit], 100) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if msgEnd <= 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| msgEnd = limit | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if this would end with an incomplete code block | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| candidate := content[:msgEnd] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| unclosedIdx := findLastUnclosedCodeBlock(candidate) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if unclosedIdx >= 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Message would end with incomplete code block | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Try to extend to include the closing ``` (with some buffer) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| extendedLimit := limit + 500 // Allow buffer for code blocks | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if len(content) > extendedLimit { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| closingIdx := findNextClosingCodeBlock(content, msgEnd) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if closingIdx > 0 && closingIdx <= extendedLimit { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Extend to include the closing ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| msgEnd = closingIdx | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Can't find closing, split before the code block | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| msgEnd = findLastNewline(content[:unclosedIdx], 200) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if msgEnd <= 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| msgEnd = findLastSpace(content[:unclosedIdx], 100) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if msgEnd <= 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| msgEnd = unclosedIdx | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Remaining content fits within extended limit | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| msgEnd = len(content) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if msgEnd <= 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| msgEnd = limit | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| chunk := strings.TrimSpace(content[:msgEnd]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if chunk != "" { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := cb(chunk); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return err | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| content = strings.TrimSpace(content[msgEnd:]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // findLastUnclosedCodeBlock finds the last opening ``` that doesn't have a closing ```. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Returns the position of the opening ``` or -1 if all code blocks are complete. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func findLastUnclosedCodeBlock(text string) int { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Check failure on line 106 in pkg/utils/string.go
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| count := 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lastOpenIdx := -1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for i := 0; i < len(text); i++ { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if i+2 < len(text) && text[i] == '`' && text[i+1] == '`' && text[i+2] == '`' { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if count == 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lastOpenIdx = i | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| count++ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| i += 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // If odd number of ``` markers, last one is unclosed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if count%2 == 1 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| count := 0 | |
| lastOpenIdx := -1 | |
| for i := 0; i < len(text); i++ { | |
| if i+2 < len(text) && text[i] == '`' && text[i+1] == '`' && text[i+2] == '`' { | |
| if count == 0 { | |
| lastOpenIdx = i | |
| } | |
| count++ | |
| i += 2 | |
| } | |
| } | |
| // If odd number of ``` markers, last one is unclosed | |
| if count%2 == 1 { | |
| lastOpenIdx := -1 | |
| inCodeBlock := false | |
| for i := 0; i < len(text); i++ { | |
| if i+2 < len(text) && text[i] == '`' && text[i+1] == '`' && text[i+2] == '`' { | |
| if !inCodeBlock { | |
| // Opening fence | |
| lastOpenIdx = i | |
| inCodeBlock = true | |
| } else { | |
| // Closing fence | |
| inCodeBlock = false | |
| } | |
| i += 2 | |
| } | |
| } | |
| if inCodeBlock { |
Check failure on line 129 in pkg/utils/string.go
GitHub Actions / Linter
findNextClosingCodeBlock redeclared in this block
Check failure on line 129 in pkg/utils/string.go
GitHub Actions / Vet
findNextClosingCodeBlock redeclared in this block
Check failure on line 140 in pkg/utils/string.go
GitHub Actions / Linter
findLastNewline redeclared in this block
Check failure on line 140 in pkg/utils/string.go
GitHub Actions / Vet
findLastNewline redeclared in this block
Check failure on line 155 in pkg/utils/string.go
GitHub Actions / Linter
findLastSpace redeclared in this block
Check failure on line 155 in pkg/utils/string.go
GitHub Actions / Vet
findLastSpace redeclared in this block
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
erris referenced outside the scope where it’s declared:if _, err := c.bot.EditMessageText(...); ...defineserronly inside theif, butlogger.WarnCF(..., {"error": err.Error()})uses it after the block. This will not compile. Capture the error into a variable (e.g.,editErr := ...) before logging, or restructure theifto keep the error in scope.