diff --git a/CHANGELOG.md b/CHANGELOG.md index 80ed008df..524ac160f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## [Unreleased] +Requires libsignal-client version 0.58.2 + ## [0.13.7] - 2024-09-28 Requires libsignal-client version 0.58.0 @@ -706,7 +708,8 @@ Requires libsignal-client version 0.52.2 ### Added - New parameters for `updateGroup` command for group v2 features: - `--description`, `--remove-member`, `--admin`, `--remove-admin`, `--reset-link`, `--link`, `--set-permission-add-member`, `--set-permission-edit-details`, `--expiration` + `--description`, `--remove-member`, `--admin`, `--remove-admin`, `--reset-link`, `--link`, + `--set-permission-add-member`, `--set-permission-edit-details`, `--expiration` - New `--admin` parameter for `quitGroup` to set an admin before leaving the group - New `--delete` parameter for `quitGroup`, to delete the local group data - New 'sendTyping' command to send typing indicators diff --git a/graalvm-config-dir/reflect-config.json b/graalvm-config-dir/reflect-config.json index 2ae5c4522..62b89b78f 100644 --- a/graalvm-config-dir/reflect-config.json +++ b/graalvm-config-dir/reflect-config.json @@ -39,6 +39,24 @@ { "name":"[Ljava.sql.Statement;" }, +{ + "name":"[Lorg.asamk.signal.json.JsonAttachment;" +}, +{ + "name":"[Lorg.asamk.signal.json.JsonMention;" +}, +{ + "name":"[Lorg.asamk.signal.json.JsonPreview;" +}, +{ + "name":"[Lorg.asamk.signal.json.JsonQuotedAttachment;" +}, +{ + "name":"[Lorg.asamk.signal.json.JsonTextStyle;" +}, +{ + "name":"[Lorg.asamk.signal.manager.storage.accounts.AccountsStorage$Account;" +}, { "name":"[Lorg.whispersystems.signalservice.api.groupsv2.TemporalCredential;" }, @@ -371,6 +389,11 @@ "allDeclaredFields":true, "allDeclaredMethods":true }, +{ + "name":"java.util.AbstractMap", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, { "name":"java.util.ArrayList", "allDeclaredMethods":true, @@ -385,6 +408,39 @@ "allDeclaredMethods":true, "allDeclaredConstructors":true }, +{ + "name":"java.util.ImmutableCollections$AbstractImmutableCollection", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.ImmutableCollections$AbstractImmutableList", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.ImmutableCollections$AbstractImmutableMap", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"java.util.ImmutableCollections$ListN", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.util.ImmutableCollections$Map1", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, +{ + "name":"java.util.ImmutableCollections$MapN", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, { "name":"java.util.LinkedHashMap", "allDeclaredMethods":true, @@ -399,7 +455,8 @@ "methods":[{"name":"getUnicodeLocaleType","parameterTypes":["java.lang.String"] }] }, { - "name":"java.util.Map" + "name":"java.util.Map", + "queryAllDeclaredMethods":true }, { "name":"java.util.Optional", @@ -887,18 +944,21 @@ "name":"org.asamk.signal.json.JsonContact", "allDeclaredFields":true, "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, "methods":[{"name":"color","parameterTypes":[] }, {"name":"familyName","parameterTypes":[] }, {"name":"givenName","parameterTypes":[] }, {"name":"internal","parameterTypes":[] }, {"name":"isBlocked","parameterTypes":[] }, {"name":"isHidden","parameterTypes":[] }, {"name":"messageExpirationTime","parameterTypes":[] }, {"name":"name","parameterTypes":[] }, {"name":"nickFamilyName","parameterTypes":[] }, {"name":"nickGivenName","parameterTypes":[] }, {"name":"nickName","parameterTypes":[] }, {"name":"note","parameterTypes":[] }, {"name":"number","parameterTypes":[] }, {"name":"profile","parameterTypes":[] }, {"name":"profileSharing","parameterTypes":[] }, {"name":"unregistered","parameterTypes":[] }, {"name":"username","parameterTypes":[] }, {"name":"uuid","parameterTypes":[] }] }, { "name":"org.asamk.signal.json.JsonContact$JsonInternal", "allDeclaredFields":true, "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, "methods":[{"name":"capabilities","parameterTypes":[] }, {"name":"discoverableByPhonenumber","parameterTypes":[] }, {"name":"sharesPhoneNumber","parameterTypes":[] }, {"name":"unidentifiedAccessMode","parameterTypes":[] }] }, { "name":"org.asamk.signal.json.JsonContact$JsonProfile", "allDeclaredFields":true, "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, "methods":[{"name":"about","parameterTypes":[] }, {"name":"aboutEmoji","parameterTypes":[] }, {"name":"familyName","parameterTypes":[] }, {"name":"givenName","parameterTypes":[] }, {"name":"hasAvatar","parameterTypes":[] }, {"name":"lastUpdateTimestamp","parameterTypes":[] }, {"name":"mobileCoinAddress","parameterTypes":[] }] }, { @@ -2160,6 +2220,9 @@ "name":"org.signal.storageservice.protos.groups.local.DecryptedTimer", "fields":[{"name":"duration_"}] }, +{ + "name":"org.slf4j.Logger" +}, { "name":"org.sqlite.JDBC" }, @@ -2557,6 +2620,7 @@ "allDeclaredFields":true, "allDeclaredClasses":true, "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, "methods":[{"name":"accountAttributes","parameterTypes":[] }, {"name":"aciPqLastResortPreKey","parameterTypes":[] }, {"name":"aciSignedPreKey","parameterTypes":[] }, {"name":"pniPqLastResortPreKey","parameterTypes":[] }, {"name":"pniSignedPreKey","parameterTypes":[] }, {"name":"verificationCode","parameterTypes":[] }] }, { diff --git a/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java b/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java index 5163c8fc2..ba18c63c2 100644 --- a/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java +++ b/lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java @@ -168,12 +168,13 @@ public void startChangeNumber( String newNumber, boolean voiceVerification, String captcha ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethodNotAvailableException { final var accountManager = dependencies.createUnauthenticatedAccountManager(newNumber, account.getPassword()); - String sessionId = NumberVerificationUtils.handleVerificationSession(accountManager, + final var registrationApi = accountManager.getRegistrationApi(); + String sessionId = NumberVerificationUtils.handleVerificationSession(registrationApi, account.getSessionId(newNumber), id -> account.setSessionId(newNumber, id), voiceVerification, captcha); - NumberVerificationUtils.requestVerificationCode(accountManager, sessionId, voiceVerification); + NumberVerificationUtils.requestVerificationCode(registrationApi, sessionId, voiceVerification); } public void finishChangeNumber( @@ -280,13 +281,13 @@ private void finishChangeNumberInternal( pin, context.getPinHelper(), (sessionId1, verificationCode1, registrationLock) -> { - final var accountManager = dependencies.getAccountManager(); + final var registrationApi = dependencies.getRegistrationApi(); try { - Utils.handleResponseException(accountManager.verifyAccount(verificationCode1, sessionId1)); + Utils.handleResponseException(registrationApi.verifyAccount(verificationCode1, sessionId1)); } catch (AlreadyVerifiedException e) { // Already verified so can continue changing number } - return Utils.handleResponseException(accountManager.changeNumber(new ChangePhoneNumberRequest( + return Utils.handleResponseException(registrationApi.changeNumber(new ChangePhoneNumberRequest( sessionId1, null, newNumber, diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java b/lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java index 015d17a00..eaaba787c 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/RegistrationManagerImpl.java @@ -129,14 +129,13 @@ public void register( return; } - String sessionId = NumberVerificationUtils.handleVerificationSession(unauthenticatedAccountManager, + final var registrationApi = unauthenticatedAccountManager.getRegistrationApi(); + String sessionId = NumberVerificationUtils.handleVerificationSession(registrationApi, account.getSessionId(account.getNumber()), id -> account.setSessionId(account.getNumber(), id), voiceVerification, captcha); - NumberVerificationUtils.requestVerificationCode(unauthenticatedAccountManager, - sessionId, - voiceVerification); + NumberVerificationUtils.requestVerificationCode(registrationApi, sessionId, voiceVerification); account.setRegistered(false); } catch (DeprecatedVersionException e) { logger.debug("Signal-Server returned deprecated version exception", e); @@ -196,7 +195,8 @@ private boolean attemptReregisterAccount(final String recoveryPassword) { final var aciPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.ACI)); final var pniPreKeys = generatePreKeysForType(account.getAccountData(ServiceIdType.PNI)); - final var response = Utils.handleResponseException(unauthenticatedAccountManager.registerAccount(null, + final var registrationApi = unauthenticatedAccountManager.getRegistrationApi(); + final var response = Utils.handleResponseException(registrationApi.registerAccount(null, recoveryPassword, account.getAccountAttributes(null), aciPreKeys, @@ -256,12 +256,13 @@ private VerifyAccountResponse verifyAccountWithCode( final PreKeyCollection aciPreKeys, final PreKeyCollection pniPreKeys ) throws IOException { + final var registrationApi = unauthenticatedAccountManager.getRegistrationApi(); try { - Utils.handleResponseException(unauthenticatedAccountManager.verifyAccount(verificationCode, sessionId)); + Utils.handleResponseException(registrationApi.verifyAccount(verificationCode, sessionId)); } catch (AlreadyVerifiedException e) { // Already verified so can continue registering } - return Utils.handleResponseException(unauthenticatedAccountManager.registerAccount(sessionId, + return Utils.handleResponseException(registrationApi.registerAccount(sessionId, null, account.getAccountAttributes(registrationLock), aciPreKeys, diff --git a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java index f4f72aca6..7ee2b39e9 100644 --- a/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java +++ b/lib/src/main/java/org/asamk/signal/manager/internal/SignalDependencies.java @@ -17,6 +17,7 @@ import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; import org.whispersystems.signalservice.api.push.ServiceIdType; import org.whispersystems.signalservice.api.push.SignalServiceAddress; +import org.whispersystems.signalservice.api.registration.RegistrationApi; import org.whispersystems.signalservice.api.services.ProfileService; import org.whispersystems.signalservice.api.svr.SecureValueRecovery; import org.whispersystems.signalservice.api.util.CredentialsProvider; @@ -47,6 +48,7 @@ public class SignalDependencies { private SignalServiceAccountManager accountManager; private GroupsV2Api groupsV2Api; + private RegistrationApi registrationApi; private GroupsV2Operations groupsV2Operations; private ClientZkOperations clientZkOperations; @@ -80,8 +82,14 @@ public void resetAfterAddressChange() { if (this.pushServiceSocket != null) { this.pushServiceSocket.close(); this.pushServiceSocket = null; + this.accountManager = null; + this.messageReceiver = null; + this.messageSender = null; + this.profileService = null; + this.groupsV2Api = null; + this.registrationApi = null; + this.secureValueRecovery = null; } - this.messageSender = null; getSignalWebSocket().forceNewWebSockets(); } @@ -143,6 +151,10 @@ public GroupsV2Api getGroupsV2Api() { return getOrCreate(() -> groupsV2Api, () -> groupsV2Api = getAccountManager().getGroupsV2Api()); } + public RegistrationApi getRegistrationApi() { + return getOrCreate(() -> registrationApi, () -> registrationApi = getAccountManager().getRegistrationApi()); + } + public GroupsV2Operations getGroupsV2Operations() { return getOrCreate(() -> groupsV2Operations, () -> groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.signalServiceConfiguration()), diff --git a/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java b/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java index f96e79d37..1c5aa4117 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/NumberVerificationUtils.java @@ -10,14 +10,15 @@ import org.asamk.signal.manager.helper.PinHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.signalservice.api.SignalServiceAccountManager; +import org.whispersystems.signalservice.api.NetworkResult; import org.whispersystems.signalservice.api.kbs.MasterKey; import org.whispersystems.signalservice.api.push.exceptions.NoSuchSessionException; import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException; import org.whispersystems.signalservice.api.push.exceptions.PushChallengeRequiredException; import org.whispersystems.signalservice.api.push.exceptions.TokenNotAcceptedException; -import org.whispersystems.signalservice.internal.ServiceResponse; +import org.whispersystems.signalservice.api.registration.RegistrationApi; import org.whispersystems.signalservice.internal.push.LockedException; +import org.whispersystems.signalservice.internal.push.PushServiceSocket.VerificationCodeTransport; import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse; import org.whispersystems.signalservice.internal.push.VerifyAccountResponse; @@ -30,7 +31,7 @@ public class NumberVerificationUtils { private static final Logger logger = LoggerFactory.getLogger(NumberVerificationUtils.class); public static String handleVerificationSession( - SignalServiceAccountManager accountManager, + RegistrationApi registrationApi, String sessionId, Consumer sessionIdSaver, boolean voiceVerification, @@ -38,11 +39,11 @@ public static String handleVerificationSession( ) throws CaptchaRequiredException, IOException, RateLimitException, VerificationMethodNotAvailableException { RegistrationSessionMetadataResponse sessionResponse; try { - sessionResponse = getValidSession(accountManager, sessionId); + sessionResponse = getValidSession(registrationApi, sessionId); } catch (PushChallengeRequiredException | org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException e) { if (captcha != null) { - sessionResponse = submitCaptcha(accountManager, sessionId, captcha); + sessionResponse = submitCaptcha(registrationApi, sessionId, captcha); } else { throw new CaptchaRequiredException("Captcha Required"); } @@ -77,7 +78,7 @@ public static String handleVerificationSession( if (sessionResponse.getBody().getRequestedInformation().contains("captcha")) { if (captcha != null) { - sessionResponse = submitCaptcha(accountManager, sessionId, captcha); + sessionResponse = submitCaptcha(registrationApi, sessionId, captcha); } if (!sessionResponse.getBody().getAllowedToRequestCode()) { throw new CaptchaRequiredException("Captcha Required"); @@ -88,14 +89,20 @@ public static String handleVerificationSession( } public static void requestVerificationCode( - SignalServiceAccountManager accountManager, String sessionId, boolean voiceVerification + RegistrationApi registrationApi, String sessionId, boolean voiceVerification ) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException { - final ServiceResponse response; + final NetworkResult response; final var locale = Utils.getDefaultLocale(Locale.US); if (voiceVerification) { - response = accountManager.requestVoiceVerificationCode(sessionId, locale, false); + response = registrationApi.requestSmsVerificationCode(sessionId, + locale, + false, + VerificationCodeTransport.VOICE); } else { - response = accountManager.requestSmsVerificationCode(sessionId, locale, false); + response = registrationApi.requestSmsVerificationCode(sessionId, + locale, + false, + VerificationCodeTransport.SMS); } try { Utils.handleResponseException(response); @@ -140,37 +147,37 @@ public static Pair verifyNumber( } private static RegistrationSessionMetadataResponse validateSession( - final SignalServiceAccountManager accountManager, final String sessionId + final RegistrationApi registrationApi, final String sessionId ) throws IOException { if (sessionId == null || sessionId.isEmpty()) { throw new NoSuchSessionException(); } - return Utils.handleResponseException(accountManager.getRegistrationSession(sessionId)); + return Utils.handleResponseException(registrationApi.getRegistrationSessionStatus(sessionId)); } private static RegistrationSessionMetadataResponse requestValidSession( - final SignalServiceAccountManager accountManager + final RegistrationApi registrationApi ) throws IOException { - return Utils.handleResponseException(accountManager.createRegistrationSession(null, "", "")); + return Utils.handleResponseException(registrationApi.createRegistrationSession(null, "", "")); } private static RegistrationSessionMetadataResponse getValidSession( - final SignalServiceAccountManager accountManager, final String sessionId + final RegistrationApi registrationApi, final String sessionId ) throws IOException { try { - return validateSession(accountManager, sessionId); + return validateSession(registrationApi, sessionId); } catch (NoSuchSessionException e) { logger.debug("No registration session, creating new one."); - return requestValidSession(accountManager); + return requestValidSession(registrationApi); } } private static RegistrationSessionMetadataResponse submitCaptcha( - SignalServiceAccountManager accountManager, String sessionId, String captcha + RegistrationApi registrationApi, String sessionId, String captcha ) throws IOException, CaptchaRequiredException { captcha = captcha == null ? null : captcha.replace("signalcaptcha://", ""); try { - return Utils.handleResponseException(accountManager.submitCaptchaToken(sessionId, captcha)); + return Utils.handleResponseException(registrationApi.submitCaptchaToken(sessionId, captcha)); } catch (PushChallengeRequiredException | org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException | TokenNotAcceptedException _e) { diff --git a/lib/src/main/java/org/asamk/signal/manager/util/Utils.java b/lib/src/main/java/org/asamk/signal/manager/util/Utils.java index e6d8e4d30..16aa32755 100644 --- a/lib/src/main/java/org/asamk/signal/manager/util/Utils.java +++ b/lib/src/main/java/org/asamk/signal/manager/util/Utils.java @@ -6,9 +6,9 @@ import org.signal.libsignal.protocol.fingerprint.NumericFingerprintGenerator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.signalservice.api.NetworkResult; import org.whispersystems.signalservice.api.push.ServiceId; import org.whispersystems.signalservice.api.util.StreamDetails; -import org.whispersystems.signalservice.internal.ServiceResponse; import java.io.ByteArrayInputStream; import java.io.File; @@ -140,15 +140,15 @@ public static Map getQueryMap(String query) { return map; } - public static T handleResponseException(final ServiceResponse response) throws IOException { - final var throwableOptional = response.getExecutionError().or(response::getApplicationError); - if (throwableOptional.isPresent()) { - if (throwableOptional.get() instanceof IOException) { - throw (IOException) throwableOptional.get(); + public static T handleResponseException(final NetworkResult response) throws IOException { + final var throwableOptional = response.getCause(); + if (throwableOptional != null) { + if (throwableOptional instanceof IOException ioException) { + throw ioException; } else { - throw new IOException(throwableOptional.get()); + throw new IOException(throwableOptional); } } - return response.getResult().orElse(null); + return response.successOrThrow(); } } diff --git a/settings.gradle.kts b/settings.gradle.kts index d06a1e1dc..ef9ec4865 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,19 +7,19 @@ dependencyResolutionManagement { versionCatalogs { create("libs") { library("bouncycastle", "org.bouncycastle", "bcprov-jdk18on").version("1.78.1") - library("jackson.databind", "com.fasterxml.jackson.core", "jackson-databind").version("2.17.2") + library("jackson.databind", "com.fasterxml.jackson.core", "jackson-databind").version("2.18.0") library("argparse4j", "net.sourceforge.argparse4j", "argparse4j").version("0.9.0") library("dbusjava", "com.github.hypfvieh", "dbus-java-transport-native-unixsocket").version("5.0.0") version("slf4j", "2.0.16") library("slf4j.api", "org.slf4j", "slf4j-api").versionRef("slf4j") library("slf4j.jul", "org.slf4j", "jul-to-slf4j").versionRef("slf4j") - library("logback", "ch.qos.logback", "logback-classic").version("1.5.8") + library("logback", "ch.qos.logback", "logback-classic").version("1.5.11") - library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_109") - library("sqlite", "org.xerial", "sqlite-jdbc").version("3.46.1.0") - library("hikari", "com.zaxxer", "HikariCP").version("5.1.0") - library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").version("5.11.0") - library("junit.launcher", "org.junit.platform", "junit-platform-launcher").version("1.11.0") + library("signalservice", "com.github.turasa", "signal-service-java").version("2.15.3_unofficial_110") + library("sqlite", "org.xerial", "sqlite-jdbc").version("3.47.0.0") + library("hikari", "com.zaxxer", "HikariCP").version("6.0.0") + library("junit.jupiter", "org.junit.jupiter", "junit-jupiter").version("5.11.3") + library("junit.launcher", "org.junit.platform", "junit-platform-launcher").version("1.11.3") } } } diff --git a/src/main/java/org/asamk/signal/BaseConfig.java b/src/main/java/org/asamk/signal/BaseConfig.java index ae85882c0..7b182351d 100644 --- a/src/main/java/org/asamk/signal/BaseConfig.java +++ b/src/main/java/org/asamk/signal/BaseConfig.java @@ -8,7 +8,7 @@ public class BaseConfig { public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion(); static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT")) - .orElse("Signal-Android/7.18.2"); + .orElse("Signal-Android/7.21.4"); static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null ? "signal-cli" : PROJECT_NAME + "/" + PROJECT_VERSION;