package xmpp import ( "strconv" "strings" log "github.com/sirupsen/logrus" "gosrc.io/xmpp" "gosrc.io/xmpp/stanza" "dev.narayana.im/narayana/telegabber/telegram" "dev.narayana.im/narayana/telegabber/xmpp/extensions" "dev.narayana.im/narayana/telegabber/xmpp/gateway" ) // collectForwardedMessages returns every XEP-0297 wrapping a // message on msg, in stanza order. A client forwarding several selected // messages at once (e.g. an album plus a reply) puts them all in one // stanza as sibling elements - stanza.Message.Get only // returns the first match of a given extension type, so callers that // need all of them must scan msg.Extensions directly instead. func collectForwardedMessages(msg stanza.Message) []*extensions.ForwardedMessage { var result []*extensions.ForwardedMessage for _, e := range msg.Extensions { if forwarded, ok := e.(*extensions.ForwardedMessage); ok && forwarded.Message != nil { result = append(result, forwarded) } } return result } // hasForwardedPayload reports whether msg carries at least one XEP-0297 // wrapping a message - such stanzas may ride without a plain // body, mirroring hasReactionsPayload/hasOMEMOPayload's role in // HandleMessage's body-gate. func hasForwardedPayload(msg stanza.Message) bool { return len(collectForwardedMessages(msg)) > 0 } // fileSharingNames extracts each XEP-0447 // value nested in inner - a client bundling several files (e.g. a // gallery selection) under one forwarded reference lists them as sibling // file-sharing elements rather than separate wrappers. func fileSharingNames(inner *extensions.ClientMessage) []string { var names []string for _, e := range inner.Extensions { if fs, ok := e.(*extensions.FileSharing); ok && fs.File.Name != "" { names = append(names, fs.File.Name) } } return names } // handleIncomingForward relays one or more native XEP-0297 forwards to // Telegram via TDLib's own ForwardMessages, ignoring whatever plain-text // body/fallback rides alongside them - only each wrapped message's own // identity matters. Message ids are grouped by their resolved source // chat and forwarded together per chat (in original order) so TDLib can // still recognize and re-form an album, rather than resending each item // as its own standalone forward. bare and toID are already resolved by // HandleMessage's caller; toID is the forward's destination chat. func handleIncomingForward(component *xmpp.Component, msg stanza.Message, session *telegram.Client, bare string, toID int64, isGroupchat bool, forwardedList []*extensions.ForwardedMessage) { type sourceGroup struct { chatId int64 msgIds []int64 seen map[int64]bool } var groups []*sourceGroup groupByChatId := make(map[int64]*sourceGroup) addToGroup := func(chatId int64, msgIds []int64) { group, ok := groupByChatId[chatId] if !ok { group = &sourceGroup{chatId: chatId, seen: make(map[int64]bool)} groupByChatId[chatId] = group groups = append(groups, group) } for _, msgId := range msgIds { if group.seen[msgId] { continue } group.seen[msgId] = true group.msgIds = append(group.msgIds, msgId) } } for _, forwarded := range forwardedList { inner := forwarded.Message if inner.Id == "" { log.Warn("Forwarded message has no id, cannot resolve its source") continue } var sourceChatId, sourceMsgId int64 if chatId, msgId, err := gateway.IdsDB.GetByXmppId(session.Session.Login, bare, inner.Id); err == nil { sourceChatId, sourceMsgId = chatId, msgId } else { msgId, ok := parseMessageId(inner.Id) if !ok { continue } fromBare, _, ok := gateway.SplitJID(inner.From) if !ok { log.Warn("Could not parse forwarded message sender JID") continue } node := strings.TrimPrefix(strings.SplitN(fromBare, "@", 2)[0], "c") chatId, parseErr := strconv.ParseInt(node, 10, 64) if parseErr != nil { log.Warn("Could not parse forwarded source chat id") continue } sourceChatId, sourceMsgId = chatId, msgId } // A reference bundling several files (SendAlbumToGateway's own // anchor id, or any client that groups a gallery selection this // way) only ever names one representative message on its own - // match the embedded file-sharing filenames back to the album's // real per-file messages instead of resending just that one. if names := fileSharingNames(inner); len(names) > 1 { matched := session.ResolveAlbumFileNames(sourceChatId, sourceMsgId, names) log.Infof("Forwarded album %v/%v: names=%v matched=%v", sourceChatId, sourceMsgId, names, matched) if len(matched) < len(names) { log.Warnf("Only matched %v/%v forwarded file-sharing names to album messages for %v/%v", len(matched), len(names), sourceChatId, sourceMsgId) } if len(matched) > 0 { addToGroup(sourceChatId, matched) continue } } addToGroup(sourceChatId, []int64{sourceMsgId}) } if len(groups) == 0 { return } var from string if isGroupchat { from = gateway.MUCJID(toID) } else { from = gateway.CHATNODE(toID) } for _, group := range groups { messages, err := session.ForwardMessage(group.chatId, toID, group.msgIds) if err != nil { gateway.SendErrorMessage(msg.From, from, err.Error(), 500, isGroupchat, component) continue } for _, message := range messages { session.ProcessIncomingMessage(toID, message) } } }