From f9be86b5dbcc7523dbde930cd3d006d5a1024425 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Thu, 24 Jul 2025 12:34:05 -0400 Subject: [PATCH] Bump MUC occupants and send unavailable presences for ones removed from LRU --- telegram/muc.go | 10 +++++++++- telegram/utils.go | 42 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/telegram/muc.go b/telegram/muc.go index c933ae1..9021de5 100644 --- a/telegram/muc.go +++ b/telegram/muc.go @@ -90,7 +90,8 @@ func (lru *MUCOccupantsLRU) insertNewest(occupant *MUCOccupant) { lru.newest = occupant } -func (lru *MUCOccupantsLRU) Set(key int64, occupant *MUCOccupant) { +// Set adds or replaces an occupant and possibly returns an occupant removed instead because of overflow +func (lru *MUCOccupantsLRU) Set(key int64, occupant *MUCOccupant) (deleted *MUCOccupant) { lru.lock.Lock() defer lru.lock.Unlock() @@ -117,11 +118,15 @@ func (lru *MUCOccupantsLRU) Set(key int64, occupant *MUCOccupant) { } if len(lru.m) > int(MUCOccupantsLimit) && lru.oldest != nil { + deleted = lru.oldest delete(lru.m, lru.oldest.key) lru.cutOut(lru.oldest) } + + return } +// Delete occupant by member ID func (lru *MUCOccupantsLRU) Delete(key int64) { lru.lock.Lock() defer lru.lock.Unlock() @@ -134,6 +139,7 @@ func (lru *MUCOccupantsLRU) Delete(key int64) { } } +// Bump raises the occupant in LRU func (lru *MUCOccupantsLRU) Bump(occupant *MUCOccupant) { lru.lock.Lock() defer lru.lock.Unlock() @@ -147,6 +153,7 @@ func (lru *MUCOccupantsLRU) Bump(occupant *MUCOccupant) { lru.insertNewest(occupant) } +// Range loops over all occupants func (lru *MUCOccupantsLRU) Range() chan *MUCOccupant { lru.lock.Lock() @@ -166,6 +173,7 @@ func (lru *MUCOccupantsLRU) Range() chan *MUCOccupant { return occupantChan } +// Clear properly removes all occupants and their possible mutual references (not necessary in Golang, yet still) func (lru *MUCOccupantsLRU) Clear() { lru.lock.Lock() defer lru.lock.Unlock() diff --git a/telegram/utils.go b/telegram/utils.go index aadcc28..937ca8b 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -567,6 +567,11 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o for mucId, state := range c.mucCache { occupant, ok := state.Occupants.Get(chatID) if ok { + if show == "" { + // Bump occupants who just went online + state.Occupants.Bump(occupant) + } + _, toJids := c.getMUCJoinedJIDs(mucId, state, false) newMucArgs := append( newArgs, @@ -753,7 +758,8 @@ func (c *Client) mucCacheHasOccupant(mucID int64, memberID int64) bool { return false // no MUC to be added to } - _, ok = mucState.Occupants.Get(memberID) + occupant, ok := mucState.Occupants.Get(memberID) + mucState.Occupants.Bump(occupant) return ok } @@ -780,12 +786,14 @@ func (c *Client) addMUCOccupant(mucID int64, memberID int64, affiliation, role s ) if err == nil { - mucState.Occupants.Set(memberID, &MUCOccupant{ + deleted := mucState.Occupants.Set(memberID, &MUCOccupant{ Nickname: nickname, Affiliation: affiliation, Role: role, Status: status, }) + c.kickStaleOccupant(mucID, deleted, mucState) + return true } @@ -832,12 +840,13 @@ func (c *Client) updateMUCsNickname(memberID int64, newNickname string) { for mucId, state := range c.mucCache { oldOccupant, ok := state.Occupants.Get(memberID) if ok { - state.Occupants.Set(memberID, &MUCOccupant{ + deleted := state.Occupants.Set(memberID, &MUCOccupant{ Nickname: newNickname, Affiliation: oldOccupant.Affiliation, Role: oldOccupant.Role, Status: oldOccupant.Status, }) + c.kickStaleOccupant(mucId, deleted, state) sMucId := gateway.MUCNODE(mucId) unavailableStatusCodes := []uint16{303, 210} @@ -925,6 +934,7 @@ func (c *Client) GetMyMUCNickname(chatID int64) (string, bool) { if !ok { return "", false } + mucState.Occupants.Bump(occupant) return occupant.Nickname, true } @@ -2882,6 +2892,31 @@ func (c *Client) kickMeFromMUC(chatID int64, statusCodes []uint16, destroy bool, return c.sendPresence(args...) } +// achtung: assuming a locked mucState context +func (c *Client) kickStaleOccupant(chatID int64, deleted *MUCOccupant, mucState *MUCState) { + if deleted == nil { + return + } + + // u mad? put me back! + if c.me != nil && c.me.Id == deleted.key { + deleted = mucState.Occupants.Set(deleted.key, deleted) + if deleted == nil { + // WTF but okay + return + } + } + + c.sendPresence( + gateway.SPFrom(gateway.MUCNODE(chatID)), + gateway.SPResource(deleted.Nickname), + gateway.SPType("unavailable"), + gateway.SPMUCAffiliation(deleted.Affiliation), + gateway.SPMUCRole(deleted.Role), + gateway.SPMUCStatusCodes([]uint16{307, 333}), + ) +} + // MigrateToMUCs unsubscribes from legacy group chats and invites to MUCs func (c *Client) MigrateToMUCs() { var chatIDs []int64 @@ -3072,6 +3107,7 @@ func (c *Client) mucOccupantRolePresence(chatID, userID int64, status ChatMember } else { occupant, ok := mucState.Occupants.Get(userID) if ok { + mucState.Occupants.Bump(occupant) occupant.Affiliation = newAffiliation occupant.Role = newRole }