summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bridge/bridge.go534
-rw-r--r--bridge/config/config.go (renamed from bridge/config.go)24
-rw-r--r--bridge/irc/helper.go (renamed from bridge/helper.go)2
-rw-r--r--bridge/irc/irc.go214
-rw-r--r--bridge/mattermost/helper.go59
-rw-r--r--bridge/mattermost/mattermost.go194
-rw-r--r--bridge/xmpp/xmpp.go137
-rw-r--r--matterbridge.conf.sample6
-rw-r--r--matterbridge.go5
-rw-r--r--vendor/github.com/thoj/go-ircevent/irc.go1
-rw-r--r--vendor/github.com/thoj/go-ircevent/irc_callback.go4
-rw-r--r--vendor/github.com/thoj/go-ircevent/irc_sasl.go (renamed from vendor/github.com/thoj/go-ircevent/sasl.go)0
-rw-r--r--vendor/manifest2
13 files changed, 699 insertions, 483 deletions
diff --git a/bridge/bridge.go b/bridge/bridge.go
index 76654c40..898014e7 100644
--- a/bridge/bridge.go
+++ b/bridge/bridge.go
@@ -1,57 +1,22 @@
package bridge
import (
- "crypto/tls"
- "github.com/42wim/matterbridge/matterclient"
- "github.com/42wim/matterbridge/matterhook"
+ //"fmt"
+ "github.com/42wim/matterbridge/bridge/config"
+ "github.com/42wim/matterbridge/bridge/irc"
+ "github.com/42wim/matterbridge/bridge/mattermost"
+ "github.com/42wim/matterbridge/bridge/xmpp"
log "github.com/Sirupsen/logrus"
- "github.com/mattn/go-xmpp"
- "github.com/peterhellberg/giphy"
- ircm "github.com/sorcix/irc"
- "github.com/thoj/go-ircevent"
- "regexp"
- "sort"
- "strconv"
"strings"
- "time"
)
-//type Bridge struct {
-type MMhook struct {
- mh *matterhook.Client
-}
-
-type MMapi struct {
- mc *matterclient.MMClient
- mmMap map[string]string
- mmIgnoreNicks []string
-}
-
-type MMxmpp struct {
- xc *xmpp.Client
- xmppMap map[string]string
-}
-type MMirc struct {
- i *irc.Connection
- ircNick string
- ircMap map[string]string
- names map[string][]string
- ircIgnoreNicks []string
-}
-
-type MMMessage struct {
- Text string
- Channel string
- Username string
-}
-
type Bridge struct {
- MMhook
- MMapi
- MMirc
- MMxmpp
- *Config
- kind string
+ *config.Config
+ Source string
+ Bridges []Bridger
+ kind string
+ Channels []map[string]string
+ ignoreNicks map[string][]string
}
type FancyLog struct {
@@ -60,6 +25,12 @@ type FancyLog struct {
xmpp *log.Entry
}
+type Bridger interface {
+ Send(msg config.Message) error
+ Name() string
+ //Command(cmd string) string
+}
+
var flog FancyLog
const Legacy = "legacy"
@@ -70,455 +41,82 @@ func initFLog() {
flog.xmpp = log.WithFields(log.Fields{"module": "xmpp"})
}
-func NewBridge(name string, config *Config, kind string) *Bridge {
+func NewBridge(name string, cfg *config.Config, kind string) *Bridge {
+ c := make(chan config.Message)
initFLog()
b := &Bridge{}
- b.Config = config
- b.kind = kind
- b.mmMap = make(map[string]string)
- if b.Config.General.Irc {
- b.ircNick = b.Config.IRC.Nick
- b.ircMap = make(map[string]string)
- b.MMirc.names = make(map[string][]string)
- b.ircIgnoreNicks = strings.Fields(b.Config.IRC.IgnoreNicks)
- b.mmIgnoreNicks = strings.Fields(b.Config.Mattermost.IgnoreNicks)
- for _, val := range b.Config.Channel {
- b.ircMap[val.IRC] = val.Mattermost
- b.mmMap[val.Mattermost] = val.IRC
- }
+ b.Config = cfg
+ if cfg.General.Irc {
+ b.Bridges = append(b.Bridges, birc.New(cfg, c))
}
- if b.Config.General.Xmpp {
- b.xmppMap = make(map[string]string)
- for _, val := range b.Config.Channel {
- b.xmppMap[val.Xmpp] = val.Mattermost
- b.mmMap[val.Mattermost] = val.Xmpp
- }
+ if cfg.General.Mattermost {
+ b.Bridges = append(b.Bridges, bmattermost.New(cfg, c))
}
-
- if kind == Legacy {
- b.mh = matterhook.New(b.Config.Mattermost.URL,
- matterhook.Config{InsecureSkipVerify: b.Config.Mattermost.SkipTLSVerify,
- BindAddress: b.Config.Mattermost.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)
- err := b.mc.Login()
- if err != nil {
- flog.mm.Fatal("Can not connect", err)
- }
- flog.mm.Info("Login ok")
- b.mc.JoinChannel(b.Config.Mattermost.Channel)
- for _, val := range b.Config.Channel {
- b.mc.JoinChannel(val.Mattermost)
- }
- go b.mc.WsReceiver()
+ if cfg.General.Xmpp {
+ b.Bridges = append(b.Bridges, bxmpp.New(cfg, c))
}
-
- if b.Config.General.Irc {
- flog.irc.Info("Trying IRC connection")
- b.i = b.createIRC(name)
- flog.irc.Info("Connection succeeded")
- }
- if b.Config.General.Xmpp {
- var err error
- flog.xmpp.Info("Trying XMPP connection")
- b.xc, err = b.createXMPP()
- if err != nil {
- flog.xmpp.Debugf("%#v", err)
- panic("xmpp failure")
- }
- flog.xmpp.Info("Connection succeeded")
- b.setupChannels()
- go b.handleXmpp()
- }
-
- go b.handleMatter()
+ b.mapChannels()
+ b.mapIgnores()
+ go b.handleReceive(c)
return b
}
-func (b *Bridge) createIRC(name string) *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
- }
- i.AddCallback(ircm.RPL_WELCOME, b.handleNewConnection)
- err := i.Connect(b.Config.IRC.Server)
- if err != nil {
- flog.irc.Fatal(err)
- }
- return i
-}
-
-func (b *Bridge) createXMPP() (*xmpp.Client, error) {
- options := xmpp.Options{
- Host: b.Config.Xmpp.Server,
- User: b.Config.Xmpp.Jid,
- Password: b.Config.Xmpp.Password,
- NoTLS: true,
- StartTLS: true,
- Debug: true,
- Session: true,
- Status: "",
- StatusMessage: "",
- Resource: "",
- InsecureAllowUnencryptedAuth: false,
- }
- var err error
- b.xc, err = options.NewClient()
- return b.xc, err
-}
-
-func (b *Bridge) handleNewConnection(event *irc.Event) {
- flog.irc.Info("Registering callbacks")
- i := b.i
- b.ircNick = event.Arguments[0]
- i.AddCallback("PRIVMSG", b.handlePrivMsg)
- i.AddCallback("CTCP_ACTION", b.handlePrivMsg)
- i.AddCallback(ircm.RPL_ENDOFNAMES, b.endNames)
- i.AddCallback(ircm.RPL_NAMREPLY, b.storeNames)
- i.AddCallback(ircm.RPL_TOPICWHOTIME, b.handleTopicWhoTime)
- 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("PING", func(e *irc.Event) {
- i.SendRaw("PONG :" + e.Message())
- flog.irc.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 *Bridge) setupChannels() {
- if b.Config.General.Irc {
- for _, val := range b.Config.Channel {
- flog.irc.Infof("Joining %s as %s", val.IRC, b.ircNick)
- b.i.Join(val.IRC)
- }
- }
- if b.Config.General.Xmpp {
- 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 *Bridge) handleIrcBotCommand(event *irc.Event) bool {
- parts := strings.Fields(event.Message())
- exp, _ := regexp.Compile("[:,]+$")
- channel := event.Arguments[0]
- command := ""
- if len(parts) == 2 {
- command = parts[1]
- }
- if exp.ReplaceAllString(parts[0], "") == b.ircNick {
- switch command {
- case "users":
- usernames := b.mc.UsernamesInChannel(b.getMMChannel(channel))
- sort.Strings(usernames)
- b.i.Privmsg(channel, "Users on Mattermost: "+strings.Join(usernames, ", "))
- default:
- b.i.Privmsg(channel, "Valid commands are: [users, help]")
- }
- return true
- }
- return false
-}
-
-func (b *Bridge) ircNickFormat(nick string) string {
- if nick == b.ircNick {
- return nick
- }
- if b.Config.Mattermost.RemoteNickFormat == nil {
- return "irc-" + nick
- }
- return strings.Replace(*b.Config.Mattermost.RemoteNickFormat, "{NICK}", nick, -1)
-}
-
-func (b *Bridge) handlePrivMsg(event *irc.Event) {
- flog.irc.Debugf("handlePrivMsg() %s %s", event.Nick, event.Message())
- if b.ignoreMessage(event.Nick, event.Message(), "irc") {
- return
- }
- if b.handleIrcBotCommand(event) {
- return
- }
- msg := ""
- if event.Code == "CTCP_ACTION" {
- msg = event.Nick + " "
- }
- msg += event.Message()
- b.Send(b.ircNickFormat(event.Nick), msg, b.getMMChannel(event.Arguments[0]))
-}
-
-func (b *Bridge) 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 *Bridge) 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)
- }
-}
-
-func (b *Bridge) nicksPerRow() int {
- if b.Config.Mattermost.NicksPerRow < 1 {
- return 4
- }
- return b.Config.Mattermost.NicksPerRow
-}
-
-func (b *Bridge) 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())
- }
-}
-
-func (b *Bridge) storeNames(event *irc.Event) {
- channel := event.Arguments[2]
- b.MMirc.names[channel] = append(
- b.MMirc.names[channel],
- strings.Split(strings.TrimSpace(event.Message()), " ")...)
-}
-
-func (b *Bridge) endNames(event *irc.Event) {
- channel := event.Arguments[1]
- sort.Strings(b.MMirc.names[channel])
- maxNamesPerPost := (300 / b.nicksPerRow()) * b.nicksPerRow()
- continued := false
- for len(b.MMirc.names[channel]) > maxNamesPerPost {
- b.Send(
- b.ircNick,
- b.formatnicks(b.MMirc.names[channel][0:maxNamesPerPost], continued),
- b.getMMChannel(channel))
- b.MMirc.names[channel] = b.MMirc.names[channel][maxNamesPerPost:]
- continued = true
- }
- b.Send(b.ircNick, b.formatnicks(b.MMirc.names[channel], continued), b.getMMChannel(channel))
- b.MMirc.names[channel] = nil
-}
-
-func (b *Bridge) 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])
- }
- 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))
-}
-
-func (b *Bridge) handleOther(event *irc.Event) {
- flog.irc.Debugf("%#v", event)
-}
-
-func (b *Bridge) Send(nick string, message string, channel string) error {
- return b.SendType(nick, message, channel, "")
-}
-
-func (b *Bridge) SendType(nick string, message string, channel string, mtype string) error {
- if b.Config.Mattermost.PrefixMessagesWithNick {
- if IsMarkup(message) {
- message = nick + "\n\n" + message
- } else {
- message = nick + " " + message
- }
- }
- if b.kind == Legacy {
- matterMessage := matterhook.OMessage{IconURL: b.Config.Mattermost.IconURL}
- matterMessage.Channel = channel
- matterMessage.UserName = nick
- matterMessage.Type = mtype
- matterMessage.Text = message
- err := b.mh.Send(matterMessage)
- if err != nil {
- flog.mm.Info(err)
- return err
- }
- flog.mm.Debug("->mattermost channel: ", channel, " ", message)
- return nil
- }
- flog.mm.Debug("->mattermost channel: ", channel, " ", message)
- b.mc.PostMessage(channel, message)
- return nil
-}
-
-func (b *Bridge) handleMatterHook(mchan chan *MMMessage) {
+func (b *Bridge) handleReceive(c chan config.Message) {
for {
- message := b.mh.Receive()
- flog.mm.Debugf("receiving from matterhook %#v", message)
- m := &MMMessage{}
- m.Username = message.UserName
- m.Text = message.Text
- m.Channel = message.ChannelName
- mchan <- m
- }
-}
-
-func (b *Bridge) handleMatterClient(mchan chan *MMMessage) {
- for message := range b.mc.MessageChan {
- // do not post our own messages back to irc
- if message.Raw.Action == "posted" && b.mc.User.Username != message.Username {
- flog.mm.Debugf("receiving from matterclient %#v", message)
- m := &MMMessage{}
- m.Username = message.Username
- m.Channel = message.Channel
- m.Text = message.Text
- mchan <- m
- }
- }
-}
-
-func (b *Bridge) handleMatter() {
- flog.mm.Infof("Choosing Mattermost connection type %s", b.kind)
- mchan := make(chan *MMMessage)
- if b.kind == Legacy {
- go b.handleMatterHook(mchan)
- } else {
- go b.handleMatterClient(mchan)
- }
- flog.mm.Info("Start listening for Mattermost messages")
- for message := range mchan {
- var username string
- if b.ignoreMessage(message.Username, message.Text, "mattermost") {
- continue
- }
- if b.Config.General.Irc {
- username = message.Username + ": "
- if b.Config.IRC.RemoteNickFormat != "" {
- username = strings.Replace(b.Config.IRC.RemoteNickFormat, "{NICK}", message.Username, -1)
+ select {
+ case msg := <-c:
+ m := b.getChannel(msg.Origin, msg.Channel)
+ if m == nil {
+ continue
}
- }
- cmds := strings.Fields(message.Text)
- // empty message
- if len(cmds) == 0 {
- continue
- }
- cmd := cmds[0]
- switch cmd {
- case "!users":
- flog.mm.Info("Received !users from ", message.Username)
- if b.Config.General.Irc {
- b.i.SendRaw("NAMES " + b.getIRCChannel(message.Channel))
- }
- continue
- case "!gif":
- message.Text = b.giphyRandom(strings.Fields(strings.Replace(message.Text, "!gif ", "", 1)))
- if b.Config.General.Irc {
- b.Send(b.ircNick, message.Text, b.getIRCChannel(message.Channel))
- }
- continue
- }
- texts := strings.Split(message.Text, "\n")
- for _, text := range texts {
- flog.mm.Debug("Sending message from " + message.Username + " to " + message.Channel)
- if b.Config.General.Irc {
- b.i.Privmsg(b.getIRCChannel(message.Channel), username+text)
- }
- if b.Config.General.Xmpp {
- b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: "testje@c.sw.be", Text: username + text})
-
+ for _, br := range b.Bridges {
+ if b.ignoreMessage(msg.Username, msg.Text, msg.Origin) {
+ continue
+ }
+ // do not send to originated bridge
+ if br.Name() != msg.Origin {
+ msg.Channel = m[br.Name()]
+ br.Send(msg)
+ }
}
}
}
}
-func (b *Bridge) giphyRandom(query []string) string {
- g := giphy.DefaultClient
- if b.Config.General.GiphyAPIKey != "" {
- g.APIKey = b.Config.General.GiphyAPIKey
- }
- res, err := g.Random(query)
- if err != nil {
- return "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
+ b.Channels = append(b.Channels, m)
}
- return res.Data.FixedHeightDownsampledURL
+ return nil
}
-func (b *Bridge) getMMChannel(channel string) string {
- var mmChannel string
- if b.Config.General.Irc {
- mmChannel = b.ircMap[channel]
- }
- if b.Config.General.Xmpp {
- mmChannel = b.xmppMap[channel]
- }
- if b.kind == Legacy {
- return mmChannel
- }
- return b.mc.GetChannelId(mmChannel, "")
+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.Mattermost.IgnoreNicks)
+ b.ignoreNicks = m
}
-func (b *Bridge) getIRCChannel(mmChannel string) string {
- return b.mmMap[mmChannel]
+func (b *Bridge) getChannel(src, name string) map[string]string {
+ for _, v := range b.Channels {
+ if v[src] == name {
+ return v
+ }
+ }
+ return nil
}
func (b *Bridge) ignoreMessage(nick string, message string, protocol string) bool {
- var ignoreNicks = b.mmIgnoreNicks
- if protocol == "irc" {
- ignoreNicks = b.ircIgnoreNicks
- }
// should we discard messages ?
- for _, entry := range ignoreNicks {
+ for _, entry := range b.ignoreNicks[protocol] {
if nick == entry {
return true
}
}
return false
}
-
-func (b *Bridge) xmppKeepAlive() {
- go func() {
- ticker := time.NewTicker(90 * time.Second)
- for {
- select {
- case <-ticker.C:
- b.xc.Send(xmpp.Chat{})
- }
- }
- }()
-}
-
-func (b *Bridge) handleXmpp() error {
- for {
- m, err := b.xc.Recv()
- if err != nil {
- return err
- }
- switch v := m.(type) {
- case xmpp.Chat:
- var channel, nick string
- if v.Type == "groupchat" {
- s := strings.Split(v.Remote, "@")
- if len(s) == 2 {
- channel = s[0]
- }
- s = strings.Split(s[1], "/")
- if len(s) == 2 {
- nick = s[1]
- }
- b.Send(nick, v.Text, b.getMMChannel(channel))
- }
- case xmpp.Presence:
- // do nothing
- }
- }
-}
diff --git a/bridge/config.go b/bridge/config/config.go
index 274d4261..131ff95e 100644
--- a/bridge/config.go
+++ b/bridge/config/config.go
@@ -1,4 +1,4 @@
-package bridge
+package config
import (
"gopkg.in/gcfg.v1"
@@ -6,6 +6,13 @@ import (
"log"
)
+type Message struct {
+ Text string
+ Channel string
+ Username string
+ Origin string
+}
+
type Config struct {
IRC struct {
UseTLS bool
@@ -34,16 +41,17 @@ type Config struct {
Team string
Login string
Password string
- RemoteNickFormat *string
+ RemoteNickFormat string
IgnoreNicks string
NoTLS bool
}
Xmpp struct {
- Jid string
- Password string
- Server string
- Muc string
- Nick string
+ Jid string
+ Password string
+ Server string
+ Muc string
+ Nick string
+ RemoteNickFormat string
}
Channel map[string]*struct {
IRC string
@@ -54,6 +62,8 @@ type Config struct {
GiphyAPIKey string
Xmpp bool
Irc bool
+ Mattermost bool
+ Plus bool
}
}
diff --git a/bridge/helper.go b/bridge/irc/helper.go
index 7669ad57..355dc552 100644
--- a/bridge/helper.go
+++ b/bridge/irc/helper.go
@@ -1,4 +1,4 @@
-package bridge
+package birc
import (
"strings"
diff --git a/bridge/irc/irc.go b/bridge/irc/irc.go
new file mode 100644
index 00000000..d37149a0
--- /dev/null
+++ b/bridge/irc/irc.go
@@ -0,0 +1,214 @@
+package birc
+
+import (
+ "crypto/tls"
+ "github.com/42wim/matterbridge/bridge/config"
+ log "github.com/Sirupsen/logrus"
+ ircm "github.com/sorcix/irc"
+ "github.com/thoj/go-ircevent"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+)
+
+//type Bridge struct {
+type Birc struct {
+ i *irc.Connection
+ ircNick string
+ ircMap map[string]string
+ names map[string][]string
+ ircIgnoreNicks []string
+ *config.Config
+ kind string
+ Remote chan config.Message
+}
+
+type FancyLog struct {
+ irc *log.Entry
+ mm *log.Entry
+ xmpp *log.Entry
+}
+
+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(config *config.Config, c chan config.Message) *Birc {
+ b := &Birc{}
+ b.Config = config
+ b.kind = "legacy"
+ b.Remote = c
+ b.ircNick = b.Config.IRC.Nick
+ b.ircMap = make(map[string]string)
+ b.names = make(map[string][]string)
+ b.ircIgnoreNicks = strings.Fields(b.Config.IRC.IgnoreNicks)
+ flog.irc.Info("Trying IRC connection")
+ b.i = b.connect()
+ flog.irc.Info("Connection succeeded")
+ return b
+}
+
+func (b *Birc) Command(msg *config.Message) string {
+ switch msg.Text {
+ case "!users":
+ b.i.SendRaw("NAMES " + msg.Channel)
+ }
+ return ""
+}
+
+func (b *Birc) Name() string {
+ return "irc"
+}
+
+func (b *Birc) Send(msg config.Message) error {
+ if msg.Origin == "irc" {
+ return nil
+ }
+ if strings.HasPrefix(msg.Text, "!") {
+ b.Command(&msg)
+ return nil
+ }
+ username := b.ircNickFormat(msg.Username)
+ b.i.Privmsg(msg.Channel, username+msg.Text)
+ return nil
+}
+
+func (b *Birc) connect() *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
+ }
+ i.AddCallback(ircm.RPL_WELCOME, b.handleNewConnection)
+ err := i.Connect(b.Config.IRC.Server)
+ if err != nil {
+ flog.irc.Fatal(err)
+ }
+ return i
+}
+
+func (b *Birc) endNames(event *irc.Event) {
+ channel := event.Arguments[1]
+ sort.Strings(b.names[channel])
+ 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.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.names[channel] = nil
+}
+
+func (b *Birc) handleNewConnection(event *irc.Event) {
+ flog.irc.Info("Registering callbacks")
+ i := b.i
+ b.ircNick = event.Arguments[0]
+ i.AddCallback("PRIVMSG", b.handlePrivMsg)
+ i.AddCallback("CTCP_ACTION", b.handlePrivMsg)
+ i.AddCallback(ircm.RPL_TOPICWHOTIME, b.handleTopicWhoTime)
+ 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("PING", func(e *irc.Event) {
+ i.SendRaw("PONG :" + e.Message())
+ flog.irc.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)
+ }
+}
+
+func (b *Birc) handleOther(event *irc.Event) {
+ flog.irc.Debugf("%#v", event)
+}
+
+func (b *Birc) handlePrivMsg(event *irc.Event) {
+ flog.irc.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"}
+}
+
+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])
+ }
+ 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))
+}
+
+func (b *Birc) ircNickFormat(nick string) string {
+ flog.irc.Debug("ircnick", nick)
+ if nick == b.ircNick {
+ return nick
+ }
+ if b.Config.IRC.RemoteNickFormat == "" {
+ return "irc-" + nick
+ }
+ return strings.Replace(b.Config.IRC.RemoteNickFormat, "{NICK}", nick, -1)
+}
+
+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)
+ }
+}
+
+func (b *Birc) storeNames(event *irc.Event) {
+ channel := event.Arguments[2]
+ b.names[channel] = append(
+ b.names[channel],
+ strings.Split(strings.TrimSpace(event.Message()), " ")...)
+}
+
+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())
+ }
+}
diff --git a/bridge/mattermost/helper.go b/bridge/mattermost/helper.go
new file mode 100644
index 00000000..2caba719
--- /dev/null
+++ b/bridge/mattermost/helper.go
@@ -0,0 +1,59 @@
+package bmattermost
+
+import (
+ "strings"
+)
+
+func tableformatter(nicks []string, nicksPerRow int, continued bool) string {
+ result := "|IRC users"
+ if continued {
+ result = "|(continued)"
+ }
+ for i := 0; i < 2; i++ {
+ for j := 1; j <= nicksPerRow && j <= len(nicks); j++ {
+ if i == 0 {
+ result += "|"
+ } else {
+ result += ":-|"
+ }
+ }
+ result += "\r\n|"
+ }
+ result += nicks[0] + "|"
+ for i := 1; i < len(nicks); i++ {
+ if i%nicksPerRow == 0 {
+ result += "\r\n|" + nicks[i] + "|"
+ } else {
+ result += nicks[i] + "|"
+ }
+ }
+ return result
+}
+
+func plainformatter(nicks []string, nicksPerRow int) string {
+ return strings.Join(nicks, ", ") + " currently on IRC"
+}
+
+func IsMarkup(message string) bool {
+ switch message[0] {
+ case '|':
+ fallthrough
+ case '#':
+ fallthrough
+ case '_':
+ fallthrough
+ case '*':
+ fallthrough
+ case '~':
+ fallthrough
+ case '-':
+ fallthrough
+ case ':':
+ fallthrough
+ case '>':
+ fallthrough
+ case '=':
+ return true
+ }
+ return false
+}
diff --git a/bridge/mattermost/mattermost.go b/bridge/mattermost/mattermost.go
new file mode 100644
index 00000000..b4057d57
--- /dev/null
+++ b/bridge/mattermost/mattermost.go
@@ -0,0 +1,194 @@
+package bmattermost
+
+import (
+ "github.com/42wim/matterbridge/bridge/config"
+ "github.com/42wim/matterbridge/matterclient"
+ "github.com/42wim/matterbridge/matterhook"
+ log "github.com/Sirupsen/logrus"
+ "strings"
+)
+
+//type Bridge struct {
+type MMhook struct {
+ mh *matterhook.Client
+}
+
+type MMapi struct {
+ mc *matterclient.MMClient
+ mmMap map[string]string
+ mmIgnoreNicks []string
+}
+
+type MMMessage struct {
+ Text string
+ Channel string
+ Username string
+}
+
+type Bmattermost struct {
+ MMhook
+ MMapi
+ *config.Config
+ Plus bool
+ Remote chan config.Message
+}
+
+type FancyLog struct {
+ irc *log.Entry
+ mm *log.Entry
+ xmpp *log.Entry
+}
+
+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 {
+ b := &Bmattermost{}
+ b.Config = cfg
+ b.Remote = c
+ b.Plus = cfg.General.Plus
+ b.mmMap = make(map[string]string)
+ if !b.Plus {
+ b.mh = matterhook.New(b.Config.Mattermost.URL,
+ matterhook.Config{InsecureSkipVerify: b.Config.Mattermost.SkipTLSVerify,
+ BindAddress: b.Config.Mattermost.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)
+ err := b.mc.Login()
+ if err != nil {
+ flog.mm.Fatal("Can not connect", err)
+ }
+ flog.mm.Info("Login ok")
+ b.mc.JoinChannel(b.Config.Mattermost.Channel)
+ for _, val := range b.Config.Channel {
+ b.mc.JoinChannel(val.Mattermost)
+ }
+ go b.mc.WsReceiver()
+ }
+ go b.handleMatter()
+ return b
+}
+
+func (b *Bmattermost) Command(cmd string) string {
+ return ""
+}
+
+func (b *Bmattermost) Name() string {
+ return "mattermost"
+}
+
+func (b *Bmattermost) Send(msg config.Message) error {
+ flog.mm.Infof("mattermost send %#v", msg)
+ if msg.Origin != "mattermost" {
+ username := msg.Username + ": "
+ if b.Config.Mattermost.RemoteNickFormat != "" {
+ username = strings.Replace(b.Config.Mattermost.RemoteNickFormat, "{NICK}", msg.Username, -1)
+ }
+ return b.SendType(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 IsMarkup(message) {
+ message = nick + "\n\n" + message
+ } else {
+ */
+ message = nick + " " + message
+ //}
+ }
+ if !b.Plus {
+ matterMessage := matterhook.OMessage{IconURL: b.Config.Mattermost.IconURL}
+ matterMessage.Channel = channel
+ matterMessage.UserName = nick
+ matterMessage.Type = mtype
+ matterMessage.Text = message
+ err := b.mh.Send(matterMessage)
+ if err != nil {
+ flog.mm.Info(err)
+ return err
+ }
+ flog.mm.Debug("->mattermost channel: ", channel, " ", message)
+ return nil
+ }
+ flog.mm.Debug("->mattermost channel plus: ", channel, " ", message)
+ b.mc.PostMessage(b.mc.GetChannelId(channel, ""), message)
+ return nil
+}
+
+func (b *Bmattermost) handleMatter() {
+ flog.mm.Infof("Choosing Mattermost connection type %s", b.Plus)
+ mchan := make(chan *MMMessage)
+ if b.Plus {
+ go b.handleMatterClient(mchan)
+ } else {
+ go b.handleMatterHook(mchan)
+ }
+ flog.mm.Info("Start listening for Mattermost messages")
+ for message := range mchan {
+ /*
+ if b.ignoreMessage(message.Username, message.Text, "mattermost") {
+ continue
+ }
+ */
+ 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"}
+ }
+ }
+}
+
+func (b *Bmattermost) handleMatterClient(mchan chan *MMMessage) {
+ for message := range b.mc.MessageChan {
+ // do not post our own messages back to irc
+ if message.Raw.Action == "posted" && b.mc.User.Username != message.Username {
+ flog.mm.Debugf("receiving from matterclient %#v", message)
+ m := &MMMessage{}
+ m.Username = message.Username
+ m.Channel = message.Channel
+ m.Text = message.Text
+ mchan <- m
+ }
+ }
+}
+
+func (b *Bmattermost) handleMatterHook(mchan chan *MMMessage) {
+ for {
+ message := b.mh.Receive()
+ flog.mm.Debugf("receiving from matterhook %#v", message)
+ m := &MMMessage{}
+ m.Username = message.UserName
+ m.Text = message.Text
+ m.Channel = message.ChannelName
+ mchan <- m
+ }
+}
+
+func (b *Bmattermost) 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())
+ }
+}
+
+func (b *Bmattermost) nicksPerRow() int {
+ if b.Config.Mattermost.NicksPerRow < 1 {
+ return 4
+ }
+ return b.Config.Mattermost.NicksPerRow
+}
diff --git a/bridge/xmpp/xmpp.go b/bridge/xmpp/xmpp.go
new file mode 100644
index 00000000..32cf999b
--- /dev/null
+++ b/bridge/xmpp/xmpp.go
@@ -0,0 +1,137 @@
+package bxmpp
+
+import (
+ "github.com/42wim/matterbridge/bridge/config"
+ log "github.com/Sirupsen/logrus"
+ "github.com/mattn/go-xmpp"
+
+ "strings"
+ "time"
+)
+
+type Bxmpp struct {
+ xc *xmpp.Client
+ xmppMap map[string]string
+ *config.Config
+ Remote chan config.Message
+}
+
+type FancyLog struct {
+ irc *log.Entry
+ mm *log.Entry
+ xmpp *log.Entry
+}
+
+type Message struct {
+ Text string
+ Channel string
+ Username string
+}
+
+var flog FancyLog
+
+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(config *config.Config, c chan config.Message) *Bxmpp {
+ b := &Bxmpp{}
+ b.xmppMap = make(map[string]string)
+ var err error
+ b.Config = config
+ b.Remote = c
+ flog.xmpp.Info("Trying XMPP connection")
+ b.xc, err = b.createXMPP()
+ if err != nil {
+ flog.xmpp.Debugf("%#v", err)
+ panic("xmpp failure")
+ }
+ flog.xmpp.Info("Connection succeeded")
+ b.setupChannels()
+ go b.handleXmpp()
+ return b
+}
+
+func (b *Bxmpp) Name() string {
+ return "xmpp"
+}
+
+func (b *Bxmpp) Send(msg config.Message) error {
+ username := msg.Username + ": "
+ if b.Config.Xmpp.RemoteNickFormat != "" {
+ username = strings.Replace(b.Config.Xmpp.RemoteNickFormat, "{NICK}", msg.Username, -1)
+ }
+ b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Xmpp.Muc, Text: 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,
+ NoTLS: true,
+ StartTLS: true,
+ //StartTLS: false,
+ Debug: true,
+ Session: true,
+ Status: "",
+ StatusMessage: "",
+ Resource: "",
+ InsecureAllowUnencryptedAuth: false,
+ //InsecureAllowUnencryptedAuth: true,
+ }
+ var err error
+ b.xc, err = options.NewClient()
+ 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)
+ for {
+ select {
+ case <-ticker.C:
+ b.xc.Send(xmpp.Chat{})
+ }
+ }
+ }()
+}
+
+func (b *Bxmpp) handleXmpp() error {
+ for {
+ m, err := b.xc.Recv()
+ if err != nil {
+ return err
+ }
+ switch v := m.(type) {
+ case xmpp.Chat:
+ var channel, nick string
+ if v.Type == "groupchat" {
+ s := strings.Split(v.Remote, "@")
+ if len(s) == 2 {
+ channel = s[0]
+ }
+ s = strings.Split(s[1], "/")
+ if len(s) == 2 {
+ nick = s[1]
+ }
+ if nick != b.Xmpp.Nick {
+ flog.xmpp.Info("sending message to remote", nick, v.Text, channel)
+ b.Remote <- config.Message{Username: nick, Text: v.Text, Channel: channel, Origin: "xmpp"}
+ }
+ }
+ case xmpp.Presence:
+ // do nothing
+ }
+ }
+}
diff --git a/matterbridge.conf.sample b/matterbridge.conf.sample
index a8afc48c..f8f07f70 100644
--- a/matterbridge.conf.sample
+++ b/matterbridge.conf.sample
@@ -172,7 +172,11 @@ mattermost="testing"
#OPTIONAL
GiphyApiKey="dc6zaTOxFJmzC"
-#Choose only one protocol to bridge, do not set both to true!
+#Enabling plus means you'll use the API version instead of the webhooks one
+Plus=false
+
+#Choose protocols to bridge. You need to specify at least two
#REQUIRED
Irc=true
Xmpp=false
+Mattermost=true
diff --git a/matterbridge.go b/matterbridge.go
index 4d2c4c06..689a08df 100644
--- a/matterbridge.go
+++ b/matterbridge.go
@@ -4,6 +4,7 @@ import (
"flag"
"fmt"
"github.com/42wim/matterbridge/bridge"
+ "github.com/42wim/matterbridge/bridge/config"
log "github.com/Sirupsen/logrus"
)
@@ -30,9 +31,9 @@ func main() {
}
fmt.Println("running version", version)
if *flagPlus {
- bridge.NewBridge("matterbot", bridge.NewConfig(*flagConfig), "")
+ bridge.NewBridge("matterbot", config.NewConfig(*flagConfig), "")
} else {
- bridge.NewBridge("matterbot", bridge.NewConfig(*flagConfig), "legacy")
+ bridge.NewBridge("matterbot", config.NewConfig(*flagConfig), "legacy")
}
select {}
}
diff --git a/vendor/github.com/thoj/go-ircevent/irc.go b/vendor/github.com/thoj/go-ircevent/irc.go
index 0ba1d650..3981761a 100644
--- a/vendor/github.com/thoj/go-ircevent/irc.go
+++ b/vendor/github.com/thoj/go-ircevent/irc.go
@@ -152,7 +152,6 @@ func (irc *Connection) writeLoop() {
}
}
}
- return
}
// Pings the server if we have not received any messages for 5 minutes
diff --git a/vendor/github.com/thoj/go-ircevent/irc_callback.go b/vendor/github.com/thoj/go-ircevent/irc_callback.go
index 109cbb1f..b5622367 100644
--- a/vendor/github.com/thoj/go-ircevent/irc_callback.go
+++ b/vendor/github.com/thoj/go-ircevent/irc_callback.go
@@ -33,7 +33,7 @@ func (irc *Connection) RemoveCallback(eventcode string, i int) bool {
delete(irc.events[eventcode], i)
return true
}
- irc.Log.Printf("Event found, but no callback found at id %s\n", i)
+ irc.Log.Printf("Event found, but no callback found at id %d\n", i)
return false
}
@@ -64,7 +64,7 @@ func (irc *Connection) ReplaceCallback(eventcode string, i int, callback func(*E
event[i] = callback
return
}
- irc.Log.Printf("Event found, but no callback found at id %s\n", i)
+ irc.Log.Printf("Event found, but no callback found at id %d\n", i)
}
irc.Log.Printf("Event not found. Use AddCallBack\n")
}
diff --git a/vendor/github.com/thoj/go-ircevent/sasl.go b/vendor/github.com/thoj/go-ircevent/irc_sasl.go
index e5ff9e38..e5ff9e38 100644
--- a/vendor/github.com/thoj/go-ircevent/sasl.go
+++ b/vendor/github.com/thoj/go-ircevent/irc_sasl.go
diff --git a/vendor/manifest b/vendor/manifest
index 417c6ddd..98c01162 100644
--- a/vendor/manifest
+++ b/vendor/manifest
@@ -179,4 +179,4 @@
"notests": true
}
]
-} \ No newline at end of file
+}