Improve XMPP bridge: less flashing

This commit is contained in:
Alex 2020-10-10 00:01:42 +02:00
parent 07f9f640f0
commit 78b4037410

View file

@ -4,6 +4,7 @@ import (
"crypto/tls"
"fmt"
"strings"
"sync"
"time"
gxmpp "github.com/matterbridge/go-xmpp"
@ -35,8 +36,14 @@ type XMPP struct {
conn *gxmpp.Client
isMUC map[string]bool
joinedMUC map[string]bool
stateLock sync.Mutex
muc map[RoomID]*mucInfo
}
type mucInfo struct {
joined bool
pendingJoins map[UserID]string
pendingLeaves map[UserID]struct{}
}
func (xm *XMPP) SetHandler(h Handler) {
@ -84,10 +91,7 @@ func (xm *XMPP) Configure(c Configuration) error {
}
// Try to connect
if xm.isMUC == nil {
xm.isMUC = make(map[string]bool)
}
xm.joinedMUC = make(map[string]bool)
xm.muc = make(map[RoomID]*mucInfo)
xm.connectorLoopNum += 1
go xm.connectLoop(xm.connectorLoopNum)
@ -134,13 +138,13 @@ func (xm *XMPP) connectLoop(num int) {
xm.connected = true
xm.timeout = 10
for muc, joined := range xm.joinedMUC {
if joined {
_, err := xm.conn.JoinMUCNoHistory(muc, xm.nickname)
for muc, mucInfo := range xm.muc {
if mucInfo.joined {
_, err := xm.conn.JoinMUCNoHistory(string(muc), xm.nickname)
if err != nil {
xm.handler.SystemMessage(fmt.Sprintf("Could not rejoin MUC %s after reconnection: %s", muc, err))
xm.handler.Left(RoomID(muc))
delete(xm.joinedMUC, muc)
mucInfo.joined = false
}
}
}
@ -184,85 +188,123 @@ func (xm *XMPP) handleXMPP() error {
log.Tracef("XMPP: %#v\n", m)
switch v := m.(type) {
case gxmpp.Chat:
remote_sp := strings.Split(v.Remote, "/")
xm.handleXMPPStance(m)
}
}
// Skip self-sent events
if v.Remote == xm.jid || (v.Type == "groupchat" && len(remote_sp) == 2 && remote_sp[1] == xm.nickname) {
continue
}
func (xm *XMPP) handleXMPPStance(m interface{}) {
xm.stateLock.Lock()
defer xm.stateLock.Unlock()
// 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]))
}
switch v := m.(type) {
case gxmpp.Chat:
remote_sp := strings.Split(v.Remote, "/")
// Handle subject change in group chats
if v.Subject != "" && v.Type == "groupchat" {
author := UserID("")
if len(remote_sp) == 2 {
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,
})
}
// Skip self-sent events
if v.Remote == xm.jid || (v.Type == "groupchat" && len(remote_sp) == 2 && remote_sp[1] == xm.nickname) {
return
}
// Handle text message
if v.Text != "" {
event := &Event{
Type: EVENT_MESSAGE,
Text: v.Text,
}
// 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]))
}
if strings.HasPrefix(event.Text, "/me ") {
event.Type = EVENT_ACTION
event.Text = strings.Replace(event.Text, "/me ", "", 1)
}
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)
// Handle subject change in group chats
if v.Subject != "" && v.Type == "groupchat" {
author := UserID("")
if len(remote_sp) == 2 {
if remote_sp[1] == xm.nickname {
author = xm.User()
} else {
author = UserID(remote_sp[1] + "@" + remote_sp[0])
}
}
case gxmpp.Presence:
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
}
xm.handler.RoomInfoUpdated(RoomID(remote_sp[0]), author, &RoomInfo{
Topic: v.Subject,
})
}
user := UserID(remote[1] + "@" + remote[0])
event := &Event{
Type: EVENT_JOIN,
Room: RoomID(remote[0]),
Author: user,
}
if v.Type == "unavailable" {
event.Type = EVENT_LEAVE
}
// Handle text message
if v.Text != "" {
event := &Event{
Type: EVENT_MESSAGE,
Text: v.Text,
}
if strings.HasPrefix(event.Text, "/me ") {
event.Type = EVENT_ACTION
event.Text = strings.Replace(event.Text, "/me ", "", 1)
}
if v.Type == "chat" {
event.Author = UserID(remote_sp[0])
xm.handler.Event(event)
xm.handler.UserInfoUpdated(user, &UserInfo{
DisplayName: remote[1],
})
}
if v.Type == "groupchat" && len(remote_sp) == 2 {
// First flush pending leaves and joins
room_id := RoomID(remote_sp[0])
if muc, ok := xm.muc[room_id]; ok {
muc.flushLeavesJoins(room_id, xm.handler)
}
// Now send event
event.Room = room_id
event.Author = UserID(remote_sp[1] + "@" + remote_sp[0])
event.Id = v.ID
xm.handler.Event(event)
}
}
case gxmpp.Presence:
remote := strings.Split(v.From, "/")
room := RoomID(remote[0])
if mucInfo, ok := xm.muc[room]; ok {
// skip presence with no user and self-presence
if len(remote) < 2 || remote[1] == xm.nickname {
return
}
user := UserID(remote[1] + "@" + remote[0])
if v.Type != "unavailable" {
if _, ok := mucInfo.pendingLeaves[user]; ok {
delete(mucInfo.pendingLeaves, user)
} else {
mucInfo.pendingJoins[user] = remote[1]
}
} else {
if _, ok := mucInfo.pendingJoins[user]; ok {
delete(mucInfo.pendingJoins, user)
} else {
mucInfo.pendingLeaves[user] = struct{}{}
}
}
}
}
}
func (muc *mucInfo) flushLeavesJoins(room RoomID, handler Handler) {
for user, display_name := range muc.pendingJoins {
handler.Event(&Event{
Type: EVENT_JOIN,
Room: room,
Author: user,
})
handler.UserInfoUpdated(user, &UserInfo{
DisplayName: display_name,
})
}
for user, _ := range muc.pendingLeaves {
handler.Event(&Event{
Type: EVENT_LEAVE,
Room: room,
Author: user,
})
}
muc.pendingJoins = make(map[UserID]string)
muc.pendingLeaves = make(map[UserID]struct{})
}
func (xm *XMPP) User() UserID {
return UserID(xm.jid)
}
@ -296,13 +338,19 @@ func (xm *XMPP) SetRoomInfo(roomId RoomID, info *RoomInfo) error {
}
func (xm *XMPP) Join(roomId RoomID) error {
xm.isMUC[string(roomId)] = true
xm.stateLock.Lock()
defer xm.stateLock.Unlock()
xm.muc[roomId] = &mucInfo{
pendingJoins: make(map[UserID]string),
pendingLeaves: make(map[UserID]struct{}),
}
log.Tracef("Join %s with nick %s\n", roomId, xm.nickname)
_, err := xm.conn.JoinMUCNoHistory(string(roomId), xm.nickname)
if err == nil {
xm.joinedMUC[string(roomId)] = true
xm.muc[roomId].joined = true
}
return err
@ -319,8 +367,14 @@ func (xm *XMPP) Invite(userId UserID, roomId RoomID) error {
}
func (xm *XMPP) Leave(roomId RoomID) {
xm.stateLock.Lock()
defer xm.stateLock.Unlock()
xm.conn.LeaveMUC(string(roomId))
delete(xm.joinedMUC, string(roomId))
if muc, ok := xm.muc[roomId]; ok {
muc.joined = false
}
}
func (xm *XMPP) SearchForUsers(query string) ([]UserSearchResult, error) {
@ -329,6 +383,9 @@ func (xm *XMPP) SearchForUsers(query string) ([]UserSearchResult, error) {
}
func (xm *XMPP) Send(event *Event) (string, error) {
xm.stateLock.Lock()
defer xm.stateLock.Unlock()
if event.Attachments != nil && len(event.Attachments) > 0 {
for _, at := range event.Attachments {
url := at.URL()
@ -355,6 +412,9 @@ func (xm *XMPP) Send(event *Event) (string, error) {
})
return event.Id, err
} else if len(event.Room) > 0 {
if muc, ok := xm.muc[event.Room]; ok {
muc.flushLeavesJoins(event.Room, xm.handler)
}
_, err := xm.conn.Send(gxmpp.Chat{
Type: "groupchat",
Remote: string(event.Room),
@ -372,6 +432,9 @@ func (xm *XMPP) UserCommand(cmd string) {
}
func (xm *XMPP) Close() {
xm.stateLock.Lock()
defer xm.stateLock.Unlock()
if xm.conn != nil {
xm.conn.Close()
}