From 7ee5aa647cd8a35881b4ea2d4e6c0f9067e48097 Mon Sep 17 00:00:00 2001 From: Kishan Bagaria <1093313+KishanBagaria@users.noreply.github.com> Date: Thu, 26 Mar 2026 22:02:48 -0700 Subject: [PATCH 01/94] - --- pkg/connector/wamsgtype.go | 2 + pkg/msgconv/from-whatsapp.go | 4 ++ pkg/msgconv/wa-misc.go | 81 ++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/pkg/connector/wamsgtype.go b/pkg/connector/wamsgtype.go index 92e7f86..e716f5d 100644 --- a/pkg/connector/wamsgtype.go +++ b/pkg/connector/wamsgtype.go @@ -132,6 +132,8 @@ func getMessageType(waMsg *waE2E.Message) string { return "poll result snapshot" case waMsg.MessageHistoryBundle != nil: return "message history bundle" + case waMsg.MessageHistoryNotice != nil: + return "message history notice" case waMsg.RequestPhoneNumberMessage != nil: return "request phone number" case waMsg.KeepInChatMessage != nil: diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 11569e2..2117535 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -217,6 +217,10 @@ func (mc *MessageConverter) ToMatrix( part, contextInfo = mc.convertGroupInviteMessage(ctx, info, waMsg.GroupInviteMessage) case waMsg.ProtocolMessage != nil && waMsg.ProtocolMessage.GetType() == waE2E.ProtocolMessage_EPHEMERAL_SETTING: part, contextInfo = mc.convertEphemeralSettingMessage(ctx, waMsg.ProtocolMessage, info.Timestamp, isBackfill) + case waMsg.MessageHistoryBundle != nil: + part, contextInfo = mc.convertMessageHistoryShare(ctx, info, waMsg.MessageHistoryBundle.GetMessageHistoryMetadata(), waMsg.MessageHistoryBundle.GetContextInfo()) + case waMsg.MessageHistoryNotice != nil: + part, contextInfo = mc.convertMessageHistoryShare(ctx, info, waMsg.MessageHistoryNotice.GetMessageHistoryMetadata(), waMsg.MessageHistoryNotice.GetContextInfo()) case waMsg.EncCommentMessage != nil: part = failedCommentPart default: diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index 7ca4427..9222f78 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -21,6 +21,7 @@ import ( "encoding/base64" "fmt" "html/template" + "slices" "strings" "time" @@ -73,6 +74,86 @@ func (mc *MessageConverter) convertPlaceholderMessage(ctx context.Context, rawMs } } +func joinNaturalNames(names []string) string { + names = slices.DeleteFunc(names, func(name string) bool { + return name == "" + }) + switch len(names) { + case 0: + return "" + case 1: + return names[0] + case 2: + return names[0] + " and " + names[1] + default: + return strings.Join(names[:len(names)-1], ", ") + ", and " + names[len(names)-1] + } +} + +func (mc *MessageConverter) getHistoryReceiverName(ctx context.Context, receiver string) string { + jid, err := types.ParseJID(receiver) + if err != nil { + zerolog.Ctx(ctx).Err(err).Str("receiver_jid", receiver).Msg("Failed to parse message history receiver JID") + return receiver + } + _, displayname, err := mc.getBasicUserInfo(ctx, jid) + if err != nil { + zerolog.Ctx(ctx).Err(err).Stringer("receiver_jid", jid).Msg("Failed to resolve message history receiver") + if jid.User != "" { + return jid.User + } + return receiver + } + return displayname +} + +func messageHistoryStartTime(metadata *waE2E.MessageHistoryMetadata, fallback time.Time) time.Time { + if metadata == nil { + return fallback + } + if ts := metadata.GetOldestMessageTimestamp(); ts > 0 { + return time.Unix(ts, 0).Local() + } + return fallback +} + +func (mc *MessageConverter) convertMessageHistoryShare(ctx context.Context, info *types.MessageInfo, metadata *waE2E.MessageHistoryMetadata, contextInfo *waE2E.ContextInfo) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { + names := make([]string, 0, len(metadata.GetHistoryReceivers())) + for _, receiver := range metadata.GetHistoryReceivers() { + names = append(names, mc.getHistoryReceiverName(ctx, receiver)) + } + + receivers := joinNaturalNames(names) + var fallback time.Time + if info != nil { + fallback = info.Timestamp + } + startAt := messageHistoryStartTime(metadata, fallback) + body := "Message history shared." + if !startAt.IsZero() { + startTime := startAt.Format("Jan 2, 2006 at 3:04 PM") + body = fmt.Sprintf("Message history shared starting on %s.", startTime) + switch { + case info != nil && info.IsFromMe && receivers != "": + body = fmt.Sprintf("You sent %s message history that starts on %s.", receivers, startTime) + case receivers != "": + body = fmt.Sprintf("Sent %s message history that starts on %s.", receivers, startTime) + } + } else if info != nil && info.IsFromMe && receivers != "" { + body = fmt.Sprintf("You sent %s message history.", receivers) + } else if receivers != "" { + body = fmt.Sprintf("Sent %s message history.", receivers) + } + + return &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: &event.MessageEventContent{ + MsgType: event.MsgNotice, + Body: body, + }, + }, contextInfo +} + const inviteMsg = `%s
This invitation to join "%s" expires at %s. Reply to this message with %s accept to accept the invite.` const inviteMsgBroken = `%s
This invitation to join "%s" expires at %s. However, the invite message is broken or unsupported and cannot be accepted.` const GroupInviteMetaField = "fi.mau.whatsapp.invite" From 5a7217e0ffe5c6c126335fc86c5cc80be1a27eb7 Mon Sep 17 00:00:00 2001 From: Kishan Bagaria <1093313+KishanBagaria@users.noreply.github.com> Date: Fri, 27 Mar 2026 09:10:36 -0700 Subject: [PATCH 02/94] - --- pkg/msgconv/wa-misc.go | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index 9222f78..22c014c 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -21,7 +21,6 @@ import ( "encoding/base64" "fmt" "html/template" - "slices" "strings" "time" @@ -74,22 +73,6 @@ func (mc *MessageConverter) convertPlaceholderMessage(ctx context.Context, rawMs } } -func joinNaturalNames(names []string) string { - names = slices.DeleteFunc(names, func(name string) bool { - return name == "" - }) - switch len(names) { - case 0: - return "" - case 1: - return names[0] - case 2: - return names[0] + " and " + names[1] - default: - return strings.Join(names[:len(names)-1], ", ") + ", and " + names[len(names)-1] - } -} - func (mc *MessageConverter) getHistoryReceiverName(ctx context.Context, receiver string) string { jid, err := types.ParseJID(receiver) if err != nil { @@ -120,10 +103,12 @@ func messageHistoryStartTime(metadata *waE2E.MessageHistoryMetadata, fallback ti func (mc *MessageConverter) convertMessageHistoryShare(ctx context.Context, info *types.MessageInfo, metadata *waE2E.MessageHistoryMetadata, contextInfo *waE2E.ContextInfo) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { names := make([]string, 0, len(metadata.GetHistoryReceivers())) for _, receiver := range metadata.GetHistoryReceivers() { - names = append(names, mc.getHistoryReceiverName(ctx, receiver)) + if name := mc.getHistoryReceiverName(ctx, receiver); name != "" { + names = append(names, name) + } } - receivers := joinNaturalNames(names) + receivers := strings.Join(names, ", ") var fallback time.Time if info != nil { fallback = info.Timestamp From 21514cd218b01c81e36d3c9a2474d6957836d1fd Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 31 Mar 2026 19:34:11 +0300 Subject: [PATCH 03/94] dependencies: update mautrix-go --- go.mod | 14 +++++++------- go.sum | 36 ++++++++++++++--------------------- pkg/connector/handlematrix.go | 4 ++++ 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index e8f8bb1..a28f7c7 100644 --- a/go.mod +++ b/go.mod @@ -7,30 +7,30 @@ toolchain go1.26.1 tool go.mau.fi/util/cmd/maubuild require ( - github.com/lib/pq v1.11.2 - github.com/rs/zerolog v1.34.0 + github.com/lib/pq v1.12.0 + github.com/rs/zerolog v1.35.0 go.mau.fi/util v0.9.7 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260305215846-fc65416c22c4 + go.mau.fi/whatsmeow v0.0.0-20260327181659-02ec817e7cf4 golang.org/x/image v0.37.0 golang.org/x/net v0.52.0 golang.org/x/sync v0.20.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.5-0.20260322102453-0c955c396df7 + maunium.net/go/mautrix v0.26.5-0.20260331163037-18917f3bdc14 ) require ( filippo.io/edwards25519 v1.2.0 // indirect github.com/beeper/argo-go v1.1.2 // indirect github.com/coder/websocket v1.8.14 // indirect - github.com/coreos/go-systemd/v22 v22.6.0 // indirect + github.com/coreos/go-systemd/v22 v22.7.0 // indirect github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect github.com/google/uuid v1.6.0 // indirect 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.34 // indirect + github.com/mattn/go-sqlite3 v1.14.37 // indirect github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/rs/xid v1.6.0 // indirect @@ -40,7 +40,7 @@ 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.7.16 // indirect + github.com/yuin/goldmark v1.8.2 // indirect go.mau.fi/libsignal v0.2.1 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect golang.org/x/crypto v0.49.0 // indirect diff --git a/go.sum b/go.sum index 6fedc40..ff1245a 100644 --- a/go.sum +++ b/go.sum @@ -10,15 +10,13 @@ github.com/beeper/argo-go v1.1.2 h1:UQI2G8F+NLfGTOmTUI0254pGKx/HUU/etbUGTJv91Fs= github.com/beeper/argo-go v1.1.2/go.mod h1:M+LJAnyowKVQ6Rdj6XYGEn+qcVFkb3R/MUpqkGR0hM4= github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= -github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= +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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -30,21 +28,17 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= -github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo= +github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk= -github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.37 h1:3DOZp4cXis1cUIpCfXLtmlGolNLp2VEqhiB/PARNBIg= +github.com/mattn/go-sqlite3 v1.14.37/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 h1:rh2lKw/P/EqHa724vYH2+VVQ1YnW4u6EOXl0PMAovZE= github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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= @@ -52,8 +46,8 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= -github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI= +github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= 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= @@ -73,16 +67,16 @@ 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.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE= -github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= +github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.1 h1:vRZG4EzTn70XY6Oh/pVKrQGuMHBkAWlGRC22/85m9L0= go.mau.fi/libsignal v0.2.1/go.mod h1:iVvjrHyfQqWajOUaMEsIfo3IqgVMrhWcPiiEzk7NgoU= go.mau.fi/util v0.9.7 h1:AWGNbJfz1zRcQOKeOEYhKUG2fT+/26Gy6kyqcH8tnBg= go.mau.fi/util v0.9.7/go.mod h1:5T2f3ZWZFAGgmFwg3dGw7YK6kIsb9lryDzvynoR98pE= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260305215846-fc65416c22c4 h1:FGA3NtCVNeCJ+C+KBg1pODsrfxC/trM3RHFWIeY7y4c= -go.mau.fi/whatsmeow v0.0.0-20260305215846-fc65416c22c4/go.mod h1:mXCRFyPEPn4jqWz6Afirn8vY7DpHCPnlKq6I2cWwFHM= +go.mau.fi/whatsmeow v0.0.0-20260327181659-02ec817e7cf4 h1:E4A6eca9vMJQctC9DIfzUIg27TrJ8IrDHgkJwJ8WPUQ= +go.mau.fi/whatsmeow v0.0.0-20260327181659-02ec817e7cf4/go.mod h1:mXCRFyPEPn4jqWz6Afirn8vY7DpHCPnlKq6I2cWwFHM= 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.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= @@ -97,9 +91,7 @@ golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= @@ -115,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.26.5-0.20260322102453-0c955c396df7 h1:KUhlBHWGgknqYC2V8di4DFNh73atDtgPlqqO5FoLmPc= -maunium.net/go/mautrix v0.26.5-0.20260322102453-0c955c396df7/go.mod h1:YWw8NWTszsbyFAznboicBObwHPgTSLcuTbVX2kY7U2M= +maunium.net/go/mautrix v0.26.5-0.20260331163037-18917f3bdc14 h1:y+4gtqKBMTtcVUiAeWJnvp88JLo/h3myQPsz1rZfNOY= +maunium.net/go/mautrix v0.26.5-0.20260331163037-18917f3bdc14/go.mod h1:RUSMBPky3jhXB7Ux+AptfkEvFlJ4ajZKCYiXI8YzxVE= diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index a330a98..a0a67fa 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -391,6 +391,10 @@ func (wa *WhatsAppClient) HandleMatrixDisappearingTimer(ctx context.Context, msg } func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2.MatrixMembershipChange) (*bridgev2.MatrixMembershipResult, error) { + if msg.Type.IsSelf && msg.OrigSender != nil { + return nil, nil + } + portalJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return nil, err From e2869f2a1d968217d21e20416231bbe4ddf4b62b Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 2 Apr 2026 15:08:00 +0300 Subject: [PATCH 04/94] backfill: add support for on-demand backwards backfill requests --- pkg/connector/backfill.go | 238 +++++++++++++++++++++++++----- pkg/connector/config.go | 3 + pkg/connector/example-config.yaml | 3 + 3 files changed, 206 insertions(+), 38 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 21d0c4d..f33351f 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -115,12 +115,28 @@ func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context, Uint32("chunk_order", evt.GetChunkOrder()). Uint32("progress", evt.GetProgress()). Logger() - log.Debug().Msg("Downloading history sync") + log.Debug(). + Int64("oldest_msg_in_chunk_ts", evt.GetOldestMsgInChunkTimestampSec()). + Any("full_request_meta", evt.GetFullHistorySyncOnDemandRequestMetadata()). + Any("access_status", evt.GetMessageAccessStatus()). + Str("peer_data_request_session_id", evt.GetPeerDataRequestSessionID()). + Msg("Downloading history sync") blob, err := wa.Client.DownloadHistorySync(log.WithContext(ctx), evt, true) if err != nil { log.Err(err).Msg("Failed to download history sync") return } + if blob.GetSyncType() == waHistorySync.HistorySync_ON_DEMAND { + wa.handleOnDemandHistorySync(ctx, blob) + if err = wa.Main.DB.HSNotif.Delete(ctx, rowid); err != nil { + log.Err(err).Msg("Failed to delete queued on-demand history sync notification") + } else if err = wa.Client.DeleteMedia(ctx, whatsmeow.MediaHistory, evt.GetDirectPath(), evt.GetFileEncSHA256(), evt.GetEncHandle()); err != nil { + log.Err(err).Msg("Failed to delete history sync blob from server") + } else { + log.Debug().Msg("Finished handling on-demand history sync and deleted history sync blob from server") + } + return + } err = wa.Main.DB.DoTxn(ctx, nil, func(ctx context.Context) (innerErr error) { resetTimer, innerErr = wa.handleWAHistorySync(ctx, evt, blob, true) if innerErr != nil { @@ -134,6 +150,13 @@ func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context, }) if err != nil { log.Err(err).Msg("Failed to store history sync notification data") + } else { + err = wa.Client.DeleteMedia(ctx, whatsmeow.MediaHistory, evt.GetDirectPath(), evt.GetFileEncSHA256(), evt.GetEncHandle()) + if err != nil { + log.Err(err).Msg("Failed to delete history sync blob from server") + } else { + log.Debug().Msg("Deleted history sync blob from server") + } } return } @@ -178,7 +201,8 @@ func (wa *WhatsAppClient) handleWAHistorySync( Dict("notification_metadata", zerolog.Dict(). Int64("oldest_msg_in_chunk_ts", notif.GetOldestMsgInChunkTimestampSec()). Any("full_request_meta", notif.GetFullHistorySyncOnDemandRequestMetadata()). - Any("access_status", notif.GetMessageAccessStatus())). + Any("access_status", notif.GetMessageAccessStatus()). + Str("peer_data_request_session_id", notif.GetPeerDataRequestSessionID())). Msg("Storing history sync") start := time.Now() successfullySavedTotal := 0 @@ -275,7 +299,9 @@ func (wa *WhatsAppClient) handleWAHistorySync( Bool("archived", conv.GetArchived()). Uint32("pinned", conv.GetPinned()). Uint64("mute_end", conv.GetMuteEndTime()). - Uint32("unread_count", conv.GetUnreadCount()), + Uint32("unread_count", conv.GetUnreadCount()). + Bool("end_of_history", conv.GetEndOfHistoryTransfer()). + Stringer("end_of_history_type", conv.GetEndOfHistoryTransferType()), ). Msg("Collected messages to save from history sync conversation") @@ -439,24 +465,34 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet } var markRead bool var startTime, endTime *time.Time + var conv *wadb.Conversation + if params.Forward || wa.Main.Config.HistorySync.BackwardsOnDemand { + conv, err = wa.Main.DB.Conversation.Get(ctx, wa.UserLogin.ID, portalJID) + if err != nil { + return nil, fmt.Errorf("failed to get conversation from database: %w", err) + } + } if params.Forward { if params.AnchorMessage != nil { startTime = ptr.Ptr(params.AnchorMessage.Timestamp) } - conv, err := wa.Main.DB.Conversation.Get(ctx, wa.UserLogin.ID, portalJID) - if err != nil { - return nil, fmt.Errorf("failed to get conversation from database: %w", err) - } else if conv != nil { + if conv != nil { markRead = !ptr.Val(conv.MarkedAsUnread) && ptr.Val(conv.UnreadCount) == 0 } - } else if params.Cursor != "" { - endTimeUnix, err := strconv.ParseInt(string(params.Cursor), 10, 64) - if err != nil { - return nil, fmt.Errorf("failed to parse cursor: %w", err) + } else { + if params.AnchorMessage != nil { + endTime = ptr.Ptr(params.AnchorMessage.Timestamp) + } + if params.Cursor != "" { + endTimeUnix, err := strconv.ParseInt(string(params.Cursor), 10, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse cursor: %w", err) + } + cursorTime := time.Unix(endTimeUnix, 0) + if endTime == nil || cursorTime.Before(*endTime) { + endTime = &cursorTime + } } - endTime = ptr.Ptr(time.Unix(endTimeUnix, 0)) - } else if params.AnchorMessage != nil { - endTime = ptr.Ptr(params.AnchorMessage.Timestamp) } var anchorID types.MessageID if params.AnchorMessage != nil { @@ -465,19 +501,24 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet anchorID = parsedID.ID } } + var hasMore bool + if !params.Forward && wa.Main.Config.HistorySync.BackwardsOnDemand { + hasMore = conv != nil && ptr.Val(conv.EndOfHistoryTransferType) == waHistorySync.Conversation_COMPLETE_BUT_MORE_MESSAGES_REMAIN_ON_PRIMARY + } messages, err := wa.Main.DB.Message.GetBetween(ctx, wa.UserLogin.ID, portalJID, startTime, endTime, params.Count+1) if err != nil { 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) { + if hasMore { + return wa.fetchMessagesFromPhone(ctx, params) + } return &bridgev2.FetchMessagesResponse{ HasMore: false, Forward: params.Forward, }, nil } - hasMore := false - oldestTS := messages[len(messages)-1].GetMessageTimestamp() - newestTS := messages[0].GetMessageTimestamp() if len(messages) > params.Count { + oldestTS := messages[len(messages)-1].GetMessageTimestamp() hasMore = true // For safety, cut off messages with the oldest timestamp in the response. // Otherwise, if there are multiple messages with the same timestamp, the next fetch may miss some. @@ -488,19 +529,66 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet } } } - convertedMessages := make([]*bridgev2.BackfillMessage, len(messages)) + resp, err := wa.convertHistorySyncMessages(ctx, params.Portal, portalJID, messages, true) + if err != nil { + return nil, fmt.Errorf("failed to convert messages: %w", err) + } + resp.HasMore = hasMore + resp.Forward = params.Forward + resp.MarkRead = markRead + return resp, nil +} + +func (wa *WhatsAppClient) deleteHistorySyncMessages(ctx context.Context, portalJID types.JID, newestTS, oldestTS uint64) { + var err error + if (newestTS == 0 && oldestTS == 0) || (!wa.Main.Bridge.Config.Backfill.Queue.Enabled && !wa.Main.Bridge.Config.Backfill.WillPaginateManually) { + // If the backfill queue isn't enabled, delete all messages after backfilling a batch. + err = wa.Main.DB.Message.DeleteAllInChat(ctx, wa.UserLogin.ID, portalJID) + } else { + // Otherwise just delete the messages that got backfilled + err = wa.Main.DB.Message.DeleteBetween(ctx, wa.UserLogin.ID, portalJID, newestTS, oldestTS) + } + if err != nil { + zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to delete messages from database after backfill") + } +} + +func (wa *WhatsAppClient) convertHistorySyncMessages( + ctx context.Context, + portal *bridgev2.Portal, + portalJID types.JID, + messages []*waWeb.WebMessageInfo, + explodeOnError bool, +) (*bridgev2.FetchMessagesResponse, error) { + oldestTS := messages[len(messages)-1].GetMessageTimestamp() + newestTS := messages[0].GetMessageTimestamp() + convertedMessages := make([]*bridgev2.BackfillMessage, 0, len(messages)) var mediaRequests []*wadb.MediaRequest for i, msg := range messages { evt, err := wa.Client.ParseWebMessage(portalJID, msg) if err != nil { - // This should never happen because the info is already parsed once before being stored in the database - return nil, fmt.Errorf("failed to parse info of message %s: %w", msg.GetKey().GetID(), err) + if explodeOnError { + // This should never happen because the info is already parsed once before being stored in the database + return nil, fmt.Errorf("failed to parse info of message %s: %w", msg.GetKey().GetID(), err) + } + zerolog.Ctx(ctx).Warn().Err(err). + Int("msg_index", i). + Str("msg_id", msg.GetKey().GetID()). + Uint64("msg_time_seconds", msg.GetMessageTimestamp()). + Msg("Dropping historical message due to parse error") + continue + } + if !explodeOnError { + msgType := getMessageType(evt.Message) + if msgType == "ignore" || strings.HasPrefix(msgType, "unknown_protocol_") { + continue + } } - var mediaReq *wadb.MediaRequest isViewOnce := evt.IsViewOnce || evt.IsViewOnceV2 || evt.IsViewOnceV2Extension - convertedMessages[i], mediaReq = wa.convertHistorySyncMessage( - ctx, params.Portal, &evt.Info, evt.Message, evt.RawMessage, isViewOnce, msg.Reactions, + converted, mediaReq := wa.convertHistorySyncMessage( + ctx, portal, &evt.Info, evt.Message, evt.RawMessage, isViewOnce, msg.Reactions, ) + convertedMessages = append(convertedMessages, converted) if mediaReq != nil { mediaRequests = append(mediaRequests, mediaReq) } @@ -509,24 +597,10 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet return &bridgev2.FetchMessagesResponse{ Messages: convertedMessages, Cursor: networkid.PaginationCursor(strconv.FormatUint(oldestTS, 10)), - HasMore: hasMore, - Forward: endTime == nil, - MarkRead: markRead, - // TODO set remaining or total count CompleteCallback: func() { // TODO this only deletes after backfilling. If there's no need for backfill after a relogin, // the messages will be stuck in the database - var err error - if !wa.Main.Bridge.Config.Backfill.Queue.Enabled && !wa.Main.Bridge.Config.Backfill.WillPaginateManually { - // If the backfill queue isn't enabled, delete all messages after backfilling a batch. - err = wa.Main.DB.Message.DeleteAllInChat(ctx, wa.UserLogin.ID, portalJID) - } else { - // Otherwise just delete the messages that got backfilled - err = wa.Main.DB.Message.DeleteBetween(ctx, wa.UserLogin.ID, portalJID, newestTS, oldestTS) - } - if err != nil { - zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to delete messages from database after backfill") - } + wa.deleteHistorySyncMessages(ctx, portalJID, newestTS, oldestTS) if len(mediaRequests) > 0 { go func(ctx context.Context) { for _, req := range mediaRequests { @@ -544,6 +618,94 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet }, nil } +func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, params bridgev2.FetchMessagesParams) (*bridgev2.FetchMessagesResponse, error) { + if params.AnchorMessage == nil { + return nil, fmt.Errorf("anchor message is required to fetch messages from phone") + } + parsed, err := waid.ParseMessageID(params.AnchorMessage.ID) + if err != nil { + return nil, fmt.Errorf("failed to parse anchor message ID: %w", err) + } + + msgID := wa.Client.GenerateMessageID() + reqData := wa.Client.BuildHistorySyncRequest(&types.MessageInfo{ + MessageSource: types.MessageSource{ + Chat: parsed.Chat, + Sender: parsed.Sender, + IsFromMe: parsed.Sender.ToNonAD() == wa.JID.ToNonAD() || parsed.Sender.ToNonAD() == wa.Device.GetLID().ToNonAD(), + IsGroup: parsed.Chat.Server == types.GroupServer, + }, + ID: parsed.ID, + Timestamp: params.AnchorMessage.Timestamp, + }, 50) + zerolog.Ctx(ctx).Debug(). + Str("request_msg_id", msgID). + Any("anchor_msg_parsed", parsed). + Any("request_data", reqData). + Msg("Sending history sync request") + _, err = wa.Client.SendMessage(ctx, wa.JID.ToNonAD(), reqData, whatsmeow.SendRequestExtra{ + ID: msgID, + Peer: true, + }) + if err != nil { + return nil, fmt.Errorf("failed to send history sync request: %w", err) + } + return &bridgev2.FetchMessagesResponse{ + HasMore: true, + Pending: true, + }, nil +} + +func (wa *WhatsAppClient) handleOnDemandHistorySync(ctx context.Context, blob *waHistorySync.HistorySync) { + if len(blob.GetConversations()) > 1 { + zerolog.Ctx(ctx).Warn(). + Int("conversation_count", len(blob.GetConversations())). + Msg("Received on-demand history sync with multiple conversations") + } + for _, conv := range blob.GetConversations() { + portalJID, err := types.ParseJID(conv.GetID()) + if err != nil { + zerolog.Ctx(ctx).Err(err).Str("jid", conv.GetID()).Msg("Failed to parse portal JID") + continue + } + portal, err := wa.Main.Bridge.GetPortalByKey(ctx, wa.makeWAPortalKey(portalJID)) + if err != nil { + zerolog.Ctx(ctx).Err(err).Stringer("portal_jid", portalJID).Msg("Failed to get portal for on-demand history sync") + continue + } + ctx := zerolog.Ctx(ctx).With(). + Str("portal_id", string(portal.ID)). + Str("portal_receiver", string(portal.Receiver)). + Stringer("portal_mxid", portal.MXID). + Logger().WithContext(ctx) + portal.HandleRemoteBackfill(ctx, wa.UserLogin, &simplevent.Backfill{ + EventMeta: simplevent.EventMeta{ + Type: bridgev2.RemoteEventBackfill, + PortalKey: portal.PortalKey, + }, + GetDataFunc: func(ctx context.Context, portal *bridgev2.Portal) (*bridgev2.FetchMessagesResponse, error) { + if len(conv.GetMessages()) == 0 { + return &bridgev2.FetchMessagesResponse{}, nil + } + messages := make([]*waWeb.WebMessageInfo, len(conv.GetMessages())) + for i, rawMsg := range conv.GetMessages() { + messages[i] = rawMsg.Message + } + zerolog.Ctx(ctx).Debug(). + Int("message_count", len(messages)). + Stringer("end_of_history_type", conv.GetEndOfHistoryTransferType()). + Msg("Converting messages to bridge from on-demand history sync") + resp, err := wa.convertHistorySyncMessages(ctx, portal, portalJID, messages, false) + if err != nil { + return nil, err + } + resp.HasMore = conv.GetEndOfHistoryTransferType() == waHistorySync.Conversation_COMPLETE_BUT_MORE_MESSAGES_REMAIN_ON_PRIMARY + return resp, nil + }, + }) + } +} + func (wa *WhatsAppClient) convertHistorySyncMessage( ctx context.Context, portal *bridgev2.Portal, info *types.MessageInfo, msg, rawMsg *waE2E.Message, isViewOnce bool, reactions []*waWeb.Reaction, ) (*bridgev2.BackfillMessage, *wadb.MediaRequest) { diff --git a/pkg/connector/config.go b/pkg/connector/config.go index 6a097c2..2445647 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -70,6 +70,8 @@ type Config struct { RequestLocalTime int `yaml:"request_local_time"` MaxAsyncHandle int64 `yaml:"max_async_handle"` } `yaml:"media_requests"` + + BackwardsOnDemand bool `yaml:"backwards_on_demand"` } `yaml:"history_sync"` displaynameTemplate *template.Template `yaml:"-"` @@ -134,6 +136,7 @@ func upgradeConfig(helper up.Helper) { helper.Copy(up.Str, "history_sync", "media_requests", "request_method") helper.Copy(up.Int, "history_sync", "media_requests", "request_local_time") helper.Copy(up.Int, "history_sync", "media_requests", "max_async_handle") + helper.Copy(up.Bool, "history_sync", "backwards_on_demand") } type DisplaynameParams struct { diff --git a/pkg/connector/example-config.yaml b/pkg/connector/example-config.yaml index e441f67..564f25e 100644 --- a/pkg/connector/example-config.yaml +++ b/pkg/connector/example-config.yaml @@ -121,3 +121,6 @@ history_sync: request_local_time: 120 # Maximum number of media request responses to handle in parallel per user. max_async_handle: 2 + # Use on-demand history sync requests for fetching older messages? + # This only applies when using the backfill queue, never for forward backfills. + backwards_on_demand: false From b1c89119437dd963844894a607ed8ac4b75e9d25 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 3 Apr 2026 18:16:20 +0300 Subject: [PATCH 05/94] .github: add checklist to bug report template --- .github/ISSUE_TEMPLATE/bug.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index 3703df9..c10630f 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -7,10 +7,11 @@ type: Bug --- - -It's always best to ask in the Matrix room first, especially if you aren't sure -what details are needed. Issues with insufficient detail will likely just be -ignored or closed immediately. ---> +### Checklist + + + +* [ ] This is an actual bug, not just a setup issue (see the [troubleshooting docs](https://docs.mau.fi/bridges/general/troubleshooting.html) or ask in the Matrix room for setup help). +* [ ] I am certain that sufficient information is included. Ask in the Matrix room first if not. From 2fc79dba1a2f771d8034bda7f965ab197695c8c7 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 8 Apr 2026 19:59:07 +0300 Subject: [PATCH 06/94] dependencies: update mautrix-go --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index a28f7c7..80eacc1 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ tool go.mau.fi/util/cmd/maubuild require ( github.com/lib/pq v1.12.0 github.com/rs/zerolog v1.35.0 - go.mau.fi/util v0.9.7 + go.mau.fi/util v0.9.8-0.20260406161447-0300c476893a go.mau.fi/webp v0.2.0 go.mau.fi/whatsmeow v0.0.0-20260327181659-02ec817e7cf4 golang.org/x/image v0.37.0 @@ -17,7 +17,7 @@ require ( golang.org/x/sync v0.20.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.5-0.20260331163037-18917f3bdc14 + maunium.net/go/mautrix v0.26.5-0.20260408131844-9db6af36a393 ) require ( diff --git a/go.sum b/go.sum index ff1245a..d6183fc 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,8 @@ github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.1 h1:vRZG4EzTn70XY6Oh/pVKrQGuMHBkAWlGRC22/85m9L0= go.mau.fi/libsignal v0.2.1/go.mod h1:iVvjrHyfQqWajOUaMEsIfo3IqgVMrhWcPiiEzk7NgoU= -go.mau.fi/util v0.9.7 h1:AWGNbJfz1zRcQOKeOEYhKUG2fT+/26Gy6kyqcH8tnBg= -go.mau.fi/util v0.9.7/go.mod h1:5T2f3ZWZFAGgmFwg3dGw7YK6kIsb9lryDzvynoR98pE= +go.mau.fi/util v0.9.8-0.20260406161447-0300c476893a h1:OQQF3rTJH10l6+dcP0OKnYbNDMBTGoIZZINNJm8QBG8= +go.mau.fi/util v0.9.8-0.20260406161447-0300c476893a/go.mod h1:5T2f3ZWZFAGgmFwg3dGw7YK6kIsb9lryDzvynoR98pE= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= go.mau.fi/whatsmeow v0.0.0-20260327181659-02ec817e7cf4 h1:E4A6eca9vMJQctC9DIfzUIg27TrJ8IrDHgkJwJ8WPUQ= @@ -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.26.5-0.20260331163037-18917f3bdc14 h1:y+4gtqKBMTtcVUiAeWJnvp88JLo/h3myQPsz1rZfNOY= -maunium.net/go/mautrix v0.26.5-0.20260331163037-18917f3bdc14/go.mod h1:RUSMBPky3jhXB7Ux+AptfkEvFlJ4ajZKCYiXI8YzxVE= +maunium.net/go/mautrix v0.26.5-0.20260408131844-9db6af36a393 h1:mvoL0cW9nbMO6sJjyO1JZvK5C4y1wr7+JjyyU70Lhuw= +maunium.net/go/mautrix v0.26.5-0.20260408131844-9db6af36a393/go.mod h1:MX4DQLiBe0c7sI/wizruqdxHinSOWs42/DYsP9GH7Q4= From 8b16f9d470a4d783c40645176fa5da96ede94678 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 10 Apr 2026 17:34:05 +0300 Subject: [PATCH 07/94] backfill: log when deleting messages --- pkg/connector/backfill.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index f33351f..8a29311 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -549,7 +549,17 @@ func (wa *WhatsAppClient) deleteHistorySyncMessages(ctx context.Context, portalJ err = wa.Main.DB.Message.DeleteBetween(ctx, wa.UserLogin.ID, portalJID, newestTS, oldestTS) } if err != nil { - zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to delete messages from database after backfill") + zerolog.Ctx(ctx).Warn().Err(err). + Stringer("portal_jid", portalJID). + Uint64("newest_ts", newestTS). + Uint64("oldest_ts", oldestTS). + Msg("Failed to delete messages from database after backfill") + } else { + zerolog.Ctx(ctx).Debug(). + Stringer("portal_jid", portalJID). + Uint64("newest_ts", newestTS). + Uint64("oldest_ts", oldestTS). + Msg("Deleted history sync messages from database") } } From 520d7b9f266ff42bb8c7e944878ddbe830f6b694 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 10 Apr 2026 15:00:46 +0300 Subject: [PATCH 08/94] backfill: use new slow fetch flags --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/backfill.go | 11 +++++++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 80eacc1..15383af 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( golang.org/x/sync v0.20.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.5-0.20260408131844-9db6af36a393 + maunium.net/go/mautrix v0.26.5-0.20260410170402-f76d1a0ce631 ) require ( diff --git a/go.sum b/go.sum index d6183fc..c25697d 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.26.5-0.20260408131844-9db6af36a393 h1:mvoL0cW9nbMO6sJjyO1JZvK5C4y1wr7+JjyyU70Lhuw= -maunium.net/go/mautrix v0.26.5-0.20260408131844-9db6af36a393/go.mod h1:MX4DQLiBe0c7sI/wizruqdxHinSOWs42/DYsP9GH7Q4= +maunium.net/go/mautrix v0.26.5-0.20260410170402-f76d1a0ce631 h1:rx0v5XOqjBIlQ5oyD2tgRu8C2pHGJnkNyFoZcg4YbeU= +maunium.net/go/mautrix v0.26.5-0.20260410170402-f76d1a0ce631/go.mod h1:MX4DQLiBe0c7sI/wizruqdxHinSOWs42/DYsP9GH7Q4= diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 8a29311..8ce0cc5 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -509,7 +509,14 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet if err != nil { 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) { - if hasMore { + wa.deleteHistorySyncMessages(ctx, portalJID, 0, 0) + if hasMore && !params.AllowSlowFetch { + return &bridgev2.FetchMessagesResponse{ + MoreRequiresSlowFetch: true, + HasMore: true, + Forward: params.Forward, + }, nil + } else if hasMore { return wa.fetchMessagesFromPhone(ctx, params) } return &bridgev2.FetchMessagesResponse{ @@ -541,7 +548,7 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet func (wa *WhatsAppClient) deleteHistorySyncMessages(ctx context.Context, portalJID types.JID, newestTS, oldestTS uint64) { var err error - if (newestTS == 0 && oldestTS == 0) || (!wa.Main.Bridge.Config.Backfill.Queue.Enabled && !wa.Main.Bridge.Config.Backfill.WillPaginateManually) { + if (newestTS == 0 && oldestTS == 0) || !wa.Main.Bridge.Config.Backfill.Queue.AnyEnabled() { // If the backfill queue isn't enabled, delete all messages after backfilling a batch. err = wa.Main.DB.Message.DeleteAllInChat(ctx, wa.UserLogin.ID, portalJID) } else { From e218f9c62975035c1d325ec22b6428eca5bec2f2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 10 Apr 2026 20:12:51 +0300 Subject: [PATCH 09/94] backfill: log number of rows when deleting messages --- pkg/connector/backfill.go | 6 ++++-- pkg/connector/wadb/message.go | 18 ++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 8ce0cc5..e20d717 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -548,12 +548,13 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet func (wa *WhatsAppClient) deleteHistorySyncMessages(ctx context.Context, portalJID types.JID, newestTS, oldestTS uint64) { var err error + var rows int64 if (newestTS == 0 && oldestTS == 0) || !wa.Main.Bridge.Config.Backfill.Queue.AnyEnabled() { // If the backfill queue isn't enabled, delete all messages after backfilling a batch. - err = wa.Main.DB.Message.DeleteAllInChat(ctx, wa.UserLogin.ID, portalJID) + rows, err = wa.Main.DB.Message.DeleteAllInChat(ctx, wa.UserLogin.ID, portalJID) } else { // Otherwise just delete the messages that got backfilled - err = wa.Main.DB.Message.DeleteBetween(ctx, wa.UserLogin.ID, portalJID, newestTS, oldestTS) + rows, err = wa.Main.DB.Message.DeleteBetween(ctx, wa.UserLogin.ID, portalJID, newestTS, oldestTS) } if err != nil { zerolog.Ctx(ctx).Warn().Err(err). @@ -566,6 +567,7 @@ func (wa *WhatsAppClient) deleteHistorySyncMessages(ctx context.Context, portalJ Stringer("portal_jid", portalJID). Uint64("newest_ts", newestTS). Uint64("oldest_ts", oldestTS). + Int64("rows_affected", rows). Msg("Deleted history sync messages from database") } } diff --git a/pkg/connector/wadb/message.go b/pkg/connector/wadb/message.go index f2450c1..4b16002 100644 --- a/pkg/connector/wadb/message.go +++ b/pkg/connector/wadb/message.go @@ -116,9 +116,12 @@ func (mq *MessageQuery) GetBetween(ctx context.Context, loginID networkid.UserLo AsList() } -func (mq *MessageQuery) DeleteBetween(ctx context.Context, loginID networkid.UserLoginID, chatJID types.JID, before, after uint64) error { - _, err := mq.Exec(ctx, deleteHistorySyncMessagesBetweenQuery, mq.BridgeID, loginID, chatJID, before, after) - return err +func (mq *MessageQuery) DeleteBetween(ctx context.Context, loginID networkid.UserLoginID, chatJID types.JID, before, after uint64) (int64, error) { + res, err := mq.Exec(ctx, deleteHistorySyncMessagesBetweenQuery, mq.BridgeID, loginID, chatJID, before, after) + if err != nil { + return 0, err + } + return res.RowsAffected() } func (mq *MessageQuery) DeleteAll(ctx context.Context, loginID networkid.UserLoginID) error { @@ -126,9 +129,12 @@ func (mq *MessageQuery) DeleteAll(ctx context.Context, loginID networkid.UserLog return err } -func (mq *MessageQuery) DeleteAllInChat(ctx context.Context, loginID networkid.UserLoginID, chatJID types.JID) error { - _, err := mq.Exec(ctx, deleteHistorySyncMessagesForPortalQuery, mq.BridgeID, loginID, chatJID) - return err +func (mq *MessageQuery) DeleteAllInChat(ctx context.Context, loginID networkid.UserLoginID, chatJID types.JID) (int64, error) { + res, err := mq.Exec(ctx, deleteHistorySyncMessagesForPortalQuery, mq.BridgeID, loginID, chatJID) + if err != nil { + return 0, err + } + return res.RowsAffected() } func (mq *MessageQuery) ConversationHasMessages(ctx context.Context, loginID networkid.UserLoginID, chatJID types.JID) (exists bool, err error) { From 6510ff1ffd98479d472f0b1b43d3f08e89967b4f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 13 Apr 2026 12:34:32 +0300 Subject: [PATCH 10/94] backfill: do initial backfill even if only bootstrap is received --- pkg/connector/backfill.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index e20d717..90c1316 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -138,7 +138,7 @@ func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context, return } err = wa.Main.DB.DoTxn(ctx, nil, func(ctx context.Context) (innerErr error) { - resetTimer, innerErr = wa.handleWAHistorySync(ctx, evt, blob, true) + innerErr = wa.handleWAHistorySync(ctx, evt, blob, true) if innerErr != nil { return } @@ -151,6 +151,9 @@ func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context, if err != nil { log.Err(err).Msg("Failed to store history sync notification data") } else { + resetTimer = blob.GetSyncType() == waHistorySync.HistorySync_INITIAL_BOOTSTRAP || + blob.GetSyncType() == waHistorySync.HistorySync_RECENT || + blob.GetSyncType() == waHistorySync.HistorySync_FULL err = wa.Client.DeleteMedia(ctx, whatsmeow.MediaHistory, evt.GetDirectPath(), evt.GetFileEncSHA256(), evt.GetEncHandle()) if err != nil { log.Err(err).Msg("Failed to delete history sync blob from server") @@ -166,9 +169,9 @@ func (wa *WhatsAppClient) handleWAHistorySync( notif *waE2E.HistorySyncNotification, evt *waHistorySync.HistorySync, stopOnError bool, -) (bool, error) { +) error { if evt == nil || evt.SyncType == nil { - return false, nil + return nil } log := wa.UserLogin.Log.With(). Str("action", "store history sync"). @@ -193,7 +196,7 @@ func (wa *WhatsAppClient) handleWAHistorySync( Int("recent_sticker_count", len(evt.GetRecentStickers())). Int("past_participant_count", len(evt.GetPastParticipants())). Msg("Ignoring history sync") - return false, nil + return nil } log.Info(). Int("conversation_count", len(evt.GetConversations())). @@ -309,7 +312,7 @@ func (wa *WhatsAppClient) handleWAHistorySync( err = wa.Main.DB.Conversation.Put(ctx, wadb.NewConversation(wa.UserLogin.ID, jid, conv, maxTime)) if err != nil { if stopOnError { - return false, fmt.Errorf("failed to save conversation metadata for %s: %w", jid, err) + return fmt.Errorf("failed to save conversation metadata for %s: %w", jid, err) } log.Err(err).Msg("Failed to save conversation metadata") continue @@ -317,7 +320,7 @@ func (wa *WhatsAppClient) handleWAHistorySync( err = wa.Main.DB.Message.Put(ctx, wa.UserLogin.ID, jid, messages) if err != nil { if stopOnError { - return false, fmt.Errorf("failed to save messages in %s: %w", jid, err) + return fmt.Errorf("failed to save messages in %s: %w", jid, err) } log.Err(err).Msg("Failed to save messages") failedToSaveTotal += len(messages) @@ -327,7 +330,7 @@ func (wa *WhatsAppClient) handleWAHistorySync( err = wa.Main.Bridge.DB.BackfillTask.MarkNotDone(ctx, wa.makeWAPortalKey(jid), wa.UserLogin.ID) if err != nil { if stopOnError { - return false, fmt.Errorf("failed to mark backfill task as not done for %s: %w", jid, err) + return fmt.Errorf("failed to mark backfill task as not done for %s: %w", jid, err) } log.Err(err).Msg("Failed to mark backfill task as not done") } @@ -339,9 +342,7 @@ func (wa *WhatsAppClient) handleWAHistorySync( Int("total_message_count", totalMessageCount). Dur("duration", time.Since(start)). Msg("Finished storing history sync") - resetTimer := evt.GetSyncType() == waHistorySync.HistorySync_RECENT || - evt.GetSyncType() == waHistorySync.HistorySync_FULL - return resetTimer, nil + return nil } func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { From 3dc3c1a603e64a089f617537406a6fcbe74fb4fe Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 14 Apr 2026 14:38:18 +0300 Subject: [PATCH 11/94] dependencies: update --- go.mod | 6 +++--- go.sum | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 15383af..dfd3692 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module go.mau.fi/mautrix-whatsapp go 1.25.0 -toolchain go1.26.1 +toolchain go1.26.2 tool go.mau.fi/util/cmd/maubuild @@ -11,13 +11,13 @@ require ( github.com/rs/zerolog v1.35.0 go.mau.fi/util v0.9.8-0.20260406161447-0300c476893a go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260327181659-02ec817e7cf4 + go.mau.fi/whatsmeow v0.0.0-20260410162419-b95d92207080 golang.org/x/image v0.37.0 golang.org/x/net v0.52.0 golang.org/x/sync v0.20.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.5-0.20260410170402-f76d1a0ce631 + maunium.net/go/mautrix v0.26.5-0.20260413182302-f3fab8d38470 ) require ( diff --git a/go.sum b/go.sum index c25697d..a7d7ef6 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.8-0.20260406161447-0300c476893a h1:OQQF3rTJH10l6+dcP0OKnYbND go.mau.fi/util v0.9.8-0.20260406161447-0300c476893a/go.mod h1:5T2f3ZWZFAGgmFwg3dGw7YK6kIsb9lryDzvynoR98pE= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260327181659-02ec817e7cf4 h1:E4A6eca9vMJQctC9DIfzUIg27TrJ8IrDHgkJwJ8WPUQ= -go.mau.fi/whatsmeow v0.0.0-20260327181659-02ec817e7cf4/go.mod h1:mXCRFyPEPn4jqWz6Afirn8vY7DpHCPnlKq6I2cWwFHM= +go.mau.fi/whatsmeow v0.0.0-20260410162419-b95d92207080 h1:j7D8kKa7A1n9kUTmVqHfwHNtoSkgM7FsiSyko/Tye5o= +go.mau.fi/whatsmeow v0.0.0-20260410162419-b95d92207080/go.mod h1:mXCRFyPEPn4jqWz6Afirn8vY7DpHCPnlKq6I2cWwFHM= 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.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= @@ -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.26.5-0.20260410170402-f76d1a0ce631 h1:rx0v5XOqjBIlQ5oyD2tgRu8C2pHGJnkNyFoZcg4YbeU= -maunium.net/go/mautrix v0.26.5-0.20260410170402-f76d1a0ce631/go.mod h1:MX4DQLiBe0c7sI/wizruqdxHinSOWs42/DYsP9GH7Q4= +maunium.net/go/mautrix v0.26.5-0.20260413182302-f3fab8d38470 h1:vehV8Ev2TzpV5DH9ToCxt43svkXRKcn/kJaZ4mNvRFQ= +maunium.net/go/mautrix v0.26.5-0.20260413182302-f3fab8d38470/go.mod h1:MX4DQLiBe0c7sI/wizruqdxHinSOWs42/DYsP9GH7Q4= From 32ad31902ef24730e5dcf29a0f8f6fab2414c67f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 16 Apr 2026 16:44:29 +0300 Subject: [PATCH 12/94] Bump version to v26.04 --- CHANGELOG.md | 7 +++++ cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 26 +++++++++--------- go.sum | 52 ++++++++++++++++++------------------ 4 files changed, 47 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cefd6d..892be2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# v26.04 + +* Added support for @room mentions in both directions. +* Changed initial backfill to happen even if WhatsApp doesn't send full history. +* Fixed panic when handling updates to unknown polls from WhatsApp. +* Fixed some background loops not stopping when a user is logged out. + # v26.03 * Added option to save outgoing messages in the database to allow encryption diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index e64f02f..b97bc7f 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.03", + Version: "26.04", SemCalVer: true, Connector: &connector.WhatsAppConnector{}, } diff --git a/go.mod b/go.mod index dfd3692..9aaca46 100644 --- a/go.mod +++ b/go.mod @@ -7,17 +7,17 @@ toolchain go1.26.2 tool go.mau.fi/util/cmd/maubuild require ( - github.com/lib/pq v1.12.0 + github.com/lib/pq v1.12.3 github.com/rs/zerolog v1.35.0 - go.mau.fi/util v0.9.8-0.20260406161447-0300c476893a + go.mau.fi/util v0.9.8 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260410162419-b95d92207080 - golang.org/x/image v0.37.0 - golang.org/x/net v0.52.0 + go.mau.fi/whatsmeow v0.0.0-20260416104156-3ff20cd3462a + golang.org/x/image v0.39.0 + golang.org/x/net v0.53.0 golang.org/x/sync v0.20.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.5-0.20260413182302-f3fab8d38470 + maunium.net/go/mautrix v0.27.0 ) require ( @@ -30,8 +30,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.37 // indirect - github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 // indirect + github.com/mattn/go-sqlite3 v1.14.42 // indirect + github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 // 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 @@ -43,11 +43,11 @@ require ( github.com/yuin/goldmark v1.8.2 // indirect go.mau.fi/libsignal v0.2.1 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect - golang.org/x/crypto v0.49.0 // indirect - golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect - golang.org/x/mod v0.34.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/text v0.35.0 // indirect + golang.org/x/crypto v0.50.0 // indirect + golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/text v0.36.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 a7d7ef6..dd982b9 100644 --- a/go.sum +++ b/go.sum @@ -28,16 +28,16 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo= -github.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= +github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= 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.37 h1:3DOZp4cXis1cUIpCfXLtmlGolNLp2VEqhiB/PARNBIg= -github.com/mattn/go-sqlite3 v1.14.37/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 h1:rh2lKw/P/EqHa724vYH2+VVQ1YnW4u6EOXl0PMAovZE= -github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/mattn/go-sqlite3 v1.14.42 h1:MigqEP4ZmHw3aIdIT7T+9TLa90Z6smwcthx+Azv4Cgo= +github.com/mattn/go-sqlite3 v1.14.42/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= +github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 h1:WDsQxOJDy0N1VRAjXLpi8sCEZRSGarLWQevDxpTBRrM= +github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81/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= @@ -71,31 +71,31 @@ github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.1 h1:vRZG4EzTn70XY6Oh/pVKrQGuMHBkAWlGRC22/85m9L0= go.mau.fi/libsignal v0.2.1/go.mod h1:iVvjrHyfQqWajOUaMEsIfo3IqgVMrhWcPiiEzk7NgoU= -go.mau.fi/util v0.9.8-0.20260406161447-0300c476893a h1:OQQF3rTJH10l6+dcP0OKnYbNDMBTGoIZZINNJm8QBG8= -go.mau.fi/util v0.9.8-0.20260406161447-0300c476893a/go.mod h1:5T2f3ZWZFAGgmFwg3dGw7YK6kIsb9lryDzvynoR98pE= +go.mau.fi/util v0.9.8 h1:+/jf8eM2dAT2wx9UidmaneH28r/CSCKCniCyby1qWz8= +go.mau.fi/util v0.9.8/go.mod h1:up/5mbzH2M1pSBNXqRxODn8dg/hEKbLJu92W4/SNAX0= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260410162419-b95d92207080 h1:j7D8kKa7A1n9kUTmVqHfwHNtoSkgM7FsiSyko/Tye5o= -go.mau.fi/whatsmeow v0.0.0-20260410162419-b95d92207080/go.mod h1:mXCRFyPEPn4jqWz6Afirn8vY7DpHCPnlKq6I2cWwFHM= +go.mau.fi/whatsmeow v0.0.0-20260416104156-3ff20cd3462a h1:/7erOAOkZ5d/k9bghMMQPciR0ypmOsM8wGv7bIwyyZo= +go.mau.fi/whatsmeow v0.0.0-20260416104156-3ff20cd3462a/go.mod h1:B/y3nOUaK8BDJKvyvq6YbLh2UKTCoiA5xQ2sFwbuOWk= 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.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= -golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= -golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= -golang.org/x/image v0.37.0 h1:ZiRjArKI8GwxZOoEtUfhrBtaCN+4b/7709dlT6SSnQA= -golang.org/x/image v0.37.0/go.mod h1:/3f6vaXC+6CEanU4KJxbcUZyEePbyKbaLoDOe4ehFYY= -golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= +golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= +golang.org/x/image v0.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww= +golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -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.26.5-0.20260413182302-f3fab8d38470 h1:vehV8Ev2TzpV5DH9ToCxt43svkXRKcn/kJaZ4mNvRFQ= -maunium.net/go/mautrix v0.26.5-0.20260413182302-f3fab8d38470/go.mod h1:MX4DQLiBe0c7sI/wizruqdxHinSOWs42/DYsP9GH7Q4= +maunium.net/go/mautrix v0.27.0 h1:yfEYwoIluVWkofUgbZl9gP4i5nQTF+QNsxtb+r5bKlM= +maunium.net/go/mautrix v0.27.0/go.mod h1:7QpEQiTy6p4LHkXXaZI+N46tGYy8HMhD0JjzZAFoFWs= From 7254783a32bb3361efc716c9571a2b33cfacd9a1 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 24 Apr 2026 12:46:48 +0300 Subject: [PATCH 13/94] backfill: add debug logs for incorrect timestamps from phone --- pkg/connector/backfill.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 90c1316..b4ef181 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -246,7 +246,7 @@ func (wa *WhatsAppClient) handleWAHistorySync( return c.Stringer("chat_jid", jid) }) - var minTime, maxTime time.Time + var minTime, maxTime, firstItemTime, lastItemTime time.Time var minTimeIndex, maxTimeIndex int ignoredTypes := 0 @@ -262,6 +262,10 @@ func (wa *WhatsAppClient) handleWAHistorySync( Msg("Dropping historical message due to parse error") continue } + if firstItemTime.IsZero() { + firstItemTime = msgEvt.Info.Timestamp + } + lastItemTime = msgEvt.Info.Timestamp if minTime.IsZero() || msgEvt.Info.Timestamp.Before(minTime) { minTime = msgEvt.Info.Timestamp minTimeIndex = i @@ -294,6 +298,9 @@ func (wa *WhatsAppClient) handleWAHistorySync( 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()). From 8af9a2f4977333d928b73bb9a85aaffc41ccc829 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 28 Apr 2026 18:16:17 +0300 Subject: [PATCH 14/94] msgconv/sticker: parse metadata from received stickers --- go.mod | 8 +- go.sum | 12 +- pkg/connector/directmedia.go | 2 +- pkg/msgconv/wa-media.go | 102 +------------- pkg/msgconv/wa-sticker.go | 266 +++++++++++++++++++++++++++++++++++ 5 files changed, 282 insertions(+), 108 deletions(-) create mode 100644 pkg/msgconv/wa-sticker.go diff --git a/go.mod b/go.mod index 9aaca46..3054e5a 100644 --- a/go.mod +++ b/go.mod @@ -9,15 +9,16 @@ tool go.mau.fi/util/cmd/maubuild require ( github.com/lib/pq v1.12.3 github.com/rs/zerolog v1.35.0 - go.mau.fi/util v0.9.8 + github.com/tidwall/gjson v1.18.0 + go.mau.fi/util v0.9.9-0.20260428124215-c47a7212562e go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260416104156-3ff20cd3462a + go.mau.fi/whatsmeow v0.0.0-20260427122815-7514259253a7 golang.org/x/image v0.39.0 golang.org/x/net v0.53.0 golang.org/x/sync v0.20.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.27.0 + maunium.net/go/mautrix v0.27.1-0.20260428110059-49a05bf06436 ) require ( @@ -35,7 +36,6 @@ require ( 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 - github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect diff --git a/go.sum b/go.sum index dd982b9..ebe18fa 100644 --- a/go.sum +++ b/go.sum @@ -71,12 +71,12 @@ github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.1 h1:vRZG4EzTn70XY6Oh/pVKrQGuMHBkAWlGRC22/85m9L0= go.mau.fi/libsignal v0.2.1/go.mod h1:iVvjrHyfQqWajOUaMEsIfo3IqgVMrhWcPiiEzk7NgoU= -go.mau.fi/util v0.9.8 h1:+/jf8eM2dAT2wx9UidmaneH28r/CSCKCniCyby1qWz8= -go.mau.fi/util v0.9.8/go.mod h1:up/5mbzH2M1pSBNXqRxODn8dg/hEKbLJu92W4/SNAX0= +go.mau.fi/util v0.9.9-0.20260428124215-c47a7212562e h1:o0O9sLa4CeZbxbgoSqavwaORrt9BB+trOLKBSoGzJ3Q= +go.mau.fi/util v0.9.9-0.20260428124215-c47a7212562e/go.mod h1:up/5mbzH2M1pSBNXqRxODn8dg/hEKbLJu92W4/SNAX0= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260416104156-3ff20cd3462a h1:/7erOAOkZ5d/k9bghMMQPciR0ypmOsM8wGv7bIwyyZo= -go.mau.fi/whatsmeow v0.0.0-20260416104156-3ff20cd3462a/go.mod h1:B/y3nOUaK8BDJKvyvq6YbLh2UKTCoiA5xQ2sFwbuOWk= +go.mau.fi/whatsmeow v0.0.0-20260427122815-7514259253a7 h1:jEOI4I7kU+MYUNI1L94rhYXhUg8N9+YUNHVY525aYTc= +go.mau.fi/whatsmeow v0.0.0-20260427122815-7514259253a7/go.mod h1:ijfkzOXauA/Vz/htXEMfOAJSUgglribW5oQeYC9tSSg= 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.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= @@ -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.27.0 h1:yfEYwoIluVWkofUgbZl9gP4i5nQTF+QNsxtb+r5bKlM= -maunium.net/go/mautrix v0.27.0/go.mod h1:7QpEQiTy6p4LHkXXaZI+N46tGYy8HMhD0JjzZAFoFWs= +maunium.net/go/mautrix v0.27.1-0.20260428110059-49a05bf06436 h1:vga9ypiOLJmGguxq4D1aquDPFihOuD99EGPEwva12UI= +maunium.net/go/mautrix v0.27.1-0.20260428110059-49a05bf06436/go.mod h1:4fZ0M0xB5ZtueQI65RilX28J/3794BeK+LaCg4U61Jk= diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index a4a3d73..10cdbbb 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -203,7 +203,7 @@ func (wa *WhatsAppConnector) downloadMessageDirectMedia(ctx context.Context, par return nil, fmt.Errorf("failed to seek to start of sticker zip: %w", err) } else if zipData, err := io.ReadAll(f); err != nil { return nil, fmt.Errorf("failed to read sticker zip: %w", err) - } else if data, err := msgconv.ExtractAnimatedSticker(zipData); err != nil { + } else if data, _, err := msgconv.ExtractAnimatedSticker(zipData); err != nil { return nil, fmt.Errorf("failed to extract animated sticker: %w %x", err, zipData) } else if _, err := f.WriteAt(data, 0); err != nil { return nil, fmt.Errorf("failed to write animated sticker to file: %w", err) diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index 9a1ceb3..d1b5332 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -17,8 +17,6 @@ package msgconv import ( - "archive/zip" - "bytes" "context" "encoding/json" "errors" @@ -26,15 +24,11 @@ import ( "io" "net/http" "os" - "path/filepath" - "strconv" "strings" "github.com/rs/zerolog" "go.mau.fi/util/exmime" "go.mau.fi/util/exslices" - "go.mau.fi/util/lottie" - "go.mau.fi/util/random" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waE2E" "go.mau.fi/whatsmeow/types" @@ -198,7 +192,9 @@ type PreparedMedia struct { } func (pm *PreparedMedia) FillFileName() *PreparedMedia { - if pm.FileName == "" { + if pm.Type == event.EventSticker { + pm.FileName = "" + } else if pm.FileName == "" { pm.FileName = strings.TrimPrefix(string(pm.MsgType), "m.") + exmime.ExtensionFromMimetype(pm.Info.MimeType) } return pm @@ -287,9 +283,6 @@ func prepareMediaMessage(rawMsg MediaMessage) *PreparedMedia { case *waE2E.StickerMessage: data.Type = event.EventSticker data.FileName = "sticker" + exmime.ExtensionFromMimetype(msg.GetMimetype()) - if msg.GetMimetype() == "application/was" && data.FileName == "sticker" { - data.FileName = "sticker.json" - } if data.Info.Width == data.Info.Height { data.Info.Width = WhatsAppStickerSize data.Info.Height = WhatsAppStickerSize @@ -397,6 +390,8 @@ func (mc *MessageConverter) reuploadWhatsAppAttachment( if err != nil { return err } + } else if part.Type == event.EventSticker && part.Info.MimeType == "image/webp" { + mc.fillWebPStickerInfo(ctx, part, data) } if part.Info.MimeType == "" { part.Info.MimeType = http.DetectContentType(data) @@ -425,68 +420,6 @@ func (mc *MessageConverter) reuploadWhatsAppAttachment( return nil } -func (mc *MessageConverter) extractAnimatedSticker(fileInfo *PreparedMedia, data []byte) ([]byte, error) { - data, err := ExtractAnimatedSticker(data) - if err != nil { - return nil, err - } - fileInfo.Info.MimeType = "video/lottie+json" - fileInfo.FileName = "sticker.json" - return data, nil -} - -func (mc *MessageConverter) convertAnimatedSticker(ctx context.Context, fileInfo *PreparedMedia, data []byte) ([]byte, []byte, *event.FileInfo, error) { - data, err := mc.extractAnimatedSticker(fileInfo, data) - if err != nil { - return nil, nil, nil, err - } - c := mc.AnimatedStickerConfig - if c.Target == "disable" { - return data, nil, nil, nil - } else if !lottie.Supported() { - zerolog.Ctx(ctx).Warn().Msg("Animated sticker conversion is enabled, but lottieconverter is not installed") - return data, nil, nil, nil - } - input := bytes.NewReader(data) - fileInfo.Info.MimeType = "image/" + c.Target - fileInfo.FileName = "sticker." + c.Target - switch c.Target { - case "png": - var output bytes.Buffer - err = lottie.Convert(ctx, input, "", &output, c.Target, c.Args.Width, c.Args.Height, "1") - return output.Bytes(), nil, nil, err - case "gif": - var output bytes.Buffer - err = lottie.Convert(ctx, input, "", &output, c.Target, c.Args.Width, c.Args.Height, strconv.Itoa(c.Args.FPS)) - return output.Bytes(), nil, nil, err - case "webm", "webp": - tmpFile := filepath.Join(os.TempDir(), fmt.Sprintf("mautrix-whatsapp-lottieconverter-%s.%s", random.String(10), c.Target)) - defer func() { - _ = os.Remove(tmpFile) - }() - thumbnailData, err := lottie.FFmpegConvert(ctx, input, tmpFile, c.Args.Width, c.Args.Height, c.Args.FPS) - if err != nil { - return nil, nil, nil, err - } - data, err = os.ReadFile(tmpFile) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to read converted file: %w", err) - } - var thumbnailInfo *event.FileInfo - if thumbnailData != nil { - thumbnailInfo = &event.FileInfo{ - MimeType: "image/png", - Width: c.Args.Width, - Height: c.Args.Height, - Size: len(thumbnailData), - } - } - return data, thumbnailData, thumbnailInfo, nil - default: - return nil, nil, nil, fmt.Errorf("unsupported target format %s", c.Target) - } -} - func (mc *MessageConverter) makeMediaFailure(ctx context.Context, mediaInfo *PreparedMedia, keys *FailedMediaKeys, err error) *bridgev2.ConvertedMessagePart { logLevel := zerolog.ErrorLevel var extra map[string]any @@ -531,28 +464,3 @@ func (mc *MessageConverter) makeMediaFailure(ctx context.Context, mediaInfo *Pre } return part } - -func ExtractAnimatedSticker(data []byte) ([]byte, error) { - zipReader, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) - if err != nil { - return nil, fmt.Errorf("failed to read sticker zip: %w", err) - } - animationFile, err := zipReader.Open("animation/animation.json") - if err != nil { - return nil, fmt.Errorf("failed to open animation.json: %w", err) - } - animationFileInfo, err := animationFile.Stat() - if err != nil { - _ = animationFile.Close() - return nil, fmt.Errorf("failed to stat animation.json: %w", err) - } else if animationFileInfo.Size() > uploadFileThreshold { - _ = animationFile.Close() - return nil, fmt.Errorf("animation.json is too large (%.2f MiB)", float64(animationFileInfo.Size())/1024/1024) - } - data, err = io.ReadAll(animationFile) - _ = animationFile.Close() - if err != nil { - return nil, fmt.Errorf("failed to read animation.json: %w", err) - } - return data, nil -} diff --git a/pkg/msgconv/wa-sticker.go b/pkg/msgconv/wa-sticker.go new file mode 100644 index 0000000..fef111a --- /dev/null +++ b/pkg/msgconv/wa-sticker.go @@ -0,0 +1,266 @@ +// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge. +// Copyright (C) 2026 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package msgconv + +import ( + "archive/zip" + "bytes" + "context" + "encoding/binary" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/rs/zerolog" + "github.com/tidwall/gjson" + "go.mau.fi/util/exstrings" + "go.mau.fi/util/lottie" + "go.mau.fi/util/random" + "maunium.net/go/mautrix/event" +) + +type StickerMetadata struct { + StickerPackID string `json:"sticker-pack-id"` + AccessibilityText string `json:"accessibility-text"` + Emojis []string `json:"emojis"` + IsFirstPartySticker int `json:"is-first-party-sticker"` +} + +func (sm *StickerMetadata) ToMatrix(content *event.MessageEventContent) { + if sm == nil { + return + } + if sm.StickerPackID != "" { + content.Info.BridgedSticker = &event.BridgedSticker{ + Network: StickerSourceID, + PackURL: StickerPackURLPrefix + sm.StickerPackID, + } + if len(sm.Emojis) > 0 { + content.Info.BridgedSticker.Emoji = sm.Emojis[0] + } + } + if sm.AccessibilityText != "" { + content.Body = sm.AccessibilityText + } else if len(sm.Emojis) > 0 { + content.Body = strings.Join(sm.Emojis, " ") + } +} + +const StickerSourceID = "whatsapp" +const StickerPackURLPrefix = "https://wa.me/stickerpack/" + +func ExtractAnimatedSticker(data []byte) ([]byte, *StickerMetadata, error) { + zipReader, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) + if err != nil { + return nil, nil, fmt.Errorf("failed to read sticker zip: %w", err) + } + animationFile, err := zipReader.Open("animation/animation.json") + if err != nil { + return nil, nil, fmt.Errorf("failed to open animation.json: %w", err) + } + animationFileInfo, err := animationFile.Stat() + if err != nil { + _ = animationFile.Close() + return nil, nil, fmt.Errorf("failed to stat animation.json: %w", err) + } else if animationFileInfo.Size() > uploadFileThreshold { + _ = animationFile.Close() + return nil, nil, fmt.Errorf("animation.json is too large (%.2f MiB)", float64(animationFileInfo.Size())/1024/1024) + } + data, err = io.ReadAll(animationFile) + _ = animationFile.Close() + if err != nil { + return nil, nil, fmt.Errorf("failed to read animation.json: %w", err) + } + var meta StickerMetadata + metaFile, err := zipReader.Open("animation/animation.json.overridden_metadata") + if err == nil { + _ = json.NewDecoder(metaFile).Decode(&meta) + _ = metaFile.Close() + } + if meta.StickerPackID == "" { + res := gjson.GetBytes(data, "metadata.customProps") + if res.IsObject() { + _ = json.Unmarshal(exstrings.UnsafeBytes(res.Raw), &meta) + } + } + return data, &meta, nil +} + +func (mc *MessageConverter) extractAnimatedSticker(fileInfo *PreparedMedia, data []byte) ([]byte, error) { + data, meta, err := ExtractAnimatedSticker(data) + if err != nil { + return nil, err + } + meta.ToMatrix(fileInfo.MessageEventContent) + fileInfo.Info.MimeType = "video/lottie+json" + fileInfo.FileName = "sticker.json" + return data, nil +} + +func (mc *MessageConverter) convertAnimatedSticker(ctx context.Context, fileInfo *PreparedMedia, data []byte) ([]byte, []byte, *event.FileInfo, error) { + data, err := mc.extractAnimatedSticker(fileInfo, data) + if err != nil { + return nil, nil, nil, err + } + c := mc.AnimatedStickerConfig + if c.Target == "disable" { + return data, nil, nil, nil + } else if !lottie.Supported() { + zerolog.Ctx(ctx).Warn().Msg("Animated sticker conversion is enabled, but lottieconverter is not installed") + return data, nil, nil, nil + } + input := bytes.NewReader(data) + fileInfo.Info.MimeType = "image/" + c.Target + fileInfo.FileName = "sticker." + c.Target + switch c.Target { + case "png": + var output bytes.Buffer + err = lottie.Convert(ctx, input, "", &output, c.Target, c.Args.Width, c.Args.Height, "1") + return output.Bytes(), nil, nil, err + case "gif": + var output bytes.Buffer + err = lottie.Convert(ctx, input, "", &output, c.Target, c.Args.Width, c.Args.Height, strconv.Itoa(c.Args.FPS)) + return output.Bytes(), nil, nil, err + case "webm", "webp": + tmpFile := filepath.Join(os.TempDir(), fmt.Sprintf("mautrix-whatsapp-lottieconverter-%s.%s", random.String(10), c.Target)) + defer func() { + _ = os.Remove(tmpFile) + }() + thumbnailData, err := lottie.FFmpegConvert(ctx, input, tmpFile, c.Args.Width, c.Args.Height, c.Args.FPS) + if err != nil { + return nil, nil, nil, err + } + data, err = os.ReadFile(tmpFile) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to read converted file: %w", err) + } + var thumbnailInfo *event.FileInfo + if thumbnailData != nil { + thumbnailInfo = &event.FileInfo{ + MimeType: "image/png", + Width: c.Args.Width, + Height: c.Args.Height, + Size: len(thumbnailData), + } + } + return data, thumbnailData, thumbnailInfo, nil + default: + return nil, nil, nil, fmt.Errorf("unsupported target format %s", c.Target) + } +} + +func (mc *MessageConverter) fillWebPStickerInfo(ctx context.Context, fileInfo *PreparedMedia, data []byte) { + meta, err := extractWebPStickerMetadata(data) + if err != nil { + zerolog.Ctx(ctx).Debug().Err(err).Msg("Failed to extract webp sticker metadata") + return + } + meta.ToMatrix(fileInfo.MessageEventContent) +} + +// stickerMetadataEXIFTag is the custom EXIF tag WhatsApp uses to embed +// sticker pack metadata as a JSON object inside non-animated webp stickers. +const stickerMetadataEXIFTag = 0x5741 + +// extractWebPStickerMetadata parses the WhatsApp sticker pack metadata JSON +// embedded in EXIF tag 0x5741 of a non-animated webp sticker. +func extractWebPStickerMetadata(data []byte) (*StickerMetadata, error) { + exif, err := findWebPChunk(data, "EXIF") + if err != nil { + return nil, err + } + raw, err := findEXIFTagValue(exif, stickerMetadataEXIFTag) + if err != nil { + return nil, err + } + var meta StickerMetadata + err = json.Unmarshal(raw, &meta) + if err != nil { + return nil, fmt.Errorf("failed to parse sticker metadata JSON: %w", err) + } + return &meta, nil +} + +func findWebPChunk(data []byte, chunkType string) ([]byte, error) { + if len(data) < 12 || string(data[0:4]) != "RIFF" || string(data[8:12]) != "WEBP" { + return nil, fmt.Errorf("not a webp file") + } + for pos := 12; pos+8 <= len(data); { + size := binary.LittleEndian.Uint32(data[pos+4 : pos+8]) + start := pos + 8 + end := start + int(size) + if end > len(data) { + return nil, fmt.Errorf("webp chunk %q extends past end of file", data[pos:pos+4]) + } + if string(data[pos:pos+4]) == chunkType { + return data[start:end], nil + } + pos = end + if pos%2 != 0 { + pos++ + } + } + return nil, fmt.Errorf("webp chunk %q not found", chunkType) +} + +func findEXIFTagValue(exif []byte, tag uint16) ([]byte, error) { + if len(exif) < 8 { + return nil, fmt.Errorf("exif data too short") + } + var bo binary.ByteOrder + switch string(exif[0:2]) { + case "II": + bo = binary.LittleEndian + case "MM": + bo = binary.BigEndian + default: + return nil, fmt.Errorf("invalid TIFF byte order %q", exif[0:2]) + } + if bo.Uint16(exif[2:4]) != 0x002A { + return nil, fmt.Errorf("invalid TIFF magic") + } + ifdOffset := int(bo.Uint32(exif[4:8])) + if ifdOffset < 0 || ifdOffset+2 > len(exif) { + return nil, fmt.Errorf("IFD offset out of range") + } + count := int(bo.Uint16(exif[ifdOffset : ifdOffset+2])) + entries := ifdOffset + 2 + if entries+count*12 > len(exif) { + return nil, fmt.Errorf("IFD entries out of range") + } + for i := 0; i < count; i++ { + entry := exif[entries+i*12 : entries+(i+1)*12] + if bo.Uint16(entry[0:2]) != tag { + continue + } + // Tag 0x5741 stores JSON as type 7 (UNDEFINED), where size == count bytes. + size := int(bo.Uint32(entry[4:8])) + if size <= 4 { + return entry[8 : 8+size], nil + } + offset := int(bo.Uint32(entry[8:12])) + if offset+size > len(exif) { + return nil, fmt.Errorf("exif tag value out of range") + } + return exif[offset : offset+size], nil + } + return nil, fmt.Errorf("exif tag 0x%04x not found", tag) +} From 3f568f0133a50e7b64b651a91d8dae59e7558ff7 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 29 Apr 2026 09:10:15 +0300 Subject: [PATCH 15/94] 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 3054e5a..f499a24 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.20.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.27.1-0.20260428110059-49a05bf06436 + maunium.net/go/mautrix v0.27.1-0.20260429060852-d7aad0e862c7 ) require ( diff --git a/go.sum b/go.sum index ebe18fa..38542dc 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.27.1-0.20260428110059-49a05bf06436 h1:vga9ypiOLJmGguxq4D1aquDPFihOuD99EGPEwva12UI= -maunium.net/go/mautrix v0.27.1-0.20260428110059-49a05bf06436/go.mod h1:4fZ0M0xB5ZtueQI65RilX28J/3794BeK+LaCg4U61Jk= +maunium.net/go/mautrix v0.27.1-0.20260429060852-d7aad0e862c7 h1:ZL/dTgBuj7ZzH543brFUvxZo2lJGsCMBvnfKIvjdHC4= +maunium.net/go/mautrix v0.27.1-0.20260429060852-d7aad0e862c7/go.mod h1:4fZ0M0xB5ZtueQI65RilX28J/3794BeK+LaCg4U61Jk= From 0632478ce0deb14fa0e6860655cfc123a5626100 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 29 Apr 2026 19:04:18 +0300 Subject: [PATCH 16/94] 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 f499a24..63b26bc 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.20.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.27.1-0.20260429060852-d7aad0e862c7 + maunium.net/go/mautrix v0.27.1-0.20260429160319-674a25f9b6ee ) require ( diff --git a/go.sum b/go.sum index 38542dc..44ae65d 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.27.1-0.20260429060852-d7aad0e862c7 h1:ZL/dTgBuj7ZzH543brFUvxZo2lJGsCMBvnfKIvjdHC4= -maunium.net/go/mautrix v0.27.1-0.20260429060852-d7aad0e862c7/go.mod h1:4fZ0M0xB5ZtueQI65RilX28J/3794BeK+LaCg4U61Jk= +maunium.net/go/mautrix v0.27.1-0.20260429160319-674a25f9b6ee h1:OiKSGPfWLQYir1QmvkMvfo/0Dh78hVo8boGwU0Ub32k= +maunium.net/go/mautrix v0.27.1-0.20260429160319-674a25f9b6ee/go.mod h1:4fZ0M0xB5ZtueQI65RilX28J/3794BeK+LaCg4U61Jk= From 60a46fd81bf72cea06f9dd71152ef7cd019912cb Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 30 Apr 2026 13:23:21 +0300 Subject: [PATCH 17/94] .github: add another item to bug report template [skip ci] --- .github/ISSUE_TEMPLATE/bug.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index c10630f..06ba9e8 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -11,7 +11,8 @@ type: Bug ### Checklist - + * [ ] This is an actual bug, not just a setup issue (see the [troubleshooting docs](https://docs.mau.fi/bridges/general/troubleshooting.html) or ask in the Matrix room for setup help). * [ ] I am certain that sufficient information is included. Ask in the Matrix room first if not. +* [ ] The bug is still present on the main branch. From 42e83b1ea9f16a916a27ecab60c2ae4b082ab359 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 4 May 2026 17:01:16 +0300 Subject: [PATCH 18/94] dependencies: update --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 63b26bc..2a66a9d 100644 --- a/go.mod +++ b/go.mod @@ -8,17 +8,17 @@ tool go.mau.fi/util/cmd/maubuild require ( github.com/lib/pq v1.12.3 - github.com/rs/zerolog v1.35.0 + github.com/rs/zerolog v1.35.1 github.com/tidwall/gjson v1.18.0 - go.mau.fi/util v0.9.9-0.20260428124215-c47a7212562e + go.mau.fi/util v0.9.9-0.20260501211038-7535d5590b78 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260427122815-7514259253a7 + go.mau.fi/whatsmeow v0.0.0-20260504140538-51dcc5e33be0 golang.org/x/image v0.39.0 golang.org/x/net v0.53.0 golang.org/x/sync v0.20.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.27.1-0.20260429160319-674a25f9b6ee + maunium.net/go/mautrix v0.27.1-0.20260502202615-25947505f4a2 ) require ( diff --git a/go.sum b/go.sum index 44ae65d..e22300a 100644 --- a/go.sum +++ b/go.sum @@ -46,8 +46,8 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI= -github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= +github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI= +github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= 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= @@ -71,12 +71,12 @@ github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.1 h1:vRZG4EzTn70XY6Oh/pVKrQGuMHBkAWlGRC22/85m9L0= go.mau.fi/libsignal v0.2.1/go.mod h1:iVvjrHyfQqWajOUaMEsIfo3IqgVMrhWcPiiEzk7NgoU= -go.mau.fi/util v0.9.9-0.20260428124215-c47a7212562e h1:o0O9sLa4CeZbxbgoSqavwaORrt9BB+trOLKBSoGzJ3Q= -go.mau.fi/util v0.9.9-0.20260428124215-c47a7212562e/go.mod h1:up/5mbzH2M1pSBNXqRxODn8dg/hEKbLJu92W4/SNAX0= +go.mau.fi/util v0.9.9-0.20260501211038-7535d5590b78 h1:MRz5RQxXgohVSulsFHqokfZDJzhqk1w+fDQxJksxbZc= +go.mau.fi/util v0.9.9-0.20260501211038-7535d5590b78/go.mod h1:up/5mbzH2M1pSBNXqRxODn8dg/hEKbLJu92W4/SNAX0= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260427122815-7514259253a7 h1:jEOI4I7kU+MYUNI1L94rhYXhUg8N9+YUNHVY525aYTc= -go.mau.fi/whatsmeow v0.0.0-20260427122815-7514259253a7/go.mod h1:ijfkzOXauA/Vz/htXEMfOAJSUgglribW5oQeYC9tSSg= +go.mau.fi/whatsmeow v0.0.0-20260504140538-51dcc5e33be0 h1:LIqLhtTxsOsJbQn+WQVqQjgVQJgWYRGQptLJG1DN0/Y= +go.mau.fi/whatsmeow v0.0.0-20260504140538-51dcc5e33be0/go.mod h1:ijfkzOXauA/Vz/htXEMfOAJSUgglribW5oQeYC9tSSg= 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.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= @@ -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.27.1-0.20260429160319-674a25f9b6ee h1:OiKSGPfWLQYir1QmvkMvfo/0Dh78hVo8boGwU0Ub32k= -maunium.net/go/mautrix v0.27.1-0.20260429160319-674a25f9b6ee/go.mod h1:4fZ0M0xB5ZtueQI65RilX28J/3794BeK+LaCg4U61Jk= +maunium.net/go/mautrix v0.27.1-0.20260502202615-25947505f4a2 h1:yuYNi5X7baSlW/rDXlTV1n+x72uMVwPCubNnMG5wrqk= +maunium.net/go/mautrix v0.27.1-0.20260502202615-25947505f4a2/go.mod h1:t9xgVOeRTI3QAX04dBEM6iql+SnOOLdIy2jaKWyL2M0= From e13f63e6b827ad2a0fefc7d3604e771f72891221 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 6 May 2026 13:14:07 +0300 Subject: [PATCH 19/94] dependencies: update --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 2a66a9d..d55b65c 100644 --- a/go.mod +++ b/go.mod @@ -10,15 +10,15 @@ require ( github.com/lib/pq v1.12.3 github.com/rs/zerolog v1.35.1 github.com/tidwall/gjson v1.18.0 - go.mau.fi/util v0.9.9-0.20260501211038-7535d5590b78 + go.mau.fi/util v0.9.9-0.20260505143909-8e67f0d355e0 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260504140538-51dcc5e33be0 + go.mau.fi/whatsmeow v0.0.0-20260506100936-a763037b215a golang.org/x/image v0.39.0 golang.org/x/net v0.53.0 golang.org/x/sync v0.20.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.27.1-0.20260502202615-25947505f4a2 + maunium.net/go/mautrix v0.27.1-0.20260506130904-37580129eaaf ) 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.42 // indirect + github.com/mattn/go-sqlite3 v1.14.44 // indirect github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/rs/xid v1.6.0 // indirect diff --git a/go.sum b/go.sum index e22300a..bbef170 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ 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.42 h1:MigqEP4ZmHw3aIdIT7T+9TLa90Z6smwcthx+Azv4Cgo= -github.com/mattn/go-sqlite3 v1.14.42/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= +github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8= +github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 h1:WDsQxOJDy0N1VRAjXLpi8sCEZRSGarLWQevDxpTBRrM= github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -71,12 +71,12 @@ github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.1 h1:vRZG4EzTn70XY6Oh/pVKrQGuMHBkAWlGRC22/85m9L0= go.mau.fi/libsignal v0.2.1/go.mod h1:iVvjrHyfQqWajOUaMEsIfo3IqgVMrhWcPiiEzk7NgoU= -go.mau.fi/util v0.9.9-0.20260501211038-7535d5590b78 h1:MRz5RQxXgohVSulsFHqokfZDJzhqk1w+fDQxJksxbZc= -go.mau.fi/util v0.9.9-0.20260501211038-7535d5590b78/go.mod h1:up/5mbzH2M1pSBNXqRxODn8dg/hEKbLJu92W4/SNAX0= +go.mau.fi/util v0.9.9-0.20260505143909-8e67f0d355e0 h1:stkCMpY3ULN6sNrPoRYZ5AQ/kc20a7pmhv6t0sdyVhE= +go.mau.fi/util v0.9.9-0.20260505143909-8e67f0d355e0/go.mod h1:jE9FfhbgEgAwxei6lomO9v8zdCIATcquONUu4vjRwSs= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260504140538-51dcc5e33be0 h1:LIqLhtTxsOsJbQn+WQVqQjgVQJgWYRGQptLJG1DN0/Y= -go.mau.fi/whatsmeow v0.0.0-20260504140538-51dcc5e33be0/go.mod h1:ijfkzOXauA/Vz/htXEMfOAJSUgglribW5oQeYC9tSSg= +go.mau.fi/whatsmeow v0.0.0-20260506100936-a763037b215a h1:DfD7BXe4m+MIPAe0TjFb8hFUd42CqybeWaTvOH6dMiw= +go.mau.fi/whatsmeow v0.0.0-20260506100936-a763037b215a/go.mod h1:ijfkzOXauA/Vz/htXEMfOAJSUgglribW5oQeYC9tSSg= 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.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= @@ -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.27.1-0.20260502202615-25947505f4a2 h1:yuYNi5X7baSlW/rDXlTV1n+x72uMVwPCubNnMG5wrqk= -maunium.net/go/mautrix v0.27.1-0.20260502202615-25947505f4a2/go.mod h1:t9xgVOeRTI3QAX04dBEM6iql+SnOOLdIy2jaKWyL2M0= +maunium.net/go/mautrix v0.27.1-0.20260506130904-37580129eaaf h1:AXGEYhQsiQArdtD1XE0NnGIl614j9stHXFmaB+Kb8Sw= +maunium.net/go/mautrix v0.27.1-0.20260506130904-37580129eaaf/go.mod h1:2ANjihDB+wv2UAqJapkRekmNXw7khSisccAkE5Jg3P0= From 0f6e7a522c3ae2ccdcd3928a39e6ede8ef513524 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 6 May 2026 13:23:02 +0300 Subject: [PATCH 20/94] .github: add version command to bug report template --- .github/ISSUE_TEMPLATE/bug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index 06ba9e8..4b3b934 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -15,4 +15,4 @@ type: Bug * [ ] This is an actual bug, not just a setup issue (see the [troubleshooting docs](https://docs.mau.fi/bridges/general/troubleshooting.html) or ask in the Matrix room for setup help). * [ ] I am certain that sufficient information is included. Ask in the Matrix room first if not. -* [ ] The bug is still present on the main branch. +* [ ] The bug is still present on the main branch. The `!wa version` command output is: `` From ac2912a14536d80453ec57390465edb05afcb71f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 6 May 2026 16:03:28 +0300 Subject: [PATCH 21/94] msgconv,directmedia: add support for importing image packs --- go.mod | 2 +- go.sum | 4 +- pkg/connector/capabilities.go | 4 +- pkg/connector/client.go | 11 +++ pkg/connector/directmedia.go | 47 ++++++++-- pkg/connector/handlematrix.go | 1 + pkg/msgconv/from-matrix.go | 38 +++++++- pkg/msgconv/msgconv.go | 11 ++- pkg/msgconv/wa-media.go | 14 ++- pkg/msgconv/wa-sticker.go | 172 +++++++++++++++++++++++++++++++++- pkg/waid/mediaid.go | 42 +++++++++ 11 files changed, 323 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index d55b65c..1f12612 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.18.0 go.mau.fi/util v0.9.9-0.20260505143909-8e67f0d355e0 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260506100936-a763037b215a + go.mau.fi/whatsmeow v0.0.0-20260506122147-6a7198d94d26 golang.org/x/image v0.39.0 golang.org/x/net v0.53.0 golang.org/x/sync v0.20.0 diff --git a/go.sum b/go.sum index bbef170..08791dd 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.9-0.20260505143909-8e67f0d355e0 h1:stkCMpY3ULN6sNrPoRYZ5AQ/k go.mau.fi/util v0.9.9-0.20260505143909-8e67f0d355e0/go.mod h1:jE9FfhbgEgAwxei6lomO9v8zdCIATcquONUu4vjRwSs= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260506100936-a763037b215a h1:DfD7BXe4m+MIPAe0TjFb8hFUd42CqybeWaTvOH6dMiw= -go.mau.fi/whatsmeow v0.0.0-20260506100936-a763037b215a/go.mod h1:ijfkzOXauA/Vz/htXEMfOAJSUgglribW5oQeYC9tSSg= +go.mau.fi/whatsmeow v0.0.0-20260506122147-6a7198d94d26 h1:DyFksXWn7z/NN+TNJ0DomV1/drWjkyiVuJ6RIiy/bo4= +go.mau.fi/whatsmeow v0.0.0-20260506122147-6a7198d94d26/go.mod h1:ijfkzOXauA/Vz/htXEMfOAJSUgglribW5oQeYC9tSSg= 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.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= diff --git a/pkg/connector/capabilities.go b/pkg/connector/capabilities.go index 202569b..d8b1367 100644 --- a/pkg/connector/capabilities.go +++ b/pkg/connector/capabilities.go @@ -125,10 +125,10 @@ var whatsappCaps = &event.RoomFeatures{ event.CapMsgSticker: { MimeTypes: map[string]event.CapabilitySupportLevel{ "image/webp": event.CapLevelFullySupported, - // TODO see if sending lottie is possible - //"video/lottie+json": event.CapLevelFullySupported, "image/png": event.CapLevelPartialSupport, "image/jpeg": event.CapLevelPartialSupport, + // This will only be accepted if it was imported from WhatsApp + "video/lottie+json": event.CapLevelPartialSupport, }, Caption: event.CapLevelDropped, MaxSize: WAMaxFileSize, diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 367ad14..3f19bf7 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -38,6 +38,7 @@ import ( "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/networkid" "maunium.net/go/mautrix/bridgev2/status" + "maunium.net/go/mautrix/event" "go.mau.fi/mautrix-whatsapp/pkg/waid" ) @@ -128,6 +129,7 @@ var ( _ bridgev2.PushableNetworkAPI = (*WhatsAppClient)(nil) _ bridgev2.BackgroundSyncingNetworkAPI = (*WhatsAppClient)(nil) _ bridgev2.ChatViewingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.StickerImportingNetworkAPI = (*WhatsAppClient)(nil) ) var pushCfg = &bridgev2.PushConfig{ @@ -467,3 +469,12 @@ func (wa *WhatsAppClient) updatePresence(ctx context.Context, presence types.Pre } return err } + +func (wa *WhatsAppClient) DownloadImagePack(ctx context.Context, url string) (*bridgev2.ImportedImagePack, error) { + return wa.Main.MsgConv.DownloadImagePack(ctx, wa.UserLogin.ID, wa.Client, url) +} + +func (wa *WhatsAppClient) ListImagePacks(ctx context.Context) ([]*event.ImagePackMetadata, error) { + // TODO + return nil, nil +} diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index 10cdbbb..ac2fbd2 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -67,6 +67,8 @@ func (wa *WhatsAppConnector) Download(ctx context.Context, mediaID networkid.Med return wa.downloadMessageDirectMedia(ctx, parsedID, params) } else if parsedID.Avatar != nil { return wa.downloadAvatarDirectMedia(ctx, parsedID, params) + } else if parsedID.Sticker != nil { + return wa.downloadStickerDirectMedia(ctx, parsedID, params) } else { return nil, fmt.Errorf("unexpected media ID parsing result") } @@ -135,8 +137,25 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars }, nil } +func (wa *WhatsAppConnector) downloadStickerDirectMedia(ctx context.Context, parsedID *waid.ParsedMediaID, params map[string]string) (mediaproxy.GetMediaResponse, error) { + ul := wa.Bridge.GetCachedUserLoginByID(parsedID.UserLogin) + if ul == nil { + return nil, fmt.Errorf("%w: user login %s not found", bridgev2.ErrNotLoggedIn, parsedID.UserLogin) + } + waClient := ul.Client.(*WhatsAppClient) + if waClient.Client == nil { + return nil, fmt.Errorf("no WhatsApp client found on login %s", parsedID.UserLogin) + } + sticker, err := wa.MsgConv.GetCachedSticker(ctx, waClient.Client, parsedID.Sticker.PackID, parsedID.Sticker.FileHash) + if err != nil { + return nil, err + } else if sticker == nil { + return nil, mautrix.MNotFound.WithMessage("Sticker not found in pack") + } + return wa.makeDirectMediaResponse(ctx, waClient, sticker, sticker.MimeType, "", nil, params) +} + func (wa *WhatsAppConnector) downloadMessageDirectMedia(ctx context.Context, parsedID *waid.ParsedMediaID, params map[string]string) (mediaproxy.GetMediaResponse, error) { - log := zerolog.Ctx(ctx) msg, err := wa.Bridge.DB.Message.GetFirstPartByID(ctx, parsedID.UserLogin, parsedID.Message.String()) if err != nil { return nil, fmt.Errorf("failed to get message: %w", err) @@ -174,16 +193,29 @@ func (wa *WhatsAppConnector) downloadMessageDirectMedia(ctx context.Context, par if waClient.Client == nil { return nil, fmt.Errorf("no WhatsApp client found on login") } + return wa.makeDirectMediaResponse(ctx, waClient, keys, keys.MimeType, msg.ID, keys, params) +} + +func (wa *WhatsAppConnector) makeDirectMediaResponse( + ctx context.Context, + waClient *WhatsAppClient, + dm whatsmeow.DownloadableMessage, + mimeType string, + msgID networkid.MessageID, + keys *msgconv.FailedMediaKeys, + params map[string]string, +) (mediaproxy.GetMediaResponse, error) { return &mediaproxy.GetMediaResponseFile{ Callback: func(f *os.File) (*mediaproxy.FileMeta, error) { - err := waClient.Client.DownloadToFile(ctx, keys, f) - if errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith403) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith404) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith410) || errors.Is(err, whatsmeow.ErrNoURLPresent) { + log := zerolog.Ctx(ctx) + err := waClient.Client.DownloadToFile(ctx, dm, f) + if keys != nil && (errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith403) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith404) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith410) || errors.Is(err, whatsmeow.ErrNoURLPresent)) { val := params["fi.mau.whatsapp.reload_media"] if val == "false" || (!wa.Config.DirectMediaAutoRequest && val != "true") { return nil, ErrReloadNeeded } log.Trace().Msg("Media not found for direct download, requesting and waiting") - err = waClient.requestAndWaitDirectMedia(ctx, msg.ID, keys) + err = waClient.requestAndWaitDirectMedia(ctx, msgID, keys) if err != nil { log.Trace().Err(err).Msg("Failed to wait for media for direct download") return nil, err @@ -197,8 +229,7 @@ func (wa *WhatsAppConnector) downloadMessageDirectMedia(ctx context.Context, par return nil, err } - mime := keys.MimeType - if mime == "application/was" { + if mimeType == "application/was" { if _, err := f.Seek(0, io.SeekStart); err != nil { return nil, fmt.Errorf("failed to seek to start of sticker zip: %w", err) } else if zipData, err := io.ReadAll(f); err != nil { @@ -210,11 +241,11 @@ func (wa *WhatsAppConnector) downloadMessageDirectMedia(ctx context.Context, par } else if err := f.Truncate(int64(len(data))); err != nil { return nil, fmt.Errorf("failed to truncate animated sticker file: %w", err) } - mime = "video/lottie+json" + mimeType = "video/lottie+json" } return &mediaproxy.FileMeta{ - ContentType: mime, + ContentType: mimeType, }, nil }, }, nil diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index a0a67fa..b0963ec 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -107,6 +107,7 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg wrappedMsgID2 := waid.MakeMessageID(chatJID, wa.GetStore().GetLID(), req.ID) msg.AddPendingToIgnore(networkid.TransactionID(wrappedMsgID)) msg.AddPendingToIgnore(networkid.TransactionID(wrappedMsgID2)) + zerolog.Ctx(ctx).Trace().Any("payload", waMsg).Msg("Outgoing message payload") resp, err := wa.Client.SendMessage(ctx, chatJID, waMsg, *req) if err != nil { return nil, err diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 49f6048..4eb0a88 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -19,6 +19,7 @@ package msgconv import ( "bytes" "context" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -201,6 +202,7 @@ func (mc *MessageConverter) constructMediaMessage( FileSHA256: uploaded.FileSHA256, FileLength: proto.Uint64(uploaded.FileLength), URL: proto.String(uploaded.URL), + IsLottie: proto.Bool(mime == "application/was"), }, } case event.MsgAudio: @@ -482,6 +484,17 @@ func (mc *MessageConverter) convertToWebP(img []byte) ([]byte, int, error) { return webpBuffer.Bytes(), size, nil } +func (mc *MessageConverter) getOriginalBridgedSticker(ctx context.Context, info *event.BridgedSticker) (*types.StickerPackItem, error) { + if info == nil || info.Network != StickerSourceID || !strings.HasPrefix(info.PackURL, StickerPackURLPrefix) || info.ID == "" { + return nil, nil + } + fileHash, err := base64.StdEncoding.DecodeString(info.ID) + if err != nil { + return nil, nil + } + return mc.GetCachedSticker(ctx, getClient(ctx), strings.TrimPrefix(info.PackURL, StickerPackURLPrefix), fileHash) +} + func (mc *MessageConverter) reuploadFileToWhatsApp( ctx context.Context, content *event.MessageEventContent, ) (*whatsmeow.UploadResponse, []byte, string, error) { @@ -490,7 +503,21 @@ func (mc *MessageConverter) reuploadFileToWhatsApp( if content.FileName != "" { fileName = content.FileName } - data, err := mc.Bridge.Bot.DownloadMedia(ctx, content.URL, content.File) + var data []byte + var err error + var sticker *types.StickerPackItem + if sticker, err = mc.getOriginalBridgedSticker(ctx, content.Info.BridgedSticker); sticker != nil && sticker.MimeType == "application/was" { + data, err = getClient(ctx).Download(ctx, sticker) + mime = sticker.MimeType + content.Info.Width = sticker.Width + content.Info.Height = sticker.Height + } else { + if err != nil { + zerolog.Ctx(ctx).Warn().Err(err). + Msg("Failed to get original bridged sticker, falling back to downloading from URL") + } + data, err = mc.Bridge.Bot.DownloadMedia(ctx, content.URL, content.File) + } if err != nil { return nil, nil, "", fmt.Errorf("%w: %w", bridgev2.ErrMediaDownloadFailed, err) } @@ -508,7 +535,14 @@ func (mc *MessageConverter) reuploadFileToWhatsApp( case event.MessageType(event.EventSticker.Type): isSticker = true mediaType = whatsmeow.MediaImage - if mime != "image/webp" || content.Info.Width != content.Info.Height { + if mime == "video/lottie+json" { + // This likely won't work + data, err = PackAnimatedSticker(data) + if err != nil { + return nil, nil, mime, fmt.Errorf("%w (packing animated sticker): %w", bridgev2.ErrMediaConvertFailed, err) + } + mime = "application/was" + } else if (mime != "image/webp" || content.Info.Width != content.Info.Height) && mime != "application/was" { var size int data, size, err = mc.convertToWebP(data) if err != nil { diff --git a/pkg/msgconv/msgconv.go b/pkg/msgconv/msgconv.go index e4109c8..185ee0f 100644 --- a/pkg/msgconv/msgconv.go +++ b/pkg/msgconv/msgconv.go @@ -17,6 +17,9 @@ package msgconv import ( + "sync" + + "go.mau.fi/whatsmeow/types" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/format" @@ -43,12 +46,16 @@ type MessageConverter struct { DisableViewOnce bool DirectMedia bool OldMediaSuffix string + + stickerPackCache map[string]*types.StickerPack + stickerPackCacheLock sync.Mutex } func New(br *bridgev2.Bridge) *MessageConverter { mc := &MessageConverter{ - Bridge: br, - MaxFileSize: 50 * 1024 * 1024, + Bridge: br, + MaxFileSize: 50 * 1024 * 1024, + stickerPackCache: make(map[string]*types.StickerPack), } mc.HTMLParser = &format.HTMLParser{ PillConverter: mc.convertPill, diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index d1b5332..03bb274 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -35,6 +35,7 @@ import ( "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/database" "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" "go.mau.fi/mautrix-whatsapp/pkg/waid" ) @@ -81,11 +82,11 @@ func (mc *MessageConverter) convertMediaMessage( MimeType: msg.GetMimetype(), } if mc.DirectMedia { - preparedMedia.FillFileName() if preparedMedia.Info.MimeType == "application/was" { preparedMedia.Info.MimeType = "video/lottie+json" preparedMedia.FileName = "sticker.json" } + preparedMedia.FillFileName() var err error portal := getPortal(ctx) idOverride := getEditTargetID(ctx) @@ -352,12 +353,15 @@ func (mc *MessageConverter) reuploadWhatsAppAttachment( ) error { client := getClient(ctx) intent := getIntent(ctx) - portal := getPortal(ctx) + var roomID id.RoomID + if portal := getPortal(ctx); portal != nil { + roomID = portal.MXID + } var thumbnailData []byte var thumbnailInfo *event.FileInfo if part.Info.Size > uploadFileThreshold { var err error - part.URL, part.File, err = intent.UploadMediaStream(ctx, portal.MXID, -1, true, func(file io.Writer) (*bridgev2.FileStreamResult, error) { + part.URL, part.File, err = intent.UploadMediaStream(ctx, roomID, -1, true, func(file io.Writer) (*bridgev2.FileStreamResult, error) { err := client.DownloadToFile(ctx, message, file.(*os.File)) if errors.Is(err, whatsmeow.ErrFileLengthMismatch) || errors.Is(err, whatsmeow.ErrInvalidMediaSHA256) { zerolog.Ctx(ctx).Warn().Err(err).Msg("Mismatching media checksums in message. Ignoring because WhatsApp seems to ignore them too") @@ -397,7 +401,7 @@ func (mc *MessageConverter) reuploadWhatsAppAttachment( part.Info.MimeType = http.DetectContentType(data) } part.FillFileName() - part.URL, part.File, err = intent.UploadMedia(ctx, portal.MXID, data, part.FileName, part.Info.MimeType) + part.URL, part.File, err = intent.UploadMedia(ctx, roomID, data, part.FileName, part.Info.MimeType) if err != nil { return fmt.Errorf("%w: %w", bridgev2.ErrMediaReuploadFailed, err) } @@ -406,7 +410,7 @@ func (mc *MessageConverter) reuploadWhatsAppAttachment( var err error part.Info.ThumbnailURL, part.Info.ThumbnailFile, err = intent.UploadMedia( ctx, - portal.MXID, + roomID, thumbnailData, "thumbnail"+exmime.ExtensionFromMimetype(thumbnailInfo.MimeType), thumbnailInfo.MimeType, diff --git a/pkg/msgconv/wa-sticker.go b/pkg/msgconv/wa-sticker.go index fef111a..85766ed 100644 --- a/pkg/msgconv/wa-sticker.go +++ b/pkg/msgconv/wa-sticker.go @@ -20,10 +20,13 @@ import ( "archive/zip" "bytes" "context" + "encoding/base64" "encoding/binary" "encoding/json" + "errors" "fmt" "io" + "net/url" "os" "path/filepath" "strconv" @@ -34,9 +37,158 @@ import ( "go.mau.fi/util/exstrings" "go.mau.fi/util/lottie" "go.mau.fi/util/random" + "go.mau.fi/whatsmeow" + "go.mau.fi/whatsmeow/types" + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/networkid" "maunium.net/go/mautrix/event" + + "go.mau.fi/mautrix-whatsapp/pkg/waid" ) +func (mc *MessageConverter) GetCachedStickerPack(ctx context.Context, client *whatsmeow.Client, packID string) (*types.StickerPack, error) { + mc.stickerPackCacheLock.Lock() + defer mc.stickerPackCacheLock.Unlock() + cached, ok := mc.stickerPackCache[packID] + if ok { + if cached == nil { + return nil, bridgev2.RespError(mautrix.MNotFound.WithMessage("sticker pack not found (cached)")) + } + return cached, nil + } + + pack, err := client.FetchStickerPack(ctx, packID) + if errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith404) { + mc.stickerPackCache[packID] = nil + return nil, bridgev2.WrapRespErr(err, mautrix.MNotFound) + } else if err != nil { + return nil, err + } + mc.stickerPackCache[packID] = pack + if packID != pack.StickerPackID { + mc.stickerPackCache[pack.StickerPackID] = pack + } + return pack, nil +} + +func (mc *MessageConverter) GetCachedSticker(ctx context.Context, client *whatsmeow.Client, packID string, hash []byte) (*types.StickerPackItem, error) { + pack, err := mc.GetCachedStickerPack(ctx, client, packID) + if err != nil { + return nil, err + } + for _, sticker := range pack.Stickers { + if bytes.Equal(sticker.FileHash, hash) { + return sticker, nil + } + } + return nil, nil +} + +func (mc *MessageConverter) DownloadImagePack(ctx context.Context, userLoginID networkid.UserLoginID, client *whatsmeow.Client, inputURL string) (*bridgev2.ImportedImagePack, error) { + parsedURL, err := url.Parse(inputURL) + if err != nil { + return nil, bridgev2.WrapRespErr(err, mautrix.MNotFound) + } else if parsedURL.Host != "api.whatsapp.com" && parsedURL.Host != "wa.me" { + return nil, bridgev2.WrapRespErr(fmt.Errorf("invalid host %q", parsedURL.Host), mautrix.MNotFound) + } else if !strings.HasPrefix(parsedURL.Path, "/stickerpack/") { + return nil, bridgev2.WrapRespErr(fmt.Errorf("invalid path %q", parsedURL.Path), mautrix.MNotFound) + } + packName := strings.Split(strings.TrimPrefix(parsedURL.Path, "/stickerpack/"), "/")[0] + if packName == "" { + return nil, bridgev2.WrapRespErr(fmt.Errorf("empty pack name"), mautrix.MNotFound) + } + pack, err := mc.GetCachedStickerPack(ctx, client, packName) + if err != nil { + return nil, err + } + canonicalURL := "https://wa.me/stickerpack/" + pack.StickerPackID + topLevelExtra := map[string]any{ + "fi.mau.whatsapp.stickerpack": map[string]any{ + "id": pack.StickerPackID, + "name": pack.Name, + "description": pack.Description, + "publisher": pack.Publisher, + "animated": pack.Animated > 0, + "lottie": pack.Lottie > 0, + }, + } + content := &event.ImagePackEventContent{ + Images: make(map[string]*event.ImagePackImage, len(pack.Stickers)), + Metadata: event.ImagePackMetadata{ + DisplayName: pack.Name, + AvatarURL: "", + Usage: []event.ImagePackUsage{event.ImagePackUsageSticker}, + Attribution: fmt.Sprintf("By %s on WhatsApp %s", pack.Publisher, canonicalURL), + BridgedPack: &event.BridgedStickerPack{ + Network: StickerSourceID, + URL: canonicalURL, + }, + }, + } + ctx = context.WithValue(ctx, contextKeyClient, client) + ctx = context.WithValue(ctx, contextKeyIntent, mc.Bridge.Bot) + ctx = context.WithValue(ctx, contextKeyPortal, (*bridgev2.Portal)(nil)) + for i, sticker := range pack.Stickers { + shortcode := sticker.PreviewWebpID + if shortcode == "" { + shortcode = fmt.Sprintf("%s_img%d", pack.StickerPackID, i+1) + } + body := sticker.AccessibilityText + var emoji string + if len(sticker.Emojis) > 0 { + emoji = sticker.Emojis[0] + if body == "" { + body = strings.Join(sticker.Emojis, " ") + } + } + part := &PreparedMedia{ + Type: event.EventSticker, + MessageEventContent: &event.MessageEventContent{ + Body: body, + Info: &event.FileInfo{ + MimeType: sticker.MimeType, + Width: sticker.Width, + Height: sticker.Height, + Size: int(sticker.FileSize), + BridgedSticker: &event.BridgedSticker{ + Network: StickerSourceID, + ID: base64.StdEncoding.EncodeToString(sticker.FileHash), + Emoji: emoji, + PackURL: canonicalURL, + }, + }, + }, + TypeDescription: "sticker", + } + if mc.DirectMedia { + if part.Info.MimeType == "application/was" { + part.Info.MimeType = "video/lottie+json" + } + part.URL, err = mc.Bridge.Matrix.GenerateContentURI(ctx, waid.MakeStickerPackMediaID(pack.StickerPackID, sticker.FileHash, userLoginID)) + if err != nil { + panic(fmt.Errorf("failed to generate content URI: %w", err)) + } + } else { + err = mc.reuploadWhatsAppAttachment(ctx, sticker, part) + if err != nil { + return nil, fmt.Errorf("failed to reupload sticker %q: %w", sticker.GetDirectPath(), err) + } + } + content.Images[shortcode] = &event.ImagePackImage{ + URL: part.URL, + Body: part.Body, + Info: part.Info, + } + } + + return &bridgev2.ImportedImagePack{ + Content: content, + Extra: topLevelExtra, + Shortcode: pack.StickerPackID, + }, nil +} + type StickerMetadata struct { StickerPackID string `json:"sticker-pack-id"` AccessibilityText string `json:"accessibility-text"` @@ -48,7 +200,7 @@ func (sm *StickerMetadata) ToMatrix(content *event.MessageEventContent) { if sm == nil { return } - if sm.StickerPackID != "" { + if sm.StickerPackID != "" && content.Info.BridgedSticker == nil { content.Info.BridgedSticker = &event.BridgedSticker{ Network: StickerSourceID, PackURL: StickerPackURLPrefix + sm.StickerPackID, @@ -67,6 +219,24 @@ func (sm *StickerMetadata) ToMatrix(content *event.MessageEventContent) { const StickerSourceID = "whatsapp" const StickerPackURLPrefix = "https://wa.me/stickerpack/" +func PackAnimatedSticker(data []byte) ([]byte, error) { + var buf bytes.Buffer + zipWriter := zip.NewWriter(&buf) + f, err := zipWriter.Create("animation/animation.json") + if err != nil { + return nil, fmt.Errorf("failed to create zip entry: %w", err) + } + _, err = f.Write(data) + if err != nil { + return nil, fmt.Errorf("failed to write zip entry: %w", err) + } + err = zipWriter.Close() + if err != nil { + return nil, fmt.Errorf("failed to close zip writer: %w", err) + } + return buf.Bytes(), nil +} + func ExtractAnimatedSticker(data []byte) ([]byte, *StickerMetadata, error) { zipReader, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) if err != nil { diff --git a/pkg/waid/mediaid.go b/pkg/waid/mediaid.go index 6a94de4..093ece1 100644 --- a/pkg/waid/mediaid.go +++ b/pkg/waid/mediaid.go @@ -33,6 +33,7 @@ const ( mediaIDTypeMessage = 255 mediaIDTypeAvatar = 254 mediaIDTypeCommunityAvatar = 253 + mediaIDTypeStickerPackItem = 252 ) func MakeMediaID(messageInfo *types.MessageInfo, idOverride types.MessageID, receiver networkid.UserLoginID) networkid.MediaID { @@ -82,9 +83,28 @@ type AvatarMediaInfo struct { Community bool } +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)) + mediaID = append(mediaID, mediaIDTypeStickerPackItem) + mediaID = append(mediaID, byte(len(packID))) + mediaID = append(mediaID, packID...) + mediaID = append(mediaID, byte(len(fileHash))) + mediaID = append(mediaID, fileHash...) + mediaID = append(mediaID, byte(len(receiverID))) + mediaID = append(mediaID, receiverID...) + return mediaID +} + +type StickerPackMediaInfo struct { + PackID string + FileHash []byte +} + type ParsedMediaID struct { Message *ParsedMessageID Avatar *AvatarMediaInfo + Sticker *StickerPackMediaInfo UserLogin networkid.UserLoginID } @@ -138,6 +158,24 @@ func ParseMediaID(mediaID networkid.MediaID) (*ParsedMediaID, error) { Community: mediaIDType == mediaIDTypeCommunityAvatar, } parsed.UserLogin = MakeUserLoginID(receiverID) + case mediaIDTypeStickerPackItem: + packID, err := readCompact(&mediaID, parseString) + if err != nil { + return nil, fmt.Errorf("failed to parse sticker pack ID: %w", err) + } + fileHash, err := readCompact(&mediaID, rawBytes) + if err != nil { + return nil, fmt.Errorf("failed to parse sticker file hash: %w", err) + } + receiverID, err := readCompact(&mediaID, parseCompactJID) + if err != nil { + return nil, fmt.Errorf("failed to parse receiver JID: %w", err) + } + parsed.Sticker = &StickerPackMediaInfo{ + PackID: packID, + FileHash: fileHash, + } + parsed.UserLogin = MakeUserLoginID(receiverID) default: return nil, fmt.Errorf("unknown media ID type %d", mediaIDType) } @@ -246,6 +284,10 @@ func parseCompactJID(jid []byte) (types.JID, error) { } } +func rawBytes(data []byte) ([]byte, error) { + return data, nil +} + func readCompact[T any](data *networkid.MediaID, fn func(data []byte) (T, error)) (T, error) { var defVal T if len(*data) < 1 { From aeeea94e8ea0eb7740a470a6f5e41e51d20d27d4 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 6 May 2026 16:12:40 +0300 Subject: [PATCH 22/94] msgconv/wa-sticker: apply correct dimensions to imported packs --- pkg/msgconv/from-matrix.go | 18 +++++++++++------- pkg/msgconv/wa-media.go | 24 ++++++++++++++---------- pkg/msgconv/wa-sticker.go | 1 + 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 4eb0a88..93de90c 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -506,16 +506,20 @@ func (mc *MessageConverter) reuploadFileToWhatsApp( var data []byte var err error var sticker *types.StickerPackItem - if sticker, err = mc.getOriginalBridgedSticker(ctx, content.Info.BridgedSticker); sticker != nil && sticker.MimeType == "application/was" { - data, err = getClient(ctx).Download(ctx, sticker) - mime = sticker.MimeType + if sticker, err = mc.getOriginalBridgedSticker(ctx, content.Info.BridgedSticker); err != nil { + zerolog.Ctx(ctx).Warn().Err(err). + Msg("Failed to get original bridged sticker, falling back to downloading from URL") + data, err = mc.Bridge.Bot.DownloadMedia(ctx, content.URL, content.File) + } else if sticker != nil { + if sticker.MimeType == "application/was" { + data, err = getClient(ctx).Download(ctx, sticker) + mime = sticker.MimeType + } else { + data, err = mc.Bridge.Bot.DownloadMedia(ctx, content.URL, content.File) + } content.Info.Width = sticker.Width content.Info.Height = sticker.Height } else { - if err != nil { - zerolog.Ctx(ctx).Warn().Err(err). - Msg("Failed to get original bridged sticker, falling back to downloading from URL") - } data, err = mc.Bridge.Bot.DownloadMedia(ctx, content.URL, content.File) } if err != nil { diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index 03bb274..c2a8624 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -236,6 +236,19 @@ type MediaMessageWithDuration interface { const WhatsAppStickerSize = 190 +func fixStickerDimensions(info *event.FileInfo) { + if info.Width == info.Height { + info.Width = WhatsAppStickerSize + info.Height = WhatsAppStickerSize + } else if info.Width > info.Height { + info.Height /= info.Width / WhatsAppStickerSize + info.Width = WhatsAppStickerSize + } else { + info.Width /= info.Height / WhatsAppStickerSize + info.Height = WhatsAppStickerSize + } +} + func prepareMediaMessage(rawMsg MediaMessage) *PreparedMedia { extraInfo := map[string]any{} data := &PreparedMedia{ @@ -284,16 +297,7 @@ func prepareMediaMessage(rawMsg MediaMessage) *PreparedMedia { case *waE2E.StickerMessage: data.Type = event.EventSticker data.FileName = "sticker" + exmime.ExtensionFromMimetype(msg.GetMimetype()) - if data.Info.Width == data.Info.Height { - data.Info.Width = WhatsAppStickerSize - data.Info.Height = WhatsAppStickerSize - } else if data.Info.Width > data.Info.Height { - data.Info.Height /= data.Info.Width / WhatsAppStickerSize - data.Info.Width = WhatsAppStickerSize - } else { - data.Info.Width /= data.Info.Height / WhatsAppStickerSize - data.Info.Height = WhatsAppStickerSize - } + fixStickerDimensions(data.Info) case *waE2E.VideoMessage: data.MsgType = event.MsgVideo pairedMediaType := msg.GetContextInfo().GetPairedMediaType() diff --git a/pkg/msgconv/wa-sticker.go b/pkg/msgconv/wa-sticker.go index 85766ed..e9507bc 100644 --- a/pkg/msgconv/wa-sticker.go +++ b/pkg/msgconv/wa-sticker.go @@ -161,6 +161,7 @@ func (mc *MessageConverter) DownloadImagePack(ctx context.Context, userLoginID n }, TypeDescription: "sticker", } + fixStickerDimensions(part.Info) if mc.DirectMedia { if part.Info.MimeType == "application/was" { part.Info.MimeType = "video/lottie+json" From fb6ff807a865b9f853378d1b8ab86b3b02b092eb Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Thu, 7 May 2026 14:51:09 +0200 Subject: [PATCH 23/94] capabilities: promote image pack import support (#915) --- pkg/connector/capabilities.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/connector/capabilities.go b/pkg/connector/capabilities.go index d8b1367..3864356 100644 --- a/pkg/connector/capabilities.go +++ b/pkg/connector/capabilities.go @@ -19,6 +19,7 @@ var WhatsAppGeneralCaps = &bridgev2.NetworkGeneralCapabilities{ AggressiveUpdateInfo: true, ImplicitReadReceipts: true, Provisioning: bridgev2.ProvisioningCapabilities{ + ImagePackImport: true, ResolveIdentifier: bridgev2.ResolveIdentifierCapabilities{ CreateDM: true, LookupPhone: true, From 3f1223cdedd840832a5d5fbdaced4bb4198c4b9a Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 7 May 2026 17:02:00 +0300 Subject: [PATCH 24/94] 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 1f12612..b9f5347 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.20.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.27.1-0.20260506130904-37580129eaaf + maunium.net/go/mautrix v0.27.1-0.20260507135742-7ec18e08eac3 ) require ( diff --git a/go.sum b/go.sum index 08791dd..0575b9c 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.27.1-0.20260506130904-37580129eaaf h1:AXGEYhQsiQArdtD1XE0NnGIl614j9stHXFmaB+Kb8Sw= -maunium.net/go/mautrix v0.27.1-0.20260506130904-37580129eaaf/go.mod h1:2ANjihDB+wv2UAqJapkRekmNXw7khSisccAkE5Jg3P0= +maunium.net/go/mautrix v0.27.1-0.20260507135742-7ec18e08eac3 h1:K2Aci+LppxMA2CGzj1FQBSzTh2z4F3Kv4l0tKUsaaxs= +maunium.net/go/mautrix v0.27.1-0.20260507135742-7ec18e08eac3/go.mod h1:2ANjihDB+wv2UAqJapkRekmNXw7khSisccAkE5Jg3P0= From f5f26e5ef45db052801bf72e1ff2b40ec42337c6 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 8 May 2026 17:16:10 +0300 Subject: [PATCH 25/94] msgconv/wa-sticker: cache converted sticker pack items --- pkg/msgconv/wa-sticker.go | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/pkg/msgconv/wa-sticker.go b/pkg/msgconv/wa-sticker.go index e9507bc..b9797a6 100644 --- a/pkg/msgconv/wa-sticker.go +++ b/pkg/msgconv/wa-sticker.go @@ -41,6 +41,7 @@ import ( "go.mau.fi/whatsmeow/types" "maunium.net/go/mautrix" "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/database" "maunium.net/go/mautrix/bridgev2/networkid" "maunium.net/go/mautrix/event" @@ -161,8 +162,11 @@ func (mc *MessageConverter) DownloadImagePack(ctx context.Context, userLoginID n }, TypeDescription: "sticker", } + dbKey := database.Key(fmt.Sprintf("stickercache:%x", part.Info.BridgedSticker.ID)) fixStickerDimensions(part.Info) + var packed *event.ImagePackImage if mc.DirectMedia { + dbKey = "" if part.Info.MimeType == "application/was" { part.Info.MimeType = "video/lottie+json" } @@ -170,17 +174,31 @@ func (mc *MessageConverter) DownloadImagePack(ctx context.Context, userLoginID n if err != nil { panic(fmt.Errorf("failed to generate content URI: %w", err)) } + } else if cached := mc.Bridge.DB.KV.Get(ctx, dbKey); cached != "" { + err = json.Unmarshal([]byte(cached), &packed) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal cached sticker data: %w", err) + } } else { err = mc.reuploadWhatsAppAttachment(ctx, sticker, part) if err != nil { return nil, fmt.Errorf("failed to reupload sticker %q: %w", sticker.GetDirectPath(), err) } } - content.Images[shortcode] = &event.ImagePackImage{ - URL: part.URL, - Body: part.Body, - Info: part.Info, + if packed == nil { + packed = &event.ImagePackImage{ + URL: part.URL, + Body: part.Body, + Info: part.Info, + } + if dbKey != "" { + data, _ := json.Marshal(packed) + if data != nil { + mc.Bridge.DB.KV.Set(ctx, dbKey, string(data)) + } + } } + content.Images[shortcode] = packed } return &bridgev2.ImportedImagePack{ From 1010a4d52060cf37734ed0658c71cac8948ef292 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 11 May 2026 14:27:06 +0300 Subject: [PATCH 26/94] 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 b9f5347..7ba785b 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.18.0 go.mau.fi/util v0.9.9-0.20260505143909-8e67f0d355e0 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260506122147-6a7198d94d26 + go.mau.fi/whatsmeow v0.0.0-20260511112314-81f8702130bd golang.org/x/image v0.39.0 golang.org/x/net v0.53.0 golang.org/x/sync v0.20.0 diff --git a/go.sum b/go.sum index 0575b9c..220edd5 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.9-0.20260505143909-8e67f0d355e0 h1:stkCMpY3ULN6sNrPoRYZ5AQ/k go.mau.fi/util v0.9.9-0.20260505143909-8e67f0d355e0/go.mod h1:jE9FfhbgEgAwxei6lomO9v8zdCIATcquONUu4vjRwSs= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260506122147-6a7198d94d26 h1:DyFksXWn7z/NN+TNJ0DomV1/drWjkyiVuJ6RIiy/bo4= -go.mau.fi/whatsmeow v0.0.0-20260506122147-6a7198d94d26/go.mod h1:ijfkzOXauA/Vz/htXEMfOAJSUgglribW5oQeYC9tSSg= +go.mau.fi/whatsmeow v0.0.0-20260511112314-81f8702130bd h1:tqp8Bvki8H9OcoKHDmy94QiQdV7eaiSR/dD9APUlKc0= +go.mau.fi/whatsmeow v0.0.0-20260511112314-81f8702130bd/go.mod h1:ijfkzOXauA/Vz/htXEMfOAJSUgglribW5oQeYC9tSSg= 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.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= From 4ae0bd38f2be5366a4b3004b3dede8de0e230a06 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 11 May 2026 18:57:45 +0300 Subject: [PATCH 27/94] 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 7ba785b..705e578 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.18.0 go.mau.fi/util v0.9.9-0.20260505143909-8e67f0d355e0 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260511112314-81f8702130bd + go.mau.fi/whatsmeow v0.0.0-20260511155711-eb05d94dea7d golang.org/x/image v0.39.0 golang.org/x/net v0.53.0 golang.org/x/sync v0.20.0 diff --git a/go.sum b/go.sum index 220edd5..12e29fb 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.9-0.20260505143909-8e67f0d355e0 h1:stkCMpY3ULN6sNrPoRYZ5AQ/k go.mau.fi/util v0.9.9-0.20260505143909-8e67f0d355e0/go.mod h1:jE9FfhbgEgAwxei6lomO9v8zdCIATcquONUu4vjRwSs= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260511112314-81f8702130bd h1:tqp8Bvki8H9OcoKHDmy94QiQdV7eaiSR/dD9APUlKc0= -go.mau.fi/whatsmeow v0.0.0-20260511112314-81f8702130bd/go.mod h1:ijfkzOXauA/Vz/htXEMfOAJSUgglribW5oQeYC9tSSg= +go.mau.fi/whatsmeow v0.0.0-20260511155711-eb05d94dea7d h1:GBtuMd+MvpxZ0hII0xWzc9N4z1BPhph7aDZb6EizhO4= +go.mau.fi/whatsmeow v0.0.0-20260511155711-eb05d94dea7d/go.mod h1:ijfkzOXauA/Vz/htXEMfOAJSUgglribW5oQeYC9tSSg= 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.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= From 1a4490843684ea488a06c487b7a04070b47b26e1 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 12 May 2026 12:24:46 +0300 Subject: [PATCH 28/94] capabilities: bump versions --- pkg/connector/capabilities.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/connector/capabilities.go b/pkg/connector/capabilities.go index 3864356..3e6f658 100644 --- a/pkg/connector/capabilities.go +++ b/pkg/connector/capabilities.go @@ -52,7 +52,7 @@ func (wa *WhatsAppConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilit } func (wa *WhatsAppConnector) GetBridgeInfoVersion() (info, caps int) { - return 1, 7 + return 1, 8 } const WAMaxFileSize = 2000 * 1024 * 1024 @@ -67,7 +67,7 @@ func supportedIfFFmpeg() event.CapabilitySupportLevel { } func capID() string { - base := "fi.mau.whatsapp.capabilities.2025_12_15" + base := "fi.mau.whatsapp.capabilities.2026_05_12" if ffmpeg.Supported() { return base + "+ffmpeg" } From 0d02df4cf5b08217945b1898da0c5a85157e4334 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 13 May 2026 15:05:53 +0300 Subject: [PATCH 29/94] dependencies: update mautrix-go --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 705e578..033ecf9 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/lib/pq v1.12.3 github.com/rs/zerolog v1.35.1 github.com/tidwall/gjson v1.18.0 - go.mau.fi/util v0.9.9-0.20260505143909-8e67f0d355e0 + go.mau.fi/util v0.9.9-0.20260511124621-9241e81bdf25 go.mau.fi/webp v0.2.0 go.mau.fi/whatsmeow v0.0.0-20260511155711-eb05d94dea7d golang.org/x/image v0.39.0 @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.20.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.27.1-0.20260507135742-7ec18e08eac3 + maunium.net/go/mautrix v0.27.1-0.20260513120123-5fba7e3afae4 ) require ( diff --git a/go.sum b/go.sum index 12e29fb..3ea04fb 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,8 @@ github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.1 h1:vRZG4EzTn70XY6Oh/pVKrQGuMHBkAWlGRC22/85m9L0= go.mau.fi/libsignal v0.2.1/go.mod h1:iVvjrHyfQqWajOUaMEsIfo3IqgVMrhWcPiiEzk7NgoU= -go.mau.fi/util v0.9.9-0.20260505143909-8e67f0d355e0 h1:stkCMpY3ULN6sNrPoRYZ5AQ/kc20a7pmhv6t0sdyVhE= -go.mau.fi/util v0.9.9-0.20260505143909-8e67f0d355e0/go.mod h1:jE9FfhbgEgAwxei6lomO9v8zdCIATcquONUu4vjRwSs= +go.mau.fi/util v0.9.9-0.20260511124621-9241e81bdf25 h1:YPEmc+li7TF6C9AdRTcSLMb6yCHdF27/wNT7kFLIVNg= +go.mau.fi/util v0.9.9-0.20260511124621-9241e81bdf25/go.mod h1:jE9FfhbgEgAwxei6lomO9v8zdCIATcquONUu4vjRwSs= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= go.mau.fi/whatsmeow v0.0.0-20260511155711-eb05d94dea7d h1:GBtuMd+MvpxZ0hII0xWzc9N4z1BPhph7aDZb6EizhO4= @@ -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.27.1-0.20260507135742-7ec18e08eac3 h1:K2Aci+LppxMA2CGzj1FQBSzTh2z4F3Kv4l0tKUsaaxs= -maunium.net/go/mautrix v0.27.1-0.20260507135742-7ec18e08eac3/go.mod h1:2ANjihDB+wv2UAqJapkRekmNXw7khSisccAkE5Jg3P0= +maunium.net/go/mautrix v0.27.1-0.20260513120123-5fba7e3afae4 h1:zNC9eVAhw8FhKpM3AxNAh/iy75UEYX91uJUvqqAYlvo= +maunium.net/go/mautrix v0.27.1-0.20260513120123-5fba7e3afae4/go.mod h1:3sOGhXi3P1V6/NruTA0gujkvTypXVUraWktCuTGyDuM= From bf12524eefee5c83ca2eae8c759715138e0efd09 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 13 May 2026 17:03:48 +0300 Subject: [PATCH 30/94] handlewhatsapp: decrypt message secret data before rerouting LIDs --- go.mod | 2 +- go.sum | 4 +- pkg/connector/handlewhatsapp.go | 74 +++++++++++++++++---------------- 3 files changed, 42 insertions(+), 38 deletions(-) diff --git a/go.mod b/go.mod index 033ecf9..9cd6c8f 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.18.0 go.mau.fi/util v0.9.9-0.20260511124621-9241e81bdf25 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260511155711-eb05d94dea7d + go.mau.fi/whatsmeow v0.0.0-20260513140310-c551a4055c0f golang.org/x/image v0.39.0 golang.org/x/net v0.53.0 golang.org/x/sync v0.20.0 diff --git a/go.sum b/go.sum index 3ea04fb..be22fdf 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.9-0.20260511124621-9241e81bdf25 h1:YPEmc+li7TF6C9AdRTcSLMb6y go.mau.fi/util v0.9.9-0.20260511124621-9241e81bdf25/go.mod h1:jE9FfhbgEgAwxei6lomO9v8zdCIATcquONUu4vjRwSs= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260511155711-eb05d94dea7d h1:GBtuMd+MvpxZ0hII0xWzc9N4z1BPhph7aDZb6EizhO4= -go.mau.fi/whatsmeow v0.0.0-20260511155711-eb05d94dea7d/go.mod h1:ijfkzOXauA/Vz/htXEMfOAJSUgglribW5oQeYC9tSSg= +go.mau.fi/whatsmeow v0.0.0-20260513140310-c551a4055c0f h1:icWtsD1MH5nlo8mEpHMPZ9+1kgHkjmXQroYi0lHXKZ0= +go.mau.fi/whatsmeow v0.0.0-20260513140310-c551a4055c0f/go.mod h1:ijfkzOXauA/Vz/htXEMfOAJSUgglribW5oQeYC9tSSg= 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.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index eb42f43..4c1016c 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -309,14 +309,50 @@ func (wa *WhatsAppClient) rerouteWAMessage(ctx context.Context, evtType string, 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 + } + parsedMessageType := getMessageType(evt.Message) + if parsedMessageType == "ignore" || strings.HasPrefix(parsedMessageType, "unknown_protocol_") { + return + } + if encReact := evt.Message.GetEncReactionMessage(); encReact != nil { + decrypted, err := wa.Client.DecryptReaction(ctx, evt) + if err != nil { + wa.UserLogin.Log.Err(err).Str("message_id", evt.Info.ID).Msg("Failed to decrypt reaction") + return + } + decrypted.Key = encReact.GetTargetMessageKey() + evt.Message.ReactionMessage = decrypted + } + if encComment := evt.Message.GetEncCommentMessage(); encComment != nil { + decrypted, err := wa.Client.DecryptComment(ctx, evt) + if err != nil { + wa.UserLogin.Log.Err(err).Str("message_id", evt.Info.ID).Msg("Failed to decrypt comment") + } else { + decrypted.EncCommentMessage = evt.Message.GetEncCommentMessage() + evt.Message = decrypted + } + } + if encMessage := evt.Message.GetSecretEncryptedMessage(); encMessage != nil { + decrypted, err := wa.Client.DecryptSecretEncryptedMessage(ctx, evt) + if err != nil { + wa.UserLogin.Log.Err(err). + Str("message_id", evt.Info.ID). + Stringer("evt_sender", evt.Info.Sender). + Any("target_message_key", encMessage.TargetMessageKey). + Msg("Failed to decrypt secret-encrypted message") + return + } + evt.RawMessage = decrypted + evt.UnwrapRaw() + parsedMessageType = getMessageType(evt.Message) + } wa.rerouteWAMessage(ctx, "message", &evt.Info.MessageSource, evt.Info.ID) wa.UserLogin.Log.Trace(). Any("info", evt.Info). Any("payload", evt.Message). Msg("Received WhatsApp message") - if evt.Info.Chat == types.StatusBroadcastJID && !wa.Main.Config.EnableStatusBroadcast { - return - } if evt.Info.IsFromMe && evt.Message.GetProtocolMessage().GetHistorySyncNotification() != nil && wa.Main.Bridge.Config.Backfill.Enabled && @@ -351,38 +387,6 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa return } - parsedMessageType := getMessageType(evt.Message) - if parsedMessageType == "ignore" || strings.HasPrefix(parsedMessageType, "unknown_protocol_") { - return - } - if encReact := evt.Message.GetEncReactionMessage(); encReact != nil { - decrypted, err := wa.Client.DecryptReaction(ctx, evt) - if err != nil { - wa.UserLogin.Log.Err(err).Str("message_id", evt.Info.ID).Msg("Failed to decrypt reaction") - return - } - decrypted.Key = encReact.GetTargetMessageKey() - evt.Message.ReactionMessage = decrypted - } - if encComment := evt.Message.GetEncCommentMessage(); encComment != nil { - decrypted, err := wa.Client.DecryptComment(ctx, evt) - if err != nil { - wa.UserLogin.Log.Err(err).Str("message_id", evt.Info.ID).Msg("Failed to decrypt comment") - } else { - decrypted.EncCommentMessage = evt.Message.GetEncCommentMessage() - evt.Message = decrypted - } - } - if encMessage := evt.Message.GetSecretEncryptedMessage(); encMessage != nil { - decrypted, err := wa.Client.DecryptSecretEncryptedMessage(ctx, evt) - if err != nil { - wa.UserLogin.Log.Err(err).Str("message_id", evt.Info.ID).Msg("Failed to decrypt message") - return - } - evt.RawMessage = decrypted - evt.UnwrapRaw() - parsedMessageType = getMessageType(evt.Message) - } res := wa.UserLogin.QueueRemoteEvent(&WAMessageEvent{ MessageInfoWrapper: &MessageInfoWrapper{ Info: evt.Info, From 7f91a71e9d058d2a0cf86e94260b2a117ddbdd65 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 14 May 2026 15:27:58 +0300 Subject: [PATCH 31/94] handlewhatsapp: fix saving history sync notifications --- pkg/connector/handlewhatsapp.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 4c1016c..3abcede 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -313,9 +313,6 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa return } parsedMessageType := getMessageType(evt.Message) - if parsedMessageType == "ignore" || strings.HasPrefix(parsedMessageType, "unknown_protocol_") { - return - } if encReact := evt.Message.GetEncReactionMessage(); encReact != nil { decrypted, err := wa.Client.DecryptReaction(ctx, evt) if err != nil { @@ -355,10 +352,12 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa Msg("Received WhatsApp message") if evt.Info.IsFromMe && evt.Message.GetProtocolMessage().GetHistorySyncNotification() != nil && - wa.Main.Bridge.Config.Backfill.Enabled && - wa.Client.ManualHistorySyncDownload { + wa.Main.Bridge.Config.Backfill.Enabled { wa.saveWAHistorySyncNotification(ctx, evt.Message.ProtocolMessage.HistorySyncNotification) } + if parsedMessageType == "ignore" || strings.HasPrefix(parsedMessageType, "unknown_protocol_") { + return + } messageAssoc := evt.Message.GetMessageContextInfo().GetMessageAssociation() if assocType := messageAssoc.GetAssociationType(); assocType == waE2E.MessageAssociation_HD_IMAGE_DUAL_UPLOAD || assocType == waE2E.MessageAssociation_HD_VIDEO_DUAL_UPLOAD { From 405207108850207c2c7d277832172dae473dd23e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 16 May 2026 13:32:45 +0300 Subject: [PATCH 32/94] Bump version to v26.05 --- CHANGELOG.md | 5 ++++ cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 24 ++++++++++---------- go.sum | 44 ++++++++++++++++++------------------ 4 files changed, 40 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 892be2e..5c0314e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# v26.05 + +* Added support for importing sticker packs from WhatsApp. +* Added support for WhatsApp's new message edit encryption scheme. + # v26.04 * Added support for @room mentions in both directions. diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index b97bc7f..cb1632c 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.04", + Version: "26.05", SemCalVer: true, Connector: &connector.WhatsAppConnector{}, } diff --git a/go.mod b/go.mod index 9cd6c8f..c52b925 100644 --- a/go.mod +++ b/go.mod @@ -2,23 +2,23 @@ module go.mau.fi/mautrix-whatsapp go 1.25.0 -toolchain go1.26.2 +toolchain go1.26.3 tool go.mau.fi/util/cmd/maubuild require ( github.com/lib/pq v1.12.3 github.com/rs/zerolog v1.35.1 - github.com/tidwall/gjson v1.18.0 - go.mau.fi/util v0.9.9-0.20260511124621-9241e81bdf25 + github.com/tidwall/gjson v1.19.0 + go.mau.fi/util v0.9.9 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260513140310-c551a4055c0f - golang.org/x/image v0.39.0 - golang.org/x/net v0.53.0 + go.mau.fi/whatsmeow v0.0.0-20260516102357-8d3700152a69 + golang.org/x/image v0.40.0 + golang.org/x/net v0.54.0 golang.org/x/sync v0.20.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.27.1-0.20260513120123-5fba7e3afae4 + maunium.net/go/mautrix v0.28.0 ) require ( @@ -43,11 +43,11 @@ require ( github.com/yuin/goldmark v1.8.2 // indirect go.mau.fi/libsignal v0.2.1 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect - golang.org/x/crypto v0.50.0 // indirect - golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect - golang.org/x/mod v0.35.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/text v0.36.0 // indirect + golang.org/x/crypto v0.51.0 // indirect + golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a // indirect + golang.org/x/mod v0.36.0 // indirect + golang.org/x/sys v0.44.0 // indirect + golang.org/x/text v0.37.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 be22fdf..5fe1561 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq 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/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= -github.com/tidwall/gjson v1.18.0/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= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -71,31 +71,31 @@ github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.1 h1:vRZG4EzTn70XY6Oh/pVKrQGuMHBkAWlGRC22/85m9L0= go.mau.fi/libsignal v0.2.1/go.mod h1:iVvjrHyfQqWajOUaMEsIfo3IqgVMrhWcPiiEzk7NgoU= -go.mau.fi/util v0.9.9-0.20260511124621-9241e81bdf25 h1:YPEmc+li7TF6C9AdRTcSLMb6yCHdF27/wNT7kFLIVNg= -go.mau.fi/util v0.9.9-0.20260511124621-9241e81bdf25/go.mod h1:jE9FfhbgEgAwxei6lomO9v8zdCIATcquONUu4vjRwSs= +go.mau.fi/util v0.9.9 h1:ujDeXCo07HBor5oQLyO1tHklupmqVmPgasc53d7q/NE= +go.mau.fi/util v0.9.9/go.mod h1:pqt4Vcrt+5gcH/CgrHZg11qSx+b34o6mknGzOEA6waY= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260513140310-c551a4055c0f h1:icWtsD1MH5nlo8mEpHMPZ9+1kgHkjmXQroYi0lHXKZ0= -go.mau.fi/whatsmeow v0.0.0-20260513140310-c551a4055c0f/go.mod h1:ijfkzOXauA/Vz/htXEMfOAJSUgglribW5oQeYC9tSSg= +go.mau.fi/whatsmeow v0.0.0-20260516102357-8d3700152a69 h1:rRcG2I1LVQ11kH2+KSYxNeFNZXp1oKA5Yxmagsf5bks= +go.mau.fi/whatsmeow v0.0.0-20260516102357-8d3700152a69/go.mod h1:SY+3c678dbtckGZF16M+sfEW8ZxTb9xkaKwhXueF5yE= 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.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= -golang.org/x/image v0.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww= -golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw= +golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= +golang.org/x/image v0.40.0 h1:Tw4GyDXMo+daZN1znreBRC3VayR1aLFUyUEOLUdW1a8= +golang.org/x/image v0.40.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -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.27.1-0.20260513120123-5fba7e3afae4 h1:zNC9eVAhw8FhKpM3AxNAh/iy75UEYX91uJUvqqAYlvo= -maunium.net/go/mautrix v0.27.1-0.20260513120123-5fba7e3afae4/go.mod h1:3sOGhXi3P1V6/NruTA0gujkvTypXVUraWktCuTGyDuM= +maunium.net/go/mautrix v0.28.0 h1:vBakLzf8MAdfED3NzAKiMeKQbc3AQ4EAS03NC+TVMXQ= +maunium.net/go/mautrix v0.28.0/go.mod h1:/a9A7LGaqb9B3nho4tLd28n0EPcCdwpm2dxkxkLLgh0= From cbe182fcb1082d5f24225ee812b39d9c2c14db70 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 19 May 2026 17:54:05 +0300 Subject: [PATCH 33/94] 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 c52b925..89dc3a7 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.20.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.28.0 + maunium.net/go/mautrix v0.28.1-0.20260519145316-34b5f49408a3 ) require ( diff --git a/go.sum b/go.sum index 5fe1561..bfe01a5 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.28.0 h1:vBakLzf8MAdfED3NzAKiMeKQbc3AQ4EAS03NC+TVMXQ= -maunium.net/go/mautrix v0.28.0/go.mod h1:/a9A7LGaqb9B3nho4tLd28n0EPcCdwpm2dxkxkLLgh0= +maunium.net/go/mautrix v0.28.1-0.20260519145316-34b5f49408a3 h1:l86igJY8Te5JEBNPL9NSpZaGv6eHsxtCrAabXv8KPJo= +maunium.net/go/mautrix v0.28.1-0.20260519145316-34b5f49408a3/go.mod h1:/a9A7LGaqb9B3nho4tLd28n0EPcCdwpm2dxkxkLLgh0= From b46b41edf882059131bfcdd4dbecb3ab8ea808d5 Mon Sep 17 00:00:00 2001 From: Kishan Bagaria <1093313+KishanBagaria@users.noreply.github.com> Date: Tue, 19 May 2026 20:51:26 +0530 Subject: [PATCH 34/94] handlewhatsapp,msgconv,directmedia: fix HD media replacements (#920) Co-authored-by: Tulir Asokan --- pkg/connector/directmedia.go | 4 ++++ pkg/connector/events.go | 3 ++- pkg/connector/handlewhatsapp.go | 4 ++++ pkg/msgconv/wa-media.go | 9 ++++++++- pkg/waid/id.go | 7 ++++--- pkg/waid/mediaid.go | 12 ++++++++++-- 6 files changed, 32 insertions(+), 7 deletions(-) diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index ac2fbd2..70289cd 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -17,6 +17,7 @@ package connector import ( + "bytes" "context" "encoding/json" "errors" @@ -171,6 +172,9 @@ func (wa *WhatsAppConnector) downloadMessageDirectMedia(ctx context.Context, par if err != nil { return nil, fmt.Errorf("failed to unmarshal media keys: %w", err) } + if version := parsedID.Message.Version; len(version) > 0 && !bytes.Equal(version, keys.EncSHA256) && !bytes.Equal(version, keys.SHA256) { + return nil, mautrix.MNotFound.WithMessage("Version mismatch, media was likely replaced") + } var ul *bridgev2.UserLogin if parsedID.UserLogin != "" { ul = wa.Bridge.GetCachedUserLoginByID(parsedID.UserLogin) diff --git a/pkg/connector/events.go b/pkg/connector/events.go index de5bdb8..3e809b7 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -91,6 +91,7 @@ type WAMessageEvent struct { parsedMessageType string isUndecryptableUpsertSubEvent bool + dontRenderEdited bool postHandle func() } @@ -204,7 +205,7 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por } } editPart := cm.Parts[0].ToEditPart(existing[0]) - if evt.isUndecryptableUpsertSubEvent { + if evt.isUndecryptableUpsertSubEvent || evt.dontRenderEdited { if editPart.TopLevelExtra == nil { editPart.TopLevelExtra = make(map[string]any) } diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 3abcede..79871b3 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -359,6 +359,7 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa return } + dontRenderEdited := false 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() @@ -377,6 +378,8 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa evt.Message = &waE2E.Message{ ProtocolMessage: protocolMsg, } + parsedMessageType = getMessageType(evt.Message) + dontRenderEdited = true } else if assocType == waE2E.MessageAssociation_MOTION_PHOTO { //evt.Message = evt.Message.GetAssociatedChildMessage().GetMessage() wa.UserLogin.Log.Debug(). @@ -395,6 +398,7 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa MsgEvent: evt, parsedMessageType: parsedMessageType, + dontRenderEdited: dontRenderEdited, }) return res.Success } diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index c2a8624..38fe9e2 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -90,7 +90,7 @@ func (mc *MessageConverter) convertMediaMessage( var err error portal := getPortal(ctx) idOverride := getEditTargetID(ctx) - preparedMedia.URL, err = portal.Bridge.Matrix.GenerateContentURI(ctx, waid.MakeMediaID(messageInfo, idOverride, portal.Receiver)) + preparedMedia.URL, err = portal.Bridge.Matrix.GenerateContentURI(ctx, waid.MakeMediaID(messageInfo, idOverride, portal.Receiver, getMediaIDVersion(msg))) if err != nil { panic(fmt.Errorf("failed to generate content URI: %w", err)) } @@ -119,6 +119,13 @@ func (mc *MessageConverter) convertMediaMessage( return } +func getMediaIDVersion(msg MediaMessage) []byte { + if encSHA256 := msg.GetFileEncSHA256(); len(encSHA256) > 0 { + return encSHA256 + } + return msg.GetFileSHA256() +} + func (mc *MessageConverter) convertAlbumMessage(ctx context.Context, msg *waE2E.AlbumMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { parts := make([]string, 0, 2) if msg.GetExpectedImageCount() > 0 { diff --git a/pkg/waid/id.go b/pkg/waid/id.go index 840e0f1..e522f5f 100644 --- a/pkg/waid/id.go +++ b/pkg/waid/id.go @@ -88,9 +88,10 @@ func MakeFakeMessageID(chat, sender types.JID, data string) networkid.MessageID } type ParsedMessageID struct { - Chat types.JID - Sender types.JID - ID types.MessageID + Chat types.JID + Sender types.JID + ID types.MessageID + Version []byte } func (pmi *ParsedMessageID) String() networkid.MessageID { diff --git a/pkg/waid/mediaid.go b/pkg/waid/mediaid.go index 093ece1..716e187 100644 --- a/pkg/waid/mediaid.go +++ b/pkg/waid/mediaid.go @@ -36,7 +36,7 @@ const ( mediaIDTypeStickerPackItem = 252 ) -func MakeMediaID(messageInfo *types.MessageInfo, idOverride types.MessageID, receiver networkid.UserLoginID) networkid.MediaID { +func MakeMediaID(messageInfo *types.MessageInfo, idOverride types.MessageID, receiver networkid.UserLoginID, version []byte) networkid.MediaID { compactChat := compactJID(messageInfo.Chat.ToNonAD()) compactSender := compactJID(messageInfo.Sender.ToNonAD()) receiverID := compactJID(ParseUserLoginID(receiver, 0)) @@ -46,7 +46,7 @@ func MakeMediaID(messageInfo *types.MessageInfo, idOverride types.MessageID, rec } else { compactID = compactMsgID(messageInfo.ID) } - mediaID := make([]byte, 0, 5+len(compactChat)+len(compactSender)+len(receiverID)+len(compactID)) + mediaID := make([]byte, 0, 6+len(compactChat)+len(compactSender)+len(receiverID)+len(compactID)+len(version)) mediaID = append(mediaID, mediaIDTypeMessage) mediaID = append(mediaID, byte(len(compactChat))) mediaID = append(mediaID, compactChat...) @@ -56,6 +56,8 @@ func MakeMediaID(messageInfo *types.MessageInfo, idOverride types.MessageID, rec mediaID = append(mediaID, receiverID...) mediaID = append(mediaID, byte(len(compactID))) mediaID = append(mediaID, compactID...) + mediaID = append(mediaID, byte(len(version))) + mediaID = append(mediaID, version...) return mediaID } @@ -138,6 +140,12 @@ func ParseMediaID(mediaID networkid.MediaID) (*ParsedMediaID, error) { Sender: senderJID, ID: id, } + if len(mediaID) > 0 { + parsed.Message.Version, err = readCompact(&mediaID, rawBytes) + if err != nil { + return nil, fmt.Errorf("failed to parse version: %w", err) + } + } parsed.UserLogin = MakeUserLoginID(receiverID) case mediaIDTypeAvatar, mediaIDTypeCommunityAvatar: targetJID, err := readCompact(&mediaID, parseCompactJID) From c74327e8e48dd5f0be3bd36d32909434822d346b Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 21 May 2026 16:49:45 +0300 Subject: [PATCH 35/94] login: use 400 for whatsapp ratelimits --- pkg/connector/login.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/connector/login.go b/pkg/connector/login.go index 88e7b75..b70ab1a 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -76,8 +76,8 @@ var ( } ErrRateLimitedByWhatsApp = bridgev2.RespError{ ErrCode: "FI.MAU.WHATSAPP.RATE_LIMITED", - Err: "Rate limited by WhatsApp", - StatusCode: http.StatusTooManyRequests, + Err: "Rate limited by WhatsApp. Try again later.", + StatusCode: http.StatusBadRequest, } ) From 9e7dc0bef0066ab536af5382ebac8523190d8b45 Mon Sep 17 00:00:00 2001 From: Kishan Bagaria <1093313+KishanBagaria@users.noreply.github.com> Date: Mon, 25 May 2026 18:10:58 +0530 Subject: [PATCH 36/94] msgconv/from-whatsapp: convert message history notices (#922) Co-authored-by: Tulir Asokan --- pkg/connector/mediarequest.go | 5 +- pkg/connector/wamsgtype.go | 2 + pkg/msgconv/from-whatsapp.go | 2 + pkg/msgconv/wa-misc.go | 87 +++++++++++++++++++++++++++++++++++ pkg/waid/dbmeta.go | 7 +++ 5 files changed, 100 insertions(+), 3 deletions(-) diff --git a/pkg/connector/mediarequest.go b/pkg/connector/mediarequest.go index 2c382d1..2686250 100644 --- a/pkg/connector/mediarequest.go +++ b/pkg/connector/mediarequest.go @@ -70,10 +70,9 @@ func (wa *WhatsAppClient) processFailedMedia(ctx context.Context, portalKey netw func (wa *WhatsAppClient) mediaRequestLoop(ctx context.Context) { log := wa.UserLogin.Log.With().Str("loop", "media requests").Logger() ctx = log.WithContext(ctx) - tzName := wa.UserLogin.Metadata.(*waid.UserLoginMetadata).Timezone - userTz, err := time.LoadLocation(tzName) + userTz, err := wa.UserLogin.Metadata.(*waid.UserLoginMetadata).LoadTimezone() var startIn time.Duration - if tzName != "" && err == nil && userTz != nil { + if err == nil && userTz != nil { now := time.Now() startAt := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, userTz) startAt = startAt.Add(time.Duration(wa.Main.Config.HistorySync.MediaRequests.RequestLocalTime) * time.Minute) diff --git a/pkg/connector/wamsgtype.go b/pkg/connector/wamsgtype.go index 92e7f86..a5b65e8 100644 --- a/pkg/connector/wamsgtype.go +++ b/pkg/connector/wamsgtype.go @@ -130,6 +130,8 @@ func getMessageType(waMsg *waE2E.Message) string { return "secret encrypted" case waMsg.PollResultSnapshotMessage != nil: return "poll result snapshot" + case waMsg.MessageHistoryNotice != nil: + return "message history notice" case waMsg.MessageHistoryBundle != nil: return "message history bundle" case waMsg.RequestPhoneNumberMessage != nil: diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 11569e2..f0e4560 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -215,6 +215,8 @@ func (mc *MessageConverter) ToMatrix( part, contextInfo = mc.convertPlaceholderMessage(ctx, waMsg) case waMsg.GroupInviteMessage != nil: part, contextInfo = mc.convertGroupInviteMessage(ctx, info, waMsg.GroupInviteMessage) + case waMsg.MessageHistoryNotice != nil: + part, contextInfo = mc.convertMessageHistoryNotice(ctx, info, waMsg.MessageHistoryNotice) case waMsg.ProtocolMessage != nil && waMsg.ProtocolMessage.GetType() == waE2E.ProtocolMessage_EPHEMERAL_SETTING: part, contextInfo = mc.convertEphemeralSettingMessage(ctx, waMsg.ProtocolMessage, info.Timestamp, isBackfill) case waMsg.EncCommentMessage != nil: diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index 7ca4427..71de586 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -26,6 +26,7 @@ import ( "github.com/rs/zerolog" "go.mau.fi/util/exerrors" + "go.mau.fi/util/exfmt" "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow/proto/waAICommonDeprecated" "go.mau.fi/whatsmeow/proto/waE2E" @@ -117,6 +118,92 @@ func (mc *MessageConverter) convertGroupInviteMessage(ctx context.Context, info }, msg.GetContextInfo() } +func (mc *MessageConverter) formatMessageHistoryNoticeJID(ctx context.Context, jid types.JID) string { + _, displayName, err := mc.getBasicUserInfo(ctx, jid) + if err != nil { + zerolog.Ctx(ctx).Err(err).Stringer("jid", jid).Msg("Failed to get user info for message history notice") + } else if displayName != "" { + return displayName + } + switch jid.Server { + case types.DefaultUserServer: + return "+" + jid.User + default: + return "Unknown user " + jid.String() + } +} + +const maxMessageHistoryNoticeReceivers = 5 + +var others = exfmt.Pluralizable("other") + +func (mc *MessageConverter) formatMessageHistoryNoticeReceivers(ctx context.Context, receivers []string) string { + receiverLimit := min(len(receivers), maxMessageHistoryNoticeReceivers) + receiverNames := make([]string, 0, receiverLimit) + for _, receiver := range receivers[:receiverLimit] { + jid, err := types.ParseJID(receiver) + if err != nil { + zerolog.Ctx(ctx).Err(err).Str("receiver", receiver).Msg("Failed to parse message history receiver JID") + receiverNames = append(receiverNames, receiver) + } else { + receiverNames = append(receiverNames, mc.formatMessageHistoryNoticeJID(ctx, jid)) + } + } + receiverText := strings.Join(receiverNames, ", ") + if len(receivers) > receiverLimit { + receiverText = fmt.Sprintf("%s + %s", receiverText, others(len(receivers)-receiverLimit)) + } + return receiverText +} + +func (mc *MessageConverter) messageHistoryNoticeLocation(ctx context.Context) *time.Location { + portal := getPortal(ctx) + loginID := portal.Receiver + if loginID == "" { + loginID = waid.MakeUserLoginID(getClient(ctx).Store.GetJID().ToNonAD()) + } + if login := mc.Bridge.GetCachedUserLoginByID(loginID); login != nil { + meta, _ := login.Metadata.(*waid.UserLoginMetadata) + loc, err := meta.LoadTimezone() + if err != nil { + zerolog.Ctx(ctx).Err(err).Str("timezone", meta.Timezone).Msg("Failed to load user timezone for message history notice") + } else if loc != nil { + return loc + } + } + return time.Local +} + +func (mc *MessageConverter) convertMessageHistoryNotice(ctx context.Context, info *types.MessageInfo, msg *waE2E.MessageHistoryNotice) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { + metadata := msg.GetMessageHistoryMetadata() + + sender := mc.formatMessageHistoryNoticeJID(ctx, info.Sender) + body := fmt.Sprintf("%s sent message history", sender) + if receiverText := mc.formatMessageHistoryNoticeReceivers(ctx, metadata.GetHistoryReceivers()); receiverText != "" { + body = fmt.Sprintf("%s to %s", body, receiverText) + } + if count := metadata.GetMessageCount(); count > 0 { + messageWord := "messages" + if count == 1 { + messageWord = "message" + } + body = fmt.Sprintf("%s (%d %s)", body, count, messageWord) + } + if metadata != nil && metadata.OldestMessageTimestampInWindow != nil { + oldestTS := time.Unix(metadata.GetOldestMessageTimestampInWindow(), 0).In(mc.messageHistoryNoticeLocation(ctx)) + body = fmt.Sprintf("%s, starting %s", body, oldestTS.Format("Jan 2, 2006 at 3:04 PM")) + } + body += "." + + return &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: &event.MessageEventContent{ + MsgType: event.MsgNotice, + Body: body, + }, + }, msg.GetContextInfo() +} + func (mc *MessageConverter) convertEphemeralSettingMessage(ctx context.Context, msg *waE2E.ProtocolMessage, ts time.Time, isBackfill bool) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { portal := getPortal(ctx) portalMeta := portal.Metadata.(*waid.PortalMetadata) diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index 2785105..59e9a9e 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -61,6 +61,13 @@ func (m *UserLoginMetadata) GeneratePushKeys() { } } +func (m *UserLoginMetadata) LoadTimezone() (*time.Location, error) { + if m == nil || m.Timezone == "" { + return nil, nil + } + return time.LoadLocation(m.Timezone) +} + type MessageErrorType string const ( From df86e40e1ef60c11b346b76ae7697e98ca185a5e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 25 May 2026 15:20:23 +0300 Subject: [PATCH 37/94] config: validate displayname template when loading config --- pkg/connector/config.go | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/pkg/connector/config.go b/pkg/connector/config.go index 2445647..e51c80a 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -2,6 +2,7 @@ package connector import ( _ "embed" + "fmt" "strings" "text/template" "time" @@ -90,7 +91,15 @@ func (c *Config) UnmarshalYAML(node *yaml.Node) error { func (c *Config) PostProcess() error { var err error c.displaynameTemplate, err = template.New("displayname").Parse(c.DisplaynameTemplate) - return err + if err != nil { + return err + } + // Try to execute template to make sure it's valid + _, err = c.formatDisplayname(types.PSAJID, "", types.ContactInfo{}) + if err != nil { + return fmt.Errorf("failed to execute displayname template: %w", err) + } + return nil } func upgradeConfig(helper up.Helper) { @@ -151,7 +160,7 @@ type DisplaynameParams struct { Short string } -func (c *Config) FormatDisplayname(jid types.JID, phone string, contact types.ContactInfo) string { +func (c *Config) formatDisplayname(jid types.JID, phone string, contact types.ContactInfo) (string, error) { var nameBuf strings.Builder if phone == "" && jid.Server == types.DefaultUserServer { phone = "+" + jid.User @@ -170,13 +179,21 @@ func (c *Config) FormatDisplayname(jid types.JID, phone string, contact types.Co Name: contact.FullName, Short: contact.FirstName, }) + return nameBuf.String(), err +} + +func (c *Config) FormatDisplayname(jid types.JID, phone string, contact types.ContactInfo) string { + name, err := c.formatDisplayname(jid, phone, contact) if err != nil { panic(err) } - return nameBuf.String() + return name } func redactPhone(phone string) string { + if len(phone) <= 4 { + return phone + } // This doesn't keep 2+ digit country codes properly, but whatever return phone[:2] + strings.Repeat("∙", len(phone)-4) + phone[len(phone)-2:] } From 30bbbcaf6f351526c9a43beb4d49ece904bf1e95 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 25 May 2026 15:24:13 +0300 Subject: [PATCH 38/94] msgconv/from-whatsapp: add some html escapes --- pkg/msgconv/wa-location.go | 3 ++- pkg/msgconv/wa-misc.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/msgconv/wa-location.go b/pkg/msgconv/wa-location.go index 17efecd..4a1e043 100644 --- a/pkg/msgconv/wa-location.go +++ b/pkg/msgconv/wa-location.go @@ -20,6 +20,7 @@ import ( "bytes" "context" "fmt" + "html" "image" "math" "net/http" @@ -51,7 +52,7 @@ func (mc *MessageConverter) convertLocationMessage(ctx context.Context, msg *waE MsgType: event.MsgLocation, Body: fmt.Sprintf("Location: %s\n%s\n%s", name, msg.GetAddress(), url), Format: event.FormatHTML, - FormattedBody: fmt.Sprintf("Location: %s
%s", url, name, msg.GetAddress()), + FormattedBody: fmt.Sprintf(`Location: %s
%s`, html.EscapeString(url), html.EscapeString(name), html.EscapeString(msg.GetAddress())), GeoURI: fmt.Sprintf("geo:%.5f,%.5f", msg.GetDegreesLatitude(), msg.GetDegreesLongitude()), } diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index 71de586..fa495e0 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -20,6 +20,7 @@ import ( "context" "encoding/base64" "fmt" + "html" "html/template" "strings" "time" @@ -101,7 +102,7 @@ func (mc *MessageConverter) convertGroupInviteMessage(ctx context.Context, info } } - htmlMessage := fmt.Sprintf(template, event.TextToHTML(msg.GetCaption()), msg.GetGroupName(), expiry, mc.Bridge.Config.CommandPrefix) + htmlMessage := fmt.Sprintf(template, event.TextToHTML(msg.GetCaption()), html.EscapeString(msg.GetGroupName()), expiry, mc.Bridge.Config.CommandPrefix) content := &event.MessageEventContent{ MsgType: event.MsgText, Body: format.HTMLToText(htmlMessage), From 5e3172b8168454efc1098071a96550a6bdaa968b Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 25 May 2026 15:33:41 +0300 Subject: [PATCH 39/94] dependencies: update whatsmeow Closes #918 Fixes #901 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 89dc3a7..e38d875 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.9 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260516102357-8d3700152a69 + go.mau.fi/whatsmeow v0.0.0-20260525123251-933deb5f2ee9 golang.org/x/image v0.40.0 golang.org/x/net v0.54.0 golang.org/x/sync v0.20.0 diff --git a/go.sum b/go.sum index bfe01a5..2782985 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.9 h1:ujDeXCo07HBor5oQLyO1tHklupmqVmPgasc53d7q/NE= go.mau.fi/util v0.9.9/go.mod h1:pqt4Vcrt+5gcH/CgrHZg11qSx+b34o6mknGzOEA6waY= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260516102357-8d3700152a69 h1:rRcG2I1LVQ11kH2+KSYxNeFNZXp1oKA5Yxmagsf5bks= -go.mau.fi/whatsmeow v0.0.0-20260516102357-8d3700152a69/go.mod h1:SY+3c678dbtckGZF16M+sfEW8ZxTb9xkaKwhXueF5yE= +go.mau.fi/whatsmeow v0.0.0-20260525123251-933deb5f2ee9 h1:l1E9YRXQhjS7Sm6XbYPq8oiSedSCOB6h3FMqZ8pt+K4= +go.mau.fi/whatsmeow v0.0.0-20260525123251-933deb5f2ee9/go.mod h1:SY+3c678dbtckGZF16M+sfEW8ZxTb9xkaKwhXueF5yE= 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.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= From a1f7bf9b9318543f1af0aa6dbbee2b8efe5d7d3e Mon Sep 17 00:00:00 2001 From: Kishan Bagaria <1093313+KishanBagaria@users.noreply.github.com> Date: Mon, 25 May 2026 19:02:24 +0530 Subject: [PATCH 40/94] msgconv/from-whatsapp: hide message history bundles (#924) --- pkg/connector/wamsgtype.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/connector/wamsgtype.go b/pkg/connector/wamsgtype.go index a5b65e8..4bcf483 100644 --- a/pkg/connector/wamsgtype.go +++ b/pkg/connector/wamsgtype.go @@ -132,8 +132,6 @@ func getMessageType(waMsg *waE2E.Message) string { return "poll result snapshot" case waMsg.MessageHistoryNotice != nil: return "message history notice" - case waMsg.MessageHistoryBundle != nil: - return "message history bundle" case waMsg.RequestPhoneNumberMessage != nil: return "request phone number" case waMsg.KeepInChatMessage != nil: @@ -154,7 +152,7 @@ func getMessageType(waMsg *waE2E.Message) string { return "chat" case waMsg.PlaceholderMessage != nil: return "placeholder" - case waMsg.SenderKeyDistributionMessage != nil, waMsg.StickerSyncRmrMessage != nil: + case waMsg.SenderKeyDistributionMessage != nil, waMsg.StickerSyncRmrMessage != nil, waMsg.MessageHistoryBundle != nil: return "ignore" default: return "unknown" From 91932df97923c57b074f634f527226a7aadac497 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 27 May 2026 14:00:43 +0300 Subject: [PATCH 41/94] 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 e38d875..0a9d8f8 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.9 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260525123251-933deb5f2ee9 + go.mau.fi/whatsmeow v0.0.0-20260525144132-563bcaa0f632 golang.org/x/image v0.40.0 golang.org/x/net v0.54.0 golang.org/x/sync v0.20.0 diff --git a/go.sum b/go.sum index 2782985..d86e782 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.9 h1:ujDeXCo07HBor5oQLyO1tHklupmqVmPgasc53d7q/NE= go.mau.fi/util v0.9.9/go.mod h1:pqt4Vcrt+5gcH/CgrHZg11qSx+b34o6mknGzOEA6waY= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260525123251-933deb5f2ee9 h1:l1E9YRXQhjS7Sm6XbYPq8oiSedSCOB6h3FMqZ8pt+K4= -go.mau.fi/whatsmeow v0.0.0-20260525123251-933deb5f2ee9/go.mod h1:SY+3c678dbtckGZF16M+sfEW8ZxTb9xkaKwhXueF5yE= +go.mau.fi/whatsmeow v0.0.0-20260525144132-563bcaa0f632 h1:ewBVzZW9EBFoxcZJnR8KCYCNXVHH9leNuF6V+pr8Keg= +go.mau.fi/whatsmeow v0.0.0-20260525144132-563bcaa0f632/go.mod h1:SY+3c678dbtckGZF16M+sfEW8ZxTb9xkaKwhXueF5yE= 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.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= From 6c6ea098de74d57dff3e45b15dcfa2f049f76e31 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 27 May 2026 19:23:11 +0300 Subject: [PATCH 42/94] 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 0a9d8f8..f38c6d0 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.20.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.28.1-0.20260519145316-34b5f49408a3 + maunium.net/go/mautrix v0.28.1-0.20260527103108-d00b9b09fcd5 ) require ( diff --git a/go.sum b/go.sum index d86e782..741ec53 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.28.1-0.20260519145316-34b5f49408a3 h1:l86igJY8Te5JEBNPL9NSpZaGv6eHsxtCrAabXv8KPJo= -maunium.net/go/mautrix v0.28.1-0.20260519145316-34b5f49408a3/go.mod h1:/a9A7LGaqb9B3nho4tLd28n0EPcCdwpm2dxkxkLLgh0= +maunium.net/go/mautrix v0.28.1-0.20260527103108-d00b9b09fcd5 h1:9bx9er6TJxQ2saQw63gwT0HZWg9/0Q30x8V3foKuNBo= +maunium.net/go/mautrix v0.28.1-0.20260527103108-d00b9b09fcd5/go.mod h1:/a9A7LGaqb9B3nho4tLd28n0EPcCdwpm2dxkxkLLgh0= From 7592f9810377ac5052d321d2cceca2a2dee4fe36 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 28 May 2026 17:22:50 +0300 Subject: [PATCH 43/94] chatinfo: add implicit own membership to parent groups --- pkg/connector/chatinfo.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 5555566..7c7dc8e 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -257,7 +257,7 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn Name: ptr.Ptr(info.Name), Topic: ptr.Ptr(info.Topic), Members: &bridgev2.ChatMemberList{ - IsFull: !info.IsIncognito, + IsFull: !info.IsIncognito && !info.IsParent, TotalMemberCount: len(info.Participants), MemberMap: make(map[networkid.UserID]bridgev2.ChatMember, len(info.Participants)), PowerLevels: &bridgev2.PowerLevelOverrides{ @@ -284,11 +284,15 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn }, ExtraUpdates: extraUpdater, } + var hasSelf bool for _, pcp := range info.Participants { member := bridgev2.ChatMember{ EventSender: wa.makeEventSender(ctx, pcp.JID), Membership: event.MembershipJoin, } + if member.EventSender.IsFromMe { + hasSelf = true + } if pcp.IsSuperAdmin { member.PowerLevel = ptr.Ptr(superAdminPL) } else if pcp.IsAdmin { @@ -299,18 +303,21 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn member.MemberEventExtra = map[string]any{ "com.beeper.exclude_from_timeline": true, } - wrapped.Members.MemberMap[waid.MakeUserID(pcp.JID)] = member + wrapped.Members.MemberMap.Set(member) if pcp.JID.Server == types.HiddenUserServer && !pcp.PhoneNumber.IsEmpty() { - wrapped.Members.MemberMap[waid.MakeUserID(pcp.PhoneNumber)] = bridgev2.ChatMember{ + wrapped.Members.MemberMap.Add(bridgev2.ChatMember{ EventSender: bridgev2.EventSender{Sender: waid.MakeUserID(pcp.PhoneNumber)}, Membership: event.MembershipLeave, PrevMembership: event.MembershipJoin, MemberEventExtra: map[string]any{ "com.beeper.exclude_from_timeline": true, }, - } + }) } } + if info.IsParent && !hasSelf && info.AddressingMode == types.AddressingModeLID { + wrapped.Members.MemberMap.Add(bridgev2.ChatMember{EventSender: wa.makeEventSender(ctx, wa.Device.LID)}) + } if !info.LinkedParentJID.IsEmpty() { wrapped.ParentID = ptr.Ptr(waid.MakePortalID(info.LinkedParentJID)) From eefd547259dd4ec166ab07ca66ec42ad6e131f85 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 29 May 2026 13:24:27 +0300 Subject: [PATCH 44/94] dependencies: update whatsmeow --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index f38c6d0..1265cfc 100644 --- a/go.mod +++ b/go.mod @@ -12,9 +12,9 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.9 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260525144132-563bcaa0f632 + go.mau.fi/whatsmeow v0.0.0-20260529101937-a7ea56383ec4 golang.org/x/image v0.40.0 - golang.org/x/net v0.54.0 + golang.org/x/net v0.55.0 golang.org/x/sync v0.20.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 @@ -41,12 +41,12 @@ require ( github.com/tidwall/sjson v1.2.5 // indirect github.com/vektah/gqlparser/v2 v2.5.27 // indirect github.com/yuin/goldmark v1.8.2 // indirect - go.mau.fi/libsignal v0.2.1 // indirect + go.mau.fi/libsignal v0.2.2 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect - golang.org/x/crypto v0.51.0 // indirect + golang.org/x/crypto v0.52.0 // indirect golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a // indirect golang.org/x/mod v0.36.0 // indirect - golang.org/x/sys v0.44.0 // indirect + golang.org/x/sys v0.45.0 // indirect golang.org/x/text v0.37.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect diff --git a/go.sum b/go.sum index 741ec53..b1c29ff 100644 --- a/go.sum +++ b/go.sum @@ -69,31 +69,31 @@ github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTd github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= -go.mau.fi/libsignal v0.2.1 h1:vRZG4EzTn70XY6Oh/pVKrQGuMHBkAWlGRC22/85m9L0= -go.mau.fi/libsignal v0.2.1/go.mod h1:iVvjrHyfQqWajOUaMEsIfo3IqgVMrhWcPiiEzk7NgoU= +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.9.9 h1:ujDeXCo07HBor5oQLyO1tHklupmqVmPgasc53d7q/NE= go.mau.fi/util v0.9.9/go.mod h1:pqt4Vcrt+5gcH/CgrHZg11qSx+b34o6mknGzOEA6waY= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260525144132-563bcaa0f632 h1:ewBVzZW9EBFoxcZJnR8KCYCNXVHH9leNuF6V+pr8Keg= -go.mau.fi/whatsmeow v0.0.0-20260525144132-563bcaa0f632/go.mod h1:SY+3c678dbtckGZF16M+sfEW8ZxTb9xkaKwhXueF5yE= +go.mau.fi/whatsmeow v0.0.0-20260529101937-a7ea56383ec4 h1:PasY/1lMvmSeSxzXLxqgxf95pjwcnTeQ77t4ERiCbY8= +go.mau.fi/whatsmeow v0.0.0-20260529101937-a7ea56383ec4/go.mod h1:9hto2r5yVE5yyNTRrZErKNSflGBKxIplUVXAD3EJFDE= 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.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= -golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw= golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= golang.org/x/image v0.40.0 h1:Tw4GyDXMo+daZN1znreBRC3VayR1aLFUyUEOLUdW1a8= golang.org/x/image v0.40.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA= golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= -golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= -golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= -golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= From c1ae62ccaed21b5e45013e03be35063b4e4e48c3 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 1 Jun 2026 01:43:49 +0300 Subject: [PATCH 45/94] 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 1265cfc..47daa19 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.9 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260529101937-a7ea56383ec4 + go.mau.fi/whatsmeow v0.0.0-20260531220358-9ff5508a26c2 golang.org/x/image v0.40.0 golang.org/x/net v0.55.0 golang.org/x/sync v0.20.0 diff --git a/go.sum b/go.sum index b1c29ff..b4fcfd5 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.9 h1:ujDeXCo07HBor5oQLyO1tHklupmqVmPgasc53d7q/NE= go.mau.fi/util v0.9.9/go.mod h1:pqt4Vcrt+5gcH/CgrHZg11qSx+b34o6mknGzOEA6waY= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260529101937-a7ea56383ec4 h1:PasY/1lMvmSeSxzXLxqgxf95pjwcnTeQ77t4ERiCbY8= -go.mau.fi/whatsmeow v0.0.0-20260529101937-a7ea56383ec4/go.mod h1:9hto2r5yVE5yyNTRrZErKNSflGBKxIplUVXAD3EJFDE= +go.mau.fi/whatsmeow v0.0.0-20260531220358-9ff5508a26c2 h1:1ymXAy8tRIYzwEL8cmXEWg3QX6XP3hDt2eqBSh2ZtRI= +go.mau.fi/whatsmeow v0.0.0-20260531220358-9ff5508a26c2/go.mod h1:9hto2r5yVE5yyNTRrZErKNSflGBKxIplUVXAD3EJFDE= 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.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= From ccd62ee30e799c0674d7138a1275c2f64152a5ea Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 2 Jun 2026 20:44:57 +0300 Subject: [PATCH 46/94] backfill: add trace log with content if message fails to parse --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/backfill.go | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 47daa19..4665f82 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.9 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260531220358-9ff5508a26c2 + go.mau.fi/whatsmeow v0.0.0-20260602173933-595ceb0c22bf golang.org/x/image v0.40.0 golang.org/x/net v0.55.0 golang.org/x/sync v0.20.0 diff --git a/go.sum b/go.sum index b4fcfd5..b682043 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.9 h1:ujDeXCo07HBor5oQLyO1tHklupmqVmPgasc53d7q/NE= go.mau.fi/util v0.9.9/go.mod h1:pqt4Vcrt+5gcH/CgrHZg11qSx+b34o6mknGzOEA6waY= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260531220358-9ff5508a26c2 h1:1ymXAy8tRIYzwEL8cmXEWg3QX6XP3hDt2eqBSh2ZtRI= -go.mau.fi/whatsmeow v0.0.0-20260531220358-9ff5508a26c2/go.mod h1:9hto2r5yVE5yyNTRrZErKNSflGBKxIplUVXAD3EJFDE= +go.mau.fi/whatsmeow v0.0.0-20260602173933-595ceb0c22bf h1:zyTgHzoTL1rPeLn8B761tnUoNTHxfhy6DsQbHNzc7/Q= +go.mau.fi/whatsmeow v0.0.0-20260602173933-595ceb0c22bf/go.mod h1:9hto2r5yVE5yyNTRrZErKNSflGBKxIplUVXAD3EJFDE= 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.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index b4ef181..0e6de76 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -260,6 +260,9 @@ func (wa *WhatsAppClient) handleWAHistorySync( Str("msg_id", rawMsg.GetMessage().GetKey().GetID()). Uint64("msg_time_seconds", rawMsg.GetMessage().GetMessageTimestamp()). Msg("Dropping historical message due to parse error") + log.Trace(). + Any("web_message_info", rawMsg.GetMessage()). + Msg("Content of event that failed to parse") continue } if firstItemTime.IsZero() { @@ -603,6 +606,9 @@ func (wa *WhatsAppClient) convertHistorySyncMessages( Str("msg_id", msg.GetKey().GetID()). Uint64("msg_time_seconds", msg.GetMessageTimestamp()). Msg("Dropping historical message due to parse error") + zerolog.Ctx(ctx).Trace(). + Any("web_message_info", msg.GetMessage()). + Msg("Content of event that failed to parse") continue } if !explodeOnError { From e4f84014e7921783f34f5d88f2a2b8a8bf23c8a9 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 3 Jun 2026 16:32:17 +0300 Subject: [PATCH 47/94] 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 4665f82..4efd3b8 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.9 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260602173933-595ceb0c22bf + go.mau.fi/whatsmeow v0.0.0-20260603132417-6a7ac9915382 golang.org/x/image v0.40.0 golang.org/x/net v0.55.0 golang.org/x/sync v0.20.0 diff --git a/go.sum b/go.sum index b682043..764681f 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.9 h1:ujDeXCo07HBor5oQLyO1tHklupmqVmPgasc53d7q/NE= go.mau.fi/util v0.9.9/go.mod h1:pqt4Vcrt+5gcH/CgrHZg11qSx+b34o6mknGzOEA6waY= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260602173933-595ceb0c22bf h1:zyTgHzoTL1rPeLn8B761tnUoNTHxfhy6DsQbHNzc7/Q= -go.mau.fi/whatsmeow v0.0.0-20260602173933-595ceb0c22bf/go.mod h1:9hto2r5yVE5yyNTRrZErKNSflGBKxIplUVXAD3EJFDE= +go.mau.fi/whatsmeow v0.0.0-20260603132417-6a7ac9915382 h1:j/iSrXBAN0bmQRJysLdnE0hMAqO2ttQTsovO/0nLar4= +go.mau.fi/whatsmeow v0.0.0-20260603132417-6a7ac9915382/go.mod h1:9hto2r5yVE5yyNTRrZErKNSflGBKxIplUVXAD3EJFDE= 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.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= From d9baddaae1f8df98288899038e852f08d2964ee7 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 4 Jun 2026 23:58:09 +0300 Subject: [PATCH 48/94] dependencies: update whatsmeow --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/chatinfo.go | 6 +++--- pkg/connector/directmedia.go | 6 ++---- pkg/connector/userinfo.go | 2 +- pkg/msgconv/wa-media.go | 4 ++-- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 4efd3b8..ae10fd5 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.9 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260603132417-6a7ac9915382 + go.mau.fi/whatsmeow v0.0.0-20260604205742-c6a4b703e48f golang.org/x/image v0.40.0 golang.org/x/net v0.55.0 golang.org/x/sync v0.20.0 diff --git a/go.sum b/go.sum index 764681f..29beb99 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.9 h1:ujDeXCo07HBor5oQLyO1tHklupmqVmPgasc53d7q/NE= go.mau.fi/util v0.9.9/go.mod h1:pqt4Vcrt+5gcH/CgrHZg11qSx+b34o6mknGzOEA6waY= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260603132417-6a7ac9915382 h1:j/iSrXBAN0bmQRJysLdnE0hMAqO2ttQTsovO/0nLar4= -go.mau.fi/whatsmeow v0.0.0-20260603132417-6a7ac9915382/go.mod h1:9hto2r5yVE5yyNTRrZErKNSflGBKxIplUVXAD3EJFDE= +go.mau.fi/whatsmeow v0.0.0-20260604205742-c6a4b703e48f h1:7JxeZa++EnM3z+94NL+gxnDKWLYyml4W7x2dMMYGUNQ= +go.mau.fi/whatsmeow v0.0.0-20260604205742-c6a4b703e48f/go.mod h1:9hto2r5yVE5yyNTRrZErKNSflGBKxIplUVXAD3EJFDE= 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.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 7c7dc8e..7ffbb14 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -457,7 +457,7 @@ func (wa *WhatsAppClient) makePortalAvatarFetcher(avatarID string, sender types. wrappedAvatar = &bridgev2.Avatar{ ID: networkid.AvatarID(avatar.ID), Get: func(ctx context.Context) ([]byte, error) { - return wa.Client.DownloadMediaWithPath(ctx, avatar.DirectPath, nil, nil, nil, 0, "", "") + return wa.Client.DownloadMediaWithOnlyPath(ctx, avatar.DirectPath) }, } } @@ -496,7 +496,7 @@ func (wa *WhatsAppClient) wrapNewsletterInfo(ctx context.Context, info *types.Ne if info.ThreadMeta.Picture != nil { avatar.ID = networkid.AvatarID(info.ThreadMeta.Picture.ID) avatar.Get = func(ctx context.Context) ([]byte, error) { - return wa.Client.DownloadMediaWithPath(ctx, info.ThreadMeta.Picture.DirectPath, nil, nil, nil, 0, "", "") + return wa.Client.DownloadMediaWithOnlyPath(ctx, info.ThreadMeta.Picture.DirectPath) } } else if info.ThreadMeta.Preview.ID != "" { avatar.ID = networkid.AvatarID(info.ThreadMeta.Preview.ID) @@ -507,7 +507,7 @@ func (wa *WhatsAppClient) wrapNewsletterInfo(ctx context.Context, info *types.Ne } else if meta.ThreadMeta.Picture == nil { return nil, fmt.Errorf("full res avatar info is missing") } - return wa.Client.DownloadMediaWithPath(ctx, meta.ThreadMeta.Picture.DirectPath, nil, nil, nil, 0, "", "") + return wa.Client.DownloadMediaWithOnlyPath(ctx, meta.ThreadMeta.Picture.DirectPath) } } else { avatar.ID = "remove" diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index 70289cd..89a54fc 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -131,9 +131,7 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars } return &mediaproxy.GetMediaResponseFile{ Callback: func(w *os.File) (*mediaproxy.FileMeta, error) { - return &mediaproxy.FileMeta{}, waClient.Client.DownloadMediaWithPathToFile( - ctx, cachedInfo.DirectPath, nil, nil, nil, 0, "", "", w, - ) + return &mediaproxy.FileMeta{}, waClient.Client.DownloadMediaWithOnlyPathToFile(ctx, cachedInfo.DirectPath, w) }, }, nil } @@ -227,7 +225,7 @@ func (wa *WhatsAppConnector) makeDirectMediaResponse( log.Trace().Msg("Retrying download after successful retry") err = waClient.Client.DownloadToFile(ctx, keys, f) } - if errors.Is(err, whatsmeow.ErrFileLengthMismatch) || errors.Is(err, whatsmeow.ErrInvalidMediaSHA256) { + if errors.Is(err, whatsmeow.ErrInvalidMediaSHA256) { zerolog.Ctx(ctx).Warn().Err(err).Msg("Mismatching media checksums in message. Ignoring because WhatsApp seems to ignore them too") } else if err != nil { return nil, err diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index aa84938..2b94047 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -357,7 +357,7 @@ func (wa *WhatsAppClient) fetchGhostAvatar(ctx context.Context, ghost *bridgev2. wrappedAvatar = &bridgev2.Avatar{ ID: networkid.AvatarID(avatar.ID), Get: func(ctx context.Context) ([]byte, error) { - return wa.Client.DownloadMediaWithPath(ctx, avatar.DirectPath, nil, nil, nil, 0, "", "") + return wa.Client.DownloadMediaWithOnlyPath(ctx, avatar.DirectPath) }, } } diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index 38fe9e2..34c529b 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -374,7 +374,7 @@ func (mc *MessageConverter) reuploadWhatsAppAttachment( var err error part.URL, part.File, err = intent.UploadMediaStream(ctx, roomID, -1, true, func(file io.Writer) (*bridgev2.FileStreamResult, error) { err := client.DownloadToFile(ctx, message, file.(*os.File)) - if errors.Is(err, whatsmeow.ErrFileLengthMismatch) || errors.Is(err, whatsmeow.ErrInvalidMediaSHA256) { + if errors.Is(err, whatsmeow.ErrInvalidMediaSHA256) { zerolog.Ctx(ctx).Warn().Err(err).Msg("Mismatching media checksums in message. Ignoring because WhatsApp seems to ignore them too") } else if err != nil { return nil, fmt.Errorf("%w: %w", bridgev2.ErrMediaDownloadFailed, err) @@ -395,7 +395,7 @@ func (mc *MessageConverter) reuploadWhatsAppAttachment( } } else { data, err := client.Download(ctx, message) - if errors.Is(err, whatsmeow.ErrFileLengthMismatch) || errors.Is(err, whatsmeow.ErrInvalidMediaSHA256) { + if errors.Is(err, whatsmeow.ErrInvalidMediaSHA256) { zerolog.Ctx(ctx).Warn().Err(err).Msg("Mismatching media checksums in message. Ignoring because WhatsApp seems to ignore them too") } else if err != nil { return fmt.Errorf("%w: %w", bridgev2.ErrMediaDownloadFailed, err) From 5e13abdc30419c73250d90f242f5c3ce54be7050 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 9 Jun 2026 12:25:24 +0300 Subject: [PATCH 49/94] dependencies: update --- go.mod | 20 ++++++++++---------- go.sum | 36 ++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index ae10fd5..f37b568 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module go.mau.fi/mautrix-whatsapp go 1.25.0 -toolchain go1.26.3 +toolchain go1.26.4 tool go.mau.fi/util/cmd/maubuild @@ -12,13 +12,13 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.9 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260604205742-c6a4b703e48f - golang.org/x/image v0.40.0 + go.mau.fi/whatsmeow v0.0.0-20260609091626-4e622162b959 + golang.org/x/image v0.42.0 golang.org/x/net v0.55.0 - golang.org/x/sync v0.20.0 + golang.org/x/sync v0.21.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.28.1-0.20260527103108-d00b9b09fcd5 + maunium.net/go/mautrix v0.28.1-0.20260609092200-dbbc3d0ed084 ) 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.44 // indirect + github.com/mattn/go-sqlite3 v1.14.45 // indirect github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/rs/xid v1.6.0 // indirect @@ -43,11 +43,11 @@ require ( github.com/yuin/goldmark v1.8.2 // indirect go.mau.fi/libsignal v0.2.2 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect - golang.org/x/crypto v0.52.0 // indirect - golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a // indirect + golang.org/x/crypto v0.53.0 // indirect + golang.org/x/exp v0.0.0-20260603202125-055de637280b // indirect golang.org/x/mod v0.36.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/text v0.37.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/text v0.38.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 29beb99..589125f 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ 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.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8= -github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= +github.com/mattn/go-sqlite3 v1.14.45 h1:6KA/spDguL3KV8rnybG7ezSaE4SeMR3KC9VbUoAQaIk= +github.com/mattn/go-sqlite3 v1.14.45/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 h1:WDsQxOJDy0N1VRAjXLpi8sCEZRSGarLWQevDxpTBRrM= github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -75,27 +75,27 @@ go.mau.fi/util v0.9.9 h1:ujDeXCo07HBor5oQLyO1tHklupmqVmPgasc53d7q/NE= go.mau.fi/util v0.9.9/go.mod h1:pqt4Vcrt+5gcH/CgrHZg11qSx+b34o6mknGzOEA6waY= go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260604205742-c6a4b703e48f h1:7JxeZa++EnM3z+94NL+gxnDKWLYyml4W7x2dMMYGUNQ= -go.mau.fi/whatsmeow v0.0.0-20260604205742-c6a4b703e48f/go.mod h1:9hto2r5yVE5yyNTRrZErKNSflGBKxIplUVXAD3EJFDE= +go.mau.fi/whatsmeow v0.0.0-20260609091626-4e622162b959 h1:5MpMyxG2lGLgnN0zKfD6fnDBvyGXoOlruLK34tV281w= +go.mau.fi/whatsmeow v0.0.0-20260609091626-4e622162b959/go.mod h1:9hto2r5yVE5yyNTRrZErKNSflGBKxIplUVXAD3EJFDE= 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.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= -golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= -golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw= -golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= -golang.org/x/image v0.40.0 h1:Tw4GyDXMo+daZN1znreBRC3VayR1aLFUyUEOLUdW1a8= -golang.org/x/image v0.40.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/exp v0.0.0-20260603202125-055de637280b h1:v1uXiEBHo8QA0LiGCo7UgHMzHT4Kdfpl2zmtH5vaP1Q= +golang.org/x/exp v0.0.0-20260603202125-055de637280b/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= +golang.org/x/image v0.42.0 h1:1gSs6ehNWXLbkHBIPcWztk3D/6aIA/8hauiAYtlodVY= +golang.org/x/image v0.42.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q= golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -107,5 +107,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.28.1-0.20260527103108-d00b9b09fcd5 h1:9bx9er6TJxQ2saQw63gwT0HZWg9/0Q30x8V3foKuNBo= -maunium.net/go/mautrix v0.28.1-0.20260527103108-d00b9b09fcd5/go.mod h1:/a9A7LGaqb9B3nho4tLd28n0EPcCdwpm2dxkxkLLgh0= +maunium.net/go/mautrix v0.28.1-0.20260609092200-dbbc3d0ed084 h1:eYPasZ1KwR+8p4nOmafIX4ZAu25lY4ouN99m64qg8iA= +maunium.net/go/mautrix v0.28.1-0.20260609092200-dbbc3d0ed084/go.mod h1:5rE5/8dcwa7V6BpJ28TDNP676GQHHiTkBtACPbwtdTE= From 1d68051dae9337a3466de6409b0c8ec86ab73807 Mon Sep 17 00:00:00 2001 From: Kishan Bagaria <1093313+KishanBagaria@users.noreply.github.com> Date: Tue, 9 Jun 2026 15:25:21 +0530 Subject: [PATCH 50/94] handlewhatsapp: fix HD media caption edits (#927) --- pkg/connector/events.go | 7 ++++++- pkg/connector/handlewhatsapp.go | 19 ++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 3e809b7..411c39e 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -182,12 +182,15 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por } var editedMsg *waE2E.Message var previouslyConvertedPart *bridgev2.ConvertedMessagePart + targetMessage := evt.GetTargetMessage() + cacheMessage := targetMessage if evt.isUndecryptableUpsertSubEvent { // TODO db metadata needs to be updated in this case to remove the error editedMsg = evt.Message + cacheMessage = evt.GetID() } else { editedMsg = evt.Message.GetProtocolMessage().GetEditedMessage() - previouslyConvertedPart = evt.wa.Main.GetMediaEditCache(portal, evt.GetTargetMessage()) + previouslyConvertedPart = evt.wa.Main.GetMediaEditCache(portal, targetMessage) meta := existing[0].Metadata.(*waid.MessageMetadata) if slices.Contains(meta.Edits, evt.Info.ID) { return nil, fmt.Errorf("%w: edit already handled", bridgev2.ErrIgnoringRemoteEvent) @@ -203,6 +206,8 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por evt.postHandle = func() { evt.wa.processFailedMedia(ctx, portal.PortalKey, evt.GetID(), cm, false) } + } else if len(cm.Parts) > 0 && cacheMessage != "" { + evt.wa.Main.AddMediaEditCache(portal, cacheMessage, cm.Parts[0]) } editPart := cm.Parts[0].ToEditPart(existing[0]) if evt.isUndecryptableUpsertSubEvent || evt.dontRenderEdited { diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 79871b3..e6437ec 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -363,23 +363,28 @@ 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() - associatedMessage := evt.Message.GetAssociatedChildMessage().GetMessage() + 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 + } wa.UserLogin.Log.Debug(). Str("message_id", evt.Info.ID). Str("parent_id", parentKey.GetID()). Stringer("assoc_type", assocType). Msg("Received HD replacement message, converting to edit") - protocolMsg := &waE2E.ProtocolMessage{ - Type: waE2E.ProtocolMessage_MESSAGE_EDIT.Enum(), - Key: parentKey, - EditedMessage: associatedMessage, - } evt.Message = &waE2E.Message{ ProtocolMessage: protocolMsg, } parsedMessageType = getMessageType(evt.Message) - dontRenderEdited = true } else if assocType == waE2E.MessageAssociation_MOTION_PHOTO { //evt.Message = evt.Message.GetAssociatedChildMessage().GetMessage() wa.UserLogin.Log.Debug(). From 3cd66ef74a198545a0ba64c869c2e28833d75e66 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 13 Jun 2026 18:37:41 +0300 Subject: [PATCH 51/94] legacyprovision: remove last parts --- cmd/mautrix-whatsapp/legacyprovision.go | 153 ------------------------ cmd/mautrix-whatsapp/main.go | 9 -- 2 files changed, 162 deletions(-) delete mode 100644 cmd/mautrix-whatsapp/legacyprovision.go diff --git a/cmd/mautrix-whatsapp/legacyprovision.go b/cmd/mautrix-whatsapp/legacyprovision.go deleted file mode 100644 index f0527b2..0000000 --- a/cmd/mautrix-whatsapp/legacyprovision.go +++ /dev/null @@ -1,153 +0,0 @@ -package main - -import ( - "net/http" - "strings" - - "github.com/rs/zerolog/hlog" - "go.mau.fi/util/exhttp" - "go.mau.fi/whatsmeow" - "go.mau.fi/whatsmeow/appstate" - "go.mau.fi/whatsmeow/types" - "maunium.net/go/mautrix" - "maunium.net/go/mautrix/bridgev2" - "maunium.net/go/mautrix/bridgev2/matrix" - "maunium.net/go/mautrix/id" - - "go.mau.fi/mautrix-whatsapp/pkg/connector" - "go.mau.fi/mautrix-whatsapp/pkg/waid" -) - -type OtherUserInfo struct { - MXID id.UserID `json:"mxid"` - JID types.JID `json:"jid"` - Name string `json:"displayname"` - Avatar id.ContentURIString `json:"avatar_url"` -} - -type PortalInfo struct { - RoomID id.RoomID `json:"room_id"` - OtherUser *OtherUserInfo `json:"other_user,omitempty"` - GroupInfo *types.GroupInfo `json:"group_info,omitempty"` - JustCreated bool `json:"just_created"` -} - -type Error struct { - Success bool `json:"success"` - Error string `json:"error"` - ErrCode string `json:"errcode"` -} - -func legacyProvContacts(w http.ResponseWriter, r *http.Request) { - userLogin := m.Matrix.Provisioning.GetLoginForRequest(w, r) - if userLogin == nil { - return - } - if contacts, err := userLogin.Client.(*connector.WhatsAppClient).GetStore().Contacts.GetAllContacts(r.Context()); err != nil { - hlog.FromRequest(r).Err(err).Msg("Failed to fetch all contacts") - exhttp.WriteJSONResponse(w, http.StatusInternalServerError, Error{ - Error: "Internal server error while fetching contact list", - ErrCode: "failed to get contacts", - }) - } else { - augmentedContacts := map[types.JID]any{} - for jid, contact := range contacts { - var avatarURL id.ContentURIString - if puppet, _ := m.Bridge.GetExistingGhostByID(r.Context(), waid.MakeUserID(jid)); puppet != nil { - avatarURL = puppet.AvatarMXC - } - augmentedContacts[jid] = map[string]interface{}{ - "Found": contact.Found, - "FirstName": contact.FirstName, - "FullName": contact.FullName, - "PushName": contact.PushName, - "BusinessName": contact.BusinessName, - "AvatarURL": avatarURL, - } - } - exhttp.WriteJSONResponse(w, http.StatusOK, augmentedContacts) - } -} - -func legacyProvResolveIdentifier(w http.ResponseWriter, r *http.Request) { - number := r.PathValue("number") - userLogin := m.Matrix.Provisioning.GetLoginForRequest(w, r) - if userLogin == nil { - return - } - startChat := strings.Contains(r.URL.Path, "/v1/pm/") - resp, err := userLogin.Client.(*connector.WhatsAppClient).ResolveIdentifier(r.Context(), number, startChat) - if err != nil { - hlog.FromRequest(r).Warn().Err(err).Str("identifier", number).Msg("Failed to resolve identifier") - matrix.RespondWithError(w, err, "Internal error resolving identifier") - return - } - var portal *bridgev2.Portal - if startChat { - portal, err = m.Bridge.GetPortalByKey(r.Context(), resp.Chat.PortalKey) - if err != nil { - hlog.FromRequest(r).Warn().Err(err).Stringer("portal_key", resp.Chat.PortalKey).Msg("Failed to get portal by key") - matrix.RespondWithError(w, err, "Internal error getting portal by key") - return - } - err = portal.CreateMatrixRoom(r.Context(), userLogin, nil) - if err != nil { - hlog.FromRequest(r).Warn().Err(err).Stringer("portal_key", resp.Chat.PortalKey).Msg("Failed to create matrix room for portal") - matrix.RespondWithError(w, err, "Internal error creating matrix room for portal") - return - } - } else { - portal, _ = m.Bridge.GetExistingPortalByKey(r.Context(), resp.Chat.PortalKey) - } - var roomID id.RoomID - if portal != nil { - roomID = portal.MXID - } - exhttp.WriteJSONResponse(w, http.StatusOK, PortalInfo{ - RoomID: roomID, - OtherUser: &OtherUserInfo{ - JID: waid.ParseUserID(resp.UserID), - MXID: resp.Ghost.Intent.GetMXID(), - Name: resp.Ghost.Name, - Avatar: resp.Ghost.AvatarMXC, - }, - }) -} - -func provAppStateDebug(w http.ResponseWriter, r *http.Request) { - userLogin := m.Matrix.Provisioning.GetLoginForRequest(w, r) - if userLogin == nil { - return - } - client := userLogin.Client.(*connector.WhatsAppClient) - if client.Client == nil { - mautrix.MNotFound.WithMessage("WhatsApp client not connected").Write(w) - return - } - client.Client.AppStateDebugLogs = true - err := client.Client.FetchAppState(r.Context(), appstate.WAPatchName(r.PathValue("patch")), r.URL.Query().Get("full") == "1", false) - client.Client.AppStateDebugLogs = false - if err != nil { - mautrix.MUnknown.WithMessage("Failed to fetch app state: %v", err).Write(w) - } else { - exhttp.WriteEmptyJSONResponse(w, http.StatusOK) - } -} - -func provRecoverAppStateDebug(w http.ResponseWriter, r *http.Request) { - userLogin := m.Matrix.Provisioning.GetLoginForRequest(w, r) - if userLogin == nil { - return - } - client := userLogin.Client.(*connector.WhatsAppClient) - if client.Client == nil { - mautrix.MNotFound.WithMessage("WhatsApp client not connected").Write(w) - return - } - resp, err := client.Client.SendPeerMessage(r.Context(), whatsmeow.BuildAppStateRecoveryRequest(appstate.WAPatchName(r.PathValue("patch")))) - if err != nil { - mautrix.MUnknown.WithMessage("Failed to send app state recovery request: %v", err).Write(w) - } else { - exhttp.WriteJSONResponse(w, http.StatusOK, resp) - } -} diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index cb1632c..1da86bf 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -24,15 +24,6 @@ var m = mxmain.BridgeMain{ } func main() { - m.PostStart = func() { - if m.Matrix.Provisioning != nil { - m.Matrix.Provisioning.Router.HandleFunc("GET /v1/contacts", legacyProvContacts) - m.Matrix.Provisioning.Router.HandleFunc("GET /v1/resolve_identifier/{number}", legacyProvResolveIdentifier) - m.Matrix.Provisioning.Router.HandleFunc("POST /v1/pm/{number}", legacyProvResolveIdentifier) - m.Matrix.Provisioning.Router.HandleFunc("POST /v1/debug/appstate/{patch}", provAppStateDebug) - m.Matrix.Provisioning.Router.HandleFunc("POST /v1/debug/recover-appstate/{patch}", provRecoverAppStateDebug) - } - } m.InitVersion(Tag, Commit, BuildTime) m.Run() } From c97a1c4b4dcc994464745cc7fa232082c28912b5 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 13 Jun 2026 21:32:17 +0300 Subject: [PATCH 52/94] mclient: add more hooks --- pkg/connector/handlematrix.go | 55 ++++++++++++++++++++++----------- pkg/connector/handlewhatsapp.go | 3 +- pkg/connector/mclient.go | 11 +++++-- 3 files changed, 47 insertions(+), 22 deletions(-) diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index b0963ec..299449f 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -48,11 +48,12 @@ var ( _ bridgev2.DeleteChatHandlingNetworkAPI = (*WhatsAppClient)(nil) ) -func (wa *WhatsAppClient) HandleMatrixPollStart(ctx context.Context, msg *bridgev2.MatrixPollStart) (*bridgev2.MatrixMessageResponse, error) { +func (wa *WhatsAppClient) HandleMatrixPollStart(ctx context.Context, msg *bridgev2.MatrixPollStart) (result *bridgev2.MatrixMessageResponse, retErr error) { waMsg, optionMap, err := wa.Main.MsgConv.PollStartToWhatsApp(ctx, msg.Content, msg.ReplyTo, msg.Portal) if err != nil { return nil, fmt.Errorf("failed to convert poll vote: %w", err) } + defer wa.mcTrack(msg, time.Now(), &retErr) resp, err := wa.handleConvertedMatrixMessage(ctx, &msg.MatrixMessage, waMsg, nil) if err != nil { return nil, err @@ -67,19 +68,21 @@ func (wa *WhatsAppClient) HandleMatrixPollStart(ctx context.Context, msg *bridge return resp, nil } -func (wa *WhatsAppClient) HandleMatrixPollVote(ctx context.Context, msg *bridgev2.MatrixPollVote) (*bridgev2.MatrixMessageResponse, error) { +func (wa *WhatsAppClient) HandleMatrixPollVote(ctx context.Context, msg *bridgev2.MatrixPollVote) (result *bridgev2.MatrixMessageResponse, retErr error) { waMsg, err := wa.Main.MsgConv.PollVoteToWhatsApp(ctx, wa.Client, msg.Content, msg.VoteTo) if err != nil { return nil, fmt.Errorf("failed to convert poll vote: %w", err) } + defer wa.mcTrack(msg, time.Now(), &retErr) return wa.handleConvertedMatrixMessage(ctx, &msg.MatrixMessage, waMsg, nil) } -func (wa *WhatsAppClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage) (*bridgev2.MatrixMessageResponse, error) { +func (wa *WhatsAppClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage) (result *bridgev2.MatrixMessageResponse, retErr error) { waMsg, req, err := wa.Main.MsgConv.ToWhatsApp(ctx, wa.Client, msg.Event, msg.Content, msg.ReplyTo, msg.ThreadRoot, msg.Portal) if err != nil { return nil, fmt.Errorf("failed to convert message: %w", err) } + defer wa.mcTrack(msg, time.Now(), &retErr) return wa.handleConvertedMatrixMessage(ctx, msg, waMsg, req) } @@ -154,7 +157,7 @@ func (wa *WhatsAppClient) PreHandleMatrixReaction(_ context.Context, msg *bridge }, nil } -func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev2.MatrixReaction) (*database.Reaction, error) { +func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev2.MatrixReaction) (result *database.Reaction, retErr error) { messageID, err := waid.ParseMessageID(msg.TargetMessage.ID) if err != nil { return nil, fmt.Errorf("failed to parse target message ID: %w", err) @@ -171,6 +174,7 @@ func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev SenderTimestampMS: proto.Int64(msg.Event.Timestamp), }, } + defer wa.mcTrack(msg, time.Now(), &retErr) var req whatsmeow.SendRequestExtra if msg.Portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup { reactionMsg.EncReactionMessage, err = wa.Client.EncryptReaction(ctx, msgconv.MessageIDToInfo(wa.Client, messageID), reactionMsg.ReactionMessage) @@ -192,7 +196,7 @@ func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev }, err } -func (wa *WhatsAppClient) HandleMatrixReactionRemove(ctx context.Context, msg *bridgev2.MatrixReactionRemove) error { +func (wa *WhatsAppClient) HandleMatrixReactionRemove(ctx context.Context, msg *bridgev2.MatrixReactionRemove) (retErr error) { messageID, err := waid.ParseMessageID(msg.TargetReaction.MessageID) if err != nil { return fmt.Errorf("failed to parse target message ID: %w", err) @@ -216,12 +220,13 @@ func (wa *WhatsAppClient) HandleMatrixReactionRemove(ctx context.Context, msg *b extra.ID = types.MessageID(msg.InputTransactionID) } + defer wa.mcTrack(msg, time.Now(), &retErr) resp, err := wa.Client.SendMessage(ctx, portalJID, reactionMsg, extra) zerolog.Ctx(ctx).Trace().Any("response", resp).Msg("WhatsApp reaction response") return err } -func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.MatrixEdit) error { +func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.MatrixEdit) (retErr error) { log := zerolog.Ctx(ctx) var editID types.MessageID @@ -245,6 +250,8 @@ func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.M if err != nil { return fmt.Errorf("failed to convert message: %w", err) } + defer wa.mcTrack(edit, time.Now(), &retErr) + convertedEdit := wa.Client.BuildEdit(messageID.Chat, messageID.ID, waMsg) if edit.OrigSender == nil { convertedEdit.EditedMessage.Message.ProtocolMessage.TimestampMS = proto.Int64(edit.Event.Timestamp) @@ -259,7 +266,7 @@ func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.M return err } -func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *bridgev2.MatrixMessageRemove) error { +func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *bridgev2.MatrixMessageRemove) (retErr error) { log := zerolog.Ctx(ctx) messageID, err := waid.ParseMessageID(msg.TargetMessage.ID) if err != nil { @@ -271,6 +278,7 @@ func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *br return fmt.Errorf("failed to parse portal ID: %w", err) } + defer wa.mcTrack(msg, time.Now(), &retErr) revokeMessage := wa.Client.BuildRevoke(messageID.Chat, messageID.Sender, messageID.ID) extra := whatsmeow.SendRequestExtra{} @@ -283,7 +291,7 @@ func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *br return err } -func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt *bridgev2.MatrixReadReceipt) error { +func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt *bridgev2.MatrixReadReceipt) (retErr error) { if !receipt.ReadUpTo.After(receipt.LastRead) { return nil } @@ -322,6 +330,7 @@ func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt * } messagesToRead[key] = append(messagesToRead[key], parsed.ID) } + defer wa.mcTrack(receipt, time.Now(), &retErr) for messageSender, ids := range messagesToRead { err = wa.Client.MarkRead(ctx, ids, receipt.Receipt.Timestamp, portalJID, messageSender) if err != nil { @@ -331,7 +340,7 @@ func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt * return err } -func (wa *WhatsAppClient) HandleMatrixTyping(ctx context.Context, msg *bridgev2.MatrixTyping) error { +func (wa *WhatsAppClient) HandleMatrixTyping(ctx context.Context, msg *bridgev2.MatrixTyping) (retErr error) { portalJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return err @@ -352,6 +361,7 @@ func (wa *WhatsAppClient) HandleMatrixTyping(ctx context.Context, msg *bridgev2. return nil } + defer wa.mcTrack(msg, time.Now(), &retErr) if wa.Main.Config.SendPresenceOnTyping { err = wa.updatePresence(ctx, types.PresenceAvailable) if err != nil { @@ -363,7 +373,7 @@ func (wa *WhatsAppClient) HandleMatrixTyping(ctx context.Context, msg *bridgev2. var errUnsupportedDisappearingTimer = bridgev2.WrapErrorInStatus(errors.New("invalid value for disappearing timer")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true) -func (wa *WhatsAppClient) HandleMatrixDisappearingTimer(ctx context.Context, msg *bridgev2.MatrixDisappearingTimer) (bool, error) { +func (wa *WhatsAppClient) HandleMatrixDisappearingTimer(ctx context.Context, msg *bridgev2.MatrixDisappearingTimer) (ok bool, retErr error) { portalJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return false, err @@ -375,6 +385,7 @@ func (wa *WhatsAppClient) HandleMatrixDisappearingTimer(ctx context.Context, msg return false, fmt.Errorf("%w (%s)", errUnsupportedDisappearingTimer, msg.Content.Timer.Duration) } + defer wa.mcTrack(msg, time.Now(), &retErr) settingTS := time.UnixMilli(msg.Event.Timestamp) err = wa.Client.SetDisappearingTimer(ctx, portalJID, msg.Content.Timer.Duration, settingTS) if err != nil { @@ -391,7 +402,7 @@ func (wa *WhatsAppClient) HandleMatrixDisappearingTimer(ctx context.Context, msg return true, nil } -func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2.MatrixMembershipChange) (*bridgev2.MatrixMembershipResult, error) { +func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2.MatrixMembershipChange) (result *bridgev2.MatrixMembershipResult, retErr error) { if msg.Type.IsSelf && msg.OrigSender != nil { return nil, nil } @@ -435,6 +446,7 @@ func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridg return nil, fmt.Errorf("cannot get target intent: unknown type: %T", target) } + defer wa.mcTrack(msg, time.Now(), &retErr) resp, err := wa.Client.UpdateGroupParticipants(ctx, portalJID, changes, action) if err != nil { return nil, err @@ -450,7 +462,7 @@ func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridg return &bridgev2.MatrixMembershipResult{RedirectTo: waid.MakeUserID(resp[0].JID)}, nil } -func (wa *WhatsAppClient) HandleMatrixRoomName(ctx context.Context, msg *bridgev2.MatrixRoomName) (bool, error) { +func (wa *WhatsAppClient) HandleMatrixRoomName(ctx context.Context, msg *bridgev2.MatrixRoomName) (ok bool, retErr error) { portalJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return false, err @@ -460,6 +472,7 @@ func (wa *WhatsAppClient) HandleMatrixRoomName(ctx context.Context, msg *bridgev return false, fmt.Errorf("cannot set room name for DM") } + defer wa.mcTrack(msg, time.Now(), &retErr) err = wa.Client.SetGroupName(ctx, portalJID, msg.Content.Name) if err != nil { return false, err @@ -471,7 +484,7 @@ func (wa *WhatsAppClient) HandleMatrixRoomName(ctx context.Context, msg *bridgev return true, nil } -func (wa *WhatsAppClient) HandleMatrixRoomTopic(ctx context.Context, msg *bridgev2.MatrixRoomTopic) (bool, error) { +func (wa *WhatsAppClient) HandleMatrixRoomTopic(ctx context.Context, msg *bridgev2.MatrixRoomTopic) (ok bool, retErr error) { portalJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return false, err @@ -481,6 +494,7 @@ func (wa *WhatsAppClient) HandleMatrixRoomTopic(ctx context.Context, msg *bridge return false, fmt.Errorf("cannot set room topic for DM") } + defer wa.mcTrack(msg, time.Now(), &retErr) newID := wa.Client.GenerateMessageID() oldID := msg.Portal.Metadata.(*waid.PortalMetadata).TopicID err = wa.Client.SetGroupTopic(ctx, portalJID, oldID, newID, msg.Content.Topic) @@ -495,7 +509,7 @@ func (wa *WhatsAppClient) HandleMatrixRoomTopic(ctx context.Context, msg *bridge return true, nil } -func (wa *WhatsAppClient) HandleMatrixRoomAvatar(ctx context.Context, msg *bridgev2.MatrixRoomAvatar) (bool, error) { +func (wa *WhatsAppClient) HandleMatrixRoomAvatar(ctx context.Context, msg *bridgev2.MatrixRoomAvatar) (ok bool, retErr error) { portalJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return false, err @@ -518,6 +532,7 @@ func (wa *WhatsAppClient) HandleMatrixRoomAvatar(ctx context.Context, msg *bridg } } + defer wa.mcTrack(msg, time.Now(), &retErr) avatarID, err := wa.Client.SetGroupPhoto(ctx, portalJID, data) if err != nil { return false, err @@ -585,7 +600,7 @@ func convertRoomAvatar(data []byte) ([]byte, error) { return buf.Bytes(), nil } -func (wa *WhatsAppClient) HandleMute(ctx context.Context, msg *bridgev2.MatrixMute) error { +func (wa *WhatsAppClient) HandleMute(ctx context.Context, msg *bridgev2.MatrixMute) (retErr error) { chatJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return err @@ -596,14 +611,16 @@ func (wa *WhatsAppClient) HandleMute(ctx context.Context, msg *bridgev2.MatrixMu if !muted || mutedUntil == event.MutedForever { muteTS = nil } + defer wa.mcTrack(msg, time.Now(), &retErr) return wa.Client.SendAppState(ctx, appstate.BuildMuteAbs(chatJID, muted, muteTS)) } -func (wa *WhatsAppClient) HandleRoomTag(ctx context.Context, msg *bridgev2.MatrixRoomTag) error { +func (wa *WhatsAppClient) HandleRoomTag(ctx context.Context, msg *bridgev2.MatrixRoomTag) (retErr error) { chatJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return err } + defer wa.mcTrack(msg, time.Now(), &retErr) _, isFavorite := msg.Content.Tags[event.RoomTagFavourite] return wa.Client.SendAppState(ctx, appstate.BuildPin(chatJID, isFavorite)) } @@ -635,7 +652,7 @@ func (wa *WhatsAppClient) getLastMessageInfo(ctx context.Context, chatJID types. return lastTS, lastKey, nil } -func (wa *WhatsAppClient) HandleMarkedUnread(ctx context.Context, msg *bridgev2.MatrixMarkedUnread) error { +func (wa *WhatsAppClient) HandleMarkedUnread(ctx context.Context, msg *bridgev2.MatrixMarkedUnread) (retErr error) { chatJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return err @@ -644,10 +661,11 @@ func (wa *WhatsAppClient) HandleMarkedUnread(ctx context.Context, msg *bridgev2. if err != nil { return err } + defer wa.mcTrack(msg, time.Now(), &retErr) return wa.Client.SendAppState(ctx, appstate.BuildMarkChatAsRead(chatJID, msg.Content.Unread, lastTS, lastKey)) } -func (wa *WhatsAppClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridgev2.MatrixDeleteChat) error { +func (wa *WhatsAppClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridgev2.MatrixDeleteChat) (retErr error) { chatJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return err @@ -668,5 +686,6 @@ func (wa *WhatsAppClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridg if err != nil { return err } + defer wa.mcTrack(msg, time.Now(), &retErr) return wa.Client.SendAppState(ctx, appstate.BuildDeleteChat(chatJID, lastTS, lastKey, true)) } diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index e6437ec..30a9cd8 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -28,7 +28,6 @@ import ( "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/appstate" "go.mau.fi/whatsmeow/proto/waE2E" - "go.mau.fi/whatsmeow/store" "go.mau.fi/whatsmeow/types" "go.mau.fi/whatsmeow/types/events" "maunium.net/go/mautrix/bridgev2" @@ -77,6 +76,7 @@ func init() { func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { log := wa.UserLogin.Log ctx := log.WithContext(wa.Main.Bridge.BackgroundCtx) + wa.MC.OnWhatsAppEvent(rawEvt) success = true switch evt := rawEvt.(type) { @@ -168,7 +168,6 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { }() go wa.syncRemoteProfile(ctx, nil) } - wa.MC.OnConnect(store.GetWAVersion()[2], wa.Device.Platform) case *events.OfflineSyncPreview: log.Info(). Int("message_count", evt.Messages). diff --git a/pkg/connector/mclient.go b/pkg/connector/mclient.go index 0930617..f85545a 100644 --- a/pkg/connector/mclient.go +++ b/pkg/connector/mclient.go @@ -21,6 +21,7 @@ import ( "encoding/json" "time" + "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" waBinary "go.mau.fi/whatsmeow/binary" "go.mau.fi/whatsmeow/types" @@ -39,14 +40,16 @@ func (wa *WhatsAppClient) initMC() { } type mClient = interface { - OnConnect(version uint32, platform string) + OnMatrixEvent(any, time.Duration, error) + OnWhatsAppEvent(any) } type noopMC struct{} var noopMCInstance mClient = &noopMC{} -func (n *noopMC) OnConnect(version uint32, platform string) {} +func (n *noopMC) OnMatrixEvent(any, time.Duration, error) {} +func (n *noopMC) OnWhatsAppEvent(any) {} type mWAClient = interface { MSend(data []byte) @@ -79,3 +82,7 @@ func (wa *WhatsAppClient) MSave(s json.RawMessage) { wa.UserLogin.Log.Err(err).Msg("Failed to save MC data") } } + +func (wa *WhatsAppClient) mcTrack(evt any, start time.Time, err *error) { + wa.MC.OnMatrixEvent(evt, time.Since(start), ptr.Val(err)) +} From f597fd91f4e53532ccb325f0033565dd7193a985 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 16 Jun 2026 15:13:03 +0300 Subject: [PATCH 53/94] Bump version to v26.06 --- CHANGELOG.md | 8 ++++++++ cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c0314e..560e2b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# v26.06 + +* Added placeholder for group message history share notices. +* Fixed community spaces not being bridged properly in some cases. +* Fixed handling edits of HD media. +* Fixed duplicate message when a message from another own device is initially + undecryptable and later gets resolved. + # v26.05 * Added support for importing sticker packs from WhatsApp. diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 1da86bf..07baaec 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.05", + Version: "26.06", SemCalVer: true, Connector: &connector.WhatsAppConnector{}, } diff --git a/go.mod b/go.mod index f37b568..85f5c6c 100644 --- a/go.mod +++ b/go.mod @@ -10,21 +10,21 @@ 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.9.9 - go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260609091626-4e622162b959 + go.mau.fi/util v0.9.10 + go.mau.fi/webp v0.3.0 + go.mau.fi/whatsmeow v0.0.0-20260616120636-eaa388b4e537 golang.org/x/image v0.42.0 - golang.org/x/net v0.55.0 + golang.org/x/net v0.56.0 golang.org/x/sync v0.21.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.28.1-0.20260609092200-dbbc3d0ed084 + maunium.net/go/mautrix v0.28.1 ) require ( filippo.io/edwards25519 v1.2.0 // indirect github.com/beeper/argo-go v1.1.2 // indirect - github.com/coder/websocket v1.8.14 // indirect + github.com/coder/websocket v1.8.15 // indirect github.com/coreos/go-systemd/v22 v22.7.0 // indirect github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect github.com/google/uuid v1.6.0 // indirect @@ -44,8 +44,8 @@ require ( go.mau.fi/libsignal v0.2.2 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect golang.org/x/crypto v0.53.0 // indirect - golang.org/x/exp v0.0.0-20260603202125-055de637280b // indirect - golang.org/x/mod v0.36.0 // indirect + golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect + golang.org/x/mod v0.37.0 // indirect golang.org/x/sys v0.46.0 // indirect golang.org/x/text v0.38.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index 589125f..e0a405c 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNg github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/beeper/argo-go v1.1.2 h1:UQI2G8F+NLfGTOmTUI0254pGKx/HUU/etbUGTJv91Fs= github.com/beeper/argo-go v1.1.2/go.mod h1:M+LJAnyowKVQ6Rdj6XYGEn+qcVFkb3R/MUpqkGR0hM4= -github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= -github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA= +github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= 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= @@ -71,24 +71,24 @@ github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= github.com/yuin/goldmark v1.8.2/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.9.9 h1:ujDeXCo07HBor5oQLyO1tHklupmqVmPgasc53d7q/NE= -go.mau.fi/util v0.9.9/go.mod h1:pqt4Vcrt+5gcH/CgrHZg11qSx+b34o6mknGzOEA6waY= -go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg= -go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q= -go.mau.fi/whatsmeow v0.0.0-20260609091626-4e622162b959 h1:5MpMyxG2lGLgnN0zKfD6fnDBvyGXoOlruLK34tV281w= -go.mau.fi/whatsmeow v0.0.0-20260609091626-4e622162b959/go.mod h1:9hto2r5yVE5yyNTRrZErKNSflGBKxIplUVXAD3EJFDE= +go.mau.fi/util v0.9.10 h1:wzvz5iDHyqDXB8vgisD4d3SzucLXNM3iNY+1O1RoHtg= +go.mau.fi/util v0.9.10/go.mod h1:YQOxySn+ZE3qSYqNxvyX7Yi3suA8YK17PS6QqBREW7A= +go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= +go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= +go.mau.fi/whatsmeow v0.0.0-20260616120636-eaa388b4e537 h1:LhxtFShA1NXJWz4yNygwtl3Eycp9XsfMqJeDdcLyKmk= +go.mau.fi/whatsmeow v0.0.0-20260616120636-eaa388b4e537/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= -golang.org/x/exp v0.0.0-20260603202125-055de637280b h1:v1uXiEBHo8QA0LiGCo7UgHMzHT4Kdfpl2zmtH5vaP1Q= -golang.org/x/exp v0.0.0-20260603202125-055de637280b/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= +golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M= +golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY= golang.org/x/image v0.42.0 h1:1gSs6ehNWXLbkHBIPcWztk3D/6aIA/8hauiAYtlodVY= golang.org/x/image v0.42.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q= -golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -107,5 +107,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.28.1-0.20260609092200-dbbc3d0ed084 h1:eYPasZ1KwR+8p4nOmafIX4ZAu25lY4ouN99m64qg8iA= -maunium.net/go/mautrix v0.28.1-0.20260609092200-dbbc3d0ed084/go.mod h1:5rE5/8dcwa7V6BpJ28TDNP676GQHHiTkBtACPbwtdTE= +maunium.net/go/mautrix v0.28.1 h1:Hic3oDMPbLbQu1fhboTRAKZcORMjzzkjxsa+SGk60b0= +maunium.net/go/mautrix v0.28.1/go.mod h1:mWXQNmOlrq4VTDU9f1HO03BSIswdUIyyY4wUKHqwzzY= From 879fc7cb41b0adb7ce986ee50939554c36bfa6a7 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Wed, 17 Jun 2026 14:15:56 +0100 Subject: [PATCH 54/94] backfill: skip groups that 404 on info (#929) --- pkg/connector/backfill.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 0e6de76..ee92ac2 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -400,8 +400,8 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { return } wrappedInfo, err := wa.getChatInfo(ctx, conv.ChatJID, conv, true) - if errors.Is(err, whatsmeow.ErrNotInGroup) { - log.Debug().Stringer("chat_jid", conv.ChatJID). + if errors.Is(err, whatsmeow.ErrNotInGroup) || errors.Is(err, whatsmeow.ErrGroupNotFound) { + log.Debug().Err(err).Stringer("chat_jid", conv.ChatJID). Msg("Skipping creating room because the user is not a participant") //err = wa.Main.DB.Message.DeleteAllInChat(ctx, wa.UserLogin.ID, conv.ChatJID) //if err != nil { From d90c7d94d128466195945ef1a8990be8e3e7759d Mon Sep 17 00:00:00 2001 From: Kishan Bagaria <1093313+KishanBagaria@users.noreply.github.com> Date: Fri, 19 Jun 2026 03:53:06 +0900 Subject: [PATCH 55/94] msgconv/wa-media: pluralize album media counts (#933) --- pkg/msgconv/wa-media.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index 34c529b..9964800 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -27,6 +27,7 @@ import ( "strings" "github.com/rs/zerolog" + "go.mau.fi/util/exfmt" "go.mau.fi/util/exmime" "go.mau.fi/util/exslices" "go.mau.fi/whatsmeow" @@ -129,10 +130,10 @@ func getMediaIDVersion(msg MediaMessage) []byte { func (mc *MessageConverter) convertAlbumMessage(ctx context.Context, msg *waE2E.AlbumMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { parts := make([]string, 0, 2) if msg.GetExpectedImageCount() > 0 { - parts = append(parts, fmt.Sprintf("%d images", msg.GetExpectedImageCount())) + parts = append(parts, exfmt.Pluralizable("image")(int(msg.GetExpectedImageCount()))) } if msg.GetExpectedVideoCount() > 0 { - parts = append(parts, fmt.Sprintf("%d videos", msg.GetExpectedVideoCount())) + parts = append(parts, exfmt.Pluralizable("video")(int(msg.GetExpectedVideoCount()))) } var partDesc string if len(parts) > 0 { From 41ce80f58393200e9249d29e700c778302ca2a69 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 22 Jun 2026 15:46:21 +0300 Subject: [PATCH 56/94] 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 85f5c6c..791f3e7 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.10 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260616120636-eaa388b4e537 + go.mau.fi/whatsmeow v0.0.0-20260622124518-e94c58afd43b golang.org/x/image v0.42.0 golang.org/x/net v0.56.0 golang.org/x/sync v0.21.0 diff --git a/go.sum b/go.sum index e0a405c..0112e10 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.10 h1:wzvz5iDHyqDXB8vgisD4d3SzucLXNM3iNY+1O1RoHtg= go.mau.fi/util v0.9.10/go.mod h1:YQOxySn+ZE3qSYqNxvyX7Yi3suA8YK17PS6QqBREW7A= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260616120636-eaa388b4e537 h1:LhxtFShA1NXJWz4yNygwtl3Eycp9XsfMqJeDdcLyKmk= -go.mau.fi/whatsmeow v0.0.0-20260616120636-eaa388b4e537/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U= +go.mau.fi/whatsmeow v0.0.0-20260622124518-e94c58afd43b h1:SeXzfZd3zFyHg+JEDOb5NZqMJRCRK5NXtXy+qzDJGSs= +go.mau.fi/whatsmeow v0.0.0-20260622124518-e94c58afd43b/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= From 80b2c16b3d35aace6af59dbe89413046350c6687 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 22 Jun 2026 15:46:41 +0300 Subject: [PATCH 57/94] userinfo: log last sync time when enqueuing resync --- pkg/connector/userinfo.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 2b94047..e827955 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -29,7 +29,8 @@ var ResyncLoopInterval = 4 * time.Hour var ResyncJitterSeconds = 3600 func (wa *WhatsAppClient) EnqueueGhostResync(ghost *bridgev2.Ghost) { - if ghost.Metadata.(*waid.GhostMetadata).LastSync.Add(ResyncMinInterval).After(time.Now()) { + lastSync := ghost.Metadata.(*waid.GhostMetadata).LastSync.Time + if lastSync.Add(ResyncMinInterval).After(time.Now()) { return } wa.resyncQueueLock.Lock() @@ -43,6 +44,7 @@ func (wa *WhatsAppClient) EnqueueGhostResync(ghost *bridgev2.Ghost) { wa.UserLogin.Log.Debug(). Stringer("jid", jid). Str("next_resync_in", nextResyncIn). + Time("last_ghost_resync", lastSync). Msg("Enqueued resync for ghost") } wa.resyncQueueLock.Unlock() @@ -50,7 +52,8 @@ func (wa *WhatsAppClient) EnqueueGhostResync(ghost *bridgev2.Ghost) { func (wa *WhatsAppClient) EnqueuePortalResync(portal *bridgev2.Portal, allowDM bool) { jid, _ := waid.ParsePortalID(portal.ID) - if portal.Metadata.(*waid.PortalMetadata).LastSync.Add(ResyncMinInterval).After(time.Now()) { + lastSync := portal.Metadata.(*waid.PortalMetadata).LastSync.Time + if lastSync.Add(ResyncMinInterval).After(time.Now()) { return } else if !allowDM && jid.Server != types.GroupServer { return @@ -61,6 +64,7 @@ func (wa *WhatsAppClient) EnqueuePortalResync(portal *bridgev2.Portal, allowDM b wa.UserLogin.Log.Debug(). Stringer("jid", jid). Stringer("next_resync_in", time.Until(wa.nextResync)). + Time("last_portal_resync", lastSync). Msg("Enqueued resync for portal") } wa.resyncQueueLock.Unlock() From 0c7f5233c8b4a030e396315b9f5d6cc2d598901c Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 22 Jun 2026 15:48:40 +0300 Subject: [PATCH 58/94] handlewhatsapp: send analytics for timelock event --- pkg/connector/handlewhatsapp.go | 14 ++++++++++++++ pkg/connector/phoneping.go | 7 +++++++ pkg/waid/dbmeta.go | 3 ++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 30a9cd8..2b9b145 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -126,6 +126,20 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { success = wa.handleWANewsletterLeave(evt) case *events.Picture: success = wa.handleWAPictureUpdate(ctx, evt) + case *events.NotifyAccountReachoutTimelock: + wa.UserLogin.TrackAnalytics("WhatsApp Account Reachout Timelock", map[string]any{ + "enforcement_type": evt.EnforcementType, + "is_active": evt.IsActive, + "time_enforcement_ends": evt.TimeEnforcementEnds.Time, + }) + wa.UserLogin.Metadata.(*waid.UserLoginMetadata).ReachoutTimelockUntil = evt.TimeEnforcementEnds.Time + if wa.UserLogin.BridgeState.GetPrevUnsent().StateEvent == status.StateConnected { + wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected}) + } + err := wa.UserLogin.Save(ctx) + if err != nil { + log.Err(err).Msg("Failed to save user login metadata after reachout timelock update") + } case *events.AppStateSyncComplete: wa.handleWAAppStateSyncComplete(ctx, evt) diff --git a/pkg/connector/phoneping.go b/pkg/connector/phoneping.go index ce38079..71094c9 100644 --- a/pkg/connector/phoneping.go +++ b/pkg/connector/phoneping.go @@ -37,6 +37,13 @@ func (wa *WhatsAppClient) FillBridgeState(state status.BridgeState) status.Bridg state.Error = WAPhoneOffline state.UserAction = status.UserActionOpenNative } + rtu := wa.UserLogin.Metadata.(*waid.UserLoginMetadata).ReachoutTimelockUntil + if !rtu.IsZero() { + if state.Info == nil { + state.Info = make(map[string]any) + } + state.Info["reachout_timelock_until"] = rtu + } return state } diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index 59e9a9e..1e54426 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -41,7 +41,8 @@ type UserLoginMetadata struct { AppStateRecoveryAttempted map[appstate.WAPatchName]time.Time `json:"app_state_recovery_attempted,omitempty"` - HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitempty"` + HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitempty"` + ReachoutTimelockUntil time.Time `json:"reachout_timelock_until,omitempty"` MData json.RawMessage `json:"mdata,omitempty"` } From d5cf334423689ffb67b1469b4bca6b3b0e61a34a Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 1 Jul 2026 23:01:34 +0300 Subject: [PATCH 59/94] login: add support for passkeys (#936) --- go.mod | 4 +- go.sum | 8 ++-- pkg/connector/login.go | 105 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 105 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 791f3e7..c16824c 100644 --- a/go.mod +++ b/go.mod @@ -12,13 +12,13 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.10 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260622124518-e94c58afd43b + go.mau.fi/whatsmeow v0.0.0-20260630180629-b572e5bcb92b golang.org/x/image v0.42.0 golang.org/x/net v0.56.0 golang.org/x/sync v0.21.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.28.1 + maunium.net/go/mautrix v0.28.2-0.20260701195654-d33ead4e1d86 ) require ( diff --git a/go.sum b/go.sum index 0112e10..d38c8cb 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.10 h1:wzvz5iDHyqDXB8vgisD4d3SzucLXNM3iNY+1O1RoHtg= go.mau.fi/util v0.9.10/go.mod h1:YQOxySn+ZE3qSYqNxvyX7Yi3suA8YK17PS6QqBREW7A= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260622124518-e94c58afd43b h1:SeXzfZd3zFyHg+JEDOb5NZqMJRCRK5NXtXy+qzDJGSs= -go.mau.fi/whatsmeow v0.0.0-20260622124518-e94c58afd43b/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U= +go.mau.fi/whatsmeow v0.0.0-20260630180629-b572e5bcb92b h1:ZUk1ErarDNpnbosXR/MeOz2gkqA4S1bh8zjaSRj7N+Y= +go.mau.fi/whatsmeow v0.0.0-20260630180629-b572e5bcb92b/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= @@ -107,5 +107,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.28.1 h1:Hic3oDMPbLbQu1fhboTRAKZcORMjzzkjxsa+SGk60b0= -maunium.net/go/mautrix v0.28.1/go.mod h1:mWXQNmOlrq4VTDU9f1HO03BSIswdUIyyY4wUKHqwzzY= +maunium.net/go/mautrix v0.28.2-0.20260701195654-d33ead4e1d86 h1:OKNg/klCID3MoZHuhpMSQThvWOqKtbCrXC6HjlY4+QA= +maunium.net/go/mautrix v0.28.2-0.20260701195654-d33ead4e1d86/go.mod h1:mWXQNmOlrq4VTDU9f1HO03BSIswdUIyyY4wUKHqwzzY= diff --git a/pkg/connector/login.go b/pkg/connector/login.go index b70ab1a..5da419f 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -2,6 +2,7 @@ package connector import ( "context" + "encoding/json" "errors" "fmt" "net/http" @@ -12,6 +13,7 @@ import ( "go.mau.fi/util/exsync" "go.mau.fi/util/jsontime" "go.mau.fi/whatsmeow" + "go.mau.fi/whatsmeow/types" "go.mau.fi/whatsmeow/types/events" waLog "go.mau.fi/whatsmeow/util/log" "maunium.net/go/mautrix/bridgev2" @@ -25,6 +27,7 @@ const ( LoginStepIDQR = "fi.mau.whatsapp.login.qr" LoginStepIDPhoneNumber = "fi.mau.whatsapp.login.phone" LoginStepIDCode = "fi.mau.whatsapp.login.code" + LoginStepIDPasskey = "fi.mau.whatsapp.login.passkey" LoginStepIDComplete = "fi.mau.whatsapp.login.complete" LoginFlowIDQR = "qr" @@ -91,9 +94,11 @@ func (wa *WhatsAppConnector) CreateLogin(_ context.Context, user *bridgev2.User, Bool("phone_code", flowID == LoginFlowIDPhone). Logger(), - WaitForQRs: exsync.NewEvent(), - LoginComplete: exsync.NewEvent(), - Received515: exsync.NewEvent(), + WaitForQRs: exsync.NewEvent(), + LoginComplete: exsync.NewEvent(), + PasskeyRequest: exsync.NewEvent(), + PasskeyConfirmation: exsync.NewEvent(), + Received515: exsync.NewEvent(), }, nil } @@ -114,6 +119,11 @@ type WALogin struct { Received515 *exsync.Event PrevQRIndex atomic.Int32 + PasskeyRequest *exsync.Event + PasskeyRequestData *events.PairPasskeyRequest + PasskeyConfirmation *exsync.Event + PasskeyConfirmationData *events.PairPasskeyConfirmation + Closed atomic.Bool EventHandlerID uint32 } @@ -122,6 +132,7 @@ var ( _ bridgev2.LoginProcessDisplayAndWait = (*WALogin)(nil) _ bridgev2.LoginProcessUserInput = (*WALogin)(nil) _ bridgev2.LoginProcessWithOverride = (*WALogin)(nil) + _ bridgev2.LoginProcessWebAuthn = (*WALogin)(nil) ) const LoginConnectWait = 15 * time.Second @@ -262,6 +273,17 @@ func (wl *WALogin) handleEvent(rawEvt any) { case *events.ClientOutdated: wl.Log.Error().Msg("Got client outdated error") wl.LoginError = ErrLoginClientOutdated + case *events.PairPasskeyRequest: + wl.PasskeyRequestData = evt + wl.PasskeyRequest.Set() + return + case *events.PairPasskeyConfirmation: + wl.PasskeyConfirmationData = evt + wl.PasskeyConfirmation.Set() + return + case *events.PairPasskeyError: + wl.Log.Err(evt.Error).Msg("Got passkey error") + wl.LoginError = evt.Error case *events.PairSuccess: wl.Log.Info().Any("event_data", evt).Msg("Got pair successful event") wl.LoginSuccess = evt @@ -289,10 +311,14 @@ func (wl *WALogin) handleEvent(rawEvt any) { func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { if wl.PhoneCode { - err := wl.LoginComplete.Wait(ctx) - if err != nil { + select { + case <-ctx.Done(): wl.Cancel() - return nil, err + return nil, ctx.Err() + case <-wl.PasskeyRequest.GetChan(): + return wl.makePasskeyStep() + case <-wl.LoginComplete.GetChan(): + // continue } } else { prevIndex := int(wl.PrevQRIndex.Load()) @@ -319,9 +345,16 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { case <-ctx.Done(): wl.Cancel() return nil, ctx.Err() + case <-wl.PasskeyRequest.GetChan(): + return wl.makePasskeyStep() case <-wl.LoginComplete.GetChan(): + // continue } } + return wl.onLoginComplete(ctx) +} + +func (wl *WALogin) onLoginComplete(ctx context.Context) (*bridgev2.LoginStep, error) { if wl.LoginError != nil { wl.Log.Debug().Err(wl.LoginError).Msg("Login completed with error") wl.Cancel() @@ -372,6 +405,66 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { }, nil } +func (wl *WALogin) makePasskeyStep() (*bridgev2.LoginStep, error) { + pubkeyData, err := json.Marshal(wl.PasskeyRequestData.PublicKey) + if err != nil { + wl.Log.Err(err).Msg("Failed to marshal public key data") + return nil, err + } + return &bridgev2.LoginStep{ + Type: bridgev2.LoginStepTypeWebAuthn, + StepID: LoginStepIDPasskey, + WebAuthnParams: &bridgev2.LoginWebAuthnParams{ + URL: "https://web.whatsapp.com", + PublicKey: pubkeyData, + }, + }, nil +} + +func (wl *WALogin) SubmitWebAuthnResponse(ctx context.Context, rawResp json.RawMessage) (*bridgev2.LoginStep, error) { + var resp types.WebAuthnResponse + err := json.Unmarshal(rawResp, &resp) + if err != nil { + wl.Log.Err(err).Msg("Failed to unmarshal WebAuthn response") + wl.Cancel() + return nil, err + } + err = wl.Client.SendPasskeyResponse(ctx, &resp) + if err != nil { + wl.Log.Err(err).Msg("Failed to send WebAuthn response") + wl.Cancel() + return nil, err + } + select { + case <-ctx.Done(): + wl.Cancel() + return nil, ctx.Err() + case <-wl.PasskeyConfirmation.GetChan(): + if !wl.PasskeyConfirmationData.SkipHandoffUX { + wl.Cancel() + return nil, bridgev2.RespError{ + ErrCode: "FI.MAU.WHATSAPP.NOT_IMPLEMENTED", + Err: "Displaying WebAuthn pairing confirmation codes is not yet implemented", + StatusCode: http.StatusBadRequest, + } + } + err = wl.Client.SendPasskeyConfirmation(ctx) + if err != nil { + wl.Log.Err(err).Msg("Failed to send WebAuthn confirmation") + wl.Cancel() + return nil, err + } + case <-wl.LoginComplete.GetChan(): + } + select { + case <-ctx.Done(): + wl.Cancel() + return nil, ctx.Err() + case <-wl.LoginComplete.GetChan(): + } + return wl.onLoginComplete(ctx) +} + func (wl *WALogin) Cancel() { wl.Closed.Store(true) wl.Client.RemoveEventHandler(wl.EventHandlerID) From f3ca7ac1c39401e99ef05112082b1d2bc022df83 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 2 Jul 2026 00:15:15 +0300 Subject: [PATCH 60/94] 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 c16824c..bf909be 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.21.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.28.2-0.20260701195654-d33ead4e1d86 + maunium.net/go/mautrix v0.28.2-0.20260701211445-bbd547b7edc7 ) require ( diff --git a/go.sum b/go.sum index d38c8cb..5be58b3 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.28.2-0.20260701195654-d33ead4e1d86 h1:OKNg/klCID3MoZHuhpMSQThvWOqKtbCrXC6HjlY4+QA= -maunium.net/go/mautrix v0.28.2-0.20260701195654-d33ead4e1d86/go.mod h1:mWXQNmOlrq4VTDU9f1HO03BSIswdUIyyY4wUKHqwzzY= +maunium.net/go/mautrix v0.28.2-0.20260701211445-bbd547b7edc7 h1:zqwDd/c02keWWjn5AqafdKnahtGChn595Vuw6af5WdU= +maunium.net/go/mautrix v0.28.2-0.20260701211445-bbd547b7edc7/go.mod h1:mWXQNmOlrq4VTDU9f1HO03BSIswdUIyyY4wUKHqwzzY= From 7753bb3999305ea7c7f1825348a56e4d73f30055 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 2 Jul 2026 15:40:33 +0300 Subject: [PATCH 61/94] 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 bf909be..9dc2e58 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.21.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.28.2-0.20260701211445-bbd547b7edc7 + maunium.net/go/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895 ) require ( diff --git a/go.sum b/go.sum index 5be58b3..1d4b8af 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.28.2-0.20260701211445-bbd547b7edc7 h1:zqwDd/c02keWWjn5AqafdKnahtGChn595Vuw6af5WdU= -maunium.net/go/mautrix v0.28.2-0.20260701211445-bbd547b7edc7/go.mod h1:mWXQNmOlrq4VTDU9f1HO03BSIswdUIyyY4wUKHqwzzY= +maunium.net/go/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895 h1:oe87/mP1vo4LnA6CJDEfyg5ZiAOar2quJNypfO8eS4Y= +maunium.net/go/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895/go.mod h1:mWXQNmOlrq4VTDU9f1HO03BSIswdUIyyY4wUKHqwzzY= From 8d34f6f6bddfa8dee7a5f8a8af4a3c5a17ada342 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 9 Jul 2026 12:12:18 +0300 Subject: [PATCH 62/94] msgconv/from-whatsapp: use correct message info for decrypting poll votes --- pkg/connector/backfill.go | 2 +- pkg/connector/events.go | 9 +++++---- pkg/connector/handlewhatsapp.go | 6 ++++-- pkg/msgconv/from-whatsapp.go | 3 ++- pkg/msgconv/wa-poll.go | 8 ++++++-- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index ee92ac2..dd04696 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -750,7 +750,7 @@ func (wa *WhatsAppClient) convertHistorySyncMessage( // TODO use proper intent intent := wa.Main.Bridge.Bot wrapped := &bridgev2.BackfillMessage{ - ConvertedMessage: wa.Main.MsgConv.ToMatrix(ctx, portal, wa.Client, intent, msg, rawMsg, info, isViewOnce, true, nil), + ConvertedMessage: wa.Main.MsgConv.ToMatrix(ctx, portal, wa.Client, intent, msg, rawMsg, info, nil, isViewOnce, true, nil), Sender: wa.makeEventSender(ctx, info.Sender), ID: waid.MakeMessageID(info.Chat, info.Sender, info.ID), TxnID: networkid.TransactionID(waid.MakeMessageID(info.Chat, info.Sender, info.ID)), diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 411c39e..cb51e64 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -52,8 +52,9 @@ func (wa *WhatsAppClient) getPortalKeyByMessageSource(ms types.MessageSource) ne } type MessageInfoWrapper struct { - Info types.MessageInfo - wa *WhatsAppClient + OrigSource types.MessageSource + Info types.MessageInfo + wa *WhatsAppClient } func (evt *MessageInfoWrapper) ShouldCreatePortal() bool { @@ -200,7 +201,7 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por ctx = context.WithValue(ctx, msgconv.ContextKeyEditTargetID, evt.Message.GetProtocolMessage().GetKey().GetID()) cm := evt.wa.Main.MsgConv.ToMatrix( - ctx, portal, evt.wa.Client, intent, editedMsg, evt.MsgEvent.RawMessage, &evt.Info, evt.isViewOnce(), false, previouslyConvertedPart, + ctx, portal, evt.wa.Client, intent, editedMsg, evt.MsgEvent.RawMessage, &evt.Info, &evt.OrigSource, evt.isViewOnce(), false, previouslyConvertedPart, ) if evt.isUndecryptableUpsertSubEvent && isFailedMedia(cm) { evt.postHandle = func() { @@ -285,7 +286,7 @@ func (evt *WAMessageEvent) HandleExisting(ctx context.Context, portal *bridgev2. func (evt *WAMessageEvent) ConvertMessage(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI) (*bridgev2.ConvertedMessage, error) { evt.wa.EnqueuePortalResync(portal, false) converted := evt.wa.Main.MsgConv.ToMatrix( - ctx, portal, evt.wa.Client, intent, evt.Message, evt.MsgEvent.RawMessage, &evt.Info, evt.isViewOnce(), false, nil, + ctx, portal, evt.wa.Client, intent, evt.Message, evt.MsgEvent.RawMessage, &evt.Info, &evt.OrigSource, evt.isViewOnce(), false, nil, ) if isFailedMedia(converted) { evt.postHandle = func() { diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 2b9b145..f12ebad 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -358,6 +358,7 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa evt.UnwrapRaw() parsedMessageType = getMessageType(evt.Message) } + origSource := evt.Info.MessageSource wa.rerouteWAMessage(ctx, "message", &evt.Info.MessageSource, evt.Info.ID) wa.UserLogin.Log.Trace(). Any("info", evt.Info). @@ -409,8 +410,9 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa res := wa.UserLogin.QueueRemoteEvent(&WAMessageEvent{ MessageInfoWrapper: &MessageInfoWrapper{ - Info: evt.Info, - wa: wa, + OrigSource: origSource, + Info: evt.Info, + wa: wa, }, Message: evt.Message, MsgEvent: evt, diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index f0e4560..6eea25f 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -136,6 +136,7 @@ func (mc *MessageConverter) ToMatrix( waMsg *waE2E.Message, rawWaMsg *waE2E.Message, info *types.MessageInfo, + origSource *types.MessageSource, isViewOnce bool, isBackfill bool, previouslyConvertedPart *bridgev2.ConvertedMessagePart, @@ -176,7 +177,7 @@ func (mc *MessageConverter) ToMatrix( case waMsg.PollCreationMessageV3 != nil: part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV3) case waMsg.PollUpdateMessage != nil: - part, contextInfo = mc.convertPollUpdateMessage(ctx, info, waMsg.PollUpdateMessage) + part, contextInfo = mc.convertPollUpdateMessage(ctx, info, origSource, waMsg.PollUpdateMessage) case waMsg.EventMessage != nil: part, contextInfo = mc.convertEventMessage(ctx, waMsg.EventMessage) case waMsg.PinInChatMessage != nil: diff --git a/pkg/msgconv/wa-poll.go b/pkg/msgconv/wa-poll.go index 14388be..dfe1a25 100644 --- a/pkg/msgconv/wa-poll.go +++ b/pkg/msgconv/wa-poll.go @@ -170,7 +170,7 @@ var failedPollUpdatePart = &bridgev2.ConvertedMessagePart{ DontBridge: true, } -func (mc *MessageConverter) convertPollUpdateMessage(ctx context.Context, info *types.MessageInfo, msg *waE2E.PollUpdateMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { +func (mc *MessageConverter) convertPollUpdateMessage(ctx context.Context, info *types.MessageInfo, origSource *types.MessageSource, msg *waE2E.PollUpdateMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { log := zerolog.Ctx(ctx) pollMessageID := KeyToMessageID(ctx, getClient(ctx), info.Chat, info.Sender, msg.PollCreationMessageKey) pollMessage, err := mc.Bridge.DB.Message.GetPartByID(ctx, getPortal(ctx).Receiver, pollMessageID, "") @@ -181,8 +181,12 @@ func (mc *MessageConverter) convertPollUpdateMessage(ctx context.Context, info * log.Warn().Str("target_message_id", string(pollMessageID)).Msg("Poll update target message not found") return failedPollUpdatePart, nil } + infoForDecrypt := *info + if origSource != nil { + infoForDecrypt.MessageSource = *origSource + } vote, err := getClient(ctx).DecryptPollVote(ctx, &events.Message{ - Info: *info, + Info: infoForDecrypt, Message: &waE2E.Message{PollUpdateMessage: msg}, }) if err != nil { From 942d1b4c0ade8ccc8101b61cc2d9be0a5ec9abcd Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 9 Jul 2026 14:27:54 +0300 Subject: [PATCH 63/94] 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 9dc2e58..35a6655 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.10 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260630180629-b572e5bcb92b + go.mau.fi/whatsmeow v0.0.0-20260709092057-73fe7355f59f golang.org/x/image v0.42.0 golang.org/x/net v0.56.0 golang.org/x/sync v0.21.0 diff --git a/go.sum b/go.sum index 1d4b8af..3d0391c 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.10 h1:wzvz5iDHyqDXB8vgisD4d3SzucLXNM3iNY+1O1RoHtg= go.mau.fi/util v0.9.10/go.mod h1:YQOxySn+ZE3qSYqNxvyX7Yi3suA8YK17PS6QqBREW7A= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260630180629-b572e5bcb92b h1:ZUk1ErarDNpnbosXR/MeOz2gkqA4S1bh8zjaSRj7N+Y= -go.mau.fi/whatsmeow v0.0.0-20260630180629-b572e5bcb92b/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U= +go.mau.fi/whatsmeow v0.0.0-20260709092057-73fe7355f59f h1:VZkwFBEQ9TbB9IWsldw8BJDhHbEBElm9VD2VQXbSc9s= +go.mau.fi/whatsmeow v0.0.0-20260709092057-73fe7355f59f/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= From a1a4cf87b336703e3af9a3e172598a2e7fb04d25 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 9 Jul 2026 14:44:00 +0300 Subject: [PATCH 64/94] msgconv/from-matrix: add hack to use LID for message secrets in DMs --- pkg/connector/handlematrix.go | 2 +- pkg/msgconv/from-matrix.go | 2 +- pkg/msgconv/matrixpoll.go | 22 ++++++++++++++++------ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index 299449f..9aea18b 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -177,7 +177,7 @@ func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev defer wa.mcTrack(msg, time.Now(), &retErr) var req whatsmeow.SendRequestExtra if msg.Portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup { - reactionMsg.EncReactionMessage, err = wa.Client.EncryptReaction(ctx, msgconv.MessageIDToInfo(wa.Client, messageID), reactionMsg.ReactionMessage) + reactionMsg.EncReactionMessage, err = wa.Client.EncryptReaction(ctx, msgconv.MessageIDToInfo(ctx, wa.Client, messageID), reactionMsg.ReactionMessage) if err != nil { return nil, fmt.Errorf("failed to encrypt reaction: %w", err) } diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 93de90c..49aedf8 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -143,7 +143,7 @@ func (mc *MessageConverter) ToWhatsApp( if err != nil { return nil, nil, fmt.Errorf("failed to parse message ID: %w", err) } - rootMsgInfo := MessageIDToInfo(client, parsedID) + rootMsgInfo := MessageIDToInfo(ctx, client, parsedID) message, err = client.EncryptComment(ctx, rootMsgInfo, message) if err != nil { return nil, nil, fmt.Errorf("failed to encrypt comment: %w", err) diff --git a/pkg/msgconv/matrixpoll.go b/pkg/msgconv/matrixpoll.go index 0dc3213..b53c22f 100644 --- a/pkg/msgconv/matrixpoll.go +++ b/pkg/msgconv/matrixpoll.go @@ -115,7 +115,7 @@ func (mc *MessageConverter) PollVoteToWhatsApp( zerolog.Ctx(ctx).Err(err).Msg("Failed to parse message ID") return nil, fmt.Errorf("failed to parse message ID") } - pollMsgInfo := MessageIDToInfo(client, parsedMsgID) + pollMsgInfo := MessageIDToInfo(ctx, client, parsedMsgID) pollMsgInfo.Type = "poll" optionHashes := make([][]byte, 0, len(content.Response.Answers)) if pollMsg.Metadata.(*waid.MessageMetadata).IsMatrixPoll { @@ -146,13 +146,23 @@ func (mc *MessageConverter) PollVoteToWhatsApp( return &waE2E.Message{PollUpdateMessage: pollUpdate}, err } -func MessageIDToInfo(client *whatsmeow.Client, parsedMsgID *waid.ParsedMessageID) *types.MessageInfo { +func MessageIDToInfo(ctx context.Context, client *whatsmeow.Client, parsedMsgID *waid.ParsedMessageID) *types.MessageInfo { + chat := parsedMsgID.Chat + sender := parsedMsgID.Sender + if client.Store.LIDMigrationTimestamp != 0 && chat.Server == types.DefaultUserServer { + chatLID, _ := client.Store.LIDs.GetLIDForPN(ctx, chat) + senderLID, _ := client.Store.LIDs.GetLIDForPN(ctx, sender) + if !chatLID.IsEmpty() && !senderLID.IsEmpty() { + chat = chatLID + sender = senderLID + } + } return &types.MessageInfo{ MessageSource: types.MessageSource{ - Chat: parsedMsgID.Chat, - Sender: parsedMsgID.Sender, - IsFromMe: parsedMsgID.Sender.User == client.Store.GetLID().User || parsedMsgID.Sender.User == client.Store.GetJID().User, - IsGroup: parsedMsgID.Chat.Server == types.GroupServer, + Chat: chat, + Sender: sender, + IsFromMe: sender.User == client.Store.GetLID().User || sender.User == client.Store.GetJID().User, + IsGroup: chat.Server == types.GroupServer, }, ID: parsedMsgID.ID, } From 5250ddfa7d6b92228aff3f29d18b40795e7709d8 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 10 Jul 2026 18:36:17 +0300 Subject: [PATCH 65/94] msgconv/from-whatsapp: handle v5 and v6 poll creations --- pkg/connector/wamsgtype.go | 7 ++++--- pkg/msgconv/from-whatsapp.go | 6 ++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pkg/connector/wamsgtype.go b/pkg/connector/wamsgtype.go index 4bcf483..0a29b82 100644 --- a/pkg/connector/wamsgtype.go +++ b/pkg/connector/wamsgtype.go @@ -67,10 +67,11 @@ func getMessageType(waMsg *waE2E.Message) string { return "encrypted comment" case waMsg.CommentMessage != nil: return "comment" - case waMsg.PollCreationMessage != nil || waMsg.PollCreationMessageV2 != nil || waMsg.PollCreationMessageV3 != nil: + case waMsg.PollCreationMessage != nil || waMsg.PollCreationMessageV2 != nil || waMsg.PollCreationMessageV3 != nil || + waMsg.PollCreationMessageV5 != nil || waMsg.PollCreationMessageV6 != nil: return "poll create" - case waMsg.PollCreationMessageV4 != nil || waMsg.PollCreationMessageV5 != nil: - return "poll create (vNext)" + case waMsg.PollCreationMessageV4 != nil: + return "poll create (v4)" case waMsg.PollUpdateMessage != nil: return "poll update" case waMsg.ProtocolMessage != nil: diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 6eea25f..e8cb032 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -176,6 +176,12 @@ func (mc *MessageConverter) ToMatrix( part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV2) case waMsg.PollCreationMessageV3 != nil: part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV3) + //case waMsg.PollCreationMessageV4 != nil: + // part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV4) + case waMsg.PollCreationMessageV5 != nil: + part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV5) + case waMsg.PollCreationMessageV6 != nil: + part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV6) case waMsg.PollUpdateMessage != nil: part, contextInfo = mc.convertPollUpdateMessage(ctx, info, origSource, waMsg.PollUpdateMessage) case waMsg.EventMessage != nil: From c2fc0d0b81d23ecadd54cbeb58c62a75df7824d0 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 13 Jul 2026 14:21:42 +0300 Subject: [PATCH 66/94] 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 35a6655..7c706b5 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.10 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260709092057-73fe7355f59f + go.mau.fi/whatsmeow v0.0.0-20260713112039-592721a00b51 golang.org/x/image v0.42.0 golang.org/x/net v0.56.0 golang.org/x/sync v0.21.0 diff --git a/go.sum b/go.sum index 3d0391c..f7bf881 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.10 h1:wzvz5iDHyqDXB8vgisD4d3SzucLXNM3iNY+1O1RoHtg= go.mau.fi/util v0.9.10/go.mod h1:YQOxySn+ZE3qSYqNxvyX7Yi3suA8YK17PS6QqBREW7A= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260709092057-73fe7355f59f h1:VZkwFBEQ9TbB9IWsldw8BJDhHbEBElm9VD2VQXbSc9s= -go.mau.fi/whatsmeow v0.0.0-20260709092057-73fe7355f59f/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U= +go.mau.fi/whatsmeow v0.0.0-20260713112039-592721a00b51 h1:KkppXopVRx6Juio9FmPBVQzzK0e1uW65ynIDBi/v4c8= +go.mau.fi/whatsmeow v0.0.0-20260713112039-592721a00b51/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= From ba6430c70131eab3265b80e854bc9e77181618e6 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 13 Jul 2026 14:22:00 +0300 Subject: [PATCH 67/94] startchat: add cache to avoid repeated IsOnWhatsApp calls --- pkg/connector/startchat.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index a0b0692..fb2ce16 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -25,6 +25,7 @@ import ( "time" "github.com/rs/zerolog" + "go.mau.fi/util/exsync" "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/types" @@ -62,6 +63,13 @@ func looksEmaily(str string) bool { return false } +type cacheEntry struct { + jid types.JID + ts time.Time +} + +var isOnWhatsappCache = exsync.NewMap[string, cacheEntry]() + func (wa *WhatsAppClient) validateIdentifer(ctx context.Context, number string) (types.JID, error) { if strings.HasSuffix(number, "@"+types.BotServer) || strings.HasSuffix(number, "@"+types.HiddenUserServer) { return types.ParseJID(number) @@ -76,6 +84,8 @@ func (wa *WhatsAppClient) validateIdentifer(ctx context.Context, number string) return types.EmptyJID, ErrInputLooksLikeEmail } else if wa.Client == nil || !wa.Client.IsLoggedIn() { return types.EmptyJID, bridgev2.ErrNotLoggedIn + } else if entry, ok := isOnWhatsappCache.Get(number); ok && time.Since(entry.ts) < 4*time.Hour { + return entry.jid, nil } else if resp, err := wa.Client.IsOnWhatsApp(ctx, []string{number}); err != nil { return types.EmptyJID, fmt.Errorf("failed to check if number is on WhatsApp: %w", err) } else if len(resp) == 0 { @@ -83,6 +93,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()}) return resp[0].JID, nil } } From 316a65bae093daada204cc62a8d19346c2569f6a Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 13 Jul 2026 14:22:47 +0300 Subject: [PATCH 68/94] changelog: update --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 560e2b5..37b9fe8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# v26.07 (unreleased) + +* Added support for the extra passkey authentication step when logging in. +* Added support for newer versions of poll messages from WhatsApp. +* Fixed poll votes in DMs. + # v26.06 * Added placeholder for group message history share notices. From e0d37d9fa94684955bcca97b202bdf27a06789aa Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 13 Jul 2026 14:25:04 +0300 Subject: [PATCH 69/94] msgconv/matrixpoll: assume DMs are always sent to LIDs --- pkg/msgconv/matrixpoll.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/msgconv/matrixpoll.go b/pkg/msgconv/matrixpoll.go index b53c22f..a5f02ea 100644 --- a/pkg/msgconv/matrixpoll.go +++ b/pkg/msgconv/matrixpoll.go @@ -149,7 +149,7 @@ func (mc *MessageConverter) PollVoteToWhatsApp( func MessageIDToInfo(ctx context.Context, client *whatsmeow.Client, parsedMsgID *waid.ParsedMessageID) *types.MessageInfo { chat := parsedMsgID.Chat sender := parsedMsgID.Sender - if client.Store.LIDMigrationTimestamp != 0 && chat.Server == types.DefaultUserServer { + if chat.Server == types.DefaultUserServer { chatLID, _ := client.Store.LIDs.GetLIDForPN(ctx, chat) senderLID, _ := client.Store.LIDs.GetLIDForPN(ctx, sender) if !chatLID.IsEmpty() && !senderLID.IsEmpty() { From 70c7325bb270fde43d0ef6d5b5ae72e7aceda662 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 13 Jul 2026 14:30:21 +0300 Subject: [PATCH 70/94] 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 7c706b5..ba3792b 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.10 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260713112039-592721a00b51 + go.mau.fi/whatsmeow v0.0.0-20260713112832-d8960d9575d2 golang.org/x/image v0.42.0 golang.org/x/net v0.56.0 golang.org/x/sync v0.21.0 diff --git a/go.sum b/go.sum index f7bf881..f24a245 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.10 h1:wzvz5iDHyqDXB8vgisD4d3SzucLXNM3iNY+1O1RoHtg= go.mau.fi/util v0.9.10/go.mod h1:YQOxySn+ZE3qSYqNxvyX7Yi3suA8YK17PS6QqBREW7A= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260713112039-592721a00b51 h1:KkppXopVRx6Juio9FmPBVQzzK0e1uW65ynIDBi/v4c8= -go.mau.fi/whatsmeow v0.0.0-20260713112039-592721a00b51/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U= +go.mau.fi/whatsmeow v0.0.0-20260713112832-d8960d9575d2 h1:2J3waQ2Br8Mh3SW5tVuuChLysXKeprGcTonORU51CIo= +go.mau.fi/whatsmeow v0.0.0-20260713112832-d8960d9575d2/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= From 06a93a559d4f0e2b8744afa8b4a8a42edfbfa10d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 14 Jul 2026 22:22:21 +0300 Subject: [PATCH 71/94] docker: update to Alpine 3.24 --- Dockerfile | 4 ++-- Dockerfile.ci | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4efc9d5..e59d110 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1-alpine3.23 AS builder +FROM golang:1-alpine3.24 AS builder RUN apk add --no-cache git ca-certificates build-base su-exec olm-dev @@ -6,7 +6,7 @@ COPY . /build WORKDIR /build RUN ./build.sh -FROM alpine:3.23 +FROM alpine:3.24 ENV UID=1337 \ GID=1337 diff --git a/Dockerfile.ci b/Dockerfile.ci index cb3be51..0a73ff5 100644 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -1,6 +1,6 @@ ARG DOCKER_HUB="docker.io" -FROM ${DOCKER_HUB}/alpine:3.23 +FROM ${DOCKER_HUB}/alpine:3.24 ENV UID=1337 \ GID=1337 From a86f5eb9bf7d5a4a6cc7a1c4e42d322bdcb03aa2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 16 Jul 2026 14:15:56 +0300 Subject: [PATCH 72/94] Bump version to v26.07 --- CHANGELOG.md | 3 +- cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 30 +++++++++---------- go.sum | 56 ++++++++++++++++++------------------ 4 files changed, 46 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37b9fe8..e47e7b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ -# v26.07 (unreleased) +# v26.07 +* Updated Docker image to Alpine 3.24. * Added support for the extra passkey authentication step when logging in. * Added support for newer versions of poll messages from WhatsApp. * Fixed poll votes in DMs. diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 07baaec..47d971c 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.06", + Version: "26.07", SemCalVer: true, Connector: &connector.WhatsAppConnector{}, } diff --git a/go.mod b/go.mod index ba3792b..16504a2 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module go.mau.fi/mautrix-whatsapp go 1.25.0 -toolchain go1.26.4 +toolchain go1.26.5 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.9.10 + go.mau.fi/util v0.9.11 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260713112832-d8960d9575d2 - golang.org/x/image v0.42.0 - golang.org/x/net v0.56.0 - golang.org/x/sync v0.21.0 + go.mau.fi/whatsmeow v0.0.0-20260716095330-85d99080dee8 + golang.org/x/image v0.44.0 + golang.org/x/net v0.57.0 + golang.org/x/sync v0.22.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895 + maunium.net/go/mautrix v0.29.0 ) 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.45 // indirect - github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 // indirect + github.com/mattn/go-sqlite3 v1.14.48 // indirect + github.com/petermattis/goid v0.0.0-20260713124913-97594f28f5ca // 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 @@ -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.2 // indirect + github.com/yuin/goldmark v1.8.4 // indirect go.mau.fi/libsignal v0.2.2 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect - golang.org/x/crypto v0.53.0 // indirect - golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect - golang.org/x/mod v0.37.0 // indirect - golang.org/x/sys v0.46.0 // indirect - golang.org/x/text v0.38.0 // indirect + golang.org/x/crypto v0.54.0 // indirect + golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597 // indirect + golang.org/x/mod v0.38.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.40.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 f24a245..7899436 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.45 h1:6KA/spDguL3KV8rnybG7ezSaE4SeMR3KC9VbUoAQaIk= -github.com/mattn/go-sqlite3 v1.14.45/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= -github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 h1:WDsQxOJDy0N1VRAjXLpi8sCEZRSGarLWQevDxpTBRrM= -github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/mattn/go-sqlite3 v1.14.48 h1:7XHIgl0a8HwOaiK4E47ozLkST78rR9+OtNGx27D/TFs= +github.com/mattn/go-sqlite3 v1.14.48/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= +github.com/petermattis/goid v0.0.0-20260713124913-97594f28f5ca h1:GHSUVE4yOgX4E7kTRzpxCPbCOYkd3Kj8Dgdod30OI1E= +github.com/petermattis/goid v0.0.0-20260713124913-97594f28f5ca/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= @@ -67,35 +67,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.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= -github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark v1.8.4 h1:oat/nd3U6NeQqFEL3xpEJq7d7c86NI+DbSNGAs4xnjA= +github.com/yuin/goldmark v1.8.4/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.9.10 h1:wzvz5iDHyqDXB8vgisD4d3SzucLXNM3iNY+1O1RoHtg= -go.mau.fi/util v0.9.10/go.mod h1:YQOxySn+ZE3qSYqNxvyX7Yi3suA8YK17PS6QqBREW7A= +go.mau.fi/util v0.9.11 h1:Cus1Lu/t7d3OG6VF4aYWvlUUS0Q4O1/lcpPNJZ0jsw0= +go.mau.fi/util v0.9.11/go.mod h1:xunp/oIQfFD68HHcNHfG0pOiHkvEtDhTweeIwKJ//+Q= 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-20260713112832-d8960d9575d2 h1:2J3waQ2Br8Mh3SW5tVuuChLysXKeprGcTonORU51CIo= -go.mau.fi/whatsmeow v0.0.0-20260713112832-d8960d9575d2/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U= +go.mau.fi/whatsmeow v0.0.0-20260716095330-85d99080dee8 h1:7RQA3v4pCZcmgHaEQXKfHKLVSqPThizkApt6Uw+DcA8= +go.mau.fi/whatsmeow v0.0.0-20260716095330-85d99080dee8/go.mod h1:SX7VdCALDRNx7HZ7mqZxjyi3U7N0QtbSXXXTl8rG5S4= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= -golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M= -golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY= -golang.org/x/image v0.42.0 h1:1gSs6ehNWXLbkHBIPcWztk3D/6aIA/8hauiAYtlodVY= -golang.org/x/image v0.42.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q= -golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= -golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= -golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597 h1:qLvzZeaANDgyVOA8pyHCOStGlXn0rseXma+GQjeuv2g= +golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q= +golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I= +golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY= +golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +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/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +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.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -107,5 +107,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895 h1:oe87/mP1vo4LnA6CJDEfyg5ZiAOar2quJNypfO8eS4Y= -maunium.net/go/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895/go.mod h1:mWXQNmOlrq4VTDU9f1HO03BSIswdUIyyY4wUKHqwzzY= +maunium.net/go/mautrix v0.29.0 h1:OkcBJF1dvp+93EgahxMxOUZZOrGTYculI9IprvRIMOQ= +maunium.net/go/mautrix v0.29.0/go.mod h1:LynuVr8N9nWsE1N4WAE+vItRACDB1pt9M3gN4SIBpeY= From 13c2470aeef71af155a381078f135fc01a93fc8b Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 18 Jul 2026 16:55:01 +0300 Subject: [PATCH 73/94] dependencies: update mautrix-go --- go.mod | 6 +++--- go.sum | 12 ++++++------ pkg/connector/wadb/upgrades/upgrades.go | 8 +++----- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 16504a2..a4e7f07 100644 --- a/go.mod +++ b/go.mod @@ -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.9.11 + go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260716095330-85d99080dee8 + go.mau.fi/whatsmeow v0.0.0-20260718134955-fac667d55293 golang.org/x/image v0.44.0 golang.org/x/net v0.57.0 golang.org/x/sync v0.22.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.29.0 + maunium.net/go/mautrix v0.29.1-0.20260719130752-5743d9b6f27e ) require ( diff --git a/go.sum b/go.sum index 7899436..0786304 100644 --- a/go.sum +++ b/go.sum @@ -71,12 +71,12 @@ github.com/yuin/goldmark v1.8.4 h1:oat/nd3U6NeQqFEL3xpEJq7d7c86NI+DbSNGAs4xnjA= github.com/yuin/goldmark v1.8.4/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.9.11 h1:Cus1Lu/t7d3OG6VF4aYWvlUUS0Q4O1/lcpPNJZ0jsw0= -go.mau.fi/util v0.9.11/go.mod h1:xunp/oIQfFD68HHcNHfG0pOiHkvEtDhTweeIwKJ//+Q= +go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391 h1:lsvBEY8MJfYdV61YbwikiQvb0Al/onbmLW5wfl/0tag= +go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391/go.mod h1:xunp/oIQfFD68HHcNHfG0pOiHkvEtDhTweeIwKJ//+Q= 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-20260716095330-85d99080dee8 h1:7RQA3v4pCZcmgHaEQXKfHKLVSqPThizkApt6Uw+DcA8= -go.mau.fi/whatsmeow v0.0.0-20260716095330-85d99080dee8/go.mod h1:SX7VdCALDRNx7HZ7mqZxjyi3U7N0QtbSXXXTl8rG5S4= +go.mau.fi/whatsmeow v0.0.0-20260718134955-fac667d55293 h1:tQ2HAlFBB6KidkNqnTHvQ67WLZPNEZbjAQKviAHJr/M= +go.mau.fi/whatsmeow v0.0.0-20260718134955-fac667d55293/go.mod h1:Iy/xVSuVU2payR26MB1hv0UZUWRraEn4qKZ7+VRHulg= 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.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= @@ -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.29.0 h1:OkcBJF1dvp+93EgahxMxOUZZOrGTYculI9IprvRIMOQ= -maunium.net/go/mautrix v0.29.0/go.mod h1:LynuVr8N9nWsE1N4WAE+vItRACDB1pt9M3gN4SIBpeY= +maunium.net/go/mautrix v0.29.1-0.20260719130752-5743d9b6f27e h1:tPGnL/s5dfqhNoVLvmCKY3V60migQsNHBXAhUALBSd8= +maunium.net/go/mautrix v0.29.1-0.20260719130752-5743d9b6f27e/go.mod h1:PeLuIih5jnbwb2xKNNi0Te7AtINEQE2Uv3p6iG3RV80= diff --git a/pkg/connector/wadb/upgrades/upgrades.go b/pkg/connector/wadb/upgrades/upgrades.go index 22c5414..5547982 100644 --- a/pkg/connector/wadb/upgrades/upgrades.go +++ b/pkg/connector/wadb/upgrades/upgrades.go @@ -6,11 +6,9 @@ import ( "go.mau.fi/util/dbutil" ) -var Table dbutil.UpgradeTable - //go:embed *.sql var rawUpgrades embed.FS -func init() { - Table.RegisterFS(rawUpgrades) -} +var Table = dbutil.BuildUpgradeTable(). + WithFS(rawUpgrades). + Finish() From 5affed71d30ad7dea93435458a3d4f51dec385a7 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 20 Jul 2026 17:00:59 +0300 Subject: [PATCH 74/94] 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 a4e7f07..0f147f9 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260718134955-fac667d55293 + go.mau.fi/whatsmeow v0.0.0-20260720135917-a2381054887e golang.org/x/image v0.44.0 golang.org/x/net v0.57.0 golang.org/x/sync v0.22.0 diff --git a/go.sum b/go.sum index 0786304..8138dfe 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391 h1:lsvBEY8MJfYdV61YbwikiQvb go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391/go.mod h1:xunp/oIQfFD68HHcNHfG0pOiHkvEtDhTweeIwKJ//+Q= 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-20260718134955-fac667d55293 h1:tQ2HAlFBB6KidkNqnTHvQ67WLZPNEZbjAQKviAHJr/M= -go.mau.fi/whatsmeow v0.0.0-20260718134955-fac667d55293/go.mod h1:Iy/xVSuVU2payR26MB1hv0UZUWRraEn4qKZ7+VRHulg= +go.mau.fi/whatsmeow v0.0.0-20260720135917-a2381054887e h1:Kn2XQTpYRploXqtmJKjeX4ZYGevhIeQkO2PWGPGdwrY= +go.mau.fi/whatsmeow v0.0.0-20260720135917-a2381054887e/go.mod h1:Iy/xVSuVU2payR26MB1hv0UZUWRraEn4qKZ7+VRHulg= 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.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= From 28b69e99f752c8593ab959e0780f5352145898c4 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 22 Jul 2026 22:27:42 +0300 Subject: [PATCH 75/94] main: remove option to load mclient from plugin --- cmd/mautrix-whatsapp/plugin.go | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 cmd/mautrix-whatsapp/plugin.go diff --git a/cmd/mautrix-whatsapp/plugin.go b/cmd/mautrix-whatsapp/plugin.go deleted file mode 100644 index a1c9ca0..0000000 --- a/cmd/mautrix-whatsapp/plugin.go +++ /dev/null @@ -1,24 +0,0 @@ -//go:build amd64 && cgo && !noplugin - -package main - -import ( - "fmt" - "os" - "plugin" - - "go.mau.fi/util/exerrors" - - "go.mau.fi/mautrix-whatsapp/pkg/connector" -) - -func init() { - path := os.Getenv("WM_PLUGIN_PATH") - if path == "" { - return - } - fmt.Println("Loading plugin from", path) - plug := exerrors.Must(plugin.Open(path)) - sym := exerrors.Must(plug.Lookup("NewClient")) - connector.NewMC = sym.(connector.NewMCFunc) -} From 09ec71d21a20666fe4bbacd27e42e530528b76a9 Mon Sep 17 00:00:00 2001 From: Bradley Birch Date: Thu, 23 Jul 2026 14:46:22 +0100 Subject: [PATCH 76/94] capabilities: add more details for polls (#941) --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/capabilities.go | 39 ++++++++++++++++++++--------------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 0f147f9..2aa402f 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.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.29.1-0.20260719130752-5743d9b6f27e + maunium.net/go/mautrix v0.29.1-0.20260723095015-f7cfa8766d2b ) require ( diff --git a/go.sum b/go.sum index 8138dfe..388c420 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.29.1-0.20260719130752-5743d9b6f27e h1:tPGnL/s5dfqhNoVLvmCKY3V60migQsNHBXAhUALBSd8= -maunium.net/go/mautrix v0.29.1-0.20260719130752-5743d9b6f27e/go.mod h1:PeLuIih5jnbwb2xKNNi0Te7AtINEQE2Uv3p6iG3RV80= +maunium.net/go/mautrix v0.29.1-0.20260723095015-f7cfa8766d2b h1:5ZKdE95/xcAGDUtS7IYhaa/ENdEmedG5CEjkgkzu6H8= +maunium.net/go/mautrix v0.29.1-0.20260723095015-f7cfa8766d2b/go.mod h1:PeLuIih5jnbwb2xKNNi0Te7AtINEQE2Uv3p6iG3RV80= diff --git a/pkg/connector/capabilities.go b/pkg/connector/capabilities.go index 3e6f658..152e7ad 100644 --- a/pkg/connector/capabilities.go +++ b/pkg/connector/capabilities.go @@ -52,7 +52,7 @@ func (wa *WhatsAppConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilit } func (wa *WhatsAppConnector) GetBridgeInfoVersion() (info, caps int) { - return 1, 8 + return 1, 9 } const WAMaxFileSize = 2000 * 1024 * 1024 @@ -67,7 +67,7 @@ func supportedIfFFmpeg() event.CapabilitySupportLevel { } func capID() string { - base := "fi.mau.whatsapp.capabilities.2026_05_12" + base := "fi.mau.whatsapp.capabilities.2026_07_22" if ffmpeg.Supported() { return base + "+ffmpeg" } @@ -174,21 +174,26 @@ var whatsappCaps = &event.RoomFeatures{ event.MemberActionKick: event.CapLevelFullySupported, event.MemberActionLeave: event.CapLevelFullySupported, }, - MaxTextLength: MaxTextLength, - LocationMessage: event.CapLevelFullySupported, - Poll: event.CapLevelFullySupported, - Reply: event.CapLevelFullySupported, - Edit: event.CapLevelFullySupported, - EditMaxAge: ptr.Ptr(jsontime.S(EditMaxAge)), - Delete: event.CapLevelFullySupported, - DeleteForMe: false, - DeleteMaxAge: ptr.Ptr(jsontime.S(2 * 24 * time.Hour)), - Reaction: event.CapLevelFullySupported, - ReactionCount: 1, - ReadReceipts: true, - TypingNotifications: true, - DisappearingTimer: waDisappearingCap, - DeleteChat: true, + MaxTextLength: MaxTextLength, + LocationMessage: event.CapLevelFullySupported, + Poll: event.CapLevelFullySupported, + PollEnd: event.CapLevelUnsupported, + PollHiddenVotes: event.CapLevelUnsupported, + PollDuplicateOptions: event.CapLevelUnsupported, + PollMaxOptions: 12, + PollOptionMaxLength: 100, + Reply: event.CapLevelFullySupported, + Edit: event.CapLevelFullySupported, + EditMaxAge: ptr.Ptr(jsontime.S(EditMaxAge)), + Delete: event.CapLevelFullySupported, + DeleteForMe: false, + DeleteMaxAge: ptr.Ptr(jsontime.S(2 * 24 * time.Hour)), + Reaction: event.CapLevelFullySupported, + ReactionCount: 1, + ReadReceipts: true, + TypingNotifications: true, + DisappearingTimer: waDisappearingCap, + DeleteChat: true, } var whatsappDMCaps *event.RoomFeatures From 911f5a1568c400b872866323960a9b008d4510e5 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 4 Aug 2026 18:10:24 +0300 Subject: [PATCH 77/94] directmedia: use more generic keys for re-requesting media --- pkg/connector/directmedia.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index 89a54fc..8a7afd9 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -52,7 +52,7 @@ func (wa *WhatsAppConnector) SetUseDirectMedia() { } var ErrReloadNeeded = mautrix.RespError{ - ErrCode: "FI.MAU.WHATSAPP_RELOAD_NEEDED", + ErrCode: "COM.BEEPER.MEDIA_RELOAD_NEEDED", Err: "Media is no longer available on WhatsApp servers and must be re-requested from your phone", StatusCode: http.StatusNotFound, } @@ -212,7 +212,7 @@ func (wa *WhatsAppConnector) makeDirectMediaResponse( log := zerolog.Ctx(ctx) err := waClient.Client.DownloadToFile(ctx, dm, f) if keys != nil && (errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith403) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith404) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith410) || errors.Is(err, whatsmeow.ErrNoURLPresent)) { - val := params["fi.mau.whatsapp.reload_media"] + val := params["com.beeper.interactive_download_request"] if val == "false" || (!wa.Config.DirectMediaAutoRequest && val != "true") { return nil, ErrReloadNeeded } From 3ec28346c879a682e211d677ede41f75ee06ca3e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 5 Aug 2026 17:08:20 +0300 Subject: [PATCH 78/94] all: switch DMs to use LIDs (#944) --- go.mod | 4 +- go.sum | 8 +- pkg/connector/backfill.go | 60 +++-- pkg/connector/chatinfo.go | 34 ++- pkg/connector/client.go | 16 +- pkg/connector/connector.go | 81 +------ pkg/connector/events.go | 91 ++++---- pkg/connector/handlematrix.go | 22 +- pkg/connector/handlewhatsapp.go | 189 ++++++++-------- pkg/connector/id.go | 23 +- pkg/connector/lidmigrate.go | 212 ++++++++++++++++++ pkg/connector/mediarequest.go | 4 +- pkg/connector/startchat.go | 53 +++-- .../wadb/upgrades/00-latest-schema.sql | 2 +- .../upgrades/08-may-need-lid-dm-deletion.sql | 2 - .../09-may-need-lid-dm-deletion-again.sql | 3 - .../10-history-sync-conversation-to-lid.sql | 8 + pkg/msgconv/from-matrix.go | 2 +- pkg/msgconv/from-whatsapp.go | 41 ++-- pkg/msgconv/wa-poll.go | 44 +--- pkg/waid/id.go | 7 + 21 files changed, 563 insertions(+), 343 deletions(-) create mode 100644 pkg/connector/lidmigrate.go delete mode 100644 pkg/connector/wadb/upgrades/08-may-need-lid-dm-deletion.sql delete mode 100644 pkg/connector/wadb/upgrades/09-may-need-lid-dm-deletion-again.sql create mode 100644 pkg/connector/wadb/upgrades/10-history-sync-conversation-to-lid.sql diff --git a/go.mod b/go.mod index 2aa402f..9521937 100644 --- a/go.mod +++ b/go.mod @@ -12,13 +12,13 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260720135917-a2381054887e + go.mau.fi/whatsmeow v0.0.0-20260805124304-6723e556f35a golang.org/x/image v0.44.0 golang.org/x/net v0.57.0 golang.org/x/sync v0.22.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.29.1-0.20260723095015-f7cfa8766d2b + maunium.net/go/mautrix v0.29.1-0.20260804113340-56938b8a508d ) require ( diff --git a/go.sum b/go.sum index 388c420..322636b 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391 h1:lsvBEY8MJfYdV61YbwikiQvb go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391/go.mod h1:xunp/oIQfFD68HHcNHfG0pOiHkvEtDhTweeIwKJ//+Q= 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-20260720135917-a2381054887e h1:Kn2XQTpYRploXqtmJKjeX4ZYGevhIeQkO2PWGPGdwrY= -go.mau.fi/whatsmeow v0.0.0-20260720135917-a2381054887e/go.mod h1:Iy/xVSuVU2payR26MB1hv0UZUWRraEn4qKZ7+VRHulg= +go.mau.fi/whatsmeow v0.0.0-20260805124304-6723e556f35a h1:TTRND9Dvq7YfT9vwJVQK4Zrit/sm3FHE74B5FWe8CgA= +go.mau.fi/whatsmeow v0.0.0-20260805124304-6723e556f35a/go.mod h1:Iy/xVSuVU2payR26MB1hv0UZUWRraEn4qKZ7+VRHulg= 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.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= @@ -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.29.1-0.20260723095015-f7cfa8766d2b h1:5ZKdE95/xcAGDUtS7IYhaa/ENdEmedG5CEjkgkzu6H8= -maunium.net/go/mautrix v0.29.1-0.20260723095015-f7cfa8766d2b/go.mod h1:PeLuIih5jnbwb2xKNNi0Te7AtINEQE2Uv3p6iG3RV80= +maunium.net/go/mautrix v0.29.1-0.20260804113340-56938b8a508d h1:jbC6tXphot/X1UngnZLv7LcDTzaHBi2phwxrpyhtfrs= +maunium.net/go/mautrix v0.29.1-0.20260804113340-56938b8a508d/go.mod h1:PeLuIih5jnbwb2xKNNi0Te7AtINEQE2Uv3p6iG3RV80= diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index dd04696..65e8f72 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -228,18 +228,18 @@ func (wa *WhatsAppClient) handleWAHistorySync( } else { totalMessageCount += len(conv.GetMessages()) } - if jid.Server == types.HiddenUserServer { - pn, err := wa.GetStore().LIDs.GetPNForLID(ctx, jid) + if jid.Server == types.DefaultUserServer { + lid, err := wa.GetStore().LIDs.GetLIDForPN(ctx, jid) if err != nil { - log.Err(err).Stringer("lid", jid).Msg("Failed to get PN for LID in history sync") - } else if pn.IsEmpty() { - log.Warn().Stringer("lid", jid).Msg("No PN found for LID in history sync") + log.Err(err).Stringer("pn", jid).Msg("Failed to get LID for phone number in history sync") + } else if lid.IsEmpty() { + log.Warn().Stringer("pn", jid).Msg("No LID found for phone number in history sync") } else { log.Debug(). - Stringer("lid", jid). - Stringer("pn", pn). - Msg("Rerouting LID DM to phone number in history sync") - jid = pn + Stringer("lid", lid). + Stringer("pn", jid). + Msg("Rerouting phone number DM to LID in history sync") + jid = lid } } log.UpdateContext(func(c zerolog.Context) zerolog.Context { @@ -474,6 +474,9 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet if err != nil { return nil, err } + if portalJID.Server == types.DefaultUserServer { + zerolog.Ctx(ctx).Warn().Stringer("portal_jid", portalJID).Msg("FetchMessages called for phone number portal") + } var markRead bool var startTime, endTime *time.Time var conv *wadb.Conversation @@ -528,7 +531,7 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet Forward: params.Forward, }, nil } else if hasMore { - return wa.fetchMessagesFromPhone(ctx, params) + return wa.fetchMessagesFromPhone(ctx, portalJID, params) } return &bridgev2.FetchMessagesResponse{ HasMore: false, @@ -617,6 +620,9 @@ func (wa *WhatsAppClient) convertHistorySyncMessages( continue } } + if !wa.ensureAltJIDs(ctx, &evt.Info.MessageSource, false) { + return nil, fmt.Errorf("failed to ensure alt JIDs for message %s", evt.Info.ID) + } isViewOnce := evt.IsViewOnce || evt.IsViewOnceV2 || evt.IsViewOnceV2Extension converted, mediaReq := wa.convertHistorySyncMessage( ctx, portal, &evt.Info, evt.Message, evt.RawMessage, isViewOnce, msg.Reactions, @@ -651,7 +657,7 @@ func (wa *WhatsAppClient) convertHistorySyncMessages( }, nil } -func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, params bridgev2.FetchMessagesParams) (*bridgev2.FetchMessagesResponse, error) { +func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, portalJID types.JID, params bridgev2.FetchMessagesParams) (*bridgev2.FetchMessagesResponse, error) { if params.AnchorMessage == nil { return nil, fmt.Errorf("anchor message is required to fetch messages from phone") } @@ -663,9 +669,9 @@ func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, params bri msgID := wa.Client.GenerateMessageID() reqData := wa.Client.BuildHistorySyncRequest(&types.MessageInfo{ MessageSource: types.MessageSource{ - Chat: parsed.Chat, + Chat: portalJID, Sender: parsed.Sender, - IsFromMe: parsed.Sender.ToNonAD() == wa.JID.ToNonAD() || parsed.Sender.ToNonAD() == wa.Device.GetLID().ToNonAD(), + IsFromMe: wa.IsOwnJID(parsed.Sender), IsGroup: parsed.Chat.Server == types.GroupServer, }, ID: parsed.ID, @@ -673,6 +679,7 @@ func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, params bri }, 50) zerolog.Ctx(ctx).Debug(). Str("request_msg_id", msgID). + Stringer("portal_jid", portalJID). Any("anchor_msg_parsed", parsed). Any("request_data", reqData). Msg("Sending history sync request") @@ -701,6 +708,20 @@ func (wa *WhatsAppClient) handleOnDemandHistorySync(ctx context.Context, blob *w zerolog.Ctx(ctx).Err(err).Str("jid", conv.GetID()).Msg("Failed to parse portal JID") continue } + if portalJID.Server == types.DefaultUserServer { + lid, err := wa.GetStore().LIDs.GetLIDForPN(ctx, portalJID) + if err != nil { + zerolog.Ctx(ctx).Err(err).Stringer("lid", portalJID).Msg("Failed to get LID for phone number in on-demand history sync") + } else if lid.IsEmpty() { + zerolog.Ctx(ctx).Warn().Stringer("lid", portalJID).Msg("No LID found for phone number in on-demand history sync") + } else { + zerolog.Ctx(ctx).Debug(). + Stringer("lid", lid). + Stringer("pn", portalJID). + Msg("Rerouting phone number DM to LID in on-demand history sync") + portalJID = lid + } + } portal, err := wa.Main.Bridge.GetPortalByKey(ctx, wa.makeWAPortalKey(portalJID)) if err != nil { zerolog.Ctx(ctx).Err(err).Stringer("portal_jid", portalJID).Msg("Failed to get portal for on-demand history sync") @@ -749,11 +770,12 @@ func (wa *WhatsAppClient) convertHistorySyncMessage( } // TODO use proper intent intent := wa.Main.Bridge.Bot + msgID := waid.MakeMessageIDWithAltSender(info.Chat, info.Sender, info.SenderAlt, info.ID) wrapped := &bridgev2.BackfillMessage{ - ConvertedMessage: wa.Main.MsgConv.ToMatrix(ctx, portal, wa.Client, intent, msg, rawMsg, info, nil, isViewOnce, true, nil), - Sender: wa.makeEventSender(ctx, info.Sender), - ID: waid.MakeMessageID(info.Chat, info.Sender, info.ID), - TxnID: networkid.TransactionID(waid.MakeMessageID(info.Chat, info.Sender, info.ID)), + ConvertedMessage: wa.Main.MsgConv.ToMatrix(ctx, portal, wa.Client, intent, msg, rawMsg, info, isViewOnce, true, nil), + Sender: wa.makeEventSender(ctx, pickLID(info.Sender, info.SenderAlt)), + ID: msgID, + TxnID: networkid.TransactionID(msgID), Timestamp: info.Timestamp, StreamOrder: info.Timestamp.Unix(), Reactions: make([]*bridgev2.BackfillReaction, 0, len(reactions)), @@ -762,10 +784,10 @@ func (wa *WhatsAppClient) convertHistorySyncMessage( for _, reaction := range reactions { var sender types.JID if reaction.GetKey().GetFromMe() { - sender = wa.JID + sender = wa.GetLID() } else if reaction.GetKey().GetParticipant() != "" { sender, _ = types.ParseJID(*reaction.Key.Participant) - } else if info.Chat.Server == types.DefaultUserServer || info.Chat.Server == types.BotServer { + } else if info.Chat.Server == types.DefaultUserServer || info.Chat.Server == types.HiddenUserServer || info.Chat.Server == types.BotServer { sender = info.Chat } if sender.IsEmpty() { diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 7ffbb14..1dbebcc 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -99,6 +99,18 @@ func (wa *WhatsAppClient) applyChatSettings(ctx context.Context, chatID types.JI zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to get chat settings") return } + if !chat.Found { + chatID, err = wa.GetStore().GetAltJID(ctx, chatID) + if err != nil { + zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to get alternate JID to get chat settings") + return + } + chat, err = wa.GetStore().ChatSettings.GetChatSettings(ctx, chatID) + if err != nil { + zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to get chat settings with alternate JID") + return + } + } info.UserLocal = &bridgev2.UserLocalPortalInfo{ MutedUntil: ptr.Ptr(chat.MutedUntil), } @@ -141,15 +153,20 @@ const PrivateChatTopic = "WhatsApp private chat" const BotChatTopic = "WhatsApp chat with a bot" func (wa *WhatsAppClient) wrapDMInfo(ctx context.Context, jid types.JID) *bridgev2.ChatInfo { + ownID := wa.JID + if jid.Server == types.HiddenUserServer { + ownID = wa.GetLID() + } info := &bridgev2.ChatInfo{ + Type: ptr.Ptr(database.RoomTypeDM), Topic: ptr.Ptr(PrivateChatTopic), Members: &bridgev2.ChatMemberList{ IsFull: true, TotalMemberCount: 2, OtherUserID: waid.MakeUserID(jid), MemberMap: map[networkid.UserID]bridgev2.ChatMember{ - waid.MakeUserID(jid): {EventSender: wa.makeEventSender(ctx, jid)}, - waid.MakeUserID(wa.JID): {EventSender: wa.makeEventSender(ctx, wa.JID)}, + waid.MakeUserID(jid): {EventSender: wa.makeEventSender(ctx, jid)}, + waid.MakeUserID(ownID): {EventSender: wa.makeEventSender(ctx, ownID)}, }, PowerLevels: &bridgev2.PowerLevelOverrides{ Events: map[event.Type]int{ @@ -159,13 +176,14 @@ func (wa *WhatsAppClient) wrapDMInfo(ctx context.Context, jid types.JID) *bridge event.StateBeeperDisappearingTimer: 0, }, }, + ExcludeChangesFromTimeline: true, }, - Type: ptr.Ptr(database.RoomTypeDM), + ExcludeChangesFromTimeline: true, } if jid.Server == types.BotServer { info.Topic = ptr.Ptr(BotChatTopic) } - if jid == wa.JID.ToNonAD() { + if wa.IsOwnJID(jid) { // For chats with self, force-split the members so the user's own ghost is always in the room. info.Members.MemberMap = map[networkid.UserID]bridgev2.ChatMember{ waid.MakeUserID(jid): {EventSender: bridgev2.EventSender{Sender: waid.MakeUserID(jid)}}, @@ -189,7 +207,7 @@ func (wa *WhatsAppClient) wrapStatusBroadcastInfo(ctx context.Context) *bridgev2 Members: &bridgev2.ChatMemberList{ IsFull: false, MemberMap: map[networkid.UserID]bridgev2.ChatMember{ - waid.MakeUserID(wa.JID): {EventSender: wa.makeEventSender(ctx, wa.JID)}, + waid.MakeUserID(wa.GetLID()): {EventSender: wa.makeEventSender(ctx, wa.GetLID())}, }, }, Type: ptr.Ptr(database.RoomTypeDefault), @@ -316,7 +334,7 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn } } if info.IsParent && !hasSelf && info.AddressingMode == types.AddressingModeLID { - wrapped.Members.MemberMap.Add(bridgev2.ChatMember{EventSender: wa.makeEventSender(ctx, wa.Device.LID)}) + wrapped.Members.MemberMap.Add(bridgev2.ChatMember{EventSender: wa.makeEventSender(ctx, wa.GetLID())}) } if !info.LinkedParentJID.IsEmpty() { @@ -523,8 +541,8 @@ func (wa *WhatsAppClient) wrapNewsletterInfo(ctx context.Context, info *types.Ne Members: &bridgev2.ChatMemberList{ TotalMemberCount: info.ThreadMeta.SubscriberCount, MemberMap: map[networkid.UserID]bridgev2.ChatMember{ - waid.MakeUserID(wa.JID): { - EventSender: wa.makeEventSender(ctx, wa.JID), + waid.MakeUserID(wa.GetLID()): { + EventSender: wa.makeEventSender(ctx, wa.GetLID()), PowerLevel: &ownPowerLevel, }, }, diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 3f19bf7..c5a9348 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -70,6 +70,7 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. if err != nil { return err } + w.LID = w.Device.GetLID() if w.Device != nil { log := w.UserLogin.Log.With().Str("component", "whatsmeow").Logger() @@ -104,6 +105,7 @@ type WhatsAppClient struct { Client *whatsmeow.Client Device *store.Device JID types.JID + LID types.JID MC mClient historySyncWakeup chan struct{} @@ -185,7 +187,19 @@ func (wa *WhatsAppClient) RegisterPushNotifications(ctx context.Context, pushTyp } func (wa *WhatsAppClient) IsThisUser(_ context.Context, userID networkid.UserID) bool { - return userID == waid.MakeUserID(wa.JID) + return userID == waid.MakeUserID(wa.JID) || userID == waid.MakeUserID(wa.GetLID()) +} + +func (wa *WhatsAppClient) IsOwnJID(jid types.JID) bool { + return (jid.Server == types.DefaultUserServer && jid.User == wa.JID.User) || + (jid.Server == types.HiddenUserServer && jid.User == wa.GetLID().User) +} + +func (wa *WhatsAppClient) GetLID() types.JID { + if wa.LID.IsEmpty() && !wa.JID.IsEmpty() { + wa.LID = wa.GetStore().GetLID() + } + return wa.LID } func (wa *WhatsAppClient) Connect(ctx context.Context) { diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 91b1e11..e262c82 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -28,27 +28,24 @@ import ( "time" "github.com/lib/pq" - "github.com/rs/zerolog" "go.mau.fi/util/dbutil" + "go.mau.fi/util/exsync" "go.mau.fi/util/random" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waCompanionReg" "go.mau.fi/whatsmeow/store" "go.mau.fi/whatsmeow/store/sqlstore" whatsmeowUpgrades "go.mau.fi/whatsmeow/store/sqlstore/upgrades" - "go.mau.fi/whatsmeow/types" waLog "go.mau.fi/whatsmeow/util/log" "google.golang.org/protobuf/proto" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/commands" - "maunium.net/go/mautrix/bridgev2/database" "maunium.net/go/mautrix/bridgev2/networkid" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" "go.mau.fi/mautrix-whatsapp/pkg/connector/wadb" "go.mau.fi/mautrix-whatsapp/pkg/msgconv" - "go.mau.fi/mautrix-whatsapp/pkg/waid" ) type WhatsAppConnector struct { @@ -64,6 +61,8 @@ type WhatsAppConnector struct { mediaEditCache MediaEditCache mediaEditCacheLock sync.RWMutex stopMediaEditCacheLoop atomic.Pointer[context.CancelFunc] + + unmigratedDMs *exsync.Set[networkid.PortalKey] } func init() { @@ -114,6 +113,7 @@ func (wa *WhatsAppConnector) Init(bridge *bridgev2.Bridge) { cmdAccept, cmdSync, cmdInviteLink, cmdResolveLink, cmdJoin, ) wa.mediaEditCache = make(MediaEditCache) + wa.unmigratedDMs = exsync.NewSet[networkid.PortalKey]() whatsmeowDBLog := bridge.Log.With().Str("db_section", "whatsmeow").Logger() wa.DeviceStore = sqlstore.NewWithWrappedDB( @@ -157,78 +157,7 @@ func (wa *WhatsAppConnector) Start(ctx context.Context) error { return bridgev2.DBUpgradeError{Err: err, Section: "whatsapp"} } - if !wa.Bridge.Background && wa.Bridge.DB.KV.Get(ctx, "whatsapp_lid_dms_deleted") == "false" { - wa.deleteLIDDMsMigration(ctx) - } - - return nil -} - -func (wa *WhatsAppConnector) deleteLIDDMsMigration(ctx context.Context) { - log := zerolog.Ctx(ctx).With().Str("action", "delete lid dms").Logger() - portals, err := wa.Bridge.GetAllPortalsWithMXID(ctx) - if err != nil { - log.Err(err).Msg("Failed to get portals for LID DM deletion") - return - } - defer wa.Bridge.DB.KV.Set(ctx, "whatsapp_lid_dms_deleted", "true") - if len(portals) == 0 { - log.Debug().Msg("No portals found") - return - } - portalsByKey := make(map[networkid.PortalKey]*bridgev2.Portal, len(portals)) - for _, p := range portals { - if p.Receiver == "" || p.RoomType != database.RoomTypeDM { - continue - } - portalsByKey[p.PortalKey] = p - } - _, err = wa.DB.Exec(ctx, "DELETE FROM whatsapp_history_sync_conversation WHERE chat_jid LIKE '%@lid'") - if err != nil { - log.Err(err).Msg("Failed to remove LID conversations from history sync") - } - for key, portal := range portalsByKey { - parsedID, err := waid.ParsePortalID(key.ID) - if err != nil { - log.Warn().Err(err).Str("portal_id", string(key.ID)).Msg("Failed to parse portal ID") - continue - } else if parsedID.Server != types.HiddenUserServer { - continue - } - var pnStr string - err = wa.DB.QueryRow(ctx, "SELECT pn FROM whatsmeow_lid_map WHERE lid=$1", parsedID.User).Scan(&pnStr) - if err != nil { - log.Warn().Err(err).Str("portal_id", string(key.ID)).Msg("Failed to get PN for LID portal") - continue - } - key.ID = waid.MakePortalID(types.JID{User: pnStr, Server: types.DefaultUserServer}) - _, pnPortalExists := portalsByKey[key] - if !pnPortalExists { - log.Warn().Str("portal_id", string(key.ID)).Msg("PN portal does not exist, not deleting LID DM") - continue - } - err = portal.Delete(ctx) - if err != nil { - log.Err(err). - Object("portal_key", portal.PortalKey). - Stringer("portal_mxid", portal.MXID). - Msg("Failed to delete LID DM portal from database") - continue - } - err = wa.Bridge.Bot.DeleteRoom(ctx, portal.MXID, false) - if err != nil { - log.Err(err). - Object("portal_key", portal.PortalKey). - Stringer("portal_mxid", portal.MXID). - Msg("Failed to delete LID DM portal from Matrix") - continue - } - log.Debug(). - Object("portal_key", portal.PortalKey). - Stringer("portal_mxid", portal.MXID). - Msg("Deleted LID DM portal") - } - log.Info().Msg("Finished deleting LID DM portals") + return wa.migrateToLIDDMs(ctx) } func (wa *WhatsAppConnector) Stop() { diff --git a/pkg/connector/events.go b/pkg/connector/events.go index cb51e64..1f4e118 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -39,22 +39,9 @@ import ( "go.mau.fi/mautrix-whatsapp/pkg/waid" ) -func (wa *WhatsAppClient) getPortalKeyByMessageSource(ms types.MessageSource) networkid.PortalKey { - jid := ms.Chat - if ms.IsIncomingBroadcast() { - if ms.IsFromMe { - jid = ms.BroadcastListOwner.ToNonAD() - } else { - jid = ms.Sender.ToNonAD() - } - } - return wa.makeWAPortalKey(jid) -} - type MessageInfoWrapper struct { - OrigSource types.MessageSource - Info types.MessageInfo - wa *WhatsAppClient + Info types.MessageInfo + wa *WhatsAppClient } func (evt *MessageInfoWrapper) ShouldCreatePortal() bool { @@ -62,7 +49,26 @@ func (evt *MessageInfoWrapper) ShouldCreatePortal() bool { } func (evt *MessageInfoWrapper) GetPortalKey() networkid.PortalKey { - return evt.wa.getPortalKeyByMessageSource(evt.Info.MessageSource) + ms := evt.Info.MessageSource + jid := ms.Chat + if ms.IsIncomingBroadcast() { + if ms.IsFromMe { + // TODO can this still be a phone number? + jid = ms.BroadcastListOwner.ToNonAD() + } else { + jid = ms.Sender.ToNonAD() + if jid.Server == types.DefaultUserServer && !ms.SenderAlt.IsEmpty() { + jid = ms.SenderAlt.ToNonAD() + } + } + } else if jid.Server == types.DefaultUserServer { + if !ms.IsFromMe && ms.Chat.ToNonAD() == ms.Sender.ToNonAD() && !ms.SenderAlt.IsEmpty() { + jid = ms.SenderAlt.ToNonAD() + } else if !ms.RecipientAlt.IsEmpty() { + jid = ms.RecipientAlt.ToNonAD() + } + } + return evt.wa.makeWAPortalKey(jid) } func (evt *MessageInfoWrapper) AddLogContext(c zerolog.Context) zerolog.Context { @@ -73,12 +79,19 @@ func (evt *MessageInfoWrapper) GetTimestamp() time.Time { return evt.Info.Timestamp } +func pickLID(main, alt types.JID) types.JID { + if main.Server == types.DefaultUserServer && alt.Server == types.HiddenUserServer { + return alt + } + return main +} + func (evt *MessageInfoWrapper) GetSender() bridgev2.EventSender { - return evt.wa.makeEventSender(evt.wa.Main.Bridge.BackgroundCtx, evt.Info.Sender) + return evt.wa.makeEventSender(evt.wa.Main.Bridge.BackgroundCtx, pickLID(evt.Info.Sender, evt.Info.SenderAlt)) } func (evt *MessageInfoWrapper) GetID() networkid.MessageID { - return waid.MakeMessageID(evt.Info.Chat, evt.Info.Sender, evt.Info.ID) + return waid.MakeMessageIDWithAltSender(evt.Info.Chat, evt.Info.Sender, evt.Info.SenderAlt, evt.Info.ID) } func (evt *MessageInfoWrapper) GetTransactionID() networkid.TransactionID { @@ -135,14 +148,6 @@ func (evt *WAMessageEvent) PreHandle(ctx context.Context, portal *bridgev2.Porta return } meta := portal.Metadata.(*waid.PortalMetadata) - if meta.AddressingMode == types.AddressingModeLID && evt.Info.Sender.Server == types.DefaultUserServer { - evt.Info.Sender, evt.Info.SenderAlt = evt.Info.SenderAlt, evt.Info.Sender - zerolog.Ctx(ctx).Debug(). - Stringer("lid", evt.Info.Sender). - Stringer("pn", evt.Info.SenderAlt). - Str("message_id", evt.Info.ID). - Msg("Forced phone number sender to LID in group message") - } if meta.AddressingMode == types.AddressingModeLID || meta.LIDMigrationAttempted { return } @@ -161,13 +166,6 @@ func (evt *WAMessageEvent) PreHandle(ctx context.Context, portal *bridgev2.Porta log.Info().Msg("Resyncing group members as it appears to have switched to LID addressing mode") portal.UpdateInfo(ctx, evt.wa.wrapGroupInfo(ctx, info), evt.wa.UserLogin, nil, time.Time{}) log.Debug().Msg("Finished resyncing after LID change") - if evt.Info.Sender.Server == types.DefaultUserServer && evt.Info.SenderAlt.Server == types.HiddenUserServer { - evt.Info.Sender, evt.Info.SenderAlt = evt.Info.SenderAlt, evt.Info.Sender - log.Debug(). - Stringer("new_sender", evt.Info.Sender). - Stringer("new_sender_alt", evt.Info.SenderAlt). - Msg("Overriding sender to LID after resyncing group members") - } } func (evt *WAMessageEvent) PostHandle(ctx context.Context, portal *bridgev2.Portal) { @@ -201,7 +199,7 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por ctx = context.WithValue(ctx, msgconv.ContextKeyEditTargetID, evt.Message.GetProtocolMessage().GetKey().GetID()) cm := evt.wa.Main.MsgConv.ToMatrix( - ctx, portal, evt.wa.Client, intent, editedMsg, evt.MsgEvent.RawMessage, &evt.Info, &evt.OrigSource, evt.isViewOnce(), false, previouslyConvertedPart, + ctx, portal, evt.wa.Client, intent, editedMsg, evt.MsgEvent.RawMessage, &evt.Info, evt.isViewOnce(), false, previouslyConvertedPart, ) if evt.isUndecryptableUpsertSubEvent && isFailedMedia(cm) { evt.postHandle = func() { @@ -286,7 +284,7 @@ func (evt *WAMessageEvent) HandleExisting(ctx context.Context, portal *bridgev2. func (evt *WAMessageEvent) ConvertMessage(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI) (*bridgev2.ConvertedMessage, error) { evt.wa.EnqueuePortalResync(portal, false) converted := evt.wa.Main.MsgConv.ToMatrix( - ctx, portal, evt.wa.Client, intent, evt.Message, evt.MsgEvent.RawMessage, &evt.Info, &evt.OrigSource, evt.isViewOnce(), false, nil, + ctx, portal, evt.wa.Client, intent, evt.Message, evt.MsgEvent.RawMessage, &evt.Info, evt.isViewOnce(), false, nil, ) if isFailedMedia(converted) { evt.postHandle = func() { @@ -392,7 +390,9 @@ func (evt *WAUndecryptableMessage) GetStreamOrder() int64 { type WAMediaRetry struct { *events.MediaRetry - wa *WhatsAppClient + wa *WhatsAppClient + senderLID types.JID + chatLID types.JID } func (evt *WAMediaRetry) GetType() bridgev2.RemoteEventType { @@ -400,7 +400,7 @@ func (evt *WAMediaRetry) GetType() bridgev2.RemoteEventType { } func (evt *WAMediaRetry) GetPortalKey() networkid.PortalKey { - return evt.wa.makeWAPortalKey(evt.ChatID) + return evt.wa.makeWAPortalKey(pickLID(evt.ChatID, evt.chatLID)) } func (evt *WAMediaRetry) AddLogContext(c zerolog.Context) zerolog.Context { @@ -414,16 +414,23 @@ func (evt *WAMediaRetry) AddLogContext(c zerolog.Context) zerolog.Context { func (evt *WAMediaRetry) getRealSender() types.JID { sender := evt.SenderID - if evt.FromMe { - sender = evt.wa.JID.ToNonAD() - } else if sender.IsEmpty() && (evt.ChatID.Server == types.DefaultUserServer || evt.ChatID.Server == types.BotServer) { - sender = evt.ChatID.ToNonAD() + if sender.IsEmpty() { + if evt.FromMe { + if evt.ChatID.Server == types.HiddenUserServer { + sender = evt.wa.GetLID().ToNonAD() + } else { + sender = evt.wa.JID.ToNonAD() + } + } else if evt.ChatID.Server == types.DefaultUserServer || evt.ChatID.Server == types.HiddenUserServer || evt.ChatID.Server == types.BotServer { + sender = evt.ChatID.ToNonAD() + } } return sender } func (evt *WAMediaRetry) GetSender() bridgev2.EventSender { - return evt.wa.makeEventSender(evt.wa.Main.Bridge.BackgroundCtx, evt.getRealSender()) + realSender := pickLID(evt.getRealSender(), evt.senderLID) + return evt.wa.makeEventSender(evt.wa.Main.Bridge.BackgroundCtx, realSender) } func (evt *WAMediaRetry) GetTargetMessage() networkid.MessageID { diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index 9aea18b..c55f523 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -103,11 +103,14 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg if err != nil { return nil, err } + if chatJID.Server == types.DefaultUserServer { + zerolog.Ctx(ctx).Warn().Stringer("portal_jid", chatJID).Msg("Matrix message received in phone number portal") + } if chatJID == types.StatusBroadcastJID && wa.Main.Config.DisableStatusBroadcastSend { return nil, ErrBroadcastSendDisabled } wrappedMsgID := waid.MakeMessageID(chatJID, wa.JID, req.ID) - wrappedMsgID2 := waid.MakeMessageID(chatJID, wa.GetStore().GetLID(), req.ID) + wrappedMsgID2 := waid.MakeMessageID(chatJID, wa.GetLID(), req.ID) msg.AddPendingToIgnore(networkid.TransactionID(wrappedMsgID)) msg.AddPendingToIgnore(networkid.TransactionID(wrappedMsgID2)) zerolog.Ctx(ctx).Trace().Any("payload", waMsg).Msg("Outgoing message payload") @@ -116,7 +119,7 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg return nil, err } var pickedMessageID networkid.MessageID - if resp.Sender == wa.GetStore().GetLID() && chatJID.Server != types.DefaultUserServer { + if resp.Sender == wa.GetLID() { pickedMessageID = wrappedMsgID2 msg.RemovePending(networkid.TransactionID(wrappedMsgID)) } else { @@ -137,18 +140,17 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg }, nil } -func (wa *WhatsAppClient) PreHandleMatrixReaction(_ context.Context, msg *bridgev2.MatrixReaction) (bridgev2.MatrixReactionPreResponse, error) { +func (wa *WhatsAppClient) PreHandleMatrixReaction(ctx context.Context, msg *bridgev2.MatrixReaction) (bridgev2.MatrixReactionPreResponse, error) { portalJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return bridgev2.MatrixReactionPreResponse{}, fmt.Errorf("failed to parse portal ID: %w", err) } else if portalJID == types.StatusBroadcastJID { return bridgev2.MatrixReactionPreResponse{}, ErrBroadcastReactionUnsupported } - sender := wa.JID - if portalJID.Server == types.HiddenUserServer || - msg.Portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup || - msg.Portal.Metadata.(*waid.PortalMetadata).AddressingMode == types.AddressingModeLID { - sender = wa.GetStore().GetLID() + sender := wa.GetLID() + if portalJID.Server == types.DefaultUserServer { + zerolog.Ctx(ctx).Warn().Stringer("portal_jid", portalJID).Msg("Matrix reaction received in phone number portal") + sender = wa.JID } return bridgev2.MatrixReactionPreResponse{ SenderID: waid.MakeUserID(sender), @@ -320,7 +322,7 @@ func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt * if err != nil { continue } - if parsed.Sender.User == wa.GetStore().GetLID().User || parsed.Sender.User == wa.JID.User { + if wa.IsOwnJID(parsed.Sender) { continue } var key types.JID @@ -636,7 +638,7 @@ func (wa *WhatsAppClient) getLastMessageInfo(ctx context.Context, chatJID types. lastTS = msgs[0].Timestamp parsed, _ := waid.ParseMessageID(msgs[0].ID) if parsed != nil { - fromMe := parsed.Sender.ToNonAD() == wa.JID.ToNonAD() || parsed.Sender.ToNonAD() == wa.GetStore().GetLID().ToNonAD() + fromMe := wa.IsOwnJID(parsed.Sender) var participant *string if chatJID.Server == types.GroupServer { participant = ptr.Ptr(parsed.Sender.String()) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index f12ebad..f8cee2a 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -113,8 +113,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { case *events.HistorySync: wa.UserLogin.Log.Warn().Msg("Unexpected history sync event received") case *events.MediaRetry: - wa.phoneSeen(evt.Timestamp) - success = wa.UserLogin.QueueRemoteEvent(&WAMediaRetry{MediaRetry: evt, wa: wa}).Success + success = wa.handleWAMediaRetry(ctx, evt) case *events.GroupInfo: success = wa.handleWAGroupInfoChange(ctx, evt) @@ -158,7 +157,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { if err != nil { log.Err(err).Msg("Failed to update push name in store") } - _, _, err = wa.GetStore().Contacts.PutPushName(ctx, wa.GetStore().GetLID().ToNonAD(), evt.Action.GetName()) + _, _, err = wa.GetStore().Contacts.PutPushName(ctx, wa.GetLID().ToNonAD(), evt.Action.GetName()) if err != nil { log.Err(err).Msg("Failed to update push name in store") } @@ -259,65 +258,26 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { return } -func (wa *WhatsAppClient) rerouteWAMessage(ctx context.Context, evtType string, info *types.MessageSource, msgID any) { - if (info.Chat.Server == types.HiddenUserServer || info.Chat.Server == types.BroadcastServer) && - info.Sender.Server == types.HiddenUserServer && info.SenderAlt.IsEmpty() { - info.SenderAlt, _ = wa.GetStore().LIDs.GetPNForLID(ctx, info.Sender) - } - if info.Chat.Server == types.HiddenUserServer && info.IsFromMe && info.RecipientAlt.IsEmpty() { - info.RecipientAlt, _ = wa.GetStore().LIDs.GetPNForLID(ctx, info.Chat) - } - if info.Chat.Server == types.HiddenUserServer && info.Sender.ToNonAD() == info.Chat && info.SenderAlt.Server == types.DefaultUserServer { - wa.UserLogin.Log.Debug(). - Stringer("lid", info.Sender). - Stringer("pn", info.SenderAlt). - Any("message_id", msgID). - Str("evt_type", evtType). - Msg("Forced LID DM sender to phone number in incoming message") - info.Sender, info.SenderAlt = info.SenderAlt, info.Sender - info.Chat = info.Sender.ToNonAD() - } else if info.Chat.Server == types.HiddenUserServer && info.IsFromMe && info.RecipientAlt.Server == types.DefaultUserServer { - wa.UserLogin.Log.Debug(). - Stringer("lid", info.Chat). - Stringer("pn", info.RecipientAlt). - Any("message_id", msgID). - Str("evt_type", evtType). - Msg("Forced LID DM sender to phone number in own message sent from another device") - info.Chat = info.RecipientAlt.ToNonAD() - if info.Sender.Server == types.HiddenUserServer { - info.Sender, info.SenderAlt = info.SenderAlt, info.Sender - if info.Sender.IsEmpty() { - info.Sender = wa.GetStore().GetJID() - info.Sender.Device = info.SenderAlt.Device - } - } - } else if info.Chat.Server == types.BroadcastServer && info.Sender.Server == types.HiddenUserServer && info.SenderAlt.Server == types.DefaultUserServer { - wa.UserLogin.Log.Debug(). - Stringer("lid", info.Sender). - Stringer("pn", info.SenderAlt). - Stringer("chat", info.Chat). - Any("message_id", msgID). - Str("evt_type", evtType). - Msg("Forced LID broadcast list sender to phone number in incoming message") - info.Sender, info.SenderAlt = info.SenderAlt, info.Sender - } else if info.Sender.Server == types.BotServer && info.Chat.Server == types.HiddenUserServer { - chatPN, err := wa.GetStore().LIDs.GetPNForLID(ctx, info.Chat) +func (wa *WhatsAppClient) ensureAltJIDs(ctx context.Context, info *types.MessageSource, checkPhones bool) bool { + var err error + if info.Sender.Server == types.DefaultUserServer && info.SenderAlt.IsEmpty() { + info.SenderAlt, err = wa.GetStore().LIDs.GetLIDForPN(ctx, info.Sender) if err != nil { - wa.UserLogin.Log.Err(err). - Any("message_id", msgID). - Stringer("lid", info.Chat). - Str("evt_type", evtType). - Msg("Failed to get phone number of DM for incoming bot message") - } else if !chatPN.IsEmpty() { - wa.UserLogin.Log.Debug(). - Stringer("lid", info.Chat). - Stringer("pn", chatPN). - Any("message_id", msgID). - Str("evt_type", evtType). - Msg("Forced LID chat to phone number in bot message") - info.Chat = chatPN + zerolog.Ctx(ctx).Err(err).Stringer("sender", info.Sender).Msg("Failed to get LID for sender") + return false } } + if info.Chat.Server == types.DefaultUserServer && info.IsFromMe && info.RecipientAlt.IsEmpty() { + info.RecipientAlt, err = wa.GetStore().LIDs.GetLIDForPN(ctx, info.Chat) + if err != nil { + zerolog.Ctx(ctx).Err(err).Stringer("chat", info.Chat).Msg("Failed to get LID for chat") + return false + } + } + if checkPhones { + return wa.checkAllPhonesInMessage(ctx, info) + } + return true } func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Message) (success bool) { @@ -325,6 +285,9 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa if evt.Info.Chat == types.StatusBroadcastJID && !wa.Main.Config.EnableStatusBroadcast { return } + if !wa.ensureAltJIDs(ctx, &evt.Info.MessageSource, true) { + return false + } parsedMessageType := getMessageType(evt.Message) if encReact := evt.Message.GetEncReactionMessage(); encReact != nil { decrypted, err := wa.Client.DecryptReaction(ctx, evt) @@ -358,8 +321,6 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa evt.UnwrapRaw() parsedMessageType = getMessageType(evt.Message) } - origSource := evt.Info.MessageSource - wa.rerouteWAMessage(ctx, "message", &evt.Info.MessageSource, evt.Info.ID) wa.UserLogin.Log.Trace(). Any("info", evt.Info). Any("payload", evt.Message). @@ -410,9 +371,8 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa res := wa.UserLogin.QueueRemoteEvent(&WAMessageEvent{ MessageInfoWrapper: &MessageInfoWrapper{ - OrigSource: origSource, - Info: evt.Info, - wa: wa, + Info: evt.Info, + wa: wa, }, Message: evt.Message, MsgEvent: evt, @@ -424,7 +384,9 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa } func (wa *WhatsAppClient) handleWAUndecryptableMessage(ctx context.Context, evt *events.UndecryptableMessage) bool { - wa.rerouteWAMessage(ctx, "undecryptable message", &evt.Info.MessageSource, evt.Info.ID) + if !wa.ensureAltJIDs(ctx, &evt.Info.MessageSource, true) { + return false + } wa.UserLogin.Log.Debug(). Any("info", evt.Info). Bool("unavailable", evt.IsUnavailable). @@ -447,12 +409,44 @@ func (wa *WhatsAppClient) handleWAUndecryptableMessage(ctx context.Context, evt return res.Success } +func (wa *WhatsAppClient) handleWAMediaRetry(ctx context.Context, evt *events.MediaRetry) bool { + wa.phoneSeen(evt.Timestamp) + var senderLID, chatLID types.JID + var err error + if evt.SenderID.Server == types.DefaultUserServer { + senderLID, err = wa.GetStore().LIDs.GetLIDForPN(ctx, evt.SenderID) + if err != nil { + wa.UserLogin.Log.Err(err). + Stringer("sender_id", evt.SenderID). + Msg("Failed to get LID for media retry sender") + return false + } + } + if evt.ChatID.Server == types.DefaultUserServer { + chatLID, err = wa.GetStore().LIDs.GetLIDForPN(ctx, evt.ChatID) + if err != nil { + wa.UserLogin.Log.Err(err). + Stringer("chat_id", evt.ChatID). + Msg("Failed to get LID for media retry chat") + return false + } + } + res := wa.UserLogin.QueueRemoteEvent(&WAMediaRetry{ + MediaRetry: evt, + wa: wa, + senderLID: senderLID, + chatLID: chatLID, + }) + return res.Success +} + func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Receipt) (success bool) { - origChat := evt.Chat - wa.rerouteWAMessage(ctx, "receipt", &evt.MessageSource, evt.MessageIDs) if evt.IsFromMe && evt.Sender.Device == 0 { wa.phoneSeen(evt.Timestamp) } + if !wa.ensureAltJIDs(ctx, &evt.MessageSource, true) { + return false + } var evtType bridgev2.RemoteEventType switch evt.Type { case types.ReceiptTypeRead, types.ReceiptTypeReadSelf: @@ -464,28 +458,34 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei default: return true } - targets := make([]networkid.MessageID, len(evt.MessageIDs)) - messageSender := wa.JID + targets := make([]networkid.MessageID, 0, len(evt.MessageIDs)) + messageSender := wa.GetLID() if !evt.MessageSender.IsEmpty() { messageSender = evt.MessageSender - // Second part of rerouting receipts in LID chats - if messageSender == origChat && evt.Chat != origChat { - messageSender = evt.Chat - } - } else if evt.Chat.Server == types.GroupServer && evt.Sender.Server == types.HiddenUserServer { - lid := wa.GetStore().GetLID() - if !lid.IsEmpty() { - messageSender = lid + } + var chatAlt types.JID + if evt.Chat.Server == types.DefaultUserServer { + chatLID, _ := wa.GetStore().LIDs.GetLIDForPN(ctx, evt.Chat) + if !chatLID.IsEmpty() { + chatAlt = evt.Chat + evt.Chat = chatLID } } - for i, id := range evt.MessageIDs { - targets[i] = waid.MakeMessageID(evt.Chat, messageSender, id) + for _, id := range evt.MessageIDs { + targets = append(targets, waid.MakeMessageID(evt.Chat, messageSender, id)) + if !chatAlt.IsEmpty() { + targets = append(targets, waid.MakeMessageID(chatAlt, messageSender, id)) + } + } + senderLID := evt.Sender + if senderLID.Server == types.DefaultUserServer && !evt.SenderAlt.IsEmpty() { + senderLID = evt.SenderAlt } res := wa.UserLogin.QueueRemoteEvent(&simplevent.Receipt{ EventMeta: simplevent.EventMeta{ Type: evtType, PortalKey: wa.makeWAPortalKey(evt.Chat), - Sender: wa.makeEventSender(ctx, evt.Sender), + Sender: wa.makeEventSender(ctx, senderLID), Timestamp: evt.Timestamp, }, Targets: targets, @@ -494,11 +494,11 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei } func (wa *WhatsAppClient) handleWAChatPresence(ctx context.Context, evt *events.ChatPresence) { - if evt.Chat.Server == types.HiddenUserServer && evt.Sender.ToNonAD() == evt.Chat { + if evt.Chat.Server == types.DefaultUserServer && evt.Sender.ToNonAD() == evt.Chat { if evt.SenderAlt.IsEmpty() { - evt.SenderAlt, _ = wa.GetStore().LIDs.GetPNForLID(ctx, evt.Sender) + evt.SenderAlt, _ = wa.GetStore().LIDs.GetLIDForPN(ctx, evt.Sender) } - if evt.SenderAlt.Server == types.DefaultUserServer { + if evt.SenderAlt.Server == types.HiddenUserServer { evt.Sender, evt.SenderAlt = evt.SenderAlt, evt.Sender evt.Chat = evt.Sender.ToNonAD() } @@ -535,6 +535,7 @@ func (wa *WhatsAppClient) handleWALogout(reason events.ConnectFailureReason, onC wa.Disconnect() wa.Client = nil wa.JID = types.EmptyJID + wa.LID = types.EmptyJID wa.UserLogin.Metadata.(*waid.UserLoginMetadata).WADeviceID = 0 wa.UserLogin.BridgeState.Send(status.BridgeState{ StateEvent: status.StateBadCredentials, @@ -548,12 +549,15 @@ func (wa *WhatsAppClient) handleWACallStart(ctx context.Context, group, sender, if !wa.Main.Config.CallStartNotices || time.Since(ts) > callEventMaxAge { return true } - if sender.Server == types.HiddenUserServer && senderAlt.Server == types.DefaultUserServer { + if sender.Server == types.DefaultUserServer && senderAlt.IsEmpty() { + senderAlt, _ = wa.GetStore().LIDs.GetLIDForPN(ctx, sender) + } + if sender.Server == types.DefaultUserServer && senderAlt.Server == types.HiddenUserServer { wa.UserLogin.Log.Debug(). - Stringer("lid", sender). - Stringer("pn", senderAlt). + Stringer("lid", senderAlt). + Stringer("pn", sender). Str("call_id", id). - Msg("Forced LID caller to phone number in incoming call") + Msg("Forced phone number caller to LID in incoming call") sender, senderAlt = senderAlt, sender } chat := group @@ -600,6 +604,12 @@ func (wa *WhatsAppClient) handleWAIdentityChange(ctx context.Context, evt *event if !wa.Main.Config.IdentityChangeNotices { return } + if evt.JID.Server == types.DefaultUserServer { + lid, _ := wa.GetStore().LIDs.GetLIDForPN(ctx, evt.JID) + if !lid.IsEmpty() { + evt.JID = lid + } + } wa.UserLogin.QueueRemoteEvent(&simplevent.Message[*events.IdentityChange]{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventMessage, @@ -650,13 +660,14 @@ func (wa *WhatsAppClient) handleWADeleteChat(ctx context.Context, evt *events.De func (wa *WhatsAppClient) handleWADeleteForMe(ctx context.Context, evt *events.DeleteForMe) bool { chatJID := wa.maybeConvertJIDToLID(ctx, evt.ChatJID) + senderJID := wa.maybeConvertJIDToLID(ctx, evt.SenderJID) return wa.UserLogin.QueueRemoteEvent(&simplevent.MessageRemove{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventMessageRemove, PortalKey: wa.makeWAPortalKey(chatJID), Timestamp: evt.Timestamp, }, - TargetMessage: waid.MakeMessageID(chatJID, evt.SenderJID, evt.MessageID), + TargetMessage: waid.MakeMessageID(chatJID, senderJID, evt.MessageID), OnlyForMe: true, }).Success } @@ -667,7 +678,7 @@ func (wa *WhatsAppClient) handleWAMarkChatAsRead(ctx context.Context, evt *event EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventReadReceipt, PortalKey: wa.makeWAPortalKey(chatJID), - Sender: wa.makeEventSender(ctx, wa.JID), + Sender: wa.makeEventSender(ctx, wa.GetLID()), Timestamp: evt.Timestamp, }, ReadUpTo: evt.Timestamp, diff --git a/pkg/connector/id.go b/pkg/connector/id.go index c07e431..c0aa06b 100644 --- a/pkg/connector/id.go +++ b/pkg/connector/id.go @@ -49,7 +49,7 @@ func (wa *WhatsAppClient) makeEventSender(ctx context.Context, id types.JID) bri } } return bridgev2.EventSender{ - IsFromMe: id.User == wa.GetStore().GetJID().User || id.User == wa.GetStore().GetLID().User, + IsFromMe: wa.IsOwnJID(id), Sender: waid.MakeUserID(id), SenderLogin: waid.MakeUserLoginID(senderLoginJID), } @@ -60,24 +60,25 @@ func (wa *WhatsAppClient) messageIDToKey(id *waid.ParsedMessageID) *waCommon.Mes RemoteJID: ptr.Ptr(id.Chat.String()), ID: ptr.Ptr(id.ID), } - if id.Sender.User == wa.GetStore().GetJID().User || id.Sender.User == wa.GetStore().GetLID().User { + if wa.IsOwnJID(id.Sender) { key.FromMe = ptr.Ptr(true) } - if id.Chat.Server != types.MessengerServer && id.Chat.Server != types.DefaultUserServer && id.Chat.Server != types.HiddenUserServer && id.Chat.Server != types.BotServer { + if id.Chat.Server != types.MessengerServer && id.Chat.Server != types.DefaultUserServer && + id.Chat.Server != types.HiddenUserServer && id.Chat.Server != types.BotServer { key.Participant = ptr.Ptr(id.Sender.String()) } return key } -func (wa *WhatsAppClient) maybeConvertJIDToLID(ctx context.Context, chatJID types.JID) types.JID { - if chatJID.Server == types.HiddenUserServer { - if pn, err := wa.GetStore().LIDs.GetPNForLID(ctx, chatJID); err != nil { +func (wa *WhatsAppClient) maybeConvertJIDToLID(ctx context.Context, jid types.JID) types.JID { + if jid.Server == types.DefaultUserServer { + if lidForPN, err := wa.GetStore().LIDs.GetLIDForPN(ctx, jid); err != nil { wa.UserLogin.Log.Err(err). - Stringer("lid", chatJID). - Msg("Failed to get phone number for LID chat") - } else if !pn.IsEmpty() { - return pn.ToNonAD() + Stringer("pn", jid). + Msg("Failed to get LID for phone number chat") + } else if !lidForPN.IsEmpty() { + return lidForPN } } - return chatJID + return jid } diff --git a/pkg/connector/lidmigrate.go b/pkg/connector/lidmigrate.go new file mode 100644 index 0000000..cfab099 --- /dev/null +++ b/pkg/connector/lidmigrate.go @@ -0,0 +1,212 @@ +// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge. +// Copyright (C) 2026 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package connector + +import ( + "context" + "fmt" + "time" + + "github.com/rs/zerolog" + "go.mau.fi/util/dbutil" + "go.mau.fi/whatsmeow/types" + + "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/networkid" + "maunium.net/go/mautrix/event" + + "go.mau.fi/mautrix-whatsapp/pkg/waid" +) + +func (wa *WhatsAppClient) FindAltTargetMessage(ctx context.Context, targetMsg networkid.MessageID, evt bridgev2.RemoteEventWithTargetMessage) (alts []networkid.MessageID, err error) { + parsed, err := waid.ParseMessageID(targetMsg) + if err != nil { + return nil, fmt.Errorf("failed to parse target message ID: %w", err) + } + altSender, err := wa.GetStore().GetAltJID(ctx, parsed.Sender) + if err != nil { + return nil, err + } + var altChat types.JID + if parsed.Chat.Server == types.HiddenUserServer { + altChat, err = wa.GetStore().LIDs.GetPNForLID(ctx, parsed.Chat) + if err != nil { + return nil, err + } + } + if !altSender.IsEmpty() { + altSenderID := *parsed + altSenderID.Sender = altSender + alts = append(alts, altSenderID.String()) + } + if !altChat.IsEmpty() { + altChatID := *parsed + altChatID.Chat = altChat + if altSender.Server == types.DefaultUserServer { + altChatID.Sender = altSender + } + alts = append(alts, altChatID.String()) + } + return +} + +func (wa *WhatsAppClient) checkAllPhonesInMessage(ctx context.Context, info *types.MessageSource) (ok bool) { + for _, jid := range []types.JID{info.Sender, info.SenderAlt, info.Chat, info.RecipientAlt, info.BroadcastListOwner} { + if !wa.reIDPhoneDMToLIDIfNeeded(ctx, jid) { + return false + } + } + return true +} + +func (wa *WhatsAppClient) reIDPhoneDMToLIDIfNeeded(ctx context.Context, pn types.JID) (ok bool) { + if pn.Server != types.DefaultUserServer { + return true + } + portalKey := wa.makeWAPortalKey(pn) + if wa.Main.unmigratedDMs.Has(portalKey) { + lid, err := wa.GetStore().LIDs.GetLIDForPN(ctx, pn) + if err != nil { + zerolog.Ctx(ctx).Err(err).Stringer("pn", pn).Msg("Failed to get LID for PN") + return false + } else if lid.IsEmpty() { + zerolog.Ctx(ctx).Warn().Stringer("pn", pn).Msg("No found LID for phone number") + return true + } + zerolog.Ctx(ctx).Info(). + Object("portal_key", portalKey). + Stringer("pn", pn). + Stringer("lid", lid). + Msg("Received event for portal in unmigrated DMs list, trying migration") + _, err = wa.Main.reIDPhoneDMToLID(ctx, pn, lid, wa.UserLogin.ID) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to re-ID phone DM to LID") + return false + } + } + return true +} + +func (wa *WhatsAppConnector) reIDPhoneDMToLID(ctx context.Context, pn, lid types.JID, receiver networkid.UserLoginID) (bridgev2.ReIDResult, error) { + pnKey := networkid.PortalKey{ + ID: waid.MakePortalID(pn), + Receiver: receiver, + } + lidKey := networkid.PortalKey{ + ID: waid.MakePortalID(lid), + Receiver: receiver, + } + result, portal, err := wa.Bridge.ReIDPortal(ctx, pnKey, lidKey) + if err != nil { + return result, err + } + if result == bridgev2.ReIDResultSourceReIDd || result == bridgev2.ReIDResultTargetDeletedAndSourceReIDd { + var pnGhost, lidGhost *bridgev2.Ghost + pnGhost, err = wa.Bridge.GetGhostByID(ctx, waid.MakeUserID(pn)) + if err != nil { + return result, fmt.Errorf("failed to get PN ghost: %w", err) + } + lidGhost, err = wa.Bridge.GetGhostByID(ctx, waid.MakeUserID(lid)) + if err != nil { + return result, fmt.Errorf("failed to get LID ghost: %w", err) + } + _, err = pnGhost.Intent.SendState(ctx, portal.MXID, event.StateMember, pnGhost.Intent.GetMXID().String(), &event.Content{ + Parsed: &event.MemberEventContent{Membership: event.MembershipLeave, Reason: "Migrating to LIDs"}, + Raw: map[string]any{"com.beeper.exclude_from_timeline": true}, + }, time.Time{}) + if err != nil { + return result, fmt.Errorf("failed to send leave event for PN ghost: %w", err) + } + _, err = wa.Bridge.Bot.SendState(ctx, portal.MXID, event.StateMember, lidGhost.Intent.GetMXID().String(), &event.Content{ + Parsed: &event.MemberEventContent{Membership: event.MembershipInvite, Reason: "Migrating to LIDs"}, + Raw: map[string]any{"com.beeper.exclude_from_timeline": true}, + }, time.Time{}) + if err != nil { + return result, fmt.Errorf("failed to send invite event for LID ghost: %w", err) + } + _, err = lidGhost.Intent.SendState(ctx, portal.MXID, event.StateMember, lidGhost.Intent.GetMXID().String(), &event.Content{ + Parsed: &event.MemberEventContent{Membership: event.MembershipJoin, Reason: "Migrating to LIDs"}, + Raw: map[string]any{"com.beeper.exclude_from_timeline": true}, + }, time.Time{}) + if err != nil { + return result, fmt.Errorf("failed to send join event for LID ghost: %w", err) + } + portal.OtherUserID = lidGhost.ID + err = portal.Save(ctx) + if err != nil { + return result, fmt.Errorf("failed to save portal after re-ID: %w", err) + } + portal.UpdateBridgeInfo(ctx) + } + return result, nil +} + +var scanPortalKey = dbutil.ConvertRowFn[networkid.PortalKey](func(row dbutil.Scannable) (key networkid.PortalKey, err error) { + err = row.Scan(&key.ID, &key.Receiver) + return +}) + +func (wa *WhatsAppConnector) migrateToLIDDMs(ctx context.Context) error { + if wa.Bridge.Background { + if wa.Bridge.DB.KV.Get(ctx, "whatsapp_lid_dms_migrated") == "true" { + return nil + } + return fmt.Errorf("can't migrate to LID DMs in background mode") + } + log := zerolog.Ctx(ctx).With().Str("action", "migrate to lid dms").Logger() + const findPNPortals = "SELECT id, receiver FROM portal WHERE bridge_id=$1 AND room_type='dm' AND id LIKE '%@s.whatsapp.net'" + pnPortalKeys, err := scanPortalKey.NewRowIter(wa.Bridge.DB.Query(ctx, findPNPortals, wa.Bridge.ID)).AsList() + if err != nil { + return fmt.Errorf("failed to get phone number portals: %w", err) + } + var updatedPortals, missingLID int + for _, key := range pnPortalKeys { + pnJID, err := waid.ParsePortalID(key.ID) + if err != nil { + log.Warn().Err(err).Str("portal_id", string(key.ID)).Msg("Failed to parse portal ID") + continue + } else if pnJID.Server != types.DefaultUserServer { + continue + } + lid, err := wa.DeviceStore.LIDMap.GetLIDForPN(ctx, pnJID) + if err != nil { + return fmt.Errorf("failed to get LID for PN portal %s: %w", key.ID, err) + } else if lid.IsEmpty() { + log.Warn().Stringer("pn", pnJID).Msg("No LID for PN portal") + wa.unmigratedDMs.Add(key) + missingLID++ + continue + } + res, err := wa.reIDPhoneDMToLID(ctx, pnJID, lid, key.Receiver) + if err != nil { + return fmt.Errorf("failed to re-ID %s to %s: %w", pnJID, lid, err) + } + updatedPortals++ + log.Info(). + Stringer("pn", pnJID). + Stringer("lid", lid). + Stringer("result", res). + Msg("Re-ID'd phone number DM portal") + } + log.Info(). + Int("updated_portals", updatedPortals). + Int("total_pn_portals", len(pnPortalKeys)). + Int("missing_lid", missingLID). + Msg("Finished re-IDing phone number DM portals") + wa.Bridge.DB.KV.Set(ctx, "whatsapp_lid_dms_migrated", "true") + return nil +} diff --git a/pkg/connector/mediarequest.go b/pkg/connector/mediarequest.go index 2686250..3806196 100644 --- a/pkg/connector/mediarequest.go +++ b/pkg/connector/mediarequest.go @@ -155,8 +155,8 @@ func (wa *WhatsAppClient) sendMediaRequestDirect(ctx context.Context, rawMsgID n return wa.Client.SendMediaRetryReceipt(ctx, &types.MessageInfo{ ID: msgID.ID, MessageSource: types.MessageSource{ - IsFromMe: msgID.Sender.User == wa.JID.User, - IsGroup: msgID.Chat.Server != types.DefaultUserServer && msgID.Chat.Server != types.BotServer, + IsFromMe: wa.IsOwnJID(msgID.Sender), + IsGroup: msgID.Chat.Server != types.DefaultUserServer && msgID.Chat.Server != types.HiddenUserServer && msgID.Chat.Server != types.BotServer, Sender: msgID.Sender, Chat: msgID.Chat, }, diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index fb2ce16..f0d869b 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -25,6 +25,7 @@ import ( "time" "github.com/rs/zerolog" + "go.mau.fi/util/exmaps" "go.mau.fi/util/exsync" "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" @@ -119,16 +120,24 @@ func (wa *WhatsAppConnector) ValidateUserID(id networkid.UserID) bool { } } -func (wa *WhatsAppClient) startChatLIDToPN(ctx context.Context, jid types.JID) (types.JID, error) { - if jid.Server == types.HiddenUserServer { - pn, err := wa.GetStore().LIDs.GetPNForLID(ctx, jid) +func (wa *WhatsAppClient) startChatPNToLID(ctx context.Context, jid types.JID) (types.JID, error) { + if jid.Server == types.DefaultUserServer { + lid, err := wa.GetStore().LIDs.GetLIDForPN(ctx, jid) if err != nil { - return jid, fmt.Errorf("failed to get phone number for lid: %w", err) - } else if pn.IsEmpty() { - // Don't allow starting chats with LIDs for now - return jid, fmt.Errorf("phone number not found") + return jid, fmt.Errorf("failed to get lid for phone number: %w", err) + } else if lid.IsEmpty() { + resp, err := wa.Client.GetUserInfo(ctx, []types.JID{jid}) + if err != nil { + return jid, fmt.Errorf("failed to get user info for phone number: %w", err) + } else if info, ok := resp[jid]; !ok { + return jid, fmt.Errorf("server didn't return user info for phone number") + } else if info.LID.IsEmpty() { + return jid, fmt.Errorf("server didn't return lid for phone number") + } else { + return info.LID, nil + } } - return pn, nil + return lid, nil } return jid, nil } @@ -147,7 +156,7 @@ func (wa *WhatsAppClient) makeCreateChatResponse(ctx context.Context, jid, origJ func (wa *WhatsAppClient) CreateChatWithGhost(ctx context.Context, ghost *bridgev2.Ghost) (*bridgev2.CreateChatResponse, error) { origJID := waid.ParseUserID(ghost.ID) - jid, err := wa.startChatLIDToPN(ctx, origJID) + jid, err := wa.startChatPNToLID(ctx, origJID) if err != nil { return nil, err } @@ -159,7 +168,7 @@ func (wa *WhatsAppClient) ResolveIdentifier(ctx context.Context, identifier stri if err != nil { return nil, err } - jid, err := wa.startChatLIDToPN(ctx, origJID) + jid, err := wa.startChatPNToLID(ctx, origJID) if err != nil { return nil, err } @@ -199,6 +208,7 @@ func (wa *WhatsAppClient) getContactList(ctx context.Context, filter string, onl return nil, err } resp := make([]*bridgev2.ResolveIdentifierResponse, 0, len(contacts)) + addedIDs := make(exmaps.Set[types.JID]) for jid, contactInfo := range contacts { if onlyContacts && (contactInfo.FirstName == "" && contactInfo.FullName == "") { continue @@ -206,12 +216,30 @@ func (wa *WhatsAppClient) getContactList(ctx context.Context, filter string, onl if !matchesQuery(contactInfo.PushName, filter) && !matchesQuery(contactInfo.FullName, filter) && !matchesQuery(jid.User, filter) { continue } + var lid types.JID + if jid.Server == types.HiddenUserServer { + lid = jid + } else if jid.Server == types.DefaultUserServer { + lid, err = wa.GetStore().LIDs.GetLIDForPN(ctx, jid) + if err != nil { + return nil, fmt.Errorf("failed to get lid for phone number %s: %w", jid, err) + } else if !lid.IsEmpty() { + jid = lid + } + } + if !addedIDs.Add(jid) { + continue + } + var chatResp *bridgev2.CreateChatResponse + if !lid.IsEmpty() { + chatResp = &bridgev2.CreateChatResponse{PortalKey: wa.makeWAPortalKey(lid)} + } ghost, _ := wa.Main.Bridge.GetGhostByID(ctx, waid.MakeUserID(jid)) resp = append(resp, &bridgev2.ResolveIdentifierResponse{ Ghost: ghost, UserID: waid.MakeUserID(jid), UserInfo: wa.contactToUserInfo(ctx, jid, contactInfo, false), - Chat: &bridgev2.CreateChatResponse{PortalKey: wa.makeWAPortalKey(jid)}, + Chat: chatResp, }) } return resp, nil @@ -229,8 +257,7 @@ func (wa *WhatsAppClient) CreateGroup(ctx context.Context, params *bridgev2.Grou } for i, participant := range params.Participants { jid := waid.ParseUserID(participant) - // Normalize to PN if it's a LID - jid, err := wa.startChatLIDToPN(ctx, jid) + jid, err := wa.startChatPNToLID(ctx, jid) if err != nil { return nil, fmt.Errorf("failed to normalize participant %s: %w", participant, err) } diff --git a/pkg/connector/wadb/upgrades/00-latest-schema.sql b/pkg/connector/wadb/upgrades/00-latest-schema.sql index 850f5b4..0f1fef9 100644 --- a/pkg/connector/wadb/upgrades/00-latest-schema.sql +++ b/pkg/connector/wadb/upgrades/00-latest-schema.sql @@ -1,4 +1,4 @@ --- v0 -> v9 (compatible with v3+): Latest revision +-- v0 -> v10 (compatible with v3+): Latest revision CREATE TABLE whatsapp_poll_option_id ( bridge_id TEXT NOT NULL, diff --git a/pkg/connector/wadb/upgrades/08-may-need-lid-dm-deletion.sql b/pkg/connector/wadb/upgrades/08-may-need-lid-dm-deletion.sql deleted file mode 100644 index d5b4ca0..0000000 --- a/pkg/connector/wadb/upgrades/08-may-need-lid-dm-deletion.sql +++ /dev/null @@ -1,2 +0,0 @@ --- v8 (compatible with v3+): Mark LID DMs for deletion -INSERT INTO kv_store (bridge_id, key, value) VALUES ('', 'whatsapp_lid_dms_deleted', 'false'); diff --git a/pkg/connector/wadb/upgrades/09-may-need-lid-dm-deletion-again.sql b/pkg/connector/wadb/upgrades/09-may-need-lid-dm-deletion-again.sql deleted file mode 100644 index 5c32e65..0000000 --- a/pkg/connector/wadb/upgrades/09-may-need-lid-dm-deletion-again.sql +++ /dev/null @@ -1,3 +0,0 @@ --- v9 (compatible with v3+): Mark LID DMs for deletion (again) -DELETE FROM kv_store WHERE bridge_id='' AND key='whatsapp_lid_dms_deleted'; -INSERT INTO kv_store (bridge_id, key, value) VALUES ('', 'whatsapp_lid_dms_deleted', 'false'); diff --git a/pkg/connector/wadb/upgrades/10-history-sync-conversation-to-lid.sql b/pkg/connector/wadb/upgrades/10-history-sync-conversation-to-lid.sql new file mode 100644 index 0000000..9e50f51 --- /dev/null +++ b/pkg/connector/wadb/upgrades/10-history-sync-conversation-to-lid.sql @@ -0,0 +1,8 @@ +-- v10 (compatible with v3+): Move history sync conversations to LIDs +UPDATE whatsapp_history_sync_conversation +SET chat_jid=COALESCE( + (SELECT lid || '@lid' FROM whatsmeow_lid_map WHERE pn=replace(chat_jid, '@s.whatsapp.net', '')), + chat_jid +) +WHERE chat_jid LIKE '%@s.whatsapp.net'; +DELETE FROM portal WHERE id LIKE '%@s.whatsapp.net' AND (mxid IS NULL OR mxid='') AND room_type=''; diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 49aedf8..57f9113 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -150,7 +150,7 @@ func (mc *MessageConverter) ToWhatsApp( } lid := parsedID.Sender if lid.Server == types.DefaultUserServer { - lid, err = client.Store.LIDs.GetLIDForPN(ctx, parsedID.Sender) + lid, err = client.Store.LIDs.GetLIDForPN(ctx, lid) if err != nil { return nil, nil, fmt.Errorf("failed to get LID for PN: %w", err) } diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index e8cb032..e912732 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -136,7 +136,6 @@ func (mc *MessageConverter) ToMatrix( waMsg *waE2E.Message, rawWaMsg *waE2E.Message, info *types.MessageInfo, - origSource *types.MessageSource, isViewOnce bool, isBackfill bool, previouslyConvertedPart *bridgev2.ConvertedMessagePart, @@ -183,7 +182,7 @@ func (mc *MessageConverter) ToMatrix( case waMsg.PollCreationMessageV6 != nil: part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV6) case waMsg.PollUpdateMessage != nil: - part, contextInfo = mc.convertPollUpdateMessage(ctx, info, origSource, waMsg.PollUpdateMessage) + part, contextInfo = mc.convertPollUpdateMessage(ctx, info, waMsg.PollUpdateMessage) case waMsg.EventMessage != nil: part, contextInfo = mc.convertEventMessage(ctx, waMsg.EventMessage) case waMsg.PinInChatMessage != nil: @@ -272,30 +271,26 @@ func (mc *MessageConverter) ToMatrix( if chat.IsEmpty() { chat, _ = waid.ParsePortalID(portal.ID) } - // We reroute all DMs to the phone number JID, so reroute reply participants too - pcp = rerouteMessageKey(ctx, chat, pcp, getPortal(ctx).Metadata.(*waid.PortalMetadata).AddressingMode == types.AddressingModeLID) - if store := getClient(ctx).Store; store != nil && chat.Server == types.DefaultUserServer && pcp.Server == types.HiddenUserServer { - pcpPN, _ := store.LIDs.GetPNForLID(ctx, pcp) - zerolog.Ctx(ctx).Debug(). - Stringer("orig_participant", pcp). - Stringer("rerouted_participant", pcpPN). - Msg("Rerouting reply target (PN recipient in LID DM)") - if !pcpPN.IsEmpty() { - pcp = pcpPN - } - } else if store != nil && chat.Server == types.GroupServer && pcp.Server == types.DefaultUserServer && getPortal(ctx).Metadata.(*waid.PortalMetadata).AddressingMode == types.AddressingModeLID { - pcpLID, _ := store.LIDs.GetLIDForPN(ctx, pcp) - zerolog.Ctx(ctx).Debug(). - Stringer("orig_participant", pcp). - Stringer("rerouted_participant", pcpLID). - Msg("Rerouting reply target (PN recipient in LID group)") - if !pcpLID.IsEmpty() { - pcp = pcpLID - } - } cm.ReplyTo = &networkid.MessageOptionalPartID{ MessageID: waid.MakeMessageID(chat, pcp, contextInfo.GetStanzaID()), } + var pn, lid types.JID + if pcp.Server == types.DefaultUserServer { + pn = pcp + lid, _ = client.Store.LIDs.GetLIDForPN(ctx, pcp) + } else if pcp.Server == types.HiddenUserServer { + lid = pcp + pn, _ = client.Store.LIDs.GetPNForLID(ctx, pcp) + } else if pcp.Server == types.BotServer { + lid = pcp + } + if !pn.IsEmpty() { + cm.ReplyToLogin = waid.MakeUserLoginID(pn) + } + if !lid.IsEmpty() { + cm.ReplyToUser = waid.MakeUserID(lid) + } + // TODO set reply to room } if contextInfo.GetIsForwarded() { hasCaption := part.Content.FileName != "" && part.Content.FileName != part.Content.Body diff --git a/pkg/msgconv/wa-poll.go b/pkg/msgconv/wa-poll.go index dfe1a25..881a5fb 100644 --- a/pkg/msgconv/wa-poll.go +++ b/pkg/msgconv/wa-poll.go @@ -94,31 +94,7 @@ func (mc *MessageConverter) convertPollCreationMessage(ctx context.Context, msg }, msg.GetContextInfo() } -func rerouteMessageKey(ctx context.Context, chat, sender types.JID, groupLIDAddressing bool) types.JID { - if store := getClient(ctx).Store; store != nil && chat.Server == types.DefaultUserServer && sender.Server == types.HiddenUserServer { - senderPN, _ := store.LIDs.GetPNForLID(ctx, sender) - zerolog.Ctx(ctx).Debug(). - Stringer("orig_participant", sender). - Stringer("rerouted_participant", senderPN). - Msg("Rerouting message key (PN recipient in LID DM)") - if !senderPN.IsEmpty() { - return senderPN - } - } else if store != nil && chat.Server == types.GroupServer && sender.Server == types.DefaultUserServer && groupLIDAddressing { - senderLID, _ := store.LIDs.GetLIDForPN(ctx, sender) - zerolog.Ctx(ctx).Debug(). - Stringer("orig_participant", sender). - Stringer("rerouted_participant", senderLID). - Msg("Rerouting message key (PN recipient in LID group)") - if !senderLID.IsEmpty() { - return senderLID - } - } - return sender -} - func KeyToMessageID(ctx context.Context, client *whatsmeow.Client, chat, sender types.JID, key *waCommon.MessageKey) networkid.MessageID { - groupLIDAddressing := sender.Server == types.HiddenUserServer sender = sender.ToNonAD() var err error if !key.GetFromMe() { @@ -131,7 +107,7 @@ func KeyToMessageID(ctx context.Context, client *whatsmeow.Client, chat, sender if sender.Server == types.LegacyUserServer { sender.Server = types.DefaultUserServer } - } else if chat.Server == types.DefaultUserServer || chat.Server == types.BotServer { + } else if chat.Server == types.DefaultUserServer || chat.Server == types.HiddenUserServer || chat.Server == types.BotServer { if sender.User == client.Store.GetJID().User || sender.User == client.Store.GetLID().User { // Message key is not from the sender, but message sender (containing key) is me, // so message key sender is the other user in the DM @@ -139,7 +115,11 @@ func KeyToMessageID(ctx context.Context, client *whatsmeow.Client, chat, sender } else { // Message key is not from the sender, but message sender (containing key) is not me, // so message key sender is me - sender = client.Store.GetJID().ToNonAD() + if chat.Server == types.HiddenUserServer { + sender = client.Store.GetLID().ToNonAD() + } else { + sender = client.Store.GetJID().ToNonAD() + } } } else { zerolog.Ctx(ctx).Warn(). @@ -157,10 +137,6 @@ func KeyToMessageID(ctx context.Context, client *whatsmeow.Client, chat, sender chat = remoteJID } } - sender = rerouteMessageKey( - context.WithValue(ctx, contextKeyClient, client), - chat, sender, groupLIDAddressing, - ) return waid.MakeMessageID(chat, sender, key.GetID()) } @@ -170,7 +146,7 @@ var failedPollUpdatePart = &bridgev2.ConvertedMessagePart{ DontBridge: true, } -func (mc *MessageConverter) convertPollUpdateMessage(ctx context.Context, info *types.MessageInfo, origSource *types.MessageSource, msg *waE2E.PollUpdateMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { +func (mc *MessageConverter) convertPollUpdateMessage(ctx context.Context, info *types.MessageInfo, msg *waE2E.PollUpdateMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { log := zerolog.Ctx(ctx) pollMessageID := KeyToMessageID(ctx, getClient(ctx), info.Chat, info.Sender, msg.PollCreationMessageKey) pollMessage, err := mc.Bridge.DB.Message.GetPartByID(ctx, getPortal(ctx).Receiver, pollMessageID, "") @@ -181,12 +157,8 @@ func (mc *MessageConverter) convertPollUpdateMessage(ctx context.Context, info * log.Warn().Str("target_message_id", string(pollMessageID)).Msg("Poll update target message not found") return failedPollUpdatePart, nil } - infoForDecrypt := *info - if origSource != nil { - infoForDecrypt.MessageSource = *origSource - } vote, err := getClient(ctx).DecryptPollVote(ctx, &events.Message{ - Info: infoForDecrypt, + Info: *info, Message: &waE2E.Message{PollUpdateMessage: msg}, }) if err != nil { diff --git a/pkg/waid/id.go b/pkg/waid/id.go index e522f5f..7f8fe5b 100644 --- a/pkg/waid/id.go +++ b/pkg/waid/id.go @@ -83,6 +83,13 @@ func MakeMessageID(chat, sender types.JID, id types.MessageID) networkid.Message return networkid.MessageID(fmt.Sprintf("%s:%s:%s", chat.ToNonAD().String(), sender.ToNonAD().String(), id)) } +func MakeMessageIDWithAltSender(chat, sender, altSender types.JID, id types.MessageID) networkid.MessageID { + if chat.Server == types.HiddenUserServer && sender.Server == types.DefaultUserServer && altSender.Server == types.HiddenUserServer { + sender = altSender + } + return MakeMessageID(chat, sender, id) +} + func MakeFakeMessageID(chat, sender types.JID, data string) networkid.MessageID { return networkid.MessageID(fmt.Sprintf("fake:%s:%s:%s", chat.ToNonAD().String(), sender.ToNonAD().String(), data)) } From ee8592c842ebdd4423d28034e7242df62f8a7aba Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 5 Aug 2026 17:18:31 +0300 Subject: [PATCH 79/94] wadb: make lid migration of history sync conversations safer --- .../10-history-sync-conversation-to-lid.sql | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/pkg/connector/wadb/upgrades/10-history-sync-conversation-to-lid.sql b/pkg/connector/wadb/upgrades/10-history-sync-conversation-to-lid.sql index 9e50f51..334c597 100644 --- a/pkg/connector/wadb/upgrades/10-history-sync-conversation-to-lid.sql +++ b/pkg/connector/wadb/upgrades/10-history-sync-conversation-to-lid.sql @@ -1,8 +1,22 @@ -- v10 (compatible with v3+): Move history sync conversations to LIDs + +-- Delete history sync conversations where a @lid conversation already exists +DELETE FROM whatsapp_history_sync_conversation +WHERE chat_jid LIKE '%@lid' AND EXISTS ( + SELECT 1 + FROM whatsapp_history_sync_conversation pnconv + WHERE pnconv.chat_jid=( + SELECT pn || '@s.whatsapp.net' + FROM whatsmeow_lid_map + WHERE lid=replace(whatsapp_history_sync_conversation.chat_jid, '@lid', '') + ) +); + +-- Update all phone number conversations to lids if the lid is known UPDATE whatsapp_history_sync_conversation -SET chat_jid=COALESCE( - (SELECT lid || '@lid' FROM whatsmeow_lid_map WHERE pn=replace(chat_jid, '@s.whatsapp.net', '')), - chat_jid -) -WHERE chat_jid LIKE '%@s.whatsapp.net'; +SET chat_jid=(SELECT lid || '@lid' FROM whatsmeow_lid_map WHERE pn=replace(chat_jid, '@s.whatsapp.net', '')) +WHERE chat_jid LIKE '%@s.whatsapp.net' + AND EXISTS (SELECT 1 FROM whatsmeow_lid_map WHERE pn=replace(chat_jid, '@s.whatsapp.net', '')); + +-- Delete blank phone number portals DELETE FROM portal WHERE id LIKE '%@s.whatsapp.net' AND (mxid IS NULL OR mxid='') AND room_type=''; From 09a43f5099c7441b69642b8c391de4fc965e6c12 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 6 Aug 2026 15:03:21 +0300 Subject: [PATCH 80/94] handlewhatsapp: add analytics for app state errors --- pkg/connector/handlewhatsapp.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index f8cee2a..fce47df 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -880,7 +880,12 @@ func (wa *WhatsAppClient) handleWAAppStateSyncComplete(ctx context.Context, evt } else { log.Info(). Time("recovery_ts", ts). + Bool("recovery_evt", evt.Recovery). Msg("Unmarked app state recovery as attempted after successful full sync") + wa.UserLogin.TrackAnalytics("WhatsApp Appstate Recovery Success", map[string]any{ + "patch_name": evt.Name, + "from_recovery": evt.Recovery, + }) } } else if ts, exists = wa.appStateFullSyncAttempted[evt.Name]; exists { delete(wa.appStateFullSyncAttempted, evt.Name) @@ -937,6 +942,9 @@ func (wa *WhatsAppClient) handleWAAppStateSyncError(ctx context.Context, evt *ev if err != nil { log.Err(err).Msg("Failed to save login metadata after marking app state recovery as attempted") } + wa.UserLogin.TrackAnalytics("WhatsApp Appstate Recovery Request", map[string]any{ + "patch_name": evt.Name, + }) go func() { resp, err := wa.Client.SendPeerMessage(ctx, whatsmeow.BuildAppStateRecoveryRequest(evt.Name)) if err != nil { From 85ace3ddcb011fc249b25f80893aa9a0090001cd Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 6 Aug 2026 18:06:54 +0300 Subject: [PATCH 81/94] 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 9521937..9aa9e7e 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260805124304-6723e556f35a + go.mau.fi/whatsmeow v0.0.0-20260806130833-ad9cc5cbdf9d golang.org/x/image v0.44.0 golang.org/x/net v0.57.0 golang.org/x/sync v0.22.0 diff --git a/go.sum b/go.sum index 322636b..9ba5037 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391 h1:lsvBEY8MJfYdV61YbwikiQvb go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391/go.mod h1:xunp/oIQfFD68HHcNHfG0pOiHkvEtDhTweeIwKJ//+Q= 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-20260805124304-6723e556f35a h1:TTRND9Dvq7YfT9vwJVQK4Zrit/sm3FHE74B5FWe8CgA= -go.mau.fi/whatsmeow v0.0.0-20260805124304-6723e556f35a/go.mod h1:Iy/xVSuVU2payR26MB1hv0UZUWRraEn4qKZ7+VRHulg= +go.mau.fi/whatsmeow v0.0.0-20260806130833-ad9cc5cbdf9d h1:fr7vlv8+xPmAgClPXaXLFspjW2LtXY88CRZ9MB3fZe8= +go.mau.fi/whatsmeow v0.0.0-20260806130833-ad9cc5cbdf9d/go.mod h1:Iy/xVSuVU2payR26MB1hv0UZUWRraEn4qKZ7+VRHulg= 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.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= From d96c57046c0158414bd7b208f176e2447b1dd9aa Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 6 Aug 2026 22:23:50 +0300 Subject: [PATCH 82/94] 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 9aa9e7e..18468ba 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260806130833-ad9cc5cbdf9d + go.mau.fi/whatsmeow v0.0.0-20260806192107-39b719baa629 golang.org/x/image v0.44.0 golang.org/x/net v0.57.0 golang.org/x/sync v0.22.0 diff --git a/go.sum b/go.sum index 9ba5037..b543aa6 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391 h1:lsvBEY8MJfYdV61YbwikiQvb go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391/go.mod h1:xunp/oIQfFD68HHcNHfG0pOiHkvEtDhTweeIwKJ//+Q= 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-20260806130833-ad9cc5cbdf9d h1:fr7vlv8+xPmAgClPXaXLFspjW2LtXY88CRZ9MB3fZe8= -go.mau.fi/whatsmeow v0.0.0-20260806130833-ad9cc5cbdf9d/go.mod h1:Iy/xVSuVU2payR26MB1hv0UZUWRraEn4qKZ7+VRHulg= +go.mau.fi/whatsmeow v0.0.0-20260806192107-39b719baa629 h1:q3Jfm1wTs48+b0yXYy3TVlIGi4OR6FvJByAppvYdHTY= +go.mau.fi/whatsmeow v0.0.0-20260806192107-39b719baa629/go.mod h1:Iy/xVSuVU2payR26MB1hv0UZUWRraEn4qKZ7+VRHulg= 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.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= From 3a31b5bd8f1ae2684bf6d34a110c69b1c144308d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 7 Aug 2026 12:21:17 +0300 Subject: [PATCH 83/94] 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 18468ba..2b0f16e 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260806192107-39b719baa629 + go.mau.fi/whatsmeow v0.0.0-20260806224404-e277b766ab33 golang.org/x/image v0.44.0 golang.org/x/net v0.57.0 golang.org/x/sync v0.22.0 diff --git a/go.sum b/go.sum index b543aa6..d01face 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391 h1:lsvBEY8MJfYdV61YbwikiQvb go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391/go.mod h1:xunp/oIQfFD68HHcNHfG0pOiHkvEtDhTweeIwKJ//+Q= 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-20260806192107-39b719baa629 h1:q3Jfm1wTs48+b0yXYy3TVlIGi4OR6FvJByAppvYdHTY= -go.mau.fi/whatsmeow v0.0.0-20260806192107-39b719baa629/go.mod h1:Iy/xVSuVU2payR26MB1hv0UZUWRraEn4qKZ7+VRHulg= +go.mau.fi/whatsmeow v0.0.0-20260806224404-e277b766ab33 h1:NTzJLmLB8ysR35TXPT8Dl4h2AHumaV4sKDX6JeJuVA4= +go.mau.fi/whatsmeow v0.0.0-20260806224404-e277b766ab33/go.mod h1:Iy/xVSuVU2payR26MB1hv0UZUWRraEn4qKZ7+VRHulg= 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.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= From b0be90d0d10c91bb1c96525995167b45abb6b1e6 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 10 Aug 2026 14:22:47 +0300 Subject: [PATCH 84/94] backfill: add hack for duplicate phone number messages --- pkg/connector/backfill.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 65e8f72..7356d11 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -12,6 +12,7 @@ import ( "time" "github.com/rs/zerolog" + "go.mau.fi/util/exmaps" "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waE2E" @@ -597,6 +598,7 @@ func (wa *WhatsAppClient) convertHistorySyncMessages( newestTS := messages[0].GetMessageTimestamp() convertedMessages := make([]*bridgev2.BackfillMessage, 0, len(messages)) var mediaRequests []*wadb.MediaRequest + dups := make(exmaps.Set[networkid.MessageID]) for i, msg := range messages { evt, err := wa.Client.ParseWebMessage(portalJID, msg) if err != nil { @@ -627,6 +629,11 @@ func (wa *WhatsAppClient) convertHistorySyncMessages( converted, mediaReq := wa.convertHistorySyncMessage( ctx, portal, &evt.Info, evt.Message, evt.RawMessage, isViewOnce, msg.Reactions, ) + // This is a hack to remove duplicates where the same message is inserted with both the LID and phone number sender + // TODO prevent those being inserted in the first place instead of hacking around it here + if !dups.Add(converted.ID) { + continue + } convertedMessages = append(convertedMessages, converted) if mediaReq != nil { mediaRequests = append(mediaRequests, mediaReq) From a13989e656eee32eebfbbbca88e57865d6e3c68c Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 10 Aug 2026 15:08:24 +0300 Subject: [PATCH 85/94] wadb/message: don't include anchor when querying backfill messages --- pkg/connector/wadb/message.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/connector/wadb/message.go b/pkg/connector/wadb/message.go index 4b16002..7a6c63d 100644 --- a/pkg/connector/wadb/message.go +++ b/pkg/connector/wadb/message.go @@ -96,12 +96,12 @@ func (mq *MessageQuery) GetBetween(ctx context.Context, loginID networkid.UserLo args := []any{mq.BridgeID, loginID, chatJID} argNum := 4 if startTime != nil { - whereClauses += fmt.Sprintf(" AND timestamp >= $%d", argNum) + whereClauses += fmt.Sprintf(" AND timestamp > $%d", argNum) args = append(args, startTime.Unix()) argNum++ } if endTime != nil { - whereClauses += fmt.Sprintf(" AND timestamp <= $%d", argNum) + whereClauses += fmt.Sprintf(" AND timestamp < $%d", argNum) args = append(args, endTime.Unix()) } From 1ee6deeb9e7622cd41d128d918f0b953fea0f940 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 11 Aug 2026 13:06:59 +0300 Subject: [PATCH 86/94] handlewhatsapp: ignore newsletter messages when bridging isn't possible --- pkg/connector/client.go | 4 ++++ pkg/connector/handlewhatsapp.go | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index c5a9348..08c71c5 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -56,6 +56,8 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. pushNamesSynced: exsync.NewEvent(), createDedup: exsync.NewSet[types.MessageID](), appStateFullSyncAttempted: make(map[appstate.WAPatchName]time.Time), + + disableNewsletter: store.BaseClientPayload.GetUserAgent().GetPlatform() == waWa6.ClientPayload_UserAgent_MACOS, } login.Client = w @@ -122,6 +124,8 @@ type WhatsAppClient struct { lastPresence types.Presence createDedup *exsync.Set[types.MessageID] + disableNewsletter bool + appStateRecoveryLock sync.Mutex appStateFullSyncAttempted map[appstate.WAPatchName]time.Time } diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index fce47df..ef2012f 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -285,6 +285,9 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa if evt.Info.Chat == types.StatusBroadcastJID && !wa.Main.Config.EnableStatusBroadcast { return } + if evt.Info.Chat.Server == types.NewsletterServer && wa.disableNewsletter { + return + } if !wa.ensureAltJIDs(ctx, &evt.Info.MessageSource, true) { return false } @@ -782,6 +785,9 @@ func (wa *WhatsAppClient) handleWAJoinedGroup(ctx context.Context, evt *events.J } func (wa *WhatsAppClient) handleWANewsletterJoin(ctx context.Context, evt *events.NewsletterJoin) bool { + if wa.disableNewsletter { + return true + } return wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatResync, From 07f0c835094eb64ee45a5864ef7324cffa5e8169 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 11 Aug 2026 15:22:56 +0300 Subject: [PATCH 87/94] 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 2b0f16e..5ad5c40 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.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.29.1-0.20260804113340-56938b8a508d + maunium.net/go/mautrix v0.29.1-0.20260811114840-3e65f58bc022 ) require ( diff --git a/go.sum b/go.sum index d01face..bdec40e 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.29.1-0.20260804113340-56938b8a508d h1:jbC6tXphot/X1UngnZLv7LcDTzaHBi2phwxrpyhtfrs= -maunium.net/go/mautrix v0.29.1-0.20260804113340-56938b8a508d/go.mod h1:PeLuIih5jnbwb2xKNNi0Te7AtINEQE2Uv3p6iG3RV80= +maunium.net/go/mautrix v0.29.1-0.20260811114840-3e65f58bc022 h1:g6mfG2ZZTI1x9wy/vJBB3R6/gLKq6+tR6h4FDnuW82E= +maunium.net/go/mautrix v0.29.1-0.20260811114840-3e65f58bc022/go.mod h1:PeLuIih5jnbwb2xKNNi0Te7AtINEQE2Uv3p6iG3RV80= From 80d69434fb266d4b4a1ba19d2ea53e6afc93c807 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 14 Aug 2026 15:34:27 +0300 Subject: [PATCH 88/94] dependencies: update whatsmeow --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/client.go | 2 -- pkg/connector/handlewhatsapp.go | 3 --- pkg/connector/startchat.go | 5 ----- 5 files changed, 3 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 5ad5c40..593f51e 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260806224404-e277b766ab33 + go.mau.fi/whatsmeow v0.0.0-20260814123134-0dcf1f50f4b1 golang.org/x/image v0.44.0 golang.org/x/net v0.57.0 golang.org/x/sync v0.22.0 diff --git a/go.sum b/go.sum index bdec40e..99259c0 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391 h1:lsvBEY8MJfYdV61YbwikiQvb go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391/go.mod h1:xunp/oIQfFD68HHcNHfG0pOiHkvEtDhTweeIwKJ//+Q= 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-20260806224404-e277b766ab33 h1:NTzJLmLB8ysR35TXPT8Dl4h2AHumaV4sKDX6JeJuVA4= -go.mau.fi/whatsmeow v0.0.0-20260806224404-e277b766ab33/go.mod h1:Iy/xVSuVU2payR26MB1hv0UZUWRraEn4qKZ7+VRHulg= +go.mau.fi/whatsmeow v0.0.0-20260814123134-0dcf1f50f4b1 h1:00/21vJJvBBkG7Ia/3EjE/DBiheg6/HrFyy8jWUQZr0= +go.mau.fi/whatsmeow v0.0.0-20260814123134-0dcf1f50f4b1/go.mod h1:Iy/xVSuVU2payR26MB1hv0UZUWRraEn4qKZ7+VRHulg= 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.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 08c71c5..b3ecdba 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -54,7 +54,6 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. directMediaRetries: make(map[networkid.MessageID]*directMediaRetry), mediaRetryLock: semaphore.NewWeighted(wa.Config.HistorySync.MediaRequests.MaxAsyncHandle), pushNamesSynced: exsync.NewEvent(), - createDedup: exsync.NewSet[types.MessageID](), appStateFullSyncAttempted: make(map[appstate.WAPatchName]time.Time), disableNewsletter: store.BaseClientPayload.GetUserAgent().GetPlatform() == waWa6.ClientPayload_UserAgent_MACOS, @@ -122,7 +121,6 @@ type WhatsAppClient struct { isNewLogin bool pushNamesSynced *exsync.Event lastPresence types.Presence - createDedup *exsync.Set[types.MessageID] disableNewsletter bool diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index ef2012f..509c589 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -770,9 +770,6 @@ func (wa *WhatsAppClient) handleWAGroupInfoChange(ctx context.Context, evt *even } func (wa *WhatsAppClient) handleWAJoinedGroup(ctx context.Context, evt *events.JoinedGroup) bool { - if wa.createDedup.Pop(evt.CreateKey) { - return true - } return wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatResync, diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index f0d869b..c123311 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -246,14 +246,9 @@ func (wa *WhatsAppClient) getContactList(ctx context.Context, filter string, onl } func (wa *WhatsAppClient) CreateGroup(ctx context.Context, params *bridgev2.GroupCreateParams) (*bridgev2.CreateChatResponse, error) { - createKey := wa.Client.GenerateMessageID() - if params.RoomID != "" { - wa.createDedup.Add(createKey) - } req := whatsmeow.ReqCreateGroup{ Name: ptr.Val(params.Name).Name, Participants: make([]types.JID, len(params.Participants)), - CreateKey: createKey, } for i, participant := range params.Participants { jid := waid.ParseUserID(participant) From e7e5e57d9ed9ea12e3e54c2d9d3dcdc998516fb5 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 16 Aug 2026 15:48:54 +0300 Subject: [PATCH 89/94] Bump version to v26.08 --- CHANGELOG.md | 4 +++ cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 28 +++++++++---------- go.sum | 52 ++++++++++++++++++------------------ 4 files changed, 45 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e47e7b2..4d17865 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# v26.08 + +* Switched direct chats to use LIDs instead of phone numbers. + # v26.07 * Updated Docker image to Alpine 3.24. diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 47d971c..011e9b0 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.07", + Version: "26.08", SemCalVer: true, Connector: &connector.WhatsAppConnector{}, } diff --git a/go.mod b/go.mod index 593f51e..ad3d58a 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module go.mau.fi/mautrix-whatsapp go 1.25.0 -toolchain go1.26.5 +toolchain go1.26.6 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.9.12-0.20260719092501-f9c03d846391 + go.mau.fi/util v0.10.0 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260814123134-0dcf1f50f4b1 - golang.org/x/image v0.44.0 - golang.org/x/net v0.57.0 + go.mau.fi/whatsmeow v0.0.0-20260816113502-fb386f152837 + 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.11 + google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.29.1-0.20260811114840-3e65f58bc022 + maunium.net/go/mautrix v0.30.0 ) 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.48 // indirect - github.com/petermattis/goid v0.0.0-20260713124913-97594f28f5ca // indirect + github.com/mattn/go-sqlite3 v1.14.49 // indirect + github.com/petermattis/goid v0.0.0-20260816044145-ed329add6b1b // 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 @@ -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.4 // indirect + github.com/yuin/goldmark v1.8.5 // indirect go.mau.fi/libsignal v0.2.2 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect - golang.org/x/crypto v0.54.0 // indirect - golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597 // indirect - golang.org/x/mod v0.38.0 // indirect + golang.org/x/crypto v0.55.0 // indirect + golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297 // indirect + golang.org/x/mod v0.40.0 // indirect golang.org/x/sys v0.47.0 // indirect - golang.org/x/text v0.40.0 // indirect + golang.org/x/text v0.41.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 99259c0..62f54b2 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.48 h1:7XHIgl0a8HwOaiK4E47ozLkST78rR9+OtNGx27D/TFs= -github.com/mattn/go-sqlite3 v1.14.48/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= -github.com/petermattis/goid v0.0.0-20260713124913-97594f28f5ca h1:GHSUVE4yOgX4E7kTRzpxCPbCOYkd3Kj8Dgdod30OI1E= -github.com/petermattis/goid v0.0.0-20260713124913-97594f28f5ca/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +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/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= @@ -67,37 +67,37 @@ 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.4 h1:oat/nd3U6NeQqFEL3xpEJq7d7c86NI+DbSNGAs4xnjA= -github.com/yuin/goldmark v1.8.4/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +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.9.12-0.20260719092501-f9c03d846391 h1:lsvBEY8MJfYdV61YbwikiQvb0Al/onbmLW5wfl/0tag= -go.mau.fi/util v0.9.12-0.20260719092501-f9c03d846391/go.mod h1:xunp/oIQfFD68HHcNHfG0pOiHkvEtDhTweeIwKJ//+Q= +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/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-20260814123134-0dcf1f50f4b1 h1:00/21vJJvBBkG7Ia/3EjE/DBiheg6/HrFyy8jWUQZr0= -go.mau.fi/whatsmeow v0.0.0-20260814123134-0dcf1f50f4b1/go.mod h1:Iy/xVSuVU2payR26MB1hv0UZUWRraEn4qKZ7+VRHulg= +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/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= -golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= -golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= -golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597 h1:qLvzZeaANDgyVOA8pyHCOStGlXn0rseXma+GQjeuv2g= -golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q= -golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I= -golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY= -golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= -golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= -golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= -golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +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/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/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.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= -golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +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= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -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.29.1-0.20260811114840-3e65f58bc022 h1:g6mfG2ZZTI1x9wy/vJBB3R6/gLKq6+tR6h4FDnuW82E= -maunium.net/go/mautrix v0.29.1-0.20260811114840-3e65f58bc022/go.mod h1:PeLuIih5jnbwb2xKNNi0Te7AtINEQE2Uv3p6iG3RV80= +maunium.net/go/mautrix v0.30.0 h1:bad+q7w5tLqiHpr+oUxVI+8m8ePbV3AvoFKg2jQzPyo= +maunium.net/go/mautrix v0.30.0/go.mod h1:bb0gjxbTFOqTaAYKGw5E7j9XROUR2Sl1Etm3IbmYXbo= From 0f1832595fcd61b859bf4326f50234c5511eac27 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Wed, 19 Aug 2026 15:41:15 +0100 Subject: [PATCH 90/94] 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 91/94] 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 92/94] 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 93/94] 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 94/94] 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=