Thumbnail

rani/matterbridge.git

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

Viewing file on branch master

1package bmumble
2
3import (
4 "fmt"
5 "mime"
6 "net/http"
7 "regexp"
8 "strings"
9
10 "github.com/matterbridge-org/matterbridge/bridge/config"
11 "github.com/mattn/godown"
12 "github.com/vincent-petithory/dataurl"
13)
14
15type MessagePart struct {
16 Text string
17 FileExtension string
18 Image []byte
19}
20
21func (b *Bmumble) decodeImage(uri string, parts *[]MessagePart) error {
22 // Decode the data:image/... URI
23 image, err := dataurl.DecodeString(uri)
24 if err != nil {
25 b.Log.WithError(err).Info("No image extracted")
26 return err
27 }
28 // Determine the file extensions for that image
29 ext, err := mime.ExtensionsByType(image.MediaType.ContentType())
30 if err != nil || len(ext) == 0 {
31 b.Log.WithError(err).Infof("No file extension registered for MIME type '%s'", image.MediaType.ContentType())
32 return err
33 }
34 // Add the image to the MessagePart slice
35 *parts = append(*parts, MessagePart{"", ext[0], image.Data})
36 return nil
37}
38
39func (b *Bmumble) tokenize(t *string) ([]MessagePart, error) {
40 // `^(.*?)` matches everything before the image
41 // `!\[[^\]]*\]\(` matches the `![alt](` part of markdown images
42 // `(data:image\/[^)]+)` matches the data: URI used by Mumble
43 // `\)` matches the closing parenthesis after the URI
44 // `(.*)$` matches the remaining text to be examined in the next iteration
45 p := regexp.MustCompile(`^(?ms)(.*?)!\[[^\]]*\]\((data:image\/[^)]+)\)(.*)$`)
46 remaining := *t
47 var parts []MessagePart
48 for {
49 tokens := p.FindStringSubmatch(remaining)
50 if tokens == nil {
51 // no match -> remaining string is non-image text
52 pre := strings.TrimSpace(remaining)
53 if len(pre) > 0 {
54 parts = append(parts, MessagePart{pre, "", nil})
55 }
56 return parts, nil
57 }
58
59 // tokens[1] is the text before the image
60 if len(tokens[1]) > 0 {
61 pre := strings.TrimSpace(tokens[1])
62 parts = append(parts, MessagePart{pre, "", nil})
63 }
64 // tokens[2] is the image URL
65 uri, err := dataurl.UnescapeToString(strings.TrimSpace(strings.ReplaceAll(tokens[2], " ", "")))
66 if err != nil {
67 b.Log.WithError(err).Info("URL unescaping failed")
68 remaining = strings.TrimSpace(tokens[3])
69 continue
70 }
71 err = b.decodeImage(uri, &parts)
72 if err != nil {
73 b.Log.WithError(err).Info("Decoding the image failed")
74 }
75 // tokens[3] is the text after the image, processed in the next iteration
76 remaining = strings.TrimSpace(tokens[3])
77 }
78}
79
80func (b *Bmumble) convertHTMLtoMarkdown(html string) ([]MessagePart, error) {
81 var sb strings.Builder
82 err := godown.Convert(&sb, strings.NewReader(html), nil)
83 if err != nil {
84 return nil, err
85 }
86 markdown := sb.String()
87 b.Log.Debugf("### to markdown: %s", markdown)
88 return b.tokenize(&markdown)
89}
90
91func (b *Bmumble) extractFiles(msg *config.Message) []config.Message {
92 var messages []config.Message
93 if msg.Extra == nil || len(msg.Extra["file"]) == 0 {
94 return messages
95 }
96 // Create a separate message for each file
97 for _, f := range msg.Extra["file"] {
98 fi := f.(config.FileInfo)
99 imsg := config.Message{
100 Channel: msg.Channel,
101 Username: msg.Username,
102 UserID: msg.UserID,
103 Account: msg.Account,
104 Protocol: msg.Protocol,
105 Timestamp: msg.Timestamp,
106 Event: "mumble_image",
107 }
108 // If no data is present for the file, send a link instead
109 if fi.Data == nil || len(*fi.Data) == 0 {
110 if len(fi.URL) > 0 {
111 imsg.Text = fmt.Sprintf(`<a href="%s">%s</a>`, fi.URL, fi.URL)
112 messages = append(messages, imsg)
113 } else {
114 b.Log.Infof("Not forwarding file without local data")
115 }
116 continue
117 }
118 mimeType := http.DetectContentType(*fi.Data)
119 // Mumble only supports images natively, send a link instead
120 if !strings.HasPrefix(mimeType, "image/") {
121 if len(fi.URL) > 0 {
122 imsg.Text = fmt.Sprintf(`<a href="%s">%s</a>`, fi.URL, fi.URL)
123 messages = append(messages, imsg)
124 } else {
125 b.Log.Infof("Not forwarding file of type %s", mimeType)
126 }
127 continue
128 }
129 mimeType = strings.TrimSpace(strings.Split(mimeType, ";")[0])
130 // Build data:image/...;base64,... style image URL and embed image directly into the message
131 du := dataurl.New(*fi.Data, mimeType)
132 dataURL, err := du.MarshalText()
133 if err != nil {
134 b.Log.WithError(err).Infof("Image Serialization into data URL failed (type: %s, length: %d)", mimeType, len(*fi.Data))
135 continue
136 }
137 imsg.Text = fmt.Sprintf(`<img src="%s"/>`, dataURL)
138 messages = append(messages, imsg)
139 }
140 // Remove files from original message
141 msg.Extra["file"] = nil
142 return messages
143}
144