diff --git a/go.mod b/go.mod index 85f5c6c..35a6655 100644 --- a/go.mod +++ b/go.mod @@ -12,13 +12,13 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.10 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260616120636-eaa388b4e537 + go.mau.fi/whatsmeow v0.0.0-20260709092057-73fe7355f59f golang.org/x/image v0.42.0 golang.org/x/net v0.56.0 golang.org/x/sync v0.21.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.28.1 + maunium.net/go/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895 ) require ( diff --git a/go.sum b/go.sum index e0a405c..3d0391c 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.10 h1:wzvz5iDHyqDXB8vgisD4d3SzucLXNM3iNY+1O1RoHtg= go.mau.fi/util v0.9.10/go.mod h1:YQOxySn+ZE3qSYqNxvyX7Yi3suA8YK17PS6QqBREW7A= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260616120636-eaa388b4e537 h1:LhxtFShA1NXJWz4yNygwtl3Eycp9XsfMqJeDdcLyKmk= -go.mau.fi/whatsmeow v0.0.0-20260616120636-eaa388b4e537/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U= +go.mau.fi/whatsmeow v0.0.0-20260709092057-73fe7355f59f h1:VZkwFBEQ9TbB9IWsldw8BJDhHbEBElm9VD2VQXbSc9s= +go.mau.fi/whatsmeow v0.0.0-20260709092057-73fe7355f59f/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= @@ -107,5 +107,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.28.1 h1:Hic3oDMPbLbQu1fhboTRAKZcORMjzzkjxsa+SGk60b0= -maunium.net/go/mautrix v0.28.1/go.mod h1:mWXQNmOlrq4VTDU9f1HO03BSIswdUIyyY4wUKHqwzzY= +maunium.net/go/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895 h1:oe87/mP1vo4LnA6CJDEfyg5ZiAOar2quJNypfO8eS4Y= +maunium.net/go/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895/go.mod h1:mWXQNmOlrq4VTDU9f1HO03BSIswdUIyyY4wUKHqwzzY= diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 0e6de76..dd04696 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -400,8 +400,8 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { return } wrappedInfo, err := wa.getChatInfo(ctx, conv.ChatJID, conv, true) - if errors.Is(err, whatsmeow.ErrNotInGroup) { - log.Debug().Stringer("chat_jid", conv.ChatJID). + if errors.Is(err, whatsmeow.ErrNotInGroup) || errors.Is(err, whatsmeow.ErrGroupNotFound) { + log.Debug().Err(err).Stringer("chat_jid", conv.ChatJID). Msg("Skipping creating room because the user is not a participant") //err = wa.Main.DB.Message.DeleteAllInChat(ctx, wa.UserLogin.ID, conv.ChatJID) //if err != nil { @@ -750,7 +750,7 @@ func (wa *WhatsAppClient) convertHistorySyncMessage( // TODO use proper intent intent := wa.Main.Bridge.Bot wrapped := &bridgev2.BackfillMessage{ - ConvertedMessage: wa.Main.MsgConv.ToMatrix(ctx, portal, wa.Client, intent, msg, rawMsg, info, isViewOnce, true, nil), + ConvertedMessage: wa.Main.MsgConv.ToMatrix(ctx, portal, wa.Client, intent, msg, rawMsg, info, nil, isViewOnce, true, nil), Sender: wa.makeEventSender(ctx, info.Sender), ID: waid.MakeMessageID(info.Chat, info.Sender, info.ID), TxnID: networkid.TransactionID(waid.MakeMessageID(info.Chat, info.Sender, info.ID)), diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 411c39e..cb51e64 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -52,8 +52,9 @@ func (wa *WhatsAppClient) getPortalKeyByMessageSource(ms types.MessageSource) ne } type MessageInfoWrapper struct { - Info types.MessageInfo - wa *WhatsAppClient + OrigSource types.MessageSource + Info types.MessageInfo + wa *WhatsAppClient } func (evt *MessageInfoWrapper) ShouldCreatePortal() bool { @@ -200,7 +201,7 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por ctx = context.WithValue(ctx, msgconv.ContextKeyEditTargetID, evt.Message.GetProtocolMessage().GetKey().GetID()) cm := evt.wa.Main.MsgConv.ToMatrix( - ctx, portal, evt.wa.Client, intent, editedMsg, evt.MsgEvent.RawMessage, &evt.Info, evt.isViewOnce(), false, previouslyConvertedPart, + ctx, portal, evt.wa.Client, intent, editedMsg, evt.MsgEvent.RawMessage, &evt.Info, &evt.OrigSource, evt.isViewOnce(), false, previouslyConvertedPart, ) if evt.isUndecryptableUpsertSubEvent && isFailedMedia(cm) { evt.postHandle = func() { @@ -285,7 +286,7 @@ func (evt *WAMessageEvent) HandleExisting(ctx context.Context, portal *bridgev2. func (evt *WAMessageEvent) ConvertMessage(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI) (*bridgev2.ConvertedMessage, error) { evt.wa.EnqueuePortalResync(portal, false) converted := evt.wa.Main.MsgConv.ToMatrix( - ctx, portal, evt.wa.Client, intent, evt.Message, evt.MsgEvent.RawMessage, &evt.Info, evt.isViewOnce(), false, nil, + ctx, portal, evt.wa.Client, intent, evt.Message, evt.MsgEvent.RawMessage, &evt.Info, &evt.OrigSource, evt.isViewOnce(), false, nil, ) if isFailedMedia(converted) { evt.postHandle = func() { diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index 299449f..9aea18b 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -177,7 +177,7 @@ func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev defer wa.mcTrack(msg, time.Now(), &retErr) var req whatsmeow.SendRequestExtra if msg.Portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup { - reactionMsg.EncReactionMessage, err = wa.Client.EncryptReaction(ctx, msgconv.MessageIDToInfo(wa.Client, messageID), reactionMsg.ReactionMessage) + reactionMsg.EncReactionMessage, err = wa.Client.EncryptReaction(ctx, msgconv.MessageIDToInfo(ctx, wa.Client, messageID), reactionMsg.ReactionMessage) if err != nil { return nil, fmt.Errorf("failed to encrypt reaction: %w", err) } diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 30a9cd8..f12ebad 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -126,6 +126,20 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { success = wa.handleWANewsletterLeave(evt) case *events.Picture: success = wa.handleWAPictureUpdate(ctx, evt) + case *events.NotifyAccountReachoutTimelock: + wa.UserLogin.TrackAnalytics("WhatsApp Account Reachout Timelock", map[string]any{ + "enforcement_type": evt.EnforcementType, + "is_active": evt.IsActive, + "time_enforcement_ends": evt.TimeEnforcementEnds.Time, + }) + wa.UserLogin.Metadata.(*waid.UserLoginMetadata).ReachoutTimelockUntil = evt.TimeEnforcementEnds.Time + if wa.UserLogin.BridgeState.GetPrevUnsent().StateEvent == status.StateConnected { + wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected}) + } + err := wa.UserLogin.Save(ctx) + if err != nil { + log.Err(err).Msg("Failed to save user login metadata after reachout timelock update") + } case *events.AppStateSyncComplete: wa.handleWAAppStateSyncComplete(ctx, evt) @@ -344,6 +358,7 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa evt.UnwrapRaw() parsedMessageType = getMessageType(evt.Message) } + origSource := evt.Info.MessageSource wa.rerouteWAMessage(ctx, "message", &evt.Info.MessageSource, evt.Info.ID) wa.UserLogin.Log.Trace(). Any("info", evt.Info). @@ -395,8 +410,9 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa res := wa.UserLogin.QueueRemoteEvent(&WAMessageEvent{ MessageInfoWrapper: &MessageInfoWrapper{ - Info: evt.Info, - wa: wa, + OrigSource: origSource, + Info: evt.Info, + wa: wa, }, Message: evt.Message, MsgEvent: evt, diff --git a/pkg/connector/login.go b/pkg/connector/login.go index b70ab1a..5da419f 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -2,6 +2,7 @@ package connector import ( "context" + "encoding/json" "errors" "fmt" "net/http" @@ -12,6 +13,7 @@ import ( "go.mau.fi/util/exsync" "go.mau.fi/util/jsontime" "go.mau.fi/whatsmeow" + "go.mau.fi/whatsmeow/types" "go.mau.fi/whatsmeow/types/events" waLog "go.mau.fi/whatsmeow/util/log" "maunium.net/go/mautrix/bridgev2" @@ -25,6 +27,7 @@ const ( LoginStepIDQR = "fi.mau.whatsapp.login.qr" LoginStepIDPhoneNumber = "fi.mau.whatsapp.login.phone" LoginStepIDCode = "fi.mau.whatsapp.login.code" + LoginStepIDPasskey = "fi.mau.whatsapp.login.passkey" LoginStepIDComplete = "fi.mau.whatsapp.login.complete" LoginFlowIDQR = "qr" @@ -91,9 +94,11 @@ func (wa *WhatsAppConnector) CreateLogin(_ context.Context, user *bridgev2.User, Bool("phone_code", flowID == LoginFlowIDPhone). Logger(), - WaitForQRs: exsync.NewEvent(), - LoginComplete: exsync.NewEvent(), - Received515: exsync.NewEvent(), + WaitForQRs: exsync.NewEvent(), + LoginComplete: exsync.NewEvent(), + PasskeyRequest: exsync.NewEvent(), + PasskeyConfirmation: exsync.NewEvent(), + Received515: exsync.NewEvent(), }, nil } @@ -114,6 +119,11 @@ type WALogin struct { Received515 *exsync.Event PrevQRIndex atomic.Int32 + PasskeyRequest *exsync.Event + PasskeyRequestData *events.PairPasskeyRequest + PasskeyConfirmation *exsync.Event + PasskeyConfirmationData *events.PairPasskeyConfirmation + Closed atomic.Bool EventHandlerID uint32 } @@ -122,6 +132,7 @@ var ( _ bridgev2.LoginProcessDisplayAndWait = (*WALogin)(nil) _ bridgev2.LoginProcessUserInput = (*WALogin)(nil) _ bridgev2.LoginProcessWithOverride = (*WALogin)(nil) + _ bridgev2.LoginProcessWebAuthn = (*WALogin)(nil) ) const LoginConnectWait = 15 * time.Second @@ -262,6 +273,17 @@ func (wl *WALogin) handleEvent(rawEvt any) { case *events.ClientOutdated: wl.Log.Error().Msg("Got client outdated error") wl.LoginError = ErrLoginClientOutdated + case *events.PairPasskeyRequest: + wl.PasskeyRequestData = evt + wl.PasskeyRequest.Set() + return + case *events.PairPasskeyConfirmation: + wl.PasskeyConfirmationData = evt + wl.PasskeyConfirmation.Set() + return + case *events.PairPasskeyError: + wl.Log.Err(evt.Error).Msg("Got passkey error") + wl.LoginError = evt.Error case *events.PairSuccess: wl.Log.Info().Any("event_data", evt).Msg("Got pair successful event") wl.LoginSuccess = evt @@ -289,10 +311,14 @@ func (wl *WALogin) handleEvent(rawEvt any) { func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { if wl.PhoneCode { - err := wl.LoginComplete.Wait(ctx) - if err != nil { + select { + case <-ctx.Done(): wl.Cancel() - return nil, err + return nil, ctx.Err() + case <-wl.PasskeyRequest.GetChan(): + return wl.makePasskeyStep() + case <-wl.LoginComplete.GetChan(): + // continue } } else { prevIndex := int(wl.PrevQRIndex.Load()) @@ -319,9 +345,16 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { case <-ctx.Done(): wl.Cancel() return nil, ctx.Err() + case <-wl.PasskeyRequest.GetChan(): + return wl.makePasskeyStep() case <-wl.LoginComplete.GetChan(): + // continue } } + return wl.onLoginComplete(ctx) +} + +func (wl *WALogin) onLoginComplete(ctx context.Context) (*bridgev2.LoginStep, error) { if wl.LoginError != nil { wl.Log.Debug().Err(wl.LoginError).Msg("Login completed with error") wl.Cancel() @@ -372,6 +405,66 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { }, nil } +func (wl *WALogin) makePasskeyStep() (*bridgev2.LoginStep, error) { + pubkeyData, err := json.Marshal(wl.PasskeyRequestData.PublicKey) + if err != nil { + wl.Log.Err(err).Msg("Failed to marshal public key data") + return nil, err + } + return &bridgev2.LoginStep{ + Type: bridgev2.LoginStepTypeWebAuthn, + StepID: LoginStepIDPasskey, + WebAuthnParams: &bridgev2.LoginWebAuthnParams{ + URL: "https://web.whatsapp.com", + PublicKey: pubkeyData, + }, + }, nil +} + +func (wl *WALogin) SubmitWebAuthnResponse(ctx context.Context, rawResp json.RawMessage) (*bridgev2.LoginStep, error) { + var resp types.WebAuthnResponse + err := json.Unmarshal(rawResp, &resp) + if err != nil { + wl.Log.Err(err).Msg("Failed to unmarshal WebAuthn response") + wl.Cancel() + return nil, err + } + err = wl.Client.SendPasskeyResponse(ctx, &resp) + if err != nil { + wl.Log.Err(err).Msg("Failed to send WebAuthn response") + wl.Cancel() + return nil, err + } + select { + case <-ctx.Done(): + wl.Cancel() + return nil, ctx.Err() + case <-wl.PasskeyConfirmation.GetChan(): + if !wl.PasskeyConfirmationData.SkipHandoffUX { + wl.Cancel() + return nil, bridgev2.RespError{ + ErrCode: "FI.MAU.WHATSAPP.NOT_IMPLEMENTED", + Err: "Displaying WebAuthn pairing confirmation codes is not yet implemented", + StatusCode: http.StatusBadRequest, + } + } + err = wl.Client.SendPasskeyConfirmation(ctx) + if err != nil { + wl.Log.Err(err).Msg("Failed to send WebAuthn confirmation") + wl.Cancel() + return nil, err + } + case <-wl.LoginComplete.GetChan(): + } + select { + case <-ctx.Done(): + wl.Cancel() + return nil, ctx.Err() + case <-wl.LoginComplete.GetChan(): + } + return wl.onLoginComplete(ctx) +} + func (wl *WALogin) Cancel() { wl.Closed.Store(true) wl.Client.RemoveEventHandler(wl.EventHandlerID) diff --git a/pkg/connector/phoneping.go b/pkg/connector/phoneping.go index ce38079..71094c9 100644 --- a/pkg/connector/phoneping.go +++ b/pkg/connector/phoneping.go @@ -37,6 +37,13 @@ func (wa *WhatsAppClient) FillBridgeState(state status.BridgeState) status.Bridg state.Error = WAPhoneOffline state.UserAction = status.UserActionOpenNative } + rtu := wa.UserLogin.Metadata.(*waid.UserLoginMetadata).ReachoutTimelockUntil + if !rtu.IsZero() { + if state.Info == nil { + state.Info = make(map[string]any) + } + state.Info["reachout_timelock_until"] = rtu + } return state } diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 2b94047..e827955 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -29,7 +29,8 @@ var ResyncLoopInterval = 4 * time.Hour var ResyncJitterSeconds = 3600 func (wa *WhatsAppClient) EnqueueGhostResync(ghost *bridgev2.Ghost) { - if ghost.Metadata.(*waid.GhostMetadata).LastSync.Add(ResyncMinInterval).After(time.Now()) { + lastSync := ghost.Metadata.(*waid.GhostMetadata).LastSync.Time + if lastSync.Add(ResyncMinInterval).After(time.Now()) { return } wa.resyncQueueLock.Lock() @@ -43,6 +44,7 @@ func (wa *WhatsAppClient) EnqueueGhostResync(ghost *bridgev2.Ghost) { wa.UserLogin.Log.Debug(). Stringer("jid", jid). Str("next_resync_in", nextResyncIn). + Time("last_ghost_resync", lastSync). Msg("Enqueued resync for ghost") } wa.resyncQueueLock.Unlock() @@ -50,7 +52,8 @@ func (wa *WhatsAppClient) EnqueueGhostResync(ghost *bridgev2.Ghost) { func (wa *WhatsAppClient) EnqueuePortalResync(portal *bridgev2.Portal, allowDM bool) { jid, _ := waid.ParsePortalID(portal.ID) - if portal.Metadata.(*waid.PortalMetadata).LastSync.Add(ResyncMinInterval).After(time.Now()) { + lastSync := portal.Metadata.(*waid.PortalMetadata).LastSync.Time + if lastSync.Add(ResyncMinInterval).After(time.Now()) { return } else if !allowDM && jid.Server != types.GroupServer { return @@ -61,6 +64,7 @@ func (wa *WhatsAppClient) EnqueuePortalResync(portal *bridgev2.Portal, allowDM b wa.UserLogin.Log.Debug(). Stringer("jid", jid). Stringer("next_resync_in", time.Until(wa.nextResync)). + Time("last_portal_resync", lastSync). Msg("Enqueued resync for portal") } wa.resyncQueueLock.Unlock() diff --git a/pkg/connector/wamsgtype.go b/pkg/connector/wamsgtype.go index 4bcf483..0a29b82 100644 --- a/pkg/connector/wamsgtype.go +++ b/pkg/connector/wamsgtype.go @@ -67,10 +67,11 @@ func getMessageType(waMsg *waE2E.Message) string { return "encrypted comment" case waMsg.CommentMessage != nil: return "comment" - case waMsg.PollCreationMessage != nil || waMsg.PollCreationMessageV2 != nil || waMsg.PollCreationMessageV3 != nil: + case waMsg.PollCreationMessage != nil || waMsg.PollCreationMessageV2 != nil || waMsg.PollCreationMessageV3 != nil || + waMsg.PollCreationMessageV5 != nil || waMsg.PollCreationMessageV6 != nil: return "poll create" - case waMsg.PollCreationMessageV4 != nil || waMsg.PollCreationMessageV5 != nil: - return "poll create (vNext)" + case waMsg.PollCreationMessageV4 != nil: + return "poll create (v4)" case waMsg.PollUpdateMessage != nil: return "poll update" case waMsg.ProtocolMessage != nil: diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 93de90c..49aedf8 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -143,7 +143,7 @@ func (mc *MessageConverter) ToWhatsApp( if err != nil { return nil, nil, fmt.Errorf("failed to parse message ID: %w", err) } - rootMsgInfo := MessageIDToInfo(client, parsedID) + rootMsgInfo := MessageIDToInfo(ctx, client, parsedID) message, err = client.EncryptComment(ctx, rootMsgInfo, message) if err != nil { return nil, nil, fmt.Errorf("failed to encrypt comment: %w", err) diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index f0e4560..e8cb032 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -136,6 +136,7 @@ func (mc *MessageConverter) ToMatrix( waMsg *waE2E.Message, rawWaMsg *waE2E.Message, info *types.MessageInfo, + origSource *types.MessageSource, isViewOnce bool, isBackfill bool, previouslyConvertedPart *bridgev2.ConvertedMessagePart, @@ -175,8 +176,14 @@ func (mc *MessageConverter) ToMatrix( part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV2) case waMsg.PollCreationMessageV3 != nil: part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV3) + //case waMsg.PollCreationMessageV4 != nil: + // part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV4) + case waMsg.PollCreationMessageV5 != nil: + part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV5) + case waMsg.PollCreationMessageV6 != nil: + part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV6) case waMsg.PollUpdateMessage != nil: - part, contextInfo = mc.convertPollUpdateMessage(ctx, info, waMsg.PollUpdateMessage) + part, contextInfo = mc.convertPollUpdateMessage(ctx, info, origSource, waMsg.PollUpdateMessage) case waMsg.EventMessage != nil: part, contextInfo = mc.convertEventMessage(ctx, waMsg.EventMessage) case waMsg.PinInChatMessage != nil: diff --git a/pkg/msgconv/matrixpoll.go b/pkg/msgconv/matrixpoll.go index 0dc3213..b53c22f 100644 --- a/pkg/msgconv/matrixpoll.go +++ b/pkg/msgconv/matrixpoll.go @@ -115,7 +115,7 @@ func (mc *MessageConverter) PollVoteToWhatsApp( zerolog.Ctx(ctx).Err(err).Msg("Failed to parse message ID") return nil, fmt.Errorf("failed to parse message ID") } - pollMsgInfo := MessageIDToInfo(client, parsedMsgID) + pollMsgInfo := MessageIDToInfo(ctx, client, parsedMsgID) pollMsgInfo.Type = "poll" optionHashes := make([][]byte, 0, len(content.Response.Answers)) if pollMsg.Metadata.(*waid.MessageMetadata).IsMatrixPoll { @@ -146,13 +146,23 @@ func (mc *MessageConverter) PollVoteToWhatsApp( return &waE2E.Message{PollUpdateMessage: pollUpdate}, err } -func MessageIDToInfo(client *whatsmeow.Client, parsedMsgID *waid.ParsedMessageID) *types.MessageInfo { +func MessageIDToInfo(ctx context.Context, client *whatsmeow.Client, parsedMsgID *waid.ParsedMessageID) *types.MessageInfo { + chat := parsedMsgID.Chat + sender := parsedMsgID.Sender + if client.Store.LIDMigrationTimestamp != 0 && chat.Server == types.DefaultUserServer { + chatLID, _ := client.Store.LIDs.GetLIDForPN(ctx, chat) + senderLID, _ := client.Store.LIDs.GetLIDForPN(ctx, sender) + if !chatLID.IsEmpty() && !senderLID.IsEmpty() { + chat = chatLID + sender = senderLID + } + } 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, + Chat: chat, + Sender: sender, + IsFromMe: sender.User == client.Store.GetLID().User || sender.User == client.Store.GetJID().User, + IsGroup: chat.Server == types.GroupServer, }, ID: parsedMsgID.ID, } diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index 34c529b..9964800 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -27,6 +27,7 @@ import ( "strings" "github.com/rs/zerolog" + "go.mau.fi/util/exfmt" "go.mau.fi/util/exmime" "go.mau.fi/util/exslices" "go.mau.fi/whatsmeow" @@ -129,10 +130,10 @@ func getMediaIDVersion(msg MediaMessage) []byte { func (mc *MessageConverter) convertAlbumMessage(ctx context.Context, msg *waE2E.AlbumMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { parts := make([]string, 0, 2) if msg.GetExpectedImageCount() > 0 { - parts = append(parts, fmt.Sprintf("%d images", msg.GetExpectedImageCount())) + parts = append(parts, exfmt.Pluralizable("image")(int(msg.GetExpectedImageCount()))) } if msg.GetExpectedVideoCount() > 0 { - parts = append(parts, fmt.Sprintf("%d videos", msg.GetExpectedVideoCount())) + parts = append(parts, exfmt.Pluralizable("video")(int(msg.GetExpectedVideoCount()))) } var partDesc string if len(parts) > 0 { diff --git a/pkg/msgconv/wa-poll.go b/pkg/msgconv/wa-poll.go index 14388be..dfe1a25 100644 --- a/pkg/msgconv/wa-poll.go +++ b/pkg/msgconv/wa-poll.go @@ -170,7 +170,7 @@ var failedPollUpdatePart = &bridgev2.ConvertedMessagePart{ DontBridge: true, } -func (mc *MessageConverter) convertPollUpdateMessage(ctx context.Context, info *types.MessageInfo, msg *waE2E.PollUpdateMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { +func (mc *MessageConverter) convertPollUpdateMessage(ctx context.Context, info *types.MessageInfo, origSource *types.MessageSource, msg *waE2E.PollUpdateMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { log := zerolog.Ctx(ctx) pollMessageID := KeyToMessageID(ctx, getClient(ctx), info.Chat, info.Sender, msg.PollCreationMessageKey) pollMessage, err := mc.Bridge.DB.Message.GetPartByID(ctx, getPortal(ctx).Receiver, pollMessageID, "") @@ -181,8 +181,12 @@ func (mc *MessageConverter) convertPollUpdateMessage(ctx context.Context, info * log.Warn().Str("target_message_id", string(pollMessageID)).Msg("Poll update target message not found") return failedPollUpdatePart, nil } + infoForDecrypt := *info + if origSource != nil { + infoForDecrypt.MessageSource = *origSource + } vote, err := getClient(ctx).DecryptPollVote(ctx, &events.Message{ - Info: *info, + Info: infoForDecrypt, Message: &waE2E.Message{PollUpdateMessage: msg}, }) if err != nil { diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index 59e9a9e..1e54426 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -41,7 +41,8 @@ type UserLoginMetadata struct { AppStateRecoveryAttempted map[appstate.WAPatchName]time.Time `json:"app_state_recovery_attempted,omitempty"` - HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitempty"` + HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitempty"` + ReachoutTimelockUntil time.Time `json:"reachout_timelock_until,omitempty"` MData json.RawMessage `json:"mdata,omitempty"` }