refactor: improve SplitMessage API clarity

- Accept hard upper limit (maxLen) instead of pre-subtracted value
- Caller now passes actual platform limit (e.g., 2000 for Discord)
- Internal buffer of 500 chars is handled within message.go
- Preferred split at maxLen - 500, may extend to maxLen for code blocks
- Never exceeds maxLen, no more mental math for callers
This commit is contained in:
Hua 2026-02-18 20:21:51 +00:00
parent 94a1b8664b
commit e03124dc8a
2 changed files with 24 additions and 19 deletions

View file

@ -105,7 +105,7 @@ func (c *DiscordChannel) Send(ctx context.Context, msg bus.OutboundMessage) erro
return nil return nil
} }
chunks := utils.SplitMessage(msg.Content, 1500) // Discord has a limit of 2000 characters per message, leave 500 for natural split e.g. code blocks chunks := utils.SplitMessage(msg.Content, 2000) // Discord hard limit: 2000 chars (prefers split at 1500 to leave room for code blocks)
for _, chunk := range chunks { for _, chunk := range chunks {
if err := c.sendChunk(ctx, channelID, chunk); err != nil { if err := c.sendChunk(ctx, channelID, chunk); err != nil {

View file

@ -4,26 +4,35 @@ import (
"strings" "strings"
) )
// SplitMessage splits long messages into chunks, preserving code block integrity const defaultCodeBlockBuffer = 500
// Uses natural boundaries (newlines, spaces) and extends messages slightly to avoid breaking code blocks
func SplitMessage(content string, limit int) []string { // SplitMessage splits long messages into chunks, preserving code block integrity.
// The maxLen parameter is the hard upper limit - no message will exceed this length.
// The function prefers to split at maxLen - defaultCodeBlockBuffer to leave room for code blocks,
// but may extend up to maxLen when needed to avoid breaking incomplete code blocks.
func SplitMessage(content string, maxLen int) []string {
var messages []string var messages []string
codeBlockBuffer := defaultCodeBlockBuffer
for len(content) > 0 { for len(content) > 0 {
if len(content) <= limit { if len(content) <= maxLen {
messages = append(messages, content) messages = append(messages, content)
break break
} }
msgEnd := limit // Effective split point: maxLen minus buffer, to leave room for code blocks
effectiveLimit := maxLen - codeBlockBuffer
if effectiveLimit < maxLen/2 {
effectiveLimit = maxLen / 2
}
// Find natural split point within the limit // Find natural split point within the effective limit
msgEnd = FindLastNewline(content[:limit], 200) msgEnd := FindLastNewline(content[:effectiveLimit], 200)
if msgEnd <= 0 { if msgEnd <= 0 {
msgEnd = FindLastSpace(content[:limit], 100) msgEnd = FindLastSpace(content[:effectiveLimit], 100)
} }
if msgEnd <= 0 { if msgEnd <= 0 {
msgEnd = limit msgEnd = effectiveLimit
} }
// Check if this would end with an incomplete code block // Check if this would end with an incomplete code block
@ -32,15 +41,14 @@ func SplitMessage(content string, limit int) []string {
if unclosedIdx >= 0 { if unclosedIdx >= 0 {
// Message would end with incomplete code block // Message would end with incomplete code block
// Try to extend to include the closing ``` (with some buffer) // Try to extend up to maxLen (hard limit, never exceed) to include the closing ```
extendedLimit := limit + 500 // Allow 500 char buffer for code blocks if len(content) > msgEnd {
if len(content) > extendedLimit {
closingIdx := FindNextClosingCodeBlock(content, msgEnd) closingIdx := FindNextClosingCodeBlock(content, msgEnd)
if closingIdx > 0 && closingIdx <= extendedLimit { if closingIdx > 0 && closingIdx <= maxLen {
// Extend to include the closing ``` // Extend to include the closing ```
msgEnd = closingIdx msgEnd = closingIdx
} else { } else {
// Can't find closing, split before the code block // Can't find closing within maxLen, split before the code block
msgEnd = FindLastNewline(content[:unclosedIdx], 200) msgEnd = FindLastNewline(content[:unclosedIdx], 200)
if msgEnd <= 0 { if msgEnd <= 0 {
msgEnd = FindLastSpace(content[:unclosedIdx], 100) msgEnd = FindLastSpace(content[:unclosedIdx], 100)
@ -49,14 +57,11 @@ func SplitMessage(content string, limit int) []string {
msgEnd = unclosedIdx msgEnd = unclosedIdx
} }
} }
} else {
// Remaining content fits within extended limit
msgEnd = len(content)
} }
} }
if msgEnd <= 0 { if msgEnd <= 0 {
msgEnd = limit msgEnd = effectiveLimit
} }
messages = append(messages, content[:msgEnd]) messages = append(messages, content[:msgEnd])