Files
goclaw/internal/channels/replycontext/reply-context-render.go
T
2026-07-03 06:53:46 +07:00

131 lines
2.7 KiB
Go

package replycontext
import "strings"
type renderMessage struct {
sender string
body string
}
func NormalizeIDs(ids []string) []string {
out := make([]string, 0, len(ids))
seen := make(map[string]struct{}, len(ids))
for _, id := range ids {
id = strings.TrimSpace(id)
if id == "" {
continue
}
if _, ok := seen[id]; ok {
continue
}
seen[id] = struct{}{}
out = append(out, id)
}
return out
}
func RenderQuote(quote Quote) string {
return renderQuote(quote, DefaultOptions())
}
func normalizeQuote(quote Quote, opts Options) Quote {
return Quote{
IDs: NormalizeIDs(quote.IDs),
Sender: strings.TrimSpace(quote.Sender),
Body: truncateText(strings.TrimSpace(quote.Body), opts.MaxCharsPerMessage),
}
}
func renderQuote(quote Quote, opts Options) string {
body := strings.TrimSpace(quote.Body)
if body == "" {
return ""
}
body = truncateText(body, opts.MaxCharsPerMessage)
block, ok := renderReplyBlock(strings.TrimSpace(quote.Sender), body, opts.MaxTotalChars)
if !ok {
return ""
}
return block
}
func Compose(context, body string) string {
context = strings.TrimSpace(context)
body = strings.TrimSpace(body)
switch {
case context != "" && body != "":
return context + "\n" + body
case context != "":
return context
default:
return body
}
}
func renderChain(chain []renderMessage, opts Options) string {
remaining := opts.MaxTotalChars
var b strings.Builder
for i, msg := range chain {
if i > 0 {
if remaining <= 1 {
break
}
b.WriteByte('\n')
remaining--
}
body := truncateText(msg.body, opts.MaxCharsPerMessage)
block, ok := renderReplyBlock(msg.sender, body, remaining)
if !ok {
break
}
b.WriteString(block)
remaining -= len(block)
}
return b.String()
}
func renderReplyBlock(sender, body string, maxBytes int) (string, bool) {
body = strings.TrimSpace(body)
if body == "" {
return "", false
}
prefix := "[Replying to"
if sender = inlineText(sender); sender != "" {
prefix += " " + sender
}
prefix += "]\n"
suffix := "\n[/Replying]"
if maxBytes > 0 {
bodyBudget := maxBytes - len(prefix) - len(suffix)
if bodyBudget <= 0 {
return "", false
}
body = truncateText(body, bodyBudget)
}
return prefix + body + suffix, true
}
func truncateText(text string, maxBytes int) string {
text = strings.TrimSpace(text)
if maxBytes <= 0 || len(text) <= maxBytes {
return text
}
const marker = "..."
if maxBytes <= len(marker) {
return marker[:maxBytes]
}
limit := maxBytes - len(marker)
var b strings.Builder
for _, r := range text {
if b.Len()+len(string(r)) > limit {
break
}
b.WriteRune(r)
}
return strings.TrimSpace(b.String()) + marker
}
func inlineText(text string) string {
return strings.Join(strings.Fields(text), " ")
}