Thumbnail

rani/matterbridge.git

Clone URL: https://git.buni.party/rani/matterbridge.git

Viewing file on branch master

1package btelegram
2
3import (
4 "fmt"
5 "html"
6 "path/filepath"
7 "strconv"
8 "strings"
9 "unicode/utf16"
10
11 "github.com/davecgh/go-spew/spew"
12 "github.com/matterbridge-org/matterbridge/bridge/config"
13 "github.com/matterbridge-org/matterbridge/bridge/helper"
14
15 // Seems not much different from upstream https://github.com/go-telegram-bot-api/telegram-bot-api replace?
16 tgbotapi "github.com/matterbridge/telegram-bot-api/v6"
17)
18
19func (b *Btelegram) handleUpdate(rmsg *config.Message, message, posted, edited *tgbotapi.Message) *tgbotapi.Message {
20 // handle channels
21 if posted != nil {
22 if posted.Text == "/chatId" {
23 chatID := strconv.FormatInt(posted.Chat.ID, 10)
24
25 // Handle chat topics
26 if posted.IsTopicMessage {
27 chatID = chatID + "/" + strconv.Itoa(posted.MessageThreadID)
28 }
29
30 _, err := b.Send(config.Message{
31 Channel: chatID,
32 Text: fmt.Sprintf("ID of this chat: %s", chatID),
33 })
34 if err != nil {
35 b.Log.Warnf("Unable to send chatID to %s", chatID)
36 }
37 } else {
38 message = posted
39 rmsg.Text = message.Text
40 }
41 }
42
43 // edited channel message
44 if edited != nil && !b.GetBool("EditDisable") {
45 message = edited
46 rmsg.Text = rmsg.Text + message.Text + b.GetString("EditSuffix")
47 }
48 return message
49}
50
51// handleChannels checks if it's a channel message and if the message is a new or edited messages
52func (b *Btelegram) handleChannels(rmsg *config.Message, message *tgbotapi.Message, update tgbotapi.Update) *tgbotapi.Message {
53 return b.handleUpdate(rmsg, message, update.ChannelPost, update.EditedChannelPost)
54}
55
56// handleGroups checks if it's a group message and if the message is a new or edited messages
57func (b *Btelegram) handleGroups(rmsg *config.Message, message *tgbotapi.Message, update tgbotapi.Update) *tgbotapi.Message {
58 return b.handleUpdate(rmsg, message, update.Message, update.EditedMessage)
59}
60
61// handleForwarded handles forwarded messages
62func (b *Btelegram) handleForwarded(rmsg *config.Message, message *tgbotapi.Message) {
63 if message.ForwardDate == 0 {
64 return
65 }
66
67 if message.ForwardFromChat != nil && message.ForwardFrom == nil {
68 rmsg.Text = "Forwarded from " + message.ForwardFromChat.Title + ": " + rmsg.Text
69 return
70 }
71
72 if message.ForwardFrom == nil {
73 rmsg.Text = "Forwarded from " + unknownUser + ": " + rmsg.Text
74 return
75 }
76
77 usernameForward := ""
78 if b.GetBool("UseFirstName") {
79 usernameForward = message.ForwardFrom.FirstName
80 }
81 if b.GetBool("UseFullName") {
82 usernameForward = message.ForwardFrom.FirstName + " " + message.ForwardFrom.LastName
83 }
84
85 if usernameForward == "" {
86 usernameForward = message.ForwardFrom.UserName
87 if usernameForward == "" {
88 usernameForward = message.ForwardFrom.FirstName
89 }
90 }
91
92 if usernameForward == "" {
93 usernameForward = unknownUser
94 }
95
96 rmsg.Text = "Forwarded from " + usernameForward + ": " + rmsg.Text
97}
98
99// handleQuoting handles quoting of previous messages
100func (b *Btelegram) handleQuoting(rmsg *config.Message, message *tgbotapi.Message) {
101 // Used to check if the message was a reply to the root topic
102 if message.ReplyToMessage != nil && (!message.IsTopicMessage || message.ReplyToMessage.MessageID != message.MessageThreadID) { //nolint:nestif
103 usernameReply := ""
104 if message.ReplyToMessage.From != nil {
105 if b.GetBool("UseFirstName") {
106 usernameReply = message.ReplyToMessage.From.FirstName
107 }
108 if b.GetBool("UseFullName") {
109 usernameReply = message.ReplyToMessage.From.FirstName + " " + message.ReplyToMessage.From.LastName
110 }
111 if usernameReply == "" {
112 usernameReply = message.ReplyToMessage.From.UserName
113 if usernameReply == "" {
114 usernameReply = message.ReplyToMessage.From.FirstName
115 }
116 }
117 }
118 if usernameReply == "" {
119 usernameReply = unknownUser
120 }
121 if !b.GetBool("QuoteDisable") {
122 quote := message.ReplyToMessage.Text
123 if quote == "" {
124 quote = message.ReplyToMessage.Caption
125 }
126 rmsg.Text = b.handleQuote(rmsg.Text, usernameReply, quote)
127 }
128 }
129}
130
131// handleUsername handles the correct setting of the username
132func (b *Btelegram) handleUsername(rmsg *config.Message, message *tgbotapi.Message) {
133 if message.From != nil {
134 rmsg.UserID = strconv.FormatInt(message.From.ID, 10)
135 if b.GetBool("UseFirstName") {
136 rmsg.Username = message.From.FirstName
137 }
138 if b.GetBool("UseFullName") {
139 if message.From.FirstName != "" && message.From.LastName != "" {
140 rmsg.Username = message.From.FirstName + " " + message.From.LastName
141 }
142 }
143 if rmsg.Username == "" {
144 rmsg.Username = message.From.UserName
145 if rmsg.Username == "" {
146 rmsg.Username = message.From.FirstName
147 }
148 }
149 // only download avatars if we have a place to upload them (configured mediaserver)
150 if b.General.MediaServerDownload != "" && b.General.MediaDownloadPath != "" {
151 b.handleDownloadAvatar(message.From.ID, rmsg.Channel)
152 }
153 }
154
155 if message.SenderChat != nil { //nolint:nestif
156 rmsg.UserID = strconv.FormatInt(message.SenderChat.ID, 10)
157 if b.GetBool("UseFirstName") {
158 rmsg.Username = message.SenderChat.FirstName
159 }
160 if b.GetBool("UseFullName") {
161 if message.SenderChat.FirstName != "" && message.SenderChat.LastName != "" {
162 rmsg.Username = message.SenderChat.FirstName + " " + message.SenderChat.LastName
163 }
164 }
165
166 if rmsg.Username == "" || rmsg.Username == "Channel_Bot" {
167 rmsg.Username = message.SenderChat.UserName
168
169 if rmsg.Username == "" || rmsg.Username == "Channel_Bot" {
170 rmsg.Username = message.SenderChat.FirstName
171 }
172 }
173 // only download avatars if we have a place to upload them (configured mediaserver)
174 if b.General.MediaServerDownload != "" && b.General.MediaDownloadPath != "" {
175 b.handleDownloadAvatar(message.SenderChat.ID, rmsg.Channel)
176 }
177 }
178
179 // Fallback on author signature (used in "channel" type of chat)
180 if rmsg.Username == "" && message.AuthorSignature != "" {
181 rmsg.Username = message.AuthorSignature
182 }
183
184 // if we really didn't find a username, set it to unknown
185 if rmsg.Username == "" {
186 rmsg.Username = unknownUser
187 }
188}
189
190func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
191 for update := range updates {
192 b.Log.Debugf("== Receiving event: %#v", update.Message)
193
194 if update.Message == nil && update.ChannelPost == nil &&
195 update.EditedMessage == nil && update.EditedChannelPost == nil {
196 b.Log.Info("Received event without messages, skipping.")
197 continue
198 }
199
200 if b.GetInt("debuglevel") == 1 {
201 spew.Dump(update.Message)
202 }
203
204 b.handleGroupUpdate(update)
205
206 var message *tgbotapi.Message
207
208 rmsg := config.Message{Account: b.Account, Extra: make(map[string][]interface{})}
209
210 // handle channels
211 message = b.handleChannels(&rmsg, message, update)
212
213 // handle groups
214 message = b.handleGroups(&rmsg, message, update)
215
216 if message == nil {
217 b.Log.Error("message is nil, this shouldn't happen.")
218 continue
219 }
220
221 // set the ID's from the channel or group message
222 rmsg.ID = strconv.Itoa(message.MessageID)
223 rmsg.Channel = strconv.FormatInt(message.Chat.ID, 10)
224 if message.IsTopicMessage {
225 rmsg.Channel += "/" + strconv.Itoa(message.MessageThreadID)
226 }
227
228 // preserve threading from telegram reply
229 if message.ReplyToMessage != nil &&
230 // Used to check if the message was a reply to the root topic
231 (!message.IsTopicMessage || message.ReplyToMessage.MessageID != message.MessageThreadID) {
232 rmsg.ParentID = strconv.Itoa(message.ReplyToMessage.MessageID)
233 }
234
235 // handle entities (adding URLs)
236 b.handleEntities(&rmsg, message)
237
238 // handle username
239 b.handleUsername(&rmsg, message)
240
241 // handle any downloads
242 err := b.handleDownload(&rmsg, message)
243 if err != nil {
244 b.Log.Errorf("download failed: %s", err)
245 }
246
247 // handle forwarded messages
248 b.handleForwarded(&rmsg, message)
249
250 // quote the previous message
251 b.handleQuoting(&rmsg, message)
252
253 if rmsg.Text != "" || len(rmsg.Extra) > 0 {
254 // Comment the next line out due to avoid removing empty lines in Telegram
255 // rmsg.Text = helper.RemoveEmptyNewLines(rmsg.Text)
256 // channels don't have (always?) user information. see #410
257 if message.From != nil {
258 rmsg.Avatar = helper.GetAvatar(b.avatarMap, strconv.FormatInt(message.From.ID, 10), b.General)
259 }
260
261 b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
262 b.Log.Debugf("<= Message is %#v", rmsg)
263 b.Remote <- rmsg
264 }
265 }
266}
267
268func (b *Btelegram) handleGroupUpdate(update tgbotapi.Update) {
269 if msg := update.Message; msg != nil {
270 switch {
271 case msg.NewChatMembers != nil:
272 b.handleUserJoin(update)
273 case msg.LeftChatMember != nil:
274 b.handleUserLeave(update)
275 }
276 }
277}
278
279func (b *Btelegram) handleUserJoin(update tgbotapi.Update) {
280 msg := update.Message
281 for _, user := range msg.NewChatMembers {
282 rmsg := config.Message{
283 UserID: strconv.FormatInt(user.ID, 10),
284 Username: user.FirstName, // for some reason all the other name felids are empty on this event (at least for me)
285 Channel: strconv.FormatInt(msg.Chat.ID, 10),
286 Account: b.Account,
287 Protocol: b.Protocol,
288 Event: config.EventJoinLeave,
289 Text: "joined chat",
290 }
291 b.Remote <- rmsg
292 }
293}
294
295func (b *Btelegram) handleUserLeave(update tgbotapi.Update) {
296 msg := update.Message
297 user := msg.LeftChatMember
298
299 rmsg := config.Message{
300 UserID: strconv.FormatInt(user.ID, 10),
301 Username: user.FirstName, // for some reason all the other name felids are empty on this event (at least for me)
302 Channel: strconv.FormatInt(msg.Chat.ID, 10),
303 Account: b.Account,
304 Protocol: b.Protocol,
305 Event: config.EventJoinLeave,
306 Text: "left chat",
307 }
308
309 b.Remote <- rmsg
310}
311
312// handleDownloadAvatar downloads the avatar of userid from channel
313// sends a EVENT_AVATAR_DOWNLOAD message to the gateway if successful.
314// logs an error message if it fails
315func (b *Btelegram) handleDownloadAvatar(userid int64, channel string) {
316 rmsg := config.Message{
317 Username: "system",
318 Text: "avatar",
319 Channel: channel,
320 Account: b.Account,
321 UserID: strconv.FormatInt(userid, 10),
322 Event: config.EventAvatarDownload,
323 Extra: make(map[string][]interface{}),
324 }
325
326 if _, ok := b.avatarMap[strconv.FormatInt(userid, 10)]; ok {
327 return
328 }
329
330 photos, err := b.c.GetUserProfilePhotos(tgbotapi.UserProfilePhotosConfig{UserID: userid, Limit: 1})
331 if err != nil {
332 b.Log.Errorf("Userprofile download failed for %#v %s", userid, err)
333 }
334
335 if len(photos.Photos) > 0 {
336 photo := photos.Photos[0][0]
337 url := b.getFileDirectURL(photo.FileID)
338 name := strconv.FormatInt(userid, 10) + ".png"
339 b.Log.Debugf("trying to download %#v fileid %#v with size %#v", name, photo.FileID, photo.FileSize)
340
341 err := helper.HandleDownloadSize(b.Log, &rmsg, name, int64(photo.FileSize), b.General)
342 if err != nil {
343 b.Log.Error(err)
344 return
345 }
346 data, err := helper.DownloadFile(url)
347 if err != nil {
348 b.Log.Errorf("download %s failed %#v", url, err)
349 return
350 }
351 helper.HandleDownloadData(b.Log, &rmsg, name, rmsg.Text, "", data, b.General)
352 b.Remote <- rmsg
353 }
354}
355
356func (b *Btelegram) maybeConvertTgs(name *string, data *[]byte) {
357 format := b.GetString("MediaConvertTgs")
358 if helper.SupportsFormat(format) {
359 b.Log.Debugf("Format supported by %s, converting %v", helper.LottieBackend(), name)
360 } else {
361 // Otherwise, no conversion was requested. Trying to run the usual webp
362 // converter would fail, because '.tgs.webp' is actually a gzipped JSON
363 // file, and has nothing to do with WebP.
364 return
365 }
366 err := helper.ConvertTgsToX(data, format, b.Log)
367 if err != nil {
368 b.Log.Errorf("conversion failed: %v", err)
369 } else {
370 *name = strings.Replace(*name, "tgs.webp", format, 1)
371 }
372}
373
374func (b *Btelegram) maybeConvertWebp(name *string, data *[]byte) {
375 if b.GetBool("MediaConvertWebPToPNG") {
376 b.Log.Debugf("WebP to PNG conversion enabled, converting %v", name)
377 err := helper.ConvertWebPToPNG(data)
378 if err != nil {
379 b.Log.Errorf("conversion failed: %v", err)
380 } else {
381 *name = strings.Replace(*name, ".webp", ".png", 1)
382 }
383 }
384}
385
386// handleDownloadFile handles file download
387func (b *Btelegram) handleDownload(rmsg *config.Message, message *tgbotapi.Message) error {
388 size := int64(0)
389 var url, name, text string
390 switch {
391 case message.Sticker != nil:
392 text, name, url = b.getDownloadInfo(message.Sticker.FileID, ".webp", true)
393 size = int64(message.Sticker.FileSize)
394 case message.Voice != nil:
395 text, name, url = b.getDownloadInfo(message.Voice.FileID, ".ogg", true)
396 size = message.Voice.FileSize
397 case message.Video != nil:
398 text, name, url = b.getDownloadInfo(message.Video.FileID, "", true)
399 size = message.Video.FileSize
400 case message.Audio != nil:
401 text, name, url = b.getDownloadInfo(message.Audio.FileID, "", true)
402 size = message.Audio.FileSize
403 case message.Document != nil:
404 _, _, url = b.getDownloadInfo(message.Document.FileID, "", false)
405 size = message.Document.FileSize
406 name = message.Document.FileName
407 text = " " + message.Document.FileName + " : " + url
408 case message.Photo != nil:
409 photos := message.Photo
410 size = int64(photos[len(photos)-1].FileSize)
411 text, name, url = b.getDownloadInfo(photos[len(photos)-1].FileID, "", true)
412 }
413
414 // if name is empty we didn't match a thing to download
415 if name == "" {
416 return nil
417 }
418 // use the URL instead of native upload
419 if b.GetBool("UseInsecureURL") {
420 b.Log.Debugf("Setting message text to :%s", text)
421 rmsg.Text += text
422 return nil
423 }
424 // if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
425 err := helper.HandleDownloadSize(b.Log, rmsg, name, int64(size), b.General)
426 if err != nil {
427 return err
428 }
429 data, err := helper.DownloadFile(url)
430 if err != nil {
431 return err
432 }
433
434 if strings.HasSuffix(name, ".tgs.webp") {
435 b.maybeConvertTgs(&name, data)
436 } else if strings.HasSuffix(name, ".webp") {
437 b.maybeConvertWebp(&name, data)
438 }
439
440 // rename .oga to .ogg https://github.com/42wim/matterbridge/issues/906#issuecomment-741793512
441 if strings.HasSuffix(name, ".oga") && message.Audio != nil {
442 name = strings.Replace(name, ".oga", ".ogg", 1)
443 }
444
445 helper.HandleDownloadData(b.Log, rmsg, name, message.Caption, "", data, b.General)
446 return nil
447}
448
449func (b *Btelegram) getDownloadInfo(id string, suffix string, urlpart bool) (string, string, string) {
450 url := b.getFileDirectURL(id)
451 name := ""
452 if urlpart {
453 urlPart := strings.Split(url, "/")
454 name = urlPart[len(urlPart)-1]
455 }
456 if suffix != "" && !strings.HasSuffix(name, suffix) && !strings.HasSuffix(name, ".webm") {
457 name += suffix
458 }
459 text := " " + url
460 return text, name, url
461}
462
463// handleDelete handles message deleting
464func (b *Btelegram) handleDelete(msg *config.Message, chatid int64) (string, error) {
465 if msg.ID == "" {
466 return "", nil
467 }
468
469 msgid, err := strconv.Atoi(msg.ID)
470 if err != nil {
471 return "", err
472 }
473
474 cfg := tgbotapi.NewDeleteMessage(chatid, msgid)
475 _, err = b.c.Request(cfg)
476
477 return "", err
478}
479
480// handleEdit handles message editing.
481func (b *Btelegram) handleEdit(msg *config.Message, chatid int64) (string, error) {
482 msgid, err := strconv.Atoi(msg.ID)
483 if err != nil {
484 return "", err
485 }
486 if strings.ToLower(b.GetString("MessageFormat")) == HTMLNick {
487 b.Log.Debug("Using mode HTML - nick only")
488 msg.Text = html.EscapeString(msg.Text)
489 }
490 m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text)
491 switch b.GetString("MessageFormat") {
492 case HTMLFormat:
493 b.Log.Debug("Using mode HTML")
494 m.ParseMode = tgbotapi.ModeHTML
495 case "Markdown":
496 b.Log.Debug("Using mode markdown")
497 m.ParseMode = tgbotapi.ModeMarkdown
498 case MarkdownV2:
499 b.Log.Debug("Using mode MarkdownV2")
500 m.ParseMode = MarkdownV2
501 }
502 if strings.ToLower(b.GetString("MessageFormat")) == HTMLNick {
503 b.Log.Debug("Using mode HTML - nick only")
504 m.ParseMode = tgbotapi.ModeHTML
505 }
506 _, err = b.c.Send(m)
507 if err != nil {
508 return "", err
509 }
510 return "", nil
511}
512
513// handleUploadFile handles native upload of files
514func (b *Btelegram) handleUploadFile(msg *config.Message, chatid int64, threadid int, parentID int) (string, error) {
515 var media []interface{}
516 equal := true
517 first := true
518 var prev string
519
520 for _, f := range msg.Extra["file"] {
521 fi, ok := f.(config.FileInfo)
522 if !ok {
523 continue
524 }
525
526 var ftype string
527
528 switch filepath.Ext(fi.Name) {
529 case ".jpg", ".jpe", ".png":
530 ftype = "image"
531 case ".mp4", ".m4v":
532 ftype = "video"
533 case ".mp3", ".oga", ".ogg", ".opus", ".flac":
534 ftype = "audio"
535 default:
536 ftype = "document"
537 }
538
539 if ftype == "document" {
540 equal = false
541 break
542 }
543
544 if first {
545 prev = ftype
546 equal = true
547 first = false
548 } else {
549 if prev != ftype {
550 equal = false
551 break
552 }
553 prev = ftype
554 }
555 }
556
557 for _, f := range msg.Extra["file"] {
558 fi := f.(config.FileInfo)
559 file := tgbotapi.FileBytes{
560 Name: fi.Name,
561 Bytes: *fi.Data,
562 }
563
564 if b.GetString("MessageFormat") == HTMLFormat {
565 fi.Comment = makeHTML(html.EscapeString(fi.Comment))
566 }
567
568 if !equal {
569 dc := tgbotapi.NewInputMediaDocument(file)
570 if fi.Comment != "" {
571 dc.Caption, dc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
572 }
573 media = append(media, dc)
574 continue
575 }
576
577 switch filepath.Ext(fi.Name) {
578 case ".jpg", ".jpe", ".png":
579 pc := tgbotapi.NewInputMediaPhoto(file)
580 if fi.Comment != "" {
581 pc.Caption, pc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
582 }
583 media = append(media, pc)
584 case ".mp4", ".m4v":
585 vc := tgbotapi.NewInputMediaVideo(file)
586 if fi.Comment != "" {
587 vc.Caption, vc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
588 }
589 media = append(media, vc)
590 case ".mp3", ".oga", ".ogg", ".opus", ".flac":
591 ac := tgbotapi.NewInputMediaAudio(file)
592 if fi.Comment != "" {
593 ac.Caption, ac.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
594 }
595 ac.Caption = fi.Name
596 media = append(media, ac)
597 default:
598 dc := tgbotapi.NewInputMediaDocument(file)
599 if fi.Comment != "" {
600 dc.Caption, dc.ParseMode = TGGetParseMode(b, msg.Username, fi.Comment)
601 }
602 media = append(media, dc)
603 }
604 }
605
606 return b.sendMediaFiles(msg, chatid, threadid, parentID, media)
607}
608
609func (b *Btelegram) handleQuote(message, quoteNick, quoteMessage string) string {
610 format := b.GetString("quoteformat")
611 if format == "" {
612 format = "{MESSAGE} (re @{QUOTENICK}: {QUOTEMESSAGE})"
613 }
614 quoteMessagelength := len([]rune(quoteMessage))
615 if b.GetInt("QuoteLengthLimit") != 0 && quoteMessagelength >= b.GetInt("QuoteLengthLimit") {
616 runes := []rune(quoteMessage)
617 quoteMessage = string(runes[0:b.GetInt("QuoteLengthLimit")])
618 if quoteMessagelength > b.GetInt("QuoteLengthLimit") {
619 quoteMessage += "..."
620 }
621 }
622 format = strings.ReplaceAll(format, "{MESSAGE}", message)
623 format = strings.ReplaceAll(format, "{QUOTENICK}", quoteNick)
624 format = strings.ReplaceAll(format, "{QUOTEMESSAGE}", quoteMessage)
625 return format
626}
627
628// handleEntities handles messageEntities
629func (b *Btelegram) handleEntities(rmsg *config.Message, message *tgbotapi.Message) {
630 if message.Entities == nil {
631 return
632 }
633
634 indexMovedBy := 0
635 prevLinkOffset := -1
636
637 for _, e := range message.Entities {
638
639 asRunes := utf16.Encode([]rune(rmsg.Text))
640
641 if e.Type == "text_link" {
642 offset := e.Offset + indexMovedBy
643 url, err := e.ParseURL()
644 if err != nil {
645 b.Log.Errorf("entity text_link url parse failed: %s", err)
646 continue
647 }
648 utfEncodedString := utf16.Encode([]rune(rmsg.Text))
649 if offset+e.Length > len(utfEncodedString) {
650 b.Log.Errorf("entity length is too long %d > %d", offset+e.Length, len(utfEncodedString))
651 continue
652 }
653 rmsg.Text = string(utf16.Decode(asRunes[:offset+e.Length])) + " (" + url.String() + ")" + string(utf16.Decode(asRunes[offset+e.Length:]))
654 indexMovedBy += len(url.String()) + 3
655 prevLinkOffset = e.Offset
656 }
657
658 if e.Offset == prevLinkOffset {
659 continue
660 }
661
662 if e.Type == "code" {
663 offset := e.Offset + indexMovedBy
664 rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "`" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "`" + string(utf16.Decode(asRunes[offset+e.Length:]))
665 indexMovedBy += 2
666 }
667
668 if e.Type == "pre" {
669 offset := e.Offset + indexMovedBy
670 rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "```\n" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "```\n" + string(utf16.Decode(asRunes[offset+e.Length:]))
671 indexMovedBy += 8
672 }
673
674 if e.Type == "bold" {
675 offset := e.Offset + indexMovedBy
676 rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "*" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "*" + string(utf16.Decode(asRunes[offset+e.Length:]))
677 indexMovedBy += 2
678 }
679 if e.Type == "italic" {
680 offset := e.Offset + indexMovedBy
681 rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "_" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "_" + string(utf16.Decode(asRunes[offset+e.Length:]))
682 indexMovedBy += 2
683 }
684 if e.Type == "strike" {
685 offset := e.Offset + indexMovedBy
686 rmsg.Text = string(utf16.Decode(asRunes[:offset])) + "~" + string(utf16.Decode(asRunes[offset:offset+e.Length])) + "~" + string(utf16.Decode(asRunes[offset+e.Length:]))
687 indexMovedBy += 2
688 }
689 }
690}
691