From a05724b93aaf7a422b86dab2d78f42d571d40ab8 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Fri, 9 May 2025 00:07:55 -0400 Subject: [PATCH] Add legacy-to-MUC migrator --- telegram/commands.go | 3 +++ telegram/utils.go | 10 +++++++++ xmpp/extensions/extensions.go | 27 +++++++++++++++++++++++ xmpp/gateway/gateway.go | 40 ++++++++++++++++++++++++++--------- xmpp/handlers.go | 3 +++ 5 files changed, 73 insertions(+), 10 deletions(-) diff --git a/telegram/commands.go b/telegram/commands.go index cda84cb..ef73c37 100644 --- a/telegram/commands.go +++ b/telegram/commands.go @@ -463,6 +463,9 @@ func (c *Client) ProcessTransportCommand(cmdline string, resource string) (strin if err != nil { return err.Error(), false } + if args[0] == "muc" && args[1] == "true" { + go c.MigrateToMUCs() + } gateway.DirtySessions = true return fmt.Sprintf("%s set to %s", args[0], value), true diff --git a/telegram/utils.go b/telegram/utils.go index 5ea904a..b158c71 100644 --- a/telegram/utils.go +++ b/telegram/utils.go @@ -2351,3 +2351,13 @@ func (c *Client) GetChatMembers(chatID int64, limited bool, query string, member } return members, nil } + +// MigrateToMUCs unsubscribes from legacy group chats and invites to MUCs +func (c *Client) MigrateToMUCs() { + for _, chat := range c.GetGroupChats() { + c.unsubscribe(chat.Id) + for resource := range c.resourcesRange() { + gateway.InviteToMUC(chat.Id, c.jid+"/"+resource, c.xmpp) + } + } +} diff --git a/xmpp/extensions/extensions.go b/xmpp/extensions/extensions.go index 45ab839..d40b5f6 100644 --- a/xmpp/extensions/extensions.go +++ b/xmpp/extensions/extensions.go @@ -213,6 +213,27 @@ type QueryRegisterRemove struct { XMLName xml.Name `xml:"remove"` } +// MessageXMucUserExtension is from XEP-0045 +type MessageXMucUserExtension struct { + XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"` + Invite MessageXMucUserInvite + Password string `xml:"password,omitempty"` +} + +// MessageXMucUserInvite is from XEP-0045 +type MessageXMucUserInvite struct { + XMLName xml.Name `xml:"invite"` + From string `xml:"from,attr"` + Reason string `xml:"reason,omitempty"` + Continue MessageXMucUserInviteContinue +} + +// MessageXMucUserInviteContinue is from XEP-0045 +type MessageXMucUserInviteContinue struct { + XMLName xml.Name `xml:"continue"` + Thread string `xml:"thread,attr,omitempty"` +} + // PresenceXMucUserExtension is from XEP-0045 type PresenceXMucUserExtension struct { XMLName xml.Name `xml:"http://jabber.org/protocol/muc#user x"` @@ -432,6 +453,12 @@ func init() { "query", }, QueryRegister{}) + // message muc user + stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{ + "http://jabber.org/protocol/muc#user", + "x", + }, MessageXMucUserExtension{}) + // presence muc user stanza.TypeRegistry.MapExtension(stanza.PKTPresence, xml.Name{ "http://jabber.org/protocol/muc#user", diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index 8bf3302..65d488a 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -63,7 +63,7 @@ var MessageOutgoingPermissionVersion = 0 // SendMessage creates and sends a message stanza func SendMessage(to, from, body, id string, component *xmpp.Component, reply *Reply, timestamp int64, replaceId string, isCarbon, isGroupchat, requestReceipt bool, originalFrom string) { - sendMessageWrapper(to, from, body, "", "", id, component, reply, nil, timestamp, "", replaceId, isCarbon, isGroupchat, false, requestReceipt, originalFrom, 0) + sendMessageWrapper(to, from, body, "", "", id, component, reply, nil, timestamp, "", replaceId, isCarbon, isGroupchat, false, requestReceipt, originalFrom, 0, "") } // SendServiceMessage creates and sends a simple message stanza from transport @@ -72,7 +72,7 @@ func SendServiceMessage(to, body string, component *xmpp.Component) { if uuid, err := uuid.NewRandom(); err == nil { id = uuid.String() } - sendMessageWrapper(to, "", body, "", "", id, component, nil, nil, 0, "", "", false, false, false, false, "", 0) + sendMessageWrapper(to, "", body, "", "", id, component, nil, nil, 0, "", "", false, false, false, false, "", 0, "") } // SendTextMessage creates and sends a simple message stanza @@ -81,27 +81,27 @@ func SendTextMessage(to, from, body string, component *xmpp.Component) { if uuid, err := uuid.NewRandom(); err == nil { id = uuid.String() } - sendMessageWrapper(to, from, body, "", "", id, component, nil, nil, 0, "", "", false, false, false, false, "", 0) + sendMessageWrapper(to, from, body, "", "", id, component, nil, nil, 0, "", "", false, false, false, false, "", 0, "") } // SendErrorMessage creates and sends an error message stanza func SendErrorMessage(to, from, text string, code int, isGroupchat bool, component *xmpp.Component) { - sendMessageWrapper(to, from, "", "", text, "", component, nil, nil, 0, "", "", false, isGroupchat, false, false, "", code) + sendMessageWrapper(to, from, "", "", text, "", component, nil, nil, 0, "", "", false, isGroupchat, false, false, "", code, "") } // SendErrorMessageWithBody creates and sends an error message stanza with body payload func SendErrorMessageWithBody(to, from, body, errorText, id string, code int, isGroupchat bool, component *xmpp.Component) { - sendMessageWrapper(to, from, body, "", errorText, id, component, nil, nil, 0, "", "", false, isGroupchat, false, false, "", code) + sendMessageWrapper(to, from, body, "", errorText, id, component, nil, nil, 0, "", "", false, isGroupchat, false, false, "", code, "") } // SendMessageWithOOB creates and sends a message stanza with OOB URL func SendMessageWithOOB(to, from, body, id string, component *xmpp.Component, reply *Reply, timestamp int64, oob, replaceId string, isCarbon, isGroupchat, requestReceipt bool, originalFrom string) { - sendMessageWrapper(to, from, body, "", "", id, component, reply, nil, timestamp, oob, replaceId, isCarbon, isGroupchat, false, requestReceipt, originalFrom, 0) + sendMessageWrapper(to, from, body, "", "", id, component, reply, nil, timestamp, oob, replaceId, isCarbon, isGroupchat, false, requestReceipt, originalFrom, 0, "") } // SendSubjectMessage creates and sends a MUC subject func SendSubjectMessage(to, from, subject, id string, component *xmpp.Component, timestamp int64) { - sendMessageWrapper(to, from, "", subject, "", id, component, nil, nil, timestamp, "", "", false, true, true, false, "", 0) + sendMessageWrapper(to, from, "", subject, "", id, component, nil, nil, timestamp, "", "", false, true, true, false, "", 0, "") } // SendMessageMarker creates and sends a message stanza with a XEP-0333 marker @@ -109,10 +109,15 @@ func SendMessageMarker(to string, from string, component *xmpp.Component, marker sendMessageWrapper(to, from, "", "", "", "", component, nil, &marker{ Type: markerType, Id: markerId, - }, 0, "", "", false, false, false, false, "", 0) + }, 0, "", "", false, false, false, false, "", 0, "") } -func sendMessageWrapper(to, from, body, subject, errorText, id string, component *xmpp.Component, reply *Reply, marker *marker, timestamp int64, oob, replaceId string, isCarbon, isGroupchat, forceSubject, requestReceipt bool, originalFrom string, errorCode int) { +// SendMUCInvite creates and send a MUC invitation message +func SendMUCInvite(to string, from string, component *xmpp.Component, inviteFrom string) { + sendMessageWrapper(to, from, "", "", "", "", component, nil, nil, 0, "", "", false, false, false, false, "", 0, inviteFrom) +} + +func sendMessageWrapper(to, from, body, subject, errorText, id string, component *xmpp.Component, reply *Reply, marker *marker, timestamp int64, oob, replaceId string, isCarbon, isGroupchat, forceSubject, requestReceipt bool, originalFrom string, errorCode int, inviteFrom string) { toJid, err := stanza.NewJid(to) if err != nil { log.WithFields(log.Fields{ @@ -134,6 +139,9 @@ func sendMessageWrapper(to, from, body, subject, errorText, id string, component if from == "" { logFrom = componentJid messageFrom = componentJid + } else if inviteFrom != "" { + logFrom = from + messageFrom = from + "@" + Jid.Bare() } else { logFrom = from messageFrom = from + "@" + componentJid @@ -212,7 +220,7 @@ func sendMessageWrapper(to, from, body, subject, errorText, id string, component message.Extensions = append(message.Extensions, extensions.NewReplyFallback(reply.Start, reply.End)) } } - if !isGroupchat && !isCarbon && toJid.Resource != "" { + if !isGroupchat && !isCarbon && toJid.Resource != "" && inviteFrom == "" { message.Extensions = append(message.Extensions, stanza.HintNoCopy{}) } if timestamp != 0 { @@ -256,6 +264,13 @@ func sendMessageWrapper(to, from, body, subject, errorText, id string, component if replaceId != "" { message.Extensions = append(message.Extensions, extensions.Replace{Id: replaceId}) } + if inviteFrom != "" { + message.Extensions = append(message.Extensions, extensions.MessageXMucUserExtension{ + Invite: extensions.MessageXMucUserInvite{ + From: inviteFrom, + }, + }) + } if isCarbon { carbonMessage := extensions.ClientMessage{ @@ -616,3 +631,8 @@ func SendPubSubAvatarNotification(component *xmpp.Component, jid string, chatId _ = ResumableSend(component, message) } + +func InviteToMUC(chatID int64, jid string, component *xmpp.Component) { + sChatID := strconv.FormatInt(chatID, 10) + SendMUCInvite(jid, sChatID, component, Jid.Full()) +} diff --git a/xmpp/handlers.go b/xmpp/handlers.go index 1db3a41..2f3cb0a 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -1137,6 +1137,9 @@ func handleSetQueryCommand(s xmpp.Sender, iq *stanza.IQ, command *stanza.Command errString = fmt.Sprintf("Error for field %v: %v, aborting", field.Var, err.Error()) break } + if field.Var == "muc" && fieldValue == "true" { + go session.MigrateToMUCs() + } infoStrings = append(infoStrings, fmt.Sprintf("%s set to %s", field.Var, value)) gateway.DirtySessions = true }