mirror of
https://github.com/mautrix/signal.git
synced 2026-11-09 15:49:17 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
650c7c243b |
311 changed files with 35329 additions and 99487 deletions
8
.envrc
Normal file
8
.envrc
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
if [[ $(uname -s) == "Linux" && $(uname --kernel-version | grep "NixOS") ]]; then
|
||||
echo "The best OS (NixOS) has been detected. Using nice tools."
|
||||
if ! has nix_direnv_version || ! nix_direnv_version 3.0.0; then
|
||||
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.0/direnvrc" "sha256-21TMnI2xWX7HkSTjFFri2UaohXVj854mgvWapWrxRXg="
|
||||
fi
|
||||
|
||||
use flake
|
||||
fi
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
|
|
@ -1,2 +0,0 @@
|
|||
*.pb.go linguist-generated=true
|
||||
*.pb.raw binary linguist-generated=true
|
||||
15
.github/ISSUE_TEMPLATE/bug.md
vendored
15
.github/ISSUE_TEMPLATE/bug.md
vendored
|
|
@ -1,18 +1,7 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: If something is definitely wrong in the bridge (rather than just a setup issue),
|
||||
file a bug report. Remember to include relevant logs. Asking in the Matrix room first
|
||||
is strongly recommended.
|
||||
type: Bug
|
||||
file a bug report. Remember to include relevant logs.
|
||||
labels: bug
|
||||
|
||||
---
|
||||
|
||||
<!-- Include relevant logs, the bridge version and other important details here -->
|
||||
|
||||
### Checklist
|
||||
|
||||
<!-- All items below are mandatory. Issues not following the rules may be closed without comment. -->
|
||||
|
||||
* [ ] This is an actual bug, not just a setup issue (see the [troubleshooting docs](https://docs.mau.fi/bridges/general/troubleshooting.html) or ask in the Matrix room for setup help).
|
||||
* [ ] I am certain that sufficient information is included. Ask in the Matrix room first if not.
|
||||
* [ ] The bug is still present on the main branch. The `!signal version` command output is: ``
|
||||
|
|
|
|||
2
.github/ISSUE_TEMPLATE/enhancement.md
vendored
2
.github/ISSUE_TEMPLATE/enhancement.md
vendored
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
name: Enhancement request
|
||||
about: Submit a feature request or other suggestion
|
||||
type: Feature
|
||||
labels: enhancement
|
||||
|
||||
---
|
||||
|
|
|
|||
33
.github/workflows/go.yml
vendored
33
.github/workflows/go.yml
vendored
|
|
@ -2,23 +2,20 @@ name: Go
|
|||
|
||||
on: [push, pull_request]
|
||||
|
||||
env:
|
||||
GOTOOLCHAIN: local
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go-version: ["1.26", "1.27"]
|
||||
name: Lint ${{ matrix.go-version == '1.27' && '(latest)' || '(old)' }}
|
||||
go-version: ["1.21", "1.22"]
|
||||
name: Lint ${{ matrix.go-version == '1.22' && '(latest)' || '(old)' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
cache: true
|
||||
|
|
@ -30,33 +27,32 @@ jobs:
|
|||
run: |
|
||||
go install golang.org/x/tools/cmd/goimports@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"
|
||||
|
||||
- name: Run pre-commit
|
||||
uses: pre-commit/action@v3.0.1
|
||||
uses: pre-commit/action@v3.0.0
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go-version: ["1.26", "1.27"]
|
||||
name: Test ${{ matrix.go-version == '1.27' && '(latest)' || '(old)' }}
|
||||
go-version: ["1.21", "1.22"]
|
||||
name: Test ${{ matrix.go-version == '1.22' && '(latest)' || '(old)' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v7
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
cache: true
|
||||
|
||||
#- name: Set up gotestfmt
|
||||
# uses: GoTestTools/gotestfmt-action@v2
|
||||
# with:
|
||||
# token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set up gotestfmt
|
||||
uses: GoTestTools/gotestfmt-action@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install libolm
|
||||
run: sudo apt-get install libolm-dev
|
||||
|
|
@ -69,5 +65,4 @@ jobs:
|
|||
run: |
|
||||
set -euo pipefail
|
||||
export LIBRARY_PATH=.
|
||||
#go test -v -json ./... -cover | gotestfmt
|
||||
go test ./...
|
||||
go test -v -json ./... -cover | gotestfmt
|
||||
|
|
|
|||
29
.github/workflows/stale.yml
vendored
29
.github/workflows/stale.yml
vendored
|
|
@ -1,29 +0,0 @@
|
|||
name: 'Lock old issues'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 12 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
# pull-requests: write
|
||||
# discussions: write
|
||||
|
||||
concurrency:
|
||||
group: lock-threads
|
||||
|
||||
jobs:
|
||||
lock-stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v6
|
||||
id: lock
|
||||
with:
|
||||
issue-inactive-days: 90
|
||||
process-only: issues
|
||||
- name: Log processed threads
|
||||
run: |
|
||||
if [ '${{ steps.lock.outputs.issues }}' ]; then
|
||||
echo "Issues:" && echo '${{ steps.lock.outputs.issues }}' | jq -r '.[] | "https://github.com/\(.owner)/\(.repo)/issues/\(.issue_number)"'
|
||||
fi
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -6,7 +6,6 @@
|
|||
*.log*
|
||||
|
||||
/mautrix-signal
|
||||
/mautrix-signalgo
|
||||
/start
|
||||
/libsignal_ffi.a
|
||||
|
||||
.idea
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
include:
|
||||
- project: 'mautrix/ci'
|
||||
file: '/gov2-as-default.yml'
|
||||
file: '/go.yml'
|
||||
|
||||
variables:
|
||||
BUILDER_IMAGE: dock.mau.dev/tulir/gomuks-build-docker/signal
|
||||
BINARY_NAME: mautrix-signal
|
||||
|
||||
# 32-bit arm builds aren't supported
|
||||
build arm:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
exclude_types: [markdown]
|
||||
|
|
@ -9,23 +9,16 @@ repos:
|
|||
- id: check-added-large-files
|
||||
|
||||
- repo: https://github.com/tekwizely/pre-commit-golang
|
||||
rev: v1.0.0-rc.4
|
||||
rev: v1.0.0-rc.1
|
||||
hooks:
|
||||
- id: go-imports
|
||||
exclude: "pb\\.go$"
|
||||
args:
|
||||
- "-local"
|
||||
- "go.mau.fi/mautrix-signal"
|
||||
- "-w"
|
||||
- id: go-vet-mod
|
||||
# - id: go-staticcheck-repo-mod
|
||||
#- id: go-staticcheck-repo-mod
|
||||
# TODO: reenable this and fix all the problems
|
||||
|
||||
- repo: https://github.com/beeper/pre-commit-go
|
||||
rev: v0.5.0
|
||||
rev: v0.3.0
|
||||
hooks:
|
||||
- id: zerolog-ban-msgf
|
||||
- id: zerolog-use-stringer
|
||||
- id: bridgelint
|
||||
language: system
|
||||
verbose: true
|
||||
|
|
|
|||
322
CHANGELOG.md
322
CHANGELOG.md
|
|
@ -1,325 +1,3 @@
|
|||
# v26.11 (unreleased)
|
||||
|
||||
* Updated libsignal to v0.106.1.
|
||||
* Added support for sending more than 2048 characters of text to Signal by
|
||||
uploading the rest of the text as an attachment, like official clients do.
|
||||
* Added automatic retrying of group message sends if the server returns a 500
|
||||
or 503 error.
|
||||
* Added support for encrypting direct media with FLOE ([MSC4571]).
|
||||
* Fixed memory leak in libsignal store implementation.
|
||||
|
||||
[MSC4571]: https://github.com/matrix-org/matrix-spec-proposals/pull/4571
|
||||
|
||||
# v26.10
|
||||
|
||||
* Updated libsignal to v0.105.0.
|
||||
* Added support for phonenumberless accounts.
|
||||
* Added support for starting chats with usernames and username links.
|
||||
* Added support for blocking and unblocking users on Signal.
|
||||
* Added support for new view-limited media interface in bridgev2.
|
||||
* Added proper sender certificate validation.
|
||||
* Changed storage sync to only sync once if repeated fetch latest notices are
|
||||
received.
|
||||
* Fixed contact syncs not happening even when `sync_contacts_on_startup` was
|
||||
set to true.
|
||||
* Fixed Signal encryption using wrong timestamp precision for session expiry.
|
||||
* Fixed sender key sharing not handling removed users properly.
|
||||
* Fixed handling retry receipts with no sender key info.
|
||||
* Fixed deadlock in prekey upload loop if the client gets logged out.
|
||||
* Fixed infinite loop when importing sticker packs with duplicate emoji
|
||||
shortcuts.
|
||||
|
||||
# v26.09
|
||||
|
||||
* Bumped minimum Go version to 1.26.
|
||||
* Updated libsignal to v0.102.2.
|
||||
* Fixed handling messages with invalid values in formatting body ranges.
|
||||
* Fixed websocket request retrying not working correctly if the first attempt
|
||||
times out.
|
||||
* Fixed another potential race condition with edited message bridging.
|
||||
|
||||
# v26.08
|
||||
|
||||
* Updated libsignal to v0.100.0
|
||||
* Added support for handling own profile key changes.
|
||||
* Changed attachment bridging behavior to match Signal Desktop.
|
||||
* Fixed edited messages being bridged twice if certain race conditions occurred.
|
||||
|
||||
# v26.07
|
||||
|
||||
* Updated Docker image to Alpine 3.24.
|
||||
* Updated libsignal to v0.97.2.
|
||||
* Added automatic retry when sender key send fails due to missing session.
|
||||
* Fixed changing poll votes from Matrix.
|
||||
|
||||
# v26.06
|
||||
|
||||
* Updated libsignal to v0.94.4.
|
||||
* Updated mrenclave to fix looking up phone numbers.
|
||||
* Changed knock accept handling to auto-join the ghost user afterwards.
|
||||
|
||||
# v26.05
|
||||
|
||||
* Updated libsignal to v0.93.2.
|
||||
* Added support for importing sticker packs from Signal.
|
||||
|
||||
# v26.04
|
||||
|
||||
* Updated libsignal to v0.92.1.
|
||||
* Added support for admin message deletes from Signal.
|
||||
* Added support for binary service IDs in storage service.
|
||||
* Fixed `private_chat_portal_meta` option not setting DM room names correctly.
|
||||
* Fixed panic if user is logged out during initial chat sync.
|
||||
* Fixed avatar upload failing when creating new Signal group.
|
||||
|
||||
# v26.03
|
||||
|
||||
* Switched to sending binary service ID fields in outgoing messages.
|
||||
* Added support for roundtripping large attachments via disk to avoid keeping
|
||||
the entire file in memory during en/decryption.
|
||||
|
||||
# v26.02.2
|
||||
|
||||
* Added support for more new binary service ID fields.
|
||||
|
||||
# v26.02.1
|
||||
|
||||
* Updated libsignal to v0.87.5.
|
||||
* Added support for new binary service ID fields that Signal 8.0 switched to.
|
||||
|
||||
# v26.02
|
||||
|
||||
* Bumped minimum Go version to 1.25.
|
||||
* Updated libsignal to v0.87.1.
|
||||
* Added automatic recovery for the session not found error from libsignal.
|
||||
* Fixed sender key state not being cleared on logout properly.
|
||||
|
||||
# v26.01
|
||||
|
||||
* Updated libsignal to v0.86.12.
|
||||
* Changed automatic contact list sync option to only sync every 3 days rather
|
||||
than on every restart.
|
||||
* Fixed sending messages to groups with no other registered members.
|
||||
* Fixed sender key sends failing if some users had changed devices.
|
||||
* Fixed timestamps of outgoing typing notifications in DMs.
|
||||
|
||||
# v25.12
|
||||
|
||||
* Updated libsignal to v0.86.8.
|
||||
* Updated Docker image to Alpine 3.23.
|
||||
* Added support for dropping incoming DMs from blocked contacts on Signal.
|
||||
* Added support for sender key encryption when sending to groups, which makes
|
||||
sending much faster and enables sending typing notifications.
|
||||
* Added support for encryption retry receipts.
|
||||
* Fixed bugs with handling poll votes.
|
||||
* Fixed history transfer option not showing up when pairing with Signal Android.
|
||||
* Fixed nicknames being cleared not being bridged
|
||||
(thanks to [@Enzime] in [#623]).
|
||||
|
||||
[#623]: https://github.com/mautrix/signal/pull/623
|
||||
[@Enzime]: https://github.com/Enzime
|
||||
|
||||
# v25.11
|
||||
|
||||
* Updated libsignal to v0.86.4.
|
||||
* Added support for bridging invite state in groups for phone number invites.
|
||||
* Added support for polls.
|
||||
* Fixed PNI signature not being sent when replying to message requests.
|
||||
* Fixed unnecessary repeating error notices when Signal is down.
|
||||
* Fixed sticker size metadata on Matrix not matching how native Signal Desktop
|
||||
renders them.
|
||||
|
||||
# v25.10
|
||||
|
||||
* Switched to calendar versioning.
|
||||
* Updated libsignal to v0.84.0.
|
||||
* Fixed backfill creating incorrect disappearing timer change notices.
|
||||
|
||||
# v0.8.7 (2025-09-16)
|
||||
|
||||
* Removed legacy provisioning API and database legacy migration.
|
||||
Upgrading directly from versions prior to v0.7.0 is not supported.
|
||||
* If you've been using the bridge since before v0.7.0 and have prevented the
|
||||
bridge from writing to the config, you must either update the config
|
||||
manually or allow the bridge to update it for you **before** upgrading to
|
||||
this release (i.e. run v0.8.6 once with config writing allowed).
|
||||
* Updated libsignal to v0.80.3.
|
||||
* Added support for `com.beeper.disappearing_timer` state event, which stores
|
||||
the disappearing setting of chats and allows changing the setting from Matrix.
|
||||
* Added support for nicknames in displayname templates.
|
||||
* Like contact list names, nicknames are not safe to use on multi-user instances.
|
||||
* Added support for creating Signal groups.
|
||||
* Fixed certain types of logouts not being detected properly.
|
||||
|
||||
# v0.8.6 (2025-08-16)
|
||||
|
||||
* Deprecated legacy provisioning API. The `/_matrix/provision/v2` endpoints will
|
||||
be deleted in the next release.
|
||||
* Bumped minimum Go version to 1.24.
|
||||
* Updated libsignal to v0.78.2.
|
||||
* Added support for "delete to me" of chats and messages.
|
||||
* Added support for latest Signal backup/transfer protocol.
|
||||
|
||||
# v0.8.5 (2025-07-16)
|
||||
|
||||
* Updated libsignal to v0.76.1.
|
||||
|
||||
# v0.8.4 (2025-06-16)
|
||||
|
||||
* Updated libsignal to v0.74.1.
|
||||
* Updated Docker image to Alpine 3.22.
|
||||
* Fixed avatars when using direct media.
|
||||
* Fixed starting chats with non-contact users.
|
||||
* Fixed Matrix media being rejected if the mime type isn't specified.
|
||||
|
||||
# v0.8.3 (2025-05-16)
|
||||
|
||||
* Updated libsignal to v0.72.1.
|
||||
* Added initial support for direct media access.
|
||||
* Note that media is only kept on the Signal servers for 45 days, after which
|
||||
any direct media links will permanently stop working.
|
||||
* Added buffer for decrypted events to prevent losing messages if the bridge is
|
||||
stopped in the middle of event handling.
|
||||
* Fixed backfilling messages in existing portals after relogining.
|
||||
|
||||
# v0.8.2 (2025-04-16)
|
||||
|
||||
* Updated libsignal to v0.70.0.
|
||||
* Fixed panics in some cases when the bridge was under heavy load.
|
||||
|
||||
# v0.8.1 (2025-03-16)
|
||||
|
||||
* Added QR refreshing when logging in.
|
||||
* Updated libsignal to v0.67.4.
|
||||
|
||||
# v0.8.0 (2025-02-16)
|
||||
|
||||
* Bumped minimum Go version to 1.23.
|
||||
* Added support for history transfer.
|
||||
* Updated libsignal to v0.66.2.
|
||||
|
||||
# v0.7.5 (2025-01-16)
|
||||
|
||||
* Added support for bridging mp4 gifs in both directions.
|
||||
* Added support for signaling supported features to clients using the
|
||||
`com.beeper.room_features` state event.
|
||||
* Updated Signal websocket authentication method.
|
||||
* Fixed some cases where websocket would get stuck after a ping timeout.
|
||||
|
||||
# v0.7.4 (2024-12-16)
|
||||
|
||||
* Fixed syncing server-side storage after Signal login.
|
||||
* Added support for new SSRE2 method of receiving the server-side storage key.
|
||||
* Updated libsignal to v0.64.1.
|
||||
* Updated Docker image to Alpine 3.21.
|
||||
|
||||
# v0.7.3 (2024-11-16)
|
||||
|
||||
* Updated libsignal to v0.62.0.
|
||||
* Note for bridges running in systemd: the new version of libsignal may be
|
||||
incompatible with the `MemoryDenyWriteExecute=true` option (see [#750]).
|
||||
* Added basic support for Signal's new file upload protocol.
|
||||
|
||||
[#750]: https://github.com/mautrix/signal/issues/570
|
||||
|
||||
# v0.7.2 (2024-10-16)
|
||||
|
||||
* Updated to libsignal v0.58.3.
|
||||
* Fixed spurious decryption error notices for Signal messages when the
|
||||
websocket reconnects and receives old already-bridged messages.
|
||||
* Fixed signalmeow not respecting account settings for choosing sender
|
||||
certificate.
|
||||
* Fixed bugs in storage service decryption, which could cause issues with
|
||||
missing contact names among other things.
|
||||
* Fixed call start notices only working once per direct chat.
|
||||
|
||||
# v0.7.1 (2024-09-16)
|
||||
|
||||
* Updated to libsignal v0.57.1.
|
||||
* Dropped support for unauthenticated media on Matrix.
|
||||
* Added support for Matrix->Signal power level bridging
|
||||
(thanks to [@maltee1] in [#531]).
|
||||
* Changed voice message conversion to convert to aac instead of m4a,
|
||||
because Signal iOS doesn't appear to like ffmpeg's m4a files.
|
||||
* Fixed outgoing sync messages not including disappearing start timestamp,
|
||||
which would cause native clients to disappear messages at the wrong time.
|
||||
* Re-added notices about decryption errors.
|
||||
|
||||
[#531]: https://github.com/mautrix/signal/pull/531
|
||||
|
||||
# v0.7.0 (2024-08-16)
|
||||
|
||||
* Bumped minimum Go version to 1.22.
|
||||
* Updated to libsignal v0.55.0.
|
||||
* Rewrote bridge using bridgev2 architecture.
|
||||
* It is recommended to check the config file after upgrading. If you have
|
||||
prevented the bridge from writing to the config, you should update it
|
||||
manually.
|
||||
* Thanks to [@maltee1] for reimplementing Matrix -> Signal membership
|
||||
handling in the rewrite.
|
||||
* If you are still somehow using a pre-v0.5.0 versions, upgrading to v0.6.3
|
||||
is required before upgrading to v0.7.0 or higher.
|
||||
|
||||
# v0.6.3 (2024-07-16)
|
||||
|
||||
* Updated to libsignal v0.52.0.
|
||||
* Fixed bridge losing track of user phone numbers in some cases.
|
||||
* Fixed edge cases in handling new outgoing DMs started from other devices.
|
||||
* Added `sync groups` command (thanks to [@maltee1] in [#490]).
|
||||
* Fixed typo in location bridging example config
|
||||
(thanks to [@AndrewFerr] in [#516]).
|
||||
|
||||
[#490]: https://github.com/mautrix/signal/pull/490
|
||||
[#516]: https://github.com/mautrix/signal/pull/516
|
||||
[@AndrewFerr]: https://github.com/mautrix/signal/pull/516
|
||||
|
||||
# v0.6.2 (2024-06-16)
|
||||
|
||||
* Updated to libsignal v0.51.0.
|
||||
* Fixed voice messages not being rendered correctly in Element X.
|
||||
* Fixed contact avatars not being bridged correctly even when enabled in
|
||||
the bridge config.
|
||||
* Implemented connector for the upcoming bridgev2 architecture.
|
||||
|
||||
# v0.6.1 (2024-05-16)
|
||||
|
||||
* Added support for bridging location messages from Matrix to Signal
|
||||
(thanks to [@maltee1] in [#504]).
|
||||
* Note that Signal doesn't support real location messages, so they're just
|
||||
bridged as links. The link template is configurable.
|
||||
* Fixed bridging long text messages from Signal
|
||||
(thanks to [@maltee1] in [#506]).
|
||||
* Improved handling of ping timeouts in Signal websocket.
|
||||
|
||||
[#504]: https://github.com/mautrix/signal/pull/504
|
||||
[#506]: https://github.com/mautrix/signal/pull/506
|
||||
|
||||
# v0.6.0 (2024-04-16)
|
||||
|
||||
* Updated to libsignal v0.44.0.
|
||||
* Refactored bridge to support Signal's new phone number identifier (PNI)
|
||||
system in order to fix starting new chats and receiving messages from new
|
||||
users.
|
||||
* When starting a chat with a user you haven't talked to before, the portal
|
||||
room will not have a ghost user for the recipient until they accept the
|
||||
message request.
|
||||
* Added support for syncing existing groups on login instead of having to wait
|
||||
for new messages.
|
||||
* Added notices if decrypting incoming message from Signal fails.
|
||||
* Added bridging of group metadata from Matrix to Signal
|
||||
(thanks to [@maltee1] in [#461]).
|
||||
* Added command to create new Signal group for Matrix room
|
||||
(thanks to [@maltee1] in [#461] and [#491]).
|
||||
* Added commands for inviting users to Signal groups by phone number
|
||||
(thanks to [@maltee1] in [#495]).
|
||||
* Improved handling of missed Signal group metadata changes
|
||||
(thanks to [@maltee1] in [#488]).
|
||||
|
||||
[#461]: https://github.com/mautrix/signal/pull/461
|
||||
[#488]: https://github.com/mautrix/signal/pull/488
|
||||
[#491]: https://github.com/mautrix/signal/pull/491
|
||||
[#495]: https://github.com/mautrix/signal/pull/495
|
||||
|
||||
# v0.5.1 (2024-03-16)
|
||||
|
||||
* Updated to libsignal v0.41.0.
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
See <https://docs.mau.fi/bridges/general/contributing.html>
|
||||
32
Dockerfile
32
Dockerfile
|
|
@ -1,17 +1,18 @@
|
|||
# -- Build libsignal (with Rust) --
|
||||
FROM rust:1-alpine AS rust-builder
|
||||
RUN apk add --no-cache git make cmake protoc musl-dev g++ clang-dev protobuf-dev
|
||||
FROM rust:1-alpine as rust-builder
|
||||
RUN apk add --no-cache git make cmake protoc musl-dev g++ clang-dev
|
||||
|
||||
WORKDIR /build
|
||||
# Copy all files needed for Rust build, and no Go files
|
||||
COPY pkg/libsignalgo/libsignal/. pkg/libsignalgo/libsignal/.
|
||||
COPY build-rust.sh .
|
||||
|
||||
ARG DBG=0
|
||||
RUN ./build-rust.sh
|
||||
|
||||
# -- Build mautrix-signal (with Go) --
|
||||
FROM golang:1-alpine3.24 AS go-builder
|
||||
RUN apk add --no-cache git ca-certificates build-base olm-dev zlib-dev
|
||||
FROM golang:1-alpine3.19 AS go-builder
|
||||
RUN apk add --no-cache git ca-certificates build-base olm-dev
|
||||
|
||||
WORKDIR /build
|
||||
# Copy all files needed for Go build, and no Rust files
|
||||
|
|
@ -19,28 +20,39 @@ COPY *.go go.* *.yaml *.sh ./
|
|||
COPY pkg/signalmeow/. pkg/signalmeow/.
|
||||
COPY pkg/libsignalgo/* pkg/libsignalgo/
|
||||
COPY pkg/libsignalgo/resources/. pkg/libsignalgo/resources/.
|
||||
COPY pkg/msgconv/. pkg/msgconv/.
|
||||
COPY pkg/signalid/. pkg/signalid/.
|
||||
COPY pkg/connector/. pkg/connector/.
|
||||
COPY cmd/. cmd/.
|
||||
COPY config/. config/.
|
||||
COPY database/. database/.
|
||||
COPY msgconv/. msgconv/.
|
||||
COPY .git .git
|
||||
|
||||
ARG DBG=0
|
||||
ENV LIBRARY_PATH=.
|
||||
COPY --from=rust-builder /build/pkg/libsignalgo/libsignal/target/*/libsignal_ffi.a ./
|
||||
RUN <<EOF
|
||||
if [ "$DBG" = 1 ]; then
|
||||
go install github.com/go-delve/delve/cmd/dlv@latest
|
||||
else
|
||||
touch /go/bin/dlv
|
||||
fi
|
||||
EOF
|
||||
RUN ./build-go.sh
|
||||
|
||||
# -- Run mautrix-signal --
|
||||
FROM alpine:3.24
|
||||
FROM alpine:3.19
|
||||
|
||||
ENV UID=1337 \
|
||||
GID=1337
|
||||
|
||||
RUN apk add --no-cache ffmpeg su-exec ca-certificates bash jq curl yq-go olm
|
||||
RUN apk add --no-cache ffmpeg su-exec ca-certificates bash jq curl yq olm
|
||||
|
||||
COPY --from=go-builder /build/mautrix-signal /usr/bin/mautrix-signal
|
||||
COPY --from=go-builder /build/example-config.yaml /opt/mautrix-signal/example-config.yaml
|
||||
COPY --from=go-builder /build/docker-run.sh /docker-run.sh
|
||||
COPY --from=go-builder /go/bin/dlv /usr/bin/dlv
|
||||
VOLUME /data
|
||||
|
||||
ARG DBG
|
||||
ARG DBGWAIT=0
|
||||
ENV DBG=${DBG} DBGWAIT=${DBGWAIT}
|
||||
RUN echo "Debug mode: DBG=${DBG} DBGWAIT=${DBGWAIT}"
|
||||
CMD ["/docker-run.sh"]
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
ARG DOCKER_HUB="docker.io"
|
||||
|
||||
FROM ${DOCKER_HUB}/alpine:3.24
|
||||
FROM alpine:3.19
|
||||
|
||||
ENV UID=1337 \
|
||||
GID=1337
|
||||
|
||||
RUN apk add --no-cache ffmpeg su-exec ca-certificates bash jq curl yq-go
|
||||
RUN apk add --no-cache ffmpeg su-exec ca-certificates bash jq curl yq
|
||||
|
||||
ARG EXECUTABLE=./mautrix-signal
|
||||
COPY $EXECUTABLE /usr/bin/mautrix-signal
|
||||
COPY ./example-config.yaml /opt/mautrix-signal/example-config.yaml
|
||||
COPY ./docker-run.sh /docker-run.sh
|
||||
ENV BRIDGEV2=1
|
||||
VOLUME /data
|
||||
WORKDIR /data
|
||||
|
||||
CMD ["/docker-run.sh"]
|
||||
|
|
|
|||
28
Makefile
Normal file
28
Makefile
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
.PHONY: all build_rust copy_library build_go clean
|
||||
|
||||
all: build_rust copy_library build_go
|
||||
|
||||
LIBRARY_FILENAME=libsignal_ffi.a
|
||||
RUST_DIR=pkg/libsignalgo/libsignal
|
||||
GO_BINARY=mautrix-signal
|
||||
|
||||
# TODO fix linking with debug library
|
||||
#ifneq ($(DBG),1)
|
||||
RUST_TARGET_SUBDIR=release
|
||||
#else
|
||||
#RUST_TARGET_SUBDIR=debug
|
||||
#endif
|
||||
|
||||
build_rust:
|
||||
./build-rust.sh
|
||||
|
||||
copy_library:
|
||||
cp $(RUST_DIR)/target/$(RUST_TARGET_SUBDIR)/$(LIBRARY_FILENAME) .
|
||||
|
||||
build_go:
|
||||
LIBRARY_PATH="$${LIBRARY_PATH}:." ./build-go.sh
|
||||
|
||||
clean:
|
||||
rm -f ./$(LIBRARY_FILENAME)
|
||||
cd $(RUST_DIR) && cargo clean
|
||||
rm -f $(GO_BINARY)
|
||||
12
ROADMAP.md
12
ROADMAP.md
|
|
@ -1,18 +1,17 @@
|
|||
# Features & roadmap
|
||||
|
||||
* Matrix → Signal
|
||||
* [x] Message content
|
||||
* [ ] Message content
|
||||
* [x] Text
|
||||
* [x] Formatting
|
||||
* [x] Mentions
|
||||
* [x] Polls
|
||||
* [x] Media
|
||||
* [ ] Media
|
||||
* [x] Images
|
||||
* [x] Audio files
|
||||
* [x] Voice messages
|
||||
* [x] Files
|
||||
* [x] Gifs
|
||||
* [x] Locations
|
||||
* [ ] Locations
|
||||
* [x] Stickers
|
||||
* [x] Message edits
|
||||
* [x] Message reactions
|
||||
|
|
@ -35,7 +34,6 @@
|
|||
* [x] Text
|
||||
* [x] Formatting
|
||||
* [x] Mentions
|
||||
* [x] Polls
|
||||
* [ ] Media
|
||||
* [x] Images
|
||||
* [x] Voice notes
|
||||
|
|
@ -67,8 +65,8 @@
|
|||
* [ ] Delivery receipts (there's no good way to bridge these)
|
||||
* [x] Disappearing messages
|
||||
* Misc
|
||||
* [x] Automatic portal creation
|
||||
* [x] After login
|
||||
* [ ] Automatic portal creation
|
||||
* [ ] After login
|
||||
* [x] When receiving message
|
||||
* [x] Linking as secondary device
|
||||
* [ ] Registering as primary device
|
||||
|
|
|
|||
|
|
@ -1,2 +1,9 @@
|
|||
#!/bin/sh
|
||||
BINARY_NAME=mautrix-signal go tool maubuild "$@"
|
||||
MAUTRIX_VERSION=$(cat go.mod | grep 'maunium.net/go/mautrix ' | awk '{ print $2 }')
|
||||
GO_LDFLAGS="-X main.Tag=$(git describe --exact-match --tags 2>/dev/null) -X main.Commit=$(git rev-parse HEAD) -X 'main.BuildTime=`date '+%b %_d %Y, %H:%M:%S'`' -X 'maunium.net/go/mautrix.GoModVersion=$MAUTRIX_VERSION'"
|
||||
if [ "$DBG" = 1 ]; then
|
||||
GO_GCFLAGS='all=-N -l'
|
||||
else
|
||||
GO_LDFLAGS="-s -w ${GO_LDFLAGS}"
|
||||
fi
|
||||
go build -gcflags="$GO_GCFLAGS" -ldflags="$GO_LDFLAGS" -o mautrix-signal "$@"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
#!/bin/sh
|
||||
git submodule update --init
|
||||
cd pkg/libsignalgo/libsignal && RUSTFLAGS="-Ctarget-feature=-crt-static" RUSTC_WRAPPER="" cargo build -p libsignal-ffi --profile=release
|
||||
# TODO fix linking with debug library
|
||||
#if [ "$DBG" != 1 ]; then
|
||||
# RUST_PROFILE=release
|
||||
#else
|
||||
# RUST_PROFILE=dev
|
||||
#fi
|
||||
RUST_PROFILE=release
|
||||
cd pkg/libsignalgo/libsignal && RUSTFLAGS="-Ctarget-feature=-crt-static" RUSTC_WRAPPER="" cargo build -p libsignal-ffi --profile=$RUST_PROFILE
|
||||
|
|
|
|||
7
build.sh
7
build.sh
|
|
@ -1,5 +1,4 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
./build-rust.sh
|
||||
cp -f pkg/libsignalgo/libsignal/target/release/libsignal_ffi.a .
|
||||
LIBRARY_PATH=.:$LIBRARY_PATH ./build-go.sh
|
||||
git submodule init
|
||||
git submodule update
|
||||
make
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
// 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 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 main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"maunium.net/go/mautrix/bridgev2/matrix/mxmain"
|
||||
|
||||
"go.mau.fi/mautrix-signal/pkg/connector"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/web"
|
||||
)
|
||||
|
||||
// Information to find out exactly which commit the bridge was built from.
|
||||
// These are filled at build time with the -X linker flag.
|
||||
var (
|
||||
Tag = "unknown"
|
||||
Commit = "unknown"
|
||||
BuildTime = "unknown"
|
||||
)
|
||||
|
||||
var m = mxmain.BridgeMain{
|
||||
Name: "mautrix-signal",
|
||||
URL: "https://github.com/mautrix/signal",
|
||||
Description: "A Matrix-Signal puppeting bridge.",
|
||||
Version: "26.10",
|
||||
SemCalVer: true,
|
||||
|
||||
Connector: &connector.SignalConnector{},
|
||||
}
|
||||
|
||||
func main() {
|
||||
web.UserAgent = fmt.Sprintf("mautrix-signal/%s %s", m.Version, web.BaseUserAgent)
|
||||
m.InitVersion(Tag, Commit, BuildTime)
|
||||
m.Run()
|
||||
}
|
||||
918
commands.go
Normal file
918
commands.go
Normal file
|
|
@ -0,0 +1,918 @@
|
|||
// mautrix-signal - A Matrix-signal puppeting bridge.
|
||||
// Copyright (C) 2023 Scott Weber
|
||||
//
|
||||
// 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 main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/skip2/go-qrcode"
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/bridge/commands"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
||||
"go.mau.fi/mautrix-signal/pkg/libsignalgo"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow"
|
||||
"go.mau.fi/mautrix-signal/pkg/signalmeow/types"
|
||||
)
|
||||
|
||||
var (
|
||||
HelpSectionConnectionManagement = commands.HelpSection{Name: "Connection management", Order: 11}
|
||||
HelpSectionCreatingPortals = commands.HelpSection{Name: "Creating portals", Order: 15}
|
||||
HelpSectionPortalManagement = commands.HelpSection{Name: "Portal management", Order: 20}
|
||||
HelpSectionInvites = commands.HelpSection{Name: "Group invites", Order: 25}
|
||||
HelpSectionMiscellaneous = commands.HelpSection{Name: "Miscellaneous", Order: 30}
|
||||
)
|
||||
|
||||
type WrappedCommandEvent struct {
|
||||
*commands.Event
|
||||
Bridge *SignalBridge
|
||||
User *User
|
||||
Portal *Portal
|
||||
}
|
||||
|
||||
func (br *SignalBridge) RegisterCommands() {
|
||||
proc := br.CommandProcessor.(*commands.Processor)
|
||||
proc.AddHandlers(
|
||||
cmdPing,
|
||||
cmdLogin,
|
||||
cmdSetDeviceName,
|
||||
cmdPM,
|
||||
cmdResolvePhone,
|
||||
cmdSyncSpace,
|
||||
cmdDeleteSession,
|
||||
cmdSetRelay,
|
||||
cmdUnsetRelay,
|
||||
cmdDeletePortal,
|
||||
cmdDeleteAllPortals,
|
||||
cmdCleanupLostPortals,
|
||||
cmdInviteLink,
|
||||
cmdResetInviteLink,
|
||||
cmdCreate,
|
||||
)
|
||||
}
|
||||
|
||||
func wrapCommand(handler func(*WrappedCommandEvent)) func(*commands.Event) {
|
||||
return func(ce *commands.Event) {
|
||||
user := ce.User.(*User)
|
||||
var portal *Portal
|
||||
if ce.Portal != nil {
|
||||
portal = ce.Portal.(*Portal)
|
||||
}
|
||||
br := ce.Bridge.Child.(*SignalBridge)
|
||||
handler(&WrappedCommandEvent{ce, br, user, portal})
|
||||
}
|
||||
}
|
||||
|
||||
var cmdSetRelay = &commands.FullHandler{
|
||||
Func: wrapCommand(fnSetRelay),
|
||||
Name: "set-relay",
|
||||
Help: commands.HelpMeta{
|
||||
Section: HelpSectionPortalManagement,
|
||||
Description: "Relay messages in this room through your Signal account.",
|
||||
},
|
||||
RequiresPortal: true,
|
||||
RequiresLogin: true,
|
||||
}
|
||||
|
||||
func fnSetRelay(ce *WrappedCommandEvent) {
|
||||
if !ce.Bridge.Config.Bridge.Relay.Enabled {
|
||||
ce.Reply("Relay mode is not enabled on this instance of the bridge")
|
||||
} else if ce.Bridge.Config.Bridge.Relay.AdminOnly && !ce.User.Admin {
|
||||
ce.Reply("Only bridge admins are allowed to enable relay mode on this instance of the bridge")
|
||||
} else {
|
||||
ce.Portal.RelayUserID = ce.User.MXID
|
||||
ce.Portal.Update(ce.Ctx)
|
||||
ce.Reply("Messages from non-logged-in users in this room will now be bridged through your Signal account")
|
||||
}
|
||||
}
|
||||
|
||||
var cmdUnsetRelay = &commands.FullHandler{
|
||||
Func: wrapCommand(fnUnsetRelay),
|
||||
Name: "unset-relay",
|
||||
Help: commands.HelpMeta{
|
||||
Section: HelpSectionPortalManagement,
|
||||
Description: "Stop relaying messages in this room.",
|
||||
},
|
||||
RequiresPortal: true,
|
||||
}
|
||||
|
||||
func fnUnsetRelay(ce *WrappedCommandEvent) {
|
||||
if !ce.Bridge.Config.Bridge.Relay.Enabled {
|
||||
ce.Reply("Relay mode is not enabled on this instance of the bridge")
|
||||
} else if ce.Bridge.Config.Bridge.Relay.AdminOnly && !ce.User.Admin {
|
||||
ce.Reply("Only bridge admins are allowed to enable relay mode on this instance of the bridge")
|
||||
} else {
|
||||
ce.Portal.RelayUserID = ""
|
||||
ce.Portal.Update(ce.Ctx)
|
||||
ce.Reply("Messages from non-logged-in users will no longer be bridged in this room")
|
||||
}
|
||||
}
|
||||
|
||||
var cmdDeleteSession = &commands.FullHandler{
|
||||
Func: wrapCommand(fnDeleteSession),
|
||||
Name: "delete-session",
|
||||
Help: commands.HelpMeta{
|
||||
Section: HelpSectionConnectionManagement,
|
||||
Description: "Disconnect from Signal, clearing sessions but keeping other data. Reconnect with `login`",
|
||||
},
|
||||
}
|
||||
|
||||
func fnDeleteSession(ce *WrappedCommandEvent) {
|
||||
if !ce.User.IsLoggedIn() {
|
||||
ce.Reply("You're not logged in")
|
||||
return
|
||||
}
|
||||
ce.User.Client.ClearKeysAndDisconnect(ce.Ctx)
|
||||
ce.Reply("Disconnected from Signal")
|
||||
}
|
||||
|
||||
var cmdPing = &commands.FullHandler{
|
||||
Func: wrapCommand(fnPing),
|
||||
Name: "ping",
|
||||
Help: commands.HelpMeta{
|
||||
Section: commands.HelpSectionAuth,
|
||||
Description: "Check your connection to Signal",
|
||||
},
|
||||
}
|
||||
|
||||
func fnPing(ce *WrappedCommandEvent) {
|
||||
if ce.User.SignalID == uuid.Nil {
|
||||
ce.Reply("You're not logged in")
|
||||
} else if !ce.User.IsLoggedIn() {
|
||||
ce.Reply("You were logged in at some point, but are not anymore")
|
||||
} else if !ce.User.Client.IsConnected() {
|
||||
ce.Reply("You're logged into Signal, but not connected to the server")
|
||||
} else {
|
||||
ce.Reply("You're logged into Signal and probably connected to the server")
|
||||
}
|
||||
}
|
||||
|
||||
var cmdSetDeviceName = &commands.FullHandler{
|
||||
Func: wrapCommand(fnSetDeviceName),
|
||||
Name: "set-device-name",
|
||||
Help: commands.HelpMeta{
|
||||
Section: HelpSectionConnectionManagement,
|
||||
Description: "Set the name of this device in Signal",
|
||||
Args: "<name>",
|
||||
},
|
||||
RequiresLogin: true,
|
||||
}
|
||||
|
||||
func fnSetDeviceName(ce *WrappedCommandEvent) {
|
||||
if len(ce.Args) == 0 {
|
||||
ce.Reply("**Usage:** `set-device-name <name>`")
|
||||
return
|
||||
}
|
||||
|
||||
name := strings.Join(ce.Args, " ")
|
||||
err := ce.User.Client.UpdateDeviceName(ce.Ctx, name)
|
||||
if err != nil {
|
||||
ce.Reply("Error setting device name: %v", err)
|
||||
return
|
||||
}
|
||||
ce.Reply("Device name updated")
|
||||
}
|
||||
|
||||
var cmdPM = &commands.FullHandler{
|
||||
Func: wrapCommand(fnPM),
|
||||
Name: "pm",
|
||||
Help: commands.HelpMeta{
|
||||
Section: HelpSectionCreatingPortals,
|
||||
Description: "Open a private chat with the given phone number.",
|
||||
Args: "<_international phone number_>",
|
||||
},
|
||||
RequiresLogin: true,
|
||||
}
|
||||
|
||||
var numberCleaner = strings.NewReplacer("-", "", " ", "", "(", "", ")", "", "+", "")
|
||||
|
||||
func fnPM(ce *WrappedCommandEvent) {
|
||||
if len(ce.Args) == 0 {
|
||||
ce.Reply("**Usage:** `pm <international phone number>`")
|
||||
return
|
||||
}
|
||||
number, err := strconv.ParseUint(numberCleaner.Replace(strings.Join(ce.Args, "")), 10, 64)
|
||||
if err != nil {
|
||||
ce.Reply("Failed to parse number")
|
||||
return
|
||||
}
|
||||
|
||||
user := ce.User
|
||||
var aci, pni uuid.UUID
|
||||
e164 := fmt.Sprintf("+%d", number)
|
||||
var recipient *types.Recipient
|
||||
|
||||
if recipient, err = user.Client.ContactByE164(ce.Ctx, e164); err != nil {
|
||||
ce.Reply("Error looking up number in local contact list: %v", err)
|
||||
return
|
||||
} else if recipient != nil && (recipient.ACI != uuid.Nil || recipient.PNI != uuid.Nil) {
|
||||
// TODO maybe lookup PNI if there's only ACI and E164 stored?
|
||||
aci = recipient.ACI
|
||||
pni = recipient.PNI
|
||||
} else if resp, err := user.Client.LookupPhone(ce.Ctx, number); err != nil {
|
||||
ce.ZLog.Err(err).Uint64("e164", number).Msg("Failed to lookup number on server")
|
||||
ce.Reply("Error looking up number on server: %v", err)
|
||||
return
|
||||
} else {
|
||||
aci = resp[number].ACI
|
||||
pni = resp[number].PNI
|
||||
if aci == uuid.Nil && pni == uuid.Nil {
|
||||
ce.Reply("+%d doesn't seem to be on Signal", number)
|
||||
return
|
||||
}
|
||||
recipient, err = user.Client.Store.RecipientStore.UpdateRecipientE164(ce.Ctx, aci, pni, e164)
|
||||
if err != nil {
|
||||
ce.ZLog.Err(err).Msg("Failed to save recipient entry after looking up phone")
|
||||
}
|
||||
aci, pni = recipient.ACI, recipient.PNI
|
||||
}
|
||||
ce.ZLog.Debug().
|
||||
Uint64("e164", number).
|
||||
Stringer("aci", aci).
|
||||
Stringer("pni", pni).
|
||||
Msg("Found DM target user")
|
||||
|
||||
var targetServiceID libsignalgo.ServiceID
|
||||
if aci != uuid.Nil {
|
||||
targetServiceID = libsignalgo.NewACIServiceID(aci)
|
||||
} else {
|
||||
targetServiceID = libsignalgo.NewPNIServiceID(pni)
|
||||
}
|
||||
portal := user.GetPortalByChatID(targetServiceID.String())
|
||||
if portal == nil {
|
||||
ce.Reply("Couldn't get portal with %s/+%d", targetServiceID, number)
|
||||
return
|
||||
} else if portal.MXID != "" {
|
||||
ok := portal.ensureUserInvited(ce.Ctx, ce.User)
|
||||
if ok {
|
||||
ce.Reply("You already have a portal with +%d at [%s](%s)", number, portal.MXID, portal.MXID.URI(portal.bridge.Config.Homeserver.Domain).MatrixToURL())
|
||||
return
|
||||
}
|
||||
ce.ZLog.Warn().Stringer("existing_room_id", portal.MXID).Msg("Ensuring user is invited to existing room failed, creating new room")
|
||||
portal.Cleanup(ce.Ctx, false)
|
||||
portal.MXID = ""
|
||||
}
|
||||
|
||||
if err = portal.CreateMatrixRoom(ce.Ctx, user, 0); err != nil {
|
||||
ce.ZLog.Err(err).Msg("Failed to create portal room")
|
||||
ce.Reply("Error creating Matrix room for portal to +%d", number)
|
||||
} else {
|
||||
ce.Reply("Created portal room [%s](%s) with +%d and invited you to it.", portal.MXID, portal.MXID.URI(portal.bridge.Config.Homeserver.Domain).MatrixToURL(), number)
|
||||
}
|
||||
}
|
||||
|
||||
var cmdResolvePhone = &commands.FullHandler{
|
||||
Func: wrapCommand(fnResolvePhone),
|
||||
Name: "resolve-phone",
|
||||
Help: commands.HelpMeta{
|
||||
Section: HelpSectionCreatingPortals,
|
||||
Description: "Look up phone numbers on the Signal servers.",
|
||||
Args: "<numbers...>",
|
||||
},
|
||||
RequiresLogin: true,
|
||||
}
|
||||
|
||||
func fnResolvePhone(ce *WrappedCommandEvent) {
|
||||
numbers := make([]uint64, len(ce.Args))
|
||||
for i, arg := range ce.Args {
|
||||
var err error
|
||||
numbers[i], err = strconv.ParseUint(numberCleaner.Replace(arg), 10, 64)
|
||||
if err != nil {
|
||||
ce.Reply("Failed to parse number %s: %v", arg, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
resp, err := ce.User.Client.LookupPhone(ce.Ctx, numbers...)
|
||||
if err != nil {
|
||||
ce.Reply("Failed to look up: %v", err)
|
||||
} else {
|
||||
var out strings.Builder
|
||||
for _, phone := range numbers {
|
||||
result, found := resp[phone]
|
||||
if found {
|
||||
_, _ = fmt.Fprintf(&out, "+%d: %s / %s\n", phone, result.ACI, result.PNI)
|
||||
} else {
|
||||
_, _ = fmt.Fprintf(&out, "+%d: not found\n", phone)
|
||||
}
|
||||
}
|
||||
ce.Reply(strings.TrimSpace(out.String()))
|
||||
}
|
||||
}
|
||||
|
||||
var cmdSyncSpace = &commands.FullHandler{
|
||||
Func: wrapCommand(fnSyncSpace),
|
||||
Name: "sync-space",
|
||||
Help: commands.HelpMeta{
|
||||
Section: HelpSectionMiscellaneous,
|
||||
Description: "Synchronize your personal filtering space",
|
||||
},
|
||||
RequiresLogin: true,
|
||||
}
|
||||
|
||||
func fnSyncSpace(ce *WrappedCommandEvent) {
|
||||
if !ce.Bridge.Config.Bridge.PersonalFilteringSpaces {
|
||||
ce.Reply("Personal filtering spaces are not enabled on this instance of the bridge")
|
||||
return
|
||||
}
|
||||
ctx := ce.Ctx
|
||||
dmKeys, err := ce.Bridge.DB.Portal.FindPrivateChatsNotInSpace(ctx, ce.User.SignalID)
|
||||
if err != nil {
|
||||
ce.ZLog.Err(err).Msg("Failed to get private chat keys")
|
||||
ce.Reply("Failed to get private chat IDs from database")
|
||||
return
|
||||
}
|
||||
count := 0
|
||||
allPortals := ce.Bridge.GetAllPortalsWithMXID()
|
||||
for _, portal := range allPortals {
|
||||
if portal.IsPrivateChat() {
|
||||
continue
|
||||
}
|
||||
if ce.Bridge.StateStore.IsInRoom(ctx, portal.MXID, ce.User.MXID) && portal.addToPersonalSpace(ctx, ce.User) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
for _, key := range dmKeys {
|
||||
portal := ce.Bridge.GetPortalByChatID(key)
|
||||
portal.addToPersonalSpace(ctx, ce.User)
|
||||
count++
|
||||
}
|
||||
plural := "s"
|
||||
if count == 1 {
|
||||
plural = ""
|
||||
}
|
||||
ce.Reply("Added %d room%s to space", count, plural)
|
||||
}
|
||||
|
||||
var cmdLogin = &commands.FullHandler{
|
||||
Func: wrapCommand(fnLogin),
|
||||
Name: "login",
|
||||
Help: commands.HelpMeta{
|
||||
Section: commands.HelpSectionAuth,
|
||||
Description: "Link the bridge to your Signal account as a web client.",
|
||||
},
|
||||
}
|
||||
|
||||
func fnLogin(ce *WrappedCommandEvent) {
|
||||
if ce.User.IsLoggedIn() {
|
||||
if ce.User.Client.IsConnected() {
|
||||
ce.Reply("You're already logged in")
|
||||
} else {
|
||||
ce.Reply("You're already logged in, but not connected 🤔")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var qrEventID, msgEventID id.EventID
|
||||
var signalID uuid.UUID
|
||||
var signalPhone string
|
||||
|
||||
// First get the provisioning URL
|
||||
provChan, err := ce.User.Login()
|
||||
if err != nil {
|
||||
ce.ZLog.Err(err).Msg("Failure logging in")
|
||||
ce.Reply("Failure logging in: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := <-provChan
|
||||
if resp.Err != nil || resp.State == signalmeow.StateProvisioningError {
|
||||
ce.Reply("Error getting provisioning URL: %v", resp.Err)
|
||||
return
|
||||
}
|
||||
if resp.State == signalmeow.StateProvisioningURLReceived {
|
||||
qrEventID, msgEventID = ce.User.sendQR(ce, resp.ProvisioningURL, qrEventID, msgEventID)
|
||||
} else {
|
||||
ce.Reply("Unexpected state: %v", resp.State)
|
||||
return
|
||||
}
|
||||
|
||||
// Next, get the results of finishing registration
|
||||
resp = <-provChan
|
||||
_, _ = ce.Bot.RedactEvent(ce.Ctx, ce.RoomID, qrEventID)
|
||||
_, _ = ce.Bot.RedactEvent(ce.Ctx, ce.RoomID, msgEventID)
|
||||
if resp.Err != nil || resp.State == signalmeow.StateProvisioningError {
|
||||
if resp.Err != nil && strings.HasSuffix(resp.Err.Error(), " EOF") {
|
||||
ce.Reply("Logging in timed out, please try again.")
|
||||
} else {
|
||||
ce.Reply("Error finishing registration: %v", resp.Err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if resp.State == signalmeow.StateProvisioningDataReceived {
|
||||
signalID = resp.ProvisioningData.ACI
|
||||
signalPhone = resp.ProvisioningData.Number
|
||||
} else {
|
||||
ce.Reply("Unexpected state: %v", resp.State)
|
||||
return
|
||||
}
|
||||
|
||||
// Finally, get the results of generating and registering prekeys
|
||||
resp = <-provChan
|
||||
if resp.Err != nil || resp.State == signalmeow.StateProvisioningError {
|
||||
ce.Reply("Error with prekeys: %v", resp.Err)
|
||||
return
|
||||
} else if resp.State != signalmeow.StateProvisioningPreKeysRegistered {
|
||||
ce.Reply("Unexpected state: %v", resp.State)
|
||||
return
|
||||
}
|
||||
|
||||
if signalID == uuid.Nil {
|
||||
ce.Reply("Problem logging in - No SignalID received")
|
||||
return
|
||||
}
|
||||
ce.User.saveSignalID(ce.Ctx, signalID, signalPhone)
|
||||
|
||||
// Connect to Signal
|
||||
ce.User.Connect()
|
||||
ce.Reply("Successfully logged in as %s (UUID: %s)", ce.User.SignalUsername, ce.User.SignalID)
|
||||
}
|
||||
|
||||
func (user *User) sendQR(ce *WrappedCommandEvent, code string, prevQR, prevMsg id.EventID) (qr, msg id.EventID) {
|
||||
content, ok := user.uploadQR(ce, code)
|
||||
if !ok {
|
||||
return prevQR, prevMsg
|
||||
}
|
||||
if len(prevQR) != 0 {
|
||||
content.SetEdit(prevQR)
|
||||
}
|
||||
resp, err := ce.Bot.SendMessageEvent(ce.Ctx, ce.RoomID, event.EventMessage, &content)
|
||||
if err != nil {
|
||||
ce.ZLog.Err(err).Msg("Failed to send QR code to user")
|
||||
} else if len(prevQR) == 0 {
|
||||
prevQR = resp.EventID
|
||||
}
|
||||
content = event.MessageEventContent{
|
||||
MsgType: event.MsgNotice,
|
||||
Body: fmt.Sprintf("Raw linking URI: %s", code),
|
||||
Format: event.FormatHTML,
|
||||
FormattedBody: fmt.Sprintf("Raw linking URI: <code>%s</code>", code),
|
||||
}
|
||||
if len(prevMsg) != 0 {
|
||||
content.SetEdit(prevMsg)
|
||||
}
|
||||
resp, err = ce.Bot.SendMessageEvent(ce.Ctx, ce.RoomID, event.EventMessage, &content)
|
||||
if err != nil {
|
||||
ce.ZLog.Err(err).Msg("Failed to send raw code to user")
|
||||
} else if len(prevMsg) == 0 {
|
||||
prevMsg = resp.EventID
|
||||
}
|
||||
return prevQR, prevMsg
|
||||
}
|
||||
|
||||
func (user *User) uploadQR(ce *WrappedCommandEvent, code string) (event.MessageEventContent, bool) {
|
||||
const size = 512
|
||||
qrCode, err := qrcode.Encode(code, qrcode.Low, size)
|
||||
if err != nil {
|
||||
ce.ZLog.Err(err).Msg("Failed to encode QR code")
|
||||
ce.Reply("Failed to encode QR code: %v", err)
|
||||
return event.MessageEventContent{}, false
|
||||
}
|
||||
|
||||
bot := user.bridge.AS.BotClient()
|
||||
|
||||
resp, err := bot.UploadBytes(ce.Ctx, qrCode, "image/png")
|
||||
if err != nil {
|
||||
ce.ZLog.Err(err).Msg("Failed to upload QR code")
|
||||
ce.Reply("Failed to upload QR code: %v", err)
|
||||
return event.MessageEventContent{}, false
|
||||
}
|
||||
return event.MessageEventContent{
|
||||
MsgType: event.MsgImage,
|
||||
Info: &event.FileInfo{
|
||||
MimeType: "image/png",
|
||||
Width: size,
|
||||
Height: size,
|
||||
Size: len(qrCode),
|
||||
},
|
||||
Body: "qr.png",
|
||||
URL: resp.ContentURI.CUString(),
|
||||
}, true
|
||||
}
|
||||
|
||||
func canDeletePortal(ctx context.Context, portal *Portal, userID id.UserID) bool {
|
||||
if len(portal.MXID) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
members, err := portal.MainIntent().JoinedMembers(ctx, portal.MXID)
|
||||
if err != nil {
|
||||
portal.log.Err(err).
|
||||
Stringer("user_id", userID).
|
||||
Msg("Failed to get joined members to check if user can delete portal")
|
||||
return false
|
||||
}
|
||||
for otherUser := range members.Joined {
|
||||
_, isPuppet := portal.bridge.ParsePuppetMXID(otherUser)
|
||||
if isPuppet || otherUser == portal.bridge.Bot.UserID || otherUser == userID {
|
||||
continue
|
||||
}
|
||||
user := portal.bridge.GetUserByMXID(otherUser)
|
||||
if user != nil && user.IsLoggedIn() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var cmdDeletePortal = &commands.FullHandler{
|
||||
Func: wrapCommand(fnDeletePortal),
|
||||
Name: "delete-portal",
|
||||
Help: commands.HelpMeta{
|
||||
Section: HelpSectionPortalManagement,
|
||||
Description: "Delete the current portal. If the portal is used by other people, this is limited to bridge admins.",
|
||||
},
|
||||
RequiresPortal: true,
|
||||
}
|
||||
|
||||
func fnDeletePortal(ce *WrappedCommandEvent) {
|
||||
if !ce.User.Admin && !canDeletePortal(ce.Ctx, ce.Portal, ce.User.MXID) {
|
||||
ce.Reply("Only bridge admins can delete portals with other Matrix users")
|
||||
return
|
||||
}
|
||||
|
||||
ce.Portal.log.Info().Stringer("user_id", ce.User.MXID).Msg("User requested deletion of portal")
|
||||
ce.Portal.Delete()
|
||||
ce.Portal.Cleanup(ce.Ctx, false)
|
||||
}
|
||||
|
||||
var cmdDeleteAllPortals = &commands.FullHandler{
|
||||
Func: wrapCommand(fnDeleteAllPortals),
|
||||
Name: "delete-all-portals",
|
||||
Help: commands.HelpMeta{
|
||||
Section: HelpSectionPortalManagement,
|
||||
Description: "Delete all portals.",
|
||||
},
|
||||
}
|
||||
|
||||
func fnDeleteAllPortals(ce *WrappedCommandEvent) {
|
||||
portals := ce.Bridge.GetAllPortalsWithMXID()
|
||||
var portalsToDelete []*Portal
|
||||
|
||||
if ce.User.Admin {
|
||||
portalsToDelete = portals
|
||||
} else {
|
||||
portalsToDelete = portals[:0]
|
||||
for _, portal := range portals {
|
||||
if canDeletePortal(ce.Ctx, portal, ce.User.MXID) {
|
||||
portalsToDelete = append(portalsToDelete, portal)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(portalsToDelete) == 0 {
|
||||
ce.Reply("Didn't find any portals to delete")
|
||||
return
|
||||
}
|
||||
|
||||
leave := func(portal *Portal) {
|
||||
if len(portal.MXID) > 0 {
|
||||
_, _ = portal.MainIntent().KickUser(ce.Ctx, portal.MXID, &mautrix.ReqKickUser{
|
||||
Reason: "Deleting portal",
|
||||
UserID: ce.User.MXID,
|
||||
})
|
||||
}
|
||||
}
|
||||
customPuppet := ce.Bridge.GetPuppetByCustomMXID(ce.User.MXID)
|
||||
if customPuppet != nil && customPuppet.CustomIntent() != nil {
|
||||
intent := customPuppet.CustomIntent()
|
||||
leave = func(portal *Portal) {
|
||||
if len(portal.MXID) > 0 {
|
||||
_, _ = intent.LeaveRoom(ce.Ctx, portal.MXID)
|
||||
_, _ = intent.ForgetRoom(ce.Ctx, portal.MXID)
|
||||
}
|
||||
}
|
||||
}
|
||||
ce.Reply("Found %d portals, deleting...", len(portalsToDelete))
|
||||
for _, portal := range portalsToDelete {
|
||||
portal.Delete()
|
||||
leave(portal)
|
||||
}
|
||||
ce.Reply("Finished deleting portal info. Now cleaning up rooms in background.")
|
||||
|
||||
backgroundCtx := context.TODO()
|
||||
go func() {
|
||||
for _, portal := range portalsToDelete {
|
||||
portal.Cleanup(backgroundCtx, false)
|
||||
}
|
||||
ce.Reply("Finished background cleanup of deleted portal rooms.")
|
||||
}()
|
||||
}
|
||||
|
||||
var cmdCleanupLostPortals = &commands.FullHandler{
|
||||
Func: wrapCommand(fnCleanupLostPortals),
|
||||
Name: "cleanup-lost-portals",
|
||||
Help: commands.HelpMeta{
|
||||
Section: HelpSectionPortalManagement,
|
||||
Description: "Clean up portals that were discarded due to the receiver not being logged into the bridge",
|
||||
},
|
||||
RequiresAdmin: true,
|
||||
}
|
||||
|
||||
func fnCleanupLostPortals(ce *WrappedCommandEvent) {
|
||||
portals, err := ce.Bridge.DB.LostPortal.GetAll(ce.Ctx)
|
||||
if err != nil {
|
||||
ce.Reply("Failed to get portals: %v", err)
|
||||
return
|
||||
} else if len(portals) == 0 {
|
||||
ce.Reply("No lost portals found")
|
||||
return
|
||||
}
|
||||
|
||||
ce.Reply("Found %d lost portals, deleting...", len(portals))
|
||||
for _, portal := range portals {
|
||||
dmUUID, err := uuid.Parse(portal.ChatID)
|
||||
intent := ce.Bot
|
||||
if err == nil {
|
||||
intent = ce.Bridge.GetPuppetBySignalID(dmUUID).DefaultIntent()
|
||||
}
|
||||
ce.Bridge.CleanupRoom(ce.Ctx, ce.ZLog, intent, portal.MXID, false)
|
||||
err = portal.Delete(ce.Ctx)
|
||||
if err != nil {
|
||||
ce.ZLog.Err(err).Msg("Failed to delete lost portal from database after cleanup")
|
||||
}
|
||||
}
|
||||
ce.Reply("Finished cleaning up portals")
|
||||
}
|
||||
|
||||
var cmdInviteLink = &commands.FullHandler{
|
||||
Func: wrapCommand(fnInviteLink),
|
||||
Name: "invite-link",
|
||||
Help: commands.HelpMeta{
|
||||
Section: HelpSectionPortalManagement,
|
||||
Description: "Get the invite link for the corresponding Signal Group",
|
||||
},
|
||||
RequiresLogin: true,
|
||||
}
|
||||
|
||||
func fnInviteLink(ce *WrappedCommandEvent) {
|
||||
if ce.Portal == nil {
|
||||
ce.Reply("This is not a portal room")
|
||||
return
|
||||
}
|
||||
if ce.Portal.IsPrivateChat() {
|
||||
ce.Reply("Invite Links are not available for private chats")
|
||||
return
|
||||
}
|
||||
inviteLinkPassword, err := ce.Portal.GetInviteLink(ce.Ctx, ce.User)
|
||||
if err != nil {
|
||||
ce.Reply("Error getting invite link %w", err)
|
||||
return
|
||||
}
|
||||
ce.Reply(inviteLinkPassword)
|
||||
}
|
||||
|
||||
var cmdResetInviteLink = &commands.FullHandler{
|
||||
Func: wrapCommand(fnResetInviteLink),
|
||||
Name: "reset-invite-link",
|
||||
Help: commands.HelpMeta{
|
||||
Section: HelpSectionPortalManagement,
|
||||
Description: "Generate a new invite link password",
|
||||
},
|
||||
RequiresLogin: true,
|
||||
}
|
||||
|
||||
func fnResetInviteLink(ce *WrappedCommandEvent) {
|
||||
if ce.Portal == nil {
|
||||
ce.Reply("This is not a portal room")
|
||||
return
|
||||
}
|
||||
if ce.Portal.IsPrivateChat() {
|
||||
ce.Reply("Invite Links are not available for private chats")
|
||||
return
|
||||
}
|
||||
err := ce.Portal.ResetInviteLink(ce.Ctx, ce.User)
|
||||
if err != nil {
|
||||
ce.Reply("Error setting new invite link %w", err)
|
||||
}
|
||||
inviteLink, err := ce.Portal.GetInviteLink(ce.Ctx, ce.User)
|
||||
if err != nil {
|
||||
ce.Reply("Error getting new invite link %w", err)
|
||||
return
|
||||
}
|
||||
ce.Reply(inviteLink)
|
||||
}
|
||||
|
||||
var cmdCreate = &commands.FullHandler{
|
||||
Func: wrapCommand(fnCreate),
|
||||
Name: "create",
|
||||
Help: commands.HelpMeta{
|
||||
Section: HelpSectionCreatingPortals,
|
||||
Description: "Create a Signal group chat for the current Matrix room.",
|
||||
},
|
||||
RequiresLogin: true,
|
||||
}
|
||||
|
||||
func fnCreate(ce *WrappedCommandEvent) {
|
||||
if ce.Portal != nil {
|
||||
ce.Reply("This is already a portal room")
|
||||
return
|
||||
}
|
||||
|
||||
roomState, err := ce.Bot.State(ce.Ctx, ce.RoomID)
|
||||
if err != nil {
|
||||
ce.Reply("Failed to get room state: %w", err)
|
||||
return
|
||||
}
|
||||
members := roomState[event.StateMember]
|
||||
powerLevelsRaw, ok := roomState[event.StatePowerLevels][""]
|
||||
if !ok {
|
||||
ce.Reply("Failed to get room power levels")
|
||||
return
|
||||
}
|
||||
powerLevelsRaw.Content.ParseRaw(event.StatePowerLevels)
|
||||
powerLevels := powerLevelsRaw.Content.AsPowerLevels()
|
||||
joinRulesRaw, ok := roomState[event.StateJoinRules][""]
|
||||
if !ok {
|
||||
ce.Reply("Failed to get join rules")
|
||||
return
|
||||
}
|
||||
joinRulesRaw.Content.ParseRaw(event.StateJoinRules)
|
||||
joinRule := joinRulesRaw.Content.AsJoinRules().JoinRule
|
||||
roomNameEventRaw, ok := roomState[event.StateRoomName][""]
|
||||
if !ok {
|
||||
ce.Reply("Failed to get room name")
|
||||
return
|
||||
}
|
||||
roomNameEventRaw.Content.ParseRaw(event.StateRoomName)
|
||||
roomName := roomNameEventRaw.Content.AsRoomName().Name
|
||||
if len(roomName) == 0 {
|
||||
ce.Reply("Please set a name for the room first")
|
||||
return
|
||||
}
|
||||
roomTopic := ""
|
||||
roomTopicEvent, ok := roomState[event.StateTopic][""]
|
||||
if ok {
|
||||
roomTopicEvent.Content.ParseRaw(event.StateTopic)
|
||||
roomTopic = roomTopicEvent.Content.AsTopic().Topic
|
||||
}
|
||||
roomAvatarEvent, ok := roomState[event.StateRoomAvatar][""]
|
||||
var avatarHash string
|
||||
var avatarURL id.ContentURI
|
||||
var avatarBytes []byte
|
||||
if ok {
|
||||
roomAvatarEvent.Content.ParseRaw(event.StateRoomAvatar)
|
||||
avatarURL = roomAvatarEvent.Content.AsRoomAvatar().URL
|
||||
if !avatarURL.IsEmpty() {
|
||||
avatarBytes, err = ce.Bot.DownloadBytes(ce.Ctx, avatarURL)
|
||||
if err != nil {
|
||||
ce.ZLog.Err(err).Stringer("Failed to download updated avatar %s", avatarURL)
|
||||
return
|
||||
}
|
||||
hash := sha256.Sum256(avatarBytes)
|
||||
avatarHash = hex.EncodeToString(hash[:])
|
||||
ce.ZLog.Debug().Stringers("%s set the group avatar to %s", []fmt.Stringer{ce.User.MXID, avatarURL})
|
||||
}
|
||||
}
|
||||
var encryptionEvent *event.EncryptionEventContent
|
||||
encryptionEventContent, ok := roomState[event.StateEncryption][""]
|
||||
if ok {
|
||||
encryptionEventContent.Content.ParseRaw(event.StateEncryption)
|
||||
encryptionEvent = encryptionEventContent.Content.AsEncryption()
|
||||
}
|
||||
var participants []*signalmeow.GroupMember
|
||||
var bannedMembers []*signalmeow.BannedMember
|
||||
participantDedup := make(map[uuid.UUID]bool)
|
||||
participantDedup[uuid.Nil] = true
|
||||
for key, member := range members {
|
||||
mxid := id.UserID(key)
|
||||
member.Content.ParseRaw(event.StateMember)
|
||||
content := member.Content.AsMember()
|
||||
membership := content.Membership
|
||||
var uuid uuid.UUID
|
||||
puppet := ce.Bridge.GetPuppetByMXID(mxid)
|
||||
if puppet != nil {
|
||||
uuid = puppet.SignalID
|
||||
} else {
|
||||
user := ce.Bridge.GetUserByMXID(mxid)
|
||||
if user != nil && user.IsLoggedIn() {
|
||||
uuid = user.SignalID
|
||||
}
|
||||
}
|
||||
role := signalmeow.GroupMember_DEFAULT
|
||||
if powerLevels.GetUserLevel(mxid) >= 50 {
|
||||
role = signalmeow.GroupMember_ADMINISTRATOR
|
||||
}
|
||||
if !participantDedup[uuid] {
|
||||
participantDedup[uuid] = true
|
||||
// invites should be added on signal and then auto-joined
|
||||
// joined members that need to be pending-Members should have their signal invite auto-accepted
|
||||
if membership == event.MembershipJoin || membership == event.MembershipInvite {
|
||||
participants = append(participants, &signalmeow.GroupMember{
|
||||
UserID: uuid,
|
||||
Role: role,
|
||||
})
|
||||
} else if membership == event.MembershipBan {
|
||||
bannedMembers = append(bannedMembers, &signalmeow.BannedMember{
|
||||
UserID: uuid,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
addFromInviteLinkAccess := signalmeow.AccessControl_UNSATISFIABLE
|
||||
if joinRule == event.JoinRulePublic {
|
||||
addFromInviteLinkAccess = signalmeow.AccessControl_ANY
|
||||
} else if joinRule == event.JoinRuleKnock {
|
||||
addFromInviteLinkAccess = signalmeow.AccessControl_ADMINISTRATOR
|
||||
}
|
||||
var inviteLinkPassword types.SerializedInviteLinkPassword
|
||||
if addFromInviteLinkAccess != signalmeow.AccessControl_UNSATISFIABLE {
|
||||
inviteLinkPassword = signalmeow.GenerateInviteLinkPassword()
|
||||
}
|
||||
membersAccess := signalmeow.AccessControl_MEMBER
|
||||
if powerLevels.Invite() >= 50 {
|
||||
membersAccess = signalmeow.AccessControl_ADMINISTRATOR
|
||||
}
|
||||
attributesAccess := signalmeow.AccessControl_MEMBER
|
||||
if powerLevels.StateDefault() >= 50 {
|
||||
attributesAccess = signalmeow.AccessControl_ADMINISTRATOR
|
||||
}
|
||||
announcementsOnly := false
|
||||
if powerLevels.EventsDefault >= 50 {
|
||||
announcementsOnly = true
|
||||
}
|
||||
ce.ZLog.Info().
|
||||
Str("room_name", roomName).
|
||||
Any("participants", participants).
|
||||
Msg("Creating Signal group for Matrix room")
|
||||
group, err := ce.User.Client.CreateGroupOnServer(ce.Ctx, &signalmeow.Group{
|
||||
Title: roomName,
|
||||
Description: roomTopic,
|
||||
Members: participants,
|
||||
AccessControl: &signalmeow.GroupAccessControl{
|
||||
Members: membersAccess,
|
||||
Attributes: attributesAccess,
|
||||
AddFromInviteLink: addFromInviteLinkAccess,
|
||||
},
|
||||
InviteLinkPassword: &inviteLinkPassword,
|
||||
BannedMembers: bannedMembers,
|
||||
AnnouncementsOnly: announcementsOnly,
|
||||
}, avatarBytes)
|
||||
if err != nil {
|
||||
ce.Reply("Failed to create group: %v", err)
|
||||
return
|
||||
}
|
||||
gid := group.GroupIdentifier
|
||||
ce.ZLog.UpdateContext(func(c zerolog.Context) zerolog.Context {
|
||||
return c.Stringer("group_id", gid)
|
||||
})
|
||||
portal := ce.User.GetPortalByChatID(gid.String())
|
||||
portal.roomCreateLock.Lock()
|
||||
defer portal.roomCreateLock.Unlock()
|
||||
if len(portal.MXID) != 0 {
|
||||
ce.ZLog.Warn().Msg("Detected race condition in room creation")
|
||||
// TODO race condition, clean up the old room
|
||||
}
|
||||
portal.MXID = ce.RoomID
|
||||
portal.Name = roomName
|
||||
portal.Encrypted = encryptionEvent.Algorithm == id.AlgorithmMegolmV1
|
||||
if !portal.Encrypted && ce.Bridge.Config.Bridge.Encryption.Default {
|
||||
_, err = portal.MainIntent().SendStateEvent(ce.Ctx, portal.MXID, event.StateEncryption, "", portal.GetEncryptionEventContent())
|
||||
if err != nil {
|
||||
ce.ZLog.Err(err).Msg("Failed to enable encryption in room")
|
||||
if errors.Is(err, mautrix.MForbidden) {
|
||||
ce.Reply("I don't seem to have permission to enable encryption in this room.")
|
||||
} else {
|
||||
ce.Reply("Failed to enable encryption in room: %v", err)
|
||||
}
|
||||
}
|
||||
portal.Encrypted = true
|
||||
}
|
||||
revision, err := ce.User.Client.UpdateGroup(ce.Ctx, &signalmeow.GroupChange{}, gid)
|
||||
if err != nil {
|
||||
ce.Reply("Failed to update Group")
|
||||
return
|
||||
}
|
||||
portal.Revision = revision
|
||||
portal.AvatarHash = avatarHash
|
||||
portal.AvatarURL = avatarURL
|
||||
portal.AvatarPath = group.AvatarPath
|
||||
portal.AvatarSet = true
|
||||
err = portal.Update(ce.Ctx)
|
||||
if err != nil {
|
||||
ce.ZLog.Err(err).Msg("Failed to save portal after creating group")
|
||||
}
|
||||
portal.UpdateBridgeInfo(ce.Ctx)
|
||||
ce.Reply("Successfully created Signal group %s", gid.String())
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue