From 0b1e204912f4979de26f648c8fdafd3db3992c81 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Sun, 22 Jun 2025 10:37:20 -0400 Subject: [PATCH] Process MUC deletion --- telegram/commands.go | 4 +- telegram/utils.go | 41 +++++++++++++ xmpp/extensions/extensions.go | 11 +++- xmpp/gateway/gateway.go | 64 +++++++++++++------- xmpp/handlers.go | 110 ++++++++++++++++++---------------- 5 files changed, 152 insertions(+), 78 deletions(-) diff --git a/telegram/commands.go b/telegram/commands.go index b43f2f8..d78cd20 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -980,9 +980,7 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool, } // leave current chat (for owners) case "leave!": - _, err := c.client.DeleteChat(&client.DeleteChatRequest{ - ChatId: chatID, - }) + err := c.DeleteChat(chatID) if err != nil { return err.Error(), true, false } diff --git a/telegram/utils.go b/telegram/utils.go index 76f8abd..80ff4b3 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -588,6 +588,39 @@ func (c *Client) LeaveMUC(chatId int64, resource string) { } } +// DestroyMUC removes everyone from the MUC +func (c *Client) DestroyMUC(chatId int64) error { + err := c.DeleteChat(chatId) + if err != nil { + return err + } + + c.locks.mucCacheLock.Lock() + defer c.locks.mucCacheLock.Unlock() + + mucState, ok := c.mucCache[chatId] + if !ok || mucState == nil { + return nil + } + + var toResources []string + for resource := range mucState.Resources { + toResources = append(toResources, resource) + } + c.sendPresence( + gateway.SPFrom(gateway.MUCNODE(chatId)), + gateway.SPResource(c.GetMUCNickname(0)), + gateway.SPToResources(toResources), + gateway.SPMUCAffiliation("none"), + gateway.SPMUCRole("none"), + gateway.SPMUCDestroy(""), + ) + + delete(c.mucCache, chatId) + + return nil +} + func (c *Client) getFullName(user *client.User) string { fullName := user.FirstName if user.LastName != "" { @@ -2710,6 +2743,14 @@ func (c *Client) SetChatPermissions(chatID int64, permissions *client.ChatPermis return err } +// DeleteChat is a handy wrapper for the following TDLib method +func (c *Client) DeleteChat(chatID int64) error { + _, err := c.client.DeleteChat(&client.DeleteChatRequest{ + ChatId: chatID, + }) + return err +} + // CloneChatPermissions makes a copy of ChatPermissions structure func CloneChatPermissions(permissions *client.ChatPermissions) *client.ChatPermissions { return &client.ChatPermissions{ diff --git a/xmpp/extensions/extensions.go b/xmpp/extensions/extensions.go index 432b882..b1f9634 100644 --- a/xmpp/extensions/extensions.go +++ b/xmpp/extensions/extensions.go @@ -244,6 +244,7 @@ type MessageXMucUserInviteContinue struct { type PresenceXMucUserExtension struct { XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"` Item PresenceXMucUserItem + Destroy *MucDestroy Statuses []PresenceXMucUserStatus } @@ -327,10 +328,18 @@ type QueryMucAdminItem struct { // QueryMucOwner is from XEP-0045 type QueryMucOwner struct { XMLName xml.Name `xml:"http://jabber.org/protocol/muc#owner query"` - Form *stanza.Form `xml:"jabber:x:data x` + Form *stanza.Form `xml:"jabber:x:data x,omitempty"` + Destroy *MucDestroy `xml:"destroy,omitempty"` ResultSet *stanza.ResultSet `xml:"set,omitempty"` } +// MucDestroy is a child element from XEP-0045 +type MucDestroy struct { + XMLName xml.Name `xml:"destroy"` + Jid string `xml:"jid,attr,omitempty"` + Reason string `xml:"reason,omitempty"` +} + // Namespace is a namespace! func (c PresenceNickExtension) Namespace() string { return c.XMLName.Space diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index 604a1eb..777c5eb 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -450,6 +450,12 @@ var SPMUCJid = args.NewString() // SPMUCStatusCodes is a set of XEP-0045 MUC status codes var SPMUCStatusCodes = args.New() +// SPMUCDestroy is a XEP-0045 room destruction element +var SPMUCDestroy = args.NewString() + +// SPToResources achieves to send the presence to certain resources only +var SPToResources = args.New() + func newPresence(bareJid string, to string, args ...args.V) stanza.Presence { var presenceFrom string if SPFrom.IsSet(args) { @@ -534,6 +540,12 @@ func newPresence(bareJid string, to string, args ...args.V) stanza.Presence { }) } } + if SPMUCDestroy.IsSet(args) { + userExt.Destroy = &extensions.MucDestroy{ + Jid: SPMUCDestroy.Get(args), + Reason: "Group was deleted", + } + } presence.Extensions = append(presence.Extensions, userExt) } } @@ -557,29 +569,39 @@ func SendPresence(component *xmpp.Component, to string, args ...args.V) error { "to": to, }).Info("Got presence") - presence := newPresence(bareJid, to, args...) - - // explicit check, as marshalling is expensive - if log.GetLevel() == log.DebugLevel { - xmlPresence, err := xml.Marshal(presence) - if err == nil { - log.Debug(string(xmlPresence)) - } else { - log.Debugf("%#v", presence) - } - } - - immed := SPImmed.Get(args) - if immed { - err := ResumableSend(component, presence) - if err != nil { - LogBadPresence(&presence) - return err + var tos []string + if SPToResources.IsSet(args) { + for _, toResource := range SPToResources.Get(args).([]string) { + tos = append(tos, to + "/" + toResource) } } else { - QueueLock.Lock() - Queue[presence.From+presence.To] = &presence - QueueLock.Unlock() + tos = []string{to} + } + for _, to := range tos { + presence := newPresence(bareJid, to, args...) + + // explicit check, as marshalling is expensive + if log.GetLevel() == log.DebugLevel { + xmlPresence, err := xml.Marshal(presence) + if err == nil { + log.Debug(string(xmlPresence)) + } else { + log.Debugf("%#v", presence) + } + } + + immed := SPImmed.Get(args) + if immed { + err := ResumableSend(component, presence) + if err != nil { + LogBadPresence(&presence) + return err + } + } else { + QueueLock.Lock() + Queue[presence.From+presence.To] = &presence + QueueLock.Unlock() + } } return nil diff --git a/xmpp/handlers.go b/xmpp/handlers.go index fee1cf4..38a0f5c 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -1899,69 +1899,72 @@ func handleSetQueryMucOwner(s xmpp.Sender, iq *stanza.IQ, query *extensions.Quer return } - if query.Form == nil { - iqAnswerSetError(answer, 400) - return - } - - switch query.Form.Type { - case stanza.FormTypeSubmit: - // okay, noop - case stanza.FormTypeCancel: - return - default: - iqAnswerSetError(answer, 400) - return - } - - err = nil - for _, field := range query.Form.Fields { - switch field.Var { - case "muc#roomconfig_roomname", "muc#roomconfig_roomdesc", "muc#roomconfig_changesubject": - if len(field.ValuesList) != 1 { - iqAnswerSetError(answer, 400) - return - } - - value := field.ValuesList[0] + if query.Form != nil { + switch query.Form.Type { + case stanza.FormTypeSubmit: + // okay, noop + case stanza.FormTypeCancel: + return + default: + iqAnswerSetError(answer, 400) + return + } + err = nil + for _, field := range query.Form.Fields { switch field.Var { - case "muc#roomconfig_roomname": - if value == "" { + case "muc#roomconfig_roomname", "muc#roomconfig_roomdesc", "muc#roomconfig_changesubject": + if len(field.ValuesList) != 1 { iqAnswerSetError(answer, 400) return } - if value != chat.Title { - err = session.SetChatTitle(toID, value) - if err != nil { - break - } - } - case "muc#roomconfig_roomdesc": - if value != session.GetChatDescription(chat) { - err = session.SetChatDescription(toID, value) - if err != nil { - break - } - } - case "muc#roomconfig_changesubject": - b, ok := ToBool(value) - if !ok { - iqAnswerSetError(answer, 400) - return - } - permissions := chat.Permissions - if permissions != nil && b != permissions.CanPinMessages { - newPermissions := telegram.CloneChatPermissions(permissions) - newPermissions.CanPinMessages = b - err = session.SetChatPermissions(toID, newPermissions) - if err != nil { - break + value := field.ValuesList[0] + + switch field.Var { + case "muc#roomconfig_roomname": + if value == "" { + iqAnswerSetError(answer, 400) + return + } + if value != chat.Title { + err = session.SetChatTitle(toID, value) + if err != nil { + break + } + } + case "muc#roomconfig_roomdesc": + if value != session.GetChatDescription(chat) { + err = session.SetChatDescription(toID, value) + if err != nil { + break + } + } + case "muc#roomconfig_changesubject": + b, ok := ToBool(value) + if !ok { + iqAnswerSetError(answer, 400) + return + } + permissions := chat.Permissions + if permissions != nil && b != permissions.CanPinMessages { + newPermissions := telegram.CloneChatPermissions(permissions) + newPermissions.CanPinMessages = b + + err = session.SetChatPermissions(toID, newPermissions) + if err != nil { + break + } } } } } + } else if query.Destroy != nil { + err = session.DestroyMUC(toID) + } else { + // per 1.0 spec version, it also could be a destruction, but too dangerous to cover probably + iqAnswerSetError(answer, 400) + return } if err != nil { @@ -1973,6 +1976,7 @@ func handleSetQueryMucOwner(s xmpp.Sender, iq *stanza.IQ, query *extensions.Quer answer.Error.Text = err.Error() return } + } func iqAnswerSetError(answer *stanza.IQ, code int) {