From cffa6d32402370b1afbeadd429088edeac9be499 Mon Sep 17 00:00:00 2001 From: Yousef Mansy Date: Thu, 23 Feb 2023 03:19:04 -0800 Subject: [PATCH] Fix replies. Sender JID can have a `:` inside of it, using `/` as a delimiter now. Added messageID parser + struct. messages sent with an attachment do not show replies But at least common `sendMessage` will make repies from whatsapp to an attachement bridge across. The new message ID format broke message deleting, so we change the messageID into the real id at the beginning of send. We really do need the extra info for when we reply to a message though. --- bridge/whatsappmulti/helpers.go | 29 +++++++++- bridge/whatsappmulti/whatsapp.go | 95 +++++++++++++++----------------- 2 files changed, 72 insertions(+), 52 deletions(-) diff --git a/bridge/whatsappmulti/helpers.go b/bridge/whatsappmulti/helpers.go index 513286f0..7c59b9a9 100644 --- a/bridge/whatsappmulti/helpers.go +++ b/bridge/whatsappmulti/helpers.go @@ -134,6 +134,31 @@ func appendParentID(ci *proto.ContextInfo, rmsg *config.Message) { } } -func getMessageIdFormat(authorJID string, messageID string) string { - return fmt.Sprintf("%s:%s", authorJID, messageID) +func (b *Bwhatsapp) parseMessageID(id string) (*Replyable, error) { + // No message ID in case action is executed on a message sent before the bridge was started + // and then the bridge cache doesn't have this message ID mapped + if id == "" { + return &Replyable{MessageID: id}, nil + } + + replyInfo := strings.Split(id, "/") + + if len(replyInfo) == 2 { + sender, err := types.ParseJID(replyInfo[0]) + + if err == nil { + return &Replyable{ + MessageID: types.MessageID(replyInfo[1]), + Sender: sender, + }, nil + } + } + + err := fmt.Errorf("MessageID does not match format of {senderJID}:{messageID} : \"%s\"", id) + + return &Replyable{MessageID: id}, err +} + +func getMessageIdFormat(authorJID string, messageID string) string { + return fmt.Sprintf("%s/%s", authorJID, messageID) } diff --git a/bridge/whatsappmulti/whatsapp.go b/bridge/whatsappmulti/whatsapp.go index e606e621..c403be16 100644 --- a/bridge/whatsappmulti/whatsapp.go +++ b/bridge/whatsappmulti/whatsapp.go @@ -10,7 +10,6 @@ import ( "mime" "os" "path/filepath" - "strings" "time" "github.com/42wim/matterbridge/bridge" @@ -43,6 +42,11 @@ type Bwhatsapp struct { userAvatars map[string]string } +type Replyable struct { + MessageID types.MessageID + Sender types.JID +} + // New Create a new WhatsApp bridge. This will be called for each [whatsapp.] entry you have in the config file func New(cfg *bridge.Config) bridge.Bridger { number := cfg.GetString(cfgNumber) @@ -247,8 +251,6 @@ func (b *Bwhatsapp) PostDocumentMessage(msg config.Message, filetype string) (st // Post an image message from the bridge to WhatsApp // Handle, for sure image/jpeg, image/png and image/gif MIME types func (b *Bwhatsapp) PostImageMessage(msg config.Message, filetype string) (string, error) { - groupJID, _ := types.ParseJID(msg.Channel) - fi := msg.Extra["file"][0].(config.FileInfo) caption := msg.Username + fi.Comment @@ -272,16 +274,11 @@ func (b *Bwhatsapp) PostImageMessage(msg config.Message, filetype string) (strin b.Log.Debugf("=> Sending %#v as an image", msg) - ID := whatsmeow.GenerateMessageID() - _, err = b.wc.SendMessage(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID}) - - return ID, err + return b.sendMessage(msg, &message) } // Post a video message from the bridge to WhatsApp func (b *Bwhatsapp) PostVideoMessage(msg config.Message, filetype string) (string, error) { - groupJID, _ := types.ParseJID(msg.Channel) - fi := msg.Extra["file"][0].(config.FileInfo) caption := msg.Username + fi.Comment @@ -305,10 +302,7 @@ func (b *Bwhatsapp) PostVideoMessage(msg config.Message, filetype string) (strin b.Log.Debugf("=> Sending %#v as a video", msg) - ID := whatsmeow.GenerateMessageID() - _, err = b.wc.SendMessage(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID}) - - return ID, err + return b.sendMessage(msg, &message) } // Post audio inline @@ -335,8 +329,7 @@ func (b *Bwhatsapp) PostAudioMessage(msg config.Message, filetype string) (strin b.Log.Debugf("=> Sending %#v as audio", msg) - ID := whatsmeow.GenerateMessageID() - _, err = b.wc.SendMessage(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID}) + ID, err := b.sendMessage(msg, &message) var captionMessage proto.Message caption := msg.Username + fi.Comment + "\u2B06" // the char on the end is upwards arrow emoji @@ -348,10 +341,46 @@ func (b *Bwhatsapp) PostAudioMessage(msg config.Message, filetype string) (strin return ID, err } +func (b *Bwhatsapp) sendMessage(rmsg config.Message, message *proto.Message) (string, error) { + groupJID, _ := types.ParseJID(rmsg.Channel) + ID := whatsmeow.GenerateMessageID() + text := rmsg.Username + rmsg.Text + + // If we have a parent ID send an extended message + if rmsg.ParentID != "" { + replyInfo, err := b.parseMessageID(rmsg.ParentID) + + if err == nil { + sender := replyInfo.Sender.String() + + // append reply info + message.ExtendedTextMessage = &proto.ExtendedTextMessage{ + Text: &text, + ContextInfo: &proto.ContextInfo{ + StanzaId: &replyInfo.MessageID, + Participant: &sender, + QuotedMessage: &proto.Message{Conversation: goproto.String("")}, + }, + } + + _, err := b.wc.SendMessage(context.Background(), groupJID, message, whatsmeow.SendRequestExtra{ID: ID}) + + return getMessageIdFormat(b.Config.GetString("Number")[1:]+"@s.whatsapp.net", ID), err + } + } + + _, err := b.wc.SendMessage(context.TODO(), groupJID, message, whatsmeow.SendRequestExtra{ID: ID}) + + return getMessageIdFormat(b.Config.GetString("Number")[1:]+"@s.whatsapp.net", ID), err +} + // Send a message from the bridge to WhatsApp func (b *Bwhatsapp) Send(msg config.Message) (string, error) { groupJID, _ := types.ParseJID(msg.Channel) + extendedMsgID, _ := b.parseMessageID(msg.ID) + msg.ID = extendedMsgID.MessageID + b.Log.Debugf("=> Receiving %#v", msg) // Delete message @@ -403,43 +432,9 @@ func (b *Bwhatsapp) Send(msg config.Message) (string, error) { text := msg.Username + msg.Text - ID := whatsmeow.GenerateMessageID() - - // If we have a parent ID send an extended message - if msg.ParentID != "" { - // in appendParentID() we combine the "Participant" and the "StanzaID" as both are needed to send a reply - replyInfo := strings.Split(msg.ParentID, ":") - - if len(replyInfo) != 2 { - b.Log.Debug("Malformed reply info to whatsapp: %s", msg.ParentID) - } else { - // Send message with reply (not working) - // https://github.com/tulir/whatsmeow/issues/88#issuecomment-1093195237 - _, err := b.wc.SendMessage( - context.Background(), - groupJID, - &proto.Message{ - ExtendedTextMessage: &proto.ExtendedTextMessage{ - Text: &text, - ContextInfo: &proto.ContextInfo{ - StanzaId: &replyInfo[1], - Participant: &replyInfo[0], - QuotedMessage: &proto.Message{Conversation: goproto.String("")}, - }, - }, - }, - whatsmeow.SendRequestExtra{ID: ID}, - ) - - return getMessageIdFormat(b.Config.GetString("Number")[1:]+"@s.whatsapp.net", ID), err - } - } - var message proto.Message message.Conversation = &text - _, err := b.wc.SendMessage(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID}) - - return getMessageIdFormat(b.Config.GetString("Number")[1:]+"@s.whatsapp.net", ID), err + return b.sendMessage(msg, &message) }