package snowluma 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("&", "&", "[", "[", "]", "]") return r.Replace(s) } func escapeParam(s string) string { r := strings.NewReplacer("&", "&", "[", "[", "]", "]", ",", ",") return r.Replace(s) } func unescapeText(s string) string { r := strings.NewReplacer("[", "[", "]", "]", "&", "&") return r.Replace(s) } func unescapeParam(s string) string { r := strings.NewReplacer(",", ",", "[", "[", "]", "]", "&", "&") return r.Replace(s) }