Compare commits

...

28 Commits

Author SHA1 Message Date
AsamK
14297986f2 Use SequencedCollection instead of List 2026-01-24 17:24:27 +01:00
AsamK
0bd4d554d8 Use virtual threads 2026-01-24 17:24:27 +01:00
AsamK
32c8d4f801 Update to java 25 2026-01-24 15:38:02 +01:00
AsamK
82abc20871 Remove deprecated functionality 2026-01-24 15:37:00 +01:00
AsamK
e8ab01f665 Fix use of deprecated API in plugin 2026-01-24 15:20:55 +01:00
AsamK
dee557a9ad Prepare next release 2026-01-24 15:09:23 +01:00
AsamK
f1fa2eba1d Bump version to 0.13.23 2026-01-24 14:21:40 +01:00
AsamK
ccd58bbf23 Use graalvm-community 2026-01-24 14:21:40 +01:00
AsamK
54700d9cd0 Update libsignal-service 2026-01-24 12:36:29 +01:00
AsamK
984ea47f9d Update gradle 2026-01-24 12:36:29 +01:00
AsamK
9458972d15 Update dependencies 2026-01-24 12:36:29 +01:00
AsamK
8eb9662694 Implement new updateDevice command to update device names
Fixes #1906
2026-01-23 20:00:25 +01:00
Benjamin Loison
a9be1aa608 Remove an unnecessary space in README.md 2026-01-23 19:37:26 +01:00
AsamK
3af9dff0ed Update libsignal-service 2025-12-10 19:49:57 +01:00
AsamK
c5e4b250b8 Parse binary aci/pni in storage records 2025-12-10 18:07:25 +01:00
AsamK
5fafa24974 Create correct nickname record if nickname is empty 2025-12-08 19:09:57 +01:00
AsamK
b26c521930 Extend username documentation
Fixes #1886
2025-12-08 17:44:06 +01:00
AsamK
eb52380ecf Remove now unnecessary check for primary device from updateContact
Fixes #1880
2025-12-08 17:08:38 +01:00
AsamK
f1de69d7ff Set same toolchain in lib module as in main module 2025-12-07 19:58:25 +01:00
AsamK
ba2214d8c7 Silence unchecked error 2025-12-07 19:47:53 +01:00
AsamK
fca4d7459c Clear verification sessionId after registration/changeNumber
Fixes #1882
2025-12-05 21:28:36 +01:00
AsamK
ad2338b898 Add documentation for polls
Closes #1885
2025-12-05 21:20:53 +01:00
AsamK
c237f98044 Mark legacy accounts without ACI as unregistered 2025-12-05 20:08:07 +01:00
AsamK
87945ac506 Ignore authorization failed errors in multi account mode
Fixes #1884
2025-12-05 20:08:07 +01:00
Artemii Bigdan
11d96c894d Print user number in UserAlreadyExistsException DBus error
Currently, the error looks like "UserAlreadyExistsException null", which does not give enough information to handle this situation in the multi-account daemon mode.

This change adds a phone number to the error message resolving my issue and achieving functional parity with CLI interface.
2025-12-03 18:39:48 +01:00
AsamK
8dcc16d640 Update README.md 2025-11-19 20:31:54 +01:00
Ankur Heramb Joshi
9a1ddd0d41 Revise installation commands in README.md
Updated installation instructions for signal-cli to use the latest version dynamically.

1] Uncompressed signal-cli-"${VERSION}".tar.gz directly produces executable named signal-cli and NOT named signal-cli-"${VERSION}" , thus was getting error in installation. 
This change should rectify the issue.

2] Also finding latest release version is simplified
2025-11-19 20:31:54 +01:00
AsamK
b8bb58b083 Prepare next release 2025-11-14 20:38:10 +01:00
107 changed files with 10058 additions and 562 deletions

View File

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
java: [ '21', '25' ]
java: [ '25' ]
steps:
- uses: actions/checkout@v4
@ -58,8 +58,8 @@ jobs:
- uses: actions/checkout@v4
- uses: graalvm/setup-graalvm@v1
with:
version: 'latest'
java-version: '21'
distribution: 'graalvm'
java-version: '25'
cache: 'gradle'
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build with Gradle

View File

@ -24,7 +24,7 @@ jobs:
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 21
java-version: 25
- name: Checkout repository
uses: actions/checkout@v4

View File

@ -1,5 +1,32 @@
# Changelog
## [Unreleased]
**Attention**: Now requires Java 25
### Breaking changes
- Remove isRegistered method without parameters from Signal dbus interface, which always returned `true`
- Remove `sandbox` value for --service-environment parameter, use `staging` instead
- The `daemon` command now requires at least one channel parameter (`--socket`, `--dbus`, ...) and no longer defaults to dbus
## [0.13.23] - 2026-01-24
Requires libsignal-client version 0.86.12.
### Added
- Add sendPollCreate, sendPollVote, sendPollTerminate commands for polls
- Add updateDevice command to set device name of linked devices
### Changed
- Allow updating contact names from linked devices
### Fixed
- Start multi account mode even if some accounts have authorization failures
## [0.13.22] - 2025-11-14
Requires libsignal-client version 0.86.1.

View File

@ -1,4 +1,4 @@
FROM docker.io/azul/zulu-openjdk:21-jre-headless
FROM docker.io/azul/zulu-openjdk:25-jre-headless
LABEL org.opencontainers.image.source=https://github.com/AsamK/signal-cli
LABEL org.opencontainers.image.description="signal-cli provides an unofficial commandline, dbus and JSON-RPC interface for the Signal messenger."

View File

@ -8,7 +8,7 @@ For registering you need a phone number where you can receive SMS or incoming ca
signal-cli is primarily intended to be used on servers to notify admins of important events.
For this use-case, it has a daemon mode with JSON-RPC interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-jsonrpc.5.adoc))
and D-BUS interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-dbus.5.adoc)) .
and D-BUS interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-dbus.5.adoc)).
For the JSON-RPC interface there's also a simple [example client](https://github.com/AsamK/signal-cli/tree/master/client), written in Rust.
signal-cli needs to be kept up-to-date to keep up with Signal-Server changes.
@ -23,24 +23,33 @@ Windows. There's also a [docker image and some Linux packages](https://github.co
System requirements:
- at least Java Runtime Environment (JRE) 21
- at least Java Runtime Environment (JRE) 25
- native library: libsignal-client
The native libs are bundled for x86_64 Linux (with recent enough glibc), Windows and MacOS. For other
systems/architectures
see: [Provide native lib for libsignal](https://github.com/AsamK/signal-cli/wiki/Provide-native-lib-for-libsignal)
### Install system-wide on Linux
### Install system-wide on Linux [ JVM build ]
See [latest version](https://github.com/AsamK/signal-cli/releases).
```sh
export VERSION=<latest version, format "x.y.z">
wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz
VERSION=$(curl -Ls -o /dev/null -w %{url_effective} https://github.com/AsamK/signal-cli/releases/latest | sed -e 's/^.*\/v//')
curl -L -O https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz
sudo tar xf signal-cli-"${VERSION}".tar.gz -C /opt
sudo ln -sf /opt/signal-cli-"${VERSION}"/bin/signal-cli /usr/local/bin/
```
### Install system-wide on Linux [ GraalVM native build ]
```sh
VERSION=$(curl -Ls -o /dev/null -w %{url_effective} https://github.com/AsamK/signal-cli/releases/latest | sed -e 's/^.*\/v//')
curl -L -O https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}"-Linux-native.tar.gz
sudo tar xf signal-cli-"${VERSION}"-Linux-native.tar.gz -C /opt
sudo ln -sf /opt/signal-cli /usr/local/bin/
```
You can find further instructions on the Wiki:
- [Quickstart](https://github.com/AsamK/signal-cli/wiki/Quickstart)

View File

@ -3,17 +3,17 @@ plugins {
application
eclipse
`check-lib-versions`
id("org.graalvm.buildtools.native") version "0.11.3"
id("org.graalvm.buildtools.native") version "0.11.4"
}
allprojects {
group = "org.asamk"
version = "0.13.22"
version = "0.14.0-SNAPSHOT"
}
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
sourceCompatibility = JavaVersion.VERSION_25
targetCompatibility = JavaVersion.VERSION_25
if (!JavaVersion.current().isCompatibleWith(targetCompatibility)) {
toolchain {
@ -39,7 +39,7 @@ graalvmNative {
if (System.getenv("GRAALVM_HOME") == null) {
toolchainDetection.set(true)
javaLauncher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(21))
languageVersion.set(JavaLanguageVersion.of(25))
})
} else {
toolchainDetection.set(false)

View File

@ -7,11 +7,11 @@ plugins {
}
tasks.named<KotlinCompilationTask<KotlinJvmCompilerOptions>>("compileKotlin").configure {
compilerOptions.jvmTarget.set(JvmTarget.JVM_17)
compilerOptions.jvmTarget.set(JvmTarget.JVM_24)
}
java {
targetCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_24
}
repositories {

View File

@ -1,5 +1,3 @@
@file:Suppress("DEPRECATION")
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
@ -8,7 +6,7 @@ import javax.xml.parsers.DocumentBuilderFactory
class CheckLibVersionsPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.task("checkLibVersions") {
project.tasks.register("checkLibVersions") {
description =
"Find any 3rd party libraries which have released new versions to the central Maven repo since we last upgraded."
doLast {
@ -28,7 +26,7 @@ class CheckLibVersionsPlugin : Plugin<Project> {
try {
val dbf = DocumentBuilderFactory.newInstance()
val db = dbf.newDocumentBuilder()
val doc = db.parse(metaDataUrl);
val doc = db.parse(metaDataUrl)
val newest = doc.getElementsByTagName("latest").item(0).textContent
if (version != newest.toString()) {
println("UPGRADE {\"group\": \"$group\", \"name\": \"$name\", \"current\": \"$version\", \"latest\": \"$newest\"}")

View File

@ -45,6 +45,9 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="0.13.23" date="2026-01-24">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.23</url>
</release>
<release version="0.13.22" date="2025-11-14">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.22</url>
</release>

View File

@ -121,7 +121,7 @@
},
{
"name":"org.signal.libsignal.net.ChatConnection$ListenerBridge",
"methods":[{"name":"onConnectionInterrupted","parameterTypes":["java.lang.Throwable"] }, {"name":"onIncomingMessage","parameterTypes":["byte[]","long","long"] }, {"name":"onQueueEmpty","parameterTypes":[] }, {"name":"onReceivedAlerts","parameterTypes":["java.lang.String[]"] }]
"methods":[{"name":"connectionInterrupted","parameterTypes":["java.lang.Throwable"] }, {"name":"onConnectionInterrupted","parameterTypes":["java.lang.Throwable"] }, {"name":"onIncomingMessage","parameterTypes":["byte[]","long","long"] }, {"name":"onQueueEmpty","parameterTypes":[] }, {"name":"onReceivedAlerts","parameterTypes":["java.lang.String[]"] }, {"name":"receivedAlerts","parameterTypes":["java.lang.String[]"] }, {"name":"receivedIncomingMessage","parameterTypes":["byte[]","long","long"] }, {"name":"receivedQueueEmpty","parameterTypes":[] }]
},
{
"name":"org.signal.libsignal.net.ChatConnection$Response",
@ -139,6 +139,14 @@
{
"name":"org.signal.libsignal.net.ChatService$ResponseAndDebugInfo"
},
{
"name":"org.signal.libsignal.net.ChatServiceException",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"org.signal.libsignal.net.ChatServiceInactiveException",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"org.signal.libsignal.net.DeviceDeregisteredException",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
@ -151,6 +159,10 @@
"name":"org.signal.libsignal.net.RetryLaterException",
"methods":[{"name":"<init>","parameterTypes":["long"] }]
},
{
"name":"org.signal.libsignal.net.TransportFailureException",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"org.signal.libsignal.net.internal.BridgeChatListener"
},

View File

@ -1,26 +1 @@
[
{
"interfaces":["java.sql.Connection"]
},
{
"interfaces":["org.asamk.Signal"]
},
{
"interfaces":["org.asamk.Signal$Configuration"]
},
{
"interfaces":["org.asamk.Signal$Device"]
},
{
"interfaces":["org.asamk.Signal$Group"]
},
{
"interfaces":["org.asamk.Signal$Identity"]
},
{
"interfaces":["org.asamk.SignalControl"]
},
{
"interfaces":["org.freedesktop.dbus.interfaces.DBus"]
}
]
[{"interfaces":["java.sql.Connection"]},{"interfaces":["org.asamk.Signal"]},{"interfaces":["org.asamk.Signal$Configuration"]},{"interfaces":["org.asamk.Signal$Device"]},{"interfaces":["org.asamk.Signal$Group"]},{"interfaces":["org.asamk.Signal$Identity"]},{"interfaces":["org.asamk.SignalControl"]},{"interfaces":["org.freedesktop.dbus.interfaces.DBus"]}]

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,18 +1,18 @@
[versions]
slf4j = "2.0.17"
junit = "6.0.1"
junit = "6.0.2"
[libraries]
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.82"
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.83"
jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.20.1"
argparse4j = "net.sourceforge.argparse4j:argparse4j:0.9.0"
dbusjava = "com.github.hypfvieh:dbus-java-transport-native-unixsocket:5.0.0"
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" }
logback = "ch.qos.logback:logback-classic:1.5.21"
logback = "ch.qos.logback:logback-classic:1.5.25"
signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_134"
sqlite = "org.xerial:sqlite-jdbc:3.51.0.0"
signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_136"
sqlite = "org.xerial:sqlite-jdbc:3.51.1.0"
hikari = "com.zaxxer:HikariCP:7.0.2"
junit-jupiter-bom = { module = "org.junit:junit-bom", version.ref = "junit" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@ -4,11 +4,13 @@ plugins {
}
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
sourceCompatibility = JavaVersion.VERSION_25
targetCompatibility = JavaVersion.VERSION_25
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
if (!JavaVersion.current().isCompatibleWith(targetCompatibility)) {
toolchain {
languageVersion.set(JavaLanguageVersion.of(targetCompatibility.majorVersion))
}
}
}

View File

@ -154,6 +154,8 @@ public interface Manager extends Closeable {
List<Device> getLinkedDevices() throws IOException;
void updateLinkedDevice(int deviceId, String name) throws IOException, NotPrimaryDeviceException;
void removeLinkedDevices(int deviceId) throws IOException, NotPrimaryDeviceException;
void addDeviceLink(DeviceLinkUrl linkUri) throws IOException, InvalidDeviceLinkException, NotPrimaryDeviceException, DeviceLimitExceededException;
@ -271,7 +273,7 @@ public interface Manager extends Closeable {
final String nickGivenName,
final String nickFamilyName,
final String note
) throws NotPrimaryDeviceException, UnregisteredRecipientException;
) throws UnregisteredRecipientException;
void setContactsBlocked(
Collection<RecipientIdentifier.Single> recipient,

View File

@ -64,7 +64,7 @@ public class SignalAccountFiles {
return accountsStore.getAllNumbers();
}
public MultiAccountManager initMultiAccountManager() throws IOException, AccountCheckException {
public MultiAccountManager initMultiAccountManager() throws IOException {
final var managerPairs = accountsStore.getAllAccounts().parallelStream().map(a -> {
try {
return new Pair<Manager, Throwable>(initManager(a.number(), a.path()), null);
@ -80,12 +80,13 @@ public class SignalAccountFiles {
for (final var pair : managerPairs) {
if (pair.second() instanceof IOException e) {
throw e;
} else if (pair.second() instanceof AccountCheckException e) {
throw e;
}
}
final var managers = managerPairs.stream().map(Pair::first).toList();
final var managers = managerPairs.stream()
.filter(p -> p != null && p.first() != null)
.map(Pair::first)
.toList();
return new MultiAccountManagerImpl(managers, this);
}
@ -132,7 +133,7 @@ public class SignalAccountFiles {
manager.checkAccountState();
} catch (DeprecatedVersionException e) {
manager.close();
throw new AccountCheckException("signal-cli version is too old for the Signal-Server, please update.");
throw new IOException("signal-cli version is too old for the Signal-Server, please update.");
} catch (IOException e) {
manager.close();
throw new AccountCheckException("Error while checking account " + number + ": " + e.getMessage(), e);

View File

@ -2,7 +2,7 @@ package org.asamk.signal.manager.actions;
import org.asamk.signal.manager.helper.Context;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.signal.core.models.ServiceId;
public class RenewSessionAction implements HandleAction {

View File

@ -0,0 +1,20 @@
package org.asamk.signal.manager.actions;
import org.asamk.signal.manager.helper.Context;
public class RetrieveDeviceNameAction implements HandleAction {
private static final RetrieveDeviceNameAction INSTANCE = new RetrieveDeviceNameAction();
public static RetrieveDeviceNameAction create() {
return INSTANCE;
}
private RetrieveDeviceNameAction() {
}
@Override
public void execute(Context context) throws Throwable {
context.getAccountHelper().refreshDeviceName();
}
}

View File

@ -4,8 +4,8 @@ import org.asamk.signal.manager.groups.GroupLinkPassword;
import org.signal.core.util.Base64;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.storageservice.protos.groups.GroupInviteLink;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.storage.protos.groups.GroupInviteLink;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
import java.io.IOException;
import java.net.URI;
@ -52,8 +52,8 @@ public final class GroupInviteLinkUrl {
var bytes = Base64.decode(encoding);
GroupInviteLink groupInviteLink = GroupInviteLink.ADAPTER.decode(bytes);
if (groupInviteLink.v1Contents != null) {
var groupInviteLinkContentsV1 = groupInviteLink.v1Contents;
if (groupInviteLink.contentsV1 != null) {
var groupInviteLinkContentsV1 = groupInviteLink.contentsV1;
var groupMasterKey = new GroupMasterKey(groupInviteLinkContentsV1.groupMasterKey.toByteArray());
var password = GroupLinkPassword.fromBytes(groupInviteLinkContentsV1.inviteLinkPassword.toByteArray());
@ -90,7 +90,7 @@ public final class GroupInviteLinkUrl {
}
private static String createUrl(GroupMasterKey groupMasterKey, GroupLinkPassword password) {
var groupInviteLink = new GroupInviteLink.Builder().v1Contents(new GroupInviteLink.GroupInviteLinkContentsV1.Builder().groupMasterKey(
var groupInviteLink = new GroupInviteLink.Builder().contentsV1(new GroupInviteLink.GroupInviteLinkContentsV1.Builder().groupMasterKey(
ByteString.of(groupMasterKey.serialize()))
.inviteLinkPassword(ByteString.of(password.serialize()))
.build()).build();

View File

@ -3,6 +3,7 @@ package org.asamk.signal.manager.api;
import org.asamk.signal.manager.groups.GroupUtils;
import org.asamk.signal.manager.helper.RecipientAddressResolver;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.signal.core.models.ServiceId;
import org.signal.libsignal.metadata.ProtocolException;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
@ -32,7 +33,6 @@ import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptM
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.messages.multidevice.ViewOnceOpenMessage;
import org.whispersystems.signalservice.api.messages.multidevice.ViewedMessage;
import org.whispersystems.signalservice.api.push.ServiceId;
import java.io.File;
import java.io.IOException;

View File

@ -1,6 +1,6 @@
package org.asamk.signal.manager.api;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.signal.core.util.UuidUtil;
import java.util.Optional;
import java.util.UUID;

View File

@ -1,9 +1,9 @@
package org.asamk.signal.manager.api;
import org.asamk.signal.manager.util.PhoneNumberFormatter;
import org.signal.core.util.UuidUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.util.UUID;
@ -24,13 +24,13 @@ public sealed interface RecipientIdentifier {
sealed interface Single extends RecipientIdentifier {
static Single fromString(String identifier, String localNumber) throws InvalidNumberException {
if (UuidUtil.isUuid(identifier)) {
if (UuidUtil.INSTANCE.isUuid(identifier)) {
return new Uuid(UUID.fromString(identifier));
}
if (identifier.startsWith("PNI:")) {
final var pni = identifier.substring(4);
if (!UuidUtil.isUuid(pni)) {
if (!UuidUtil.INSTANCE.isUuid(pni)) {
throw new InvalidNumberException("Invalid PNI");
}
return new Pni(UUID.fromString(pni));

View File

@ -1,6 +1,6 @@
package org.asamk.signal.manager.api;
import org.whispersystems.signalservice.internal.util.Hex;
import org.signal.core.util.Hex;
import java.util.Arrays;

View File

@ -1,7 +1,7 @@
package org.asamk.signal.manager.api;
import org.asamk.signal.manager.util.Utils;
import org.whispersystems.signalservice.internal.util.Hex;
import org.signal.core.util.Hex;
import java.io.IOException;
import java.net.URI;

View File

@ -1,8 +1,8 @@
package org.asamk.signal.manager.api;
import org.signal.core.util.Base64;
import org.signal.core.util.UuidUtil;
import org.whispersystems.signalservice.api.push.UsernameLinkComponents;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.IOException;
import java.util.Arrays;
@ -36,7 +36,7 @@ public final class UsernameLinkUrl {
final var entropy = Arrays.copyOfRange(allBytes, 0, 32);
final var serverId = Arrays.copyOfRange(allBytes, 32, allBytes.length);
final var serverIdUuid = UuidUtil.parseOrNull(serverId);
final var serverIdUuid = UuidUtil.INSTANCE.parseOrNull(serverId);
if (serverIdUuid == null) {
throw new InvalidUsernameLinkException("Invalid serverId");
}

View File

@ -1,6 +1,6 @@
package org.asamk.signal.manager.helper;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.signal.core.models.ServiceId.ACI;
public interface AccountFileUpdater {

View File

@ -14,6 +14,8 @@ import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.util.KeyUtils;
import org.asamk.signal.manager.util.NumberVerificationUtils;
import org.asamk.signal.manager.util.Utils;
import org.signal.core.models.ServiceId.ACI;
import org.signal.core.models.ServiceId.PNI;
import org.signal.core.util.Base64;
import org.signal.libsignal.protocol.IdentityKeyPair;
import org.signal.libsignal.protocol.InvalidKeyException;
@ -28,8 +30,6 @@ import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest;
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
import org.whispersystems.signalservice.api.link.LinkedDeviceVerificationCodeResponse;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity;
@ -82,16 +82,9 @@ public class AccountHelper {
}
public void checkAccountState() throws IOException {
if (account.getLastReceiveTimestamp() == 0) {
logger.info("The Signal protocol expects that incoming messages are regularly received.");
} else {
var diffInMilliseconds = System.currentTimeMillis() - account.getLastReceiveTimestamp();
long days = TimeUnit.DAYS.convert(diffInMilliseconds, TimeUnit.MILLISECONDS);
if (days > 7) {
logger.warn(
"Messages have been last received {} days ago. The Signal protocol expects that incoming messages are regularly received.",
days);
}
if (account.getAci() == null) {
account.setRegistered(false);
throw new IOException("Account without ACI");
}
try {
updateAccountAttributes();
@ -100,7 +93,7 @@ public class AccountHelper {
} else {
context.getPreKeyHelper().refreshPreKeysIfNecessary();
}
if (account.getAci() == null || account.getPni() == null) {
if (account.getPni() == null) {
checkWhoAmiI();
}
if (!account.isPrimaryDevice() && account.getPniIdentityKeyPair() == null) {
@ -125,6 +118,17 @@ public class AccountHelper {
account.setRegistered(false);
throw e;
}
if (account.getLastReceiveTimestamp() == 0) {
logger.info("The Signal protocol expects that incoming messages are regularly received.");
} else {
var diffInMilliseconds = System.currentTimeMillis() - account.getLastReceiveTimestamp();
long days = TimeUnit.DAYS.convert(diffInMilliseconds, TimeUnit.MILLISECONDS);
if (days > 7) {
logger.warn(
"Messages have been last received {} days ago. The Signal protocol expects that incoming messages are regularly received.",
days);
}
}
}
public void checkWhoAmiI() throws IOException {
@ -311,6 +315,7 @@ public class AccountHelper {
Utils.mapKeys(pniRegistrationIds, Object::toString))));
});
account.clearSessionId();
final var updatePni = PNI.parseOrThrow(result.first().getPni());
if (updatePni.equals(account.getPni())) {
logger.debug("PNI is unchanged after change number");
@ -517,6 +522,22 @@ public class AccountHelper {
account.setEncryptedDeviceName(encryptedDeviceName);
}
public void setDeviceName(int deviceId, String deviceName) throws IOException {
final var privateKey = account.getAciIdentityKeyPair().getPrivateKey();
final var encryptedDeviceName = DeviceNameUtil.encryptDeviceName(deviceName, privateKey);
handleResponseException(dependencies.getLinkDeviceApi().setDeviceName(encryptedDeviceName, deviceId));
context.getSyncHelper().sendDeviceNameChange(deviceId);
}
public void refreshDeviceName() throws IOException {
final var devices = handleResponseException(dependencies.getLinkDeviceApi().getDevices());
final var deviceId = account.getDeviceId();
final var device = devices.stream().filter(d -> d.id == deviceId).findFirst();
if (device.isPresent()) {
account.setEncryptedDeviceName(device.get().name);
}
}
public void updateAccountAttributes() throws IOException {
handleResponseException(dependencies.getAccountApi().setAccountAttributes(account.getAccountAttributes(null)));
}

View File

@ -28,15 +28,16 @@ import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.util.AttachmentUtils;
import org.asamk.signal.manager.util.IOUtils;
import org.asamk.signal.manager.util.Utils;
import org.signal.core.models.ServiceId;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsementsResponse;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.storageservice.protos.groups.GroupChangeResponse;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
import org.signal.storageservice.storage.protos.groups.GroupChangeResponse;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupJoinInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupChangeLog;
@ -47,7 +48,6 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStre
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupV2;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.exceptions.ConflictException;
import java.io.IOException;
@ -192,9 +192,9 @@ public class GroupHelper {
final GroupInfoV2 groupInfoV2,
final GroupChangeResponse groupChangeResponse
) {
if (groupChangeResponse.groupSendEndorsementsResponse.size() > 0) {
if (groupChangeResponse.group_send_endorsements_response.size() > 0) {
try {
final var groupSendEndorsementsResponse = new GroupSendEndorsementsResponse(groupChangeResponse.groupSendEndorsementsResponse.toByteArray());
final var groupSendEndorsementsResponse = new GroupSendEndorsementsResponse(groupChangeResponse.group_send_endorsements_response.toByteArray());
updateGroupEndorsements(groupInfoV2.getGroupId(),
groupInfoV2.getMasterKey(),
@ -391,7 +391,7 @@ public class GroupHelper {
.joinGroup(inviteLinkUrl.getGroupMasterKey(), inviteLinkUrl.getPassword(), groupJoinInfo);
final var group = getOrMigrateGroup(inviteLinkUrl.getGroupMasterKey(),
groupJoinInfo.revision + 1,
changeResponse.groupChange == null ? null : changeResponse.groupChange.encode());
changeResponse.group_change == null ? null : changeResponse.group_change.encode());
if (group.getGroup() == null) {
// Only requested member, can't send update to group members
@ -873,10 +873,10 @@ public class GroupHelper {
final var groupChangeResponse = groupGroupChangePair.second();
handleGroupChangeResponse(groupInfoV2, groupChangeResponse);
if (groupChangeResponse.groupChange == null) {
if (groupChangeResponse.group_change == null) {
throw new AssertionError("groupChange is null");
}
var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupChangeResponse.groupChange.encode());
var messageBuilder = getGroupUpdateMessageBuilder(groupInfoV2, groupChangeResponse.group_change.encode());
return sendGroupMessage(messageBuilder,
groupInfoV2.getMembersIncludingPendingWithout(account.getSelfRecipientId()),
groupInfoV2);
@ -924,10 +924,10 @@ public class GroupHelper {
members.addAll(group.getMembersIncludingPendingWithout(selfRecipientId));
account.getGroupStore().updateGroup(group);
if (groupChangeResponse.groupChange == null) {
if (groupChangeResponse.group_change == null) {
throw new AssertionError("groupChange is null");
}
final var messageBuilder = getGroupUpdateMessageBuilder(group, groupChangeResponse.groupChange.encode());
final var messageBuilder = getGroupUpdateMessageBuilder(group, groupChangeResponse.group_change.encode());
return sendGroupMessage(messageBuilder, members, group);
}

View File

@ -10,6 +10,10 @@ import org.asamk.signal.manager.internal.SignalDependencies;
import org.asamk.signal.manager.storage.groups.GroupInfoV2;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.util.Utils;
import org.signal.core.models.ServiceId;
import org.signal.core.models.ServiceId.ACI;
import org.signal.core.models.ServiceId.PNI;
import org.signal.core.util.UuidUtil;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.VerificationFailedException;
import org.signal.libsignal.zkgroup.auth.AuthCredentialWithPniResponse;
@ -17,15 +21,15 @@ import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
import org.signal.libsignal.zkgroup.groups.UuidCiphertext;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.GroupChangeResponse;
import org.signal.storageservice.protos.groups.Member;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
import org.signal.storageservice.storage.protos.groups.AccessControl;
import org.signal.storageservice.storage.protos.groups.GroupChange;
import org.signal.storageservice.storage.protos.groups.GroupChangeResponse;
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.DecryptedGroupChange;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupJoinInfo;
import org.signal.storageservice.storage.protos.groups.local.DecryptedMember;
import org.signal.storageservice.storage.protos.groups.local.DecryptedPendingMember;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.groupsv2.DecryptChangeVerificationMode;
@ -38,12 +42,8 @@ import org.whispersystems.signalservice.api.groupsv2.GroupsV2AuthorizationString
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
import org.whispersystems.signalservice.api.groupsv2.InvalidGroupStateException;
import org.whispersystems.signalservice.api.groupsv2.NotAbleToApplyGroupV2ChangeException;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.push.exceptions.NotInGroupException;
import java.io.IOException;
@ -225,7 +225,7 @@ class GroupV2Helper {
change.modifyAvatar(new GroupChange.Actions.ModifyAvatarAction.Builder().avatar(avatarCdnKey).build());
}
change.sourceServiceId(getSelfAci().toByteString());
change.sourceUserId(getSelfAci().toByteString());
return commitChange(groupInfoV2, change);
}
@ -252,7 +252,7 @@ class GroupV2Helper {
final var aci = getSelfAci();
final var change = groupOperations.createModifyGroupMembershipChange(candidates, bannedUuids, aci);
change.sourceServiceId(getSelfAci().toByteString());
change.sourceUserId(getSelfAci().toByteString());
return commitChange(groupInfoV2, change);
}
@ -343,7 +343,7 @@ class GroupV2Helper {
false,
groupInfoV2.getGroup().bannedMembers);
change.sourceServiceId(getSelfAci().toByteString());
change.sourceUserId(getSelfAci().toByteString());
return commitChange(groupInfoV2, change);
}
@ -360,7 +360,7 @@ class GroupV2Helper {
final var change = groupOperations.createUnbanServiceIdsChange(serviceIds);
change.sourceServiceId(getSelfAci().toByteString());
change.sourceUserId(getSelfAci().toByteString());
return commitChange(groupInfoV2, change);
}
@ -436,7 +436,7 @@ class GroupV2Helper {
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
final var change = groupOperations.createUpdateProfileKeyCredentialChange(profileKeyCredential);
change.sourceServiceId(getSelfAci().toByteString());
change.sourceUserId(getSelfAci().toByteString());
return commitChange(groupInfoV2, change);
}
@ -459,7 +459,7 @@ class GroupV2Helper {
? groupOperations.createGroupJoinRequest(profileKeyCredential)
: groupOperations.createGroupJoinDirect(profileKeyCredential);
change.sourceServiceId(context.getRecipientHelper()
change.sourceUserId(context.getRecipientHelper()
.resolveSignalServiceAddress(selfRecipientId)
.getServiceId()
.toByteString());
@ -479,7 +479,7 @@ class GroupV2Helper {
final var change = groupOperations.createAcceptInviteChange(profileKeyCredential);
final var aci = context.getRecipientHelper().resolveSignalServiceAddress(selfRecipientId).getServiceId();
change.sourceServiceId(aci.toByteString());
change.sourceUserId(aci.toByteString());
return commitChange(groupInfoV2, change);
}
@ -585,7 +585,7 @@ class GroupV2Helper {
final var groupOperations = dependencies.getGroupsV2Operations().forGroup(groupSecretParams);
final var previousGroupState = groupInfoV2.getGroup();
final var nextRevision = previousGroupState.revision + 1;
final var changeActions = change.revision(nextRevision).build();
final var changeActions = change.version(nextRevision).build();
final DecryptedGroupChange decryptedChange;
final DecryptedGroup decryptedGroupState;
@ -611,7 +611,7 @@ class GroupV2Helper {
GroupLinkPassword password
) throws IOException {
final var nextRevision = currentRevision + 1;
final var changeActions = change.revision(nextRevision).build();
final var changeActions = change.version(nextRevision).build();
return dependencies.getGroupsV2Api()
.patchGroup(changeActions,
@ -621,6 +621,9 @@ class GroupV2Helper {
Pair<ServiceId, ProfileKey> getAuthoritativeProfileKeyFromChange(final DecryptedGroupChange change) {
UUID editor = UuidUtil.fromByteStringOrNull(change.editorServiceIdBytes);
if (editor == null) {
return null;
}
final var editorProfileKeyBytes = Stream.concat(Stream.of(change.newMembers.stream(),
change.promotePendingMembers.stream(),
change.modifiedProfileKeys.stream())

View File

@ -4,6 +4,7 @@ import org.asamk.signal.manager.api.TrustLevel;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.util.Utils;
import org.signal.core.models.ServiceId;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.fingerprint.Fingerprint;
import org.signal.libsignal.protocol.fingerprint.FingerprintParsingException;
@ -12,7 +13,6 @@ import org.signal.libsignal.protocol.fingerprint.ScannableFingerprint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.push.ServiceId;
import java.util.Arrays;
import java.util.function.BiFunction;

View File

@ -5,6 +5,7 @@ import org.asamk.signal.manager.actions.HandleAction;
import org.asamk.signal.manager.actions.RefreshPreKeysAction;
import org.asamk.signal.manager.actions.RenewSessionAction;
import org.asamk.signal.manager.actions.ResendMessageAction;
import org.asamk.signal.manager.actions.RetrieveDeviceNameAction;
import org.asamk.signal.manager.actions.RetrieveProfileAction;
import org.asamk.signal.manager.actions.SendGroupInfoAction;
import org.asamk.signal.manager.actions.SendGroupInfoRequestAction;
@ -34,6 +35,8 @@ import org.asamk.signal.manager.storage.groups.GroupInfoV1;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.stickers.StickerPack;
import org.signal.core.models.ServiceId;
import org.signal.core.models.ServiceId.ACI;
import org.signal.libsignal.metadata.ProtocolInvalidKeyException;
import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException;
import org.signal.libsignal.metadata.ProtocolInvalidMessageException;
@ -62,8 +65,6 @@ import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage
import org.whispersystems.signalservice.api.messages.SignalServiceStoryMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.internal.push.Envelope;
@ -633,6 +634,12 @@ public final class IncomingMessageHandler {
context.getAccountHelper().handlePniChangeNumberMessage(pniChangeNumber, updatedPni);
}
}
if (syncMessage.getDeviceNameChange().isPresent()) {
final var deviceNameChange = syncMessage.getDeviceNameChange().get();
if (deviceNameChange.deviceId != null && deviceNameChange.deviceId == account.getDeviceId()) {
actions.add(RetrieveDeviceNameAction.create());
}
}
return actions;
}

View File

@ -1,9 +1,9 @@
package org.asamk.signal.manager.helper;
import org.asamk.signal.manager.api.IncorrectPinException;
import org.signal.core.models.MasterKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.kbs.MasterKey;
import org.whispersystems.signalservice.api.svr.SecureValueRecovery;
import org.whispersystems.signalservice.internal.push.AuthCredentials;
import org.whispersystems.signalservice.internal.push.LockedException;

View File

@ -9,11 +9,11 @@ import org.asamk.signal.manager.jobs.CleanOldPreKeysJob;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.messageCache.CachedMessage;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.signal.core.models.ServiceId;
import org.signal.core.models.ServiceId.ACI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.websocket.SignalWebSocket;
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState;
import org.whispersystems.signalservice.api.websocket.WebSocketUnavailableException;

View File

@ -6,14 +6,14 @@ import org.asamk.signal.manager.api.UsernameLinkUrl;
import org.asamk.signal.manager.internal.SignalDependencies;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.signal.core.models.ServiceId;
import org.signal.core.models.ServiceId.ACI;
import org.signal.core.models.ServiceId.PNI;
import org.signal.libsignal.usernames.BaseUsernameException;
import org.signal.libsignal.usernames.Username;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.cds.CdsiV2Service;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidArgumentException;
import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidTokenException;

View File

@ -15,6 +15,7 @@ import org.asamk.signal.manager.storage.groups.GroupInfo;
import org.asamk.signal.manager.storage.groups.GroupInfoV2;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.sendLog.MessageSendLogEntry;
import org.signal.core.models.ServiceId.ACI;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.InvalidRegistrationIdException;
import org.signal.libsignal.protocol.NoSessionException;
@ -38,7 +39,6 @@ import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
import org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException;

View File

@ -7,10 +7,10 @@ import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.stickerPacks.JsonStickerPack;
import org.asamk.signal.manager.storage.stickers.StickerPack;
import org.asamk.signal.manager.util.IOUtils;
import org.signal.core.util.Hex;
import org.signal.libsignal.protocol.InvalidMessageException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.internal.util.Hex;
import java.io.IOException;
import java.util.HashSet;

View File

@ -14,6 +14,7 @@ import org.asamk.signal.manager.syncStorage.StorageSyncModels;
import org.asamk.signal.manager.syncStorage.StorageSyncValidations;
import org.asamk.signal.manager.syncStorage.WriteOperationResult;
import org.asamk.signal.manager.util.KeyUtils;
import org.signal.core.models.storageservice.StorageKey;
import org.signal.core.util.SetUtil;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.slf4j.Logger;
@ -23,7 +24,6 @@ import org.whispersystems.signalservice.api.storage.RecordIkm;
import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
import org.whispersystems.signalservice.api.storage.StorageId;
import org.whispersystems.signalservice.api.storage.StorageKey;
import org.whispersystems.signalservice.api.storage.StorageRecordConvertersKt;
import org.whispersystems.signalservice.api.storage.StorageServiceRepository;
import org.whispersystems.signalservice.api.storage.StorageServiceRepository.ManifestIfDifferentVersionResult;

View File

@ -12,6 +12,7 @@ import org.asamk.signal.manager.storage.stickers.StickerPack;
import org.asamk.signal.manager.util.IOUtils;
import org.asamk.signal.manager.util.MimeUtils;
import org.jetbrains.annotations.NotNull;
import org.signal.core.models.ServiceId;
import org.signal.libsignal.protocol.IdentityKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -36,7 +37,6 @@ import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSy
import org.whispersystems.signalservice.api.messages.multidevice.StickerPackOperationMessage;
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
import org.whispersystems.signalservice.api.messages.multidevice.ViewedMessage;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.internal.push.SyncMessage;
@ -52,6 +52,8 @@ import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import okio.ByteString;
public class SyncHelper {
private static final Logger logger = LoggerFactory.getLogger(SyncHelper.class);
@ -406,6 +408,11 @@ public class SyncHelper {
return context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forMessageRequestResponse(response));
}
public SendMessageResult sendDeviceNameChange(final int deviceId) {
final var deviceNameChange = new SyncMessage.DeviceNameChange(deviceId, ByteString.EMPTY);
return context.getSendHelper().sendSyncMessage(SignalServiceSyncMessage.forDeviceNameChange(deviceNameChange));
}
private SendMessageResult requestSyncData(final SyncMessage.Request.Type type) {
var r = new SyncMessage.Request.Builder().type(type).build();
var message = SignalServiceSyncMessage.forRequest(new RequestMessage(r));

View File

@ -2,7 +2,7 @@ package org.asamk.signal.manager.internal;
import org.asamk.signal.manager.helper.AccountFileUpdater;
import org.asamk.signal.manager.storage.accounts.AccountsStore;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.signal.core.models.ServiceId.ACI;
public class AccountFileUpdaterImpl implements AccountFileUpdater {

View File

@ -20,7 +20,7 @@ public class JobExecutor implements AutoCloseable {
public JobExecutor(final Context context) {
this.context = context;
this.executorService = Executors.newCachedThreadPool();
this.executorService = Executors.newVirtualThreadPerTaskExecutor();
}
public void enqueueJob(Job job) {

View File

@ -91,7 +91,11 @@ import org.asamk.signal.manager.util.KeyUtils;
import org.asamk.signal.manager.util.MimeUtils;
import org.asamk.signal.manager.util.PhoneNumberFormatter;
import org.asamk.signal.manager.util.StickerUtils;
import org.signal.core.models.ServiceId;
import org.signal.core.models.ServiceId.ACI;
import org.signal.core.models.ServiceId.PNI;
import org.signal.core.util.Base64;
import org.signal.core.util.Hex;
import org.signal.libsignal.protocol.InvalidMessageException;
import org.signal.libsignal.usernames.BaseUsernameException;
import org.slf4j.Logger;
@ -102,16 +106,12 @@ import org.whispersystems.signalservice.api.messages.SignalServicePreview;
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import org.whispersystems.signalservice.api.push.exceptions.CdsiResourceExhaustedException;
import org.whispersystems.signalservice.api.push.exceptions.UsernameMalformedException;
import org.whispersystems.signalservice.api.push.exceptions.UsernameTakenException;
import org.whispersystems.signalservice.api.util.DeviceNameUtil;
import org.whispersystems.signalservice.api.util.StreamDetails;
import org.whispersystems.signalservice.internal.util.Hex;
import org.whispersystems.signalservice.internal.util.Util;
import java.io.ByteArrayInputStream;
@ -157,7 +157,7 @@ public class ManagerImpl implements Manager {
private final SignalDependencies dependencies;
private final Context context;
private final ExecutorService executor = Executors.newCachedThreadPool();
private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
private Thread receiveThread;
private boolean isReceivingSynchronous;
@ -487,6 +487,21 @@ public class ManagerImpl implements Manager {
}).toList();
}
@Override
public void updateLinkedDevice(
final int deviceId,
final String name
) throws IOException, NotPrimaryDeviceException {
if (deviceId == account.getDeviceId()) {
context.getAccountHelper().setDeviceName(name);
} else {
if (!account.isPrimaryDevice()) {
throw new NotPrimaryDeviceException();
}
context.getAccountHelper().setDeviceName(deviceId, name);
}
}
private Long getPlaintextCreatedAt(DeviceInfo d) {
final var DECRYPTION_INFO = "deviceCreatedAt";
var identityKey = account.getAciIdentityKeyPair().getPrivateKey();
@ -1138,10 +1153,7 @@ public class ManagerImpl implements Manager {
final String nickGivenName,
final String nickFamilyName,
final String note
) throws NotPrimaryDeviceException, UnregisteredRecipientException {
if (!account.isPrimaryDevice()) {
throw new NotPrimaryDeviceException();
}
) throws UnregisteredRecipientException {
context.getContactHelper()
.setContactName(context.getRecipientHelper().resolveRecipient(recipient),
givenName,

View File

@ -33,14 +33,14 @@ import org.asamk.signal.manager.helper.PinHelper;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.util.KeyUtils;
import org.asamk.signal.manager.util.NumberVerificationUtils;
import org.signal.core.models.MasterKey;
import org.signal.core.models.ServiceId.ACI;
import org.signal.core.models.ServiceId.PNI;
import org.signal.libsignal.usernames.BaseUsernameException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.account.PreKeyCollection;
import org.whispersystems.signalservice.api.kbs.MasterKey;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.exceptions.AlreadyVerifiedException;

View File

@ -2,10 +2,10 @@ package org.asamk.signal.manager.jobs;
import org.asamk.signal.manager.api.StickerPackId;
import org.asamk.signal.manager.helper.Context;
import org.signal.core.util.Hex;
import org.signal.libsignal.protocol.InvalidMessageException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.internal.util.Hex;
import java.io.IOException;

View File

@ -16,11 +16,11 @@ import org.asamk.signal.manager.storage.senderKeys.SenderKeyRecordStore;
import org.asamk.signal.manager.storage.senderKeys.SenderKeySharedStore;
import org.asamk.signal.manager.storage.sessions.SessionStore;
import org.asamk.signal.manager.storage.stickers.StickerStore;
import org.signal.core.models.ServiceId;
import org.signal.core.models.ServiceId.ACI;
import org.signal.core.util.UuidUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.File;
import java.sql.Connection;
@ -646,9 +646,10 @@ public class AccountDatabase extends Database {
try (final var preparedStatement = connection.prepareStatement(sql)) {
try (var result = Utils.executeQueryForStream(preparedStatement, (resultSet) -> {
final var pni = Optional.ofNullable(resultSet.getBytes("pni"))
.map(UuidUtil::parseOrNull)
.map(UuidUtil.INSTANCE::parseOrNull)
.map(ServiceId.PNI::from);
final var serviceIdUuid = Optional.ofNullable(resultSet.getBytes("uuid")).map(UuidUtil::parseOrNull);
final var serviceIdUuid = Optional.ofNullable(resultSet.getBytes("uuid"))
.map(UuidUtil.INSTANCE::parseOrNull);
final var serviceId = serviceIdUuid.isPresent() && pni.isPresent() && serviceIdUuid.get()
.equals(pni.get().getRawUuid())
? pni.<ServiceId>map(p -> p)

View File

@ -53,6 +53,14 @@ import org.asamk.signal.manager.storage.stickers.StickerStore;
import org.asamk.signal.manager.storage.threads.LegacyJsonThreadStore;
import org.asamk.signal.manager.util.IOUtils;
import org.asamk.signal.manager.util.KeyUtils;
import org.signal.core.models.AccountEntropyPool;
import org.signal.core.models.MasterKey;
import org.signal.core.models.ServiceId;
import org.signal.core.models.ServiceId.ACI;
import org.signal.core.models.ServiceId.PNI;
import org.signal.core.models.backup.MediaRootBackupKey;
import org.signal.core.models.storageservice.StorageKey;
import org.signal.core.util.UuidUtil;
import org.signal.libsignal.protocol.IdentityKeyPair;
import org.signal.libsignal.protocol.InvalidMessageException;
import org.signal.libsignal.protocol.SignalProtocolAddress;
@ -65,24 +73,16 @@ import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.AccountEntropyPool;
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore;
import org.whispersystems.signalservice.api.SignalServiceDataStore;
import org.whispersystems.signalservice.api.account.AccountAttributes;
import org.whispersystems.signalservice.api.account.PreKeyCollection;
import org.whispersystems.signalservice.api.backup.MediaRootBackupKey;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.kbs.MasterKey;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.push.UsernameLinkComponents;
import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
import org.whispersystems.signalservice.api.storage.StorageKey;
import org.whispersystems.signalservice.api.util.CredentialsProvider;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@ -376,6 +376,7 @@ public class SignalAccount implements Closeable {
trustSelfIdentity(ServiceIdType.ACI);
trustSelfIdentity(ServiceIdType.PNI);
getKeyValueStore().storeEntry(lastRecipientsRefresh, null);
clearSessionId();
}
public void initDatabase() {
@ -961,7 +962,7 @@ public class SignalAccount implements Closeable {
continue;
}
try {
if (UuidUtil.isUuid(thread.id) || thread.id.startsWith("+")) {
if (UuidUtil.INSTANCE.isUuid(thread.id) || thread.id.startsWith("+")) {
final var recipientId = getRecipientResolver().resolveRecipient(thread.id);
var contact = getContactStore().getContact(recipientId);
if (contact != null) {
@ -1485,6 +1486,12 @@ public class SignalAccount implements Closeable {
keyValueStore.storeEntry(verificationSessionId, sessionId);
}
public void clearSessionId() {
final var keyValueStore = getKeyValueStore();
keyValueStore.storeEntry(verificationSessionNumber, null);
keyValueStore.storeEntry(verificationSessionId, null);
}
public void setEncryptedDeviceName(final String encryptedDeviceName) {
this.encryptedDeviceName = encryptedDeviceName;
save();

View File

@ -10,11 +10,11 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.signal.core.models.ServiceId;
import org.signal.core.util.UuidUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.InvalidObjectException;
import java.sql.PreparedStatement;
@ -57,7 +57,7 @@ public class Utils {
}
public static RecipientAddress getRecipientAddressFromLegacyIdentifier(final String identifier) {
if (UuidUtil.isUuid(identifier)) {
if (UuidUtil.INSTANCE.isUuid(identifier)) {
return new RecipientAddress(ServiceId.parseOrThrow(identifier));
} else {
return new RecipientAddress(Optional.empty(), Optional.of(identifier));

View File

@ -8,9 +8,9 @@ import org.asamk.signal.manager.api.ServiceEnvironment;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.Utils;
import org.asamk.signal.manager.util.IOUtils;
import org.signal.core.models.ServiceId.ACI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

View File

@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.signal.core.models.ServiceId.ACI;
import java.util.UUID;

View File

@ -6,13 +6,13 @@ 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.RecipientResolver;
import org.signal.core.models.ServiceId;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.Member;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.EnabledState;
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.EnabledState;
import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.push.ServiceId;
import java.util.Set;
import java.util.stream.Collectors;

View File

@ -11,16 +11,16 @@ import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.recipients.RecipientIdCreator;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.asamk.signal.manager.util.KeyUtils;
import org.signal.core.util.UuidUtil;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.libsignal.zkgroup.groups.GroupSecretParams;
import org.signal.libsignal.zkgroup.groupsend.GroupSendEndorsement;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.storage.StorageId;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.IOException;
import java.sql.Connection;

View File

@ -12,14 +12,14 @@ import org.asamk.signal.manager.api.GroupIdV1;
import org.asamk.signal.manager.api.GroupIdV2;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.signal.core.models.ServiceId;
import org.signal.core.util.Hex;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.internal.util.Hex;
import java.io.File;
import java.io.FileInputStream;

View File

@ -1,8 +1,8 @@
package org.asamk.signal.manager.storage.identities;
import org.asamk.signal.manager.api.TrustLevel;
import org.signal.core.models.ServiceId;
import org.signal.libsignal.protocol.IdentityKey;
import org.whispersystems.signalservice.api.push.ServiceId;
public class IdentityInfo {

View File

@ -5,13 +5,13 @@ import org.asamk.signal.manager.api.TrustNewIdentity;
import org.asamk.signal.manager.storage.Database;
import org.asamk.signal.manager.storage.Utils;
import org.asamk.signal.manager.storage.recipients.RecipientStore;
import org.signal.core.models.ServiceId;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.state.IdentityKeyStore.Direction;
import org.signal.libsignal.protocol.state.IdentityKeyStore.IdentityChange;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.ServiceId;
import java.sql.Connection;
import java.sql.ResultSet;

View File

@ -9,9 +9,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.signal.core.models.ServiceId;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.whispersystems.signalservice.api.push.ServiceId;
import java.io.IOException;
import java.util.ArrayList;

View File

@ -8,13 +8,13 @@ import com.fasterxml.jackson.databind.JsonNode;
import org.asamk.signal.manager.api.TrustLevel;
import org.asamk.signal.manager.storage.Utils;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.signal.core.models.ServiceId.ACI;
import org.signal.core.util.UuidUtil;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.IdentityKeyPair;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.IOException;
import java.util.ArrayList;
@ -99,7 +99,7 @@ public class LegacyJsonIdentityKeyStore {
for (var trustedKey : trustedKeysNode) {
var trustedKeyName = trustedKey.hasNonNull("name") ? trustedKey.get("name").asText() : null;
if (UuidUtil.isUuid(trustedKeyName)) {
if (UuidUtil.INSTANCE.isUuid(trustedKeyName)) {
// Ignore identities that were incorrectly created with UUIDs as name
continue;
}

View File

@ -7,8 +7,8 @@ import com.fasterxml.jackson.databind.JsonNode;
import org.asamk.signal.manager.storage.Utils;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.signal.core.models.ServiceId.ACI;
import org.signal.core.util.UuidUtil;
import java.io.IOException;
import java.util.ArrayList;
@ -41,7 +41,7 @@ public class LegacyJsonSessionStore {
if (node.isArray()) {
for (var session : node) {
var sessionName = session.hasNonNull("name") ? session.get("name").asText() : null;
if (UuidUtil.isUuid(sessionName)) {
if (UuidUtil.INSTANCE.isUuid(sessionName)) {
// Ignore sessions that were incorrectly created with UUIDs as name
continue;
}

View File

@ -7,7 +7,7 @@ import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.signal.core.models.ServiceId;
import java.io.IOException;
import java.util.ArrayList;

View File

@ -3,12 +3,12 @@ package org.asamk.signal.manager.storage.recipients;
import org.asamk.signal.manager.api.Contact;
import org.asamk.signal.manager.api.Profile;
import org.asamk.signal.manager.storage.Utils;
import org.signal.core.models.ServiceId;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.ServiceId;
import java.io.File;
import java.io.FileInputStream;

View File

@ -1,8 +1,8 @@
package org.asamk.signal.manager.storage.recipients;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.signal.core.models.ServiceId;
import org.signal.core.models.ServiceId.ACI;
import org.signal.core.models.ServiceId.PNI;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.util.Optional;

View File

@ -1,6 +1,6 @@
package org.asamk.signal.manager.storage.recipients;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.signal.core.models.ServiceId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.util.function.Supplier;

View File

@ -10,14 +10,14 @@ import org.asamk.signal.manager.storage.Utils;
import org.asamk.signal.manager.storage.contacts.ContactsStore;
import org.asamk.signal.manager.storage.profiles.ProfileStore;
import org.asamk.signal.manager.util.KeyUtils;
import org.signal.core.models.ServiceId;
import org.signal.core.models.ServiceId.ACI;
import org.signal.core.models.ServiceId.PNI;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.storage.StorageId;

View File

@ -1,7 +1,7 @@
package org.asamk.signal.manager.storage.recipients;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.signal.core.models.ServiceId.ACI;
import org.signal.core.models.ServiceId.PNI;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.util.Optional;

View File

@ -4,13 +4,13 @@ import org.asamk.signal.manager.api.GroupId;
import org.asamk.signal.manager.groups.GroupUtils;
import org.asamk.signal.manager.storage.Database;
import org.asamk.signal.manager.storage.Utils;
import org.signal.core.models.ServiceId;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.crypto.ContentHint;
import org.whispersystems.signalservice.api.messages.SendMessageResult;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.internal.push.Content;
import java.io.IOException;

View File

@ -3,14 +3,14 @@ package org.asamk.signal.manager.storage.senderKeys;
import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.storage.Database;
import org.asamk.signal.manager.storage.Utils;
import org.signal.core.models.ServiceId;
import org.signal.core.util.UuidUtil;
import org.signal.libsignal.protocol.InvalidMessageException;
import org.signal.libsignal.protocol.SignalProtocolAddress;
import org.signal.libsignal.protocol.groups.state.SenderKeyRecord;
import org.signal.libsignal.protocol.groups.state.SenderKeyStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.sql.Connection;
import java.sql.ResultSet;

View File

@ -2,12 +2,12 @@ package org.asamk.signal.manager.storage.senderKeys;
import org.asamk.signal.manager.storage.Database;
import org.asamk.signal.manager.storage.Utils;
import org.signal.core.models.ServiceId;
import org.signal.core.util.UuidUtil;
import org.signal.libsignal.protocol.SignalProtocolAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.sql.Connection;
import java.sql.ResultSet;

View File

@ -2,11 +2,11 @@ package org.asamk.signal.manager.storage.senderKeys;
import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.storage.Database;
import org.signal.core.models.ServiceId;
import org.signal.libsignal.protocol.SignalProtocolAddress;
import org.signal.libsignal.protocol.groups.state.SenderKeyRecord;
import org.whispersystems.signalservice.api.SignalServiceSenderKeyStore;
import org.whispersystems.signalservice.api.push.DistributionId;
import org.whispersystems.signalservice.api.push.ServiceId;
import java.util.Collection;
import java.util.Map;

View File

@ -3,6 +3,7 @@ package org.asamk.signal.manager.storage.sessions;
import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.storage.Database;
import org.asamk.signal.manager.storage.Utils;
import org.signal.core.models.ServiceId;
import org.signal.libsignal.protocol.NoSessionException;
import org.signal.libsignal.protocol.SignalProtocolAddress;
import org.signal.libsignal.protocol.ecc.ECPublicKey;
@ -10,7 +11,6 @@ import org.signal.libsignal.protocol.state.SessionRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.SignalServiceSessionStore;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import java.sql.Connection;

View File

@ -5,8 +5,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.asamk.signal.manager.api.StickerPackId;
import org.asamk.signal.manager.util.IOUtils;
import org.asamk.signal.manager.util.Utils;
import org.signal.core.util.Hex;
import org.whispersystems.signalservice.api.util.StreamDetails;
import org.whispersystems.signalservice.internal.util.Hex;
import java.io.BufferedWriter;
import java.io.File;

View File

@ -5,6 +5,7 @@ import org.asamk.signal.manager.internal.JobExecutor;
import org.asamk.signal.manager.jobs.DownloadProfileAvatarJob;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.util.KeyUtils;
import org.signal.core.util.UuidUtil;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.slf4j.Logger;
@ -13,7 +14,6 @@ import org.whispersystems.signalservice.api.push.UsernameLinkComponents;
import org.whispersystems.signalservice.api.storage.IAPSubscriptionId;
import org.whispersystems.signalservice.api.storage.SignalAccountRecord;
import org.whispersystems.signalservice.api.storage.StorageId;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord;
import org.whispersystems.signalservice.internal.storage.protos.OptionalBool;

View File

@ -8,14 +8,14 @@ import org.asamk.signal.manager.jobs.RefreshRecipientsJob;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.manager.util.KeyUtils;
import org.signal.core.models.ServiceId.ACI;
import org.signal.core.models.ServiceId.PNI;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.whispersystems.signalservice.api.storage.SignalContactRecord;
import org.whispersystems.signalservice.api.storage.StorageId;
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord;
@ -63,8 +63,8 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
@Override
protected boolean isInvalid(SignalContactRecord remoteRecord) {
final var remote = remoteRecord.getProto();
final var aci = ACI.parseOrNull(remote.aci);
final var pni = PNI.parseOrNull(remote.pni);
final var aci = ACI.parseOrNull(remote.aci, remote.aciBinary);
final var pni = PNI.parseOrNull(remote.pni, remote.pniBinary);
final var e164 = nullIfEmpty(remote.e164);
boolean hasAci = aci != null && aci.isValid();
boolean hasPni = pni != null && pni.isValid();
@ -129,7 +129,7 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
identityKey = local.identityKey.size() > 0 ? local.identityKey : ByteString.EMPTY;
}
if (!local.aci.isEmpty()
if ((!local.aci.isEmpty() || local.aciBinary.size() > 0)
&& local.identityKey.size() > 0
&& remote.identityKey.size() > 0
&& !local.identityKey.equals(remote.identityKey)) {
@ -327,8 +327,8 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
}
private static RecipientAddress getRecipientAddress(final ContactRecord contactRecord) {
return new RecipientAddress(ACI.parseOrNull(contactRecord.aci),
PNI.parseOrNull(contactRecord.pni),
return new RecipientAddress(ACI.parseOrNull(contactRecord.aci, contactRecord.aciBinary),
PNI.parseOrNull(contactRecord.pni, contactRecord.pniBinary),
nullIfEmpty(contactRecord.e164),
nullIfEmpty(contactRecord.username));
}

View File

@ -1,5 +1,6 @@
package org.asamk.signal.manager.syncStorage;
import org.asamk.signal.manager.api.Contact;
import org.asamk.signal.manager.api.PhoneNumberSharingMode;
import org.asamk.signal.manager.api.TrustLevel;
import org.asamk.signal.manager.storage.configuration.ConfigurationStore;
@ -7,14 +8,14 @@ import org.asamk.signal.manager.storage.groups.GroupInfoV1;
import org.asamk.signal.manager.storage.groups.GroupInfoV2;
import org.asamk.signal.manager.storage.identities.IdentityInfo;
import org.asamk.signal.manager.storage.recipients.Recipient;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.signal.core.models.ServiceId.ACI;
import org.signal.core.models.ServiceId.PNI;
import org.signal.core.util.UuidUtil;
import org.whispersystems.signalservice.api.push.UsernameLinkComponents;
import org.whispersystems.signalservice.api.storage.SignalAccountRecord;
import org.whispersystems.signalservice.api.storage.SignalContactRecord;
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record;
import org.whispersystems.signalservice.api.storage.SignalGroupV2Record;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord;
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord.UsernameLink;
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord;
@ -32,6 +33,9 @@ import static org.signal.core.util.StringExtensionsKt.emptyIfNull;
public final class StorageSyncModels {
private final static boolean useBinaryId = false;
private final static boolean useStringId = true;
private StorageSyncModels() {
}
@ -89,13 +93,19 @@ public final class StorageSyncModels {
public static ContactRecord localToRemoteRecord(Recipient recipient, IdentityInfo identity) {
final var address = recipient.getAddress();
final var builder = SignalContactRecord.Companion.newBuilder(recipient.getStorageRecord())
.aci(address.aci().map(ACI::toString).orElse(""))
.e164(address.number().orElse(""))
.pni(address.pni().map(PNI::toStringWithoutPrefix).orElse(""))
.username(address.username().orElse(""))
.profileKey(recipient.getProfileKey() == null
? ByteString.EMPTY
: ByteString.of(recipient.getProfileKey().serialize()));
if (useBinaryId) {
builder.aciBinary(address.aci().map(ACI::toByteString).orElse(ByteString.EMPTY))
.pniBinary(address.pni().map(PNI::toByteString).orElse(ByteString.EMPTY));
}
if (useStringId) {
builder.aci(address.aci().map(ACI::toString).orElse(""))
.pni(address.pni().map(PNI::toStringWithoutPrefix).orElse(""));
}
if (recipient.getProfile() != null) {
builder.givenName(emptyIfNull(recipient.getProfile().getGivenName()))
.familyName(emptyIfNull(recipient.getProfile().getFamilyName()));
@ -104,10 +114,7 @@ public final class StorageSyncModels {
builder.systemGivenName(emptyIfNull(recipient.getContact().givenName()))
.systemFamilyName(emptyIfNull(recipient.getContact().familyName()))
.systemNickname(emptyIfNull(recipient.getContact().nickName()))
.nickname(new ContactRecord.Name.Builder().given(emptyIfNull(recipient.getContact()
.nickNameGivenName()))
.family(emptyIfNull(recipient.getContact().nickNameFamilyName()))
.build())
.nickname(getNicknameRemoteRecord(recipient.getContact()))
.note(emptyIfNull(recipient.getContact().note()))
.blocked(recipient.getContact().isBlocked())
.whitelisted(recipient.getContact().isProfileSharingEnabled())
@ -126,6 +133,15 @@ public final class StorageSyncModels {
return builder.build();
}
private static ContactRecord.Name getNicknameRemoteRecord(final Contact contact) {
final var given = emptyIfNull(contact.nickNameGivenName());
final var family = emptyIfNull(contact.nickNameFamilyName());
if (given.isEmpty() && family.isEmpty()) {
return null;
}
return new ContactRecord.Name.Builder().given(given).family(family).build();
}
public static GroupV1Record localToRemoteRecord(GroupInfoV1 group) {
final var builder = SignalGroupV1Record.Companion.newBuilder(group.getStorageRecord());
builder.id(ByteString.of(group.getGroupId().serialize()));

View File

@ -1,12 +1,12 @@
package org.asamk.signal.manager.syncStorage;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.signal.core.models.ServiceId.ACI;
import org.signal.core.models.ServiceId.PNI;
import org.signal.core.util.Base64;
import org.signal.core.util.SetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
import org.whispersystems.signalservice.api.storage.StorageId;

View File

@ -1,6 +1,7 @@
package org.asamk.signal.manager.util;
import org.asamk.signal.manager.storage.SignalAccount;
import org.signal.core.models.backup.MediaRootBackupKey;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.IdentityKeyPair;
import org.signal.libsignal.protocol.InvalidKeyException;
@ -14,7 +15,6 @@ import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.whispersystems.signalservice.api.account.PreKeyCollection;
import org.whispersystems.signalservice.api.backup.MediaRootBackupKey;
import java.security.SecureRandom;
import java.util.ArrayList;

View File

@ -1,11 +1,11 @@
package org.asamk.signal.manager.util;
import org.signal.core.models.ServiceId;
import org.signal.core.util.UuidUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.push.Envelope;
import java.io.DataInputStream;

View File

@ -9,9 +9,9 @@ import org.asamk.signal.manager.api.PinLockedException;
import org.asamk.signal.manager.api.RateLimitException;
import org.asamk.signal.manager.api.VerificationMethodNotAvailableException;
import org.asamk.signal.manager.helper.PinHelper;
import org.signal.core.models.MasterKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.kbs.MasterKey;
import org.whispersystems.signalservice.api.push.exceptions.ChallengeRequiredException;
import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException;
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException;

View File

@ -1,6 +1,7 @@
package org.asamk.signal.manager.util;
import org.asamk.signal.manager.api.Pair;
import org.signal.core.models.ServiceId;
import org.signal.libsignal.net.BadRequestError;
import org.signal.libsignal.net.RequestResult;
import org.signal.libsignal.protocol.IdentityKey;
@ -10,7 +11,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.NetworkResult;
import org.whispersystems.signalservice.api.NetworkResultUtil;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.util.StreamDetails;
import java.io.ByteArrayInputStream;
@ -160,9 +160,10 @@ public class Utils {
return NetworkResultUtil.toBasicLegacy(response);
}
@SuppressWarnings("unchecked")
public static <T, E extends BadRequestError> T handleResponseException(final RequestResult<T, E> result) throws IOException {
if (result instanceof RequestResult.Success) {
return ((RequestResult.Success<T>) result).getResult();
if (result instanceof RequestResult.Success<?> success) {
return ((RequestResult.Success<T>) success).getResult();
} else if (result instanceof RequestResult.ApplicationError e) {
final var cause = e.getCause();
switch (cause) {

View File

@ -3,8 +3,8 @@ package org.asamk.signal.manager.storage.recipients;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceId.PNI;
import org.signal.core.models.ServiceId.ACI;
import org.signal.core.models.ServiceId.PNI;
import java.util.Arrays;
import java.util.HashSet;

View File

@ -285,7 +285,7 @@ In json mode this is outputted as a list of objects.
One or more numbers to check.
[--username [USERNAME ...]]::
One or more usernames to check.
One or more usernames or username links to check.
=== send
@ -385,6 +385,89 @@ Clear session state and send end session message.
*--edit-timestamp*::
Specify the timestamp of a previous message with the recipient or group to send an edited message.
=== sendPollCreate
Send a poll create message to another user or group.
RECIPIENT::
Specify the recipients.
*--note-to-self*::
Send the message to self.
*--notify-self*::
If self is part of recipients/groups send a normal message, not a sync message.
*-g* GROUP, *--group-id* GROUP::
Specify the recipient group ID in base64 encoding.
*-u* USERNAME, *--username* USERNAME::
Specify the recipient username or username link.
*-q* QUESTION, *--question* QUESTION::
Specify the poll question.
*--no-multi*::
Allow only one option to be selected by each recipient.
By default, recipients can select multiple options.
*-o* OPTION [OPTION ...], *--option* OPTION [OPTION ...]*::
The options for the poll.
=== sendPollVote
Send a poll vote message to another user or group, to vote on a previously created vote.
RECIPIENT::
Specify the recipients.
*--note-to-self*::
Send the message to self.
*--notify-self*::
If self is part of recipients/groups send a normal message, not a sync message.
*-g* GROUP, *--group-id* GROUP::
Specify the recipient group ID in base64 encoding.
*-u* USERNAME, *--username* USERNAME::
Specify the recipient username or username link.
*--poll-author* POLL_AUTHOR::
Specify the number or uuid of the author of the poll message.
*--poll-timestamp* POLL_TIMESTAMP::
Specify the timestamp of the original poll create message.
*-o* [OPTION [OPTION ...]], *--option* [OPTION [OPTION ...]]::
The option indexes of the poll to vote for.
For none multi polls, only one option may be specified.
*--vote-count: VOTE_COUNT::
Specify the number of this vote (increase by one for every time you vote).
=== sendPollTerminate
Send a poll terminate message to another user or group, to close on a previously created vote.
RECIPIENT::
Specify the recipients.
*--note-to-self*::
Send the message to self.
*--notify-self*::
If self is part of recipients/groups send a normal message, not a sync message.
*-g* GROUP, *--group-id* GROUP::
Specify the recipient group ID in base64 encoding.
*-u* USERNAME, *--username* USERNAME::
Specify the recipient username or username link.
*--poll-timestamp* POLL_TIMESTAMP::
Specify the timestamp of the original poll create message.
=== sendMessageRequestResponse
Send response to a message request to linked devices.
@ -676,6 +759,7 @@ If the contact doesn't exist yet, it will be added.
RECIPIENT::
Specify the recipient.
Use "u:" prefix to specify a username or username link.
*--given-name* GIVEN_NAME, *--name* NAME::
New system given name.
@ -916,6 +1000,9 @@ signal-cli -a ACCOUNT verify CODE
Send a message to one or more recipients::
signal-cli -a ACCOUNT send -m "This is a message" [RECIPIENT [RECIPIENT ...]] [-a [ATTACHMENT [ATTACHMENT ...]]]
Send a message to a username (or username link)::
signal-cli -a ACCOUNT send u:someusername.000 -m "This is a message"
Pipe the message content from another process::
uname -a | signal-cli -a ACCOUNT send --message-from-stdin [RECIPIENT [RECIPIENT ...]]

View File

@ -32,7 +32,7 @@ fi
run() {
# To update graalvm config, set GRAALVM_HOME, e.g:
# export GRAALVM_HOME=/usr/lib/jvm/java-21-graalvm
# export GRAALVM_HOME=/usr/lib/jvm/java-25-graalvm
if [ ! -z "$GRAALVM_HOME" ]; then
export JAVA_HOME=$GRAALVM_HOME
export SIGNAL_CLI_OPTS="-agentlib:native-image-agent=config-merge-dir=graalvm-config-dir-${SIGNAL_CLI_AGENT_ID}/"

View File

@ -1,7 +1,14 @@
dependencyResolutionManagement {
repositories {
mavenLocal()
mavenCentral()
mavenLocal()
maven {
name = "SignalBuildArtifacts"
url = uri("https://build-artifacts.signal.org/libraries/maven/")
content {
includeGroupByRegex("org\\.signal.*")
}
}
}
}

View File

@ -143,9 +143,6 @@ public interface Signal extends DBusInterface {
String avatar
) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.GroupNotFound, Error.InvalidGroupId;
@Deprecated
boolean isRegistered() throws Error.Failure, Error.InvalidNumber;
boolean isRegistered(String number) throws Error.Failure, Error.InvalidNumber;
List<Boolean> isRegistered(List<String> numbers) throws Error.Failure, Error.InvalidNumber;

View File

@ -291,8 +291,6 @@ public class App {
commandHandler.handleMultiLocalCommand(command, multiAccountManager);
} catch (IOException e) {
throw new IOErrorException("Failed to load local accounts file", e);
} catch (AccountCheckException e) {
throw new UnexpectedErrorException("Failed to load account file", e);
}
}

View File

@ -13,10 +13,4 @@ public enum ServiceEnvironmentCli {
return "staging";
}
},
@Deprecated SANDBOX {
@Override
public String toString() {
return "sandbox";
}
},
}

View File

@ -3,12 +3,13 @@ package org.asamk.signal.commands;
import org.asamk.signal.OutputType;
import java.util.List;
import java.util.SequencedCollection;
public interface Command {
String getName();
default List<OutputType> getSupportedOutputTypes() {
default SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT);
}
}

View File

@ -58,6 +58,7 @@ public class Commands {
addCommand(new UpdateAccountCommand());
addCommand(new UpdateConfigurationCommand());
addCommand(new UpdateContactCommand());
addCommand(new UpdateDeviceCommand());
addCommand(new UpdateGroupCommand());
addCommand(new UpdateProfileCommand());
addCommand(new UploadStickerPackCommand());

View File

@ -10,6 +10,7 @@ import org.asamk.signal.ReceiveMessageHandler;
import org.asamk.signal.Shutdown;
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.IOErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.dbus.DbusHandler;
import org.asamk.signal.http.HttpServerHandler;
import org.asamk.signal.json.JsonReceiveMessageHandler;
@ -31,6 +32,7 @@ import java.nio.channels.Channel;
import java.nio.channels.ServerSocketChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.SequencedCollection;
import static org.asamk.signal.util.CommandUtil.getReceiveConfig;
@ -86,7 +88,7 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand {
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}
@ -201,9 +203,7 @@ public class DaemonCommand implements MultiLocalCommand, LocalCommand {
&& tcpAddress == null
&& httpAddress == null
&& inheritedChannel == null) {
logger.warn(
"Running daemon command without explicit mode is deprecated. Use 'daemon --dbus' to use the dbus interface.");
daemonHandler.runDbus(false, DbusConfig.getBusname());
throw new UserErrorException("At least one channel parameter is required, e.g. --socket or --dbus.");
}
}

View File

@ -16,6 +16,7 @@ import org.asamk.signal.output.JsonWriter;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.SequencedCollection;
public class DeleteLocalAccountDataCommand implements RegistrationCommand, JsonRpcRegistrationCommand<Map<String, Object>> {
@ -54,7 +55,7 @@ public class DeleteLocalAccountDataCommand implements RegistrationCommand, JsonR
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}

View File

@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
import org.asamk.signal.OutputType;
import java.util.List;
import java.util.SequencedCollection;
public interface JsonRpcCommand<T> extends Command {
@ -12,7 +13,7 @@ public interface JsonRpcCommand<T> extends Command {
return null;
}
default List<OutputType> getSupportedOutputTypes() {
default SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.JSON);
}
}

View File

@ -21,6 +21,7 @@ import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.channels.Channels;
import java.util.List;
import java.util.SequencedCollection;
import java.util.function.Supplier;
import static org.asamk.signal.util.CommandUtil.getReceiveConfig;
@ -53,7 +54,7 @@ public class JsonRpcDispatcherCommand implements LocalCommand, MultiLocalCommand
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.JSON);
}

View File

@ -11,6 +11,7 @@ import org.asamk.signal.output.JsonWriter;
import java.util.List;
import java.util.Map;
import java.util.SequencedCollection;
public interface JsonRpcLocalCommand extends JsonRpcSingleCommand<Map<String, Object>>, LocalCommand {
@ -23,7 +24,7 @@ public interface JsonRpcLocalCommand extends JsonRpcSingleCommand<Map<String, Ob
handleCommand(commandNamespace, m, jsonWriter);
}
default List<OutputType> getSupportedOutputTypes() {
default SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}
}

View File

@ -11,6 +11,7 @@ import org.asamk.signal.output.JsonWriter;
import java.util.List;
import java.util.Map;
import java.util.SequencedCollection;
public interface JsonRpcMultiLocalCommand extends JsonRpcMultiCommand<Map<String, Object>>, MultiLocalCommand {
@ -27,7 +28,7 @@ public interface JsonRpcMultiLocalCommand extends JsonRpcMultiCommand<Map<String
handleCommand(commandNamespace, c, jsonWriter);
}
default List<OutputType> getSupportedOutputTypes() {
default SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}
}

View File

@ -27,6 +27,7 @@ import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.SequencedCollection;
public class ReceiveCommand implements LocalCommand, JsonRpcSingleCommand<ReceiveCommand.ReceiveParams> {
@ -60,7 +61,7 @@ public class ReceiveCommand implements LocalCommand, JsonRpcSingleCommand<Receiv
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}

View File

@ -21,6 +21,7 @@ import org.asamk.signal.util.CommandUtil;
import java.io.IOException;
import java.util.List;
import java.util.SequencedCollection;
public class RegisterCommand implements RegistrationCommand, JsonRpcRegistrationCommand<RegisterCommand.RegistrationParams> {
@ -57,7 +58,7 @@ public class RegisterCommand implements RegistrationCommand, JsonRpcRegistration
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}

View File

@ -7,7 +7,6 @@ import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.IOErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.api.NotPrimaryDeviceException;
import org.asamk.signal.manager.api.UnregisteredRecipientException;
import org.asamk.signal.output.OutputWriter;
import org.asamk.signal.util.CommandUtil;
@ -63,8 +62,6 @@ public class UpdateContactCommand implements JsonRpcLocalCommand {
m.setContactName(recipient, givenName, familyName, nickGivenName, nickFamilyName, note);
} catch (IOException e) {
throw new IOErrorException("Update contact error: " + e.getMessage(), e);
} catch (NotPrimaryDeviceException e) {
throw new UserErrorException("This command doesn't work on linked devices.");
} catch (UnregisteredRecipientException e) {
throw new UserErrorException("The user " + e.getSender().getIdentifier() + " is not registered.");
}

View File

@ -0,0 +1,50 @@
package org.asamk.signal.commands;
import net.sourceforge.argparse4j.inf.Namespace;
import net.sourceforge.argparse4j.inf.Subparser;
import org.asamk.signal.commands.exceptions.CommandException;
import org.asamk.signal.commands.exceptions.IOErrorException;
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.api.NotPrimaryDeviceException;
import org.asamk.signal.output.OutputWriter;
import java.io.IOException;
public class UpdateDeviceCommand implements JsonRpcLocalCommand {
@Override
public String getName() {
return "updateDevice";
}
@Override
public void attachToSubparser(final Subparser subparser) {
subparser.help("Update a linked device.");
subparser.addArgument("-d", "--device-id", "--deviceId")
.type(int.class)
.required(true)
.help("Specify the device you want to update. Use listDevices to see the deviceIds.");
subparser.addArgument("-n", "--device-name")
.required(true)
.help("Specify a name to describe the given device.");
}
@Override
public void handleCommand(
final Namespace ns,
final Manager m,
final OutputWriter outputWriter
) throws CommandException {
try {
final var deviceId = ns.getInt("device-id");
final var deviceName = ns.getString("device-name");
m.updateLinkedDevice(deviceId, deviceName);
} catch (NotPrimaryDeviceException e) {
throw new UserErrorException("This command doesn't work on linked devices.");
} catch (IOException e) {
throw new IOErrorException("Error while updating device: " + e.getMessage(), e);
}
}
}

View File

@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.SequencedCollection;
public class VerifyCommand implements RegistrationCommand, JsonRpcRegistrationCommand<VerifyCommand.VerifyParams> {
@ -50,7 +51,7 @@ public class VerifyCommand implements RegistrationCommand, JsonRpcRegistrationCo
}
@Override
public List<OutputType> getSupportedOutputTypes() {
public SequencedCollection<OutputType> getSupportedOutputTypes() {
return List.of(OutputType.PLAIN_TEXT, OutputType.JSON);
}

Some files were not shown because too many files have changed in this diff Show More