mirror of
https://github.com/AsamK/signal-cli.git
synced 2026-05-25 14:24:36 +00:00
Compare commits
6 Commits
16c7283466
...
c43e7f4d3b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c43e7f4d3b | ||
|
|
daef4d1cf8 | ||
|
|
b498d2050a | ||
|
|
27a722dc75 | ||
|
|
7014f629fe | ||
|
|
30b57bdb3d |
48
.github/workflows/build.yml
vendored
Normal file
48
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
name: build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
|
pull_request:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- name: Build
|
||||||
|
run: ./reproducible-builds/build.sh
|
||||||
|
- name: Upload build artifacts
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: signal-cli-build
|
||||||
|
path: dist/*
|
||||||
|
|
||||||
|
build-client:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- ubuntu
|
||||||
|
- macos
|
||||||
|
- windows
|
||||||
|
runs-on: ${{ matrix.os }}-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: ./client
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- name: Install rust
|
||||||
|
run: rustup default stable
|
||||||
|
- name: Build client
|
||||||
|
run: cargo build --release --verbose
|
||||||
|
- name: Archive production artifacts
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: signal-cli-client-${{ matrix.os }}
|
||||||
|
path: |
|
||||||
|
client/target/release/signal-cli-client
|
||||||
|
client/target/release/signal-cli-client.exe
|
||||||
96
.github/workflows/ci.yml
vendored
96
.github/workflows/ci.yml
vendored
@ -1,96 +0,0 @@
|
|||||||
name: signal-cli CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- '**'
|
|
||||||
pull_request:
|
|
||||||
workflow_call:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write # to fetch code (actions/checkout) and submit dependency graph (gradle/gradle-build-action)
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
java: [ '25' ]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- name: Set up JDK
|
|
||||||
uses: actions/setup-java@v5
|
|
||||||
with:
|
|
||||||
distribution: 'zulu'
|
|
||||||
java-version: ${{ matrix.java }}
|
|
||||||
- name: Setup Gradle
|
|
||||||
uses: gradle/actions/setup-gradle@v5
|
|
||||||
with:
|
|
||||||
dependency-graph: generate-and-submit
|
|
||||||
- name: Install asciidoc
|
|
||||||
run: sudo apt update && sudo apt --no-install-recommends install -y asciidoc-base
|
|
||||||
- name: Build with Gradle
|
|
||||||
run: ./gradlew --no-daemon build
|
|
||||||
- name: Build man page
|
|
||||||
run: |
|
|
||||||
cd man
|
|
||||||
make install
|
|
||||||
- name: Add man page to archive
|
|
||||||
run: |
|
|
||||||
version=$(tar tf build/distributions/signal-cli-*.tar | head -n1 | sed 's|signal-cli-\([^/]*\)/.*|\1|')
|
|
||||||
echo $version
|
|
||||||
tar --transform="flags=r;s|man|signal-cli-${version}/man|" -rf build/distributions/signal-cli-${version}.tar man/man{1,5}
|
|
||||||
- name: Compress archive
|
|
||||||
run: gzip -n -9 build/distributions/signal-cli-*.tar
|
|
||||||
- name: Archive production artifacts
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: signal-cli-archive-${{ matrix.java }}
|
|
||||||
path: build/distributions/signal-cli-*.tar.gz
|
|
||||||
|
|
||||||
build-graalvm:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- uses: graalvm/setup-graalvm@v1
|
|
||||||
with:
|
|
||||||
distribution: 'graalvm'
|
|
||||||
java-version: '25'
|
|
||||||
cache: 'gradle'
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Build with Gradle
|
|
||||||
run: ./gradlew --no-daemon nativeCompile
|
|
||||||
- name: Archive production artifacts
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: signal-cli-native
|
|
||||||
path: build/native/nativeCompile/signal-cli
|
|
||||||
|
|
||||||
build-client:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os:
|
|
||||||
- ubuntu
|
|
||||||
- macos
|
|
||||||
- windows
|
|
||||||
runs-on: ${{ matrix.os }}-latest
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: ./client
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
- name: Install rust
|
|
||||||
run: rustup default stable
|
|
||||||
- name: Build client
|
|
||||||
run: cargo build --release --verbose
|
|
||||||
- name: Archive production artifacts
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: signal-cli-client-${{ matrix.os }}
|
|
||||||
path: |
|
|
||||||
client/target/release/signal-cli-client
|
|
||||||
client/target/release/signal-cli-client.exe
|
|
||||||
194
.github/workflows/release.yml
vendored
194
.github/workflows/release.yml
vendored
@ -5,8 +5,7 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- v*
|
- v*
|
||||||
|
|
||||||
permissions:
|
permissions: {}
|
||||||
contents: write # to fetch code (actions/checkout) and create release
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
IMAGE_NAME: signal-cli
|
IMAGE_NAME: signal-cli
|
||||||
@ -15,96 +14,25 @@ env:
|
|||||||
REGISTRY_PASSWORD: ${{ github.token }}
|
REGISTRY_PASSWORD: ${{ github.token }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
build:
|
||||||
|
uses: AsamK/signal-cli/.github/workflows/build.yml@master
|
||||||
|
|
||||||
ci_wf:
|
release:
|
||||||
permissions:
|
needs: build
|
||||||
contents: write
|
|
||||||
uses: AsamK/signal-cli/.github/workflows/ci.yml@master
|
|
||||||
# ${{ github.repository }} not accepted here
|
|
||||||
|
|
||||||
lib_to_jar:
|
|
||||||
needs: ci_wf
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
outputs:
|
outputs:
|
||||||
signal_cli_version: ${{ steps.cli_ver.outputs.version }}
|
version: ${{ steps.version.outputs.version }}
|
||||||
release_id: ${{ steps.create_release.outputs.id }}
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Download signal-cli build from CI workflow
|
- name: Download signal-cli build from CI workflow
|
||||||
uses: actions/download-artifact@v8
|
uses: actions/download-artifact@v8
|
||||||
|
|
||||||
- name: Get signal-cli version
|
- name: Get signal-cli version
|
||||||
id: cli_ver
|
id: version
|
||||||
run: |
|
run: |
|
||||||
ver="${GITHUB_REF_NAME#v}"
|
mv ./signal-cli-build/* .
|
||||||
echo "version=${ver}" >> $GITHUB_OUTPUT
|
echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Extract archive
|
|
||||||
run: |
|
|
||||||
tree .
|
|
||||||
ARCHIVE_DIR=$(ls signal-cli-archive-*/ -d | tail -n1)
|
|
||||||
tar -xzf ./"${ARCHIVE_DIR}"/*.tar.gz
|
|
||||||
mv ./"${ARCHIVE_DIR}"/*.tar.gz signal-cli-${{ steps.cli_ver.outputs.version }}.tar.gz
|
|
||||||
rm -rf signal-cli-archive-*/
|
|
||||||
|
|
||||||
# - name: Get signal-client jar version
|
|
||||||
# id: lib_ver
|
|
||||||
# run: |
|
|
||||||
# JAR_PREFIX=libsignal-client-
|
|
||||||
# jar_file=$(find ./signal-cli-*/lib/ -name "$JAR_PREFIX*.jar")
|
|
||||||
# jar_version=$(echo "$jar_file" | xargs basename | sed "s/$JAR_PREFIX//; s/.jar//")
|
|
||||||
# echo "$jar_version"
|
|
||||||
# echo "signal_client_version=${jar_version}" >> $GITHUB_OUTPUT
|
|
||||||
#
|
|
||||||
# - name: Download signal-client builds
|
|
||||||
# env:
|
|
||||||
# RELEASES_URL: https://github.com/signalapp/libsignal/releases/download/
|
|
||||||
# FILE_NAMES: signal_jni.dll libsignal_jni.dylib
|
|
||||||
# SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }}
|
|
||||||
# run: |
|
|
||||||
# for file_name in $FILE_NAMES; do
|
|
||||||
# curl -sOL "${RELEASES_URL}/v${SIGNAL_CLIENT_VER}/${file_name}" # note: added v
|
|
||||||
# done
|
|
||||||
# tree .
|
|
||||||
|
|
||||||
- name: Compress native app
|
|
||||||
env:
|
|
||||||
SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.version }}
|
|
||||||
run: |
|
|
||||||
chmod +x signal-cli-native/signal-cli
|
|
||||||
tar -czf signal-cli-${SIGNAL_CLI_VER}-Linux-native.tar.gz -C signal-cli-native signal-cli
|
|
||||||
rm -rf signal-cli-native/
|
|
||||||
|
|
||||||
- name: Compress client app
|
|
||||||
env:
|
|
||||||
SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.version }}
|
|
||||||
run: |
|
|
||||||
chmod +x signal-cli-client-ubuntu/signal-cli-client
|
|
||||||
tar -czf signal-cli-${SIGNAL_CLI_VER}-Linux-client.tar.gz -C signal-cli-client-ubuntu signal-cli-client
|
|
||||||
rm -rf signal-cli-client-ubuntu/
|
|
||||||
|
|
||||||
# - name: Replace Windows lib
|
|
||||||
# env:
|
|
||||||
# SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.version }}
|
|
||||||
# SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }}
|
|
||||||
# run: |
|
|
||||||
# mv signal_jni.dll libsignal_jni.so
|
|
||||||
# zip -u ./signal-cli-*/lib/libsignal-client-${SIGNAL_CLIENT_VER}.jar ./libsignal_jni.so
|
|
||||||
# tar -czf signal-cli-${SIGNAL_CLI_VER}-Windows.tar.gz signal-cli-*/
|
|
||||||
#
|
|
||||||
# - name: Replace macOS lib
|
|
||||||
# env:
|
|
||||||
# SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.version }}
|
|
||||||
# SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }}
|
|
||||||
# run: |
|
|
||||||
# jar_file=./signal-cli-*/lib/libsignal-client-${SIGNAL_CLIENT_VER}.jar
|
|
||||||
# zip -d $jar_file libsignal_jni.so
|
|
||||||
# zip $jar_file libsignal_jni.dylib
|
|
||||||
# tar -czf signal-cli-${SIGNAL_CLI_VER}-macOS.tar.gz signal-cli-*/
|
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
id: create_release
|
id: create_release
|
||||||
@ -112,8 +40,8 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
tag_name: v${{ steps.cli_ver.outputs.version }} # note: added `v`
|
tag_name: v${{ steps.version.outputs.version }} # note: added `v`
|
||||||
release_name: v${{ steps.cli_ver.outputs.version }} # note: added `v`
|
release_name: v${{ steps.version.outputs.version }} # note: added `v`
|
||||||
draft: true
|
draft: true
|
||||||
|
|
||||||
- name: Upload archive
|
- name: Upload archive
|
||||||
@ -122,28 +50,18 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}.tar.gz
|
asset_path: signal-cli-${{ steps.version.outputs.version }}.tar.gz
|
||||||
asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}.tar.gz
|
asset_name: signal-cli-${{ steps.version.outputs.version }}.tar.gz
|
||||||
asset_content_type: application/x-compressed-tar # .tar.gz
|
asset_content_type: application/x-compressed-tar # .tar.gz
|
||||||
|
|
||||||
# - name: Upload Linux archive
|
|
||||||
# uses: actions/upload-release-asset@v1
|
|
||||||
# env:
|
|
||||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
# with:
|
|
||||||
# upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
# asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux.tar.gz
|
|
||||||
# asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux.tar.gz
|
|
||||||
# asset_content_type: application/x-compressed-tar # .tar.gz
|
|
||||||
|
|
||||||
- name: Upload Linux native archive
|
- name: Upload Linux native archive
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux-native.tar.gz
|
asset_path: signal-cli-${{ steps.version.outputs.version }}-Linux-native.tar.gz
|
||||||
asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux-native.tar.gz
|
asset_name: signal-cli-${{ steps.version.outputs.version }}-Linux-native.tar.gz
|
||||||
asset_content_type: application/x-compressed-tar # .tar.gz
|
asset_content_type: application/x-compressed-tar # .tar.gz
|
||||||
|
|
||||||
- name: Upload Linux client archive
|
- name: Upload Linux client archive
|
||||||
@ -152,35 +70,14 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux-client.tar.gz
|
asset_path: signal-cli-${{ steps.version.outputs.version }}-Linux-client.tar.gz
|
||||||
asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux-client.tar.gz
|
asset_name: signal-cli-${{ steps.version.outputs.version }}-Linux-client.tar.gz
|
||||||
asset_content_type: application/x-compressed-tar # .tar.gz
|
asset_content_type: application/x-compressed-tar # .tar.gz
|
||||||
|
|
||||||
# - name: Upload windows archive
|
|
||||||
# uses: actions/upload-release-asset@v1
|
|
||||||
# env:
|
|
||||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
# with:
|
|
||||||
# upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
# asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-Windows.tar.gz
|
|
||||||
# asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-Windows.tar.gz
|
|
||||||
# asset_content_type: application/x-compressed-tar # .tar.gz
|
|
||||||
#
|
|
||||||
# - name: Upload macos archive
|
|
||||||
# uses: actions/upload-release-asset@v1
|
|
||||||
# env:
|
|
||||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
# with:
|
|
||||||
# upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
# asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-macOS.tar.gz
|
|
||||||
# asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-macOS.tar.gz
|
|
||||||
# asset_content_type: application/x-compressed-tar # .tar.gz
|
|
||||||
|
|
||||||
build-container:
|
build-container:
|
||||||
needs: ci_wf
|
needs: release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
|
||||||
packages: write
|
packages: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -188,28 +85,19 @@ jobs:
|
|||||||
- name: Download signal-cli build from CI workflow
|
- name: Download signal-cli build from CI workflow
|
||||||
uses: actions/download-artifact@v8
|
uses: actions/download-artifact@v8
|
||||||
|
|
||||||
- name: Get signal-cli version
|
|
||||||
id: cli_ver
|
|
||||||
run: |
|
|
||||||
ver="${GITHUB_REF_NAME#v}"
|
|
||||||
echo "version=${ver}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Move archive file
|
- name: Move archive file
|
||||||
run: |
|
run: |
|
||||||
ARCHIVE_DIR=$(ls signal-cli-archive-*/ -d | tail -n1)
|
tar xf ./signal-cli-build/signal-cli-${{ needs.release.outputs.version }}.tar.gz
|
||||||
tar xf ./"${ARCHIVE_DIR}"/*.tar.gz
|
|
||||||
rm -r signal-cli-archive-* signal-cli-native
|
|
||||||
mkdir -p build/install/
|
mkdir -p build/install/
|
||||||
mv ./signal-cli-"${GITHUB_REF_NAME#v}"/ build/install/signal-cli
|
mv ./signal-cli-"${{ needs.release.outputs.version }}"/ build/install/signal-cli
|
||||||
|
|
||||||
- name: Build Image
|
- name: Build Image
|
||||||
id: build_image
|
id: build_image
|
||||||
uses: redhat-actions/buildah-build@v2
|
uses: redhat-actions/buildah-build@v2
|
||||||
with:
|
with:
|
||||||
image: ${{ env.IMAGE_NAME }}
|
image: ${{ env.IMAGE_NAME }}
|
||||||
tags: latest ${{ github.sha }} ${{ steps.cli_ver.outputs.version }}
|
tags: latest ${{ github.sha }} ${{ needs.release.outputs.version }}
|
||||||
containerfiles:
|
containerfiles: ./Containerfile
|
||||||
./Containerfile
|
|
||||||
oci: true
|
oci: true
|
||||||
|
|
||||||
- name: Push To GHCR
|
- name: Push To GHCR
|
||||||
@ -227,10 +115,9 @@ jobs:
|
|||||||
echo "${{ toJSON(steps.push.outputs) }}"
|
echo "${{ toJSON(steps.push.outputs) }}"
|
||||||
|
|
||||||
build-container-native:
|
build-container-native:
|
||||||
needs: ci_wf
|
needs: release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
|
||||||
packages: write
|
packages: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -238,26 +125,20 @@ jobs:
|
|||||||
- name: Download signal-cli build from CI workflow
|
- name: Download signal-cli build from CI workflow
|
||||||
uses: actions/download-artifact@v8
|
uses: actions/download-artifact@v8
|
||||||
|
|
||||||
- name: Get signal-cli version
|
|
||||||
id: cli_ver
|
|
||||||
run: |
|
|
||||||
ver="${GITHUB_REF_NAME#v}"
|
|
||||||
echo "version=${ver}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Move archive file
|
- name: Move archive file
|
||||||
run: |
|
run: |
|
||||||
|
tar xf ./signal-cli-build/signal-cli-${{ needs.release.outputs.version }}-Linux-native.tar.gz
|
||||||
mkdir -p build/native/nativeCompile/
|
mkdir -p build/native/nativeCompile/
|
||||||
chmod +x ./signal-cli-native/signal-cli
|
mv signal-cli build/native/nativeCompile/
|
||||||
mv ./signal-cli-native/signal-cli build/native/nativeCompile/
|
chmod +x build/native/nativeCompile/signal-cli
|
||||||
|
|
||||||
- name: Build Image
|
- name: Build Image
|
||||||
id: build_image
|
id: build_image
|
||||||
uses: redhat-actions/buildah-build@v2
|
uses: redhat-actions/buildah-build@v2
|
||||||
with:
|
with:
|
||||||
image: ${{ env.IMAGE_NAME }}
|
image: ${{ env.IMAGE_NAME }}
|
||||||
tags: latest-native ${{ github.sha }}-native ${{ steps.cli_ver.outputs.version }}-native
|
tags: latest-native ${{ github.sha }}-native ${{ needs.release.outputs.version }}-native
|
||||||
containerfiles:
|
containerfiles: ./native.Containerfile
|
||||||
./native.Containerfile
|
|
||||||
oci: true
|
oci: true
|
||||||
|
|
||||||
- name: Push To GHCR
|
- name: Push To GHCR
|
||||||
@ -275,10 +156,9 @@ jobs:
|
|||||||
echo "${{ toJSON(steps.push.outputs) }}"
|
echo "${{ toJSON(steps.push.outputs) }}"
|
||||||
|
|
||||||
build-container-client:
|
build-container-client:
|
||||||
needs: ci_wf
|
needs: release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
|
||||||
packages: write
|
packages: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@ -286,26 +166,20 @@ jobs:
|
|||||||
- name: Download signal-cli build from CI workflow
|
- name: Download signal-cli build from CI workflow
|
||||||
uses: actions/download-artifact@v8
|
uses: actions/download-artifact@v8
|
||||||
|
|
||||||
- name: Get signal-cli version
|
|
||||||
id: cli_ver
|
|
||||||
run: |
|
|
||||||
ver="${GITHUB_REF_NAME#v}"
|
|
||||||
echo "version=${ver}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Move archive file
|
- name: Move archive file
|
||||||
run: |
|
run: |
|
||||||
|
tar xf ./signal-cli-build/signal-cli-${{ needs.release.outputs.version }}-Linux-client.tar.gz
|
||||||
mkdir -p client/target/release/
|
mkdir -p client/target/release/
|
||||||
chmod +x ./signal-cli-client-ubuntu/signal-cli-client
|
mv signal-cli-client client/target/release/
|
||||||
mv ./signal-cli-client-ubuntu/signal-cli-client client/target/release/
|
chmod +x client/target/release/signal-cli-client
|
||||||
|
|
||||||
- name: Build Image
|
- name: Build Image
|
||||||
id: build_image
|
id: build_image
|
||||||
uses: redhat-actions/buildah-build@v2
|
uses: redhat-actions/buildah-build@v2
|
||||||
with:
|
with:
|
||||||
image: ${{ env.IMAGE_NAME }}
|
image: ${{ env.IMAGE_NAME }}
|
||||||
tags: latest-client ${{ github.sha }}-client ${{ steps.cli_ver.outputs.version }}-client
|
tags: latest-client ${{ github.sha }}-client ${{ needs.release.outputs.version }}-client
|
||||||
containerfiles:
|
containerfiles: ./client.Containerfile
|
||||||
./client.Containerfile
|
|
||||||
oci: true
|
oci: true
|
||||||
|
|
||||||
- name: Push To GHCR
|
- name: Push To GHCR
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,4 +1,5 @@
|
|||||||
.gradle/
|
.gradle/
|
||||||
|
.kotlin/
|
||||||
.idea/*
|
.idea/*
|
||||||
!.idea/codeStyles/
|
!.idea/codeStyles/
|
||||||
build/
|
build/
|
||||||
@ -13,3 +14,9 @@ out/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
/bin/
|
/bin/
|
||||||
/test-config/
|
/test-config/
|
||||||
|
/dist/
|
||||||
|
/github/
|
||||||
|
man/*.1
|
||||||
|
man/*.5
|
||||||
|
man/man1
|
||||||
|
man/man5
|
||||||
|
|||||||
11
CHANGELOG.md
11
CHANGELOG.md
@ -1,6 +1,15 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [Unreleased]
|
## [0.14.1] - 2026-03-08
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added isArchived to contact json output (Thanks @moppman)
|
||||||
|
- Added support for group member labels
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Adapt registration to signal server changes
|
||||||
|
|
||||||
## [0.14.0] - 2026-03-01
|
## [0.14.0] - 2026-03-01
|
||||||
|
|
||||||
|
|||||||
@ -3,12 +3,12 @@ plugins {
|
|||||||
application
|
application
|
||||||
eclipse
|
eclipse
|
||||||
`check-lib-versions`
|
`check-lib-versions`
|
||||||
id("org.graalvm.buildtools.native") version "0.11.4"
|
id("org.graalvm.buildtools.native") version "0.11.5"
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = "org.asamk"
|
group = "org.asamk"
|
||||||
version = "0.14.1-SNAPSHOT"
|
version = "0.14.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
|
|||||||
@ -598,6 +598,12 @@ pub enum CliCommands {
|
|||||||
|
|
||||||
#[arg(short = 'e', long)]
|
#[arg(short = 'e', long)]
|
||||||
expiration: Option<u32>,
|
expiration: Option<u32>,
|
||||||
|
|
||||||
|
#[arg(long = "member-label-emoji")]
|
||||||
|
member_label_emoji: Option<String>,
|
||||||
|
|
||||||
|
#[arg(long = "member-label")]
|
||||||
|
member_label: Option<String>,
|
||||||
},
|
},
|
||||||
UpdateProfile {
|
UpdateProfile {
|
||||||
#[arg(long = "given-name")]
|
#[arg(long = "given-name")]
|
||||||
|
|||||||
@ -460,6 +460,8 @@ pub trait Rpc {
|
|||||||
#[allow(non_snake_case)] setPermissionEditDetails: Option<String>,
|
#[allow(non_snake_case)] setPermissionEditDetails: Option<String>,
|
||||||
#[allow(non_snake_case)] setPermissionSendMessages: Option<String>,
|
#[allow(non_snake_case)] setPermissionSendMessages: Option<String>,
|
||||||
expiration: Option<u32>,
|
expiration: Option<u32>,
|
||||||
|
#[allow(non_snake_case)] memberLabelEmoji: Option<String>,
|
||||||
|
#[allow(non_snake_case)] memberLabel: Option<String>,
|
||||||
) -> Result<Value, ErrorObjectOwned>;
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
#[method(name = "updateProfile", param_kind = map)]
|
#[method(name = "updateProfile", param_kind = map)]
|
||||||
|
|||||||
@ -535,6 +535,8 @@ async fn handle_command(
|
|||||||
set_permission_edit_details,
|
set_permission_edit_details,
|
||||||
set_permission_send_messages,
|
set_permission_send_messages,
|
||||||
expiration,
|
expiration,
|
||||||
|
member_label_emoji,
|
||||||
|
member_label,
|
||||||
} => {
|
} => {
|
||||||
client
|
client
|
||||||
.update_group(
|
.update_group(
|
||||||
@ -568,6 +570,8 @@ async fn handle_command(
|
|||||||
GroupPermission::OnlyAdmins => "onlyAdmins".to_owned(),
|
GroupPermission::OnlyAdmins => "onlyAdmins".to_owned(),
|
||||||
}),
|
}),
|
||||||
expiration,
|
expiration,
|
||||||
|
member_label_emoji,
|
||||||
|
member_label,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,6 +45,9 @@
|
|||||||
<content_attribute id="social-chat">intense</content_attribute>
|
<content_attribute id="social-chat">intense</content_attribute>
|
||||||
</content_rating>
|
</content_rating>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="0.14.1" date="2026-03-08">
|
||||||
|
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.14.1</url>
|
||||||
|
</release>
|
||||||
<release version="0.14.0" date="2026-03-01">
|
<release version="0.14.0" date="2026-03-01">
|
||||||
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.14.0</url>
|
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.14.0</url>
|
||||||
</release>
|
</release>
|
||||||
|
|||||||
@ -12,10 +12,9 @@ public record Group(
|
|||||||
String title,
|
String title,
|
||||||
String description,
|
String description,
|
||||||
GroupInviteLinkUrl groupInviteLinkUrl,
|
GroupInviteLinkUrl groupInviteLinkUrl,
|
||||||
Set<RecipientAddress> members,
|
Set<GroupMember> members,
|
||||||
Set<RecipientAddress> pendingMembers,
|
Set<RecipientAddress> pendingMembers,
|
||||||
Set<RecipientAddress> requestingMembers,
|
Set<RecipientAddress> requestingMembers,
|
||||||
Set<RecipientAddress> adminMembers,
|
|
||||||
Set<RecipientAddress> bannedMembers,
|
Set<RecipientAddress> bannedMembers,
|
||||||
boolean isBlocked,
|
boolean isBlocked,
|
||||||
int messageExpirationTimer,
|
int messageExpirationTimer,
|
||||||
@ -37,8 +36,7 @@ public record Group(
|
|||||||
groupInfo.getGroupInviteLink(),
|
groupInfo.getGroupInviteLink(),
|
||||||
groupInfo.getMembers()
|
groupInfo.getMembers()
|
||||||
.stream()
|
.stream()
|
||||||
.map(recipientStore::resolveRecipientAddress)
|
.map(m -> org.asamk.signal.manager.api.GroupMember.from(m, recipientStore))
|
||||||
.map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress)
|
|
||||||
.collect(Collectors.toSet()),
|
.collect(Collectors.toSet()),
|
||||||
groupInfo.getPendingMembers()
|
groupInfo.getPendingMembers()
|
||||||
.stream()
|
.stream()
|
||||||
@ -50,11 +48,6 @@ public record Group(
|
|||||||
.map(recipientStore::resolveRecipientAddress)
|
.map(recipientStore::resolveRecipientAddress)
|
||||||
.map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress)
|
.map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress)
|
||||||
.collect(Collectors.toSet()),
|
.collect(Collectors.toSet()),
|
||||||
groupInfo.getAdminMembers()
|
|
||||||
.stream()
|
|
||||||
.map(recipientStore::resolveRecipientAddress)
|
|
||||||
.map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress)
|
|
||||||
.collect(Collectors.toSet()),
|
|
||||||
groupInfo.getBannedMembers()
|
groupInfo.getBannedMembers()
|
||||||
.stream()
|
.stream()
|
||||||
.map(recipientStore::resolveRecipientAddress)
|
.map(recipientStore::resolveRecipientAddress)
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.helper.RecipientAddressResolver;
|
||||||
|
import org.asamk.signal.manager.storage.groups.GroupMemberInfo;
|
||||||
|
|
||||||
|
public record GroupMember(
|
||||||
|
RecipientAddress recipientAddress, boolean isAdmin, String labelEmoji, String label
|
||||||
|
) {
|
||||||
|
|
||||||
|
public static GroupMember from(final GroupMemberInfo memberInfo, final RecipientAddressResolver recipientStore) {
|
||||||
|
return new GroupMember(recipientStore.resolveRecipientAddress(memberInfo.getRecipientId())
|
||||||
|
.toApiRecipientAddress(), memberInfo.isAdmin(), memberInfo.labelEmoji(), memberInfo.labelString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -19,6 +19,8 @@ public class UpdateGroup {
|
|||||||
private final String avatarFile;
|
private final String avatarFile;
|
||||||
private final Integer expirationTimer;
|
private final Integer expirationTimer;
|
||||||
private final Boolean isAnnouncementGroup;
|
private final Boolean isAnnouncementGroup;
|
||||||
|
private final String labelEmoji;
|
||||||
|
private final String labelString;
|
||||||
|
|
||||||
private UpdateGroup(final Builder builder) {
|
private UpdateGroup(final Builder builder) {
|
||||||
name = builder.name;
|
name = builder.name;
|
||||||
@ -36,6 +38,8 @@ public class UpdateGroup {
|
|||||||
avatarFile = builder.avatarFile;
|
avatarFile = builder.avatarFile;
|
||||||
expirationTimer = builder.expirationTimer;
|
expirationTimer = builder.expirationTimer;
|
||||||
isAnnouncementGroup = builder.isAnnouncementGroup;
|
isAnnouncementGroup = builder.isAnnouncementGroup;
|
||||||
|
labelEmoji = builder.labelEmoji;
|
||||||
|
labelString = builder.labelString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder newBuilder() {
|
public static Builder newBuilder() {
|
||||||
@ -57,7 +61,9 @@ public class UpdateGroup {
|
|||||||
copy.editDetailsPermission,
|
copy.editDetailsPermission,
|
||||||
copy.avatarFile,
|
copy.avatarFile,
|
||||||
copy.expirationTimer,
|
copy.expirationTimer,
|
||||||
copy.isAnnouncementGroup);
|
copy.isAnnouncementGroup,
|
||||||
|
copy.labelEmoji,
|
||||||
|
copy.labelString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder newBuilder(
|
public static Builder newBuilder(
|
||||||
@ -75,7 +81,9 @@ public class UpdateGroup {
|
|||||||
final GroupPermission editDetailsPermission,
|
final GroupPermission editDetailsPermission,
|
||||||
final String avatarFile,
|
final String avatarFile,
|
||||||
final Integer expirationTimer,
|
final Integer expirationTimer,
|
||||||
final Boolean isAnnouncementGroup
|
final Boolean isAnnouncementGroup,
|
||||||
|
final String labelEmoji,
|
||||||
|
final String labelString
|
||||||
) {
|
) {
|
||||||
return new Builder(name,
|
return new Builder(name,
|
||||||
description,
|
description,
|
||||||
@ -91,7 +99,9 @@ public class UpdateGroup {
|
|||||||
editDetailsPermission,
|
editDetailsPermission,
|
||||||
avatarFile,
|
avatarFile,
|
||||||
expirationTimer,
|
expirationTimer,
|
||||||
isAnnouncementGroup);
|
isAnnouncementGroup,
|
||||||
|
labelEmoji,
|
||||||
|
labelString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@ -154,6 +164,14 @@ public class UpdateGroup {
|
|||||||
return isAnnouncementGroup;
|
return isAnnouncementGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getLabelEmoji() {
|
||||||
|
return labelEmoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabelString() {
|
||||||
|
return labelString;
|
||||||
|
}
|
||||||
|
|
||||||
public static final class Builder {
|
public static final class Builder {
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
@ -171,6 +189,8 @@ public class UpdateGroup {
|
|||||||
private String avatarFile;
|
private String avatarFile;
|
||||||
private Integer expirationTimer;
|
private Integer expirationTimer;
|
||||||
private Boolean isAnnouncementGroup;
|
private Boolean isAnnouncementGroup;
|
||||||
|
private String labelEmoji;
|
||||||
|
private String labelString;
|
||||||
|
|
||||||
private Builder() {
|
private Builder() {
|
||||||
}
|
}
|
||||||
@ -190,7 +210,9 @@ public class UpdateGroup {
|
|||||||
final GroupPermission editDetailsPermission,
|
final GroupPermission editDetailsPermission,
|
||||||
final String avatarFile,
|
final String avatarFile,
|
||||||
final Integer expirationTimer,
|
final Integer expirationTimer,
|
||||||
final Boolean isAnnouncementGroup
|
final Boolean isAnnouncementGroup,
|
||||||
|
final String labelEmoji,
|
||||||
|
final String labelString
|
||||||
) {
|
) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
@ -207,6 +229,8 @@ public class UpdateGroup {
|
|||||||
this.avatarFile = avatarFile;
|
this.avatarFile = avatarFile;
|
||||||
this.expirationTimer = expirationTimer;
|
this.expirationTimer = expirationTimer;
|
||||||
this.isAnnouncementGroup = isAnnouncementGroup;
|
this.isAnnouncementGroup = isAnnouncementGroup;
|
||||||
|
this.labelEmoji = labelEmoji;
|
||||||
|
this.labelString = labelString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder withName(final String val) {
|
public Builder withName(final String val) {
|
||||||
@ -284,6 +308,16 @@ public class UpdateGroup {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder withLabelEmoji(final String val) {
|
||||||
|
labelEmoji = val;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withLabelString(final String val) {
|
||||||
|
labelString = val;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public UpdateGroup build() {
|
public UpdateGroup build() {
|
||||||
return new UpdateGroup(this);
|
return new UpdateGroup(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -299,7 +299,9 @@ public class GroupHelper {
|
|||||||
final GroupPermission editDetailsPermission,
|
final GroupPermission editDetailsPermission,
|
||||||
final String avatarFile,
|
final String avatarFile,
|
||||||
final Integer expirationTimer,
|
final Integer expirationTimer,
|
||||||
final Boolean isAnnouncementGroup
|
final Boolean isAnnouncementGroup,
|
||||||
|
final String labelEmoji,
|
||||||
|
final String labelString
|
||||||
) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException {
|
) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException {
|
||||||
var group = getGroupForUpdating(groupId);
|
var group = getGroupForUpdating(groupId);
|
||||||
final var avatarBytes = readAvatarBytes(avatarFile);
|
final var avatarBytes = readAvatarBytes(avatarFile);
|
||||||
@ -323,7 +325,9 @@ public class GroupHelper {
|
|||||||
editDetailsPermission,
|
editDetailsPermission,
|
||||||
avatarBytes,
|
avatarBytes,
|
||||||
expirationTimer,
|
expirationTimer,
|
||||||
isAnnouncementGroup);
|
isAnnouncementGroup,
|
||||||
|
labelEmoji,
|
||||||
|
labelString);
|
||||||
} catch (ConflictException e) {
|
} catch (ConflictException e) {
|
||||||
// Detected conflicting update, refreshing group and trying again
|
// Detected conflicting update, refreshing group and trying again
|
||||||
group = getGroup(groupId, true);
|
group = getGroup(groupId, true);
|
||||||
@ -342,7 +346,9 @@ public class GroupHelper {
|
|||||||
editDetailsPermission,
|
editDetailsPermission,
|
||||||
avatarBytes,
|
avatarBytes,
|
||||||
expirationTimer,
|
expirationTimer,
|
||||||
isAnnouncementGroup);
|
isAnnouncementGroup,
|
||||||
|
labelEmoji,
|
||||||
|
labelString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -701,7 +707,9 @@ public class GroupHelper {
|
|||||||
final GroupPermission editDetailsPermission,
|
final GroupPermission editDetailsPermission,
|
||||||
final byte[] avatarFile,
|
final byte[] avatarFile,
|
||||||
final Integer expirationTimer,
|
final Integer expirationTimer,
|
||||||
final Boolean isAnnouncementGroup
|
final Boolean isAnnouncementGroup,
|
||||||
|
final String labelEmoji,
|
||||||
|
final String labelString
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
SendGroupMessageResults result = null;
|
SendGroupMessageResults result = null;
|
||||||
final var groupV2Helper = context.getGroupV2Helper();
|
final var groupV2Helper = context.getGroupV2Helper();
|
||||||
@ -758,7 +766,7 @@ public class GroupHelper {
|
|||||||
if (admins != null) {
|
if (admins != null) {
|
||||||
final var newAdmins = new HashSet<>(admins);
|
final var newAdmins = new HashSet<>(admins);
|
||||||
newAdmins.retainAll(group.getMembers());
|
newAdmins.retainAll(group.getMembers());
|
||||||
newAdmins.removeAll(group.getAdminMembers());
|
newAdmins.removeAll(group.getAdminMemberRecipientIds());
|
||||||
if (!newAdmins.isEmpty()) {
|
if (!newAdmins.isEmpty()) {
|
||||||
for (var admin : newAdmins) {
|
for (var admin : newAdmins) {
|
||||||
var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, true);
|
var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, true);
|
||||||
@ -771,7 +779,7 @@ public class GroupHelper {
|
|||||||
|
|
||||||
if (removeAdmins != null) {
|
if (removeAdmins != null) {
|
||||||
final var existingRemoveAdmins = new HashSet<>(removeAdmins);
|
final var existingRemoveAdmins = new HashSet<>(removeAdmins);
|
||||||
existingRemoveAdmins.retainAll(group.getAdminMembers());
|
existingRemoveAdmins.retainAll(group.getAdminMemberRecipientIds());
|
||||||
if (!existingRemoveAdmins.isEmpty()) {
|
if (!existingRemoveAdmins.isEmpty()) {
|
||||||
for (var admin : existingRemoveAdmins) {
|
for (var admin : existingRemoveAdmins) {
|
||||||
var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, false);
|
var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, false);
|
||||||
@ -830,6 +838,15 @@ public class GroupHelper {
|
|||||||
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
|
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (labelString != null || labelEmoji != null) {
|
||||||
|
final var selfRecipientId = account.getSelfRecipientId();
|
||||||
|
final var selfMember = group.getMember(selfRecipientId);
|
||||||
|
var groupGroupChangePair = groupV2Helper.setMemberLabels(group,
|
||||||
|
labelEmoji != null ? labelEmoji : selfMember.labelEmoji(),
|
||||||
|
labelString != null ? labelString : selfMember.labelString());
|
||||||
|
result = sendUpdateGroupV2Message(group, groupGroupChangePair.first(), groupGroupChangePair.second());
|
||||||
|
}
|
||||||
|
|
||||||
if (name != null || description != null || avatarFile != null) {
|
if (name != null || description != null || avatarFile != null) {
|
||||||
var groupGroupChangePair = groupV2Helper.updateGroup(group, name, description, avatarFile);
|
var groupGroupChangePair = groupV2Helper.updateGroup(group, name, description, avatarFile);
|
||||||
if (avatarFile != null) {
|
if (avatarFile != null) {
|
||||||
@ -859,7 +876,7 @@ public class GroupHelper {
|
|||||||
final GroupInfoV2 groupInfoV2,
|
final GroupInfoV2 groupInfoV2,
|
||||||
final Set<RecipientId> newAdmins
|
final Set<RecipientId> newAdmins
|
||||||
) throws LastGroupAdminException, IOException {
|
) throws LastGroupAdminException, IOException {
|
||||||
final var currentAdmins = groupInfoV2.getAdminMembers();
|
final var currentAdmins = groupInfoV2.getAdminMemberRecipientIds();
|
||||||
newAdmins.removeAll(currentAdmins);
|
newAdmins.removeAll(currentAdmins);
|
||||||
newAdmins.retainAll(groupInfoV2.getMembers());
|
newAdmins.retainAll(groupInfoV2.getMembers());
|
||||||
if (currentAdmins.contains(account.getSelfRecipientId())
|
if (currentAdmins.contains(account.getSelfRecipientId())
|
||||||
@ -888,7 +905,7 @@ public class GroupHelper {
|
|||||||
var group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.UPDATE)
|
var group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.UPDATE)
|
||||||
.withId(g.getGroupId().serialize())
|
.withId(g.getGroupId().serialize())
|
||||||
.withName(g.name)
|
.withName(g.name)
|
||||||
.withMembers(g.getMembers()
|
.withMembers(g.getMemberRecipientIds()
|
||||||
.stream()
|
.stream()
|
||||||
.map(context.getRecipientHelper()::resolveSignalServiceAddress)
|
.map(context.getRecipientHelper()::resolveSignalServiceAddress)
|
||||||
.toList());
|
.toList());
|
||||||
|
|||||||
@ -533,6 +533,18 @@ class GroupV2Helper {
|
|||||||
return commitChange(groupInfoV2, change);
|
return commitChange(groupInfoV2, change);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Pair<DecryptedGroup, GroupChangeResponse> setMemberLabels(
|
||||||
|
GroupInfoV2 groupInfoV2,
|
||||||
|
String labelEmoji,
|
||||||
|
String labelString
|
||||||
|
) throws IOException {
|
||||||
|
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
|
||||||
|
final var change = groupOperations.createChangeMemberLabel(getSelfAci(),
|
||||||
|
labelString == null ? "" : labelString,
|
||||||
|
labelEmoji);
|
||||||
|
return commitChange(groupInfoV2, change);
|
||||||
|
}
|
||||||
|
|
||||||
private AccessControl.AccessRequired toAccessControl(final GroupLinkState state) {
|
private AccessControl.AccessRequired toAccessControl(final GroupLinkState state) {
|
||||||
return switch (state) {
|
return switch (state) {
|
||||||
case DISABLED -> AccessControl.AccessRequired.UNSATISFIABLE;
|
case DISABLED -> AccessControl.AccessRequired.UNSATISFIABLE;
|
||||||
|
|||||||
@ -320,7 +320,9 @@ public class SendHelper {
|
|||||||
messageBuilder.withExpiration(g.getMessageExpirationTimer());
|
messageBuilder.withExpiration(g.getMessageExpirationTimer());
|
||||||
|
|
||||||
final var message = messageBuilder.build();
|
final var message = messageBuilder.build();
|
||||||
final var recipients = includeSelf ? g.getMembers() : g.getMembersWithout(account.getSelfRecipientId());
|
final var recipients = includeSelf
|
||||||
|
? g.getMemberRecipientIds()
|
||||||
|
: g.getMembersWithout(account.getSelfRecipientId());
|
||||||
|
|
||||||
if (g.isAnnouncementGroup() && !g.isAdmin(account.getSelfRecipientId())) {
|
if (g.isAnnouncementGroup() && !g.isAdmin(account.getSelfRecipientId())) {
|
||||||
if (message.getBody().isPresent()
|
if (message.getBody().isPresent()
|
||||||
|
|||||||
@ -114,7 +114,7 @@ public class SyncHelper {
|
|||||||
if (record instanceof GroupInfoV1 groupInfo) {
|
if (record instanceof GroupInfoV1 groupInfo) {
|
||||||
out.write(new DeviceGroup(groupInfo.getGroupId().serialize(),
|
out.write(new DeviceGroup(groupInfo.getGroupId().serialize(),
|
||||||
Optional.ofNullable(groupInfo.name),
|
Optional.ofNullable(groupInfo.name),
|
||||||
groupInfo.getMembers()
|
groupInfo.getMemberRecipientIds()
|
||||||
.stream()
|
.stream()
|
||||||
.map(context.getRecipientHelper()::resolveSignalServiceAddress)
|
.map(context.getRecipientHelper()::resolveSignalServiceAddress)
|
||||||
.toList(),
|
.toList(),
|
||||||
|
|||||||
@ -633,7 +633,9 @@ public class ManagerImpl implements Manager {
|
|||||||
updateGroup.getEditDetailsPermission(),
|
updateGroup.getEditDetailsPermission(),
|
||||||
updateGroup.getAvatarFile(),
|
updateGroup.getAvatarFile(),
|
||||||
updateGroup.getExpirationTimer(),
|
updateGroup.getExpirationTimer(),
|
||||||
updateGroup.getIsAnnouncementGroup());
|
updateGroup.getIsAnnouncementGroup(),
|
||||||
|
updateGroup.getLabelEmoji(),
|
||||||
|
updateGroup.getLabelString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import org.asamk.signal.manager.api.GroupPermission;
|
|||||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
import org.whispersystems.signalservice.api.push.DistributionId;
|
import org.whispersystems.signalservice.api.push.DistributionId;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@ -24,7 +25,18 @@ public sealed abstract class GroupInfo permits GroupInfoV1, GroupInfoV2 {
|
|||||||
|
|
||||||
public abstract GroupInviteLinkUrl getGroupInviteLink();
|
public abstract GroupInviteLinkUrl getGroupInviteLink();
|
||||||
|
|
||||||
public abstract Set<RecipientId> getMembers();
|
public abstract Collection<GroupMemberInfo> getMembers();
|
||||||
|
|
||||||
|
public Set<RecipientId> getMemberRecipientIds() {
|
||||||
|
return getMembers().stream().map(GroupMemberInfo::getRecipientId).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public GroupMemberInfo getMember(RecipientId recipientId) {
|
||||||
|
return getMembers().stream()
|
||||||
|
.filter(member -> member.getRecipientId().equals(recipientId))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
public Set<RecipientId> getBannedMembers() {
|
public Set<RecipientId> getBannedMembers() {
|
||||||
return Set.of();
|
return Set.of();
|
||||||
@ -38,7 +50,7 @@ public sealed abstract class GroupInfo permits GroupInfoV1, GroupInfoV2 {
|
|||||||
return Set.of();
|
return Set.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<RecipientId> getAdminMembers() {
|
public Set<RecipientId> getAdminMemberRecipientIds() {
|
||||||
return Set.of();
|
return Set.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,21 +73,23 @@ public sealed abstract class GroupInfo permits GroupInfoV1, GroupInfoV2 {
|
|||||||
public abstract GroupPermission getPermissionSendMessage();
|
public abstract GroupPermission getPermissionSendMessage();
|
||||||
|
|
||||||
public Set<RecipientId> getMembersWithout(RecipientId recipientId) {
|
public Set<RecipientId> getMembersWithout(RecipientId recipientId) {
|
||||||
return getMembers().stream().filter(member -> !member.equals(recipientId)).collect(Collectors.toSet());
|
return getMemberRecipientIds().stream()
|
||||||
|
.filter(member -> !member.equals(recipientId))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<RecipientId> getMembersIncludingPendingWithout(RecipientId recipientId) {
|
public Set<RecipientId> getMembersIncludingPendingWithout(RecipientId recipientId) {
|
||||||
return Stream.concat(getMembers().stream(), getPendingMembers().stream())
|
return Stream.concat(getMemberRecipientIds().stream(), getPendingMembers().stream())
|
||||||
.filter(member -> !member.equals(recipientId))
|
.filter(member -> !member.equals(recipientId))
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMember(RecipientId recipientId) {
|
public boolean isMember(RecipientId recipientId) {
|
||||||
return getMembers().contains(recipientId);
|
return getMembers().stream().anyMatch(m -> m.getRecipientId().equals(recipientId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAdmin(RecipientId recipientId) {
|
public boolean isAdmin(RecipientId recipientId) {
|
||||||
return getAdminMembers().contains(recipientId);
|
return getMembers().stream().anyMatch(m -> m.isAdmin() && m.getRecipientId().equals(recipientId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPendingMember(RecipientId recipientId) {
|
public boolean isPendingMember(RecipientId recipientId) {
|
||||||
|
|||||||
@ -80,8 +80,8 @@ public final class GroupInfoV1 extends GroupInfo {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<RecipientId> getMembers() {
|
public Collection<GroupMemberInfo> getMembers() {
|
||||||
return new HashSet<>(members);
|
return members.stream().map(m -> (GroupMemberInfo) new GroupMemberInfoV1(m)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -3,18 +3,17 @@ package org.asamk.signal.manager.storage.groups;
|
|||||||
import org.asamk.signal.manager.api.GroupIdV2;
|
import org.asamk.signal.manager.api.GroupIdV2;
|
||||||
import org.asamk.signal.manager.api.GroupInviteLinkUrl;
|
import org.asamk.signal.manager.api.GroupInviteLinkUrl;
|
||||||
import org.asamk.signal.manager.api.GroupPermission;
|
import org.asamk.signal.manager.api.GroupPermission;
|
||||||
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
|
|
||||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
||||||
import org.signal.core.models.ServiceId;
|
import org.signal.core.models.ServiceId;
|
||||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
|
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
|
||||||
import org.signal.storageservice.storage.protos.groups.AccessControl;
|
import org.signal.storageservice.storage.protos.groups.AccessControl;
|
||||||
import org.signal.storageservice.storage.protos.groups.Member;
|
|
||||||
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
|
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
|
||||||
import org.signal.storageservice.storage.protos.groups.local.EnabledState;
|
import org.signal.storageservice.storage.protos.groups.local.EnabledState;
|
||||||
import org.whispersystems.signalservice.api.push.DistributionId;
|
import org.whispersystems.signalservice.api.push.DistributionId;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -122,14 +121,11 @@ public final class GroupInfoV2 extends GroupInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<RecipientId> getMembers() {
|
public Collection<GroupMemberInfo> getMembers() {
|
||||||
if (this.group == null) {
|
if (this.group == null) {
|
||||||
return Set.of();
|
return Set.of();
|
||||||
}
|
}
|
||||||
return group.members.stream()
|
return group.members.stream().map(m -> (GroupMemberInfo) new GroupMemberInfoV2(m, recipientResolver)).toList();
|
||||||
.map(m -> ServiceId.parseOrThrow(m.aciBytes))
|
|
||||||
.map(recipientResolver::resolveRecipient)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -175,16 +171,11 @@ public final class GroupInfoV2 extends GroupInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<RecipientId> getAdminMembers() {
|
public Set<RecipientId> getAdminMemberRecipientIds() {
|
||||||
if (this.group == null) {
|
return this.getMembers()
|
||||||
return Set.of();
|
.stream()
|
||||||
}
|
.filter(GroupMemberInfo::isAdmin)
|
||||||
return group.members.stream()
|
.map(GroupMemberInfo::getRecipientId)
|
||||||
.filter(m -> m.role == Member.Role.ADMINISTRATOR)
|
|
||||||
.map(m -> new RecipientAddress(ServiceId.ACI.parseOrNull(m.aciBytes),
|
|
||||||
ServiceId.PNI.parseOrNull(m.pniBytes),
|
|
||||||
null))
|
|
||||||
.map(recipientResolver::resolveRecipient)
|
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,20 @@
|
|||||||
|
package org.asamk.signal.manager.storage.groups;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
|
|
||||||
|
public interface GroupMemberInfo {
|
||||||
|
|
||||||
|
RecipientId getRecipientId();
|
||||||
|
|
||||||
|
default boolean isAdmin() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default String labelEmoji() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
default String labelString() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package org.asamk.signal.manager.storage.groups;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
|
|
||||||
|
public class GroupMemberInfoV1 implements GroupMemberInfo {
|
||||||
|
|
||||||
|
private final RecipientId recipientId;
|
||||||
|
|
||||||
|
public GroupMemberInfoV1(final RecipientId recipientId) {
|
||||||
|
this.recipientId = recipientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecipientId getRecipientId() {
|
||||||
|
return this.recipientId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package org.asamk.signal.manager.storage.groups;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
|
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
||||||
|
import org.signal.core.models.ServiceId;
|
||||||
|
import org.signal.storageservice.storage.protos.groups.Member;
|
||||||
|
import org.signal.storageservice.storage.protos.groups.local.DecryptedMember;
|
||||||
|
|
||||||
|
public class GroupMemberInfoV2 implements GroupMemberInfo {
|
||||||
|
|
||||||
|
private final RecipientResolver recipientResolver;
|
||||||
|
private final DecryptedMember member;
|
||||||
|
|
||||||
|
public GroupMemberInfoV2(final DecryptedMember member, final RecipientResolver recipientResolver) {
|
||||||
|
this.recipientResolver = recipientResolver;
|
||||||
|
this.member = member;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RecipientId getRecipientId() {
|
||||||
|
return recipientResolver.resolveRecipient(ServiceId.ACI.parseOrThrow(member.aciBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAdmin() {
|
||||||
|
return member.role == Member.Role.ADMINISTRATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String labelEmoji() {
|
||||||
|
return member.labelEmoji.isEmpty() ? null : member.labelEmoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String labelString() {
|
||||||
|
return member.labelString.isEmpty() ? null : member.labelString;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -648,7 +648,7 @@ public class GroupStore {
|
|||||||
ON CONFLICT (group_id, recipient_id) DO NOTHING
|
ON CONFLICT (group_id, recipient_id) DO NOTHING
|
||||||
""".formatted(TABLE_GROUP_V1_MEMBER);
|
""".formatted(TABLE_GROUP_V1_MEMBER);
|
||||||
try (final var statement = connection.prepareStatement(sqlInsertMember)) {
|
try (final var statement = connection.prepareStatement(sqlInsertMember)) {
|
||||||
for (final var recipient : groupV1.getMembers()) {
|
for (final var recipient : groupV1.getMemberRecipientIds()) {
|
||||||
statement.setLong(1, internalId);
|
statement.setLong(1, internalId);
|
||||||
statement.setLong(2, recipient.id());
|
statement.setLong(2, recipient.id());
|
||||||
statement.executeUpdate();
|
statement.executeUpdate();
|
||||||
|
|||||||
@ -133,6 +133,11 @@ public class KyberPreKeyStore implements SignalServiceKyberPreKeyStore {
|
|||||||
return getPreKey(keyId) != null;
|
return getPreKey(keyId) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When we mark Kyber pre-keys used, we want to keep a record of last resort tuples, which are deleted when the key
|
||||||
|
* itself is deleted from this table via a cascading delete.
|
||||||
|
* For non-last-resort keys, this method just deletes them like normal.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void markKyberPreKeyUsed(
|
public void markKyberPreKeyUsed(
|
||||||
final int kyberPreKeyId,
|
final int kyberPreKeyId,
|
||||||
|
|||||||
@ -14,8 +14,8 @@ all: $(MANPAGESRC)
|
|||||||
.PHONY: install
|
.PHONY: install
|
||||||
install: all
|
install: all
|
||||||
$(MKDIR) -p man1 man5
|
$(MKDIR) -p man1 man5
|
||||||
for f in *.1; do $(GZIP) < "$$f" > man1/"$$f".gz ; done
|
for f in *.1; do $(GZIP) -n < "$$f" > man1/"$$f".gz ; done
|
||||||
for f in *.5; do $(GZIP) < "$$f" > man5/"$$f".gz ; done
|
for f in *.5; do $(GZIP) -n < "$$f" > man5/"$$f".gz ; done
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
|
|||||||
@ -154,7 +154,7 @@ RESPONSE: `{"jsonrpc":"2.0","result":{"timestamp":999},"id":4}`
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
REQUEST: `{"jsonrpc":"2.0","method":"updateGroup","params":{"groupId":"GROUP_ID=","name":"new group name","members":["+ZZZ"],"link":"enabledWithApproval","setPermissionEditDetails":"only-admins"},"id":"someId"}`
|
REQUEST: `{"jsonrpc":"2.0","method":"updateGroup","params":{"groupId":"GROUP_ID=","name":"new group name","members":["+ZZZ"],"link":"enabledWithApproval","setPermissionEditDetails":"only-admins","memberLabelEmoji":"😀","memberLabel":"My Label"},"id":"someId"}`
|
||||||
|
|
||||||
RESPONSE: `{"jsonrpc":"2.0","result":{"timestamp":9999},"id":"someId"}`
|
RESPONSE: `{"jsonrpc":"2.0","result":{"timestamp":9999},"id":"someId"}`
|
||||||
|
|
||||||
|
|||||||
@ -754,6 +754,12 @@ Groups where only admins can send messages are also called announcement groups
|
|||||||
Set expiration time of messages (seconds).
|
Set expiration time of messages (seconds).
|
||||||
To disable expiration set expiration time to 0.
|
To disable expiration set expiration time to 0.
|
||||||
|
|
||||||
|
*--member-label-emoji* EMOJI::
|
||||||
|
Specify the emoji for the member label.
|
||||||
|
|
||||||
|
*--member-label* STRING::
|
||||||
|
Specify the string for the member label.
|
||||||
|
|
||||||
=== quitGroup
|
=== quitGroup
|
||||||
|
|
||||||
Send a quit group message to all group members and remove self from member list.
|
Send a quit group message to all group members and remove self from member list.
|
||||||
|
|||||||
34
reproducible-builds/README.md
Normal file
34
reproducible-builds/README.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Reproducible builds
|
||||||
|
|
||||||
|
This process lets you verify that the version of the app that was downloaded from the Githud Releases matches the source code in our public repository.
|
||||||
|
|
||||||
|
This is achieved by replicating the build environment as Docker images.
|
||||||
|
|
||||||
|
Currently, only the following binaries are reproducible:
|
||||||
|
|
||||||
|
- [x] JAR package (`signal-cli-XXX.tar.gz`)
|
||||||
|
- [ ] Native binary (`signal-cli-XXX-Linux-native.tar.gz`)
|
||||||
|
- [x] Rust client binary (`signal-cli-XXX-Linux-client.tar.gz`)
|
||||||
|
|
||||||
|
In the following section, we will use Signal version 0.14.0 as the reference example. Simply replace all occurrences of 0.14.0 with the version number you are about to verify.
|
||||||
|
|
||||||
|
## Step-by-step instructions
|
||||||
|
|
||||||
|
### 0. Prerequisites
|
||||||
|
|
||||||
|
Before you begin, ensure you have the following installed:
|
||||||
|
|
||||||
|
- git
|
||||||
|
- docker (or podman)
|
||||||
|
|
||||||
|
### 1. Verifying reproducibility
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone --depth 1 --branch v0.14.0 https://github.com/AsamK/signal-cli
|
||||||
|
cd ./signal-cli
|
||||||
|
./reproducible-builds/verify.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
If each one ends with `... matches!` for every binary (except the native one for now), you're good to go! You've successfully verified that the Github Release binaries were built from exactly the same code as is in the signal-cli git repository.
|
||||||
|
|
||||||
|
If you get `... doesn't match!`, it means something went wrong (except for the native one for now). Please [open an issue](https://github.com/AsamK/signal-cli/issues/new/choose).
|
||||||
11
reproducible-builds/build.Containerfile
Normal file
11
reproducible-builds/build.Containerfile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM docker.io/azul/zulu-openjdk:25.0.2-jdk@sha256:0349494e05c22fe439e65be99771581b2bc428d89f07363b539389a11103fa5f
|
||||||
|
ENV SOURCE_DATE_EPOCH=1767225600
|
||||||
|
ENV LANG=C.UTF-8
|
||||||
|
ENV LC_CTYPE=en_US.UTF-8
|
||||||
|
ARG SNAPSHOT=20260101T000000Z
|
||||||
|
RUN echo "deb http://snapshot.ubuntu.com/ubuntu/${SNAPSHOT}/ jammy main" > /etc/apt/sources.list \
|
||||||
|
&& echo "deb http://snapshot.ubuntu.com/ubuntu/${SNAPSHOT}/ jammy universe" >> /etc/apt/sources.list
|
||||||
|
RUN apt update && apt install -y make asciidoc-base
|
||||||
|
COPY --chmod=0700 reproducible-builds/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
|
WORKDIR /signal-cli
|
||||||
|
ENTRYPOINT [ "/usr/local/bin/entrypoint.sh", "build" ]
|
||||||
45
reproducible-builds/build.sh
Executable file
45
reproducible-builds/build.sh
Executable file
@ -0,0 +1,45 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../"
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
rm -rf "$ROOT_DIR/dist"
|
||||||
|
mkdir -p "$ROOT_DIR/dist"
|
||||||
|
|
||||||
|
if command -v podman >/dev/null; then
|
||||||
|
ENGINE=podman
|
||||||
|
USER=
|
||||||
|
else
|
||||||
|
ENGINE=docker
|
||||||
|
USER="--user $(id -u):$(id -g)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION=$(sed -n 's/\s*version\s*=\s*"\(.*\)".*/\1/p' build.gradle.kts | tail -n1)
|
||||||
|
echo "$VERSION" >dist/VERSION
|
||||||
|
|
||||||
|
$ENGINE build -t signal-cli:build -f reproducible-builds/build.Containerfile .
|
||||||
|
$ENGINE build -t signal-cli:native -f reproducible-builds/native.Containerfile .
|
||||||
|
$ENGINE build -t signal-cli:client -f reproducible-builds/client.Containerfile .
|
||||||
|
|
||||||
|
# Build jar
|
||||||
|
git clean -Xfd -e '!/dist/' -e '!/dist/**' -e '!/github/' -e '!/github/**'
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
$ENGINE run --pull=never --rm -v "$(pwd)":/signal-cli:Z -e VERSION="$VERSION" $USER signal-cli:build
|
||||||
|
mv build/distributions/signal-cli-*.tar.gz dist/
|
||||||
|
|
||||||
|
# Build native-image
|
||||||
|
git clean -Xfd -e '!/dist/' -e '!/dist/**' -e '!/github/' -e '!/github/**'
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
$ENGINE run --pull=never --rm -v "$(pwd)":/signal-cli:Z -e VERSION="$VERSION" $USER signal-cli:native
|
||||||
|
mv build/signal-cli-*-Linux-native.tar.gz dist/
|
||||||
|
|
||||||
|
# Build rust client
|
||||||
|
git clean -Xfd -e '!/dist/' -e '!/dist/**' -e '!/github/' -e '!/github/**'
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
$ENGINE run --pull=never --rm -v "$(pwd)":/signal-cli:Z -e VERSION="$VERSION" $USER signal-cli:client
|
||||||
|
mv build/signal-cli-*-Linux-client.tar.gz dist/
|
||||||
|
|
||||||
|
ls -lsh dist/
|
||||||
|
|
||||||
|
echo -e "\e[32mBuild successful!\e[0m"
|
||||||
7
reproducible-builds/client.Containerfile
Normal file
7
reproducible-builds/client.Containerfile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
FROM docker.io/rust:1.93.1-slim-trixie@sha256:7f2c9f2f0dad8f4afa6faf5efa971e7e566398a36e54fb7684061407ea067058
|
||||||
|
ENV SOURCE_DATE_EPOCH=1767225600
|
||||||
|
ENV LANG=C.UTF-8
|
||||||
|
ENV LC_CTYPE=en_US.UTF-8
|
||||||
|
COPY --chmod=0700 reproducible-builds/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
|
WORKDIR /signal-cli
|
||||||
|
ENTRYPOINT [ "/usr/local/bin/entrypoint.sh", "client" ]
|
||||||
68
reproducible-builds/entrypoint.sh
Normal file
68
reproducible-builds/entrypoint.sh
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
echo "Build '$1' variant $VERSION ..."
|
||||||
|
|
||||||
|
function reset_file_dates() {
|
||||||
|
find . -exec touch -m -d "@$SOURCE_DATE_EPOCH" {} \;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_file_dates
|
||||||
|
|
||||||
|
if [ "$1" == "build" ]; then
|
||||||
|
|
||||||
|
./gradlew build \
|
||||||
|
--no-daemon \
|
||||||
|
--max-workers=1 \
|
||||||
|
-Dkotlin.compiler.execution.strategy=in-process \
|
||||||
|
--no-build-cache \
|
||||||
|
-Dorg.gradle.caching=false \
|
||||||
|
-Porg.gradle.java.installations.auto-download=false \
|
||||||
|
-Porg.gradle.java.installations.auto-detect=false
|
||||||
|
cd man
|
||||||
|
make install
|
||||||
|
cd ..
|
||||||
|
reset_file_dates
|
||||||
|
tar tf build/distributions/signal-cli-*.tar | head -n1 | sed 's|signal-cli-\([^/]*\)/.*|\1|'
|
||||||
|
tar --mtime="@$SOURCE_DATE_EPOCH" --transform="flags=r;s|man|signal-cli-${VERSION}/man|" -rf "build/distributions/signal-cli-${VERSION}.tar" man/man{1,5}
|
||||||
|
gzip -n -9 build/distributions/signal-cli-*.tar
|
||||||
|
|
||||||
|
elif [ "$1" == "native" ]; then
|
||||||
|
|
||||||
|
./gradlew nativeCompile \
|
||||||
|
--no-daemon \
|
||||||
|
--max-workers=1 \
|
||||||
|
-Dkotlin.compiler.execution.strategy=in-process \
|
||||||
|
--no-build-cache \
|
||||||
|
-Dorg.gradle.caching=false \
|
||||||
|
-Dgraalvm.native-image.build-time=2026-01-01T00:00:00Z \
|
||||||
|
-Porg.gradle.java.installations.auto-download=false \
|
||||||
|
-Porg.gradle.java.installations.auto-detect=false
|
||||||
|
|
||||||
|
strip --strip-all \
|
||||||
|
--remove-section=.note.gnu.build-id \
|
||||||
|
--remove-section=.comment \
|
||||||
|
--remove-section=.gnu_debuglink \
|
||||||
|
--remove-section=.annobin.notes \
|
||||||
|
--remove-section=.gnu.build.attributes \
|
||||||
|
--remove-section=.note.ABI-tag \
|
||||||
|
build/native/nativeCompile/signal-cli
|
||||||
|
|
||||||
|
chmod +x build/native/nativeCompile/signal-cli
|
||||||
|
reset_file_dates
|
||||||
|
tar --mtime="@$SOURCE_DATE_EPOCH" -czf "build/signal-cli-${VERSION}-Linux-native.tar.gz" -C build/native/nativeCompile signal-cli
|
||||||
|
|
||||||
|
elif [ "$1" == "client" ]; then
|
||||||
|
|
||||||
|
cd client
|
||||||
|
cargo build --release --locked
|
||||||
|
cd ..
|
||||||
|
chmod +x client/target/release/signal-cli-client
|
||||||
|
mkdir -p build
|
||||||
|
tar --mtime="@$SOURCE_DATE_EPOCH" -czf "build/signal-cli-${VERSION}-Linux-client.tar.gz" -C client/target/release signal-cli-client
|
||||||
|
|
||||||
|
else
|
||||||
|
echo "Unknown build variant '$1'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
7
reproducible-builds/native.Containerfile
Normal file
7
reproducible-builds/native.Containerfile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
FROM container-registry.oracle.com/graalvm/native-image:25.0.2@sha256:e8c5ec4f256bf958f327aea060e1424aa87f63114aeb4a4318a0ac169bbdb9a1
|
||||||
|
ENV SOURCE_DATE_EPOCH=1767225600
|
||||||
|
ENV LANG=C.UTF-8
|
||||||
|
ENV LC_CTYPE=en_US.UTF-8
|
||||||
|
COPY --chmod=0700 reproducible-builds/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
|
WORKDIR /signal-cli
|
||||||
|
ENTRYPOINT [ "/usr/local/bin/entrypoint.sh", "native" ]
|
||||||
41
reproducible-builds/verify.sh
Executable file
41
reproducible-builds/verify.sh
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../"
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
rm -rf "$ROOT_DIR/github"
|
||||||
|
mkdir -p "$ROOT_DIR/github"
|
||||||
|
|
||||||
|
VERSION=$(sed -n 's/\s*version\s*=\s*"\(.*\)".*/\1/p' build.gradle.kts | tail -n1)
|
||||||
|
|
||||||
|
echo "Download latest release from GitHub..."
|
||||||
|
|
||||||
|
curl -L --fail "https://github.com/AsamK/signal-cli/releases/download/v${VERSION}/signal-cli-${VERSION}.tar.gz" -o "github/signal-cli-${VERSION}.tar.gz"
|
||||||
|
curl -L --fail "https://github.com/AsamK/signal-cli/releases/download/v${VERSION}/signal-cli-${VERSION}-Linux-native.tar.gz" -o "github/signal-cli-${VERSION}-Linux-native.tar.gz"
|
||||||
|
curl -L --fail "https://github.com/AsamK/signal-cli/releases/download/v${VERSION}/signal-cli-${VERSION}-Linux-client.tar.gz" -o "github/signal-cli-${VERSION}-Linux-client.tar.gz"
|
||||||
|
|
||||||
|
./reproducible-builds/build.sh
|
||||||
|
|
||||||
|
rm -f {github,dist}/VERSION
|
||||||
|
|
||||||
|
echo "commit: $(git rev-parse HEAD)"
|
||||||
|
|
||||||
|
echo "sha256 hashes of GitHub release:"
|
||||||
|
sha256sum github/*
|
||||||
|
echo "sha256 hashes of locally built files:"
|
||||||
|
sha256sum dist/*
|
||||||
|
|
||||||
|
reproducible=true
|
||||||
|
for file in $(cd github && find . -type f); do
|
||||||
|
if diff "github/$file" "dist/$file" >/dev/null 2>&1; then
|
||||||
|
echo -e "\e[32m[+] '$(basename "$file")' matches!\e[0m"
|
||||||
|
else
|
||||||
|
echo -e "\e[31m[-] '$(basename "$file")' doesn't match!\e[0m"
|
||||||
|
reproducible=false
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$reproducible" = false ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@ -1,5 +1,7 @@
|
|||||||
package org.asamk.signal.commands;
|
package org.asamk.signal.commands;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
|
||||||
import net.sourceforge.argparse4j.impl.Arguments;
|
import net.sourceforge.argparse4j.impl.Arguments;
|
||||||
import net.sourceforge.argparse4j.inf.Namespace;
|
import net.sourceforge.argparse4j.inf.Namespace;
|
||||||
import net.sourceforge.argparse4j.inf.Subparser;
|
import net.sourceforge.argparse4j.inf.Subparser;
|
||||||
@ -7,6 +9,7 @@ import net.sourceforge.argparse4j.inf.Subparser;
|
|||||||
import org.asamk.signal.commands.exceptions.CommandException;
|
import org.asamk.signal.commands.exceptions.CommandException;
|
||||||
import org.asamk.signal.manager.Manager;
|
import org.asamk.signal.manager.Manager;
|
||||||
import org.asamk.signal.manager.api.Group;
|
import org.asamk.signal.manager.api.Group;
|
||||||
|
import org.asamk.signal.manager.api.GroupMember;
|
||||||
import org.asamk.signal.manager.api.RecipientAddress;
|
import org.asamk.signal.manager.api.RecipientAddress;
|
||||||
import org.asamk.signal.output.JsonWriter;
|
import org.asamk.signal.output.JsonWriter;
|
||||||
import org.asamk.signal.output.OutputWriter;
|
import org.asamk.signal.output.OutputWriter;
|
||||||
@ -37,33 +40,55 @@ public class ListGroupsCommand implements JsonRpcLocalCommand {
|
|||||||
subparser.addArgument("-g", "--group-id").help("Specify one or more group IDs to show.").nargs("*");
|
subparser.addArgument("-g", "--group-id").help("Specify one or more group IDs to show.").nargs("*");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<String> resolveMembers(Set<RecipientAddress> addresses) {
|
private static Set<String> resolveMembers(Set<GroupMember> addresses) {
|
||||||
|
return addresses.stream()
|
||||||
|
.map(m -> m.recipientAddress().getLegacyIdentifier() + (m.isAdmin() ? "{ADMIN}" : "") + (
|
||||||
|
m.labelEmoji() != null || m.label() != null ? "(" + (
|
||||||
|
m.labelEmoji() != null ? m.labelEmoji() : ""
|
||||||
|
) + (
|
||||||
|
m.label() != null ? m.label() : ""
|
||||||
|
) + ")" : ""
|
||||||
|
))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<String> resolveMemberAddress(Set<RecipientAddress> addresses) {
|
||||||
return addresses.stream().map(RecipientAddress::getLegacyIdentifier).collect(Collectors.toSet());
|
return addresses.stream().map(RecipientAddress::getLegacyIdentifier).collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<JsonGroupMember> resolveJsonMembers(Set<RecipientAddress> addresses) {
|
private static Set<JsonGroupMemberAddress> resolveJsonMembers(Set<RecipientAddress> addresses) {
|
||||||
return addresses.stream()
|
return addresses.stream()
|
||||||
.map(address -> new JsonGroupMember(address.number().orElse(null),
|
.map(address -> new JsonGroupMemberAddress(address.number().orElse(null),
|
||||||
address.uuid().map(UUID::toString).orElse(null)))
|
address.uuid().map(UUID::toString).orElse(null)))
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Set<JsonGroupMember> resolveFullJsonMembers(Set<GroupMember> addresses) {
|
||||||
|
return addresses.stream().map(member -> {
|
||||||
|
final var address = member.recipientAddress();
|
||||||
|
return new JsonGroupMember(address.number().orElse(null),
|
||||||
|
address.uuid().map(UUID::toString).orElse(null),
|
||||||
|
member.isAdmin(),
|
||||||
|
member.labelEmoji(),
|
||||||
|
member.label());
|
||||||
|
}).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
private static void printGroupPlainText(PlainTextWriter writer, Group group, boolean detailed) {
|
private static void printGroupPlainText(PlainTextWriter writer, Group group, boolean detailed) {
|
||||||
if (detailed) {
|
if (detailed) {
|
||||||
final var groupInviteLink = group.groupInviteLinkUrl();
|
final var groupInviteLink = group.groupInviteLinkUrl();
|
||||||
|
|
||||||
writer.println(
|
writer.println(
|
||||||
"Id: {} Name: {} Description: {} Active: {} Blocked: {} Members: {} Pending members: {} Requesting members: {} Admins: {} Banned: {} Message expiration: {} Link: {}",
|
"Id: {} Name: {} Description: {} Active: {} Blocked: {} Members: {} Pending members: {} Requesting members: {} Banned: {} Message expiration: {} Link: {}",
|
||||||
group.groupId().toBase64(),
|
group.groupId().toBase64(),
|
||||||
group.title(),
|
group.title(),
|
||||||
group.description(),
|
group.description(),
|
||||||
group.isMember(),
|
group.isMember(),
|
||||||
group.isBlocked(),
|
group.isBlocked(),
|
||||||
resolveMembers(group.members()),
|
resolveMembers(group.members()),
|
||||||
resolveMembers(group.pendingMembers()),
|
resolveMemberAddress(group.pendingMembers()),
|
||||||
resolveMembers(group.requestingMembers()),
|
resolveMemberAddress(group.requestingMembers()),
|
||||||
resolveMembers(group.adminMembers()),
|
resolveMemberAddress(group.bannedMembers()),
|
||||||
resolveMembers(group.bannedMembers()),
|
|
||||||
group.messageExpirationTimer() == 0 ? "disabled" : group.messageExpirationTimer() + "s",
|
group.messageExpirationTimer() == 0 ? "disabled" : group.messageExpirationTimer() + "s",
|
||||||
groupInviteLink == null ? '-' : groupInviteLink.getUrl());
|
groupInviteLink == null ? '-' : groupInviteLink.getUrl());
|
||||||
} else {
|
} else {
|
||||||
@ -96,10 +121,14 @@ public class ListGroupsCommand implements JsonRpcLocalCommand {
|
|||||||
group.isMember(),
|
group.isMember(),
|
||||||
group.isBlocked(),
|
group.isBlocked(),
|
||||||
group.messageExpirationTimer(),
|
group.messageExpirationTimer(),
|
||||||
resolveJsonMembers(group.members()),
|
resolveFullJsonMembers(group.members()),
|
||||||
resolveJsonMembers(group.pendingMembers()),
|
resolveJsonMembers(group.pendingMembers()),
|
||||||
resolveJsonMembers(group.requestingMembers()),
|
resolveJsonMembers(group.requestingMembers()),
|
||||||
resolveJsonMembers(group.adminMembers()),
|
resolveJsonMembers(group.members()
|
||||||
|
.stream()
|
||||||
|
.filter(GroupMember::isAdmin)
|
||||||
|
.map(GroupMember::recipientAddress)
|
||||||
|
.collect(Collectors.toSet())),
|
||||||
resolveJsonMembers(group.bannedMembers()),
|
resolveJsonMembers(group.bannedMembers()),
|
||||||
group.permissionAddMember().name(),
|
group.permissionAddMember().name(),
|
||||||
group.permissionEditDetails().name(),
|
group.permissionEditDetails().name(),
|
||||||
@ -125,15 +154,23 @@ public class ListGroupsCommand implements JsonRpcLocalCommand {
|
|||||||
boolean isBlocked,
|
boolean isBlocked,
|
||||||
int messageExpirationTime,
|
int messageExpirationTime,
|
||||||
Set<JsonGroupMember> members,
|
Set<JsonGroupMember> members,
|
||||||
Set<JsonGroupMember> pendingMembers,
|
Set<JsonGroupMemberAddress> pendingMembers,
|
||||||
Set<JsonGroupMember> requestingMembers,
|
Set<JsonGroupMemberAddress> requestingMembers,
|
||||||
Set<JsonGroupMember> admins,
|
@Deprecated Set<JsonGroupMemberAddress> admins,
|
||||||
Set<JsonGroupMember> banned,
|
Set<JsonGroupMemberAddress> banned,
|
||||||
String permissionAddMember,
|
String permissionAddMember,
|
||||||
String permissionEditDetails,
|
String permissionEditDetails,
|
||||||
String permissionSendMessage,
|
String permissionSendMessage,
|
||||||
String groupInviteLink
|
String groupInviteLink
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private record JsonGroupMember(String number, String uuid) {}
|
private record JsonGroupMemberAddress(String number, String uuid) {}
|
||||||
|
|
||||||
|
private record JsonGroupMember(
|
||||||
|
String number,
|
||||||
|
String uuid,
|
||||||
|
boolean isAdmin,
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL) String labelEmoji,
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL) String label
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,6 +75,8 @@ public class UpdateGroupCommand implements JsonRpcLocalCommand {
|
|||||||
.choices("every-member", "only-admins");
|
.choices("every-member", "only-admins");
|
||||||
|
|
||||||
subparser.addArgument("-e", "--expiration").type(int.class).help("Set expiration time of messages (seconds)");
|
subparser.addArgument("-e", "--expiration").type(int.class).help("Set expiration time of messages (seconds)");
|
||||||
|
subparser.addArgument("--member-label-emoji").help("Specify the emoji for the member label.");
|
||||||
|
subparser.addArgument("--member-label").help("Specify the string for the member label.");
|
||||||
}
|
}
|
||||||
|
|
||||||
GroupLinkState getGroupLinkState(String value) throws UserErrorException {
|
GroupLinkState getGroupLinkState(String value) throws UserErrorException {
|
||||||
@ -126,6 +128,8 @@ public class UpdateGroupCommand implements JsonRpcLocalCommand {
|
|||||||
var groupAddMemberPermission = getGroupPermission(ns.getString("set-permission-add-member"));
|
var groupAddMemberPermission = getGroupPermission(ns.getString("set-permission-add-member"));
|
||||||
var groupEditDetailsPermission = getGroupPermission(ns.getString("set-permission-edit-details"));
|
var groupEditDetailsPermission = getGroupPermission(ns.getString("set-permission-edit-details"));
|
||||||
var groupSendMessagesPermission = getGroupPermission(ns.getString("set-permission-send-messages"));
|
var groupSendMessagesPermission = getGroupPermission(ns.getString("set-permission-send-messages"));
|
||||||
|
var memberLabelEmoji = ns.getString("member-label-emoji");
|
||||||
|
var memberLabelString = ns.getString("member-label");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
boolean isNewGroup = false;
|
boolean isNewGroup = false;
|
||||||
@ -159,6 +163,8 @@ public class UpdateGroupCommand implements JsonRpcLocalCommand {
|
|||||||
.withIsAnnouncementGroup(groupSendMessagesPermission == null
|
.withIsAnnouncementGroup(groupSendMessagesPermission == null
|
||||||
? null
|
? null
|
||||||
: groupSendMessagesPermission == GroupPermission.ONLY_ADMINS)
|
: groupSendMessagesPermission == GroupPermission.ONLY_ADMINS)
|
||||||
|
.withLabelEmoji(memberLabelEmoji)
|
||||||
|
.withLabelString(memberLabelString)
|
||||||
.build());
|
.build());
|
||||||
if (results != null) {
|
if (results != null) {
|
||||||
if (groupMessageResults == null) {
|
if (groupMessageResults == null) {
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import org.asamk.signal.manager.api.DeviceLinkUrl;
|
|||||||
import org.asamk.signal.manager.api.Group;
|
import org.asamk.signal.manager.api.Group;
|
||||||
import org.asamk.signal.manager.api.GroupId;
|
import org.asamk.signal.manager.api.GroupId;
|
||||||
import org.asamk.signal.manager.api.GroupInviteLinkUrl;
|
import org.asamk.signal.manager.api.GroupInviteLinkUrl;
|
||||||
|
import org.asamk.signal.manager.api.GroupMember;
|
||||||
import org.asamk.signal.manager.api.GroupNotFoundException;
|
import org.asamk.signal.manager.api.GroupNotFoundException;
|
||||||
import org.asamk.signal.manager.api.GroupPermission;
|
import org.asamk.signal.manager.api.GroupPermission;
|
||||||
import org.asamk.signal.manager.api.GroupSendingNotAllowedException;
|
import org.asamk.signal.manager.api.GroupSendingNotAllowedException;
|
||||||
@ -834,24 +835,22 @@ public class DbusManagerImpl implements Manager {
|
|||||||
final var group = getRemoteObject(groupPath, Signal.Group.class).GetAll("org.asamk.Signal.Group");
|
final var group = getRemoteObject(groupPath, Signal.Group.class).GetAll("org.asamk.Signal.Group");
|
||||||
final var id = (byte[]) group.get("Id").getValue();
|
final var id = (byte[]) group.get("Id").getValue();
|
||||||
try {
|
try {
|
||||||
|
final var admins = new HashSet<>(((List<String>) group.get("Admins").getValue()));
|
||||||
return new Group(GroupId.unknownVersion(id),
|
return new Group(GroupId.unknownVersion(id),
|
||||||
(String) group.get("Name").getValue(),
|
(String) group.get("Name").getValue(),
|
||||||
(String) group.get("Description").getValue(),
|
(String) group.get("Description").getValue(),
|
||||||
GroupInviteLinkUrl.fromUri((String) group.get("GroupInviteLink").getValue()),
|
GroupInviteLinkUrl.fromUri((String) group.get("GroupInviteLink").getValue()),
|
||||||
((List<String>) group.get("Members").getValue()).stream()
|
((List<String>) group.get("Members").getValue()).stream()
|
||||||
.map(m -> new RecipientAddress(m))
|
.map(m -> new GroupMember(new RecipientAddress(m), admins.contains(m), null, null))
|
||||||
.collect(Collectors.toSet()),
|
.collect(Collectors.toSet()),
|
||||||
((List<String>) group.get("PendingMembers").getValue()).stream()
|
((List<String>) group.get("PendingMembers").getValue()).stream()
|
||||||
.map(m -> new RecipientAddress(m))
|
.map(RecipientAddress::new)
|
||||||
.collect(Collectors.toSet()),
|
.collect(Collectors.toSet()),
|
||||||
((List<String>) group.get("RequestingMembers").getValue()).stream()
|
((List<String>) group.get("RequestingMembers").getValue()).stream()
|
||||||
.map(m -> new RecipientAddress(m))
|
.map(RecipientAddress::new)
|
||||||
.collect(Collectors.toSet()),
|
|
||||||
((List<String>) group.get("Admins").getValue()).stream()
|
|
||||||
.map(m -> new RecipientAddress(m))
|
|
||||||
.collect(Collectors.toSet()),
|
.collect(Collectors.toSet()),
|
||||||
((List<String>) group.get("Banned").getValue()).stream()
|
((List<String>) group.get("Banned").getValue()).stream()
|
||||||
.map(m -> new RecipientAddress(m))
|
.map(RecipientAddress::new)
|
||||||
.collect(Collectors.toSet()),
|
.collect(Collectors.toSet()),
|
||||||
(boolean) group.get("IsBlocked").getValue(),
|
(boolean) group.get("IsBlocked").getValue(),
|
||||||
(int) group.get("MessageExpirationTimer").getValue(),
|
(int) group.get("MessageExpirationTimer").getValue(),
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import org.asamk.signal.manager.api.DeviceLinkUrl;
|
|||||||
import org.asamk.signal.manager.api.GroupId;
|
import org.asamk.signal.manager.api.GroupId;
|
||||||
import org.asamk.signal.manager.api.GroupInviteLinkUrl;
|
import org.asamk.signal.manager.api.GroupInviteLinkUrl;
|
||||||
import org.asamk.signal.manager.api.GroupLinkState;
|
import org.asamk.signal.manager.api.GroupLinkState;
|
||||||
|
import org.asamk.signal.manager.api.GroupMember;
|
||||||
import org.asamk.signal.manager.api.GroupNotFoundException;
|
import org.asamk.signal.manager.api.GroupNotFoundException;
|
||||||
import org.asamk.signal.manager.api.GroupPermission;
|
import org.asamk.signal.manager.api.GroupPermission;
|
||||||
import org.asamk.signal.manager.api.GroupSendingNotAllowedException;
|
import org.asamk.signal.manager.api.GroupSendingNotAllowedException;
|
||||||
@ -624,7 +625,7 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
|
|||||||
if (group == null) {
|
if (group == null) {
|
||||||
return List.of();
|
return List.of();
|
||||||
} else {
|
} else {
|
||||||
final var members = group.members();
|
final var members = group.members().stream().map(GroupMember::recipientAddress).collect(Collectors.toSet());
|
||||||
return getRecipientStrings(members);
|
return getRecipientStrings(members);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1300,13 +1301,20 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
|
|||||||
() -> getGroup().messageExpirationTimer(),
|
() -> getGroup().messageExpirationTimer(),
|
||||||
this::setMessageExpirationTime),
|
this::setMessageExpirationTime),
|
||||||
new DbusProperty<>("Members",
|
new DbusProperty<>("Members",
|
||||||
() -> new Variant<>(getRecipientStrings(getGroup().members()), "as")),
|
() -> new Variant<>(getRecipientStrings(getGroup().members()
|
||||||
|
.stream()
|
||||||
|
.map(GroupMember::recipientAddress)
|
||||||
|
.collect(Collectors.toSet())), "as")),
|
||||||
new DbusProperty<>("PendingMembers",
|
new DbusProperty<>("PendingMembers",
|
||||||
() -> new Variant<>(getRecipientStrings(getGroup().pendingMembers()), "as")),
|
() -> new Variant<>(getRecipientStrings(getGroup().pendingMembers()), "as")),
|
||||||
new DbusProperty<>("RequestingMembers",
|
new DbusProperty<>("RequestingMembers",
|
||||||
() -> new Variant<>(getRecipientStrings(getGroup().requestingMembers()), "as")),
|
() -> new Variant<>(getRecipientStrings(getGroup().requestingMembers()), "as")),
|
||||||
new DbusProperty<>("Admins",
|
new DbusProperty<>("Admins",
|
||||||
() -> new Variant<>(getRecipientStrings(getGroup().adminMembers()), "as")),
|
() -> new Variant<>(getRecipientStrings(getGroup().members()
|
||||||
|
.stream()
|
||||||
|
.filter(GroupMember::isAdmin)
|
||||||
|
.map(GroupMember::recipientAddress)
|
||||||
|
.collect(Collectors.toSet())), "as")),
|
||||||
new DbusProperty<>("Banned",
|
new DbusProperty<>("Banned",
|
||||||
() -> new Variant<>(getRecipientStrings(getGroup().bannedMembers()), "as")),
|
() -> new Variant<>(getRecipientStrings(getGroup().bannedMembers()), "as")),
|
||||||
new DbusProperty<>("PermissionAddMember",
|
new DbusProperty<>("PermissionAddMember",
|
||||||
|
|||||||
@ -2048,6 +2048,19 @@
|
|||||||
"allDeclaredMethods": true,
|
"allDeclaredMethods": true,
|
||||||
"allDeclaredConstructors": true
|
"allDeclaredConstructors": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "org.asamk.signal.commands.ListGroupsCommand$JsonGroupMemberAddress",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "number",
|
||||||
|
"parameterTypes": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "uuid",
|
||||||
|
"parameterTypes": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "org.asamk.signal.commands.ListIdentitiesCommand$JsonIdentity",
|
"type": "org.asamk.signal.commands.ListIdentitiesCommand$JsonIdentity",
|
||||||
"allDeclaredFields": true,
|
"allDeclaredFields": true,
|
||||||
@ -7793,6 +7806,18 @@
|
|||||||
{
|
{
|
||||||
"type": "org.whispersystems.signalservice.internal.push.GroupMismatchedDevices[]"
|
"type": "org.whispersystems.signalservice.internal.push.GroupMismatchedDevices[]"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "org.whispersystems.signalservice.internal.push.GroupPatchResponse",
|
||||||
|
"methods": [
|
||||||
|
{
|
||||||
|
"name": "<init>",
|
||||||
|
"parameterTypes": [
|
||||||
|
"java.lang.Integer",
|
||||||
|
"java.lang.String"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "org.whispersystems.signalservice.internal.push.GroupStaleDevices",
|
"type": "org.whispersystems.signalservice.internal.push.GroupStaleDevices",
|
||||||
"allDeclaredFields": true,
|
"allDeclaredFields": true,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user