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("&amp;")
    43  		case '[':
    44  			b.WriteString("&#91;")
    45  		case ']':
    46  			b.WriteString("&#93;")
    47  		}
    48  		start = j + 1
    49  	}
    50  	b.WriteString(s[start:])
    51  	return b.String()
    52  }
    53  
    54  // EscapeValue 将字符串value中部分字符转义
    55  //
    56  //   - , -> &#44;
    57  //   - & -> &amp;
    58  //   - [ -> &#91;
    59  //   - ] -> &#93;
    60  func EscapeValue(value string) string {
    61  	ret := EscapeText(value)
    62  	return strings.ReplaceAll(ret, ",", "&#44;")
    63  }
    64  
    65  // UnescapeText 将字符串content中部分字符反转义
    66  //
    67  //   - &amp; -> &
    68  //   - &#91; -> [
    69  //   - &#93; -> ]
    70  func UnescapeText(content string) string {
    71  	ret := content
    72  	ret = strings.ReplaceAll(ret, "&#91;", "[")
    73  	ret = strings.ReplaceAll(ret, "&#93;", "]")
    74  	ret = strings.ReplaceAll(ret, "&amp;", "&")
    75  	return ret
    76  }
    77  
    78  // UnescapeValue 将字符串content中部分字符反转义
    79  //
    80  //   - &#44; -> ,
    81  //   - &amp; -> &
    82  //   - &#91; -> [
    83  //   - &#93; -> ]
    84  func UnescapeValue(content string) string {
    85  	ret := strings.ReplaceAll(content, "&#44;", ",")
    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  }