2016-08-14 19:48:51 +00:00
|
|
|
package bxmpp
|
|
|
|
|
|
|
|
import (
|
2017-02-14 20:12:02 +00:00
|
|
|
"crypto/tls"
|
2018-02-26 23:33:21 +00:00
|
|
|
"github.com/42wim/matterbridge/bridge"
|
2016-08-14 19:48:51 +00:00
|
|
|
"github.com/42wim/matterbridge/bridge/config"
|
2018-02-03 00:11:11 +00:00
|
|
|
"github.com/42wim/matterbridge/bridge/helper"
|
2017-07-20 21:16:43 +00:00
|
|
|
"github.com/jpillora/backoff"
|
2018-02-27 21:55:55 +00:00
|
|
|
"github.com/matterbridge/go-xmpp"
|
2016-08-14 19:48:51 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Bxmpp struct {
|
2016-11-13 22:06:37 +00:00
|
|
|
xc *xmpp.Client
|
|
|
|
xmppMap map[string]string
|
2017-12-19 22:15:03 +00:00
|
|
|
*config.BridgeConfig
|
2016-08-14 19:48:51 +00:00
|
|
|
}
|
|
|
|
|
2018-02-26 23:33:21 +00:00
|
|
|
func New(cfg *config.BridgeConfig) bridge.Bridger {
|
2017-12-19 22:15:03 +00:00
|
|
|
b := &Bxmpp{BridgeConfig: cfg}
|
2016-08-14 19:48:51 +00:00
|
|
|
b.xmppMap = make(map[string]string)
|
2016-08-15 21:16:07 +00:00
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Bxmpp) Connect() error {
|
|
|
|
var err error
|
2018-02-26 23:33:21 +00:00
|
|
|
b.Log.Infof("Connecting %s", b.Config.Server)
|
2016-08-14 19:48:51 +00:00
|
|
|
b.xc, err = b.createXMPP()
|
|
|
|
if err != nil {
|
2018-02-26 23:33:21 +00:00
|
|
|
b.Log.Debugf("%#v", err)
|
2016-08-15 21:16:07 +00:00
|
|
|
return err
|
2016-08-14 19:48:51 +00:00
|
|
|
}
|
2018-02-26 23:33:21 +00:00
|
|
|
b.Log.Info("Connection succeeded")
|
2017-07-20 21:16:43 +00:00
|
|
|
go func() {
|
|
|
|
initial := true
|
|
|
|
bf := &backoff.Backoff{
|
|
|
|
Min: time.Second,
|
|
|
|
Max: 5 * time.Minute,
|
|
|
|
Jitter: true,
|
|
|
|
}
|
|
|
|
for {
|
|
|
|
if initial {
|
2018-02-26 16:09:23 +00:00
|
|
|
b.handleXMPP()
|
2017-07-20 21:16:43 +00:00
|
|
|
initial = false
|
|
|
|
}
|
|
|
|
d := bf.Duration()
|
2018-02-26 23:33:21 +00:00
|
|
|
b.Log.Infof("Disconnected. Reconnecting in %s", d)
|
2017-07-20 21:16:43 +00:00
|
|
|
time.Sleep(d)
|
|
|
|
b.xc, err = b.createXMPP()
|
|
|
|
if err == nil {
|
|
|
|
b.Remote <- config.Message{Username: "system", Text: "rejoin", Channel: "", Account: b.Account, Event: config.EVENT_REJOIN_CHANNELS}
|
2018-02-26 16:09:23 +00:00
|
|
|
b.handleXMPP()
|
2017-07-20 21:16:43 +00:00
|
|
|
bf.Reset()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
2016-08-15 21:16:07 +00:00
|
|
|
return nil
|
2016-08-14 19:48:51 +00:00
|
|
|
}
|
|
|
|
|
2017-02-14 20:12:02 +00:00
|
|
|
func (b *Bxmpp) Disconnect() error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-08-12 12:51:41 +00:00
|
|
|
func (b *Bxmpp) JoinChannel(channel config.ChannelInfo) error {
|
|
|
|
b.xc.JoinMUCNoHistory(channel.Name+"@"+b.Config.Muc, b.Config.Nick)
|
2016-09-18 17:21:15 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-08-27 20:59:37 +00:00
|
|
|
func (b *Bxmpp) Send(msg config.Message) (string, error) {
|
2017-09-11 20:45:15 +00:00
|
|
|
// ignore delete messages
|
|
|
|
if msg.Event == config.EVENT_MSG_DELETE {
|
|
|
|
return "", nil
|
|
|
|
}
|
2018-02-28 21:23:29 +00:00
|
|
|
b.Log.Debugf("=> Receiving %#v", msg)
|
2018-02-26 16:09:23 +00:00
|
|
|
|
|
|
|
// Upload a file (in xmpp case send the upload URL because xmpp has no native upload support)
|
2017-11-24 21:55:24 +00:00
|
|
|
if msg.Extra != nil {
|
2018-02-03 00:11:11 +00:00
|
|
|
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
|
|
|
b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: rmsg.Channel + "@" + b.Config.Muc, Text: rmsg.Username + rmsg.Text})
|
|
|
|
}
|
2017-11-24 21:55:24 +00:00
|
|
|
if len(msg.Extra["file"]) > 0 {
|
2018-02-26 16:09:23 +00:00
|
|
|
return b.handleUploadFile(&msg)
|
2017-11-24 21:55:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-26 16:09:23 +00:00
|
|
|
// Post normal message
|
|
|
|
_, err := b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text})
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2017-08-27 20:59:37 +00:00
|
|
|
return "", nil
|
2016-08-14 19:48:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Bxmpp) createXMPP() (*xmpp.Client, error) {
|
2017-01-13 23:35:45 +00:00
|
|
|
tc := new(tls.Config)
|
|
|
|
tc.InsecureSkipVerify = b.Config.SkipTLSVerify
|
2017-01-18 20:01:42 +00:00
|
|
|
tc.ServerName = strings.Split(b.Config.Server, ":")[0]
|
2016-08-14 19:48:51 +00:00
|
|
|
options := xmpp.Options{
|
2017-02-14 20:12:02 +00:00
|
|
|
Host: b.Config.Server,
|
|
|
|
User: b.Config.Jid,
|
|
|
|
Password: b.Config.Password,
|
|
|
|
NoTLS: true,
|
|
|
|
StartTLS: true,
|
2017-01-13 23:35:45 +00:00
|
|
|
TLSConfig: tc,
|
|
|
|
|
2018-01-26 20:54:09 +00:00
|
|
|
Debug: b.General.Debug,
|
2018-02-27 21:55:55 +00:00
|
|
|
Logger: b.Log.Writer(),
|
2016-08-14 19:48:51 +00:00
|
|
|
Session: true,
|
|
|
|
Status: "",
|
|
|
|
StatusMessage: "",
|
|
|
|
Resource: "",
|
|
|
|
InsecureAllowUnencryptedAuth: false,
|
|
|
|
}
|
|
|
|
var err error
|
|
|
|
b.xc, err = options.NewClient()
|
|
|
|
return b.xc, err
|
|
|
|
}
|
|
|
|
|
2016-11-26 14:02:39 +00:00
|
|
|
func (b *Bxmpp) xmppKeepAlive() chan bool {
|
|
|
|
done := make(chan bool)
|
2016-08-14 19:48:51 +00:00
|
|
|
go func() {
|
|
|
|
ticker := time.NewTicker(90 * time.Second)
|
2016-11-26 14:02:39 +00:00
|
|
|
defer ticker.Stop()
|
2016-08-14 19:48:51 +00:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-ticker.C:
|
2018-02-26 23:33:21 +00:00
|
|
|
b.Log.Debugf("PING")
|
2017-07-20 21:16:43 +00:00
|
|
|
err := b.xc.PingC2S("", "")
|
|
|
|
if err != nil {
|
2018-02-26 23:33:21 +00:00
|
|
|
b.Log.Debugf("PING failed %#v", err)
|
2017-07-20 21:16:43 +00:00
|
|
|
}
|
2016-11-26 14:02:39 +00:00
|
|
|
case <-done:
|
|
|
|
return
|
2016-08-14 19:48:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
2016-11-26 14:02:39 +00:00
|
|
|
return done
|
2016-08-14 19:48:51 +00:00
|
|
|
}
|
|
|
|
|
2018-02-26 16:09:23 +00:00
|
|
|
func (b *Bxmpp) handleXMPP() error {
|
2017-07-30 15:48:23 +00:00
|
|
|
var ok bool
|
2016-11-26 14:02:39 +00:00
|
|
|
done := b.xmppKeepAlive()
|
|
|
|
defer close(done)
|
2016-08-14 19:48:51 +00:00
|
|
|
for {
|
|
|
|
m, err := b.xc.Recv()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
switch v := m.(type) {
|
|
|
|
case xmpp.Chat:
|
|
|
|
if v.Type == "groupchat" {
|
2018-02-26 16:09:23 +00:00
|
|
|
// skip invalid messages
|
|
|
|
if b.skipMessage(v) {
|
|
|
|
continue
|
2016-08-14 19:48:51 +00:00
|
|
|
}
|
2018-02-26 16:09:23 +00:00
|
|
|
rmsg := config.Message{Username: b.parseNick(v.Remote), Text: v.Text, Channel: b.parseChannel(v.Remote), Account: b.Account, UserID: v.Remote}
|
|
|
|
|
|
|
|
// check if we have an action event
|
|
|
|
rmsg.Text, ok = b.replaceAction(rmsg.Text)
|
|
|
|
if ok {
|
|
|
|
rmsg.Event = config.EVENT_USER_ACTION
|
2016-08-14 19:48:51 +00:00
|
|
|
}
|
2018-02-28 21:23:29 +00:00
|
|
|
b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
|
|
|
|
b.Log.Debugf("<= Message is %#v", rmsg)
|
2018-02-26 16:09:23 +00:00
|
|
|
b.Remote <- rmsg
|
2016-08-14 19:48:51 +00:00
|
|
|
}
|
|
|
|
case xmpp.Presence:
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-07-30 15:48:23 +00:00
|
|
|
|
|
|
|
func (b *Bxmpp) replaceAction(text string) (string, bool) {
|
|
|
|
if strings.HasPrefix(text, "/me ") {
|
|
|
|
return strings.Replace(text, "/me ", "", -1), true
|
|
|
|
}
|
|
|
|
return text, false
|
|
|
|
}
|
2018-02-26 16:09:23 +00:00
|
|
|
|
|
|
|
// handleUploadFile handles native upload of files
|
|
|
|
func (b *Bxmpp) handleUploadFile(msg *config.Message) (string, error) {
|
|
|
|
for _, f := range msg.Extra["file"] {
|
|
|
|
fi := f.(config.FileInfo)
|
|
|
|
if fi.Comment != "" {
|
|
|
|
msg.Text += fi.Comment + ": "
|
|
|
|
}
|
|
|
|
if fi.URL != "" {
|
|
|
|
msg.Text += fi.URL
|
|
|
|
}
|
|
|
|
_, err := b.xc.Send(xmpp.Chat{Type: "groupchat", Remote: msg.Channel + "@" + b.Config.Muc, Text: msg.Username + msg.Text})
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Bxmpp) parseNick(remote string) string {
|
|
|
|
s := strings.Split(remote, "@")
|
|
|
|
if len(s) > 0 {
|
|
|
|
s = strings.Split(s[1], "/")
|
|
|
|
if len(s) == 2 {
|
|
|
|
return s[1] // nick
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Bxmpp) parseChannel(remote string) string {
|
|
|
|
s := strings.Split(remote, "@")
|
|
|
|
if len(s) >= 2 {
|
|
|
|
return s[0] // channel
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// skipMessage skips messages that need to be skipped
|
|
|
|
func (b *Bxmpp) skipMessage(message xmpp.Chat) bool {
|
|
|
|
// skip messages from ourselves
|
|
|
|
if b.parseNick(message.Remote) == b.Config.Nick {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip empty messages
|
|
|
|
if message.Text == "" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip subject messages
|
|
|
|
if strings.Contains(message.Text, "</subject>") {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip delayed messages
|
|
|
|
t := time.Time{}
|
2018-02-27 22:38:36 +00:00
|
|
|
return message.Stamp == t
|
2018-02-26 16:09:23 +00:00
|
|
|
}
|