Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions pkg/sendMessage/service/mention.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package send_service

import (
"go.mau.fi/whatsmeow/proto/waE2E"
"go.mau.fi/whatsmeow/types"
)

// participantMentionJID prefers the phone-number JID when available.
// Mentions require @s.whatsapp.net; participant.JID may be @lid.
func participantMentionJID(p types.GroupParticipant) string {
if !p.PhoneNumber.IsEmpty() {
return p.PhoneNumber.String()
}
return p.JID.String()
}

// setMessageMentionedJIDs writes ContextInfo.MentionedJID for the given message type.
// DocumentMessage may live under DocumentWithCaptionMessage when a caption is set.
func setMessageMentionedJIDs(msg *waE2E.Message, messageType string, mentionedJIDs []string) {
if msg == nil || len(mentionedJIDs) == 0 {
return
}

switch messageType {
case "ExtendedTextMessage":
if msg.ExtendedTextMessage == nil {
return
}
if msg.ExtendedTextMessage.ContextInfo == nil {
msg.ExtendedTextMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.ExtendedTextMessage.ContextInfo.MentionedJID = mentionedJIDs
case "ImageMessage":
if msg.ImageMessage == nil {
return
}
if msg.ImageMessage.ContextInfo == nil {
msg.ImageMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.ImageMessage.ContextInfo.MentionedJID = mentionedJIDs
case "VideoMessage":
if msg.VideoMessage == nil {
return
}
if msg.VideoMessage.ContextInfo == nil {
msg.VideoMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.VideoMessage.ContextInfo.MentionedJID = mentionedJIDs
case "PtvMessage":
if msg.PtvMessage == nil {
return
}
if msg.PtvMessage.ContextInfo == nil {
msg.PtvMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.PtvMessage.ContextInfo.MentionedJID = mentionedJIDs
case "AudioMessage":
if msg.AudioMessage == nil {
return
}
if msg.AudioMessage.ContextInfo == nil {
msg.AudioMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.AudioMessage.ContextInfo.MentionedJID = mentionedJIDs
case "DocumentMessage":
if msg.DocumentMessage != nil {
if msg.DocumentMessage.ContextInfo == nil {
msg.DocumentMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.DocumentMessage.ContextInfo.MentionedJID = mentionedJIDs
} else if msg.DocumentWithCaptionMessage != nil &&
msg.DocumentWithCaptionMessage.Message != nil &&
msg.DocumentWithCaptionMessage.Message.DocumentMessage != nil {
doc := msg.DocumentWithCaptionMessage.Message.DocumentMessage
if doc.ContextInfo == nil {
doc.ContextInfo = &waE2E.ContextInfo{}
}
doc.ContextInfo.MentionedJID = mentionedJIDs
}
case "PollCreationMessage":
if msg.PollCreationMessage == nil {
return
}
if msg.PollCreationMessage.ContextInfo == nil {
msg.PollCreationMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.PollCreationMessage.ContextInfo.MentionedJID = mentionedJIDs
case "StickerMessage":
if msg.StickerMessage == nil {
return
}
if msg.StickerMessage.ContextInfo == nil {
msg.StickerMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.StickerMessage.ContextInfo.MentionedJID = mentionedJIDs
case "LocationMessage":
if msg.LocationMessage == nil {
return
}
if msg.LocationMessage.ContextInfo == nil {
msg.LocationMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.LocationMessage.ContextInfo.MentionedJID = mentionedJIDs
case "ContactMessage":
if msg.ContactMessage == nil {
return
}
if msg.ContactMessage.ContextInfo == nil {
msg.ContactMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.ContactMessage.ContextInfo.MentionedJID = mentionedJIDs
}
}
110 changes: 3 additions & 107 deletions pkg/sendMessage/service/send_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2638,117 +2638,13 @@ func (s *sendService) SendMessage(instance *instance_model.Instance, msg *waE2E.

var mentionedJIDs []string
for _, participant := range groupInfo.Participants {
mentionedJIDs = append(mentionedJIDs, participant.JID.String())
mentionedJIDs = append(mentionedJIDs, participantMentionJID(participant))
}

switch messageType {
case "ExtendedTextMessage":
if msg.ExtendedTextMessage.ContextInfo == nil {
msg.ExtendedTextMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.ExtendedTextMessage.ContextInfo.MentionedJID = mentionedJIDs
case "ImageMessage":
if msg.ImageMessage.ContextInfo == nil {
msg.ImageMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.ImageMessage.ContextInfo.MentionedJID = mentionedJIDs
case "VideoMessage":
if msg.VideoMessage.ContextInfo == nil {
msg.VideoMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.VideoMessage.ContextInfo.MentionedJID = mentionedJIDs
case "PtvMessage":
if msg.PtvMessage.ContextInfo == nil {
msg.PtvMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.PtvMessage.ContextInfo.MentionedJID = mentionedJIDs
case "AudioMessage":
if msg.AudioMessage.ContextInfo == nil {
msg.AudioMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.AudioMessage.ContextInfo.MentionedJID = mentionedJIDs
case "DocumentMessage":
if msg.DocumentMessage.ContextInfo == nil {
msg.DocumentMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.DocumentMessage.ContextInfo.MentionedJID = mentionedJIDs
case "PollCreationMessage":
if msg.PollCreationMessage.ContextInfo == nil {
msg.PollCreationMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.PollCreationMessage.ContextInfo.MentionedJID = mentionedJIDs
case "StickerMessage":
if msg.StickerMessage.ContextInfo == nil {
msg.StickerMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.StickerMessage.ContextInfo.MentionedJID = mentionedJIDs
case "LocationMessage":
if msg.LocationMessage.ContextInfo == nil {
msg.LocationMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.LocationMessage.ContextInfo.MentionedJID = mentionedJIDs
case "ContactMessage":
if msg.ContactMessage.ContextInfo == nil {
msg.ContactMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.ContactMessage.ContextInfo.MentionedJID = mentionedJIDs
}

setMessageMentionedJIDs(msg, messageType, mentionedJIDs)
}

if len(data.MentionedJID) > 0 {
switch messageType {
case "ExtendedTextMessage":
if msg.ExtendedTextMessage.ContextInfo == nil {
msg.ExtendedTextMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.ExtendedTextMessage.ContextInfo.MentionedJID = data.MentionedJID
case "ImageMessage":
if msg.ImageMessage.ContextInfo == nil {
msg.ImageMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.ImageMessage.ContextInfo.MentionedJID = data.MentionedJID
case "VideoMessage":
if msg.VideoMessage.ContextInfo == nil {
msg.VideoMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.VideoMessage.ContextInfo.MentionedJID = data.MentionedJID
case "PtvMessage":
if msg.PtvMessage.ContextInfo == nil {
msg.PtvMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.PtvMessage.ContextInfo.MentionedJID = data.MentionedJID
case "AudioMessage":
if msg.AudioMessage.ContextInfo == nil {
msg.AudioMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.AudioMessage.ContextInfo.MentionedJID = data.MentionedJID
case "DocumentMessage":
if msg.DocumentMessage.ContextInfo == nil {
msg.DocumentMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.DocumentMessage.ContextInfo.MentionedJID = data.MentionedJID
case "PollCreationMessage":
if msg.PollCreationMessage.ContextInfo == nil {
msg.PollCreationMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.PollCreationMessage.ContextInfo.MentionedJID = data.MentionedJID
case "StickerMessage":
if msg.StickerMessage.ContextInfo == nil {
msg.StickerMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.StickerMessage.ContextInfo.MentionedJID = data.MentionedJID
case "LocationMessage":
if msg.LocationMessage.ContextInfo == nil {
msg.LocationMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.LocationMessage.ContextInfo.MentionedJID = data.MentionedJID
case "ContactMessage":
if msg.ContactMessage.ContextInfo == nil {
msg.ContactMessage.ContextInfo = &waE2E.ContextInfo{}
}
msg.ContactMessage.ContextInfo.MentionedJID = data.MentionedJID
}
setMessageMentionedJIDs(msg, messageType, data.MentionedJID)
}
}

Expand Down
164 changes: 164 additions & 0 deletions pkg/sendMessage/service/send_service_mention_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package send_service

import (
"testing"

"go.mau.fi/whatsmeow/proto/waE2E"
"go.mau.fi/whatsmeow/types"
"google.golang.org/protobuf/proto"
)

func TestParticipantMentionJID(t *testing.T) {
t.Parallel()

tests := []struct {
name string
p types.GroupParticipant
want string
}{
{
name: "prefers_phone_number_over_lid",
p: types.GroupParticipant{
JID: types.NewJID("123456789012345", types.HiddenUserServer),
PhoneNumber: types.NewJID("5511999999999", types.DefaultUserServer),
},
want: "5511999999999@s.whatsapp.net",
},
{
name: "falls_back_to_jid_when_phone_empty",
p: types.GroupParticipant{
JID: types.NewJID("5511888888888", types.DefaultUserServer),
},
want: "5511888888888@s.whatsapp.net",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := participantMentionJID(tt.p)
if got != tt.want {
t.Fatalf("participantMentionJID() = %q, want %q", got, tt.want)
}
})
}
}

func TestSetMessageMentionedJIDs(t *testing.T) {
t.Parallel()

mentioned := []string{"5511999999999@s.whatsapp.net", "5511888888888@s.whatsapp.net"}

tests := []struct {
name string
messageType string
msg *waE2E.Message
wantFrom func(msg *waE2E.Message) []string
}{
{
name: "doc_without_caption",
messageType: "DocumentMessage",
msg: &waE2E.Message{
DocumentMessage: &waE2E.DocumentMessage{
Caption: proto.String(""),
},
},
wantFrom: func(msg *waE2E.Message) []string {
return msg.DocumentMessage.ContextInfo.MentionedJID
},
},
{
name: "doc_with_caption",
messageType: "DocumentMessage",
msg: &waE2E.Message{
DocumentWithCaptionMessage: &waE2E.FutureProofMessage{
Message: &waE2E.Message{
DocumentMessage: &waE2E.DocumentMessage{
Caption: proto.String("hello @all"),
},
},
},
},
wantFrom: func(msg *waE2E.Message) []string {
return msg.DocumentWithCaptionMessage.Message.DocumentMessage.ContextInfo.MentionedJID
},
},
{
name: "image",
messageType: "ImageMessage",
msg: &waE2E.Message{
ImageMessage: &waE2E.ImageMessage{
Caption: proto.String("caption"),
},
},
wantFrom: func(msg *waE2E.Message) []string {
return msg.ImageMessage.ContextInfo.MentionedJID
},
},
{
name: "video",
messageType: "VideoMessage",
msg: &waE2E.Message{
VideoMessage: &waE2E.VideoMessage{
Caption: proto.String("caption"),
},
},
wantFrom: func(msg *waE2E.Message) []string {
return msg.VideoMessage.ContextInfo.MentionedJID
},
},
{
name: "extended_text",
messageType: "ExtendedTextMessage",
msg: &waE2E.Message{
ExtendedTextMessage: &waE2E.ExtendedTextMessage{
Text: proto.String("@todos"),
},
},
wantFrom: func(msg *waE2E.Message) []string {
return msg.ExtendedTextMessage.ContextInfo.MentionedJID
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
setMessageMentionedJIDs(tt.msg, tt.messageType, mentioned)
got := tt.wantFrom(tt.msg)
if len(got) != len(mentioned) {
t.Fatalf("MentionedJID len = %d, want %d (got %#v)", len(got), len(mentioned), got)
}
for i := range mentioned {
if got[i] != mentioned[i] {
t.Fatalf("MentionedJID[%d] = %q, want %q", i, got[i], mentioned[i])
}
}
})
}
}

func TestSetMessageMentionedJIDs_DocWithCaptionDoesNotPanic(t *testing.T) {
t.Parallel()

// Reproduces the #114 failure mode: DocumentMessage is nil after wrap.
msg := &waE2E.Message{
DocumentWithCaptionMessage: &waE2E.FutureProofMessage{
Message: &waE2E.Message{
DocumentMessage: &waE2E.DocumentMessage{
Caption: proto.String("doc caption"),
},
},
},
}

setMessageMentionedJIDs(msg, "DocumentMessage", []string{"5511999999999@s.whatsapp.net"})

got := msg.DocumentWithCaptionMessage.Message.DocumentMessage.ContextInfo.MentionedJID
if len(got) != 1 || got[0] != "5511999999999@s.whatsapp.net" {
t.Fatalf("unexpected MentionedJID: %#v", got)
}
if msg.DocumentMessage != nil {
t.Fatal("DocumentMessage should remain nil for captioned documents")
}
}