mirror of
https://github.com/mautrix/whatsapp.git
synced 2026-09-16 15:52:06 -04:00
Compare commits
38 changed files with 544 additions and 1051 deletions
8
.github/workflows/go.yml
vendored
8
.github/workflows/go.yml
vendored
|
|
@ -11,14 +11,14 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
go-version: ["1.26", "1.27"]
|
go-version: ["1.25", "1.26"]
|
||||||
name: Lint ${{ matrix.go-version == '1.27' && '(latest)' || '(old)' }}
|
name: Lint ${{ matrix.go-version == '1.26' && '(latest)' || '(old)' }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v7
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
cache: true
|
cache: true
|
||||||
|
|
|
||||||
22
CHANGELOG.md
22
CHANGELOG.md
|
|
@ -1,25 +1,3 @@
|
||||||
# v26.09
|
|
||||||
|
|
||||||
* 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.
|
|
||||||
* 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
|
|
||||||
|
|
||||||
* Switched direct chats to use LIDs instead of phone numbers.
|
|
||||||
|
|
||||||
# v26.07
|
# v26.07
|
||||||
|
|
||||||
* Updated Docker image to Alpine 3.24.
|
* Updated Docker image to Alpine 3.24.
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
See <https://docs.mau.fi/bridges/general/contributing.html>
|
|
||||||
|
|
@ -18,7 +18,7 @@ var m = mxmain.BridgeMain{
|
||||||
Name: "mautrix-whatsapp",
|
Name: "mautrix-whatsapp",
|
||||||
URL: "https://github.com/mautrix/whatsapp",
|
URL: "https://github.com/mautrix/whatsapp",
|
||||||
Description: "A Matrix-WhatsApp puppeting bridge.",
|
Description: "A Matrix-WhatsApp puppeting bridge.",
|
||||||
Version: "26.09",
|
Version: "26.07",
|
||||||
SemCalVer: true,
|
SemCalVer: true,
|
||||||
Connector: &connector.WhatsAppConnector{},
|
Connector: &connector.WhatsAppConnector{},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
cmd/mautrix-whatsapp/plugin.go
Normal file
24
cmd/mautrix-whatsapp/plugin.go
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
34
go.mod
34
go.mod
|
|
@ -1,8 +1,8 @@
|
||||||
module go.mau.fi/mautrix-whatsapp
|
module go.mau.fi/mautrix-whatsapp
|
||||||
|
|
||||||
go 1.26.0
|
go 1.25.0
|
||||||
|
|
||||||
toolchain go1.27.1
|
toolchain go1.26.5
|
||||||
|
|
||||||
tool go.mau.fi/util/cmd/maubuild
|
tool go.mau.fi/util/cmd/maubuild
|
||||||
|
|
||||||
|
|
@ -10,15 +10,15 @@ require (
|
||||||
github.com/lib/pq v1.12.3
|
github.com/lib/pq v1.12.3
|
||||||
github.com/rs/zerolog v1.35.1
|
github.com/rs/zerolog v1.35.1
|
||||||
github.com/tidwall/gjson v1.19.0
|
github.com/tidwall/gjson v1.19.0
|
||||||
go.mau.fi/util v0.10.1
|
go.mau.fi/util v0.9.11
|
||||||
go.mau.fi/webp v0.3.0
|
go.mau.fi/webp v0.3.0
|
||||||
go.mau.fi/whatsmeow v0.0.0-20260916100317-2375e1751bbd
|
go.mau.fi/whatsmeow v0.0.0-20260716095330-85d99080dee8
|
||||||
golang.org/x/image v0.46.0
|
golang.org/x/image v0.44.0
|
||||||
golang.org/x/net v0.59.0
|
golang.org/x/net v0.57.0
|
||||||
golang.org/x/sync v0.23.0
|
golang.org/x/sync v0.22.0
|
||||||
google.golang.org/protobuf v1.36.12
|
google.golang.org/protobuf v1.36.11
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
maunium.net/go/mautrix v0.31.0
|
maunium.net/go/mautrix v0.29.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
@ -31,8 +31,8 @@ require (
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.52 // indirect
|
github.com/mattn/go-sqlite3 v1.14.48 // indirect
|
||||||
github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 // indirect
|
github.com/petermattis/goid v0.0.0-20260713124913-97594f28f5ca // indirect
|
||||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||||
github.com/rs/xid v1.6.0 // indirect
|
github.com/rs/xid v1.6.0 // indirect
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // 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/pretty v1.2.1 // indirect
|
||||||
github.com/tidwall/sjson v1.2.5 // indirect
|
github.com/tidwall/sjson v1.2.5 // indirect
|
||||||
github.com/vektah/gqlparser/v2 v2.5.27 // indirect
|
github.com/vektah/gqlparser/v2 v2.5.27 // indirect
|
||||||
github.com/yuin/goldmark v1.8.6 // indirect
|
github.com/yuin/goldmark v1.8.4 // indirect
|
||||||
go.mau.fi/libsignal v0.2.2 // indirect
|
go.mau.fi/libsignal v0.2.2 // indirect
|
||||||
go.mau.fi/zeroconfig v0.2.0 // indirect
|
go.mau.fi/zeroconfig v0.2.0 // indirect
|
||||||
golang.org/x/crypto v0.57.0 // indirect
|
golang.org/x/crypto v0.54.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba // indirect
|
golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597 // indirect
|
||||||
golang.org/x/mod v0.41.0 // indirect
|
golang.org/x/mod v0.38.0 // indirect
|
||||||
golang.org/x/sys v0.48.0 // indirect
|
golang.org/x/sys v0.47.0 // indirect
|
||||||
golang.org/x/text v0.42.0 // indirect
|
golang.org/x/text v0.40.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
maunium.net/go/mauflag v1.0.0 // indirect
|
maunium.net/go/mauflag v1.0.0 // indirect
|
||||||
|
|
|
||||||
68
go.sum
68
go.sum
|
|
@ -13,6 +13,8 @@ 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 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
|
||||||
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
|
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/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 h1:j4DJ5ObEmMBt/lcwIecKcoRxIQUEnw0L804lXYDt/pg=
|
||||||
github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo=
|
github.com/elliotchance/orderedmap/v3 v3.1.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
|
@ -32,11 +34,13 @@ 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-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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
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.48 h1:7XHIgl0a8HwOaiK4E47ozLkST78rR9+OtNGx27D/TFs=
|
||||||
github.com/mattn/go-sqlite3 v1.14.52/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=
|
github.com/mattn/go-sqlite3 v1.14.48/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=
|
||||||
github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 h1:lcWAnrqr2nNfDiArwFNHCE4787Mw2tCdVSOXCru0/0E=
|
github.com/petermattis/goid v0.0.0-20260713124913-97594f28f5ca h1:GHSUVE4yOgX4E7kTRzpxCPbCOYkd3Kj8Dgdod30OI1E=
|
||||||
github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
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/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.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 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
|
|
@ -48,8 +52,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/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 h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||||
github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw=
|
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.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=
|
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.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
|
||||||
|
|
@ -63,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/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 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTdwFp0s=
|
||||||
github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
|
github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
|
||||||
github.com/yuin/goldmark v1.8.6 h1:d0VcaP1sx9GkFVkoW+KtggpGi2KZ965i14b0+bDQST4=
|
github.com/yuin/goldmark v1.8.4 h1:oat/nd3U6NeQqFEL3xpEJq7d7c86NI+DbSNGAs4xnjA=
|
||||||
github.com/yuin/goldmark v1.8.6/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
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 h1:QV+XdzQkm3x3aSG7FcqfGSZuFXz83pRZPBFaPygHbOU=
|
||||||
go.mau.fi/libsignal v0.2.2/go.mod h1:CRlIQg2J8uYTfDFvNoO8/KcZjs5cey0vbc6oj/bssY0=
|
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.9.11 h1:Cus1Lu/t7d3OG6VF4aYWvlUUS0Q4O1/lcpPNJZ0jsw0=
|
||||||
go.mau.fi/util v0.10.1/go.mod h1:40TDo7/ekSeOjgr8KAmX31Yf4zrOF94j83WQB+u5ZPc=
|
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 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA=
|
||||||
go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo=
|
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-20260716095330-85d99080dee8 h1:7RQA3v4pCZcmgHaEQXKfHKLVSqPThizkApt6Uw+DcA8=
|
||||||
go.mau.fi/whatsmeow v0.0.0-20260916100317-2375e1751bbd/go.mod h1:7G7AeRACrC8Se+01+SQbdOp2J/Ce0DUMGxTKFKfRHW4=
|
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 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU=
|
||||||
go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w=
|
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.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
||||||
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
|
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
||||||
golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba h1:Ck8QetSgk912qxWLMCKxd0in+aiyBQyDSMae6e/xmpU=
|
golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597 h1:qLvzZeaANDgyVOA8pyHCOStGlXn0rseXma+GQjeuv2g=
|
||||||
golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba/go.mod h1:50RgIsmK7OwqzTTeqcSXQW8SswW0o8fRcDxmqGluJ8E=
|
golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q=
|
||||||
golang.org/x/image v0.46.0 h1:b1+oYj0Jbp6K5MDT4i4/eZpYlk3V8SJhhDKh6LBHAyQ=
|
golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I=
|
||||||
golang.org/x/image v0.46.0/go.mod h1:3B3W05VGVQyuXucLINLjXKrqISASfi4Xj+iCVkLMwew=
|
golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY=
|
||||||
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
|
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
|
||||||
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
|
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
|
||||||
golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
|
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||||
golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||||
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
|
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||||
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
|
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||||
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
|
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||||
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
|
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||||
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
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 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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|
@ -103,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=
|
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 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
|
||||||
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
|
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.29.0 h1:OkcBJF1dvp+93EgahxMxOUZZOrGTYculI9IprvRIMOQ=
|
||||||
maunium.net/go/mautrix v0.31.0/go.mod h1:vfPYtoGAjlTnIF+W8tl+rjX3yB4PEfqYR2sDXvQo4Co=
|
maunium.net/go/mautrix v0.29.0/go.mod h1:LynuVr8N9nWsE1N4WAE+vItRACDB1pt9M3gN4SIBpeY=
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"go.mau.fi/util/exmaps"
|
|
||||||
"go.mau.fi/util/ptr"
|
"go.mau.fi/util/ptr"
|
||||||
"go.mau.fi/whatsmeow"
|
"go.mau.fi/whatsmeow"
|
||||||
"go.mau.fi/whatsmeow/proto/waE2E"
|
"go.mau.fi/whatsmeow/proto/waE2E"
|
||||||
|
|
@ -229,32 +228,30 @@ func (wa *WhatsAppClient) handleWAHistorySync(
|
||||||
} else {
|
} else {
|
||||||
totalMessageCount += len(conv.GetMessages())
|
totalMessageCount += len(conv.GetMessages())
|
||||||
}
|
}
|
||||||
if jid.Server == types.DefaultUserServer {
|
if jid.Server == types.HiddenUserServer {
|
||||||
lid, err := wa.GetStore().LIDs.GetLIDForPN(ctx, jid)
|
pn, err := wa.GetStore().LIDs.GetPNForLID(ctx, jid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Stringer("pn", jid).Msg("Failed to get LID for phone number in history sync")
|
log.Err(err).Stringer("lid", jid).Msg("Failed to get PN for LID in history sync")
|
||||||
} else if lid.IsEmpty() {
|
} else if pn.IsEmpty() {
|
||||||
log.Warn().Stringer("pn", jid).Msg("No LID found for phone number in history sync")
|
log.Warn().Stringer("lid", jid).Msg("No PN found for LID in history sync")
|
||||||
} else {
|
} else {
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Stringer("lid", lid).
|
Stringer("lid", jid).
|
||||||
Stringer("pn", jid).
|
Stringer("pn", pn).
|
||||||
Msg("Rerouting phone number DM to LID in history sync")
|
Msg("Rerouting LID DM to phone number in history sync")
|
||||||
jid = lid
|
jid = pn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||||
return c.Stringer("chat_jid", jid)
|
return c.Stringer("chat_jid", jid)
|
||||||
})
|
})
|
||||||
|
|
||||||
var firstItemTime, lastItemTime time.Time
|
var minTime, maxTime, firstItemTime, lastItemTime time.Time
|
||||||
|
var minTimeIndex, maxTimeIndex int
|
||||||
|
|
||||||
ignoredTypes := 0
|
ignoredTypes := 0
|
||||||
rawMessages := conv.GetMessages()
|
messages := make([]*wadb.HistorySyncMessageTuple, 0, len(conv.GetMessages()))
|
||||||
messages := make([]*wadb.HistorySyncMessageTuple, 0, len(rawMessages))
|
for i, rawMsg := range conv.GetMessages() {
|
||||||
allowClamp := conv.GetCommentsCount() == 0
|
|
||||||
var newerTS uint64
|
|
||||||
for i, rawMsg := range rawMessages {
|
|
||||||
// Don't store messages that will just be skipped.
|
// Don't store messages that will just be skipped.
|
||||||
msgEvt, err := wa.Client.ParseWebMessage(jid, rawMsg.GetMessage())
|
msgEvt, err := wa.Client.ParseWebMessage(jid, rawMsg.GetMessage())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -272,30 +269,20 @@ func (wa *WhatsAppClient) handleWAHistorySync(
|
||||||
firstItemTime = msgEvt.Info.Timestamp
|
firstItemTime = msgEvt.Info.Timestamp
|
||||||
}
|
}
|
||||||
lastItemTime = 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)
|
msgType := getMessageType(msgEvt.Message)
|
||||||
if msgType == "ignore" || strings.HasPrefix(msgType, "unknown_protocol_") {
|
if msgType == "ignore" || strings.HasPrefix(msgType, "unknown_protocol_") {
|
||||||
ignoredTypes++
|
ignoredTypes++
|
||||||
continue
|
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)
|
marshaled, err := proto.Marshal(rawMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).
|
log.Warn().Err(err).
|
||||||
|
|
@ -310,8 +297,13 @@ func (wa *WhatsAppClient) handleWAHistorySync(
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Int("wrapped_count", len(messages)).
|
Int("wrapped_count", len(messages)).
|
||||||
Int("ignored_msg_type_count", ignoredTypes).
|
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("first_item_time", firstItemTime).
|
||||||
Time("last_item_time", lastItemTime).
|
Time("last_item_time", lastItemTime).
|
||||||
|
Bool("highest_time_mismatch", firstItemTime != maxTime).
|
||||||
Dict("metadata", zerolog.Dict().
|
Dict("metadata", zerolog.Dict().
|
||||||
Uint32("ephemeral_expiration", conv.GetEphemeralExpiration()).
|
Uint32("ephemeral_expiration", conv.GetEphemeralExpiration()).
|
||||||
Int64("ephemeral_setting_timestamp", conv.GetEphemeralSettingTimestamp()).
|
Int64("ephemeral_setting_timestamp", conv.GetEphemeralSettingTimestamp()).
|
||||||
|
|
@ -327,7 +319,7 @@ func (wa *WhatsAppClient) handleWAHistorySync(
|
||||||
Msg("Collected messages to save from history sync conversation")
|
Msg("Collected messages to save from history sync conversation")
|
||||||
|
|
||||||
if len(messages) > 0 {
|
if len(messages) > 0 {
|
||||||
err = wa.Main.DB.Conversation.Put(ctx, wadb.NewConversation(wa.UserLogin.ID, jid, conv, firstItemTime))
|
err = wa.Main.DB.Conversation.Put(ctx, wadb.NewConversation(wa.UserLogin.ID, jid, conv, maxTime))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if stopOnError {
|
if stopOnError {
|
||||||
return 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)
|
||||||
|
|
@ -482,9 +474,6 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 markRead bool
|
||||||
var startTime, endTime *time.Time
|
var startTime, endTime *time.Time
|
||||||
var conv *wadb.Conversation
|
var conv *wadb.Conversation
|
||||||
|
|
@ -532,7 +521,6 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet
|
||||||
return nil, fmt.Errorf("failed to load messages from database: %w", err)
|
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) {
|
} else if len(messages) == 0 || (len(messages) == 1 && anchorID != "" && messages[0].GetKey().GetID() == anchorID) {
|
||||||
wa.deleteHistorySyncMessages(ctx, portalJID, 0, 0)
|
wa.deleteHistorySyncMessages(ctx, portalJID, 0, 0)
|
||||||
hasMore = hasMore && params.AnchorMessage != nil
|
|
||||||
if hasMore && !params.AllowSlowFetch {
|
if hasMore && !params.AllowSlowFetch {
|
||||||
return &bridgev2.FetchMessagesResponse{
|
return &bridgev2.FetchMessagesResponse{
|
||||||
MoreRequiresSlowFetch: true,
|
MoreRequiresSlowFetch: true,
|
||||||
|
|
@ -540,7 +528,7 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet
|
||||||
Forward: params.Forward,
|
Forward: params.Forward,
|
||||||
}, nil
|
}, nil
|
||||||
} else if hasMore {
|
} else if hasMore {
|
||||||
return wa.fetchMessagesFromPhone(ctx, portalJID, params)
|
return wa.fetchMessagesFromPhone(ctx, params)
|
||||||
}
|
}
|
||||||
return &bridgev2.FetchMessagesResponse{
|
return &bridgev2.FetchMessagesResponse{
|
||||||
HasMore: false,
|
HasMore: false,
|
||||||
|
|
@ -602,14 +590,10 @@ func (wa *WhatsAppClient) convertHistorySyncMessages(
|
||||||
messages []*waWeb.WebMessageInfo,
|
messages []*waWeb.WebMessageInfo,
|
||||||
explodeOnError bool,
|
explodeOnError bool,
|
||||||
) (*bridgev2.FetchMessagesResponse, error) {
|
) (*bridgev2.FetchMessagesResponse, error) {
|
||||||
if wa.Client == nil {
|
|
||||||
return nil, bridgev2.ErrNotLoggedIn
|
|
||||||
}
|
|
||||||
oldestTS := messages[len(messages)-1].GetMessageTimestamp()
|
oldestTS := messages[len(messages)-1].GetMessageTimestamp()
|
||||||
newestTS := messages[0].GetMessageTimestamp()
|
newestTS := messages[0].GetMessageTimestamp()
|
||||||
convertedMessages := make([]*bridgev2.BackfillMessage, 0, len(messages))
|
convertedMessages := make([]*bridgev2.BackfillMessage, 0, len(messages))
|
||||||
var mediaRequests []*wadb.MediaRequest
|
var mediaRequests []*wadb.MediaRequest
|
||||||
dups := make(exmaps.Set[networkid.MessageID])
|
|
||||||
for i, msg := range messages {
|
for i, msg := range messages {
|
||||||
evt, err := wa.Client.ParseWebMessage(portalJID, msg)
|
evt, err := wa.Client.ParseWebMessage(portalJID, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -633,18 +617,10 @@ func (wa *WhatsAppClient) convertHistorySyncMessages(
|
||||||
continue
|
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
|
isViewOnce := evt.IsViewOnce || evt.IsViewOnceV2 || evt.IsViewOnceV2Extension
|
||||||
converted, mediaReq := wa.convertHistorySyncMessage(
|
converted, mediaReq := wa.convertHistorySyncMessage(
|
||||||
ctx, portal, &evt.Info, evt.Message, evt.RawMessage, isViewOnce, msg.Reactions,
|
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)
|
convertedMessages = append(convertedMessages, converted)
|
||||||
if mediaReq != nil {
|
if mediaReq != nil {
|
||||||
mediaRequests = append(mediaRequests, mediaReq)
|
mediaRequests = append(mediaRequests, mediaReq)
|
||||||
|
|
@ -675,7 +651,7 @@ func (wa *WhatsAppClient) convertHistorySyncMessages(
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, portalJID types.JID, params bridgev2.FetchMessagesParams) (*bridgev2.FetchMessagesResponse, error) {
|
func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, params bridgev2.FetchMessagesParams) (*bridgev2.FetchMessagesResponse, error) {
|
||||||
if params.AnchorMessage == nil {
|
if params.AnchorMessage == nil {
|
||||||
return nil, fmt.Errorf("anchor message is required to fetch messages from phone")
|
return nil, fmt.Errorf("anchor message is required to fetch messages from phone")
|
||||||
}
|
}
|
||||||
|
|
@ -687,9 +663,9 @@ func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, portalJID
|
||||||
msgID := wa.Client.GenerateMessageID()
|
msgID := wa.Client.GenerateMessageID()
|
||||||
reqData := wa.Client.BuildHistorySyncRequest(&types.MessageInfo{
|
reqData := wa.Client.BuildHistorySyncRequest(&types.MessageInfo{
|
||||||
MessageSource: types.MessageSource{
|
MessageSource: types.MessageSource{
|
||||||
Chat: portalJID,
|
Chat: parsed.Chat,
|
||||||
Sender: parsed.Sender,
|
Sender: parsed.Sender,
|
||||||
IsFromMe: wa.IsOwnJID(parsed.Sender),
|
IsFromMe: parsed.Sender.ToNonAD() == wa.JID.ToNonAD() || parsed.Sender.ToNonAD() == wa.Device.GetLID().ToNonAD(),
|
||||||
IsGroup: parsed.Chat.Server == types.GroupServer,
|
IsGroup: parsed.Chat.Server == types.GroupServer,
|
||||||
},
|
},
|
||||||
ID: parsed.ID,
|
ID: parsed.ID,
|
||||||
|
|
@ -697,7 +673,6 @@ func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, portalJID
|
||||||
}, 50)
|
}, 50)
|
||||||
zerolog.Ctx(ctx).Debug().
|
zerolog.Ctx(ctx).Debug().
|
||||||
Str("request_msg_id", msgID).
|
Str("request_msg_id", msgID).
|
||||||
Stringer("portal_jid", portalJID).
|
|
||||||
Any("anchor_msg_parsed", parsed).
|
Any("anchor_msg_parsed", parsed).
|
||||||
Any("request_data", reqData).
|
Any("request_data", reqData).
|
||||||
Msg("Sending history sync request")
|
Msg("Sending history sync request")
|
||||||
|
|
@ -726,20 +701,6 @@ func (wa *WhatsAppClient) handleOnDemandHistorySync(ctx context.Context, blob *w
|
||||||
zerolog.Ctx(ctx).Err(err).Str("jid", conv.GetID()).Msg("Failed to parse portal JID")
|
zerolog.Ctx(ctx).Err(err).Str("jid", conv.GetID()).Msg("Failed to parse portal JID")
|
||||||
continue
|
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))
|
portal, err := wa.Main.Bridge.GetPortalByKey(ctx, wa.makeWAPortalKey(portalJID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Stringer("portal_jid", portalJID).Msg("Failed to get portal for on-demand history sync")
|
zerolog.Ctx(ctx).Err(err).Stringer("portal_jid", portalJID).Msg("Failed to get portal for on-demand history sync")
|
||||||
|
|
@ -788,12 +749,11 @@ func (wa *WhatsAppClient) convertHistorySyncMessage(
|
||||||
}
|
}
|
||||||
// TODO use proper intent
|
// TODO use proper intent
|
||||||
intent := wa.Main.Bridge.Bot
|
intent := wa.Main.Bridge.Bot
|
||||||
msgID := waid.MakeMessageIDWithAltSender(info.Chat, info.Sender, info.SenderAlt, info.ID)
|
|
||||||
wrapped := &bridgev2.BackfillMessage{
|
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, pickLID(info.Sender, info.SenderAlt)),
|
Sender: wa.makeEventSender(ctx, info.Sender),
|
||||||
ID: msgID,
|
ID: waid.MakeMessageID(info.Chat, info.Sender, info.ID),
|
||||||
TxnID: networkid.TransactionID(msgID),
|
TxnID: networkid.TransactionID(waid.MakeMessageID(info.Chat, info.Sender, info.ID)),
|
||||||
Timestamp: info.Timestamp,
|
Timestamp: info.Timestamp,
|
||||||
StreamOrder: info.Timestamp.Unix(),
|
StreamOrder: info.Timestamp.Unix(),
|
||||||
Reactions: make([]*bridgev2.BackfillReaction, 0, len(reactions)),
|
Reactions: make([]*bridgev2.BackfillReaction, 0, len(reactions)),
|
||||||
|
|
@ -802,10 +762,10 @@ func (wa *WhatsAppClient) convertHistorySyncMessage(
|
||||||
for _, reaction := range reactions {
|
for _, reaction := range reactions {
|
||||||
var sender types.JID
|
var sender types.JID
|
||||||
if reaction.GetKey().GetFromMe() {
|
if reaction.GetKey().GetFromMe() {
|
||||||
sender = wa.GetLID()
|
sender = wa.JID
|
||||||
} else if reaction.GetKey().GetParticipant() != "" {
|
} else if reaction.GetKey().GetParticipant() != "" {
|
||||||
sender, _ = types.ParseJID(*reaction.Key.Participant)
|
sender, _ = types.ParseJID(*reaction.Key.Participant)
|
||||||
} else if info.Chat.Server == types.DefaultUserServer || info.Chat.Server == types.HiddenUserServer || info.Chat.Server == types.BotServer {
|
} else if info.Chat.Server == types.DefaultUserServer || info.Chat.Server == types.BotServer {
|
||||||
sender = info.Chat
|
sender = info.Chat
|
||||||
}
|
}
|
||||||
if sender.IsEmpty() {
|
if sender.IsEmpty() {
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ func (wa *WhatsAppConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilit
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppConnector) GetBridgeInfoVersion() (info, caps int) {
|
func (wa *WhatsAppConnector) GetBridgeInfoVersion() (info, caps int) {
|
||||||
return 1, 9
|
return 1, 8
|
||||||
}
|
}
|
||||||
|
|
||||||
const WAMaxFileSize = 2000 * 1024 * 1024
|
const WAMaxFileSize = 2000 * 1024 * 1024
|
||||||
|
|
@ -67,7 +67,7 @@ func supportedIfFFmpeg() event.CapabilitySupportLevel {
|
||||||
}
|
}
|
||||||
|
|
||||||
func capID() string {
|
func capID() string {
|
||||||
base := "fi.mau.whatsapp.capabilities.2026_07_22"
|
base := "fi.mau.whatsapp.capabilities.2026_05_12"
|
||||||
if ffmpeg.Supported() {
|
if ffmpeg.Supported() {
|
||||||
return base + "+ffmpeg"
|
return base + "+ffmpeg"
|
||||||
}
|
}
|
||||||
|
|
@ -177,11 +177,6 @@ var whatsappCaps = &event.RoomFeatures{
|
||||||
MaxTextLength: MaxTextLength,
|
MaxTextLength: MaxTextLength,
|
||||||
LocationMessage: event.CapLevelFullySupported,
|
LocationMessage: event.CapLevelFullySupported,
|
||||||
Poll: event.CapLevelFullySupported,
|
Poll: event.CapLevelFullySupported,
|
||||||
PollEnd: event.CapLevelUnsupported,
|
|
||||||
PollHiddenVotes: event.CapLevelUnsupported,
|
|
||||||
PollDuplicateOptions: event.CapLevelUnsupported,
|
|
||||||
PollMaxOptions: 12,
|
|
||||||
PollOptionMaxLength: 100,
|
|
||||||
Reply: event.CapLevelFullySupported,
|
Reply: event.CapLevelFullySupported,
|
||||||
Edit: event.CapLevelFullySupported,
|
Edit: event.CapLevelFullySupported,
|
||||||
EditMaxAge: ptr.Ptr(jsontime.S(EditMaxAge)),
|
EditMaxAge: ptr.Ptr(jsontime.S(EditMaxAge)),
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,6 @@ func (wa *WhatsAppClient) GetChatInfo(ctx context.Context, portal *bridgev2.Port
|
||||||
return wa.getChatInfo(ctx, portalJID, nil, portal.MXID == "")
|
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) {
|
func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, conv *wadb.Conversation, isNew bool) (wrapped *bridgev2.ChatInfo, err error) {
|
||||||
switch portalJID.Server {
|
switch portalJID.Server {
|
||||||
case types.DefaultUserServer, types.HiddenUserServer, types.BotServer:
|
case types.DefaultUserServer, types.HiddenUserServer, types.BotServer:
|
||||||
|
|
@ -39,7 +37,7 @@ func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID,
|
||||||
if portalJID == types.StatusBroadcastJID {
|
if portalJID == types.StatusBroadcastJID {
|
||||||
wrapped = wa.wrapStatusBroadcastInfo(ctx)
|
wrapped = wa.wrapStatusBroadcastInfo(ctx)
|
||||||
} else {
|
} else {
|
||||||
return nil, ErrBroadcastList
|
return nil, fmt.Errorf("broadcast list bridging is currently not supported")
|
||||||
}
|
}
|
||||||
case types.GroupServer:
|
case types.GroupServer:
|
||||||
info, err := wa.Client.GetGroupInfo(ctx, portalJID)
|
info, err := wa.Client.GetGroupInfo(ctx, portalJID)
|
||||||
|
|
@ -101,18 +99,6 @@ func (wa *WhatsAppClient) applyChatSettings(ctx context.Context, chatID types.JI
|
||||||
zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to get chat settings")
|
zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to get chat settings")
|
||||||
return
|
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{
|
info.UserLocal = &bridgev2.UserLocalPortalInfo{
|
||||||
MutedUntil: ptr.Ptr(chat.MutedUntil),
|
MutedUntil: ptr.Ptr(chat.MutedUntil),
|
||||||
}
|
}
|
||||||
|
|
@ -155,12 +141,7 @@ const PrivateChatTopic = "WhatsApp private chat"
|
||||||
const BotChatTopic = "WhatsApp chat with a bot"
|
const BotChatTopic = "WhatsApp chat with a bot"
|
||||||
|
|
||||||
func (wa *WhatsAppClient) wrapDMInfo(ctx context.Context, jid types.JID) *bridgev2.ChatInfo {
|
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{
|
info := &bridgev2.ChatInfo{
|
||||||
Type: ptr.Ptr(database.RoomTypeDM),
|
|
||||||
Topic: ptr.Ptr(PrivateChatTopic),
|
Topic: ptr.Ptr(PrivateChatTopic),
|
||||||
Members: &bridgev2.ChatMemberList{
|
Members: &bridgev2.ChatMemberList{
|
||||||
IsFull: true,
|
IsFull: true,
|
||||||
|
|
@ -168,7 +149,7 @@ func (wa *WhatsAppClient) wrapDMInfo(ctx context.Context, jid types.JID) *bridge
|
||||||
OtherUserID: waid.MakeUserID(jid),
|
OtherUserID: waid.MakeUserID(jid),
|
||||||
MemberMap: map[networkid.UserID]bridgev2.ChatMember{
|
MemberMap: map[networkid.UserID]bridgev2.ChatMember{
|
||||||
waid.MakeUserID(jid): {EventSender: wa.makeEventSender(ctx, jid)},
|
waid.MakeUserID(jid): {EventSender: wa.makeEventSender(ctx, jid)},
|
||||||
waid.MakeUserID(ownID): {EventSender: wa.makeEventSender(ctx, ownID)},
|
waid.MakeUserID(wa.JID): {EventSender: wa.makeEventSender(ctx, wa.JID)},
|
||||||
},
|
},
|
||||||
PowerLevels: &bridgev2.PowerLevelOverrides{
|
PowerLevels: &bridgev2.PowerLevelOverrides{
|
||||||
Events: map[event.Type]int{
|
Events: map[event.Type]int{
|
||||||
|
|
@ -178,14 +159,13 @@ func (wa *WhatsAppClient) wrapDMInfo(ctx context.Context, jid types.JID) *bridge
|
||||||
event.StateBeeperDisappearingTimer: 0,
|
event.StateBeeperDisappearingTimer: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ExcludeChangesFromTimeline: true,
|
|
||||||
},
|
},
|
||||||
ExcludeChangesFromTimeline: true,
|
Type: ptr.Ptr(database.RoomTypeDM),
|
||||||
}
|
}
|
||||||
if jid.Server == types.BotServer {
|
if jid.Server == types.BotServer {
|
||||||
info.Topic = ptr.Ptr(BotChatTopic)
|
info.Topic = ptr.Ptr(BotChatTopic)
|
||||||
}
|
}
|
||||||
if wa.IsOwnJID(jid) {
|
if jid == wa.JID.ToNonAD() {
|
||||||
// For chats with self, force-split the members so the user's own ghost is always in the room.
|
// 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{
|
info.Members.MemberMap = map[networkid.UserID]bridgev2.ChatMember{
|
||||||
waid.MakeUserID(jid): {EventSender: bridgev2.EventSender{Sender: waid.MakeUserID(jid)}},
|
waid.MakeUserID(jid): {EventSender: bridgev2.EventSender{Sender: waid.MakeUserID(jid)}},
|
||||||
|
|
@ -209,7 +189,7 @@ func (wa *WhatsAppClient) wrapStatusBroadcastInfo(ctx context.Context) *bridgev2
|
||||||
Members: &bridgev2.ChatMemberList{
|
Members: &bridgev2.ChatMemberList{
|
||||||
IsFull: false,
|
IsFull: false,
|
||||||
MemberMap: map[networkid.UserID]bridgev2.ChatMember{
|
MemberMap: map[networkid.UserID]bridgev2.ChatMember{
|
||||||
waid.MakeUserID(wa.GetLID()): {EventSender: wa.makeEventSender(ctx, wa.GetLID())},
|
waid.MakeUserID(wa.JID): {EventSender: wa.makeEventSender(ctx, wa.JID)},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Type: ptr.Ptr(database.RoomTypeDefault),
|
Type: ptr.Ptr(database.RoomTypeDefault),
|
||||||
|
|
@ -273,12 +253,11 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn
|
||||||
setAddressingMode(info.AddressingMode),
|
setAddressingMode(info.AddressingMode),
|
||||||
setTopicID(info.TopicID, info.Topic),
|
setTopicID(info.TopicID, info.Topic),
|
||||||
)
|
)
|
||||||
syncAllMembers := wa.Main.Config.MaxMemberSync < 0 || len(info.Participants) < wa.Main.Config.MaxMemberSync
|
|
||||||
wrapped := &bridgev2.ChatInfo{
|
wrapped := &bridgev2.ChatInfo{
|
||||||
Name: ptr.Ptr(info.Name),
|
Name: ptr.Ptr(info.Name),
|
||||||
Topic: ptr.Ptr(info.Topic),
|
Topic: ptr.Ptr(info.Topic),
|
||||||
Members: &bridgev2.ChatMemberList{
|
Members: &bridgev2.ChatMemberList{
|
||||||
IsFull: !info.IsIncognito && !info.IsParent && syncAllMembers,
|
IsFull: !info.IsIncognito && !info.IsParent,
|
||||||
TotalMemberCount: len(info.Participants),
|
TotalMemberCount: len(info.Participants),
|
||||||
MemberMap: make(map[networkid.UserID]bridgev2.ChatMember, len(info.Participants)),
|
MemberMap: make(map[networkid.UserID]bridgev2.ChatMember, len(info.Participants)),
|
||||||
PowerLevels: &bridgev2.PowerLevelOverrides{
|
PowerLevels: &bridgev2.PowerLevelOverrides{
|
||||||
|
|
@ -292,8 +271,9 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn
|
||||||
event.StateTopic: metaChangePL,
|
event.StateTopic: metaChangePL,
|
||||||
event.EventReaction: defaultPL,
|
event.EventReaction: defaultPL,
|
||||||
event.EventRedaction: defaultPL,
|
event.EventRedaction: defaultPL,
|
||||||
event.EventUnstablePollResponse: defaultPL,
|
|
||||||
event.StateBeeperDisappearingTimer: metaChangePL,
|
event.StateBeeperDisappearingTimer: metaChangePL,
|
||||||
|
// TODO always allow poll responses
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -318,9 +298,6 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn
|
||||||
} else if pcp.IsAdmin {
|
} else if pcp.IsAdmin {
|
||||||
member.PowerLevel = ptr.Ptr(adminPL)
|
member.PowerLevel = ptr.Ptr(adminPL)
|
||||||
} else {
|
} else {
|
||||||
if !syncAllMembers && !member.EventSender.IsFromMe {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
member.PowerLevel = ptr.Ptr(defaultPL)
|
member.PowerLevel = ptr.Ptr(defaultPL)
|
||||||
}
|
}
|
||||||
member.MemberEventExtra = map[string]any{
|
member.MemberEventExtra = map[string]any{
|
||||||
|
|
@ -339,7 +316,7 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if info.IsParent && !hasSelf && info.AddressingMode == types.AddressingModeLID {
|
if info.IsParent && !hasSelf && info.AddressingMode == types.AddressingModeLID {
|
||||||
wrapped.Members.MemberMap.Add(bridgev2.ChatMember{EventSender: wa.makeEventSender(ctx, wa.GetLID())})
|
wrapped.Members.MemberMap.Add(bridgev2.ChatMember{EventSender: wa.makeEventSender(ctx, wa.Device.LID)})
|
||||||
}
|
}
|
||||||
|
|
||||||
if !info.LinkedParentJID.IsEmpty() {
|
if !info.LinkedParentJID.IsEmpty() {
|
||||||
|
|
@ -546,8 +523,8 @@ func (wa *WhatsAppClient) wrapNewsletterInfo(ctx context.Context, info *types.Ne
|
||||||
Members: &bridgev2.ChatMemberList{
|
Members: &bridgev2.ChatMemberList{
|
||||||
TotalMemberCount: info.ThreadMeta.SubscriberCount,
|
TotalMemberCount: info.ThreadMeta.SubscriberCount,
|
||||||
MemberMap: map[networkid.UserID]bridgev2.ChatMember{
|
MemberMap: map[networkid.UserID]bridgev2.ChatMember{
|
||||||
waid.MakeUserID(wa.GetLID()): {
|
waid.MakeUserID(wa.JID): {
|
||||||
EventSender: wa.makeEventSender(ctx, wa.GetLID()),
|
EventSender: wa.makeEventSender(ctx, wa.JID),
|
||||||
PowerLevel: &ownPowerLevel,
|
PowerLevel: &ownPowerLevel,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -561,7 +538,7 @@ func (wa *WhatsAppClient) wrapNewsletterInfo(ctx context.Context, info *types.Ne
|
||||||
event.StateTopic: adminPL,
|
event.StateTopic: adminPL,
|
||||||
event.EventReaction: defaultPL,
|
event.EventReaction: defaultPL,
|
||||||
event.EventRedaction: defaultPL,
|
event.EventRedaction: defaultPL,
|
||||||
event.EventUnstablePollResponse: defaultPL,
|
// TODO always allow poll responses
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,8 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2.
|
||||||
directMediaRetries: make(map[networkid.MessageID]*directMediaRetry),
|
directMediaRetries: make(map[networkid.MessageID]*directMediaRetry),
|
||||||
mediaRetryLock: semaphore.NewWeighted(wa.Config.HistorySync.MediaRequests.MaxAsyncHandle),
|
mediaRetryLock: semaphore.NewWeighted(wa.Config.HistorySync.MediaRequests.MaxAsyncHandle),
|
||||||
pushNamesSynced: exsync.NewEvent(),
|
pushNamesSynced: exsync.NewEvent(),
|
||||||
|
createDedup: exsync.NewSet[types.MessageID](),
|
||||||
appStateFullSyncAttempted: make(map[appstate.WAPatchName]time.Time),
|
appStateFullSyncAttempted: make(map[appstate.WAPatchName]time.Time),
|
||||||
|
|
||||||
disableNewsletter: store.BaseClientPayload.GetUserAgent().GetPlatform() == waWa6.ClientPayload_UserAgent_MACOS,
|
|
||||||
}
|
}
|
||||||
login.Client = w
|
login.Client = w
|
||||||
|
|
||||||
|
|
@ -71,14 +70,13 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w.LID = w.Device.GetLID()
|
|
||||||
|
|
||||||
if w.Device != nil {
|
if w.Device != nil {
|
||||||
log := w.UserLogin.Log.With().Str("component", "whatsmeow").Logger()
|
log := w.UserLogin.Log.With().Str("component", "whatsmeow").Logger()
|
||||||
w.Client = whatsmeow.NewClient(w.Device, waLog.Zerolog(log))
|
w.Client = whatsmeow.NewClient(w.Device, waLog.Zerolog(log))
|
||||||
w.Client.AddEventHandlerWithSuccessStatus(w.handleWAEvent)
|
w.Client.AddEventHandlerWithSuccessStatus(w.handleWAEvent)
|
||||||
w.Client.SynchronousAck = true
|
w.Client.SynchronousAck = true
|
||||||
w.Client.EnableDecryptedEventBuffer = wa.Bridge.Config.PortalEventBuffer == 0
|
w.Client.EnableDecryptedEventBuffer = bridgev2.PortalEventBuffer == 0
|
||||||
w.Client.ManualHistorySyncDownload = true
|
w.Client.ManualHistorySyncDownload = true
|
||||||
w.Client.SendReportingTokens = true
|
w.Client.SendReportingTokens = true
|
||||||
w.Client.AutomaticMessageRerequestFromPhone = true
|
w.Client.AutomaticMessageRerequestFromPhone = true
|
||||||
|
|
@ -106,7 +104,6 @@ type WhatsAppClient struct {
|
||||||
Client *whatsmeow.Client
|
Client *whatsmeow.Client
|
||||||
Device *store.Device
|
Device *store.Device
|
||||||
JID types.JID
|
JID types.JID
|
||||||
LID types.JID
|
|
||||||
MC mClient
|
MC mClient
|
||||||
|
|
||||||
historySyncWakeup chan struct{}
|
historySyncWakeup chan struct{}
|
||||||
|
|
@ -116,14 +113,12 @@ type WhatsAppClient struct {
|
||||||
nextResync time.Time
|
nextResync time.Time
|
||||||
directMediaRetries map[networkid.MessageID]*directMediaRetry
|
directMediaRetries map[networkid.MessageID]*directMediaRetry
|
||||||
directMediaLock sync.Mutex
|
directMediaLock sync.Mutex
|
||||||
avatarLock exsync.KeyedMutex[types.JID]
|
|
||||||
mediaRetryLock *semaphore.Weighted
|
mediaRetryLock *semaphore.Weighted
|
||||||
offlineSyncWaiter atomic.Pointer[chan error]
|
offlineSyncWaiter atomic.Pointer[chan error]
|
||||||
isNewLogin bool
|
isNewLogin bool
|
||||||
pushNamesSynced *exsync.Event
|
pushNamesSynced *exsync.Event
|
||||||
lastPresence types.Presence
|
lastPresence types.Presence
|
||||||
|
createDedup *exsync.Set[types.MessageID]
|
||||||
disableNewsletter bool
|
|
||||||
|
|
||||||
appStateRecoveryLock sync.Mutex
|
appStateRecoveryLock sync.Mutex
|
||||||
appStateFullSyncAttempted map[appstate.WAPatchName]time.Time
|
appStateFullSyncAttempted map[appstate.WAPatchName]time.Time
|
||||||
|
|
@ -190,19 +185,7 @@ func (wa *WhatsAppClient) RegisterPushNotifications(ctx context.Context, pushTyp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) IsThisUser(_ context.Context, userID networkid.UserID) bool {
|
func (wa *WhatsAppClient) IsThisUser(_ context.Context, userID networkid.UserID) bool {
|
||||||
return userID == waid.MakeUserID(wa.JID) || userID == waid.MakeUserID(wa.GetLID())
|
return userID == waid.MakeUserID(wa.JID)
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
func (wa *WhatsAppClient) Connect(ctx context.Context) {
|
||||||
|
|
@ -394,10 +377,6 @@ func (wa *WhatsAppClient) LogoutRemote(ctx context.Context) {
|
||||||
}
|
}
|
||||||
wa.Disconnect()
|
wa.Disconnect()
|
||||||
wa.Client = nil
|
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 {
|
func (wa *WhatsAppClient) IsLoggedIn() bool {
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,6 @@ type Config struct {
|
||||||
DirectMediaAutoRequest bool `yaml:"direct_media_auto_request"`
|
DirectMediaAutoRequest bool `yaml:"direct_media_auto_request"`
|
||||||
InitialAutoReconnect bool `yaml:"initial_auto_reconnect"`
|
InitialAutoReconnect bool `yaml:"initial_auto_reconnect"`
|
||||||
UseWhatsAppRetryStore bool `yaml:"use_whatsapp_retry_store"`
|
UseWhatsAppRetryStore bool `yaml:"use_whatsapp_retry_store"`
|
||||||
MaxMemberSync int `yaml:"max_member_sync"`
|
|
||||||
LazyAvatars bool `yaml:"lazy_avatars"`
|
|
||||||
|
|
||||||
AnimatedSticker msgconv.AnimatedStickerConfig `yaml:"animated_sticker"`
|
AnimatedSticker msgconv.AnimatedStickerConfig `yaml:"animated_sticker"`
|
||||||
|
|
||||||
|
|
@ -131,8 +129,6 @@ func upgradeConfig(helper up.Helper) {
|
||||||
helper.Copy(up.Bool, "direct_media_auto_request")
|
helper.Copy(up.Bool, "direct_media_auto_request")
|
||||||
helper.Copy(up.Bool, "initial_auto_reconnect")
|
helper.Copy(up.Bool, "initial_auto_reconnect")
|
||||||
helper.Copy(up.Bool, "use_whatsapp_retry_store")
|
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.Str, "animated_sticker", "target")
|
||||||
helper.Copy(up.Int, "animated_sticker", "args", "width")
|
helper.Copy(up.Int, "animated_sticker", "args", "width")
|
||||||
|
|
@ -209,7 +205,6 @@ func (wa *WhatsAppConnector) GetConfig() (string, any, up.Upgrader) {
|
||||||
{"proxy"},
|
{"proxy"},
|
||||||
{"displayname_template"},
|
{"displayname_template"},
|
||||||
{"call_start_notices"},
|
{"call_start_notices"},
|
||||||
{"animated_sticker"},
|
|
||||||
{"history_sync"},
|
{"history_sync"},
|
||||||
},
|
},
|
||||||
Base: ExampleConfig,
|
Base: ExampleConfig,
|
||||||
|
|
|
||||||
|
|
@ -28,24 +28,27 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
"go.mau.fi/util/dbutil"
|
"go.mau.fi/util/dbutil"
|
||||||
"go.mau.fi/util/exsync"
|
|
||||||
"go.mau.fi/util/random"
|
"go.mau.fi/util/random"
|
||||||
"go.mau.fi/whatsmeow"
|
"go.mau.fi/whatsmeow"
|
||||||
"go.mau.fi/whatsmeow/proto/waCompanionReg"
|
"go.mau.fi/whatsmeow/proto/waCompanionReg"
|
||||||
"go.mau.fi/whatsmeow/store"
|
"go.mau.fi/whatsmeow/store"
|
||||||
"go.mau.fi/whatsmeow/store/sqlstore"
|
"go.mau.fi/whatsmeow/store/sqlstore"
|
||||||
whatsmeowUpgrades "go.mau.fi/whatsmeow/store/sqlstore/upgrades"
|
whatsmeowUpgrades "go.mau.fi/whatsmeow/store/sqlstore/upgrades"
|
||||||
|
"go.mau.fi/whatsmeow/types"
|
||||||
waLog "go.mau.fi/whatsmeow/util/log"
|
waLog "go.mau.fi/whatsmeow/util/log"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
"maunium.net/go/mautrix/bridgev2"
|
"maunium.net/go/mautrix/bridgev2"
|
||||||
"maunium.net/go/mautrix/bridgev2/commands"
|
"maunium.net/go/mautrix/bridgev2/commands"
|
||||||
|
"maunium.net/go/mautrix/bridgev2/database"
|
||||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
"maunium.net/go/mautrix/id"
|
"maunium.net/go/mautrix/id"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-whatsapp/pkg/connector/wadb"
|
"go.mau.fi/mautrix-whatsapp/pkg/connector/wadb"
|
||||||
"go.mau.fi/mautrix-whatsapp/pkg/msgconv"
|
"go.mau.fi/mautrix-whatsapp/pkg/msgconv"
|
||||||
|
"go.mau.fi/mautrix-whatsapp/pkg/waid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WhatsAppConnector struct {
|
type WhatsAppConnector struct {
|
||||||
|
|
@ -61,8 +64,6 @@ type WhatsAppConnector struct {
|
||||||
mediaEditCache MediaEditCache
|
mediaEditCache MediaEditCache
|
||||||
mediaEditCacheLock sync.RWMutex
|
mediaEditCacheLock sync.RWMutex
|
||||||
stopMediaEditCacheLoop atomic.Pointer[context.CancelFunc]
|
stopMediaEditCacheLoop atomic.Pointer[context.CancelFunc]
|
||||||
|
|
||||||
unmigratedDMs *exsync.Set[networkid.PortalKey]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -113,7 +114,6 @@ func (wa *WhatsAppConnector) Init(bridge *bridgev2.Bridge) {
|
||||||
cmdAccept, cmdSync, cmdInviteLink, cmdResolveLink, cmdJoin,
|
cmdAccept, cmdSync, cmdInviteLink, cmdResolveLink, cmdJoin,
|
||||||
)
|
)
|
||||||
wa.mediaEditCache = make(MediaEditCache)
|
wa.mediaEditCache = make(MediaEditCache)
|
||||||
wa.unmigratedDMs = exsync.NewSet[networkid.PortalKey]()
|
|
||||||
|
|
||||||
whatsmeowDBLog := bridge.Log.With().Str("db_section", "whatsmeow").Logger()
|
whatsmeowDBLog := bridge.Log.With().Str("db_section", "whatsmeow").Logger()
|
||||||
wa.DeviceStore = sqlstore.NewWithWrappedDB(
|
wa.DeviceStore = sqlstore.NewWithWrappedDB(
|
||||||
|
|
@ -142,9 +142,6 @@ func (wa *WhatsAppConnector) Init(bridge *bridgev2.Bridge) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppConnector) Start(ctx context.Context) error {
|
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)
|
err := wa.DeviceStore.Upgrade(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return bridgev2.DBUpgradeError{Err: err, Section: "whatsmeow"}
|
return bridgev2.DBUpgradeError{Err: err, Section: "whatsmeow"}
|
||||||
|
|
@ -160,19 +157,80 @@ func (wa *WhatsAppConnector) Start(ctx context.Context) error {
|
||||||
return bridgev2.DBUpgradeError{Err: err, Section: "whatsapp"}
|
return bridgev2.DBUpgradeError{Err: err, Section: "whatsapp"}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = wa.migrateToLIDDMs(ctx)
|
if !wa.Bridge.Background && wa.Bridge.DB.KV.Get(ctx, "whatsapp_lid_dms_deleted") == "false" {
|
||||||
if err != nil {
|
wa.deleteLIDDMsMigration(ctx)
|
||||||
return fmt.Errorf("failed to migrate to LID DMs: %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
|
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() {
|
func (wa *WhatsAppConnector) Stop() {
|
||||||
if stop := wa.stopMediaEditCacheLoop.Swap(nil); stop != nil {
|
if stop := wa.stopMediaEditCacheLoop.Swap(nil); stop != nil {
|
||||||
(*stop)()
|
(*stop)()
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ import (
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"go.mau.fi/util/exsync"
|
"go.mau.fi/util/exsync"
|
||||||
"go.mau.fi/util/jsontime"
|
|
||||||
"go.mau.fi/util/ptr"
|
"go.mau.fi/util/ptr"
|
||||||
"go.mau.fi/whatsmeow"
|
"go.mau.fi/whatsmeow"
|
||||||
"go.mau.fi/whatsmeow/proto/waMmsRetry"
|
"go.mau.fi/whatsmeow/proto/waMmsRetry"
|
||||||
|
|
@ -53,7 +52,7 @@ func (wa *WhatsAppConnector) SetUseDirectMedia() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrReloadNeeded = mautrix.RespError{
|
var ErrReloadNeeded = mautrix.RespError{
|
||||||
ErrCode: "COM.BEEPER.MEDIA_RELOAD_NEEDED",
|
ErrCode: "FI.MAU.WHATSAPP_RELOAD_NEEDED",
|
||||||
Err: "Media is no longer available on WhatsApp servers and must be re-requested from your phone",
|
Err: "Media is no longer available on WhatsApp servers and must be re-requested from your phone",
|
||||||
StatusCode: http.StatusNotFound,
|
StatusCode: http.StatusNotFound,
|
||||||
}
|
}
|
||||||
|
|
@ -85,15 +84,13 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars
|
||||||
if waClient.Client == nil {
|
if waClient.Client == nil {
|
||||||
return nil, fmt.Errorf("no WhatsApp client found on login %s", parsedID.UserLogin)
|
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)
|
cachedInfo, err := wa.DB.AvatarCache.Get(ctx, parsedID.Avatar.TargetJID, parsedID.Avatar.AvatarID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get avatar cache entry: %w", err)
|
return nil, fmt.Errorf("failed to get avatar cache entry: %w", err)
|
||||||
}
|
}
|
||||||
if cachedInfo.IsGone() {
|
if cachedInfo != nil && cachedInfo.Gone {
|
||||||
return nil, mautrix.MNotFound.WithMessage("Avatar is no longer available (cached response)")
|
return nil, mautrix.MNotFound.WithMessage("Avatar is no longer available (cached response)")
|
||||||
} else if cachedInfo.Expired() {
|
} else if cachedInfo == nil || cachedInfo.Expiry.Time.Before(time.Now().Add(5*time.Minute)) {
|
||||||
zerolog.Ctx(ctx).Debug().
|
zerolog.Ctx(ctx).Debug().
|
||||||
Str("avatar_id", parsedID.Avatar.AvatarID).
|
Str("avatar_id", parsedID.Avatar.AvatarID).
|
||||||
Msg("Refreshing avatar URL from WhatsApp servers")
|
Msg("Refreshing avatar URL from WhatsApp servers")
|
||||||
|
|
@ -102,7 +99,7 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars
|
||||||
})
|
})
|
||||||
if errors.Is(err, whatsmeow.ErrProfilePictureNotSet) ||
|
if errors.Is(err, whatsmeow.ErrProfilePictureNotSet) ||
|
||||||
errors.Is(err, whatsmeow.ErrProfilePictureUnauthorized) ||
|
errors.Is(err, whatsmeow.ErrProfilePictureUnauthorized) ||
|
||||||
(err == nil && (avatar == nil || (avatar.ID != parsedID.Avatar.AvatarID && !parsedID.Avatar.IsRandom()))) {
|
(err == nil && (avatar == nil || avatar.ID != parsedID.Avatar.AvatarID)) {
|
||||||
zerolog.Ctx(ctx).Debug().
|
zerolog.Ctx(ctx).Debug().
|
||||||
Err(err).
|
Err(err).
|
||||||
Stringer("target_jid", parsedID.Avatar.TargetJID).
|
Stringer("target_jid", parsedID.Avatar.TargetJID).
|
||||||
|
|
@ -110,14 +107,9 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars
|
||||||
Str("wanted_avatar_id", parsedID.Avatar.AvatarID).
|
Str("wanted_avatar_id", parsedID.Avatar.AvatarID).
|
||||||
Str("got_avatar_id", ptr.Val(avatar).ID).
|
Str("got_avatar_id", ptr.Val(avatar).ID).
|
||||||
Msg("Avatar is no longer available")
|
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{
|
err = wa.DB.AvatarCache.Put(ctx, &wadb.AvatarCacheEntry{
|
||||||
EntityJID: parsedID.Avatar.TargetJID,
|
EntityJID: parsedID.Avatar.TargetJID,
|
||||||
AvatarID: parsedID.Avatar.AvatarID,
|
AvatarID: parsedID.Avatar.AvatarID,
|
||||||
Expiry: goneExpiry,
|
|
||||||
Gone: true,
|
Gone: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -136,15 +128,6 @@ func (wa *WhatsAppConnector) downloadAvatarDirectMedia(ctx context.Context, pars
|
||||||
Str("avatar_id", avatar.ID).
|
Str("avatar_id", avatar.ID).
|
||||||
Msg("Failed to update avatar cache entry")
|
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{
|
return &mediaproxy.GetMediaResponseFile{
|
||||||
Callback: func(w *os.File) (*mediaproxy.FileMeta, error) {
|
Callback: func(w *os.File) (*mediaproxy.FileMeta, error) {
|
||||||
|
|
@ -229,7 +212,7 @@ func (wa *WhatsAppConnector) makeDirectMediaResponse(
|
||||||
log := zerolog.Ctx(ctx)
|
log := zerolog.Ctx(ctx)
|
||||||
err := waClient.Client.DownloadToFile(ctx, dm, f)
|
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)) {
|
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["com.beeper.interactive_download_request"]
|
val := params["fi.mau.whatsapp.reload_media"]
|
||||||
if val == "false" || (!wa.Config.DirectMediaAutoRequest && val != "true") {
|
if val == "false" || (!wa.Config.DirectMediaAutoRequest && val != "true") {
|
||||||
return nil, ErrReloadNeeded
|
return nil, ErrReloadNeeded
|
||||||
}
|
}
|
||||||
|
|
@ -276,7 +259,6 @@ type directMediaRetry struct {
|
||||||
wait *exsync.Event
|
wait *exsync.Event
|
||||||
requested bool
|
requested bool
|
||||||
resultType waMmsRetry.MediaRetryNotification_ResultType
|
resultType waMmsRetry.MediaRetryNotification_ResultType
|
||||||
decryptFail bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) getDirectMediaRetryState(msgID networkid.MessageID, create bool) *directMediaRetry {
|
func (wa *WhatsAppClient) getDirectMediaRetryState(msgID networkid.MessageID, create bool) *directMediaRetry {
|
||||||
|
|
@ -303,9 +285,6 @@ func (wa *WhatsAppClient) requestAndWaitDirectMedia(ctx context.Context, rawMsgI
|
||||||
keys.DirectPath = state.resultURL
|
keys.DirectPath = state.resultURL
|
||||||
return nil
|
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 {
|
switch state.resultType {
|
||||||
case waMmsRetry.MediaRetryNotification_NOT_FOUND:
|
case waMmsRetry.MediaRetryNotification_NOT_FOUND:
|
||||||
return mautrix.MNotFound.WithMessage("This media was not found on your phone.")
|
return mautrix.MNotFound.WithMessage("This media was not found on your phone.")
|
||||||
|
|
@ -359,9 +338,6 @@ func (wa *WhatsAppClient) receiveDirectMediaRetry(ctx context.Context, msg *data
|
||||||
retryData, err := whatsmeow.DecryptMediaRetryNotification(retry, keys.Key)
|
retryData, err := whatsmeow.DecryptMediaRetryNotification(retry, keys.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Msg("Failed to decrypt media retry notification")
|
log.Warn().Err(err).Msg("Failed to decrypt media retry notification")
|
||||||
if state != nil {
|
|
||||||
state.decryptFail = true
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if state != nil {
|
if state != nil {
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,20 @@ import (
|
||||||
"go.mau.fi/mautrix-whatsapp/pkg/waid"
|
"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 {
|
type MessageInfoWrapper struct {
|
||||||
|
OrigSource types.MessageSource
|
||||||
Info types.MessageInfo
|
Info types.MessageInfo
|
||||||
wa *WhatsAppClient
|
wa *WhatsAppClient
|
||||||
}
|
}
|
||||||
|
|
@ -49,26 +62,7 @@ func (evt *MessageInfoWrapper) ShouldCreatePortal() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evt *MessageInfoWrapper) GetPortalKey() networkid.PortalKey {
|
func (evt *MessageInfoWrapper) GetPortalKey() networkid.PortalKey {
|
||||||
ms := evt.Info.MessageSource
|
return evt.wa.getPortalKeyByMessageSource(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 {
|
func (evt *MessageInfoWrapper) AddLogContext(c zerolog.Context) zerolog.Context {
|
||||||
|
|
@ -79,19 +73,12 @@ func (evt *MessageInfoWrapper) GetTimestamp() time.Time {
|
||||||
return evt.Info.Timestamp
|
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 {
|
func (evt *MessageInfoWrapper) GetSender() bridgev2.EventSender {
|
||||||
return evt.wa.makeEventSender(evt.wa.Main.Bridge.BackgroundCtx, pickLID(evt.Info.Sender, evt.Info.SenderAlt))
|
return evt.wa.makeEventSender(evt.wa.Main.Bridge.BackgroundCtx, evt.Info.Sender)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evt *MessageInfoWrapper) GetID() networkid.MessageID {
|
func (evt *MessageInfoWrapper) GetID() networkid.MessageID {
|
||||||
return waid.MakeMessageIDWithAltSender(evt.Info.Chat, evt.Info.Sender, evt.Info.SenderAlt, evt.Info.ID)
|
return waid.MakeMessageID(evt.Info.Chat, evt.Info.Sender, evt.Info.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evt *MessageInfoWrapper) GetTransactionID() networkid.TransactionID {
|
func (evt *MessageInfoWrapper) GetTransactionID() networkid.TransactionID {
|
||||||
|
|
@ -148,6 +135,14 @@ func (evt *WAMessageEvent) PreHandle(ctx context.Context, portal *bridgev2.Porta
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
meta := portal.Metadata.(*waid.PortalMetadata)
|
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 {
|
if meta.AddressingMode == types.AddressingModeLID || meta.LIDMigrationAttempted {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -166,6 +161,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")
|
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{})
|
portal.UpdateInfo(ctx, evt.wa.wrapGroupInfo(ctx, info), evt.wa.UserLogin, nil, time.Time{})
|
||||||
log.Debug().Msg("Finished resyncing after LID change")
|
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) {
|
func (evt *WAMessageEvent) PostHandle(ctx context.Context, portal *bridgev2.Portal) {
|
||||||
|
|
@ -199,7 +201,7 @@ func (evt *WAMessageEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Por
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, msgconv.ContextKeyEditTargetID, evt.Message.GetProtocolMessage().GetKey().GetID())
|
ctx = context.WithValue(ctx, msgconv.ContextKeyEditTargetID, evt.Message.GetProtocolMessage().GetKey().GetID())
|
||||||
cm := evt.wa.Main.MsgConv.ToMatrix(
|
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) {
|
if evt.isUndecryptableUpsertSubEvent && isFailedMedia(cm) {
|
||||||
evt.postHandle = func() {
|
evt.postHandle = func() {
|
||||||
|
|
@ -284,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) {
|
func (evt *WAMessageEvent) ConvertMessage(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI) (*bridgev2.ConvertedMessage, error) {
|
||||||
evt.wa.EnqueuePortalResync(portal, false)
|
evt.wa.EnqueuePortalResync(portal, false)
|
||||||
converted := evt.wa.Main.MsgConv.ToMatrix(
|
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) {
|
if isFailedMedia(converted) {
|
||||||
evt.postHandle = func() {
|
evt.postHandle = func() {
|
||||||
|
|
@ -391,8 +393,6 @@ func (evt *WAUndecryptableMessage) GetStreamOrder() int64 {
|
||||||
type WAMediaRetry struct {
|
type WAMediaRetry struct {
|
||||||
*events.MediaRetry
|
*events.MediaRetry
|
||||||
wa *WhatsAppClient
|
wa *WhatsAppClient
|
||||||
senderLID types.JID
|
|
||||||
chatLID types.JID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evt *WAMediaRetry) GetType() bridgev2.RemoteEventType {
|
func (evt *WAMediaRetry) GetType() bridgev2.RemoteEventType {
|
||||||
|
|
@ -400,7 +400,7 @@ func (evt *WAMediaRetry) GetType() bridgev2.RemoteEventType {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evt *WAMediaRetry) GetPortalKey() networkid.PortalKey {
|
func (evt *WAMediaRetry) GetPortalKey() networkid.PortalKey {
|
||||||
return evt.wa.makeWAPortalKey(pickLID(evt.ChatID, evt.chatLID))
|
return evt.wa.makeWAPortalKey(evt.ChatID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evt *WAMediaRetry) AddLogContext(c zerolog.Context) zerolog.Context {
|
func (evt *WAMediaRetry) AddLogContext(c zerolog.Context) zerolog.Context {
|
||||||
|
|
@ -414,23 +414,16 @@ func (evt *WAMediaRetry) AddLogContext(c zerolog.Context) zerolog.Context {
|
||||||
|
|
||||||
func (evt *WAMediaRetry) getRealSender() types.JID {
|
func (evt *WAMediaRetry) getRealSender() types.JID {
|
||||||
sender := evt.SenderID
|
sender := evt.SenderID
|
||||||
if sender.IsEmpty() {
|
|
||||||
if evt.FromMe {
|
if evt.FromMe {
|
||||||
if evt.ChatID.Server == types.HiddenUserServer {
|
|
||||||
sender = evt.wa.GetLID().ToNonAD()
|
|
||||||
} else {
|
|
||||||
sender = evt.wa.JID.ToNonAD()
|
sender = evt.wa.JID.ToNonAD()
|
||||||
}
|
} else if sender.IsEmpty() && (evt.ChatID.Server == types.DefaultUserServer || evt.ChatID.Server == types.BotServer) {
|
||||||
} else if evt.ChatID.Server == types.DefaultUserServer || evt.ChatID.Server == types.HiddenUserServer || evt.ChatID.Server == types.BotServer {
|
|
||||||
sender = evt.ChatID.ToNonAD()
|
sender = evt.ChatID.ToNonAD()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return sender
|
return sender
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evt *WAMediaRetry) GetSender() bridgev2.EventSender {
|
func (evt *WAMediaRetry) GetSender() bridgev2.EventSender {
|
||||||
realSender := pickLID(evt.getRealSender(), evt.senderLID)
|
return evt.wa.makeEventSender(evt.wa.Main.Bridge.BackgroundCtx, evt.getRealSender())
|
||||||
return evt.wa.makeEventSender(evt.wa.Main.Bridge.BackgroundCtx, realSender)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evt *WAMediaRetry) GetTargetMessage() networkid.MessageID {
|
func (evt *WAMediaRetry) GetTargetMessage() networkid.MessageID {
|
||||||
|
|
|
||||||
|
|
@ -69,12 +69,6 @@ initial_auto_reconnect: true
|
||||||
# the bridge? By default, the bridge only stores messages in memory, and therefore can't accept
|
# 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.
|
# retry receipts if the bridge is restarted after the message is sent.
|
||||||
use_whatsapp_retry_store: false
|
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.
|
# Settings for converting animated stickers.
|
||||||
animated_sticker:
|
animated_sticker:
|
||||||
|
|
|
||||||
|
|
@ -103,14 +103,11 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if chatJID == types.StatusBroadcastJID && wa.Main.Config.DisableStatusBroadcastSend {
|
||||||
return nil, ErrBroadcastSendDisabled
|
return nil, ErrBroadcastSendDisabled
|
||||||
}
|
}
|
||||||
wrappedMsgID := waid.MakeMessageID(chatJID, wa.JID, req.ID)
|
wrappedMsgID := waid.MakeMessageID(chatJID, wa.JID, req.ID)
|
||||||
wrappedMsgID2 := waid.MakeMessageID(chatJID, wa.GetLID(), req.ID)
|
wrappedMsgID2 := waid.MakeMessageID(chatJID, wa.GetStore().GetLID(), req.ID)
|
||||||
msg.AddPendingToIgnore(networkid.TransactionID(wrappedMsgID))
|
msg.AddPendingToIgnore(networkid.TransactionID(wrappedMsgID))
|
||||||
msg.AddPendingToIgnore(networkid.TransactionID(wrappedMsgID2))
|
msg.AddPendingToIgnore(networkid.TransactionID(wrappedMsgID2))
|
||||||
zerolog.Ctx(ctx).Trace().Any("payload", waMsg).Msg("Outgoing message payload")
|
zerolog.Ctx(ctx).Trace().Any("payload", waMsg).Msg("Outgoing message payload")
|
||||||
|
|
@ -119,7 +116,7 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var pickedMessageID networkid.MessageID
|
var pickedMessageID networkid.MessageID
|
||||||
if resp.Sender == wa.GetLID() {
|
if resp.Sender == wa.GetStore().GetLID() && chatJID.Server != types.DefaultUserServer {
|
||||||
pickedMessageID = wrappedMsgID2
|
pickedMessageID = wrappedMsgID2
|
||||||
msg.RemovePending(networkid.TransactionID(wrappedMsgID))
|
msg.RemovePending(networkid.TransactionID(wrappedMsgID))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -140,17 +137,18 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) PreHandleMatrixReaction(ctx context.Context, msg *bridgev2.MatrixReaction) (bridgev2.MatrixReactionPreResponse, error) {
|
func (wa *WhatsAppClient) PreHandleMatrixReaction(_ context.Context, msg *bridgev2.MatrixReaction) (bridgev2.MatrixReactionPreResponse, error) {
|
||||||
portalJID, err := waid.ParsePortalID(msg.Portal.ID)
|
portalJID, err := waid.ParsePortalID(msg.Portal.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return bridgev2.MatrixReactionPreResponse{}, fmt.Errorf("failed to parse portal ID: %w", err)
|
return bridgev2.MatrixReactionPreResponse{}, fmt.Errorf("failed to parse portal ID: %w", err)
|
||||||
} else if portalJID == types.StatusBroadcastJID {
|
} else if portalJID == types.StatusBroadcastJID {
|
||||||
return bridgev2.MatrixReactionPreResponse{}, ErrBroadcastReactionUnsupported
|
return bridgev2.MatrixReactionPreResponse{}, ErrBroadcastReactionUnsupported
|
||||||
}
|
}
|
||||||
sender := wa.GetLID()
|
sender := wa.JID
|
||||||
if portalJID.Server == types.DefaultUserServer {
|
if portalJID.Server == types.HiddenUserServer ||
|
||||||
zerolog.Ctx(ctx).Warn().Stringer("portal_jid", portalJID).Msg("Matrix reaction received in phone number portal")
|
msg.Portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup ||
|
||||||
sender = wa.JID
|
msg.Portal.Metadata.(*waid.PortalMetadata).AddressingMode == types.AddressingModeLID {
|
||||||
|
sender = wa.GetStore().GetLID()
|
||||||
}
|
}
|
||||||
return bridgev2.MatrixReactionPreResponse{
|
return bridgev2.MatrixReactionPreResponse{
|
||||||
SenderID: waid.MakeUserID(sender),
|
SenderID: waid.MakeUserID(sender),
|
||||||
|
|
@ -322,7 +320,7 @@ func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt *
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if wa.IsOwnJID(parsed.Sender) {
|
if parsed.Sender.User == wa.GetStore().GetLID().User || parsed.Sender.User == wa.JID.User {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var key types.JID
|
var key types.JID
|
||||||
|
|
@ -638,7 +636,7 @@ func (wa *WhatsAppClient) getLastMessageInfo(ctx context.Context, chatJID types.
|
||||||
lastTS = msgs[0].Timestamp
|
lastTS = msgs[0].Timestamp
|
||||||
parsed, _ := waid.ParseMessageID(msgs[0].ID)
|
parsed, _ := waid.ParseMessageID(msgs[0].ID)
|
||||||
if parsed != nil {
|
if parsed != nil {
|
||||||
fromMe := wa.IsOwnJID(parsed.Sender)
|
fromMe := parsed.Sender.ToNonAD() == wa.JID.ToNonAD() || parsed.Sender.ToNonAD() == wa.GetStore().GetLID().ToNonAD()
|
||||||
var participant *string
|
var participant *string
|
||||||
if chatJID.Server == types.GroupServer {
|
if chatJID.Server == types.GroupServer {
|
||||||
participant = ptr.Ptr(parsed.Sender.String())
|
participant = ptr.Ptr(parsed.Sender.String())
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,9 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"go.mau.fi/util/exslices"
|
|
||||||
"go.mau.fi/util/ptr"
|
"go.mau.fi/util/ptr"
|
||||||
"go.mau.fi/whatsmeow"
|
"go.mau.fi/whatsmeow"
|
||||||
"go.mau.fi/whatsmeow/appstate"
|
"go.mau.fi/whatsmeow/appstate"
|
||||||
"go.mau.fi/whatsmeow/proto/waCommon"
|
|
||||||
"go.mau.fi/whatsmeow/proto/waE2E"
|
"go.mau.fi/whatsmeow/proto/waE2E"
|
||||||
"go.mau.fi/whatsmeow/types"
|
"go.mau.fi/whatsmeow/types"
|
||||||
"go.mau.fi/whatsmeow/types/events"
|
"go.mau.fi/whatsmeow/types/events"
|
||||||
|
|
@ -115,7 +113,8 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) {
|
||||||
case *events.HistorySync:
|
case *events.HistorySync:
|
||||||
wa.UserLogin.Log.Warn().Msg("Unexpected history sync event received")
|
wa.UserLogin.Log.Warn().Msg("Unexpected history sync event received")
|
||||||
case *events.MediaRetry:
|
case *events.MediaRetry:
|
||||||
success = wa.handleWAMediaRetry(ctx, evt)
|
wa.phoneSeen(evt.Timestamp)
|
||||||
|
success = wa.UserLogin.QueueRemoteEvent(&WAMediaRetry{MediaRetry: evt, wa: wa}).Success
|
||||||
|
|
||||||
case *events.GroupInfo:
|
case *events.GroupInfo:
|
||||||
success = wa.handleWAGroupInfoChange(ctx, evt)
|
success = wa.handleWAGroupInfoChange(ctx, evt)
|
||||||
|
|
@ -159,7 +158,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to update push name in store")
|
log.Err(err).Msg("Failed to update push name in store")
|
||||||
}
|
}
|
||||||
_, _, err = wa.GetStore().Contacts.PutPushName(ctx, wa.GetLID().ToNonAD(), evt.Action.GetName())
|
_, _, err = wa.GetStore().Contacts.PutPushName(ctx, wa.GetStore().GetLID().ToNonAD(), evt.Action.GetName())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to update push name in store")
|
log.Err(err).Msg("Failed to update push name in store")
|
||||||
}
|
}
|
||||||
|
|
@ -204,7 +203,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) {
|
||||||
wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected})
|
wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnected})
|
||||||
wa.notifyOfflineSyncWaiter(nil)
|
wa.notifyOfflineSyncWaiter(nil)
|
||||||
case *events.LoggedOut:
|
case *events.LoggedOut:
|
||||||
wa.handleWALogout(ctx, evt.Reason, evt.OnConnect)
|
wa.handleWALogout(evt.Reason, evt.OnConnect)
|
||||||
wa.notifyOfflineSyncWaiter(fmt.Errorf("logged out: %s", evt.Reason))
|
wa.notifyOfflineSyncWaiter(fmt.Errorf("logged out: %s", evt.Reason))
|
||||||
case *events.Disconnected:
|
case *events.Disconnected:
|
||||||
// Don't send the normal transient disconnect state if we're already in a different transient disconnect state.
|
// Don't send the normal transient disconnect state if we're already in a different transient disconnect state.
|
||||||
|
|
@ -260,38 +259,72 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) ensureAltJIDs(ctx context.Context, info *types.MessageSource, checkPhones bool) bool {
|
func (wa *WhatsAppClient) rerouteWAMessage(ctx context.Context, evtType string, info *types.MessageSource, msgID any) {
|
||||||
var err error
|
if (info.Chat.Server == types.HiddenUserServer || info.Chat.Server == types.BroadcastServer) &&
|
||||||
if info.Sender.Server == types.DefaultUserServer && info.SenderAlt.IsEmpty() {
|
info.Sender.Server == types.HiddenUserServer && info.SenderAlt.IsEmpty() {
|
||||||
info.SenderAlt, err = wa.GetStore().LIDs.GetLIDForPN(ctx, info.Sender)
|
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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Stringer("sender", info.Sender).Msg("Failed to get LID for sender")
|
wa.UserLogin.Log.Err(err).
|
||||||
return false
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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) {
|
func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Message) (success bool) {
|
||||||
success = true
|
success = true
|
||||||
if (evt.Info.Chat == types.StatusBroadcastJID && !wa.Main.Config.EnableStatusBroadcast) ||
|
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
|
return
|
||||||
}
|
}
|
||||||
if !wa.ensureAltJIDs(ctx, &evt.Info.MessageSource, true) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
parsedMessageType := getMessageType(evt.Message)
|
parsedMessageType := getMessageType(evt.Message)
|
||||||
if encReact := evt.Message.GetEncReactionMessage(); encReact != nil {
|
if encReact := evt.Message.GetEncReactionMessage(); encReact != nil {
|
||||||
decrypted, err := wa.Client.DecryptReaction(ctx, evt)
|
decrypted, err := wa.Client.DecryptReaction(ctx, evt)
|
||||||
|
|
@ -325,6 +358,8 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa
|
||||||
evt.UnwrapRaw()
|
evt.UnwrapRaw()
|
||||||
parsedMessageType = getMessageType(evt.Message)
|
parsedMessageType = getMessageType(evt.Message)
|
||||||
}
|
}
|
||||||
|
origSource := evt.Info.MessageSource
|
||||||
|
wa.rerouteWAMessage(ctx, "message", &evt.Info.MessageSource, evt.Info.ID)
|
||||||
wa.UserLogin.Log.Trace().
|
wa.UserLogin.Log.Trace().
|
||||||
Any("info", evt.Info).
|
Any("info", evt.Info).
|
||||||
Any("payload", evt.Message).
|
Any("payload", evt.Message).
|
||||||
|
|
@ -334,15 +369,7 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa
|
||||||
wa.Main.Bridge.Config.Backfill.Enabled {
|
wa.Main.Bridge.Config.Backfill.Enabled {
|
||||||
wa.saveWAHistorySyncNotification(ctx, evt.Message.ProtocolMessage.HistorySyncNotification)
|
wa.saveWAHistorySyncNotification(ctx, evt.Message.ProtocolMessage.HistorySyncNotification)
|
||||||
}
|
}
|
||||||
if parsedMessageType == "ignore" {
|
if parsedMessageType == "ignore" || strings.HasPrefix(parsedMessageType, "unknown_protocol_") {
|
||||||
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -350,8 +377,18 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa
|
||||||
messageAssoc := evt.Message.GetMessageContextInfo().GetMessageAssociation()
|
messageAssoc := evt.Message.GetMessageContextInfo().GetMessageAssociation()
|
||||||
if assocType := messageAssoc.GetAssociationType(); assocType == waE2E.MessageAssociation_HD_IMAGE_DUAL_UPLOAD || assocType == waE2E.MessageAssociation_HD_VIDEO_DUAL_UPLOAD {
|
if assocType := messageAssoc.GetAssociationType(); assocType == waE2E.MessageAssociation_HD_IMAGE_DUAL_UPLOAD || assocType == waE2E.MessageAssociation_HD_VIDEO_DUAL_UPLOAD {
|
||||||
parentKey := messageAssoc.GetParentMessageKey()
|
parentKey := messageAssoc.GetParentMessageKey()
|
||||||
protocolMsg, shouldHideEdit := makeHDMediaReplacementEdit(evt.Message, parentKey)
|
protocolMsg := evt.Message.GetProtocolMessage()
|
||||||
dontRenderEdited = shouldHideEdit
|
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().
|
wa.UserLogin.Log.Debug().
|
||||||
Str("message_id", evt.Info.ID).
|
Str("message_id", evt.Info.ID).
|
||||||
Str("parent_id", parentKey.GetID()).
|
Str("parent_id", parentKey.GetID()).
|
||||||
|
|
@ -373,6 +410,7 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa
|
||||||
|
|
||||||
res := wa.UserLogin.QueueRemoteEvent(&WAMessageEvent{
|
res := wa.UserLogin.QueueRemoteEvent(&WAMessageEvent{
|
||||||
MessageInfoWrapper: &MessageInfoWrapper{
|
MessageInfoWrapper: &MessageInfoWrapper{
|
||||||
|
OrigSource: origSource,
|
||||||
Info: evt.Info,
|
Info: evt.Info,
|
||||||
wa: wa,
|
wa: wa,
|
||||||
},
|
},
|
||||||
|
|
@ -385,30 +423,8 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa
|
||||||
return res.Success
|
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 {
|
func (wa *WhatsAppClient) handleWAUndecryptableMessage(ctx context.Context, evt *events.UndecryptableMessage) bool {
|
||||||
if !wa.ensureAltJIDs(ctx, &evt.Info.MessageSource, true) {
|
wa.rerouteWAMessage(ctx, "undecryptable message", &evt.Info.MessageSource, evt.Info.ID)
|
||||||
return false
|
|
||||||
}
|
|
||||||
wa.UserLogin.Log.Debug().
|
wa.UserLogin.Log.Debug().
|
||||||
Any("info", evt.Info).
|
Any("info", evt.Info).
|
||||||
Bool("unavailable", evt.IsUnavailable).
|
Bool("unavailable", evt.IsUnavailable).
|
||||||
|
|
@ -431,44 +447,12 @@ func (wa *WhatsAppClient) handleWAUndecryptableMessage(ctx context.Context, evt
|
||||||
return res.Success
|
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) {
|
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 {
|
if evt.IsFromMe && evt.Sender.Device == 0 {
|
||||||
wa.phoneSeen(evt.Timestamp)
|
wa.phoneSeen(evt.Timestamp)
|
||||||
}
|
}
|
||||||
if !wa.ensureAltJIDs(ctx, &evt.MessageSource, true) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
var evtType bridgev2.RemoteEventType
|
var evtType bridgev2.RemoteEventType
|
||||||
switch evt.Type {
|
switch evt.Type {
|
||||||
case types.ReceiptTypeRead, types.ReceiptTypeReadSelf:
|
case types.ReceiptTypeRead, types.ReceiptTypeReadSelf:
|
||||||
|
|
@ -480,44 +464,29 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
targets := make([]networkid.MessageID, 0, len(evt.MessageIDs))
|
targets := make([]networkid.MessageID, len(evt.MessageIDs))
|
||||||
messageSender := wa.GetLID()
|
messageSender := wa.JID
|
||||||
if !evt.MessageSender.IsEmpty() {
|
if !evt.MessageSender.IsEmpty() {
|
||||||
messageSender = evt.MessageSender
|
messageSender = evt.MessageSender
|
||||||
} else if evt.Chat.Server == types.NewsletterServer {
|
// Second part of rerouting receipts in LID chats
|
||||||
|
if messageSender == origChat && evt.Chat != origChat {
|
||||||
messageSender = evt.Chat
|
messageSender = evt.Chat
|
||||||
}
|
}
|
||||||
var chatAlt types.JID
|
} else if evt.Chat.Server == types.GroupServer && evt.Sender.Server == types.HiddenUserServer {
|
||||||
if evt.Chat.Server == types.DefaultUserServer {
|
lid := wa.GetStore().GetLID()
|
||||||
chatLID, _ := wa.GetStore().LIDs.GetLIDForPN(ctx, evt.Chat)
|
if !lid.IsEmpty() {
|
||||||
if !chatLID.IsEmpty() {
|
messageSender = lid
|
||||||
chatAlt = evt.Chat
|
|
||||||
evt.Chat = chatLID
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, id := range evt.MessageIDs {
|
for i, id := range evt.MessageIDs {
|
||||||
targets = append(targets, waid.MakeMessageID(evt.Chat, messageSender, id))
|
targets[i] = 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
|
|
||||||
} else if evt.Chat.Server == types.NewsletterServer && evt.Type == types.ReceiptTypeReadSelf {
|
|
||||||
senderLID = wa.GetLID()
|
|
||||||
}
|
}
|
||||||
res := wa.UserLogin.QueueRemoteEvent(&simplevent.Receipt{
|
res := wa.UserLogin.QueueRemoteEvent(&simplevent.Receipt{
|
||||||
EventMeta: simplevent.EventMeta{
|
EventMeta: simplevent.EventMeta{
|
||||||
Type: evtType,
|
Type: evtType,
|
||||||
PortalKey: wa.makeWAPortalKey(evt.Chat),
|
PortalKey: wa.makeWAPortalKey(evt.Chat),
|
||||||
Sender: wa.makeEventSender(ctx, senderLID),
|
Sender: wa.makeEventSender(ctx, evt.Sender),
|
||||||
Timestamp: evt.Timestamp,
|
Timestamp: evt.Timestamp,
|
||||||
LogContext: func(c zerolog.Context) zerolog.Context {
|
|
||||||
return c.
|
|
||||||
Strs("targets", exslices.CastToString[string](targets)).
|
|
||||||
Stringer("receipt_sender", senderLID)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Targets: targets,
|
Targets: targets,
|
||||||
})
|
})
|
||||||
|
|
@ -525,11 +494,11 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) handleWAChatPresence(ctx context.Context, evt *events.ChatPresence) {
|
func (wa *WhatsAppClient) handleWAChatPresence(ctx context.Context, evt *events.ChatPresence) {
|
||||||
if evt.Chat.Server == types.DefaultUserServer && evt.Sender.ToNonAD() == evt.Chat {
|
if evt.Chat.Server == types.HiddenUserServer && evt.Sender.ToNonAD() == evt.Chat {
|
||||||
if evt.SenderAlt.IsEmpty() {
|
if evt.SenderAlt.IsEmpty() {
|
||||||
evt.SenderAlt, _ = wa.GetStore().LIDs.GetLIDForPN(ctx, evt.Sender)
|
evt.SenderAlt, _ = wa.GetStore().LIDs.GetPNForLID(ctx, evt.Sender)
|
||||||
}
|
}
|
||||||
if evt.SenderAlt.Server == types.HiddenUserServer {
|
if evt.SenderAlt.Server == types.DefaultUserServer {
|
||||||
evt.Sender, evt.SenderAlt = evt.SenderAlt, evt.Sender
|
evt.Sender, evt.SenderAlt = evt.SenderAlt, evt.Sender
|
||||||
evt.Chat = evt.Sender.ToNonAD()
|
evt.Chat = evt.Sender.ToNonAD()
|
||||||
}
|
}
|
||||||
|
|
@ -556,7 +525,7 @@ func (wa *WhatsAppClient) handleWAChatPresence(ctx context.Context, evt *events.
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) handleWALogout(ctx context.Context, reason events.ConnectFailureReason, onConnect bool) {
|
func (wa *WhatsAppClient) handleWALogout(reason events.ConnectFailureReason, onConnect bool) {
|
||||||
errorCode := WAUnknownLogout
|
errorCode := WAUnknownLogout
|
||||||
if reason == events.ConnectFailureLoggedOut {
|
if reason == events.ConnectFailureLoggedOut {
|
||||||
errorCode = WALoggedOut
|
errorCode = WALoggedOut
|
||||||
|
|
@ -566,12 +535,7 @@ func (wa *WhatsAppClient) handleWALogout(ctx context.Context, reason events.Conn
|
||||||
wa.Disconnect()
|
wa.Disconnect()
|
||||||
wa.Client = nil
|
wa.Client = nil
|
||||||
wa.JID = types.EmptyJID
|
wa.JID = types.EmptyJID
|
||||||
wa.LID = types.EmptyJID
|
|
||||||
wa.UserLogin.Metadata.(*waid.UserLoginMetadata).WADeviceID = 0
|
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{
|
wa.UserLogin.BridgeState.Send(status.BridgeState{
|
||||||
StateEvent: status.StateBadCredentials,
|
StateEvent: status.StateBadCredentials,
|
||||||
Error: errorCode,
|
Error: errorCode,
|
||||||
|
|
@ -584,15 +548,12 @@ func (wa *WhatsAppClient) handleWACallStart(ctx context.Context, group, sender,
|
||||||
if !wa.Main.Config.CallStartNotices || time.Since(ts) > callEventMaxAge {
|
if !wa.Main.Config.CallStartNotices || time.Since(ts) > callEventMaxAge {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if sender.Server == types.DefaultUserServer && senderAlt.IsEmpty() {
|
if sender.Server == types.HiddenUserServer && senderAlt.Server == types.DefaultUserServer {
|
||||||
senderAlt, _ = wa.GetStore().LIDs.GetLIDForPN(ctx, sender)
|
|
||||||
}
|
|
||||||
if sender.Server == types.DefaultUserServer && senderAlt.Server == types.HiddenUserServer {
|
|
||||||
wa.UserLogin.Log.Debug().
|
wa.UserLogin.Log.Debug().
|
||||||
Stringer("lid", senderAlt).
|
Stringer("lid", sender).
|
||||||
Stringer("pn", sender).
|
Stringer("pn", senderAlt).
|
||||||
Str("call_id", id).
|
Str("call_id", id).
|
||||||
Msg("Forced phone number caller to LID in incoming call")
|
Msg("Forced LID caller to phone number in incoming call")
|
||||||
sender, senderAlt = senderAlt, sender
|
sender, senderAlt = senderAlt, sender
|
||||||
}
|
}
|
||||||
chat := group
|
chat := group
|
||||||
|
|
@ -639,12 +600,6 @@ func (wa *WhatsAppClient) handleWAIdentityChange(ctx context.Context, evt *event
|
||||||
if !wa.Main.Config.IdentityChangeNotices {
|
if !wa.Main.Config.IdentityChangeNotices {
|
||||||
return
|
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]{
|
wa.UserLogin.QueueRemoteEvent(&simplevent.Message[*events.IdentityChange]{
|
||||||
EventMeta: simplevent.EventMeta{
|
EventMeta: simplevent.EventMeta{
|
||||||
Type: bridgev2.RemoteEventMessage,
|
Type: bridgev2.RemoteEventMessage,
|
||||||
|
|
@ -695,14 +650,13 @@ func (wa *WhatsAppClient) handleWADeleteChat(ctx context.Context, evt *events.De
|
||||||
|
|
||||||
func (wa *WhatsAppClient) handleWADeleteForMe(ctx context.Context, evt *events.DeleteForMe) bool {
|
func (wa *WhatsAppClient) handleWADeleteForMe(ctx context.Context, evt *events.DeleteForMe) bool {
|
||||||
chatJID := wa.maybeConvertJIDToLID(ctx, evt.ChatJID)
|
chatJID := wa.maybeConvertJIDToLID(ctx, evt.ChatJID)
|
||||||
senderJID := wa.maybeConvertJIDToLID(ctx, evt.SenderJID)
|
|
||||||
return wa.UserLogin.QueueRemoteEvent(&simplevent.MessageRemove{
|
return wa.UserLogin.QueueRemoteEvent(&simplevent.MessageRemove{
|
||||||
EventMeta: simplevent.EventMeta{
|
EventMeta: simplevent.EventMeta{
|
||||||
Type: bridgev2.RemoteEventMessageRemove,
|
Type: bridgev2.RemoteEventMessageRemove,
|
||||||
PortalKey: wa.makeWAPortalKey(chatJID),
|
PortalKey: wa.makeWAPortalKey(chatJID),
|
||||||
Timestamp: evt.Timestamp,
|
Timestamp: evt.Timestamp,
|
||||||
},
|
},
|
||||||
TargetMessage: waid.MakeMessageID(chatJID, senderJID, evt.MessageID),
|
TargetMessage: waid.MakeMessageID(chatJID, evt.SenderJID, evt.MessageID),
|
||||||
OnlyForMe: true,
|
OnlyForMe: true,
|
||||||
}).Success
|
}).Success
|
||||||
}
|
}
|
||||||
|
|
@ -713,7 +667,7 @@ func (wa *WhatsAppClient) handleWAMarkChatAsRead(ctx context.Context, evt *event
|
||||||
EventMeta: simplevent.EventMeta{
|
EventMeta: simplevent.EventMeta{
|
||||||
Type: bridgev2.RemoteEventReadReceipt,
|
Type: bridgev2.RemoteEventReadReceipt,
|
||||||
PortalKey: wa.makeWAPortalKey(chatJID),
|
PortalKey: wa.makeWAPortalKey(chatJID),
|
||||||
Sender: wa.makeEventSender(ctx, wa.GetLID()),
|
Sender: wa.makeEventSender(ctx, wa.JID),
|
||||||
Timestamp: evt.Timestamp,
|
Timestamp: evt.Timestamp,
|
||||||
},
|
},
|
||||||
ReadUpTo: evt.Timestamp,
|
ReadUpTo: evt.Timestamp,
|
||||||
|
|
@ -736,13 +690,13 @@ func (wa *WhatsAppClient) syncGhost(jid types.JID, reason string, pictureID *str
|
||||||
if pictureID != nil && *pictureID != "" && ghost.AvatarID == networkid.AvatarID(*pictureID) {
|
if pictureID != nil && *pictureID != "" && ghost.AvatarID == networkid.AvatarID(*pictureID) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userInfo, err := wa.getUserInfo(ctx, jid, ptr.Val(pictureID), pictureID != nil)
|
userInfo, err := wa.getUserInfo(ctx, jid, pictureID != nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to get user info")
|
log.Err(err).Msg("Failed to get user info")
|
||||||
} else {
|
} else {
|
||||||
ghost.UpdateInfo(ctx, userInfo)
|
ghost.UpdateInfo(ctx, userInfo)
|
||||||
log.Debug().Msg("Synced ghost info")
|
log.Debug().Msg("Synced ghost info")
|
||||||
wa.syncAltGhostWithInfo(ctx, jid, ghost)
|
wa.syncAltGhostWithInfo(ctx, jid, userInfo)
|
||||||
}
|
}
|
||||||
go wa.syncRemoteProfile(ctx, ghost)
|
go wa.syncRemoteProfile(ctx, ghost)
|
||||||
}
|
}
|
||||||
|
|
@ -802,6 +756,9 @@ func (wa *WhatsAppClient) handleWAGroupInfoChange(ctx context.Context, evt *even
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) handleWAJoinedGroup(ctx context.Context, evt *events.JoinedGroup) bool {
|
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{
|
return wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{
|
||||||
EventMeta: simplevent.EventMeta{
|
EventMeta: simplevent.EventMeta{
|
||||||
Type: bridgev2.RemoteEventChatResync,
|
Type: bridgev2.RemoteEventChatResync,
|
||||||
|
|
@ -814,9 +771,6 @@ func (wa *WhatsAppClient) handleWAJoinedGroup(ctx context.Context, evt *events.J
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) handleWANewsletterJoin(ctx context.Context, evt *events.NewsletterJoin) bool {
|
func (wa *WhatsAppClient) handleWANewsletterJoin(ctx context.Context, evt *events.NewsletterJoin) bool {
|
||||||
if wa.disableNewsletter {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{
|
return wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{
|
||||||
EventMeta: simplevent.EventMeta{
|
EventMeta: simplevent.EventMeta{
|
||||||
Type: bridgev2.RemoteEventChatResync,
|
Type: bridgev2.RemoteEventChatResync,
|
||||||
|
|
@ -915,12 +869,7 @@ func (wa *WhatsAppClient) handleWAAppStateSyncComplete(ctx context.Context, evt
|
||||||
} else {
|
} else {
|
||||||
log.Info().
|
log.Info().
|
||||||
Time("recovery_ts", ts).
|
Time("recovery_ts", ts).
|
||||||
Bool("recovery_evt", evt.Recovery).
|
|
||||||
Msg("Unmarked app state recovery as attempted after successful full sync")
|
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 {
|
} else if ts, exists = wa.appStateFullSyncAttempted[evt.Name]; exists {
|
||||||
delete(wa.appStateFullSyncAttempted, evt.Name)
|
delete(wa.appStateFullSyncAttempted, evt.Name)
|
||||||
|
|
@ -977,9 +926,6 @@ func (wa *WhatsAppClient) handleWAAppStateSyncError(ctx context.Context, evt *ev
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to save login metadata after marking app state recovery as attempted")
|
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() {
|
go func() {
|
||||||
resp, err := wa.Client.SendPeerMessage(ctx, whatsmeow.BuildAppStateRecoveryRequest(evt.Name))
|
resp, err := wa.Client.SendPeerMessage(ctx, whatsmeow.BuildAppStateRecoveryRequest(evt.Name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ func (wa *WhatsAppClient) makeEventSender(ctx context.Context, id types.JID) bri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bridgev2.EventSender{
|
return bridgev2.EventSender{
|
||||||
IsFromMe: wa.IsOwnJID(id),
|
IsFromMe: id.User == wa.GetStore().GetJID().User || id.User == wa.GetStore().GetLID().User,
|
||||||
Sender: waid.MakeUserID(id),
|
Sender: waid.MakeUserID(id),
|
||||||
SenderLogin: waid.MakeUserLoginID(senderLoginJID),
|
SenderLogin: waid.MakeUserLoginID(senderLoginJID),
|
||||||
}
|
}
|
||||||
|
|
@ -60,25 +60,24 @@ func (wa *WhatsAppClient) messageIDToKey(id *waid.ParsedMessageID) *waCommon.Mes
|
||||||
RemoteJID: ptr.Ptr(id.Chat.String()),
|
RemoteJID: ptr.Ptr(id.Chat.String()),
|
||||||
ID: ptr.Ptr(id.ID),
|
ID: ptr.Ptr(id.ID),
|
||||||
}
|
}
|
||||||
if wa.IsOwnJID(id.Sender) {
|
if id.Sender.User == wa.GetStore().GetJID().User || id.Sender.User == wa.GetStore().GetLID().User {
|
||||||
key.FromMe = ptr.Ptr(true)
|
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.HiddenUserServer && id.Chat.Server != types.BotServer {
|
||||||
id.Chat.Server != types.HiddenUserServer && id.Chat.Server != types.BotServer {
|
|
||||||
key.Participant = ptr.Ptr(id.Sender.String())
|
key.Participant = ptr.Ptr(id.Sender.String())
|
||||||
}
|
}
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) maybeConvertJIDToLID(ctx context.Context, jid types.JID) types.JID {
|
func (wa *WhatsAppClient) maybeConvertJIDToLID(ctx context.Context, chatJID types.JID) types.JID {
|
||||||
if jid.Server == types.DefaultUserServer {
|
if chatJID.Server == types.HiddenUserServer {
|
||||||
if lidForPN, err := wa.GetStore().LIDs.GetLIDForPN(ctx, jid); err != nil {
|
if pn, err := wa.GetStore().LIDs.GetPNForLID(ctx, chatJID); err != nil {
|
||||||
wa.UserLogin.Log.Err(err).
|
wa.UserLogin.Log.Err(err).
|
||||||
Stringer("pn", jid).
|
Stringer("lid", chatJID).
|
||||||
Msg("Failed to get LID for phone number chat")
|
Msg("Failed to get phone number for LID chat")
|
||||||
} else if !lidForPN.IsEmpty() {
|
} else if !pn.IsEmpty() {
|
||||||
return lidForPN
|
return pn.ToNonAD()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return jid
|
return chatJID
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,269 +0,0 @@
|
||||||
// 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 <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -99,7 +98,6 @@ func (wa *WhatsAppConnector) CreateLogin(_ context.Context, user *bridgev2.User,
|
||||||
LoginComplete: exsync.NewEvent(),
|
LoginComplete: exsync.NewEvent(),
|
||||||
PasskeyRequest: exsync.NewEvent(),
|
PasskeyRequest: exsync.NewEvent(),
|
||||||
PasskeyConfirmation: exsync.NewEvent(),
|
PasskeyConfirmation: exsync.NewEvent(),
|
||||||
ADVRotate: exsync.NewEvent(),
|
|
||||||
Received515: exsync.NewEvent(),
|
Received515: exsync.NewEvent(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -126,8 +124,6 @@ type WALogin struct {
|
||||||
PasskeyConfirmation *exsync.Event
|
PasskeyConfirmation *exsync.Event
|
||||||
PasskeyConfirmationData *events.PairPasskeyConfirmation
|
PasskeyConfirmationData *events.PairPasskeyConfirmation
|
||||||
|
|
||||||
ADVRotate *exsync.Event
|
|
||||||
|
|
||||||
Closed atomic.Bool
|
Closed atomic.Bool
|
||||||
EventHandlerID uint32
|
EventHandlerID uint32
|
||||||
}
|
}
|
||||||
|
|
@ -139,8 +135,7 @@ var (
|
||||||
_ bridgev2.LoginProcessWebAuthn = (*WALogin)(nil)
|
_ bridgev2.LoginProcessWebAuthn = (*WALogin)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
const LoginConnectWait = 30 * time.Second
|
const LoginConnectWait = 15 * time.Second
|
||||||
const LoginPairPhoneWait = 30 * time.Second
|
|
||||||
|
|
||||||
func (wl *WALogin) Start(ctx context.Context) (*bridgev2.LoginStep, error) {
|
func (wl *WALogin) Start(ctx context.Context) (*bridgev2.LoginStep, error) {
|
||||||
wl.Main.firstClientConnectOnce.Do(wl.Main.onFirstClientConnect)
|
wl.Main.firstClientConnectOnce.Do(wl.Main.onFirstClientConnect)
|
||||||
|
|
@ -198,21 +193,19 @@ func (wl *WALogin) StartWithOverride(ctx context.Context, old *bridgev2.UserLogi
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wl *WALogin) SubmitUserInput(ctx context.Context, input map[string]string) (*bridgev2.LoginStep, error) {
|
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()
|
err := wl.Client.Connect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wl.Log.Err(err).Msg("Failed to connect to WhatsApp for phone code login")
|
wl.Log.Err(err).Msg("Failed to connect to WhatsApp for phone code login")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
connectCtx, cancelConnect := context.WithTimeout(ctx, LoginConnectWait)
|
err = wl.WaitForQRs.Wait(ctx)
|
||||||
err = wl.WaitForQRs.Wait(connectCtx)
|
|
||||||
cancelConnect()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wl.Log.Warn().Err(err).Msg("Timed out waiting for connection")
|
wl.Log.Warn().Err(err).Msg("Timed out waiting for connection")
|
||||||
return nil, fmt.Errorf("failed to wait for connection: %w", err)
|
return nil, fmt.Errorf("failed to wait for connection: %w", err)
|
||||||
}
|
}
|
||||||
pairCtx, cancelPair := context.WithTimeout(ctx, LoginPairPhoneWait)
|
pairingCode, err := wl.Client.PairPhone(ctx, input["phone_number"], true, whatsmeow.PairClientChrome, "Chrome (Linux)")
|
||||||
defer cancelPair()
|
|
||||||
pairingCode, err := wl.Client.PairPhone(pairCtx, input["phone_number"], true, whatsmeow.PairClientChrome, "Chrome (Linux)")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wl.Log.Err(err).Msg("Failed to request phone code login")
|
wl.Log.Err(err).Msg("Failed to request phone code login")
|
||||||
if errors.Is(err, whatsmeow.ErrPhoneNumberTooShort) {
|
if errors.Is(err, whatsmeow.ErrPhoneNumberTooShort) {
|
||||||
|
|
@ -274,12 +267,6 @@ func (wl *WALogin) handleEvent(rawEvt any) {
|
||||||
wl.StartTime = time.Now()
|
wl.StartTime = time.Now()
|
||||||
wl.WaitForQRs.Set()
|
wl.WaitForQRs.Set()
|
||||||
return
|
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:
|
case *events.QRScannedWithoutMultidevice:
|
||||||
wl.Log.Error().Msg("QR code scanned without multidevice enabled")
|
wl.Log.Error().Msg("QR code scanned without multidevice enabled")
|
||||||
wl.LoginError = ErrLoginMultideviceNotEnabled
|
wl.LoginError = ErrLoginMultideviceNotEnabled
|
||||||
|
|
@ -342,7 +329,6 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) {
|
||||||
Int("current_index", currentIndex)
|
Int("current_index", currentIndex)
|
||||||
if currentIndex > prevIndex {
|
if currentIndex > prevIndex {
|
||||||
logEvt.Msg("Returning new QR immediately")
|
logEvt.Msg("Returning new QR immediately")
|
||||||
wl.ADVRotate.Clear()
|
|
||||||
wl.PrevQRIndex.Store(int32(currentIndex))
|
wl.PrevQRIndex.Store(int32(currentIndex))
|
||||||
return makeQRStep(wl.QRs[currentIndex]), nil
|
return makeQRStep(wl.QRs[currentIndex]), nil
|
||||||
}
|
}
|
||||||
|
|
@ -354,7 +340,6 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) {
|
||||||
wl.Cancel()
|
wl.Cancel()
|
||||||
return nil, ErrLoginTimeout
|
return nil, ErrLoginTimeout
|
||||||
}
|
}
|
||||||
wl.ADVRotate.Clear()
|
|
||||||
wl.PrevQRIndex.Store(int32(nextIndex))
|
wl.PrevQRIndex.Store(int32(nextIndex))
|
||||||
return makeQRStep(wl.QRs[nextIndex]), nil
|
return makeQRStep(wl.QRs[nextIndex]), nil
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
|
@ -362,10 +347,6 @@ func (wl *WALogin) Wait(ctx context.Context) (*bridgev2.LoginStep, error) {
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
case <-wl.PasskeyRequest.GetChan():
|
case <-wl.PasskeyRequest.GetChan():
|
||||||
return wl.makePasskeyStep()
|
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():
|
case <-wl.LoginComplete.GetChan():
|
||||||
// continue
|
// continue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,8 +155,8 @@ func (wa *WhatsAppClient) sendMediaRequestDirect(ctx context.Context, rawMsgID n
|
||||||
return wa.Client.SendMediaRetryReceipt(ctx, &types.MessageInfo{
|
return wa.Client.SendMediaRetryReceipt(ctx, &types.MessageInfo{
|
||||||
ID: msgID.ID,
|
ID: msgID.ID,
|
||||||
MessageSource: types.MessageSource{
|
MessageSource: types.MessageSource{
|
||||||
IsFromMe: wa.IsOwnJID(msgID.Sender),
|
IsFromMe: msgID.Sender.User == wa.JID.User,
|
||||||
IsGroup: msgID.Chat.Server != types.DefaultUserServer && msgID.Chat.Server != types.HiddenUserServer && msgID.Chat.Server != types.BotServer,
|
IsGroup: msgID.Chat.Server != types.DefaultUserServer && msgID.Chat.Server != types.BotServer,
|
||||||
Sender: msgID.Sender,
|
Sender: msgID.Sender,
|
||||||
Chat: msgID.Chat,
|
Chat: msgID.Chat,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"go.mau.fi/util/exmaps"
|
|
||||||
"go.mau.fi/util/exsync"
|
"go.mau.fi/util/exsync"
|
||||||
"go.mau.fi/util/ptr"
|
"go.mau.fi/util/ptr"
|
||||||
"go.mau.fi/whatsmeow"
|
"go.mau.fi/whatsmeow"
|
||||||
|
|
@ -64,12 +63,12 @@ func looksEmaily(str string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type isOnWhatsappCacheEntry struct {
|
type cacheEntry struct {
|
||||||
jid types.JID
|
jid types.JID
|
||||||
ts time.Time
|
ts time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
var isOnWhatsappCache = exsync.NewMap[string, isOnWhatsappCacheEntry]()
|
var isOnWhatsappCache = exsync.NewMap[string, cacheEntry]()
|
||||||
|
|
||||||
func (wa *WhatsAppClient) validateIdentifer(ctx context.Context, 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) {
|
if strings.HasSuffix(number, "@"+types.BotServer) || strings.HasSuffix(number, "@"+types.HiddenUserServer) {
|
||||||
|
|
@ -94,7 +93,7 @@ func (wa *WhatsAppClient) validateIdentifer(ctx context.Context, number string)
|
||||||
} else if !resp[0].IsIn {
|
} 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)
|
return types.EmptyJID, bridgev2.WrapRespErr(fmt.Errorf("the server said +%s is not on WhatsApp", resp[0].JID.User), mautrix.MNotFound)
|
||||||
} else {
|
} else {
|
||||||
isOnWhatsappCache.Set(number, isOnWhatsappCacheEntry{resp[0].JID, time.Now()})
|
isOnWhatsappCache.Set(number, cacheEntry{resp[0].JID, time.Now()})
|
||||||
return resp[0].JID, nil
|
return resp[0].JID, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -120,24 +119,16 @@ func (wa *WhatsAppConnector) ValidateUserID(id networkid.UserID) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) startChatPNToLID(ctx context.Context, jid types.JID) (types.JID, error) {
|
func (wa *WhatsAppClient) startChatLIDToPN(ctx context.Context, jid types.JID) (types.JID, error) {
|
||||||
if jid.Server == types.DefaultUserServer {
|
if jid.Server == types.HiddenUserServer {
|
||||||
lid, err := wa.GetStore().LIDs.GetLIDForPN(ctx, jid)
|
pn, err := wa.GetStore().LIDs.GetPNForLID(ctx, jid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return jid, fmt.Errorf("failed to get lid for phone number: %w", err)
|
return jid, fmt.Errorf("failed to get phone number for lid: %w", err)
|
||||||
} else if lid.IsEmpty() {
|
} else if pn.IsEmpty() {
|
||||||
resp, err := wa.Client.GetUserInfo(ctx, []types.JID{jid})
|
// Don't allow starting chats with LIDs for now
|
||||||
if err != nil {
|
return jid, fmt.Errorf("phone number not found")
|
||||||
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
|
return jid, nil
|
||||||
}
|
}
|
||||||
|
|
@ -156,7 +147,7 @@ func (wa *WhatsAppClient) makeCreateChatResponse(ctx context.Context, jid, origJ
|
||||||
|
|
||||||
func (wa *WhatsAppClient) CreateChatWithGhost(ctx context.Context, ghost *bridgev2.Ghost) (*bridgev2.CreateChatResponse, error) {
|
func (wa *WhatsAppClient) CreateChatWithGhost(ctx context.Context, ghost *bridgev2.Ghost) (*bridgev2.CreateChatResponse, error) {
|
||||||
origJID := waid.ParseUserID(ghost.ID)
|
origJID := waid.ParseUserID(ghost.ID)
|
||||||
jid, err := wa.startChatPNToLID(ctx, origJID)
|
jid, err := wa.startChatLIDToPN(ctx, origJID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -168,7 +159,7 @@ func (wa *WhatsAppClient) ResolveIdentifier(ctx context.Context, identifier stri
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
jid, err := wa.startChatPNToLID(ctx, origJID)
|
jid, err := wa.startChatLIDToPN(ctx, origJID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -176,15 +167,10 @@ func (wa *WhatsAppClient) ResolveIdentifier(ctx context.Context, identifier stri
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get ghost: %w", err)
|
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{
|
return &bridgev2.ResolveIdentifierResponse{
|
||||||
Ghost: ghost,
|
Ghost: ghost,
|
||||||
UserID: waid.MakeUserID(jid),
|
UserID: waid.MakeUserID(jid),
|
||||||
UserInfo: userInfo,
|
|
||||||
Chat: wa.makeCreateChatResponse(ctx, jid, origJID),
|
Chat: wa.makeCreateChatResponse(ctx, jid, origJID),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -213,7 +199,6 @@ func (wa *WhatsAppClient) getContactList(ctx context.Context, filter string, onl
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resp := make([]*bridgev2.ResolveIdentifierResponse, 0, len(contacts))
|
resp := make([]*bridgev2.ResolveIdentifierResponse, 0, len(contacts))
|
||||||
addedIDs := make(exmaps.Set[types.JID])
|
|
||||||
for jid, contactInfo := range contacts {
|
for jid, contactInfo := range contacts {
|
||||||
if onlyContacts && (contactInfo.FirstName == "" && contactInfo.FullName == "") {
|
if onlyContacts && (contactInfo.FirstName == "" && contactInfo.FullName == "") {
|
||||||
continue
|
continue
|
||||||
|
|
@ -221,43 +206,31 @@ func (wa *WhatsAppClient) getContactList(ctx context.Context, filter string, onl
|
||||||
if !matchesQuery(contactInfo.PushName, filter) && !matchesQuery(contactInfo.FullName, filter) && !matchesQuery(jid.User, filter) {
|
if !matchesQuery(contactInfo.PushName, filter) && !matchesQuery(contactInfo.FullName, filter) && !matchesQuery(jid.User, filter) {
|
||||||
continue
|
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))
|
ghost, _ := wa.Main.Bridge.GetGhostByID(ctx, waid.MakeUserID(jid))
|
||||||
resp = append(resp, &bridgev2.ResolveIdentifierResponse{
|
resp = append(resp, &bridgev2.ResolveIdentifierResponse{
|
||||||
Ghost: ghost,
|
Ghost: ghost,
|
||||||
UserID: waid.MakeUserID(jid),
|
UserID: waid.MakeUserID(jid),
|
||||||
UserInfo: wa.contactToUserInfo(ctx, jid, contactInfo, "", false),
|
UserInfo: wa.contactToUserInfo(ctx, jid, contactInfo, false),
|
||||||
Chat: chatResp,
|
Chat: &bridgev2.CreateChatResponse{PortalKey: wa.makeWAPortalKey(jid)},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) CreateGroup(ctx context.Context, params *bridgev2.GroupCreateParams) (*bridgev2.CreateChatResponse, error) {
|
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{
|
req := whatsmeow.ReqCreateGroup{
|
||||||
Name: ptr.Val(params.Name).Name,
|
Name: ptr.Val(params.Name).Name,
|
||||||
Participants: make([]types.JID, len(params.Participants)),
|
Participants: make([]types.JID, len(params.Participants)),
|
||||||
|
CreateKey: createKey,
|
||||||
}
|
}
|
||||||
for i, participant := range params.Participants {
|
for i, participant := range params.Participants {
|
||||||
jid := waid.ParseUserID(participant)
|
jid := waid.ParseUserID(participant)
|
||||||
jid, err := wa.startChatPNToLID(ctx, jid)
|
// Normalize to PN if it's a LID
|
||||||
|
jid, err := wa.startChatLIDToPN(ctx, jid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to normalize participant %s: %w", participant, err)
|
return nil, fmt.Errorf("failed to normalize participant %s: %w", participant, err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -165,13 +165,13 @@ 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")
|
log.Warn().Stringer("jid", jid).Msg("Didn't get info for puppet in background sync")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
userInfo, err := wa.getUserInfo(ctx, jid, info.PictureID, info.PictureID != "" && string(ghost.AvatarID) != info.PictureID)
|
userInfo, err := wa.getUserInfo(ctx, jid, info.PictureID != "" && string(ghost.AvatarID) != info.PictureID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Stringer("jid", jid).Msg("Failed to get user info for puppet in background sync")
|
log.Err(err).Stringer("jid", jid).Msg("Failed to get user info for puppet in background sync")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ghost.UpdateInfo(ctx, userInfo)
|
ghost.UpdateInfo(ctx, userInfo)
|
||||||
wa.syncAltGhostWithInfo(ctx, jid, ghost)
|
wa.syncAltGhostWithInfo(ctx, jid, userInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,18 +181,18 @@ func (wa *WhatsAppClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
jid := waid.ParseUserID(ghost.ID)
|
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, avatarID string, fetchAvatar bool) (*bridgev2.UserInfo, error) {
|
func (wa *WhatsAppClient) getUserInfo(ctx context.Context, jid types.JID, fetchAvatar bool) (*bridgev2.UserInfo, error) {
|
||||||
contact, err := wa.GetStore().Contacts.GetContact(ctx, jid)
|
contact, err := wa.GetStore().Contacts.GetContact(ctx, jid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return wa.contactToUserInfo(ctx, jid, contact, avatarID, fetchAvatar), nil
|
return wa.contactToUserInfo(ctx, jid, contact, fetchAvatar), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) contactToUserInfo(ctx context.Context, jid types.JID, contact types.ContactInfo, avatarID string, fetchAvatar 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 {
|
if jid == types.MetaAIJID && contact.PushName == jid.User {
|
||||||
contact.PushName = "Meta AI"
|
contact.PushName = "Meta AI"
|
||||||
} else if jid == types.LegacyPSAJID || jid == types.PSAJID {
|
} else if jid == types.LegacyPSAJID || jid == types.PSAJID {
|
||||||
|
|
@ -270,9 +270,7 @@ func (wa *WhatsAppClient) contactToUserInfo(ctx context.Context, jid types.JID,
|
||||||
} else if phone != "" {
|
} else if phone != "" {
|
||||||
ui.Identifiers = []string{fmt.Sprintf("tel:%s", phone)}
|
ui.Identifiers = []string{fmt.Sprintf("tel:%s", phone)}
|
||||||
}
|
}
|
||||||
if wa.Main.Config.LazyAvatars {
|
if getAvatar {
|
||||||
ui.ExtraUpdates = bridgev2.MergeExtraUpdaters(ui.ExtraUpdates, wa.makeLazyGhostAvatarUpdater(avatarID, fetchAvatar))
|
|
||||||
} else if fetchAvatar {
|
|
||||||
ui.ExtraUpdates = bridgev2.MergeExtraUpdaters(ui.ExtraUpdates, wa.fetchGhostAvatar)
|
ui.ExtraUpdates = bridgev2.MergeExtraUpdaters(ui.ExtraUpdates, wa.fetchGhostAvatar)
|
||||||
}
|
}
|
||||||
return ui
|
return ui
|
||||||
|
|
@ -309,21 +307,6 @@ 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) {
|
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))
|
mxc, err := wa.Main.Bridge.Matrix.GenerateContentURI(ctx, waid.MakeAvatarMediaID(jid, avatar.ID, wa.UserLogin.ID, community))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -345,21 +328,6 @@ func (wa *WhatsAppClient) makeDirectMediaAvatar(ctx context.Context, jid types.J
|
||||||
}, nil
|
}, 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 {
|
func (wa *WhatsAppClient) fetchGhostAvatar(ctx context.Context, ghost *bridgev2.Ghost) bool {
|
||||||
jid := waid.ParseUserID(ghost.ID)
|
jid := waid.ParseUserID(ghost.ID)
|
||||||
existingID := string(ghost.AvatarID)
|
existingID := string(ghost.AvatarID)
|
||||||
|
|
@ -431,14 +399,14 @@ func (wa *WhatsAppClient) resyncContacts(forceAvatarSync, automatic bool) {
|
||||||
} else if contact, err := contactStore.GetContact(ctx, jid); err != nil {
|
} else if contact, err := contactStore.GetContact(ctx, jid); err != nil {
|
||||||
log.Err(err).Stringer("jid", jid).Msg("Failed to get contact info")
|
log.Err(err).Stringer("jid", jid).Msg("Failed to get contact info")
|
||||||
} else {
|
} else {
|
||||||
userInfo := wa.contactToUserInfo(ctx, jid, contact, "", forceAvatarSync || ghost.AvatarID == "")
|
userInfo := wa.contactToUserInfo(ctx, jid, contact, forceAvatarSync || ghost.AvatarID == "")
|
||||||
ghost.UpdateInfo(ctx, userInfo)
|
ghost.UpdateInfo(ctx, userInfo)
|
||||||
wa.syncAltGhostWithInfo(ctx, jid, ghost)
|
wa.syncAltGhostWithInfo(ctx, jid, userInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) syncAltGhostWithInfo(ctx context.Context, jid types.JID, mainGhost *bridgev2.Ghost) {
|
func (wa *WhatsAppClient) syncAltGhostWithInfo(ctx context.Context, jid types.JID, info *bridgev2.UserInfo) {
|
||||||
log := zerolog.Ctx(ctx)
|
log := zerolog.Ctx(ctx)
|
||||||
var altJID types.JID
|
var altJID types.JID
|
||||||
var err error
|
var err error
|
||||||
|
|
@ -463,25 +431,10 @@ func (wa *WhatsAppClient) syncAltGhostWithInfo(ctx context.Context, jid types.JI
|
||||||
Msg("Failed to get ghost for alternate JID")
|
Msg("Failed to get ghost for alternate JID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ghost.UpdateInfo(ctx, makeInfoFromGhost(mainGhost))
|
ghost.UpdateInfo(ctx, info)
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Stringer("jid", jid).
|
Stringer("jid", jid).
|
||||||
Stringer("alternate_jid", altJID).
|
Stringer("alternate_jid", altJID).
|
||||||
Msg("Synced alternate ghost with info")
|
Msg("Synced alternate ghost with info")
|
||||||
go wa.syncRemoteProfile(ctx, ghost)
|
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,10 @@ package wadb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.mau.fi/util/dbutil"
|
"go.mau.fi/util/dbutil"
|
||||||
"go.mau.fi/util/jsontime"
|
"go.mau.fi/util/jsontime"
|
||||||
"go.mau.fi/whatsmeow/types"
|
"go.mau.fi/whatsmeow/types"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-whatsapp/pkg/waid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AvatarCacheQuery struct {
|
type AvatarCacheQuery struct {
|
||||||
|
|
@ -53,13 +49,3 @@ func (ace *AvatarCacheEntry) Scan(row dbutil.Scannable) (*AvatarCacheEntry, erro
|
||||||
func (ace *AvatarCacheEntry) sqlVariables() []any {
|
func (ace *AvatarCacheEntry) sqlVariables() []any {
|
||||||
return []any{ace.EntityJID, ace.AvatarID, ace.DirectPath, ace.Expiry, ace.Gone}
|
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))
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -96,12 +96,12 @@ func (mq *MessageQuery) GetBetween(ctx context.Context, loginID networkid.UserLo
|
||||||
args := []any{mq.BridgeID, loginID, chatJID}
|
args := []any{mq.BridgeID, loginID, chatJID}
|
||||||
argNum := 4
|
argNum := 4
|
||||||
if startTime != nil {
|
if startTime != nil {
|
||||||
whereClauses += fmt.Sprintf(" AND timestamp > $%d", argNum)
|
whereClauses += fmt.Sprintf(" AND timestamp >= $%d", argNum)
|
||||||
args = append(args, startTime.Unix())
|
args = append(args, startTime.Unix())
|
||||||
argNum++
|
argNum++
|
||||||
}
|
}
|
||||||
if endTime != nil {
|
if endTime != nil {
|
||||||
whereClauses += fmt.Sprintf(" AND timestamp < $%d", argNum)
|
whereClauses += fmt.Sprintf(" AND timestamp <= $%d", argNum)
|
||||||
args = append(args, endTime.Unix())
|
args = append(args, endTime.Unix())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
-- v0 -> v10 (compatible with v3+): Latest revision
|
-- v0 -> v9 (compatible with v3+): Latest revision
|
||||||
|
|
||||||
CREATE TABLE whatsapp_poll_option_id (
|
CREATE TABLE whatsapp_poll_option_id (
|
||||||
bridge_id TEXT NOT NULL,
|
bridge_id TEXT NOT NULL,
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
@ -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');
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
-- 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=(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='';
|
|
||||||
|
|
@ -6,9 +6,11 @@ import (
|
||||||
"go.mau.fi/util/dbutil"
|
"go.mau.fi/util/dbutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var Table dbutil.UpgradeTable
|
||||||
|
|
||||||
//go:embed *.sql
|
//go:embed *.sql
|
||||||
var rawUpgrades embed.FS
|
var rawUpgrades embed.FS
|
||||||
|
|
||||||
var Table = dbutil.BuildUpgradeTable().
|
func init() {
|
||||||
WithFS(rawUpgrades).
|
Table.RegisterFS(rawUpgrades)
|
||||||
Finish()
|
}
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ func (mc *MessageConverter) ToWhatsApp(
|
||||||
}
|
}
|
||||||
lid := parsedID.Sender
|
lid := parsedID.Sender
|
||||||
if lid.Server == types.DefaultUserServer {
|
if lid.Server == types.DefaultUserServer {
|
||||||
lid, err = client.Store.LIDs.GetLIDForPN(ctx, lid)
|
lid, err = client.Store.LIDs.GetLIDForPN(ctx, parsedID.Sender)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to get LID for PN: %w", err)
|
return nil, nil, fmt.Errorf("failed to get LID for PN: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,7 @@ func (mc *MessageConverter) ToMatrix(
|
||||||
waMsg *waE2E.Message,
|
waMsg *waE2E.Message,
|
||||||
rawWaMsg *waE2E.Message,
|
rawWaMsg *waE2E.Message,
|
||||||
info *types.MessageInfo,
|
info *types.MessageInfo,
|
||||||
|
origSource *types.MessageSource,
|
||||||
isViewOnce bool,
|
isViewOnce bool,
|
||||||
isBackfill bool,
|
isBackfill bool,
|
||||||
previouslyConvertedPart *bridgev2.ConvertedMessagePart,
|
previouslyConvertedPart *bridgev2.ConvertedMessagePart,
|
||||||
|
|
@ -182,7 +183,7 @@ func (mc *MessageConverter) ToMatrix(
|
||||||
case waMsg.PollCreationMessageV6 != nil:
|
case waMsg.PollCreationMessageV6 != nil:
|
||||||
part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV6)
|
part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV6)
|
||||||
case waMsg.PollUpdateMessage != nil:
|
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:
|
case waMsg.EventMessage != nil:
|
||||||
part, contextInfo = mc.convertEventMessage(ctx, waMsg.EventMessage)
|
part, contextInfo = mc.convertEventMessage(ctx, waMsg.EventMessage)
|
||||||
case waMsg.PinInChatMessage != nil:
|
case waMsg.PinInChatMessage != nil:
|
||||||
|
|
@ -190,7 +191,7 @@ func (mc *MessageConverter) ToMatrix(
|
||||||
case waMsg.KeepInChatMessage != nil:
|
case waMsg.KeepInChatMessage != nil:
|
||||||
part, contextInfo = mc.convertKeepInChatMessage(ctx, waMsg.KeepInChatMessage)
|
part, contextInfo = mc.convertKeepInChatMessage(ctx, waMsg.KeepInChatMessage)
|
||||||
case waMsg.RichResponseMessage != nil:
|
case waMsg.RichResponseMessage != nil:
|
||||||
part, contextInfo = mc.convertRichResponseMessage(ctx, waMsg.RichResponseMessage, waMsg)
|
part, contextInfo = mc.convertRichResponseMessage(ctx, waMsg.RichResponseMessage)
|
||||||
case waMsg.ImageMessage != nil:
|
case waMsg.ImageMessage != nil:
|
||||||
part, contextInfo = mc.convertMediaMessage(ctx, waMsg.ImageMessage, "photo", info, isViewOnce, previouslyConvertedPart)
|
part, contextInfo = mc.convertMediaMessage(ctx, waMsg.ImageMessage, "photo", info, isViewOnce, previouslyConvertedPart)
|
||||||
case waMsg.StickerMessage != nil:
|
case waMsg.StickerMessage != nil:
|
||||||
|
|
@ -271,26 +272,30 @@ func (mc *MessageConverter) ToMatrix(
|
||||||
if chat.IsEmpty() {
|
if chat.IsEmpty() {
|
||||||
chat, _ = waid.ParsePortalID(portal.ID)
|
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{
|
cm.ReplyTo = &networkid.MessageOptionalPartID{
|
||||||
MessageID: waid.MakeMessageID(chat, pcp, contextInfo.GetStanzaID()),
|
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() {
|
if contextInfo.GetIsForwarded() {
|
||||||
hasCaption := part.Content.FileName != "" && part.Content.FileName != part.Content.Body
|
hasCaption := part.Content.FileName != "" && part.Content.FileName != part.Content.Body
|
||||||
|
|
|
||||||
|
|
@ -351,7 +351,7 @@ func (mc *MessageConverter) convertKeepInChatMessage(ctx context.Context, msg *w
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *MessageConverter) convertRichResponseMessage(ctx context.Context, msg *waE2E.AIRichResponseMessage, fullMsg *waE2E.Message) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) {
|
func (mc *MessageConverter) convertRichResponseMessage(ctx context.Context, msg *waE2E.AIRichResponseMessage) (*bridgev2.ConvertedMessagePart, *waE2E.ContextInfo) {
|
||||||
var body strings.Builder
|
var body strings.Builder
|
||||||
|
|
||||||
// TODO switch to new format?
|
// TODO switch to new format?
|
||||||
|
|
@ -363,14 +363,10 @@ func (mc *MessageConverter) convertRichResponseMessage(ctx context.Context, msg
|
||||||
body.WriteString(submsg.GetMessageText())
|
body.WriteString(submsg.GetMessageText())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unknownPart, _ := mc.convertUnknownMessage(ctx, fullMsg)
|
|
||||||
if body.Len() == 0 {
|
|
||||||
return unknownPart, msg.GetContextInfo()
|
|
||||||
}
|
|
||||||
content := format.RenderMarkdown(body.String(), true, false)
|
content := format.RenderMarkdown(body.String(), true, false)
|
||||||
return &bridgev2.ConvertedMessagePart{
|
return &bridgev2.ConvertedMessagePart{
|
||||||
Type: event.EventMessage,
|
Type: event.EventMessage,
|
||||||
Content: &content,
|
Content: &content,
|
||||||
Extra: unknownPart.Extra,
|
|
||||||
}, msg.GetContextInfo()
|
}, msg.GetContextInfo()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,31 @@ func (mc *MessageConverter) convertPollCreationMessage(ctx context.Context, msg
|
||||||
}, msg.GetContextInfo()
|
}, 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 {
|
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()
|
sender = sender.ToNonAD()
|
||||||
var err error
|
var err error
|
||||||
if !key.GetFromMe() {
|
if !key.GetFromMe() {
|
||||||
|
|
@ -107,7 +131,7 @@ func KeyToMessageID(ctx context.Context, client *whatsmeow.Client, chat, sender
|
||||||
if sender.Server == types.LegacyUserServer {
|
if sender.Server == types.LegacyUserServer {
|
||||||
sender.Server = types.DefaultUserServer
|
sender.Server = types.DefaultUserServer
|
||||||
}
|
}
|
||||||
} else if chat.Server == types.DefaultUserServer || chat.Server == types.HiddenUserServer || chat.Server == types.BotServer {
|
} else if chat.Server == types.DefaultUserServer || chat.Server == types.BotServer {
|
||||||
if sender.User == client.Store.GetJID().User || sender.User == client.Store.GetLID().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,
|
// 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
|
// so message key sender is the other user in the DM
|
||||||
|
|
@ -115,12 +139,8 @@ func KeyToMessageID(ctx context.Context, client *whatsmeow.Client, chat, sender
|
||||||
} else {
|
} else {
|
||||||
// Message key is not from the sender, but message sender (containing key) is not me,
|
// Message key is not from the sender, but message sender (containing key) is not me,
|
||||||
// so message key sender is me
|
// so message key sender is me
|
||||||
if chat.Server == types.HiddenUserServer {
|
|
||||||
sender = client.Store.GetLID().ToNonAD()
|
|
||||||
} else {
|
|
||||||
sender = client.Store.GetJID().ToNonAD()
|
sender = client.Store.GetJID().ToNonAD()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
zerolog.Ctx(ctx).Warn().
|
zerolog.Ctx(ctx).Warn().
|
||||||
Stringer("chat", chat).
|
Stringer("chat", chat).
|
||||||
|
|
@ -137,6 +157,10 @@ func KeyToMessageID(ctx context.Context, client *whatsmeow.Client, chat, sender
|
||||||
chat = remoteJID
|
chat = remoteJID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sender = rerouteMessageKey(
|
||||||
|
context.WithValue(ctx, contextKeyClient, client),
|
||||||
|
chat, sender, groupLIDAddressing,
|
||||||
|
)
|
||||||
return waid.MakeMessageID(chat, sender, key.GetID())
|
return waid.MakeMessageID(chat, sender, key.GetID())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,7 +170,7 @@ var failedPollUpdatePart = &bridgev2.ConvertedMessagePart{
|
||||||
DontBridge: true,
|
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)
|
log := zerolog.Ctx(ctx)
|
||||||
pollMessageID := KeyToMessageID(ctx, 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, "")
|
pollMessage, err := mc.Bridge.DB.Message.GetPartByID(ctx, getPortal(ctx).Receiver, pollMessageID, "")
|
||||||
|
|
@ -157,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")
|
log.Warn().Str("target_message_id", string(pollMessageID)).Msg("Poll update target message not found")
|
||||||
return failedPollUpdatePart, nil
|
return failedPollUpdatePart, nil
|
||||||
}
|
}
|
||||||
|
infoForDecrypt := *info
|
||||||
|
if origSource != nil {
|
||||||
|
infoForDecrypt.MessageSource = *origSource
|
||||||
|
}
|
||||||
vote, err := getClient(ctx).DecryptPollVote(ctx, &events.Message{
|
vote, err := getClient(ctx).DecryptPollVote(ctx, &events.Message{
|
||||||
Info: *info,
|
Info: infoForDecrypt,
|
||||||
Message: &waE2E.Message{PollUpdateMessage: msg},
|
Message: &waE2E.Message{PollUpdateMessage: msg},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,12 @@ type UserLoginMetadata struct {
|
||||||
PushKeys *PushKeys `json:"push_keys,omitempty"`
|
PushKeys *PushKeys `json:"push_keys,omitempty"`
|
||||||
APNSEncPubKey []byte `json:"apns_enc_pubkey,omitempty"`
|
APNSEncPubKey []byte `json:"apns_enc_pubkey,omitempty"`
|
||||||
APNSEncPrivKey []byte `json:"apns_enc_privkey,omitempty"`
|
APNSEncPrivKey []byte `json:"apns_enc_privkey,omitempty"`
|
||||||
LoggedInAt jsontime.Unix `json:"logged_in_at,omitzero"`
|
LoggedInAt jsontime.Unix `json:"logged_in_at,omitempty"`
|
||||||
|
|
||||||
AppStateRecoveryAttempted map[appstate.WAPatchName]time.Time `json:"app_state_recovery_attempted,omitempty"`
|
AppStateRecoveryAttempted map[appstate.WAPatchName]time.Time `json:"app_state_recovery_attempted,omitempty"`
|
||||||
|
|
||||||
HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitzero"`
|
HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitempty"`
|
||||||
ReachoutTimelockUntil time.Time `json:"reachout_timelock_until,omitzero"`
|
ReachoutTimelockUntil time.Time `json:"reachout_timelock_until,omitempty"`
|
||||||
|
|
||||||
MData json.RawMessage `json:"mdata,omitempty"`
|
MData json.RawMessage `json:"mdata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
@ -88,13 +88,13 @@ type GroupInviteMeta struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type MessageMetadata struct {
|
type MessageMetadata struct {
|
||||||
SenderDeviceID uint16 `json:"sender_device_id,omitzero"`
|
SenderDeviceID uint16 `json:"sender_device_id,omitempty"`
|
||||||
Error MessageErrorType `json:"error,omitempty"`
|
Error MessageErrorType `json:"error,omitempty"`
|
||||||
BroadcastListJID *types.JID `json:"broadcast_list_jid,omitempty"`
|
BroadcastListJID *types.JID `json:"broadcast_list_jid,omitempty"`
|
||||||
GroupInvite *GroupInviteMeta `json:"group_invite,omitempty"`
|
GroupInvite *GroupInviteMeta `json:"group_invite,omitempty"`
|
||||||
FailedMediaMeta json.RawMessage `json:"media_meta,omitempty"`
|
FailedMediaMeta json.RawMessage `json:"media_meta,omitempty"`
|
||||||
DirectMediaMeta json.RawMessage `json:"direct_media_meta,omitempty"`
|
DirectMediaMeta json.RawMessage `json:"direct_media_meta,omitempty"`
|
||||||
IsMatrixPoll bool `json:"is_matrix_poll,omitzero"`
|
IsMatrixPoll bool `json:"is_matrix_poll,omitempty"`
|
||||||
Edits []types.MessageID `json:"edits,omitempty"`
|
Edits []types.MessageID `json:"edits,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,16 +122,14 @@ type ReactionMetadata struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type PortalMetadata struct {
|
type PortalMetadata struct {
|
||||||
DisappearingTimerSetAt int64 `json:"disappearing_timer_set_at,omitzero"`
|
DisappearingTimerSetAt int64 `json:"disappearing_timer_set_at,omitempty"`
|
||||||
TopicID string `json:"topic_id,omitempty"`
|
TopicID string `json:"topic_id,omitempty"`
|
||||||
LastSync jsontime.Unix `json:"last_sync,omitzero"`
|
LastSync jsontime.Unix `json:"last_sync,omitempty"`
|
||||||
CommunityAnnouncementGroup bool `json:"is_cag,omitzero"`
|
CommunityAnnouncementGroup bool `json:"is_cag,omitempty"`
|
||||||
AddressingMode types.AddressingMode `json:"addressing_mode,omitempty"`
|
AddressingMode types.AddressingMode `json:"addressing_mode,omitempty"`
|
||||||
LIDMigrationAttempted bool `json:"lid_migration_attempted,omitzero"`
|
LIDMigrationAttempted bool `json:"lid_migration_attempted,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GhostMetadata struct {
|
type GhostMetadata struct {
|
||||||
LastSync jsontime.Unix `json:"last_sync,omitzero"`
|
LastSync jsontime.Unix `json:"last_sync,omitempty"`
|
||||||
|
|
||||||
DirectAvatarURL string `json:"direct_avatar_url,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,13 +83,6 @@ 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))
|
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 {
|
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))
|
return networkid.MessageID(fmt.Sprintf("fake:%s:%s:%s", chat.ToNonAD().String(), sender.ToNonAD().String(), data))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.mau.fi/util/random"
|
|
||||||
"go.mau.fi/whatsmeow/types"
|
"go.mau.fi/whatsmeow/types"
|
||||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||||
)
|
)
|
||||||
|
|
@ -86,16 +85,6 @@ type AvatarMediaInfo struct {
|
||||||
Community bool
|
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 {
|
func MakeStickerPackMediaID(packID string, fileHash []byte, receiver networkid.UserLoginID) networkid.MediaID {
|
||||||
receiverID := compactJID(ParseUserLoginID(receiver, 0))
|
receiverID := compactJID(ParseUserLoginID(receiver, 0))
|
||||||
mediaID := make([]byte, 0, 4+len(packID)+len(fileHash)+len(receiverID))
|
mediaID := make([]byte, 0, 4+len(packID)+len(fileHash)+len(receiverID))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue