diff --git a/README.md b/README.md index 3c808363..523e6076 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # matterbridge ![matterbridge.gif](https://s15.postimg.org/qpjhp6y3f/matterbridge.gif) -Simple bridge between mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram and Hipchat(via xmpp). +Simple bridge between mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram, Rocket.Chat and Hipchat(via xmpp). -* Relays public channel messages between multiple mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram and Hipchat (via xmpp). Pick and mix. +* Relays public channel messages between multiple mattermost, IRC, XMPP, Gitter, Slack, Discord, Telegram, Rocket.Chat and Hipchat (via xmpp). Pick and mix. * Supports multiple channels. * Matterbridge can also work with private groups on your mattermost. * Allow for bridging the same bridges, which means you can eg bridge between multiple mattermosts. @@ -26,6 +26,7 @@ Accounts to one of the supported bridges * [Discord] (https://discordapp.com) * [Telegram] (https://telegram.org) * [Hipchat] (https://www.hipchat.com) +* [Rocket.chat] (https://rocket.chat) ## Docker Create your matterbridge.toml file locally eg in ```/tmp/matterbridge.toml``` diff --git a/bridge/bridge.go b/bridge/bridge.go index 8c715369..b387812a 100644 --- a/bridge/bridge.go +++ b/bridge/bridge.go @@ -6,6 +6,7 @@ import ( "github.com/42wim/matterbridge/bridge/gitter" "github.com/42wim/matterbridge/bridge/irc" "github.com/42wim/matterbridge/bridge/mattermost" + "github.com/42wim/matterbridge/bridge/rocketchat" "github.com/42wim/matterbridge/bridge/slack" "github.com/42wim/matterbridge/bridge/telegram" "github.com/42wim/matterbridge/bridge/xmpp" @@ -59,6 +60,9 @@ func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) *Brid case "telegram": b.Config = cfg.Telegram[name] b.Bridger = btelegram.New(cfg.Telegram[name], bridge.Account, c) + case "rocketchat": + b.Config = cfg.Rocketchat[name] + b.Bridger = brocketchat.New(cfg.Rocketchat[name], bridge.Account, c) } return b } diff --git a/bridge/config/config.go b/bridge/config/config.go index 4b0a7ead..971013d7 100644 --- a/bridge/config/config.go +++ b/bridge/config/config.go @@ -80,6 +80,7 @@ type Config struct { Xmpp map[string]Protocol Discord map[string]Protocol Telegram map[string]Protocol + Rocketchat map[string]Protocol General Protocol Gateway []Gateway SameChannelGateway []SameChannelGateway diff --git a/bridge/rocketchat/rocketchat.go b/bridge/rocketchat/rocketchat.go new file mode 100644 index 00000000..d87450ec --- /dev/null +++ b/bridge/rocketchat/rocketchat.go @@ -0,0 +1,82 @@ +package brocketchat + +import ( + "github.com/42wim/matterbridge/bridge/config" + "github.com/42wim/matterbridge/hook/rockethook" + "github.com/42wim/matterbridge/matterhook" + log "github.com/Sirupsen/logrus" +) + +type MMhook struct { + mh *matterhook.Client + rh *rockethook.Client +} + +type Brocketchat struct { + MMhook + Config *config.Protocol + Remote chan config.Message + name string + Account string +} + +var flog *log.Entry +var protocol = "rocketchat" + +func init() { + flog = log.WithFields(log.Fields{"module": protocol}) +} + +func New(cfg config.Protocol, account string, c chan config.Message) *Brocketchat { + b := &Brocketchat{} + b.Config = &cfg + b.Remote = c + b.Account = account + return b +} + +func (b *Brocketchat) Command(cmd string) string { + return "" +} + +func (b *Brocketchat) Connect() error { + flog.Info("Connecting webhooks") + b.mh = matterhook.New(b.Config.URL, + matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, + DisableServer: true}) + b.rh = rockethook.New(b.Config.URL, rockethook.Config{BindAddress: b.Config.BindAddress}) + go b.handleRocketHook() + return nil +} + +func (b *Brocketchat) JoinChannel(channel string) error { + return nil +} + +func (b *Brocketchat) Send(msg config.Message) error { + flog.Debugf("Receiving %#v", msg) + matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL} + matterMessage.Channel = msg.Channel + matterMessage.UserName = msg.Username + matterMessage.Type = "" + matterMessage.Text = msg.Text + err := b.mh.Send(matterMessage) + if err != nil { + flog.Info(err) + return err + } + return nil +} + +func (b *Brocketchat) handleRocketHook() { + for { + message := b.rh.Receive() + flog.Debugf("Receiving from rockethook %#v", message) + // do not loop + if message.UserName == b.Config.Nick { + continue + } + flog.Debugf("Sending message from %s on %s to gateway", message.UserName, b.Account) + b.Remote <- config.Message{Text: message.Text, Username: message.UserName, Channel: message.ChannelName, Account: b.Account} + } +} diff --git a/hook/rockethook/rockethook.go b/hook/rockethook/rockethook.go new file mode 100644 index 00000000..eafd4f43 --- /dev/null +++ b/hook/rockethook/rockethook.go @@ -0,0 +1,108 @@ +package rockethook + +import ( + "crypto/tls" + "encoding/json" + "io/ioutil" + "log" + "net" + "net/http" +) + +// Message for rocketchat outgoing webhook. +type Message struct { + Token string `json:"token"` + ChannelID string `json:"channel_id"` + ChannelName string `json:"channel_name"` + Timestamp string `json:"timestamp"` + UserID string `json:"user_id"` + UserName string `json:"user_name"` + Text string `json:"text"` +} + +// Client for Rocketchat. +type Client struct { + In chan Message + httpclient *http.Client + Config +} + +// Config for client. +type Config struct { + BindAddress string // Address to listen on + Token string // Only allow this token from Rocketchat. (Allow everything when empty) + InsecureSkipVerify bool // disable certificate checking +} + +// New Rocketchat client. +func New(url string, config Config) *Client { + c := &Client{In: make(chan Message), Config: config} + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}, + } + c.httpclient = &http.Client{Transport: tr} + _, _, err := net.SplitHostPort(c.BindAddress) + if err != nil { + log.Fatalf("incorrect bindaddress %s", c.BindAddress) + } + go c.StartServer() + return c +} + +// StartServer starts a webserver listening for incoming mattermost POSTS. +func (c *Client) StartServer() { + mux := http.NewServeMux() + mux.Handle("/", c) + log.Printf("Listening on http://%v...\n", c.BindAddress) + if err := http.ListenAndServe(c.BindAddress, mux); err != nil { + log.Fatal(err) + } +} + +// ServeHTTP implementation. +func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + log.Println("invalid " + r.Method + " connection from " + r.RemoteAddr) + http.NotFound(w, r) + return + } + msg := Message{} + body, err := ioutil.ReadAll(r.Body) + log.Println(string(body)) + if err != nil { + log.Println(err) + http.NotFound(w, r) + return + } + defer r.Body.Close() + err = json.Unmarshal(body, &msg) + if err != nil { + log.Println(err) + http.NotFound(w, r) + return + } + if msg.Token == "" { + log.Println("no token from " + r.RemoteAddr) + http.NotFound(w, r) + return + } + msg.ChannelName = "#" + msg.ChannelName + if c.Token != "" { + if msg.Token != c.Token { + log.Println("invalid token " + msg.Token + " from " + r.RemoteAddr) + http.NotFound(w, r) + return + } + } + c.In <- msg +} + +// Receive returns an incoming message from mattermost outgoing webhooks URL. +func (c *Client) Receive() Message { + for { + select { + case msg := <-c.In: + return msg + } + } +} diff --git a/matterbridge.go b/matterbridge.go index 11b9a5d2..5778c184 100644 --- a/matterbridge.go +++ b/matterbridge.go @@ -9,7 +9,7 @@ import ( log "github.com/Sirupsen/logrus" ) -var version = "0.9.0" +var version = "0.9.1-dev" func init() { log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) diff --git a/matterbridge.toml.sample b/matterbridge.toml.sample index a8b18623..1a1de89c 100644 --- a/matterbridge.toml.sample +++ b/matterbridge.toml.sample @@ -415,6 +415,64 @@ RemoteNickFormat="[{PROTOCOL}] <{NICK}> " #OPTIONAL (default false) ShowJoinPart=false + +################################################################### +#rocketchat section +################################################################### +[rocketchat] +#You can configure multiple servers "[rocketchat.name]" or "[rocketchat.name2]" +#In this example we use [rocketchat.work] +#REQUIRED + +[rocketchat.rockme] +#Url is your incoming webhook url as specified in rocketchat +#Read #https://rocket.chat/docs/administrator-guides/integrations/#how-to-create-a-new-incoming-webhook +#See administration - integrations - new integration - incoming webhook +#REQUIRED +URL="https://yourdomain/hooks/yourhookkey" + +#Address to listen on for outgoing webhook requests from rocketchat. +#See administration - integrations - new integration - outgoing webhook +#REQUIRED +BindAddress="0.0.0.0:9999" + +#Your nick/username as specified in your incoming webhook "Post as" setting +#REQUIRED +Nick="matterbot" + +#Enable this to make a http connection (instead of https) to your rocketchat +#OPTIONAL (default false) +NoTLS=false + +#Enable to not verify the certificate on your rocketchat server. +#e.g. when using selfsigned certificates +#OPTIONAL (default false) +SkipTLSVerify=true + +#Whether to prefix messages from other bridges to rocketchat with the sender's nick. +#Useful if username overrides for incoming webhooks isn't enabled on the +#rocketchat server. If you set PrefixMessagesWithNick to true, each message +#from bridge to rocketchat will by default be prefixed by the RemoteNickFormat setting. i +#OPTIONAL (default false) +PrefixMessagesWithNick=false + +#Nicks you want to ignore. +#Messages from those users will not be sent to other bridges. +#OPTIONAL +IgnoreNicks="ircspammer1 ircspammer2" + +#RemoteNickFormat defines how remote users appear on this bridge +#The string "{NICK}" (case sensitive) will be replaced by the actual nick / username. +#The string "{BRIDGE}" (case sensitive) will be replaced by the sending bridge +#The string "{PROTOCOL}" (case sensitive) will be replaced by the protocol used by the bridge +#OPTIONAL (default empty) +RemoteNickFormat="[{PROTOCOL}] <{NICK}> " + +#Enable to show users joins/parts from other bridges (only from irc-bridge at the moment) +#OPTIONAL (default false) +ShowJoinPart=false + + ################################################################### #General configuration ################################################################### @@ -427,8 +485,6 @@ ShowJoinPart=false #OPTIONAL (default empty) RemoteNickFormat="[{PROTOCOL}] <{NICK}> " - - ################################################################### #Gateway configuration ################################################################### @@ -470,6 +526,7 @@ enable=true # (https://github.com/42wim/matterbridge/issues/57) #telegram - chatid (a large negative number, eg -123456789) #hipchat - id_channel (see https://www.hipchat.com/account/xmpp for the correct channel) + #rocketchat - #channel (# is required) #REQUIRED channel="#testing"