2024-09-27 14:44:15 +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/>.
2024-08-13 14:11:10 +03:00
package connector
import (
2024-09-26 12:37:21 +03:00
"context"
2024-08-14 20:31:49 +03:00
"fmt"
2024-09-26 12:37:21 +03:00
"strconv"
2024-09-25 17:40:10 +03:00
"strings"
2024-09-04 05:46:27 +03:00
"time"
2024-08-13 14:11:10 +03:00
2024-09-26 13:23:23 +03:00
"github.com/rs/zerolog"
2025-01-05 23:12:34 +02:00
"go.mau.fi/util/ptr"
2026-01-26 20:38:36 +02:00
"go.mau.fi/whatsmeow"
2024-09-10 15:56:51 +03:00
"go.mau.fi/whatsmeow/appstate"
2025-07-29 20:22:17 +05:30
"go.mau.fi/whatsmeow/proto/waE2E"
2026-01-20 13:14:48 +02:00
"go.mau.fi/whatsmeow/store"
2024-08-13 14:11:10 +03:00
"go.mau.fi/whatsmeow/types"
"go.mau.fi/whatsmeow/types/events"
"maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/bridgev2/networkid"
"maunium.net/go/mautrix/bridgev2/simplevent"
2025-03-16 15:41:38 +02:00
"maunium.net/go/mautrix/bridgev2/status"
2024-09-26 12:37:21 +03:00
"maunium.net/go/mautrix/event"
2024-08-13 14:11:10 +03:00
2024-10-15 17:59:57 +03:00
"go.mau.fi/mautrix-whatsapp/pkg/waid"
2024-08-13 14:11:10 +03:00
)
const (
2024-09-10 15:56:51 +03:00
WANotLoggedIn status . BridgeStateErrorCode = "wa-not-logged-in"
2024-09-06 17:41:26 +03:00
WALoggedOut status . BridgeStateErrorCode = "wa-logged-out"
WAMainDeviceGone status . BridgeStateErrorCode = "wa-main-device-gone"
WAUnknownLogout status . BridgeStateErrorCode = "wa-unknown-logout"
WANotConnected status . BridgeStateErrorCode = "wa-not-connected"
WAConnecting status . BridgeStateErrorCode = "wa-connecting"
WAKeepaliveTimeout status . BridgeStateErrorCode = "wa-keepalive-timeout"
WAPhoneOffline status . BridgeStateErrorCode = "wa-phone-offline"
WAConnectionFailed status . BridgeStateErrorCode = "wa-connection-failed"
WADisconnected status . BridgeStateErrorCode = "wa-transient-disconnect"
WAStreamReplaced status . BridgeStateErrorCode = "wa-stream-replaced"
WAStreamError status . BridgeStateErrorCode = "wa-stream-error"
WAClientOutdated status . BridgeStateErrorCode = "wa-client-outdated"
WATemporaryBan status . BridgeStateErrorCode = "wa-temporary-ban"
2024-08-13 14:11:10 +03:00
)
2024-09-06 17:41:26 +03:00
func init ( ) {
status . BridgeStateHumanErrors . Update ( status . BridgeStateErrorMap {
WALoggedOut : "You were logged out from another device. Relogin to continue using the bridge." ,
2024-09-11 20:34:29 +03:00
WANotLoggedIn : "You're not logged into WhatsApp. Relogin to continue using the bridge." ,
2024-09-06 17:41:26 +03:00
WAMainDeviceGone : "Your phone was logged out from WhatsApp. Relogin to continue using the bridge." ,
WAUnknownLogout : "You were logged out for an unknown reason. Relogin to continue using the bridge." ,
WANotConnected : "You're not connected to WhatsApp" ,
WAConnecting : "Reconnecting to WhatsApp..." ,
WAKeepaliveTimeout : "The WhatsApp web servers are not responding. The bridge will try to reconnect." ,
WAPhoneOffline : "Your phone hasn't been seen in over 12 days. The bridge is currently connected, but will get disconnected if you don't open the app soon." ,
WAConnectionFailed : "Connecting to the WhatsApp web servers failed." ,
WADisconnected : "Disconnected from WhatsApp. Trying to reconnect." ,
WAClientOutdated : "Connect failure: 405 client outdated. Bridge must be updated." ,
WAStreamReplaced : "Stream replaced: the bridge was started in another location." ,
} )
}
2025-06-17 22:24:37 +05:30
func ( wa * WhatsAppClient ) handleWAEvent ( rawEvt any ) ( success bool ) {
2024-08-13 14:11:10 +03:00
log := wa . UserLogin . Log
2025-05-14 14:40:03 +03:00
ctx := log . WithContext ( wa . Main . Bridge . BackgroundCtx )
2024-08-13 14:11:10 +03:00
2025-07-10 18:03:16 +03:00
success = true
2024-08-13 14:11:10 +03:00
switch evt := rawEvt . ( type ) {
2024-08-15 18:14:49 +03:00
case * events . Message :
2025-07-10 18:03:16 +03:00
success = wa . handleWAMessage ( ctx , evt )
2024-08-13 14:11:10 +03:00
case * events . Receipt :
2025-07-10 18:03:16 +03:00
success = wa . handleWAReceipt ( ctx , evt )
2024-09-04 05:46:27 +03:00
case * events . ChatPresence :
2025-07-03 21:44:15 +03:00
wa . handleWAChatPresence ( ctx , evt )
2024-09-10 18:15:38 +03:00
case * events . UndecryptableMessage :
2025-08-11 17:17:32 +03:00
success = wa . handleWAUndecryptableMessage ( ctx , evt )
2024-09-10 18:15:38 +03:00
case * events . CallOffer :
2025-09-19 15:48:58 +03:00
success = wa . handleWACallStart ( ctx , evt . GroupJID , evt . CallCreator , evt . CallCreatorAlt , evt . CallID , "" , evt . Timestamp )
2024-09-10 18:15:38 +03:00
case * events . CallOfferNotice :
2025-09-19 15:48:58 +03:00
success = wa . handleWACallStart ( ctx , evt . GroupJID , evt . CallCreator , evt . CallCreatorAlt , evt . CallID , evt . Type , evt . Timestamp )
2024-09-10 18:15:38 +03:00
case * events . CallTerminate , * events . CallRelayLatency , * events . CallAccept , * events . UnknownCallEvent :
// ignore
case * events . IdentityChange :
2025-07-03 21:44:15 +03:00
wa . handleWAIdentityChange ( ctx , evt )
2024-09-10 18:15:38 +03:00
case * events . MarkChatAsRead :
2025-07-10 18:03:16 +03:00
success = wa . handleWAMarkChatAsRead ( ctx , evt )
2024-09-10 18:15:38 +03:00
case * events . DeleteForMe :
2026-01-22 15:56:20 +00:00
success = wa . handleWADeleteForMe ( ctx , evt )
2024-09-10 18:15:38 +03:00
case * events . DeleteChat :
2026-01-22 15:56:20 +00:00
success = wa . handleWADeleteChat ( ctx , evt )
2024-09-10 18:15:38 +03:00
case * events . Mute :
2025-07-10 18:03:16 +03:00
success = wa . handleWAMute ( evt )
2024-09-10 18:15:38 +03:00
case * events . Archive :
2025-07-10 18:03:16 +03:00
success = wa . handleWAArchive ( evt )
2024-09-10 18:15:38 +03:00
case * events . Pin :
2025-07-10 18:03:16 +03:00
success = wa . handleWAPin ( evt )
2024-09-10 18:15:38 +03:00
case * events . HistorySync :
2026-03-25 20:15:12 +02:00
wa . UserLogin . Log . Warn ( ) . Msg ( "Unexpected history sync event received" )
2024-09-10 18:15:38 +03:00
case * events . MediaRetry :
2024-09-27 15:20:33 +03:00
wa . phoneSeen ( evt . Timestamp )
2025-07-10 18:03:16 +03:00
success = wa . UserLogin . QueueRemoteEvent ( & WAMediaRetry { MediaRetry : evt , wa : wa } ) . Success
2024-09-10 18:15:38 +03:00
case * events . GroupInfo :
2025-07-10 18:03:16 +03:00
success = wa . handleWAGroupInfoChange ( ctx , evt )
2024-09-10 18:15:38 +03:00
case * events . JoinedGroup :
2025-07-10 18:03:16 +03:00
success = wa . handleWAJoinedGroup ( ctx , evt )
2024-09-10 18:15:38 +03:00
case * events . NewsletterJoin :
2025-07-10 18:03:16 +03:00
success = wa . handleWANewsletterJoin ( ctx , evt )
2024-09-10 18:15:38 +03:00
case * events . NewsletterLeave :
2025-07-10 18:03:16 +03:00
success = wa . handleWANewsletterLeave ( evt )
2024-09-10 18:15:38 +03:00
case * events . Picture :
2025-07-10 18:03:16 +03:00
success = wa . handleWAPictureUpdate ( ctx , evt )
2024-09-04 05:46:27 +03:00
2024-09-10 15:56:51 +03:00
case * events . AppStateSyncComplete :
2026-01-26 20:38:36 +02:00
wa . handleWAAppStateSyncComplete ( ctx , evt )
case * events . AppStateSyncError :
wa . handleWAAppStateSyncError ( ctx , evt )
2024-09-10 18:15:38 +03:00
case * events . AppState :
// Intentionally ignored
2024-09-10 15:56:51 +03:00
case * events . PushNameSetting :
// Send presence available when connecting and when the pushname is changed.
// This makes sure that outgoing messages always have the right pushname.
2025-10-27 16:20:47 +02:00
err := wa . updatePresence ( ctx , types . PresenceUnavailable )
2024-09-10 15:56:51 +03:00
if err != nil {
log . Warn ( ) . Err ( err ) . Msg ( "Failed to send presence after push name update" )
}
2025-05-14 14:40:03 +03:00
_ , _ , err = wa . GetStore ( ) . Contacts . PutPushName ( ctx , wa . JID . ToNonAD ( ) , evt . Action . GetName ( ) )
2024-09-10 15:56:51 +03:00
if err != nil {
log . Err ( err ) . Msg ( "Failed to update push name in store" )
}
2025-10-28 17:08:13 +02:00
_ , _ , err = wa . GetStore ( ) . Contacts . PutPushName ( ctx , wa . GetStore ( ) . GetLID ( ) . ToNonAD ( ) , evt . Action . GetName ( ) )
if err != nil {
log . Err ( err ) . Msg ( "Failed to update push name in store" )
}
2025-01-05 23:12:34 +02:00
go wa . syncGhost ( wa . JID . ToNonAD ( ) , "push name setting" , nil )
2024-09-10 18:15:38 +03:00
case * events . Contact :
2025-01-05 23:12:34 +02:00
go wa . syncGhost ( evt . JID , "contact event" , nil )
2024-09-10 18:15:38 +03:00
case * events . PushName :
2025-01-05 23:12:34 +02:00
go wa . syncGhost ( evt . JID , "push name event" , nil )
2024-09-10 18:15:38 +03:00
case * events . BusinessName :
2025-01-05 23:12:34 +02:00
go wa . syncGhost ( evt . JID , "business name event" , nil )
2024-09-10 18:15:38 +03:00
case * events . Connected :
log . Debug ( ) . Msg ( "Connected to WhatsApp socket" )
wa . UserLogin . BridgeState . Send ( status . BridgeState { StateEvent : status . StateConnected } )
2025-03-06 15:53:29 +02:00
if len ( wa . GetStore ( ) . PushName ) > 0 {
2024-09-10 18:15:38 +03:00
go func ( ) {
2025-10-27 16:20:47 +02:00
err := wa . updatePresence ( ctx , types . PresenceUnavailable )
2024-09-10 18:15:38 +03:00
if err != nil {
log . Warn ( ) . Err ( err ) . Msg ( "Failed to send initial presence after connecting" )
}
} ( )
2025-10-27 16:20:47 +02:00
go wa . syncRemoteProfile ( ctx , nil )
2024-09-10 18:15:38 +03:00
}
2026-01-20 13:14:48 +02:00
wa . MC . OnConnect ( store . GetWAVersion ( ) [ 2 ] , wa . Device . Platform )
2024-09-10 18:15:38 +03:00
case * events . OfflineSyncPreview :
log . Info ( ) .
Int ( "message_count" , evt . Messages ) .
Int ( "receipt_count" , evt . Receipts ) .
Int ( "notification_count" , evt . Notifications ) .
Int ( "app_data_change_count" , evt . AppDataChanges ) .
Msg ( "Server sent number of events that were missed during downtime" )
case * events . OfflineSyncCompleted :
if ! wa . PhoneRecentlySeen ( true ) {
log . Info ( ) .
2025-07-30 13:27:16 +03:00
Int ( "evt_count" , evt . Count ) .
2024-09-16 15:38:39 +03:00
Time ( "phone_last_seen" , wa . UserLogin . Metadata . ( * waid . UserLoginMetadata ) . PhoneLastSeen . Time ) .
2025-04-15 15:01:14 +03:00
Msg ( "Offline sync completed, but phone last seen date is still old" )
2024-09-10 18:15:38 +03:00
} else {
2025-07-30 13:27:16 +03:00
log . Info ( ) .
Int ( "evt_count" , evt . Count ) .
Msg ( "Offline sync completed" )
2024-09-10 18:15:38 +03:00
}
2025-04-15 15:01:14 +03:00
wa . UserLogin . BridgeState . Send ( status . BridgeState { StateEvent : status . StateConnected } )
2025-01-15 15:13:42 +02:00
wa . notifyOfflineSyncWaiter ( nil )
2024-09-10 18:15:38 +03:00
case * events . LoggedOut :
wa . handleWALogout ( evt . Reason , evt . OnConnect )
2025-01-15 15:13:42 +02:00
wa . notifyOfflineSyncWaiter ( fmt . Errorf ( "logged out: %s" , evt . Reason ) )
2024-08-13 14:11:10 +03:00
case * events . Disconnected :
2024-09-06 17:41:26 +03:00
// Don't send the normal transient disconnect state if we're already in a different transient disconnect state.
// TODO remove this if/when the phone offline state is moved to a sub-state of CONNECTED
if wa . UserLogin . BridgeState . GetPrev ( ) . Error != WAPhoneOffline && wa . PhoneRecentlySeen ( false ) {
wa . UserLogin . BridgeState . Send ( status . BridgeState { StateEvent : status . StateTransientDisconnect , Error : WADisconnected } )
2024-08-13 14:11:10 +03:00
}
2025-01-15 15:13:42 +02:00
wa . notifyOfflineSyncWaiter ( fmt . Errorf ( "disconnected" ) )
2024-09-06 17:41:26 +03:00
case * events . StreamError :
var message string
if evt . Code != "" {
message = fmt . Sprintf ( "Unknown stream error with code %s" , evt . Code )
} else if children := evt . Raw . GetChildren ( ) ; len ( children ) > 0 {
message = fmt . Sprintf ( "Unknown stream error (contains %s node)" , children [ 0 ] . Tag )
} else {
message = "Unknown stream error"
2024-08-13 14:11:10 +03:00
}
2024-09-06 17:41:26 +03:00
wa . UserLogin . BridgeState . Send ( status . BridgeState {
2024-08-13 14:11:10 +03:00
StateEvent : status . StateUnknownError ,
2024-09-06 17:41:26 +03:00
Error : WAStreamError ,
Message : message ,
} )
2025-01-15 15:13:42 +02:00
wa . notifyOfflineSyncWaiter ( fmt . Errorf ( "stream error: %s" , message ) )
2024-09-06 17:41:26 +03:00
case * events . StreamReplaced :
wa . UserLogin . BridgeState . Send ( status . BridgeState { StateEvent : status . StateUnknownError , Error : WAStreamReplaced } )
2025-01-15 15:13:42 +02:00
wa . notifyOfflineSyncWaiter ( fmt . Errorf ( "stream replaced" ) )
2024-09-10 18:15:38 +03:00
case * events . KeepAliveTimeout :
wa . UserLogin . BridgeState . Send ( status . BridgeState { StateEvent : status . StateTransientDisconnect , Error : WAKeepaliveTimeout } )
case * events . KeepAliveRestored :
log . Info ( ) . Msg ( "Keepalive restored after timeouts, sending connected event" )
wa . UserLogin . BridgeState . Send ( status . BridgeState { StateEvent : status . StateConnected } )
2024-09-06 17:41:26 +03:00
case * events . ConnectFailure :
wa . UserLogin . BridgeState . Send ( status . BridgeState {
StateEvent : status . StateUnknownError ,
Error : status . BridgeStateErrorCode ( fmt . Sprintf ( "wa-connect-failure-%d" , evt . Reason ) ) ,
Message : fmt . Sprintf ( "Unknown connection failure: %s (%s)" , evt . Reason , evt . Message ) ,
} )
2025-01-15 15:13:42 +02:00
wa . notifyOfflineSyncWaiter ( fmt . Errorf ( "connection failure: %s (%s)" , evt . Reason , evt . Message ) )
2024-09-06 17:41:26 +03:00
case * events . ClientOutdated :
wa . UserLogin . Log . Error ( ) . Msg ( "Got a client outdated connect failure. The bridge is likely out of date, please update immediately." )
wa . UserLogin . BridgeState . Send ( status . BridgeState { StateEvent : status . StateUnknownError , Error : WAClientOutdated } )
2025-01-15 15:13:42 +02:00
wa . notifyOfflineSyncWaiter ( fmt . Errorf ( "client outdated" ) )
2024-09-06 17:41:26 +03:00
case * events . TemporaryBan :
wa . UserLogin . BridgeState . Send ( status . BridgeState {
StateEvent : status . StateBadCredentials ,
Error : WATemporaryBan ,
Message : evt . String ( ) ,
} )
2025-01-15 15:13:42 +02:00
wa . notifyOfflineSyncWaiter ( fmt . Errorf ( "temporary ban: %s" , evt . String ( ) ) )
2024-08-13 14:11:10 +03:00
default :
log . Debug ( ) . Type ( "event_type" , rawEvt ) . Msg ( "Unhandled WhatsApp event" )
}
2025-07-10 18:03:16 +03:00
return
2024-08-13 14:11:10 +03:00
}
2024-09-10 18:15:38 +03:00
2025-11-11 18:22:25 +02:00
func ( wa * WhatsAppClient ) rerouteWAMessage ( ctx context . Context , evtType string , info * types . MessageSource , msgID any ) {
2026-01-07 13:38:22 +02:00
if ( info . Chat . Server == types . HiddenUserServer || info . Chat . Server == types . BroadcastServer ) &&
info . Sender . Server == types . HiddenUserServer && info . SenderAlt . IsEmpty ( ) {
2025-11-11 18:22:25 +02:00
info . SenderAlt , _ = wa . GetStore ( ) . LIDs . GetPNForLID ( ctx , info . Sender )
}
2026-01-07 20:54:06 +02:00
if info . Chat . Server == types . HiddenUserServer && info . IsFromMe && info . RecipientAlt . IsEmpty ( ) {
info . RecipientAlt , _ = wa . GetStore ( ) . LIDs . GetPNForLID ( ctx , info . Chat )
}
2025-08-11 17:17:32 +03:00
if info . Chat . Server == types . HiddenUserServer && info . Sender . ToNonAD ( ) == info . Chat && info . SenderAlt . Server == types . DefaultUserServer {
2025-06-10 16:40:41 +03:00
wa . UserLogin . Log . Debug ( ) .
2025-08-11 17:17:32 +03:00
Stringer ( "lid" , info . Sender ) .
Stringer ( "pn" , info . SenderAlt ) .
2025-11-11 18:22:25 +02:00
Any ( "message_id" , msgID ) .
Str ( "evt_type" , evtType ) .
2025-06-10 16:40:41 +03:00
Msg ( "Forced LID DM sender to phone number in incoming message" )
2025-08-11 17:17:32 +03:00
info . Sender , info . SenderAlt = info . SenderAlt , info . Sender
info . Chat = info . Sender . ToNonAD ( )
} else if info . Chat . Server == types . HiddenUserServer && info . IsFromMe && info . RecipientAlt . Server == types . DefaultUserServer {
2025-06-14 12:36:25 +03:00
wa . UserLogin . Log . Debug ( ) .
2025-08-11 17:17:32 +03:00
Stringer ( "lid" , info . Chat ) .
Stringer ( "pn" , info . RecipientAlt ) .
2025-11-11 18:22:25 +02:00
Any ( "message_id" , msgID ) .
Str ( "evt_type" , evtType ) .
2025-06-14 12:36:25 +03:00
Msg ( "Forced LID DM sender to phone number in own message sent from another device" )
2025-08-11 17:17:32 +03:00
info . Chat = info . RecipientAlt . ToNonAD ( )
2025-11-11 18:22:25 +02:00
if info . Sender . Server == types . HiddenUserServer {
info . Sender , info . SenderAlt = info . SenderAlt , info . Sender
if info . Sender . IsEmpty ( ) {
2025-11-11 19:13:33 +02:00
info . Sender = wa . GetStore ( ) . GetJID ( )
2025-11-11 18:22:25 +02:00
info . Sender . Device = info . SenderAlt . Device
}
}
2026-01-07 13:38:22 +02:00
} else if info . Chat . Server == types . BroadcastServer && info . Sender . Server == types . HiddenUserServer && info . SenderAlt . Server == types . DefaultUserServer {
wa . UserLogin . Log . Debug ( ) .
Stringer ( "lid" , info . Sender ) .
Stringer ( "pn" , info . SenderAlt ) .
Stringer ( "chat" , info . Chat ) .
Any ( "message_id" , msgID ) .
Str ( "evt_type" , evtType ) .
Msg ( "Forced LID broadcast list sender to phone number in incoming message" )
info . Sender , info . SenderAlt = info . SenderAlt , info . Sender
2025-08-11 17:17:32 +03:00
} else if info . Sender . Server == types . BotServer && info . Chat . Server == types . HiddenUserServer {
2025-10-20 22:30:04 +03:00
chatPN , err := wa . GetStore ( ) . LIDs . GetPNForLID ( ctx , info . Chat )
2025-07-22 23:10:34 +03:00
if err != nil {
wa . UserLogin . Log . Err ( err ) .
2025-11-11 18:22:25 +02:00
Any ( "message_id" , msgID ) .
2025-08-11 17:17:32 +03:00
Stringer ( "lid" , info . Chat ) .
2025-11-11 18:22:25 +02:00
Str ( "evt_type" , evtType ) .
2025-07-22 23:10:34 +03:00
Msg ( "Failed to get phone number of DM for incoming bot message" )
} else if ! chatPN . IsEmpty ( ) {
wa . UserLogin . Log . Debug ( ) .
2025-08-11 17:17:32 +03:00
Stringer ( "lid" , info . Chat ) .
2025-07-22 23:10:34 +03:00
Stringer ( "pn" , chatPN ) .
2025-11-11 18:22:25 +02:00
Any ( "message_id" , msgID ) .
Str ( "evt_type" , evtType ) .
2025-07-22 23:10:34 +03:00
Msg ( "Forced LID chat to phone number in bot message" )
2025-08-11 17:17:32 +03:00
info . Chat = chatPN
2025-07-22 23:10:34 +03:00
}
2025-06-10 16:40:41 +03:00
}
2025-08-11 17:17:32 +03:00
}
func ( wa * WhatsAppClient ) handleWAMessage ( ctx context . Context , evt * events . Message ) ( success bool ) {
success = true
2025-11-11 18:22:25 +02:00
wa . rerouteWAMessage ( ctx , "message" , & evt . Info . MessageSource , evt . Info . ID )
2024-09-10 18:15:38 +03:00
wa . UserLogin . Log . Trace ( ) .
Any ( "info" , evt . Info ) .
Any ( "payload" , evt . Message ) .
Msg ( "Received WhatsApp message" )
2024-10-16 16:44:57 +03:00
if evt . Info . Chat == types . StatusBroadcastJID && ! wa . Main . Config . EnableStatusBroadcast {
return
}
2025-06-03 22:23:00 +05:30
if evt . Info . IsFromMe &&
evt . Message . GetProtocolMessage ( ) . GetHistorySyncNotification ( ) != nil &&
wa . Main . Bridge . Config . Backfill . Enabled &&
wa . Client . ManualHistorySyncDownload {
wa . saveWAHistorySyncNotification ( ctx , evt . Message . ProtocolMessage . HistorySyncNotification )
}
2025-07-29 20:22:17 +05:30
messageAssoc := evt . Message . GetMessageContextInfo ( ) . GetMessageAssociation ( )
if assocType := messageAssoc . GetAssociationType ( ) ; assocType == waE2E . MessageAssociation_HD_IMAGE_DUAL_UPLOAD || assocType == waE2E . MessageAssociation_HD_VIDEO_DUAL_UPLOAD {
parentKey := messageAssoc . GetParentMessageKey ( )
associatedMessage := evt . Message . GetAssociatedChildMessage ( ) . GetMessage ( )
wa . UserLogin . Log . Debug ( ) .
Str ( "message_id" , evt . Info . ID ) .
Str ( "parent_id" , parentKey . GetID ( ) ) .
Stringer ( "assoc_type" , assocType ) .
Msg ( "Received HD replacement message, converting to edit" )
protocolMsg := & waE2E . ProtocolMessage {
Type : waE2E . ProtocolMessage_MESSAGE_EDIT . Enum ( ) ,
Key : parentKey ,
EditedMessage : associatedMessage ,
}
evt . Message = & waE2E . Message {
ProtocolMessage : protocolMsg ,
}
2025-10-28 15:17:51 +02:00
} else if assocType == waE2E . MessageAssociation_MOTION_PHOTO {
2025-10-29 14:53:40 +02:00
//evt.Message = evt.Message.GetAssociatedChildMessage().GetMessage()
wa . UserLogin . Log . Debug ( ) .
Str ( "message_id" , evt . Info . ID ) .
Str ( "parent_id" , messageAssoc . GetParentMessageKey ( ) . GetID ( ) ) .
Msg ( "Ignoring motion photo update" )
return
2025-07-29 20:22:17 +05:30
}
2024-09-10 19:41:30 +03:00
parsedMessageType := getMessageType ( evt . Message )
2024-09-25 17:40:10 +03:00
if parsedMessageType == "ignore" || strings . HasPrefix ( parsedMessageType , "unknown_protocol_" ) {
2024-09-10 19:41:30 +03:00
return
}
2025-04-08 21:00:38 +03:00
if encReact := evt . Message . GetEncReactionMessage ( ) ; encReact != nil {
2025-05-14 14:40:03 +03:00
decrypted , err := wa . Client . DecryptReaction ( ctx , evt )
2025-04-08 21:00:38 +03:00
if err != nil {
wa . UserLogin . Log . Err ( err ) . Str ( "message_id" , evt . Info . ID ) . Msg ( "Failed to decrypt reaction" )
return
}
decrypted . Key = encReact . GetTargetMessageKey ( )
evt . Message . ReactionMessage = decrypted
}
if encComment := evt . Message . GetEncCommentMessage ( ) ; encComment != nil {
2025-05-14 14:40:03 +03:00
decrypted , err := wa . Client . DecryptComment ( ctx , evt )
2025-04-08 21:00:38 +03:00
if err != nil {
wa . UserLogin . Log . Err ( err ) . Str ( "message_id" , evt . Info . ID ) . Msg ( "Failed to decrypt comment" )
} else {
decrypted . EncCommentMessage = evt . Message . GetEncCommentMessage ( )
evt . Message = decrypted
}
}
2025-09-29 19:23:13 +03:00
if encMessage := evt . Message . GetSecretEncryptedMessage ( ) ; encMessage != nil {
decrypted , err := wa . Client . DecryptSecretEncryptedMessage ( ctx , evt )
if err != nil {
wa . UserLogin . Log . Err ( err ) . Str ( "message_id" , evt . Info . ID ) . Msg ( "Failed to decrypt message" )
return
}
evt . RawMessage = decrypted
evt . UnwrapRaw ( )
parsedMessageType = getMessageType ( evt . Message )
}
2025-07-10 17:57:31 +03:00
res := wa . UserLogin . QueueRemoteEvent ( & WAMessageEvent {
2024-09-10 18:15:38 +03:00
MessageInfoWrapper : & MessageInfoWrapper {
Info : evt . Info ,
wa : wa ,
} ,
Message : evt . Message ,
MsgEvent : evt ,
2024-09-10 19:41:30 +03:00
parsedMessageType : parsedMessageType ,
2024-09-10 18:15:38 +03:00
} )
2025-06-17 22:24:37 +05:30
return res . Success
2024-09-10 18:15:38 +03:00
}
2025-08-11 17:17:32 +03:00
func ( wa * WhatsAppClient ) handleWAUndecryptableMessage ( ctx context . Context , evt * events . UndecryptableMessage ) bool {
2025-11-11 18:22:25 +02:00
wa . rerouteWAMessage ( ctx , "undecryptable message" , & evt . Info . MessageSource , evt . Info . ID )
2024-09-10 18:15:38 +03:00
wa . UserLogin . Log . Debug ( ) .
Any ( "info" , evt . Info ) .
Bool ( "unavailable" , evt . IsUnavailable ) .
Str ( "decrypt_fail" , string ( evt . DecryptFailMode ) ) .
Msg ( "Received undecryptable WhatsApp message" )
2024-09-27 14:44:15 +03:00
wa . trackUndecryptable ( evt )
2025-04-08 19:42:10 +03:00
if evt . DecryptFailMode == events . DecryptFailHide {
2025-06-17 22:24:37 +05:30
return true
2024-09-10 18:15:38 +03:00
}
2024-10-16 16:44:57 +03:00
if evt . Info . Chat == types . StatusBroadcastJID && ! wa . Main . Config . EnableStatusBroadcast {
2025-06-17 22:24:37 +05:30
return true
2024-10-16 16:44:57 +03:00
}
2025-07-10 17:57:31 +03:00
res := wa . UserLogin . QueueRemoteEvent ( & WAUndecryptableMessage {
2024-09-10 18:15:38 +03:00
MessageInfoWrapper : & MessageInfoWrapper {
Info : evt . Info ,
wa : wa ,
} ,
2024-12-02 19:42:47 +02:00
Type : evt . UnavailableType ,
2024-09-10 18:15:38 +03:00
} )
2025-06-17 22:24:37 +05:30
return res . Success
2024-09-10 18:15:38 +03:00
}
2025-07-03 21:44:15 +03:00
func ( wa * WhatsAppClient ) handleWAReceipt ( ctx context . Context , evt * events . Receipt ) ( success bool ) {
2026-01-07 20:54:06 +02:00
origChat := evt . Chat
2025-11-11 18:22:25 +02:00
wa . rerouteWAMessage ( ctx , "receipt" , & evt . MessageSource , evt . MessageIDs )
2024-09-27 15:20:33 +03:00
if evt . IsFromMe && evt . Sender . Device == 0 {
wa . phoneSeen ( evt . Timestamp )
}
2024-09-10 18:15:38 +03:00
var evtType bridgev2 . RemoteEventType
switch evt . Type {
case types . ReceiptTypeRead , types . ReceiptTypeReadSelf :
evtType = bridgev2 . RemoteEventReadReceipt
case types . ReceiptTypeDelivered :
evtType = bridgev2 . RemoteEventDeliveryReceipt
case types . ReceiptTypeSender :
fallthrough
default :
2025-06-17 22:24:37 +05:30
return true
2024-09-10 18:15:38 +03:00
}
targets := make ( [ ] networkid . MessageID , len ( evt . MessageIDs ) )
2024-09-27 16:46:40 +03:00
messageSender := wa . JID
if ! evt . MessageSender . IsEmpty ( ) {
messageSender = evt . MessageSender
2026-01-07 20:54:06 +02:00
// Second part of rerouting receipts in LID chats
if messageSender == origChat && evt . Chat != origChat {
messageSender = evt . Chat
}
2025-06-16 21:37:22 +03:00
} else if evt . Chat . Server == types . GroupServer && evt . Sender . Server == types . HiddenUserServer {
2025-10-20 22:30:04 +03:00
lid := wa . GetStore ( ) . GetLID ( )
2025-06-16 21:37:22 +03:00
if ! lid . IsEmpty ( ) {
messageSender = lid
}
2024-09-27 16:46:40 +03:00
}
2024-09-10 18:15:38 +03:00
for i , id := range evt . MessageIDs {
2024-09-27 16:46:40 +03:00
targets [ i ] = waid . MakeMessageID ( evt . Chat , messageSender , id )
2024-09-10 18:15:38 +03:00
}
2025-07-10 17:57:31 +03:00
res := wa . UserLogin . QueueRemoteEvent ( & simplevent . Receipt {
2024-09-10 18:15:38 +03:00
EventMeta : simplevent . EventMeta {
Type : evtType ,
PortalKey : wa . makeWAPortalKey ( evt . Chat ) ,
2025-07-03 21:44:15 +03:00
Sender : wa . makeEventSender ( ctx , evt . Sender ) ,
2024-09-10 18:15:38 +03:00
Timestamp : evt . Timestamp ,
} ,
Targets : targets ,
} )
2025-06-17 22:24:37 +05:30
return res . Success
2024-09-10 18:15:38 +03:00
}
2025-07-03 21:44:15 +03:00
func ( wa * WhatsAppClient ) handleWAChatPresence ( ctx context . Context , evt * events . ChatPresence ) {
2025-11-08 23:19:12 +02:00
if evt . Chat . Server == types . HiddenUserServer && evt . Sender . ToNonAD ( ) == evt . Chat {
if evt . SenderAlt . IsEmpty ( ) {
evt . SenderAlt , _ = wa . GetStore ( ) . LIDs . GetPNForLID ( ctx , evt . Sender )
}
if evt . SenderAlt . Server == types . DefaultUserServer {
evt . Sender , evt . SenderAlt = evt . SenderAlt , evt . Sender
evt . Chat = evt . Sender . ToNonAD ( )
}
}
2024-09-10 18:15:38 +03:00
typingType := bridgev2 . TypingTypeText
timeout := 15 * time . Second
if evt . Media == types . ChatPresenceMediaAudio {
typingType = bridgev2 . TypingTypeRecordingMedia
}
if evt . State == types . ChatPresencePaused {
timeout = 0
}
2025-07-10 17:57:31 +03:00
wa . UserLogin . QueueRemoteEvent ( & simplevent . Typing {
2024-09-10 18:15:38 +03:00
EventMeta : simplevent . EventMeta {
Type : bridgev2 . RemoteEventTyping ,
LogContext : nil ,
PortalKey : wa . makeWAPortalKey ( evt . Chat ) ,
2025-07-03 21:44:15 +03:00
Sender : wa . makeEventSender ( ctx , evt . Sender ) ,
2024-09-10 18:15:38 +03:00
Timestamp : time . Now ( ) ,
} ,
Timeout : timeout ,
Type : typingType ,
} )
}
func ( wa * WhatsAppClient ) handleWALogout ( reason events . ConnectFailureReason , onConnect bool ) {
errorCode := WAUnknownLogout
if reason == events . ConnectFailureLoggedOut {
errorCode = WALoggedOut
} else if reason == events . ConnectFailureMainDeviceGone {
errorCode = WAMainDeviceGone
}
2026-03-25 18:13:47 +02:00
wa . Disconnect ( )
2024-09-10 18:15:38 +03:00
wa . Client = nil
wa . JID = types . EmptyJID
2024-09-16 15:38:39 +03:00
wa . UserLogin . Metadata . ( * waid . UserLoginMetadata ) . WADeviceID = 0
2024-09-10 18:15:38 +03:00
wa . UserLogin . BridgeState . Send ( status . BridgeState {
StateEvent : status . StateBadCredentials ,
Error : errorCode ,
} )
}
2024-09-27 14:57:52 +03:00
const callEventMaxAge = 15 * time . Minute
2025-09-19 15:48:58 +03:00
func ( wa * WhatsAppClient ) handleWACallStart ( ctx context . Context , group , sender , senderAlt types . JID , id , callType string , ts time . Time ) bool {
2024-09-27 14:57:52 +03:00
if ! wa . Main . Config . CallStartNotices || time . Since ( ts ) > callEventMaxAge {
2025-07-10 18:03:16 +03:00
return true
2024-09-26 12:37:21 +03:00
}
2025-09-19 15:48:58 +03:00
if sender . Server == types . HiddenUserServer && senderAlt . Server == types . DefaultUserServer {
wa . UserLogin . Log . Debug ( ) .
Stringer ( "lid" , sender ) .
Stringer ( "pn" , senderAlt ) .
Str ( "call_id" , id ) .
Msg ( "Forced LID caller to phone number in incoming call" )
sender , senderAlt = senderAlt , sender
}
2025-07-09 15:56:14 +03:00
chat := group
if chat . IsEmpty ( ) {
chat = sender
}
2025-07-10 18:03:16 +03:00
return wa . UserLogin . QueueRemoteEvent ( & simplevent . Message [ string ] {
2024-09-26 12:37:21 +03:00
EventMeta : simplevent . EventMeta {
Type : bridgev2 . RemoteEventMessage ,
LogContext : nil ,
2025-07-09 15:56:14 +03:00
PortalKey : wa . makeWAPortalKey ( chat ) ,
2025-07-03 21:44:15 +03:00
Sender : wa . makeEventSender ( ctx , sender ) ,
2024-09-26 12:37:21 +03:00
CreatePortal : true ,
Timestamp : ts ,
2026-01-13 15:10:53 +01:00
StreamOrder : ts . Unix ( ) ,
2024-09-26 12:37:21 +03:00
} ,
Data : callType ,
2025-07-09 15:56:14 +03:00
ID : waid . MakeFakeMessageID ( chat , sender , "call-" + id ) ,
2024-09-26 12:37:21 +03:00
ConvertMessageFunc : convertCallStart ,
2025-07-10 18:03:16 +03:00
} ) . Success
2024-09-26 12:37:21 +03:00
}
func convertCallStart ( ctx context . Context , portal * bridgev2 . Portal , intent bridgev2 . MatrixAPI , callType string ) ( * bridgev2 . ConvertedMessage , error ) {
text := "Incoming call. Use the WhatsApp app to answer."
if callType != "" {
text = fmt . Sprintf ( "Incoming %s call. Use the WhatsApp app to answer." , callType )
}
return & bridgev2 . ConvertedMessage {
Parts : [ ] * bridgev2 . ConvertedMessagePart { {
Type : event . EventMessage ,
Content : & event . MessageEventContent {
MsgType : event . MsgText ,
Body : text ,
2026-01-23 16:49:49 +01:00
BeeperActionMessage : & event . BeeperActionMessage {
Type : event . BeeperActionMessageCall ,
CallType : event . BeeperActionMessageCallType ( callType ) ,
} ,
2024-09-26 12:37:21 +03:00
} ,
} } ,
} , nil
2024-09-10 18:15:38 +03:00
}
2025-07-03 21:44:15 +03:00
func ( wa * WhatsAppClient ) handleWAIdentityChange ( ctx context . Context , evt * events . IdentityChange ) {
2024-09-26 12:37:21 +03:00
if ! wa . Main . Config . IdentityChangeNotices {
return
}
wa . UserLogin . QueueRemoteEvent ( & simplevent . Message [ * events . IdentityChange ] {
EventMeta : simplevent . EventMeta {
Type : bridgev2 . RemoteEventMessage ,
LogContext : nil ,
PortalKey : wa . makeWAPortalKey ( evt . JID ) ,
2025-07-03 21:44:15 +03:00
Sender : wa . makeEventSender ( ctx , evt . JID ) ,
2024-09-26 12:37:21 +03:00
CreatePortal : false ,
Timestamp : evt . Timestamp ,
} ,
Data : evt ,
ID : waid . MakeFakeMessageID ( evt . JID , evt . JID , "idchange-" + strconv . FormatInt ( evt . Timestamp . UnixMilli ( ) , 10 ) ) ,
ConvertMessageFunc : convertIdentityChange ,
} )
}
func convertIdentityChange ( ctx context . Context , portal * bridgev2 . Portal , intent bridgev2 . MatrixAPI , data * events . IdentityChange ) ( * bridgev2 . ConvertedMessage , error ) {
ghost , err := portal . Bridge . GetGhostByID ( ctx , waid . MakeUserID ( data . JID ) )
if err != nil {
return nil , err
}
text := fmt . Sprintf ( "Your security code with %s changed." , ghost . Name )
if data . Implicit {
text = fmt . Sprintf ( "Your security code with %s (device #%d) changed." , ghost . Name , data . JID . Device )
}
return & bridgev2 . ConvertedMessage {
Parts : [ ] * bridgev2 . ConvertedMessagePart { {
Type : event . EventMessage ,
Content : & event . MessageEventContent {
MsgType : event . MsgNotice ,
Body : text ,
} ,
} } ,
} , nil
}
2026-01-22 15:56:20 +00:00
func ( wa * WhatsAppClient ) handleWADeleteChat ( ctx context . Context , evt * events . DeleteChat ) bool {
chatJID := wa . maybeConvertJIDToLID ( ctx , evt . JID )
2025-07-10 18:03:16 +03:00
return wa . UserLogin . QueueRemoteEvent ( & simplevent . ChatDelete {
2024-09-26 12:37:21 +03:00
EventMeta : simplevent . EventMeta {
Type : bridgev2 . RemoteEventChatDelete ,
2026-01-22 15:56:20 +00:00
PortalKey : wa . makeWAPortalKey ( chatJID ) ,
2024-09-26 12:37:21 +03:00
Timestamp : evt . Timestamp ,
} ,
OnlyForMe : true ,
2025-10-22 18:57:28 +03:00
Children : true ,
2025-07-10 18:03:16 +03:00
} ) . Success
2024-09-26 12:37:21 +03:00
}
2026-01-22 15:56:20 +00:00
func ( wa * WhatsAppClient ) handleWADeleteForMe ( ctx context . Context , evt * events . DeleteForMe ) bool {
chatJID := wa . maybeConvertJIDToLID ( ctx , evt . ChatJID )
2025-07-10 18:03:16 +03:00
return wa . UserLogin . QueueRemoteEvent ( & simplevent . MessageRemove {
2024-09-26 12:37:21 +03:00
EventMeta : simplevent . EventMeta {
Type : bridgev2 . RemoteEventMessageRemove ,
2026-01-22 15:56:20 +00:00
PortalKey : wa . makeWAPortalKey ( chatJID ) ,
2024-09-26 12:37:21 +03:00
Timestamp : evt . Timestamp ,
} ,
2026-01-22 15:56:20 +00:00
TargetMessage : waid . MakeMessageID ( chatJID , evt . SenderJID , evt . MessageID ) ,
2024-09-26 12:37:21 +03:00
OnlyForMe : true ,
2025-07-10 18:03:16 +03:00
} ) . Success
2024-09-26 12:37:21 +03:00
}
2025-07-10 18:03:16 +03:00
func ( wa * WhatsAppClient ) handleWAMarkChatAsRead ( ctx context . Context , evt * events . MarkChatAsRead ) bool {
2026-01-22 15:56:20 +00:00
chatJID := wa . maybeConvertJIDToLID ( ctx , evt . JID )
2025-07-10 18:03:16 +03:00
return wa . UserLogin . QueueRemoteEvent ( & simplevent . Receipt {
2024-09-26 12:37:21 +03:00
EventMeta : simplevent . EventMeta {
Type : bridgev2 . RemoteEventReadReceipt ,
2026-01-22 15:56:20 +00:00
PortalKey : wa . makeWAPortalKey ( chatJID ) ,
2025-07-03 21:44:15 +03:00
Sender : wa . makeEventSender ( ctx , wa . JID ) ,
2024-09-26 12:37:21 +03:00
Timestamp : evt . Timestamp ,
} ,
ReadUpTo : evt . Timestamp ,
2025-07-10 18:03:16 +03:00
} ) . Success
2024-09-10 18:15:38 +03:00
}
2024-09-26 12:48:19 +03:00
2025-01-05 23:12:34 +02:00
func ( wa * WhatsAppClient ) syncGhost ( jid types . JID , reason string , pictureID * string ) {
2024-09-26 12:48:19 +03:00
log := wa . UserLogin . Log . With ( ) .
Str ( "action" , "sync ghost" ) .
Str ( "reason" , reason ) .
2025-01-05 23:12:34 +02:00
Str ( "picture_id" , ptr . Val ( pictureID ) ) .
2024-09-26 12:48:19 +03:00
Stringer ( "jid" , jid ) .
Logger ( )
2025-07-22 15:58:13 +03:00
ctx := log . WithContext ( wa . Main . Bridge . BackgroundCtx )
2024-09-26 12:48:19 +03:00
ghost , err := wa . Main . Bridge . GetGhostByID ( ctx , waid . MakeUserID ( jid ) )
if err != nil {
log . Err ( err ) . Msg ( "Failed to get ghost" )
return
}
2025-01-05 23:12:34 +02:00
if pictureID != nil && * pictureID != "" && ghost . AvatarID == networkid . AvatarID ( * pictureID ) {
2024-09-26 13:23:23 +03:00
return
}
2025-01-05 23:12:34 +02:00
userInfo , err := wa . getUserInfo ( ctx , jid , pictureID != nil )
2024-09-26 12:48:19 +03:00
if err != nil {
log . Err ( err ) . Msg ( "Failed to get user info" )
} else {
ghost . UpdateInfo ( ctx , userInfo )
log . Debug ( ) . Msg ( "Synced ghost info" )
2025-07-22 17:57:56 +03:00
wa . syncAltGhostWithInfo ( ctx , jid , userInfo )
2024-09-26 12:48:19 +03:00
}
2025-04-15 14:33:53 +03:00
go wa . syncRemoteProfile ( ctx , ghost )
2024-09-26 12:48:19 +03:00
}
2024-09-26 13:23:23 +03:00
2025-07-10 18:03:16 +03:00
func ( wa * WhatsAppClient ) handleWAPictureUpdate ( ctx context . Context , evt * events . Picture ) bool {
2025-07-22 15:58:13 +03:00
if evt . JID . Server == types . DefaultUserServer || evt . JID . Server == types . HiddenUserServer || evt . JID . Server == types . BotServer {
2025-07-10 18:03:16 +03:00
go wa . syncGhost ( evt . JID , "picture event" , & evt . PictureID )
return true
2024-09-26 13:23:23 +03:00
} else {
var changes bridgev2 . ChatInfo
if evt . Remove {
changes . Avatar = & bridgev2 . Avatar { Remove : true , ID : "remove" }
} else {
2024-10-03 00:21:39 +03:00
changes . ExtraUpdates = wa . makePortalAvatarFetcher ( evt . PictureID , evt . Author , evt . Timestamp )
2024-09-26 13:23:23 +03:00
}
2025-07-10 18:03:16 +03:00
return wa . UserLogin . QueueRemoteEvent ( & simplevent . ChatInfoChange {
2024-09-26 13:23:23 +03:00
EventMeta : simplevent . EventMeta {
Type : bridgev2 . RemoteEventChatInfoChange ,
LogContext : func ( c zerolog . Context ) zerolog . Context {
return c .
Str ( "wa_event_type" , "picture" ) .
Stringer ( "picture_author" , evt . Author ) .
Str ( "new_picture_id" , evt . PictureID ) .
Bool ( "remove_picture" , evt . Remove )
} ,
PortalKey : wa . makeWAPortalKey ( evt . JID ) ,
2025-07-03 21:44:15 +03:00
Sender : wa . makeEventSender ( ctx , evt . Author ) ,
2024-09-26 13:23:23 +03:00
Timestamp : evt . Timestamp ,
} ,
ChatInfoChange : & bridgev2 . ChatInfoChange {
ChatInfo : & changes ,
} ,
2025-07-10 18:03:16 +03:00
} ) . Success
2024-09-26 13:23:23 +03:00
}
}
2024-09-26 13:38:11 +03:00
2025-07-10 18:03:16 +03:00
func ( wa * WhatsAppClient ) handleWAGroupInfoChange ( ctx context . Context , evt * events . GroupInfo ) bool {
2024-09-26 13:38:11 +03:00
eventMeta := simplevent . EventMeta {
Type : bridgev2 . RemoteEventChatInfoChange ,
LogContext : nil ,
PortalKey : wa . makeWAPortalKey ( evt . JID ) ,
CreatePortal : true ,
Timestamp : evt . Timestamp ,
}
if evt . Sender != nil {
2025-07-03 21:44:15 +03:00
eventMeta . Sender = wa . makeEventSender ( ctx , * evt . Sender )
2024-09-26 13:38:11 +03:00
}
if evt . Delete != nil {
eventMeta . Type = bridgev2 . RemoteEventChatDelete
2025-07-10 18:03:16 +03:00
return wa . UserLogin . QueueRemoteEvent ( & simplevent . ChatDelete { EventMeta : eventMeta } ) . Success
2024-09-26 13:38:11 +03:00
} else {
2025-07-10 18:03:16 +03:00
return wa . UserLogin . QueueRemoteEvent ( & simplevent . ChatInfoChange {
2024-09-26 13:38:11 +03:00
EventMeta : eventMeta ,
2025-07-03 21:44:15 +03:00
ChatInfoChange : wa . wrapGroupInfoChange ( ctx , evt ) ,
2025-07-10 18:03:16 +03:00
} ) . Success
2024-09-26 13:38:11 +03:00
}
}
2024-09-26 14:18:55 +03:00
2025-07-10 18:03:16 +03:00
func ( wa * WhatsAppClient ) handleWAJoinedGroup ( ctx context . Context , evt * events . JoinedGroup ) bool {
2025-09-01 18:03:59 +03:00
if wa . createDedup . Pop ( evt . CreateKey ) {
return true
}
2025-07-10 18:03:16 +03:00
return wa . UserLogin . QueueRemoteEvent ( & simplevent . ChatResync {
2024-09-26 14:18:55 +03:00
EventMeta : simplevent . EventMeta {
Type : bridgev2 . RemoteEventChatResync ,
LogContext : nil ,
PortalKey : wa . makeWAPortalKey ( evt . JID ) ,
CreatePortal : true ,
} ,
2025-07-03 21:44:15 +03:00
ChatInfo : wa . wrapGroupInfo ( ctx , & evt . GroupInfo ) ,
2025-07-10 18:03:16 +03:00
} ) . Success
2024-09-26 14:18:55 +03:00
}
2024-09-27 14:27:13 +03:00
2025-07-10 18:03:16 +03:00
func ( wa * WhatsAppClient ) handleWANewsletterJoin ( ctx context . Context , evt * events . NewsletterJoin ) bool {
return wa . UserLogin . QueueRemoteEvent ( & simplevent . ChatResync {
2024-09-27 14:30:40 +03:00
EventMeta : simplevent . EventMeta {
Type : bridgev2 . RemoteEventChatResync ,
LogContext : nil ,
PortalKey : wa . makeWAPortalKey ( evt . ID ) ,
CreatePortal : true ,
} ,
2025-07-03 21:44:15 +03:00
ChatInfo : wa . wrapNewsletterInfo ( ctx , & evt . NewsletterMetadata ) ,
2025-07-10 18:03:16 +03:00
} ) . Success
2024-09-27 14:30:40 +03:00
}
2025-07-10 18:03:16 +03:00
func ( wa * WhatsAppClient ) handleWANewsletterLeave ( evt * events . NewsletterLeave ) bool {
return wa . UserLogin . QueueRemoteEvent ( & simplevent . ChatDelete {
2024-09-27 14:30:40 +03:00
EventMeta : simplevent . EventMeta {
Type : bridgev2 . RemoteEventChatDelete ,
LogContext : nil ,
PortalKey : wa . makeWAPortalKey ( evt . ID ) ,
} ,
OnlyForMe : true ,
2025-07-10 18:03:16 +03:00
} ) . Success
2024-09-27 14:30:40 +03:00
}
2025-07-10 18:03:16 +03:00
func ( wa * WhatsAppClient ) handleWAUserLocalPortalInfo ( chatJID types . JID , ts time . Time , info * bridgev2 . UserLocalPortalInfo ) bool {
return wa . UserLogin . QueueRemoteEvent ( & simplevent . ChatInfoChange {
2024-09-27 14:27:13 +03:00
EventMeta : simplevent . EventMeta {
Type : bridgev2 . RemoteEventChatInfoChange ,
PortalKey : wa . makeWAPortalKey ( chatJID ) ,
Timestamp : ts ,
} ,
ChatInfoChange : & bridgev2 . ChatInfoChange {
ChatInfo : & bridgev2 . ChatInfo {
UserLocal : info ,
} ,
} ,
2025-07-10 18:03:16 +03:00
} ) . Success
2024-09-27 14:27:13 +03:00
}
2025-07-10 18:03:16 +03:00
func ( wa * WhatsAppClient ) handleWAMute ( evt * events . Mute ) bool {
2024-09-27 14:27:13 +03:00
var mutedUntil time . Time
if evt . Action . GetMuted ( ) {
mutedUntil = event . MutedForever
2025-10-22 13:04:21 +03:00
if evt . Action . GetMuteEndTimestamp ( ) > 0 {
2024-09-27 14:27:13 +03:00
mutedUntil = time . Unix ( evt . Action . GetMuteEndTimestamp ( ) , 0 )
}
} else {
mutedUntil = bridgev2 . Unmuted
}
2025-07-10 18:03:16 +03:00
return wa . handleWAUserLocalPortalInfo ( evt . JID , evt . Timestamp , & bridgev2 . UserLocalPortalInfo {
2024-09-27 14:27:13 +03:00
MutedUntil : & mutedUntil ,
} )
}
2025-07-10 18:03:16 +03:00
func ( wa * WhatsAppClient ) handleWAArchive ( evt * events . Archive ) bool {
2024-09-27 14:27:13 +03:00
var tag event . RoomTag
if evt . Action . GetArchived ( ) {
2024-11-13 19:04:46 +02:00
tag = wa . Main . Config . ArchiveTag
2024-09-27 14:27:13 +03:00
}
2025-07-10 18:03:16 +03:00
return wa . handleWAUserLocalPortalInfo ( evt . JID , evt . Timestamp , & bridgev2 . UserLocalPortalInfo {
2024-09-27 14:27:13 +03:00
Tag : & tag ,
} )
}
2025-07-10 18:03:16 +03:00
func ( wa * WhatsAppClient ) handleWAPin ( evt * events . Pin ) bool {
2024-09-27 14:27:13 +03:00
var tag event . RoomTag
if evt . Action . GetPinned ( ) {
2024-11-13 19:04:46 +02:00
tag = wa . Main . Config . PinnedTag
2024-09-27 14:27:13 +03:00
}
2025-07-10 18:03:16 +03:00
return wa . handleWAUserLocalPortalInfo ( evt . JID , evt . Timestamp , & bridgev2 . UserLocalPortalInfo {
2024-09-27 14:27:13 +03:00
Tag : & tag ,
} )
}
2026-01-26 20:38:36 +02:00
func ( wa * WhatsAppClient ) handleWAAppStateSyncComplete ( ctx context . Context , evt * events . AppStateSyncComplete ) {
log := zerolog . Ctx ( ctx ) . With ( ) .
Str ( "patch_name" , string ( evt . Name ) ) .
Uint64 ( "patch_version" , evt . Version ) .
Logger ( )
if len ( wa . GetStore ( ) . PushName ) > 0 && evt . Name == appstate . WAPatchCriticalBlock {
err := wa . updatePresence ( ctx , types . PresenceUnavailable )
if err != nil {
log . Warn ( ) . Err ( err ) . Msg ( "Failed to send presence after app state sync" )
}
go wa . syncRemoteProfile ( log . WithContext ( context . Background ( ) ) , nil )
} else if evt . Name == appstate . WAPatchCriticalUnblockLow {
go wa . resyncContacts ( false , true )
}
wa . appStateRecoveryLock . Lock ( )
defer wa . appStateRecoveryLock . Unlock ( )
meta := wa . UserLogin . Metadata . ( * waid . UserLoginMetadata )
if ts , exists := meta . AppStateRecoveryAttempted [ evt . Name ] ; exists {
delete ( wa . appStateFullSyncAttempted , evt . Name )
delete ( meta . AppStateRecoveryAttempted , evt . Name )
err := wa . UserLogin . Save ( ctx )
if err != nil {
log . Err ( err ) . Msg ( "Failed to save login metadata after unmarking app state recovery as attempted" )
} else {
log . Info ( ) .
Time ( "recovery_ts" , ts ) .
Msg ( "Unmarked app state recovery as attempted after successful full sync" )
}
} else if ts , exists = wa . appStateFullSyncAttempted [ evt . Name ] ; exists {
delete ( wa . appStateFullSyncAttempted , evt . Name )
log . Debug ( ) . Time ( "full_sync_ts" , ts ) . Msg ( "Unmarked app state full sync attempted after successful sync" )
}
}
func ( wa * WhatsAppClient ) handleWAAppStateSyncError ( ctx context . Context , evt * events . AppStateSyncError ) {
log := zerolog . Ctx ( ctx ) . With ( ) .
Str ( "patch_name" , string ( evt . Name ) ) .
Logger ( )
wa . appStateRecoveryLock . Lock ( )
defer wa . appStateRecoveryLock . Unlock ( )
meta := wa . UserLogin . Metadata . ( * waid . UserLoginMetadata )
lastRecovery := meta . AppStateRecoveryAttempted [ evt . Name ]
lastFullSync := wa . appStateFullSyncAttempted [ evt . Name ]
2026-01-26 21:55:47 +02:00
if ! lastRecovery . IsZero ( ) && time . Since ( lastRecovery ) < 48 * time . Hour {
2026-01-26 20:38:36 +02:00
log . Debug ( ) . Err ( evt . Error ) .
Time ( "last_recovery_attempt" , lastRecovery ) .
Time ( "last_full_sync_attempt" , lastFullSync ) .
Msg ( "App state sync failed, but recovery already attempted" )
return
}
if ! evt . FullSync {
if ! lastFullSync . IsZero ( ) {
log . Debug ( ) .
Err ( evt . Error ) .
Time ( "last_full_sync_attempt" , lastFullSync ) .
Msg ( "App state sync failed, but full sync already attempted" )
return
}
wa . appStateFullSyncAttempted [ evt . Name ] = time . Now ( )
log . Info ( ) .
Err ( evt . Error ) .
Msg ( "Trying full sync for app state after partial sync error" )
go func ( ) {
err := wa . Client . FetchAppState ( ctx , evt . Name , true , false )
if err != nil {
log . Err ( err ) . Msg ( "Full app state sync failed" )
} else {
log . Debug ( ) . Msg ( "Full app state sync succeeded" )
}
} ( )
return
}
log . Info ( ) .
Err ( evt . Error ) .
Msg ( "Trying recovery for app state after full sync error" )
if meta . AppStateRecoveryAttempted == nil {
meta . AppStateRecoveryAttempted = make ( map [ appstate . WAPatchName ] time . Time )
}
meta . AppStateRecoveryAttempted [ evt . Name ] = time . Now ( )
err := wa . UserLogin . Save ( ctx )
if err != nil {
log . Err ( err ) . Msg ( "Failed to save login metadata after marking app state recovery as attempted" )
}
go func ( ) {
resp , err := wa . Client . SendPeerMessage ( ctx , whatsmeow . BuildAppStateRecoveryRequest ( evt . Name ) )
if err != nil {
log . Err ( err ) . Msg ( "Failed to send app state recovery request" )
} else {
log . Debug ( ) .
Str ( "message_id" , resp . ID ) .
Time ( "message_ts" , resp . Timestamp ) .
Msg ( "Sent app state recovery request" )
}
} ( )
}