From 0f1832595fcd61b859bf4326f50234c5511eac27 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Wed, 19 Aug 2026 15:41:15 +0100 Subject: [PATCH 01/35] login: split connect and phone pair timeouts, increase to 30s (#947) --- pkg/connector/login.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pkg/connector/login.go b/pkg/connector/login.go index 5da419f..a62b294 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -135,7 +135,8 @@ var ( _ bridgev2.LoginProcessWebAuthn = (*WALogin)(nil) ) -const LoginConnectWait = 15 * time.Second +const LoginConnectWait = 30 * time.Second +const LoginPairPhoneWait = 30 * time.Second func (wl *WALogin) Start(ctx context.Context) (*bridgev2.LoginStep, error) { wl.Main.firstClientConnectOnce.Do(wl.Main.onFirstClientConnect) @@ -193,19 +194,21 @@ func (wl *WALogin) StartWithOverride(ctx context.Context, old *bridgev2.UserLogi } func (wl *WALogin) SubmitUserInput(ctx context.Context, input map[string]string) (*bridgev2.LoginStep, error) { - ctx, cancel := context.WithTimeout(ctx, LoginConnectWait) - defer cancel() err := wl.Client.Connect() if err != nil { wl.Log.Err(err).Msg("Failed to connect to WhatsApp for phone code login") return nil, err } - err = wl.WaitForQRs.Wait(ctx) + connectCtx, cancelConnect := context.WithTimeout(ctx, LoginConnectWait) + err = wl.WaitForQRs.Wait(connectCtx) + cancelConnect() if err != nil { wl.Log.Warn().Err(err).Msg("Timed out waiting for connection") return nil, fmt.Errorf("failed to wait for connection: %w", err) } - pairingCode, err := wl.Client.PairPhone(ctx, input["phone_number"], true, whatsmeow.PairClientChrome, "Chrome (Linux)") + pairCtx, cancelPair := context.WithTimeout(ctx, LoginPairPhoneWait) + defer cancelPair() + pairingCode, err := wl.Client.PairPhone(pairCtx, input["phone_number"], true, whatsmeow.PairClientChrome, "Chrome (Linux)") if err != nil { wl.Log.Err(err).Msg("Failed to request phone code login") if errors.Is(err, whatsmeow.ErrPhoneNumberTooShort) { From 5ef4c47b444da3b219d58bda4b9cb7758bec1acc Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 19 Aug 2026 18:52:38 +0300 Subject: [PATCH 02/35] chatinfo: always allow poll responses in power levels --- pkg/connector/chatinfo.go | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 1dbebcc..1461561 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -284,14 +284,13 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn Ban: ptr.Ptr(nobodyPL), // TODO allow invites if bridge config says to allow them, or maybe if relay mode is enabled? Events: map[event.Type]int{ - event.StateRoomName: metaChangePL, - event.StateRoomAvatar: metaChangePL, - event.StateTopic: metaChangePL, - event.EventReaction: defaultPL, - event.EventRedaction: defaultPL, - + event.StateRoomName: metaChangePL, + event.StateRoomAvatar: metaChangePL, + event.StateTopic: metaChangePL, + event.EventReaction: defaultPL, + event.EventRedaction: defaultPL, + event.EventUnstablePollResponse: defaultPL, event.StateBeeperDisappearingTimer: metaChangePL, - // TODO always allow poll responses }, }, }, @@ -551,12 +550,12 @@ func (wa *WhatsAppClient) wrapNewsletterInfo(ctx context.Context, info *types.Ne StateDefault: ptr.Ptr(nobodyPL), Ban: ptr.Ptr(nobodyPL), Events: map[event.Type]int{ - event.StateRoomName: adminPL, - event.StateRoomAvatar: adminPL, - event.StateTopic: adminPL, - event.EventReaction: defaultPL, - event.EventRedaction: defaultPL, - // TODO always allow poll responses + event.StateRoomName: adminPL, + event.StateRoomAvatar: adminPL, + event.StateTopic: adminPL, + event.EventReaction: defaultPL, + event.EventRedaction: defaultPL, + event.EventUnstablePollResponse: defaultPL, }, }, }, From ba58807b0f42d702af31faa5d1413779dec23ab2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 20 Aug 2026 16:09:02 +0300 Subject: [PATCH 03/35] backfill: clamp message timestamps in history syncs (#946) --- pkg/connector/backfill.go | 43 +++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 7356d11..bb555e6 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -247,12 +247,14 @@ func (wa *WhatsAppClient) handleWAHistorySync( return c.Stringer("chat_jid", jid) }) - var minTime, maxTime, firstItemTime, lastItemTime time.Time - var minTimeIndex, maxTimeIndex int + var firstItemTime, lastItemTime time.Time ignoredTypes := 0 - messages := make([]*wadb.HistorySyncMessageTuple, 0, len(conv.GetMessages())) - for i, rawMsg := range conv.GetMessages() { + rawMessages := conv.GetMessages() + messages := make([]*wadb.HistorySyncMessageTuple, 0, len(rawMessages)) + allowClamp := conv.GetCommentsCount() == 0 + var newerTS uint64 + for i, rawMsg := range rawMessages { // Don't store messages that will just be skipped. msgEvt, err := wa.Client.ParseWebMessage(jid, rawMsg.GetMessage()) if err != nil { @@ -270,20 +272,30 @@ func (wa *WhatsAppClient) handleWAHistorySync( firstItemTime = msgEvt.Info.Timestamp } lastItemTime = msgEvt.Info.Timestamp - if minTime.IsZero() || msgEvt.Info.Timestamp.Before(minTime) { - minTime = msgEvt.Info.Timestamp - minTimeIndex = i - } - if maxTime.IsZero() || msgEvt.Info.Timestamp.After(maxTime) { - maxTime = msgEvt.Info.Timestamp - maxTimeIndex = i - } msgType := getMessageType(msgEvt.Message) if msgType == "ignore" || strings.HasPrefix(msgType, "unknown_protocol_") { ignoredTypes++ continue } + // Comments (replies) in announcement groups are not ordered by timestamp, so don't clamp them. + if rawMsg.GetMessage().GetCommentMetadata().GetCommentParentKey() != nil { + allowClamp = false + } + // WhatsApp has bugs where some random messages will have timestamps decades in the future. + // To ensure they don't mess up our ordering, require timestamps of older messages to be + // before the previous (newer) message. + if currentTS := rawMsg.GetMessage().GetMessageTimestamp(); newerTS > 0 && allowClamp && currentTS > newerTS { + log.Warn(). + Time("current_ts", time.Unix(int64(currentTS), 0)). + Time("prev_ts", time.Unix(int64(newerTS), 0)). + Int("msg_index", i). + Str("msg_id", rawMsg.GetMessage().GetKey().GetID()). + Msg("Clamping message timestamp") + rawMsg.Message.MessageTimestamp = ptr.Ptr(newerTS) + msgEvt.Info.Timestamp = time.Unix(int64(newerTS), 0) + } + newerTS = rawMsg.GetMessage().GetMessageTimestamp() marshaled, err := proto.Marshal(rawMsg) if err != nil { log.Warn().Err(err). @@ -298,13 +310,8 @@ func (wa *WhatsAppClient) handleWAHistorySync( log.Debug(). Int("wrapped_count", len(messages)). Int("ignored_msg_type_count", ignoredTypes). - Time("lowest_time", minTime). - Int("lowest_time_index", minTimeIndex). - Time("highest_time", maxTime). - Int("highest_time_index", maxTimeIndex). Time("first_item_time", firstItemTime). Time("last_item_time", lastItemTime). - Bool("highest_time_mismatch", firstItemTime != maxTime). Dict("metadata", zerolog.Dict(). Uint32("ephemeral_expiration", conv.GetEphemeralExpiration()). Int64("ephemeral_setting_timestamp", conv.GetEphemeralSettingTimestamp()). @@ -320,7 +327,7 @@ func (wa *WhatsAppClient) handleWAHistorySync( Msg("Collected messages to save from history sync conversation") if len(messages) > 0 { - err = wa.Main.DB.Conversation.Put(ctx, wadb.NewConversation(wa.UserLogin.ID, jid, conv, maxTime)) + err = wa.Main.DB.Conversation.Put(ctx, wadb.NewConversation(wa.UserLogin.ID, jid, conv, firstItemTime)) if err != nil { if stopOnError { return fmt.Errorf("failed to save conversation metadata for %s: %w", jid, err) From bbc9a37b30467c6b7e79def8a5d061e5c5ec0dd1 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 20 Aug 2026 18:37:07 +0300 Subject: [PATCH 04/35] dependencies: bump minimum Go version to 1.26 --- .github/workflows/go.yml | 8 ++++---- go.mod | 8 ++++---- go.sum | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 343443b..c9fef73 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -11,14 +11,14 @@ jobs: strategy: fail-fast: false matrix: - go-version: ["1.25", "1.26"] - name: Lint ${{ matrix.go-version == '1.26' && '(latest)' || '(old)' }} + go-version: ["1.26", "1.27"] + name: Lint ${{ matrix.go-version == '1.27' && '(latest)' || '(old)' }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up Go - uses: actions/setup-go@v6 + uses: actions/setup-go@v7 with: go-version: ${{ matrix.go-version }} cache: true diff --git a/go.mod b/go.mod index ad3d58a..1d4c99f 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module go.mau.fi/mautrix-whatsapp -go 1.25.0 +go 1.26.0 -toolchain go1.26.6 +toolchain go1.27.0 tool go.mau.fi/util/cmd/maubuild @@ -10,7 +10,7 @@ require ( github.com/lib/pq v1.12.3 github.com/rs/zerolog v1.35.1 github.com/tidwall/gjson v1.19.0 - go.mau.fi/util v0.10.0 + go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde go.mau.fi/webp v0.3.0 go.mau.fi/whatsmeow v0.0.0-20260816113502-fb386f152837 golang.org/x/image v0.45.0 @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.22.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.30.0 + maunium.net/go/mautrix v0.30.1-0.20260820142923-ae00f2c62e1e ) require ( diff --git a/go.sum b/go.sum index 62f54b2..9a666fc 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,8 @@ github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA= github.com/yuin/goldmark v1.8.5/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.2 h1:QV+XdzQkm3x3aSG7FcqfGSZuFXz83pRZPBFaPygHbOU= go.mau.fi/libsignal v0.2.2/go.mod h1:CRlIQg2J8uYTfDFvNoO8/KcZjs5cey0vbc6oj/bssY0= -go.mau.fi/util v0.10.0 h1:vH9IXZmfBKa96p47HxrVqEPkrj02zDJg3o4EF172+Lk= -go.mau.fi/util v0.10.0/go.mod h1:uZwpm9sK4wO2Qqy+t6QoVq29szMsRxWXp9/BkQLG4xk= +go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbsxj7G6mfujjKei1HaFQM= +go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= 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-20260816113502-fb386f152837 h1:xJ13dqFcK/oPNImltlk7sumI/uiQ9EON/6g/iwpT1Nc= @@ -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.30.0 h1:bad+q7w5tLqiHpr+oUxVI+8m8ePbV3AvoFKg2jQzPyo= -maunium.net/go/mautrix v0.30.0/go.mod h1:bb0gjxbTFOqTaAYKGw5E7j9XROUR2Sl1Etm3IbmYXbo= +maunium.net/go/mautrix v0.30.1-0.20260820142923-ae00f2c62e1e h1:ATvERyBIUXJrnL7ezP2xCLLGCNmGClmfzoQq4N3jBcA= +maunium.net/go/mautrix v0.30.1-0.20260820142923-ae00f2c62e1e/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= From d70eb6b135a53a6843f17f42b1d909dc061ff6e3 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 21 Aug 2026 17:25:12 +0300 Subject: [PATCH 05/35] dependencies: update whatsmeow --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1d4c99f..f7a6451 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260816113502-fb386f152837 + go.mau.fi/whatsmeow v0.0.0-20260821141805-33cfac511629 golang.org/x/image v0.45.0 golang.org/x/net v0.58.0 golang.org/x/sync v0.22.0 diff --git a/go.sum b/go.sum index 9a666fc..1adbb52 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbs go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= 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-20260816113502-fb386f152837 h1:xJ13dqFcK/oPNImltlk7sumI/uiQ9EON/6g/iwpT1Nc= -go.mau.fi/whatsmeow v0.0.0-20260816113502-fb386f152837/go.mod h1:UFP0D7aj+biuFejsllcoNiHTQorh0ODFUyG4Txpqfks= +go.mau.fi/whatsmeow v0.0.0-20260821141805-33cfac511629 h1:99p9fTS1G73a1aifCVaV5MX4hDq0lVUKWGoUNoXyE3A= +go.mau.fi/whatsmeow v0.0.0-20260821141805-33cfac511629/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= 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.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= From 03eb3900826932fa4c661997df03bceb233cf6e2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 24 Aug 2026 13:28:23 +0300 Subject: [PATCH 06/35] backfill: return no more messages flag if there's no anchor message --- pkg/connector/backfill.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index bb555e6..cab460f 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -532,6 +532,7 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet return nil, fmt.Errorf("failed to load messages from database: %w", err) } else if len(messages) == 0 || (len(messages) == 1 && anchorID != "" && messages[0].GetKey().GetID() == anchorID) { wa.deleteHistorySyncMessages(ctx, portalJID, 0, 0) + hasMore = hasMore && params.AnchorMessage != nil if hasMore && !params.AllowSlowFetch { return &bridgev2.FetchMessagesResponse{ MoreRequiresSlowFetch: true, From e1220a8d4eb80dae42e01c8e9fcabe22aeae23fc Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 24 Aug 2026 13:30:35 +0300 Subject: [PATCH 07/35] handlewhatsapp: delete history sync data on logout --- pkg/connector/client.go | 4 ++++ pkg/connector/handlewhatsapp.go | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index b3ecdba..f0a5947 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -393,6 +393,10 @@ func (wa *WhatsAppClient) LogoutRemote(ctx context.Context) { } wa.Disconnect() wa.Client = nil + err := wa.Main.DB.Conversation.DeleteAll(ctx, wa.UserLogin.ID) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to delete history sync data on logout") + } } func (wa *WhatsAppClient) IsLoggedIn() bool { diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 509c589..130c275 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -202,7 +202,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected}) wa.notifyOfflineSyncWaiter(nil) case *events.LoggedOut: - wa.handleWALogout(evt.Reason, evt.OnConnect) + wa.handleWALogout(ctx, evt.Reason, evt.OnConnect) wa.notifyOfflineSyncWaiter(fmt.Errorf("logged out: %s", evt.Reason)) case *events.Disconnected: // Don't send the normal transient disconnect state if we're already in a different transient disconnect state. @@ -528,7 +528,7 @@ func (wa *WhatsAppClient) handleWAChatPresence(ctx context.Context, evt *events. }) } -func (wa *WhatsAppClient) handleWALogout(reason events.ConnectFailureReason, onConnect bool) { +func (wa *WhatsAppClient) handleWALogout(ctx context.Context, reason events.ConnectFailureReason, onConnect bool) { errorCode := WAUnknownLogout if reason == events.ConnectFailureLoggedOut { errorCode = WALoggedOut @@ -540,6 +540,10 @@ func (wa *WhatsAppClient) handleWALogout(reason events.ConnectFailureReason, onC wa.JID = types.EmptyJID wa.LID = types.EmptyJID wa.UserLogin.Metadata.(*waid.UserLoginMetadata).WADeviceID = 0 + err := wa.Main.DB.Conversation.DeleteAll(ctx, wa.UserLogin.ID) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to delete history sync data on logout") + } wa.UserLogin.BridgeState.Send(status.BridgeState{ StateEvent: status.StateBadCredentials, Error: errorCode, From 39bc900ac930a060041bb819ecda0ffd8e502189 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Mon, 24 Aug 2026 11:33:43 +0100 Subject: [PATCH 08/35] dependencies: update mautrix-go --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index f7a6451..db7c17f 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.22.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.30.1-0.20260820142923-ae00f2c62e1e + maunium.net/go/mautrix v0.30.1-0.20260822101838-bdb58f2e6c0a ) require ( diff --git a/go.sum b/go.sum index 1adbb52..6eca722 100644 --- a/go.sum +++ b/go.sum @@ -109,3 +109,5 @@ 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.30.1-0.20260820142923-ae00f2c62e1e h1:ATvERyBIUXJrnL7ezP2xCLLGCNmGClmfzoQq4N3jBcA= maunium.net/go/mautrix v0.30.1-0.20260820142923-ae00f2c62e1e/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= +maunium.net/go/mautrix v0.30.1-0.20260822101838-bdb58f2e6c0a h1:BYeNDQQLR0edhy6Smq41T3QBNWn6FhrNtRG8CZTuUV4= +maunium.net/go/mautrix v0.30.1-0.20260822101838-bdb58f2e6c0a/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= From 6f767fb951fd0190c754d045c5d879a1f574a7a2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 24 Aug 2026 16:46:43 +0300 Subject: [PATCH 09/35] dependencies: fix go mod tidy --- go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.sum b/go.sum index 6eca722..f97f0a2 100644 --- a/go.sum +++ b/go.sum @@ -107,7 +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.30.1-0.20260820142923-ae00f2c62e1e h1:ATvERyBIUXJrnL7ezP2xCLLGCNmGClmfzoQq4N3jBcA= -maunium.net/go/mautrix v0.30.1-0.20260820142923-ae00f2c62e1e/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= maunium.net/go/mautrix v0.30.1-0.20260822101838-bdb58f2e6c0a h1:BYeNDQQLR0edhy6Smq41T3QBNWn6FhrNtRG8CZTuUV4= maunium.net/go/mautrix v0.30.1-0.20260822101838-bdb58f2e6c0a/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= From c4aabf0b1a819d7e8a5555ea62e12a0914b4253f Mon Sep 17 00:00:00 2001 From: Kishan Bagaria <1093313+KishanBagaria@users.noreply.github.com> Date: Mon, 24 Aug 2026 06:51:06 -0700 Subject: [PATCH 10/35] handlewhatsapp: fix nested HD media caption edits (#948) --- pkg/connector/handlewhatsapp.go | 35 ++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 130c275..5508334 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -27,6 +27,7 @@ import ( "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/appstate" + "go.mau.fi/whatsmeow/proto/waCommon" "go.mau.fi/whatsmeow/proto/waE2E" "go.mau.fi/whatsmeow/types" "go.mau.fi/whatsmeow/types/events" @@ -341,18 +342,8 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa 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() - protocolMsg := evt.Message.GetProtocolMessage() - if protocolMsg.GetType() != waE2E.ProtocolMessage_MESSAGE_EDIT || protocolMsg.GetKey() == nil { - protocolMsg = &waE2E.ProtocolMessage{ - Type: waE2E.ProtocolMessage_MESSAGE_EDIT.Enum(), - Key: parentKey, - EditedMessage: evt.Message.GetAssociatedChildMessage().GetMessage(), - } - dontRenderEdited = true - } else if child := protocolMsg.GetEditedMessage().GetAssociatedChildMessage().GetMessage(); child != nil { - protocolMsg.EditedMessage = child - protocolMsg.Key = parentKey - } + protocolMsg, shouldHideEdit := makeHDMediaReplacementEdit(evt.Message, parentKey) + dontRenderEdited = shouldHideEdit wa.UserLogin.Log.Debug(). Str("message_id", evt.Info.ID). Str("parent_id", parentKey.GetID()). @@ -386,6 +377,26 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa return res.Success } +func makeHDMediaReplacementEdit(message *waE2E.Message, parentKey *waCommon.MessageKey) (*waE2E.ProtocolMessage, bool) { + protocolMsg := message.GetProtocolMessage() + associatedMessage := message.GetAssociatedChildMessage().GetMessage() + if protocolMsg.GetType() != waE2E.ProtocolMessage_MESSAGE_EDIT || protocolMsg.GetKey() == nil { + protocolMsg = associatedMessage.GetProtocolMessage() + } + if protocolMsg.GetType() == waE2E.ProtocolMessage_MESSAGE_EDIT && protocolMsg.GetKey() != nil { + if child := protocolMsg.GetEditedMessage().GetAssociatedChildMessage().GetMessage(); child != nil { + protocolMsg.EditedMessage = child + } + protocolMsg.Key = parentKey + return protocolMsg, false + } + return &waE2E.ProtocolMessage{ + Type: waE2E.ProtocolMessage_MESSAGE_EDIT.Enum(), + Key: parentKey, + EditedMessage: associatedMessage, + }, true +} + func (wa *WhatsAppClient) handleWAUndecryptableMessage(ctx context.Context, evt *events.UndecryptableMessage) bool { if !wa.ensureAltJIDs(ctx, &evt.Info.MessageSource, true) { return false From 508b35e1ae2533605cb3ba39bfd9f131ce652bb0 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Tue, 25 Aug 2026 12:33:30 +0100 Subject: [PATCH 11/35] directmedia: mark media retry decrypt failures (#939) Co-authored-by: Nick Mills-Barrett --- pkg/connector/directmedia.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index 8a7afd9..a875fab 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -255,10 +255,11 @@ func (wa *WhatsAppConnector) makeDirectMediaResponse( type directMediaRetry struct { sync.Mutex - resultURL string - wait *exsync.Event - requested bool - resultType waMmsRetry.MediaRetryNotification_ResultType + resultURL string + wait *exsync.Event + requested bool + resultType waMmsRetry.MediaRetryNotification_ResultType + decryptFail bool } func (wa *WhatsAppClient) getDirectMediaRetryState(msgID networkid.MessageID, create bool) *directMediaRetry { @@ -285,6 +286,9 @@ func (wa *WhatsAppClient) requestAndWaitDirectMedia(ctx context.Context, rawMsgI keys.DirectPath = state.resultURL return nil } + if state.decryptFail { + return mautrix.MNotFound.WithMessage("Unable to retrieve media: failed to decrypt the media retry notification from your phone.") + } switch state.resultType { case waMmsRetry.MediaRetryNotification_NOT_FOUND: return mautrix.MNotFound.WithMessage("This media was not found on your phone.") @@ -338,6 +342,9 @@ func (wa *WhatsAppClient) receiveDirectMediaRetry(ctx context.Context, msg *data retryData, err := whatsmeow.DecryptMediaRetryNotification(retry, keys.Key) if err != nil { log.Warn().Err(err).Msg("Failed to decrypt media retry notification") + if state != nil { + state.decryptFail = true + } return } if state != nil { From 4973412350b2b3231295c4b685a1e6ec8f45e961 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 25 Aug 2026 17:30:08 +0300 Subject: [PATCH 12/35] dependencies: update mautrix-go --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index db7c17f..6be8397 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.22.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.30.1-0.20260822101838-bdb58f2e6c0a + maunium.net/go/mautrix v0.30.1-0.20260825141151-92fa7b2e4545 ) require ( diff --git a/go.sum b/go.sum index f97f0a2..5b26c4f 100644 --- a/go.sum +++ b/go.sum @@ -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.30.1-0.20260822101838-bdb58f2e6c0a h1:BYeNDQQLR0edhy6Smq41T3QBNWn6FhrNtRG8CZTuUV4= -maunium.net/go/mautrix v0.30.1-0.20260822101838-bdb58f2e6c0a/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= +maunium.net/go/mautrix v0.30.1-0.20260825141151-92fa7b2e4545 h1:cVpn8+sWu96w/lGLl6pO9t7hcuEouXbIQiOtnH58JwI= +maunium.net/go/mautrix v0.30.1-0.20260825141151-92fa7b2e4545/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= From e59633c99192bcac117e830d83363b10826a1eb6 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 25 Aug 2026 23:56:48 +0300 Subject: [PATCH 13/35] handlewhatsapp: fix handling newsletter read receipts --- pkg/connector/handlewhatsapp.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 5508334..4ca2217 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -24,6 +24,7 @@ import ( "time" "github.com/rs/zerolog" + "go.mau.fi/util/exslices" "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/appstate" @@ -476,6 +477,8 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei messageSender := wa.GetLID() if !evt.MessageSender.IsEmpty() { messageSender = evt.MessageSender + } else if evt.Chat.Server == types.NewsletterServer { + messageSender = evt.Chat } var chatAlt types.JID if evt.Chat.Server == types.DefaultUserServer { @@ -494,6 +497,8 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei senderLID := evt.Sender if senderLID.Server == types.DefaultUserServer && !evt.SenderAlt.IsEmpty() { senderLID = evt.SenderAlt + } else if evt.Chat.Server == types.NewsletterServer && evt.Type == types.ReceiptTypeReadSelf { + senderLID = wa.GetLID() } res := wa.UserLogin.QueueRemoteEvent(&simplevent.Receipt{ EventMeta: simplevent.EventMeta{ @@ -501,6 +506,11 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei PortalKey: wa.makeWAPortalKey(evt.Chat), Sender: wa.makeEventSender(ctx, senderLID), Timestamp: evt.Timestamp, + LogContext: func(c zerolog.Context) zerolog.Context { + return c. + Strs("targets", exslices.CastToString[string](targets)). + Stringer("receipt_sender", senderLID) + }, }, Targets: targets, }) From a7bf2c89f8691d8a4ab1851cdc7b49d063c4bdd4 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 29 Aug 2026 01:44:57 +0300 Subject: [PATCH 14/35] dependencies: update --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 6be8397..9a26127 100644 --- a/go.mod +++ b/go.mod @@ -12,13 +12,13 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260821141805-33cfac511629 + go.mau.fi/whatsmeow v0.0.0-20260828224850-0fadda796019 golang.org/x/image v0.45.0 golang.org/x/net v0.58.0 golang.org/x/sync v0.22.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.30.1-0.20260825141151-92fa7b2e4545 + maunium.net/go/mautrix v0.30.1-0.20260828224424-c22bd4fdc9e6 ) require ( @@ -31,8 +31,8 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.49 // indirect - github.com/petermattis/goid v0.0.0-20260816044145-ed329add6b1b // indirect + github.com/mattn/go-sqlite3 v1.14.50 // indirect + github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/rs/xid v1.6.0 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect @@ -44,7 +44,7 @@ require ( go.mau.fi/libsignal v0.2.2 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect golang.org/x/crypto v0.55.0 // indirect - golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297 // indirect + golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa // indirect golang.org/x/mod v0.40.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.41.0 // indirect diff --git a/go.sum b/go.sum index 5b26c4f..e5827da 100644 --- a/go.sum +++ b/go.sum @@ -34,10 +34,10 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.49 h1:B8jBHC3xhxZgxztrgruTuLucebnULQnx4W7cF7SAE9w= -github.com/mattn/go-sqlite3 v1.14.49/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= -github.com/petermattis/goid v0.0.0-20260816044145-ed329add6b1b h1:sS7HLzwS+dO+gxATgQfeZDEdUZe2pKAB3nGoUwP5zU0= -github.com/petermattis/goid v0.0.0-20260816044145-ed329add6b1b/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/mattn/go-sqlite3 v1.14.50 h1:dmdFvo1XG4MPzA4IkAmE9upVz/Nj31uRoM5+jC8hYbY= +github.com/mattn/go-sqlite3 v1.14.50/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= +github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 h1:lcWAnrqr2nNfDiArwFNHCE4787Mw2tCdVSOXCru0/0E= +github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -75,14 +75,14 @@ go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbs go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= 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-20260821141805-33cfac511629 h1:99p9fTS1G73a1aifCVaV5MX4hDq0lVUKWGoUNoXyE3A= -go.mau.fi/whatsmeow v0.0.0-20260821141805-33cfac511629/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= +go.mau.fi/whatsmeow v0.0.0-20260828224850-0fadda796019 h1:vUX4rgZhobwtMYH6tkYOSyfd2ufTysNEDWVpTSVPjmc= +go.mau.fi/whatsmeow v0.0.0-20260828224850-0fadda796019/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= 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.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= -golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297 h1:YXnL44eJ77R+ji4/ooy8UsXIhz+lbi2Qgdlc8iRN0gY= -golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297/go.mod h1:Mkmymgv+uMpSQ/XxJ/7GpdrdYoqm3u72jEbpCLiJmNk= +golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa h1:QSyA8ishJCyT21kER9KwNt0b7BM3iRK4x9QXhjN5Fdk= +golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0= golang.org/x/image v0.45.0/go.mod h1:n62x/7RqlwXDvGsSU4u6IUTUf6KghUZ9Bt7cG/T9Fx4= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= @@ -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.30.1-0.20260825141151-92fa7b2e4545 h1:cVpn8+sWu96w/lGLl6pO9t7hcuEouXbIQiOtnH58JwI= -maunium.net/go/mautrix v0.30.1-0.20260825141151-92fa7b2e4545/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= +maunium.net/go/mautrix v0.30.1-0.20260828224424-c22bd4fdc9e6 h1:mL1BquGn+7efD4ECX62uHVN2MnsrC65JuOgxDdKFF7o= +maunium.net/go/mautrix v0.30.1-0.20260828224424-c22bd4fdc9e6/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= From d1dbe393b6875a1997a8b4f6f1dd54b33ad7fec9 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 3 Sep 2026 14:17:20 +0300 Subject: [PATCH 15/35] dependencies: update whatsmeow --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9a26127..288eb6f 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260828224850-0fadda796019 + go.mau.fi/whatsmeow v0.0.0-20260903111606-de26b4ab6499 golang.org/x/image v0.45.0 golang.org/x/net v0.58.0 golang.org/x/sync v0.22.0 diff --git a/go.sum b/go.sum index e5827da..e5d6221 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbs go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= 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-20260828224850-0fadda796019 h1:vUX4rgZhobwtMYH6tkYOSyfd2ufTysNEDWVpTSVPjmc= -go.mau.fi/whatsmeow v0.0.0-20260828224850-0fadda796019/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= +go.mau.fi/whatsmeow v0.0.0-20260903111606-de26b4ab6499 h1:7RG3BeC9qXh2aQ0RwjIFZxhCwTohdtNzJaKHxna32pE= +go.mau.fi/whatsmeow v0.0.0-20260903111606-de26b4ab6499/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= 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.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= From 51504db3098ec1c7b81c314f01a7d0458ac309e0 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 3 Sep 2026 02:17:28 +0300 Subject: [PATCH 16/35] chatinfo: add option to limit eager member sync in groups --- pkg/connector/chatinfo.go | 6 +++++- pkg/connector/config.go | 2 ++ pkg/connector/example-config.yaml | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 1461561..675a5ce 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -271,11 +271,12 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn setAddressingMode(info.AddressingMode), setTopicID(info.TopicID, info.Topic), ) + syncAllMembers := wa.Main.Config.MaxMemberSync < 0 || len(info.Participants) < wa.Main.Config.MaxMemberSync wrapped := &bridgev2.ChatInfo{ Name: ptr.Ptr(info.Name), Topic: ptr.Ptr(info.Topic), Members: &bridgev2.ChatMemberList{ - IsFull: !info.IsIncognito && !info.IsParent, + IsFull: !info.IsIncognito && !info.IsParent && syncAllMembers, TotalMemberCount: len(info.Participants), MemberMap: make(map[networkid.UserID]bridgev2.ChatMember, len(info.Participants)), PowerLevels: &bridgev2.PowerLevelOverrides{ @@ -315,6 +316,9 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn } else if pcp.IsAdmin { member.PowerLevel = ptr.Ptr(adminPL) } else { + if !syncAllMembers && !member.EventSender.IsFromMe { + continue + } member.PowerLevel = ptr.Ptr(defaultPL) } member.MemberEventExtra = map[string]any{ diff --git a/pkg/connector/config.go b/pkg/connector/config.go index e51c80a..d069598 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -52,6 +52,7 @@ type Config struct { DirectMediaAutoRequest bool `yaml:"direct_media_auto_request"` InitialAutoReconnect bool `yaml:"initial_auto_reconnect"` UseWhatsAppRetryStore bool `yaml:"use_whatsapp_retry_store"` + MaxMemberSync int `yaml:"max_member_sync"` AnimatedSticker msgconv.AnimatedStickerConfig `yaml:"animated_sticker"` @@ -129,6 +130,7 @@ func upgradeConfig(helper up.Helper) { helper.Copy(up.Bool, "direct_media_auto_request") helper.Copy(up.Bool, "initial_auto_reconnect") helper.Copy(up.Bool, "use_whatsapp_retry_store") + helper.Copy(up.Int, "max_member_sync") helper.Copy(up.Str, "animated_sticker", "target") helper.Copy(up.Int, "animated_sticker", "args", "width") diff --git a/pkg/connector/example-config.yaml b/pkg/connector/example-config.yaml index 564f25e..d277383 100644 --- a/pkg/connector/example-config.yaml +++ b/pkg/connector/example-config.yaml @@ -69,6 +69,9 @@ initial_auto_reconnect: true # the bridge? By default, the bridge only stores messages in memory, and therefore can't accept # retry receipts if the bridge is restarted after the message is sent. use_whatsapp_retry_store: false +# Maximum size of groups to sync members eagerly in. Defaults to unlimited. +# If set, groups with more than this number of members will not have non-admin participants synced until they talk. +max_member_sync: -1 # Settings for converting animated stickers. animated_sticker: From 6302a1c709e48aaca3cd6b272f3ca4a6ea2cd080 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 3 Sep 2026 14:29:17 +0300 Subject: [PATCH 17/35] userinfo: add option to lazily fetch avatars using direct media --- pkg/connector/config.go | 3 ++ pkg/connector/connector.go | 3 ++ pkg/connector/directmedia.go | 21 ++++++++++++-- pkg/connector/example-config.yaml | 3 ++ pkg/connector/handlewhatsapp.go | 2 +- pkg/connector/startchat.go | 8 +++--- pkg/connector/userinfo.go | 46 ++++++++++++++++++++++++++----- pkg/connector/wadb/avatarcache.go | 14 ++++++++++ pkg/waid/dbmeta.go | 22 ++++++++------- pkg/waid/mediaid.go | 11 ++++++++ 10 files changed, 108 insertions(+), 25 deletions(-) diff --git a/pkg/connector/config.go b/pkg/connector/config.go index d069598..24874a2 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -53,6 +53,7 @@ type Config struct { InitialAutoReconnect bool `yaml:"initial_auto_reconnect"` UseWhatsAppRetryStore bool `yaml:"use_whatsapp_retry_store"` MaxMemberSync int `yaml:"max_member_sync"` + LazyAvatars bool `yaml:"lazy_avatars"` AnimatedSticker msgconv.AnimatedStickerConfig `yaml:"animated_sticker"` @@ -131,6 +132,7 @@ func upgradeConfig(helper up.Helper) { helper.Copy(up.Bool, "initial_auto_reconnect") helper.Copy(up.Bool, "use_whatsapp_retry_store") helper.Copy(up.Int, "max_member_sync") + helper.Copy(up.Bool, "lazy_avatars") helper.Copy(up.Str, "animated_sticker", "target") helper.Copy(up.Int, "animated_sticker", "args", "width") @@ -207,6 +209,7 @@ func (wa *WhatsAppConnector) GetConfig() (string, any, up.Upgrader) { {"proxy"}, {"displayname_template"}, {"call_start_notices"}, + {"animated_sticker"}, {"history_sync"}, }, Base: ExampleConfig, diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index e262c82..193dc4d 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -142,6 +142,9 @@ func (wa *WhatsAppConnector) Init(bridge *bridgev2.Bridge) { } func (wa *WhatsAppConnector) Start(ctx context.Context) error { + if !wa.MsgConv.DirectMedia && wa.Config.LazyAvatars { + return fmt.Errorf("lazy_avatars set without enabling global direct_media") + } err := wa.DeviceStore.Upgrade(ctx) if err != nil { return bridgev2.DBUpgradeError{Err: err, Section: "whatsmeow"} diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index a875fab..092ce3e 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -30,6 +30,7 @@ import ( "github.com/rs/zerolog" "go.mau.fi/util/exsync" + "go.mau.fi/util/jsontime" "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waMmsRetry" @@ -88,9 +89,9 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars if err != nil { return nil, fmt.Errorf("failed to get avatar cache entry: %w", err) } - if cachedInfo != nil && cachedInfo.Gone { + if cachedInfo.IsGone() { return nil, mautrix.MNotFound.WithMessage("Avatar is no longer available (cached response)") - } else if cachedInfo == nil || cachedInfo.Expiry.Time.Before(time.Now().Add(5*time.Minute)) { + } else if cachedInfo.Expired() { zerolog.Ctx(ctx).Debug(). Str("avatar_id", parsedID.Avatar.AvatarID). Msg("Refreshing avatar URL from WhatsApp servers") @@ -99,7 +100,7 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars }) if errors.Is(err, whatsmeow.ErrProfilePictureNotSet) || errors.Is(err, whatsmeow.ErrProfilePictureUnauthorized) || - (err == nil && (avatar == nil || avatar.ID != parsedID.Avatar.AvatarID)) { + (err == nil && (avatar == nil || (avatar.ID != parsedID.Avatar.AvatarID && !parsedID.Avatar.IsRandom()))) { zerolog.Ctx(ctx).Debug(). Err(err). Stringer("target_jid", parsedID.Avatar.TargetJID). @@ -107,9 +108,14 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars Str("wanted_avatar_id", parsedID.Avatar.AvatarID). Str("got_avatar_id", ptr.Val(avatar).ID). Msg("Avatar is no longer available") + var goneExpiry jsontime.Unix + if parsedID.Avatar.IsRandom() { + goneExpiry = jsontime.U(time.Now().Add(7 * 24 * time.Hour)) + } err = wa.DB.AvatarCache.Put(ctx, &wadb.AvatarCacheEntry{ EntityJID: parsedID.Avatar.TargetJID, AvatarID: parsedID.Avatar.AvatarID, + Expiry: goneExpiry, Gone: true, }) if err != nil { @@ -128,6 +134,15 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars Str("avatar_id", avatar.ID). Msg("Failed to update avatar cache entry") } + if cachedInfo.AvatarID != parsedID.Avatar.AvatarID { + cachedInfo.AvatarID = parsedID.Avatar.AvatarID + err = wa.DB.AvatarCache.Put(ctx, cachedInfo) + if err != nil { + zerolog.Ctx(ctx).Warn().Err(err). + Str("avatar_id", parsedID.Avatar.AvatarID). + Msg("Failed to update avatar cache entry") + } + } } return &mediaproxy.GetMediaResponseFile{ Callback: func(w *os.File) (*mediaproxy.FileMeta, error) { diff --git a/pkg/connector/example-config.yaml b/pkg/connector/example-config.yaml index d277383..905e6d6 100644 --- a/pkg/connector/example-config.yaml +++ b/pkg/connector/example-config.yaml @@ -72,6 +72,9 @@ use_whatsapp_retry_store: false # Maximum size of groups to sync members eagerly in. Defaults to unlimited. # If set, groups with more than this number of members will not have non-admin participants synced until they talk. max_member_sync: -1 +# Lazily load avatars using direct media? +# Global direct media must be enabled for this. +lazy_avatars: false # Settings for converting animated stickers. animated_sticker: diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 4ca2217..8a758eb 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -729,7 +729,7 @@ func (wa *WhatsAppClient) syncGhost(jid types.JID, reason string, pictureID *str if pictureID != nil && *pictureID != "" && ghost.AvatarID == networkid.AvatarID(*pictureID) { return } - userInfo, err := wa.getUserInfo(ctx, jid, pictureID != nil) + userInfo, err := wa.getUserInfo(ctx, jid, ptr.Val(pictureID), pictureID != nil) if err != nil { log.Err(err).Msg("Failed to get user info") } else { diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index c123311..2ba1e09 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -64,12 +64,12 @@ func looksEmaily(str string) bool { return false } -type cacheEntry struct { +type isOnWhatsappCacheEntry struct { jid types.JID ts time.Time } -var isOnWhatsappCache = exsync.NewMap[string, cacheEntry]() +var isOnWhatsappCache = exsync.NewMap[string, isOnWhatsappCacheEntry]() func (wa *WhatsAppClient) validateIdentifer(ctx context.Context, number string) (types.JID, error) { if strings.HasSuffix(number, "@"+types.BotServer) || strings.HasSuffix(number, "@"+types.HiddenUserServer) { @@ -94,7 +94,7 @@ func (wa *WhatsAppClient) validateIdentifer(ctx context.Context, number string) } else if !resp[0].IsIn { return types.EmptyJID, bridgev2.WrapRespErr(fmt.Errorf("the server said +%s is not on WhatsApp", resp[0].JID.User), mautrix.MNotFound) } else { - isOnWhatsappCache.Set(number, cacheEntry{resp[0].JID, time.Now()}) + isOnWhatsappCache.Set(number, isOnWhatsappCacheEntry{resp[0].JID, time.Now()}) return resp[0].JID, nil } } @@ -238,7 +238,7 @@ func (wa *WhatsAppClient) getContactList(ctx context.Context, filter string, onl resp = append(resp, &bridgev2.ResolveIdentifierResponse{ Ghost: ghost, UserID: waid.MakeUserID(jid), - UserInfo: wa.contactToUserInfo(ctx, jid, contactInfo, false), + UserInfo: wa.contactToUserInfo(ctx, jid, contactInfo, "", false), Chat: chatResp, }) } diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index e827955..05d6c40 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -165,7 +165,7 @@ func (wa *WhatsAppClient) doGhostResync(ctx context.Context, queue map[types.JID log.Warn().Stringer("jid", jid).Msg("Didn't get info for puppet in background sync") continue } - userInfo, err := wa.getUserInfo(ctx, jid, info.PictureID != "" && string(ghost.AvatarID) != info.PictureID) + userInfo, err := wa.getUserInfo(ctx, jid, info.PictureID, info.PictureID != "" && string(ghost.AvatarID) != info.PictureID) if err != nil { log.Err(err).Stringer("jid", jid).Msg("Failed to get user info for puppet in background sync") continue @@ -181,18 +181,18 @@ func (wa *WhatsAppClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost return nil, nil } jid := waid.ParseUserID(ghost.ID) - return wa.getUserInfo(ctx, jid, ghost.AvatarID == "") + return wa.getUserInfo(ctx, jid, "", ghost.AvatarID == "") } -func (wa *WhatsAppClient) getUserInfo(ctx context.Context, jid types.JID, fetchAvatar bool) (*bridgev2.UserInfo, error) { +func (wa *WhatsAppClient) getUserInfo(ctx context.Context, jid types.JID, avatarID string, fetchAvatar bool) (*bridgev2.UserInfo, error) { contact, err := wa.GetStore().Contacts.GetContact(ctx, jid) if err != nil { return nil, err } - return wa.contactToUserInfo(ctx, jid, contact, fetchAvatar), nil + return wa.contactToUserInfo(ctx, jid, contact, avatarID, fetchAvatar), nil } -func (wa *WhatsAppClient) contactToUserInfo(ctx context.Context, jid types.JID, contact types.ContactInfo, getAvatar bool) *bridgev2.UserInfo { +func (wa *WhatsAppClient) contactToUserInfo(ctx context.Context, jid types.JID, contact types.ContactInfo, avatarID string, fetchAvatar bool) *bridgev2.UserInfo { if jid == types.MetaAIJID && contact.PushName == jid.User { contact.PushName = "Meta AI" } else if jid == types.LegacyPSAJID || jid == types.PSAJID { @@ -270,7 +270,9 @@ func (wa *WhatsAppClient) contactToUserInfo(ctx context.Context, jid types.JID, } else if phone != "" { ui.Identifiers = []string{fmt.Sprintf("tel:%s", phone)} } - if getAvatar { + if wa.Main.Config.LazyAvatars { + ui.ExtraUpdates = bridgev2.MergeExtraUpdaters(ui.ExtraUpdates, wa.makeLazyGhostAvatarUpdater(avatarID, fetchAvatar)) + } else if fetchAvatar { ui.ExtraUpdates = bridgev2.MergeExtraUpdaters(ui.ExtraUpdates, wa.fetchGhostAvatar) } return ui @@ -307,6 +309,21 @@ func avatarInfoToCacheEntry(ctx context.Context, jid types.JID, avatar *types.Pr } } +func (wa *WhatsAppClient) makeLazyDirectMediaAvatar(ctx context.Context, jid types.JID, avatarID string, community bool) (*bridgev2.Avatar, error) { + if avatarID == "" { + avatarID = waid.MakeRandomAvatarID() + } + mxc, err := wa.Main.Bridge.Matrix.GenerateContentURI(ctx, waid.MakeAvatarMediaID(jid, avatarID, wa.UserLogin.ID, community)) + if err != nil { + return nil, fmt.Errorf("failed to generate MXC URI: %w", err) + } + return &bridgev2.Avatar{ + ID: networkid.AvatarID(avatarID), + MXC: mxc, + Hash: sha256.Sum256([]byte(avatarID)), + }, nil +} + func (wa *WhatsAppClient) makeDirectMediaAvatar(ctx context.Context, jid types.JID, avatar *types.ProfilePictureInfo, community bool) (*bridgev2.Avatar, error) { mxc, err := wa.Main.Bridge.Matrix.GenerateContentURI(ctx, waid.MakeAvatarMediaID(jid, avatar.ID, wa.UserLogin.ID, community)) if err != nil { @@ -328,6 +345,21 @@ func (wa *WhatsAppClient) makeDirectMediaAvatar(ctx context.Context, jid types.J }, nil } +func (wa *WhatsAppClient) makeLazyGhostAvatarUpdater(avatarID string, forceUpdate bool) func(context.Context, *bridgev2.Ghost) bool { + return func(ctx context.Context, ghost *bridgev2.Ghost) bool { + if ghost.AvatarID != "" && (networkid.AvatarID(avatarID) == ghost.AvatarID || (!forceUpdate && avatarID == "")) { + return false + } + jid := waid.ParseUserID(ghost.ID) + wrappedAvatar, err := wa.makeLazyDirectMediaAvatar(ctx, jid, avatarID, false) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to prepare lazy direct media avatar") + return false + } + return ghost.UpdateAvatar(ctx, wrappedAvatar) + } +} + func (wa *WhatsAppClient) fetchGhostAvatar(ctx context.Context, ghost *bridgev2.Ghost) bool { jid := waid.ParseUserID(ghost.ID) existingID := string(ghost.AvatarID) @@ -399,7 +431,7 @@ func (wa *WhatsAppClient) resyncContacts(forceAvatarSync, automatic bool) { } else if contact, err := contactStore.GetContact(ctx, jid); err != nil { log.Err(err).Stringer("jid", jid).Msg("Failed to get contact info") } else { - userInfo := wa.contactToUserInfo(ctx, jid, contact, forceAvatarSync || ghost.AvatarID == "") + userInfo := wa.contactToUserInfo(ctx, jid, contact, "", forceAvatarSync || ghost.AvatarID == "") ghost.UpdateInfo(ctx, userInfo) wa.syncAltGhostWithInfo(ctx, jid, userInfo) } diff --git a/pkg/connector/wadb/avatarcache.go b/pkg/connector/wadb/avatarcache.go index 8ea947f..55fb4b9 100644 --- a/pkg/connector/wadb/avatarcache.go +++ b/pkg/connector/wadb/avatarcache.go @@ -2,10 +2,14 @@ package wadb import ( "context" + "strings" + "time" "go.mau.fi/util/dbutil" "go.mau.fi/util/jsontime" "go.mau.fi/whatsmeow/types" + + "go.mau.fi/mautrix-whatsapp/pkg/waid" ) type AvatarCacheQuery struct { @@ -49,3 +53,13 @@ func (ace *AvatarCacheEntry) Scan(row dbutil.Scannable) (*AvatarCacheEntry, erro func (ace *AvatarCacheEntry) sqlVariables() []any { return []any{ace.EntityJID, ace.AvatarID, ace.DirectPath, ace.Expiry, ace.Gone} } + +func (ace *AvatarCacheEntry) IsGone() bool { + return ace != nil && ace.Gone && + // Random IDs can be retried after a specific expiry time, other types of gones can't + (ace.Expiry.IsZero() || !strings.HasPrefix(ace.AvatarID, waid.RandomAvatarIDPrefix) || ace.Expiry.After(time.Now())) +} + +func (ace *AvatarCacheEntry) Expired() bool { + return ace == nil || ace.Expiry.Before(time.Now().Add(5*time.Minute)) +} diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index 1e54426..e2b60f7 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -37,12 +37,12 @@ type UserLoginMetadata struct { PushKeys *PushKeys `json:"push_keys,omitempty"` APNSEncPubKey []byte `json:"apns_enc_pubkey,omitempty"` APNSEncPrivKey []byte `json:"apns_enc_privkey,omitempty"` - LoggedInAt jsontime.Unix `json:"logged_in_at,omitempty"` + LoggedInAt jsontime.Unix `json:"logged_in_at,omitzero"` AppStateRecoveryAttempted map[appstate.WAPatchName]time.Time `json:"app_state_recovery_attempted,omitempty"` - HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitempty"` - ReachoutTimelockUntil time.Time `json:"reachout_timelock_until,omitempty"` + HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitzero"` + ReachoutTimelockUntil time.Time `json:"reachout_timelock_until,omitzero"` MData json.RawMessage `json:"mdata,omitempty"` } @@ -88,13 +88,13 @@ type GroupInviteMeta struct { } type MessageMetadata struct { - SenderDeviceID uint16 `json:"sender_device_id,omitempty"` + SenderDeviceID uint16 `json:"sender_device_id,omitzero"` Error MessageErrorType `json:"error,omitempty"` BroadcastListJID *types.JID `json:"broadcast_list_jid,omitempty"` GroupInvite *GroupInviteMeta `json:"group_invite,omitempty"` FailedMediaMeta json.RawMessage `json:"media_meta,omitempty"` DirectMediaMeta json.RawMessage `json:"direct_media_meta,omitempty"` - IsMatrixPoll bool `json:"is_matrix_poll,omitempty"` + IsMatrixPoll bool `json:"is_matrix_poll,omitzero"` Edits []types.MessageID `json:"edits,omitempty"` } @@ -122,14 +122,16 @@ type ReactionMetadata struct { } type PortalMetadata struct { - DisappearingTimerSetAt int64 `json:"disappearing_timer_set_at,omitempty"` + DisappearingTimerSetAt int64 `json:"disappearing_timer_set_at,omitzero"` TopicID string `json:"topic_id,omitempty"` - LastSync jsontime.Unix `json:"last_sync,omitempty"` - CommunityAnnouncementGroup bool `json:"is_cag,omitempty"` + LastSync jsontime.Unix `json:"last_sync,omitzero"` + CommunityAnnouncementGroup bool `json:"is_cag,omitzero"` AddressingMode types.AddressingMode `json:"addressing_mode,omitempty"` - LIDMigrationAttempted bool `json:"lid_migration_attempted,omitempty"` + LIDMigrationAttempted bool `json:"lid_migration_attempted,omitzero"` } type GhostMetadata struct { - LastSync jsontime.Unix `json:"last_sync,omitempty"` + LastSync jsontime.Unix `json:"last_sync,omitzero"` + + DirectAvatarURL string `json:"direct_avatar_url,omitempty"` } diff --git a/pkg/waid/mediaid.go b/pkg/waid/mediaid.go index 716e187..a0ac2d9 100644 --- a/pkg/waid/mediaid.go +++ b/pkg/waid/mediaid.go @@ -24,6 +24,7 @@ import ( "strconv" "strings" + "go.mau.fi/util/random" "go.mau.fi/whatsmeow/types" "maunium.net/go/mautrix/bridgev2/networkid" ) @@ -85,6 +86,16 @@ type AvatarMediaInfo struct { Community bool } +const RandomAvatarIDPrefix = "mxwa-lazy-avatar-" + +func MakeRandomAvatarID() string { + return RandomAvatarIDPrefix + random.String(8) +} + +func (ami *AvatarMediaInfo) IsRandom() bool { + return ami != nil && strings.HasPrefix(ami.AvatarID, RandomAvatarIDPrefix) +} + func MakeStickerPackMediaID(packID string, fileHash []byte, receiver networkid.UserLoginID) networkid.MediaID { receiverID := compactJID(ParseUserLoginID(receiver, 0)) mediaID := make([]byte, 0, 4+len(packID)+len(fileHash)+len(receiverID)) From 240648081e1ac70b6d5a530f57a6d26f317c9480 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 3 Sep 2026 14:37:14 +0300 Subject: [PATCH 18/35] startchat: return user info with resolve identifier requests --- pkg/connector/startchat.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index 2ba1e09..18e8326 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -176,11 +176,16 @@ func (wa *WhatsAppClient) ResolveIdentifier(ctx context.Context, identifier stri if err != nil { return nil, fmt.Errorf("failed to get ghost: %w", err) } + userInfo, err := wa.getUserInfo(ctx, jid, "", false) + if err != nil { + return nil, fmt.Errorf("failed to get user info: %w", err) + } return &bridgev2.ResolveIdentifierResponse{ - Ghost: ghost, - UserID: waid.MakeUserID(jid), - Chat: wa.makeCreateChatResponse(ctx, jid, origJID), + Ghost: ghost, + UserID: waid.MakeUserID(jid), + UserInfo: userInfo, + Chat: wa.makeCreateChatResponse(ctx, jid, origJID), }, nil } From 63dd2582b5c11397aecfa938240e094d7fe45d0d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 8 Sep 2026 14:41:40 +0300 Subject: [PATCH 19/35] dependencies: update --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 288eb6f..5b53985 100644 --- a/go.mod +++ b/go.mod @@ -12,13 +12,13 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260903111606-de26b4ab6499 + go.mau.fi/whatsmeow v0.0.0-20260908082135-57796d3d6b41 golang.org/x/image v0.45.0 golang.org/x/net v0.58.0 golang.org/x/sync v0.22.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.30.1-0.20260828224424-c22bd4fdc9e6 + maunium.net/go/mautrix v0.30.1-0.20260908102503-2f2c98079d9e ) require ( diff --git a/go.sum b/go.sum index e5d6221..38bb208 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbs go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= 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-20260903111606-de26b4ab6499 h1:7RG3BeC9qXh2aQ0RwjIFZxhCwTohdtNzJaKHxna32pE= -go.mau.fi/whatsmeow v0.0.0-20260903111606-de26b4ab6499/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= +go.mau.fi/whatsmeow v0.0.0-20260908082135-57796d3d6b41 h1:EUpS1/OiThY4/S1jgG5jGpC7bILOtM/Q1r1b7BI9NrM= +go.mau.fi/whatsmeow v0.0.0-20260908082135-57796d3d6b41/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= 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.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= @@ -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.30.1-0.20260828224424-c22bd4fdc9e6 h1:mL1BquGn+7efD4ECX62uHVN2MnsrC65JuOgxDdKFF7o= -maunium.net/go/mautrix v0.30.1-0.20260828224424-c22bd4fdc9e6/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= +maunium.net/go/mautrix v0.30.1-0.20260908102503-2f2c98079d9e h1:T4g/2u7ZEbAGkYGNizjYZSltWR5m0DrX5UDWqfZJdS4= +maunium.net/go/mautrix v0.30.1-0.20260908102503-2f2c98079d9e/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= From 446bd22695570a0b13486f41851251772e8697b8 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 8 Sep 2026 14:42:26 +0300 Subject: [PATCH 20/35] lidmigrate: resync missing avatars and reuse info for alt ghost sync --- pkg/connector/connector.go | 10 +++++- pkg/connector/handlewhatsapp.go | 2 +- pkg/connector/lidmigrate.go | 57 +++++++++++++++++++++++++++++++++ pkg/connector/userinfo.go | 23 ++++++++++--- 4 files changed, 86 insertions(+), 6 deletions(-) diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 193dc4d..3602e93 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -160,7 +160,15 @@ func (wa *WhatsAppConnector) Start(ctx context.Context) error { return bridgev2.DBUpgradeError{Err: err, Section: "whatsapp"} } - return wa.migrateToLIDDMs(ctx) + err = wa.migrateToLIDDMs(ctx) + if err != nil { + return fmt.Errorf("failed to migrate to LID DMs: %w", err) + } + err = wa.syncMismatchingGhosts(ctx) + if err != nil { + return fmt.Errorf("failed to sync mismatching ghosts: %w", err) + } + return nil } func (wa *WhatsAppConnector) Stop() { diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 8a758eb..d8df69f 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -735,7 +735,7 @@ func (wa *WhatsAppClient) syncGhost(jid types.JID, reason string, pictureID *str } else { ghost.UpdateInfo(ctx, userInfo) log.Debug().Msg("Synced ghost info") - wa.syncAltGhostWithInfo(ctx, jid, userInfo) + wa.syncAltGhostWithInfo(ctx, jid, ghost) } go wa.syncRemoteProfile(ctx, ghost) } diff --git a/pkg/connector/lidmigrate.go b/pkg/connector/lidmigrate.go index cfab099..ec1c700 100644 --- a/pkg/connector/lidmigrate.go +++ b/pkg/connector/lidmigrate.go @@ -210,3 +210,60 @@ func (wa *WhatsAppConnector) migrateToLIDDMs(ctx context.Context) error { wa.Bridge.DB.KV.Set(ctx, "whatsapp_lid_dms_migrated", "true") return nil } + +func (wa *WhatsAppConnector) syncMismatchingGhosts(ctx context.Context) error { + if wa.Bridge.Background || wa.Bridge.DB.KV.Get(ctx, "whatsapp_lid_avatars_resynced") == "true" { + return nil + } + const findMismatchingGhosts = ` + SELECT id + FROM ghost + WHERE bridge_id=$1 + AND id LIKE 'lid-%' + AND avatar_mxc='' + AND EXISTS ( + SELECT pnghost.avatar_mxc + FROM ghost pnghost + WHERE pnghost.bridge_id=$1 + AND pnghost.id=(SELECT pn FROM whatsmeow_lid_map WHERE lid=replace(ghost.id, 'lid-', '')) + AND pnghost.avatar_mxc<>'' + ) + ` + var scanGhostID = dbutil.ConvertRowFn[networkid.UserID](dbutil.ScanSingleColumn[networkid.UserID]) + ghostIDs, err := scanGhostID.NewRowIter(wa.Bridge.DB.Query(ctx, findMismatchingGhosts, wa.Bridge.ID)).AsList() + if err != nil { + return fmt.Errorf("failed to get mismatching ghosts: %w", err) + } + for _, ghostID := range ghostIDs { + lid := waid.ParseUserID(ghostID) + pn, err := wa.DeviceStore.LIDMap.GetPNForLID(ctx, lid) + if err != nil { + return fmt.Errorf("failed to get PN for LID %s: %w", lid, err) + } else if pn.IsEmpty() { + zerolog.Ctx(ctx).Warn().Stringer("lid", lid).Msg("No PN for LID") + continue + } + pnGhost, err := wa.Bridge.GetGhostByID(ctx, waid.MakeUserID(pn)) + if err != nil { + return fmt.Errorf("failed to get PN ghost for %s: %w", pn, err) + } + lidGhost, err := wa.Bridge.GetGhostByID(ctx, ghostID) + if err != nil { + return fmt.Errorf("failed to get LID ghost for %s: %w", ghostID, err) + } + if lidGhost.AvatarMXC != "" || pnGhost.AvatarMXC == "" { + continue + } + zerolog.Ctx(ctx).Debug(). + Stringer("pn", pn). + Stringer("lid", lid). + Str("pn_ghost_avatar", string(pnGhost.AvatarMXC)). + Str("lid_ghost_avatar", string(lidGhost.AvatarMXC)). + Str("pn_ghost_name", pnGhost.Name). + Str("lid_ghost_name", lidGhost.Name). + Msg("Updating LID ghost avatar") + lidGhost.UpdateInfo(ctx, makeInfoFromGhost(pnGhost)) + } + wa.Bridge.DB.KV.Set(ctx, "whatsapp_lid_avatars_resynced", "true") + return nil +} diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 05d6c40..c78b0a8 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -171,7 +171,7 @@ func (wa *WhatsAppClient) doGhostResync(ctx context.Context, queue map[types.JID continue } ghost.UpdateInfo(ctx, userInfo) - wa.syncAltGhostWithInfo(ctx, jid, userInfo) + wa.syncAltGhostWithInfo(ctx, jid, ghost) } } @@ -433,12 +433,12 @@ func (wa *WhatsAppClient) resyncContacts(forceAvatarSync, automatic bool) { } else { userInfo := wa.contactToUserInfo(ctx, jid, contact, "", forceAvatarSync || ghost.AvatarID == "") ghost.UpdateInfo(ctx, userInfo) - wa.syncAltGhostWithInfo(ctx, jid, userInfo) + wa.syncAltGhostWithInfo(ctx, jid, ghost) } } } -func (wa *WhatsAppClient) syncAltGhostWithInfo(ctx context.Context, jid types.JID, info *bridgev2.UserInfo) { +func (wa *WhatsAppClient) syncAltGhostWithInfo(ctx context.Context, jid types.JID, mainGhost *bridgev2.Ghost) { log := zerolog.Ctx(ctx) var altJID types.JID var err error @@ -463,10 +463,25 @@ func (wa *WhatsAppClient) syncAltGhostWithInfo(ctx context.Context, jid types.JI Msg("Failed to get ghost for alternate JID") return } - ghost.UpdateInfo(ctx, info) + ghost.UpdateInfo(ctx, makeInfoFromGhost(mainGhost)) log.Debug(). Stringer("jid", jid). Stringer("alternate_jid", altJID). Msg("Synced alternate ghost with info") go wa.syncRemoteProfile(ctx, ghost) } + +func makeInfoFromGhost(ghost *bridgev2.Ghost) *bridgev2.UserInfo { + return &bridgev2.UserInfo{ + Identifiers: ghost.Identifiers, + Name: &ghost.Name, + Avatar: &bridgev2.Avatar{ + ID: ghost.AvatarID, + Remove: ghost.AvatarID == "" || ghost.AvatarMXC == "", + MXC: ghost.AvatarMXC, + Hash: ghost.AvatarHash, + }, + IsBot: &ghost.IsBot, + ExtraProfile: ghost.ExtraProfile, + } +} From a19ef78dd33af9d6a520a58c549bf5e01d46a73b Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 8 Sep 2026 15:03:18 +0300 Subject: [PATCH 21/35] directmedia: don't allow parallel downloads of the same avatar --- pkg/connector/client.go | 1 + pkg/connector/directmedia.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index f0a5947..8c302e3 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -116,6 +116,7 @@ type WhatsAppClient struct { nextResync time.Time directMediaRetries map[networkid.MessageID]*directMediaRetry directMediaLock sync.Mutex + avatarLock exsync.KeyedMutex[types.JID] mediaRetryLock *semaphore.Weighted offlineSyncWaiter atomic.Pointer[chan error] isNewLogin bool diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index 092ce3e..8ed94ce 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -85,6 +85,8 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars if waClient.Client == nil { return nil, fmt.Errorf("no WhatsApp client found on login %s", parsedID.UserLogin) } + waClient.avatarLock.Lock(parsedID.Avatar.TargetJID) + defer waClient.avatarLock.Unlock(parsedID.Avatar.TargetJID) cachedInfo, err := wa.DB.AvatarCache.Get(ctx, parsedID.Avatar.TargetJID, parsedID.Avatar.AvatarID) if err != nil { return nil, fmt.Errorf("failed to get avatar cache entry: %w", err) From 82c79d1108e7c1badbcc9b2019cb560b6daaa7a2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 8 Sep 2026 19:09:47 +0300 Subject: [PATCH 22/35] dependencies: update mautrix-go --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5b53985..bf892b0 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.22.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.30.1-0.20260908102503-2f2c98079d9e + maunium.net/go/mautrix v0.30.1-0.20260908153423-501dff148280 ) require ( diff --git a/go.sum b/go.sum index 38bb208..a0ba2a8 100644 --- a/go.sum +++ b/go.sum @@ -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.30.1-0.20260908102503-2f2c98079d9e h1:T4g/2u7ZEbAGkYGNizjYZSltWR5m0DrX5UDWqfZJdS4= -maunium.net/go/mautrix v0.30.1-0.20260908102503-2f2c98079d9e/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= +maunium.net/go/mautrix v0.30.1-0.20260908153423-501dff148280 h1:AoNwPhJCX0y93tp2nJPu/RuIt/+K+Zu4p4Q44+hKEhc= +maunium.net/go/mautrix v0.30.1-0.20260908153423-501dff148280/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= From 9ea41dcb0efa00597230cb17da42b01463c82700 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 9 Sep 2026 12:40:38 +0300 Subject: [PATCH 23/35] login: add support for adv rotation --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/login.go | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bf892b0..1c7be5c 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260908082135-57796d3d6b41 + go.mau.fi/whatsmeow v0.0.0-20260909093947-9ec8f76db5f1 golang.org/x/image v0.45.0 golang.org/x/net v0.58.0 golang.org/x/sync v0.22.0 diff --git a/go.sum b/go.sum index a0ba2a8..1c70cc4 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbs go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= 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-20260908082135-57796d3d6b41 h1:EUpS1/OiThY4/S1jgG5jGpC7bILOtM/Q1r1b7BI9NrM= -go.mau.fi/whatsmeow v0.0.0-20260908082135-57796d3d6b41/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= +go.mau.fi/whatsmeow v0.0.0-20260909093947-9ec8f76db5f1 h1:4EyoinbJqFq4qozxem5nklT8+pI4OaokUuG+ayE3nKQ= +go.mau.fi/whatsmeow v0.0.0-20260909093947-9ec8f76db5f1/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= 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.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= diff --git a/pkg/connector/login.go b/pkg/connector/login.go index a62b294..80a6abb 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/http" + "strings" "sync/atomic" "time" @@ -98,6 +99,7 @@ func (wa *WhatsAppConnector) CreateLogin(_ context.Context, user *bridgev2.User, LoginComplete: exsync.NewEvent(), PasskeyRequest: exsync.NewEvent(), PasskeyConfirmation: exsync.NewEvent(), + ADVRotate: exsync.NewEvent(), Received515: exsync.NewEvent(), }, nil } @@ -124,6 +126,8 @@ type WALogin struct { PasskeyConfirmation *exsync.Event PasskeyConfirmationData *events.PairPasskeyConfirmation + ADVRotate *exsync.Event + Closed atomic.Bool EventHandlerID uint32 } @@ -270,6 +274,12 @@ func (wl *WALogin) handleEvent(rawEvt any) { wl.StartTime = time.Now() wl.WaitForQRs.Set() return + case *events.RotateADVSecret: + wl.Log.Debug().Msg("Rotating ADV secret in all QRs") + for i, code := range wl.QRs { + wl.QRs[i] = strings.Replace(code, evt.OldSecret, evt.NewSecret, 1) + } + wl.ADVRotate.Set() case *events.QRScannedWithoutMultidevice: wl.Log.Error().Msg("QR code scanned without multidevice enabled") wl.LoginError = ErrLoginMultideviceNotEnabled @@ -332,6 +342,7 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { Int("current_index", currentIndex) if currentIndex > prevIndex { logEvt.Msg("Returning new QR immediately") + wl.ADVRotate.Clear() wl.PrevQRIndex.Store(int32(currentIndex)) return makeQRStep(wl.QRs[currentIndex]), nil } @@ -343,6 +354,7 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { wl.Cancel() return nil, ErrLoginTimeout } + wl.ADVRotate.Clear() wl.PrevQRIndex.Store(int32(nextIndex)) return makeQRStep(wl.QRs[nextIndex]), nil case <-ctx.Done(): @@ -350,6 +362,10 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { return nil, ctx.Err() case <-wl.PasskeyRequest.GetChan(): return wl.makePasskeyStep() + case <-wl.ADVRotate.GetChan(): + wl.Log.Debug().Msg("ADV secret was rotated, returning new QR immediately") + wl.ADVRotate.Clear() + return makeQRStep(wl.QRs[nextIndex]), nil case <-wl.LoginComplete.GetChan(): // continue } From 79164b22ad498b3de829205d5e112f0e817f28a6 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 10 Sep 2026 10:42:06 +0300 Subject: [PATCH 24/35] lidmigrate: resync ghost avatars in background --- pkg/connector/connector.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 3602e93..d7261d3 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -164,10 +164,12 @@ func (wa *WhatsAppConnector) Start(ctx context.Context) error { if err != nil { return fmt.Errorf("failed to migrate to LID DMs: %w", err) } - err = wa.syncMismatchingGhosts(ctx) - if err != nil { - return fmt.Errorf("failed to sync mismatching ghosts: %w", err) - } + go func() { + err = wa.syncMismatchingGhosts(wa.Bridge.BackgroundCtx) + if err != nil { + wa.Bridge.Log.Err(err).Msg("Failed to sync mismatching ghosts") + } + }() return nil } From 18ae384c98866536bb3db90d6c56ba016227848a Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 11 Sep 2026 19:03:14 +0300 Subject: [PATCH 25/35] .github: make contributing.md more visible --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..764f753 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +See From 5d3fce8eff7e5079e24a924d83663e0f4bddec2f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 11 Sep 2026 19:42:24 +0300 Subject: [PATCH 26/35] handlewhatsapp: ignore sent broadcasts --- pkg/connector/chatinfo.go | 4 +++- pkg/connector/handlewhatsapp.go | 7 +++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 675a5ce..267200c 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -29,6 +29,8 @@ func (wa *WhatsAppClient) GetChatInfo(ctx context.Context, portal *bridgev2.Port return wa.getChatInfo(ctx, portalJID, nil, portal.MXID == "") } +var ErrBroadcastList = errors.New("broadcast list bridging is currently not supported") + func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, conv *wadb.Conversation, isNew bool) (wrapped *bridgev2.ChatInfo, err error) { switch portalJID.Server { case types.DefaultUserServer, types.HiddenUserServer, types.BotServer: @@ -37,7 +39,7 @@ func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, if portalJID == types.StatusBroadcastJID { wrapped = wa.wrapStatusBroadcastInfo(ctx) } else { - return nil, fmt.Errorf("broadcast list bridging is currently not supported") + return nil, ErrBroadcastList } case types.GroupServer: info, err := wa.Client.GetGroupInfo(ctx, portalJID) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index d8df69f..ed36dca 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -284,10 +284,9 @@ func (wa *WhatsAppClient) ensureAltJIDs(ctx context.Context, info *types.Message func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Message) (success bool) { success = true - if evt.Info.Chat == types.StatusBroadcastJID && !wa.Main.Config.EnableStatusBroadcast { - return - } - if evt.Info.Chat.Server == types.NewsletterServer && wa.disableNewsletter { + if (evt.Info.Chat == types.StatusBroadcastJID && !wa.Main.Config.EnableStatusBroadcast) || + (evt.Info.Chat.Server == types.NewsletterServer && wa.disableNewsletter) || + (evt.Info.Chat.Server == types.BroadcastServer && evt.Info.IsFromMe) { return } if !wa.ensureAltJIDs(ctx, &evt.Info.MessageSource, true) { From d6bfdeb477dc1cd4d7d33717b860a268624211c4 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 11 Sep 2026 21:33:53 +0300 Subject: [PATCH 27/35] dependencies: update mautrix-go --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/client.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 1c7be5c..692c5ab 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.22.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.30.1-0.20260908153423-501dff148280 + maunium.net/go/mautrix v0.30.1-0.20260911182621-dc9b2d7a77fb ) require ( diff --git a/go.sum b/go.sum index 1c70cc4..3634f94 100644 --- a/go.sum +++ b/go.sum @@ -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.30.1-0.20260908153423-501dff148280 h1:AoNwPhJCX0y93tp2nJPu/RuIt/+K+Zu4p4Q44+hKEhc= -maunium.net/go/mautrix v0.30.1-0.20260908153423-501dff148280/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= +maunium.net/go/mautrix v0.30.1-0.20260911182621-dc9b2d7a77fb h1:QimfAexEmbycZEYon7vv11k8jgb7SYCDrsaniDNETLU= +maunium.net/go/mautrix v0.30.1-0.20260911182621-dc9b2d7a77fb/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 8c302e3..a909946 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -78,7 +78,7 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. w.Client = whatsmeow.NewClient(w.Device, waLog.Zerolog(log)) w.Client.AddEventHandlerWithSuccessStatus(w.handleWAEvent) w.Client.SynchronousAck = true - w.Client.EnableDecryptedEventBuffer = bridgev2.PortalEventBuffer == 0 + w.Client.EnableDecryptedEventBuffer = wa.Bridge.Config.PortalEventBuffer == 0 w.Client.ManualHistorySyncDownload = true w.Client.SendReportingTokens = true w.Client.AutomaticMessageRerequestFromPhone = true From 037423f0b2eb93019e52da8ada6c96e8a40e8955 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 14 Sep 2026 15:34:54 +0300 Subject: [PATCH 28/35] dependencies: update whatsmeow --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 692c5ab..a8390bf 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260909093947-9ec8f76db5f1 + go.mau.fi/whatsmeow v0.0.0-20260914123426-9120a3fc6159 golang.org/x/image v0.45.0 golang.org/x/net v0.58.0 golang.org/x/sync v0.22.0 diff --git a/go.sum b/go.sum index 3634f94..7124070 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbs go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= 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-20260909093947-9ec8f76db5f1 h1:4EyoinbJqFq4qozxem5nklT8+pI4OaokUuG+ayE3nKQ= -go.mau.fi/whatsmeow v0.0.0-20260909093947-9ec8f76db5f1/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= +go.mau.fi/whatsmeow v0.0.0-20260914123426-9120a3fc6159 h1:M23L3U7EL7tDIIVgNvp1XV4wU+sKKjBmFkZ7DBNEzdg= +go.mau.fi/whatsmeow v0.0.0-20260914123426-9120a3fc6159/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= 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.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= From 991c12cdab59416ce98271f0ef983ecee04ce68e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 14 Sep 2026 15:35:08 +0300 Subject: [PATCH 29/35] changelog: update --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d17865..b6567d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +# v26.09 (unreleased) + +* Bumped minimum Go version to 1.26. +* Added option to limit eager member sync in group chat portals. +* Added option to fetch avatars lazily when using direct media. +* Added one-time migration to ensure LID ghost avatars are consistent with the + old phone number ghosts. +* Fixed the resolve identifier command and API not returning any user info for + users who hadn't previously been encountered. +* Fixed group portal power levels to always allow poll responses. +* Fixed handling history sync events where the phone sends nonsensical + timestamps. +* Fixed handling edits to HD media captions. +* Fixed handling bridging own read receipts from the native apps in channels. + # v26.08 * Switched direct chats to use LIDs instead of phone numbers. From c1e30935dc3fb407d6c0a8bd778b64175949db5e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 14 Sep 2026 18:07:29 +0300 Subject: [PATCH 30/35] dependencies: update whatsmeow again --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a8390bf..83f457f 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260914123426-9120a3fc6159 + go.mau.fi/whatsmeow v0.0.0-20260914150520-0d3b644136bf golang.org/x/image v0.45.0 golang.org/x/net v0.58.0 golang.org/x/sync v0.22.0 diff --git a/go.sum b/go.sum index 7124070..d56e1c4 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbs go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= 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-20260914123426-9120a3fc6159 h1:M23L3U7EL7tDIIVgNvp1XV4wU+sKKjBmFkZ7DBNEzdg= -go.mau.fi/whatsmeow v0.0.0-20260914123426-9120a3fc6159/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= +go.mau.fi/whatsmeow v0.0.0-20260914150520-0d3b644136bf h1:9iToTs8b6MMX8lWSryybzR9sqYGYG4/LpXj+cy0UC4k= +go.mau.fi/whatsmeow v0.0.0-20260914150520-0d3b644136bf/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= 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.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= From a76341f281c7b6f1ac3913a2a47bd309f082b5ab Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 14 Sep 2026 18:17:24 +0300 Subject: [PATCH 31/35] handlewhatsapp: log unknown protocol message types --- pkg/connector/handlewhatsapp.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index ed36dca..5381311 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -334,7 +334,15 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa wa.Main.Bridge.Config.Backfill.Enabled { wa.saveWAHistorySyncNotification(ctx, evt.Message.ProtocolMessage.HistorySyncNotification) } - if parsedMessageType == "ignore" || strings.HasPrefix(parsedMessageType, "unknown_protocol_") { + if parsedMessageType == "ignore" { + return + } else if strings.HasPrefix(parsedMessageType, "unknown_protocol_") { + wa.UserLogin.Log.Debug(). + Str("message_id", evt.Info.ID). + Stringer("chat_jid", evt.Info.Chat). + Stringer("sender_jid", evt.Info.Sender). + Stringer("protocol_message_type", evt.Message.GetProtocolMessage().GetType()). + Msg("Ignoring unknown protocol message") return } From 1ea9f02ade4b7c5c2cc058199912c5ce9c5189b0 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 14 Sep 2026 19:28:43 +0300 Subject: [PATCH 32/35] msgconv/from-whatsapp: bridge advanced rich responses as unknown message types --- pkg/msgconv/from-whatsapp.go | 2 +- pkg/msgconv/wa-misc.go | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index e912732..6dac5a2 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -190,7 +190,7 @@ func (mc *MessageConverter) ToMatrix( case waMsg.KeepInChatMessage != nil: part, contextInfo = mc.convertKeepInChatMessage(ctx, waMsg.KeepInChatMessage) case waMsg.RichResponseMessage != nil: - part, contextInfo = mc.convertRichResponseMessage(ctx, waMsg.RichResponseMessage) + part, contextInfo = mc.convertRichResponseMessage(ctx, waMsg.RichResponseMessage, waMsg) case waMsg.ImageMessage != nil: part, contextInfo = mc.convertMediaMessage(ctx, waMsg.ImageMessage, "photo", info, isViewOnce, previouslyConvertedPart) case waMsg.StickerMessage != nil: diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index fa495e0..be5b60f 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -351,7 +351,7 @@ func (mc *MessageConverter) convertKeepInChatMessage(ctx context.Context, msg *w }, nil } -func (mc *MessageConverter) convertRichResponseMessage(ctx context.Context, msg *waE2E.AIRichResponseMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { +func (mc *MessageConverter) convertRichResponseMessage(ctx context.Context, msg *waE2E.AIRichResponseMessage, fullMsg *waE2E.Message) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { var body strings.Builder // TODO switch to new format? @@ -363,10 +363,14 @@ func (mc *MessageConverter) convertRichResponseMessage(ctx context.Context, msg body.WriteString(submsg.GetMessageText()) } } - + unknownPart, _ := mc.convertUnknownMessage(ctx, fullMsg) + if body.Len() == 0 { + return unknownPart, msg.GetContextInfo() + } content := format.RenderMarkdown(body.String(), true, false) return &bridgev2.ConvertedMessagePart{ Type: event.EventMessage, Content: &content, + Extra: unknownPart.Extra, }, msg.GetContextInfo() } From 741f45d6497abb18bbcf9bb93106ddc3b9b5aa68 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 16 Sep 2026 00:22:26 +0300 Subject: [PATCH 33/35] dependencies: update whatsmeow --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 83f457f..ccd3fce 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260914150520-0d3b644136bf + go.mau.fi/whatsmeow v0.0.0-20260915211301-f376da267f95 golang.org/x/image v0.45.0 golang.org/x/net v0.58.0 golang.org/x/sync v0.22.0 diff --git a/go.sum b/go.sum index d56e1c4..5c05090 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbs go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= 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-20260914150520-0d3b644136bf h1:9iToTs8b6MMX8lWSryybzR9sqYGYG4/LpXj+cy0UC4k= -go.mau.fi/whatsmeow v0.0.0-20260914150520-0d3b644136bf/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= +go.mau.fi/whatsmeow v0.0.0-20260915211301-f376da267f95 h1:xwn1RBsgFKCFDTLKpsdrpfgZoLX6wlfHKRBpGjnpkVI= +go.mau.fi/whatsmeow v0.0.0-20260915211301-f376da267f95/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= 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.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= From 9361103b64f5718ffd03f76bc40cb436f30c9efd Mon Sep 17 00:00:00 2001 From: Toni Spets Date: Wed, 16 Sep 2026 12:00:30 +0300 Subject: [PATCH 34/35] backfill: don't panic when the whatsmeow client is nil (#954) --- pkg/connector/backfill.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index cab460f..e0f5ffc 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -602,6 +602,9 @@ func (wa *WhatsAppClient) convertHistorySyncMessages( messages []*waWeb.WebMessageInfo, explodeOnError bool, ) (*bridgev2.FetchMessagesResponse, error) { + if wa.Client == nil { + return nil, bridgev2.ErrNotLoggedIn + } oldestTS := messages[len(messages)-1].GetMessageTimestamp() newestTS := messages[0].GetMessageTimestamp() convertedMessages := make([]*bridgev2.BackfillMessage, 0, len(messages)) From 52e75964a0f03a0b3e21e658a2fd7b345dff40b4 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 16 Sep 2026 13:14:00 +0300 Subject: [PATCH 35/35] Bump version to v26.09 --- CHANGELOG.md | 5 ++- cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 28 ++++++++--------- go.sum | 60 +++++++++++++++++------------------- 4 files changed, 47 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6567d6..d5dd173 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# v26.09 (unreleased) +# v26.09 * Bumped minimum Go version to 1.26. * Added option to limit eager member sync in group chat portals. @@ -12,6 +12,9 @@ timestamps. * Fixed handling edits to HD media captions. * Fixed handling bridging own read receipts from the native apps in channels. +* Fixed decrypting messages from the new Muse AI bot + * May require resyncing app state using `!wa sync appstate regular_high`. + * The message contents aren't supported yet. # v26.08 diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 011e9b0..a1d85c3 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -18,7 +18,7 @@ var m = mxmain.BridgeMain{ Name: "mautrix-whatsapp", URL: "https://github.com/mautrix/whatsapp", Description: "A Matrix-WhatsApp puppeting bridge.", - Version: "26.08", + Version: "26.09", SemCalVer: true, Connector: &connector.WhatsAppConnector{}, } diff --git a/go.mod b/go.mod index ccd3fce..3768dca 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module go.mau.fi/mautrix-whatsapp go 1.26.0 -toolchain go1.27.0 +toolchain go1.27.1 tool go.mau.fi/util/cmd/maubuild @@ -10,15 +10,15 @@ require ( github.com/lib/pq v1.12.3 github.com/rs/zerolog v1.35.1 github.com/tidwall/gjson v1.19.0 - go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde + go.mau.fi/util v0.10.1 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260915211301-f376da267f95 - golang.org/x/image v0.45.0 - golang.org/x/net v0.58.0 - golang.org/x/sync v0.22.0 + go.mau.fi/whatsmeow v0.0.0-20260916100317-2375e1751bbd + golang.org/x/image v0.46.0 + golang.org/x/net v0.59.0 + golang.org/x/sync v0.23.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.30.1-0.20260911182621-dc9b2d7a77fb + maunium.net/go/mautrix v0.31.0 ) require ( @@ -31,7 +31,7 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.50 // indirect + github.com/mattn/go-sqlite3 v1.14.52 // indirect github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/rs/xid v1.6.0 // indirect @@ -40,14 +40,14 @@ require ( github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect github.com/vektah/gqlparser/v2 v2.5.27 // indirect - github.com/yuin/goldmark v1.8.5 // indirect + github.com/yuin/goldmark v1.8.6 // indirect go.mau.fi/libsignal v0.2.2 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect - golang.org/x/crypto v0.55.0 // indirect - golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa // indirect - golang.org/x/mod v0.40.0 // indirect - golang.org/x/sys v0.47.0 // indirect - golang.org/x/text v0.41.0 // indirect + golang.org/x/crypto v0.57.0 // indirect + golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba // indirect + golang.org/x/mod v0.41.0 // indirect + golang.org/x/sys v0.48.0 // indirect + golang.org/x/text v0.42.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect maunium.net/go/mauflag v1.0.0 // indirect diff --git a/go.sum b/go.sum index 5c05090..ac06de4 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,6 @@ github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6p github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg= github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -34,13 +32,11 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.50 h1:dmdFvo1XG4MPzA4IkAmE9upVz/Nj31uRoM5+jC8hYbY= -github.com/mattn/go-sqlite3 v1.14.50/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= +github.com/mattn/go-sqlite3 v1.14.52 h1:wVbm2Qnf4OXkqhBTSPuCRZDRnxfbVrrmiCEroVdog8U= +github.com/mattn/go-sqlite3 v1.14.52/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 h1:lcWAnrqr2nNfDiArwFNHCE4787Mw2tCdVSOXCru0/0E= github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= @@ -52,8 +48,8 @@ github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI= +github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= @@ -67,35 +63,35 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTdwFp0s= github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= -github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA= -github.com/yuin/goldmark v1.8.5/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark v1.8.6 h1:d0VcaP1sx9GkFVkoW+KtggpGi2KZ965i14b0+bDQST4= +github.com/yuin/goldmark v1.8.6/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.2 h1:QV+XdzQkm3x3aSG7FcqfGSZuFXz83pRZPBFaPygHbOU= go.mau.fi/libsignal v0.2.2/go.mod h1:CRlIQg2J8uYTfDFvNoO8/KcZjs5cey0vbc6oj/bssY0= -go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbsxj7G6mfujjKei1HaFQM= -go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= +go.mau.fi/util v0.10.1 h1:1oSqb4TwzLA0cUDY0aomyBPFKkZ3J5fqCmrXV3VH3GQ= +go.mau.fi/util v0.10.1/go.mod h1:40TDo7/ekSeOjgr8KAmX31Yf4zrOF94j83WQB+u5ZPc= 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-20260915211301-f376da267f95 h1:xwn1RBsgFKCFDTLKpsdrpfgZoLX6wlfHKRBpGjnpkVI= -go.mau.fi/whatsmeow v0.0.0-20260915211301-f376da267f95/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= +go.mau.fi/whatsmeow v0.0.0-20260916100317-2375e1751bbd h1:ZRMG9rK+Vghe4U+xGDGI2hkwJgoPTqf25HWm3nkBM8w= +go.mau.fi/whatsmeow v0.0.0-20260916100317-2375e1751bbd/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= 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.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= -golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= -golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa h1:QSyA8ishJCyT21kER9KwNt0b7BM3iRK4x9QXhjN5Fdk= -golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= -golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0= -golang.org/x/image v0.45.0/go.mod h1:n62x/7RqlwXDvGsSU4u6IUTUf6KghUZ9Bt7cG/T9Fx4= -golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= -golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= -golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= -golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= -golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= +golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba h1:Ck8QetSgk912qxWLMCKxd0in+aiyBQyDSMae6e/xmpU= +golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba/go.mod h1:50RgIsmK7OwqzTTeqcSXQW8SswW0o8fRcDxmqGluJ8E= +golang.org/x/image v0.46.0 h1:b1+oYj0Jbp6K5MDT4i4/eZpYlk3V8SJhhDKh6LBHAyQ= +golang.org/x/image v0.46.0/go.mod h1:3B3W05VGVQyuXucLINLjXKrqISASfi4Xj+iCVkLMwew= +golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= +golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= +golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= -golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -107,5 +103,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.30.1-0.20260911182621-dc9b2d7a77fb h1:QimfAexEmbycZEYon7vv11k8jgb7SYCDrsaniDNETLU= -maunium.net/go/mautrix v0.30.1-0.20260911182621-dc9b2d7a77fb/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= +maunium.net/go/mautrix v0.31.0 h1:x6XNBSa0kOaBgcleI+ZBLkGvBdHIE0qdeY6TZ4wWUeI= +maunium.net/go/mautrix v0.31.0/go.mod h1:vfPYtoGAjlTnIF+W8tl+rjX3yB4PEfqYR2sDXvQo4Co=