Files
napcat/cqcode.go

99 lines
2.2 KiB
Go

package napcat
import (
"sort"
"strings"
)
// ParseCQ 将 CQ 码字符串解析为消息段数组。
// 文本与 [CQ:type,k=v] 混排时会还原为对应的段序列。
func ParseCQ(s string) []Segment {
var out []Segment
var text strings.Builder
flush := func() {
if text.Len() > 0 {
out = append(out, Text(unescapeText(text.String())))
text.Reset()
}
}
for i := 0; i < len(s); {
if strings.HasPrefix(s[i:], "[CQ:") {
end := strings.IndexByte(s[i:], ']')
if end < 0 {
text.WriteString(s[i:])
break
}
end += i
flush()
out = append(out, parseCQBody(s[i+4:end]))
i = end + 1
continue
}
text.WriteByte(s[i])
i++
}
flush()
return out
}
func parseCQBody(body string) Segment {
parts := strings.Split(body, ",")
seg := Segment{Type: strings.ToLower(strings.TrimSpace(parts[0])), Data: map[string]any{}}
for _, p := range parts[1:] {
k, v, ok := strings.Cut(p, "=")
if !ok {
continue
}
seg.Data[strings.TrimSpace(k)] = unescapeParam(v)
}
return seg
}
// EncodeCQ 将消息段数组编码为 CQ 码字符串。
func EncodeCQ(segs []Segment) string {
var b strings.Builder
for _, s := range segs {
if s.Type == "text" {
b.WriteString(escapeText(strOf(s.Data["text"])))
continue
}
b.WriteString("[CQ:" + s.Type)
keys := make([]string, 0, len(s.Data))
for k := range s.Data {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
b.WriteString(",")
b.WriteString(k)
b.WriteString("=")
b.WriteString(escapeParam(strOf(s.Data[k])))
}
b.WriteString("]")
}
return b.String()
}
// String 将单个消息段编码为 CQ 码。
func (s Segment) String() string { return EncodeCQ([]Segment{s}) }
func escapeText(s string) string {
r := strings.NewReplacer("&", "&#38;", "[", "&#91;", "]", "&#93;")
return r.Replace(s)
}
func escapeParam(s string) string {
r := strings.NewReplacer("&", "&#38;", "[", "&#91;", "]", "&#93;", ",", "&#44;")
return r.Replace(s)
}
func unescapeText(s string) string {
r := strings.NewReplacer("&#91;", "[", "&#93;", "]", "&#38;", "&")
return r.Replace(s)
}
func unescapeParam(s string) string {
r := strings.NewReplacer("&#44;", ",", "&#91;", "[", "&#93;", "]", "&#38;", "&")
return r.Replace(s)
}