2024-10-07 14:21:56 +03:00
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
// Copyright (C) 2024 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package msgconv
import (
2024-10-07 16:14:58 +03:00
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
2024-10-07 14:21:56 +03:00
2024-10-07 16:14:58 +03:00
"github.com/rs/zerolog"
"go.mau.fi/util/ptr"
"go.mau.fi/util/random"
"go.mau.fi/whatsmeow"
"go.mau.fi/whatsmeow/proto/waE2E"
"go.mau.fi/whatsmeow/types"
"maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/bridgev2/database"
2024-10-07 14:21:56 +03:00
"maunium.net/go/mautrix/event"
2024-10-07 16:14:58 +03:00
"maunium.net/go/mautrix/format"
2024-10-07 14:21:56 +03:00
2024-10-15 17:59:57 +03:00
"go.mau.fi/mautrix-whatsapp/pkg/waid"
2024-10-07 14:21:56 +03:00
)
2024-10-07 16:14:58 +03:00
func ( mc * MessageConverter ) msc1767ToWhatsApp ( ctx context . Context , msg event . MSC1767Message , allowedMentions * event . Mentions ) ( string , [ ] string ) {
2024-10-07 14:21:56 +03:00
for _ , part := range msg . Message {
if part . MimeType == "text/html" && msg . HTML == "" {
msg . HTML = part . Body
} else if part . MimeType == "text/plain" && msg . Text == "" {
msg . Text = part . Body
}
}
2024-10-07 16:14:58 +03:00
mentions := make ( [ ] string , 0 )
2024-10-07 14:21:56 +03:00
if msg . HTML != "" {
2024-10-07 16:14:58 +03:00
parseCtx := format . NewContext ( ctx )
parseCtx . ReturnData [ "allowed_mentions" ] = allowedMentions
parseCtx . ReturnData [ "output_mentions" ] = & mentions
return mc . HTMLParser . Parse ( msg . HTML , parseCtx ) , mentions
2024-10-07 14:21:56 +03:00
}
2024-10-07 16:14:58 +03:00
return msg . Text , mentions
2024-10-07 14:21:56 +03:00
}
2024-10-07 16:14:58 +03:00
var (
errPollMissingQuestion = bridgev2 . WrapErrorInStatus ( errors . New ( "poll message is missing question" ) ) . WithIsCertain ( true ) . WithErrorAsMessage ( ) . WithSendNotice ( true ) . WithErrorReason ( event . MessageStatusUnsupported )
errPollDuplicateOption = bridgev2 . WrapErrorInStatus ( errors . New ( "poll options must be unique" ) ) . WithIsCertain ( true ) . WithErrorAsMessage ( ) . WithSendNotice ( true ) . WithErrorReason ( event . MessageStatusUnsupported )
)
2024-10-07 14:21:56 +03:00
2024-10-07 16:14:58 +03:00
func ( mc * MessageConverter ) PollStartToWhatsApp (
ctx context . Context ,
content * event . PollStartEventContent ,
replyTo * database . Message ,
portal * bridgev2 . Portal ,
) ( * waE2E . Message , map [ [ 32 ] byte ] string , error ) {
maxAnswers := content . PollStart . MaxSelections
if maxAnswers >= len ( content . PollStart . Answers ) || maxAnswers < 0 {
maxAnswers = 0
2024-10-07 14:21:56 +03:00
}
2026-03-18 15:26:28 +02:00
contextInfo := mc . generateContextInfo ( ctx , replyTo , portal , nil , content . Mentions != nil && content . Mentions . Room )
2024-10-07 16:14:58 +03:00
var question string
question , contextInfo . MentionedJID = mc . msc1767ToWhatsApp ( ctx , content . PollStart . Question , content . Mentions )
if len ( question ) == 0 {
return nil , nil , errPollMissingQuestion
}
options := make ( [ ] * waE2E . PollCreationMessage_Option , len ( content . PollStart . Answers ) )
optionMap := make ( map [ [ 32 ] byte ] string , len ( options ) )
for i , opt := range content . PollStart . Answers {
body , _ := mc . msc1767ToWhatsApp ( ctx , opt . MSC1767Message , & event . Mentions { } )
hash := sha256 . Sum256 ( [ ] byte ( body ) )
if _ , alreadyExists := optionMap [ hash ] ; alreadyExists {
zerolog . Ctx ( ctx ) . Warn ( ) . Str ( "option" , body ) . Msg ( "Poll has duplicate options, rejecting" )
return nil , nil , errPollDuplicateOption
}
optionMap [ hash ] = opt . ID
options [ i ] = & waE2E . PollCreationMessage_Option {
OptionName : ptr . Ptr ( body ) ,
}
}
return & waE2E . Message {
PollCreationMessage : & waE2E . PollCreationMessage {
Name : ptr . Ptr ( question ) ,
Options : options ,
SelectableOptionsCount : ptr . Ptr ( uint32 ( maxAnswers ) ) ,
ContextInfo : contextInfo ,
} ,
MessageContextInfo : & waE2E . MessageContextInfo {
MessageSecret : random . Bytes ( 32 ) ,
} ,
} , optionMap , nil
2024-10-07 14:21:56 +03:00
}
2024-10-07 16:14:58 +03:00
func ( mc * MessageConverter ) PollVoteToWhatsApp (
ctx context . Context ,
client * whatsmeow . Client ,
content * event . PollResponseEventContent ,
pollMsg * database . Message ,
) ( * waE2E . Message , error ) {
parsedMsgID , err := waid . ParseMessageID ( pollMsg . ID )
if err != nil {
zerolog . Ctx ( ctx ) . Err ( err ) . Msg ( "Failed to parse message ID" )
return nil , fmt . Errorf ( "failed to parse message ID" )
}
2025-04-08 21:00:38 +03:00
pollMsgInfo := MessageIDToInfo ( client , parsedMsgID )
pollMsgInfo . Type = "poll"
2024-10-07 16:14:58 +03:00
optionHashes := make ( [ ] [ ] byte , 0 , len ( content . Response . Answers ) )
if pollMsg . Metadata . ( * waid . MessageMetadata ) . IsMatrixPoll {
mappedAnswers , err := mc . DB . PollOption . GetHashes ( ctx , pollMsg . MXID , content . Response . Answers )
if err != nil {
zerolog . Ctx ( ctx ) . Err ( err ) . Msg ( "Failed to get poll option hashes from database" )
return nil , fmt . Errorf ( "failed to get poll option hashes" )
}
for _ , selection := range content . Response . Answers {
hash , ok := mappedAnswers [ selection ]
if ok {
optionHashes = append ( optionHashes , hash [ : ] )
} else {
zerolog . Ctx ( ctx ) . Warn ( ) . Str ( "option" , selection ) . Msg ( "Didn't find hash for selected option" )
}
}
} else {
for _ , selection := range content . Response . Answers {
hash , _ := hex . DecodeString ( selection )
if len ( hash ) == 32 {
optionHashes = append ( optionHashes , hash )
}
}
}
2025-05-14 14:40:03 +03:00
pollUpdate , err := client . EncryptPollVote ( ctx , pollMsgInfo , & waE2E . PollVoteMessage {
2024-10-07 16:14:58 +03:00
SelectedOptions : optionHashes ,
} )
return & waE2E . Message { PollUpdateMessage : pollUpdate } , err
2024-10-07 14:21:56 +03:00
}
2025-04-08 21:00:38 +03:00
func MessageIDToInfo ( client * whatsmeow . Client , parsedMsgID * waid . ParsedMessageID ) * types . MessageInfo {
return & types . MessageInfo {
MessageSource : types . MessageSource {
Chat : parsedMsgID . Chat ,
Sender : parsedMsgID . Sender ,
IsFromMe : parsedMsgID . Sender . User == client . Store . GetLID ( ) . User || parsedMsgID . Sender . User == client . Store . GetJID ( ) . User ,
IsGroup : parsedMsgID . Chat . Server == types . GroupServer ,
} ,
ID : parsedMsgID . ID ,
}
}