From f99f4f6acc6734ecbd0da80015285e6b3d39fdd8 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sun, 17 Sep 2023 23:21:57 -0400 Subject: [PATCH] Send memberlist on MUC join, suppress PM statuses for MUC JIDs --- telegram/utils.go | 55 ++++++++++++++++++++++ xmpp/extensions/extensions.go | 24 ++++++++++ xmpp/gateway/gateway.go | 24 ++++++++++ xmpp/handlers.go | 86 +++++++++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+) diff --git a/telegram/utils.go b/telegram/utils.go index 08c45d2..3ab6b88 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -217,6 +217,10 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o return err } + if chat != nil && c.Session.MUC && c.IsGroup(chat) { + return nil + } + var photo string if chat != nil && chat.Photo != nil { file, path, err := c.ForceOpenFile(chat.Photo.Small, 1) @@ -290,6 +294,35 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o ) } +func (c *Client) SendMUCStatuses(chatID int64) { + members, err := c.client.SearchChatMembers(&client.SearchChatMembersRequest{ + ChatId: chatID, + Limit: 200, + Filter: &client.ChatMembersFilterMembers{}, + }) + if err == nil { + for _, member := range members.Members { + var senderId int64 + switch member.MemberId.MessageSenderType() { + case client.TypeMessageSenderUser: + memberUser, _ := member.MemberId.(*client.MessageSenderUser) + senderId = memberUser.UserId + case client.TypeMessageSenderChat: + memberChat, _ := member.MemberId.(*client.MessageSenderChat) + senderId = memberChat.ChatId + } + gateway.SendPresence( + c.xmpp, + c.jid, + gateway.SPFrom(strconv.FormatInt(chatID, 10)), + gateway.SPResource(c.formatContact(senderId)), + gateway.SPImmed(true), + gateway.SPAffiliation(c.memberStatusToAffiliation(member.Status)), + ) + } + } +} + func (c *Client) formatContact(chatID int64) string { if chatID == 0 { return "" @@ -1434,6 +1467,10 @@ func (c *Client) UpdateChatNicknames() { for _, id := range c.cache.ChatsKeys() { chat, ok := c.cache.GetChat(id) if ok { + if c.Session.MUC && c.IsGroup(chat) { + continue + } + newArgs := []args.V{ gateway.SPFrom(strconv.FormatInt(id, 10)), gateway.SPNickname(chat.Title), @@ -1560,3 +1597,21 @@ func (c *Client) usernamesToString(usernames []string) string { } return strings.Join(atUsernames, ", ") } + +func (c *Client) memberStatusToAffiliation(memberStatus client.ChatMemberStatus) string { + switch memberStatus.ChatMemberStatusType() { + case client.TypeChatMemberStatusCreator: + return "owner" + case client.TypeChatMemberStatusAdministrator: + return "admin" + case client.TypeChatMemberStatusMember: + return "member" + case client.TypeChatMemberStatusRestricted: + return "outcast" + case client.TypeChatMemberStatusLeft: + return "none" + case client.TypeChatMemberStatusBanned: + return "outcast" + } + return "member" +} diff --git a/xmpp/extensions/extensions.go b/xmpp/extensions/extensions.go index 8e2f743..0b7269f 100644 --- a/xmpp/extensions/extensions.go +++ b/xmpp/extensions/extensions.go @@ -213,6 +213,19 @@ type QueryRegisterRemove struct { XMLName xml.Name `xml:"remove"` } +// PresenceXMucUserExtension is from XEP-0045 +type PresenceXMucUserExtension struct { + XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"` + Item PresenceXMucUserItem +} + +// PresenceXMucUserItem is from XEP-0045 +type PresenceXMucUserItem struct { + XMLName xml.Name `xml:"item"` + Affiliation string `xml:"affiliation,attr"` + Role string `xml:"role,attr"` +} + // Namespace is a namespace! func (c PresenceNickExtension) Namespace() string { return c.XMLName.Space @@ -278,6 +291,11 @@ func (c QueryRegister) GetSet() *stanza.ResultSet { return c.ResultSet } +// Namespace is a namespace! +func (c PresenceXMucUserExtension) Namespace() string { + return c.XMLName.Space +} + // Name is a packet name func (ClientMessage) Name() string { return "message" @@ -362,4 +380,10 @@ func init() { "jabber:iq:register", "query", }, QueryRegister{}) + + // presence muc user + stanza.TypeRegistry.MapExtension(stanza.PKTPresence, xml.Name{ + "http://jabber.org/protocol/muc#user", + "x", + }, PresenceXMucUserExtension{}) } diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index dfe2ebf..c09a061 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -240,6 +240,9 @@ var SPResource = args.NewString() // SPImmed skips queueing var SPImmed = args.NewBool(args.Default(true)) +// SPAffiliation is a XEP-0045 MUC affiliation +var SPAffiliation = args.NewString() + func newPresence(bareJid string, to string, args ...args.V) stanza.Presence { var presenceFrom string if SPFrom.IsSet(args) { @@ -295,6 +298,17 @@ func newPresence(bareJid string, to string, args ...args.V) stanza.Presence { }) } } + if SPAffiliation.IsSet(args) { + affiliation := SPAffiliation.Get(args) + if affiliation != "" { + presence.Extensions = append(presence.Extensions, extensions.PresenceXMucUserExtension{ + Item: extensions.PresenceXMucUserItem{ + Affiliation: affiliation, + Role: affilationToRole(affiliation), + }, + }) + } + } return presence } @@ -377,3 +391,13 @@ func SplitJID(from string) (string, string, bool) { } return fromJid.Bare(), fromJid.Resource, true } + +func affilationToRole(affilation string) string { + switch affilation { + case "owner", "admin": + return "moderator" + case "member": + return "participant" + } + return "none" +} diff --git a/xmpp/handlers.go b/xmpp/handlers.go index 6f51427..a1f6d74 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -287,6 +287,12 @@ func HandlePresence(s xmpp.Sender, p stanza.Packet) { } if prs.To == gateway.Jid.Bare() { handlePresence(s, prs) + return + } + var mucExt stanza.MucPresence + prs.Get(&mucExt) + if mucExt.XMLName.Space != "" { + handleMUCPresence(s, prs) } } @@ -397,6 +403,64 @@ func handlePresence(s xmpp.Sender, p stanza.Presence) { } } +func handleMUCPresence(s xmpp.Sender, p stanza.Presence) { + log.WithFields(log.Fields{ + "type": p.Type, + "from": p.From, + "to": p.To, + }).Warn("MUC presence") + log.Debugf("%#v", p) + + if p.Type == "" { + toBare, nickname, ok := gateway.SplitJID(p.To) + if ok { + component, ok := s.(*xmpp.Component) + if !ok { + log.Error("Not a component") + return + } + + reply := stanza.Presence{Attrs: stanza.Attrs{ + From: toBare, + To: p.From, + Id: p.Id, + }} + defer gateway.ResumableSend(component, reply) + + if nickname == "" { + presenceReplySetError(&reply, 400) + return + } + + chatId, ok := toToID(toBare) + if !ok { + presenceReplySetError(&reply, 404) + return + } + + fromBare, _, ok := gateway.SplitJID(p.From) + if !ok { + presenceReplySetError(&reply, 400) + return + } + + session, ok := sessions[fromBare] + if !ok || !session.Session.MUC { + presenceReplySetError(&reply, 401) + return + } + + chat, _, err := session.GetContactByID(chatId, nil) + if err != nil || !session.IsGroup(chat) { + presenceReplySetError(&reply, 404) + return + } + + session.SendMUCStatuses(chatId) + } + } +} + func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) { log.WithFields(log.Fields{ "from": iq.From, @@ -711,6 +775,28 @@ func iqAnswerSetError(answer *stanza.IQ, payload *extensions.QueryRegister, code } } +func presenceReplySetError(reply *stanza.Presence, code int) { + reply.Type = stanza.PresenceTypeError + reply.Error = stanza.Err{ + Code: code, + } + switch code { + case 400: + reply.Error.Type = stanza.ErrorTypeModify + reply.Error.Reason = "jid-malformed" + case 401: + reply.Error.Type = stanza.ErrorTypeAuth + reply.Error.Reason = "not-authorized" + case 404: + reply.Error.Type = stanza.ErrorTypeCancel + reply.Error.Reason = "item-not-found" + default: + log.Error("Unknown error code, falling back with empty reason") + reply.Error.Type = stanza.ErrorTypeCancel + reply.Error.Reason = "undefined-condition" + } +} + func toToID(to string) (int64, bool) { toParts := strings.Split(to, "@") if len(toParts) < 2 {