Refactor bridge. Allows bridging between every protocol
This commit is contained in:
parent
1f72ca4c4e
commit
ff94796700
524
bridge/bridge.go
524
bridge/bridge.go
|
@ -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
|
|
||||||
*Config
|
|
||||||
kind string
|
kind string
|
||||||
|
Channels []map[string]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 cfg.General.Mattermost {
|
||||||
|
b.Bridges = append(b.Bridges, bmattermost.New(cfg, c))
|
||||||
}
|
}
|
||||||
if b.Config.General.Xmpp {
|
if cfg.General.Xmpp {
|
||||||
b.xmppMap = make(map[string]string)
|
b.Bridges = append(b.Bridges, bxmpp.New(cfg, c))
|
||||||
for _, val := range b.Config.Channel {
|
|
||||||
b.xmppMap[val.Xmpp] = val.Mattermost
|
|
||||||
b.mmMap[val.Mattermost] = val.Xmpp
|
|
||||||
}
|
}
|
||||||
}
|
b.mapChannels()
|
||||||
|
b.mapIgnores()
|
||||||
if kind == Legacy {
|
go b.handleReceive(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()
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
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)
|
|
||||||
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) {
|
|
||||||
for {
|
for {
|
||||||
message := b.mh.Receive()
|
select {
|
||||||
flog.mm.Debugf("receiving from matterhook %#v", message)
|
case msg := <-c:
|
||||||
m := &MMMessage{}
|
m := b.getChannel(msg.Origin, msg.Channel)
|
||||||
m.Username = message.UserName
|
if m == nil {
|
||||||
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
|
continue
|
||||||
}
|
}
|
||||||
if b.Config.General.Irc {
|
for _, br := range b.Bridges {
|
||||||
username = message.Username + ": "
|
if b.ignoreMessage(msg.Username, msg.Text, msg.Origin) {
|
||||||
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
|
continue
|
||||||
}
|
}
|
||||||
cmd := cmds[0]
|
// do not send to originated bridge
|
||||||
switch cmd {
|
if br.Name() != msg.Origin {
|
||||||
case "!users":
|
msg.Channel = m[br.Name()]
|
||||||
flog.mm.Info("Received !users from ", message.Username)
|
br.Send(msg)
|
||||||
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 {
|
func (b *Bridge) mapChannels() error {
|
||||||
g := giphy.DefaultClient
|
for _, val := range b.Config.Channel {
|
||||||
if b.Config.General.GiphyAPIKey != "" {
|
m := make(map[string]string)
|
||||||
g.APIKey = b.Config.General.GiphyAPIKey
|
m["irc"] = val.IRC
|
||||||
|
m["mattermost"] = val.Mattermost
|
||||||
|
m["xmpp"] = val.Xmpp
|
||||||
|
b.Channels = append(b.Channels, m)
|
||||||
}
|
}
|
||||||
res, err := g.Random(query)
|
return nil
|
||||||
if err != nil {
|
|
||||||
return "error"
|
|
||||||
}
|
|
||||||
return res.Data.FixedHeightDownsampledURL
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bridge) getMMChannel(channel string) string {
|
func (b *Bridge) mapIgnores() {
|
||||||
var mmChannel string
|
m := make(map[string][]string)
|
||||||
if b.Config.General.Irc {
|
m["irc"] = strings.Fields(b.Config.IRC.IgnoreNicks)
|
||||||
mmChannel = b.ircMap[channel]
|
m["mattermost"] = strings.Fields(b.Config.Mattermost.IgnoreNicks)
|
||||||
}
|
m["xmpp"] = strings.Fields(b.Config.Mattermost.IgnoreNicks)
|
||||||
if b.Config.General.Xmpp {
|
b.ignoreNicks = m
|
||||||
mmChannel = b.xmppMap[channel]
|
|
||||||
}
|
|
||||||
if b.kind == Legacy {
|
|
||||||
return mmChannel
|
|
||||||
}
|
|
||||||
return b.mc.GetChannelId(mmChannel, "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bridge) getIRCChannel(mmChannel string) string {
|
func (b *Bridge) getChannel(src, name string) map[string]string {
|
||||||
return b.mmMap[mmChannel]
|
for _, v := range b.Channels {
|
||||||
|
if v[src] == name {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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,7 +41,7 @@ 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
|
||||||
}
|
}
|
||||||
|
@ -44,6 +51,7 @@ type Config struct {
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package bridge
|
package birc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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 {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue