Thumbnail

rani/matterbridge.git

Clone URL: https://git.buni.party/rani/matterbridge.git

commit b9ce369f904a5e50e776d87a085f0e7087fbd2ff Author: Wim <wim@42.be> Date: Sun Sep 18 19:21:15 2016 +0000 Refactor for more flexibility * Move from gcfg to toml configuration because gcfg was too restrictive * Implemented gateway which has support multiple in and out bridges. * Allow for bridging the same bridges, which means eg you can now bridge between multiple mattermosts. * Support multiple gateways diff --git a/bridge/bridge.go b/bridge/bridge.go index 45a0160..719af4f 100644 --- a/bridge/bridge.go +++ b/bridge/bridge.go @@ -1151 +140 @@  package bridge    import ( - //"fmt"   "github.com/42wim/matterbridge/bridge/config"   "github.com/42wim/matterbridge/bridge/gitter"   "github.com/42wim/matterbridge/bridge/irc"   "github.com/42wim/matterbridge/bridge/mattermost"   "github.com/42wim/matterbridge/bridge/slack"   "github.com/42wim/matterbridge/bridge/xmpp" - log "github.com/Sirupsen/logrus"   "strings"  )   -type Bridge struct { - *config.Config - Source string - Bridges []Bridger - Channels []map[string]string - ignoreNicks map[string][]string -} - -type Bridger interface { +type Bridge interface {   Send(msg config.Message) error   Name() string   Connect() error - //Command(cmd string) string -} - -func NewBridge(cfg *config.Config) error { - c := make(chan config.Message) - b := &Bridge{} - b.Config = cfg - if cfg.IRC.Enable { - b.Bridges = append(b.Bridges, birc.New(cfg, c)) - } - if cfg.Mattermost.Enable { - b.Bridges = append(b.Bridges, bmattermost.New(cfg, c)) - } - if cfg.Xmpp.Enable { - b.Bridges = append(b.Bridges, bxmpp.New(cfg, c)) - } - if cfg.Gitter.Enable { - b.Bridges = append(b.Bridges, bgitter.New(cfg, c)) - } - if cfg.Slack.Enable { - b.Bridges = append(b.Bridges, bslack.New(cfg, c)) - } - if len(b.Bridges) < 2 { - log.Fatalf("only %d sections enabled. Need at least 2 sections enabled (eg [IRC] and [mattermost]", len(b.Bridges)) - } - for _, br := range b.Bridges { - br.Connect() - } - b.mapChannels() - b.mapIgnores() - b.handleReceive(c) - return nil -} - -func (b *Bridge) handleReceive(c chan config.Message) { - for { - select { - case msg := <-c: - for _, br := range b.Bridges { - b.handleMessage(msg, br) - } - } - } + FullOrigin() string + Origin() string + Protocol() string + JoinChannel(channel string) error  }   -func (b *Bridge) mapChannels() error { - for _, val := range b.Config.Channel { - m := make(map[string]string) - m["irc"] = val.IRC - m["mattermost"] = val.Mattermost - m["xmpp"] = val.Xmpp - m["gitter"] = val.Gitter - m["slack"] = val.Slack - b.Channels = append(b.Channels, m) - } - return nil -} - -func (b *Bridge) mapIgnores() { - m := make(map[string][]string) - m["irc"] = strings.Fields(b.Config.IRC.IgnoreNicks) - m["mattermost"] = strings.Fields(b.Config.Mattermost.IgnoreNicks) - m["xmpp"] = strings.Fields(b.Config.Xmpp.IgnoreNicks) - m["gitter"] = strings.Fields(b.Config.Gitter.IgnoreNicks) - m["slack"] = strings.Fields(b.Config.Slack.IgnoreNicks) - b.ignoreNicks = m -} - -func (b *Bridge) getDestChannel(msg *config.Message, dest string) string { - for _, v := range b.Channels { - if v[msg.Origin] == msg.Channel { - return v[dest] - } - } - return "" -} - -func (b *Bridge) handleMessage(msg config.Message, dest Bridger) { - if b.ignoreMessage(&msg) { - return - } - if dest.Name() != msg.Origin { - msg.Channel = b.getDestChannel(&msg, dest.Name()) - if msg.Channel == "" { - return - } - b.modifyMessage(&msg, dest.Name()) - log.Debugf("sending %#v from %s to %s", msg, msg.Origin, dest.Name()) - dest.Send(msg) - } -} - -func (b *Bridge) ignoreMessage(msg *config.Message) bool { - // should we discard messages ? - for _, entry := range b.ignoreNicks[msg.Origin] { - if msg.Username == entry { - return true - } - } - return false -} - -func setNickFormat(msg *config.Message, format string) { - if format == "" { - msg.Username = msg.Origin + "-" + msg.Username + ": " - return - } - msg.Username = strings.Replace(format, "{NICK}", msg.Username, -1) - msg.Username = strings.Replace(msg.Username, "{BRIDGE}", msg.Origin, -1) -} - -func (b *Bridge) modifyMessage(msg *config.Message, dest string) { - switch dest { +func New(cfg *config.Config, bridge *config.Bridge, c chan config.Message) Bridge { + accInfo := strings.Split(bridge.Account, ".") + protocol := accInfo[0] + name := accInfo[1] + switch protocol { + case "mattermost": + return bmattermost.New(cfg.Mattermost[name], name, c)   case "irc": - setNickFormat(msg, b.Config.IRC.RemoteNickFormat) + return birc.New(cfg.IRC[name], name, c)   case "gitter": - setNickFormat(msg, b.Config.Gitter.RemoteNickFormat) - case "xmpp": - setNickFormat(msg, b.Config.Xmpp.RemoteNickFormat) - case "mattermost": - setNickFormat(msg, b.Config.Mattermost.RemoteNickFormat) + return bgitter.New(cfg.Gitter[name], name, c)   case "slack": - setNickFormat(msg, b.Config.Slack.RemoteNickFormat) + return bslack.New(cfg.Slack[name], name, c) + case "xmpp": + return bxmpp.New(cfg.Xmpp[name], name, c)   } + return nil  } diff --git a/bridge/config/config.go b/bridge/config/config.go index 48574e2..ad8a523 100644 --- a/bridge/config/config.go +++ b/bridge/config/config.go @@ -1107 +173 @@  package config    import ( - "gopkg.in/gcfg.v1" - "io/ioutil" + "github.com/BurntSushi/toml"   "log"  )    type Message struct { - Text string - Channel string - Username string - Origin string + Text string + Channel string + Username string + Origin string + FullOrigin string + Protocol string +} + +type Protocol struct { + BindAddress string // mattermost, slack + IconURL string // mattermost, slack + IgnoreNicks string // all protocols + Jid string // xmpp + Login string // mattermost + Muc string // xmpp + Name string // all protocols + Nick string // all protocols + NickFormatter string // mattermost, slack + NickServNick string // IRC + NickServPassword string // IRC + NicksPerRow int // mattermost, slack + NoTLS bool // mattermost + Password string // IRC,mattermost,XMPP + PrefixMessagesWithNick bool // mattemost, slack + Protocol string //all protocols + RemoteNickFormat string // all protocols + Server string // IRC,mattermost,XMPP + ShowJoinPart bool // all protocols + SkipTLSVerify bool // IRC, mattermost + Team string // mattermost + Token string // gitter, slack + URL string // mattermost, slack + UseAPI bool // mattermost, slack + UseSASL bool // IRC + UseTLS bool // IRC +} + +type Bridge struct { + Account string + Channel string +} + +type Gateway struct { + Name string + Enable bool + In []Bridge + Out []Bridge  }    type Config struct { - IRC struct { - UseTLS bool - UseSASL bool - SkipTLSVerify bool - Server string - Nick string - Password string - Channel string - NickServNick string - NickServPassword string - RemoteNickFormat string - IgnoreNicks string - Enable bool - } - Gitter struct { - Enable bool - IgnoreNicks string - Nick string - RemoteNickFormat string - Token string - } - Mattermost struct { - URL string - ShowJoinPart bool - IconURL string - SkipTLSVerify bool - BindAddress string - Channel string - PrefixMessagesWithNick bool - NicksPerRow int - NickFormatter string - Server string - Team string - Login string - Password string - RemoteNickFormat string - IgnoreNicks string - NoTLS bool - Enable bool - } - Slack struct { - BindAddress string - Enable bool - IconURL string - IgnoreNicks string - NickFormatter string - NicksPerRow int - PrefixMessagesWithNick bool - RemoteNickFormat string - Token string - URL string - UseAPI bool - } - Xmpp struct { - IgnoreNicks string - Jid string - Password string - Server string - Muc string - Nick string - RemoteNickFormat string - Enable bool - } - Channel map[string]*struct { - IRC string - Mattermost string - Xmpp string - Gitter string - Slack string - } - General struct { - GiphyAPIKey string - Xmpp bool - Irc bool - Mattermost bool - Plus bool - } + IRC map[string]Protocol + Mattermost map[string]Protocol + Slack map[string]Protocol + Gitter map[string]Protocol + Xmpp map[string]Protocol + Gateway []Gateway  }    func NewConfig(cfgfile string) *Config {   var cfg Config - content, err := ioutil.ReadFile(cfgfile) - if err != nil { + if _, err := toml.DecodeFile("matterbridge.toml", &cfg); err != nil {   log.Fatal(err)   } - err = gcfg.ReadStringInto(&cfg, string(content)) - if err != nil { - log.Fatal("Failed to parse "+cfgfile+":", err) - }   return &cfg  } diff --git a/bridge/gitter/gitter.go b/bridge/gitter/gitter.go index 1d1b0a5..b00970d 100644 --- a/bridge/gitter/gitter.go +++ b/bridge/gitter/gitter.go @@ -848 +891 @@ import (  )    type Bgitter struct { - c *gitter.Gitter - *config.Config - Remote chan config.Message - Rooms []gitter.Room -} - -type Message struct { - Text string - Channel string - Username string + c *gitter.Gitter + Config *config.Protocol + Remote chan config.Message + protocol string + origin string + Rooms []gitter.Room  }    var flog *log.Entry +var protocol = "gitter"    func init() { - flog = log.WithFields(log.Fields{"module": "gitter"}) + flog = log.WithFields(log.Fields{"module": protocol})  }   -func New(config *config.Config, c chan config.Message) *Bgitter { +func New(config config.Protocol, origin string, c chan config.Message) *Bgitter {   b := &Bgitter{} - b.Config = config + b.Config = &config   b.Remote = c + b.protocol = protocol + b.origin = origin   return b  }    func (b *Bgitter) Connect() error {   var err error - flog.Info("Trying Gitter connection") - b.c = gitter.New(b.Config.Gitter.Token) + flog.Info("Trying " + b.protocol + " connection") + b.c = gitter.New(b.Config.Token)   _, err = b.c.GetUser()   if err != nil {   flog.Debugf("%#v", err)   return err   }   flog.Info("Connection succeeded") - b.setupChannels() - go b.handleGitter() + //b.setupChannels() + b.Rooms, _ = b.c.GetRooms() + //go b.handleGitter() + return nil +} + +func (b *Bgitter) FullOrigin() string { + return b.protocol + "." + b.origin +} + +func (b *Bgitter) JoinChannel(channel string) error { + _, err := b.c.JoinRoom(channel) + if err != nil { + return err + } + room := channel + roomID := b.getRoomID(room) + if roomID == "" { + return nil + } + stream := b.c.Stream(roomID) + go b.c.Listen(stream) + + go func(stream *gitter.Stream, room string) { + for { + event := <-stream.Event + switch ev := event.Data.(type) { + case *gitter.MessageReceived: + // check for ZWSP to see if it's not an echo + if !strings.HasSuffix(ev.Message.Text, "​") { + b.Remote <- config.Message{Username: ev.Message.From.Username, Text: ev.Message.Text, Channel: room, + Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()} + } + case *gitter.GitterConnectionClosed: + flog.Errorf("connection with gitter closed for room %s", room) + } + } + }(stream, room)   return nil  }    func (b *Bgitter) Name() string { - return "gitter" + return b.protocol + "." + b.origin +} + +func (b *Bgitter) Protocol() string { + return b.protocol +} + +func (b *Bgitter) Origin() string { + return b.origin  }    func (b *Bgitter) Send(msg config.Message) error { @@ -716 +1147 @@ func (b *Bgitter) getRoomID(channel string) string {   return ""  }   +/*  func (b *Bgitter) handleGitter() {   for _, val := range b.Config.Channel {   room := val.Gitter @@ -887 +1328 @@ func (b *Bgitter) handleGitter() {   case *gitter.MessageReceived:   // check for ZWSP to see if it's not an echo   if !strings.HasSuffix(ev.Message.Text, "​") { - b.Remote <- config.Message{Username: ev.Message.From.Username, Text: ev.Message.Text, Channel: room, Origin: "gitter"} + b.Remote <- config.Message{Username: ev.Message.From.Username, Text: ev.Message.Text, Channel: room, + Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()}   }   case *gitter.GitterConnectionClosed:   flog.Errorf("connection with gitter closed for room %s", room) @@ -9714 +1424 @@ func (b *Bgitter) handleGitter() {   }(stream, room)   }  } - -func (b *Bgitter) setupChannels() { - b.Rooms, _ = b.c.GetRooms() - for _, val := range b.Config.Channel { - flog.Infof("Joining %s as %s", val.Gitter, b.Gitter.Nick) - _, err := b.c.JoinRoom(val.Gitter) - if err != nil { - log.Errorf("Joining %s failed", val.Gitter) - } - } -} +*/ diff --git a/bridge/irc/irc.go b/bridge/irc/irc.go index 102efa5..cb70732 100644 --- a/bridge/irc/irc.go +++ b/bridge/irc/irc.go @@ -1235 +1232 @@ import (   "time"  )   -//type Bridge struct {  type Birc struct { - i *irc.Connection - ircNick string - ircMap map[string]string - names map[string][]string - ircIgnoreNicks []string - *config.Config - Remote chan config.Message + i *irc.Connection + ircNick string + name string + names map[string][]string + Config *config.Protocol + origin string + protocol string + Remote chan config.Message  }   -type FancyLog struct { - irc *log.Entry -} - -var flog FancyLog +var flog *log.Entry +var protocol = "irc"    func init() { - flog.irc = log.WithFields(log.Fields{"module": "irc"}) + flog = log.WithFields(log.Fields{"module": protocol})  }   -func New(config *config.Config, c chan config.Message) *Birc { +func New(config config.Protocol, origin string, c chan config.Message) *Birc {   b := &Birc{} - b.Config = config + b.Config = &config   b.Remote = c - b.ircNick = b.Config.IRC.Nick - b.ircMap = make(map[string]string) + b.protocol = protocol + b.ircNick = b.Config.Nick + b.origin = origin   b.names = make(map[string][]string) - b.ircIgnoreNicks = strings.Fields(b.Config.IRC.IgnoreNicks)   return b  }   @@ -5332 +5049 @@ func (b *Birc) Command(msg *config.Message) string {  }    func (b *Birc) Connect() error { - flog.irc.Info("Trying IRC connection") - i := irc.IRC(b.Config.IRC.Nick, b.Config.IRC.Nick) - i.UseTLS = b.Config.IRC.UseTLS - i.UseSASL = b.Config.IRC.UseSASL - i.SASLLogin = b.Config.IRC.NickServNick - i.SASLPassword = b.Config.IRC.NickServPassword - i.TLSConfig = &tls.Config{InsecureSkipVerify: b.Config.IRC.SkipTLSVerify} - if b.Config.IRC.Password != "" { - i.Password = b.Config.IRC.Password + flog.Info("Trying IRC connection") + i := irc.IRC(b.Config.Nick, b.Config.Nick) + i.UseTLS = b.Config.UseTLS + i.UseSASL = b.Config.UseSASL + i.SASLLogin = b.Config.NickServNick + i.SASLPassword = b.Config.NickServPassword + i.TLSConfig = &tls.Config{InsecureSkipVerify: b.Config.SkipTLSVerify} + if b.Config.Password != "" { + i.Password = b.Config.Password   }   i.AddCallback(ircm.RPL_WELCOME, b.handleNewConnection) - err := i.Connect(b.Config.IRC.Server) + err := i.Connect(b.Config.Server)   if err != nil {   return err   } - flog.irc.Info("Connection succeeded") + flog.Info("Connection succeeded")   b.i = i   return nil  }   +func (b *Birc) FullOrigin() string { + return b.protocol + "." + b.origin +} + +func (b *Birc) JoinChannel(channel string) error { + b.i.Join(channel) + return nil +} +  func (b *Birc) Name() string { - return "irc" + return b.protocol + "." + b.origin +} + +func (b *Birc) Protocol() string { + return b.protocol +} + +func (b *Birc) Origin() string { + return b.origin  }    func (b *Birc) Send(msg config.Message) error { - if msg.Origin == "irc" { + if msg.FullOrigin == b.FullOrigin() {   return nil   }   if strings.HasPrefix(msg.Text, "!") { @@ -9516 +10918 @@ func (b *Birc) endNames(event *irc.Event) {   maxNamesPerPost := (300 / b.nicksPerRow()) * b.nicksPerRow()   continued := false   for len(b.names[channel]) > maxNamesPerPost { - b.Remote <- config.Message{Username: b.ircNick, Text: b.formatnicks(b.names[channel][0:maxNamesPerPost], continued), Channel: channel, Origin: "irc"} + b.Remote <- config.Message{Username: b.ircNick, Text: b.formatnicks(b.names[channel][0:maxNamesPerPost], continued), + Channel: channel, Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()}   b.names[channel] = b.names[channel][maxNamesPerPost:]   continued = true   } - b.Remote <- config.Message{Username: b.ircNick, Text: b.formatnicks(b.names[channel], continued), Channel: channel, Origin: "irc"} + b.Remote <- config.Message{Username: b.ircNick, Text: b.formatnicks(b.names[channel], continued), Channel: channel, + Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()}   b.names[channel] = nil  }    func (b *Birc) handleNewConnection(event *irc.Event) { - flog.irc.Info("Registering callbacks") + flog.Info("Registering callbacks")   i := b.i   b.ircNick = event.Arguments[0]   i.AddCallback("PRIVMSG", b.handlePrivMsg) @@ -11368 +12955 @@ func (b *Birc) handleNewConnection(event *irc.Event) {   i.AddCallback(ircm.RPL_ENDOFNAMES, b.endNames)   i.AddCallback(ircm.RPL_NAMREPLY, b.storeNames)   i.AddCallback(ircm.NOTICE, b.handleNotice) - i.AddCallback(ircm.RPL_MYINFO, func(e *irc.Event) { flog.irc.Infof("%s: %s", e.Code, strings.Join(e.Arguments[1:], " ")) }) + i.AddCallback(ircm.RPL_MYINFO, func(e *irc.Event) { flog.Infof("%s: %s", e.Code, strings.Join(e.Arguments[1:], " ")) })   i.AddCallback("PING", func(e *irc.Event) {   i.SendRaw("PONG :" + e.Message()) - flog.irc.Debugf("PING/PONG") + flog.Debugf("PING/PONG")   }) - if b.Config.Mattermost.ShowJoinPart { - i.AddCallback("JOIN", b.handleJoinPart) - i.AddCallback("PART", b.handleJoinPart) - }   i.AddCallback("*", b.handleOther) - b.setupChannels() -} - -func (b *Birc) handleJoinPart(event *irc.Event) { - //b.Send(b.ircNick, b.ircNickFormat(event.Nick)+" "+strings.ToLower(event.Code)+"s "+event.Message(), b.getMMChannel(event.Arguments[0]))  }    func (b *Birc) handleNotice(event *irc.Event) {   if strings.Contains(event.Message(), "This nickname is registered") { - b.i.Privmsg(b.Config.IRC.NickServNick, "IDENTIFY "+b.Config.IRC.NickServPassword) + b.i.Privmsg(b.Config.NickServNick, "IDENTIFY "+b.Config.NickServPassword)   }  }    func (b *Birc) handleOther(event *irc.Event) { - flog.irc.Debugf("%#v", event) + flog.Debugf("%#v", event)  }    func (b *Birc) handlePrivMsg(event *irc.Event) { - flog.irc.Debugf("handlePrivMsg() %s %s", event.Nick, event.Message()) + flog.Debugf("handlePrivMsg() %s %s", event.Nick, event.Message())   msg := ""   if event.Code == "CTCP_ACTION" {   msg = event.Nick + " "   }   msg += event.Message() - b.Remote <- config.Message{Username: event.Nick, Text: msg, Channel: event.Arguments[0], Origin: "irc"} + b.Remote <- config.Message{Username: event.Nick, Text: msg, Channel: event.Arguments[0], Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()}  }    func (b *Birc) handleTopicWhoTime(event *irc.Event) {   parts := strings.Split(event.Arguments[2], "!")   t, err := strconv.ParseInt(event.Arguments[3], 10, 64)   if err != nil { - flog.irc.Errorf("Invalid time stamp: %s", event.Arguments[3]) + flog.Errorf("Invalid time stamp: %s", event.Arguments[3])   }   user := parts[0]   if len(parts) > 1 {   user += " [" + parts[1] + "]"   } - flog.irc.Infof("%s: Topic set by %s [%s]", event.Code, user, time.Unix(t, 0)) + flog.Infof("%s: Topic set by %s [%s]", event.Code, user, time.Unix(t, 0))  }    func (b *Birc) nicksPerRow() int { - if b.Config.Mattermost.NicksPerRow < 1 { - return 4 - } - return b.Config.Mattermost.NicksPerRow -} - -func (b *Birc) setupChannels() { - for _, val := range b.Config.Channel { - flog.irc.Infof("Joining %s as %s", val.IRC, b.ircNick) - b.i.Join(val.IRC) - } + return 4 + /* + if b.Config.Mattermost.NicksPerRow < 1 { + return 4 + } + return b.Config.Mattermost.NicksPerRow + */  }    func (b *Birc) storeNames(event *irc.Event) { @@ -18510 +18813 @@ func (b *Birc) storeNames(event *irc.Event) {  }    func (b *Birc) formatnicks(nicks []string, continued bool) string { - switch b.Config.Mattermost.NickFormatter { - case "table": - return tableformatter(nicks, b.nicksPerRow(), continued) - default: - return plainformatter(nicks, b.nicksPerRow()) - } + return plainformatter(nicks, b.nicksPerRow()) + /* + switch b.Config.Mattermost.NickFormatter { + case "table": + return tableformatter(nicks, b.nicksPerRow(), continued) + default: + return plainformatter(nicks, b.nicksPerRow()) + } + */  } diff --git a/bridge/mattermost/mattermost.go b/bridge/mattermost/mattermost.go index 6482b91..7f56059 100644 --- a/bridge/mattermost/mattermost.go +++ b/bridge/mattermost/mattermost.go @@ -87 +86 @@ import (   "strings"  )   -//type Bridge struct {  type MMhook struct {   mh *matterhook.Client  } @@ -2815 +2716 @@ type MMMessage struct {  type Bmattermost struct {   MMhook   MMapi - *config.Config - Plus bool - Remote chan config.Message + Config *config.Protocol + Plus bool + Remote chan config.Message + name string + origin string + protocol string  }    type FancyLog struct { - irc *log.Entry - mm *log.Entry - xmpp *log.Entry + mm *log.Entry  }    var flog FancyLog @@ -4416 +4417 @@ var flog FancyLog  const Legacy = "legacy"    func init() { - flog.irc = log.WithFields(log.Fields{"module": "irc"})   flog.mm = log.WithFields(log.Fields{"module": "mattermost"}) - flog.xmpp = log.WithFields(log.Fields{"module": "xmpp"})  }   -func New(cfg *config.Config, c chan config.Message) *Bmattermost { +func New(cfg config.Protocol, origin string, c chan config.Message) *Bmattermost {   b := &Bmattermost{} - b.Config = cfg + b.Config = &cfg + b.origin = origin   b.Remote = c - b.Plus = cfg.General.Plus + b.protocol = "mattermost" + b.name = cfg.Name + b.Plus = cfg.UseAPI   b.mmMap = make(map[string]string)   return b  } @@ -6444 +6562 @@ func (b *Bmattermost) Command(cmd string) string {    func (b *Bmattermost) Connect() error {   if !b.Plus { - b.mh = matterhook.New(b.Config.Mattermost.URL, - matterhook.Config{InsecureSkipVerify: b.Config.Mattermost.SkipTLSVerify, - BindAddress: b.Config.Mattermost.BindAddress}) + b.mh = matterhook.New(b.Config.URL, + matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify, + BindAddress: b.Config.BindAddress})   } else { - b.mc = matterclient.New(b.Config.Mattermost.Login, b.Config.Mattermost.Password, - b.Config.Mattermost.Team, b.Config.Mattermost.Server) - b.mc.SkipTLSVerify = b.Config.Mattermost.SkipTLSVerify - b.mc.NoTLS = b.Config.Mattermost.NoTLS - flog.mm.Infof("Trying login %s (team: %s) on %s", b.Config.Mattermost.Login, b.Config.Mattermost.Team, b.Config.Mattermost.Server) + b.mc = matterclient.New(b.Config.Login, b.Config.Password, + b.Config.Team, b.Config.Server) + b.mc.SkipTLSVerify = b.Config.SkipTLSVerify + b.mc.NoTLS = b.Config.NoTLS + flog.mm.Infof("Trying login %s (team: %s) on %s", b.Config.Login, b.Config.Team, b.Config.Server)   err := b.mc.Login()   if err != nil {   return err   }   flog.mm.Info("Login ok") - b.mc.JoinChannel(b.Config.Mattermost.Channel) - for _, val := range b.Config.Channel { - b.mc.JoinChannel(val.Mattermost) - } + /* + b.mc.JoinChannel(b.Config.Channel) + for _, val := range b.Config.Channel { + b.mc.JoinChannel(val.Mattermost) + } + */   go b.mc.WsReceiver()   }   go b.handleMatter()   return nil  }   +func (b *Bmattermost) FullOrigin() string { + return b.protocol + "." + b.origin +} + +func (b *Bmattermost) JoinChannel(channel string) error { + return b.mc.JoinChannel(channel) +} +  func (b *Bmattermost) Name() string { - return "mattermost" + return b.protocol + "." + b.origin +} + +func (b *Bmattermost) Origin() string { + return b.origin +} + +func (b *Bmattermost) Protocol() string { + return b.protocol  }    func (b *Bmattermost) Send(msg config.Message) error {   flog.mm.Infof("mattermost send %#v", msg) - if msg.Origin != "mattermost" { + if msg.Origin != b.origin {   return b.SendType(msg.Username, msg.Text, msg.Channel, "")   }   return nil  }    func (b *Bmattermost) SendType(nick string, message string, channel string, mtype string) error { - if b.Config.Mattermost.PrefixMessagesWithNick { + if b.Config.PrefixMessagesWithNick {   /*if IsMarkup(message) {   message = nick + "\n\n" + message   } else { @@ -1107 +1297 @@ func (b *Bmattermost) SendType(nick string, message string, channel string, mtyp   //}   }   if !b.Plus { - matterMessage := matterhook.OMessage{IconURL: b.Config.Mattermost.IconURL} + matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL}   matterMessage.Channel = channel   matterMessage.UserName = nick   matterMessage.Type = mtype @@ -1417 +1607 @@ func (b *Bmattermost) handleMatter() {   texts := strings.Split(message.Text, "\n")   for _, text := range texts {   flog.mm.Debug("Sending message from " + message.Username + " to " + message.Channel) - b.Remote <- config.Message{Text: text, Username: message.Username, Channel: message.Channel, Origin: "mattermost"} + b.Remote <- config.Message{Text: text, Username: message.Username, Channel: message.Channel, Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()}   }   }  } diff --git a/bridge/slack/slack.go b/bridge/slack/slack.go index 804eaea..fbaff70 100644 --- a/bridge/slack/slack.go +++ b/bridge/slack/slack.go @@ -1541 +1546 @@ type MMMessage struct {   Username string  }   -type bslack struct { +type Bslack struct {   mh *matterhook.Client   sc *slack.Client   // MMapi - *config.Config + Config *config.Protocol   rtm *slack.RTM   Plus bool   Remote chan config.Message + protocol string + origin string   channels []slack.Channel  }    var flog *log.Entry +var protocol = "slack"    func init() { - flog = log.WithFields(log.Fields{"module": "slack"}) + flog = log.WithFields(log.Fields{"module": protocol})  }   -func New(cfg *config.Config, c chan config.Message) *bslack { - b := &bslack{} - b.Config = cfg +func New(config config.Protocol, origin string, c chan config.Message) *Bslack { + b := &Bslack{} + b.Config = &config   b.Remote = c - b.Plus = cfg.Slack.UseAPI + b.protocol = protocol + b.origin = origin + b.Plus = config.UseAPI   return b  }   -func (b *bslack) Command(cmd string) string { +func (b *Bslack) Command(cmd string) string {   return ""  }   -func (b *bslack) Connect() error { +func (b *Bslack) Connect() error {   if !b.Plus { - b.mh = matterhook.New(b.Config.Slack.URL, - matterhook.Config{BindAddress: b.Config.Slack.BindAddress}) + b.mh = matterhook.New(b.Config.URL, + matterhook.Config{BindAddress: b.Config.BindAddress})   } else { - b.sc = slack.New(b.Config.Slack.Token) + b.sc = slack.New(b.Config.Token)   flog.Infof("Trying login on slack with Token")   /*   if err != nil { @@ -6411 +6932 @@ func (b *bslack) Connect() error {   return nil  }   -func (b *bslack) Name() string { - return "slack" +func (b *Bslack) FullOrigin() string { + return b.protocol + "." + b.origin  }   -func (b *bslack) Send(msg config.Message) error { +func (b *Bslack) JoinChannel(channel string) error { + schannel := b.getChannelByName(channel) + if schannel != nil && !schannel.IsMember { + flog.Infof("Joining %s", channel) + b.sc.JoinChannel(schannel.ID) + } + return nil +} + +func (b *Bslack) Name() string { + return b.protocol + "." + b.origin +} + +func (b *Bslack) Protocol() string { + return b.protocol +} + +func (b *Bslack) Origin() string { + return b.origin +} + +func (b *Bslack) Send(msg config.Message) error {   flog.Infof("slack send %#v", msg)   if msg.Origin != "slack" {   return b.SendType(msg.Username, msg.Text, msg.Channel, "") @@ -7612 +10212 @@ func (b *bslack) Send(msg config.Message) error {   return nil  }   -func (b *bslack) SendType(nick string, message string, channel string, mtype string) error { - if b.Config.Slack.PrefixMessagesWithNick { +func (b *Bslack) SendType(nick string, message string, channel string, mtype string) error { + if b.Config.PrefixMessagesWithNick {   message = nick + " " + message   }   if !b.Plus { - matterMessage := matterhook.OMessage{IconURL: b.Config.Slack.IconURL} + matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL}   matterMessage.Channel = channel   matterMessage.UserName = nick   matterMessage.Type = mtype @@ -1007 +1267 @@ func (b *bslack) SendType(nick string, message string, channel string, mtype str   return nil  }   -func (b *bslack) getChannelByName(name string) *slack.Channel { +func (b *Bslack) getChannelByName(name string) *slack.Channel {   if b.channels == nil {   return nil   } @@ -1127 +1387 @@ func (b *bslack) getChannelByName(name string) *slack.Channel {   return nil  }   -func (b *bslack) handleSlack() { +func (b *Bslack) handleSlack() {   flog.Infof("Choosing API based slack connection: %t", b.Plus)   mchan := make(chan *MMMessage)   if b.Plus { @@ -12612 +15212 @@ func (b *bslack) handleSlack() {   texts := strings.Split(message.Text, "\n")   for _, text := range texts {   flog.Debug("Sending message from " + message.Username + " to " + message.Channel) - b.Remote <- config.Message{Text: text, Username: message.Username, Channel: message.Channel, Origin: "slack"} + b.Remote <- config.Message{Text: text, Username: message.Username, Channel: message.Channel, Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()}   }   }  }   -func (b *bslack) handleSlackClient(mchan chan *MMMessage) { +func (b *Bslack) handleSlackClient(mchan chan *MMMessage) {   for msg := range b.rtm.IncomingEvents {   switch ev := msg.Data.(type) {   case *slack.MessageEvent: @@ -15313 +1796 @@ func (b *bslack) handleSlackClient(mchan chan *MMMessage) {   flog.Debugf("%#v", ev.Error())   case *slack.ConnectedEvent:   b.channels = ev.Info.Channels - for _, val := range b.Config.Channel { - channel := b.getChannelByName(val.Slack) - if channel != nil && !channel.IsMember { - flog.Infof("Joining %s", val.Slack) - b.sc.JoinChannel(channel.ID) - } - }   case *slack.InvalidAuthEvent:   flog.Fatalf("Invalid Token %#v", ev)   default: @@ -1677 +1867 @@ func (b *bslack) handleSlackClient(mchan chan *MMMessage) {   }  }   -func (b *bslack) handleMatterHook(mchan chan *MMMessage) { +func (b *Bslack) handleMatterHook(mchan chan *MMMessage) {   for {   message := b.mh.Receive()   flog.Debugf("receiving from slack %#v", message) diff --git a/bridge/xmpp/xmpp.go b/bridge/xmpp/xmpp.go index 5d60247..3de9545 100644 --- a/bridge/xmpp/xmpp.go +++ b/bridge/xmpp/xmpp.go @@ -1064 +1075 @@ import (  )    type Bxmpp struct { - xc *xmpp.Client - xmppMap map[string]string - *config.Config - Remote chan config.Message + xc *xmpp.Client + xmppMap map[string]string + Config *config.Protocol + origin string + protocol string + Remote chan config.Message  }   -type FancyLog struct { - xmpp *log.Entry -} - -type Message struct { - Text string - Channel string - Username string -} - -var flog FancyLog +var flog *log.Entry +var protocol = "xmpp"    func init() { - flog.xmpp = log.WithFields(log.Fields{"module": "xmpp"}) + flog = log.WithFields(log.Fields{"module": protocol})  }   -func New(config *config.Config, c chan config.Message) *Bxmpp { +func New(config config.Protocol, origin string, c chan config.Message) *Bxmpp {   b := &Bxmpp{}   b.xmppMap = make(map[string]string) - b.Config = config + b.Config = &config + b.protocol = protocol + b.origin = origin   b.Remote = c   return b  }    func (b *Bxmpp) Connect() error {   var err error - flog.xmpp.Info("Trying XMPP connection") + flog.Info("Trying XMPP connection")   b.xc, err = b.createXMPP()   if err != nil { - flog.xmpp.Debugf("%#v", err) + flog.Debugf("%#v", err)   return err   } - flog.xmpp.Info("Connection succeeded") - b.setupChannels() + flog.Info("Connection succeeded")   go b.handleXmpp()   return nil  }   +func (b *Bxmpp) FullOrigin() string { + return b.protocol + "." + b.origin +} + +func (b *Bxmpp) JoinChannel(channel string) error { + b.xc.JoinMUCNoHistory(channel+"@"+b.Config.Muc, b.Config.Nick) + return nil +} +  func (b *Bxmpp) Name() string { - return "xmpp" + return b.protocol + "." + b.origin +} + +func (b *Bxmpp) Protocol() string { + return b.protocol +} + +func (b *Bxmpp) Origin() string { + return b.origin  }    func (b *Bxmpp) Send(msg config.Message) error { - b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Xmpp.Muc, Text: msg.Username + msg.Text}) + b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text})   return nil  }    func (b *Bxmpp) createXMPP() (*xmpp.Client, error) {   options := xmpp.Options{ - Host: b.Config.Xmpp.Server, - User: b.Config.Xmpp.Jid, - Password: b.Config.Xmpp.Password, + Host: b.Config.Server, + User: b.Config.Jid, + Password: b.Config.Password,   NoTLS: true,   StartTLS: true,   //StartTLS: false, @@ -8413 +956 @@ func (b *Bxmpp) createXMPP() (*xmpp.Client, error) {   return b.xc, err  }   -func (b *Bxmpp) setupChannels() { - for _, val := range b.Config.Channel { - flog.xmpp.Infof("Joining %s as %s", val.Xmpp, b.Xmpp.Nick) - b.xc.JoinMUCNoHistory(val.Xmpp+"@"+b.Xmpp.Muc, b.Xmpp.Nick) - } -} -  func (b *Bxmpp) xmppKeepAlive() {   go func() {   ticker := time.NewTicker(90 * time.Second) @@ -1219 +1259 @@ func (b *Bxmpp) handleXmpp() error {   if len(s) == 2 {   nick = s[1]   } - if nick != b.Xmpp.Nick { - flog.xmpp.Infof("sending message to remote %s %s %s", nick, v.Text, channel) - b.Remote <- config.Message{Username: nick, Text: v.Text, Channel: channel, Origin: "xmpp"} + if nick != b.Config.Nick { + flog.Infof("sending message to remote %s %s %s", nick, v.Text, channel) + b.Remote <- config.Message{Username: nick, Text: v.Text, Channel: channel, Origin: b.origin, Protocol: b.protocol, FullOrigin: b.FullOrigin()}   }   }   case xmpp.Presence: diff --git a/gateway/gateway.go b/gateway/gateway.go new file mode 100644 index 0000000..54440d6 --- /dev/null +++ b/gateway/gateway.go @@ -00 +1135 @@ +package gateway + +import ( + "github.com/42wim/matterbridge/bridge" + "github.com/42wim/matterbridge/bridge/config" + log "github.com/Sirupsen/logrus" + "strings" +) + +type Gateway struct { + *config.Config + MyConfig *config.Gateway + Bridges []bridge.Bridge + ChannelsOut map[string][]string + ChannelsIn map[string][]string + ignoreNicks map[string][]string + Name string +} + +func New(cfg *config.Config, gateway *config.Gateway) error { + c := make(chan config.Message) + gw := &Gateway{} + gw.Name = gateway.Name + gw.Config = cfg + gw.MyConfig = gateway + for _, br := range gateway.In { + gw.Bridges = append(gw.Bridges, bridge.New(cfg, &br, c)) + } + gw.mapChannels() + gw.mapIgnores() + for _, br := range gw.Bridges { + br.Connect() + for _, channel := range gw.ChannelsOut[br.FullOrigin()] { + br.JoinChannel(channel) + } + } + gw.handleReceive(c) + return nil +} + +func (gw *Gateway) handleReceive(c chan config.Message) { + for { + select { + case msg := <-c: + for _, br := range gw.Bridges { + gw.handleMessage(msg, br) + } + } + } +} + +func (gw *Gateway) mapChannels() error { + m := make(map[string][]string) + for _, br := range gw.MyConfig.Out { + m[br.Account] = append(m[br.Account], br.Channel) + } + gw.ChannelsOut = m + m = nil + m = make(map[string][]string) + for _, br := range gw.MyConfig.In { + m[br.Account] = append(m[br.Account], br.Channel) + } + gw.ChannelsIn = m + return nil +} + +func (gw *Gateway) mapIgnores() { + m := make(map[string][]string) + for _, br := range gw.MyConfig.In { + accInfo := strings.Split(br.Account, ".") + m[br.Account] = strings.Fields(gw.Config.IRC[accInfo[1]].IgnoreNicks) + } + gw.ignoreNicks = m +} + +func (gw *Gateway) getDestChannel(msg *config.Message, dest string) []string { + return gw.ChannelsOut[dest] +} + +func (gw *Gateway) handleMessage(msg config.Message, dest bridge.Bridge) { + if gw.ignoreMessage(&msg) { + return + } + channels := gw.getDestChannel(&msg, dest.FullOrigin()) + for _, channel := range channels { + // do not send the message to the bridge we come from if also the channel is the same + if msg.FullOrigin == dest.FullOrigin() && msg.Channel == channel { + log.Debug("continue", msg.Protocol, msg.Origin, dest.Protocol(), dest.Origin()) + continue + } + msg.Channel = channel + if msg.Channel == "" { + log.Debug("empty channel") + return + } + gw.modifyMessage(&msg, dest) + log.Debugf("sending %#v from %s to %s", msg, msg.Origin, dest.Origin()) + dest.Send(msg) + } +} + +func (gw *Gateway) ignoreMessage(msg *config.Message) bool { + // should we discard messages ? + for _, entry := range gw.ignoreNicks[msg.FullOrigin] { + if msg.Username == entry { + return true + } + } + return false +} + +func setNickFormat(msg *config.Message, format string) { + if format == "" { + msg.Username = msg.Protocol + "." + msg.Origin + "-" + msg.Username + ": " + return + } + msg.Username = strings.Replace(format, "{NICK}", msg.Username, -1) + msg.Username = strings.Replace(msg.Username, "{BRIDGE}", msg.Origin, -1) + msg.Username = strings.Replace(msg.Username, "{PROTOCOL}", msg.Protocol, -1) +} + +func (gw *Gateway) modifyMessage(msg *config.Message, dest bridge.Bridge) { + switch dest.Protocol() { + case "irc": + setNickFormat(msg, gw.Config.IRC[dest.Origin()].RemoteNickFormat) + case "gitter": + setNickFormat(msg, gw.Config.Gitter[dest.Origin()].RemoteNickFormat) + case "xmpp": + setNickFormat(msg, gw.Config.Xmpp[dest.Origin()].RemoteNickFormat) + case "mattermost": + setNickFormat(msg, gw.Config.Mattermost[dest.Origin()].RemoteNickFormat) + case "slack": + setNickFormat(msg, gw.Config.Slack[dest.Origin()].RemoteNickFormat) + } +} diff --git a/matterbridge.go b/matterbridge.go index ebf6dda..470c605 100644 --- a/matterbridge.go +++ b/matterbridge.go @@ -322 +321 @@ package main  import (   "flag"   "fmt" - "github.com/42wim/matterbridge/bridge"   "github.com/42wim/matterbridge/bridge/config" + "github.com/42wim/matterbridge/gateway"   log "github.com/Sirupsen/logrus"  )   -var version = "0.6.1" +var version = "0.7.0-dev"    func init() {   log.SetFormatter(&log.TextFormatter{FullTimestamp: true})  }    func main() { - flagConfig := flag.String("conf", "matterbridge.conf", "config file") + flagConfig := flag.String("conf", "matterbridge.toml", "config file")   flagDebug := flag.Bool("debug", false, "enable debug")   flagVersion := flag.Bool("version", false, "show version") - flagPlus := flag.Bool("plus", false, "running using API instead of webhooks (deprecated, set Plus flag in [general] config)")   flag.Parse()   if *flagVersion {   fmt.Println("version:", version) @@ -3111 +3017 @@ func main() {   }   fmt.Println("running version", version)   cfg := config.NewConfig(*flagConfig) - if *flagPlus { - cfg.General.Plus = true - } - err := bridge.NewBridge(cfg) - if err != nil { - log.Debugf("starting bridge failed %#v", err) + for _, gw := range cfg.Gateway { + if !gw.Enable { + continue + } + fmt.Printf("starting gateway %#v\n", gw.Name) + go func(gw config.Gateway) { + err := gateway.New(cfg, &gw) + if err != nil { + log.Debugf("starting gateway failed %#v", err) + } + }(gw)   } + select {}  }