2016-09-05 16:34:37 +02:00
|
|
|
package bslack
|
|
|
|
|
|
|
|
import (
|
2017-07-15 16:49:47 +02:00
|
|
|
"errors"
|
2016-09-05 16:34:37 +02:00
|
|
|
"strings"
|
2018-03-04 23:52:14 +01:00
|
|
|
"sync"
|
2018-03-22 22:28:27 +01:00
|
|
|
|
|
|
|
"github.com/42wim/matterbridge/bridge"
|
|
|
|
"github.com/42wim/matterbridge/bridge/config"
|
|
|
|
"github.com/42wim/matterbridge/bridge/helper"
|
|
|
|
"github.com/42wim/matterbridge/matterhook"
|
2018-08-18 00:12:05 +02:00
|
|
|
"github.com/hashicorp/golang-lru"
|
2018-03-22 22:28:27 +01:00
|
|
|
"github.com/nlopes/slack"
|
2018-05-27 21:50:00 +02:00
|
|
|
"github.com/rs/xid"
|
2016-09-05 16:34:37 +02:00
|
|
|
)
|
|
|
|
|
2016-09-18 19:21:15 +02:00
|
|
|
type Bslack struct {
|
2018-07-13 23:23:11 +02:00
|
|
|
mh *matterhook.Client
|
|
|
|
sc *slack.Client
|
|
|
|
rtm *slack.RTM
|
|
|
|
Users []slack.User
|
|
|
|
Usergroups []slack.UserGroup
|
|
|
|
si *slack.Info
|
|
|
|
channels []slack.Channel
|
2018-08-18 00:12:05 +02:00
|
|
|
cache *lru.Cache
|
2018-07-13 23:23:11 +02:00
|
|
|
UseChannelID bool
|
|
|
|
uuid string
|
2018-03-04 23:52:14 +01:00
|
|
|
*bridge.Config
|
|
|
|
sync.RWMutex
|
2016-09-05 16:34:37 +02:00
|
|
|
}
|
|
|
|
|
2018-02-27 00:33:21 +01:00
|
|
|
const messageDeleted = "message_deleted"
|
2016-09-05 16:34:37 +02:00
|
|
|
|
2018-03-04 23:52:14 +01:00
|
|
|
func New(cfg *bridge.Config) bridge.Bridger {
|
2018-08-18 00:12:05 +02:00
|
|
|
b := &Bslack{Config: cfg, uuid: xid.New().String()}
|
|
|
|
b.cache, _ = lru.New(5000)
|
|
|
|
return b
|
2016-09-05 16:34:37 +02:00
|
|
|
}
|
|
|
|
|
2016-09-18 19:21:15 +02:00
|
|
|
func (b *Bslack) Command(cmd string) string {
|
2016-09-05 16:34:37 +02:00
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2016-09-18 19:21:15 +02:00
|
|
|
func (b *Bslack) Connect() error {
|
2018-03-04 23:52:14 +01:00
|
|
|
b.RLock()
|
|
|
|
defer b.RUnlock()
|
|
|
|
if b.GetString("WebhookBindAddress") != "" {
|
|
|
|
if b.GetString("WebhookURL") != "" {
|
2018-02-27 00:33:21 +01:00
|
|
|
b.Log.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)")
|
2018-03-04 23:52:14 +01:00
|
|
|
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
|
|
|
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
|
|
|
BindAddress: b.GetString("WebhookBindAddress")})
|
|
|
|
} else if b.GetString("Token") != "" {
|
2018-02-27 00:33:21 +01:00
|
|
|
b.Log.Info("Connecting using token (sending)")
|
2018-03-04 23:52:14 +01:00
|
|
|
b.sc = slack.New(b.GetString("Token"))
|
2017-07-15 16:49:47 +02:00
|
|
|
b.rtm = b.sc.NewRTM()
|
|
|
|
go b.rtm.ManageConnection()
|
2018-02-27 00:33:21 +01:00
|
|
|
b.Log.Info("Connecting using webhookbindaddress (receiving)")
|
2018-03-04 23:52:14 +01:00
|
|
|
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
|
|
|
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
|
|
|
BindAddress: b.GetString("WebhookBindAddress")})
|
2017-07-15 16:49:47 +02:00
|
|
|
} else {
|
2018-02-27 00:33:21 +01:00
|
|
|
b.Log.Info("Connecting using webhookbindaddress (receiving)")
|
2018-03-04 23:52:14 +01:00
|
|
|
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
|
|
|
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
|
|
|
BindAddress: b.GetString("WebhookBindAddress")})
|
2017-07-15 16:49:47 +02:00
|
|
|
}
|
|
|
|
go b.handleSlack()
|
|
|
|
return nil
|
|
|
|
}
|
2018-03-04 23:52:14 +01:00
|
|
|
if b.GetString("WebhookURL") != "" {
|
2018-02-27 00:33:21 +01:00
|
|
|
b.Log.Info("Connecting using webhookurl (sending)")
|
2018-03-04 23:52:14 +01:00
|
|
|
b.mh = matterhook.New(b.GetString("WebhookURL"),
|
|
|
|
matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"),
|
2017-07-15 16:49:47 +02:00
|
|
|
DisableServer: true})
|
2018-03-04 23:52:14 +01:00
|
|
|
if b.GetString("Token") != "" {
|
2018-02-27 00:33:21 +01:00
|
|
|
b.Log.Info("Connecting using token (receiving)")
|
2018-03-04 23:52:14 +01:00
|
|
|
b.sc = slack.New(b.GetString("Token"))
|
2017-07-15 16:49:47 +02:00
|
|
|
b.rtm = b.sc.NewRTM()
|
|
|
|
go b.rtm.ManageConnection()
|
|
|
|
go b.handleSlack()
|
|
|
|
}
|
2018-03-04 23:52:14 +01:00
|
|
|
} else if b.GetString("Token") != "" {
|
2018-02-27 00:33:21 +01:00
|
|
|
b.Log.Info("Connecting using token (sending and receiving)")
|
2018-03-04 23:52:14 +01:00
|
|
|
b.sc = slack.New(b.GetString("Token"))
|
2016-10-25 23:29:32 +02:00
|
|
|
b.rtm = b.sc.NewRTM()
|
|
|
|
go b.rtm.ManageConnection()
|
2017-07-15 16:49:47 +02:00
|
|
|
go b.handleSlack()
|
|
|
|
}
|
2018-03-04 23:52:14 +01:00
|
|
|
if b.GetString("WebhookBindAddress") == "" && b.GetString("WebhookURL") == "" && b.GetString("Token") == "" {
|
2018-02-24 23:35:53 +01:00
|
|
|
return errors.New("no connection method found. See that you have WebhookBindAddress, WebhookURL or Token configured")
|
2016-09-05 16:34:37 +02:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-02-14 21:12:02 +01:00
|
|
|
func (b *Bslack) Disconnect() error {
|
2018-03-04 23:52:14 +01:00
|
|
|
return b.rtm.Disconnect()
|
2017-02-14 21:12:02 +01:00
|
|
|
}
|
|
|
|
|
2017-08-12 14:51:41 +02:00
|
|
|
func (b *Bslack) JoinChannel(channel config.ChannelInfo) error {
|
2018-07-13 23:23:11 +02:00
|
|
|
// use ID:channelid and resolve it to the actual name
|
|
|
|
idcheck := strings.Split(channel.Name, "ID:")
|
|
|
|
if len(idcheck) > 1 {
|
|
|
|
b.UseChannelID = true
|
|
|
|
ch, err := b.sc.GetChannelInfo(idcheck[1])
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
channel.Name = ch.Name
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-20 12:20:44 +02:00
|
|
|
// we can only join channels using the API
|
2018-01-02 14:31:44 +01:00
|
|
|
if b.sc != nil {
|
2018-03-04 23:52:14 +01:00
|
|
|
if strings.HasPrefix(b.GetString("Token"), "xoxb") {
|
2017-03-27 20:15:05 +02:00
|
|
|
// TODO check if bot has already joined channel
|
|
|
|
return nil
|
|
|
|
}
|
2017-08-12 14:51:41 +02:00
|
|
|
_, err := b.sc.JoinChannel(channel.Name)
|
2016-09-30 23:15:35 +02:00
|
|
|
if err != nil {
|
2018-03-12 21:14:13 +01:00
|
|
|
switch err.Error() {
|
|
|
|
case "name_taken", "restricted_action":
|
|
|
|
case "default":
|
|
|
|
{
|
|
|
|
return err
|
|
|
|
}
|
2017-04-17 18:01:24 +02:00
|
|
|
}
|
2016-09-20 12:20:44 +02:00
|
|
|
}
|
2016-09-18 19:21:15 +02:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-08-27 22:59:37 +02:00
|
|
|
func (b *Bslack) Send(msg config.Message) (string, error) {
|
2018-02-28 22:23:29 +01:00
|
|
|
b.Log.Debugf("=> Receiving %#v", msg)
|
2018-02-24 23:35:53 +01:00
|
|
|
|
|
|
|
// Make a action /me of the message
|
2017-07-30 17:48:23 +02:00
|
|
|
if msg.Event == config.EVENT_USER_ACTION {
|
|
|
|
msg.Text = "_" + msg.Text + "_"
|
|
|
|
}
|
2018-02-24 23:35:53 +01:00
|
|
|
|
|
|
|
// Use webhook to send the message
|
2018-03-04 23:52:14 +01:00
|
|
|
if b.GetString("WebhookURL") != "" {
|
2018-02-24 23:35:53 +01:00
|
|
|
return b.sendWebhook(msg)
|
|
|
|
}
|
|
|
|
|
2018-07-13 23:23:11 +02:00
|
|
|
channelID := b.getChannelID(msg.Channel)
|
2018-02-24 23:35:53 +01:00
|
|
|
|
|
|
|
// Delete message
|
|
|
|
if msg.Event == config.EVENT_MSG_DELETE {
|
|
|
|
// some protocols echo deletes, but with empty ID
|
|
|
|
if msg.ID == "" {
|
|
|
|
return "", nil
|
2018-02-03 01:11:11 +01:00
|
|
|
}
|
2018-02-24 23:35:53 +01:00
|
|
|
// we get a "slack <ID>", split it
|
|
|
|
ts := strings.Fields(msg.ID)
|
2018-07-13 23:23:11 +02:00
|
|
|
_, _, err := b.sc.DeleteMessage(channelID, ts[1])
|
2018-02-24 23:35:53 +01:00
|
|
|
if err != nil {
|
|
|
|
return msg.ID, err
|
2018-02-23 00:49:32 +01:00
|
|
|
}
|
2018-02-24 23:35:53 +01:00
|
|
|
return msg.ID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepend nick if configured
|
2018-03-04 23:52:14 +01:00
|
|
|
if b.GetBool("PrefixMessagesWithNick") {
|
2018-02-24 23:35:53 +01:00
|
|
|
msg.Text = msg.Username + msg.Text
|
|
|
|
}
|
2018-02-03 01:11:11 +01:00
|
|
|
|
2018-02-24 23:35:53 +01:00
|
|
|
// Edit message if we have an ID
|
|
|
|
if msg.ID != "" {
|
|
|
|
ts := strings.Fields(msg.ID)
|
2018-07-13 23:23:11 +02:00
|
|
|
_, _, _, err := b.sc.UpdateMessage(channelID, ts[1], msg.Text)
|
2016-09-05 16:34:37 +02:00
|
|
|
if err != nil {
|
2018-02-24 23:35:53 +01:00
|
|
|
return msg.ID, err
|
2016-09-05 16:34:37 +02:00
|
|
|
}
|
2018-02-24 23:35:53 +01:00
|
|
|
return msg.ID, nil
|
2016-10-08 21:57:03 +02:00
|
|
|
}
|
2018-02-24 23:35:53 +01:00
|
|
|
|
|
|
|
// create slack new post parameters
|
2016-11-05 01:11:28 +01:00
|
|
|
np := slack.NewPostMessageParameters()
|
2018-03-04 23:52:14 +01:00
|
|
|
if b.GetBool("PrefixMessagesWithNick") {
|
2016-11-05 01:11:28 +01:00
|
|
|
np.AsUser = true
|
|
|
|
}
|
2018-02-24 23:35:53 +01:00
|
|
|
np.Username = msg.Username
|
|
|
|
np.LinkNames = 1 // replace mentions
|
2018-03-04 23:52:14 +01:00
|
|
|
np.IconURL = config.GetIconURL(&msg, b.GetString("iconurl"))
|
2016-11-06 00:46:32 +01:00
|
|
|
if msg.Avatar != "" {
|
|
|
|
np.IconURL = msg.Avatar
|
|
|
|
}
|
2018-02-24 23:35:53 +01:00
|
|
|
// add a callback ID so we can see we created it
|
2018-05-27 21:50:00 +02:00
|
|
|
np.Attachments = append(np.Attachments, slack.Attachment{CallbackID: "matterbridge_" + b.uuid})
|
2018-02-24 23:35:53 +01:00
|
|
|
// add file attachments
|
2017-09-18 23:51:27 +02:00
|
|
|
np.Attachments = append(np.Attachments, b.createAttach(msg.Extra)...)
|
2018-02-24 23:35:53 +01:00
|
|
|
// add slack attachments (from another slack bridge)
|
2018-02-23 00:49:32 +01:00
|
|
|
if len(msg.Extra["slack_attachment"]) > 0 {
|
|
|
|
for _, attach := range msg.Extra["slack_attachment"] {
|
|
|
|
np.Attachments = append(np.Attachments, attach.([]slack.Attachment)...)
|
|
|
|
}
|
|
|
|
}
|
2017-09-18 23:51:27 +02:00
|
|
|
|
2018-02-24 23:35:53 +01:00
|
|
|
// Upload a file if it exists
|
2017-11-03 23:10:16 +01:00
|
|
|
if msg.Extra != nil {
|
2018-02-03 01:11:11 +01:00
|
|
|
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
2018-07-13 23:23:11 +02:00
|
|
|
b.sc.PostMessage(channelID, rmsg.Username+rmsg.Text, np)
|
2018-02-03 01:11:11 +01:00
|
|
|
}
|
2017-11-03 23:10:16 +01:00
|
|
|
// check if we have files to upload (from slack, telegram or mattermost)
|
|
|
|
if len(msg.Extra["file"]) > 0 {
|
2018-07-13 23:23:11 +02:00
|
|
|
b.handleUploadFile(&msg, channelID)
|
2017-11-03 23:10:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-24 23:35:53 +01:00
|
|
|
// Post normal message
|
2018-07-13 23:23:11 +02:00
|
|
|
_, id, err := b.sc.PostMessage(channelID, msg.Text, np)
|
2017-08-28 20:29:02 +02:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return "slack " + id, nil
|
2016-09-05 16:34:37 +02:00
|
|
|
}
|
|
|
|
|
2018-03-04 23:52:14 +01:00
|
|
|
func (b *Bslack) Reload(cfg *bridge.Config) (string, error) {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
2017-09-21 22:35:21 +02:00
|
|
|
func (b *Bslack) createAttach(extra map[string][]interface{}) []slack.Attachment {
|
2017-09-18 23:51:27 +02:00
|
|
|
var attachs []slack.Attachment
|
2017-09-21 22:35:21 +02:00
|
|
|
for _, v := range extra["attachments"] {
|
2017-09-19 00:04:27 +02:00
|
|
|
entry := v.(map[string]interface{})
|
|
|
|
s := slack.Attachment{}
|
|
|
|
s.Fallback = entry["fallback"].(string)
|
|
|
|
s.Color = entry["color"].(string)
|
|
|
|
s.Pretext = entry["pretext"].(string)
|
|
|
|
s.AuthorName = entry["author_name"].(string)
|
|
|
|
s.AuthorLink = entry["author_link"].(string)
|
|
|
|
s.AuthorIcon = entry["author_icon"].(string)
|
|
|
|
s.Title = entry["title"].(string)
|
|
|
|
s.TitleLink = entry["title_link"].(string)
|
|
|
|
s.Text = entry["text"].(string)
|
|
|
|
s.ImageURL = entry["image_url"].(string)
|
|
|
|
s.ThumbURL = entry["thumb_url"].(string)
|
|
|
|
s.Footer = entry["footer"].(string)
|
|
|
|
s.FooterIcon = entry["footer_icon"].(string)
|
|
|
|
attachs = append(attachs, s)
|
2017-09-18 23:51:27 +02:00
|
|
|
}
|
|
|
|
return attachs
|
|
|
|
}
|
2017-09-21 22:35:21 +02:00
|
|
|
|
2018-02-24 23:35:53 +01:00
|
|
|
// sendWebhook uses the configured WebhookURL to send the message
|
|
|
|
func (b *Bslack) sendWebhook(msg config.Message) (string, error) {
|
|
|
|
// skip events
|
|
|
|
if msg.Event != "" {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
2018-03-04 23:52:14 +01:00
|
|
|
if b.GetBool("PrefixMessagesWithNick") {
|
2018-02-24 23:35:53 +01:00
|
|
|
msg.Text = msg.Username + msg.Text
|
|
|
|
}
|
|
|
|
|
|
|
|
if msg.Extra != nil {
|
|
|
|
// this sends a message only if we received a config.EVENT_FILE_FAILURE_SIZE
|
|
|
|
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
2018-05-27 22:30:17 +02:00
|
|
|
iconURL := config.GetIconURL(&rmsg, b.GetString("iconurl"))
|
|
|
|
matterMessage := matterhook.OMessage{IconURL: iconURL, Channel: msg.Channel, UserName: rmsg.Username, Text: rmsg.Text}
|
2018-02-24 23:35:53 +01:00
|
|
|
b.mh.Send(matterMessage)
|
|
|
|
}
|
|
|
|
|
|
|
|
// webhook doesn't support file uploads, so we add the url manually
|
|
|
|
if len(msg.Extra["file"]) > 0 {
|
|
|
|
for _, f := range msg.Extra["file"] {
|
|
|
|
fi := f.(config.FileInfo)
|
|
|
|
if fi.URL != "" {
|
|
|
|
msg.Text += " " + fi.URL
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we have native slack_attachments add them
|
|
|
|
var attachs []slack.Attachment
|
|
|
|
if len(msg.Extra["slack_attachment"]) > 0 {
|
|
|
|
for _, attach := range msg.Extra["slack_attachment"] {
|
|
|
|
attachs = append(attachs, attach.([]slack.Attachment)...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-27 22:30:17 +02:00
|
|
|
iconURL := config.GetIconURL(&msg, b.GetString("iconurl"))
|
|
|
|
matterMessage := matterhook.OMessage{IconURL: iconURL, Attachments: attachs, Channel: msg.Channel, UserName: msg.Username, Text: msg.Text}
|
2018-02-24 23:35:53 +01:00
|
|
|
if msg.Avatar != "" {
|
|
|
|
matterMessage.IconURL = msg.Avatar
|
|
|
|
}
|
|
|
|
err := b.mh.Send(matterMessage)
|
|
|
|
if err != nil {
|
2018-02-27 00:33:21 +01:00
|
|
|
b.Log.Error(err)
|
2018-02-24 23:35:53 +01:00
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return "", nil
|
|
|
|
}
|