github.com/Mrs4s/go-cqhttp@v1.2.0/internal/msg/element.go (about) 1 // Package msg 提供了go-cqhttp消息中间表示,CQ码处理等等 2 package msg 3 4 import ( 5 "bytes" 6 "strings" 7 "unicode/utf8" 8 9 "github.com/Mrs4s/MiraiGo/binary" 10 ) 11 12 // @@@ CQ码转义处理 @@@ 13 14 // EscapeText 将字符串raw中部分字符转义 15 // 16 // - & -> & 17 // - [ -> [ 18 // - ] -> ] 19 func EscapeText(s string) string { 20 count := strings.Count(s, "&") 21 count += strings.Count(s, "[") 22 count += strings.Count(s, "]") 23 if count == 0 { 24 return s 25 } 26 27 // Apply replacements to buffer. 28 var b strings.Builder 29 b.Grow(len(s) + count*4) 30 start := 0 31 for i := 0; i < count; i++ { 32 j := start 33 for index, r := range s[start:] { 34 if r == '&' || r == '[' || r == ']' { 35 j += index 36 break 37 } 38 } 39 b.WriteString(s[start:j]) 40 switch s[j] { 41 case '&': 42 b.WriteString("&") 43 case '[': 44 b.WriteString("[") 45 case ']': 46 b.WriteString("]") 47 } 48 start = j + 1 49 } 50 b.WriteString(s[start:]) 51 return b.String() 52 } 53 54 // EscapeValue 将字符串value中部分字符转义 55 // 56 // - , -> , 57 // - & -> & 58 // - [ -> [ 59 // - ] -> ] 60 func EscapeValue(value string) string { 61 ret := EscapeText(value) 62 return strings.ReplaceAll(ret, ",", ",") 63 } 64 65 // UnescapeText 将字符串content中部分字符反转义 66 // 67 // - & -> & 68 // - [ -> [ 69 // - ] -> ] 70 func UnescapeText(content string) string { 71 ret := content 72 ret = strings.ReplaceAll(ret, "[", "[") 73 ret = strings.ReplaceAll(ret, "]", "]") 74 ret = strings.ReplaceAll(ret, "&", "&") 75 return ret 76 } 77 78 // UnescapeValue 将字符串content中部分字符反转义 79 // 80 // - , -> , 81 // - & -> & 82 // - [ -> [ 83 // - ] -> ] 84 func UnescapeValue(content string) string { 85 ret := strings.ReplaceAll(content, ",", ",") 86 return UnescapeText(ret) 87 } 88 89 // @@@ 消息中间表示 @@@ 90 91 // Pair key value pair 92 type Pair struct { 93 K string 94 V string 95 } 96 97 // Element single message 98 type Element struct { 99 Type string 100 Data []Pair 101 } 102 103 // Get 获取指定值 104 func (e *Element) Get(k string) string { 105 for _, datum := range e.Data { 106 if datum.K == k { 107 return datum.V 108 } 109 } 110 return "" 111 } 112 113 // CQCode convert element to cqcode 114 func (e *Element) CQCode() string { 115 buf := strings.Builder{} 116 e.WriteCQCodeTo(&buf) 117 return buf.String() 118 } 119 120 // WriteCQCodeTo write element's cqcode into sb 121 func (e *Element) WriteCQCodeTo(sb *strings.Builder) { 122 if e.Type == "text" { 123 sb.WriteString(EscapeText(e.Data[0].V)) // must be {"text": value} 124 return 125 } 126 sb.WriteString("[CQ:") 127 sb.WriteString(e.Type) 128 for _, data := range e.Data { 129 sb.WriteByte(',') 130 sb.WriteString(data.K) 131 sb.WriteByte('=') 132 sb.WriteString(EscapeValue(data.V)) 133 } 134 sb.WriteByte(']') 135 } 136 137 // MarshalJSON see encoding/json.Marshaler 138 func (e *Element) MarshalJSON() ([]byte, error) { 139 return binary.NewWriterF(func(w *binary.Writer) { 140 buf := (*bytes.Buffer)(w) 141 // fmt.Fprintf(buf, `{"type":"%s","data":{`, e.Type) 142 buf.WriteString(`{"type":"`) 143 buf.WriteString(e.Type) 144 buf.WriteString(`","data":{`) 145 for i, data := range e.Data { 146 if i != 0 { 147 buf.WriteByte(',') 148 } 149 // fmt.Fprintf(buf, `"%s":%q`, data.K, data.V) 150 buf.WriteByte('"') 151 buf.WriteString(data.K) 152 buf.WriteString(`":`) 153 buf.WriteString(QuoteJSON(data.V)) 154 } 155 buf.WriteString(`}}`) 156 }), nil 157 } 158 159 const hex = "0123456789abcdef" 160 161 // QuoteJSON 按JSON转义为字符加上双引号 162 func QuoteJSON(s string) string { 163 i, j := 0, 0 164 var b strings.Builder 165 b.WriteByte('"') 166 for j < len(s) { 167 c := s[j] 168 169 if c >= 0x20 && c <= 0x7f && c != '\\' && c != '"' { 170 // fast path: most of the time, printable ascii characters are used 171 j++ 172 continue 173 } 174 175 switch c { 176 case '\\', '"', '\n', '\r', '\t': 177 b.WriteString(s[i:j]) 178 b.WriteByte('\\') 179 switch c { 180 case '\n': 181 c = 'n' 182 case '\r': 183 c = 'r' 184 case '\t': 185 c = 't' 186 } 187 b.WriteByte(c) 188 j++ 189 i = j 190 continue 191 192 case '<', '>', '&': 193 b.WriteString(s[i:j]) 194 b.WriteString(`\u00`) 195 b.WriteByte(hex[c>>4]) 196 b.WriteByte(hex[c&0xF]) 197 j++ 198 i = j 199 continue 200 } 201 202 // This encodes bytes < 0x20 except for \t, \n and \r. 203 if c < 0x20 { 204 b.WriteString(s[i:j]) 205 b.WriteString(`\u00`) 206 b.WriteByte(hex[c>>4]) 207 b.WriteByte(hex[c&0xF]) 208 j++ 209 i = j 210 continue 211 } 212 213 r, size := utf8.DecodeRuneInString(s[j:]) 214 215 if r == utf8.RuneError && size == 1 { 216 b.WriteString(s[i:j]) 217 b.WriteString(`\ufffd`) 218 j += size 219 i = j 220 continue 221 } 222 223 switch r { 224 case '\u2028', '\u2029': 225 // U+2028 is LINE SEPARATOR. 226 // U+2029 is PARAGRAPH SEPARATOR. 227 // They are both technically valid characters in JSON strings, 228 // but don't work in JSONP, which has to be evaluated as JavaScript, 229 // and can lead to security holes there. It is valid JSON to 230 // escape them, so we do so unconditionally. 231 // See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion. 232 b.WriteString(s[i:j]) 233 b.WriteString(`\u202`) 234 b.WriteByte(hex[r&0xF]) 235 j += size 236 i = j 237 continue 238 } 239 240 j += size 241 } 242 243 b.WriteString(s[i:]) 244 b.WriteByte('"') 245 return b.String() 246 }