fix(discord): restore splitMessage and typing indicator

These functions were mistakenly removed in the original PR.
- Re-add splitMessage to handle messages >2000 chars (Discord limit)
- Re-add ChannelTyping to show typing indicator
- Add sendChunk helper for sending message pieces

The splitMessage function properly handles:
- Newline boundaries
- Space boundaries
- Preserves code block integrity
This commit is contained in:
Vernon Stinebaker 2026-02-17 02:32:03 +08:00
parent 6803568153
commit 7026f23f65

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"os" "os"
"strings"
"time" "time"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
@ -102,15 +103,31 @@ func (c *DiscordChannel) Send(ctx context.Context, msg bus.OutboundMessage) erro
return fmt.Errorf("channel ID is empty") return fmt.Errorf("channel ID is empty")
} }
message := msg.Content content := msg.Content
// 使用传入的 ctx 进行超时控制 runes := []rune(content)
if len(runes) == 0 {
return nil
}
chunks := splitMessage(msg.Content, 1500)
for _, chunk := range chunks {
if err := c.sendChunk(ctx, channelID, chunk); err != nil {
return err
}
}
return nil
}
func (c *DiscordChannel) sendChunk(ctx context.Context, channelID, content string) error {
sendCtx, cancel := context.WithTimeout(ctx, sendTimeout) sendCtx, cancel := context.WithTimeout(ctx, sendTimeout)
defer cancel() defer cancel()
done := make(chan error, 1) done := make(chan error, 1)
go func() { go func() {
_, err := c.session.ChannelMessageSend(channelID, message) _, err := c.session.ChannelMessageSend(channelID, content)
done <- err done <- err
}() }()
@ -125,6 +142,114 @@ func (c *DiscordChannel) Send(ctx context.Context, msg bus.OutboundMessage) erro
} }
} }
func splitMessage(content string, limit int) []string {
var messages []string
for len(content) > 0 {
if len(content) <= limit {
messages = append(messages, content)
break
}
msgEnd := limit
msgEnd = findLastNewline(content[:limit], 200)
if msgEnd <= 0 {
msgEnd = findLastSpace(content[:limit], 100)
}
if msgEnd <= 0 {
msgEnd = limit
}
candidate := content[:msgEnd]
unclosedIdx := findLastUnclosedCodeBlock(candidate)
if unclosedIdx >= 0 {
extendedLimit := limit + 500
if len(content) > extendedLimit {
closingIdx := findNextClosingCodeBlock(content, msgEnd)
if closingIdx > 0 && closingIdx <= extendedLimit {
msgEnd = closingIdx
} else {
msgEnd = findLastNewline(content[:unclosedIdx], 200)
if msgEnd <= 0 {
msgEnd = findLastSpace(content[:unclosedIdx], 100)
}
if msgEnd <= 0 {
msgEnd = unclosedIdx
}
}
} else {
msgEnd = len(content)
}
}
if msgEnd <= 0 {
msgEnd = limit
}
messages = append(messages, content[:msgEnd])
content = strings.TrimSpace(content[msgEnd:])
}
return messages
}
func findLastUnclosedCodeBlock(text string) int {
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 count%2 == 1 {
return lastOpenIdx
}
return -1
}
func findNextClosingCodeBlock(text string, startIdx int) int {
for i := startIdx; i < len(text); i++ {
if i+2 < len(text) && text[i] == '`' && text[i+1] == '`' && text[i+2] == '`' {
return i + 3
}
}
return -1
}
func findLastNewline(s string, searchWindow int) int {
searchStart := len(s) - searchWindow
if searchStart < 0 {
searchStart = 0
}
for i := len(s) - 1; i >= searchStart; i-- {
if s[i] == '\n' {
return i
}
}
return -1
}
func findLastSpace(s string, searchWindow int) int {
searchStart := len(s) - searchWindow
if searchStart < 0 {
searchStart = 0
}
for i := len(s) - 1; i >= searchStart; i-- {
if s[i] == ' ' || s[i] == '\t' {
return i
}
}
return -1
}
// appendContent 安全地追加内容到现有文本 // appendContent 安全地追加内容到现有文本
func appendContent(content, suffix string) string { func appendContent(content, suffix string) string {
if content == "" { if content == "" {
@ -172,6 +297,12 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag
} }
} }
if err := c.session.ChannelTyping(m.ChannelID); err != nil {
logger.ErrorCF("discord", "Failed to send typing indicator", map[string]any{
"error": err.Error(),
})
}
senderID := m.Author.ID senderID := m.Author.ID
senderName := m.Author.Username senderName := m.Author.Username
if m.Author.Discriminator != "" && m.Author.Discriminator != "0" { if m.Author.Discriminator != "" && m.Author.Discriminator != "0" {