forked from jshiffer/matterbridge
d00dcf3f58
* Handle Whatsapp threading/replies.
In this change we are updating whatsapp message IDs to include sender JID as this is necessary to reply to a message
https://github.com/tulir/whatsmeow/issues/88#issuecomment-1093195237
https://github.com/tulir/whatsmeow/discussions/148#discussioncomment-3094325
Based on commit 6afa93e537
from #1934
Author: Iiro Laiho <iiro.laiho@iki.fi>
* 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.
* Refactored message replies.
file/Image/audio/replies all work now.
362 lines
9.5 KiB
Go
362 lines
9.5 KiB
Go
//go:build whatsappmulti
|
|
// +build whatsappmulti
|
|
|
|
package bwhatsapp
|
|
|
|
import (
|
|
"fmt"
|
|
"mime"
|
|
"strings"
|
|
|
|
"github.com/42wim/matterbridge/bridge/config"
|
|
"github.com/42wim/matterbridge/bridge/helper"
|
|
|
|
"go.mau.fi/whatsmeow/binary/proto"
|
|
"go.mau.fi/whatsmeow/types"
|
|
"go.mau.fi/whatsmeow/types/events"
|
|
)
|
|
|
|
// nolint:gocritic
|
|
func (b *Bwhatsapp) eventHandler(evt interface{}) {
|
|
switch e := evt.(type) {
|
|
case *events.Message:
|
|
b.handleMessage(e)
|
|
}
|
|
}
|
|
|
|
func (b *Bwhatsapp) handleMessage(message *events.Message) {
|
|
msg := message.Message
|
|
switch {
|
|
case msg == nil, message.Info.IsFromMe, message.Info.Timestamp.Before(b.startedAt):
|
|
return
|
|
}
|
|
|
|
b.Log.Infof("Receiving message %#v", msg)
|
|
|
|
switch {
|
|
case msg.Conversation != nil || msg.ExtendedTextMessage != nil:
|
|
b.handleTextMessage(message.Info, msg)
|
|
case msg.VideoMessage != nil:
|
|
b.handleVideoMessage(message)
|
|
case msg.AudioMessage != nil:
|
|
b.handleAudioMessage(message)
|
|
case msg.DocumentMessage != nil:
|
|
b.handleDocumentMessage(message)
|
|
case msg.ImageMessage != nil:
|
|
b.handleImageMessage(message)
|
|
}
|
|
}
|
|
|
|
// nolint:funlen
|
|
func (b *Bwhatsapp) handleTextMessage(messageInfo types.MessageInfo, msg *proto.Message) {
|
|
senderJID := messageInfo.Sender
|
|
channel := messageInfo.Chat
|
|
|
|
senderName := b.getSenderName(messageInfo)
|
|
|
|
if msg.GetExtendedTextMessage() == nil && msg.GetConversation() == "" {
|
|
b.Log.Debugf("message without text content? %#v", msg)
|
|
return
|
|
}
|
|
|
|
var text string
|
|
|
|
// nolint:nestif
|
|
if msg.GetExtendedTextMessage() == nil {
|
|
text = msg.GetConversation()
|
|
} else if msg.GetExtendedTextMessage().GetContextInfo() == nil {
|
|
// Handle pure text message with a link preview
|
|
// A pure text message with a link preview acts as an extended text message but will not contain any context info
|
|
text = msg.GetExtendedTextMessage().GetText()
|
|
} else {
|
|
text = msg.GetExtendedTextMessage().GetText()
|
|
ci := msg.GetExtendedTextMessage().GetContextInfo()
|
|
|
|
if senderJID == (types.JID{}) && ci.Participant != nil {
|
|
senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer)
|
|
}
|
|
|
|
if ci.MentionedJid != nil {
|
|
// handle user mentions
|
|
for _, mentionedJID := range ci.MentionedJid {
|
|
numberAndSuffix := strings.SplitN(mentionedJID, "@", 2)
|
|
|
|
// mentions comes as telephone numbers and we don't want to expose it to other bridges
|
|
// replace it with something more meaninful to others
|
|
mention := b.getSenderNotify(types.NewJID(numberAndSuffix[0], types.DefaultUserServer))
|
|
|
|
text = strings.Replace(text, "@"+numberAndSuffix[0], "@"+mention, 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
parentID := ""
|
|
if msg.GetExtendedTextMessage() != nil {
|
|
ci := msg.GetExtendedTextMessage().GetContextInfo()
|
|
parentID = getParentIdFromCtx(ci)
|
|
}
|
|
|
|
rmsg := config.Message{
|
|
UserID: senderJID.String(),
|
|
Username: senderName,
|
|
Text: text,
|
|
Channel: channel.String(),
|
|
Account: b.Account,
|
|
Protocol: b.Protocol,
|
|
Extra: make(map[string][]interface{}),
|
|
ID: getMessageIdFormat(senderJID, messageInfo.ID),
|
|
ParentID: parentID,
|
|
}
|
|
|
|
if avatarURL, exists := b.userAvatars[senderJID.String()]; exists {
|
|
rmsg.Avatar = avatarURL
|
|
}
|
|
|
|
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
|
|
b.Log.Debugf("<= Message is %#v", rmsg)
|
|
|
|
b.Remote <- rmsg
|
|
}
|
|
|
|
// HandleImageMessage sent from WhatsApp, relay it to the brige
|
|
func (b *Bwhatsapp) handleImageMessage(msg *events.Message) {
|
|
imsg := msg.Message.GetImageMessage()
|
|
|
|
senderJID := msg.Info.Sender
|
|
senderName := b.getSenderName(msg.Info)
|
|
ci := imsg.GetContextInfo()
|
|
|
|
if senderJID == (types.JID{}) && ci.Participant != nil {
|
|
senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer)
|
|
}
|
|
|
|
rmsg := config.Message{
|
|
UserID: senderJID.String(),
|
|
Username: senderName,
|
|
Channel: msg.Info.Chat.String(),
|
|
Account: b.Account,
|
|
Protocol: b.Protocol,
|
|
Extra: make(map[string][]interface{}),
|
|
ID: getMessageIdFormat(senderJID, msg.Info.ID),
|
|
ParentID: getParentIdFromCtx(ci),
|
|
}
|
|
|
|
if avatarURL, exists := b.userAvatars[senderJID.String()]; exists {
|
|
rmsg.Avatar = avatarURL
|
|
}
|
|
|
|
fileExt, err := mime.ExtensionsByType(imsg.GetMimetype())
|
|
if err != nil {
|
|
b.Log.Errorf("Mimetype detection error: %s", err)
|
|
|
|
return
|
|
}
|
|
|
|
// rename .jfif to .jpg https://github.com/42wim/matterbridge/issues/1292
|
|
if fileExt[0] == ".jfif" {
|
|
fileExt[0] = ".jpg"
|
|
}
|
|
|
|
// rename .jpe to .jpg https://github.com/42wim/matterbridge/issues/1463
|
|
if fileExt[0] == ".jpe" {
|
|
fileExt[0] = ".jpg"
|
|
}
|
|
|
|
filename := fmt.Sprintf("%v%v", msg.Info.ID, fileExt[0])
|
|
|
|
b.Log.Debugf("Trying to download %s with type %s", filename, imsg.GetMimetype())
|
|
|
|
data, err := b.wc.Download(imsg)
|
|
if err != nil {
|
|
b.Log.Errorf("Download image failed: %s", err)
|
|
|
|
return
|
|
}
|
|
|
|
// Move file to bridge storage
|
|
helper.HandleDownloadData(b.Log, &rmsg, filename, imsg.GetCaption(), "", &data, b.General)
|
|
|
|
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
|
|
b.Log.Debugf("<= Message is %#v", rmsg)
|
|
|
|
b.Remote <- rmsg
|
|
}
|
|
|
|
// HandleVideoMessage downloads video messages
|
|
func (b *Bwhatsapp) handleVideoMessage(msg *events.Message) {
|
|
imsg := msg.Message.GetVideoMessage()
|
|
|
|
senderJID := msg.Info.Sender
|
|
senderName := b.getSenderName(msg.Info)
|
|
ci := imsg.GetContextInfo()
|
|
|
|
if senderJID == (types.JID{}) && ci.Participant != nil {
|
|
senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer)
|
|
}
|
|
|
|
rmsg := config.Message{
|
|
UserID: senderJID.String(),
|
|
Username: senderName,
|
|
Channel: msg.Info.Chat.String(),
|
|
Account: b.Account,
|
|
Protocol: b.Protocol,
|
|
Extra: make(map[string][]interface{}),
|
|
ID: getMessageIdFormat(senderJID, msg.Info.ID),
|
|
ParentID: getParentIdFromCtx(ci),
|
|
}
|
|
|
|
if avatarURL, exists := b.userAvatars[senderJID.String()]; exists {
|
|
rmsg.Avatar = avatarURL
|
|
}
|
|
|
|
fileExt, err := mime.ExtensionsByType(imsg.GetMimetype())
|
|
if err != nil {
|
|
b.Log.Errorf("Mimetype detection error: %s", err)
|
|
|
|
return
|
|
}
|
|
|
|
if len(fileExt) == 0 {
|
|
fileExt = append(fileExt, ".mp4")
|
|
}
|
|
|
|
// Prefer .mp4 extension, otherwise fallback to first index
|
|
fileExtIndex := 0
|
|
for i, n := range fileExt {
|
|
if ".mp4" == n {
|
|
fileExtIndex = i
|
|
break
|
|
}
|
|
}
|
|
|
|
filename := fmt.Sprintf("%v%v", msg.Info.ID, fileExt[fileExtIndex])
|
|
|
|
b.Log.Debugf("Trying to download %s with size %#v and type %s", filename, imsg.GetFileLength(), imsg.GetMimetype())
|
|
|
|
data, err := b.wc.Download(imsg)
|
|
if err != nil {
|
|
b.Log.Errorf("Download video failed: %s", err)
|
|
|
|
return
|
|
}
|
|
|
|
// Move file to bridge storage
|
|
helper.HandleDownloadData(b.Log, &rmsg, filename, imsg.GetCaption(), "", &data, b.General)
|
|
|
|
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
|
|
b.Log.Debugf("<= Message is %#v", rmsg)
|
|
|
|
b.Remote <- rmsg
|
|
}
|
|
|
|
// HandleAudioMessage downloads audio messages
|
|
func (b *Bwhatsapp) handleAudioMessage(msg *events.Message) {
|
|
imsg := msg.Message.GetAudioMessage()
|
|
|
|
senderJID := msg.Info.Sender
|
|
senderName := b.getSenderName(msg.Info)
|
|
ci := imsg.GetContextInfo()
|
|
|
|
if senderJID == (types.JID{}) && ci.Participant != nil {
|
|
senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer)
|
|
}
|
|
rmsg := config.Message{
|
|
UserID: senderJID.String(),
|
|
Username: senderName,
|
|
Channel: msg.Info.Chat.String(),
|
|
Account: b.Account,
|
|
Protocol: b.Protocol,
|
|
Extra: make(map[string][]interface{}),
|
|
ID: getMessageIdFormat(senderJID, msg.Info.ID),
|
|
ParentID: getParentIdFromCtx(ci),
|
|
}
|
|
|
|
if avatarURL, exists := b.userAvatars[senderJID.String()]; exists {
|
|
rmsg.Avatar = avatarURL
|
|
}
|
|
|
|
fileExt, err := mime.ExtensionsByType(imsg.GetMimetype())
|
|
if err != nil {
|
|
b.Log.Errorf("Mimetype detection error: %s", err)
|
|
|
|
return
|
|
}
|
|
|
|
if len(fileExt) == 0 {
|
|
fileExt = append(fileExt, ".ogg")
|
|
}
|
|
|
|
filename := fmt.Sprintf("%v%v", msg.Info.ID, fileExt[0])
|
|
|
|
b.Log.Debugf("Trying to download %s with size %#v and type %s", filename, imsg.GetFileLength(), imsg.GetMimetype())
|
|
|
|
data, err := b.wc.Download(imsg)
|
|
if err != nil {
|
|
b.Log.Errorf("Download video failed: %s", err)
|
|
|
|
return
|
|
}
|
|
|
|
// Move file to bridge storage
|
|
helper.HandleDownloadData(b.Log, &rmsg, filename, "audio message", "", &data, b.General)
|
|
|
|
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
|
|
b.Log.Debugf("<= Message is %#v", rmsg)
|
|
|
|
b.Remote <- rmsg
|
|
}
|
|
|
|
// HandleDocumentMessage downloads documents
|
|
func (b *Bwhatsapp) handleDocumentMessage(msg *events.Message) {
|
|
imsg := msg.Message.GetDocumentMessage()
|
|
|
|
senderJID := msg.Info.Sender
|
|
senderName := b.getSenderName(msg.Info)
|
|
ci := imsg.GetContextInfo()
|
|
|
|
if senderJID == (types.JID{}) && ci.Participant != nil {
|
|
senderJID = types.NewJID(ci.GetParticipant(), types.DefaultUserServer)
|
|
}
|
|
|
|
rmsg := config.Message{
|
|
UserID: senderJID.String(),
|
|
Username: senderName,
|
|
Channel: msg.Info.Chat.String(),
|
|
Account: b.Account,
|
|
Protocol: b.Protocol,
|
|
Extra: make(map[string][]interface{}),
|
|
ID: getMessageIdFormat(senderJID, msg.Info.ID),
|
|
ParentID: getParentIdFromCtx(ci),
|
|
}
|
|
|
|
if avatarURL, exists := b.userAvatars[senderJID.String()]; exists {
|
|
rmsg.Avatar = avatarURL
|
|
}
|
|
|
|
fileExt, err := mime.ExtensionsByType(imsg.GetMimetype())
|
|
if err != nil {
|
|
b.Log.Errorf("Mimetype detection error: %s", err)
|
|
|
|
return
|
|
}
|
|
|
|
filename := fmt.Sprintf("%v", imsg.GetFileName())
|
|
|
|
b.Log.Debugf("Trying to download %s with extension %s and type %s", filename, fileExt, imsg.GetMimetype())
|
|
|
|
data, err := b.wc.Download(imsg)
|
|
if err != nil {
|
|
b.Log.Errorf("Download document message failed: %s", err)
|
|
|
|
return
|
|
}
|
|
|
|
// Move file to bridge storage
|
|
helper.HandleDownloadData(b.Log, &rmsg, filename, imsg.GetCaption(), "", &data, b.General)
|
|
|
|
b.Log.Debugf("<= Sending message from %s on %s to gateway", senderJID, b.Account)
|
|
b.Log.Debugf("<= Message is %#v", rmsg)
|
|
|
|
b.Remote <- rmsg
|
|
}
|