mirror of
https://github.com/mautrix/whatsapp.git
synced 2026-08-22 12:04:55 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d70eb6b135 | ||
|
|
bbc9a37b30 | ||
|
|
ba58807b0f |
||
|
|
5ef4c47b44 | ||
|
|
0f1832595f |
||
|
|
e7e5e57d9e | ||
|
|
80d69434fb | ||
|
|
07f0c83509 | ||
|
|
1ee6deeb9e | ||
|
|
a13989e656 | ||
|
|
b0be90d0d1 | ||
|
|
3a31b5bd8f | ||
|
|
d96c57046c | ||
|
|
85ace3ddcb | ||
|
|
09a43f5099 | ||
|
|
ee8592c842 | ||
|
|
3ec28346c8 |
||
|
|
911f5a1568 | ||
|
|
09ec71d21a |
||
|
|
28b69e99f7 | ||
|
|
5affed71d3 | ||
|
|
13c2470aee | ||
|
|
a86f5eb9bf | ||
|
|
06a93a559d | ||
|
|
70c7325bb2 | ||
|
|
e0d37d9fa9 | ||
|
|
316a65bae0 | ||
|
|
ba6430c701 | ||
|
|
c2fc0d0b81 | ||
|
|
5250ddfa7d | ||
|
|
a1a4cf87b3 | ||
|
|
942d1b4c0a | ||
|
|
8d34f6f6bd |
34 changed files with 767 additions and 490 deletions
8
.github/workflows/go.yml
vendored
8
.github/workflows/go.yml
vendored
|
|
@ -11,14 +11,14 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go-version: ["1.25", "1.26"]
|
||||
name: Lint ${{ matrix.go-version == '1.26' && '(latest)' || '(old)' }}
|
||||
go-version: ["1.26", "1.27"]
|
||||
name: Lint ${{ matrix.go-version == '1.27' && '(latest)' || '(old)' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
cache: true
|
||||
|
|
|
|||
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -1,3 +1,14 @@
|
|||
# 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
|
||||
|
||||
* Added placeholder for group message history share notices.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
FROM golang:1-alpine3.23 AS builder
|
||||
FROM golang:1-alpine3.24 AS builder
|
||||
|
||||
RUN apk add --no-cache git ca-certificates build-base su-exec olm-dev
|
||||
|
||||
|
|
@ -6,7 +6,7 @@ COPY . /build
|
|||
WORKDIR /build
|
||||
RUN ./build.sh
|
||||
|
||||
FROM alpine:3.23
|
||||
FROM alpine:3.24
|
||||
|
||||
ENV UID=1337 \
|
||||
GID=1337
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
ARG DOCKER_HUB="docker.io"
|
||||
|
||||
FROM ${DOCKER_HUB}/alpine:3.23
|
||||
FROM ${DOCKER_HUB}/alpine:3.24
|
||||
|
||||
ENV UID=1337 \
|
||||
GID=1337
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ var m = mxmain.BridgeMain{
|
|||
Name: "mautrix-whatsapp",
|
||||
URL: "https://github.com/mautrix/whatsapp",
|
||||
Description: "A Matrix-WhatsApp puppeting bridge.",
|
||||
Version: "26.06",
|
||||
Version: "26.08",
|
||||
SemCalVer: true,
|
||||
Connector: &connector.WhatsAppConnector{},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
//go:build amd64 && cgo && !noplugin
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"plugin"
|
||||
|
||||
"go.mau.fi/util/exerrors"
|
||||
|
||||
"go.mau.fi/mautrix-whatsapp/pkg/connector"
|
||||
)
|
||||
|
||||
func init() {
|
||||
path := os.Getenv("WM_PLUGIN_PATH")
|
||||
if path == "" {
|
||||
return
|
||||
}
|
||||
fmt.Println("Loading plugin from", path)
|
||||
plug := exerrors.Must(plugin.Open(path))
|
||||
sym := exerrors.Must(plug.Lookup("NewClient"))
|
||||
connector.NewMC = sym.(connector.NewMCFunc)
|
||||
}
|
||||
34
go.mod
34
go.mod
|
|
@ -1,8 +1,8 @@
|
|||
module go.mau.fi/mautrix-whatsapp
|
||||
|
||||
go 1.25.0
|
||||
go 1.26.0
|
||||
|
||||
toolchain go1.26.4
|
||||
toolchain go1.27.0
|
||||
|
||||
tool go.mau.fi/util/cmd/maubuild
|
||||
|
||||
|
|
@ -10,15 +10,15 @@ require (
|
|||
github.com/lib/pq v1.12.3
|
||||
github.com/rs/zerolog v1.35.1
|
||||
github.com/tidwall/gjson v1.19.0
|
||||
go.mau.fi/util v0.9.10
|
||||
go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde
|
||||
go.mau.fi/webp v0.3.0
|
||||
go.mau.fi/whatsmeow v0.0.0-20260630180629-b572e5bcb92b
|
||||
golang.org/x/image v0.42.0
|
||||
golang.org/x/net v0.56.0
|
||||
golang.org/x/sync v0.21.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
go.mau.fi/whatsmeow v0.0.0-20260821141805-33cfac511629
|
||||
golang.org/x/image v0.45.0
|
||||
golang.org/x/net v0.58.0
|
||||
golang.org/x/sync v0.22.0
|
||||
google.golang.org/protobuf v1.36.12
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
maunium.net/go/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895
|
||||
maunium.net/go/mautrix v0.30.1-0.20260820142923-ae00f2c62e1e
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
@ -31,8 +31,8 @@ require (
|
|||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.45 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.49 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20260816044145-ed329add6b1b // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
||||
|
|
@ -40,14 +40,14 @@ require (
|
|||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.5.27 // indirect
|
||||
github.com/yuin/goldmark v1.8.2 // indirect
|
||||
github.com/yuin/goldmark v1.8.5 // indirect
|
||||
go.mau.fi/libsignal v0.2.2 // indirect
|
||||
go.mau.fi/zeroconfig v0.2.0 // indirect
|
||||
golang.org/x/crypto v0.53.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect
|
||||
golang.org/x/mod v0.37.0 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
golang.org/x/crypto v0.55.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297 // indirect
|
||||
golang.org/x/mod v0.40.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/text v0.41.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
maunium.net/go/mauflag v1.0.0 // indirect
|
||||
|
|
|
|||
60
go.sum
60
go.sum
|
|
@ -34,10 +34,10 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP
|
|||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.45 h1:6KA/spDguL3KV8rnybG7ezSaE4SeMR3KC9VbUoAQaIk=
|
||||
github.com/mattn/go-sqlite3 v1.14.45/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
|
||||
github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81 h1:WDsQxOJDy0N1VRAjXLpi8sCEZRSGarLWQevDxpTBRrM=
|
||||
github.com/petermattis/goid v0.0.0-20260330135022-df67b199bc81/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/mattn/go-sqlite3 v1.14.49 h1:B8jBHC3xhxZgxztrgruTuLucebnULQnx4W7cF7SAE9w=
|
||||
github.com/mattn/go-sqlite3 v1.14.49/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=
|
||||
github.com/petermattis/goid v0.0.0-20260816044145-ed329add6b1b h1:sS7HLzwS+dO+gxATgQfeZDEdUZe2pKAB3nGoUwP5zU0=
|
||||
github.com/petermattis/goid v0.0.0-20260816044145-ed329add6b1b/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
|
|
@ -67,37 +67,37 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
|||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTdwFp0s=
|
||||
github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
|
||||
github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
|
||||
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
||||
github.com/yuin/goldmark v1.8.5/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
go.mau.fi/libsignal v0.2.2 h1:QV+XdzQkm3x3aSG7FcqfGSZuFXz83pRZPBFaPygHbOU=
|
||||
go.mau.fi/libsignal v0.2.2/go.mod h1:CRlIQg2J8uYTfDFvNoO8/KcZjs5cey0vbc6oj/bssY0=
|
||||
go.mau.fi/util v0.9.10 h1:wzvz5iDHyqDXB8vgisD4d3SzucLXNM3iNY+1O1RoHtg=
|
||||
go.mau.fi/util v0.9.10/go.mod h1:YQOxySn+ZE3qSYqNxvyX7Yi3suA8YK17PS6QqBREW7A=
|
||||
go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde h1:eMHY9dMDkNuDMWhfTbMZHbbsxj7G6mfujjKei1HaFQM=
|
||||
go.mau.fi/util v0.10.1-0.20260820140024-eb612d936fde/go.mod h1:z0ZZNt4hq3FZbUKnunexE/QscCx7VkLvQSvtggc/aE8=
|
||||
go.mau.fi/webp v0.3.0 h1:gVHQZtz21Ziwj+CDuklbX9mqpsnDIFKxs/BJyV7iZzA=
|
||||
go.mau.fi/webp v0.3.0/go.mod h1:rlZFTev+dYxhvk+XNBP/5GcTt4gXmzAB4DU0aGUYIQo=
|
||||
go.mau.fi/whatsmeow v0.0.0-20260630180629-b572e5bcb92b h1:ZUk1ErarDNpnbosXR/MeOz2gkqA4S1bh8zjaSRj7N+Y=
|
||||
go.mau.fi/whatsmeow v0.0.0-20260630180629-b572e5bcb92b/go.mod h1:9dmNTYZ/1pHjPw/bz+azBsGjAkcrZbqzMrKcvG5bJ8U=
|
||||
go.mau.fi/whatsmeow v0.0.0-20260821141805-33cfac511629 h1:99p9fTS1G73a1aifCVaV5MX4hDq0lVUKWGoUNoXyE3A=
|
||||
go.mau.fi/whatsmeow v0.0.0-20260821141805-33cfac511629/go.mod h1:aMd13H2xFFGH9cskcvxo4Aae+TmyFN38yw+HvsrpwVg=
|
||||
go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU=
|
||||
go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M=
|
||||
golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY=
|
||||
golang.org/x/image v0.42.0 h1:1gSs6ehNWXLbkHBIPcWztk3D/6aIA/8hauiAYtlodVY=
|
||||
golang.org/x/image v0.42.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
|
||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
||||
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
|
||||
golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297 h1:YXnL44eJ77R+ji4/ooy8UsXIhz+lbi2Qgdlc8iRN0gY=
|
||||
golang.org/x/exp v0.0.0-20260813180055-c1d0aacb2297/go.mod h1:Mkmymgv+uMpSQ/XxJ/7GpdrdYoqm3u72jEbpCLiJmNk=
|
||||
golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0=
|
||||
golang.org/x/image v0.45.0/go.mod h1:n62x/7RqlwXDvGsSU4u6IUTUf6KghUZ9Bt7cG/T9Fx4=
|
||||
golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
|
||||
golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
|
||||
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
|
@ -107,5 +107,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
|
||||
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
|
||||
maunium.net/go/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895 h1:oe87/mP1vo4LnA6CJDEfyg5ZiAOar2quJNypfO8eS4Y=
|
||||
maunium.net/go/mautrix v0.28.2-0.20260702123919-bf2dbb2aa895/go.mod h1:mWXQNmOlrq4VTDU9f1HO03BSIswdUIyyY4wUKHqwzzY=
|
||||
maunium.net/go/mautrix v0.30.1-0.20260820142923-ae00f2c62e1e h1:ATvERyBIUXJrnL7ezP2xCLLGCNmGClmfzoQq4N3jBcA=
|
||||
maunium.net/go/mautrix v0.30.1-0.20260820142923-ae00f2c62e1e/go.mod h1:Y02sBiAvfEVqK24bwVGCprmLATRZ7prWel3ZpB413e0=
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.mau.fi/util/exmaps"
|
||||
"go.mau.fi/util/ptr"
|
||||
"go.mau.fi/whatsmeow"
|
||||
"go.mau.fi/whatsmeow/proto/waE2E"
|
||||
|
|
@ -228,30 +229,32 @@ func (wa *WhatsAppClient) handleWAHistorySync(
|
|||
} else {
|
||||
totalMessageCount += len(conv.GetMessages())
|
||||
}
|
||||
if jid.Server == types.HiddenUserServer {
|
||||
pn, err := wa.GetStore().LIDs.GetPNForLID(ctx, jid)
|
||||
if jid.Server == types.DefaultUserServer {
|
||||
lid, err := wa.GetStore().LIDs.GetLIDForPN(ctx, jid)
|
||||
if err != nil {
|
||||
log.Err(err).Stringer("lid", jid).Msg("Failed to get PN for LID in history sync")
|
||||
} else if pn.IsEmpty() {
|
||||
log.Warn().Stringer("lid", jid).Msg("No PN found for LID in history sync")
|
||||
log.Err(err).Stringer("pn", jid).Msg("Failed to get LID for phone number in history sync")
|
||||
} else if lid.IsEmpty() {
|
||||
log.Warn().Stringer("pn", jid).Msg("No LID found for phone number in history sync")
|
||||
} else {
|
||||
log.Debug().
|
||||
Stringer("lid", jid).
|
||||
Stringer("pn", pn).
|
||||
Msg("Rerouting LID DM to phone number in history sync")
|
||||
jid = pn
|
||||
Stringer("lid", lid).
|
||||
Stringer("pn", jid).
|
||||
Msg("Rerouting phone number DM to LID in history sync")
|
||||
jid = lid
|
||||
}
|
||||
}
|
||||
log.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||
return c.Stringer("chat_jid", jid)
|
||||
})
|
||||
|
||||
var minTime, maxTime, firstItemTime, lastItemTime time.Time
|
||||
var minTimeIndex, maxTimeIndex int
|
||||
var firstItemTime, lastItemTime time.Time
|
||||
|
||||
ignoredTypes := 0
|
||||
messages := make([]*wadb.HistorySyncMessageTuple, 0, len(conv.GetMessages()))
|
||||
for i, rawMsg := range conv.GetMessages() {
|
||||
rawMessages := conv.GetMessages()
|
||||
messages := make([]*wadb.HistorySyncMessageTuple, 0, len(rawMessages))
|
||||
allowClamp := conv.GetCommentsCount() == 0
|
||||
var newerTS uint64
|
||||
for i, rawMsg := range rawMessages {
|
||||
// Don't store messages that will just be skipped.
|
||||
msgEvt, err := wa.Client.ParseWebMessage(jid, rawMsg.GetMessage())
|
||||
if err != nil {
|
||||
|
|
@ -269,20 +272,30 @@ func (wa *WhatsAppClient) handleWAHistorySync(
|
|||
firstItemTime = msgEvt.Info.Timestamp
|
||||
}
|
||||
lastItemTime = msgEvt.Info.Timestamp
|
||||
if minTime.IsZero() || msgEvt.Info.Timestamp.Before(minTime) {
|
||||
minTime = msgEvt.Info.Timestamp
|
||||
minTimeIndex = i
|
||||
}
|
||||
if maxTime.IsZero() || msgEvt.Info.Timestamp.After(maxTime) {
|
||||
maxTime = msgEvt.Info.Timestamp
|
||||
maxTimeIndex = i
|
||||
}
|
||||
|
||||
msgType := getMessageType(msgEvt.Message)
|
||||
if msgType == "ignore" || strings.HasPrefix(msgType, "unknown_protocol_") {
|
||||
ignoredTypes++
|
||||
continue
|
||||
}
|
||||
// Comments (replies) in announcement groups are not ordered by timestamp, so don't clamp them.
|
||||
if rawMsg.GetMessage().GetCommentMetadata().GetCommentParentKey() != nil {
|
||||
allowClamp = false
|
||||
}
|
||||
// WhatsApp has bugs where some random messages will have timestamps decades in the future.
|
||||
// To ensure they don't mess up our ordering, require timestamps of older messages to be
|
||||
// before the previous (newer) message.
|
||||
if currentTS := rawMsg.GetMessage().GetMessageTimestamp(); newerTS > 0 && allowClamp && currentTS > newerTS {
|
||||
log.Warn().
|
||||
Time("current_ts", time.Unix(int64(currentTS), 0)).
|
||||
Time("prev_ts", time.Unix(int64(newerTS), 0)).
|
||||
Int("msg_index", i).
|
||||
Str("msg_id", rawMsg.GetMessage().GetKey().GetID()).
|
||||
Msg("Clamping message timestamp")
|
||||
rawMsg.Message.MessageTimestamp = ptr.Ptr(newerTS)
|
||||
msgEvt.Info.Timestamp = time.Unix(int64(newerTS), 0)
|
||||
}
|
||||
newerTS = rawMsg.GetMessage().GetMessageTimestamp()
|
||||
marshaled, err := proto.Marshal(rawMsg)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).
|
||||
|
|
@ -297,13 +310,8 @@ func (wa *WhatsAppClient) handleWAHistorySync(
|
|||
log.Debug().
|
||||
Int("wrapped_count", len(messages)).
|
||||
Int("ignored_msg_type_count", ignoredTypes).
|
||||
Time("lowest_time", minTime).
|
||||
Int("lowest_time_index", minTimeIndex).
|
||||
Time("highest_time", maxTime).
|
||||
Int("highest_time_index", maxTimeIndex).
|
||||
Time("first_item_time", firstItemTime).
|
||||
Time("last_item_time", lastItemTime).
|
||||
Bool("highest_time_mismatch", firstItemTime != maxTime).
|
||||
Dict("metadata", zerolog.Dict().
|
||||
Uint32("ephemeral_expiration", conv.GetEphemeralExpiration()).
|
||||
Int64("ephemeral_setting_timestamp", conv.GetEphemeralSettingTimestamp()).
|
||||
|
|
@ -319,7 +327,7 @@ func (wa *WhatsAppClient) handleWAHistorySync(
|
|||
Msg("Collected messages to save from history sync conversation")
|
||||
|
||||
if len(messages) > 0 {
|
||||
err = wa.Main.DB.Conversation.Put(ctx, wadb.NewConversation(wa.UserLogin.ID, jid, conv, maxTime))
|
||||
err = wa.Main.DB.Conversation.Put(ctx, wadb.NewConversation(wa.UserLogin.ID, jid, conv, firstItemTime))
|
||||
if err != nil {
|
||||
if stopOnError {
|
||||
return fmt.Errorf("failed to save conversation metadata for %s: %w", jid, err)
|
||||
|
|
@ -474,6 +482,9 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if portalJID.Server == types.DefaultUserServer {
|
||||
zerolog.Ctx(ctx).Warn().Stringer("portal_jid", portalJID).Msg("FetchMessages called for phone number portal")
|
||||
}
|
||||
var markRead bool
|
||||
var startTime, endTime *time.Time
|
||||
var conv *wadb.Conversation
|
||||
|
|
@ -528,7 +539,7 @@ func (wa *WhatsAppClient) FetchMessages(ctx context.Context, params bridgev2.Fet
|
|||
Forward: params.Forward,
|
||||
}, nil
|
||||
} else if hasMore {
|
||||
return wa.fetchMessagesFromPhone(ctx, params)
|
||||
return wa.fetchMessagesFromPhone(ctx, portalJID, params)
|
||||
}
|
||||
return &bridgev2.FetchMessagesResponse{
|
||||
HasMore: false,
|
||||
|
|
@ -594,6 +605,7 @@ func (wa *WhatsAppClient) convertHistorySyncMessages(
|
|||
newestTS := messages[0].GetMessageTimestamp()
|
||||
convertedMessages := make([]*bridgev2.BackfillMessage, 0, len(messages))
|
||||
var mediaRequests []*wadb.MediaRequest
|
||||
dups := make(exmaps.Set[networkid.MessageID])
|
||||
for i, msg := range messages {
|
||||
evt, err := wa.Client.ParseWebMessage(portalJID, msg)
|
||||
if err != nil {
|
||||
|
|
@ -617,10 +629,18 @@ func (wa *WhatsAppClient) convertHistorySyncMessages(
|
|||
continue
|
||||
}
|
||||
}
|
||||
if !wa.ensureAltJIDs(ctx, &evt.Info.MessageSource, false) {
|
||||
return nil, fmt.Errorf("failed to ensure alt JIDs for message %s", evt.Info.ID)
|
||||
}
|
||||
isViewOnce := evt.IsViewOnce || evt.IsViewOnceV2 || evt.IsViewOnceV2Extension
|
||||
converted, mediaReq := wa.convertHistorySyncMessage(
|
||||
ctx, portal, &evt.Info, evt.Message, evt.RawMessage, isViewOnce, msg.Reactions,
|
||||
)
|
||||
// This is a hack to remove duplicates where the same message is inserted with both the LID and phone number sender
|
||||
// TODO prevent those being inserted in the first place instead of hacking around it here
|
||||
if !dups.Add(converted.ID) {
|
||||
continue
|
||||
}
|
||||
convertedMessages = append(convertedMessages, converted)
|
||||
if mediaReq != nil {
|
||||
mediaRequests = append(mediaRequests, mediaReq)
|
||||
|
|
@ -651,7 +671,7 @@ func (wa *WhatsAppClient) convertHistorySyncMessages(
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, params bridgev2.FetchMessagesParams) (*bridgev2.FetchMessagesResponse, error) {
|
||||
func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, portalJID types.JID, params bridgev2.FetchMessagesParams) (*bridgev2.FetchMessagesResponse, error) {
|
||||
if params.AnchorMessage == nil {
|
||||
return nil, fmt.Errorf("anchor message is required to fetch messages from phone")
|
||||
}
|
||||
|
|
@ -663,9 +683,9 @@ func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, params bri
|
|||
msgID := wa.Client.GenerateMessageID()
|
||||
reqData := wa.Client.BuildHistorySyncRequest(&types.MessageInfo{
|
||||
MessageSource: types.MessageSource{
|
||||
Chat: parsed.Chat,
|
||||
Chat: portalJID,
|
||||
Sender: parsed.Sender,
|
||||
IsFromMe: parsed.Sender.ToNonAD() == wa.JID.ToNonAD() || parsed.Sender.ToNonAD() == wa.Device.GetLID().ToNonAD(),
|
||||
IsFromMe: wa.IsOwnJID(parsed.Sender),
|
||||
IsGroup: parsed.Chat.Server == types.GroupServer,
|
||||
},
|
||||
ID: parsed.ID,
|
||||
|
|
@ -673,6 +693,7 @@ func (wa *WhatsAppClient) fetchMessagesFromPhone(ctx context.Context, params bri
|
|||
}, 50)
|
||||
zerolog.Ctx(ctx).Debug().
|
||||
Str("request_msg_id", msgID).
|
||||
Stringer("portal_jid", portalJID).
|
||||
Any("anchor_msg_parsed", parsed).
|
||||
Any("request_data", reqData).
|
||||
Msg("Sending history sync request")
|
||||
|
|
@ -701,6 +722,20 @@ func (wa *WhatsAppClient) handleOnDemandHistorySync(ctx context.Context, blob *w
|
|||
zerolog.Ctx(ctx).Err(err).Str("jid", conv.GetID()).Msg("Failed to parse portal JID")
|
||||
continue
|
||||
}
|
||||
if portalJID.Server == types.DefaultUserServer {
|
||||
lid, err := wa.GetStore().LIDs.GetLIDForPN(ctx, portalJID)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Stringer("lid", portalJID).Msg("Failed to get LID for phone number in on-demand history sync")
|
||||
} else if lid.IsEmpty() {
|
||||
zerolog.Ctx(ctx).Warn().Stringer("lid", portalJID).Msg("No LID found for phone number in on-demand history sync")
|
||||
} else {
|
||||
zerolog.Ctx(ctx).Debug().
|
||||
Stringer("lid", lid).
|
||||
Stringer("pn", portalJID).
|
||||
Msg("Rerouting phone number DM to LID in on-demand history sync")
|
||||
portalJID = lid
|
||||
}
|
||||
}
|
||||
portal, err := wa.Main.Bridge.GetPortalByKey(ctx, wa.makeWAPortalKey(portalJID))
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Stringer("portal_jid", portalJID).Msg("Failed to get portal for on-demand history sync")
|
||||
|
|
@ -749,11 +784,12 @@ func (wa *WhatsAppClient) convertHistorySyncMessage(
|
|||
}
|
||||
// TODO use proper intent
|
||||
intent := wa.Main.Bridge.Bot
|
||||
msgID := waid.MakeMessageIDWithAltSender(info.Chat, info.Sender, info.SenderAlt, info.ID)
|
||||
wrapped := &bridgev2.BackfillMessage{
|
||||
ConvertedMessage: wa.Main.MsgConv.ToMatrix(ctx, portal, wa.Client, intent, msg, rawMsg, info, isViewOnce, true, nil),
|
||||
Sender: wa.makeEventSender(ctx, info.Sender),
|
||||
ID: waid.MakeMessageID(info.Chat, info.Sender, info.ID),
|
||||
TxnID: networkid.TransactionID(waid.MakeMessageID(info.Chat, info.Sender, info.ID)),
|
||||
Sender: wa.makeEventSender(ctx, pickLID(info.Sender, info.SenderAlt)),
|
||||
ID: msgID,
|
||||
TxnID: networkid.TransactionID(msgID),
|
||||
Timestamp: info.Timestamp,
|
||||
StreamOrder: info.Timestamp.Unix(),
|
||||
Reactions: make([]*bridgev2.BackfillReaction, 0, len(reactions)),
|
||||
|
|
@ -762,10 +798,10 @@ func (wa *WhatsAppClient) convertHistorySyncMessage(
|
|||
for _, reaction := range reactions {
|
||||
var sender types.JID
|
||||
if reaction.GetKey().GetFromMe() {
|
||||
sender = wa.JID
|
||||
sender = wa.GetLID()
|
||||
} else if reaction.GetKey().GetParticipant() != "" {
|
||||
sender, _ = types.ParseJID(*reaction.Key.Participant)
|
||||
} else if info.Chat.Server == types.DefaultUserServer || info.Chat.Server == types.BotServer {
|
||||
} else if info.Chat.Server == types.DefaultUserServer || info.Chat.Server == types.HiddenUserServer || info.Chat.Server == types.BotServer {
|
||||
sender = info.Chat
|
||||
}
|
||||
if sender.IsEmpty() {
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ func (wa *WhatsAppConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilit
|
|||
}
|
||||
|
||||
func (wa *WhatsAppConnector) GetBridgeInfoVersion() (info, caps int) {
|
||||
return 1, 8
|
||||
return 1, 9
|
||||
}
|
||||
|
||||
const WAMaxFileSize = 2000 * 1024 * 1024
|
||||
|
|
@ -67,7 +67,7 @@ func supportedIfFFmpeg() event.CapabilitySupportLevel {
|
|||
}
|
||||
|
||||
func capID() string {
|
||||
base := "fi.mau.whatsapp.capabilities.2026_05_12"
|
||||
base := "fi.mau.whatsapp.capabilities.2026_07_22"
|
||||
if ffmpeg.Supported() {
|
||||
return base + "+ffmpeg"
|
||||
}
|
||||
|
|
@ -174,21 +174,26 @@ var whatsappCaps = &event.RoomFeatures{
|
|||
event.MemberActionKick: event.CapLevelFullySupported,
|
||||
event.MemberActionLeave: event.CapLevelFullySupported,
|
||||
},
|
||||
MaxTextLength: MaxTextLength,
|
||||
LocationMessage: event.CapLevelFullySupported,
|
||||
Poll: event.CapLevelFullySupported,
|
||||
Reply: event.CapLevelFullySupported,
|
||||
Edit: event.CapLevelFullySupported,
|
||||
EditMaxAge: ptr.Ptr(jsontime.S(EditMaxAge)),
|
||||
Delete: event.CapLevelFullySupported,
|
||||
DeleteForMe: false,
|
||||
DeleteMaxAge: ptr.Ptr(jsontime.S(2 * 24 * time.Hour)),
|
||||
Reaction: event.CapLevelFullySupported,
|
||||
ReactionCount: 1,
|
||||
ReadReceipts: true,
|
||||
TypingNotifications: true,
|
||||
DisappearingTimer: waDisappearingCap,
|
||||
DeleteChat: true,
|
||||
MaxTextLength: MaxTextLength,
|
||||
LocationMessage: event.CapLevelFullySupported,
|
||||
Poll: event.CapLevelFullySupported,
|
||||
PollEnd: event.CapLevelUnsupported,
|
||||
PollHiddenVotes: event.CapLevelUnsupported,
|
||||
PollDuplicateOptions: event.CapLevelUnsupported,
|
||||
PollMaxOptions: 12,
|
||||
PollOptionMaxLength: 100,
|
||||
Reply: event.CapLevelFullySupported,
|
||||
Edit: event.CapLevelFullySupported,
|
||||
EditMaxAge: ptr.Ptr(jsontime.S(EditMaxAge)),
|
||||
Delete: event.CapLevelFullySupported,
|
||||
DeleteForMe: false,
|
||||
DeleteMaxAge: ptr.Ptr(jsontime.S(2 * 24 * time.Hour)),
|
||||
Reaction: event.CapLevelFullySupported,
|
||||
ReactionCount: 1,
|
||||
ReadReceipts: true,
|
||||
TypingNotifications: true,
|
||||
DisappearingTimer: waDisappearingCap,
|
||||
DeleteChat: true,
|
||||
}
|
||||
|
||||
var whatsappDMCaps *event.RoomFeatures
|
||||
|
|
|
|||
|
|
@ -99,6 +99,18 @@ func (wa *WhatsAppClient) applyChatSettings(ctx context.Context, chatID types.JI
|
|||
zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to get chat settings")
|
||||
return
|
||||
}
|
||||
if !chat.Found {
|
||||
chatID, err = wa.GetStore().GetAltJID(ctx, chatID)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to get alternate JID to get chat settings")
|
||||
return
|
||||
}
|
||||
chat, err = wa.GetStore().ChatSettings.GetChatSettings(ctx, chatID)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to get chat settings with alternate JID")
|
||||
return
|
||||
}
|
||||
}
|
||||
info.UserLocal = &bridgev2.UserLocalPortalInfo{
|
||||
MutedUntil: ptr.Ptr(chat.MutedUntil),
|
||||
}
|
||||
|
|
@ -141,15 +153,20 @@ const PrivateChatTopic = "WhatsApp private chat"
|
|||
const BotChatTopic = "WhatsApp chat with a bot"
|
||||
|
||||
func (wa *WhatsAppClient) wrapDMInfo(ctx context.Context, jid types.JID) *bridgev2.ChatInfo {
|
||||
ownID := wa.JID
|
||||
if jid.Server == types.HiddenUserServer {
|
||||
ownID = wa.GetLID()
|
||||
}
|
||||
info := &bridgev2.ChatInfo{
|
||||
Type: ptr.Ptr(database.RoomTypeDM),
|
||||
Topic: ptr.Ptr(PrivateChatTopic),
|
||||
Members: &bridgev2.ChatMemberList{
|
||||
IsFull: true,
|
||||
TotalMemberCount: 2,
|
||||
OtherUserID: waid.MakeUserID(jid),
|
||||
MemberMap: map[networkid.UserID]bridgev2.ChatMember{
|
||||
waid.MakeUserID(jid): {EventSender: wa.makeEventSender(ctx, jid)},
|
||||
waid.MakeUserID(wa.JID): {EventSender: wa.makeEventSender(ctx, wa.JID)},
|
||||
waid.MakeUserID(jid): {EventSender: wa.makeEventSender(ctx, jid)},
|
||||
waid.MakeUserID(ownID): {EventSender: wa.makeEventSender(ctx, ownID)},
|
||||
},
|
||||
PowerLevels: &bridgev2.PowerLevelOverrides{
|
||||
Events: map[event.Type]int{
|
||||
|
|
@ -159,13 +176,14 @@ func (wa *WhatsAppClient) wrapDMInfo(ctx context.Context, jid types.JID) *bridge
|
|||
event.StateBeeperDisappearingTimer: 0,
|
||||
},
|
||||
},
|
||||
ExcludeChangesFromTimeline: true,
|
||||
},
|
||||
Type: ptr.Ptr(database.RoomTypeDM),
|
||||
ExcludeChangesFromTimeline: true,
|
||||
}
|
||||
if jid.Server == types.BotServer {
|
||||
info.Topic = ptr.Ptr(BotChatTopic)
|
||||
}
|
||||
if jid == wa.JID.ToNonAD() {
|
||||
if wa.IsOwnJID(jid) {
|
||||
// For chats with self, force-split the members so the user's own ghost is always in the room.
|
||||
info.Members.MemberMap = map[networkid.UserID]bridgev2.ChatMember{
|
||||
waid.MakeUserID(jid): {EventSender: bridgev2.EventSender{Sender: waid.MakeUserID(jid)}},
|
||||
|
|
@ -189,7 +207,7 @@ func (wa *WhatsAppClient) wrapStatusBroadcastInfo(ctx context.Context) *bridgev2
|
|||
Members: &bridgev2.ChatMemberList{
|
||||
IsFull: false,
|
||||
MemberMap: map[networkid.UserID]bridgev2.ChatMember{
|
||||
waid.MakeUserID(wa.JID): {EventSender: wa.makeEventSender(ctx, wa.JID)},
|
||||
waid.MakeUserID(wa.GetLID()): {EventSender: wa.makeEventSender(ctx, wa.GetLID())},
|
||||
},
|
||||
},
|
||||
Type: ptr.Ptr(database.RoomTypeDefault),
|
||||
|
|
@ -266,14 +284,13 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn
|
|||
Ban: ptr.Ptr(nobodyPL),
|
||||
// TODO allow invites if bridge config says to allow them, or maybe if relay mode is enabled?
|
||||
Events: map[event.Type]int{
|
||||
event.StateRoomName: metaChangePL,
|
||||
event.StateRoomAvatar: metaChangePL,
|
||||
event.StateTopic: metaChangePL,
|
||||
event.EventReaction: defaultPL,
|
||||
event.EventRedaction: defaultPL,
|
||||
|
||||
event.StateRoomName: metaChangePL,
|
||||
event.StateRoomAvatar: metaChangePL,
|
||||
event.StateTopic: metaChangePL,
|
||||
event.EventReaction: defaultPL,
|
||||
event.EventRedaction: defaultPL,
|
||||
event.EventUnstablePollResponse: defaultPL,
|
||||
event.StateBeeperDisappearingTimer: metaChangePL,
|
||||
// TODO always allow poll responses
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -316,7 +333,7 @@ func (wa *WhatsAppClient) wrapGroupInfo(ctx context.Context, info *types.GroupIn
|
|||
}
|
||||
}
|
||||
if info.IsParent && !hasSelf && info.AddressingMode == types.AddressingModeLID {
|
||||
wrapped.Members.MemberMap.Add(bridgev2.ChatMember{EventSender: wa.makeEventSender(ctx, wa.Device.LID)})
|
||||
wrapped.Members.MemberMap.Add(bridgev2.ChatMember{EventSender: wa.makeEventSender(ctx, wa.GetLID())})
|
||||
}
|
||||
|
||||
if !info.LinkedParentJID.IsEmpty() {
|
||||
|
|
@ -523,8 +540,8 @@ func (wa *WhatsAppClient) wrapNewsletterInfo(ctx context.Context, info *types.Ne
|
|||
Members: &bridgev2.ChatMemberList{
|
||||
TotalMemberCount: info.ThreadMeta.SubscriberCount,
|
||||
MemberMap: map[networkid.UserID]bridgev2.ChatMember{
|
||||
waid.MakeUserID(wa.JID): {
|
||||
EventSender: wa.makeEventSender(ctx, wa.JID),
|
||||
waid.MakeUserID(wa.GetLID()): {
|
||||
EventSender: wa.makeEventSender(ctx, wa.GetLID()),
|
||||
PowerLevel: &ownPowerLevel,
|
||||
},
|
||||
},
|
||||
|
|
@ -533,12 +550,12 @@ func (wa *WhatsAppClient) wrapNewsletterInfo(ctx context.Context, info *types.Ne
|
|||
StateDefault: ptr.Ptr(nobodyPL),
|
||||
Ban: ptr.Ptr(nobodyPL),
|
||||
Events: map[event.Type]int{
|
||||
event.StateRoomName: adminPL,
|
||||
event.StateRoomAvatar: adminPL,
|
||||
event.StateTopic: adminPL,
|
||||
event.EventReaction: defaultPL,
|
||||
event.EventRedaction: defaultPL,
|
||||
// TODO always allow poll responses
|
||||
event.StateRoomName: adminPL,
|
||||
event.StateRoomAvatar: adminPL,
|
||||
event.StateTopic: adminPL,
|
||||
event.EventReaction: defaultPL,
|
||||
event.EventRedaction: defaultPL,
|
||||
event.EventUnstablePollResponse: defaultPL,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -54,8 +54,9 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2.
|
|||
directMediaRetries: make(map[networkid.MessageID]*directMediaRetry),
|
||||
mediaRetryLock: semaphore.NewWeighted(wa.Config.HistorySync.MediaRequests.MaxAsyncHandle),
|
||||
pushNamesSynced: exsync.NewEvent(),
|
||||
createDedup: exsync.NewSet[types.MessageID](),
|
||||
appStateFullSyncAttempted: make(map[appstate.WAPatchName]time.Time),
|
||||
|
||||
disableNewsletter: store.BaseClientPayload.GetUserAgent().GetPlatform() == waWa6.ClientPayload_UserAgent_MACOS,
|
||||
}
|
||||
login.Client = w
|
||||
|
||||
|
|
@ -70,6 +71,7 @@ func (wa *WhatsAppConnector) LoadUserLogin(ctx context.Context, login *bridgev2.
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.LID = w.Device.GetLID()
|
||||
|
||||
if w.Device != nil {
|
||||
log := w.UserLogin.Log.With().Str("component", "whatsmeow").Logger()
|
||||
|
|
@ -104,6 +106,7 @@ type WhatsAppClient struct {
|
|||
Client *whatsmeow.Client
|
||||
Device *store.Device
|
||||
JID types.JID
|
||||
LID types.JID
|
||||
MC mClient
|
||||
|
||||
historySyncWakeup chan struct{}
|
||||
|
|
@ -118,7 +121,8 @@ type WhatsAppClient struct {
|
|||
isNewLogin bool
|
||||
pushNamesSynced *exsync.Event
|
||||
lastPresence types.Presence
|
||||
createDedup *exsync.Set[types.MessageID]
|
||||
|
||||
disableNewsletter bool
|
||||
|
||||
appStateRecoveryLock sync.Mutex
|
||||
appStateFullSyncAttempted map[appstate.WAPatchName]time.Time
|
||||
|
|
@ -185,7 +189,19 @@ func (wa *WhatsAppClient) RegisterPushNotifications(ctx context.Context, pushTyp
|
|||
}
|
||||
|
||||
func (wa *WhatsAppClient) IsThisUser(_ context.Context, userID networkid.UserID) bool {
|
||||
return userID == waid.MakeUserID(wa.JID)
|
||||
return userID == waid.MakeUserID(wa.JID) || userID == waid.MakeUserID(wa.GetLID())
|
||||
}
|
||||
|
||||
func (wa *WhatsAppClient) IsOwnJID(jid types.JID) bool {
|
||||
return (jid.Server == types.DefaultUserServer && jid.User == wa.JID.User) ||
|
||||
(jid.Server == types.HiddenUserServer && jid.User == wa.GetLID().User)
|
||||
}
|
||||
|
||||
func (wa *WhatsAppClient) GetLID() types.JID {
|
||||
if wa.LID.IsEmpty() && !wa.JID.IsEmpty() {
|
||||
wa.LID = wa.GetStore().GetLID()
|
||||
}
|
||||
return wa.LID
|
||||
}
|
||||
|
||||
func (wa *WhatsAppClient) Connect(ctx context.Context) {
|
||||
|
|
|
|||
|
|
@ -28,27 +28,24 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"github.com/rs/zerolog"
|
||||
"go.mau.fi/util/dbutil"
|
||||
"go.mau.fi/util/exsync"
|
||||
"go.mau.fi/util/random"
|
||||
"go.mau.fi/whatsmeow"
|
||||
"go.mau.fi/whatsmeow/proto/waCompanionReg"
|
||||
"go.mau.fi/whatsmeow/store"
|
||||
"go.mau.fi/whatsmeow/store/sqlstore"
|
||||
whatsmeowUpgrades "go.mau.fi/whatsmeow/store/sqlstore/upgrades"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
waLog "go.mau.fi/whatsmeow/util/log"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
"maunium.net/go/mautrix/bridgev2/commands"
|
||||
"maunium.net/go/mautrix/bridgev2/database"
|
||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
||||
"go.mau.fi/mautrix-whatsapp/pkg/connector/wadb"
|
||||
"go.mau.fi/mautrix-whatsapp/pkg/msgconv"
|
||||
"go.mau.fi/mautrix-whatsapp/pkg/waid"
|
||||
)
|
||||
|
||||
type WhatsAppConnector struct {
|
||||
|
|
@ -64,6 +61,8 @@ type WhatsAppConnector struct {
|
|||
mediaEditCache MediaEditCache
|
||||
mediaEditCacheLock sync.RWMutex
|
||||
stopMediaEditCacheLoop atomic.Pointer[context.CancelFunc]
|
||||
|
||||
unmigratedDMs *exsync.Set[networkid.PortalKey]
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
@ -114,6 +113,7 @@ func (wa *WhatsAppConnector) Init(bridge *bridgev2.Bridge) {
|
|||
cmdAccept, cmdSync, cmdInviteLink, cmdResolveLink, cmdJoin,
|
||||
)
|
||||
wa.mediaEditCache = make(MediaEditCache)
|
||||
wa.unmigratedDMs = exsync.NewSet[networkid.PortalKey]()
|
||||
|
||||
whatsmeowDBLog := bridge.Log.With().Str("db_section", "whatsmeow").Logger()
|
||||
wa.DeviceStore = sqlstore.NewWithWrappedDB(
|
||||
|
|
@ -157,78 +157,7 @@ func (wa *WhatsAppConnector) Start(ctx context.Context) error {
|
|||
return bridgev2.DBUpgradeError{Err: err, Section: "whatsapp"}
|
||||
}
|
||||
|
||||
if !wa.Bridge.Background && wa.Bridge.DB.KV.Get(ctx, "whatsapp_lid_dms_deleted") == "false" {
|
||||
wa.deleteLIDDMsMigration(ctx)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wa *WhatsAppConnector) deleteLIDDMsMigration(ctx context.Context) {
|
||||
log := zerolog.Ctx(ctx).With().Str("action", "delete lid dms").Logger()
|
||||
portals, err := wa.Bridge.GetAllPortalsWithMXID(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to get portals for LID DM deletion")
|
||||
return
|
||||
}
|
||||
defer wa.Bridge.DB.KV.Set(ctx, "whatsapp_lid_dms_deleted", "true")
|
||||
if len(portals) == 0 {
|
||||
log.Debug().Msg("No portals found")
|
||||
return
|
||||
}
|
||||
portalsByKey := make(map[networkid.PortalKey]*bridgev2.Portal, len(portals))
|
||||
for _, p := range portals {
|
||||
if p.Receiver == "" || p.RoomType != database.RoomTypeDM {
|
||||
continue
|
||||
}
|
||||
portalsByKey[p.PortalKey] = p
|
||||
}
|
||||
_, err = wa.DB.Exec(ctx, "DELETE FROM whatsapp_history_sync_conversation WHERE chat_jid LIKE '%@lid'")
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to remove LID conversations from history sync")
|
||||
}
|
||||
for key, portal := range portalsByKey {
|
||||
parsedID, err := waid.ParsePortalID(key.ID)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("portal_id", string(key.ID)).Msg("Failed to parse portal ID")
|
||||
continue
|
||||
} else if parsedID.Server != types.HiddenUserServer {
|
||||
continue
|
||||
}
|
||||
var pnStr string
|
||||
err = wa.DB.QueryRow(ctx, "SELECT pn FROM whatsmeow_lid_map WHERE lid=$1", parsedID.User).Scan(&pnStr)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("portal_id", string(key.ID)).Msg("Failed to get PN for LID portal")
|
||||
continue
|
||||
}
|
||||
key.ID = waid.MakePortalID(types.JID{User: pnStr, Server: types.DefaultUserServer})
|
||||
_, pnPortalExists := portalsByKey[key]
|
||||
if !pnPortalExists {
|
||||
log.Warn().Str("portal_id", string(key.ID)).Msg("PN portal does not exist, not deleting LID DM")
|
||||
continue
|
||||
}
|
||||
err = portal.Delete(ctx)
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Object("portal_key", portal.PortalKey).
|
||||
Stringer("portal_mxid", portal.MXID).
|
||||
Msg("Failed to delete LID DM portal from database")
|
||||
continue
|
||||
}
|
||||
err = wa.Bridge.Bot.DeleteRoom(ctx, portal.MXID, false)
|
||||
if err != nil {
|
||||
log.Err(err).
|
||||
Object("portal_key", portal.PortalKey).
|
||||
Stringer("portal_mxid", portal.MXID).
|
||||
Msg("Failed to delete LID DM portal from Matrix")
|
||||
continue
|
||||
}
|
||||
log.Debug().
|
||||
Object("portal_key", portal.PortalKey).
|
||||
Stringer("portal_mxid", portal.MXID).
|
||||
Msg("Deleted LID DM portal")
|
||||
}
|
||||
log.Info().Msg("Finished deleting LID DM portals")
|
||||
return wa.migrateToLIDDMs(ctx)
|
||||
}
|
||||
|
||||
func (wa *WhatsAppConnector) Stop() {
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ func (wa *WhatsAppConnector) SetUseDirectMedia() {
|
|||
}
|
||||
|
||||
var ErrReloadNeeded = mautrix.RespError{
|
||||
ErrCode: "FI.MAU.WHATSAPP_RELOAD_NEEDED",
|
||||
ErrCode: "COM.BEEPER.MEDIA_RELOAD_NEEDED",
|
||||
Err: "Media is no longer available on WhatsApp servers and must be re-requested from your phone",
|
||||
StatusCode: http.StatusNotFound,
|
||||
}
|
||||
|
|
@ -212,7 +212,7 @@ func (wa *WhatsAppConnector) makeDirectMediaResponse(
|
|||
log := zerolog.Ctx(ctx)
|
||||
err := waClient.Client.DownloadToFile(ctx, dm, f)
|
||||
if keys != nil && (errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith403) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith404) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith410) || errors.Is(err, whatsmeow.ErrNoURLPresent)) {
|
||||
val := params["fi.mau.whatsapp.reload_media"]
|
||||
val := params["com.beeper.interactive_download_request"]
|
||||
if val == "false" || (!wa.Config.DirectMediaAutoRequest && val != "true") {
|
||||
return nil, ErrReloadNeeded
|
||||
}
|
||||
|
|
@ -255,11 +255,10 @@ func (wa *WhatsAppConnector) makeDirectMediaResponse(
|
|||
|
||||
type directMediaRetry struct {
|
||||
sync.Mutex
|
||||
resultURL string
|
||||
wait *exsync.Event
|
||||
requested bool
|
||||
resultType waMmsRetry.MediaRetryNotification_ResultType
|
||||
decryptFail bool
|
||||
resultURL string
|
||||
wait *exsync.Event
|
||||
requested bool
|
||||
resultType waMmsRetry.MediaRetryNotification_ResultType
|
||||
}
|
||||
|
||||
func (wa *WhatsAppClient) getDirectMediaRetryState(msgID networkid.MessageID, create bool) *directMediaRetry {
|
||||
|
|
@ -286,9 +285,6 @@ func (wa *WhatsAppClient) requestAndWaitDirectMedia(ctx context.Context, rawMsgI
|
|||
keys.DirectPath = state.resultURL
|
||||
return nil
|
||||
}
|
||||
if state.decryptFail {
|
||||
return mautrix.MNotFound.WithMessage("Unable to retrieve media: failed to decrypt the media retry notification from your phone.")
|
||||
}
|
||||
switch state.resultType {
|
||||
case waMmsRetry.MediaRetryNotification_NOT_FOUND:
|
||||
return mautrix.MNotFound.WithMessage("This media was not found on your phone.")
|
||||
|
|
@ -342,9 +338,6 @@ func (wa *WhatsAppClient) receiveDirectMediaRetry(ctx context.Context, msg *data
|
|||
retryData, err := whatsmeow.DecryptMediaRetryNotification(retry, keys.Key)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to decrypt media retry notification")
|
||||
if state != nil {
|
||||
state.decryptFail = true
|
||||
}
|
||||
return
|
||||
}
|
||||
if state != nil {
|
||||
|
|
|
|||
|
|
@ -39,18 +39,6 @@ import (
|
|||
"go.mau.fi/mautrix-whatsapp/pkg/waid"
|
||||
)
|
||||
|
||||
func (wa *WhatsAppClient) getPortalKeyByMessageSource(ms types.MessageSource) networkid.PortalKey {
|
||||
jid := ms.Chat
|
||||
if ms.IsIncomingBroadcast() {
|
||||
if ms.IsFromMe {
|
||||
jid = ms.BroadcastListOwner.ToNonAD()
|
||||
} else {
|
||||
jid = ms.Sender.ToNonAD()
|
||||
}
|
||||
}
|
||||
return wa.makeWAPortalKey(jid)
|
||||
}
|
||||
|
||||
type MessageInfoWrapper struct {
|
||||
Info types.MessageInfo
|
||||
wa *WhatsAppClient
|
||||
|
|
@ -61,7 +49,26 @@ func (evt *MessageInfoWrapper) ShouldCreatePortal() bool {
|
|||
}
|
||||
|
||||
func (evt *MessageInfoWrapper) GetPortalKey() networkid.PortalKey {
|
||||
return evt.wa.getPortalKeyByMessageSource(evt.Info.MessageSource)
|
||||
ms := evt.Info.MessageSource
|
||||
jid := ms.Chat
|
||||
if ms.IsIncomingBroadcast() {
|
||||
if ms.IsFromMe {
|
||||
// TODO can this still be a phone number?
|
||||
jid = ms.BroadcastListOwner.ToNonAD()
|
||||
} else {
|
||||
jid = ms.Sender.ToNonAD()
|
||||
if jid.Server == types.DefaultUserServer && !ms.SenderAlt.IsEmpty() {
|
||||
jid = ms.SenderAlt.ToNonAD()
|
||||
}
|
||||
}
|
||||
} else if jid.Server == types.DefaultUserServer {
|
||||
if !ms.IsFromMe && ms.Chat.ToNonAD() == ms.Sender.ToNonAD() && !ms.SenderAlt.IsEmpty() {
|
||||
jid = ms.SenderAlt.ToNonAD()
|
||||
} else if !ms.RecipientAlt.IsEmpty() {
|
||||
jid = ms.RecipientAlt.ToNonAD()
|
||||
}
|
||||
}
|
||||
return evt.wa.makeWAPortalKey(jid)
|
||||
}
|
||||
|
||||
func (evt *MessageInfoWrapper) AddLogContext(c zerolog.Context) zerolog.Context {
|
||||
|
|
@ -72,12 +79,19 @@ func (evt *MessageInfoWrapper) GetTimestamp() time.Time {
|
|||
return evt.Info.Timestamp
|
||||
}
|
||||
|
||||
func pickLID(main, alt types.JID) types.JID {
|
||||
if main.Server == types.DefaultUserServer && alt.Server == types.HiddenUserServer {
|
||||
return alt
|
||||
}
|
||||
return main
|
||||
}
|
||||
|
||||
func (evt *MessageInfoWrapper) GetSender() bridgev2.EventSender {
|
||||
return evt.wa.makeEventSender(evt.wa.Main.Bridge.BackgroundCtx, evt.Info.Sender)
|
||||
return evt.wa.makeEventSender(evt.wa.Main.Bridge.BackgroundCtx, pickLID(evt.Info.Sender, evt.Info.SenderAlt))
|
||||
}
|
||||
|
||||
func (evt *MessageInfoWrapper) GetID() networkid.MessageID {
|
||||
return waid.MakeMessageID(evt.Info.Chat, evt.Info.Sender, evt.Info.ID)
|
||||
return waid.MakeMessageIDWithAltSender(evt.Info.Chat, evt.Info.Sender, evt.Info.SenderAlt, evt.Info.ID)
|
||||
}
|
||||
|
||||
func (evt *MessageInfoWrapper) GetTransactionID() networkid.TransactionID {
|
||||
|
|
@ -134,14 +148,6 @@ func (evt *WAMessageEvent) PreHandle(ctx context.Context, portal *bridgev2.Porta
|
|||
return
|
||||
}
|
||||
meta := portal.Metadata.(*waid.PortalMetadata)
|
||||
if meta.AddressingMode == types.AddressingModeLID && evt.Info.Sender.Server == types.DefaultUserServer {
|
||||
evt.Info.Sender, evt.Info.SenderAlt = evt.Info.SenderAlt, evt.Info.Sender
|
||||
zerolog.Ctx(ctx).Debug().
|
||||
Stringer("lid", evt.Info.Sender).
|
||||
Stringer("pn", evt.Info.SenderAlt).
|
||||
Str("message_id", evt.Info.ID).
|
||||
Msg("Forced phone number sender to LID in group message")
|
||||
}
|
||||
if meta.AddressingMode == types.AddressingModeLID || meta.LIDMigrationAttempted {
|
||||
return
|
||||
}
|
||||
|
|
@ -160,13 +166,6 @@ func (evt *WAMessageEvent) PreHandle(ctx context.Context, portal *bridgev2.Porta
|
|||
log.Info().Msg("Resyncing group members as it appears to have switched to LID addressing mode")
|
||||
portal.UpdateInfo(ctx, evt.wa.wrapGroupInfo(ctx, info), evt.wa.UserLogin, nil, time.Time{})
|
||||
log.Debug().Msg("Finished resyncing after LID change")
|
||||
if evt.Info.Sender.Server == types.DefaultUserServer && evt.Info.SenderAlt.Server == types.HiddenUserServer {
|
||||
evt.Info.Sender, evt.Info.SenderAlt = evt.Info.SenderAlt, evt.Info.Sender
|
||||
log.Debug().
|
||||
Stringer("new_sender", evt.Info.Sender).
|
||||
Stringer("new_sender_alt", evt.Info.SenderAlt).
|
||||
Msg("Overriding sender to LID after resyncing group members")
|
||||
}
|
||||
}
|
||||
|
||||
func (evt *WAMessageEvent) PostHandle(ctx context.Context, portal *bridgev2.Portal) {
|
||||
|
|
@ -391,7 +390,9 @@ func (evt *WAUndecryptableMessage) GetStreamOrder() int64 {
|
|||
|
||||
type WAMediaRetry struct {
|
||||
*events.MediaRetry
|
||||
wa *WhatsAppClient
|
||||
wa *WhatsAppClient
|
||||
senderLID types.JID
|
||||
chatLID types.JID
|
||||
}
|
||||
|
||||
func (evt *WAMediaRetry) GetType() bridgev2.RemoteEventType {
|
||||
|
|
@ -399,7 +400,7 @@ func (evt *WAMediaRetry) GetType() bridgev2.RemoteEventType {
|
|||
}
|
||||
|
||||
func (evt *WAMediaRetry) GetPortalKey() networkid.PortalKey {
|
||||
return evt.wa.makeWAPortalKey(evt.ChatID)
|
||||
return evt.wa.makeWAPortalKey(pickLID(evt.ChatID, evt.chatLID))
|
||||
}
|
||||
|
||||
func (evt *WAMediaRetry) AddLogContext(c zerolog.Context) zerolog.Context {
|
||||
|
|
@ -413,16 +414,23 @@ func (evt *WAMediaRetry) AddLogContext(c zerolog.Context) zerolog.Context {
|
|||
|
||||
func (evt *WAMediaRetry) getRealSender() types.JID {
|
||||
sender := evt.SenderID
|
||||
if evt.FromMe {
|
||||
sender = evt.wa.JID.ToNonAD()
|
||||
} else if sender.IsEmpty() && (evt.ChatID.Server == types.DefaultUserServer || evt.ChatID.Server == types.BotServer) {
|
||||
sender = evt.ChatID.ToNonAD()
|
||||
if sender.IsEmpty() {
|
||||
if evt.FromMe {
|
||||
if evt.ChatID.Server == types.HiddenUserServer {
|
||||
sender = evt.wa.GetLID().ToNonAD()
|
||||
} else {
|
||||
sender = evt.wa.JID.ToNonAD()
|
||||
}
|
||||
} else if evt.ChatID.Server == types.DefaultUserServer || evt.ChatID.Server == types.HiddenUserServer || evt.ChatID.Server == types.BotServer {
|
||||
sender = evt.ChatID.ToNonAD()
|
||||
}
|
||||
}
|
||||
return sender
|
||||
}
|
||||
|
||||
func (evt *WAMediaRetry) GetSender() bridgev2.EventSender {
|
||||
return evt.wa.makeEventSender(evt.wa.Main.Bridge.BackgroundCtx, evt.getRealSender())
|
||||
realSender := pickLID(evt.getRealSender(), evt.senderLID)
|
||||
return evt.wa.makeEventSender(evt.wa.Main.Bridge.BackgroundCtx, realSender)
|
||||
}
|
||||
|
||||
func (evt *WAMediaRetry) GetTargetMessage() networkid.MessageID {
|
||||
|
|
|
|||
|
|
@ -103,11 +103,14 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if chatJID.Server == types.DefaultUserServer {
|
||||
zerolog.Ctx(ctx).Warn().Stringer("portal_jid", chatJID).Msg("Matrix message received in phone number portal")
|
||||
}
|
||||
if chatJID == types.StatusBroadcastJID && wa.Main.Config.DisableStatusBroadcastSend {
|
||||
return nil, ErrBroadcastSendDisabled
|
||||
}
|
||||
wrappedMsgID := waid.MakeMessageID(chatJID, wa.JID, req.ID)
|
||||
wrappedMsgID2 := waid.MakeMessageID(chatJID, wa.GetStore().GetLID(), req.ID)
|
||||
wrappedMsgID2 := waid.MakeMessageID(chatJID, wa.GetLID(), req.ID)
|
||||
msg.AddPendingToIgnore(networkid.TransactionID(wrappedMsgID))
|
||||
msg.AddPendingToIgnore(networkid.TransactionID(wrappedMsgID2))
|
||||
zerolog.Ctx(ctx).Trace().Any("payload", waMsg).Msg("Outgoing message payload")
|
||||
|
|
@ -116,7 +119,7 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg
|
|||
return nil, err
|
||||
}
|
||||
var pickedMessageID networkid.MessageID
|
||||
if resp.Sender == wa.GetStore().GetLID() && chatJID.Server != types.DefaultUserServer {
|
||||
if resp.Sender == wa.GetLID() {
|
||||
pickedMessageID = wrappedMsgID2
|
||||
msg.RemovePending(networkid.TransactionID(wrappedMsgID))
|
||||
} else {
|
||||
|
|
@ -137,18 +140,17 @@ func (wa *WhatsAppClient) handleConvertedMatrixMessage(ctx context.Context, msg
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (wa *WhatsAppClient) PreHandleMatrixReaction(_ context.Context, msg *bridgev2.MatrixReaction) (bridgev2.MatrixReactionPreResponse, error) {
|
||||
func (wa *WhatsAppClient) PreHandleMatrixReaction(ctx context.Context, msg *bridgev2.MatrixReaction) (bridgev2.MatrixReactionPreResponse, error) {
|
||||
portalJID, err := waid.ParsePortalID(msg.Portal.ID)
|
||||
if err != nil {
|
||||
return bridgev2.MatrixReactionPreResponse{}, fmt.Errorf("failed to parse portal ID: %w", err)
|
||||
} else if portalJID == types.StatusBroadcastJID {
|
||||
return bridgev2.MatrixReactionPreResponse{}, ErrBroadcastReactionUnsupported
|
||||
}
|
||||
sender := wa.JID
|
||||
if portalJID.Server == types.HiddenUserServer ||
|
||||
msg.Portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup ||
|
||||
msg.Portal.Metadata.(*waid.PortalMetadata).AddressingMode == types.AddressingModeLID {
|
||||
sender = wa.GetStore().GetLID()
|
||||
sender := wa.GetLID()
|
||||
if portalJID.Server == types.DefaultUserServer {
|
||||
zerolog.Ctx(ctx).Warn().Stringer("portal_jid", portalJID).Msg("Matrix reaction received in phone number portal")
|
||||
sender = wa.JID
|
||||
}
|
||||
return bridgev2.MatrixReactionPreResponse{
|
||||
SenderID: waid.MakeUserID(sender),
|
||||
|
|
@ -177,7 +179,7 @@ func (wa *WhatsAppClient) HandleMatrixReaction(ctx context.Context, msg *bridgev
|
|||
defer wa.mcTrack(msg, time.Now(), &retErr)
|
||||
var req whatsmeow.SendRequestExtra
|
||||
if msg.Portal.Metadata.(*waid.PortalMetadata).CommunityAnnouncementGroup {
|
||||
reactionMsg.EncReactionMessage, err = wa.Client.EncryptReaction(ctx, msgconv.MessageIDToInfo(wa.Client, messageID), reactionMsg.ReactionMessage)
|
||||
reactionMsg.EncReactionMessage, err = wa.Client.EncryptReaction(ctx, msgconv.MessageIDToInfo(ctx, wa.Client, messageID), reactionMsg.ReactionMessage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encrypt reaction: %w", err)
|
||||
}
|
||||
|
|
@ -320,7 +322,7 @@ func (wa *WhatsAppClient) HandleMatrixReadReceipt(ctx context.Context, receipt *
|
|||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if parsed.Sender.User == wa.GetStore().GetLID().User || parsed.Sender.User == wa.JID.User {
|
||||
if wa.IsOwnJID(parsed.Sender) {
|
||||
continue
|
||||
}
|
||||
var key types.JID
|
||||
|
|
@ -636,7 +638,7 @@ func (wa *WhatsAppClient) getLastMessageInfo(ctx context.Context, chatJID types.
|
|||
lastTS = msgs[0].Timestamp
|
||||
parsed, _ := waid.ParseMessageID(msgs[0].ID)
|
||||
if parsed != nil {
|
||||
fromMe := parsed.Sender.ToNonAD() == wa.JID.ToNonAD() || parsed.Sender.ToNonAD() == wa.GetStore().GetLID().ToNonAD()
|
||||
fromMe := wa.IsOwnJID(parsed.Sender)
|
||||
var participant *string
|
||||
if chatJID.Server == types.GroupServer {
|
||||
participant = ptr.Ptr(parsed.Sender.String())
|
||||
|
|
|
|||
|
|
@ -113,8 +113,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) {
|
|||
case *events.HistorySync:
|
||||
wa.UserLogin.Log.Warn().Msg("Unexpected history sync event received")
|
||||
case *events.MediaRetry:
|
||||
wa.phoneSeen(evt.Timestamp)
|
||||
success = wa.UserLogin.QueueRemoteEvent(&WAMediaRetry{MediaRetry: evt, wa: wa}).Success
|
||||
success = wa.handleWAMediaRetry(ctx, evt)
|
||||
|
||||
case *events.GroupInfo:
|
||||
success = wa.handleWAGroupInfoChange(ctx, evt)
|
||||
|
|
@ -158,7 +157,7 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) {
|
|||
if err != nil {
|
||||
log.Err(err).Msg("Failed to update push name in store")
|
||||
}
|
||||
_, _, err = wa.GetStore().Contacts.PutPushName(ctx, wa.GetStore().GetLID().ToNonAD(), evt.Action.GetName())
|
||||
_, _, err = wa.GetStore().Contacts.PutPushName(ctx, wa.GetLID().ToNonAD(), evt.Action.GetName())
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to update push name in store")
|
||||
}
|
||||
|
|
@ -259,65 +258,26 @@ func (wa *WhatsAppClient) handleWAEvent(rawEvt any) (success bool) {
|
|||
return
|
||||
}
|
||||
|
||||
func (wa *WhatsAppClient) rerouteWAMessage(ctx context.Context, evtType string, info *types.MessageSource, msgID any) {
|
||||
if (info.Chat.Server == types.HiddenUserServer || info.Chat.Server == types.BroadcastServer) &&
|
||||
info.Sender.Server == types.HiddenUserServer && info.SenderAlt.IsEmpty() {
|
||||
info.SenderAlt, _ = wa.GetStore().LIDs.GetPNForLID(ctx, info.Sender)
|
||||
}
|
||||
if info.Chat.Server == types.HiddenUserServer && info.IsFromMe && info.RecipientAlt.IsEmpty() {
|
||||
info.RecipientAlt, _ = wa.GetStore().LIDs.GetPNForLID(ctx, info.Chat)
|
||||
}
|
||||
if info.Chat.Server == types.HiddenUserServer && info.Sender.ToNonAD() == info.Chat && info.SenderAlt.Server == types.DefaultUserServer {
|
||||
wa.UserLogin.Log.Debug().
|
||||
Stringer("lid", info.Sender).
|
||||
Stringer("pn", info.SenderAlt).
|
||||
Any("message_id", msgID).
|
||||
Str("evt_type", evtType).
|
||||
Msg("Forced LID DM sender to phone number in incoming message")
|
||||
info.Sender, info.SenderAlt = info.SenderAlt, info.Sender
|
||||
info.Chat = info.Sender.ToNonAD()
|
||||
} else if info.Chat.Server == types.HiddenUserServer && info.IsFromMe && info.RecipientAlt.Server == types.DefaultUserServer {
|
||||
wa.UserLogin.Log.Debug().
|
||||
Stringer("lid", info.Chat).
|
||||
Stringer("pn", info.RecipientAlt).
|
||||
Any("message_id", msgID).
|
||||
Str("evt_type", evtType).
|
||||
Msg("Forced LID DM sender to phone number in own message sent from another device")
|
||||
info.Chat = info.RecipientAlt.ToNonAD()
|
||||
if info.Sender.Server == types.HiddenUserServer {
|
||||
info.Sender, info.SenderAlt = info.SenderAlt, info.Sender
|
||||
if info.Sender.IsEmpty() {
|
||||
info.Sender = wa.GetStore().GetJID()
|
||||
info.Sender.Device = info.SenderAlt.Device
|
||||
}
|
||||
}
|
||||
} else if info.Chat.Server == types.BroadcastServer && info.Sender.Server == types.HiddenUserServer && info.SenderAlt.Server == types.DefaultUserServer {
|
||||
wa.UserLogin.Log.Debug().
|
||||
Stringer("lid", info.Sender).
|
||||
Stringer("pn", info.SenderAlt).
|
||||
Stringer("chat", info.Chat).
|
||||
Any("message_id", msgID).
|
||||
Str("evt_type", evtType).
|
||||
Msg("Forced LID broadcast list sender to phone number in incoming message")
|
||||
info.Sender, info.SenderAlt = info.SenderAlt, info.Sender
|
||||
} else if info.Sender.Server == types.BotServer && info.Chat.Server == types.HiddenUserServer {
|
||||
chatPN, err := wa.GetStore().LIDs.GetPNForLID(ctx, info.Chat)
|
||||
func (wa *WhatsAppClient) ensureAltJIDs(ctx context.Context, info *types.MessageSource, checkPhones bool) bool {
|
||||
var err error
|
||||
if info.Sender.Server == types.DefaultUserServer && info.SenderAlt.IsEmpty() {
|
||||
info.SenderAlt, err = wa.GetStore().LIDs.GetLIDForPN(ctx, info.Sender)
|
||||
if err != nil {
|
||||
wa.UserLogin.Log.Err(err).
|
||||
Any("message_id", msgID).
|
||||
Stringer("lid", info.Chat).
|
||||
Str("evt_type", evtType).
|
||||
Msg("Failed to get phone number of DM for incoming bot message")
|
||||
} else if !chatPN.IsEmpty() {
|
||||
wa.UserLogin.Log.Debug().
|
||||
Stringer("lid", info.Chat).
|
||||
Stringer("pn", chatPN).
|
||||
Any("message_id", msgID).
|
||||
Str("evt_type", evtType).
|
||||
Msg("Forced LID chat to phone number in bot message")
|
||||
info.Chat = chatPN
|
||||
zerolog.Ctx(ctx).Err(err).Stringer("sender", info.Sender).Msg("Failed to get LID for sender")
|
||||
return false
|
||||
}
|
||||
}
|
||||
if info.Chat.Server == types.DefaultUserServer && info.IsFromMe && info.RecipientAlt.IsEmpty() {
|
||||
info.RecipientAlt, err = wa.GetStore().LIDs.GetLIDForPN(ctx, info.Chat)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Stringer("chat", info.Chat).Msg("Failed to get LID for chat")
|
||||
return false
|
||||
}
|
||||
}
|
||||
if checkPhones {
|
||||
return wa.checkAllPhonesInMessage(ctx, info)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Message) (success bool) {
|
||||
|
|
@ -325,6 +285,12 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa
|
|||
if evt.Info.Chat == types.StatusBroadcastJID && !wa.Main.Config.EnableStatusBroadcast {
|
||||
return
|
||||
}
|
||||
if evt.Info.Chat.Server == types.NewsletterServer && wa.disableNewsletter {
|
||||
return
|
||||
}
|
||||
if !wa.ensureAltJIDs(ctx, &evt.Info.MessageSource, true) {
|
||||
return false
|
||||
}
|
||||
parsedMessageType := getMessageType(evt.Message)
|
||||
if encReact := evt.Message.GetEncReactionMessage(); encReact != nil {
|
||||
decrypted, err := wa.Client.DecryptReaction(ctx, evt)
|
||||
|
|
@ -358,7 +324,6 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa
|
|||
evt.UnwrapRaw()
|
||||
parsedMessageType = getMessageType(evt.Message)
|
||||
}
|
||||
wa.rerouteWAMessage(ctx, "message", &evt.Info.MessageSource, evt.Info.ID)
|
||||
wa.UserLogin.Log.Trace().
|
||||
Any("info", evt.Info).
|
||||
Any("payload", evt.Message).
|
||||
|
|
@ -422,7 +387,9 @@ func (wa *WhatsAppClient) handleWAMessage(ctx context.Context, evt *events.Messa
|
|||
}
|
||||
|
||||
func (wa *WhatsAppClient) handleWAUndecryptableMessage(ctx context.Context, evt *events.UndecryptableMessage) bool {
|
||||
wa.rerouteWAMessage(ctx, "undecryptable message", &evt.Info.MessageSource, evt.Info.ID)
|
||||
if !wa.ensureAltJIDs(ctx, &evt.Info.MessageSource, true) {
|
||||
return false
|
||||
}
|
||||
wa.UserLogin.Log.Debug().
|
||||
Any("info", evt.Info).
|
||||
Bool("unavailable", evt.IsUnavailable).
|
||||
|
|
@ -445,12 +412,44 @@ func (wa *WhatsAppClient) handleWAUndecryptableMessage(ctx context.Context, evt
|
|||
return res.Success
|
||||
}
|
||||
|
||||
func (wa *WhatsAppClient) handleWAMediaRetry(ctx context.Context, evt *events.MediaRetry) bool {
|
||||
wa.phoneSeen(evt.Timestamp)
|
||||
var senderLID, chatLID types.JID
|
||||
var err error
|
||||
if evt.SenderID.Server == types.DefaultUserServer {
|
||||
senderLID, err = wa.GetStore().LIDs.GetLIDForPN(ctx, evt.SenderID)
|
||||
if err != nil {
|
||||
wa.UserLogin.Log.Err(err).
|
||||
Stringer("sender_id", evt.SenderID).
|
||||
Msg("Failed to get LID for media retry sender")
|
||||
return false
|
||||
}
|
||||
}
|
||||
if evt.ChatID.Server == types.DefaultUserServer {
|
||||
chatLID, err = wa.GetStore().LIDs.GetLIDForPN(ctx, evt.ChatID)
|
||||
if err != nil {
|
||||
wa.UserLogin.Log.Err(err).
|
||||
Stringer("chat_id", evt.ChatID).
|
||||
Msg("Failed to get LID for media retry chat")
|
||||
return false
|
||||
}
|
||||
}
|
||||
res := wa.UserLogin.QueueRemoteEvent(&WAMediaRetry{
|
||||
MediaRetry: evt,
|
||||
wa: wa,
|
||||
senderLID: senderLID,
|
||||
chatLID: chatLID,
|
||||
})
|
||||
return res.Success
|
||||
}
|
||||
|
||||
func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Receipt) (success bool) {
|
||||
origChat := evt.Chat
|
||||
wa.rerouteWAMessage(ctx, "receipt", &evt.MessageSource, evt.MessageIDs)
|
||||
if evt.IsFromMe && evt.Sender.Device == 0 {
|
||||
wa.phoneSeen(evt.Timestamp)
|
||||
}
|
||||
if !wa.ensureAltJIDs(ctx, &evt.MessageSource, true) {
|
||||
return false
|
||||
}
|
||||
var evtType bridgev2.RemoteEventType
|
||||
switch evt.Type {
|
||||
case types.ReceiptTypeRead, types.ReceiptTypeReadSelf:
|
||||
|
|
@ -462,28 +461,34 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei
|
|||
default:
|
||||
return true
|
||||
}
|
||||
targets := make([]networkid.MessageID, len(evt.MessageIDs))
|
||||
messageSender := wa.JID
|
||||
targets := make([]networkid.MessageID, 0, len(evt.MessageIDs))
|
||||
messageSender := wa.GetLID()
|
||||
if !evt.MessageSender.IsEmpty() {
|
||||
messageSender = evt.MessageSender
|
||||
// Second part of rerouting receipts in LID chats
|
||||
if messageSender == origChat && evt.Chat != origChat {
|
||||
messageSender = evt.Chat
|
||||
}
|
||||
} else if evt.Chat.Server == types.GroupServer && evt.Sender.Server == types.HiddenUserServer {
|
||||
lid := wa.GetStore().GetLID()
|
||||
if !lid.IsEmpty() {
|
||||
messageSender = lid
|
||||
}
|
||||
var chatAlt types.JID
|
||||
if evt.Chat.Server == types.DefaultUserServer {
|
||||
chatLID, _ := wa.GetStore().LIDs.GetLIDForPN(ctx, evt.Chat)
|
||||
if !chatLID.IsEmpty() {
|
||||
chatAlt = evt.Chat
|
||||
evt.Chat = chatLID
|
||||
}
|
||||
}
|
||||
for i, id := range evt.MessageIDs {
|
||||
targets[i] = waid.MakeMessageID(evt.Chat, messageSender, id)
|
||||
for _, id := range evt.MessageIDs {
|
||||
targets = append(targets, waid.MakeMessageID(evt.Chat, messageSender, id))
|
||||
if !chatAlt.IsEmpty() {
|
||||
targets = append(targets, waid.MakeMessageID(chatAlt, messageSender, id))
|
||||
}
|
||||
}
|
||||
senderLID := evt.Sender
|
||||
if senderLID.Server == types.DefaultUserServer && !evt.SenderAlt.IsEmpty() {
|
||||
senderLID = evt.SenderAlt
|
||||
}
|
||||
res := wa.UserLogin.QueueRemoteEvent(&simplevent.Receipt{
|
||||
EventMeta: simplevent.EventMeta{
|
||||
Type: evtType,
|
||||
PortalKey: wa.makeWAPortalKey(evt.Chat),
|
||||
Sender: wa.makeEventSender(ctx, evt.Sender),
|
||||
Sender: wa.makeEventSender(ctx, senderLID),
|
||||
Timestamp: evt.Timestamp,
|
||||
},
|
||||
Targets: targets,
|
||||
|
|
@ -492,11 +497,11 @@ func (wa *WhatsAppClient) handleWAReceipt(ctx context.Context, evt *events.Recei
|
|||
}
|
||||
|
||||
func (wa *WhatsAppClient) handleWAChatPresence(ctx context.Context, evt *events.ChatPresence) {
|
||||
if evt.Chat.Server == types.HiddenUserServer && evt.Sender.ToNonAD() == evt.Chat {
|
||||
if evt.Chat.Server == types.DefaultUserServer && evt.Sender.ToNonAD() == evt.Chat {
|
||||
if evt.SenderAlt.IsEmpty() {
|
||||
evt.SenderAlt, _ = wa.GetStore().LIDs.GetPNForLID(ctx, evt.Sender)
|
||||
evt.SenderAlt, _ = wa.GetStore().LIDs.GetLIDForPN(ctx, evt.Sender)
|
||||
}
|
||||
if evt.SenderAlt.Server == types.DefaultUserServer {
|
||||
if evt.SenderAlt.Server == types.HiddenUserServer {
|
||||
evt.Sender, evt.SenderAlt = evt.SenderAlt, evt.Sender
|
||||
evt.Chat = evt.Sender.ToNonAD()
|
||||
}
|
||||
|
|
@ -533,6 +538,7 @@ func (wa *WhatsAppClient) handleWALogout(reason events.ConnectFailureReason, onC
|
|||
wa.Disconnect()
|
||||
wa.Client = nil
|
||||
wa.JID = types.EmptyJID
|
||||
wa.LID = types.EmptyJID
|
||||
wa.UserLogin.Metadata.(*waid.UserLoginMetadata).WADeviceID = 0
|
||||
wa.UserLogin.BridgeState.Send(status.BridgeState{
|
||||
StateEvent: status.StateBadCredentials,
|
||||
|
|
@ -546,12 +552,15 @@ func (wa *WhatsAppClient) handleWACallStart(ctx context.Context, group, sender,
|
|||
if !wa.Main.Config.CallStartNotices || time.Since(ts) > callEventMaxAge {
|
||||
return true
|
||||
}
|
||||
if sender.Server == types.HiddenUserServer && senderAlt.Server == types.DefaultUserServer {
|
||||
if sender.Server == types.DefaultUserServer && senderAlt.IsEmpty() {
|
||||
senderAlt, _ = wa.GetStore().LIDs.GetLIDForPN(ctx, sender)
|
||||
}
|
||||
if sender.Server == types.DefaultUserServer && senderAlt.Server == types.HiddenUserServer {
|
||||
wa.UserLogin.Log.Debug().
|
||||
Stringer("lid", sender).
|
||||
Stringer("pn", senderAlt).
|
||||
Stringer("lid", senderAlt).
|
||||
Stringer("pn", sender).
|
||||
Str("call_id", id).
|
||||
Msg("Forced LID caller to phone number in incoming call")
|
||||
Msg("Forced phone number caller to LID in incoming call")
|
||||
sender, senderAlt = senderAlt, sender
|
||||
}
|
||||
chat := group
|
||||
|
|
@ -598,6 +607,12 @@ func (wa *WhatsAppClient) handleWAIdentityChange(ctx context.Context, evt *event
|
|||
if !wa.Main.Config.IdentityChangeNotices {
|
||||
return
|
||||
}
|
||||
if evt.JID.Server == types.DefaultUserServer {
|
||||
lid, _ := wa.GetStore().LIDs.GetLIDForPN(ctx, evt.JID)
|
||||
if !lid.IsEmpty() {
|
||||
evt.JID = lid
|
||||
}
|
||||
}
|
||||
wa.UserLogin.QueueRemoteEvent(&simplevent.Message[*events.IdentityChange]{
|
||||
EventMeta: simplevent.EventMeta{
|
||||
Type: bridgev2.RemoteEventMessage,
|
||||
|
|
@ -648,13 +663,14 @@ func (wa *WhatsAppClient) handleWADeleteChat(ctx context.Context, evt *events.De
|
|||
|
||||
func (wa *WhatsAppClient) handleWADeleteForMe(ctx context.Context, evt *events.DeleteForMe) bool {
|
||||
chatJID := wa.maybeConvertJIDToLID(ctx, evt.ChatJID)
|
||||
senderJID := wa.maybeConvertJIDToLID(ctx, evt.SenderJID)
|
||||
return wa.UserLogin.QueueRemoteEvent(&simplevent.MessageRemove{
|
||||
EventMeta: simplevent.EventMeta{
|
||||
Type: bridgev2.RemoteEventMessageRemove,
|
||||
PortalKey: wa.makeWAPortalKey(chatJID),
|
||||
Timestamp: evt.Timestamp,
|
||||
},
|
||||
TargetMessage: waid.MakeMessageID(chatJID, evt.SenderJID, evt.MessageID),
|
||||
TargetMessage: waid.MakeMessageID(chatJID, senderJID, evt.MessageID),
|
||||
OnlyForMe: true,
|
||||
}).Success
|
||||
}
|
||||
|
|
@ -665,7 +681,7 @@ func (wa *WhatsAppClient) handleWAMarkChatAsRead(ctx context.Context, evt *event
|
|||
EventMeta: simplevent.EventMeta{
|
||||
Type: bridgev2.RemoteEventReadReceipt,
|
||||
PortalKey: wa.makeWAPortalKey(chatJID),
|
||||
Sender: wa.makeEventSender(ctx, wa.JID),
|
||||
Sender: wa.makeEventSender(ctx, wa.GetLID()),
|
||||
Timestamp: evt.Timestamp,
|
||||
},
|
||||
ReadUpTo: evt.Timestamp,
|
||||
|
|
@ -754,9 +770,6 @@ func (wa *WhatsAppClient) handleWAGroupInfoChange(ctx context.Context, evt *even
|
|||
}
|
||||
|
||||
func (wa *WhatsAppClient) handleWAJoinedGroup(ctx context.Context, evt *events.JoinedGroup) bool {
|
||||
if wa.createDedup.Pop(evt.CreateKey) {
|
||||
return true
|
||||
}
|
||||
return wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{
|
||||
EventMeta: simplevent.EventMeta{
|
||||
Type: bridgev2.RemoteEventChatResync,
|
||||
|
|
@ -769,6 +782,9 @@ func (wa *WhatsAppClient) handleWAJoinedGroup(ctx context.Context, evt *events.J
|
|||
}
|
||||
|
||||
func (wa *WhatsAppClient) handleWANewsletterJoin(ctx context.Context, evt *events.NewsletterJoin) bool {
|
||||
if wa.disableNewsletter {
|
||||
return true
|
||||
}
|
||||
return wa.UserLogin.QueueRemoteEvent(&simplevent.ChatResync{
|
||||
EventMeta: simplevent.EventMeta{
|
||||
Type: bridgev2.RemoteEventChatResync,
|
||||
|
|
@ -867,7 +883,12 @@ func (wa *WhatsAppClient) handleWAAppStateSyncComplete(ctx context.Context, evt
|
|||
} else {
|
||||
log.Info().
|
||||
Time("recovery_ts", ts).
|
||||
Bool("recovery_evt", evt.Recovery).
|
||||
Msg("Unmarked app state recovery as attempted after successful full sync")
|
||||
wa.UserLogin.TrackAnalytics("WhatsApp Appstate Recovery Success", map[string]any{
|
||||
"patch_name": evt.Name,
|
||||
"from_recovery": evt.Recovery,
|
||||
})
|
||||
}
|
||||
} else if ts, exists = wa.appStateFullSyncAttempted[evt.Name]; exists {
|
||||
delete(wa.appStateFullSyncAttempted, evt.Name)
|
||||
|
|
@ -924,6 +945,9 @@ func (wa *WhatsAppClient) handleWAAppStateSyncError(ctx context.Context, evt *ev
|
|||
if err != nil {
|
||||
log.Err(err).Msg("Failed to save login metadata after marking app state recovery as attempted")
|
||||
}
|
||||
wa.UserLogin.TrackAnalytics("WhatsApp Appstate Recovery Request", map[string]any{
|
||||
"patch_name": evt.Name,
|
||||
})
|
||||
go func() {
|
||||
resp, err := wa.Client.SendPeerMessage(ctx, whatsmeow.BuildAppStateRecoveryRequest(evt.Name))
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ func (wa *WhatsAppClient) makeEventSender(ctx context.Context, id types.JID) bri
|
|||
}
|
||||
}
|
||||
return bridgev2.EventSender{
|
||||
IsFromMe: id.User == wa.GetStore().GetJID().User || id.User == wa.GetStore().GetLID().User,
|
||||
IsFromMe: wa.IsOwnJID(id),
|
||||
Sender: waid.MakeUserID(id),
|
||||
SenderLogin: waid.MakeUserLoginID(senderLoginJID),
|
||||
}
|
||||
|
|
@ -60,24 +60,25 @@ func (wa *WhatsAppClient) messageIDToKey(id *waid.ParsedMessageID) *waCommon.Mes
|
|||
RemoteJID: ptr.Ptr(id.Chat.String()),
|
||||
ID: ptr.Ptr(id.ID),
|
||||
}
|
||||
if id.Sender.User == wa.GetStore().GetJID().User || id.Sender.User == wa.GetStore().GetLID().User {
|
||||
if wa.IsOwnJID(id.Sender) {
|
||||
key.FromMe = ptr.Ptr(true)
|
||||
}
|
||||
if id.Chat.Server != types.MessengerServer && id.Chat.Server != types.DefaultUserServer && id.Chat.Server != types.HiddenUserServer && id.Chat.Server != types.BotServer {
|
||||
if id.Chat.Server != types.MessengerServer && id.Chat.Server != types.DefaultUserServer &&
|
||||
id.Chat.Server != types.HiddenUserServer && id.Chat.Server != types.BotServer {
|
||||
key.Participant = ptr.Ptr(id.Sender.String())
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func (wa *WhatsAppClient) maybeConvertJIDToLID(ctx context.Context, chatJID types.JID) types.JID {
|
||||
if chatJID.Server == types.HiddenUserServer {
|
||||
if pn, err := wa.GetStore().LIDs.GetPNForLID(ctx, chatJID); err != nil {
|
||||
func (wa *WhatsAppClient) maybeConvertJIDToLID(ctx context.Context, jid types.JID) types.JID {
|
||||
if jid.Server == types.DefaultUserServer {
|
||||
if lidForPN, err := wa.GetStore().LIDs.GetLIDForPN(ctx, jid); err != nil {
|
||||
wa.UserLogin.Log.Err(err).
|
||||
Stringer("lid", chatJID).
|
||||
Msg("Failed to get phone number for LID chat")
|
||||
} else if !pn.IsEmpty() {
|
||||
return pn.ToNonAD()
|
||||
Stringer("pn", jid).
|
||||
Msg("Failed to get LID for phone number chat")
|
||||
} else if !lidForPN.IsEmpty() {
|
||||
return lidForPN
|
||||
}
|
||||
}
|
||||
return chatJID
|
||||
return jid
|
||||
}
|
||||
|
|
|
|||
212
pkg/connector/lidmigrate.go
Normal file
212
pkg/connector/lidmigrate.go
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
|
||||
// Copyright (C) 2026 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package connector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.mau.fi/util/dbutil"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
|
||||
"maunium.net/go/mautrix/bridgev2"
|
||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||
"maunium.net/go/mautrix/event"
|
||||
|
||||
"go.mau.fi/mautrix-whatsapp/pkg/waid"
|
||||
)
|
||||
|
||||
func (wa *WhatsAppClient) FindAltTargetMessage(ctx context.Context, targetMsg networkid.MessageID, evt bridgev2.RemoteEventWithTargetMessage) (alts []networkid.MessageID, err error) {
|
||||
parsed, err := waid.ParseMessageID(targetMsg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse target message ID: %w", err)
|
||||
}
|
||||
altSender, err := wa.GetStore().GetAltJID(ctx, parsed.Sender)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var altChat types.JID
|
||||
if parsed.Chat.Server == types.HiddenUserServer {
|
||||
altChat, err = wa.GetStore().LIDs.GetPNForLID(ctx, parsed.Chat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !altSender.IsEmpty() {
|
||||
altSenderID := *parsed
|
||||
altSenderID.Sender = altSender
|
||||
alts = append(alts, altSenderID.String())
|
||||
}
|
||||
if !altChat.IsEmpty() {
|
||||
altChatID := *parsed
|
||||
altChatID.Chat = altChat
|
||||
if altSender.Server == types.DefaultUserServer {
|
||||
altChatID.Sender = altSender
|
||||
}
|
||||
alts = append(alts, altChatID.String())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (wa *WhatsAppClient) checkAllPhonesInMessage(ctx context.Context, info *types.MessageSource) (ok bool) {
|
||||
for _, jid := range []types.JID{info.Sender, info.SenderAlt, info.Chat, info.RecipientAlt, info.BroadcastListOwner} {
|
||||
if !wa.reIDPhoneDMToLIDIfNeeded(ctx, jid) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (wa *WhatsAppClient) reIDPhoneDMToLIDIfNeeded(ctx context.Context, pn types.JID) (ok bool) {
|
||||
if pn.Server != types.DefaultUserServer {
|
||||
return true
|
||||
}
|
||||
portalKey := wa.makeWAPortalKey(pn)
|
||||
if wa.Main.unmigratedDMs.Has(portalKey) {
|
||||
lid, err := wa.GetStore().LIDs.GetLIDForPN(ctx, pn)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Stringer("pn", pn).Msg("Failed to get LID for PN")
|
||||
return false
|
||||
} else if lid.IsEmpty() {
|
||||
zerolog.Ctx(ctx).Warn().Stringer("pn", pn).Msg("No found LID for phone number")
|
||||
return true
|
||||
}
|
||||
zerolog.Ctx(ctx).Info().
|
||||
Object("portal_key", portalKey).
|
||||
Stringer("pn", pn).
|
||||
Stringer("lid", lid).
|
||||
Msg("Received event for portal in unmigrated DMs list, trying migration")
|
||||
_, err = wa.Main.reIDPhoneDMToLID(ctx, pn, lid, wa.UserLogin.ID)
|
||||
if err != nil {
|
||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to re-ID phone DM to LID")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (wa *WhatsAppConnector) reIDPhoneDMToLID(ctx context.Context, pn, lid types.JID, receiver networkid.UserLoginID) (bridgev2.ReIDResult, error) {
|
||||
pnKey := networkid.PortalKey{
|
||||
ID: waid.MakePortalID(pn),
|
||||
Receiver: receiver,
|
||||
}
|
||||
lidKey := networkid.PortalKey{
|
||||
ID: waid.MakePortalID(lid),
|
||||
Receiver: receiver,
|
||||
}
|
||||
result, portal, err := wa.Bridge.ReIDPortal(ctx, pnKey, lidKey)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if result == bridgev2.ReIDResultSourceReIDd || result == bridgev2.ReIDResultTargetDeletedAndSourceReIDd {
|
||||
var pnGhost, lidGhost *bridgev2.Ghost
|
||||
pnGhost, err = wa.Bridge.GetGhostByID(ctx, waid.MakeUserID(pn))
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("failed to get PN ghost: %w", err)
|
||||
}
|
||||
lidGhost, err = wa.Bridge.GetGhostByID(ctx, waid.MakeUserID(lid))
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("failed to get LID ghost: %w", err)
|
||||
}
|
||||
_, err = pnGhost.Intent.SendState(ctx, portal.MXID, event.StateMember, pnGhost.Intent.GetMXID().String(), &event.Content{
|
||||
Parsed: &event.MemberEventContent{Membership: event.MembershipLeave, Reason: "Migrating to LIDs"},
|
||||
Raw: map[string]any{"com.beeper.exclude_from_timeline": true},
|
||||
}, time.Time{})
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("failed to send leave event for PN ghost: %w", err)
|
||||
}
|
||||
_, err = wa.Bridge.Bot.SendState(ctx, portal.MXID, event.StateMember, lidGhost.Intent.GetMXID().String(), &event.Content{
|
||||
Parsed: &event.MemberEventContent{Membership: event.MembershipInvite, Reason: "Migrating to LIDs"},
|
||||
Raw: map[string]any{"com.beeper.exclude_from_timeline": true},
|
||||
}, time.Time{})
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("failed to send invite event for LID ghost: %w", err)
|
||||
}
|
||||
_, err = lidGhost.Intent.SendState(ctx, portal.MXID, event.StateMember, lidGhost.Intent.GetMXID().String(), &event.Content{
|
||||
Parsed: &event.MemberEventContent{Membership: event.MembershipJoin, Reason: "Migrating to LIDs"},
|
||||
Raw: map[string]any{"com.beeper.exclude_from_timeline": true},
|
||||
}, time.Time{})
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("failed to send join event for LID ghost: %w", err)
|
||||
}
|
||||
portal.OtherUserID = lidGhost.ID
|
||||
err = portal.Save(ctx)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("failed to save portal after re-ID: %w", err)
|
||||
}
|
||||
portal.UpdateBridgeInfo(ctx)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var scanPortalKey = dbutil.ConvertRowFn[networkid.PortalKey](func(row dbutil.Scannable) (key networkid.PortalKey, err error) {
|
||||
err = row.Scan(&key.ID, &key.Receiver)
|
||||
return
|
||||
})
|
||||
|
||||
func (wa *WhatsAppConnector) migrateToLIDDMs(ctx context.Context) error {
|
||||
if wa.Bridge.Background {
|
||||
if wa.Bridge.DB.KV.Get(ctx, "whatsapp_lid_dms_migrated") == "true" {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("can't migrate to LID DMs in background mode")
|
||||
}
|
||||
log := zerolog.Ctx(ctx).With().Str("action", "migrate to lid dms").Logger()
|
||||
const findPNPortals = "SELECT id, receiver FROM portal WHERE bridge_id=$1 AND room_type='dm' AND id LIKE '%@s.whatsapp.net'"
|
||||
pnPortalKeys, err := scanPortalKey.NewRowIter(wa.Bridge.DB.Query(ctx, findPNPortals, wa.Bridge.ID)).AsList()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get phone number portals: %w", err)
|
||||
}
|
||||
var updatedPortals, missingLID int
|
||||
for _, key := range pnPortalKeys {
|
||||
pnJID, err := waid.ParsePortalID(key.ID)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("portal_id", string(key.ID)).Msg("Failed to parse portal ID")
|
||||
continue
|
||||
} else if pnJID.Server != types.DefaultUserServer {
|
||||
continue
|
||||
}
|
||||
lid, err := wa.DeviceStore.LIDMap.GetLIDForPN(ctx, pnJID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get LID for PN portal %s: %w", key.ID, err)
|
||||
} else if lid.IsEmpty() {
|
||||
log.Warn().Stringer("pn", pnJID).Msg("No LID for PN portal")
|
||||
wa.unmigratedDMs.Add(key)
|
||||
missingLID++
|
||||
continue
|
||||
}
|
||||
res, err := wa.reIDPhoneDMToLID(ctx, pnJID, lid, key.Receiver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to re-ID %s to %s: %w", pnJID, lid, err)
|
||||
}
|
||||
updatedPortals++
|
||||
log.Info().
|
||||
Stringer("pn", pnJID).
|
||||
Stringer("lid", lid).
|
||||
Stringer("result", res).
|
||||
Msg("Re-ID'd phone number DM portal")
|
||||
}
|
||||
log.Info().
|
||||
Int("updated_portals", updatedPortals).
|
||||
Int("total_pn_portals", len(pnPortalKeys)).
|
||||
Int("missing_lid", missingLID).
|
||||
Msg("Finished re-IDing phone number DM portals")
|
||||
wa.Bridge.DB.KV.Set(ctx, "whatsapp_lid_dms_migrated", "true")
|
||||
return nil
|
||||
}
|
||||
|
|
@ -135,7 +135,8 @@ var (
|
|||
_ bridgev2.LoginProcessWebAuthn = (*WALogin)(nil)
|
||||
)
|
||||
|
||||
const LoginConnectWait = 15 * time.Second
|
||||
const LoginConnectWait = 30 * time.Second
|
||||
const LoginPairPhoneWait = 30 * time.Second
|
||||
|
||||
func (wl *WALogin) Start(ctx context.Context) (*bridgev2.LoginStep, error) {
|
||||
wl.Main.firstClientConnectOnce.Do(wl.Main.onFirstClientConnect)
|
||||
|
|
@ -193,19 +194,21 @@ func (wl *WALogin) StartWithOverride(ctx context.Context, old *bridgev2.UserLogi
|
|||
}
|
||||
|
||||
func (wl *WALogin) SubmitUserInput(ctx context.Context, input map[string]string) (*bridgev2.LoginStep, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, LoginConnectWait)
|
||||
defer cancel()
|
||||
err := wl.Client.Connect()
|
||||
if err != nil {
|
||||
wl.Log.Err(err).Msg("Failed to connect to WhatsApp for phone code login")
|
||||
return nil, err
|
||||
}
|
||||
err = wl.WaitForQRs.Wait(ctx)
|
||||
connectCtx, cancelConnect := context.WithTimeout(ctx, LoginConnectWait)
|
||||
err = wl.WaitForQRs.Wait(connectCtx)
|
||||
cancelConnect()
|
||||
if err != nil {
|
||||
wl.Log.Warn().Err(err).Msg("Timed out waiting for connection")
|
||||
return nil, fmt.Errorf("failed to wait for connection: %w", err)
|
||||
}
|
||||
pairingCode, err := wl.Client.PairPhone(ctx, input["phone_number"], true, whatsmeow.PairClientChrome, "Chrome (Linux)")
|
||||
pairCtx, cancelPair := context.WithTimeout(ctx, LoginPairPhoneWait)
|
||||
defer cancelPair()
|
||||
pairingCode, err := wl.Client.PairPhone(pairCtx, input["phone_number"], true, whatsmeow.PairClientChrome, "Chrome (Linux)")
|
||||
if err != nil {
|
||||
wl.Log.Err(err).Msg("Failed to request phone code login")
|
||||
if errors.Is(err, whatsmeow.ErrPhoneNumberTooShort) {
|
||||
|
|
|
|||
|
|
@ -155,8 +155,8 @@ func (wa *WhatsAppClient) sendMediaRequestDirect(ctx context.Context, rawMsgID n
|
|||
return wa.Client.SendMediaRetryReceipt(ctx, &types.MessageInfo{
|
||||
ID: msgID.ID,
|
||||
MessageSource: types.MessageSource{
|
||||
IsFromMe: msgID.Sender.User == wa.JID.User,
|
||||
IsGroup: msgID.Chat.Server != types.DefaultUserServer && msgID.Chat.Server != types.BotServer,
|
||||
IsFromMe: wa.IsOwnJID(msgID.Sender),
|
||||
IsGroup: msgID.Chat.Server != types.DefaultUserServer && msgID.Chat.Server != types.HiddenUserServer && msgID.Chat.Server != types.BotServer,
|
||||
Sender: msgID.Sender,
|
||||
Chat: msgID.Chat,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.mau.fi/util/exmaps"
|
||||
"go.mau.fi/util/exsync"
|
||||
"go.mau.fi/util/ptr"
|
||||
"go.mau.fi/whatsmeow"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
|
|
@ -62,6 +64,13 @@ func looksEmaily(str string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
type cacheEntry struct {
|
||||
jid types.JID
|
||||
ts time.Time
|
||||
}
|
||||
|
||||
var isOnWhatsappCache = exsync.NewMap[string, cacheEntry]()
|
||||
|
||||
func (wa *WhatsAppClient) validateIdentifer(ctx context.Context, number string) (types.JID, error) {
|
||||
if strings.HasSuffix(number, "@"+types.BotServer) || strings.HasSuffix(number, "@"+types.HiddenUserServer) {
|
||||
return types.ParseJID(number)
|
||||
|
|
@ -76,6 +85,8 @@ func (wa *WhatsAppClient) validateIdentifer(ctx context.Context, number string)
|
|||
return types.EmptyJID, ErrInputLooksLikeEmail
|
||||
} else if wa.Client == nil || !wa.Client.IsLoggedIn() {
|
||||
return types.EmptyJID, bridgev2.ErrNotLoggedIn
|
||||
} else if entry, ok := isOnWhatsappCache.Get(number); ok && time.Since(entry.ts) < 4*time.Hour {
|
||||
return entry.jid, nil
|
||||
} else if resp, err := wa.Client.IsOnWhatsApp(ctx, []string{number}); err != nil {
|
||||
return types.EmptyJID, fmt.Errorf("failed to check if number is on WhatsApp: %w", err)
|
||||
} else if len(resp) == 0 {
|
||||
|
|
@ -83,6 +94,7 @@ func (wa *WhatsAppClient) validateIdentifer(ctx context.Context, number string)
|
|||
} else if !resp[0].IsIn {
|
||||
return types.EmptyJID, bridgev2.WrapRespErr(fmt.Errorf("the server said +%s is not on WhatsApp", resp[0].JID.User), mautrix.MNotFound)
|
||||
} else {
|
||||
isOnWhatsappCache.Set(number, cacheEntry{resp[0].JID, time.Now()})
|
||||
return resp[0].JID, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -108,16 +120,24 @@ func (wa *WhatsAppConnector) ValidateUserID(id networkid.UserID) bool {
|
|||
}
|
||||
}
|
||||
|
||||
func (wa *WhatsAppClient) startChatLIDToPN(ctx context.Context, jid types.JID) (types.JID, error) {
|
||||
if jid.Server == types.HiddenUserServer {
|
||||
pn, err := wa.GetStore().LIDs.GetPNForLID(ctx, jid)
|
||||
func (wa *WhatsAppClient) startChatPNToLID(ctx context.Context, jid types.JID) (types.JID, error) {
|
||||
if jid.Server == types.DefaultUserServer {
|
||||
lid, err := wa.GetStore().LIDs.GetLIDForPN(ctx, jid)
|
||||
if err != nil {
|
||||
return jid, fmt.Errorf("failed to get phone number for lid: %w", err)
|
||||
} else if pn.IsEmpty() {
|
||||
// Don't allow starting chats with LIDs for now
|
||||
return jid, fmt.Errorf("phone number not found")
|
||||
return jid, fmt.Errorf("failed to get lid for phone number: %w", err)
|
||||
} else if lid.IsEmpty() {
|
||||
resp, err := wa.Client.GetUserInfo(ctx, []types.JID{jid})
|
||||
if err != nil {
|
||||
return jid, fmt.Errorf("failed to get user info for phone number: %w", err)
|
||||
} else if info, ok := resp[jid]; !ok {
|
||||
return jid, fmt.Errorf("server didn't return user info for phone number")
|
||||
} else if info.LID.IsEmpty() {
|
||||
return jid, fmt.Errorf("server didn't return lid for phone number")
|
||||
} else {
|
||||
return info.LID, nil
|
||||
}
|
||||
}
|
||||
return pn, nil
|
||||
return lid, nil
|
||||
}
|
||||
return jid, nil
|
||||
}
|
||||
|
|
@ -136,7 +156,7 @@ func (wa *WhatsAppClient) makeCreateChatResponse(ctx context.Context, jid, origJ
|
|||
|
||||
func (wa *WhatsAppClient) CreateChatWithGhost(ctx context.Context, ghost *bridgev2.Ghost) (*bridgev2.CreateChatResponse, error) {
|
||||
origJID := waid.ParseUserID(ghost.ID)
|
||||
jid, err := wa.startChatLIDToPN(ctx, origJID)
|
||||
jid, err := wa.startChatPNToLID(ctx, origJID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -148,7 +168,7 @@ func (wa *WhatsAppClient) ResolveIdentifier(ctx context.Context, identifier stri
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jid, err := wa.startChatLIDToPN(ctx, origJID)
|
||||
jid, err := wa.startChatPNToLID(ctx, origJID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -188,6 +208,7 @@ func (wa *WhatsAppClient) getContactList(ctx context.Context, filter string, onl
|
|||
return nil, err
|
||||
}
|
||||
resp := make([]*bridgev2.ResolveIdentifierResponse, 0, len(contacts))
|
||||
addedIDs := make(exmaps.Set[types.JID])
|
||||
for jid, contactInfo := range contacts {
|
||||
if onlyContacts && (contactInfo.FirstName == "" && contactInfo.FullName == "") {
|
||||
continue
|
||||
|
|
@ -195,31 +216,43 @@ func (wa *WhatsAppClient) getContactList(ctx context.Context, filter string, onl
|
|||
if !matchesQuery(contactInfo.PushName, filter) && !matchesQuery(contactInfo.FullName, filter) && !matchesQuery(jid.User, filter) {
|
||||
continue
|
||||
}
|
||||
var lid types.JID
|
||||
if jid.Server == types.HiddenUserServer {
|
||||
lid = jid
|
||||
} else if jid.Server == types.DefaultUserServer {
|
||||
lid, err = wa.GetStore().LIDs.GetLIDForPN(ctx, jid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get lid for phone number %s: %w", jid, err)
|
||||
} else if !lid.IsEmpty() {
|
||||
jid = lid
|
||||
}
|
||||
}
|
||||
if !addedIDs.Add(jid) {
|
||||
continue
|
||||
}
|
||||
var chatResp *bridgev2.CreateChatResponse
|
||||
if !lid.IsEmpty() {
|
||||
chatResp = &bridgev2.CreateChatResponse{PortalKey: wa.makeWAPortalKey(lid)}
|
||||
}
|
||||
ghost, _ := wa.Main.Bridge.GetGhostByID(ctx, waid.MakeUserID(jid))
|
||||
resp = append(resp, &bridgev2.ResolveIdentifierResponse{
|
||||
Ghost: ghost,
|
||||
UserID: waid.MakeUserID(jid),
|
||||
UserInfo: wa.contactToUserInfo(ctx, jid, contactInfo, false),
|
||||
Chat: &bridgev2.CreateChatResponse{PortalKey: wa.makeWAPortalKey(jid)},
|
||||
Chat: chatResp,
|
||||
})
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (wa *WhatsAppClient) CreateGroup(ctx context.Context, params *bridgev2.GroupCreateParams) (*bridgev2.CreateChatResponse, error) {
|
||||
createKey := wa.Client.GenerateMessageID()
|
||||
if params.RoomID != "" {
|
||||
wa.createDedup.Add(createKey)
|
||||
}
|
||||
req := whatsmeow.ReqCreateGroup{
|
||||
Name: ptr.Val(params.Name).Name,
|
||||
Participants: make([]types.JID, len(params.Participants)),
|
||||
CreateKey: createKey,
|
||||
}
|
||||
for i, participant := range params.Participants {
|
||||
jid := waid.ParseUserID(participant)
|
||||
// Normalize to PN if it's a LID
|
||||
jid, err := wa.startChatLIDToPN(ctx, jid)
|
||||
jid, err := wa.startChatPNToLID(ctx, jid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to normalize participant %s: %w", participant, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,12 +96,12 @@ func (mq *MessageQuery) GetBetween(ctx context.Context, loginID networkid.UserLo
|
|||
args := []any{mq.BridgeID, loginID, chatJID}
|
||||
argNum := 4
|
||||
if startTime != nil {
|
||||
whereClauses += fmt.Sprintf(" AND timestamp >= $%d", argNum)
|
||||
whereClauses += fmt.Sprintf(" AND timestamp > $%d", argNum)
|
||||
args = append(args, startTime.Unix())
|
||||
argNum++
|
||||
}
|
||||
if endTime != nil {
|
||||
whereClauses += fmt.Sprintf(" AND timestamp <= $%d", argNum)
|
||||
whereClauses += fmt.Sprintf(" AND timestamp < $%d", argNum)
|
||||
args = append(args, endTime.Unix())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
-- v0 -> v9 (compatible with v3+): Latest revision
|
||||
-- v0 -> v10 (compatible with v3+): Latest revision
|
||||
|
||||
CREATE TABLE whatsapp_poll_option_id (
|
||||
bridge_id TEXT NOT NULL,
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
-- v8 (compatible with v3+): Mark LID DMs for deletion
|
||||
INSERT INTO kv_store (bridge_id, key, value) VALUES ('', 'whatsapp_lid_dms_deleted', 'false');
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
-- v9 (compatible with v3+): Mark LID DMs for deletion (again)
|
||||
DELETE FROM kv_store WHERE bridge_id='' AND key='whatsapp_lid_dms_deleted';
|
||||
INSERT INTO kv_store (bridge_id, key, value) VALUES ('', 'whatsapp_lid_dms_deleted', 'false');
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
-- v10 (compatible with v3+): Move history sync conversations to LIDs
|
||||
|
||||
-- Delete history sync conversations where a @lid conversation already exists
|
||||
DELETE FROM whatsapp_history_sync_conversation
|
||||
WHERE chat_jid LIKE '%@lid' AND EXISTS (
|
||||
SELECT 1
|
||||
FROM whatsapp_history_sync_conversation pnconv
|
||||
WHERE pnconv.chat_jid=(
|
||||
SELECT pn || '@s.whatsapp.net'
|
||||
FROM whatsmeow_lid_map
|
||||
WHERE lid=replace(whatsapp_history_sync_conversation.chat_jid, '@lid', '')
|
||||
)
|
||||
);
|
||||
|
||||
-- Update all phone number conversations to lids if the lid is known
|
||||
UPDATE whatsapp_history_sync_conversation
|
||||
SET chat_jid=(SELECT lid || '@lid' FROM whatsmeow_lid_map WHERE pn=replace(chat_jid, '@s.whatsapp.net', ''))
|
||||
WHERE chat_jid LIKE '%@s.whatsapp.net'
|
||||
AND EXISTS (SELECT 1 FROM whatsmeow_lid_map WHERE pn=replace(chat_jid, '@s.whatsapp.net', ''));
|
||||
|
||||
-- Delete blank phone number portals
|
||||
DELETE FROM portal WHERE id LIKE '%@s.whatsapp.net' AND (mxid IS NULL OR mxid='') AND room_type='';
|
||||
|
|
@ -6,11 +6,9 @@ import (
|
|||
"go.mau.fi/util/dbutil"
|
||||
)
|
||||
|
||||
var Table dbutil.UpgradeTable
|
||||
|
||||
//go:embed *.sql
|
||||
var rawUpgrades embed.FS
|
||||
|
||||
func init() {
|
||||
Table.RegisterFS(rawUpgrades)
|
||||
}
|
||||
var Table = dbutil.BuildUpgradeTable().
|
||||
WithFS(rawUpgrades).
|
||||
Finish()
|
||||
|
|
|
|||
|
|
@ -67,10 +67,11 @@ func getMessageType(waMsg *waE2E.Message) string {
|
|||
return "encrypted comment"
|
||||
case waMsg.CommentMessage != nil:
|
||||
return "comment"
|
||||
case waMsg.PollCreationMessage != nil || waMsg.PollCreationMessageV2 != nil || waMsg.PollCreationMessageV3 != nil:
|
||||
case waMsg.PollCreationMessage != nil || waMsg.PollCreationMessageV2 != nil || waMsg.PollCreationMessageV3 != nil ||
|
||||
waMsg.PollCreationMessageV5 != nil || waMsg.PollCreationMessageV6 != nil:
|
||||
return "poll create"
|
||||
case waMsg.PollCreationMessageV4 != nil || waMsg.PollCreationMessageV5 != nil:
|
||||
return "poll create (vNext)"
|
||||
case waMsg.PollCreationMessageV4 != nil:
|
||||
return "poll create (v4)"
|
||||
case waMsg.PollUpdateMessage != nil:
|
||||
return "poll update"
|
||||
case waMsg.ProtocolMessage != nil:
|
||||
|
|
|
|||
|
|
@ -143,14 +143,14 @@ func (mc *MessageConverter) ToWhatsApp(
|
|||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse message ID: %w", err)
|
||||
}
|
||||
rootMsgInfo := MessageIDToInfo(client, parsedID)
|
||||
rootMsgInfo := MessageIDToInfo(ctx, client, parsedID)
|
||||
message, err = client.EncryptComment(ctx, rootMsgInfo, message)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to encrypt comment: %w", err)
|
||||
}
|
||||
lid := parsedID.Sender
|
||||
if lid.Server == types.DefaultUserServer {
|
||||
lid, err = client.Store.LIDs.GetLIDForPN(ctx, parsedID.Sender)
|
||||
lid, err = client.Store.LIDs.GetLIDForPN(ctx, lid)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get LID for PN: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,6 +175,12 @@ func (mc *MessageConverter) ToMatrix(
|
|||
part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV2)
|
||||
case waMsg.PollCreationMessageV3 != nil:
|
||||
part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV3)
|
||||
//case waMsg.PollCreationMessageV4 != nil:
|
||||
// part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV4)
|
||||
case waMsg.PollCreationMessageV5 != nil:
|
||||
part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV5)
|
||||
case waMsg.PollCreationMessageV6 != nil:
|
||||
part, contextInfo = mc.convertPollCreationMessage(ctx, waMsg.PollCreationMessageV6)
|
||||
case waMsg.PollUpdateMessage != nil:
|
||||
part, contextInfo = mc.convertPollUpdateMessage(ctx, info, waMsg.PollUpdateMessage)
|
||||
case waMsg.EventMessage != nil:
|
||||
|
|
@ -265,30 +271,26 @@ func (mc *MessageConverter) ToMatrix(
|
|||
if chat.IsEmpty() {
|
||||
chat, _ = waid.ParsePortalID(portal.ID)
|
||||
}
|
||||
// We reroute all DMs to the phone number JID, so reroute reply participants too
|
||||
pcp = rerouteMessageKey(ctx, chat, pcp, getPortal(ctx).Metadata.(*waid.PortalMetadata).AddressingMode == types.AddressingModeLID)
|
||||
if store := getClient(ctx).Store; store != nil && chat.Server == types.DefaultUserServer && pcp.Server == types.HiddenUserServer {
|
||||
pcpPN, _ := store.LIDs.GetPNForLID(ctx, pcp)
|
||||
zerolog.Ctx(ctx).Debug().
|
||||
Stringer("orig_participant", pcp).
|
||||
Stringer("rerouted_participant", pcpPN).
|
||||
Msg("Rerouting reply target (PN recipient in LID DM)")
|
||||
if !pcpPN.IsEmpty() {
|
||||
pcp = pcpPN
|
||||
}
|
||||
} else if store != nil && chat.Server == types.GroupServer && pcp.Server == types.DefaultUserServer && getPortal(ctx).Metadata.(*waid.PortalMetadata).AddressingMode == types.AddressingModeLID {
|
||||
pcpLID, _ := store.LIDs.GetLIDForPN(ctx, pcp)
|
||||
zerolog.Ctx(ctx).Debug().
|
||||
Stringer("orig_participant", pcp).
|
||||
Stringer("rerouted_participant", pcpLID).
|
||||
Msg("Rerouting reply target (PN recipient in LID group)")
|
||||
if !pcpLID.IsEmpty() {
|
||||
pcp = pcpLID
|
||||
}
|
||||
}
|
||||
cm.ReplyTo = &networkid.MessageOptionalPartID{
|
||||
MessageID: waid.MakeMessageID(chat, pcp, contextInfo.GetStanzaID()),
|
||||
}
|
||||
var pn, lid types.JID
|
||||
if pcp.Server == types.DefaultUserServer {
|
||||
pn = pcp
|
||||
lid, _ = client.Store.LIDs.GetLIDForPN(ctx, pcp)
|
||||
} else if pcp.Server == types.HiddenUserServer {
|
||||
lid = pcp
|
||||
pn, _ = client.Store.LIDs.GetPNForLID(ctx, pcp)
|
||||
} else if pcp.Server == types.BotServer {
|
||||
lid = pcp
|
||||
}
|
||||
if !pn.IsEmpty() {
|
||||
cm.ReplyToLogin = waid.MakeUserLoginID(pn)
|
||||
}
|
||||
if !lid.IsEmpty() {
|
||||
cm.ReplyToUser = waid.MakeUserID(lid)
|
||||
}
|
||||
// TODO set reply to room
|
||||
}
|
||||
if contextInfo.GetIsForwarded() {
|
||||
hasCaption := part.Content.FileName != "" && part.Content.FileName != part.Content.Body
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ func (mc *MessageConverter) PollVoteToWhatsApp(
|
|||
zerolog.Ctx(ctx).Err(err).Msg("Failed to parse message ID")
|
||||
return nil, fmt.Errorf("failed to parse message ID")
|
||||
}
|
||||
pollMsgInfo := MessageIDToInfo(client, parsedMsgID)
|
||||
pollMsgInfo := MessageIDToInfo(ctx, client, parsedMsgID)
|
||||
pollMsgInfo.Type = "poll"
|
||||
optionHashes := make([][]byte, 0, len(content.Response.Answers))
|
||||
if pollMsg.Metadata.(*waid.MessageMetadata).IsMatrixPoll {
|
||||
|
|
@ -146,13 +146,23 @@ func (mc *MessageConverter) PollVoteToWhatsApp(
|
|||
return &waE2E.Message{PollUpdateMessage: pollUpdate}, err
|
||||
}
|
||||
|
||||
func MessageIDToInfo(client *whatsmeow.Client, parsedMsgID *waid.ParsedMessageID) *types.MessageInfo {
|
||||
func MessageIDToInfo(ctx context.Context, client *whatsmeow.Client, parsedMsgID *waid.ParsedMessageID) *types.MessageInfo {
|
||||
chat := parsedMsgID.Chat
|
||||
sender := parsedMsgID.Sender
|
||||
if chat.Server == types.DefaultUserServer {
|
||||
chatLID, _ := client.Store.LIDs.GetLIDForPN(ctx, chat)
|
||||
senderLID, _ := client.Store.LIDs.GetLIDForPN(ctx, sender)
|
||||
if !chatLID.IsEmpty() && !senderLID.IsEmpty() {
|
||||
chat = chatLID
|
||||
sender = senderLID
|
||||
}
|
||||
}
|
||||
return &types.MessageInfo{
|
||||
MessageSource: types.MessageSource{
|
||||
Chat: parsedMsgID.Chat,
|
||||
Sender: parsedMsgID.Sender,
|
||||
IsFromMe: parsedMsgID.Sender.User == client.Store.GetLID().User || parsedMsgID.Sender.User == client.Store.GetJID().User,
|
||||
IsGroup: parsedMsgID.Chat.Server == types.GroupServer,
|
||||
Chat: chat,
|
||||
Sender: sender,
|
||||
IsFromMe: sender.User == client.Store.GetLID().User || sender.User == client.Store.GetJID().User,
|
||||
IsGroup: chat.Server == types.GroupServer,
|
||||
},
|
||||
ID: parsedMsgID.ID,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,31 +94,7 @@ func (mc *MessageConverter) convertPollCreationMessage(ctx context.Context, msg
|
|||
}, msg.GetContextInfo()
|
||||
}
|
||||
|
||||
func rerouteMessageKey(ctx context.Context, chat, sender types.JID, groupLIDAddressing bool) types.JID {
|
||||
if store := getClient(ctx).Store; store != nil && chat.Server == types.DefaultUserServer && sender.Server == types.HiddenUserServer {
|
||||
senderPN, _ := store.LIDs.GetPNForLID(ctx, sender)
|
||||
zerolog.Ctx(ctx).Debug().
|
||||
Stringer("orig_participant", sender).
|
||||
Stringer("rerouted_participant", senderPN).
|
||||
Msg("Rerouting message key (PN recipient in LID DM)")
|
||||
if !senderPN.IsEmpty() {
|
||||
return senderPN
|
||||
}
|
||||
} else if store != nil && chat.Server == types.GroupServer && sender.Server == types.DefaultUserServer && groupLIDAddressing {
|
||||
senderLID, _ := store.LIDs.GetLIDForPN(ctx, sender)
|
||||
zerolog.Ctx(ctx).Debug().
|
||||
Stringer("orig_participant", sender).
|
||||
Stringer("rerouted_participant", senderLID).
|
||||
Msg("Rerouting message key (PN recipient in LID group)")
|
||||
if !senderLID.IsEmpty() {
|
||||
return senderLID
|
||||
}
|
||||
}
|
||||
return sender
|
||||
}
|
||||
|
||||
func KeyToMessageID(ctx context.Context, client *whatsmeow.Client, chat, sender types.JID, key *waCommon.MessageKey) networkid.MessageID {
|
||||
groupLIDAddressing := sender.Server == types.HiddenUserServer
|
||||
sender = sender.ToNonAD()
|
||||
var err error
|
||||
if !key.GetFromMe() {
|
||||
|
|
@ -131,7 +107,7 @@ func KeyToMessageID(ctx context.Context, client *whatsmeow.Client, chat, sender
|
|||
if sender.Server == types.LegacyUserServer {
|
||||
sender.Server = types.DefaultUserServer
|
||||
}
|
||||
} else if chat.Server == types.DefaultUserServer || chat.Server == types.BotServer {
|
||||
} else if chat.Server == types.DefaultUserServer || chat.Server == types.HiddenUserServer || chat.Server == types.BotServer {
|
||||
if sender.User == client.Store.GetJID().User || sender.User == client.Store.GetLID().User {
|
||||
// Message key is not from the sender, but message sender (containing key) is me,
|
||||
// so message key sender is the other user in the DM
|
||||
|
|
@ -139,7 +115,11 @@ func KeyToMessageID(ctx context.Context, client *whatsmeow.Client, chat, sender
|
|||
} else {
|
||||
// Message key is not from the sender, but message sender (containing key) is not me,
|
||||
// so message key sender is me
|
||||
sender = client.Store.GetJID().ToNonAD()
|
||||
if chat.Server == types.HiddenUserServer {
|
||||
sender = client.Store.GetLID().ToNonAD()
|
||||
} else {
|
||||
sender = client.Store.GetJID().ToNonAD()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
zerolog.Ctx(ctx).Warn().
|
||||
|
|
@ -157,10 +137,6 @@ func KeyToMessageID(ctx context.Context, client *whatsmeow.Client, chat, sender
|
|||
chat = remoteJID
|
||||
}
|
||||
}
|
||||
sender = rerouteMessageKey(
|
||||
context.WithValue(ctx, contextKeyClient, client),
|
||||
chat, sender, groupLIDAddressing,
|
||||
)
|
||||
return waid.MakeMessageID(chat, sender, key.GetID())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,13 @@ func MakeMessageID(chat, sender types.JID, id types.MessageID) networkid.Message
|
|||
return networkid.MessageID(fmt.Sprintf("%s:%s:%s", chat.ToNonAD().String(), sender.ToNonAD().String(), id))
|
||||
}
|
||||
|
||||
func MakeMessageIDWithAltSender(chat, sender, altSender types.JID, id types.MessageID) networkid.MessageID {
|
||||
if chat.Server == types.HiddenUserServer && sender.Server == types.DefaultUserServer && altSender.Server == types.HiddenUserServer {
|
||||
sender = altSender
|
||||
}
|
||||
return MakeMessageID(chat, sender, id)
|
||||
}
|
||||
|
||||
func MakeFakeMessageID(chat, sender types.JID, data string) networkid.MessageID {
|
||||
return networkid.MessageID(fmt.Sprintf("fake:%s:%s:%s", chat.ToNonAD().String(), sender.ToNonAD().String(), data))
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue