easybridge/connector/xmpp/xmpp.go

361 lines
7.6 KiB
Go
Raw Normal View History

2020-02-16 16:53:31 +00:00
package xmpp
import (
"crypto/tls"
2020-02-23 19:24:50 +00:00
"fmt"
"strings"
2020-02-27 10:13:15 +00:00
"time"
2020-02-16 16:53:31 +00:00
gxmpp "github.com/matterbridge/go-xmpp"
"github.com/rs/xid"
2020-02-23 19:24:50 +00:00
log "github.com/sirupsen/logrus"
2020-02-17 08:41:08 +00:00
. "git.deuxfleurs.fr/Deuxfleurs/easybridge/connector"
2020-02-16 16:53:31 +00:00
)
// User id format: username@server (= JID)
// OR: nickname@room_name@muc_server
// Room id format: room_name@muc_server (= MUC ID)
type XMPP struct {
handler Handler
connectorLoopNum int
2020-02-23 19:24:50 +00:00
connected bool
timeout int
2020-02-16 16:53:31 +00:00
2020-02-23 19:24:50 +00:00
server string
port int
ssl bool
jid string
2020-02-16 16:53:31 +00:00
jid_localpart string
2020-02-23 19:24:50 +00:00
password string
nickname string
2020-02-16 16:53:31 +00:00
conn *gxmpp.Client
2020-02-17 08:41:08 +00:00
isMUC map[string]bool
2020-02-16 16:53:31 +00:00
}
func (xm *XMPP) SetHandler(h Handler) {
xm.handler = h
}
2020-02-23 19:24:50 +00:00
func (xm *XMPP) Protocol() string {
2020-02-28 09:34:22 +00:00
return XMPP_PROTOCOL
2020-02-16 16:53:31 +00:00
}
func (xm *XMPP) Configure(c Configuration) error {
if xm.conn != nil {
xm.Close()
}
// Parse and validate configuration
var err error
xm.port, err = c.GetInt("port", 5222)
if err != nil {
return err
}
xm.ssl, err = c.GetBool("ssl", true)
if err != nil {
return err
}
xm.jid, err = c.GetString("jid")
if err != nil {
return err
}
jid_parts := strings.Split(xm.jid, "@")
if len(jid_parts) != 2 {
return fmt.Errorf("Invalid JID: %s", xm.jid)
}
xm.server = jid_parts[1]
2020-02-16 16:53:31 +00:00
xm.jid_localpart = jid_parts[0]
2020-02-26 15:30:10 +00:00
2020-02-26 15:32:58 +00:00
xm.nickname, _ = c.GetString("nickname", xm.jid_localpart)
2020-02-16 16:53:31 +00:00
xm.password, err = c.GetString("password")
if err != nil {
return err
}
// Try to connect
2020-02-17 08:41:08 +00:00
if xm.isMUC == nil {
xm.isMUC = make(map[string]bool)
}
2020-02-16 16:53:31 +00:00
xm.connectorLoopNum += 1
go xm.connectLoop(xm.connectorLoopNum)
for i := 0; i < 42; i++ {
2020-02-23 19:24:50 +00:00
time.Sleep(time.Duration(1) * time.Second)
2020-02-16 16:53:31 +00:00
if xm.connected {
return nil
}
}
return fmt.Errorf("Failed to connect after 42s attempting")
}
func (xm *XMPP) connectLoop(num int) {
xm.timeout = 10
for {
if xm.connectorLoopNum != num {
return
}
tc := &tls.Config{
ServerName: strings.Split(xm.jid, "@")[1],
InsecureSkipVerify: true,
}
options := gxmpp.Options{
2020-02-23 19:24:50 +00:00
Host: xm.server,
User: xm.jid,
Password: xm.password,
NoTLS: true,
StartTLS: xm.ssl,
Session: true,
2020-02-16 16:53:31 +00:00
TLSConfig: tc,
}
var err error
xm.conn, err = options.NewClient()
if err != nil {
xm.connected = false
2020-02-27 10:13:15 +00:00
log.Debugf("XMPP failed to connect / disconnected: %s\n", err)
log.Debugf("Retrying in %ds\n", xm.timeout)
2020-02-16 16:53:31 +00:00
time.Sleep(time.Duration(xm.timeout) * time.Second)
xm.timeout *= 2
if xm.timeout > 600 {
xm.timeout = 600
}
} else {
xm.connected = true
xm.timeout = 10
err = xm.handleXMPP()
if err != nil {
xm.connected = false
2020-02-27 10:13:15 +00:00
log.Debugf("XMPP disconnected: %s\n", err)
log.Debugf("Reconnecting.\n")
2020-02-16 16:53:31 +00:00
}
}
}
}
func (xm *XMPP) xmppKeepAlive() chan bool {
done := make(chan bool)
go func() {
ticker := time.NewTicker(90 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := xm.conn.PingC2S("", ""); err != nil {
2020-02-27 10:13:15 +00:00
log.Debugf("PING failed %#v\n", err)
2020-02-16 16:53:31 +00:00
}
case <-done:
return
}
}
}()
return done
}
func (xm *XMPP) handleXMPP() error {
done := xm.xmppKeepAlive()
defer close(done)
for {
m, err := xm.conn.Recv()
if err != nil {
return err
}
2020-02-27 10:13:15 +00:00
log.Tracef("XMPP: %#v\n", m)
2020-02-17 08:41:08 +00:00
2020-02-16 16:53:31 +00:00
switch v := m.(type) {
case gxmpp.Chat:
remote_sp := strings.Split(v.Remote, "/")
2020-02-16 16:53:31 +00:00
// Skip self-sent events
if v.Remote == xm.jid || (v.Type == "groupchat" && len(remote_sp) == 2 && remote_sp[1] == xm.nickname) {
2020-02-16 16:53:31 +00:00
continue
}
// If empty text, make sure we joined the room
// We would do this at every incoming message if it were not so costly
if v.Text == "" && v.Type == "groupchat" {
xm.handler.Joined(RoomID(remote_sp[0]))
2020-02-16 16:53:31 +00:00
}
// Handle subject change in group chats
if v.Subject != "" && v.Type == "groupchat" {
author := UserID("")
if len(remote_sp) == 2 {
2020-02-26 16:03:49 +00:00
if remote_sp[1] == xm.nickname {
author = xm.User()
} else {
author = UserID(remote_sp[1] + "@" + remote_sp[0])
}
}
xm.handler.RoomInfoUpdated(RoomID(remote_sp[0]), author, &RoomInfo{
Topic: v.Subject,
})
2020-02-16 16:53:31 +00:00
}
// Handle text message
if v.Text != "" {
event := &Event{
Type: EVENT_MESSAGE,
Text: v.Text,
2020-02-16 16:53:31 +00:00
}
if strings.HasPrefix(event.Text, "/me ") {
event.Type = EVENT_ACTION
event.Text = strings.Replace(event.Text, "/me ", "", 1)
2020-02-16 16:53:31 +00:00
}
if v.Type == "chat" {
event.Author = UserID(remote_sp[0])
xm.handler.Event(event)
}
if v.Type == "groupchat" && len(remote_sp) == 2 {
event.Room = RoomID(remote_sp[0])
event.Author = UserID(remote_sp[1] + "@" + remote_sp[0])
event.Id = v.ID
xm.handler.Event(event)
}
2020-02-16 16:53:31 +00:00
}
case gxmpp.Presence:
2020-02-17 08:41:08 +00:00
remote := strings.Split(v.From, "/")
if ismuc, ok := xm.isMUC[remote[0]]; ok && ismuc {
// skip presence with no user and self-presence
if len(remote) < 2 || remote[1] == xm.nickname {
continue
}
user := UserID(remote[1] + "@" + remote[0])
2020-02-17 08:41:08 +00:00
event := &Event{
2020-02-23 19:24:50 +00:00
Type: EVENT_JOIN,
Room: RoomID(remote[0]),
Author: user,
2020-02-17 08:41:08 +00:00
}
if v.Type == "unavailable" {
event.Type = EVENT_LEAVE
}
xm.handler.Event(event)
xm.handler.UserInfoUpdated(user, &UserInfo{
DisplayName: remote[1],
})
2020-02-17 08:41:08 +00:00
}
2020-02-16 16:53:31 +00:00
}
}
}
func (xm *XMPP) User() UserID {
return UserID(xm.jid)
}
func (xm *XMPP) SetUserInfo(info *UserInfo) error {
return fmt.Errorf("Not implemented")
}
func (xm *XMPP) SetRoomInfo(roomId RoomID, info *RoomInfo) error {
if info.Topic != "" {
2020-02-17 18:02:00 +00:00
_, err := xm.conn.Send(gxmpp.Chat{
2020-02-23 19:24:50 +00:00
Type: "groupchat",
Remote: string(roomId),
Subject: info.Topic,
})
2020-02-17 18:02:00 +00:00
if err != nil {
return err
}
}
if info.Picture.MediaObject != nil {
// TODO
return fmt.Errorf("Room picture change not implemented on xmpp")
}
if info.Name != "" && info.Name != string(roomId) {
// TODO
return fmt.Errorf("Room name change not implemented on xmpp")
}
return nil
2020-02-16 16:53:31 +00:00
}
func (xm *XMPP) Join(roomId RoomID) error {
2020-02-17 08:41:08 +00:00
xm.isMUC[string(roomId)] = true
2020-02-27 10:13:15 +00:00
log.Tracef("Join %s with nick %s\n", roomId, xm.nickname)
2020-02-16 16:53:31 +00:00
_, err := xm.conn.JoinMUCNoHistory(string(roomId), xm.nickname)
return err
}
func (xm *XMPP) Invite(userId UserID, roomId RoomID) error {
if roomId == "" {
xm.conn.RequestSubscription(string(userId))
xm.conn.ApproveSubscription(string(userId))
return nil
}
2020-02-16 16:53:31 +00:00
// TODO
return fmt.Errorf("Not implemented")
}
func (xm *XMPP) Leave(roomId RoomID) {
2020-02-17 18:02:00 +00:00
xm.conn.LeaveMUC(string(roomId))
2020-02-16 16:53:31 +00:00
}
func (xm *XMPP) SearchForUsers(query string) ([]UserSearchResult, error) {
// TODO: search roster
return nil, fmt.Errorf("Not implemented")
}
func (xm *XMPP) Send(event *Event) (string, error) {
if event.Attachments != nil && len(event.Attachments) > 0 {
for _, at := range event.Attachments {
url := at.URL()
if url == "" {
// TODO find a way to send them using some hosing of some kind
return "", fmt.Errorf("Attachment without URL sent to XMPP")
} else {
event.Text += fmt.Sprintf("\n%s (%s, %dkb)",
url, at.Mimetype(), at.Size()/1024)
}
}
}
if event.Id == "" {
event.Id = xid.New().String()
}
2020-02-27 10:13:15 +00:00
log.Tracef("xm *XMPP Send %#v\n", event)
2020-02-16 16:53:31 +00:00
if len(event.Recipient) > 0 {
2020-02-17 18:02:00 +00:00
_, err := xm.conn.Send(gxmpp.Chat{
2020-02-23 19:24:50 +00:00
Type: "chat",
2020-02-16 16:53:31 +00:00
Remote: string(event.Recipient),
2020-02-23 19:24:50 +00:00
Text: event.Text,
2020-02-16 16:53:31 +00:00
})
return event.Id, err
2020-02-16 16:53:31 +00:00
} else if len(event.Room) > 0 {
2020-02-17 18:02:00 +00:00
_, err := xm.conn.Send(gxmpp.Chat{
2020-02-23 19:24:50 +00:00
Type: "groupchat",
2020-02-16 16:53:31 +00:00
Remote: string(event.Room),
2020-02-23 19:24:50 +00:00
Text: event.Text,
ID: event.Id,
2020-02-16 16:53:31 +00:00
})
return event.Id, err
2020-02-16 16:53:31 +00:00
} else {
return "", fmt.Errorf("Invalid event")
2020-02-16 16:53:31 +00:00
}
}
func (xm *XMPP) Close() {
if xm.conn != nil {
xm.conn.Close()
}
2020-02-16 16:53:31 +00:00
xm.conn = nil
xm.connectorLoopNum += 1
2020-02-26 22:01:34 +00:00
xm.connected = false
2020-02-16 16:53:31 +00:00
}