mirror of
https://github.com/mautrix/signal.git
synced 2026-11-09 15:49:17 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ffa38469f | ||
|
|
606d904cac | ||
|
|
c795cea7a1 |
237 changed files with 12144 additions and 63871 deletions
14
.github/ISSUE_TEMPLATE/bug.md
vendored
14
.github/ISSUE_TEMPLATE/bug.md
vendored
|
|
@ -7,12 +7,10 @@ type: Bug
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Include relevant logs, the bridge version and other important details here -->
|
<!--
|
||||||
|
Remember to include relevant logs, the bridge version and any other details.
|
||||||
|
|
||||||
### Checklist
|
It's always best to ask in the Matrix room first, especially if you aren't sure
|
||||||
|
what details are needed. Issues with insufficient detail will likely just be
|
||||||
<!-- All items below are mandatory. Issues not following the rules may be closed without comment. -->
|
ignored or closed immediately.
|
||||||
|
-->
|
||||||
* [ ] This is an actual bug, not just a setup issue (see the [troubleshooting docs](https://docs.mau.fi/bridges/general/troubleshooting.html) or ask in the Matrix room for setup help).
|
|
||||||
* [ ] I am certain that sufficient information is included. Ask in the Matrix room first if not.
|
|
||||||
* [ ] The bug is still present on the main branch. The `!signal version` command output is: ``
|
|
||||||
|
|
|
||||||
17
.github/workflows/go.yml
vendored
17
.github/workflows/go.yml
vendored
|
|
@ -11,14 +11,14 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
go-version: ["1.26", "1.27"]
|
go-version: ["1.24", "1.25"]
|
||||||
name: Lint ${{ matrix.go-version == '1.27' && '(latest)' || '(old)' }}
|
name: Lint ${{ matrix.go-version == '1.25' && '(latest)' || '(old)' }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v7
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
cache: true
|
cache: true
|
||||||
|
|
@ -30,7 +30,6 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
go install golang.org/x/tools/cmd/goimports@latest
|
go install golang.org/x/tools/cmd/goimports@latest
|
||||||
go install honnef.co/go/tools/cmd/staticcheck@latest
|
go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||||
go install github.com/beeper/pre-commit-go/bridgelint@latest
|
|
||||||
export PATH="$HOME/go/bin:$PATH"
|
export PATH="$HOME/go/bin:$PATH"
|
||||||
|
|
||||||
- name: Run pre-commit
|
- name: Run pre-commit
|
||||||
|
|
@ -41,14 +40,14 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
go-version: ["1.26", "1.27"]
|
go-version: ["1.24", "1.25"]
|
||||||
name: Test ${{ matrix.go-version == '1.27' && '(latest)' || '(old)' }}
|
name: Test ${{ matrix.go-version == '1.25' && '(latest)' || '(old)' }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v7
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
cache: true
|
cache: true
|
||||||
|
|
|
||||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
||||||
lock-stale:
|
lock-stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v6
|
- uses: dessant/lock-threads@v5
|
||||||
id: lock
|
id: lock
|
||||||
with:
|
with:
|
||||||
issue-inactive-days: 90
|
issue-inactive-days: 90
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ repos:
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
|
|
||||||
- repo: https://github.com/tekwizely/pre-commit-golang
|
- repo: https://github.com/tekwizely/pre-commit-golang
|
||||||
rev: v1.0.0-rc.4
|
rev: v1.0.0-rc.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: go-imports
|
- id: go-imports
|
||||||
exclude: "pb\\.go$"
|
exclude: "pb\\.go$"
|
||||||
|
|
@ -22,10 +22,7 @@ repos:
|
||||||
# TODO: reenable this and fix all the problems
|
# TODO: reenable this and fix all the problems
|
||||||
|
|
||||||
- repo: https://github.com/beeper/pre-commit-go
|
- repo: https://github.com/beeper/pre-commit-go
|
||||||
rev: v0.5.0
|
rev: v0.4.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: zerolog-ban-msgf
|
- id: zerolog-ban-msgf
|
||||||
- id: zerolog-use-stringer
|
- id: zerolog-use-stringer
|
||||||
- id: bridgelint
|
|
||||||
language: system
|
|
||||||
verbose: true
|
|
||||||
|
|
|
||||||
137
CHANGELOG.md
137
CHANGELOG.md
|
|
@ -1,140 +1,3 @@
|
||||||
# v26.11 (unreleased)
|
|
||||||
|
|
||||||
* Updated libsignal to v0.106.1.
|
|
||||||
* Added support for sending more than 2048 characters of text to Signal by
|
|
||||||
uploading the rest of the text as an attachment, like official clients do.
|
|
||||||
* Added automatic retrying of group message sends if the server returns a 500
|
|
||||||
or 503 error.
|
|
||||||
* Added support for encrypting direct media with FLOE ([MSC4571]).
|
|
||||||
* Fixed memory leak in libsignal store implementation.
|
|
||||||
|
|
||||||
[MSC4571]: https://github.com/matrix-org/matrix-spec-proposals/pull/4571
|
|
||||||
|
|
||||||
# v26.10
|
|
||||||
|
|
||||||
* Updated libsignal to v0.105.0.
|
|
||||||
* Added support for phonenumberless accounts.
|
|
||||||
* Added support for starting chats with usernames and username links.
|
|
||||||
* Added support for blocking and unblocking users on Signal.
|
|
||||||
* Added support for new view-limited media interface in bridgev2.
|
|
||||||
* Added proper sender certificate validation.
|
|
||||||
* Changed storage sync to only sync once if repeated fetch latest notices are
|
|
||||||
received.
|
|
||||||
* Fixed contact syncs not happening even when `sync_contacts_on_startup` was
|
|
||||||
set to true.
|
|
||||||
* Fixed Signal encryption using wrong timestamp precision for session expiry.
|
|
||||||
* Fixed sender key sharing not handling removed users properly.
|
|
||||||
* Fixed handling retry receipts with no sender key info.
|
|
||||||
* Fixed deadlock in prekey upload loop if the client gets logged out.
|
|
||||||
* Fixed infinite loop when importing sticker packs with duplicate emoji
|
|
||||||
shortcuts.
|
|
||||||
|
|
||||||
# v26.09
|
|
||||||
|
|
||||||
* Bumped minimum Go version to 1.26.
|
|
||||||
* Updated libsignal to v0.102.2.
|
|
||||||
* Fixed handling messages with invalid values in formatting body ranges.
|
|
||||||
* Fixed websocket request retrying not working correctly if the first attempt
|
|
||||||
times out.
|
|
||||||
* Fixed another potential race condition with edited message bridging.
|
|
||||||
|
|
||||||
# v26.08
|
|
||||||
|
|
||||||
* Updated libsignal to v0.100.0
|
|
||||||
* Added support for handling own profile key changes.
|
|
||||||
* Changed attachment bridging behavior to match Signal Desktop.
|
|
||||||
* Fixed edited messages being bridged twice if certain race conditions occurred.
|
|
||||||
|
|
||||||
# v26.07
|
|
||||||
|
|
||||||
* Updated Docker image to Alpine 3.24.
|
|
||||||
* Updated libsignal to v0.97.2.
|
|
||||||
* Added automatic retry when sender key send fails due to missing session.
|
|
||||||
* Fixed changing poll votes from Matrix.
|
|
||||||
|
|
||||||
# v26.06
|
|
||||||
|
|
||||||
* Updated libsignal to v0.94.4.
|
|
||||||
* Updated mrenclave to fix looking up phone numbers.
|
|
||||||
* Changed knock accept handling to auto-join the ghost user afterwards.
|
|
||||||
|
|
||||||
# v26.05
|
|
||||||
|
|
||||||
* Updated libsignal to v0.93.2.
|
|
||||||
* Added support for importing sticker packs from Signal.
|
|
||||||
|
|
||||||
# v26.04
|
|
||||||
|
|
||||||
* Updated libsignal to v0.92.1.
|
|
||||||
* Added support for admin message deletes from Signal.
|
|
||||||
* Added support for binary service IDs in storage service.
|
|
||||||
* Fixed `private_chat_portal_meta` option not setting DM room names correctly.
|
|
||||||
* Fixed panic if user is logged out during initial chat sync.
|
|
||||||
* Fixed avatar upload failing when creating new Signal group.
|
|
||||||
|
|
||||||
# v26.03
|
|
||||||
|
|
||||||
* Switched to sending binary service ID fields in outgoing messages.
|
|
||||||
* Added support for roundtripping large attachments via disk to avoid keeping
|
|
||||||
the entire file in memory during en/decryption.
|
|
||||||
|
|
||||||
# v26.02.2
|
|
||||||
|
|
||||||
* Added support for more new binary service ID fields.
|
|
||||||
|
|
||||||
# v26.02.1
|
|
||||||
|
|
||||||
* Updated libsignal to v0.87.5.
|
|
||||||
* Added support for new binary service ID fields that Signal 8.0 switched to.
|
|
||||||
|
|
||||||
# v26.02
|
|
||||||
|
|
||||||
* Bumped minimum Go version to 1.25.
|
|
||||||
* Updated libsignal to v0.87.1.
|
|
||||||
* Added automatic recovery for the session not found error from libsignal.
|
|
||||||
* Fixed sender key state not being cleared on logout properly.
|
|
||||||
|
|
||||||
# v26.01
|
|
||||||
|
|
||||||
* Updated libsignal to v0.86.12.
|
|
||||||
* Changed automatic contact list sync option to only sync every 3 days rather
|
|
||||||
than on every restart.
|
|
||||||
* Fixed sending messages to groups with no other registered members.
|
|
||||||
* Fixed sender key sends failing if some users had changed devices.
|
|
||||||
* Fixed timestamps of outgoing typing notifications in DMs.
|
|
||||||
|
|
||||||
# v25.12
|
|
||||||
|
|
||||||
* Updated libsignal to v0.86.8.
|
|
||||||
* Updated Docker image to Alpine 3.23.
|
|
||||||
* Added support for dropping incoming DMs from blocked contacts on Signal.
|
|
||||||
* Added support for sender key encryption when sending to groups, which makes
|
|
||||||
sending much faster and enables sending typing notifications.
|
|
||||||
* Added support for encryption retry receipts.
|
|
||||||
* Fixed bugs with handling poll votes.
|
|
||||||
* Fixed history transfer option not showing up when pairing with Signal Android.
|
|
||||||
* Fixed nicknames being cleared not being bridged
|
|
||||||
(thanks to [@Enzime] in [#623]).
|
|
||||||
|
|
||||||
[#623]: https://github.com/mautrix/signal/pull/623
|
|
||||||
[@Enzime]: https://github.com/Enzime
|
|
||||||
|
|
||||||
# v25.11
|
|
||||||
|
|
||||||
* Updated libsignal to v0.86.4.
|
|
||||||
* Added support for bridging invite state in groups for phone number invites.
|
|
||||||
* Added support for polls.
|
|
||||||
* Fixed PNI signature not being sent when replying to message requests.
|
|
||||||
* Fixed unnecessary repeating error notices when Signal is down.
|
|
||||||
* Fixed sticker size metadata on Matrix not matching how native Signal Desktop
|
|
||||||
renders them.
|
|
||||||
|
|
||||||
# v25.10
|
|
||||||
|
|
||||||
* Switched to calendar versioning.
|
|
||||||
* Updated libsignal to v0.84.0.
|
|
||||||
* Fixed backfill creating incorrect disappearing timer change notices.
|
|
||||||
|
|
||||||
# v0.8.7 (2025-09-16)
|
# v0.8.7 (2025-09-16)
|
||||||
|
|
||||||
* Removed legacy provisioning API and database legacy migration.
|
* Removed legacy provisioning API and database legacy migration.
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
See <https://docs.mau.fi/bridges/general/contributing.html>
|
|
||||||
22
Dockerfile
22
Dockerfile
|
|
@ -1,17 +1,18 @@
|
||||||
# -- Build libsignal (with Rust) --
|
# -- Build libsignal (with Rust) --
|
||||||
FROM rust:1-alpine AS rust-builder
|
FROM rust:1-alpine as rust-builder
|
||||||
RUN apk add --no-cache git make cmake protoc musl-dev g++ clang-dev protobuf-dev
|
RUN apk add --no-cache git make cmake protoc musl-dev g++ clang-dev
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
# Copy all files needed for Rust build, and no Go files
|
# Copy all files needed for Rust build, and no Go files
|
||||||
COPY pkg/libsignalgo/libsignal/. pkg/libsignalgo/libsignal/.
|
COPY pkg/libsignalgo/libsignal/. pkg/libsignalgo/libsignal/.
|
||||||
COPY build-rust.sh .
|
COPY build-rust.sh .
|
||||||
|
|
||||||
|
ARG DBG=0
|
||||||
RUN ./build-rust.sh
|
RUN ./build-rust.sh
|
||||||
|
|
||||||
# -- Build mautrix-signal (with Go) --
|
# -- Build mautrix-signal (with Go) --
|
||||||
FROM golang:1-alpine3.24 AS go-builder
|
FROM golang:1-alpine3.22 AS go-builder
|
||||||
RUN apk add --no-cache git ca-certificates build-base olm-dev zlib-dev
|
RUN apk add --no-cache git ca-certificates build-base olm-dev
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
# Copy all files needed for Go build, and no Rust files
|
# Copy all files needed for Go build, and no Rust files
|
||||||
|
|
@ -25,14 +26,20 @@ COPY pkg/connector/. pkg/connector/.
|
||||||
COPY cmd/. cmd/.
|
COPY cmd/. cmd/.
|
||||||
COPY .git .git
|
COPY .git .git
|
||||||
|
|
||||||
|
ARG DBG=0
|
||||||
ENV LIBRARY_PATH=.
|
ENV LIBRARY_PATH=.
|
||||||
COPY --from=rust-builder /build/pkg/libsignalgo/libsignal/target/*/libsignal_ffi.a ./
|
COPY --from=rust-builder /build/pkg/libsignalgo/libsignal/target/*/libsignal_ffi.a ./
|
||||||
RUN <<EOF
|
RUN <<EOF
|
||||||
|
if [ "$DBG" = 1 ]; then
|
||||||
|
go install github.com/go-delve/delve/cmd/dlv@latest
|
||||||
|
else
|
||||||
|
touch /go/bin/dlv
|
||||||
|
fi
|
||||||
EOF
|
EOF
|
||||||
RUN ./build-go.sh
|
RUN ./build-go.sh
|
||||||
|
|
||||||
# -- Run mautrix-signal --
|
# -- Run mautrix-signal --
|
||||||
FROM alpine:3.24
|
FROM alpine:3.22
|
||||||
|
|
||||||
ENV UID=1337 \
|
ENV UID=1337 \
|
||||||
GID=1337
|
GID=1337
|
||||||
|
|
@ -41,6 +48,11 @@ RUN apk add --no-cache ffmpeg su-exec ca-certificates bash jq curl yq-go olm
|
||||||
|
|
||||||
COPY --from=go-builder /build/mautrix-signal /usr/bin/mautrix-signal
|
COPY --from=go-builder /build/mautrix-signal /usr/bin/mautrix-signal
|
||||||
COPY --from=go-builder /build/docker-run.sh /docker-run.sh
|
COPY --from=go-builder /build/docker-run.sh /docker-run.sh
|
||||||
|
COPY --from=go-builder /go/bin/dlv /usr/bin/dlv
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
|
|
||||||
|
ARG DBG
|
||||||
|
ARG DBGWAIT=0
|
||||||
|
ENV DBG=${DBG} DBGWAIT=${DBGWAIT}
|
||||||
|
RUN echo "Debug mode: DBG=${DBG} DBGWAIT=${DBGWAIT}"
|
||||||
CMD ["/docker-run.sh"]
|
CMD ["/docker-run.sh"]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
ARG DOCKER_HUB="docker.io"
|
FROM alpine:3.22
|
||||||
|
|
||||||
FROM ${DOCKER_HUB}/alpine:3.24
|
|
||||||
|
|
||||||
ENV UID=1337 \
|
ENV UID=1337 \
|
||||||
GID=1337
|
GID=1337
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
* [x] Text
|
* [x] Text
|
||||||
* [x] Formatting
|
* [x] Formatting
|
||||||
* [x] Mentions
|
* [x] Mentions
|
||||||
* [x] Polls
|
|
||||||
* [x] Media
|
* [x] Media
|
||||||
* [x] Images
|
* [x] Images
|
||||||
* [x] Audio files
|
* [x] Audio files
|
||||||
|
|
@ -35,7 +34,6 @@
|
||||||
* [x] Text
|
* [x] Text
|
||||||
* [x] Formatting
|
* [x] Formatting
|
||||||
* [x] Mentions
|
* [x] Mentions
|
||||||
* [x] Polls
|
|
||||||
* [ ] Media
|
* [ ] Media
|
||||||
* [x] Images
|
* [x] Images
|
||||||
* [x] Voice notes
|
* [x] Voice notes
|
||||||
|
|
@ -67,8 +65,8 @@
|
||||||
* [ ] Delivery receipts (there's no good way to bridge these)
|
* [ ] Delivery receipts (there's no good way to bridge these)
|
||||||
* [x] Disappearing messages
|
* [x] Disappearing messages
|
||||||
* Misc
|
* Misc
|
||||||
* [x] Automatic portal creation
|
* [ ] Automatic portal creation
|
||||||
* [x] After login
|
* [ ] After login
|
||||||
* [x] When receiving message
|
* [x] When receiving message
|
||||||
* [x] Linking as secondary device
|
* [x] Linking as secondary device
|
||||||
* [ ] Registering as primary device
|
* [ ] Registering as primary device
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,9 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
BINARY_NAME=mautrix-signal go tool maubuild "$@"
|
MAUTRIX_VERSION=$(cat go.mod | grep 'maunium.net/go/mautrix ' | awk '{ print $2 }')
|
||||||
|
GO_LDFLAGS="-X main.Tag=$(git describe --exact-match --tags 2>/dev/null) -X main.Commit=$(git rev-parse HEAD) -X 'main.BuildTime=`date -Iseconds`' -X 'maunium.net/go/mautrix.GoModVersion=$MAUTRIX_VERSION'"
|
||||||
|
if [ "$DBG" = 1 ]; then
|
||||||
|
GO_GCFLAGS='all=-N -l'
|
||||||
|
else
|
||||||
|
GO_LDFLAGS="-s -w ${GO_LDFLAGS}"
|
||||||
|
fi
|
||||||
|
go build -gcflags="$GO_GCFLAGS" -ldflags="$GO_LDFLAGS" -o mautrix-signal "$@" ./cmd/mautrix-signal
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
# TODO fix linking with debug library
|
||||||
|
#if [ "$DBG" != 1 ]; then
|
||||||
|
# RUST_PROFILE=release
|
||||||
|
#else
|
||||||
|
# RUST_PROFILE=dev
|
||||||
|
#fi
|
||||||
|
RUST_PROFILE=release
|
||||||
git submodule update --init
|
git submodule update --init
|
||||||
cd pkg/libsignalgo/libsignal && RUSTFLAGS="-Ctarget-feature=-crt-static" RUSTC_WRAPPER="" cargo build -p libsignal-ffi --profile=release
|
cd pkg/libsignalgo/libsignal && RUSTFLAGS="-Ctarget-feature=-crt-static" RUSTC_WRAPPER="" cargo build -p libsignal-ffi --profile=$RUST_PROFILE
|
||||||
|
|
|
||||||
1
build.sh
1
build.sh
|
|
@ -1,5 +1,4 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
|
||||||
./build-rust.sh
|
./build-rust.sh
|
||||||
cp -f pkg/libsignalgo/libsignal/target/release/libsignal_ffi.a .
|
cp -f pkg/libsignalgo/libsignal/target/release/libsignal_ffi.a .
|
||||||
LIBRARY_PATH=.:$LIBRARY_PATH ./build-go.sh
|
LIBRARY_PATH=.:$LIBRARY_PATH ./build-go.sh
|
||||||
|
|
|
||||||
147
cmd/mautrix-signal/legacyprovision.go
Normal file
147
cmd/mautrix-signal/legacyprovision.go
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
// mautrix-signal - A Matrix-Signal puppeting bridge.
|
||||||
|
// Copyright (C) 2024 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 istributed 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"maunium.net/go/mautrix"
|
||||||
|
"maunium.net/go/mautrix/bridgev2"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
|
)
|
||||||
|
|
||||||
|
func legacyResolveIdentifierOrStartChat(w http.ResponseWriter, r *http.Request, create bool) {
|
||||||
|
login := m.Matrix.Provisioning.GetLoginForRequest(w, r)
|
||||||
|
if login == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
api := login.Client.(bridgev2.IdentifierResolvingNetworkAPI)
|
||||||
|
phonenum := r.PathValue("phonenum")
|
||||||
|
resp, err := api.ResolveIdentifier(r.Context(), phonenum, create)
|
||||||
|
if err != nil {
|
||||||
|
zerolog.Ctx(r.Context()).Err(err).Msg("Failed to resolve identifier")
|
||||||
|
JSONResponse(w, http.StatusInternalServerError, &Error{
|
||||||
|
Error: fmt.Sprintf("Failed to resolve identifier: %v", err),
|
||||||
|
ErrCode: "M_UNKNOWN",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
} else if resp == nil {
|
||||||
|
JSONResponse(w, http.StatusNotFound, &Error{
|
||||||
|
ErrCode: mautrix.MNotFound.ErrCode,
|
||||||
|
Error: "User not found on Signal",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
status := http.StatusOK
|
||||||
|
apiResp := &ResolveIdentifierResponse{
|
||||||
|
ChatID: ResolveIdentifierResponseChatID{
|
||||||
|
UUID: string(resp.UserID),
|
||||||
|
Number: phonenum,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if resp.Ghost != nil {
|
||||||
|
if resp.UserInfo != nil {
|
||||||
|
resp.Ghost.UpdateInfo(r.Context(), resp.UserInfo)
|
||||||
|
}
|
||||||
|
apiResp.OtherUser = &ResolveIdentifierResponseOtherUser{
|
||||||
|
MXID: resp.Ghost.Intent.GetMXID(),
|
||||||
|
DisplayName: resp.Ghost.Name,
|
||||||
|
AvatarURL: resp.Ghost.AvatarMXC.ParseOrIgnore(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if resp.Chat != nil {
|
||||||
|
if resp.Chat.Portal == nil {
|
||||||
|
resp.Chat.Portal, err = m.Bridge.GetPortalByKey(r.Context(), resp.Chat.PortalKey)
|
||||||
|
if err != nil {
|
||||||
|
zerolog.Ctx(r.Context()).Err(err).Msg("Failed to get portal")
|
||||||
|
JSONResponse(w, http.StatusInternalServerError, &mautrix.RespError{
|
||||||
|
Err: "Failed to get portal",
|
||||||
|
ErrCode: "M_UNKNOWN",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if create && resp.Chat.Portal.MXID == "" {
|
||||||
|
apiResp.JustCreated = true
|
||||||
|
status = http.StatusCreated
|
||||||
|
err = resp.Chat.Portal.CreateMatrixRoom(r.Context(), login, resp.Chat.PortalInfo)
|
||||||
|
if err != nil {
|
||||||
|
zerolog.Ctx(r.Context()).Err(err).Msg("Failed to create portal room")
|
||||||
|
JSONResponse(w, http.StatusInternalServerError, &mautrix.RespError{
|
||||||
|
Err: "Failed to create portal room",
|
||||||
|
ErrCode: "M_UNKNOWN",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
apiResp.RoomID = resp.Chat.Portal.MXID
|
||||||
|
}
|
||||||
|
JSONResponse(w, status, &Response{
|
||||||
|
Success: true,
|
||||||
|
Status: "ok",
|
||||||
|
ResolveIdentifierResponse: apiResp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func legacyProvResolveIdentifier(w http.ResponseWriter, r *http.Request) {
|
||||||
|
legacyResolveIdentifierOrStartChat(w, r, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func legacyProvPM(w http.ResponseWriter, r *http.Request) {
|
||||||
|
legacyResolveIdentifierOrStartChat(w, r, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func JSONResponse(w http.ResponseWriter, status int, response any) {
|
||||||
|
w.Header().Add("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(status)
|
||||||
|
_ = json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
ErrCode string `json:"errcode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
|
||||||
|
// For response in ResolveIdentifier
|
||||||
|
*ResolveIdentifierResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResolveIdentifierResponse struct {
|
||||||
|
RoomID id.RoomID `json:"room_id"`
|
||||||
|
ChatID ResolveIdentifierResponseChatID `json:"chat_id"`
|
||||||
|
JustCreated bool `json:"just_created"`
|
||||||
|
OtherUser *ResolveIdentifierResponseOtherUser `json:"other_user,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResolveIdentifierResponseChatID struct {
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
Number string `json:"number"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResolveIdentifierResponseOtherUser struct {
|
||||||
|
MXID id.UserID `json:"mxid"`
|
||||||
|
DisplayName string `json:"displayname"`
|
||||||
|
AvatarURL id.ContentURI `json:"avatar_url"`
|
||||||
|
}
|
||||||
|
|
@ -17,12 +17,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"maunium.net/go/mautrix/bridgev2/matrix/mxmain"
|
"maunium.net/go/mautrix/bridgev2/matrix/mxmain"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/connector"
|
"go.mau.fi/mautrix-signal/pkg/connector"
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/web"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Information to find out exactly which commit the bridge was built from.
|
// Information to find out exactly which commit the bridge was built from.
|
||||||
|
|
@ -37,14 +34,18 @@ var m = mxmain.BridgeMain{
|
||||||
Name: "mautrix-signal",
|
Name: "mautrix-signal",
|
||||||
URL: "https://github.com/mautrix/signal",
|
URL: "https://github.com/mautrix/signal",
|
||||||
Description: "A Matrix-Signal puppeting bridge.",
|
Description: "A Matrix-Signal puppeting bridge.",
|
||||||
Version: "26.10",
|
Version: "0.8.7",
|
||||||
SemCalVer: true,
|
|
||||||
|
|
||||||
Connector: &connector.SignalConnector{},
|
Connector: &connector.SignalConnector{},
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
web.UserAgent = fmt.Sprintf("mautrix-signal/%s %s", m.Version, web.BaseUserAgent)
|
m.PostStart = func() {
|
||||||
|
if m.Matrix.Provisioning != nil {
|
||||||
|
m.Matrix.Provisioning.Router.HandleFunc("GET /v2/resolve_identifier/{phonenum}", legacyProvResolveIdentifier)
|
||||||
|
m.Matrix.Provisioning.Router.HandleFunc("POST /v2/pm/{phonenum}", legacyProvPM)
|
||||||
|
}
|
||||||
|
}
|
||||||
m.InitVersion(Tag, Commit, BuildTime)
|
m.InitVersion(Tag, Commit, BuildTime)
|
||||||
m.Run()
|
m.Run()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
51
go.mod
51
go.mod
|
|
@ -1,51 +1,48 @@
|
||||||
module go.mau.fi/mautrix-signal
|
module go.mau.fi/mautrix-signal
|
||||||
|
|
||||||
go 1.26.0
|
go 1.24.0
|
||||||
|
|
||||||
toolchain go1.27.2
|
toolchain go1.25.1
|
||||||
|
|
||||||
tool go.mau.fi/util/cmd/maubuild
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/coder/websocket v1.8.15
|
github.com/coder/websocket v1.8.14
|
||||||
github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff
|
github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/mattn/go-pointer v0.0.1
|
github.com/mattn/go-pointer v0.0.1
|
||||||
github.com/rs/zerolog v1.35.1
|
github.com/rs/zerolog v1.34.0
|
||||||
github.com/stretchr/testify v1.12.0
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/tidwall/gjson v1.20.0
|
github.com/tidwall/gjson v1.18.0
|
||||||
go.mau.fi/util v0.11.0
|
go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10
|
||||||
golang.org/x/crypto v0.58.0
|
golang.org/x/crypto v0.42.0
|
||||||
golang.org/x/exp v0.0.0-20261014143641-f0251eb4aa3e
|
golang.org/x/exp v0.0.0-20250911091902-df9299821621
|
||||||
golang.org/x/net v0.61.0
|
golang.org/x/net v0.44.0
|
||||||
golang.org/x/sync v0.24.0
|
google.golang.org/protobuf v1.36.9
|
||||||
google.golang.org/grpc v1.84.0
|
|
||||||
google.golang.org/protobuf v1.36.12
|
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
maunium.net/go/mautrix v0.32.1-0.20261026145030-920bc073ba8d
|
maunium.net/go/mautrix v0.25.2-0.20251001115535-dd778ae0cdaf
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.2.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/coreos/go-systemd/v22 v22.7.0 // indirect
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/lib/pq v1.12.3 // indirect
|
github.com/lib/pq v1.10.9 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.52 // indirect
|
github.com/mattn/go-sqlite3 v1.14.32 // indirect
|
||||||
github.com/petermattis/goid v0.0.0-20260918085751-abfca077860b // indirect
|
github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||||
github.com/rs/xid v1.6.0 // indirect
|
github.com/rs/xid v1.6.0 // indirect
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
||||||
github.com/tidwall/match v1.2.0 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
github.com/tidwall/sjson v1.2.5 // indirect
|
github.com/tidwall/sjson v1.2.5 // indirect
|
||||||
github.com/yuin/goldmark v1.8.6 // indirect
|
github.com/yuin/goldmark v1.7.13 // indirect
|
||||||
go.mau.fi/zeroconfig v0.2.0 // indirect
|
go.mau.fi/zeroconfig v0.2.0 // indirect
|
||||||
golang.org/x/mod v0.42.0 // indirect
|
golang.org/x/sync v0.17.0 // indirect
|
||||||
golang.org/x/sys v0.49.0 // indirect
|
golang.org/x/sys v0.36.0 // indirect
|
||||||
golang.org/x/text v0.43.0 // indirect
|
golang.org/x/text v0.29.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
maunium.net/go/mauflag v1.0.0 // indirect
|
maunium.net/go/mauflag v1.0.0 // indirect
|
||||||
|
|
|
||||||
104
go.sum
104
go.sum
|
|
@ -1,18 +1,19 @@
|
||||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||||
github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA=
|
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||||
github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||||
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
|
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||||
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff h1:4N8wnS3f1hNHSmFD5zgFkWCyA4L1kCDkImPAtK7D6tg=
|
github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff h1:4N8wnS3f1hNHSmFD5zgFkWCyA4L1kCDkImPAtK7D6tg=
|
||||||
github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
|
@ -22,70 +23,69 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0=
|
github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0=
|
||||||
github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc=
|
github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc=
|
||||||
github.com/mattn/go-sqlite3 v1.14.52 h1:wVbm2Qnf4OXkqhBTSPuCRZDRnxfbVrrmiCEroVdog8U=
|
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||||
github.com/mattn/go-sqlite3 v1.14.52/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=
|
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/petermattis/goid v0.0.0-20260918085751-abfca077860b h1:OzNsuVdSWGwXvWKTtChx9ve89k4cFJ6NxYM4Aw4/f+E=
|
github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490 h1:QTvNkZ5ylY0PGgA+Lih+GdboMLY/G9SEGLMEGVjTVA4=
|
||||||
github.com/petermattis/goid v0.0.0-20260918085751-abfca077860b/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
|
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||||
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||||
github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/gjson v1.20.0 h1:+agJ3rEzKcCXKDKo0ml26UROcpcAlnZyjq+TjbY5Fto=
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||||
github.com/tidwall/gjson v1.20.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
|
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
|
||||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
|
||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||||
github.com/yuin/goldmark v1.8.6 h1:d0VcaP1sx9GkFVkoW+KtggpGi2KZ965i14b0+bDQST4=
|
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||||
github.com/yuin/goldmark v1.8.6/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||||
go.mau.fi/util v0.11.0 h1:JNEpkVRxfTdJUC0DIGKiRlLKzl4G/qLX/eVzoP7QuwY=
|
go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10 h1:EvX/di02gOriKN0xGDJuQ5mgiNdAF4LJc8moffI7Svo=
|
||||||
go.mau.fi/util v0.11.0/go.mod h1:04AEFVsc4zNdIkDC1gkdqbg/nZFolHY50pyXh2DSy7s=
|
go.mau.fi/util v0.9.2-0.20251001114608-d99877b9cc10/go.mod h1:M0bM9SyaOWJniaHs9hxEzz91r5ql6gYq6o1q5O1SsjQ=
|
||||||
go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU=
|
go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU=
|
||||||
go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w=
|
go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w=
|
||||||
golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54=
|
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||||
golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w=
|
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||||
golang.org/x/exp v0.0.0-20261014143641-f0251eb4aa3e h1:HgI6W1OolKK6tcV74is+rl9SMT0njsHlp0TbQiGXI/A=
|
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
|
||||||
golang.org/x/exp v0.0.0-20261014143641-f0251eb4aa3e/go.mod h1:DBoxwWhjaW8DJJw/YeM4zpeOKMC1FMXSblsYy+1mFSw=
|
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
|
||||||
golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg=
|
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||||
golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs=
|
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||||
golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw=
|
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||||
golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
|
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||||
golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc=
|
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s=
|
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 h1:qEHAMpSaUhtD0p3NbEEI83HwNGFxEwaSJ1G9PLnCBZE=
|
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
|
||||||
google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0=
|
|
||||||
google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0=
|
|
||||||
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 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
|
@ -95,5 +95,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
|
maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
|
||||||
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
|
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
|
||||||
maunium.net/go/mautrix v0.32.1-0.20261026145030-920bc073ba8d h1:DsMn0i7cBKcQZ0AVxJm9hfWAu6xJdjRrf457EtXxyNY=
|
maunium.net/go/mautrix v0.25.2-0.20251001115535-dd778ae0cdaf h1:prmIYgiziW4A8H2v/TliQ7fis8uTWblabxyPIeLFlNg=
|
||||||
maunium.net/go/mautrix v0.32.1-0.20261026145030-920bc073ba8d/go.mod h1:tZSwPzw+/0pa0dxGufkh0uJqM84BNxB6e2I0+S0UyjU=
|
maunium.net/go/mautrix v0.25.2-0.20251001115535-dd778ae0cdaf/go.mod h1:eWXuX2UAGye4AU7i/8Fv2L2Nh7L9kZtuv3R0O0n1KaM=
|
||||||
|
|
|
||||||
|
|
@ -77,8 +77,8 @@ func (s *SignalClient) FetchMessages(ctx context.Context, params bridgev2.FetchM
|
||||||
}
|
}
|
||||||
// GetBackupChatItems returns in reverse chronological order, so flip the list
|
// GetBackupChatItems returns in reverse chronological order, so flip the list
|
||||||
slices.Reverse(items)
|
slices.Reverse(items)
|
||||||
|
var firstDirectionfulProcessed bool
|
||||||
var isRead bool
|
var isRead bool
|
||||||
var readUpTo time.Time
|
|
||||||
convertedMessages := make([]*bridgev2.BackfillMessage, 0, len(items))
|
convertedMessages := make([]*bridgev2.BackfillMessage, 0, len(items))
|
||||||
attMap := make(msgconv.AttachmentMap)
|
attMap := make(msgconv.AttachmentMap)
|
||||||
recipientMap := make(map[uint64]*backuppb.Recipient)
|
recipientMap := make(map[uint64]*backuppb.Recipient)
|
||||||
|
|
@ -123,17 +123,20 @@ func (s *SignalClient) FetchMessages(ctx context.Context, params bridgev2.FetchM
|
||||||
case *backuppb.ChatItem_Incoming:
|
case *backuppb.ChatItem_Incoming:
|
||||||
streamOrder = int64(dt.Incoming.GetDateServerSent())
|
streamOrder = int64(dt.Incoming.GetDateServerSent())
|
||||||
prevStreamOrder = streamOrder
|
prevStreamOrder = streamOrder
|
||||||
isRead = dt.Incoming.Read
|
if !firstDirectionfulProcessed {
|
||||||
if isRead {
|
firstDirectionfulProcessed = true
|
||||||
readUpTo = time.UnixMilli(int64(item.DateSent))
|
isRead = dt.Incoming.Read
|
||||||
}
|
}
|
||||||
case *backuppb.ChatItem_Outgoing:
|
case *backuppb.ChatItem_Outgoing:
|
||||||
streamOrder = int64(item.GetDateSent())
|
streamOrder = int64(item.GetDateSent())
|
||||||
// Ensure stream order is higher than previous incoming item, but lower than next incoming item
|
// Ensure stream order is higher than previous incoming item, but lower than next incoming item
|
||||||
streamOrder = min(streamOrder, findNextStreamOrder(i+1)-1)
|
streamOrder = min(streamOrder, findNextStreamOrder(i+1)-1)
|
||||||
streamOrder = max(streamOrder, prevStreamOrder+1)
|
streamOrder = max(streamOrder, prevStreamOrder+1)
|
||||||
isRead = true
|
|
||||||
readUpTo = time.UnixMilli(int64(item.DateSent))
|
if !firstDirectionfulProcessed {
|
||||||
|
firstDirectionfulProcessed = true
|
||||||
|
isRead = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(attMap) > 0 {
|
if len(attMap) > 0 {
|
||||||
clear(attMap)
|
clear(attMap)
|
||||||
|
|
@ -148,7 +151,7 @@ func (s *SignalClient) FetchMessages(ctx context.Context, params bridgev2.FetchM
|
||||||
if dm == nil {
|
if dm == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cm := s.Main.MsgConv.ToMatrix(ctx, s.Client, params.Portal, senderACI, s.Main.Bridge.Bot, dm, attMap)
|
cm := s.Main.MsgConv.ToMatrix(ctx, s.Client, params.Portal, s.Main.Bridge.Bot, dm, attMap)
|
||||||
convertedReactions := make([]*bridgev2.BackfillReaction, 0, len(reactions))
|
convertedReactions := make([]*bridgev2.BackfillReaction, 0, len(reactions))
|
||||||
for _, reaction := range reactions {
|
for _, reaction := range reactions {
|
||||||
reactionSenderACI, err := getRecipientACI(reaction.AuthorId)
|
reactionSenderACI, err := getRecipientACI(reaction.AuthorId)
|
||||||
|
|
@ -175,20 +178,16 @@ func (s *SignalClient) FetchMessages(ctx context.Context, params bridgev2.FetchM
|
||||||
Reactions: convertedReactions,
|
Reactions: convertedReactions,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if isRead {
|
|
||||||
readUpTo = time.Time{}
|
|
||||||
}
|
|
||||||
return &bridgev2.FetchMessagesResponse{
|
return &bridgev2.FetchMessagesResponse{
|
||||||
Messages: convertedMessages,
|
Messages: convertedMessages,
|
||||||
HasMore: len(items) >= params.Count,
|
HasMore: len(items) >= params.Count,
|
||||||
Forward: params.Forward,
|
Forward: params.Forward,
|
||||||
MarkRead: isRead,
|
MarkRead: isRead,
|
||||||
ReadUpTo: readUpTo,
|
|
||||||
ApproxTotalCount: chat.TotalMessages,
|
ApproxTotalCount: chat.TotalMessages,
|
||||||
CompleteCallback: func() {
|
CompleteCallback: func() {
|
||||||
// When reaching the last backwards backfill batch, delete the chat from the backup store.
|
// When reaching the last backwards backfill batch, delete the chat from the backup store.
|
||||||
// If backwards backfilling isn't enabled, delete immediately after the first backfill request.
|
// If backwards backfilling isn't enabled, delete immediately after the first backfill request.
|
||||||
if (!params.Forward && len(items) < params.Count) || !s.Main.Bridge.Config.Backfill.Queue.AnyEnabled() {
|
if (!params.Forward && len(items) < params.Count) || (!s.Main.Bridge.Config.Backfill.Queue.Enabled && !s.Main.Bridge.Config.Backfill.WillPaginateManually) {
|
||||||
err := s.Client.Store.BackupStore.DeleteBackupChat(ctx, chat.Id)
|
err := s.Client.Store.BackupStore.DeleteBackupChat(ctx, chat.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to delete chat from backup store")
|
zerolog.Ctx(ctx).Err(err).Msg("Failed to delete chat from backup store")
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,10 @@ import (
|
||||||
"go.mau.fi/util/ffmpeg"
|
"go.mau.fi/util/ffmpeg"
|
||||||
"go.mau.fi/util/jsontime"
|
"go.mau.fi/util/jsontime"
|
||||||
"go.mau.fi/util/ptr"
|
"go.mau.fi/util/ptr"
|
||||||
|
|
||||||
"maunium.net/go/mautrix/bridgev2"
|
"maunium.net/go/mautrix/bridgev2"
|
||||||
"maunium.net/go/mautrix/bridgev2/database"
|
|
||||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func supportedIfFFmpeg() event.CapabilitySupportLevel {
|
func supportedIfFFmpeg() event.CapabilitySupportLevel {
|
||||||
|
|
@ -40,7 +37,7 @@ func supportedIfFFmpeg() event.CapabilitySupportLevel {
|
||||||
}
|
}
|
||||||
|
|
||||||
func capID() string {
|
func capID() string {
|
||||||
base := "fi.mau.signal.capabilities.2026_10_21"
|
base := "fi.mau.signal.capabilities.2025_08_25"
|
||||||
if ffmpeg.Supported() {
|
if ffmpeg.Supported() {
|
||||||
return base + "+ffmpeg"
|
return base + "+ffmpeg"
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +45,7 @@ func capID() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
const MaxFileSize = 100 * 1024 * 1024
|
const MaxFileSize = 100 * 1024 * 1024
|
||||||
const MaxTextLength = 64 * 1024
|
const MaxTextLength = 2000
|
||||||
|
|
||||||
var signalCaps = &event.RoomFeatures{
|
var signalCaps = &event.RoomFeatures{
|
||||||
ID: capID(),
|
ID: capID(),
|
||||||
|
|
@ -79,11 +76,9 @@ var signalCaps = &event.RoomFeatures{
|
||||||
"image/jpeg": event.CapLevelFullySupported,
|
"image/jpeg": event.CapLevelFullySupported,
|
||||||
"image/webp": event.CapLevelFullySupported,
|
"image/webp": event.CapLevelFullySupported,
|
||||||
"image/bmp": event.CapLevelFullySupported,
|
"image/bmp": event.CapLevelFullySupported,
|
||||||
"image/avif": event.CapLevelFullySupported,
|
|
||||||
},
|
},
|
||||||
MaxWidth: 4096,
|
MaxWidth: 4096,
|
||||||
MaxHeight: 4096,
|
MaxHeight: 4096,
|
||||||
ViewLimitedTypes: []*event.BeeperViewLimitedMedia{{Type: "count", Count: 1}},
|
|
||||||
MaxSize: MaxFileSize,
|
MaxSize: MaxFileSize,
|
||||||
Caption: event.CapLevelFullySupported,
|
Caption: event.CapLevelFullySupported,
|
||||||
MaxCaptionLength: MaxTextLength,
|
MaxCaptionLength: MaxTextLength,
|
||||||
|
|
@ -94,7 +89,6 @@ var signalCaps = &event.RoomFeatures{
|
||||||
"video/ogg": event.CapLevelFullySupported,
|
"video/ogg": event.CapLevelFullySupported,
|
||||||
"video/webm": event.CapLevelFullySupported,
|
"video/webm": event.CapLevelFullySupported,
|
||||||
},
|
},
|
||||||
ViewLimitedTypes: []*event.BeeperViewLimitedMedia{{Type: "count", Count: 1}},
|
|
||||||
MaxSize: MaxFileSize,
|
MaxSize: MaxFileSize,
|
||||||
Caption: event.CapLevelFullySupported,
|
Caption: event.CapLevelFullySupported,
|
||||||
MaxCaptionLength: MaxTextLength,
|
MaxCaptionLength: MaxTextLength,
|
||||||
|
|
@ -103,10 +97,6 @@ var signalCaps = &event.RoomFeatures{
|
||||||
MimeTypes: map[string]event.CapabilitySupportLevel{
|
MimeTypes: map[string]event.CapabilitySupportLevel{
|
||||||
"audio/aac": event.CapLevelFullySupported,
|
"audio/aac": event.CapLevelFullySupported,
|
||||||
"audio/mpeg": event.CapLevelFullySupported,
|
"audio/mpeg": event.CapLevelFullySupported,
|
||||||
"audio/mp3": event.CapLevelFullySupported,
|
|
||||||
"audio/flac": event.CapLevelFullySupported,
|
|
||||||
"audio/mp4": event.CapLevelFullySupported,
|
|
||||||
"audio/wav": event.CapLevelFullySupported,
|
|
||||||
},
|
},
|
||||||
MaxSize: MaxFileSize,
|
MaxSize: MaxFileSize,
|
||||||
},
|
},
|
||||||
|
|
@ -120,8 +110,7 @@ var signalCaps = &event.RoomFeatures{
|
||||||
},
|
},
|
||||||
event.CapMsgSticker: {
|
event.CapMsgSticker: {
|
||||||
MimeTypes: map[string]event.CapabilitySupportLevel{
|
MimeTypes: map[string]event.CapabilitySupportLevel{
|
||||||
// Signal clients will only render static webp, so apng is preferred
|
"image/webp": event.CapLevelFullySupported,
|
||||||
"image/webp": event.CapLevelPartialSupport,
|
|
||||||
"image/png": event.CapLevelFullySupported,
|
"image/png": event.CapLevelFullySupported,
|
||||||
"image/apng": event.CapLevelFullySupported,
|
"image/apng": event.CapLevelFullySupported,
|
||||||
"image/gif": supportedIfFFmpeg(),
|
"image/gif": supportedIfFFmpeg(),
|
||||||
|
|
@ -147,36 +136,18 @@ var signalCaps = &event.RoomFeatures{
|
||||||
MaxSize: MaxFileSize,
|
MaxSize: MaxFileSize,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
State: event.StateFeatureMap{
|
MaxTextLength: MaxTextLength, // TODO support arbitrary sized text messages with files
|
||||||
event.StateRoomName.Type: {Level: event.CapLevelFullySupported},
|
LocationMessage: event.CapLevelPartialSupport,
|
||||||
event.StateRoomAvatar.Type: {Level: event.CapLevelFullySupported},
|
Poll: event.CapLevelRejected,
|
||||||
event.StateTopic.Type: {Level: event.CapLevelFullySupported},
|
Thread: event.CapLevelUnsupported,
|
||||||
event.StateBeeperDisappearingTimer.Type: {Level: event.CapLevelFullySupported},
|
Reply: event.CapLevelFullySupported,
|
||||||
},
|
Edit: event.CapLevelFullySupported,
|
||||||
MemberActions: event.MemberFeatureMap{
|
EditMaxCount: 10,
|
||||||
event.MemberActionInvite: event.CapLevelFullySupported,
|
EditMaxAge: ptr.Ptr(jsontime.S(24 * time.Hour)),
|
||||||
event.MemberActionRevokeInvite: event.CapLevelFullySupported,
|
Delete: event.CapLevelFullySupported,
|
||||||
event.MemberActionLeave: event.CapLevelFullySupported,
|
DeleteForMe: false,
|
||||||
event.MemberActionBan: event.CapLevelFullySupported,
|
DeleteMaxAge: ptr.Ptr(jsontime.S(24 * time.Hour)),
|
||||||
event.MemberActionKick: event.CapLevelFullySupported,
|
DisappearingTimer: signalDisappearingCap,
|
||||||
},
|
|
||||||
MaxTextLength: MaxTextLength,
|
|
||||||
LocationMessage: event.CapLevelPartialSupport,
|
|
||||||
Poll: event.CapLevelFullySupported,
|
|
||||||
PollEnd: event.CapLevelUnsupported,
|
|
||||||
PollHiddenVotes: event.CapLevelUnsupported,
|
|
||||||
PollDuplicateOptions: event.CapLevelFullySupported,
|
|
||||||
PollMaxOptions: 10,
|
|
||||||
PollOptionMaxLength: 100,
|
|
||||||
Thread: event.CapLevelUnsupported,
|
|
||||||
Reply: event.CapLevelFullySupported,
|
|
||||||
Edit: event.CapLevelFullySupported,
|
|
||||||
EditMaxCount: 10,
|
|
||||||
EditMaxAge: ptr.Ptr(jsontime.S(24 * time.Hour)),
|
|
||||||
Delete: event.CapLevelFullySupported,
|
|
||||||
DeleteForMe: false,
|
|
||||||
DeleteMaxAge: ptr.Ptr(jsontime.S(24 * time.Hour)),
|
|
||||||
DisappearingTimer: signalDisappearingCap,
|
|
||||||
|
|
||||||
Reaction: event.CapLevelFullySupported,
|
Reaction: event.CapLevelFullySupported,
|
||||||
ReactionCount: 1,
|
ReactionCount: 1,
|
||||||
|
|
@ -184,12 +155,6 @@ var signalCaps = &event.RoomFeatures{
|
||||||
CustomEmojiReactions: false,
|
CustomEmojiReactions: false,
|
||||||
ReadReceipts: true,
|
ReadReceipts: true,
|
||||||
TypingNotifications: true,
|
TypingNotifications: true,
|
||||||
|
|
||||||
DeleteChat: true,
|
|
||||||
MessageRequest: &event.MessageRequestFeatures{
|
|
||||||
AcceptWithMessage: event.CapLevelPartialSupport,
|
|
||||||
AcceptWithButton: event.CapLevelFullySupported,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var signalDisappearingCap = &event.DisappearingTimerCapability{
|
var signalDisappearingCap = &event.DisappearingTimerCapability{
|
||||||
|
|
@ -197,26 +162,9 @@ var signalDisappearingCap = &event.DisappearingTimerCapability{
|
||||||
}
|
}
|
||||||
|
|
||||||
var signalCapsNoteToSelf *event.RoomFeatures
|
var signalCapsNoteToSelf *event.RoomFeatures
|
||||||
var signalCapsDM *event.RoomFeatures
|
|
||||||
var signalCapsPNIDM *event.RoomFeatures
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
signalCapsDM = ptr.Clone(signalCaps)
|
signalCapsNoteToSelf = ptr.Clone(signalCaps)
|
||||||
signalCapsDM.ID = capID() + "+dm"
|
|
||||||
signalCapsDM.MemberActions = nil
|
|
||||||
signalCapsDM.State = event.StateFeatureMap{
|
|
||||||
event.StateBeeperDisappearingTimer.Type: {Level: event.CapLevelFullySupported},
|
|
||||||
}
|
|
||||||
|
|
||||||
signalCapsPNIDM = ptr.Clone(signalCapsDM)
|
|
||||||
signalCapsPNIDM.ID += "+pni"
|
|
||||||
|
|
||||||
signalCapsDM.BlockUser = true
|
|
||||||
signalCapsDM.ReportSpam = true
|
|
||||||
|
|
||||||
signalCapsNoteToSelf = ptr.Clone(signalCapsDM)
|
|
||||||
signalCapsNoteToSelf.BlockUser = false
|
|
||||||
signalCapsNoteToSelf.ReportSpam = false
|
|
||||||
signalCapsNoteToSelf.EditMaxAge = nil
|
signalCapsNoteToSelf.EditMaxAge = nil
|
||||||
signalCapsNoteToSelf.DeleteMaxAge = nil
|
signalCapsNoteToSelf.DeleteMaxAge = nil
|
||||||
signalCapsNoteToSelf.ID = capID() + "+note_to_self"
|
signalCapsNoteToSelf.ID = capID() + "+note_to_self"
|
||||||
|
|
@ -225,12 +173,6 @@ func init() {
|
||||||
func (s *SignalClient) GetCapabilities(ctx context.Context, portal *bridgev2.Portal) *event.RoomFeatures {
|
func (s *SignalClient) GetCapabilities(ctx context.Context, portal *bridgev2.Portal) *event.RoomFeatures {
|
||||||
if portal.Receiver == s.UserLogin.ID && portal.ID == networkid.PortalID(s.UserLogin.ID) {
|
if portal.Receiver == s.UserLogin.ID && portal.ID == networkid.PortalID(s.UserLogin.ID) {
|
||||||
return signalCapsNoteToSelf
|
return signalCapsNoteToSelf
|
||||||
} else if portal.RoomType == database.RoomTypeDM {
|
|
||||||
userID, _, _ := signalid.ParsePortalID(portal.ID)
|
|
||||||
if userID.Type == libsignalgo.ServiceIDTypePNI {
|
|
||||||
return signalCapsPNIDM
|
|
||||||
}
|
|
||||||
return signalCapsDM
|
|
||||||
}
|
}
|
||||||
return signalCaps
|
return signalCaps
|
||||||
}
|
}
|
||||||
|
|
@ -240,11 +182,10 @@ var signalGeneralCaps = &bridgev2.NetworkGeneralCapabilities{
|
||||||
AggressiveUpdateInfo: true,
|
AggressiveUpdateInfo: true,
|
||||||
ImplicitReadReceipts: true,
|
ImplicitReadReceipts: true,
|
||||||
Provisioning: bridgev2.ProvisioningCapabilities{
|
Provisioning: bridgev2.ProvisioningCapabilities{
|
||||||
ImagePackImport: true,
|
|
||||||
ResolveIdentifier: bridgev2.ResolveIdentifierCapabilities{
|
ResolveIdentifier: bridgev2.ResolveIdentifierCapabilities{
|
||||||
CreateDM: true,
|
CreateDM: true,
|
||||||
LookupPhone: true,
|
LookupPhone: true,
|
||||||
LookupUsername: true,
|
LookupUsername: false, // TODO implement
|
||||||
ContactList: true,
|
ContactList: true,
|
||||||
},
|
},
|
||||||
GroupCreation: map[string]bridgev2.GroupTypeCapabilities{
|
GroupCreation: map[string]bridgev2.GroupTypeCapabilities{
|
||||||
|
|
@ -265,5 +206,5 @@ func (s *SignalConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilities
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalConnector) GetBridgeInfoVersion() (info, capabilities int) {
|
func (s *SignalConnector) GetBridgeInfoVersion() (info, capabilities int) {
|
||||||
return 1, 15
|
return 1, 5
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,16 +45,13 @@ var (
|
||||||
_ bridgev2.IdentifierResolvingNetworkAPI = (*SignalClient)(nil)
|
_ bridgev2.IdentifierResolvingNetworkAPI = (*SignalClient)(nil)
|
||||||
_ bridgev2.GroupCreatingNetworkAPI = (*SignalClient)(nil)
|
_ bridgev2.GroupCreatingNetworkAPI = (*SignalClient)(nil)
|
||||||
_ bridgev2.ContactListingNetworkAPI = (*SignalClient)(nil)
|
_ bridgev2.ContactListingNetworkAPI = (*SignalClient)(nil)
|
||||||
_ bridgev2.GhostDMCreatingNetworkAPI = (*SignalClient)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ bridgev2.IdentifierValidatingNetwork = (*SignalConnector)(nil)
|
|
||||||
|
|
||||||
const PrivateChatTopic = "Signal private chat"
|
const PrivateChatTopic = "Signal private chat"
|
||||||
const NoteToSelfName = "Signal Note to Self"
|
const NoteToSelfName = "Signal Note to Self"
|
||||||
|
|
||||||
func (s *SignalClient) GetUserInfoWithRefreshAfter(ctx context.Context, ghost *bridgev2.Ghost, refreshAfter time.Duration) (*bridgev2.UserInfo, error) {
|
func (s *SignalClient) GetUserInfoWithRefreshAfter(ctx context.Context, ghost *bridgev2.Ghost, refreshAfter time.Duration) (*bridgev2.UserInfo, error) {
|
||||||
userID, err := signalid.ParseUserIDAsServiceID(ghost.ID)
|
userID, err := signalid.ParseUserID(ghost.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -62,20 +59,15 @@ func (s *SignalClient) GetUserInfoWithRefreshAfter(ctx context.Context, ghost *b
|
||||||
// Don't do unnecessary fetches in background mode
|
// Don't do unnecessary fetches in background mode
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
var contact *types.Recipient
|
contact, err := s.Client.ContactByACIWithRefreshAfter(ctx, userID, refreshAfter)
|
||||||
if userID.Type == libsignalgo.ServiceIDTypePNI {
|
|
||||||
contact, err = s.Client.Store.RecipientStore.LoadAndUpdateRecipient(ctx, uuid.Nil, userID.UUID, nil)
|
|
||||||
} else {
|
|
||||||
contact, err = s.Client.ContactByACIWithRefreshAfter(ctx, userID.UUID, refreshAfter)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
meta := ghost.Metadata.(*signalid.GhostMetadata)
|
meta := ghost.Metadata.(*signalid.GhostMetadata)
|
||||||
if userID.Type != libsignalgo.ServiceIDTypePNI && (!s.Main.Config.UseOutdatedProfiles && meta.ProfileFetchedAt.After(contact.Profile.FetchedAt)) {
|
if !s.Main.Config.UseOutdatedProfiles && meta.ProfileFetchedAt.After(contact.Profile.FetchedAt) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return s.contactToUserInfo(ctx, contact, meta.Username, false)
|
return s.contactToUserInfo(ctx, contact)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost) (*bridgev2.UserInfo, error) {
|
func (s *SignalClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost) (*bridgev2.UserInfo, error) {
|
||||||
|
|
@ -99,7 +91,7 @@ func (s *SignalClient) GetChatInfo(ctx context.Context, portal *bridgev2.Portal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) contactToUserInfo(ctx context.Context, contact *types.Recipient, username string, saveUsername bool) (*bridgev2.UserInfo, error) {
|
func (s *SignalClient) contactToUserInfo(ctx context.Context, contact *types.Recipient) (*bridgev2.UserInfo, error) {
|
||||||
isBot := false
|
isBot := false
|
||||||
ui := &bridgev2.UserInfo{
|
ui := &bridgev2.UserInfo{
|
||||||
IsBot: &isBot,
|
IsBot: &isBot,
|
||||||
|
|
@ -110,25 +102,17 @@ func (s *SignalClient) contactToUserInfo(ctx context.Context, contact *types.Rec
|
||||||
changed = meta.ProfileFetchedAt.IsZero() && !contact.Profile.FetchedAt.IsZero()
|
changed = meta.ProfileFetchedAt.IsZero() && !contact.Profile.FetchedAt.IsZero()
|
||||||
meta.ProfileFetchedAt.Time = contact.Profile.FetchedAt
|
meta.ProfileFetchedAt.Time = contact.Profile.FetchedAt
|
||||||
}
|
}
|
||||||
if saveUsername && username != "" && meta.Username != username {
|
return false
|
||||||
meta.Username = username
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if contact.E164 != "" {
|
if contact.E164 != "" {
|
||||||
ui.Identifiers = append(ui.Identifiers, "tel:"+contact.E164)
|
ui.Identifiers = append(ui.Identifiers, "tel:"+contact.E164)
|
||||||
}
|
}
|
||||||
if username != "" {
|
name := s.Main.Config.FormatDisplayname(contact)
|
||||||
ui.Identifiers = append(ui.Identifiers, "signal:"+username)
|
|
||||||
}
|
|
||||||
name := s.Main.Config.FormatDisplayname(contact, username)
|
|
||||||
ui.Name = &name
|
ui.Name = &name
|
||||||
contactAvatarID := networkid.AvatarID("hash:" + contact.ContactAvatar.Hash)
|
if s.Main.Config.UseContactAvatars && contact.ContactAvatar.Hash != "" {
|
||||||
if s.Main.Config.UseContactAvatars && contact.ContactAvatar.Hash != "" && s.canUseContactAvatar(ctx, contact, contactAvatarID) {
|
|
||||||
ui.Avatar = &bridgev2.Avatar{
|
ui.Avatar = &bridgev2.Avatar{
|
||||||
ID: contactAvatarID,
|
ID: networkid.AvatarID("hash:" + contact.ContactAvatar.Hash),
|
||||||
Get: func(ctx context.Context) ([]byte, error) {
|
Get: func(ctx context.Context) ([]byte, error) {
|
||||||
if contact.ContactAvatar.Image == nil {
|
if contact.ContactAvatar.Image == nil {
|
||||||
return nil, fmt.Errorf("contact avatar not available")
|
return nil, fmt.Errorf("contact avatar not available")
|
||||||
|
|
@ -173,137 +157,49 @@ func (s *SignalClient) contactToUserInfo(ctx context.Context, contact *types.Rec
|
||||||
return ui, nil
|
return ui, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) canUseContactAvatar(ctx context.Context, contact *types.Recipient, avatarID networkid.AvatarID) bool {
|
var _ bridgev2.IdentifierValidatingNetwork = (*SignalConnector)(nil)
|
||||||
if contact.ContactAvatar.Image != nil {
|
|
||||||
return true
|
|
||||||
} else if contact.ACI == uuid.Nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
ghost, err := s.Main.Bridge.GetExistingGhostByID(ctx, signalid.MakeUserID(contact.ACI))
|
|
||||||
if err != nil {
|
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to get ghost to check contact avatar")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return ghost != nil && ghost.AvatarID == avatarID && ghost.AvatarMXC != "" && ghost.AvatarSet
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SignalConnector) ValidateUserID(id networkid.UserID) bool {
|
func (s *SignalConnector) ValidateUserID(id networkid.UserID) bool {
|
||||||
_, err := signalid.ParseUserIDAsServiceID(id)
|
_, err := signalid.ParseUserIDAsServiceID(id)
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) CreateChatWithGhost(ctx context.Context, ghost *bridgev2.Ghost) (*bridgev2.CreateChatResponse, error) {
|
func (s *SignalClient) ResolveIdentifier(ctx context.Context, number string, createChat bool) (*bridgev2.ResolveIdentifierResponse, error) {
|
||||||
parsedID, err := signalid.ParseUserIDAsServiceID(ghost.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp, err := s.ResolveIdentifier(ctx, parsedID.String(), true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if resp == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
resultID, err := signalid.ParseUserIDAsServiceID(resp.UserID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse result user ID: %w", err)
|
|
||||||
}
|
|
||||||
if parsedID.Type == libsignalgo.ServiceIDTypePNI {
|
|
||||||
if resultID.Type == libsignalgo.ServiceIDTypeACI && !resultID.IsEmpty() {
|
|
||||||
resp.Chat.DMRedirectedTo = resp.UserID
|
|
||||||
} else {
|
|
||||||
resp.Chat.DMRedirectedTo = bridgev2.SpecialValueDMRedirectedToBot
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resp.Chat, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SignalClient) resolvePhone(ctx context.Context, number string) (aci, pni uuid.UUID, e164Number uint64, recipient *types.Recipient, err error) {
|
|
||||||
number, err = bridgev2.CleanPhoneNumber(number)
|
|
||||||
if err != nil {
|
|
||||||
err = bridgev2.WrapRespErr(err, mautrix.MInvalidParam)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e164Number, err = strconv.ParseUint(strings.TrimPrefix(number, "+"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
err = bridgev2.WrapRespErr(fmt.Errorf("error parsing phone number: %w", err), mautrix.MInvalidParam)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e164String := fmt.Sprintf("+%d", e164Number)
|
|
||||||
if recipient, err = s.Client.ContactByE164(ctx, e164String); err != nil {
|
|
||||||
err = fmt.Errorf("error looking up number in local contact list: %w", err)
|
|
||||||
} else if recipient != nil && (recipient.ACI == uuid.Nil || !s.Client.Store.RecipientStore.IsUnregistered(ctx, libsignalgo.NewACIServiceID(recipient.ACI))) {
|
|
||||||
aci = recipient.ACI
|
|
||||||
pni = recipient.PNI
|
|
||||||
} else if resp, lookupErr := s.Client.LookupPhone(ctx, e164Number); lookupErr != nil {
|
|
||||||
err = wrapRespError(fmt.Errorf("error looking up number on server: %w", lookupErr))
|
|
||||||
} else {
|
|
||||||
aci = resp[e164Number].ACI
|
|
||||||
pni = resp[e164Number].PNI
|
|
||||||
if aci == uuid.Nil && pni == uuid.Nil {
|
|
||||||
recipient = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
recipient, err = s.Client.Store.RecipientStore.UpdateRecipientE164(ctx, aci, pni, e164String)
|
|
||||||
if err != nil {
|
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to save recipient entry after looking up phone")
|
|
||||||
}
|
|
||||||
aci, pni = recipient.ACI, recipient.PNI
|
|
||||||
if aci != uuid.Nil {
|
|
||||||
s.Client.Store.RecipientStore.MarkUnregistered(ctx, libsignalgo.NewACIServiceID(aci), false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SignalClient) resolveUsername(ctx context.Context, username string) (aci, pni uuid.UUID, err error) {
|
|
||||||
serviceID, err := s.Client.ResolveUsername(ctx, username)
|
|
||||||
if err != nil {
|
|
||||||
err = wrapRespError(fmt.Errorf("error looking up username on server: %w", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
aci, pni = serviceID.ToACIAndPNI()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const signalUserLinkPrefix = "https://signal.me/#eu/"
|
|
||||||
|
|
||||||
func (s *SignalClient) resolveLink(ctx context.Context, linkHandle string) (aci, pni uuid.UUID, username string, err error) {
|
|
||||||
username, err = s.Client.ResolveUsernameLink(ctx, linkHandle)
|
|
||||||
if err != nil {
|
|
||||||
err = bridgev2.WrapRespErr(err, mautrix.MInvalidParam)
|
|
||||||
return
|
|
||||||
} else if username == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
aci, pni, err = s.resolveUsername(ctx, username)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SignalClient) ResolveIdentifier(ctx context.Context, number string, _ bool) (*bridgev2.ResolveIdentifierResponse, error) {
|
|
||||||
var aci, pni uuid.UUID
|
var aci, pni uuid.UUID
|
||||||
var e164Number uint64
|
var e164Number uint64
|
||||||
var recipient *types.Recipient
|
var recipient *types.Recipient
|
||||||
var username string
|
serviceID, err := libsignalgo.ServiceIDFromString(number)
|
||||||
serviceID, err := signalid.ParseUserIDAsServiceID(networkid.UserID(number))
|
|
||||||
if err == nil {
|
|
||||||
aci, pni = serviceID.ToACIAndPNI()
|
|
||||||
} else if strings.HasPrefix(number, "+") {
|
|
||||||
aci, pni, e164Number, recipient, err = s.resolvePhone(ctx, number)
|
|
||||||
} else if signalmeow.SignalUsernameRegex.MatchString(number) {
|
|
||||||
username = strings.TrimPrefix(number, "@")
|
|
||||||
aci, pni, err = s.resolveUsername(ctx, number)
|
|
||||||
} else if handle, ok := strings.CutPrefix(number, signalUserLinkPrefix); ok {
|
|
||||||
aci, pni, username, err = s.resolveLink(ctx, handle)
|
|
||||||
} else {
|
|
||||||
err = bridgev2.RespError(mautrix.MInvalidParam.WithMessage("Input must be an international phone number, username, link or user ID"))
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
number, err = bridgev2.CleanPhoneNumber(number)
|
||||||
}
|
if err != nil {
|
||||||
if recipient == nil {
|
return nil, bridgev2.WrapRespErr(err, mautrix.MInvalidParam)
|
||||||
if aci == uuid.Nil && pni == uuid.Nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
|
e164Number, err = strconv.ParseUint(strings.TrimPrefix(number, "+"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, bridgev2.WrapRespErr(fmt.Errorf("error parsing phone number: %w", err), mautrix.MInvalidParam)
|
||||||
|
}
|
||||||
|
e164String := fmt.Sprintf("+%d", e164Number)
|
||||||
|
if recipient, err = s.Client.ContactByE164(ctx, e164String); err != nil {
|
||||||
|
return nil, fmt.Errorf("error looking up number in local contact list: %w", err)
|
||||||
|
} else if recipient != nil {
|
||||||
|
aci = recipient.ACI
|
||||||
|
pni = recipient.PNI
|
||||||
|
} else if resp, err := s.Client.LookupPhone(ctx, e164Number); err != nil {
|
||||||
|
return nil, fmt.Errorf("error looking up number on server: %w", err)
|
||||||
|
} else {
|
||||||
|
aci = resp[e164Number].ACI
|
||||||
|
pni = resp[e164Number].PNI
|
||||||
|
if aci == uuid.Nil && pni == uuid.Nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
recipient, err = s.Client.Store.RecipientStore.UpdateRecipientE164(ctx, aci, pni, e164String)
|
||||||
|
if err != nil {
|
||||||
|
zerolog.Ctx(ctx).Err(err).Msg("Failed to save recipient entry after looking up phone")
|
||||||
|
}
|
||||||
|
aci, pni = recipient.ACI, recipient.PNI
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
aci, pni = serviceID.ToACIAndPNI()
|
||||||
recipient, err = s.Client.Store.RecipientStore.LoadAndUpdateRecipient(ctx, aci, pni, nil)
|
recipient, err = s.Client.Store.RecipientStore.LoadAndUpdateRecipient(ctx, aci, pni, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error loading recipient: %w", err)
|
return nil, fmt.Errorf("error loading recipient: %w", err)
|
||||||
|
|
@ -315,34 +211,36 @@ func (s *SignalClient) ResolveIdentifier(ctx context.Context, number string, _ b
|
||||||
Stringer("pni", pni).
|
Stringer("pni", pni).
|
||||||
Msg("Found resolve identifier target user")
|
Msg("Found resolve identifier target user")
|
||||||
|
|
||||||
userInfo, err := s.contactToUserInfo(ctx, recipient, username, true)
|
userInfo, err := s.contactToUserInfo(ctx, recipient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to convert contact: %w", err)
|
return nil, fmt.Errorf("failed to convert contact: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var userID networkid.UserID
|
|
||||||
if aci != uuid.Nil {
|
|
||||||
userID = signalid.MakeUserID(aci)
|
|
||||||
} else {
|
|
||||||
userID = signalid.MakeUserIDFromServiceID(libsignalgo.NewPNIServiceID(pni))
|
|
||||||
}
|
|
||||||
// createChat is a no-op: chats don't need to be created, and we always return chat info
|
// createChat is a no-op: chats don't need to be created, and we always return chat info
|
||||||
resp := &bridgev2.ResolveIdentifierResponse{
|
if aci != uuid.Nil {
|
||||||
UserID: userID,
|
ghost, err := s.Main.Bridge.GetGhostByID(ctx, signalid.MakeUserID(aci))
|
||||||
UserInfo: userInfo,
|
if err != nil {
|
||||||
Chat: s.makeCreateDMResponse(ctx, recipient, nil),
|
return nil, fmt.Errorf("failed to get ghost: %w", err)
|
||||||
|
}
|
||||||
|
return &bridgev2.ResolveIdentifierResponse{
|
||||||
|
UserID: signalid.MakeUserID(aci),
|
||||||
|
UserInfo: userInfo,
|
||||||
|
Ghost: ghost,
|
||||||
|
Chat: s.makeCreateDMResponse(ctx, recipient, nil),
|
||||||
|
}, nil
|
||||||
|
} else {
|
||||||
|
return &bridgev2.ResolveIdentifierResponse{
|
||||||
|
UserID: signalid.MakeUserIDFromServiceID(libsignalgo.NewPNIServiceID(pni)),
|
||||||
|
UserInfo: userInfo,
|
||||||
|
Chat: s.makeCreateDMResponse(ctx, recipient, nil),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
resp.Ghost, err = s.Main.Bridge.GetGhostByID(ctx, resp.UserID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get ghost: %w", err)
|
|
||||||
}
|
|
||||||
return resp, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) CreateGroup(ctx context.Context, params *bridgev2.GroupCreateParams) (*bridgev2.CreateChatResponse, error) {
|
func (s *SignalClient) CreateGroup(ctx context.Context, params *bridgev2.GroupCreateParams) (*bridgev2.CreateChatResponse, error) {
|
||||||
group := &signalmeow.Group{
|
group := &signalmeow.Group{
|
||||||
Title: ptr.Val(params.Name).Name,
|
Title: ptr.Val(params.Name).Name,
|
||||||
Members: make([]*signalmeow.GroupMember, 1, len(params.Participants)+1),
|
Members: make([]*signalmeow.GroupMember, len(params.Participants)+1),
|
||||||
Description: ptr.Val(params.Topic).Topic,
|
Description: ptr.Val(params.Topic).Topic,
|
||||||
AnnouncementsOnly: false,
|
AnnouncementsOnly: false,
|
||||||
DisappearingMessagesDuration: uint32(ptr.Val(params.Disappear).Timer.Seconds()),
|
DisappearingMessagesDuration: uint32(ptr.Val(params.Disappear).Timer.Seconds()),
|
||||||
|
|
@ -369,25 +267,14 @@ func (s *SignalClient) CreateGroup(ctx context.Context, params *bridgev2.GroupCr
|
||||||
ACI: s.Client.Store.ACI,
|
ACI: s.Client.Store.ACI,
|
||||||
Role: signalmeow.GroupMember_ADMINISTRATOR,
|
Role: signalmeow.GroupMember_ADMINISTRATOR,
|
||||||
}
|
}
|
||||||
currentTS := uint64(time.Now().UnixMilli())
|
for i, member := range params.Participants {
|
||||||
for _, member := range params.Participants {
|
userID, err := signalid.ParseUserID(member)
|
||||||
userID, err := signalid.ParseUserIDAsServiceID(member)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid user ID %q: %w", member, err)
|
return nil, fmt.Errorf("invalid user ID %q: %w", member, err)
|
||||||
}
|
}
|
||||||
if userID.Type == libsignalgo.ServiceIDTypeACI {
|
group.Members[i+1] = &signalmeow.GroupMember{
|
||||||
group.Members = append(group.Members, &signalmeow.GroupMember{
|
ACI: userID,
|
||||||
ACI: userID.UUID,
|
Role: signalmeow.GroupMember_DEFAULT, // TODO set proper role from power levels
|
||||||
Role: signalmeow.GroupMember_DEFAULT, // TODO set proper role from power levels
|
|
||||||
})
|
|
||||||
} else if userID.Type == libsignalgo.ServiceIDTypePNI {
|
|
||||||
// TODO check if this is correct
|
|
||||||
group.PendingMembers = append(group.PendingMembers, &signalmeow.PendingMember{
|
|
||||||
ServiceID: userID,
|
|
||||||
Role: signalmeow.GroupMember_DEFAULT,
|
|
||||||
AddedByUserID: s.Client.Store.ACI,
|
|
||||||
Timestamp: currentTS,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err := signalmeow.PrepareGroupCreation(group)
|
_, err := signalmeow.PrepareGroupCreation(group)
|
||||||
|
|
@ -396,15 +283,15 @@ func (s *SignalClient) CreateGroup(ctx context.Context, params *bridgev2.GroupCr
|
||||||
}
|
}
|
||||||
var avatarBytes []byte
|
var avatarBytes []byte
|
||||||
var avatarMXC id.ContentURIString
|
var avatarMXC id.ContentURIString
|
||||||
if params.Avatar != nil && params.Avatar.URL != "" {
|
if params.Avatar != nil {
|
||||||
avatarMXC = params.Avatar.URL
|
avatarMXC = params.Avatar.URL
|
||||||
avatarBytes, err = s.Main.Bridge.Bot.DownloadMedia(ctx, params.Avatar.URL, nil)
|
avatarBytes, err = s.Main.Bridge.Bot.DownloadMedia(ctx, params.Avatar.URL, params.Avatar.MSC3414File)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to download avatar: %w", err)
|
return nil, fmt.Errorf("failed to download avatar: %w", err)
|
||||||
}
|
}
|
||||||
group.AvatarPath, err = s.Client.UploadGroupAvatar(ctx, avatarBytes, group.GroupIdentifier, group.GroupMasterKey)
|
group.AvatarPath, err = s.Client.UploadGroupAvatar(ctx, avatarBytes, group.GroupIdentifier)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, wrapRespError(fmt.Errorf("failed to upload avatar: %w", err))
|
return nil, fmt.Errorf("failed to upload avatar: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
portal, err := s.Main.Bridge.GetPortalByKey(ctx, s.makePortalKey(string(group.GroupIdentifier)))
|
portal, err := s.Main.Bridge.GetPortalByKey(ctx, s.makePortalKey(string(group.GroupIdentifier)))
|
||||||
|
|
@ -432,9 +319,9 @@ func (s *SignalClient) CreateGroup(ctx context.Context, params *bridgev2.GroupCr
|
||||||
return nil, fmt.Errorf("failed to set portal room ID: %w", err)
|
return nil, fmt.Errorf("failed to set portal room ID: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp, err := s.Client.CreateGroup(ctx, group)
|
resp, err := s.Client.CreateGroup(ctx, group, avatarBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, wrapRespError(fmt.Errorf("failed to create group: %w", err))
|
return nil, fmt.Errorf("failed to create group: %w", err)
|
||||||
}
|
}
|
||||||
if params.RoomID != "" {
|
if params.RoomID != "" {
|
||||||
// UpdateMatrixRoomID could do this for us if we passed ChatInfoSource to it,
|
// UpdateMatrixRoomID could do this for us if we passed ChatInfoSource to it,
|
||||||
|
|
@ -460,32 +347,23 @@ func (s *SignalClient) GetContactList(ctx context.Context) ([]*bridgev2.ResolveI
|
||||||
}
|
}
|
||||||
resp := make([]*bridgev2.ResolveIdentifierResponse, len(recipients))
|
resp := make([]*bridgev2.ResolveIdentifierResponse, len(recipients))
|
||||||
for i, recipient := range recipients {
|
for i, recipient := range recipients {
|
||||||
if err = ctx.Err(); err != nil {
|
userInfo, err := s.contactToUserInfo(ctx, recipient)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to convert contact: %w", err)
|
||||||
|
}
|
||||||
|
recipientResp := &bridgev2.ResolveIdentifierResponse{
|
||||||
|
UserInfo: userInfo,
|
||||||
|
Chat: s.makeCreateDMResponse(ctx, recipient, nil),
|
||||||
}
|
}
|
||||||
recipientResp := &bridgev2.ResolveIdentifierResponse{}
|
|
||||||
var username string
|
|
||||||
if recipient.ACI != uuid.Nil {
|
if recipient.ACI != uuid.Nil {
|
||||||
recipientResp.Chat = &bridgev2.CreateChatResponse{
|
|
||||||
PortalKey: s.makeDMPortalKey(libsignalgo.NewACIServiceID(recipient.ACI)),
|
|
||||||
}
|
|
||||||
recipientResp.UserID = signalid.MakeUserID(recipient.ACI)
|
recipientResp.UserID = signalid.MakeUserID(recipient.ACI)
|
||||||
ghost, err := s.Main.Bridge.GetGhostByID(ctx, recipientResp.UserID)
|
ghost, err := s.Main.Bridge.GetGhostByID(ctx, recipientResp.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get ghost for %s: %w", recipient.ACI, err)
|
return nil, fmt.Errorf("failed to get ghost for %s: %w", recipient.ACI, err)
|
||||||
}
|
}
|
||||||
recipientResp.Ghost = ghost
|
recipientResp.Ghost = ghost
|
||||||
username = ghost.Metadata.(*signalid.GhostMetadata).Username
|
|
||||||
} else {
|
} else {
|
||||||
pniServiceID := libsignalgo.NewPNIServiceID(recipient.PNI)
|
recipientResp.UserID = signalid.MakeUserIDFromServiceID(libsignalgo.NewPNIServiceID(recipient.PNI))
|
||||||
recipientResp.Chat = &bridgev2.CreateChatResponse{
|
|
||||||
PortalKey: s.makeDMPortalKey(pniServiceID),
|
|
||||||
}
|
|
||||||
recipientResp.UserID = signalid.MakeUserIDFromServiceID(pniServiceID)
|
|
||||||
}
|
|
||||||
recipientResp.UserInfo, err = s.contactToUserInfo(ctx, recipient, username, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to convert contact: %w", err)
|
|
||||||
}
|
}
|
||||||
resp[i] = recipientResp
|
resp[i] = recipientResp
|
||||||
}
|
}
|
||||||
|
|
@ -493,7 +371,7 @@ func (s *SignalClient) GetContactList(ctx context.Context) ([]*bridgev2.ResolveI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) makeCreateDMResponse(ctx context.Context, recipient *types.Recipient, backupChat *store.BackupChat) *bridgev2.CreateChatResponse {
|
func (s *SignalClient) makeCreateDMResponse(ctx context.Context, recipient *types.Recipient, backupChat *store.BackupChat) *bridgev2.CreateChatResponse {
|
||||||
namePtr := bridgev2.DefaultChatName
|
name := ""
|
||||||
topic := PrivateChatTopic
|
topic := PrivateChatTopic
|
||||||
selfUser := s.makeEventSender(s.Client.Store.ACI)
|
selfUser := s.makeEventSender(s.Client.Store.ACI)
|
||||||
members := &bridgev2.ChatMemberList{
|
members := &bridgev2.ChatMemberList{
|
||||||
|
|
@ -520,7 +398,7 @@ func (s *SignalClient) makeCreateDMResponse(ctx context.Context, recipient *type
|
||||||
var serviceID libsignalgo.ServiceID
|
var serviceID libsignalgo.ServiceID
|
||||||
var avatar *bridgev2.Avatar
|
var avatar *bridgev2.Avatar
|
||||||
if recipient.ACI == uuid.Nil {
|
if recipient.ACI == uuid.Nil {
|
||||||
namePtr = ptr.Ptr(s.Main.Config.FormatDisplayname(recipient, ""))
|
name = s.Main.Config.FormatDisplayname(recipient)
|
||||||
serviceID = libsignalgo.NewPNIServiceID(recipient.PNI)
|
serviceID = libsignalgo.NewPNIServiceID(recipient.PNI)
|
||||||
} else {
|
} else {
|
||||||
if backupChat == nil {
|
if backupChat == nil {
|
||||||
|
|
@ -532,7 +410,7 @@ func (s *SignalClient) makeCreateDMResponse(ctx context.Context, recipient *type
|
||||||
}
|
}
|
||||||
members.OtherUserID = signalid.MakeUserID(recipient.ACI)
|
members.OtherUserID = signalid.MakeUserID(recipient.ACI)
|
||||||
if recipient.ACI == s.Client.Store.ACI {
|
if recipient.ACI == s.Client.Store.ACI {
|
||||||
namePtr = ptr.Ptr(NoteToSelfName)
|
name = NoteToSelfName
|
||||||
avatar = &bridgev2.Avatar{
|
avatar = &bridgev2.Avatar{
|
||||||
ID: networkid.AvatarID(s.Main.Config.NoteToSelfAvatar),
|
ID: networkid.AvatarID(s.Main.Config.NoteToSelfAvatar),
|
||||||
Remove: len(s.Main.Config.NoteToSelfAvatar) == 0,
|
Remove: len(s.Main.Config.NoteToSelfAvatar) == 0,
|
||||||
|
|
@ -553,16 +431,15 @@ func (s *SignalClient) makeCreateDMResponse(ctx context.Context, recipient *type
|
||||||
return &bridgev2.CreateChatResponse{
|
return &bridgev2.CreateChatResponse{
|
||||||
PortalKey: s.makeDMPortalKey(serviceID),
|
PortalKey: s.makeDMPortalKey(serviceID),
|
||||||
PortalInfo: &bridgev2.ChatInfo{
|
PortalInfo: &bridgev2.ChatInfo{
|
||||||
Name: namePtr,
|
Name: &name,
|
||||||
Avatar: avatar,
|
Avatar: avatar,
|
||||||
Topic: &topic,
|
Topic: &topic,
|
||||||
Members: members,
|
Members: members,
|
||||||
Type: ptr.Ptr(database.RoomTypeDM),
|
Type: ptr.Ptr(database.RoomTypeDM),
|
||||||
|
|
||||||
UserBlocked: ptr.Ptr(recipient.Blocked),
|
CanBackfill: backupChat != nil,
|
||||||
MessageRequest: ptr.Ptr(recipient.ACI != uuid.Nil && recipient.ProbablyMessageRequest()),
|
|
||||||
CanBackfill: backupChat != nil,
|
ExtraUpdates: updatePortalSyncMeta,
|
||||||
ExtraUpdates: updatePortalSyncMeta,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package connector
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
@ -29,36 +28,18 @@ import (
|
||||||
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalid"
|
"go.mau.fi/mautrix-signal/pkg/signalid"
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow"
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf/backuppb"
|
"go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf/backuppb"
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
|
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *SignalClient) stopChatSync() {
|
func (s *SignalClient) syncChats(ctx context.Context) {
|
||||||
if cancel := s.cancelChatSync.Swap(nil); cancel != nil {
|
|
||||||
(*cancel)()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SignalClient) syncChats(ctx context.Context, cancel context.CancelFunc) {
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if s.UserLogin.Metadata.(*signalid.UserLoginMetadata).ChatsSynced {
|
if s.UserLogin.Metadata.(*signalid.UserLoginMetadata).ChatsSynced {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Client.Store.EphemeralBackupKey != nil {
|
if s.Client.Store.EphemeralBackupKey != nil {
|
||||||
zerolog.Ctx(ctx).Info().Msg("Fetching transfer archive before syncing chats")
|
zerolog.Ctx(ctx).Info().Msg("Fetching transfer archive before syncing chats")
|
||||||
meta, err := s.Client.WaitForTransfer(ctx)
|
meta, err := s.Client.WaitForTransfer(ctx)
|
||||||
if errors.Is(err, signalmeow.ErrTransferTimedOut) {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Warn().Msg("Transfer archive wasn't uploaded in time, giving up")
|
|
||||||
s.UserLogin.Metadata.(*signalid.UserLoginMetadata).ChatsSynced = true
|
|
||||||
err = s.UserLogin.Save(ctx)
|
|
||||||
if err != nil {
|
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to save user login metadata after transfer archive timed out")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
} else if err != nil {
|
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to request transfer archive")
|
zerolog.Ctx(ctx).Err(err).Msg("Failed to request transfer archive")
|
||||||
return
|
return
|
||||||
} else if meta.Error != "" {
|
} else if meta.Error != "" {
|
||||||
|
|
@ -84,22 +65,10 @@ func (s *SignalClient) syncChats(ctx context.Context, cancel context.CancelFunc)
|
||||||
}
|
}
|
||||||
zerolog.Ctx(ctx).Info().Int("chat_count", len(chats)).Msg("Fetched chats to sync from database")
|
zerolog.Ctx(ctx).Info().Int("chat_count", len(chats)).Msg("Fetched chats to sync from database")
|
||||||
for _, chat := range chats {
|
for _, chat := range chats {
|
||||||
if ctx.Err() != nil {
|
|
||||||
zerolog.Ctx(ctx).Debug().
|
|
||||||
AnErr("ctx_err", ctx.Err()).
|
|
||||||
Msg("Context cancelled while syncing chats, stopping")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
recipient, err := s.Client.Store.BackupStore.GetBackupRecipient(ctx, chat.RecipientId)
|
recipient, err := s.Client.Store.BackupStore.GetBackupRecipient(ctx, chat.RecipientId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to get recipient for chat")
|
zerolog.Ctx(ctx).Err(err).Msg("Failed to get recipient for chat")
|
||||||
continue
|
continue
|
||||||
} else if recipient == nil {
|
|
||||||
zerolog.Ctx(ctx).Warn().
|
|
||||||
Uint64("backup_chat_id", chat.Id).
|
|
||||||
Uint64("backup_recipient_id", chat.RecipientId).
|
|
||||||
Msg("No recipient found for chat")
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
resyncEvt := &simplevent.ChatResync{
|
resyncEvt := &simplevent.ChatResync{
|
||||||
EventMeta: simplevent.EventMeta{
|
EventMeta: simplevent.EventMeta{
|
||||||
|
|
@ -161,10 +130,7 @@ func (s *SignalClient) syncChats(ctx context.Context, cancel context.CancelFunc)
|
||||||
groupID := types.GroupIdentifier(base64.StdEncoding.EncodeToString(rawGroupID[:]))
|
groupID := types.GroupIdentifier(base64.StdEncoding.EncodeToString(rawGroupID[:]))
|
||||||
groupInfo, err := s.getGroupInfo(ctx, groupID, dest.Group.GetSnapshot().GetVersion(), chat)
|
groupInfo, err := s.getGroupInfo(ctx, groupID, dest.Group.GetSnapshot().GetVersion(), chat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).
|
zerolog.Ctx(ctx).Err(err).Msg("Failed to get full group info")
|
||||||
Uint64("recipient_id", recipient.Id).
|
|
||||||
Stringer("group_id", groupID).
|
|
||||||
Msg("Failed to get full group info")
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
resyncEvt.PortalKey = s.makePortalKey(string(groupID))
|
resyncEvt.PortalKey = s.makePortalKey(string(groupID))
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue