mirror of
https://github.com/cwinfo/matterbridge.git
synced 2025-06-26 07:29:24 +00:00
Add vk bridge (#1372)
* Add vk bridge * Vk bridge attachments * Vk bridge forwarded messages * Vk bridge sample config and code cleanup * Vk bridge add vendor * Vk bridge message edit * Vk bridge: fix fetching names of other bots * Vk bridge: code cleanup * Vk bridge: fix shadows declaration * Vk bridge: remove UseFileURL
This commit is contained in:
327
bridge/vk/vk.go
Normal file
327
bridge/vk/vk.go
Normal file
@ -0,0 +1,327 @@
|
||||
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
|
||||
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"))
|
||||
lp, err := longpoll.NewLongPoll(b.c, b.GetInt("GroupID"))
|
||||
if err != nil {
|
||||
b.Log.Debugf("%#v", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
lp.MessageNew(func(ctx context.Context, obj events.MessageNewObject) {
|
||||
b.handleMessage(obj.Message, false)
|
||||
})
|
||||
|
||||
b.Log.Info("Connection succeeded")
|
||||
|
||||
go func() {
|
||||
err := lp.Run()
|
||||
if err != nil {
|
||||
b.Log.Fatal("Enable longpoll in group management")
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bvk) Disconnect() error {
|
||||
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.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) {
|
||||
p, err := b.c.UploadMessagesPhoto(peerID, 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)
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user