-
-
Notifications
You must be signed in to change notification settings - Fork 129
/
fix.go
150 lines (126 loc) · 3.9 KB
/
fix.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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package entity
import (
"reflect"
"sort"
"strings"
"unicode"
"github.com/gotd/td/tg"
)
// SortEntities sorts entities as TDLib does it.
func SortEntities(entity []tg.MessageEntityClass) {
sort.Sort(entitySorter(entity))
}
type entitySorter []tg.MessageEntityClass
func (e entitySorter) Len() int {
return len(e)
}
func (e entitySorter) Less(i, j int) bool {
a, b := e[i], e[j]
return a.GetOffset() < b.GetOffset() ||
a.GetLength() > b.GetLength()
}
func (e entitySorter) Swap(i, j int) {
e[i], e[j] = e[j], e[i]
}
// 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))
}
// fixEntities trims space, if needed and fixes entities offsets.
func (b *Builder) fixEntities(msg string, entities []tg.MessageEntityClass) (string, []tg.MessageEntityClass) {
// If there are no entities or last text block does not have entities,
// so we just return built message.
if len(b.lengths) == 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 := b.lengths[len(b.lengths)-1]
offset := entity.offset
length := entity.length
// 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 length >= len(lastBlock) && len(trimmed) != len(lastBlock) {
length := ComputeLength(trimmed)
for idx := range entities[b.lastFormatIndex:] {
setLength(idx, length, entities[b.lastFormatIndex:])
}
msg = msg[:offset+len(trimmed)]
}
return msg, entities
}
// Raw returns raw result and resets builder without fixing spaces.
func (b *Builder) Raw() (string, []tg.MessageEntityClass) {
msg := b.message.String()
entities := b.entities
b.Reset()
return msg, entities
}
// Complete returns build result and resets builder.
func (b *Builder) Complete() (string, []tg.MessageEntityClass) {
msg, entities := b.Raw()
defer SortEntities(entities)
return b.fixEntities(msg, entities)
}
// ShrinkPreCode merges following <pre> and <code> entities, if needed.
//
// This function is used by formatters to be compliant with TDLib.
func (b *Builder) ShrinkPreCode() {
b.entities = shrinkPreCode(b.entities)
}
// equalRange compares ranges of given entities.
func equalRange(a, b tg.MessageEntityClass) bool {
return a.GetLength() == b.GetLength() && a.GetOffset() == b.GetOffset()
}
// shrinkPreCode merges following <pre> and <code> entities, if needed.
func shrinkPreCode(entities []tg.MessageEntityClass) []tg.MessageEntityClass {
for i, j := 0, len(entities)-1; i < j; i, j = i+1, j-1 {
entities[i], entities[j] = entities[j], entities[i]
}
filter := func(keep func(prev, cur tg.MessageEntityClass) bool) []tg.MessageEntityClass {
n := 0
for i, val := range entities {
if i == 0 || keep(entities[i-1], val) {
entities[n] = val
n++
}
}
return entities[:n]
}
isPreCode := func(class tg.MessageEntityClass) bool {
typeID := class.TypeID()
return typeID == tg.MessageEntityCodeTypeID || typeID == tg.MessageEntityPreTypeID
}
hasLang := func(class tg.MessageEntityClass) bool {
pre, ok := class.(*tg.MessageEntityPre)
return ok && pre.Language != ""
}
resetLang := func(class tg.MessageEntityClass) {
pre, ok := class.(*tg.MessageEntityPre)
if !ok {
return
}
pre.Language = ""
}
return filter(func(prev, cur tg.MessageEntityClass) bool {
if !isPreCode(prev) ||
!isPreCode(cur) ||
prev.TypeID() == cur.TypeID() {
// Keep if not is Pre/Code entities or if they are same.
return true
}
if !equalRange(prev, cur) {
resetLang(prev)
resetLang(cur)
return true
}
return !hasLang(prev)
})
}