forked from jshiffer/matterbridge
334 lines
7.1 KiB
Go
334 lines
7.1 KiB
Go
package bvk
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/42wim/matterbridge/bridge"
|
|
"github.com/42wim/matterbridge/bridge/config"
|
|
"github.com/42wim/matterbridge/bridge/helper"
|
|
|
|
"github.com/SevereCloud/vksdk/v2/api"
|
|
"github.com/SevereCloud/vksdk/v2/events"
|
|
longpoll "github.com/SevereCloud/vksdk/v2/longpoll-bot"
|
|
"github.com/SevereCloud/vksdk/v2/object"
|
|
)
|
|
|
|
const (
|
|
audioMessage = "audio_message"
|
|
document = "doc"
|
|
photo = "photo"
|
|
video = "video"
|
|
graffiti = "graffiti"
|
|
sticker = "sticker"
|
|
wall = "wall"
|
|
)
|
|
|
|
type user struct {
|
|
lastname, firstname, avatar string
|
|
}
|
|
|
|
type Bvk struct {
|
|
c *api.VK
|
|
lp *longpoll.LongPoll
|
|
usernamesMap map[int]user // cache of user names and avatar URLs
|
|
*bridge.Config
|
|
}
|
|
|
|
func New(cfg *bridge.Config) bridge.Bridger {
|
|
return &Bvk{usernamesMap: make(map[int]user), Config: cfg}
|
|
}
|
|
|
|
func (b *Bvk) Connect() error {
|
|
b.Log.Info("Connecting")
|
|
b.c = api.NewVK(b.GetString("Token"))
|
|
|
|
var err error
|
|
b.lp, err = longpoll.NewLongPollCommunity(b.c)
|
|
if err != nil {
|
|
b.Log.Debugf("%#v", err)
|
|
|
|
return err
|
|
}
|
|
|
|
b.lp.MessageNew(func(ctx context.Context, obj events.MessageNewObject) {
|
|
b.handleMessage(obj.Message, false)
|
|
})
|
|
|
|
b.Log.Info("Connection succeeded")
|
|
|
|
go func() {
|
|
err := b.lp.Run()
|
|
if err != nil {
|
|
b.Log.WithError(err).Fatal("Enable longpoll in group management")
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Bvk) Disconnect() error {
|
|
b.lp.Shutdown()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Bvk) JoinChannel(channel config.ChannelInfo) error {
|
|
return nil
|
|
}
|
|
|
|
func (b *Bvk) Send(msg config.Message) (string, error) {
|
|
b.Log.Debugf("=> Receiving %#v", msg)
|
|
|
|
peerID, err := strconv.Atoi(msg.Channel)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
params := api.Params{}
|
|
|
|
text := msg.Username + msg.Text
|
|
|
|
if msg.Extra != nil {
|
|
if len(msg.Extra["file"]) > 0 {
|
|
// generate attachments string
|
|
attachment, urls := b.uploadFiles(msg.Extra, peerID)
|
|
params["attachment"] = attachment
|
|
text += urls
|
|
}
|
|
}
|
|
|
|
params["message"] = text
|
|
|
|
if msg.ID == "" {
|
|
// New message
|
|
params["random_id"] = time.Now().Unix()
|
|
params["peer_ids"] = msg.Channel
|
|
|
|
res, e := b.c.MessagesSendPeerIDs(params)
|
|
if e != nil {
|
|
return "", err
|
|
}
|
|
|
|
return strconv.Itoa(res[0].ConversationMessageID), nil
|
|
}
|
|
// Edit message
|
|
messageID, err := strconv.ParseInt(msg.ID, 10, 64)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
params["peer_id"] = peerID
|
|
params["conversation_message_id"] = messageID
|
|
|
|
_, err = b.c.MessagesEdit(params)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return msg.ID, nil
|
|
}
|
|
|
|
func (b *Bvk) getUser(id int) user {
|
|
u, found := b.usernamesMap[id]
|
|
if !found {
|
|
b.Log.Debug("Fetching username for ", id)
|
|
|
|
if id >= 0 {
|
|
result, _ := b.c.UsersGet(api.Params{
|
|
"user_ids": id,
|
|
"fields": "photo_200",
|
|
})
|
|
|
|
resUser := result[0]
|
|
u = user{lastname: resUser.LastName, firstname: resUser.FirstName, avatar: resUser.Photo200}
|
|
b.usernamesMap[id] = u
|
|
} else {
|
|
result, _ := b.c.GroupsGetByID(api.Params{
|
|
"group_id": id * -1,
|
|
})
|
|
|
|
resGroup := result[0]
|
|
u = user{lastname: resGroup.Name, avatar: resGroup.Photo200}
|
|
}
|
|
}
|
|
|
|
return u
|
|
}
|
|
|
|
func (b *Bvk) handleMessage(msg object.MessagesMessage, isFwd bool) {
|
|
b.Log.Debug("ChatID: ", msg.PeerID)
|
|
// fetch user info
|
|
u := b.getUser(msg.FromID)
|
|
|
|
rmsg := config.Message{
|
|
Text: msg.Text,
|
|
Username: u.firstname + " " + u.lastname,
|
|
Avatar: u.avatar,
|
|
Channel: strconv.Itoa(msg.PeerID),
|
|
Account: b.Account,
|
|
UserID: strconv.Itoa(msg.FromID),
|
|
ID: strconv.Itoa(msg.ConversationMessageID),
|
|
Extra: make(map[string][]interface{}),
|
|
}
|
|
|
|
if msg.ReplyMessage != nil {
|
|
ur := b.getUser(msg.ReplyMessage.FromID)
|
|
rmsg.Text = "Re: " + ur.firstname + " " + ur.lastname + "\n" + rmsg.Text
|
|
}
|
|
|
|
if isFwd {
|
|
rmsg.Username = "Fwd: " + rmsg.Username
|
|
}
|
|
|
|
if len(msg.Attachments) > 0 {
|
|
urls, text := b.getFiles(msg.Attachments)
|
|
|
|
if text != "" {
|
|
rmsg.Text += "\n" + text
|
|
}
|
|
|
|
// download
|
|
b.downloadFiles(&rmsg, urls)
|
|
}
|
|
|
|
if len(msg.FwdMessages) > 0 {
|
|
rmsg.Text += strconv.Itoa(len(msg.FwdMessages)) + " forwarded messages"
|
|
}
|
|
|
|
b.Remote <- rmsg
|
|
|
|
if len(msg.FwdMessages) > 0 {
|
|
// recursive processing of forwarded messages
|
|
for _, m := range msg.FwdMessages {
|
|
m.PeerID = msg.PeerID
|
|
b.handleMessage(m, true)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *Bvk) uploadFiles(extra map[string][]interface{}, peerID int) (string, string) {
|
|
var attachments []string
|
|
text := ""
|
|
|
|
for _, f := range extra["file"] {
|
|
fi := f.(config.FileInfo)
|
|
|
|
if fi.Comment != "" {
|
|
text += fi.Comment + "\n"
|
|
}
|
|
a, err := b.uploadFile(fi, peerID)
|
|
if err != nil {
|
|
b.Log.WithError(err).Error("File upload error ", fi.Name)
|
|
}
|
|
|
|
attachments = append(attachments, a)
|
|
}
|
|
|
|
return strings.Join(attachments, ","), text
|
|
}
|
|
|
|
func (b *Bvk) uploadFile(file config.FileInfo, peerID int) (string, error) {
|
|
r := bytes.NewReader(*file.Data)
|
|
|
|
photoRE := regexp.MustCompile(".(jpg|jpe|png)$")
|
|
if photoRE.MatchString(file.Name) {
|
|
// BUG(VK): for community chat peerID=0
|
|
p, err := b.c.UploadMessagesPhoto(0, r)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return photo + strconv.Itoa(p[0].OwnerID) + "_" + strconv.Itoa(p[0].ID), nil
|
|
}
|
|
|
|
var doctype string
|
|
if strings.Contains(file.Name, ".ogg") {
|
|
doctype = audioMessage
|
|
} else {
|
|
doctype = document
|
|
}
|
|
|
|
doc, err := b.c.UploadMessagesDoc(peerID, doctype, file.Name, "", r)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
switch doc.Type {
|
|
case audioMessage:
|
|
return document + strconv.Itoa(doc.AudioMessage.OwnerID) + "_" + strconv.Itoa(doc.AudioMessage.ID), nil
|
|
case document:
|
|
return document + strconv.Itoa(doc.Doc.OwnerID) + "_" + strconv.Itoa(doc.Doc.ID), nil
|
|
}
|
|
|
|
return "", nil
|
|
}
|
|
|
|
func (b *Bvk) getFiles(attachments []object.MessagesMessageAttachment) ([]string, string) {
|
|
var urls []string
|
|
var text []string
|
|
|
|
for _, a := range attachments {
|
|
switch a.Type {
|
|
case photo:
|
|
var resolution float64 = 0
|
|
url := a.Photo.Sizes[0].URL
|
|
for _, size := range a.Photo.Sizes {
|
|
r := size.Height * size.Width
|
|
if resolution < r {
|
|
resolution = r
|
|
url = size.URL
|
|
}
|
|
}
|
|
|
|
urls = append(urls, url)
|
|
|
|
case document:
|
|
urls = append(urls, a.Doc.URL)
|
|
|
|
case graffiti:
|
|
urls = append(urls, a.Graffiti.URL)
|
|
|
|
case audioMessage:
|
|
urls = append(urls, a.AudioMessage.DocsDocPreviewAudioMessage.LinkOgg)
|
|
|
|
case sticker:
|
|
var resolution float64 = 0
|
|
url := a.Sticker.Images[0].URL
|
|
for _, size := range a.Sticker.Images {
|
|
r := size.Height * size.Width
|
|
if resolution < r {
|
|
resolution = r
|
|
url = size.URL
|
|
}
|
|
}
|
|
urls = append(urls, url+".png")
|
|
case video:
|
|
text = append(text, "https://vk.com/video"+strconv.Itoa(a.Video.OwnerID)+"_"+strconv.Itoa(a.Video.ID))
|
|
|
|
case wall:
|
|
text = append(text, "https://vk.com/wall"+strconv.Itoa(a.Wall.FromID)+"_"+strconv.Itoa(a.Wall.ID))
|
|
|
|
default:
|
|
text = append(text, "This attachment is not supported ("+a.Type+")")
|
|
}
|
|
}
|
|
|
|
return urls, strings.Join(text, "\n")
|
|
}
|
|
|
|
func (b *Bvk) downloadFiles(rmsg *config.Message, urls []string) {
|
|
for _, url := range urls {
|
|
data, err := helper.DownloadFile(url)
|
|
if err == nil {
|
|
urlPart := strings.Split(url, "/")
|
|
name := strings.Split(urlPart[len(urlPart)-1], "?")[0]
|
|
helper.HandleDownloadData(b.Log, rmsg, name, "", url, data, b.General)
|
|
}
|
|
}
|
|
}
|