Thumbnail

rani/matterbridge.git

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

Viewing file on branch master

1package bvk
2
3import (
4 "bytes"
5 "context"
6 "regexp"
7 "strconv"
8 "strings"
9 "time"
10
11 "github.com/matterbridge-org/matterbridge/bridge"
12 "github.com/matterbridge-org/matterbridge/bridge/config"
13 "github.com/matterbridge-org/matterbridge/bridge/helper"
14
15 "github.com/SevereCloud/vksdk/v2/api"
16 "github.com/SevereCloud/vksdk/v2/events"
17 longpoll "github.com/SevereCloud/vksdk/v2/longpoll-bot"
18 "github.com/SevereCloud/vksdk/v2/object"
19)
20
21const (
22 audioMessage = "audio_message"
23 document = "doc"
24 photo = "photo"
25 video = "video"
26 graffiti = "graffiti"
27 sticker = "sticker"
28 wall = "wall"
29)
30
31type user struct {
32 lastname, firstname, avatar string
33}
34
35type Bvk struct {
36 c *api.VK
37 lp *longpoll.LongPoll
38 usernamesMap map[int]user // cache of user names and avatar URLs
39 *bridge.Config
40}
41
42func New(cfg *bridge.Config) bridge.Bridger {
43 return &Bvk{usernamesMap: make(map[int]user), Config: cfg}
44}
45
46func (b *Bvk) Connect() error {
47 b.Log.Info("Connecting")
48 b.c = api.NewVK(b.GetString("Token"))
49
50 var err error
51 b.lp, err = longpoll.NewLongPollCommunity(b.c)
52 if err != nil {
53 b.Log.Debugf("%#v", err)
54
55 return err
56 }
57
58 b.lp.MessageNew(func(ctx context.Context, obj events.MessageNewObject) {
59 b.handleMessage(obj.Message, false)
60 })
61
62 b.Log.Info("Connection succeeded")
63
64 go func() {
65 err := b.lp.Run()
66 if err != nil {
67 b.Log.WithError(err).Fatal("Enable longpoll in group management")
68 }
69 }()
70
71 return nil
72}
73
74func (b *Bvk) Disconnect() error {
75 b.lp.Shutdown()
76
77 return nil
78}
79
80func (b *Bvk) JoinChannel(channel config.ChannelInfo) error {
81 return nil
82}
83
84func (b *Bvk) Send(msg config.Message) (string, error) {
85 b.Log.Debugf("=> Receiving %#v", msg)
86
87 peerID, err := strconv.Atoi(msg.Channel)
88 if err != nil {
89 return "", err
90 }
91
92 params := api.Params{}
93
94 text := msg.Username + msg.Text
95
96 if msg.Extra != nil {
97 if len(msg.Extra["file"]) > 0 {
98 // generate attachments string
99 attachment, urls := b.uploadFiles(msg.Extra, peerID)
100 params["attachment"] = attachment
101 text += urls
102 }
103 }
104
105 params["message"] = text
106
107 if msg.ID == "" {
108 // New message
109 params["random_id"] = time.Now().Unix()
110 params["peer_ids"] = msg.Channel
111
112 res, e := b.c.MessagesSendPeerIDs(params)
113 if e != nil {
114 return "", err
115 }
116
117 return strconv.Itoa(res[0].ConversationMessageID), nil
118 }
119 // Edit message
120 messageID, err := strconv.ParseInt(msg.ID, 10, 64)
121 if err != nil {
122 return "", err
123 }
124
125 params["peer_id"] = peerID
126 params["conversation_message_id"] = messageID
127
128 _, err = b.c.MessagesEdit(params)
129 if err != nil {
130 return "", err
131 }
132
133 return msg.ID, nil
134}
135
136func (b *Bvk) getUser(id int) user {
137 u, found := b.usernamesMap[id]
138 if !found {
139 b.Log.Debug("Fetching username for ", id)
140
141 if id >= 0 {
142 result, _ := b.c.UsersGet(api.Params{
143 "user_ids": id,
144 "fields": "photo_200",
145 })
146
147 resUser := result[0]
148 u = user{lastname: resUser.LastName, firstname: resUser.FirstName, avatar: resUser.Photo200}
149 b.usernamesMap[id] = u
150 } else {
151 result, _ := b.c.GroupsGetByID(api.Params{
152 "group_id": id * -1,
153 })
154
155 resGroup := result[0]
156 u = user{lastname: resGroup.Name, avatar: resGroup.Photo200}
157 }
158 }
159
160 return u
161}
162
163func (b *Bvk) handleMessage(msg object.MessagesMessage, isFwd bool) {
164 b.Log.Debug("ChatID: ", msg.PeerID)
165 // fetch user info
166 u := b.getUser(msg.FromID)
167
168 rmsg := config.Message{
169 Text: msg.Text,
170 Username: u.firstname + " " + u.lastname,
171 Avatar: u.avatar,
172 Channel: strconv.Itoa(msg.PeerID),
173 Account: b.Account,
174 UserID: strconv.Itoa(msg.FromID),
175 ID: strconv.Itoa(msg.ConversationMessageID),
176 Extra: make(map[string][]interface{}),
177 }
178
179 if msg.ReplyMessage != nil {
180 ur := b.getUser(msg.ReplyMessage.FromID)
181 rmsg.Text = "Re: " + ur.firstname + " " + ur.lastname + "\n" + rmsg.Text
182 }
183
184 if isFwd {
185 rmsg.Username = "Fwd: " + rmsg.Username
186 }
187
188 if len(msg.Attachments) > 0 {
189 urls, text := b.getFiles(msg.Attachments)
190
191 if text != "" {
192 rmsg.Text += "\n" + text
193 }
194
195 // download
196 b.downloadFiles(&rmsg, urls)
197 }
198
199 if len(msg.FwdMessages) > 0 {
200 rmsg.Text += strconv.Itoa(len(msg.FwdMessages)) + " forwarded messages"
201 }
202
203 b.Remote <- rmsg
204
205 if len(msg.FwdMessages) > 0 {
206 // recursive processing of forwarded messages
207 for _, m := range msg.FwdMessages {
208 m.PeerID = msg.PeerID
209 b.handleMessage(m, true)
210 }
211 }
212}
213
214func (b *Bvk) uploadFiles(extra map[string][]interface{}, peerID int) (string, string) {
215 var attachments []string
216 text := ""
217
218 for _, f := range extra["file"] {
219 fi := f.(config.FileInfo)
220
221 if fi.Comment != "" {
222 text += fi.Comment + "\n"
223 }
224 a, err := b.uploadFile(fi, peerID)
225 if err != nil {
226 b.Log.WithError(err).Error("File upload error ", fi.Name)
227 }
228
229 attachments = append(attachments, a)
230 }
231
232 return strings.Join(attachments, ","), text
233}
234
235func (b *Bvk) uploadFile(file config.FileInfo, peerID int) (string, error) {
236 r := bytes.NewReader(*file.Data)
237
238 photoRE := regexp.MustCompile(".(jpg|jpe|png)$")
239 if photoRE.MatchString(file.Name) {
240 // BUG(VK): for community chat peerID=0
241 p, err := b.c.UploadMessagesPhoto(0, r)
242 if err != nil {
243 return "", err
244 }
245
246 return photo + strconv.Itoa(p[0].OwnerID) + "_" + strconv.Itoa(p[0].ID), nil
247 }
248
249 var doctype string
250 if strings.Contains(file.Name, ".ogg") {
251 doctype = audioMessage
252 } else {
253 doctype = document
254 }
255
256 doc, err := b.c.UploadMessagesDoc(peerID, doctype, file.Name, "", r)
257 if err != nil {
258 return "", err
259 }
260
261 switch doc.Type {
262 case audioMessage:
263 return document + strconv.Itoa(doc.AudioMessage.OwnerID) + "_" + strconv.Itoa(doc.AudioMessage.ID), nil
264 case document:
265 return document + strconv.Itoa(doc.Doc.OwnerID) + "_" + strconv.Itoa(doc.Doc.ID), nil
266 }
267
268 return "", nil
269}
270
271func (b *Bvk) getFiles(attachments []object.MessagesMessageAttachment) ([]string, string) {
272 var urls []string
273 var text []string
274
275 for _, a := range attachments {
276 switch a.Type {
277 case photo:
278 var resolution float64 = 0
279 url := a.Photo.Sizes[0].URL
280 for _, size := range a.Photo.Sizes {
281 r := size.Height * size.Width
282 if resolution < r {
283 resolution = r
284 url = size.URL
285 }
286 }
287
288 urls = append(urls, url)
289
290 case document:
291 urls = append(urls, a.Doc.URL)
292
293 case graffiti:
294 urls = append(urls, a.Graffiti.URL)
295
296 case audioMessage:
297 urls = append(urls, a.AudioMessage.DocsDocPreviewAudioMessage.LinkOgg)
298
299 case sticker:
300 var resolution float64 = 0
301 url := a.Sticker.Images[0].URL
302 for _, size := range a.Sticker.Images {
303 r := size.Height * size.Width
304 if resolution < r {
305 resolution = r
306 url = size.URL
307 }
308 }
309 urls = append(urls, url+".png")
310 case video:
311 text = append(text, "https://vk.com/video"+strconv.Itoa(a.Video.OwnerID)+"_"+strconv.Itoa(a.Video.ID))
312
313 case wall:
314 text = append(text, "https://vk.com/wall"+strconv.Itoa(a.Wall.FromID)+"_"+strconv.Itoa(a.Wall.ID))
315
316 default:
317 text = append(text, "This attachment is not supported ("+a.Type+")")
318 }
319 }
320
321 return urls, strings.Join(text, "\n")
322}
323
324func (b *Bvk) downloadFiles(rmsg *config.Message, urls []string) {
325 for _, url := range urls {
326 data, err := helper.DownloadFile(url)
327 if err == nil {
328 urlPart := strings.Split(url, "/")
329 name := strings.Split(urlPart[len(urlPart)-1], "?")[0]
330 helper.HandleDownloadData(b.Log, rmsg, name, "", url, data, b.General)
331 }
332 }
333}
334