-
-
Notifications
You must be signed in to change notification settings - Fork 116
/
format.go
127 lines (107 loc) · 3.07 KB
/
format.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package entity
import (
"reflect"
"strings"
"unicode"
"github.com/gotd/td/tg"
)
// Builder builds message string and text entities.
type Builder struct {
entities []tg.MessageEntityClass
// We store index of first entity added at last Format call.
// It needed to trim space in all entities of last text block.
lastFormatIndex int
message strings.Builder
}
// GrowText grows internal buffer capacity.
func (b *Builder) GrowText(n int) {
b.message.Grow(n)
}
// GrowEntities grows internal buffer capacity.
func (b *Builder) GrowEntities(n int) {
if n < 0 {
panic("entity.Builder.GrowEntities: negative count")
}
buf := make([]tg.MessageEntityClass, len(b.entities), 2*cap(b.entities)+n)
copy(buf, b.entities)
b.entities = buf
}
func (b *Builder) reset() {
b.message.Reset()
b.entities = nil
}
// Complete returns build result and resets builder.
func (b *Builder) Complete() (string, []tg.MessageEntityClass) {
msg := b.message.String()
entities := b.entities
b.reset()
// If there are not entities or last text block does not have entities
// so we just return built message.
if len(entities) == 0 || b.lastFormatIndex >= len(entities) {
return msg, entities
}
// Since Telegram client does not handle space after formatted message
// we should compute length of the last block to trim it.
// Get first entity of last text block.
entity := entities[len(entities)-1]
offset := entity.GetOffset()
// Get last text block.
lastBlock := msg[offset:]
// Trim this block.
trimmed := strings.TrimRightFunc(lastBlock, unicode.IsSpace)
// If there are a difference, we should change length of the all entities.
if len(trimmed) != len(lastBlock) {
length := ComputeLength(trimmed)
for idx := range entities[b.lastFormatIndex:] {
setLength(idx, length, entities[b.lastFormatIndex:])
}
return msg[:offset+len(trimmed)], entities
}
return msg, entities
}
// setLength sets Length field of entity.
func setLength(index, value int, slice []tg.MessageEntityClass) {
reflect.ValueOf(&slice[index]).
Elem().Elem().Elem().
FieldByName("Length").
SetInt(int64(value))
}
// ComputeLength returns length of s encoded as UTF-16 string.
//
// While Telegram API docs state that they expect the number of UTF-8
// code points, in fact they are talking about UTF-16 code units.
func ComputeLength(s string) int {
const (
surrSelf = 0x10000
maxRune = '\U0010FFFF' // Maximum valid Unicode code point.
)
// From utf16 package.
n := 0
for _, v := range s {
if surrSelf <= v && v <= maxRune {
n += 2
} else {
n++
}
}
return n
}
// AddEntities adds given raw entities to the builder.
// Use carefully.
func (b *Builder) AddEntities(e ...tg.MessageEntityClass) *Builder {
b.entities = append(b.entities, e...)
return b
}
func (b *Builder) appendMessage(s string, formats ...Formatter) *Builder {
if s == "" {
return b
}
offset := ComputeLength(b.message.String())
length := ComputeLength(s)
b.lastFormatIndex = len(b.entities)
for i := range formats {
b.entities = append(b.entities, formats[i](offset, length))
}
b.message.WriteString(s)
return b
}