From eb7127fb0ee334b86537cb62087ea1db4dcc870e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 12 Mar 2025 18:26:03 +0200 Subject: [PATCH 001/486] 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 1a10477c..8ed1a7f8 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.33.0 go.mau.fi/util v0.8.6-0.20250227184636-7ff63b0b9d95 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250311112832-01523b1e7109 + go.mau.fi/whatsmeow v0.0.0-20250312162510-2c51c6803f86 golang.org/x/image v0.24.0 golang.org/x/net v0.35.0 golang.org/x/sync v0.11.0 diff --git a/go.sum b/go.sum index c41b8270..21f5ded2 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ go.mau.fi/util v0.8.6-0.20250227184636-7ff63b0b9d95 h1:5EfVWWjU2Hte9uE6B/hBgvjnV go.mau.fi/util v0.8.6-0.20250227184636-7ff63b0b9d95/go.mod h1:Ycug9mrbztlahHPEJ6H5r8Nu/xqZaWbE5vPHVWmfz6M= 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-20250311112832-01523b1e7109 h1:/B6T0f6dmPKmld8uuZEhDTINJltxSBrUYA4ECsUQ9pE= -go.mau.fi/whatsmeow v0.0.0-20250311112832-01523b1e7109/go.mod h1:6hRrUtDWI2wTRClOd6m17GwrFE2a8/p5R4pjJsIVn+U= +go.mau.fi/whatsmeow v0.0.0-20250312162510-2c51c6803f86 h1:3VRr3I2SP5xgac5j+aveKtKJosHVEoBa979A4Xrg06w= +go.mau.fi/whatsmeow v0.0.0-20250312162510-2c51c6803f86/go.mod h1:6hRrUtDWI2wTRClOd6m17GwrFE2a8/p5R4pjJsIVn+U= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= From 652a21959c6569a69b63632eb7f50ceca245986c Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 14 Mar 2025 16:29:52 +0200 Subject: [PATCH 002/486] Update whatsmeow --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8ed1a7f8..05d57bbb 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.33.0 go.mau.fi/util v0.8.6-0.20250227184636-7ff63b0b9d95 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250312162510-2c51c6803f86 + go.mau.fi/whatsmeow v0.0.0-20250314142923-7f288886b30a golang.org/x/image v0.24.0 golang.org/x/net v0.35.0 golang.org/x/sync v0.11.0 diff --git a/go.sum b/go.sum index 21f5ded2..09a54fbe 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ go.mau.fi/util v0.8.6-0.20250227184636-7ff63b0b9d95 h1:5EfVWWjU2Hte9uE6B/hBgvjnV go.mau.fi/util v0.8.6-0.20250227184636-7ff63b0b9d95/go.mod h1:Ycug9mrbztlahHPEJ6H5r8Nu/xqZaWbE5vPHVWmfz6M= 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-20250312162510-2c51c6803f86 h1:3VRr3I2SP5xgac5j+aveKtKJosHVEoBa979A4Xrg06w= -go.mau.fi/whatsmeow v0.0.0-20250312162510-2c51c6803f86/go.mod h1:6hRrUtDWI2wTRClOd6m17GwrFE2a8/p5R4pjJsIVn+U= +go.mau.fi/whatsmeow v0.0.0-20250314142923-7f288886b30a h1:SA9klI4WzHszdHrwjb/4z2vKLTOPyrbJFyeX8YObJ54= +go.mau.fi/whatsmeow v0.0.0-20250314142923-7f288886b30a/go.mod h1:6hRrUtDWI2wTRClOd6m17GwrFE2a8/p5R4pjJsIVn+U= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= From 483a233536426e834ea9d7f3df8c9b719f0bea9b Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 16 Mar 2025 15:41:38 +0200 Subject: [PATCH 003/486] handlewhatsapp: store handled edits in metadata --- cmd/mautrix-whatsapp/legacyprovision.go | 2 +- go.mod | 18 ++++++------- go.sum | 36 ++++++++++++------------- pkg/connector/client.go | 2 +- pkg/connector/events.go | 6 +++++ pkg/connector/handlewhatsapp.go | 2 +- pkg/connector/login.go | 2 +- pkg/connector/phoneping.go | 2 +- pkg/waid/dbmeta.go | 34 ++++++++++++++++++----- 9 files changed, 65 insertions(+), 39 deletions(-) diff --git a/cmd/mautrix-whatsapp/legacyprovision.go b/cmd/mautrix-whatsapp/legacyprovision.go index d9b8517c..30e43c5a 100644 --- a/cmd/mautrix-whatsapp/legacyprovision.go +++ b/cmd/mautrix-whatsapp/legacyprovision.go @@ -13,9 +13,9 @@ import ( "go.mau.fi/util/exhttp" "go.mau.fi/whatsmeow/types" "maunium.net/go/mautrix" - "maunium.net/go/mautrix/bridge/status" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/matrix" + "maunium.net/go/mautrix/bridgev2/status" "maunium.net/go/mautrix/id" "go.mau.fi/mautrix-whatsapp/pkg/connector" diff --git a/go.mod b/go.mod index 1a10477c..25bffa99 100644 --- a/go.mod +++ b/go.mod @@ -9,15 +9,15 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.33.0 - go.mau.fi/util v0.8.6-0.20250227184636-7ff63b0b9d95 + go.mau.fi/util v0.8.6-0.20250316130503-05facedd4121 go.mau.fi/webp v0.2.0 go.mau.fi/whatsmeow v0.0.0-20250311112832-01523b1e7109 golang.org/x/image v0.24.0 - golang.org/x/net v0.35.0 - golang.org/x/sync v0.11.0 + golang.org/x/net v0.37.0 + golang.org/x/sync v0.12.0 google.golang.org/protobuf v1.36.5 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.23.2-0.20250311105500-a6d948f7c2bb + maunium.net/go/mautrix v0.23.2-0.20250316130555-c0d1df18b423 ) require ( @@ -28,7 +28,7 @@ require ( 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.24 // indirect - github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a // indirect + github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 // 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 @@ -39,10 +39,10 @@ require ( github.com/yuin/goldmark v1.7.8 // indirect go.mau.fi/libsignal v0.1.2 // indirect go.mau.fi/zeroconfig v0.1.3 // indirect - golang.org/x/crypto v0.33.0 // indirect - golang.org/x/exp v0.0.0-20250215185904-eff6e970281f // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.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 c41b8270..0cf6564b 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a h1:ckxP/kGzsxvxXo8jO6E/0QJ8MMmwI7IRj4Fys9QbAZA= -github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 h1:E7Kmf11E4K7B5hDti2K2NqPb1nlYlGYsu02S1JNd/Bs= +github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203/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= @@ -66,31 +66,31 @@ github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= go.mau.fi/libsignal v0.1.2 h1:Vs16DXWxSKyzVtI+EEXLCSy5pVWzzCzp/2eqFGvLyP0= go.mau.fi/libsignal v0.1.2/go.mod h1:JpnLSSJptn/s1sv7I56uEMywvz8x4YzxeF5OzdPb6PE= -go.mau.fi/util v0.8.6-0.20250227184636-7ff63b0b9d95 h1:5EfVWWjU2Hte9uE6B/hBgvjnVfBx/7SYDZBnsuo+EBs= -go.mau.fi/util v0.8.6-0.20250227184636-7ff63b0b9d95/go.mod h1:Ycug9mrbztlahHPEJ6H5r8Nu/xqZaWbE5vPHVWmfz6M= +go.mau.fi/util v0.8.6-0.20250316130503-05facedd4121 h1:d7KUA46BWjtyEwJjVSvtZnQln+lR3+cdvzw4z2nCyhM= +go.mau.fi/util v0.8.6-0.20250316130503-05facedd4121/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 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-20250311112832-01523b1e7109 h1:/B6T0f6dmPKmld8uuZEhDTINJltxSBrUYA4ECsUQ9pE= go.mau.fi/whatsmeow v0.0.0-20250311112832-01523b1e7109/go.mod h1:6hRrUtDWI2wTRClOd6m17GwrFE2a8/p5R4pjJsIVn+U= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/exp v0.0.0-20250215185904-eff6e970281f h1:oFMYAjX0867ZD2jcNiLBrI9BdpmEkvPyi5YrBGXbamg= -golang.org/x/exp v0.0.0-20250215185904-eff6e970281f/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -102,5 +102,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.23.2-0.20250311105500-a6d948f7c2bb h1:HAKsPqJiBsugs8qOy9mMmASEpmqpJ/Cbc/2Bj6wuNYQ= -maunium.net/go/mautrix v0.23.2-0.20250311105500-a6d948f7c2bb/go.mod h1:IHMaSJh7YIxMrZSDVefS+nLdr3RbeLowsCSa6ibONZ0= +maunium.net/go/mautrix v0.23.2-0.20250316130555-c0d1df18b423 h1:HX+j7DLkDncMYwanWRVk2hAutGesUJF/l3v1V2981kY= +maunium.net/go/mautrix v0.23.2-0.20250316130555-c0d1df18b423/go.mod h1:tbf67TwYJBH+83k4hJFhNa7C1Az1gMTHzldUR2rg/YM= diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 002219b2..1e01f5b2 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -34,9 +34,9 @@ import ( "go.mau.fi/whatsmeow/util/keys" waLog "go.mau.fi/whatsmeow/util/log" "golang.org/x/sync/semaphore" - "maunium.net/go/mautrix/bridge/status" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/networkid" + "maunium.net/go/mautrix/bridgev2/status" "go.mau.fi/mautrix-whatsapp/pkg/waid" ) diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 426e408b..0ba595c5 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "fmt" + "slices" "time" "github.com/rs/zerolog" @@ -142,6 +143,11 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por } else { editedMsg = evt.Message.GetProtocolMessage().GetEditedMessage() previouslyConvertedPart = evt.wa.Main.GetMediaEditCache(portal, evt.GetTargetMessage()) + meta := existing[0].Metadata.(*waid.MessageMetadata) + if slices.Contains(meta.Edits, evt.Info.ID) { + return nil, fmt.Errorf("%w: edit already handled", bridgev2.ErrIgnoringRemoteEvent) + } + meta.Edits = append(meta.Edits, evt.Info.ID) } cm := evt.wa.Main.MsgConv.ToMatrix(ctx, portal, evt.wa.Client, intent, editedMsg, &evt.Info, evt.isViewOnce(), previouslyConvertedPart) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index b53427f4..0e02a70b 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -28,10 +28,10 @@ import ( "go.mau.fi/whatsmeow/appstate" "go.mau.fi/whatsmeow/types" "go.mau.fi/whatsmeow/types/events" - "maunium.net/go/mautrix/bridge/status" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/networkid" "maunium.net/go/mautrix/bridgev2/simplevent" + "maunium.net/go/mautrix/bridgev2/status" "maunium.net/go/mautrix/event" "go.mau.fi/mautrix-whatsapp/pkg/waid" diff --git a/pkg/connector/login.go b/pkg/connector/login.go index cb267ef5..89e7abcb 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -12,9 +12,9 @@ import ( "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/types/events" waLog "go.mau.fi/whatsmeow/util/log" - "maunium.net/go/mautrix/bridge/status" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/database" + "maunium.net/go/mautrix/bridgev2/status" "go.mau.fi/mautrix-whatsapp/pkg/waid" ) diff --git a/pkg/connector/phoneping.go b/pkg/connector/phoneping.go index 308db456..cc430d1f 100644 --- a/pkg/connector/phoneping.go +++ b/pkg/connector/phoneping.go @@ -25,7 +25,7 @@ import ( "go.mau.fi/util/jsontime" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waE2E" - "maunium.net/go/mautrix/bridge/status" + "maunium.net/go/mautrix/bridgev2/status" "maunium.net/go/mautrix/event" "go.mau.fi/mautrix-whatsapp/pkg/waid" diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index b8ed7ee3..73987c23 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -70,13 +70,33 @@ type GroupInviteMeta struct { } type MessageMetadata struct { - SenderDeviceID uint16 `json:"sender_device_id,omitempty"` - Error MessageErrorType `json:"error,omitempty"` - BroadcastListJID *types.JID `json:"broadcast_list_jid,omitempty"` - GroupInvite *GroupInviteMeta `json:"group_invite,omitempty"` - FailedMediaMeta json.RawMessage `json:"media_meta,omitempty"` - DirectMediaMeta json.RawMessage `json:"direct_media_meta,omitempty"` - IsMatrixPoll bool `json:"is_matrix_poll,omitempty"` + SenderDeviceID uint16 `json:"sender_device_id,omitempty"` + Error MessageErrorType `json:"error,omitempty"` + BroadcastListJID *types.JID `json:"broadcast_list_jid,omitempty"` + GroupInvite *GroupInviteMeta `json:"group_invite,omitempty"` + FailedMediaMeta json.RawMessage `json:"media_meta,omitempty"` + DirectMediaMeta json.RawMessage `json:"direct_media_meta,omitempty"` + IsMatrixPoll bool `json:"is_matrix_poll,omitempty"` + Edits []types.MessageID `json:"edits,omitempty"` +} + +func (mm *MessageMetadata) CopyFrom(other any) { + otherMM := other.(*MessageMetadata) + mm.SenderDeviceID = otherMM.SenderDeviceID + mm.Error = otherMM.Error + if otherMM.BroadcastListJID != nil { + mm.BroadcastListJID = otherMM.BroadcastListJID + } + if otherMM.FailedMediaMeta != nil { + mm.FailedMediaMeta = otherMM.FailedMediaMeta + } + if otherMM.DirectMediaMeta != nil { + mm.DirectMediaMeta = otherMM.DirectMediaMeta + } + if otherMM.GroupInvite != nil { + mm.GroupInvite = otherMM.GroupInvite + } + mm.IsMatrixPoll = mm.IsMatrixPoll || otherMM.IsMatrixPoll } type ReactionMetadata struct { From e8067d89f45fc07e0cadf645059ac77c913e3a2d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 16 Mar 2025 16:53:31 +0200 Subject: [PATCH 004/486] Bump version to v0.11.4 --- CHANGELOG.md | 7 +++++++ cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 10 +++++----- go.sum | 16 ++++++++-------- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 161e8a03..ac7b1199 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# v0.11.4 (2025-03-16) + +* Fixed edits being bridged multiple times if a single chat had multiple + logged-in Matrix users. +* Fixed bridging some types of business messages that were supposed to be + supported (there are still some unsupported types). + # v0.11.3 (2025-02-16) * Bumped minimum Go version to 1.23. diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index ac5292b9..cc0dd041 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -23,7 +23,7 @@ var m = mxmain.BridgeMain{ Name: "mautrix-whatsapp", URL: "https://github.com/mautrix/whatsapp", Description: "A Matrix-WhatsApp puppeting bridge.", - Version: "0.11.3", + Version: "0.11.4", Connector: c, } diff --git a/go.mod b/go.mod index 25bffa99..95866207 100644 --- a/go.mod +++ b/go.mod @@ -2,22 +2,22 @@ module go.mau.fi/mautrix-whatsapp go 1.23.0 -toolchain go1.24.0 +toolchain go1.24.1 require ( github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.33.0 - go.mau.fi/util v0.8.6-0.20250316130503-05facedd4121 + go.mau.fi/util v0.8.6 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250311112832-01523b1e7109 - golang.org/x/image v0.24.0 + go.mau.fi/whatsmeow v0.0.0-20250316144733-e7e263bf2175 + golang.org/x/image v0.25.0 golang.org/x/net v0.37.0 golang.org/x/sync v0.12.0 google.golang.org/protobuf v1.36.5 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.23.2-0.20250316130555-c0d1df18b423 + maunium.net/go/mautrix v0.23.2 ) require ( diff --git a/go.sum b/go.sum index 0cf6564b..08cd8fe2 100644 --- a/go.sum +++ b/go.sum @@ -66,20 +66,20 @@ github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= go.mau.fi/libsignal v0.1.2 h1:Vs16DXWxSKyzVtI+EEXLCSy5pVWzzCzp/2eqFGvLyP0= go.mau.fi/libsignal v0.1.2/go.mod h1:JpnLSSJptn/s1sv7I56uEMywvz8x4YzxeF5OzdPb6PE= -go.mau.fi/util v0.8.6-0.20250316130503-05facedd4121 h1:d7KUA46BWjtyEwJjVSvtZnQln+lR3+cdvzw4z2nCyhM= -go.mau.fi/util v0.8.6-0.20250316130503-05facedd4121/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= +go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= +go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 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-20250311112832-01523b1e7109 h1:/B6T0f6dmPKmld8uuZEhDTINJltxSBrUYA4ECsUQ9pE= -go.mau.fi/whatsmeow v0.0.0-20250311112832-01523b1e7109/go.mod h1:6hRrUtDWI2wTRClOd6m17GwrFE2a8/p5R4pjJsIVn+U= +go.mau.fi/whatsmeow v0.0.0-20250316144733-e7e263bf2175 h1:BDShdc10qJzi3B0xPGA6HVQl+929wIFst8/W+8EnvbI= +go.mau.fi/whatsmeow v0.0.0-20250316144733-e7e263bf2175/go.mod h1:WNhj4JeQ6YR6dUOEiCXKqmE4LavSFkwRoKmu4atRrRs= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= -golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= -golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= +golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= +golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= @@ -102,5 +102,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.23.2-0.20250316130555-c0d1df18b423 h1:HX+j7DLkDncMYwanWRVk2hAutGesUJF/l3v1V2981kY= -maunium.net/go/mautrix v0.23.2-0.20250316130555-c0d1df18b423/go.mod h1:tbf67TwYJBH+83k4hJFhNa7C1Az1gMTHzldUR2rg/YM= +maunium.net/go/mautrix v0.23.2 h1:Bo3tPrQJwkxyL7aMmy/T+d2tqIrypZjHqeHe8fyeAOg= +maunium.net/go/mautrix v0.23.2/go.mod h1:pCYLHmo02Jauak/9VlTkbGPrBMvLXsGqTGMNOx+L2PE= From 7162ff3011c04a045960db0be302db846d5c64f3 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 17 Mar 2025 22:32:00 +0200 Subject: [PATCH 005/486] msgconv/wa-business: add fallback for buttons messages --- pkg/msgconv/from-whatsapp.go | 4 + pkg/msgconv/wa-business.go | 137 +++++++++++++++++++++++++---------- 2 files changed, 101 insertions(+), 40 deletions(-) diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index ffe83715..cfcd1422 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -115,6 +115,10 @@ func (mc *MessageConverter) ToMatrix( part, contextInfo = mc.convertTextMessage(ctx, waMsg) case waMsg.TemplateMessage != nil: part, contextInfo = mc.convertTemplateMessage(ctx, info, waMsg.TemplateMessage) + case waMsg.ButtonsMessage != nil: + part, contextInfo = mc.convertButtonsMessage(ctx, info, waMsg.ButtonsMessage) + case waMsg.ButtonsResponseMessage != nil: + part, contextInfo = mc.convertButtonsResponseMessage(ctx, waMsg.ButtonsResponseMessage) case waMsg.HighlyStructuredMessage != nil: part, contextInfo = mc.convertTemplateMessage(ctx, info, waMsg.HighlyStructuredMessage.GetHydratedHsm()) case waMsg.TemplateButtonReplyMessage != nil: diff --git a/pkg/msgconv/wa-business.go b/pkg/msgconv/wa-business.go index 4b352ecd..dae9c054 100644 --- a/pkg/msgconv/wa-business.go +++ b/pkg/msgconv/wa-business.go @@ -30,20 +30,18 @@ import ( ) func (mc *MessageConverter) convertTemplateMessage(ctx context.Context, info *types.MessageInfo, tplMsg *waE2E.TemplateMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { - converted := &bridgev2.ConvertedMessagePart{ - Type: event.EventMessage, - Content: &event.MessageEventContent{ - Body: "Unsupported business message (template)", - MsgType: event.MsgText, - }, - } - tpl := tplMsg.GetHydratedTemplate() if tpl == nil { tpl = tplMsg.GetHydratedFourRowTemplate() if tpl == nil { // TODO there are a few other types too - return converted, tplMsg.GetContextInfo() + return &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: &event.MessageEventContent{ + Body: "Unsupported business message (template)", + MsgType: event.MsgText, + }, + }, tplMsg.GetContextInfo() } } content := tpl.GetHydratedContentText() @@ -85,12 +83,86 @@ func (mc *MessageConverter) convertTemplateMessage(ctx context.Context, info *ty content = fmt.Sprintf("%s\n\n%s", title.HydratedTitleText, content) } - converted.Content.Body = content + converted := mc.postProcessBusinessMessage(content, convertedTitle) + converted.Extra["fi.mau.whatsapp.hydrated_template_id"] = tpl.GetTemplateID() + converted.Extra["fi.mau.whatsapp.business_message_type"] = "template" + return converted, tplMsg.GetContextInfo() +} + +func (mc *MessageConverter) convertTemplateButtonReplyMessage(ctx context.Context, msg *waE2E.TemplateButtonReplyMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { + return &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: &event.MessageEventContent{ + Body: msg.GetSelectedDisplayText(), + MsgType: event.MsgText, + }, + Extra: map[string]any{ + "fi.mau.whatsapp.template_button_reply": map[string]any{ + "id": msg.GetSelectedID(), + "index": msg.GetSelectedIndex(), + }, + }, + }, msg.GetContextInfo() +} + +func (mc *MessageConverter) convertButtonsMessage(ctx context.Context, info *types.MessageInfo, msg *waE2E.ButtonsMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { + content := msg.GetContentText() + if buttons := msg.GetButtons(); len(buttons) > 0 { + descriptions := make([]string, len(buttons)) + for i, button := range buttons { + descriptions[i] = fmt.Sprintf("<%s>", button.GetButtonText().GetDisplayText()) + } + content = fmt.Sprintf("%s\n\n%s\nUse the WhatsApp app to click buttons", content, strings.Join(descriptions, " - ")) + } + if footer := msg.GetFooterText(); footer != "" { + content = fmt.Sprintf("%s\n\n%s", content, footer) + } + var convertedHeader *bridgev2.ConvertedMessagePart + switch header := msg.GetHeader().(type) { + case *waE2E.ButtonsMessage_DocumentMessage: + convertedHeader, _ = mc.convertMediaMessage(ctx, header.DocumentMessage, "file attachment", info, false, nil) + case *waE2E.ButtonsMessage_ImageMessage: + convertedHeader, _ = mc.convertMediaMessage(ctx, header.ImageMessage, "photo", info, false, nil) + case *waE2E.ButtonsMessage_VideoMessage: + convertedHeader, _ = mc.convertMediaMessage(ctx, header.VideoMessage, "video attachment", info, false, nil) + case *waE2E.ButtonsMessage_LocationMessage: + content = fmt.Sprintf("Unsupported location message\n\n%s", content) + case *waE2E.ButtonsMessage_Text: + content = fmt.Sprintf("%s\n\n%s", header.Text, content) + } + converted := mc.postProcessBusinessMessage(content, convertedHeader) + converted.Extra["fi.mau.whatsapp.business_message_type"] = "buttons" + return converted, msg.GetContextInfo() +} + +func (mc *MessageConverter) convertButtonsResponseMessage(ctx context.Context, msg *waE2E.ButtonsResponseMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { + return &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: &event.MessageEventContent{ + Body: msg.GetSelectedDisplayText(), + MsgType: event.MsgText, + }, + Extra: map[string]any{ + "fi.mau.whatsapp.buttons_response_message": map[string]any{ + "selected_button_id": msg.GetSelectedButtonID(), + }, + }, + }, msg.GetContextInfo() +} + +func (mc *MessageConverter) postProcessBusinessMessage(content string, headerMediaPart *bridgev2.ConvertedMessagePart) *bridgev2.ConvertedMessagePart { + converted := &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: &event.MessageEventContent{ + MsgType: event.MsgText, + Body: content, + }, + } mc.parseFormatting(converted.Content, true, false) - if convertedTitle != nil { - converted.Content = convertedTitle.Content - converted.Extra = convertedTitle.Extra - converted.DBMetadata = convertedTitle.DBMetadata + if headerMediaPart != nil { + converted.Content = headerMediaPart.Content + converted.Extra = headerMediaPart.Extra + converted.DBMetadata = headerMediaPart.DBMetadata if content != "" { if converted.Content.FileName == "" || converted.Content.FileName == converted.Content.Body { converted.Content.FileName = converted.Content.Body @@ -113,34 +185,10 @@ func (mc *MessageConverter) convertTemplateMessage(ctx context.Context, info *ty if converted.Extra == nil { converted.Extra = make(map[string]any) } - converted.Extra["fi.mau.whatsapp.hydrated_template_id"] = tpl.GetTemplateID() - return converted, tplMsg.GetContextInfo() -} - -func (mc *MessageConverter) convertTemplateButtonReplyMessage(ctx context.Context, msg *waE2E.TemplateButtonReplyMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { - return &bridgev2.ConvertedMessagePart{ - Type: event.EventMessage, - Content: &event.MessageEventContent{ - Body: msg.GetSelectedDisplayText(), - MsgType: event.MsgText, - }, - Extra: map[string]any{ - "fi.mau.whatsapp.template_button_reply": map[string]any{ - "id": msg.GetSelectedID(), - "index": msg.GetSelectedIndex(), - }, - }, - }, msg.GetContextInfo() + return converted } func (mc *MessageConverter) convertListMessage(ctx context.Context, msg *waE2E.ListMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { - converted := &bridgev2.ConvertedMessagePart{ - Type: event.EventMessage, - Content: &event.MessageEventContent{ - Body: "Unsupported business message (list)", - MsgType: event.MsgText, - }, - } body := msg.GetDescription() if msg.GetTitle() != "" { if body == "" { @@ -154,7 +202,16 @@ func (mc *MessageConverter) convertListMessage(ctx context.Context, msg *waE2E.L if msg.GetFooterText() != "" { body = fmt.Sprintf("%s\n\n%s", body, msg.GetFooterText()) } - converted.Content.Body = body + converted := &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: &event.MessageEventContent{ + Body: body, + MsgType: event.MsgText, + }, + Extra: map[string]any{ + "fi.mau.whatsapp.business_message_type": "list", + }, + } mc.parseFormatting(converted.Content, false, true) var optionsMarkdown strings.Builder From 1611ca8c815094d4c8f48d77f1c95774300b3336 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 17 Mar 2025 22:43:38 +0200 Subject: [PATCH 006/486] msgconv/wa-business: add fallback for interactive messages --- pkg/connector/wamsgtype.go | 2 + pkg/msgconv/from-whatsapp.go | 4 ++ pkg/msgconv/wa-business.go | 83 ++++++++++++++++++++++++++++++++---- 3 files changed, 80 insertions(+), 9 deletions(-) diff --git a/pkg/connector/wamsgtype.go b/pkg/connector/wamsgtype.go index 0ff904fa..73727044 100644 --- a/pkg/connector/wamsgtype.go +++ b/pkg/connector/wamsgtype.go @@ -99,6 +99,8 @@ func getMessageType(waMsg *waE2E.Message) string { return "template button reply" case waMsg.InteractiveMessage != nil: return "interactive" + case waMsg.InteractiveResponseMessage != nil: + return "interactive response" case waMsg.ListMessage != nil: return "list" case waMsg.ProductMessage != nil: diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index cfcd1422..89bd15ca 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -119,6 +119,10 @@ func (mc *MessageConverter) ToMatrix( part, contextInfo = mc.convertButtonsMessage(ctx, info, waMsg.ButtonsMessage) case waMsg.ButtonsResponseMessage != nil: part, contextInfo = mc.convertButtonsResponseMessage(ctx, waMsg.ButtonsResponseMessage) + case waMsg.InteractiveMessage != nil: + part, contextInfo = mc.convertInteractiveMessage(ctx, info, waMsg.InteractiveMessage) + case waMsg.InteractiveResponseMessage != nil: + part, contextInfo = mc.convertInteractiveResponseMessage(ctx, waMsg.InteractiveResponseMessage) case waMsg.HighlyStructuredMessage != nil: part, contextInfo = mc.convertTemplateMessage(ctx, info, waMsg.HighlyStructuredMessage.GetHydratedHsm()) case waMsg.TemplateButtonReplyMessage != nil: diff --git a/pkg/msgconv/wa-business.go b/pkg/msgconv/wa-business.go index dae9c054..c3ed9761 100644 --- a/pkg/msgconv/wa-business.go +++ b/pkg/msgconv/wa-business.go @@ -33,16 +33,19 @@ func (mc *MessageConverter) convertTemplateMessage(ctx context.Context, info *ty tpl := tplMsg.GetHydratedTemplate() if tpl == nil { tpl = tplMsg.GetHydratedFourRowTemplate() - if tpl == nil { - // TODO there are a few other types too - return &bridgev2.ConvertedMessagePart{ - Type: event.EventMessage, - Content: &event.MessageEventContent{ - Body: "Unsupported business message (template)", - MsgType: event.MsgText, - }, - }, tplMsg.GetContextInfo() + } + if tpl == nil { + if interactiveMsg := tplMsg.GetInteractiveMessageTemplate(); interactiveMsg != nil { + return mc.convertInteractiveMessage(ctx, info, interactiveMsg) } + // TODO there are a few other types too + return &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: &event.MessageEventContent{ + Body: "Unsupported business message (template)", + MsgType: event.MsgText, + }, + }, tplMsg.GetContextInfo() } content := tpl.GetHydratedContentText() if buttons := tpl.GetHydratedButtons(); len(buttons) > 0 { @@ -105,6 +108,68 @@ func (mc *MessageConverter) convertTemplateButtonReplyMessage(ctx context.Contex }, msg.GetContextInfo() } +func (mc *MessageConverter) convertInteractiveMessage(ctx context.Context, info *types.MessageInfo, msg *waE2E.InteractiveMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { + content := msg.GetBody().GetText() + switch interactive := msg.GetInteractiveMessage().(type) { + case *waE2E.InteractiveMessage_ShopStorefrontMessage: + content = fmt.Sprintf("%s\n\nUnsupported shop storefront message", content) + case *waE2E.InteractiveMessage_CollectionMessage_: + // There doesn't seem to be much content here, maybe it's just meant to show the body/header/footer? + case *waE2E.InteractiveMessage_NativeFlowMessage_: + if buttons := interactive.NativeFlowMessage.GetButtons(); len(buttons) > 0 { + descriptions := make([]string, len(buttons)) + for i, button := range buttons { + descriptions[i] = fmt.Sprintf("<%s>", button.GetName()) + } + content = fmt.Sprintf("%s\n\n%s\nUse the WhatsApp app to click buttons", content, strings.Join(descriptions, " - ")) + } + case *waE2E.InteractiveMessage_CarouselMessage_: + content = fmt.Sprintf("%s\n\nUnsupported carousel message", content) + } + if footer := msg.GetFooter().GetText(); footer != "" { + content = fmt.Sprintf("%s\n\n%s", content, footer) + } + if title := msg.GetHeader().GetTitle(); title != "" { + if subtitle := msg.GetHeader().GetSubtitle(); subtitle != "" { + title = fmt.Sprintf("%s\n%s", title, subtitle) + } + content = fmt.Sprintf("%s\n\n%s", title, content) + } + + var convertedTitle *bridgev2.ConvertedMessagePart + switch headerMedia := msg.GetHeader().GetMedia().(type) { + case *waE2E.InteractiveMessage_Header_DocumentMessage: + convertedTitle, _ = mc.convertMediaMessage(ctx, headerMedia.DocumentMessage, "file attachment", info, false, nil) + case *waE2E.InteractiveMessage_Header_ImageMessage: + convertedTitle, _ = mc.convertMediaMessage(ctx, headerMedia.ImageMessage, "photo", info, false, nil) + case *waE2E.InteractiveMessage_Header_VideoMessage: + convertedTitle, _ = mc.convertMediaMessage(ctx, headerMedia.VideoMessage, "video attachment", info, false, nil) + case *waE2E.InteractiveMessage_Header_LocationMessage: + content = fmt.Sprintf("Unsupported location message\n\n%s", content) + case *waE2E.InteractiveMessage_Header_ProductMessage: + content = fmt.Sprintf("Unsupported product message\n\n%s", content) + case *waE2E.InteractiveMessage_Header_JPEGThumbnail: + content = fmt.Sprintf("Unsupported thumbnail message\n\n%s", content) + } + + converted := mc.postProcessBusinessMessage(content, convertedTitle) + converted.Extra["fi.mau.whatsapp.business_message_type"] = "interactive" + return converted, msg.GetContextInfo() +} + +func (mc *MessageConverter) convertInteractiveResponseMessage(ctx context.Context, msg *waE2E.InteractiveResponseMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { + return &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: &event.MessageEventContent{ + Body: msg.GetBody().GetText(), + MsgType: event.MsgText, + }, + Extra: map[string]any{ + "fi.mau.whatsapp.interactive_response_message": map[string]any{}, + }, + }, msg.GetContextInfo() +} + func (mc *MessageConverter) convertButtonsMessage(ctx context.Context, info *types.MessageInfo, msg *waE2E.ButtonsMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { content := msg.GetContentText() if buttons := msg.GetButtons(); len(buttons) > 0 { From c9220946da02a5ca059511e7aabae4356f70befb Mon Sep 17 00:00:00 2001 From: Ping Chen Date: Wed, 19 Mar 2025 20:18:28 +0900 Subject: [PATCH 007/486] client: implement HandleMatrixMembership (#765) --- pkg/connector/handlematrix.go | 48 +++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index 0b58c6f7..dfd214df 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -291,3 +291,51 @@ func (wa *WhatsAppClient) HandleMatrixTyping(ctx context.Context, msg *bridgev2. } return wa.Client.SendChatPresence(portalJID, chatPresence, mediaPresence) } + +func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2.MatrixMembershipChange) (bool, error) { + portalJID, err := waid.ParsePortalID(msg.Portal.ID) + if err != nil { + return false, err + } + + if msg.Portal.RoomType == database.RoomTypeDM { + switch msg.Type { + case bridgev2.Invite: + return false, fmt.Errorf("cannot invite additional user to dm") + default: + return false, nil + } + } + + changes := make([]types.JID, 1) + var action whatsmeow.ParticipantChange + + switch msg.Type { + case bridgev2.Invite: + action = whatsmeow.ParticipantChangeAdd + case bridgev2.Leave, bridgev2.Kick: + action = whatsmeow.ParticipantChangeRemove + default: + return false, nil + } + + switch target := msg.Target.(type) { + case *bridgev2.Ghost: + changes[0] = waid.ParseUserID(target.ID) + case *bridgev2.UserLogin: + ghost, err := target.Bridge.GetGhostByID(ctx, networkid.UserID(target.ID)) + if err != nil { + return false, fmt.Errorf("failed to get ghost for user: %w", err) + } + changes[0] = waid.ParseUserID(ghost.ID) + default: + return false, fmt.Errorf("cannot get target intent: unknown type: %T", target) + } + + _, err = wa.Client.UpdateGroupParticipants(portalJID, changes, action) + if err != nil { + return false, err + } + + return true, nil +} From 605c3fbaa16d9f1260921b59d27fd959ef0f14d7 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 20 Mar 2025 16:50:32 +0200 Subject: [PATCH 008/486] 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 95866207..c46c97a2 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( golang.org/x/sync v0.12.0 google.golang.org/protobuf v1.36.5 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.23.2 + maunium.net/go/mautrix v0.23.3-0.20250320134109-06f200da0d10 ) require ( diff --git a/go.sum b/go.sum index 08cd8fe2..caeb2795 100644 --- a/go.sum +++ b/go.sum @@ -102,5 +102,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.23.2 h1:Bo3tPrQJwkxyL7aMmy/T+d2tqIrypZjHqeHe8fyeAOg= -maunium.net/go/mautrix v0.23.2/go.mod h1:pCYLHmo02Jauak/9VlTkbGPrBMvLXsGqTGMNOx+L2PE= +maunium.net/go/mautrix v0.23.3-0.20250320134109-06f200da0d10 h1:XE2szxSvJVngk+qHqlKO2fiBZwcAp3oYq+9P5jlAm6w= +maunium.net/go/mautrix v0.23.3-0.20250320134109-06f200da0d10/go.mod h1:pCYLHmo02Jauak/9VlTkbGPrBMvLXsGqTGMNOx+L2PE= From e2bafd9c31138b7cef178e16c0d851f8e631d748 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 26 Mar 2025 14:26:07 +0200 Subject: [PATCH 009/486] handlewhatsapp: allow chats with @bot users --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/backfill.go | 2 +- pkg/connector/chatinfo.go | 6 +++++- pkg/connector/config.go | 8 ++++++-- pkg/connector/events.go | 2 +- pkg/connector/handlewhatsapp.go | 2 +- pkg/connector/id.go | 4 ++-- pkg/connector/mediarequest.go | 2 +- pkg/connector/startchat.go | 5 ++++- pkg/connector/userinfo.go | 3 +++ pkg/msgconv/wa-poll.go | 2 +- pkg/waid/id.go | 5 +++++ 13 files changed, 33 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index c46c97a2..5e8f1288 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.33.0 go.mau.fi/util v0.8.6 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250316144733-e7e263bf2175 + go.mau.fi/whatsmeow v0.0.0-20250326122510-493635f6f3f8 golang.org/x/image v0.25.0 golang.org/x/net v0.37.0 golang.org/x/sync v0.12.0 diff --git a/go.sum b/go.sum index caeb2795..6b03f421 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 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-20250316144733-e7e263bf2175 h1:BDShdc10qJzi3B0xPGA6HVQl+929wIFst8/W+8EnvbI= -go.mau.fi/whatsmeow v0.0.0-20250316144733-e7e263bf2175/go.mod h1:WNhj4JeQ6YR6dUOEiCXKqmE4LavSFkwRoKmu4atRrRs= +go.mau.fi/whatsmeow v0.0.0-20250326122510-493635f6f3f8 h1:3deXYpkmNQWzoBhaCBHHfA8PtZM714BzGknyrRINPzI= +go.mau.fi/whatsmeow v0.0.0-20250326122510-493635f6f3f8/go.mod h1:WNhj4JeQ6YR6dUOEiCXKqmE4LavSFkwRoKmu4atRrRs= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 7362fcf4..c630cf0d 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -400,7 +400,7 @@ func (wa *WhatsAppClient) convertHistorySyncMessage( sender = wa.JID } else if reaction.GetKey().GetParticipant() != "" { sender, _ = types.ParseJID(*reaction.Key.Participant) - } else if info.Chat.Server == types.DefaultUserServer { + } else if info.Chat.Server == types.DefaultUserServer || info.Chat.Server == types.BotServer { sender = info.Chat } if sender.IsEmpty() { diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 969ea7d6..12909a1b 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -31,7 +31,7 @@ func (wa *WhatsAppClient) GetChatInfo(ctx context.Context, portal *bridgev2.Port func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, conv *wadb.Conversation) (wrapped *bridgev2.ChatInfo, err error) { switch portalJID.Server { - case types.DefaultUserServer: + case types.DefaultUserServer, types.BotServer: wrapped = wa.wrapDMInfo(portalJID) case types.BroadcastServer: if portalJID == types.StatusBroadcastJID { @@ -131,6 +131,7 @@ const StatusBroadcastName = "WhatsApp Status Broadcast" const BroadcastTopic = "WhatsApp broadcast list" const UnnamedBroadcastName = "Unnamed broadcast list" const PrivateChatTopic = "WhatsApp private chat" +const BotChatTopic = "WhatsApp chat with a bot" func (wa *WhatsAppClient) wrapDMInfo(jid types.JID) *bridgev2.ChatInfo { info := &bridgev2.ChatInfo{ @@ -147,6 +148,9 @@ func (wa *WhatsAppClient) wrapDMInfo(jid types.JID) *bridgev2.ChatInfo { }, Type: ptr.Ptr(database.RoomTypeDM), } + if jid.Server == types.BotServer { + info.Topic = ptr.Ptr(BotChatTopic) + } if jid == wa.JID.ToNonAD() { // 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{ diff --git a/pkg/connector/config.go b/pkg/connector/config.go index b947ac35..11146098 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -143,12 +143,16 @@ type DisplaynameParams struct { func (c *Config) FormatDisplayname(jid types.JID, contact types.ContactInfo) string { var nameBuf strings.Builder + phone := "+" + jid.User + if jid.Server == types.BotServer { + phone = jid.User + } err := c.displaynameTemplate.Execute(&nameBuf, &DisplaynameParams{ ContactInfo: contact, - Phone: "+" + jid.User, + Phone: phone, // Deprecated legacy fields - JID: "+" + jid.User, + JID: phone, Notify: contact.PushName, VName: contact.BusinessName, Name: contact.FullName, diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 0ba595c5..8a1ef7b9 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -342,7 +342,7 @@ 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 { + } else if sender.IsEmpty() && (evt.ChatID.Server == types.DefaultUserServer || evt.ChatID.Server == types.BotServer) { sender = evt.ChatID.ToNonAD() } return sender diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 0e02a70b..4f5df5fc 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -498,7 +498,7 @@ func (wa *WhatsAppClient) syncGhost(jid types.JID, reason string, pictureID *str } func (wa *WhatsAppClient) handleWAPictureUpdate(evt *events.Picture) { - if evt.JID.Server == types.DefaultUserServer { + if evt.JID.Server == types.DefaultUserServer || evt.JID.Server == types.BotServer { wa.syncGhost(evt.JID, "picture event", &evt.PictureID) } else { var changes bridgev2.ChatInfo diff --git a/pkg/connector/id.go b/pkg/connector/id.go index f4c62496..a605fa71 100644 --- a/pkg/connector/id.go +++ b/pkg/connector/id.go @@ -15,7 +15,7 @@ func (wa *WhatsAppClient) makeWAPortalKey(chatJID types.JID) networkid.PortalKey ID: waid.MakePortalID(chatJID), } switch chatJID.Server { - case types.DefaultUserServer, types.HiddenUserServer, types.BroadcastServer: + case types.DefaultUserServer, types.BotServer, types.HiddenUserServer, types.BroadcastServer: key.Receiver = wa.UserLogin.ID default: if wa.Main.Bridge.Config.SplitPortals { @@ -45,7 +45,7 @@ func (wa *WhatsAppClient) messageIDToKey(id *waid.ParsedMessageID) *waCommon.Mes if id.Sender.User == string(wa.UserLogin.ID) { key.FromMe = ptr.Ptr(true) } - if id.Chat.Server != types.MessengerServer && id.Chat.Server != types.DefaultUserServer { + if id.Chat.Server != types.MessengerServer && id.Chat.Server != types.DefaultUserServer && id.Chat.Server != types.BotServer { key.Participant = ptr.Ptr(id.Sender.String()) } return key diff --git a/pkg/connector/mediarequest.go b/pkg/connector/mediarequest.go index 82907446..e0f4c5b9 100644 --- a/pkg/connector/mediarequest.go +++ b/pkg/connector/mediarequest.go @@ -157,7 +157,7 @@ func (wa *WhatsAppClient) sendMediaRequestDirect(rawMsgID networkid.MessageID, k ID: msgID.ID, MessageSource: types.MessageSource{ IsFromMe: msgID.Sender.User == wa.JID.User, - IsGroup: msgID.Chat.Server != types.DefaultUserServer, + IsGroup: msgID.Chat.Server != types.DefaultUserServer && msgID.Chat.Server != types.BotServer, Sender: msgID.Sender, Chat: msgID.Chat, }, diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index 602b49ee..a52b106d 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -53,6 +53,9 @@ func looksEmaily(str string) bool { } func (wa *WhatsAppClient) validateIdentifer(number string) (types.JID, error) { + if strings.HasSuffix(number, "@"+types.BotServer) { + return types.ParseJID(number) + } if strings.HasSuffix(number, "@"+types.DefaultUserServer) { jid, _ := types.ParseJID(number) number = "+" + jid.User @@ -86,7 +89,7 @@ func (wa *WhatsAppConnector) ValidateUserID(id networkid.UserID) bool { switch jid.Server { case types.DefaultUserServer: return len(jid.User) <= 13 && (jid.User == "0" || len(jid.User) >= 7) && isOnlyNumbers(jid.User) - case types.HiddenUserServer: + case types.HiddenUserServer, types.BotServer: return true default: return false diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index b8873ede..54519ebf 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -186,6 +186,9 @@ func (wa *WhatsAppClient) contactToUserInfo(jid types.JID, contact types.Contact Identifiers: []string{fmt.Sprintf("tel:+%s", jid.User)}, ExtraUpdates: updateGhostLastSyncAt, } + if jid.Server == types.BotServer { + ui.Identifiers = []string{} + } if getAvatar { ui.ExtraUpdates = bridgev2.MergeExtraUpdaters(ui.ExtraUpdates, wa.fetchGhostAvatar) } diff --git a/pkg/msgconv/wa-poll.go b/pkg/msgconv/wa-poll.go index 38801dc9..10876847 100644 --- a/pkg/msgconv/wa-poll.go +++ b/pkg/msgconv/wa-poll.go @@ -108,7 +108,7 @@ func KeyToMessageID(client *whatsmeow.Client, chat, sender types.JID, key *waCom if sender.Server == types.LegacyUserServer { sender.Server = types.DefaultUserServer } - } else if chat.Server == types.DefaultUserServer { + } else if chat.Server == types.DefaultUserServer || chat.Server == types.BotServer { ownID := ptr.Val(client.Store.ID).ToNonAD() if sender.User == ownID.User { sender = chat diff --git a/pkg/waid/id.go b/pkg/waid/id.go index a4bec825..840e0f12 100644 --- a/pkg/waid/id.go +++ b/pkg/waid/id.go @@ -37,11 +37,14 @@ func ParsePortalID(portal networkid.PortalID) (types.JID, error) { } const LIDPrefix = "lid-" +const BotPrefix = "bot-" func MakeUserID(user types.JID) networkid.UserID { switch user.Server { case types.DefaultUserServer: return networkid.UserID(user.User) + case types.BotServer: + return networkid.UserID(BotPrefix + user.User) case types.HiddenUserServer: return networkid.UserID(LIDPrefix + user.User) default: @@ -52,6 +55,8 @@ func MakeUserID(user types.JID) networkid.UserID { func ParseUserID(user networkid.UserID) types.JID { if strings.HasPrefix(string(user), LIDPrefix) { return types.NewJID(strings.TrimPrefix(string(user), LIDPrefix), types.HiddenUserServer) + } else if strings.HasPrefix(string(user), BotPrefix) { + return types.NewJID(strings.TrimPrefix(string(user), BotPrefix), types.BotServer) } return types.NewJID(string(user), types.DefaultUserServer) } From 037bdac783b370e8475b2e20523dd152a033bcf7 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 8 Apr 2025 19:42:10 +0300 Subject: [PATCH 010/486] connector: allow messages from @lid users --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/backfill.go | 3 --- pkg/connector/connector.go | 21 +++++++++++++++++---- pkg/connector/handlewhatsapp.go | 19 +++++++++++++++---- pkg/connector/login.go | 1 + pkg/connector/startchat.go | 2 +- pkg/waid/dbmeta.go | 1 + 8 files changed, 38 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 5e8f1288..86459913 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.33.0 go.mau.fi/util v0.8.6 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250326122510-493635f6f3f8 + go.mau.fi/whatsmeow v0.0.0-20250408181558-1f39429905e6 golang.org/x/image v0.25.0 golang.org/x/net v0.37.0 golang.org/x/sync v0.12.0 diff --git a/go.sum b/go.sum index 6b03f421..1d3a267c 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 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-20250326122510-493635f6f3f8 h1:3deXYpkmNQWzoBhaCBHHfA8PtZM714BzGknyrRINPzI= -go.mau.fi/whatsmeow v0.0.0-20250326122510-493635f6f3f8/go.mod h1:WNhj4JeQ6YR6dUOEiCXKqmE4LavSFkwRoKmu4atRrRs= +go.mau.fi/whatsmeow v0.0.0-20250408181558-1f39429905e6 h1:kzk8uXsUYpfWacTRjt8Ej5HqEi75Z+q5iABmLIgzJ4Q= +go.mau.fi/whatsmeow v0.0.0-20250408181558-1f39429905e6/go.mod h1:aj6MpceCuJ6m712lPEMAh8iAw2TkABx6TnPjMcdG0eE= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index c630cf0d..2c9bccc0 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -97,9 +97,6 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor } else if jid.Server == types.BroadcastServer { log.Debug().Stringer("chat_jid", jid).Msg("Skipping broadcast list in history sync") continue - } else if jid.Server == types.HiddenUserServer { - log.Debug().Stringer("chat_jid", jid).Msg("Skipping hidden user JID chat in history sync") - continue } totalMessageCount += len(conv.GetMessages()) log := log.With(). diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 780aac3b..17808c0b 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -18,14 +18,17 @@ package connector import ( "context" + "fmt" "strings" "sync" "sync/atomic" + "go.mau.fi/util/dbutil" "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" waLog "go.mau.fi/whatsmeow/util/log" "google.golang.org/protobuf/proto" "maunium.net/go/mautrix/bridgev2" @@ -93,10 +96,14 @@ func (wa *WhatsAppConnector) Init(bridge *bridgev2.Bridge) { ) wa.mediaEditCache = make(MediaEditCache) - wa.DeviceStore = sqlstore.NewWithDB( - bridge.DB.RawDB, - bridge.DB.Dialect.String(), - waLog.Zerolog(bridge.Log.With().Str("db_section", "whatsmeow").Logger()), + whatsmeowDBLog := bridge.Log.With().Str("db_section", "whatsmeow").Logger() + wa.DeviceStore = sqlstore.NewWithWrappedDB( + bridge.DB.Child( + "whatsmeow_version", + whatsmeowUpgrades.Table, + dbutil.ZeroLogger(whatsmeowDBLog), + ), + waLog.Zerolog(whatsmeowDBLog), ) store.DeviceProps.Os = proto.String(wa.Config.OSName) @@ -119,6 +126,12 @@ func (wa *WhatsAppConnector) Start(ctx context.Context) error { if err != nil { return bridgev2.DBUpgradeError{Err: err, Section: "whatsmeow"} } + if !wa.Bridge.Background { + err = wa.DeviceStore.LIDMap.FillCache(ctx) + if err != nil { + return fmt.Errorf("failed to fill LID cache: %w", err) + } + } err = wa.DB.Upgrade(ctx) if err != nil { return bridgev2.DBUpgradeError{Err: err, Section: "whatsapp"} diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 4f5df5fc..fcf73a21 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -165,6 +165,20 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) { } }() } + meta := wa.UserLogin.Metadata.(*waid.UserLoginMetadata) + if meta.WALID == "" { + meta.WALID = wa.Client.Store.GetLID().User + if meta.WALID != "" { + go func() { + err := wa.UserLogin.Save(log.WithContext(context.Background())) + if err != nil { + log.Err(err).Msg("Failed to save user login metadata after updating LID") + } else { + log.Info().Msg("Updated LID in user login metadata") + } + }() + } + } case *events.OfflineSyncPreview: log.Info(). Int("message_count", evt.Messages). @@ -243,9 +257,6 @@ func (wa *WhatsAppClient) handleWAMessage(evt *events.Message) { Any("info", evt.Info). Any("payload", evt.Message). Msg("Received WhatsApp message") - if evt.Info.Chat.Server == types.HiddenUserServer || evt.Info.Sender.Server == types.HiddenUserServer { - return - } if evt.Info.Chat == types.StatusBroadcastJID && !wa.Main.Config.EnableStatusBroadcast { return } @@ -272,7 +283,7 @@ func (wa *WhatsAppClient) handleWAUndecryptableMessage(evt *events.Undecryptable Str("decrypt_fail", string(evt.DecryptFailMode)). Msg("Received undecryptable WhatsApp message") wa.trackUndecryptable(evt) - if evt.DecryptFailMode == events.DecryptFailHide || evt.Info.Chat.Server == types.HiddenUserServer || evt.Info.Sender.Server == types.HiddenUserServer { + if evt.DecryptFailMode == events.DecryptFailHide { return } if evt.Info.Chat == types.StatusBroadcastJID && !wa.Main.Config.EnableStatusBroadcast { diff --git a/pkg/connector/login.go b/pkg/connector/login.go index 89e7abcb..cc04b6f7 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -306,6 +306,7 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { Name: wl.LoginSuccess.BusinessName, }, Metadata: &waid.UserLoginMetadata{ + WALID: wl.LoginSuccess.LID.User, WADeviceID: wl.LoginSuccess.ID.Device, Timezone: wl.Timezone, diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index a52b106d..3e3ca14a 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -90,7 +90,7 @@ func (wa *WhatsAppConnector) ValidateUserID(id networkid.UserID) bool { case types.DefaultUserServer: return len(jid.User) <= 13 && (jid.User == "0" || len(jid.User) >= 7) && isOnlyNumbers(jid.User) case types.HiddenUserServer, types.BotServer: - return true + return len(jid.User) >= 12 default: return false } diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index 73987c23..0a92ddd7 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -29,6 +29,7 @@ import ( type UserLoginMetadata struct { WADeviceID uint16 `json:"wa_device_id"` + WALID string `json:"wa_lid"` PhoneLastSeen jsontime.Unix `json:"phone_last_seen"` PhoneLastPinged jsontime.Unix `json:"phone_last_pinged"` Timezone string `json:"timezone"` From 68d732aa5149bd0163753993ca11450dd06972aa Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 8 Apr 2025 21:00:38 +0300 Subject: [PATCH 011/486] handlewhatsapp: handle announcement group comments --- go.mod | 2 +- go.sum | 4 +-- pkg/connector/capabilities.go | 15 ++++++++- pkg/connector/chatinfo.go | 36 ++++++++++---------- pkg/connector/events.go | 3 +- pkg/connector/handlematrix.go | 60 ++++++++++++++++++++++++--------- pkg/connector/handlewhatsapp.go | 18 ++++++++++ pkg/connector/id.go | 6 ++-- pkg/connector/wamsgtype.go | 4 +++ pkg/msgconv/from-matrix.go | 47 ++++++++++++++++++++++---- pkg/msgconv/from-whatsapp.go | 23 +++++++++++++ pkg/msgconv/matrixpoll.go | 24 +++++++------ pkg/waid/dbmeta.go | 5 +-- 13 files changed, 186 insertions(+), 61 deletions(-) diff --git a/go.mod b/go.mod index 86459913..13ecdcc4 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.33.0 go.mau.fi/util v0.8.6 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250408181558-1f39429905e6 + go.mau.fi/whatsmeow v0.0.0-20250408193601-9910d4800e47 golang.org/x/image v0.25.0 golang.org/x/net v0.37.0 golang.org/x/sync v0.12.0 diff --git a/go.sum b/go.sum index 1d3a267c..60b40bf5 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 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-20250408181558-1f39429905e6 h1:kzk8uXsUYpfWacTRjt8Ej5HqEi75Z+q5iABmLIgzJ4Q= -go.mau.fi/whatsmeow v0.0.0-20250408181558-1f39429905e6/go.mod h1:aj6MpceCuJ6m712lPEMAh8iAw2TkABx6TnPjMcdG0eE= +go.mau.fi/whatsmeow v0.0.0-20250408193601-9910d4800e47 h1:7V+QLw7WcAn1vSZuqDv0loFjTyejpjez/wFDLCxFR54= +go.mau.fi/whatsmeow v0.0.0-20250408193601-9910d4800e47/go.mod h1:aj6MpceCuJ6m712lPEMAh8iAw2TkABx6TnPjMcdG0eE= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= diff --git a/pkg/connector/capabilities.go b/pkg/connector/capabilities.go index 795460b9..306f1803 100644 --- a/pkg/connector/capabilities.go +++ b/pkg/connector/capabilities.go @@ -7,9 +7,10 @@ import ( "go.mau.fi/util/ffmpeg" "go.mau.fi/util/jsontime" "go.mau.fi/util/ptr" - "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/event" + + "go.mau.fi/mautrix-whatsapp/pkg/waid" ) var WhatsAppGeneralCaps = &bridgev2.NetworkGeneralCapabilities{ @@ -148,6 +149,18 @@ var whatsappCaps = &event.RoomFeatures{ TypingNotifications: true, } +var whatsappCAGCaps *event.RoomFeatures + +func init() { + whatsappCAGCaps = ptr.Clone(whatsappCaps) + whatsappCAGCaps.ID = capID() + "+cag" + whatsappCAGCaps.Reply = event.CapLevelUnsupported + whatsappCAGCaps.Thread = event.CapLevelFullySupported +} + func (wa *WhatsAppClient) GetCapabilities(ctx context.Context, portal *bridgev2.Portal) *event.RoomFeatures { + if portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup { + return whatsappCAGCaps + } return whatsappCaps } diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 12909a1b..1f8582c8 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -31,7 +31,7 @@ func (wa *WhatsAppClient) GetChatInfo(ctx context.Context, portal *bridgev2.Port func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, conv *wadb.Conversation) (wrapped *bridgev2.ChatInfo, err error) { switch portalJID.Server { - case types.DefaultUserServer, types.BotServer: + case types.DefaultUserServer, types.HiddenUserServer, types.BotServer: wrapped = wa.wrapDMInfo(portalJID) case types.BroadcastServer: if portalJID == types.StatusBroadcastJID { @@ -191,15 +191,30 @@ const ( defaultPL = 0 ) +func setDefaultSubGroupFlag(isCommunityAnnouncementGroup bool) bridgev2.ExtraUpdater[*bridgev2.Portal] { + return func(_ context.Context, portal *bridgev2.Portal) bool { + meta := portal.Metadata.(*waid.PortalMetadata) + if meta.CommunityAnnouncementGroup != isCommunityAnnouncementGroup { + meta.CommunityAnnouncementGroup = isCommunityAnnouncementGroup + return true + } + return false + } +} + func (wa *WhatsAppClient) wrapGroupInfo(info *types.GroupInfo) *bridgev2.ChatInfo { sendEventPL := defaultPL - if info.IsAnnounce { + if info.IsAnnounce && !info.IsDefaultSubGroup { sendEventPL = adminPL } metaChangePL := defaultPL if info.IsLocked { metaChangePL = adminPL } + extraUpdater := bridgev2.MergeExtraUpdaters( + wa.makePortalAvatarFetcher("", types.EmptyJID, time.Time{}), + setDefaultSubGroupFlag(info.IsDefaultSubGroup && info.IsAnnounce), + ) wrapped := &bridgev2.ChatInfo{ Name: ptr.Ptr(info.Name), Topic: ptr.Ptr(info.Topic), @@ -226,12 +241,9 @@ func (wa *WhatsAppClient) wrapGroupInfo(info *types.GroupInfo) *bridgev2.ChatInf Type: database.DisappearingTypeAfterRead, Timer: time.Duration(info.DisappearingTimer) * time.Second, }, - ExtraUpdates: wa.makePortalAvatarFetcher("", types.EmptyJID, time.Time{}), + ExtraUpdates: extraUpdater, } for _, pcp := range info.Participants { - if pcp.JID.Server != types.DefaultUserServer { - continue - } member := bridgev2.ChatMember{ EventSender: wa.makeEventSender(pcp.JID), Membership: event.MembershipJoin, @@ -290,35 +302,23 @@ func (wa *WhatsAppClient) wrapGroupInfoChange(evt *events.GroupInfo) *bridgev2.C MemberMap: make(map[networkid.UserID]bridgev2.ChatMember), } for _, userID := range evt.Join { - if userID.Server != types.DefaultUserServer { - continue - } memberChanges.MemberMap[waid.MakeUserID(userID)] = bridgev2.ChatMember{ EventSender: wa.makeEventSender(userID), } } for _, userID := range evt.Promote { - if userID.Server != types.DefaultUserServer { - continue - } memberChanges.MemberMap[waid.MakeUserID(userID)] = bridgev2.ChatMember{ EventSender: wa.makeEventSender(userID), PowerLevel: ptr.Ptr(adminPL), } } for _, userID := range evt.Demote { - if userID.Server != types.DefaultUserServer { - continue - } memberChanges.MemberMap[waid.MakeUserID(userID)] = bridgev2.ChatMember{ EventSender: wa.makeEventSender(userID), PowerLevel: ptr.Ptr(defaultPL), } } for _, userID := range evt.Leave { - if userID.Server != types.DefaultUserServer { - continue - } memberChanges.MemberMap[waid.MakeUserID(userID)] = bridgev2.ChatMember{ EventSender: wa.makeEventSender(userID), Membership: event.MembershipLeave, diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 8a1ef7b9..970c5e06 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -186,7 +186,7 @@ func (evt *WAMessageEvent) GetRemovedEmojiID() networkid.EmojiID { func (evt *WAMessageEvent) GetType() bridgev2.RemoteEventType { switch evt.parsedMessageType { - case "reaction": + case "reaction", "encrypted reaction": return bridgev2.RemoteEventReaction case "reaction remove": return bridgev2.RemoteEventReactionRemove @@ -297,6 +297,7 @@ func (evt *WAUndecryptableMessage) ConvertMessage(ctx context.Context, portal *b Body: "You received a view once message. For added privacy, you can only open it on the WhatsApp app.", } } + // TODO thread root for comments return &bridgev2.ConvertedMessage{ Parts: []*bridgev2.ConvertedMessagePart{{ Type: event.EventMessage, diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index dfd214df..f3ada57a 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -7,6 +7,7 @@ import ( "time" "github.com/rs/zerolog" + "go.mau.fi/util/ptr" "go.mau.fi/util/variationselector" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waE2E" @@ -17,6 +18,7 @@ import ( "maunium.net/go/mautrix/bridgev2/networkid" "maunium.net/go/mautrix/event" + "go.mau.fi/mautrix-whatsapp/pkg/msgconv" "go.mau.fi/mautrix-whatsapp/pkg/waid" ) @@ -34,7 +36,7 @@ func (wa *WhatsAppClient) HandleMatrixPollStart(ctx context.Context, msg *bridge if err != nil { return nil, fmt.Errorf("failed to convert poll vote: %w", err) } - resp, err := wa.handleConvertedMatrixMessage(ctx, &msg.MatrixMessage, waMsg) + resp, err := wa.handleConvertedMatrixMessage(ctx, &msg.MatrixMessage, waMsg, nil) if err != nil { return nil, err } @@ -53,22 +55,25 @@ func (wa *WhatsAppClient) HandleMatrixPollVote(ctx context.Context, msg *bridgev if err != nil { return nil, fmt.Errorf("failed to convert poll vote: %w", err) } - return wa.handleConvertedMatrixMessage(ctx, &msg.MatrixMessage, waMsg) + return wa.handleConvertedMatrixMessage(ctx, &msg.MatrixMessage, waMsg, nil) } func (wa *WhatsAppClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage) (*bridgev2.MatrixMessageResponse, error) { - waMsg, err := wa.Main.MsgConv.ToWhatsApp(ctx, wa.Client, msg.Event, msg.Content, msg.ReplyTo, msg.Portal) + 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) } - return wa.handleConvertedMatrixMessage(ctx, msg, waMsg) + return wa.handleConvertedMatrixMessage(ctx, msg, waMsg, req) } var ErrBroadcastSendDisabled = bridgev2.WrapErrorInStatus(errors.New("sending status messages is disabled")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported) var ErrBroadcastReactionUnsupported = bridgev2.WrapErrorInStatus(errors.New("reacting to status messages is not currently supported")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported) -func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage, waMsg *waE2E.Message) (*bridgev2.MatrixMessageResponse, error) { - messageID := wa.Client.GenerateMessageID() +func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage, waMsg *waE2E.Message, req *whatsmeow.SendRequestExtra) (*bridgev2.MatrixMessageResponse, error) { + if req == nil { + req = &whatsmeow.SendRequestExtra{} + } + req.ID = wa.Client.GenerateMessageID() chatJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return nil, err @@ -76,25 +81,33 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg if chatJID == types.StatusBroadcastJID && wa.Main.Config.DisableStatusBroadcastSend { return nil, ErrBroadcastSendDisabled } - wrappedMsgID := waid.MakeMessageID(chatJID, wa.JID, messageID) + wrappedMsgID := waid.MakeMessageID(chatJID, wa.JID, req.ID) + wrappedMsgID2 := waid.MakeMessageID(chatJID, wa.Device.GetLID(), req.ID) msg.AddPendingToIgnore(networkid.TransactionID(wrappedMsgID)) - resp, err := wa.Client.SendMessage(ctx, chatJID, waMsg, whatsmeow.SendRequestExtra{ - ID: messageID, - }) + msg.AddPendingToIgnore(networkid.TransactionID(wrappedMsgID2)) + resp, err := wa.Client.SendMessage(ctx, chatJID, waMsg, *req) if err != nil { return nil, err } + var pickedMessageID networkid.MessageID + if resp.Sender == wa.Device.GetLID() { + pickedMessageID = wrappedMsgID2 + msg.RemovePending(networkid.TransactionID(wrappedMsgID)) + } else { + pickedMessageID = wrappedMsgID + msg.RemovePending(networkid.TransactionID(wrappedMsgID2)) + } return &bridgev2.MatrixMessageResponse{ DB: &database.Message{ - ID: wrappedMsgID, - SenderID: waid.MakeUserID(wa.JID), + ID: pickedMessageID, + SenderID: waid.MakeUserID(resp.Sender), Timestamp: resp.Timestamp, Metadata: &waid.MessageMetadata{ SenderDeviceID: wa.JID.Device, }, }, StreamOrder: resp.Timestamp.Unix(), - RemovePending: networkid.TransactionID(wrappedMsgID), + RemovePending: networkid.TransactionID(pickedMessageID), }, nil } @@ -105,8 +118,12 @@ func (wa *WhatsAppClient) PreHandleMatrixReaction(_ context.Context, msg *bridge } else if portalJID == types.StatusBroadcastJID { return bridgev2.MatrixReactionPreResponse{}, ErrBroadcastReactionUnsupported } + sender := wa.JID + if msg.Portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup { + sender = wa.Device.GetLID() + } return bridgev2.MatrixReactionPreResponse{ - SenderID: waid.MakeUserID(wa.JID), + SenderID: waid.MakeUserID(sender), Emoji: variationselector.Remove(msg.Content.RelatesTo.Key), MaxReactions: 1, }, nil @@ -129,8 +146,19 @@ func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev SenderTimestampMS: proto.Int64(msg.Event.Timestamp), }, } + var req whatsmeow.SendRequestExtra + if msg.Portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup { + reactionMsg.EncReactionMessage, err = wa.Client.EncryptReaction(msgconv.MessageIDToInfo(wa.Client, messageID), reactionMsg.ReactionMessage) + if err != nil { + return nil, fmt.Errorf("failed to encrypt reaction: %w", err) + } + reactionMsg.ReactionMessage = nil + req.Meta = &types.MsgMetaInfo{ + DeprecatedLIDSession: ptr.Ptr(false), + } + } - resp, err := wa.Client.SendMessage(ctx, portalJID, reactionMsg) + resp, err := wa.Client.SendMessage(ctx, portalJID, reactionMsg, req) zerolog.Ctx(ctx).Trace().Any("response", resp).Msg("WhatsApp reaction response") return &database.Reaction{ Metadata: &waid.ReactionMetadata{ @@ -177,7 +205,7 @@ func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.M return err } - waMsg, err := wa.Main.MsgConv.ToWhatsApp(ctx, wa.Client, edit.Event, edit.Content, nil, edit.Portal) + waMsg, _, err := wa.Main.MsgConv.ToWhatsApp(ctx, wa.Client, edit.Event, edit.Content, nil, nil, edit.Portal) if err != nil { return fmt.Errorf("failed to convert message: %w", err) } diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index fcf73a21..307862bf 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -264,6 +264,24 @@ func (wa *WhatsAppClient) handleWAMessage(evt *events.Message) { if parsedMessageType == "ignore" || strings.HasPrefix(parsedMessageType, "unknown_protocol_") { return } + if encReact := evt.Message.GetEncReactionMessage(); encReact != nil { + decrypted, err := wa.Client.DecryptReaction(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(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 + } + } wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &WAMessageEvent{ MessageInfoWrapper: &MessageInfoWrapper{ Info: evt.Info, diff --git a/pkg/connector/id.go b/pkg/connector/id.go index a605fa71..68e3f632 100644 --- a/pkg/connector/id.go +++ b/pkg/connector/id.go @@ -31,7 +31,7 @@ func (wa *WhatsAppClient) makeEventSender(id types.JID) bridgev2.EventSender { return bridgev2.EventSender{} } return bridgev2.EventSender{ - IsFromMe: waid.MakeUserLoginID(id) == wa.UserLogin.ID, + IsFromMe: id.User == wa.Client.Store.GetJID().User || id.User == wa.Client.Store.GetLID().User, Sender: waid.MakeUserID(id), SenderLogin: waid.MakeUserLoginID(id), } @@ -42,10 +42,10 @@ func (wa *WhatsAppClient) messageIDToKey(id *waid.ParsedMessageID) *waCommon.Mes RemoteJID: ptr.Ptr(id.Chat.String()), ID: ptr.Ptr(id.ID), } - if id.Sender.User == string(wa.UserLogin.ID) { + if id.Sender.User == wa.Client.Store.GetJID().User || id.Sender.User == wa.Client.Store.GetLID().User { key.FromMe = ptr.Ptr(true) } - if id.Chat.Server != types.MessengerServer && id.Chat.Server != types.DefaultUserServer && 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 diff --git a/pkg/connector/wamsgtype.go b/pkg/connector/wamsgtype.go index 73727044..7a8547c1 100644 --- a/pkg/connector/wamsgtype.go +++ b/pkg/connector/wamsgtype.go @@ -63,6 +63,10 @@ func getMessageType(waMsg *waE2E.Message) string { return "reaction" case waMsg.EncReactionMessage != nil: return "encrypted reaction" + case waMsg.EncCommentMessage != nil: + return "encrypted comment" + case waMsg.CommentMessage != nil: + return "comment" case waMsg.PollCreationMessage != nil || waMsg.PollCreationMessageV2 != nil || waMsg.PollCreationMessageV3 != nil: return "poll create" case waMsg.PollCreationMessageV4 != nil || waMsg.PollCreationMessageV5 != nil: diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 94ec6ff2..f6f670e5 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -32,6 +32,7 @@ import ( "github.com/rs/zerolog" "go.mau.fi/util/ffmpeg" "go.mau.fi/util/ptr" + "go.mau.fi/util/random" cwebp "go.mau.fi/webp" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waE2E" @@ -74,9 +75,10 @@ func (mc *MessageConverter) ToWhatsApp( client *whatsmeow.Client, evt *event.Event, content *event.MessageEventContent, - replyTo *database.Message, + replyTo, + threadRoot *database.Message, portal *bridgev2.Portal, -) (*waE2E.Message, error) { +) (*waE2E.Message, *whatsmeow.SendRequestExtra, error) { ctx = context.WithValue(ctx, contextKeyClient, client) ctx = context.WithValue(ctx, contextKeyPortal, portal) if evt.Type == event.EventSticker { @@ -86,7 +88,7 @@ func (mc *MessageConverter) ToWhatsApp( message := &waE2E.Message{} contextInfo, err := mc.generateContextInfo(replyTo, portal) if err != nil { - return nil, err + return nil, nil, err } switch content.MsgType { @@ -95,13 +97,13 @@ func (mc *MessageConverter) ToWhatsApp( case event.MessageType(event.EventSticker.Type), event.MsgImage, event.MsgVideo, event.MsgAudio, event.MsgFile: uploaded, thumbnail, mime, err := mc.reuploadFileToWhatsApp(ctx, content) if err != nil { - return nil, err + return nil, nil, err } message = mc.constructMediaMessage(ctx, content, evt, uploaded, thumbnail, contextInfo, mime) case event.MsgLocation: lat, long, err := parseGeoURI(content.GeoURI) if err != nil { - return nil, err + return nil, nil, err } message.LocationMessage = &waE2E.LocationMessage{ DegreesLatitude: &lat, @@ -110,9 +112,40 @@ func (mc *MessageConverter) ToWhatsApp( ContextInfo: contextInfo, } default: - return nil, fmt.Errorf("%w %s", bridgev2.ErrUnsupportedMessageType, content.MsgType) + return nil, nil, fmt.Errorf("%w %s", bridgev2.ErrUnsupportedMessageType, content.MsgType) } - return message, nil + extra := &whatsmeow.SendRequestExtra{} + if portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup { + if threadRoot != nil { + parsedID, err := waid.ParseMessageID(threadRoot.ID) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse message ID: %w", err) + } + rootMsgInfo := MessageIDToInfo(client, parsedID) + message, err = client.EncryptComment(rootMsgInfo, message) + if err != nil { + return nil, nil, fmt.Errorf("failed to encrypt comment: %w", err) + } + lid := parsedID.Sender + if lid.Server == types.DefaultUserServer { + lid, err = client.Store.LIDs.GetLIDForPN(ctx, parsedID.Sender) + if err != nil { + return nil, nil, fmt.Errorf("failed to get LID for PN: %w", err) + } + } + extra.Meta = &types.MsgMetaInfo{ + ThreadMessageID: parsedID.ID, + ThreadMessageSenderJID: lid, + DeprecatedLIDSession: ptr.Ptr(false), + } + } else { + // TODO check permissions? + message.MessageContextInfo = &waE2E.MessageContextInfo{ + MessageSecret: random.Bytes(32), + } + } + } + return message, extra, nil } func (mc *MessageConverter) constructMediaMessage( diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 89bd15ca..54e8e1a7 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -27,6 +27,7 @@ import ( "time" "github.com/rs/zerolog" + "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waE2E" "go.mau.fi/whatsmeow/types" @@ -95,6 +96,14 @@ func (mc *MessageConverter) addMentions(ctx context.Context, mentionedJID []stri } } +var failedCommentPart = &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: &event.MessageEventContent{ + Body: "Failed to decrypt comment", + MsgType: event.MsgNotice, + }, +} + func (mc *MessageConverter) ToMatrix( ctx context.Context, portal *bridgev2.Portal, @@ -171,6 +180,8 @@ 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) + case waMsg.EncCommentMessage != nil: + part = failedCommentPart default: part, contextInfo = mc.convertUnknownMessage(ctx, waMsg) } @@ -210,6 +221,18 @@ func (mc *MessageConverter) ToMatrix( MessageID: waid.MakeMessageID(chat, pcp, contextInfo.GetStanzaID()), } } + commentTarget := waMsg.GetEncCommentMessage().GetTargetMessageKey() + if commentTarget == nil { + commentTarget = waMsg.GetCommentMessage().GetTargetMessageKey() + } + if commentTarget != nil { + pcp, _ := types.ParseJID(commentTarget.GetParticipant()) + chat, _ := types.ParseJID(commentTarget.GetRemoteJID()) + if chat.IsEmpty() { + chat, _ = waid.ParsePortalID(portal.ID) + } + cm.ThreadRoot = ptr.Ptr(waid.MakeMessageID(chat, pcp, commentTarget.GetID())) + } return cm } diff --git a/pkg/msgconv/matrixpoll.go b/pkg/msgconv/matrixpoll.go index 3c373004..ad3b4e58 100644 --- a/pkg/msgconv/matrixpoll.go +++ b/pkg/msgconv/matrixpoll.go @@ -118,16 +118,8 @@ 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 := &types.MessageInfo{ - MessageSource: types.MessageSource{ - Chat: parsedMsgID.Chat, - Sender: parsedMsgID.Sender, - IsFromMe: parsedMsgID.Sender.User == client.Store.ID.User, - IsGroup: parsedMsgID.Chat.Server == types.GroupServer, - }, - ID: parsedMsgID.ID, - Type: "poll", - } + pollMsgInfo := MessageIDToInfo(client, parsedMsgID) + pollMsgInfo.Type = "poll" optionHashes := make([][]byte, 0, len(content.Response.Answers)) if pollMsg.Metadata.(*waid.MessageMetadata).IsMatrixPoll { mappedAnswers, err := mc.DB.PollOption.GetHashes(ctx, pollMsg.MXID, content.Response.Answers) @@ -156,3 +148,15 @@ func (mc *MessageConverter) PollVoteToWhatsApp( }) return &waE2E.Message{PollUpdateMessage: pollUpdate}, err } + +func MessageIDToInfo(client *whatsmeow.Client, parsedMsgID *waid.ParsedMessageID) *types.MessageInfo { + 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, + }, + ID: parsedMsgID.ID, + } +} diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index 0a92ddd7..57d66e01 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -105,8 +105,9 @@ type ReactionMetadata struct { } type PortalMetadata struct { - DisappearingTimerSetAt int64 `json:"disappearing_timer_set_at,omitempty"` - LastSync jsontime.Unix `json:"last_sync,omitempty"` + DisappearingTimerSetAt int64 `json:"disappearing_timer_set_at,omitempty"` + LastSync jsontime.Unix `json:"last_sync,omitempty"` + CommunityAnnouncementGroup bool `json:"is_cag,omitempty"` } type GhostMetadata struct { From 07ffa4f863ab140947e3ae99c406e19f59f97fca Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 8 Apr 2025 22:39:56 +0300 Subject: [PATCH 012/486] userinfo: fix name of PSA user --- pkg/connector/userinfo.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 54519ebf..108716d4 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -179,6 +179,8 @@ func (wa *WhatsAppClient) getUserInfo(ctx context.Context, jid types.JID, fetchA func (wa *WhatsAppClient) contactToUserInfo(jid types.JID, contact types.ContactInfo, getAvatar bool) *bridgev2.UserInfo { if jid == types.MetaAIJID && contact.PushName == jid.User { contact.PushName = "Meta AI" + } else if jid == types.PSAJID { + contact.PushName = "WhatsApp" } ui := &bridgev2.UserInfo{ Name: ptr.Ptr(wa.Main.Config.FormatDisplayname(jid, contact)), From a64a25ffc83cc35e47a9821b2ec40e8e7e9ebea5 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 8 Apr 2025 23:32:52 +0300 Subject: [PATCH 013/486] userinfo: get pn info for lid users --- pkg/connector/config.go | 10 ++++++---- pkg/connector/startchat.go | 2 +- pkg/connector/userinfo.go | 39 ++++++++++++++++++++++++++++++++++---- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/pkg/connector/config.go b/pkg/connector/config.go index 11146098..daffe99d 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -141,11 +141,13 @@ type DisplaynameParams struct { Short string } -func (c *Config) FormatDisplayname(jid types.JID, contact types.ContactInfo) string { +func (c *Config) FormatDisplayname(jid types.JID, phone string, contact types.ContactInfo) string { var nameBuf strings.Builder - phone := "+" + jid.User - if jid.Server == types.BotServer { - phone = jid.User + if phone == "" { + phone = "+" + jid.User + if jid.Server != types.DefaultUserServer { + phone = jid.User + } } err := c.displaynameTemplate.Execute(&nameBuf, &DisplaynameParams{ ContactInfo: contact, diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index 3e3ca14a..4f4ce645 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -149,7 +149,7 @@ func (wa *WhatsAppClient) getContactList(ctx context.Context, filter string) ([] resp = append(resp, &bridgev2.ResolveIdentifierResponse{ Ghost: ghost, UserID: waid.MakeUserID(jid), - UserInfo: wa.contactToUserInfo(jid, contactInfo, false), + UserInfo: wa.contactToUserInfo(ctx, jid, contactInfo, false), Chat: &bridgev2.CreateChatResponse{PortalKey: wa.makeWAPortalKey(jid)}, }) } diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 108716d4..9556ece5 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -173,17 +173,48 @@ func (wa *WhatsAppClient) getUserInfo(ctx context.Context, jid types.JID, fetchA if err != nil { return nil, err } - return wa.contactToUserInfo(jid, contact, fetchAvatar), nil + return wa.contactToUserInfo(ctx, jid, contact, fetchAvatar), nil } -func (wa *WhatsAppClient) contactToUserInfo(jid types.JID, contact types.ContactInfo, getAvatar bool) *bridgev2.UserInfo { +func (wa *WhatsAppClient) contactToUserInfo(ctx context.Context, jid types.JID, contact types.ContactInfo, getAvatar bool) *bridgev2.UserInfo { if jid == types.MetaAIJID && contact.PushName == jid.User { contact.PushName = "Meta AI" } else if jid == types.PSAJID { contact.PushName = "WhatsApp" } + var phone string + if jid.Server == types.DefaultUserServer { + phone = "+" + jid.User + } else if jid.Server == types.HiddenUserServer { + pnJID, err := wa.GetStore().LIDs.GetPNForLID(ctx, jid) + if err != nil { + zerolog.Ctx(ctx).Err(err).Stringer("lid", jid).Msg("Failed to get PN for LID") + } else { + phone = "+" + pnJID.User + extraContact, err := wa.GetStore().Contacts.GetContact(pnJID) + if err != nil { + zerolog.Ctx(ctx).Err(err). + Stringer("lid", jid). + Stringer("pn_jid", pnJID). + Msg("Failed to get contact info from PN") + } else { + if contact.FirstName == "" { + contact.FirstName = extraContact.FirstName + } + if contact.FullName == "" { + contact.FullName = extraContact.FullName + } + if contact.PushName == "" { + contact.PushName = extraContact.PushName + } + if contact.BusinessName == "" { + contact.BusinessName = extraContact.BusinessName + } + } + } + } ui := &bridgev2.UserInfo{ - Name: ptr.Ptr(wa.Main.Config.FormatDisplayname(jid, contact)), + Name: ptr.Ptr(wa.Main.Config.FormatDisplayname(jid, phone, contact)), IsBot: ptr.Ptr(jid.IsBot()), Identifiers: []string{fmt.Sprintf("tel:+%s", jid.User)}, ExtraUpdates: updateGhostLastSyncAt, @@ -252,7 +283,7 @@ func (wa *WhatsAppClient) resyncContacts(forceAvatarSync bool) { if err != nil { log.Err(err).Msg("Failed to get ghost") } else if ghost != nil { - ghost.UpdateInfo(ctx, wa.contactToUserInfo(jid, contact, forceAvatarSync || ghost.AvatarID == "")) + ghost.UpdateInfo(ctx, wa.contactToUserInfo(ctx, jid, contact, forceAvatarSync || ghost.AvatarID == "")) } } } From 42958081860fb24ff8c8dd5c23f0a6811d63e365 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 9 Apr 2025 00:25:37 +0300 Subject: [PATCH 014/486] chatinfo: store group addressing mode --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/chatinfo.go | 12 ++++++++++++ pkg/connector/handlematrix.go | 6 ++++-- pkg/waid/dbmeta.go | 7 ++++--- 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 13ecdcc4..5a6e469d 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.33.0 go.mau.fi/util v0.8.6 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250408193601-9910d4800e47 + go.mau.fi/whatsmeow v0.0.0-20250408212909-3a9a76e3973a golang.org/x/image v0.25.0 golang.org/x/net v0.37.0 golang.org/x/sync v0.12.0 diff --git a/go.sum b/go.sum index 60b40bf5..b6615216 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 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-20250408193601-9910d4800e47 h1:7V+QLw7WcAn1vSZuqDv0loFjTyejpjez/wFDLCxFR54= -go.mau.fi/whatsmeow v0.0.0-20250408193601-9910d4800e47/go.mod h1:aj6MpceCuJ6m712lPEMAh8iAw2TkABx6TnPjMcdG0eE= +go.mau.fi/whatsmeow v0.0.0-20250408212909-3a9a76e3973a h1:N9t5uOnRKKPIbYHGWgVddPIat5pi+f2cGM6QTXzkgUA= +go.mau.fi/whatsmeow v0.0.0-20250408212909-3a9a76e3973a/go.mod h1:aj6MpceCuJ6m712lPEMAh8iAw2TkABx6TnPjMcdG0eE= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 1f8582c8..062c56d7 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -202,6 +202,17 @@ func setDefaultSubGroupFlag(isCommunityAnnouncementGroup bool) bridgev2.ExtraUpd } } +func setAddressingMode(mode types.AddressingMode) bridgev2.ExtraUpdater[*bridgev2.Portal] { + return func(_ context.Context, portal *bridgev2.Portal) bool { + meta := portal.Metadata.(*waid.PortalMetadata) + if meta.AddressingMode != mode { + meta.AddressingMode = mode + return true + } + return false + } +} + func (wa *WhatsAppClient) wrapGroupInfo(info *types.GroupInfo) *bridgev2.ChatInfo { sendEventPL := defaultPL if info.IsAnnounce && !info.IsDefaultSubGroup { @@ -214,6 +225,7 @@ func (wa *WhatsAppClient) wrapGroupInfo(info *types.GroupInfo) *bridgev2.ChatInf extraUpdater := bridgev2.MergeExtraUpdaters( wa.makePortalAvatarFetcher("", types.EmptyJID, time.Time{}), setDefaultSubGroupFlag(info.IsDefaultSubGroup && info.IsAnnounce), + setAddressingMode(info.AddressingMode), ) wrapped := &bridgev2.ChatInfo{ Name: ptr.Ptr(info.Name), diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index f3ada57a..4046a3da 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -119,7 +119,9 @@ func (wa *WhatsAppClient) PreHandleMatrixReaction(_ context.Context, msg *bridge return bridgev2.MatrixReactionPreResponse{}, ErrBroadcastReactionUnsupported } sender := wa.JID - if msg.Portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup { + if portalJID.Server == types.HiddenUserServer || + msg.Portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup || + msg.Portal.Metadata.(*waid.PortalMetadata).AddressingMode == types.AddressingModeLID { sender = wa.Device.GetLID() } return bridgev2.MatrixReactionPreResponse{ @@ -271,7 +273,7 @@ func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt * if err != nil { continue } - if msg.SenderID == networkid.UserID(wa.UserLogin.ID) { + if parsed.Sender.User == wa.Device.GetLID().User || parsed.Sender.User == wa.JID.User { continue } var key types.JID diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index 57d66e01..65760d2c 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -105,9 +105,10 @@ type ReactionMetadata struct { } type PortalMetadata struct { - DisappearingTimerSetAt int64 `json:"disappearing_timer_set_at,omitempty"` - LastSync jsontime.Unix `json:"last_sync,omitempty"` - CommunityAnnouncementGroup bool `json:"is_cag,omitempty"` + DisappearingTimerSetAt int64 `json:"disappearing_timer_set_at,omitempty"` + LastSync jsontime.Unix `json:"last_sync,omitempty"` + CommunityAnnouncementGroup bool `json:"is_cag,omitempty"` + AddressingMode types.AddressingMode `json:"addressing_mode,omitempty"` } type GhostMetadata struct { From 824c52b3dec0ed5143563e07570c6a093dea4776 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 9 Apr 2025 12:12:00 +0300 Subject: [PATCH 015/486] dependencies: update --- go.mod | 20 ++++++++++---------- go.sum | 41 ++++++++++++++++++++--------------------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/go.mod b/go.mod index 5a6e469d..2e8b2335 100644 --- a/go.mod +++ b/go.mod @@ -8,16 +8,16 @@ require ( github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/lib/pq v1.10.9 - github.com/rs/zerolog v1.33.0 + github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.6 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250408212909-3a9a76e3973a - golang.org/x/image v0.25.0 - golang.org/x/net v0.37.0 - golang.org/x/sync v0.12.0 - google.golang.org/protobuf v1.36.5 + go.mau.fi/whatsmeow v0.0.0-20250409085829-1bc4a25a01b0 + golang.org/x/image v0.26.0 + golang.org/x/net v0.39.0 + golang.org/x/sync v0.13.0 + google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.23.3-0.20250320134109-06f200da0d10 + maunium.net/go/mautrix v0.23.3-0.20250405234116-e675a3c09c38 ) require ( @@ -39,10 +39,10 @@ require ( github.com/yuin/goldmark v1.7.8 // indirect go.mau.fi/libsignal v0.1.2 // indirect go.mau.fi/zeroconfig v0.1.3 // indirect - golang.org/x/crypto v0.36.0 // indirect + golang.org/x/crypto v0.37.0 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.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 b6615216..adc5e697 100644 --- a/go.sum +++ b/go.sum @@ -43,11 +43,10 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 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.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= -github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= @@ -70,29 +69,29 @@ go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 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-20250408212909-3a9a76e3973a h1:N9t5uOnRKKPIbYHGWgVddPIat5pi+f2cGM6QTXzkgUA= -go.mau.fi/whatsmeow v0.0.0-20250408212909-3a9a76e3973a/go.mod h1:aj6MpceCuJ6m712lPEMAh8iAw2TkABx6TnPjMcdG0eE= +go.mau.fi/whatsmeow v0.0.0-20250409085829-1bc4a25a01b0 h1:WbmFFtGkyOKnTp5QxPhzAzpQtL8Hq+/iCHBOXuE3AmE= +go.mau.fi/whatsmeow v0.0.0-20250409085829-1bc4a25a01b0/go.mod h1:aj6MpceCuJ6m712lPEMAh8iAw2TkABx6TnPjMcdG0eE= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= -golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= -golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY= +golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 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= @@ -102,5 +101,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.23.3-0.20250320134109-06f200da0d10 h1:XE2szxSvJVngk+qHqlKO2fiBZwcAp3oYq+9P5jlAm6w= -maunium.net/go/mautrix v0.23.3-0.20250320134109-06f200da0d10/go.mod h1:pCYLHmo02Jauak/9VlTkbGPrBMvLXsGqTGMNOx+L2PE= +maunium.net/go/mautrix v0.23.3-0.20250405234116-e675a3c09c38 h1:fIe2+kYndm3Mm/DwQ4FsODk2DjrLeEeW7tKtZjyERqM= +maunium.net/go/mautrix v0.23.3-0.20250405234116-e675a3c09c38/go.mod h1:pCYLHmo02Jauak/9VlTkbGPrBMvLXsGqTGMNOx+L2PE= From 49b2a9382c767dfcd496c2ba7cde32554e1be599 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 9 Apr 2025 12:13:37 +0300 Subject: [PATCH 016/486] dependencies: update whatsmeow to prevent downgrades --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2e8b2335..4f088e4a 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.6 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250409085829-1bc4a25a01b0 + go.mau.fi/whatsmeow v0.0.0-20250409091307-c13c74800b61 golang.org/x/image v0.26.0 golang.org/x/net v0.39.0 golang.org/x/sync v0.13.0 diff --git a/go.sum b/go.sum index adc5e697..50092f76 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 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-20250409085829-1bc4a25a01b0 h1:WbmFFtGkyOKnTp5QxPhzAzpQtL8Hq+/iCHBOXuE3AmE= -go.mau.fi/whatsmeow v0.0.0-20250409085829-1bc4a25a01b0/go.mod h1:aj6MpceCuJ6m712lPEMAh8iAw2TkABx6TnPjMcdG0eE= +go.mau.fi/whatsmeow v0.0.0-20250409091307-c13c74800b61 h1:1kJDby0aFgiFq/CEAykL1ceOUddZBUj0kDNLz05TyUo= +go.mau.fi/whatsmeow v0.0.0-20250409091307-c13c74800b61/go.mod h1:aj6MpceCuJ6m712lPEMAh8iAw2TkABx6TnPjMcdG0eE= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= From f274134afec99a8cc25a4deb47fad0bb4aaf3a65 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 9 Apr 2025 12:37:43 +0300 Subject: [PATCH 017/486] commands: add sync command --- pkg/connector/chatinfo.go | 7 ++- pkg/connector/commands.go | 93 ++++++++++++++++++++++++++++++++++++++ pkg/connector/connector.go | 2 +- 3 files changed, 100 insertions(+), 2 deletions(-) diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 062c56d7..81776dc1 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -55,7 +55,13 @@ func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, default: return nil, fmt.Errorf("unsupported server %s", portalJID.Server) } + wa.addExtrasToWrapped(ctx, portalJID, wrapped, conv) + return wrapped, nil +} + +func (wa *WhatsAppClient) addExtrasToWrapped(ctx context.Context, portalJID types.JID, wrapped *bridgev2.ChatInfo, conv *wadb.Conversation) { if conv == nil { + var err error conv, err = wa.Main.DB.Conversation.Get(ctx, wa.UserLogin.ID, portalJID) if err != nil { zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to get history sync conversation info") @@ -65,7 +71,6 @@ func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, wa.applyHistoryInfo(wrapped, conv) } wa.applyChatSettings(ctx, portalJID, wrapped) - return wrapped, nil } func updatePortalLastSyncAt(_ context.Context, portal *bridgev2.Portal) bool { diff --git a/pkg/connector/commands.go b/pkg/connector/commands.go index a1c872a4..9812a6b7 100644 --- a/pkg/connector/commands.go +++ b/pkg/connector/commands.go @@ -17,7 +17,14 @@ package connector import ( + "errors" + "strings" + + "github.com/rs/zerolog" + "go.mau.fi/whatsmeow/appstate" + "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/commands" + "maunium.net/go/mautrix/bridgev2/simplevent" "go.mau.fi/mautrix-whatsapp/pkg/waid" ) @@ -61,3 +68,89 @@ func fnAccept(ce *commands.Event) { ce.Reply("Successfully accepted the invite, the portal should be created momentarily") } } + +var cmdSync = &commands.FullHandler{ + Func: fnSync, + Name: "sync", + Help: commands.HelpMeta{ + Section: commands.HelpSectionAdmin, + Description: "Sync data from WhatsApp.", + Args: "", + }, + RequiresLogin: true, +} + +func fnSync(ce *commands.Event) { + login := ce.User.GetDefaultLogin() + if login == nil { + ce.Reply("Login not found") + return + } + if len(ce.Args) == 0 { + ce.Reply("Usage: `$cmdprefix sync `") + return + } + logContext := func(c zerolog.Context) zerolog.Context { + return c.Stringer("triggered_by_user", ce.User.MXID) + } + wa := login.Client.(*WhatsAppClient) + switch strings.ToLower(ce.Args[0]) { + case "group", "portal", "room": + if ce.Portal == nil { + ce.Reply("`!wa sync group` can only be used in a portal room.") + return + } + login.QueueRemoteEvent(&simplevent.ChatResync{ + EventMeta: simplevent.EventMeta{ + Type: bridgev2.RemoteEventChatResync, + PortalKey: ce.Portal.PortalKey, + LogContext: logContext, + }, + GetChatInfoFunc: wa.GetChatInfo, + }) + ce.React("✅") + case "groups": + groups, err := wa.Client.GetJoinedGroups() + if err != nil { + ce.Reply("Failed to get joined groups: %v", err) + return + } + for _, group := range groups { + wrapped := wa.wrapGroupInfo(group) + wrapped.ExtraUpdates = bridgev2.MergeExtraUpdaters(wrapped.ExtraUpdates, updatePortalLastSyncAt) + wa.addExtrasToWrapped(ce.Ctx, group.JID, wrapped, nil) + login.QueueRemoteEvent(&simplevent.ChatResync{ + EventMeta: simplevent.EventMeta{ + Type: bridgev2.RemoteEventChatResync, + PortalKey: wa.makeWAPortalKey(group.JID), + LogContext: logContext, + CreatePortal: true, + }, + ChatInfo: wrapped, + }) + } + ce.Reply("Queued syncs for %d groups", len(groups)) + case "contacts": + wa.resyncContacts(false) + ce.React("✅") + case "contacts-with-avatars": + wa.resyncContacts(true) + ce.React("✅") + case "appstate": + for _, name := range appstate.AllPatchNames { + err := wa.Client.FetchAppState(name, true, false) + if errors.Is(err, appstate.ErrKeyNotFound) { + ce.Reply("Key not found error syncing app state %s: %v\n\nKey requests are sent automatically, and the sync should happen in the background after your phone responds.", name, err) + return + } else if err != nil { + ce.Reply("Error syncing app state %s: %v", name, err) + } else if name == appstate.WAPatchCriticalUnblockLow { + ce.Reply("Synced app state %s, contact sync running in background", name) + } else { + ce.Reply("Synced app state %s", name) + } + } + default: + ce.Reply("Unknown sync target `%s`", ce.Args[0]) + } +} diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 17808c0b..c22e0c3d 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -92,7 +92,7 @@ func (wa *WhatsAppConnector) Init(bridge *bridgev2.Bridge) { wa.DB = wadb.New(bridge.ID, bridge.DB.Database, bridge.Log.With().Str("db_section", "whatsapp").Logger()) wa.MsgConv.DB = wa.DB wa.Bridge.Commands.(*commands.Processor).AddHandlers( - cmdAccept, + cmdAccept, cmdSync, ) wa.mediaEditCache = make(MediaEditCache) From 81ef33ffe8fd1b0f41939cad3c51da9d67201f59 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 9 Apr 2025 12:41:50 +0300 Subject: [PATCH 018/486] commands: add invite link and join commands --- pkg/connector/commands.go | 150 +++++++++++++++++++++++++++++++++++++ pkg/connector/connector.go | 2 +- 2 files changed, 151 insertions(+), 1 deletion(-) diff --git a/pkg/connector/commands.go b/pkg/connector/commands.go index 9812a6b7..4c62db0b 100644 --- a/pkg/connector/commands.go +++ b/pkg/connector/commands.go @@ -18,10 +18,14 @@ package connector import ( "errors" + "fmt" + "html" "strings" "github.com/rs/zerolog" + "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/appstate" + "go.mau.fi/whatsmeow/types" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/commands" "maunium.net/go/mautrix/bridgev2/simplevent" @@ -154,3 +158,149 @@ func fnSync(ce *commands.Event) { ce.Reply("Unknown sync target `%s`", ce.Args[0]) } } + +var cmdInviteLink = &commands.FullHandler{ + Func: fnInviteLink, + Name: "invite-link", + Help: commands.HelpMeta{ + Section: HelpSectionInvites, + Description: "Get an invite link to the current group chat, optionally regenerating the link and revoking the old link.", + Args: "[--reset]", + }, + RequiresPortal: true, + RequiresLogin: true, +} + +func fnInviteLink(ce *commands.Event) { + login := ce.User.GetDefaultLogin() + if login == nil { + ce.Reply("Login not found") + return + } + portalJID, err := waid.ParsePortalID(ce.Portal.ID) + if err != nil { + ce.Reply("Failed to parse portal ID: %v", err) + return + } + + wa := login.Client.(*WhatsAppClient) + reset := len(ce.Args) > 0 && strings.ToLower(ce.Args[0]) == "--reset" + if portalJID.Server == types.DefaultUserServer || portalJID.Server == types.HiddenUserServer { + ce.Reply("Can't get invite link to private chat") + } else if portalJID.IsBroadcastList() { + ce.Reply("Can't get invite link to broadcast list") + } else if link, err := wa.Client.GetGroupInviteLink(portalJID, reset); err != nil { + ce.Reply("Failed to get invite link: %v", err) + } else { + ce.Reply(link) + } +} + +var cmdResolveLink = &commands.FullHandler{ + Func: fnResolveLink, + Name: "resolve-link", + Help: commands.HelpMeta{ + Section: HelpSectionInvites, + Description: "Resolve a WhatsApp group invite or business message link.", + Args: "<_group, contact, or message link_>", + }, + RequiresLogin: true, +} + +func fnResolveLink(ce *commands.Event) { + if len(ce.Args) == 0 { + ce.Reply("**Usage:** `$cmdprefix resolve-link `") + return + } + login := ce.User.GetDefaultLogin() + if login == nil { + ce.Reply("Login not found") + return + } + wa := login.Client.(*WhatsAppClient) + if strings.HasPrefix(ce.Args[0], whatsmeow.InviteLinkPrefix) { + group, err := wa.Client.GetGroupInfoFromLink(ce.Args[0]) + if err != nil { + ce.Reply("Failed to get group info: %v", err) + return + } + ce.Reply("That invite link points at %s (`%s`)", group.Name, group.JID) + } else if strings.HasPrefix(ce.Args[0], whatsmeow.BusinessMessageLinkPrefix) || strings.HasPrefix(ce.Args[0], whatsmeow.BusinessMessageLinkDirectPrefix) { + target, err := wa.Client.ResolveBusinessMessageLink(ce.Args[0]) + if err != nil { + ce.Reply("Failed to get business info: %v", err) + return + } + message := "" + if len(target.Message) > 0 { + parts := strings.Split(target.Message, "\n") + for i, part := range parts { + parts[i] = "> " + html.EscapeString(part) + } + message = fmt.Sprintf(" The following prefilled message is attached:\n\n%s", strings.Join(parts, "\n")) + } + ce.Reply("That link points at %s (+%s).%s", target.PushName, target.JID.User, message) + } else if strings.HasPrefix(ce.Args[0], whatsmeow.ContactQRLinkPrefix) || strings.HasPrefix(ce.Args[0], whatsmeow.ContactQRLinkDirectPrefix) { + target, err := wa.Client.ResolveContactQRLink(ce.Args[0]) + if err != nil { + ce.Reply("Failed to get contact info: %v", err) + return + } + if target.PushName != "" { + ce.Reply("That link points at %s (+%s)", target.PushName, target.JID.User) + } else { + ce.Reply("That link points at +%s", target.JID.User) + } + } else { + ce.Reply("That doesn't look like a group invite link nor a business message link.") + } +} + +var cmdJoin = &commands.FullHandler{ + Func: fnJoin, + Name: "join", + Help: commands.HelpMeta{ + Section: HelpSectionInvites, + Description: "Join a group chat with an invite link.", + Args: "<_invite link_>", + }, + RequiresLogin: true, +} + +func fnJoin(ce *commands.Event) { + if len(ce.Args) == 0 { + ce.Reply("**Usage:** `$cmdprefix join `") + return + } + login := ce.User.GetDefaultLogin() + if login == nil { + ce.Reply("Login not found") + return + } + wa := login.Client.(*WhatsAppClient) + + if strings.HasPrefix(ce.Args[0], whatsmeow.InviteLinkPrefix) { + jid, err := wa.Client.JoinGroupWithLink(ce.Args[0]) + if err != nil { + ce.Reply("Failed to join group: %v", err) + return + } + ce.Log.Debug().Stringer("group_jid", jid).Msg("User successfully joined WhatsApp group with link") + ce.Reply("Successfully joined group `%s`, the portal should be created momentarily", jid) + } else if strings.HasPrefix(ce.Args[0], whatsmeow.NewsletterLinkPrefix) { + info, err := wa.Client.GetNewsletterInfoWithInvite(ce.Args[0]) + if err != nil { + ce.Reply("Failed to get channel info: %v", err) + return + } + err = wa.Client.FollowNewsletter(info.ID) + if err != nil { + ce.Reply("Failed to follow channel: %v", err) + return + } + ce.Log.Debug().Stringer("channel_jid", info.ID).Msg("User successfully followed WhatsApp channel with link") + ce.Reply("Successfully followed channel `%s`, the portal should be created momentarily", info.ID) + } else { + ce.Reply("That doesn't look like a WhatsApp invite link") + } +} diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index c22e0c3d..58b369a3 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -92,7 +92,7 @@ func (wa *WhatsAppConnector) Init(bridge *bridgev2.Bridge) { wa.DB = wadb.New(bridge.ID, bridge.DB.Database, bridge.Log.With().Str("db_section", "whatsapp").Logger()) wa.MsgConv.DB = wa.DB wa.Bridge.Commands.(*commands.Processor).AddHandlers( - cmdAccept, cmdSync, + cmdAccept, cmdSync, cmdInviteLink, cmdResolveLink, cmdJoin, ) wa.mediaEditCache = make(MediaEditCache) From 7dca8ae6bf94eb95ff7da89c3fcc44bad8ff005d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 9 Apr 2025 16:42:02 +0300 Subject: [PATCH 019/486] 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 4f088e4a..e1e17b58 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.6 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250409091307-c13c74800b61 + go.mau.fi/whatsmeow v0.0.0-20250409130157-7d8031d8ba6f golang.org/x/image v0.26.0 golang.org/x/net v0.39.0 golang.org/x/sync v0.13.0 diff --git a/go.sum b/go.sum index 50092f76..c41bede3 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 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-20250409091307-c13c74800b61 h1:1kJDby0aFgiFq/CEAykL1ceOUddZBUj0kDNLz05TyUo= -go.mau.fi/whatsmeow v0.0.0-20250409091307-c13c74800b61/go.mod h1:aj6MpceCuJ6m712lPEMAh8iAw2TkABx6TnPjMcdG0eE= +go.mau.fi/whatsmeow v0.0.0-20250409130157-7d8031d8ba6f h1:hcpc//STZ30ozFflhEKbKHuR8oKsPZGW/ACpa4j443U= +go.mau.fi/whatsmeow v0.0.0-20250409130157-7d8031d8ba6f/go.mod h1:aj6MpceCuJ6m712lPEMAh8iAw2TkABx6TnPjMcdG0eE= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= From 729fce0f5835da56a4ba9a3152a58a7bae426660 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 11 Apr 2025 19:59:56 +0300 Subject: [PATCH 020/486] handlewhatsapp: add dont_render_edited flag for decryption retries --- pkg/connector/events.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 970c5e06..2ceffa8e 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -156,6 +156,14 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por evt.wa.processFailedMedia(ctx, portal.PortalKey, evt.GetID(), cm, false) } } + if evt.isUndecryptableUpsertSubEvent { + for _, part := range cm.Parts { + if part.Extra == nil { + part.Extra = make(map[string]any) + } + part.Extra["com.beeper.dont_render_edited"] = true + } + } return &bridgev2.ConvertedEdit{ ModifiedParts: []*bridgev2.ConvertedEditPart{cm.Parts[0].ToEditPart(existing[0])}, }, nil From 05adb89f544bd28ee5e39f16e9d492e7e94b9975 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 11 Apr 2025 20:11:08 +0300 Subject: [PATCH 021/486] 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 e1e17b58..fe6c4ebd 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.6 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250409130157-7d8031d8ba6f + go.mau.fi/whatsmeow v0.0.0-20250411165525-29d7ebe20b4d golang.org/x/image v0.26.0 golang.org/x/net v0.39.0 golang.org/x/sync v0.13.0 diff --git a/go.sum b/go.sum index c41bede3..11a14156 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 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-20250409130157-7d8031d8ba6f h1:hcpc//STZ30ozFflhEKbKHuR8oKsPZGW/ACpa4j443U= -go.mau.fi/whatsmeow v0.0.0-20250409130157-7d8031d8ba6f/go.mod h1:aj6MpceCuJ6m712lPEMAh8iAw2TkABx6TnPjMcdG0eE= +go.mau.fi/whatsmeow v0.0.0-20250411165525-29d7ebe20b4d h1:y2efKNkNbx99+s1DIaCIdbEQWUFfG5f8xnBUBljK9hU= +go.mau.fi/whatsmeow v0.0.0-20250411165525-29d7ebe20b4d/go.mod h1:aj6MpceCuJ6m712lPEMAh8iAw2TkABx6TnPjMcdG0eE= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= From 148cee6f5ddd6074c41eb822268a1fb52b5ea457 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 11 Apr 2025 20:25:12 +0300 Subject: [PATCH 022/486] 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 fe6c4ebd..7218d137 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.6 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250411165525-29d7ebe20b4d + go.mau.fi/whatsmeow v0.0.0-20250411172454-9b79914958b1 golang.org/x/image v0.26.0 golang.org/x/net v0.39.0 golang.org/x/sync v0.13.0 diff --git a/go.sum b/go.sum index 11a14156..fab1669e 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 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-20250411165525-29d7ebe20b4d h1:y2efKNkNbx99+s1DIaCIdbEQWUFfG5f8xnBUBljK9hU= -go.mau.fi/whatsmeow v0.0.0-20250411165525-29d7ebe20b4d/go.mod h1:aj6MpceCuJ6m712lPEMAh8iAw2TkABx6TnPjMcdG0eE= +go.mau.fi/whatsmeow v0.0.0-20250411172454-9b79914958b1 h1:pKV2fran6x28jSuZGFMKRDQ0v4rCFsIavt1NaEFLi8M= +go.mau.fi/whatsmeow v0.0.0-20250411172454-9b79914958b1/go.mod h1:aj6MpceCuJ6m712lPEMAh8iAw2TkABx6TnPjMcdG0eE= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= From c7f19fe8b1bca61a963a805282b04fff58d1f08c Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 11 Apr 2025 20:29:13 +0300 Subject: [PATCH 023/486] handlewhatsapp: move dont_render_edited to top level --- pkg/connector/events.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 2ceffa8e..7f5435bd 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -156,16 +156,15 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por evt.wa.processFailedMedia(ctx, portal.PortalKey, evt.GetID(), cm, false) } } + editPart := cm.Parts[0].ToEditPart(existing[0]) if evt.isUndecryptableUpsertSubEvent { - for _, part := range cm.Parts { - if part.Extra == nil { - part.Extra = make(map[string]any) - } - part.Extra["com.beeper.dont_render_edited"] = true + if editPart.TopLevelExtra == nil { + editPart.TopLevelExtra = make(map[string]any) } + editPart.TopLevelExtra["com.beeper.dont_render_edited"] = true } return &bridgev2.ConvertedEdit{ - ModifiedParts: []*bridgev2.ConvertedEditPart{cm.Parts[0].ToEditPart(existing[0])}, + ModifiedParts: []*bridgev2.ConvertedEditPart{editPart}, }, nil } From 37e18f0adad92c2c9b4c74a6cf791bd20badde3d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 11 Apr 2025 22:31:06 +0300 Subject: [PATCH 024/486] 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 7218d137..780d67c3 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.6 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250411172454-9b79914958b1 + go.mau.fi/whatsmeow v0.0.0-20250411192951-5ab78fadbf91 golang.org/x/image v0.26.0 golang.org/x/net v0.39.0 golang.org/x/sync v0.13.0 diff --git a/go.sum b/go.sum index fab1669e..3e492a88 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 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-20250411172454-9b79914958b1 h1:pKV2fran6x28jSuZGFMKRDQ0v4rCFsIavt1NaEFLi8M= -go.mau.fi/whatsmeow v0.0.0-20250411172454-9b79914958b1/go.mod h1:aj6MpceCuJ6m712lPEMAh8iAw2TkABx6TnPjMcdG0eE= +go.mau.fi/whatsmeow v0.0.0-20250411192951-5ab78fadbf91 h1:eziiUdmaGyhM7Fxn4IMRClBrX1b2sCAFGSBE2Z57yNg= +go.mau.fi/whatsmeow v0.0.0-20250411192951-5ab78fadbf91/go.mod h1:aj6MpceCuJ6m712lPEMAh8iAw2TkABx6TnPjMcdG0eE= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= From 83dda14d34bfb361b58c823cf500180c137ca835 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 14 Apr 2025 14:30:33 +0300 Subject: [PATCH 025/486] analytics: don't obfuscate lids Only phone numbers need to be obfuscated --- pkg/connector/analytics.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/connector/analytics.go b/pkg/connector/analytics.go index 8431ebfa..588a9824 100644 --- a/pkg/connector/analytics.go +++ b/pkg/connector/analytics.go @@ -28,6 +28,9 @@ import ( ) func (wa *WhatsAppClient) obfuscateJID(jid types.JID) string { + if jid.Server == types.HiddenUserServer { + return jid.String() + } // Turn the first 4 bytes of HMAC-SHA256(user_mxid, phone) into a number and replace the middle of the actual phone with that deterministic random number. randomNumber := binary.BigEndian.Uint32(hmac.New(sha256.New, []byte(wa.UserLogin.UserMXID)).Sum([]byte(jid.User))[:4]) return fmt.Sprintf("+%s-%d-%s:%d", jid.User[:1], randomNumber, jid.User[len(jid.User)-2:], jid.Device) From b65fac7427ba1994865796eb15c19a7acf003339 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 15 Apr 2025 14:33:53 +0300 Subject: [PATCH 026/486] client: set name/avatar in remote profile Closes #784 --- pkg/connector/client.go | 35 +++++++++++++++++++++++++++++++++ pkg/connector/handlewhatsapp.go | 3 +++ 2 files changed, 38 insertions(+) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 1e01f5b2..ac79e5fe 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -339,3 +339,38 @@ func (wa *WhatsAppClient) LogoutRemote(ctx context.Context) { func (wa *WhatsAppClient) IsLoggedIn() bool { return wa.Client != nil && wa.Client.IsLoggedIn() } + +func (wa *WhatsAppClient) syncRemoteProfile(ctx context.Context, ghost *bridgev2.Ghost) { + ownID := waid.MakeUserID(wa.Device.GetJID()) + if ghost == nil { + var err error + ghost, err = wa.Main.Bridge.GetExistingGhostByID(ctx, ownID) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to get own ghost to sync remote profile") + return + } else if ghost == nil { + return + } + } + if ghost.ID != ownID { + return + } + name := wa.Device.BusinessName + if name == "" { + name = wa.Device.PushName + } + if name == "" || wa.UserLogin.RemoteProfile.Name == name && wa.UserLogin.RemoteProfile.Avatar == ghost.AvatarMXC { + return + } + wa.UserLogin.RemoteProfile.Name = name + wa.UserLogin.RemoteProfile.Avatar = ghost.AvatarMXC + err := wa.UserLogin.Save(ctx) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to save remote profile") + } + // FIXME this might be racy, should invent a proper way to send last state with info filled + if wa.Client.IsConnected() { + wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected}) + } + zerolog.Ctx(ctx).Info().Msg("Remote profile updated") +} diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 307862bf..b7a0667c 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -130,6 +130,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) { if err != nil { log.Warn().Err(err).Msg("Failed to send presence after app state sync") } + go wa.syncRemoteProfile(log.WithContext(context.Background()), nil) } else if evt.Name == appstate.WAPatchCriticalUnblockLow { go wa.resyncContacts(false) } @@ -164,6 +165,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) { log.Warn().Err(err).Msg("Failed to send initial presence after connecting") } }() + go wa.syncRemoteProfile(log.WithContext(context.Background()), nil) } meta := wa.UserLogin.Metadata.(*waid.UserLoginMetadata) if meta.WALID == "" { @@ -524,6 +526,7 @@ func (wa *WhatsAppClient) syncGhost(jid types.JID, reason string, pictureID *str ghost.UpdateInfo(ctx, userInfo) log.Debug().Msg("Synced ghost info") } + go wa.syncRemoteProfile(ctx, ghost) } func (wa *WhatsAppClient) handleWAPictureUpdate(evt *events.Picture) { From 5d0d1ce1f5b25a202b9ac2a23ea08cefac6c453c Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 15 Apr 2025 15:01:14 +0300 Subject: [PATCH 027/486] phoneping: remove custom error messages Fixes #783 --- go.mod | 2 +- go.sum | 4 +-- pkg/connector/client.go | 5 +--- pkg/connector/handlewhatsapp.go | 4 +-- pkg/connector/phoneping.go | 46 +++++++++------------------------ 5 files changed, 18 insertions(+), 43 deletions(-) diff --git a/go.mod b/go.mod index 780d67c3..8005c9ae 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( golang.org/x/sync v0.13.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.23.3-0.20250405234116-e675a3c09c38 + maunium.net/go/mautrix v0.23.3-0.20250415114347-7cb13f8fd3df ) require ( diff --git a/go.sum b/go.sum index 3e492a88..c765bfe4 100644 --- a/go.sum +++ b/go.sum @@ -101,5 +101,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.23.3-0.20250405234116-e675a3c09c38 h1:fIe2+kYndm3Mm/DwQ4FsODk2DjrLeEeW7tKtZjyERqM= -maunium.net/go/mautrix v0.23.3-0.20250405234116-e675a3c09c38/go.mod h1:pCYLHmo02Jauak/9VlTkbGPrBMvLXsGqTGMNOx+L2PE= +maunium.net/go/mautrix v0.23.3-0.20250415114347-7cb13f8fd3df h1:PcvCzghuKgHXvi5f1mdNgphkYxIZCq+ZEmPRMZMNbL0= +maunium.net/go/mautrix v0.23.3-0.20250415114347-7cb13f8fd3df/go.mod h1:pCYLHmo02Jauak/9VlTkbGPrBMvLXsGqTGMNOx+L2PE= diff --git a/pkg/connector/client.go b/pkg/connector/client.go index ac79e5fe..aca32aa0 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -104,9 +104,7 @@ type WhatsAppClient struct { directMediaLock sync.Mutex mediaRetryLock *semaphore.Weighted offlineSyncWaiter chan error - - lastPhoneOfflineWarning time.Time - isNewLogin bool + isNewLogin bool } var ( @@ -300,7 +298,6 @@ func (wa *WhatsAppClient) startLoops() { } go wa.historySyncLoop(ctx) go wa.ghostResyncLoop(ctx) - go wa.disconnectWarningLoop(ctx) if mrc := wa.Main.Config.HistorySync.MediaRequests; mrc.AutoRequestMedia && mrc.RequestMethod == MediaRequestMethodLocalTime { go wa.mediaRequestLoop(ctx) } diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index b7a0667c..25b732fb 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -192,11 +192,11 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) { if !wa.PhoneRecentlySeen(true) { log.Info(). Time("phone_last_seen", wa.UserLogin.Metadata.(*waid.UserLoginMetadata).PhoneLastSeen.Time). - Msg("Offline sync completed, but phone last seen date is still old - sending phone offline bridge status") - wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateTransientDisconnect, Error: WAPhoneOffline}) + Msg("Offline sync completed, but phone last seen date is still old") } else { log.Info().Msg("Offline sync completed") } + wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected}) wa.notifyOfflineSyncWaiter(nil) case *events.LoggedOut: wa.handleWALogout(evt.Reason, evt.OnConnect) diff --git a/pkg/connector/phoneping.go b/pkg/connector/phoneping.go index cc430d1f..ce38079e 100644 --- a/pkg/connector/phoneping.go +++ b/pkg/connector/phoneping.go @@ -18,50 +18,26 @@ package connector import ( "context" - "fmt" "time" - "go.mau.fi/util/exfmt" "go.mau.fi/util/jsontime" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waE2E" "maunium.net/go/mautrix/bridgev2/status" - "maunium.net/go/mautrix/event" "go.mau.fi/mautrix-whatsapp/pkg/waid" ) -func (wa *WhatsAppClient) disconnectWarningLoop(ctx context.Context) { - ticker := time.NewTicker(1 * time.Hour) - defer ticker.Stop() - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - if wa.Client != nil && wa.Client.IsConnected() { - if !wa.PhoneRecentlySeen(true) { - go wa.sendPhoneOfflineWarning(ctx) - } - } - } - } -} +var _ status.BridgeStateFiller = (*WhatsAppClient)(nil) -func (wa *WhatsAppClient) sendPhoneOfflineWarning(ctx context.Context) { - if wa.UserLogin.User.ManagementRoom == "" || time.Since(wa.lastPhoneOfflineWarning) < 12*time.Hour { - // Don't spam the warning too much - return +func (wa *WhatsAppClient) FillBridgeState(state status.BridgeState) status.BridgeState { + if !wa.PhoneRecentlySeen(false) && state.StateEvent == status.StateConnected { + // TODO transient disconnect is wrong, this should be bad credentials or connected + state.StateEvent = status.StateTransientDisconnect + state.Error = WAPhoneOffline + state.UserAction = status.UserActionOpenNative } - wa.lastPhoneOfflineWarning = time.Now() - timeSinceSeen := time.Since(wa.UserLogin.Metadata.(*waid.UserLoginMetadata).PhoneLastSeen.Time).Round(time.Hour) - // TODO remove this manual message after bridge states are plumbed to the management room as messages - _, _ = wa.Main.Bridge.Bot.SendMessage(ctx, wa.UserLogin.User.ManagementRoom, event.EventMessage, &event.Content{ - Parsed: &event.MessageEventContent{ - MsgType: event.MsgText, - Body: fmt.Sprintf("Your phone hasn't been seen in %s. The server will force the bridge to log out if the phone is not active at least every 2 weeks.", exfmt.Duration(timeSinceSeen)), - }, - }, nil) + return state } const PhoneDisconnectWarningTime = 12 * 24 * time.Hour // 12 days @@ -128,7 +104,10 @@ func (wa *WhatsAppClient) phoneSeen(ts time.Time) { // The last seen timestamp isn't going to be perfectly accurate in any case, // so don't spam the database with an update every time there's an event. return - } else if !wa.PhoneRecentlySeen(false) { + } + hadBeenSeen := wa.PhoneRecentlySeen(false) + meta.PhoneLastSeen = jsontime.U(ts) + if !hadBeenSeen { isConnected := wa.IsLoggedIn() && wa.Client.IsConnected() prevStateError := wa.UserLogin.BridgeState.GetPrev().Error if prevStateError == WAPhoneOffline && isConnected { @@ -141,7 +120,6 @@ func (wa *WhatsAppClient) phoneSeen(ts time.Time) { Msg("Saw phone after current bridge state said it has been offline, not sending new bridge state") } } - meta.PhoneLastSeen = jsontime.U(ts) go func() { err := wa.UserLogin.Save(ctx) if err != nil { From 5ff8960e2ba777afb44afac79ae7c30c1483c111 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 16 Apr 2025 12:42:02 +0300 Subject: [PATCH 028/486] Bump version to v0.12.0 --- CHANGELOG.md | 9 +++++++++ cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 14 +++++++------- go.sum | 24 ++++++++++++------------ 4 files changed, 29 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac7b1199..9b7f100e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# v0.12.0 (2025-04-16) + +* Migrated Signal session store to use new `@lid` identifiers to support future + chats that don't expose phone numbers. +* Added fallbacks for various business message types. +* Added support for bridging invites, kicks and leaves in groups. +* Re-added `invite-link`, `join` and `sync` commands for groups. +* Fixed bridging chats with Meta AI. + # v0.11.4 (2025-03-16) * Fixed edits being bridged multiple times if a single chat had multiple diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index cc0dd041..4867132f 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -23,7 +23,7 @@ var m = mxmain.BridgeMain{ Name: "mautrix-whatsapp", URL: "https://github.com/mautrix/whatsapp", Description: "A Matrix-WhatsApp puppeting bridge.", - Version: "0.11.4", + Version: "0.12.0", Connector: c, } diff --git a/go.mod b/go.mod index 8005c9ae..b8afbe80 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module go.mau.fi/mautrix-whatsapp go 1.23.0 -toolchain go1.24.1 +toolchain go1.24.2 require ( github.com/gorilla/mux v1.8.0 @@ -11,13 +11,13 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.6 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250411192951-5ab78fadbf91 + go.mau.fi/whatsmeow v0.0.0-20250416093955-be60e7f990cd golang.org/x/image v0.26.0 golang.org/x/net v0.39.0 golang.org/x/sync v0.13.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.23.3-0.20250415114347-7cb13f8fd3df + maunium.net/go/mautrix v0.23.3 ) require ( @@ -27,8 +27,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.24 // indirect - github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 // indirect + github.com/mattn/go-sqlite3 v1.14.27 // indirect + github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a // 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 @@ -36,11 +36,11 @@ require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect - github.com/yuin/goldmark v1.7.8 // indirect + github.com/yuin/goldmark v1.7.10 // indirect go.mau.fi/libsignal v0.1.2 // indirect go.mau.fi/zeroconfig v0.1.3 // indirect golang.org/x/crypto v0.37.0 // indirect - golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect + golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect golang.org/x/sys v0.32.0 // indirect golang.org/x/text v0.24.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index c765bfe4..5eb4ac1a 100644 --- a/go.sum +++ b/go.sum @@ -32,10 +32,10 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ 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.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= -github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 h1:E7Kmf11E4K7B5hDti2K2NqPb1nlYlGYsu02S1JNd/Bs= -github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/mattn/go-sqlite3 v1.14.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU= +github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a h1:S+AGcmAESQ0pXCUNnRH7V+bOUIgkSX5qVt2cNKCrm0Q= +github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a/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= @@ -61,22 +61,22 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 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/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= -github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.10 h1:S+LrtBjRmqMac2UdtB6yyCEJm+UILZ2fefI4p7o0QpI= +github.com/yuin/goldmark v1.7.10/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.1.2 h1:Vs16DXWxSKyzVtI+EEXLCSy5pVWzzCzp/2eqFGvLyP0= go.mau.fi/libsignal v0.1.2/go.mod h1:JpnLSSJptn/s1sv7I56uEMywvz8x4YzxeF5OzdPb6PE= go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 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-20250411192951-5ab78fadbf91 h1:eziiUdmaGyhM7Fxn4IMRClBrX1b2sCAFGSBE2Z57yNg= -go.mau.fi/whatsmeow v0.0.0-20250411192951-5ab78fadbf91/go.mod h1:aj6MpceCuJ6m712lPEMAh8iAw2TkABx6TnPjMcdG0eE= +go.mau.fi/whatsmeow v0.0.0-20250416093955-be60e7f990cd h1:qDNM7J4aK+8v8utyDOSHDlXHV+5hnEB1bFYvCEBcosA= +go.mau.fi/whatsmeow v0.0.0-20250416093955-be60e7f990cd/go.mod h1:NlPtoLdpX3RnltqCTCZQ6kIUfprqLirtSK1gHvwoNx0= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= +golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY= golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c= golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= @@ -101,5 +101,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.23.3-0.20250415114347-7cb13f8fd3df h1:PcvCzghuKgHXvi5f1mdNgphkYxIZCq+ZEmPRMZMNbL0= -maunium.net/go/mautrix v0.23.3-0.20250415114347-7cb13f8fd3df/go.mod h1:pCYLHmo02Jauak/9VlTkbGPrBMvLXsGqTGMNOx+L2PE= +maunium.net/go/mautrix v0.23.3 h1:U+fzdcLhFKLUm5gf2+Q0hEUqWkwDMRfvE+paUH9ogSk= +maunium.net/go/mautrix v0.23.3/go.mod h1:LX+3evXVKSvh/b43BVC3rkvN2qV7b0bkIV4fY7Snn/4= From 94dad0912d14d2060d2ebfcfe81aef2a4d647ebe Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 16 Apr 2025 15:07:22 +0300 Subject: [PATCH 029/486] all: use safer store access --- pkg/connector/client.go | 8 ++++---- pkg/connector/handlematrix.go | 8 ++++---- pkg/connector/handlewhatsapp.go | 2 +- pkg/connector/id.go | 4 ++-- pkg/connector/userinfo.go | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index aca32aa0..d9a752fb 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -221,7 +221,7 @@ func (wa *WhatsAppClient) ConnectBackground(ctx context.Context, params *bridgev zerolog.Ctx(ctx).Err(err).Msg("Failed to update proxy") } wa.Client.GetClientPayload = func() *waWa6.ClientPayload { - payload := wa.Client.Store.GetClientPayload() + payload := wa.GetStore().GetClientPayload() payload.ConnectReason = waWa6.ClientPayload_PUSH.Enum() return payload } @@ -338,7 +338,7 @@ func (wa *WhatsAppClient) IsLoggedIn() bool { } func (wa *WhatsAppClient) syncRemoteProfile(ctx context.Context, ghost *bridgev2.Ghost) { - ownID := waid.MakeUserID(wa.Device.GetJID()) + ownID := waid.MakeUserID(wa.GetStore().GetJID()) if ghost == nil { var err error ghost, err = wa.Main.Bridge.GetExistingGhostByID(ctx, ownID) @@ -352,9 +352,9 @@ func (wa *WhatsAppClient) syncRemoteProfile(ctx context.Context, ghost *bridgev2 if ghost.ID != ownID { return } - name := wa.Device.BusinessName + name := wa.GetStore().BusinessName if name == "" { - name = wa.Device.PushName + name = wa.GetStore().PushName } if name == "" || wa.UserLogin.RemoteProfile.Name == name && wa.UserLogin.RemoteProfile.Avatar == ghost.AvatarMXC { return diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index 4046a3da..4047ef94 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -82,7 +82,7 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg return nil, ErrBroadcastSendDisabled } wrappedMsgID := waid.MakeMessageID(chatJID, wa.JID, req.ID) - wrappedMsgID2 := waid.MakeMessageID(chatJID, wa.Device.GetLID(), req.ID) + wrappedMsgID2 := waid.MakeMessageID(chatJID, wa.GetStore().GetLID(), req.ID) msg.AddPendingToIgnore(networkid.TransactionID(wrappedMsgID)) msg.AddPendingToIgnore(networkid.TransactionID(wrappedMsgID2)) resp, err := wa.Client.SendMessage(ctx, chatJID, waMsg, *req) @@ -90,7 +90,7 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg return nil, err } var pickedMessageID networkid.MessageID - if resp.Sender == wa.Device.GetLID() { + if resp.Sender == wa.GetStore().GetLID() { pickedMessageID = wrappedMsgID2 msg.RemovePending(networkid.TransactionID(wrappedMsgID)) } else { @@ -122,7 +122,7 @@ func (wa *WhatsAppClient) PreHandleMatrixReaction(_ context.Context, msg *bridge if portalJID.Server == types.HiddenUserServer || msg.Portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup || msg.Portal.Metadata.(*waid.PortalMetadata).AddressingMode == types.AddressingModeLID { - sender = wa.Device.GetLID() + sender = wa.GetStore().GetLID() } return bridgev2.MatrixReactionPreResponse{ SenderID: waid.MakeUserID(sender), @@ -273,7 +273,7 @@ func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt * if err != nil { continue } - if parsed.Sender.User == wa.Device.GetLID().User || parsed.Sender.User == wa.JID.User { + if parsed.Sender.User == wa.GetStore().GetLID().User || parsed.Sender.User == wa.JID.User { continue } var key types.JID diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 25b732fb..0a83902d 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -169,7 +169,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) { } meta := wa.UserLogin.Metadata.(*waid.UserLoginMetadata) if meta.WALID == "" { - meta.WALID = wa.Client.Store.GetLID().User + meta.WALID = wa.GetStore().GetLID().User if meta.WALID != "" { go func() { err := wa.UserLogin.Save(log.WithContext(context.Background())) diff --git a/pkg/connector/id.go b/pkg/connector/id.go index 68e3f632..3b5d23d1 100644 --- a/pkg/connector/id.go +++ b/pkg/connector/id.go @@ -31,7 +31,7 @@ func (wa *WhatsAppClient) makeEventSender(id types.JID) bridgev2.EventSender { return bridgev2.EventSender{} } return bridgev2.EventSender{ - IsFromMe: id.User == wa.Client.Store.GetJID().User || id.User == wa.Client.Store.GetLID().User, + IsFromMe: id.User == wa.GetStore().GetJID().User || id.User == wa.GetStore().GetLID().User, Sender: waid.MakeUserID(id), SenderLogin: waid.MakeUserLoginID(id), } @@ -42,7 +42,7 @@ 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.Client.Store.GetJID().User || id.Sender.User == wa.Client.Store.GetLID().User { + if id.Sender.User == wa.GetStore().GetJID().User || id.Sender.User == wa.GetStore().GetLID().User { 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 { diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 9556ece5..f4e0d337 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -272,7 +272,7 @@ func (wa *WhatsAppClient) fetchGhostAvatar(ctx context.Context, ghost *bridgev2. func (wa *WhatsAppClient) resyncContacts(forceAvatarSync bool) { log := wa.UserLogin.Log.With().Str("action", "resync contacts").Logger() ctx := log.WithContext(context.Background()) - contacts, err := wa.Device.Contacts.GetAllContacts() + contacts, err := wa.GetStore().Contacts.GetAllContacts() if err != nil { log.Err(err).Msg("Failed to get cached contacts") return From 4b8aacf96455005480a8cdb0a1259e90b8f522b2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 17 Apr 2025 01:41:12 +0300 Subject: [PATCH 030/486] 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 b8afbe80..d9006e1f 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.6 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250416093955-be60e7f990cd + go.mau.fi/whatsmeow v0.0.0-20250416223844-bc839c5a1270 golang.org/x/image v0.26.0 golang.org/x/net v0.39.0 golang.org/x/sync v0.13.0 diff --git a/go.sum b/go.sum index 5eb4ac1a..25becb1d 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 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-20250416093955-be60e7f990cd h1:qDNM7J4aK+8v8utyDOSHDlXHV+5hnEB1bFYvCEBcosA= -go.mau.fi/whatsmeow v0.0.0-20250416093955-be60e7f990cd/go.mod h1:NlPtoLdpX3RnltqCTCZQ6kIUfprqLirtSK1gHvwoNx0= +go.mau.fi/whatsmeow v0.0.0-20250416223844-bc839c5a1270 h1:EAONKTw0ufGCkgJsD+v9gHNNU4kiiCDIAapwfOTu4+A= +go.mau.fi/whatsmeow v0.0.0-20250416223844-bc839c5a1270/go.mod h1:NlPtoLdpX3RnltqCTCZQ6kIUfprqLirtSK1gHvwoNx0= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= From 35ced95b8fc6d5a21f9f3fa6e3fe22f034feca13 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 17 Apr 2025 16:18:07 +0300 Subject: [PATCH 031/486] 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 d9006e1f..ac9e5df0 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.6 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250416223844-bc839c5a1270 + go.mau.fi/whatsmeow v0.0.0-20250417131650-164ddf482526 golang.org/x/image v0.26.0 golang.org/x/net v0.39.0 golang.org/x/sync v0.13.0 diff --git a/go.sum b/go.sum index 25becb1d..b696b832 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 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-20250416223844-bc839c5a1270 h1:EAONKTw0ufGCkgJsD+v9gHNNU4kiiCDIAapwfOTu4+A= -go.mau.fi/whatsmeow v0.0.0-20250416223844-bc839c5a1270/go.mod h1:NlPtoLdpX3RnltqCTCZQ6kIUfprqLirtSK1gHvwoNx0= +go.mau.fi/whatsmeow v0.0.0-20250417131650-164ddf482526 h1:i9w16FdM3zmOWdF5nh1l2MlmE/wK7ulL6rbT02WBBJs= +go.mau.fi/whatsmeow v0.0.0-20250417131650-164ddf482526/go.mod h1:NlPtoLdpX3RnltqCTCZQ6kIUfprqLirtSK1gHvwoNx0= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= From 24da35d4f72d1ab3d56f47507445424e5dd86fb1 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 21 Apr 2025 15:03:15 +0300 Subject: [PATCH 032/486] msgconv/from-matrix: ignore failing to parse reply message ID --- pkg/msgconv/from-matrix.go | 14 +++++++------- pkg/msgconv/matrixpoll.go | 5 +---- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index f6f670e5..9b9b529b 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -48,7 +48,7 @@ import ( "go.mau.fi/mautrix-whatsapp/pkg/waid" ) -func (mc *MessageConverter) generateContextInfo(replyTo *database.Message, portal *bridgev2.Portal) (*waE2E.ContextInfo, error) { +func (mc *MessageConverter) generateContextInfo(ctx context.Context, replyTo *database.Message, portal *bridgev2.Portal) *waE2E.ContextInfo { contextInfo := &waE2E.ContextInfo{} if replyTo != nil { msgID, err := waid.ParseMessageID(replyTo.ID) @@ -57,7 +57,10 @@ func (mc *MessageConverter) generateContextInfo(replyTo *database.Message, porta contextInfo.Participant = proto.String(msgID.Sender.String()) contextInfo.QuotedMessage = &waE2E.Message{Conversation: proto.String("")} } else { - return nil, err + zerolog.Ctx(ctx).Warn().Err(err). + Stringer("reply_to_event_id", replyTo.MXID). + Str("reply_to_message_id", string(replyTo.ID)). + Msg("Failed to parse reply to message ID") } } if portal.Disappear.Timer > 0 { @@ -67,7 +70,7 @@ func (mc *MessageConverter) generateContextInfo(replyTo *database.Message, porta contextInfo.EphemeralSettingTimestamp = ptr.Ptr(setAt) } } - return contextInfo, nil + return contextInfo } func (mc *MessageConverter) ToWhatsApp( @@ -86,10 +89,7 @@ func (mc *MessageConverter) ToWhatsApp( } message := &waE2E.Message{} - contextInfo, err := mc.generateContextInfo(replyTo, portal) - if err != nil { - return nil, nil, err - } + contextInfo := mc.generateContextInfo(ctx, replyTo, portal) switch content.MsgType { case event.MsgText, event.MsgNotice, event.MsgEmote: diff --git a/pkg/msgconv/matrixpoll.go b/pkg/msgconv/matrixpoll.go index ad3b4e58..b4b20b1c 100644 --- a/pkg/msgconv/matrixpoll.go +++ b/pkg/msgconv/matrixpoll.go @@ -71,10 +71,7 @@ func (mc *MessageConverter) PollStartToWhatsApp( if maxAnswers >= len(content.PollStart.Answers) || maxAnswers < 0 { maxAnswers = 0 } - contextInfo, err := mc.generateContextInfo(replyTo, portal) - if err != nil { - return nil, nil, err - } + contextInfo := mc.generateContextInfo(ctx, replyTo, portal) var question string question, contextInfo.MentionedJID = mc.msc1767ToWhatsApp(ctx, content.PollStart.Question, content.Mentions) if len(question) == 0 { From 88efecef60285c8f1f78a565dc9432ee7232ee58 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 21 Apr 2025 15:03:33 +0300 Subject: [PATCH 033/486] handlematrix: update error messages for failing to parse IDs --- pkg/connector/handlematrix.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index 4047ef94..f77a03fd 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -114,7 +114,7 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg func (wa *WhatsAppClient) PreHandleMatrixReaction(_ context.Context, msg *bridgev2.MatrixReaction) (bridgev2.MatrixReactionPreResponse, error) { portalJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { - return bridgev2.MatrixReactionPreResponse{}, err + return bridgev2.MatrixReactionPreResponse{}, fmt.Errorf("failed to parse portal ID: %w", err) } else if portalJID == types.StatusBroadcastJID { return bridgev2.MatrixReactionPreResponse{}, ErrBroadcastReactionUnsupported } @@ -134,12 +134,12 @@ func (wa *WhatsAppClient) PreHandleMatrixReaction(_ context.Context, msg *bridge func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev2.MatrixReaction) (*database.Reaction, error) { messageID, err := waid.ParseMessageID(msg.TargetMessage.ID) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to parse target message ID: %w", err) } portalJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to parse portal ID: %w", err) } reactionMsg := &waE2E.Message{ ReactionMessage: &waE2E.ReactionMessage{ @@ -172,12 +172,12 @@ func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev func (wa *WhatsAppClient) HandleMatrixReactionRemove(ctx context.Context, msg *bridgev2.MatrixReactionRemove) error { messageID, err := waid.ParseMessageID(msg.TargetReaction.MessageID) if err != nil { - return err + return fmt.Errorf("failed to parse target message ID: %w", err) } portalJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { - return err + return fmt.Errorf("failed to parse portal ID: %w", err) } reactionMsg := &waE2E.Message{ @@ -199,12 +199,12 @@ func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.M messageID, err := waid.ParseMessageID(edit.EditTarget.ID) if err != nil { - return err + return fmt.Errorf("failed to parse target message ID: %w", err) } portalJID, err := waid.ParsePortalID(edit.Portal.ID) if err != nil { - return err + return fmt.Errorf("failed to parse portal ID: %w", err) } waMsg, _, err := wa.Main.MsgConv.ToWhatsApp(ctx, wa.Client, edit.Event, edit.Content, nil, nil, edit.Portal) @@ -229,12 +229,12 @@ func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *br log := zerolog.Ctx(ctx) messageID, err := waid.ParseMessageID(msg.TargetMessage.ID) if err != nil { - return err + return fmt.Errorf("failed to parse target message ID: %w", err) } portalJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { - return err + return fmt.Errorf("failed to parse portal ID: %w", err) } revokeMessage := wa.Client.BuildRevoke(messageID.Chat, messageID.Sender, messageID.ID) @@ -253,7 +253,7 @@ func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt * } portalJID, err := waid.ParsePortalID(receipt.Portal.ID) if err != nil { - return err + return fmt.Errorf("failed to parse portal ID: %w", err) } messages, err := receipt.Portal.Bridge.DB.Message.GetMessagesBetweenTimeQuery(ctx, receipt.Portal.PortalKey, receipt.LastRead, receipt.ReadUpTo) if err != nil { From eb73e2b97320418ba0f5b177172c612fac20699c Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 22 Apr 2025 21:52:45 +0300 Subject: [PATCH 034/486] msgconv/from-whatsapp: add prefix for forwarded messages --- pkg/msgconv/from-whatsapp.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 54e8e1a7..3bb824e7 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -221,6 +221,21 @@ func (mc *MessageConverter) ToMatrix( MessageID: waid.MakeMessageID(chat, pcp, contextInfo.GetStanzaID()), } } + if contextInfo.GetIsForwarded() { + hasCaption := part.Content.FileName != "" && part.Content.FileName != part.Content.Body + isMedia := part.Content.MsgType.IsMedia() + isText := part.Content.MsgType.IsText() + if isMedia && !hasCaption { + part.Content.FileName = part.Content.Body + part.Content.Body = "↷ Forwarded" + part.Content.Format = event.FormatHTML + part.Content.FormattedBody = "

↷ Forwarded

" + } else if isText || isMedia { + part.Content.EnsureHasHTML() + part.Content.Body = "↷ Forwarded\n\n" + part.Content.Body + part.Content.FormattedBody = "

↷ Forwarded

" + part.Content.FormattedBody + } + } commentTarget := waMsg.GetEncCommentMessage().GetTargetMessageKey() if commentTarget == nil { commentTarget = waMsg.GetCommentMessage().GetTargetMessageKey() From 927db4505b1af948b4b2b509e0f38ded28ee5f16 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 24 Apr 2025 12:36:34 +0300 Subject: [PATCH 035/486] backfill: disable backfilling PSA chat --- pkg/connector/backfill.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 2c9bccc0..bbb50f21 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -213,6 +213,10 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { if conv.ChatJID == types.StatusBroadcastJID && !wa.Main.Config.EnableStatusBroadcast { wg.Done() continue + } else if conv.ChatJID == types.PSAJID { + // We don't currently support new PSAs, so don't bother backfilling them either + wg.Done() + continue } // TODO can the chat info fetch be avoided entirely? time.Sleep(time.Duration(rateLimitErrors) * time.Second) From 73296a3c0173249394caa22f8a48acd6f26c853a Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 24 Apr 2025 13:15:11 +0300 Subject: [PATCH 036/486] wadb/conversation: use highest message timestamp if conversation info doesn't have it --- pkg/connector/backfill.go | 11 ++++++++--- pkg/connector/wadb/conversation.go | 8 ++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index bbb50f21..56cb8474 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -155,6 +155,7 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor Dict("metadata", zerolog.Dict(). Uint32("ephemeral_expiration", conv.GetEphemeralExpiration()). Int64("ephemeral_setting_timestamp", conv.GetEphemeralSettingTimestamp()). + Uint64("last_message_ts", conv.GetLastMsgTimestamp()). Bool("marked_unread", conv.GetMarkedAsUnread()). Bool("archived", conv.GetArchived()). Uint32("pinned", conv.GetPinned()). @@ -164,7 +165,7 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor 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)) + err = wa.Main.DB.Conversation.Put(ctx, wadb.NewConversation(wa.UserLogin.ID, jid, conv, maxTime)) if err != nil { log.Err(err).Msg("Failed to save conversation metadata") continue @@ -249,8 +250,12 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { } wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &simplevent.ChatResync{ EventMeta: simplevent.EventMeta{ - Type: bridgev2.RemoteEventChatResync, - LogContext: nil, + Type: bridgev2.RemoteEventChatResync, + LogContext: func(c zerolog.Context) zerolog.Context { + return c. + Stringer("chat_jid", conv.ChatJID). + Time("latest_message_ts", conv.LastMessageTimestamp) + }, PortalKey: wa.makeWAPortalKey(conv.ChatJID), CreatePortal: true, PostHandleFunc: func(ctx context.Context, portal *bridgev2.Portal) { diff --git a/pkg/connector/wadb/conversation.go b/pkg/connector/wadb/conversation.go index 3141f3c5..29694d77 100644 --- a/pkg/connector/wadb/conversation.go +++ b/pkg/connector/wadb/conversation.go @@ -40,15 +40,19 @@ func parseHistoryTime(ts *uint64) time.Time { return time.Unix(int64(*ts), 0) } -func NewConversation(loginID networkid.UserLoginID, chatJID types.JID, conv *waHistorySync.Conversation) *Conversation { +func NewConversation(loginID networkid.UserLoginID, chatJID types.JID, conv *waHistorySync.Conversation, mostRecentMessage time.Time) *Conversation { var pinned *bool if conv.Pinned != nil { pinned = ptr.Ptr(*conv.Pinned > 0) } + lastMessageTS := parseHistoryTime(conv.LastMsgTimestamp) + if lastMessageTS.IsZero() { + lastMessageTS = mostRecentMessage + } return &Conversation{ UserLoginID: loginID, ChatJID: chatJID, - LastMessageTimestamp: parseHistoryTime(conv.LastMsgTimestamp), + LastMessageTimestamp: lastMessageTS, Archived: conv.Archived, Pinned: pinned, MuteEndTime: parseHistoryTime(conv.MuteEndTime), From 9336033722b32beed90e29ad4071658567fb3e92 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 24 Apr 2025 13:17:01 +0300 Subject: [PATCH 037/486] 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 ac9e5df0..f46d65f8 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.6 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250417131650-164ddf482526 + go.mau.fi/whatsmeow v0.0.0-20250424100714-086604102f64 golang.org/x/image v0.26.0 golang.org/x/net v0.39.0 golang.org/x/sync v0.13.0 diff --git a/go.sum b/go.sum index b696b832..cbe31314 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 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-20250417131650-164ddf482526 h1:i9w16FdM3zmOWdF5nh1l2MlmE/wK7ulL6rbT02WBBJs= -go.mau.fi/whatsmeow v0.0.0-20250417131650-164ddf482526/go.mod h1:NlPtoLdpX3RnltqCTCZQ6kIUfprqLirtSK1gHvwoNx0= +go.mau.fi/whatsmeow v0.0.0-20250424100714-086604102f64 h1:4w1P3c8HACMpsFeGvOZxG20i+TWbBK0a8ExwQvI0nYs= +go.mau.fi/whatsmeow v0.0.0-20250424100714-086604102f64/go.mod h1:NlPtoLdpX3RnltqCTCZQ6kIUfprqLirtSK1gHvwoNx0= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= From 746aa0fbcb9a3382420500fbc969c068843a6122 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 24 Apr 2025 15:57:49 +0300 Subject: [PATCH 038/486] msgconv/wa-media: update lottie mime type As per --- pkg/connector/capabilities.go | 2 +- pkg/msgconv/wa-media.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/connector/capabilities.go b/pkg/connector/capabilities.go index 306f1803..1af3a9a9 100644 --- a/pkg/connector/capabilities.go +++ b/pkg/connector/capabilities.go @@ -98,7 +98,7 @@ var whatsappCaps = &event.RoomFeatures{ MimeTypes: map[string]event.CapabilitySupportLevel{ "image/webp": event.CapLevelFullySupported, // TODO see if sending lottie is possible - //"image/lottie+json": event.CapLevelFullySupported, + //"video/lottie+json": event.CapLevelFullySupported, "image/png": event.CapLevelPartialSupport, "image/jpeg": event.CapLevelPartialSupport, }, diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index de7b59d9..dea7f545 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -400,7 +400,7 @@ func (mc *MessageConverter) extractAnimatedSticker(fileInfo *PreparedMedia, data if err != nil { return nil, fmt.Errorf("failed to read animation.json: %w", err) } - fileInfo.Info.MimeType = "image/lottie+json" + fileInfo.Info.MimeType = "video/lottie+json" fileInfo.FileName = "sticker.json" return data, nil } From b81f71d5432c8a5ff2c6fa01241faaad3c55a899 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 1 May 2025 16:59:57 +0300 Subject: [PATCH 039/486] handlematrix: use client-generated transaction IDs --- go.mod | 6 +++--- go.sum | 12 ++++++------ pkg/connector/connector.go | 11 +++++++++++ pkg/connector/handlematrix.go | 30 ++++++++++++++++++++++++++---- 4 files changed, 46 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index f46d65f8..718687c1 100644 --- a/go.mod +++ b/go.mod @@ -9,15 +9,15 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.8.6 + go.mau.fi/util v0.8.7-0.20250427215252-d2d18a7e463c go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250424100714-086604102f64 + go.mau.fi/whatsmeow v0.0.0-20250501130609-4c93ee4e6efa golang.org/x/image v0.26.0 golang.org/x/net v0.39.0 golang.org/x/sync v0.13.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.23.3 + maunium.net/go/mautrix v0.23.4-0.20250501132821-5094eea71864 ) require ( diff --git a/go.sum b/go.sum index cbe31314..5070d5b2 100644 --- a/go.sum +++ b/go.sum @@ -65,12 +65,12 @@ github.com/yuin/goldmark v1.7.10 h1:S+LrtBjRmqMac2UdtB6yyCEJm+UILZ2fefI4p7o0QpI= github.com/yuin/goldmark v1.7.10/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.1.2 h1:Vs16DXWxSKyzVtI+EEXLCSy5pVWzzCzp/2eqFGvLyP0= go.mau.fi/libsignal v0.1.2/go.mod h1:JpnLSSJptn/s1sv7I56uEMywvz8x4YzxeF5OzdPb6PE= -go.mau.fi/util v0.8.6 h1:AEK13rfgtiZJL2YsNK+W4ihhYCuukcRom8WPP/w/L54= -go.mau.fi/util v0.8.6/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= +go.mau.fi/util v0.8.7-0.20250427215252-d2d18a7e463c h1:qfJyMZq1pPyuXKoVWwHs6OmR9CzO3pHFRPYT/QpaaaA= +go.mau.fi/util v0.8.7-0.20250427215252-d2d18a7e463c/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 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-20250424100714-086604102f64 h1:4w1P3c8HACMpsFeGvOZxG20i+TWbBK0a8ExwQvI0nYs= -go.mau.fi/whatsmeow v0.0.0-20250424100714-086604102f64/go.mod h1:NlPtoLdpX3RnltqCTCZQ6kIUfprqLirtSK1gHvwoNx0= +go.mau.fi/whatsmeow v0.0.0-20250501130609-4c93ee4e6efa h1:+bQKfMtnhX2jVoCSaneH4Ctk51IVT1K2gvjyqfFjVW0= +go.mau.fi/whatsmeow v0.0.0-20250501130609-4c93ee4e6efa/go.mod h1:NlPtoLdpX3RnltqCTCZQ6kIUfprqLirtSK1gHvwoNx0= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= @@ -101,5 +101,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.23.3 h1:U+fzdcLhFKLUm5gf2+Q0hEUqWkwDMRfvE+paUH9ogSk= -maunium.net/go/mautrix v0.23.3/go.mod h1:LX+3evXVKSvh/b43BVC3rkvN2qV7b0bkIV4fY7Snn/4= +maunium.net/go/mautrix v0.23.4-0.20250501132821-5094eea71864 h1:1NmHnfbxtVlX2hcwL+zTEnVKSBncZIFV6NW9i2q8EaI= +maunium.net/go/mautrix v0.23.4-0.20250501132821-5094eea71864/go.mod h1:pT4G5RZQ+nLfKzsmeDa4NhHghOVTrasLLwY9tZ2mO08= diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 58b369a3..d052ed2b 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -18,12 +18,14 @@ package connector import ( "context" + "encoding/hex" "fmt" "strings" "sync" "sync/atomic" "go.mau.fi/util/dbutil" + "go.mau.fi/util/random" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waCompanionReg" "go.mau.fi/whatsmeow/store" @@ -33,6 +35,9 @@ import ( "google.golang.org/protobuf/proto" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/commands" + "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" @@ -161,3 +166,9 @@ func (wa *WhatsAppConnector) onFirstClientConnect() { wa.stopMediaEditCacheLoop.Store(&cancel) go wa.mediaEditCacheExpireLoop(meclCtx) } + +func (wa *WhatsAppConnector) GenerateTransactionID(_ id.UserID, _ id.RoomID, _ event.Type) networkid.RawTransactionID { + // The "proper" way would be a hash of the user ID among other things, but the hash includes random bytes too, + // so nobody can tell the difference if we just generate random bytes. + return networkid.RawTransactionID(whatsmeow.WebMessageIDPrefix + strings.ToUpper(hex.EncodeToString(random.Bytes(9)))) +} diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index f77a03fd..8dfa5c93 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strings" "time" "github.com/rs/zerolog" @@ -73,7 +74,12 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg if req == nil { req = &whatsmeow.SendRequestExtra{} } - req.ID = wa.Client.GenerateMessageID() + if strings.HasPrefix(string(msg.InputTransactionID), whatsmeow.WebMessageIDPrefix) { + req.ID = types.MessageID(msg.InputTransactionID) + } else { + req.ID = wa.Client.GenerateMessageID() + } + chatJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { return nil, err @@ -188,14 +194,25 @@ func (wa *WhatsAppClient) HandleMatrixReactionRemove(ctx context.Context, msg *b }, } - resp, err := wa.Client.SendMessage(ctx, portalJID, reactionMsg) + extra := whatsmeow.SendRequestExtra{} + if strings.HasPrefix(string(msg.InputTransactionID), whatsmeow.WebMessageIDPrefix) { + extra.ID = types.MessageID(msg.InputTransactionID) + } + + 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 { log := zerolog.Ctx(ctx) - editID := wa.Client.GenerateMessageID() + + var editID types.MessageID + if strings.HasPrefix(string(edit.InputTransactionID), whatsmeow.WebMessageIDPrefix) { + editID = types.MessageID(edit.InputTransactionID) + } else { + editID = wa.Client.GenerateMessageID() + } messageID, err := waid.ParseMessageID(edit.EditTarget.ID) if err != nil { @@ -239,7 +256,12 @@ func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *br revokeMessage := wa.Client.BuildRevoke(messageID.Chat, messageID.Sender, messageID.ID) - resp, err := wa.Client.SendMessage(ctx, portalJID, revokeMessage) + extra := whatsmeow.SendRequestExtra{} + if strings.HasPrefix(string(msg.InputTransactionID), whatsmeow.WebMessageIDPrefix) { + extra.ID = types.MessageID(msg.InputTransactionID) + } + + resp, err := wa.Client.SendMessage(ctx, portalJID, revokeMessage, extra) log.Trace().Any("response", resp).Msg("WhatsApp delete response") return err } From 6f3e8ea5d1f706d6cb859f1c6faab20cf0f727cc Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 14 May 2025 14:40:03 +0300 Subject: [PATCH 040/486] all: update whatsmeow to use more contexts --- cmd/mautrix-whatsapp/legacyprovision.go | 2 +- go.mod | 18 +++++++------- go.sum | 32 ++++++++++++------------- pkg/connector/chatinfo.go | 8 +++---- pkg/connector/client.go | 7 +++--- pkg/connector/commands.go | 2 +- pkg/connector/connector.go | 4 ++-- pkg/connector/directmedia.go | 4 ++-- pkg/connector/handlematrix.go | 2 +- pkg/connector/handlewhatsapp.go | 11 +++++---- pkg/connector/login.go | 2 +- pkg/connector/startchat.go | 2 +- pkg/connector/userinfo.go | 8 +++---- pkg/msgconv/from-matrix.go | 2 +- pkg/msgconv/matrixpoll.go | 2 +- pkg/msgconv/urlpreview.go | 2 +- pkg/msgconv/wa-media.go | 4 ++-- pkg/msgconv/wa-poll.go | 2 +- 18 files changed, 58 insertions(+), 56 deletions(-) diff --git a/cmd/mautrix-whatsapp/legacyprovision.go b/cmd/mautrix-whatsapp/legacyprovision.go index 30e43c5a..4b4793cb 100644 --- a/cmd/mautrix-whatsapp/legacyprovision.go +++ b/cmd/mautrix-whatsapp/legacyprovision.go @@ -236,7 +236,7 @@ func legacyProvContacts(w http.ResponseWriter, r *http.Request) { if userLogin == nil { return } - if contacts, err := userLogin.Client.(*connector.WhatsAppClient).Device.Contacts.GetAllContacts(); err != nil { + if contacts, err := userLogin.Client.(*connector.WhatsAppClient).Device.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", diff --git a/go.mod b/go.mod index 718687c1..62d2d7f9 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module go.mau.fi/mautrix-whatsapp go 1.23.0 -toolchain go1.24.2 +toolchain go1.24.3 require ( github.com/gorilla/mux v1.8.0 @@ -11,13 +11,13 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.7-0.20250427215252-d2d18a7e463c go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250501130609-4c93ee4e6efa + go.mau.fi/whatsmeow v0.0.0-20250514120219-ef9c1e91094e golang.org/x/image v0.26.0 - golang.org/x/net v0.39.0 - golang.org/x/sync v0.13.0 + golang.org/x/net v0.40.0 + golang.org/x/sync v0.14.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.23.4-0.20250501132821-5094eea71864 + maunium.net/go/mautrix v0.23.4-0.20250509121614-a0191c8f5847 ) require ( @@ -37,12 +37,12 @@ require ( github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect github.com/yuin/goldmark v1.7.10 // indirect - go.mau.fi/libsignal v0.1.2 // indirect + go.mau.fi/libsignal v0.2.0 // indirect go.mau.fi/zeroconfig v0.1.3 // indirect - golang.org/x/crypto v0.37.0 // indirect + golang.org/x/crypto v0.38.0 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/text v0.24.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.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 5070d5b2..4317b489 100644 --- a/go.sum +++ b/go.sum @@ -63,33 +63,33 @@ 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/yuin/goldmark v1.7.10 h1:S+LrtBjRmqMac2UdtB6yyCEJm+UILZ2fefI4p7o0QpI= github.com/yuin/goldmark v1.7.10/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= -go.mau.fi/libsignal v0.1.2 h1:Vs16DXWxSKyzVtI+EEXLCSy5pVWzzCzp/2eqFGvLyP0= -go.mau.fi/libsignal v0.1.2/go.mod h1:JpnLSSJptn/s1sv7I56uEMywvz8x4YzxeF5OzdPb6PE= +go.mau.fi/libsignal v0.2.0 h1:oRXj3OHhEJq51BFEM8/50UZblmWiTYH93hsNTPcbk90= +go.mau.fi/libsignal v0.2.0/go.mod h1:tvjoDsMejgT38CXTXwqaYu8itBiY8O2Mb6biWvZBb9k= go.mau.fi/util v0.8.7-0.20250427215252-d2d18a7e463c h1:qfJyMZq1pPyuXKoVWwHs6OmR9CzO3pHFRPYT/QpaaaA= go.mau.fi/util v0.8.7-0.20250427215252-d2d18a7e463c/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= 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-20250501130609-4c93ee4e6efa h1:+bQKfMtnhX2jVoCSaneH4Ctk51IVT1K2gvjyqfFjVW0= -go.mau.fi/whatsmeow v0.0.0-20250501130609-4c93ee4e6efa/go.mod h1:NlPtoLdpX3RnltqCTCZQ6kIUfprqLirtSK1gHvwoNx0= +go.mau.fi/whatsmeow v0.0.0-20250514120219-ef9c1e91094e h1:7lboFyrizLaof8CBFIlqk1B50DVeVAq0vlMkBQVGE7A= +go.mau.fi/whatsmeow v0.0.0-20250514120219-ef9c1e91094e/go.mod h1:0b/hOeTpA38V89YTtE2yqMKfDfaffj8gMu+AswqprQI= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY= golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -101,5 +101,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.23.4-0.20250501132821-5094eea71864 h1:1NmHnfbxtVlX2hcwL+zTEnVKSBncZIFV6NW9i2q8EaI= -maunium.net/go/mautrix v0.23.4-0.20250501132821-5094eea71864/go.mod h1:pT4G5RZQ+nLfKzsmeDa4NhHghOVTrasLLwY9tZ2mO08= +maunium.net/go/mautrix v0.23.4-0.20250509121614-a0191c8f5847 h1:umv6WzuHjAVLVJlNeGEUogZtcD+3I6/OIpFnThUBb+A= +maunium.net/go/mautrix v0.23.4-0.20250509121614-a0191c8f5847/go.mod h1:pT4G5RZQ+nLfKzsmeDa4NhHghOVTrasLLwY9tZ2mO08= diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 81776dc1..af75af34 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -92,7 +92,7 @@ func updateDisappearingTimerSetAt(ts int64) bridgev2.ExtraUpdater[*bridgev2.Port } func (wa *WhatsAppClient) applyChatSettings(ctx context.Context, chatID types.JID, info *bridgev2.ChatInfo) { - chat, err := wa.GetStore().ChatSettings.GetChatSettings(chatID) + chat, err := wa.GetStore().ChatSettings.GetChatSettings(ctx, chatID) if err != nil { zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to get chat settings") return @@ -406,7 +406,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(avatar.DirectPath, nil, nil, nil, 0, "", "") + return wa.Client.DownloadMediaWithPath(ctx, avatar.DirectPath, nil, nil, nil, 0, "", "") }, } } @@ -441,7 +441,7 @@ func (wa *WhatsAppClient) wrapNewsletterInfo(info *types.NewsletterMetadata) *br 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(info.ThreadMeta.Picture.DirectPath, nil, nil, nil, 0, "", "") + return wa.Client.DownloadMediaWithPath(ctx, info.ThreadMeta.Picture.DirectPath, nil, nil, nil, 0, "", "") } } else if info.ThreadMeta.Preview.ID != "" { avatar.ID = networkid.AvatarID(info.ThreadMeta.Preview.ID) @@ -452,7 +452,7 @@ func (wa *WhatsAppClient) wrapNewsletterInfo(info *types.NewsletterMetadata) *br } else if meta.ThreadMeta.Picture == nil { return nil, fmt.Errorf("full res avatar info is missing") } - return wa.Client.DownloadMediaWithPath(meta.ThreadMeta.Picture.DirectPath, nil, nil, nil, 0, "", "") + return wa.Client.DownloadMediaWithPath(ctx, meta.ThreadMeta.Picture.DirectPath, nil, nil, nil, 0, "", "") } } else { avatar.ID = "remove" diff --git a/pkg/connector/client.go b/pkg/connector/client.go index d9a752fb..b6eb3768 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -41,7 +41,7 @@ import ( "go.mau.fi/mautrix-whatsapp/pkg/waid" ) -func (wa *WhatsAppConnector) LoadUserLogin(_ context.Context, login *bridgev2.UserLogin) error { +func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2.UserLogin) error { w := &WhatsAppClient{ Main: wa, UserLogin: login, @@ -60,7 +60,7 @@ func (wa *WhatsAppConnector) LoadUserLogin(_ context.Context, login *bridgev2.Us var err error w.JID = waid.ParseUserLoginID(login.ID, loginMetadata.WADeviceID) - w.Device, err = wa.DeviceStore.GetDevice(w.JID) + w.Device, err = wa.DeviceStore.GetDevice(ctx, w.JID) if err != nil { return err } @@ -71,6 +71,7 @@ func (wa *WhatsAppConnector) LoadUserLogin(_ context.Context, login *bridgev2.Us w.Client.AddEventHandler(w.handleWAEvent) if bridgev2.PortalEventBuffer == 0 { w.Client.SynchronousAck = true + w.Client.EnableDecryptedEventBuffer = true } w.Client.AutomaticMessageRerequestFromPhone = true w.Client.GetMessageForRetry = w.trackNotFoundRetry @@ -324,7 +325,7 @@ func (wa *WhatsAppClient) Disconnect() { func (wa *WhatsAppClient) LogoutRemote(ctx context.Context) { if cli := wa.Client; cli != nil { - err := cli.Logout() + err := cli.Logout(ctx) if err != nil { zerolog.Ctx(ctx).Err(err).Msg("Failed to log out") } diff --git a/pkg/connector/commands.go b/pkg/connector/commands.go index 4c62db0b..8e2ddf01 100644 --- a/pkg/connector/commands.go +++ b/pkg/connector/commands.go @@ -142,7 +142,7 @@ func fnSync(ce *commands.Event) { ce.React("✅") case "appstate": for _, name := range appstate.AllPatchNames { - err := wa.Client.FetchAppState(name, true, false) + err := wa.Client.FetchAppState(ce.Ctx, name, true, false) if errors.Is(err, appstate.ErrKeyNotFound) { ce.Reply("Key not found error syncing app state %s: %v\n\nKey requests are sent automatically, and the sync should happen in the background after your phone responds.", name, err) return diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index d052ed2b..ca8cd5b7 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -127,7 +127,7 @@ func (wa *WhatsAppConnector) Init(bridge *bridgev2.Bridge) { } func (wa *WhatsAppConnector) Start(ctx context.Context) error { - err := wa.DeviceStore.Upgrade() + err := wa.DeviceStore.Upgrade(ctx) if err != nil { return bridgev2.DBUpgradeError{Err: err, Section: "whatsmeow"} } @@ -152,7 +152,7 @@ func (wa *WhatsAppConnector) Stop() { } func (wa *WhatsAppConnector) onFirstClientConnect() { - ver, err := whatsmeow.GetLatestVersion(nil) + ver, err := whatsmeow.GetLatestVersion(wa.Bridge.BackgroundCtx, nil) if err != nil { wa.Bridge.Log.Err(err).Msg("Failed to get latest WhatsApp web version number") } else { diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index 64bb5e45..df234ff8 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -99,7 +99,7 @@ func (wa *WhatsAppConnector) Download(ctx context.Context, mediaID networkid.Med } return &mediaproxy.GetMediaResponseFile{ Callback: func(f *os.File) error { - err := waClient.Client.DownloadToFile(keys, f) + err := waClient.Client.DownloadToFile(ctx, keys, f) if errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith403) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith404) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith410) { val := params["fi.mau.whatsapp.reload_media"] if val == "false" || (!wa.Config.DirectMediaAutoRequest && val != "true") { @@ -112,7 +112,7 @@ func (wa *WhatsAppConnector) Download(ctx context.Context, mediaID networkid.Med return err } log.Trace().Msg("Retrying download after successful retry") - err = waClient.Client.DownloadToFile(keys, f) + err = waClient.Client.DownloadToFile(ctx, keys, f) } 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") diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index 8dfa5c93..a90b6771 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -156,7 +156,7 @@ func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev } var req whatsmeow.SendRequestExtra if msg.Portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup { - reactionMsg.EncReactionMessage, err = wa.Client.EncryptReaction(msgconv.MessageIDToInfo(wa.Client, messageID), reactionMsg.ReactionMessage) + reactionMsg.EncReactionMessage, err = wa.Client.EncryptReaction(ctx, msgconv.MessageIDToInfo(wa.Client, messageID), reactionMsg.ReactionMessage) if err != nil { return nil, fmt.Errorf("failed to encrypt reaction: %w", err) } diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 0a83902d..196905ed 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -73,10 +73,11 @@ func init() { func (wa *WhatsAppClient) handleWAEvent(rawEvt any) { log := wa.UserLogin.Log + ctx := log.WithContext(wa.Main.Bridge.BackgroundCtx) switch evt := rawEvt.(type) { case *events.Message: - wa.handleWAMessage(evt) + wa.handleWAMessage(ctx, evt) case *events.Receipt: wa.handleWAReceipt(evt) case *events.ChatPresence: @@ -143,7 +144,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) { if err != nil { log.Warn().Err(err).Msg("Failed to send presence after push name update") } - _, _, err = wa.GetStore().Contacts.PutPushName(wa.JID.ToNonAD(), evt.Action.GetName()) + _, _, err = wa.GetStore().Contacts.PutPushName(ctx, wa.JID.ToNonAD(), evt.Action.GetName()) if err != nil { log.Err(err).Msg("Failed to update push name in store") } @@ -254,7 +255,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) { } } -func (wa *WhatsAppClient) handleWAMessage(evt *events.Message) { +func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Message) { wa.UserLogin.Log.Trace(). Any("info", evt.Info). Any("payload", evt.Message). @@ -267,7 +268,7 @@ func (wa *WhatsAppClient) handleWAMessage(evt *events.Message) { return } if encReact := evt.Message.GetEncReactionMessage(); encReact != nil { - decrypted, err := wa.Client.DecryptReaction(evt) + 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 @@ -276,7 +277,7 @@ func (wa *WhatsAppClient) handleWAMessage(evt *events.Message) { evt.Message.ReactionMessage = decrypted } if encComment := evt.Message.GetEncCommentMessage(); encComment != nil { - decrypted, err := wa.Client.DecryptComment(evt) + 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 { diff --git a/pkg/connector/login.go b/pkg/connector/login.go index cc04b6f7..7a232d9e 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -162,7 +162,7 @@ func (wl *WALogin) SubmitUserInput(ctx context.Context, input map[string]string) 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(input["phone_number"], true, whatsmeow.PairClientChrome, "Chrome (Linux)") + pairingCode, err := wl.Client.PairPhone(ctx, input["phone_number"], true, whatsmeow.PairClientChrome, "Chrome (Linux)") if err != nil { wl.Log.Err(err).Msg("Failed to request phone code login") return nil, err diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index 4f4ce645..e2631bc7 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -136,7 +136,7 @@ func (wa *WhatsAppClient) getContactList(ctx context.Context, filter string) ([] if !wa.IsLoggedIn() { return nil, mautrix.MForbidden.WithMessage("You must be logged in to list contacts") } - contacts, err := wa.GetStore().Contacts.GetAllContacts() + contacts, err := wa.GetStore().Contacts.GetAllContacts(ctx) if err != nil { return nil, err } diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index f4e0d337..c0520b79 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -169,7 +169,7 @@ func (wa *WhatsAppClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost } func (wa *WhatsAppClient) getUserInfo(ctx context.Context, jid types.JID, fetchAvatar bool) (*bridgev2.UserInfo, error) { - contact, err := wa.GetStore().Contacts.GetContact(jid) + contact, err := wa.GetStore().Contacts.GetContact(ctx, jid) if err != nil { return nil, err } @@ -191,7 +191,7 @@ func (wa *WhatsAppClient) contactToUserInfo(ctx context.Context, jid types.JID, zerolog.Ctx(ctx).Err(err).Stringer("lid", jid).Msg("Failed to get PN for LID") } else { phone = "+" + pnJID.User - extraContact, err := wa.GetStore().Contacts.GetContact(pnJID) + extraContact, err := wa.GetStore().Contacts.GetContact(ctx, pnJID) if err != nil { zerolog.Ctx(ctx).Err(err). Stringer("lid", jid). @@ -262,7 +262,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(avatar.DirectPath, nil, nil, nil, 0, "", "") + return wa.Client.DownloadMediaWithPath(ctx, avatar.DirectPath, nil, nil, nil, 0, "", "") }, } } @@ -272,7 +272,7 @@ func (wa *WhatsAppClient) fetchGhostAvatar(ctx context.Context, ghost *bridgev2. func (wa *WhatsAppClient) resyncContacts(forceAvatarSync bool) { log := wa.UserLogin.Log.With().Str("action", "resync contacts").Logger() ctx := log.WithContext(context.Background()) - contacts, err := wa.GetStore().Contacts.GetAllContacts() + contacts, err := wa.GetStore().Contacts.GetAllContacts(ctx) if err != nil { log.Err(err).Msg("Failed to get cached contacts") return diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 9b9b529b..3e82b06c 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -122,7 +122,7 @@ func (mc *MessageConverter) ToWhatsApp( return nil, nil, fmt.Errorf("failed to parse message ID: %w", err) } rootMsgInfo := MessageIDToInfo(client, parsedID) - message, err = client.EncryptComment(rootMsgInfo, message) + 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 b4b20b1c..34040abd 100644 --- a/pkg/msgconv/matrixpoll.go +++ b/pkg/msgconv/matrixpoll.go @@ -140,7 +140,7 @@ func (mc *MessageConverter) PollVoteToWhatsApp( } } } - pollUpdate, err := client.EncryptPollVote(pollMsgInfo, &waE2E.PollVoteMessage{ + pollUpdate, err := client.EncryptPollVote(ctx, pollMsgInfo, &waE2E.PollVoteMessage{ SelectedOptions: optionHashes, }) return &waE2E.Message{PollUpdateMessage: pollUpdate}, err diff --git a/pkg/msgconv/urlpreview.go b/pkg/msgconv/urlpreview.go index 62ac3122..cb241c7d 100644 --- a/pkg/msgconv/urlpreview.go +++ b/pkg/msgconv/urlpreview.go @@ -51,7 +51,7 @@ func (mc *MessageConverter) convertURLPreviewToBeeper(ctx context.Context, msg * var thumbnailData []byte if msg.ThumbnailDirectPath != nil { var err error - thumbnailData, err = getClient(ctx).DownloadThumbnail(msg) + thumbnailData, err = getClient(ctx).DownloadThumbnail(ctx, msg) if err != nil { zerolog.Ctx(ctx).Err(err).Msg("Failed to download thumbnail for link preview") } diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index dea7f545..d5f3be09 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -318,7 +318,7 @@ func (mc *MessageConverter) reuploadWhatsAppAttachment( 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) { - err := client.DownloadToFile(message, file.(*os.File)) + 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") } else if err != nil { @@ -339,7 +339,7 @@ func (mc *MessageConverter) reuploadWhatsAppAttachment( return err } } else { - data, err := client.Download(message) + data, err := client.Download(ctx, message) 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") } else if err != nil { diff --git a/pkg/msgconv/wa-poll.go b/pkg/msgconv/wa-poll.go index 10876847..97a75e2e 100644 --- a/pkg/msgconv/wa-poll.go +++ b/pkg/msgconv/wa-poll.go @@ -144,7 +144,7 @@ func (mc *MessageConverter) convertPollUpdateMessage(ctx context.Context, info * log.Err(err).Msg("Failed to get poll update target message") return failedPollUpdatePart, nil } - vote, err := getClient(ctx).DecryptPollVote(&events.Message{ + vote, err := getClient(ctx).DecryptPollVote(ctx, &events.Message{ Info: *info, Message: &waE2E.Message{PollUpdateMessage: msg}, }) From 81f5f3b5222139bcdf11acf38e8b15daa056bd94 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 14 May 2025 15:07:50 +0300 Subject: [PATCH 041/486] dependencies: update --- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 62d2d7f9..94a3145f 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,10 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.8.7-0.20250427215252-d2d18a7e463c + go.mau.fi/util v0.8.7-0.20250514115735-05fea4054bbd go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250514120219-ef9c1e91094e - golang.org/x/image v0.26.0 + go.mau.fi/whatsmeow v0.0.0-20250514120708-22ca98ea604a + golang.org/x/image v0.27.0 golang.org/x/net v0.40.0 golang.org/x/sync v0.14.0 google.golang.org/protobuf v1.36.6 @@ -27,8 +27,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.27 // indirect - github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a // indirect + github.com/mattn/go-sqlite3 v1.14.28 // indirect + github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb // 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,7 +40,7 @@ require ( go.mau.fi/libsignal v0.2.0 // indirect go.mau.fi/zeroconfig v0.1.3 // indirect golang.org/x/crypto v0.38.0 // indirect - golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect + golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index 4317b489..9c1ecc7a 100644 --- a/go.sum +++ b/go.sum @@ -32,10 +32,10 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ 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.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU= -github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a h1:S+AGcmAESQ0pXCUNnRH7V+bOUIgkSX5qVt2cNKCrm0Q= -github.com/petermattis/goid v0.0.0-20250319124200-ccd6737f222a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= +github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb h1:3PrKuO92dUTMrQ9dx0YNejC6U/Si6jqKmyQ9vWjwqR4= +github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb/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= @@ -65,20 +65,20 @@ github.com/yuin/goldmark v1.7.10 h1:S+LrtBjRmqMac2UdtB6yyCEJm+UILZ2fefI4p7o0QpI= github.com/yuin/goldmark v1.7.10/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.0 h1:oRXj3OHhEJq51BFEM8/50UZblmWiTYH93hsNTPcbk90= go.mau.fi/libsignal v0.2.0/go.mod h1:tvjoDsMejgT38CXTXwqaYu8itBiY8O2Mb6biWvZBb9k= -go.mau.fi/util v0.8.7-0.20250427215252-d2d18a7e463c h1:qfJyMZq1pPyuXKoVWwHs6OmR9CzO3pHFRPYT/QpaaaA= -go.mau.fi/util v0.8.7-0.20250427215252-d2d18a7e463c/go.mod h1:uNB3UTXFbkpp7xL1M/WvQks90B/L4gvbLpbS0603KOE= +go.mau.fi/util v0.8.7-0.20250514115735-05fea4054bbd h1:+0zVSU9hJygQo9NDU/m8ObP6Kgl8buhBu7wiUHuzK6s= +go.mau.fi/util v0.8.7-0.20250514115735-05fea4054bbd/go.mod h1:Fg6GdUM0iMTlaRK4/TGg/3dSGmXBatbhtjpMwK2waVk= 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-20250514120219-ef9c1e91094e h1:7lboFyrizLaof8CBFIlqk1B50DVeVAq0vlMkBQVGE7A= -go.mau.fi/whatsmeow v0.0.0-20250514120219-ef9c1e91094e/go.mod h1:0b/hOeTpA38V89YTtE2yqMKfDfaffj8gMu+AswqprQI= +go.mau.fi/whatsmeow v0.0.0-20250514120708-22ca98ea604a h1:k9ggM9UTxHPRX8nUDMHdwqGFtNF/9RGkGUM1EYnKPMY= +go.mau.fi/whatsmeow v0.0.0-20250514120708-22ca98ea604a/go.mod h1:0b/hOeTpA38V89YTtE2yqMKfDfaffj8gMu+AswqprQI= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= -golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= -golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= -golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY= -golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= +golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= +golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= From 343aab50adc88d6f95a4f34a125374688e584873 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 15 May 2025 13:55:10 +0300 Subject: [PATCH 042/486] 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 94a3145f..10d3bccc 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.7-0.20250514115735-05fea4054bbd go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250514120708-22ca98ea604a + go.mau.fi/whatsmeow v0.0.0-20250515105332-8c870897140e golang.org/x/image v0.27.0 golang.org/x/net v0.40.0 golang.org/x/sync v0.14.0 diff --git a/go.sum b/go.sum index 9c1ecc7a..99b5e1aa 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.7-0.20250514115735-05fea4054bbd h1:+0zVSU9hJygQo9NDU/m8ObP6K go.mau.fi/util v0.8.7-0.20250514115735-05fea4054bbd/go.mod h1:Fg6GdUM0iMTlaRK4/TGg/3dSGmXBatbhtjpMwK2waVk= 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-20250514120708-22ca98ea604a h1:k9ggM9UTxHPRX8nUDMHdwqGFtNF/9RGkGUM1EYnKPMY= -go.mau.fi/whatsmeow v0.0.0-20250514120708-22ca98ea604a/go.mod h1:0b/hOeTpA38V89YTtE2yqMKfDfaffj8gMu+AswqprQI= +go.mau.fi/whatsmeow v0.0.0-20250515105332-8c870897140e h1:j0voXCIzgx7lwVdrIiw1nBufnzCb08pXb1aN1qVerXQ= +go.mau.fi/whatsmeow v0.0.0-20250515105332-8c870897140e/go.mod h1:0b/hOeTpA38V89YTtE2yqMKfDfaffj8gMu+AswqprQI= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= From 98a695f15c65dd13e5a007714695ffb0edd7ea9e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 15 May 2025 15:25:44 +0300 Subject: [PATCH 043/486] login: don't require entering phone number for relogin --- pkg/connector/login.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pkg/connector/login.go b/pkg/connector/login.go index 7a232d9e..d0c17180 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -104,6 +104,7 @@ type WALogin struct { var ( _ bridgev2.LoginProcessDisplayAndWait = (*WALogin)(nil) _ bridgev2.LoginProcessUserInput = (*WALogin)(nil) + _ bridgev2.LoginProcessWithOverride = (*WALogin)(nil) ) const LoginConnectWait = 15 * time.Second @@ -149,6 +150,20 @@ func (wl *WALogin) Start(ctx context.Context) (*bridgev2.LoginStep, error) { return makeQRStep(wl.QRs[0]), nil } +func (wl *WALogin) StartWithOverride(ctx context.Context, old *bridgev2.UserLogin) (*bridgev2.LoginStep, error) { + step, err := wl.Start(ctx) + if err == nil && step != nil && old != nil && step.StepID == LoginStepIDPhoneNumber { + phoneNumber := fmt.Sprintf("+%s", old.ID) + wl.Log.Debug(). + Str("phone_number", phoneNumber). + Msg("Auto-submitting phone number for relogin") + return wl.SubmitUserInput(ctx, map[string]string{ + "phone_number": phoneNumber, + }) + } + return step, err +} + func (wl *WALogin) SubmitUserInput(ctx context.Context, input map[string]string) (*bridgev2.LoginStep, error) { ctx, cancel := context.WithTimeout(ctx, LoginConnectWait) defer cancel() From 906fa435808958843a4e75afb96dceecd6626697 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 16 May 2025 08:16:44 +0300 Subject: [PATCH 044/486] Bump version to v0.12.1 --- CHANGELOG.md | 11 +++++++++++ cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 6 +++--- go.sum | 12 ++++++------ 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b7f100e..f2f2485d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# v0.12.1 (2025-05-16) + +* Added prefix to identify forwarded messages on WhatsApp. +* Updated mime type of unconverted animated stickers to `video/lottie+json` + which is now registered with IANA. +* Changed relogin command to not require entering phone number twice when using + phone code login. +* Fixed outgoing messages being rejected if they replied to a fake message + generated by the bridge. +* Fixed backfilling messages in existing portals after relogining. + # v0.12.0 (2025-04-16) * Migrated Signal session store to use new `@lid` identifiers to support future diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 4867132f..78df77b8 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -23,7 +23,7 @@ var m = mxmain.BridgeMain{ Name: "mautrix-whatsapp", URL: "https://github.com/mautrix/whatsapp", Description: "A Matrix-WhatsApp puppeting bridge.", - Version: "0.12.0", + Version: "0.12.1", Connector: c, } diff --git a/go.mod b/go.mod index 10d3bccc..a0765d62 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.8.7-0.20250514115735-05fea4054bbd + go.mau.fi/util v0.8.7 go.mau.fi/webp v0.2.0 go.mau.fi/whatsmeow v0.0.0-20250515105332-8c870897140e golang.org/x/image v0.27.0 @@ -17,7 +17,7 @@ require ( golang.org/x/sync v0.14.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.23.4-0.20250509121614-a0191c8f5847 + maunium.net/go/mautrix v0.24.0 ) require ( @@ -36,7 +36,7 @@ require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect - github.com/yuin/goldmark v1.7.10 // indirect + github.com/yuin/goldmark v1.7.11 // indirect go.mau.fi/libsignal v0.2.0 // indirect go.mau.fi/zeroconfig v0.1.3 // indirect golang.org/x/crypto v0.38.0 // indirect diff --git a/go.sum b/go.sum index 99b5e1aa..499b996a 100644 --- a/go.sum +++ b/go.sum @@ -61,12 +61,12 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 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/yuin/goldmark v1.7.10 h1:S+LrtBjRmqMac2UdtB6yyCEJm+UILZ2fefI4p7o0QpI= -github.com/yuin/goldmark v1.7.10/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark v1.7.11 h1:ZCxLyDMtz0nT2HFfsYG8WZ47Trip2+JyLysKcMYE5bo= +github.com/yuin/goldmark v1.7.11/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.0 h1:oRXj3OHhEJq51BFEM8/50UZblmWiTYH93hsNTPcbk90= go.mau.fi/libsignal v0.2.0/go.mod h1:tvjoDsMejgT38CXTXwqaYu8itBiY8O2Mb6biWvZBb9k= -go.mau.fi/util v0.8.7-0.20250514115735-05fea4054bbd h1:+0zVSU9hJygQo9NDU/m8ObP6Kgl8buhBu7wiUHuzK6s= -go.mau.fi/util v0.8.7-0.20250514115735-05fea4054bbd/go.mod h1:Fg6GdUM0iMTlaRK4/TGg/3dSGmXBatbhtjpMwK2waVk= +go.mau.fi/util v0.8.7 h1:ywKarPxouJQEEijTs4mPlxC7F4AWEKokEpWc+2TYy6c= +go.mau.fi/util v0.8.7/go.mod h1:j6R3cENakc1f8HpQeFl0N15UiSTcNmIfDBNJUbL71RY= 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-20250515105332-8c870897140e h1:j0voXCIzgx7lwVdrIiw1nBufnzCb08pXb1aN1qVerXQ= @@ -101,5 +101,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.23.4-0.20250509121614-a0191c8f5847 h1:umv6WzuHjAVLVJlNeGEUogZtcD+3I6/OIpFnThUBb+A= -maunium.net/go/mautrix v0.23.4-0.20250509121614-a0191c8f5847/go.mod h1:pT4G5RZQ+nLfKzsmeDa4NhHghOVTrasLLwY9tZ2mO08= +maunium.net/go/mautrix v0.24.0 h1:kBeyWhgL1W8/d8BEFlBSlgIpItPgP1l37hzF8cN3R70= +maunium.net/go/mautrix v0.24.0/go.mod h1:HqA1HUutQYJkrYRPkK64itARDz79PCec1oWVEB72HVQ= From c9f1e9cb77a01731b53459b3325a89c3cce32b3d Mon Sep 17 00:00:00 2001 From: Toni Spets Date: Wed, 21 May 2025 13:39:57 +0300 Subject: [PATCH 045/486] connector: Retry connect network errors in background --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/client.go | 1 + pkg/connector/config.go | 2 ++ 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a0765d62..7cdbaec5 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.7 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250515105332-8c870897140e + go.mau.fi/whatsmeow v0.0.0-20250521101145-106fc9aa085a golang.org/x/image v0.27.0 golang.org/x/net v0.40.0 golang.org/x/sync v0.14.0 diff --git a/go.sum b/go.sum index 499b996a..992f5caa 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.7 h1:ywKarPxouJQEEijTs4mPlxC7F4AWEKokEpWc+2TYy6c= go.mau.fi/util v0.8.7/go.mod h1:j6R3cENakc1f8HpQeFl0N15UiSTcNmIfDBNJUbL71RY= 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-20250515105332-8c870897140e h1:j0voXCIzgx7lwVdrIiw1nBufnzCb08pXb1aN1qVerXQ= -go.mau.fi/whatsmeow v0.0.0-20250515105332-8c870897140e/go.mod h1:0b/hOeTpA38V89YTtE2yqMKfDfaffj8gMu+AswqprQI= +go.mau.fi/whatsmeow v0.0.0-20250521101145-106fc9aa085a h1:MeK4HVUXWZrDqyYzb4SfgJZHKb2l2OZ7NdkY0X71mGI= +go.mau.fi/whatsmeow v0.0.0-20250521101145-106fc9aa085a/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= diff --git a/pkg/connector/client.go b/pkg/connector/client.go index b6eb3768..9ba42b42 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -77,6 +77,7 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. w.Client.GetMessageForRetry = w.trackNotFoundRetry w.Client.PreRetryCallback = w.trackFoundRetry w.Client.SetForceActiveDeliveryReceipts(wa.Config.ForceActiveDeliveryReceipts) + w.Client.InitialAutoReconnect = wa.Config.InitialAutoReconnect } else { w.UserLogin.Log.Warn().Stringer("jid", w.JID).Msg("No device found for user in whatsmeow store") } diff --git a/pkg/connector/config.go b/pkg/connector/config.go index daffe99d..e5a92096 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -48,6 +48,7 @@ type Config struct { DisableViewOnce bool `yaml:"disable_view_once"` ForceActiveDeliveryReceipts bool `yaml:"force_active_delivery_receipts"` DirectMediaAutoRequest bool `yaml:"direct_media_auto_request"` + InitialAutoReconnect bool `yaml:"initial_auto_reconnect"` AnimatedSticker msgconv.AnimatedStickerConfig `yaml:"animated_sticker"` @@ -112,6 +113,7 @@ func upgradeConfig(helper up.Helper) { helper.Copy(up.Bool, "disable_view_once") helper.Copy(up.Bool, "force_active_delivery_receipts") helper.Copy(up.Bool, "direct_media_auto_request") + helper.Copy(up.Bool, "initial_auto_reconnect") helper.Copy(up.Str, "animated_sticker", "target") helper.Copy(up.Int, "animated_sticker", "args", "width") From 513d5ecaf0a37d12cf655b4f4803def4b965bb6b Mon Sep 17 00:00:00 2001 From: Conan Date: Mon, 26 May 2025 23:51:46 +0800 Subject: [PATCH 046/486] directmedia: extract lottie zip (#793) --- pkg/connector/directmedia.go | 16 +++++++++++ pkg/msgconv/wa-media.go | 52 +++++++++++++++++++++++------------- 2 files changed, 49 insertions(+), 19 deletions(-) diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index df234ff8..29c4462d 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -21,6 +21,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "net/http" "os" "sync" @@ -119,6 +120,21 @@ func (wa *WhatsAppConnector) Download(ctx context.Context, mediaID networkid.Med } else if err != nil { return err } + + if keys.MimeType == "application/was" { + if _, err := f.Seek(0, io.SeekStart); err != nil { + return fmt.Errorf("failed to seek to start of sticker zip: %w", err) + } else if zipData, err := io.ReadAll(f); err != nil { + return fmt.Errorf("failed to read sticker zip: %w", err) + } else if data, err := msgconv.ExtractAnimatedSticker(zipData); err != nil { + return fmt.Errorf("failed to extract animated sticker: %w %x", err, zipData) + } else if _, err := f.WriteAt(data, 0); err != nil { + return fmt.Errorf("failed to write animated sticker to file: %w", err) + } else if err := f.Truncate(int64(len(data))); err != nil { + return fmt.Errorf("failed to truncate animated sticker file: %w", err) + } + } + return nil }, // TODO? diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index d5f3be09..dc8b2274 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -80,9 +80,14 @@ func (mc *MessageConverter) convertMediaMessage( Type: whatsmeow.GetMediaType(msg), SHA256: msg.GetFileSHA256(), EncSHA256: msg.GetFileEncSHA256(), + MimeType: msg.GetMimetype(), } if mc.DirectMedia { preparedMedia.FillFileName() + if preparedMedia.Info.MimeType == "application/was" { + preparedMedia.Info.MimeType = "video/lottie+json" + preparedMedia.FileName = "sticker.json" + } var err error portal := getPortal(ctx) preparedMedia.URL, err = portal.Bridge.Matrix.GenerateContentURI(ctx, waid.MakeMediaID(messageInfo, portal.Receiver)) @@ -123,6 +128,7 @@ type FailedMediaKeys struct { SHA256 []byte `json:"sha256"` EncSHA256 []byte `json:"enc_sha256"` DirectPath string `json:"direct_path,omitempty"` + MimeType string `json:"mime_type,omitempty"` } func (f *FailedMediaKeys) GetDirectPath() string { @@ -379,26 +385,9 @@ func (mc *MessageConverter) reuploadWhatsAppAttachment( } func (mc *MessageConverter) extractAnimatedSticker(fileInfo *PreparedMedia, data []byte) ([]byte, error) { - zipReader, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) + data, err := ExtractAnimatedSticker(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 nil, err } fileInfo.Info.MimeType = "video/lottie+json" fileInfo.FileName = "sticker.json" @@ -501,3 +490,28 @@ 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 +} From 9c42210a2beec9955b955a0037490d5e9e2adea9 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 27 May 2025 11:44:13 +0300 Subject: [PATCH 047/486] dependencies: update mautrix-go --- go.mod | 2 +- go.sum | 4 ++-- pkg/msgconv/urlpreview.go | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 7cdbaec5..00c8a0ca 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( golang.org/x/sync v0.14.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.0 + maunium.net/go/mautrix v0.24.1-0.20250527083757-8a745c0d03ec ) require ( diff --git a/go.sum b/go.sum index 992f5caa..306f2234 100644 --- a/go.sum +++ b/go.sum @@ -101,5 +101,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.24.0 h1:kBeyWhgL1W8/d8BEFlBSlgIpItPgP1l37hzF8cN3R70= -maunium.net/go/mautrix v0.24.0/go.mod h1:HqA1HUutQYJkrYRPkK64itARDz79PCec1oWVEB72HVQ= +maunium.net/go/mautrix v0.24.1-0.20250527083757-8a745c0d03ec h1:kn/SHMTE4FiafMFS9WKXzHQc/Z9yN09Aa7TiFOgYfSY= +maunium.net/go/mautrix v0.24.1-0.20250527083757-8a745c0d03ec/go.mod h1:HqA1HUutQYJkrYRPkK64itARDz79PCec1oWVEB72HVQ= diff --git a/pkg/msgconv/urlpreview.go b/pkg/msgconv/urlpreview.go index cb241c7d..4bce93bc 100644 --- a/pkg/msgconv/urlpreview.go +++ b/pkg/msgconv/urlpreview.go @@ -60,16 +60,16 @@ func (mc *MessageConverter) convertURLPreviewToBeeper(ctx context.Context, msg * thumbnailData = msg.JPEGThumbnail } if thumbnailData != nil { - output.ImageHeight = int(msg.GetThumbnailHeight()) - output.ImageWidth = int(msg.GetThumbnailWidth()) + output.ImageHeight = event.IntOrString(msg.GetThumbnailHeight()) + output.ImageWidth = event.IntOrString(msg.GetThumbnailWidth()) if output.ImageHeight == 0 || output.ImageWidth == 0 { src, _, err := image.Decode(bytes.NewReader(thumbnailData)) if err == nil { imageBounds := src.Bounds() - output.ImageWidth, output.ImageHeight = imageBounds.Max.X, imageBounds.Max.Y + output.ImageWidth, output.ImageHeight = event.IntOrString(imageBounds.Max.X), event.IntOrString(imageBounds.Max.Y) } } - output.ImageSize = len(thumbnailData) + output.ImageSize = event.IntOrString(len(thumbnailData)) output.ImageType = http.DetectContentType(thumbnailData) var err error output.ImageURL, output.ImageEncryption, err = getIntent(ctx).UploadMedia( From 24b61134ca796b70534bd65a8ff23be86fe10a61 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 29 May 2025 22:12:41 +0300 Subject: [PATCH 048/486] userinfo: fix setting identifiers of lid users --- pkg/connector/userinfo.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index c0520b79..9a6c1591 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -189,7 +189,7 @@ func (wa *WhatsAppClient) contactToUserInfo(ctx context.Context, jid types.JID, pnJID, err := wa.GetStore().LIDs.GetPNForLID(ctx, jid) if err != nil { zerolog.Ctx(ctx).Err(err).Stringer("lid", jid).Msg("Failed to get PN for LID") - } else { + } else if !pnJID.IsEmpty() { phone = "+" + pnJID.User extraContact, err := wa.GetStore().Contacts.GetContact(ctx, pnJID) if err != nil { @@ -216,11 +216,12 @@ func (wa *WhatsAppClient) contactToUserInfo(ctx context.Context, jid types.JID, ui := &bridgev2.UserInfo{ Name: ptr.Ptr(wa.Main.Config.FormatDisplayname(jid, phone, contact)), IsBot: ptr.Ptr(jid.IsBot()), - Identifiers: []string{fmt.Sprintf("tel:+%s", jid.User)}, ExtraUpdates: updateGhostLastSyncAt, } if jid.Server == types.BotServer { ui.Identifiers = []string{} + } else if phone != "" { + ui.Identifiers = []string{fmt.Sprintf("tel:%s", phone)} } if getAvatar { ui.ExtraUpdates = bridgev2.MergeExtraUpdaters(ui.ExtraUpdates, wa.fetchGhostAvatar) From 8ed1a47af41521db8044818ffd8bb7993ca8a27d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 30 May 2025 12:09:30 +0300 Subject: [PATCH 049/486] backfill: short circuits sleeps if context is canceled --- pkg/connector/backfill.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 56cb8474..c7c4bcfa 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -192,13 +192,15 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { Logger() ctx = log.WithContext(ctx) limit := wa.Main.Config.HistorySync.MaxInitialConversations - log.Info().Int("limit", limit).Msg("Creating portals from history sync") conversations, err := wa.Main.DB.Conversation.GetRecent(ctx, wa.UserLogin.ID, limit) if err != nil { log.Err(err).Msg("Failed to get recent conversations from database") return } - log.Info().Int("conversation_count", len(conversations)).Msg("Creating portals from history sync") + log.Info(). + Int("limit", limit). + Int("conversation_count", len(conversations)). + Msg("Creating portals from history sync") rateLimitErrors := 0 var wg sync.WaitGroup wg.Add(len(conversations)) @@ -220,7 +222,12 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { continue } // TODO can the chat info fetch be avoided entirely? - time.Sleep(time.Duration(rateLimitErrors) * time.Second) + select { + case <-time.After(time.Duration(rateLimitErrors) * time.Second): + case <-ctx.Done(): + log.Warn().Err(ctx.Err()).Msg("Context cancelled, stopping history sync portal creation") + return + } wrappedInfo, err := wa.getChatInfo(ctx, conv.ChatJID, conv) if errors.Is(err, whatsmeow.ErrNotInGroup) { log.Debug().Stringer("chat_jid", conv.ChatJID). @@ -241,7 +248,12 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { log.Err(err).Stringer("chat_jid", conv.ChatJID). Int("error_count", rateLimitErrors). Msg("Ratelimit error getting chat info, retrying after sleep") - time.Sleep(time.Duration(rateLimitErrors) * time.Minute) + select { + case <-time.After(time.Duration(rateLimitErrors) * time.Second): + case <-ctx.Done(): + log.Warn().Err(ctx.Err()).Msg("Context cancelled, stopping history sync portal creation") + return + } continue } else if err != nil { log.Err(err).Stringer("chat_jid", conv.ChatJID).Msg("Failed to get chat info") From 57d6ec721ee7eaec41e87bf6c4504e6e34d3b05b Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 30 May 2025 17:15:36 +0300 Subject: [PATCH 050/486] msgconv/from-whatsapp: fix mentions of Matrix users in lid groups --- pkg/connector/id.go | 2 +- pkg/msgconv/from-whatsapp.go | 30 ++++++++++++++++++++++++------ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/pkg/connector/id.go b/pkg/connector/id.go index 3b5d23d1..ea4e4c8d 100644 --- a/pkg/connector/id.go +++ b/pkg/connector/id.go @@ -33,7 +33,7 @@ func (wa *WhatsAppClient) makeEventSender(id types.JID) bridgev2.EventSender { return bridgev2.EventSender{ IsFromMe: id.User == wa.GetStore().GetJID().User || id.User == wa.GetStore().GetLID().User, Sender: waid.MakeUserID(id), - SenderLogin: waid.MakeUserLoginID(id), + SenderLogin: waid.MakeUserLoginID(id), // TODO add support for lids here by looking up pn jid } } diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 3bb824e7..7ee11ee3 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -61,14 +61,32 @@ func getPortal(ctx context.Context) *bridgev2.Portal { return ctx.Value(contextKeyPortal).(*bridgev2.Portal) } -func (mc *MessageConverter) getBasicUserInfo(ctx context.Context, user networkid.UserID) (id.UserID, string, error) { - ghost, err := mc.Bridge.GetGhostByID(ctx, user) +func (mc *MessageConverter) getBasicUserInfo(ctx context.Context, user types.JID) (id.UserID, string, error) { + ghost, err := mc.Bridge.GetGhostByID(ctx, waid.MakeUserID(user)) if err != nil { return "", "", fmt.Errorf("failed to get ghost by ID: %w", err) } - login := mc.Bridge.GetCachedUserLoginByID(networkid.UserLoginID(user)) - if login != nil { - return login.UserMXID, ghost.Name, nil + var pnJID types.JID + if user.Server == types.DefaultUserServer { + pnJID = user + } else if user.Server == types.HiddenUserServer { + cli := getClient(ctx) + if user.User == cli.Store.GetLID().User { + pnJID = cli.Store.GetJID() + } else { + pnJID, err = cli.Store.LIDs.GetPNForLID(ctx, user) + if err != nil { + zerolog.Ctx(ctx).Err(err). + Stringer("lid", user). + Msg("Failed to get PN for LID in mention bridging") + } + } + } + if !pnJID.IsEmpty() { + login := mc.Bridge.GetCachedUserLoginByID(waid.MakeUserLoginID(pnJID)) + if login != nil { + return login.UserMXID, ghost.Name, nil + } } return ghost.Intent.GetMXID(), ghost.Name, nil } @@ -84,7 +102,7 @@ func (mc *MessageConverter) addMentions(ctx context.Context, mentionedJID []stri zerolog.Ctx(ctx).Err(err).Str("jid", jid).Msg("Failed to parse mentioned JID") continue } - mxid, displayname, err := mc.getBasicUserInfo(ctx, waid.MakeUserID(parsed)) + mxid, displayname, err := mc.getBasicUserInfo(ctx, parsed) if err != nil { zerolog.Ctx(ctx).Err(err).Str("jid", jid).Msg("Failed to get user info") continue From 94f4678a99a7d31c8fb4f4aba65e3b8e294a213f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 3 Jun 2025 14:15:51 +0300 Subject: [PATCH 051/486] 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 00c8a0ca..4d91231f 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.7 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250521101145-106fc9aa085a + go.mau.fi/whatsmeow v0.0.0-20250603110639-ff268dee27a7 golang.org/x/image v0.27.0 golang.org/x/net v0.40.0 golang.org/x/sync v0.14.0 diff --git a/go.sum b/go.sum index 306f2234..d4601f76 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.7 h1:ywKarPxouJQEEijTs4mPlxC7F4AWEKokEpWc+2TYy6c= go.mau.fi/util v0.8.7/go.mod h1:j6R3cENakc1f8HpQeFl0N15UiSTcNmIfDBNJUbL71RY= 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-20250521101145-106fc9aa085a h1:MeK4HVUXWZrDqyYzb4SfgJZHKb2l2OZ7NdkY0X71mGI= -go.mau.fi/whatsmeow v0.0.0-20250521101145-106fc9aa085a/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= +go.mau.fi/whatsmeow v0.0.0-20250603110639-ff268dee27a7 h1:RxtSEtqm/Eib5BBB7/joyK9yqZUSTv0c0qBuz+gZXPM= +go.mau.fi/whatsmeow v0.0.0-20250603110639-ff268dee27a7/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= From 03b42f0d51036bc46f13c19e7aaa5ecf70102fc7 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Tue, 3 Jun 2025 10:07:28 +0100 Subject: [PATCH 052/486] userinfo: expose portal resync configuration settings Enables tweaking the sync interval/etc for non-cloud environments. --- pkg/connector/userinfo.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 9a6c1591..1fed10f4 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -20,11 +20,12 @@ import ( "go.mau.fi/mautrix-whatsapp/pkg/waid" ) -const resyncMinInterval = 7 * 24 * time.Hour -const resyncLoopInterval = 4 * time.Hour +var ResyncMinInterval = 7 * 24 * time.Hour +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()) { + if ghost.Metadata.(*waid.GhostMetadata).LastSync.Add(ResyncMinInterval).After(time.Now()) { return } wa.resyncQueueLock.Lock() @@ -41,7 +42,7 @@ func (wa *WhatsAppClient) EnqueueGhostResync(ghost *bridgev2.Ghost) { func (wa *WhatsAppClient) EnqueuePortalResync(portal *bridgev2.Portal) { jid, _ := waid.ParsePortalID(portal.ID) - if jid.Server != types.GroupServer || portal.Metadata.(*waid.PortalMetadata).LastSync.Add(resyncMinInterval).After(time.Now()) { + if jid.Server != types.GroupServer || portal.Metadata.(*waid.PortalMetadata).LastSync.Add(ResyncMinInterval).After(time.Now()) { return } wa.resyncQueueLock.Lock() @@ -58,7 +59,7 @@ func (wa *WhatsAppClient) EnqueuePortalResync(portal *bridgev2.Portal) { func (wa *WhatsAppClient) ghostResyncLoop(ctx context.Context) { log := wa.UserLogin.Log.With().Str("action", "ghost resync loop").Logger() ctx = log.WithContext(ctx) - wa.nextResync = time.Now().Add(resyncLoopInterval).Add(-time.Duration(rand.IntN(3600)) * time.Second) + wa.nextResync = time.Now().Add(ResyncLoopInterval).Add(-time.Duration(rand.IntN(ResyncJitterSeconds)) * time.Second) timer := time.NewTimer(time.Until(wa.nextResync)) log.Info().Time("first_resync", wa.nextResync).Msg("Ghost resync queue starting") for { @@ -81,7 +82,7 @@ func (wa *WhatsAppClient) ghostResyncLoop(ctx context.Context) { func (wa *WhatsAppClient) rotateResyncQueue() map[types.JID]resyncQueueItem { wa.resyncQueueLock.Lock() defer wa.resyncQueueLock.Unlock() - wa.nextResync = time.Now().Add(resyncLoopInterval) + wa.nextResync = time.Now().Add(ResyncLoopInterval) if len(wa.resyncQueue) == 0 { return nil } @@ -108,7 +109,7 @@ func (wa *WhatsAppClient) doGhostResync(ctx context.Context, queue map[types.JID } else if item.portal != nil { lastSync = item.portal.Metadata.(*waid.PortalMetadata).LastSync.Time } - if lastSync.Add(resyncMinInterval).After(time.Now()) { + if lastSync.Add(ResyncMinInterval).After(time.Now()) { log.Debug(). Stringer("jid", jid). Time("last_sync", lastSync). From cbcc302f8900e8921166c0c9eae31b8a1f0a8264 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Tue, 3 Jun 2025 10:08:10 +0100 Subject: [PATCH 053/486] client: immediately sync portal and DM ghost when viewed Implements the `bridgev2.ChatViewingNetworkAPI` API and triggers chat or ghost resync when a chat is viewed. --- pkg/connector/client.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 9ba42b42..642072b0 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -113,6 +113,7 @@ var ( _ bridgev2.NetworkAPI = (*WhatsAppClient)(nil) _ bridgev2.PushableNetworkAPI = (*WhatsAppClient)(nil) _ bridgev2.BackgroundSyncingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.ChatViewingNetworkAPI = (*WhatsAppClient)(nil) ) var pushCfg = &bridgev2.PushConfig{ @@ -373,3 +374,33 @@ func (wa *WhatsAppClient) syncRemoteProfile(ctx context.Context, ghost *bridgev2 } zerolog.Ctx(ctx).Info().Msg("Remote profile updated") } + +func (wa *WhatsAppClient) HandleMatrixViewingChat(ctx context.Context, msg *bridgev2.MatrixViewingChat) error { + if msg.Portal.Metadata.(*waid.PortalMetadata).LastSync.Add(5 * time.Minute).After(time.Now()) { + // If we resynced this portal within the last 5 minutes, don't do it again + return nil + } + + // Reset, but don't save, portal last sync time for immediate sync now + msg.Portal.Metadata.(*waid.PortalMetadata).LastSync.Time = time.Time{} + // Enqueue for the sync, don't block on it completing + wa.EnqueuePortalResync(msg.Portal) + + if msg.Portal.OtherUserID != "" { + // If this is a DM, also sync the ghost of the other user immediately + ghost, err := wa.Main.Bridge.GetExistingGhostByID(ctx, msg.Portal.OtherUserID) + if err != nil { + return fmt.Errorf("failed to get ghost for sync: %w", err) + } else if ghost == nil { + zerolog.Ctx(ctx).Warn(). + Str("other_user_id", string(msg.Portal.OtherUserID)). + Msg("No ghost found for other user in portal") + } else { + // Reset, but don't save, portal last sync time for immediate sync now + ghost.Metadata.(*waid.GhostMetadata).LastSync.Time = time.Time{} + wa.EnqueueGhostResync(ghost) + } + } + + return nil +} From 2fa26d5161e335b79db44fecaa5188e3da603baf Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 3 Jun 2025 16:54:30 +0300 Subject: [PATCH 054/486] legacymigrate: drop invalid disappearing message rows --- cmd/mautrix-whatsapp/legacymigrate.sql | 3 ++- cmd/mautrix-whatsapp/main.go | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/mautrix-whatsapp/legacymigrate.sql b/cmd/mautrix-whatsapp/legacymigrate.sql index 013519f5..e3b9c3e3 100644 --- a/cmd/mautrix-whatsapp/legacymigrate.sql +++ b/cmd/mautrix-whatsapp/legacymigrate.sql @@ -193,7 +193,8 @@ SELECT 'after_read', expire_in * 1000000, -- timer expire_at * 1000000 -- disappear_at -FROM disappearing_message_old; +FROM disappearing_message_old +WHERE room_id IN (SELECT mxid FROM portal WHERE mxid IS NOT NULL); INSERT INTO whatsapp_poll_option_id (bridge_id, msg_mxid, opt_id, opt_hash) SELECT '', msg_mxid, opt_id, opt_hash diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 78df77b8..a0542f2d 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -35,8 +35,8 @@ func main() { "v0.8.6", "v0.11.0", m.LegacyMigrateWithAnotherUpgrader( - legacyMigrateRenameTables, legacyMigrateCopyData, 17, - upgrades.Table, "whatsapp_version", 3, + legacyMigrateRenameTables, legacyMigrateCopyData, 21, + upgrades.Table, "whatsapp_version", 5, ), true, ) From c47fced2fdfd0da1d4b86c80fc1e4611d932b478 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 3 Jun 2025 22:23:00 +0530 Subject: [PATCH 055/486] handlewhatsapp: add queue for incoming history sync notifications (#798) --- go.mod | 2 +- go.sum | 4 +- pkg/connector/backfill.go | 86 ++++++++++++++++++- pkg/connector/client.go | 3 + pkg/connector/handlewhatsapp.go | 6 ++ pkg/connector/wadb/database.go | 5 ++ pkg/connector/wadb/hsnotif.go | 78 +++++++++++++++++ .../wadb/upgrades/00-latest-schema.sql | 17 +++- .../upgrades/05-history-sync-notification.sql | 15 ++++ 9 files changed, 208 insertions(+), 8 deletions(-) create mode 100644 pkg/connector/wadb/hsnotif.go create mode 100644 pkg/connector/wadb/upgrades/05-history-sync-notification.sql diff --git a/go.mod b/go.mod index 4d91231f..30fdc59f 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.7 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250603110639-ff268dee27a7 + go.mau.fi/whatsmeow v0.0.0-20250603144927-2b269a5d396a golang.org/x/image v0.27.0 golang.org/x/net v0.40.0 golang.org/x/sync v0.14.0 diff --git a/go.sum b/go.sum index d4601f76..24e135f5 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.7 h1:ywKarPxouJQEEijTs4mPlxC7F4AWEKokEpWc+2TYy6c= go.mau.fi/util v0.8.7/go.mod h1:j6R3cENakc1f8HpQeFl0N15UiSTcNmIfDBNJUbL71RY= 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-20250603110639-ff268dee27a7 h1:RxtSEtqm/Eib5BBB7/joyK9yqZUSTv0c0qBuz+gZXPM= -go.mau.fi/whatsmeow v0.0.0-20250603110639-ff268dee27a7/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= +go.mau.fi/whatsmeow v0.0.0-20250603144927-2b269a5d396a h1:LkppC9KJ5BQb+A7PQbGL+b8sKMpRO8QJcRnl6w93gy4= +go.mau.fi/whatsmeow v0.0.0-20250603144927-2b269a5d396a/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index c7c4bcfa..9a982eaa 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -38,12 +38,35 @@ func (wa *WhatsAppClient) historySyncLoop(ctx context.Context) { } else { dispatchTimer.Stop() } + if wa.Client.ManualHistorySyncDownload { + // Wake up the queue once to check if there are pending notifications + select { + case wa.historySyncWakeup <- struct{}{}: + default: + } + } wa.UserLogin.Log.Debug().Msg("Starting history sync loop") for { select { case evt := <-wa.historySyncs: dispatchTimer.Stop() - wa.handleWAHistorySync(ctx, evt) + _ = wa.handleWAHistorySync(ctx, evt, false) + dispatchTimer.Reset(historySyncDispatchWait) + case <-wa.historySyncWakeup: + dispatchTimer.Stop() + notif, rowid, err := wa.Main.DB.HSNotif.GetNext(ctx, wa.UserLogin.ID) + if err != nil { + wa.UserLogin.Log.Err(err).Msg("Failed to get next history sync notification") + } else if notif == nil { + wa.UserLogin.Log.Debug().Msg("No more queued history sync notifications") + } else { + wa.downloadAndSaveWAHistorySyncData(ctx, notif, rowid) + // Continue waking up the loop until all queued notifications are processed + select { + case wa.historySyncWakeup <- struct{}{}: + default: + } + } dispatchTimer.Reset(historySyncDispatchWait) case <-dispatchTimer.C: wa.createPortalsFromHistorySync(ctx) @@ -54,10 +77,56 @@ func (wa *WhatsAppClient) historySyncLoop(ctx context.Context) { } } -func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistorySync.HistorySync) { - if evt == nil || evt.SyncType == nil { +func (wa *WhatsAppClient) saveWAHistorySyncNotification(ctx context.Context, evt *waE2E.HistorySyncNotification) { + err := wa.Main.DB.HSNotif.Put(ctx, wa.UserLogin.ID, evt) + if err != nil { + wa.UserLogin.Log.Err(err).Msg("Failed to store history sync notification in queue") return } + wa.UserLogin.Log.Debug(). + Stringer("sync_type", evt.GetSyncType()). + Uint32("chunk_order", evt.GetChunkOrder()). + Uint32("progress", evt.GetProgress()). + Msg("Stored history sync notification in queue") + select { + case wa.historySyncWakeup <- struct{}{}: + default: + } +} + +func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context, evt *waE2E.HistorySyncNotification, rowid int) { + log := wa.UserLogin.Log.With(). + Str("action", "download history sync"). + Stringer("sync_type", evt.GetSyncType()). + Uint32("chunk_order", evt.GetChunkOrder()). + Uint32("progress", evt.GetProgress()). + Logger() + log.Debug().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 + } + err = wa.Main.DB.DoTxn(ctx, nil, func(ctx context.Context) error { + err := wa.handleWAHistorySync(ctx, blob, true) + if err != nil { + return err + } + err = wa.Main.DB.HSNotif.Delete(ctx, rowid) + if err != nil { + return fmt.Errorf("failed to delete queued history sync notification: %w", err) + } + return nil + }) + if err != nil { + log.Err(err).Msg("Failed to store history sync notification data") + } +} + +func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistorySync.HistorySync, stopOnError bool) error { + if evt == nil || evt.SyncType == nil { + return nil + } log := wa.UserLogin.Log.With(). Str("action", "store history sync"). Stringer("sync_type", evt.GetSyncType()). @@ -76,12 +145,13 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor Int("recent_sticker_count", len(evt.GetRecentStickers())). Int("past_participant_count", len(evt.GetPastParticipants())). Msg("Ignoring history sync") - return + return nil } log.Info(). Int("conversation_count", len(evt.GetConversations())). Int("past_participant_count", len(evt.GetPastParticipants())). Msg("Storing history sync") + start := time.Now() successfullySavedTotal := 0 failedToSaveTotal := 0 totalMessageCount := 0 @@ -167,11 +237,17 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor if len(messages) > 0 { err = wa.Main.DB.Conversation.Put(ctx, wadb.NewConversation(wa.UserLogin.ID, jid, conv, maxTime)) if err != nil { + if stopOnError { + return fmt.Errorf("failed to save conversation metadata for %s: %w", jid, err) + } log.Err(err).Msg("Failed to save conversation metadata") continue } err = wa.Main.DB.Message.Put(ctx, wa.UserLogin.ID, jid, messages) if err != nil { + if stopOnError { + return fmt.Errorf("failed to save messages in %s: %w", jid, err) + } log.Err(err).Msg("Failed to save messages") failedToSaveTotal += len(messages) } else { @@ -183,7 +259,9 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor Int("total_saved_count", successfullySavedTotal). Int("total_failed_count", failedToSaveTotal). Int("total_message_count", totalMessageCount). + Dur("duration", time.Since(start)). Msg("Finished storing history sync") + return nil } func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 642072b0..434fe342 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -47,6 +47,7 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. UserLogin: login, historySyncs: make(chan *waHistorySync.HistorySync, 64), + historySyncWakeup: make(chan struct{}, 1), resyncQueue: make(map[types.JID]resyncQueueItem), directMediaRetries: make(map[networkid.MessageID]*directMediaRetry), mediaRetryLock: semaphore.NewWeighted(wa.Config.HistorySync.MediaRequests.MaxAsyncHandle), @@ -72,6 +73,7 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. if bridgev2.PortalEventBuffer == 0 { w.Client.SynchronousAck = true w.Client.EnableDecryptedEventBuffer = true + w.Client.ManualHistorySyncDownload = true } w.Client.AutomaticMessageRerequestFromPhone = true w.Client.GetMessageForRetry = w.trackNotFoundRetry @@ -98,6 +100,7 @@ type WhatsAppClient struct { JID types.JID historySyncs chan *waHistorySync.HistorySync + historySyncWakeup chan struct{} stopLoops atomic.Pointer[context.CancelFunc] resyncQueue map[types.JID]resyncQueueItem resyncQueueLock sync.Mutex diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 196905ed..8df3bcc7 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -263,6 +263,12 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa 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 && + wa.Client.ManualHistorySyncDownload { + wa.saveWAHistorySyncNotification(ctx, evt.Message.ProtocolMessage.HistorySyncNotification) + } parsedMessageType := getMessageType(evt.Message) if parsedMessageType == "ignore" || strings.HasPrefix(parsedMessageType, "unknown_protocol_") { return diff --git a/pkg/connector/wadb/database.go b/pkg/connector/wadb/database.go index f6f26555..5090980c 100644 --- a/pkg/connector/wadb/database.go +++ b/pkg/connector/wadb/database.go @@ -14,6 +14,7 @@ type Database struct { Message *MessageQuery PollOption *PollOptionQuery MediaRequest *MediaRequestQuery + HSNotif *HistorySyncNotificationQuery } func New(bridgeID networkid.BridgeID, db *dbutil.Database, log zerolog.Logger) *Database { @@ -40,5 +41,9 @@ func New(bridgeID networkid.BridgeID, db *dbutil.Database, log zerolog.Logger) * return &MediaRequest{} }), }, + HSNotif: &HistorySyncNotificationQuery{ + BridgeID: bridgeID, + Database: db, + }, } } diff --git a/pkg/connector/wadb/hsnotif.go b/pkg/connector/wadb/hsnotif.go new file mode 100644 index 00000000..0221cddf --- /dev/null +++ b/pkg/connector/wadb/hsnotif.go @@ -0,0 +1,78 @@ +// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge. +// Copyright (C) 2025 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 wadb + +import ( + "context" + "database/sql" + "errors" + + "go.mau.fi/util/dbutil" + "google.golang.org/protobuf/proto" + "maunium.net/go/mautrix/bridgev2/networkid" + + "go.mau.fi/whatsmeow/proto/waE2E" +) + +type HistorySyncNotificationQuery struct { + BridgeID networkid.BridgeID + *dbutil.Database +} + +const ( + putHSNotificationQuery = ` + INSERT INTO whatsapp_history_sync_notification (bridge_id, user_login_id, data) + VALUES ($1, $2, $3) + ` + getNextHSNotificationQuery = ` + SELECT rowid, data FROM whatsapp_history_sync_notification + WHERE bridge_id=$1 AND user_login_id=$2 + ` + deleteHSNotificationQuery = ` + DELETE FROM whatsapp_history_sync_notification WHERE rowid=$1 + ` +) + +func (hsnq *HistorySyncNotificationQuery) Put(ctx context.Context, loginID networkid.UserLoginID, notif *waE2E.HistorySyncNotification) error { + notifBytes, err := proto.Marshal(notif) + if err != nil { + return err + } + _, err = hsnq.Exec(ctx, putHSNotificationQuery, hsnq.BridgeID, loginID, notifBytes) + return err +} + +func (hsnq *HistorySyncNotificationQuery) GetNext(ctx context.Context, loginID networkid.UserLoginID) (*waE2E.HistorySyncNotification, int, error) { + var notifBytes []byte + var rowid int + err := hsnq.QueryRow(ctx, getNextHSNotificationQuery, hsnq.BridgeID, loginID).Scan(&rowid, ¬ifBytes) + if errors.Is(err, sql.ErrNoRows) { + return nil, 0, nil + } else if err != nil { + return nil, 0, err + } + var notif waE2E.HistorySyncNotification + if err = proto.Unmarshal(notifBytes, ¬if); err != nil { + return nil, 0, err + } + return ¬if, rowid, nil +} + +func (hsnq *HistorySyncNotificationQuery) Delete(ctx context.Context, rowid int) error { + _, err := hsnq.Exec(ctx, deleteHSNotificationQuery, rowid) + return err +} diff --git a/pkg/connector/wadb/upgrades/00-latest-schema.sql b/pkg/connector/wadb/upgrades/00-latest-schema.sql index dbc41d12..f99bad42 100644 --- a/pkg/connector/wadb/upgrades/00-latest-schema.sql +++ b/pkg/connector/wadb/upgrades/00-latest-schema.sql @@ -1,4 +1,4 @@ --- v0 -> v4 (compatible with v3+): Latest revision +-- v0 -> v5 (compatible with v3+): Latest revision CREATE TABLE whatsapp_poll_option_id ( bridge_id TEXT NOT NULL, @@ -74,3 +74,18 @@ CREATE TABLE whatsapp_media_backfill_request ( ); CREATE INDEX whatsapp_media_backfill_request_portal_idx ON whatsapp_media_backfill_request (bridge_id, portal_id, portal_receiver); CREATE INDEX whatsapp_media_backfill_request_message_idx ON whatsapp_media_backfill_request (bridge_id, portal_receiver, message_id, _part_id); + +CREATE TABLE whatsapp_history_sync_notification ( + -- only: sqlite (line commented) +-- rowid INTEGER PRIMARY KEY, + -- only: postgres + rowid BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, + + bridge_id TEXT NOT NULL, + user_login_id TEXT NOT NULL, + data bytea NOT NULL, + + CONSTRAINT whatsapp_history_sync_notification_user_login_fkey FOREIGN KEY (bridge_id, user_login_id) + REFERENCES user_login (bridge_id, id) ON UPDATE CASCADE ON DELETE CASCADE +); +CREATE INDEX whatsapp_history_sync_notification_login_idx ON whatsapp_history_sync_notification (bridge_id, user_login_id); diff --git a/pkg/connector/wadb/upgrades/05-history-sync-notification.sql b/pkg/connector/wadb/upgrades/05-history-sync-notification.sql new file mode 100644 index 00000000..d444d430 --- /dev/null +++ b/pkg/connector/wadb/upgrades/05-history-sync-notification.sql @@ -0,0 +1,15 @@ +-- v5 (compatible with v3+): Add buffer for history sync notifications +CREATE TABLE whatsapp_history_sync_notification ( + -- only: sqlite (line commented) +-- rowid INTEGER PRIMARY KEY, + -- only: postgres + rowid BIGINT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, + + bridge_id TEXT NOT NULL, + user_login_id TEXT NOT NULL, + data bytea NOT NULL, + + CONSTRAINT whatsapp_history_sync_notification_user_login_fkey FOREIGN KEY (bridge_id, user_login_id) + REFERENCES user_login (bridge_id, id) ON UPDATE CASCADE ON DELETE CASCADE +); +CREATE INDEX whatsapp_history_sync_notification_login_idx ON whatsapp_history_sync_notification (bridge_id, user_login_id); From f5b835e042ee943aa232f306bca85dcc34a25066 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 4 Jun 2025 00:43:51 +0300 Subject: [PATCH 056/486] client: fix viewing chat handler --- pkg/connector/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 434fe342..300bbeca 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -379,7 +379,7 @@ func (wa *WhatsAppClient) syncRemoteProfile(ctx context.Context, ghost *bridgev2 } func (wa *WhatsAppClient) HandleMatrixViewingChat(ctx context.Context, msg *bridgev2.MatrixViewingChat) error { - if msg.Portal.Metadata.(*waid.PortalMetadata).LastSync.Add(5 * time.Minute).After(time.Now()) { + if msg.Portal == nil || msg.Portal.Metadata.(*waid.PortalMetadata).LastSync.Add(5*time.Minute).After(time.Now()) { // If we resynced this portal within the last 5 minutes, don't do it again return nil } From 30f0ac71fbf0af8a28c82506b2b50528abdeeda3 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 5 Jun 2025 14:29:51 +0300 Subject: [PATCH 057/486] dependencies: update whatsmeow to use bridge background context --- go.mod | 4 ++-- go.sum | 8 ++++---- pkg/connector/client.go | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 30fdc59f..8ea054c7 100644 --- a/go.mod +++ b/go.mod @@ -11,13 +11,13 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.7 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250603144927-2b269a5d396a + go.mau.fi/whatsmeow v0.0.0-20250605112842-73d070ff1c78 golang.org/x/image v0.27.0 golang.org/x/net v0.40.0 golang.org/x/sync v0.14.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.1-0.20250527083757-8a745c0d03ec + maunium.net/go/mautrix v0.24.1-0.20250605042548-d228995d718a ) require ( diff --git a/go.sum b/go.sum index 24e135f5..c444ef47 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.7 h1:ywKarPxouJQEEijTs4mPlxC7F4AWEKokEpWc+2TYy6c= go.mau.fi/util v0.8.7/go.mod h1:j6R3cENakc1f8HpQeFl0N15UiSTcNmIfDBNJUbL71RY= 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-20250603144927-2b269a5d396a h1:LkppC9KJ5BQb+A7PQbGL+b8sKMpRO8QJcRnl6w93gy4= -go.mau.fi/whatsmeow v0.0.0-20250603144927-2b269a5d396a/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= +go.mau.fi/whatsmeow v0.0.0-20250605112842-73d070ff1c78 h1:LGAZWk6ofDh6SrkVX/y/yJHGAv8ThHJTh0WFfHMZ1oI= +go.mau.fi/whatsmeow v0.0.0-20250605112842-73d070ff1c78/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= @@ -101,5 +101,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.24.1-0.20250527083757-8a745c0d03ec h1:kn/SHMTE4FiafMFS9WKXzHQc/Z9yN09Aa7TiFOgYfSY= -maunium.net/go/mautrix v0.24.1-0.20250527083757-8a745c0d03ec/go.mod h1:HqA1HUutQYJkrYRPkK64itARDz79PCec1oWVEB72HVQ= +maunium.net/go/mautrix v0.24.1-0.20250605042548-d228995d718a h1:XsaGeWjlp2WJVptRXase3EL9t5sYT2DC8Us9gfVqOXY= +maunium.net/go/mautrix v0.24.1-0.20250605042548-d228995d718a/go.mod h1:HqA1HUutQYJkrYRPkK64itARDz79PCec1oWVEB72HVQ= diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 300bbeca..3d3ba5ba 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -78,6 +78,7 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. w.Client.AutomaticMessageRerequestFromPhone = true w.Client.GetMessageForRetry = w.trackNotFoundRetry w.Client.PreRetryCallback = w.trackFoundRetry + w.Client.BackgroundEventCtx = wa.Bridge.BackgroundCtx w.Client.SetForceActiveDeliveryReceipts(wa.Config.ForceActiveDeliveryReceipts) w.Client.InitialAutoReconnect = wa.Config.InitialAutoReconnect } else { From 07b52859df45b309cde78dc80b1eb0ace0d5c292 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 5 Jun 2025 16:45:25 +0300 Subject: [PATCH 058/486] 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 8ea054c7..b52bca37 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.7 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250605112842-73d070ff1c78 + go.mau.fi/whatsmeow v0.0.0-20250605134436-66acc15b3df4 golang.org/x/image v0.27.0 golang.org/x/net v0.40.0 golang.org/x/sync v0.14.0 diff --git a/go.sum b/go.sum index c444ef47..dd65d5eb 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.7 h1:ywKarPxouJQEEijTs4mPlxC7F4AWEKokEpWc+2TYy6c= go.mau.fi/util v0.8.7/go.mod h1:j6R3cENakc1f8HpQeFl0N15UiSTcNmIfDBNJUbL71RY= 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-20250605112842-73d070ff1c78 h1:LGAZWk6ofDh6SrkVX/y/yJHGAv8ThHJTh0WFfHMZ1oI= -go.mau.fi/whatsmeow v0.0.0-20250605112842-73d070ff1c78/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= +go.mau.fi/whatsmeow v0.0.0-20250605134436-66acc15b3df4 h1:iFZQX++78gz6lGCOBHkkiMtpG1Ea7HLJZB53bF0UFKg= +go.mau.fi/whatsmeow v0.0.0-20250605134436-66acc15b3df4/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= From 65af858748aa103ed0cd7f6346f95b2c2d6f95aa Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 6 Jun 2025 12:47:56 +0300 Subject: [PATCH 059/486] dependencies: update whatsmeow --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/client.go | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b52bca37..9739ae0b 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.7 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250605134436-66acc15b3df4 + go.mau.fi/whatsmeow v0.0.0-20250606094702-6110de3471ab golang.org/x/image v0.27.0 golang.org/x/net v0.40.0 golang.org/x/sync v0.14.0 diff --git a/go.sum b/go.sum index dd65d5eb..535c8ef0 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.7 h1:ywKarPxouJQEEijTs4mPlxC7F4AWEKokEpWc+2TYy6c= go.mau.fi/util v0.8.7/go.mod h1:j6R3cENakc1f8HpQeFl0N15UiSTcNmIfDBNJUbL71RY= 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-20250605134436-66acc15b3df4 h1:iFZQX++78gz6lGCOBHkkiMtpG1Ea7HLJZB53bF0UFKg= -go.mau.fi/whatsmeow v0.0.0-20250605134436-66acc15b3df4/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= +go.mau.fi/whatsmeow v0.0.0-20250606094702-6110de3471ab h1:O4B+yX5a7vvp/PFONGRhiZqUTxTZ+F195j0zBdr8QBg= +go.mau.fi/whatsmeow v0.0.0-20250606094702-6110de3471ab/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 3d3ba5ba..161c2475 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -75,6 +75,7 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. w.Client.EnableDecryptedEventBuffer = true w.Client.ManualHistorySyncDownload = true } + w.Client.SendReportingTokens = true w.Client.AutomaticMessageRerequestFromPhone = true w.Client.GetMessageForRetry = w.trackNotFoundRetry w.Client.PreRetryCallback = w.trackFoundRetry From d528ea3e13373ed154c2a64b0149b336d4ddaa3a Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 6 Jun 2025 16:51:55 +0300 Subject: [PATCH 060/486] 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 9739ae0b..207dd648 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.7 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250606094702-6110de3471ab + go.mau.fi/whatsmeow v0.0.0-20250606135134-9359be831c50 golang.org/x/image v0.27.0 golang.org/x/net v0.40.0 golang.org/x/sync v0.14.0 diff --git a/go.sum b/go.sum index 535c8ef0..b8820bf7 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.7 h1:ywKarPxouJQEEijTs4mPlxC7F4AWEKokEpWc+2TYy6c= go.mau.fi/util v0.8.7/go.mod h1:j6R3cENakc1f8HpQeFl0N15UiSTcNmIfDBNJUbL71RY= 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-20250606094702-6110de3471ab h1:O4B+yX5a7vvp/PFONGRhiZqUTxTZ+F195j0zBdr8QBg= -go.mau.fi/whatsmeow v0.0.0-20250606094702-6110de3471ab/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= +go.mau.fi/whatsmeow v0.0.0-20250606135134-9359be831c50 h1:sd55J2a0u28g8znMzFbyVUTThXdPncjztO/BWg1QCoo= +go.mau.fi/whatsmeow v0.0.0-20250606135134-9359be831c50/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= From d6c6b09d166da2e0ce308c803dcf597234530575 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 6 Jun 2025 19:50:35 +0300 Subject: [PATCH 061/486] dependencies: update whatsmeow for event context cancel fix --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 207dd648..02bb856a 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.7 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250606135134-9359be831c50 + go.mau.fi/whatsmeow v0.0.0-20250606163700-6e87b20af9a9 golang.org/x/image v0.27.0 golang.org/x/net v0.40.0 golang.org/x/sync v0.14.0 diff --git a/go.sum b/go.sum index b8820bf7..33220bc4 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.7 h1:ywKarPxouJQEEijTs4mPlxC7F4AWEKokEpWc+2TYy6c= go.mau.fi/util v0.8.7/go.mod h1:j6R3cENakc1f8HpQeFl0N15UiSTcNmIfDBNJUbL71RY= 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-20250606135134-9359be831c50 h1:sd55J2a0u28g8znMzFbyVUTThXdPncjztO/BWg1QCoo= -go.mau.fi/whatsmeow v0.0.0-20250606135134-9359be831c50/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= +go.mau.fi/whatsmeow v0.0.0-20250606163700-6e87b20af9a9 h1:lYf5nA3VZouNbl3kG9iCnYEqCCbJcOQzzgo4K6eEcG4= +go.mau.fi/whatsmeow v0.0.0-20250606163700-6e87b20af9a9/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= From cf526768278e6af4c0c23cef5fbd2b17cb5ad3fe Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 6 Jun 2025 20:01:29 +0300 Subject: [PATCH 062/486] dependencies: update whatsmeow for more context fixes --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 02bb856a..5372e66b 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.7 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250606163700-6e87b20af9a9 + go.mau.fi/whatsmeow v0.0.0-20250606170101-3afe34f8ab8f golang.org/x/image v0.27.0 golang.org/x/net v0.40.0 golang.org/x/sync v0.14.0 diff --git a/go.sum b/go.sum index 33220bc4..bdd1c007 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.7 h1:ywKarPxouJQEEijTs4mPlxC7F4AWEKokEpWc+2TYy6c= go.mau.fi/util v0.8.7/go.mod h1:j6R3cENakc1f8HpQeFl0N15UiSTcNmIfDBNJUbL71RY= 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-20250606163700-6e87b20af9a9 h1:lYf5nA3VZouNbl3kG9iCnYEqCCbJcOQzzgo4K6eEcG4= -go.mau.fi/whatsmeow v0.0.0-20250606163700-6e87b20af9a9/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= +go.mau.fi/whatsmeow v0.0.0-20250606170101-3afe34f8ab8f h1:8csRM0kOS9nGgT162JFwi3FZ93NPM6fWVf/d5AfTceA= +go.mau.fi/whatsmeow v0.0.0-20250606170101-3afe34f8ab8f/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= From 7b01903ca1487aa870da57093456434fcbc43c87 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 9 Jun 2025 18:53:23 +0530 Subject: [PATCH 063/486] handlewhatsapp: sync member list on first lid message (#799) --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/chatinfo.go | 13 +++++++++++++ pkg/connector/events.go | 28 ++++++++++++++++++++++++++++ pkg/waid/dbmeta.go | 1 + 5 files changed, 45 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5372e66b..31d0808a 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( golang.org/x/sync v0.14.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.1-0.20250605042548-d228995d718a + maunium.net/go/mautrix v0.24.1-0.20250609115446-a154718b5d4c ) require ( diff --git a/go.sum b/go.sum index bdd1c007..226d3ee2 100644 --- a/go.sum +++ b/go.sum @@ -101,5 +101,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.24.1-0.20250605042548-d228995d718a h1:XsaGeWjlp2WJVptRXase3EL9t5sYT2DC8Us9gfVqOXY= -maunium.net/go/mautrix v0.24.1-0.20250605042548-d228995d718a/go.mod h1:HqA1HUutQYJkrYRPkK64itARDz79PCec1oWVEB72HVQ= +maunium.net/go/mautrix v0.24.1-0.20250609115446-a154718b5d4c h1:VLAz8J3UU6AS8OaPpiwrp1NvhgjJOWBbbQ8Y8oh7Cdg= +maunium.net/go/mautrix v0.24.1-0.20250609115446-a154718b5d4c/go.mod h1:HqA1HUutQYJkrYRPkK64itARDz79PCec1oWVEB72HVQ= diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index af75af34..4d06ccc9 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -272,7 +272,20 @@ func (wa *WhatsAppClient) wrapGroupInfo(info *types.GroupInfo) *bridgev2.ChatInf } else { member.PowerLevel = ptr.Ptr(defaultPL) } + member.MemberEventExtra = map[string]any{ + "com.beeper.exclude_from_timeline": true, + } wrapped.Members.MemberMap[waid.MakeUserID(pcp.JID)] = member + if pcp.JID.Server == types.HiddenUserServer && !pcp.PhoneNumber.IsEmpty() { + wrapped.Members.MemberMap[waid.MakeUserID(pcp.PhoneNumber)] = bridgev2.ChatMember{ + EventSender: wa.makeEventSender(pcp.PhoneNumber), + Membership: event.MembershipLeave, + PrevMembership: event.MembershipJoin, + MemberEventExtra: map[string]any{ + "com.beeper.exclude_from_timeline": true, + }, + } + } } if !info.LinkedParentJID.IsEmpty() { diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 7f5435bd..da3ec54c 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -124,6 +124,34 @@ func (evt *WAMessageEvent) AddLogContext(c zerolog.Context) zerolog.Context { return evt.MessageInfoWrapper.AddLogContext(c).Str("parsed_message_type", evt.parsedMessageType) } +func (evt *WAMessageEvent) PreHandle(ctx context.Context, portal *bridgev2.Portal) { + if evt.Info.Sender.Server != types.HiddenUserServer || evt.Info.Chat.Server != types.GroupServer { + return + } + portalJID, err := waid.ParsePortalID(portal.ID) + if err != nil { + return + } + meta := portal.Metadata.(*waid.PortalMetadata) + if meta.AddressingMode == types.AddressingModeLID || meta.LIDMigrationAttempted { + return + } + log := zerolog.Ctx(ctx) + meta.LIDMigrationAttempted = true + info, err := evt.wa.Client.GetGroupInfo(portalJID) + if err != nil { + log.Err(err).Msg("Failed to get group info for lid migration") + return + } + if info.AddressingMode != types.AddressingModeLID { + log.Warn().Msg("Received LID message, but group addressing mode isn't set to LID? Not migrating") + return + } + log.Info().Msg("Resyncing group members as it appears to have switched to LID addressing mode") + portal.UpdateInfo(ctx, evt.wa.wrapGroupInfo(info), evt.wa.UserLogin, nil, time.Time{}) + log.Debug().Msg("Finished resyncing after LID change") +} + func (evt *WAMessageEvent) PostHandle(ctx context.Context, portal *bridgev2.Portal) { if ph := evt.postHandle; ph != nil { evt.postHandle = nil diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index 65760d2c..4e0cd66c 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -109,6 +109,7 @@ type PortalMetadata struct { LastSync jsontime.Unix `json:"last_sync,omitempty"` CommunityAnnouncementGroup bool `json:"is_cag,omitempty"` AddressingMode types.AddressingMode `json:"addressing_mode,omitempty"` + LIDMigrationAttempted bool `json:"lid_migration_attempted,omitempty"` } type GhostMetadata struct { From 3dd3ed82d5f0e41154a824c5a44106d9abab1b8e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 9 Jun 2025 17:05:03 +0300 Subject: [PATCH 064/486] client: update background context on connect --- pkg/connector/client.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 161c2475..d75a7fd2 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -191,6 +191,7 @@ func (wa *WhatsAppClient) Connect(ctx context.Context) { zerolog.Ctx(ctx).Err(err).Msg("Failed to update proxy") } wa.startLoops() + wa.Client.BackgroundEventCtx = wa.Main.Bridge.BackgroundCtx if err := wa.Client.Connect(); err != nil { state := status.BridgeState{ StateEvent: status.StateUnknownError, @@ -223,6 +224,7 @@ func (wa *WhatsAppClient) ConnectBackground(ctx context.Context, params *bridgev if wa.Client == nil { return bridgev2.ErrNotLoggedIn } + wa.Client.BackgroundEventCtx = wa.Main.Bridge.BackgroundCtx wa.offlineSyncWaiter = make(chan error) wa.Main.firstClientConnectOnce.Do(wa.Main.onFirstClientConnect) if err := wa.Main.updateProxy(ctx, wa.Client, false); err != nil { From 7933a1ff2365b1052c79a022e8d653bf5d9fe08a Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 9 Jun 2025 19:48:47 +0300 Subject: [PATCH 065/486] handlewhatsapp: improve lid migration detection --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/events.go | 3 ++- pkg/connector/userinfo.go | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 31d0808a..99660113 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( golang.org/x/sync v0.14.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.1-0.20250609115446-a154718b5d4c + maunium.net/go/mautrix v0.24.1-0.20250609163037-72bacbb666fb ) require ( diff --git a/go.sum b/go.sum index 226d3ee2..dcb40a8f 100644 --- a/go.sum +++ b/go.sum @@ -101,5 +101,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.24.1-0.20250609115446-a154718b5d4c h1:VLAz8J3UU6AS8OaPpiwrp1NvhgjJOWBbbQ8Y8oh7Cdg= -maunium.net/go/mautrix v0.24.1-0.20250609115446-a154718b5d4c/go.mod h1:HqA1HUutQYJkrYRPkK64itARDz79PCec1oWVEB72HVQ= +maunium.net/go/mautrix v0.24.1-0.20250609163037-72bacbb666fb h1:Cq5ewnYg3tu5eRo4h9tsWI4YjALVgnARBVTebqYe5qw= +maunium.net/go/mautrix v0.24.1-0.20250609163037-72bacbb666fb/go.mod h1:HqA1HUutQYJkrYRPkK64itARDz79PCec1oWVEB72HVQ= diff --git a/pkg/connector/events.go b/pkg/connector/events.go index da3ec54c..fbda2bcb 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -125,7 +125,8 @@ func (evt *WAMessageEvent) AddLogContext(c zerolog.Context) zerolog.Context { } func (evt *WAMessageEvent) PreHandle(ctx context.Context, portal *bridgev2.Portal) { - if evt.Info.Sender.Server != types.HiddenUserServer || evt.Info.Chat.Server != types.GroupServer { + if (evt.Info.Sender.Server != types.HiddenUserServer && evt.Info.AddressingMode != types.AddressingModeLID) || + evt.Info.Chat.Server != types.GroupServer { return } portalJID, err := waid.ParsePortalID(portal.ID) diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 1fed10f4..848359ca 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -161,7 +161,7 @@ func (wa *WhatsAppClient) doGhostResync(ctx context.Context, queue map[types.JID } func (wa *WhatsAppClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost) (*bridgev2.UserInfo, error) { - if ghost.Name != "" { + if ghost.Name != "" && ghost.NameSet { wa.EnqueueGhostResync(ghost) return nil, nil } From 9d1b49de7dd05f73342b7039205531c127ccf5ee Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 9 Jun 2025 19:53:23 +0300 Subject: [PATCH 066/486] handlewhatsapp: add log identifier for lid migration --- pkg/connector/events.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/connector/events.go b/pkg/connector/events.go index fbda2bcb..a8ca798e 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -137,7 +137,8 @@ func (evt *WAMessageEvent) PreHandle(ctx context.Context, portal *bridgev2.Porta if meta.AddressingMode == types.AddressingModeLID || meta.LIDMigrationAttempted { return } - log := zerolog.Ctx(ctx) + log := zerolog.Ctx(ctx).With().Str("action", "group lid migration").Logger() + ctx = log.WithContext(ctx) meta.LIDMigrationAttempted = true info, err := evt.wa.Client.GetGroupInfo(portalJID) if err != nil { From 5ba8c9e43d005f5ff234788260176744a480fb63 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 9 Jun 2025 22:27:50 +0300 Subject: [PATCH 067/486] handlewhatsapp: only consider addressing mode for migration check --- pkg/connector/events.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/connector/events.go b/pkg/connector/events.go index a8ca798e..31a2a3bc 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -125,8 +125,7 @@ func (evt *WAMessageEvent) AddLogContext(c zerolog.Context) zerolog.Context { } func (evt *WAMessageEvent) PreHandle(ctx context.Context, portal *bridgev2.Portal) { - if (evt.Info.Sender.Server != types.HiddenUserServer && evt.Info.AddressingMode != types.AddressingModeLID) || - evt.Info.Chat.Server != types.GroupServer { + if evt.Info.AddressingMode != types.AddressingModeLID || evt.Info.Chat.Server != types.GroupServer { return } portalJID, err := waid.ParsePortalID(portal.ID) From 053c4a6a766ea89ceeea3a927415b7596a672f79 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 10 Jun 2025 16:40:41 +0300 Subject: [PATCH 068/486] handlewhatsapp: force lid DMs into phone number portal --- pkg/connector/handlewhatsapp.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 8df3bcc7..8a1480df 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -256,6 +256,15 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) { } func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Message) { + if evt.Info.Chat.Server == types.HiddenUserServer && evt.Info.Sender.ToNonAD() == evt.Info.Chat && evt.Info.SenderAlt.Server == types.DefaultUserServer { + wa.UserLogin.Log.Debug(). + Stringer("lid", evt.Info.Sender). + Stringer("pn", evt.Info.SenderAlt). + Str("message_id", evt.Info.ID). + Msg("Forced LID DM sender to phone number in incoming message") + evt.Info.Sender, evt.Info.SenderAlt = evt.Info.SenderAlt, evt.Info.Sender + evt.Info.Chat = evt.Info.Sender.ToNonAD() + } wa.UserLogin.Log.Trace(). Any("info", evt.Info). Any("payload", evt.Message). @@ -326,6 +335,15 @@ func (wa *WhatsAppClient) handleWAUndecryptableMessage(evt *events.Undecryptable } func (wa *WhatsAppClient) handleWAReceipt(evt *events.Receipt) { + if evt.Chat.Server == types.HiddenUserServer && evt.Sender.ToNonAD() == evt.Chat && evt.SenderAlt.Server == types.DefaultUserServer { + wa.UserLogin.Log.Debug(). + Stringer("lid", evt.Sender). + Stringer("pn", evt.SenderAlt). + Strs("message_id", evt.MessageIDs). + Msg("Forced LID DM sender to phone number in incoming receipt") + evt.Sender, evt.SenderAlt = evt.SenderAlt, evt.Sender + evt.Chat = evt.Sender.ToNonAD() + } if evt.IsFromMe && evt.Sender.Device == 0 { wa.phoneSeen(evt.Timestamp) } From 107ccf36448bc57bd4643c70255bf6c811648627 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 11 Jun 2025 16:09:45 +0300 Subject: [PATCH 069/486] dependencies: update --- go.mod | 14 +++++++------- go.sum | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 99660113..8ee6a934 100644 --- a/go.mod +++ b/go.mod @@ -11,13 +11,13 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.7 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250606170101-3afe34f8ab8f - golang.org/x/image v0.27.0 - golang.org/x/net v0.40.0 - golang.org/x/sync v0.14.0 + go.mau.fi/whatsmeow v0.0.0-20250611130243-afe87b6dd8b4 + golang.org/x/image v0.28.0 + golang.org/x/net v0.41.0 + golang.org/x/sync v0.15.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.1-0.20250609163037-72bacbb666fb + maunium.net/go/mautrix v0.24.1-0.20250611123434-15d0b63eb6ab ) require ( @@ -39,10 +39,10 @@ require ( github.com/yuin/goldmark v1.7.11 // indirect go.mau.fi/libsignal v0.2.0 // indirect go.mau.fi/zeroconfig v0.1.3 // indirect - golang.org/x/crypto v0.38.0 // indirect + golang.org/x/crypto v0.39.0 // indirect golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.25.0 // indirect + golang.org/x/text v0.26.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 dcb40a8f..c053b02b 100644 --- a/go.sum +++ b/go.sum @@ -69,27 +69,27 @@ go.mau.fi/util v0.8.7 h1:ywKarPxouJQEEijTs4mPlxC7F4AWEKokEpWc+2TYy6c= go.mau.fi/util v0.8.7/go.mod h1:j6R3cENakc1f8HpQeFl0N15UiSTcNmIfDBNJUbL71RY= 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-20250606170101-3afe34f8ab8f h1:8csRM0kOS9nGgT162JFwi3FZ93NPM6fWVf/d5AfTceA= -go.mau.fi/whatsmeow v0.0.0-20250606170101-3afe34f8ab8f/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= +go.mau.fi/whatsmeow v0.0.0-20250611130243-afe87b6dd8b4 h1:z8sNDR8Ix933MBrihNrfo9eauhQw26D6gBEPpk5KXL8= +go.mau.fi/whatsmeow v0.0.0-20250611130243-afe87b6dd8b4/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= -golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= -golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE= +golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -101,5 +101,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.24.1-0.20250609163037-72bacbb666fb h1:Cq5ewnYg3tu5eRo4h9tsWI4YjALVgnARBVTebqYe5qw= -maunium.net/go/mautrix v0.24.1-0.20250609163037-72bacbb666fb/go.mod h1:HqA1HUutQYJkrYRPkK64itARDz79PCec1oWVEB72HVQ= +maunium.net/go/mautrix v0.24.1-0.20250611123434-15d0b63eb6ab h1:2oysPkLLkQkLhtH29C+hOXFxRU486dLQCmngTFw7oAA= +maunium.net/go/mautrix v0.24.1-0.20250611123434-15d0b63eb6ab/go.mod h1:HqA1HUutQYJkrYRPkK64itARDz79PCec1oWVEB72HVQ= From bbbfe4f8464267d890f38a4305b4f6089c170fef Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 11 Jun 2025 16:36:41 +0300 Subject: [PATCH 070/486] docker: update to Alpine 3.22 --- Dockerfile | 4 ++-- Dockerfile.ci | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 34fba376..32d7a6d8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1-alpine3.21 AS builder +FROM golang:1-alpine3.22 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.21 +FROM alpine:3.22 ENV UID=1337 \ GID=1337 diff --git a/Dockerfile.ci b/Dockerfile.ci index e8e79e17..28c7cfbc 100644 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -1,4 +1,4 @@ -FROM alpine:3.21 +FROM alpine:3.22 ENV UID=1337 \ GID=1337 From c72dde9ab506fe177c3dbbd13f89a551c8c74bb4 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 11 Jun 2025 17:45:07 +0300 Subject: [PATCH 071/486] 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 8ee6a934..6f463f4a 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.7 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250611130243-afe87b6dd8b4 + go.mau.fi/whatsmeow v0.0.0-20250611144432-af1a723d11ee golang.org/x/image v0.28.0 golang.org/x/net v0.41.0 golang.org/x/sync v0.15.0 diff --git a/go.sum b/go.sum index c053b02b..5347d51a 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.7 h1:ywKarPxouJQEEijTs4mPlxC7F4AWEKokEpWc+2TYy6c= go.mau.fi/util v0.8.7/go.mod h1:j6R3cENakc1f8HpQeFl0N15UiSTcNmIfDBNJUbL71RY= 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-20250611130243-afe87b6dd8b4 h1:z8sNDR8Ix933MBrihNrfo9eauhQw26D6gBEPpk5KXL8= -go.mau.fi/whatsmeow v0.0.0-20250611130243-afe87b6dd8b4/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= +go.mau.fi/whatsmeow v0.0.0-20250611144432-af1a723d11ee h1:mnM1j3XNOVkE/yepWa2ym5dLdhhEV7FWADx7I/vvkis= +go.mau.fi/whatsmeow v0.0.0-20250611144432-af1a723d11ee/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= From ed5deee13c4578092a1cf71429d50695cf6a81bd Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 14 Jun 2025 12:31:57 +0300 Subject: [PATCH 072/486] dependencies: update --- go.mod | 12 ++++++------ go.sum | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 6f463f4a..84d094a3 100644 --- a/go.mod +++ b/go.mod @@ -2,22 +2,22 @@ module go.mau.fi/mautrix-whatsapp go 1.23.0 -toolchain go1.24.3 +toolchain go1.24.4 require ( github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.8.7 + go.mau.fi/util v0.8.8-0.20250612103042-2aa072eb60f0 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250611144432-af1a723d11ee + go.mau.fi/whatsmeow v0.0.0-20250612124742-cc05d16b464b golang.org/x/image v0.28.0 golang.org/x/net v0.41.0 golang.org/x/sync v0.15.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.1-0.20250611123434-15d0b63eb6ab + maunium.net/go/mautrix v0.24.1-0.20250614093103-c836dbafdfd9 ) require ( @@ -36,11 +36,11 @@ require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect - github.com/yuin/goldmark v1.7.11 // indirect + github.com/yuin/goldmark v1.7.12 // indirect go.mau.fi/libsignal v0.2.0 // indirect go.mau.fi/zeroconfig v0.1.3 // indirect golang.org/x/crypto v0.39.0 // indirect - golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect + golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.26.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index 5347d51a..d39c070c 100644 --- a/go.sum +++ b/go.sum @@ -61,22 +61,22 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 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/yuin/goldmark v1.7.11 h1:ZCxLyDMtz0nT2HFfsYG8WZ47Trip2+JyLysKcMYE5bo= -github.com/yuin/goldmark v1.7.11/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY= +github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.0 h1:oRXj3OHhEJq51BFEM8/50UZblmWiTYH93hsNTPcbk90= go.mau.fi/libsignal v0.2.0/go.mod h1:tvjoDsMejgT38CXTXwqaYu8itBiY8O2Mb6biWvZBb9k= -go.mau.fi/util v0.8.7 h1:ywKarPxouJQEEijTs4mPlxC7F4AWEKokEpWc+2TYy6c= -go.mau.fi/util v0.8.7/go.mod h1:j6R3cENakc1f8HpQeFl0N15UiSTcNmIfDBNJUbL71RY= +go.mau.fi/util v0.8.8-0.20250612103042-2aa072eb60f0 h1:EcDJfYWX6aVT3c6nWTg9Qly41rNKabzzERt7OFzVerA= +go.mau.fi/util v0.8.8-0.20250612103042-2aa072eb60f0/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= 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-20250611144432-af1a723d11ee h1:mnM1j3XNOVkE/yepWa2ym5dLdhhEV7FWADx7I/vvkis= -go.mau.fi/whatsmeow v0.0.0-20250611144432-af1a723d11ee/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= +go.mau.fi/whatsmeow v0.0.0-20250612124742-cc05d16b464b h1:jiEaJcX+R5bzfdpNIyAPzp5BgvdAjs2L7GEmjH98qm4= +go.mau.fi/whatsmeow v0.0.0-20250612124742-cc05d16b464b/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= -golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= -golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= +golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 h1:bsqhLWFR6G6xiQcb+JoGqdKdRU6WzPWmK8E0jxTjzo4= +golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE= golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY= golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= @@ -101,5 +101,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.24.1-0.20250611123434-15d0b63eb6ab h1:2oysPkLLkQkLhtH29C+hOXFxRU486dLQCmngTFw7oAA= -maunium.net/go/mautrix v0.24.1-0.20250611123434-15d0b63eb6ab/go.mod h1:HqA1HUutQYJkrYRPkK64itARDz79PCec1oWVEB72HVQ= +maunium.net/go/mautrix v0.24.1-0.20250614093103-c836dbafdfd9 h1:4wv3FArgoOG2Ro+pYUdQdtBo+25TdzIGwtL44DFbSbY= +maunium.net/go/mautrix v0.24.1-0.20250614093103-c836dbafdfd9/go.mod h1:IKIE5XbiJ38HbWzHcq9Bo3LYTiKARiEFQfzlZYmi5KE= From d58b0267f3af033b424219ec8f00d85594365e57 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 14 Jun 2025 12:36:01 +0300 Subject: [PATCH 073/486] startchat: don't allow creating DMs with LIDs for now --- pkg/connector/startchat.go | 43 ++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index e2631bc7..7443ed3d 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -53,7 +53,7 @@ func looksEmaily(str string) bool { } func (wa *WhatsAppClient) validateIdentifer(number string) (types.JID, error) { - if strings.HasSuffix(number, "@"+types.BotServer) { + if strings.HasSuffix(number, "@"+types.BotServer) || strings.HasSuffix(number, "@"+types.HiddenUserServer) { return types.ParseJID(number) } if strings.HasSuffix(number, "@"+types.DefaultUserServer) { @@ -96,12 +96,47 @@ 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.Device.LIDs.GetPNForLID(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 pn, nil + } + return jid, nil +} + +func (wa *WhatsAppClient) makeCreateChatResponse(jid, origJID types.JID) *bridgev2.CreateChatResponse { + var redirID networkid.UserID + if origJID != jid { + redirID = waid.MakeUserID(jid) + } + return &bridgev2.CreateChatResponse{ + PortalKey: wa.makeWAPortalKey(jid), + PortalInfo: wa.wrapDMInfo(jid), + DMRedirectedTo: redirID, + } +} + func (wa *WhatsAppClient) CreateChatWithGhost(ctx context.Context, ghost *bridgev2.Ghost) (*bridgev2.CreateChatResponse, error) { - return &bridgev2.CreateChatResponse{PortalKey: wa.makeWAPortalKey(waid.ParseUserID(ghost.ID))}, nil + origJID := waid.ParseUserID(ghost.ID) + jid, err := wa.startChatLIDToPN(ctx, origJID) + if err != nil { + return nil, err + } + return wa.makeCreateChatResponse(jid, origJID), nil } func (wa *WhatsAppClient) ResolveIdentifier(ctx context.Context, identifier string, startChat bool) (*bridgev2.ResolveIdentifierResponse, error) { - jid, err := wa.validateIdentifer(identifier) + origJID, err := wa.validateIdentifer(identifier) + if err != nil { + return nil, err + } + jid, err := wa.startChatLIDToPN(ctx, origJID) if err != nil { return nil, err } @@ -113,7 +148,7 @@ func (wa *WhatsAppClient) ResolveIdentifier(ctx context.Context, identifier stri return &bridgev2.ResolveIdentifierResponse{ Ghost: ghost, UserID: waid.MakeUserID(jid), - Chat: &bridgev2.CreateChatResponse{PortalKey: wa.makeWAPortalKey(jid)}, + Chat: wa.makeCreateChatResponse(jid, origJID), }, nil } From 27045b27c6aa71e9b49f0ce564823e61f758006e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 14 Jun 2025 12:36:25 +0300 Subject: [PATCH 074/486] handlewhatsapp: redirect LID DMs sent from other devices to PN portal --- pkg/connector/handlewhatsapp.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 8a1480df..8caddc1c 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -264,6 +264,13 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa Msg("Forced LID DM sender to phone number in incoming message") evt.Info.Sender, evt.Info.SenderAlt = evt.Info.SenderAlt, evt.Info.Sender evt.Info.Chat = evt.Info.Sender.ToNonAD() + } else if evt.Info.Chat.Server == types.HiddenUserServer && evt.Info.IsFromMe && evt.Info.RecipientAlt.Server == types.DefaultUserServer { + wa.UserLogin.Log.Debug(). + Stringer("lid", evt.Info.Chat). + Stringer("pn", evt.Info.RecipientAlt). + Str("message_id", evt.Info.ID). + Msg("Forced LID DM sender to phone number in own message sent from another device") + evt.Info.Chat = evt.Info.RecipientAlt.ToNonAD() } wa.UserLogin.Log.Trace(). Any("info", evt.Info). @@ -343,6 +350,13 @@ func (wa *WhatsAppClient) handleWAReceipt(evt *events.Receipt) { Msg("Forced LID DM sender to phone number in incoming receipt") evt.Sender, evt.SenderAlt = evt.SenderAlt, evt.Sender evt.Chat = evt.Sender.ToNonAD() + } else if evt.Chat.Server == types.HiddenUserServer && evt.IsFromMe && evt.RecipientAlt.Server == types.DefaultUserServer { + wa.UserLogin.Log.Debug(). + Stringer("lid", evt.Chat). + Stringer("pn", evt.RecipientAlt). + Strs("message_id", evt.MessageIDs). + Msg("Forced LID DM sender to phone number in own receipt sent from another device") + evt.Chat = evt.RecipientAlt.ToNonAD() } if evt.IsFromMe && evt.Sender.Device == 0 { wa.phoneSeen(evt.Timestamp) From 3697418d32280273c5814ea562c274f0bbd8fc9d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 14 Jun 2025 16:02:40 +0300 Subject: [PATCH 075/486] client: log connect errors --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/client.go | 4 ++++ pkg/connector/example-config.yaml | 2 ++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 84d094a3..82743d67 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.8-0.20250612103042-2aa072eb60f0 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250612124742-cc05d16b464b + go.mau.fi/whatsmeow v0.0.0-20250614130217-d5152ad30f01 golang.org/x/image v0.28.0 golang.org/x/net v0.41.0 golang.org/x/sync v0.15.0 diff --git a/go.sum b/go.sum index d39c070c..e28a1fa7 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.8-0.20250612103042-2aa072eb60f0 h1:EcDJfYWX6aVT3c6nWTg9Qly41 go.mau.fi/util v0.8.8-0.20250612103042-2aa072eb60f0/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= 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-20250612124742-cc05d16b464b h1:jiEaJcX+R5bzfdpNIyAPzp5BgvdAjs2L7GEmjH98qm4= -go.mau.fi/whatsmeow v0.0.0-20250612124742-cc05d16b464b/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= +go.mau.fi/whatsmeow v0.0.0-20250614130217-d5152ad30f01 h1:sB4V19Cvf68lQTrCTmpf3AxUyV9Vl/MTR/zs92z1kpk= +go.mau.fi/whatsmeow v0.0.0-20250614130217-d5152ad30f01/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= diff --git a/pkg/connector/client.go b/pkg/connector/client.go index d75a7fd2..543c7d4d 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -193,9 +193,13 @@ func (wa *WhatsAppClient) Connect(ctx context.Context) { wa.startLoops() wa.Client.BackgroundEventCtx = wa.Main.Bridge.BackgroundCtx if err := wa.Client.Connect(); err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to connect to WhatsApp") state := status.BridgeState{ StateEvent: status.StateUnknownError, Error: WAConnectionFailed, + Info: map[string]any{ + "go_error": err.Error(), + }, } wa.UserLogin.BridgeState.Send(state) } diff --git a/pkg/connector/example-config.yaml b/pkg/connector/example-config.yaml index eb5fb2dd..37c41f7b 100644 --- a/pkg/connector/example-config.yaml +++ b/pkg/connector/example-config.yaml @@ -61,6 +61,8 @@ force_active_delivery_receipts: false # When direct media is enabled and a piece of media isn't available on the WhatsApp servers, # should it be automatically requested from the phone? direct_media_auto_request: true +# Should the bridge automatically reconnect if it fails to connect on startup? +initial_auto_reconnect: true # Settings for converting animated stickers. animated_sticker: From c298436511786b896486ddf9b947b1a41fa22c70 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 16 Jun 2025 18:17:00 +0300 Subject: [PATCH 076/486] Bump version to v0.12.2 --- CHANGELOG.md | 13 +++++++++++++ cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 6 +++--- go.sum | 12 ++++++------ 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2f2485d..17d1e729 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# v0.12.2 (2025-06-16) + +* Improved support for `@lid` users. + * **N.B.** As mentioned in the v0.12.0 release, old registration files may + have `[0-9]+` in the `users` regex. You must change it to `.+`, as the new + `lid` identifiers are bridged as `lid-` instead of just ``. +* Updated Docker image to Alpine 3.22. +* Fixed network errors on first connect not triggering automatic reconnect. +* Fixed animated sticker zips not being extracted when using direct media. + # v0.12.1 (2025-05-16) * Added prefix to identify forwarded messages on WhatsApp. @@ -13,6 +23,9 @@ * Migrated Signal session store to use new `@lid` identifiers to support future chats that don't expose phone numbers. + * **N.B.** Old registration files may have `[0-9]+` in the `users` regex. You + must change it to `.+`, as the new `lid` identifiers are bridged as + `lid-` instead of just ``. * Added fallbacks for various business message types. * Added support for bridging invites, kicks and leaves in groups. * Re-added `invite-link`, `join` and `sync` commands for groups. diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index a0542f2d..40170f5b 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -23,7 +23,7 @@ var m = mxmain.BridgeMain{ Name: "mautrix-whatsapp", URL: "https://github.com/mautrix/whatsapp", Description: "A Matrix-WhatsApp puppeting bridge.", - Version: "0.12.1", + Version: "0.12.2", Connector: c, } diff --git a/go.mod b/go.mod index 82743d67..b64022a0 100644 --- a/go.mod +++ b/go.mod @@ -9,15 +9,15 @@ require ( github.com/gorilla/websocket v1.5.0 github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.8.8-0.20250612103042-2aa072eb60f0 + go.mau.fi/util v0.8.8 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250614130217-d5152ad30f01 + go.mau.fi/whatsmeow v0.0.0-20250616150942-80f2a1807103 golang.org/x/image v0.28.0 golang.org/x/net v0.41.0 golang.org/x/sync v0.15.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.1-0.20250614093103-c836dbafdfd9 + maunium.net/go/mautrix v0.24.1 ) require ( diff --git a/go.sum b/go.sum index e28a1fa7..5b0554f0 100644 --- a/go.sum +++ b/go.sum @@ -65,12 +65,12 @@ github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY= github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.0 h1:oRXj3OHhEJq51BFEM8/50UZblmWiTYH93hsNTPcbk90= go.mau.fi/libsignal v0.2.0/go.mod h1:tvjoDsMejgT38CXTXwqaYu8itBiY8O2Mb6biWvZBb9k= -go.mau.fi/util v0.8.8-0.20250612103042-2aa072eb60f0 h1:EcDJfYWX6aVT3c6nWTg9Qly41rNKabzzERt7OFzVerA= -go.mau.fi/util v0.8.8-0.20250612103042-2aa072eb60f0/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= +go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c= +go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= 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-20250614130217-d5152ad30f01 h1:sB4V19Cvf68lQTrCTmpf3AxUyV9Vl/MTR/zs92z1kpk= -go.mau.fi/whatsmeow v0.0.0-20250614130217-d5152ad30f01/go.mod h1:Qy3L3BNBcnxfrAQ09lmFMa0ItZfg8zl9DzxKrptzfU4= +go.mau.fi/whatsmeow v0.0.0-20250616150942-80f2a1807103 h1:9X0vTlbEPIa9X7Vr/dszlJnKdGjCXftmasc81qo7Wxk= +go.mau.fi/whatsmeow v0.0.0-20250616150942-80f2a1807103/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= @@ -101,5 +101,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.24.1-0.20250614093103-c836dbafdfd9 h1:4wv3FArgoOG2Ro+pYUdQdtBo+25TdzIGwtL44DFbSbY= -maunium.net/go/mautrix v0.24.1-0.20250614093103-c836dbafdfd9/go.mod h1:IKIE5XbiJ38HbWzHcq9Bo3LYTiKARiEFQfzlZYmi5KE= +maunium.net/go/mautrix v0.24.1 h1:09/xi4qTeA03g1n/DPmmqAlT8Cx4QrgwiPlmLVzA9AU= +maunium.net/go/mautrix v0.24.1/go.mod h1:Xy6o+pXmbqmgWsUWh15EQ1eozjC+k/VT/7kloByv9PI= From 5f3abdba539f0ac6802a95f0fb9c25a764cc87e0 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 16 Jun 2025 21:37:22 +0300 Subject: [PATCH 077/486] handlewhatsapp: fix read receipts in lid groups --- pkg/connector/handlewhatsapp.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 8caddc1c..375b31dd 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -376,6 +376,11 @@ func (wa *WhatsAppClient) handleWAReceipt(evt *events.Receipt) { messageSender := wa.JID if !evt.MessageSender.IsEmpty() { messageSender = evt.MessageSender + } else if evt.Chat.Server == types.GroupServer && evt.Sender.Server == types.HiddenUserServer { + lid := wa.Device.GetLID() + if !lid.IsEmpty() { + messageSender = lid + } } for i, id := range evt.MessageIDs { targets[i] = waid.MakeMessageID(evt.Chat, messageSender, id) From 628902dae83e4af21107a8eb97419317ce05907d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 17 Jun 2025 22:24:37 +0530 Subject: [PATCH 078/486] handlewhatsapp: plumb event handling result to whatsmeow (#804) --- go.mod | 4 ++-- go.sum | 8 ++++---- pkg/connector/chatinfo.go | 5 ++++- pkg/connector/client.go | 2 +- pkg/connector/handlewhatsapp.go | 31 ++++++++++++++++++------------- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index b64022a0..0593f4d8 100644 --- a/go.mod +++ b/go.mod @@ -11,13 +11,13 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.8 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250616150942-80f2a1807103 + go.mau.fi/whatsmeow v0.0.0-20250617164104-eea582eed117 golang.org/x/image v0.28.0 golang.org/x/net v0.41.0 golang.org/x/sync v0.15.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.1 + maunium.net/go/mautrix v0.24.2-0.20250617163829-26da46dbbf6e ) require ( diff --git a/go.sum b/go.sum index 5b0554f0..764c3958 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c= go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= 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-20250616150942-80f2a1807103 h1:9X0vTlbEPIa9X7Vr/dszlJnKdGjCXftmasc81qo7Wxk= -go.mau.fi/whatsmeow v0.0.0-20250616150942-80f2a1807103/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= +go.mau.fi/whatsmeow v0.0.0-20250617164104-eea582eed117 h1:DJLAjmXGaEzLvkOb/Kb0Cg21KG1xBdq5lNJg2gkTOPE= +go.mau.fi/whatsmeow v0.0.0-20250617164104-eea582eed117/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= @@ -101,5 +101,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.24.1 h1:09/xi4qTeA03g1n/DPmmqAlT8Cx4QrgwiPlmLVzA9AU= -maunium.net/go/mautrix v0.24.1/go.mod h1:Xy6o+pXmbqmgWsUWh15EQ1eozjC+k/VT/7kloByv9PI= +maunium.net/go/mautrix v0.24.2-0.20250617163829-26da46dbbf6e h1:Y8kbRpPcKMZn2gIjUFd15xzMB5GJ2bS6ZcOfvlx4KnE= +maunium.net/go/mautrix v0.24.2-0.20250617163829-26da46dbbf6e/go.mod h1:Xy6o+pXmbqmgWsUWh15EQ1eozjC+k/VT/7kloByv9PI= diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 4d06ccc9..4ee0a1b7 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -427,7 +427,10 @@ func (wa *WhatsAppClient) makePortalAvatarFetcher(avatarID string, sender types. if !sender.IsEmpty() { evtSender = wa.makeEventSender(sender) } - senderIntent := portal.GetIntentFor(ctx, evtSender, wa.UserLogin, bridgev2.RemoteEventChatInfoChange) + senderIntent, ok := portal.GetIntentFor(ctx, evtSender, wa.UserLogin, bridgev2.RemoteEventChatInfoChange) + if !ok { + return false + } //lint:ignore SA1019 TODO invent a cleaner way to fetch avatar metadata before updating? return portal.Internal().UpdateAvatar(ctx, wrappedAvatar, senderIntent, ts) } diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 543c7d4d..3cc56335 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -69,7 +69,7 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. if w.Device != nil { log := w.UserLogin.Log.With().Str("component", "whatsmeow").Logger() w.Client = whatsmeow.NewClient(w.Device, waLog.Zerolog(log)) - w.Client.AddEventHandler(w.handleWAEvent) + w.Client.AddEventHandlerWithSuccessStatus(w.handleWAEvent) if bridgev2.PortalEventBuffer == 0 { w.Client.SynchronousAck = true w.Client.EnableDecryptedEventBuffer = true diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 375b31dd..3a9d5f2a 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -71,19 +71,19 @@ func init() { }) } -func (wa *WhatsAppClient) handleWAEvent(rawEvt any) { +func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { log := wa.UserLogin.Log ctx := log.WithContext(wa.Main.Bridge.BackgroundCtx) switch evt := rawEvt.(type) { case *events.Message: - wa.handleWAMessage(ctx, evt) + return wa.handleWAMessage(ctx, evt) case *events.Receipt: - wa.handleWAReceipt(evt) + return wa.handleWAReceipt(evt) case *events.ChatPresence: wa.handleWAChatPresence(evt) case *events.UndecryptableMessage: - wa.handleWAUndecryptableMessage(evt) + return wa.handleWAUndecryptableMessage(evt) case *events.CallOffer: wa.handleWACallStart(evt.CallCreator, evt.CallID, "", evt.Timestamp) @@ -253,9 +253,11 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) { default: log.Debug().Type("event_type", rawEvt).Msg("Unhandled WhatsApp event") } + return true } -func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Message) { +func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Message) (success bool) { + success = true if evt.Info.Chat.Server == types.HiddenUserServer && evt.Info.Sender.ToNonAD() == evt.Info.Chat && evt.Info.SenderAlt.Server == types.DefaultUserServer { wa.UserLogin.Log.Debug(). Stringer("lid", evt.Info.Sender). @@ -307,7 +309,7 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa evt.Message = decrypted } } - wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &WAMessageEvent{ + res := wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &WAMessageEvent{ MessageInfoWrapper: &MessageInfoWrapper{ Info: evt.Info, wa: wa, @@ -317,9 +319,10 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa parsedMessageType: parsedMessageType, }) + return res.Success } -func (wa *WhatsAppClient) handleWAUndecryptableMessage(evt *events.UndecryptableMessage) { +func (wa *WhatsAppClient) handleWAUndecryptableMessage(evt *events.UndecryptableMessage) bool { wa.UserLogin.Log.Debug(). Any("info", evt.Info). Bool("unavailable", evt.IsUnavailable). @@ -327,21 +330,22 @@ func (wa *WhatsAppClient) handleWAUndecryptableMessage(evt *events.Undecryptable Msg("Received undecryptable WhatsApp message") wa.trackUndecryptable(evt) if evt.DecryptFailMode == events.DecryptFailHide { - return + return true } if evt.Info.Chat == types.StatusBroadcastJID && !wa.Main.Config.EnableStatusBroadcast { - return + return true } - wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &WAUndecryptableMessage{ + res := wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &WAUndecryptableMessage{ MessageInfoWrapper: &MessageInfoWrapper{ Info: evt.Info, wa: wa, }, Type: evt.UnavailableType, }) + return res.Success } -func (wa *WhatsAppClient) handleWAReceipt(evt *events.Receipt) { +func (wa *WhatsAppClient) handleWAReceipt(evt *events.Receipt) (success bool) { if evt.Chat.Server == types.HiddenUserServer && evt.Sender.ToNonAD() == evt.Chat && evt.SenderAlt.Server == types.DefaultUserServer { wa.UserLogin.Log.Debug(). Stringer("lid", evt.Sender). @@ -370,7 +374,7 @@ func (wa *WhatsAppClient) handleWAReceipt(evt *events.Receipt) { case types.ReceiptTypeSender: fallthrough default: - return + return true } targets := make([]networkid.MessageID, len(evt.MessageIDs)) messageSender := wa.JID @@ -385,7 +389,7 @@ func (wa *WhatsAppClient) handleWAReceipt(evt *events.Receipt) { for i, id := range evt.MessageIDs { targets[i] = waid.MakeMessageID(evt.Chat, messageSender, id) } - wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &simplevent.Receipt{ + res := wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &simplevent.Receipt{ EventMeta: simplevent.EventMeta{ Type: evtType, PortalKey: wa.makeWAPortalKey(evt.Chat), @@ -394,6 +398,7 @@ func (wa *WhatsAppClient) handleWAReceipt(evt *events.Receipt) { }, Targets: targets, }) + return res.Success } func (wa *WhatsAppClient) handleWAChatPresence(evt *events.ChatPresence) { From ab48b4123f4569b7f9824ec8e406c4a69556ad6c Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 17 Jun 2025 20:05:38 +0300 Subject: [PATCH 079/486] 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 0593f4d8..a8be453a 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.8 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250617164104-eea582eed117 + go.mau.fi/whatsmeow v0.0.0-20250617170509-947866bb9f75 golang.org/x/image v0.28.0 golang.org/x/net v0.41.0 golang.org/x/sync v0.15.0 diff --git a/go.sum b/go.sum index 764c3958..0e608112 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c= go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= 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-20250617164104-eea582eed117 h1:DJLAjmXGaEzLvkOb/Kb0Cg21KG1xBdq5lNJg2gkTOPE= -go.mau.fi/whatsmeow v0.0.0-20250617164104-eea582eed117/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= +go.mau.fi/whatsmeow v0.0.0-20250617170509-947866bb9f75 h1:5SvY8TY8Yo0wXpn+enqE1sCQBKRSHxLcMrP+AG4Pe+k= +go.mau.fi/whatsmeow v0.0.0-20250617170509-947866bb9f75/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= From ca37208de3324b9b0102defe4bf392a43bee2b97 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 27 Jun 2025 15:28:16 +0200 Subject: [PATCH 080/486] 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 a8be453a..6a97684f 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.8 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250617170509-947866bb9f75 + go.mau.fi/whatsmeow v0.0.0-20250627132727-b8996aaf77fc golang.org/x/image v0.28.0 golang.org/x/net v0.41.0 golang.org/x/sync v0.15.0 diff --git a/go.sum b/go.sum index 0e608112..9b74da3e 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c= go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= 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-20250617170509-947866bb9f75 h1:5SvY8TY8Yo0wXpn+enqE1sCQBKRSHxLcMrP+AG4Pe+k= -go.mau.fi/whatsmeow v0.0.0-20250617170509-947866bb9f75/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= +go.mau.fi/whatsmeow v0.0.0-20250627132727-b8996aaf77fc h1:82eP5GFAu/4Kxt4OrTZ+UH+NB/W4Wsy6VmW9dTkWI/g= +go.mau.fi/whatsmeow v0.0.0-20250627132727-b8996aaf77fc/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= From 4a27484cbed94b21ba13eeee811add704cbf657d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 27 Jun 2025 15:33:53 +0200 Subject: [PATCH 081/486] 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 6a97684f..087ac5b7 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.8 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250627132727-b8996aaf77fc + go.mau.fi/whatsmeow v0.0.0-20250627133320-9948ada1f8aa golang.org/x/image v0.28.0 golang.org/x/net v0.41.0 golang.org/x/sync v0.15.0 diff --git a/go.sum b/go.sum index 9b74da3e..16bf155d 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c= go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= 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-20250627132727-b8996aaf77fc h1:82eP5GFAu/4Kxt4OrTZ+UH+NB/W4Wsy6VmW9dTkWI/g= -go.mau.fi/whatsmeow v0.0.0-20250627132727-b8996aaf77fc/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= +go.mau.fi/whatsmeow v0.0.0-20250627133320-9948ada1f8aa h1:iptlIdHZm4vv1np6iZ3+tV4XdADVWUUcX3zc18NhXmU= +go.mau.fi/whatsmeow v0.0.0-20250627133320-9948ada1f8aa/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= From da130190f0cfb76b0a71ed539cfa759f2935b641 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 30 Jun 2025 19:52:05 +0300 Subject: [PATCH 082/486] chatinfo: fix phone number removes in group lid migration --- pkg/connector/chatinfo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 4ee0a1b7..1c984c03 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -278,7 +278,7 @@ func (wa *WhatsAppClient) wrapGroupInfo(info *types.GroupInfo) *bridgev2.ChatInf wrapped.Members.MemberMap[waid.MakeUserID(pcp.JID)] = member if pcp.JID.Server == types.HiddenUserServer && !pcp.PhoneNumber.IsEmpty() { wrapped.Members.MemberMap[waid.MakeUserID(pcp.PhoneNumber)] = bridgev2.ChatMember{ - EventSender: wa.makeEventSender(pcp.PhoneNumber), + EventSender: bridgev2.EventSender{Sender: waid.MakeUserID(pcp.PhoneNumber)}, Membership: event.MembershipLeave, PrevMembership: event.MembershipJoin, MemberEventExtra: map[string]any{ From 58cc5110d2932f1cdee42975847e069e60dae72f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 1 Jul 2025 01:23:56 +0300 Subject: [PATCH 083/486] client: don't fetch latest version number for background connections --- pkg/connector/client.go | 2 +- pkg/connector/connector.go | 29 ++++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 3cc56335..cfd63741 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -230,7 +230,7 @@ func (wa *WhatsAppClient) ConnectBackground(ctx context.Context, params *bridgev } wa.Client.BackgroundEventCtx = wa.Main.Bridge.BackgroundCtx wa.offlineSyncWaiter = make(chan error) - wa.Main.firstClientConnectOnce.Do(wa.Main.onFirstClientConnect) + wa.Main.backgroundConnectOnce.Do(wa.Main.onFirstBackgroundConnect) if err := wa.Main.updateProxy(ctx, wa.Client, false); err != nil { zerolog.Ctx(ctx).Err(err).Msg("Failed to update proxy") } diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index ca8cd5b7..1e41e806 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -51,6 +51,7 @@ type WhatsAppConnector struct { DB *wadb.Database firstClientConnectOnce sync.Once + backgroundConnectOnce sync.Once mediaEditCache MediaEditCache mediaEditCacheLock sync.RWMutex @@ -146,13 +147,34 @@ func (wa *WhatsAppConnector) Start(ctx context.Context) error { } func (wa *WhatsAppConnector) Stop() { - if stop := wa.stopMediaEditCacheLoop.Load(); stop != nil { + if stop := wa.stopMediaEditCacheLoop.Swap(nil); stop != nil { (*stop)() } } +const kvWAVersion = "whatsapp_web_version" + +func (wa *WhatsAppConnector) onFirstBackgroundConnect() { + verStr := wa.Bridge.DB.KV.Get(wa.Bridge.BackgroundCtx, kvWAVersion) + if verStr == "" { + wa.Bridge.Log.Warn().Msg("No WhatsApp web version number cached in database") + return + } + ver, err := store.ParseVersion(verStr) + if err != nil { + wa.Bridge.Log.Err(err).Msg("Failed to parse WhatsApp web version number from database") + return + } + wa.Bridge.Log.Debug(). + Stringer("hardcoded_version", store.GetWAVersion()). + Stringer("cached_version", ver). + Msg("Using cached WhatsApp web version number") + store.SetWAVersion(ver) +} + func (wa *WhatsAppConnector) onFirstClientConnect() { - ver, err := whatsmeow.GetLatestVersion(wa.Bridge.BackgroundCtx, nil) + ctx := wa.Bridge.BackgroundCtx + ver, err := whatsmeow.GetLatestVersion(ctx, nil) if err != nil { wa.Bridge.Log.Err(err).Msg("Failed to get latest WhatsApp web version number") } else { @@ -161,8 +183,9 @@ func (wa *WhatsAppConnector) onFirstClientConnect() { Stringer("latest_version", *ver). Msg("Got latest WhatsApp web version number") store.SetWAVersion(*ver) + wa.Bridge.DB.KV.Set(ctx, kvWAVersion, ver.String()) } - meclCtx, cancel := context.WithCancel(context.Background()) + meclCtx, cancel := context.WithCancel(ctx) wa.stopMediaEditCacheLoop.Store(&cancel) go wa.mediaEditCacheExpireLoop(meclCtx) } From 216fe786aeceb1e6b4d938a81a53f7f04bd888cc Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 1 Jul 2025 01:24:44 +0300 Subject: [PATCH 084/486] 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 087ac5b7..7d10d2fc 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.8 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250627133320-9948ada1f8aa + go.mau.fi/whatsmeow v0.0.0-20250630101711-a0eb38c885c5 golang.org/x/image v0.28.0 golang.org/x/net v0.41.0 golang.org/x/sync v0.15.0 diff --git a/go.sum b/go.sum index 16bf155d..f24e145e 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c= go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= 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-20250627133320-9948ada1f8aa h1:iptlIdHZm4vv1np6iZ3+tV4XdADVWUUcX3zc18NhXmU= -go.mau.fi/whatsmeow v0.0.0-20250627133320-9948ada1f8aa/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= +go.mau.fi/whatsmeow v0.0.0-20250630101711-a0eb38c885c5 h1:e6TbliVnWP/FUoMwpkzT2lBzINogtoK0VR2dI0iUl1M= +go.mau.fi/whatsmeow v0.0.0-20250630101711-a0eb38c885c5/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= From c7cb936e068194d1f0eb9c024dc01da7862bdf0d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 1 Jul 2025 15:08:56 +0300 Subject: [PATCH 085/486] 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 7d10d2fc..754f9c99 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.8 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250630101711-a0eb38c885c5 + go.mau.fi/whatsmeow v0.0.0-20250701120710-561c998da20e golang.org/x/image v0.28.0 golang.org/x/net v0.41.0 golang.org/x/sync v0.15.0 diff --git a/go.sum b/go.sum index f24e145e..7ca686ae 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c= go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= 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-20250630101711-a0eb38c885c5 h1:e6TbliVnWP/FUoMwpkzT2lBzINogtoK0VR2dI0iUl1M= -go.mau.fi/whatsmeow v0.0.0-20250630101711-a0eb38c885c5/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= +go.mau.fi/whatsmeow v0.0.0-20250701120710-561c998da20e h1:BHka19pqaymXLg9WpgWBidQNC+bGJrzQWg5pBHe748w= +go.mau.fi/whatsmeow v0.0.0-20250701120710-561c998da20e/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= From 2f90bd4a0c6cd5a058481229be57488da0dcf602 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 2 Jul 2025 21:01:23 +0300 Subject: [PATCH 086/486] directmedia: fix handling retries when there's no state --- pkg/connector/directmedia.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index 29c4462d..9a5eb86e 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -225,7 +225,9 @@ func (wa *WhatsAppClient) receiveDirectMediaRetry(ctx context.Context, msg *data log.Warn().Err(err).Msg("Failed to decrypt media retry notification") return } - state.resultType = retryData.GetResult() + if state != nil { + state.resultType = retryData.GetResult() + } if retryData.GetResult() != waMmsRetry.MediaRetryNotification_SUCCESS { errorName := waMmsRetry.MediaRetryNotification_ResultType_name[int32(retryData.GetResult())] if retryData.GetDirectPath() == "" { From 4f6a3675a2f0592a7c12bbe1a1f609a163c46b1d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 2 Jul 2025 21:12:24 +0300 Subject: [PATCH 087/486] 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 754f9c99..c6aa440b 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.8 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250701120710-561c998da20e + go.mau.fi/whatsmeow v0.0.0-20250701221811-9adf672adc90 golang.org/x/image v0.28.0 golang.org/x/net v0.41.0 golang.org/x/sync v0.15.0 diff --git a/go.sum b/go.sum index 7ca686ae..00109dc7 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c= go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= 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-20250701120710-561c998da20e h1:BHka19pqaymXLg9WpgWBidQNC+bGJrzQWg5pBHe748w= -go.mau.fi/whatsmeow v0.0.0-20250701120710-561c998da20e/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= +go.mau.fi/whatsmeow v0.0.0-20250701221811-9adf672adc90 h1:K9knC6oQMLPutKwDVnqdgrDEjMJ8D5nREE9Li3dH71k= +go.mau.fi/whatsmeow v0.0.0-20250701221811-9adf672adc90/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= From d27e65510951d3b253ea3cc59aa84d221dd0246d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 3 Jul 2025 00:54:33 +0300 Subject: [PATCH 088/486] msgconv/from-matrix: convert quicktime videos to mp4 when sending --- pkg/connector/capabilities.go | 11 ++++++----- pkg/msgconv/from-matrix.go | 10 +++++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/pkg/connector/capabilities.go b/pkg/connector/capabilities.go index 1af3a9a9..c54c3784 100644 --- a/pkg/connector/capabilities.go +++ b/pkg/connector/capabilities.go @@ -23,7 +23,7 @@ func (wa *WhatsAppConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilit } func (wa *WhatsAppConnector) GetBridgeInfoVersion() (info, caps int) { - return 1, 1 + return 1, 2 } const WAMaxFileSize = 2000 * 1024 * 1024 @@ -38,7 +38,7 @@ func supportedIfFFmpeg() event.CapabilitySupportLevel { } func capID() string { - base := "fi.mau.whatsapp.capabilities.2025_01_10" + base := "fi.mau.whatsapp.capabilities.2025_06_03" if ffmpeg.Supported() { return base + "+ffmpeg" } @@ -116,9 +116,10 @@ var whatsappCaps = &event.RoomFeatures{ }, event.MsgVideo: { MimeTypes: map[string]event.CapabilitySupportLevel{ - "video/mp4": event.CapLevelFullySupported, - "video/3gpp": event.CapLevelFullySupported, - "video/webm": supportedIfFFmpeg(), + "video/mp4": event.CapLevelFullySupported, + "video/3gpp": event.CapLevelFullySupported, + "video/webm": supportedIfFFmpeg(), + "video/quicktime": supportedIfFFmpeg(), }, Caption: event.CapLevelFullySupported, MaxCaptionLength: MaxTextLength, diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 3e82b06c..105c1c2d 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -464,13 +464,17 @@ func (mc *MessageConverter) reuploadFileToWhatsApp( switch mime { case "video/mp4", "video/3gpp": // allowed - case "video/webm": - data, err = ffmpeg.ConvertBytes(ctx, data, ".mp4", []string{"-f", "webm"}, []string{ + case "video/webm", "video/quicktime": + sourceFormat := "webm" + if mime == "video/quicktime" { + sourceFormat = "mov" + } + data, err = ffmpeg.ConvertBytes(ctx, data, ".mp4", []string{"-f", sourceFormat}, []string{ "-pix_fmt", "yuv420p", "-c:v", "libx264", "-filter:v", "crop='floor(in_w/2)*2:floor(in_h/2)*2'", }, mime) if err != nil { - return nil, nil, "video/webm", fmt.Errorf("%w (webm to mp4): %w", bridgev2.ErrMediaConvertFailed, err) + return nil, nil, mime, fmt.Errorf("%w (%s to mp4): %w", bridgev2.ErrMediaConvertFailed, sourceFormat, err) } mime = "video/mp4" case "image/gif": From a7e6fe39fb6531ff129487d60eeb67cd37590f4a Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 3 Jul 2025 21:18:13 +0300 Subject: [PATCH 089/486] msgconv/from-whatsapp: sync disappearing message timer from incoming messages even if it's 0 --- go.mod | 2 +- go.sum | 4 ++-- pkg/msgconv/from-whatsapp.go | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index c6aa440b..e74c3084 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( golang.org/x/sync v0.15.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.2-0.20250617163829-26da46dbbf6e + maunium.net/go/mautrix v0.24.2-0.20250703182219-b62535edaa57 ) require ( diff --git a/go.sum b/go.sum index 00109dc7..9c6cce28 100644 --- a/go.sum +++ b/go.sum @@ -101,5 +101,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.24.2-0.20250617163829-26da46dbbf6e h1:Y8kbRpPcKMZn2gIjUFd15xzMB5GJ2bS6ZcOfvlx4KnE= -maunium.net/go/mautrix v0.24.2-0.20250617163829-26da46dbbf6e/go.mod h1:Xy6o+pXmbqmgWsUWh15EQ1eozjC+k/VT/7kloByv9PI= +maunium.net/go/mautrix v0.24.2-0.20250703182219-b62535edaa57 h1:z7/37EKswVuTiW26dF6lkPgf60JtKvxsOb0CfWrMeBI= +maunium.net/go/mautrix v0.24.2-0.20250703182219-b62535edaa57/go.mod h1:Xy6o+pXmbqmgWsUWh15EQ1eozjC+k/VT/7kloByv9PI= diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 7ee11ee3..11ef09ad 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -225,9 +225,9 @@ func (mc *MessageConverter) ToMatrix( if contextInfo.GetExpiration() > 0 { cm.Disappear.Timer = time.Duration(contextInfo.GetExpiration()) * time.Second cm.Disappear.Type = database.DisappearingTypeAfterRead - if portal.Disappear.Timer != cm.Disappear.Timer && portal.Metadata.(*waid.PortalMetadata).DisappearingTimerSetAt < contextInfo.GetEphemeralSettingTimestamp() { - portal.UpdateDisappearingSetting(ctx, cm.Disappear, intent, info.Timestamp, true, true) - } + } + if portal.Disappear.Timer != cm.Disappear.Timer && portal.Metadata.(*waid.PortalMetadata).DisappearingTimerSetAt < contextInfo.GetEphemeralSettingTimestamp() { + portal.UpdateDisappearingSetting(ctx, cm.Disappear, intent, info.Timestamp, true, true) } if contextInfo.GetStanzaID() != "" { pcp, _ := types.ParseJID(contextInfo.GetParticipant()) From 3e96e7158fcf7a8d7193a8c5b7c610450c070fff Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 3 Jul 2025 21:44:15 +0300 Subject: [PATCH 090/486] id: fill sender login ID properly in LID event senders --- pkg/connector/backfill.go | 4 +-- pkg/connector/chatinfo.go | 38 ++++++++++----------- pkg/connector/commands.go | 2 +- pkg/connector/events.go | 6 ++-- pkg/connector/handlewhatsapp.go | 58 ++++++++++++++++----------------- pkg/connector/id.go | 22 +++++++++++-- pkg/connector/startchat.go | 8 ++--- 7 files changed, 78 insertions(+), 60 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 9a982eaa..c0359cf3 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -482,7 +482,7 @@ func (wa *WhatsAppClient) convertHistorySyncMessage( intent := wa.Main.Bridge.Bot wrapped := &bridgev2.BackfillMessage{ ConvertedMessage: wa.Main.MsgConv.ToMatrix(ctx, portal, wa.Client, intent, msg, info, isViewOnce, nil), - Sender: wa.makeEventSender(info.Sender), + 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)), Timestamp: info.Timestamp, @@ -505,7 +505,7 @@ func (wa *WhatsAppClient) convertHistorySyncMessage( wrapped.Reactions[i] = &bridgev2.BackfillReaction{ TargetPart: ptr.Ptr(networkid.PartID("")), Timestamp: time.UnixMilli(reaction.GetSenderTimestampMS()), - Sender: wa.makeEventSender(sender), + Sender: wa.makeEventSender(ctx, sender), Emoji: reaction.GetText(), } } diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 1c984c03..ef52e8d3 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -32,10 +32,10 @@ func (wa *WhatsAppClient) GetChatInfo(ctx context.Context, portal *bridgev2.Port func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, conv *wadb.Conversation) (wrapped *bridgev2.ChatInfo, err error) { switch portalJID.Server { case types.DefaultUserServer, types.HiddenUserServer, types.BotServer: - wrapped = wa.wrapDMInfo(portalJID) + wrapped = wa.wrapDMInfo(ctx, portalJID) case types.BroadcastServer: if portalJID == types.StatusBroadcastJID { - wrapped = wa.wrapStatusBroadcastInfo() + wrapped = wa.wrapStatusBroadcastInfo(ctx) } else { return nil, fmt.Errorf("broadcast list bridging is currently not supported") } @@ -44,14 +44,14 @@ func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, if err != nil { return nil, err } - wrapped = wa.wrapGroupInfo(info) + wrapped = wa.wrapGroupInfo(ctx, info) wrapped.ExtraUpdates = bridgev2.MergeExtraUpdaters(wrapped.ExtraUpdates, updatePortalLastSyncAt) case types.NewsletterServer: info, err := wa.Client.GetNewsletterInfo(portalJID) if err != nil { return nil, err } - wrapped = wa.wrapNewsletterInfo(info) + wrapped = wa.wrapNewsletterInfo(ctx, info) default: return nil, fmt.Errorf("unsupported server %s", portalJID.Server) } @@ -138,7 +138,7 @@ const UnnamedBroadcastName = "Unnamed broadcast list" const PrivateChatTopic = "WhatsApp private chat" const BotChatTopic = "WhatsApp chat with a bot" -func (wa *WhatsAppClient) wrapDMInfo(jid types.JID) *bridgev2.ChatInfo { +func (wa *WhatsAppClient) wrapDMInfo(ctx context.Context, jid types.JID) *bridgev2.ChatInfo { info := &bridgev2.ChatInfo{ Topic: ptr.Ptr(PrivateChatTopic), Members: &bridgev2.ChatMemberList{ @@ -146,8 +146,8 @@ func (wa *WhatsAppClient) wrapDMInfo(jid types.JID) *bridgev2.ChatInfo { TotalMemberCount: 2, OtherUserID: waid.MakeUserID(jid), MemberMap: map[networkid.UserID]bridgev2.ChatMember{ - waid.MakeUserID(jid): {EventSender: wa.makeEventSender(jid)}, - waid.MakeUserID(wa.JID): {EventSender: wa.makeEventSender(wa.JID)}, + waid.MakeUserID(jid): {EventSender: wa.makeEventSender(ctx, jid)}, + waid.MakeUserID(wa.JID): {EventSender: wa.makeEventSender(ctx, wa.JID)}, }, PowerLevels: nil, }, @@ -166,7 +166,7 @@ func (wa *WhatsAppClient) wrapDMInfo(jid types.JID) *bridgev2.ChatInfo { return info } -func (wa *WhatsAppClient) wrapStatusBroadcastInfo() *bridgev2.ChatInfo { +func (wa *WhatsAppClient) wrapStatusBroadcastInfo(ctx context.Context) *bridgev2.ChatInfo { userLocal := &bridgev2.UserLocalPortalInfo{} if wa.Main.Config.MuteStatusBroadcast { userLocal.MutedUntil = ptr.Ptr(event.MutedForever) @@ -180,7 +180,7 @@ func (wa *WhatsAppClient) wrapStatusBroadcastInfo() *bridgev2.ChatInfo { Members: &bridgev2.ChatMemberList{ IsFull: false, MemberMap: map[networkid.UserID]bridgev2.ChatMember{ - waid.MakeUserID(wa.JID): {EventSender: wa.makeEventSender(wa.JID)}, + waid.MakeUserID(wa.JID): {EventSender: wa.makeEventSender(ctx, wa.JID)}, }, }, Type: ptr.Ptr(database.RoomTypeDefault), @@ -218,7 +218,7 @@ func setAddressingMode(mode types.AddressingMode) bridgev2.ExtraUpdater[*bridgev } } -func (wa *WhatsAppClient) wrapGroupInfo(info *types.GroupInfo) *bridgev2.ChatInfo { +func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupInfo) *bridgev2.ChatInfo { sendEventPL := defaultPL if info.IsAnnounce && !info.IsDefaultSubGroup { sendEventPL = adminPL @@ -262,7 +262,7 @@ func (wa *WhatsAppClient) wrapGroupInfo(info *types.GroupInfo) *bridgev2.ChatInf } for _, pcp := range info.Participants { member := bridgev2.ChatMember{ - EventSender: wa.makeEventSender(pcp.JID), + EventSender: wa.makeEventSender(ctx, pcp.JID), Membership: event.MembershipJoin, } if pcp.IsSuperAdmin { @@ -299,7 +299,7 @@ func (wa *WhatsAppClient) wrapGroupInfo(info *types.GroupInfo) *bridgev2.ChatInf return wrapped } -func (wa *WhatsAppClient) wrapGroupInfoChange(evt *events.GroupInfo) *bridgev2.ChatInfoChange { +func (wa *WhatsAppClient) wrapGroupInfoChange(ctx context.Context, evt *events.GroupInfo) *bridgev2.ChatInfoChange { var changes *bridgev2.ChatInfo if evt.Name != nil || evt.Topic != nil || evt.Ephemeral != nil || evt.Unlink != nil || evt.Link != nil { changes = &bridgev2.ChatInfo{} @@ -333,24 +333,24 @@ func (wa *WhatsAppClient) wrapGroupInfoChange(evt *events.GroupInfo) *bridgev2.C } for _, userID := range evt.Join { memberChanges.MemberMap[waid.MakeUserID(userID)] = bridgev2.ChatMember{ - EventSender: wa.makeEventSender(userID), + EventSender: wa.makeEventSender(ctx, userID), } } for _, userID := range evt.Promote { memberChanges.MemberMap[waid.MakeUserID(userID)] = bridgev2.ChatMember{ - EventSender: wa.makeEventSender(userID), + EventSender: wa.makeEventSender(ctx, userID), PowerLevel: ptr.Ptr(adminPL), } } for _, userID := range evt.Demote { memberChanges.MemberMap[waid.MakeUserID(userID)] = bridgev2.ChatMember{ - EventSender: wa.makeEventSender(userID), + EventSender: wa.makeEventSender(ctx, userID), PowerLevel: ptr.Ptr(defaultPL), } } for _, userID := range evt.Leave { memberChanges.MemberMap[waid.MakeUserID(userID)] = bridgev2.ChatMember{ - EventSender: wa.makeEventSender(userID), + EventSender: wa.makeEventSender(ctx, userID), Membership: event.MembershipLeave, } } @@ -425,7 +425,7 @@ func (wa *WhatsAppClient) makePortalAvatarFetcher(avatarID string, sender types. } var evtSender bridgev2.EventSender if !sender.IsEmpty() { - evtSender = wa.makeEventSender(sender) + evtSender = wa.makeEventSender(ctx, sender) } senderIntent, ok := portal.GetIntentFor(ctx, evtSender, wa.UserLogin, bridgev2.RemoteEventChatInfoChange) if !ok { @@ -436,7 +436,7 @@ func (wa *WhatsAppClient) makePortalAvatarFetcher(avatarID string, sender types. } } -func (wa *WhatsAppClient) wrapNewsletterInfo(info *types.NewsletterMetadata) *bridgev2.ChatInfo { +func (wa *WhatsAppClient) wrapNewsletterInfo(ctx context.Context, info *types.NewsletterMetadata) *bridgev2.ChatInfo { ownPowerLevel := defaultPL var mutedUntil *time.Time if info.ViewerMeta != nil { @@ -485,7 +485,7 @@ func (wa *WhatsAppClient) wrapNewsletterInfo(info *types.NewsletterMetadata) *br TotalMemberCount: info.ThreadMeta.SubscriberCount, MemberMap: map[networkid.UserID]bridgev2.ChatMember{ waid.MakeUserID(wa.JID): { - EventSender: wa.makeEventSender(wa.JID), + EventSender: wa.makeEventSender(ctx, wa.JID), PowerLevel: &ownPowerLevel, }, }, diff --git a/pkg/connector/commands.go b/pkg/connector/commands.go index 8e2ddf01..3017b157 100644 --- a/pkg/connector/commands.go +++ b/pkg/connector/commands.go @@ -120,7 +120,7 @@ func fnSync(ce *commands.Event) { return } for _, group := range groups { - wrapped := wa.wrapGroupInfo(group) + wrapped := wa.wrapGroupInfo(ce.Ctx, group) wrapped.ExtraUpdates = bridgev2.MergeExtraUpdaters(wrapped.ExtraUpdates, updatePortalLastSyncAt) wa.addExtrasToWrapped(ce.Ctx, group.JID, wrapped, nil) login.QueueRemoteEvent(&simplevent.ChatResync{ diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 31a2a3bc..035b2d56 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -73,7 +73,7 @@ func (evt *MessageInfoWrapper) GetTimestamp() time.Time { } func (evt *MessageInfoWrapper) GetSender() bridgev2.EventSender { - return evt.wa.makeEventSender(evt.Info.Sender) + return evt.wa.makeEventSender(evt.wa.Main.Bridge.BackgroundCtx, evt.Info.Sender) } func (evt *MessageInfoWrapper) GetID() networkid.MessageID { @@ -149,7 +149,7 @@ func (evt *WAMessageEvent) PreHandle(ctx context.Context, portal *bridgev2.Porta return } log.Info().Msg("Resyncing group members as it appears to have switched to LID addressing mode") - portal.UpdateInfo(ctx, evt.wa.wrapGroupInfo(info), evt.wa.UserLogin, nil, time.Time{}) + portal.UpdateInfo(ctx, evt.wa.wrapGroupInfo(ctx, info), evt.wa.UserLogin, nil, time.Time{}) log.Debug().Msg("Finished resyncing after LID change") } @@ -386,7 +386,7 @@ func (evt *WAMediaRetry) getRealSender() types.JID { } func (evt *WAMediaRetry) GetSender() bridgev2.EventSender { - return evt.wa.makeEventSender(evt.getRealSender()) + return evt.wa.makeEventSender(evt.wa.Main.Bridge.BackgroundCtx, evt.getRealSender()) } func (evt *WAMediaRetry) GetTargetMessage() networkid.MessageID { diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 3a9d5f2a..bd3f8e34 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -79,22 +79,22 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { case *events.Message: return wa.handleWAMessage(ctx, evt) case *events.Receipt: - return wa.handleWAReceipt(evt) + return wa.handleWAReceipt(ctx, evt) case *events.ChatPresence: - wa.handleWAChatPresence(evt) + wa.handleWAChatPresence(ctx, evt) case *events.UndecryptableMessage: return wa.handleWAUndecryptableMessage(evt) case *events.CallOffer: - wa.handleWACallStart(evt.CallCreator, evt.CallID, "", evt.Timestamp) + wa.handleWACallStart(ctx, evt.CallCreator, evt.CallID, "", evt.Timestamp) case *events.CallOfferNotice: - wa.handleWACallStart(evt.CallCreator, evt.CallID, evt.Type, evt.Timestamp) + wa.handleWACallStart(ctx, evt.CallCreator, evt.CallID, evt.Type, evt.Timestamp) case *events.CallTerminate, *events.CallRelayLatency, *events.CallAccept, *events.UnknownCallEvent: // ignore case *events.IdentityChange: - wa.handleWAIdentityChange(evt) + wa.handleWAIdentityChange(ctx, evt) case *events.MarkChatAsRead: - wa.handleWAMarkChatAsRead(evt) + wa.handleWAMarkChatAsRead(ctx, evt) case *events.DeleteForMe: wa.handleWADeleteForMe(evt) case *events.DeleteChat: @@ -115,15 +115,15 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { wa.UserLogin.QueueRemoteEvent(&WAMediaRetry{MediaRetry: evt, wa: wa}) case *events.GroupInfo: - wa.handleWAGroupInfoChange(evt) + wa.handleWAGroupInfoChange(ctx, evt) case *events.JoinedGroup: - wa.handleWAJoinedGroup(evt) + wa.handleWAJoinedGroup(ctx, evt) case *events.NewsletterJoin: - wa.handleWANewsletterJoin(evt) + wa.handleWANewsletterJoin(ctx, evt) case *events.NewsletterLeave: wa.handleWANewsletterLeave(evt) case *events.Picture: - go wa.handleWAPictureUpdate(evt) + go wa.handleWAPictureUpdate(ctx, evt) case *events.AppStateSyncComplete: if len(wa.GetStore().PushName) > 0 && evt.Name == appstate.WAPatchCriticalBlock { @@ -345,7 +345,7 @@ func (wa *WhatsAppClient) handleWAUndecryptableMessage(evt *events.Undecryptable return res.Success } -func (wa *WhatsAppClient) handleWAReceipt(evt *events.Receipt) (success bool) { +func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Receipt) (success bool) { if evt.Chat.Server == types.HiddenUserServer && evt.Sender.ToNonAD() == evt.Chat && evt.SenderAlt.Server == types.DefaultUserServer { wa.UserLogin.Log.Debug(). Stringer("lid", evt.Sender). @@ -393,7 +393,7 @@ func (wa *WhatsAppClient) handleWAReceipt(evt *events.Receipt) (success bool) { EventMeta: simplevent.EventMeta{ Type: evtType, PortalKey: wa.makeWAPortalKey(evt.Chat), - Sender: wa.makeEventSender(evt.Sender), + Sender: wa.makeEventSender(ctx, evt.Sender), Timestamp: evt.Timestamp, }, Targets: targets, @@ -401,7 +401,7 @@ func (wa *WhatsAppClient) handleWAReceipt(evt *events.Receipt) (success bool) { return res.Success } -func (wa *WhatsAppClient) handleWAChatPresence(evt *events.ChatPresence) { +func (wa *WhatsAppClient) handleWAChatPresence(ctx context.Context, evt *events.ChatPresence) { typingType := bridgev2.TypingTypeText timeout := 15 * time.Second if evt.Media == types.ChatPresenceMediaAudio { @@ -416,7 +416,7 @@ func (wa *WhatsAppClient) handleWAChatPresence(evt *events.ChatPresence) { Type: bridgev2.RemoteEventTyping, LogContext: nil, PortalKey: wa.makeWAPortalKey(evt.Chat), - Sender: wa.makeEventSender(evt.Sender), + Sender: wa.makeEventSender(ctx, evt.Sender), Timestamp: time.Now(), }, Timeout: timeout, @@ -443,7 +443,7 @@ func (wa *WhatsAppClient) handleWALogout(reason events.ConnectFailureReason, onC const callEventMaxAge = 15 * time.Minute -func (wa *WhatsAppClient) handleWACallStart(sender types.JID, id, callType string, ts time.Time) { +func (wa *WhatsAppClient) handleWACallStart(ctx context.Context, sender types.JID, id, callType string, ts time.Time) { if !wa.Main.Config.CallStartNotices || time.Since(ts) > callEventMaxAge { return } @@ -452,7 +452,7 @@ func (wa *WhatsAppClient) handleWACallStart(sender types.JID, id, callType strin Type: bridgev2.RemoteEventMessage, LogContext: nil, PortalKey: wa.makeWAPortalKey(sender), - Sender: wa.makeEventSender(sender), + Sender: wa.makeEventSender(ctx, sender), CreatePortal: true, Timestamp: ts, }, @@ -478,7 +478,7 @@ func convertCallStart(ctx context.Context, portal *bridgev2.Portal, intent bridg }, nil } -func (wa *WhatsAppClient) handleWAIdentityChange(evt *events.IdentityChange) { +func (wa *WhatsAppClient) handleWAIdentityChange(ctx context.Context, evt *events.IdentityChange) { if !wa.Main.Config.IdentityChangeNotices { return } @@ -487,7 +487,7 @@ func (wa *WhatsAppClient) handleWAIdentityChange(evt *events.IdentityChange) { Type: bridgev2.RemoteEventMessage, LogContext: nil, PortalKey: wa.makeWAPortalKey(evt.JID), - Sender: wa.makeEventSender(evt.JID), + Sender: wa.makeEventSender(ctx, evt.JID), CreatePortal: false, Timestamp: evt.Timestamp, }, @@ -540,12 +540,12 @@ func (wa *WhatsAppClient) handleWADeleteForMe(evt *events.DeleteForMe) { }) } -func (wa *WhatsAppClient) handleWAMarkChatAsRead(evt *events.MarkChatAsRead) { +func (wa *WhatsAppClient) handleWAMarkChatAsRead(ctx context.Context, evt *events.MarkChatAsRead) { wa.UserLogin.QueueRemoteEvent(&simplevent.Receipt{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventReadReceipt, PortalKey: wa.makeWAPortalKey(evt.JID), - Sender: wa.makeEventSender(wa.JID), + Sender: wa.makeEventSender(ctx, wa.JID), Timestamp: evt.Timestamp, }, ReadUpTo: evt.Timestamp, @@ -578,7 +578,7 @@ func (wa *WhatsAppClient) syncGhost(jid types.JID, reason string, pictureID *str go wa.syncRemoteProfile(ctx, ghost) } -func (wa *WhatsAppClient) handleWAPictureUpdate(evt *events.Picture) { +func (wa *WhatsAppClient) handleWAPictureUpdate(ctx context.Context, evt *events.Picture) { if evt.JID.Server == types.DefaultUserServer || evt.JID.Server == types.BotServer { wa.syncGhost(evt.JID, "picture event", &evt.PictureID) } else { @@ -599,7 +599,7 @@ func (wa *WhatsAppClient) handleWAPictureUpdate(evt *events.Picture) { Bool("remove_picture", evt.Remove) }, PortalKey: wa.makeWAPortalKey(evt.JID), - Sender: wa.makeEventSender(evt.Author), + Sender: wa.makeEventSender(ctx, evt.Author), Timestamp: evt.Timestamp, }, ChatInfoChange: &bridgev2.ChatInfoChange{ @@ -609,7 +609,7 @@ func (wa *WhatsAppClient) handleWAPictureUpdate(evt *events.Picture) { } } -func (wa *WhatsAppClient) handleWAGroupInfoChange(evt *events.GroupInfo) { +func (wa *WhatsAppClient) handleWAGroupInfoChange(ctx context.Context, evt *events.GroupInfo) { eventMeta := simplevent.EventMeta{ Type: bridgev2.RemoteEventChatInfoChange, LogContext: nil, @@ -618,7 +618,7 @@ func (wa *WhatsAppClient) handleWAGroupInfoChange(evt *events.GroupInfo) { Timestamp: evt.Timestamp, } if evt.Sender != nil { - eventMeta.Sender = wa.makeEventSender(*evt.Sender) + eventMeta.Sender = wa.makeEventSender(ctx, *evt.Sender) } if evt.Delete != nil { eventMeta.Type = bridgev2.RemoteEventChatDelete @@ -626,12 +626,12 @@ func (wa *WhatsAppClient) handleWAGroupInfoChange(evt *events.GroupInfo) { } else { wa.UserLogin.QueueRemoteEvent(&simplevent.ChatInfoChange{ EventMeta: eventMeta, - ChatInfoChange: wa.wrapGroupInfoChange(evt), + ChatInfoChange: wa.wrapGroupInfoChange(ctx, evt), }) } } -func (wa *WhatsAppClient) handleWAJoinedGroup(evt *events.JoinedGroup) { +func (wa *WhatsAppClient) handleWAJoinedGroup(ctx context.Context, evt *events.JoinedGroup) { wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &simplevent.ChatResync{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatResync, @@ -639,11 +639,11 @@ func (wa *WhatsAppClient) handleWAJoinedGroup(evt *events.JoinedGroup) { PortalKey: wa.makeWAPortalKey(evt.JID), CreatePortal: true, }, - ChatInfo: wa.wrapGroupInfo(&evt.GroupInfo), + ChatInfo: wa.wrapGroupInfo(ctx, &evt.GroupInfo), }) } -func (wa *WhatsAppClient) handleWANewsletterJoin(evt *events.NewsletterJoin) { +func (wa *WhatsAppClient) handleWANewsletterJoin(ctx context.Context, evt *events.NewsletterJoin) { wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &simplevent.ChatResync{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatResync, @@ -651,7 +651,7 @@ func (wa *WhatsAppClient) handleWANewsletterJoin(evt *events.NewsletterJoin) { PortalKey: wa.makeWAPortalKey(evt.ID), CreatePortal: true, }, - ChatInfo: wa.wrapNewsletterInfo(&evt.NewsletterMetadata), + ChatInfo: wa.wrapNewsletterInfo(ctx, &evt.NewsletterMetadata), }) } diff --git a/pkg/connector/id.go b/pkg/connector/id.go index ea4e4c8d..e956e590 100644 --- a/pkg/connector/id.go +++ b/pkg/connector/id.go @@ -1,6 +1,9 @@ package connector import ( + "context" + + "github.com/rs/zerolog" "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow/proto/waCommon" "go.mau.fi/whatsmeow/types" @@ -25,15 +28,30 @@ func (wa *WhatsAppClient) makeWAPortalKey(chatJID types.JID) networkid.PortalKey return key } -func (wa *WhatsAppClient) makeEventSender(id types.JID) bridgev2.EventSender { +func (wa *WhatsAppClient) makeEventSender(ctx context.Context, id types.JID) bridgev2.EventSender { if id.Server == types.NewsletterServer { // Send as bot return bridgev2.EventSender{} } + var senderLoginJID types.JID + if wa.Main.Bridge.Config.SplitPortals { + // no need for sender login ID + } else if id.Server == types.DefaultUserServer { + senderLoginJID = id + } else if id.Server == types.HiddenUserServer { + pn, err := wa.Device.LIDs.GetPNForLID(ctx, id) + if err != nil { + zerolog.Ctx(ctx).Err(err). + Stringer("lid", id). + Msg("Failed to get phone number for LID to make event sender") + } else if !pn.IsEmpty() { + senderLoginJID = pn + } + } return bridgev2.EventSender{ IsFromMe: id.User == wa.GetStore().GetJID().User || id.User == wa.GetStore().GetLID().User, Sender: waid.MakeUserID(id), - SenderLogin: waid.MakeUserLoginID(id), // TODO add support for lids here by looking up pn jid + SenderLogin: waid.MakeUserLoginID(senderLoginJID), } } diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index 7443ed3d..039fef7b 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -110,14 +110,14 @@ func (wa *WhatsAppClient) startChatLIDToPN(ctx context.Context, jid types.JID) ( return jid, nil } -func (wa *WhatsAppClient) makeCreateChatResponse(jid, origJID types.JID) *bridgev2.CreateChatResponse { +func (wa *WhatsAppClient) makeCreateChatResponse(ctx context.Context, jid, origJID types.JID) *bridgev2.CreateChatResponse { var redirID networkid.UserID if origJID != jid { redirID = waid.MakeUserID(jid) } return &bridgev2.CreateChatResponse{ PortalKey: wa.makeWAPortalKey(jid), - PortalInfo: wa.wrapDMInfo(jid), + PortalInfo: wa.wrapDMInfo(ctx, jid), DMRedirectedTo: redirID, } } @@ -128,7 +128,7 @@ func (wa *WhatsAppClient) CreateChatWithGhost(ctx context.Context, ghost *bridge if err != nil { return nil, err } - return wa.makeCreateChatResponse(jid, origJID), nil + return wa.makeCreateChatResponse(ctx, jid, origJID), nil } func (wa *WhatsAppClient) ResolveIdentifier(ctx context.Context, identifier string, startChat bool) (*bridgev2.ResolveIdentifierResponse, error) { @@ -148,7 +148,7 @@ func (wa *WhatsAppClient) ResolveIdentifier(ctx context.Context, identifier stri return &bridgev2.ResolveIdentifierResponse{ Ghost: ghost, UserID: waid.MakeUserID(jid), - Chat: wa.makeCreateChatResponse(jid, origJID), + Chat: wa.makeCreateChatResponse(ctx, jid, origJID), }, nil } From 4e5c4ccf0cb31841889df31689f6d4bd9aecbe22 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 9 Jul 2025 15:56:14 +0300 Subject: [PATCH 091/486] handlewhatsapp: fix call notices in groups --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/handlewhatsapp.go | 14 +++++++++----- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index e74c3084..ecbef9a9 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.8 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250701221811-9adf672adc90 + go.mau.fi/whatsmeow v0.0.0-20250709125544-2ec5b51770ba golang.org/x/image v0.28.0 golang.org/x/net v0.41.0 golang.org/x/sync v0.15.0 diff --git a/go.sum b/go.sum index 9c6cce28..93fc89ff 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c= go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= 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-20250701221811-9adf672adc90 h1:K9knC6oQMLPutKwDVnqdgrDEjMJ8D5nREE9Li3dH71k= -go.mau.fi/whatsmeow v0.0.0-20250701221811-9adf672adc90/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= +go.mau.fi/whatsmeow v0.0.0-20250709125544-2ec5b51770ba h1:9dfTlcISkZluN/vHc6WMFJ0qX8WU5EKATtZV8+ovp8E= +go.mau.fi/whatsmeow v0.0.0-20250709125544-2ec5b51770ba/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index bd3f8e34..3db5419a 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -86,9 +86,9 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { return wa.handleWAUndecryptableMessage(evt) case *events.CallOffer: - wa.handleWACallStart(ctx, evt.CallCreator, evt.CallID, "", evt.Timestamp) + wa.handleWACallStart(ctx, evt.GroupJID, evt.CallCreator, evt.CallID, "", evt.Timestamp) case *events.CallOfferNotice: - wa.handleWACallStart(ctx, evt.CallCreator, evt.CallID, evt.Type, evt.Timestamp) + wa.handleWACallStart(ctx, evt.GroupJID, evt.CallCreator, evt.CallID, evt.Type, evt.Timestamp) case *events.CallTerminate, *events.CallRelayLatency, *events.CallAccept, *events.UnknownCallEvent: // ignore case *events.IdentityChange: @@ -443,21 +443,25 @@ func (wa *WhatsAppClient) handleWALogout(reason events.ConnectFailureReason, onC const callEventMaxAge = 15 * time.Minute -func (wa *WhatsAppClient) handleWACallStart(ctx context.Context, sender types.JID, id, callType string, ts time.Time) { +func (wa *WhatsAppClient) handleWACallStart(ctx context.Context, group, sender types.JID, id, callType string, ts time.Time) { if !wa.Main.Config.CallStartNotices || time.Since(ts) > callEventMaxAge { return } + chat := group + if chat.IsEmpty() { + chat = sender + } wa.UserLogin.QueueRemoteEvent(&simplevent.Message[string]{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventMessage, LogContext: nil, - PortalKey: wa.makeWAPortalKey(sender), + PortalKey: wa.makeWAPortalKey(chat), Sender: wa.makeEventSender(ctx, sender), CreatePortal: true, Timestamp: ts, }, Data: callType, - ID: waid.MakeFakeMessageID(sender, sender, "call-"+id), + ID: waid.MakeFakeMessageID(chat, sender, "call-"+id), ConvertMessageFunc: convertCallStart, }) } From 6c023f0290c8ad896f79f8976d32da1d395a2654 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 9 Jul 2025 16:37:28 +0300 Subject: [PATCH 092/486] userinfo: fix log if next ghost resync isn't scheduled --- pkg/connector/userinfo.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 848359ca..2f5d8054 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -32,9 +32,13 @@ func (wa *WhatsAppClient) EnqueueGhostResync(ghost *bridgev2.Ghost) { jid := waid.ParseUserID(ghost.ID) if _, exists := wa.resyncQueue[jid]; !exists { wa.resyncQueue[jid] = resyncQueueItem{ghost: ghost} + nextResyncIn := time.Until(wa.nextResync).String() + if wa.nextResync.IsZero() { + nextResyncIn = "never" + } wa.UserLogin.Log.Debug(). Stringer("jid", jid). - Stringer("next_resync_in", time.Until(wa.nextResync)). + Str("next_resync_in", nextResyncIn). Msg("Enqueued resync for ghost") } wa.resyncQueueLock.Unlock() From 3fdd4e035f65ee44187e5085745307906d7a410f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 10 Jul 2025 00:24:15 +0300 Subject: [PATCH 093/486] client: fix logger in whatsmeow background context --- pkg/connector/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index cfd63741..e1b01d32 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -79,7 +79,7 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. w.Client.AutomaticMessageRerequestFromPhone = true w.Client.GetMessageForRetry = w.trackNotFoundRetry w.Client.PreRetryCallback = w.trackFoundRetry - w.Client.BackgroundEventCtx = wa.Bridge.BackgroundCtx + w.Client.BackgroundEventCtx = w.UserLogin.Log.WithContext(wa.Bridge.BackgroundCtx) w.Client.SetForceActiveDeliveryReceipts(wa.Config.ForceActiveDeliveryReceipts) w.Client.InitialAutoReconnect = wa.Config.InitialAutoReconnect } else { @@ -228,7 +228,7 @@ func (wa *WhatsAppClient) ConnectBackground(ctx context.Context, params *bridgev if wa.Client == nil { return bridgev2.ErrNotLoggedIn } - wa.Client.BackgroundEventCtx = wa.Main.Bridge.BackgroundCtx + wa.Client.BackgroundEventCtx = wa.UserLogin.Log.WithContext(wa.Main.Bridge.BackgroundCtx) wa.offlineSyncWaiter = make(chan error) wa.Main.backgroundConnectOnce.Do(wa.Main.onFirstBackgroundConnect) if err := wa.Main.updateProxy(ctx, wa.Client, false); err != nil { From 559a09bb4a71da56ea0b91a0f859a87ea5c28e09 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 10 Jul 2025 00:26:43 +0300 Subject: [PATCH 094/486] 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 ecbef9a9..eb73f546 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.8 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250709125544-2ec5b51770ba + go.mau.fi/whatsmeow v0.0.0-20250709212552-0b8557ee0860 golang.org/x/image v0.28.0 golang.org/x/net v0.41.0 golang.org/x/sync v0.15.0 diff --git a/go.sum b/go.sum index 93fc89ff..0170fc38 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c= go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= 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-20250709125544-2ec5b51770ba h1:9dfTlcISkZluN/vHc6WMFJ0qX8WU5EKATtZV8+ovp8E= -go.mau.fi/whatsmeow v0.0.0-20250709125544-2ec5b51770ba/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= +go.mau.fi/whatsmeow v0.0.0-20250709212552-0b8557ee0860 h1:0AdAzM/QtzgUKiJ2oyVfiSiZq/fIjxadUtepPy2lZwk= +go.mau.fi/whatsmeow v0.0.0-20250709212552-0b8557ee0860/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= From 3c4ee7c17a25ed5bdda399a8664014388f4907d0 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 10 Jul 2025 00:26:51 +0300 Subject: [PATCH 095/486] changelog: update --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17d1e729..3044d45a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# v0.12.3 (unreleased) + +* Further improved support for `@lid` users. +* Added automatic conversion when sending quicktime/mov videos to WhatsApp. +* Fixed disappearing message timer not automatically fixing itself in some cases. +* Fixed call notices being sent to DM portal even if the call was in a group. + # v0.12.2 (2025-06-16) * Improved support for `@lid` users. From d8812597733126b8958fb280f063768ed8283225 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 10 Jul 2025 17:57:31 +0300 Subject: [PATCH 096/486] handlewhatsapp: use simpler queue remote event call --- pkg/connector/backfill.go | 2 +- pkg/connector/handlewhatsapp.go | 14 +++++++------- pkg/connector/userinfo.go | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index c0359cf3..8c29d225 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -338,7 +338,7 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { wg.Done() continue } - wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &simplevent.ChatResync{ + wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatResync, LogContext: func(c zerolog.Context) zerolog.Context { diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 3db5419a..fa4e7e15 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -309,7 +309,7 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa evt.Message = decrypted } } - res := wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &WAMessageEvent{ + res := wa.UserLogin.QueueRemoteEvent(&WAMessageEvent{ MessageInfoWrapper: &MessageInfoWrapper{ Info: evt.Info, wa: wa, @@ -335,7 +335,7 @@ func (wa *WhatsAppClient) handleWAUndecryptableMessage(evt *events.Undecryptable if evt.Info.Chat == types.StatusBroadcastJID && !wa.Main.Config.EnableStatusBroadcast { return true } - res := wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &WAUndecryptableMessage{ + res := wa.UserLogin.QueueRemoteEvent(&WAUndecryptableMessage{ MessageInfoWrapper: &MessageInfoWrapper{ Info: evt.Info, wa: wa, @@ -389,7 +389,7 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei for i, id := range evt.MessageIDs { targets[i] = waid.MakeMessageID(evt.Chat, messageSender, id) } - res := wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &simplevent.Receipt{ + res := wa.UserLogin.QueueRemoteEvent(&simplevent.Receipt{ EventMeta: simplevent.EventMeta{ Type: evtType, PortalKey: wa.makeWAPortalKey(evt.Chat), @@ -411,7 +411,7 @@ func (wa *WhatsAppClient) handleWAChatPresence(ctx context.Context, evt *events. timeout = 0 } - wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &simplevent.Typing{ + wa.UserLogin.QueueRemoteEvent(&simplevent.Typing{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventTyping, LogContext: nil, @@ -636,7 +636,7 @@ func (wa *WhatsAppClient) handleWAGroupInfoChange(ctx context.Context, evt *even } func (wa *WhatsAppClient) handleWAJoinedGroup(ctx context.Context, evt *events.JoinedGroup) { - wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &simplevent.ChatResync{ + wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatResync, LogContext: nil, @@ -648,7 +648,7 @@ func (wa *WhatsAppClient) handleWAJoinedGroup(ctx context.Context, evt *events.J } func (wa *WhatsAppClient) handleWANewsletterJoin(ctx context.Context, evt *events.NewsletterJoin) { - wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &simplevent.ChatResync{ + wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatResync, LogContext: nil, @@ -660,7 +660,7 @@ func (wa *WhatsAppClient) handleWANewsletterJoin(ctx context.Context, evt *event } func (wa *WhatsAppClient) handleWANewsletterLeave(evt *events.NewsletterLeave) { - wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &simplevent.ChatDelete{ + wa.UserLogin.QueueRemoteEvent(&simplevent.ChatDelete{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatDelete, LogContext: nil, diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 2f5d8054..4d7d9825 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -128,7 +128,7 @@ func (wa *WhatsAppClient) doGhostResync(ctx context.Context, queue map[types.JID } } for _, portal := range portals { - wa.Main.Bridge.QueueRemoteEvent(wa.UserLogin, &simplevent.ChatResync{ + wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatResync, LogContext: func(c zerolog.Context) zerolog.Context { From aa3bb1ea5ea98b61039c722a60fdaea289100e15 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 10 Jul 2025 18:03:16 +0300 Subject: [PATCH 097/486] handlewhatsapp,backfill: propagate QueueRemoteEvent results in more places --- pkg/connector/backfill.go | 6 +- pkg/connector/handlewhatsapp.go | 116 ++++++++++++++++---------------- 2 files changed, 64 insertions(+), 58 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 8c29d225..81346b96 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -338,7 +338,7 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { wg.Done() continue } - wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{ + res := wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatResync, LogContext: func(c zerolog.Context) zerolog.Context { @@ -359,6 +359,10 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { ChatInfo: wrappedInfo, LatestMessageTS: conv.LastMessageTimestamp, }) + if !res.Success { + log.Debug().Msg("Cancelling history sync portal creation loop") + return + } } log.Info().Int("conversation_count", len(conversations)).Msg("Finished creating portals from history sync") go func() { diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index fa4e7e15..37943bda 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -75,36 +75,37 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { log := wa.UserLogin.Log ctx := log.WithContext(wa.Main.Bridge.BackgroundCtx) + success = true switch evt := rawEvt.(type) { case *events.Message: - return wa.handleWAMessage(ctx, evt) + success = wa.handleWAMessage(ctx, evt) case *events.Receipt: - return wa.handleWAReceipt(ctx, evt) + success = wa.handleWAReceipt(ctx, evt) case *events.ChatPresence: wa.handleWAChatPresence(ctx, evt) case *events.UndecryptableMessage: - return wa.handleWAUndecryptableMessage(evt) + success = wa.handleWAUndecryptableMessage(evt) case *events.CallOffer: - wa.handleWACallStart(ctx, evt.GroupJID, evt.CallCreator, evt.CallID, "", evt.Timestamp) + success = wa.handleWACallStart(ctx, evt.GroupJID, evt.CallCreator, evt.CallID, "", evt.Timestamp) case *events.CallOfferNotice: - wa.handleWACallStart(ctx, evt.GroupJID, evt.CallCreator, evt.CallID, evt.Type, evt.Timestamp) + success = wa.handleWACallStart(ctx, evt.GroupJID, evt.CallCreator, evt.CallID, evt.Type, evt.Timestamp) case *events.CallTerminate, *events.CallRelayLatency, *events.CallAccept, *events.UnknownCallEvent: // ignore case *events.IdentityChange: wa.handleWAIdentityChange(ctx, evt) case *events.MarkChatAsRead: - wa.handleWAMarkChatAsRead(ctx, evt) + success = wa.handleWAMarkChatAsRead(ctx, evt) case *events.DeleteForMe: - wa.handleWADeleteForMe(evt) + success = wa.handleWADeleteForMe(evt) case *events.DeleteChat: - wa.handleWADeleteChat(evt) + success = wa.handleWADeleteChat(evt) case *events.Mute: - wa.handleWAMute(evt) + success = wa.handleWAMute(evt) case *events.Archive: - wa.handleWAArchive(evt) + success = wa.handleWAArchive(evt) case *events.Pin: - wa.handleWAPin(evt) + success = wa.handleWAPin(evt) case *events.HistorySync: if wa.Main.Bridge.Config.Backfill.Enabled { @@ -112,18 +113,18 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { } case *events.MediaRetry: wa.phoneSeen(evt.Timestamp) - wa.UserLogin.QueueRemoteEvent(&WAMediaRetry{MediaRetry: evt, wa: wa}) + success = wa.UserLogin.QueueRemoteEvent(&WAMediaRetry{MediaRetry: evt, wa: wa}).Success case *events.GroupInfo: - wa.handleWAGroupInfoChange(ctx, evt) + success = wa.handleWAGroupInfoChange(ctx, evt) case *events.JoinedGroup: - wa.handleWAJoinedGroup(ctx, evt) + success = wa.handleWAJoinedGroup(ctx, evt) case *events.NewsletterJoin: - wa.handleWANewsletterJoin(ctx, evt) + success = wa.handleWANewsletterJoin(ctx, evt) case *events.NewsletterLeave: - wa.handleWANewsletterLeave(evt) + success = wa.handleWANewsletterLeave(evt) case *events.Picture: - go wa.handleWAPictureUpdate(ctx, evt) + success = wa.handleWAPictureUpdate(ctx, evt) case *events.AppStateSyncComplete: if len(wa.GetStore().PushName) > 0 && evt.Name == appstate.WAPatchCriticalBlock { @@ -253,7 +254,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { default: log.Debug().Type("event_type", rawEvt).Msg("Unhandled WhatsApp event") } - return true + return } func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Message) (success bool) { @@ -443,15 +444,15 @@ func (wa *WhatsAppClient) handleWALogout(reason events.ConnectFailureReason, onC const callEventMaxAge = 15 * time.Minute -func (wa *WhatsAppClient) handleWACallStart(ctx context.Context, group, sender types.JID, id, callType string, ts time.Time) { +func (wa *WhatsAppClient) handleWACallStart(ctx context.Context, group, sender types.JID, id, callType string, ts time.Time) bool { if !wa.Main.Config.CallStartNotices || time.Since(ts) > callEventMaxAge { - return + return true } chat := group if chat.IsEmpty() { chat = sender } - wa.UserLogin.QueueRemoteEvent(&simplevent.Message[string]{ + return wa.UserLogin.QueueRemoteEvent(&simplevent.Message[string]{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventMessage, LogContext: nil, @@ -463,7 +464,7 @@ func (wa *WhatsAppClient) handleWACallStart(ctx context.Context, group, sender t Data: callType, ID: waid.MakeFakeMessageID(chat, sender, "call-"+id), ConvertMessageFunc: convertCallStart, - }) + }).Success } func convertCallStart(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, callType string) (*bridgev2.ConvertedMessage, error) { @@ -521,19 +522,19 @@ func convertIdentityChange(ctx context.Context, portal *bridgev2.Portal, intent }, nil } -func (wa *WhatsAppClient) handleWADeleteChat(evt *events.DeleteChat) { - wa.UserLogin.QueueRemoteEvent(&simplevent.ChatDelete{ +func (wa *WhatsAppClient) handleWADeleteChat(evt *events.DeleteChat) bool { + return wa.UserLogin.QueueRemoteEvent(&simplevent.ChatDelete{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatDelete, PortalKey: wa.makeWAPortalKey(evt.JID), Timestamp: evt.Timestamp, }, OnlyForMe: true, - }) + }).Success } -func (wa *WhatsAppClient) handleWADeleteForMe(evt *events.DeleteForMe) { - wa.UserLogin.QueueRemoteEvent(&simplevent.MessageRemove{ +func (wa *WhatsAppClient) handleWADeleteForMe(evt *events.DeleteForMe) bool { + return wa.UserLogin.QueueRemoteEvent(&simplevent.MessageRemove{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventMessageRemove, PortalKey: wa.makeWAPortalKey(evt.ChatJID), @@ -541,11 +542,11 @@ func (wa *WhatsAppClient) handleWADeleteForMe(evt *events.DeleteForMe) { }, TargetMessage: waid.MakeMessageID(evt.ChatJID, evt.SenderJID, evt.MessageID), OnlyForMe: true, - }) + }).Success } -func (wa *WhatsAppClient) handleWAMarkChatAsRead(ctx context.Context, evt *events.MarkChatAsRead) { - wa.UserLogin.QueueRemoteEvent(&simplevent.Receipt{ +func (wa *WhatsAppClient) handleWAMarkChatAsRead(ctx context.Context, evt *events.MarkChatAsRead) bool { + return wa.UserLogin.QueueRemoteEvent(&simplevent.Receipt{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventReadReceipt, PortalKey: wa.makeWAPortalKey(evt.JID), @@ -553,7 +554,7 @@ func (wa *WhatsAppClient) handleWAMarkChatAsRead(ctx context.Context, evt *event Timestamp: evt.Timestamp, }, ReadUpTo: evt.Timestamp, - }) + }).Success } func (wa *WhatsAppClient) syncGhost(jid types.JID, reason string, pictureID *string) { @@ -582,9 +583,10 @@ func (wa *WhatsAppClient) syncGhost(jid types.JID, reason string, pictureID *str go wa.syncRemoteProfile(ctx, ghost) } -func (wa *WhatsAppClient) handleWAPictureUpdate(ctx context.Context, evt *events.Picture) { +func (wa *WhatsAppClient) handleWAPictureUpdate(ctx context.Context, evt *events.Picture) bool { if evt.JID.Server == types.DefaultUserServer || evt.JID.Server == types.BotServer { - wa.syncGhost(evt.JID, "picture event", &evt.PictureID) + go wa.syncGhost(evt.JID, "picture event", &evt.PictureID) + return true } else { var changes bridgev2.ChatInfo if evt.Remove { @@ -592,7 +594,7 @@ func (wa *WhatsAppClient) handleWAPictureUpdate(ctx context.Context, evt *events } else { changes.ExtraUpdates = wa.makePortalAvatarFetcher(evt.PictureID, evt.Author, evt.Timestamp) } - wa.UserLogin.QueueRemoteEvent(&simplevent.ChatInfoChange{ + return wa.UserLogin.QueueRemoteEvent(&simplevent.ChatInfoChange{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatInfoChange, LogContext: func(c zerolog.Context) zerolog.Context { @@ -609,11 +611,11 @@ func (wa *WhatsAppClient) handleWAPictureUpdate(ctx context.Context, evt *events ChatInfoChange: &bridgev2.ChatInfoChange{ ChatInfo: &changes, }, - }) + }).Success } } -func (wa *WhatsAppClient) handleWAGroupInfoChange(ctx context.Context, evt *events.GroupInfo) { +func (wa *WhatsAppClient) handleWAGroupInfoChange(ctx context.Context, evt *events.GroupInfo) bool { eventMeta := simplevent.EventMeta{ Type: bridgev2.RemoteEventChatInfoChange, LogContext: nil, @@ -626,17 +628,17 @@ func (wa *WhatsAppClient) handleWAGroupInfoChange(ctx context.Context, evt *even } if evt.Delete != nil { eventMeta.Type = bridgev2.RemoteEventChatDelete - wa.UserLogin.QueueRemoteEvent(&simplevent.ChatDelete{EventMeta: eventMeta}) + return wa.UserLogin.QueueRemoteEvent(&simplevent.ChatDelete{EventMeta: eventMeta}).Success } else { - wa.UserLogin.QueueRemoteEvent(&simplevent.ChatInfoChange{ + return wa.UserLogin.QueueRemoteEvent(&simplevent.ChatInfoChange{ EventMeta: eventMeta, ChatInfoChange: wa.wrapGroupInfoChange(ctx, evt), - }) + }).Success } } -func (wa *WhatsAppClient) handleWAJoinedGroup(ctx context.Context, evt *events.JoinedGroup) { - wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{ +func (wa *WhatsAppClient) handleWAJoinedGroup(ctx context.Context, evt *events.JoinedGroup) bool { + return wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatResync, LogContext: nil, @@ -644,11 +646,11 @@ func (wa *WhatsAppClient) handleWAJoinedGroup(ctx context.Context, evt *events.J CreatePortal: true, }, ChatInfo: wa.wrapGroupInfo(ctx, &evt.GroupInfo), - }) + }).Success } -func (wa *WhatsAppClient) handleWANewsletterJoin(ctx context.Context, evt *events.NewsletterJoin) { - wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{ +func (wa *WhatsAppClient) handleWANewsletterJoin(ctx context.Context, evt *events.NewsletterJoin) bool { + return wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatResync, LogContext: nil, @@ -656,22 +658,22 @@ func (wa *WhatsAppClient) handleWANewsletterJoin(ctx context.Context, evt *event CreatePortal: true, }, ChatInfo: wa.wrapNewsletterInfo(ctx, &evt.NewsletterMetadata), - }) + }).Success } -func (wa *WhatsAppClient) handleWANewsletterLeave(evt *events.NewsletterLeave) { - wa.UserLogin.QueueRemoteEvent(&simplevent.ChatDelete{ +func (wa *WhatsAppClient) handleWANewsletterLeave(evt *events.NewsletterLeave) bool { + return wa.UserLogin.QueueRemoteEvent(&simplevent.ChatDelete{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatDelete, LogContext: nil, PortalKey: wa.makeWAPortalKey(evt.ID), }, OnlyForMe: true, - }) + }).Success } -func (wa *WhatsAppClient) handleWAUserLocalPortalInfo(chatJID types.JID, ts time.Time, info *bridgev2.UserLocalPortalInfo) { - wa.UserLogin.QueueRemoteEvent(&simplevent.ChatInfoChange{ +func (wa *WhatsAppClient) handleWAUserLocalPortalInfo(chatJID types.JID, ts time.Time, info *bridgev2.UserLocalPortalInfo) bool { + return wa.UserLogin.QueueRemoteEvent(&simplevent.ChatInfoChange{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatInfoChange, PortalKey: wa.makeWAPortalKey(chatJID), @@ -682,10 +684,10 @@ func (wa *WhatsAppClient) handleWAUserLocalPortalInfo(chatJID types.JID, ts time UserLocal: info, }, }, - }) + }).Success } -func (wa *WhatsAppClient) handleWAMute(evt *events.Mute) { +func (wa *WhatsAppClient) handleWAMute(evt *events.Mute) bool { var mutedUntil time.Time if evt.Action.GetMuted() { mutedUntil = event.MutedForever @@ -695,27 +697,27 @@ func (wa *WhatsAppClient) handleWAMute(evt *events.Mute) { } else { mutedUntil = bridgev2.Unmuted } - wa.handleWAUserLocalPortalInfo(evt.JID, evt.Timestamp, &bridgev2.UserLocalPortalInfo{ + return wa.handleWAUserLocalPortalInfo(evt.JID, evt.Timestamp, &bridgev2.UserLocalPortalInfo{ MutedUntil: &mutedUntil, }) } -func (wa *WhatsAppClient) handleWAArchive(evt *events.Archive) { +func (wa *WhatsAppClient) handleWAArchive(evt *events.Archive) bool { var tag event.RoomTag if evt.Action.GetArchived() { tag = wa.Main.Config.ArchiveTag } - wa.handleWAUserLocalPortalInfo(evt.JID, evt.Timestamp, &bridgev2.UserLocalPortalInfo{ + return wa.handleWAUserLocalPortalInfo(evt.JID, evt.Timestamp, &bridgev2.UserLocalPortalInfo{ Tag: &tag, }) } -func (wa *WhatsAppClient) handleWAPin(evt *events.Pin) { +func (wa *WhatsAppClient) handleWAPin(evt *events.Pin) bool { var tag event.RoomTag if evt.Action.GetPinned() { tag = wa.Main.Config.PinnedTag } - wa.handleWAUserLocalPortalInfo(evt.JID, evt.Timestamp, &bridgev2.UserLocalPortalInfo{ + return wa.handleWAUserLocalPortalInfo(evt.JID, evt.Timestamp, &bridgev2.UserLocalPortalInfo{ Tag: &tag, }) } From 3dd9b894d0519a4471b89674de6e9d991907f704 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 16 Jul 2025 11:44:44 +0300 Subject: [PATCH 098/486] Bump version to v0.12.3 --- CHANGELOG.md | 2 +- cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 20 ++++++++++---------- go.sum | 36 ++++++++++++++++++------------------ 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3044d45a..c9a99834 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# v0.12.3 (unreleased) +# v0.12.3 (2025-07-16) * Further improved support for `@lid` users. * Added automatic conversion when sending quicktime/mov videos to WhatsApp. diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 40170f5b..0ec230fa 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -23,7 +23,7 @@ var m = mxmain.BridgeMain{ Name: "mautrix-whatsapp", URL: "https://github.com/mautrix/whatsapp", Description: "A Matrix-WhatsApp puppeting bridge.", - Version: "0.12.2", + Version: "0.12.3", Connector: c, } diff --git a/go.mod b/go.mod index eb73f546..402d48cc 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module go.mau.fi/mautrix-whatsapp go 1.23.0 -toolchain go1.24.4 +toolchain go1.24.5 require ( github.com/gorilla/mux v1.8.0 @@ -11,13 +11,13 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.8 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250709212552-0b8557ee0860 - golang.org/x/image v0.28.0 - golang.org/x/net v0.41.0 - golang.org/x/sync v0.15.0 + go.mau.fi/whatsmeow v0.0.0-20250716083826-71ea87a455e7 + golang.org/x/image v0.29.0 + golang.org/x/net v0.42.0 + golang.org/x/sync v0.16.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.2-0.20250703182219-b62535edaa57 + maunium.net/go/mautrix v0.24.2 ) require ( @@ -39,10 +39,10 @@ require ( github.com/yuin/goldmark v1.7.12 // indirect go.mau.fi/libsignal v0.2.0 // indirect go.mau.fi/zeroconfig v0.1.3 // indirect - golang.org/x/crypto v0.39.0 // indirect - golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.26.0 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/text v0.27.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 0170fc38..67342ebe 100644 --- a/go.sum +++ b/go.sum @@ -69,27 +69,27 @@ go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c= go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= 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-20250709212552-0b8557ee0860 h1:0AdAzM/QtzgUKiJ2oyVfiSiZq/fIjxadUtepPy2lZwk= -go.mau.fi/whatsmeow v0.0.0-20250709212552-0b8557ee0860/go.mod h1:bEyyFvXlwr/18B2pOkdX1vWAx1+y1NJX+sCXVyw01UA= +go.mau.fi/whatsmeow v0.0.0-20250716083826-71ea87a455e7 h1:qxn0LG8cfPMzzy5Aq2eri+9m2qbpxvMzQdULHgAE3Bk= +go.mau.fi/whatsmeow v0.0.0-20250716083826-71ea87a455e7/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= -golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 h1:bsqhLWFR6G6xiQcb+JoGqdKdRU6WzPWmK8E0jxTjzo4= -golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= -golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE= -golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc= +golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= +golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas= +golang.org/x/image v0.29.0/go.mod h1:RVJROnf3SLK8d26OW91j4FrIHGbsJ8QnbEocVTOWQDA= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -101,5 +101,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.24.2-0.20250703182219-b62535edaa57 h1:z7/37EKswVuTiW26dF6lkPgf60JtKvxsOb0CfWrMeBI= -maunium.net/go/mautrix v0.24.2-0.20250703182219-b62535edaa57/go.mod h1:Xy6o+pXmbqmgWsUWh15EQ1eozjC+k/VT/7kloByv9PI= +maunium.net/go/mautrix v0.24.2 h1:+AVT5kbcA/QuT5svrJKp4ivwoUmz+RRplMp3DnfpheI= +maunium.net/go/mautrix v0.24.2/go.mod h1:1ut900w++eE9by9yqCR2dQdMqwsHwZG5L+1bKB1EvSA= From 1183251621f272c50073518668bd0964eace8ce5 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 17 Jul 2025 11:22:53 +0300 Subject: [PATCH 099/486] .github: update issue templates [skip ci] --- .github/ISSUE_TEMPLATE/bug.md | 10 ++++++---- .github/ISSUE_TEMPLATE/enhancement.md | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index 18862a5d..3703df9e 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -1,14 +1,16 @@ --- name: Bug report about: If something is definitely wrong in the bridge (rather than just a setup issue), - file a bug report. Remember to include relevant logs. -labels: bug + file a bug report. Remember to include relevant logs. Asking in the Matrix room first + is strongly recommended. +type: Bug --- diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md index 264e67ff..a04fe588 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.md +++ b/.github/ISSUE_TEMPLATE/enhancement.md @@ -1,6 +1,6 @@ --- name: Enhancement request about: Submit a feature request or other suggestion -labels: enhancement +type: Feature --- From 0be53e58c63ccd90d93631f12115176ce4be9abf Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 17 Jul 2025 11:42:11 +0300 Subject: [PATCH 100/486] waid/dbmeta: remove unnecessary LID field in user login metadata --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/handlewhatsapp.go | 14 -------------- pkg/connector/login.go | 1 - pkg/waid/dbmeta.go | 1 - 5 files changed, 3 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index 402d48cc..800c7491 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.8 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250716083826-71ea87a455e7 + go.mau.fi/whatsmeow v0.0.0-20250717084138-aecc878ab213 golang.org/x/image v0.29.0 golang.org/x/net v0.42.0 golang.org/x/sync v0.16.0 diff --git a/go.sum b/go.sum index 67342ebe..5b9e23f4 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c= go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= 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-20250716083826-71ea87a455e7 h1:qxn0LG8cfPMzzy5Aq2eri+9m2qbpxvMzQdULHgAE3Bk= -go.mau.fi/whatsmeow v0.0.0-20250716083826-71ea87a455e7/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= +go.mau.fi/whatsmeow v0.0.0-20250717084138-aecc878ab213 h1:CYkW6OUQk0uvzMa3wN7RtGd7H8DxYdYqw/DoFobTK2w= +go.mau.fi/whatsmeow v0.0.0-20250717084138-aecc878ab213/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 37943bda..efd261de 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -169,20 +169,6 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { }() go wa.syncRemoteProfile(log.WithContext(context.Background()), nil) } - meta := wa.UserLogin.Metadata.(*waid.UserLoginMetadata) - if meta.WALID == "" { - meta.WALID = wa.GetStore().GetLID().User - if meta.WALID != "" { - go func() { - err := wa.UserLogin.Save(log.WithContext(context.Background())) - if err != nil { - log.Err(err).Msg("Failed to save user login metadata after updating LID") - } else { - log.Info().Msg("Updated LID in user login metadata") - } - }() - } - } case *events.OfflineSyncPreview: log.Info(). Int("message_count", evt.Messages). diff --git a/pkg/connector/login.go b/pkg/connector/login.go index d0c17180..5e8784e9 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -321,7 +321,6 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { Name: wl.LoginSuccess.BusinessName, }, Metadata: &waid.UserLoginMetadata{ - WALID: wl.LoginSuccess.LID.User, WADeviceID: wl.LoginSuccess.ID.Device, Timezone: wl.Timezone, diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index 4e0cd66c..c67f5871 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -29,7 +29,6 @@ import ( type UserLoginMetadata struct { WADeviceID uint16 `json:"wa_device_id"` - WALID string `json:"wa_lid"` PhoneLastSeen jsontime.Unix `json:"phone_last_seen"` PhoneLastPinged jsontime.Unix `json:"phone_last_pinged"` Timezone string `json:"timezone"` From 825dc7728f6bd8d6d878cf2ea4558cc5450ded10 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 17 Jul 2025 15:13:21 +0300 Subject: [PATCH 101/486] events: force first message after resync to use lid ghost --- pkg/connector/events.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 035b2d56..76eef7a8 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -151,6 +151,13 @@ 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) { From e8697341dde12a76e64c7f044857c55776f7c860 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 17 Jul 2025 15:29:41 +0300 Subject: [PATCH 102/486] msgconv/from-whatsapp: add better placeholder for album messages --- pkg/connector/wamsgtype.go | 4 ---- pkg/msgconv/from-whatsapp.go | 2 ++ pkg/msgconv/wa-media.go | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/pkg/connector/wamsgtype.go b/pkg/connector/wamsgtype.go index 7a8547c1..92e7f869 100644 --- a/pkg/connector/wamsgtype.go +++ b/pkg/connector/wamsgtype.go @@ -124,10 +124,6 @@ func getMessageType(waMsg *waE2E.Message) string { case waMsg.EncEventResponseMessage != nil: return "ignore" // these are ignored for now as they're not meant to be shown as new messages //return "encrypted event response" - case waMsg.CommentMessage != nil: - return "comment" - case waMsg.EncCommentMessage != nil: - return "encrypted comment" case waMsg.NewsletterAdminInviteMessage != nil: return "newsletter admin invite" case waMsg.SecretEncryptedMessage != nil: diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 11ef09ad..1784ede1 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -184,6 +184,8 @@ func (mc *MessageConverter) ToMatrix( part, contextInfo = mc.convertMediaMessage(ctx, waMsg.AudioMessage, typeName, info, isViewOnce, previouslyConvertedPart) case waMsg.DocumentMessage != nil: part, contextInfo = mc.convertMediaMessage(ctx, waMsg.DocumentMessage, "file attachment", info, isViewOnce, previouslyConvertedPart) + case waMsg.AlbumMessage != nil: + part, contextInfo = mc.convertAlbumMessage(ctx, waMsg.AlbumMessage) case waMsg.LocationMessage != nil: part, contextInfo = mc.convertLocationMessage(ctx, waMsg.LocationMessage) case waMsg.LiveLocationMessage != nil: diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index dc8b2274..abff34ec 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -119,6 +119,28 @@ func (mc *MessageConverter) convertMediaMessage( return } +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())) + } + if msg.GetExpectedVideoCount() > 0 { + parts = append(parts, fmt.Sprintf("%d videos", msg.GetExpectedVideoCount())) + } + var partDesc string + if len(parts) > 0 { + partDesc = fmt.Sprintf(" with %s", strings.Join(parts, " and ")) + } + body := fmt.Sprintf("Sent an album%s:", partDesc) + return &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: &event.MessageEventContent{ + MsgType: event.MsgNotice, + Body: body, + }, + }, msg.GetContextInfo() +} + const FailedMediaField = "fi.mau.whatsapp.failed_media" type FailedMediaKeys struct { From d61c197be50dda58e6b88bdae2913ba79d1a934e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 22 Jul 2025 14:06:37 +0300 Subject: [PATCH 103/486] msgconv/from-matrix: try to escape formatting characters in plain text --- pkg/msgconv/from-matrix.go | 9 ++++++++- pkg/msgconv/msgconv.go | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 105c1c2d..edf33f1e 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -279,6 +279,13 @@ func (mc *MessageConverter) constructMediaMessage( } } +var hackyFormattingEscaper = strings.NewReplacer( + "~", "\u200b~", + "_", "\u200b_", + "*", "\u200b*", + "`", "\u200b`", +) + func (mc *MessageConverter) parseText(ctx context.Context, content *event.MessageEventContent) (text string, mentions []string) { mentions = make([]string, 0) @@ -288,7 +295,7 @@ func (mc *MessageConverter) parseText(ctx context.Context, content *event.Messag if content.Format == event.FormatHTML { text = mc.HTMLParser.Parse(content.FormattedBody, parseCtx) } else { - text = content.Body + text = hackyFormattingEscaper.Replace(content.Body) } return } diff --git a/pkg/msgconv/msgconv.go b/pkg/msgconv/msgconv.go index e4109c8e..daa00b50 100644 --- a/pkg/msgconv/msgconv.go +++ b/pkg/msgconv/msgconv.go @@ -69,6 +69,9 @@ func New(br *bridgev2.Bridge) *MessageConverter { MonospaceBlockConverter: func(code, language string, ctx format.Context) string { return "```\n" + code + "\n```" }, + TextConverter: func(s string, context format.Context) string { + return hackyFormattingEscaper.Replace(s) + }, } return mc } From cadc10719e8363159283e93e17a395f541548643 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 22 Jul 2025 14:07:54 +0300 Subject: [PATCH 104/486] 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 800c7491..53354e51 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.8 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250717084138-aecc878ab213 + go.mau.fi/whatsmeow v0.0.0-20250722103218-f0d022d58b17 golang.org/x/image v0.29.0 golang.org/x/net v0.42.0 golang.org/x/sync v0.16.0 diff --git a/go.sum b/go.sum index 5b9e23f4..a057254c 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c= go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= 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-20250717084138-aecc878ab213 h1:CYkW6OUQk0uvzMa3wN7RtGd7H8DxYdYqw/DoFobTK2w= -go.mau.fi/whatsmeow v0.0.0-20250717084138-aecc878ab213/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= +go.mau.fi/whatsmeow v0.0.0-20250722103218-f0d022d58b17 h1:oIqM1Rq1S8YlD76MeVlmv3C5RfA/w4PNPeGSOwhCLZg= +go.mau.fi/whatsmeow v0.0.0-20250722103218-f0d022d58b17/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= From 76079263d106ec26f858cf33481c655928dfba51 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 22 Jul 2025 15:58:13 +0300 Subject: [PATCH 105/486] handlewhatsapp: add support for lid avatar changes --- pkg/connector/handlewhatsapp.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index efd261de..beb360fd 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -550,7 +550,7 @@ func (wa *WhatsAppClient) syncGhost(jid types.JID, reason string, pictureID *str Str("picture_id", ptr.Val(pictureID)). Stringer("jid", jid). Logger() - ctx := log.WithContext(context.Background()) + ctx := log.WithContext(wa.Main.Bridge.BackgroundCtx) ghost, err := wa.Main.Bridge.GetGhostByID(ctx, waid.MakeUserID(jid)) if err != nil { log.Err(err).Msg("Failed to get ghost") @@ -567,10 +567,22 @@ func (wa *WhatsAppClient) syncGhost(jid types.JID, reason string, pictureID *str log.Debug().Msg("Synced ghost info") } go wa.syncRemoteProfile(ctx, ghost) + var altJID types.JID + if jid.Server == types.HiddenUserServer { + altJID, err = wa.Device.LIDs.GetPNForLID(ctx, jid) + } else if jid.Server == types.DefaultUserServer { + altJID, err = wa.Device.LIDs.GetLIDForPN(ctx, jid) + } + if err != nil { + log.Warn().Err(err).Msg("Failed to get alternate JID for avatar change event") + } + if !altJID.IsEmpty() { + wa.syncGhost(altJID, reason+" + alt jid", pictureID) + } } func (wa *WhatsAppClient) handleWAPictureUpdate(ctx context.Context, evt *events.Picture) bool { - if evt.JID.Server == types.DefaultUserServer || evt.JID.Server == types.BotServer { + if evt.JID.Server == types.DefaultUserServer || evt.JID.Server == types.HiddenUserServer || evt.JID.Server == types.BotServer { go wa.syncGhost(evt.JID, "picture event", &evt.PictureID) return true } else { From 0be2c5e3b68762d20fac5962b67458b252a5571c Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 22 Jul 2025 16:21:54 +0300 Subject: [PATCH 106/486] backfill: mark task as not done when receiving new messages --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/backfill.go | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 53354e51..0014f8e9 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( golang.org/x/sync v0.16.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.2 + maunium.net/go/mautrix v0.24.3-0.20250722131825-3ecdb886bfd0 ) require ( diff --git a/go.sum b/go.sum index a057254c..6b26e2ec 100644 --- a/go.sum +++ b/go.sum @@ -101,5 +101,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.24.2 h1:+AVT5kbcA/QuT5svrJKp4ivwoUmz+RRplMp3DnfpheI= -maunium.net/go/mautrix v0.24.2/go.mod h1:1ut900w++eE9by9yqCR2dQdMqwsHwZG5L+1bKB1EvSA= +maunium.net/go/mautrix v0.24.3-0.20250722131825-3ecdb886bfd0 h1:9IgrNVTJlZVrTkqc8Ln2h+oxirCtStDiHy45ja+998I= +maunium.net/go/mautrix v0.24.3-0.20250722131825-3ecdb886bfd0/go.mod h1:1ut900w++eE9by9yqCR2dQdMqwsHwZG5L+1bKB1EvSA= diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 81346b96..a9a05646 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -253,6 +253,13 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor } else { successfullySavedTotal += len(messages) } + err = wa.Main.Bridge.DB.BackfillTask.MarkNotDone(ctx, wa.makeWAPortalKey(jid), wa.UserLogin.ID) + if err != nil { + if stopOnError { + 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") + } } } log.Info(). From 70195c8d22027006a24501e7ea541780d0d8a911 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 22 Jul 2025 16:33:26 +0300 Subject: [PATCH 107/486] backfill,wadb: store login ts when chat was synced instead of boolean flag --- pkg/connector/backfill.go | 6 ++-- pkg/connector/login.go | 2 ++ pkg/connector/wadb/conversation.go | 31 +++++++++---------- .../wadb/upgrades/00-latest-schema.sql | 5 ++- .../upgrades/06-conversation-bridged-ts.sql | 4 +++ pkg/waid/dbmeta.go | 1 + 6 files changed, 28 insertions(+), 21 deletions(-) create mode 100644 pkg/connector/wadb/upgrades/06-conversation-bridged-ts.sql diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index a9a05646..6952f9cb 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -277,7 +277,8 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { Logger() ctx = log.WithContext(ctx) limit := wa.Main.Config.HistorySync.MaxInitialConversations - conversations, err := wa.Main.DB.Conversation.GetRecent(ctx, wa.UserLogin.ID, limit) + loginTS := wa.UserLogin.Metadata.(*waid.UserLoginMetadata).LoggedInAt + conversations, err := wa.Main.DB.Conversation.GetRecent(ctx, wa.UserLogin.ID, limit, loginTS) if err != nil { log.Err(err).Msg("Failed to get recent conversations from database") return @@ -285,6 +286,7 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { log.Info(). Int("limit", limit). Int("conversation_count", len(conversations)). + Int64("login_timestamp", loginTS.Unix()). Msg("Creating portals from history sync") rateLimitErrors := 0 var wg sync.WaitGroup @@ -356,7 +358,7 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { PortalKey: wa.makeWAPortalKey(conv.ChatJID), CreatePortal: true, PostHandleFunc: func(ctx context.Context, portal *bridgev2.Portal) { - err := wa.Main.DB.Conversation.MarkBridged(ctx, wa.UserLogin.ID, conv.ChatJID) + err := wa.Main.DB.Conversation.MarkSynced(ctx, wa.UserLogin.ID, conv.ChatJID, loginTS) if err != nil { zerolog.Ctx(ctx).Err(err).Msg("Failed to mark conversation as bridged") } diff --git a/pkg/connector/login.go b/pkg/connector/login.go index 5e8784e9..ebf30600 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -9,6 +9,7 @@ import ( "github.com/rs/zerolog" "go.mau.fi/util/exsync" + "go.mau.fi/util/jsontime" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/types/events" waLog "go.mau.fi/whatsmeow/util/log" @@ -322,6 +323,7 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { }, Metadata: &waid.UserLoginMetadata{ WADeviceID: wl.LoginSuccess.ID.Device, + LoggedInAt: jsontime.UnixNow(), Timezone: wl.Timezone, HistorySyncPortalsNeedCreating: true, diff --git a/pkg/connector/wadb/conversation.go b/pkg/connector/wadb/conversation.go index 29694d77..85c7f44a 100644 --- a/pkg/connector/wadb/conversation.go +++ b/pkg/connector/wadb/conversation.go @@ -6,6 +6,7 @@ import ( "time" "go.mau.fi/util/dbutil" + "go.mau.fi/util/jsontime" "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow/proto/waHistorySync" "go.mau.fi/whatsmeow/types" @@ -30,7 +31,6 @@ type Conversation struct { EphemeralSettingTimestamp *int64 MarkedAsUnread *bool UnreadCount *uint32 - Bridged bool } func parseHistoryTime(ts *uint64) time.Time { @@ -69,9 +69,9 @@ const ( INSERT INTO whatsapp_history_sync_conversation ( bridge_id, user_login_id, chat_jid, last_message_timestamp, archived, pinned, mute_end_time, end_of_history_transfer_type, ephemeral_expiration, ephemeral_setting_timestamp, marked_as_unread, - unread_count, bridged + unread_count ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) ON CONFLICT (bridge_id, user_login_id, chat_jid) DO UPDATE SET last_message_timestamp=CASE @@ -87,16 +87,15 @@ const ( ephemeral_expiration=COALESCE(excluded.ephemeral_expiration, whatsapp_history_sync_conversation.ephemeral_expiration), ephemeral_setting_timestamp=COALESCE(excluded.ephemeral_setting_timestamp, whatsapp_history_sync_conversation.ephemeral_setting_timestamp), marked_as_unread=COALESCE(excluded.marked_as_unread, whatsapp_history_sync_conversation.marked_as_unread), - unread_count=COALESCE(excluded.unread_count, whatsapp_history_sync_conversation.unread_count), - bridged=false + unread_count=COALESCE(excluded.unread_count, whatsapp_history_sync_conversation.unread_count) ` getRecentConversations = ` SELECT bridge_id, user_login_id, chat_jid, last_message_timestamp, archived, pinned, mute_end_time, end_of_history_transfer_type, ephemeral_expiration, ephemeral_setting_timestamp, marked_as_unread, - unread_count, bridged + unread_count FROM whatsapp_history_sync_conversation - WHERE bridge_id=$1 AND user_login_id=$2 AND bridged=false + WHERE bridge_id=$1 AND user_login_id=$2 AND (synced_login_ts IS NULL OR synced_login_ts < $4) ORDER BY last_message_timestamp DESC LIMIT $3 ` @@ -104,7 +103,7 @@ const ( SELECT bridge_id, user_login_id, chat_jid, last_message_timestamp, archived, pinned, mute_end_time, end_of_history_transfer_type, ephemeral_expiration, ephemeral_setting_timestamp, marked_as_unread, - unread_count, bridged + unread_count FROM whatsapp_history_sync_conversation WHERE bridge_id=$1 AND user_login_id=$2 AND chat_jid=$3 ` @@ -113,9 +112,9 @@ const ( DELETE FROM whatsapp_history_sync_conversation WHERE bridge_id=$1 AND user_login_id=$2 AND chat_jid=$3 ` - markConversationBridged = ` + markConversationSynced = ` UPDATE whatsapp_history_sync_conversation - SET bridged=true + SET synced_login_ts=$4 WHERE bridge_id=$1 AND user_login_id=$2 AND chat_jid=$3 ` ) @@ -125,17 +124,19 @@ func (cq *ConversationQuery) Put(ctx context.Context, conv *Conversation) error return cq.Exec(ctx, upsertHistorySyncConversationQuery, conv.sqlVariables()...) } -func (cq *ConversationQuery) GetRecent(ctx context.Context, loginID networkid.UserLoginID, limit int) ([]*Conversation, error) { +func (cq *ConversationQuery) GetRecent( + ctx context.Context, loginID networkid.UserLoginID, limit int, notSyncedAfter jsontime.Unix, +) ([]*Conversation, error) { limitPtr := &limit // Negative limit on SQLite means unlimited, but Postgres prefers a NULL limit. if limit < 0 && cq.GetDB().Dialect == dbutil.Postgres { limitPtr = nil } - return cq.QueryMany(ctx, getRecentConversations, cq.BridgeID, loginID, limitPtr) + return cq.QueryMany(ctx, getRecentConversations, cq.BridgeID, loginID, limitPtr, notSyncedAfter) } -func (cq *ConversationQuery) MarkBridged(ctx context.Context, loginID networkid.UserLoginID, chatJID types.JID) error { - return cq.Exec(ctx, markConversationBridged, cq.BridgeID, loginID, chatJID) +func (cq *ConversationQuery) MarkSynced(ctx context.Context, loginID networkid.UserLoginID, chatJID types.JID, loginTS jsontime.Unix) error { + return cq.Exec(ctx, markConversationSynced, cq.BridgeID, loginID, chatJID, loginTS) } func (cq *ConversationQuery) Get(ctx context.Context, loginID networkid.UserLoginID, chatJID types.JID) (*Conversation, error) { @@ -171,7 +172,6 @@ func (c *Conversation) sqlVariables() []any { c.EphemeralSettingTimestamp, c.MarkedAsUnread, c.UnreadCount, - c.Bridged, } } @@ -190,7 +190,6 @@ func (c *Conversation) Scan(row dbutil.Scannable) (*Conversation, error) { &c.EphemeralSettingTimestamp, &c.MarkedAsUnread, &c.UnreadCount, - &c.Bridged, ) if err != nil { return nil, err diff --git a/pkg/connector/wadb/upgrades/00-latest-schema.sql b/pkg/connector/wadb/upgrades/00-latest-schema.sql index f99bad42..59baff41 100644 --- a/pkg/connector/wadb/upgrades/00-latest-schema.sql +++ b/pkg/connector/wadb/upgrades/00-latest-schema.sql @@ -1,4 +1,4 @@ --- v0 -> v5 (compatible with v3+): Latest revision +-- v0 -> v6 (compatible with v3+): Latest revision CREATE TABLE whatsapp_poll_option_id ( bridge_id TEXT NOT NULL, @@ -26,8 +26,7 @@ CREATE TABLE whatsapp_history_sync_conversation ( ephemeral_setting_timestamp BIGINT, marked_as_unread BOOLEAN, unread_count INTEGER, - - bridged BOOLEAN NOT NULL DEFAULT false, + synced_login_ts BIGINT, PRIMARY KEY (bridge_id, user_login_id, chat_jid), CONSTRAINT whatsapp_history_sync_conversation_user_login_fkey FOREIGN KEY (bridge_id, user_login_id) diff --git a/pkg/connector/wadb/upgrades/06-conversation-bridged-ts.sql b/pkg/connector/wadb/upgrades/06-conversation-bridged-ts.sql new file mode 100644 index 00000000..8d8001b6 --- /dev/null +++ b/pkg/connector/wadb/upgrades/06-conversation-bridged-ts.sql @@ -0,0 +1,4 @@ +-- v6 (compatible with v3+): Store timestamp for which login a conversation was synced with +ALTER TABLE whatsapp_history_sync_conversation ADD COLUMN synced_login_ts BIGINT; +UPDATE whatsapp_history_sync_conversation SET synced_login_ts = 0 WHERE bridged = true; +ALTER TABLE whatsapp_history_sync_conversation DROP COLUMN bridged; diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index c67f5871..58b1521e 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -35,6 +35,7 @@ type UserLoginMetadata struct { PushKeys *PushKeys `json:"push_keys,omitempty"` APNSEncPubKey []byte `json:"apns_enc_pubkey,omitempty"` APNSEncPrivKey []byte `json:"apns_enc_privkey,omitempty"` + LoggedInAt jsontime.Unix `json:"logged_in_at,omitempty"` HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitempty"` } From 8b36b353cfd96109538c653d190035656a9f9717 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 22 Jul 2025 17:53:05 +0300 Subject: [PATCH 108/486] backfill: make wait time before starting portal creation configurable --- pkg/connector/backfill.go | 78 +++++++++++++++++++------------ pkg/connector/config.go | 7 ++- pkg/connector/example-config.yaml | 4 ++ 3 files changed, 58 insertions(+), 31 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 6952f9cb..392fceab 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" "sync" + "sync/atomic" "time" "github.com/rs/zerolog" @@ -28,13 +29,13 @@ import ( var _ bridgev2.BackfillingNetworkAPI = (*WhatsAppClient)(nil) -const historySyncDispatchWait = 30 * time.Second - func (wa *WhatsAppClient) historySyncLoop(ctx context.Context) { - dispatchTimer := time.NewTimer(historySyncDispatchWait) + dispatchTimer := time.NewTimer(wa.Main.Config.HistorySync.DispatchWait) + var timerPending atomic.Bool if !wa.isNewLogin && wa.UserLogin.Metadata.(*waid.UserLoginMetadata).HistorySyncPortalsNeedCreating { dispatchTimer.Reset(5 * time.Second) + timerPending.Store(true) } else { dispatchTimer.Stop() } @@ -45,35 +46,52 @@ func (wa *WhatsAppClient) historySyncLoop(ctx context.Context) { default: } } - wa.UserLogin.Log.Debug().Msg("Starting history sync loop") + wa.UserLogin.Log.Debug().Msg("Starting history sync loops") + // Separate loop for creating portals to ensure it doesn't block processing new history sync payloads. + go func() { + for { + select { + case <-dispatchTimer.C: + timerPending.Store(false) + wa.createPortalsFromHistorySync(ctx) + case <-ctx.Done(): + wa.UserLogin.Log.Debug().Msg("Stopping portal creation history sync loop") + return + } + } + }() for { + var resetTimer bool + // The timer is stopped unconditionally and restarted if either handleWAHistorySync had conversations, + // or if the timer was previously started and hadn't reached the loop above yet. + dispatchTimer.Stop() select { case evt := <-wa.historySyncs: - dispatchTimer.Stop() - _ = wa.handleWAHistorySync(ctx, evt, false) - dispatchTimer.Reset(historySyncDispatchWait) + resetTimer, _ = wa.handleWAHistorySync(ctx, evt, false) case <-wa.historySyncWakeup: - dispatchTimer.Stop() notif, rowid, err := wa.Main.DB.HSNotif.GetNext(ctx, wa.UserLogin.ID) if err != nil { wa.UserLogin.Log.Err(err).Msg("Failed to get next history sync notification") } else if notif == nil { wa.UserLogin.Log.Debug().Msg("No more queued history sync notifications") } else { - wa.downloadAndSaveWAHistorySyncData(ctx, notif, rowid) + resetTimer = wa.downloadAndSaveWAHistorySyncData(ctx, notif, rowid) // Continue waking up the loop until all queued notifications are processed select { case wa.historySyncWakeup <- struct{}{}: default: } } - dispatchTimer.Reset(historySyncDispatchWait) - case <-dispatchTimer.C: - wa.createPortalsFromHistorySync(ctx) case <-ctx.Done(): - wa.UserLogin.Log.Debug().Msg("Stopping history sync loop") + wa.UserLogin.Log.Debug().Msg("Stopping main history sync loop") return } + if resetTimer { + timerPending.Store(true) + } + if timerPending.Load() { + dispatchTimer.Reset(wa.Main.Config.HistorySync.DispatchWait) + } } } @@ -94,7 +112,7 @@ func (wa *WhatsAppClient) saveWAHistorySyncNotification(ctx context.Context, evt } } -func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context, evt *waE2E.HistorySyncNotification, rowid int) { +func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context, evt *waE2E.HistorySyncNotification, rowid int) (resetTimer bool) { log := wa.UserLogin.Log.With(). Str("action", "download history sync"). Stringer("sync_type", evt.GetSyncType()). @@ -107,25 +125,26 @@ func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context, log.Err(err).Msg("Failed to download history sync") return } - err = wa.Main.DB.DoTxn(ctx, nil, func(ctx context.Context) error { - err := wa.handleWAHistorySync(ctx, blob, true) - if err != nil { - return err + err = wa.Main.DB.DoTxn(ctx, nil, func(ctx context.Context) (innerErr error) { + resetTimer, innerErr = wa.handleWAHistorySync(ctx, blob, true) + if innerErr != nil { + return } - err = wa.Main.DB.HSNotif.Delete(ctx, rowid) - if err != nil { - return fmt.Errorf("failed to delete queued history sync notification: %w", err) + innerErr = wa.Main.DB.HSNotif.Delete(ctx, rowid) + if innerErr != nil { + innerErr = fmt.Errorf("failed to delete queued history sync notification: %w", innerErr) } - return nil + return }) if err != nil { log.Err(err).Msg("Failed to store history sync notification data") } + return } -func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistorySync.HistorySync, stopOnError bool) error { +func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistorySync.HistorySync, stopOnError bool) (bool, error) { if evt == nil || evt.SyncType == nil { - return nil + return false, nil } log := wa.UserLogin.Log.With(). Str("action", "store history sync"). @@ -145,7 +164,7 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor Int("recent_sticker_count", len(evt.GetRecentStickers())). Int("past_participant_count", len(evt.GetPastParticipants())). Msg("Ignoring history sync") - return nil + return false, nil } log.Info(). Int("conversation_count", len(evt.GetConversations())). @@ -238,7 +257,7 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor err = wa.Main.DB.Conversation.Put(ctx, wadb.NewConversation(wa.UserLogin.ID, jid, conv, maxTime)) if err != nil { if stopOnError { - return fmt.Errorf("failed to save conversation metadata for %s: %w", jid, err) + return false, fmt.Errorf("failed to save conversation metadata for %s: %w", jid, err) } log.Err(err).Msg("Failed to save conversation metadata") continue @@ -246,7 +265,7 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor err = wa.Main.DB.Message.Put(ctx, wa.UserLogin.ID, jid, messages) if err != nil { if stopOnError { - return fmt.Errorf("failed to save messages in %s: %w", jid, err) + return false, fmt.Errorf("failed to save messages in %s: %w", jid, err) } log.Err(err).Msg("Failed to save messages") failedToSaveTotal += len(messages) @@ -256,7 +275,7 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor err = wa.Main.Bridge.DB.BackfillTask.MarkNotDone(ctx, wa.makeWAPortalKey(jid), wa.UserLogin.ID) if err != nil { if stopOnError { - return fmt.Errorf("failed to mark backfill task as not done for %s: %w", jid, err) + return false, 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") } @@ -268,7 +287,8 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor Int("total_message_count", totalMessageCount). Dur("duration", time.Since(start)). Msg("Finished storing history sync") - return nil + resetTimer := evt.GetSyncType() == waHistorySync.HistorySync_RECENT || evt.GetSyncType() == waHistorySync.HistorySync_FULL + return resetTimer, nil } func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { diff --git a/pkg/connector/config.go b/pkg/connector/config.go index e5a92096..fd6ac0bd 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -4,6 +4,7 @@ import ( _ "embed" "strings" "text/template" + "time" up "go.mau.fi/util/configupgrade" "go.mau.fi/whatsmeow/types" @@ -53,8 +54,9 @@ type Config struct { AnimatedSticker msgconv.AnimatedStickerConfig `yaml:"animated_sticker"` HistorySync struct { - MaxInitialConversations int `yaml:"max_initial_conversations"` - RequestFullSync bool `yaml:"request_full_sync"` + MaxInitialConversations int `yaml:"max_initial_conversations"` + RequestFullSync bool `yaml:"request_full_sync"` + DispatchWait time.Duration `yaml:"dispatch_wait"` FullSyncConfig struct { DaysLimit uint32 `yaml:"days_limit"` SizeLimit uint32 `yaml:"size_mb_limit"` @@ -122,6 +124,7 @@ func upgradeConfig(helper up.Helper) { helper.Copy(up.Int, "history_sync", "max_initial_conversations") helper.Copy(up.Bool, "history_sync", "request_full_sync") + helper.Copy(up.Str|up.Int, "history_sync", "dispatch_wait") helper.Copy(up.Int|up.Null, "history_sync", "full_sync_config", "days_limit") helper.Copy(up.Int|up.Null, "history_sync", "full_sync_config", "size_mb_limit") helper.Copy(up.Int|up.Null, "history_sync", "full_sync_config", "storage_quota_mb") diff --git a/pkg/connector/example-config.yaml b/pkg/connector/example-config.yaml index 37c41f7b..6b9e2e43 100644 --- a/pkg/connector/example-config.yaml +++ b/pkg/connector/example-config.yaml @@ -88,6 +88,10 @@ history_sync: # Should the bridge request a full sync from the phone when logging in? # This bumps the size of history syncs from 3 months to 1 year. request_full_sync: false + # Time to wait for history sync payloads before starting backfill. Each new payload resets the timer. + # If this is too low, the backfill may happen with incomplete history + # and backfill less messages than what is configured in the backfill section. + dispatch_wait: 1m # Configuration parameters that are sent to the phone along with the request full sync flag. # By default, (when the values are null or 0), the config isn't sent at all. full_sync_config: From b4f42f2e9cc73563b1e9ea9190355c5a55e50b19 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 22 Jul 2025 17:57:56 +0300 Subject: [PATCH 109/486] userinfo: ensure lid/pn ghost is always synced when the other one is --- pkg/connector/handlewhatsapp.go | 13 +---------- pkg/connector/userinfo.go | 38 ++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index beb360fd..330bea00 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -565,20 +565,9 @@ func (wa *WhatsAppClient) syncGhost(jid types.JID, reason string, pictureID *str } else { ghost.UpdateInfo(ctx, userInfo) log.Debug().Msg("Synced ghost info") + wa.syncAltGhostWithInfo(ctx, jid, userInfo) } go wa.syncRemoteProfile(ctx, ghost) - var altJID types.JID - if jid.Server == types.HiddenUserServer { - altJID, err = wa.Device.LIDs.GetPNForLID(ctx, jid) - } else if jid.Server == types.DefaultUserServer { - altJID, err = wa.Device.LIDs.GetLIDForPN(ctx, jid) - } - if err != nil { - log.Warn().Err(err).Msg("Failed to get alternate JID for avatar change event") - } - if !altJID.IsEmpty() { - wa.syncGhost(altJID, reason+" + alt jid", pictureID) - } } func (wa *WhatsAppClient) handleWAPictureUpdate(ctx context.Context, evt *events.Picture) bool { diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 4d7d9825..ce4f3606 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -161,6 +161,7 @@ func (wa *WhatsAppClient) doGhostResync(ctx context.Context, queue map[types.JID continue } ghost.UpdateInfo(ctx, userInfo) + wa.syncAltGhostWithInfo(ctx, jid, userInfo) } } @@ -289,7 +290,42 @@ func (wa *WhatsAppClient) resyncContacts(forceAvatarSync bool) { if err != nil { log.Err(err).Msg("Failed to get ghost") } else if ghost != nil { - ghost.UpdateInfo(ctx, wa.contactToUserInfo(ctx, jid, contact, forceAvatarSync || ghost.AvatarID == "")) + userInfo := wa.contactToUserInfo(ctx, jid, contact, forceAvatarSync || ghost.AvatarID == "") + ghost.UpdateInfo(ctx, userInfo) + wa.syncAltGhostWithInfo(ctx, jid, userInfo) } } } + +func (wa *WhatsAppClient) syncAltGhostWithInfo(ctx context.Context, jid types.JID, info *bridgev2.UserInfo) { + log := zerolog.Ctx(ctx) + var altJID types.JID + var err error + if jid.Server == types.HiddenUserServer { + altJID, err = wa.Device.LIDs.GetPNForLID(ctx, jid) + } else if jid.Server == types.DefaultUserServer { + altJID, err = wa.Device.LIDs.GetLIDForPN(ctx, jid) + } + if err != nil { + log.Warn().Err(err). + Stringer("jid", jid). + Msg("Failed to get alternate JID for syncing user info") + return + } else if altJID.IsEmpty() { + return + } + ghost, err := wa.Main.Bridge.GetGhostByID(ctx, waid.MakeUserID(altJID)) + if err != nil { + log.Err(err). + Stringer("alternate_jid", altJID). + Stringer("jid", jid). + Msg("Failed to get ghost for alternate JID") + return + } + ghost.UpdateInfo(ctx, info) + log.Debug(). + Stringer("jid", jid). + Stringer("alternate_jid", altJID). + Msg("Synced alternate ghost with info") + go wa.syncRemoteProfile(ctx, ghost) +} From 1b6ed76fe9df8a64ed6e19233b9f13d694b97770 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 22 Jul 2025 19:12:07 +0300 Subject: [PATCH 110/486] backfill: fix timer stopping --- pkg/connector/backfill.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 392fceab..bebce984 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -62,13 +62,14 @@ func (wa *WhatsAppClient) historySyncLoop(ctx context.Context) { }() for { var resetTimer bool - // The timer is stopped unconditionally and restarted if either handleWAHistorySync had conversations, - // or if the timer was previously started and hadn't reached the loop above yet. - dispatchTimer.Stop() select { case evt := <-wa.historySyncs: + // The timer is stopped unconditionally and restarted if either handleWAHistorySync had conversations, + // or if the timer was previously started and hadn't reached the loop above yet. + dispatchTimer.Stop() resetTimer, _ = wa.handleWAHistorySync(ctx, evt, false) case <-wa.historySyncWakeup: + dispatchTimer.Stop() notif, rowid, err := wa.Main.DB.HSNotif.GetNext(ctx, wa.UserLogin.ID) if err != nil { wa.UserLogin.Log.Err(err).Msg("Failed to get next history sync notification") @@ -287,7 +288,8 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor 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 + resetTimer := evt.GetSyncType() == waHistorySync.HistorySync_RECENT || + evt.GetSyncType() == waHistorySync.HistorySync_FULL return resetTimer, nil } From e103a8bee4da94669ca555737d30949f3f3551a2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 22 Jul 2025 19:25:21 +0300 Subject: [PATCH 111/486] backfill: fix ignoring PSA chat --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/backfill.go | 2 +- pkg/connector/userinfo.go | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 0014f8e9..43f795b3 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.8 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250722103218-f0d022d58b17 + go.mau.fi/whatsmeow v0.0.0-20250722162452-75177ce21187 golang.org/x/image v0.29.0 golang.org/x/net v0.42.0 golang.org/x/sync v0.16.0 diff --git a/go.sum b/go.sum index 6b26e2ec..d2ea53c3 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c= go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= 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-20250722103218-f0d022d58b17 h1:oIqM1Rq1S8YlD76MeVlmv3C5RfA/w4PNPeGSOwhCLZg= -go.mau.fi/whatsmeow v0.0.0-20250722103218-f0d022d58b17/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= +go.mau.fi/whatsmeow v0.0.0-20250722162452-75177ce21187 h1:3gLaJu5UhCIoD+FpcpfPrqopRSu90PVyei685CifCYY= +go.mau.fi/whatsmeow v0.0.0-20250722162452-75177ce21187/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index bebce984..78a5a4f4 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -325,7 +325,7 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { if conv.ChatJID == types.StatusBroadcastJID && !wa.Main.Config.EnableStatusBroadcast { wg.Done() continue - } else if conv.ChatJID == types.PSAJID { + } else if conv.ChatJID == types.PSAJID || conv.ChatJID == types.LegacyPSAJID { // We don't currently support new PSAs, so don't bother backfilling them either wg.Done() continue diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index ce4f3606..0a25d484 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -185,7 +185,7 @@ func (wa *WhatsAppClient) getUserInfo(ctx context.Context, jid types.JID, fetchA func (wa *WhatsAppClient) contactToUserInfo(ctx context.Context, jid types.JID, contact types.ContactInfo, getAvatar bool) *bridgev2.UserInfo { if jid == types.MetaAIJID && contact.PushName == jid.User { contact.PushName = "Meta AI" - } else if jid == types.PSAJID { + } else if jid == types.LegacyPSAJID || jid == types.PSAJID { contact.PushName = "WhatsApp" } var phone string From ebeef0bbbb41ac6ee7ca31f8908a0205445c8794 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 22 Jul 2025 20:33:29 +0300 Subject: [PATCH 112/486] 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 43f795b3..4785e41b 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( golang.org/x/sync v0.16.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.3-0.20250722131825-3ecdb886bfd0 + maunium.net/go/mautrix v0.24.3-0.20250722173131-cb80e5c63f7f ) require ( diff --git a/go.sum b/go.sum index d2ea53c3..e213289e 100644 --- a/go.sum +++ b/go.sum @@ -101,5 +101,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.24.3-0.20250722131825-3ecdb886bfd0 h1:9IgrNVTJlZVrTkqc8Ln2h+oxirCtStDiHy45ja+998I= -maunium.net/go/mautrix v0.24.3-0.20250722131825-3ecdb886bfd0/go.mod h1:1ut900w++eE9by9yqCR2dQdMqwsHwZG5L+1bKB1EvSA= +maunium.net/go/mautrix v0.24.3-0.20250722173131-cb80e5c63f7f h1:GeptxClgWaqGyRMOPh7beZHcySmyBMpRsaOCU7ZkKWg= +maunium.net/go/mautrix v0.24.3-0.20250722173131-cb80e5c63f7f/go.mod h1:1ut900w++eE9by9yqCR2dQdMqwsHwZG5L+1bKB1EvSA= From a2275a587f48faa1f498e589e68379c9c4a9e6b6 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 22 Jul 2025 22:45:54 +0300 Subject: [PATCH 113/486] 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 4785e41b..71cb34a7 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.8 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250722162452-75177ce21187 + go.mau.fi/whatsmeow v0.0.0-20250722194234-b61df67bf925 golang.org/x/image v0.29.0 golang.org/x/net v0.42.0 golang.org/x/sync v0.16.0 diff --git a/go.sum b/go.sum index e213289e..e300c40a 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c= go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= 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-20250722162452-75177ce21187 h1:3gLaJu5UhCIoD+FpcpfPrqopRSu90PVyei685CifCYY= -go.mau.fi/whatsmeow v0.0.0-20250722162452-75177ce21187/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= +go.mau.fi/whatsmeow v0.0.0-20250722194234-b61df67bf925 h1:GxOYzZ6x/mRtuIx/ijDy5bGzQkRW8grSET51OfGA1bk= +go.mau.fi/whatsmeow v0.0.0-20250722194234-b61df67bf925/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= From 2ae35f03778d1cf8dd98e4db18c2c026d336fbfe Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 22 Jul 2025 23:10:34 +0300 Subject: [PATCH 114/486] handlewhatsapp: route bot messages to phone number DM as well --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/handlewhatsapp.go | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 71cb34a7..e99ec008 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( golang.org/x/sync v0.16.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.3-0.20250722173131-cb80e5c63f7f + maunium.net/go/mautrix v0.24.3-0.20250722203558-463d2ea6d011 ) require ( diff --git a/go.sum b/go.sum index e300c40a..26a8122b 100644 --- a/go.sum +++ b/go.sum @@ -101,5 +101,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.24.3-0.20250722173131-cb80e5c63f7f h1:GeptxClgWaqGyRMOPh7beZHcySmyBMpRsaOCU7ZkKWg= -maunium.net/go/mautrix v0.24.3-0.20250722173131-cb80e5c63f7f/go.mod h1:1ut900w++eE9by9yqCR2dQdMqwsHwZG5L+1bKB1EvSA= +maunium.net/go/mautrix v0.24.3-0.20250722203558-463d2ea6d011 h1:s83n+xEOe6iv6K430pwKc4TkId8AJ8DuwvoEIWKEj0o= +maunium.net/go/mautrix v0.24.3-0.20250722203558-463d2ea6d011/go.mod h1:1ut900w++eE9by9yqCR2dQdMqwsHwZG5L+1bKB1EvSA= diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 330bea00..3386f997 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -260,6 +260,21 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa Str("message_id", evt.Info.ID). Msg("Forced LID DM sender to phone number in own message sent from another device") evt.Info.Chat = evt.Info.RecipientAlt.ToNonAD() + } else if evt.Info.Sender.Server == types.BotServer && evt.Info.Chat.Server == types.HiddenUserServer { + chatPN, err := wa.Device.LIDs.GetPNForLID(ctx, evt.Info.Chat) + if err != nil { + wa.UserLogin.Log.Err(err). + Str("message_id", evt.Info.ID). + Stringer("lid", evt.Info.Chat). + Msg("Failed to get phone number of DM for incoming bot message") + } else if !chatPN.IsEmpty() { + wa.UserLogin.Log.Debug(). + Stringer("lid", evt.Info.Chat). + Stringer("pn", chatPN). + Str("message_id", evt.Info.ID). + Msg("Forced LID chat to phone number in bot message") + evt.Info.Chat = chatPN + } } wa.UserLogin.Log.Trace(). Any("info", evt.Info). From a26b8e72bb27339e7829c40d400cd4a38106e24c Mon Sep 17 00:00:00 2001 From: Kishan Bagaria <1093313+KishanBagaria@users.noreply.github.com> Date: Sun, 27 Jul 2025 19:58:55 +0530 Subject: [PATCH 115/486] client: translate viewing chat status into presence on whatsapp (#816) --- pkg/connector/client.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index e1b01d32..122b393e 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -112,6 +112,7 @@ type WhatsAppClient struct { mediaRetryLock *semaphore.Weighted offlineSyncWaiter chan error isNewLogin bool + lastPresence types.Presence } var ( @@ -387,6 +388,22 @@ func (wa *WhatsAppClient) syncRemoteProfile(ctx context.Context, ghost *bridgev2 } func (wa *WhatsAppClient) HandleMatrixViewingChat(ctx context.Context, msg *bridgev2.MatrixViewingChat) error { + var presence types.Presence + if msg.Portal != nil { + presence = types.PresenceAvailable + } else { + presence = types.PresenceUnavailable + } + + if wa.lastPresence != presence { + err := wa.Client.SendPresence(presence) + if err != nil { + zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to set presence when viewing chat") + } else { + wa.lastPresence = presence + } + } + if msg.Portal == nil || msg.Portal.Metadata.(*waid.PortalMetadata).LastSync.Add(5*time.Minute).After(time.Now()) { // If we resynced this portal within the last 5 minutes, don't do it again return nil From 5965ee828d575a98fb53c4091c895ef91689310e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 28 Jul 2025 15:59:00 +0300 Subject: [PATCH 116/486] legacyprovision: switch to standard http mux --- cmd/mautrix-whatsapp/legacyprovision.go | 3 +-- cmd/mautrix-whatsapp/main.go | 12 +++++------- go.mod | 12 ++++++------ go.sum | 24 ++++++++++++------------ 4 files changed, 24 insertions(+), 27 deletions(-) diff --git a/cmd/mautrix-whatsapp/legacyprovision.go b/cmd/mautrix-whatsapp/legacyprovision.go index 4b4793cb..169fa014 100644 --- a/cmd/mautrix-whatsapp/legacyprovision.go +++ b/cmd/mautrix-whatsapp/legacyprovision.go @@ -7,7 +7,6 @@ import ( "regexp" "strings" - "github.com/gorilla/mux" "github.com/gorilla/websocket" "github.com/rs/zerolog/hlog" "go.mau.fi/util/exhttp" @@ -263,7 +262,7 @@ func legacyProvContacts(w http.ResponseWriter, r *http.Request) { } func legacyProvResolveIdentifier(w http.ResponseWriter, r *http.Request) { - number := mux.Vars(r)["number"] + number := r.PathValue("number") userLogin := m.Matrix.Provisioning.GetLoginForRequest(w, r) if userLogin == nil { return diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 0ec230fa..fc0364de 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -1,8 +1,6 @@ package main import ( - "net/http" - "maunium.net/go/mautrix/bridgev2/bridgeconfig" "maunium.net/go/mautrix/bridgev2/matrix/mxmain" @@ -43,11 +41,11 @@ func main() { } m.PostStart = func() { if m.Matrix.Provisioning != nil { - m.Matrix.Provisioning.Router.HandleFunc("/v1/login", legacyProvLogin).Methods(http.MethodGet) - m.Matrix.Provisioning.Router.HandleFunc("/v1/logout", legacyProvLogout).Methods(http.MethodPost) - m.Matrix.Provisioning.Router.HandleFunc("/v1/contacts", legacyProvContacts).Methods(http.MethodGet) - m.Matrix.Provisioning.Router.HandleFunc("/v1/resolve_identifier/{number}", legacyProvResolveIdentifier).Methods(http.MethodGet) - m.Matrix.Provisioning.Router.HandleFunc("/v1/pm/{number}", legacyProvResolveIdentifier).Methods(http.MethodPost) + m.Matrix.Provisioning.Router.HandleFunc("GET /v1/login", legacyProvLogin) + m.Matrix.Provisioning.Router.HandleFunc("POST /v1/logout", legacyProvLogout) + 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.GetAuthFromRequest = legacyProvAuth } } diff --git a/go.mod b/go.mod index e99ec008..685af2e8 100644 --- a/go.mod +++ b/go.mod @@ -5,30 +5,30 @@ go 1.23.0 toolchain go1.24.5 require ( - github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.8.8 + go.mau.fi/util v0.8.9-0.20250723171559-474867266038 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250722194234-b61df67bf925 + go.mau.fi/whatsmeow v0.0.0-20250723082402-b6b13c69c9cb golang.org/x/image v0.29.0 golang.org/x/net v0.42.0 golang.org/x/sync v0.16.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.3-0.20250722203558-463d2ea6d011 + maunium.net/go/mautrix v0.24.3-0.20250728125317-74ab3b118e10 ) require ( filippo.io/edwards25519 v1.1.0 // indirect + github.com/coder/websocket v1.8.13 // indirect github.com/coreos/go-systemd/v22 v22.5.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.28 // indirect - github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb // indirect + github.com/petermattis/goid v0.0.0-20250721140440-ea1c0173183e // 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,7 +40,7 @@ require ( go.mau.fi/libsignal v0.2.0 // indirect go.mau.fi/zeroconfig v0.1.3 // indirect golang.org/x/crypto v0.40.0 // indirect - golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect + golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect golang.org/x/sys v0.34.0 // indirect golang.org/x/text v0.27.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index 26a8122b..bcdbb97c 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE= +github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -12,8 +14,6 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -34,8 +34,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb h1:3PrKuO92dUTMrQ9dx0YNejC6U/Si6jqKmyQ9vWjwqR4= -github.com/petermattis/goid v0.0.0-20250508124226-395b08cebbdb/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/petermattis/goid v0.0.0-20250721140440-ea1c0173183e h1:D0bJD+4O3G4izvrQUmzCL80zazlN7EwJ0PPDhpJWC/I= +github.com/petermattis/goid v0.0.0-20250721140440-ea1c0173183e/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= @@ -65,18 +65,18 @@ github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY= github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.0 h1:oRXj3OHhEJq51BFEM8/50UZblmWiTYH93hsNTPcbk90= go.mau.fi/libsignal v0.2.0/go.mod h1:tvjoDsMejgT38CXTXwqaYu8itBiY8O2Mb6biWvZBb9k= -go.mau.fi/util v0.8.8 h1:OnuEEc/sIJFhnq4kFggiImUpcmnmL/xpvQMRu5Fiy5c= -go.mau.fi/util v0.8.8/go.mod h1:Y/kS3loxTEhy8Vill513EtPXr+CRDdae+Xj2BXXMy/c= +go.mau.fi/util v0.8.9-0.20250723171559-474867266038 h1:RVL8TVaYc3LTBBopfjCNDtD+6eZks0O+qgXN/9hsz7k= +go.mau.fi/util v0.8.9-0.20250723171559-474867266038/go.mod h1:GZZp5f9r2MgEu4GDvtB0XxCF7i6Z7Z8fM0w9a5oZH3Y= 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-20250722194234-b61df67bf925 h1:GxOYzZ6x/mRtuIx/ijDy5bGzQkRW8grSET51OfGA1bk= -go.mau.fi/whatsmeow v0.0.0-20250722194234-b61df67bf925/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= +go.mau.fi/whatsmeow v0.0.0-20250723082402-b6b13c69c9cb h1:sMrCRx3Xm1pdbBKm4YFMsjiAmSI23WwKYutR3mGKntE= +go.mau.fi/whatsmeow v0.0.0-20250723082402-b6b13c69c9cb/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= -golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc= -golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= +golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4= +golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas= golang.org/x/image v0.29.0/go.mod h1:RVJROnf3SLK8d26OW91j4FrIHGbsJ8QnbEocVTOWQDA= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= @@ -101,5 +101,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.24.3-0.20250722203558-463d2ea6d011 h1:s83n+xEOe6iv6K430pwKc4TkId8AJ8DuwvoEIWKEj0o= -maunium.net/go/mautrix v0.24.3-0.20250722203558-463d2ea6d011/go.mod h1:1ut900w++eE9by9yqCR2dQdMqwsHwZG5L+1bKB1EvSA= +maunium.net/go/mautrix v0.24.3-0.20250728125317-74ab3b118e10 h1:IMOGeYKTOzHNnBx1ksVCVCw3t6joQRqDOSI2Ou4Xr/U= +maunium.net/go/mautrix v0.24.3-0.20250728125317-74ab3b118e10/go.mod h1:KrE/TdIeAo6cfAUICmbaiZ18UgHimTOozAGOrioK9SU= From 101906ca3c264a897e9d15936b0c12279974a8b5 Mon Sep 17 00:00:00 2001 From: Kishan Bagaria <1093313+KishanBagaria@users.noreply.github.com> Date: Mon, 28 Jul 2025 20:05:41 +0530 Subject: [PATCH 117/486] client: refactor SendPresence calls (#817) in https://github.com/mautrix/whatsapp/pull/816 we started setting lastPresence in this one, we make sure it's set in some other codepaths --- pkg/connector/client.go | 12 +++++++++--- pkg/connector/handlematrix.go | 2 +- pkg/connector/handlewhatsapp.go | 6 +++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 122b393e..bfc03cf2 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -396,11 +396,9 @@ func (wa *WhatsAppClient) HandleMatrixViewingChat(ctx context.Context, msg *brid } if wa.lastPresence != presence { - err := wa.Client.SendPresence(presence) + err := wa.updatePresence(presence) if err != nil { zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to set presence when viewing chat") - } else { - wa.lastPresence = presence } } @@ -432,3 +430,11 @@ func (wa *WhatsAppClient) HandleMatrixViewingChat(ctx context.Context, msg *brid return nil } + +func (wa *WhatsAppClient) updatePresence(presence types.Presence) error { + err := wa.Client.SendPresence(presence) + if err == nil { + wa.lastPresence = presence + } + return err +} diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index a90b6771..a97aee7d 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -336,7 +336,7 @@ func (wa *WhatsAppClient) HandleMatrixTyping(ctx context.Context, msg *bridgev2. } if wa.Main.Config.SendPresenceOnTyping { - err = wa.Client.SendPresence(types.PresenceAvailable) + err = wa.updatePresence(types.PresenceAvailable) if err != nil { zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to set presence on typing") } diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 3386f997..95b6f331 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -128,7 +128,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { case *events.AppStateSyncComplete: if len(wa.GetStore().PushName) > 0 && evt.Name == appstate.WAPatchCriticalBlock { - err := wa.Client.SendPresence(types.PresenceUnavailable) + err := wa.updatePresence(types.PresenceUnavailable) if err != nil { log.Warn().Err(err).Msg("Failed to send presence after app state sync") } @@ -141,7 +141,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { case *events.PushNameSetting: // Send presence available when connecting and when the pushname is changed. // This makes sure that outgoing messages always have the right pushname. - err := wa.Client.SendPresence(types.PresenceUnavailable) + err := wa.updatePresence(types.PresenceUnavailable) if err != nil { log.Warn().Err(err).Msg("Failed to send presence after push name update") } @@ -162,7 +162,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected}) if len(wa.GetStore().PushName) > 0 { go func() { - err := wa.Client.SendPresence(types.PresenceUnavailable) + err := wa.updatePresence(types.PresenceUnavailable) if err != nil { log.Warn().Err(err).Msg("Failed to send initial presence after connecting") } From 16fa09e94e7c3912262c88b64d98e86bbc7b070b Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 28 Jul 2025 17:35:58 +0300 Subject: [PATCH 118/486] 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 685af2e8..c096b2c6 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( golang.org/x/sync v0.16.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.3-0.20250728125317-74ab3b118e10 + maunium.net/go/mautrix v0.24.3-0.20250728143428-ae2c07fb863a ) require ( diff --git a/go.sum b/go.sum index bcdbb97c..38c07ea4 100644 --- a/go.sum +++ b/go.sum @@ -101,5 +101,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.24.3-0.20250728125317-74ab3b118e10 h1:IMOGeYKTOzHNnBx1ksVCVCw3t6joQRqDOSI2Ou4Xr/U= -maunium.net/go/mautrix v0.24.3-0.20250728125317-74ab3b118e10/go.mod h1:KrE/TdIeAo6cfAUICmbaiZ18UgHimTOozAGOrioK9SU= +maunium.net/go/mautrix v0.24.3-0.20250728143428-ae2c07fb863a h1:3lQKIbNLbHKxMLjwoChuCstvfJ8LT9+So4+QabIjKI8= +maunium.net/go/mautrix v0.24.3-0.20250728143428-ae2c07fb863a/go.mod h1:KrE/TdIeAo6cfAUICmbaiZ18UgHimTOozAGOrioK9SU= From bf96ce9f215a19169fd3b07de530428c666f7181 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 28 Jul 2025 22:04:28 +0300 Subject: [PATCH 119/486] 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 c096b2c6..54248525 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( golang.org/x/sync v0.16.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.3-0.20250728143428-ae2c07fb863a + maunium.net/go/mautrix v0.24.3-0.20250728190343-2e7ff3fedd4c ) require ( diff --git a/go.sum b/go.sum index 38c07ea4..fbf371e4 100644 --- a/go.sum +++ b/go.sum @@ -101,5 +101,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.24.3-0.20250728143428-ae2c07fb863a h1:3lQKIbNLbHKxMLjwoChuCstvfJ8LT9+So4+QabIjKI8= -maunium.net/go/mautrix v0.24.3-0.20250728143428-ae2c07fb863a/go.mod h1:KrE/TdIeAo6cfAUICmbaiZ18UgHimTOozAGOrioK9SU= +maunium.net/go/mautrix v0.24.3-0.20250728190343-2e7ff3fedd4c h1:ciLyRuYMrqQK8Y0Rp06QcUz5YOnuieNlEbLg65ERLe8= +maunium.net/go/mautrix v0.24.3-0.20250728190343-2e7ff3fedd4c/go.mod h1:KrE/TdIeAo6cfAUICmbaiZ18UgHimTOozAGOrioK9SU= From 261304a4ad5c9d502e58ffd52c4349f122316d76 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 29 Jul 2025 16:18:56 +0300 Subject: [PATCH 120/486] directmedia: add support for avatars --- go.mod | 4 +- go.sum | 8 +- pkg/connector/chatinfo.go | 7 + pkg/connector/directmedia.go | 79 ++++++++++- pkg/connector/userinfo.go | 55 +++++++ pkg/connector/wadb/avatarcache.go | 51 +++++++ pkg/connector/wadb/database.go | 6 + .../wadb/upgrades/00-latest-schema.sql | 12 +- .../upgrades/07-direct-media-avatar-cache.sql | 10 ++ pkg/waid/mediaid.go | 134 ++++++++++++++---- 10 files changed, 323 insertions(+), 43 deletions(-) create mode 100644 pkg/connector/wadb/avatarcache.go create mode 100644 pkg/connector/wadb/upgrades/07-direct-media-avatar-cache.sql diff --git a/go.mod b/go.mod index 54248525..8cc51141 100644 --- a/go.mod +++ b/go.mod @@ -10,13 +10,13 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.9-0.20250723171559-474867266038 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250723082402-b6b13c69c9cb + go.mau.fi/whatsmeow v0.0.0-20250729133431-9166d862a88c golang.org/x/image v0.29.0 golang.org/x/net v0.42.0 golang.org/x/sync v0.16.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.3-0.20250728190343-2e7ff3fedd4c + maunium.net/go/mautrix v0.24.3-0.20250729144204-bcf92ba0e80a ) require ( diff --git a/go.sum b/go.sum index fbf371e4..d89d11ea 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.9-0.20250723171559-474867266038 h1:RVL8TVaYc3LTBBopfjCNDtD+6 go.mau.fi/util v0.8.9-0.20250723171559-474867266038/go.mod h1:GZZp5f9r2MgEu4GDvtB0XxCF7i6Z7Z8fM0w9a5oZH3Y= 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-20250723082402-b6b13c69c9cb h1:sMrCRx3Xm1pdbBKm4YFMsjiAmSI23WwKYutR3mGKntE= -go.mau.fi/whatsmeow v0.0.0-20250723082402-b6b13c69c9cb/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= +go.mau.fi/whatsmeow v0.0.0-20250729133431-9166d862a88c h1:Pms0CGY/5akaS2cl9NwkgZhmoaPAFkeaVUkXDf09Ovc= +go.mau.fi/whatsmeow v0.0.0-20250729133431-9166d862a88c/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= @@ -101,5 +101,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.24.3-0.20250728190343-2e7ff3fedd4c h1:ciLyRuYMrqQK8Y0Rp06QcUz5YOnuieNlEbLg65ERLe8= -maunium.net/go/mautrix v0.24.3-0.20250728190343-2e7ff3fedd4c/go.mod h1:KrE/TdIeAo6cfAUICmbaiZ18UgHimTOozAGOrioK9SU= +maunium.net/go/mautrix v0.24.3-0.20250729144204-bcf92ba0e80a h1:ze+5l/XKp38/MgQJCxW5ic/eiZq3YeZ0QPS6M36qmC8= +maunium.net/go/mautrix v0.24.3-0.20250729144204-bcf92ba0e80a/go.mod h1:KrE/TdIeAo6cfAUICmbaiZ18UgHimTOozAGOrioK9SU= diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index ef52e8d3..b83b1f76 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -415,6 +415,12 @@ func (wa *WhatsAppClient) makePortalAvatarFetcher(avatarID string, sender types. return false } else if avatar == nil { return false + } else if wa.Main.MsgConv.DirectMedia { + wrappedAvatar, err = wa.makeDirectMediaAvatar(ctx, jid, avatar, portal.RoomType == database.RoomTypeSpace) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to prepare direct media avatar") + return false + } } else { wrappedAvatar = &bridgev2.Avatar{ ID: networkid.AvatarID(avatar.ID), @@ -454,6 +460,7 @@ func (wa *WhatsAppClient) wrapNewsletterInfo(ctx context.Context, info *types.Ne } } avatar := &bridgev2.Avatar{} + // TODO direct media for newsletter avatars if info.ThreadMeta.Picture != nil { avatar.ID = networkid.AvatarID(info.ThreadMeta.Picture.ID) avatar.Get = func(ctx context.Context) ([]byte, error) { diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index 9a5eb86e..ec1610c5 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -38,6 +38,7 @@ import ( "maunium.net/go/mautrix/bridgev2/networkid" "maunium.net/go/mautrix/mediaproxy" + "go.mau.fi/mautrix-whatsapp/pkg/connector/wadb" "go.mau.fi/mautrix-whatsapp/pkg/msgconv" "go.mau.fi/mautrix-whatsapp/pkg/waid" ) @@ -55,13 +56,81 @@ var ErrReloadNeeded = mautrix.RespError{ } func (wa *WhatsAppConnector) Download(ctx context.Context, mediaID networkid.MediaID, params map[string]string) (mediaproxy.GetMediaResponse, error) { - parsedID, receiverID, err := waid.ParseMediaID(mediaID) + parsedID, err := waid.ParseMediaID(mediaID) if err != nil { return nil, err } - log := zerolog.Ctx(ctx).With().Any("message_id", parsedID).Logger() + log := zerolog.Ctx(ctx).With().Any("parsed_media_id", parsedID).Logger() ctx = log.WithContext(ctx) - msg, err := wa.Bridge.DB.Message.GetFirstPartByID(ctx, receiverID, parsedID.String()) + if parsedID.Message != nil { + return wa.downloadMessageDirectMedia(ctx, parsedID, params) + } else if parsedID.Avatar != nil { + return wa.downloadAvatarDirectMedia(ctx, parsedID, params) + } else { + return nil, fmt.Errorf("unexpected media ID parsing result") + } +} + +func (wa *WhatsAppConnector) downloadAvatarDirectMedia(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) + } + cachedInfo, err := wa.DB.AvatarCache.Get(ctx, parsedID.Avatar.TargetJID, parsedID.Avatar.AvatarID) + if err != nil { + return nil, fmt.Errorf("failed to get avatar cache entry: %w", err) + } + if cachedInfo != nil && cachedInfo.Gone { + return nil, mautrix.MNotFound.WithMessage("Avatar is no longer available") + } else if cachedInfo == nil || cachedInfo.Expiry.Time.Before(time.Now().Add(5*time.Minute)) { + zerolog.Ctx(ctx).Debug(). + Str("avatar_id", parsedID.Avatar.AvatarID). + Msg("Refreshing avatar URL from WhatsApp servers") + avatar, err := waClient.Client.GetProfilePictureInfo(parsedID.Avatar.TargetJID, &whatsmeow.GetProfilePictureParams{ + IsCommunity: parsedID.Avatar.Community, + }) + if errors.Is(err, whatsmeow.ErrProfilePictureNotSet) || + errors.Is(err, whatsmeow.ErrProfilePictureUnauthorized) || + (err == nil && (avatar == nil || avatar.ID != parsedID.Avatar.AvatarID)) { + err = wa.DB.AvatarCache.Put(ctx, &wadb.AvatarCacheEntry{ + EntityJID: parsedID.Avatar.TargetJID, + AvatarID: parsedID.Avatar.AvatarID, + Gone: true, + }) + if err != nil { + zerolog.Ctx(ctx).Warn().Err(err). + Str("avatar_id", parsedID.Avatar.AvatarID). + Msg("Failed to mark avatar as gone in cache") + } + return nil, mautrix.MNotFound.WithMessage("Avatar is no longer available") + } else if err != nil { + return nil, fmt.Errorf("failed to refresh avatar url: %w", err) + } + cachedInfo = avatarInfoToCacheEntry(ctx, parsedID.Avatar.TargetJID, avatar) + err = wa.DB.AvatarCache.Put(ctx, cachedInfo) + if err != nil { + zerolog.Ctx(ctx).Warn().Err(err). + Str("avatar_id", avatar.ID). + Msg("Failed to update avatar cache entry") + } + } + return &mediaproxy.GetMediaResponseFile{ + Callback: func(w *os.File) error { + return waClient.Client.DownloadMediaWithPathToFile( + ctx, cachedInfo.DirectPath, nil, nil, nil, 0, "", "", w, + ) + }, + ContentType: "", // TODO are avatars always jpeg? + }, nil +} + +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) } else if msg == nil { @@ -77,8 +146,8 @@ func (wa *WhatsAppConnector) Download(ctx context.Context, mediaID networkid.Med return nil, fmt.Errorf("failed to unmarshal media keys: %w", err) } var ul *bridgev2.UserLogin - if receiverID != "" { - ul = wa.Bridge.GetCachedUserLoginByID(receiverID) + if parsedID.UserLogin != "" { + ul = wa.Bridge.GetCachedUserLoginByID(parsedID.UserLogin) } else { logins, err := wa.Bridge.GetUserLoginsInPortal(ctx, msg.Room) if err != nil { diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 0a25d484..320a90a8 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -2,9 +2,12 @@ package connector import ( "context" + "crypto/sha256" "errors" "fmt" "math/rand/v2" + "regexp" + "strconv" "time" "github.com/rs/zerolog" @@ -17,6 +20,7 @@ import ( "maunium.net/go/mautrix/bridgev2/networkid" "maunium.net/go/mautrix/bridgev2/simplevent" + "go.mau.fi/mautrix-whatsapp/pkg/connector/wadb" "go.mau.fi/mautrix-whatsapp/pkg/waid" ) @@ -242,6 +246,51 @@ func updateGhostLastSyncAt(_ context.Context, ghost *bridgev2.Ghost) bool { return forceSave } +var expiryRegex = regexp.MustCompile("oe=([0-9A-Fa-f]+)") + +func avatarInfoToCacheEntry(ctx context.Context, jid types.JID, avatar *types.ProfilePictureInfo) *wadb.AvatarCacheEntry { + expiry := time.Now().Add(24 * time.Hour) + match := expiryRegex.FindStringSubmatch(avatar.DirectPath) + if len(match) == 2 { + expiryUnix, err := strconv.ParseInt(match[1], 16, 64) + if err == nil { + expiry = time.Unix(expiryUnix, 0) + } else { + zerolog.Ctx(ctx).Warn().Err(err). + Strs("match", match). + Msg("Failed to parse expiry from avatar direct path") + } + } + return &wadb.AvatarCacheEntry{ + EntityJID: jid, + AvatarID: avatar.ID, + DirectPath: avatar.DirectPath, + Expiry: jsontime.U(expiry), + Gone: false, + } +} + +func (wa *WhatsAppClient) makeDirectMediaAvatar(ctx context.Context, jid types.JID, avatar *types.ProfilePictureInfo, community bool) (*bridgev2.Avatar, error) { + mxc, err := wa.Main.Bridge.Matrix.GenerateContentURI(ctx, waid.MakeAvatarMediaID(jid, avatar.ID, wa.UserLogin.ID, community)) + if err != nil { + return nil, fmt.Errorf("failed to generate MXC URI: %w", err) + } + cacheEntry := avatarInfoToCacheEntry(ctx, jid, avatar) + err = wa.Main.DB.AvatarCache.Put(ctx, cacheEntry) + if err != nil { + return nil, fmt.Errorf("failed to cache avatar info: %w", err) + } + hash := sha256.Sum256([]byte(avatar.ID)) + if len(avatar.Hash) == 32 { + hash = [32]byte(avatar.Hash) + } + return &bridgev2.Avatar{ + ID: networkid.AvatarID(avatar.ID), + MXC: mxc, + Hash: hash, + }, nil +} + func (wa *WhatsAppClient) fetchGhostAvatar(ctx context.Context, ghost *bridgev2.Ghost) bool { jid := waid.ParseUserID(ghost.ID) existingID := string(ghost.AvatarID) @@ -265,6 +314,12 @@ func (wa *WhatsAppClient) fetchGhostAvatar(ctx context.Context, ghost *bridgev2. return false } else if avatar == nil { return false + } else if wa.Main.MsgConv.DirectMedia { + wrappedAvatar, err = wa.makeDirectMediaAvatar(ctx, jid, avatar, false) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to prepare direct media avatar") + return false + } } else { wrappedAvatar = &bridgev2.Avatar{ ID: networkid.AvatarID(avatar.ID), diff --git a/pkg/connector/wadb/avatarcache.go b/pkg/connector/wadb/avatarcache.go new file mode 100644 index 00000000..8ea947f2 --- /dev/null +++ b/pkg/connector/wadb/avatarcache.go @@ -0,0 +1,51 @@ +package wadb + +import ( + "context" + + "go.mau.fi/util/dbutil" + "go.mau.fi/util/jsontime" + "go.mau.fi/whatsmeow/types" +) + +type AvatarCacheQuery struct { + *dbutil.QueryHelper[*AvatarCacheEntry] +} + +const ( + getAvatarCacheEntry = ` + SELECT entity_jid, avatar_id, direct_path, expiry, gone + FROM whatsapp_avatar_cache + WHERE entity_jid = $1 AND avatar_id = $2 + ` + putAvatarCacheEntry = ` + INSERT INTO whatsapp_avatar_cache (entity_jid, avatar_id, direct_path, expiry, gone) + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (entity_jid, avatar_id) DO UPDATE + SET direct_path = EXCLUDED.direct_path, expiry = EXCLUDED.expiry, gone = EXCLUDED.gone + ` +) + +func (acq *AvatarCacheQuery) Get(ctx context.Context, entityJID types.JID, avatarID string) (*AvatarCacheEntry, error) { + return acq.QueryOne(ctx, getAvatarCacheEntry, entityJID, avatarID) +} + +func (acq *AvatarCacheQuery) Put(ctx context.Context, entry *AvatarCacheEntry) error { + return acq.Exec(ctx, putAvatarCacheEntry, entry.sqlVariables()...) +} + +type AvatarCacheEntry struct { + EntityJID types.JID + AvatarID string + DirectPath string + Expiry jsontime.Unix + Gone bool +} + +func (ace *AvatarCacheEntry) Scan(row dbutil.Scannable) (*AvatarCacheEntry, error) { + return dbutil.ValueOrErr(ace, row.Scan(&ace.EntityJID, &ace.AvatarID, &ace.DirectPath, &ace.Expiry, &ace.Gone)) +} + +func (ace *AvatarCacheEntry) sqlVariables() []any { + return []any{ace.EntityJID, ace.AvatarID, ace.DirectPath, ace.Expiry, ace.Gone} +} diff --git a/pkg/connector/wadb/database.go b/pkg/connector/wadb/database.go index 5090980c..9d135687 100644 --- a/pkg/connector/wadb/database.go +++ b/pkg/connector/wadb/database.go @@ -15,6 +15,7 @@ type Database struct { PollOption *PollOptionQuery MediaRequest *MediaRequestQuery HSNotif *HistorySyncNotificationQuery + AvatarCache *AvatarCacheQuery } func New(bridgeID networkid.BridgeID, db *dbutil.Database, log zerolog.Logger) *Database { @@ -45,5 +46,10 @@ func New(bridgeID networkid.BridgeID, db *dbutil.Database, log zerolog.Logger) * BridgeID: bridgeID, Database: db, }, + AvatarCache: &AvatarCacheQuery{ + QueryHelper: dbutil.MakeQueryHelper(db, func(_ *dbutil.QueryHelper[*AvatarCacheEntry]) *AvatarCacheEntry { + return &AvatarCacheEntry{} + }), + }, } } diff --git a/pkg/connector/wadb/upgrades/00-latest-schema.sql b/pkg/connector/wadb/upgrades/00-latest-schema.sql index 59baff41..97275f59 100644 --- a/pkg/connector/wadb/upgrades/00-latest-schema.sql +++ b/pkg/connector/wadb/upgrades/00-latest-schema.sql @@ -1,4 +1,4 @@ --- v0 -> v6 (compatible with v3+): Latest revision +-- v0 -> v7 (compatible with v3+): Latest revision CREATE TABLE whatsapp_poll_option_id ( bridge_id TEXT NOT NULL, @@ -88,3 +88,13 @@ CREATE TABLE whatsapp_history_sync_notification ( REFERENCES user_login (bridge_id, id) ON UPDATE CASCADE ON DELETE CASCADE ); CREATE INDEX whatsapp_history_sync_notification_login_idx ON whatsapp_history_sync_notification (bridge_id, user_login_id); + +CREATE TABLE whatsapp_avatar_cache ( + entity_jid TEXT NOT NULL, + avatar_id TEXT NOT NULL, + direct_path TEXT NOT NULL, + expiry BIGINT NOT NULL, + gone BOOLEAN NOT NULL DEFAULT false, + + PRIMARY KEY (entity_jid, avatar_id) +); diff --git a/pkg/connector/wadb/upgrades/07-direct-media-avatar-cache.sql b/pkg/connector/wadb/upgrades/07-direct-media-avatar-cache.sql new file mode 100644 index 00000000..7dd9dba6 --- /dev/null +++ b/pkg/connector/wadb/upgrades/07-direct-media-avatar-cache.sql @@ -0,0 +1,10 @@ +-- v7 (compatible with v3+): Add cache for avatar URLs when using direct media +CREATE TABLE whatsapp_avatar_cache ( + entity_jid TEXT NOT NULL, + avatar_id TEXT NOT NULL, + direct_path TEXT NOT NULL, + expiry BIGINT NOT NULL, + gone BOOLEAN NOT NULL DEFAULT false, + + PRIMARY KEY (entity_jid, avatar_id) +); diff --git a/pkg/waid/mediaid.go b/pkg/waid/mediaid.go index ab94977e..646e23dd 100644 --- a/pkg/waid/mediaid.go +++ b/pkg/waid/mediaid.go @@ -17,7 +17,6 @@ package waid import ( - "bytes" "encoding/binary" "encoding/hex" "fmt" @@ -29,12 +28,20 @@ import ( "maunium.net/go/mautrix/bridgev2/networkid" ) +const ( + // Media ID types start from 255, because old media IDs didn't have a type byte and had the length at the start. + mediaIDTypeMessage = 255 + mediaIDTypeAvatar = 254 + mediaIDTypeCommunityAvatar = 253 +) + func MakeMediaID(messageInfo *types.MessageInfo, receiver networkid.UserLoginID) networkid.MediaID { compactChat := compactJID(messageInfo.Chat.ToNonAD()) compactSender := compactJID(messageInfo.Sender.ToNonAD()) receiverID := compactJID(ParseUserLoginID(receiver, 0)) compactID := compactMsgID(messageInfo.ID) - mediaID := make([]byte, 0, 3+len(compactChat)+len(compactSender)+len(compactID)) + mediaID := make([]byte, 0, 5+len(compactChat)+len(compactSender)+len(receiverID)+len(compactID)) + mediaID = append(mediaID, mediaIDTypeMessage) mediaID = append(mediaID, byte(len(compactChat))) mediaID = append(mediaID, compactChat...) mediaID = append(mediaID, byte(len(compactSender))) @@ -46,29 +53,94 @@ func MakeMediaID(messageInfo *types.MessageInfo, receiver networkid.UserLoginID) return mediaID } -func ParseMediaID(mediaID networkid.MediaID) (*ParsedMessageID, networkid.UserLoginID, error) { - reader := bytes.NewReader(mediaID) - chatJID, err := readCompact(reader, parseCompactJID) - if err != nil { - return nil, "", fmt.Errorf("failed to parse chat JID: %w", err) +func MakeAvatarMediaID(targetJID types.JID, id string, receiver networkid.UserLoginID, community bool) networkid.MediaID { + compactTarget := compactJID(targetJID.ToNonAD()) + receiverID := compactJID(ParseUserLoginID(receiver, 0)) + mediaID := make([]byte, 0, 4+len(compactTarget)+len(id)+len(receiverID)) + if community { + mediaID = append(mediaID, mediaIDTypeCommunityAvatar) + } else { + mediaID = append(mediaID, mediaIDTypeAvatar) } - senderJID, err := readCompact(reader, parseCompactJID) - if err != nil { - return nil, "", fmt.Errorf("failed to parse sender JID: %w", err) + mediaID = append(mediaID, byte(len(compactTarget))) + mediaID = append(mediaID, compactTarget...) + mediaID = append(mediaID, byte(len(id))) + mediaID = append(mediaID, id...) + mediaID = append(mediaID, byte(len(receiverID))) + mediaID = append(mediaID, receiverID...) + return mediaID +} + +type AvatarMediaInfo struct { + TargetJID types.JID + AvatarID string + Community bool +} + +type ParsedMediaID struct { + Message *ParsedMessageID + Avatar *AvatarMediaInfo + UserLogin networkid.UserLoginID +} + +func ParseMediaID(mediaID networkid.MediaID) (*ParsedMediaID, error) { + mediaIDType := mediaIDTypeMessage + if mediaID[0] > 127 { + mediaIDType = int(mediaID[0]) + mediaID = mediaID[1:] } - receiverID, err := readCompact(reader, parseCompactJID) - if err != nil { - return nil, "", fmt.Errorf("failed to parse receiver JID: %w", err) + var parsed ParsedMediaID + switch mediaIDType { + case mediaIDTypeMessage: + chatJID, err := readCompact(&mediaID, parseCompactJID) + if err != nil { + return nil, fmt.Errorf("failed to parse chat JID: %w", err) + } + senderJID, err := readCompact(&mediaID, parseCompactJID) + if err != nil { + return nil, fmt.Errorf("failed to parse sender JID: %w", err) + } + receiverID, err := readCompact(&mediaID, parseCompactJID) + if err != nil { + return nil, fmt.Errorf("failed to parse receiver JID: %w", err) + } + id, err := readCompact(&mediaID, parseCompactMsgID) + if err != nil { + return nil, fmt.Errorf("failed to parse message ID: %w", err) + } + parsed.Message = &ParsedMessageID{ + Chat: chatJID, + Sender: senderJID, + ID: id, + } + parsed.UserLogin = MakeUserLoginID(receiverID) + case mediaIDTypeAvatar, mediaIDTypeCommunityAvatar: + targetJID, err := readCompact(&mediaID, parseCompactJID) + if err != nil { + return nil, fmt.Errorf("failed to parse target JID: %w", err) + } + avatarID, err := readCompact(&mediaID, parseString) + if err != nil { + return nil, fmt.Errorf("failed to parse avatar ID: %w", err) + } + receiverID, err := readCompact(&mediaID, parseCompactJID) + if err != nil { + return nil, fmt.Errorf("failed to parse receiver JID: %w", err) + } + parsed.Avatar = &AvatarMediaInfo{ + TargetJID: targetJID, + AvatarID: avatarID, + Community: mediaIDType == mediaIDTypeCommunityAvatar, + } + parsed.UserLogin = MakeUserLoginID(receiverID) + default: + return nil, fmt.Errorf("unknown media ID type %d", mediaIDType) } - id, err := readCompact(reader, parseCompactMsgID) - if err != nil { - return nil, "", fmt.Errorf("failed to parse message ID: %w", err) - } - return &ParsedMessageID{ - Chat: chatJID, - Sender: senderJID, - ID: id, - }, MakeUserLoginID(receiverID), nil + return &parsed, nil +} + +func parseString(data []byte) (string, error) { + return string(data), nil } func isUpperHex(str string) bool { @@ -169,16 +241,16 @@ func parseCompactJID(jid []byte) (types.JID, error) { } } -func readCompact[T any](reader *bytes.Reader, fn func(data []byte) (T, error)) (T, error) { +func readCompact[T any](data *networkid.MediaID, fn func(data []byte) (T, error)) (T, error) { var defVal T - length, err := reader.ReadByte() - if err != nil { - return defVal, err + if len(*data) < 1 { + return defVal, fmt.Errorf("%w (data too short to read length)", io.ErrUnexpectedEOF) } - data := make([]byte, length) - _, err = io.ReadFull(reader, data) - if err != nil { - return defVal, err + length := int((*data)[0]) + if len(*data) < length+1 { + return defVal, fmt.Errorf("%w (wanted %d+1 bytes, only have %d)", io.ErrUnexpectedEOF, length, len(*data)) } - return fn(data) + dataToParse := (*data)[1 : length+1] + *data = (*data)[length+1:] + return fn(dataToParse) } From de08fe910984094ff665243143b872e1749b338a Mon Sep 17 00:00:00 2001 From: Kishan Bagaria <1093313+KishanBagaria@users.noreply.github.com> Date: Tue, 29 Jul 2025 20:22:17 +0530 Subject: [PATCH 121/486] handlewhatsapp: convert HD image/video dual uploads into edits (#818) looks like whatsapp is now uploading a SD version first and replacing the message with a HD version subsequently (which currently shows up as "Unknown message type, please view it on the WhatsApp app") --------- Co-authored-by: Tulir Asokan --- pkg/connector/handlewhatsapp.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 95b6f331..5023be75 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -26,6 +26,7 @@ import ( "github.com/rs/zerolog" "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow/appstate" + "go.mau.fi/whatsmeow/proto/waE2E" "go.mau.fi/whatsmeow/types" "go.mau.fi/whatsmeow/types/events" "maunium.net/go/mautrix/bridgev2" @@ -289,6 +290,27 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa wa.Client.ManualHistorySyncDownload { wa.saveWAHistorySyncNotification(ctx, evt.Message.ProtocolMessage.HistorySyncNotification) } + + messageAssoc := evt.Message.GetMessageContextInfo().GetMessageAssociation() + if assocType := messageAssoc.GetAssociationType(); assocType == waE2E.MessageAssociation_HD_IMAGE_DUAL_UPLOAD || assocType == waE2E.MessageAssociation_HD_VIDEO_DUAL_UPLOAD { + parentKey := messageAssoc.GetParentMessageKey() + associatedMessage := evt.Message.GetAssociatedChildMessage().GetMessage() + wa.UserLogin.Log.Debug(). + Str("message_id", evt.Info.ID). + Str("parent_id", parentKey.GetID()). + Stringer("assoc_type", assocType). + Msg("Received HD replacement message, converting to edit") + + protocolMsg := &waE2E.ProtocolMessage{ + Type: waE2E.ProtocolMessage_MESSAGE_EDIT.Enum(), + Key: parentKey, + EditedMessage: associatedMessage, + } + evt.Message = &waE2E.Message{ + ProtocolMessage: protocolMsg, + } + } + parsedMessageType := getMessageType(evt.Message) if parsedMessageType == "ignore" || strings.HasPrefix(parsedMessageType, "unknown_protocol_") { return From 1dfcdcb463ad621e0ce95cc4dea526bf153cd7bb Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 29 Jul 2025 17:10:25 +0200 Subject: [PATCH 122/486] directmedia: add hacky support for media edits (#819) --- pkg/connector/events.go | 1 + pkg/msgconv/from-whatsapp.go | 6 ++++++ pkg/msgconv/wa-media.go | 3 ++- pkg/waid/mediaid.go | 9 +++++++-- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 76eef7a8..6b916305 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -186,6 +186,7 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por meta.Edits = append(meta.Edits, evt.Info.ID) } + 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.Info, evt.isViewOnce(), previouslyConvertedPart) if evt.isUndecryptableUpsertSubEvent && isFailedMedia(cm) { evt.postHandle = func() { diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 1784ede1..5a590acd 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -47,6 +47,7 @@ const ( contextKeyClient contextKey = iota contextKeyIntent contextKeyPortal + ContextKeyEditTargetID ) func getClient(ctx context.Context) *whatsmeow.Client { @@ -61,6 +62,11 @@ func getPortal(ctx context.Context) *bridgev2.Portal { return ctx.Value(contextKeyPortal).(*bridgev2.Portal) } +func getEditTargetID(ctx context.Context) types.MessageID { + editID, _ := ctx.Value(ContextKeyEditTargetID).(types.MessageID) + return editID +} + func (mc *MessageConverter) getBasicUserInfo(ctx context.Context, user types.JID) (id.UserID, string, error) { ghost, err := mc.Bridge.GetGhostByID(ctx, waid.MakeUserID(user)) if err != nil { diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index abff34ec..d95d8f9c 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -90,7 +90,8 @@ func (mc *MessageConverter) convertMediaMessage( } var err error portal := getPortal(ctx) - preparedMedia.URL, err = portal.Bridge.Matrix.GenerateContentURI(ctx, waid.MakeMediaID(messageInfo, portal.Receiver)) + idOverride := getEditTargetID(ctx) + preparedMedia.URL, err = portal.Bridge.Matrix.GenerateContentURI(ctx, waid.MakeMediaID(messageInfo, idOverride, portal.Receiver)) if err != nil { panic(fmt.Errorf("failed to generate content URI: %w", err)) } diff --git a/pkg/waid/mediaid.go b/pkg/waid/mediaid.go index 646e23dd..6a94de4e 100644 --- a/pkg/waid/mediaid.go +++ b/pkg/waid/mediaid.go @@ -35,11 +35,16 @@ const ( mediaIDTypeCommunityAvatar = 253 ) -func MakeMediaID(messageInfo *types.MessageInfo, receiver networkid.UserLoginID) networkid.MediaID { +func MakeMediaID(messageInfo *types.MessageInfo, idOverride types.MessageID, receiver networkid.UserLoginID) networkid.MediaID { compactChat := compactJID(messageInfo.Chat.ToNonAD()) compactSender := compactJID(messageInfo.Sender.ToNonAD()) receiverID := compactJID(ParseUserLoginID(receiver, 0)) - compactID := compactMsgID(messageInfo.ID) + var compactID []byte + if idOverride != "" { + compactID = compactMsgID(idOverride) + } else { + compactID = compactMsgID(messageInfo.ID) + } mediaID := make([]byte, 0, 5+len(compactChat)+len(compactSender)+len(receiverID)+len(compactID)) mediaID = append(mediaID, mediaIDTypeMessage) mediaID = append(mediaID, byte(len(compactChat))) From 8e60111d2eda64b8f25eca8f1c081931a241b7d2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 29 Jul 2025 19:32:36 +0300 Subject: [PATCH 123/486] userinfo: reduce risk of races when syncing contacts --- pkg/connector/backfill.go | 7 ++++++- pkg/connector/client.go | 2 ++ pkg/connector/commands.go | 4 ++-- pkg/connector/handlewhatsapp.go | 2 +- pkg/connector/userinfo.go | 29 +++++++++++++++++++++++------ 5 files changed, 34 insertions(+), 10 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 78a5a4f4..96c51c3c 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -157,7 +157,12 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor if evt.GetGlobalSettings() != nil { log.Debug().Interface("global_settings", evt.GetGlobalSettings()).Msg("Got global settings in history sync") } - if evt.GetSyncType() == waHistorySync.HistorySync_INITIAL_STATUS_V3 || evt.GetSyncType() == waHistorySync.HistorySync_PUSH_NAME || evt.GetSyncType() == waHistorySync.HistorySync_NON_BLOCKING_DATA { + if evt.GetSyncType() == waHistorySync.HistorySync_INITIAL_STATUS_V3 || + evt.GetSyncType() == waHistorySync.HistorySync_PUSH_NAME || + evt.GetSyncType() == waHistorySync.HistorySync_NON_BLOCKING_DATA { + if evt.GetSyncType() == waHistorySync.HistorySync_PUSH_NAME { + wa.pushNamesSynced.Set() + } log.Debug(). Int("conversation_count", len(evt.GetConversations())). Int("pushname_count", len(evt.GetPushnames())). diff --git a/pkg/connector/client.go b/pkg/connector/client.go index bfc03cf2..0750526d 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -25,6 +25,7 @@ import ( "time" "github.com/rs/zerolog" + "go.mau.fi/util/exsync" "go.mau.fi/whatsmeow" waBinary "go.mau.fi/whatsmeow/binary" "go.mau.fi/whatsmeow/proto/waHistorySync" @@ -112,6 +113,7 @@ type WhatsAppClient struct { mediaRetryLock *semaphore.Weighted offlineSyncWaiter chan error isNewLogin bool + pushNamesSynced exsync.Event lastPresence types.Presence } diff --git a/pkg/connector/commands.go b/pkg/connector/commands.go index 3017b157..88687411 100644 --- a/pkg/connector/commands.go +++ b/pkg/connector/commands.go @@ -135,10 +135,10 @@ func fnSync(ce *commands.Event) { } ce.Reply("Queued syncs for %d groups", len(groups)) case "contacts": - wa.resyncContacts(false) + wa.resyncContacts(false, false) ce.React("✅") case "contacts-with-avatars": - wa.resyncContacts(true) + wa.resyncContacts(true, false) ce.React("✅") case "appstate": for _, name := range appstate.AllPatchNames { diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 5023be75..22075402 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -135,7 +135,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { } go wa.syncRemoteProfile(log.WithContext(context.Background()), nil) } else if evt.Name == appstate.WAPatchCriticalUnblockLow { - go wa.resyncContacts(false) + go wa.resyncContacts(false, true) } case *events.AppState: // Intentionally ignored diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 320a90a8..42763cf0 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -331,20 +331,37 @@ func (wa *WhatsAppClient) fetchGhostAvatar(ctx context.Context, ghost *bridgev2. return ghost.UpdateAvatar(ctx, wrappedAvatar) } -func (wa *WhatsAppClient) resyncContacts(forceAvatarSync bool) { +func (wa *WhatsAppClient) resyncContacts(forceAvatarSync, automatic bool) { log := wa.UserLogin.Log.With().Str("action", "resync contacts").Logger() - ctx := log.WithContext(context.Background()) - contacts, err := wa.GetStore().Contacts.GetAllContacts(ctx) + ctx := log.WithContext(wa.Main.Bridge.BackgroundCtx) + if automatic && wa.isNewLogin { + log.Debug().Msg("Waiting for push name history sync before resyncing contacts") + timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Minute) + _ = wa.pushNamesSynced.Wait(timeoutCtx) + cancel() + if ctx.Err() != nil { + return + } + } + contactStore := wa.GetStore().Contacts + contacts, err := contactStore.GetAllContacts(ctx) if err != nil { log.Err(err).Msg("Failed to get cached contacts") return } log.Info().Int("contact_count", len(contacts)).Msg("Resyncing displaynames with contact info") - for jid, contact := range contacts { + for jid := range contacts { + if ctx.Err() != nil { + return + } ghost, err := wa.Main.Bridge.GetGhostByID(ctx, waid.MakeUserID(jid)) if err != nil { - log.Err(err).Msg("Failed to get ghost") - } else if ghost != nil { + log.Err(err).Stringer("jid", jid).Msg("Failed to get ghost") + // Refetch contact info from the store to reduce the risk of races. + // This should always hit the cache. + } else if contact, err := contactStore.GetContact(ctx, jid); err != nil { + log.Err(err).Stringer("jid", jid).Msg("Failed to get contact info") + } else { userInfo := wa.contactToUserInfo(ctx, jid, contact, forceAvatarSync || ghost.AvatarID == "") ghost.UpdateInfo(ctx, userInfo) wa.syncAltGhostWithInfo(ctx, jid, userInfo) From 2624702a39a428da9d7d7fff2fdf86325a4a120e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 30 Jul 2025 13:27:16 +0300 Subject: [PATCH 124/486] client: fix potential deadlock in background connections --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/client.go | 18 +++++++++++++----- pkg/connector/handlewhatsapp.go | 5 ++++- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 8cc51141..329e187e 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.9-0.20250723171559-474867266038 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250729133431-9166d862a88c + go.mau.fi/whatsmeow v0.0.0-20250730102611-88501e9b1125 golang.org/x/image v0.29.0 golang.org/x/net v0.42.0 golang.org/x/sync v0.16.0 diff --git a/go.sum b/go.sum index d89d11ea..d642bdea 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.9-0.20250723171559-474867266038 h1:RVL8TVaYc3LTBBopfjCNDtD+6 go.mau.fi/util v0.8.9-0.20250723171559-474867266038/go.mod h1:GZZp5f9r2MgEu4GDvtB0XxCF7i6Z7Z8fM0w9a5oZH3Y= 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-20250729133431-9166d862a88c h1:Pms0CGY/5akaS2cl9NwkgZhmoaPAFkeaVUkXDf09Ovc= -go.mau.fi/whatsmeow v0.0.0-20250729133431-9166d862a88c/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= +go.mau.fi/whatsmeow v0.0.0-20250730102611-88501e9b1125 h1:KkmlnwgYizUr+E5V+hjFRuwouKnXpHizF/dz00qeVr0= +go.mau.fi/whatsmeow v0.0.0-20250730102611-88501e9b1125/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 0750526d..f4dd100a 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -111,7 +111,7 @@ type WhatsAppClient struct { directMediaRetries map[networkid.MessageID]*directMediaRetry directMediaLock sync.Mutex mediaRetryLock *semaphore.Weighted - offlineSyncWaiter chan error + offlineSyncWaiter atomic.Pointer[chan error] isNewLogin bool pushNamesSynced exsync.Event lastPresence types.Presence @@ -209,8 +209,14 @@ func (wa *WhatsAppClient) Connect(ctx context.Context) { } func (wa *WhatsAppClient) notifyOfflineSyncWaiter(err error) { - if wa.offlineSyncWaiter != nil { - wa.offlineSyncWaiter <- err + if ch := wa.offlineSyncWaiter.Load(); ch != nil { + select { + case *ch <- err: + default: + wa.UserLogin.Log.Warn(). + AnErr("dropped_error", err). + Msg("Offline sync waiter channel was full, dropping input") + } } } @@ -232,7 +238,9 @@ func (wa *WhatsAppClient) ConnectBackground(ctx context.Context, params *bridgev return bridgev2.ErrNotLoggedIn } wa.Client.BackgroundEventCtx = wa.UserLogin.Log.WithContext(wa.Main.Bridge.BackgroundCtx) - wa.offlineSyncWaiter = make(chan error) + ch := make(chan error, 1) + wa.offlineSyncWaiter.Store(&ch) + defer wa.offlineSyncWaiter.Store(nil) wa.Main.backgroundConnectOnce.Do(wa.Main.onFirstBackgroundConnect) if err := wa.Main.updateProxy(ctx, wa.Client, false); err != nil { zerolog.Ctx(ctx).Err(err).Msg("Failed to update proxy") @@ -253,7 +261,7 @@ func (wa *WhatsAppClient) ConnectBackground(ctx context.Context, params *bridgev select { case <-ctx.Done(): return ctx.Err() - case err = <-wa.offlineSyncWaiter: + case err = <-ch: if err == nil { var data wrappedPushNotificationData err = json.Unmarshal(params.RawData, &data) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 22075402..e8a0d5a3 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -180,10 +180,13 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { case *events.OfflineSyncCompleted: if !wa.PhoneRecentlySeen(true) { log.Info(). + Int("evt_count", evt.Count). Time("phone_last_seen", wa.UserLogin.Metadata.(*waid.UserLoginMetadata).PhoneLastSeen.Time). Msg("Offline sync completed, but phone last seen date is still old") } else { - log.Info().Msg("Offline sync completed") + log.Info(). + Int("evt_count", evt.Count). + Msg("Offline sync completed") } wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected}) wa.notifyOfflineSyncWaiter(nil) From c21648f31e7bb84521a35d73b1ce10ffc77f3a2b Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 31 Jul 2025 12:38:47 +0300 Subject: [PATCH 125/486] backfill: unwrap associated child messages --- pkg/connector/backfill.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 96c51c3c..34322cea 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -518,6 +518,11 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet func (wa *WhatsAppClient) convertHistorySyncMessage( ctx context.Context, portal *bridgev2.Portal, info *types.MessageInfo, msg *waE2E.Message, isViewOnce bool, reactions []*waWeb.Reaction, ) (*bridgev2.BackfillMessage, *wadb.MediaRequest) { + // New messages turn these into edits, but in backfill we only have the last version, + // so no need to do the edit thing. Instead, just unwrap the message. + if msg.GetAssociatedChildMessage().GetMessage() != nil { + msg = msg.GetAssociatedChildMessage().GetMessage() + } // TODO use proper intent intent := wa.Main.Bridge.Bot wrapped := &bridgev2.BackfillMessage{ From 81875864edafa3a5ba79a13917fa4459652a2363 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 31 Jul 2025 14:28:07 +0300 Subject: [PATCH 126/486] 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 329e187e..50c417ce 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( golang.org/x/sync v0.16.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.3-0.20250729144204-bcf92ba0e80a + maunium.net/go/mautrix v0.24.3-0.20250731110000-94f53c5853c6 ) require ( diff --git a/go.sum b/go.sum index d642bdea..7b921bf1 100644 --- a/go.sum +++ b/go.sum @@ -101,5 +101,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.24.3-0.20250729144204-bcf92ba0e80a h1:ze+5l/XKp38/MgQJCxW5ic/eiZq3YeZ0QPS6M36qmC8= -maunium.net/go/mautrix v0.24.3-0.20250729144204-bcf92ba0e80a/go.mod h1:KrE/TdIeAo6cfAUICmbaiZ18UgHimTOozAGOrioK9SU= +maunium.net/go/mautrix v0.24.3-0.20250731110000-94f53c5853c6 h1:27wTzsIxqJ+xzqcGGSXfe3MGXHPgzmWHO2+lw7V8Tdk= +maunium.net/go/mautrix v0.24.3-0.20250731110000-94f53c5853c6/go.mod h1:KrE/TdIeAo6cfAUICmbaiZ18UgHimTOozAGOrioK9SU= From 262e7fc79311a73eca6cdbc379d3f62aafdbc4f3 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 31 Jul 2025 14:54:13 +0300 Subject: [PATCH 127/486] Revert "msgconv/from-matrix: try to escape formatting characters in plain text" This reverts commit d61c197be50dda58e6b88bdae2913ba79d1a934e. --- pkg/msgconv/from-matrix.go | 9 +-------- pkg/msgconv/msgconv.go | 3 --- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index edf33f1e..105c1c2d 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -279,13 +279,6 @@ func (mc *MessageConverter) constructMediaMessage( } } -var hackyFormattingEscaper = strings.NewReplacer( - "~", "\u200b~", - "_", "\u200b_", - "*", "\u200b*", - "`", "\u200b`", -) - func (mc *MessageConverter) parseText(ctx context.Context, content *event.MessageEventContent) (text string, mentions []string) { mentions = make([]string, 0) @@ -295,7 +288,7 @@ func (mc *MessageConverter) parseText(ctx context.Context, content *event.Messag if content.Format == event.FormatHTML { text = mc.HTMLParser.Parse(content.FormattedBody, parseCtx) } else { - text = hackyFormattingEscaper.Replace(content.Body) + text = content.Body } return } diff --git a/pkg/msgconv/msgconv.go b/pkg/msgconv/msgconv.go index daa00b50..e4109c8e 100644 --- a/pkg/msgconv/msgconv.go +++ b/pkg/msgconv/msgconv.go @@ -69,9 +69,6 @@ func New(br *bridgev2.Bridge) *MessageConverter { MonospaceBlockConverter: func(code, language string, ctx format.Context) string { return "```\n" + code + "\n```" }, - TextConverter: func(s string, context format.Context) string { - return hackyFormattingEscaper.Replace(s) - }, } return mc } From 56c157ad5e952e3cfcb681a71b783cdacd74f1d3 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 31 Jul 2025 17:20:56 +0300 Subject: [PATCH 128/486] client: fix push name syncing flag --- pkg/connector/client.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index f4dd100a..a0951f85 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -52,6 +52,7 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. resyncQueue: make(map[types.JID]resyncQueueItem), directMediaRetries: make(map[networkid.MessageID]*directMediaRetry), mediaRetryLock: semaphore.NewWeighted(wa.Config.HistorySync.MediaRequests.MaxAsyncHandle), + pushNamesSynced: exsync.NewEvent(), } login.Client = w @@ -113,7 +114,7 @@ type WhatsAppClient struct { mediaRetryLock *semaphore.Weighted offlineSyncWaiter atomic.Pointer[chan error] isNewLogin bool - pushNamesSynced exsync.Event + pushNamesSynced *exsync.Event lastPresence types.Presence } From 394622e20545b5f978e432663f6733a92ff63321 Mon Sep 17 00:00:00 2001 From: Brad Murray Date: Thu, 31 Jul 2025 13:07:22 -0400 Subject: [PATCH 129/486] login: add better error codes for invalid login inputs (#821) --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/login.go | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 50c417ce..f423f493 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.9-0.20250723171559-474867266038 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250730102611-88501e9b1125 + go.mau.fi/whatsmeow v0.0.0-20250731124915-c8a3f7009971 golang.org/x/image v0.29.0 golang.org/x/net v0.42.0 golang.org/x/sync v0.16.0 diff --git a/go.sum b/go.sum index 7b921bf1..c0eeac98 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.9-0.20250723171559-474867266038 h1:RVL8TVaYc3LTBBopfjCNDtD+6 go.mau.fi/util v0.8.9-0.20250723171559-474867266038/go.mod h1:GZZp5f9r2MgEu4GDvtB0XxCF7i6Z7Z8fM0w9a5oZH3Y= 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-20250730102611-88501e9b1125 h1:KkmlnwgYizUr+E5V+hjFRuwouKnXpHizF/dz00qeVr0= -go.mau.fi/whatsmeow v0.0.0-20250730102611-88501e9b1125/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= +go.mau.fi/whatsmeow v0.0.0-20250731124915-c8a3f7009971 h1:7rtwNU41DumwGNAKlPHgbyLau2siuFhQDsJ3OO8uN3g= +go.mau.fi/whatsmeow v0.0.0-20250731124915-c8a3f7009971/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= diff --git a/pkg/connector/login.go b/pkg/connector/login.go index ebf30600..24423568 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -2,6 +2,7 @@ package connector import ( "context" + "errors" "fmt" "net/http" "sync/atomic" @@ -63,6 +64,16 @@ var ( Err: "Unexpected event while waiting for login", StatusCode: http.StatusInternalServerError, } + ErrPhoneNumberTooShort = bridgev2.RespError{ + ErrCode: "FI.MAU.WHATSAPP.PHONE_NUMBER_TOO_SHORT", + Err: "Phone number too short", + StatusCode: http.StatusBadRequest, + } + ErrPhoneNumberIsNotInternational = bridgev2.RespError{ + ErrCode: "FI.MAU.WHATSAPP.PHONE_NUMBER_NOT_INTERNATIONAL", + Err: "Phone number must be in international format", + StatusCode: http.StatusBadRequest, + } ) func (wa *WhatsAppConnector) CreateLogin(_ context.Context, user *bridgev2.User, flowID string) (bridgev2.LoginProcess, error) { @@ -181,6 +192,11 @@ func (wl *WALogin) SubmitUserInput(ctx context.Context, input map[string]string) pairingCode, err := wl.Client.PairPhone(ctx, 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) { + return nil, ErrPhoneNumberTooShort + } else if errors.Is(err, whatsmeow.ErrPhoneNumberIsNotInternational) { + return nil, ErrPhoneNumberIsNotInternational + } return nil, err } wl.Log.Debug().Msg("Phone code login started") From 40fe582cf91498ec74aa107654c3d0c0717fec96 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 1 Aug 2025 11:37:34 +0300 Subject: [PATCH 130/486] 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 f423f493..c0f0db26 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( golang.org/x/sync v0.16.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.3-0.20250731110000-94f53c5853c6 + maunium.net/go/mautrix v0.24.3-0.20250801084753-196164ed6749 ) require ( diff --git a/go.sum b/go.sum index c0eeac98..96dd94c0 100644 --- a/go.sum +++ b/go.sum @@ -101,5 +101,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.24.3-0.20250731110000-94f53c5853c6 h1:27wTzsIxqJ+xzqcGGSXfe3MGXHPgzmWHO2+lw7V8Tdk= -maunium.net/go/mautrix v0.24.3-0.20250731110000-94f53c5853c6/go.mod h1:KrE/TdIeAo6cfAUICmbaiZ18UgHimTOozAGOrioK9SU= +maunium.net/go/mautrix v0.24.3-0.20250801084753-196164ed6749 h1:eEIsN9CufSHs4SeRjUwIpJo3n8QaX0rfO2sEERjPkKY= +maunium.net/go/mautrix v0.24.3-0.20250801084753-196164ed6749/go.mod h1:KrE/TdIeAo6cfAUICmbaiZ18UgHimTOozAGOrioK9SU= From 3cefbb2cfc41656a535f4905d69ad07dd557bd55 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 3 Aug 2025 19:51:53 +0300 Subject: [PATCH 131/486] msgconv/from-matrix: restore passthrough of animated webp stickers --- pkg/msgconv/from-matrix.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 105c1c2d..a71824a7 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -440,11 +440,16 @@ func (mc *MessageConverter) reuploadFileToWhatsApp( var size int data, size, err = mc.convertToWebP(data) if err != nil { - return nil, nil, "image/webp", fmt.Errorf("%w (to webp): %w", bridgev2.ErrMediaConvertFailed, err) + if mime != "image/webp" { + return nil, nil, "image/webp", fmt.Errorf("%w (to webp): %w", bridgev2.ErrMediaConvertFailed, err) + } else { + zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to add padding to webp, continuing with original file") + } + } else { + content.Info.Width = size + content.Info.Height = size + mime = "image/webp" } - content.Info.Width = size - content.Info.Height = size - mime = "image/webp" } case event.MsgImage: mediaType = whatsmeow.MediaImage From 18684ac5b272f46b4f23651854b4834c483c3c79 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 5 Aug 2025 12:48:41 +0300 Subject: [PATCH 132/486] userinfo: update whatsmeow to fix new group member names in certain cases --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/userinfo.go | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index c0f0db26..91fb60ef 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.9-0.20250723171559-474867266038 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250731124915-c8a3f7009971 + go.mau.fi/whatsmeow v0.0.0-20250805094724-a2272061b926 golang.org/x/image v0.29.0 golang.org/x/net v0.42.0 golang.org/x/sync v0.16.0 diff --git a/go.sum b/go.sum index 96dd94c0..6dd6c583 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.9-0.20250723171559-474867266038 h1:RVL8TVaYc3LTBBopfjCNDtD+6 go.mau.fi/util v0.8.9-0.20250723171559-474867266038/go.mod h1:GZZp5f9r2MgEu4GDvtB0XxCF7i6Z7Z8fM0w9a5oZH3Y= 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-20250731124915-c8a3f7009971 h1:7rtwNU41DumwGNAKlPHgbyLau2siuFhQDsJ3OO8uN3g= -go.mau.fi/whatsmeow v0.0.0-20250731124915-c8a3f7009971/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= +go.mau.fi/whatsmeow v0.0.0-20250805094724-a2272061b926 h1:ZEY7vEnglugWtLwmRDsmVsyThdrdLhcNZAhTbAmGuzs= +go.mau.fi/whatsmeow v0.0.0-20250805094724-a2272061b926/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 42763cf0..bfa98654 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -199,7 +199,9 @@ func (wa *WhatsAppClient) contactToUserInfo(ctx context.Context, jid types.JID, pnJID, err := wa.GetStore().LIDs.GetPNForLID(ctx, jid) if err != nil { zerolog.Ctx(ctx).Err(err).Stringer("lid", jid).Msg("Failed to get PN for LID") - } else if !pnJID.IsEmpty() { + } else if pnJID.IsEmpty() { + zerolog.Ctx(ctx).Debug().Stringer("lid", jid).Msg("Phone number not found for LID in contactToUserInfo") + } else { phone = "+" + pnJID.User extraContact, err := wa.GetStore().Contacts.GetContact(ctx, pnJID) if err != nil { From 38ccb929011084421e909b577b081befa02708a2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 6 Aug 2025 16:37:23 +0300 Subject: [PATCH 133/486] msgconv/from-whatsapp: include raw data in unknown message info --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/backfill.go | 8 +++++--- pkg/connector/events.go | 8 ++++++-- pkg/msgconv/from-whatsapp.go | 3 ++- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 91fb60ef..9c30a0c0 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.9-0.20250723171559-474867266038 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250805094724-a2272061b926 + go.mau.fi/whatsmeow v0.0.0-20250806133617-13d563e58486 golang.org/x/image v0.29.0 golang.org/x/net v0.42.0 golang.org/x/sync v0.16.0 diff --git a/go.sum b/go.sum index 6dd6c583..ea959caf 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.9-0.20250723171559-474867266038 h1:RVL8TVaYc3LTBBopfjCNDtD+6 go.mau.fi/util v0.8.9-0.20250723171559-474867266038/go.mod h1:GZZp5f9r2MgEu4GDvtB0XxCF7i6Z7Z8fM0w9a5oZH3Y= 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-20250805094724-a2272061b926 h1:ZEY7vEnglugWtLwmRDsmVsyThdrdLhcNZAhTbAmGuzs= -go.mau.fi/whatsmeow v0.0.0-20250805094724-a2272061b926/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= +go.mau.fi/whatsmeow v0.0.0-20250806133617-13d563e58486 h1:JvjaCc5uM5sdzRx0keOB88Tjv2mJu+i5rLVW7vK0iyg= +go.mau.fi/whatsmeow v0.0.0-20250806133617-13d563e58486/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 34322cea..dce00382 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -471,7 +471,9 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet } var mediaReq *wadb.MediaRequest isViewOnce := evt.IsViewOnce || evt.IsViewOnceV2 || evt.IsViewOnceV2Extension - convertedMessages[i], mediaReq = wa.convertHistorySyncMessage(ctx, params.Portal, &evt.Info, evt.Message, isViewOnce, msg.Reactions) + convertedMessages[i], mediaReq = wa.convertHistorySyncMessage( + ctx, params.Portal, &evt.Info, evt.Message, evt.RawMessage, isViewOnce, msg.Reactions, + ) if mediaReq != nil { mediaRequests = append(mediaRequests, mediaReq) } @@ -516,7 +518,7 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet } func (wa *WhatsAppClient) convertHistorySyncMessage( - ctx context.Context, portal *bridgev2.Portal, info *types.MessageInfo, msg *waE2E.Message, isViewOnce bool, reactions []*waWeb.Reaction, + ctx context.Context, portal *bridgev2.Portal, info *types.MessageInfo, msg, rawMsg *waE2E.Message, isViewOnce bool, reactions []*waWeb.Reaction, ) (*bridgev2.BackfillMessage, *wadb.MediaRequest) { // New messages turn these into edits, but in backfill we only have the last version, // so no need to do the edit thing. Instead, just unwrap the message. @@ -526,7 +528,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, info, isViewOnce, nil), + ConvertedMessage: wa.Main.MsgConv.ToMatrix(ctx, portal, wa.Client, intent, msg, rawMsg, info, isViewOnce, 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 6b916305..2917f91a 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -187,7 +187,9 @@ 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.Info, evt.isViewOnce(), previouslyConvertedPart) + cm := evt.wa.Main.MsgConv.ToMatrix( + ctx, portal, evt.wa.Client, intent, editedMsg, evt.MsgEvent.RawMessage, &evt.Info, evt.isViewOnce(), previouslyConvertedPart, + ) if evt.isUndecryptableUpsertSubEvent && isFailedMedia(cm) { evt.postHandle = func() { evt.wa.processFailedMedia(ctx, portal.PortalKey, evt.GetID(), cm, false) @@ -262,7 +264,9 @@ 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) - converted := evt.wa.Main.MsgConv.ToMatrix(ctx, portal, evt.wa.Client, intent, evt.Message, &evt.Info, evt.isViewOnce(), nil) + converted := evt.wa.Main.MsgConv.ToMatrix( + ctx, portal, evt.wa.Client, intent, evt.Message, evt.MsgEvent.RawMessage, &evt.Info, evt.isViewOnce(), nil, + ) if isFailedMedia(converted) { evt.postHandle = func() { evt.wa.processFailedMedia(ctx, portal.PortalKey, evt.GetID(), converted, false) diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 5a590acd..401a19d6 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -134,6 +134,7 @@ func (mc *MessageConverter) ToMatrix( client *whatsmeow.Client, intent bridgev2.MatrixAPI, waMsg *waE2E.Message, + rawWaMsg *waE2E.Message, info *types.MessageInfo, isViewOnce bool, previouslyConvertedPart *bridgev2.ConvertedMessagePart, @@ -209,7 +210,7 @@ func (mc *MessageConverter) ToMatrix( case waMsg.EncCommentMessage != nil: part = failedCommentPart default: - part, contextInfo = mc.convertUnknownMessage(ctx, waMsg) + part, contextInfo = mc.convertUnknownMessage(ctx, rawWaMsg) } part.Content.Mentions = &event.Mentions{} From 1b65e1aa45741727fba9546060b4acaeff94f955 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 7 Aug 2025 10:22:08 +0300 Subject: [PATCH 134/486] 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 9c30a0c0..b5c59f25 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.9-0.20250723171559-474867266038 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250806133617-13d563e58486 + go.mau.fi/whatsmeow v0.0.0-20250807072145-72ce90b82194 golang.org/x/image v0.29.0 golang.org/x/net v0.42.0 golang.org/x/sync v0.16.0 diff --git a/go.sum b/go.sum index ea959caf..adb9935f 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.9-0.20250723171559-474867266038 h1:RVL8TVaYc3LTBBopfjCNDtD+6 go.mau.fi/util v0.8.9-0.20250723171559-474867266038/go.mod h1:GZZp5f9r2MgEu4GDvtB0XxCF7i6Z7Z8fM0w9a5oZH3Y= 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-20250806133617-13d563e58486 h1:JvjaCc5uM5sdzRx0keOB88Tjv2mJu+i5rLVW7vK0iyg= -go.mau.fi/whatsmeow v0.0.0-20250806133617-13d563e58486/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= +go.mau.fi/whatsmeow v0.0.0-20250807072145-72ce90b82194 h1:/ow/oKzvxxwJEyCJ4bq8U7W2yar2f0HSq89yto+sD9Q= +go.mau.fi/whatsmeow v0.0.0-20250807072145-72ce90b82194/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= From c160ecbf16b9c63fb26a0cd55ec78631b5a7d7b2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 11 Aug 2025 17:17:32 +0300 Subject: [PATCH 135/486] handlewhatsapp: fix lid rerouting for undecryptable messages --- go.mod | 2 +- go.sum | 4 +-- pkg/connector/handlewhatsapp.go | 49 ++++++++++++++++++--------------- 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index b5c59f25..172dd54c 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.8.9-0.20250723171559-474867266038 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250807072145-72ce90b82194 + go.mau.fi/whatsmeow v0.0.0-20250811141640-b804d10c54c2 golang.org/x/image v0.29.0 golang.org/x/net v0.42.0 golang.org/x/sync v0.16.0 diff --git a/go.sum b/go.sum index adb9935f..ad1b4ebf 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.8.9-0.20250723171559-474867266038 h1:RVL8TVaYc3LTBBopfjCNDtD+6 go.mau.fi/util v0.8.9-0.20250723171559-474867266038/go.mod h1:GZZp5f9r2MgEu4GDvtB0XxCF7i6Z7Z8fM0w9a5oZH3Y= 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-20250807072145-72ce90b82194 h1:/ow/oKzvxxwJEyCJ4bq8U7W2yar2f0HSq89yto+sD9Q= -go.mau.fi/whatsmeow v0.0.0-20250807072145-72ce90b82194/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= +go.mau.fi/whatsmeow v0.0.0-20250811141640-b804d10c54c2 h1:pPht2EbpwrDKrMKW7t67tpx+6BsFcJyIPy5R45Bu0dk= +go.mau.fi/whatsmeow v0.0.0-20250811141640-b804d10c54c2/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index e8a0d5a3..3e22f4ed 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -85,7 +85,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { case *events.ChatPresence: wa.handleWAChatPresence(ctx, evt) case *events.UndecryptableMessage: - success = wa.handleWAUndecryptableMessage(evt) + success = wa.handleWAUndecryptableMessage(ctx, evt) case *events.CallOffer: success = wa.handleWACallStart(ctx, evt.GroupJID, evt.CallCreator, evt.CallID, "", evt.Timestamp) @@ -247,39 +247,43 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { return } -func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Message) (success bool) { - success = true - if evt.Info.Chat.Server == types.HiddenUserServer && evt.Info.Sender.ToNonAD() == evt.Info.Chat && evt.Info.SenderAlt.Server == types.DefaultUserServer { +func (wa *WhatsAppClient) rerouteWAMessage(ctx context.Context, info *types.MessageInfo) { + if info.Chat.Server == types.HiddenUserServer && info.Sender.ToNonAD() == info.Chat && info.SenderAlt.Server == types.DefaultUserServer { wa.UserLogin.Log.Debug(). - Stringer("lid", evt.Info.Sender). - Stringer("pn", evt.Info.SenderAlt). - Str("message_id", evt.Info.ID). + Stringer("lid", info.Sender). + Stringer("pn", info.SenderAlt). + Str("message_id", info.ID). Msg("Forced LID DM sender to phone number in incoming message") - evt.Info.Sender, evt.Info.SenderAlt = evt.Info.SenderAlt, evt.Info.Sender - evt.Info.Chat = evt.Info.Sender.ToNonAD() - } else if evt.Info.Chat.Server == types.HiddenUserServer && evt.Info.IsFromMe && evt.Info.RecipientAlt.Server == types.DefaultUserServer { + 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", evt.Info.Chat). - Stringer("pn", evt.Info.RecipientAlt). - Str("message_id", evt.Info.ID). + Stringer("lid", info.Chat). + Stringer("pn", info.RecipientAlt). + Str("message_id", info.ID). Msg("Forced LID DM sender to phone number in own message sent from another device") - evt.Info.Chat = evt.Info.RecipientAlt.ToNonAD() - } else if evt.Info.Sender.Server == types.BotServer && evt.Info.Chat.Server == types.HiddenUserServer { - chatPN, err := wa.Device.LIDs.GetPNForLID(ctx, evt.Info.Chat) + info.Chat = info.RecipientAlt.ToNonAD() + } else if info.Sender.Server == types.BotServer && info.Chat.Server == types.HiddenUserServer { + chatPN, err := wa.Device.LIDs.GetPNForLID(ctx, info.Chat) if err != nil { wa.UserLogin.Log.Err(err). - Str("message_id", evt.Info.ID). - Stringer("lid", evt.Info.Chat). + Str("message_id", info.ID). + Stringer("lid", info.Chat). Msg("Failed to get phone number of DM for incoming bot message") } else if !chatPN.IsEmpty() { wa.UserLogin.Log.Debug(). - Stringer("lid", evt.Info.Chat). + Stringer("lid", info.Chat). Stringer("pn", chatPN). - Str("message_id", evt.Info.ID). + Str("message_id", info.ID). Msg("Forced LID chat to phone number in bot message") - evt.Info.Chat = chatPN + info.Chat = chatPN } } +} + +func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Message) (success bool) { + success = true + wa.rerouteWAMessage(ctx, &evt.Info) wa.UserLogin.Log.Trace(). Any("info", evt.Info). Any("payload", evt.Message). @@ -349,7 +353,8 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa return res.Success } -func (wa *WhatsAppClient) handleWAUndecryptableMessage(evt *events.UndecryptableMessage) bool { +func (wa *WhatsAppClient) handleWAUndecryptableMessage(ctx context.Context, evt *events.UndecryptableMessage) bool { + wa.rerouteWAMessage(ctx, &evt.Info) wa.UserLogin.Log.Debug(). Any("info", evt.Info). Bool("unavailable", evt.IsUnavailable). From b92fb2033666c46bb453abce3240617736c12922 Mon Sep 17 00:00:00 2001 From: Kishan Bagaria <1093313+KishanBagaria@users.noreply.github.com> Date: Fri, 15 Aug 2025 19:29:44 +0530 Subject: [PATCH 136/486] msgconv/from-whatsapp: add better placeholder for pinning messages (#825) --- pkg/msgconv/from-whatsapp.go | 2 ++ pkg/msgconv/wa-misc.go | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 401a19d6..e6f8d725 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -175,6 +175,8 @@ func (mc *MessageConverter) ToMatrix( part, contextInfo = mc.convertPollUpdateMessage(ctx, info, waMsg.PollUpdateMessage) case waMsg.EventMessage != nil: part, contextInfo = mc.convertEventMessage(ctx, waMsg.EventMessage) + case waMsg.PinInChatMessage != nil: + part, contextInfo = mc.convertPinInChatMessage(ctx, waMsg.PinInChatMessage) case waMsg.ImageMessage != nil: part, contextInfo = mc.convertMediaMessage(ctx, waMsg.ImageMessage, "photo", info, isViewOnce, previouslyConvertedPart) case waMsg.StickerMessage != nil: diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index 2b11542d..24454c6d 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -214,3 +214,18 @@ func (mc *MessageConverter) convertEventMessage(ctx context.Context, msg *waE2E. Content: &content, }, msg.GetContextInfo() } + +func (mc *MessageConverter) convertPinInChatMessage(ctx context.Context, msg *waE2E.PinInChatMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { + body := "Pinned a message" + if msg.GetType() == waE2E.PinInChatMessage_UNPIN_FOR_ALL { + body = "Unpinned a message" + } + + return &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: &event.MessageEventContent{ + MsgType: event.MsgNotice, + Body: body, + }, + }, nil +} From f71349fcc433963cd17b5b7591ec70b02e6aef56 Mon Sep 17 00:00:00 2001 From: Kishan Bagaria <1093313+KishanBagaria@users.noreply.github.com> Date: Fri, 15 Aug 2025 20:00:47 +0530 Subject: [PATCH 137/486] msgconv/from-whatsapp: add better placeholder for kept messages (#826) --- pkg/msgconv/from-whatsapp.go | 2 ++ pkg/msgconv/wa-misc.go | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index e6f8d725..07805d62 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -177,6 +177,8 @@ func (mc *MessageConverter) ToMatrix( part, contextInfo = mc.convertEventMessage(ctx, waMsg.EventMessage) case waMsg.PinInChatMessage != nil: part, contextInfo = mc.convertPinInChatMessage(ctx, waMsg.PinInChatMessage) + case waMsg.KeepInChatMessage != nil: + part, contextInfo = mc.convertKeepInChatMessage(ctx, waMsg.KeepInChatMessage) case waMsg.ImageMessage != nil: part, contextInfo = mc.convertMediaMessage(ctx, waMsg.ImageMessage, "photo", info, isViewOnce, previouslyConvertedPart) case waMsg.StickerMessage != nil: diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index 24454c6d..ef5fc101 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -229,3 +229,18 @@ func (mc *MessageConverter) convertPinInChatMessage(ctx context.Context, msg *wa }, }, nil } + +func (mc *MessageConverter) convertKeepInChatMessage(ctx context.Context, msg *waE2E.KeepInChatMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { + body := "Kept a message" + if msg.GetKeepType() == waE2E.KeepType_UNDO_KEEP_FOR_ALL { + body = "Unkept a message" + } + + return &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: &event.MessageEventContent{ + MsgType: event.MsgNotice, + Body: body, + }, + }, nil +} From ec57ea91aaf2d1899130b5866ed1ffe2a8758808 Mon Sep 17 00:00:00 2001 From: Kishan Bagaria <1093313+KishanBagaria@users.noreply.github.com> Date: Fri, 15 Aug 2025 20:29:57 +0530 Subject: [PATCH 138/486] msgconv/from-whatsapp: add better support for meta ai responses (#827) --- pkg/msgconv/from-whatsapp.go | 2 ++ pkg/msgconv/wa-misc.go | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 07805d62..de149286 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -179,6 +179,8 @@ func (mc *MessageConverter) ToMatrix( part, contextInfo = mc.convertPinInChatMessage(ctx, waMsg.PinInChatMessage) case waMsg.KeepInChatMessage != nil: part, contextInfo = mc.convertKeepInChatMessage(ctx, waMsg.KeepInChatMessage) + case waMsg.RichResponseMessage != nil: + part, contextInfo = mc.convertRichResponseMessage(ctx, waMsg.RichResponseMessage) case waMsg.ImageMessage != nil: part, contextInfo = mc.convertMediaMessage(ctx, waMsg.ImageMessage, "photo", info, isViewOnce, previouslyConvertedPart) case waMsg.StickerMessage != nil: diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index ef5fc101..8865215c 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -244,3 +244,24 @@ func (mc *MessageConverter) convertKeepInChatMessage(ctx context.Context, msg *w }, }, nil } + +func (mc *MessageConverter) convertRichResponseMessage(ctx context.Context, msg *waE2E.AIRichResponseMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { + var body strings.Builder + + for i, submsg := range msg.GetSubmessages() { + if submsg.GetMessageType() == waE2E.AIRichResponseMessage_AI_RICH_RESPONSE_TEXT { + if i > 0 { + body.WriteString("\n") + } + body.WriteString(submsg.GetMessageText()) + } + } + + return &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: &event.MessageEventContent{ + MsgType: event.MsgText, + Body: body.String(), + }, + }, msg.GetContextInfo() +} From 59068ca5fc95cc5c711dfe83f51d1ed16546543b Mon Sep 17 00:00:00 2001 From: Kishan Bagaria <1093313+KishanBagaria@users.noreply.github.com> Date: Fri, 15 Aug 2025 20:49:41 +0530 Subject: [PATCH 139/486] msgconv/from-whatsapp: fix markdown support in meta ai responses (#828) --- pkg/msgconv/wa-misc.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index 8865215c..6d1bca1d 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -257,11 +257,9 @@ func (mc *MessageConverter) convertRichResponseMessage(ctx context.Context, msg } } + content := format.RenderMarkdown(body.String(), true, false) return &bridgev2.ConvertedMessagePart{ - Type: event.EventMessage, - Content: &event.MessageEventContent{ - MsgType: event.MsgText, - Body: body.String(), - }, + Type: event.EventMessage, + Content: &content, }, msg.GetContextInfo() } From 4904f5fff3113402b829a339f0a58c1040bc640b Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 16 Aug 2025 14:02:19 +0300 Subject: [PATCH 140/486] startchat: allow bot and lid mxids in validateIdentifier --- pkg/connector/startchat.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index 039fef7b..8543bfa6 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -55,6 +55,8 @@ func looksEmaily(str string) bool { func (wa *WhatsAppClient) validateIdentifer(number string) (types.JID, error) { if strings.HasSuffix(number, "@"+types.BotServer) || strings.HasSuffix(number, "@"+types.HiddenUserServer) { return types.ParseJID(number) + } else if strings.HasPrefix(number, waid.BotPrefix) || strings.HasPrefix(number, waid.LIDPrefix) { + return waid.ParseUserID(networkid.UserID(number)), nil } if strings.HasSuffix(number, "@"+types.DefaultUserServer) { jid, _ := types.ParseJID(number) From 48a42719de6e9b05f2ba258742bd6bfc69ad050f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 16 Aug 2025 14:04:26 +0300 Subject: [PATCH 141/486] Bump version to v0.12.4 --- .github/workflows/go.yml | 4 +-- .pre-commit-config.yaml | 4 +-- CHANGELOG.md | 18 ++++++++++++ cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 32 ++++++++++----------- go.sum | 56 ++++++++++++++++++------------------ 6 files changed, 67 insertions(+), 49 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index e8818840..ae5f5aad 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -11,8 +11,8 @@ jobs: strategy: fail-fast: false matrix: - go-version: ["1.23", "1.24"] - name: Lint ${{ matrix.go-version == '1.24' && '(latest)' || '(old)' }} + go-version: ["1.24", "1.25"] + name: Lint ${{ matrix.go-version == '1.25' && '(latest)' || '(old)' }} steps: - uses: actions/checkout@v4 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b8d2d93d..a48f8b86 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: trailing-whitespace exclude_types: [markdown] @@ -9,7 +9,7 @@ repos: - id: check-added-large-files - repo: https://github.com/tekwizely/pre-commit-golang - rev: v1.0.0-rc.1 + rev: v1.0.0-rc.2 hooks: - id: go-imports-repo args: diff --git a/CHANGELOG.md b/CHANGELOG.md index c9a99834..5f9f4431 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +# v0.12.4 (2025-08-16) + +* Deprecated legacy provisioning API. The `/_matrix/provision/v1` endpoints will + be deleted in the next release. +* Bumped minimum Go version to 1.24. +* Added support for bridging HD dual uploads from WhatsApp into edits on Matrix. +* Added better placeholders for pin and keep messages from WhatsApp. +* Fixed bridging animated webp stickers to WhatsApp. + * Note that non-square stickers may appear corrupted on native clients. + The bridge will not automatically add padding to animated stickers like it + does for static ones. +* Fixed avatar changes not reflecting on both the LID and phone number ghost of + a given user in certain cases. +* Fixed first message after group LID migration still using the phone number + ghost. +* Fixed bot messages in DMs being split into another portal room. +* Fixed new group members not having a phone number name in some cases. + # v0.12.3 (2025-07-16) * Further improved support for `@lid` users. diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index fc0364de..33dc8651 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -21,7 +21,7 @@ var m = mxmain.BridgeMain{ Name: "mautrix-whatsapp", URL: "https://github.com/mautrix/whatsapp", Description: "A Matrix-WhatsApp puppeting bridge.", - Version: "0.12.3", + Version: "0.12.4", Connector: c, } diff --git a/go.mod b/go.mod index 172dd54c..1ddfc3b1 100644 --- a/go.mod +++ b/go.mod @@ -1,22 +1,22 @@ module go.mau.fi/mautrix-whatsapp -go 1.23.0 +go 1.24.0 -toolchain go1.24.5 +toolchain go1.25.0 require ( github.com/gorilla/websocket v1.5.0 github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.8.9-0.20250723171559-474867266038 + go.mau.fi/util v0.9.0 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250811141640-b804d10c54c2 - golang.org/x/image v0.29.0 - golang.org/x/net v0.42.0 + go.mau.fi/whatsmeow v0.0.0-20250816105909-9a955523cf0c + golang.org/x/image v0.30.0 + golang.org/x/net v0.43.0 golang.org/x/sync v0.16.0 - google.golang.org/protobuf v1.36.6 + google.golang.org/protobuf v1.36.7 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.24.3-0.20250801084753-196164ed6749 + maunium.net/go/mautrix v0.25.0 ) require ( @@ -27,8 +27,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.28 // indirect - github.com/petermattis/goid v0.0.0-20250721140440-ea1c0173183e // indirect + github.com/mattn/go-sqlite3 v1.14.32 // indirect + github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // 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 @@ -36,13 +36,13 @@ require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect - github.com/yuin/goldmark v1.7.12 // indirect + github.com/yuin/goldmark v1.7.13 // indirect go.mau.fi/libsignal v0.2.0 // indirect - go.mau.fi/zeroconfig v0.1.3 // indirect - golang.org/x/crypto v0.40.0 // indirect - golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.27.0 // indirect + go.mau.fi/zeroconfig v0.2.0 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.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 ad1b4ebf..5abfb453 100644 --- a/go.sum +++ b/go.sum @@ -32,10 +32,10 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ 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.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= -github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/petermattis/goid v0.0.0-20250721140440-ea1c0173183e h1:D0bJD+4O3G4izvrQUmzCL80zazlN7EwJ0PPDhpJWC/I= -github.com/petermattis/goid v0.0.0-20250721140440-ea1c0173183e/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe h1:vHpqOnPlnkba8iSxU4j/CvDSS9J4+F4473esQsYLGoE= +github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/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= @@ -61,37 +61,37 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 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/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY= -github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= +github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.0 h1:oRXj3OHhEJq51BFEM8/50UZblmWiTYH93hsNTPcbk90= go.mau.fi/libsignal v0.2.0/go.mod h1:tvjoDsMejgT38CXTXwqaYu8itBiY8O2Mb6biWvZBb9k= -go.mau.fi/util v0.8.9-0.20250723171559-474867266038 h1:RVL8TVaYc3LTBBopfjCNDtD+6eZks0O+qgXN/9hsz7k= -go.mau.fi/util v0.8.9-0.20250723171559-474867266038/go.mod h1:GZZp5f9r2MgEu4GDvtB0XxCF7i6Z7Z8fM0w9a5oZH3Y= +go.mau.fi/util v0.9.0 h1:ya3s3pX+Y8R2fgp0DbE7a0o3FwncoelDX5iyaeVE8ls= +go.mau.fi/util v0.9.0/go.mod h1:pdL3lg2aaeeHIreGXNnPwhJPXkXdc3ZxsI6le8hOWEA= 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-20250811141640-b804d10c54c2 h1:pPht2EbpwrDKrMKW7t67tpx+6BsFcJyIPy5R45Bu0dk= -go.mau.fi/whatsmeow v0.0.0-20250811141640-b804d10c54c2/go.mod h1:ltDTXUgOAT7LcFKp11H+5S7UY7+xHBMGzNJcv3dLHGk= -go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM= -go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= -golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4= -golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= -golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas= -golang.org/x/image v0.29.0/go.mod h1:RVJROnf3SLK8d26OW91j4FrIHGbsJ8QnbEocVTOWQDA= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +go.mau.fi/whatsmeow v0.0.0-20250816105909-9a955523cf0c h1:5qVuYMjp1NZHfuMHTxBuITujkPmrXnxOZ9si16i3MzM= +go.mau.fi/whatsmeow v0.0.0-20250816105909-9a955523cf0c/go.mod h1:xD0DR3s4T6PDd3BzgQG05AzLWxdKCmnvdCP3UuQvn9w= +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.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE= +golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= +golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4= +golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= +google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 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= @@ -101,5 +101,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.24.3-0.20250801084753-196164ed6749 h1:eEIsN9CufSHs4SeRjUwIpJo3n8QaX0rfO2sEERjPkKY= -maunium.net/go/mautrix v0.24.3-0.20250801084753-196164ed6749/go.mod h1:KrE/TdIeAo6cfAUICmbaiZ18UgHimTOozAGOrioK9SU= +maunium.net/go/mautrix v0.25.0 h1:dhYoXIXSxI9A+kEPwBceuRP0wcpho15dVLucUF8k2eE= +maunium.net/go/mautrix v0.25.0/go.mod h1:pDd6Ppg+1PbWrw/rg4ZQQfVYZICRGzH+DcliZ/BODvU= From b3903f6cb5e10294689278baff27ed07b19224f3 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 19 Aug 2025 17:44:36 +0300 Subject: [PATCH 142/486] docker: install lottieconverter Fixes #831 --- Dockerfile | 2 +- Dockerfile.ci | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 32d7a6d8..3b71e4f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ FROM alpine:3.22 ENV UID=1337 \ GID=1337 -RUN apk add --no-cache ffmpeg su-exec ca-certificates olm bash jq yq curl +RUN apk add --no-cache ffmpeg su-exec ca-certificates olm bash jq curl yq-go lottieconverter COPY --from=builder /build/mautrix-whatsapp /usr/bin/mautrix-whatsapp COPY --from=builder /build/docker-run.sh /docker-run.sh diff --git a/Dockerfile.ci b/Dockerfile.ci index 28c7cfbc..9b7189dd 100644 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -3,7 +3,7 @@ FROM alpine:3.22 ENV UID=1337 \ GID=1337 -RUN apk add --no-cache ffmpeg su-exec ca-certificates bash jq curl yq-go +RUN apk add --no-cache ffmpeg su-exec ca-certificates bash jq curl yq-go lottieconverter ARG EXECUTABLE=./mautrix-whatsapp COPY $EXECUTABLE /usr/bin/mautrix-whatsapp From dca368d02e260d71654f674ccffd083fb07e0e61 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 21 Aug 2025 17:03:30 +0300 Subject: [PATCH 143/486] msgconv/from-whatsapp: don't use different logins for mentions in split portals --- pkg/msgconv/from-whatsapp.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index de149286..2f68b34c 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -89,8 +89,9 @@ func (mc *MessageConverter) getBasicUserInfo(ctx context.Context, user types.JID } } if !pnJID.IsEmpty() { + portal := getPortal(ctx) login := mc.Bridge.GetCachedUserLoginByID(waid.MakeUserLoginID(pnJID)) - if login != nil { + if login != nil && (portal.Receiver == "" || portal.Receiver == login.ID) { return login.UserMXID, ghost.Name, nil } } From 7ff2f9f20665a52310a61d172822e359fe18a660 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 25 Aug 2025 14:47:07 +0300 Subject: [PATCH 144/486] msgconv/from-matrix: don't allow sending png to whatsapp --- pkg/connector/capabilities.go | 6 +++--- pkg/msgconv/from-matrix.go | 21 ++++++++++----------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/pkg/connector/capabilities.go b/pkg/connector/capabilities.go index c54c3784..81e77ecc 100644 --- a/pkg/connector/capabilities.go +++ b/pkg/connector/capabilities.go @@ -23,7 +23,7 @@ func (wa *WhatsAppConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilit } func (wa *WhatsAppConnector) GetBridgeInfoVersion() (info, caps int) { - return 1, 2 + return 1, 3 } const WAMaxFileSize = 2000 * 1024 * 1024 @@ -38,7 +38,7 @@ func supportedIfFFmpeg() event.CapabilitySupportLevel { } func capID() string { - base := "fi.mau.whatsapp.capabilities.2025_06_03" + base := "fi.mau.whatsapp.capabilities.2025_08_25" if ffmpeg.Supported() { return base + "+ffmpeg" } @@ -66,8 +66,8 @@ var whatsappCaps = &event.RoomFeatures{ File: map[event.CapabilityMsgType]*event.FileFeatures{ event.MsgImage: { MimeTypes: map[string]event.CapabilitySupportLevel{ - "image/png": event.CapLevelFullySupported, "image/jpeg": event.CapLevelFullySupported, + "image/png": event.CapLevelPartialSupport, "image/webp": event.CapLevelPartialSupport, "image/gif": supportedIfFFmpeg(), }, diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index a71824a7..3cca335a 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -23,7 +23,7 @@ import ( "fmt" "image" "image/color" - "image/png" + "image/jpeg" "net/http" "slices" "strconv" @@ -37,7 +37,6 @@ import ( "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waE2E" "go.mau.fi/whatsmeow/types" - "golang.org/x/image/webp" "google.golang.org/protobuf/proto" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/database" @@ -360,18 +359,18 @@ func (img *PaddedImage) At(x, y int) color.Color { return img.Image.At(x-img.OffsetX, y-img.OffsetY) } -func (mc *MessageConverter) convertWebPtoPNG(webpImage []byte) ([]byte, error) { - webpDecoded, err := webp.Decode(bytes.NewReader(webpImage)) +func (mc *MessageConverter) convertToJPEG(webpImage []byte) ([]byte, error) { + decoded, _, err := image.Decode(bytes.NewReader(webpImage)) if err != nil { return nil, fmt.Errorf("failed to decode webp image: %w", err) } - var pngBuffer bytes.Buffer - if err = png.Encode(&pngBuffer, webpDecoded); err != nil { + var jpgBuffer bytes.Buffer + if err = jpeg.Encode(&jpgBuffer, decoded, &jpeg.Options{Quality: 80}); err != nil { return nil, fmt.Errorf("failed to encode png image: %w", err) } - return pngBuffer.Bytes(), nil + return jpgBuffer.Bytes(), nil } func (mc *MessageConverter) convertToWebP(img []byte) ([]byte, int, error) { @@ -454,14 +453,14 @@ func (mc *MessageConverter) reuploadFileToWhatsApp( case event.MsgImage: mediaType = whatsmeow.MediaImage switch mime { - case "image/jpeg", "image/png": + case "image/jpeg": // allowed - case "image/webp": - data, err = mc.convertWebPtoPNG(data) + case "image/webp", "image/png": + data, err = mc.convertToJPEG(data) if err != nil { return nil, nil, "image/webp", fmt.Errorf("%w (webp to png): %s", bridgev2.ErrMediaConvertFailed, err) } - mime = "image/png" + mime = "image/jpeg" default: return nil, nil, mime, fmt.Errorf("%w %s in image message", bridgev2.ErrUnsupportedMediaType, mime) } From c7602c02c908efcad2f5c7eb8267564a54f51e52 Mon Sep 17 00:00:00 2001 From: Kishan Bagaria <1093313+KishanBagaria@users.noreply.github.com> Date: Mon, 25 Aug 2025 17:26:55 +0530 Subject: [PATCH 145/486] all: enable mautrix-go's new disappearing message features (#829) --- go.mod | 4 ++-- go.sum | 8 ++++---- pkg/connector/capabilities.go | 12 ++++++++++-- pkg/connector/chatinfo.go | 6 +++--- pkg/msgconv/from-matrix.go | 23 +++++++++++++++-------- pkg/msgconv/from-whatsapp.go | 3 +-- pkg/msgconv/matrixpoll.go | 2 +- pkg/msgconv/wa-misc.go | 2 +- 8 files changed, 37 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index 1ddfc3b1..a389b3d1 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( golang.org/x/sync v0.16.0 google.golang.org/protobuf v1.36.7 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.0 + maunium.net/go/mautrix v0.25.1-0.20250823214755-7e07700a6943 ) require ( @@ -40,7 +40,7 @@ require ( go.mau.fi/libsignal v0.2.0 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect golang.org/x/crypto v0.41.0 // indirect - golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect + golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect golang.org/x/sys v0.35.0 // indirect golang.org/x/text v0.28.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index 5abfb453..5eaf2d10 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ 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.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= -golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE= -golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= +golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0= +golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4= golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= @@ -101,5 +101,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.25.0 h1:dhYoXIXSxI9A+kEPwBceuRP0wcpho15dVLucUF8k2eE= -maunium.net/go/mautrix v0.25.0/go.mod h1:pDd6Ppg+1PbWrw/rg4ZQQfVYZICRGzH+DcliZ/BODvU= +maunium.net/go/mautrix v0.25.1-0.20250823214755-7e07700a6943 h1:qNRr7dDDJhGUMTqkp4p1EJ18SL0yMKzB1pdzSZ2XUWo= +maunium.net/go/mautrix v0.25.1-0.20250823214755-7e07700a6943/go.mod h1:pDd6Ppg+1PbWrw/rg4ZQQfVYZICRGzH+DcliZ/BODvU= diff --git a/pkg/connector/capabilities.go b/pkg/connector/capabilities.go index 81e77ecc..f850cf0e 100644 --- a/pkg/connector/capabilities.go +++ b/pkg/connector/capabilities.go @@ -23,7 +23,7 @@ func (wa *WhatsAppConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilit } func (wa *WhatsAppConnector) GetBridgeInfoVersion() (info, caps int) { - return 1, 3 + return 1, 4 } const WAMaxFileSize = 2000 * 1024 * 1024 @@ -38,7 +38,7 @@ func supportedIfFFmpeg() event.CapabilitySupportLevel { } func capID() string { - base := "fi.mau.whatsapp.capabilities.2025_08_25" + base := "fi.mau.whatsapp.capabilities.2025_08_25+1" if ffmpeg.Supported() { return base + "+ffmpeg" } @@ -148,6 +148,14 @@ var whatsappCaps = &event.RoomFeatures{ ReactionCount: 1, ReadReceipts: true, TypingNotifications: true, + DisappearingTimer: &event.DisappearingTimerCapability{ + Types: []event.DisappearingType{event.DisappearingTypeAfterSend}, + Timers: []jsontime.Milliseconds{ + jsontime.MS(24 * time.Hour), // 24 hours + jsontime.MS(7 * 24 * time.Hour), // 7 days + jsontime.MS(90 * 24 * time.Hour), // 90 days + }, + }, } var whatsappCAGCaps *event.RoomFeatures diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index b83b1f76..57a18acf 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -122,7 +122,7 @@ func (wa *WhatsAppClient) applyHistoryInfo(info *bridgev2.ChatInfo, conv *wadb.C } if info.Disappear == nil && ptr.Val(conv.EphemeralExpiration) > 0 { info.Disappear = &database.DisappearingSetting{ - Type: database.DisappearingTypeAfterRead, + Type: event.DisappearingTypeAfterSend, Timer: time.Duration(*conv.EphemeralExpiration) * time.Second, } if conv.EphemeralSettingTimestamp != nil { @@ -255,7 +255,7 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn }, }, Disappear: &database.DisappearingSetting{ - Type: database.DisappearingTypeAfterRead, + Type: event.DisappearingTypeAfterSend, Timer: time.Duration(info.DisappearingTimer) * time.Second, }, ExtraUpdates: extraUpdater, @@ -311,7 +311,7 @@ func (wa *WhatsAppClient) wrapGroupInfoChange(ctx context.Context, evt *events.G } if evt.Ephemeral != nil { changes.Disappear = &database.DisappearingSetting{ - Type: database.DisappearingTypeAfterRead, + Type: event.DisappearingTypeAfterSend, Timer: time.Duration(evt.Ephemeral.DisappearingTimer) * time.Second, } if !evt.Ephemeral.IsEphemeral { diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 3cca335a..7fde6a1f 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -28,6 +28,7 @@ import ( "slices" "strconv" "strings" + "time" "github.com/rs/zerolog" "go.mau.fi/util/ffmpeg" @@ -47,7 +48,7 @@ import ( "go.mau.fi/mautrix-whatsapp/pkg/waid" ) -func (mc *MessageConverter) generateContextInfo(ctx context.Context, replyTo *database.Message, portal *bridgev2.Portal) *waE2E.ContextInfo { +func (mc *MessageConverter) generateContextInfo(ctx context.Context, replyTo *database.Message, portal *bridgev2.Portal, perMessageTimer *event.BeeperDisappearingTimer) *waE2E.ContextInfo { contextInfo := &waE2E.ContextInfo{} if replyTo != nil { msgID, err := waid.ParseMessageID(replyTo.ID) @@ -62,12 +63,18 @@ func (mc *MessageConverter) generateContextInfo(ctx context.Context, replyTo *da Msg("Failed to parse reply to message ID") } } - if portal.Disappear.Timer > 0 { - contextInfo.Expiration = ptr.Ptr(uint32(portal.Disappear.Timer.Seconds())) - setAt := portal.Metadata.(*waid.PortalMetadata).DisappearingTimerSetAt - if setAt > 0 { - contextInfo.EphemeralSettingTimestamp = ptr.Ptr(setAt) - } + var timer time.Duration + if perMessageTimer != nil { + timer = perMessageTimer.Timer.Duration + } else { + timer = portal.Disappear.Timer + } + if timer > 0 { + contextInfo.Expiration = ptr.Ptr(uint32(timer.Seconds())) + } + setAt := portal.Metadata.(*waid.PortalMetadata).DisappearingTimerSetAt + if setAt > 0 && contextInfo.Expiration != nil { + contextInfo.EphemeralSettingTimestamp = ptr.Ptr(setAt) } return contextInfo } @@ -88,7 +95,7 @@ func (mc *MessageConverter) ToWhatsApp( } message := &waE2E.Message{} - contextInfo := mc.generateContextInfo(ctx, replyTo, portal) + contextInfo := mc.generateContextInfo(ctx, replyTo, portal, content.BeeperDisappearingTimer) switch content.MsgType { case event.MsgText, event.MsgNotice, event.MsgEmote: diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 2f68b34c..a603efa6 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -33,7 +33,6 @@ import ( "go.mau.fi/whatsmeow/types" _ "golang.org/x/image/webp" "maunium.net/go/mautrix/bridgev2" - "maunium.net/go/mautrix/bridgev2/database" "maunium.net/go/mautrix/bridgev2/networkid" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" @@ -240,7 +239,7 @@ func (mc *MessageConverter) ToMatrix( } if contextInfo.GetExpiration() > 0 { cm.Disappear.Timer = time.Duration(contextInfo.GetExpiration()) * time.Second - cm.Disappear.Type = database.DisappearingTypeAfterRead + cm.Disappear.Type = event.DisappearingTypeAfterSend } if portal.Disappear.Timer != cm.Disappear.Timer && portal.Metadata.(*waid.PortalMetadata).DisappearingTimerSetAt < contextInfo.GetEphemeralSettingTimestamp() { portal.UpdateDisappearingSetting(ctx, cm.Disappear, intent, info.Timestamp, true, true) diff --git a/pkg/msgconv/matrixpoll.go b/pkg/msgconv/matrixpoll.go index 34040abd..22cc7afe 100644 --- a/pkg/msgconv/matrixpoll.go +++ b/pkg/msgconv/matrixpoll.go @@ -71,7 +71,7 @@ func (mc *MessageConverter) PollStartToWhatsApp( if maxAnswers >= len(content.PollStart.Answers) || maxAnswers < 0 { maxAnswers = 0 } - contextInfo := mc.generateContextInfo(ctx, replyTo, portal) + contextInfo := mc.generateContextInfo(ctx, replyTo, portal, nil) var question string question, contextInfo.MentionedJID = mc.msc1767ToWhatsApp(ctx, content.PollStart.Question, content.Mentions) if len(question) == 0 { diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index 6d1bca1d..f24c9ee7 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -118,7 +118,7 @@ func (mc *MessageConverter) convertEphemeralSettingMessage(ctx context.Context, portal := getPortal(ctx) portalMeta := portal.Metadata.(*waid.PortalMetadata) disappear := database.DisappearingSetting{ - Type: database.DisappearingTypeAfterRead, + Type: event.DisappearingTypeAfterSend, Timer: time.Duration(msg.GetEphemeralExpiration()) * time.Second, } if disappear.Timer == 0 { From 6c5f05ce3622fe644fbc42e11c87b74084c988b0 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 25 Aug 2025 17:22:35 +0300 Subject: [PATCH 146/486] msgconv/from-whatsapp: properly update disappearing message timer in room state --- go.mod | 2 +- go.sum | 4 ++-- pkg/msgconv/from-whatsapp.go | 10 ++++++++-- pkg/msgconv/wa-misc.go | 14 ++++++++------ 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index a389b3d1..a5dd7e5d 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( golang.org/x/sync v0.16.0 google.golang.org/protobuf v1.36.7 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.1-0.20250823214755-7e07700a6943 + maunium.net/go/mautrix v0.25.1-0.20250825142325-f860b0e2386a ) require ( diff --git a/go.sum b/go.sum index 5eaf2d10..a7806f9c 100644 --- a/go.sum +++ b/go.sum @@ -101,5 +101,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.25.1-0.20250823214755-7e07700a6943 h1:qNRr7dDDJhGUMTqkp4p1EJ18SL0yMKzB1pdzSZ2XUWo= -maunium.net/go/mautrix v0.25.1-0.20250823214755-7e07700a6943/go.mod h1:pDd6Ppg+1PbWrw/rg4ZQQfVYZICRGzH+DcliZ/BODvU= +maunium.net/go/mautrix v0.25.1-0.20250825142325-f860b0e2386a h1:Qfsfq3BEzLKNsLy+BJIv0kfUvJK6cXKav7R2zyqMsIc= +maunium.net/go/mautrix v0.25.1-0.20250825142325-f860b0e2386a/go.mod h1:pDd6Ppg+1PbWrw/rg4ZQQfVYZICRGzH+DcliZ/BODvU= diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index a603efa6..8e053849 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -212,7 +212,7 @@ func (mc *MessageConverter) ToMatrix( case waMsg.GroupInviteMessage != nil: 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) + part, contextInfo = mc.convertEphemeralSettingMessage(ctx, waMsg.ProtocolMessage, info.Timestamp) case waMsg.EncCommentMessage != nil: part = failedCommentPart default: @@ -242,7 +242,13 @@ func (mc *MessageConverter) ToMatrix( cm.Disappear.Type = event.DisappearingTypeAfterSend } if portal.Disappear.Timer != cm.Disappear.Timer && portal.Metadata.(*waid.PortalMetadata).DisappearingTimerSetAt < contextInfo.GetEphemeralSettingTimestamp() { - portal.UpdateDisappearingSetting(ctx, cm.Disappear, intent, info.Timestamp, true, true) + portal.UpdateDisappearingSetting(ctx, cm.Disappear, bridgev2.UpdateDisappearingSettingOpts{ + Sender: intent, + Timestamp: info.Timestamp, + Implicit: true, + Save: true, + SendNotice: true, + }) } if contextInfo.GetStanzaID() != "" { pcp, _ := types.ParseJID(contextInfo.GetParticipant()) diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index f24c9ee7..6120d77a 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -114,7 +114,7 @@ func (mc *MessageConverter) convertGroupInviteMessage(ctx context.Context, info }, msg.GetContextInfo() } -func (mc *MessageConverter) convertEphemeralSettingMessage(ctx context.Context, msg *waE2E.ProtocolMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { +func (mc *MessageConverter) convertEphemeralSettingMessage(ctx context.Context, msg *waE2E.ProtocolMessage, ts time.Time) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { portal := getPortal(ctx) portalMeta := portal.Metadata.(*waid.PortalMetadata) disappear := database.DisappearingSetting{ @@ -127,12 +127,14 @@ func (mc *MessageConverter) convertEphemeralSettingMessage(ctx context.Context, dontBridge := portal.Disappear == disappear content := bridgev2.DisappearingMessageNotice(disappear.Timer, false) if msg.EphemeralSettingTimestamp == nil || portalMeta.DisappearingTimerSetAt < msg.GetEphemeralSettingTimestamp() { - portal.Disappear = disappear portalMeta.DisappearingTimerSetAt = msg.GetEphemeralSettingTimestamp() - err := portal.Save(ctx) - if err != nil { - zerolog.Ctx(ctx).Err(err).Msg("Failed to save portal after updating expiration timer") - } + portal.UpdateDisappearingSetting(ctx, disappear, bridgev2.UpdateDisappearingSettingOpts{ + Sender: getIntent(ctx), + Timestamp: ts, + Implicit: false, + Save: true, + SendNotice: false, + }) } else { content.Body += ", but the change was ignored." } From 37f0c4ad1897daf87e110b5c903d08ed06ef1c6c Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 25 Aug 2025 18:15:50 +0300 Subject: [PATCH 147/486] handlematrix: add support for changing disappearing timer --- ROADMAP.md | 8 +++---- go.mod | 4 ++-- go.sum | 8 +++---- pkg/connector/chatinfo.go | 11 ++++++++- pkg/connector/handlematrix.go | 44 ++++++++++++++++++++++++++++++----- 5 files changed, 58 insertions(+), 17 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 91f43710..980606d2 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -14,10 +14,10 @@ * [x] Typing notifications * [x] Read receipts * [ ] Power level - * [ ] Membership actions - * [ ] Invite - * [ ] Leave - * [ ] Kick + * [x] Membership actions + * [x] Invite + * [x] Leave + * [x] Kick * [ ] Room metadata changes * [ ] Name * [ ] Avatar diff --git a/go.mod b/go.mod index a5dd7e5d..7c59149c 100644 --- a/go.mod +++ b/go.mod @@ -10,13 +10,13 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.0 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250816105909-9a955523cf0c + go.mau.fi/whatsmeow v0.0.0-20250825151841-7b1a3f7e5746 golang.org/x/image v0.30.0 golang.org/x/net v0.43.0 golang.org/x/sync v0.16.0 google.golang.org/protobuf v1.36.7 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.1-0.20250825142325-f860b0e2386a + maunium.net/go/mautrix v0.25.1-0.20250825160307-63b654187d40 ) require ( diff --git a/go.sum b/go.sum index a7806f9c..80b94341 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.9.0 h1:ya3s3pX+Y8R2fgp0DbE7a0o3FwncoelDX5iyaeVE8ls= go.mau.fi/util v0.9.0/go.mod h1:pdL3lg2aaeeHIreGXNnPwhJPXkXdc3ZxsI6le8hOWEA= 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-20250816105909-9a955523cf0c h1:5qVuYMjp1NZHfuMHTxBuITujkPmrXnxOZ9si16i3MzM= -go.mau.fi/whatsmeow v0.0.0-20250816105909-9a955523cf0c/go.mod h1:xD0DR3s4T6PDd3BzgQG05AzLWxdKCmnvdCP3UuQvn9w= +go.mau.fi/whatsmeow v0.0.0-20250825151841-7b1a3f7e5746 h1:WquxGXJBTlOTkBSltW52rqzRbQDAYD7JJgMli6FiIiY= +go.mau.fi/whatsmeow v0.0.0-20250825151841-7b1a3f7e5746/go.mod h1:xD0DR3s4T6PDd3BzgQG05AzLWxdKCmnvdCP3UuQvn9w= 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.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= @@ -101,5 +101,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.25.1-0.20250825142325-f860b0e2386a h1:Qfsfq3BEzLKNsLy+BJIv0kfUvJK6cXKav7R2zyqMsIc= -maunium.net/go/mautrix v0.25.1-0.20250825142325-f860b0e2386a/go.mod h1:pDd6Ppg+1PbWrw/rg4ZQQfVYZICRGzH+DcliZ/BODvU= +maunium.net/go/mautrix v0.25.1-0.20250825160307-63b654187d40 h1:hxHT1z1lixT0s/y/8E6M/OB0D5PwBlNYpZtgKkREIQU= +maunium.net/go/mautrix v0.25.1-0.20250825160307-63b654187d40/go.mod h1:pDd6Ppg+1PbWrw/rg4ZQQfVYZICRGzH+DcliZ/BODvU= diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 57a18acf..1a912a02 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -149,7 +149,14 @@ func (wa *WhatsAppClient) wrapDMInfo(ctx context.Context, jid types.JID) *bridge waid.MakeUserID(jid): {EventSender: wa.makeEventSender(ctx, jid)}, waid.MakeUserID(wa.JID): {EventSender: wa.makeEventSender(ctx, wa.JID)}, }, - PowerLevels: nil, + PowerLevels: &bridgev2.PowerLevelOverrides{ + Events: map[event.Type]int{ + event.StateRoomName: 0, + event.StateRoomAvatar: 0, + event.StateTopic: 0, + event.StateBeeperDisappearingTimer: 0, + }, + }, }, Type: ptr.Ptr(database.RoomTypeDM), } @@ -250,6 +257,8 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn event.StateTopic: metaChangePL, event.EventReaction: defaultPL, event.EventRedaction: defaultPL, + + event.StateBeeperDisappearingTimer: metaChangePL, // TODO always allow poll responses }, }, diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index a97aee7d..d271da67 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -24,12 +24,14 @@ import ( ) var ( - _ bridgev2.TypingHandlingNetworkAPI = (*WhatsAppClient)(nil) - _ bridgev2.EditHandlingNetworkAPI = (*WhatsAppClient)(nil) - _ bridgev2.ReactionHandlingNetworkAPI = (*WhatsAppClient)(nil) - _ bridgev2.RedactionHandlingNetworkAPI = (*WhatsAppClient)(nil) - _ bridgev2.ReadReceiptHandlingNetworkAPI = (*WhatsAppClient)(nil) - _ bridgev2.PollHandlingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.TypingHandlingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.EditHandlingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.ReactionHandlingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.RedactionHandlingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.ReadReceiptHandlingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.PollHandlingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.DisappearTimerChangingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.MembershipHandlingNetworkAPI = (*WhatsAppClient)(nil) ) func (wa *WhatsAppClient) HandleMatrixPollStart(ctx context.Context, msg *bridgev2.MatrixPollStart) (*bridgev2.MatrixMessageResponse, error) { @@ -344,6 +346,36 @@ func (wa *WhatsAppClient) HandleMatrixTyping(ctx context.Context, msg *bridgev2. return wa.Client.SendChatPresence(portalJID, chatPresence, mediaPresence) } +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) { + portalJID, err := waid.ParsePortalID(msg.Portal.ID) + if err != nil { + return false, err + } + + switch msg.Content.Timer.Duration { + case whatsmeow.DisappearingTimerOff, whatsmeow.DisappearingTimer24Hours, whatsmeow.DisappearingTimer7Days, whatsmeow.DisappearingTimer90Days: + default: + return false, fmt.Errorf("%w (%s)", errUnsupportedDisappearingTimer, msg.Content.Timer.Duration) + } + + settingTS := time.UnixMilli(msg.Event.Timestamp) + err = wa.Client.SetDisappearingTimer(portalJID, msg.Content.Timer.Duration, settingTS) + if err != nil { + return false, err + } + msg.Portal.Metadata.(*waid.PortalMetadata).DisappearingTimerSetAt = settingTS.Unix() + msg.Portal.Disappear = database.DisappearingSetting{ + Type: event.DisappearingTypeAfterSend, + Timer: msg.Content.Timer.Duration, + } + if msg.Portal.Disappear.Timer == 0 { + msg.Portal.Disappear.Type = event.DisappearingTypeNone + } + return true, nil +} + func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2.MatrixMembershipChange) (bool, error) { portalJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { From 08948811bb0968f5ebccc7ac682aac4134975d51 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 26 Aug 2025 17:07:46 +0300 Subject: [PATCH 148/486] 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 7c59149c..3ffe06e0 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( golang.org/x/sync v0.16.0 google.golang.org/protobuf v1.36.7 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.1-0.20250825160307-63b654187d40 + maunium.net/go/mautrix v0.25.1-0.20250826140716-0345a5356de1 ) require ( diff --git a/go.sum b/go.sum index 80b94341..e591eb16 100644 --- a/go.sum +++ b/go.sum @@ -101,5 +101,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.25.1-0.20250825160307-63b654187d40 h1:hxHT1z1lixT0s/y/8E6M/OB0D5PwBlNYpZtgKkREIQU= -maunium.net/go/mautrix v0.25.1-0.20250825160307-63b654187d40/go.mod h1:pDd6Ppg+1PbWrw/rg4ZQQfVYZICRGzH+DcliZ/BODvU= +maunium.net/go/mautrix v0.25.1-0.20250826140716-0345a5356de1 h1:8U8dwv4dxhMLrtqjf9LoDuQ6daGj9qdw1/62HIvK2cY= +maunium.net/go/mautrix v0.25.1-0.20250826140716-0345a5356de1/go.mod h1:pDd6Ppg+1PbWrw/rg4ZQQfVYZICRGzH+DcliZ/BODvU= From c033ce0948c1cadde89480d3af4ccffeb98d937d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 28 Aug 2025 02:29:17 +0300 Subject: [PATCH 149/486] legacy{migrate,provision}: delete Closes #782 Closes #739 --- CHANGELOG.md | 10 + cmd/mautrix-whatsapp/legacymigrate.go | 70 ------ cmd/mautrix-whatsapp/legacymigrate.sql | 215 ----------------- cmd/mautrix-whatsapp/legacyprovision.go | 307 ------------------------ cmd/mautrix-whatsapp/main.go | 28 +-- go.mod | 4 +- go.sum | 4 +- 7 files changed, 15 insertions(+), 623 deletions(-) delete mode 100644 cmd/mautrix-whatsapp/legacymigrate.go delete mode 100644 cmd/mautrix-whatsapp/legacymigrate.sql delete mode 100644 cmd/mautrix-whatsapp/legacyprovision.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f9f4431..0b85c5e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# v0.12.5 (unreleased) + +* Removed legacy provisioning API and database legacy migration. + Upgrading directly from versions prior to v0.11.0 is not supported. +* Added support for `com.beeper.disappearing_timer` state event, which stores + the disappearing setting of chats and allows changing the setting from Matrix. +* Added lottieconverter to Docker images to enable converting animated stickers + from WhatsApp. +* Fixed sent PNGs not being rendered on WhatsApp iOS. + # v0.12.4 (2025-08-16) * Deprecated legacy provisioning API. The `/_matrix/provision/v1` endpoints will diff --git a/cmd/mautrix-whatsapp/legacymigrate.go b/cmd/mautrix-whatsapp/legacymigrate.go deleted file mode 100644 index 34bcf64e..00000000 --- a/cmd/mautrix-whatsapp/legacymigrate.go +++ /dev/null @@ -1,70 +0,0 @@ -package main - -import ( - _ "embed" - "strings" - - up "go.mau.fi/util/configupgrade" - "go.mau.fi/util/dbutil/litestream" - "maunium.net/go/mautrix/bridgev2/bridgeconfig" -) - -const legacyMigrateRenameTables = ` -ALTER TABLE backfill_queue RENAME TO backfill_queue_old; -ALTER TABLE backfill_state RENAME TO backfill_state_old; -ALTER TABLE disappearing_message RENAME TO disappearing_message_old; -ALTER TABLE history_sync_message RENAME TO history_sync_message_old; -ALTER TABLE history_sync_conversation RENAME TO history_sync_conversation_old; -ALTER TABLE media_backfill_requests RENAME TO media_backfill_requests_old; -ALTER TABLE poll_option_id RENAME TO poll_option_id_old; -ALTER TABLE user_portal RENAME TO user_portal_old; -ALTER TABLE portal RENAME TO portal_old; -ALTER TABLE puppet RENAME TO puppet_old; -ALTER TABLE message RENAME TO message_old; -ALTER TABLE reaction RENAME TO reaction_old; -ALTER TABLE "user" RENAME TO user_old; -` - -//go:embed legacymigrate.sql -var legacyMigrateCopyData string - -func init() { - litestream.Functions["split_part"] = func(input, delimiter string, partNum int) string { - // split_part is 1-indexed - partNum-- - parts := strings.Split(input, delimiter) - if len(parts) <= partNum { - return "" - } - return parts[partNum] - } -} - -func migrateLegacyConfig(helper up.Helper) { - helper.Set(up.Str, "maunium.net/go/mautrix-whatsapp", "encryption", "pickle_key") - bridgeconfig.CopyToOtherLocation(helper, up.Str, []string{"whatsapp", "os_name"}, []string{"network", "os_name"}) - bridgeconfig.CopyToOtherLocation(helper, up.Str, []string{"whatsapp", "browser_name"}, []string{"network", "browser_name"}) - bridgeconfig.CopyToOtherLocation(helper, up.Str, []string{"whatsapp", "proxy"}, []string{"network", "proxy"}) - bridgeconfig.CopyToOtherLocation(helper, up.Str, []string{"whatsapp", "get_proxy_url"}, []string{"network", "get_proxy_url"}) - bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"whatsapp", "proxy_only_login"}, []string{"network", "proxy_only_login"}) - bridgeconfig.CopyToOtherLocation(helper, up.Str, []string{"bridge", "displayname_template"}, []string{"network", "displayname_template"}) - bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "call_start_notices"}, []string{"network", "call_start_notices"}) - bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "send_presence_on_typing"}, []string{"network", "send_presence_on_typing"}) - bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "enable_status_broadcast"}, []string{"network", "enable_status_broadcast"}) - bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "disable_status_broadcast_send"}, []string{"network", "disable_status_broadcast_send"}) - bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "mute_status_broadcast"}, []string{"network", "mute_status_broadcast"}) - bridgeconfig.CopyToOtherLocation(helper, up.Str, []string{"bridge", "status_broadcast_tag"}, []string{"network", "status_broadcast_tag"}) - bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "whatsapp_thumbnail"}, []string{"network", "whatsapp_thumbnail"}) - bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "url_previews"}, []string{"network", "url_previews"}) - bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "extev_polls"}, []string{"network", "extev_polls"}) - bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "force_active_delivery_receipts"}, []string{"network", "force_active_delivery_receipts"}) - bridgeconfig.CopyToOtherLocation(helper, up.Int, []string{"bridge", "history_sync", "max_initial_conversations"}, []string{"network", "history_sync", "max_initial_conversations"}) - bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "history_sync", "request_full_sync"}, []string{"network", "history_sync", "request_full_sync"}) - bridgeconfig.CopyToOtherLocation(helper, up.Int, []string{"bridge", "history_sync", "full_sync_config", "days_limit"}, []string{"network", "history_sync", "full_sync_config", "days_limit"}) - bridgeconfig.CopyToOtherLocation(helper, up.Int, []string{"bridge", "history_sync", "full_sync_config", "size_limit_mb"}, []string{"network", "history_sync", "full_sync_config", "size_limit_mb"}) - bridgeconfig.CopyToOtherLocation(helper, up.Int, []string{"bridge", "history_sync", "full_sync_config", "storage_quota_mb"}, []string{"network", "history_sync", "full_sync_config", "storage_quota_mb"}) - bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "history_sync", "media_requests", "auto_request_media"}, []string{"network", "history_sync", "media_requests", "auto_request_media"}) - bridgeconfig.CopyToOtherLocation(helper, up.Str, []string{"bridge", "history_sync", "media_requests", "request_method"}, []string{"network", "history_sync", "media_requests", "request_method"}) - bridgeconfig.CopyToOtherLocation(helper, up.Int, []string{"bridge", "history_sync", "media_requests", "request_local_time"}, []string{"network", "history_sync", "media_requests", "request_local_time"}) - bridgeconfig.CopyToOtherLocation(helper, up.Int, []string{"bridge", "history_sync", "media_requests", "max_async_handle"}, []string{"network", "history_sync", "media_requests", "max_async_handle"}) -} diff --git a/cmd/mautrix-whatsapp/legacymigrate.sql b/cmd/mautrix-whatsapp/legacymigrate.sql deleted file mode 100644 index e3b9c3e3..00000000 --- a/cmd/mautrix-whatsapp/legacymigrate.sql +++ /dev/null @@ -1,215 +0,0 @@ -INSERT INTO "user" (bridge_id, mxid, management_room, access_token) -SELECT '', mxid, management_room, '' -FROM user_old; - -UPDATE "user" SET access_token=COALESCE((SELECT access_token FROM puppet_old WHERE custom_mxid="user".mxid AND access_token<>'' LIMIT 1), ''); - -INSERT INTO user_login (bridge_id, user_mxid, id, remote_name, space_room, metadata, remote_profile) -SELECT - '', -- bridge_id - mxid, -- user_mxid - username, -- id - '+' || username, -- remote_name - space_room, - -- only: postgres - jsonb_build_object --- only: sqlite (line commented) --- json_object - ( - 'wa_device_id', device, - 'phone_last_seen', phone_last_seen, - 'phone_last_pinged', phone_last_pinged, - 'timezone', timezone - ), -- metadata - '{}' -- remote_profile -FROM user_old -WHERE username<>'' AND device<>0; - -INSERT INTO ghost ( - bridge_id, id, name, avatar_id, avatar_hash, avatar_mxc, - name_set, avatar_set, contact_info_set, is_bot, identifiers, metadata -) -SELECT - '', -- bridge_id - username, -- id - COALESCE(displayname, ''), -- name - COALESCE(avatar, ''), -- avatar_id - '', -- avatar_hash - COALESCE(avatar_url, ''), -- avatar_mxc - name_set, - avatar_set, - contact_info_set, - false, -- is_bot - '[]', -- identifiers - -- only: postgres - jsonb_build_object - -- only: sqlite (line commented) --- json_object - ( - 'last_sync', last_sync - -- TODO name quality - ) -- metadata -FROM puppet_old; - --- Some messages don't have senders, so insert an empty ghost to match the foreign key constraint. -INSERT INTO ghost (bridge_id, id, name, avatar_id, avatar_hash, avatar_mxc, name_set, avatar_set, contact_info_set, is_bot, identifiers, metadata) -VALUES ('', '', '', '', '', '', false, false, false, false, '[]', '{}') -ON CONFLICT (bridge_id, id) DO NOTHING; - -DELETE FROM portal_old WHERE jid LIKE '%@s.whatsapp.net' AND (receiver='' OR receiver IS NULL) and mxid IS NULL; - -INSERT INTO portal ( - bridge_id, id, receiver, mxid, parent_id, parent_receiver, relay_bridge_id, relay_login_id, other_user_id, - name, topic, avatar_id, avatar_hash, avatar_mxc, name_set, avatar_set, topic_set, - name_is_custom, in_space, room_type, disappear_type, disappear_timer, metadata -) -SELECT - '', -- bridge_id - jid, -- id - CASE WHEN receiver LIKE '%@s.whatsapp.net' THEN replace(receiver, '@s.whatsapp.net', '') ELSE '' END, -- receiver - mxid, - CASE WHEN EXISTS(SELECT 1 FROM portal_old WHERE jid=parent_group) THEN parent_group ELSE NULL END, -- parent_id - '', -- parent_receiver - CASE WHEN relay_user_id<>'' THEN '' END, -- relay_bridge_id - (SELECT id FROM user_login WHERE user_mxid=relay_user_id), -- relay_login_id - CASE WHEN jid LIKE '%@s.whatsapp.net' THEN replace(jid, '@s.whatsapp.net', '') ELSE '' END, -- other_user_id - name, - topic, - avatar, -- avatar_id - '', -- avatar_hash - COALESCE(avatar_url, ''), -- avatar_mxc - name_set, - avatar_set, - topic_set, - jid NOT LIKE '%@s.whatsapp.net', -- name_is_custom - in_space, - CASE - WHEN is_parent THEN 'space' - WHEN jid LIKE '%@s.whatsapp.net' THEN 'dm' - ELSE '' - END, -- room_type - CASE WHEN expiration_time>0 THEN 'after_read' END, -- disappear_type - CASE WHEN expiration_time > 0 THEN expiration_time * 1000000000 END, -- disappear_timer - -- only: postgres - jsonb_build_object - -- only: sqlite (line commented) --- json_object - ( - 'last_sync', last_sync - ) -- metadata -FROM portal_old; - --- only: sqlite -DELETE FROM user_portal_old WHERE rowid IN (SELECT rowid FROM pragma_foreign_key_check('user_portal_old')); - -INSERT INTO user_portal (bridge_id, user_mxid, login_id, portal_id, portal_receiver, in_space, preferred, last_read) -SELECT - '', -- bridge_id - user_mxid, - (SELECT id FROM user_login WHERE user_login.user_mxid=user_portal_old.user_mxid), -- login_id - portal_jid, -- portal_id - CASE WHEN portal_receiver LIKE '%@s.whatsapp.net' THEN replace(portal_receiver, '@s.whatsapp.net', '') ELSE '' END, -- portal_receiver - in_space, - false, -- preferred - last_read_ts * 1000000000 -- last_read -FROM user_portal_old WHERE EXISTS(SELECT 1 FROM user_login WHERE user_login.user_mxid=user_portal_old.user_mxid); - -ALTER TABLE message_old ADD COLUMN combined_id TEXT; -DELETE FROM message_old WHERE sender IS NULL; -UPDATE message_old SET combined_id = chat_jid || ':' || ( - CASE WHEN sender LIKE '%:%@s.whatsapp.net' - THEN (split_part(replace(sender, '@s.whatsapp.net', ''), ':', 1) || '@s.whatsapp.net') - ELSE sender - END -) || ':' || jid; -DELETE FROM message_old WHERE timestamp<0; --- only: sqlite for next 2 lines -DELETE FROM message_old WHERE rowid IN (SELECT rowid FROM pragma_foreign_key_check('message_old')); -DELETE FROM reaction_old WHERE rowid IN (SELECT rowid FROM pragma_foreign_key_check('reaction_old')); -DELETE FROM message_old WHERE sender NOT LIKE '%@s.whatsapp.net' AND sender<>chat_jid; -DELETE FROM reaction_old WHERE sender NOT LIKE '%@s.whatsapp.net'; -DELETE FROM reaction_old WHERE NOT EXISTS(SELECT 1 FROM puppet_old WHERE username=replace(sender, '@s.whatsapp.net', '')); - -INSERT INTO message ( - bridge_id, id, part_id, mxid, room_id, room_receiver, sender_id, sender_mxid, timestamp, edit_count, metadata -) -SELECT - '', -- bridge_id - combined_id, -- id - '', -- part_id - mxid, - chat_jid, -- room_id - CASE WHEN chat_receiver LIKE '%@s.whatsapp.net' THEN replace(chat_receiver, '@s.whatsapp.net', '') ELSE '' END, -- room_receiver - CASE WHEN sender=chat_jid AND sender NOT LIKE '%@s.whatsapp.net' - THEN '' - ELSE split_part(split_part(replace(sender, '@s.whatsapp.net', ''), ':', 1), '.', 1) - END, -- sender_id - sender_mxid, -- sender_mxid - timestamp * 1000000000, -- timestamp - 0, -- edit_count - -- only: postgres - jsonb_build_object - -- only: sqlite (line commented) --- json_object - ( - 'sender_device_id', CAST(nullif(split_part(replace(sender, '@s.whatsapp.net', ''), ':', 2), '') AS INTEGER), - 'broadcast_list_jid', broadcast_list_jid, - 'error', CAST(error AS TEXT) - ) -- metadata -FROM message_old; - -INSERT INTO reaction ( - bridge_id, message_id, message_part_id, sender_id, emoji_id, room_id, room_receiver, mxid, timestamp, emoji, metadata -) -SELECT - '', -- bridge_id - message_old.combined_id, -- message_id - '', -- message_part_id - replace(reaction_old.sender, '@s.whatsapp.net', ''), -- sender_id - '', -- emoji_id - reaction_old.chat_jid, -- room_id - CASE WHEN reaction_old.chat_receiver LIKE '%@s.whatsapp.net' THEN replace(reaction_old.chat_receiver, '@s.whatsapp.net', '') ELSE '' END, -- room_receiver - reaction_old.mxid, - 0, -- timestamp - '', -- emoji - -- only: postgres - jsonb_build_object - -- only: sqlite (line commented) --- json_object - ( - 'sender_device_id', CAST(nullif(split_part(replace(reaction_old.sender, '@s.whatsapp.net', ''), ':', 2), '') AS INTEGER) - ) -- metadata -FROM reaction_old -LEFT JOIN message_old - ON reaction_old.chat_jid = message_old.chat_jid - AND reaction_old.chat_receiver = message_old.chat_receiver - AND reaction_old.target_jid = message_old.jid; - -INSERT INTO disappearing_message (bridge_id, mx_room, mxid, type, timer, disappear_at) -SELECT - '', -- bridge_id - room_id, - event_id, - 'after_read', - expire_in * 1000000, -- timer - expire_at * 1000000 -- disappear_at -FROM disappearing_message_old -WHERE room_id IN (SELECT mxid FROM portal WHERE mxid IS NOT NULL); - -INSERT INTO whatsapp_poll_option_id (bridge_id, msg_mxid, opt_id, opt_hash) -SELECT '', msg_mxid, opt_id, opt_hash -FROM poll_option_id_old; - -DROP TABLE backfill_queue_old; -DROP TABLE backfill_state_old; -DROP TABLE disappearing_message_old; -DROP TABLE history_sync_message_old; -DROP TABLE history_sync_conversation_old; -DROP TABLE media_backfill_requests_old; -DROP TABLE poll_option_id_old; -DROP TABLE user_portal_old; -DROP TABLE reaction_old; -DROP TABLE message_old; -DROP TABLE puppet_old; -DROP TABLE portal_old; -DROP TABLE user_old; diff --git a/cmd/mautrix-whatsapp/legacyprovision.go b/cmd/mautrix-whatsapp/legacyprovision.go deleted file mode 100644 index 169fa014..00000000 --- a/cmd/mautrix-whatsapp/legacyprovision.go +++ /dev/null @@ -1,307 +0,0 @@ -package main - -import ( - "context" - "errors" - "net/http" - "regexp" - "strings" - - "github.com/gorilla/websocket" - "github.com/rs/zerolog/hlog" - "go.mau.fi/util/exhttp" - "go.mau.fi/whatsmeow/types" - "maunium.net/go/mautrix" - "maunium.net/go/mautrix/bridgev2" - "maunium.net/go/mautrix/bridgev2/matrix" - "maunium.net/go/mautrix/bridgev2/status" - "maunium.net/go/mautrix/id" - - "go.mau.fi/mautrix-whatsapp/pkg/connector" - "go.mau.fi/mautrix-whatsapp/pkg/waid" -) - -var upgrader = websocket.Upgrader{ - CheckOrigin: func(r *http.Request) bool { - return true - }, - Subprotocols: []string{"net.maunium.whatsapp.login"}, -} - -func legacyProvAuth(r *http.Request) string { - if !strings.HasSuffix(r.URL.Path, "/v1/login") { - return "" - } - authParts := strings.Split(r.Header.Get("Sec-WebSocket-Protocol"), ",") - for _, part := range authParts { - part = strings.TrimSpace(part) - if strings.HasPrefix(part, "net.maunium.whatsapp.auth-") { - return strings.TrimPrefix(part, "net.maunium.whatsapp.auth-") - } - } - return "" -} - -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"` -} - -type Response struct { - Success bool `json:"success"` - Status string `json:"status"` -} - -func respondWebsocketWithError(conn *websocket.Conn, err error, message string) { - var mautrixRespErr mautrix.RespError - var bv2RespErr bridgev2.RespError - if errors.As(err, &bv2RespErr) { - mautrixRespErr = mautrix.RespError(bv2RespErr) - } else if !errors.As(err, &mautrixRespErr) { - mautrixRespErr = mautrix.RespError{ - Err: message, - ErrCode: "M_UNKNOWN", - StatusCode: http.StatusInternalServerError, - } - } - _ = conn.WriteJSON(&mautrixRespErr) -} - -var notNumbers = regexp.MustCompile("[^0-9]") - -func legacyProvLogin(w http.ResponseWriter, r *http.Request) { - log := hlog.FromRequest(r) - - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - log.Err(err).Msg("Failed to upgrade connection to websocket") - return - } - defer func() { - err := conn.Close() - if err != nil { - log.Debug().Err(err).Msg("Error closing websocket") - } - }() - - go func() { - // Read everything so SetCloseHandler() works - for { - _, _, err = conn.ReadMessage() - if err != nil { - break - } - } - }() - ctx, cancel := context.WithCancel(context.Background()) - conn.SetCloseHandler(func(code int, text string) error { - log.Debug().Int("close_code", code).Msg("Login websocket closed, cancelling login") - cancel() - return nil - }) - - user := m.Matrix.Provisioning.GetUser(r) - loginFlowID := connector.LoginFlowIDQR - phoneNum := r.URL.Query().Get("phone_number") - if phoneNum != "" { - phoneNum = notNumbers.ReplaceAllString(phoneNum, "") - if len(phoneNum) < 7 || strings.HasPrefix(phoneNum, "0") { - errorMsg := "Invalid phone number" - if len(phoneNum) > 6 { - errorMsg = "Please enter the phone number in international format" - } - _ = conn.WriteJSON(Error{ - Error: errorMsg, - ErrCode: "invalid phone number", - }) - return - } - loginFlowID = connector.LoginFlowIDPhone - } - login, err := c.CreateLogin(ctx, user, loginFlowID) - if err != nil { - log.Err(err).Msg("Failed to create login") - respondWebsocketWithError(conn, err, "Failed to create login") - return - } - waLogin := login.(*connector.WALogin) - waLogin.Timezone = r.URL.Query().Get("tz") - step, err := waLogin.Start(ctx) - if err != nil { - log.Err(err).Msg("Failed to start login") - respondWebsocketWithError(conn, err, "Failed to start login") - return - } - if phoneNum != "" { - if step.StepID != connector.LoginStepIDPhoneNumber { - respondWebsocketWithError(conn, errors.New("unexpected step"), "Unexpected step while starting phone number login") - waLogin.Cancel() - return - } - step, err = waLogin.SubmitUserInput(ctx, map[string]string{"phone_number": phoneNum}) - if err != nil { - log.Err(err).Msg("Failed to submit phone number") - respondWebsocketWithError(conn, err, "Failed to start phone code login") - return - } else if step.StepID != connector.LoginStepIDCode { - respondWebsocketWithError(conn, errors.New("unexpected step"), "Unexpected step after submitting phone number") - waLogin.Cancel() - return - } - _ = conn.WriteJSON(map[string]any{ - "pairing_code": step.DisplayAndWaitParams.Data, - "timeout": 180, - }) - } else if step.StepID != connector.LoginStepIDQR { - respondWebsocketWithError(conn, errors.New("unexpected step"), "Unexpected step while starting QR login") - waLogin.Cancel() - return - } else { - _ = conn.WriteJSON(map[string]any{ - "code": step.DisplayAndWaitParams.Data, - "timeout": 60, - }) - } - for { - step, err = waLogin.Wait(ctx) - if err != nil { - log.Err(err).Msg("Failed to wait for login") - respondWebsocketWithError(conn, err, "Failed to wait for login") - } else if step.StepID == connector.LoginStepIDQR { - _ = conn.WriteJSON(map[string]any{ - "code": step.DisplayAndWaitParams.Data, - "timeout": 20, - }) - continue - } else if step.StepID != connector.LoginStepIDComplete { - respondWebsocketWithError(conn, errors.New("unexpected step"), "Unexpected step while waiting for login") - waLogin.Cancel() - } else { - // TODO delete old logins - _ = conn.WriteJSON(map[string]any{ - "success": true, - "jid": waid.ParseUserLoginID(step.CompleteParams.UserLoginID, step.CompleteParams.UserLogin.Metadata.(*waid.UserLoginMetadata).WADeviceID).String(), - "platform": step.CompleteParams.UserLogin.Client.(*connector.WhatsAppClient).Device.Platform, - "phone": step.CompleteParams.UserLogin.RemoteProfile.Phone, - }) - go handleLoginComplete(context.WithoutCancel(ctx), user, step.CompleteParams.UserLogin) - } - break - } -} -func handleLoginComplete(ctx context.Context, user *bridgev2.User, newLogin *bridgev2.UserLogin) { - allLogins := user.GetUserLogins() - for _, login := range allLogins { - if login.ID != newLogin.ID { - login.Delete(ctx, status.BridgeState{StateEvent: status.StateLoggedOut, Reason: "LOGIN_OVERRIDDEN"}, bridgev2.DeleteOpts{}) - } - } -} - -func legacyProvLogout(w http.ResponseWriter, r *http.Request) { - user := m.Matrix.Provisioning.GetUser(r) - allLogins := user.GetUserLogins() - if len(allLogins) == 0 { - exhttp.WriteJSONResponse(w, http.StatusOK, Error{ - Error: "You're not logged in", - ErrCode: "not logged in", - }) - return - } - for _, login := range allLogins { - // Intentionally don't delete the user login, only logout remote - login.Client.(*connector.WhatsAppClient).LogoutRemote(r.Context()) - } - exhttp.WriteJSONResponse(w, http.StatusOK, Response{true, "Logged out successfully"}) -} - -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).Device.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, - }, - }) -} diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 33dc8651..9dbf088e 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -1,11 +1,9 @@ package main import ( - "maunium.net/go/mautrix/bridgev2/bridgeconfig" "maunium.net/go/mautrix/bridgev2/matrix/mxmain" "go.mau.fi/mautrix-whatsapp/pkg/connector" - "go.mau.fi/mautrix-whatsapp/pkg/connector/wadb/upgrades" ) // Information to find out exactly which commit the bridge was built from. @@ -16,39 +14,15 @@ var ( BuildTime = "unknown" ) -var c = &connector.WhatsAppConnector{} var m = mxmain.BridgeMain{ Name: "mautrix-whatsapp", URL: "https://github.com/mautrix/whatsapp", Description: "A Matrix-WhatsApp puppeting bridge.", Version: "0.12.4", - Connector: c, + Connector: &connector.WhatsAppConnector{}, } func main() { - bridgeconfig.HackyMigrateLegacyNetworkConfig = migrateLegacyConfig - m.PostInit = func() { - m.CheckLegacyDB( - 57, - "v0.8.6", - "v0.11.0", - m.LegacyMigrateWithAnotherUpgrader( - legacyMigrateRenameTables, legacyMigrateCopyData, 21, - upgrades.Table, "whatsapp_version", 5, - ), - true, - ) - } - m.PostStart = func() { - if m.Matrix.Provisioning != nil { - m.Matrix.Provisioning.Router.HandleFunc("GET /v1/login", legacyProvLogin) - m.Matrix.Provisioning.Router.HandleFunc("POST /v1/logout", legacyProvLogout) - 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.GetAuthFromRequest = legacyProvAuth - } - } m.InitVersion(Tag, Commit, BuildTime) m.Run() } diff --git a/go.mod b/go.mod index 3ffe06e0..c0c6f672 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.24.0 toolchain go1.25.0 require ( - github.com/gorilla/websocket v1.5.0 github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.0 @@ -16,7 +15,7 @@ require ( golang.org/x/sync v0.16.0 google.golang.org/protobuf v1.36.7 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.1-0.20250826140716-0345a5356de1 + maunium.net/go/mautrix v0.25.1-0.20250827232041-3048d2edab7a ) require ( @@ -24,6 +23,7 @@ require ( github.com/coder/websocket v1.8.13 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.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 diff --git a/go.sum b/go.sum index e591eb16..e5f42c25 100644 --- a/go.sum +++ b/go.sum @@ -101,5 +101,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.25.1-0.20250826140716-0345a5356de1 h1:8U8dwv4dxhMLrtqjf9LoDuQ6daGj9qdw1/62HIvK2cY= -maunium.net/go/mautrix v0.25.1-0.20250826140716-0345a5356de1/go.mod h1:pDd6Ppg+1PbWrw/rg4ZQQfVYZICRGzH+DcliZ/BODvU= +maunium.net/go/mautrix v0.25.1-0.20250827232041-3048d2edab7a h1:dUczO0BSW59ldg2UC5ZhybfEnpGwLxMr7JPnKGt9+kE= +maunium.net/go/mautrix v0.25.1-0.20250827232041-3048d2edab7a/go.mod h1:pDd6Ppg+1PbWrw/rg4ZQQfVYZICRGzH+DcliZ/BODvU= From bd0d24e1e44f098c72c3f966638a1679a1623a85 Mon Sep 17 00:00:00 2001 From: Peter Smit <43498358+Petersmit27@users.noreply.github.com> Date: Thu, 28 Aug 2025 02:08:25 +0200 Subject: [PATCH 150/486] handlematrix: add support for room name/topic/avatar changes (#834) Fixes #833 Co-authored-by: Tulir Asokan --- pkg/connector/chatinfo.go | 13 ++++ pkg/connector/handlematrix.go | 143 ++++++++++++++++++++++++++++++++++ pkg/waid/dbmeta.go | 1 + 3 files changed, 157 insertions(+) diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 1a912a02..58855b52 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -225,6 +225,17 @@ func setAddressingMode(mode types.AddressingMode) bridgev2.ExtraUpdater[*bridgev } } +func setTopicID(id, topic string) bridgev2.ExtraUpdater[*bridgev2.Portal] { + return func(_ context.Context, portal *bridgev2.Portal) bool { + meta := portal.Metadata.(*waid.PortalMetadata) + if meta.TopicID != id && portal.Topic == topic { + meta.TopicID = id + return true + } + return false + } +} + func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupInfo) *bridgev2.ChatInfo { sendEventPL := defaultPL if info.IsAnnounce && !info.IsDefaultSubGroup { @@ -238,6 +249,7 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn wa.makePortalAvatarFetcher("", types.EmptyJID, time.Time{}), setDefaultSubGroupFlag(info.IsDefaultSubGroup && info.IsAnnounce), setAddressingMode(info.AddressingMode), + setTopicID(info.TopicID, info.Topic), ) wrapped := &bridgev2.ChatInfo{ Name: ptr.Ptr(info.Name), @@ -317,6 +329,7 @@ func (wa *WhatsAppClient) wrapGroupInfoChange(ctx context.Context, evt *events.G } if evt.Topic != nil { changes.Topic = &evt.Topic.Topic + changes.ExtraUpdates = bridgev2.MergeExtraUpdaters(changes.ExtraUpdates, setTopicID(evt.Topic.TopicID, evt.Topic.Topic)) } if evt.Ephemeral != nil { changes.Disappear = &database.DisappearingSetting{ diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index d271da67..c12ca98d 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -1,9 +1,13 @@ package connector import ( + "bytes" "context" + "crypto/sha256" "errors" "fmt" + "image" + "image/jpeg" "strings" "time" @@ -13,6 +17,7 @@ import ( "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waE2E" "go.mau.fi/whatsmeow/types" + "golang.org/x/image/draw" "google.golang.org/protobuf/proto" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/database" @@ -32,6 +37,9 @@ var ( _ bridgev2.PollHandlingNetworkAPI = (*WhatsAppClient)(nil) _ bridgev2.DisappearTimerChangingNetworkAPI = (*WhatsAppClient)(nil) _ bridgev2.MembershipHandlingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.RoomNameHandlingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.RoomTopicHandlingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.RoomAvatarHandlingNetworkAPI = (*WhatsAppClient)(nil) ) func (wa *WhatsAppClient) HandleMatrixPollStart(ctx context.Context, msg *bridgev2.MatrixPollStart) (*bridgev2.MatrixMessageResponse, error) { @@ -423,3 +431,138 @@ func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridg return true, nil } + +func (wa *WhatsAppClient) HandleMatrixRoomName(ctx context.Context, msg *bridgev2.MatrixRoomName) (bool, error) { + portalJID, err := waid.ParsePortalID(msg.Portal.ID) + if err != nil { + return false, err + } + + if msg.Portal.RoomType == database.RoomTypeDM { + return false, fmt.Errorf("cannot set room name for DM") + } + + err = wa.Client.SetGroupName(portalJID, msg.Content.Name) + if err != nil { + return false, err + } + + msg.Portal.Name = msg.Content.Name + msg.Portal.NameSet = true + + return true, nil +} + +func (wa *WhatsAppClient) HandleMatrixRoomTopic(ctx context.Context, msg *bridgev2.MatrixRoomTopic) (bool, error) { + portalJID, err := waid.ParsePortalID(msg.Portal.ID) + if err != nil { + return false, err + } + + if msg.Portal.RoomType == database.RoomTypeDM { + return false, fmt.Errorf("cannot set room topic for DM") + } + + newID := wa.Client.GenerateMessageID() + oldID := msg.Portal.Metadata.(*waid.PortalMetadata).TopicID + err = wa.Client.SetGroupTopic(portalJID, oldID, newID, msg.Content.Topic) + if err != nil { + return false, err + } + + msg.Portal.Topic = msg.Content.Topic + msg.Portal.TopicSet = true + msg.Portal.Metadata.(*waid.PortalMetadata).TopicID = newID + + return true, nil +} + +func (wa *WhatsAppClient) HandleMatrixRoomAvatar(ctx context.Context, msg *bridgev2.MatrixRoomAvatar) (bool, error) { + portalJID, err := waid.ParsePortalID(msg.Portal.ID) + if err != nil { + return false, err + } + + if msg.Portal.RoomType == database.RoomTypeDM { + return false, fmt.Errorf("cannot set room avatar for DM") + } + + var data []byte + if msg.Content.URL != "" || msg.Content.MSC3414File != nil { + data, err = msg.Portal.Bridge.Bot.DownloadMedia(ctx, msg.Content.URL, msg.Content.MSC3414File) + if err != nil { + return false, fmt.Errorf("failed to download avatar: %w", err) + } + + data, err = convertRoomAvatar(data) + if err != nil { + return false, err + } + } + + avatarID, err := wa.Client.SetGroupPhoto(portalJID, data) + if err != nil { + return false, err + } + + msg.Portal.AvatarMXC = msg.Content.URL + if data == nil { + msg.Portal.AvatarHash = [32]byte{} + msg.Portal.AvatarID = "remove" + } else { + msg.Portal.AvatarHash = sha256.Sum256(data) + msg.Portal.AvatarID = networkid.AvatarID(avatarID) + } + msg.Portal.AvatarSet = true + + return true, nil +} + +const avatarMaxSize = 720 +const avatarMinSize = 190 + +func convertRoomAvatar(data []byte) ([]byte, error) { + cfg, imageType, err := image.DecodeConfig(bytes.NewReader(data)) + if err != nil { + return nil, fmt.Errorf("failed to decode avatar: %w", err) + } + width, height := cfg.Width, cfg.Height + isCorrectSize := width == height && avatarMinSize < width && width < avatarMaxSize + if isCorrectSize && imageType == "jpeg" { + return data, nil + } else if len(data) > 10*1024*1024 || width > 12000 || height > 12000 { + return nil, fmt.Errorf("avatar is too large for re-encoding") + } + + img, _, err := image.Decode(bytes.NewReader(data)) + if err != nil { + return nil, fmt.Errorf("failed to decode avatar: %w", err) + } + + if !isCorrectSize { + var squareCrop image.Rectangle + var dstSize int + if width > height { + dstSize = max(avatarMinSize, min(height, avatarMaxSize)) + + offset := (width - height) / 2 + squareCrop = image.Rect(offset, 0, width-offset, height) + } else { + dstSize = max(avatarMinSize, min(width, avatarMaxSize)) + + offset := (height - width) / 2 + squareCrop = image.Rect(0, offset, width, height-offset) + } + + cropped := image.NewRGBA(image.Rect(0, 0, dstSize, dstSize)) + draw.BiLinear.Scale(cropped, cropped.Rect, img, squareCrop, draw.Src, nil) + img = cropped + } + + var buf bytes.Buffer + err = jpeg.Encode(&buf, img, &jpeg.Options{Quality: jpeg.DefaultQuality}) + if err != nil { + return nil, fmt.Errorf("failed to re-encode avatar: %w", err) + } + return buf.Bytes(), nil +} diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index 58b1521e..81fd895a 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -106,6 +106,7 @@ type ReactionMetadata struct { type PortalMetadata struct { DisappearingTimerSetAt int64 `json:"disappearing_timer_set_at,omitempty"` + TopicID string `json:"topic_id,omitempty"` LastSync jsontime.Unix `json:"last_sync,omitempty"` CommunityAnnouncementGroup bool `json:"is_cag,omitempty"` AddressingMode types.AddressingMode `json:"addressing_mode,omitempty"` From 38390b9801af04e355e96ce0aa48ca2514b740b6 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 31 Aug 2025 22:25:00 +0300 Subject: [PATCH 151/486] changelog: document requirement to have an up-to-date config --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b85c5e7..ef09e27b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ * Removed legacy provisioning API and database legacy migration. Upgrading directly from versions prior to v0.11.0 is not supported. + * If you've been using the bridge since before v0.11.0 and have prevented the + bridge from writing to the config, you must either update the config + manually or allow the bridge to update it for you **before** upgrading to + this release. * Added support for `com.beeper.disappearing_timer` state event, which stores the disappearing setting of chats and allows changing the setting from Matrix. * Added lottieconverter to Docker images to enable converting animated stickers From 55d275b9e98893781ab05d9691ebb9ca30e272be Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 1 Sep 2025 18:03:59 +0300 Subject: [PATCH 152/486] startchat: add support for creating groups --- go.mod | 4 +- go.sum | 8 +- pkg/connector/capabilities.go | 35 +++++++-- pkg/connector/client.go | 2 + pkg/connector/handlewhatsapp.go | 3 + pkg/connector/startchat.go | 129 ++++++++++++++++++++++++++++++++ 6 files changed, 167 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index c0c6f672..b0f098e1 100644 --- a/go.mod +++ b/go.mod @@ -9,13 +9,13 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.0 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250825151841-7b1a3f7e5746 + go.mau.fi/whatsmeow v0.0.0-20250829123043-72d2ed58e998 golang.org/x/image v0.30.0 golang.org/x/net v0.43.0 golang.org/x/sync v0.16.0 google.golang.org/protobuf v1.36.7 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.1-0.20250827232041-3048d2edab7a + maunium.net/go/mautrix v0.25.1-0.20250901150120-f8c3a95de7a1 ) require ( diff --git a/go.sum b/go.sum index e5f42c25..773f3936 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ go.mau.fi/util v0.9.0 h1:ya3s3pX+Y8R2fgp0DbE7a0o3FwncoelDX5iyaeVE8ls= go.mau.fi/util v0.9.0/go.mod h1:pdL3lg2aaeeHIreGXNnPwhJPXkXdc3ZxsI6le8hOWEA= 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-20250825151841-7b1a3f7e5746 h1:WquxGXJBTlOTkBSltW52rqzRbQDAYD7JJgMli6FiIiY= -go.mau.fi/whatsmeow v0.0.0-20250825151841-7b1a3f7e5746/go.mod h1:xD0DR3s4T6PDd3BzgQG05AzLWxdKCmnvdCP3UuQvn9w= +go.mau.fi/whatsmeow v0.0.0-20250829123043-72d2ed58e998 h1:d6Utj53kD7W3O1actrw40Od7xC2/UUr/LHwbIkamE78= +go.mau.fi/whatsmeow v0.0.0-20250829123043-72d2ed58e998/go.mod h1:xD0DR3s4T6PDd3BzgQG05AzLWxdKCmnvdCP3UuQvn9w= 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.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= @@ -101,5 +101,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.25.1-0.20250827232041-3048d2edab7a h1:dUczO0BSW59ldg2UC5ZhybfEnpGwLxMr7JPnKGt9+kE= -maunium.net/go/mautrix v0.25.1-0.20250827232041-3048d2edab7a/go.mod h1:pDd6Ppg+1PbWrw/rg4ZQQfVYZICRGzH+DcliZ/BODvU= +maunium.net/go/mautrix v0.25.1-0.20250901150120-f8c3a95de7a1 h1:6eF+jMxhKxKADdJCHQ9SkmY8+xBpjObRfOOHwHgqrqg= +maunium.net/go/mautrix v0.25.1-0.20250901150120-f8c3a95de7a1/go.mod h1:pDd6Ppg+1PbWrw/rg4ZQQfVYZICRGzH+DcliZ/BODvU= diff --git a/pkg/connector/capabilities.go b/pkg/connector/capabilities.go index f850cf0e..f9b19251 100644 --- a/pkg/connector/capabilities.go +++ b/pkg/connector/capabilities.go @@ -16,6 +16,32 @@ import ( var WhatsAppGeneralCaps = &bridgev2.NetworkGeneralCapabilities{ DisappearingMessages: true, AggressiveUpdateInfo: true, + Provisioning: bridgev2.ProvisioningCapabilities{ + ResolveIdentifier: bridgev2.ResolveIdentifierCapabilities{ + CreateDM: true, + LookupPhone: true, + ContactList: true, + }, + GroupCreation: map[string]bridgev2.GroupTypeCapabilities{ + "group": { + TypeDescription: "a group chat", + + Name: bridgev2.GroupFieldCapability{Allowed: true, MaxLength: 100}, + Disappear: bridgev2.GroupFieldCapability{Allowed: true, DisappearSettings: waDisappearingCap}, + Participants: bridgev2.GroupFieldCapability{Allowed: true, Required: true, MinLength: 1}, + Parent: bridgev2.GroupFieldCapability{Allowed: true}, + }, + }, + }, +} + +var waDisappearingCap = &event.DisappearingTimerCapability{ + Types: []event.DisappearingType{event.DisappearingTypeAfterSend}, + Timers: []jsontime.Milliseconds{ + jsontime.MS(24 * time.Hour), // 24 hours + jsontime.MS(7 * 24 * time.Hour), // 7 days + jsontime.MS(90 * 24 * time.Hour), // 90 days + }, } func (wa *WhatsAppConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilities { @@ -148,14 +174,7 @@ var whatsappCaps = &event.RoomFeatures{ ReactionCount: 1, ReadReceipts: true, TypingNotifications: true, - DisappearingTimer: &event.DisappearingTimerCapability{ - Types: []event.DisappearingType{event.DisappearingTypeAfterSend}, - Timers: []jsontime.Milliseconds{ - jsontime.MS(24 * time.Hour), // 24 hours - jsontime.MS(7 * 24 * time.Hour), // 7 days - jsontime.MS(90 * 24 * time.Hour), // 90 days - }, - }, + DisappearingTimer: waDisappearingCap, } var whatsappCAGCaps *event.RoomFeatures diff --git a/pkg/connector/client.go b/pkg/connector/client.go index a0951f85..aefa5c61 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -53,6 +53,7 @@ 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](), } login.Client = w @@ -116,6 +117,7 @@ type WhatsAppClient struct { isNewLogin bool pushNamesSynced *exsync.Event lastPresence types.Presence + createDedup *exsync.Set[types.MessageID] } var ( diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 3e22f4ed..e5e8029a 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -670,6 +670,9 @@ 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 8543bfa6..7e03fc0b 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -18,14 +18,22 @@ package connector import ( "context" + "crypto/sha256" "errors" "fmt" "strings" + "time" + "github.com/rs/zerolog" + "go.mau.fi/util/ptr" + "go.mau.fi/whatsmeow" "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" + "maunium.net/go/mautrix/id" "go.mau.fi/mautrix-whatsapp/pkg/waid" ) @@ -35,6 +43,7 @@ var ( _ bridgev2.ContactListingNetworkAPI = (*WhatsAppClient)(nil) _ bridgev2.UserSearchingNetworkAPI = (*WhatsAppClient)(nil) _ bridgev2.GhostDMCreatingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.GroupCreatingNetworkAPI = (*WhatsAppClient)(nil) _ bridgev2.IdentifierValidatingNetwork = (*WhatsAppConnector)(nil) ) @@ -192,3 +201,123 @@ func (wa *WhatsAppClient) getContactList(ctx context.Context, filter string) ([] } return resp, nil } + +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 { + req.Participants[i] = waid.ParseUserID(participant) + } + if params.Parent != nil { + var err error + req.GroupLinkedParent.LinkedParentJID, err = waid.ParsePortalID(params.Parent.ID) + if err != nil { + return nil, fmt.Errorf("failed to parse parent ID: %w", err) + } + } + if params.Disappear != nil { + req.GroupEphemeral = types.GroupEphemeral{ + IsEphemeral: true, + DisappearingTimer: uint32(params.Disappear.Timer.Seconds()), + } + } + var avatarBytes []byte + var avatarMXC id.ContentURIString + if params.Avatar != nil { + avatarMXC = params.Avatar.URL + var err error + avatarBytes, err = wa.Main.Bridge.Bot.DownloadMedia(ctx, params.Avatar.URL, params.Avatar.MSC3414File) + if err != nil { + return nil, fmt.Errorf("failed to download avatar: %w", err) + } + avatarBytes, err = convertRoomAvatar(avatarBytes) + if err != nil { + return nil, err + } + } + resp, err := wa.Client.CreateGroup(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to create group: %w", err) + } + portal, err := wa.Main.Bridge.GetPortalByKey(ctx, wa.makeWAPortalKey(resp.JID)) + if err != nil { + return nil, fmt.Errorf("failed to get portal: %w", err) + } + groupInfo := wa.wrapGroupInfo(ctx, resp) + if params.RoomID != "" { + err = portal.UpdateMatrixRoomID(ctx, params.RoomID, bridgev2.UpdateMatrixRoomIDParams{ + SyncDBMetadata: func() { + portal.Name = req.Name + portal.NameSet = true + portal.ParentKey = ptr.Val(params.Parent) + if avatarBytes != nil { + portal.AvatarSet = true + portal.AvatarHash = sha256.Sum256(avatarBytes) + portal.AvatarMXC = avatarMXC + } + if req.DisappearingTimer > 0 { + portal.Disappear = database.DisappearingSetting{ + Type: event.DisappearingTypeAfterSend, + Timer: time.Duration(req.DisappearingTimer) * time.Second, + } + } + }, + OverwriteOldPortal: true, + TombstoneOldRoom: true, + DeleteOldRoom: true, + ChatInfo: groupInfo, + ChatInfoSource: wa.UserLogin, + }) + if err != nil { + return nil, fmt.Errorf("failed to update room ID after creating group: %w", err) + } + } + changed := false + if avatarBytes != nil { + avatarID, err := wa.Client.SetGroupPhoto(resp.JID, avatarBytes) + if err != nil { + zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to set group avatar after creating group") + } else { + portal.AvatarID = networkid.AvatarID(avatarID) + portal.AvatarHash = sha256.Sum256(avatarBytes) + portal.AvatarMXC = avatarMXC + groupInfo.Avatar = &bridgev2.Avatar{ + ID: portal.AvatarID, + MXC: portal.AvatarMXC, + Hash: portal.AvatarHash, + } + changed = true + } + } + if params.Topic != nil { + newTopicID := wa.Client.GenerateMessageID() + err = wa.Client.SetGroupTopic(resp.JID, "", newTopicID, params.Topic.Topic) + if err != nil { + zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to set group topic after creating group") + } else { + portal.Topic = params.Topic.Topic + portal.TopicSet = params.RoomID != "" + portal.Metadata.(*waid.PortalMetadata).TopicID = newTopicID + changed = true + groupInfo.Topic = ¶ms.Topic.Topic + } + } + if changed { + err = portal.Save(ctx) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to save portal after post-creation updates") + } + } + return &bridgev2.CreateChatResponse{ + PortalKey: wa.makeWAPortalKey(resp.JID), + Portal: portal, + PortalInfo: groupInfo, + }, nil +} From 3c96a4186c43b611ffad985978d48a7bf5d76e30 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 9 Sep 2025 16:27:55 +0300 Subject: [PATCH 153/486] msgconv/from-whatsapp: add action message metadata to disappearing notices --- go.mod | 2 +- go.sum | 4 ++-- pkg/msgconv/wa-misc.go | 12 ++++++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index b0f098e1..eb8a3312 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( golang.org/x/sync v0.16.0 google.golang.org/protobuf v1.36.7 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.1-0.20250901150120-f8c3a95de7a1 + maunium.net/go/mautrix v0.25.1-0.20250909132418-41bbe4ace4c7 ) require ( diff --git a/go.sum b/go.sum index 773f3936..5bd26b76 100644 --- a/go.sum +++ b/go.sum @@ -101,5 +101,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.25.1-0.20250901150120-f8c3a95de7a1 h1:6eF+jMxhKxKADdJCHQ9SkmY8+xBpjObRfOOHwHgqrqg= -maunium.net/go/mautrix v0.25.1-0.20250901150120-f8c3a95de7a1/go.mod h1:pDd6Ppg+1PbWrw/rg4ZQQfVYZICRGzH+DcliZ/BODvU= +maunium.net/go/mautrix v0.25.1-0.20250909132418-41bbe4ace4c7 h1:TywnMUPZakwE9oU1jPC8pVZk3q5dP66LkyOQ5Z+e7cI= +maunium.net/go/mautrix v0.25.1-0.20250909132418-41bbe4ace4c7/go.mod h1:pDd6Ppg+1PbWrw/rg4ZQQfVYZICRGzH+DcliZ/BODvU= diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index 6120d77a..4cc72547 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -139,8 +139,16 @@ func (mc *MessageConverter) convertEphemeralSettingMessage(ctx context.Context, content.Body += ", but the change was ignored." } return &bridgev2.ConvertedMessagePart{ - Type: event.EventMessage, - Content: content, + Type: event.EventMessage, + Content: content, + Extra: map[string]any{ + "com.beeper.action_message": map[string]any{ + "type": "disappearing_timer", + "timer": disappear.Timer.Milliseconds(), + "timer_type": disappear.Type, + "implicit": false, + }, + }, DontBridge: dontBridge, }, nil } From c6798ebfeae530f787c85711543043f60c661091 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 12 Sep 2025 11:42:23 +0300 Subject: [PATCH 154/486] 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 eb8a3312..cca20e62 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( golang.org/x/sync v0.16.0 google.golang.org/protobuf v1.36.7 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.1-0.20250909132418-41bbe4ace4c7 + maunium.net/go/mautrix v0.25.1-0.20250911181014-4603a344ce1d ) require ( diff --git a/go.sum b/go.sum index 5bd26b76..b5b0598c 100644 --- a/go.sum +++ b/go.sum @@ -101,5 +101,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.25.1-0.20250909132418-41bbe4ace4c7 h1:TywnMUPZakwE9oU1jPC8pVZk3q5dP66LkyOQ5Z+e7cI= -maunium.net/go/mautrix v0.25.1-0.20250909132418-41bbe4ace4c7/go.mod h1:pDd6Ppg+1PbWrw/rg4ZQQfVYZICRGzH+DcliZ/BODvU= +maunium.net/go/mautrix v0.25.1-0.20250911181014-4603a344ce1d h1:jKAn+2wwFC3aLdgE7IBSxhVjB4JisBOz2oCLzj928+c= +maunium.net/go/mautrix v0.25.1-0.20250911181014-4603a344ce1d/go.mod h1:pDd6Ppg+1PbWrw/rg4ZQQfVYZICRGzH+DcliZ/BODvU= From 05b8c63efd788d470992dadb86d85802172655d8 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 12 Sep 2025 11:53:39 +0300 Subject: [PATCH 155/486] legacyprovision: temporarily restore start chat endpoints --- cmd/mautrix-whatsapp/legacyprovision.go | 112 ++++++++++++++++++++++++ cmd/mautrix-whatsapp/main.go | 7 ++ 2 files changed, 119 insertions(+) create mode 100644 cmd/mautrix-whatsapp/legacyprovision.go diff --git a/cmd/mautrix-whatsapp/legacyprovision.go b/cmd/mautrix-whatsapp/legacyprovision.go new file mode 100644 index 00000000..2a8479d8 --- /dev/null +++ b/cmd/mautrix-whatsapp/legacyprovision.go @@ -0,0 +1,112 @@ +package main + +import ( + "net/http" + "strings" + + "github.com/rs/zerolog/hlog" + "go.mau.fi/util/exhttp" + "go.mau.fi/whatsmeow/types" + "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).Device.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, + }, + }) +} diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 9dbf088e..25640718 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -23,6 +23,13 @@ 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.InitVersion(Tag, Commit, BuildTime) m.Run() } From 5d352f464abf69721d484d98c03ca134a2929ab9 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 12 Sep 2025 15:15:48 +0300 Subject: [PATCH 156/486] config: add support for redacted phone in displayname template --- go.mod | 7 +++++-- go.sum | 20 ++++++++++++++++---- pkg/connector/commands.go | 2 +- pkg/connector/config.go | 13 +++++++++---- pkg/connector/example-config.yaml | 11 ++++++----- 5 files changed, 37 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index cca20e62..8958b2c6 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,9 @@ toolchain go1.25.0 require ( github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.9.0 + go.mau.fi/util v0.9.1-0.20250912114103-419604f95907 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250829123043-72d2ed58e998 + go.mau.fi/whatsmeow v0.0.0-20250912121005-de128e174913 golang.org/x/image v0.30.0 golang.org/x/net v0.43.0 golang.org/x/sync v0.16.0 @@ -20,8 +20,10 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect + github.com/beeper/argo-go v1.1.2 // indirect github.com/coder/websocket v1.8.13 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/kr/pretty v0.3.1 // indirect @@ -36,6 +38,7 @@ require ( github.com/tidwall/match v1.1.1 // indirect 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.13 // indirect go.mau.fi/libsignal v0.2.0 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect diff --git a/go.sum b/go.sum index b5b0598c..3c81a2b6 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,12 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= +github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +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.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE= github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= @@ -9,6 +15,8 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV 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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -47,6 +55,8 @@ 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/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= @@ -61,16 +71,18 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 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.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.0 h1:oRXj3OHhEJq51BFEM8/50UZblmWiTYH93hsNTPcbk90= go.mau.fi/libsignal v0.2.0/go.mod h1:tvjoDsMejgT38CXTXwqaYu8itBiY8O2Mb6biWvZBb9k= -go.mau.fi/util v0.9.0 h1:ya3s3pX+Y8R2fgp0DbE7a0o3FwncoelDX5iyaeVE8ls= -go.mau.fi/util v0.9.0/go.mod h1:pdL3lg2aaeeHIreGXNnPwhJPXkXdc3ZxsI6le8hOWEA= +go.mau.fi/util v0.9.1-0.20250912114103-419604f95907 h1:VBApThdwZPIaNKt2sdfTk2Pal2Qqcaokjpq9e/LkVmg= +go.mau.fi/util v0.9.1-0.20250912114103-419604f95907/go.mod h1:pdL3lg2aaeeHIreGXNnPwhJPXkXdc3ZxsI6le8hOWEA= 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-20250829123043-72d2ed58e998 h1:d6Utj53kD7W3O1actrw40Od7xC2/UUr/LHwbIkamE78= -go.mau.fi/whatsmeow v0.0.0-20250829123043-72d2ed58e998/go.mod h1:xD0DR3s4T6PDd3BzgQG05AzLWxdKCmnvdCP3UuQvn9w= +go.mau.fi/whatsmeow v0.0.0-20250912121005-de128e174913 h1:7HoljuytFlZuatxwloGqwMi2o1ovqY+IN67oxlpj4Mo= +go.mau.fi/whatsmeow v0.0.0-20250912121005-de128e174913/go.mod h1:HSrGXWxbQ7nIBbLfl4d1XB/qGtEs7GXrQMjfnGLeymM= 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.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= diff --git a/pkg/connector/commands.go b/pkg/connector/commands.go index 88687411..ec1b999e 100644 --- a/pkg/connector/commands.go +++ b/pkg/connector/commands.go @@ -114,7 +114,7 @@ func fnSync(ce *commands.Event) { }) ce.React("✅") case "groups": - groups, err := wa.Client.GetJoinedGroups() + groups, err := wa.Client.GetJoinedGroups(ce.Ctx) if err != nil { ce.Reply("Failed to get joined groups: %v", err) return diff --git a/pkg/connector/config.go b/pkg/connector/config.go index fd6ac0bd..aedb8f97 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -148,11 +148,11 @@ type DisplaynameParams struct { func (c *Config) FormatDisplayname(jid types.JID, phone string, contact types.ContactInfo) string { var nameBuf strings.Builder - if phone == "" { + if phone == "" && jid.Server == types.DefaultUserServer { phone = "+" + jid.User - if jid.Server != types.DefaultUserServer { - phone = jid.User - } + } + if contact.RedactedPhone == "" && phone != "" { + contact.RedactedPhone = redactPhone(phone) } err := c.displaynameTemplate.Execute(&nameBuf, &DisplaynameParams{ ContactInfo: contact, @@ -171,6 +171,11 @@ func (c *Config) FormatDisplayname(jid types.JID, phone string, contact types.Co return nameBuf.String() } +func redactPhone(phone string) string { + // This doesn't keep 2+ digit country codes properly, but whatever + return phone[:2] + strings.Repeat("∙", len(phone)-4) + phone[len(phone)-2:] +} + func (wa *WhatsAppConnector) GetConfig() (string, any, up.Upgrader) { return ExampleConfig, &wa.Config, &up.StructUpgrader{ SimpleUpgrader: up.SimpleUpgrader(upgradeConfig), diff --git a/pkg/connector/example-config.yaml b/pkg/connector/example-config.yaml index 6b9e2e43..99162b37 100644 --- a/pkg/connector/example-config.yaml +++ b/pkg/connector/example-config.yaml @@ -13,11 +13,12 @@ get_proxy_url: null proxy_only_login: false # Displayname template for WhatsApp users. -# {{.PushName}} - nickname set by the WhatsApp user -# {{.BusinessName}} - validated WhatsApp business name -# {{.Phone}} - phone number (international format) -# {{.FullName}} - Name you set in the contacts list -displayname_template: "{{or .BusinessName .PushName .Phone}} (WA)" +# {{.PushName}} - nickname set by the WhatsApp user +# {{.BusinessName}} - validated WhatsApp business name +# {{.Phone}} - phone number (international format) +# {{.RedactedPhone}} - phone number with middle digits replaced by "∙" +# {{.FullName}} - Name you set in the contacts list +displayname_template: '{{or .BusinessName .PushName .Phone .RedactedPhone "Unknown user"}} (WA)' # Should incoming calls send a message to the Matrix room? call_start_notices: true From 2b0c291c49a21e8ae7345ae32a4d9a72c90cf758 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 16 Sep 2025 15:05:03 +0300 Subject: [PATCH 157/486] Bump version to v0.12.5 --- CHANGELOG.md | 13 +++++++-- cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 28 +++++++++--------- go.sum | 56 ++++++++++++++++++------------------ 4 files changed, 54 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef09e27b..d32234d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,26 @@ -# v0.12.5 (unreleased) +# v0.12.5 (2025-09-16) * Removed legacy provisioning API and database legacy migration. Upgrading directly from versions prior to v0.11.0 is not supported. * If you've been using the bridge since before v0.11.0 and have prevented the bridge from writing to the config, you must either update the config manually or allow the bridge to update it for you **before** upgrading to - this release. + this release (i.e. run v0.12.4 once with config writing allowed). +* Added support for changing group name/topic/avatar from Matrix + (thanks to [@Petersmit27] in [#834]). +* Added `RedactedPhone` placeholder for displayname templates. This allows + community announcement groups (where you can't see participants phone numbers) + to have better names than random numbers. * Added support for `com.beeper.disappearing_timer` state event, which stores the disappearing setting of chats and allows changing the setting from Matrix. * Added lottieconverter to Docker images to enable converting animated stickers from WhatsApp. +* Added support for creating WhatsApp groups. * Fixed sent PNGs not being rendered on WhatsApp iOS. +[@Petersmit27]: https://github.com/Petersmit27 +[#834]: https://github.com/mautrix/whatsapp/pull/834 + # v0.12.4 (2025-08-16) * Deprecated legacy provisioning API. The `/_matrix/provision/v1` endpoints will diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 25640718..068e67d8 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: "0.12.4", + Version: "0.12.5", Connector: &connector.WhatsAppConnector{}, } diff --git a/go.mod b/go.mod index 8958b2c6..b78e9f6c 100644 --- a/go.mod +++ b/go.mod @@ -2,26 +2,26 @@ module go.mau.fi/mautrix-whatsapp go 1.24.0 -toolchain go1.25.0 +toolchain go1.25.1 require ( github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.9.1-0.20250912114103-419604f95907 + go.mau.fi/util v0.9.1 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250912121005-de128e174913 - golang.org/x/image v0.30.0 - golang.org/x/net v0.43.0 - golang.org/x/sync v0.16.0 - google.golang.org/protobuf v1.36.7 + go.mau.fi/whatsmeow v0.0.0-20250916115455-914d640cc83c + golang.org/x/image v0.31.0 + golang.org/x/net v0.44.0 + golang.org/x/sync v0.17.0 + google.golang.org/protobuf v1.36.9 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.1-0.20250911181014-4603a344ce1d + maunium.net/go/mautrix v0.25.1 ) require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/beeper/argo-go v1.1.2 // indirect - github.com/coder/websocket v1.8.13 // indirect + github.com/coder/websocket v1.8.14 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect github.com/google/uuid v1.6.0 // indirect @@ -30,7 +30,7 @@ require ( 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.32 // indirect - github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect + github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490 // 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 @@ -42,10 +42,10 @@ require ( github.com/yuin/goldmark v1.7.13 // indirect go.mau.fi/libsignal v0.2.0 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect - golang.org/x/crypto v0.41.0 // indirect - golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.28.0 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.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 3c81a2b6..eab67864 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.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE= -github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= +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 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -42,8 +42,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe h1:vHpqOnPlnkba8iSxU4j/CvDSS9J4+F4473esQsYLGoE= -github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490 h1:QTvNkZ5ylY0PGgA+Lih+GdboMLY/G9SEGLMEGVjTVA4= +github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490/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= @@ -59,8 +59,8 @@ github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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= @@ -77,33 +77,33 @@ github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.0 h1:oRXj3OHhEJq51BFEM8/50UZblmWiTYH93hsNTPcbk90= go.mau.fi/libsignal v0.2.0/go.mod h1:tvjoDsMejgT38CXTXwqaYu8itBiY8O2Mb6biWvZBb9k= -go.mau.fi/util v0.9.1-0.20250912114103-419604f95907 h1:VBApThdwZPIaNKt2sdfTk2Pal2Qqcaokjpq9e/LkVmg= -go.mau.fi/util v0.9.1-0.20250912114103-419604f95907/go.mod h1:pdL3lg2aaeeHIreGXNnPwhJPXkXdc3ZxsI6le8hOWEA= +go.mau.fi/util v0.9.1 h1:A+XKHRsjKkFi2qOm4RriR1HqY2hoOXNS3WFHaC89r2Y= +go.mau.fi/util v0.9.1/go.mod h1:M0bM9SyaOWJniaHs9hxEzz91r5ql6gYq6o1q5O1SsjQ= 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-20250912121005-de128e174913 h1:7HoljuytFlZuatxwloGqwMi2o1ovqY+IN67oxlpj4Mo= -go.mau.fi/whatsmeow v0.0.0-20250912121005-de128e174913/go.mod h1:HSrGXWxbQ7nIBbLfl4d1XB/qGtEs7GXrQMjfnGLeymM= +go.mau.fi/whatsmeow v0.0.0-20250916115455-914d640cc83c h1:G8mT+1CY76BM49kCnw2OQmE2PzNUWjotIv2DxqcykCc= +go.mau.fi/whatsmeow v0.0.0-20250916115455-914d640cc83c/go.mod h1:dvltpCF0rOHbbur25DHbQ3Ovi747z2Pm11S2M7p1T74= 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.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= -golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0= -golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= -golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4= -golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= +golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= +golang.org/x/image v0.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA= +golang.org/x/image v0.31.0/go.mod h1:R9ec5Lcp96v9FTF+ajwaH3uGxPH4fKfHHAVbUILxghA= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 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.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= -google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= 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= @@ -113,5 +113,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.25.1-0.20250911181014-4603a344ce1d h1:jKAn+2wwFC3aLdgE7IBSxhVjB4JisBOz2oCLzj928+c= -maunium.net/go/mautrix v0.25.1-0.20250911181014-4603a344ce1d/go.mod h1:pDd6Ppg+1PbWrw/rg4ZQQfVYZICRGzH+DcliZ/BODvU= +maunium.net/go/mautrix v0.25.1 h1:+xe3eXtQNcDPU/HoWzvSOA5YX57iqlYI1TXf/fM0KWs= +maunium.net/go/mautrix v0.25.1/go.mod h1:iSueLJ/2fBaNrsTObGqi1j0cl/loxrtAjmjay1scYD8= From 8692347211f874af0aeb0a94138b4ca7eca9b909 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 17 Sep 2025 15:04:12 +0300 Subject: [PATCH 158/486] msgconv/from-whatsapp: ignore disappearing timers in backfill --- pkg/connector/backfill.go | 2 +- pkg/connector/events.go | 4 ++-- pkg/msgconv/from-whatsapp.go | 3 ++- pkg/msgconv/wa-misc.go | 27 +++++++++++++++------------ 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index dce00382..2c3a14e4 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -528,7 +528,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, nil), + ConvertedMessage: wa.Main.MsgConv.ToMatrix(ctx, portal, wa.Client, intent, msg, rawMsg, info, 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 2917f91a..3a30d365 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -188,7 +188,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(), 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() { @@ -265,7 +265,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) converted := evt.wa.Main.MsgConv.ToMatrix( - ctx, portal, evt.wa.Client, intent, evt.Message, evt.MsgEvent.RawMessage, &evt.Info, evt.isViewOnce(), nil, + ctx, portal, evt.wa.Client, intent, evt.Message, evt.MsgEvent.RawMessage, &evt.Info, evt.isViewOnce(), false, nil, ) if isFailedMedia(converted) { evt.postHandle = func() { diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 8e053849..c4be4a46 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -137,6 +137,7 @@ func (mc *MessageConverter) ToMatrix( rawWaMsg *waE2E.Message, info *types.MessageInfo, isViewOnce bool, + isBackfill bool, previouslyConvertedPart *bridgev2.ConvertedMessagePart, ) *bridgev2.ConvertedMessage { ctx = context.WithValue(ctx, contextKeyClient, client) @@ -212,7 +213,7 @@ func (mc *MessageConverter) ToMatrix( case waMsg.GroupInviteMessage != nil: 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) + part, contextInfo = mc.convertEphemeralSettingMessage(ctx, waMsg.ProtocolMessage, info.Timestamp, isBackfill) case waMsg.EncCommentMessage != nil: part = failedCommentPart default: diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index 4cc72547..f610715d 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -114,7 +114,7 @@ func (mc *MessageConverter) convertGroupInviteMessage(ctx context.Context, info }, msg.GetContextInfo() } -func (mc *MessageConverter) convertEphemeralSettingMessage(ctx context.Context, msg *waE2E.ProtocolMessage, ts time.Time) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { +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) disappear := database.DisappearingSetting{ @@ -126,17 +126,19 @@ func (mc *MessageConverter) convertEphemeralSettingMessage(ctx context.Context, } dontBridge := portal.Disappear == disappear content := bridgev2.DisappearingMessageNotice(disappear.Timer, false) - if msg.EphemeralSettingTimestamp == nil || portalMeta.DisappearingTimerSetAt < msg.GetEphemeralSettingTimestamp() { - portalMeta.DisappearingTimerSetAt = msg.GetEphemeralSettingTimestamp() - portal.UpdateDisappearingSetting(ctx, disappear, bridgev2.UpdateDisappearingSettingOpts{ - Sender: getIntent(ctx), - Timestamp: ts, - Implicit: false, - Save: true, - SendNotice: false, - }) - } else { - content.Body += ", but the change was ignored." + if !isBackfill { + if msg.EphemeralSettingTimestamp == nil || portalMeta.DisappearingTimerSetAt < msg.GetEphemeralSettingTimestamp() { + portalMeta.DisappearingTimerSetAt = msg.GetEphemeralSettingTimestamp() + portal.UpdateDisappearingSetting(ctx, disappear, bridgev2.UpdateDisappearingSettingOpts{ + Sender: getIntent(ctx), + Timestamp: ts, + Implicit: false, + Save: true, + SendNotice: false, + }) + } else { + content.Body += ", but the change was ignored." + } } return &bridgev2.ConvertedMessagePart{ Type: event.EventMessage, @@ -147,6 +149,7 @@ func (mc *MessageConverter) convertEphemeralSettingMessage(ctx context.Context, "timer": disappear.Timer.Milliseconds(), "timer_type": disappear.Type, "implicit": false, + "backfill": isBackfill, }, }, DontBridge: dontBridge, From a5181b03ff65b0dadc8ac5c81edeb1d44e074e3e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 19 Sep 2025 14:32:31 +0300 Subject: [PATCH 159/486] capabilities: enable implicit read receipts --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/capabilities.go | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b78e9f6c..a83728a4 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( golang.org/x/sync v0.17.0 google.golang.org/protobuf v1.36.9 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.1 + maunium.net/go/mautrix v0.25.2-0.20250919113047-b760023dcaa3 ) require ( diff --git a/go.sum b/go.sum index eab67864..d23f26ed 100644 --- a/go.sum +++ b/go.sum @@ -113,5 +113,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.25.1 h1:+xe3eXtQNcDPU/HoWzvSOA5YX57iqlYI1TXf/fM0KWs= -maunium.net/go/mautrix v0.25.1/go.mod h1:iSueLJ/2fBaNrsTObGqi1j0cl/loxrtAjmjay1scYD8= +maunium.net/go/mautrix v0.25.2-0.20250919113047-b760023dcaa3 h1:MU7lyDtxVAsd1Mh+WGJOFD5PsGLRRl3Xv67zizxLkgc= +maunium.net/go/mautrix v0.25.2-0.20250919113047-b760023dcaa3/go.mod h1:iSueLJ/2fBaNrsTObGqi1j0cl/loxrtAjmjay1scYD8= diff --git a/pkg/connector/capabilities.go b/pkg/connector/capabilities.go index f9b19251..095459fd 100644 --- a/pkg/connector/capabilities.go +++ b/pkg/connector/capabilities.go @@ -16,6 +16,7 @@ import ( var WhatsAppGeneralCaps = &bridgev2.NetworkGeneralCapabilities{ DisappearingMessages: true, AggressiveUpdateInfo: true, + ImplicitReadReceipts: true, Provisioning: bridgev2.ProvisioningCapabilities{ ResolveIdentifier: bridgev2.ResolveIdentifierCapabilities{ CreateDM: true, From f63240298944d47c1d523e85dfc81a7d7825f470 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 19 Sep 2025 15:48:58 +0300 Subject: [PATCH 160/486] handlewhatsapp: reroute LID calls to phone number DM portal --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/handlewhatsapp.go | 14 +++++++++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index a83728a4..06ea7457 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.1 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250916115455-914d640cc83c + go.mau.fi/whatsmeow v0.0.0-20250919124702-c8bdfd36d05e golang.org/x/image v0.31.0 golang.org/x/net v0.44.0 golang.org/x/sync v0.17.0 diff --git a/go.sum b/go.sum index d23f26ed..dea63605 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.mau.fi/util v0.9.1 h1:A+XKHRsjKkFi2qOm4RriR1HqY2hoOXNS3WFHaC89r2Y= go.mau.fi/util v0.9.1/go.mod h1:M0bM9SyaOWJniaHs9hxEzz91r5ql6gYq6o1q5O1SsjQ= 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-20250916115455-914d640cc83c h1:G8mT+1CY76BM49kCnw2OQmE2PzNUWjotIv2DxqcykCc= -go.mau.fi/whatsmeow v0.0.0-20250916115455-914d640cc83c/go.mod h1:dvltpCF0rOHbbur25DHbQ3Ovi747z2Pm11S2M7p1T74= +go.mau.fi/whatsmeow v0.0.0-20250919124702-c8bdfd36d05e h1:+o9+K5Qyo/490H4NyPVA0Es+aEzmmjaK37/437hV2LM= +go.mau.fi/whatsmeow v0.0.0-20250919124702-c8bdfd36d05e/go.mod h1:dvltpCF0rOHbbur25DHbQ3Ovi747z2Pm11S2M7p1T74= 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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index e5e8029a..f7c2348a 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -88,9 +88,9 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { success = wa.handleWAUndecryptableMessage(ctx, evt) case *events.CallOffer: - success = wa.handleWACallStart(ctx, evt.GroupJID, evt.CallCreator, evt.CallID, "", evt.Timestamp) + success = wa.handleWACallStart(ctx, evt.GroupJID, evt.CallCreator, evt.CallCreatorAlt, evt.CallID, "", evt.Timestamp) case *events.CallOfferNotice: - success = wa.handleWACallStart(ctx, evt.GroupJID, evt.CallCreator, evt.CallID, evt.Type, evt.Timestamp) + success = wa.handleWACallStart(ctx, evt.GroupJID, evt.CallCreator, evt.CallCreatorAlt, evt.CallID, evt.Type, evt.Timestamp) case *events.CallTerminate, *events.CallRelayLatency, *events.CallAccept, *events.UnknownCallEvent: // ignore case *events.IdentityChange: @@ -475,10 +475,18 @@ func (wa *WhatsAppClient) handleWALogout(reason events.ConnectFailureReason, onC const callEventMaxAge = 15 * time.Minute -func (wa *WhatsAppClient) handleWACallStart(ctx context.Context, group, sender types.JID, id, callType string, ts time.Time) bool { +func (wa *WhatsAppClient) handleWACallStart(ctx context.Context, group, sender, senderAlt types.JID, id, callType string, ts time.Time) bool { if !wa.Main.Config.CallStartNotices || time.Since(ts) > callEventMaxAge { return true } + if sender.Server == types.HiddenUserServer && senderAlt.Server == types.DefaultUserServer { + wa.UserLogin.Log.Debug(). + Stringer("lid", sender). + Stringer("pn", senderAlt). + Str("call_id", id). + Msg("Forced LID caller to phone number in incoming call") + sender, senderAlt = senderAlt, sender + } chat := group if chat.IsEmpty() { chat = sender From c2c98f273679f96bd3d083d852dde253ae5c1641 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 24 Sep 2025 20:30:20 +0300 Subject: [PATCH 161/486] 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 06ea7457..624d7acf 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( golang.org/x/sync v0.17.0 google.golang.org/protobuf v1.36.9 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.2-0.20250919113047-b760023dcaa3 + maunium.net/go/mautrix v0.25.2-0.20250924172949-cf29b07f32ce ) require ( diff --git a/go.sum b/go.sum index dea63605..06e36889 100644 --- a/go.sum +++ b/go.sum @@ -113,5 +113,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.25.2-0.20250919113047-b760023dcaa3 h1:MU7lyDtxVAsd1Mh+WGJOFD5PsGLRRl3Xv67zizxLkgc= -maunium.net/go/mautrix v0.25.2-0.20250919113047-b760023dcaa3/go.mod h1:iSueLJ/2fBaNrsTObGqi1j0cl/loxrtAjmjay1scYD8= +maunium.net/go/mautrix v0.25.2-0.20250924172949-cf29b07f32ce h1:sRBScG2xa66ERjgrX+7D//HorAhmNHwxB2Kzltg+aUg= +maunium.net/go/mautrix v0.25.2-0.20250924172949-cf29b07f32ce/go.mod h1:iSueLJ/2fBaNrsTObGqi1j0cl/loxrtAjmjay1scYD8= From a39d89c9d848b3f0862dab1f68517f405c070c1b Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 25 Sep 2025 20:24:15 +0300 Subject: [PATCH 162/486] handlematrix: allow resyncing DM portals on viewing chat event --- pkg/connector/client.go | 2 +- pkg/connector/events.go | 2 +- pkg/connector/userinfo.go | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index aefa5c61..dc160ba7 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -423,7 +423,7 @@ func (wa *WhatsAppClient) HandleMatrixViewingChat(ctx context.Context, msg *brid // Reset, but don't save, portal last sync time for immediate sync now msg.Portal.Metadata.(*waid.PortalMetadata).LastSync.Time = time.Time{} // Enqueue for the sync, don't block on it completing - wa.EnqueuePortalResync(msg.Portal) + wa.EnqueuePortalResync(msg.Portal, true) if msg.Portal.OtherUserID != "" { // If this is a DM, also sync the ghost of the other user immediately diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 3a30d365..46545fd0 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -263,7 +263,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) + 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, ) diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index bfa98654..74ffc279 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -48,9 +48,11 @@ func (wa *WhatsAppClient) EnqueueGhostResync(ghost *bridgev2.Ghost) { wa.resyncQueueLock.Unlock() } -func (wa *WhatsAppClient) EnqueuePortalResync(portal *bridgev2.Portal) { +func (wa *WhatsAppClient) EnqueuePortalResync(portal *bridgev2.Portal, allowDM bool) { jid, _ := waid.ParsePortalID(portal.ID) - if jid.Server != types.GroupServer || portal.Metadata.(*waid.PortalMetadata).LastSync.Add(ResyncMinInterval).After(time.Now()) { + if portal.Metadata.(*waid.PortalMetadata).LastSync.Add(ResyncMinInterval).After(time.Now()) { + return + } else if !allowDM && jid.Server != types.GroupServer { return } wa.resyncQueueLock.Lock() From 9482665e9503a9df24c9106adbfd8449c15d8cf2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 25 Sep 2025 20:27:28 +0300 Subject: [PATCH 163/486] {chat,user}info: always save last sync ts if min interval is less than a day --- pkg/connector/chatinfo.go | 2 +- pkg/connector/userinfo.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 58855b52..5a8bfcf8 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -75,7 +75,7 @@ func (wa *WhatsAppClient) addExtrasToWrapped(ctx context.Context, portalJID type func updatePortalLastSyncAt(_ context.Context, portal *bridgev2.Portal) bool { meta := portal.Metadata.(*waid.PortalMetadata) - forceSave := time.Since(meta.LastSync.Time) > 24*time.Hour + forceSave := ResyncMinInterval < 24*time.Hour || time.Since(meta.LastSync.Time) > 24*time.Hour meta.LastSync = jsontime.UnixNow() return forceSave } diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 74ffc279..c9e2e9fb 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -245,7 +245,7 @@ func (wa *WhatsAppClient) contactToUserInfo(ctx context.Context, jid types.JID, func updateGhostLastSyncAt(_ context.Context, ghost *bridgev2.Ghost) bool { meta := ghost.Metadata.(*waid.GhostMetadata) - forceSave := time.Since(meta.LastSync.Time) > 24*time.Hour + forceSave := ResyncMinInterval < 24*time.Hour || time.Since(meta.LastSync.Time) > 24*time.Hour meta.LastSync = jsontime.UnixNow() return forceSave } From 462bce6f3761d6db2b8edc48965f05ae71782bc6 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 29 Sep 2025 19:23:13 +0300 Subject: [PATCH 164/486] handlewhatsapp: decrypt secret encrypted messages Closes #841 --- pkg/connector/handlewhatsapp.go | 10 ++++++++++ pkg/msgconv/wa-misc.go | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index f7c2348a..752ba33b 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -340,6 +340,16 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa 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, diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index f610715d..e6b100e0 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -158,7 +158,7 @@ func (mc *MessageConverter) convertEphemeralSettingMessage(ctx context.Context, const eventMessageTemplate = ` {{- if .Name -}} -

{{ .Name }}

+

{{ .Name }} {{- if .IsCanceled -}} (Canceled){{- end -}}

{{- end -}} {{- if .StartTime -}}

@@ -184,6 +184,7 @@ var eventMessageTplParsed = exerrors.Must(template.New("eventmessage").Parse(str type eventMessageParams struct { Name string + IsCanceled bool JoinLink string StartTimeISO string StartTime string @@ -196,6 +197,7 @@ type eventMessageParams struct { func (mc *MessageConverter) convertEventMessage(ctx context.Context, msg *waE2E.EventMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { params := &eventMessageParams{ Name: msg.GetName(), + IsCanceled: msg.GetIsCanceled(), JoinLink: msg.GetJoinLink(), Location: msg.GetLocation().GetName(), DescriptionHTML: template.HTML(parseWAFormattingToHTML(msg.GetDescription(), false)), From ca117161e8de83e67cf13f13773a56f6531cc9ae Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 29 Sep 2025 19:26:47 +0300 Subject: [PATCH 165/486] dependencies: update whatsmeow --- go.mod | 2 +- go.sum | 4 ++-- pkg/msgconv/wa-misc.go | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 624d7acf..f28ee2ee 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.1 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250919124702-c8bdfd36d05e + go.mau.fi/whatsmeow v0.0.0-20250929162548-7c04e9b206b1 golang.org/x/image v0.31.0 golang.org/x/net v0.44.0 golang.org/x/sync v0.17.0 diff --git a/go.sum b/go.sum index 06e36889..f026f47f 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.mau.fi/util v0.9.1 h1:A+XKHRsjKkFi2qOm4RriR1HqY2hoOXNS3WFHaC89r2Y= go.mau.fi/util v0.9.1/go.mod h1:M0bM9SyaOWJniaHs9hxEzz91r5ql6gYq6o1q5O1SsjQ= 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-20250919124702-c8bdfd36d05e h1:+o9+K5Qyo/490H4NyPVA0Es+aEzmmjaK37/437hV2LM= -go.mau.fi/whatsmeow v0.0.0-20250919124702-c8bdfd36d05e/go.mod h1:dvltpCF0rOHbbur25DHbQ3Ovi747z2Pm11S2M7p1T74= +go.mau.fi/whatsmeow v0.0.0-20250929162548-7c04e9b206b1 h1:JYsRQj8OiqZT6opjhtwUsndTDsHwDtRKaW3AERkGK7E= +go.mau.fi/whatsmeow v0.0.0-20250929162548-7c04e9b206b1/go.mod h1:dvltpCF0rOHbbur25DHbQ3Ovi747z2Pm11S2M7p1T74= 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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index e6b100e0..b7907015 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -27,6 +27,7 @@ import ( "github.com/rs/zerolog" "go.mau.fi/util/exerrors" "go.mau.fi/util/ptr" + "go.mau.fi/whatsmeow/proto/waAICommon" "go.mau.fi/whatsmeow/proto/waE2E" "go.mau.fi/whatsmeow/types" "google.golang.org/protobuf/proto" @@ -264,7 +265,7 @@ func (mc *MessageConverter) convertRichResponseMessage(ctx context.Context, msg var body strings.Builder for i, submsg := range msg.GetSubmessages() { - if submsg.GetMessageType() == waE2E.AIRichResponseMessage_AI_RICH_RESPONSE_TEXT { + if submsg.GetMessageType() == waAICommon.AIRichResponseSubMessageType_AI_RICH_RESPONSE_TEXT { if i > 0 { body.WriteString("\n") } From 3541409ae63c9efab13410ac41ef4d4747bb0031 Mon Sep 17 00:00:00 2001 From: fru-9D Date: Mon, 29 Sep 2025 21:31:51 +0500 Subject: [PATCH 166/486] msgconv/from-matrix: fix filename of documents without caption (#840) --- pkg/msgconv/from-matrix.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 7fde6a1f..559f3bc6 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -255,9 +255,14 @@ func (mc *MessageConverter) constructMediaMessage( }, } case event.MsgFile: + fileName := content.FileName + if fileName == "" { + fileName = content.Body + } + msg := &waE2E.Message{ DocumentMessage: &waE2E.DocumentMessage{ - FileName: proto.String(content.FileName), + FileName: proto.String(fileName), Caption: proto.String(caption), JPEGThumbnail: thumbnail, From b34c75374d667a27666921677c8c31d0f1e25168 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 1 Oct 2025 00:55:49 +0300 Subject: [PATCH 167/486] 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 f28ee2ee..e8d87fc5 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.1 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250929162548-7c04e9b206b1 + go.mau.fi/whatsmeow v0.0.0-20250930215512-38f9aaa3ba7c golang.org/x/image v0.31.0 golang.org/x/net v0.44.0 golang.org/x/sync v0.17.0 diff --git a/go.sum b/go.sum index f026f47f..c98c5bf1 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.mau.fi/util v0.9.1 h1:A+XKHRsjKkFi2qOm4RriR1HqY2hoOXNS3WFHaC89r2Y= go.mau.fi/util v0.9.1/go.mod h1:M0bM9SyaOWJniaHs9hxEzz91r5ql6gYq6o1q5O1SsjQ= 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-20250929162548-7c04e9b206b1 h1:JYsRQj8OiqZT6opjhtwUsndTDsHwDtRKaW3AERkGK7E= -go.mau.fi/whatsmeow v0.0.0-20250929162548-7c04e9b206b1/go.mod h1:dvltpCF0rOHbbur25DHbQ3Ovi747z2Pm11S2M7p1T74= +go.mau.fi/whatsmeow v0.0.0-20250930215512-38f9aaa3ba7c h1:mlDr3/zLUCf8aylmGwds0mnAb4SNYimpb43ylQQlY3Q= +go.mau.fi/whatsmeow v0.0.0-20250930215512-38f9aaa3ba7c/go.mod h1:dvltpCF0rOHbbur25DHbQ3Ovi747z2Pm11S2M7p1T74= 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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= From b4a929b7aa11a9bf092491ebacaf2faf12fcb135 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 1 Oct 2025 14:59:46 +0300 Subject: [PATCH 168/486] chatinfo: flag group resyncs as excluded from timeline --- go.mod | 4 ++-- go.sum | 8 ++++---- pkg/connector/chatinfo.go | 3 ++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index e8d87fc5..3803e49a 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.25.1 require ( github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.9.1 + go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10 go.mau.fi/webp v0.2.0 go.mau.fi/whatsmeow v0.0.0-20250930215512-38f9aaa3ba7c golang.org/x/image v0.31.0 @@ -15,7 +15,7 @@ require ( golang.org/x/sync v0.17.0 google.golang.org/protobuf v1.36.9 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.2-0.20250924172949-cf29b07f32ce + maunium.net/go/mautrix v0.25.2-0.20251001115535-dd778ae0cdaf ) require ( diff --git a/go.sum b/go.sum index c98c5bf1..1ba53fd9 100644 --- a/go.sum +++ b/go.sum @@ -77,8 +77,8 @@ github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.0 h1:oRXj3OHhEJq51BFEM8/50UZblmWiTYH93hsNTPcbk90= go.mau.fi/libsignal v0.2.0/go.mod h1:tvjoDsMejgT38CXTXwqaYu8itBiY8O2Mb6biWvZBb9k= -go.mau.fi/util v0.9.1 h1:A+XKHRsjKkFi2qOm4RriR1HqY2hoOXNS3WFHaC89r2Y= -go.mau.fi/util v0.9.1/go.mod h1:M0bM9SyaOWJniaHs9hxEzz91r5ql6gYq6o1q5O1SsjQ= +go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10 h1:EvX/di02gOriKN0xGDJuQ5mgiNdAF4LJc8moffI7Svo= +go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10/go.mod h1:M0bM9SyaOWJniaHs9hxEzz91r5ql6gYq6o1q5O1SsjQ= 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-20250930215512-38f9aaa3ba7c h1:mlDr3/zLUCf8aylmGwds0mnAb4SNYimpb43ylQQlY3Q= @@ -113,5 +113,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.25.2-0.20250924172949-cf29b07f32ce h1:sRBScG2xa66ERjgrX+7D//HorAhmNHwxB2Kzltg+aUg= -maunium.net/go/mautrix v0.25.2-0.20250924172949-cf29b07f32ce/go.mod h1:iSueLJ/2fBaNrsTObGqi1j0cl/loxrtAjmjay1scYD8= +maunium.net/go/mautrix v0.25.2-0.20251001115535-dd778ae0cdaf h1:prmIYgiziW4A8H2v/TliQ7fis8uTWblabxyPIeLFlNg= +maunium.net/go/mautrix v0.25.2-0.20251001115535-dd778ae0cdaf/go.mod h1:eWXuX2UAGye4AU7i/8Fv2L2Nh7L9kZtuv3R0O0n1KaM= diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 5a8bfcf8..8d47564b 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -275,6 +275,7 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn }, }, }, + ExcludeChangesFromTimeline: true, Disappear: &database.DisappearingSetting{ Type: event.DisappearingTypeAfterSend, Timer: time.Duration(info.DisappearingTimer) * time.Second, @@ -460,7 +461,7 @@ func (wa *WhatsAppClient) makePortalAvatarFetcher(avatarID string, sender types. return false } //lint:ignore SA1019 TODO invent a cleaner way to fetch avatar metadata before updating? - return portal.Internal().UpdateAvatar(ctx, wrappedAvatar, senderIntent, ts) + return portal.Internal().UpdateAvatar(ctx, wrappedAvatar, senderIntent, ts, false) } } From b034552d6f2f828aa03e1599d2998b55d9d50518 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 2 Oct 2025 15:14:19 +0300 Subject: [PATCH 169/486] handlematrix: implement account data interfaces --- go.mod | 2 +- go.sum | 4 +-- pkg/connector/client.go | 2 +- pkg/connector/handlematrix.go | 59 +++++++++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 3803e49a..742cc31e 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20250930215512-38f9aaa3ba7c + go.mau.fi/whatsmeow v0.0.0-20251002120845-b3cd0a5002d4 golang.org/x/image v0.31.0 golang.org/x/net v0.44.0 golang.org/x/sync v0.17.0 diff --git a/go.sum b/go.sum index 1ba53fd9..55c5c0bd 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10 h1:EvX/di02gOriKN0xGDJuQ5mgi go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10/go.mod h1:M0bM9SyaOWJniaHs9hxEzz91r5ql6gYq6o1q5O1SsjQ= 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-20250930215512-38f9aaa3ba7c h1:mlDr3/zLUCf8aylmGwds0mnAb4SNYimpb43ylQQlY3Q= -go.mau.fi/whatsmeow v0.0.0-20250930215512-38f9aaa3ba7c/go.mod h1:dvltpCF0rOHbbur25DHbQ3Ovi747z2Pm11S2M7p1T74= +go.mau.fi/whatsmeow v0.0.0-20251002120845-b3cd0a5002d4 h1:uysENrRsp3Ko6LjrOIsNnCT2l3lTibrUkp+CCx1IxG4= +go.mau.fi/whatsmeow v0.0.0-20251002120845-b3cd0a5002d4/go.mod h1:dvltpCF0rOHbbur25DHbQ3Ovi747z2Pm11S2M7p1T74= 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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= diff --git a/pkg/connector/client.go b/pkg/connector/client.go index dc160ba7..087741f5 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -197,7 +197,7 @@ func (wa *WhatsAppClient) Connect(ctx context.Context) { zerolog.Ctx(ctx).Err(err).Msg("Failed to update proxy") } wa.startLoops() - wa.Client.BackgroundEventCtx = wa.Main.Bridge.BackgroundCtx + wa.Client.BackgroundEventCtx = wa.UserLogin.Log.WithContext(wa.Main.Bridge.BackgroundCtx) if err := wa.Client.Connect(); err != nil { zerolog.Ctx(ctx).Err(err).Msg("Failed to connect to WhatsApp") state := status.BridgeState{ diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index c12ca98d..31720f40 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -15,6 +15,8 @@ import ( "go.mau.fi/util/ptr" "go.mau.fi/util/variationselector" "go.mau.fi/whatsmeow" + "go.mau.fi/whatsmeow/appstate" + "go.mau.fi/whatsmeow/proto/waCommon" "go.mau.fi/whatsmeow/proto/waE2E" "go.mau.fi/whatsmeow/types" "golang.org/x/image/draw" @@ -40,6 +42,9 @@ var ( _ bridgev2.RoomNameHandlingNetworkAPI = (*WhatsAppClient)(nil) _ bridgev2.RoomTopicHandlingNetworkAPI = (*WhatsAppClient)(nil) _ bridgev2.RoomAvatarHandlingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.MuteHandlingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.TagHandlingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.MarkedUnreadHandlingNetworkAPI = (*WhatsAppClient)(nil) ) func (wa *WhatsAppClient) HandleMatrixPollStart(ctx context.Context, msg *bridgev2.MatrixPollStart) (*bridgev2.MatrixMessageResponse, error) { @@ -566,3 +571,57 @@ func convertRoomAvatar(data []byte) ([]byte, error) { } return buf.Bytes(), nil } + +func (wa *WhatsAppClient) HandleMute(ctx context.Context, msg *bridgev2.MatrixMute) error { + chatJID, err := waid.ParsePortalID(msg.Portal.ID) + if err != nil { + return err + } + mutedUntil := msg.Content.GetMutedUntilTime() + muted := mutedUntil.After(time.Now()) + muteTS := ptr.Ptr(mutedUntil.UnixMilli()) + if !muted || mutedUntil == event.MutedForever { + muteTS = nil + } + return wa.Client.SendAppState(ctx, appstate.BuildMuteAbs(chatJID, muted, muteTS)) +} + +func (wa *WhatsAppClient) HandleRoomTag(ctx context.Context, msg *bridgev2.MatrixRoomTag) error { + chatJID, err := waid.ParsePortalID(msg.Portal.ID) + if err != nil { + return err + } + _, isFavorite := msg.Content.Tags[event.RoomTagFavourite] + return wa.Client.SendAppState(ctx, appstate.BuildPin(chatJID, isFavorite)) +} + +func (wa *WhatsAppClient) HandleMarkedUnread(ctx context.Context, msg *bridgev2.MatrixMarkedUnread) error { + chatJID, err := waid.ParsePortalID(msg.Portal.ID) + if err != nil { + return err + } + msgs, err := wa.Main.Bridge.DB.Message.GetLastNInPortal(ctx, msg.Portal.PortalKey, 1) + if err != nil { + return fmt.Errorf("failed to get last message in portal: %w", err) + } + var lastTS time.Time + var lastKey *waCommon.MessageKey + if len(msgs) == 1 { + 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() + var participant *string + if chatJID.Server == types.GroupServer { + participant = ptr.Ptr(parsed.Sender.String()) + } + lastKey = &waCommon.MessageKey{ + RemoteJID: ptr.Ptr(chatJID.String()), + FromMe: &fromMe, + ID: &parsed.ID, + Participant: participant, + } + } + } + return wa.Client.SendAppState(ctx, appstate.BuildMarkChatAsRead(chatJID, msg.Content.Unread, lastTS, lastKey)) +} From aab115344974457119b361284b4852bc2165c974 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 3 Oct 2025 15:05:02 +0300 Subject: [PATCH 170/486] dependencies: update whatsmeow and set PostgresArrayWrapper properly --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/connector.go | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 742cc31e..2119bc62 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251002120845-b3cd0a5002d4 + go.mau.fi/whatsmeow v0.0.0-20251003120353-0091f66a98cc golang.org/x/image v0.31.0 golang.org/x/net v0.44.0 golang.org/x/sync v0.17.0 diff --git a/go.sum b/go.sum index 55c5c0bd..2edcff5d 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10 h1:EvX/di02gOriKN0xGDJuQ5mgi go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10/go.mod h1:M0bM9SyaOWJniaHs9hxEzz91r5ql6gYq6o1q5O1SsjQ= 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-20251002120845-b3cd0a5002d4 h1:uysENrRsp3Ko6LjrOIsNnCT2l3lTibrUkp+CCx1IxG4= -go.mau.fi/whatsmeow v0.0.0-20251002120845-b3cd0a5002d4/go.mod h1:dvltpCF0rOHbbur25DHbQ3Ovi747z2Pm11S2M7p1T74= +go.mau.fi/whatsmeow v0.0.0-20251003120353-0091f66a98cc h1:ebVSx8jdPkDiQM/1V2/RA7z/mrIx2PsTjxiBgE51p4I= +go.mau.fi/whatsmeow v0.0.0-20251003120353-0091f66a98cc/go.mod h1:dvltpCF0rOHbbur25DHbQ3Ovi747z2Pm11S2M7p1T74= 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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 1e41e806..61a961de 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -24,6 +24,7 @@ import ( "sync" "sync/atomic" + "github.com/lib/pq" "go.mau.fi/util/dbutil" "go.mau.fi/util/random" "go.mau.fi/whatsmeow" @@ -58,6 +59,10 @@ type WhatsAppConnector struct { stopMediaEditCacheLoop atomic.Pointer[context.CancelFunc] } +func init() { + sqlstore.PostgresArrayWrapper = pq.Array +} + var ( _ bridgev2.NetworkConnector = (*WhatsAppConnector)(nil) _ bridgev2.MaxFileSizeingNetwork = (*WhatsAppConnector)(nil) From 72dae66a472b1232e2194a244434108c0f415221 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 3 Oct 2025 18:49:58 +0300 Subject: [PATCH 171/486] 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 2119bc62..1155162a 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251003120353-0091f66a98cc + go.mau.fi/whatsmeow v0.0.0-20251003154939-d562355c4d82 golang.org/x/image v0.31.0 golang.org/x/net v0.44.0 golang.org/x/sync v0.17.0 diff --git a/go.sum b/go.sum index 2edcff5d..236c07f1 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10 h1:EvX/di02gOriKN0xGDJuQ5mgi go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10/go.mod h1:M0bM9SyaOWJniaHs9hxEzz91r5ql6gYq6o1q5O1SsjQ= 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-20251003120353-0091f66a98cc h1:ebVSx8jdPkDiQM/1V2/RA7z/mrIx2PsTjxiBgE51p4I= -go.mau.fi/whatsmeow v0.0.0-20251003120353-0091f66a98cc/go.mod h1:dvltpCF0rOHbbur25DHbQ3Ovi747z2Pm11S2M7p1T74= +go.mau.fi/whatsmeow v0.0.0-20251003154939-d562355c4d82 h1:sLwBH2Q70EoOt8uCZb1gCKGwp2hdZG+ObqpXHWJ4sNk= +go.mau.fi/whatsmeow v0.0.0-20251003154939-d562355c4d82/go.mod h1:dvltpCF0rOHbbur25DHbQ3Ovi747z2Pm11S2M7p1T74= 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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= From 03f04fe893bf9fbcb0be0ff13efd54928ee84cbc Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 4 Oct 2025 16:00:23 +0300 Subject: [PATCH 172/486] 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 1155162a..2c0b0d52 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251003154939-d562355c4d82 + go.mau.fi/whatsmeow v0.0.0-20251004125807-565fd64f96bd golang.org/x/image v0.31.0 golang.org/x/net v0.44.0 golang.org/x/sync v0.17.0 diff --git a/go.sum b/go.sum index 236c07f1..031348bf 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10 h1:EvX/di02gOriKN0xGDJuQ5mgi go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10/go.mod h1:M0bM9SyaOWJniaHs9hxEzz91r5ql6gYq6o1q5O1SsjQ= 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-20251003154939-d562355c4d82 h1:sLwBH2Q70EoOt8uCZb1gCKGwp2hdZG+ObqpXHWJ4sNk= -go.mau.fi/whatsmeow v0.0.0-20251003154939-d562355c4d82/go.mod h1:dvltpCF0rOHbbur25DHbQ3Ovi747z2Pm11S2M7p1T74= +go.mau.fi/whatsmeow v0.0.0-20251004125807-565fd64f96bd h1:bIvudPiVur5JhbOunTZ/ozbsiqIZJQNLBYsYY8ek46M= +go.mau.fi/whatsmeow v0.0.0-20251004125807-565fd64f96bd/go.mod h1:dvltpCF0rOHbbur25DHbQ3Ovi747z2Pm11S2M7p1T74= 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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= From a4fb0844f503f8a3d2fb07bfeb52db1664652288 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 4 Oct 2025 20:12:05 +0300 Subject: [PATCH 173/486] client: enable synchronous acks and decrypted event buffer for everyone --- pkg/connector/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 087741f5..1787a0a6 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -73,9 +73,9 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. log := w.UserLogin.Log.With().Str("component", "whatsmeow").Logger() w.Client = whatsmeow.NewClient(w.Device, waLog.Zerolog(log)) w.Client.AddEventHandlerWithSuccessStatus(w.handleWAEvent) + w.Client.EnableDecryptedEventBuffer = true + w.Client.SynchronousAck = true if bridgev2.PortalEventBuffer == 0 { - w.Client.SynchronousAck = true - w.Client.EnableDecryptedEventBuffer = true w.Client.ManualHistorySyncDownload = true } w.Client.SendReportingTokens = true From 562e2422dd3044564cdcc86a989e78ad911f793c Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 4 Oct 2025 20:33:19 +0300 Subject: [PATCH 174/486] dependencies: update whatsmeow --- go.mod | 6 +++--- go.sum | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 2c0b0d52..6eb2e5a6 100644 --- a/go.mod +++ b/go.mod @@ -9,11 +9,11 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251004125807-565fd64f96bd + go.mau.fi/whatsmeow v0.0.0-20251004173248-359e39387ad3 golang.org/x/image v0.31.0 golang.org/x/net v0.44.0 golang.org/x/sync v0.17.0 - google.golang.org/protobuf v1.36.9 + google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 maunium.net/go/mautrix v0.25.2-0.20251001115535-dd778ae0cdaf ) @@ -40,7 +40,7 @@ require ( github.com/tidwall/sjson v1.2.5 // indirect github.com/vektah/gqlparser/v2 v2.5.27 // indirect github.com/yuin/goldmark v1.7.13 // indirect - go.mau.fi/libsignal v0.2.0 // indirect + go.mau.fi/libsignal v0.2.1-0.20251004173110-6e0a3f2435ed // indirect go.mau.fi/zeroconfig v0.2.0 // indirect golang.org/x/crypto v0.42.0 // indirect golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect diff --git a/go.sum b/go.sum index 031348bf..2d1a8b93 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= @@ -75,14 +75,14 @@ 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.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= -go.mau.fi/libsignal v0.2.0 h1:oRXj3OHhEJq51BFEM8/50UZblmWiTYH93hsNTPcbk90= -go.mau.fi/libsignal v0.2.0/go.mod h1:tvjoDsMejgT38CXTXwqaYu8itBiY8O2Mb6biWvZBb9k= +go.mau.fi/libsignal v0.2.1-0.20251004173110-6e0a3f2435ed h1:f44xyYgZBCEic3OiKY4eYfDA2b3kBB/LlgVha4NpjRs= +go.mau.fi/libsignal v0.2.1-0.20251004173110-6e0a3f2435ed/go.mod h1:iVvjrHyfQqWajOUaMEsIfo3IqgVMrhWcPiiEzk7NgoU= go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10 h1:EvX/di02gOriKN0xGDJuQ5mgiNdAF4LJc8moffI7Svo= go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10/go.mod h1:M0bM9SyaOWJniaHs9hxEzz91r5ql6gYq6o1q5O1SsjQ= 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-20251004125807-565fd64f96bd h1:bIvudPiVur5JhbOunTZ/ozbsiqIZJQNLBYsYY8ek46M= -go.mau.fi/whatsmeow v0.0.0-20251004125807-565fd64f96bd/go.mod h1:dvltpCF0rOHbbur25DHbQ3Ovi747z2Pm11S2M7p1T74= +go.mau.fi/whatsmeow v0.0.0-20251004173248-359e39387ad3 h1:dgZ3Pz/AIsx3Yc70wnD9WsgtkD/FrokTY7ACCI7JhO0= +go.mau.fi/whatsmeow v0.0.0-20251004173248-359e39387ad3/go.mod h1:Erq9GGhe5DuIGL9i2gp2LH4nEYkBB+lsgdu3i4KcvPU= 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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= @@ -102,8 +102,8 @@ golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= -google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/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= From 425556d0fa511bd2c898469f55de10c98cd912f5 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 4 Oct 2025 20:45:38 +0300 Subject: [PATCH 175/486] client: disable decrypted event buffer --- pkg/connector/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 1787a0a6..6196e169 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -73,9 +73,9 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. log := w.UserLogin.Log.With().Str("component", "whatsmeow").Logger() w.Client = whatsmeow.NewClient(w.Device, waLog.Zerolog(log)) w.Client.AddEventHandlerWithSuccessStatus(w.handleWAEvent) - w.Client.EnableDecryptedEventBuffer = true w.Client.SynchronousAck = true if bridgev2.PortalEventBuffer == 0 { + w.Client.EnableDecryptedEventBuffer = true w.Client.ManualHistorySyncDownload = true } w.Client.SendReportingTokens = true From 6fbd3455247a37fb586fa8d14839c737955d1848 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 5 Oct 2025 11:32:04 +0300 Subject: [PATCH 176/486] 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 6eb2e5a6..8e7cd874 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251004173248-359e39387ad3 + go.mau.fi/whatsmeow v0.0.0-20251005083110-4fe97da162dc golang.org/x/image v0.31.0 golang.org/x/net v0.44.0 golang.org/x/sync v0.17.0 diff --git a/go.sum b/go.sum index 2d1a8b93..0ae65fc7 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10 h1:EvX/di02gOriKN0xGDJuQ5mgi go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10/go.mod h1:M0bM9SyaOWJniaHs9hxEzz91r5ql6gYq6o1q5O1SsjQ= 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-20251004173248-359e39387ad3 h1:dgZ3Pz/AIsx3Yc70wnD9WsgtkD/FrokTY7ACCI7JhO0= -go.mau.fi/whatsmeow v0.0.0-20251004173248-359e39387ad3/go.mod h1:Erq9GGhe5DuIGL9i2gp2LH4nEYkBB+lsgdu3i4KcvPU= +go.mau.fi/whatsmeow v0.0.0-20251005083110-4fe97da162dc h1:O19PRYoEZJyC7BgUM9PSillFLAkABiBK7AMORNwwK3Q= +go.mau.fi/whatsmeow v0.0.0-20251005083110-4fe97da162dc/go.mod h1:Erq9GGhe5DuIGL9i2gp2LH4nEYkBB+lsgdu3i4KcvPU= 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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= From f7fa9fe142fdacb598e941940bccabcfb62ef661 Mon Sep 17 00:00:00 2001 From: Kishan Bagaria <1093313+KishanBagaria@users.noreply.github.com> Date: Sun, 5 Oct 2025 20:58:58 +0530 Subject: [PATCH 177/486] msgconv: fix view once placeholder copy when you send instead of receive (#848) Co-authored-by: Rajeh Taher --- pkg/connector/events.go | 6 +++++- pkg/msgconv/wa-media.go | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 46545fd0..f371cc0a 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -340,9 +340,13 @@ func (evt *WAUndecryptableMessage) ConvertMessage(ctx context.Context, portal *b } content := &undecryptableMessageContent if evt.Type == events.UnavailableTypeViewOnce { + body := "You received a view once message. For added privacy, you can only open it on the WhatsApp app." + if evt.Info.IsFromMe { + body = "You sent a view once message from another device." + } content = &event.MessageEventContent{ MsgType: event.MsgNotice, - Body: "You received a view once message. For added privacy, you can only open it on the WhatsApp app.", + Body: body, } } // TODO thread root for comments diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index d95d8f9c..647e98f5 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -54,11 +54,15 @@ func (mc *MessageConverter) convertMediaMessage( cachedPart *bridgev2.ConvertedMessagePart, ) (part *bridgev2.ConvertedMessagePart, contextInfo *waE2E.ContextInfo) { if mc.DisableViewOnce && isViewOnce { + body := "You received a view once message. For added privacy, you can only open it on the WhatsApp app." + if messageInfo.IsFromMe { + body = "You sent a view once message from another device." + } return &bridgev2.ConvertedMessagePart{ Type: event.EventMessage, Content: &event.MessageEventContent{ MsgType: event.MsgNotice, - Body: fmt.Sprintf("You received a view once %s. For added privacy, you can only open it on the WhatsApp app.", typeName), + Body: body, }, }, nil } From 2c38e786a27ae55583b28e2cf7fb48b1baf1a264 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 6 Oct 2025 16:52:00 +0300 Subject: [PATCH 178/486] changelog: update --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d32234d3..9dc6c9e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# v25.10 (unreleased) + +* Switched to calendar versioning. +* Added support for bridging event edits. +* Fixed backfill creating incorrect disappearing timer change notices. +* Fixed previous messages not being marked as read when sending a new message. +* Fixed incoming call notices with LID addressing going into different DM room. + # v0.12.5 (2025-09-16) * Removed legacy provisioning API and database legacy migration. From 1385a4f93f9e9e4540adabd0246c497d41857428 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 10 Oct 2025 01:38:58 +0300 Subject: [PATCH 179/486] connector: fix hardcoded_wa_version in log --- pkg/connector/connector.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 61a961de..4e50ca51 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -159,6 +159,8 @@ func (wa *WhatsAppConnector) Stop() { const kvWAVersion = "whatsapp_web_version" +var hardcodedWAVersion = store.GetWAVersion() + func (wa *WhatsAppConnector) onFirstBackgroundConnect() { verStr := wa.Bridge.DB.KV.Get(wa.Bridge.BackgroundCtx, kvWAVersion) if verStr == "" { @@ -171,7 +173,7 @@ func (wa *WhatsAppConnector) onFirstBackgroundConnect() { return } wa.Bridge.Log.Debug(). - Stringer("hardcoded_version", store.GetWAVersion()). + Stringer("hardcoded_version", hardcodedWAVersion). Stringer("cached_version", ver). Msg("Using cached WhatsApp web version number") store.SetWAVersion(ver) @@ -184,7 +186,7 @@ func (wa *WhatsAppConnector) onFirstClientConnect() { wa.Bridge.Log.Err(err).Msg("Failed to get latest WhatsApp web version number") } else { wa.Bridge.Log.Debug(). - Stringer("hardcoded_version", store.GetWAVersion()). + Stringer("hardcoded_version", hardcodedWAVersion). Stringer("latest_version", *ver). Msg("Got latest WhatsApp web version number") store.SetWAVersion(*ver) From dba863c38a1b0d415084c003c546c4774b27612c Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 10 Oct 2025 14:40:08 +0300 Subject: [PATCH 180/486] dependencies: update whatsmeow --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 8e7cd874..2169c871 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,9 @@ toolchain go1.25.1 require ( github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10 + go.mau.fi/util v0.9.2-0.20251005111801-c13b66219cee go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251005083110-4fe97da162dc + go.mau.fi/whatsmeow v0.0.0-20251010113933-5806d60827b0 golang.org/x/image v0.31.0 golang.org/x/net v0.44.0 golang.org/x/sync v0.17.0 diff --git a/go.sum b/go.sum index 0ae65fc7..1794a46d 100644 --- a/go.sum +++ b/go.sum @@ -77,12 +77,12 @@ github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.1-0.20251004173110-6e0a3f2435ed h1:f44xyYgZBCEic3OiKY4eYfDA2b3kBB/LlgVha4NpjRs= go.mau.fi/libsignal v0.2.1-0.20251004173110-6e0a3f2435ed/go.mod h1:iVvjrHyfQqWajOUaMEsIfo3IqgVMrhWcPiiEzk7NgoU= -go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10 h1:EvX/di02gOriKN0xGDJuQ5mgiNdAF4LJc8moffI7Svo= -go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10/go.mod h1:M0bM9SyaOWJniaHs9hxEzz91r5ql6gYq6o1q5O1SsjQ= +go.mau.fi/util v0.9.2-0.20251005111801-c13b66219cee h1:wk1XGP/E1UH3YBqXbb9dajWdO7e9cizgx5+NYkEXT2g= +go.mau.fi/util v0.9.2-0.20251005111801-c13b66219cee/go.mod h1:M0bM9SyaOWJniaHs9hxEzz91r5ql6gYq6o1q5O1SsjQ= 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-20251005083110-4fe97da162dc h1:O19PRYoEZJyC7BgUM9PSillFLAkABiBK7AMORNwwK3Q= -go.mau.fi/whatsmeow v0.0.0-20251005083110-4fe97da162dc/go.mod h1:Erq9GGhe5DuIGL9i2gp2LH4nEYkBB+lsgdu3i4KcvPU= +go.mau.fi/whatsmeow v0.0.0-20251010113933-5806d60827b0 h1:Q4gIHMOPglOvX8JQ7ewWhoHMhveew2pwnBzmepgJi1w= +go.mau.fi/whatsmeow v0.0.0-20251010113933-5806d60827b0/go.mod h1:CSdGU471Ss7bWunGomSe9RObY0MRxQDvxFH8i5Ndfk4= 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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= From 9802479e6b67c71d04113cb1721848f0d37c236e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 16 Oct 2025 12:05:32 +0200 Subject: [PATCH 181/486] Bump version to v25.10 --- CHANGELOG.md | 2 +- cmd/mautrix-whatsapp/main.go | 3 ++- go.mod | 22 ++++++++++---------- go.sum | 40 ++++++++++++++++++------------------ 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dc6c9e1..7e65fefa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# v25.10 (unreleased) +# v25.10 * Switched to calendar versioning. * Added support for bridging event edits. diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 068e67d8..a3e06654 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -18,7 +18,8 @@ var m = mxmain.BridgeMain{ Name: "mautrix-whatsapp", URL: "https://github.com/mautrix/whatsapp", Description: "A Matrix-WhatsApp puppeting bridge.", - Version: "0.12.5", + Version: "25.10", + SemCalVer: true, Connector: &connector.WhatsAppConnector{}, } diff --git a/go.mod b/go.mod index 2169c871..20a595ef 100644 --- a/go.mod +++ b/go.mod @@ -2,20 +2,20 @@ module go.mau.fi/mautrix-whatsapp go 1.24.0 -toolchain go1.25.1 +toolchain go1.25.3 require ( github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.9.2-0.20251005111801-c13b66219cee + go.mau.fi/util v0.9.2 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251010113933-5806d60827b0 - golang.org/x/image v0.31.0 - golang.org/x/net v0.44.0 + go.mau.fi/whatsmeow v0.0.0-20251016095441-02c50743e601 + golang.org/x/image v0.32.0 + golang.org/x/net v0.46.0 golang.org/x/sync v0.17.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.2-0.20251001115535-dd778ae0cdaf + maunium.net/go/mautrix v0.25.2 ) require ( @@ -40,12 +40,12 @@ require ( github.com/tidwall/sjson v1.2.5 // indirect github.com/vektah/gqlparser/v2 v2.5.27 // indirect github.com/yuin/goldmark v1.7.13 // indirect - go.mau.fi/libsignal v0.2.1-0.20251004173110-6e0a3f2435ed // indirect + go.mau.fi/libsignal v0.2.1 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect - golang.org/x/crypto v0.42.0 // indirect - golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.29.0 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.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 1794a46d..6d02a06a 100644 --- a/go.sum +++ b/go.sum @@ -75,33 +75,33 @@ 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.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= -go.mau.fi/libsignal v0.2.1-0.20251004173110-6e0a3f2435ed h1:f44xyYgZBCEic3OiKY4eYfDA2b3kBB/LlgVha4NpjRs= -go.mau.fi/libsignal v0.2.1-0.20251004173110-6e0a3f2435ed/go.mod h1:iVvjrHyfQqWajOUaMEsIfo3IqgVMrhWcPiiEzk7NgoU= -go.mau.fi/util v0.9.2-0.20251005111801-c13b66219cee h1:wk1XGP/E1UH3YBqXbb9dajWdO7e9cizgx5+NYkEXT2g= -go.mau.fi/util v0.9.2-0.20251005111801-c13b66219cee/go.mod h1:M0bM9SyaOWJniaHs9hxEzz91r5ql6gYq6o1q5O1SsjQ= +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.2 h1:+S4Z03iCsGqU2WY8X2gySFsFjaLlUHFRDVCYvVwynKM= +go.mau.fi/util v0.9.2/go.mod h1:055elBBCJSdhRsmub7ci9hXZPgGr1U6dYg44cSgRgoU= 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-20251010113933-5806d60827b0 h1:Q4gIHMOPglOvX8JQ7ewWhoHMhveew2pwnBzmepgJi1w= -go.mau.fi/whatsmeow v0.0.0-20251010113933-5806d60827b0/go.mod h1:CSdGU471Ss7bWunGomSe9RObY0MRxQDvxFH8i5Ndfk4= +go.mau.fi/whatsmeow v0.0.0-20251016095441-02c50743e601 h1:6tytDOaqizeBVG3OdEojIiJtZXY3DWMvFKNkjbxq4tI= +go.mau.fi/whatsmeow v0.0.0-20251016095441-02c50743e601/go.mod h1:VJq+D05Fe5EroZxs2StEYD/AsWJO2aQ7Niucz7lCvao= 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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= -golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= -golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= -golang.org/x/image v0.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA= -golang.org/x/image v0.31.0/go.mod h1:R9ec5Lcp96v9FTF+ajwaH3uGxPH4fKfHHAVbUILxghA= -golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= -golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b h1:18qgiDvlvH7kk8Ioa8Ov+K6xCi0GMvmGfGW0sgd/SYA= +golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= +golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ= +golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 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.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -113,5 +113,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.25.2-0.20251001115535-dd778ae0cdaf h1:prmIYgiziW4A8H2v/TliQ7fis8uTWblabxyPIeLFlNg= -maunium.net/go/mautrix v0.25.2-0.20251001115535-dd778ae0cdaf/go.mod h1:eWXuX2UAGye4AU7i/8Fv2L2Nh7L9kZtuv3R0O0n1KaM= +maunium.net/go/mautrix v0.25.2 h1:CUG23zp754yGOTMh9Q4mVSENS9FyweE/G+6ZsPDMCUU= +maunium.net/go/mautrix v0.25.2/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= From 91b2a32b3c38489ac16401f12d37c6e3ec1cf18c Mon Sep 17 00:00:00 2001 From: Conan Date: Fri, 17 Oct 2025 07:27:05 +0800 Subject: [PATCH 182/486] handlematrix: implement DeleteChatHandlingNetworkAPI (#851) --- pkg/connector/capabilities.go | 5 +++-- pkg/connector/handlematrix.go | 38 ++++++++++++++++++++++++++++------- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/pkg/connector/capabilities.go b/pkg/connector/capabilities.go index 095459fd..09307e24 100644 --- a/pkg/connector/capabilities.go +++ b/pkg/connector/capabilities.go @@ -50,7 +50,7 @@ func (wa *WhatsAppConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilit } func (wa *WhatsAppConnector) GetBridgeInfoVersion() (info, caps int) { - return 1, 4 + return 1, 5 } const WAMaxFileSize = 2000 * 1024 * 1024 @@ -65,7 +65,7 @@ func supportedIfFFmpeg() event.CapabilitySupportLevel { } func capID() string { - base := "fi.mau.whatsapp.capabilities.2025_08_25+1" + base := "fi.mau.whatsapp.capabilities.2025_10_07" if ffmpeg.Supported() { return base + "+ffmpeg" } @@ -176,6 +176,7 @@ var whatsappCaps = &event.RoomFeatures{ ReadReceipts: true, TypingNotifications: true, DisappearingTimer: waDisappearingCap, + DeleteChat: true, } var whatsappCAGCaps *event.RoomFeatures diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index 31720f40..fe77c37f 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -45,6 +45,7 @@ var ( _ bridgev2.MuteHandlingNetworkAPI = (*WhatsAppClient)(nil) _ bridgev2.TagHandlingNetworkAPI = (*WhatsAppClient)(nil) _ bridgev2.MarkedUnreadHandlingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.DeleteChatHandlingNetworkAPI = (*WhatsAppClient)(nil) ) func (wa *WhatsAppClient) HandleMatrixPollStart(ctx context.Context, msg *bridgev2.MatrixPollStart) (*bridgev2.MatrixMessageResponse, error) { @@ -595,14 +596,10 @@ func (wa *WhatsAppClient) HandleRoomTag(ctx context.Context, msg *bridgev2.Matri return wa.Client.SendAppState(ctx, appstate.BuildPin(chatJID, isFavorite)) } -func (wa *WhatsAppClient) HandleMarkedUnread(ctx context.Context, msg *bridgev2.MatrixMarkedUnread) error { - chatJID, err := waid.ParsePortalID(msg.Portal.ID) +func (wa *WhatsAppClient) getLastMessageInfo(ctx context.Context, chatJID types.JID, portalKey networkid.PortalKey) (time.Time, *waCommon.MessageKey, error) { + msgs, err := wa.Main.Bridge.DB.Message.GetLastNInPortal(ctx, portalKey, 1) if err != nil { - return err - } - msgs, err := wa.Main.Bridge.DB.Message.GetLastNInPortal(ctx, msg.Portal.PortalKey, 1) - if err != nil { - return fmt.Errorf("failed to get last message in portal: %w", err) + return time.Time{}, nil, fmt.Errorf("failed to get last message in portal: %w", err) } var lastTS time.Time var lastKey *waCommon.MessageKey @@ -623,5 +620,32 @@ func (wa *WhatsAppClient) HandleMarkedUnread(ctx context.Context, msg *bridgev2. } } } + return lastTS, lastKey, nil +} + +func (wa *WhatsAppClient) HandleMarkedUnread(ctx context.Context, msg *bridgev2.MatrixMarkedUnread) error { + chatJID, err := waid.ParsePortalID(msg.Portal.ID) + if err != nil { + return err + } + lastTS, lastKey, err := wa.getLastMessageInfo(ctx, chatJID, msg.Portal.PortalKey) + if err != nil { + return err + } return wa.Client.SendAppState(ctx, appstate.BuildMarkChatAsRead(chatJID, msg.Content.Unread, lastTS, lastKey)) } + +func (wa *WhatsAppClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridgev2.MatrixDeleteChat) error { + chatJID, err := waid.ParsePortalID(msg.Portal.ID) + if err != nil { + return err + } + lastTS, lastKey, err := wa.getLastMessageInfo(ctx, chatJID, msg.Portal.PortalKey) + if err != nil { + return err + } + if lastKey == nil { + return fmt.Errorf("failed to delete chat: no messages found") + } + return wa.Client.SendAppState(ctx, appstate.BuildDeleteChat(chatJID, lastTS, lastKey)) +} From d188d14c1e7542278744a54a661ef8958e5ef3a7 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 20 Oct 2025 22:30:04 +0300 Subject: [PATCH 183/486] all: remove unsafe direct store access --- cmd/mautrix-whatsapp/legacyprovision.go | 2 +- pkg/connector/handlewhatsapp.go | 4 ++-- pkg/connector/id.go | 2 +- pkg/connector/startchat.go | 2 +- pkg/connector/userinfo.go | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/mautrix-whatsapp/legacyprovision.go b/cmd/mautrix-whatsapp/legacyprovision.go index 2a8479d8..ef4d9845 100644 --- a/cmd/mautrix-whatsapp/legacyprovision.go +++ b/cmd/mautrix-whatsapp/legacyprovision.go @@ -40,7 +40,7 @@ func legacyProvContacts(w http.ResponseWriter, r *http.Request) { if userLogin == nil { return } - if contacts, err := userLogin.Client.(*connector.WhatsAppClient).Device.Contacts.GetAllContacts(r.Context()); err != nil { + 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", diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 752ba33b..2eb1284c 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -264,7 +264,7 @@ func (wa *WhatsAppClient) rerouteWAMessage(ctx context.Context, info *types.Mess Msg("Forced LID DM sender to phone number in own message sent from another device") info.Chat = info.RecipientAlt.ToNonAD() } else if info.Sender.Server == types.BotServer && info.Chat.Server == types.HiddenUserServer { - chatPN, err := wa.Device.LIDs.GetPNForLID(ctx, info.Chat) + chatPN, err := wa.GetStore().LIDs.GetPNForLID(ctx, info.Chat) if err != nil { wa.UserLogin.Log.Err(err). Str("message_id", info.ID). @@ -423,7 +423,7 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei if !evt.MessageSender.IsEmpty() { messageSender = evt.MessageSender } else if evt.Chat.Server == types.GroupServer && evt.Sender.Server == types.HiddenUserServer { - lid := wa.Device.GetLID() + lid := wa.GetStore().GetLID() if !lid.IsEmpty() { messageSender = lid } diff --git a/pkg/connector/id.go b/pkg/connector/id.go index e956e590..5028b7b0 100644 --- a/pkg/connector/id.go +++ b/pkg/connector/id.go @@ -39,7 +39,7 @@ func (wa *WhatsAppClient) makeEventSender(ctx context.Context, id types.JID) bri } else if id.Server == types.DefaultUserServer { senderLoginJID = id } else if id.Server == types.HiddenUserServer { - pn, err := wa.Device.LIDs.GetPNForLID(ctx, id) + pn, err := wa.GetStore().LIDs.GetPNForLID(ctx, id) if err != nil { zerolog.Ctx(ctx).Err(err). Stringer("lid", id). diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index 7e03fc0b..a4db5de5 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -109,7 +109,7 @@ 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.Device.LIDs.GetPNForLID(ctx, jid) + pn, err := wa.GetStore().LIDs.GetPNForLID(ctx, jid) if err != nil { return jid, fmt.Errorf("failed to get phone number for lid: %w", err) } else if pn.IsEmpty() { diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index c9e2e9fb..150a97d6 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -378,9 +378,9 @@ func (wa *WhatsAppClient) syncAltGhostWithInfo(ctx context.Context, jid types.JI var altJID types.JID var err error if jid.Server == types.HiddenUserServer { - altJID, err = wa.Device.LIDs.GetPNForLID(ctx, jid) + altJID, err = wa.GetStore().LIDs.GetPNForLID(ctx, jid) } else if jid.Server == types.DefaultUserServer { - altJID, err = wa.Device.LIDs.GetLIDForPN(ctx, jid) + altJID, err = wa.GetStore().LIDs.GetLIDForPN(ctx, jid) } if err != nil { log.Warn().Err(err). From 4b388237aa8af2f2b0ebe2e8b0119153b9bbe5f7 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 21 Oct 2025 14:18:41 +0300 Subject: [PATCH 184/486] docker: use gitlab dependency proxy for CI builds --- Dockerfile.ci | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile.ci b/Dockerfile.ci index 9b7189dd..16ac07d2 100644 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -1,4 +1,6 @@ -FROM alpine:3.22 +ARG DOCKER_HUB="docker.io" + +FROM ${DOCKER_HUB}/alpine:3.22 ENV UID=1337 \ GID=1337 From 4171a95b014b2c2f512ddfb37f4aa43398ef70bd Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 21 Oct 2025 17:16:54 +0300 Subject: [PATCH 185/486] startchat: exclude non-contacts from GetContactList response --- pkg/connector/startchat.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index a4db5de5..d7c61fc4 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -164,11 +164,11 @@ func (wa *WhatsAppClient) ResolveIdentifier(ctx context.Context, identifier stri } func (wa *WhatsAppClient) GetContactList(ctx context.Context) ([]*bridgev2.ResolveIdentifierResponse, error) { - return wa.getContactList(ctx, "") + return wa.getContactList(ctx, "", true) } func (wa *WhatsAppClient) SearchUsers(ctx context.Context, query string) ([]*bridgev2.ResolveIdentifierResponse, error) { - return wa.getContactList(ctx, strings.ToLower(query)) + return wa.getContactList(ctx, strings.ToLower(query), false) } func matchesQuery(str string, query string) bool { @@ -178,7 +178,7 @@ func matchesQuery(str string, query string) bool { return strings.Contains(strings.ToLower(str), query) } -func (wa *WhatsAppClient) getContactList(ctx context.Context, filter string) ([]*bridgev2.ResolveIdentifierResponse, error) { +func (wa *WhatsAppClient) getContactList(ctx context.Context, filter string, onlyContacts bool) ([]*bridgev2.ResolveIdentifierResponse, error) { if !wa.IsLoggedIn() { return nil, mautrix.MForbidden.WithMessage("You must be logged in to list contacts") } @@ -188,6 +188,9 @@ func (wa *WhatsAppClient) getContactList(ctx context.Context, filter string) ([] } resp := make([]*bridgev2.ResolveIdentifierResponse, 0, len(contacts)) for jid, contactInfo := range contacts { + if onlyContacts && contactInfo.FirstName == "" { + continue + } if !matchesQuery(contactInfo.PushName, filter) && !matchesQuery(contactInfo.FullName, filter) && !matchesQuery(jid.User, filter) { continue } From 98b7e08f4ed1e9b7868242682151811c0dd8efd1 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 21 Oct 2025 17:42:25 +0300 Subject: [PATCH 186/486] startchat,msgconv: return group create errors and allow sending invites in DMs --- go.mod | 2 +- go.sum | 4 +-- pkg/connector/startchat.go | 35 +++++++++++++++++++++++ pkg/msgconv/from-matrix.go | 57 ++++++++++++++++++++++++++++++++++++-- pkg/msgconv/wa-misc.go | 10 ++++--- pkg/waid/dbmeta.go | 3 ++ 6 files changed, 101 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 20a595ef..245626a9 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( golang.org/x/sync v0.17.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.2 + maunium.net/go/mautrix v0.25.3-0.20251021144018-1aacf6e987b1 ) require ( diff --git a/go.sum b/go.sum index 6d02a06a..50995ed9 100644 --- a/go.sum +++ b/go.sum @@ -113,5 +113,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.25.2 h1:CUG23zp754yGOTMh9Q4mVSENS9FyweE/G+6ZsPDMCUU= -maunium.net/go/mautrix v0.25.2/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= +maunium.net/go/mautrix v0.25.3-0.20251021144018-1aacf6e987b1 h1:jE/W7DZnnD3wWVinyiUkuN8lMBM8AcUiJX5V2fClj3Y= +maunium.net/go/mautrix v0.25.3-0.20251021144018-1aacf6e987b1/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index d7c61fc4..ad0dd05e 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -35,6 +35,7 @@ import ( "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" + "go.mau.fi/mautrix-whatsapp/pkg/msgconv" "go.mau.fi/mautrix-whatsapp/pkg/waid" ) @@ -249,6 +250,38 @@ func (wa *WhatsAppClient) CreateGroup(ctx context.Context, params *bridgev2.Grou if err != nil { return nil, fmt.Errorf("failed to create group: %w", err) } + failedParticipants := make(map[networkid.UserID]bridgev2.CreateChatFailedParticipant) + filteredParticipants := resp.Participants[:0] + for _, pcp := range resp.Participants { + if pcp.Error != 0 { + var inviteContent *event.Content + if pcp.AddRequest != nil { + inviteContent = &event.Content{ + Raw: map[string]any{ + msgconv.GroupInviteMetaField: &waid.GroupInviteMeta{ + JID: resp.JID, + Code: pcp.AddRequest.Code, + Expiration: pcp.AddRequest.Expiration.Unix(), + Inviter: wa.JID.ToNonAD(), + GroupName: resp.Name, + IsParentGroup: resp.IsParent, + }, + }, + Parsed: &event.MessageEventContent{ + Body: "Invitation to join my WhatsApp group", + MsgType: event.MsgText, + }, + } + } + failedParticipants[waid.MakeUserID(pcp.JID)] = bridgev2.CreateChatFailedParticipant{ + Reason: fmt.Sprintf("error %d", pcp.Error), + InviteContent: inviteContent, + } + } else { + filteredParticipants = append(filteredParticipants, pcp) + } + } + resp.Participants = filteredParticipants portal, err := wa.Main.Bridge.GetPortalByKey(ctx, wa.makeWAPortalKey(resp.JID)) if err != nil { return nil, fmt.Errorf("failed to get portal: %w", err) @@ -322,5 +355,7 @@ func (wa *WhatsAppClient) CreateGroup(ctx context.Context, params *bridgev2.Grou PortalKey: wa.makeWAPortalKey(resp.JID), Portal: portal, PortalInfo: groupInfo, + + FailedParticipants: failedParticipants, }, nil } diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 559f3bc6..78b8554d 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -19,6 +19,7 @@ package msgconv import ( "bytes" "context" + "encoding/json" "errors" "fmt" "image" @@ -99,7 +100,11 @@ func (mc *MessageConverter) ToWhatsApp( switch content.MsgType { case event.MsgText, event.MsgNotice, event.MsgEmote: - message = mc.constructTextMessage(ctx, content, contextInfo) + var err error + message, err = mc.constructTextMessage(ctx, content, evt.Content.Raw, contextInfo) + if err != nil { + return nil, nil, err + } case event.MessageType(event.EventSticker.Type), event.MsgImage, event.MsgVideo, event.MsgAudio, event.MsgFile: uploaded, thumbnail, mime, err := mc.reuploadFileToWhatsApp(ctx, content) if err != nil { @@ -304,7 +309,16 @@ func (mc *MessageConverter) parseText(ctx context.Context, content *event.Messag return } -func (mc *MessageConverter) constructTextMessage(ctx context.Context, content *event.MessageEventContent, contextInfo *waE2E.ContextInfo) *waE2E.Message { +func (mc *MessageConverter) constructTextMessage( + ctx context.Context, + content *event.MessageEventContent, + raw map[string]any, + contextInfo *waE2E.ContextInfo, +) (*waE2E.Message, error) { + groupInvite, ok := raw[GroupInviteMetaField].(map[string]any) + if ok { + return mc.constructGroupInviteMessage(ctx, content, groupInvite, contextInfo) + } text, mentions := mc.parseText(ctx, content) if len(mentions) > 0 { contextInfo.MentionedJID = mentions @@ -315,7 +329,44 @@ func (mc *MessageConverter) constructTextMessage(ctx context.Context, content *e } mc.convertURLPreviewToWhatsApp(ctx, content, etm) - return &waE2E.Message{ExtendedTextMessage: etm} + return &waE2E.Message{ExtendedTextMessage: etm}, nil +} + +func (mc *MessageConverter) constructGroupInviteMessage( + ctx context.Context, + content *event.MessageEventContent, + inviteMeta map[string]any, + contextInfo *waE2E.ContextInfo, +) (*waE2E.Message, error) { + payload, err := json.Marshal(inviteMeta) + if err != nil { + return nil, fmt.Errorf("failed to marshal invite meta: %w", err) + } + var parsedInviteMeta waid.GroupInviteMeta + err = json.Unmarshal(payload, &parsedInviteMeta) + if err != nil { + return nil, fmt.Errorf("failed to parse invite meta: %w", err) + } + text, mentions := mc.parseText(ctx, content) + if len(mentions) > 0 { + contextInfo.MentionedJID = mentions + } + groupType := waE2E.GroupInviteMessage_DEFAULT + if parsedInviteMeta.IsParentGroup { + groupType = waE2E.GroupInviteMessage_PARENT + } + return &waE2E.Message{ + GroupInviteMessage: &waE2E.GroupInviteMessage{ + GroupJID: proto.String(parsedInviteMeta.JID.String()), + InviteCode: proto.String(parsedInviteMeta.Code), + InviteExpiration: proto.Int64(parsedInviteMeta.Expiration), + GroupName: proto.String(parsedInviteMeta.GroupName), + JPEGThumbnail: nil, + Caption: proto.String(text), + ContextInfo: contextInfo, + GroupType: groupType.Enum(), + }, + }, nil } func (mc *MessageConverter) convertPill(displayname, mxid, eventID string, ctx format.Context) string { diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index b7907015..8dcdfb94 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -88,10 +88,12 @@ func (mc *MessageConverter) convertGroupInviteMessage(ctx context.Context, info template = inviteMsgBroken } else { inviteMeta = &waid.GroupInviteMeta{ - JID: groupJID, - Code: msg.GetInviteCode(), - Expiration: msg.GetInviteExpiration(), - Inviter: info.Sender.ToNonAD(), + JID: groupJID, + Code: msg.GetInviteCode(), + Expiration: msg.GetInviteExpiration(), + Inviter: info.Sender.ToNonAD(), + GroupName: msg.GetGroupName(), + IsParentGroup: msg.GetGroupType() == waE2E.GroupInviteMessage_PARENT, } extraAttrs = map[string]any{ GroupInviteMetaField: inviteMeta, diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index 81fd895a..5f47bdda 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -68,6 +68,9 @@ type GroupInviteMeta struct { Code string `json:"code"` Expiration int64 `json:"expiration,string"` Inviter types.JID `json:"inviter"` + + GroupName string `json:"group_name,omitempty"` + IsParentGroup bool `json:"is_parent_group,omitempty"` } type MessageMetadata struct { From 09153f6acfb1cba13d3323b76eccf8651c150daf Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 21 Oct 2025 18:56:23 +0300 Subject: [PATCH 187/486] startchat: update failed participants format --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/startchat.go | 9 +++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 245626a9..9895a3a1 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( golang.org/x/sync v0.17.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.3-0.20251021144018-1aacf6e987b1 + maunium.net/go/mautrix v0.25.3-0.20251021155549-ef31dae082e5 ) require ( diff --git a/go.sum b/go.sum index 50995ed9..336e845e 100644 --- a/go.sum +++ b/go.sum @@ -113,5 +113,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.25.3-0.20251021144018-1aacf6e987b1 h1:jE/W7DZnnD3wWVinyiUkuN8lMBM8AcUiJX5V2fClj3Y= -maunium.net/go/mautrix v0.25.3-0.20251021144018-1aacf6e987b1/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= +maunium.net/go/mautrix v0.25.3-0.20251021155549-ef31dae082e5 h1:DgOFsVa2evs4ZK6VFpwOrU50oh7PwCWBi7vXPEpW4Dk= +maunium.net/go/mautrix v0.25.3-0.20251021155549-ef31dae082e5/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index ad0dd05e..ee4ed9b7 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -250,7 +250,7 @@ func (wa *WhatsAppClient) CreateGroup(ctx context.Context, params *bridgev2.Grou if err != nil { return nil, fmt.Errorf("failed to create group: %w", err) } - failedParticipants := make(map[networkid.UserID]bridgev2.CreateChatFailedParticipant) + failedParticipants := make(map[networkid.UserID]*bridgev2.CreateChatFailedParticipant) filteredParticipants := resp.Participants[:0] for _, pcp := range resp.Participants { if pcp.Error != 0 { @@ -273,9 +273,10 @@ func (wa *WhatsAppClient) CreateGroup(ctx context.Context, params *bridgev2.Grou }, } } - failedParticipants[waid.MakeUserID(pcp.JID)] = bridgev2.CreateChatFailedParticipant{ - Reason: fmt.Sprintf("error %d", pcp.Error), - InviteContent: inviteContent, + failedParticipants[waid.MakeUserID(pcp.JID)] = &bridgev2.CreateChatFailedParticipant{ + Reason: fmt.Sprintf("error %d", pcp.Error), + InviteEventType: event.EventMessage.Type, + InviteContent: inviteContent, } } else { filteredParticipants = append(filteredParticipants, pcp) From a138a7157ba37cad93d982bc347abcc981d3a5ea Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 22 Oct 2025 13:04:21 +0300 Subject: [PATCH 188/486] handle*: fix handling mute forever --- go.mod | 4 ++-- go.sum | 8 ++++---- pkg/connector/handlewhatsapp.go | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 9895a3a1..7f14c636 100644 --- a/go.mod +++ b/go.mod @@ -9,13 +9,13 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.2 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251016095441-02c50743e601 + go.mau.fi/whatsmeow v0.0.0-20251022100208-a81333ed9f79 golang.org/x/image v0.32.0 golang.org/x/net v0.46.0 golang.org/x/sync v0.17.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.3-0.20251021155549-ef31dae082e5 + maunium.net/go/mautrix v0.25.3-0.20251022100332-e805815e4120 ) require ( diff --git a/go.sum b/go.sum index 336e845e..c24693ad 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.mau.fi/util v0.9.2 h1:+S4Z03iCsGqU2WY8X2gySFsFjaLlUHFRDVCYvVwynKM= go.mau.fi/util v0.9.2/go.mod h1:055elBBCJSdhRsmub7ci9hXZPgGr1U6dYg44cSgRgoU= 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-20251016095441-02c50743e601 h1:6tytDOaqizeBVG3OdEojIiJtZXY3DWMvFKNkjbxq4tI= -go.mau.fi/whatsmeow v0.0.0-20251016095441-02c50743e601/go.mod h1:VJq+D05Fe5EroZxs2StEYD/AsWJO2aQ7Niucz7lCvao= +go.mau.fi/whatsmeow v0.0.0-20251022100208-a81333ed9f79 h1:ps7TvlR7BlLgCKDWy9L1eFtwpLcezhvAe4sMUo/gwIY= +go.mau.fi/whatsmeow v0.0.0-20251022100208-a81333ed9f79/go.mod h1:VJq+D05Fe5EroZxs2StEYD/AsWJO2aQ7Niucz7lCvao= 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.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= @@ -113,5 +113,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.25.3-0.20251021155549-ef31dae082e5 h1:DgOFsVa2evs4ZK6VFpwOrU50oh7PwCWBi7vXPEpW4Dk= -maunium.net/go/mautrix v0.25.3-0.20251021155549-ef31dae082e5/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= +maunium.net/go/mautrix v0.25.3-0.20251022100332-e805815e4120 h1:9tknT3v95cmHYYCssiJHUWEU/N1rn7Ft1rziTn0bIHU= +maunium.net/go/mautrix v0.25.3-0.20251022100332-e805815e4120/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 2eb1284c..c7082f72 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -744,7 +744,7 @@ func (wa *WhatsAppClient) handleWAMute(evt *events.Mute) bool { var mutedUntil time.Time if evt.Action.GetMuted() { mutedUntil = event.MutedForever - if evt.Action.GetMuteEndTimestamp() != 0 { + if evt.Action.GetMuteEndTimestamp() > 0 { mutedUntil = time.Unix(evt.Action.GetMuteEndTimestamp(), 0) } } else { From 0f9dc4a395f20604e3ed3b1847b80c13c722a85a Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 22 Oct 2025 18:57:28 +0300 Subject: [PATCH 189/486] handlewhatsapp: fix deleting space when leaving community --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/handlewhatsapp.go | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7f14c636..d1ad9dc2 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( golang.org/x/sync v0.17.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.3-0.20251022100332-e805815e4120 + maunium.net/go/mautrix v0.25.3-0.20251022155641-9fd1e0f87cef ) require ( diff --git a/go.sum b/go.sum index c24693ad..8d159588 100644 --- a/go.sum +++ b/go.sum @@ -113,5 +113,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.25.3-0.20251022100332-e805815e4120 h1:9tknT3v95cmHYYCssiJHUWEU/N1rn7Ft1rziTn0bIHU= -maunium.net/go/mautrix v0.25.3-0.20251022100332-e805815e4120/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= +maunium.net/go/mautrix v0.25.3-0.20251022155641-9fd1e0f87cef h1:h+v9hjL0+fOmyi12jLC56l7332VDGLkm3+b7Cz/BLh4= +maunium.net/go/mautrix v0.25.3-0.20251022155641-9fd1e0f87cef/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index c7082f72..4429d052 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -579,6 +579,7 @@ func (wa *WhatsAppClient) handleWADeleteChat(evt *events.DeleteChat) bool { Timestamp: evt.Timestamp, }, OnlyForMe: true, + Children: true, }).Success } From 1d320e9094b4f59de829f7c55bde2d4695269beb Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 24 Oct 2025 12:45:35 +0300 Subject: [PATCH 190/486] dependencies: update --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index d1ad9dc2..b60775a5 100644 --- a/go.mod +++ b/go.mod @@ -9,13 +9,13 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.2 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251022100208-a81333ed9f79 + go.mau.fi/whatsmeow v0.0.0-20251023183934-2ced35dd7e8c golang.org/x/image v0.32.0 golang.org/x/net v0.46.0 golang.org/x/sync v0.17.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.3-0.20251022155641-9fd1e0f87cef + maunium.net/go/mautrix v0.25.3-0.20251024094209-5d87d14b8858 ) require ( diff --git a/go.sum b/go.sum index 8d159588..a67ba281 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.mau.fi/util v0.9.2 h1:+S4Z03iCsGqU2WY8X2gySFsFjaLlUHFRDVCYvVwynKM= go.mau.fi/util v0.9.2/go.mod h1:055elBBCJSdhRsmub7ci9hXZPgGr1U6dYg44cSgRgoU= 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-20251022100208-a81333ed9f79 h1:ps7TvlR7BlLgCKDWy9L1eFtwpLcezhvAe4sMUo/gwIY= -go.mau.fi/whatsmeow v0.0.0-20251022100208-a81333ed9f79/go.mod h1:VJq+D05Fe5EroZxs2StEYD/AsWJO2aQ7Niucz7lCvao= +go.mau.fi/whatsmeow v0.0.0-20251023183934-2ced35dd7e8c h1:7QLq7oW+q8U5AU7uhGsVOB2qPD3306fr7oVSni77c+A= +go.mau.fi/whatsmeow v0.0.0-20251023183934-2ced35dd7e8c/go.mod h1:VJq+D05Fe5EroZxs2StEYD/AsWJO2aQ7Niucz7lCvao= 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.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= @@ -113,5 +113,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.25.3-0.20251022155641-9fd1e0f87cef h1:h+v9hjL0+fOmyi12jLC56l7332VDGLkm3+b7Cz/BLh4= -maunium.net/go/mautrix v0.25.3-0.20251022155641-9fd1e0f87cef/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= +maunium.net/go/mautrix v0.25.3-0.20251024094209-5d87d14b8858 h1:w4rgGYjb+lA/AIbyOaUAMDQ6KhxyrTxNxZMPljaXoYw= +maunium.net/go/mautrix v0.25.3-0.20251024094209-5d87d14b8858/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= From 1cd8d7441adc27428103f26a7e005c137f1b5ed6 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 27 Oct 2025 16:20:47 +0200 Subject: [PATCH 191/486] all: update whatsmeow methods to use contexts --- go.mod | 3 +-- go.sum | 6 ++---- pkg/connector/chatinfo.go | 8 ++++---- pkg/connector/client.go | 18 ++++++++++-------- pkg/connector/commands.go | 16 ++++++++-------- pkg/connector/directmedia.go | 4 ++-- pkg/connector/events.go | 2 +- pkg/connector/handlematrix.go | 16 ++++++++-------- pkg/connector/handlewhatsapp.go | 8 ++++---- pkg/connector/login.go | 2 +- pkg/connector/mediarequest.go | 6 +++--- pkg/connector/proxy.go | 5 ++++- pkg/connector/startchat.go | 10 +++++----- pkg/connector/userinfo.go | 4 ++-- 14 files changed, 55 insertions(+), 53 deletions(-) diff --git a/go.mod b/go.mod index b60775a5..971dfba4 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.2 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251023183934-2ced35dd7e8c + go.mau.fi/whatsmeow v0.0.0-20251027141726-3d82d3101dd1 golang.org/x/image v0.32.0 golang.org/x/net v0.46.0 golang.org/x/sync v0.17.0 @@ -25,7 +25,6 @@ require ( github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.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 diff --git a/go.sum b/go.sum index a67ba281..e991e5bf 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,6 @@ 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= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -81,8 +79,8 @@ go.mau.fi/util v0.9.2 h1:+S4Z03iCsGqU2WY8X2gySFsFjaLlUHFRDVCYvVwynKM= go.mau.fi/util v0.9.2/go.mod h1:055elBBCJSdhRsmub7ci9hXZPgGr1U6dYg44cSgRgoU= 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-20251023183934-2ced35dd7e8c h1:7QLq7oW+q8U5AU7uhGsVOB2qPD3306fr7oVSni77c+A= -go.mau.fi/whatsmeow v0.0.0-20251023183934-2ced35dd7e8c/go.mod h1:VJq+D05Fe5EroZxs2StEYD/AsWJO2aQ7Niucz7lCvao= +go.mau.fi/whatsmeow v0.0.0-20251027141726-3d82d3101dd1 h1:xjTxq7OfIV+oVJnsWrXX5lrIt8cSMV6yHppdp2+fVQE= +go.mau.fi/whatsmeow v0.0.0-20251027141726-3d82d3101dd1/go.mod h1:RwBrMQAWCHGzMdDZ6EwjcY4Aj3g8Efx8c7GACTdiAME= 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.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 8d47564b..c3545f53 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -40,14 +40,14 @@ func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, return nil, fmt.Errorf("broadcast list bridging is currently not supported") } case types.GroupServer: - info, err := wa.Client.GetGroupInfo(portalJID) + info, err := wa.Client.GetGroupInfo(ctx, portalJID) if err != nil { return nil, err } wrapped = wa.wrapGroupInfo(ctx, info) wrapped.ExtraUpdates = bridgev2.MergeExtraUpdaters(wrapped.ExtraUpdates, updatePortalLastSyncAt) case types.NewsletterServer: - info, err := wa.Client.GetNewsletterInfo(portalJID) + info, err := wa.Client.GetNewsletterInfo(ctx, portalJID) if err != nil { return nil, err } @@ -419,7 +419,7 @@ func (wa *WhatsAppClient) makePortalAvatarFetcher(avatarID string, sender types. existingID = "" } var wrappedAvatar *bridgev2.Avatar - avatar, err := wa.Client.GetProfilePictureInfo(jid, &whatsmeow.GetProfilePictureParams{ + avatar, err := wa.Client.GetProfilePictureInfo(ctx, jid, &whatsmeow.GetProfilePictureParams{ ExistingID: existingID, IsCommunity: portal.RoomType == database.RoomTypeSpace, }) @@ -492,7 +492,7 @@ func (wa *WhatsAppClient) wrapNewsletterInfo(ctx context.Context, info *types.Ne } else if info.ThreadMeta.Preview.ID != "" { avatar.ID = networkid.AvatarID(info.ThreadMeta.Preview.ID) avatar.Get = func(ctx context.Context) ([]byte, error) { - meta, err := wa.Client.GetNewsletterInfo(info.ID) + meta, err := wa.Client.GetNewsletterInfo(ctx, info.ID) if err != nil { return nil, fmt.Errorf("failed to fetch full res avatar info: %w", err) } else if meta.ThreadMeta.Picture == nil { diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 6196e169..7c5c6e93 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -196,9 +196,12 @@ func (wa *WhatsAppClient) Connect(ctx context.Context) { if err := wa.Main.updateProxy(ctx, wa.Client, false); err != nil { zerolog.Ctx(ctx).Err(err).Msg("Failed to update proxy") } + if ctx.Err() != nil { + return + } wa.startLoops() wa.Client.BackgroundEventCtx = wa.UserLogin.Log.WithContext(wa.Main.Bridge.BackgroundCtx) - if err := wa.Client.Connect(); err != nil { + if err := wa.Client.ConnectContext(ctx); err != nil { zerolog.Ctx(ctx).Err(err).Msg("Failed to connect to WhatsApp") state := status.BridgeState{ StateEvent: status.StateUnknownError, @@ -281,7 +284,7 @@ func (wa *WhatsAppClient) ConnectBackground(ctx context.Context, params *bridgev func (wa *WhatsAppClient) sendPNData(ctx context.Context, pn string) error { //lint:ignore SA1019 this is supposed to be dangerous - resp, err := wa.Client.DangerousInternals().SendIQ(whatsmeow.DangerousInfoQuery{ + resp, err := wa.Client.DangerousInternals().SendIQ(ctx, whatsmeow.DangerousInfoQuery{ Namespace: "urn:xmpp:whatsapp:push", Type: "get", To: types.ServerJID, @@ -289,7 +292,6 @@ func (wa *WhatsAppClient) sendPNData(ctx context.Context, pn string) error { Tag: "pn", Content: pn, }}, - Context: ctx, }) if err != nil { return fmt.Errorf("failed to send pn: %w", err) @@ -304,7 +306,7 @@ func (wa *WhatsAppClient) sendPNData(ctx context.Context, pn string) error { } zerolog.Ctx(ctx).Debug().Str("cat_data", string(catContentBytes)).Msg("Received cat response from sending pn data") //lint:ignore SA1019 this is supposed to be dangerous - err = wa.Client.DangerousInternals().SendNode(waBinary.Node{ + err = wa.Client.DangerousInternals().SendNode(ctx, waBinary.Node{ Tag: "ib", Content: []waBinary.Node{{ Tag: "cat", @@ -319,7 +321,7 @@ func (wa *WhatsAppClient) sendPNData(ctx context.Context, pn string) error { } func (wa *WhatsAppClient) startLoops() { - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(wa.Main.Bridge.BackgroundCtx) oldStop := wa.stopLoops.Swap(&cancel) if oldStop != nil { (*oldStop)() @@ -409,7 +411,7 @@ func (wa *WhatsAppClient) HandleMatrixViewingChat(ctx context.Context, msg *brid } if wa.lastPresence != presence { - err := wa.updatePresence(presence) + err := wa.updatePresence(ctx, presence) if err != nil { zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to set presence when viewing chat") } @@ -444,8 +446,8 @@ func (wa *WhatsAppClient) HandleMatrixViewingChat(ctx context.Context, msg *brid return nil } -func (wa *WhatsAppClient) updatePresence(presence types.Presence) error { - err := wa.Client.SendPresence(presence) +func (wa *WhatsAppClient) updatePresence(ctx context.Context, presence types.Presence) error { + err := wa.Client.SendPresence(ctx, presence) if err == nil { wa.lastPresence = presence } diff --git a/pkg/connector/commands.go b/pkg/connector/commands.go index ec1b999e..e86a6f9b 100644 --- a/pkg/connector/commands.go +++ b/pkg/connector/commands.go @@ -65,7 +65,7 @@ func fnAccept(ce *commands.Event) { ce.Reply("Login not found") } else if !login.Client.IsLoggedIn() { ce.Reply("Not logged in") - } else if err = login.Client.(*WhatsAppClient).Client.JoinGroupWithInvite(meta.JID, meta.Inviter, meta.Code, meta.Expiration); err != nil { + } else if err = login.Client.(*WhatsAppClient).Client.JoinGroupWithInvite(ce.Ctx, meta.JID, meta.Inviter, meta.Code, meta.Expiration); err != nil { ce.Log.Err(err).Msg("Failed to accept group invite") ce.Reply("Failed to accept group invite: %v", err) } else { @@ -189,7 +189,7 @@ func fnInviteLink(ce *commands.Event) { ce.Reply("Can't get invite link to private chat") } else if portalJID.IsBroadcastList() { ce.Reply("Can't get invite link to broadcast list") - } else if link, err := wa.Client.GetGroupInviteLink(portalJID, reset); err != nil { + } else if link, err := wa.Client.GetGroupInviteLink(ce.Ctx, portalJID, reset); err != nil { ce.Reply("Failed to get invite link: %v", err) } else { ce.Reply(link) @@ -219,14 +219,14 @@ func fnResolveLink(ce *commands.Event) { } wa := login.Client.(*WhatsAppClient) if strings.HasPrefix(ce.Args[0], whatsmeow.InviteLinkPrefix) { - group, err := wa.Client.GetGroupInfoFromLink(ce.Args[0]) + group, err := wa.Client.GetGroupInfoFromLink(ce.Ctx, ce.Args[0]) if err != nil { ce.Reply("Failed to get group info: %v", err) return } ce.Reply("That invite link points at %s (`%s`)", group.Name, group.JID) } else if strings.HasPrefix(ce.Args[0], whatsmeow.BusinessMessageLinkPrefix) || strings.HasPrefix(ce.Args[0], whatsmeow.BusinessMessageLinkDirectPrefix) { - target, err := wa.Client.ResolveBusinessMessageLink(ce.Args[0]) + target, err := wa.Client.ResolveBusinessMessageLink(ce.Ctx, ce.Args[0]) if err != nil { ce.Reply("Failed to get business info: %v", err) return @@ -241,7 +241,7 @@ func fnResolveLink(ce *commands.Event) { } ce.Reply("That link points at %s (+%s).%s", target.PushName, target.JID.User, message) } else if strings.HasPrefix(ce.Args[0], whatsmeow.ContactQRLinkPrefix) || strings.HasPrefix(ce.Args[0], whatsmeow.ContactQRLinkDirectPrefix) { - target, err := wa.Client.ResolveContactQRLink(ce.Args[0]) + target, err := wa.Client.ResolveContactQRLink(ce.Ctx, ce.Args[0]) if err != nil { ce.Reply("Failed to get contact info: %v", err) return @@ -280,7 +280,7 @@ func fnJoin(ce *commands.Event) { wa := login.Client.(*WhatsAppClient) if strings.HasPrefix(ce.Args[0], whatsmeow.InviteLinkPrefix) { - jid, err := wa.Client.JoinGroupWithLink(ce.Args[0]) + jid, err := wa.Client.JoinGroupWithLink(ce.Ctx, ce.Args[0]) if err != nil { ce.Reply("Failed to join group: %v", err) return @@ -288,12 +288,12 @@ func fnJoin(ce *commands.Event) { ce.Log.Debug().Stringer("group_jid", jid).Msg("User successfully joined WhatsApp group with link") ce.Reply("Successfully joined group `%s`, the portal should be created momentarily", jid) } else if strings.HasPrefix(ce.Args[0], whatsmeow.NewsletterLinkPrefix) { - info, err := wa.Client.GetNewsletterInfoWithInvite(ce.Args[0]) + info, err := wa.Client.GetNewsletterInfoWithInvite(ce.Ctx, ce.Args[0]) if err != nil { ce.Reply("Failed to get channel info: %v", err) return } - err = wa.Client.FollowNewsletter(info.ID) + err = wa.Client.FollowNewsletter(ce.Ctx, info.ID) if err != nil { ce.Reply("Failed to follow channel: %v", err) return diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index ec1610c5..c5d51ea9 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -90,7 +90,7 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars zerolog.Ctx(ctx).Debug(). Str("avatar_id", parsedID.Avatar.AvatarID). Msg("Refreshing avatar URL from WhatsApp servers") - avatar, err := waClient.Client.GetProfilePictureInfo(parsedID.Avatar.TargetJID, &whatsmeow.GetProfilePictureParams{ + avatar, err := waClient.Client.GetProfilePictureInfo(ctx, parsedID.Avatar.TargetJID, &whatsmeow.GetProfilePictureParams{ IsCommunity: parsedID.Avatar.Community, }) if errors.Is(err, whatsmeow.ErrProfilePictureNotSet) || @@ -262,7 +262,7 @@ func (wa *WhatsAppClient) requestDirectMedia(ctx context.Context, rawMsgID netwo defer state.Unlock() if !state.requested { zerolog.Ctx(ctx).Debug().Msg("Sending request for missing media in direct download") - err := wa.sendMediaRequestDirect(rawMsgID, key) + err := wa.sendMediaRequestDirect(ctx, rawMsgID, key) if err != nil { return nil, fmt.Errorf("failed to send media retry request: %w", err) } diff --git a/pkg/connector/events.go b/pkg/connector/events.go index f371cc0a..87916724 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -139,7 +139,7 @@ func (evt *WAMessageEvent) PreHandle(ctx context.Context, portal *bridgev2.Porta log := zerolog.Ctx(ctx).With().Str("action", "group lid migration").Logger() ctx = log.WithContext(ctx) meta.LIDMigrationAttempted = true - info, err := evt.wa.Client.GetGroupInfo(portalJID) + info, err := evt.wa.Client.GetGroupInfo(ctx, portalJID) if err != nil { log.Err(err).Msg("Failed to get group info for lid migration") return diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index fe77c37f..38ea4afd 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -322,7 +322,7 @@ func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt * messagesToRead[key] = append(messagesToRead[key], parsed.ID) } for messageSender, ids := range messagesToRead { - err = wa.Client.MarkRead(ids, receipt.Receipt.Timestamp, portalJID, messageSender) + err = wa.Client.MarkRead(ctx, ids, receipt.Receipt.Timestamp, portalJID, messageSender) if err != nil { log.Err(err).Strs("ids", ids).Msg("Failed to mark messages as read") } @@ -352,12 +352,12 @@ func (wa *WhatsAppClient) HandleMatrixTyping(ctx context.Context, msg *bridgev2. } if wa.Main.Config.SendPresenceOnTyping { - err = wa.updatePresence(types.PresenceAvailable) + err = wa.updatePresence(ctx, types.PresenceAvailable) if err != nil { zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to set presence on typing") } } - return wa.Client.SendChatPresence(portalJID, chatPresence, mediaPresence) + return wa.Client.SendChatPresence(ctx, portalJID, chatPresence, mediaPresence) } var errUnsupportedDisappearingTimer = bridgev2.WrapErrorInStatus(errors.New("invalid value for disappearing timer")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true) @@ -375,7 +375,7 @@ func (wa *WhatsAppClient) HandleMatrixDisappearingTimer(ctx context.Context, msg } settingTS := time.UnixMilli(msg.Event.Timestamp) - err = wa.Client.SetDisappearingTimer(portalJID, msg.Content.Timer.Duration, settingTS) + err = wa.Client.SetDisappearingTimer(ctx, portalJID, msg.Content.Timer.Duration, settingTS) if err != nil { return false, err } @@ -430,7 +430,7 @@ func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridg return false, fmt.Errorf("cannot get target intent: unknown type: %T", target) } - _, err = wa.Client.UpdateGroupParticipants(portalJID, changes, action) + _, err = wa.Client.UpdateGroupParticipants(ctx, portalJID, changes, action) if err != nil { return false, err } @@ -448,7 +448,7 @@ func (wa *WhatsAppClient) HandleMatrixRoomName(ctx context.Context, msg *bridgev return false, fmt.Errorf("cannot set room name for DM") } - err = wa.Client.SetGroupName(portalJID, msg.Content.Name) + err = wa.Client.SetGroupName(ctx, portalJID, msg.Content.Name) if err != nil { return false, err } @@ -471,7 +471,7 @@ func (wa *WhatsAppClient) HandleMatrixRoomTopic(ctx context.Context, msg *bridge newID := wa.Client.GenerateMessageID() oldID := msg.Portal.Metadata.(*waid.PortalMetadata).TopicID - err = wa.Client.SetGroupTopic(portalJID, oldID, newID, msg.Content.Topic) + err = wa.Client.SetGroupTopic(ctx, portalJID, oldID, newID, msg.Content.Topic) if err != nil { return false, err } @@ -506,7 +506,7 @@ func (wa *WhatsAppClient) HandleMatrixRoomAvatar(ctx context.Context, msg *bridg } } - avatarID, err := wa.Client.SetGroupPhoto(portalJID, data) + avatarID, err := wa.Client.SetGroupPhoto(ctx, portalJID, data) if err != nil { return false, err } diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 4429d052..4a2c04e3 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -129,7 +129,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { case *events.AppStateSyncComplete: if len(wa.GetStore().PushName) > 0 && evt.Name == appstate.WAPatchCriticalBlock { - err := wa.updatePresence(types.PresenceUnavailable) + err := wa.updatePresence(ctx, types.PresenceUnavailable) if err != nil { log.Warn().Err(err).Msg("Failed to send presence after app state sync") } @@ -142,7 +142,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { case *events.PushNameSetting: // Send presence available when connecting and when the pushname is changed. // This makes sure that outgoing messages always have the right pushname. - err := wa.updatePresence(types.PresenceUnavailable) + err := wa.updatePresence(ctx, types.PresenceUnavailable) if err != nil { log.Warn().Err(err).Msg("Failed to send presence after push name update") } @@ -163,12 +163,12 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected}) if len(wa.GetStore().PushName) > 0 { go func() { - err := wa.updatePresence(types.PresenceUnavailable) + err := wa.updatePresence(ctx, types.PresenceUnavailable) if err != nil { log.Warn().Err(err).Msg("Failed to send initial presence after connecting") } }() - go wa.syncRemoteProfile(log.WithContext(context.Background()), nil) + go wa.syncRemoteProfile(ctx, nil) } case *events.OfflineSyncPreview: log.Info(). diff --git a/pkg/connector/login.go b/pkg/connector/login.go index 24423568..9614e333 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -352,7 +352,7 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { } ul.Client.(*WhatsAppClient).isNewLogin = true - ul.Client.Connect(ul.Log.WithContext(context.Background())) + ul.Client.Connect(ul.Log.WithContext(wl.Main.Bridge.BackgroundCtx)) return &bridgev2.LoginStep{ Type: bridgev2.LoginStepTypeComplete, diff --git a/pkg/connector/mediarequest.go b/pkg/connector/mediarequest.go index e0f4c5b9..2c382d12 100644 --- a/pkg/connector/mediarequest.go +++ b/pkg/connector/mediarequest.go @@ -137,7 +137,7 @@ func (wa *WhatsAppClient) sendMediaRequest(ctx context.Context, req *wadb.MediaR req.Status = wadb.MediaBackfillRequestStatusRequestSkipped return } - err = wa.sendMediaRequestDirect(req.MessageID, req.MediaKey) + err = wa.sendMediaRequestDirect(ctx, req.MessageID, req.MediaKey) if err != nil { log.Err(err).Msg("Failed to send media retry request") req.Status = wadb.MediaBackfillRequestStatusRequestFailed @@ -148,12 +148,12 @@ func (wa *WhatsAppClient) sendMediaRequest(ctx context.Context, req *wadb.MediaR } } -func (wa *WhatsAppClient) sendMediaRequestDirect(rawMsgID networkid.MessageID, key []byte) error { +func (wa *WhatsAppClient) sendMediaRequestDirect(ctx context.Context, rawMsgID networkid.MessageID, key []byte) error { msgID, err := waid.ParseMessageID(rawMsgID) if err != nil { return fmt.Errorf("failed to parse message ID: %w", err) } - return wa.Client.SendMediaRetryReceipt(&types.MessageInfo{ + return wa.Client.SendMediaRetryReceipt(ctx, &types.MessageInfo{ ID: msgID.ID, MessageSource: types.MessageSource{ IsFromMe: msgID.Sender.User == wa.JID.User, diff --git a/pkg/connector/proxy.go b/pkg/connector/proxy.go index 770d66d0..4ec202a0 100644 --- a/pkg/connector/proxy.go +++ b/pkg/connector/proxy.go @@ -58,7 +58,10 @@ func (wa *WhatsAppConnector) updateProxy(ctx context.Context, client *whatsmeow. } if proxy, err := wa.getProxy(reason); err != nil { return fmt.Errorf("failed to get proxy address: %w", err) - } else if err = client.SetProxyAddress(proxy); err != nil { + } else if err = client.SetProxyAddress(proxy, whatsmeow.SetProxyOptions{ + OnlyLogin: wa.Config.ProxyOnlyLogin, + NoMedia: wa.Config.ProxyOnlyLogin, + }); err != nil { return fmt.Errorf("failed to set proxy address: %w", err) } zerolog.Ctx(ctx).Debug().Msg("Enabled proxy") diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index ee4ed9b7..d11c8304 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -62,7 +62,7 @@ func looksEmaily(str string) bool { return false } -func (wa *WhatsAppClient) validateIdentifer(number string) (types.JID, error) { +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) } else if strings.HasPrefix(number, waid.BotPrefix) || strings.HasPrefix(number, waid.LIDPrefix) { @@ -76,7 +76,7 @@ func (wa *WhatsAppClient) validateIdentifer(number string) (types.JID, error) { return types.EmptyJID, ErrInputLooksLikeEmail } else if wa.Client == nil || !wa.Client.IsLoggedIn() { return types.EmptyJID, bridgev2.ErrNotLoggedIn - } else if resp, err := wa.Client.IsOnWhatsApp([]string{number}); err != 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 { return types.EmptyJID, fmt.Errorf("the server did not respond to the query") @@ -144,7 +144,7 @@ func (wa *WhatsAppClient) CreateChatWithGhost(ctx context.Context, ghost *bridge } func (wa *WhatsAppClient) ResolveIdentifier(ctx context.Context, identifier string, startChat bool) (*bridgev2.ResolveIdentifierResponse, error) { - origJID, err := wa.validateIdentifer(identifier) + origJID, err := wa.validateIdentifer(ctx, identifier) if err != nil { return nil, err } @@ -318,7 +318,7 @@ func (wa *WhatsAppClient) CreateGroup(ctx context.Context, params *bridgev2.Grou } changed := false if avatarBytes != nil { - avatarID, err := wa.Client.SetGroupPhoto(resp.JID, avatarBytes) + avatarID, err := wa.Client.SetGroupPhoto(ctx, resp.JID, avatarBytes) if err != nil { zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to set group avatar after creating group") } else { @@ -335,7 +335,7 @@ func (wa *WhatsAppClient) CreateGroup(ctx context.Context, params *bridgev2.Grou } if params.Topic != nil { newTopicID := wa.Client.GenerateMessageID() - err = wa.Client.SetGroupTopic(resp.JID, "", newTopicID, params.Topic.Topic) + err = wa.Client.SetGroupTopic(ctx, resp.JID, "", newTopicID, params.Topic.Topic) if err != nil { zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to set group topic after creating group") } else { diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 150a97d6..5af33630 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -149,7 +149,7 @@ func (wa *WhatsAppClient) doGhostResync(ctx context.Context, queue map[types.JID return } log.Debug().Array("jids", exzerolog.ArrayOfStringers(ghostJIDs)).Msg("Doing background sync for users") - infos, err := wa.Client.GetUserInfo(ghostJIDs) + infos, err := wa.Client.GetUserInfo(ctx, ghostJIDs) if err != nil { log.Err(err).Msg("Failed to get user info for background sync") return @@ -302,7 +302,7 @@ func (wa *WhatsAppClient) fetchGhostAvatar(ctx context.Context, ghost *bridgev2. existingID = "" } var wrappedAvatar *bridgev2.Avatar - avatar, err := wa.Client.GetProfilePictureInfo(jid, &whatsmeow.GetProfilePictureParams{ExistingID: existingID}) + avatar, err := wa.Client.GetProfilePictureInfo(ctx, jid, &whatsmeow.GetProfilePictureParams{ExistingID: existingID}) if errors.Is(err, whatsmeow.ErrProfilePictureNotSet) { wrappedAvatar = &bridgev2.Avatar{ ID: "remove", From 759c05028b80c0f9dafb1fce5964514aa2716061 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 27 Oct 2025 17:46:54 +0200 Subject: [PATCH 192/486] msgconv/wa-media: reduce sticker size --- pkg/msgconv/wa-media.go | 43 +++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index 647e98f5..ed1dc8dc 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -237,6 +237,8 @@ type MediaMessageWithDuration interface { GetSeconds() uint32 } +const WhatsAppStickerSize = 190 + func prepareMediaMessage(rawMsg MediaMessage) *PreparedMedia { extraInfo := map[string]any{} data := &PreparedMedia{ @@ -248,6 +250,22 @@ func prepareMediaMessage(rawMsg MediaMessage) *PreparedMedia { "info": extraInfo, }, } + if durationMsg, ok := rawMsg.(MediaMessageWithDuration); ok { + data.Info.Duration = int(durationMsg.GetSeconds() * 1000) + } + if dimensionMsg, ok := rawMsg.(MediaMessageWithDimensions); ok { + data.Info.Width = int(dimensionMsg.GetWidth()) + data.Info.Height = int(dimensionMsg.GetHeight()) + } + if captionMsg, ok := rawMsg.(MediaMessageWithCaption); ok && captionMsg.GetCaption() != "" { + data.Body = captionMsg.GetCaption() + } else { + data.Body = data.FileName + } + data.Info.Size = int(rawMsg.GetFileLength()) + data.Info.MimeType = rawMsg.GetMimetype() + data.ContextInfo = rawMsg.GetContextInfo() + switch msg := rawMsg.(type) { case *waE2E.ImageMessage: data.MsgType = event.MsgImage @@ -272,6 +290,16 @@ func prepareMediaMessage(rawMsg MediaMessage) *PreparedMedia { 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 + } 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 + } case *waE2E.VideoMessage: data.MsgType = event.MsgVideo if msg.GetGifPlayback() { @@ -285,22 +313,7 @@ func prepareMediaMessage(rawMsg MediaMessage) *PreparedMedia { default: panic(fmt.Errorf("unknown media message type %T", rawMsg)) } - if durationMsg, ok := rawMsg.(MediaMessageWithDuration); ok { - data.Info.Duration = int(durationMsg.GetSeconds() * 1000) - } - if dimensionMsg, ok := rawMsg.(MediaMessageWithDimensions); ok { - data.Info.Width = int(dimensionMsg.GetWidth()) - data.Info.Height = int(dimensionMsg.GetHeight()) - } - if captionMsg, ok := rawMsg.(MediaMessageWithCaption); ok && captionMsg.GetCaption() != "" { - data.Body = captionMsg.GetCaption() - } else { - data.Body = data.FileName - } - data.Info.Size = int(rawMsg.GetFileLength()) - data.Info.MimeType = rawMsg.GetMimetype() - data.ContextInfo = rawMsg.GetContextInfo() return data } From 11bd416e03a527747a97b63cb6e10f353f2d390e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 27 Oct 2025 17:58:11 +0200 Subject: [PATCH 193/486] 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 971dfba4..232d685e 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( golang.org/x/sync v0.17.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.3-0.20251024094209-5d87d14b8858 + maunium.net/go/mautrix v0.25.3-0.20251025135936-d486dba9271c ) require ( diff --git a/go.sum b/go.sum index e991e5bf..40979e36 100644 --- a/go.sum +++ b/go.sum @@ -111,5 +111,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.25.3-0.20251024094209-5d87d14b8858 h1:w4rgGYjb+lA/AIbyOaUAMDQ6KhxyrTxNxZMPljaXoYw= -maunium.net/go/mautrix v0.25.3-0.20251024094209-5d87d14b8858/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= +maunium.net/go/mautrix v0.25.3-0.20251025135936-d486dba9271c h1:3mRJUCCMc9QDpXVZToqKuQNoCFABldGae0HFxiuCBzE= +maunium.net/go/mautrix v0.25.3-0.20251025135936-d486dba9271c/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= From e1c3d5092f7e2b3e8151e9b27cf1e8af52c65a1d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 27 Oct 2025 18:40:19 +0200 Subject: [PATCH 194/486] capabilities: advertise supported state events and member actions Closes #858 Closes #856 --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/capabilities.go | 25 +++++++++++++++++++++++-- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 232d685e..2683e5bc 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( golang.org/x/sync v0.17.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.3-0.20251025135936-d486dba9271c + maunium.net/go/mautrix v0.25.3-0.20251027163910-adc035b6a555 ) require ( diff --git a/go.sum b/go.sum index 40979e36..bf392fbf 100644 --- a/go.sum +++ b/go.sum @@ -111,5 +111,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.25.3-0.20251025135936-d486dba9271c h1:3mRJUCCMc9QDpXVZToqKuQNoCFABldGae0HFxiuCBzE= -maunium.net/go/mautrix v0.25.3-0.20251025135936-d486dba9271c/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= +maunium.net/go/mautrix v0.25.3-0.20251027163910-adc035b6a555 h1:6D/kRJyT+5+RRnJErNNglVwDMYU6EwQN0bRYqcdkfZw= +maunium.net/go/mautrix v0.25.3-0.20251027163910-adc035b6a555/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= diff --git a/pkg/connector/capabilities.go b/pkg/connector/capabilities.go index 09307e24..75a47e9a 100644 --- a/pkg/connector/capabilities.go +++ b/pkg/connector/capabilities.go @@ -8,6 +8,7 @@ import ( "go.mau.fi/util/jsontime" "go.mau.fi/util/ptr" "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/database" "maunium.net/go/mautrix/event" "go.mau.fi/mautrix-whatsapp/pkg/waid" @@ -50,7 +51,7 @@ func (wa *WhatsAppConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilit } func (wa *WhatsAppConnector) GetBridgeInfoVersion() (info, caps int) { - return 1, 5 + return 1, 6 } const WAMaxFileSize = 2000 * 1024 * 1024 @@ -65,7 +66,7 @@ func supportedIfFFmpeg() event.CapabilitySupportLevel { } func capID() string { - base := "fi.mau.whatsapp.capabilities.2025_10_07" + base := "fi.mau.whatsapp.capabilities.2025_10_27" if ffmpeg.Supported() { return base + "+ffmpeg" } @@ -161,6 +162,17 @@ var whatsappCaps = &event.RoomFeatures{ MaxSize: WAMaxFileSize, }, }, + State: event.StateFeatureMap{ + event.StateRoomName.Type: {Level: event.CapLevelFullySupported}, + event.StateRoomAvatar.Type: {Level: event.CapLevelFullySupported}, + event.StateTopic.Type: {Level: event.CapLevelFullySupported}, + event.StateBeeperDisappearingTimer.Type: {Level: event.CapLevelFullySupported}, + }, + MemberActions: event.MemberFeatureMap{ + event.MemberActionInvite: event.CapLevelFullySupported, + event.MemberActionKick: event.CapLevelFullySupported, + event.MemberActionLeave: event.CapLevelFullySupported, + }, MaxTextLength: MaxTextLength, LocationMessage: event.CapLevelFullySupported, Poll: event.CapLevelFullySupported, @@ -179,9 +191,16 @@ var whatsappCaps = &event.RoomFeatures{ DeleteChat: true, } +var whatsappDMCaps *event.RoomFeatures var whatsappCAGCaps *event.RoomFeatures func init() { + whatsappDMCaps = ptr.Clone(whatsappCaps) + whatsappDMCaps.ID = capID() + "+dm" + whatsappDMCaps.State = event.StateFeatureMap{ + event.StateBeeperDisappearingTimer.Type: {Level: event.CapLevelFullySupported}, + } + whatsappDMCaps.MemberActions = nil whatsappCAGCaps = ptr.Clone(whatsappCaps) whatsappCAGCaps.ID = capID() + "+cag" whatsappCAGCaps.Reply = event.CapLevelUnsupported @@ -191,6 +210,8 @@ func init() { func (wa *WhatsAppClient) GetCapabilities(ctx context.Context, portal *bridgev2.Portal) *event.RoomFeatures { if portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup { return whatsappCAGCaps + } else if portal.RoomType == database.RoomTypeDM { + return whatsappDMCaps } return whatsappCaps } From ddc6f504a5369e989a7d81dfcfd45b8703764378 Mon Sep 17 00:00:00 2001 From: Vaibhav Raj <68665948+dead8309@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:39:06 +0530 Subject: [PATCH 195/486] login: wrap ratelimit errors from whatsapp (#852) --- pkg/connector/login.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/connector/login.go b/pkg/connector/login.go index 9614e333..88e7b758 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -74,6 +74,11 @@ var ( Err: "Phone number must be in international format", StatusCode: http.StatusBadRequest, } + ErrRateLimitedByWhatsApp = bridgev2.RespError{ + ErrCode: "FI.MAU.WHATSAPP.RATE_LIMITED", + Err: "Rate limited by WhatsApp", + StatusCode: http.StatusTooManyRequests, + } ) func (wa *WhatsAppConnector) CreateLogin(_ context.Context, user *bridgev2.User, flowID string) (bridgev2.LoginProcess, error) { @@ -196,6 +201,8 @@ func (wl *WALogin) SubmitUserInput(ctx context.Context, input map[string]string) return nil, ErrPhoneNumberTooShort } else if errors.Is(err, whatsmeow.ErrPhoneNumberIsNotInternational) { return nil, ErrPhoneNumberIsNotInternational + } else if errors.Is(err, whatsmeow.ErrIQRateOverLimit) { + return nil, ErrRateLimitedByWhatsApp } return nil, err } From 1b24b61c0ae22e2dada56877e1eb06ede6d240fa Mon Sep 17 00:00:00 2001 From: Rajeh Taher Date: Tue, 28 Oct 2025 15:17:51 +0200 Subject: [PATCH 196/486] msgconv,handewhatsapp: Bridge "Live photos" as GIFs (#845) Co-authored-by: Tulir Asokan --- pkg/connector/handlewhatsapp.go | 2 ++ pkg/msgconv/wa-media.go | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 4a2c04e3..17324ccf 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -316,6 +316,8 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa evt.Message = &waE2E.Message{ ProtocolMessage: protocolMsg, } + } else if assocType == waE2E.MessageAssociation_MOTION_PHOTO { + evt.Message = evt.Message.GetAssociatedChildMessage().GetMessage() } parsedMessageType := getMessageType(evt.Message) diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index ed1dc8dc..9a1ceb3d 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -302,7 +302,8 @@ func prepareMediaMessage(rawMsg MediaMessage) *PreparedMedia { } case *waE2E.VideoMessage: data.MsgType = event.MsgVideo - if msg.GetGifPlayback() { + pairedMediaType := msg.GetContextInfo().GetPairedMediaType() + if msg.GetGifPlayback() || pairedMediaType == waE2E.ContextInfo_MOTION_PHOTO_PARENT || pairedMediaType == waE2E.ContextInfo_MOTION_PHOTO_CHILD { extraInfo["fi.mau.gif"] = true extraInfo["fi.mau.loop"] = true extraInfo["fi.mau.autoplay"] = true From f57f78b705670ad2ddb51ba2ca1d05efff2ae1e7 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 28 Oct 2025 15:09:42 +0200 Subject: [PATCH 197/486] 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 2683e5bc..0f59234e 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( golang.org/x/sync v0.17.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.3-0.20251027163910-adc035b6a555 + maunium.net/go/mautrix v0.25.3-0.20251028130646-bea28c1381cd ) require ( diff --git a/go.sum b/go.sum index bf392fbf..14068a9e 100644 --- a/go.sum +++ b/go.sum @@ -111,5 +111,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.25.3-0.20251027163910-adc035b6a555 h1:6D/kRJyT+5+RRnJErNNglVwDMYU6EwQN0bRYqcdkfZw= -maunium.net/go/mautrix v0.25.3-0.20251027163910-adc035b6a555/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= +maunium.net/go/mautrix v0.25.3-0.20251028130646-bea28c1381cd h1:4OfgnwTd71vgHGc+kBwhWsb92ePZVvsDbyTLJiy+PKU= +maunium.net/go/mautrix v0.25.3-0.20251028130646-bea28c1381cd/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= From 3531a96bd47390b929fd836f2053d03e810d22db Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 28 Oct 2025 17:08:13 +0200 Subject: [PATCH 198/486] userinfo: always fetch alt jid contact when updating user info --- go.mod | 2 +- go.sum | 4 +-- pkg/connector/handlewhatsapp.go | 4 +++ pkg/connector/userinfo.go | 43 +++++++++++++++++++-------------- 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 0f59234e..2e100807 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.2 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251027141726-3d82d3101dd1 + go.mau.fi/whatsmeow v0.0.0-20251028144453-ba0d4eb09a00 golang.org/x/image v0.32.0 golang.org/x/net v0.46.0 golang.org/x/sync v0.17.0 diff --git a/go.sum b/go.sum index 14068a9e..577ac964 100644 --- a/go.sum +++ b/go.sum @@ -79,8 +79,8 @@ go.mau.fi/util v0.9.2 h1:+S4Z03iCsGqU2WY8X2gySFsFjaLlUHFRDVCYvVwynKM= go.mau.fi/util v0.9.2/go.mod h1:055elBBCJSdhRsmub7ci9hXZPgGr1U6dYg44cSgRgoU= 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-20251027141726-3d82d3101dd1 h1:xjTxq7OfIV+oVJnsWrXX5lrIt8cSMV6yHppdp2+fVQE= -go.mau.fi/whatsmeow v0.0.0-20251027141726-3d82d3101dd1/go.mod h1:RwBrMQAWCHGzMdDZ6EwjcY4Aj3g8Efx8c7GACTdiAME= +go.mau.fi/whatsmeow v0.0.0-20251028144453-ba0d4eb09a00 h1:dbr9L/PbazTXwuKyrwQepy6+NpDBd3GhrpmTWfWIZSo= +go.mau.fi/whatsmeow v0.0.0-20251028144453-ba0d4eb09a00/go.mod h1:RwBrMQAWCHGzMdDZ6EwjcY4Aj3g8Efx8c7GACTdiAME= 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.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 17324ccf..5f71a64f 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -150,6 +150,10 @@ 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()) + if err != nil { + log.Err(err).Msg("Failed to update push name in store") + } go wa.syncGhost(wa.JID.ToNonAD(), "push name setting", nil) case *events.Contact: go wa.syncGhost(evt.JID, "contact event", nil) diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 5af33630..6fbb5658 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -194,29 +194,30 @@ func (wa *WhatsAppClient) contactToUserInfo(ctx context.Context, jid types.JID, } else if jid == types.LegacyPSAJID || jid == types.PSAJID { contact.PushName = "WhatsApp" } - var phone string - if jid.Server == types.DefaultUserServer { - phone = "+" + jid.User - } else if jid.Server == types.HiddenUserServer { - pnJID, err := wa.GetStore().LIDs.GetPNForLID(ctx, jid) + var altJID types.JID + if jid.Server == types.DefaultUserServer || jid.Server == types.HiddenUserServer { + var err error + altJID, err = wa.GetStore().GetAltJID(ctx, jid) if err != nil { - zerolog.Ctx(ctx).Err(err).Stringer("lid", jid).Msg("Failed to get PN for LID") - } else if pnJID.IsEmpty() { - zerolog.Ctx(ctx).Debug().Stringer("lid", jid).Msg("Phone number not found for LID in contactToUserInfo") + zerolog.Ctx(ctx).Err(err).Stringer("source_jid", jid).Msg("Failed to get alt JID") + } else if altJID.IsEmpty() { + zerolog.Ctx(ctx).Debug().Stringer("source_jid", jid).Msg("Alternate JID not found in contactToUserInfo") } else { - phone = "+" + pnJID.User - extraContact, err := wa.GetStore().Contacts.GetContact(ctx, pnJID) + extraContact, err := wa.GetStore().Contacts.GetContact(ctx, altJID) if err != nil { zerolog.Ctx(ctx).Err(err). - Stringer("lid", jid). - Stringer("pn_jid", pnJID). - Msg("Failed to get contact info from PN") + Stringer("source_jid", jid). + Stringer("alt_jid", altJID). + Msg("Failed to get contact info from alternate JID") } else { - if contact.FirstName == "" { - contact.FirstName = extraContact.FirstName - } - if contact.FullName == "" { - contact.FullName = extraContact.FullName + // Phone contact info should only be stored for phone number JIDs + if altJID.Server == types.DefaultUserServer { + if contact.FirstName == "" { + contact.FirstName = extraContact.FirstName + } + if contact.FullName == "" { + contact.FullName = extraContact.FullName + } } if contact.PushName == "" { contact.PushName = extraContact.PushName @@ -227,6 +228,12 @@ func (wa *WhatsAppClient) contactToUserInfo(ctx context.Context, jid types.JID, } } } + var phone string + if jid.Server == types.DefaultUserServer { + phone = "+" + jid.User + } else if altJID.Server == types.DefaultUserServer { + phone = "+" + altJID.User + } ui := &bridgev2.UserInfo{ Name: ptr.Ptr(wa.Main.Config.FormatDisplayname(jid, phone, contact)), IsBot: ptr.Ptr(jid.IsBot()), From 1f65164cf32a20a61dc21ecf47ef023350fb2366 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 28 Oct 2025 18:14:23 +0200 Subject: [PATCH 199/486] userinfo: add debug log if lid and pn contact info don't agree --- pkg/connector/userinfo.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 6fbb5658..aa849384 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -225,6 +225,28 @@ func (wa *WhatsAppClient) contactToUserInfo(ctx context.Context, jid types.JID, if contact.BusinessName == "" { contact.BusinessName = extraContact.BusinessName } + if contact.PushName != "" && extraContact.PushName != "" && contact.PushName != extraContact.PushName { + zerolog.Ctx(ctx).Debug(). + Stringer("source_jid", jid). + Stringer("alt_jid", altJID). + Str("source_push_name", contact.PushName). + Str("alt_push_name", extraContact.PushName). + Msg("Conflicting push names between JIDs") + if altJID.Server == types.DefaultUserServer { + contact.PushName = extraContact.PushName + } + } + if contact.BusinessName != "" && extraContact.BusinessName != "" && contact.BusinessName != extraContact.BusinessName { + zerolog.Ctx(ctx).Debug(). + Stringer("source_jid", jid). + Stringer("alt_jid", altJID). + Str("source_push_name", contact.BusinessName). + Str("alt_push_name", extraContact.BusinessName). + Msg("Conflicting business names between JIDs") + if altJID.Server == types.DefaultUserServer { + contact.BusinessName = extraContact.BusinessName + } + } } } } From 040456f34621f740baa654a575a9f12a86f19398 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 28 Oct 2025 18:51:55 +0200 Subject: [PATCH 200/486] 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 2e100807..7039778d 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.2 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251028144453-ba0d4eb09a00 + go.mau.fi/whatsmeow v0.0.0-20251028165006-ad7a618ba42f golang.org/x/image v0.32.0 golang.org/x/net v0.46.0 golang.org/x/sync v0.17.0 diff --git a/go.sum b/go.sum index 577ac964..7ca494b0 100644 --- a/go.sum +++ b/go.sum @@ -79,8 +79,8 @@ go.mau.fi/util v0.9.2 h1:+S4Z03iCsGqU2WY8X2gySFsFjaLlUHFRDVCYvVwynKM= go.mau.fi/util v0.9.2/go.mod h1:055elBBCJSdhRsmub7ci9hXZPgGr1U6dYg44cSgRgoU= 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-20251028144453-ba0d4eb09a00 h1:dbr9L/PbazTXwuKyrwQepy6+NpDBd3GhrpmTWfWIZSo= -go.mau.fi/whatsmeow v0.0.0-20251028144453-ba0d4eb09a00/go.mod h1:RwBrMQAWCHGzMdDZ6EwjcY4Aj3g8Efx8c7GACTdiAME= +go.mau.fi/whatsmeow v0.0.0-20251028165006-ad7a618ba42f h1:UfzKgeEBRlDj3E2B/z+no17BstkAxO4kIUNSgR6Cwrw= +go.mau.fi/whatsmeow v0.0.0-20251028165006-ad7a618ba42f/go.mod h1:RwBrMQAWCHGzMdDZ6EwjcY4Aj3g8Efx8c7GACTdiAME= 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.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= From 25ea0d986a0b3d6cc4eb77228d31ed438bdfb0b1 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 29 Oct 2025 14:53:40 +0200 Subject: [PATCH 201/486] handlewhatsapp: ignore motion photos for now --- pkg/connector/handlewhatsapp.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 5f71a64f..621caf05 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -321,7 +321,12 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa ProtocolMessage: protocolMsg, } } else if assocType == waE2E.MessageAssociation_MOTION_PHOTO { - evt.Message = evt.Message.GetAssociatedChildMessage().GetMessage() + //evt.Message = evt.Message.GetAssociatedChildMessage().GetMessage() + wa.UserLogin.Log.Debug(). + Str("message_id", evt.Info.ID). + Str("parent_id", messageAssoc.GetParentMessageKey().GetID()). + Msg("Ignoring motion photo update") + return } parsedMessageType := getMessageType(evt.Message) From 22e60277763a4dc8911ee8b138ef86080dc70270 Mon Sep 17 00:00:00 2001 From: Conan Date: Fri, 31 Oct 2025 00:13:03 +0800 Subject: [PATCH 202/486] startchat: normalize identifiers to phone numbers when creating group (#855) --- pkg/connector/startchat.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index d11c8304..22d4ad8c 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -217,7 +217,13 @@ func (wa *WhatsAppClient) CreateGroup(ctx context.Context, params *bridgev2.Grou CreateKey: createKey, } for i, participant := range params.Participants { - req.Participants[i] = waid.ParseUserID(participant) + jid := waid.ParseUserID(participant) + // Normalize to PN if it's a LID + jid, err := wa.startChatLIDToPN(ctx, jid) + if err != nil { + return nil, fmt.Errorf("failed to normalize participant %s: %w", participant, err) + } + req.Participants[i] = jid } if params.Parent != nil { var err error From e4ea06f68eff8e8e142b59e7238930bb12399a11 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 6 Nov 2025 17:38:05 +0100 Subject: [PATCH 203/486] dependencies: update whatsmeow --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/connector.go | 9 +++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 7039778d..37fbaeff 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.2 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251028165006-ad7a618ba42f + go.mau.fi/whatsmeow v0.0.0-20251106163046-720bd0b4a715 golang.org/x/image v0.32.0 golang.org/x/net v0.46.0 golang.org/x/sync v0.17.0 diff --git a/go.sum b/go.sum index 7ca494b0..9b8920a7 100644 --- a/go.sum +++ b/go.sum @@ -79,8 +79,8 @@ go.mau.fi/util v0.9.2 h1:+S4Z03iCsGqU2WY8X2gySFsFjaLlUHFRDVCYvVwynKM= go.mau.fi/util v0.9.2/go.mod h1:055elBBCJSdhRsmub7ci9hXZPgGr1U6dYg44cSgRgoU= 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-20251028165006-ad7a618ba42f h1:UfzKgeEBRlDj3E2B/z+no17BstkAxO4kIUNSgR6Cwrw= -go.mau.fi/whatsmeow v0.0.0-20251028165006-ad7a618ba42f/go.mod h1:RwBrMQAWCHGzMdDZ6EwjcY4Aj3g8Efx8c7GACTdiAME= +go.mau.fi/whatsmeow v0.0.0-20251106163046-720bd0b4a715 h1:JxVirSDFmhhDEv3LmXW8ybwHAKV51Izz3burUg81w2o= +go.mau.fi/whatsmeow v0.0.0-20251106163046-720bd0b4a715/go.mod h1:RwBrMQAWCHGzMdDZ6EwjcY4Aj3g8Efx8c7GACTdiAME= 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.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 4e50ca51..25a0707a 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -120,11 +120,12 @@ func (wa *WhatsAppConnector) Init(bridge *bridgev2.Bridge) { store.DeviceProps.Os = proto.String(wa.Config.OSName) store.DeviceProps.RequireFullSync = proto.Bool(wa.Config.HistorySync.RequestFullSync) if fsc := wa.Config.HistorySync.FullSyncConfig; fsc.DaysLimit > 0 && fsc.SizeLimit > 0 && fsc.StorageQuota > 0 { - store.DeviceProps.HistorySyncConfig = &waCompanionReg.DeviceProps_HistorySyncConfig{ - FullSyncDaysLimit: proto.Uint32(fsc.DaysLimit), - FullSyncSizeMbLimit: proto.Uint32(fsc.SizeLimit), - StorageQuotaMb: proto.Uint32(fsc.StorageQuota), + if store.DeviceProps.HistorySyncConfig == nil { + store.DeviceProps.HistorySyncConfig = &waCompanionReg.DeviceProps_HistorySyncConfig{} } + store.DeviceProps.HistorySyncConfig.FullSyncDaysLimit = proto.Uint32(fsc.DaysLimit) + store.DeviceProps.HistorySyncConfig.FullSyncSizeMbLimit = proto.Uint32(fsc.SizeLimit) + store.DeviceProps.HistorySyncConfig.StorageQuotaMb = proto.Uint32(fsc.StorageQuota) } platformID, ok := waCompanionReg.DeviceProps_PlatformType_value[strings.ToUpper(wa.Config.BrowserName)] if ok { From cba1704046a7842f9833004b86b7d831cf719144 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 8 Nov 2025 23:19:12 +0200 Subject: [PATCH 204/486] handlewhatsapp: reroute LID typing notifications and fix receipts Fixes #860 --- pkg/connector/handlewhatsapp.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 621caf05..9bac6ad5 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -399,6 +399,9 @@ func (wa *WhatsAppClient) handleWAUndecryptableMessage(ctx context.Context, evt } func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Receipt) (success bool) { + if evt.Chat.Server == types.HiddenUserServer && evt.SenderAlt.IsEmpty() { + evt.SenderAlt, _ = wa.GetStore().LIDs.GetPNForLID(ctx, evt.Sender) + } if evt.Chat.Server == types.HiddenUserServer && evt.Sender.ToNonAD() == evt.Chat && evt.SenderAlt.Server == types.DefaultUserServer { wa.UserLogin.Log.Debug(). Stringer("lid", evt.Sender). @@ -455,6 +458,15 @@ 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.SenderAlt.IsEmpty() { + evt.SenderAlt, _ = wa.GetStore().LIDs.GetPNForLID(ctx, evt.Sender) + } + if evt.SenderAlt.Server == types.DefaultUserServer { + evt.Sender, evt.SenderAlt = evt.SenderAlt, evt.Sender + evt.Chat = evt.Sender.ToNonAD() + } + } typingType := bridgev2.TypingTypeText timeout := 15 * time.Second if evt.Media == types.ChatPresenceMediaAudio { From 3b59a4627723fbc087d7c450c7c3466b26b2521e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 9 Nov 2025 11:27:39 +0200 Subject: [PATCH 205/486] msgconv/from-whatsapp: reroute DM reply participants to phone numbers --- pkg/msgconv/from-whatsapp.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index c4be4a46..80b92ed6 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -257,6 +257,13 @@ 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 + if store := getClient(ctx).Store; store != nil && chat.Server == types.DefaultUserServer { + pcpPN, _ := store.LIDs.GetPNForLID(ctx, pcp) + if !pcpPN.IsEmpty() { + pcp = pcpPN + } + } cm.ReplyTo = &networkid.MessageOptionalPartID{ MessageID: waid.MakeMessageID(chat, pcp, contextInfo.GetStanzaID()), } From ec1229a4cb3e3402736f84e8d6162ab046d3f1fe Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 9 Nov 2025 11:41:37 +0200 Subject: [PATCH 206/486] 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 37fbaeff..bb2aa341 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( golang.org/x/sync v0.17.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.3-0.20251028130646-bea28c1381cd + maunium.net/go/mautrix v0.25.3-0.20251109094010-14e16a3a8190 ) require ( diff --git a/go.sum b/go.sum index 9b8920a7..6bdffa02 100644 --- a/go.sum +++ b/go.sum @@ -111,5 +111,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.25.3-0.20251028130646-bea28c1381cd h1:4OfgnwTd71vgHGc+kBwhWsb92ePZVvsDbyTLJiy+PKU= -maunium.net/go/mautrix v0.25.3-0.20251028130646-bea28c1381cd/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= +maunium.net/go/mautrix v0.25.3-0.20251109094010-14e16a3a8190 h1:5v25ZS99ilLXHjYX3uMiyHGBNhFPXgOgGfr9GkakRTg= +maunium.net/go/mautrix v0.25.3-0.20251109094010-14e16a3a8190/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= From b4f3a1f49233abb7c6e81a6be465ea952c67483e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 9 Nov 2025 13:35:51 +0200 Subject: [PATCH 207/486] msgconv/from-whatsapp: update reply participant rerouting --- pkg/msgconv/from-whatsapp.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 80b92ed6..a1156bc5 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -258,11 +258,24 @@ func (mc *MessageConverter) ToMatrix( chat, _ = waid.ParsePortalID(portal.ID) } // We reroute all DMs to the phone number JID, so reroute reply participants too - if store := getClient(ctx).Store; store != nil && chat.Server == types.DefaultUserServer { + 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()), From 5ef54de8f04cb70b7192eeafd150120b26c39c2f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 10 Nov 2025 13:08:50 +0200 Subject: [PATCH 208/486] 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 bb2aa341..de830cc6 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.2 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251106163046-720bd0b4a715 + go.mau.fi/whatsmeow v0.0.0-20251110110826-a121e2b9cd1e golang.org/x/image v0.32.0 golang.org/x/net v0.46.0 golang.org/x/sync v0.17.0 diff --git a/go.sum b/go.sum index 6bdffa02..992979e9 100644 --- a/go.sum +++ b/go.sum @@ -79,8 +79,8 @@ go.mau.fi/util v0.9.2 h1:+S4Z03iCsGqU2WY8X2gySFsFjaLlUHFRDVCYvVwynKM= go.mau.fi/util v0.9.2/go.mod h1:055elBBCJSdhRsmub7ci9hXZPgGr1U6dYg44cSgRgoU= 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-20251106163046-720bd0b4a715 h1:JxVirSDFmhhDEv3LmXW8ybwHAKV51Izz3burUg81w2o= -go.mau.fi/whatsmeow v0.0.0-20251106163046-720bd0b4a715/go.mod h1:RwBrMQAWCHGzMdDZ6EwjcY4Aj3g8Efx8c7GACTdiAME= +go.mau.fi/whatsmeow v0.0.0-20251110110826-a121e2b9cd1e h1:I93PJ14VjWNTJ2BVm2jT9WQ60fMDNmGng6qZs2CzYpg= +go.mau.fi/whatsmeow v0.0.0-20251110110826-a121e2b9cd1e/go.mod h1:RwBrMQAWCHGzMdDZ6EwjcY4Aj3g8Efx8c7GACTdiAME= 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.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= From 6be815e6f584ab73469e6a35be90178b89439f8a Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Fri, 31 Oct 2025 12:03:12 +0000 Subject: [PATCH 209/486] connector: re-request media when no URL present in current message --- pkg/connector/directmedia.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index c5d51ea9..2b289b81 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -170,7 +170,7 @@ func (wa *WhatsAppConnector) downloadMessageDirectMedia(ctx context.Context, par return &mediaproxy.GetMediaResponseFile{ Callback: func(f *os.File) error { err := waClient.Client.DownloadToFile(ctx, keys, f) - if errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith403) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith404) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith410) { + if 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 ErrReloadNeeded From 69622b6ee75b2a40f424433737437001d81999dd Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 11 Nov 2025 01:45:47 +0200 Subject: [PATCH 210/486] changelog: update --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e65fefa..b1a81a38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +# v25.11 (unreleased) + +* Added interface support for notifying about failed invites when creating a + group and sending the invites via DM (only applicable to provisioning API). +* Changed contact list API to only include actual phone contacts. +* Removed extra unrecognized message notice when receiving live photos + (bridging the live photo video is not currently planned). +* Fixed pairing not working with latest WhatsApp Android version. +* Fixed replies, read receipts and typing notifications not being bridged + correctly after DM LID migration. +* Fixed some cases of LID and phone number user infos getting out of sync. +* Fixed muting chat forever not being bridged correctly from WhatsApp. +* Fixed space not being deleted when leaving community on WhatsApp. +* Fixed sticker size metadata on Matrix not matching how native WhatsApp Web + renders them. +* Fixed ratelimit errors in login not being exposed to the user properly + (thanks to [@dead8309] in [#852]). + +[@dead8309]: https://github.com/dead8309 +[#852]: https://github.com/mautrix/whatsapp/pull/852 + # v25.10 * Switched to calendar versioning. From ba9f5c61c19f022cd62387c328684c8ab0721fc1 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 11 Nov 2025 18:22:25 +0200 Subject: [PATCH 211/486] handle*: fix more DM LID rerouting issues --- pkg/connector/events.go | 10 +++++-- pkg/connector/handlematrix.go | 2 +- pkg/connector/handlewhatsapp.go | 48 +++++++++++++++------------------ pkg/msgconv/from-whatsapp.go | 1 + pkg/msgconv/wa-poll.go | 48 ++++++++++++++++++++++++++++----- 5 files changed, 73 insertions(+), 36 deletions(-) diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 87916724..35fded4f 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -209,9 +209,15 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por func (evt *WAMessageEvent) GetTargetMessage() networkid.MessageID { if reactionMsg := evt.Message.GetReactionMessage(); reactionMsg != nil { - return msgconv.KeyToMessageID(evt.wa.Client, evt.Info.Chat, evt.Info.Sender, reactionMsg.GetKey()) + ctx := evt.wa.UserLogin.Log. + With().Str("action", "get reaction target message").Str("message_id", evt.Info.ID).Logger(). + WithContext(evt.wa.Main.Bridge.BackgroundCtx) + return msgconv.KeyToMessageID(ctx, evt.wa.Client, evt.Info.Chat, evt.Info.Sender, reactionMsg.GetKey()) } else if protocolMsg := evt.Message.GetProtocolMessage(); protocolMsg != nil { - return msgconv.KeyToMessageID(evt.wa.Client, evt.Info.Chat, evt.Info.Sender, protocolMsg.GetKey()) + ctx := evt.wa.UserLogin.Log. + With().Str("action", "get edit target message").Str("message_id", evt.Info.ID).Logger(). + WithContext(evt.wa.Main.Bridge.BackgroundCtx) + return msgconv.KeyToMessageID(ctx, evt.wa.Client, evt.Info.Chat, evt.Info.Sender, protocolMsg.GetKey()) } return "" } diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index 38ea4afd..3abc38e1 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -112,7 +112,7 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg return nil, err } var pickedMessageID networkid.MessageID - if resp.Sender == wa.GetStore().GetLID() { + if resp.Sender == wa.GetStore().GetLID() && chatJID.Server != types.DefaultUserServer { pickedMessageID = wrappedMsgID2 msg.RemovePending(networkid.TransactionID(wrappedMsgID)) } else { diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 9bac6ad5..d362de2e 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -251,12 +251,16 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { return } -func (wa *WhatsAppClient) rerouteWAMessage(ctx context.Context, info *types.MessageInfo) { +func (wa *WhatsAppClient) rerouteWAMessage(ctx context.Context, evtType string, info *types.MessageSource, msgID any) { + if info.Chat.Server == types.HiddenUserServer && info.SenderAlt.IsEmpty() { + info.SenderAlt, _ = wa.GetStore().LIDs.GetPNForLID(ctx, info.Sender) + } 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). - Str("message_id", info.ID). + 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() @@ -264,21 +268,31 @@ func (wa *WhatsAppClient) rerouteWAMessage(ctx context.Context, info *types.Mess wa.UserLogin.Log.Debug(). Stringer("lid", info.Chat). Stringer("pn", info.RecipientAlt). - Str("message_id", info.ID). + 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.Device.GetJID() + info.Sender.Device = info.SenderAlt.Device + } + } } else if info.Sender.Server == types.BotServer && info.Chat.Server == types.HiddenUserServer { chatPN, err := wa.GetStore().LIDs.GetPNForLID(ctx, info.Chat) if err != nil { wa.UserLogin.Log.Err(err). - Str("message_id", info.ID). + 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). - Str("message_id", info.ID). + Any("message_id", msgID). + Str("evt_type", evtType). Msg("Forced LID chat to phone number in bot message") info.Chat = chatPN } @@ -287,7 +301,7 @@ func (wa *WhatsAppClient) rerouteWAMessage(ctx context.Context, info *types.Mess func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Message) (success bool) { success = true - wa.rerouteWAMessage(ctx, &evt.Info) + wa.rerouteWAMessage(ctx, "message", &evt.Info.MessageSource, evt.Info.ID) wa.UserLogin.Log.Trace(). Any("info", evt.Info). Any("payload", evt.Message). @@ -375,7 +389,7 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa } func (wa *WhatsAppClient) handleWAUndecryptableMessage(ctx context.Context, evt *events.UndecryptableMessage) bool { - wa.rerouteWAMessage(ctx, &evt.Info) + wa.rerouteWAMessage(ctx, "undecryptable message", &evt.Info.MessageSource, evt.Info.ID) wa.UserLogin.Log.Debug(). Any("info", evt.Info). Bool("unavailable", evt.IsUnavailable). @@ -399,25 +413,7 @@ func (wa *WhatsAppClient) handleWAUndecryptableMessage(ctx context.Context, evt } func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Receipt) (success bool) { - if evt.Chat.Server == types.HiddenUserServer && evt.SenderAlt.IsEmpty() { - evt.SenderAlt, _ = wa.GetStore().LIDs.GetPNForLID(ctx, evt.Sender) - } - if evt.Chat.Server == types.HiddenUserServer && evt.Sender.ToNonAD() == evt.Chat && evt.SenderAlt.Server == types.DefaultUserServer { - wa.UserLogin.Log.Debug(). - Stringer("lid", evt.Sender). - Stringer("pn", evt.SenderAlt). - Strs("message_id", evt.MessageIDs). - Msg("Forced LID DM sender to phone number in incoming receipt") - evt.Sender, evt.SenderAlt = evt.SenderAlt, evt.Sender - evt.Chat = evt.Sender.ToNonAD() - } else if evt.Chat.Server == types.HiddenUserServer && evt.IsFromMe && evt.RecipientAlt.Server == types.DefaultUserServer { - wa.UserLogin.Log.Debug(). - Stringer("lid", evt.Chat). - Stringer("pn", evt.RecipientAlt). - Strs("message_id", evt.MessageIDs). - Msg("Forced LID DM sender to phone number in own receipt sent from another device") - evt.Chat = evt.RecipientAlt.ToNonAD() - } + wa.rerouteWAMessage(ctx, "receipt", &evt.MessageSource, evt.MessageIDs) if evt.IsFromMe && evt.Sender.Device == 0 { wa.phoneSeen(evt.Timestamp) } diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index a1156bc5..c1f36ce4 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -258,6 +258,7 @@ func (mc *MessageConverter) ToMatrix( 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(). diff --git a/pkg/msgconv/wa-poll.go b/pkg/msgconv/wa-poll.go index 97a75e2e..6dac2d65 100644 --- a/pkg/msgconv/wa-poll.go +++ b/pkg/msgconv/wa-poll.go @@ -24,7 +24,6 @@ import ( "strings" "github.com/rs/zerolog" - "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waCommon" "go.mau.fi/whatsmeow/proto/waE2E" @@ -95,7 +94,31 @@ func (mc *MessageConverter) convertPollCreationMessage(ctx context.Context, msg }, msg.GetContextInfo() } -func KeyToMessageID(client *whatsmeow.Client, chat, sender types.JID, key *waCommon.MessageKey) networkid.MessageID { +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() { @@ -109,14 +132,21 @@ func KeyToMessageID(client *whatsmeow.Client, chat, sender types.JID, key *waCom sender.Server = types.DefaultUserServer } } else if chat.Server == types.DefaultUserServer || chat.Server == types.BotServer { - ownID := ptr.Val(client.Store.ID).ToNonAD() - if sender.User == ownID.User { + 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 sender = chat } else { - sender = ownID + // 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() } } else { - // TODO log somehow? + zerolog.Ctx(ctx).Warn(). + Stringer("chat", chat). + Stringer("sender", sender). + Any("key", key). + Msg("Failed to get message ID from key") return "" } } @@ -127,6 +157,10 @@ func KeyToMessageID(client *whatsmeow.Client, chat, sender types.JID, key *waCom chat = remoteJID } } + sender = rerouteMessageKey( + context.WithValue(ctx, contextKeyClient, client), + chat, sender, groupLIDAddressing, + ) return waid.MakeMessageID(chat, sender, key.GetID()) } @@ -138,7 +172,7 @@ var failedPollUpdatePart = &bridgev2.ConvertedMessagePart{ func (mc *MessageConverter) convertPollUpdateMessage(ctx context.Context, info *types.MessageInfo, msg *waE2E.PollUpdateMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { log := zerolog.Ctx(ctx) - pollMessageID := KeyToMessageID(getClient(ctx), info.Chat, info.Sender, msg.PollCreationMessageKey) + pollMessageID := KeyToMessageID(ctx, getClient(ctx), info.Chat, info.Sender, msg.PollCreationMessageKey) pollMessage, err := mc.Bridge.DB.Message.GetPartByID(ctx, getPortal(ctx).Receiver, pollMessageID, "") if err != nil { log.Err(err).Msg("Failed to get poll update target message") From a291d40d66555883b9165458157f2f00f61b587e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 11 Nov 2025 19:13:33 +0200 Subject: [PATCH 212/486] backfill: reroute LID DMs in history syncs --- pkg/connector/backfill.go | 14 ++++++++++++++ pkg/connector/handlewhatsapp.go | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 2c3a14e4..dcc4bca3 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -193,6 +193,20 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor log.Debug().Stringer("chat_jid", jid).Msg("Skipping broadcast list in history sync") continue } + if jid.Server == types.HiddenUserServer { + pn, err := wa.GetStore().LIDs.GetPNForLID(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") + } else { + log.Debug(). + Stringer("lid", jid). + Stringer("pn", pn). + Msg("Rerouting LID DM to phone number in history sync") + jid = pn + } + } totalMessageCount += len(conv.GetMessages()) log := log.With(). Stringer("chat_jid", jid). diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index d362de2e..a5426949 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -275,7 +275,7 @@ func (wa *WhatsAppClient) rerouteWAMessage(ctx context.Context, evtType string, if info.Sender.Server == types.HiddenUserServer { info.Sender, info.SenderAlt = info.SenderAlt, info.Sender if info.Sender.IsEmpty() { - info.Sender = wa.Device.GetJID() + info.Sender = wa.GetStore().GetJID() info.Sender.Device = info.SenderAlt.Device } } From 88a7a2b8d7d04e1b3a380aa7f3c2b8ad580fbbaf Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 13 Nov 2025 15:03:59 +0200 Subject: [PATCH 213/486] chatinfo: don't apply history sync metadata on later resyncs --- pkg/connector/backfill.go | 2 +- pkg/connector/chatinfo.go | 26 ++++++++++++++------------ pkg/connector/commands.go | 11 +++++++---- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index dcc4bca3..5d2e9ae5 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -356,7 +356,7 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { log.Warn().Err(ctx.Err()).Msg("Context cancelled, stopping history sync portal creation") return } - wrappedInfo, err := wa.getChatInfo(ctx, conv.ChatJID, conv) + wrappedInfo, err := wa.getChatInfo(ctx, conv.ChatJID, conv, true) if errors.Is(err, whatsmeow.ErrNotInGroup) { log.Debug().Stringer("chat_jid", conv.ChatJID). Msg("Skipping creating room because the user is not a participant") diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index c3545f53..55555665 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -26,10 +26,10 @@ func (wa *WhatsAppClient) GetChatInfo(ctx context.Context, portal *bridgev2.Port if err != nil { return nil, err } - return wa.getChatInfo(ctx, portalJID, nil) + return wa.getChatInfo(ctx, portalJID, nil, portal.MXID == "") } -func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, conv *wadb.Conversation) (wrapped *bridgev2.ChatInfo, err error) { +func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, conv *wadb.Conversation, isNew bool) (wrapped *bridgev2.ChatInfo, err error) { switch portalJID.Server { case types.DefaultUserServer, types.HiddenUserServer, types.BotServer: wrapped = wa.wrapDMInfo(ctx, portalJID) @@ -55,20 +55,22 @@ func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, default: return nil, fmt.Errorf("unsupported server %s", portalJID.Server) } - wa.addExtrasToWrapped(ctx, portalJID, wrapped, conv) + wa.addExtrasToWrapped(ctx, portalJID, wrapped, conv, isNew) return wrapped, nil } -func (wa *WhatsAppClient) addExtrasToWrapped(ctx context.Context, portalJID types.JID, wrapped *bridgev2.ChatInfo, conv *wadb.Conversation) { - if conv == nil { - var err error - conv, err = wa.Main.DB.Conversation.Get(ctx, wa.UserLogin.ID, portalJID) - if err != nil { - zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to get history sync conversation info") +func (wa *WhatsAppClient) addExtrasToWrapped(ctx context.Context, portalJID types.JID, wrapped *bridgev2.ChatInfo, conv *wadb.Conversation, isNew bool) { + if isNew { + if conv == nil { + var err error + conv, err = wa.Main.DB.Conversation.Get(ctx, wa.UserLogin.ID, portalJID) + if err != nil { + zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to get history sync conversation info") + } + } + if conv != nil { + wa.applyHistoryInfo(wrapped, conv) } - } - if conv != nil { - wa.applyHistoryInfo(wrapped, conv) } wa.applyChatSettings(ctx, portalJID, wrapped) } diff --git a/pkg/connector/commands.go b/pkg/connector/commands.go index e86a6f9b..a0ebfcf3 100644 --- a/pkg/connector/commands.go +++ b/pkg/connector/commands.go @@ -17,6 +17,7 @@ package connector import ( + "context" "errors" "fmt" "html" @@ -120,9 +121,6 @@ func fnSync(ce *commands.Event) { return } for _, group := range groups { - wrapped := wa.wrapGroupInfo(ce.Ctx, group) - wrapped.ExtraUpdates = bridgev2.MergeExtraUpdaters(wrapped.ExtraUpdates, updatePortalLastSyncAt) - wa.addExtrasToWrapped(ce.Ctx, group.JID, wrapped, nil) login.QueueRemoteEvent(&simplevent.ChatResync{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatResync, @@ -130,7 +128,12 @@ func fnSync(ce *commands.Event) { LogContext: logContext, CreatePortal: true, }, - ChatInfo: wrapped, + GetChatInfoFunc: func(ctx context.Context, portal *bridgev2.Portal) (*bridgev2.ChatInfo, error) { + wrapped := wa.wrapGroupInfo(ce.Ctx, group) + wrapped.ExtraUpdates = bridgev2.MergeExtraUpdaters(wrapped.ExtraUpdates, updatePortalLastSyncAt) + wa.addExtrasToWrapped(ce.Ctx, group.JID, wrapped, nil, portal.MXID == "") + return wrapped, nil + }, }) } ce.Reply("Queued syncs for %d groups", len(groups)) From 10a8c64d63eb0c8b497c3536949d2fe722b71442 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 13 Nov 2025 18:09:26 +0200 Subject: [PATCH 214/486] backfill: fix panic when reaction is missing sender --- pkg/connector/backfill.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 5d2e9ae5..c9aa521f 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -548,10 +548,10 @@ func (wa *WhatsAppClient) convertHistorySyncMessage( TxnID: networkid.TransactionID(waid.MakeMessageID(info.Chat, info.Sender, info.ID)), Timestamp: info.Timestamp, StreamOrder: info.Timestamp.Unix(), - Reactions: make([]*bridgev2.BackfillReaction, len(reactions)), + Reactions: make([]*bridgev2.BackfillReaction, 0, len(reactions)), } mediaReq := wa.processFailedMedia(ctx, portal.PortalKey, wrapped.ID, wrapped.ConvertedMessage, true) - for i, reaction := range reactions { + for _, reaction := range reactions { var sender types.JID if reaction.GetKey().GetFromMe() { sender = wa.JID @@ -563,12 +563,12 @@ func (wa *WhatsAppClient) convertHistorySyncMessage( if sender.IsEmpty() { continue } - wrapped.Reactions[i] = &bridgev2.BackfillReaction{ + wrapped.Reactions = append(wrapped.Reactions, &bridgev2.BackfillReaction{ TargetPart: ptr.Ptr(networkid.PartID("")), Timestamp: time.UnixMilli(reaction.GetSenderTimestampMS()), Sender: wa.makeEventSender(ctx, sender), Emoji: reaction.GetText(), - } + }) } return wrapped, mediaReq } From 0f7582cf198f5d04e2c8cd4bb11630a3b64ffc74 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 14 Nov 2025 17:47:58 +0200 Subject: [PATCH 215/486] connector: add migration to delete LID DM portals --- pkg/connector/connector.go | 75 +++++++++++++++++++ .../wadb/upgrades/00-latest-schema.sql | 2 +- .../upgrades/08-may-need-lid-dm-deletion.sql | 2 + 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 pkg/connector/wadb/upgrades/08-may-need-lid-dm-deletion.sql diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 25a0707a..af23ed4d 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -25,6 +25,7 @@ import ( "sync/atomic" "github.com/lib/pq" + "github.com/rs/zerolog" "go.mau.fi/util/dbutil" "go.mau.fi/util/random" "go.mau.fi/whatsmeow" @@ -32,16 +33,19 @@ import ( "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 { @@ -149,9 +153,80 @@ 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") +} + func (wa *WhatsAppConnector) Stop() { if stop := wa.stopMediaEditCacheLoop.Swap(nil); stop != nil { (*stop)() diff --git a/pkg/connector/wadb/upgrades/00-latest-schema.sql b/pkg/connector/wadb/upgrades/00-latest-schema.sql index 97275f59..d9aeac9e 100644 --- a/pkg/connector/wadb/upgrades/00-latest-schema.sql +++ b/pkg/connector/wadb/upgrades/00-latest-schema.sql @@ -1,4 +1,4 @@ --- v0 -> v7 (compatible with v3+): Latest revision +-- v0 -> v8 (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 new file mode 100644 index 00000000..d5b4ca07 --- /dev/null +++ b/pkg/connector/wadb/upgrades/08-may-need-lid-dm-deletion.sql @@ -0,0 +1,2 @@ +-- v8 (compatible with v3+): Mark LID DMs for deletion +INSERT INTO kv_store (bridge_id, key, value) VALUES ('', 'whatsapp_lid_dms_deleted', 'false'); From 1caa0cb78d77ac15c90decc9bddc1fe86720ec18 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 15 Nov 2025 22:00:36 +0200 Subject: [PATCH 216/486] 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 de830cc6..d65e28c7 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.2 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251110110826-a121e2b9cd1e + go.mau.fi/whatsmeow v0.0.0-20251115195115-7159d9053646 golang.org/x/image v0.32.0 golang.org/x/net v0.46.0 golang.org/x/sync v0.17.0 diff --git a/go.sum b/go.sum index 992979e9..27ba0c9e 100644 --- a/go.sum +++ b/go.sum @@ -79,8 +79,8 @@ go.mau.fi/util v0.9.2 h1:+S4Z03iCsGqU2WY8X2gySFsFjaLlUHFRDVCYvVwynKM= go.mau.fi/util v0.9.2/go.mod h1:055elBBCJSdhRsmub7ci9hXZPgGr1U6dYg44cSgRgoU= 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-20251110110826-a121e2b9cd1e h1:I93PJ14VjWNTJ2BVm2jT9WQ60fMDNmGng6qZs2CzYpg= -go.mau.fi/whatsmeow v0.0.0-20251110110826-a121e2b9cd1e/go.mod h1:RwBrMQAWCHGzMdDZ6EwjcY4Aj3g8Efx8c7GACTdiAME= +go.mau.fi/whatsmeow v0.0.0-20251115195115-7159d9053646 h1:kPrIYSYxrvK2jJQPey7RXWLrP/c9psz58mZWD93BRao= +go.mau.fi/whatsmeow v0.0.0-20251115195115-7159d9053646/go.mod h1:RwBrMQAWCHGzMdDZ6EwjcY4Aj3g8Efx8c7GACTdiAME= 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.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= From f77169b4262bad8765e935b6a4bb0d3cec447a8b Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 16 Nov 2025 13:43:08 +0200 Subject: [PATCH 217/486] Bump version to v25.11 --- CHANGELOG.md | 8 +++++++- cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 22 ++++++++++---------- go.sum | 40 ++++++++++++++++++------------------ 4 files changed, 39 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1a81a38..2c199b19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,21 @@ -# v25.11 (unreleased) +# v25.11 * Added interface support for notifying about failed invites when creating a group and sending the invites via DM (only applicable to provisioning API). +* Added migration to automatically delete duplicate LID DM portals that were + created earlier. * Changed contact list API to only include actual phone contacts. * Removed extra unrecognized message notice when receiving live photos (bridging the live photo video is not currently planned). * Fixed pairing not working with latest WhatsApp Android version. * Fixed replies, read receipts and typing notifications not being bridged correctly after DM LID migration. +* Fixed backfill creating duplicate portals if history sync contains both LID + and phone number DM data. * Fixed some cases of LID and phone number user infos getting out of sync. * Fixed muting chat forever not being bridged correctly from WhatsApp. +* Fixed old mutes being re-applied on chat resync in some cases. +* Fixed backfilling failing if some reactions were missing sender info. * Fixed space not being deleted when leaving community on WhatsApp. * Fixed sticker size metadata on Matrix not matching how native WhatsApp Web renders them. diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index a3e06654..b71912fb 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: "25.10", + Version: "25.11", SemCalVer: true, Connector: &connector.WhatsAppConnector{}, } diff --git a/go.mod b/go.mod index d65e28c7..d03bb190 100644 --- a/go.mod +++ b/go.mod @@ -2,20 +2,20 @@ module go.mau.fi/mautrix-whatsapp go 1.24.0 -toolchain go1.25.3 +toolchain go1.25.4 require ( github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.9.2 + go.mau.fi/util v0.9.3 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251115195115-7159d9053646 - golang.org/x/image v0.32.0 - golang.org/x/net v0.46.0 - golang.org/x/sync v0.17.0 + go.mau.fi/whatsmeow v0.0.0-20251116104239-3aca43070cd4 + golang.org/x/image v0.33.0 + golang.org/x/net v0.47.0 + golang.org/x/sync v0.18.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.25.3-0.20251109094010-14e16a3a8190 + maunium.net/go/mautrix v0.26.0 ) require ( @@ -41,10 +41,10 @@ require ( github.com/yuin/goldmark v1.7.13 // indirect go.mau.fi/libsignal v0.2.1 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect - golang.org/x/crypto v0.43.0 // indirect - golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b // indirect - golang.org/x/sys v0.37.0 // indirect - golang.org/x/text v0.30.0 // indirect + golang.org/x/crypto v0.44.0 // indirect + golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.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 27ba0c9e..6a9f38b6 100644 --- a/go.sum +++ b/go.sum @@ -75,31 +75,31 @@ github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.13/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.2 h1:+S4Z03iCsGqU2WY8X2gySFsFjaLlUHFRDVCYvVwynKM= -go.mau.fi/util v0.9.2/go.mod h1:055elBBCJSdhRsmub7ci9hXZPgGr1U6dYg44cSgRgoU= +go.mau.fi/util v0.9.3 h1:aqNF8KDIN8bFpFbybSk+mEBil7IHeBwlujfyTnvP0uU= +go.mau.fi/util v0.9.3/go.mod h1:krWWfBM1jWTb5f8NCa2TLqWMQuM81X7TGQjhMjBeXmQ= 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-20251115195115-7159d9053646 h1:kPrIYSYxrvK2jJQPey7RXWLrP/c9psz58mZWD93BRao= -go.mau.fi/whatsmeow v0.0.0-20251115195115-7159d9053646/go.mod h1:RwBrMQAWCHGzMdDZ6EwjcY4Aj3g8Efx8c7GACTdiAME= +go.mau.fi/whatsmeow v0.0.0-20251116104239-3aca43070cd4 h1:7hXdxCFs2Me4nypiWjdBNonaFrPfmYJvEtTOwLctSHU= +go.mau.fi/whatsmeow v0.0.0-20251116104239-3aca43070cd4/go.mod h1:5aYaEa3FF5e5XWsA8Xa80ttUXZvb6HyaBGgo2SfzUkE= 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.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= -golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b h1:18qgiDvlvH7kk8Ioa8Ov+K6xCi0GMvmGfGW0sgd/SYA= -golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= -golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ= -golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= +golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0= +golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= +golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ= +golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 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.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -111,5 +111,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.25.3-0.20251109094010-14e16a3a8190 h1:5v25ZS99ilLXHjYX3uMiyHGBNhFPXgOgGfr9GkakRTg= -maunium.net/go/mautrix v0.25.3-0.20251109094010-14e16a3a8190/go.mod h1:EWgYyp2iFZP7pnSm+rufHlO8YVnA2KnoNBDpwekiAwI= +maunium.net/go/mautrix v0.26.0 h1:valc2VmZF+oIY4bMq4Cd5H9cEKMRe8eP4FM7iiaYLxI= +maunium.net/go/mautrix v0.26.0/go.mod h1:NWMv+243NX/gDrLofJ2nNXJPrG8vzoM+WUCWph85S6Q= From d3cfd0868fac02343a5608d10a3c6664ddc43435 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 19 Nov 2025 15:07:08 +0200 Subject: [PATCH 218/486] 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 d03bb190..2b6024c5 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( golang.org/x/sync v0.18.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.0 + maunium.net/go/mautrix v0.26.1-0.20251119111538-57657d54eeac ) require ( diff --git a/go.sum b/go.sum index 6a9f38b6..6db9f1c4 100644 --- a/go.sum +++ b/go.sum @@ -111,5 +111,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.0 h1:valc2VmZF+oIY4bMq4Cd5H9cEKMRe8eP4FM7iiaYLxI= -maunium.net/go/mautrix v0.26.0/go.mod h1:NWMv+243NX/gDrLofJ2nNXJPrG8vzoM+WUCWph85S6Q= +maunium.net/go/mautrix v0.26.1-0.20251119111538-57657d54eeac h1:zRQXrl+c9Isc1pJ7/MAdov8OKt+MVPGh5qjTzefxb+k= +maunium.net/go/mautrix v0.26.1-0.20251119111538-57657d54eeac/go.mod h1:NWMv+243NX/gDrLofJ2nNXJPrG8vzoM+WUCWph85S6Q= From 7502fa37d45439e0b95374dd4ccb6ee74ccbbda3 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 19 Nov 2025 23:57:31 +0200 Subject: [PATCH 219/486] dependencies: update mautrix-go again --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2b6024c5..885fa7dc 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( golang.org/x/sync v0.18.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.1-0.20251119111538-57657d54eeac + maunium.net/go/mautrix v0.26.1-0.20251119212156-1fac8ceb6653 ) require ( diff --git a/go.sum b/go.sum index 6db9f1c4..6f18633e 100644 --- a/go.sum +++ b/go.sum @@ -111,5 +111,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.1-0.20251119111538-57657d54eeac h1:zRQXrl+c9Isc1pJ7/MAdov8OKt+MVPGh5qjTzefxb+k= -maunium.net/go/mautrix v0.26.1-0.20251119111538-57657d54eeac/go.mod h1:NWMv+243NX/gDrLofJ2nNXJPrG8vzoM+WUCWph85S6Q= +maunium.net/go/mautrix v0.26.1-0.20251119212156-1fac8ceb6653 h1:z62cPre4V+NGIonN5qlvt1ZtZH5/93ix9DR7KR8yImU= +maunium.net/go/mautrix v0.26.1-0.20251119212156-1fac8ceb6653/go.mod h1:NWMv+243NX/gDrLofJ2nNXJPrG8vzoM+WUCWph85S6Q= From 3a65bdaf1e3416a60f05e5abee0f9a50e639e5c1 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 26 Nov 2025 19:13:48 +0200 Subject: [PATCH 220/486] directmedia: add debug log for missing avatars --- pkg/connector/directmedia.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index 2b289b81..f91c28d7 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -29,6 +29,7 @@ import ( "github.com/rs/zerolog" "go.mau.fi/util/exsync" + "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waMmsRetry" "go.mau.fi/whatsmeow/types/events" @@ -85,7 +86,7 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars return nil, fmt.Errorf("failed to get avatar cache entry: %w", err) } if cachedInfo != nil && cachedInfo.Gone { - return nil, mautrix.MNotFound.WithMessage("Avatar is no longer available") + return nil, mautrix.MNotFound.WithMessage("Avatar is no longer available (cached response)") } else if cachedInfo == nil || cachedInfo.Expiry.Time.Before(time.Now().Add(5*time.Minute)) { zerolog.Ctx(ctx).Debug(). Str("avatar_id", parsedID.Avatar.AvatarID). @@ -96,6 +97,13 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars if errors.Is(err, whatsmeow.ErrProfilePictureNotSet) || errors.Is(err, whatsmeow.ErrProfilePictureUnauthorized) || (err == nil && (avatar == nil || avatar.ID != parsedID.Avatar.AvatarID)) { + zerolog.Ctx(ctx).Debug(). + Err(err). + Stringer("target_jid", parsedID.Avatar.TargetJID). + Bool("is_community", parsedID.Avatar.Community). + Str("wanted_avatar_id", parsedID.Avatar.AvatarID). + Str("got_avatar_id", ptr.Val(avatar).ID). + Msg("Avatar is no longer available") err = wa.DB.AvatarCache.Put(ctx, &wadb.AvatarCacheEntry{ EntityJID: parsedID.Avatar.TargetJID, AvatarID: parsedID.Avatar.AvatarID, From 8b2106b9bc9135f11e6f0fedfe800084d3f82045 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Thu, 27 Nov 2025 14:08:50 +0000 Subject: [PATCH 221/486] 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 885fa7dc..a7f52f1e 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.3 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251116104239-3aca43070cd4 + go.mau.fi/whatsmeow v0.0.0-20251127132918-b9ac3d51d746 golang.org/x/image v0.33.0 golang.org/x/net v0.47.0 golang.org/x/sync v0.18.0 diff --git a/go.sum b/go.sum index 6f18633e..885f90d5 100644 --- a/go.sum +++ b/go.sum @@ -79,8 +79,8 @@ go.mau.fi/util v0.9.3 h1:aqNF8KDIN8bFpFbybSk+mEBil7IHeBwlujfyTnvP0uU= go.mau.fi/util v0.9.3/go.mod h1:krWWfBM1jWTb5f8NCa2TLqWMQuM81X7TGQjhMjBeXmQ= 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-20251116104239-3aca43070cd4 h1:7hXdxCFs2Me4nypiWjdBNonaFrPfmYJvEtTOwLctSHU= -go.mau.fi/whatsmeow v0.0.0-20251116104239-3aca43070cd4/go.mod h1:5aYaEa3FF5e5XWsA8Xa80ttUXZvb6HyaBGgo2SfzUkE= +go.mau.fi/whatsmeow v0.0.0-20251127132918-b9ac3d51d746 h1:51hAK0a+KA4BD7MgqfVd6dbJd5cJBhotOlhWUybYCn0= +go.mau.fi/whatsmeow v0.0.0-20251127132918-b9ac3d51d746/go.mod h1:5aYaEa3FF5e5XWsA8Xa80ttUXZvb6HyaBGgo2SfzUkE= 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.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= From 89dee0afd9ce1c07f5ad77f663ae8a280b232619 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 28 Nov 2025 22:03:27 +0200 Subject: [PATCH 222/486] build.sh: replace with go tool Closes #587 --- build.sh | 4 +--- go.mod | 13 ++++++++----- go.sum | 22 ++++++++++++---------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/build.sh b/build.sh index 0676fdd0..24421357 100755 --- a/build.sh +++ b/build.sh @@ -1,4 +1,2 @@ #!/bin/sh -MAUTRIX_VERSION=$(cat go.mod | grep 'maunium.net/go/mautrix ' | awk '{ print $2 }') -GO_LDFLAGS="-s -w -X main.Tag=$(git describe --exact-match --tags 2>/dev/null) -X main.Commit=$(git rev-parse HEAD) -X 'main.BuildTime=`date -Iseconds`' -X 'maunium.net/go/mautrix.GoModVersion=$MAUTRIX_VERSION'" -go build -ldflags="$GO_LDFLAGS" "$@" ./cmd/mautrix-whatsapp +BINARY_NAME=mautrix-whatsapp go tool maubuild "$@" diff --git a/go.mod b/go.mod index a7f52f1e..875c93c3 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,12 @@ go 1.24.0 toolchain go1.25.4 +tool go.mau.fi/util/cmd/maubuild + require ( github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.9.3 + go.mau.fi/util v0.9.4-0.20251128195053-c7fab4d88a04 go.mau.fi/webp v0.2.0 go.mau.fi/whatsmeow v0.0.0-20251127132918-b9ac3d51d746 golang.org/x/image v0.33.0 @@ -15,7 +17,7 @@ require ( golang.org/x/sync v0.18.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.1-0.20251119212156-1fac8ceb6653 + maunium.net/go/mautrix v0.26.1-0.20251128114054-1d1ecb228668 ) require ( @@ -29,7 +31,7 @@ require ( 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.32 // indirect - github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490 // indirect + github.com/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a // 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 @@ -41,8 +43,9 @@ require ( github.com/yuin/goldmark v1.7.13 // indirect go.mau.fi/libsignal v0.2.1 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect - golang.org/x/crypto v0.44.0 // indirect - golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect + golang.org/x/crypto v0.45.0 // indirect + golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 // indirect + golang.org/x/mod v0.30.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.31.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index 885f90d5..6141c5a3 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490 h1:QTvNkZ5ylY0PGgA+Lih+GdboMLY/G9SEGLMEGVjTVA4= -github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a h1:VweslR2akb/ARhXfqSfRbj1vpWwYXf3eeAUyw/ndms0= +github.com/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a/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= @@ -75,20 +75,22 @@ github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.13/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.3 h1:aqNF8KDIN8bFpFbybSk+mEBil7IHeBwlujfyTnvP0uU= -go.mau.fi/util v0.9.3/go.mod h1:krWWfBM1jWTb5f8NCa2TLqWMQuM81X7TGQjhMjBeXmQ= +go.mau.fi/util v0.9.4-0.20251128195053-c7fab4d88a04 h1:1xsqu1gqwLUBEolvrsURhj/A3e9GACg95i8gITI0Be0= +go.mau.fi/util v0.9.4-0.20251128195053-c7fab4d88a04/go.mod h1:viDmhBOAFfcqDdKSk53EPJV3N4Mi8Jst5/ahGJ/vwsA= 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-20251127132918-b9ac3d51d746 h1:51hAK0a+KA4BD7MgqfVd6dbJd5cJBhotOlhWUybYCn0= go.mau.fi/whatsmeow v0.0.0-20251127132918-b9ac3d51d746/go.mod h1:5aYaEa3FF5e5XWsA8Xa80ttUXZvb6HyaBGgo2SfzUkE= 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.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= -golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0= -golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY= +golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ= golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= @@ -111,5 +113,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.1-0.20251119212156-1fac8ceb6653 h1:z62cPre4V+NGIonN5qlvt1ZtZH5/93ix9DR7KR8yImU= -maunium.net/go/mautrix v0.26.1-0.20251119212156-1fac8ceb6653/go.mod h1:NWMv+243NX/gDrLofJ2nNXJPrG8vzoM+WUCWph85S6Q= +maunium.net/go/mautrix v0.26.1-0.20251128114054-1d1ecb228668 h1:Y6B67gtYwMpePlI9Spz1EKVsLiAvKJ+rxRBZRWbGjmI= +maunium.net/go/mautrix v0.26.1-0.20251128114054-1d1ecb228668/go.mod h1:NaesYcOQWFDbixVYywCVS+Twlzab9hOUpFNlCBlvciE= From fcb1710cd8d5f7a908da36da8eccd556912226b7 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 1 Dec 2025 15:37:00 +0200 Subject: [PATCH 223/486] dependencies: update whatsmeow --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 875c93c3..76e976b3 100644 --- a/go.mod +++ b/go.mod @@ -11,13 +11,13 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.4-0.20251128195053-c7fab4d88a04 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251127132918-b9ac3d51d746 + go.mau.fi/whatsmeow v0.0.0-20251201133539-d5bb5361b3d7 golang.org/x/image v0.33.0 golang.org/x/net v0.47.0 golang.org/x/sync v0.18.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.1-0.20251128114054-1d1ecb228668 + maunium.net/go/mautrix v0.26.1-0.20251201132856-09052986b2d3 ) require ( diff --git a/go.sum b/go.sum index 6141c5a3..f2a0637e 100644 --- a/go.sum +++ b/go.sum @@ -79,8 +79,8 @@ go.mau.fi/util v0.9.4-0.20251128195053-c7fab4d88a04 h1:1xsqu1gqwLUBEolvrsURhj/A3 go.mau.fi/util v0.9.4-0.20251128195053-c7fab4d88a04/go.mod h1:viDmhBOAFfcqDdKSk53EPJV3N4Mi8Jst5/ahGJ/vwsA= 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-20251127132918-b9ac3d51d746 h1:51hAK0a+KA4BD7MgqfVd6dbJd5cJBhotOlhWUybYCn0= -go.mau.fi/whatsmeow v0.0.0-20251127132918-b9ac3d51d746/go.mod h1:5aYaEa3FF5e5XWsA8Xa80ttUXZvb6HyaBGgo2SfzUkE= +go.mau.fi/whatsmeow v0.0.0-20251201133539-d5bb5361b3d7 h1:9vh1AMoNAR6YFJI2jSgCaTCqJMkgDf2wo02xayIL6l8= +go.mau.fi/whatsmeow v0.0.0-20251201133539-d5bb5361b3d7/go.mod h1:5aYaEa3FF5e5XWsA8Xa80ttUXZvb6HyaBGgo2SfzUkE= 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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= @@ -113,5 +113,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.1-0.20251128114054-1d1ecb228668 h1:Y6B67gtYwMpePlI9Spz1EKVsLiAvKJ+rxRBZRWbGjmI= -maunium.net/go/mautrix v0.26.1-0.20251128114054-1d1ecb228668/go.mod h1:NaesYcOQWFDbixVYywCVS+Twlzab9hOUpFNlCBlvciE= +maunium.net/go/mautrix v0.26.1-0.20251201132856-09052986b2d3 h1:2hzPLYsZbDY4MykMYS0xGjKYMXnlbAsznFkIHVp55Fo= +maunium.net/go/mautrix v0.26.1-0.20251201132856-09052986b2d3/go.mod h1:NaesYcOQWFDbixVYywCVS+Twlzab9hOUpFNlCBlvciE= From 23ea890b47fd5ed62e9374a87711fe2d077a3b80 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 2 Dec 2025 15:27:34 +0200 Subject: [PATCH 224/486] handlematrix: use new interface for redirecting invites to correct ID --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/handlematrix.go | 27 +++++++++++++++++---------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 76e976b3..eb8de66e 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( golang.org/x/sync v0.18.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.1-0.20251201132856-09052986b2d3 + maunium.net/go/mautrix v0.26.1-0.20251202132204-2eeece694254 ) require ( diff --git a/go.sum b/go.sum index f2a0637e..d95748e6 100644 --- a/go.sum +++ b/go.sum @@ -113,5 +113,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.1-0.20251201132856-09052986b2d3 h1:2hzPLYsZbDY4MykMYS0xGjKYMXnlbAsznFkIHVp55Fo= -maunium.net/go/mautrix v0.26.1-0.20251201132856-09052986b2d3/go.mod h1:NaesYcOQWFDbixVYywCVS+Twlzab9hOUpFNlCBlvciE= +maunium.net/go/mautrix v0.26.1-0.20251202132204-2eeece694254 h1:rsc4H0sZVban6xQeIS6err1YangUiK5WzVfTS6s03ms= +maunium.net/go/mautrix v0.26.1-0.20251202132204-2eeece694254/go.mod h1:NaesYcOQWFDbixVYywCVS+Twlzab9hOUpFNlCBlvciE= diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index 3abc38e1..a057d769 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -390,18 +390,18 @@ func (wa *WhatsAppClient) HandleMatrixDisappearingTimer(ctx context.Context, msg return true, nil } -func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2.MatrixMembershipChange) (bool, error) { +func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2.MatrixMembershipChange) (*bridgev2.MatrixMembershipResult, error) { portalJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { - return false, err + return nil, err } if msg.Portal.RoomType == database.RoomTypeDM { switch msg.Type { case bridgev2.Invite: - return false, fmt.Errorf("cannot invite additional user to dm") + return nil, fmt.Errorf("cannot invite additional user to dm") default: - return false, nil + return nil, nil } } @@ -414,7 +414,7 @@ func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridg case bridgev2.Leave, bridgev2.Kick: action = whatsmeow.ParticipantChangeRemove default: - return false, nil + return nil, nil } switch target := msg.Target.(type) { @@ -423,19 +423,26 @@ func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridg case *bridgev2.UserLogin: ghost, err := target.Bridge.GetGhostByID(ctx, networkid.UserID(target.ID)) if err != nil { - return false, fmt.Errorf("failed to get ghost for user: %w", err) + return nil, fmt.Errorf("failed to get ghost for user: %w", err) } changes[0] = waid.ParseUserID(ghost.ID) default: - return false, fmt.Errorf("cannot get target intent: unknown type: %T", target) + return nil, fmt.Errorf("cannot get target intent: unknown type: %T", target) } - _, err = wa.Client.UpdateGroupParticipants(ctx, portalJID, changes, action) + resp, err := wa.Client.UpdateGroupParticipants(ctx, portalJID, changes, action) if err != nil { - return false, err + return nil, err + } else if len(resp) == 0 { + return nil, fmt.Errorf("no response for participant change") + } else if resp[0].Error != 0 { + return nil, fmt.Errorf("failed to change participant: code %d", resp[0].Error) } + zerolog.Ctx(ctx).Debug(). + Any("change_response", resp). + Msg("Handled membership change") - return true, nil + return &bridgev2.MatrixMembershipResult{RedirectTo: waid.MakeUserID(resp[0].JID)}, nil } func (wa *WhatsAppClient) HandleMatrixRoomName(ctx context.Context, msg *bridgev2.MatrixRoomName) (bool, error) { From 95a9a6233abadb040b0428efc1ef803d391f52b8 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 2 Dec 2025 15:48:56 +0200 Subject: [PATCH 225/486] 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 eb8de66e..0502c75f 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.4-0.20251128195053-c7fab4d88a04 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251201133539-d5bb5361b3d7 + go.mau.fi/whatsmeow v0.0.0-20251202134806-b8b6014103aa golang.org/x/image v0.33.0 golang.org/x/net v0.47.0 golang.org/x/sync v0.18.0 diff --git a/go.sum b/go.sum index d95748e6..e19ae59d 100644 --- a/go.sum +++ b/go.sum @@ -79,8 +79,8 @@ go.mau.fi/util v0.9.4-0.20251128195053-c7fab4d88a04 h1:1xsqu1gqwLUBEolvrsURhj/A3 go.mau.fi/util v0.9.4-0.20251128195053-c7fab4d88a04/go.mod h1:viDmhBOAFfcqDdKSk53EPJV3N4Mi8Jst5/ahGJ/vwsA= 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-20251201133539-d5bb5361b3d7 h1:9vh1AMoNAR6YFJI2jSgCaTCqJMkgDf2wo02xayIL6l8= -go.mau.fi/whatsmeow v0.0.0-20251201133539-d5bb5361b3d7/go.mod h1:5aYaEa3FF5e5XWsA8Xa80ttUXZvb6HyaBGgo2SfzUkE= +go.mau.fi/whatsmeow v0.0.0-20251202134806-b8b6014103aa h1:eflj1+ZBVyerJ0drRo84+rkUmVvYZEFryt0Cjg0och8= +go.mau.fi/whatsmeow v0.0.0-20251202134806-b8b6014103aa/go.mod h1:5aYaEa3FF5e5XWsA8Xa80ttUXZvb6HyaBGgo2SfzUkE= 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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= From 8fb00ccc7c4127114b424258b00523b047921a14 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 3 Dec 2025 22:10:57 +0200 Subject: [PATCH 226/486] directmedia: update interface --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/directmedia.go | 33 +++++++++++++++++---------------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index 0502c75f..c75f15fa 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( golang.org/x/sync v0.18.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.1-0.20251202132204-2eeece694254 + maunium.net/go/mautrix v0.26.1-0.20251203195941-02ce6ff91851 ) require ( diff --git a/go.sum b/go.sum index e19ae59d..9d3f3525 100644 --- a/go.sum +++ b/go.sum @@ -113,5 +113,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.1-0.20251202132204-2eeece694254 h1:rsc4H0sZVban6xQeIS6err1YangUiK5WzVfTS6s03ms= -maunium.net/go/mautrix v0.26.1-0.20251202132204-2eeece694254/go.mod h1:NaesYcOQWFDbixVYywCVS+Twlzab9hOUpFNlCBlvciE= +maunium.net/go/mautrix v0.26.1-0.20251203195941-02ce6ff91851 h1:5dty5IkJGxpLj0SQ2+wwKIcrPfZML1uHFcGaQIA9te0= +maunium.net/go/mautrix v0.26.1-0.20251203195941-02ce6ff91851/go.mod h1:NaesYcOQWFDbixVYywCVS+Twlzab9hOUpFNlCBlvciE= diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index f91c28d7..84d54db3 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -127,12 +127,11 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars } } return &mediaproxy.GetMediaResponseFile{ - Callback: func(w *os.File) error { - return waClient.Client.DownloadMediaWithPathToFile( + Callback: func(w *os.File) (*mediaproxy.FileMeta, error) { + return &mediaproxy.FileMeta{}, waClient.Client.DownloadMediaWithPathToFile( ctx, cachedInfo.DirectPath, nil, nil, nil, 0, "", "", w, ) }, - ContentType: "", // TODO are avatars always jpeg? }, nil } @@ -176,18 +175,18 @@ func (wa *WhatsAppConnector) downloadMessageDirectMedia(ctx context.Context, par return nil, fmt.Errorf("no WhatsApp client found on login") } return &mediaproxy.GetMediaResponseFile{ - Callback: func(f *os.File) error { + 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) { val := params["fi.mau.whatsapp.reload_media"] if val == "false" || (!wa.Config.DirectMediaAutoRequest && val != "true") { - return ErrReloadNeeded + return nil, ErrReloadNeeded } log.Trace().Msg("Media not found for direct download, requesting and waiting") err = waClient.requestAndWaitDirectMedia(ctx, msg.ID, keys) if err != nil { log.Trace().Err(err).Msg("Failed to wait for media for direct download") - return err + return nil, err } log.Trace().Msg("Retrying download after successful retry") err = waClient.Client.DownloadToFile(ctx, keys, f) @@ -195,27 +194,29 @@ func (wa *WhatsAppConnector) downloadMessageDirectMedia(ctx context.Context, par 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") } else if err != nil { - return err + return nil, err } - if keys.MimeType == "application/was" { + mime := keys.MimeType + if mime == "application/was" { if _, err := f.Seek(0, io.SeekStart); err != nil { - return fmt.Errorf("failed to seek to start of sticker zip: %w", err) + return nil, fmt.Errorf("failed to seek to start of sticker zip: %w", err) } else if zipData, err := io.ReadAll(f); err != nil { - return fmt.Errorf("failed to read sticker zip: %w", err) + return nil, fmt.Errorf("failed to read sticker zip: %w", err) } else if data, err := msgconv.ExtractAnimatedSticker(zipData); err != nil { - return fmt.Errorf("failed to extract animated sticker: %w %x", err, zipData) + return nil, fmt.Errorf("failed to extract animated sticker: %w %x", err, zipData) } else if _, err := f.WriteAt(data, 0); err != nil { - return fmt.Errorf("failed to write animated sticker to file: %w", err) + return nil, fmt.Errorf("failed to write animated sticker to file: %w", err) } else if err := f.Truncate(int64(len(data))); err != nil { - return fmt.Errorf("failed to truncate animated sticker file: %w", err) + return nil, fmt.Errorf("failed to truncate animated sticker file: %w", err) } + mime = "video/lottie+json" } - return nil + return &mediaproxy.FileMeta{ + ContentType: mime, + }, nil }, - // TODO? - ContentType: "", }, nil } From dc48e38fbcfba87314d8be306ee3481eb7bc7b3e Mon Sep 17 00:00:00 2001 From: Radon Rosborough Date: Wed, 3 Dec 2025 14:15:08 -0800 Subject: [PATCH 227/486] dependencies: upgrade whatsmeow to add 5xx retries (#868) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c75f15fa..93f14b6b 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.4-0.20251128195053-c7fab4d88a04 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251202134806-b8b6014103aa + go.mau.fi/whatsmeow v0.0.0-20251203212742-364369929a75 golang.org/x/image v0.33.0 golang.org/x/net v0.47.0 golang.org/x/sync v0.18.0 diff --git a/go.sum b/go.sum index 9d3f3525..06bbbdb5 100644 --- a/go.sum +++ b/go.sum @@ -79,8 +79,8 @@ go.mau.fi/util v0.9.4-0.20251128195053-c7fab4d88a04 h1:1xsqu1gqwLUBEolvrsURhj/A3 go.mau.fi/util v0.9.4-0.20251128195053-c7fab4d88a04/go.mod h1:viDmhBOAFfcqDdKSk53EPJV3N4Mi8Jst5/ahGJ/vwsA= 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-20251202134806-b8b6014103aa h1:eflj1+ZBVyerJ0drRo84+rkUmVvYZEFryt0Cjg0och8= -go.mau.fi/whatsmeow v0.0.0-20251202134806-b8b6014103aa/go.mod h1:5aYaEa3FF5e5XWsA8Xa80ttUXZvb6HyaBGgo2SfzUkE= +go.mau.fi/whatsmeow v0.0.0-20251203212742-364369929a75 h1:SqZYh7ssKBuBtC6g9Mvj5i0roB34WpUWzLNXtQ9azUI= +go.mau.fi/whatsmeow v0.0.0-20251203212742-364369929a75/go.mod h1:5aYaEa3FF5e5XWsA8Xa80ttUXZvb6HyaBGgo2SfzUkE= 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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= From 82d3445ffdb7fb970d29226a4168564645561921 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 9 Dec 2025 12:42:07 +0200 Subject: [PATCH 228/486] 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 93f14b6b..5ec0dae1 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ tool go.mau.fi/util/cmd/maubuild require ( github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.9.4-0.20251128195053-c7fab4d88a04 + go.mau.fi/util v0.9.4-0.20251206205611-85e6fd6551e0 go.mau.fi/webp v0.2.0 go.mau.fi/whatsmeow v0.0.0-20251203212742-364369929a75 golang.org/x/image v0.33.0 @@ -17,7 +17,7 @@ require ( golang.org/x/sync v0.18.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.1-0.20251203195941-02ce6ff91851 + maunium.net/go/mautrix v0.26.1-0.20251208143302-e7a95b7f9732 ) require ( diff --git a/go.sum b/go.sum index 06bbbdb5..ca2cb4bf 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.13/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.4-0.20251128195053-c7fab4d88a04 h1:1xsqu1gqwLUBEolvrsURhj/A3e9GACg95i8gITI0Be0= -go.mau.fi/util v0.9.4-0.20251128195053-c7fab4d88a04/go.mod h1:viDmhBOAFfcqDdKSk53EPJV3N4Mi8Jst5/ahGJ/vwsA= +go.mau.fi/util v0.9.4-0.20251206205611-85e6fd6551e0 h1:ESebxPGULuuxxcZigjcBFyyU62tiyY6ivtX17P4BkvY= +go.mau.fi/util v0.9.4-0.20251206205611-85e6fd6551e0/go.mod h1:viDmhBOAFfcqDdKSk53EPJV3N4Mi8Jst5/ahGJ/vwsA= 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-20251203212742-364369929a75 h1:SqZYh7ssKBuBtC6g9Mvj5i0roB34WpUWzLNXtQ9azUI= @@ -113,5 +113,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.1-0.20251203195941-02ce6ff91851 h1:5dty5IkJGxpLj0SQ2+wwKIcrPfZML1uHFcGaQIA9te0= -maunium.net/go/mautrix v0.26.1-0.20251203195941-02ce6ff91851/go.mod h1:NaesYcOQWFDbixVYywCVS+Twlzab9hOUpFNlCBlvciE= +maunium.net/go/mautrix v0.26.1-0.20251208143302-e7a95b7f9732 h1:k+wQVLO5IoVxP3Ouo8OSxs2zDIRBG9QTHlXVsHxN+wc= +maunium.net/go/mautrix v0.26.1-0.20251208143302-e7a95b7f9732/go.mod h1:pzwIT42s+BhBjEYovmcOt69VlNW2RkJ6pCyZjYQHKIc= From b5a1b1b1b1e4dd26f7155d4aa113fa3a3e682671 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 9 Dec 2025 12:42:19 +0200 Subject: [PATCH 229/486] client: add connecting bridge state --- pkg/connector/client.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 7c5c6e93..1b707183 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -192,6 +192,7 @@ func (wa *WhatsAppClient) Connect(ctx context.Context) { wa.UserLogin.BridgeState.Send(state) return } + wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnecting}) wa.Main.firstClientConnectOnce.Do(wa.Main.onFirstClientConnect) if err := wa.Main.updateProxy(ctx, wa.Client, false); err != nil { zerolog.Ctx(ctx).Err(err).Msg("Failed to update proxy") @@ -243,6 +244,7 @@ func (wa *WhatsAppClient) ConnectBackground(ctx context.Context, params *bridgev if wa.Client == nil { return bridgev2.ErrNotLoggedIn } + wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnecting}) wa.Client.BackgroundEventCtx = wa.UserLogin.Log.WithContext(wa.Main.Bridge.BackgroundCtx) ch := make(chan error, 1) wa.offlineSyncWaiter.Store(&ch) @@ -259,7 +261,7 @@ func (wa *WhatsAppClient) ConnectBackground(ctx context.Context, params *bridgev defer func() { wa.Client.GetClientPayload = nil }() - err := wa.Client.Connect() + err := wa.Client.ConnectContext(ctx) if err != nil { return err } From 499857ce8da5d977b9e74337937e1dbfcac8b2dd Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 9 Dec 2025 12:52:47 +0200 Subject: [PATCH 230/486] client: undo connecting state for background connections --- pkg/connector/client.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 1b707183..1ffe33ee 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -244,7 +244,6 @@ func (wa *WhatsAppClient) ConnectBackground(ctx context.Context, params *bridgev if wa.Client == nil { return bridgev2.ErrNotLoggedIn } - wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnecting}) wa.Client.BackgroundEventCtx = wa.UserLogin.Log.WithContext(wa.Main.Bridge.BackgroundCtx) ch := make(chan error, 1) wa.offlineSyncWaiter.Store(&ch) From f3a47772f3f6ee99d311b9636856cb69e35f115b Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 14 Dec 2025 14:39:08 +0200 Subject: [PATCH 231/486] docker: update to Alpine 3.23 --- Dockerfile | 4 ++-- Dockerfile.ci | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3b71e4f9..4efc9d5f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1-alpine3.22 AS builder +FROM golang:1-alpine3.23 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.22 +FROM alpine:3.23 ENV UID=1337 \ GID=1337 diff --git a/Dockerfile.ci b/Dockerfile.ci index 16ac07d2..cb3be51d 100644 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -1,6 +1,6 @@ ARG DOCKER_HUB="docker.io" -FROM ${DOCKER_HUB}/alpine:3.22 +FROM ${DOCKER_HUB}/alpine:3.23 ENV UID=1337 \ GID=1337 From 34b2e2fb303c4667fc104f2a756e66c510c78ae3 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 15 Dec 2025 13:48:27 +0200 Subject: [PATCH 232/486] capabilities: remove incorrect edit max count --- pkg/connector/capabilities.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/connector/capabilities.go b/pkg/connector/capabilities.go index 75a47e9a..202569b2 100644 --- a/pkg/connector/capabilities.go +++ b/pkg/connector/capabilities.go @@ -51,7 +51,7 @@ func (wa *WhatsAppConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilit } func (wa *WhatsAppConnector) GetBridgeInfoVersion() (info, caps int) { - return 1, 6 + return 1, 7 } const WAMaxFileSize = 2000 * 1024 * 1024 @@ -66,7 +66,7 @@ func supportedIfFFmpeg() event.CapabilitySupportLevel { } func capID() string { - base := "fi.mau.whatsapp.capabilities.2025_10_27" + base := "fi.mau.whatsapp.capabilities.2025_12_15" if ffmpeg.Supported() { return base + "+ffmpeg" } @@ -178,7 +178,6 @@ var whatsappCaps = &event.RoomFeatures{ Poll: event.CapLevelFullySupported, Reply: event.CapLevelFullySupported, Edit: event.CapLevelFullySupported, - EditMaxCount: 10, EditMaxAge: ptr.Ptr(jsontime.S(EditMaxAge)), Delete: event.CapLevelFullySupported, DeleteForMe: false, From 713320ffb0d1d148e6a2b05dda7b0d8a3693a348 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 15 Dec 2025 13:48:40 +0200 Subject: [PATCH 233/486] changelog: update --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c199b19..1eaefb3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# v25.12 (unreleased) + +* Updated Docker image to Alpine 3.23. +* Fixed group member invites from Matrix not automatically disinviting the phone + number ghost when the invite is redirected to a LID ghost. + # v25.11 * Added interface support for notifying about failed invites when creating a From 7faaf99896821d211b2b2fa023f119c2da925b56 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 15 Dec 2025 17:27:17 +0200 Subject: [PATCH 234/486] ci,dependencies: update --- .github/workflows/go.yml | 4 ++-- .github/workflows/stale.yml | 2 +- .pre-commit-config.yaml | 2 +- go.mod | 26 ++++++++++---------- go.sum | 48 ++++++++++++++++++------------------- 5 files changed, 41 insertions(+), 41 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index ae5f5aad..dbc573f5 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -15,10 +15,10 @@ jobs: name: Lint ${{ matrix.go-version == '1.25' && '(latest)' || '(old)' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} cache: true diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 0ae50e42..cae8a4db 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -17,7 +17,7 @@ jobs: lock-stale: runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v5 + - uses: dessant/lock-threads@v6 id: lock with: issue-inactive-days: 90 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a48f8b86..1bfb5360 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: - id: check-added-large-files - repo: https://github.com/tekwizely/pre-commit-golang - rev: v1.0.0-rc.2 + rev: v1.0.0-rc.4 hooks: - id: go-imports-repo args: diff --git a/go.mod b/go.mod index 5ec0dae1..38c22570 100644 --- a/go.mod +++ b/go.mod @@ -2,22 +2,22 @@ module go.mau.fi/mautrix-whatsapp go 1.24.0 -toolchain go1.25.4 +toolchain go1.25.5 tool go.mau.fi/util/cmd/maubuild require ( github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.9.4-0.20251206205611-85e6fd6551e0 + go.mau.fi/util v0.9.4-0.20251214123745-64997f646f1d go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251203212742-364369929a75 - golang.org/x/image v0.33.0 - golang.org/x/net v0.47.0 - golang.org/x/sync v0.18.0 - google.golang.org/protobuf v1.36.10 + go.mau.fi/whatsmeow v0.0.0-20251215152620-b553db2f6bb9 + golang.org/x/image v0.34.0 + golang.org/x/net v0.48.0 + golang.org/x/sync v0.19.0 + google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.1-0.20251208143302-e7a95b7f9732 + maunium.net/go/mautrix v0.26.1-0.20251214123757-4be256229706 ) require ( @@ -43,11 +43,11 @@ require ( github.com/yuin/goldmark v1.7.13 // indirect go.mau.fi/libsignal v0.2.1 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 // indirect - golang.org/x/mod v0.30.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/text v0.32.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 ca2cb4bf..b0423a45 100644 --- a/go.sum +++ b/go.sum @@ -75,35 +75,35 @@ github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.13/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.4-0.20251206205611-85e6fd6551e0 h1:ESebxPGULuuxxcZigjcBFyyU62tiyY6ivtX17P4BkvY= -go.mau.fi/util v0.9.4-0.20251206205611-85e6fd6551e0/go.mod h1:viDmhBOAFfcqDdKSk53EPJV3N4Mi8Jst5/ahGJ/vwsA= +go.mau.fi/util v0.9.4-0.20251214123745-64997f646f1d h1:/w7U+vxbtIYEhMHZaXxYqTU1HmO6kJK3BxnG66Rzk3o= +go.mau.fi/util v0.9.4-0.20251214123745-64997f646f1d/go.mod h1:OwI76F1QINxtH/TOydGAAj5/VvtPG0RnZzB41rtnKcA= 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-20251203212742-364369929a75 h1:SqZYh7ssKBuBtC6g9Mvj5i0roB34WpUWzLNXtQ9azUI= -go.mau.fi/whatsmeow v0.0.0-20251203212742-364369929a75/go.mod h1:5aYaEa3FF5e5XWsA8Xa80ttUXZvb6HyaBGgo2SfzUkE= +go.mau.fi/whatsmeow v0.0.0-20251215152620-b553db2f6bb9 h1:vcQ9vuWiwSHBPdwgEyuqMDgHcCdRC+p1aOAIx6j2eVY= +go.mau.fi/whatsmeow v0.0.0-20251215152620-b553db2f6bb9/go.mod h1:5aYaEa3FF5e5XWsA8Xa80ttUXZvb6HyaBGgo2SfzUkE= 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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY= -golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= -golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ= -golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM= +golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= +golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8= +golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 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.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +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= 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= @@ -113,5 +113,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.1-0.20251208143302-e7a95b7f9732 h1:k+wQVLO5IoVxP3Ouo8OSxs2zDIRBG9QTHlXVsHxN+wc= -maunium.net/go/mautrix v0.26.1-0.20251208143302-e7a95b7f9732/go.mod h1:pzwIT42s+BhBjEYovmcOt69VlNW2RkJ6pCyZjYQHKIc= +maunium.net/go/mautrix v0.26.1-0.20251214123757-4be256229706 h1:i0C+KohzQsz1WzhF3TSCA3pSGl7k2MQZ5kNFpUllWrg= +maunium.net/go/mautrix v0.26.1-0.20251214123757-4be256229706/go.mod h1:tMXkJiGf+2dEPvfrmhQVD6jReL5E8jI1o2dPSsy/39Y= From 1f06bd38cdf69123f86fd5ab3042b958a197a137 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 16 Dec 2025 12:34:49 +0200 Subject: [PATCH 235/486] Bump version to v25.12 --- CHANGELOG.md | 2 +- cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 8 ++++---- go.sum | 15 ++++++++------- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eaefb3b..62c06b89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# v25.12 (unreleased) +# v25.12 * Updated Docker image to Alpine 3.23. * Fixed group member invites from Matrix not automatically disinviting the phone diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index b71912fb..6750c3ac 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: "25.11", + Version: "25.12", SemCalVer: true, Connector: &connector.WhatsAppConnector{}, } diff --git a/go.mod b/go.mod index 38c22570..01bb3758 100644 --- a/go.mod +++ b/go.mod @@ -9,22 +9,22 @@ tool go.mau.fi/util/cmd/maubuild require ( github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.9.4-0.20251214123745-64997f646f1d + go.mau.fi/util v0.9.4 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251215152620-b553db2f6bb9 + go.mau.fi/whatsmeow v0.0.0-20251216102424-56a8e44b0cec golang.org/x/image v0.34.0 golang.org/x/net v0.48.0 golang.org/x/sync v0.19.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.1-0.20251214123757-4be256229706 + maunium.net/go/mautrix v0.26.1 ) require ( filippo.io/edwards25519 v1.1.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.5.0 // indirect + github.com/coreos/go-systemd/v22 v22.6.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 diff --git a/go.sum b/go.sum index b0423a45..9a0bb67c 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,9 @@ 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 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= 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/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= @@ -75,12 +76,12 @@ github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.13/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.4-0.20251214123745-64997f646f1d h1:/w7U+vxbtIYEhMHZaXxYqTU1HmO6kJK3BxnG66Rzk3o= -go.mau.fi/util v0.9.4-0.20251214123745-64997f646f1d/go.mod h1:OwI76F1QINxtH/TOydGAAj5/VvtPG0RnZzB41rtnKcA= +go.mau.fi/util v0.9.4 h1:gWdUff+K2rCynRPysXalqqQyr2ahkSWaestH6YhSpso= +go.mau.fi/util v0.9.4/go.mod h1:647nVfwUvuhlZFOnro3aRNPmRd2y3iDha9USb8aKSmM= 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-20251215152620-b553db2f6bb9 h1:vcQ9vuWiwSHBPdwgEyuqMDgHcCdRC+p1aOAIx6j2eVY= -go.mau.fi/whatsmeow v0.0.0-20251215152620-b553db2f6bb9/go.mod h1:5aYaEa3FF5e5XWsA8Xa80ttUXZvb6HyaBGgo2SfzUkE= +go.mau.fi/whatsmeow v0.0.0-20251216102424-56a8e44b0cec h1:pNU+bpMGDodCJt6ufF1ImhuuBgzut4kaHklU7tey2g0= +go.mau.fi/whatsmeow v0.0.0-20251216102424-56a8e44b0cec/go.mod h1:S4OWR9+hTx+54+jRzl+NfRBXnGpPm5IRPyhXB7haSd0= 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.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= @@ -113,5 +114,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.1-0.20251214123757-4be256229706 h1:i0C+KohzQsz1WzhF3TSCA3pSGl7k2MQZ5kNFpUllWrg= -maunium.net/go/mautrix v0.26.1-0.20251214123757-4be256229706/go.mod h1:tMXkJiGf+2dEPvfrmhQVD6jReL5E8jI1o2dPSsy/39Y= +maunium.net/go/mautrix v0.26.1 h1:FWCC1xY5vwJ5ou3duEBjB6w9IIlwfc9el3q3Mju3Dlg= +maunium.net/go/mautrix v0.26.1/go.mod h1:UySSpb8OqXG1sMJ6dDqyzmfcqr2ayZK+KzwqOTAkAOM= From c050d3eb345d6feb464e185557198290a3f393cb Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 17 Dec 2025 16:36:29 +0200 Subject: [PATCH 236/486] connector: add timeout for fetching latest whatsapp web version --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/client.go | 1 + pkg/connector/connector.go | 14 +++++++++++++- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 01bb3758..75020389 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.4 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251216102424-56a8e44b0cec + go.mau.fi/whatsmeow v0.0.0-20251217143725-11cf47c62d32 golang.org/x/image v0.34.0 golang.org/x/net v0.48.0 golang.org/x/sync v0.19.0 diff --git a/go.sum b/go.sum index 9a0bb67c..79dd3eeb 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ go.mau.fi/util v0.9.4 h1:gWdUff+K2rCynRPysXalqqQyr2ahkSWaestH6YhSpso= go.mau.fi/util v0.9.4/go.mod h1:647nVfwUvuhlZFOnro3aRNPmRd2y3iDha9USb8aKSmM= 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-20251216102424-56a8e44b0cec h1:pNU+bpMGDodCJt6ufF1ImhuuBgzut4kaHklU7tey2g0= -go.mau.fi/whatsmeow v0.0.0-20251216102424-56a8e44b0cec/go.mod h1:S4OWR9+hTx+54+jRzl+NfRBXnGpPm5IRPyhXB7haSd0= +go.mau.fi/whatsmeow v0.0.0-20251217143725-11cf47c62d32 h1:NeE9eEYY4kEJVCfCXaAU27LgAPugPHRHJdC9IpXFPzI= +go.mau.fi/whatsmeow v0.0.0-20251217143725-11cf47c62d32/go.mod h1:S4OWR9+hTx+54+jRzl+NfRBXnGpPm5IRPyhXB7haSd0= 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.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 1ffe33ee..2eecf044 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -202,6 +202,7 @@ func (wa *WhatsAppClient) Connect(ctx context.Context) { } wa.startLoops() wa.Client.BackgroundEventCtx = wa.UserLogin.Log.WithContext(wa.Main.Bridge.BackgroundCtx) + zerolog.Ctx(ctx).Debug().Msg("Connecting to WhatsApp") if err := wa.Client.ConnectContext(ctx); err != nil { zerolog.Ctx(ctx).Err(err).Msg("Failed to connect to WhatsApp") state := status.BridgeState{ diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index af23ed4d..1b7fc68b 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -20,9 +20,12 @@ import ( "context" "encoding/hex" "fmt" + "net" + "net/http" "strings" "sync" "sync/atomic" + "time" "github.com/lib/pq" "github.com/rs/zerolog" @@ -256,8 +259,17 @@ func (wa *WhatsAppConnector) onFirstBackgroundConnect() { } func (wa *WhatsAppConnector) onFirstClientConnect() { + wa.Bridge.Log.Debug().Msg("Fetching latest WhatsApp web version number") ctx := wa.Bridge.BackgroundCtx - ver, err := whatsmeow.GetLatestVersion(ctx, nil) + ver, err := whatsmeow.GetLatestVersion(ctx, &http.Client{ + Transport: &http.Transport{ + DialContext: (&net.Dialer{Timeout: 5 * time.Second}).DialContext, + TLSHandshakeTimeout: 5 * time.Second, + ResponseHeaderTimeout: 5 * time.Second, + ForceAttemptHTTP2: true, + }, + Timeout: 10 * time.Second, + }) if err != nil { wa.Bridge.Log.Err(err).Msg("Failed to get latest WhatsApp web version number") } else { From 1214b97c656eb48c006ef70489d67ad1a904b416 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 18 Dec 2025 14:35:35 +0200 Subject: [PATCH 237/486] handlematrix: leave group before deleting chat (#870) --- pkg/connector/handlematrix.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index a057d769..ce8b7b25 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -647,6 +647,18 @@ func (wa *WhatsAppClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridg if err != nil { return err } + if chatJID.Server == types.GroupServer { + memberInfo, err := wa.Main.Bridge.Matrix.GetMemberInfo(ctx, msg.Portal.MXID, wa.UserLogin.UserMXID) + if err != nil { + return fmt.Errorf("failed to get own member info: %w", err) + } else if memberInfo.Membership == event.MembershipJoin { + err = wa.Client.LeaveGroup(ctx, chatJID) + if err != nil { + // TODO ignore errors saying you already left the group? + return fmt.Errorf("failed to leave group before deleting chat: %w", err) + } + } + } lastTS, lastKey, err := wa.getLastMessageInfo(ctx, chatJID, msg.Portal.PortalKey) if err != nil { return err From c39f5eb9cb17fa3fad57184a5c5ba6af4323274c Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 30 Dec 2025 23:01:00 +0200 Subject: [PATCH 238/486] startchat,handlematrix: fix handling room avatars --- pkg/connector/handlematrix.go | 4 ++-- pkg/connector/startchat.go | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index ce8b7b25..5d51d127 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -501,8 +501,8 @@ func (wa *WhatsAppClient) HandleMatrixRoomAvatar(ctx context.Context, msg *bridg } var data []byte - if msg.Content.URL != "" || msg.Content.MSC3414File != nil { - data, err = msg.Portal.Bridge.Bot.DownloadMedia(ctx, msg.Content.URL, msg.Content.MSC3414File) + if msg.Content.URL != "" { + data, err = msg.Portal.Bridge.Bot.DownloadMedia(ctx, msg.Content.URL, nil) if err != nil { return false, fmt.Errorf("failed to download avatar: %w", err) } diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index 22d4ad8c..aa468cb4 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -240,10 +240,10 @@ func (wa *WhatsAppClient) CreateGroup(ctx context.Context, params *bridgev2.Grou } var avatarBytes []byte var avatarMXC id.ContentURIString - if params.Avatar != nil { + if params.Avatar != nil && params.Avatar.URL != "" { avatarMXC = params.Avatar.URL var err error - avatarBytes, err = wa.Main.Bridge.Bot.DownloadMedia(ctx, params.Avatar.URL, params.Avatar.MSC3414File) + avatarBytes, err = wa.Main.Bridge.Bot.DownloadMedia(ctx, params.Avatar.URL, nil) if err != nil { return nil, fmt.Errorf("failed to download avatar: %w", err) } @@ -331,6 +331,7 @@ func (wa *WhatsAppClient) CreateGroup(ctx context.Context, params *bridgev2.Grou portal.AvatarID = networkid.AvatarID(avatarID) portal.AvatarHash = sha256.Sum256(avatarBytes) portal.AvatarMXC = avatarMXC + portal.AvatarSet = true groupInfo.Avatar = &bridgev2.Avatar{ ID: portal.AvatarID, MXC: portal.AvatarMXC, From 59bc54e60f39151802d47ef2d9349b5a5f8c7907 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 7 Jan 2026 13:38:22 +0200 Subject: [PATCH 239/486] handlewhatsapp: reroute LID senders in broadcast lists --- pkg/connector/handlewhatsapp.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index a5426949..37a355c1 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -252,7 +252,8 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { } func (wa *WhatsAppClient) rerouteWAMessage(ctx context.Context, evtType string, info *types.MessageSource, msgID any) { - if info.Chat.Server == types.HiddenUserServer && info.SenderAlt.IsEmpty() { + 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.Sender.ToNonAD() == info.Chat && info.SenderAlt.Server == types.DefaultUserServer { @@ -279,6 +280,15 @@ func (wa *WhatsAppClient) rerouteWAMessage(ctx context.Context, evtType string, 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) if err != nil { From 0a0d72e7435691d58c9e5e719d099ee4fa17805c Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 7 Jan 2026 14:53:26 +0200 Subject: [PATCH 240/486] backfill: include message count in all history sync storage logs --- pkg/connector/backfill.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index c9aa521f..f05c0f2d 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -181,17 +181,21 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor failedToSaveTotal := 0 totalMessageCount := 0 for _, conv := range evt.GetConversations() { + log := log.With(). + Int("msg_count", len(conv.GetMessages())). + Logger() jid, err := types.ParseJID(conv.GetID()) if err != nil { totalMessageCount += len(conv.GetMessages()) log.Warn().Err(err). Str("chat_jid", conv.GetID()). - Int("msg_count", len(conv.GetMessages())). Msg("Failed to parse chat JID in history sync") continue } else if jid.Server == types.BroadcastServer { log.Debug().Stringer("chat_jid", jid).Msg("Skipping broadcast list in history sync") continue + } else { + totalMessageCount += len(conv.GetMessages()) } if jid.Server == types.HiddenUserServer { pn, err := wa.GetStore().LIDs.GetPNForLID(ctx, jid) @@ -207,11 +211,9 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor jid = pn } } - totalMessageCount += len(conv.GetMessages()) - log := log.With(). - Stringer("chat_jid", jid). - Int("msg_count", len(conv.GetMessages())). - Logger() + log.UpdateContext(func(c zerolog.Context) zerolog.Context { + return c.Stringer("chat_jid", jid) + }) var minTime, maxTime time.Time var minTimeIndex, maxTimeIndex int From 360564585689696ba19d99500ac13138cb205205 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 7 Jan 2026 14:53:35 +0200 Subject: [PATCH 241/486] client: always use manual history sync downloads --- pkg/connector/client.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 2eecf044..2556a149 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -74,10 +74,8 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. w.Client = whatsmeow.NewClient(w.Device, waLog.Zerolog(log)) w.Client.AddEventHandlerWithSuccessStatus(w.handleWAEvent) w.Client.SynchronousAck = true - if bridgev2.PortalEventBuffer == 0 { - w.Client.EnableDecryptedEventBuffer = true - w.Client.ManualHistorySyncDownload = true - } + w.Client.EnableDecryptedEventBuffer = bridgev2.PortalEventBuffer == 0 + w.Client.ManualHistorySyncDownload = true w.Client.SendReportingTokens = true w.Client.AutomaticMessageRerequestFromPhone = true w.Client.GetMessageForRetry = w.trackNotFoundRetry From 0c5704fc4d31e70c4de2376a680fc09881bdbc98 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 7 Jan 2026 15:03:45 +0200 Subject: [PATCH 242/486] wadb: rerun LID DM migration --- pkg/connector/wadb/upgrades/00-latest-schema.sql | 2 +- .../wadb/upgrades/09-may-need-lid-dm-deletion-again.sql | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 pkg/connector/wadb/upgrades/09-may-need-lid-dm-deletion-again.sql diff --git a/pkg/connector/wadb/upgrades/00-latest-schema.sql b/pkg/connector/wadb/upgrades/00-latest-schema.sql index d9aeac9e..850f5b4a 100644 --- a/pkg/connector/wadb/upgrades/00-latest-schema.sql +++ b/pkg/connector/wadb/upgrades/00-latest-schema.sql @@ -1,4 +1,4 @@ --- v0 -> v8 (compatible with v3+): Latest revision +-- v0 -> v9 (compatible with v3+): Latest revision CREATE TABLE whatsapp_poll_option_id ( bridge_id TEXT NOT NULL, 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 new file mode 100644 index 00000000..5c32e653 --- /dev/null +++ b/pkg/connector/wadb/upgrades/09-may-need-lid-dm-deletion-again.sql @@ -0,0 +1,3 @@ +-- 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'); From 7e9cf12b4f266f307ef37864653bc33b4ba9e1a1 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 7 Jan 2026 20:01:19 +0200 Subject: [PATCH 243/486] handlematrix: remove unnecessary check when deleting chats --- pkg/connector/handlematrix.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index 5d51d127..7282c7cf 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -663,8 +663,5 @@ func (wa *WhatsAppClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridg if err != nil { return err } - if lastKey == nil { - return fmt.Errorf("failed to delete chat: no messages found") - } return wa.Client.SendAppState(ctx, appstate.BuildDeleteChat(chatJID, lastTS, lastKey)) } From e3351ce5b0915a8aed8c26165f371329ffd53a8b Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 7 Jan 2026 20:54:06 +0200 Subject: [PATCH 244/486] handlewhatsapp: fix rerouting receipts in LID DMs --- pkg/connector/handlewhatsapp.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 37a355c1..2ccd9369 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -256,6 +256,9 @@ func (wa *WhatsAppClient) rerouteWAMessage(ctx context.Context, evtType string, 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). @@ -423,6 +426,7 @@ func (wa *WhatsAppClient) handleWAUndecryptableMessage(ctx context.Context, evt } 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) @@ -442,6 +446,10 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei messageSender := wa.JID 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() { @@ -613,6 +621,7 @@ func (wa *WhatsAppClient) handleWADeleteChat(evt *events.DeleteChat) bool { } func (wa *WhatsAppClient) handleWADeleteForMe(evt *events.DeleteForMe) bool { + // FIXME this doesn't handle IsFromMe properly, might also need LID fixes return wa.UserLogin.QueueRemoteEvent(&simplevent.MessageRemove{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventMessageRemove, From 881dce121a5ed682d94b58ecfe8e3fcad698d76c Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Thu, 8 Jan 2026 16:34:38 +0000 Subject: [PATCH 245/486] directmedia: improve error messages after requesting media --- pkg/connector/directmedia.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index 84d54db3..c085d415 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -254,12 +254,16 @@ func (wa *WhatsAppClient) requestAndWaitDirectMedia(ctx context.Context, rawMsgI } switch state.resultType { case waMmsRetry.MediaRetryNotification_NOT_FOUND: - return mautrix.MNotFound.WithMessage("Media not found on phone") + return mautrix.MNotFound.WithMessage("This media was not found on your phone.") + case waMmsRetry.MediaRetryNotification_DECRYPTION_ERROR: + return mautrix.MNotFound.WithMessage("Unable to retrieve media: phone reported a decryption error. The original message may have been deleted.") + case waMmsRetry.MediaRetryNotification_GENERAL_ERROR: + return mautrix.MNotFound.WithMessage("Unable to retrieve media: phone returned an error. Please ensure your phone is connected to the internet and WhatsApp is running.") default: - return mautrix.MNotFound.WithMessage("Phone returned error response") + return mautrix.MNotFound.WithMessage(fmt.Sprintf("Unable to retrieve media: phone returned error code %d", state.resultType)) } case <-time.After(30 * time.Second): - return mautrix.MNotFound.WithMessage("Phone did not respond in time").WithStatus(http.StatusGatewayTimeout) + return mautrix.MNotFound.WithMessage("Phone did not respond in time. Please ensure your phone is connected to the internet and WhatsApp is open.").WithStatus(http.StatusGatewayTimeout) case <-ctx.Done(): return ctx.Err() } From 474c7f135ae118fe49c643ed1233c16c1b477126 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Fri, 9 Jan 2026 19:00:11 +0000 Subject: [PATCH 246/486] directmedia: use `bridgev2.ErrNotLoggedIn` --- pkg/connector/directmedia.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index c085d415..18962d18 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -168,7 +168,7 @@ func (wa *WhatsAppConnector) downloadMessageDirectMedia(ctx context.Context, par } } if ul == nil || !ul.Client.IsLoggedIn() { - return nil, fmt.Errorf("no logged in user found") + return nil, bridgev2.ErrNotLoggedIn } waClient := ul.Client.(*WhatsAppClient) if waClient.Client == nil { From 571392a6daa84d19b9f652f96cf7c9d3e5fe2c7b Mon Sep 17 00:00:00 2001 From: Rafael Bartolome Date: Tue, 13 Jan 2026 15:10:53 +0100 Subject: [PATCH 247/486] handlewhatsapp: add stream order to start call event (#883) --- pkg/connector/handlewhatsapp.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 2ccd9369..5c813094 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -546,6 +546,7 @@ func (wa *WhatsAppClient) handleWACallStart(ctx context.Context, group, sender, Sender: wa.makeEventSender(ctx, sender), CreatePortal: true, Timestamp: ts, + StreamOrder: ts.Unix(), }, Data: callType, ID: waid.MakeFakeMessageID(chat, sender, "call-"+id), From efeaf3ac27d883849d71d2058b8cb82416f7dd89 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 15 Jan 2026 14:39:48 +0200 Subject: [PATCH 248/486] connector: implement network connection resetting --- go.mod | 6 +++--- go.sum | 12 ++++++------ pkg/connector/connector.go | 17 ++++++++++++++--- pkg/connector/proxy.go | 2 ++ 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 75020389..04784a61 100644 --- a/go.mod +++ b/go.mod @@ -9,15 +9,15 @@ tool go.mau.fi/util/cmd/maubuild require ( github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.9.4 + go.mau.fi/util v0.9.5-0.20260114152041-9f2e2b82b503 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20251217143725-11cf47c62d32 + go.mau.fi/whatsmeow v0.0.0-20260115125149-20f0273af4c4 golang.org/x/image v0.34.0 golang.org/x/net v0.48.0 golang.org/x/sync v0.19.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.1 + maunium.net/go/mautrix v0.26.2-0.20260115120200-34bcd027e54c ) require ( diff --git a/go.sum b/go.sum index 79dd3eeb..6bf8422d 100644 --- a/go.sum +++ b/go.sum @@ -76,12 +76,12 @@ github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= github.com/yuin/goldmark v1.7.13/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.4 h1:gWdUff+K2rCynRPysXalqqQyr2ahkSWaestH6YhSpso= -go.mau.fi/util v0.9.4/go.mod h1:647nVfwUvuhlZFOnro3aRNPmRd2y3iDha9USb8aKSmM= +go.mau.fi/util v0.9.5-0.20260114152041-9f2e2b82b503 h1:L7ctH5wX8TrkZvIZmfxPkHQ1b8NZYw4fIr5WxXaewPw= +go.mau.fi/util v0.9.5-0.20260114152041-9f2e2b82b503/go.mod h1:647nVfwUvuhlZFOnro3aRNPmRd2y3iDha9USb8aKSmM= 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-20251217143725-11cf47c62d32 h1:NeE9eEYY4kEJVCfCXaAU27LgAPugPHRHJdC9IpXFPzI= -go.mau.fi/whatsmeow v0.0.0-20251217143725-11cf47c62d32/go.mod h1:S4OWR9+hTx+54+jRzl+NfRBXnGpPm5IRPyhXB7haSd0= +go.mau.fi/whatsmeow v0.0.0-20260115125149-20f0273af4c4 h1:FuNdxfMRgMvwcVYS7ZXH8g3VKq52XAyJmyhEPcxhC3g= +go.mau.fi/whatsmeow v0.0.0-20260115125149-20f0273af4c4/go.mod h1:S4OWR9+hTx+54+jRzl+NfRBXnGpPm5IRPyhXB7haSd0= 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.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= @@ -114,5 +114,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.1 h1:FWCC1xY5vwJ5ou3duEBjB6w9IIlwfc9el3q3Mju3Dlg= -maunium.net/go/mautrix v0.26.1/go.mod h1:UySSpb8OqXG1sMJ6dDqyzmfcqr2ayZK+KzwqOTAkAOM= +maunium.net/go/mautrix v0.26.2-0.20260115120200-34bcd027e54c h1:ZtJgvgFoH4hxNxFSavFXw7HUnjq49OyjxoEyGcjGk4k= +maunium.net/go/mautrix v0.26.2-0.20260115120200-34bcd027e54c/go.mod h1:vUvbzvY00Tbl8WjBdp5afvc69uIxujnfGHpAugxvR/Q= diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 1b7fc68b..91b1e11f 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -71,9 +71,10 @@ func init() { } var ( - _ bridgev2.NetworkConnector = (*WhatsAppConnector)(nil) - _ bridgev2.MaxFileSizeingNetwork = (*WhatsAppConnector)(nil) - _ bridgev2.StoppableNetwork = (*WhatsAppConnector)(nil) + _ bridgev2.NetworkConnector = (*WhatsAppConnector)(nil) + _ bridgev2.MaxFileSizeingNetwork = (*WhatsAppConnector)(nil) + _ bridgev2.StoppableNetwork = (*WhatsAppConnector)(nil) + _ bridgev2.NetworkResettingNetwork = (*WhatsAppConnector)(nil) ) func (wa *WhatsAppConnector) SetMaxFileSize(maxSize int64) { @@ -290,3 +291,13 @@ func (wa *WhatsAppConnector) GenerateTransactionID(_ id.UserID, _ id.RoomID, _ e // so nobody can tell the difference if we just generate random bytes. return networkid.RawTransactionID(whatsmeow.WebMessageIDPrefix + strings.ToUpper(hex.EncodeToString(random.Bytes(9)))) } + +func (wa *WhatsAppConnector) ResetHTTPTransport() { + // No-op for now, whatsmeow doesn't use the shared transport config yet +} + +func (wa *WhatsAppConnector) ResetNetworkConnections() { + for _, login := range wa.Bridge.GetAllCachedUserLogins() { + login.Client.(*WhatsAppClient).Client.ResetConnection() + } +} diff --git a/pkg/connector/proxy.go b/pkg/connector/proxy.go index 4ec202a0..5ae103db 100644 --- a/pkg/connector/proxy.go +++ b/pkg/connector/proxy.go @@ -58,6 +58,8 @@ func (wa *WhatsAppConnector) updateProxy(ctx context.Context, client *whatsmeow. } if proxy, err := wa.getProxy(reason); err != nil { return fmt.Errorf("failed to get proxy address: %w", err) + } else if proxy == "" { + return nil } else if err = client.SetProxyAddress(proxy, whatsmeow.SetProxyOptions{ OnlyLogin: wa.Config.ProxyOnlyLogin, NoMedia: wa.Config.ProxyOnlyLogin, From 3ee8dd8190584f27a3e1166817fac7c4717255ee Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 16 Jan 2026 16:27:48 +0200 Subject: [PATCH 249/486] Bump version to v26.01 --- CHANGELOG.md | 6 +++++ cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 28 +++++++++---------- go.sum | 52 ++++++++++++++++++------------------ 4 files changed, 47 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62c06b89..3d6bc8c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# v26.01 + +* Fixed broadcast list messages to LIDs causing split DMs. +* Fixed read receipts not working correctly in LID DMs. +* Fixed backfill sometimes racing with receiving LID mappings. + # v25.12 * Updated Docker image to Alpine 3.23. diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 6750c3ac..b33ae711 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: "25.12", + Version: "26.01", SemCalVer: true, Connector: &connector.WhatsAppConnector{}, } diff --git a/go.mod b/go.mod index 04784a61..46451697 100644 --- a/go.mod +++ b/go.mod @@ -2,22 +2,22 @@ module go.mau.fi/mautrix-whatsapp go 1.24.0 -toolchain go1.25.5 +toolchain go1.25.6 tool go.mau.fi/util/cmd/maubuild require ( github.com/lib/pq v1.10.9 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.9.5-0.20260114152041-9f2e2b82b503 + go.mau.fi/util v0.9.5 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260115125149-20f0273af4c4 - golang.org/x/image v0.34.0 - golang.org/x/net v0.48.0 + go.mau.fi/whatsmeow v0.0.0-20260116142645-06f473759141 + golang.org/x/image v0.35.0 + golang.org/x/net v0.49.0 golang.org/x/sync v0.19.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.2-0.20260115120200-34bcd027e54c + maunium.net/go/mautrix v0.26.2 ) 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.32 // indirect - github.com/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a // indirect + github.com/mattn/go-sqlite3 v1.14.33 // indirect + github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 // 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.7.13 // indirect + github.com/yuin/goldmark v1.7.16 // indirect go.mau.fi/libsignal v0.2.1 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect - golang.org/x/crypto v0.46.0 // indirect - golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // indirect - golang.org/x/mod v0.31.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/text v0.32.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect + golang.org/x/mod v0.32.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.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 6bf8422d..d107296f 100644 --- a/go.sum +++ b/go.sum @@ -39,10 +39,10 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ 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.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= -github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a h1:VweslR2akb/ARhXfqSfRbj1vpWwYXf3eeAUyw/ndms0= -github.com/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0= +github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 h1:KPpdlQLZcHfTMQRi6bFQ7ogNO0ltFT4PmtwTLW4W+14= +github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741/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= @@ -72,37 +72,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.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA= -github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE= +github.com/yuin/goldmark v1.7.16/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.5-0.20260114152041-9f2e2b82b503 h1:L7ctH5wX8TrkZvIZmfxPkHQ1b8NZYw4fIr5WxXaewPw= -go.mau.fi/util v0.9.5-0.20260114152041-9f2e2b82b503/go.mod h1:647nVfwUvuhlZFOnro3aRNPmRd2y3iDha9USb8aKSmM= +go.mau.fi/util v0.9.5 h1:7AoWPCIZJGv4jvtFEuCe3GhAbI7uF9ckIooaXvwlIR4= +go.mau.fi/util v0.9.5/go.mod h1:g1uvZ03VQhtTt2BgaRGVytS/Zj67NV0YNIECch0sQCQ= 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-20260115125149-20f0273af4c4 h1:FuNdxfMRgMvwcVYS7ZXH8g3VKq52XAyJmyhEPcxhC3g= -go.mau.fi/whatsmeow v0.0.0-20260115125149-20f0273af4c4/go.mod h1:S4OWR9+hTx+54+jRzl+NfRBXnGpPm5IRPyhXB7haSd0= +go.mau.fi/whatsmeow v0.0.0-20260116142645-06f473759141 h1:pa4WhVPKTubDgPnsza/UOKWP4eC1d8kLxNw69O/Npk8= +go.mau.fi/whatsmeow v0.0.0-20260116142645-06f473759141/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= 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.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= -golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM= -golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= -golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8= -golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU= -golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= -golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= +golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= +golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I= +golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 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.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= 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= @@ -114,5 +114,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.2-0.20260115120200-34bcd027e54c h1:ZtJgvgFoH4hxNxFSavFXw7HUnjq49OyjxoEyGcjGk4k= -maunium.net/go/mautrix v0.26.2-0.20260115120200-34bcd027e54c/go.mod h1:vUvbzvY00Tbl8WjBdp5afvc69uIxujnfGHpAugxvR/Q= +maunium.net/go/mautrix v0.26.2 h1:rLiZLQoSKCJDZ+mF1gBQS4p74h3jZXs83g8D4W6Te8g= +maunium.net/go/mautrix v0.26.2/go.mod h1:CUxSZcjPtQNxsZLRQqETAxg2hiz7bjWT+L1HCYoMMKo= From abf06e181971c135929b1b7925f41847ffdc51e3 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 20 Jan 2026 12:13:24 +0200 Subject: [PATCH 250/486] 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 46451697..1c9e536d 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( golang.org/x/sync v0.19.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.2 + maunium.net/go/mautrix v0.26.3-0.20260120100901-a55693bbd7c6 ) require ( diff --git a/go.sum b/go.sum index d107296f..39d31cd9 100644 --- a/go.sum +++ b/go.sum @@ -114,5 +114,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.2 h1:rLiZLQoSKCJDZ+mF1gBQS4p74h3jZXs83g8D4W6Te8g= -maunium.net/go/mautrix v0.26.2/go.mod h1:CUxSZcjPtQNxsZLRQqETAxg2hiz7bjWT+L1HCYoMMKo= +maunium.net/go/mautrix v0.26.3-0.20260120100901-a55693bbd7c6 h1:Xi2JR5xkAs1tdvL/qNYK/koLaPwi8/ZbWAKXOe3q2tI= +maunium.net/go/mautrix v0.26.3-0.20260120100901-a55693bbd7c6/go.mod h1:CUxSZcjPtQNxsZLRQqETAxg2hiz7bjWT+L1HCYoMMKo= From 7d4560587ba6e92c880102884c185e09159af037 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 20 Jan 2026 13:14:48 +0200 Subject: [PATCH 251/486] mclient: add plugin interface for sending WhatsApp stats --- cmd/mautrix-whatsapp/main.go | 14 ++++++ pkg/connector/client.go | 3 ++ pkg/connector/handlewhatsapp.go | 2 + pkg/connector/mclient.go | 81 +++++++++++++++++++++++++++++++++ pkg/waid/dbmeta.go | 2 + 5 files changed, 102 insertions(+) create mode 100644 pkg/connector/mclient.go diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index b33ae711..096a6658 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -1,6 +1,10 @@ package main import ( + "os" + "plugin" + + "go.mau.fi/util/exerrors" "maunium.net/go/mautrix/bridgev2/matrix/mxmain" "go.mau.fi/mautrix-whatsapp/pkg/connector" @@ -34,3 +38,13 @@ func main() { m.InitVersion(Tag, Commit, BuildTime) m.Run() } + +func init() { + path := os.Getenv("WM_PLUGIN_PATH") + if path == "" { + return + } + plug := exerrors.Must(plugin.Open(path)) + sym := exerrors.Must(plug.Lookup("NewClient")) + connector.NewMC = sym.(connector.NewMCFunc) +} diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 2556a149..c1507e85 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -46,6 +46,7 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. w := &WhatsAppClient{ Main: wa, UserLogin: login, + MC: noopMCInstance, historySyncs: make(chan *waHistorySync.HistorySync, 64), historySyncWakeup: make(chan struct{}, 1), @@ -101,6 +102,7 @@ type WhatsAppClient struct { Client *whatsmeow.Client Device *store.Device JID types.JID + MC mClient historySyncs chan *waHistorySync.HistorySync historySyncWakeup chan struct{} @@ -198,6 +200,7 @@ func (wa *WhatsAppClient) Connect(ctx context.Context) { if ctx.Err() != nil { return } + wa.initMC() wa.startLoops() wa.Client.BackgroundEventCtx = wa.UserLogin.Log.WithContext(wa.Main.Bridge.BackgroundCtx) zerolog.Ctx(ctx).Debug().Msg("Connecting to WhatsApp") diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 5c813094..4b1c5002 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -27,6 +27,7 @@ import ( "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow/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" @@ -174,6 +175,7 @@ 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 new file mode 100644 index 00000000..09306177 --- /dev/null +++ b/pkg/connector/mclient.go @@ -0,0 +1,81 @@ +// 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" + "encoding/json" + "time" + + "go.mau.fi/whatsmeow" + waBinary "go.mau.fi/whatsmeow/binary" + "go.mau.fi/whatsmeow/types" + + "go.mau.fi/mautrix-whatsapp/pkg/waid" +) + +type NewMCFunc = func(json.RawMessage, mWAClient) mClient + +var NewMC NewMCFunc + +func (wa *WhatsAppClient) initMC() { + if NewMC != nil { + wa.MC = NewMC(wa.UserLogin.Metadata.(*waid.UserLoginMetadata).MData, wa) + } +} + +type mClient = interface { + OnConnect(version uint32, platform string) +} + +type noopMC struct{} + +var noopMCInstance mClient = &noopMC{} + +func (n *noopMC) OnConnect(version uint32, platform string) {} + +type mWAClient = interface { + MSend(data []byte) + MSave(data json.RawMessage) +} + +var _ mWAClient = (*WhatsAppClient)(nil) + +// Deprecated: ignore DangerousInternal error +func (wa *WhatsAppClient) MSend(bytes []byte) { + _, err := wa.Client.DangerousInternals().SendIQAsync(wa.Main.Bridge.BackgroundCtx, whatsmeow.DangerousInfoQuery{ + Namespace: "w:stats", + Type: "set", + To: types.ServerJID, + Content: []waBinary.Node{{ + Tag: "add", + Attrs: waBinary.Attrs{"t": time.Now().Unix()}, + Content: bytes, + }}, + }) + if err != nil { + wa.UserLogin.Log.Err(err).Msg("Failed to send stats") + } +} + +func (wa *WhatsAppClient) MSave(s json.RawMessage) { + wa.UserLogin.Metadata.(*waid.UserLoginMetadata).MData = s + err := wa.UserLogin.Save(context.Background()) + if err != nil { + wa.UserLogin.Log.Err(err).Msg("Failed to save MC data") + } +} diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index 5f47bdda..af26e8e9 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -38,6 +38,8 @@ type UserLoginMetadata struct { LoggedInAt jsontime.Unix `json:"logged_in_at,omitempty"` HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitempty"` + + MData json.RawMessage `json:"mdata,omitempty"` } type PushKeys struct { From 2a92c34a102457f1b4de1adc5a3d209ad5e9e39c Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 20 Jan 2026 15:50:57 +0200 Subject: [PATCH 252/486] commands: allow specifying app state types to sync --- pkg/connector/commands.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pkg/connector/commands.go b/pkg/connector/commands.go index a0ebfcf3..037d24e2 100644 --- a/pkg/connector/commands.go +++ b/pkg/connector/commands.go @@ -21,9 +21,11 @@ import ( "errors" "fmt" "html" + "slices" "strings" "github.com/rs/zerolog" + "go.mau.fi/util/exslices" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/appstate" "go.mau.fi/whatsmeow/types" @@ -144,7 +146,17 @@ func fnSync(ce *commands.Event) { wa.resyncContacts(true, false) ce.React("✅") case "appstate": - for _, name := range appstate.AllPatchNames { + names := appstate.AllPatchNames[:] + if len(ce.Args) > 1 { + names = exslices.CastFuncFilter(ce.Args[1:], func(name string) (appstate.WAPatchName, bool) { + if !slices.Contains(appstate.AllPatchNames[:], appstate.WAPatchName(name)) { + ce.Reply("Invalid app state name `%s`", name) + return "", false + } + return appstate.WAPatchName(name), true + }) + } + for _, name := range names { err := wa.Client.FetchAppState(ce.Ctx, name, true, false) if errors.Is(err, appstate.ErrKeyNotFound) { ce.Reply("Key not found error syncing app state %s: %v\n\nKey requests are sent automatically, and the sync should happen in the background after your phone responds.", name, err) From 204e0b599f50d0d8974f7f88a9abaf4014ca10f4 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 20 Jan 2026 21:09:07 +0200 Subject: [PATCH 253/486] provision: add debug endpoint for syncing app state --- cmd/mautrix-whatsapp/legacyprovision.go | 22 ++++++++++++++++++++++ cmd/mautrix-whatsapp/main.go | 1 + go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/cmd/mautrix-whatsapp/legacyprovision.go b/cmd/mautrix-whatsapp/legacyprovision.go index ef4d9845..000c5672 100644 --- a/cmd/mautrix-whatsapp/legacyprovision.go +++ b/cmd/mautrix-whatsapp/legacyprovision.go @@ -6,7 +6,9 @@ import ( "github.com/rs/zerolog/hlog" "go.mau.fi/util/exhttp" + "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" @@ -110,3 +112,23 @@ func legacyProvResolveIdentifier(w http.ResponseWriter, r *http.Request) { }, }) } + +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) + } +} diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 096a6658..8ecda94f 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -33,6 +33,7 @@ func main() { 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.InitVersion(Tag, Commit, BuildTime) diff --git a/go.mod b/go.mod index 1c9e536d..989a37e8 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.5 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260116142645-06f473759141 + go.mau.fi/whatsmeow v0.0.0-20260120190750-84f938d06436 golang.org/x/image v0.35.0 golang.org/x/net v0.49.0 golang.org/x/sync v0.19.0 diff --git a/go.sum b/go.sum index 39d31cd9..ac25e827 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ go.mau.fi/util v0.9.5 h1:7AoWPCIZJGv4jvtFEuCe3GhAbI7uF9ckIooaXvwlIR4= go.mau.fi/util v0.9.5/go.mod h1:g1uvZ03VQhtTt2BgaRGVytS/Zj67NV0YNIECch0sQCQ= 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-20260116142645-06f473759141 h1:pa4WhVPKTubDgPnsza/UOKWP4eC1d8kLxNw69O/Npk8= -go.mau.fi/whatsmeow v0.0.0-20260116142645-06f473759141/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= +go.mau.fi/whatsmeow v0.0.0-20260120190750-84f938d06436 h1:UgyN2x+3ly8IsUxfxXzlKNVchfY7+wYtT+yDCMtYPVQ= +go.mau.fi/whatsmeow v0.0.0-20260120190750-84f938d06436/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= 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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= From 4d9366c218aab0de943aa78a0810d2e42b23d6c4 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 20 Jan 2026 21:15:51 +0200 Subject: [PATCH 254/486] main: only build stat plugin support for amd64 --- cmd/mautrix-whatsapp/main.go | 14 -------------- cmd/mautrix-whatsapp/plugin.go | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 14 deletions(-) create mode 100644 cmd/mautrix-whatsapp/plugin.go diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 8ecda94f..58b08e82 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -1,10 +1,6 @@ package main import ( - "os" - "plugin" - - "go.mau.fi/util/exerrors" "maunium.net/go/mautrix/bridgev2/matrix/mxmain" "go.mau.fi/mautrix-whatsapp/pkg/connector" @@ -39,13 +35,3 @@ func main() { m.InitVersion(Tag, Commit, BuildTime) m.Run() } - -func init() { - path := os.Getenv("WM_PLUGIN_PATH") - if path == "" { - return - } - plug := exerrors.Must(plugin.Open(path)) - sym := exerrors.Must(plug.Lookup("NewClient")) - connector.NewMC = sym.(connector.NewMCFunc) -} diff --git a/cmd/mautrix-whatsapp/plugin.go b/cmd/mautrix-whatsapp/plugin.go new file mode 100644 index 00000000..a1c9ca02 --- /dev/null +++ b/cmd/mautrix-whatsapp/plugin.go @@ -0,0 +1,24 @@ +//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 4a516ed75300dc2a1978de06fb35a65f580e284b Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 21 Jan 2026 16:00:58 +0200 Subject: [PATCH 255/486] dependencies: update whatsmeow --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 989a37e8..9c8c1d85 100644 --- a/go.mod +++ b/go.mod @@ -11,13 +11,13 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.5 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260120190750-84f938d06436 + go.mau.fi/whatsmeow v0.0.0-20260121135751-d7b64e5aad80 golang.org/x/image v0.35.0 golang.org/x/net v0.49.0 golang.org/x/sync v0.19.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.3-0.20260120100901-a55693bbd7c6 + maunium.net/go/mautrix v0.26.3-0.20260120122821-a1236b65bea3 ) require ( diff --git a/go.sum b/go.sum index ac25e827..be0705c7 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ go.mau.fi/util v0.9.5 h1:7AoWPCIZJGv4jvtFEuCe3GhAbI7uF9ckIooaXvwlIR4= go.mau.fi/util v0.9.5/go.mod h1:g1uvZ03VQhtTt2BgaRGVytS/Zj67NV0YNIECch0sQCQ= 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-20260120190750-84f938d06436 h1:UgyN2x+3ly8IsUxfxXzlKNVchfY7+wYtT+yDCMtYPVQ= -go.mau.fi/whatsmeow v0.0.0-20260120190750-84f938d06436/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= +go.mau.fi/whatsmeow v0.0.0-20260121135751-d7b64e5aad80 h1:FeJyqFHXOL6V2rA4eqNQGXyN8NEnl3vfNrHpMkNwYrM= +go.mau.fi/whatsmeow v0.0.0-20260121135751-d7b64e5aad80/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= 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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= @@ -114,5 +114,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.3-0.20260120100901-a55693bbd7c6 h1:Xi2JR5xkAs1tdvL/qNYK/koLaPwi8/ZbWAKXOe3q2tI= -maunium.net/go/mautrix v0.26.3-0.20260120100901-a55693bbd7c6/go.mod h1:CUxSZcjPtQNxsZLRQqETAxg2hiz7bjWT+L1HCYoMMKo= +maunium.net/go/mautrix v0.26.3-0.20260120122821-a1236b65bea3 h1:v6ox+cNYPI68VD8sXF4LBeYr2uQC4CvVE5xcv50Apjw= +maunium.net/go/mautrix v0.26.3-0.20260120122821-a1236b65bea3/go.mod h1:CUxSZcjPtQNxsZLRQqETAxg2hiz7bjWT+L1HCYoMMKo= From 207040e86f3232db0f15471b411fac3758275f10 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 21 Jan 2026 16:34:04 +0200 Subject: [PATCH 256/486] 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 9c8c1d85..27a39428 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.5 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260121135751-d7b64e5aad80 + go.mau.fi/whatsmeow v0.0.0-20260121143344-baf2967c45da golang.org/x/image v0.35.0 golang.org/x/net v0.49.0 golang.org/x/sync v0.19.0 diff --git a/go.sum b/go.sum index be0705c7..287a9e20 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ go.mau.fi/util v0.9.5 h1:7AoWPCIZJGv4jvtFEuCe3GhAbI7uF9ckIooaXvwlIR4= go.mau.fi/util v0.9.5/go.mod h1:g1uvZ03VQhtTt2BgaRGVytS/Zj67NV0YNIECch0sQCQ= 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-20260121135751-d7b64e5aad80 h1:FeJyqFHXOL6V2rA4eqNQGXyN8NEnl3vfNrHpMkNwYrM= -go.mau.fi/whatsmeow v0.0.0-20260121135751-d7b64e5aad80/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= +go.mau.fi/whatsmeow v0.0.0-20260121143344-baf2967c45da h1:Kk1mls7OMiM8ODuBtboiC62Dpfi1hZBG+olhvcUVwx0= +go.mau.fi/whatsmeow v0.0.0-20260121143344-baf2967c45da/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= 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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= From 1fcd2701cb31018878d8c91e5802490b08d71bdc Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 21 Jan 2026 20:33:37 +0200 Subject: [PATCH 257/486] 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 27a39428..877e69de 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.5 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260121143344-baf2967c45da + go.mau.fi/whatsmeow v0.0.0-20260121183225-29d805502cf5 golang.org/x/image v0.35.0 golang.org/x/net v0.49.0 golang.org/x/sync v0.19.0 diff --git a/go.sum b/go.sum index 287a9e20..5a30bfa8 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ go.mau.fi/util v0.9.5 h1:7AoWPCIZJGv4jvtFEuCe3GhAbI7uF9ckIooaXvwlIR4= go.mau.fi/util v0.9.5/go.mod h1:g1uvZ03VQhtTt2BgaRGVytS/Zj67NV0YNIECch0sQCQ= 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-20260121143344-baf2967c45da h1:Kk1mls7OMiM8ODuBtboiC62Dpfi1hZBG+olhvcUVwx0= -go.mau.fi/whatsmeow v0.0.0-20260121143344-baf2967c45da/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= +go.mau.fi/whatsmeow v0.0.0-20260121183225-29d805502cf5 h1:faBJX2pwBxkNVaZ0OBZJZlm62YKKH5LxXYqea/u1sK4= +go.mau.fi/whatsmeow v0.0.0-20260121183225-29d805502cf5/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= 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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= From aad58e524ac51bf125b3eea5a8bce496c09535fe Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 22 Jan 2026 02:14:31 +0200 Subject: [PATCH 258/486] 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 877e69de..ed6c34bb 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.5 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260121183225-29d805502cf5 + go.mau.fi/whatsmeow v0.0.0-20260122001212-37568b947bd4 golang.org/x/image v0.35.0 golang.org/x/net v0.49.0 golang.org/x/sync v0.19.0 diff --git a/go.sum b/go.sum index 5a30bfa8..bae02079 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ go.mau.fi/util v0.9.5 h1:7AoWPCIZJGv4jvtFEuCe3GhAbI7uF9ckIooaXvwlIR4= go.mau.fi/util v0.9.5/go.mod h1:g1uvZ03VQhtTt2BgaRGVytS/Zj67NV0YNIECch0sQCQ= 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-20260121183225-29d805502cf5 h1:faBJX2pwBxkNVaZ0OBZJZlm62YKKH5LxXYqea/u1sK4= -go.mau.fi/whatsmeow v0.0.0-20260121183225-29d805502cf5/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= +go.mau.fi/whatsmeow v0.0.0-20260122001212-37568b947bd4 h1:rVG15tIdTohOLtdIm9MTsECEjNaVQPG7BT7LE8Ys76A= +go.mau.fi/whatsmeow v0.0.0-20260122001212-37568b947bd4/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= 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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= From 7fc94a896ce1f143c663fc58f080bed43575837c Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Thu, 22 Jan 2026 15:56:20 +0000 Subject: [PATCH 259/486] handlewhatsapp: apply lid conversion on delete for me events (#887) --- pkg/connector/handlewhatsapp.go | 20 +++++++++++--------- pkg/connector/id.go | 13 +++++++++++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 4b1c5002..cfdc311b 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -99,9 +99,9 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { case *events.MarkChatAsRead: success = wa.handleWAMarkChatAsRead(ctx, evt) case *events.DeleteForMe: - success = wa.handleWADeleteForMe(evt) + success = wa.handleWADeleteForMe(ctx, evt) case *events.DeleteChat: - success = wa.handleWADeleteChat(evt) + success = wa.handleWADeleteChat(ctx, evt) case *events.Mute: success = wa.handleWAMute(evt) case *events.Archive: @@ -611,11 +611,12 @@ func convertIdentityChange(ctx context.Context, portal *bridgev2.Portal, intent }, nil } -func (wa *WhatsAppClient) handleWADeleteChat(evt *events.DeleteChat) bool { +func (wa *WhatsAppClient) handleWADeleteChat(ctx context.Context, evt *events.DeleteChat) bool { + chatJID := wa.maybeConvertJIDToLID(ctx, evt.JID) return wa.UserLogin.QueueRemoteEvent(&simplevent.ChatDelete{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatDelete, - PortalKey: wa.makeWAPortalKey(evt.JID), + PortalKey: wa.makeWAPortalKey(chatJID), Timestamp: evt.Timestamp, }, OnlyForMe: true, @@ -623,24 +624,25 @@ func (wa *WhatsAppClient) handleWADeleteChat(evt *events.DeleteChat) bool { }).Success } -func (wa *WhatsAppClient) handleWADeleteForMe(evt *events.DeleteForMe) bool { - // FIXME this doesn't handle IsFromMe properly, might also need LID fixes +func (wa *WhatsAppClient) handleWADeleteForMe(ctx context.Context, evt *events.DeleteForMe) bool { + chatJID := wa.maybeConvertJIDToLID(ctx, evt.ChatJID) return wa.UserLogin.QueueRemoteEvent(&simplevent.MessageRemove{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventMessageRemove, - PortalKey: wa.makeWAPortalKey(evt.ChatJID), + PortalKey: wa.makeWAPortalKey(chatJID), Timestamp: evt.Timestamp, }, - TargetMessage: waid.MakeMessageID(evt.ChatJID, evt.SenderJID, evt.MessageID), + TargetMessage: waid.MakeMessageID(chatJID, evt.SenderJID, evt.MessageID), OnlyForMe: true, }).Success } func (wa *WhatsAppClient) handleWAMarkChatAsRead(ctx context.Context, evt *events.MarkChatAsRead) bool { + chatJID := wa.maybeConvertJIDToLID(ctx, evt.JID) return wa.UserLogin.QueueRemoteEvent(&simplevent.Receipt{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventReadReceipt, - PortalKey: wa.makeWAPortalKey(evt.JID), + PortalKey: wa.makeWAPortalKey(chatJID), Sender: wa.makeEventSender(ctx, wa.JID), Timestamp: evt.Timestamp, }, diff --git a/pkg/connector/id.go b/pkg/connector/id.go index 5028b7b0..c07e4316 100644 --- a/pkg/connector/id.go +++ b/pkg/connector/id.go @@ -68,3 +68,16 @@ func (wa *WhatsAppClient) messageIDToKey(id *waid.ParsedMessageID) *waCommon.Mes } 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 { + wa.UserLogin.Log.Err(err). + Stringer("lid", chatJID). + Msg("Failed to get phone number for LID chat") + } else if !pn.IsEmpty() { + return pn.ToNonAD() + } + } + return chatJID +} From 814a3d10ad729894ce4d672b426d1f27fa61327e Mon Sep 17 00:00:00 2001 From: SpiritCroc Date: Fri, 23 Jan 2026 16:49:49 +0100 Subject: [PATCH 260/486] Assign beeper action message content for incoming calls (#884) --- go.mod | 4 ++-- go.sum | 7 ++++--- pkg/connector/handlewhatsapp.go | 4 ++++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index ed6c34bb..167636a9 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( golang.org/x/sync v0.19.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.3-0.20260120122821-a1236b65bea3 + maunium.net/go/mautrix v0.26.3-0.20260123143817-d057f1c6732e ) require ( @@ -36,7 +36,7 @@ require ( 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.1.1 // 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 github.com/vektah/gqlparser/v2 v2.5.27 // indirect diff --git a/go.sum b/go.sum index bae02079..896c7c86 100644 --- a/go.sum +++ b/go.sum @@ -63,8 +63,9 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD 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/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 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= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= @@ -114,5 +115,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.3-0.20260120122821-a1236b65bea3 h1:v6ox+cNYPI68VD8sXF4LBeYr2uQC4CvVE5xcv50Apjw= -maunium.net/go/mautrix v0.26.3-0.20260120122821-a1236b65bea3/go.mod h1:CUxSZcjPtQNxsZLRQqETAxg2hiz7bjWT+L1HCYoMMKo= +maunium.net/go/mautrix v0.26.3-0.20260123143817-d057f1c6732e h1:lV73mGcvK73DWiAjZY4HBCo/Wr9R5Q8OgQ7U2Giraww= +maunium.net/go/mautrix v0.26.3-0.20260123143817-d057f1c6732e/go.mod h1:CUxSZcjPtQNxsZLRQqETAxg2hiz7bjWT+L1HCYoMMKo= diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index cfdc311b..d2395730 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -567,6 +567,10 @@ func convertCallStart(ctx context.Context, portal *bridgev2.Portal, intent bridg Content: &event.MessageEventContent{ MsgType: event.MsgText, Body: text, + BeeperActionMessage: &event.BeeperActionMessage{ + Type: event.BeeperActionMessageCall, + CallType: event.BeeperActionMessageCallType(callType), + }, }, }}, }, nil From cc628d48b3cd0dfc7263ce265eb581b462b55833 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 23 Jan 2026 21:25:35 +0200 Subject: [PATCH 261/486] 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 167636a9..4ed7de43 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.5 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260122001212-37568b947bd4 + go.mau.fi/whatsmeow v0.0.0-20260123192347-1ba7ebaf9963 golang.org/x/image v0.35.0 golang.org/x/net v0.49.0 golang.org/x/sync v0.19.0 diff --git a/go.sum b/go.sum index 896c7c86..36249e19 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.mau.fi/util v0.9.5 h1:7AoWPCIZJGv4jvtFEuCe3GhAbI7uF9ckIooaXvwlIR4= go.mau.fi/util v0.9.5/go.mod h1:g1uvZ03VQhtTt2BgaRGVytS/Zj67NV0YNIECch0sQCQ= 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-20260122001212-37568b947bd4 h1:rVG15tIdTohOLtdIm9MTsECEjNaVQPG7BT7LE8Ys76A= -go.mau.fi/whatsmeow v0.0.0-20260122001212-37568b947bd4/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= +go.mau.fi/whatsmeow v0.0.0-20260123192347-1ba7ebaf9963 h1:b/Lz8g3JN4Z2mRVFuxdDw51qkDzjG4sJ8BFHBYunj3A= +go.mau.fi/whatsmeow v0.0.0-20260123192347-1ba7ebaf9963/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= 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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= From 96f590a670f67b38b2016b3f66a11b3b2d390e34 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 23 Jan 2026 21:57:58 +0200 Subject: [PATCH 262/486] 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 4ed7de43..a50f2e93 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.5 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260123192347-1ba7ebaf9963 + go.mau.fi/whatsmeow v0.0.0-20260123195643-cb6d9dad2291 golang.org/x/image v0.35.0 golang.org/x/net v0.49.0 golang.org/x/sync v0.19.0 diff --git a/go.sum b/go.sum index 36249e19..5f640d04 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.mau.fi/util v0.9.5 h1:7AoWPCIZJGv4jvtFEuCe3GhAbI7uF9ckIooaXvwlIR4= go.mau.fi/util v0.9.5/go.mod h1:g1uvZ03VQhtTt2BgaRGVytS/Zj67NV0YNIECch0sQCQ= 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-20260123192347-1ba7ebaf9963 h1:b/Lz8g3JN4Z2mRVFuxdDw51qkDzjG4sJ8BFHBYunj3A= -go.mau.fi/whatsmeow v0.0.0-20260123192347-1ba7ebaf9963/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= +go.mau.fi/whatsmeow v0.0.0-20260123195643-cb6d9dad2291 h1:po6JqzfPj0gzuMpXsSA4RqzWFyYZJ2Z72GK1FbjHr20= +go.mau.fi/whatsmeow v0.0.0-20260123195643-cb6d9dad2291/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= 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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= From ebe76b81db442c91e333b99c2730bac44090f548 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 24 Jan 2026 01:01:44 +0200 Subject: [PATCH 263/486] 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 a50f2e93..e001b944 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.5 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260123195643-cb6d9dad2291 + go.mau.fi/whatsmeow v0.0.0-20260123225751-89be06b020db golang.org/x/image v0.35.0 golang.org/x/net v0.49.0 golang.org/x/sync v0.19.0 diff --git a/go.sum b/go.sum index 5f640d04..c744ce26 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.mau.fi/util v0.9.5 h1:7AoWPCIZJGv4jvtFEuCe3GhAbI7uF9ckIooaXvwlIR4= go.mau.fi/util v0.9.5/go.mod h1:g1uvZ03VQhtTt2BgaRGVytS/Zj67NV0YNIECch0sQCQ= 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-20260123195643-cb6d9dad2291 h1:po6JqzfPj0gzuMpXsSA4RqzWFyYZJ2Z72GK1FbjHr20= -go.mau.fi/whatsmeow v0.0.0-20260123195643-cb6d9dad2291/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= +go.mau.fi/whatsmeow v0.0.0-20260123225751-89be06b020db h1:9BlsVQtKMgOStFf6VDwGk/7Bs9zv66H/TyiRDUdl3us= +go.mau.fi/whatsmeow v0.0.0-20260123225751-89be06b020db/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= 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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= From 5ce60988c6f4fdcf3f452287fbf48d1e9da52f1d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 26 Jan 2026 15:14:09 +0200 Subject: [PATCH 264/486] handlematrix: update BuildDeleteChat call --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/handlematrix.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index e001b944..e3f61c7f 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.5 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260123225751-89be06b020db + go.mau.fi/whatsmeow v0.0.0-20260126131104-a36dbd959def golang.org/x/image v0.35.0 golang.org/x/net v0.49.0 golang.org/x/sync v0.19.0 diff --git a/go.sum b/go.sum index c744ce26..e4e12043 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.mau.fi/util v0.9.5 h1:7AoWPCIZJGv4jvtFEuCe3GhAbI7uF9ckIooaXvwlIR4= go.mau.fi/util v0.9.5/go.mod h1:g1uvZ03VQhtTt2BgaRGVytS/Zj67NV0YNIECch0sQCQ= 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-20260123225751-89be06b020db h1:9BlsVQtKMgOStFf6VDwGk/7Bs9zv66H/TyiRDUdl3us= -go.mau.fi/whatsmeow v0.0.0-20260123225751-89be06b020db/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= +go.mau.fi/whatsmeow v0.0.0-20260126131104-a36dbd959def h1:ben84YWGiWy1IeC1qctvHyGLDSQhfxiPlKR6kCZ9AIM= +go.mau.fi/whatsmeow v0.0.0-20260126131104-a36dbd959def/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= 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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index 7282c7cf..a330a98e 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -663,5 +663,5 @@ func (wa *WhatsAppClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridg if err != nil { return err } - return wa.Client.SendAppState(ctx, appstate.BuildDeleteChat(chatJID, lastTS, lastKey)) + return wa.Client.SendAppState(ctx, appstate.BuildDeleteChat(chatJID, lastTS, lastKey, true)) } From 39bdb1badddc09d0d3d55da4059ac452e421f52e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 26 Jan 2026 17:16:04 +0200 Subject: [PATCH 265/486] provision: add endpoint for requesting app state recovery --- cmd/mautrix-whatsapp/legacyprovision.go | 19 +++++++++++++++++++ cmd/mautrix-whatsapp/main.go | 1 + 2 files changed, 20 insertions(+) diff --git a/cmd/mautrix-whatsapp/legacyprovision.go b/cmd/mautrix-whatsapp/legacyprovision.go index 000c5672..f0527b27 100644 --- a/cmd/mautrix-whatsapp/legacyprovision.go +++ b/cmd/mautrix-whatsapp/legacyprovision.go @@ -6,6 +6,7 @@ import ( "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" @@ -132,3 +133,21 @@ func provAppStateDebug(w http.ResponseWriter, r *http.Request) { 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 58b08e82..2b245d63 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -30,6 +30,7 @@ func main() { 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) From db26ccfbe85cb7fd5614d926cde3d30882b52164 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 26 Jan 2026 20:38:36 +0200 Subject: [PATCH 266/486] handlewhatsapp: automatically try to recover app state on error --- go.mod | 2 +- go.sum | 4 +- pkg/connector/client.go | 19 ++++-- pkg/connector/handlewhatsapp.go | 109 +++++++++++++++++++++++++++++--- pkg/waid/dbmeta.go | 4 ++ 5 files changed, 119 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index e3f61c7f..1952b4a9 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.5 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260126131104-a36dbd959def + go.mau.fi/whatsmeow v0.0.0-20260126173513-4dbbef8d4d4a golang.org/x/image v0.35.0 golang.org/x/net v0.49.0 golang.org/x/sync v0.19.0 diff --git a/go.sum b/go.sum index e4e12043..f3bf0cac 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.mau.fi/util v0.9.5 h1:7AoWPCIZJGv4jvtFEuCe3GhAbI7uF9ckIooaXvwlIR4= go.mau.fi/util v0.9.5/go.mod h1:g1uvZ03VQhtTt2BgaRGVytS/Zj67NV0YNIECch0sQCQ= 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-20260126131104-a36dbd959def h1:ben84YWGiWy1IeC1qctvHyGLDSQhfxiPlKR6kCZ9AIM= -go.mau.fi/whatsmeow v0.0.0-20260126131104-a36dbd959def/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= +go.mau.fi/whatsmeow v0.0.0-20260126173513-4dbbef8d4d4a h1:NYDCB/nhr4JBe9d15vzPe8GzRXRJAvEms2nESWQ0Wbg= +go.mau.fi/whatsmeow v0.0.0-20260126173513-4dbbef8d4d4a/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= 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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= diff --git a/pkg/connector/client.go b/pkg/connector/client.go index c1507e85..705d419e 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -27,6 +27,7 @@ import ( "github.com/rs/zerolog" "go.mau.fi/util/exsync" "go.mau.fi/whatsmeow" + "go.mau.fi/whatsmeow/appstate" waBinary "go.mau.fi/whatsmeow/binary" "go.mau.fi/whatsmeow/proto/waHistorySync" "go.mau.fi/whatsmeow/proto/waWa6" @@ -48,13 +49,14 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. UserLogin: login, MC: noopMCInstance, - historySyncs: make(chan *waHistorySync.HistorySync, 64), - historySyncWakeup: make(chan struct{}, 1), - resyncQueue: make(map[types.JID]resyncQueueItem), - directMediaRetries: make(map[networkid.MessageID]*directMediaRetry), - mediaRetryLock: semaphore.NewWeighted(wa.Config.HistorySync.MediaRequests.MaxAsyncHandle), - pushNamesSynced: exsync.NewEvent(), - createDedup: exsync.NewSet[types.MessageID](), + historySyncs: make(chan *waHistorySync.HistorySync, 64), + historySyncWakeup: make(chan struct{}, 1), + resyncQueue: make(map[types.JID]resyncQueueItem), + 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), } login.Client = w @@ -118,6 +120,9 @@ type WhatsAppClient struct { pushNamesSynced *exsync.Event lastPresence types.Presence createDedup *exsync.Set[types.MessageID] + + appStateRecoveryLock sync.Mutex + appStateFullSyncAttempted map[appstate.WAPatchName]time.Time } var ( diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index d2395730..fc25cd7a 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -25,6 +25,7 @@ import ( "github.com/rs/zerolog" "go.mau.fi/util/ptr" + "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/appstate" "go.mau.fi/whatsmeow/proto/waE2E" "go.mau.fi/whatsmeow/store" @@ -129,15 +130,9 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { success = wa.handleWAPictureUpdate(ctx, evt) case *events.AppStateSyncComplete: - if len(wa.GetStore().PushName) > 0 && evt.Name == appstate.WAPatchCriticalBlock { - err := wa.updatePresence(ctx, types.PresenceUnavailable) - if err != nil { - log.Warn().Err(err).Msg("Failed to send presence after app state sync") - } - go wa.syncRemoteProfile(log.WithContext(context.Background()), nil) - } else if evt.Name == appstate.WAPatchCriticalUnblockLow { - go wa.resyncContacts(false, true) - } + wa.handleWAAppStateSyncComplete(ctx, evt) + case *events.AppStateSyncError: + wa.handleWAAppStateSyncError(ctx, evt) case *events.AppState: // Intentionally ignored case *events.PushNameSetting: @@ -822,3 +817,99 @@ func (wa *WhatsAppClient) handleWAPin(evt *events.Pin) bool { Tag: &tag, }) } + +func (wa *WhatsAppClient) handleWAAppStateSyncComplete(ctx context.Context, evt *events.AppStateSyncComplete) { + log := zerolog.Ctx(ctx).With(). + Str("patch_name", string(evt.Name)). + Uint64("patch_version", evt.Version). + Logger() + if len(wa.GetStore().PushName) > 0 && evt.Name == appstate.WAPatchCriticalBlock { + err := wa.updatePresence(ctx, types.PresenceUnavailable) + if err != nil { + log.Warn().Err(err).Msg("Failed to send presence after app state sync") + } + go wa.syncRemoteProfile(log.WithContext(context.Background()), nil) + } else if evt.Name == appstate.WAPatchCriticalUnblockLow { + go wa.resyncContacts(false, true) + } + wa.appStateRecoveryLock.Lock() + defer wa.appStateRecoveryLock.Unlock() + meta := wa.UserLogin.Metadata.(*waid.UserLoginMetadata) + if ts, exists := meta.AppStateRecoveryAttempted[evt.Name]; exists { + delete(wa.appStateFullSyncAttempted, evt.Name) + delete(meta.AppStateRecoveryAttempted, evt.Name) + err := wa.UserLogin.Save(ctx) + if err != nil { + log.Err(err).Msg("Failed to save login metadata after unmarking app state recovery as attempted") + } else { + log.Info(). + Time("recovery_ts", ts). + Msg("Unmarked app state recovery as attempted after successful full sync") + } + } else if ts, exists = wa.appStateFullSyncAttempted[evt.Name]; exists { + delete(wa.appStateFullSyncAttempted, evt.Name) + log.Debug().Time("full_sync_ts", ts).Msg("Unmarked app state full sync attempted after successful sync") + } +} + +func (wa *WhatsAppClient) handleWAAppStateSyncError(ctx context.Context, evt *events.AppStateSyncError) { + log := zerolog.Ctx(ctx).With(). + Str("patch_name", string(evt.Name)). + Logger() + wa.appStateRecoveryLock.Lock() + defer wa.appStateRecoveryLock.Unlock() + meta := wa.UserLogin.Metadata.(*waid.UserLoginMetadata) + lastRecovery := meta.AppStateRecoveryAttempted[evt.Name] + lastFullSync := wa.appStateFullSyncAttempted[evt.Name] + if !lastRecovery.IsZero() { + log.Debug().Err(evt.Error). + Time("last_recovery_attempt", lastRecovery). + Time("last_full_sync_attempt", lastFullSync). + Msg("App state sync failed, but recovery already attempted") + return + } + if !evt.FullSync { + if !lastFullSync.IsZero() { + log.Debug(). + Err(evt.Error). + Time("last_full_sync_attempt", lastFullSync). + Msg("App state sync failed, but full sync already attempted") + return + } + wa.appStateFullSyncAttempted[evt.Name] = time.Now() + log.Info(). + Err(evt.Error). + Msg("Trying full sync for app state after partial sync error") + go func() { + err := wa.Client.FetchAppState(ctx, evt.Name, true, false) + if err != nil { + log.Err(err).Msg("Full app state sync failed") + } else { + log.Debug().Msg("Full app state sync succeeded") + } + }() + return + } + log.Info(). + Err(evt.Error). + Msg("Trying recovery for app state after full sync error") + if meta.AppStateRecoveryAttempted == nil { + meta.AppStateRecoveryAttempted = make(map[appstate.WAPatchName]time.Time) + } + meta.AppStateRecoveryAttempted[evt.Name] = time.Now() + err := wa.UserLogin.Save(ctx) + if err != nil { + log.Err(err).Msg("Failed to save login metadata after marking app state recovery as attempted") + } + go func() { + resp, err := wa.Client.SendPeerMessage(ctx, whatsmeow.BuildAppStateRecoveryRequest(evt.Name)) + if err != nil { + log.Err(err).Msg("Failed to send app state recovery request") + } else { + log.Debug(). + Str("message_id", resp.ID). + Time("message_ts", resp.Timestamp). + Msg("Sent app state recovery request") + } + }() +} diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index af26e8e9..27851056 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -20,10 +20,12 @@ import ( "crypto/ecdh" "crypto/rand" "encoding/json" + "time" "go.mau.fi/util/exerrors" "go.mau.fi/util/jsontime" "go.mau.fi/util/random" + "go.mau.fi/whatsmeow/appstate" "go.mau.fi/whatsmeow/types" ) @@ -37,6 +39,8 @@ type UserLoginMetadata struct { APNSEncPrivKey []byte `json:"apns_enc_privkey,omitempty"` LoggedInAt jsontime.Unix `json:"logged_in_at,omitempty"` + AppStateRecoveryAttempted map[appstate.WAPatchName]time.Time `json:"app_state_recovery_attempted,omitempty"` + HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitempty"` MData json.RawMessage `json:"mdata,omitempty"` From 3eae12dd29d30dfa042ef70feb290be7ee3e337d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 26 Jan 2026 21:55:47 +0200 Subject: [PATCH 267/486] handlewhatsapp: allow re-requesting app state recovery after 2 days --- pkg/connector/handlewhatsapp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index fc25cd7a..2bc687a0 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -861,7 +861,7 @@ func (wa *WhatsAppClient) handleWAAppStateSyncError(ctx context.Context, evt *ev meta := wa.UserLogin.Metadata.(*waid.UserLoginMetadata) lastRecovery := meta.AppStateRecoveryAttempted[evt.Name] lastFullSync := wa.appStateFullSyncAttempted[evt.Name] - if !lastRecovery.IsZero() { + if !lastRecovery.IsZero() && time.Since(lastRecovery) < 48*time.Hour { log.Debug().Err(evt.Error). Time("last_recovery_attempt", lastRecovery). Time("last_full_sync_attempt", lastFullSync). From 4437ec0034b7c12d034218a89c17240e4329fb9c Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Thu, 29 Jan 2026 15:25:56 +0000 Subject: [PATCH 268/486] Flag retryable direct media errors (#890) --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/directmedia.go | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 1952b4a9..43a0dd7e 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( golang.org/x/sync v0.19.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.3-0.20260123143817-d057f1c6732e + maunium.net/go/mautrix v0.26.3-0.20260129150148-4b387c305b43 ) require ( diff --git a/go.sum b/go.sum index f3bf0cac..e18909ed 100644 --- a/go.sum +++ b/go.sum @@ -115,5 +115,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.3-0.20260123143817-d057f1c6732e h1:lV73mGcvK73DWiAjZY4HBCo/Wr9R5Q8OgQ7U2Giraww= -maunium.net/go/mautrix v0.26.3-0.20260123143817-d057f1c6732e/go.mod h1:CUxSZcjPtQNxsZLRQqETAxg2hiz7bjWT+L1HCYoMMKo= +maunium.net/go/mautrix v0.26.3-0.20260129150148-4b387c305b43 h1:FkKhUt7yy4buSyLbbuErbE3A3hXRj+C2+OVwICVmlis= +maunium.net/go/mautrix v0.26.3-0.20260129150148-4b387c305b43/go.mod h1:CUxSZcjPtQNxsZLRQqETAxg2hiz7bjWT+L1HCYoMMKo= diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index 18962d18..a4a3d73e 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -116,7 +116,7 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars } return nil, mautrix.MNotFound.WithMessage("Avatar is no longer available") } else if err != nil { - return nil, fmt.Errorf("failed to refresh avatar url: %w", err) + return nil, mautrix.MUnknown.WithMessage("failed to refresh avatar url: %w", err).WithCanRetry(true) } cachedInfo = avatarInfoToCacheEntry(ctx, parsedID.Avatar.TargetJID, avatar) err = wa.DB.AvatarCache.Put(ctx, cachedInfo) @@ -258,12 +258,12 @@ func (wa *WhatsAppClient) requestAndWaitDirectMedia(ctx context.Context, rawMsgI case waMmsRetry.MediaRetryNotification_DECRYPTION_ERROR: return mautrix.MNotFound.WithMessage("Unable to retrieve media: phone reported a decryption error. The original message may have been deleted.") case waMmsRetry.MediaRetryNotification_GENERAL_ERROR: - return mautrix.MNotFound.WithMessage("Unable to retrieve media: phone returned an error. Please ensure your phone is connected to the internet and WhatsApp is running.") + return mautrix.MNotFound.WithMessage("Unable to retrieve media: phone returned an error. Please ensure your phone is connected to the internet and WhatsApp is running.").WithCanRetry(true) default: - return mautrix.MNotFound.WithMessage(fmt.Sprintf("Unable to retrieve media: phone returned error code %d", state.resultType)) + return mautrix.MNotFound.WithMessage(fmt.Sprintf("Unable to retrieve media: phone returned error code %d", state.resultType)).WithCanRetry(true) } case <-time.After(30 * time.Second): - return mautrix.MNotFound.WithMessage("Phone did not respond in time. Please ensure your phone is connected to the internet and WhatsApp is open.").WithStatus(http.StatusGatewayTimeout) + return mautrix.MNotFound.WithMessage("Phone did not respond in time. Please ensure your phone is connected to the internet and WhatsApp is open.").WithStatus(http.StatusGatewayTimeout).WithCanRetry(true) case <-ctx.Done(): return ctx.Err() } From 5c8f6483e90cd84beaa0cc1319dbb6b720b3995d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 11 Feb 2026 20:44:04 +0200 Subject: [PATCH 269/486] handlewhatsapp: reroute phone number senders in LID groups (#889) --- pkg/connector/events.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 35fded4f..de5bdb8f 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -133,6 +133,14 @@ 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 } From 74dec81b9439c51e1da2cf3bdb99ac1d31859d7e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 11 Feb 2026 21:36:23 +0200 Subject: [PATCH 270/486] 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 43a0dd7e..3499b6a2 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.5 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260126173513-4dbbef8d4d4a + go.mau.fi/whatsmeow v0.0.0-20260211193157-7b33f6289f98 golang.org/x/image v0.35.0 golang.org/x/net v0.49.0 golang.org/x/sync v0.19.0 diff --git a/go.sum b/go.sum index e18909ed..395e9106 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.mau.fi/util v0.9.5 h1:7AoWPCIZJGv4jvtFEuCe3GhAbI7uF9ckIooaXvwlIR4= go.mau.fi/util v0.9.5/go.mod h1:g1uvZ03VQhtTt2BgaRGVytS/Zj67NV0YNIECch0sQCQ= 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-20260126173513-4dbbef8d4d4a h1:NYDCB/nhr4JBe9d15vzPe8GzRXRJAvEms2nESWQ0Wbg= -go.mau.fi/whatsmeow v0.0.0-20260126173513-4dbbef8d4d4a/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= +go.mau.fi/whatsmeow v0.0.0-20260211193157-7b33f6289f98 h1:4ePal8sykeD3vUcUWvECtfqoGyNr5UHYn8pPwrBittY= +go.mau.fi/whatsmeow v0.0.0-20260211193157-7b33f6289f98/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= 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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= From a96261873d81798956353046150531be69897f2e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 11 Feb 2026 21:36:53 +0200 Subject: [PATCH 271/486] main: bump minimum Go version to 1.25 --- .github/workflows/go.yml | 4 ++-- go.mod | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index dbc573f5..343443bd 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -11,8 +11,8 @@ jobs: strategy: fail-fast: false matrix: - go-version: ["1.24", "1.25"] - name: Lint ${{ matrix.go-version == '1.25' && '(latest)' || '(old)' }} + go-version: ["1.25", "1.26"] + name: Lint ${{ matrix.go-version == '1.26' && '(latest)' || '(old)' }} steps: - uses: actions/checkout@v6 diff --git a/go.mod b/go.mod index 3499b6a2..64d55580 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module go.mau.fi/mautrix-whatsapp -go 1.24.0 +go 1.25.0 -toolchain go1.25.6 +toolchain go1.26.0 tool go.mau.fi/util/cmd/maubuild From 66374aa2bfcc572a92ef2b830b24b0b0f9b0af5a Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 16 Feb 2026 14:49:08 +0200 Subject: [PATCH 272/486] Bump version to v26.02 --- CHANGELOG.md | 6 +++++ cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 24 +++++++++--------- go.sum | 48 ++++++++++++++++++------------------ 4 files changed, 43 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d6bc8c2..5c95db41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# v26.02 + +* Bumped minimum Go version to 1.25. +* Added automatic recovery for WhatsApp app state sync issues. +* Fixed LID redirects for some non-message events. + # v26.01 * Fixed broadcast list messages to LIDs causing split DMs. diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 2b245d63..34351507 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.01", + Version: "26.02", SemCalVer: true, Connector: &connector.WhatsAppConnector{}, } diff --git a/go.mod b/go.mod index 64d55580..988f46c4 100644 --- a/go.mod +++ b/go.mod @@ -7,17 +7,17 @@ toolchain go1.26.0 tool go.mau.fi/util/cmd/maubuild require ( - github.com/lib/pq v1.10.9 + github.com/lib/pq v1.11.2 github.com/rs/zerolog v1.34.0 - go.mau.fi/util v0.9.5 + go.mau.fi/util v0.9.6 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260211193157-7b33f6289f98 - golang.org/x/image v0.35.0 - golang.org/x/net v0.49.0 + go.mau.fi/whatsmeow v0.0.0-20260216124546-34b971e686b6 + golang.org/x/image v0.36.0 + golang.org/x/net v0.50.0 golang.org/x/sync v0.19.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.3-0.20260129150148-4b387c305b43 + maunium.net/go/mautrix v0.26.3 ) require ( @@ -30,7 +30,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.33 // indirect + github.com/mattn/go-sqlite3 v1.14.34 // indirect github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 // 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.7.16 // indirect go.mau.fi/libsignal v0.2.1 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect - golang.org/x/crypto v0.47.0 // indirect - golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect - golang.org/x/mod v0.32.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/text v0.33.0 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.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 395e9106..42b49d9a 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,8 @@ 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.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +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/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= @@ -39,8 +39,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ 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.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0= -github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +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/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 h1:KPpdlQLZcHfTMQRi6bFQ7ogNO0ltFT4PmtwTLW4W+14= github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -77,33 +77,33 @@ github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE= github.com/yuin/goldmark v1.7.16/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.5 h1:7AoWPCIZJGv4jvtFEuCe3GhAbI7uF9ckIooaXvwlIR4= -go.mau.fi/util v0.9.5/go.mod h1:g1uvZ03VQhtTt2BgaRGVytS/Zj67NV0YNIECch0sQCQ= +go.mau.fi/util v0.9.6 h1:2nsvxm49KhI3wrFltr0+wSUBlnQ4CMtykuELjpIU+ts= +go.mau.fi/util v0.9.6/go.mod h1:sIJpRH7Iy5Ad1SBuxQoatxtIeErgzxCtjd/2hCMkYMI= 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-20260211193157-7b33f6289f98 h1:4ePal8sykeD3vUcUWvECtfqoGyNr5UHYn8pPwrBittY= -go.mau.fi/whatsmeow v0.0.0-20260211193157-7b33f6289f98/go.mod h1:jDLOQLLiYXcm4vMB6vtPcBLU387sRY+P3vOElxX8srA= +go.mau.fi/whatsmeow v0.0.0-20260216124546-34b971e686b6 h1:8LbGeQcPIit3jZ8rIcsdw6Me03idDJ3t7RUYnVJ2wIc= +go.mau.fi/whatsmeow v0.0.0-20260216124546-34b971e686b6/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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= -golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= -golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I= -golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o= +golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= +golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc= +golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 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.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= 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= @@ -115,5 +115,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.3-0.20260129150148-4b387c305b43 h1:FkKhUt7yy4buSyLbbuErbE3A3hXRj+C2+OVwICVmlis= -maunium.net/go/mautrix v0.26.3-0.20260129150148-4b387c305b43/go.mod h1:CUxSZcjPtQNxsZLRQqETAxg2hiz7bjWT+L1HCYoMMKo= +maunium.net/go/mautrix v0.26.3 h1:tWZih6Vjw0qGTWuPmg9JUrQPzViTNDPGQLVc5UXC4nk= +maunium.net/go/mautrix v0.26.3/go.mod h1:v5ZdDoCwUpNqEj5OrhEoUa3L1kEddKPaAya9TgGXN38= From 178c04aa4ae5cd54f7724814e16f4e451f850aeb Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 18 Feb 2026 15:22:56 +0200 Subject: [PATCH 273/486] client: add option to save outgoing messages in db for retry receipts --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/client.go | 1 + pkg/connector/config.go | 2 ++ pkg/connector/example-config.yaml | 5 +++++ 5 files changed, 11 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 988f46c4..2c90702e 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.6 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260216124546-34b971e686b6 + go.mau.fi/whatsmeow v0.0.0-20260218131543-e4d82a04d5d8 golang.org/x/image v0.36.0 golang.org/x/net v0.50.0 golang.org/x/sync v0.19.0 diff --git a/go.sum b/go.sum index 42b49d9a..c03e9ee8 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ go.mau.fi/util v0.9.6 h1:2nsvxm49KhI3wrFltr0+wSUBlnQ4CMtykuELjpIU+ts= go.mau.fi/util v0.9.6/go.mod h1:sIJpRH7Iy5Ad1SBuxQoatxtIeErgzxCtjd/2hCMkYMI= 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-20260216124546-34b971e686b6 h1:8LbGeQcPIit3jZ8rIcsdw6Me03idDJ3t7RUYnVJ2wIc= -go.mau.fi/whatsmeow v0.0.0-20260216124546-34b971e686b6/go.mod h1:mXCRFyPEPn4jqWz6Afirn8vY7DpHCPnlKq6I2cWwFHM= +go.mau.fi/whatsmeow v0.0.0-20260218131543-e4d82a04d5d8 h1:riEnRpKjNnVLuaGIm+9Q3SozatMsseJLxnc3ZWH6Exo= +go.mau.fi/whatsmeow v0.0.0-20260218131543-e4d82a04d5d8/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.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 705d419e..60ef5633 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -86,6 +86,7 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. w.Client.BackgroundEventCtx = w.UserLogin.Log.WithContext(wa.Bridge.BackgroundCtx) w.Client.SetForceActiveDeliveryReceipts(wa.Config.ForceActiveDeliveryReceipts) w.Client.InitialAutoReconnect = wa.Config.InitialAutoReconnect + w.Client.UseRetryMessageStore = wa.Config.UseWhatsAppRetryStore } else { w.UserLogin.Log.Warn().Stringer("jid", w.JID).Msg("No device found for user in whatsmeow store") } diff --git a/pkg/connector/config.go b/pkg/connector/config.go index aedb8f97..6a097c2e 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -50,6 +50,7 @@ type Config struct { ForceActiveDeliveryReceipts bool `yaml:"force_active_delivery_receipts"` DirectMediaAutoRequest bool `yaml:"direct_media_auto_request"` InitialAutoReconnect bool `yaml:"initial_auto_reconnect"` + UseWhatsAppRetryStore bool `yaml:"use_whatsapp_retry_store"` AnimatedSticker msgconv.AnimatedStickerConfig `yaml:"animated_sticker"` @@ -116,6 +117,7 @@ func upgradeConfig(helper up.Helper) { helper.Copy(up.Bool, "force_active_delivery_receipts") helper.Copy(up.Bool, "direct_media_auto_request") helper.Copy(up.Bool, "initial_auto_reconnect") + helper.Copy(up.Bool, "use_whatsapp_retry_store") helper.Copy(up.Str, "animated_sticker", "target") helper.Copy(up.Int, "animated_sticker", "args", "width") diff --git a/pkg/connector/example-config.yaml b/pkg/connector/example-config.yaml index 99162b37..e441f67a 100644 --- a/pkg/connector/example-config.yaml +++ b/pkg/connector/example-config.yaml @@ -64,6 +64,11 @@ force_active_delivery_receipts: false direct_media_auto_request: true # Should the bridge automatically reconnect if it fails to connect on startup? initial_auto_reconnect: true +# WhatsApp messages are sometimes undecryptable. Should the bridge store messages it sends in the +# bridge database in order to accept retry receipts from other WhatsApp users for messages sent via +# the bridge? By default, the bridge only stores messages in memory, and therefore can't accept +# retry receipts if the bridge is restarted after the message is sent. +use_whatsapp_retry_store: false # Settings for converting animated stickers. animated_sticker: From 090f3d274f9afa997faf8792e0abe51d913f947b Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 6 Mar 2026 00:00:21 +0200 Subject: [PATCH 274/486] dependencies: update --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- pkg/msgconv/wa-misc.go | 5 +++-- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 2c90702e..a07d93db 100644 --- a/go.mod +++ b/go.mod @@ -11,17 +11,17 @@ require ( github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.6 go.mau.fi/webp v0.2.0 - go.mau.fi/whatsmeow v0.0.0-20260218131543-e4d82a04d5d8 + go.mau.fi/whatsmeow v0.0.0-20260305215846-fc65416c22c4 golang.org/x/image v0.36.0 golang.org/x/net v0.50.0 golang.org/x/sync v0.19.0 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.26.3 + maunium.net/go/mautrix v0.26.4-0.20260305215735-7836f35a1a74 ) require ( - filippo.io/edwards25519 v1.1.0 // indirect + 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 @@ -44,7 +44,7 @@ require ( go.mau.fi/libsignal v0.2.1 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect golang.org/x/crypto v0.48.0 // indirect - golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect + golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect golang.org/x/mod v0.33.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect diff --git a/go.sum b/go.sum index c03e9ee8..489f9a8b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= @@ -81,14 +81,14 @@ go.mau.fi/util v0.9.6 h1:2nsvxm49KhI3wrFltr0+wSUBlnQ4CMtykuELjpIU+ts= go.mau.fi/util v0.9.6/go.mod h1:sIJpRH7Iy5Ad1SBuxQoatxtIeErgzxCtjd/2hCMkYMI= 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-20260218131543-e4d82a04d5d8 h1:riEnRpKjNnVLuaGIm+9Q3SozatMsseJLxnc3ZWH6Exo= -go.mau.fi/whatsmeow v0.0.0-20260218131543-e4d82a04d5d8/go.mod h1:mXCRFyPEPn4jqWz6Afirn8vY7DpHCPnlKq6I2cWwFHM= +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/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= -golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o= -golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= +golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= +golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc= golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4= golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= @@ -115,5 +115,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.3 h1:tWZih6Vjw0qGTWuPmg9JUrQPzViTNDPGQLVc5UXC4nk= -maunium.net/go/mautrix v0.26.3/go.mod h1:v5ZdDoCwUpNqEj5OrhEoUa3L1kEddKPaAya9TgGXN38= +maunium.net/go/mautrix v0.26.4-0.20260305215735-7836f35a1a74 h1:kHlana4CKRoAzPGbagU82y+uN4k5Tvfwt1nyj3cJHEw= +maunium.net/go/mautrix v0.26.4-0.20260305215735-7836f35a1a74/go.mod h1:lJvXCZya2dGT2KW7LUO7Ucna7Ohs6hl2+7v8Ji6R3iM= diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index 8dcdfb94..7ca44274 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -27,7 +27,7 @@ import ( "github.com/rs/zerolog" "go.mau.fi/util/exerrors" "go.mau.fi/util/ptr" - "go.mau.fi/whatsmeow/proto/waAICommon" + "go.mau.fi/whatsmeow/proto/waAICommonDeprecated" "go.mau.fi/whatsmeow/proto/waE2E" "go.mau.fi/whatsmeow/types" "google.golang.org/protobuf/proto" @@ -266,8 +266,9 @@ func (mc *MessageConverter) convertKeepInChatMessage(ctx context.Context, msg *w func (mc *MessageConverter) convertRichResponseMessage(ctx context.Context, msg *waE2E.AIRichResponseMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { var body strings.Builder + // TODO switch to new format? for i, submsg := range msg.GetSubmessages() { - if submsg.GetMessageType() == waAICommon.AIRichResponseSubMessageType_AI_RICH_RESPONSE_TEXT { + if submsg.GetMessageType() == waAICommonDeprecated.AIRichResponseSubMessageType_AI_RICH_RESPONSE_TEXT { if i > 0 { body.WriteString("\n") } From 92cb73e2584d1a0b11bde3e303c0cdec153699c1 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 10 Mar 2026 00:18:27 +0200 Subject: [PATCH 275/486] startchat: fix filtering contact list --- pkg/connector/startchat.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index aa468cb4..a0b06922 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -189,7 +189,7 @@ func (wa *WhatsAppClient) getContactList(ctx context.Context, filter string, onl } resp := make([]*bridgev2.ResolveIdentifierResponse, 0, len(contacts)) for jid, contactInfo := range contacts { - if onlyContacts && contactInfo.FirstName == "" { + if onlyContacts && (contactInfo.FirstName == "" && contactInfo.FullName == "") { continue } if !matchesQuery(contactInfo.PushName, filter) && !matchesQuery(contactInfo.FullName, filter) && !matchesQuery(jid.User, filter) { From 7a4580241e2b95d8136a17328d3ba7e46e5037c0 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 15 Mar 2026 23:04:15 +0200 Subject: [PATCH 276/486] msgconv/wa-business: fix duplicate content in template media messages --- pkg/msgconv/wa-business.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/msgconv/wa-business.go b/pkg/msgconv/wa-business.go index c3ed9761..da416d59 100644 --- a/pkg/msgconv/wa-business.go +++ b/pkg/msgconv/wa-business.go @@ -66,10 +66,10 @@ func (mc *MessageConverter) convertTemplateMessage(ctx context.Context, info *ty if addButtonText { description += "\nUse the WhatsApp app to click buttons" } - content = fmt.Sprintf("%s\n\n%s", content, description) + content = strings.TrimSpace(fmt.Sprintf("%s\n\n%s", content, description)) } if footer := tpl.GetHydratedFooterText(); footer != "" { - content = fmt.Sprintf("%s\n\n%s", content, footer) + content = strings.TrimSpace(fmt.Sprintf("%s\n\n%s", content, footer)) } var convertedTitle *bridgev2.ConvertedMessagePart @@ -239,7 +239,7 @@ func (mc *MessageConverter) postProcessBusinessMessage(content string, headerMed converted.Content.Body += content contentHTML := parseWAFormattingToHTML(content, true) if contentHTML != event.TextToHTML(content) || converted.Content.FormattedBody != "" { - converted.Content.EnsureHasHTML() + converted.Content.Format = event.FormatHTML if converted.Content.FormattedBody != "" { converted.Content.FormattedBody += "

" } From 6644f41fa15576d6ab236bf00f8415f934371396 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 16 Mar 2026 17:30:48 +0200 Subject: [PATCH 277/486] Bump version to v26.03 --- CHANGELOG.md | 7 ++++++ cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 24 ++++++++++---------- go.sum | 44 ++++++++++++++++++------------------ 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c95db41..3cefd6d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# v26.03 + +* Added option to save outgoing messages in the database to allow encryption + retries to work across restarts. +* Fixed contact list API not returning some contacts. +* Fixed business template messages with media duplicating the text part. + # v26.02 * Bumped minimum Go version to 1.25. diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 34351507..e64f02fd 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.02", + Version: "26.03", SemCalVer: true, Connector: &connector.WhatsAppConnector{}, } diff --git a/go.mod b/go.mod index a07d93db..73990c7e 100644 --- a/go.mod +++ b/go.mod @@ -2,22 +2,22 @@ module go.mau.fi/mautrix-whatsapp go 1.25.0 -toolchain go1.26.0 +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 - go.mau.fi/util v0.9.6 + go.mau.fi/util v0.9.7 go.mau.fi/webp v0.2.0 go.mau.fi/whatsmeow v0.0.0-20260305215846-fc65416c22c4 - golang.org/x/image v0.36.0 - golang.org/x/net v0.50.0 - golang.org/x/sync v0.19.0 + 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.4-0.20260305215735-7836f35a1a74 + maunium.net/go/mautrix v0.26.4 ) require ( @@ -31,7 +31,7 @@ require ( 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/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 // 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 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect @@ -43,11 +43,11 @@ require ( github.com/yuin/goldmark v1.7.16 // indirect go.mau.fi/libsignal v0.2.1 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect - golang.org/x/crypto v0.48.0 // indirect - golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect - golang.org/x/mod v0.33.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/text v0.34.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 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 489f9a8b..492b55bf 100644 --- a/go.sum +++ b/go.sum @@ -41,8 +41,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE 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/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 h1:KPpdlQLZcHfTMQRi6bFQ7ogNO0ltFT4PmtwTLW4W+14= -github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +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= @@ -77,33 +77,33 @@ github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE= github.com/yuin/goldmark v1.7.16/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.6 h1:2nsvxm49KhI3wrFltr0+wSUBlnQ4CMtykuELjpIU+ts= -go.mau.fi/util v0.9.6/go.mod h1:sIJpRH7Iy5Ad1SBuxQoatxtIeErgzxCtjd/2hCMkYMI= +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/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= -golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= -golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= -golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc= -golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= -golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= -golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +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/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.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +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= 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= @@ -115,5 +115,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.4-0.20260305215735-7836f35a1a74 h1:kHlana4CKRoAzPGbagU82y+uN4k5Tvfwt1nyj3cJHEw= -maunium.net/go/mautrix v0.26.4-0.20260305215735-7836f35a1a74/go.mod h1:lJvXCZya2dGT2KW7LUO7Ucna7Ohs6hl2+7v8Ji6R3iM= +maunium.net/go/mautrix v0.26.4 h1:enHSnkf0L2V9+VnfJfNhKSReSW6pBKS/x3Su+v+Vovs= +maunium.net/go/mautrix v0.26.4/go.mod h1:YWw8NWTszsbyFAznboicBObwHPgTSLcuTbVX2kY7U2M= From f73b06fdc46ab60586f22a274f6e3174b4313b54 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 18 Mar 2026 15:26:28 +0200 Subject: [PATCH 278/486] msgconv/from-*: add support for room mentions --- pkg/msgconv/from-matrix.go | 14 ++++++++++++-- pkg/msgconv/from-whatsapp.go | 3 +++ pkg/msgconv/matrixpoll.go | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 78b8554d..49f60486 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -49,7 +49,13 @@ import ( "go.mau.fi/mautrix-whatsapp/pkg/waid" ) -func (mc *MessageConverter) generateContextInfo(ctx context.Context, replyTo *database.Message, portal *bridgev2.Portal, perMessageTimer *event.BeeperDisappearingTimer) *waE2E.ContextInfo { +func (mc *MessageConverter) generateContextInfo( + ctx context.Context, + replyTo *database.Message, + portal *bridgev2.Portal, + perMessageTimer *event.BeeperDisappearingTimer, + roomMention bool, +) *waE2E.ContextInfo { contextInfo := &waE2E.ContextInfo{} if replyTo != nil { msgID, err := waid.ParseMessageID(replyTo.ID) @@ -57,6 +63,7 @@ func (mc *MessageConverter) generateContextInfo(ctx context.Context, replyTo *da contextInfo.StanzaID = proto.String(msgID.ID) contextInfo.Participant = proto.String(msgID.Sender.String()) contextInfo.QuotedMessage = &waE2E.Message{Conversation: proto.String("")} + contextInfo.QuotedType = waE2E.ContextInfo_EXPLICIT.Enum() } else { zerolog.Ctx(ctx).Warn().Err(err). Stringer("reply_to_event_id", replyTo.MXID). @@ -77,6 +84,9 @@ func (mc *MessageConverter) generateContextInfo(ctx context.Context, replyTo *da if setAt > 0 && contextInfo.Expiration != nil { contextInfo.EphemeralSettingTimestamp = ptr.Ptr(setAt) } + if roomMention { + contextInfo.NonJIDMentions = proto.Uint32(1) + } return contextInfo } @@ -96,7 +106,7 @@ func (mc *MessageConverter) ToWhatsApp( } message := &waE2E.Message{} - contextInfo := mc.generateContextInfo(ctx, replyTo, portal, content.BeeperDisappearingTimer) + contextInfo := mc.generateContextInfo(ctx, replyTo, portal, content.BeeperDisappearingTimer, content.Mentions != nil && content.Mentions.Room) switch content.MsgType { case event.MsgText, event.MsgNotice, event.MsgEmote: diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index c1f36ce4..0ffbba67 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -234,6 +234,9 @@ func (mc *MessageConverter) ToMatrix( part.Extra["fi.mau.whatsapp.source_broadcast_list"] = info.Chat.String() } mc.addMentions(ctx, contextInfo.GetMentionedJID(), part.Content) + if contextInfo.GetNonJIDMentions() == 1 { + part.Content.Mentions.Room = true + } cm := &bridgev2.ConvertedMessage{ Parts: []*bridgev2.ConvertedMessagePart{part}, diff --git a/pkg/msgconv/matrixpoll.go b/pkg/msgconv/matrixpoll.go index 22cc7afe..0dc32132 100644 --- a/pkg/msgconv/matrixpoll.go +++ b/pkg/msgconv/matrixpoll.go @@ -71,7 +71,7 @@ func (mc *MessageConverter) PollStartToWhatsApp( if maxAnswers >= len(content.PollStart.Answers) || maxAnswers < 0 { maxAnswers = 0 } - contextInfo := mc.generateContextInfo(ctx, replyTo, portal, nil) + contextInfo := mc.generateContextInfo(ctx, replyTo, portal, nil, content.Mentions != nil && content.Mentions.Room) var question string question, contextInfo.MentionedJID = mc.msc1767ToWhatsApp(ctx, content.PollStart.Question, content.Mentions) if len(question) == 0 { From 7f1a40ccd317f098e6334ac3de107e1d0084f9f7 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 18 Mar 2026 17:58:37 +0200 Subject: [PATCH 279/486] msgconv/wa-poll: fix handling updates to unknown polls --- pkg/msgconv/wa-poll.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/msgconv/wa-poll.go b/pkg/msgconv/wa-poll.go index 6dac2d65..14388bed 100644 --- a/pkg/msgconv/wa-poll.go +++ b/pkg/msgconv/wa-poll.go @@ -177,6 +177,9 @@ func (mc *MessageConverter) convertPollUpdateMessage(ctx context.Context, info * if err != nil { log.Err(err).Msg("Failed to get poll update target message") return failedPollUpdatePart, nil + } else if pollMessage == nil { + log.Warn().Str("target_message_id", string(pollMessageID)).Msg("Poll update target message not found") + return failedPollUpdatePart, nil } vote, err := getClient(ctx).DecryptPollVote(ctx, &events.Message{ Info: *info, From 2f5ad1f4354bde5aa16f3200c690ce404d1f33aa Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 18 Mar 2026 18:21:17 +0200 Subject: [PATCH 280/486] msgconv/from-whatsapp: ensure nil messages are treated as unknown --- pkg/msgconv/from-whatsapp.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 0ffbba67..11569e29 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -140,6 +140,9 @@ func (mc *MessageConverter) ToMatrix( isBackfill bool, previouslyConvertedPart *bridgev2.ConvertedMessagePart, ) *bridgev2.ConvertedMessage { + if waMsg == nil { + waMsg = &waE2E.Message{} + } ctx = context.WithValue(ctx, contextKeyClient, client) ctx = context.WithValue(ctx, contextKeyIntent, intent) ctx = context.WithValue(ctx, contextKeyPortal, portal) From e7c801468689e42e70870ffb4faa77ca77ade092 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 18 Mar 2026 18:24:58 +0200 Subject: [PATCH 281/486] client: don't panic if background connection gets logged out --- pkg/connector/client.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 60ef5633..4b5f7260 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -266,7 +266,9 @@ func (wa *WhatsAppClient) ConnectBackground(ctx context.Context, params *bridgev return payload } defer func() { - wa.Client.GetClientPayload = nil + if cli := wa.Client; cli != nil { + cli.GetClientPayload = nil + } }() err := wa.Client.ConnectContext(ctx) if err != nil { From 568c6767182c36085d8bc39bd9ce6dd47fb47e47 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 22 Mar 2026 12:26:06 +0200 Subject: [PATCH 282/486] 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 73990c7e..e8f8bb1f 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.4 + maunium.net/go/mautrix v0.26.5-0.20260322102453-0c955c396df7 ) require ( diff --git a/go.sum b/go.sum index 492b55bf..6fedc40b 100644 --- a/go.sum +++ b/go.sum @@ -115,5 +115,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.4 h1:enHSnkf0L2V9+VnfJfNhKSReSW6pBKS/x3Su+v+Vovs= -maunium.net/go/mautrix v0.26.4/go.mod h1:YWw8NWTszsbyFAznboicBObwHPgTSLcuTbVX2kY7U2M= +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= From 5c072300bb98fb606cbd87089536594c6769c728 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 25 Mar 2026 18:13:47 +0200 Subject: [PATCH 283/486] handlewhatsapp: stop background loops when logged out --- pkg/connector/client.go | 8 +++++++- pkg/connector/handlewhatsapp.go | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 4b5f7260..6216ed09 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -211,6 +211,7 @@ func (wa *WhatsAppClient) Connect(ctx context.Context) { wa.Client.BackgroundEventCtx = wa.UserLogin.Log.WithContext(wa.Main.Bridge.BackgroundCtx) zerolog.Ctx(ctx).Debug().Msg("Connecting to WhatsApp") if err := wa.Client.ConnectContext(ctx); err != nil { + wa.callStopLoops() zerolog.Ctx(ctx).Err(err).Msg("Failed to connect to WhatsApp") state := status.BridgeState{ StateEvent: status.StateUnknownError, @@ -337,6 +338,7 @@ func (wa *WhatsAppClient) startLoops() { if oldStop != nil { (*oldStop)() } + ctx = wa.UserLogin.Log.WithContext(ctx) go wa.historySyncLoop(ctx) go wa.ghostResyncLoop(ctx) if mrc := wa.Main.Config.HistorySync.MediaRequests; mrc.AutoRequestMedia && mrc.RequestMethod == MediaRequestMethodLocalTime { @@ -354,10 +356,14 @@ func (wa *WhatsAppClient) GetStore() *store.Device { return store.NoopDevice } -func (wa *WhatsAppClient) Disconnect() { +func (wa *WhatsAppClient) callStopLoops() { if stopHistorySyncLoop := wa.stopLoops.Swap(nil); stopHistorySyncLoop != nil { (*stopHistorySyncLoop)() } +} + +func (wa *WhatsAppClient) Disconnect() { + wa.callStopLoops() if cli := wa.Client; cli != nil { cli.Disconnect() } diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 2bc687a0..e683a44c 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -507,7 +507,7 @@ func (wa *WhatsAppClient) handleWALogout(reason events.ConnectFailureReason, onC } else if reason == events.ConnectFailureMainDeviceGone { errorCode = WAMainDeviceGone } - wa.Client.Disconnect() + wa.Disconnect() wa.Client = nil wa.JID = types.EmptyJID wa.UserLogin.Metadata.(*waid.UserLoginMetadata).WADeviceID = 0 From 450ac8960ff926d013c98e2315651ad5aafa29ad Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 25 Mar 2026 19:11:33 +0200 Subject: [PATCH 284/486] backfill: mark backfill as complete if only message is the anchor --- 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 f05c0f2d..e4a1ece0 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -454,10 +454,17 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet } else if params.AnchorMessage != nil { endTime = ptr.Ptr(params.AnchorMessage.Timestamp) } + var anchorID types.MessageID + if params.AnchorMessage != nil { + parsedID, _ := waid.ParseMessageID(params.AnchorMessage.ID) + if parsedID != nil { + anchorID = parsedID.ID + } + } 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 { + } else if len(messages) == 0 || (len(messages) == 1 && anchorID != "" && messages[0].GetKey().GetID() == anchorID) { return &bridgev2.FetchMessagesResponse{ HasMore: false, Forward: params.Forward, From 46d51593e5f6ce64cb2bef417e3277495fc909c0 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 25 Mar 2026 20:15:12 +0200 Subject: [PATCH 285/486] backfill: remove non-manual history sync download code path --- pkg/connector/backfill.go | 18 +++++++++++------- pkg/connector/client.go | 3 --- pkg/connector/handlewhatsapp.go | 4 +--- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index e4a1ece0..21d0c4d1 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -63,11 +63,6 @@ func (wa *WhatsAppClient) historySyncLoop(ctx context.Context) { for { var resetTimer bool select { - case evt := <-wa.historySyncs: - // The timer is stopped unconditionally and restarted if either handleWAHistorySync had conversations, - // or if the timer was previously started and hadn't reached the loop above yet. - dispatchTimer.Stop() - resetTimer, _ = wa.handleWAHistorySync(ctx, evt, false) case <-wa.historySyncWakeup: dispatchTimer.Stop() notif, rowid, err := wa.Main.DB.HSNotif.GetNext(ctx, wa.UserLogin.ID) @@ -127,7 +122,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, blob, true) + resetTimer, innerErr = wa.handleWAHistorySync(ctx, evt, blob, true) if innerErr != nil { return } @@ -143,7 +138,12 @@ func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context, return } -func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistorySync.HistorySync, stopOnError bool) (bool, error) { +func (wa *WhatsAppClient) handleWAHistorySync( + ctx context.Context, + notif *waE2E.HistorySyncNotification, + evt *waHistorySync.HistorySync, + stopOnError bool, +) (bool, error) { if evt == nil || evt.SyncType == nil { return false, nil } @@ -175,6 +175,10 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor log.Info(). Int("conversation_count", len(evt.GetConversations())). Int("past_participant_count", len(evt.GetPastParticipants())). + Dict("notification_metadata", zerolog.Dict(). + Int64("oldest_msg_in_chunk_ts", notif.GetOldestMsgInChunkTimestampSec()). + Any("full_request_meta", notif.GetFullHistorySyncOnDemandRequestMetadata()). + Any("access_status", notif.GetMessageAccessStatus())). Msg("Storing history sync") start := time.Now() successfullySavedTotal := 0 diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 6216ed09..367ad147 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -29,7 +29,6 @@ import ( "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/appstate" waBinary "go.mau.fi/whatsmeow/binary" - "go.mau.fi/whatsmeow/proto/waHistorySync" "go.mau.fi/whatsmeow/proto/waWa6" "go.mau.fi/whatsmeow/store" "go.mau.fi/whatsmeow/types" @@ -49,7 +48,6 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. UserLogin: login, MC: noopMCInstance, - historySyncs: make(chan *waHistorySync.HistorySync, 64), historySyncWakeup: make(chan struct{}, 1), resyncQueue: make(map[types.JID]resyncQueueItem), directMediaRetries: make(map[networkid.MessageID]*directMediaRetry), @@ -107,7 +105,6 @@ type WhatsAppClient struct { JID types.JID MC mClient - historySyncs chan *waHistorySync.HistorySync historySyncWakeup chan struct{} stopLoops atomic.Pointer[context.CancelFunc] resyncQueue map[types.JID]resyncQueueItem diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index e683a44c..eb42f439 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -111,9 +111,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { success = wa.handleWAPin(evt) case *events.HistorySync: - if wa.Main.Bridge.Config.Backfill.Enabled { - wa.historySyncs <- evt.Data - } + 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 From 21514cd218b01c81e36d3c9a2474d6957836d1fd Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 31 Mar 2026 19:34:11 +0300 Subject: [PATCH 286/486] 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 e8f8bb1f..a28f7c7b 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 6fedc40b..ff1245a3 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 a330a98e..a0a67fa8 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 287/486] 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 21d0c4d1..f33351f5 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 6a097c2e..24456475 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 e441f67a..564f25eb 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 288/486] .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 3703df9e..c10630f9 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 289/486] 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 a28f7c7b..80eacc1f 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 ff1245a3..d6183fcc 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 290/486] 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 f33351f5..8a293114 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 291/486] 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 80eacc1f..15383afd 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 d6183fcc..c25697dd 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 8a293114..8ce0cc57 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 292/486] 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 8ce0cc57..e20d717d 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 f2450c18..4b160026 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 293/486] 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 e20d717d..90c13162 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 294/486] dependencies: update --- go.mod | 6 +++--- go.sum | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 15383afd..dfd36924 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 c25697dd..a7d7ef6c 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 295/486] 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 3cefd6d0..892be2e4 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 e64f02fd..b97bc7f6 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 dfd36924..9aaca46b 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 a7d7ef6c..dd982b93 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 296/486] 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 90c13162..b4ef1810 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 297/486] 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 9aaca46b..3054e5ae 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 dd982b93..ebe18fa6 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 a4a3d73e..10cdbbb4 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 9a1ceb3d..d1b53324 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 00000000..fef111a6 --- /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 298/486] 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 3054e5ae..f499a24b 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 ebe18fa6..38542dc7 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 299/486] 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 f499a24b..63b26bcb 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 38542dc7..44ae65df 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 300/486] .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 c10630f9..06ba9e81 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 301/486] dependencies: update --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 63b26bcb..2a66a9d3 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 44ae65df..e22300a1 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 302/486] dependencies: update --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 2a66a9d3..d55b65cc 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 e22300a1..bbef170a 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 303/486] .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 06ba9e81..4b3b9344 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 304/486] 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 d55b65cc..1f126128 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 bbef170a..08791ddb 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 202569b2..d8b1367e 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 367ad147..3f19bf78 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 10cdbbb4..ac2fbd24 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 a0a67fa8..b0963ecf 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 49f60486..4eb0a88c 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 e4109c8e..185ee0fc 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 d1b53324..03bb2744 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 fef111a6..85766ed4 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 6a94de4e..093ece1c 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 305/486] 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 4eb0a88c..93de90c7 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 03bb2744..c2a86248 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 85766ed4..e9507bc9 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 306/486] 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 d8b1367e..3864356d 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 307/486] 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 1f126128..b9f5347f 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 08791ddb..0575b9c2 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 308/486] 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 e9507bc9..b9797a63 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 309/486] 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 b9f5347f..7ba785b6 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 0575b9c2..220edd52 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 310/486] 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 7ba785b6..705e5785 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 220edd52..12e29fb8 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 311/486] 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 3864356d..3e6f6584 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 312/486] 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 705e5785..033ecf90 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 12e29fb8..3ea04fbb 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 313/486] 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 033ecf90..9cd6c8fa 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 3ea04fbb..be22fdfc 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 eb42f439..4c1016cd 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 314/486] 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 4c1016cd..3abcede7 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 315/486] 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 892be2e4..5c0314ee 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 b97bc7f6..cb1632ca 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 9cd6c8fa..c52b9253 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 be22fdfc..5fe1561c 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 316/486] 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 c52b9253..89dc3a71 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 5fe1561c..bfe01a50 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 317/486] 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 ac2fbd24..70289cd1 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 de5bdb8f..3e809b77 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 3abcede7..79871b3b 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 c2a86248..38fe9e2d 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 840e0f12..e522f5f4 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 093ece1c..716e187b 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 318/486] 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 88e7b758..b70ab1a7 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 319/486] 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 2c382d12..26862504 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 92e7f869..a5b65e81 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 11569e29..f0e45606 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 7ca44274..71de5861 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 27851056..59e9a9e2 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 320/486] 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 24456475..e51c80a5 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 321/486] 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 17efecdb..4a1e043f 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 71de5861..fa495e0c 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 322/486] 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 89dc3a71..e38d875b 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 bfe01a50..2782985b 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 323/486] 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 a5b65e81..4bcf4832 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 324/486] 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 e38d875b..0a9d8f8a 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 2782985b..d86e7827 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 325/486] 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 0a9d8f8a..f38c6d0e 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 d86e7827..741ec53f 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 326/486] 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 55555665..7c7dc8ef 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 327/486] 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 f38c6d0e..1265cfc5 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 741ec53f..b1c29ff3 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 328/486] 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 1265cfc5..47daa19c 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 b1c29ff3..b4fcfd5c 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 329/486] 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 47daa19c..4665f826 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 b4fcfd5c..b6820434 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 b4ef1810..0e6de76e 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 330/486] 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 4665f826..4efd3b86 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 b6820434..764681fd 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 331/486] 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 4efd3b86..ae10fd57 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 764681fd..29beb999 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 7c7dc8ef..7ffbb141 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 70289cd1..89a54fc7 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 aa849384..2b940475 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 38fe9e2d..34c529b4 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 332/486] dependencies: update --- go.mod | 20 ++++++++++---------- go.sum | 36 ++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index ae10fd57..f37b5689 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 29beb999..589125f0 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 333/486] 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 3e809b77..411c39e7 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 79871b3b..e6437ec0 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 334/486] 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 f0527b27..00000000 --- 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 cb1632ca..1da86bf9 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 335/486] 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 b0963ecf..299449f7 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 e6437ec0..30a9cd8f 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 09306177..f85545a7 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 336/486] 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 5c0314ee..560e2b58 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 1da86bf9..07baaecc 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 f37b5689..85f5c6c8 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 589125f0..e0a405c0 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 337/486] 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 0e6de76e..ee92ac2a 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 338/486] 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 34c529b4..99648009 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 339/486] 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 85f5c6c8..791f3e77 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 e0a405c0..0112e109 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 340/486] 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 2b940475..e8279558 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 341/486] 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 30a9cd8f..2b9b145f 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 ce38079e..71094c99 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 59e9a9e2..1e544267 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 342/486] 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 791f3e77..c16824cb 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 0112e109..d38c8cb1 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 b70ab1a7..5da419f6 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 343/486] 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 c16824cb..bf909bed 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 d38c8cb1..5be58b35 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 344/486] 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 bf909bed..9dc2e585 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 5be58b35..1d4b8afc 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 345/486] 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 ee92ac2a..dd046961 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 411c39e7..cb51e64e 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 2b9b145f..f12ebadf 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 f0e45606..6eea25f2 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 14388bed..dfe1a25c 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 346/486] 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 9dc2e585..35a6655a 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 1d4b8afc..3d0391c7 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 347/486] 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 299449f7..9aea18b3 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 93de90c7..49aedf85 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 0dc32132..b53c22f0 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 348/486] 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 4bcf4832..0a29b829 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 6eea25f2..e8cb0327 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 349/486] 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 35a6655a..7c706b5a 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 3d0391c7..f7bf881f 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 350/486] 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 a0b06922..fb2ce16f 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 351/486] changelog: update --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 560e2b58..37b9fe8b 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 352/486] 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 b53c22f0..a5f02ea9 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 353/486] 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 7c706b5a..ba3792bb 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 f7bf881f..f24a245d 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 354/486] 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 4efc9d5f..e59d110b 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 cb3be51d..0a73ff50 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 355/486] 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 37b9fe8b..e47e7b27 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 07baaecc..47d971cf 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 ba3792bb..16504a23 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 f24a245d..78994361 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 356/486] 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 16504a23..a4e7f071 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 78994361..07863042 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 22c5414e..55479820 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 357/486] 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 a4e7f071..0f147f91 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 07863042..8138dfe9 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 358/486] 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 a1c9ca02..00000000 --- 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 359/486] 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 0f147f91..2aa402fb 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 8138dfe9..388c420e 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 3e6f6584..152e7ad6 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 360/486] 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 89a54fc7..8a7afd9f 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 361/486] 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 2aa402fb..95219379 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 388c420e..322636bc 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 dd046961..65e8f72c 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 7ffbb141..1dbebcc4 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 3f19bf78..c5a9348b 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 91b1e11f..e262c82f 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 cb51e64e..1f4e118b 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 9aea18b3..c55f523c 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 f12ebadf..f8cee2ad 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 c07e4316..c0aa06b0 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 00000000..cfab099b --- /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 26862504..38061967 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 fb2ce16f..f0d869b9 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 850f5b4a..0f1fef94 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 d5b4ca07..00000000 --- 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 5c32e653..00000000 --- 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 00000000..9e50f51b --- /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 49aedf85..57f9113c 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 e8cb0327..e912732d 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 dfe1a25c..881a5fb0 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 e522f5f4..7f8fe5bf 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 362/486] 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 9e50f51b..334c597e 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 363/486] 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 f8cee2ad..fce47df0 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 364/486] 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 95219379..9aa9e7e5 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 322636bc..9ba5037c 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 365/486] 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 9aa9e7e5..18468ba2 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 9ba5037c..b543aa6c 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 366/486] 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 18468ba2..2b0f16e0 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 b543aa6c..d01face7 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 367/486] 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 65e8f72c..7356d11b 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 368/486] 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 4b160026..7a6c63d1 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 369/486] 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 c5a9348b..08c71c5f 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 fce47df0..ef2012f3 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 370/486] 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 2b0f16e0..5ad5c407 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 d01face7..bdec40ed 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 371/486] 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 5ad5c407..593f51eb 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 bdec40ed..99259c06 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 08c71c5f..b3ecdbae 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 ef2012f3..509c589a 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 f0d869b9..c1233111 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 372/486] 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 e47e7b27..4d17865f 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 47d971cf..011e9b0d 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 593f51eb..ad3d58a5 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 99259c06..62f54b2d 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 373/486] 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 5da419f6..a62b2948 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 374/486] 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 1dbebcc4..1461561b 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 375/486] 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 7356d11b..bb555e6f 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 376/486] 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 343443bd..c9fef736 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 ad3d58a5..1d4c99f7 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 62f54b2d..9a666fca 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 377/486] 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 1d4c99f7..f7a6451c 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 9a666fca..1adbb525 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbs go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260816113502-fb386f152837 h1:xJ13dqFcK/oPNImltlk7sumI/uiQ9EON/6g/iwpT1Nc= -go.mau.fi/whatsmeow v0.0.0-20260816113502-fb386f152837/go.mod h1:UFP0D7aj+biuFejsllcoNiHTQorh0ODFUyG4Txpqfks= +go.mau.fi/whatsmeow v0.0.0-20260821141805-33cfac511629 h1:99p9fTS1G73a1aifCVaV5MX4hDq0lVUKWGoUNoXyE3A= +go.mau.fi/whatsmeow v0.0.0-20260821141805-33cfac511629/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= From 03eb3900826932fa4c661997df03bceb233cf6e2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 24 Aug 2026 13:28:23 +0300 Subject: [PATCH 378/486] backfill: return no more messages flag if there's no anchor message --- pkg/connector/backfill.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index bb555e6f..cab460fd 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -532,6 +532,7 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet return nil, fmt.Errorf("failed to load messages from database: %w", err) } else if len(messages) == 0 || (len(messages) == 1 && anchorID != "" && messages[0].GetKey().GetID() == anchorID) { wa.deleteHistorySyncMessages(ctx, portalJID, 0, 0) + hasMore = hasMore && params.AnchorMessage != nil if hasMore && !params.AllowSlowFetch { return &bridgev2.FetchMessagesResponse{ MoreRequiresSlowFetch: true, From e1220a8d4eb80dae42e01c8e9fcabe22aeae23fc Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 24 Aug 2026 13:30:35 +0300 Subject: [PATCH 379/486] handlewhatsapp: delete history sync data on logout --- pkg/connector/client.go | 4 ++++ pkg/connector/handlewhatsapp.go | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index b3ecdbae..f0a59475 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -393,6 +393,10 @@ func (wa *WhatsAppClient) LogoutRemote(ctx context.Context) { } wa.Disconnect() wa.Client = nil + err := wa.Main.DB.Conversation.DeleteAll(ctx, wa.UserLogin.ID) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to delete history sync data on logout") + } } func (wa *WhatsAppClient) IsLoggedIn() bool { diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 509c589a..130c2756 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -202,7 +202,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected}) wa.notifyOfflineSyncWaiter(nil) case *events.LoggedOut: - wa.handleWALogout(evt.Reason, evt.OnConnect) + wa.handleWALogout(ctx, evt.Reason, evt.OnConnect) wa.notifyOfflineSyncWaiter(fmt.Errorf("logged out: %s", evt.Reason)) case *events.Disconnected: // Don't send the normal transient disconnect state if we're already in a different transient disconnect state. @@ -528,7 +528,7 @@ func (wa *WhatsAppClient) handleWAChatPresence(ctx context.Context, evt *events. }) } -func (wa *WhatsAppClient) handleWALogout(reason events.ConnectFailureReason, onConnect bool) { +func (wa *WhatsAppClient) handleWALogout(ctx context.Context, reason events.ConnectFailureReason, onConnect bool) { errorCode := WAUnknownLogout if reason == events.ConnectFailureLoggedOut { errorCode = WALoggedOut @@ -540,6 +540,10 @@ func (wa *WhatsAppClient) handleWALogout(reason events.ConnectFailureReason, onC wa.JID = types.EmptyJID wa.LID = types.EmptyJID wa.UserLogin.Metadata.(*waid.UserLoginMetadata).WADeviceID = 0 + err := wa.Main.DB.Conversation.DeleteAll(ctx, wa.UserLogin.ID) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to delete history sync data on logout") + } wa.UserLogin.BridgeState.Send(status.BridgeState{ StateEvent: status.StateBadCredentials, Error: errorCode, From 39bc900ac930a060041bb819ecda0ffd8e502189 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Mon, 24 Aug 2026 11:33:43 +0100 Subject: [PATCH 380/486] dependencies: update mautrix-go --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index f7a6451c..db7c17fd 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.22.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.30.1-0.20260820142923-ae00f2c62e1e + maunium.net/go/mautrix v0.30.1-0.20260822101838-bdb58f2e6c0a ) require ( diff --git a/go.sum b/go.sum index 1adbb525..6eca7221 100644 --- a/go.sum +++ b/go.sum @@ -109,3 +109,5 @@ maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= maunium.net/go/mautrix v0.30.1-0.20260820142923-ae00f2c62e1e h1:ATvERyBIUXJrnL7ezP2xCLLGCNmGClmfzoQq4N3jBcA= maunium.net/go/mautrix v0.30.1-0.20260820142923-ae00f2c62e1e/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= +maunium.net/go/mautrix v0.30.1-0.20260822101838-bdb58f2e6c0a h1:BYeNDQQLR0edhy6Smq41T3QBNWn6FhrNtRG8CZTuUV4= +maunium.net/go/mautrix v0.30.1-0.20260822101838-bdb58f2e6c0a/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= From 6f767fb951fd0190c754d045c5d879a1f574a7a2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 24 Aug 2026 16:46:43 +0300 Subject: [PATCH 381/486] dependencies: fix go mod tidy --- go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.sum b/go.sum index 6eca7221..f97f0a23 100644 --- a/go.sum +++ b/go.sum @@ -107,7 +107,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.30.1-0.20260820142923-ae00f2c62e1e h1:ATvERyBIUXJrnL7ezP2xCLLGCNmGClmfzoQq4N3jBcA= -maunium.net/go/mautrix v0.30.1-0.20260820142923-ae00f2c62e1e/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= maunium.net/go/mautrix v0.30.1-0.20260822101838-bdb58f2e6c0a h1:BYeNDQQLR0edhy6Smq41T3QBNWn6FhrNtRG8CZTuUV4= maunium.net/go/mautrix v0.30.1-0.20260822101838-bdb58f2e6c0a/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= From c4aabf0b1a819d7e8a5555ea62e12a0914b4253f Mon Sep 17 00:00:00 2001 From: Kishan Bagaria <1093313+KishanBagaria@users.noreply.github.com> Date: Mon, 24 Aug 2026 06:51:06 -0700 Subject: [PATCH 382/486] handlewhatsapp: fix nested HD media caption edits (#948) --- pkg/connector/handlewhatsapp.go | 35 ++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 130c2756..5508334a 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -27,6 +27,7 @@ import ( "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/appstate" + "go.mau.fi/whatsmeow/proto/waCommon" "go.mau.fi/whatsmeow/proto/waE2E" "go.mau.fi/whatsmeow/types" "go.mau.fi/whatsmeow/types/events" @@ -341,18 +342,8 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa messageAssoc := evt.Message.GetMessageContextInfo().GetMessageAssociation() if assocType := messageAssoc.GetAssociationType(); assocType == waE2E.MessageAssociation_HD_IMAGE_DUAL_UPLOAD || assocType == waE2E.MessageAssociation_HD_VIDEO_DUAL_UPLOAD { parentKey := messageAssoc.GetParentMessageKey() - protocolMsg := evt.Message.GetProtocolMessage() - if protocolMsg.GetType() != waE2E.ProtocolMessage_MESSAGE_EDIT || protocolMsg.GetKey() == nil { - protocolMsg = &waE2E.ProtocolMessage{ - Type: waE2E.ProtocolMessage_MESSAGE_EDIT.Enum(), - Key: parentKey, - EditedMessage: evt.Message.GetAssociatedChildMessage().GetMessage(), - } - dontRenderEdited = true - } else if child := protocolMsg.GetEditedMessage().GetAssociatedChildMessage().GetMessage(); child != nil { - protocolMsg.EditedMessage = child - protocolMsg.Key = parentKey - } + protocolMsg, shouldHideEdit := makeHDMediaReplacementEdit(evt.Message, parentKey) + dontRenderEdited = shouldHideEdit wa.UserLogin.Log.Debug(). Str("message_id", evt.Info.ID). Str("parent_id", parentKey.GetID()). @@ -386,6 +377,26 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa return res.Success } +func makeHDMediaReplacementEdit(message *waE2E.Message, parentKey *waCommon.MessageKey) (*waE2E.ProtocolMessage, bool) { + protocolMsg := message.GetProtocolMessage() + associatedMessage := message.GetAssociatedChildMessage().GetMessage() + if protocolMsg.GetType() != waE2E.ProtocolMessage_MESSAGE_EDIT || protocolMsg.GetKey() == nil { + protocolMsg = associatedMessage.GetProtocolMessage() + } + if protocolMsg.GetType() == waE2E.ProtocolMessage_MESSAGE_EDIT && protocolMsg.GetKey() != nil { + if child := protocolMsg.GetEditedMessage().GetAssociatedChildMessage().GetMessage(); child != nil { + protocolMsg.EditedMessage = child + } + protocolMsg.Key = parentKey + return protocolMsg, false + } + return &waE2E.ProtocolMessage{ + Type: waE2E.ProtocolMessage_MESSAGE_EDIT.Enum(), + Key: parentKey, + EditedMessage: associatedMessage, + }, true +} + func (wa *WhatsAppClient) handleWAUndecryptableMessage(ctx context.Context, evt *events.UndecryptableMessage) bool { if !wa.ensureAltJIDs(ctx, &evt.Info.MessageSource, true) { return false From 508b35e1ae2533605cb3ba39bfd9f131ce652bb0 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Tue, 25 Aug 2026 12:33:30 +0100 Subject: [PATCH 383/486] directmedia: mark media retry decrypt failures (#939) Co-authored-by: Nick Mills-Barrett --- pkg/connector/directmedia.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index 8a7afd9f..a875fab9 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -255,10 +255,11 @@ func (wa *WhatsAppConnector) makeDirectMediaResponse( type directMediaRetry struct { sync.Mutex - resultURL string - wait *exsync.Event - requested bool - resultType waMmsRetry.MediaRetryNotification_ResultType + resultURL string + wait *exsync.Event + requested bool + resultType waMmsRetry.MediaRetryNotification_ResultType + decryptFail bool } func (wa *WhatsAppClient) getDirectMediaRetryState(msgID networkid.MessageID, create bool) *directMediaRetry { @@ -285,6 +286,9 @@ func (wa *WhatsAppClient) requestAndWaitDirectMedia(ctx context.Context, rawMsgI keys.DirectPath = state.resultURL return nil } + if state.decryptFail { + return mautrix.MNotFound.WithMessage("Unable to retrieve media: failed to decrypt the media retry notification from your phone.") + } switch state.resultType { case waMmsRetry.MediaRetryNotification_NOT_FOUND: return mautrix.MNotFound.WithMessage("This media was not found on your phone.") @@ -338,6 +342,9 @@ func (wa *WhatsAppClient) receiveDirectMediaRetry(ctx context.Context, msg *data retryData, err := whatsmeow.DecryptMediaRetryNotification(retry, keys.Key) if err != nil { log.Warn().Err(err).Msg("Failed to decrypt media retry notification") + if state != nil { + state.decryptFail = true + } return } if state != nil { From 4973412350b2b3231295c4b685a1e6ec8f45e961 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 25 Aug 2026 17:30:08 +0300 Subject: [PATCH 384/486] 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 db7c17fd..6be8397a 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.22.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.30.1-0.20260822101838-bdb58f2e6c0a + maunium.net/go/mautrix v0.30.1-0.20260825141151-92fa7b2e4545 ) require ( diff --git a/go.sum b/go.sum index f97f0a23..5b26c4fc 100644 --- a/go.sum +++ b/go.sum @@ -107,5 +107,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.30.1-0.20260822101838-bdb58f2e6c0a h1:BYeNDQQLR0edhy6Smq41T3QBNWn6FhrNtRG8CZTuUV4= -maunium.net/go/mautrix v0.30.1-0.20260822101838-bdb58f2e6c0a/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= +maunium.net/go/mautrix v0.30.1-0.20260825141151-92fa7b2e4545 h1:cVpn8+sWu96w/lGLl6pO9t7hcuEouXbIQiOtnH58JwI= +maunium.net/go/mautrix v0.30.1-0.20260825141151-92fa7b2e4545/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= From e59633c99192bcac117e830d83363b10826a1eb6 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 25 Aug 2026 23:56:48 +0300 Subject: [PATCH 385/486] handlewhatsapp: fix handling newsletter read receipts --- pkg/connector/handlewhatsapp.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 5508334a..4ca2217e 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -24,6 +24,7 @@ import ( "time" "github.com/rs/zerolog" + "go.mau.fi/util/exslices" "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/appstate" @@ -476,6 +477,8 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei messageSender := wa.GetLID() if !evt.MessageSender.IsEmpty() { messageSender = evt.MessageSender + } else if evt.Chat.Server == types.NewsletterServer { + messageSender = evt.Chat } var chatAlt types.JID if evt.Chat.Server == types.DefaultUserServer { @@ -494,6 +497,8 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei senderLID := evt.Sender if senderLID.Server == types.DefaultUserServer && !evt.SenderAlt.IsEmpty() { senderLID = evt.SenderAlt + } else if evt.Chat.Server == types.NewsletterServer && evt.Type == types.ReceiptTypeReadSelf { + senderLID = wa.GetLID() } res := wa.UserLogin.QueueRemoteEvent(&simplevent.Receipt{ EventMeta: simplevent.EventMeta{ @@ -501,6 +506,11 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei PortalKey: wa.makeWAPortalKey(evt.Chat), Sender: wa.makeEventSender(ctx, senderLID), Timestamp: evt.Timestamp, + LogContext: func(c zerolog.Context) zerolog.Context { + return c. + Strs("targets", exslices.CastToString[string](targets)). + Stringer("receipt_sender", senderLID) + }, }, Targets: targets, }) From a7bf2c89f8691d8a4ab1851cdc7b49d063c4bdd4 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 29 Aug 2026 01:44:57 +0300 Subject: [PATCH 386/486] dependencies: update --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 6be8397a..9a261277 100644 --- a/go.mod +++ b/go.mod @@ -12,13 +12,13 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260821141805-33cfac511629 + go.mau.fi/whatsmeow v0.0.0-20260828224850-0fadda796019 golang.org/x/image v0.45.0 golang.org/x/net v0.58.0 golang.org/x/sync v0.22.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.30.1-0.20260825141151-92fa7b2e4545 + maunium.net/go/mautrix v0.30.1-0.20260828224424-c22bd4fdc9e6 ) require ( @@ -31,8 +31,8 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.49 // indirect - github.com/petermattis/goid v0.0.0-20260816044145-ed329add6b1b // indirect + github.com/mattn/go-sqlite3 v1.14.50 // indirect + github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/rs/xid v1.6.0 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect @@ -44,7 +44,7 @@ require ( go.mau.fi/libsignal v0.2.2 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect golang.org/x/crypto v0.55.0 // indirect - golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297 // indirect + golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa // indirect golang.org/x/mod v0.40.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.41.0 // indirect diff --git a/go.sum b/go.sum index 5b26c4fc..e5827da1 100644 --- a/go.sum +++ b/go.sum @@ -34,10 +34,10 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.49 h1:B8jBHC3xhxZgxztrgruTuLucebnULQnx4W7cF7SAE9w= -github.com/mattn/go-sqlite3 v1.14.49/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= -github.com/petermattis/goid v0.0.0-20260816044145-ed329add6b1b h1:sS7HLzwS+dO+gxATgQfeZDEdUZe2pKAB3nGoUwP5zU0= -github.com/petermattis/goid v0.0.0-20260816044145-ed329add6b1b/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/mattn/go-sqlite3 v1.14.50 h1:dmdFvo1XG4MPzA4IkAmE9upVz/Nj31uRoM5+jC8hYbY= +github.com/mattn/go-sqlite3 v1.14.50/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= +github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 h1:lcWAnrqr2nNfDiArwFNHCE4787Mw2tCdVSOXCru0/0E= +github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -75,14 +75,14 @@ go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbs go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260821141805-33cfac511629 h1:99p9fTS1G73a1aifCVaV5MX4hDq0lVUKWGoUNoXyE3A= -go.mau.fi/whatsmeow v0.0.0-20260821141805-33cfac511629/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= +go.mau.fi/whatsmeow v0.0.0-20260828224850-0fadda796019 h1:vUX4rgZhobwtMYH6tkYOSyfd2ufTysNEDWVpTSVPjmc= +go.mau.fi/whatsmeow v0.0.0-20260828224850-0fadda796019/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= -golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297 h1:YXnL44eJ77R+ji4/ooy8UsXIhz+lbi2Qgdlc8iRN0gY= -golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297/go.mod h1:Mkmymgv+uMpSQ/XxJ/7GpdrdYoqm3u72jEbpCLiJmNk= +golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa h1:QSyA8ishJCyT21kER9KwNt0b7BM3iRK4x9QXhjN5Fdk= +golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0= golang.org/x/image v0.45.0/go.mod h1:n62x/7RqlwXDvGsSU4u6IUTUf6KghUZ9Bt7cG/T9Fx4= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= @@ -107,5 +107,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.30.1-0.20260825141151-92fa7b2e4545 h1:cVpn8+sWu96w/lGLl6pO9t7hcuEouXbIQiOtnH58JwI= -maunium.net/go/mautrix v0.30.1-0.20260825141151-92fa7b2e4545/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= +maunium.net/go/mautrix v0.30.1-0.20260828224424-c22bd4fdc9e6 h1:mL1BquGn+7efD4ECX62uHVN2MnsrC65JuOgxDdKFF7o= +maunium.net/go/mautrix v0.30.1-0.20260828224424-c22bd4fdc9e6/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= From d1dbe393b6875a1997a8b4f6f1dd54b33ad7fec9 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 3 Sep 2026 14:17:20 +0300 Subject: [PATCH 387/486] 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 9a261277..288eb6f3 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260828224850-0fadda796019 + go.mau.fi/whatsmeow v0.0.0-20260903111606-de26b4ab6499 golang.org/x/image v0.45.0 golang.org/x/net v0.58.0 golang.org/x/sync v0.22.0 diff --git a/go.sum b/go.sum index e5827da1..e5d6221f 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbs go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260828224850-0fadda796019 h1:vUX4rgZhobwtMYH6tkYOSyfd2ufTysNEDWVpTSVPjmc= -go.mau.fi/whatsmeow v0.0.0-20260828224850-0fadda796019/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= +go.mau.fi/whatsmeow v0.0.0-20260903111606-de26b4ab6499 h1:7RG3BeC9qXh2aQ0RwjIFZxhCwTohdtNzJaKHxna32pE= +go.mau.fi/whatsmeow v0.0.0-20260903111606-de26b4ab6499/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= From 51504db3098ec1c7b81c314f01a7d0458ac309e0 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 3 Sep 2026 02:17:28 +0300 Subject: [PATCH 388/486] chatinfo: add option to limit eager member sync in groups --- pkg/connector/chatinfo.go | 6 +++++- pkg/connector/config.go | 2 ++ pkg/connector/example-config.yaml | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 1461561b..675a5ce4 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -271,11 +271,12 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn setAddressingMode(info.AddressingMode), setTopicID(info.TopicID, info.Topic), ) + syncAllMembers := wa.Main.Config.MaxMemberSync < 0 || len(info.Participants) < wa.Main.Config.MaxMemberSync wrapped := &bridgev2.ChatInfo{ Name: ptr.Ptr(info.Name), Topic: ptr.Ptr(info.Topic), Members: &bridgev2.ChatMemberList{ - IsFull: !info.IsIncognito && !info.IsParent, + IsFull: !info.IsIncognito && !info.IsParent && syncAllMembers, TotalMemberCount: len(info.Participants), MemberMap: make(map[networkid.UserID]bridgev2.ChatMember, len(info.Participants)), PowerLevels: &bridgev2.PowerLevelOverrides{ @@ -315,6 +316,9 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn } else if pcp.IsAdmin { member.PowerLevel = ptr.Ptr(adminPL) } else { + if !syncAllMembers && !member.EventSender.IsFromMe { + continue + } member.PowerLevel = ptr.Ptr(defaultPL) } member.MemberEventExtra = map[string]any{ diff --git a/pkg/connector/config.go b/pkg/connector/config.go index e51c80a5..d0695985 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -52,6 +52,7 @@ type Config struct { DirectMediaAutoRequest bool `yaml:"direct_media_auto_request"` InitialAutoReconnect bool `yaml:"initial_auto_reconnect"` UseWhatsAppRetryStore bool `yaml:"use_whatsapp_retry_store"` + MaxMemberSync int `yaml:"max_member_sync"` AnimatedSticker msgconv.AnimatedStickerConfig `yaml:"animated_sticker"` @@ -129,6 +130,7 @@ func upgradeConfig(helper up.Helper) { helper.Copy(up.Bool, "direct_media_auto_request") helper.Copy(up.Bool, "initial_auto_reconnect") helper.Copy(up.Bool, "use_whatsapp_retry_store") + helper.Copy(up.Int, "max_member_sync") helper.Copy(up.Str, "animated_sticker", "target") helper.Copy(up.Int, "animated_sticker", "args", "width") diff --git a/pkg/connector/example-config.yaml b/pkg/connector/example-config.yaml index 564f25eb..d2773831 100644 --- a/pkg/connector/example-config.yaml +++ b/pkg/connector/example-config.yaml @@ -69,6 +69,9 @@ initial_auto_reconnect: true # the bridge? By default, the bridge only stores messages in memory, and therefore can't accept # retry receipts if the bridge is restarted after the message is sent. use_whatsapp_retry_store: false +# Maximum size of groups to sync members eagerly in. Defaults to unlimited. +# If set, groups with more than this number of members will not have non-admin participants synced until they talk. +max_member_sync: -1 # Settings for converting animated stickers. animated_sticker: From 6302a1c709e48aaca3cd6b272f3ca4a6ea2cd080 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 3 Sep 2026 14:29:17 +0300 Subject: [PATCH 389/486] userinfo: add option to lazily fetch avatars using direct media --- pkg/connector/config.go | 3 ++ pkg/connector/connector.go | 3 ++ pkg/connector/directmedia.go | 21 ++++++++++++-- pkg/connector/example-config.yaml | 3 ++ pkg/connector/handlewhatsapp.go | 2 +- pkg/connector/startchat.go | 8 +++--- pkg/connector/userinfo.go | 46 ++++++++++++++++++++++++++----- pkg/connector/wadb/avatarcache.go | 14 ++++++++++ pkg/waid/dbmeta.go | 22 ++++++++------- pkg/waid/mediaid.go | 11 ++++++++ 10 files changed, 108 insertions(+), 25 deletions(-) diff --git a/pkg/connector/config.go b/pkg/connector/config.go index d0695985..24874a26 100644 --- a/pkg/connector/config.go +++ b/pkg/connector/config.go @@ -53,6 +53,7 @@ type Config struct { InitialAutoReconnect bool `yaml:"initial_auto_reconnect"` UseWhatsAppRetryStore bool `yaml:"use_whatsapp_retry_store"` MaxMemberSync int `yaml:"max_member_sync"` + LazyAvatars bool `yaml:"lazy_avatars"` AnimatedSticker msgconv.AnimatedStickerConfig `yaml:"animated_sticker"` @@ -131,6 +132,7 @@ func upgradeConfig(helper up.Helper) { helper.Copy(up.Bool, "initial_auto_reconnect") helper.Copy(up.Bool, "use_whatsapp_retry_store") helper.Copy(up.Int, "max_member_sync") + helper.Copy(up.Bool, "lazy_avatars") helper.Copy(up.Str, "animated_sticker", "target") helper.Copy(up.Int, "animated_sticker", "args", "width") @@ -207,6 +209,7 @@ func (wa *WhatsAppConnector) GetConfig() (string, any, up.Upgrader) { {"proxy"}, {"displayname_template"}, {"call_start_notices"}, + {"animated_sticker"}, {"history_sync"}, }, Base: ExampleConfig, diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index e262c82f..193dc4d5 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -142,6 +142,9 @@ func (wa *WhatsAppConnector) Init(bridge *bridgev2.Bridge) { } func (wa *WhatsAppConnector) Start(ctx context.Context) error { + if !wa.MsgConv.DirectMedia && wa.Config.LazyAvatars { + return fmt.Errorf("lazy_avatars set without enabling global direct_media") + } err := wa.DeviceStore.Upgrade(ctx) if err != nil { return bridgev2.DBUpgradeError{Err: err, Section: "whatsmeow"} diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index a875fab9..092ce3e6 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -30,6 +30,7 @@ import ( "github.com/rs/zerolog" "go.mau.fi/util/exsync" + "go.mau.fi/util/jsontime" "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waMmsRetry" @@ -88,9 +89,9 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars if err != nil { return nil, fmt.Errorf("failed to get avatar cache entry: %w", err) } - if cachedInfo != nil && cachedInfo.Gone { + if cachedInfo.IsGone() { return nil, mautrix.MNotFound.WithMessage("Avatar is no longer available (cached response)") - } else if cachedInfo == nil || cachedInfo.Expiry.Time.Before(time.Now().Add(5*time.Minute)) { + } else if cachedInfo.Expired() { zerolog.Ctx(ctx).Debug(). Str("avatar_id", parsedID.Avatar.AvatarID). Msg("Refreshing avatar URL from WhatsApp servers") @@ -99,7 +100,7 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars }) if errors.Is(err, whatsmeow.ErrProfilePictureNotSet) || errors.Is(err, whatsmeow.ErrProfilePictureUnauthorized) || - (err == nil && (avatar == nil || avatar.ID != parsedID.Avatar.AvatarID)) { + (err == nil && (avatar == nil || (avatar.ID != parsedID.Avatar.AvatarID && !parsedID.Avatar.IsRandom()))) { zerolog.Ctx(ctx).Debug(). Err(err). Stringer("target_jid", parsedID.Avatar.TargetJID). @@ -107,9 +108,14 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars Str("wanted_avatar_id", parsedID.Avatar.AvatarID). Str("got_avatar_id", ptr.Val(avatar).ID). Msg("Avatar is no longer available") + var goneExpiry jsontime.Unix + if parsedID.Avatar.IsRandom() { + goneExpiry = jsontime.U(time.Now().Add(7 * 24 * time.Hour)) + } err = wa.DB.AvatarCache.Put(ctx, &wadb.AvatarCacheEntry{ EntityJID: parsedID.Avatar.TargetJID, AvatarID: parsedID.Avatar.AvatarID, + Expiry: goneExpiry, Gone: true, }) if err != nil { @@ -128,6 +134,15 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars Str("avatar_id", avatar.ID). Msg("Failed to update avatar cache entry") } + if cachedInfo.AvatarID != parsedID.Avatar.AvatarID { + cachedInfo.AvatarID = parsedID.Avatar.AvatarID + err = wa.DB.AvatarCache.Put(ctx, cachedInfo) + if err != nil { + zerolog.Ctx(ctx).Warn().Err(err). + Str("avatar_id", parsedID.Avatar.AvatarID). + Msg("Failed to update avatar cache entry") + } + } } return &mediaproxy.GetMediaResponseFile{ Callback: func(w *os.File) (*mediaproxy.FileMeta, error) { diff --git a/pkg/connector/example-config.yaml b/pkg/connector/example-config.yaml index d2773831..905e6d61 100644 --- a/pkg/connector/example-config.yaml +++ b/pkg/connector/example-config.yaml @@ -72,6 +72,9 @@ use_whatsapp_retry_store: false # Maximum size of groups to sync members eagerly in. Defaults to unlimited. # If set, groups with more than this number of members will not have non-admin participants synced until they talk. max_member_sync: -1 +# Lazily load avatars using direct media? +# Global direct media must be enabled for this. +lazy_avatars: false # Settings for converting animated stickers. animated_sticker: diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 4ca2217e..8a758eb1 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -729,7 +729,7 @@ func (wa *WhatsAppClient) syncGhost(jid types.JID, reason string, pictureID *str if pictureID != nil && *pictureID != "" && ghost.AvatarID == networkid.AvatarID(*pictureID) { return } - userInfo, err := wa.getUserInfo(ctx, jid, pictureID != nil) + userInfo, err := wa.getUserInfo(ctx, jid, ptr.Val(pictureID), pictureID != nil) if err != nil { log.Err(err).Msg("Failed to get user info") } else { diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index c1233111..2ba1e090 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -64,12 +64,12 @@ func looksEmaily(str string) bool { return false } -type cacheEntry struct { +type isOnWhatsappCacheEntry struct { jid types.JID ts time.Time } -var isOnWhatsappCache = exsync.NewMap[string, cacheEntry]() +var isOnWhatsappCache = exsync.NewMap[string, isOnWhatsappCacheEntry]() func (wa *WhatsAppClient) validateIdentifer(ctx context.Context, number string) (types.JID, error) { if strings.HasSuffix(number, "@"+types.BotServer) || strings.HasSuffix(number, "@"+types.HiddenUserServer) { @@ -94,7 +94,7 @@ func (wa *WhatsAppClient) validateIdentifer(ctx context.Context, number string) } else if !resp[0].IsIn { return types.EmptyJID, bridgev2.WrapRespErr(fmt.Errorf("the server said +%s is not on WhatsApp", resp[0].JID.User), mautrix.MNotFound) } else { - isOnWhatsappCache.Set(number, cacheEntry{resp[0].JID, time.Now()}) + isOnWhatsappCache.Set(number, isOnWhatsappCacheEntry{resp[0].JID, time.Now()}) return resp[0].JID, nil } } @@ -238,7 +238,7 @@ func (wa *WhatsAppClient) getContactList(ctx context.Context, filter string, onl resp = append(resp, &bridgev2.ResolveIdentifierResponse{ Ghost: ghost, UserID: waid.MakeUserID(jid), - UserInfo: wa.contactToUserInfo(ctx, jid, contactInfo, false), + UserInfo: wa.contactToUserInfo(ctx, jid, contactInfo, "", false), Chat: chatResp, }) } diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index e8279558..05d6c408 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -165,7 +165,7 @@ func (wa *WhatsAppClient) doGhostResync(ctx context.Context, queue map[types.JID log.Warn().Stringer("jid", jid).Msg("Didn't get info for puppet in background sync") continue } - userInfo, err := wa.getUserInfo(ctx, jid, info.PictureID != "" && string(ghost.AvatarID) != info.PictureID) + userInfo, err := wa.getUserInfo(ctx, jid, info.PictureID, info.PictureID != "" && string(ghost.AvatarID) != info.PictureID) if err != nil { log.Err(err).Stringer("jid", jid).Msg("Failed to get user info for puppet in background sync") continue @@ -181,18 +181,18 @@ func (wa *WhatsAppClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost return nil, nil } jid := waid.ParseUserID(ghost.ID) - return wa.getUserInfo(ctx, jid, ghost.AvatarID == "") + return wa.getUserInfo(ctx, jid, "", ghost.AvatarID == "") } -func (wa *WhatsAppClient) getUserInfo(ctx context.Context, jid types.JID, fetchAvatar bool) (*bridgev2.UserInfo, error) { +func (wa *WhatsAppClient) getUserInfo(ctx context.Context, jid types.JID, avatarID string, fetchAvatar bool) (*bridgev2.UserInfo, error) { contact, err := wa.GetStore().Contacts.GetContact(ctx, jid) if err != nil { return nil, err } - return wa.contactToUserInfo(ctx, jid, contact, fetchAvatar), nil + return wa.contactToUserInfo(ctx, jid, contact, avatarID, fetchAvatar), nil } -func (wa *WhatsAppClient) contactToUserInfo(ctx context.Context, jid types.JID, contact types.ContactInfo, getAvatar bool) *bridgev2.UserInfo { +func (wa *WhatsAppClient) contactToUserInfo(ctx context.Context, jid types.JID, contact types.ContactInfo, avatarID string, fetchAvatar bool) *bridgev2.UserInfo { if jid == types.MetaAIJID && contact.PushName == jid.User { contact.PushName = "Meta AI" } else if jid == types.LegacyPSAJID || jid == types.PSAJID { @@ -270,7 +270,9 @@ func (wa *WhatsAppClient) contactToUserInfo(ctx context.Context, jid types.JID, } else if phone != "" { ui.Identifiers = []string{fmt.Sprintf("tel:%s", phone)} } - if getAvatar { + if wa.Main.Config.LazyAvatars { + ui.ExtraUpdates = bridgev2.MergeExtraUpdaters(ui.ExtraUpdates, wa.makeLazyGhostAvatarUpdater(avatarID, fetchAvatar)) + } else if fetchAvatar { ui.ExtraUpdates = bridgev2.MergeExtraUpdaters(ui.ExtraUpdates, wa.fetchGhostAvatar) } return ui @@ -307,6 +309,21 @@ func avatarInfoToCacheEntry(ctx context.Context, jid types.JID, avatar *types.Pr } } +func (wa *WhatsAppClient) makeLazyDirectMediaAvatar(ctx context.Context, jid types.JID, avatarID string, community bool) (*bridgev2.Avatar, error) { + if avatarID == "" { + avatarID = waid.MakeRandomAvatarID() + } + mxc, err := wa.Main.Bridge.Matrix.GenerateContentURI(ctx, waid.MakeAvatarMediaID(jid, avatarID, wa.UserLogin.ID, community)) + if err != nil { + return nil, fmt.Errorf("failed to generate MXC URI: %w", err) + } + return &bridgev2.Avatar{ + ID: networkid.AvatarID(avatarID), + MXC: mxc, + Hash: sha256.Sum256([]byte(avatarID)), + }, nil +} + func (wa *WhatsAppClient) makeDirectMediaAvatar(ctx context.Context, jid types.JID, avatar *types.ProfilePictureInfo, community bool) (*bridgev2.Avatar, error) { mxc, err := wa.Main.Bridge.Matrix.GenerateContentURI(ctx, waid.MakeAvatarMediaID(jid, avatar.ID, wa.UserLogin.ID, community)) if err != nil { @@ -328,6 +345,21 @@ func (wa *WhatsAppClient) makeDirectMediaAvatar(ctx context.Context, jid types.J }, nil } +func (wa *WhatsAppClient) makeLazyGhostAvatarUpdater(avatarID string, forceUpdate bool) func(context.Context, *bridgev2.Ghost) bool { + return func(ctx context.Context, ghost *bridgev2.Ghost) bool { + if ghost.AvatarID != "" && (networkid.AvatarID(avatarID) == ghost.AvatarID || (!forceUpdate && avatarID == "")) { + return false + } + jid := waid.ParseUserID(ghost.ID) + wrappedAvatar, err := wa.makeLazyDirectMediaAvatar(ctx, jid, avatarID, false) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to prepare lazy direct media avatar") + return false + } + return ghost.UpdateAvatar(ctx, wrappedAvatar) + } +} + func (wa *WhatsAppClient) fetchGhostAvatar(ctx context.Context, ghost *bridgev2.Ghost) bool { jid := waid.ParseUserID(ghost.ID) existingID := string(ghost.AvatarID) @@ -399,7 +431,7 @@ func (wa *WhatsAppClient) resyncContacts(forceAvatarSync, automatic bool) { } else if contact, err := contactStore.GetContact(ctx, jid); err != nil { log.Err(err).Stringer("jid", jid).Msg("Failed to get contact info") } else { - userInfo := wa.contactToUserInfo(ctx, jid, contact, forceAvatarSync || ghost.AvatarID == "") + userInfo := wa.contactToUserInfo(ctx, jid, contact, "", forceAvatarSync || ghost.AvatarID == "") ghost.UpdateInfo(ctx, userInfo) wa.syncAltGhostWithInfo(ctx, jid, userInfo) } diff --git a/pkg/connector/wadb/avatarcache.go b/pkg/connector/wadb/avatarcache.go index 8ea947f2..55fb4b97 100644 --- a/pkg/connector/wadb/avatarcache.go +++ b/pkg/connector/wadb/avatarcache.go @@ -2,10 +2,14 @@ package wadb import ( "context" + "strings" + "time" "go.mau.fi/util/dbutil" "go.mau.fi/util/jsontime" "go.mau.fi/whatsmeow/types" + + "go.mau.fi/mautrix-whatsapp/pkg/waid" ) type AvatarCacheQuery struct { @@ -49,3 +53,13 @@ func (ace *AvatarCacheEntry) Scan(row dbutil.Scannable) (*AvatarCacheEntry, erro func (ace *AvatarCacheEntry) sqlVariables() []any { return []any{ace.EntityJID, ace.AvatarID, ace.DirectPath, ace.Expiry, ace.Gone} } + +func (ace *AvatarCacheEntry) IsGone() bool { + return ace != nil && ace.Gone && + // Random IDs can be retried after a specific expiry time, other types of gones can't + (ace.Expiry.IsZero() || !strings.HasPrefix(ace.AvatarID, waid.RandomAvatarIDPrefix) || ace.Expiry.After(time.Now())) +} + +func (ace *AvatarCacheEntry) Expired() bool { + return ace == nil || ace.Expiry.Before(time.Now().Add(5*time.Minute)) +} diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index 1e544267..e2b60f73 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -37,12 +37,12 @@ type UserLoginMetadata struct { PushKeys *PushKeys `json:"push_keys,omitempty"` APNSEncPubKey []byte `json:"apns_enc_pubkey,omitempty"` APNSEncPrivKey []byte `json:"apns_enc_privkey,omitempty"` - LoggedInAt jsontime.Unix `json:"logged_in_at,omitempty"` + LoggedInAt jsontime.Unix `json:"logged_in_at,omitzero"` AppStateRecoveryAttempted map[appstate.WAPatchName]time.Time `json:"app_state_recovery_attempted,omitempty"` - HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitempty"` - ReachoutTimelockUntil time.Time `json:"reachout_timelock_until,omitempty"` + HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitzero"` + ReachoutTimelockUntil time.Time `json:"reachout_timelock_until,omitzero"` MData json.RawMessage `json:"mdata,omitempty"` } @@ -88,13 +88,13 @@ type GroupInviteMeta struct { } type MessageMetadata struct { - SenderDeviceID uint16 `json:"sender_device_id,omitempty"` + SenderDeviceID uint16 `json:"sender_device_id,omitzero"` Error MessageErrorType `json:"error,omitempty"` BroadcastListJID *types.JID `json:"broadcast_list_jid,omitempty"` GroupInvite *GroupInviteMeta `json:"group_invite,omitempty"` FailedMediaMeta json.RawMessage `json:"media_meta,omitempty"` DirectMediaMeta json.RawMessage `json:"direct_media_meta,omitempty"` - IsMatrixPoll bool `json:"is_matrix_poll,omitempty"` + IsMatrixPoll bool `json:"is_matrix_poll,omitzero"` Edits []types.MessageID `json:"edits,omitempty"` } @@ -122,14 +122,16 @@ type ReactionMetadata struct { } type PortalMetadata struct { - DisappearingTimerSetAt int64 `json:"disappearing_timer_set_at,omitempty"` + DisappearingTimerSetAt int64 `json:"disappearing_timer_set_at,omitzero"` TopicID string `json:"topic_id,omitempty"` - LastSync jsontime.Unix `json:"last_sync,omitempty"` - CommunityAnnouncementGroup bool `json:"is_cag,omitempty"` + LastSync jsontime.Unix `json:"last_sync,omitzero"` + CommunityAnnouncementGroup bool `json:"is_cag,omitzero"` AddressingMode types.AddressingMode `json:"addressing_mode,omitempty"` - LIDMigrationAttempted bool `json:"lid_migration_attempted,omitempty"` + LIDMigrationAttempted bool `json:"lid_migration_attempted,omitzero"` } type GhostMetadata struct { - LastSync jsontime.Unix `json:"last_sync,omitempty"` + LastSync jsontime.Unix `json:"last_sync,omitzero"` + + DirectAvatarURL string `json:"direct_avatar_url,omitempty"` } diff --git a/pkg/waid/mediaid.go b/pkg/waid/mediaid.go index 716e187b..a0ac2d90 100644 --- a/pkg/waid/mediaid.go +++ b/pkg/waid/mediaid.go @@ -24,6 +24,7 @@ import ( "strconv" "strings" + "go.mau.fi/util/random" "go.mau.fi/whatsmeow/types" "maunium.net/go/mautrix/bridgev2/networkid" ) @@ -85,6 +86,16 @@ type AvatarMediaInfo struct { Community bool } +const RandomAvatarIDPrefix = "mxwa-lazy-avatar-" + +func MakeRandomAvatarID() string { + return RandomAvatarIDPrefix + random.String(8) +} + +func (ami *AvatarMediaInfo) IsRandom() bool { + return ami != nil && strings.HasPrefix(ami.AvatarID, RandomAvatarIDPrefix) +} + func MakeStickerPackMediaID(packID string, fileHash []byte, receiver networkid.UserLoginID) networkid.MediaID { receiverID := compactJID(ParseUserLoginID(receiver, 0)) mediaID := make([]byte, 0, 4+len(packID)+len(fileHash)+len(receiverID)) From 240648081e1ac70b6d5a530f57a6d26f317c9480 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 3 Sep 2026 14:37:14 +0300 Subject: [PATCH 390/486] startchat: return user info with resolve identifier requests --- pkg/connector/startchat.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index 2ba1e090..18e83263 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -176,11 +176,16 @@ func (wa *WhatsAppClient) ResolveIdentifier(ctx context.Context, identifier stri if err != nil { return nil, fmt.Errorf("failed to get ghost: %w", err) } + userInfo, err := wa.getUserInfo(ctx, jid, "", false) + if err != nil { + return nil, fmt.Errorf("failed to get user info: %w", err) + } return &bridgev2.ResolveIdentifierResponse{ - Ghost: ghost, - UserID: waid.MakeUserID(jid), - Chat: wa.makeCreateChatResponse(ctx, jid, origJID), + Ghost: ghost, + UserID: waid.MakeUserID(jid), + UserInfo: userInfo, + Chat: wa.makeCreateChatResponse(ctx, jid, origJID), }, nil } From 63dd2582b5c11397aecfa938240e094d7fe45d0d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 8 Sep 2026 14:41:40 +0300 Subject: [PATCH 391/486] dependencies: update --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 288eb6f3..5b539851 100644 --- a/go.mod +++ b/go.mod @@ -12,13 +12,13 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260903111606-de26b4ab6499 + go.mau.fi/whatsmeow v0.0.0-20260908082135-57796d3d6b41 golang.org/x/image v0.45.0 golang.org/x/net v0.58.0 golang.org/x/sync v0.22.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.30.1-0.20260828224424-c22bd4fdc9e6 + maunium.net/go/mautrix v0.30.1-0.20260908102503-2f2c98079d9e ) require ( diff --git a/go.sum b/go.sum index e5d6221f..38bb208b 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbs go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260903111606-de26b4ab6499 h1:7RG3BeC9qXh2aQ0RwjIFZxhCwTohdtNzJaKHxna32pE= -go.mau.fi/whatsmeow v0.0.0-20260903111606-de26b4ab6499/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= +go.mau.fi/whatsmeow v0.0.0-20260908082135-57796d3d6b41 h1:EUpS1/OiThY4/S1jgG5jGpC7bILOtM/Q1r1b7BI9NrM= +go.mau.fi/whatsmeow v0.0.0-20260908082135-57796d3d6b41/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= @@ -107,5 +107,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.30.1-0.20260828224424-c22bd4fdc9e6 h1:mL1BquGn+7efD4ECX62uHVN2MnsrC65JuOgxDdKFF7o= -maunium.net/go/mautrix v0.30.1-0.20260828224424-c22bd4fdc9e6/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= +maunium.net/go/mautrix v0.30.1-0.20260908102503-2f2c98079d9e h1:T4g/2u7ZEbAGkYGNizjYZSltWR5m0DrX5UDWqfZJdS4= +maunium.net/go/mautrix v0.30.1-0.20260908102503-2f2c98079d9e/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= From 446bd22695570a0b13486f41851251772e8697b8 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 8 Sep 2026 14:42:26 +0300 Subject: [PATCH 392/486] lidmigrate: resync missing avatars and reuse info for alt ghost sync --- pkg/connector/connector.go | 10 +++++- pkg/connector/handlewhatsapp.go | 2 +- pkg/connector/lidmigrate.go | 57 +++++++++++++++++++++++++++++++++ pkg/connector/userinfo.go | 23 ++++++++++--- 4 files changed, 86 insertions(+), 6 deletions(-) diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 193dc4d5..3602e935 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -160,7 +160,15 @@ func (wa *WhatsAppConnector) Start(ctx context.Context) error { return bridgev2.DBUpgradeError{Err: err, Section: "whatsapp"} } - return wa.migrateToLIDDMs(ctx) + err = wa.migrateToLIDDMs(ctx) + if err != nil { + return fmt.Errorf("failed to migrate to LID DMs: %w", err) + } + err = wa.syncMismatchingGhosts(ctx) + if err != nil { + return fmt.Errorf("failed to sync mismatching ghosts: %w", err) + } + return nil } func (wa *WhatsAppConnector) Stop() { diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 8a758eb1..d8df69f9 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -735,7 +735,7 @@ func (wa *WhatsAppClient) syncGhost(jid types.JID, reason string, pictureID *str } else { ghost.UpdateInfo(ctx, userInfo) log.Debug().Msg("Synced ghost info") - wa.syncAltGhostWithInfo(ctx, jid, userInfo) + wa.syncAltGhostWithInfo(ctx, jid, ghost) } go wa.syncRemoteProfile(ctx, ghost) } diff --git a/pkg/connector/lidmigrate.go b/pkg/connector/lidmigrate.go index cfab099b..ec1c700e 100644 --- a/pkg/connector/lidmigrate.go +++ b/pkg/connector/lidmigrate.go @@ -210,3 +210,60 @@ func (wa *WhatsAppConnector) migrateToLIDDMs(ctx context.Context) error { wa.Bridge.DB.KV.Set(ctx, "whatsapp_lid_dms_migrated", "true") return nil } + +func (wa *WhatsAppConnector) syncMismatchingGhosts(ctx context.Context) error { + if wa.Bridge.Background || wa.Bridge.DB.KV.Get(ctx, "whatsapp_lid_avatars_resynced") == "true" { + return nil + } + const findMismatchingGhosts = ` + SELECT id + FROM ghost + WHERE bridge_id=$1 + AND id LIKE 'lid-%' + AND avatar_mxc='' + AND EXISTS ( + SELECT pnghost.avatar_mxc + FROM ghost pnghost + WHERE pnghost.bridge_id=$1 + AND pnghost.id=(SELECT pn FROM whatsmeow_lid_map WHERE lid=replace(ghost.id, 'lid-', '')) + AND pnghost.avatar_mxc<>'' + ) + ` + var scanGhostID = dbutil.ConvertRowFn[networkid.UserID](dbutil.ScanSingleColumn[networkid.UserID]) + ghostIDs, err := scanGhostID.NewRowIter(wa.Bridge.DB.Query(ctx, findMismatchingGhosts, wa.Bridge.ID)).AsList() + if err != nil { + return fmt.Errorf("failed to get mismatching ghosts: %w", err) + } + for _, ghostID := range ghostIDs { + lid := waid.ParseUserID(ghostID) + pn, err := wa.DeviceStore.LIDMap.GetPNForLID(ctx, lid) + if err != nil { + return fmt.Errorf("failed to get PN for LID %s: %w", lid, err) + } else if pn.IsEmpty() { + zerolog.Ctx(ctx).Warn().Stringer("lid", lid).Msg("No PN for LID") + continue + } + pnGhost, err := wa.Bridge.GetGhostByID(ctx, waid.MakeUserID(pn)) + if err != nil { + return fmt.Errorf("failed to get PN ghost for %s: %w", pn, err) + } + lidGhost, err := wa.Bridge.GetGhostByID(ctx, ghostID) + if err != nil { + return fmt.Errorf("failed to get LID ghost for %s: %w", ghostID, err) + } + if lidGhost.AvatarMXC != "" || pnGhost.AvatarMXC == "" { + continue + } + zerolog.Ctx(ctx).Debug(). + Stringer("pn", pn). + Stringer("lid", lid). + Str("pn_ghost_avatar", string(pnGhost.AvatarMXC)). + Str("lid_ghost_avatar", string(lidGhost.AvatarMXC)). + Str("pn_ghost_name", pnGhost.Name). + Str("lid_ghost_name", lidGhost.Name). + Msg("Updating LID ghost avatar") + lidGhost.UpdateInfo(ctx, makeInfoFromGhost(pnGhost)) + } + wa.Bridge.DB.KV.Set(ctx, "whatsapp_lid_avatars_resynced", "true") + return nil +} diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 05d6c408..c78b0a8e 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -171,7 +171,7 @@ func (wa *WhatsAppClient) doGhostResync(ctx context.Context, queue map[types.JID continue } ghost.UpdateInfo(ctx, userInfo) - wa.syncAltGhostWithInfo(ctx, jid, userInfo) + wa.syncAltGhostWithInfo(ctx, jid, ghost) } } @@ -433,12 +433,12 @@ func (wa *WhatsAppClient) resyncContacts(forceAvatarSync, automatic bool) { } else { userInfo := wa.contactToUserInfo(ctx, jid, contact, "", forceAvatarSync || ghost.AvatarID == "") ghost.UpdateInfo(ctx, userInfo) - wa.syncAltGhostWithInfo(ctx, jid, userInfo) + wa.syncAltGhostWithInfo(ctx, jid, ghost) } } } -func (wa *WhatsAppClient) syncAltGhostWithInfo(ctx context.Context, jid types.JID, info *bridgev2.UserInfo) { +func (wa *WhatsAppClient) syncAltGhostWithInfo(ctx context.Context, jid types.JID, mainGhost *bridgev2.Ghost) { log := zerolog.Ctx(ctx) var altJID types.JID var err error @@ -463,10 +463,25 @@ func (wa *WhatsAppClient) syncAltGhostWithInfo(ctx context.Context, jid types.JI Msg("Failed to get ghost for alternate JID") return } - ghost.UpdateInfo(ctx, info) + ghost.UpdateInfo(ctx, makeInfoFromGhost(mainGhost)) log.Debug(). Stringer("jid", jid). Stringer("alternate_jid", altJID). Msg("Synced alternate ghost with info") go wa.syncRemoteProfile(ctx, ghost) } + +func makeInfoFromGhost(ghost *bridgev2.Ghost) *bridgev2.UserInfo { + return &bridgev2.UserInfo{ + Identifiers: ghost.Identifiers, + Name: &ghost.Name, + Avatar: &bridgev2.Avatar{ + ID: ghost.AvatarID, + Remove: ghost.AvatarID == "" || ghost.AvatarMXC == "", + MXC: ghost.AvatarMXC, + Hash: ghost.AvatarHash, + }, + IsBot: &ghost.IsBot, + ExtraProfile: ghost.ExtraProfile, + } +} From a19ef78dd33af9d6a520a58c549bf5e01d46a73b Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 8 Sep 2026 15:03:18 +0300 Subject: [PATCH 393/486] directmedia: don't allow parallel downloads of the same avatar --- pkg/connector/client.go | 1 + pkg/connector/directmedia.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index f0a59475..8c302e32 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -116,6 +116,7 @@ type WhatsAppClient struct { nextResync time.Time directMediaRetries map[networkid.MessageID]*directMediaRetry directMediaLock sync.Mutex + avatarLock exsync.KeyedMutex[types.JID] mediaRetryLock *semaphore.Weighted offlineSyncWaiter atomic.Pointer[chan error] isNewLogin bool diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index 092ce3e6..8ed94cea 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -85,6 +85,8 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars if waClient.Client == nil { return nil, fmt.Errorf("no WhatsApp client found on login %s", parsedID.UserLogin) } + waClient.avatarLock.Lock(parsedID.Avatar.TargetJID) + defer waClient.avatarLock.Unlock(parsedID.Avatar.TargetJID) cachedInfo, err := wa.DB.AvatarCache.Get(ctx, parsedID.Avatar.TargetJID, parsedID.Avatar.AvatarID) if err != nil { return nil, fmt.Errorf("failed to get avatar cache entry: %w", err) From 82c79d1108e7c1badbcc9b2019cb560b6daaa7a2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 8 Sep 2026 19:09:47 +0300 Subject: [PATCH 394/486] 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 5b539851..bf892b03 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.22.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.30.1-0.20260908102503-2f2c98079d9e + maunium.net/go/mautrix v0.30.1-0.20260908153423-501dff148280 ) require ( diff --git a/go.sum b/go.sum index 38bb208b..a0ba2a81 100644 --- a/go.sum +++ b/go.sum @@ -107,5 +107,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.30.1-0.20260908102503-2f2c98079d9e h1:T4g/2u7ZEbAGkYGNizjYZSltWR5m0DrX5UDWqfZJdS4= -maunium.net/go/mautrix v0.30.1-0.20260908102503-2f2c98079d9e/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= +maunium.net/go/mautrix v0.30.1-0.20260908153423-501dff148280 h1:AoNwPhJCX0y93tp2nJPu/RuIt/+K+Zu4p4Q44+hKEhc= +maunium.net/go/mautrix v0.30.1-0.20260908153423-501dff148280/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= From 9ea41dcb0efa00597230cb17da42b01463c82700 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 9 Sep 2026 12:40:38 +0300 Subject: [PATCH 395/486] login: add support for adv rotation --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/login.go | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bf892b03..1c7be5cf 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260908082135-57796d3d6b41 + go.mau.fi/whatsmeow v0.0.0-20260909093947-9ec8f76db5f1 golang.org/x/image v0.45.0 golang.org/x/net v0.58.0 golang.org/x/sync v0.22.0 diff --git a/go.sum b/go.sum index a0ba2a81..1c70cc40 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbs go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260908082135-57796d3d6b41 h1:EUpS1/OiThY4/S1jgG5jGpC7bILOtM/Q1r1b7BI9NrM= -go.mau.fi/whatsmeow v0.0.0-20260908082135-57796d3d6b41/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= +go.mau.fi/whatsmeow v0.0.0-20260909093947-9ec8f76db5f1 h1:4EyoinbJqFq4qozxem5nklT8+pI4OaokUuG+ayE3nKQ= +go.mau.fi/whatsmeow v0.0.0-20260909093947-9ec8f76db5f1/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= diff --git a/pkg/connector/login.go b/pkg/connector/login.go index a62b2948..80a6abbb 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/http" + "strings" "sync/atomic" "time" @@ -98,6 +99,7 @@ func (wa *WhatsAppConnector) CreateLogin(_ context.Context, user *bridgev2.User, LoginComplete: exsync.NewEvent(), PasskeyRequest: exsync.NewEvent(), PasskeyConfirmation: exsync.NewEvent(), + ADVRotate: exsync.NewEvent(), Received515: exsync.NewEvent(), }, nil } @@ -124,6 +126,8 @@ type WALogin struct { PasskeyConfirmation *exsync.Event PasskeyConfirmationData *events.PairPasskeyConfirmation + ADVRotate *exsync.Event + Closed atomic.Bool EventHandlerID uint32 } @@ -270,6 +274,12 @@ func (wl *WALogin) handleEvent(rawEvt any) { wl.StartTime = time.Now() wl.WaitForQRs.Set() return + case *events.RotateADVSecret: + wl.Log.Debug().Msg("Rotating ADV secret in all QRs") + for i, code := range wl.QRs { + wl.QRs[i] = strings.Replace(code, evt.OldSecret, evt.NewSecret, 1) + } + wl.ADVRotate.Set() case *events.QRScannedWithoutMultidevice: wl.Log.Error().Msg("QR code scanned without multidevice enabled") wl.LoginError = ErrLoginMultideviceNotEnabled @@ -332,6 +342,7 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { Int("current_index", currentIndex) if currentIndex > prevIndex { logEvt.Msg("Returning new QR immediately") + wl.ADVRotate.Clear() wl.PrevQRIndex.Store(int32(currentIndex)) return makeQRStep(wl.QRs[currentIndex]), nil } @@ -343,6 +354,7 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { wl.Cancel() return nil, ErrLoginTimeout } + wl.ADVRotate.Clear() wl.PrevQRIndex.Store(int32(nextIndex)) return makeQRStep(wl.QRs[nextIndex]), nil case <-ctx.Done(): @@ -350,6 +362,10 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { return nil, ctx.Err() case <-wl.PasskeyRequest.GetChan(): return wl.makePasskeyStep() + case <-wl.ADVRotate.GetChan(): + wl.Log.Debug().Msg("ADV secret was rotated, returning new QR immediately") + wl.ADVRotate.Clear() + return makeQRStep(wl.QRs[nextIndex]), nil case <-wl.LoginComplete.GetChan(): // continue } From 79164b22ad498b3de829205d5e112f0e817f28a6 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 10 Sep 2026 10:42:06 +0300 Subject: [PATCH 396/486] lidmigrate: resync ghost avatars in background --- pkg/connector/connector.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 3602e935..d7261d3a 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -164,10 +164,12 @@ func (wa *WhatsAppConnector) Start(ctx context.Context) error { if err != nil { return fmt.Errorf("failed to migrate to LID DMs: %w", err) } - err = wa.syncMismatchingGhosts(ctx) - if err != nil { - return fmt.Errorf("failed to sync mismatching ghosts: %w", err) - } + go func() { + err = wa.syncMismatchingGhosts(wa.Bridge.BackgroundCtx) + if err != nil { + wa.Bridge.Log.Err(err).Msg("Failed to sync mismatching ghosts") + } + }() return nil } From 18ae384c98866536bb3db90d6c56ba016227848a Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 11 Sep 2026 19:03:14 +0300 Subject: [PATCH 397/486] .github: make contributing.md more visible --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..764f753e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +See From 5d3fce8eff7e5079e24a924d83663e0f4bddec2f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 11 Sep 2026 19:42:24 +0300 Subject: [PATCH 398/486] handlewhatsapp: ignore sent broadcasts --- pkg/connector/chatinfo.go | 4 +++- pkg/connector/handlewhatsapp.go | 7 +++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 675a5ce4..267200ce 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -29,6 +29,8 @@ func (wa *WhatsAppClient) GetChatInfo(ctx context.Context, portal *bridgev2.Port return wa.getChatInfo(ctx, portalJID, nil, portal.MXID == "") } +var ErrBroadcastList = errors.New("broadcast list bridging is currently not supported") + func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, conv *wadb.Conversation, isNew bool) (wrapped *bridgev2.ChatInfo, err error) { switch portalJID.Server { case types.DefaultUserServer, types.HiddenUserServer, types.BotServer: @@ -37,7 +39,7 @@ func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, if portalJID == types.StatusBroadcastJID { wrapped = wa.wrapStatusBroadcastInfo(ctx) } else { - return nil, fmt.Errorf("broadcast list bridging is currently not supported") + return nil, ErrBroadcastList } case types.GroupServer: info, err := wa.Client.GetGroupInfo(ctx, portalJID) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index d8df69f9..ed36dcaa 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -284,10 +284,9 @@ func (wa *WhatsAppClient) ensureAltJIDs(ctx context.Context, info *types.Message func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Message) (success bool) { success = true - if evt.Info.Chat == types.StatusBroadcastJID && !wa.Main.Config.EnableStatusBroadcast { - return - } - if evt.Info.Chat.Server == types.NewsletterServer && wa.disableNewsletter { + if (evt.Info.Chat == types.StatusBroadcastJID && !wa.Main.Config.EnableStatusBroadcast) || + (evt.Info.Chat.Server == types.NewsletterServer && wa.disableNewsletter) || + (evt.Info.Chat.Server == types.BroadcastServer && evt.Info.IsFromMe) { return } if !wa.ensureAltJIDs(ctx, &evt.Info.MessageSource, true) { From d6bfdeb477dc1cd4d7d33717b860a268624211c4 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 11 Sep 2026 21:33:53 +0300 Subject: [PATCH 399/486] dependencies: update mautrix-go --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/client.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 1c7be5cf..692c5abf 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( golang.org/x/sync v0.22.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.30.1-0.20260908153423-501dff148280 + maunium.net/go/mautrix v0.30.1-0.20260911182621-dc9b2d7a77fb ) require ( diff --git a/go.sum b/go.sum index 1c70cc40..3634f94e 100644 --- a/go.sum +++ b/go.sum @@ -107,5 +107,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.30.1-0.20260908153423-501dff148280 h1:AoNwPhJCX0y93tp2nJPu/RuIt/+K+Zu4p4Q44+hKEhc= -maunium.net/go/mautrix v0.30.1-0.20260908153423-501dff148280/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= +maunium.net/go/mautrix v0.30.1-0.20260911182621-dc9b2d7a77fb h1:QimfAexEmbycZEYon7vv11k8jgb7SYCDrsaniDNETLU= +maunium.net/go/mautrix v0.30.1-0.20260911182621-dc9b2d7a77fb/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 8c302e32..a9099467 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -78,7 +78,7 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. w.Client = whatsmeow.NewClient(w.Device, waLog.Zerolog(log)) w.Client.AddEventHandlerWithSuccessStatus(w.handleWAEvent) w.Client.SynchronousAck = true - w.Client.EnableDecryptedEventBuffer = bridgev2.PortalEventBuffer == 0 + w.Client.EnableDecryptedEventBuffer = wa.Bridge.Config.PortalEventBuffer == 0 w.Client.ManualHistorySyncDownload = true w.Client.SendReportingTokens = true w.Client.AutomaticMessageRerequestFromPhone = true From 037423f0b2eb93019e52da8ada6c96e8a40e8955 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 14 Sep 2026 15:34:54 +0300 Subject: [PATCH 400/486] 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 692c5abf..a8390bf3 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260909093947-9ec8f76db5f1 + go.mau.fi/whatsmeow v0.0.0-20260914123426-9120a3fc6159 golang.org/x/image v0.45.0 golang.org/x/net v0.58.0 golang.org/x/sync v0.22.0 diff --git a/go.sum b/go.sum index 3634f94e..71240703 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbs go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260909093947-9ec8f76db5f1 h1:4EyoinbJqFq4qozxem5nklT8+pI4OaokUuG+ayE3nKQ= -go.mau.fi/whatsmeow v0.0.0-20260909093947-9ec8f76db5f1/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= +go.mau.fi/whatsmeow v0.0.0-20260914123426-9120a3fc6159 h1:M23L3U7EL7tDIIVgNvp1XV4wU+sKKjBmFkZ7DBNEzdg= +go.mau.fi/whatsmeow v0.0.0-20260914123426-9120a3fc6159/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= From 991c12cdab59416ce98271f0ef983ecee04ce68e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 14 Sep 2026 15:35:08 +0300 Subject: [PATCH 401/486] changelog: update --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d17865f..b6567d62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +# v26.09 (unreleased) + +* Bumped minimum Go version to 1.26. +* Added option to limit eager member sync in group chat portals. +* Added option to fetch avatars lazily when using direct media. +* Added one-time migration to ensure LID ghost avatars are consistent with the + old phone number ghosts. +* Fixed the resolve identifier command and API not returning any user info for + users who hadn't previously been encountered. +* Fixed group portal power levels to always allow poll responses. +* Fixed handling history sync events where the phone sends nonsensical + timestamps. +* Fixed handling edits to HD media captions. +* Fixed handling bridging own read receipts from the native apps in channels. + # v26.08 * Switched direct chats to use LIDs instead of phone numbers. From c1e30935dc3fb407d6c0a8bd778b64175949db5e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 14 Sep 2026 18:07:29 +0300 Subject: [PATCH 402/486] 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 a8390bf3..83f457fd 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260914123426-9120a3fc6159 + go.mau.fi/whatsmeow v0.0.0-20260914150520-0d3b644136bf golang.org/x/image v0.45.0 golang.org/x/net v0.58.0 golang.org/x/sync v0.22.0 diff --git a/go.sum b/go.sum index 71240703..d56e1c41 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbs go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260914123426-9120a3fc6159 h1:M23L3U7EL7tDIIVgNvp1XV4wU+sKKjBmFkZ7DBNEzdg= -go.mau.fi/whatsmeow v0.0.0-20260914123426-9120a3fc6159/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= +go.mau.fi/whatsmeow v0.0.0-20260914150520-0d3b644136bf h1:9iToTs8b6MMX8lWSryybzR9sqYGYG4/LpXj+cy0UC4k= +go.mau.fi/whatsmeow v0.0.0-20260914150520-0d3b644136bf/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= From a76341f281c7b6f1ac3913a2a47bd309f082b5ab Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 14 Sep 2026 18:17:24 +0300 Subject: [PATCH 403/486] handlewhatsapp: log unknown protocol message types --- pkg/connector/handlewhatsapp.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index ed36dcaa..53813117 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -334,7 +334,15 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa wa.Main.Bridge.Config.Backfill.Enabled { wa.saveWAHistorySyncNotification(ctx, evt.Message.ProtocolMessage.HistorySyncNotification) } - if parsedMessageType == "ignore" || strings.HasPrefix(parsedMessageType, "unknown_protocol_") { + if parsedMessageType == "ignore" { + return + } else if strings.HasPrefix(parsedMessageType, "unknown_protocol_") { + wa.UserLogin.Log.Debug(). + Str("message_id", evt.Info.ID). + Stringer("chat_jid", evt.Info.Chat). + Stringer("sender_jid", evt.Info.Sender). + Stringer("protocol_message_type", evt.Message.GetProtocolMessage().GetType()). + Msg("Ignoring unknown protocol message") return } From 1ea9f02ade4b7c5c2cc058199912c5ce9c5189b0 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 14 Sep 2026 19:28:43 +0300 Subject: [PATCH 404/486] msgconv/from-whatsapp: bridge advanced rich responses as unknown message types --- pkg/msgconv/from-whatsapp.go | 2 +- pkg/msgconv/wa-misc.go | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index e912732d..6dac5a28 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -190,7 +190,7 @@ func (mc *MessageConverter) ToMatrix( case waMsg.KeepInChatMessage != nil: part, contextInfo = mc.convertKeepInChatMessage(ctx, waMsg.KeepInChatMessage) case waMsg.RichResponseMessage != nil: - part, contextInfo = mc.convertRichResponseMessage(ctx, waMsg.RichResponseMessage) + part, contextInfo = mc.convertRichResponseMessage(ctx, waMsg.RichResponseMessage, waMsg) case waMsg.ImageMessage != nil: part, contextInfo = mc.convertMediaMessage(ctx, waMsg.ImageMessage, "photo", info, isViewOnce, previouslyConvertedPart) case waMsg.StickerMessage != nil: diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index fa495e0c..be5b60f9 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -351,7 +351,7 @@ func (mc *MessageConverter) convertKeepInChatMessage(ctx context.Context, msg *w }, nil } -func (mc *MessageConverter) convertRichResponseMessage(ctx context.Context, msg *waE2E.AIRichResponseMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { +func (mc *MessageConverter) convertRichResponseMessage(ctx context.Context, msg *waE2E.AIRichResponseMessage, fullMsg *waE2E.Message) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { var body strings.Builder // TODO switch to new format? @@ -363,10 +363,14 @@ func (mc *MessageConverter) convertRichResponseMessage(ctx context.Context, msg body.WriteString(submsg.GetMessageText()) } } - + unknownPart, _ := mc.convertUnknownMessage(ctx, fullMsg) + if body.Len() == 0 { + return unknownPart, msg.GetContextInfo() + } content := format.RenderMarkdown(body.String(), true, false) return &bridgev2.ConvertedMessagePart{ Type: event.EventMessage, Content: &content, + Extra: unknownPart.Extra, }, msg.GetContextInfo() } From 741f45d6497abb18bbcf9bb93106ddc3b9b5aa68 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 16 Sep 2026 00:22:26 +0300 Subject: [PATCH 405/486] 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 83f457fd..ccd3fceb 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/tidwall/gjson v1.19.0 go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260914150520-0d3b644136bf + go.mau.fi/whatsmeow v0.0.0-20260915211301-f376da267f95 golang.org/x/image v0.45.0 golang.org/x/net v0.58.0 golang.org/x/sync v0.22.0 diff --git a/go.sum b/go.sum index d56e1c41..5c05090a 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbs go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260914150520-0d3b644136bf h1:9iToTs8b6MMX8lWSryybzR9sqYGYG4/LpXj+cy0UC4k= -go.mau.fi/whatsmeow v0.0.0-20260914150520-0d3b644136bf/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= +go.mau.fi/whatsmeow v0.0.0-20260915211301-f376da267f95 h1:xwn1RBsgFKCFDTLKpsdrpfgZoLX6wlfHKRBpGjnpkVI= +go.mau.fi/whatsmeow v0.0.0-20260915211301-f376da267f95/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= From 9361103b64f5718ffd03f76bc40cb436f30c9efd Mon Sep 17 00:00:00 2001 From: Toni Spets Date: Wed, 16 Sep 2026 12:00:30 +0300 Subject: [PATCH 406/486] backfill: don't panic when the whatsmeow client is nil (#954) --- pkg/connector/backfill.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index cab460fd..e0f5ffc6 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -602,6 +602,9 @@ func (wa *WhatsAppClient) convertHistorySyncMessages( messages []*waWeb.WebMessageInfo, explodeOnError bool, ) (*bridgev2.FetchMessagesResponse, error) { + if wa.Client == nil { + return nil, bridgev2.ErrNotLoggedIn + } oldestTS := messages[len(messages)-1].GetMessageTimestamp() newestTS := messages[0].GetMessageTimestamp() convertedMessages := make([]*bridgev2.BackfillMessage, 0, len(messages)) From 52e75964a0f03a0b3e21e658a2fd7b345dff40b4 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 16 Sep 2026 13:14:00 +0300 Subject: [PATCH 407/486] Bump version to v26.09 --- CHANGELOG.md | 5 ++- cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 28 ++++++++--------- go.sum | 60 +++++++++++++++++------------------- 4 files changed, 47 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6567d62..d5dd1738 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# v26.09 (unreleased) +# v26.09 * Bumped minimum Go version to 1.26. * Added option to limit eager member sync in group chat portals. @@ -12,6 +12,9 @@ timestamps. * Fixed handling edits to HD media captions. * Fixed handling bridging own read receipts from the native apps in channels. +* Fixed decrypting messages from the new Muse AI bot + * May require resyncing app state using `!wa sync appstate regular_high`. + * The message contents aren't supported yet. # v26.08 diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index 011e9b0d..a1d85c3c 100644 --- a/cmd/mautrix-whatsapp/main.go +++ b/cmd/mautrix-whatsapp/main.go @@ -18,7 +18,7 @@ var m = mxmain.BridgeMain{ Name: "mautrix-whatsapp", URL: "https://github.com/mautrix/whatsapp", Description: "A Matrix-WhatsApp puppeting bridge.", - Version: "26.08", + Version: "26.09", SemCalVer: true, Connector: &connector.WhatsAppConnector{}, } diff --git a/go.mod b/go.mod index ccd3fceb..3768dca8 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module go.mau.fi/mautrix-whatsapp go 1.26.0 -toolchain go1.27.0 +toolchain go1.27.1 tool go.mau.fi/util/cmd/maubuild @@ -10,15 +10,15 @@ require ( github.com/lib/pq v1.12.3 github.com/rs/zerolog v1.35.1 github.com/tidwall/gjson v1.19.0 - go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde + go.mau.fi/util v0.10.1 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260915211301-f376da267f95 - golang.org/x/image v0.45.0 - golang.org/x/net v0.58.0 - golang.org/x/sync v0.22.0 + go.mau.fi/whatsmeow v0.0.0-20260916100317-2375e1751bbd + golang.org/x/image v0.46.0 + golang.org/x/net v0.59.0 + golang.org/x/sync v0.23.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.30.1-0.20260911182621-dc9b2d7a77fb + maunium.net/go/mautrix v0.31.0 ) require ( @@ -31,7 +31,7 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.50 // indirect + github.com/mattn/go-sqlite3 v1.14.52 // indirect github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/rs/xid v1.6.0 // indirect @@ -40,14 +40,14 @@ require ( github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect github.com/vektah/gqlparser/v2 v2.5.27 // indirect - github.com/yuin/goldmark v1.8.5 // indirect + github.com/yuin/goldmark v1.8.6 // indirect go.mau.fi/libsignal v0.2.2 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect - golang.org/x/crypto v0.55.0 // indirect - golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa // indirect - golang.org/x/mod v0.40.0 // indirect - golang.org/x/sys v0.47.0 // indirect - golang.org/x/text v0.41.0 // indirect + golang.org/x/crypto v0.57.0 // indirect + golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba // indirect + golang.org/x/mod v0.41.0 // indirect + golang.org/x/sys v0.48.0 // indirect + golang.org/x/text v0.42.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect maunium.net/go/mauflag v1.0.0 // indirect diff --git a/go.sum b/go.sum index 5c05090a..ac06de49 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,6 @@ github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6p github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/elliotchance/orderedmap/v3 v3.1.0 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg= github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -34,13 +32,11 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.50 h1:dmdFvo1XG4MPzA4IkAmE9upVz/Nj31uRoM5+jC8hYbY= -github.com/mattn/go-sqlite3 v1.14.50/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= +github.com/mattn/go-sqlite3 v1.14.52 h1:wVbm2Qnf4OXkqhBTSPuCRZDRnxfbVrrmiCEroVdog8U= +github.com/mattn/go-sqlite3 v1.14.52/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 h1:lcWAnrqr2nNfDiArwFNHCE4787Mw2tCdVSOXCru0/0E= github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= @@ -52,8 +48,8 @@ github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI= +github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= @@ -67,35 +63,35 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTdwFp0s= github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= -github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA= -github.com/yuin/goldmark v1.8.5/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark v1.8.6 h1:d0VcaP1sx9GkFVkoW+KtggpGi2KZ965i14b0+bDQST4= +github.com/yuin/goldmark v1.8.6/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.2 h1:QV+XdzQkm3x3aSG7FcqfGSZuFXz83pRZPBFaPygHbOU= go.mau.fi/libsignal v0.2.2/go.mod h1:CRlIQg2J8uYTfDFvNoO8/KcZjs5cey0vbc6oj/bssY0= -go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbsxj7G6mfujjKei1HaFQM= -go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8= +go.mau.fi/util v0.10.1 h1:1oSqb4TwzLA0cUDY0aomyBPFKkZ3J5fqCmrXV3VH3GQ= +go.mau.fi/util v0.10.1/go.mod h1:40TDo7/ekSeOjgr8KAmX31Yf4zrOF94j83WQB+u5ZPc= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260915211301-f376da267f95 h1:xwn1RBsgFKCFDTLKpsdrpfgZoLX6wlfHKRBpGjnpkVI= -go.mau.fi/whatsmeow v0.0.0-20260915211301-f376da267f95/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg= +go.mau.fi/whatsmeow v0.0.0-20260916100317-2375e1751bbd h1:ZRMG9rK+Vghe4U+xGDGI2hkwJgoPTqf25HWm3nkBM8w= +go.mau.fi/whatsmeow v0.0.0-20260916100317-2375e1751bbd/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= -golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= -golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= -golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa h1:QSyA8ishJCyT21kER9KwNt0b7BM3iRK4x9QXhjN5Fdk= -golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa/go.mod h1:zeBbvyFKDaLwa7CH/zI8KXt7gTl14SF7sO08Pl5jBCM= -golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0= -golang.org/x/image v0.45.0/go.mod h1:n62x/7RqlwXDvGsSU4u6IUTUf6KghUZ9Bt7cG/T9Fx4= -golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= -golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= -golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= -golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= -golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= +golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba h1:Ck8QetSgk912qxWLMCKxd0in+aiyBQyDSMae6e/xmpU= +golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba/go.mod h1:50RgIsmK7OwqzTTeqcSXQW8SswW0o8fRcDxmqGluJ8E= +golang.org/x/image v0.46.0 h1:b1+oYj0Jbp6K5MDT4i4/eZpYlk3V8SJhhDKh6LBHAyQ= +golang.org/x/image v0.46.0/go.mod h1:3B3W05VGVQyuXucLINLjXKrqISASfi4Xj+iCVkLMwew= +golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= +golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= +golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= -golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -107,5 +103,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.30.1-0.20260911182621-dc9b2d7a77fb h1:QimfAexEmbycZEYon7vv11k8jgb7SYCDrsaniDNETLU= -maunium.net/go/mautrix v0.30.1-0.20260911182621-dc9b2d7a77fb/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0= +maunium.net/go/mautrix v0.31.0 h1:x6XNBSa0kOaBgcleI+ZBLkGvBdHIE0qdeY6TZ4wWUeI= +maunium.net/go/mautrix v0.31.0/go.mod h1:vfPYtoGAjlTnIF+W8tl+rjX3yB4PEfqYR2sDXvQo4Co= From e4b680c9f31904b25bf6480108fe9f734fb4108d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 21 Sep 2026 15:32:31 +0300 Subject: [PATCH 408/486] msgconv/from-whatsapp: add support for new rich response format --- go.mod | 6 +- go.sum | 8 +- pkg/msgconv/wa-misc.go | 5 +- pkg/msgconv/wa-rich-response.go | 177 ++++++++++++++++++++++++++++++++ 4 files changed, 187 insertions(+), 9 deletions(-) create mode 100644 pkg/msgconv/wa-rich-response.go diff --git a/go.mod b/go.mod index 3768dca8..fc817495 100644 --- a/go.mod +++ b/go.mod @@ -10,9 +10,10 @@ require ( github.com/lib/pq v1.12.3 github.com/rs/zerolog v1.35.1 github.com/tidwall/gjson v1.19.0 - go.mau.fi/util v0.10.1 + github.com/yuin/goldmark v1.8.6 + go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260916100317-2375e1751bbd + go.mau.fi/whatsmeow v0.0.0-20260921121126-35ae40906e74 golang.org/x/image v0.46.0 golang.org/x/net v0.59.0 golang.org/x/sync v0.23.0 @@ -40,7 +41,6 @@ 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.6 // indirect go.mau.fi/libsignal v0.2.2 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect golang.org/x/crypto v0.57.0 // indirect diff --git a/go.sum b/go.sum index ac06de49..4a39eb48 100644 --- a/go.sum +++ b/go.sum @@ -67,12 +67,12 @@ github.com/yuin/goldmark v1.8.6 h1:d0VcaP1sx9GkFVkoW+KtggpGi2KZ965i14b0+bDQST4= github.com/yuin/goldmark v1.8.6/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.2 h1:QV+XdzQkm3x3aSG7FcqfGSZuFXz83pRZPBFaPygHbOU= go.mau.fi/libsignal v0.2.2/go.mod h1:CRlIQg2J8uYTfDFvNoO8/KcZjs5cey0vbc6oj/bssY0= -go.mau.fi/util v0.10.1 h1:1oSqb4TwzLA0cUDY0aomyBPFKkZ3J5fqCmrXV3VH3GQ= -go.mau.fi/util v0.10.1/go.mod h1:40TDo7/ekSeOjgr8KAmX31Yf4zrOF94j83WQB+u5ZPc= +go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8 h1:PxaO4u8ti7Eef1WKPAINXrcUkqKshE+n239UtbbJOn4= +go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8/go.mod h1:40TDo7/ekSeOjgr8KAmX31Yf4zrOF94j83WQB+u5ZPc= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260916100317-2375e1751bbd h1:ZRMG9rK+Vghe4U+xGDGI2hkwJgoPTqf25HWm3nkBM8w= -go.mau.fi/whatsmeow v0.0.0-20260916100317-2375e1751bbd/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= +go.mau.fi/whatsmeow v0.0.0-20260921121126-35ae40906e74 h1:z3cItqSX0u+rsM6/81MB1I4bBa92jPAf6QZAvmGc0L0= +go.mau.fi/whatsmeow v0.0.0-20260921121126-35ae40906e74/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index be5b60f9..cda33c50 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -352,9 +352,10 @@ func (mc *MessageConverter) convertKeepInChatMessage(ctx context.Context, msg *w } func (mc *MessageConverter) convertRichResponseMessage(ctx context.Context, msg *waE2E.AIRichResponseMessage, fullMsg *waE2E.Message) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { + if msg.GetUnifiedResponse() != nil { + return mc.convertUnifiedRichResponseMessage(ctx, msg.GetUnifiedResponse().GetData()), msg.GetContextInfo() + } var body strings.Builder - - // TODO switch to new format? for i, submsg := range msg.GetSubmessages() { if submsg.GetMessageType() == waAICommonDeprecated.AIRichResponseSubMessageType_AI_RICH_RESPONSE_TEXT { if i > 0 { diff --git a/pkg/msgconv/wa-rich-response.go b/pkg/msgconv/wa-rich-response.go new file mode 100644 index 00000000..d2dab516 --- /dev/null +++ b/pkg/msgconv/wa-rich-response.go @@ -0,0 +1,177 @@ +// 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 ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "html" + "strings" + + "github.com/rs/zerolog" + "github.com/yuin/goldmark" + "go.mau.fi/whatsmeow/types/richresponse" + "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/format" + "maunium.net/go/mautrix/format/mdext" +) + +func (mc *MessageConverter) convertUnifiedRichResponseMessage(ctx context.Context, data []byte) *bridgev2.ConvertedMessagePart { + extra := map[string]any{} + if json.Valid(data) && len(data) < 24*1024 { + extra["fi.mau.whatsapp.rich_message"] = json.RawMessage(data) + } else if len(data) < 16*1024 { + extra["fi.mau.whatsapp.non_json_rich_message"] = base64.StdEncoding.EncodeToString(data) + } + var rrMsg richresponse.RichResponse + err := json.Unmarshal(data, &rrMsg) + if err != nil || len(rrMsg.Sections) == 0 { + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to unmarshal unified rich response message") + } + return &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: &event.MessageEventContent{ + MsgType: event.MsgNotice, + Body: "Unknown message type, please view it on the WhatsApp app", + }, + Extra: extra, + } + } + extra["fi.mau.whatsapp.rich_response_id"] = rrMsg.ResponseID + var htmlBuf strings.Builder + for _, mv := range rrMsg.Sections { + for _, p := range mv.Model.GetPrimitives() { + mc.convertRichResponsePrimitive(ctx, p, &htmlBuf) + } + } + return &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: new(format.HTMLToContent(htmlBuf.String())), + Extra: extra, + } +} + +var mdRender = goldmark.New(format.Extensions, format.HTMLOptions, goldmark.WithExtensions(mdext.EscapeHTML)) + +func (mc *MessageConverter) convertRichMarkdownText(ctx context.Context, text *richresponse.GenAIMarkdownTextUXPrimitive, buf *strings.Builder) error { + for _, ent := range text.InlineEntities { + switch meta := ent.Metadata.(type) { + //case *richresponse.GenAISearchCitationItem: + case *richresponse.GenAIInlineLinkItem: + text.Text = strings.ReplaceAll( + text.Text, + fmt.Sprintf("{{%s}}", ent.Key), + fmt.Sprintf("[%s](%s)", meta.DisplayName, meta.URL), + ) + case *richresponse.GenAIDeepLinkItem: + text.Text = strings.ReplaceAll( + text.Text, + fmt.Sprintf("{{%s}}", ent.Key), + meta.Text, + ) + case *richresponse.GenAILatexItem: + text.Text = strings.ReplaceAll( + text.Text, + fmt.Sprintf("{{%s}}", ent.Key), + fmt.Sprintf(`%[1]s`, html.EscapeString(meta.LatexExpression)), + ) + default: + text.Text = strings.ReplaceAll( + text.Text, + fmt.Sprintf("{{%s}}", ent.Key), + ent.Key, + ) + } + } + return mdRender.Convert([]byte(text.Text), buf) +} + +func (mc *MessageConverter) convertRichResponsePrimitive(ctx context.Context, primitive richresponse.Primitive, buf *strings.Builder) error { + switch p := primitive.(type) { + case *richresponse.GenAICodeUXPrimitive: + if p.Language != "" { + _, _ = fmt.Fprintf(buf, `

`, html.EscapeString(p.Language))
+		} else {
+			buf.WriteString("
")
+		}
+		for _, part := range p.CodeBlocks {
+			buf.WriteString(part.Content)
+		}
+		buf.WriteString("
") + case *richresponse.GenAIMarkdownTextUXPrimitive: + return mc.convertRichMarkdownText(ctx, p, buf) + case *richresponse.GenATableUXPrimitive: + buf.WriteString("") + for _, row := range p.Rows { + buf.WriteString("") + cellType := "tr" + if row.IsHeader { + cellType = "th" + } + if len(row.MarkdownCells) > 0 { + for _, cell := range row.MarkdownCells { + _, _ = fmt.Fprintf(buf, "<%s>", cellType) + err := mc.convertRichMarkdownText(ctx, &cell, buf) + if err != nil { + return err + } + _, _ = fmt.Fprintf(buf, "", cellType) + } + } else { + for _, cell := range row.Cells { + _, _ = fmt.Fprintf(buf, "<%s>%s", cellType, html.EscapeString(cell), cellType) + } + } + buf.WriteString("") + } + buf.WriteString("
") + case *richresponse.GenAILatexUXPrimitive: + _, _ = fmt.Fprintf(buf, `%[1]s`, html.EscapeString(p.GetLatexExpression())) + case *richresponse.FOATextPrimitive: + // TODO remove placeholders? + return mdRender.Convert([]byte(p.Text), buf) + case *richresponse.GenAIMetadataTextPrimitive: + return mdRender.Convert([]byte(p.Text), buf) + //case *richresponse.GenAIBotThinkingStatusPrimitive: + //case *richresponse.GenAIProductItemCardPrimitive: + //case *richresponse.GenAIImagePrimitive: + //case *richresponse.GenAITaskPrimitive: + //case *richresponse.GenAIReelPrimitive: + //case *richresponse.GenAIPostPrimitive: + //case *richresponse.GenAIImaginePrimitive: + //case *richresponse.GenAISearchResultPrimitive: + //case *richresponse.FOABloksPrimitive: + case *richresponse.GenAIDividerPrimitive: + // TODO dots? + buf.WriteString("
") + case *richresponse.GenAISpacerPrimitive: + if p.Spacing <= 1 { + buf.WriteString("
") + } else { + for range p.Spacing { + buf.WriteString("
") + } + } + default: + buf.WriteString("

Unknown primitive type

") + } + return nil +} From 85aa4a6d0d2a2a34954231f30f9c9939d6b0d47e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 21 Sep 2026 15:56:51 +0300 Subject: [PATCH 409/486] msgconv/from-whatsapp: cut off too long messages --- go.mod | 2 +- go.sum | 4 ++-- pkg/msgconv/wa-text.go | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index fc817495..97c0a0a0 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( golang.org/x/sync v0.23.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.31.0 + maunium.net/go/mautrix v0.31.1-0.20260921130557-78e83388b27e ) require ( diff --git a/go.sum b/go.sum index 4a39eb48..d7de15c9 100644 --- a/go.sum +++ b/go.sum @@ -103,5 +103,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.31.0 h1:x6XNBSa0kOaBgcleI+ZBLkGvBdHIE0qdeY6TZ4wWUeI= -maunium.net/go/mautrix v0.31.0/go.mod h1:vfPYtoGAjlTnIF+W8tl+rjX3yB4PEfqYR2sDXvQo4Co= +maunium.net/go/mautrix v0.31.1-0.20260921130557-78e83388b27e h1:ndauW8laSq7G105w9hPkTjwaOw5/f2z2SgMr9Yq6MKw= +maunium.net/go/mautrix v0.31.1-0.20260921130557-78e83388b27e/go.mod h1:t97J+6KmbmeoepWOunBgpu1xIvuBpI36ebBqIbYT8Jk= diff --git a/pkg/msgconv/wa-text.go b/pkg/msgconv/wa-text.go index 032e3fd8..7859f37f 100644 --- a/pkg/msgconv/wa-text.go +++ b/pkg/msgconv/wa-text.go @@ -42,6 +42,12 @@ func (mc *MessageConverter) convertTextMessage(ctx context.Context, msg *waE2E.M } contextInfo = msg.GetExtendedTextMessage().GetContextInfo() mc.parseFormatting(part.Content, false, false) + if len(part.Content.FormattedBody) > 20000 { + part.Content.Body = part.Content.Body[:500] + "[...]" + } + if len(part.Content.FormattedBody) > 40000 { + part.Content.FormattedBody = part.Content.FormattedBody[:40000] + "
[long message cut off]" + } part.Content.BeeperLinkPreviews = mc.convertURLPreviewToBeeper(ctx, msg.GetExtendedTextMessage()) return } From 52f0c60aa60efd39fce13fa65113f026f50db43a Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 24 Sep 2026 15:04:51 +0300 Subject: [PATCH 410/486] handlewhatsapp: dispatch unavailable resend responses directly as edits --- pkg/connector/events.go | 5 ++++- pkg/connector/handlewhatsapp.go | 18 +++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 1f4e118b..c23189d6 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -315,7 +315,10 @@ func (evt *WANowDecryptableMessage) GetTargetMessage() networkid.MessageID { } func (evt *WANowDecryptableMessage) AddLogContext(c zerolog.Context) zerolog.Context { - return c + if evt.editParts != nil { + return c.Bool("from_upsert", true) + } + return evt.WAMessageEvent.AddLogContext(c.Bool("from_upsert", false)) } func (evt *WANowDecryptableMessage) GetType() bridgev2.RemoteEventType { diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 53813117..dba4f1ee 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -371,7 +371,7 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa return } - res := wa.UserLogin.QueueRemoteEvent(&WAMessageEvent{ + wrappedEvt := &WAMessageEvent{ MessageInfoWrapper: &MessageInfoWrapper{ Info: evt.Info, wa: wa, @@ -381,8 +381,20 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa parsedMessageType: parsedMessageType, dontRenderEdited: dontRenderEdited, - }) - return res.Success + } + if evt.UnavailableRequestID != "" { + wa.UserLogin.Log.Debug(). + Str("message_id", evt.Info.ID). + Str("unavailable_request_id", evt.UnavailableRequestID). + Msg("Received placeholder resend response") + wa.trackUndecryptableResolved(evt) + wrappedEvt.isUndecryptableUpsertSubEvent = true + // Dispatch directly as edit instead of the usual upsert, so that the message doesn't + // get re-bridged if it was deleted before the resend response was received. + return wa.UserLogin.QueueRemoteEvent(&WANowDecryptableMessage{WAMessageEvent: wrappedEvt}).Success + } + + return wa.UserLogin.QueueRemoteEvent(wrappedEvt).Success } func makeHDMediaReplacementEdit(message *waE2E.Message, parentKey *waCommon.MessageKey) (*waE2E.ProtocolMessage, bool) { From 173f18405c619fa69708c54ec72bb5c64bd031f8 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 25 Sep 2026 19:27:23 +0300 Subject: [PATCH 411/486] login: wait for otk upload before confirming login success --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/client.go | 10 ++++++++-- pkg/connector/login.go | 10 ++++++++-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 97c0a0a0..6f12b2a5 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/yuin/goldmark v1.8.6 go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260921121126-35ae40906e74 + go.mau.fi/whatsmeow v0.0.0-20260925162019-b3832c2bd1d1 golang.org/x/image v0.46.0 golang.org/x/net v0.59.0 golang.org/x/sync v0.23.0 diff --git a/go.sum b/go.sum index d7de15c9..7e657875 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,8 @@ go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8 h1:PxaO4u8ti7Eef1WKPAINXrcU go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8/go.mod h1:40TDo7/ekSeOjgr8KAmX31Yf4zrOF94j83WQB+u5ZPc= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260921121126-35ae40906e74 h1:z3cItqSX0u+rsM6/81MB1I4bBa92jPAf6QZAvmGc0L0= -go.mau.fi/whatsmeow v0.0.0-20260921121126-35ae40906e74/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= +go.mau.fi/whatsmeow v0.0.0-20260925162019-b3832c2bd1d1 h1:iGMEoW98F/m73hSf4+EwV8l5yVfsZGFqvYRM7dAMWs4= +go.mau.fi/whatsmeow v0.0.0-20260925162019-b3832c2bd1d1/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= diff --git a/pkg/connector/client.go b/pkg/connector/client.go index a9099467..2287fead 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -206,13 +206,17 @@ func (wa *WhatsAppClient) GetLID() types.JID { } func (wa *WhatsAppClient) Connect(ctx context.Context) { + wa.connect(ctx) +} + +func (wa *WhatsAppClient) connect(ctx context.Context) bool { if wa.Client == nil { state := status.BridgeState{ StateEvent: status.StateBadCredentials, Error: WANotLoggedIn, } wa.UserLogin.BridgeState.Send(state) - return + return false } wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnecting}) wa.Main.firstClientConnectOnce.Do(wa.Main.onFirstClientConnect) @@ -220,7 +224,7 @@ func (wa *WhatsAppClient) Connect(ctx context.Context) { zerolog.Ctx(ctx).Err(err).Msg("Failed to update proxy") } if ctx.Err() != nil { - return + return false } wa.initMC() wa.startLoops() @@ -237,7 +241,9 @@ func (wa *WhatsAppClient) Connect(ctx context.Context) { }, } wa.UserLogin.BridgeState.Send(state) + return false } + return true } func (wa *WhatsAppClient) notifyOfflineSyncWaiter(err error) { diff --git a/pkg/connector/login.go b/pkg/connector/login.go index 80a6abbb..fa2c4792 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -410,8 +410,14 @@ func (wl *WALogin) onLoginComplete(ctx context.Context) (*bridgev2.LoginStep, er return nil, fmt.Errorf("failed to create user login: %w", err) } - ul.Client.(*WhatsAppClient).isNewLogin = true - ul.Client.Connect(ul.Log.WithContext(wl.Main.Bridge.BackgroundCtx)) + c := ul.Client.(*WhatsAppClient) + c.isNewLogin = true + if c.connect(ul.Log.WithContext(wl.Main.Bridge.BackgroundCtx)) { + err = c.Client.PreKeysUploaded.WaitTimeoutCtx(ctx, 5*time.Second) + if err != nil { + wl.Log.Warn().Err(err).Msg("Prekey upload wait failed") + } + } return &bridgev2.LoginStep{ Type: bridgev2.LoginStepTypeComplete, From 054064eddb27405fe353f0a6dfc75a00e4f93982 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 27 Sep 2026 18:04:19 +0300 Subject: [PATCH 412/486] backfill,msgconv: handle panics in on-demand history syncs --- pkg/connector/backfill.go | 32 +++++++++++++++++++++++++++++--- pkg/msgconv/from-matrix.go | 2 +- pkg/msgconv/from-whatsapp.go | 14 ++++++++++++-- pkg/msgconv/matrixpoll.go | 6 +++--- pkg/msgconv/wa-poll.go | 6 +++--- 5 files changed, 48 insertions(+), 12 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index e0f5ffc6..50a0d4c2 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "runtime/debug" "slices" "strconv" "strings" @@ -12,6 +13,7 @@ import ( "time" "github.com/rs/zerolog" + "go.mau.fi/util/exerrors" "go.mau.fi/util/exmaps" "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" @@ -62,7 +64,7 @@ func (wa *WhatsAppClient) historySyncLoop(ctx context.Context) { } }() for { - var resetTimer bool + var resetTimer, errored bool select { case <-wa.historySyncWakeup: dispatchTimer.Stop() @@ -72,12 +74,21 @@ func (wa *WhatsAppClient) historySyncLoop(ctx context.Context) { } else if notif == nil { wa.UserLogin.Log.Debug().Msg("No more queued history sync notifications") } else { - resetTimer = wa.downloadAndSaveWAHistorySyncData(ctx, notif, rowid) + resetTimer, errored = wa.downloadAndSaveWAHistorySyncData(ctx, notif, rowid) // Continue waking up the loop until all queued notifications are processed select { case wa.historySyncWakeup <- struct{}{}: default: } + if errored { + wa.UserLogin.Log.Debug().Msg("Temporarily stopping history sync loop after error") + select { + case <-time.After(1 * time.Minute): + case <-ctx.Done(): + wa.UserLogin.Log.Debug().Msg("Stopping main history sync loop") + return + } + } } case <-ctx.Done(): wa.UserLogin.Log.Debug().Msg("Stopping main history sync loop") @@ -109,13 +120,21 @@ func (wa *WhatsAppClient) saveWAHistorySyncNotification(ctx context.Context, evt } } -func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context, evt *waE2E.HistorySyncNotification, rowid int) (resetTimer bool) { +func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context, evt *waE2E.HistorySyncNotification, rowid int) (resetTimer, errored bool) { log := wa.UserLogin.Log.With(). Str("action", "download history sync"). Stringer("sync_type", evt.GetSyncType()). Uint32("chunk_order", evt.GetChunkOrder()). Uint32("progress", evt.GetProgress()). Logger() + defer func() { + if v := recover(); v != nil { + log.Err(exerrors.RecoverToError(v)). + Bytes(zerolog.ErrorStackFieldName, debug.Stack()). + Msg("History sync saving panicked") + errored = true + } + }() log.Debug(). Int64("oldest_msg_in_chunk_ts", evt.GetOldestMsgInChunkTimestampSec()). Any("full_request_meta", evt.GetFullHistorySyncOnDemandRequestMetadata()). @@ -367,6 +386,13 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { log := wa.UserLogin.Log.With(). Str("action", "create portals from history sync"). Logger() + defer func() { + if v := recover(); v != nil { + log.Err(exerrors.RecoverToError(v)). + Bytes(zerolog.ErrorStackFieldName, debug.Stack()). + Msg("History sync portal creation panicked") + } + }() ctx = log.WithContext(ctx) limit := wa.Main.Config.HistorySync.MaxInitialConversations loginTS := wa.UserLogin.Metadata.(*waid.UserLoginMetadata).LoggedInAt diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 57f9113c..b3ddcf90 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, lid) + lid, err = getStore(client).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 6dac5a28..99d036f7 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -30,6 +30,7 @@ import ( "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waE2E" + "go.mau.fi/whatsmeow/store" "go.mau.fi/whatsmeow/types" _ "golang.org/x/image/webp" "maunium.net/go/mautrix/bridgev2" @@ -277,10 +278,10 @@ func (mc *MessageConverter) ToMatrix( var pn, lid types.JID if pcp.Server == types.DefaultUserServer { pn = pcp - lid, _ = client.Store.LIDs.GetLIDForPN(ctx, pcp) + lid, _ = getStore(client).LIDs.GetLIDForPN(ctx, pcp) } else if pcp.Server == types.HiddenUserServer { lid = pcp - pn, _ = client.Store.LIDs.GetPNForLID(ctx, pcp) + pn, _ = getStore(client).LIDs.GetPNForLID(ctx, pcp) } else if pcp.Server == types.BotServer { lid = pcp } @@ -322,3 +323,12 @@ func (mc *MessageConverter) ToMatrix( return cm } + +func getStore(cli *whatsmeow.Client) *store.Device { + if cli != nil { + if currentStore := cli.Store; currentStore != nil { + return currentStore + } + } + return store.NoopDevice +} diff --git a/pkg/msgconv/matrixpoll.go b/pkg/msgconv/matrixpoll.go index a5f02ea9..5d5d0145 100644 --- a/pkg/msgconv/matrixpoll.go +++ b/pkg/msgconv/matrixpoll.go @@ -150,8 +150,8 @@ func MessageIDToInfo(ctx context.Context, client *whatsmeow.Client, parsedMsgID chat := parsedMsgID.Chat sender := parsedMsgID.Sender if chat.Server == types.DefaultUserServer { - chatLID, _ := client.Store.LIDs.GetLIDForPN(ctx, chat) - senderLID, _ := client.Store.LIDs.GetLIDForPN(ctx, sender) + chatLID, _ := getStore(client).LIDs.GetLIDForPN(ctx, chat) + senderLID, _ := getStore(client).LIDs.GetLIDForPN(ctx, sender) if !chatLID.IsEmpty() && !senderLID.IsEmpty() { chat = chatLID sender = senderLID @@ -161,7 +161,7 @@ func MessageIDToInfo(ctx context.Context, client *whatsmeow.Client, parsedMsgID MessageSource: types.MessageSource{ Chat: chat, Sender: sender, - IsFromMe: sender.User == client.Store.GetLID().User || sender.User == client.Store.GetJID().User, + IsFromMe: sender.User == getStore(client).GetLID().User || sender.User == getStore(client).GetJID().User, IsGroup: chat.Server == types.GroupServer, }, ID: parsedMsgID.ID, diff --git a/pkg/msgconv/wa-poll.go b/pkg/msgconv/wa-poll.go index 881a5fb0..2bcf671c 100644 --- a/pkg/msgconv/wa-poll.go +++ b/pkg/msgconv/wa-poll.go @@ -108,7 +108,7 @@ func KeyToMessageID(ctx context.Context, client *whatsmeow.Client, chat, sender sender.Server = types.DefaultUserServer } } 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 { + if sender.User == getStore(client).GetJID().User || sender.User == getStore(client).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 sender = chat @@ -116,9 +116,9 @@ func KeyToMessageID(ctx context.Context, client *whatsmeow.Client, chat, sender // Message key is not from the sender, but message sender (containing key) is not me, // so message key sender is me if chat.Server == types.HiddenUserServer { - sender = client.Store.GetLID().ToNonAD() + sender = getStore(client).GetLID().ToNonAD() } else { - sender = client.Store.GetJID().ToNonAD() + sender = getStore(client).GetJID().ToNonAD() } } } else { From bcd77819e3f7655c953372f10191d6109f76e9e0 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Mon, 28 Sep 2026 12:21:56 +0000 Subject: [PATCH 413/486] handlewhatsapp: use proper profile for Muse and fix message handling (#958) Co-authored-by: Indent --- go.mod | 4 +- go.sum | 8 ++-- pkg/connector/events.go | 20 +++++++++ pkg/connector/handlewhatsapp.go | 6 +++ pkg/connector/muse.go | 80 +++++++++++++++++++++++++++++++++ pkg/msgconv/wa-rich-response.go | 29 +++++++----- 6 files changed, 129 insertions(+), 18 deletions(-) create mode 100644 pkg/connector/muse.go diff --git a/go.mod b/go.mod index 6f12b2a5..dcae3bb8 100644 --- a/go.mod +++ b/go.mod @@ -13,13 +13,13 @@ require ( github.com/yuin/goldmark v1.8.6 go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260925162019-b3832c2bd1d1 + go.mau.fi/whatsmeow v0.0.0-20260927171547-45cfce066cd2 golang.org/x/image v0.46.0 golang.org/x/net v0.59.0 golang.org/x/sync v0.23.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.31.1-0.20260921130557-78e83388b27e + maunium.net/go/mautrix v0.31.1-0.20260928113010-0d8772dfa579 ) require ( diff --git a/go.sum b/go.sum index 7e657875..82983afe 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,8 @@ go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8 h1:PxaO4u8ti7Eef1WKPAINXrcU go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8/go.mod h1:40TDo7/ekSeOjgr8KAmX31Yf4zrOF94j83WQB+u5ZPc= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260925162019-b3832c2bd1d1 h1:iGMEoW98F/m73hSf4+EwV8l5yVfsZGFqvYRM7dAMWs4= -go.mau.fi/whatsmeow v0.0.0-20260925162019-b3832c2bd1d1/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= +go.mau.fi/whatsmeow v0.0.0-20260927171547-45cfce066cd2 h1:MdSiBaMjvUIFbk8Cqf90CJU0JrRYXnvGKQNoM3ciaas= +go.mau.fi/whatsmeow v0.0.0-20260927171547-45cfce066cd2/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= @@ -103,5 +103,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.31.1-0.20260921130557-78e83388b27e h1:ndauW8laSq7G105w9hPkTjwaOw5/f2z2SgMr9Yq6MKw= -maunium.net/go/mautrix v0.31.1-0.20260921130557-78e83388b27e/go.mod h1:t97J+6KmbmeoepWOunBgpu1xIvuBpI36ebBqIbYT8Jk= +maunium.net/go/mautrix v0.31.1-0.20260928113010-0d8772dfa579 h1:CfIu/q0W0z8uLR9o6C0kcFQqFTTWUi2j9+Vd9yQV1xg= +maunium.net/go/mautrix v0.31.1-0.20260928113010-0d8772dfa579/go.mod h1:t97J+6KmbmeoepWOunBgpu1xIvuBpI36ebBqIbYT8Jk= diff --git a/pkg/connector/events.go b/pkg/connector/events.go index c23189d6..c5cdac85 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -201,6 +201,7 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por cm := evt.wa.Main.MsgConv.ToMatrix( ctx, portal, evt.wa.Client, intent, editedMsg, evt.MsgEvent.RawMessage, &evt.Info, evt.isViewOnce(), false, previouslyConvertedPart, ) + evt.addMuseProfile(portal, cm) if evt.isUndecryptableUpsertSubEvent && isFailedMedia(cm) { evt.postHandle = func() { evt.wa.processFailedMedia(ctx, portal.PortalKey, evt.GetID(), cm, false) @@ -281,11 +282,30 @@ func (evt *WAMessageEvent) HandleExisting(ctx context.Context, portal *bridgev2. return bridgev2.UpsertResult{}, nil } +func (evt *WAMessageEvent) addMuseProfile(portal *bridgev2.Portal, converted *bridgev2.ConvertedMessage) { + if evt.Info.Chat != types.MuseJID || evt.Info.IsFromMe || !portal.NameIsCustom { + return + } + profile := &event.BeeperPerMessageProfile{ + ID: string(portal.MXID), + Displayname: portal.Name, + } + if portal.AvatarMXC != "" { + profile.AvatarURL = &portal.AvatarMXC + } + for _, part := range converted.Parts { + if part.Content != nil { + part.Content.BeeperPerMessageProfile = profile + } + } +} + 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, ) + evt.addMuseProfile(portal, converted) if isFailedMedia(converted) { evt.postHandle = func() { evt.wa.processFailedMedia(ctx, portal.PortalKey, evt.GetID(), converted, false) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index dba4f1ee..f4010982 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -334,6 +334,12 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa wa.Main.Bridge.Config.Backfill.Enabled { wa.saveWAHistorySyncNotification(ctx, evt.Message.ProtocolMessage.HistorySyncNotification) } + if protocol := evt.Message.GetProtocolMessage(); evt.Info.Chat == types.MuseJID && protocol.GetType() == waE2E.ProtocolMessage_AI_METADATA_OPERATION { + if sync := protocol.GetAiMetadataOperation().GetHatchMetadataSync(); sync != nil { + return wa.handleMuseMetadata(ctx, sync.GetData()) + } + return + } if parsedMessageType == "ignore" { return } else if strings.HasPrefix(parsedMessageType, "unknown_protocol_") { diff --git a/pkg/connector/muse.go b/pkg/connector/muse.go new file mode 100644 index 00000000..73c4206a --- /dev/null +++ b/pkg/connector/muse.go @@ -0,0 +1,80 @@ +package connector + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + + "github.com/rs/zerolog" + "go.mau.fi/whatsmeow" + "go.mau.fi/whatsmeow/types" + "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/networkid" + "maunium.net/go/mautrix/bridgev2/simplevent" +) + +type museImage struct { + DirectPath string `json:"direct_path"` + EncHash []byte `json:"file_enc_sha256_b64"` + Hash []byte `json:"file_sha256_b64"` + Key []byte `json:"media_key_b64"` + Length int `json:"file_length"` +} + +func (wa *WhatsAppClient) handleMuseMetadata(ctx context.Context, data []byte) bool { + var msg struct { + Type string `json:"type"` + Payload struct { + Event string `json:"event"` + Payload struct { + Name string `json:"name"` + Avatar struct { + Image museImage `json:"secure_image"` + } `json:"avatar"` + } `json:"payload"` + } `json:"payload"` + } + if err := json.Unmarshal(data, &msg); err != nil { + zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to decode Muse metadata") + return true + } + if msg.Type != "event" || msg.Payload.Event != "identity.updated" || msg.Payload.Payload.Name == "" { + return true + } + name := msg.Payload.Payload.Name + return wa.UserLogin.QueueRemoteEvent(&simplevent.ChatInfoChange{ + EventMeta: simplevent.EventMeta{ + Type: bridgev2.RemoteEventChatInfoChange, + PortalKey: wa.makeWAPortalKey(types.MuseJID), + CreatePortal: true, + }, + ChatInfoChange: &bridgev2.ChatInfoChange{ + ChatInfo: &bridgev2.ChatInfo{ + Name: &name, + Avatar: wa.museAvatar(msg.Payload.Payload.Avatar.Image), + ExcludeChangesFromTimeline: true, + }, + }, + }).Success +} + +func (wa *WhatsAppClient) museAvatar(image museImage) *bridgev2.Avatar { + if image.DirectPath == "" || image.Length <= 0 || image.Length > 5<<20 { + return nil + } + if len(image.EncHash) != 32 || len(image.Hash) != 32 || len(image.Key) != 32 { + return nil + } + return &bridgev2.Avatar{ + ID: networkid.AvatarID(base64.StdEncoding.EncodeToString(image.Hash)), + Get: func(ctx context.Context) ([]byte, error) { + data, err := wa.Client.DownloadMediaWithPath(ctx, image.DirectPath, image.EncHash, image.Hash, image.Key, whatsmeow.MediaImage, "image", false) + if err != nil { + zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to download Muse avatar") + return nil, fmt.Errorf("failed to download Muse avatar: %w", err) + } + return data, nil + }, + } +} diff --git a/pkg/msgconv/wa-rich-response.go b/pkg/msgconv/wa-rich-response.go index d2dab516..41ac9ab9 100644 --- a/pkg/msgconv/wa-rich-response.go +++ b/pkg/msgconv/wa-rich-response.go @@ -26,6 +26,7 @@ import ( "github.com/rs/zerolog" "github.com/yuin/goldmark" + "github.com/yuin/goldmark/extension" "go.mau.fi/whatsmeow/types/richresponse" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/event" @@ -42,10 +43,20 @@ func (mc *MessageConverter) convertUnifiedRichResponseMessage(ctx context.Contex } var rrMsg richresponse.RichResponse err := json.Unmarshal(data, &rrMsg) - if err != nil || len(rrMsg.Sections) == 0 { - if err != nil { - zerolog.Ctx(ctx).Err(err).Msg("Failed to unmarshal unified rich response message") + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to unmarshal unified rich response message") + rrMsg.Sections = nil + } + var htmlBuf strings.Builder + for _, mv := range rrMsg.Sections { + if mv.Model == nil { + continue } + for _, p := range mv.Model.GetPrimitives() { + mc.convertRichResponsePrimitive(ctx, p, &htmlBuf) + } + } + if htmlBuf.Len() == 0 { return &bridgev2.ConvertedMessagePart{ Type: event.EventMessage, Content: &event.MessageEventContent{ @@ -56,12 +67,6 @@ func (mc *MessageConverter) convertUnifiedRichResponseMessage(ctx context.Contex } } extra["fi.mau.whatsapp.rich_response_id"] = rrMsg.ResponseID - var htmlBuf strings.Builder - for _, mv := range rrMsg.Sections { - for _, p := range mv.Model.GetPrimitives() { - mc.convertRichResponsePrimitive(ctx, p, &htmlBuf) - } - } return &bridgev2.ConvertedMessagePart{ Type: event.EventMessage, Content: new(format.HTMLToContent(htmlBuf.String())), @@ -69,7 +74,7 @@ func (mc *MessageConverter) convertUnifiedRichResponseMessage(ctx context.Contex } } -var mdRender = goldmark.New(format.Extensions, format.HTMLOptions, goldmark.WithExtensions(mdext.EscapeHTML)) +var mdRender = goldmark.New(format.HTMLOptions, goldmark.WithExtensions(extension.Strikethrough, extension.Table, mdext.EscapeHTML, mdext.ShortEmphasis, mdext.Math)) func (mc *MessageConverter) convertRichMarkdownText(ctx context.Context, text *richresponse.GenAIMarkdownTextUXPrimitive, buf *strings.Builder) error { for _, ent := range text.InlineEntities { @@ -113,7 +118,7 @@ func (mc *MessageConverter) convertRichResponsePrimitive(ctx context.Context, pr buf.WriteString("
")
 		}
 		for _, part := range p.CodeBlocks {
-			buf.WriteString(part.Content)
+			buf.WriteString(html.EscapeString(part.Content))
 		}
 		buf.WriteString("
") case *richresponse.GenAIMarkdownTextUXPrimitive: @@ -122,7 +127,7 @@ func (mc *MessageConverter) convertRichResponsePrimitive(ctx context.Context, pr buf.WriteString("") for _, row := range p.Rows { buf.WriteString("") - cellType := "tr" + cellType := "td" if row.IsHeader { cellType = "th" } From 4fc155c597a2407e015a5279c50de6f57c668cc9 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 28 Sep 2026 17:05:47 +0300 Subject: [PATCH 414/486] 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 dcae3bb8..c5b87b24 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/yuin/goldmark v1.8.6 go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260927171547-45cfce066cd2 + go.mau.fi/whatsmeow v0.0.0-20260928140511-35f522c88ce3 golang.org/x/image v0.46.0 golang.org/x/net v0.59.0 golang.org/x/sync v0.23.0 diff --git a/go.sum b/go.sum index 82983afe..6b66cf99 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,8 @@ go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8 h1:PxaO4u8ti7Eef1WKPAINXrcU go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8/go.mod h1:40TDo7/ekSeOjgr8KAmX31Yf4zrOF94j83WQB+u5ZPc= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260927171547-45cfce066cd2 h1:MdSiBaMjvUIFbk8Cqf90CJU0JrRYXnvGKQNoM3ciaas= -go.mau.fi/whatsmeow v0.0.0-20260927171547-45cfce066cd2/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= +go.mau.fi/whatsmeow v0.0.0-20260928140511-35f522c88ce3 h1:ix7+8nGKloIdLhGeggvKllb5N/J6hfwImBy/xzOvrgY= +go.mau.fi/whatsmeow v0.0.0-20260928140511-35f522c88ce3/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= From 2ffea1248ab9ddcdb83712d2a7e85d277d51925e Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Tue, 29 Sep 2026 19:15:59 +0000 Subject: [PATCH 415/486] client: automatically request muse key and profile if missing (#960) --- go.mod | 6 +- go.sum | 8 +-- pkg/connector/chatinfo.go | 3 + pkg/connector/client.go | 8 +++ pkg/connector/handlewhatsapp.go | 6 ++ pkg/connector/login.go | 1 + pkg/connector/muse.go | 106 +++++++++++++++++++++++++++++++- pkg/waid/dbmeta.go | 1 + 8 files changed, 131 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index c5b87b24..38c4495a 100644 --- a/go.mod +++ b/go.mod @@ -7,19 +7,20 @@ toolchain go1.27.1 tool go.mau.fi/util/cmd/maubuild require ( + github.com/google/uuid v1.6.0 github.com/lib/pq v1.12.3 github.com/rs/zerolog v1.35.1 github.com/tidwall/gjson v1.19.0 github.com/yuin/goldmark v1.8.6 go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260928140511-35f522c88ce3 + go.mau.fi/whatsmeow v0.0.0-20260929112325-8b41cfe6d9c4 golang.org/x/image v0.46.0 golang.org/x/net v0.59.0 golang.org/x/sync v0.23.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.31.1-0.20260928113010-0d8772dfa579 + maunium.net/go/mautrix v0.31.1-0.20260929190113-4aac2bbf2888 ) require ( @@ -28,7 +29,6 @@ require ( 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 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 diff --git a/go.sum b/go.sum index 6b66cf99..954ffc14 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,8 @@ go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8 h1:PxaO4u8ti7Eef1WKPAINXrcU go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8/go.mod h1:40TDo7/ekSeOjgr8KAmX31Yf4zrOF94j83WQB+u5ZPc= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260928140511-35f522c88ce3 h1:ix7+8nGKloIdLhGeggvKllb5N/J6hfwImBy/xzOvrgY= -go.mau.fi/whatsmeow v0.0.0-20260928140511-35f522c88ce3/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= +go.mau.fi/whatsmeow v0.0.0-20260929112325-8b41cfe6d9c4 h1:0PXBB/t9JjRLCyv8Dp5K1OyE+ut/6VgKZqGUsaAaPNk= +go.mau.fi/whatsmeow v0.0.0-20260929112325-8b41cfe6d9c4/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= @@ -103,5 +103,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.31.1-0.20260928113010-0d8772dfa579 h1:CfIu/q0W0z8uLR9o6C0kcFQqFTTWUi2j9+Vd9yQV1xg= -maunium.net/go/mautrix v0.31.1-0.20260928113010-0d8772dfa579/go.mod h1:t97J+6KmbmeoepWOunBgpu1xIvuBpI36ebBqIbYT8Jk= +maunium.net/go/mautrix v0.31.1-0.20260929190113-4aac2bbf2888 h1:sC0fpamjHE5Q2nqy9frxv30rHcX53xPg0FRZ3C2IVoU= +maunium.net/go/mautrix v0.31.1-0.20260929190113-4aac2bbf2888/go.mod h1:t97J+6KmbmeoepWOunBgpu1xIvuBpI36ebBqIbYT8Jk= diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 267200ce..7635e860 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -35,6 +35,9 @@ func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, switch portalJID.Server { case types.DefaultUserServer, types.HiddenUserServer, types.BotServer: wrapped = wa.wrapDMInfo(ctx, portalJID) + if portalJID == types.MuseJID { + go wa.requestMuseProfile(ctx) + } case types.BroadcastServer: if portalJID == types.StatusBroadcastJID { wrapped = wa.wrapStatusBroadcastInfo(ctx) diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 2287fead..9a54fcd9 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -127,6 +127,10 @@ type WhatsAppClient struct { appStateRecoveryLock sync.Mutex appStateFullSyncAttempted map[appstate.WAPatchName]time.Time + + wasaResyncLock sync.Mutex + museProfileLock sync.Mutex + lastMuseProfileRequest time.Time } var ( @@ -460,6 +464,10 @@ func (wa *WhatsAppClient) HandleMatrixViewingChat(ctx context.Context, msg *brid } } + if msg.Portal != nil && msg.Portal.ID == waid.MakePortalID(types.MuseJID) { + go wa.requestMuseProfile(ctx) + } + if msg.Portal == nil || msg.Portal.Metadata.(*waid.PortalMetadata).LastSync.Add(5*time.Minute).After(time.Now()) { // If we resynced this portal within the last 5 minutes, don't do it again return nil diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index f4010982..d320ad1f 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -174,6 +174,12 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { case *events.Connected: log.Debug().Msg("Connected to WhatsApp socket") wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected}) + if wa.offlineSyncWaiter.Load() == nil { + go func() { + wa.resyncWASARootSecrets(ctx) + wa.requestMuseProfile(ctx) + }() + } if len(wa.GetStore().PushName) > 0 { go func() { err := wa.updatePresence(ctx, types.PresenceUnavailable) diff --git a/pkg/connector/login.go b/pkg/connector/login.go index fa2c4792..e5f7cd48 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -402,6 +402,7 @@ func (wl *WALogin) onLoginComplete(ctx context.Context) (*bridgev2.LoginStep, er Timezone: wl.Timezone, HistorySyncPortalsNeedCreating: true, + WASAResynced: true, }, }, &bridgev2.NewLoginParams{ DeleteOnConflict: true, diff --git a/pkg/connector/muse.go b/pkg/connector/muse.go index 73c4206a..94a25d90 100644 --- a/pkg/connector/muse.go +++ b/pkg/connector/muse.go @@ -5,13 +5,24 @@ import ( "encoding/base64" "encoding/json" "fmt" + "runtime/debug" + "time" + "github.com/google/uuid" "github.com/rs/zerolog" + "go.mau.fi/util/exerrors" "go.mau.fi/whatsmeow" + "go.mau.fi/whatsmeow/appstate" + "go.mau.fi/whatsmeow/proto/waAICommon" + "go.mau.fi/whatsmeow/proto/waE2E" "go.mau.fi/whatsmeow/types" + "google.golang.org/protobuf/proto" "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/database" "maunium.net/go/mautrix/bridgev2/networkid" "maunium.net/go/mautrix/bridgev2/simplevent" + + "go.mau.fi/mautrix-whatsapp/pkg/waid" ) type museImage struct { @@ -22,6 +33,93 @@ type museImage struct { Length int `json:"file_length"` } +func (wa *WhatsAppClient) resyncWASARootSecrets(ctx context.Context) { + defer func() { + v := recover() + if v != nil { + zerolog.Ctx(ctx).Err(exerrors.RecoverToError(v)). + Bytes("stack", debug.Stack()). + Msg("Error resyncing WASA root secrets") + } + }() + wa.wasaResyncLock.Lock() + defer wa.wasaResyncLock.Unlock() + if wa.offlineSyncWaiter.Load() != nil || !wa.Client.IsConnected() { + return + } + meta := wa.UserLogin.Metadata.(*waid.UserLoginMetadata) + if meta.WASAResynced { + return + } + log := zerolog.Ctx(ctx) + log.Info().Msg("Resyncing WASA root secrets for existing login") + if err := wa.Client.FetchAppState(ctx, appstate.WAPatchRegularHigh, true, false); err != nil { + log.Err(err).Msg("Failed to resync WASA root secrets") + return + } + meta.WASAResynced = true + if err := wa.UserLogin.Save(ctx); err != nil { + meta.WASAResynced = false + log.Err(err).Msg("Failed to save WASA resync completion") + return + } + log.Info().Msg("Completed WASA root secret resync") +} + +func (wa *WhatsAppClient) requestMuseProfile(ctx context.Context) { + defer func() { + v := recover() + if v != nil { + zerolog.Ctx(ctx).Err(exerrors.RecoverToError(v)). + Bytes("stack", debug.Stack()). + Msg("Error requesting Muse profile") + } + }() + if !wa.museProfileLock.TryLock() { + return + } + defer wa.museProfileLock.Unlock() + if wa.offlineSyncWaiter.Load() != nil || !wa.Client.IsConnected() || time.Since(wa.lastMuseProfileRequest) < 5*time.Minute { + return + } + portal, err := wa.Main.Bridge.DB.Portal.GetByKey(ctx, wa.makeWAPortalKey(types.MuseJID)) + if err != nil { + zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to check Muse profile") + return + } else if portal != nil && portal.NameIsCustom && portal.Name != "" && portal.NameSet && museAvatarReady(portal) { + return + } + rootID, err := wa.GetStore().ChatSettings.GetWASARootSecretID(ctx, types.MuseJID) + if err != nil { + zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to check Muse root secret") + return + } else if rootID == "" { + return + } + wa.lastMuseProfileRequest = time.Now() + _, err = wa.Client.SendMessage(ctx, types.MuseJID, &waE2E.Message{ + ProtocolMessage: &waE2E.ProtocolMessage{ + Type: waE2E.ProtocolMessage_AI_METADATA_OPERATION.Enum(), + AiMetadataOperation: &waAICommon.AIMetadataOperation{ + HatchMetadataSync: &waAICommon.HatchMetadataSync{ + Data: []byte(`{"version":1,"type":"req","payload":{"method":"channel.bootstrap","params":{"sections":["agent.status","identity.updated","hitl.snapshot"]}}}`), + TimestampMS: proto.Int64(time.Now().UnixMilli()), + RequestID: proto.String(uuid.NewString()), + }, + }, + }, + }) + if err != nil { + zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to request Muse profile") + } +} + +func museAvatarReady(portal *database.Portal) bool { + expectedHash, err := base64.StdEncoding.DecodeString(string(portal.AvatarID)) + return err != nil || len(expectedHash) != 32 || + (portal.AvatarMXC != "" && portal.AvatarHash == [32]byte(expectedHash)) +} + func (wa *WhatsAppClient) handleMuseMetadata(ctx context.Context, data []byte) bool { var msg struct { Type string `json:"type"` @@ -43,16 +141,22 @@ func (wa *WhatsAppClient) handleMuseMetadata(ctx context.Context, data []byte) b return true } name := msg.Payload.Payload.Name + avatar := wa.museAvatar(msg.Payload.Payload.Avatar.Image) return wa.UserLogin.QueueRemoteEvent(&simplevent.ChatInfoChange{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventChatInfoChange, PortalKey: wa.makeWAPortalKey(types.MuseJID), CreatePortal: true, + PreHandleFunc: func(_ context.Context, portal *bridgev2.Portal) { + if avatar != nil && !museAvatarReady(portal.Portal) { + portal.AvatarSet = false + } + }, }, ChatInfoChange: &bridgev2.ChatInfoChange{ ChatInfo: &bridgev2.ChatInfo{ Name: &name, - Avatar: wa.museAvatar(msg.Payload.Payload.Avatar.Image), + Avatar: avatar, ExcludeChangesFromTimeline: true, }, }, diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index e2b60f73..6e018f63 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -40,6 +40,7 @@ type UserLoginMetadata struct { LoggedInAt jsontime.Unix `json:"logged_in_at,omitzero"` AppStateRecoveryAttempted map[appstate.WAPatchName]time.Time `json:"app_state_recovery_attempted,omitempty"` + WASAResynced bool `json:"wasa_resynced,omitzero"` HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitzero"` ReachoutTimelockUntil time.Time `json:"reachout_timelock_until,omitzero"` From 9f436b26dbb27f93cf1351a403d056431edbe061 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 29 Sep 2026 22:17:42 +0300 Subject: [PATCH 416/486] muse: log when doing initial profile sync --- pkg/connector/muse.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/connector/muse.go b/pkg/connector/muse.go index 94a25d90..7c62144c 100644 --- a/pkg/connector/muse.go +++ b/pkg/connector/muse.go @@ -79,7 +79,7 @@ func (wa *WhatsAppClient) requestMuseProfile(ctx context.Context) { return } defer wa.museProfileLock.Unlock() - if wa.offlineSyncWaiter.Load() != nil || !wa.Client.IsConnected() || time.Since(wa.lastMuseProfileRequest) < 5*time.Minute { + if wa.offlineSyncWaiter.Load() != nil || !wa.Client.IsConnected() || time.Since(wa.lastMuseProfileRequest) < 15*time.Minute { return } portal, err := wa.Main.Bridge.DB.Portal.GetByKey(ctx, wa.makeWAPortalKey(types.MuseJID)) @@ -96,6 +96,7 @@ func (wa *WhatsAppClient) requestMuseProfile(ctx context.Context) { } else if rootID == "" { return } + zerolog.Ctx(ctx).Info().Msg("Sending request to sync Muse profile") wa.lastMuseProfileRequest = time.Now() _, err = wa.Client.SendMessage(ctx, types.MuseJID, &waE2E.Message{ ProtocolMessage: &waE2E.ProtocolMessage{ From e1f141f4478a462e8de1b082e73d88e017f87bbc Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 30 Sep 2026 19:00:20 +0300 Subject: [PATCH 417/486] handlewhatsapp: log inner message type in edits --- pkg/connector/events.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/connector/events.go b/pkg/connector/events.go index c5cdac85..bd9f9bff 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -136,6 +136,9 @@ func (evt *WAMessageEvent) AddLogContext(c zerolog.Context) zerolog.Context { if targetMsg := evt.GetTargetMessage(); targetMsg != "" { c = c.Str("target_message_id", string(targetMsg)) } + if evt.parsedMessageType == "edit" { + c = c.Str("edit_inner_message_type", getMessageType(evt.Message.GetProtocolMessage().GetEditedMessage())) + } return evt.MessageInfoWrapper.AddLogContext(c).Str("parsed_message_type", evt.parsedMessageType) } From cafeca69796379abf80df1df9f6b6fdf97c21c25 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 30 Sep 2026 19:20:36 +0300 Subject: [PATCH 418/486] handlewhatsapp: unwrap some message types in edits --- pkg/connector/events.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/connector/events.go b/pkg/connector/events.go index bd9f9bff..bc113071 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -192,12 +192,18 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por cacheMessage = evt.GetID() } else { editedMsg = evt.Message.GetProtocolMessage().GetEditedMessage() - previouslyConvertedPart = evt.wa.Main.GetMediaEditCache(portal, targetMessage) + if editedMsg.GetLottieStickerMessage().GetMessage() != nil { + editedMsg = editedMsg.GetLottieStickerMessage().GetMessage() + } + if editedMsg.GetDocumentWithCaptionMessage().GetMessage() != nil { + editedMsg = editedMsg.GetDocumentWithCaptionMessage().GetMessage() + } meta := existing[0].Metadata.(*waid.MessageMetadata) if slices.Contains(meta.Edits, evt.Info.ID) { return nil, fmt.Errorf("%w: edit already handled", bridgev2.ErrIgnoringRemoteEvent) } meta.Edits = append(meta.Edits, evt.Info.ID) + previouslyConvertedPart = evt.wa.Main.GetMediaEditCache(portal, targetMessage) } ctx = context.WithValue(ctx, msgconv.ContextKeyEditTargetID, evt.Message.GetProtocolMessage().GetKey().GetID()) From c84643e70ad92a62e4e7f7096c5a56ec7fdfa5f8 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 30 Sep 2026 19:20:58 +0300 Subject: [PATCH 419/486] handlewhatsapp: try to fetch message from server for caption edit --- pkg/connector/events.go | 24 ++++++++++++++++++++++++ pkg/connector/wamsgtype.go | 10 ++++++++++ 2 files changed, 34 insertions(+) diff --git a/pkg/connector/events.go b/pkg/connector/events.go index bc113071..bd328c59 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -204,6 +204,30 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por } meta.Edits = append(meta.Edits, evt.Info.ID) previouslyConvertedPart = evt.wa.Main.GetMediaEditCache(portal, targetMessage) + if previouslyConvertedPart == nil && needsPreviousEditPart(editedMsg) { + editedEvent, err := evt.wa.Main.Bridge.Bot.GetEvent(ctx, portal.MXID, existing[0].MXID) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to fetch existing event for edit") + } else if editedEvent != nil && editedEvent.Content.AsMessage().MsgType != "" { + zerolog.Ctx(ctx).Debug().Msg("Fetched existing edit from server for caption edit") + copiedExtra := make(map[string]any) + for _, key := range []string{"info", msgconv.FailedMediaField} { + val, ok := editedEvent.Content.Raw[key] + if ok { + copiedExtra[key] = val + } + } + previouslyConvertedPart = &bridgev2.ConvertedMessagePart{ + ID: existing[0].PartID, + Type: editedEvent.Type, + Content: editedEvent.Content.AsMessage(), + Extra: copiedExtra, + DBMetadata: meta, + } + } else { + zerolog.Ctx(ctx).Debug().Msg("Didn't find existing edit on server for caption edit") + } + } } ctx = context.WithValue(ctx, msgconv.ContextKeyEditTargetID, evt.Message.GetProtocolMessage().GetKey().GetID()) diff --git a/pkg/connector/wamsgtype.go b/pkg/connector/wamsgtype.go index 0a29b829..efdf679d 100644 --- a/pkg/connector/wamsgtype.go +++ b/pkg/connector/wamsgtype.go @@ -159,3 +159,13 @@ func getMessageType(waMsg *waE2E.Message) string { return "unknown" } } + +func needsPreviousEditPart(waMsg *waE2E.Message) bool { + switch { + case waMsg.ImageMessage != nil, waMsg.StickerMessage != nil, waMsg.VideoMessage != nil, + waMsg.PtvMessage != nil, waMsg.AudioMessage != nil, waMsg.DocumentMessage != nil: + return true + default: + return false + } +} From a0325e76df3341136ab344dd3bd8401710117e42 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Wed, 30 Sep 2026 19:34:14 +0300 Subject: [PATCH 420/486] msgconv/from-whatsapp: don't create direct media url for empty paths --- pkg/msgconv/wa-media.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index 99648009..20a1a142 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -100,13 +100,17 @@ func (mc *MessageConverter) convertMediaMessage( if err != nil { panic(err) } - part = &bridgev2.ConvertedMessagePart{ - Type: preparedMedia.Type, - Content: preparedMedia.MessageEventContent, - Extra: preparedMedia.Extra, - DBMetadata: &waid.MessageMetadata{ - DirectMediaMeta: directMediaMeta, - }, + if mediaKeys.DirectPath == "" { + part = mc.makeMediaFailure(ctx, preparedMedia, mediaKeys, whatsmeow.ErrNoURLPresent) + } else { + part = &bridgev2.ConvertedMessagePart{ + Type: preparedMedia.Type, + Content: preparedMedia.MessageEventContent, + Extra: preparedMedia.Extra, + DBMetadata: &waid.MessageMetadata{ + DirectMediaMeta: directMediaMeta, + }, + } } } else if err := mc.reuploadWhatsAppAttachment(ctx, msg, preparedMedia); err != nil { part = mc.makeMediaFailure(ctx, preparedMedia, mediaKeys, err) From d97b19124562944386bc1776fafaef98ac603b91 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Tue, 6 Oct 2026 10:38:35 +0100 Subject: [PATCH 421/486] backfill: prevent panic when converting history sync messages (#955) --- pkg/connector/backfill.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 50a0d4c2..85f4499b 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -628,7 +628,8 @@ func (wa *WhatsAppClient) convertHistorySyncMessages( messages []*waWeb.WebMessageInfo, explodeOnError bool, ) (*bridgev2.FetchMessagesResponse, error) { - if wa.Client == nil { + cli := wa.Client + if cli == nil { return nil, bridgev2.ErrNotLoggedIn } oldestTS := messages[len(messages)-1].GetMessageTimestamp() @@ -637,7 +638,7 @@ func (wa *WhatsAppClient) convertHistorySyncMessages( var mediaRequests []*wadb.MediaRequest dups := make(exmaps.Set[networkid.MessageID]) for i, msg := range messages { - evt, err := wa.Client.ParseWebMessage(portalJID, msg) + evt, err := cli.ParseWebMessage(portalJID, msg) if err != nil { if explodeOnError { // This should never happen because the info is already parsed once before being stored in the database @@ -664,7 +665,7 @@ func (wa *WhatsAppClient) convertHistorySyncMessages( } isViewOnce := evt.IsViewOnce || evt.IsViewOnceV2 || evt.IsViewOnceV2Extension converted, mediaReq := wa.convertHistorySyncMessage( - ctx, portal, &evt.Info, evt.Message, evt.RawMessage, isViewOnce, msg.Reactions, + ctx, cli, 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 @@ -805,7 +806,7 @@ func (wa *WhatsAppClient) handleOnDemandHistorySync(ctx context.Context, blob *w } func (wa *WhatsAppClient) convertHistorySyncMessage( - ctx context.Context, portal *bridgev2.Portal, info *types.MessageInfo, msg, rawMsg *waE2E.Message, isViewOnce bool, reactions []*waWeb.Reaction, + ctx context.Context, cli *whatsmeow.Client, portal *bridgev2.Portal, info *types.MessageInfo, msg, rawMsg *waE2E.Message, isViewOnce bool, reactions []*waWeb.Reaction, ) (*bridgev2.BackfillMessage, *wadb.MediaRequest) { // New messages turn these into edits, but in backfill we only have the last version, // so no need to do the edit thing. Instead, just unwrap the message. @@ -816,7 +817,7 @@ func (wa *WhatsAppClient) convertHistorySyncMessage( 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, isViewOnce, true, nil), + ConvertedMessage: wa.Main.MsgConv.ToMatrix(ctx, portal, cli, intent, msg, rawMsg, info, isViewOnce, true, nil), Sender: wa.makeEventSender(ctx, pickLID(info.Sender, info.SenderAlt)), ID: msgID, TxnID: networkid.TransactionID(msgID), From 06b42e140cd48284b3220c4e1b5796a6b9d1ae69 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 6 Oct 2026 12:52:01 +0300 Subject: [PATCH 422/486] muse: mark resync as done on mismatching lthash --- pkg/connector/muse.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pkg/connector/muse.go b/pkg/connector/muse.go index 7c62144c..ee6b04d9 100644 --- a/pkg/connector/muse.go +++ b/pkg/connector/muse.go @@ -4,6 +4,7 @@ import ( "context" "encoding/base64" "encoding/json" + "errors" "fmt" "runtime/debug" "time" @@ -53,9 +54,15 @@ func (wa *WhatsAppClient) resyncWASARootSecrets(ctx context.Context) { } log := zerolog.Ctx(ctx) log.Info().Msg("Resyncing WASA root secrets for existing login") + success := true if err := wa.Client.FetchAppState(ctx, appstate.WAPatchRegularHigh, true, false); err != nil { log.Err(err).Msg("Failed to resync WASA root secrets") - return + // Mark the sync as completed on mismatching LTHash errors, because those can get permanently stuck + // (retrying won't help). In case it's not stuck, a single call should be enough to request a resync. + if !errors.Is(err, appstate.ErrMismatchingLTHash) || ctx.Err() != nil { + return + } + success = false } meta.WASAResynced = true if err := wa.UserLogin.Save(ctx); err != nil { @@ -63,7 +70,11 @@ func (wa *WhatsAppClient) resyncWASARootSecrets(ctx context.Context) { log.Err(err).Msg("Failed to save WASA resync completion") return } - log.Info().Msg("Completed WASA root secret resync") + if success { + log.Info().Msg("Completed WASA root secret resync") + } else { + log.Info().Msg("Marked WASA root resync as done despite errors") + } } func (wa *WhatsAppClient) requestMuseProfile(ctx context.Context) { From a34259020e60cf6e16cf5002e5d885ee03a3dd15 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 6 Oct 2026 15:22:21 +0300 Subject: [PATCH 423/486] handle*: add support for blocking DMs --- go.mod | 4 +- go.sum | 8 +-- pkg/connector/capabilities.go | 15 +++++- pkg/connector/chatinfo.go | 21 ++++++++ pkg/connector/client.go | 1 + pkg/connector/handlematrix.go | 48 +++++++++++++++++ pkg/connector/handlewhatsapp.go | 92 +++++++++++++++++++++++++++++++++ pkg/waid/dbmeta.go | 2 + 8 files changed, 183 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 38c4495a..b6398d42 100644 --- a/go.mod +++ b/go.mod @@ -14,13 +14,13 @@ require ( github.com/yuin/goldmark v1.8.6 go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20260929112325-8b41cfe6d9c4 + go.mau.fi/whatsmeow v0.0.0-20261006124319-9399289b022b golang.org/x/image v0.46.0 golang.org/x/net v0.59.0 golang.org/x/sync v0.23.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.31.1-0.20260929190113-4aac2bbf2888 + maunium.net/go/mautrix v0.31.1-0.20261005174338-57e1ea5dd74d ) require ( diff --git a/go.sum b/go.sum index 954ffc14..5854082c 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,8 @@ go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8 h1:PxaO4u8ti7Eef1WKPAINXrcU go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8/go.mod h1:40TDo7/ekSeOjgr8KAmX31Yf4zrOF94j83WQB+u5ZPc= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20260929112325-8b41cfe6d9c4 h1:0PXBB/t9JjRLCyv8Dp5K1OyE+ut/6VgKZqGUsaAaPNk= -go.mau.fi/whatsmeow v0.0.0-20260929112325-8b41cfe6d9c4/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= +go.mau.fi/whatsmeow v0.0.0-20261006124319-9399289b022b h1:sxHC8T2zT69QJmFx6zuB3KuDu+49KFf28bszKtv8vXg= +go.mau.fi/whatsmeow v0.0.0-20261006124319-9399289b022b/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= @@ -103,5 +103,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.31.1-0.20260929190113-4aac2bbf2888 h1:sC0fpamjHE5Q2nqy9frxv30rHcX53xPg0FRZ3C2IVoU= -maunium.net/go/mautrix v0.31.1-0.20260929190113-4aac2bbf2888/go.mod h1:t97J+6KmbmeoepWOunBgpu1xIvuBpI36ebBqIbYT8Jk= +maunium.net/go/mautrix v0.31.1-0.20261005174338-57e1ea5dd74d h1:sOdF5AF9MiU2mudfMBgQC79FJrhBBDbUkxQCi0T2uHI= +maunium.net/go/mautrix v0.31.1-0.20261005174338-57e1ea5dd74d/go.mod h1:t97J+6KmbmeoepWOunBgpu1xIvuBpI36ebBqIbYT8Jk= diff --git a/pkg/connector/capabilities.go b/pkg/connector/capabilities.go index 152e7ad6..b991893b 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, 9 + return 1, 10 } const WAMaxFileSize = 2000 * 1024 * 1024 @@ -67,7 +67,7 @@ func supportedIfFFmpeg() event.CapabilitySupportLevel { } func capID() string { - base := "fi.mau.whatsapp.capabilities.2026_07_22" + base := "fi.mau.whatsapp.capabilities.2026_10_01" if ffmpeg.Supported() { return base + "+ffmpeg" } @@ -198,6 +198,7 @@ var whatsappCaps = &event.RoomFeatures{ var whatsappDMCaps *event.RoomFeatures var whatsappCAGCaps *event.RoomFeatures +var whatsappNoteToSelfCaps *event.RoomFeatures func init() { whatsappDMCaps = ptr.Clone(whatsappCaps) @@ -205,7 +206,13 @@ func init() { whatsappDMCaps.State = event.StateFeatureMap{ event.StateBeeperDisappearingTimer.Type: {Level: event.CapLevelFullySupported}, } + whatsappDMCaps.BlockUser = true whatsappDMCaps.MemberActions = nil + + whatsappNoteToSelfCaps = ptr.Clone(whatsappDMCaps) + whatsappNoteToSelfCaps.ID = capID() + "+self" + whatsappNoteToSelfCaps.BlockUser = false + whatsappCAGCaps = ptr.Clone(whatsappCaps) whatsappCAGCaps.ID = capID() + "+cag" whatsappCAGCaps.Reply = event.CapLevelUnsupported @@ -216,6 +223,10 @@ func (wa *WhatsAppClient) GetCapabilities(ctx context.Context, portal *bridgev2. if portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup { return whatsappCAGCaps } else if portal.RoomType == database.RoomTypeDM { + jid, err := waid.ParsePortalID(portal.ID) + if err == nil && wa.IsOwnJID(jid) { + return whatsappNoteToSelfCaps + } return whatsappDMCaps } return whatsappCaps diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 7635e860..3984b5f9 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -185,6 +185,11 @@ func (wa *WhatsAppClient) wrapDMInfo(ctx context.Context, jid types.JID) *bridge }, ExcludeChangesFromTimeline: true, } + var err error + info.UserBlocked, err = wa.isUserBlocked(ctx, jid) + if err != nil { + zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to get blocked state") + } if jid.Server == types.BotServer { info.Topic = ptr.Ptr(BotChatTopic) } @@ -571,3 +576,19 @@ func (wa *WhatsAppClient) wrapNewsletterInfo(ctx context.Context, info *types.Ne Type: ptr.Ptr(database.RoomTypeDefault), } } + +func (wa *WhatsAppClient) isUserBlocked(ctx context.Context, jid types.JID) (*bool, error) { + wa.blocklistLock.Lock() + defer wa.blocklistLock.Unlock() + meta := wa.UserLogin.Metadata.(*waid.UserLoginMetadata) + blocklist := meta.Blocklist + if blocklist == nil { + runHandlers := wa.resyncBlocklistUnlocked(ctx) + go runHandlers() + blocklist = meta.Blocklist + } + if blocklist == nil { + return nil, nil + } + return new(blocklist.IsBlocked(jid)), nil +} diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 9a54fcd9..61fceecf 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -122,6 +122,7 @@ type WhatsAppClient struct { isNewLogin bool pushNamesSynced *exsync.Event lastPresence types.Presence + blocklistLock sync.Mutex disableNewsletter bool diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index c55f523c..a646ac9f 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -1,3 +1,19 @@ +// 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 ( @@ -19,6 +35,7 @@ import ( "go.mau.fi/whatsmeow/proto/waCommon" "go.mau.fi/whatsmeow/proto/waE2E" "go.mau.fi/whatsmeow/types" + "go.mau.fi/whatsmeow/types/events" "golang.org/x/image/draw" "google.golang.org/protobuf/proto" "maunium.net/go/mautrix/bridgev2" @@ -46,6 +63,7 @@ var ( _ bridgev2.TagHandlingNetworkAPI = (*WhatsAppClient)(nil) _ bridgev2.MarkedUnreadHandlingNetworkAPI = (*WhatsAppClient)(nil) _ bridgev2.DeleteChatHandlingNetworkAPI = (*WhatsAppClient)(nil) + _ bridgev2.UserBlockingNetworkAPI = (*WhatsAppClient)(nil) ) func (wa *WhatsAppClient) HandleMatrixPollStart(ctx context.Context, msg *bridgev2.MatrixPollStart) (result *bridgev2.MatrixMessageResponse, retErr error) { @@ -691,3 +709,33 @@ func (wa *WhatsAppClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridg defer wa.mcTrack(msg, time.Now(), &retErr) return wa.Client.SendAppState(ctx, appstate.BuildDeleteChat(chatJID, lastTS, lastKey, true)) } + +func (wa *WhatsAppClient) HandleMatrixBlockUser(ctx context.Context, msg *bridgev2.MatrixBlockUser) error { + if wa.Client == nil { + return bridgev2.ErrNotLoggedIn + } + jid, err := waid.ParsePortalID(msg.Portal.ID) + if err != nil { + return err + } + if msg.Content.ReportSpam { + return fmt.Errorf("spam reporting is not supported") + } + action := events.BlocklistChangeActionUnblock + if msg.Content.Block { + action = events.BlocklistChangeActionBlock + } + blocklist, err := wa.Client.UpdateBlocklist(ctx, jid, action, "") + if err == nil { + wa.blocklistLock.Lock() + evts := wa.saveBlocklistUnlocked(ctx, blocklist) + wa.blocklistLock.Unlock() + for _, evt := range evts { + if evt.PortalKey == msg.Portal.PortalKey { + continue + } + wa.UserLogin.QueueRemoteEvent(evt) + } + } + return err +} diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index d320ad1f..117cfad9 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -82,6 +82,8 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { success = true switch evt := rawEvt.(type) { + case *events.Blocklist: + success = wa.resyncBlocklist(ctx)() case *events.Message: success = wa.handleWAMessage(ctx, evt) case *events.Receipt: @@ -1016,3 +1018,93 @@ func (wa *WhatsAppClient) handleWAAppStateSyncError(ctx context.Context, evt *ev } }() } + +func (wa *WhatsAppClient) resyncBlocklist(ctx context.Context) func() bool { + wa.blocklistLock.Lock() + defer wa.blocklistLock.Unlock() + return wa.resyncBlocklistUnlocked(ctx) +} + +func noopUpdates() bool { + return true +} + +func (wa *WhatsAppClient) resyncBlocklistUnlocked(ctx context.Context) func() bool { + meta := wa.UserLogin.Metadata.(*waid.UserLoginMetadata) + dhash := ptr.Val(meta.Blocklist).DHash + newBlocklist, err := wa.Client.GetBlocklist(ctx, dhash) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to get new blocklist") + return noopUpdates + } else if newBlocklist == nil { + zerolog.Ctx(ctx).Debug().Msg("Blocklist didn't change") + return noopUpdates + } + updateEvents := wa.saveBlocklistUnlocked(ctx, newBlocklist) + return func() bool { + for _, evt := range updateEvents { + res := wa.UserLogin.QueueRemoteEvent(evt) + if !res.Success { + return false + } + } + return true + } +} + +func (wa *WhatsAppClient) saveBlocklistUnlocked(ctx context.Context, newBlocklist *types.Blocklist) []*simplevent.ChatInfoChange { + if newBlocklist.AddressingMode != types.AddressingModeLID { + zerolog.Ctx(ctx).Warn().Msg("Non-LID addressing mode in blocklist") + } + meta := wa.UserLogin.Metadata.(*waid.UserLoginMetadata) + oldBlocklist := ptr.Val(meta.Blocklist) + removed, added := exslices.Diff(oldBlocklist.LIDs(), newBlocklist.LIDs()) + var updateEvents []*simplevent.ChatInfoChange + for _, jid := range removed { + if evt := wa.makeBlockUpdateEvent(ctx, jid, false); evt != nil { + updateEvents = append(updateEvents, evt) + } + } + for _, jid := range added { + if evt := wa.makeBlockUpdateEvent(ctx, jid, true); evt != nil { + updateEvents = append(updateEvents, evt) + } + } + meta.Blocklist = newBlocklist + err := wa.UserLogin.Save(ctx) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to save blocklist to login metadata") + } + return updateEvents +} + +func (wa *WhatsAppClient) makeBlockUpdateEvent(ctx context.Context, jid types.JID, blocked bool) *simplevent.ChatInfoChange { + if jid.Server == types.DefaultUserServer { + var err error + jid, err = wa.GetStore().LIDs.GetLIDForPN(ctx, jid) + if err != nil { + zerolog.Ctx(ctx).Err(err).Stringer("pn", jid).Msg("Failed to get LID for blocklist update") + return nil + } else if jid.IsEmpty() { + zerolog.Ctx(ctx).Debug().Stringer("pn", jid).Msg("No LID for blocklist update") + return nil + } + } else if jid.Server != types.HiddenUserServer && jid.Server != types.BotServer { + zerolog.Ctx(ctx).Debug().Stringer("jid", jid).Msg("Unexpected JID server in blocklist") + return nil + } + return &simplevent.ChatInfoChange{ + EventMeta: simplevent.EventMeta{ + Type: bridgev2.RemoteEventChatInfoChange, + PortalKey: wa.makeWAPortalKey(jid), + LogContext: func(c zerolog.Context) zerolog.Context { + return c.Str("wa_event_type", "blocklist update") + }, + }, + ChatInfoChange: &bridgev2.ChatInfoChange{ + ChatInfo: &bridgev2.ChatInfo{ + UserBlocked: &blocked, + }, + }, + } +} diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index 6e018f63..15c0a422 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -45,6 +45,8 @@ type UserLoginMetadata struct { HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitzero"` ReachoutTimelockUntil time.Time `json:"reachout_timelock_until,omitzero"` + Blocklist *types.Blocklist `json:"blocklist,omitempty"` + MData json.RawMessage `json:"mdata,omitempty"` } From cb7d14e9794300944722ac22c58c200692321e42 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 6 Oct 2026 16:01:26 +0300 Subject: [PATCH 424/486] handlewhatsapp: fetch blocked DMs from db for dispatching updates --- pkg/connector/handlewhatsapp.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 117cfad9..2b30fdde 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -1058,7 +1058,24 @@ func (wa *WhatsAppClient) saveBlocklistUnlocked(ctx context.Context, newBlocklis } meta := wa.UserLogin.Metadata.(*waid.UserLoginMetadata) oldBlocklist := ptr.Val(meta.Blocklist) - removed, added := exslices.Diff(oldBlocklist.LIDs(), newBlocklist.LIDs()) + currentlyBlockedDMs, err := wa.Main.Bridge.DB.Portal.GetAllBlockedDMsOf(ctx, wa.UserLogin.ID) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to get currently blocked DMs") + } + // In addition to diffing the cached blocklist, fetch all blocked DM portals from the database + // to ensure any missed updates are retried. + oldBlockedLIDs := exslices.CastFuncFilter[types.JID](currentlyBlockedDMs, func(key networkid.PortalKey) (types.JID, bool) { + jid, _ := waid.ParsePortalID(key.ID) + return jid, !jid.IsEmpty() + }) + removed, added := exslices.Diff(append(oldBlocklist.LIDs(), oldBlockedLIDs...), newBlocklist.LIDs()) + zerolog.Ctx(ctx).Debug(). + Int("old_count", len(oldBlocklist.Items)). + Int("db_count", len(currentlyBlockedDMs)). + Int("new_count", len(newBlocklist.Items)). + Any("removed", removed). + Any("added", added). + Msg("Syncing blocklist") var updateEvents []*simplevent.ChatInfoChange for _, jid := range removed { if evt := wa.makeBlockUpdateEvent(ctx, jid, false); evt != nil { @@ -1071,7 +1088,7 @@ func (wa *WhatsAppClient) saveBlocklistUnlocked(ctx context.Context, newBlocklis } } meta.Blocklist = newBlocklist - err := wa.UserLogin.Save(ctx) + err = wa.UserLogin.Save(ctx) if err != nil { zerolog.Ctx(ctx).Err(err).Msg("Failed to save blocklist to login metadata") } From 9959878fe0c3071bb6729d54befb0b843c4b71d3 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 6 Oct 2026 16:05:15 +0300 Subject: [PATCH 425/486] handlematrix: dispatch extra block updates in the background --- pkg/connector/handlematrix.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index a646ac9f..ee3d70a6 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -728,14 +728,16 @@ func (wa *WhatsAppClient) HandleMatrixBlockUser(ctx context.Context, msg *bridge blocklist, err := wa.Client.UpdateBlocklist(ctx, jid, action, "") if err == nil { wa.blocklistLock.Lock() + defer wa.blocklistLock.Unlock() evts := wa.saveBlocklistUnlocked(ctx, blocklist) - wa.blocklistLock.Unlock() - for _, evt := range evts { - if evt.PortalKey == msg.Portal.PortalKey { - continue + go func() { + for _, evt := range evts { + if evt.PortalKey == msg.Portal.PortalKey { + continue + } + wa.UserLogin.QueueRemoteEvent(evt) } - wa.UserLogin.QueueRemoteEvent(evt) - } + }() } return err } From 5f904a6203db8f1fc45d799514faf5298bb34656 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Tue, 6 Oct 2026 13:52:22 +0000 Subject: [PATCH 426/486] msgconv/from-matrix: try to fill outgoing reply quotes (#963) --- pkg/msgconv/from-matrix.go | 36 ++++++++++++++++++++++++++++++++++-- pkg/msgconv/matrixpoll.go | 2 +- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index b3ddcf90..6f1d5153 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -53,6 +53,7 @@ import ( func (mc *MessageConverter) generateContextInfo( ctx context.Context, replyTo *database.Message, + relatesTo *event.RelatesTo, portal *bridgev2.Portal, perMessageTimer *event.BeeperDisappearingTimer, roomMention bool, @@ -63,7 +64,7 @@ func (mc *MessageConverter) generateContextInfo( if err == nil { contextInfo.StanzaID = proto.String(msgID.ID) contextInfo.Participant = proto.String(msgID.Sender.String()) - contextInfo.QuotedMessage = &waE2E.Message{Conversation: proto.String("")} + contextInfo.QuotedMessage = mc.getQuotedMessage(ctx, replyTo, relatesTo, portal) contextInfo.QuotedType = waE2E.ContextInfo_EXPLICIT.Enum() } else { zerolog.Ctx(ctx).Warn().Err(err). @@ -91,6 +92,37 @@ func (mc *MessageConverter) generateContextInfo( return contextInfo } +func (mc *MessageConverter) getQuotedMessage(ctx context.Context, replyTo *database.Message, relatesTo *event.RelatesTo, portal *bridgev2.Portal) *waE2E.Message { + log := zerolog.Ctx(ctx).With().Stringer("reply_to_event_id", replyTo.MXID).Logger() + if relatesTo.GetReplyTo() == replyTo.MXID && len(relatesTo.InReplyTo.BeeperQuote) > 0 { + var content event.MessageEventContent + if err := json.Unmarshal(relatesTo.InReplyTo.BeeperQuote, &content); err != nil { + log.Warn().Err(err).Msg("Failed to parse quoted event content") + } else { + return mc.convertQuote(ctx, content) + } + } + evt, err := mc.Bridge.Bot.GetEvent(ctx, portal.MXID, replyTo.MXID) + if err != nil { + log.Debug().Err(err).Msg("Failed to fetch quoted event") + } else if evt == nil { + log.Debug().Msg("Quoted event not found") + } else if evt.RoomID != "" && evt.RoomID != portal.MXID { + log.Warn().Msg("Quoted event is in a different room") + } else if evt.Unsigned.RedactedBecause == nil { + return mc.convertQuote(ctx, *evt.Content.AsMessage()) + } + return &waE2E.Message{Conversation: new("")} +} + +func (mc *MessageConverter) convertQuote(ctx context.Context, content event.MessageEventContent) *waE2E.Message { + content.Mentions = &event.Mentions{} + content.RemoveReplyFallback() + content.RemovePerMessageProfileFallback() + text, _ := mc.parseText(ctx, &content) + return &waE2E.Message{Conversation: &text} +} + func (mc *MessageConverter) ToWhatsApp( ctx context.Context, client *whatsmeow.Client, @@ -107,7 +139,7 @@ func (mc *MessageConverter) ToWhatsApp( } message := &waE2E.Message{} - contextInfo := mc.generateContextInfo(ctx, replyTo, portal, content.BeeperDisappearingTimer, content.Mentions != nil && content.Mentions.Room) + contextInfo := mc.generateContextInfo(ctx, replyTo, content.RelatesTo, portal, content.BeeperDisappearingTimer, content.Mentions != nil && content.Mentions.Room) switch content.MsgType { case event.MsgText, event.MsgNotice, event.MsgEmote: diff --git a/pkg/msgconv/matrixpoll.go b/pkg/msgconv/matrixpoll.go index 5d5d0145..fab83303 100644 --- a/pkg/msgconv/matrixpoll.go +++ b/pkg/msgconv/matrixpoll.go @@ -71,7 +71,7 @@ func (mc *MessageConverter) PollStartToWhatsApp( if maxAnswers >= len(content.PollStart.Answers) || maxAnswers < 0 { maxAnswers = 0 } - contextInfo := mc.generateContextInfo(ctx, replyTo, portal, nil, content.Mentions != nil && content.Mentions.Room) + contextInfo := mc.generateContextInfo(ctx, replyTo, content.RelatesTo, portal, nil, content.Mentions != nil && content.Mentions.Room) var question string question, contextInfo.MentionedJID = mc.msc1767ToWhatsApp(ctx, content.PollStart.Question, content.Mentions) if len(question) == 0 { From 977389392c7839330a4a1cfe49a74045a38d6f27 Mon Sep 17 00:00:00 2001 From: Matt Voska <3444419+voska@users.noreply.github.com> Date: Tue, 6 Oct 2026 15:10:40 -0300 Subject: [PATCH 427/486] msgconv/from-whatsapp: add support for contact array messages (#961) --- pkg/msgconv/wa-contact.go | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/pkg/msgconv/wa-contact.go b/pkg/msgconv/wa-contact.go index 7ca3f8e5..38076939 100644 --- a/pkg/msgconv/wa-contact.go +++ b/pkg/msgconv/wa-contact.go @@ -19,8 +19,10 @@ package msgconv import ( "context" "fmt" + "strings" "github.com/rs/zerolog" + "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow/proto/waE2E" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/event" @@ -65,11 +67,17 @@ func (mc *MessageConverter) convertContactMessage(ctx context.Context, msg *waE2 } func (mc *MessageConverter) convertContactsArrayMessage(ctx context.Context, msg *waE2E.ContactsArrayMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { - return &bridgev2.ConvertedMessagePart{ - Type: event.EventMessage, - Content: &event.MessageEventContent{ - MsgType: event.MsgNotice, - Body: "Contact array messages are not yet supported", - }, - }, msg.GetContextInfo() + name := msg.GetDisplayName() + if len(name) == 0 { + name = fmt.Sprintf("%d contacts", len(msg.GetContacts())) + } + vcards := make([]string, len(msg.GetContacts())) + for i, contact := range msg.GetContacts() { + vcards[i] = strings.TrimSpace(contact.GetVcard()) + } + part, _ := mc.convertContactMessage(ctx, &waE2E.ContactMessage{ + DisplayName: &name, + Vcard: ptr.Ptr(strings.Join(vcards, "\n")), + }) + return part, msg.GetContextInfo() } From aa86329e3717541b5db8ddd9e434019c8ce71070 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Wed, 7 Oct 2026 14:00:27 +0000 Subject: [PATCH 428/486] msgconv/from-matrix: use any available H.264 encoder for video conversion (#968) --- pkg/msgconv/from-matrix.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 6f1d5153..833813f7 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -617,7 +617,7 @@ func (mc *MessageConverter) reuploadFileToWhatsApp( sourceFormat = "mov" } data, err = ffmpeg.ConvertBytes(ctx, data, ".mp4", []string{"-f", sourceFormat}, []string{ - "-pix_fmt", "yuv420p", "-c:v", "libx264", + "-pix_fmt", "yuv420p", "-c:v", "h264", "-b:v", "2M", "-filter:v", "crop='floor(in_w/2)*2:floor(in_h/2)*2'", }, mime) if err != nil { @@ -626,7 +626,7 @@ func (mc *MessageConverter) reuploadFileToWhatsApp( mime = "video/mp4" case "image/gif": data, err = ffmpeg.ConvertBytes(ctx, data, ".mp4", []string{"-f", "gif"}, []string{ - "-pix_fmt", "yuv420p", "-c:v", "libx264", "-movflags", "+faststart", + "-pix_fmt", "yuv420p", "-c:v", "h264", "-b:v", "2M", "-movflags", "+faststart", "-filter:v", "crop='floor(in_w/2)*2:floor(in_h/2)*2'", }, mime) if err != nil { From be8d93edc441b436db510f646cba2184cd0a7a3e Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Fri, 9 Oct 2026 15:23:35 +0100 Subject: [PATCH 429/486] msgconv/from-whatsapp: flag message history share notices as action messages (#971) --- pkg/msgconv/wa-misc.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index cda33c50..275b1326 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -199,8 +199,9 @@ func (mc *MessageConverter) convertMessageHistoryNotice(ctx context.Context, inf return &bridgev2.ConvertedMessagePart{ Type: event.EventMessage, Content: &event.MessageEventContent{ - MsgType: event.MsgNotice, - Body: body, + MsgType: event.MsgNotice, + Body: body, + BeeperActionMessage: &event.BeeperActionMessage{Type: "message_history_notice"}, }, }, msg.GetContextInfo() } From 45d51e3905ecd28dd6cb90e6ca0bd47512fc201b Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Sat, 10 Oct 2026 10:47:11 +0000 Subject: [PATCH 430/486] login: fetch own avatar after new logins (#970) --- pkg/connector/login.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/connector/login.go b/pkg/connector/login.go index e5f7cd48..325bc550 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -13,6 +13,7 @@ import ( "github.com/rs/zerolog" "go.mau.fi/util/exsync" "go.mau.fi/util/jsontime" + "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/types" "go.mau.fi/whatsmeow/types/events" @@ -418,6 +419,7 @@ func (wl *WALogin) onLoginComplete(ctx context.Context) (*bridgev2.LoginStep, er if err != nil { wl.Log.Warn().Err(err).Msg("Prekey upload wait failed") } + go c.syncGhost(c.JID.ToNonAD(), "login", ptr.Ptr("")) } return &bridgev2.LoginStep{ From 1fc894948c033c300529bd96a282a9e7159ef543 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Mon, 12 Oct 2026 12:34:18 +0000 Subject: [PATCH 431/486] handlewhatsapp: fix target message ID for delete for me in DMs (#975) --- pkg/connector/events.go | 14 +------------- pkg/connector/handlewhatsapp.go | 2 +- pkg/connector/id.go | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/pkg/connector/events.go b/pkg/connector/events.go index bd328c59..743492d1 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -469,19 +469,7 @@ func (evt *WAMediaRetry) AddLogContext(c zerolog.Context) zerolog.Context { } func (evt *WAMediaRetry) getRealSender() types.JID { - sender := evt.SenderID - 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 + return evt.wa.getRealSender(evt.ChatID, evt.SenderID, evt.FromMe) } func (evt *WAMediaRetry) GetSender() bridgev2.EventSender { diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 2b30fdde..74606abe 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -721,7 +721,7 @@ 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) + senderJID := wa.getRealSender(chatJID, wa.maybeConvertJIDToLID(ctx, evt.SenderJID), evt.IsFromMe) return wa.UserLogin.QueueRemoteEvent(&simplevent.MessageRemove{ EventMeta: simplevent.EventMeta{ Type: bridgev2.RemoteEventMessageRemove, diff --git a/pkg/connector/id.go b/pkg/connector/id.go index c0aa06b0..43e949a6 100644 --- a/pkg/connector/id.go +++ b/pkg/connector/id.go @@ -82,3 +82,18 @@ func (wa *WhatsAppClient) maybeConvertJIDToLID(ctx context.Context, jid types.JI } return jid } + +func (wa *WhatsAppClient) getRealSender(chat, sender types.JID, fromMe bool) types.JID { + if sender.IsEmpty() { + if fromMe { + if chat.Server == types.HiddenUserServer { + sender = wa.GetLID().ToNonAD() + } else { + sender = wa.JID.ToNonAD() + } + } else if chat.Server == types.DefaultUserServer || chat.Server == types.HiddenUserServer || chat.Server == types.BotServer { + sender = chat.ToNonAD() + } + } + return sender +} From 7fb697fb89b45a42e1e5ee7a668bc0ee6a24341a Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Tue, 13 Oct 2026 11:22:06 +0000 Subject: [PATCH 432/486] handlematrix: unfollow channels on leave and delete (#977) --- pkg/connector/handlematrix.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index ee3d70a6..933c43a0 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -439,6 +439,12 @@ func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridg default: return nil, nil } + } else if portalJID.Server == types.NewsletterServer { + if msg.Type != bridgev2.Leave { + return nil, fmt.Errorf("cannot change channel members") + } + defer wa.mcTrack(msg, time.Now(), &retErr) + return nil, wa.Client.UnfollowNewsletter(ctx, portalJID) } changes := make([]types.JID, 1) @@ -690,7 +696,10 @@ func (wa *WhatsAppClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridg if err != nil { return err } - if chatJID.Server == types.GroupServer { + if chatJID.Server == types.NewsletterServer { + defer wa.mcTrack(msg, time.Now(), &retErr) + return wa.Client.UnfollowNewsletter(ctx, chatJID) + } else if chatJID.Server == types.GroupServer { memberInfo, err := wa.Main.Bridge.Matrix.GetMemberInfo(ctx, msg.Portal.MXID, wa.UserLogin.UserMXID) if err != nil { return fmt.Errorf("failed to get own member info: %w", err) From bf98cabad21ae37eacbe0c501b34abd011a59c8c Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Tue, 13 Oct 2026 11:46:49 +0000 Subject: [PATCH 433/486] chatinfo: don't overwrite mutes while regular_high recovery is pending (#978) --- pkg/connector/chatinfo.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 3984b5f9..d26aeaef 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -10,6 +10,7 @@ import ( "go.mau.fi/util/jsontime" "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" + "go.mau.fi/whatsmeow/appstate" "go.mau.fi/whatsmeow/types" "go.mau.fi/whatsmeow/types/events" "maunium.net/go/mautrix/bridgev2" @@ -116,9 +117,16 @@ func (wa *WhatsAppClient) applyChatSettings(ctx context.Context, chatID types.JI return } } - info.UserLocal = &bridgev2.UserLocalPortalInfo{ - MutedUntil: ptr.Ptr(chat.MutedUntil), + wa.appStateRecoveryLock.Lock() + _, muteStateBroken := wa.UserLogin.Metadata.(*waid.UserLoginMetadata).AppStateRecoveryAttempted[appstate.WAPatchRegularHigh] + wa.appStateRecoveryLock.Unlock() + userLocal := &bridgev2.UserLocalPortalInfo{} + if !muteStateBroken { + userLocal.MutedUntil = ptr.Ptr(chat.MutedUntil) + } else if info.UserLocal != nil { + userLocal.MutedUntil = info.UserLocal.MutedUntil } + info.UserLocal = userLocal if chat.Pinned { info.UserLocal.Tag = ptr.Ptr(wa.Main.Config.PinnedTag) } else if chat.Archived { From 73b70f2753a4a0fbede6f2a1da7946259097769a Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 13 Oct 2026 17:32:09 +0300 Subject: [PATCH 434/486] chatinfo,handlematrix: allow encrypted poll votes in read-only groups Permissions are now checked by whatsmeow to return appropriate errors --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/chatinfo.go | 16 ++++++++++------ pkg/connector/events.go | 1 + pkg/connector/handlematrix.go | 5 +++++ pkg/msgconv/from-matrix.go | 1 - 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index b6398d42..334a3bdc 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/yuin/goldmark v1.8.6 go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20261006124319-9399289b022b + go.mau.fi/whatsmeow v0.0.0-20261013142727-4cf8bc8e0aeb golang.org/x/image v0.46.0 golang.org/x/net v0.59.0 golang.org/x/sync v0.23.0 diff --git a/go.sum b/go.sum index 5854082c..e5f286db 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,8 @@ go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8 h1:PxaO4u8ti7Eef1WKPAINXrcU go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8/go.mod h1:40TDo7/ekSeOjgr8KAmX31Yf4zrOF94j83WQB+u5ZPc= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20261006124319-9399289b022b h1:sxHC8T2zT69QJmFx6zuB3KuDu+49KFf28bszKtv8vXg= -go.mau.fi/whatsmeow v0.0.0-20261006124319-9399289b022b/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= +go.mau.fi/whatsmeow v0.0.0-20261013142727-4cf8bc8e0aeb h1:yRvCcaQwqHMpUrV9Cn48QOw/YSsV1BlXCs00BTRz0Ks= +go.mau.fi/whatsmeow v0.0.0-20261013142727-4cf8bc8e0aeb/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index d26aeaef..61b9e740 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -276,6 +276,8 @@ func setTopicID(id, topic string) bridgev2.ExtraUpdater[*bridgev2.Portal] { func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupInfo) *bridgev2.ChatInfo { sendEventPL := defaultPL + // Community announcement groups allow non-admins to send threaded replies. + // Power levels can't represent that, but the power level is checked separately when handling messages. if info.IsAnnounce && !info.IsDefaultSubGroup { sendEventPL = adminPL } @@ -306,6 +308,7 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn event.StateRoomName: metaChangePL, event.StateRoomAvatar: metaChangePL, event.StateTopic: metaChangePL, + event.EventEncrypted: defaultPL, // Need to always allow encrypted events for poll responses event.EventReaction: defaultPL, event.EventRedaction: defaultPL, event.EventUnstablePollResponse: defaultPL, @@ -572,12 +575,13 @@ 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, - event.EventUnstablePollResponse: defaultPL, + event.StateRoomName: adminPL, + event.StateRoomAvatar: adminPL, + event.StateTopic: adminPL, + event.EventReaction: defaultPL, + event.EventRedaction: defaultPL, + // TODO support poll votes in channels + //event.EventUnstablePollResponse: defaultPL, }, }, }, diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 743492d1..481d0d73 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -539,6 +539,7 @@ func (evt *WAMediaRetry) ConvertEdit(ctx context.Context, portal *bridgev2.Porta log.Debug().Msg("Got error response in media retry notification, but response also contains a new download URL - trying to download") } } + log.Trace().Any("retry_content", retryData).Msg("Decrypted successful media retry response") err = evt.wa.mediaRetryLock.Acquire(ctx, 1) if err != nil { return nil, fmt.Errorf("failed to acquire media retry lock: %w", err) diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index 933c43a0..fc5760eb 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -106,6 +106,7 @@ func (wa *WhatsAppClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2 var ErrBroadcastSendDisabled = bridgev2.WrapErrorInStatus(errors.New("sending status messages is disabled")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported) var ErrBroadcastReactionUnsupported = bridgev2.WrapErrorInStatus(errors.New("reacting to status messages is not currently supported")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported) +var ErrNotCommunityAdmin = bridgev2.WrapErrorInStatus(errors.New("only community admins can send announcements")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusNoPermission) func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage, waMsg *waE2E.Message, req *whatsmeow.SendRequestExtra) (*bridgev2.MatrixMessageResponse, error) { if req == nil { @@ -116,6 +117,7 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg } else { req.ID = wa.Client.GenerateMessageID() } + req.CheckPermissions = true chatJID, err := waid.ParsePortalID(msg.Portal.ID) if err != nil { @@ -134,6 +136,9 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg zerolog.Ctx(ctx).Trace().Any("payload", waMsg).Msg("Outgoing message payload") resp, err := wa.Client.SendMessage(ctx, chatJID, waMsg, *req) if err != nil { + if errors.Is(err, whatsmeow.ErrNoPermissionInGroup) { + return nil, ErrNotCommunityAdmin + } return nil, err } var pickedMessageID networkid.MessageID diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 833813f7..192b8a6d 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -193,7 +193,6 @@ func (mc *MessageConverter) ToWhatsApp( DeprecatedLIDSession: ptr.Ptr(false), } } else { - // TODO check permissions? message.MessageContextInfo = &waE2E.MessageContextInfo{ MessageSecret: random.Bytes(32), } From abf1940af85952912487a87671c82e895d8f574a Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Wed, 14 Oct 2026 22:39:42 +0100 Subject: [PATCH 435/486] backfill: skip fake anchor messages in on-demand backwards backfill (#974) Co-authored-by: Claude Opus 5.5 --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/backfill.go | 16 +++++++++++++--- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 334a3bdc..730d9683 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( golang.org/x/sync v0.23.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.31.1-0.20261005174338-57e1ea5dd74d + maunium.net/go/mautrix v0.31.1-0.20261014100333-d27bb75b1649 ) require ( diff --git a/go.sum b/go.sum index e5f286db..fcbdc22f 100644 --- a/go.sum +++ b/go.sum @@ -103,5 +103,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.31.1-0.20261005174338-57e1ea5dd74d h1:sOdF5AF9MiU2mudfMBgQC79FJrhBBDbUkxQCi0T2uHI= -maunium.net/go/mautrix v0.31.1-0.20261005174338-57e1ea5dd74d/go.mod h1:t97J+6KmbmeoepWOunBgpu1xIvuBpI36ebBqIbYT8Jk= +maunium.net/go/mautrix v0.31.1-0.20261014100333-d27bb75b1649 h1:oL4IWgkubcKHVyvTr38FzkQ24EeDZuKTpb1ZSxLnKzU= +maunium.net/go/mautrix v0.31.1-0.20261014100333-d27bb75b1649/go.mod h1:t97J+6KmbmeoepWOunBgpu1xIvuBpI36ebBqIbYT8Jk= diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 85f4499b..e582ad1e 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -706,9 +706,19 @@ func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, portalJID if params.AnchorMessage == nil { return nil, fmt.Errorf("anchor message is required to fetch messages from phone") } - parsed, err := waid.ParseMessageID(params.AnchorMessage.ID) + anchor := params.AnchorMessage + parsed, err := waid.ParseMessageID(anchor.ID) if err != nil { - return nil, fmt.Errorf("failed to parse anchor message ID: %w", err) + anchor, err = wa.Main.Bridge.DB.Message.GetFirstPortalMessageExcludingIDPattern(ctx, params.Portal.PortalKey, "fake:%") + if err != nil { + return nil, fmt.Errorf("failed to get first non-fake portal message: %w", err) + } else if anchor == nil { + return &bridgev2.FetchMessagesResponse{}, nil + } + parsed, err = waid.ParseMessageID(anchor.ID) + if err != nil { + return nil, fmt.Errorf("failed to parse anchor message ID: %w", err) + } } msgID := wa.Client.GenerateMessageID() @@ -720,7 +730,7 @@ func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, portalJID IsGroup: parsed.Chat.Server == types.GroupServer, }, ID: parsed.ID, - Timestamp: params.AnchorMessage.Timestamp, + Timestamp: anchor.Timestamp, }, 50) zerolog.Ctx(ctx).Debug(). Str("request_msg_id", msgID). From 09329681068f38b962ee7bddf65a2488a4130df9 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 15 Oct 2026 13:36:27 +0300 Subject: [PATCH 436/486] 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 730d9683..989ec73a 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/yuin/goldmark v1.8.6 go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20261013142727-4cf8bc8e0aeb + go.mau.fi/whatsmeow v0.0.0-20261015103503-3142697f59a2 golang.org/x/image v0.46.0 golang.org/x/net v0.59.0 golang.org/x/sync v0.23.0 diff --git a/go.sum b/go.sum index fcbdc22f..47fad461 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,8 @@ go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8 h1:PxaO4u8ti7Eef1WKPAINXrcU go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8/go.mod h1:40TDo7/ekSeOjgr8KAmX31Yf4zrOF94j83WQB+u5ZPc= go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA= go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo= -go.mau.fi/whatsmeow v0.0.0-20261013142727-4cf8bc8e0aeb h1:yRvCcaQwqHMpUrV9Cn48QOw/YSsV1BlXCs00BTRz0Ks= -go.mau.fi/whatsmeow v0.0.0-20261013142727-4cf8bc8e0aeb/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= +go.mau.fi/whatsmeow v0.0.0-20261015103503-3142697f59a2 h1:SJbKhrV30ZDjQA7dpeqm4565LTIQs5AByuMmy8gbLnk= +go.mau.fi/whatsmeow v0.0.0-20261015103503-3142697f59a2/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= From 1f7131c2eef8c40a32d0e39f33398b29e2675aef Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Fri, 16 Oct 2026 11:05:18 +0000 Subject: [PATCH 437/486] mediarequest: send pending requests at the first scheduled time (#981) --- pkg/connector/mediarequest.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/connector/mediarequest.go b/pkg/connector/mediarequest.go index 38061967..6a30cf6f 100644 --- a/pkg/connector/mediarequest.go +++ b/pkg/connector/mediarequest.go @@ -88,6 +88,7 @@ func (wa *WhatsAppClient) mediaRequestLoop(ctx context.Context) { return case <-time.After(startIn): } + wa.sendMediaRequests(ctx) ticker := time.NewTicker(24 * time.Hour) defer ticker.Stop() for { From 1067291a89f4949887b328104005abe4f7163ddf Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 16 Oct 2026 18:03:24 +0300 Subject: [PATCH 438/486] Bump version to v26.10 --- CHANGELOG.md | 25 +++++++++++++++++ cmd/mautrix-whatsapp/main.go | 2 +- go.mod | 28 +++++++++---------- go.sum | 52 ++++++++++++++++++------------------ 4 files changed, 66 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5dd1738..3002f927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +# v26.10 + +* Added support for new rich response messages used by the Muse AI bot. +* Added support for bridging custom profile set for Muse. +* Added automatic one-time resync of keys needed to decrypt Muse messages. +* Added support for blocking and unblocking users on WhatsApp. +* Added support for contact array messages (thanks to [@voska] in [#961]). +* Added partial support for filling quoted message when sending replies to + WhatsApp. +* Changed undecryptable message resends to only bridge as edits and never as new + messages, to avoid races if the message was deleted before the resend. +* Changed login to wait for OTKs to be successfully uploaded, as the login will + fail if the bridge is stopped before that point. +* Changed chat info syncing to not bridge mute status if app state sync is + failing. +* Changed power levels to allow poll votes in read-only groups even if the room + is encrypted. +* Fixed backfilled media retries not getting sent if configured for local time. +* Fixed "delete for me" from WhatsApp not being bridged correctly in DMs. +* Fixed very long WhatsApp messages not being bridged. +* Fixed document message caption edits not being handled correctly. + +[@voska]: https://github.com/voska +[#961]: https://github.com/mautrix/whatsapp/pull/961 + # v26.09 * Bumped minimum Go version to 1.26. diff --git a/cmd/mautrix-whatsapp/main.go b/cmd/mautrix-whatsapp/main.go index a1d85c3c..ded185c6 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.09", + Version: "26.10", SemCalVer: true, Connector: &connector.WhatsAppConnector{}, } diff --git a/go.mod b/go.mod index 989ec73a..0c70c3dc 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module go.mau.fi/mautrix-whatsapp go 1.26.0 -toolchain go1.27.1 +toolchain go1.27.2 tool go.mau.fi/util/cmd/maubuild @@ -10,17 +10,17 @@ require ( github.com/google/uuid v1.6.0 github.com/lib/pq v1.12.3 github.com/rs/zerolog v1.35.1 - github.com/tidwall/gjson v1.19.0 + github.com/tidwall/gjson v1.20.0 github.com/yuin/goldmark v1.8.6 - go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8 + go.mau.fi/util v0.11.0 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20261015103503-3142697f59a2 - golang.org/x/image v0.46.0 - golang.org/x/net v0.59.0 - golang.org/x/sync v0.23.0 + go.mau.fi/whatsmeow v0.0.0-20261016130820-0b0a1cdac3e2 + golang.org/x/image v0.47.0 + golang.org/x/net v0.61.0 + golang.org/x/sync v0.24.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.31.1-0.20261014100333-d27bb75b1649 + maunium.net/go/mautrix v0.32.0 ) require ( @@ -33,7 +33,7 @@ require ( 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.52 // indirect - github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 // indirect + github.com/petermattis/goid v0.0.0-20260918085751-abfca077860b // 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/vektah/gqlparser/v2 v2.5.27 // indirect go.mau.fi/libsignal v0.2.2 // indirect go.mau.fi/zeroconfig v0.2.0 // indirect - golang.org/x/crypto v0.57.0 // indirect - golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba // indirect - golang.org/x/mod v0.41.0 // indirect - golang.org/x/sys v0.48.0 // indirect - golang.org/x/text v0.42.0 // indirect + golang.org/x/crypto v0.58.0 // indirect + golang.org/x/exp v0.0.0-20261014143641-f0251eb4aa3e // indirect + golang.org/x/mod v0.42.0 // indirect + golang.org/x/sys v0.49.0 // indirect + golang.org/x/text v0.43.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 47fad461..576ec249 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.52 h1:wVbm2Qnf4OXkqhBTSPuCRZDRnxfbVrrmiCEroVdog8U= github.com/mattn/go-sqlite3 v1.14.52/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= -github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 h1:lcWAnrqr2nNfDiArwFNHCE4787Mw2tCdVSOXCru0/0E= -github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/petermattis/goid v0.0.0-20260918085751-abfca077860b h1:OzNsuVdSWGwXvWKTtChx9ve89k4cFJ6NxYM4Aw4/f+E= +github.com/petermattis/goid v0.0.0-20260918085751-abfca077860b/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= @@ -51,8 +51,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI= github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= -github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= +github.com/tidwall/gjson v1.20.0 h1:+agJ3rEzKcCXKDKo0ml26UROcpcAlnZyjq+TjbY5Fto= +github.com/tidwall/gjson v1.20.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= @@ -67,31 +67,31 @@ github.com/yuin/goldmark v1.8.6 h1:d0VcaP1sx9GkFVkoW+KtggpGi2KZ965i14b0+bDQST4= github.com/yuin/goldmark v1.8.6/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mau.fi/libsignal v0.2.2 h1:QV+XdzQkm3x3aSG7FcqfGSZuFXz83pRZPBFaPygHbOU= go.mau.fi/libsignal v0.2.2/go.mod h1:CRlIQg2J8uYTfDFvNoO8/KcZjs5cey0vbc6oj/bssY0= -go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8 h1:PxaO4u8ti7Eef1WKPAINXrcUkqKshE+n239UtbbJOn4= -go.mau.fi/util v0.10.2-0.20260918225449-a4c0d5b86aa8/go.mod h1:40TDo7/ekSeOjgr8KAmX31Yf4zrOF94j83WQB+u5ZPc= +go.mau.fi/util v0.11.0 h1:JNEpkVRxfTdJUC0DIGKiRlLKzl4G/qLX/eVzoP7QuwY= +go.mau.fi/util v0.11.0/go.mod h1:04AEFVsc4zNdIkDC1gkdqbg/nZFolHY50pyXh2DSy7s= 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-20261015103503-3142697f59a2 h1:SJbKhrV30ZDjQA7dpeqm4565LTIQs5AByuMmy8gbLnk= -go.mau.fi/whatsmeow v0.0.0-20261015103503-3142697f59a2/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4= +go.mau.fi/whatsmeow v0.0.0-20261016130820-0b0a1cdac3e2 h1:+EhsWM5VeE5Vu3KRlk3PmYAY0lEYkbhI5W4mTmM9X/Q= +go.mau.fi/whatsmeow v0.0.0-20261016130820-0b0a1cdac3e2/go.mod h1:iY4lm61nTLWsNiIE1EmHUZwhKGBeoV4dhnVE2uMtNoQ= 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.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= -golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= -golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba h1:Ck8QetSgk912qxWLMCKxd0in+aiyBQyDSMae6e/xmpU= -golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba/go.mod h1:50RgIsmK7OwqzTTeqcSXQW8SswW0o8fRcDxmqGluJ8E= -golang.org/x/image v0.46.0 h1:b1+oYj0Jbp6K5MDT4i4/eZpYlk3V8SJhhDKh6LBHAyQ= -golang.org/x/image v0.46.0/go.mod h1:3B3W05VGVQyuXucLINLjXKrqISASfi4Xj+iCVkLMwew= -golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= -golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= -golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= -golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= -golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= -golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= +golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54= +golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w= +golang.org/x/exp v0.0.0-20261014143641-f0251eb4aa3e h1:HgI6W1OolKK6tcV74is+rl9SMT0njsHlp0TbQiGXI/A= +golang.org/x/exp v0.0.0-20261014143641-f0251eb4aa3e/go.mod h1:DBoxwWhjaW8DJJw/YeM4zpeOKMC1FMXSblsYy+1mFSw= +golang.org/x/image v0.47.0 h1:za/8gVz++PPZqmZJBNSISICN5Rrj5zixP/8K++fgmqo= +golang.org/x/image v0.47.0/go.mod h1:IkN5o4A7vq55vXN8FWjjnhg9Rs2GOtffqsSi9mWAKYE= +golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg= +golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs= +golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw= +golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI= +golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA= +golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= -golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= -golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= -golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= +golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA= +golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc= +golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s= 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= @@ -103,5 +103,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.31.1-0.20261014100333-d27bb75b1649 h1:oL4IWgkubcKHVyvTr38FzkQ24EeDZuKTpb1ZSxLnKzU= -maunium.net/go/mautrix v0.31.1-0.20261014100333-d27bb75b1649/go.mod h1:t97J+6KmbmeoepWOunBgpu1xIvuBpI36ebBqIbYT8Jk= +maunium.net/go/mautrix v0.32.0 h1:xuAZzzyWce0pFkBhV/MCLFiJdVs2rFkY3Eep7hNng4I= +maunium.net/go/mautrix v0.32.0/go.mod h1:tZSwPzw+/0pa0dxGufkh0uJqM84BNxB6e2I0+S0UyjU= From c8e38f1dc5093c7d30318719a60be0a8aeb00446 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Fri, 16 Oct 2026 12:39:40 -0400 Subject: [PATCH 439/486] handlewhatsapp: ignore album headers without a reply (#982) --- pkg/connector/wamsgtype.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/connector/wamsgtype.go b/pkg/connector/wamsgtype.go index efdf679d..2b1458b7 100644 --- a/pkg/connector/wamsgtype.go +++ b/pkg/connector/wamsgtype.go @@ -142,7 +142,10 @@ func getMessageType(waMsg *waE2E.Message) string { case waMsg.StickerPackMessage != nil: return "sticker pack" case waMsg.AlbumMessage != nil: - return "album" // or maybe these should be ignored? + if waMsg.GetAlbumMessage().GetContextInfo().GetStanzaID() == "" { + return "ignore" + } + return "album" case waMsg.SendPaymentMessage != nil, waMsg.RequestPaymentMessage != nil, waMsg.DeclinePaymentRequestMessage != nil, waMsg.CancelPaymentRequestMessage != nil, waMsg.PaymentInviteMessage != nil: From 46afb8f781c616514962a4478240844a44c2f1d3 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Fri, 16 Oct 2026 19:35:52 -0400 Subject: [PATCH 440/486] backfill: don't repeat on-demand history sync requests while one is in flight (#980) --- pkg/connector/backfill.go | 40 +++++++++++++++++++++++++++++++++++++++ pkg/connector/client.go | 5 +++++ 2 files changed, 45 insertions(+) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index e582ad1e..f1fc56e9 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -147,6 +147,7 @@ func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context, return } if blob.GetSyncType() == waHistorySync.HistorySync_ON_DEMAND { + wa.finishOnDemandHistorySyncRequest(evt.GetPeerDataRequestSessionID()) 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") @@ -722,6 +723,12 @@ func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, portalJID } msgID := wa.Client.GenerateMessageID() + if !wa.startOnDemandHistorySyncRequest(ctx, portalJID, msgID) { + return &bridgev2.FetchMessagesResponse{ + HasMore: true, + Pending: true, + }, nil + } reqData := wa.Client.BuildHistorySyncRequest(&types.MessageInfo{ MessageSource: types.MessageSource{ Chat: portalJID, @@ -743,6 +750,9 @@ func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, portalJID Peer: true, }) if err != nil { + wa.onDemandHistorySyncLock.Lock() + delete(wa.onDemandHistorySyncRequests, portalJID) + wa.onDemandHistorySyncLock.Unlock() return nil, fmt.Errorf("failed to send history sync request: %w", err) } return &bridgev2.FetchMessagesResponse{ @@ -751,6 +761,36 @@ func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, portalJID }, nil } +type onDemandHistorySyncRequest struct { + ID types.MessageID + SentAt time.Time +} + +func (wa *WhatsAppClient) startOnDemandHistorySyncRequest(ctx context.Context, portalJID types.JID, msgID types.MessageID) bool { + wa.onDemandHistorySyncLock.Lock() + defer wa.onDemandHistorySyncLock.Unlock() + if req, inFlight := wa.onDemandHistorySyncRequests[portalJID]; inFlight { + zerolog.Ctx(ctx).Debug(). + Str("in_flight_request_msg_id", req.ID). + Stringer("portal_jid", portalJID). + Time("sent_at", req.SentAt). + Msg("Not sending history sync request, previous request is still in flight") + return false + } + wa.onDemandHistorySyncRequests[portalJID] = onDemandHistorySyncRequest{ID: msgID, SentAt: time.Now()} + return true +} + +func (wa *WhatsAppClient) finishOnDemandHistorySyncRequest(sessionID string) { + wa.onDemandHistorySyncLock.Lock() + defer wa.onDemandHistorySyncLock.Unlock() + for chatJID, req := range wa.onDemandHistorySyncRequests { + if req.ID == sessionID { + delete(wa.onDemandHistorySyncRequests, chatJID) + } + } +} + func (wa *WhatsAppClient) handleOnDemandHistorySync(ctx context.Context, blob *waHistorySync.HistorySync) { if len(blob.GetConversations()) > 1 { zerolog.Ctx(ctx).Warn(). diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 61fceecf..cd52be28 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(), appStateFullSyncAttempted: make(map[appstate.WAPatchName]time.Time), + onDemandHistorySyncRequests: make(map[types.JID]onDemandHistorySyncRequest), + disableNewsletter: store.BaseClientPayload.GetUserAgent().GetPlatform() == waWa6.ClientPayload_UserAgent_MACOS, } login.Client = w @@ -132,6 +134,9 @@ type WhatsAppClient struct { wasaResyncLock sync.Mutex museProfileLock sync.Mutex lastMuseProfileRequest time.Time + + onDemandHistorySyncLock sync.Mutex + onDemandHistorySyncRequests map[types.JID]onDemandHistorySyncRequest } var ( From 054a7dee77b571b7716a3b0d02ef55adaa4c8c22 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Sun, 18 Oct 2026 12:35:50 +0000 Subject: [PATCH 441/486] directmedia: use LID for sender in media IDs (#983) --- pkg/connector/directmedia.go | 14 ++++++++++++++ pkg/waid/id.go | 8 ++++++-- pkg/waid/mediaid.go | 2 +- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index 8ed94cea..b86f7dcf 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -34,6 +34,7 @@ import ( "go.mau.fi/util/ptr" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waMmsRetry" + "go.mau.fi/whatsmeow/types" "go.mau.fi/whatsmeow/types/events" "maunium.net/go/mautrix" "maunium.net/go/mautrix/bridgev2" @@ -173,6 +174,9 @@ func (wa *WhatsAppConnector) downloadStickerDirectMedia(ctx context.Context, par func (wa *WhatsAppConnector) downloadMessageDirectMedia(ctx context.Context, parsedID *waid.ParsedMediaID, params map[string]string) (mediaproxy.GetMediaResponse, error) { msg, err := wa.Bridge.DB.Message.GetFirstPartByID(ctx, parsedID.UserLogin, parsedID.Message.String()) + if err == nil && msg == nil && parsedID.Message.Chat.Server == types.HiddenUserServer && parsedID.Message.Sender.Server == types.DefaultUserServer { + msg, err = wa.getDirectMediaMessageWithLIDSender(ctx, parsedID) + } if err != nil { return nil, fmt.Errorf("failed to get message: %w", err) } else if msg == nil { @@ -215,6 +219,16 @@ func (wa *WhatsAppConnector) downloadMessageDirectMedia(ctx context.Context, par return wa.makeDirectMediaResponse(ctx, waClient, keys, keys.MimeType, msg.ID, keys, params) } +func (wa *WhatsAppConnector) getDirectMediaMessageWithLIDSender(ctx context.Context, parsedID *waid.ParsedMediaID) (*database.Message, error) { + lid, err := wa.DeviceStore.LIDMap.GetLIDForPN(ctx, parsedID.Message.Sender) + if err != nil { + return nil, fmt.Errorf("failed to get LID for sender: %w", err) + } else if lid.IsEmpty() { + return nil, nil + } + return wa.Bridge.DB.Message.GetFirstPartByID(ctx, parsedID.UserLogin, waid.MakeMessageID(parsedID.Message.Chat, lid, parsedID.Message.ID)) +} + func (wa *WhatsAppConnector) makeDirectMediaResponse( ctx context.Context, waClient *WhatsAppClient, diff --git a/pkg/waid/id.go b/pkg/waid/id.go index 7f8fe5bf..6edbcb81 100644 --- a/pkg/waid/id.go +++ b/pkg/waid/id.go @@ -84,10 +84,14 @@ func MakeMessageID(chat, sender types.JID, id types.MessageID) networkid.Message } func MakeMessageIDWithAltSender(chat, sender, altSender types.JID, id types.MessageID) networkid.MessageID { + return MakeMessageID(chat, pickMessageIDSender(chat, sender, altSender), id) +} + +func pickMessageIDSender(chat, sender, altSender types.JID) types.JID { if chat.Server == types.HiddenUserServer && sender.Server == types.DefaultUserServer && altSender.Server == types.HiddenUserServer { - sender = altSender + return altSender } - return MakeMessageID(chat, sender, id) + return sender } func MakeFakeMessageID(chat, sender types.JID, data string) networkid.MessageID { diff --git a/pkg/waid/mediaid.go b/pkg/waid/mediaid.go index a0ac2d90..913a23bb 100644 --- a/pkg/waid/mediaid.go +++ b/pkg/waid/mediaid.go @@ -39,7 +39,7 @@ const ( 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()) + compactSender := compactJID(pickMessageIDSender(messageInfo.Chat, messageInfo.Sender, messageInfo.SenderAlt).ToNonAD()) receiverID := compactJID(ParseUserLoginID(receiver, 0)) var compactID []byte if idOverride != "" { From c998fd8858ce767ff5b01de71c9d8a4c6669cab8 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Thu, 22 Oct 2026 14:24:34 +0200 Subject: [PATCH 442/486] connector: map WhatsApp errors to message statuses and HTTP errors (#985) --- .bridgelint.yaml | 1 + pkg/connector/errors.go | 107 ++++++++++++++++++++++++++++++++ pkg/connector/handlematrix.go | 54 ++++++++-------- pkg/connector/handlewhatsapp.go | 1 + pkg/connector/login.go | 10 +-- pkg/connector/proxy.go | 15 +++-- pkg/connector/startchat.go | 6 +- pkg/msgconv/matrixpoll.go | 4 +- 8 files changed, 156 insertions(+), 42 deletions(-) create mode 100644 .bridgelint.yaml create mode 100644 pkg/connector/errors.go diff --git a/.bridgelint.yaml b/.bridgelint.yaml new file mode 100644 index 00000000..ea2b48b4 --- /dev/null +++ b/.bridgelint.yaml @@ -0,0 +1 @@ +code_format_allowlist: ["wa-connect-failure-%d"] diff --git a/pkg/connector/errors.go b/pkg/connector/errors.go new file mode 100644 index 00000000..1ee1b205 --- /dev/null +++ b/pkg/connector/errors.go @@ -0,0 +1,107 @@ +// 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" + "errors" + "fmt" + "net/http" + + "go.mau.fi/whatsmeow" + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/event" +) + +// wrapConversionError turns a failure to convert a Matrix event into a message status. Nothing has been +// sent to WhatsApp at that point, so the failure is certain. +func wrapConversionError(err error) error { + if err == nil || errors.As(err, &bridgev2.MessageStatus{}) { + return err + } + return bridgev2.WrapErrorInStatus(err). + WithMessage("failed to convert message for WhatsApp"). + WithIsCertain(true). + WithSendNotice(true) +} + +// wrapWhatsAppError turns an error from a whatsmeow request made while handling a Matrix event into a +// message status. +func wrapWhatsAppError(err error) error { + if err == nil || errors.As(err, &bridgev2.MessageStatus{}) { + return err + } + var iqErr *whatsmeow.IQError + var disconnectErr *whatsmeow.DisconnectedError + switch { + case errors.Is(err, whatsmeow.ErrNotConnected), errors.Is(err, whatsmeow.ErrClientIsNil): + return bridgev2.WrapErrorInStatus(err). + WithMessage("not connected to WhatsApp"). + WithIsCertain(true). + WithSendNotice(true) + case errors.Is(err, whatsmeow.ErrMessageTimedOut), errors.Is(err, whatsmeow.ErrIQTimedOut), + errors.Is(err, context.DeadlineExceeded), errors.As(err, &disconnectErr): + return bridgev2.WrapErrorInStatus(err). + WithMessage("no response from WhatsApp"). + WithIsCertain(false). + WithSendNotice(true) + case errors.Is(err, whatsmeow.ErrIQRateOverLimit): + return bridgev2.WrapErrorInStatus(err). + WithMessage("rate limited by WhatsApp"). + WithErrorReason(event.MessageStatusNetworkError). + WithIsCertain(true). + WithSendNotice(true) + case errors.Is(err, whatsmeow.ErrIQForbidden), errors.Is(err, whatsmeow.ErrIQNotAuthorized): + return bridgev2.WrapErrorInStatus(err). + WithMessage("you don't have permission to do that on WhatsApp"). + WithErrorReason(event.MessageStatusNoPermission). + WithStatus(event.MessageStatusFail). + WithIsCertain(true). + WithSendNotice(true) + case errors.Is(err, whatsmeow.ErrServerReturnedError), errors.As(err, &iqErr): + return bridgev2.WrapErrorInStatus(err). + WithMessage("WhatsApp returned an error"). + WithIsCertain(true). + WithSendNotice(true) + default: + return bridgev2.WrapErrorInStatus(err). + WithMessage("unexpected error from WhatsApp"). + WithSendNotice(true) + } +} + +// wrapWhatsAppRespError turns an error from a whatsmeow request made during login or provisioning into +// an HTTP error, keeping the original error in the chain. Clients retry 429 and 502-504 automatically, +// which isn't wanted for provisioning requests, so those statuses aren't used. +func wrapWhatsAppRespError(err error) error { + var mapped bridgev2.RespError + var disconnectErr *whatsmeow.DisconnectedError + switch { + case errors.Is(err, whatsmeow.ErrIQRateOverLimit): + mapped = bridgev2.RespError(mautrix.MLimitExceeded.WithMessage("Rate limited by WhatsApp. Try again later.").WithStatus(http.StatusBadRequest)) + case errors.Is(err, whatsmeow.ErrNotConnected), errors.Is(err, whatsmeow.ErrClientIsNil), errors.As(err, &disconnectErr): + mapped = bridgev2.RespError(mautrix.MUnknown.WithMessage("Not connected to WhatsApp")) + case errors.Is(err, whatsmeow.ErrIQTimedOut), errors.Is(err, context.DeadlineExceeded): + mapped = bridgev2.RespError(mautrix.MUnknown.WithMessage("WhatsApp didn't respond in time")) + case errors.Is(err, whatsmeow.ErrIQForbidden), errors.Is(err, whatsmeow.ErrIQNotAuthorized): + mapped = bridgev2.RespError(mautrix.MForbidden.WithMessage("WhatsApp denied the request")) + default: + mapped = bridgev2.RespError(mautrix.MUnknown.WithMessage("Request to WhatsApp failed")) + } + return fmt.Errorf("%w: %w", mapped.WithInternalError(err), err) +} diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index fc5760eb..fec40c3c 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -69,7 +69,7 @@ var ( 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) + return nil, wrapConversionError(fmt.Errorf("failed to convert poll start: %w", err)) } defer wa.mcTrack(msg, time.Now(), &retErr) resp, err := wa.handleConvertedMatrixMessage(ctx, &msg.MatrixMessage, waMsg, nil) @@ -89,7 +89,7 @@ func (wa *WhatsAppClient) HandleMatrixPollStart(ctx context.Context, msg *bridge 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) + return nil, wrapConversionError(fmt.Errorf("failed to convert poll vote: %w", err)) } defer wa.mcTrack(msg, time.Now(), &retErr) return wa.handleConvertedMatrixMessage(ctx, &msg.MatrixMessage, waMsg, nil) @@ -98,15 +98,15 @@ func (wa *WhatsAppClient) HandleMatrixPollVote(ctx context.Context, msg *bridgev 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) + return nil, wrapConversionError(fmt.Errorf("failed to convert message: %w", err)) } defer wa.mcTrack(msg, time.Now(), &retErr) return wa.handleConvertedMatrixMessage(ctx, msg, waMsg, req) } -var ErrBroadcastSendDisabled = bridgev2.WrapErrorInStatus(errors.New("sending status messages is disabled")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported) -var ErrBroadcastReactionUnsupported = bridgev2.WrapErrorInStatus(errors.New("reacting to status messages is not currently supported")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported) -var ErrNotCommunityAdmin = bridgev2.WrapErrorInStatus(errors.New("only community admins can send announcements")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusNoPermission) +var ErrBroadcastSendDisabled = bridgev2.WrapErrorInStatus(errors.New("sending status messages is disabled")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported).WithStatus(event.MessageStatusFail) +var ErrBroadcastReactionUnsupported = bridgev2.WrapErrorInStatus(errors.New("reacting to status messages is not currently supported")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported).WithStatus(event.MessageStatusFail) +var ErrNotCommunityAdmin = bridgev2.WrapErrorInStatus(errors.New("only community admins can send announcements")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusNoPermission).WithStatus(event.MessageStatusFail) func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage, waMsg *waE2E.Message, req *whatsmeow.SendRequestExtra) (*bridgev2.MatrixMessageResponse, error) { if req == nil { @@ -139,7 +139,7 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg if errors.Is(err, whatsmeow.ErrNoPermissionInGroup) { return nil, ErrNotCommunityAdmin } - return nil, err + return nil, wrapWhatsAppError(err) } var pickedMessageID networkid.MessageID if resp.Sender == wa.GetLID() { @@ -204,7 +204,7 @@ func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev if msg.Portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup { 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) + return nil, wrapWhatsAppError(fmt.Errorf("failed to encrypt reaction: %w", err)) } reactionMsg.ReactionMessage = nil req.Meta = &types.MsgMetaInfo{ @@ -218,7 +218,7 @@ func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev Metadata: &waid.ReactionMetadata{ SenderDeviceID: wa.JID.Device, }, - }, err + }, wrapWhatsAppError(err) } func (wa *WhatsAppClient) HandleMatrixReactionRemove(ctx context.Context, msg *bridgev2.MatrixReactionRemove) (retErr error) { @@ -248,7 +248,7 @@ func (wa *WhatsAppClient) HandleMatrixReactionRemove(ctx context.Context, msg *b 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 + return wrapWhatsAppError(err) } func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.MatrixEdit) (retErr error) { @@ -273,7 +273,7 @@ func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.M waMsg, _, err := wa.Main.MsgConv.ToWhatsApp(ctx, wa.Client, edit.Event, edit.Content, nil, nil, edit.Portal) if err != nil { - return fmt.Errorf("failed to convert message: %w", err) + return wrapConversionError(fmt.Errorf("failed to convert message: %w", err)) } defer wa.mcTrack(edit, time.Now(), &retErr) @@ -288,7 +288,7 @@ func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.M ID: editID, }) log.Trace().Any("response", resp).Msg("WhatsApp edit response") - return err + return wrapWhatsAppError(err) } func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *bridgev2.MatrixMessageRemove) (retErr error) { @@ -313,7 +313,7 @@ func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *br resp, err := wa.Client.SendMessage(ctx, portalJID, revokeMessage, extra) log.Trace().Any("response", resp).Msg("WhatsApp delete response") - return err + return wrapWhatsAppError(err) } func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt *bridgev2.MatrixReadReceipt) (retErr error) { @@ -362,7 +362,7 @@ func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt * log.Err(err).Strs("ids", ids).Msg("Failed to mark messages as read") } } - return err + return wrapWhatsAppError(err) } func (wa *WhatsAppClient) HandleMatrixTyping(ctx context.Context, msg *bridgev2.MatrixTyping) (retErr error) { @@ -393,7 +393,7 @@ func (wa *WhatsAppClient) HandleMatrixTyping(ctx context.Context, msg *bridgev2. zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to set presence on typing") } } - return wa.Client.SendChatPresence(ctx, portalJID, chatPresence, mediaPresence) + return wrapWhatsAppError(wa.Client.SendChatPresence(ctx, portalJID, chatPresence, mediaPresence)) } var errUnsupportedDisappearingTimer = bridgev2.WrapErrorInStatus(errors.New("invalid value for disappearing timer")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true) @@ -414,7 +414,7 @@ func (wa *WhatsAppClient) HandleMatrixDisappearingTimer(ctx context.Context, msg settingTS := time.UnixMilli(msg.Event.Timestamp) err = wa.Client.SetDisappearingTimer(ctx, portalJID, msg.Content.Timer.Duration, settingTS) if err != nil { - return false, err + return false, wrapWhatsAppError(err) } msg.Portal.Metadata.(*waid.PortalMetadata).DisappearingTimerSetAt = settingTS.Unix() msg.Portal.Disappear = database.DisappearingSetting{ @@ -449,7 +449,7 @@ func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridg return nil, fmt.Errorf("cannot change channel members") } defer wa.mcTrack(msg, time.Now(), &retErr) - return nil, wa.Client.UnfollowNewsletter(ctx, portalJID) + return nil, wrapWhatsAppError(wa.Client.UnfollowNewsletter(ctx, portalJID)) } changes := make([]types.JID, 1) @@ -480,7 +480,7 @@ func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridg defer wa.mcTrack(msg, time.Now(), &retErr) resp, err := wa.Client.UpdateGroupParticipants(ctx, portalJID, changes, action) if err != nil { - return nil, err + return nil, wrapWhatsAppError(err) } else if len(resp) == 0 { return nil, fmt.Errorf("no response for participant change") } else if resp[0].Error != 0 { @@ -506,7 +506,7 @@ func (wa *WhatsAppClient) HandleMatrixRoomName(ctx context.Context, msg *bridgev defer wa.mcTrack(msg, time.Now(), &retErr) err = wa.Client.SetGroupName(ctx, portalJID, msg.Content.Name) if err != nil { - return false, err + return false, wrapWhatsAppError(err) } msg.Portal.Name = msg.Content.Name @@ -530,7 +530,7 @@ func (wa *WhatsAppClient) HandleMatrixRoomTopic(ctx context.Context, msg *bridge oldID := msg.Portal.Metadata.(*waid.PortalMetadata).TopicID err = wa.Client.SetGroupTopic(ctx, portalJID, oldID, newID, msg.Content.Topic) if err != nil { - return false, err + return false, wrapWhatsAppError(err) } msg.Portal.Topic = msg.Content.Topic @@ -566,7 +566,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 + return false, wrapWhatsAppError(err) } msg.Portal.AvatarMXC = msg.Content.URL @@ -595,7 +595,7 @@ func convertRoomAvatar(data []byte) ([]byte, error) { if isCorrectSize && imageType == "jpeg" { return data, nil } else if len(data) > 10*1024*1024 || width > 12000 || height > 12000 { - return nil, fmt.Errorf("avatar is too large for re-encoding") + return nil, fmt.Errorf("%w: avatar is too large for re-encoding", bridgev2.ErrMediaTooLarge) } img, _, err := image.Decode(bytes.NewReader(data)) @@ -703,7 +703,7 @@ func (wa *WhatsAppClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridg } if chatJID.Server == types.NewsletterServer { defer wa.mcTrack(msg, time.Now(), &retErr) - return wa.Client.UnfollowNewsletter(ctx, chatJID) + return wrapWhatsAppError(wa.Client.UnfollowNewsletter(ctx, chatJID)) } else if chatJID.Server == types.GroupServer { memberInfo, err := wa.Main.Bridge.Matrix.GetMemberInfo(ctx, msg.Portal.MXID, wa.UserLogin.UserMXID) if err != nil { @@ -712,7 +712,7 @@ func (wa *WhatsAppClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridg err = wa.Client.LeaveGroup(ctx, chatJID) if err != nil { // TODO ignore errors saying you already left the group? - return fmt.Errorf("failed to leave group before deleting chat: %w", err) + return wrapWhatsAppError(fmt.Errorf("failed to leave group before deleting chat: %w", err)) } } } @@ -721,7 +721,7 @@ func (wa *WhatsAppClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridg return err } defer wa.mcTrack(msg, time.Now(), &retErr) - return wa.Client.SendAppState(ctx, appstate.BuildDeleteChat(chatJID, lastTS, lastKey, true)) + return wrapWhatsAppError(wa.Client.SendAppState(ctx, appstate.BuildDeleteChat(chatJID, lastTS, lastKey, true))) } func (wa *WhatsAppClient) HandleMatrixBlockUser(ctx context.Context, msg *bridgev2.MatrixBlockUser) error { @@ -733,7 +733,7 @@ func (wa *WhatsAppClient) HandleMatrixBlockUser(ctx context.Context, msg *bridge return err } if msg.Content.ReportSpam { - return fmt.Errorf("spam reporting is not supported") + return bridgev2.WrapErrorInStatus(errors.New("reporting spam is not supported on WhatsApp")).WithIsCertain(true).WithErrorAsMessage().WithErrorReason(event.MessageStatusUnsupported).WithStatus(event.MessageStatusFail) } action := events.BlocklistChangeActionUnblock if msg.Content.Block { @@ -753,5 +753,5 @@ func (wa *WhatsAppClient) HandleMatrixBlockUser(ctx context.Context, msg *bridge } }() } - return err + return wrapWhatsAppError(err) } diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 74606abe..11b6f08d 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -259,6 +259,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { wa.UserLogin.BridgeState.Send(status.BridgeState{ StateEvent: status.StateBadCredentials, Error: WATemporaryBan, + UserAction: status.UserActionRestart, Message: evt.String(), }) wa.notifyOfflineSyncWaiter(fmt.Errorf("temporary ban: %s", evt.String())) diff --git a/pkg/connector/login.go b/pkg/connector/login.go index 325bc550..387d780c 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -171,7 +171,7 @@ func (wl *WALogin) Start(ctx context.Context) (*bridgev2.LoginStep, error) { err := wl.Client.Connect() if err != nil { wl.Log.Err(err).Msg("Failed to connect to WhatsApp for QR login") - return nil, err + return nil, wrapWhatsAppRespError(err) } ctx, cancel := context.WithTimeout(ctx, LoginConnectWait) defer cancel() @@ -202,7 +202,7 @@ func (wl *WALogin) SubmitUserInput(ctx context.Context, input map[string]string) err := wl.Client.Connect() if err != nil { wl.Log.Err(err).Msg("Failed to connect to WhatsApp for phone code login") - return nil, err + return nil, wrapWhatsAppRespError(err) } connectCtx, cancelConnect := context.WithTimeout(ctx, LoginConnectWait) err = wl.WaitForQRs.Wait(connectCtx) @@ -223,7 +223,7 @@ func (wl *WALogin) SubmitUserInput(ctx context.Context, input map[string]string) } else if errors.Is(err, whatsmeow.ErrIQRateOverLimit) { return nil, ErrRateLimitedByWhatsApp } - return nil, err + return nil, wrapWhatsAppRespError(err) } wl.Log.Debug().Msg("Phone code login started") return &bridgev2.LoginStep{ @@ -461,7 +461,7 @@ func (wl *WALogin) SubmitWebAuthnResponse(ctx context.Context, rawResp json.RawM if err != nil { wl.Log.Err(err).Msg("Failed to send WebAuthn response") wl.Cancel() - return nil, err + return nil, wrapWhatsAppRespError(err) } select { case <-ctx.Done(): @@ -480,7 +480,7 @@ func (wl *WALogin) SubmitWebAuthnResponse(ctx context.Context, rawResp json.RawM if err != nil { wl.Log.Err(err).Msg("Failed to send WebAuthn confirmation") wl.Cancel() - return nil, err + return nil, wrapWhatsAppRespError(err) } case <-wl.LoginComplete.GetChan(): } diff --git a/pkg/connector/proxy.go b/pkg/connector/proxy.go index 5ae103db..fe1d7ff5 100644 --- a/pkg/connector/proxy.go +++ b/pkg/connector/proxy.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "net/url" + "time" "github.com/rs/zerolog" "go.mau.fi/whatsmeow" @@ -14,11 +15,13 @@ import ( // TODO move proxy stuff to mautrix-go +var getProxyClient = &http.Client{Timeout: 30 * time.Second} + type respGetProxy struct { ProxyURL string `json:"proxy_url"` } -func (wa *WhatsAppConnector) getProxy(reason string) (string, error) { +func (wa *WhatsAppConnector) getProxy(ctx context.Context, reason string) (string, error) { if wa.Config.GetProxyURL == "" { return wa.Config.Proxy, nil } @@ -29,15 +32,17 @@ func (wa *WhatsAppConnector) getProxy(reason string) (string, error) { q := parsed.Query() q.Set("reason", reason) parsed.RawQuery = q.Encode() - req, err := http.NewRequest(http.MethodGet, parsed.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, parsed.String(), nil) if err != nil { return "", fmt.Errorf("failed to prepare request: %w", err) } req.Header.Set("User-Agent", mautrix.DefaultUserAgent) - resp, err := http.DefaultClient.Do(req) + resp, err := getProxyClient.Do(req) if err != nil { return "", fmt.Errorf("failed to send request: %w", err) - } else if resp.StatusCode >= 300 || resp.StatusCode < 200 { + } + defer resp.Body.Close() + if resp.StatusCode >= 300 || resp.StatusCode < 200 { return "", fmt.Errorf("unexpected status code %d", resp.StatusCode) } var respData respGetProxy @@ -56,7 +61,7 @@ func (wa *WhatsAppConnector) updateProxy(ctx context.Context, client *whatsmeow. if isLogin { reason = "login" } - if proxy, err := wa.getProxy(reason); err != nil { + if proxy, err := wa.getProxy(ctx, reason); err != nil { return fmt.Errorf("failed to get proxy address: %w", err) } else if proxy == "" { return nil diff --git a/pkg/connector/startchat.go b/pkg/connector/startchat.go index 18e83263..d4e5f8e8 100644 --- a/pkg/connector/startchat.go +++ b/pkg/connector/startchat.go @@ -88,7 +88,7 @@ func (wa *WhatsAppClient) validateIdentifer(ctx context.Context, number string) } 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) + return types.EmptyJID, wrapWhatsAppRespError(fmt.Errorf("failed to check if number is on WhatsApp: %w", err)) } else if len(resp) == 0 { return types.EmptyJID, fmt.Errorf("the server did not respond to the query") } else if !resp[0].IsIn { @@ -128,7 +128,7 @@ func (wa *WhatsAppClient) startChatPNToLID(ctx context.Context, jid types.JID) ( } 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) + return jid, wrapWhatsAppRespError(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() { @@ -292,7 +292,7 @@ func (wa *WhatsAppClient) CreateGroup(ctx context.Context, params *bridgev2.Grou } resp, err := wa.Client.CreateGroup(ctx, req) if err != nil { - return nil, fmt.Errorf("failed to create group: %w", err) + return nil, wrapWhatsAppRespError(fmt.Errorf("failed to create group: %w", err)) } failedParticipants := make(map[networkid.UserID]*bridgev2.CreateChatFailedParticipant) filteredParticipants := resp.Participants[:0] diff --git a/pkg/msgconv/matrixpoll.go b/pkg/msgconv/matrixpoll.go index fab83303..0fe0026d 100644 --- a/pkg/msgconv/matrixpoll.go +++ b/pkg/msgconv/matrixpoll.go @@ -57,8 +57,8 @@ func (mc *MessageConverter) msc1767ToWhatsApp(ctx context.Context, msg event.MSC } var ( - errPollMissingQuestion = bridgev2.WrapErrorInStatus(errors.New("poll message is missing question")).WithIsCertain(true).WithErrorAsMessage().WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported) - errPollDuplicateOption = bridgev2.WrapErrorInStatus(errors.New("poll options must be unique")).WithIsCertain(true).WithErrorAsMessage().WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported) + errPollMissingQuestion = bridgev2.WrapErrorInStatus(errors.New("poll message is missing question")).WithIsCertain(true).WithErrorAsMessage().WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported).WithStatus(event.MessageStatusFail) + errPollDuplicateOption = bridgev2.WrapErrorInStatus(errors.New("poll options must be unique")).WithIsCertain(true).WithErrorAsMessage().WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported).WithStatus(event.MessageStatusFail) ) func (mc *MessageConverter) PollStartToWhatsApp( From c8564186f4b81ee8a80edbaa105d10d7ad27d9c0 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Thu, 22 Oct 2026 15:09:03 +0000 Subject: [PATCH 443/486] msgconv/from-whatsapp: cut off long messages after adding mentions (#988) --- pkg/msgconv/from-whatsapp.go | 1 + pkg/msgconv/wa-text.go | 26 ++++++++++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 99d036f7..c537e767 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -320,6 +320,7 @@ func (mc *MessageConverter) ToMatrix( } cm.ThreadRoot = ptr.Ptr(waid.MakeMessageID(chat, pcp, commentTarget.GetID())) } + mc.cutOffLongMessage(part.Content) return cm } diff --git a/pkg/msgconv/wa-text.go b/pkg/msgconv/wa-text.go index 7859f37f..1a016334 100644 --- a/pkg/msgconv/wa-text.go +++ b/pkg/msgconv/wa-text.go @@ -21,6 +21,7 @@ import ( "fmt" "html" "regexp" + "slices" "strconv" "strings" @@ -42,16 +43,29 @@ func (mc *MessageConverter) convertTextMessage(ctx context.Context, msg *waE2E.M } contextInfo = msg.GetExtendedTextMessage().GetContextInfo() mc.parseFormatting(part.Content, false, false) - if len(part.Content.FormattedBody) > 20000 { - part.Content.Body = part.Content.Body[:500] + "[...]" - } - if len(part.Content.FormattedBody) > 40000 { - part.Content.FormattedBody = part.Content.FormattedBody[:40000] + "
[long message cut off]" - } part.Content.BeeperLinkPreviews = mc.convertURLPreviewToBeeper(ctx, msg.GetExtendedTextMessage()) return } +func (mc *MessageConverter) cutOffLongMessage(content *event.MessageEventContent) { + if len(content.FormattedBody) <= 20000 && len(content.Body) <= 20000 { + return + } + if content.FormattedBody == "" { + if len(content.Body) > 40000 { + content.Body = content.Body[:40000] + "\n\n[long message cut off]" + } + } else if len(content.Body) > 500 { + content.Body = content.Body[:500] + "[...]" + } + if len(content.FormattedBody) > 40000 { + content.FormattedBody = content.FormattedBody[:40000] + "
[long message cut off]" + } + if content.Mentions != nil { + content.Mentions.UserIDs = slices.DeleteFunc(content.Mentions.UserIDs, mc.Bridge.IsGhostMXID) + } +} + func (mc *MessageConverter) parseFormatting(content *event.MessageEventContent, allowInlineURL, forceHTML bool) { parsedHTML := parseWAFormattingToHTML(content.Body, allowInlineURL) if forceHTML || parsedHTML != event.TextToHTML(content.Body) { From 33b7768ef0d0c781e6e2785f083b9d81adb12822 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Thu, 22 Oct 2026 15:10:37 +0000 Subject: [PATCH 444/486] handlewhatsapp: ignore chat deletes older than the last message (#984) --- pkg/connector/handlewhatsapp.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 11b6f08d..e7a9b65d 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -714,6 +714,27 @@ func (wa *WhatsAppClient) handleWADeleteChat(ctx context.Context, evt *events.De Type: bridgev2.RemoteEventChatDelete, PortalKey: wa.makeWAPortalKey(chatJID), Timestamp: evt.Timestamp, + PreHandleWithResultFunc: func(ctx context.Context, portal *bridgev2.Portal) *bridgev2.EventHandlingResult { + messageRange := evt.Action.GetMessageRange() + deletedAt := max( + evt.Timestamp.Unix(), + messageRange.GetLastMessageTimestamp(), + messageRange.GetLastSystemMessageTimestamp(), + ) + if deletedAt <= 0 { + return nil + } + lastMessage, err := wa.Main.Bridge.DB.Message.GetLastNInPortal(ctx, portal.PortalKey, 1) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to get last message to check if chat delete is outdated") + } else if len(lastMessage) == 1 && lastMessage[0].Timestamp.Unix() > deletedAt { + zerolog.Ctx(ctx).Debug(). + Time("last_message_ts", lastMessage[0].Timestamp). + Msg("Ignoring chat delete that is older than the last message") + return ptr.Ptr(bridgev2.EventHandlingResultIgnored) + } + return nil + }, }, OnlyForMe: true, Children: true, From 83f6cca39bf8676ee805ba02eabdee7bcf5b7d45 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Thu, 22 Oct 2026 18:39:55 +0000 Subject: [PATCH 445/486] msgconv/from-whatsapp: reply to the pinned message in pin notices (#989) --- pkg/msgconv/from-whatsapp.go | 2 +- pkg/msgconv/wa-misc.go | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index c537e767..1ff2e021 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -187,7 +187,7 @@ func (mc *MessageConverter) ToMatrix( case waMsg.EventMessage != nil: part, contextInfo = mc.convertEventMessage(ctx, waMsg.EventMessage) case waMsg.PinInChatMessage != nil: - part, contextInfo = mc.convertPinInChatMessage(ctx, waMsg.PinInChatMessage) + part, contextInfo = mc.convertPinInChatMessage(ctx, info, waMsg.PinInChatMessage) case waMsg.KeepInChatMessage != nil: part, contextInfo = mc.convertKeepInChatMessage(ctx, waMsg.KeepInChatMessage) case waMsg.RichResponseMessage != nil: diff --git a/pkg/msgconv/wa-misc.go b/pkg/msgconv/wa-misc.go index 275b1326..a0d70978 100644 --- a/pkg/msgconv/wa-misc.go +++ b/pkg/msgconv/wa-misc.go @@ -322,11 +322,19 @@ func (mc *MessageConverter) convertEventMessage(ctx context.Context, msg *waE2E. }, msg.GetContextInfo() } -func (mc *MessageConverter) convertPinInChatMessage(ctx context.Context, msg *waE2E.PinInChatMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { +func (mc *MessageConverter) convertPinInChatMessage(ctx context.Context, info *types.MessageInfo, msg *waE2E.PinInChatMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { body := "Pinned a message" if msg.GetType() == waE2E.PinInChatMessage_UNPIN_FOR_ALL { body = "Unpinned a message" } + var contextInfo *waE2E.ContextInfo + if target, err := waid.ParseMessageID(KeyToMessageID(ctx, getClient(ctx), info.Chat, info.Sender, msg.GetKey())); err == nil { + contextInfo = &waE2E.ContextInfo{ + StanzaID: proto.String(target.ID), + Participant: proto.String(target.Sender.String()), + RemoteJID: proto.String(target.Chat.String()), + } + } return &bridgev2.ConvertedMessagePart{ Type: event.EventMessage, @@ -334,7 +342,7 @@ func (mc *MessageConverter) convertPinInChatMessage(ctx context.Context, msg *wa MsgType: event.MsgNotice, Body: body, }, - }, nil + }, contextInfo } func (mc *MessageConverter) convertKeepInChatMessage(ctx context.Context, msg *waE2E.KeepInChatMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { From 8f1937b25941872fa1abbff73970ef35e0b8870e Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Mon, 26 Oct 2026 06:18:12 -0400 Subject: [PATCH 446/486] msgconv/rich-response: don't include raw data if the message would be too large (#996) --- pkg/msgconv/wa-rich-response.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/msgconv/wa-rich-response.go b/pkg/msgconv/wa-rich-response.go index 41ac9ab9..28a648c3 100644 --- a/pkg/msgconv/wa-rich-response.go +++ b/pkg/msgconv/wa-rich-response.go @@ -66,10 +66,15 @@ func (mc *MessageConverter) convertUnifiedRichResponseMessage(ctx context.Contex Extra: extra, } } + content := format.HTMLToContent(htmlBuf.String()) + if len(data)+len(content.Body)+len(content.FormattedBody) > 40000 { + delete(extra, "fi.mau.whatsapp.rich_message") + delete(extra, "fi.mau.whatsapp.non_json_rich_message") + } extra["fi.mau.whatsapp.rich_response_id"] = rrMsg.ResponseID return &bridgev2.ConvertedMessagePart{ Type: event.EventMessage, - Content: new(format.HTMLToContent(htmlBuf.String())), + Content: &content, Extra: extra, } } From 56a19a1d10b3ed17b0409174a9327bde9fefd96b Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Mon, 26 Oct 2026 06:18:46 -0400 Subject: [PATCH 447/486] msgconv/from-whatsapp: count mentions when cutting off long messages (#995) --- pkg/msgconv/wa-text.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pkg/msgconv/wa-text.go b/pkg/msgconv/wa-text.go index 1a016334..52874fd8 100644 --- a/pkg/msgconv/wa-text.go +++ b/pkg/msgconv/wa-text.go @@ -48,22 +48,28 @@ func (mc *MessageConverter) convertTextMessage(ctx context.Context, msg *waE2E.M } func (mc *MessageConverter) cutOffLongMessage(content *event.MessageEventContent) { - if len(content.FormattedBody) <= 20000 && len(content.Body) <= 20000 { + size := len(content.Body) + len(content.FormattedBody) + if content.Mentions != nil { + for _, userID := range content.Mentions.UserIDs { + size += len(userID) + } + } + if size <= 40000 { return } + if content.Mentions != nil { + content.Mentions.UserIDs = slices.DeleteFunc(content.Mentions.UserIDs, mc.Bridge.IsGhostMXID) + } if content.FormattedBody == "" { if len(content.Body) > 40000 { content.Body = content.Body[:40000] + "\n\n[long message cut off]" } - } else if len(content.Body) > 500 { + } else if len(content.FormattedBody) > 20000 && len(content.Body) > 500 { content.Body = content.Body[:500] + "[...]" } if len(content.FormattedBody) > 40000 { content.FormattedBody = content.FormattedBody[:40000] + "
[long message cut off]" } - if content.Mentions != nil { - content.Mentions.UserIDs = slices.DeleteFunc(content.Mentions.UserIDs, mc.Bridge.IsGhostMXID) - } } func (mc *MessageConverter) parseFormatting(content *event.MessageEventContent, allowInlineURL, forceHTML bool) { From 1678681af7a849b66ababf66a928d874db5186da Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Mon, 26 Oct 2026 06:24:17 -0400 Subject: [PATCH 448/486] chatinfo: don't create rooms for newsletters that no longer exist (#993) --- pkg/connector/backfill.go | 2 +- pkg/connector/chatinfo.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index f1fc56e9..629039df 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -435,7 +435,7 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) { return } wrappedInfo, err := wa.getChatInfo(ctx, conv.ChatJID, conv, true) - if errors.Is(err, whatsmeow.ErrNotInGroup) || errors.Is(err, whatsmeow.ErrGroupNotFound) { + if errors.Is(err, whatsmeow.ErrNotInGroup) || errors.Is(err, whatsmeow.ErrGroupNotFound) || errors.Is(err, ErrNewsletterNotFound) { 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) diff --git a/pkg/connector/chatinfo.go b/pkg/connector/chatinfo.go index 61b9e740..ce8c7396 100644 --- a/pkg/connector/chatinfo.go +++ b/pkg/connector/chatinfo.go @@ -31,6 +31,7 @@ func (wa *WhatsAppClient) GetChatInfo(ctx context.Context, portal *bridgev2.Port } var ErrBroadcastList = errors.New("broadcast list bridging is currently not supported") +var ErrNewsletterNotFound = errors.New("newsletter doesn't exist") func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, conv *wadb.Conversation, isNew bool) (wrapped *bridgev2.ChatInfo, err error) { switch portalJID.Server { @@ -56,6 +57,8 @@ func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, info, err := wa.Client.GetNewsletterInfo(ctx, portalJID) if err != nil { return nil, err + } else if info == nil || info.ID.IsEmpty() { + return nil, ErrNewsletterNotFound } wrapped = wa.wrapNewsletterInfo(ctx, info) default: From 2759bc5ea25237fee4645e9e2c131f014362cfb7 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 26 Oct 2026 14:17:07 +0200 Subject: [PATCH 449/486] msgconv/from-whatsapp: add support for encrypted direct media --- go.mod | 2 +- go.sum | 4 ++-- pkg/msgconv/wa-media.go | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 0c70c3dc..4dc93226 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( golang.org/x/sync v0.24.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.32.0 + maunium.net/go/mautrix v0.32.1-0.20261026114918-8c92262134ae ) require ( diff --git a/go.sum b/go.sum index 576ec249..b6989127 100644 --- a/go.sum +++ b/go.sum @@ -103,5 +103,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.32.0 h1:xuAZzzyWce0pFkBhV/MCLFiJdVs2rFkY3Eep7hNng4I= -maunium.net/go/mautrix v0.32.0/go.mod h1:tZSwPzw+/0pa0dxGufkh0uJqM84BNxB6e2I0+S0UyjU= +maunium.net/go/mautrix v0.32.1-0.20261026114918-8c92262134ae h1:4SY5+nh2B968L5NmMGmF0WPRbV9D1yFLP8U+JASuHXg= +maunium.net/go/mautrix v0.32.1-0.20261026114918-8c92262134ae/go.mod h1:tZSwPzw+/0pa0dxGufkh0uJqM84BNxB6e2I0+S0UyjU= diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index 20a1a142..ff7008b5 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -91,7 +91,9 @@ 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, getMediaIDVersion(msg))) + preparedMedia.URL, preparedMedia.File, err = portal.Bridge.GenerateContentURI( + ctx, portal.MXID, waid.MakeMediaID(messageInfo, idOverride, portal.Receiver, getMediaIDVersion(msg)), + ) if err != nil { panic(fmt.Errorf("failed to generate content URI: %w", err)) } From 960b4ed000a6d8da7b6a1d3094bc73389b637830 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 26 Oct 2026 14:54:30 +0200 Subject: [PATCH 450/486] client: send unified session on connect Closes #992 --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/client.go | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4dc93226..c58cd7ce 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/yuin/goldmark v1.8.6 go.mau.fi/util v0.11.0 go.mau.fi/webp v0.3.0 - go.mau.fi/whatsmeow v0.0.0-20261016130820-0b0a1cdac3e2 + go.mau.fi/whatsmeow v0.0.0-20261026125351-1fbae988179a golang.org/x/image v0.47.0 golang.org/x/net v0.61.0 golang.org/x/sync v0.24.0 diff --git a/go.sum b/go.sum index b6989127..df5df11a 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,8 @@ go.mau.fi/util v0.11.0 h1:JNEpkVRxfTdJUC0DIGKiRlLKzl4G/qLX/eVzoP7QuwY= go.mau.fi/util v0.11.0/go.mod h1:04AEFVsc4zNdIkDC1gkdqbg/nZFolHY50pyXh2DSy7s= 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-20261016130820-0b0a1cdac3e2 h1:+EhsWM5VeE5Vu3KRlk3PmYAY0lEYkbhI5W4mTmM9X/Q= -go.mau.fi/whatsmeow v0.0.0-20261016130820-0b0a1cdac3e2/go.mod h1:iY4lm61nTLWsNiIE1EmHUZwhKGBeoV4dhnVE2uMtNoQ= +go.mau.fi/whatsmeow v0.0.0-20261026125351-1fbae988179a h1:ZyCV/mWfRkutxs5R0K3v+oyTbF4huprSlPZg/LEFQ7k= +go.mau.fi/whatsmeow v0.0.0-20261026125351-1fbae988179a/go.mod h1:iY4lm61nTLWsNiIE1EmHUZwhKGBeoV4dhnVE2uMtNoQ= 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.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54= diff --git a/pkg/connector/client.go b/pkg/connector/client.go index cd52be28..41812355 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -84,6 +84,7 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. w.Client.ManualHistorySyncDownload = true w.Client.SendReportingTokens = true w.Client.AutomaticMessageRerequestFromPhone = true + w.Client.UnifiedSessionInterval = 24 * time.Hour w.Client.GetMessageForRetry = w.trackNotFoundRetry w.Client.PreRetryCallback = w.trackFoundRetry w.Client.BackgroundEventCtx = w.UserLogin.Log.WithContext(wa.Bridge.BackgroundCtx) From 322485c5a983682b0023bb94281a9ce08a29be8a Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Mon, 26 Oct 2026 12:14:24 -0400 Subject: [PATCH 451/486] handlewhatsapp: match own phone number in bot chat receipts (#1000) --- pkg/connector/handlewhatsapp.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index e7a9b65d..4ba4f10b 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -514,6 +514,10 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei } else if evt.Chat.Server == types.NewsletterServer { messageSender = evt.Chat } + var messageSenderAlt types.JID + if evt.MessageSender.IsEmpty() && evt.Chat.Server == types.BotServer { + messageSenderAlt = wa.JID + } var chatAlt types.JID if evt.Chat.Server == types.DefaultUserServer { chatLID, _ := wa.GetStore().LIDs.GetLIDForPN(ctx, evt.Chat) @@ -527,6 +531,9 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei if !chatAlt.IsEmpty() { targets = append(targets, waid.MakeMessageID(chatAlt, messageSender, id)) } + if !messageSenderAlt.IsEmpty() { + targets = append(targets, waid.MakeMessageID(evt.Chat, messageSenderAlt, id)) + } } senderLID := evt.Sender if senderLID.Server == types.DefaultUserServer && !evt.SenderAlt.IsEmpty() { From 57e055c3adb94c63849ac35ecc89a5f32ef3de74 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Mon, 26 Oct 2026 16:42:19 +0000 Subject: [PATCH 452/486] lidmigrate: move Meta AI phone number portals to the bot JID (#998) --- pkg/connector/backfill.go | 8 ++++++-- pkg/connector/connector.go | 4 ++++ pkg/connector/lidmigrate.go | 28 ++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 629039df..0e319e23 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -249,7 +249,9 @@ func (wa *WhatsAppClient) handleWAHistorySync( } else { totalMessageCount += len(conv.GetMessages()) } - if jid.Server == types.DefaultUserServer { + if jid == types.MetaAIJID { + jid = types.NewMetaAIJID + } else if jid.Server == types.DefaultUserServer { lid, err := wa.GetStore().LIDs.GetLIDForPN(ctx, jid) if err != nil { log.Err(err).Stringer("pn", jid).Msg("Failed to get LID for phone number in history sync") @@ -803,7 +805,9 @@ 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 { + if portalJID == types.MetaAIJID { + portalJID = types.NewMetaAIJID + } else 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") diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index d7261d3a..fb3cdc8f 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -164,6 +164,10 @@ func (wa *WhatsAppConnector) Start(ctx context.Context) error { if err != nil { return fmt.Errorf("failed to migrate to LID DMs: %w", err) } + err = wa.migrateMetaAIPortals(ctx) + if err != nil { + return fmt.Errorf("failed to migrate Meta AI portals: %w", err) + } go func() { err = wa.syncMismatchingGhosts(wa.Bridge.BackgroundCtx) if err != nil { diff --git a/pkg/connector/lidmigrate.go b/pkg/connector/lidmigrate.go index ec1c700e..befef072 100644 --- a/pkg/connector/lidmigrate.go +++ b/pkg/connector/lidmigrate.go @@ -47,6 +47,8 @@ func (wa *WhatsAppClient) FindAltTargetMessage(ctx context.Context, targetMsg ne if err != nil { return nil, err } + } else if parsed.Chat == types.NewMetaAIJID { + altChat = types.MetaAIJID } if !altSender.IsEmpty() { altSenderID := *parsed @@ -58,6 +60,8 @@ func (wa *WhatsAppClient) FindAltTargetMessage(ctx context.Context, targetMsg ne altChatID.Chat = altChat if altSender.Server == types.DefaultUserServer { altChatID.Sender = altSender + } else if parsed.Sender == types.NewMetaAIJID { + altChatID.Sender = types.MetaAIJID } alts = append(alts, altChatID.String()) } @@ -211,6 +215,30 @@ func (wa *WhatsAppConnector) migrateToLIDDMs(ctx context.Context) error { return nil } +func (wa *WhatsAppConnector) migrateMetaAIPortals(ctx context.Context) error { + if wa.Bridge.Background || wa.Bridge.DB.KV.Get(ctx, "whatsapp_meta_ai_portals_migrated") == "true" { + return nil + } + log := zerolog.Ctx(ctx).With().Str("action", "migrate meta ai portals").Logger() + const findMetaAIPortals = "SELECT id, receiver FROM portal WHERE bridge_id=$1 AND id=$2" + portalKeys, err := scanPortalKey.NewRowIter(wa.Bridge.DB.Query(ctx, findMetaAIPortals, wa.Bridge.ID, waid.MakePortalID(types.MetaAIJID))).AsList() + if err != nil { + return fmt.Errorf("failed to get Meta AI portals: %w", err) + } + for _, key := range portalKeys { + res, err := wa.reIDPhoneDMToLID(ctx, types.MetaAIJID, types.NewMetaAIJID, key.Receiver) + if err != nil { + return fmt.Errorf("failed to re-ID Meta AI portal for %s: %w", key.Receiver, err) + } + log.Info(). + Str("receiver", string(key.Receiver)). + Stringer("result", res). + Msg("Re-ID'd Meta AI phone number portal") + } + wa.Bridge.DB.KV.Set(ctx, "whatsapp_meta_ai_portals_migrated", "true") + return nil +} + func (wa *WhatsAppConnector) syncMismatchingGhosts(ctx context.Context) error { if wa.Bridge.Background || wa.Bridge.DB.KV.Get(ctx, "whatsapp_lid_avatars_resynced") == "true" { return nil From 870414b015739dec7087ea4660b33e11b7f0a237 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Mon, 26 Oct 2026 17:58:59 +0000 Subject: [PATCH 453/486] commands: add reset-appstate command (#994) --- pkg/connector/commands.go | 52 +++++++++++++++++++++++++++++++++----- pkg/connector/connector.go | 2 +- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/pkg/connector/commands.go b/pkg/connector/commands.go index 037d24e2..120d05ee 100644 --- a/pkg/connector/commands.go +++ b/pkg/connector/commands.go @@ -148,13 +148,7 @@ func fnSync(ce *commands.Event) { case "appstate": names := appstate.AllPatchNames[:] if len(ce.Args) > 1 { - names = exslices.CastFuncFilter(ce.Args[1:], func(name string) (appstate.WAPatchName, bool) { - if !slices.Contains(appstate.AllPatchNames[:], appstate.WAPatchName(name)) { - ce.Reply("Invalid app state name `%s`", name) - return "", false - } - return appstate.WAPatchName(name), true - }) + names = parseAppStateNames(ce, ce.Args[1:]) } for _, name := range names { err := wa.Client.FetchAppState(ce.Ctx, name, true, false) @@ -174,6 +168,50 @@ func fnSync(ce *commands.Event) { } } +func parseAppStateNames(ce *commands.Event, args []string) []appstate.WAPatchName { + return exslices.CastFuncFilter(args, func(name string) (appstate.WAPatchName, bool) { + if !slices.Contains(appstate.AllPatchNames[:], appstate.WAPatchName(name)) { + ce.Reply("Invalid app state name `%s`", name) + return "", false + } + return appstate.WAPatchName(name), true + }) +} + +var cmdResetAppState = &commands.FullHandler{ + Func: fnResetAppState, + Name: "reset-appstate", + Help: commands.HelpMeta{ + Section: commands.HelpSectionAdmin, + Description: "Ask your phone to reset broken app state. This logs out all linked devices.", + Args: "", + }, + RequiresLogin: true, +} + +func fnResetAppState(ce *commands.Event) { + login := ce.User.GetDefaultLogin() + if login == nil { + ce.Reply("Login not found") + return + } + if len(ce.Args) == 0 { + ce.Reply("Usage: `$cmdprefix reset-appstate `\n\nThis logs out all linked devices, including the bridge. You'll have to log in again afterwards.") + return + } + names := parseAppStateNames(ce, ce.Args) + if len(names) != len(ce.Args) { + return + } + wa := login.Client.(*WhatsAppClient) + _, err := wa.Client.SendPeerMessage(ce.Ctx, whatsmeow.BuildFatalAppStateExceptionNotification(names...)) + if err != nil { + ce.Reply("Failed to send app state reset request: %v", err) + return + } + ce.Reply("Sent app state reset request. Your phone should log out all linked devices shortly, after which you can log in again.") +} + var cmdInviteLink = &commands.FullHandler{ Func: fnInviteLink, Name: "invite-link", diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index fb3cdc8f..94c66ebe 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -110,7 +110,7 @@ func (wa *WhatsAppConnector) Init(bridge *bridgev2.Bridge) { wa.DB = wadb.New(bridge.ID, bridge.DB.Database, bridge.Log.With().Str("db_section", "whatsapp").Logger()) wa.MsgConv.DB = wa.DB wa.Bridge.Commands.(*commands.Processor).AddHandlers( - cmdAccept, cmdSync, cmdInviteLink, cmdResolveLink, cmdJoin, + cmdAccept, cmdSync, cmdResetAppState, cmdInviteLink, cmdResolveLink, cmdJoin, ) wa.mediaEditCache = make(MediaEditCache) wa.unmigratedDMs = exsync.NewSet[networkid.PortalKey]() From bb09b7584cc7ac28a4a2fdcd17b71fa2af2f6d36 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Tue, 27 Oct 2026 10:42:29 +0000 Subject: [PATCH 454/486] msgconv/from-matrix: convert non-h264 videos when sending (#987) --- pkg/msgconv/from-matrix.go | 58 +++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/pkg/msgconv/from-matrix.go b/pkg/msgconv/from-matrix.go index 192b8a6d..ba2adc23 100644 --- a/pkg/msgconv/from-matrix.go +++ b/pkg/msgconv/from-matrix.go @@ -27,12 +27,15 @@ import ( "image/color" "image/jpeg" "net/http" + "os" + "path/filepath" "slices" "strconv" "strings" "time" "github.com/rs/zerolog" + "go.mau.fi/util/exmime" "go.mau.fi/util/ffmpeg" "go.mau.fi/util/ptr" "go.mau.fi/util/random" @@ -515,6 +518,56 @@ func (mc *MessageConverter) convertToWebP(img []byte) ([]byte, int, error) { return webpBuffer.Bytes(), size, nil } +func convertUnsupportedVideoCodec(ctx context.Context, data []byte, mime string) ([]byte, string, error) { + if !ffmpeg.Supported() || !ffmpeg.ProbeSupported() { + return data, mime, nil + } + log := zerolog.Ctx(ctx) + tempdir, err := os.MkdirTemp("", "mautrix_whatsapp_video_*") + if err != nil { + log.Warn().Err(err).Msg("Failed to create temp dir for video, sending it as-is") + return data, mime, nil + } + defer os.RemoveAll(tempdir) + inputPath := filepath.Join(tempdir, "input"+exmime.ExtensionFromMimetype(mime)) + err = os.WriteFile(inputPath, data, 0600) + if err != nil { + log.Warn().Err(err).Msg("Failed to write video to temp file, sending it as-is") + return data, mime, nil + } + probe, err := ffmpeg.Probe(ctx, inputPath) + if err != nil { + log.Warn().Err(err).Msg("Failed to probe video, sending it as-is") + return data, mime, nil + } + videoSupported := !slices.ContainsFunc(probe.Streams, func(stream *ffmpeg.Stream) bool { + return stream.CodecType == "video" && stream.Disposition.AttachedPic == 0 && stream.CodecName != "h264" + }) + audioSupported := !slices.ContainsFunc(probe.Streams, func(stream *ffmpeg.Stream) bool { + return stream.CodecType == "audio" && stream.CodecName != "aac" + }) + if videoSupported && audioSupported { + return data, mime, nil + } + videoArgs := []string{"-c:v", "copy"} + if !videoSupported { + videoArgs = []string{ + "-pix_fmt", "yuv420p", "-c:v", "h264", "-b:v", "2M", + "-filter:v", "crop='floor(in_w/2)*2:floor(in_h/2)*2'", + } + } + outputPath := filepath.Join(tempdir, "output.mp4") + err = ffmpeg.ConvertPathWithDestination(ctx, inputPath, outputPath, nil, append(videoArgs, "-c:a", "aac", "-movflags", "+faststart"), false) + if err != nil { + return nil, mime, err + } + converted, err := os.ReadFile(outputPath) + if err != nil { + return nil, mime, err + } + return converted, "video/mp4", 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 @@ -609,7 +662,10 @@ func (mc *MessageConverter) reuploadFileToWhatsApp( case event.MsgVideo: switch mime { case "video/mp4", "video/3gpp": - // allowed + data, mime, err = convertUnsupportedVideoCodec(ctx, data, mime) + if err != nil { + return nil, nil, mime, fmt.Errorf("%w (%s to h264): %w", bridgev2.ErrMediaConvertFailed, mime, err) + } case "video/webm", "video/quicktime": sourceFormat := "webm" if mime == "video/quicktime" { From 76c59bd05db9c53dfd4d9181a73c605f52091976 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Tue, 27 Oct 2026 10:42:50 +0000 Subject: [PATCH 455/486] backfill: mark history sync messages read up to the unread count (#1001) --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/backfill.go | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c58cd7ce..fc190652 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( golang.org/x/sync v0.24.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.32.1-0.20261026114918-8c92262134ae + maunium.net/go/mautrix v0.32.1-0.20261026145030-920bc073ba8d ) require ( diff --git a/go.sum b/go.sum index df5df11a..2775f5f4 100644 --- a/go.sum +++ b/go.sum @@ -103,5 +103,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.32.1-0.20261026114918-8c92262134ae h1:4SY5+nh2B968L5NmMGmF0WPRbV9D1yFLP8U+JASuHXg= -maunium.net/go/mautrix v0.32.1-0.20261026114918-8c92262134ae/go.mod h1:tZSwPzw+/0pa0dxGufkh0uJqM84BNxB6e2I0+S0UyjU= +maunium.net/go/mautrix v0.32.1-0.20261026145030-920bc073ba8d h1:DsMn0i7cBKcQZ0AVxJm9hfWAu6xJdjRrf457EtXxyNY= +maunium.net/go/mautrix v0.32.1-0.20261026145030-920bc073ba8d/go.mod h1:tZSwPzw+/0pa0dxGufkh0uJqM84BNxB6e2I0+S0UyjU= diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 0e319e23..54edbaa2 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -595,9 +595,28 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet resp.HasMore = hasMore resp.Forward = params.Forward resp.MarkRead = markRead + if params.Forward && !markRead && conv != nil && !ptr.Val(conv.MarkedAsUnread) { + resp.ReadUpTo = historySyncReadUpTo(resp.Messages, int(ptr.Val(conv.UnreadCount))) + } return resp, nil } +func historySyncReadUpTo(messages []*bridgev2.BackfillMessage, unreadCount int) time.Time { + if unreadCount <= 0 { + return time.Time{} + } + for i := len(messages) - 1; i >= 0; i-- { + if messages[i].Sender.IsFromMe { + continue + } + unreadCount-- + if unreadCount == 0 { + return messages[i].Timestamp.Add(-time.Nanosecond) + } + } + return time.Time{} +} + func (wa *WhatsAppClient) deleteHistorySyncMessages(ctx context.Context, portalJID types.JID, newestTS, oldestTS uint64) { var err error var rows int64 From 021504afb01a5f117dd3ffa368f5a3afe8b19f51 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Tue, 27 Oct 2026 12:28:53 +0000 Subject: [PATCH 456/486] msgconv/wa-media: fix sticker dimension scaling for small non-square stickers (#991) --- pkg/msgconv/wa-media.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index ff7008b5..30c6b30c 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -255,10 +255,10 @@ func fixStickerDimensions(info *event.FileInfo) { info.Width = WhatsAppStickerSize info.Height = WhatsAppStickerSize } else if info.Width > info.Height { - info.Height /= info.Width / WhatsAppStickerSize + info.Height = info.Height * WhatsAppStickerSize / info.Width info.Width = WhatsAppStickerSize } else { - info.Width /= info.Height / WhatsAppStickerSize + info.Width = info.Width * WhatsAppStickerSize / info.Height info.Height = WhatsAppStickerSize } } From 51b5c5e5fb764435f278883adf63a6dc25842e0f Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Tue, 27 Oct 2026 16:45:29 +0000 Subject: [PATCH 457/486] msgconv/from-whatsapp: bridge live location start as m.location (#1002) --- pkg/msgconv/wa-location.go | 101 +++++++++++++++++++++++++------------ 1 file changed, 68 insertions(+), 33 deletions(-) diff --git a/pkg/msgconv/wa-location.go b/pkg/msgconv/wa-location.go index 4a1e043f..52f3f131 100644 --- a/pkg/msgconv/wa-location.go +++ b/pkg/msgconv/wa-location.go @@ -30,6 +30,40 @@ import ( "maunium.net/go/mautrix/event" ) +func formatCoordinates(lat, long float64) string { + latChar := 'N' + if lat < 0 { + latChar = 'S' + } + longChar := 'E' + if long < 0 { + longChar = 'W' + } + return fmt.Sprintf("%.4f° %c %.4f° %c", math.Abs(lat), latChar, math.Abs(long), longChar) +} + +func addLocationThumbnail(ctx context.Context, content *event.MessageEventContent, thumbnail []byte) { + if len(thumbnail) == 0 { + return + } + thumbnailMime := http.DetectContentType(thumbnail) + thumbnailURL, thumbnailFile, err := getIntent(ctx).UploadMedia(ctx, getPortal(ctx).MXID, thumbnail, "thumb.jpeg", thumbnailMime) + if err != nil { + return + } + cfg, _, _ := image.DecodeConfig(bytes.NewReader(thumbnail)) + content.Info = &event.FileInfo{ + ThumbnailInfo: &event.FileInfo{ + Size: len(thumbnail), + Width: cfg.Width, + Height: cfg.Height, + MimeType: thumbnailMime, + }, + ThumbnailURL: thumbnailURL, + ThumbnailFile: thumbnailFile, + } +} + func (mc *MessageConverter) convertLocationMessage(ctx context.Context, msg *waE2E.LocationMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { url := msg.GetURL() if len(url) == 0 { @@ -37,15 +71,7 @@ func (mc *MessageConverter) convertLocationMessage(ctx context.Context, msg *waE } name := msg.GetName() if len(name) == 0 { - latChar := 'N' - if msg.GetDegreesLatitude() < 0 { - latChar = 'S' - } - longChar := 'E' - if msg.GetDegreesLongitude() < 0 { - longChar = 'W' - } - name = fmt.Sprintf("%.4f° %c %.4f° %c", math.Abs(msg.GetDegreesLatitude()), latChar, math.Abs(msg.GetDegreesLongitude()), longChar) + name = formatCoordinates(msg.GetDegreesLatitude(), msg.GetDegreesLongitude()) } content := &event.MessageEventContent{ @@ -55,24 +81,7 @@ func (mc *MessageConverter) convertLocationMessage(ctx context.Context, msg *waE 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()), } - - if len(msg.GetJPEGThumbnail()) > 0 { - thumbnailMime := http.DetectContentType(msg.GetJPEGThumbnail()) - thumbnailURL, thumbnailFile, err := getIntent(ctx).UploadMedia(ctx, getPortal(ctx).MXID, msg.GetJPEGThumbnail(), "thumb.jpeg", thumbnailMime) - if err == nil { - cfg, _, _ := image.DecodeConfig(bytes.NewReader(msg.GetJPEGThumbnail())) - content.Info = &event.FileInfo{ - ThumbnailInfo: &event.FileInfo{ - Size: len(msg.GetJPEGThumbnail()), - Width: cfg.Width, - Height: cfg.Height, - MimeType: thumbnailMime, - }, - ThumbnailURL: thumbnailURL, - ThumbnailFile: thumbnailFile, - } - } - } + addLocationThumbnail(ctx, content, msg.GetJPEGThumbnail()) return &bridgev2.ConvertedMessagePart{ Type: event.EventMessage, @@ -81,14 +90,40 @@ func (mc *MessageConverter) convertLocationMessage(ctx context.Context, msg *waE } func (mc *MessageConverter) convertLiveLocationMessage(ctx context.Context, msg *waE2E.LiveLocationMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) { + if msg.DegreesLatitude == nil || msg.DegreesLongitude == nil { + content := &event.MessageEventContent{ + Body: "Started sharing live location", + MsgType: event.MsgNotice, + } + if len(msg.GetCaption()) > 0 { + content.Body += ": " + msg.GetCaption() + } + content.Body += "\n\nUse the WhatsApp app to see the location." + return &bridgev2.ConvertedMessagePart{ + Type: event.EventMessage, + Content: content, + }, msg.GetContextInfo() + } + + lat, long := msg.GetDegreesLatitude(), msg.GetDegreesLongitude() + url := fmt.Sprintf("https://maps.google.com/?q=%.5f,%.5f", lat, long) + name := formatCoordinates(lat, long) + const note = "Live updates are only visible in the WhatsApp app." + body := fmt.Sprintf("Live location: %s\n%s", name, url) + formattedBody := fmt.Sprintf(`Live location: %s`, html.EscapeString(url), html.EscapeString(name)) + if caption := msg.GetCaption(); len(caption) > 0 { + body += "\n" + caption + formattedBody += "
" + html.EscapeString(caption) + } content := &event.MessageEventContent{ - Body: "Started sharing live location", - MsgType: event.MsgNotice, + MsgType: event.MsgLocation, + Body: body + "\n\n" + note, + Format: event.FormatHTML, + FormattedBody: formattedBody + "

" + note, + GeoURI: fmt.Sprintf("geo:%.5f,%.5f", lat, long), } - if len(msg.GetCaption()) > 0 { - content.Body += ": " + msg.GetCaption() - } - content.Body += "\n\nUse the WhatsApp app to see the location." + addLocationThumbnail(ctx, content, msg.GetJPEGThumbnail()) + return &bridgev2.ConvertedMessagePart{ Type: event.EventMessage, Content: content, From 88f5fc738ca4c0abc7ec3792131233a72f56f2a2 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Wed, 28 Oct 2026 20:33:06 +0000 Subject: [PATCH 458/486] lidmigrate: remove phone number ghosts that rejoined LID DMs (#990) --- pkg/connector/connector.go | 4 +++ pkg/connector/lidmigrate.go | 72 +++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 94c66ebe..19caace5 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -173,6 +173,10 @@ func (wa *WhatsAppConnector) Start(ctx context.Context) error { if err != nil { wa.Bridge.Log.Err(err).Msg("Failed to sync mismatching ghosts") } + err = wa.removeRejoinedPNGhosts(wa.Bridge.BackgroundCtx) + if err != nil { + wa.Bridge.Log.Err(err).Msg("Failed to remove rejoined PN ghosts") + } }() return nil } diff --git a/pkg/connector/lidmigrate.go b/pkg/connector/lidmigrate.go index befef072..17b24287 100644 --- a/pkg/connector/lidmigrate.go +++ b/pkg/connector/lidmigrate.go @@ -18,6 +18,7 @@ package connector import ( "context" + "errors" "fmt" "time" @@ -25,9 +26,11 @@ import ( "go.mau.fi/util/dbutil" "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" + "maunium.net/go/mautrix/id" "go.mau.fi/mautrix-whatsapp/pkg/waid" ) @@ -295,3 +298,72 @@ func (wa *WhatsAppConnector) syncMismatchingGhosts(ctx context.Context) error { wa.Bridge.DB.KV.Set(ctx, "whatsapp_lid_avatars_resynced", "true") return nil } + +func (wa *WhatsAppConnector) removeRejoinedPNGhosts(ctx context.Context) error { + if wa.Bridge.Background || wa.Bridge.DB.KV.Get(ctx, "whatsapp_lid_dm_pn_ghosts_removed") == "true" { + return nil + } + log := zerolog.Ctx(ctx).With().Str("action", "remove rejoined pn ghosts").Logger() + const findLIDPortals = "SELECT id, mxid FROM portal WHERE bridge_id=$1 AND room_type='dm' AND id LIKE '%@lid' AND mxid IS NOT NULL" + lidPortals, err := scanLIDPortal.NewRowIter(wa.Bridge.DB.Query(ctx, findLIDPortals, wa.Bridge.ID)).AsList() + if err != nil { + return fmt.Errorf("failed to get LID portals: %w", err) + } + var removed, failed int + for _, portal := range lidPortals { + lid, err := waid.ParsePortalID(portal.ID) + if err != nil || lid.Server != types.HiddenUserServer { + continue + } + pn, err := wa.DeviceStore.LIDMap.GetPNForLID(ctx, lid) + if err != nil { + return fmt.Errorf("failed to get PN for LID portal %s: %w", portal.ID, err) + } else if pn.IsEmpty() { + continue + } + pnUserID := waid.MakeUserID(pn) + member, err := wa.Bridge.Matrix.GetMemberInfo(ctx, portal.MXID, wa.Bridge.Matrix.FormatGhostMXID(pnUserID)) + if err != nil { + log.Warn().Err(err).Stringer("room_id", portal.MXID).Msg("Failed to get PN ghost membership") + failed++ + continue + } else if member == nil || member.Membership != event.MembershipJoin { + continue + } + pnGhost, err := wa.Bridge.GetGhostByID(ctx, pnUserID) + if err != nil { + return fmt.Errorf("failed to get PN ghost for %s: %w", pn, 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 { + log.Err(err).Stringer("room_id", portal.MXID).Stringer("pn", pn).Msg("Failed to remove PN ghost from LID DM") + if !errors.Is(err, mautrix.MForbidden) && !errors.Is(err, mautrix.MNotFound) { + failed++ + } + continue + } + removed++ + } + log.Info(). + Int("removed_ghosts", removed). + Int("failed", failed). + Int("total_lid_portals", len(lidPortals)). + Msg("Finished removing rejoined PN ghosts from LID DMs") + if failed == 0 && ctx.Err() == nil { + wa.Bridge.DB.KV.Set(ctx, "whatsapp_lid_dm_pn_ghosts_removed", "true") + } + return nil +} + +type lidPortal struct { + ID networkid.PortalID + MXID id.RoomID +} + +var scanLIDPortal = dbutil.ConvertRowFn[lidPortal](func(row dbutil.Scannable) (portal lidPortal, err error) { + err = row.Scan(&portal.ID, &portal.MXID) + return +}) From 7a0b4b580875e25169b7afac36d334388424bd4d Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Thu, 29 Oct 2026 10:32:04 +0000 Subject: [PATCH 459/486] login: support client WebSocket tunnels (#1004) --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/login.go | 36 ++++++++++++++++++++++-------------- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index fc190652..b059b8b8 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( golang.org/x/sync v0.24.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.32.1-0.20261026145030-920bc073ba8d + maunium.net/go/mautrix v0.32.1-0.20261028161347-0907c3c7611e ) require ( diff --git a/go.sum b/go.sum index 2775f5f4..498f286d 100644 --- a/go.sum +++ b/go.sum @@ -103,5 +103,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.32.1-0.20261026145030-920bc073ba8d h1:DsMn0i7cBKcQZ0AVxJm9hfWAu6xJdjRrf457EtXxyNY= -maunium.net/go/mautrix v0.32.1-0.20261026145030-920bc073ba8d/go.mod h1:tZSwPzw+/0pa0dxGufkh0uJqM84BNxB6e2I0+S0UyjU= +maunium.net/go/mautrix v0.32.1-0.20261028161347-0907c3c7611e h1:7F0CBiL2wsWCfZ1gZlAc30fVeLKNMzuIQb9PZ2mPwts= +maunium.net/go/mautrix v0.32.1-0.20261028161347-0907c3c7611e/go.mod h1:tZSwPzw+/0pa0dxGufkh0uJqM84BNxB6e2I0+S0UyjU= diff --git a/pkg/connector/login.go b/pkg/connector/login.go index 387d780c..c5d4c0af 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -137,6 +137,7 @@ var ( _ bridgev2.LoginProcessDisplayAndWait = (*WALogin)(nil) _ bridgev2.LoginProcessUserInput = (*WALogin)(nil) _ bridgev2.LoginProcessWithOverride = (*WALogin)(nil) + _ bridgev2.LoginProcessWithParams = (*WALogin)(nil) _ bridgev2.LoginProcessWebAuthn = (*WALogin)(nil) ) @@ -144,6 +145,14 @@ const LoginConnectWait = 30 * time.Second const LoginPairPhoneWait = 30 * time.Second func (wl *WALogin) Start(ctx context.Context) (*bridgev2.LoginStep, error) { + return wl.StartWithParams(ctx, bridgev2.LoginStartParams{}) +} + +func (wl *WALogin) StartWithOverride(ctx context.Context, old *bridgev2.UserLogin) (*bridgev2.LoginStep, error) { + return wl.StartWithParams(ctx, bridgev2.LoginStartParams{Override: old}) +} + +func (wl *WALogin) StartWithParams(ctx context.Context, params bridgev2.LoginStartParams) (*bridgev2.LoginStep, error) { wl.Main.firstClientConnectOnce.Do(wl.Main.onFirstClientConnect) device := wl.Main.DeviceStore.NewDevice() wl.Client = whatsmeow.NewClient(device, waLog.Zerolog(wl.Log)) @@ -153,8 +162,20 @@ func (wl *WALogin) Start(ctx context.Context) (*bridgev2.LoginStep, error) { if err := wl.Main.updateProxy(ctx, wl.Client, true); err != nil { return nil, err } + if params.WebSocketHTTP != nil { + wl.Client.SetPreLoginHTTPClient(&http.Client{Transport: params.WebSocketHTTP}) + } if wl.PhoneCode { + if params.Override != nil { + phoneNumber := fmt.Sprintf("+%s", params.Override.ID) + wl.Log.Debug(). + Str("phone_number", phoneNumber). + Msg("Auto-submitting phone number for relogin") + return wl.SubmitUserInput(ctx, map[string]string{ + "phone_number": phoneNumber, + }) + } return &bridgev2.LoginStep{ Type: bridgev2.LoginStepTypeUserInput, StepID: LoginStepIDPhoneNumber, @@ -184,20 +205,6 @@ func (wl *WALogin) Start(ctx context.Context) (*bridgev2.LoginStep, error) { return makeQRStep(wl.QRs[0]), nil } -func (wl *WALogin) StartWithOverride(ctx context.Context, old *bridgev2.UserLogin) (*bridgev2.LoginStep, error) { - step, err := wl.Start(ctx) - if err == nil && step != nil && old != nil && step.StepID == LoginStepIDPhoneNumber { - phoneNumber := fmt.Sprintf("+%s", old.ID) - wl.Log.Debug(). - Str("phone_number", phoneNumber). - Msg("Auto-submitting phone number for relogin") - return wl.SubmitUserInput(ctx, map[string]string{ - "phone_number": phoneNumber, - }) - } - return step, err -} - func (wl *WALogin) SubmitUserInput(ctx context.Context, input map[string]string) (*bridgev2.LoginStep, error) { err := wl.Client.Connect() if err != nil { @@ -316,6 +323,7 @@ func (wl *WALogin) handleEvent(rawEvt any) { wl.LoginError = ErrUnexpectedDisconnect case *events.ManualLoginReconnect: wl.Received515.Set() + return default: wl.Log.Warn().Type("event_type", evt).Msg("Got unexpected event") return From ab1fae7181a25d12bacf40fdb5f77dcbdef002e2 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Thu, 29 Oct 2026 12:57:18 +0000 Subject: [PATCH 460/486] handlematrix: reject empty group names (#1007) --- pkg/connector/handlematrix.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index fec40c3c..a37d66f1 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -107,6 +107,7 @@ func (wa *WhatsAppClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2 var ErrBroadcastSendDisabled = bridgev2.WrapErrorInStatus(errors.New("sending status messages is disabled")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported).WithStatus(event.MessageStatusFail) var ErrBroadcastReactionUnsupported = bridgev2.WrapErrorInStatus(errors.New("reacting to status messages is not currently supported")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported).WithStatus(event.MessageStatusFail) var ErrNotCommunityAdmin = bridgev2.WrapErrorInStatus(errors.New("only community admins can send announcements")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusNoPermission).WithStatus(event.MessageStatusFail) +var ErrEmptyGroupName = bridgev2.WrapErrorInStatus(errors.New("WhatsApp group names can't be empty")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported).WithStatus(event.MessageStatusFail) func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage, waMsg *waE2E.Message, req *whatsmeow.SendRequestExtra) (*bridgev2.MatrixMessageResponse, error) { if req == nil { @@ -501,6 +502,8 @@ func (wa *WhatsAppClient) HandleMatrixRoomName(ctx context.Context, msg *bridgev if msg.Portal.RoomType == database.RoomTypeDM { return false, fmt.Errorf("cannot set room name for DM") + } else if msg.Content.Name == "" { + return false, ErrEmptyGroupName } defer wa.mcTrack(msg, time.Now(), &retErr) From 33357ced5ae666d9b8e85a56327f28bfb97a293c Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Thu, 29 Oct 2026 13:15:13 +0000 Subject: [PATCH 461/486] directmedia,handlematrix: report WhatsApp send rejections as permanent failures (#1006) Co-authored-by: Claude Opus 5.5 --- pkg/connector/directmedia.go | 14 +++++++++- pkg/connector/errors.go | 49 +++++++++++++++++++++++++++++++++++ pkg/connector/handlematrix.go | 13 ++++++---- 3 files changed, 70 insertions(+), 6 deletions(-) diff --git a/pkg/connector/directmedia.go b/pkg/connector/directmedia.go index b86f7dcf..03ef3984 100644 --- a/pkg/connector/directmedia.go +++ b/pkg/connector/directmedia.go @@ -164,7 +164,19 @@ func (wa *WhatsAppConnector) downloadStickerDirectMedia(ctx context.Context, par 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 { + var httpErr whatsmeow.DownloadHTTPError + if errors.As(err, &httpErr) { + switch httpErr.StatusCode { + case http.StatusNotFound, http.StatusGone: + return nil, mautrix.MNotFound.WithMessage("Sticker pack not found") + case http.StatusUnauthorized, http.StatusForbidden: + return nil, mautrix.MForbidden.WithMessage("WhatsApp denied access to the sticker pack") + case http.StatusTooManyRequests: + return nil, mautrix.MLimitExceeded.WithMessage("Rate limited by WhatsApp while fetching the sticker pack").WithCanRetry(true) + default: + return nil, mautrix.MUnknown.WithMessage("WhatsApp returned HTTP %d while fetching the sticker pack", httpErr.StatusCode).WithStatus(http.StatusBadGateway).WithCanRetry(true) + } + } else if err != nil { return nil, err } else if sticker == nil { return nil, mautrix.MNotFound.WithMessage("Sticker not found in pack") diff --git a/pkg/connector/errors.go b/pkg/connector/errors.go index 1ee1b205..38433d6c 100644 --- a/pkg/connector/errors.go +++ b/pkg/connector/errors.go @@ -21,11 +21,16 @@ import ( "errors" "fmt" "net/http" + "strconv" + "strings" + "time" "go.mau.fi/whatsmeow" "maunium.net/go/mautrix" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/event" + + "go.mau.fi/mautrix-whatsapp/pkg/waid" ) // wrapConversionError turns a failure to convert a Matrix event into a message status. Nothing has been @@ -85,6 +90,50 @@ func wrapWhatsAppError(err error) error { } } +// wrapSendError turns an error from sending a message to WhatsApp into a message status. +func (wa *WhatsAppClient) wrapSendError(err error) error { + switch sendErrorCode(err) { + case 403: + return bridgev2.WrapErrorInStatus(err). + WithMessage("you don't have permission to do that on WhatsApp"). + WithErrorReason(event.MessageStatusNoPermission). + WithStatus(event.MessageStatusFail). + WithIsCertain(true). + WithSendNotice(true) + case 463: + msg := "WhatsApp has temporarily restricted your account from messaging new contacts" + if until := wa.UserLogin.Metadata.(*waid.UserLoginMetadata).ReachoutTimelockUntil; until.After(time.Now()) { + msg = fmt.Sprintf("WhatsApp has restricted your account from messaging new contacts until %s", until.UTC().Format("Jan 2, 2006 at 3:04 PM MST")) + } + return bridgev2.WrapErrorInStatus(err). + WithMessage(msg). + WithErrorReason(event.MessageStatusNoPermission). + WithStatus(event.MessageStatusFail). + WithIsCertain(true). + WithSendNotice(true) + case 421, 479: + return bridgev2.WrapErrorInStatus(err). + WithMessage("WhatsApp rejected the message"). + WithErrorReason(event.MessageStatusNetworkError). + WithStatus(event.MessageStatusFail). + WithIsCertain(true). + WithSendNotice(true) + default: + return wrapWhatsAppError(err) + } +} + +// sendErrorCode returns the code WhatsApp rejected a message with, which whatsmeow only includes in the +// error text. +func sendErrorCode(err error) int { + if !errors.Is(err, whatsmeow.ErrServerReturnedError) { + return 0 + } + _, codeStr, _ := strings.Cut(err.Error(), whatsmeow.ErrServerReturnedError.Error()+" ") + code, _ := strconv.Atoi(codeStr) + return code +} + // wrapWhatsAppRespError turns an error from a whatsmeow request made during login or provisioning into // an HTTP error, keeping the original error in the chain. Clients retry 429 and 502-504 automatically, // which isn't wanted for provisioning requests, so those statuses aren't used. diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index a37d66f1..d51efcd2 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -108,6 +108,7 @@ var ErrBroadcastSendDisabled = bridgev2.WrapErrorInStatus(errors.New("sending st var ErrBroadcastReactionUnsupported = bridgev2.WrapErrorInStatus(errors.New("reacting to status messages is not currently supported")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported).WithStatus(event.MessageStatusFail) var ErrNotCommunityAdmin = bridgev2.WrapErrorInStatus(errors.New("only community admins can send announcements")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusNoPermission).WithStatus(event.MessageStatusFail) var ErrEmptyGroupName = bridgev2.WrapErrorInStatus(errors.New("WhatsApp group names can't be empty")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusUnsupported).WithStatus(event.MessageStatusFail) +var ErrAddBlockedByPrivacy = bridgev2.WrapErrorInStatus(errors.New("this user's privacy settings don't allow adding them to groups")).WithErrorAsMessage().WithIsCertain(true).WithSendNotice(true).WithErrorReason(event.MessageStatusNoPermission).WithStatus(event.MessageStatusFail) func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage, waMsg *waE2E.Message, req *whatsmeow.SendRequestExtra) (*bridgev2.MatrixMessageResponse, error) { if req == nil { @@ -140,7 +141,7 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg if errors.Is(err, whatsmeow.ErrNoPermissionInGroup) { return nil, ErrNotCommunityAdmin } - return nil, wrapWhatsAppError(err) + return nil, wa.wrapSendError(err) } var pickedMessageID networkid.MessageID if resp.Sender == wa.GetLID() { @@ -219,7 +220,7 @@ func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev Metadata: &waid.ReactionMetadata{ SenderDeviceID: wa.JID.Device, }, - }, wrapWhatsAppError(err) + }, wa.wrapSendError(err) } func (wa *WhatsAppClient) HandleMatrixReactionRemove(ctx context.Context, msg *bridgev2.MatrixReactionRemove) (retErr error) { @@ -249,7 +250,7 @@ func (wa *WhatsAppClient) HandleMatrixReactionRemove(ctx context.Context, msg *b 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 wrapWhatsAppError(err) + return wa.wrapSendError(err) } func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.MatrixEdit) (retErr error) { @@ -289,7 +290,7 @@ func (wa *WhatsAppClient) HandleMatrixEdit(ctx context.Context, edit *bridgev2.M ID: editID, }) log.Trace().Any("response", resp).Msg("WhatsApp edit response") - return wrapWhatsAppError(err) + return wa.wrapSendError(err) } func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *bridgev2.MatrixMessageRemove) (retErr error) { @@ -314,7 +315,7 @@ func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *br resp, err := wa.Client.SendMessage(ctx, portalJID, revokeMessage, extra) log.Trace().Any("response", resp).Msg("WhatsApp delete response") - return wrapWhatsAppError(err) + return wa.wrapSendError(err) } func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt *bridgev2.MatrixReadReceipt) (retErr error) { @@ -484,6 +485,8 @@ func (wa *WhatsAppClient) HandleMatrixMembership(ctx context.Context, msg *bridg return nil, wrapWhatsAppError(err) } else if len(resp) == 0 { return nil, fmt.Errorf("no response for participant change") + } else if resp[0].Error == 403 && action == whatsmeow.ParticipantChangeAdd { + return nil, ErrAddBlockedByPrivacy } else if resp[0].Error != 0 { return nil, fmt.Errorf("failed to change participant: code %d", resp[0].Error) } From 123ac4f340b248e1b440ae6715a680b519bcc3de Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Thu, 29 Oct 2026 15:35:40 +0000 Subject: [PATCH 462/486] userinfo: don't clear alt ghost avatar when main ghost has no avatar info (#1008) --- pkg/connector/userinfo.go | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index c78b0a8e..b109e82d 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -30,7 +30,7 @@ var ResyncJitterSeconds = 3600 func (wa *WhatsAppClient) EnqueueGhostResync(ghost *bridgev2.Ghost) { lastSync := ghost.Metadata.(*waid.GhostMetadata).LastSync.Time - if lastSync.Add(ResyncMinInterval).After(time.Now()) { + if ghost.AvatarID != "" && lastSync.Add(ResyncMinInterval).After(time.Now()) { return } wa.resyncQueueLock.Lock() @@ -118,12 +118,14 @@ func (wa *WhatsAppClient) doGhostResync(ctx context.Context, queue map[types.JID var portals []*bridgev2.Portal for jid, item := range queue { var lastSync time.Time + unknownAvatar := false if item.ghost != nil { lastSync = item.ghost.Metadata.(*waid.GhostMetadata).LastSync.Time + unknownAvatar = item.ghost.AvatarID == "" } else if item.portal != nil { lastSync = item.portal.Metadata.(*waid.PortalMetadata).LastSync.Time } - if lastSync.Add(ResyncMinInterval).After(time.Now()) { + if !unknownAvatar && lastSync.Add(ResyncMinInterval).After(time.Now()) { log.Debug(). Stringer("jid", jid). Time("last_sync", lastSync). @@ -165,7 +167,7 @@ func (wa *WhatsAppClient) doGhostResync(ctx context.Context, queue map[types.JID log.Warn().Stringer("jid", jid).Msg("Didn't get info for puppet in background sync") continue } - userInfo, err := wa.getUserInfo(ctx, jid, info.PictureID, info.PictureID != "" && string(ghost.AvatarID) != info.PictureID) + userInfo, err := wa.getUserInfo(ctx, jid, info.PictureID, ghost.AvatarID == "" || (info.PictureID != "" && string(ghost.AvatarID) != info.PictureID)) if err != nil { log.Err(err).Stringer("jid", jid).Msg("Failed to get user info for puppet in background sync") continue @@ -472,16 +474,19 @@ func (wa *WhatsAppClient) syncAltGhostWithInfo(ctx context.Context, jid types.JI } func makeInfoFromGhost(ghost *bridgev2.Ghost) *bridgev2.UserInfo { - return &bridgev2.UserInfo{ - Identifiers: ghost.Identifiers, - Name: &ghost.Name, - Avatar: &bridgev2.Avatar{ - ID: ghost.AvatarID, - Remove: ghost.AvatarID == "" || ghost.AvatarMXC == "", - MXC: ghost.AvatarMXC, - Hash: ghost.AvatarHash, - }, + info := &bridgev2.UserInfo{ + Identifiers: ghost.Identifiers, + Name: &ghost.Name, IsBot: &ghost.IsBot, ExtraProfile: ghost.ExtraProfile, } + if ghost.AvatarID != "" { + info.Avatar = &bridgev2.Avatar{ + ID: ghost.AvatarID, + Remove: ghost.AvatarMXC == "", + MXC: ghost.AvatarMXC, + Hash: ghost.AvatarHash, + } + } + return info } From e8f1d2b3f65d666ccbbd08d515c261f4c2f2e8ed Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Thu, 29 Oct 2026 22:27:40 +0000 Subject: [PATCH 463/486] msgconv/from-whatsapp: use own profile name for self mentions (#1009) --- pkg/msgconv/from-whatsapp.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/msgconv/from-whatsapp.go b/pkg/msgconv/from-whatsapp.go index 1ff2e021..db18f35a 100644 --- a/pkg/msgconv/from-whatsapp.go +++ b/pkg/msgconv/from-whatsapp.go @@ -17,6 +17,7 @@ package msgconv import ( + "cmp" "context" "fmt" "html" @@ -92,7 +93,7 @@ func (mc *MessageConverter) getBasicUserInfo(ctx context.Context, user types.JID portal := getPortal(ctx) login := mc.Bridge.GetCachedUserLoginByID(waid.MakeUserLoginID(pnJID)) if login != nil && (portal.Receiver == "" || portal.Receiver == login.ID) { - return login.UserMXID, ghost.Name, nil + return login.UserMXID, cmp.Or(login.RemoteProfile.Name, ghost.Name), nil } } return ghost.Intent.GetMXID(), ghost.Name, nil From 1f30d5516cd01424b3348c1f4792c60f7fc12931 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 30 Oct 2026 15:52:27 +0200 Subject: [PATCH 464/486] backfill: drop messages in the whatsapp official updates chat --- pkg/connector/backfill.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 54edbaa2..378b58e8 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -246,6 +246,11 @@ func (wa *WhatsAppClient) handleWAHistorySync( } else if jid.Server == types.BroadcastServer { log.Debug().Stringer("chat_jid", jid).Msg("Skipping broadcast list in history sync") continue + } else if jid == types.PSAJID { + // New messages in the official updates chat aren't bridged because they use a different path, + // so drop history as well. This should be removed if the PSA chat gets properly bridged in the future. + log.Debug().Stringer("chat_jid", jid).Msg("Skipping PSA in history sync") + continue } else { totalMessageCount += len(conv.GetMessages()) } From 678d4ebb4fa2ae9796789f7276c6fce0f79982b8 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Fri, 30 Oct 2026 16:09:59 +0200 Subject: [PATCH 465/486] wadb: re-arm backfill for chats with leftover history sync --- pkg/connector/wadb/upgrades/00-latest-schema.sql | 2 +- .../11-rearm-leftover-history-sync-backfill.sql | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 pkg/connector/wadb/upgrades/11-rearm-leftover-history-sync-backfill.sql diff --git a/pkg/connector/wadb/upgrades/00-latest-schema.sql b/pkg/connector/wadb/upgrades/00-latest-schema.sql index 0f1fef94..c4172d09 100644 --- a/pkg/connector/wadb/upgrades/00-latest-schema.sql +++ b/pkg/connector/wadb/upgrades/00-latest-schema.sql @@ -1,4 +1,4 @@ --- v0 -> v10 (compatible with v3+): Latest revision +-- v0 -> v11 (compatible with v3+): Latest revision CREATE TABLE whatsapp_poll_option_id ( bridge_id TEXT NOT NULL, diff --git a/pkg/connector/wadb/upgrades/11-rearm-leftover-history-sync-backfill.sql b/pkg/connector/wadb/upgrades/11-rearm-leftover-history-sync-backfill.sql new file mode 100644 index 00000000..69e8b1e7 --- /dev/null +++ b/pkg/connector/wadb/upgrades/11-rearm-leftover-history-sync-backfill.sql @@ -0,0 +1,10 @@ +-- v11 (compatible with v3+): Re-arm backfill for chats with leftover history sync messages +UPDATE backfill_task +SET is_done=false, queue_done=false, dispatched_at=NULL, completed_at=NULL +WHERE (is_done OR queue_done) AND EXISTS ( + SELECT 1 + FROM whatsapp_history_sync_message msg + WHERE msg.bridge_id=backfill_task.bridge_id + AND msg.user_login_id=backfill_task.user_login_id + AND msg.chat_jid=backfill_task.portal_id +); From ae8dcdec4bc4bf775e8b7185cc414c49ed8b48b2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 30 Oct 2026 16:09:08 +0200 Subject: [PATCH 466/486] backfill: delete all messages when FetchMessages returns no more history --- pkg/connector/backfill.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 378b58e8..7a6fd781 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -593,10 +593,11 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet } } } - resp, err := wa.convertHistorySyncMessages(ctx, params.Portal, portalJID, messages, true) + resp, err := wa.convertHistorySyncMessages(ctx, params.Portal, portalJID, messages, true, !params.Forward && !hasMore) if err != nil { return nil, fmt.Errorf("failed to convert messages: %w", err) } + // TODO might be better to always set true here and let GetBetween return an empty result to mark as false? resp.HasMore = hasMore resp.Forward = params.Forward resp.MarkRead = markRead @@ -654,6 +655,7 @@ func (wa *WhatsAppClient) convertHistorySyncMessages( portalJID types.JID, messages []*waWeb.WebMessageInfo, explodeOnError bool, + deleteEverything bool, ) (*bridgev2.FetchMessagesResponse, error) { cli := wa.Client if cli == nil { @@ -709,9 +711,11 @@ func (wa *WhatsAppClient) convertHistorySyncMessages( Messages: convertedMessages, Cursor: networkid.PaginationCursor(strconv.FormatUint(oldestTS, 10)), 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 - wa.deleteHistorySyncMessages(ctx, portalJID, newestTS, oldestTS) + if deleteEverything { + wa.deleteHistorySyncMessages(ctx, portalJID, 1<<62, oldestTS) + } else { + wa.deleteHistorySyncMessages(ctx, portalJID, newestTS, oldestTS) + } if len(mediaRequests) > 0 { go func(ctx context.Context) { for _, req := range mediaRequests { @@ -872,7 +876,7 @@ func (wa *WhatsAppClient) handleOnDemandHistorySync(ctx context.Context, blob *w 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) + resp, err := wa.convertHistorySyncMessages(ctx, portal, portalJID, messages, false, false) if err != nil { return nil, err } From de02399ff8ec8d84d57955565979ccca07ba8b0a Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Fri, 30 Oct 2026 14:43:44 +0000 Subject: [PATCH 467/486] msgconv/from-whatsapp: show view once notice for media with view once flag (#1012) --- pkg/msgconv/wa-media.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/msgconv/wa-media.go b/pkg/msgconv/wa-media.go index 30c6b30c..839f4c26 100644 --- a/pkg/msgconv/wa-media.go +++ b/pkg/msgconv/wa-media.go @@ -49,6 +49,9 @@ func (mc *MessageConverter) convertMediaMessage( isViewOnce bool, cachedPart *bridgev2.ConvertedMessagePart, ) (part *bridgev2.ConvertedMessagePart, contextInfo *waE2E.ContextInfo) { + if viewOnceMsg, ok := msg.(MediaMessageWithViewOnce); ok && viewOnceMsg.GetViewOnce() { + isViewOnce = true + } if mc.DisableViewOnce && isViewOnce { body := "You received a view once message. For added privacy, you can only open it on the WhatsApp app." if messageInfo.IsFromMe { @@ -232,6 +235,11 @@ type MediaMessageWithCaption interface { GetCaption() string } +type MediaMessageWithViewOnce interface { + MediaMessage + GetViewOnce() bool +} + type MediaMessageWithDimensions interface { MediaMessage GetHeight() uint32 From 5dd621fdf4232cd9ba837d0bcebac72fcb369884 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Fri, 30 Oct 2026 16:29:54 +0000 Subject: [PATCH 468/486] login: fix ADV secret rotation ending QR and pairing code logins (#1010) --- pkg/connector/login.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/connector/login.go b/pkg/connector/login.go index c5d4c0af..ae57ac1a 100644 --- a/pkg/connector/login.go +++ b/pkg/connector/login.go @@ -288,6 +288,7 @@ func (wl *WALogin) handleEvent(rawEvt any) { wl.QRs[i] = strings.Replace(code, evt.OldSecret, evt.NewSecret, 1) } wl.ADVRotate.Set() + return case *events.QRScannedWithoutMultidevice: wl.Log.Error().Msg("QR code scanned without multidevice enabled") wl.LoginError = ErrLoginMultideviceNotEnabled @@ -372,9 +373,14 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) { case <-wl.PasskeyRequest.GetChan(): return wl.makePasskeyStep() case <-wl.ADVRotate.GetChan(): - wl.Log.Debug().Msg("ADV secret was rotated, returning new QR immediately") wl.ADVRotate.Clear() - return makeQRStep(wl.QRs[nextIndex]), nil + if currentIndex < 0 || currentIndex >= len(wl.QRs) { + wl.Log.Debug().Msg("ADV secret was rotated, but no QRs are left to return") + wl.Cancel() + return nil, ErrLoginTimeout + } + wl.Log.Debug().Msg("ADV secret was rotated, returning current QR immediately") + return makeQRStep(wl.QRs[currentIndex]), nil case <-wl.LoginComplete.GetChan(): // continue } From 9f8224606920ae9d3aa5bbfc067e1cfe5d8b7475 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 1 Nov 2026 00:50:14 +0200 Subject: [PATCH 469/486] handlewhatsapp: exclude stream replaced and client outdated from unknown error autoreconnect Closes #1005 --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/handlewhatsapp.go | 12 ++++++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index b059b8b8..6e9b5033 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( golang.org/x/sync v0.24.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.32.1-0.20261028161347-0907c3c7611e + maunium.net/go/mautrix v0.32.1-0.20261031224934-b69deb427bd7 ) require ( diff --git a/go.sum b/go.sum index 498f286d..12cf8f53 100644 --- a/go.sum +++ b/go.sum @@ -103,5 +103,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.32.1-0.20261028161347-0907c3c7611e h1:7F0CBiL2wsWCfZ1gZlAc30fVeLKNMzuIQb9PZ2mPwts= -maunium.net/go/mautrix v0.32.1-0.20261028161347-0907c3c7611e/go.mod h1:tZSwPzw+/0pa0dxGufkh0uJqM84BNxB6e2I0+S0UyjU= +maunium.net/go/mautrix v0.32.1-0.20261031224934-b69deb427bd7 h1:GpLIqeJTpG0/ti7XA370HgjnSWd/pQxXVGb/FRkayOA= +maunium.net/go/mautrix v0.32.1-0.20261031224934-b69deb427bd7/go.mod h1:tZSwPzw+/0pa0dxGufkh0uJqM84BNxB6e2I0+S0UyjU= diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 4ba4f10b..a7d4c918 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -237,7 +237,11 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { }) wa.notifyOfflineSyncWaiter(fmt.Errorf("stream error: %s", message)) case *events.StreamReplaced: - wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateUnknownError, Error: WAStreamReplaced}) + wa.UserLogin.BridgeState.Send(status.BridgeState{ + StateEvent: status.StateUnknownError, + Error: WAStreamReplaced, + BlockAutoReconnect: true, + }) wa.notifyOfflineSyncWaiter(fmt.Errorf("stream replaced")) case *events.KeepAliveTimeout: wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateTransientDisconnect, Error: WAKeepaliveTimeout}) @@ -253,7 +257,11 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) { wa.notifyOfflineSyncWaiter(fmt.Errorf("connection failure: %s (%s)", evt.Reason, evt.Message)) case *events.ClientOutdated: wa.UserLogin.Log.Error().Msg("Got a client outdated connect failure. The bridge is likely out of date, please update immediately.") - wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateUnknownError, Error: WAClientOutdated}) + wa.UserLogin.BridgeState.Send(status.BridgeState{ + StateEvent: status.StateUnknownError, + Error: WAClientOutdated, + BlockAutoReconnect: true, + }) wa.notifyOfflineSyncWaiter(fmt.Errorf("client outdated")) case *events.TemporaryBan: wa.UserLogin.BridgeState.Send(status.BridgeState{ From 3a5d6448a8b4d7af1021381891f61c664084099c Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Sun, 1 Nov 2026 12:58:51 +0000 Subject: [PATCH 470/486] backfill: treat own messages from previous LIDs as own (#1017) --- pkg/connector/backfill.go | 3 +++ pkg/connector/client.go | 25 ++++++++++++++++++++++++- pkg/waid/dbmeta.go | 2 ++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 7a6fd781..fe251e1d 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -692,6 +692,9 @@ func (wa *WhatsAppClient) convertHistorySyncMessages( if !wa.ensureAltJIDs(ctx, &evt.Info.MessageSource, false) { return nil, fmt.Errorf("failed to ensure alt JIDs for message %s", evt.Info.ID) } + if evt.Info.IsFromMe { + wa.addPreviousOwnLID(ctx, evt.Info.Sender) + } isViewOnce := evt.IsViewOnce || evt.IsViewOnceV2 || evt.IsViewOnceV2Extension converted, mediaReq := wa.convertHistorySyncMessage( ctx, cli, portal, &evt.Info, evt.Message, evt.RawMessage, isViewOnce, msg.Reactions, diff --git a/pkg/connector/client.go b/pkg/connector/client.go index 41812355..678c8d22 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -55,6 +55,7 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2. mediaRetryLock: semaphore.NewWeighted(wa.Config.HistorySync.MediaRequests.MaxAsyncHandle), pushNamesSynced: exsync.NewEvent(), appStateFullSyncAttempted: make(map[appstate.WAPatchName]time.Time), + previousOwnLIDs: exsync.NewSetWithItems(login.Metadata.(*waid.UserLoginMetadata).PreviousOwnLIDs), onDemandHistorySyncRequests: make(map[types.JID]onDemandHistorySyncRequest), @@ -138,6 +139,9 @@ type WhatsAppClient struct { onDemandHistorySyncLock sync.Mutex onDemandHistorySyncRequests map[types.JID]onDemandHistorySyncRequest + + previousOwnLIDs *exsync.Set[types.JID] + previousOwnLIDsLock sync.Mutex } var ( @@ -206,7 +210,26 @@ func (wa *WhatsAppClient) IsThisUser(_ context.Context, userID networkid.UserID) 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) + (jid.Server == types.HiddenUserServer && jid.User == wa.GetLID().User) || + wa.previousOwnLIDs.Has(jid.ToNonAD()) +} + +func (wa *WhatsAppClient) addPreviousOwnLID(ctx context.Context, jid types.JID) { + if jid.Server != types.HiddenUserServer || wa.IsOwnJID(jid) { + return + } + jid = jid.ToNonAD() + wa.previousOwnLIDsLock.Lock() + defer wa.previousOwnLIDsLock.Unlock() + if !wa.previousOwnLIDs.Add(jid) { + return + } + zerolog.Ctx(ctx).Info().Stringer("lid", jid).Msg("Found previous own LID in history sync") + wa.UserLogin.Metadata.(*waid.UserLoginMetadata).PreviousOwnLIDs = wa.previousOwnLIDs.AsList() + err := wa.UserLogin.Save(ctx) + if err != nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to save user login after adding previous own LID") + } } func (wa *WhatsAppClient) GetLID() types.JID { diff --git a/pkg/waid/dbmeta.go b/pkg/waid/dbmeta.go index 15c0a422..b1a47e0e 100644 --- a/pkg/waid/dbmeta.go +++ b/pkg/waid/dbmeta.go @@ -45,6 +45,8 @@ type UserLoginMetadata struct { HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitzero"` ReachoutTimelockUntil time.Time `json:"reachout_timelock_until,omitzero"` + PreviousOwnLIDs []types.JID `json:"previous_own_lids,omitempty"` + Blocklist *types.Blocklist `json:"blocklist,omitempty"` MData json.RawMessage `json:"mdata,omitempty"` From 07d8e9417b3451f3adb38c48e8047e273c64c6e8 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Mon, 2 Nov 2026 11:14:43 +0000 Subject: [PATCH 471/486] client: build whatsmeow HTTP clients from bridge settings (#1016) --- pkg/connector/connector.go | 11 +++++++++- pkg/connector/proxy.go | 45 +++++++++++++++++++++++++------------- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 19caace5..80b9d95e 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -243,7 +243,16 @@ func (wa *WhatsAppConnector) GenerateTransactionID(_ id.UserID, _ id.RoomID, _ e } func (wa *WhatsAppConnector) ResetHTTPTransport() { - // No-op for now, whatsmeow doesn't use the shared transport config yet + for _, login := range wa.Bridge.GetAllCachedUserLogins() { + client := login.Client.(*WhatsAppClient).Client + if client == nil { + continue + } + err := wa.updateProxy(login.Log.WithContext(wa.Bridge.BackgroundCtx), client, false) + if err != nil { + login.Log.Err(err).Msg("Failed to recreate HTTP clients") + } + } } func (wa *WhatsAppConnector) ResetNetworkConnections() { diff --git a/pkg/connector/proxy.go b/pkg/connector/proxy.go index fe1d7ff5..8bd6d658 100644 --- a/pkg/connector/proxy.go +++ b/pkg/connector/proxy.go @@ -54,23 +54,38 @@ func (wa *WhatsAppConnector) getProxy(ctx context.Context, reason string) (strin } func (wa *WhatsAppConnector) updateProxy(ctx context.Context, client *whatsmeow.Client, isLogin bool) error { - if wa.Config.ProxyOnlyLogin && !isLogin { - return nil + var proxy string + if !wa.Config.ProxyOnlyLogin || isLogin { + reason := "connect" + if isLogin { + reason = "login" + } + var err error + proxy, err = wa.getProxy(ctx, reason) + if err != nil { + return fmt.Errorf("failed to get proxy address: %w", err) + } } - reason := "connect" - if isLogin { - reason = "login" + settings := wa.Bridge.GetHTTPClientSettings().WithGlobalTimeout(0) + if settings.HTTPProxy == nil { + settings.HTTPProxy = http.ProxyFromEnvironment } - if proxy, err := wa.getProxy(ctx, reason); err != nil { - return fmt.Errorf("failed to get proxy address: %w", err) - } else if proxy == "" { - return nil - } else if err = client.SetProxyAddress(proxy, whatsmeow.SetProxyOptions{ - OnlyLogin: wa.Config.ProxyOnlyLogin, - NoMedia: wa.Config.ProxyOnlyLogin, - }); err != nil { - return fmt.Errorf("failed to set proxy address: %w", err) + proxySettings := settings + if proxy != "" { + var err error + proxySettings, err = settings.WithProxy(proxy) + if err != nil { + return fmt.Errorf("failed to set proxy address: %w", err) + } + } + client.SetPreLoginHTTPClient(proxySettings.Compile()) + if wa.Config.ProxyOnlyLogin { + proxySettings = settings + } + client.SetWebsocketHTTPClient(proxySettings.Compile()) + client.SetMediaHTTPClient(proxySettings.WithResponseHeaderTimeout(0).Compile()) + if proxy != "" { + zerolog.Ctx(ctx).Debug().Msg("Enabled proxy") } - zerolog.Ctx(ctx).Debug().Msg("Enabled proxy") return nil } From 96ce0799be8c962d64cde63ab383a925d7a8fd84 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Mon, 2 Nov 2026 12:57:03 +0000 Subject: [PATCH 472/486] handlewhatsapp: don't retry full sync after a recovery response (#1018) --- pkg/connector/handlewhatsapp.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index a7d4c918..4391233b 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -970,7 +970,9 @@ func (wa *WhatsAppClient) handleWAAppStateSyncComplete(ctx context.Context, evt defer wa.appStateRecoveryLock.Unlock() meta := wa.UserLogin.Metadata.(*waid.UserLoginMetadata) if ts, exists := meta.AppStateRecoveryAttempted[evt.Name]; exists { - delete(wa.appStateFullSyncAttempted, evt.Name) + if !evt.Recovery { + delete(wa.appStateFullSyncAttempted, evt.Name) + } delete(meta.AppStateRecoveryAttempted, evt.Name) err := wa.UserLogin.Save(ctx) if err != nil { @@ -1008,7 +1010,7 @@ func (wa *WhatsAppClient) handleWAAppStateSyncError(ctx context.Context, evt *ev return } if !evt.FullSync { - if !lastFullSync.IsZero() { + if !lastFullSync.IsZero() && time.Since(lastFullSync) < 48*time.Hour { log.Debug(). Err(evt.Error). Time("last_full_sync_attempt", lastFullSync). From 4be45ddbea7d2aec2fe977cf3ea2d62c485a3a47 Mon Sep 17 00:00:00 2001 From: "indent[bot]" <297468268+indent[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2026 09:28:04 -0500 Subject: [PATCH 473/486] handlewhatsapp: send media retry edits as the original message sender (#1019) Co-authored-by: Indent --- pkg/connector/events.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/connector/events.go b/pkg/connector/events.go index 481d0d73..7f906e89 100644 --- a/pkg/connector/events.go +++ b/pkg/connector/events.go @@ -474,7 +474,9 @@ func (evt *WAMediaRetry) getRealSender() types.JID { func (evt *WAMediaRetry) GetSender() bridgev2.EventSender { realSender := pickLID(evt.getRealSender(), evt.senderLID) - return evt.wa.makeEventSender(evt.wa.Main.Bridge.BackgroundCtx, realSender) + sender := evt.wa.makeEventSender(evt.wa.Main.Bridge.BackgroundCtx, realSender) + sender.ForceEditOrigSender = true + return sender } func (evt *WAMediaRetry) GetTargetMessage() networkid.MessageID { From e097ba38de1f5d450013eb52b3136f28c7131dea Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Mon, 2 Nov 2026 15:14:20 +0000 Subject: [PATCH 474/486] userinfo: don't copy unauthorized avatar to alt ghost (#1020) --- pkg/connector/userinfo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index b109e82d..3d5499c0 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -480,7 +480,7 @@ func makeInfoFromGhost(ghost *bridgev2.Ghost) *bridgev2.UserInfo { IsBot: &ghost.IsBot, ExtraProfile: ghost.ExtraProfile, } - if ghost.AvatarID != "" { + if ghost.AvatarID != "" && ghost.AvatarID != "unauthorized" { info.Avatar = &bridgev2.Avatar{ ID: ghost.AvatarID, Remove: ghost.AvatarMXC == "", From 78c5ee3b207d982cab19bec7424279e3fc365071 Mon Sep 17 00:00:00 2001 From: "indent[bot]" <297468268+indent[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2026 19:41:06 -0500 Subject: [PATCH 475/486] backfill: expire unanswered on-demand history sync requests after an hour (#1021) Co-authored-by: Indent --- pkg/connector/backfill.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index fe251e1d..343b2405 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -799,10 +799,12 @@ type onDemandHistorySyncRequest struct { SentAt time.Time } +const onDemandHistorySyncRequestTimeout = 1 * time.Hour + func (wa *WhatsAppClient) startOnDemandHistorySyncRequest(ctx context.Context, portalJID types.JID, msgID types.MessageID) bool { wa.onDemandHistorySyncLock.Lock() defer wa.onDemandHistorySyncLock.Unlock() - if req, inFlight := wa.onDemandHistorySyncRequests[portalJID]; inFlight { + if req, inFlight := wa.onDemandHistorySyncRequests[portalJID]; inFlight && time.Since(req.SentAt) < onDemandHistorySyncRequestTimeout { zerolog.Ctx(ctx).Debug(). Str("in_flight_request_msg_id", req.ID). Stringer("portal_jid", portalJID). From 429320b373d2154cfdcca3e6981e2af9f7f00ee1 Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Tue, 3 Nov 2026 21:35:54 +0000 Subject: [PATCH 476/486] pre-commit: add bridgelint hook (#1022) --- .pre-commit-config.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1bfb5360..d6c78bb4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,9 +21,11 @@ repos: - id: go-mod-tidy - repo: https://github.com/beeper/pre-commit-go - rev: v0.4.2 + rev: v0.5.0 hooks: - id: zerolog-ban-msgf - id: zerolog-use-stringer - id: prevent-literal-http-methods - id: zerolog-ban-global-log + - id: bridgelint + verbose: true From 5ba69ab4ee9c916c488ada28c6086d7ec382225c Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Wed, 4 Nov 2026 16:24:07 +0000 Subject: [PATCH 477/486] backfill: drop history sync notifications whose blob is gone (#1023) --- pkg/connector/backfill.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index 343b2405..cbc7eec8 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -142,8 +142,16 @@ func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context, 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 { + if errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith403) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith404) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith410) { + log.Err(err).Msg("History sync blob is no longer available, dropping notification") + if err = wa.Main.DB.HSNotif.Delete(ctx, rowid); err != nil { + log.Err(err).Msg("Failed to delete queued history sync notification") + errored = true + } + return + } else if err != nil { log.Err(err).Msg("Failed to download history sync") + errored = true return } if blob.GetSyncType() == waHistorySync.HistorySync_ON_DEMAND { @@ -171,6 +179,7 @@ func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context, }) if err != nil { log.Err(err).Msg("Failed to store history sync notification data") + errored = true } else { resetTimer = blob.GetSyncType() == waHistorySync.HistorySync_INITIAL_BOOTSTRAP || blob.GetSyncType() == waHistorySync.HistorySync_RECENT || From 1d024590d2df575f1f2e184229ace51b0300ff70 Mon Sep 17 00:00:00 2001 From: "indent[bot]" <297468268+indent[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 19:16:41 -0500 Subject: [PATCH 478/486] userinfo: don't queue @bot ghosts for background resync (#1024) Co-authored-by: Indent --- pkg/connector/userinfo.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index 3d5499c0..b6594534 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -29,12 +29,15 @@ var ResyncLoopInterval = 4 * time.Hour var ResyncJitterSeconds = 3600 func (wa *WhatsAppClient) EnqueueGhostResync(ghost *bridgev2.Ghost) { + jid := waid.ParseUserID(ghost.ID) + if jid.Server == types.BotServer { + return + } lastSync := ghost.Metadata.(*waid.GhostMetadata).LastSync.Time if ghost.AvatarID != "" && lastSync.Add(ResyncMinInterval).After(time.Now()) { return } wa.resyncQueueLock.Lock() - jid := waid.ParseUserID(ghost.ID) if _, exists := wa.resyncQueue[jid]; !exists { wa.resyncQueue[jid] = resyncQueueItem{ghost: ghost} nextResyncIn := time.Until(wa.nextResync).String() From 03859bb3db0b7f597bf93f2f35a7306879b85dcc Mon Sep 17 00:00:00 2001 From: Nick Mills-Barrett Date: Thu, 5 Nov 2026 13:09:38 +0000 Subject: [PATCH 479/486] pre-commit: install bridgelint with the CI Go toolchain (#1030) --- .github/workflows/go.yml | 1 + .pre-commit-config.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index c9fef736..146b93a0 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -30,6 +30,7 @@ jobs: run: | go install golang.org/x/tools/cmd/goimports@latest go install honnef.co/go/tools/cmd/staticcheck@latest + go install github.com/beeper/pre-commit-go/bridgelint@latest export PATH="$HOME/go/bin:$PATH" - name: Run pre-commit diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d6c78bb4..1c96b066 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,4 +28,5 @@ repos: - id: prevent-literal-http-methods - id: zerolog-ban-global-log - id: bridgelint + language: system verbose: true From 81f64557ea4f8535a4323135f42569b8e26646a5 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 5 Nov 2026 09:16:26 -0500 Subject: [PATCH 480/486] handlematrix: ignore thread read receipts Closes #1028 --- go.mod | 2 +- go.sum | 4 ++-- pkg/connector/handlematrix.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 6e9b5033..f940c1de 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( golang.org/x/sync v0.24.0 google.golang.org/protobuf v1.36.12 gopkg.in/yaml.v3 v3.0.1 - maunium.net/go/mautrix v0.32.1-0.20261031224934-b69deb427bd7 + maunium.net/go/mautrix v0.32.1-0.20261105141340-842796acf445 ) require ( diff --git a/go.sum b/go.sum index 12cf8f53..fb4e878f 100644 --- a/go.sum +++ b/go.sum @@ -103,5 +103,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= -maunium.net/go/mautrix v0.32.1-0.20261031224934-b69deb427bd7 h1:GpLIqeJTpG0/ti7XA370HgjnSWd/pQxXVGb/FRkayOA= -maunium.net/go/mautrix v0.32.1-0.20261031224934-b69deb427bd7/go.mod h1:tZSwPzw+/0pa0dxGufkh0uJqM84BNxB6e2I0+S0UyjU= +maunium.net/go/mautrix v0.32.1-0.20261105141340-842796acf445 h1:ZI9Y2YEmjqQDloOCiciHeynTRYjUwK0ZZbNcRH/5NOc= +maunium.net/go/mautrix v0.32.1-0.20261105141340-842796acf445/go.mod h1:tZSwPzw+/0pa0dxGufkh0uJqM84BNxB6e2I0+S0UyjU= diff --git a/pkg/connector/handlematrix.go b/pkg/connector/handlematrix.go index d51efcd2..84198506 100644 --- a/pkg/connector/handlematrix.go +++ b/pkg/connector/handlematrix.go @@ -319,7 +319,7 @@ func (wa *WhatsAppClient) HandleMatrixMessageRemove(ctx context.Context, msg *br } func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt *bridgev2.MatrixReadReceipt) (retErr error) { - if !receipt.ReadUpTo.After(receipt.LastRead) { + if receipt.Receipt.IsThread() || !receipt.ReadUpTo.After(receipt.LastRead) { return nil } if receipt.LastRead.IsZero() { From 5640ef311b5aef90ee0d35766ec9c9bf167d25af Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Thu, 5 Nov 2026 15:00:20 +0000 Subject: [PATCH 481/486] muse: mark WASA resync as done on any error (#1026) --- pkg/connector/muse.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/connector/muse.go b/pkg/connector/muse.go index ee6b04d9..4a589ce7 100644 --- a/pkg/connector/muse.go +++ b/pkg/connector/muse.go @@ -4,7 +4,6 @@ import ( "context" "encoding/base64" "encoding/json" - "errors" "fmt" "runtime/debug" "time" @@ -57,9 +56,9 @@ func (wa *WhatsAppClient) resyncWASARootSecrets(ctx context.Context) { success := true if err := wa.Client.FetchAppState(ctx, appstate.WAPatchRegularHigh, true, false); err != nil { log.Err(err).Msg("Failed to resync WASA root secrets") - // Mark the sync as completed on mismatching LTHash errors, because those can get permanently stuck - // (retrying won't help). In case it's not stuck, a single call should be enough to request a resync. - if !errors.Is(err, appstate.ErrMismatchingLTHash) || ctx.Err() != nil { + // Mark the sync as completed on errors too, because some get permanently stuck (mismatching LTHash, + // expired patch blobs), and a failed full sync already requests app state recovery from the phone. + if ctx.Err() != nil { return } success = false From d574a67a2d22a15af72d953ff5c2d44adfe8562c Mon Sep 17 00:00:00 2001 From: "indent[bot]" <297468268+indent[bot]@users.noreply.github.com> Date: Thu, 5 Nov 2026 15:33:02 +0000 Subject: [PATCH 482/486] userinfo: query only the LID when both JIDs of a contact are queued for background resync (#1029) Co-authored-by: Indent --- pkg/connector/userinfo.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index b6594534..387971e9 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -119,6 +119,7 @@ func (wa *WhatsAppClient) doGhostResync(ctx context.Context, queue map[types.JID var ghostJIDs []types.JID var ghosts []*bridgev2.Ghost var portals []*bridgev2.Portal + selectedGhosts := make(map[types.JID]*bridgev2.Ghost) for jid, item := range queue { var lastSync time.Time unknownAvatar := false @@ -136,12 +137,23 @@ func (wa *WhatsAppClient) doGhostResync(ctx context.Context, queue map[types.JID continue } if item.ghost != nil { - ghosts = append(ghosts, item.ghost) - ghostJIDs = append(ghostJIDs, jid) + selectedGhosts[jid] = item.ghost } else if item.portal != nil { portals = append(portals, item.portal) } } + for jid, ghost := range selectedGhosts { + if jid.Server == types.DefaultUserServer { + lid, err := wa.GetStore().LIDs.GetLIDForPN(ctx, jid) + if err != nil { + log.Warn().Err(err).Stringer("jid", jid).Msg("Failed to get LID for background sync") + } else if _, lidSelected := selectedGhosts[lid]; lidSelected { + continue + } + } + ghosts = append(ghosts, ghost) + ghostJIDs = append(ghostJIDs, jid) + } for _, portal := range portals { wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{ EventMeta: simplevent.EventMeta{ From ac29fa5272ed896586320f92dfa63d305883f895 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Fri, 6 Nov 2026 20:37:55 +0000 Subject: [PATCH 483/486] lidmigrate: check homeserver membership when removing rejoined PN ghosts (#1025) --- pkg/connector/lidmigrate.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/pkg/connector/lidmigrate.go b/pkg/connector/lidmigrate.go index 17b24287..4295be63 100644 --- a/pkg/connector/lidmigrate.go +++ b/pkg/connector/lidmigrate.go @@ -300,7 +300,11 @@ func (wa *WhatsAppConnector) syncMismatchingGhosts(ctx context.Context) error { } func (wa *WhatsAppConnector) removeRejoinedPNGhosts(ctx context.Context) error { - if wa.Bridge.Background || wa.Bridge.DB.KV.Get(ctx, "whatsapp_lid_dm_pn_ghosts_removed") == "true" { + if wa.Bridge.Background || wa.Bridge.DB.KV.Get(ctx, "whatsapp_lid_dm_pn_ghosts_removed_v2") == "true" { + return nil + } + stateGetter, ok := wa.Bridge.Matrix.(bridgev2.MatrixConnectorWithArbitraryRoomState) + if !ok { return nil } log := zerolog.Ctx(ctx).With().Str("action", "remove rejoined pn ghosts").Logger() @@ -322,12 +326,14 @@ func (wa *WhatsAppConnector) removeRejoinedPNGhosts(ctx context.Context) error { continue } pnUserID := waid.MakeUserID(pn) - member, err := wa.Bridge.Matrix.GetMemberInfo(ctx, portal.MXID, wa.Bridge.Matrix.FormatGhostMXID(pnUserID)) - if err != nil { + memberEvt, err := stateGetter.GetStateEvent(ctx, portal.MXID, event.StateMember, wa.Bridge.Matrix.FormatGhostMXID(pnUserID).String()) + if errors.Is(err, mautrix.MNotFound) || errors.Is(err, mautrix.MForbidden) { + continue + } else if err != nil { log.Warn().Err(err).Stringer("room_id", portal.MXID).Msg("Failed to get PN ghost membership") failed++ continue - } else if member == nil || member.Membership != event.MembershipJoin { + } else if memberEvt == nil || memberEvt.Content.AsMember().Membership != event.MembershipJoin { continue } pnGhost, err := wa.Bridge.GetGhostByID(ctx, pnUserID) @@ -345,6 +351,12 @@ func (wa *WhatsAppConnector) removeRejoinedPNGhosts(ctx context.Context) error { } continue } + log.Debug(). + Stringer("room_id", portal.MXID). + Stringer("pn", pn). + Stringer("join_sender", memberEvt.Sender). + Time("join_ts", time.UnixMilli(memberEvt.Timestamp)). + Msg("Removed PN ghost from LID DM") removed++ } log.Info(). @@ -353,7 +365,7 @@ func (wa *WhatsAppConnector) removeRejoinedPNGhosts(ctx context.Context) error { Int("total_lid_portals", len(lidPortals)). Msg("Finished removing rejoined PN ghosts from LID DMs") if failed == 0 && ctx.Err() == nil { - wa.Bridge.DB.KV.Set(ctx, "whatsapp_lid_dm_pn_ghosts_removed", "true") + wa.Bridge.DB.KV.Set(ctx, "whatsapp_lid_dm_pn_ghosts_removed_v2", "true") } return nil } From d9b0daca891ac69226931eba5594e0f490aaee51 Mon Sep 17 00:00:00 2001 From: "indent[bot]" <297468268+indent[bot]@users.noreply.github.com> Date: Sat, 7 Nov 2026 09:46:19 -0500 Subject: [PATCH 484/486] msgconv/rich-response: only embed raw rich message JSON if it's canonical (#1033) Co-authored-by: Indent --- pkg/msgconv/wa-rich-response.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/msgconv/wa-rich-response.go b/pkg/msgconv/wa-rich-response.go index 28a648c3..e506a64e 100644 --- a/pkg/msgconv/wa-rich-response.go +++ b/pkg/msgconv/wa-rich-response.go @@ -29,6 +29,7 @@ import ( "github.com/yuin/goldmark/extension" "go.mau.fi/whatsmeow/types/richresponse" "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/crypto/canonicaljson" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/format" "maunium.net/go/mautrix/format/mdext" @@ -36,7 +37,8 @@ import ( func (mc *MessageConverter) convertUnifiedRichResponseMessage(ctx context.Context, data []byte) *bridgev2.ConvertedMessagePart { extra := map[string]any{} - if json.Valid(data) && len(data) < 24*1024 { + _, canonicalErr := canonicaljson.Marshal(json.RawMessage(data)) + if canonicalErr == nil && len(data) < 24*1024 { extra["fi.mau.whatsapp.rich_message"] = json.RawMessage(data) } else if len(data) < 16*1024 { extra["fi.mau.whatsapp.non_json_rich_message"] = base64.StdEncoding.EncodeToString(data) From 5c8c1fc8358c011891fbf6066f289b9ad5fee223 Mon Sep 17 00:00:00 2001 From: "indent[bot]" <297468268+indent[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2026 18:09:28 +0000 Subject: [PATCH 485/486] handlewhatsapp: ignore HD replacement messages without replacement content (#1037) Co-authored-by: Indent --- pkg/connector/handlewhatsapp.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/connector/handlewhatsapp.go b/pkg/connector/handlewhatsapp.go index 4391233b..e6174b92 100644 --- a/pkg/connector/handlewhatsapp.go +++ b/pkg/connector/handlewhatsapp.go @@ -374,6 +374,14 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa if assocType := messageAssoc.GetAssociationType(); assocType == waE2E.MessageAssociation_HD_IMAGE_DUAL_UPLOAD || assocType == waE2E.MessageAssociation_HD_VIDEO_DUAL_UPLOAD { parentKey := messageAssoc.GetParentMessageKey() protocolMsg, shouldHideEdit := makeHDMediaReplacementEdit(evt.Message, parentKey) + if protocolMsg.GetEditedMessage() == nil { + wa.UserLogin.Log.Debug(). + Str("message_id", evt.Info.ID). + Str("parent_id", parentKey.GetID()). + Stringer("assoc_type", assocType). + Msg("Ignoring HD replacement message without replacement content") + return + } dontRenderEdited = shouldHideEdit wa.UserLogin.Log.Debug(). Str("message_id", evt.Info.ID). From 945474343fcd7579c3d273acb78f6c60c957e2e0 Mon Sep 17 00:00:00 2001 From: "indent[bot]" <297468268+indent[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2026 18:09:56 +0000 Subject: [PATCH 486/486] backfill: log stub and message type of unparseable history messages (#1034) Co-authored-by: Indent --- pkg/connector/backfill.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/connector/backfill.go b/pkg/connector/backfill.go index cbc7eec8..8f006e0b 100644 --- a/pkg/connector/backfill.go +++ b/pkg/connector/backfill.go @@ -298,6 +298,9 @@ func (wa *WhatsAppClient) handleWAHistorySync( Int("msg_index", i). Str("msg_id", rawMsg.GetMessage().GetKey().GetID()). Uint64("msg_time_seconds", rawMsg.GetMessage().GetMessageTimestamp()). + Stringer("stub_type", rawMsg.GetMessage().GetMessageStubType()). + Str("message_type", getMessageType(rawMsg.GetMessage().GetMessage())). + Bool("from_me", rawMsg.GetMessage().GetKey().GetFromMe()). Msg("Dropping historical message due to parse error") log.Trace(). Any("web_message_info", rawMsg.GetMessage()).