mirror of
https://github.com/mautrix/whatsapp.git
synced 2026-11-09 15:49:18 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bd940726f |
54 changed files with 716 additions and 2849 deletions
|
|
@ -1 +0,0 @@
|
||||||
code_format_allowlist: ["wa-connect-failure-%d"]
|
|
||||||
9
.github/workflows/go.yml
vendored
9
.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
|
||||||
|
|
@ -30,7 +30,6 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
go install golang.org/x/tools/cmd/goimports@latest
|
go install golang.org/x/tools/cmd/goimports@latest
|
||||||
go install honnef.co/go/tools/cmd/staticcheck@latest
|
go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||||
go install github.com/beeper/pre-commit-go/bridgelint@latest
|
|
||||||
export PATH="$HOME/go/bin:$PATH"
|
export PATH="$HOME/go/bin:$PATH"
|
||||||
|
|
||||||
- name: Run pre-commit
|
- name: Run pre-commit
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,9 @@ repos:
|
||||||
- id: go-mod-tidy
|
- id: go-mod-tidy
|
||||||
|
|
||||||
- repo: https://github.com/beeper/pre-commit-go
|
- repo: https://github.com/beeper/pre-commit-go
|
||||||
rev: v0.5.0
|
rev: v0.4.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: zerolog-ban-msgf
|
- id: zerolog-ban-msgf
|
||||||
- id: zerolog-use-stringer
|
- id: zerolog-use-stringer
|
||||||
- id: prevent-literal-http-methods
|
- id: prevent-literal-http-methods
|
||||||
- id: zerolog-ban-global-log
|
- id: zerolog-ban-global-log
|
||||||
- id: bridgelint
|
|
||||||
language: system
|
|
||||||
verbose: true
|
|
||||||
|
|
|
||||||
54
CHANGELOG.md
54
CHANGELOG.md
|
|
@ -1,57 +1,3 @@
|
||||||
# v26.10
|
|
||||||
|
|
||||||
* Added support for new rich response messages used by the Muse AI bot.
|
|
||||||
* Added support for bridging custom profile set for Muse.
|
|
||||||
* Added automatic one-time resync of keys needed to decrypt Muse messages.
|
|
||||||
* Added support for blocking and unblocking users on WhatsApp.
|
|
||||||
* Added support for contact array messages (thanks to [@voska] in [#961]).
|
|
||||||
* Added partial support for filling quoted message when sending replies to
|
|
||||||
WhatsApp.
|
|
||||||
* Changed undecryptable message resends to only bridge as edits and never as new
|
|
||||||
messages, to avoid races if the message was deleted before the resend.
|
|
||||||
* Changed login to wait for OTKs to be successfully uploaded, as the login will
|
|
||||||
fail if the bridge is stopped before that point.
|
|
||||||
* Changed chat info syncing to not bridge mute status if app state sync is
|
|
||||||
failing.
|
|
||||||
* Changed power levels to allow poll votes in read-only groups even if the room
|
|
||||||
is encrypted.
|
|
||||||
* Fixed backfilled media retries not getting sent if configured for local time.
|
|
||||||
* Fixed "delete for me" from WhatsApp not being bridged correctly in DMs.
|
|
||||||
* Fixed very long WhatsApp messages not being bridged.
|
|
||||||
* Fixed document message caption edits not being handled correctly.
|
|
||||||
|
|
||||||
[@voska]: https://github.com/voska
|
|
||||||
[#961]: https://github.com/mautrix/whatsapp/pull/961
|
|
||||||
|
|
||||||
# v26.09
|
|
||||||
|
|
||||||
* Bumped minimum Go version to 1.26.
|
|
||||||
* 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
|
|
||||||
|
|
||||||
* Updated Docker image to Alpine 3.24.
|
|
||||||
* Added support for the extra passkey authentication step when logging in.
|
|
||||||
* Added support for newer versions of poll messages from WhatsApp.
|
|
||||||
* Fixed poll votes in DMs.
|
|
||||||
|
|
||||||
# v26.06
|
# v26.06
|
||||||
|
|
||||||
* Added placeholder for group message history share notices.
|
* Added placeholder for group message history share notices.
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
See <https://docs.mau.fi/bridges/general/contributing.html>
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
FROM golang:1-alpine3.24 AS builder
|
FROM golang:1-alpine3.23 AS builder
|
||||||
|
|
||||||
RUN apk add --no-cache git ca-certificates build-base su-exec olm-dev
|
RUN apk add --no-cache git ca-certificates build-base su-exec olm-dev
|
||||||
|
|
||||||
|
|
@ -6,7 +6,7 @@ COPY . /build
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
RUN ./build.sh
|
RUN ./build.sh
|
||||||
|
|
||||||
FROM alpine:3.24
|
FROM alpine:3.23
|
||||||
|
|
||||||
ENV UID=1337 \
|
ENV UID=1337 \
|
||||||
GID=1337
|
GID=1337
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
ARG DOCKER_HUB="docker.io"
|
ARG DOCKER_HUB="docker.io"
|
||||||
|
|
||||||
FROM ${DOCKER_HUB}/alpine:3.24
|
FROM ${DOCKER_HUB}/alpine:3.23
|
||||||
|
|
||||||
ENV UID=1337 \
|
ENV UID=1337 \
|
||||||
GID=1337
|
GID=1337
|
||||||
|
|
|
||||||
|
|
@ -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.10",
|
Version: "26.06",
|
||||||
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)
|
||||||
|
}
|
||||||
38
go.mod
38
go.mod
|
|
@ -1,26 +1,24 @@
|
||||||
module go.mau.fi/mautrix-whatsapp
|
module go.mau.fi/mautrix-whatsapp
|
||||||
|
|
||||||
go 1.26.0
|
go 1.25.0
|
||||||
|
|
||||||
toolchain go1.27.2
|
toolchain go1.26.4
|
||||||
|
|
||||||
tool go.mau.fi/util/cmd/maubuild
|
tool go.mau.fi/util/cmd/maubuild
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/uuid v1.6.0
|
|
||||||
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.20.0
|
github.com/tidwall/gjson v1.19.0
|
||||||
github.com/yuin/goldmark v1.8.6
|
go.mau.fi/util v0.9.10
|
||||||
go.mau.fi/util v0.11.0
|
|
||||||
go.mau.fi/webp v0.3.0
|
go.mau.fi/webp v0.3.0
|
||||||
go.mau.fi/whatsmeow v0.0.0-20261026125351-1fbae988179a
|
go.mau.fi/whatsmeow v0.0.0-20260708165445-a2136cc8d5db
|
||||||
golang.org/x/image v0.47.0
|
golang.org/x/image v0.42.0
|
||||||
golang.org/x/net v0.61.0
|
golang.org/x/net v0.56.0
|
||||||
golang.org/x/sync v0.24.0
|
golang.org/x/sync v0.21.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.32.1-0.20261105141340-842796acf445
|
maunium.net/go/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
@ -29,11 +27,12 @@ require (
|
||||||
github.com/coder/websocket v1.8.15 // indirect
|
github.com/coder/websocket v1.8.15 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.7.0 // indirect
|
github.com/coreos/go-systemd/v22 v22.7.0 // indirect
|
||||||
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
|
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/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.45 // indirect
|
||||||
github.com/petermattis/goid v0.0.0-20260918085751-abfca077860b // indirect
|
github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 // 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
|
||||||
|
|
@ -41,13 +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.2 // 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.58.0 // indirect
|
golang.org/x/crypto v0.53.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20261014143641-f0251eb4aa3e // indirect
|
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect
|
||||||
golang.org/x/mod v0.42.0 // indirect
|
golang.org/x/mod v0.37.0 // indirect
|
||||||
golang.org/x/sys v0.49.0 // indirect
|
golang.org/x/sys v0.46.0 // indirect
|
||||||
golang.org/x/text v0.43.0 // indirect
|
golang.org/x/text v0.38.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
|
||||||
|
|
|
||||||
72
go.sum
72
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.45 h1:6KA/spDguL3KV8rnybG7ezSaE4SeMR3KC9VbUoAQaIk=
|
||||||
github.com/mattn/go-sqlite3 v1.14.52/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=
|
github.com/mattn/go-sqlite3 v1.14.45/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
||||||
github.com/petermattis/goid v0.0.0-20260918085751-abfca077860b h1:OzNsuVdSWGwXvWKTtChx9ve89k4cFJ6NxYM4Aw4/f+E=
|
github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 h1:WDsQxOJDy0N1VRAjXLpi8sCEZRSGarLWQevDxpTBRrM=
|
||||||
github.com/petermattis/goid v0.0.0-20260918085751-abfca077860b/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/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,11 +52,11 @@ 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.20.0 h1:+agJ3rEzKcCXKDKo0ml26UROcpcAlnZyjq+TjbY5Fto=
|
github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=
|
||||||
github.com/tidwall/gjson v1.20.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
|
github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
|
||||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
||||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
|
@ -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.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
|
||||||
github.com/yuin/goldmark v1.8.6/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||||
go.mau.fi/libsignal v0.2.2 h1:QV+XdzQkm3x3aSG7FcqfGSZuFXz83pRZPBFaPygHbOU=
|
go.mau.fi/libsignal v0.2.2 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.11.0 h1:JNEpkVRxfTdJUC0DIGKiRlLKzl4G/qLX/eVzoP7QuwY=
|
go.mau.fi/util v0.9.10 h1:wzvz5iDHyqDXB8vgisD4d3SzucLXNM3iNY+1O1RoHtg=
|
||||||
go.mau.fi/util v0.11.0/go.mod h1:04AEFVsc4zNdIkDC1gkdqbg/nZFolHY50pyXh2DSy7s=
|
go.mau.fi/util v0.9.10/go.mod h1:YQOxySn+ZE3qSYqNxvyX7Yi3suA8YK17PS6QqBREW7A=
|
||||||
go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA=
|
go.mau.fi/webp v0.3.0 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-20261026125351-1fbae988179a h1:ZyCV/mWfRkutxs5R0K3v+oyTbF4huprSlPZg/LEFQ7k=
|
go.mau.fi/whatsmeow v0.0.0-20260708165445-a2136cc8d5db h1:giO0YkqIJlbKURx7kj4i7zicLKj2ZODMM5X1tZAadH0=
|
||||||
go.mau.fi/whatsmeow v0.0.0-20261026125351-1fbae988179a/go.mod h1:iY4lm61nTLWsNiIE1EmHUZwhKGBeoV4dhnVE2uMtNoQ=
|
go.mau.fi/whatsmeow v0.0.0-20260708165445-a2136cc8d5db/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U=
|
||||||
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.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54=
|
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||||
golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w=
|
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||||
golang.org/x/exp v0.0.0-20261014143641-f0251eb4aa3e h1:HgI6W1OolKK6tcV74is+rl9SMT0njsHlp0TbQiGXI/A=
|
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M=
|
||||||
golang.org/x/exp v0.0.0-20261014143641-f0251eb4aa3e/go.mod h1:DBoxwWhjaW8DJJw/YeM4zpeOKMC1FMXSblsYy+1mFSw=
|
golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY=
|
||||||
golang.org/x/image v0.47.0 h1:za/8gVz++PPZqmZJBNSISICN5Rrj5zixP/8K++fgmqo=
|
golang.org/x/image v0.42.0 h1:1gSs6ehNWXLbkHBIPcWztk3D/6aIA/8hauiAYtlodVY=
|
||||||
golang.org/x/image v0.47.0/go.mod h1:IkN5o4A7vq55vXN8FWjjnhg9Rs2GOtffqsSi9mWAKYE=
|
golang.org/x/image v0.42.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
|
||||||
golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg=
|
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||||
golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs=
|
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||||
golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw=
|
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||||
golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
|
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||||
golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
|
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||||
golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
|
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||||
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc=
|
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||||
golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s=
|
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||||
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.32.1-0.20261105141340-842796acf445 h1:ZI9Y2YEmjqQDloOCiciHeynTRYjUwK0ZZbNcRH/5NOc=
|
maunium.net/go/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895 h1:oe87/mP1vo4LnA6CJDEfyg5ZiAOar2quJNypfO8eS4Y=
|
||||||
maunium.net/go/mautrix v0.32.1-0.20261105141340-842796acf445/go.mod h1:tZSwPzw+/0pa0dxGufkh0uJqM84BNxB6e2I0+S0UyjU=
|
maunium.net/go/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895/go.mod h1:mWXQNmOlrq4VTDU9f1HO03BSIswdUIyyY4wUKHqwzzY=
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime/debug"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -13,8 +12,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"go.mau.fi/util/exerrors"
|
|
||||||
"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"
|
||||||
|
|
@ -64,7 +61,7 @@ func (wa *WhatsAppClient) historySyncLoop(ctx context.Context) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
for {
|
for {
|
||||||
var resetTimer, errored bool
|
var resetTimer bool
|
||||||
select {
|
select {
|
||||||
case <-wa.historySyncWakeup:
|
case <-wa.historySyncWakeup:
|
||||||
dispatchTimer.Stop()
|
dispatchTimer.Stop()
|
||||||
|
|
@ -74,21 +71,12 @@ func (wa *WhatsAppClient) historySyncLoop(ctx context.Context) {
|
||||||
} else if notif == nil {
|
} else if notif == nil {
|
||||||
wa.UserLogin.Log.Debug().Msg("No more queued history sync notifications")
|
wa.UserLogin.Log.Debug().Msg("No more queued history sync notifications")
|
||||||
} else {
|
} else {
|
||||||
resetTimer, errored = wa.downloadAndSaveWAHistorySyncData(ctx, notif, rowid)
|
resetTimer = wa.downloadAndSaveWAHistorySyncData(ctx, notif, rowid)
|
||||||
// Continue waking up the loop until all queued notifications are processed
|
// Continue waking up the loop until all queued notifications are processed
|
||||||
select {
|
select {
|
||||||
case wa.historySyncWakeup <- struct{}{}:
|
case wa.historySyncWakeup <- struct{}{}:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
if errored {
|
|
||||||
wa.UserLogin.Log.Debug().Msg("Temporarily stopping history sync loop after error")
|
|
||||||
select {
|
|
||||||
case <-time.After(1 * time.Minute):
|
|
||||||
case <-ctx.Done():
|
|
||||||
wa.UserLogin.Log.Debug().Msg("Stopping main history sync loop")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
wa.UserLogin.Log.Debug().Msg("Stopping main history sync loop")
|
wa.UserLogin.Log.Debug().Msg("Stopping main history sync loop")
|
||||||
|
|
@ -120,21 +108,13 @@ func (wa *WhatsAppClient) saveWAHistorySyncNotification(ctx context.Context, evt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context, evt *waE2E.HistorySyncNotification, rowid int) (resetTimer, errored bool) {
|
func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context, evt *waE2E.HistorySyncNotification, rowid int) (resetTimer bool) {
|
||||||
log := wa.UserLogin.Log.With().
|
log := wa.UserLogin.Log.With().
|
||||||
Str("action", "download history sync").
|
Str("action", "download history sync").
|
||||||
Stringer("sync_type", evt.GetSyncType()).
|
Stringer("sync_type", evt.GetSyncType()).
|
||||||
Uint32("chunk_order", evt.GetChunkOrder()).
|
Uint32("chunk_order", evt.GetChunkOrder()).
|
||||||
Uint32("progress", evt.GetProgress()).
|
Uint32("progress", evt.GetProgress()).
|
||||||
Logger()
|
Logger()
|
||||||
defer func() {
|
|
||||||
if v := recover(); v != nil {
|
|
||||||
log.Err(exerrors.RecoverToError(v)).
|
|
||||||
Bytes(zerolog.ErrorStackFieldName, debug.Stack()).
|
|
||||||
Msg("History sync saving panicked")
|
|
||||||
errored = true
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Int64("oldest_msg_in_chunk_ts", evt.GetOldestMsgInChunkTimestampSec()).
|
Int64("oldest_msg_in_chunk_ts", evt.GetOldestMsgInChunkTimestampSec()).
|
||||||
Any("full_request_meta", evt.GetFullHistorySyncOnDemandRequestMetadata()).
|
Any("full_request_meta", evt.GetFullHistorySyncOnDemandRequestMetadata()).
|
||||||
|
|
@ -142,20 +122,11 @@ func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context,
|
||||||
Str("peer_data_request_session_id", evt.GetPeerDataRequestSessionID()).
|
Str("peer_data_request_session_id", evt.GetPeerDataRequestSessionID()).
|
||||||
Msg("Downloading history sync")
|
Msg("Downloading history sync")
|
||||||
blob, err := wa.Client.DownloadHistorySync(log.WithContext(ctx), evt, true)
|
blob, err := wa.Client.DownloadHistorySync(log.WithContext(ctx), evt, true)
|
||||||
if errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith403) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith404) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith410) {
|
if err != nil {
|
||||||
log.Err(err).Msg("History sync blob is no longer available, dropping notification")
|
|
||||||
if err = wa.Main.DB.HSNotif.Delete(ctx, rowid); err != nil {
|
|
||||||
log.Err(err).Msg("Failed to delete queued history sync notification")
|
|
||||||
errored = true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
} else if err != nil {
|
|
||||||
log.Err(err).Msg("Failed to download history sync")
|
log.Err(err).Msg("Failed to download history sync")
|
||||||
errored = true
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if blob.GetSyncType() == waHistorySync.HistorySync_ON_DEMAND {
|
if blob.GetSyncType() == waHistorySync.HistorySync_ON_DEMAND {
|
||||||
wa.finishOnDemandHistorySyncRequest(evt.GetPeerDataRequestSessionID())
|
|
||||||
wa.handleOnDemandHistorySync(ctx, blob)
|
wa.handleOnDemandHistorySync(ctx, blob)
|
||||||
if err = wa.Main.DB.HSNotif.Delete(ctx, rowid); err != nil {
|
if err = wa.Main.DB.HSNotif.Delete(ctx, rowid); err != nil {
|
||||||
log.Err(err).Msg("Failed to delete queued on-demand history sync notification")
|
log.Err(err).Msg("Failed to delete queued on-demand history sync notification")
|
||||||
|
|
@ -179,7 +150,6 @@ func (wa *WhatsAppClient) downloadAndSaveWAHistorySyncData(ctx context.Context,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to store history sync notification data")
|
log.Err(err).Msg("Failed to store history sync notification data")
|
||||||
errored = true
|
|
||||||
} else {
|
} else {
|
||||||
resetTimer = blob.GetSyncType() == waHistorySync.HistorySync_INITIAL_BOOTSTRAP ||
|
resetTimer = blob.GetSyncType() == waHistorySync.HistorySync_INITIAL_BOOTSTRAP ||
|
||||||
blob.GetSyncType() == waHistorySync.HistorySync_RECENT ||
|
blob.GetSyncType() == waHistorySync.HistorySync_RECENT ||
|
||||||
|
|
@ -255,42 +225,33 @@ func (wa *WhatsAppClient) handleWAHistorySync(
|
||||||
} else if jid.Server == types.BroadcastServer {
|
} else if jid.Server == types.BroadcastServer {
|
||||||
log.Debug().Stringer("chat_jid", jid).Msg("Skipping broadcast list in history sync")
|
log.Debug().Stringer("chat_jid", jid).Msg("Skipping broadcast list in history sync")
|
||||||
continue
|
continue
|
||||||
} else if jid == types.PSAJID {
|
|
||||||
// New messages in the official updates chat aren't bridged because they use a different path,
|
|
||||||
// so drop history as well. This should be removed if the PSA chat gets properly bridged in the future.
|
|
||||||
log.Debug().Stringer("chat_jid", jid).Msg("Skipping PSA in history sync")
|
|
||||||
continue
|
|
||||||
} else {
|
} else {
|
||||||
totalMessageCount += len(conv.GetMessages())
|
totalMessageCount += len(conv.GetMessages())
|
||||||
}
|
}
|
||||||
if jid == types.MetaAIJID {
|
if jid.Server == types.HiddenUserServer {
|
||||||
jid = types.NewMetaAIJID
|
pn, err := wa.GetStore().LIDs.GetPNForLID(ctx, jid)
|
||||||
} else if jid.Server == types.DefaultUserServer {
|
|
||||||
lid, err := wa.GetStore().LIDs.GetLIDForPN(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 {
|
||||||
|
|
@ -298,9 +259,6 @@ func (wa *WhatsAppClient) handleWAHistorySync(
|
||||||
Int("msg_index", i).
|
Int("msg_index", i).
|
||||||
Str("msg_id", rawMsg.GetMessage().GetKey().GetID()).
|
Str("msg_id", rawMsg.GetMessage().GetKey().GetID()).
|
||||||
Uint64("msg_time_seconds", rawMsg.GetMessage().GetMessageTimestamp()).
|
Uint64("msg_time_seconds", rawMsg.GetMessage().GetMessageTimestamp()).
|
||||||
Stringer("stub_type", rawMsg.GetMessage().GetMessageStubType()).
|
|
||||||
Str("message_type", getMessageType(rawMsg.GetMessage().GetMessage())).
|
|
||||||
Bool("from_me", rawMsg.GetMessage().GetKey().GetFromMe()).
|
|
||||||
Msg("Dropping historical message due to parse error")
|
Msg("Dropping historical message due to parse error")
|
||||||
log.Trace().
|
log.Trace().
|
||||||
Any("web_message_info", rawMsg.GetMessage()).
|
Any("web_message_info", rawMsg.GetMessage()).
|
||||||
|
|
@ -311,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).
|
||||||
|
|
@ -349,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()).
|
||||||
|
|
@ -366,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)
|
||||||
|
|
@ -406,13 +359,6 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) {
|
||||||
log := wa.UserLogin.Log.With().
|
log := wa.UserLogin.Log.With().
|
||||||
Str("action", "create portals from history sync").
|
Str("action", "create portals from history sync").
|
||||||
Logger()
|
Logger()
|
||||||
defer func() {
|
|
||||||
if v := recover(); v != nil {
|
|
||||||
log.Err(exerrors.RecoverToError(v)).
|
|
||||||
Bytes(zerolog.ErrorStackFieldName, debug.Stack()).
|
|
||||||
Msg("History sync portal creation panicked")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
ctx = log.WithContext(ctx)
|
ctx = log.WithContext(ctx)
|
||||||
limit := wa.Main.Config.HistorySync.MaxInitialConversations
|
limit := wa.Main.Config.HistorySync.MaxInitialConversations
|
||||||
loginTS := wa.UserLogin.Metadata.(*waid.UserLoginMetadata).LoggedInAt
|
loginTS := wa.UserLogin.Metadata.(*waid.UserLoginMetadata).LoggedInAt
|
||||||
|
|
@ -454,7 +400,7 @@ func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
wrappedInfo, err := wa.getChatInfo(ctx, conv.ChatJID, conv, true)
|
wrappedInfo, err := wa.getChatInfo(ctx, conv.ChatJID, conv, true)
|
||||||
if errors.Is(err, whatsmeow.ErrNotInGroup) || errors.Is(err, whatsmeow.ErrGroupNotFound) || errors.Is(err, ErrNewsletterNotFound) {
|
if errors.Is(err, whatsmeow.ErrNotInGroup) || errors.Is(err, whatsmeow.ErrGroupNotFound) {
|
||||||
log.Debug().Err(err).Stringer("chat_jid", conv.ChatJID).
|
log.Debug().Err(err).Stringer("chat_jid", conv.ChatJID).
|
||||||
Msg("Skipping creating room because the user is not a participant")
|
Msg("Skipping creating room because the user is not a participant")
|
||||||
//err = wa.Main.DB.Message.DeleteAllInChat(ctx, wa.UserLogin.ID, conv.ChatJID)
|
//err = wa.Main.DB.Message.DeleteAllInChat(ctx, wa.UserLogin.ID, conv.ChatJID)
|
||||||
|
|
@ -528,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
|
||||||
|
|
@ -578,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,
|
||||||
|
|
@ -586,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,
|
||||||
|
|
@ -605,36 +547,16 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp, err := wa.convertHistorySyncMessages(ctx, params.Portal, portalJID, messages, true, !params.Forward && !hasMore)
|
resp, err := wa.convertHistorySyncMessages(ctx, params.Portal, portalJID, messages, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to convert messages: %w", err)
|
return nil, fmt.Errorf("failed to convert messages: %w", err)
|
||||||
}
|
}
|
||||||
// TODO might be better to always set true here and let GetBetween return an empty result to mark as false?
|
|
||||||
resp.HasMore = hasMore
|
resp.HasMore = hasMore
|
||||||
resp.Forward = params.Forward
|
resp.Forward = params.Forward
|
||||||
resp.MarkRead = markRead
|
resp.MarkRead = markRead
|
||||||
if params.Forward && !markRead && conv != nil && !ptr.Val(conv.MarkedAsUnread) {
|
|
||||||
resp.ReadUpTo = historySyncReadUpTo(resp.Messages, int(ptr.Val(conv.UnreadCount)))
|
|
||||||
}
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func historySyncReadUpTo(messages []*bridgev2.BackfillMessage, unreadCount int) time.Time {
|
|
||||||
if unreadCount <= 0 {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
for i := len(messages) - 1; i >= 0; i-- {
|
|
||||||
if messages[i].Sender.IsFromMe {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
unreadCount--
|
|
||||||
if unreadCount == 0 {
|
|
||||||
return messages[i].Timestamp.Add(-time.Nanosecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wa *WhatsAppClient) deleteHistorySyncMessages(ctx context.Context, portalJID types.JID, newestTS, oldestTS uint64) {
|
func (wa *WhatsAppClient) deleteHistorySyncMessages(ctx context.Context, portalJID types.JID, newestTS, oldestTS uint64) {
|
||||||
var err error
|
var err error
|
||||||
var rows int64
|
var rows int64
|
||||||
|
|
@ -667,19 +589,13 @@ func (wa *WhatsAppClient) convertHistorySyncMessages(
|
||||||
portalJID types.JID,
|
portalJID types.JID,
|
||||||
messages []*waWeb.WebMessageInfo,
|
messages []*waWeb.WebMessageInfo,
|
||||||
explodeOnError bool,
|
explodeOnError bool,
|
||||||
deleteEverything bool,
|
|
||||||
) (*bridgev2.FetchMessagesResponse, error) {
|
) (*bridgev2.FetchMessagesResponse, error) {
|
||||||
cli := wa.Client
|
|
||||||
if cli == 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 := cli.ParseWebMessage(portalJID, msg)
|
evt, err := wa.Client.ParseWebMessage(portalJID, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if explodeOnError {
|
if explodeOnError {
|
||||||
// This should never happen because the info is already parsed once before being stored in the database
|
// This should never happen because the info is already parsed once before being stored in the database
|
||||||
|
|
@ -701,21 +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)
|
|
||||||
}
|
|
||||||
if evt.Info.IsFromMe {
|
|
||||||
wa.addPreviousOwnLID(ctx, evt.Info.Sender)
|
|
||||||
}
|
|
||||||
isViewOnce := evt.IsViewOnce || evt.IsViewOnceV2 || evt.IsViewOnceV2Extension
|
isViewOnce := evt.IsViewOnce || evt.IsViewOnceV2 || evt.IsViewOnceV2Extension
|
||||||
converted, mediaReq := wa.convertHistorySyncMessage(
|
converted, mediaReq := wa.convertHistorySyncMessage(
|
||||||
ctx, cli, 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)
|
||||||
|
|
@ -726,11 +631,9 @@ func (wa *WhatsAppClient) convertHistorySyncMessages(
|
||||||
Messages: convertedMessages,
|
Messages: convertedMessages,
|
||||||
Cursor: networkid.PaginationCursor(strconv.FormatUint(oldestTS, 10)),
|
Cursor: networkid.PaginationCursor(strconv.FormatUint(oldestTS, 10)),
|
||||||
CompleteCallback: func() {
|
CompleteCallback: func() {
|
||||||
if deleteEverything {
|
// TODO this only deletes after backfilling. If there's no need for backfill after a relogin,
|
||||||
wa.deleteHistorySyncMessages(ctx, portalJID, 1<<62, oldestTS)
|
// the messages will be stuck in the database
|
||||||
} else {
|
|
||||||
wa.deleteHistorySyncMessages(ctx, portalJID, newestTS, oldestTS)
|
wa.deleteHistorySyncMessages(ctx, portalJID, newestTS, oldestTS)
|
||||||
}
|
|
||||||
if len(mediaRequests) > 0 {
|
if len(mediaRequests) > 0 {
|
||||||
go func(ctx context.Context) {
|
go func(ctx context.Context) {
|
||||||
for _, req := range mediaRequests {
|
for _, req := range mediaRequests {
|
||||||
|
|
@ -748,45 +651,28 @@ 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")
|
||||||
}
|
}
|
||||||
anchor := params.AnchorMessage
|
parsed, err := waid.ParseMessageID(params.AnchorMessage.ID)
|
||||||
parsed, err := waid.ParseMessageID(anchor.ID)
|
|
||||||
if err != nil {
|
|
||||||
anchor, err = wa.Main.Bridge.DB.Message.GetFirstPortalMessageExcludingIDPattern(ctx, params.Portal.PortalKey, "fake:%")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get first non-fake portal message: %w", err)
|
|
||||||
} else if anchor == nil {
|
|
||||||
return &bridgev2.FetchMessagesResponse{}, nil
|
|
||||||
}
|
|
||||||
parsed, err = waid.ParseMessageID(anchor.ID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse anchor message ID: %w", err)
|
return nil, fmt.Errorf("failed to parse anchor message ID: %w", err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
msgID := wa.Client.GenerateMessageID()
|
msgID := wa.Client.GenerateMessageID()
|
||||||
if !wa.startOnDemandHistorySyncRequest(ctx, portalJID, msgID) {
|
|
||||||
return &bridgev2.FetchMessagesResponse{
|
|
||||||
HasMore: true,
|
|
||||||
Pending: true,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
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,
|
||||||
Timestamp: anchor.Timestamp,
|
Timestamp: params.AnchorMessage.Timestamp,
|
||||||
}, 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")
|
||||||
|
|
@ -795,9 +681,6 @@ func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, portalJID
|
||||||
Peer: true,
|
Peer: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wa.onDemandHistorySyncLock.Lock()
|
|
||||||
delete(wa.onDemandHistorySyncRequests, portalJID)
|
|
||||||
wa.onDemandHistorySyncLock.Unlock()
|
|
||||||
return nil, fmt.Errorf("failed to send history sync request: %w", err)
|
return nil, fmt.Errorf("failed to send history sync request: %w", err)
|
||||||
}
|
}
|
||||||
return &bridgev2.FetchMessagesResponse{
|
return &bridgev2.FetchMessagesResponse{
|
||||||
|
|
@ -806,38 +689,6 @@ func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, portalJID
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type onDemandHistorySyncRequest struct {
|
|
||||||
ID types.MessageID
|
|
||||||
SentAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
const onDemandHistorySyncRequestTimeout = 1 * time.Hour
|
|
||||||
|
|
||||||
func (wa *WhatsAppClient) startOnDemandHistorySyncRequest(ctx context.Context, portalJID types.JID, msgID types.MessageID) bool {
|
|
||||||
wa.onDemandHistorySyncLock.Lock()
|
|
||||||
defer wa.onDemandHistorySyncLock.Unlock()
|
|
||||||
if req, inFlight := wa.onDemandHistorySyncRequests[portalJID]; inFlight && time.Since(req.SentAt) < onDemandHistorySyncRequestTimeout {
|
|
||||||
zerolog.Ctx(ctx).Debug().
|
|
||||||
Str("in_flight_request_msg_id", req.ID).
|
|
||||||
Stringer("portal_jid", portalJID).
|
|
||||||
Time("sent_at", req.SentAt).
|
|
||||||
Msg("Not sending history sync request, previous request is still in flight")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
wa.onDemandHistorySyncRequests[portalJID] = onDemandHistorySyncRequest{ID: msgID, SentAt: time.Now()}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wa *WhatsAppClient) finishOnDemandHistorySyncRequest(sessionID string) {
|
|
||||||
wa.onDemandHistorySyncLock.Lock()
|
|
||||||
defer wa.onDemandHistorySyncLock.Unlock()
|
|
||||||
for chatJID, req := range wa.onDemandHistorySyncRequests {
|
|
||||||
if req.ID == sessionID {
|
|
||||||
delete(wa.onDemandHistorySyncRequests, chatJID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wa *WhatsAppClient) handleOnDemandHistorySync(ctx context.Context, blob *waHistorySync.HistorySync) {
|
func (wa *WhatsAppClient) handleOnDemandHistorySync(ctx context.Context, blob *waHistorySync.HistorySync) {
|
||||||
if len(blob.GetConversations()) > 1 {
|
if len(blob.GetConversations()) > 1 {
|
||||||
zerolog.Ctx(ctx).Warn().
|
zerolog.Ctx(ctx).Warn().
|
||||||
|
|
@ -850,22 +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 == types.MetaAIJID {
|
|
||||||
portalJID = types.NewMetaAIJID
|
|
||||||
} else if portalJID.Server == types.DefaultUserServer {
|
|
||||||
lid, err := wa.GetStore().LIDs.GetLIDForPN(ctx, portalJID)
|
|
||||||
if err != nil {
|
|
||||||
zerolog.Ctx(ctx).Err(err).Stringer("lid", portalJID).Msg("Failed to get LID for phone number in on-demand history sync")
|
|
||||||
} 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")
|
||||||
|
|
@ -893,7 +728,7 @@ func (wa *WhatsAppClient) handleOnDemandHistorySync(ctx context.Context, blob *w
|
||||||
Int("message_count", len(messages)).
|
Int("message_count", len(messages)).
|
||||||
Stringer("end_of_history_type", conv.GetEndOfHistoryTransferType()).
|
Stringer("end_of_history_type", conv.GetEndOfHistoryTransferType()).
|
||||||
Msg("Converting messages to bridge from on-demand history sync")
|
Msg("Converting messages to bridge from on-demand history sync")
|
||||||
resp, err := wa.convertHistorySyncMessages(ctx, portal, portalJID, messages, false, false)
|
resp, err := wa.convertHistorySyncMessages(ctx, portal, portalJID, messages, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -905,7 +740,7 @@ func (wa *WhatsAppClient) handleOnDemandHistorySync(ctx context.Context, blob *w
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) convertHistorySyncMessage(
|
func (wa *WhatsAppClient) convertHistorySyncMessage(
|
||||||
ctx context.Context, cli *whatsmeow.Client, portal *bridgev2.Portal, info *types.MessageInfo, msg, rawMsg *waE2E.Message, isViewOnce bool, reactions []*waWeb.Reaction,
|
ctx context.Context, portal *bridgev2.Portal, info *types.MessageInfo, msg, rawMsg *waE2E.Message, isViewOnce bool, reactions []*waWeb.Reaction,
|
||||||
) (*bridgev2.BackfillMessage, *wadb.MediaRequest) {
|
) (*bridgev2.BackfillMessage, *wadb.MediaRequest) {
|
||||||
// New messages turn these into edits, but in backfill we only have the last version,
|
// New messages turn these into edits, but in backfill we only have the last version,
|
||||||
// so no need to do the edit thing. Instead, just unwrap the message.
|
// so no need to do the edit thing. Instead, just unwrap the message.
|
||||||
|
|
@ -914,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, cli, intent, msg, rawMsg, info, isViewOnce, true, nil),
|
ConvertedMessage: wa.Main.MsgConv.ToMatrix(ctx, portal, wa.Client, intent, msg, rawMsg, info, isViewOnce, true, nil),
|
||||||
Sender: wa.makeEventSender(ctx, pickLID(info.Sender, info.SenderAlt)),
|
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)),
|
||||||
|
|
@ -928,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, 10
|
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_10_01"
|
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)),
|
||||||
|
|
@ -198,7 +193,6 @@ var whatsappCaps = &event.RoomFeatures{
|
||||||
|
|
||||||
var whatsappDMCaps *event.RoomFeatures
|
var whatsappDMCaps *event.RoomFeatures
|
||||||
var whatsappCAGCaps *event.RoomFeatures
|
var whatsappCAGCaps *event.RoomFeatures
|
||||||
var whatsappNoteToSelfCaps *event.RoomFeatures
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
whatsappDMCaps = ptr.Clone(whatsappCaps)
|
whatsappDMCaps = ptr.Clone(whatsappCaps)
|
||||||
|
|
@ -206,13 +200,7 @@ func init() {
|
||||||
whatsappDMCaps.State = event.StateFeatureMap{
|
whatsappDMCaps.State = event.StateFeatureMap{
|
||||||
event.StateBeeperDisappearingTimer.Type: {Level: event.CapLevelFullySupported},
|
event.StateBeeperDisappearingTimer.Type: {Level: event.CapLevelFullySupported},
|
||||||
}
|
}
|
||||||
whatsappDMCaps.BlockUser = true
|
|
||||||
whatsappDMCaps.MemberActions = nil
|
whatsappDMCaps.MemberActions = nil
|
||||||
|
|
||||||
whatsappNoteToSelfCaps = ptr.Clone(whatsappDMCaps)
|
|
||||||
whatsappNoteToSelfCaps.ID = capID() + "+self"
|
|
||||||
whatsappNoteToSelfCaps.BlockUser = false
|
|
||||||
|
|
||||||
whatsappCAGCaps = ptr.Clone(whatsappCaps)
|
whatsappCAGCaps = ptr.Clone(whatsappCaps)
|
||||||
whatsappCAGCaps.ID = capID() + "+cag"
|
whatsappCAGCaps.ID = capID() + "+cag"
|
||||||
whatsappCAGCaps.Reply = event.CapLevelUnsupported
|
whatsappCAGCaps.Reply = event.CapLevelUnsupported
|
||||||
|
|
@ -223,10 +211,6 @@ func (wa *WhatsAppClient) GetCapabilities(ctx context.Context, portal *bridgev2.
|
||||||
if portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup {
|
if portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup {
|
||||||
return whatsappCAGCaps
|
return whatsappCAGCaps
|
||||||
} else if portal.RoomType == database.RoomTypeDM {
|
} else if portal.RoomType == database.RoomTypeDM {
|
||||||
jid, err := waid.ParsePortalID(portal.ID)
|
|
||||||
if err == nil && wa.IsOwnJID(jid) {
|
|
||||||
return whatsappNoteToSelfCaps
|
|
||||||
}
|
|
||||||
return whatsappDMCaps
|
return whatsappDMCaps
|
||||||
}
|
}
|
||||||
return whatsappCaps
|
return whatsappCaps
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"go.mau.fi/util/jsontime"
|
"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/appstate"
|
|
||||||
"go.mau.fi/whatsmeow/types"
|
"go.mau.fi/whatsmeow/types"
|
||||||
"go.mau.fi/whatsmeow/types/events"
|
"go.mau.fi/whatsmeow/types/events"
|
||||||
"maunium.net/go/mautrix/bridgev2"
|
"maunium.net/go/mautrix/bridgev2"
|
||||||
|
|
@ -30,21 +29,15 @@ 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")
|
|
||||||
var ErrNewsletterNotFound = errors.New("newsletter doesn't exist")
|
|
||||||
|
|
||||||
func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID, conv *wadb.Conversation, isNew bool) (wrapped *bridgev2.ChatInfo, err error) {
|
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:
|
||||||
wrapped = wa.wrapDMInfo(ctx, portalJID)
|
wrapped = wa.wrapDMInfo(ctx, portalJID)
|
||||||
if portalJID == types.MuseJID {
|
|
||||||
go wa.requestMuseProfile(ctx)
|
|
||||||
}
|
|
||||||
case types.BroadcastServer:
|
case types.BroadcastServer:
|
||||||
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)
|
||||||
|
|
@ -57,8 +50,6 @@ func (wa *WhatsAppClient) getChatInfo(ctx context.Context, portalJID types.JID,
|
||||||
info, err := wa.Client.GetNewsletterInfo(ctx, portalJID)
|
info, err := wa.Client.GetNewsletterInfo(ctx, portalJID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if info == nil || info.ID.IsEmpty() {
|
|
||||||
return nil, ErrNewsletterNotFound
|
|
||||||
}
|
}
|
||||||
wrapped = wa.wrapNewsletterInfo(ctx, info)
|
wrapped = wa.wrapNewsletterInfo(ctx, info)
|
||||||
default:
|
default:
|
||||||
|
|
@ -108,28 +99,9 @@ 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 {
|
info.UserLocal = &bridgev2.UserLocalPortalInfo{
|
||||||
chatID, err = wa.GetStore().GetAltJID(ctx, chatID)
|
MutedUntil: ptr.Ptr(chat.MutedUntil),
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wa.appStateRecoveryLock.Lock()
|
|
||||||
_, muteStateBroken := wa.UserLogin.Metadata.(*waid.UserLoginMetadata).AppStateRecoveryAttempted[appstate.WAPatchRegularHigh]
|
|
||||||
wa.appStateRecoveryLock.Unlock()
|
|
||||||
userLocal := &bridgev2.UserLocalPortalInfo{}
|
|
||||||
if !muteStateBroken {
|
|
||||||
userLocal.MutedUntil = ptr.Ptr(chat.MutedUntil)
|
|
||||||
} else if info.UserLocal != nil {
|
|
||||||
userLocal.MutedUntil = info.UserLocal.MutedUntil
|
|
||||||
}
|
|
||||||
info.UserLocal = userLocal
|
|
||||||
if chat.Pinned {
|
if chat.Pinned {
|
||||||
info.UserLocal.Tag = ptr.Ptr(wa.Main.Config.PinnedTag)
|
info.UserLocal.Tag = ptr.Ptr(wa.Main.Config.PinnedTag)
|
||||||
} else if chat.Archived {
|
} else if chat.Archived {
|
||||||
|
|
@ -169,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,
|
||||||
|
|
@ -182,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{
|
||||||
|
|
@ -192,19 +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),
|
||||||
}
|
|
||||||
var err error
|
|
||||||
info.UserBlocked, err = wa.isUserBlocked(ctx, jid)
|
|
||||||
if err != nil {
|
|
||||||
zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to get blocked state")
|
|
||||||
}
|
}
|
||||||
if jid.Server == types.BotServer {
|
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)}},
|
||||||
|
|
@ -228,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),
|
||||||
|
|
@ -279,8 +240,6 @@ func setTopicID(id, topic string) bridgev2.ExtraUpdater[*bridgev2.Portal] {
|
||||||
|
|
||||||
func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupInfo) *bridgev2.ChatInfo {
|
func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupInfo) *bridgev2.ChatInfo {
|
||||||
sendEventPL := defaultPL
|
sendEventPL := defaultPL
|
||||||
// Community announcement groups allow non-admins to send threaded replies.
|
|
||||||
// Power levels can't represent that, but the power level is checked separately when handling messages.
|
|
||||||
if info.IsAnnounce && !info.IsDefaultSubGroup {
|
if info.IsAnnounce && !info.IsDefaultSubGroup {
|
||||||
sendEventPL = adminPL
|
sendEventPL = adminPL
|
||||||
}
|
}
|
||||||
|
|
@ -294,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{
|
||||||
|
|
@ -311,11 +269,11 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn
|
||||||
event.StateRoomName: metaChangePL,
|
event.StateRoomName: metaChangePL,
|
||||||
event.StateRoomAvatar: metaChangePL,
|
event.StateRoomAvatar: metaChangePL,
|
||||||
event.StateTopic: metaChangePL,
|
event.StateTopic: metaChangePL,
|
||||||
event.EventEncrypted: defaultPL, // Need to always allow encrypted events for poll responses
|
|
||||||
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
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -340,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{
|
||||||
|
|
@ -361,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() {
|
||||||
|
|
@ -568,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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -583,27 +538,10 @@ 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,
|
||||||
// TODO support poll votes in channels
|
// TODO always allow poll responses
|
||||||
//event.EventUnstablePollResponse: defaultPL,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Type: ptr.Ptr(database.RoomTypeDefault),
|
Type: ptr.Ptr(database.RoomTypeDefault),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) isUserBlocked(ctx context.Context, jid types.JID) (*bool, error) {
|
|
||||||
wa.blocklistLock.Lock()
|
|
||||||
defer wa.blocklistLock.Unlock()
|
|
||||||
meta := wa.UserLogin.Metadata.(*waid.UserLoginMetadata)
|
|
||||||
blocklist := meta.Blocklist
|
|
||||||
if blocklist == nil {
|
|
||||||
runHandlers := wa.resyncBlocklistUnlocked(ctx)
|
|
||||||
go runHandlers()
|
|
||||||
blocklist = meta.Blocklist
|
|
||||||
}
|
|
||||||
if blocklist == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return new(blocklist.IsBlocked(jid)), nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -54,12 +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),
|
||||||
previousOwnLIDs: exsync.NewSetWithItems(login.Metadata.(*waid.UserLoginMetadata).PreviousOwnLIDs),
|
|
||||||
|
|
||||||
onDemandHistorySyncRequests: make(map[types.JID]onDemandHistorySyncRequest),
|
|
||||||
|
|
||||||
disableNewsletter: store.BaseClientPayload.GetUserAgent().GetPlatform() == waWa6.ClientPayload_UserAgent_MACOS,
|
|
||||||
}
|
}
|
||||||
login.Client = w
|
login.Client = w
|
||||||
|
|
||||||
|
|
@ -74,18 +70,16 @@ 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
|
||||||
w.Client.UnifiedSessionInterval = 24 * time.Hour
|
|
||||||
w.Client.GetMessageForRetry = w.trackNotFoundRetry
|
w.Client.GetMessageForRetry = w.trackNotFoundRetry
|
||||||
w.Client.PreRetryCallback = w.trackFoundRetry
|
w.Client.PreRetryCallback = w.trackFoundRetry
|
||||||
w.Client.BackgroundEventCtx = w.UserLogin.Log.WithContext(wa.Bridge.BackgroundCtx)
|
w.Client.BackgroundEventCtx = w.UserLogin.Log.WithContext(wa.Bridge.BackgroundCtx)
|
||||||
|
|
@ -110,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{}
|
||||||
|
|
@ -120,28 +113,15 @@ 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
|
||||||
blocklistLock sync.Mutex
|
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
|
||||||
|
|
||||||
wasaResyncLock sync.Mutex
|
|
||||||
museProfileLock sync.Mutex
|
|
||||||
lastMuseProfileRequest time.Time
|
|
||||||
|
|
||||||
onDemandHistorySyncLock sync.Mutex
|
|
||||||
onDemandHistorySyncRequests map[types.JID]onDemandHistorySyncRequest
|
|
||||||
|
|
||||||
previousOwnLIDs *exsync.Set[types.JID]
|
|
||||||
previousOwnLIDsLock sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -205,52 +185,17 @@ 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) ||
|
|
||||||
wa.previousOwnLIDs.Has(jid.ToNonAD())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wa *WhatsAppClient) addPreviousOwnLID(ctx context.Context, jid types.JID) {
|
|
||||||
if jid.Server != types.HiddenUserServer || wa.IsOwnJID(jid) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
jid = jid.ToNonAD()
|
|
||||||
wa.previousOwnLIDsLock.Lock()
|
|
||||||
defer wa.previousOwnLIDsLock.Unlock()
|
|
||||||
if !wa.previousOwnLIDs.Add(jid) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
zerolog.Ctx(ctx).Info().Stringer("lid", jid).Msg("Found previous own LID in history sync")
|
|
||||||
wa.UserLogin.Metadata.(*waid.UserLoginMetadata).PreviousOwnLIDs = wa.previousOwnLIDs.AsList()
|
|
||||||
err := wa.UserLogin.Save(ctx)
|
|
||||||
if err != nil {
|
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to save user login after adding previous own LID")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wa *WhatsAppClient) GetLID() types.JID {
|
|
||||||
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) {
|
||||||
wa.connect(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wa *WhatsAppClient) connect(ctx context.Context) bool {
|
|
||||||
if wa.Client == nil {
|
if wa.Client == nil {
|
||||||
state := status.BridgeState{
|
state := status.BridgeState{
|
||||||
StateEvent: status.StateBadCredentials,
|
StateEvent: status.StateBadCredentials,
|
||||||
Error: WANotLoggedIn,
|
Error: WANotLoggedIn,
|
||||||
}
|
}
|
||||||
wa.UserLogin.BridgeState.Send(state)
|
wa.UserLogin.BridgeState.Send(state)
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnecting})
|
wa.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateConnecting})
|
||||||
wa.Main.firstClientConnectOnce.Do(wa.Main.onFirstClientConnect)
|
wa.Main.firstClientConnectOnce.Do(wa.Main.onFirstClientConnect)
|
||||||
|
|
@ -258,7 +203,7 @@ func (wa *WhatsAppClient) connect(ctx context.Context) bool {
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to update proxy")
|
zerolog.Ctx(ctx).Err(err).Msg("Failed to update proxy")
|
||||||
}
|
}
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
wa.initMC()
|
wa.initMC()
|
||||||
wa.startLoops()
|
wa.startLoops()
|
||||||
|
|
@ -275,9 +220,7 @@ func (wa *WhatsAppClient) connect(ctx context.Context) bool {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
wa.UserLogin.BridgeState.Send(state)
|
wa.UserLogin.BridgeState.Send(state)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppClient) notifyOfflineSyncWaiter(err error) {
|
func (wa *WhatsAppClient) notifyOfflineSyncWaiter(err error) {
|
||||||
|
|
@ -434,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 {
|
||||||
|
|
@ -494,10 +433,6 @@ func (wa *WhatsAppClient) HandleMatrixViewingChat(ctx context.Context, msg *brid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg.Portal != nil && msg.Portal.ID == waid.MakePortalID(types.MuseJID) {
|
|
||||||
go wa.requestMuseProfile(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.Portal == nil || msg.Portal.Metadata.(*waid.PortalMetadata).LastSync.Add(5*time.Minute).After(time.Now()) {
|
if msg.Portal == nil || msg.Portal.Metadata.(*waid.PortalMetadata).LastSync.Add(5*time.Minute).After(time.Now()) {
|
||||||
// If we resynced this portal within the last 5 minutes, don't do it again
|
// If we resynced this portal within the last 5 minutes, don't do it again
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,13 @@ func fnSync(ce *commands.Event) {
|
||||||
case "appstate":
|
case "appstate":
|
||||||
names := appstate.AllPatchNames[:]
|
names := appstate.AllPatchNames[:]
|
||||||
if len(ce.Args) > 1 {
|
if len(ce.Args) > 1 {
|
||||||
names = parseAppStateNames(ce, ce.Args[1:])
|
names = exslices.CastFuncFilter(ce.Args[1:], func(name string) (appstate.WAPatchName, bool) {
|
||||||
|
if !slices.Contains(appstate.AllPatchNames[:], appstate.WAPatchName(name)) {
|
||||||
|
ce.Reply("Invalid app state name `%s`", name)
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return appstate.WAPatchName(name), true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
err := wa.Client.FetchAppState(ce.Ctx, name, true, false)
|
err := wa.Client.FetchAppState(ce.Ctx, name, true, false)
|
||||||
|
|
@ -168,50 +174,6 @@ func fnSync(ce *commands.Event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAppStateNames(ce *commands.Event, args []string) []appstate.WAPatchName {
|
|
||||||
return exslices.CastFuncFilter(args, func(name string) (appstate.WAPatchName, bool) {
|
|
||||||
if !slices.Contains(appstate.AllPatchNames[:], appstate.WAPatchName(name)) {
|
|
||||||
ce.Reply("Invalid app state name `%s`", name)
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
return appstate.WAPatchName(name), true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmdResetAppState = &commands.FullHandler{
|
|
||||||
Func: fnResetAppState,
|
|
||||||
Name: "reset-appstate",
|
|
||||||
Help: commands.HelpMeta{
|
|
||||||
Section: commands.HelpSectionAdmin,
|
|
||||||
Description: "Ask your phone to reset broken app state. This logs out all linked devices.",
|
|
||||||
Args: "<names...>",
|
|
||||||
},
|
|
||||||
RequiresLogin: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
func fnResetAppState(ce *commands.Event) {
|
|
||||||
login := ce.User.GetDefaultLogin()
|
|
||||||
if login == nil {
|
|
||||||
ce.Reply("Login not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(ce.Args) == 0 {
|
|
||||||
ce.Reply("Usage: `$cmdprefix reset-appstate <names...>`\n\nThis logs out all linked devices, including the bridge. You'll have to log in again afterwards.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
names := parseAppStateNames(ce, ce.Args)
|
|
||||||
if len(names) != len(ce.Args) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
wa := login.Client.(*WhatsAppClient)
|
|
||||||
_, err := wa.Client.SendPeerMessage(ce.Ctx, whatsmeow.BuildFatalAppStateExceptionNotification(names...))
|
|
||||||
if err != nil {
|
|
||||||
ce.Reply("Failed to send app state reset request: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ce.Reply("Sent app state reset request. Your phone should log out all linked devices shortly, after which you can log in again.")
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmdInviteLink = &commands.FullHandler{
|
var cmdInviteLink = &commands.FullHandler{
|
||||||
Func: fnInviteLink,
|
Func: fnInviteLink,
|
||||||
Name: "invite-link",
|
Name: "invite-link",
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
@ -110,10 +111,9 @@ func (wa *WhatsAppConnector) Init(bridge *bridgev2.Bridge) {
|
||||||
wa.DB = wadb.New(bridge.ID, bridge.DB.Database, bridge.Log.With().Str("db_section", "whatsapp").Logger())
|
wa.DB = wadb.New(bridge.ID, bridge.DB.Database, bridge.Log.With().Str("db_section", "whatsapp").Logger())
|
||||||
wa.MsgConv.DB = wa.DB
|
wa.MsgConv.DB = wa.DB
|
||||||
wa.Bridge.Commands.(*commands.Processor).AddHandlers(
|
wa.Bridge.Commands.(*commands.Processor).AddHandlers(
|
||||||
cmdAccept, cmdSync, cmdResetAppState, 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,27 +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)
|
|
||||||
}
|
}
|
||||||
err = wa.migrateMetaAIPortals(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to migrate Meta AI portals: %w", err)
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
err = wa.syncMismatchingGhosts(wa.Bridge.BackgroundCtx)
|
|
||||||
if err != nil {
|
|
||||||
wa.Bridge.Log.Err(err).Msg("Failed to sync mismatching ghosts")
|
|
||||||
}
|
|
||||||
err = wa.removeRejoinedPNGhosts(wa.Bridge.BackgroundCtx)
|
|
||||||
if err != nil {
|
|
||||||
wa.Bridge.Log.Err(err).Msg("Failed to remove rejoined PN ghosts")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return nil
|
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)()
|
||||||
|
|
@ -243,16 +293,7 @@ func (wa *WhatsAppConnector) GenerateTransactionID(_ id.UserID, _ id.RoomID, _ e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppConnector) ResetHTTPTransport() {
|
func (wa *WhatsAppConnector) ResetHTTPTransport() {
|
||||||
for _, login := range wa.Bridge.GetAllCachedUserLogins() {
|
// No-op for now, whatsmeow doesn't use the shared transport config yet
|
||||||
client := login.Client.(*WhatsAppClient).Client
|
|
||||||
if client == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err := wa.updateProxy(login.Log.WithContext(wa.Bridge.BackgroundCtx), client, false)
|
|
||||||
if err != nil {
|
|
||||||
login.Log.Err(err).Msg("Failed to recreate HTTP clients")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppConnector) ResetNetworkConnections() {
|
func (wa *WhatsAppConnector) ResetNetworkConnections() {
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,9 @@ 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"
|
||||||
"go.mau.fi/whatsmeow/types"
|
|
||||||
"go.mau.fi/whatsmeow/types/events"
|
"go.mau.fi/whatsmeow/types/events"
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/mautrix/bridgev2"
|
"maunium.net/go/mautrix/bridgev2"
|
||||||
|
|
@ -54,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,
|
||||||
}
|
}
|
||||||
|
|
@ -86,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")
|
||||||
|
|
@ -103,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).
|
||||||
|
|
@ -111,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 {
|
||||||
|
|
@ -137,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) {
|
||||||
|
|
@ -164,19 +146,7 @@ func (wa *WhatsAppConnector) downloadStickerDirectMedia(ctx context.Context, par
|
||||||
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)
|
||||||
}
|
}
|
||||||
sticker, err := wa.MsgConv.GetCachedSticker(ctx, waClient.Client, parsedID.Sticker.PackID, parsedID.Sticker.FileHash)
|
sticker, err := wa.MsgConv.GetCachedSticker(ctx, waClient.Client, parsedID.Sticker.PackID, parsedID.Sticker.FileHash)
|
||||||
var httpErr whatsmeow.DownloadHTTPError
|
if err != nil {
|
||||||
if errors.As(err, &httpErr) {
|
|
||||||
switch httpErr.StatusCode {
|
|
||||||
case http.StatusNotFound, http.StatusGone:
|
|
||||||
return nil, mautrix.MNotFound.WithMessage("Sticker pack not found")
|
|
||||||
case http.StatusUnauthorized, http.StatusForbidden:
|
|
||||||
return nil, mautrix.MForbidden.WithMessage("WhatsApp denied access to the sticker pack")
|
|
||||||
case http.StatusTooManyRequests:
|
|
||||||
return nil, mautrix.MLimitExceeded.WithMessage("Rate limited by WhatsApp while fetching the sticker pack").WithCanRetry(true)
|
|
||||||
default:
|
|
||||||
return nil, mautrix.MUnknown.WithMessage("WhatsApp returned HTTP %d while fetching the sticker pack", httpErr.StatusCode).WithStatus(http.StatusBadGateway).WithCanRetry(true)
|
|
||||||
}
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if sticker == nil {
|
} else if sticker == nil {
|
||||||
return nil, mautrix.MNotFound.WithMessage("Sticker not found in pack")
|
return nil, mautrix.MNotFound.WithMessage("Sticker not found in pack")
|
||||||
|
|
@ -186,9 +156,6 @@ func (wa *WhatsAppConnector) downloadStickerDirectMedia(ctx context.Context, par
|
||||||
|
|
||||||
func (wa *WhatsAppConnector) downloadMessageDirectMedia(ctx context.Context, parsedID *waid.ParsedMediaID, params map[string]string) (mediaproxy.GetMediaResponse, error) {
|
func (wa *WhatsAppConnector) downloadMessageDirectMedia(ctx context.Context, parsedID *waid.ParsedMediaID, params map[string]string) (mediaproxy.GetMediaResponse, error) {
|
||||||
msg, err := wa.Bridge.DB.Message.GetFirstPartByID(ctx, parsedID.UserLogin, parsedID.Message.String())
|
msg, err := wa.Bridge.DB.Message.GetFirstPartByID(ctx, parsedID.UserLogin, parsedID.Message.String())
|
||||||
if err == nil && msg == nil && parsedID.Message.Chat.Server == types.HiddenUserServer && parsedID.Message.Sender.Server == types.DefaultUserServer {
|
|
||||||
msg, err = wa.getDirectMediaMessageWithLIDSender(ctx, parsedID)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get message: %w", err)
|
return nil, fmt.Errorf("failed to get message: %w", err)
|
||||||
} else if msg == nil {
|
} else if msg == nil {
|
||||||
|
|
@ -231,16 +198,6 @@ func (wa *WhatsAppConnector) downloadMessageDirectMedia(ctx context.Context, par
|
||||||
return wa.makeDirectMediaResponse(ctx, waClient, keys, keys.MimeType, msg.ID, keys, params)
|
return wa.makeDirectMediaResponse(ctx, waClient, keys, keys.MimeType, msg.ID, keys, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wa *WhatsAppConnector) getDirectMediaMessageWithLIDSender(ctx context.Context, parsedID *waid.ParsedMediaID) (*database.Message, error) {
|
|
||||||
lid, err := wa.DeviceStore.LIDMap.GetLIDForPN(ctx, parsedID.Message.Sender)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get LID for sender: %w", err)
|
|
||||||
} else if lid.IsEmpty() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return wa.Bridge.DB.Message.GetFirstPartByID(ctx, parsedID.UserLogin, waid.MakeMessageID(parsedID.Message.Chat, lid, parsedID.Message.ID))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wa *WhatsAppConnector) makeDirectMediaResponse(
|
func (wa *WhatsAppConnector) makeDirectMediaResponse(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
waClient *WhatsAppClient,
|
waClient *WhatsAppClient,
|
||||||
|
|
@ -255,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
|
||||||
}
|
}
|
||||||
|
|
@ -302,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 {
|
||||||
|
|
@ -329,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.")
|
||||||
|
|
@ -385,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 {
|
||||||
|
|
|
||||||
|
|
@ -1,156 +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"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.mau.fi/whatsmeow"
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
"maunium.net/go/mautrix/bridgev2"
|
|
||||||
"maunium.net/go/mautrix/event"
|
|
||||||
|
|
||||||
"go.mau.fi/mautrix-whatsapp/pkg/waid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// wrapConversionError turns a failure to convert a Matrix event into a message status. Nothing has been
|
|
||||||
// sent to WhatsApp at that point, so the failure is certain.
|
|
||||||
func wrapConversionError(err error) error {
|
|
||||||
if err == nil || errors.As(err, &bridgev2.MessageStatus{}) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage("failed to convert message for WhatsApp").
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrapWhatsAppError turns an error from a whatsmeow request made while handling a Matrix event into a
|
|
||||||
// message status.
|
|
||||||
func wrapWhatsAppError(err error) error {
|
|
||||||
if err == nil || errors.As(err, &bridgev2.MessageStatus{}) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var iqErr *whatsmeow.IQError
|
|
||||||
var disconnectErr *whatsmeow.DisconnectedError
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, whatsmeow.ErrNotConnected), errors.Is(err, whatsmeow.ErrClientIsNil):
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage("not connected to WhatsApp").
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
case errors.Is(err, whatsmeow.ErrMessageTimedOut), errors.Is(err, whatsmeow.ErrIQTimedOut),
|
|
||||||
errors.Is(err, context.DeadlineExceeded), errors.As(err, &disconnectErr):
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage("no response from WhatsApp").
|
|
||||||
WithIsCertain(false).
|
|
||||||
WithSendNotice(true)
|
|
||||||
case errors.Is(err, whatsmeow.ErrIQRateOverLimit):
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage("rate limited by WhatsApp").
|
|
||||||
WithErrorReason(event.MessageStatusNetworkError).
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
case errors.Is(err, whatsmeow.ErrIQForbidden), errors.Is(err, whatsmeow.ErrIQNotAuthorized):
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage("you don't have permission to do that on WhatsApp").
|
|
||||||
WithErrorReason(event.MessageStatusNoPermission).
|
|
||||||
WithStatus(event.MessageStatusFail).
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
case errors.Is(err, whatsmeow.ErrServerReturnedError), errors.As(err, &iqErr):
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage("WhatsApp returned an error").
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
default:
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage("unexpected error from WhatsApp").
|
|
||||||
WithSendNotice(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrapSendError turns an error from sending a message to WhatsApp into a message status.
|
|
||||||
func (wa *WhatsAppClient) wrapSendError(err error) error {
|
|
||||||
switch sendErrorCode(err) {
|
|
||||||
case 403:
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage("you don't have permission to do that on WhatsApp").
|
|
||||||
WithErrorReason(event.MessageStatusNoPermission).
|
|
||||||
WithStatus(event.MessageStatusFail).
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
case 463:
|
|
||||||
msg := "WhatsApp has temporarily restricted your account from messaging new contacts"
|
|
||||||
if until := wa.UserLogin.Metadata.(*waid.UserLoginMetadata).ReachoutTimelockUntil; until.After(time.Now()) {
|
|
||||||
msg = fmt.Sprintf("WhatsApp has restricted your account from messaging new contacts until %s", until.UTC().Format("Jan 2, 2006 at 3:04 PM MST"))
|
|
||||||
}
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage(msg).
|
|
||||||
WithErrorReason(event.MessageStatusNoPermission).
|
|
||||||
WithStatus(event.MessageStatusFail).
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
case 421, 479:
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage("WhatsApp rejected the message").
|
|
||||||
WithErrorReason(event.MessageStatusNetworkError).
|
|
||||||
WithStatus(event.MessageStatusFail).
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
default:
|
|
||||||
return wrapWhatsAppError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendErrorCode returns the code WhatsApp rejected a message with, which whatsmeow only includes in the
|
|
||||||
// error text.
|
|
||||||
func sendErrorCode(err error) int {
|
|
||||||
if !errors.Is(err, whatsmeow.ErrServerReturnedError) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
_, codeStr, _ := strings.Cut(err.Error(), whatsmeow.ErrServerReturnedError.Error()+" ")
|
|
||||||
code, _ := strconv.Atoi(codeStr)
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrapWhatsAppRespError turns an error from a whatsmeow request made during login or provisioning into
|
|
||||||
// an HTTP error, keeping the original error in the chain. Clients retry 429 and 502-504 automatically,
|
|
||||||
// which isn't wanted for provisioning requests, so those statuses aren't used.
|
|
||||||
func wrapWhatsAppRespError(err error) error {
|
|
||||||
var mapped bridgev2.RespError
|
|
||||||
var disconnectErr *whatsmeow.DisconnectedError
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, whatsmeow.ErrIQRateOverLimit):
|
|
||||||
mapped = bridgev2.RespError(mautrix.MLimitExceeded.WithMessage("Rate limited by WhatsApp. Try again later.").WithStatus(http.StatusBadRequest))
|
|
||||||
case errors.Is(err, whatsmeow.ErrNotConnected), errors.Is(err, whatsmeow.ErrClientIsNil), errors.As(err, &disconnectErr):
|
|
||||||
mapped = bridgev2.RespError(mautrix.MUnknown.WithMessage("Not connected to WhatsApp"))
|
|
||||||
case errors.Is(err, whatsmeow.ErrIQTimedOut), errors.Is(err, context.DeadlineExceeded):
|
|
||||||
mapped = bridgev2.RespError(mautrix.MUnknown.WithMessage("WhatsApp didn't respond in time"))
|
|
||||||
case errors.Is(err, whatsmeow.ErrIQForbidden), errors.Is(err, whatsmeow.ErrIQNotAuthorized):
|
|
||||||
mapped = bridgev2.RespError(mautrix.MForbidden.WithMessage("WhatsApp denied the request"))
|
|
||||||
default:
|
|
||||||
mapped = bridgev2.RespError(mautrix.MUnknown.WithMessage("Request to WhatsApp failed"))
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%w: %w", mapped.WithInternalError(err), err)
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue