Watch
1
0
Fork
You've already forked mautrix-whatsapp
0
mirror of https://github.com/mautrix/whatsapp.git synced 2026-08-24 04:54:56 -04:00
mautrix-whatsapp/pkg/connector/matrixrtc_reactions.go
2026-07-30 05:14:00 +02:00

254 lines
8.6 KiB
Go

package connector
import (
"context"
"strings"
"github.com/purpshell/meowcaller"
"github.com/rs/zerolog"
"go.mau.fi/whatsmeow/types"
"maunium.net/go/mautrix/event"
"go.mau.fi/mautrix-whatsapp/pkg/connector/voip"
"go.mau.fi/mautrix-whatsapp/pkg/connector/wadb"
)
func isMatrixRTCCallControlEvent(evt voip.MatrixRTCEvent) bool {
switch evt.Kind {
case voip.MatrixRTCEventKindCallReaction, voip.MatrixRTCEventKindHandRaise, voip.MatrixRTCEventKindRedaction:
return true
default:
return false
}
}
func matrixRTCControlEventMatchesCall(evt voip.MatrixRTCEvent, call *wadb.MatrixRTCCall) bool {
if call == nil || !matrixRTCEventSenderMatchesPublisher(evt.Sender.String(), call.SelectedPublisherID) {
return false
}
switch evt.Kind {
case voip.MatrixRTCEventKindCallReaction, voip.MatrixRTCEventKindHandRaise:
return evt.RelatesToEventID != "" && evt.RelatesToEventID == call.SelectedMembershipEventID
case voip.MatrixRTCEventKindRedaction:
return evt.Redacts != "" && evt.Redacts == call.SelectedHandRaiseEventID
default:
return false
}
}
func matrixRTCEventSenderMatchesPublisher(sender, publisherID string) bool {
if sender == "" || publisherID == "" {
return false
}
return publisherID == sender || strings.HasPrefix(publisherID, sender+":")
}
func (wa *WhatsAppConnector) handleMatrixRTCCallControlEvent(
ctx context.Context,
evt voip.MatrixRTCEvent,
activeCalls []*wadb.MatrixRTCCall,
log zerolog.Logger,
) {
for _, activeCall := range activeCalls {
if !matrixRTCControlEventMatchesCall(evt, activeCall) {
continue
}
login, err := wa.Bridge.GetExistingUserLoginByID(ctx, activeCall.UserLoginID)
if err != nil {
log.Err(err).Str("wa_call_id", activeCall.WACallID).Msg("Failed to resolve login for MatrixRTC call control")
continue
}
if login == nil {
continue
}
client, ok := login.Client.(*WhatsAppClient)
if !ok || client == nil || client.VOIP == nil {
continue
}
switch evt.Kind {
case voip.MatrixRTCEventKindCallReaction:
if evt.RelationType != event.RelReference {
continue
}
emoji, supported := voip.NormalizeWhatsAppCallReaction(evt.ReactionEmoji)
if !supported {
log.Debug().Str("emoji", evt.ReactionEmoji).Msg("Ignoring unsupported MatrixRTC call reaction")
continue
}
if err = client.VOIP.SendReaction(activeCall.WACallID, emoji); err != nil {
log.Warn().Err(err).Str("wa_call_id", activeCall.WACallID).Str("emoji", emoji).Msg("Failed to send MatrixRTC reaction to WhatsApp")
}
case voip.MatrixRTCEventKindHandRaise:
if evt.RelationType != event.RelAnnotation || evt.RelationKey != "🖐️" || evt.EventID == "" {
continue
}
if activeCall.SelectedHandRaiseEventID != "" {
continue
}
if err = client.VOIP.SetHandRaised(activeCall.WACallID, true); err != nil {
log.Warn().Err(err).Str("wa_call_id", activeCall.WACallID).Msg("Failed to raise hand in WhatsApp call")
continue
}
activeCall.SelectedHandRaiseEventID = evt.EventID
if err = wa.DB.MatrixRTCCall.Put(ctx, activeCall); err != nil {
log.Err(err).Str("wa_call_id", activeCall.WACallID).Msg("Failed to persist MatrixRTC hand raise")
_ = client.VOIP.SetHandRaised(activeCall.WACallID, false)
}
case voip.MatrixRTCEventKindRedaction:
if err = client.VOIP.SetHandRaised(activeCall.WACallID, false); err != nil {
log.Warn().Err(err).Str("wa_call_id", activeCall.WACallID).Msg("Failed to lower hand in WhatsApp call")
continue
}
activeCall.SelectedHandRaiseEventID = ""
if err = wa.DB.MatrixRTCCall.Put(ctx, activeCall); err != nil {
log.Err(err).Str("wa_call_id", activeCall.WACallID).Msg("Failed to persist MatrixRTC hand lowering")
_ = client.VOIP.SetHandRaised(activeCall.WACallID, true)
}
}
}
}
func (wa *WhatsAppClient) handleWhatsAppCallReaction(ctx context.Context, callID string, reaction meowcaller.CallReaction) {
if reaction.Removed || wa.isOwnWhatsAppCallParticipant(reaction.Sender) {
return
}
emoji, supported := voip.NormalizeWhatsAppCallReaction(reaction.Emoji)
if !supported {
return
}
call, err := wa.Main.DB.MatrixRTCCall.Get(ctx, wa.UserLogin.ID, callID)
if err != nil || call == nil || !call.EndedTS.IsZero() || call.BridgeMembershipEventID == "" {
if err != nil {
wa.UserLogin.Log.Err(err).Str("call_id", callID).Msg("Failed to load MatrixRTC call for WhatsApp reaction")
}
return
}
intent := wa.matrixRTCIntentForMXID(ctx, call.MatrixParticipantMXID)
_, err = intent.SendMessage(ctx, call.RoomID, voip.ElementCallReactionEventType(), &event.Content{
Raw: voip.BuildElementCallReactionContent(call.BridgeMembershipEventID, emoji),
}, nil)
if err != nil {
wa.UserLogin.Log.Warn().Err(err).Str("call_id", callID).Str("emoji", emoji).Msg("Failed to bridge WhatsApp call reaction to MatrixRTC")
}
}
func (wa *WhatsAppClient) handleWhatsAppHandRaise(ctx context.Context, callID string, state meowcaller.HandRaiseState) {
if wa.isOwnWhatsAppCallParticipant(state.Participant) {
return
}
wa.voipHandBridgeLock.Lock()
defer wa.voipHandBridgeLock.Unlock()
raised, changed := wa.updateWhatsAppRemoteHandRaise(callID, state)
if !changed {
return
}
rollback := func() {
state.Raised = !state.Raised
wa.updateWhatsAppRemoteHandRaise(callID, state)
}
call, err := wa.Main.DB.MatrixRTCCall.Get(ctx, wa.UserLogin.ID, callID)
if err != nil || call == nil || !call.EndedTS.IsZero() || call.BridgeMembershipEventID == "" {
rollback()
if err != nil {
wa.UserLogin.Log.Err(err).Str("call_id", callID).Msg("Failed to load MatrixRTC call for WhatsApp hand state")
}
return
}
intent := wa.matrixRTCIntentForMXID(ctx, call.MatrixParticipantMXID)
if raised {
if call.BridgeHandRaiseEventID != "" {
return
}
resp, sendErr := intent.SendMessage(ctx, call.RoomID, event.EventReaction, &event.Content{
Raw: voip.BuildElementCallHandRaiseContent(call.BridgeMembershipEventID),
}, nil)
if sendErr != nil {
rollback()
wa.UserLogin.Log.Warn().Err(sendErr).Str("call_id", callID).Msg("Failed to bridge WhatsApp hand raise to MatrixRTC")
return
}
if resp != nil {
call.BridgeHandRaiseEventID = resp.EventID
}
if call.BridgeHandRaiseEventID == "" {
rollback()
return
}
if err = wa.Main.DB.MatrixRTCCall.Put(ctx, call); err != nil {
rollback()
_, _ = intent.SendMessage(ctx, call.RoomID, event.EventRedaction, &event.Content{
Parsed: &event.RedactionEventContent{Redacts: call.BridgeHandRaiseEventID},
}, nil)
wa.UserLogin.Log.Err(err).Str("call_id", callID).Msg("Failed to persist bridged WhatsApp hand raise")
}
return
} else {
if call.BridgeHandRaiseEventID == "" {
return
}
handRaiseEventID := call.BridgeHandRaiseEventID
call.BridgeHandRaiseEventID = ""
if err = wa.Main.DB.MatrixRTCCall.Put(ctx, call); err != nil {
rollback()
wa.UserLogin.Log.Err(err).Str("call_id", callID).Msg("Failed to persist bridged WhatsApp hand lowering")
return
}
_, sendErr := intent.SendMessage(ctx, call.RoomID, event.EventRedaction, &event.Content{
Parsed: &event.RedactionEventContent{Redacts: handRaiseEventID},
}, nil)
if sendErr != nil {
rollback()
call.BridgeHandRaiseEventID = handRaiseEventID
_ = wa.Main.DB.MatrixRTCCall.Put(ctx, call)
wa.UserLogin.Log.Warn().Err(sendErr).Str("call_id", callID).Msg("Failed to bridge WhatsApp hand lowering to MatrixRTC")
return
}
}
}
func (wa *WhatsAppClient) updateWhatsAppRemoteHandRaise(callID string, state meowcaller.HandRaiseState) (raised, changed bool) {
if callID == "" || state.Participant.IsEmpty() {
return false, false
}
participant := state.Participant.ToNonAD()
wa.voipHandRaiseLock.Lock()
defer wa.voipHandRaiseLock.Unlock()
if wa.voipHandRaises == nil {
wa.voipHandRaises = make(map[string]map[types.JID]bool)
}
hands := wa.voipHandRaises[callID]
if hands == nil {
hands = make(map[types.JID]bool)
wa.voipHandRaises[callID] = hands
}
wasRaised := len(hands) > 0
if state.Raised {
hands[participant] = true
} else {
delete(hands, participant)
}
raised = len(hands) > 0
if !raised {
delete(wa.voipHandRaises, callID)
}
return raised, wasRaised != raised
}
func (wa *WhatsAppClient) clearWhatsAppRemoteHandRaises(callID string) {
wa.voipHandBridgeLock.Lock()
defer wa.voipHandBridgeLock.Unlock()
wa.voipHandRaiseLock.Lock()
delete(wa.voipHandRaises, callID)
wa.voipHandRaiseLock.Unlock()
}
func (wa *WhatsAppClient) isOwnWhatsAppCallParticipant(participant types.JID) bool {
if participant.IsEmpty() || wa == nil || wa.Client == nil || wa.Client.Store == nil {
return false
}
device := wa.Client.Store
participant = participant.ToNonAD()
return participant == device.GetLID().ToNonAD() ||
participant == device.GetJID().ToNonAD()
}