Refactor bridge. Allows bridging between every protocol

This commit is contained in:
Wim 2016-08-14 21:48:51 +02:00
parent 1f72ca4c4e
commit ff94796700
13 changed files with 699 additions and 483 deletions

View File

@ -1,57 +1,22 @@
package bridge package bridge
import ( import (
"crypto/tls" //"fmt"
"github.com/42wim/matterbridge/matterclient" "github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/matterhook" "github.com/42wim/matterbridge/bridge/irc"
"github.com/42wim/matterbridge/bridge/mattermost"
"github.com/42wim/matterbridge/bridge/xmpp"
log "github.com/Sirupsen/logrus" 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" "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 { type Bridge struct {
MMhook *config.Config
MMapi Source string
MMirc Bridges []Bridger
MMxmpp kind string
*Config Channels []map[string]string
kind string ignoreNicks map[string][]string
} }
type FancyLog struct { type FancyLog struct {
@ -60,6 +25,12 @@ type FancyLog struct {
xmpp *log.Entry xmpp *log.Entry
} }
type Bridger interface {
Send(msg config.Message) error
Name() string
//Command(cmd string) string
}
var flog FancyLog var flog FancyLog
const Legacy = "legacy" const Legacy = "legacy"
@ -70,455 +41,82 @@ func initFLog() {
flog.xmpp = log.WithFields(log.Fields{"module": "xmpp"}) 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() initFLog()
b := &Bridge{} b := &Bridge{}
b.Config = config b.Config = cfg
b.kind = kind if cfg.General.Irc {
b.mmMap = make(map[string]string) b.Bridges = append(b.Bridges, birc.New(cfg, c))
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
}
} }
if b.Config.General.Xmpp { if cfg.General.Mattermost {
b.xmppMap = make(map[string]string) b.Bridges = append(b.Bridges, bmattermost.New(cfg, c))
for _, val := range b.Config.Channel {
b.xmppMap[val.Xmpp] = val.Mattermost
b.mmMap[val.Mattermost] = val.Xmpp
}
} }
if cfg.General.Xmpp {
if kind == Legacy { b.Bridges = append(b.Bridges, bxmpp.New(cfg, c))
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()
} }
b.mapChannels()
if b.Config.General.Irc { b.mapIgnores()
flog.irc.Info("Trying IRC connection") go b.handleReceive(c)
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()
return b return b
} }
func (b *Bridge) createIRC(name string) *irc.Connection { func (b *Bridge) handleReceive(c chan config.Message) {
i := irc.IRC(b.Config.IRC.Nick, b.Config.IRC.Nick) for {
i.UseTLS = b.Config.IRC.UseTLS select {
i.UseSASL = b.Config.IRC.UseSASL case msg := <-c:
i.SASLLogin = b.Config.IRC.NickServNick m := b.getChannel(msg.Origin, msg.Channel)
i.SASLPassword = b.Config.IRC.NickServPassword if m == nil {
i.TLSConfig = &tls.Config{InsecureSkipVerify: b.Config.IRC.SkipTLSVerify} continue
if b.Config.IRC.Password != "" { }
i.Password = b.Config.IRC.Password for _, br := range b.Bridges {
} if b.ignoreMessage(msg.Username, msg.Text, msg.Origin) {
i.AddCallback(ircm.RPL_WELCOME, b.handleNewConnection) continue
err := i.Connect(b.Config.IRC.Server) }
if err != nil { // do not send to originated bridge
flog.irc.Fatal(err) if br.Name() != msg.Origin {
} msg.Channel = m[br.Name()]
return i br.Send(msg)
} }
}
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 { func (b *Bridge) mapChannels() error {
parts := strings.Fields(event.Message()) for _, val := range b.Config.Channel {
exp, _ := regexp.Compile("[:,]+$") m := make(map[string]string)
channel := event.Arguments[0] m["irc"] = val.IRC
command := "" m["mattermost"] = val.Mattermost
if len(parts) == 2 { m["xmpp"] = val.Xmpp
command = parts[1] b.Channels = append(b.Channels, m)
} }
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 return nil
} }
func (b *Bridge) handleMatterHook(mchan chan *MMMessage) { func (b *Bridge) mapIgnores() {
for { m := make(map[string][]string)
message := b.mh.Receive() m["irc"] = strings.Fields(b.Config.IRC.IgnoreNicks)
flog.mm.Debugf("receiving from matterhook %#v", message) m["mattermost"] = strings.Fields(b.Config.Mattermost.IgnoreNicks)
m := &MMMessage{} m["xmpp"] = strings.Fields(b.Config.Mattermost.IgnoreNicks)
m.Username = message.UserName b.ignoreNicks = m
m.Text = message.Text
m.Channel = message.ChannelName
mchan <- m
}
} }
func (b *Bridge) handleMatterClient(mchan chan *MMMessage) { func (b *Bridge) getChannel(src, name string) map[string]string {
for message := range b.mc.MessageChan { for _, v := range b.Channels {
// do not post our own messages back to irc if v[src] == name {
if message.Raw.Action == "posted" && b.mc.User.Username != message.Username { return v
flog.mm.Debugf("receiving from matterclient %#v", message)
m := &MMMessage{}
m.Username = message.Username
m.Channel = message.Channel
m.Text = message.Text
mchan <- m
} }
} }
} return nil
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)
}
}
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})
}
}
}
}
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"
}
return res.Data.FixedHeightDownsampledURL
}
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) getIRCChannel(mmChannel string) string {
return b.mmMap[mmChannel]
} }
func (b *Bridge) ignoreMessage(nick string, message string, protocol string) bool { 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 ? // should we discard messages ?
for _, entry := range ignoreNicks { for _, entry := range b.ignoreNicks[protocol] {
if nick == entry { if nick == entry {
return true return true
} }
} }
return false 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
}
}
}

View File

@ -1,4 +1,4 @@
package bridge package config
import ( import (
"gopkg.in/gcfg.v1" "gopkg.in/gcfg.v1"
@ -6,6 +6,13 @@ import (
"log" "log"
) )
type Message struct {
Text string
Channel string
Username string
Origin string
}
type Config struct { type Config struct {
IRC struct { IRC struct {
UseTLS bool UseTLS bool
@ -34,16 +41,17 @@ type Config struct {
Team string Team string
Login string Login string
Password string Password string
RemoteNickFormat *string RemoteNickFormat string
IgnoreNicks string IgnoreNicks string
NoTLS bool NoTLS bool
} }
Xmpp struct { Xmpp struct {
Jid string Jid string
Password string Password string
Server string Server string
Muc string Muc string
Nick string Nick string
RemoteNickFormat string
} }
Channel map[string]*struct { Channel map[string]*struct {
IRC string IRC string
@ -54,6 +62,8 @@ type Config struct {
GiphyAPIKey string GiphyAPIKey string
Xmpp bool Xmpp bool
Irc bool Irc bool
Mattermost bool
Plus bool
} }
} }

View File

@ -1,4 +1,4 @@
package bridge package birc
import ( import (
"strings" "strings"

214
bridge/irc/irc.go Normal file
View File

@ -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())
}
}

View File

@ -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
}

View File

@ -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
}

137
bridge/xmpp/xmpp.go Normal file
View File

@ -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
}
}
}

View File

@ -172,7 +172,11 @@ mattermost="testing"
#OPTIONAL #OPTIONAL
GiphyApiKey="dc6zaTOxFJmzC" 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 #REQUIRED
Irc=true Irc=true
Xmpp=false Xmpp=false
Mattermost=true

View File

@ -4,6 +4,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"github.com/42wim/matterbridge/bridge" "github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
) )
@ -30,9 +31,9 @@ func main() {
} }
fmt.Println("running version", version) fmt.Println("running version", version)
if *flagPlus { if *flagPlus {
bridge.NewBridge("matterbot", bridge.NewConfig(*flagConfig), "") bridge.NewBridge("matterbot", config.NewConfig(*flagConfig), "")
} else { } else {
bridge.NewBridge("matterbot", bridge.NewConfig(*flagConfig), "legacy") bridge.NewBridge("matterbot", config.NewConfig(*flagConfig), "legacy")
} }
select {} select {}
} }

View File

@ -152,7 +152,6 @@ func (irc *Connection) writeLoop() {
} }
} }
} }
return
} }
// Pings the server if we have not received any messages for 5 minutes // Pings the server if we have not received any messages for 5 minutes

View File

@ -33,7 +33,7 @@ func (irc *Connection) RemoveCallback(eventcode string, i int) bool {
delete(irc.events[eventcode], i) delete(irc.events[eventcode], i)
return true 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 return false
} }
@ -64,7 +64,7 @@ func (irc *Connection) ReplaceCallback(eventcode string, i int, callback func(*E
event[i] = callback event[i] = callback
return 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") irc.Log.Printf("Event not found. Use AddCallBack\n")
} }