diff options
Diffstat (limited to 'teleirc/matterbridge/bridge/rocketchat')
| -rw-r--r-- | teleirc/matterbridge/bridge/rocketchat/handlers.go | 136 | ||||
| -rw-r--r-- | teleirc/matterbridge/bridge/rocketchat/helpers.go | 201 | ||||
| -rw-r--r-- | teleirc/matterbridge/bridge/rocketchat/rocketchat.go | 181 |
3 files changed, 518 insertions, 0 deletions
diff --git a/teleirc/matterbridge/bridge/rocketchat/handlers.go b/teleirc/matterbridge/bridge/rocketchat/handlers.go new file mode 100644 index 0000000..03b66ea --- /dev/null +++ b/teleirc/matterbridge/bridge/rocketchat/handlers.go @@ -0,0 +1,136 @@ +package brocketchat + +import ( + "fmt" + + "github.com/42wim/matterbridge/bridge/config" + "github.com/42wim/matterbridge/bridge/helper" + "github.com/matterbridge/Rocket.Chat.Go.SDK/models" +) + +func (b *Brocketchat) handleRocket() { + messages := make(chan *config.Message) + if b.GetString("WebhookBindAddress") != "" { + b.Log.Debugf("Choosing webhooks based receiving") + go b.handleRocketHook(messages) + } else { + b.Log.Debugf("Choosing login/password based receiving") + go b.handleRocketClient(messages) + } + for message := range messages { + message.Account = b.Account + b.Log.Debugf("<= Sending message from %s on %s to gateway", message.Username, b.Account) + b.Log.Debugf("<= Message is %#v", message) + b.Remote <- *message + } +} + +func (b *Brocketchat) handleRocketHook(messages chan *config.Message) { + for { + message := b.rh.Receive() + b.Log.Debugf("Receiving from rockethook %#v", message) + // do not loop + if message.UserName == b.GetString("Nick") { + continue + } + messages <- &config.Message{ + UserID: message.UserID, + Username: message.UserName, + Text: message.Text, + Channel: message.ChannelName, + } + } +} + +func (b *Brocketchat) handleStatusEvent(ev models.Message, rmsg *config.Message) bool { + switch ev.Type { + case "": + // this is a normal message, no processing needed + // return true so the message is not dropped + return true + case sUserJoined, sUserLeft: + rmsg.Event = config.EventJoinLeave + return true + case sRoomChangedTopic: + rmsg.Event = config.EventTopicChange + return true + } + b.Log.Debugf("Dropping message with unknown type: %s", ev.Type) + return false +} + +func (b *Brocketchat) handleRocketClient(messages chan *config.Message) { + for message := range b.messageChan { + message := message + // skip messages with same ID, apparently messages get duplicated for an unknown reason + if _, ok := b.cache.Get(message.ID); ok { + continue + } + b.cache.Add(message.ID, true) + b.Log.Debugf("message %#v", message) + m := message + if b.skipMessage(&m) { + b.Log.Debugf("Skipped message: %#v", message) + continue + } + + rmsg := &config.Message{Text: message.Msg, + Username: message.User.UserName, + Channel: b.getChannelName(message.RoomID), + Account: b.Account, + UserID: message.User.ID, + ID: message.ID, + Extra: make(map[string][]interface{}), + } + + b.handleAttachments(&message, rmsg) + + // handleStatusEvent returns false if the message should be dropped + // in that case it is probably some modification to the channel we do not want to relay + if b.handleStatusEvent(m, rmsg) { + messages <- rmsg + } + } +} + +func (b *Brocketchat) handleAttachments(message *models.Message, rmsg *config.Message) { + if rmsg.Text == "" { + for _, attachment := range message.Attachments { + if attachment.Title != "" { + rmsg.Text = attachment.Title + "\n" + } + if attachment.Title != "" && attachment.Text != "" { + rmsg.Text += "\n" + } + if attachment.Text != "" { + rmsg.Text += attachment.Text + } + } + } + + for i := range message.Attachments { + if err := b.handleDownloadFile(rmsg, &message.Attachments[i]); err != nil { + b.Log.Errorf("Could not download incoming file: %#v", err) + } + } +} + +func (b *Brocketchat) handleDownloadFile(rmsg *config.Message, file *models.Attachment) error { + downloadURL := b.GetString("server") + file.TitleLink + data, err := helper.DownloadFileAuthRocket(downloadURL, b.user.Token, b.user.ID) + if err != nil { + return fmt.Errorf("download %s failed %#v", downloadURL, err) + } + helper.HandleDownloadData(b.Log, rmsg, file.Title, rmsg.Text, downloadURL, data, b.General) + return nil +} + +func (b *Brocketchat) handleUploadFile(msg *config.Message) error { + for _, f := range msg.Extra["file"] { + fi := f.(config.FileInfo) + if err := b.uploadFile(&fi, b.getChannelID(msg.Channel)); err != nil { + return err + } + } + return nil +} diff --git a/teleirc/matterbridge/bridge/rocketchat/helpers.go b/teleirc/matterbridge/bridge/rocketchat/helpers.go new file mode 100644 index 0000000..936f631 --- /dev/null +++ b/teleirc/matterbridge/bridge/rocketchat/helpers.go @@ -0,0 +1,201 @@ +package brocketchat + +import ( + "context" + "io/ioutil" + "mime" + "net/http" + "net/url" + "strings" + "time" + + "github.com/42wim/matterbridge/bridge/config" + "github.com/42wim/matterbridge/bridge/helper" + "github.com/42wim/matterbridge/hook/rockethook" + "github.com/42wim/matterbridge/matterhook" + "github.com/matterbridge/Rocket.Chat.Go.SDK/models" + "github.com/matterbridge/Rocket.Chat.Go.SDK/realtime" + "github.com/matterbridge/Rocket.Chat.Go.SDK/rest" + "github.com/nelsonken/gomf" +) + +func (b *Brocketchat) doConnectWebhookBind() error { + switch { + case b.GetString("WebhookURL") != "": + b.Log.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)") + b.mh = matterhook.New(b.GetString("WebhookURL"), + matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), + DisableServer: true}) + b.rh = rockethook.New(b.GetString("WebhookURL"), rockethook.Config{BindAddress: b.GetString("WebhookBindAddress")}) + case b.GetString("Login") != "": + b.Log.Info("Connecting using login/password (sending)") + err := b.apiLogin() + if err != nil { + return err + } + default: + b.Log.Info("Connecting using webhookbindaddress (receiving)") + b.rh = rockethook.New(b.GetString("WebhookURL"), rockethook.Config{BindAddress: b.GetString("WebhookBindAddress")}) + } + return nil +} + +func (b *Brocketchat) doConnectWebhookURL() error { + b.Log.Info("Connecting using webhookurl (sending)") + b.mh = matterhook.New(b.GetString("WebhookURL"), + matterhook.Config{InsecureSkipVerify: b.GetBool("SkipTLSVerify"), + DisableServer: true}) + if b.GetString("Login") != "" { + b.Log.Info("Connecting using login/password (receiving)") + err := b.apiLogin() + if err != nil { + return err + } + } + return nil +} + +func (b *Brocketchat) apiLogin() error { + b.Log.Debugf("handling apiLogin()") + credentials := &models.UserCredentials{Email: b.GetString("login"), Password: b.GetString("password")} + if b.GetString("Token") != "" { + credentials = &models.UserCredentials{ID: b.GetString("Login"), Token: b.GetString("Token")} + } + myURL, err := url.Parse(b.GetString("server")) + if err != nil { + return err + } + client, err := realtime.NewClient(myURL, b.GetBool("debug")) + b.c = client + if err != nil { + return err + } + restclient := rest.NewClient(myURL, b.GetBool("debug")) + user, err := b.c.Login(credentials) + if err != nil { + return err + } + b.user = user + b.r = restclient + err = b.r.Login(credentials) + if err != nil { + return err + } + b.Log.Info("Connection succeeded") + return nil +} + +func (b *Brocketchat) getChannelName(id string) string { + b.RLock() + defer b.RUnlock() + if name, ok := b.channelMap[id]; ok { + return name + } + return "" +} + +func (b *Brocketchat) getChannelID(name string) string { + b.RLock() + defer b.RUnlock() + for k, v := range b.channelMap { + if v == name || v == "#"+name { + return k + } + } + return "" +} + +func (b *Brocketchat) skipMessage(message *models.Message) bool { + return message.User.ID == b.user.ID +} + +func (b *Brocketchat) uploadFile(fi *config.FileInfo, channel string) error { + fb := gomf.New() + if err := fb.WriteField("description", fi.Comment); err != nil { + return err + } + sp := strings.Split(fi.Name, ".") + mtype := mime.TypeByExtension("." + sp[len(sp)-1]) + if !strings.Contains(mtype, "image") && !strings.Contains(mtype, "video") { + return nil + } + if err := fb.WriteFile("file", fi.Name, mtype, *fi.Data); err != nil { + return err + } + req, err := fb.GetHTTPRequest(context.TODO(), b.GetString("server")+"/api/v1/rooms.upload/"+channel) + if err != nil { + return err + } + req.Header.Add("X-Auth-Token", b.user.Token) + req.Header.Add("X-User-Id", b.user.ID) + client := &http.Client{ + Timeout: time.Second * 5, + } + resp, err := client.Do(req) + if err != nil { + return err + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + if resp.StatusCode != 200 { + b.Log.Errorf("failed: %#v", string(body)) + } + return nil +} + +// sendWebhook uses the configured WebhookURL to send the message +func (b *Brocketchat) sendWebhook(msg *config.Message) error { + // skip events + if msg.Event != "" { + return nil + } + + if b.GetBool("PrefixMessagesWithNick") { + 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) { + rmsg := rmsg // scopelint + iconURL := config.GetIconURL(&rmsg, b.GetString("iconurl")) + matterMessage := matterhook.OMessage{ + IconURL: iconURL, + Channel: rmsg.Channel, + UserName: rmsg.Username, + Text: rmsg.Text, + Props: make(map[string]interface{}), + } + if err := b.mh.Send(matterMessage); err != nil { + b.Log.Errorf("sendWebhook failed: %s ", err) + } + } + + // 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 + } + } + } + } + iconURL := config.GetIconURL(msg, b.GetString("iconurl")) + matterMessage := matterhook.OMessage{ + IconURL: iconURL, + Channel: msg.Channel, + UserName: msg.Username, + Text: msg.Text, + } + if msg.Avatar != "" { + matterMessage.IconURL = msg.Avatar + } + err := b.mh.Send(matterMessage) + if err != nil { + b.Log.Info(err) + return err + } + return nil +} diff --git a/teleirc/matterbridge/bridge/rocketchat/rocketchat.go b/teleirc/matterbridge/bridge/rocketchat/rocketchat.go new file mode 100644 index 0000000..405bead --- /dev/null +++ b/teleirc/matterbridge/bridge/rocketchat/rocketchat.go @@ -0,0 +1,181 @@ +package brocketchat + +import ( + "errors" + "strings" + "sync" + + "github.com/42wim/matterbridge/bridge" + "github.com/42wim/matterbridge/bridge/config" + "github.com/42wim/matterbridge/bridge/helper" + "github.com/42wim/matterbridge/hook/rockethook" + "github.com/42wim/matterbridge/matterhook" + lru "github.com/hashicorp/golang-lru" + "github.com/matterbridge/Rocket.Chat.Go.SDK/models" + "github.com/matterbridge/Rocket.Chat.Go.SDK/realtime" + "github.com/matterbridge/Rocket.Chat.Go.SDK/rest" +) + +type Brocketchat struct { + mh *matterhook.Client + rh *rockethook.Client + c *realtime.Client + r *rest.Client + cache *lru.Cache + *bridge.Config + messageChan chan models.Message + channelMap map[string]string + user *models.User + sync.RWMutex +} + +const ( + sUserJoined = "uj" + sUserLeft = "ul" + sRoomChangedTopic = "room_changed_topic" +) + +func New(cfg *bridge.Config) bridge.Bridger { + newCache, err := lru.New(100) + if err != nil { + cfg.Log.Fatalf("Could not create LRU cache for rocketchat bridge: %v", err) + } + b := &Brocketchat{ + Config: cfg, + messageChan: make(chan models.Message), + channelMap: make(map[string]string), + cache: newCache, + } + b.Log.Debugf("enabling rocketchat") + return b +} + +func (b *Brocketchat) Command(cmd string) string { + return "" +} + +func (b *Brocketchat) Connect() error { + if b.GetString("WebhookBindAddress") != "" { + if err := b.doConnectWebhookBind(); err != nil { + return err + } + go b.handleRocket() + return nil + } + switch { + case b.GetString("WebhookURL") != "": + if err := b.doConnectWebhookURL(); err != nil { + return err + } + go b.handleRocket() + return nil + case b.GetString("Login") != "": + b.Log.Info("Connecting using login/password (sending and receiving)") + err := b.apiLogin() + if err != nil { + return err + } + go b.handleRocket() + } + if b.GetString("WebhookBindAddress") == "" && b.GetString("WebhookURL") == "" && + b.GetString("Login") == "" { + return errors.New("no connection method found. See that you have WebhookBindAddress, WebhookURL or Login/Password/Server configured") + } + return nil +} + +func (b *Brocketchat) Disconnect() error { + return nil +} + +func (b *Brocketchat) JoinChannel(channel config.ChannelInfo) error { + if b.c == nil { + return nil + } + id, err := b.c.GetChannelId(strings.TrimPrefix(channel.Name, "#")) + if err != nil { + return err + } + b.Lock() + b.channelMap[id] = channel.Name + b.Unlock() + mychannel := &models.Channel{ID: id, Name: strings.TrimPrefix(channel.Name, "#")} + if err := b.c.JoinChannel(id); err != nil { + return err + } + if err := b.c.SubscribeToMessageStream(mychannel, b.messageChan); err != nil { + return err + } + return nil +} + +func (b *Brocketchat) Send(msg config.Message) (string, error) { + // strip the # if people has set this + msg.Channel = strings.TrimPrefix(msg.Channel, "#") + channel := &models.Channel{ID: b.getChannelID(msg.Channel), Name: msg.Channel} + + // Make a action /me of the message + if msg.Event == config.EventUserAction { + msg.Text = "_" + msg.Text + "_" + } + + // Delete message + if msg.Event == config.EventMsgDelete { + if msg.ID == "" { + return "", nil + } + return msg.ID, b.c.DeleteMessage(&models.Message{ID: msg.ID}) + } + + // Use webhook to send the message + if b.GetString("WebhookURL") != "" { + return "", b.sendWebhook(&msg) + } + + // Prepend nick if configured + if b.GetBool("PrefixMessagesWithNick") { + msg.Text = msg.Username + msg.Text + } + + // Edit message if we have an ID + if msg.ID != "" { + return msg.ID, b.c.EditMessage(&models.Message{ID: msg.ID, Msg: msg.Text, RoomID: b.getChannelID(msg.Channel)}) + } + + // Upload a file if it exists + if msg.Extra != nil { + for _, rmsg := range helper.HandleExtra(&msg, b.General) { + // strip the # if people has set this + rmsg.Channel = strings.TrimPrefix(rmsg.Channel, "#") + smsg := &models.Message{ + RoomID: b.getChannelID(rmsg.Channel), + Msg: rmsg.Username + rmsg.Text, + PostMessage: models.PostMessage{ + Avatar: rmsg.Avatar, + Alias: rmsg.Username, + }, + } + if _, err := b.c.SendMessage(smsg); err != nil { + b.Log.Errorf("SendMessage failed: %s", err) + } + } + if len(msg.Extra["file"]) > 0 { + return "", b.handleUploadFile(&msg) + } + } + + smsg := &models.Message{ + RoomID: channel.ID, + Msg: msg.Text, + PostMessage: models.PostMessage{ + Avatar: msg.Avatar, + Alias: msg.Username, + }, + } + + rmsg, err := b.c.SendMessage(smsg) + if rmsg == nil { + return "", err + } + return rmsg.ID, err +} |
