diff --git a/telegram/client.go b/telegram/client.go index 6033c7c..f3bfe4a 100644 --- a/telegram/client.go +++ b/telegram/client.go @@ -41,6 +41,25 @@ type DelayedStatus struct { TimestampExpired int64 } +// MUCState holds MUC metadata +type MUCState struct { + Resources map[string]bool + Members map[int64]*MUCMember +} + +// MUCMember represents a MUC member +type MUCMember struct { + Nickname string + Affiliation string +} + +func NewMUCState() *MUCState { + return &MUCState{ + Resources: make(map[string]bool), + Members: make(map[int64]*MUCMember), + } +} + // Client stores the metadata for lazily invoked TDlib instance type Client struct { client *client.Client @@ -64,7 +83,7 @@ type Client struct { lastMsgHashes map[int64]uint64 msgHashSeed maphash.Seed - mucResources map[int64]map[string]bool + mucCache map[int64]*MUCState locks clientLocks SendMessageLock sync.Mutex @@ -75,7 +94,7 @@ type clientLocks struct { chatMessageLocks map[int64]*sync.Mutex resourcesLock sync.Mutex outboxLock sync.Mutex - mucResourcesLock sync.Mutex + mucCacheLock sync.Mutex lastMsgHashesLock sync.Mutex authorizerReadLock sync.Mutex @@ -136,7 +155,7 @@ func NewClient(conf config.TelegramConfig, jid string, component *xmpp.Component Session: session, resources: make(map[string]bool), outbox: make(map[string]string), - mucResources: make(map[int64]map[string]bool), + mucCache: make(map[int64]*MUCState), content: &conf.Content, cache: cache.NewCache(), options: options, diff --git a/telegram/handlers.go b/telegram/handlers.go index 8facc10..c7185f5 100644 --- a/telegram/handlers.go +++ b/telegram/handlers.go @@ -153,6 +153,13 @@ func (c *Client) updateHandler() { // new user discovered func (c *Client) updateUser(update *client.UpdateUser) { + // check if MUC nicknames should be updated + cacheUser, ok := c.cache.GetUser(update.User.Id) + if ok && (cacheUser.FirstName != update.User.FirstName || cacheUser.LastName != update.User.LastName) { + newNickname := c.GetMUCNickname(update.User.Id) + c.updateMUCsNickname(update.User.Id, newNickname) + } + c.cache.SetUser(update.User.Id, update.User) show, status, presenceType := c.userStatusToText(update.User.Status, update.User.Id) go c.ProcessStatusUpdate(update.User.Id, status, show, gateway.SPType(presenceType)) diff --git a/telegram/utils.go b/telegram/utils.go index cab450f..addeee8 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -306,21 +306,19 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o // JoinMUC saves MUC join fact and sends initialization data func (c *Client) JoinMUC(chatId int64, resource string, limit int32) { // save the nickname in this MUC, also as a marker of join - c.locks.mucResourcesLock.Lock() - oldMap, ok := c.mucResources[chatId] - if ok { - _, ok := oldMap[resource] - if ok { - // already joined, initializing anyway - } else { - oldMap[resource] = true - } - } else { - newMap := make(map[string]bool) - newMap[resource] = true - c.mucResources[chatId] = newMap + c.locks.mucCacheLock.Lock() + mucState, ok := c.mucCache[chatId] + if !ok || mucState == nil { + mucState = NewMUCState() + c.mucCache[chatId] = mucState } - c.locks.mucResourcesLock.Unlock() + _, ok = mucState.Resources[resource] + if ok { + // already joined, initializing anyway + } else { + mucState.Resources[resource] = true + } + c.locks.mucCacheLock.Unlock() c.sendMUCStatuses(chatId) @@ -332,14 +330,27 @@ func (c *Client) JoinMUC(chatId int64, resource string, limit int32) { c.sendMUCSubject(chatId, resource) } +func (c *Client) getFullName(user *client.User) string { + fullName := user.FirstName + if user.LastName != "" { + fullName = fullName + " " + user.LastName + } + return fullName +} + func (c *Client) sendMUCStatuses(chatID int64) { + c.locks.mucCacheLock.Lock() + defer c.locks.mucCacheLock.Unlock() + mucState, ok := c.mucCache[chatID] + if !ok || mucState == nil { + mucState = NewMUCState() + c.mucCache[chatID] = mucState + } + sChatId := strconv.FormatInt(chatID, 10) myNickname := "me" if c.me != nil { - myNickname := c.me.FirstName - if c.me.LastName != "" { - myNickname = myNickname + " " + c.me.LastName - } + myNickname = c.getFullName(c.me) } myAffiliation := "member" @@ -364,6 +375,11 @@ func (c *Client) sendMUCStatuses(chatID int64) { nickname := c.GetMUCNickname(senderId) affiliation := c.memberStatusToAffiliation(member.Status) + mucState.Members[senderId] = &MUCMember{ + Nickname: nickname, + Affiliation: affiliation, + } + if c.me != nil && senderId == c.me.Id { myNickname = nickname myAffiliation = affiliation @@ -419,6 +435,49 @@ func (c *Client) GetMUCNickname(chatID int64) string { return c.formatContact(chatID) } +func (c *Client) updateMUCsNickname(memberID int64, newNickname string) { + c.locks.mucCacheLock.Lock() + defer c.locks.mucCacheLock.Unlock() + + for mucId, state := range c.mucCache { + oldMember, ok := state.Members[memberID] + if ok { + state.Members[memberID] = &MUCMember{ + Nickname: newNickname, + Affiliation: oldMember.Affiliation, + } + + sMucId := strconv.FormatInt(mucId, 10) + unavailableStatusCodes := []uint16{303, 210} + availableStatusCodes := []uint16{100, 210} + if c.me != nil && memberID == c.me.Id { + unavailableStatusCodes = append(unavailableStatusCodes, 110) + availableStatusCodes = append(availableStatusCodes, 110) + } + gateway.SendPresence( + c.xmpp, + c.jid, + gateway.SPType("unavailable"), + gateway.SPFrom(sMucId), + gateway.SPResource(oldMember.Nickname), + gateway.SPImmed(true), + gateway.SPMUCAffiliation(oldMember.Affiliation), + gateway.SPMUCNick(newNickname), + gateway.SPMUCStatusCodes(unavailableStatusCodes), + ) + gateway.SendPresence( + c.xmpp, + c.jid, + gateway.SPFrom(sMucId), + gateway.SPResource(newNickname), + gateway.SPImmed(true), + gateway.SPMUCAffiliation(oldMember.Affiliation), + gateway.SPMUCStatusCodes(availableStatusCodes), + ) + } + } +} + func (c *Client) formatContact(chatID int64) string { if chatID == 0 { return "" diff --git a/xmpp/extensions/extensions.go b/xmpp/extensions/extensions.go index 8ff034d..45ab839 100644 --- a/xmpp/extensions/extensions.go +++ b/xmpp/extensions/extensions.go @@ -225,6 +225,7 @@ type PresenceXMucUserItem struct { XMLName xml.Name `xml:"item"` Affiliation string `xml:"affiliation,attr"` Jid string `xml:"jid,attr"` + Nick string `xml:"nick,attr,omitempty"` Role string `xml:"role,attr"` } diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index 1a37cc7..9007f6b 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -328,6 +328,9 @@ var SPImmed = args.NewBool(args.Default(true)) // SPMUCAffiliation is a XEP-0045 MUC affiliation var SPMUCAffiliation = args.NewString() +// SPMUCNick is a XEP-0045 MUC user nick +var SPMUCNick = args.NewString() + // SPMUCJid is a real jid of a MUC member var SPMUCJid = args.NewString() @@ -398,6 +401,9 @@ func newPresence(bareJid string, to string, args ...args.V) stanza.Presence { Role: affilationToRole(affiliation), }, } + if SPMUCNick.IsSet(args) { + userExt.Item.Nick = SPMUCNick.Get(args) + } if SPMUCJid.IsSet(args) { userExt.Item.Jid = SPMUCJid.Get(args) }