mirror of
https://github.com/mautrix/signal.git
synced 2026-11-13 01:29:17 -05:00
Compare commits
119 changed files with 4053 additions and 8161 deletions
1
.github/workflows/go.yml
vendored
1
.github/workflows/go.yml
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
|
||||||
33
CHANGELOG.md
33
CHANGELOG.md
|
|
@ -1,38 +1,7 @@
|
||||||
# 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
|
# v26.09
|
||||||
|
|
||||||
* Bumped minimum Go version to 1.26.
|
* Bumped minimum Go version to 1.26.
|
||||||
* Updated libsignal to v0.102.2.
|
* Updated libsignal to v1.102.2.
|
||||||
* Fixed handling messages with invalid values in formatting body ranges.
|
* Fixed handling messages with invalid values in formatting body ranges.
|
||||||
* Fixed websocket request retrying not working correctly if the first attempt
|
* Fixed websocket request retrying not working correctly if the first attempt
|
||||||
times out.
|
times out.
|
||||||
|
|
|
||||||
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"`
|
||||||
|
}
|
||||||
|
|
@ -37,7 +37,7 @@ 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: "26.09",
|
||||||
SemCalVer: true,
|
SemCalVer: true,
|
||||||
|
|
||||||
Connector: &connector.SignalConnector{},
|
Connector: &connector.SignalConnector{},
|
||||||
|
|
@ -45,6 +45,12 @@ var m = mxmain.BridgeMain{
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
web.UserAgent = fmt.Sprintf("mautrix-signal/%s %s", m.Version, web.BaseUserAgent)
|
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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
28
go.mod
28
go.mod
|
|
@ -2,7 +2,7 @@ module go.mau.fi/mautrix-signal
|
||||||
|
|
||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
toolchain go1.27.2
|
toolchain go1.27.1
|
||||||
|
|
||||||
tool go.mau.fi/util/cmd/maubuild
|
tool go.mau.fi/util/cmd/maubuild
|
||||||
|
|
||||||
|
|
@ -13,16 +13,16 @@ require (
|
||||||
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.35.1
|
||||||
github.com/stretchr/testify v1.12.0
|
github.com/stretchr/testify v1.12.0
|
||||||
github.com/tidwall/gjson v1.20.0
|
github.com/tidwall/gjson v1.19.0
|
||||||
go.mau.fi/util v0.11.0
|
go.mau.fi/util v0.10.1
|
||||||
golang.org/x/crypto v0.58.0
|
golang.org/x/crypto v0.57.0
|
||||||
golang.org/x/exp v0.0.0-20261014143641-f0251eb4aa3e
|
golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba
|
||||||
golang.org/x/net v0.61.0
|
golang.org/x/net v0.59.0
|
||||||
golang.org/x/sync v0.24.0
|
golang.org/x/sync v0.23.0
|
||||||
google.golang.org/grpc v1.84.0
|
google.golang.org/grpc v1.83.2
|
||||||
google.golang.org/protobuf v1.36.12
|
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.31.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
@ -33,7 +33,7 @@ require (
|
||||||
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.52 // indirect
|
||||||
github.com/petermattis/goid v0.0.0-20260918085751-abfca077860b // indirect
|
github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 // 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
|
||||||
|
|
@ -42,10 +42,10 @@ require (
|
||||||
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.8.6 // 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/mod v0.41.0 // indirect
|
||||||
golang.org/x/sys v0.49.0 // indirect
|
golang.org/x/sys v0.48.0 // indirect
|
||||||
golang.org/x/text v0.43.0 // indirect
|
golang.org/x/text v0.42.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // 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
|
||||||
|
|
|
||||||
70
go.sum
70
go.sum
|
|
@ -2,6 +2,8 @@ filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
github.com/DATA-DOG/go-sqlmock v1.5.2 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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA=
|
github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA=
|
||||||
github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||||
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
|
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
|
||||||
|
|
@ -9,6 +11,10 @@ github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7
|
||||||
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/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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
|
@ -32,8 +38,8 @@ github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o
|
||||||
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.52 h1:wVbm2Qnf4OXkqhBTSPuCRZDRnxfbVrrmiCEroVdog8U=
|
||||||
github.com/mattn/go-sqlite3 v1.14.52/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=
|
github.com/mattn/go-sqlite3 v1.14.52/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=
|
||||||
github.com/petermattis/goid v0.0.0-20260918085751-abfca077860b h1:OzNsuVdSWGwXvWKTtChx9ve89k4cFJ6NxYM4Aw4/f+E=
|
github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261 h1:lcWAnrqr2nNfDiArwFNHCE4787Mw2tCdVSOXCru0/0E=
|
||||||
github.com/petermattis/goid v0.0.0-20260918085751-abfca077860b/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
github.com/petermattis/goid v0.0.0-20260820044319-269ab09b5261/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.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=
|
||||||
|
|
@ -47,8 +53,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq
|
||||||
github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI=
|
github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI=
|
||||||
github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw=
|
github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw=
|
||||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/gjson v1.20.0 h1:+agJ3rEzKcCXKDKo0ml26UROcpcAlnZyjq+TjbY5Fto=
|
github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=
|
||||||
github.com/tidwall/gjson v1.20.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
|
github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
|
||||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
||||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
|
@ -59,31 +65,43 @@ 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.8.6 h1:d0VcaP1sx9GkFVkoW+KtggpGi2KZ965i14b0+bDQST4=
|
||||||
github.com/yuin/goldmark v1.8.6/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
github.com/yuin/goldmark v1.8.6/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||||
go.mau.fi/util v0.11.0 h1:JNEpkVRxfTdJUC0DIGKiRlLKzl4G/qLX/eVzoP7QuwY=
|
go.mau.fi/util v0.10.1 h1:1oSqb4TwzLA0cUDY0aomyBPFKkZ3J5fqCmrXV3VH3GQ=
|
||||||
go.mau.fi/util v0.11.0/go.mod h1:04AEFVsc4zNdIkDC1gkdqbg/nZFolHY50pyXh2DSy7s=
|
go.mau.fi/util v0.10.1/go.mod h1:40TDo7/ekSeOjgr8KAmX31Yf4zrOF94j83WQB+u5ZPc=
|
||||||
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=
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w=
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
golang.org/x/exp v0.0.0-20261014143641-f0251eb4aa3e h1:HgI6W1OolKK6tcV74is+rl9SMT0njsHlp0TbQiGXI/A=
|
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||||
golang.org/x/exp v0.0.0-20261014143641-f0251eb4aa3e/go.mod h1:DBoxwWhjaW8DJJw/YeM4zpeOKMC1FMXSblsYy+1mFSw=
|
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||||
golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg=
|
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||||
golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs=
|
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||||
golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw=
|
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||||
golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
|
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||||
golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
|
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
|
||||||
golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
|
||||||
|
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||||
|
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||||
|
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
|
||||||
|
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
|
||||||
|
golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba h1:Ck8QetSgk912qxWLMCKxd0in+aiyBQyDSMae6e/xmpU=
|
||||||
|
golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba/go.mod h1:50RgIsmK7OwqzTTeqcSXQW8SswW0o8fRcDxmqGluJ8E=
|
||||||
|
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
|
||||||
|
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
|
||||||
|
golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
|
||||||
|
golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
|
||||||
|
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
|
||||||
|
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
|
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
|
||||||
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||||
golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc=
|
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
|
||||||
golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s=
|
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
|
||||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 h1:qEHAMpSaUhtD0p3NbEEI83HwNGFxEwaSJ1G9PLnCBZE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0=
|
google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
|
||||||
google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0=
|
google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
|
||||||
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||||
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
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=
|
||||||
|
|
@ -95,5 +113,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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.31.0 h1:x6XNBSa0kOaBgcleI+ZBLkGvBdHIE0qdeY6TZ4wWUeI=
|
||||||
maunium.net/go/mautrix v0.32.1-0.20261026145030-920bc073ba8d/go.mod h1:tZSwPzw+/0pa0dxGufkh0uJqM84BNxB6e2I0+S0UyjU=
|
maunium.net/go/mautrix v0.31.0/go.mod h1:vfPYtoGAjlTnIF+W8tl+rjX3yB4PEfqYR2sDXvQo4Co=
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
if !firstDirectionfulProcessed {
|
||||||
|
firstDirectionfulProcessed = true
|
||||||
isRead = dt.Incoming.Read
|
isRead = dt.Incoming.Read
|
||||||
if isRead {
|
|
||||||
readUpTo = time.UnixMilli(int64(item.DateSent))
|
|
||||||
}
|
}
|
||||||
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)
|
||||||
|
|
||||||
|
if !firstDirectionfulProcessed {
|
||||||
|
firstDirectionfulProcessed = true
|
||||||
isRead = true
|
isRead = true
|
||||||
readUpTo = time.UnixMilli(int64(item.DateSent))
|
}
|
||||||
}
|
}
|
||||||
if len(attMap) > 0 {
|
if len(attMap) > 0 {
|
||||||
clear(attMap)
|
clear(attMap)
|
||||||
|
|
@ -175,15 +178,11 @@ 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.
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,11 @@ 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/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 +38,7 @@ func supportedIfFFmpeg() event.CapabilitySupportLevel {
|
||||||
}
|
}
|
||||||
|
|
||||||
func capID() string {
|
func capID() string {
|
||||||
base := "fi.mau.signal.capabilities.2026_10_21"
|
base := "fi.mau.signal.capabilities.2026_07_22"
|
||||||
if ffmpeg.Supported() {
|
if ffmpeg.Supported() {
|
||||||
return base + "+ffmpeg"
|
return base + "+ffmpeg"
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +46,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(),
|
||||||
|
|
@ -83,7 +81,6 @@ var signalCaps = &event.RoomFeatures{
|
||||||
},
|
},
|
||||||
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 +91,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,
|
||||||
|
|
@ -105,8 +101,6 @@ var signalCaps = &event.RoomFeatures{
|
||||||
"audio/mpeg": event.CapLevelFullySupported,
|
"audio/mpeg": event.CapLevelFullySupported,
|
||||||
"audio/mp3": event.CapLevelFullySupported,
|
"audio/mp3": event.CapLevelFullySupported,
|
||||||
"audio/flac": event.CapLevelFullySupported,
|
"audio/flac": event.CapLevelFullySupported,
|
||||||
"audio/mp4": event.CapLevelFullySupported,
|
|
||||||
"audio/wav": event.CapLevelFullySupported,
|
|
||||||
},
|
},
|
||||||
MaxSize: MaxFileSize,
|
MaxSize: MaxFileSize,
|
||||||
},
|
},
|
||||||
|
|
@ -160,7 +154,7 @@ var signalCaps = &event.RoomFeatures{
|
||||||
event.MemberActionBan: event.CapLevelFullySupported,
|
event.MemberActionBan: event.CapLevelFullySupported,
|
||||||
event.MemberActionKick: event.CapLevelFullySupported,
|
event.MemberActionKick: event.CapLevelFullySupported,
|
||||||
},
|
},
|
||||||
MaxTextLength: MaxTextLength,
|
MaxTextLength: MaxTextLength, // TODO support arbitrary sized text messages with files
|
||||||
LocationMessage: event.CapLevelPartialSupport,
|
LocationMessage: event.CapLevelPartialSupport,
|
||||||
Poll: event.CapLevelFullySupported,
|
Poll: event.CapLevelFullySupported,
|
||||||
PollEnd: event.CapLevelUnsupported,
|
PollEnd: event.CapLevelUnsupported,
|
||||||
|
|
@ -198,7 +192,6 @@ var signalDisappearingCap = &event.DisappearingTimerCapability{
|
||||||
|
|
||||||
var signalCapsNoteToSelf *event.RoomFeatures
|
var signalCapsNoteToSelf *event.RoomFeatures
|
||||||
var signalCapsDM *event.RoomFeatures
|
var signalCapsDM *event.RoomFeatures
|
||||||
var signalCapsPNIDM *event.RoomFeatures
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
signalCapsDM = ptr.Clone(signalCaps)
|
signalCapsDM = ptr.Clone(signalCaps)
|
||||||
|
|
@ -207,16 +200,7 @@ func init() {
|
||||||
signalCapsDM.State = event.StateFeatureMap{
|
signalCapsDM.State = event.StateFeatureMap{
|
||||||
event.StateBeeperDisappearingTimer.Type: {Level: event.CapLevelFullySupported},
|
event.StateBeeperDisappearingTimer.Type: {Level: event.CapLevelFullySupported},
|
||||||
}
|
}
|
||||||
|
|
||||||
signalCapsPNIDM = ptr.Clone(signalCapsDM)
|
|
||||||
signalCapsPNIDM.ID += "+pni"
|
|
||||||
|
|
||||||
signalCapsDM.BlockUser = true
|
|
||||||
signalCapsDM.ReportSpam = true
|
|
||||||
|
|
||||||
signalCapsNoteToSelf = ptr.Clone(signalCapsDM)
|
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"
|
||||||
|
|
@ -226,10 +210,6 @@ func (s *SignalClient) GetCapabilities(ctx context.Context, portal *bridgev2.Por
|
||||||
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 {
|
} else if portal.RoomType == database.RoomTypeDM {
|
||||||
userID, _, _ := signalid.ParsePortalID(portal.ID)
|
|
||||||
if userID.Type == libsignalgo.ServiceIDTypePNI {
|
|
||||||
return signalCapsPNIDM
|
|
||||||
}
|
|
||||||
return signalCapsDM
|
return signalCapsDM
|
||||||
}
|
}
|
||||||
return signalCaps
|
return signalCaps
|
||||||
|
|
@ -244,7 +224,7 @@ var signalGeneralCaps = &bridgev2.NetworkGeneralCapabilities{
|
||||||
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 +245,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, 11
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ func (s *SignalClient) GetUserInfoWithRefreshAfter(ctx context.Context, ghost *b
|
||||||
if userID.Type != libsignalgo.ServiceIDTypePNI && (!s.Main.Config.UseOutdatedProfiles && meta.ProfileFetchedAt.After(contact.Profile.FetchedAt)) {
|
if userID.Type != libsignalgo.ServiceIDTypePNI && (!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 +99,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 +110,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,20 +165,6 @@ 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 {
|
|
||||||
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
|
||||||
|
|
@ -217,31 +195,33 @@ func (s *SignalClient) CreateChatWithGhost(ctx context.Context, ghost *bridgev2.
|
||||||
return resp.Chat, nil
|
return resp.Chat, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) resolvePhone(ctx context.Context, number string) (aci, pni uuid.UUID, e164Number uint64, recipient *types.Recipient, err error) {
|
func (s *SignalClient) ResolveIdentifier(ctx context.Context, number string, _ bool) (*bridgev2.ResolveIdentifierResponse, error) {
|
||||||
|
var aci, pni uuid.UUID
|
||||||
|
var e164Number uint64
|
||||||
|
var recipient *types.Recipient
|
||||||
|
serviceID, err := signalid.ParseUserIDAsServiceID(networkid.UserID(number))
|
||||||
|
if err != nil {
|
||||||
number, err = bridgev2.CleanPhoneNumber(number)
|
number, err = bridgev2.CleanPhoneNumber(number)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = bridgev2.WrapRespErr(err, mautrix.MInvalidParam)
|
return nil, bridgev2.WrapRespErr(err, mautrix.MInvalidParam)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
e164Number, err = strconv.ParseUint(strings.TrimPrefix(number, "+"), 10, 64)
|
e164Number, err = strconv.ParseUint(strings.TrimPrefix(number, "+"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = bridgev2.WrapRespErr(fmt.Errorf("error parsing phone number: %w", err), mautrix.MInvalidParam)
|
return nil, bridgev2.WrapRespErr(fmt.Errorf("error parsing phone number: %w", err), mautrix.MInvalidParam)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
e164String := fmt.Sprintf("+%d", e164Number)
|
e164String := fmt.Sprintf("+%d", e164Number)
|
||||||
if recipient, err = s.Client.ContactByE164(ctx, e164String); err != nil {
|
if recipient, err = s.Client.ContactByE164(ctx, e164String); err != nil {
|
||||||
err = fmt.Errorf("error looking up number in local contact list: %w", err)
|
return nil, 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))) {
|
} else if recipient != nil && (recipient.ACI == uuid.Nil || !s.Client.Store.RecipientStore.IsUnregistered(ctx, libsignalgo.NewACIServiceID(recipient.ACI))) {
|
||||||
aci = recipient.ACI
|
aci = recipient.ACI
|
||||||
pni = recipient.PNI
|
pni = recipient.PNI
|
||||||
} else if resp, lookupErr := s.Client.LookupPhone(ctx, e164Number); lookupErr != nil {
|
} else if resp, err := s.Client.LookupPhone(ctx, e164Number); err != nil {
|
||||||
err = wrapRespError(fmt.Errorf("error looking up number on server: %w", lookupErr))
|
return nil, fmt.Errorf("error looking up number on server: %w", err)
|
||||||
} else {
|
} else {
|
||||||
aci = resp[e164Number].ACI
|
aci = resp[e164Number].ACI
|
||||||
pni = resp[e164Number].PNI
|
pni = resp[e164Number].PNI
|
||||||
if aci == uuid.Nil && pni == uuid.Nil {
|
if aci == uuid.Nil && pni == uuid.Nil {
|
||||||
recipient = nil
|
return nil, nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
recipient, err = s.Client.Store.RecipientStore.UpdateRecipientE164(ctx, aci, pni, e164String)
|
recipient, err = s.Client.Store.RecipientStore.UpdateRecipientE164(ctx, aci, pni, e164String)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -252,58 +232,8 @@ func (s *SignalClient) resolvePhone(ctx context.Context, number string) (aci, pn
|
||||||
s.Client.Store.RecipientStore.MarkUnregistered(ctx, libsignalgo.NewACIServiceID(aci), false)
|
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 e164Number uint64
|
|
||||||
var recipient *types.Recipient
|
|
||||||
var username string
|
|
||||||
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 {
|
} else {
|
||||||
err = bridgev2.RespError(mautrix.MInvalidParam.WithMessage("Input must be an international phone number, username, link or user ID"))
|
aci, pni = serviceID.ToACIAndPNI()
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if recipient == nil {
|
|
||||||
if aci == uuid.Nil && pni == uuid.Nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
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,7 +245,7 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
@ -404,7 +334,7 @@ func (s *SignalClient) CreateGroup(ctx context.Context, params *bridgev2.GroupCr
|
||||||
}
|
}
|
||||||
group.AvatarPath, err = s.Client.UploadGroupAvatar(ctx, avatarBytes, group.GroupIdentifier, group.GroupMasterKey)
|
group.AvatarPath, err = s.Client.UploadGroupAvatar(ctx, avatarBytes, group.GroupIdentifier, group.GroupMasterKey)
|
||||||
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)))
|
||||||
|
|
@ -434,7 +364,7 @@ func (s *SignalClient) CreateGroup(ctx context.Context, params *bridgev2.GroupCr
|
||||||
}
|
}
|
||||||
resp, err := s.Client.CreateGroup(ctx, group)
|
resp, err := s.Client.CreateGroup(ctx, group)
|
||||||
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 +390,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
|
||||||
}
|
}
|
||||||
|
|
@ -520,7 +441,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, ""))
|
namePtr = ptr.Ptr(s.Main.Config.FormatDisplayname(recipient))
|
||||||
serviceID = libsignalgo.NewPNIServiceID(recipient.PNI)
|
serviceID = libsignalgo.NewPNIServiceID(recipient.PNI)
|
||||||
} else {
|
} else {
|
||||||
if backupChat == nil {
|
if backupChat == nil {
|
||||||
|
|
@ -559,7 +480,6 @@ func (s *SignalClient) makeCreateDMResponse(ctx context.Context, recipient *type
|
||||||
Members: members,
|
Members: members,
|
||||||
Type: ptr.Ptr(database.RoomTypeDM),
|
Type: ptr.Ptr(database.RoomTypeDM),
|
||||||
|
|
||||||
UserBlocked: ptr.Ptr(recipient.Blocked),
|
|
||||||
MessageRequest: ptr.Ptr(recipient.ACI != uuid.Nil && recipient.ProbablyMessageRequest()),
|
MessageRequest: ptr.Ptr(recipient.ACI != uuid.Nil && recipient.ProbablyMessageRequest()),
|
||||||
CanBackfill: backupChat != nil,
|
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,7 +28,6 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
@ -50,15 +48,7 @@ func (s *SignalClient) syncChats(ctx context.Context, cancel context.CancelFunc)
|
||||||
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) {
|
|
||||||
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 {
|
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 != "" {
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@ type SignalClient struct {
|
||||||
Client *signalmeow.Client
|
Client *signalmeow.Client
|
||||||
Ghost *bridgev2.Ghost
|
Ghost *bridgev2.Ghost
|
||||||
|
|
||||||
recentMessageRooms *exsync.RingBuffer[networkid.MessageID, networkid.PortalKey]
|
|
||||||
queueEmptyWaiter *exsync.Event
|
queueEmptyWaiter *exsync.Event
|
||||||
cancelChatSync atomic.Pointer[context.CancelFunc]
|
cancelChatSync atomic.Pointer[context.CancelFunc]
|
||||||
}
|
}
|
||||||
|
|
@ -182,9 +181,9 @@ func (s *SignalClient) bridgeStateLoop(statusChan <-chan signalmeow.SignalConnec
|
||||||
peekedConnectionStatus.Event == signalmeow.SignalConnectionEventNone {
|
peekedConnectionStatus.Event == signalmeow.SignalConnectionEventNone {
|
||||||
s.UserLogin.Log.Debug().Msg("Sending TransientDisconnect BridgeState")
|
s.UserLogin.Log.Debug().Msg("Sending TransientDisconnect BridgeState")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateTransientDisconnect, Error: "signal-disconnected"})
|
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateTransientDisconnect})
|
||||||
} else {
|
} else {
|
||||||
s.UserLogin.BridgeState.Send(connectionErrorState(err))
|
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateTransientDisconnect, Error: "unknown-websocket-error", Message: err.Error()})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -192,7 +191,11 @@ func (s *SignalClient) bridgeStateLoop(statusChan <-chan signalmeow.SignalConnec
|
||||||
case signalmeow.SignalConnectionEventLoggedOut:
|
case signalmeow.SignalConnectionEventLoggedOut:
|
||||||
s.stopChatSync()
|
s.stopChatSync()
|
||||||
s.UserLogin.Log.Debug().Msg("Sending BadCredentials BridgeState")
|
s.UserLogin.Log.Debug().Msg("Sending BadCredentials BridgeState")
|
||||||
s.sendLoggedOutState(err)
|
if err == nil {
|
||||||
|
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateBadCredentials, Message: "You have been logged out of Signal, please reconnect"})
|
||||||
|
} else {
|
||||||
|
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateBadCredentials, Message: err.Error()})
|
||||||
|
}
|
||||||
err = s.Client.ClearKeysAndDisconnect(context.TODO())
|
err = s.Client.ClearKeysAndDisconnect(context.TODO())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.UserLogin.Log.Error().Err(err).Msg("Failed to clear keys and disconnect")
|
s.UserLogin.Log.Error().Err(err).Msg("Failed to clear keys and disconnect")
|
||||||
|
|
@ -200,18 +203,18 @@ func (s *SignalClient) bridgeStateLoop(statusChan <-chan signalmeow.SignalConnec
|
||||||
|
|
||||||
case signalmeow.SignalConnectionEventError:
|
case signalmeow.SignalConnectionEventError:
|
||||||
s.UserLogin.Log.Debug().Msg("Sending TransientDisconnect BridgeState")
|
s.UserLogin.Log.Debug().Msg("Sending TransientDisconnect BridgeState")
|
||||||
s.UserLogin.BridgeState.Send(connectionErrorState(err))
|
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateTransientDisconnect, Error: "unknown-websocket-error", Message: err.Error()})
|
||||||
|
|
||||||
case signalmeow.SignalConnectionEventFatalError:
|
case signalmeow.SignalConnectionEventFatalError:
|
||||||
s.UserLogin.Log.Debug().Msg("Sending UnknownError BridgeState")
|
s.UserLogin.Log.Debug().Msg("Sending UnknownError BridgeState")
|
||||||
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateUnknownError, Error: "signal-connection-failed", Info: map[string]any{"go_error": err.Error()}})
|
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateUnknownError, Error: "unknown-websocket-error", Message: err.Error()})
|
||||||
|
|
||||||
case signalmeow.SignalConnectionCleanShutdown:
|
case signalmeow.SignalConnectionCleanShutdown:
|
||||||
if s.Client.IsLoggedIn() {
|
if s.Client.IsLoggedIn() {
|
||||||
s.UserLogin.Log.Debug().Msg("Clean Shutdown - sending no BridgeState")
|
s.UserLogin.Log.Debug().Msg("Clean Shutdown - sending no BridgeState")
|
||||||
} else {
|
} else {
|
||||||
s.UserLogin.Log.Debug().Msg("Clean Shutdown, but logged out - Sending BadCredentials BridgeState")
|
s.UserLogin.Log.Debug().Msg("Clean Shutdown, but logged out - Sending BadCredentials BridgeState")
|
||||||
s.sendLoggedOutState(nil)
|
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateBadCredentials, Message: "You have been logged out of Signal, please reconnect"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -219,10 +222,7 @@ func (s *SignalClient) bridgeStateLoop(statusChan <-chan signalmeow.SignalConnec
|
||||||
|
|
||||||
func (s *SignalClient) Connect(ctx context.Context) {
|
func (s *SignalClient) Connect(ctx context.Context) {
|
||||||
if s.Client == nil {
|
if s.Client == nil {
|
||||||
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateBadCredentials, Error: "signal-not-logged-in", UserAction: status.UserActionRelogin})
|
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateBadCredentials, Message: "You're not logged into Signal"})
|
||||||
return
|
|
||||||
} else if !s.Client.IsLoggedIn() {
|
|
||||||
s.sendLoggedOutState(nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.updateRemoteProfile(ctx, false)
|
s.updateRemoteProfile(ctx, false)
|
||||||
|
|
@ -230,9 +230,6 @@ func (s *SignalClient) Connect(ctx context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) ConnectBackground(ctx context.Context, _ *bridgev2.ConnectBackgroundParams) error {
|
func (s *SignalClient) ConnectBackground(ctx context.Context, _ *bridgev2.ConnectBackgroundParams) error {
|
||||||
if !s.IsLoggedIn() {
|
|
||||||
return bridgev2.ErrNotLoggedIn
|
|
||||||
}
|
|
||||||
s.queueEmptyWaiter.Clear()
|
s.queueEmptyWaiter.Clear()
|
||||||
ch, unauthCh, err := s.Client.StartWebsockets(ctx)
|
ch, unauthCh, err := s.Client.StartWebsockets(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -253,11 +250,6 @@ func (s *SignalClient) ConnectBackground(ctx context.Context, _ *bridgev2.Connec
|
||||||
log.Err(status.Err).Msg("Authed websocket disconnected")
|
log.Err(status.Err).Msg("Authed websocket disconnected")
|
||||||
case web.SignalWebsocketConnectionEventLoggedOut:
|
case web.SignalWebsocketConnectionEventLoggedOut:
|
||||||
log.Err(status.Err).Msg("Authed websocket logged out")
|
log.Err(status.Err).Msg("Authed websocket logged out")
|
||||||
s.sendLoggedOutState(status.Err)
|
|
||||||
err = s.Client.ClearKeysAndDisconnect(ctx)
|
|
||||||
if err != nil {
|
|
||||||
log.Err(err).Msg("Failed to clear keys and disconnect")
|
|
||||||
}
|
|
||||||
return fmt.Errorf("authed websocket logged out: %w", status.Err)
|
return fmt.Errorf("authed websocket logged out: %w", status.Err)
|
||||||
case web.SignalWebsocketConnectionEventError, web.SignalWebsocketConnectionEventFatalError:
|
case web.SignalWebsocketConnectionEventError, web.SignalWebsocketConnectionEventFatalError:
|
||||||
log.Err(status.Err).Msg("Authed websocket error")
|
log.Err(status.Err).Msg("Authed websocket error")
|
||||||
|
|
@ -322,7 +314,7 @@ func (s *SignalClient) tryConnect(ctx context.Context, retryCount int, noLoginSy
|
||||||
ch, err := s.Client.StartReceiveLoops(ctx)
|
ch, err := s.Client.StartReceiveLoops(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to start receive loops")
|
zerolog.Ctx(ctx).Err(err).Msg("Failed to start receive loops")
|
||||||
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateTransientDisconnect, Error: "signal-connect-failed", Info: map[string]any{"go_error": err.Error()}})
|
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateTransientDisconnect, Error: "unknown-websocket-error", Message: err.Error()})
|
||||||
retryInSeconds := 2 << retryCount
|
retryInSeconds := 2 << retryCount
|
||||||
if retryInSeconds > 150 {
|
if retryInSeconds > 150 {
|
||||||
retryInSeconds = 150
|
retryInSeconds = 150
|
||||||
|
|
@ -350,7 +342,7 @@ func (s *SignalClient) tryConnect(ctx context.Context, retryCount int, noLoginSy
|
||||||
if s.Client.Store.EphemeralBackupKey != nil {
|
if s.Client.Store.EphemeralBackupKey != nil {
|
||||||
go func() {
|
go func() {
|
||||||
if s.Client.Store.MasterKey != nil {
|
if s.Client.Store.MasterKey != nil {
|
||||||
s.Client.StorageSync(ctx)
|
s.Client.SyncStorage(ctx)
|
||||||
} else {
|
} else {
|
||||||
s.UserLogin.Log.Warn().Msg("No master key for storage sync before backup sync")
|
s.UserLogin.Log.Warn().Msg("No master key for storage sync before backup sync")
|
||||||
}
|
}
|
||||||
|
|
@ -359,7 +351,7 @@ func (s *SignalClient) tryConnect(ctx context.Context, retryCount int, noLoginSy
|
||||||
} else {
|
} else {
|
||||||
cancel()
|
cancel()
|
||||||
if s.Client.Store.MasterKey != nil {
|
if s.Client.Store.MasterKey != nil {
|
||||||
s.Client.QueueStorageSync(ctx)
|
go s.Client.SyncStorage(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,8 @@ type SignalConfig struct {
|
||||||
DeviceName string `yaml:"device_name"`
|
DeviceName string `yaml:"device_name"`
|
||||||
NoteToSelfAvatar id.ContentURIString `yaml:"note_to_self_avatar"`
|
NoteToSelfAvatar id.ContentURIString `yaml:"note_to_self_avatar"`
|
||||||
LocationFormat string `yaml:"location_format"`
|
LocationFormat string `yaml:"location_format"`
|
||||||
|
DisappearViewOnce bool `yaml:"disappear_view_once"`
|
||||||
ExtEvPolls bool `yaml:"extev_polls"`
|
ExtEvPolls bool `yaml:"extev_polls"`
|
||||||
IdentityChangeNotices bool `yaml:"identity_change_notices"`
|
|
||||||
|
|
||||||
displaynameTemplate *template.Template `yaml:"-"`
|
displaynameTemplate *template.Template `yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
@ -75,16 +75,13 @@ type DisplaynameParams struct {
|
||||||
AboutEmoji string
|
AboutEmoji string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *SignalConfig) FormatDisplayname(contact *types.Recipient, username string) string {
|
func (c *SignalConfig) FormatDisplayname(contact *types.Recipient) string {
|
||||||
if username != "" && !strings.HasPrefix(username, "@") {
|
|
||||||
username = "@" + username
|
|
||||||
}
|
|
||||||
var nameBuf strings.Builder
|
var nameBuf strings.Builder
|
||||||
err := c.displaynameTemplate.Execute(&nameBuf, &DisplaynameParams{
|
err := c.displaynameTemplate.Execute(&nameBuf, &DisplaynameParams{
|
||||||
ProfileName: contact.Profile.Name,
|
ProfileName: contact.Profile.Name,
|
||||||
ContactName: contact.ContactName,
|
ContactName: contact.ContactName,
|
||||||
Nickname: contact.Nickname,
|
Nickname: contact.Nickname,
|
||||||
Username: username,
|
Username: "",
|
||||||
PhoneNumber: contact.E164,
|
PhoneNumber: contact.E164,
|
||||||
UUID: contact.ACI.String(),
|
UUID: contact.ACI.String(),
|
||||||
ACI: contact.ACI.String(),
|
ACI: contact.ACI.String(),
|
||||||
|
|
@ -106,8 +103,8 @@ func upgradeConfig(helper up.Helper) {
|
||||||
helper.Copy(up.Str, "device_name")
|
helper.Copy(up.Str, "device_name")
|
||||||
helper.Copy(up.Str, "note_to_self_avatar")
|
helper.Copy(up.Str, "note_to_self_avatar")
|
||||||
helper.Copy(up.Str, "location_format")
|
helper.Copy(up.Str, "location_format")
|
||||||
|
helper.Copy(up.Bool, "disappear_view_once")
|
||||||
helper.Copy(up.Bool, "extev_polls")
|
helper.Copy(up.Bool, "extev_polls")
|
||||||
helper.Copy(up.Bool, "identity_change_notices")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalConnector) GetConfig() (string, any, up.Upgrader) {
|
func (s *SignalConnector) GetConfig() (string, any, up.Upgrader) {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
@ -45,9 +44,6 @@ type SignalConnector struct {
|
||||||
Store *store.Container
|
Store *store.Container
|
||||||
Bridge *bridgev2.Bridge
|
Bridge *bridgev2.Bridge
|
||||||
Config SignalConfig
|
Config SignalConfig
|
||||||
|
|
||||||
lastTxnTS int64
|
|
||||||
lastTxnTSLock sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ bridgev2.NetworkConnector = (*SignalConnector)(nil)
|
var _ bridgev2.NetworkConnector = (*SignalConnector)(nil)
|
||||||
|
|
@ -70,6 +66,7 @@ func (s *SignalConnector) Init(bridge *bridgev2.Bridge) {
|
||||||
s.Bridge = bridge
|
s.Bridge = bridge
|
||||||
s.MsgConv = msgconv.NewMessageConverter(bridge)
|
s.MsgConv = msgconv.NewMessageConverter(bridge)
|
||||||
s.MsgConv.LocationFormat = s.Config.LocationFormat
|
s.MsgConv.LocationFormat = s.Config.LocationFormat
|
||||||
|
s.MsgConv.DisappearViewOnce = s.Config.DisappearViewOnce
|
||||||
s.MsgConv.ExtEvPolls = s.Config.ExtEvPolls
|
s.MsgConv.ExtEvPolls = s.Config.ExtEvPolls
|
||||||
bridge.Commands.(*commands.Processor).AddHandlers(CmdDiscardSenderKey)
|
bridge.Commands.(*commands.Processor).AddHandlers(CmdDiscardSenderKey)
|
||||||
}
|
}
|
||||||
|
|
@ -94,11 +91,8 @@ func (s *SignalConnector) ResetHTTPTransport() {
|
||||||
settings = hs.GetHTTPClientSettings()
|
settings = hs.GetHTTPClientSettings()
|
||||||
}
|
}
|
||||||
oldClient := web.SignalHTTPClient
|
oldClient := web.SignalHTTPClient
|
||||||
oldUploadClient := web.SignalUploadHTTPClient
|
|
||||||
web.SignalHTTPClient = settings.WithTLSConfig(web.SignalTLSConfig).Compile()
|
web.SignalHTTPClient = settings.WithTLSConfig(web.SignalTLSConfig).Compile()
|
||||||
web.SignalUploadHTTPClient = settings.WithTLSConfig(web.SignalTLSConfig).WithResponseHeaderTimeout(2 * time.Minute).Compile()
|
|
||||||
oldClient.CloseIdleConnections()
|
oldClient.CloseIdleConnections()
|
||||||
oldUploadClient.CloseIdleConnections()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalConnector) ResetNetworkConnections() {
|
func (s *SignalConnector) ResetNetworkConnections() {
|
||||||
|
|
@ -123,7 +117,6 @@ func (s *SignalConnector) LoadUserLogin(ctx context.Context, login *bridgev2.Use
|
||||||
Main: s,
|
Main: s,
|
||||||
UserLogin: login,
|
UserLogin: login,
|
||||||
|
|
||||||
recentMessageRooms: exsync.NewRingBuffer[networkid.MessageID, networkid.PortalKey](32),
|
|
||||||
queueEmptyWaiter: exsync.NewEvent(),
|
queueEmptyWaiter: exsync.NewEvent(),
|
||||||
}
|
}
|
||||||
if device != nil {
|
if device != nil {
|
||||||
|
|
@ -140,8 +133,5 @@ func (s *SignalConnector) LoadUserLogin(ctx context.Context, login *bridgev2.Use
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalConnector) GenerateTransactionID(userID id.UserID, roomID id.RoomID, eventType event.Type) networkid.RawTransactionID {
|
func (s *SignalConnector) GenerateTransactionID(userID id.UserID, roomID id.RoomID, eventType event.Type) networkid.RawTransactionID {
|
||||||
s.lastTxnTSLock.Lock()
|
return networkid.RawTransactionID(strconv.FormatInt(time.Now().UnixMilli(), 10))
|
||||||
defer s.lastTxnTSLock.Unlock()
|
|
||||||
s.lastTxnTS = max(time.Now().UnixMilli(), s.lastTxnTS+1)
|
|
||||||
return networkid.RawTransactionID(strconv.FormatInt(s.lastTxnTS, 10))
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ func (s *SignalConnector) Download(ctx context.Context, mediaID networkid.MediaI
|
||||||
rawDataResp, err = client.Client.DownloadGroupAvatar(ctx, info.GroupAvatarPath, groupMasterKey)
|
rawDataResp, err = client.Client.DownloadGroupAvatar(ctx, info.GroupAvatarPath, groupMasterKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Direct download failed")
|
log.Err(err).Msg("Direct download failed")
|
||||||
return nil, wrapDirectMediaError(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
case *signalid.DirectMediaProfileAvatar:
|
case *signalid.DirectMediaProfileAvatar:
|
||||||
log.Info().
|
log.Info().
|
||||||
|
|
@ -107,7 +107,7 @@ func (s *SignalConnector) Download(ctx context.Context, mediaID networkid.MediaI
|
||||||
rawDataResp, err = client.Client.DownloadUserAvatar(ctx, info.ProfileAvatarPath, *profileKey)
|
rawDataResp, err = client.Client.DownloadUserAvatar(ctx, info.ProfileAvatarPath, *profileKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Direct download failed")
|
log.Err(err).Msg("Direct download failed")
|
||||||
return nil, wrapDirectMediaError(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
case *signalid.DirectMediaSticker:
|
case *signalid.DirectMediaSticker:
|
||||||
log.Info().
|
log.Info().
|
||||||
|
|
@ -118,7 +118,7 @@ func (s *SignalConnector) Download(ctx context.Context, mediaID networkid.MediaI
|
||||||
rawDataResp, err = signalmeow.DownloadStickerPackItem(ctx, info.PackID, info.PackKey, info.StickerID)
|
rawDataResp, err = signalmeow.DownloadStickerPackItem(ctx, info.PackID, info.PackKey, info.StickerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Direct download failed")
|
log.Err(err).Msg("Direct download failed")
|
||||||
return nil, wrapDirectMediaError(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("no downloader for direct media type: %T", info)
|
return nil, fmt.Errorf("no downloader for direct media type: %T", info)
|
||||||
|
|
|
||||||
|
|
@ -1,196 +0,0 @@
|
||||||
// mautrix-signal - A Matrix-Signal puppeting bridge.
|
|
||||||
// Copyright (C) 2026 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package connector
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
grpcstatus "google.golang.org/grpc/status"
|
|
||||||
"maunium.net/go/mautrix"
|
|
||||||
"maunium.net/go/mautrix/bridgev2"
|
|
||||||
"maunium.net/go/mautrix/bridgev2/status"
|
|
||||||
"maunium.net/go/mautrix/event"
|
|
||||||
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow"
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/web"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
status.BridgeStateHumanErrors.Update(status.BridgeStateErrorMap{
|
|
||||||
"signal-logged-out": "You have been logged out of Signal, please reconnect",
|
|
||||||
"signal-not-logged-in": "You're not logged into Signal",
|
|
||||||
"signal-disconnected": "Disconnected from Signal, reconnecting",
|
|
||||||
"signal-connection-error": "Connection to Signal failed, reconnecting",
|
|
||||||
"signal-server-error": "Signal server error, reconnecting",
|
|
||||||
"signal-rate-limited": "Rate limited by Signal, reconnecting",
|
|
||||||
"signal-connect-failed": "Failed to connect to Signal, retrying",
|
|
||||||
"signal-connection-failed": "Connection to Signal failed",
|
|
||||||
"signal-idle-primary-device": "Signal hasn't seen your phone in a long time. The bridge is still connected, " +
|
|
||||||
"but Signal will unlink it if you don't open Signal on your phone soon.",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ status.BridgeStateFiller = (*SignalClient)(nil)
|
|
||||||
|
|
||||||
func (s *SignalClient) FillBridgeState(state status.BridgeState) status.BridgeState {
|
|
||||||
if state.StateEvent == status.StateConnected && s.Client != nil && s.Client.AuthedWS.PrimaryDeviceIdle() {
|
|
||||||
state.StateEvent = status.StateTransientDisconnect
|
|
||||||
state.Error = "signal-idle-primary-device"
|
|
||||||
state.UserAction = status.UserActionOpenNative
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
errEditOthersMessage = bridgev2.WrapErrorInStatus(errors.New("you can only edit your own messages")).
|
|
||||||
WithErrorAsMessage().
|
|
||||||
WithErrorReason(event.MessageStatusNoPermission).
|
|
||||||
WithStatus(event.MessageStatusFail).
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
errDeleteOthersMessage = bridgev2.WrapErrorInStatus(errors.New("you can only delete your own messages")).
|
|
||||||
WithErrorAsMessage().
|
|
||||||
WithErrorReason(event.MessageStatusNoPermission).
|
|
||||||
WithStatus(event.MessageStatusFail).
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
errInviteToDM = bridgev2.WrapErrorInStatus(errors.New("can't invite more users to Signal DMs")).
|
|
||||||
WithErrorAsMessage().
|
|
||||||
WithErrorReason(event.MessageStatusUnsupported).
|
|
||||||
WithStatus(event.MessageStatusFail).
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
errUnsupportedDisappearingType = bridgev2.WrapErrorInStatus(errors.New("only disappearing timers that start after reading are supported on Signal")).
|
|
||||||
WithErrorAsMessage().
|
|
||||||
WithErrorReason(event.MessageStatusUnsupported).
|
|
||||||
WithStatus(event.MessageStatusFail).
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
)
|
|
||||||
|
|
||||||
func wrapConversionError(err error) error {
|
|
||||||
if err == nil || errors.As(err, &bridgev2.MessageStatus{}) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage("failed to convert message for Signal").
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapSendError(err error) error {
|
|
||||||
if err == nil || errors.As(err, &bridgev2.MessageStatus{}) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, signalmeow.ErrUnregisteredUser):
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage("recipient isn't registered on Signal").
|
|
||||||
WithErrorReason(event.MessageStatusNetworkError).
|
|
||||||
WithStatus(event.MessageStatusFail).
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
case errors.Is(err, signalmeow.ErrRateLimited), errors.Is(err, signalmeow.RateLimitError):
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage("rate limited by Signal, try again later").
|
|
||||||
WithErrorReason(event.MessageStatusNetworkError).
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
case errors.Is(err, signalmeow.AuthorizationFailedError):
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage("you don't have permission to do that in this Signal group").
|
|
||||||
WithErrorReason(event.MessageStatusNoPermission).
|
|
||||||
WithStatus(event.MessageStatusFail).
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
case errors.Is(err, signalmeow.GroupPatchNotAcceptedError):
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage("Signal rejected the group change").
|
|
||||||
WithErrorReason(event.MessageStatusNetworkError).
|
|
||||||
WithStatus(event.MessageStatusFail).
|
|
||||||
WithIsCertain(true).
|
|
||||||
WithSendNotice(true)
|
|
||||||
default:
|
|
||||||
return bridgev2.WrapErrorInStatus(err).
|
|
||||||
WithMessage("failed to send to Signal").
|
|
||||||
WithSendNotice(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapRespError(err error) error {
|
|
||||||
var mapped bridgev2.RespError
|
|
||||||
var cdsRateLimit signalmeow.ContactDiscoveryRateLimitError
|
|
||||||
switch {
|
|
||||||
case errors.As(err, &cdsRateLimit), errors.Is(err, signalmeow.RateLimitError), errors.Is(err, signalmeow.ErrRateLimited),
|
|
||||||
grpcstatus.Code(err) == codes.ResourceExhausted:
|
|
||||||
mapped = bridgev2.RespError(mautrix.MLimitExceeded.WithMessage("Rate limited by Signal, please try again later").WithStatus(http.StatusBadRequest))
|
|
||||||
case errors.Is(err, signalmeow.AuthorizationFailedError):
|
|
||||||
mapped = bridgev2.RespError(mautrix.MForbidden.WithMessage("Signal denied the request"))
|
|
||||||
default:
|
|
||||||
mapped = bridgev2.RespError(mautrix.MUnknown.WithMessage("Request to Signal failed"))
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%w: %w", mapped, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapDirectMediaError(err error) error {
|
|
||||||
var statusErr web.HTTPStatusError
|
|
||||||
if !errors.As(err, &statusErr) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch statusErr.StatusCode {
|
|
||||||
case http.StatusNotFound, http.StatusGone:
|
|
||||||
return mautrix.MNotFound.WithMessage("Media not found on Signal")
|
|
||||||
case http.StatusUnauthorized, http.StatusForbidden:
|
|
||||||
return mautrix.MForbidden.WithMessage("Signal denied access to the media")
|
|
||||||
case http.StatusTooManyRequests:
|
|
||||||
return mautrix.MLimitExceeded.WithMessage("Rate limited by Signal, please try again later").WithCanRetry(true)
|
|
||||||
default:
|
|
||||||
return mautrix.MUnknown.WithMessage("Unexpected response from Signal").WithStatus(http.StatusBadGateway).WithCanRetry(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func connectionErrorState(err error) status.BridgeState {
|
|
||||||
state := status.BridgeState{
|
|
||||||
StateEvent: status.StateTransientDisconnect,
|
|
||||||
Error: "signal-connection-error",
|
|
||||||
Info: map[string]any{"go_error": err.Error()},
|
|
||||||
}
|
|
||||||
var statusErr web.HTTPStatusError
|
|
||||||
if errors.As(err, &statusErr) {
|
|
||||||
if statusErr.StatusCode == http.StatusTooManyRequests {
|
|
||||||
state.Error = "signal-rate-limited"
|
|
||||||
} else if statusErr.StatusCode >= 500 {
|
|
||||||
state.Error = "signal-server-error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SignalClient) sendLoggedOutState(err error) {
|
|
||||||
state := status.BridgeState{
|
|
||||||
StateEvent: status.StateBadCredentials,
|
|
||||||
Error: "signal-logged-out",
|
|
||||||
UserAction: status.UserActionRelogin,
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
state.Info = map[string]any{"go_error": err.Error()}
|
|
||||||
}
|
|
||||||
s.UserLogin.BridgeState.Send(state)
|
|
||||||
}
|
|
||||||
|
|
@ -2,11 +2,10 @@
|
||||||
# {{.ProfileName}} - The Signal profile name set by the user.
|
# {{.ProfileName}} - The Signal profile name set by the user.
|
||||||
# {{.ContactName}} - The name for the user from your phone's contact list. This is not safe on multi-user instances.
|
# {{.ContactName}} - The name for the user from your phone's contact list. This is not safe on multi-user instances.
|
||||||
# {{.Nickname}} - The nickname set for the user in the native Signal app. This is not safe on multi-user instances.
|
# {{.Nickname}} - The nickname set for the user in the native Signal app. This is not safe on multi-user instances.
|
||||||
# {{.Username}} - The username of the user. Only applicable when starting chats.
|
|
||||||
# {{.PhoneNumber}} - The phone number of the user.
|
# {{.PhoneNumber}} - The phone number of the user.
|
||||||
# {{.UUID}} - The UUID of the Signal user.
|
# {{.UUID}} - The UUID of the Signal user.
|
||||||
# {{.AboutEmoji}} - The emoji set by the user in their profile.
|
# {{.AboutEmoji}} - The emoji set by the user in their profile.
|
||||||
displayname_template: '{{or .ProfileName .PhoneNumber .Username "Unknown user"}}'
|
displayname_template: '{{or .ProfileName .PhoneNumber "Unknown user"}}'
|
||||||
# Should avatars from the user's contact list be used? This is not safe on multi-user instances.
|
# Should avatars from the user's contact list be used? This is not safe on multi-user instances.
|
||||||
use_contact_avatars: false
|
use_contact_avatars: false
|
||||||
# Should the bridge request the user's contact list from the phone on startup?
|
# Should the bridge request the user's contact list from the phone on startup?
|
||||||
|
|
@ -23,7 +22,7 @@ note_to_self_avatar: mxc://maunium.net/REBIVrqjZwmaWpssCZpBlmlL
|
||||||
# Google Maps: 'https://www.google.com/maps/place/%[1]s,%[2]s'
|
# Google Maps: 'https://www.google.com/maps/place/%[1]s,%[2]s'
|
||||||
# OpenStreetMap: 'https://www.openstreetmap.org/?mlat=%[1]s&mlon=%[2]s'
|
# OpenStreetMap: 'https://www.openstreetmap.org/?mlat=%[1]s&mlon=%[2]s'
|
||||||
location_format: 'https://www.google.com/maps/place/%[1]s,%[2]s'
|
location_format: 'https://www.google.com/maps/place/%[1]s,%[2]s'
|
||||||
|
# Should view-once messages disappear shortly after sending a read receipt on Matrix?
|
||||||
|
disappear_view_once: false
|
||||||
# Should polls be sent using unstable MSC3381 event types?
|
# Should polls be sent using unstable MSC3381 event types?
|
||||||
extev_polls: false
|
extev_polls: false
|
||||||
# Should another user's safety number changing send a message to Matrix?
|
|
||||||
identity_change_notices: false
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,6 @@ import (
|
||||||
|
|
||||||
var defaultPL = 0
|
var defaultPL = 0
|
||||||
var moderatorPL = 50
|
var moderatorPL = 50
|
||||||
var terminatedPL = 100
|
|
||||||
|
|
||||||
func roleToPL(role signalmeow.GroupMemberRole) *int {
|
func roleToPL(role signalmeow.GroupMemberRole) *int {
|
||||||
switch role {
|
switch role {
|
||||||
|
|
@ -58,9 +57,6 @@ func applyAnnouncementsOnly(plc *bridgev2.PowerLevelOverrides, announcementsOnly
|
||||||
} else {
|
} else {
|
||||||
plc.EventsDefault = &defaultPL
|
plc.EventsDefault = &defaultPL
|
||||||
}
|
}
|
||||||
plc.Events[event.EventReaction] = defaultPL
|
|
||||||
plc.Events[event.EventRedaction] = defaultPL
|
|
||||||
plc.Events[event.EventUnstablePollResponse] = defaultPL
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyAttributesAccess(plc *bridgev2.PowerLevelOverrides, attributeAccess signalmeow.AccessControl) {
|
func applyAttributesAccess(plc *bridgev2.PowerLevelOverrides, attributeAccess signalmeow.AccessControl) {
|
||||||
|
|
@ -74,21 +70,6 @@ func applyAttributesAccess(plc *bridgev2.PowerLevelOverrides, attributeAccess si
|
||||||
plc.Events[event.StateBeeperDisappearingTimer] = attributePL
|
plc.Events[event.StateBeeperDisappearingTimer] = attributePL
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyTerminated(plc *bridgev2.PowerLevelOverrides) {
|
|
||||||
plc.EventsDefault = &terminatedPL
|
|
||||||
plc.StateDefault = &terminatedPL
|
|
||||||
plc.Invite = &terminatedPL
|
|
||||||
plc.Kick = &terminatedPL
|
|
||||||
plc.Ban = &terminatedPL
|
|
||||||
plc.Redact = &terminatedPL
|
|
||||||
for _, evtType := range []event.Type{
|
|
||||||
event.EventReaction, event.EventRedaction, event.EventUnstablePollResponse, event.StatePowerLevels, event.StateRoomName,
|
|
||||||
event.StateRoomAvatar, event.StateTopic, event.StateBeeperDisappearingTimer,
|
|
||||||
} {
|
|
||||||
plc.Events[evtType] = terminatedPL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyMembersAccess(plc *bridgev2.PowerLevelOverrides, memberAccess signalmeow.AccessControl) {
|
func applyMembersAccess(plc *bridgev2.PowerLevelOverrides, memberAccess signalmeow.AccessControl) {
|
||||||
if memberAccess == signalmeow.AccessControl_ADMINISTRATOR {
|
if memberAccess == signalmeow.AccessControl_ADMINISTRATOR {
|
||||||
plc.Invite = &moderatorPL
|
plc.Invite = &moderatorPL
|
||||||
|
|
@ -142,9 +123,6 @@ func (s *SignalClient) wrapGroupInfo(ctx context.Context, groupInfo *signalmeow.
|
||||||
applyMembersAccess(members.PowerLevels, groupInfo.AccessControl.Members)
|
applyMembersAccess(members.PowerLevels, groupInfo.AccessControl.Members)
|
||||||
joinRule = inviteLinkToJoinRule(groupInfo.AccessControl.AddFromInviteLink)
|
joinRule = inviteLinkToJoinRule(groupInfo.AccessControl.AddFromInviteLink)
|
||||||
}
|
}
|
||||||
if groupInfo.Terminated {
|
|
||||||
applyTerminated(members.PowerLevels)
|
|
||||||
}
|
|
||||||
for _, member := range groupInfo.RequestingMembers {
|
for _, member := range groupInfo.RequestingMembers {
|
||||||
members.MemberMap.Set(bridgev2.ChatMember{
|
members.MemberMap.Set(bridgev2.ChatMember{
|
||||||
EventSender: s.makeEventSender(member.ACI),
|
EventSender: s.makeEventSender(member.ACI),
|
||||||
|
|
@ -204,12 +182,9 @@ func addMemberToMap(mc map[networkid.UserID]bridgev2.ChatMember, member bridgev2
|
||||||
mc[member.EventSender.Sender] = member
|
mc[member.EventSender.Sender] = member
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentPortalInfoVersion = 3
|
|
||||||
|
|
||||||
func updatePortalSyncMeta(ctx context.Context, portal *bridgev2.Portal) bool {
|
func updatePortalSyncMeta(ctx context.Context, portal *bridgev2.Portal) bool {
|
||||||
meta := portal.Metadata.(*signalid.PortalMetadata)
|
meta := portal.Metadata.(*signalid.PortalMetadata)
|
||||||
meta.LastSync = jsontime.UnixNow()
|
meta.LastSync = jsontime.UnixNow()
|
||||||
meta.InfoVersion = currentPortalInfoVersion
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,8 +260,7 @@ func (s *SignalClient) groupChangeToChatInfoChange(ctx context.Context, groupID
|
||||||
var pls *bridgev2.PowerLevelOverrides
|
var pls *bridgev2.PowerLevelOverrides
|
||||||
if groupChange.ModifyAnnouncementsOnly != nil ||
|
if groupChange.ModifyAnnouncementsOnly != nil ||
|
||||||
groupChange.ModifyAttributesAccess != nil ||
|
groupChange.ModifyAttributesAccess != nil ||
|
||||||
groupChange.ModifyMemberAccess != nil ||
|
groupChange.ModifyMemberAccess != nil {
|
||||||
groupChange.TerminateGroup {
|
|
||||||
pls = &bridgev2.PowerLevelOverrides{Events: make(map[event.Type]int)}
|
pls = &bridgev2.PowerLevelOverrides{Events: make(map[event.Type]int)}
|
||||||
if groupChange.ModifyAnnouncementsOnly != nil {
|
if groupChange.ModifyAnnouncementsOnly != nil {
|
||||||
applyAnnouncementsOnly(pls, *groupChange.ModifyAnnouncementsOnly)
|
applyAnnouncementsOnly(pls, *groupChange.ModifyAnnouncementsOnly)
|
||||||
|
|
@ -297,9 +271,6 @@ func (s *SignalClient) groupChangeToChatInfoChange(ctx context.Context, groupID
|
||||||
if groupChange.ModifyMemberAccess != nil {
|
if groupChange.ModifyMemberAccess != nil {
|
||||||
applyMembersAccess(pls, *groupChange.ModifyMemberAccess)
|
applyMembersAccess(pls, *groupChange.ModifyMemberAccess)
|
||||||
}
|
}
|
||||||
if groupChange.TerminateGroup {
|
|
||||||
applyTerminated(pls)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if groupChange.ModifyAddFromInviteLinkAccess != nil {
|
if groupChange.ModifyAddFromInviteLinkAccess != nil {
|
||||||
ic.ChatInfo.JoinRule = &event.JoinRulesEventContent{
|
ic.ChatInfo.JoinRule = &event.JoinRulesEventContent{
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,9 @@ import (
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
||||||
"go.mau.fi/mautrix-signal/pkg/msgconv"
|
|
||||||
"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"
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf/signalpb"
|
"go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf/signalpb"
|
||||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -58,8 +56,6 @@ var (
|
||||||
_ bridgev2.DeleteChatHandlingNetworkAPI = (*SignalClient)(nil)
|
_ bridgev2.DeleteChatHandlingNetworkAPI = (*SignalClient)(nil)
|
||||||
_ bridgev2.PollHandlingNetworkAPI = (*SignalClient)(nil)
|
_ bridgev2.PollHandlingNetworkAPI = (*SignalClient)(nil)
|
||||||
_ bridgev2.MessageRequestAcceptingNetworkAPI = (*SignalClient)(nil)
|
_ bridgev2.MessageRequestAcceptingNetworkAPI = (*SignalClient)(nil)
|
||||||
_ bridgev2.UserBlockingNetworkAPI = (*SignalClient)(nil)
|
|
||||||
_ bridgev2.ViewLimitedMediaHandlingNetworkAPI = (*SignalClient)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *SignalClient) sendMessage(ctx context.Context, portalID networkid.PortalID, content *signalpb.Content) error {
|
func (s *SignalClient) sendMessage(ctx context.Context, portalID networkid.PortalID, content *signalpb.Content) error {
|
||||||
|
|
@ -82,7 +78,7 @@ func (s *SignalClient) sendMessage(ctx context.Context, portalID networkid.Porta
|
||||||
log.Debug().Msg("No successes or failures - Probably sent to myself")
|
log.Debug().Msg("No successes or failures - Probably sent to myself")
|
||||||
} else if len(result.SuccessfullySentTo) == 0 {
|
} else if len(result.SuccessfullySentTo) == 0 {
|
||||||
log.Error().Msg("Failed to send event to all members of Signal group")
|
log.Error().Msg("Failed to send event to all members of Signal group")
|
||||||
return fmt.Errorf("failed to send to any members of Signal group: %w", result.FailureError())
|
return errors.New("failed to send to any members of Signal group")
|
||||||
} else if len(result.SuccessfullySentTo) < totalRecipients {
|
} else if len(result.SuccessfullySentTo) < totalRecipients {
|
||||||
if len(result.FailedToSendTo) > 0 {
|
if len(result.FailedToSendTo) > 0 {
|
||||||
log.Warn().Msg("Failed to send event to some members of Signal group")
|
log.Warn().Msg("Failed to send event to some members of Signal group")
|
||||||
|
|
@ -96,11 +92,8 @@ func (s *SignalClient) sendMessage(ctx context.Context, portalID networkid.Porta
|
||||||
} else {
|
} else {
|
||||||
res := s.Client.SendMessage(ctx, userID, content)
|
res := s.Client.SendMessage(ctx, userID, content)
|
||||||
if !res.WasSuccessful {
|
if !res.WasSuccessful {
|
||||||
if res.Error != nil {
|
|
||||||
return res.Error
|
return res.Error
|
||||||
}
|
}
|
||||||
return errors.New("failed to send message")
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -124,11 +117,10 @@ func (s *SignalClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Ma
|
||||||
ctx, s.Client, msg.Portal, msg.Event, msg.Content, msg.OrigSender != nil, msg.ReplyTo,
|
ctx, s.Client, msg.Portal, msg.Event, msg.Content, msg.OrigSender != nil, msg.ReplyTo,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, wrapConversionError(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
return s.doSendMessage(ctx, msg, converted, &signalid.MessageMetadata{
|
return s.doSendMessage(ctx, msg, converted, &signalid.MessageMetadata{
|
||||||
ContainsAttachments: msgconv.ContainsAttachments(converted),
|
ContainsAttachments: len(converted.Attachments) > 0,
|
||||||
ViewOnce: converted.GetIsViewOnce(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,7 +139,7 @@ func (s *SignalClient) doSendMessage(
|
||||||
msg.AddPendingToIgnore(networkid.TransactionID(msgID))
|
msg.AddPendingToIgnore(networkid.TransactionID(msgID))
|
||||||
err := s.sendMessage(ctx, msg.Portal.ID, signalmeow.WrapDataMessage(converted))
|
err := s.sendMessage(ctx, msg.Portal.ID, signalmeow.WrapDataMessage(converted))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, wrapSendError(err)
|
return nil, bridgev2.WrapErrorInStatus(err).WithSendNotice(true)
|
||||||
}
|
}
|
||||||
dbMsg := &database.Message{
|
dbMsg := &database.Message{
|
||||||
ID: msgID,
|
ID: msgID,
|
||||||
|
|
@ -161,34 +153,12 @@ func (s *SignalClient) doSendMessage(
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) HandleMatrixViewLimitedMedia(ctx context.Context, msg *bridgev2.MatrixViewLimitedMedia) error {
|
|
||||||
meta, ok := msg.Message.Metadata.(*signalid.MessageMetadata)
|
|
||||||
if !ok || !meta.ViewOnce || !meta.ContainsAttachments || msg.Content == nil || *msg.Content != (event.BeeperViewLimitedMedia{Type: "count", Count: 1}) {
|
|
||||||
return bridgev2.ErrUnsupportedViewLimitedType
|
|
||||||
}
|
|
||||||
sender, timestamp, err := signalid.ParseMessageID(msg.Message.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return wrapSendError(s.sendMessage(ctx, signalid.MakeDMPortalID(libsignalgo.NewACIServiceID(sender)), &signalpb.Content{
|
|
||||||
Content: &signalpb.Content_ReceiptMessage{
|
|
||||||
ReceiptMessage: &signalpb.ReceiptMessage{
|
|
||||||
Type: signalpb.ReceiptMessage_VIEWED.Enum(),
|
|
||||||
Timestamp: []uint64{timestamp},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SignalClient) HandleMatrixEdit(ctx context.Context, msg *bridgev2.MatrixEdit) error {
|
func (s *SignalClient) HandleMatrixEdit(ctx context.Context, msg *bridgev2.MatrixEdit) error {
|
||||||
if msg.Content.BeeperViewLimited != nil || msg.EditTarget.Metadata.(*signalid.MessageMetadata).ViewOnce {
|
|
||||||
return fmt.Errorf("%w of view-once media", bridgev2.ErrEditsNotSupported)
|
|
||||||
}
|
|
||||||
_, targetSentTimestamp, err := signalid.ParseMessageID(msg.EditTarget.ID)
|
_, targetSentTimestamp, err := signalid.ParseMessageID(msg.EditTarget.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse target message ID: %w", err)
|
return fmt.Errorf("failed to parse target message ID: %w", err)
|
||||||
} else if msg.EditTarget.SenderID != signalid.MakeUserID(s.Client.Store.ACI) {
|
} else if msg.EditTarget.SenderID != signalid.MakeUserID(s.Client.Store.ACI) {
|
||||||
return errEditOthersMessage
|
return fmt.Errorf("cannot edit other people's messages")
|
||||||
}
|
}
|
||||||
var replyTo *database.Message
|
var replyTo *database.Message
|
||||||
if msg.EditTarget.ReplyTo.MessageID != "" {
|
if msg.EditTarget.ReplyTo.MessageID != "" {
|
||||||
|
|
@ -199,7 +169,7 @@ func (s *SignalClient) HandleMatrixEdit(ctx context.Context, msg *bridgev2.Matri
|
||||||
}
|
}
|
||||||
converted, err := s.Main.MsgConv.ToSignal(ctx, s.Client, msg.Portal, msg.Event, msg.Content, msg.OrigSender != nil, replyTo)
|
converted, err := s.Main.MsgConv.ToSignal(ctx, s.Client, msg.Portal, msg.Event, msg.Content, msg.OrigSender != nil, replyTo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapConversionError(err)
|
return err
|
||||||
}
|
}
|
||||||
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
||||||
converted.Timestamp = &ts
|
converted.Timestamp = &ts
|
||||||
|
|
@ -208,11 +178,11 @@ func (s *SignalClient) HandleMatrixEdit(ctx context.Context, msg *bridgev2.Matri
|
||||||
DataMessage: converted,
|
DataMessage: converted,
|
||||||
}))
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapSendError(err)
|
return bridgev2.WrapErrorInStatus(err).WithSendNotice(true)
|
||||||
}
|
}
|
||||||
prevID := msg.EditTarget.ID
|
prevID := msg.EditTarget.ID
|
||||||
msg.EditTarget.ID = signalid.MakeMessageID(s.Client.Store.ACI, ts)
|
msg.EditTarget.ID = signalid.MakeMessageID(s.Client.Store.ACI, ts)
|
||||||
msg.EditTarget.Metadata = &signalid.MessageMetadata{ContainsAttachments: msgconv.ContainsAttachments(converted)}
|
msg.EditTarget.Metadata = &signalid.MessageMetadata{ContainsAttachments: len(converted.Attachments) > 0}
|
||||||
msg.EditTarget.EditCount++
|
msg.EditTarget.EditCount++
|
||||||
if prevID != msg.EditTarget.ID {
|
if prevID != msg.EditTarget.ID {
|
||||||
err = s.Main.Bridge.DB.DoTxn(ctx, nil, func(ctx context.Context) error {
|
err = s.Main.Bridge.DB.DoTxn(ctx, nil, func(ctx context.Context) error {
|
||||||
|
|
@ -277,7 +247,7 @@ func (s *SignalClient) HandleMatrixReaction(ctx context.Context, msg *bridgev2.M
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, wrapSendError(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
return &database.Reaction{}, nil
|
return &database.Reaction{}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -298,52 +268,30 @@ func (s *SignalClient) HandleMatrixReactionRemove(ctx context.Context, msg *brid
|
||||||
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
return wrapSendError(err)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) HandleMatrixMessageRemove(ctx context.Context, msg *bridgev2.MatrixMessageRemove) error {
|
func (s *SignalClient) HandleMatrixMessageRemove(ctx context.Context, msg *bridgev2.MatrixMessageRemove) error {
|
||||||
targetAuthorACI, targetSentTimestamp, err := signalid.ParseMessageID(msg.TargetMessage.ID)
|
_, targetSentTimestamp, err := signalid.ParseMessageID(msg.TargetMessage.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse target message ID: %w", err)
|
return fmt.Errorf("failed to parse target message ID: %w", err)
|
||||||
|
} else if msg.TargetMessage.SenderID != signalid.MakeUserID(s.Client.Store.ACI) {
|
||||||
|
return fmt.Errorf("cannot delete other people's messages")
|
||||||
}
|
}
|
||||||
dm := &signalpb.DataMessage{
|
ts := getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)
|
||||||
Timestamp: proto.Uint64(getTimestampForEvent(msg.InputTransactionID, msg.Event, msg.OrigSender)),
|
err = s.sendMessage(ctx, msg.Portal.ID, signalmeow.WrapDataMessage(&signalpb.DataMessage{
|
||||||
}
|
Timestamp: proto.Uint64(ts),
|
||||||
if msg.TargetMessage.SenderID == signalid.MakeUserID(s.Client.Store.ACI) {
|
Delete: &signalpb.DataMessage_Delete{
|
||||||
dm.Delete = &signalpb.DataMessage_Delete{
|
|
||||||
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
||||||
}
|
},
|
||||||
} else if isAdmin, err := s.isGroupAdmin(ctx, msg.Portal); err != nil {
|
}))
|
||||||
return wrapSendError(err)
|
|
||||||
} else if !isAdmin {
|
|
||||||
return errDeleteOthersMessage
|
|
||||||
} else {
|
|
||||||
dm.AdminDelete = &signalpb.DataMessage_AdminDelete{
|
|
||||||
TargetAuthorAciBinary: targetAuthorACI[:],
|
|
||||||
TargetSentTimestamp: proto.Uint64(targetSentTimestamp),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = s.sendMessage(ctx, msg.Portal.ID, signalmeow.WrapDataMessage(dm))
|
|
||||||
return wrapSendError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SignalClient) isGroupAdmin(ctx context.Context, portal *bridgev2.Portal) (bool, error) {
|
|
||||||
_, groupID, err := signalid.ParsePortalID(portal.ID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return err
|
||||||
} else if groupID == "" {
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
group, _, err := s.Client.RetrieveGroupByID(ctx, groupID, portal.Metadata.(*signalid.PortalMetadata).Revision)
|
return nil
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to get group info: %w", err)
|
|
||||||
}
|
|
||||||
for _, member := range group.Members {
|
|
||||||
if member.ACI == s.Client.Store.ACI {
|
|
||||||
return member.Role == signalmeow.GroupMember_ADMINISTRATOR, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) HandleMatrixReadReceipt(ctx context.Context, receipt *bridgev2.MatrixReadReceipt) error {
|
func (s *SignalClient) HandleMatrixReadReceipt(ctx context.Context, receipt *bridgev2.MatrixReadReceipt) error {
|
||||||
|
|
@ -426,7 +374,7 @@ func (s *SignalClient) handleMatrixRoomMeta(ctx context.Context, portal *bridgev
|
||||||
gc.Revision = portal.Metadata.(*signalid.PortalMetadata).Revision + 1
|
gc.Revision = portal.Metadata.(*signalid.PortalMetadata).Revision + 1
|
||||||
revision, err := s.Client.UpdateGroup(ctx, gc, groupID)
|
revision, err := s.Client.UpdateGroup(ctx, gc, groupID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, wrapSendError(err)
|
return false, err
|
||||||
}
|
}
|
||||||
if gc.ModifyTitle != nil {
|
if gc.ModifyTitle != nil {
|
||||||
portal.Name = *gc.ModifyTitle
|
portal.Name = *gc.ModifyTitle
|
||||||
|
|
@ -468,7 +416,7 @@ func (s *SignalClient) HandleMatrixRoomAvatar(ctx context.Context, msg *bridgev2
|
||||||
avatarHash = sha256.Sum256(data)
|
avatarHash = sha256.Sum256(data)
|
||||||
avatarPath, err = s.Client.UploadGroupAvatar(ctx, data, groupID, "")
|
avatarPath, err = s.Client.UploadGroupAvatar(ctx, data, groupID, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, wrapSendError(fmt.Errorf("failed to reupload avatar: %w", err))
|
return false, fmt.Errorf("failed to reupload avatar: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s.handleMatrixRoomMeta(ctx, msg.Portal, &signalmeow.GroupChange{
|
return s.handleMatrixRoomMeta(ctx, msg.Portal, &signalmeow.GroupChange{
|
||||||
|
|
@ -495,7 +443,7 @@ func (s *SignalClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2
|
||||||
if msg.Portal.RoomType == database.RoomTypeDM {
|
if msg.Portal.RoomType == database.RoomTypeDM {
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case bridgev2.Invite:
|
case bridgev2.Invite:
|
||||||
return nil, errInviteToDM
|
return nil, fmt.Errorf("cannot invite additional user to dm")
|
||||||
default:
|
default:
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
@ -624,7 +572,7 @@ func (s *SignalClient) HandleMatrixMembership(ctx context.Context, msg *bridgev2
|
||||||
gc.Revision = msg.Portal.Metadata.(*signalid.PortalMetadata).Revision + 1
|
gc.Revision = msg.Portal.Metadata.(*signalid.PortalMetadata).Revision + 1
|
||||||
revision, err := s.Client.UpdateGroup(ctx, gc, groupID)
|
revision, err := s.Client.UpdateGroup(ctx, gc, groupID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, wrapSendError(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
if (msg.Type == bridgev2.Invite || msg.Type == bridgev2.AcceptKnock) && targetSignalID.Type != libsignalgo.ServiceIDTypePNI {
|
if (msg.Type == bridgev2.Invite || msg.Type == bridgev2.AcceptKnock) && targetSignalID.Type != libsignalgo.ServiceIDTypePNI {
|
||||||
err = targetIntent.EnsureJoined(ctx, msg.Portal.MXID)
|
err = targetIntent.EnsureJoined(ctx, msg.Portal.MXID)
|
||||||
|
|
@ -695,7 +643,7 @@ func (s *SignalClient) HandleMatrixPowerLevels(ctx context.Context, msg *bridgev
|
||||||
}
|
}
|
||||||
revision, err := s.Client.UpdateGroup(ctx, gc, groupID)
|
revision, err := s.Client.UpdateGroup(ctx, gc, groupID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, wrapSendError(err)
|
return false, err
|
||||||
}
|
}
|
||||||
msg.Portal.Metadata.(*signalid.PortalMetadata).Revision = revision
|
msg.Portal.Metadata.(*signalid.PortalMetadata).Revision = revision
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|
@ -746,7 +694,7 @@ func (s *SignalClient) HandleMatrixViewingChat(ctx context.Context, msg *bridgev
|
||||||
|
|
||||||
func (s *SignalClient) HandleMatrixDisappearingTimer(ctx context.Context, msg *bridgev2.MatrixDisappearingTimer) (bool, error) {
|
func (s *SignalClient) HandleMatrixDisappearingTimer(ctx context.Context, msg *bridgev2.MatrixDisappearingTimer) (bool, error) {
|
||||||
if msg.Content.Type != event.DisappearingTypeAfterRead && msg.Content.Timer.Duration != 0 {
|
if msg.Content.Type != event.DisappearingTypeAfterRead && msg.Content.Timer.Duration != 0 {
|
||||||
return false, errUnsupportedDisappearingType
|
return false, fmt.Errorf("unsupported disappearing timer type: %s", msg.Content.Type)
|
||||||
}
|
}
|
||||||
userID, groupID, err := signalid.ParsePortalID(msg.Portal.ID)
|
userID, groupID, err := signalid.ParsePortalID(msg.Portal.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -773,7 +721,7 @@ func (s *SignalClient) HandleMatrixDisappearingTimer(ctx context.Context, msg *b
|
||||||
ExpireTimer: ptr.Ptr(uint32(msg.Content.Timer.Seconds())),
|
ExpireTimer: ptr.Ptr(uint32(msg.Content.Timer.Seconds())),
|
||||||
}))
|
}))
|
||||||
if !res.WasSuccessful {
|
if !res.WasSuccessful {
|
||||||
return false, wrapSendError(res.Error)
|
return false, res.Error
|
||||||
}
|
}
|
||||||
msg.Portal.Disappear = newSetting
|
msg.Portal.Disappear = newSetting
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|
@ -786,29 +734,11 @@ func (s *SignalClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridgev2
|
||||||
return fmt.Errorf("failed to parse portal ID: %w", err)
|
return fmt.Errorf("failed to parse portal ID: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if groupID != "" {
|
|
||||||
memberInfo, err := s.Main.Bridge.Matrix.GetMemberInfo(ctx, msg.Portal.MXID, s.UserLogin.UserMXID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get own member info: %w", err)
|
|
||||||
} else if memberInfo.Membership == event.MembershipJoin {
|
|
||||||
portalMeta := msg.Portal.Metadata.(*signalid.PortalMetadata)
|
|
||||||
ownACI := s.Client.Store.ACI
|
|
||||||
revision, err := s.Client.UpdateGroup(ctx, &signalmeow.GroupChange{
|
|
||||||
DeleteMembers: []*uuid.UUID{&ownACI},
|
|
||||||
Revision: portalMeta.Revision + 1,
|
|
||||||
}, groupID)
|
|
||||||
if err != nil {
|
|
||||||
return wrapSendError(fmt.Errorf("failed to leave group before deleting chat: %w", err))
|
|
||||||
}
|
|
||||||
portalMeta.Revision = revision
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.Content.FromMessageRequest {
|
if msg.Content.FromMessageRequest {
|
||||||
// TODO block and delete support?
|
// TODO block and delete support?
|
||||||
err = s.syncMessageRequestResponse(ctx, msg.Portal, signalpb.SyncMessage_MessageRequestResponse_DELETE)
|
err = s.syncMessageRequestResponse(ctx, msg.Portal, signalpb.SyncMessage_MessageRequestResponse_DELETE)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapSendError(fmt.Errorf("failed to send message request delete sync: %w", err))
|
return fmt.Errorf("failed to send message request delete sync: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -886,7 +816,7 @@ func (s *SignalClient) HandleMatrixDeleteChat(ctx context.Context, msg *bridgev2
|
||||||
Msg("Sent conversation deletion to Signal")
|
Msg("Sent conversation deletion to Signal")
|
||||||
|
|
||||||
if !result.WasSuccessful {
|
if !result.WasSuccessful {
|
||||||
return wrapSendError(fmt.Errorf("failed to send delete conversation sync message: %w %s %s", result.Error, userID, groupID))
|
return fmt.Errorf("failed to send delete conversation sync message: %w %s %s", result.Error, userID, groupID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -990,7 +920,7 @@ func (s *SignalClient) HandleMatrixAcceptMessageRequest(ctx context.Context, msg
|
||||||
}
|
}
|
||||||
err = s.syncMessageRequestResponse(ctx, msg.Portal, signalpb.SyncMessage_MessageRequestResponse_ACCEPT)
|
err = s.syncMessageRequestResponse(ctx, msg.Portal, signalpb.SyncMessage_MessageRequestResponse_ACCEPT)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return wrapSendError(fmt.Errorf("failed to sync message request acceptance: %w", err))
|
return fmt.Errorf("failed to sync message request acceptance: %w", err)
|
||||||
}
|
}
|
||||||
if userID.Type == libsignalgo.ServiceIDTypeACI {
|
if userID.Type == libsignalgo.ServiceIDTypeACI {
|
||||||
profileKey, err := s.Client.ProfileKeyForSignalID(ctx, s.Client.Store.ACI)
|
profileKey, err := s.Client.ProfileKeyForSignalID(ctx, s.Client.Store.ACI)
|
||||||
|
|
@ -998,7 +928,7 @@ func (s *SignalClient) HandleMatrixAcceptMessageRequest(ctx context.Context, msg
|
||||||
return fmt.Errorf("failed to get own profile key: %w", err)
|
return fmt.Errorf("failed to get own profile key: %w", err)
|
||||||
}
|
}
|
||||||
var pniSig *signalpb.PniSignatureMessage
|
var pniSig *signalpb.PniSignatureMessage
|
||||||
if s.Client.Store.PNI != uuid.Nil && s.Client.Store.AccountRecord.GetPhoneNumberSharingMode() == signalpb.AccountRecord_EVERYBODY {
|
if s.Client.Store.AccountRecord.GetPhoneNumberSharingMode() == signalpb.AccountRecord_EVERYBODY {
|
||||||
sig, err := s.Client.Store.PNIIdentityKeyPair.SignAlternateIdentity(s.Client.Store.ACIIdentityKeyPair.GetIdentityKey())
|
sig, err := s.Client.Store.PNIIdentityKeyPair.SignAlternateIdentity(s.Client.Store.ACIIdentityKeyPair.GetIdentityKey())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to generate PNI signature: %w", err)
|
return fmt.Errorf("failed to generate PNI signature: %w", err)
|
||||||
|
|
@ -1019,74 +949,9 @@ func (s *SignalClient) HandleMatrixAcceptMessageRequest(ctx context.Context, msg
|
||||||
PniSignatureMessage: pniSig,
|
PniSignatureMessage: pniSig,
|
||||||
})
|
})
|
||||||
if !res.WasSuccessful {
|
if !res.WasSuccessful {
|
||||||
return wrapSendError(fmt.Errorf("failed to share profile key to accept message request: %w", res.Error))
|
return fmt.Errorf("failed to share profile key to accept message request: %w", res.Error)
|
||||||
}
|
}
|
||||||
// TODO send read receipts too?
|
// TODO send read receipts too?
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) HandleMatrixBlockUser(ctx context.Context, msg *bridgev2.MatrixBlockUser) error {
|
|
||||||
if s.Client == nil {
|
|
||||||
return bridgev2.ErrNotLoggedIn
|
|
||||||
}
|
|
||||||
userID, groupID, err := signalid.ParsePortalID(msg.Portal.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if groupID != "" {
|
|
||||||
return bridgev2.ErrNonDMBlockUser
|
|
||||||
}
|
|
||||||
aci, pni := userID.ToACIAndPNI()
|
|
||||||
if aci == uuid.Nil {
|
|
||||||
return fmt.Errorf("blocking requires a known ACI")
|
|
||||||
}
|
|
||||||
respType := signalpb.SyncMessage_MessageRequestResponse_ACCEPT
|
|
||||||
if msg.Content.Block {
|
|
||||||
respType = signalpb.SyncMessage_MessageRequestResponse_BLOCK
|
|
||||||
}
|
|
||||||
if msg.Content.ReportSpam {
|
|
||||||
if err = s.reportSpam(ctx, msg.Portal, aci); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if msg.Content.Block {
|
|
||||||
respType = signalpb.SyncMessage_MessageRequestResponse_BLOCK_AND_SPAM
|
|
||||||
} else {
|
|
||||||
respType = signalpb.SyncMessage_MessageRequestResponse_SPAM
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = s.syncMessageRequestResponse(ctx, msg.Portal, respType); err != nil {
|
|
||||||
return wrapSendError(err)
|
|
||||||
}
|
|
||||||
_, err = s.Client.Store.RecipientStore.LoadAndUpdateRecipient(ctx, aci, pni, func(recipient *types.Recipient) (bool, error) {
|
|
||||||
changed := recipient.Blocked != msg.Content.Block
|
|
||||||
recipient.Blocked = msg.Content.Block
|
|
||||||
return changed, nil
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SignalClient) reportSpam(ctx context.Context, portal *bridgev2.Portal, aci uuid.UUID) error {
|
|
||||||
messages, err := s.Main.Bridge.DB.Message.GetLastNInPortal(ctx, portal.PortalKey, 100)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
seen := make(map[string]bool)
|
|
||||||
for _, message := range messages {
|
|
||||||
meta := message.Metadata.(*signalid.MessageMetadata)
|
|
||||||
if message.SenderID != signalid.MakeUserID(aci) || meta.ServerGUID == "" || seen[meta.ServerGUID] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err = s.Client.ReportSpam(ctx, aci, meta.ServerGUID, meta.ReportingToken); err != nil {
|
|
||||||
return wrapSendError(err)
|
|
||||||
}
|
|
||||||
seen[meta.ServerGUID] = true
|
|
||||||
if len(seen) == 3 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(seen) == 0 {
|
|
||||||
return fmt.Errorf("no received messages with server GUIDs available to report")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
package connector
|
package connector
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -34,6 +33,7 @@ import (
|
||||||
"maunium.net/go/mautrix/bridgev2/database"
|
"maunium.net/go/mautrix/bridgev2/database"
|
||||||
"maunium.net/go/mautrix/bridgev2/networkid"
|
"maunium.net/go/mautrix/bridgev2/networkid"
|
||||||
"maunium.net/go/mautrix/bridgev2/simplevent"
|
"maunium.net/go/mautrix/bridgev2/simplevent"
|
||||||
|
"maunium.net/go/mautrix/bridgev2/status"
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
||||||
|
|
@ -47,17 +47,11 @@ import (
|
||||||
func (s *SignalClient) handleSignalEvent(rawEvt events.SignalEvent) bool {
|
func (s *SignalClient) handleSignalEvent(rawEvt events.SignalEvent) bool {
|
||||||
switch evt := rawEvt.(type) {
|
switch evt := rawEvt.(type) {
|
||||||
case *events.ChatEvent:
|
case *events.ChatEvent:
|
||||||
wrapped := &Bv2ChatEvent{ChatEvent: evt, s: s}
|
return s.Main.Bridge.QueueRemoteEvent(s.UserLogin, &Bv2ChatEvent{ChatEvent: evt, s: s}).Success
|
||||||
if msg, ok := evt.Event.(*signalpb.DataMessage); ok && msg.GetIsViewOnce() {
|
|
||||||
s.recentMessageRooms.Push(wrapped.GetID(), wrapped.GetPortalKey())
|
|
||||||
}
|
|
||||||
return s.Main.Bridge.QueueRemoteEvent(s.UserLogin, wrapped).Success
|
|
||||||
case *events.DecryptionError:
|
case *events.DecryptionError:
|
||||||
return s.Main.Bridge.QueueRemoteEvent(s.UserLogin, s.wrapDecryptionError(evt)).Success
|
return s.Main.Bridge.QueueRemoteEvent(s.UserLogin, s.wrapDecryptionError(evt)).Success
|
||||||
case *events.Receipt:
|
case *events.Receipt:
|
||||||
return s.handleSignalReceipt(evt)
|
return s.handleSignalReceipt(evt)
|
||||||
case *events.ViewOnceOpen:
|
|
||||||
return s.handleSignalViewOnceOpen(evt)
|
|
||||||
case *events.ReadSelf:
|
case *events.ReadSelf:
|
||||||
return s.handleSignalReadSelf(evt)
|
return s.handleSignalReadSelf(evt)
|
||||||
case *events.DeleteForMe:
|
case *events.DeleteForMe:
|
||||||
|
|
@ -73,9 +67,7 @@ func (s *SignalClient) handleSignalEvent(rawEvt events.SignalEvent) bool {
|
||||||
case *events.QueueEmpty:
|
case *events.QueueEmpty:
|
||||||
s.queueEmptyWaiter.Set()
|
s.queueEmptyWaiter.Set()
|
||||||
case *events.LoggedOut:
|
case *events.LoggedOut:
|
||||||
s.sendLoggedOutState(evt.Error)
|
s.UserLogin.BridgeState.Send(status.BridgeState{StateEvent: status.StateBadCredentials, Message: evt.Error.Error()})
|
||||||
case *events.IdentityChange:
|
|
||||||
s.handleSignalIdentityChange(evt)
|
|
||||||
default:
|
default:
|
||||||
s.UserLogin.Log.Warn().Type("event_type", evt).Msg("Unrecognized signalmeow event type")
|
s.UserLogin.Log.Warn().Type("event_type", evt).Msg("Unrecognized signalmeow event type")
|
||||||
}
|
}
|
||||||
|
|
@ -161,44 +153,6 @@ func convertDecryptionError(_ context.Context, _ *bridgev2.Portal, _ bridgev2.Ma
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) handleSignalIdentityChange(evt *events.IdentityChange) {
|
|
||||||
if !s.Main.Config.IdentityChangeNotices || evt.ServiceID.Type != libsignalgo.ServiceIDTypeACI || evt.ServiceID.UUID == s.Client.Store.ACI {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.UserLogin.QueueRemoteEvent(&simplevent.Message[*events.IdentityChange]{
|
|
||||||
EventMeta: simplevent.EventMeta{
|
|
||||||
Type: bridgev2.RemoteEventMessage,
|
|
||||||
LogContext: func(c zerolog.Context) zerolog.Context {
|
|
||||||
return c.Stringer("sender_id", evt.ServiceID)
|
|
||||||
},
|
|
||||||
PortalKey: s.makeDMPortalKey(evt.ServiceID),
|
|
||||||
Sender: s.makeEventSender(evt.ServiceID.UUID),
|
|
||||||
},
|
|
||||||
Data: evt,
|
|
||||||
ID: "idchange|" + signalid.MakeMessageID(evt.ServiceID.UUID, uint64(time.Now().UnixMilli())),
|
|
||||||
ConvertMessageFunc: convertIdentityChange,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertIdentityChange(ctx context.Context, portal *bridgev2.Portal, _ bridgev2.MatrixAPI, evt *events.IdentityChange) (*bridgev2.ConvertedMessage, error) {
|
|
||||||
name := evt.ServiceID.UUID.String()
|
|
||||||
ghost, err := portal.Bridge.GetGhostByID(ctx, signalid.MakeUserID(evt.ServiceID.UUID))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if ghost.Name != "" {
|
|
||||||
name = ghost.Name
|
|
||||||
}
|
|
||||||
return &bridgev2.ConvertedMessage{
|
|
||||||
Parts: []*bridgev2.ConvertedMessagePart{{
|
|
||||||
Type: event.EventMessage,
|
|
||||||
Content: &event.MessageEventContent{
|
|
||||||
MsgType: event.MsgNotice,
|
|
||||||
Body: fmt.Sprintf("Your safety number with %s has changed.", name),
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Bv2ChatEvent struct {
|
type Bv2ChatEvent struct {
|
||||||
*events.ChatEvent
|
*events.ChatEvent
|
||||||
s *SignalClient
|
s *SignalClient
|
||||||
|
|
@ -221,9 +175,8 @@ func (evt *Bv2ChatEvent) GetType() bridgev2.RemoteEventType {
|
||||||
switch innerEvt := evt.Event.(type) {
|
switch innerEvt := evt.Event.(type) {
|
||||||
case *signalpb.DataMessage:
|
case *signalpb.DataMessage:
|
||||||
switch {
|
switch {
|
||||||
case innerEvt.GetIsViewOnce(), innerEvt.Body != nil, innerEvt.Attachments != nil, innerEvt.Contact != nil, innerEvt.Sticker != nil,
|
case innerEvt.Body != nil, innerEvt.Attachments != nil, innerEvt.Contact != nil, innerEvt.Sticker != nil,
|
||||||
innerEvt.Payment != nil, innerEvt.GiftBadge != nil, innerEvt.PollCreate != nil, innerEvt.PollVote != nil,
|
innerEvt.Payment != nil, innerEvt.GiftBadge != nil, innerEvt.PollCreate != nil, innerEvt.PollVote != nil,
|
||||||
innerEvt.PinMessage != nil, innerEvt.UnpinMessage != nil,
|
|
||||||
innerEvt.GetRequiredProtocolVersion() > uint32(signalpb.DataMessage_CURRENT),
|
innerEvt.GetRequiredProtocolVersion() > uint32(signalpb.DataMessage_CURRENT),
|
||||||
innerEvt.GetFlags()&uint32(signalpb.DataMessage_EXPIRATION_TIMER_UPDATE) != 0:
|
innerEvt.GetFlags()&uint32(signalpb.DataMessage_EXPIRATION_TIMER_UPDATE) != 0:
|
||||||
return bridgev2.RemoteEventMessage
|
return bridgev2.RemoteEventMessage
|
||||||
|
|
@ -245,25 +198,17 @@ func (evt *Bv2ChatEvent) GetType() bridgev2.RemoteEventType {
|
||||||
return bridgev2.RemoteEventUnknown
|
return bridgev2.RemoteEventUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
func (evt *Bv2ChatEvent) GetChatInfoChange(ctx context.Context, portal *bridgev2.Portal) (*bridgev2.ChatInfoChange, error) {
|
func (evt *Bv2ChatEvent) GetChatInfoChange(ctx context.Context) (*bridgev2.ChatInfoChange, error) {
|
||||||
dm, _ := evt.Event.(*signalpb.DataMessage)
|
dm, _ := evt.Event.(*signalpb.DataMessage)
|
||||||
gv2 := dm.GetGroupV2()
|
gv2 := dm.GetGroupV2()
|
||||||
if gv2 == nil || gv2.GroupChange == nil {
|
if gv2 == nil || gv2.GroupChange == nil {
|
||||||
return nil, fmt.Errorf("GetChatInfoChange() called for non-GroupChange event")
|
return nil, fmt.Errorf("GetChatInfoChange() called for non-GroupChange event")
|
||||||
}
|
}
|
||||||
portalRevision := portal.Metadata.(*signalid.PortalMetadata).Revision
|
|
||||||
groupChange, err := evt.s.Client.DecryptGroupChange(ctx, gv2)
|
groupChange, err := evt.s.Client.DecryptGroupChange(ctx, gv2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zerolog.Ctx(ctx).Err(err).Msg("Failed to decrypt group change")
|
return nil, fmt.Errorf("failed to decrypt group change: %w", err)
|
||||||
return nil, nil
|
|
||||||
} else if groupChange.Revision != gv2.GetRevision() || groupChange.Revision != portalRevision+1 {
|
|
||||||
zerolog.Ctx(ctx).Warn().
|
|
||||||
Uint32("decrypted_revision", groupChange.Revision).
|
|
||||||
Uint32("envelope_revision", gv2.GetRevision()).
|
|
||||||
Uint32("portal_revision", portalRevision).
|
|
||||||
Msg("Group change revision mismatch, ignoring event")
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
|
// XXX: is this ID compatible with types.GroupIdentifier?
|
||||||
return evt.s.groupChangeToChatInfoChange(ctx, types.GroupIdentifier(evt.Info.ChatID), gv2.GetRevision(), groupChange)
|
return evt.s.groupChangeToChatInfoChange(ctx, types.GroupIdentifier(evt.Info.ChatID), gv2.GetRevision(), groupChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -272,16 +217,13 @@ func (evt *Bv2ChatEvent) PreHandle(ctx context.Context, portal *bridgev2.Portal)
|
||||||
if !ok || dataMsg.GroupV2 == nil {
|
if !ok || dataMsg.GroupV2 == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
portalMeta := portal.Metadata.(*signalid.PortalMetadata)
|
portalRev := portal.Metadata.(*signalid.PortalMetadata).Revision
|
||||||
portalRev := portalMeta.Revision
|
|
||||||
if evt.Info.GroupRevision > portalRev {
|
if evt.Info.GroupRevision > portalRev {
|
||||||
toRevision := evt.Info.GroupRevision
|
toRevision := evt.Info.GroupRevision
|
||||||
if dataMsg.GetGroupV2().GetGroupChange() != nil {
|
if dataMsg.GetGroupV2().GetGroupChange() != nil {
|
||||||
toRevision--
|
toRevision--
|
||||||
}
|
}
|
||||||
evt.s.catchUpGroup(ctx, portal, portalRev, toRevision, dataMsg.GetTimestamp())
|
evt.s.catchUpGroup(ctx, portal, portalRev, toRevision, dataMsg.GetTimestamp())
|
||||||
} else if portalMeta.InfoVersion < currentPortalInfoVersion && portal.MXID != "" {
|
|
||||||
evt.s.catchUpGroup(ctx, portal, 0, portalRev, dataMsg.GetTimestamp())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -399,16 +341,6 @@ func (evt *Bv2ChatEvent) ConvertMessage(ctx context.Context, portal *bridgev2.Po
|
||||||
return nil, fmt.Errorf("ConvertMessage() called for non-DataMessage event")
|
return nil, fmt.Errorf("ConvertMessage() called for non-DataMessage event")
|
||||||
}
|
}
|
||||||
converted := evt.s.Main.MsgConv.ToMatrix(ctx, evt.s.Client, portal, evt.Info.Sender, intent, dataMsg, nil)
|
converted := evt.s.Main.MsgConv.ToMatrix(ctx, evt.s.Client, portal, evt.Info.Sender, intent, dataMsg, nil)
|
||||||
for _, part := range converted.Parts {
|
|
||||||
meta, ok := part.DBMetadata.(*signalid.MessageMetadata)
|
|
||||||
if !ok {
|
|
||||||
meta = &signalid.MessageMetadata{}
|
|
||||||
part.DBMetadata = meta
|
|
||||||
}
|
|
||||||
meta.ServerGUID = evt.Info.ServerGUID
|
|
||||||
meta.ReportingToken = evt.Info.ReportingToken
|
|
||||||
}
|
|
||||||
|
|
||||||
if converted.Disappear.Type != "" {
|
if converted.Disappear.Type != "" {
|
||||||
evtTS := evt.GetTimestamp()
|
evtTS := evt.GetTimestamp()
|
||||||
if !dataMsg.GetIsViewOnce() {
|
if !dataMsg.GetIsViewOnce() {
|
||||||
|
|
@ -452,14 +384,6 @@ func (evt *Bv2ChatEvent) ConvertEdit(ctx context.Context, portal *bridgev2.Porta
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("ConvertEdit() called for non-EditMessage event")
|
return nil, fmt.Errorf("ConvertEdit() called for non-EditMessage event")
|
||||||
}
|
}
|
||||||
if editMsg.GetDataMessage().GetIsViewOnce() {
|
|
||||||
return nil, bridgev2.ErrIgnoringRemoteEvent
|
|
||||||
}
|
|
||||||
for _, part := range existing {
|
|
||||||
if part.Metadata.(*signalid.MessageMetadata).ViewOnce {
|
|
||||||
return nil, fmt.Errorf("%w: edit target is view-once media", bridgev2.ErrIgnoringRemoteEvent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
existing = slices.DeleteFunc(slices.Clone(existing), isEditStub)
|
existing = slices.DeleteFunc(slices.Clone(existing), isEditStub)
|
||||||
if len(existing) == 0 {
|
if len(existing) == 0 {
|
||||||
return nil, fmt.Errorf("%w: edit target has already been edited", bridgev2.ErrIgnoringRemoteEvent)
|
return nil, fmt.Errorf("%w: edit target has already been edited", bridgev2.ErrIgnoringRemoteEvent)
|
||||||
|
|
@ -588,54 +512,6 @@ func (s *SignalClient) handleSignalReceipt(evt *events.Receipt) bool {
|
||||||
return s.dispatchReceipts(evt.Sender, evt.Content.GetType(), receipts)
|
return s.dispatchReceipts(evt.Sender, evt.Content.GetType(), receipts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) handleSignalViewOnceOpen(evt *events.ViewOnceOpen) bool {
|
|
||||||
ctx := s.Main.Bridge.BackgroundCtx
|
|
||||||
log := s.UserLogin.Log.With().
|
|
||||||
Str("action", "handle signal view once open").
|
|
||||||
Logger()
|
|
||||||
ctx = log.WithContext(ctx)
|
|
||||||
sender, err := signalmeow.ParseStringOrBinaryUUID(evt.GetSenderAci(), evt.GetSenderAciBinary())
|
|
||||||
if err != nil {
|
|
||||||
log.Err(err).Msg("Invalid sender in view-once open sync")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
messageID := signalid.MakeMessageID(sender, evt.GetTimestamp())
|
|
||||||
portalKey, ok := s.recentMessageRooms.Get(messageID)
|
|
||||||
if !ok {
|
|
||||||
msg, dbErr := s.Main.Bridge.DB.Message.GetFirstPartByID(ctx, s.UserLogin.ID, messageID)
|
|
||||||
if dbErr != nil {
|
|
||||||
log.Err(dbErr).Msg("Failed to get opened view-once message")
|
|
||||||
return ctx.Err() == nil
|
|
||||||
} else if msg == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
portalKey = msg.Room
|
|
||||||
}
|
|
||||||
return s.Main.Bridge.QueueRemoteEvent(s.UserLogin, &simplevent.EventMeta{
|
|
||||||
Type: bridgev2.RemoteEventUnknown,
|
|
||||||
PortalKey: portalKey,
|
|
||||||
PreHandleFunc: func(ctx context.Context, portal *bridgev2.Portal) {
|
|
||||||
messages, dbErr := s.Main.Bridge.DB.Message.GetAllPartsByID(ctx, s.UserLogin.ID, messageID)
|
|
||||||
if dbErr != nil {
|
|
||||||
log.Err(dbErr).Msg("Failed to get opened view-once message")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, msg := range messages {
|
|
||||||
meta := msg.Metadata.(*signalid.MessageMetadata)
|
|
||||||
if !meta.ViewOnce || !meta.ContainsAttachments {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if addErr := s.Main.Bridge.DisappearLoop.Add(ctx, &database.DisappearingMessage{
|
|
||||||
RoomID: portal.MXID, EventID: msg.MXID, Timestamp: msg.Timestamp,
|
|
||||||
DisappearingSetting: database.DisappearingSetting{Type: "view_limited", DisappearAt: time.Now()},
|
|
||||||
}); addErr != nil {
|
|
||||||
log.Err(addErr).Msg("Failed to queue opened view-once media expiry")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}).Success
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SignalClient) handleSignalReadSelf(evt *events.ReadSelf) bool {
|
func (s *SignalClient) handleSignalReadSelf(evt *events.ReadSelf) bool {
|
||||||
log := s.UserLogin.Log.With().
|
log := s.UserLogin.Log.With().
|
||||||
Str("action", "handle signal read self").
|
Str("action", "handle signal read self").
|
||||||
|
|
@ -813,14 +689,8 @@ func (s *SignalClient) handleSignalDeleteForMe(evt *events.DeleteForMe) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) handleSignalMessageRequestResponse(evt *events.MessageRequestResponse) bool {
|
func (s *SignalClient) handleSignalMessageRequestResponse(evt *events.MessageRequestResponse) bool {
|
||||||
info := &bridgev2.ChatInfo{}
|
if evt.Type != signalpb.SyncMessage_MessageRequestResponse_ACCEPT {
|
||||||
switch evt.Type {
|
// TODO do we need to do anything with blocks/deletes here or are they sent as normal delete events?
|
||||||
case signalpb.SyncMessage_MessageRequestResponse_ACCEPT:
|
|
||||||
info.MessageRequest = ptr.Ptr(false)
|
|
||||||
info.UserBlocked = ptr.Ptr(false)
|
|
||||||
case signalpb.SyncMessage_MessageRequestResponse_BLOCK, signalpb.SyncMessage_MessageRequestResponse_BLOCK_AND_SPAM, signalpb.SyncMessage_MessageRequestResponse_BLOCK_AND_DELETE:
|
|
||||||
info.UserBlocked = ptr.Ptr(true)
|
|
||||||
default:
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
var portalKey networkid.PortalKey
|
var portalKey networkid.PortalKey
|
||||||
|
|
@ -842,7 +712,9 @@ func (s *SignalClient) handleSignalMessageRequestResponse(evt *events.MessageReq
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ChatInfoChange: &bridgev2.ChatInfoChange{
|
ChatInfoChange: &bridgev2.ChatInfoChange{
|
||||||
ChatInfo: info,
|
ChatInfo: &bridgev2.ChatInfo{
|
||||||
|
MessageRequest: ptr.Ptr(false),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return res.Success
|
return res.Success
|
||||||
|
|
@ -893,7 +765,7 @@ func (s *SignalClient) handleSignalContactList(evt *events.ContactList) {
|
||||||
log.Err(err).Msg("Failed to get ghost to update contact info")
|
log.Err(err).Msg("Failed to get ghost to update contact info")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
userInfo, err := s.contactToUserInfo(ctx, contact, ghost.Metadata.(*signalid.GhostMetadata).Username, false)
|
userInfo, err := s.contactToUserInfo(ctx, contact)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to convert contact info")
|
log.Err(err).Msg("Failed to convert contact info")
|
||||||
continue
|
continue
|
||||||
|
|
@ -902,24 +774,28 @@ func (s *SignalClient) handleSignalContactList(evt *events.ContactList) {
|
||||||
if contact.ACI == s.Client.Store.ACI {
|
if contact.ACI == s.Client.Store.ACI {
|
||||||
s.updateRemoteProfile(ctx, true)
|
s.updateRemoteProfile(ctx, true)
|
||||||
}
|
}
|
||||||
|
if ptr.Val(contact.Whitelisted) {
|
||||||
portal, err := s.Main.Bridge.GetExistingPortalByKey(ctx, s.makeDMPortalKey(libsignalgo.NewACIServiceID(contact.ACI)))
|
portal, err := s.Main.Bridge.GetExistingPortalByKey(ctx, s.makeDMPortalKey(libsignalgo.NewACIServiceID(contact.ACI)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Err(err).Msg("Failed to get existing portal to update contact info")
|
log.Err(err).Msg("Failed to get existing portal to update contact info")
|
||||||
continue
|
continue
|
||||||
}
|
} else if portal != nil && portal.MessageRequest {
|
||||||
if portal != nil && (portal.UserBlocked != contact.Blocked || (portal.MessageRequest && ptr.Val(contact.Whitelisted))) {
|
|
||||||
info := &bridgev2.ChatInfo{UserBlocked: ptr.Ptr(contact.Blocked)}
|
|
||||||
if ptr.Val(contact.Whitelisted) {
|
|
||||||
info.MessageRequest = ptr.Ptr(false)
|
|
||||||
}
|
|
||||||
s.UserLogin.QueueRemoteEvent(&simplevent.ChatInfoChange{
|
s.UserLogin.QueueRemoteEvent(&simplevent.ChatInfoChange{
|
||||||
EventMeta: simplevent.EventMeta{Type: bridgev2.RemoteEventChatInfoChange, PortalKey: portal.PortalKey},
|
EventMeta: simplevent.EventMeta{
|
||||||
ChatInfoChange: &bridgev2.ChatInfoChange{ChatInfo: info},
|
Type: bridgev2.RemoteEventChatInfoChange,
|
||||||
|
LogContext: func(c zerolog.Context) zerolog.Context {
|
||||||
|
return c.Str("action", "unmark message request").Str("source", "contact list")
|
||||||
|
},
|
||||||
|
PortalKey: portal.PortalKey,
|
||||||
|
},
|
||||||
|
ChatInfoChange: &bridgev2.ChatInfoChange{
|
||||||
|
ChatInfo: &bridgev2.ChatInfo{
|
||||||
|
MessageRequest: ptr.Ptr(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if evt.IsFromDB {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
s.UserLogin.Metadata.(*signalid.UserLoginMetadata).LastContactSync = jsontime.UnixMilliNow()
|
s.UserLogin.Metadata.(*signalid.UserLoginMetadata).LastContactSync = jsontime.UnixMilliNow()
|
||||||
err := s.UserLogin.Save(ctx)
|
err := s.UserLogin.Save(ctx)
|
||||||
|
|
@ -938,13 +814,6 @@ func (s *SignalClient) updateRemoteProfile(ctx context.Context, resendState bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
changed := false
|
changed := false
|
||||||
if s.Client.Store.AccountRecord != nil && s.Client.Store.AccountRecord.Username != s.UserLogin.RemoteProfile.Username {
|
|
||||||
s.UserLogin.RemoteProfile.Username = s.Client.Store.AccountRecord.Username
|
|
||||||
if s.Client.Store.PNI == uuid.Nil {
|
|
||||||
s.UserLogin.RemoteName = cmp.Or(s.Client.Store.AccountRecord.Username, s.Client.Store.ACI.String())
|
|
||||||
}
|
|
||||||
changed = true
|
|
||||||
}
|
|
||||||
if s.UserLogin.RemoteProfile.Name != s.Ghost.Name {
|
if s.UserLogin.RemoteProfile.Name != s.Ghost.Name {
|
||||||
s.UserLogin.RemoteProfile.Name = s.Ghost.Name
|
s.UserLogin.RemoteProfile.Name = s.Ghost.Name
|
||||||
changed = true
|
changed = true
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue