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