runs-on: ubuntu-latest
strategy:
matrix:
- java: [ '11', '16' ]
+ java: [ '17' ]
steps:
- uses: actions/checkout@v1
- name: Setup Java JDK
uses: actions/setup-java@v1
with:
- java-version: 11
+ java-version: 17
- name: Checkout repository
uses: actions/checkout@v2
<emptyLine />
</value>
</option>
+ <option name="RECORD_COMPONENTS_WRAP" value="5" />
+ <option name="NEW_LINE_AFTER_LPAREN_IN_RECORD_HEADER" value="true" />
+ <option name="RPAREN_ON_NEW_LINE_IN_RECORD_HEADER" value="true" />
<option name="JD_P_AT_EMPTY_LINES" value="false" />
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
- <XML>
- <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
- </XML>
<codeStyleSettings language="JAVA">
<option name="RIGHT_MARGIN" value="120" />
<option name="KEEP_LINE_BREAKS" value="false" />
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
<option name="TERNARY_OPERATION_WRAP" value="5" />
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
+ <option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
<option name="ENUM_CONSTANTS_WRAP" value="2" />
</codeStyleSettings>
<codeStyleSettings language="XML">
# Changelog
## [Unreleased]
+**Attention**: Now requires Java 17
+
+## [0.9.2] - 2021-10-24
+### Fixed
+- dbus `listNumbers` method works again
+
+### Changed
+- Improved provisioning error handling if the last steps fail
+- Adapt behavior of receive command as dbus client to match normal mode
+- Update captcha url for proof required handling
+
+## [0.9.1] - 2021-10-16
+**Attention**: Now requires native libzkgroup version 0.8
+
+### Added
+- New command `updateConfiguration` which allows setting configurations for linked devices
+- Improved dbus daemon for group handling, groups are now exported as separate dbus objects
+- Linked devices can be managed via dbus
+- New dbus methods sendTyping and sendReadReceipt (Thanks @JtheSaw)
+- New dbus methods submitRateLimitChallenge, isRegistered, listDevices, setExpirationTimer, sendContacts, sendSyncRequest, uploadStickerPack, setPin and removePin (Thanks @John Freed)
+- New dbus method getSelfNumber
+
+### Fixed
+- Do not send message resend request to own device
+- Allow message from pending member to accept group invitations
+- Fix issue which could cause signal-cli to repeatedly send the same delivery receipts
+- Reconnect websocket after connection loss
+
+### Changed
+- Use new provisioning URL `sgnl://linkdevice` instead of `tsdevice:/`
+- The gradle command to build a graalvm native image is now `./gradlew nativeCompile`
## [0.9.0] - 2021-09-12
**Attention**: Now requires native libsignal-client version 0.9
### Fixed
- Issue where some messages were sent with an old counter index
-## Older
-
-Look at the [release tags](https://github.com/AsamK/signal-cli/releases) for information about older releases.
+## [0.6.11] - 2020-10-14
+- Fix issue with receiving message reactions
+
+## [0.6.10] - 2020-09-11
+- Fix issue when retrieving profiles
+- Workaround issue with libzkgroup on platforms other than linux x86_64
+
+## [0.6.9] - 2020-09-10
+- Minor bug fixes and improvements
+- dbus functionality now works on FreeBSD
+- signal-cli now requires Java 11
+
+**Warning: this version only works on Linux x86_64, will be fixed in 0.6.10**
+
+## [0.6.8] - 2020-05-22
+- Switch to hypfvieh dbus-java, which doesn't require a native library anymore (drops requirement of libmatthew-unix-java)
+- Bugfixes for messages with uuids
+- Add `--expiration` parameter to `updateContact` command to set expiration timer
+
+## [0.6.7] - 2020-04-03
+- Send command now returns the timestamp of the sent message
+- DBus daemon: Publish received sync message to SyncMessageReceived signal
+- Fix issue with resolving e164/uuid addresses for sessions
+- Fix pack key length for sticker upload
+
+## [0.6.6] - 2020-03-29
+- Added listContacts command
+- Added block/unblock commands to block contacts and groups
+- Added uploadStickerPack command to upload sticker packs (see man page for more details)
+- Full support for sending and receiving unidentified sender messages
+- Support for message reactions with emojis
+- Internal: support recipients with uuids
+
+## [0.6.5] - 2019-11-11
+Supports receiving messages sent with unidentified sender
+
+## [0.6.4] - 2019-11-02
+- Fix rounding error for attachment ids in json output
+- Add additional info to json output
+- Add commands to update profile name and avatar
+- Add command to update contact names
+
+## [0.6.3] - 2019-09-05
+Bug fixes and small improvements
+
+## [0.6.2] - 2018-12-16
+- Fixes sending of group messages
+
+## [0.6.1] - 2018-12-09
+- Added getGroupIds dbus command
+- Use "NativePRNG" pseudo random number generator, if available
+- Switch default data path:
+ `$XDG_DATA_HOME/signal-cli` (`$HOME/.local/share/signal-cli`)
+ Existing data paths will continue to work (used as fallback)
+
+## [0.6.0] - 2018-05-03
+- Simple json output
+- dbus signal for receiving messages
+- Registration lock PIN
+- Output quoted message
+
+## [0.5.6] - 2017-06-16
+* new listGroups command
+* Support for attachments with file names
+* Support for complete contacts sync
+* Support for contact verification sync
+* DBus interface:
+ * Get/Set group info
+ * Get/Set contact info
+
+## [0.5.5] - 2017-02-18
+- fix receiving messages on linked devices
+- add unregister command
+
+## [0.5.4] - 2017-02-17
+- Fix linking of new devices
+
+## [0.5.3] - 2017-01-29
+* New commandline paramter for receive: --ignore-attachments
+* Updated dependencies
+
+## [0.5.2] - 2016-12-16
+- Add support for group info requests
+- Improve closing of file streams
+
+## [0.5.1] - 2016-11-18
+- Support new safety numbers (https://whispersystems.org/blog/safety-number-updates/)
+- Add a man page
+- Support sending disappearing messages, if the recipient has activated it
+
+## [0.5.0] - 2016-08-29
+- Check if a number is registered on Signal, before adding it to a group
+- Prevent sending to groups that the user has quit
+- Commands to trust new identity keys (see README)
+- Messages from untrusted identities are stored on disk and decrypted when the user trusts the identity
+- Timestamps shown in ISO 8601 format
+
+## [0.4.1] - 2016-07-18
+- Fix issue with creating groups
+- Lock config file to prevent parallel access by multiple instances of signal-cli
+- Improve return codes, always return non-zero code, when sending failed
+
+## [0.4.0] - 2016-06-19
+- Linking to Signal-Desktop and Signal-Android is now possible (Provisioning)
+- Added a contact store, mainly for syncing contacts with linked devices (editing not yet possible via cli)
+- Avatars for groups and contacts are now stored (new folder "avatars" in the config path)
+
+## [0.3.1] - 2016-04-03
+- Fix running with Oracle JRE 8
+- Fix registering
+- Fix unicode warning when compiling with non utf8 locale
+
+## [0.3.0] - 2016-04-02
+- Renamed textsecure-cli to signal-cli, following the rename of libtextsecure-java to libsignal-service-java
+- The experimental dbus interface was also renamed to org.asamk.Signal
+- Upload new prekeys to the server, when there are less than 20 left, prekeys are needed to create new sessions
+
+## [0.2.1] - 2016-02-10
+- Improve dbus service
+- New command line argument --config to specify config directory
+
+## [0.2.0] - 2015-12-30
+Added an experimental dbus interface, for sending and receiving messages (The interface is unstable and may change with future releases).
+
+This release works with Java 7 and 8.
+
+## [0.1.0] - 2015-11-28
+Add support for creating/updating groups and sending to them
+
+## [0.0.5] - 2015-11-21
+- Add receive timeout commandline parameter
+- Show message group info
+
+## [0.0.4] - 2015-09-22
+
+## [0.0.3] - 2015-08-07
+
+## [0.0.2] - 2015-07-08
+First release
You can [build signal-cli](#building) yourself, or use the [provided binary files](https://github.com/AsamK/signal-cli/releases/latest), which should work on Linux, macOS and Windows. For Arch Linux there is also a [package in AUR](https://aur.archlinux.org/packages/signal-cli/) and there is a [FreeBSD port](https://www.freshports.org/net-im/signal-cli) available as well.
System requirements:
-- at least Java Runtime Environment (JRE) 11
+- at least Java Runtime Environment (JRE) 17
- native libraries: libzkgroup, libsignal-client
Those are bundled for x86_64 Linux (with recent enough glibc, see #643), for other systems/architectures see: [Provide native lib for libsignal](https://github.com/AsamK/signal-cli/wiki/Provide-native-lib-for-libsignal)
./gradlew build
- 3a. Create shell wrapper in *build/install/signal-cli/bin*:
+ 2a. Create shell wrapper in *build/install/signal-cli/bin*:
./gradlew installDist
- 3b. Create tar file in *build/distributions*:
+ 2b. Create tar file in *build/distributions*:
./gradlew distTar
- 3c. Compile and run signal-cli:
+ 2c. Create a fat tar file in *build/libs/signal-cli-fat*:
+
+ ./gradlew fatJar
+
+ 2d. Compile and run signal-cli:
./gradlew run --args="--help"
application
eclipse
`check-lib-versions`
- id("org.graalvm.buildtools.native") version "0.9.5"
+ id("org.graalvm.buildtools.native") version "0.9.6"
}
-version = "0.9.0"
+version = "0.9.2"
java {
- sourceCompatibility = JavaVersion.VERSION_11
- targetCompatibility = JavaVersion.VERSION_11
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
}
application {
this["main"].run {
configurationFileDirectories.from(file("graalvm-config-dir"))
buildArgs.add("--allow-incomplete-classpath")
+ buildArgs.add("--report-unsupported-elements-at-runtime")
}
}
}
dependencies {
implementation("org.bouncycastle:bcprov-jdk15on:1.69")
implementation("net.sourceforge.argparse4j:argparse4j:0.9.0")
- implementation("com.github.hypfvieh:dbus-java:3.3.0")
- implementation("org.slf4j:slf4j-simple:1.7.30")
+ implementation("com.github.hypfvieh:dbus-java:3.3.1")
+ implementation("org.slf4j:slf4j-simple:1.7.32")
implementation(project(":lib"))
}
)
}
}
+
+task("fatJar", type = Jar::class) {
+ archiveBaseName.set("${project.name}-fat")
+ exclude(
+ "META-INF/*.SF",
+ "META-INF/*.DSA",
+ "META-INF/*.RSA",
+ "META-INF/NOTICE",
+ "META-INF/LICENSE",
+ "**/module-info.class"
+ )
+ from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
+ with(tasks.jar.get() as CopySpec)
+}
[
+{
+ "name":"java.lang.Boolean",
+ "methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }]}
+,
{
"name":"java.lang.ClassLoader",
"methods":[
{"name":"getPlatformClassLoader","parameterTypes":[] },
{"name":"loadClass","parameterTypes":["java.lang.String"] }
- ]
-},
+ ]}
+,
{
"name":"java.lang.IllegalStateException",
- "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]}
+,
{
- "name":"java.lang.NoSuchMethodError"
-},
+ "name":"java.lang.NoSuchMethodError"}
+,
{
"name":"java.lang.UnsatisfiedLinkError",
- "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]}
+,
{
"name":"java.util.UUID",
- "methods":[{"name":"<init>","parameterTypes":["long","long"] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":["long","long"] }]}
+,
{
- "name":"jdk.internal.loader.ClassLoaders$PlatformClassLoader"
-},
+ "name":"jdk.internal.loader.ClassLoaders$PlatformClassLoader"}
+,
{
"name":"org.asamk.signal.manager.storage.protocol.SignalProtocolStore",
"methods":[
{"name":"saveIdentity","parameterTypes":["org.whispersystems.libsignal.SignalProtocolAddress","org.whispersystems.libsignal.IdentityKey"] },
{"name":"storeSenderKey","parameterTypes":["org.whispersystems.libsignal.SignalProtocolAddress","java.util.UUID","org.whispersystems.libsignal.groups.state.SenderKeyRecord"] },
{"name":"storeSession","parameterTypes":["org.whispersystems.libsignal.SignalProtocolAddress","org.whispersystems.libsignal.state.SessionRecord"] }
- ]
-},
+ ]}
+,
{
"name":"org.graalvm.nativebridge.jni.JNIExceptionWrapperEntryPoints",
- "methods":[{"name":"getClassName","parameterTypes":["java.lang.Class"] }]
-},
+ "methods":[{"name":"getClassName","parameterTypes":["java.lang.Class"] }]}
+,
{
"name":"org.whispersystems.libsignal.DuplicateMessageException",
- "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]}
+,
{
"name":"org.whispersystems.libsignal.IdentityKey",
"methods":[
{"name":"<init>","parameterTypes":["byte[]"] },
{"name":"serialize","parameterTypes":[] }
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.libsignal.IdentityKeyPair",
- "methods":[{"name":"serialize","parameterTypes":[] }]
-},
+ "methods":[{"name":"serialize","parameterTypes":[] }]}
+,
{
"name":"org.whispersystems.libsignal.InvalidMessageException",
- "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]}
+,
{
"name":"org.whispersystems.libsignal.SignalProtocolAddress",
- "methods":[{"name":"<init>","parameterTypes":["java.lang.String","int"] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":["java.lang.String","int"] }]}
+,
{
"name":"org.whispersystems.libsignal.UntrustedIdentityException",
- "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]}
+,
{
"name":"org.whispersystems.libsignal.groups.state.SenderKeyRecord",
"methods":[
{"name":"<init>","parameterTypes":["long"] },
{"name":"nativeHandle","parameterTypes":[] }
- ]
-},
+ ]}
+,
{
- "name":"org.whispersystems.libsignal.groups.state.SenderKeyStore"
-},
+ "name":"org.whispersystems.libsignal.groups.state.SenderKeyStore"}
+,
{
"name":"org.whispersystems.libsignal.logging.Log",
- "methods":[{"name":"log","parameterTypes":["int","java.lang.String","java.lang.String"] }]
-},
+ "methods":[{"name":"log","parameterTypes":["int","java.lang.String","java.lang.String"] }]}
+,
{
"name":"org.whispersystems.libsignal.protocol.PlaintextContent",
- "methods":[{"name":"nativeHandle","parameterTypes":[] }]
-},
+ "fields":[{"name":"unsafeHandle"}],
+ "methods":[{"name":"nativeHandle","parameterTypes":[] }]}
+,
{
"name":"org.whispersystems.libsignal.protocol.PreKeySignalMessage",
+ "fields":[{"name":"unsafeHandle"}],
"methods":[
{"name":"<init>","parameterTypes":["long"] },
{"name":"nativeHandle","parameterTypes":[] }
- ]
-},
+ ]}
+,
{
- "name":"org.whispersystems.libsignal.protocol.SenderKeyMessage"
-},
+ "name":"org.whispersystems.libsignal.protocol.SenderKeyMessage"}
+,
{
"name":"org.whispersystems.libsignal.protocol.SignalMessage",
+ "fields":[{"name":"unsafeHandle"}],
"methods":[
{"name":"<init>","parameterTypes":["long"] },
{"name":"nativeHandle","parameterTypes":[] }
- ]
-},
+ ]}
+,
{
- "name":"org.whispersystems.libsignal.state.IdentityKeyStore"
-},
+ "name":"org.whispersystems.libsignal.state.IdentityKeyStore"}
+,
{
"name":"org.whispersystems.libsignal.state.IdentityKeyStore$Direction",
"fields":[
{"name":"RECEIVING"},
{"name":"SENDING"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.libsignal.state.PreKeyRecord",
- "methods":[{"name":"nativeHandle","parameterTypes":[] }]
-},
+ "fields":[{"name":"unsafeHandle"}],
+ "methods":[{"name":"nativeHandle","parameterTypes":[] }]}
+,
{
- "name":"org.whispersystems.libsignal.state.PreKeyStore"
-},
+ "name":"org.whispersystems.libsignal.state.PreKeyStore"}
+,
{
"name":"org.whispersystems.libsignal.state.SessionRecord",
+ "fields":[{"name":"unsafeHandle"}],
"methods":[
{"name":"<init>","parameterTypes":["byte[]"] },
{"name":"nativeHandle","parameterTypes":[] }
- ]
-},
+ ]}
+,
{
- "name":"org.whispersystems.libsignal.state.SessionStore"
-},
+ "name":"org.whispersystems.libsignal.state.SessionStore"}
+,
{
"name":"org.whispersystems.libsignal.state.SignedPreKeyRecord",
- "methods":[{"name":"nativeHandle","parameterTypes":[] }]
-},
+ "fields":[{"name":"unsafeHandle"}],
+ "methods":[{"name":"nativeHandle","parameterTypes":[] }]}
+,
{
- "name":"org.whispersystems.libsignal.state.SignedPreKeyStore"
-}
+ "name":"org.whispersystems.libsignal.state.SignedPreKeyStore"}
+
]
[
- ["org.asamk.Signal"],
- ["org.freedesktop.dbus.interfaces.DBus"]
+ {
+ "interfaces":["org.asamk.Signal"]}
+ ,
+ {
+ "interfaces":["org.freedesktop.dbus.interfaces.DBus"]}
+
]
[
+{
+ "name":"[B",
+ "queryAllDeclaredMethods":true,
+ "queryAllPublicMethods":true}
+,
+{
+ "name":"[C"}
+,
+{
+ "name":"[I",
+ "queryAllDeclaredMethods":true,
+ "queryAllPublicMethods":true}
+,
+{
+ "name":"[J"}
+,
+{
+ "name":"[Lorg.whispersystems.signalservice.api.groupsv2.TemporalCredential;"}
+,
{
"name":"byte[]",
"allDeclaredMethods":true,
- "allPublicMethods":true
-},
+ "allPublicMethods":true}
+,
{
- "name":"char[]"
-},
+ "name":"char[]"}
+,
+{
+ "name":"com.fasterxml.jackson.databind.ext.Java7HandlersImpl",
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"com.fasterxml.jackson.databind.ext.Java7SupportImpl",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"com.google.protobuf.AbstractProtobufList",
"allDeclaredFields":true,
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"com.google.protobuf.Internal$LongList",
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"com.google.protobuf.Internal$ProtobufList",
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"com.google.protobuf.LongArrayList",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"com.google.protobuf.PrimitiveNonBoxingCollection",
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"com.kenai.jffi.Invoker",
"methods":[
{"name":"invokeI6","parameterTypes":["com.kenai.jffi.CallContext","long","int","int","int","int","int","int"] },
{"name":"invokeL6","parameterTypes":["com.kenai.jffi.CallContext","long","long","long","long","long","long","long"] },
{"name":"invokeN6","parameterTypes":["com.kenai.jffi.CallContext","long","long","long","long","long","long","long"] }
- ]
-},
+ ]}
+,
{
"name":"com.kenai.jffi.Version",
"fields":[
{"name":"MAJOR"},
{"name":"MICRO"},
{"name":"MINOR"}
- ]
-},
+ ]}
+,
{
"name":"com.kenai.jffi.internal.StubLoader",
- "methods":[{"name":"isLoaded","parameterTypes":[] }]
-},
+ "methods":[{"name":"isLoaded","parameterTypes":[] }]}
+,
{
"name":"com.sun.crypto.provider.AESCipher$General",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
+{
+ "name":"com.sun.crypto.provider.ARCFOURCipher",
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
+{
+ "name":"com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305",
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
+{
+ "name":"com.sun.crypto.provider.DESCipher",
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
+{
+ "name":"com.sun.crypto.provider.DESedeCipher",
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"com.sun.crypto.provider.DHParameters",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
+{
+ "name":"com.sun.crypto.provider.GaloisCounterMode$AESGCM",
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"com.sun.crypto.provider.HmacCore$HmacSHA256",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
+{
+ "name":"com.sun.crypto.provider.HmacCore$HmacSHA384",
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"com.sun.crypto.provider.TlsKeyMaterialGenerator",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"com.sun.crypto.provider.TlsMasterSecretGenerator",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"com.sun.crypto.provider.TlsPrfGenerator$V12",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"int",
"allDeclaredMethods":true,
- "allPublicMethods":true
-},
+ "allPublicMethods":true}
+,
{
"name":"int[]",
"allDeclaredMethods":true,
- "allPublicMethods":true
-},
+ "allPublicMethods":true}
+,
{
"name":"java.io.Serializable",
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"java.lang.Boolean",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
+{
+ "name":"java.lang.Class",
+ "methods":[{"name":"getRecordComponents","parameterTypes":[] }]}
+,
{
"name":"java.lang.Comparable",
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"java.lang.Double",
- "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
-},
+ "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]}
+,
{
"name":"java.lang.Enum",
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"java.lang.Integer",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true,
- "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"java.lang.Iterable",
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"java.lang.Long",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true,
- "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"java.lang.Number",
"allDeclaredFields":true,
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
+{
+ "name":"java.lang.Record",
+ "allDeclaredFields":true,
+ "queryAllDeclaredMethods":true}
+,
{
"name":"java.lang.String",
- "allPublicMethods":true
-},
+ "allPublicMethods":true}
+,
{
"name":"java.lang.reflect.Method",
- "methods":[{"name":"isDefault","parameterTypes":[] }]
-},
+ "methods":[{"name":"isDefault","parameterTypes":[] }]}
+,
+{
+ "name":"java.lang.reflect.RecordComponent",
+ "methods":[
+ {"name":"getName","parameterTypes":[] },
+ {"name":"getType","parameterTypes":[] }
+ ]}
+,
{
"name":"java.nio.Buffer",
"allDeclaredMethods":true,
- "fields":[{"name":"address"}]
-},
+ "fields":[{"name":"address"}]}
+,
{
"name":"java.nio.ByteBuffer",
"allDeclaredMethods":true,
- "allPublicMethods":true
-},
+ "allPublicMethods":true}
+,
{
- "name":"java.security.KeyStoreSpi"
-},
+ "name":"java.security.KeyStoreSpi"}
+,
{
- "name":"java.security.SecureRandomParameters"
-},
+ "name":"java.security.SecureRandomParameters"}
+,
{
- "name":"java.security.cert.PKIXRevocationChecker"
-},
+ "name":"java.security.cert.PKIXRevocationChecker"}
+,
{
- "name":"java.security.interfaces.ECPrivateKey"
-},
+ "name":"java.security.interfaces.ECPrivateKey"}
+,
{
- "name":"java.security.interfaces.ECPublicKey"
-},
+ "name":"java.security.interfaces.ECPublicKey"}
+,
{
- "name":"java.security.interfaces.RSAPrivateKey"
-},
+ "name":"java.security.interfaces.RSAPrivateKey"}
+,
{
- "name":"java.security.interfaces.RSAPublicKey"
-},
+ "name":"java.security.interfaces.RSAPublicKey"}
+,
{
"name":"java.util.AbstractCollection",
"allDeclaredFields":true,
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"java.util.AbstractList",
"allDeclaredFields":true,
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"java.util.ArrayList",
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"java.util.Collection",
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"java.util.HashSet",
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"java.util.LinkedHashMap",
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"java.util.List",
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"java.util.Locale",
- "methods":[{"name":"getUnicodeLocaleType","parameterTypes":["java.lang.String"] }]
-},
+ "methods":[{"name":"getUnicodeLocaleType","parameterTypes":["java.lang.String"] }]}
+,
{
"name":"java.util.RandomAccess",
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"java.util.UUID",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
+{
+ "name":"javax.security.auth.x500.X500Principal",
+ "methods":[{"name":"<init>","parameterTypes":["sun.security.x509.X500Name"] }]}
+,
{
- "name":"jnr.constants.platform.linux.ProtocolFamily"
-},
+ "name":"jnr.constants.platform.linux.ProtocolFamily"}
+,
{
- "name":"jnr.constants.platform.linux.Shutdown"
-},
+ "name":"jnr.constants.platform.linux.Shutdown"}
+,
{
- "name":"jnr.constants.platform.linux.Sock"
-},
+ "name":"jnr.constants.platform.linux.Sock"}
+,
{
- "name":"jnr.constants.platform.linux.SocketLevel"
-},
+ "name":"jnr.constants.platform.linux.SocketLevel"}
+,
{
- "name":"jnr.constants.platform.linux.SocketOption"
-},
+ "name":"jnr.constants.platform.linux.SocketOption"}
+,
{
"name":"jnr.enxio.channels.Native$LibC",
- "allPublicMethods":true
-},
+ "allPublicMethods":true}
+,
{
"name":"jnr.enxio.channels.Native$LibC$jnr$ffi$1",
- "methods":[{"name":"<init>","parameterTypes":["jnr.ffi.Runtime","jnr.ffi.provider.jffi.NativeLibrary","java.lang.Object[]"] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":["jnr.ffi.Runtime","jnr.ffi.provider.jffi.NativeLibrary","java.lang.Object[]"] }]}
+,
{
"name":"jnr.ffi.Pointer",
"allDeclaredMethods":true,
- "allPublicMethods":true
-},
+ "allPublicMethods":true}
+,
{
"name":"jnr.ffi.StructLayout$gid_t",
- "methods":[{"name":"<init>","parameterTypes":["jnr.ffi.StructLayout"] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":["jnr.ffi.StructLayout"] }]}
+,
{
"name":"jnr.ffi.byref.IntByReference",
- "allPublicMethods":true
-},
+ "allPublicMethods":true}
+,
{
"name":"jnr.ffi.provider.converters.ByReferenceParameterConverter",
- "methods":[{"name":"nativeType","parameterTypes":[] }]
-},
+ "methods":[{"name":"nativeType","parameterTypes":[] }]}
+,
{
"name":"jnr.ffi.provider.converters.ByReferenceParameterConverter$Out",
- "allPublicMethods":true
-},
+ "allPublicMethods":true}
+,
{
"name":"jnr.ffi.provider.converters.StringResultConverter",
- "allPublicMethods":true
-},
+ "allPublicMethods":true}
+,
{
"name":"jnr.ffi.provider.converters.StructByReferenceToNativeConverter",
- "allPublicMethods":true
-},
+ "allPublicMethods":true}
+,
{
"name":"jnr.ffi.provider.jffi.BufferParameterStrategy",
- "methods":[{"name":"address","parameterTypes":["java.nio.Buffer"] }]
-},
+ "methods":[{"name":"address","parameterTypes":["java.nio.Buffer"] }]}
+,
{
"name":"jnr.ffi.provider.jffi.PointerParameterStrategy",
- "methods":[{"name":"address","parameterTypes":["jnr.ffi.Pointer"] }]
-},
+ "methods":[{"name":"address","parameterTypes":["jnr.ffi.Pointer"] }]}
+,
{
"name":"jnr.ffi.provider.jffi.Provider",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"jnr.ffi.provider.jffi.platform.x86_64.linux.TypeAliases",
- "fields":[{"name":"ALIASES"}]
-},
+ "fields":[{"name":"ALIASES"}]}
+,
{
"name":"jnr.posix.Timeval",
- "allPublicMethods":true
-},
+ "allPublicMethods":true}
+,
{
"name":"jnr.unixsocket.Native$LibC",
- "allPublicMethods":true
-},
+ "allPublicMethods":true}
+,
{
"name":"jnr.unixsocket.Native$LibC$jnr$ffi$0",
- "methods":[{"name":"<init>","parameterTypes":["jnr.ffi.Runtime","jnr.ffi.provider.jffi.NativeLibrary","java.lang.Object[]"] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":["jnr.ffi.Runtime","jnr.ffi.provider.jffi.NativeLibrary","java.lang.Object[]"] }]}
+,
{
"name":"jnr.unixsocket.SockAddrUnix",
- "allPublicMethods":true
-},
+ "allPublicMethods":true}
+,
{
"name":"long",
"allDeclaredMethods":true,
- "allPublicMethods":true
-},
+ "allPublicMethods":true}
+,
{
- "name":"long[]"
-},
+ "name":"long[]"}
+,
{
"name":"org.asamk.Signal",
"allDeclaredMethods":true,
- "allDeclaredClasses":true
-},
+ "allDeclaredClasses":true}
+,
+{
+ "name":"org.asamk.Signal$Device",
+ "allDeclaredMethods":true,
+ "allDeclaredClasses":true}
+,
+{
+ "name":"org.asamk.Signal$Group",
+ "allDeclaredMethods":true,
+ "allDeclaredClasses":true}
+,
{
"name":"org.asamk.Signal$MessageReceived",
"allDeclaredConstructors":true,
- "allPublicConstructors":true
-},
+ "allPublicConstructors":true}
+,
{
"name":"org.asamk.Signal$ReceiptReceived",
"allDeclaredConstructors":true,
- "allPublicConstructors":true
-},
+ "allPublicConstructors":true}
+,
+{
+ "name":"org.asamk.Signal$StructDevice",
+ "allDeclaredFields":true}
+,
+{
+ "name":"org.asamk.Signal$StructGroup",
+ "allDeclaredFields":true}
+,
{
"name":"org.asamk.Signal$SyncMessageReceived",
"allDeclaredConstructors":true,
- "allPublicConstructors":true
-},
+ "allPublicConstructors":true}
+,
+{
+ "name":"org.asamk.SignalControl",
+ "allDeclaredMethods":true,
+ "allDeclaredClasses":true}
+,
{
"name":"org.asamk.signal.commands.GetUserStatusCommand$JsonUserStatus",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.commands.ListContactsCommand$JsonContact",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.commands.ListDevicesCommand$JsonDevice",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.commands.ListGroupsCommand$JsonGroup",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.commands.ListGroupsCommand$JsonGroupMember",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.commands.ListIdentitiesCommand$JsonIdentity",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonAttachment",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonCallMessage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonContactAddress",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonContactAvatar",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonContactEmail",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonContactName",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonContactPhone",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonDataMessage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonError",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonGroupInfo",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonMention",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonMessageEnvelope",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonQuote",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonQuotedAttachment",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonReaction",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonReceiptMessage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonRemoteDelete",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonSharedContact",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonSticker",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonSyncDataMessage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonSyncMessage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonSyncMessageType",
"allDeclaredFields":true,
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"org.asamk.signal.json.JsonSyncReadMessage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.json.JsonTypingMessage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.jsonrpc.JsonRpcBulkMessage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.jsonrpc.JsonRpcException",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.jsonrpc.JsonRpcMessage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.jsonrpc.JsonRpcRequest",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.jsonrpc.JsonRpcResponse",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.jsonrpc.JsonRpcResponse$Error",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.manager.storage.contacts.LegacyJsonContactsStore",
"allDeclaredFields":true,
"allDeclaredMethods":true,
"allDeclaredConstructors":true,
- "fields":[{"name":"contacts", "allowWrite":true}]
-},
+ "fields":[{"name":"contacts", "allowWrite":true}]}
+,
{
"name":"org.asamk.signal.manager.storage.groups.GroupInfo",
"allDeclaredFields":true,
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"org.asamk.signal.manager.storage.groups.GroupInfoV1",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.manager.storage.groups.GroupStore$GroupsDeserializer",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.asamk.signal.manager.storage.groups.GroupStore$Storage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.manager.storage.groups.GroupStore$Storage$Group",
"allDeclaredFields":true,
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"org.asamk.signal.manager.storage.groups.GroupStore$Storage$GroupV1",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.manager.storage.groups.GroupStore$Storage$GroupV1$MembersDeserializer",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.asamk.signal.manager.storage.groups.GroupStore$Storage$GroupV1$MembersSerializer",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.asamk.signal.manager.storage.groups.GroupStore$Storage$GroupV2",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.manager.storage.identities.IdentityKeyStore$IdentityStorage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.manager.storage.profiles.LegacyProfileStore",
"allDeclaredFields":true,
"allDeclaredMethods":true,
"allDeclaredConstructors":true,
- "fields":[{"name":"profiles", "allowWrite":true}]
-},
+ "fields":[{"name":"profiles", "allowWrite":true}]}
+,
{
"name":"org.asamk.signal.manager.storage.profiles.LegacyProfileStore$ProfileStoreDeserializer",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.asamk.signal.manager.storage.profiles.LegacySignalProfileEntry",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.manager.storage.profiles.ProfileStore",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.manager.storage.profiles.SignalProfile",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.manager.storage.profiles.SignalProfile$Capabilities",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.manager.storage.protocol.LegacyJsonIdentityKeyStore$JsonIdentityKeyStoreDeserializer",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.asamk.signal.manager.storage.protocol.LegacyJsonPreKeyStore$JsonPreKeyStoreDeserializer",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.asamk.signal.manager.storage.protocol.LegacyJsonSessionStore$JsonSessionStoreDeserializer",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.asamk.signal.manager.storage.protocol.LegacyJsonSignalProtocolStore",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.manager.storage.protocol.LegacyJsonSignedPreKeyStore$JsonSignedPreKeyStoreDeserializer",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.asamk.signal.manager.storage.recipients.LegacyRecipientStore",
"allDeclaredFields":true,
"allDeclaredMethods":true,
"allDeclaredConstructors":true,
- "fields":[{"name":"addresses", "allowWrite":true}]
-},
+ "fields":[{"name":"addresses", "allowWrite":true}]}
+,
{
"name":"org.asamk.signal.manager.storage.recipients.LegacyRecipientStore$RecipientStoreDeserializer",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.asamk.signal.manager.storage.recipients.RecipientStore$Storage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.manager.storage.recipients.RecipientStore$Storage$Recipient",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.manager.storage.recipients.RecipientStore$Storage$Recipient$Contact",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.manager.storage.recipients.RecipientStore$Storage$Recipient$Profile",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.manager.storage.senderKeys.SenderKeySharedStore$Storage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.manager.storage.senderKeys.SenderKeySharedStore$Storage$SharedSenderKey",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.manager.storage.stickers.StickerStore",
"allDeclaredFields":true,
"allDeclaredMethods":true,
"allDeclaredConstructors":true,
- "fields":[{"name":"stickers", "allowWrite":true}]
-},
+ "fields":[{"name":"stickers", "allowWrite":true}]}
+,
{
"name":"org.asamk.signal.manager.storage.stickers.StickerStore$Storage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.manager.storage.stickers.StickerStore$Storage$Sticker",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.asamk.signal.util.SecurityProvider$DefaultRandom",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.COMPOSITE$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.DH$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.DSA$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.DSTU4145$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.EC$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.ECGOST$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.EdEC$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.ElGamal$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.GM$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.GOST$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.IES$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.RSA$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.X509$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.edec.SignatureSpi$Ed25519",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.edec.SignatureSpi$Ed448",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.Blake2b$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.Blake2s$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.DSTU7564$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.GOST3411$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.Haraka$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.Keccak$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.MD2$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.MD4$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.MD5$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.RIPEMD128$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.RIPEMD160$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.RIPEMD256$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.RIPEMD320$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA1$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA224$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA256$Digest",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA256$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA3$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA384$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA512$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.SM3$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.Skein$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.Tiger$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.digest.Whirlpool$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.drbg.DRBG$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.keystore.BC$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.keystore.BCFKS$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.keystore.PKCS12$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.keystore.bc.BcKeyStoreSpi$Std",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.AES$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.ARC4$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.ARIA$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Blowfish$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.CAST5$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.CAST6$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Camellia$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.ChaCha$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.DES$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.DESede$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.DSTU7624$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.GOST28147$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.GOST3412_2015$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Grain128$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Grainv1$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.HC128$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.HC256$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.IDEA$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Noekeon$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.OpenSSLPBKDF$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF1$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.PBEPKCS12$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Poly1305$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.RC2$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.RC5$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.RC6$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Rijndael$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.SCRYPT$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.SEED$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.SM4$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Salsa20$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Serpent$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Shacal2$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.SipHash$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.SipHash128$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Skipjack$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.TEA$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.TLSKDF$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Threefish$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Twofish$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.VMPC$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.VMPCKSA3$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.XSalsa20$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.XTEA$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Zuc$Mappings",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.freedesktop.dbus.errors.ServiceUnknown",
- "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]}
+,
+{
+ "name":"org.freedesktop.dbus.errors.UnknownObject",
+ "methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]}
+,
{
"name":"org.freedesktop.dbus.interfaces.DBus$NameAcquired",
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.freedesktop.dbus.interfaces.Introspectable",
"allDeclaredMethods":true,
- "allDeclaredClasses":true
-},
+ "allDeclaredClasses":true}
+,
{
"name":"org.freedesktop.dbus.interfaces.Peer",
"allDeclaredMethods":true,
- "allDeclaredClasses":true
-},
+ "allDeclaredClasses":true}
+,
+{
+ "name":"org.freedesktop.dbus.interfaces.Properties",
+ "allDeclaredMethods":true,
+ "allDeclaredClasses":true}
+,
+{
+ "name":"org.freedesktop.dbus.interfaces.Properties$PropertiesChanged",
+ "allPublicConstructors":true}
+,
{
- "name":"org.objectweb.asm.util.TraceMethodVisitor"
-},
+ "name":"org.objectweb.asm.util.TraceMethodVisitor"}
+,
{
"name":"org.signal.storageservice.protos.groups.AccessControl",
"fields":[
{"name":"addFromInviteLink_"},
{"name":"attributes_"},
{"name":"members_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.AvatarUploadAttributes",
"fields":[
{"name":"key_"},
{"name":"policy_"},
{"name":"signature_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.Group",
"fields":[
{"name":"requestingMembers_"},
{"name":"revision_"},
{"name":"title_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupAttributeBlob",
"fields":[
{"name":"contentCase_"},
{"name":"content_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange",
"fields":[
{"name":"actions_"},
{"name":"changeEpoch_"},
{"name":"serverSignature_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions",
"fields":[
{"name":"promoteRequestingMembers_"},
{"name":"revision_"},
{"name":"sourceUuid_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$AddMemberAction",
"fields":[
{"name":"added_"},
{"name":"joinFromInviteLink_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$AddPendingMemberAction",
- "fields":[{"name":"added_"}]
-},
+ "fields":[{"name":"added_"}]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$AddRequestingMemberAction",
- "fields":[{"name":"added_"}]
-},
+ "fields":[{"name":"added_"}]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$DeleteMemberAction",
- "fields":[{"name":"deletedUserId_"}]
-},
+ "fields":[{"name":"deletedUserId_"}]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$DeletePendingMemberAction",
- "fields":[{"name":"deletedUserId_"}]
-},
+ "fields":[{"name":"deletedUserId_"}]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$DeleteRequestingMemberAction",
- "fields":[{"name":"deletedUserId_"}]
-},
+ "fields":[{"name":"deletedUserId_"}]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyAddFromInviteLinkAccessControlAction",
- "fields":[{"name":"addFromInviteLinkAccess_"}]
-},
+ "fields":[{"name":"addFromInviteLinkAccess_"}]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyAttributesAccessControlAction",
- "fields":[{"name":"attributesAccess_"}]
-},
+ "fields":[{"name":"attributesAccess_"}]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyDescriptionAction",
- "fields":[{"name":"description_"}]
-},
+ "fields":[{"name":"description_"}]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyDisappearingMessagesTimerAction",
- "fields":[{"name":"timer_"}]
-},
+ "fields":[{"name":"timer_"}]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyInviteLinkPasswordAction",
- "fields":[{"name":"inviteLinkPassword_"}]
-},
+ "fields":[{"name":"inviteLinkPassword_"}]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyMemberProfileKeyAction",
- "fields":[{"name":"presentation_"}]
-},
+ "fields":[{"name":"presentation_"}]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyMemberRoleAction",
"fields":[
{"name":"role_"},
{"name":"userId_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyMembersAccessControlAction",
- "fields":[{"name":"membersAccess_"}]
-},
+ "fields":[{"name":"membersAccess_"}]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$ModifyTitleAction",
- "fields":[{"name":"title_"}]
-},
+ "fields":[{"name":"title_"}]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$PromotePendingMemberAction",
- "fields":[{"name":"presentation_"}]
-},
+ "fields":[{"name":"presentation_"}]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupChange$Actions$PromoteRequestingMemberAction",
"fields":[
{"name":"role_"},
{"name":"userId_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupInviteLink",
"fields":[
{"name":"contentsCase_"},
{"name":"contents_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.GroupInviteLink$GroupInviteLinkContentsV1",
"fields":[
{"name":"groupMasterKey_"},
{"name":"inviteLinkPassword_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.Member",
"fields":[
{"name":"profileKey_"},
{"name":"role_"},
{"name":"userId_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.PendingMember",
"fields":[
{"name":"addedByUserId_"},
{"name":"member_"},
{"name":"timestamp_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.RequestingMember",
"fields":[
{"name":"profileKey_"},
{"name":"timestamp_"},
{"name":"userId_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedGroup",
"fields":[
{"name":"requestingMembers_"},
{"name":"revision_"},
{"name":"title_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedGroupChange",
"fields":[
{"name":"promotePendingMembers_"},
{"name":"promoteRequestingMembers_"},
{"name":"revision_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedMember",
"fields":[
{"name":"profileKey_"},
{"name":"role_"},
{"name":"uuid_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedModifyMemberRole",
"fields":[
{"name":"role_"},
{"name":"uuid_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedPendingMember",
"fields":[
{"name":"timestamp_"},
{"name":"uuidCipherText_"},
{"name":"uuid_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedPendingMemberRemoval",
"fields":[
{"name":"uuidCipherText_"},
{"name":"uuid_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedRequestingMember",
"fields":[
{"name":"profileKey_"},
{"name":"timestamp_"},
{"name":"uuid_"}
- ]
-},
+ ]}
+,
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedString",
- "fields":[{"name":"value_"}]
-},
+ "fields":[{"name":"value_"}]}
+,
{
"name":"org.signal.storageservice.protos.groups.local.DecryptedTimer",
- "fields":[{"name":"duration_"}]
-},
+ "fields":[{"name":"duration_"}]}
+,
{
"name":"org.whispersystems.libsignal.state.IdentityKeyStore",
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"org.whispersystems.libsignal.state.PreKeyStore",
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"org.whispersystems.libsignal.state.SessionStore",
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"org.whispersystems.libsignal.state.SignalProtocolStore",
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"org.whispersystems.libsignal.state.SignedPreKeyStore",
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"org.whispersystems.signalservice.api.account.AccountAttributes",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.api.account.AccountAttributes$Capabilities",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.api.groupsv2.CredentialResponse",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
- "name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential[]"
-},
+ "name":"org.whispersystems.signalservice.api.groupsv2.TemporalCredential[]"}
+,
{
"name":"org.whispersystems.signalservice.api.messages.calls.HangupMessage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.api.messages.calls.HangupMessage$Type",
"allDeclaredFields":true,
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.api.messages.calls.OfferMessage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.api.messages.calls.OfferMessage$Type",
"allDeclaredFields":true,
- "allDeclaredMethods":true
-},
+ "allDeclaredMethods":true}
+,
{
"name":"org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.api.profiles.SignalServiceProfile",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
+{
+ "name":"org.whispersystems.signalservice.api.profiles.SignalServiceProfile$Badge",
+ "allDeclaredFields":true,
+ "allDeclaredMethods":true,
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.api.profiles.SignalServiceProfile$Capabilities",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.api.profiles.SignalServiceProfileWrite",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.api.push.SignedPreKeyEntity",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.api.push.SignedPreKeyEntity$ByteArrayDeserializer",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.whispersystems.signalservice.api.push.SignedPreKeyEntity$ByteArraySerializer",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.whispersystems.signalservice.api.storage.StorageAuthResponse",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.contacts.crypto.SignatureBodyEntity",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.contacts.entities.DiscoveryRequest",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.contacts.entities.DiscoveryResponse",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.contacts.entities.KeyBackupRequest",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.contacts.entities.KeyBackupResponse",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.contacts.entities.MultiRemoteAttestationResponse",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.contacts.entities.QueryEnvelope",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.contacts.entities.RemoteAttestationRequest",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.contacts.entities.RemoteAttestationResponse",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.contacts.entities.TokenResponse",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.devices.DeviceNameProtos$DeviceName",
"fields":[
{"name":"ciphertext_"},
{"name":"ephemeralPublic_"},
{"name":"syntheticIv_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.keybackup.protos.BackupRequest",
"fields":[
{"name":"token_"},
{"name":"tries_"},
{"name":"validFrom_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.keybackup.protos.BackupResponse",
"fields":[
{"name":"bitField0_"},
{"name":"status_"},
{"name":"token_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.keybackup.protos.DeleteRequest",
"fields":[
{"name":"backupId_"},
{"name":"bitField0_"},
{"name":"serviceId_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.keybackup.protos.Request",
"fields":[
{"name":"bitField0_"},
{"name":"delete_"},
{"name":"restore_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.keybackup.protos.Response",
"fields":[
{"name":"bitField0_"},
{"name":"delete_"},
{"name":"restore_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.keybackup.protos.RestoreRequest",
"fields":[
{"name":"serviceId_"},
{"name":"token_"},
{"name":"validFrom_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.keybackup.protos.RestoreResponse",
"fields":[
{"name":"status_"},
{"name":"token_"},
{"name":"tries_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.AttachmentV2UploadAttributes",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.AuthCredentials",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.ConfirmCodeMessage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.DeviceCode",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.DeviceId",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.DeviceInfoList",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.MismatchedDevices",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.OutgoingPushMessage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.OutgoingPushMessageList",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.PreKeyEntity",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.PreKeyEntity$ECPublicKeyDeserializer",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.PreKeyEntity$ECPublicKeySerializer",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.PreKeyResponse",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.PreKeyResponseItem",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.PreKeyState",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.PreKeyStatus",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.ProfileAvatarUploadAttributes",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.ProvisioningMessage",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.ProvisioningProtos$ProvisionEnvelope",
"fields":[
{"name":"bitField0_"},
{"name":"body_"},
{"name":"publicKey_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.ProvisioningProtos$ProvisionMessage",
"fields":[
{"name":"readReceipts_"},
{"name":"userAgent_"},
{"name":"uuid_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.ProvisioningProtos$ProvisioningUuid",
"fields":[
{"name":"bitField0_"},
{"name":"uuid_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.PushServiceSocket$RegistrationLockFailure",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.PushServiceSocket$RegistrationLockV2",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SendMessageResponse",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SenderCertificate",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SenderCertificate$ByteArrayDesieralizer",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$AttachmentPointer",
"fields":[
{"name":"thumbnail_"},
{"name":"uploadTimestamp_"},
{"name":"width_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$CallMessage",
"fields":[
{"name":"multiRing_"},
{"name":"offer_"},
{"name":"opaque_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$CallMessage$Hangup",
"fields":[
{"name":"deviceId_"},
{"name":"id_"},
{"name":"type_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$CallMessage$IceUpdate",
"fields":[
{"name":"mid_"},
{"name":"opaque_"},
{"name":"sdp_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$CallMessage$Offer",
"fields":[
{"name":"opaque_"},
{"name":"sdp_"},
{"name":"type_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$CallMessage$Opaque",
"fields":[
{"name":"bitField0_"},
{"name":"data_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$ContactDetails",
"fields":[
{"name":"profileKey_"},
{"name":"uuid_"},
{"name":"verified_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$ContactDetails$Avatar",
"fields":[
{"name":"bitField0_"},
{"name":"contentType_"},
{"name":"length_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$Content",
"fields":[
{"name":"senderKeyDistributionMessage_"},
{"name":"syncMessage_"},
{"name":"typingMessage_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage",
"fields":[
{"name":"requiredProtocolVersion_"},
{"name":"sticker_"},
{"name":"timestamp_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$BodyRange",
"fields":[
{"name":"bitField0_"},
{"name":"length_"},
{"name":"start_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Contact",
"fields":[
{"name":"name_"},
{"name":"number_"},
{"name":"organization_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Contact$Avatar",
"fields":[
{"name":"avatar_"},
{"name":"bitField0_"},
{"name":"isProfile_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Contact$Email",
"fields":[
{"name":"label_"},
{"name":"type_"},
{"name":"value_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Contact$Name",
"fields":[
{"name":"middleName_"},
{"name":"prefix_"},
{"name":"suffix_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Contact$Phone",
"fields":[
{"name":"label_"},
{"name":"type_"},
{"name":"value_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Contact$PostalAddress",
"fields":[
{"name":"region_"},
{"name":"street_"},
{"name":"type_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Delete",
"fields":[
{"name":"bitField0_"},
{"name":"targetSentTimestamp_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$GroupCallUpdate",
"fields":[
{"name":"bitField0_"},
{"name":"eraId_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Preview",
"fields":[
{"name":"image_"},
{"name":"title_"},
{"name":"url_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Quote",
"fields":[
{"name":"bodyRanges_"},
{"name":"id_"},
{"name":"text_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Reaction",
"fields":[
{"name":"remove_"},
{"name":"targetAuthorUuid_"},
{"name":"targetSentTimestamp_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$DataMessage$Sticker",
"fields":[
{"name":"packId_"},
{"name":"packKey_"},
{"name":"stickerId_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$Envelope",
"fields":[
{"name":"sourceUuid_"},
{"name":"timestamp_"},
{"name":"type_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$GroupContext",
"fields":[
{"name":"members_"},
{"name":"name_"},
{"name":"type_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$GroupContext$Member",
"fields":[
{"name":"bitField0_"},
{"name":"e164_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$GroupContextV2",
"fields":[
{"name":"groupChange_"},
{"name":"masterKey_"},
{"name":"revision_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$NullMessage",
"fields":[
{"name":"bitField0_"},
{"name":"padding_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$ReceiptMessage",
"fields":[
{"name":"bitField0_"},
{"name":"timestamp_"},
{"name":"type_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage",
"fields":[
{"name":"verified_"},
{"name":"viewOnceOpen_"},
{"name":"viewed_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Blocked",
"fields":[
{"name":"groupIds_"},
{"name":"numbers_"},
{"name":"uuids_"}
- ]
-},
+ ]}
+,
+{
+ "name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Configuration",
+ "fields":[
+ {"name":"bitField0_"},
+ {"name":"linkPreviews_"},
+ {"name":"provisioningVersion_"},
+ {"name":"readReceipts_"},
+ {"name":"typingIndicators_"},
+ {"name":"unidentifiedDeliveryIndicators_"}
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Contacts",
"fields":[
{"name":"bitField0_"},
{"name":"blob_"},
{"name":"complete_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$FetchLatest",
"fields":[
{"name":"bitField0_"},
{"name":"type_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Keys",
"fields":[
{"name":"bitField0_"},
{"name":"storageService_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Read",
"fields":[
{"name":"senderE164_"},
{"name":"senderUuid_"},
{"name":"timestamp_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Request",
"fields":[
{"name":"bitField0_"},
{"name":"type_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Sent",
"fields":[
{"name":"message_"},
{"name":"timestamp_"},
{"name":"unidentifiedStatus_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Sent$UnidentifiedDeliveryStatus",
"fields":[
{"name":"destinationE164_"},
{"name":"destinationUuid_"},
{"name":"unidentified_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$StickerPackOperation",
"fields":[
{"name":"packId_"},
{"name":"packKey_"},
{"name":"type_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$SyncMessage$Viewed",
"fields":[
{"name":"senderE164_"},
{"name":"senderUuid_"},
{"name":"timestamp_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$TypingMessage",
"fields":[
{"name":"bitField0_"},
{"name":"groupId_"},
{"name":"timestamp_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.SignalServiceProtos$Verified",
"fields":[
{"name":"identityKey_"},
{"name":"nullMessage_"},
{"name":"state_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.push.VerifyAccountResponse",
"allDeclaredFields":true,
"allDeclaredMethods":true,
- "allDeclaredConstructors":true
-},
+ "allDeclaredConstructors":true}
+,
{
"name":"org.whispersystems.signalservice.internal.serialize.protos.AddressProto",
"fields":[
{"name":"bitField0_"},
{"name":"e164_"},
{"name":"uuid_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.serialize.protos.MetadataProto",
"fields":[
{"name":"serverGuid_"},
{"name":"serverReceivedTimestamp_"},
{"name":"timestamp_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto",
"fields":[
{"name":"data_"},
{"name":"localAddress_"},
{"name":"metadata_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.util.JsonUtil$IdentityKeyDeserializer",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.whispersystems.signalservice.internal.util.JsonUtil$IdentityKeySerializer",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.whispersystems.signalservice.internal.util.JsonUtil$UuidDeserializer",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"org.whispersystems.signalservice.internal.websocket.WebSocketProtos$WebSocketMessage",
"fields":[
{"name":"request_"},
{"name":"response_"},
{"name":"type_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.websocket.WebSocketProtos$WebSocketRequestMessage",
"fields":[
{"name":"id_"},
{"name":"path_"},
{"name":"verb_"}
- ]
-},
+ ]}
+,
{
"name":"org.whispersystems.signalservice.internal.websocket.WebSocketProtos$WebSocketResponseMessage",
"fields":[
{"name":"id_"},
{"name":"message_"},
{"name":"status_"}
- ]
-},
+ ]}
+,
{
"name":"sun.misc.Unsafe",
"allDeclaredFields":true,
{"name":"putLong","parameterTypes":["java.lang.Object","long","long"] },
{"name":"putObject","parameterTypes":["java.lang.Object","long","java.lang.Object"] },
{"name":"putShort","parameterTypes":["long","short"] }
- ]
-},
+ ]}
+,
{
"name":"sun.security.provider.DSA$SHA224withDSA",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.provider.JavaKeyStore$DualFormatJKS",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.provider.JavaKeyStore$JKS",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.provider.NativePRNG",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.provider.SHA",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.provider.SHA2$SHA224",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.provider.SHA2$SHA256",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.provider.SHA5$SHA384",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.provider.SHA5$SHA512",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.provider.SecureRandom",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.provider.certpath.PKIXCertPathValidator",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.rsa.PSSParameters",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.rsa.RSAKeyFactory$Legacy",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.rsa.RSAPSSSignature",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.rsa.RSASignature$SHA224withRSA",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.rsa.RSASignature$SHA256withRSA",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.rsa.RSASignature$SHA512withRSA",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.ssl.SSLContextImpl$TLSContext",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory",
- "methods":[{"name":"<init>","parameterTypes":[] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":[] }]}
+,
{
"name":"sun.security.x509.AuthorityKeyIdentifierExtension",
- "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]}
+,
{
"name":"sun.security.x509.BasicConstraintsExtension",
- "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]}
+,
{
"name":"sun.security.x509.CRLDistributionPointsExtension",
- "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]}
+,
{
"name":"sun.security.x509.KeyUsageExtension",
- "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]}
+,
{
"name":"sun.security.x509.SubjectAlternativeNameExtension",
- "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
-},
+ "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]}
+,
{
"name":"sun.security.x509.SubjectKeyIdentifierExtension",
- "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
-}
+ "methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]}
+
]
{
"resources":{
"includes":[
- {"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AT\\E"},
- {"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BD\\E"},
- {"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CZ\\E"},
- {"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_DE\\E"},
- {"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_EE\\E"},
- {"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_ES\\E"},
- {"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_FI\\E"},
- {"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_FR\\E"},
- {"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GB\\E"},
- {"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GR\\E"},
- {"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IN\\E"},
- {"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PL\\E"},
- {"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_RU\\E"},
- {"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_UA\\E"},
- {"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_US\\E"},
- {"pattern":"\\Qjni/x86_64-Linux/libjffi-1.2.so\\E"},
- {"pattern":"\\Qjnr/constants/ConstantSet.class\\E"},
- {"pattern":"\\Qjnr/constants/platform/linux/ProtocolFamily.class\\E"},
- {"pattern":"\\Qjnr/constants/platform/linux/Shutdown.class\\E"},
- {"pattern":"\\Qjnr/constants/platform/linux/Sock.class\\E"},
- {"pattern":"\\Qjnr/constants/platform/linux/SocketLevel.class\\E"},
- {"pattern":"\\Qjnr/constants/platform/linux/SocketOption.class\\E"},
- {"pattern":"\\Qlibsignal_jni.so\\E"},
- {"pattern":"\\Qlibzkgroup.so\\E"},
- {"pattern":"\\Qorg/asamk/signal/manager/config/ias.store\\E"},
- {"pattern":"\\Qorg/asamk/signal/manager/config/whisper.store\\E"},
- {"pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E"},
- {"pattern":"com/google/i18n/phonenumbers/data/.*"}
+ {
+ "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AT\\E"
+ },
+ {
+ "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BD\\E"
+ },
+ {
+ "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CZ\\E"
+ },
+ {
+ "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_DE\\E"
+ },
+ {
+ "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_EE\\E"
+ },
+ {
+ "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_ES\\E"
+ },
+ {
+ "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_FI\\E"
+ },
+ {
+ "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_FR\\E"
+ },
+ {
+ "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GB\\E"
+ },
+ {
+ "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GR\\E"
+ },
+ {
+ "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IN\\E"
+ },
+ {
+ "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PL\\E"
+ },
+ {
+ "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_RU\\E"
+ },
+ {
+ "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_UA\\E"
+ },
+ {
+ "pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_US\\E"
+ },
+ {
+ "pattern":"\\Qjni/x86_64-Linux/libjffi-1.2.so\\E"
+ },
+ {
+ "pattern":"\\Qjnr/constants/ConstantSet.class\\E"
+ },
+ {
+ "pattern":"\\Qjnr/constants/platform/linux/ProtocolFamily.class\\E"
+ },
+ {
+ "pattern":"\\Qjnr/constants/platform/linux/Shutdown.class\\E"
+ },
+ {
+ "pattern":"\\Qjnr/constants/platform/linux/Sock.class\\E"
+ },
+ {
+ "pattern":"\\Qjnr/constants/platform/linux/SocketLevel.class\\E"
+ },
+ {
+ "pattern":"\\Qjnr/constants/platform/linux/SocketOption.class\\E"
+ },
+ {
+ "pattern":"\\Qlibsignal_jni.so\\E"
+ },
+ {
+ "pattern":"\\Qlibzkgroup.so\\E"
+ },
+ {
+ "pattern":"\\Qorg/asamk/signal/manager/config/ias.store\\E"
+ },
+ {
+ "pattern":"\\Qorg/asamk/signal/manager/config/whisper.store\\E"
+ },
+ {
+ "pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E"
+ },
+ {
+ "pattern":"com/google/i18n/phonenumbers/data/.*"
+ }
]},
- "bundles":[{"name":"net.sourceforge.argparse4j.internal.ArgumentParserImpl"}]
+ "bundles":[{
+ "name":"net.sourceforge.argparse4j.internal.ArgumentParserImpl"
+ }]
}
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-#!/usr/bin/env sh
+#!/bin/sh
#
-# Copyright 2015 the original author or authors.
+# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
#
##############################################################################
-##
-## Gradle start up script for UN*X
-##
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
##############################################################################
# Attempt to set APP_HOME
+
# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
+APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
+MAX_FD=maximum
warn () {
echo "$*"
-}
+} >&2
die () {
echo
echo "$*"
echo
exit 1
-}
+} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACMD=$JAVA_HOME/jre/sh/java
else
- JAVACMD="$JAVA_HOME/bin/java"
+ JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
location of your Java installation."
fi
else
- JAVACMD="java"
+ JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
fi
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
# Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
fi
- i=`expr $i + 1`
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
done
- case $i in
- 0) set -- ;;
- 1) set -- "$args0" ;;
- 2) set -- "$args0" "$args1" ;;
- 3) set -- "$args0" "$args1" "$args2" ;;
- 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
fi
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=`save "$@"`
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
exec "$JAVACMD" "$@"
}
java {
- sourceCompatibility = JavaVersion.VERSION_11
- targetCompatibility = JavaVersion.VERSION_11
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
}
repositories {
}
dependencies {
- api("com.github.turasa:signal-service-java:2.15.3_unofficial_28")
+ api("com.github.turasa:signal-service-java:2.15.3_unofficial_31")
+ api("com.fasterxml.jackson.core", "jackson-databind", "2.13.0")
implementation("com.google.protobuf:protobuf-javalite:3.10.0")
implementation("org.bouncycastle:bcprov-jdk15on:1.69")
- implementation("org.slf4j:slf4j-api:1.7.30")
+ implementation("org.slf4j:slf4j-api:1.7.32")
}
configurations {
import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
-public class DeviceLinkInfo {
-
- final String deviceIdentifier;
- final ECPublicKey deviceKey;
+public record DeviceLinkInfo(String deviceIdentifier, ECPublicKey deviceKey) {
public static DeviceLinkInfo parseDeviceLinkUri(URI linkUri) throws InvalidKeyException {
final var rawQuery = linkUri.getRawQuery();
return map;
}
- public DeviceLinkInfo(final String deviceIdentifier, final ECPublicKey deviceKey) {
- this.deviceIdentifier = deviceIdentifier;
- this.deviceKey = deviceKey;
- }
-
public URI createDeviceLinkUri() {
final var deviceKeyString = Base64.getEncoder().encodeToString(deviceKey.serialize()).replace("=", "");
try {
- return new URI("tsdevice:/?uuid="
+ return new URI("sgnl://linkdevice?uuid="
+ URLEncoder.encode(deviceIdentifier, StandardCharsets.UTF_8)
+ "&pub_key="
+ URLEncoder.encode(deviceKeyString, StandardCharsets.UTF_8));
package org.asamk.signal.manager;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
import java.util.List;
-public class JsonStickerPack {
-
- @JsonProperty
- public String title;
-
- @JsonProperty
- public String author;
-
- @JsonProperty
- public JsonSticker cover;
-
- @JsonProperty
- public List<JsonSticker> stickers;
-
- // For deserialization
- private JsonStickerPack() {
- }
-
- public JsonStickerPack(
- final String title, final String author, final JsonSticker cover, final List<JsonSticker> stickers
- ) {
- this.title = title;
- this.author = author;
- this.cover = cover;
- this.stickers = stickers;
- }
-
- public static class JsonSticker {
-
- @JsonProperty
- public String emoji;
-
- @JsonProperty
- public String file;
-
- @JsonProperty
- public String contentType;
-
- // For deserialization
- private JsonSticker() {
- }
+public record JsonStickerPack(String title, String author, JsonSticker cover, List<JsonSticker> stickers) {
- public JsonSticker(final String emoji, final String file, final String contentType) {
- this.emoji = emoji;
- this.file = file;
- this.contentType = contentType;
- }
- }
+ public record JsonSticker(String emoji, String file, String contentType) {}
}
public void log(final int priority, final String tag, final String message) {
final var logMessage = String.format("[%s]: %s", tag, message);
switch (priority) {
- case SignalProtocolLogger.VERBOSE:
- logger.trace(logMessage);
- break;
- case SignalProtocolLogger.DEBUG:
- logger.debug(logMessage);
- break;
- case SignalProtocolLogger.INFO:
- logger.info(logMessage);
- break;
- case SignalProtocolLogger.WARN:
- logger.warn(logMessage);
- break;
- case SignalProtocolLogger.ERROR:
- case SignalProtocolLogger.ASSERT:
- logger.error(logMessage);
- break;
+ case SignalProtocolLogger.VERBOSE -> logger.trace(logMessage);
+ case SignalProtocolLogger.DEBUG -> logger.debug(logMessage);
+ case SignalProtocolLogger.INFO -> logger.info(logMessage);
+ case SignalProtocolLogger.WARN -> logger.warn(logMessage);
+ case SignalProtocolLogger.ERROR, SignalProtocolLogger.ASSERT -> logger.error(logMessage);
}
}
}
import org.asamk.signal.manager.api.SendGroupMessageResults;
import org.asamk.signal.manager.api.SendMessageResults;
import org.asamk.signal.manager.api.TypingAction;
+import org.asamk.signal.manager.api.UpdateGroup;
import org.asamk.signal.manager.config.ServiceConfig;
import org.asamk.signal.manager.config.ServiceEnvironment;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
-import org.asamk.signal.manager.groups.GroupLinkState;
import org.asamk.signal.manager.groups.GroupNotFoundException;
-import org.asamk.signal.manager.groups.GroupPermission;
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.storage.recipients.Contact;
import org.asamk.signal.manager.storage.recipients.Profile;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
-import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional;
) throws IOException, NotRegisteredException {
var pathConfig = PathConfig.createDefault(settingsPath);
- if (!SignalAccount.userExists(pathConfig.getDataPath(), number)) {
+ if (!SignalAccount.userExists(pathConfig.dataPath(), number)) {
throw new NotRegisteredException();
}
- var account = SignalAccount.load(pathConfig.getDataPath(), number, true, trustNewIdentity);
+ var account = SignalAccount.load(pathConfig.dataPath(), number, true, trustNewIdentity);
if (!account.isRegistered()) {
throw new NotRegisteredException();
static List<String> getAllLocalNumbers(File settingsPath) {
var pathConfig = PathConfig.createDefault(settingsPath);
- final var dataPath = pathConfig.getDataPath();
+ final var dataPath = pathConfig.dataPath();
final var files = dataPath.listFiles();
if (files == null) {
) throws IOException, AttachmentInvalidException;
SendGroupMessageResults updateGroup(
- GroupId groupId,
- String name,
- String description,
- Set<RecipientIdentifier.Single> members,
- Set<RecipientIdentifier.Single> removeMembers,
- Set<RecipientIdentifier.Single> admins,
- Set<RecipientIdentifier.Single> removeAdmins,
- boolean resetGroupLink,
- GroupLinkState groupLinkState,
- GroupPermission addMemberPermission,
- GroupPermission editDetailsPermission,
- File avatarFile,
- Integer expirationTimer,
- Boolean isAnnouncementGroup
+ final GroupId groupId, final UpdateGroup updateGroup
) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException;
Pair<GroupId, SendGroupMessageResults> joinGroup(
void setGroupBlocked(
GroupId groupId, boolean blocked
- ) throws GroupNotFoundException, IOException;
+ ) throws GroupNotFoundException, IOException, NotMasterDeviceException;
void setExpirationTimer(
RecipientIdentifier.Single recipient, int messageExpirationTimer
void requestAllSyncData() throws IOException;
- void receiveMessages(
- long timeout,
- TimeUnit unit,
- boolean returnOnTimeout,
- boolean ignoreAttachments,
- ReceiveMessageHandler handler
- ) throws IOException;
+ /**
+ * Add a handler to receive new messages.
+ * Will start receiving messages from server, if not already started.
+ */
+ void addReceiveHandler(ReceiveMessageHandler handler);
+
+ /**
+ * Remove a handler to receive new messages.
+ * Will stop receiving messages from server, if this was the last registered receiver.
+ */
+ void removeReceiveHandler(ReceiveMessageHandler handler);
+
+ boolean isReceiving();
+
+ /**
+ * Receive new messages from server, returns if no new message arrive in a timespan of timeout.
+ */
+ void receiveMessages(long timeout, TimeUnit unit, ReceiveMessageHandler handler) throws IOException;
+
+ /**
+ * Receive new messages from server, returns only if the thread is interrupted.
+ */
+ void receiveMessages(ReceiveMessageHandler handler) throws IOException;
+
+ void setIgnoreAttachments(boolean ignoreAttachments);
boolean hasCaughtUpWithOldMessages();
boolean trustIdentityAllKeys(RecipientIdentifier.Single recipient);
- String computeSafetyNumber(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey);
-
SignalServiceAddress resolveSignalServiceAddress(SignalServiceAddress address);
@Override
import org.asamk.signal.manager.api.SendGroupMessageResults;
import org.asamk.signal.manager.api.SendMessageResults;
import org.asamk.signal.manager.api.TypingAction;
+import org.asamk.signal.manager.api.UpdateGroup;
import org.asamk.signal.manager.config.ServiceConfig;
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
-import org.asamk.signal.manager.groups.GroupLinkState;
import org.asamk.signal.manager.groups.GroupNotFoundException;
-import org.asamk.signal.manager.groups.GroupPermission;
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.helper.ContactHelper;
import org.asamk.signal.manager.helper.GroupHelper;
import org.asamk.signal.manager.helper.GroupV2Helper;
+import org.asamk.signal.manager.helper.IdentityHelper;
import org.asamk.signal.manager.helper.IncomingMessageHandler;
import org.asamk.signal.manager.helper.PinHelper;
import org.asamk.signal.manager.helper.PreKeyHelper;
import org.asamk.signal.manager.storage.stickers.StickerPackId;
import org.asamk.signal.manager.util.KeyUtils;
import org.asamk.signal.manager.util.StickerUtils;
-import org.asamk.signal.manager.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.ecc.ECPublicKey;
-import org.whispersystems.libsignal.fingerprint.Fingerprint;
-import org.whispersystems.libsignal.fingerprint.FingerprintParsingException;
-import org.whispersystems.libsignal.fingerprint.FingerprintVersionMismatchException;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalSessionLock;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.SignatureException;
-import java.util.Arrays;
import java.util.Collection;
-import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReentrantLock;
-import java.util.function.Function;
import java.util.stream.Collectors;
import static org.asamk.signal.manager.config.ServiceConfig.capabilities;
private final ContactHelper contactHelper;
private final IncomingMessageHandler incomingMessageHandler;
private final PreKeyHelper preKeyHelper;
+ private final IdentityHelper identityHelper;
private final Context context;
private boolean hasCaughtUpWithOldMessages = false;
+ private boolean ignoreAttachments = false;
+
+ private Thread receiveThread;
+ private final Set<ReceiveMessageHandler> messageHandlers = new HashSet<>();
+ private boolean isReceivingSynchronous;
ManagerImpl(
SignalAccount account,
account.getSignalProtocolStore(),
executor,
sessionLock);
- final var avatarStore = new AvatarStore(pathConfig.getAvatarsPath());
- final var attachmentStore = new AttachmentStore(pathConfig.getAttachmentsPath());
- final var stickerPackStore = new StickerPackStore(pathConfig.getStickerPacksPath());
+ final var avatarStore = new AvatarStore(pathConfig.avatarsPath());
+ final var attachmentStore = new AttachmentStore(pathConfig.attachmentsPath());
+ final var stickerPackStore = new StickerPackStore(pathConfig.stickerPacksPath());
this.attachmentHelper = new AttachmentHelper(dependencies, attachmentStore);
this.pinHelper = new PinHelper(dependencies.getKeyBackupService());
- final var unidentifiedAccessHelper = new UnidentifiedAccessHelper(account::getProfileKey,
- account.getProfileStore()::getProfileKey,
- this::getRecipientProfile,
- this::getSenderCertificate);
+ final var unidentifiedAccessHelper = new UnidentifiedAccessHelper(account,
+ dependencies,
+ account::getProfileKey,
+ this::getRecipientProfile);
this.profileHelper = new ProfileHelper(account,
dependencies,
avatarStore,
- account.getProfileStore()::getProfileKey,
unidentifiedAccessHelper::getAccessFor,
this::resolveSignalServiceAddress);
final GroupV2Helper groupV2Helper = new GroupV2Helper(profileHelper::getRecipientProfileKeyCredential,
syncHelper,
this::getRecipientProfile,
jobExecutor);
+ this.identityHelper = new IdentityHelper(account,
+ dependencies,
+ this::resolveSignalServiceAddress,
+ syncHelper,
+ profileHelper);
}
@Override
public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException {
var info = DeviceLinkInfo.parseDeviceLinkUri(linkUri);
- addDevice(info.deviceIdentifier, info.deviceKey);
+ addDevice(info.deviceIdentifier(), info.deviceKey());
}
private void addDevice(String deviceIdentifier, ECPublicKey deviceKey) throws IOException, InvalidKeyException {
.map(account.getRecipientStore()::resolveRecipientAddress)
.collect(Collectors.toSet()),
groupInfo.isBlocked(),
- groupInfo.getMessageExpirationTime(),
- groupInfo.isAnnouncementGroup(),
- groupInfo.isMember(account.getSelfRecipientId()));
+ groupInfo.getMessageExpirationTimer(),
+ groupInfo.getPermissionAddMember(),
+ groupInfo.getPermissionEditDetails(),
+ groupInfo.getPermissionSendMessage(),
+ groupInfo.isMember(account.getSelfRecipientId()),
+ groupInfo.isAdmin(account.getSelfRecipientId()));
}
@Override
@Override
public SendGroupMessageResults updateGroup(
- GroupId groupId,
- String name,
- String description,
- Set<RecipientIdentifier.Single> members,
- Set<RecipientIdentifier.Single> removeMembers,
- Set<RecipientIdentifier.Single> admins,
- Set<RecipientIdentifier.Single> removeAdmins,
- boolean resetGroupLink,
- GroupLinkState groupLinkState,
- GroupPermission addMemberPermission,
- GroupPermission editDetailsPermission,
- File avatarFile,
- Integer expirationTimer,
- Boolean isAnnouncementGroup
+ final GroupId groupId, final UpdateGroup updateGroup
) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException {
return groupHelper.updateGroup(groupId,
- name,
- description,
- members == null ? null : resolveRecipients(members),
- removeMembers == null ? null : resolveRecipients(removeMembers),
- admins == null ? null : resolveRecipients(admins),
- removeAdmins == null ? null : resolveRecipients(removeAdmins),
- resetGroupLink,
- groupLinkState,
- addMemberPermission,
- editDetailsPermission,
- avatarFile,
- expirationTimer,
- isAnnouncementGroup);
+ updateGroup.getName(),
+ updateGroup.getDescription(),
+ updateGroup.getMembers() == null ? null : resolveRecipients(updateGroup.getMembers()),
+ updateGroup.getRemoveMembers() == null ? null : resolveRecipients(updateGroup.getRemoveMembers()),
+ updateGroup.getAdmins() == null ? null : resolveRecipients(updateGroup.getAdmins()),
+ updateGroup.getRemoveAdmins() == null ? null : resolveRecipients(updateGroup.getRemoveAdmins()),
+ updateGroup.isResetGroupLink(),
+ updateGroup.getGroupLinkState(),
+ updateGroup.getAddMemberPermission(),
+ updateGroup.getEditDetailsPermission(),
+ updateGroup.getAvatarFile(),
+ updateGroup.getExpirationTimer(),
+ updateGroup.getIsAnnouncementGroup());
}
@Override
long timestamp = System.currentTimeMillis();
messageBuilder.withTimestamp(timestamp);
for (final var recipient : recipients) {
- if (recipient instanceof RecipientIdentifier.Single) {
- final var recipientId = resolveRecipient((RecipientIdentifier.Single) recipient);
+ if (recipient instanceof RecipientIdentifier.Single single) {
+ final var recipientId = resolveRecipient(single);
final var result = sendHelper.sendMessage(messageBuilder, recipientId);
results.put(recipient, List.of(result));
} else if (recipient instanceof RecipientIdentifier.NoteToSelf) {
final var result = sendHelper.sendSelfMessage(messageBuilder);
results.put(recipient, List.of(result));
- } else if (recipient instanceof RecipientIdentifier.Group) {
- final var groupId = ((RecipientIdentifier.Group) recipient).groupId;
- final var result = sendHelper.sendAsGroupMessage(messageBuilder, groupId);
+ } else if (recipient instanceof RecipientIdentifier.Group group) {
+ final var result = sendHelper.sendAsGroupMessage(messageBuilder, group.groupId);
results.put(recipient, result);
}
}
private void applyMessage(
final SignalServiceDataMessage.Builder messageBuilder, final Message message
) throws AttachmentInvalidException, IOException {
- messageBuilder.withBody(message.getMessageText());
- final var attachments = message.getAttachments();
+ messageBuilder.withBody(message.messageText());
+ final var attachments = message.attachments();
if (attachments != null) {
messageBuilder.withAttachments(attachmentHelper.uploadAttachments(attachments));
}
@Override
public void setGroupBlocked(
final GroupId groupId, final boolean blocked
- ) throws GroupNotFoundException, IOException {
+ ) throws GroupNotFoundException, IOException, NotMasterDeviceException {
+ if (!account.isMasterDevice()) {
+ throw new NotMasterDeviceException();
+ }
groupHelper.setGroupBlocked(groupId, blocked);
// TODO cycle our profile key
syncHelper.sendBlockedList();
}
}
- private byte[] getSenderCertificate() {
- byte[] certificate;
- try {
- if (account.isPhoneNumberShared()) {
- certificate = dependencies.getAccountManager().getSenderCertificate();
- } else {
- certificate = dependencies.getAccountManager().getSenderCertificateForPhoneNumberPrivacy();
- }
- } catch (IOException e) {
- logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage());
- return null;
- }
- // TODO cache for a day
- return certificate;
- }
-
private RecipientId refreshRegisteredUser(RecipientId recipientId) throws IOException {
final var address = resolveSignalServiceAddress(recipientId);
if (!address.getNumber().isPresent()) {
return registeredUsers;
}
- private void retryFailedReceivedMessages(ReceiveMessageHandler handler, boolean ignoreAttachments) {
+ private void retryFailedReceivedMessages(ReceiveMessageHandler handler) {
Set<HandleAction> queuedActions = new HashSet<>();
for (var cachedMessage : account.getMessageCache().getCachedMessages()) {
- var actions = retryFailedReceivedMessage(handler, ignoreAttachments, cachedMessage);
+ var actions = retryFailedReceivedMessage(handler, cachedMessage);
if (actions != null) {
queuedActions.addAll(actions);
}
}
private List<HandleAction> retryFailedReceivedMessage(
- final ReceiveMessageHandler handler, final boolean ignoreAttachments, final CachedMessage cachedMessage
+ final ReceiveMessageHandler handler, final CachedMessage cachedMessage
) {
var envelope = cachedMessage.loadEnvelope();
if (envelope == null) {
}
@Override
- public void receiveMessages(
- long timeout,
- TimeUnit unit,
- boolean returnOnTimeout,
- boolean ignoreAttachments,
- ReceiveMessageHandler handler
+ public void addReceiveHandler(final ReceiveMessageHandler handler) {
+ if (isReceivingSynchronous) {
+ throw new IllegalStateException("Already receiving message synchronously.");
+ }
+ synchronized (messageHandlers) {
+ messageHandlers.add(handler);
+
+ startReceiveThreadIfRequired();
+ }
+ }
+
+ private void startReceiveThreadIfRequired() {
+ if (receiveThread != null) {
+ return;
+ }
+ receiveThread = new Thread(() -> {
+ while (!Thread.interrupted()) {
+ try {
+ receiveMessagesInternal(1L, TimeUnit.HOURS, false, (envelope, decryptedContent, e) -> {
+ synchronized (messageHandlers) {
+ for (ReceiveMessageHandler h : messageHandlers) {
+ try {
+ h.handleMessage(envelope, decryptedContent, e);
+ } catch (Exception ex) {
+ logger.warn("Message handler failed, ignoring", ex);
+ }
+ }
+ }
+ });
+ break;
+ } catch (IOException e) {
+ logger.warn("Receiving messages failed, retrying", e);
+ }
+ }
+ hasCaughtUpWithOldMessages = false;
+ synchronized (messageHandlers) {
+ receiveThread = null;
+
+ // Check if in the meantime another handler has been registered
+ if (!messageHandlers.isEmpty()) {
+ startReceiveThreadIfRequired();
+ }
+ }
+ });
+
+ receiveThread.start();
+ }
+
+ @Override
+ public void removeReceiveHandler(final ReceiveMessageHandler handler) {
+ final Thread thread;
+ synchronized (messageHandlers) {
+ thread = receiveThread;
+ receiveThread = null;
+ messageHandlers.remove(handler);
+ if (!messageHandlers.isEmpty() || isReceivingSynchronous) {
+ return;
+ }
+ }
+
+ stopReceiveThread(thread);
+ }
+
+ private void stopReceiveThread(final Thread thread) {
+ thread.interrupt();
+ try {
+ thread.join();
+ } catch (InterruptedException ignored) {
+ }
+ }
+
+ @Override
+ public boolean isReceiving() {
+ if (isReceivingSynchronous) {
+ return true;
+ }
+ synchronized (messageHandlers) {
+ return messageHandlers.size() > 0;
+ }
+ }
+
+ @Override
+ public void receiveMessages(long timeout, TimeUnit unit, ReceiveMessageHandler handler) throws IOException {
+ receiveMessages(timeout, unit, true, handler);
+ }
+
+ @Override
+ public void receiveMessages(ReceiveMessageHandler handler) throws IOException {
+ receiveMessages(1L, TimeUnit.HOURS, false, handler);
+ }
+
+ private void receiveMessages(
+ long timeout, TimeUnit unit, boolean returnOnTimeout, ReceiveMessageHandler handler
+ ) throws IOException {
+ if (isReceiving()) {
+ throw new IllegalStateException("Already receiving message.");
+ }
+ isReceivingSynchronous = true;
+ receiveThread = Thread.currentThread();
+ try {
+ receiveMessagesInternal(timeout, unit, returnOnTimeout, handler);
+ } finally {
+ receiveThread = null;
+ hasCaughtUpWithOldMessages = false;
+ isReceivingSynchronous = false;
+ }
+ }
+
+ private void receiveMessagesInternal(
+ long timeout, TimeUnit unit, boolean returnOnTimeout, ReceiveMessageHandler handler
) throws IOException {
- retryFailedReceivedMessages(handler, ignoreAttachments);
+ retryFailedReceivedMessages(handler);
Set<HandleAction> queuedActions = new HashSet<>();
signalWebSocket.connect();
hasCaughtUpWithOldMessages = false;
+ var backOffCounter = 0;
+ final var MAX_BACKOFF_COUNTER = 9;
while (!Thread.interrupted()) {
SignalServiceEnvelope envelope;
// store message on disk, before acknowledging receipt to the server
cachedMessage[0] = account.getMessageCache().cacheMessage(envelope1, recipientId);
});
+ backOffCounter = 0;
+
if (result.isPresent()) {
envelope = result.get();
logger.debug("New message received from server");
} else {
throw e;
}
- } catch (WebSocketUnavailableException e) {
- logger.debug("Pipe unexpectedly unavailable, connecting");
- signalWebSocket.connect();
- continue;
+ } catch (IOException e) {
+ logger.debug("Pipe unexpectedly unavailable: {}", e.getMessage());
+ if (e instanceof WebSocketUnavailableException || "Connection closed!".equals(e.getMessage())) {
+ final var sleepMilliseconds = 100 * (long) Math.pow(2, backOffCounter);
+ backOffCounter = Math.min(backOffCounter + 1, MAX_BACKOFF_COUNTER);
+ logger.warn("Connection closed unexpectedly, reconnecting in {} ms", sleepMilliseconds);
+ try {
+ Thread.sleep(sleepMilliseconds);
+ } catch (InterruptedException interruptedException) {
+ return;
+ }
+ hasCaughtUpWithOldMessages = false;
+ signalWebSocket.connect();
+ continue;
+ }
+ throw e;
} catch (TimeoutException e) {
+ backOffCounter = 0;
if (returnOnTimeout) return;
continue;
}
if (hasCaughtUpWithOldMessages) {
handleQueuedActions(queuedActions);
+ queuedActions.clear();
}
if (cachedMessage[0] != null) {
if (exception instanceof UntrustedIdentityException) {
+ logger.debug("Keeping message with untrusted identity in message cache");
final var address = ((UntrustedIdentityException) exception).getSender();
final var recipientId = resolveRecipient(address);
if (!envelope.hasSourceUuid()) {
}
}
handleQueuedActions(queuedActions);
+ queuedActions.clear();
+ }
+
+ @Override
+ public void setIgnoreAttachments(final boolean ignoreAttachments) {
+ this.ignoreAttachments = ignoreAttachments;
}
@Override
}
private void handleQueuedActions(final Collection<HandleAction> queuedActions) {
+ logger.debug("Handling message actions");
var interrupted = false;
for (var action : queuedActions) {
try {
return toGroup(groupHelper.getGroup(groupId));
}
- public GroupInfo getGroupInfo(GroupId groupId) {
+ private GroupInfo getGroupInfo(GroupId groupId) {
return groupHelper.getGroup(groupId);
}
}
final var address = account.getRecipientStore().resolveRecipientAddress(identityInfo.getRecipientId());
+ final var scannableFingerprint = identityHelper.computeSafetyNumberForScanning(identityInfo.getRecipientId(),
+ identityInfo.getIdentityKey());
return new Identity(address,
identityInfo.getIdentityKey(),
- computeSafetyNumber(address.toSignalServiceAddress(), identityInfo.getIdentityKey()),
- computeSafetyNumberForScanning(address.toSignalServiceAddress(), identityInfo.getIdentityKey()),
+ identityHelper.computeSafetyNumber(identityInfo.getRecipientId(), identityInfo.getIdentityKey()),
+ scannableFingerprint == null ? null : scannableFingerprint.getSerialized(),
identityInfo.getTrustLevel(),
identityInfo.getDateAdded());
}
} catch (UnregisteredUserException e) {
return false;
}
- return trustIdentity(recipientId,
- identityKey -> Arrays.equals(identityKey.serialize(), fingerprint),
- TrustLevel.TRUSTED_VERIFIED);
+ return identityHelper.trustIdentityVerified(recipientId, fingerprint);
}
/**
} catch (UnregisteredUserException e) {
return false;
}
- var address = resolveSignalServiceAddress(recipientId);
- return trustIdentity(recipientId,
- identityKey -> safetyNumber.equals(computeSafetyNumber(address, identityKey)),
- TrustLevel.TRUSTED_VERIFIED);
+ return identityHelper.trustIdentityVerifiedSafetyNumber(recipientId, safetyNumber);
}
/**
} catch (UnregisteredUserException e) {
return false;
}
- var address = resolveSignalServiceAddress(recipientId);
- return trustIdentity(recipientId, identityKey -> {
- final var fingerprint = computeSafetyNumberFingerprint(address, identityKey);
- try {
- return fingerprint != null && fingerprint.getScannableFingerprint().compareTo(safetyNumber);
- } catch (FingerprintVersionMismatchException | FingerprintParsingException e) {
- return false;
- }
- }, TrustLevel.TRUSTED_VERIFIED);
+ return identityHelper.trustIdentityVerifiedSafetyNumber(recipientId, safetyNumber);
}
/**
} catch (UnregisteredUserException e) {
return false;
}
- return trustIdentity(recipientId, identityKey -> true, TrustLevel.TRUSTED_UNVERIFIED);
- }
-
- private boolean trustIdentity(
- RecipientId recipientId, Function<IdentityKey, Boolean> verifier, TrustLevel trustLevel
- ) {
- var identity = account.getIdentityKeyStore().getIdentity(recipientId);
- if (identity == null) {
- return false;
- }
-
- if (!verifier.apply(identity.getIdentityKey())) {
- return false;
- }
-
- account.getIdentityKeyStore().setIdentityTrustLevel(recipientId, identity.getIdentityKey(), trustLevel);
- try {
- var address = resolveSignalServiceAddress(recipientId);
- syncHelper.sendVerifiedMessage(address, identity.getIdentityKey(), trustLevel);
- } catch (IOException e) {
- logger.warn("Failed to send verification sync message: {}", e.getMessage());
- }
-
- return true;
+ return identityHelper.trustIdentityAllKeys(recipientId);
}
private void handleIdentityFailure(
final RecipientId recipientId, final SendMessageResult.IdentityFailure identityFailure
) {
- final var identityKey = identityFailure.getIdentityKey();
- if (identityKey != null) {
- final var newIdentity = account.getIdentityKeyStore().saveIdentity(recipientId, identityKey, new Date());
- if (newIdentity) {
- account.getSessionStore().archiveSessions(recipientId);
- }
- } else {
- // Retrieve profile to get the current identity key from the server
- profileHelper.refreshRecipientProfile(recipientId);
- }
- }
-
- @Override
- public String computeSafetyNumber(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) {
- final Fingerprint fingerprint = computeSafetyNumberFingerprint(theirAddress, theirIdentityKey);
- return fingerprint == null ? null : fingerprint.getDisplayableFingerprint().getDisplayText();
- }
-
- private byte[] computeSafetyNumberForScanning(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) {
- final Fingerprint fingerprint = computeSafetyNumberFingerprint(theirAddress, theirIdentityKey);
- return fingerprint == null ? null : fingerprint.getScannableFingerprint().getSerialized();
- }
-
- private Fingerprint computeSafetyNumberFingerprint(
- final SignalServiceAddress theirAddress, final IdentityKey theirIdentityKey
- ) {
- return Utils.computeSafetyNumber(capabilities.isUuid(),
- account.getSelfAddress(),
- account.getIdentityKeyPair().getPublicKey(),
- theirAddress,
- theirIdentityKey);
+ this.identityHelper.handleIdentityFailure(recipientId, identityFailure);
}
@Override
}
private void close(boolean closeAccount) throws IOException {
+ Thread thread;
+ synchronized (messageHandlers) {
+ messageHandlers.clear();
+ thread = receiveThread;
+ receiveThread = null;
+ }
+ if (thread != null) {
+ stopReceiveThread(thread);
+ }
executor.shutdown();
dependencies.getSignalWebSocket().disconnect();
}
account = null;
}
-
}
import java.io.File;
-public class PathConfig {
-
- private final File dataPath;
- private final File attachmentsPath;
- private final File avatarsPath;
- private final File stickerPacksPath;
+public record PathConfig(
+ File dataPath, File attachmentsPath, File avatarsPath, File stickerPacksPath
+) {
public static PathConfig createDefault(final File settingsPath) {
return new PathConfig(new File(settingsPath, "data"),
new File(settingsPath, "avatars"),
new File(settingsPath, "stickers"));
}
-
- private PathConfig(
- final File dataPath, final File attachmentsPath, final File avatarsPath, final File stickerPacksPath
- ) {
- this.dataPath = dataPath;
- this.attachmentsPath = attachmentsPath;
- this.avatarsPath = avatarsPath;
- this.stickerPacksPath = stickerPacksPath;
- }
-
- public File getDataPath() {
- return dataPath;
- }
-
- public File getAttachmentsPath() {
- return attachmentsPath;
- }
-
- public File getAvatarsPath() {
- return avatarsPath;
- }
-
- public File getStickerPacksPath() {
- return stickerPacksPath;
- }
}
logger.info("Received link information from {}, linking in progress ...", number);
- if (SignalAccount.userExists(pathConfig.getDataPath(), number) && !canRelinkExistingAccount(number)) {
- throw new UserAlreadyExists(number, SignalAccount.getFileName(pathConfig.getDataPath(), number));
+ if (SignalAccount.userExists(pathConfig.dataPath(), number) && !canRelinkExistingAccount(number)) {
+ throw new UserAlreadyExists(number, SignalAccount.getFileName(pathConfig.dataPath(), number));
}
var encryptedDeviceName = deviceName == null
SignalAccount account = null;
try {
- account = SignalAccount.createOrUpdateLinkedAccount(pathConfig.getDataPath(),
+ account = SignalAccount.createOrUpdateLinkedAccount(pathConfig.dataPath(),
number,
ret.getUuid(),
password,
try {
m.refreshPreKeys();
} catch (Exception e) {
- logger.error("Failed to check new account state.");
- throw e;
+ logger.error("Failed to refresh pre keys.");
}
logger.debug("Requesting sync data");
try {
m.requestAllSyncData();
} catch (Exception e) {
- logger.error("Failed to request sync messages from linked device.");
- throw e;
+ logger.error(
+ "Failed to request sync messages from linked device, data can be requested again with `sendSyncRequest`.");
}
final var result = m;
private boolean canRelinkExistingAccount(final String number) throws IOException {
final SignalAccount signalAccount;
try {
- signalAccount = SignalAccount.load(pathConfig.getDataPath(), number, false, TrustNewIdentity.ON_FIRST_USE);
+ signalAccount = SignalAccount.load(pathConfig.dataPath(), number, false, TrustNewIdentity.ON_FIRST_USE);
} catch (IOException e) {
logger.debug("Account in use or failed to load.", e);
return false;
var pathConfig = PathConfig.createDefault(settingsPath);
final var serviceConfiguration = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent);
- if (!SignalAccount.userExists(pathConfig.getDataPath(), number)) {
+ if (!SignalAccount.userExists(pathConfig.dataPath(), number)) {
var identityKey = KeyUtils.generateIdentityKeyPair();
var registrationId = KeyHelper.generateRegistrationId(false);
var profileKey = KeyUtils.createProfileKey();
- var account = SignalAccount.create(pathConfig.getDataPath(),
+ var account = SignalAccount.create(pathConfig.dataPath(),
number,
identityKey,
registrationId,
return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
}
- var account = SignalAccount.load(pathConfig.getDataPath(), number, true, TrustNewIdentity.ON_FIRST_USE);
+ var account = SignalAccount.load(pathConfig.dataPath(), number, true, TrustNewIdentity.ON_FIRST_USE);
return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
}
private synchronized void onStateChange(WebSocketConnectionState connectionState, HealthState healthState) {
switch (connectionState) {
- case CONNECTED:
- logger.debug("WebSocket is now connected");
- break;
- case AUTHENTICATION_FAILED:
- logger.debug("WebSocket authentication failed");
- break;
- case FAILED:
- logger.debug("WebSocket connection failed");
- break;
+ case CONNECTED -> logger.debug("WebSocket is now connected");
+ case AUTHENTICATION_FAILED -> logger.debug("WebSocket authentication failed");
+ case FAILED -> logger.debug("WebSocket connection failed");
}
healthState.needsKeepAlive = connectionState == WebSocketConnectionState.CONNECTED;
if (healthState.mismatchErrorTracker.addSample(System.currentTimeMillis())) {
logger.warn("Received too many mismatch device errors, forcing new websockets.");
signalWebSocket.forceNewWebSockets();
+ signalWebSocket.connect();
}
}
}
+ " needed by: "
+ keepAliveRequiredSinceTime);
signalWebSocket.forceNewWebSockets();
+ signalWebSocket.connect();
} else {
signalWebSocket.sendKeepAlive();
}
}
public static TrustLevel fromIdentityState(ContactRecord.IdentityState identityState) {
- switch (identityState) {
- case DEFAULT:
- return TRUSTED_UNVERIFIED;
- case UNVERIFIED:
- return UNTRUSTED;
- case VERIFIED:
- return TRUSTED_VERIFIED;
- case UNRECOGNIZED:
- return null;
- }
- throw new RuntimeException("Unknown identity state: " + identityState);
+ return switch (identityState) {
+ case DEFAULT -> TRUSTED_UNVERIFIED;
+ case UNVERIFIED -> UNTRUSTED;
+ case VERIFIED -> TRUSTED_VERIFIED;
+ case UNRECOGNIZED -> null;
+ };
}
public static TrustLevel fromVerifiedState(VerifiedMessage.VerifiedState verifiedState) {
- switch (verifiedState) {
- case DEFAULT:
- return TRUSTED_UNVERIFIED;
- case UNVERIFIED:
- return UNTRUSTED;
- case VERIFIED:
- return TRUSTED_VERIFIED;
- }
- throw new RuntimeException("Unknown verified state: " + verifiedState);
+ return switch (verifiedState) {
+ case DEFAULT -> TRUSTED_UNVERIFIED;
+ case UNVERIFIED -> UNTRUSTED;
+ case VERIFIED -> TRUSTED_VERIFIED;
+ };
}
public VerifiedMessage.VerifiedState toVerifiedState() {
- switch (this) {
- case TRUSTED_UNVERIFIED:
- return VerifiedMessage.VerifiedState.DEFAULT;
- case UNTRUSTED:
- return VerifiedMessage.VerifiedState.UNVERIFIED;
- case TRUSTED_VERIFIED:
- return VerifiedMessage.VerifiedState.VERIFIED;
- }
- throw new RuntimeException("Unknown verified state: " + this);
+ return switch (this) {
+ case TRUSTED_UNVERIFIED -> VerifiedMessage.VerifiedState.DEFAULT;
+ case UNTRUSTED -> VerifiedMessage.VerifiedState.UNVERIFIED;
+ case TRUSTED_VERIFIED -> VerifiedMessage.VerifiedState.VERIFIED;
+ };
}
}
}
private static int envelopeTypeToCiphertextMessageType(int envelopeType) {
- switch (envelopeType) {
- case SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE:
- return CiphertextMessage.PREKEY_TYPE;
- case SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE:
- return CiphertextMessage.SENDERKEY_TYPE;
- case SignalServiceProtos.Envelope.Type.PLAINTEXT_CONTENT_VALUE:
- return CiphertextMessage.PLAINTEXT_CONTENT_TYPE;
- case SignalServiceProtos.Envelope.Type.CIPHERTEXT_VALUE:
- default:
- return CiphertextMessage.WHISPER_TYPE;
- }
+ return switch (envelopeType) {
+ case SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE -> CiphertextMessage.PREKEY_TYPE;
+ case SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE -> CiphertextMessage.SENDERKEY_TYPE;
+ case SignalServiceProtos.Envelope.Type.PLAINTEXT_CONTENT_VALUE -> CiphertextMessage.PLAINTEXT_CONTENT_TYPE;
+ default -> CiphertextMessage.WHISPER_TYPE;
+ };
}
@Override
package org.asamk.signal.manager.api;
-public class Device {
-
- private final long id;
- private final String name;
- private final long created;
- private final long lastSeen;
- private final boolean thisDevice;
-
- public Device(long id, String name, long created, long lastSeen, final boolean thisDevice) {
- this.id = id;
- this.name = name;
- this.created = created;
- this.lastSeen = lastSeen;
- this.thisDevice = thisDevice;
- }
-
- public long getId() {
- return id;
- }
-
- public String getName() {
- return name;
- }
-
- public long getCreated() {
- return created;
- }
-
- public long getLastSeen() {
- return lastSeen;
- }
-
- public boolean isThisDevice() {
- return thisDevice;
- }
-}
+public record Device(long id, String name, long created, long lastSeen, boolean isThisDevice) {}
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
+import org.asamk.signal.manager.groups.GroupPermission;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import java.util.Set;
-public class Group {
-
- private final GroupId groupId;
- private final String title;
- private final String description;
- private final GroupInviteLinkUrl groupInviteLinkUrl;
- private final Set<RecipientAddress> members;
- private final Set<RecipientAddress> pendingMembers;
- private final Set<RecipientAddress> requestingMembers;
- private final Set<RecipientAddress> adminMembers;
- private final boolean isBlocked;
- private final int messageExpirationTime;
- private final boolean isAnnouncementGroup;
- private final boolean isMember;
-
- public Group(
- final GroupId groupId,
- final String title,
- final String description,
- final GroupInviteLinkUrl groupInviteLinkUrl,
- final Set<RecipientAddress> members,
- final Set<RecipientAddress> pendingMembers,
- final Set<RecipientAddress> requestingMembers,
- final Set<RecipientAddress> adminMembers,
- final boolean isBlocked,
- final int messageExpirationTime,
- final boolean isAnnouncementGroup,
- final boolean isMember
- ) {
- this.groupId = groupId;
- this.title = title;
- this.description = description;
- this.groupInviteLinkUrl = groupInviteLinkUrl;
- this.members = members;
- this.pendingMembers = pendingMembers;
- this.requestingMembers = requestingMembers;
- this.adminMembers = adminMembers;
- this.isBlocked = isBlocked;
- this.messageExpirationTime = messageExpirationTime;
- this.isAnnouncementGroup = isAnnouncementGroup;
- this.isMember = isMember;
- }
-
- public GroupId getGroupId() {
- return groupId;
- }
-
- public String getTitle() {
- return title;
- }
-
- public String getDescription() {
- return description;
- }
-
- public GroupInviteLinkUrl getGroupInviteLinkUrl() {
- return groupInviteLinkUrl;
- }
-
- public Set<RecipientAddress> getMembers() {
- return members;
- }
-
- public Set<RecipientAddress> getPendingMembers() {
- return pendingMembers;
- }
-
- public Set<RecipientAddress> getRequestingMembers() {
- return requestingMembers;
- }
-
- public Set<RecipientAddress> getAdminMembers() {
- return adminMembers;
- }
-
- public boolean isBlocked() {
- return isBlocked;
- }
-
- public int getMessageExpirationTime() {
- return messageExpirationTime;
- }
-
- public boolean isAnnouncementGroup() {
- return isAnnouncementGroup;
- }
-
- public boolean isMember() {
- return isMember;
- }
-}
+public record Group(
+ GroupId groupId,
+ String title,
+ String description,
+ GroupInviteLinkUrl groupInviteLinkUrl,
+ Set<RecipientAddress> members,
+ Set<RecipientAddress> pendingMembers,
+ Set<RecipientAddress> requestingMembers,
+ Set<RecipientAddress> adminMembers,
+ boolean isBlocked,
+ int messageExpirationTimer,
+ GroupPermission permissionAddMember,
+ GroupPermission permissionEditDetails,
+ GroupPermission permissionSendMessage,
+ boolean isMember,
+ boolean isAdmin
+) {}
import java.util.Date;
-public class Identity {
-
- private final RecipientAddress recipient;
- private final IdentityKey identityKey;
- private final String safetyNumber;
- private final byte[] scannableSafetyNumber;
- private final TrustLevel trustLevel;
- private final Date dateAdded;
-
- public Identity(
- final RecipientAddress recipient,
- final IdentityKey identityKey,
- final String safetyNumber,
- final byte[] scannableSafetyNumber,
- final TrustLevel trustLevel,
- final Date dateAdded
- ) {
- this.recipient = recipient;
- this.identityKey = identityKey;
- this.safetyNumber = safetyNumber;
- this.scannableSafetyNumber = scannableSafetyNumber;
- this.trustLevel = trustLevel;
- this.dateAdded = dateAdded;
- }
-
- public RecipientAddress getRecipient() {
- return recipient;
- }
-
- public IdentityKey getIdentityKey() {
- return this.identityKey;
- }
-
- public TrustLevel getTrustLevel() {
- return this.trustLevel;
- }
-
- boolean isTrusted() {
- return trustLevel == TrustLevel.TRUSTED_UNVERIFIED || trustLevel == TrustLevel.TRUSTED_VERIFIED;
- }
-
- public Date getDateAdded() {
- return this.dateAdded;
- }
+public record Identity(
+ RecipientAddress recipient,
+ IdentityKey identityKey,
+ String safetyNumber,
+ byte[] scannableSafetyNumber,
+ TrustLevel trustLevel,
+ Date dateAdded
+) {
public byte[] getFingerprint() {
return identityKey.getPublicKey().serialize();
}
-
- public String getSafetyNumber() {
- return safetyNumber;
- }
-
- public byte[] getScannableSafetyNumber() {
- return scannableSafetyNumber;
- }
}
import java.util.List;
-public class Message {
-
- private final String messageText;
- private final List<String> attachments;
-
- public Message(final String messageText, final List<String> attachments) {
- this.messageText = messageText;
- this.attachments = attachments;
- }
-
- public String getMessageText() {
- return messageText;
- }
-
- public List<String> getAttachments() {
- return attachments;
- }
-}
+public record Message(String messageText, List<String> attachments) {}
import java.util.UUID;
-public abstract class RecipientIdentifier {
+public sealed abstract class RecipientIdentifier {
- public static class NoteToSelf extends RecipientIdentifier {
+ public static final class NoteToSelf extends RecipientIdentifier {
public static NoteToSelf INSTANCE = new NoteToSelf();
}
}
- public abstract static class Single extends RecipientIdentifier {
+ public sealed static abstract class Single extends RecipientIdentifier {
public static Single fromString(String identifier, String localNumber) throws InvalidNumberException {
return UuidUtil.isUuid(identifier)
public abstract String getIdentifier();
}
- public static class Uuid extends Single {
+ public static final class Uuid extends Single {
public final UUID uuid;
}
}
- public static class Number extends Single {
+ public static final class Number extends Single {
public final String number;
}
}
- public static class Group extends RecipientIdentifier {
+ public static final class Group extends RecipientIdentifier {
public final GroupId groupId;
import java.util.List;
-public class SendGroupMessageResults {
-
- private final long timestamp;
- private final List<SendMessageResult> results;
-
- public SendGroupMessageResults(
- final long timestamp, final List<SendMessageResult> results
- ) {
- this.timestamp = timestamp;
- this.results = results;
- }
-
- public long getTimestamp() {
- return timestamp;
- }
-
- public List<SendMessageResult> getResults() {
- return results;
- }
-}
+public record SendGroupMessageResults(long timestamp, List<SendMessageResult> results) {}
import java.util.List;
import java.util.Map;
-public class SendMessageResults {
-
- private final long timestamp;
- private final Map<RecipientIdentifier, List<SendMessageResult>> results;
-
- public SendMessageResults(
- final long timestamp, final Map<RecipientIdentifier, List<SendMessageResult>> results
- ) {
- this.timestamp = timestamp;
- this.results = results;
- }
-
- public long getTimestamp() {
- return timestamp;
- }
-
- public Map<RecipientIdentifier, List<SendMessageResult>> getResults() {
- return results;
- }
-}
+public record SendMessageResults(long timestamp, Map<RecipientIdentifier, List<SendMessageResult>> results) {}
STOP;
public SignalServiceTypingMessage.Action toSignalService() {
- switch (this) {
- case START:
- return SignalServiceTypingMessage.Action.STARTED;
- case STOP:
- return SignalServiceTypingMessage.Action.STOPPED;
- default:
- throw new IllegalStateException("Invalid typing action " + this);
- }
+ return switch (this) {
+ case START -> SignalServiceTypingMessage.Action.STARTED;
+ case STOP -> SignalServiceTypingMessage.Action.STOPPED;
+ };
}
}
--- /dev/null
+package org.asamk.signal.manager.api;
+
+import org.asamk.signal.manager.groups.GroupLinkState;
+import org.asamk.signal.manager.groups.GroupPermission;
+
+import java.io.File;
+import java.util.Set;
+
+public class UpdateGroup {
+
+ private final String name;
+ private final String description;
+ private final Set<RecipientIdentifier.Single> members;
+ private final Set<RecipientIdentifier.Single> removeMembers;
+ private final Set<RecipientIdentifier.Single> admins;
+ private final Set<RecipientIdentifier.Single> removeAdmins;
+ private final boolean resetGroupLink;
+ private final GroupLinkState groupLinkState;
+ private final GroupPermission addMemberPermission;
+ private final GroupPermission editDetailsPermission;
+ private final File avatarFile;
+ private final Integer expirationTimer;
+ private final Boolean isAnnouncementGroup;
+
+ private UpdateGroup(final Builder builder) {
+ name = builder.name;
+ description = builder.description;
+ members = builder.members;
+ removeMembers = builder.removeMembers;
+ admins = builder.admins;
+ removeAdmins = builder.removeAdmins;
+ resetGroupLink = builder.resetGroupLink;
+ groupLinkState = builder.groupLinkState;
+ addMemberPermission = builder.addMemberPermission;
+ editDetailsPermission = builder.editDetailsPermission;
+ avatarFile = builder.avatarFile;
+ expirationTimer = builder.expirationTimer;
+ isAnnouncementGroup = builder.isAnnouncementGroup;
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public static Builder newBuilder(final UpdateGroup copy) {
+ Builder builder = new Builder();
+ builder.name = copy.getName();
+ builder.description = copy.getDescription();
+ builder.members = copy.getMembers();
+ builder.removeMembers = copy.getRemoveMembers();
+ builder.admins = copy.getAdmins();
+ builder.removeAdmins = copy.getRemoveAdmins();
+ builder.resetGroupLink = copy.isResetGroupLink();
+ builder.groupLinkState = copy.getGroupLinkState();
+ builder.addMemberPermission = copy.getAddMemberPermission();
+ builder.editDetailsPermission = copy.getEditDetailsPermission();
+ builder.avatarFile = copy.getAvatarFile();
+ builder.expirationTimer = copy.getExpirationTimer();
+ builder.isAnnouncementGroup = copy.getIsAnnouncementGroup();
+ return builder;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public Set<RecipientIdentifier.Single> getMembers() {
+ return members;
+ }
+
+ public Set<RecipientIdentifier.Single> getRemoveMembers() {
+ return removeMembers;
+ }
+
+ public Set<RecipientIdentifier.Single> getAdmins() {
+ return admins;
+ }
+
+ public Set<RecipientIdentifier.Single> getRemoveAdmins() {
+ return removeAdmins;
+ }
+
+ public boolean isResetGroupLink() {
+ return resetGroupLink;
+ }
+
+ public GroupLinkState getGroupLinkState() {
+ return groupLinkState;
+ }
+
+ public GroupPermission getAddMemberPermission() {
+ return addMemberPermission;
+ }
+
+ public GroupPermission getEditDetailsPermission() {
+ return editDetailsPermission;
+ }
+
+ public File getAvatarFile() {
+ return avatarFile;
+ }
+
+ public Integer getExpirationTimer() {
+ return expirationTimer;
+ }
+
+ public Boolean getIsAnnouncementGroup() {
+ return isAnnouncementGroup;
+ }
+
+ public static final class Builder {
+
+ private String name;
+ private String description;
+ private Set<RecipientIdentifier.Single> members;
+ private Set<RecipientIdentifier.Single> removeMembers;
+ private Set<RecipientIdentifier.Single> admins;
+ private Set<RecipientIdentifier.Single> removeAdmins;
+ private boolean resetGroupLink;
+ private GroupLinkState groupLinkState;
+ private GroupPermission addMemberPermission;
+ private GroupPermission editDetailsPermission;
+ private File avatarFile;
+ private Integer expirationTimer;
+ private Boolean isAnnouncementGroup;
+
+ private Builder() {
+ }
+
+ public Builder withName(final String val) {
+ name = val;
+ return this;
+ }
+
+ public Builder withDescription(final String val) {
+ description = val;
+ return this;
+ }
+
+ public Builder withMembers(final Set<RecipientIdentifier.Single> val) {
+ members = val;
+ return this;
+ }
+
+ public Builder withRemoveMembers(final Set<RecipientIdentifier.Single> val) {
+ removeMembers = val;
+ return this;
+ }
+
+ public Builder withAdmins(final Set<RecipientIdentifier.Single> val) {
+ admins = val;
+ return this;
+ }
+
+ public Builder withRemoveAdmins(final Set<RecipientIdentifier.Single> val) {
+ removeAdmins = val;
+ return this;
+ }
+
+ public Builder withResetGroupLink(final boolean val) {
+ resetGroupLink = val;
+ return this;
+ }
+
+ public Builder withGroupLinkState(final GroupLinkState val) {
+ groupLinkState = val;
+ return this;
+ }
+
+ public Builder withAddMemberPermission(final GroupPermission val) {
+ addMemberPermission = val;
+ return this;
+ }
+
+ public Builder withEditDetailsPermission(final GroupPermission val) {
+ editDetailsPermission = val;
+ return this;
+ }
+
+ public Builder withAvatarFile(final File val) {
+ avatarFile = val;
+ return this;
+ }
+
+ public Builder withExpirationTimer(final Integer val) {
+ expirationTimer = val;
+ return this;
+ }
+
+ public Builder withIsAnnouncementGroup(final Boolean val) {
+ isAnnouncementGroup = val;
+ return this;
+ }
+
+ public UpdateGroup build() {
+ return new UpdateGroup(this);
+ }
+ }
+}
private final static Optional<SignalProxy> proxy = Optional.absent();
private final static byte[] zkGroupServerPublicParams = Base64.getDecoder()
- .decode("AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X0=");
+ .decode("AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY");
static SignalServiceConfiguration createDefaultServiceConfiguration(
final List<Interceptor> interceptors
private final static Optional<SignalProxy> proxy = Optional.absent();
private final static byte[] zkGroupServerPublicParams = Base64.getDecoder()
- .decode("ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdls=");
+ .decode("ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARB");
static SignalServiceConfiguration createDefaultServiceConfiguration(
final List<Interceptor> interceptors
final var interceptors = List.of(userAgentInterceptor);
- switch (serviceEnvironment) {
- case LIVE:
- return new ServiceEnvironmentConfig(LiveConfig.createDefaultServiceConfiguration(interceptors),
- LiveConfig.getUnidentifiedSenderTrustRoot(),
- LiveConfig.createKeyBackupConfig(),
- LiveConfig.getCdsMrenclave());
- case SANDBOX:
- return new ServiceEnvironmentConfig(SandboxConfig.createDefaultServiceConfiguration(interceptors),
- SandboxConfig.getUnidentifiedSenderTrustRoot(),
- SandboxConfig.createKeyBackupConfig(),
- SandboxConfig.getCdsMrenclave());
- default:
- throw new IllegalArgumentException("Unsupported environment");
- }
+ return switch (serviceEnvironment) {
+ case LIVE -> new ServiceEnvironmentConfig(LiveConfig.createDefaultServiceConfiguration(interceptors),
+ LiveConfig.getUnidentifiedSenderTrustRoot(),
+ LiveConfig.createKeyBackupConfig(),
+ LiveConfig.getCdsMrenclave());
+ case SANDBOX -> new ServiceEnvironmentConfig(SandboxConfig.createDefaultServiceConfiguration(interceptors),
+ SandboxConfig.getUnidentifiedSenderTrustRoot(),
+ SandboxConfig.createKeyBackupConfig(),
+ SandboxConfig.getCdsMrenclave());
+ };
}
}
return new Storage(readReceipts, unidentifiedDeliveryIndicators, typingIndicators, linkPreviews);
}
- public static final class Storage {
-
- public Boolean readReceipts;
- public Boolean unidentifiedDeliveryIndicators;
- public Boolean typingIndicators;
- public Boolean linkPreviews;
-
- // For deserialization
- private Storage() {
- }
-
- public Storage(
- final Boolean readReceipts,
- final Boolean unidentifiedDeliveryIndicators,
- final Boolean typingIndicators,
- final Boolean linkPreviews
- ) {
- this.readReceipts = readReceipts;
- this.unidentifiedDeliveryIndicators = unidentifiedDeliveryIndicators;
- this.typingIndicators = typingIndicators;
- this.linkPreviews = linkPreviews;
- }
- }
+ public record Storage(
+ Boolean readReceipts, Boolean unidentifiedDeliveryIndicators, Boolean typingIndicators, Boolean linkPreviews
+ ) {}
public interface Saver {
import java.util.Arrays;
import java.util.Base64;
-public abstract class GroupId {
+public abstract sealed class GroupId permits GroupIdV1, GroupIdV2 {
private final byte[] id;
import static org.asamk.signal.manager.util.KeyUtils.getSecretBytes;
-public class GroupIdV1 extends GroupId {
+public final class GroupIdV1 extends GroupId {
public static GroupIdV1 createRandom() {
return new GroupIdV1(getSecretBytes(16));
import java.util.Base64;
-public class GroupIdV2 extends GroupId {
+public final class GroupIdV2 extends GroupId {
public static GroupIdV2 fromBase64(String groupId) {
return new GroupIdV2(Base64.getDecoder().decode(groupId));
var groupInviteLink = GroupInviteLink.parseFrom(bytes);
switch (groupInviteLink.getContentsCase()) {
- case V1CONTENTS: {
+ case V1CONTENTS -> {
var groupInviteLinkContentsV1 = groupInviteLink.getV1Contents();
var groupMasterKey = new GroupMasterKey(groupInviteLinkContentsV1.getGroupMasterKey()
.toByteArray());
return new GroupInviteLinkUrl(groupMasterKey, password);
}
- default:
- throw new UnknownGroupLinkVersionException("Url contains no known group link content");
+ default -> throw new UnknownGroupLinkVersionException("Url contains no known group link content");
}
} catch (InvalidInputException | IOException e) {
throw new InvalidGroupLinkException(e);
private GroupInfo getGroup(GroupId groupId, boolean forceUpdate) {
final var group = account.getGroupStore().getGroup(groupId);
- if (group instanceof GroupInfoV2) {
- final var groupInfoV2 = (GroupInfoV2) group;
+ if (group instanceof GroupInfoV2 groupInfoV2) {
if (forceUpdate || (!groupInfoV2.isPermissionDenied() && groupInfoV2.getGroup() == null)) {
final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
DecryptedGroup decryptedGroup;
return SignalServiceDataMessage.newBuilder()
.asGroupMessage(group.build())
- .withExpiration(g.getMessageExpirationTime());
+ .withExpiration(g.getMessageExpirationTimer());
}
private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV2 g, byte[] signedGroupChange) {
.withSignedGroupChange(signedGroupChange);
return SignalServiceDataMessage.newBuilder()
.asGroupMessage(group.build())
- .withExpiration(g.getMessageExpirationTime());
+ .withExpiration(g.getMessageExpirationTimer());
}
private SendGroupMessageResults sendUpdateGroupV2Message(
}
private AccessControl.AccessRequired toAccessControl(final GroupLinkState state) {
- switch (state) {
- case DISABLED:
- return AccessControl.AccessRequired.UNSATISFIABLE;
- case ENABLED:
- return AccessControl.AccessRequired.ANY;
- case ENABLED_WITH_APPROVAL:
- return AccessControl.AccessRequired.ADMINISTRATOR;
- default:
- throw new AssertionError();
- }
+ return switch (state) {
+ case DISABLED -> AccessControl.AccessRequired.UNSATISFIABLE;
+ case ENABLED -> AccessControl.AccessRequired.ANY;
+ case ENABLED_WITH_APPROVAL -> AccessControl.AccessRequired.ADMINISTRATOR;
+ };
}
private AccessControl.AccessRequired toAccessControl(final GroupPermission permission) {
- switch (permission) {
- case EVERY_MEMBER:
- return AccessControl.AccessRequired.MEMBER;
- case ONLY_ADMINS:
- return AccessControl.AccessRequired.ADMINISTRATOR;
- default:
- throw new AssertionError();
- }
+ return switch (permission) {
+ case EVERY_MEMBER -> AccessControl.AccessRequired.MEMBER;
+ case ONLY_ADMINS -> AccessControl.AccessRequired.ADMINISTRATOR;
+ };
}
private GroupsV2Operations.GroupOperations getGroupOperations(final GroupInfoV2 groupInfoV2) {
--- /dev/null
+package org.asamk.signal.manager.helper;
+
+import org.asamk.signal.manager.SignalDependencies;
+import org.asamk.signal.manager.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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.whispersystems.libsignal.IdentityKey;
+import org.whispersystems.libsignal.fingerprint.Fingerprint;
+import org.whispersystems.libsignal.fingerprint.FingerprintParsingException;
+import org.whispersystems.libsignal.fingerprint.FingerprintVersionMismatchException;
+import org.whispersystems.libsignal.fingerprint.ScannableFingerprint;
+import org.whispersystems.signalservice.api.messages.SendMessageResult;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.function.Function;
+
+import static org.asamk.signal.manager.config.ServiceConfig.capabilities;
+
+public class IdentityHelper {
+
+ private final static Logger logger = LoggerFactory.getLogger(IdentityHelper.class);
+
+ private final SignalAccount account;
+ private final SignalDependencies dependencies;
+ private final SignalServiceAddressResolver addressResolver;
+ private final SyncHelper syncHelper;
+ private final ProfileHelper profileHelper;
+
+ public IdentityHelper(
+ final SignalAccount account,
+ final SignalDependencies dependencies,
+ final SignalServiceAddressResolver addressResolver,
+ final SyncHelper syncHelper,
+ final ProfileHelper profileHelper
+ ) {
+ this.account = account;
+ this.dependencies = dependencies;
+ this.addressResolver = addressResolver;
+ this.syncHelper = syncHelper;
+ this.profileHelper = profileHelper;
+ }
+
+ public boolean trustIdentityVerified(RecipientId recipientId, byte[] fingerprint) {
+ return trustIdentity(recipientId,
+ identityKey -> Arrays.equals(identityKey.serialize(), fingerprint),
+ TrustLevel.TRUSTED_VERIFIED);
+ }
+
+ public boolean trustIdentityVerifiedSafetyNumber(RecipientId recipientId, String safetyNumber) {
+ return trustIdentity(recipientId,
+ identityKey -> safetyNumber.equals(computeSafetyNumber(recipientId, identityKey)),
+ TrustLevel.TRUSTED_VERIFIED);
+ }
+
+ public boolean trustIdentityVerifiedSafetyNumber(RecipientId recipientId, byte[] safetyNumber) {
+ return trustIdentity(recipientId, identityKey -> {
+ final var fingerprint = computeSafetyNumberForScanning(recipientId, identityKey);
+ try {
+ return fingerprint != null && fingerprint.compareTo(safetyNumber);
+ } catch (FingerprintVersionMismatchException | FingerprintParsingException e) {
+ return false;
+ }
+ }, TrustLevel.TRUSTED_VERIFIED);
+ }
+
+ public boolean trustIdentityAllKeys(RecipientId recipientId) {
+ return trustIdentity(recipientId, identityKey -> true, TrustLevel.TRUSTED_UNVERIFIED);
+ }
+
+ public String computeSafetyNumber(RecipientId recipientId, IdentityKey theirIdentityKey) {
+ var address = addressResolver.resolveSignalServiceAddress(recipientId);
+ final Fingerprint fingerprint = computeSafetyNumberFingerprint(address, theirIdentityKey);
+ return fingerprint == null ? null : fingerprint.getDisplayableFingerprint().getDisplayText();
+ }
+
+ public ScannableFingerprint computeSafetyNumberForScanning(RecipientId recipientId, IdentityKey theirIdentityKey) {
+ var address = addressResolver.resolveSignalServiceAddress(recipientId);
+ final Fingerprint fingerprint = computeSafetyNumberFingerprint(address, theirIdentityKey);
+ return fingerprint == null ? null : fingerprint.getScannableFingerprint();
+ }
+
+ private Fingerprint computeSafetyNumberFingerprint(
+ final SignalServiceAddress theirAddress, final IdentityKey theirIdentityKey
+ ) {
+ return Utils.computeSafetyNumber(capabilities.isUuid(),
+ account.getSelfAddress(),
+ account.getIdentityKeyPair().getPublicKey(),
+ theirAddress,
+ theirIdentityKey);
+ }
+
+ private boolean trustIdentity(
+ RecipientId recipientId, Function<IdentityKey, Boolean> verifier, TrustLevel trustLevel
+ ) {
+ var identity = account.getIdentityKeyStore().getIdentity(recipientId);
+ if (identity == null) {
+ return false;
+ }
+
+ if (!verifier.apply(identity.getIdentityKey())) {
+ return false;
+ }
+
+ account.getIdentityKeyStore().setIdentityTrustLevel(recipientId, identity.getIdentityKey(), trustLevel);
+ try {
+ var address = addressResolver.resolveSignalServiceAddress(recipientId);
+ syncHelper.sendVerifiedMessage(address, identity.getIdentityKey(), trustLevel);
+ } catch (IOException e) {
+ logger.warn("Failed to send verification sync message: {}", e.getMessage());
+ }
+
+ return true;
+ }
+
+ public void handleIdentityFailure(
+ final RecipientId recipientId, final SendMessageResult.IdentityFailure identityFailure
+ ) {
+ final var identityKey = identityFailure.getIdentityKey();
+ if (identityKey != null) {
+ final var newIdentity = account.getIdentityKeyStore().saveIdentity(recipientId, identityKey, new Date());
+ if (newIdentity) {
+ account.getSessionStore().archiveSessions(recipientId);
+ }
+ } else {
+ // Retrieve profile to get the current identity key from the server
+ profileHelper.refreshRecipientProfile(recipientId);
+ }
+ }
+}
if (syncMessage.getConfiguration().isPresent()) {
final var configurationMessage = syncMessage.getConfiguration().get();
final var configurationStore = account.getConfigurationStore();
- configurationStore.setReadReceipts(configurationMessage.getReadReceipts().orNull());
- configurationStore.setLinkPreviews(configurationMessage.getLinkPreviews().orNull());
- configurationStore.setTypingIndicators(configurationMessage.getTypingIndicators().orNull());
- configurationStore.setUnidentifiedDeliveryIndicators(configurationMessage.getUnidentifiedDeliveryIndicators()
- .orNull());
+ if (configurationMessage.getReadReceipts().isPresent()) {
+ configurationStore.setReadReceipts(configurationMessage.getReadReceipts().get());
+ }
+ if (configurationMessage.getLinkPreviews().isPresent()) {
+ configurationStore.setLinkPreviews(configurationMessage.getLinkPreviews().get());
+ }
+ if (configurationMessage.getTypingIndicators().isPresent()) {
+ configurationStore.setTypingIndicators(configurationMessage.getTypingIndicators().get());
+ }
+ if (configurationMessage.getUnidentifiedDeliveryIndicators().isPresent()) {
+ configurationStore.setUnidentifiedDeliveryIndicators(configurationMessage.getUnidentifiedDeliveryIndicators()
+ .get());
+ }
}
return actions;
}
import java.util.Date;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Objects;
import java.util.Set;
private final SignalAccount account;
private final SignalDependencies dependencies;
private final AvatarStore avatarStore;
- private final ProfileKeyProvider profileKeyProvider;
private final UnidentifiedAccessProvider unidentifiedAccessProvider;
private final SignalServiceAddressResolver addressResolver;
final SignalAccount account,
final SignalDependencies dependencies,
final AvatarStore avatarStore,
- final ProfileKeyProvider profileKeyProvider,
final UnidentifiedAccessProvider unidentifiedAccessProvider,
final SignalServiceAddressResolver addressResolver
) {
this.account = account;
this.dependencies = dependencies;
this.avatarStore = avatarStore;
- this.profileKeyProvider = profileKeyProvider;
this.unidentifiedAccessProvider = unidentifiedAccessProvider;
this.addressResolver = addressResolver;
}
}
private SignalServiceProfile retrieveProfileSync(String username) throws IOException {
- return dependencies.getMessageReceiver().retrieveProfileByUsername(username, Optional.absent());
+ final var locale = Locale.getDefault();
+ return dependencies.getMessageReceiver().retrieveProfileByUsername(username, Optional.absent(), locale);
}
private ProfileAndCredential retrieveProfileAndCredential(
RecipientId recipientId, SignalServiceProfile.RequestType requestType
) {
var unidentifiedAccess = getUnidentifiedAccess(recipientId);
- var profileKey = Optional.fromNullable(profileKeyProvider.getProfileKey(recipientId));
+ var profileKey = Optional.fromNullable(account.getProfileStore().getProfileKey(recipientId));
final var address = addressResolver.resolveSignalServiceAddress(recipientId);
return retrieveProfile(address, profileKey, unidentifiedAccess, requestType);
var profileService = dependencies.getProfileService();
Single<ServiceResponse<ProfileAndCredential>> responseSingle;
+ final var locale = Locale.getDefault();
try {
- responseSingle = profileService.getProfile(address, profileKey, unidentifiedAccess, requestType);
+ responseSingle = profileService.getProfile(address, profileKey, unidentifiedAccess, requestType, locale);
} catch (NoClassDefFoundError e) {
// Native zkgroup lib not available for ProfileKey
- responseSingle = profileService.getProfile(address, Optional.absent(), unidentifiedAccess, requestType);
+ responseSingle = profileService.getProfile(address,
+ Optional.absent(),
+ unidentifiedAccess,
+ requestType,
+ locale);
}
return responseSingle.map(pair -> {
+++ /dev/null
-package org.asamk.signal.manager.helper;
-
-import org.asamk.signal.manager.storage.recipients.RecipientId;
-import org.signal.zkgroup.profiles.ProfileKey;
-
-public interface ProfileKeyProvider {
-
- ProfileKey getProfileKey(RecipientId address);
-}
final SignalServiceDataMessage.Builder messageBuilder, final GroupInfo g
) throws IOException, GroupSendingNotAllowedException {
GroupUtils.setGroupContext(messageBuilder, g);
- messageBuilder.withExpiration(g.getMessageExpirationTime());
+ messageBuilder.withExpiration(g.getMessageExpirationTimer());
final var message = messageBuilder.build();
final var recipients = g.getMembersWithout(account.getSelfRecipientId());
try (OutputStream fos = new FileOutputStream(groupsFile)) {
var out = new DeviceGroupsOutputStream(fos);
for (var record : account.getGroupStore().getGroups()) {
- if (record instanceof GroupInfoV1) {
- var groupInfo = (GroupInfoV1) record;
+ if (record instanceof GroupInfoV1 groupInfo) {
out.write(new DeviceGroup(groupInfo.getGroupId().serialize(),
Optional.fromNullable(groupInfo.name),
groupInfo.getMembers()
package org.asamk.signal.manager.helper;
+import org.asamk.signal.manager.SignalDependencies;
+import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.signal.libsignal.metadata.certificate.InvalidCertificateException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
+import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
public class UnidentifiedAccessHelper {
- private final SelfProfileKeyProvider selfProfileKeyProvider;
-
- private final ProfileKeyProvider profileKeyProvider;
+ private final static Logger logger = LoggerFactory.getLogger(UnidentifiedAccessHelper.class);
+ private final SignalAccount account;
+ private final SignalDependencies dependencies;
+ private final SelfProfileKeyProvider selfProfileKeyProvider;
private final ProfileProvider profileProvider;
- private final UnidentifiedAccessSenderCertificateProvider senderCertificateProvider;
-
public UnidentifiedAccessHelper(
+ final SignalAccount account,
+ final SignalDependencies dependencies,
final SelfProfileKeyProvider selfProfileKeyProvider,
- final ProfileKeyProvider profileKeyProvider,
- final ProfileProvider profileProvider,
- final UnidentifiedAccessSenderCertificateProvider senderCertificateProvider
+ final ProfileProvider profileProvider
) {
+ this.account = account;
+ this.dependencies = dependencies;
this.selfProfileKeyProvider = selfProfileKeyProvider;
- this.profileKeyProvider = profileKeyProvider;
this.profileProvider = profileProvider;
- this.senderCertificateProvider = senderCertificateProvider;
+ }
+
+ private byte[] getSenderCertificate() {
+ byte[] certificate;
+ try {
+ if (account.isPhoneNumberShared()) {
+ certificate = dependencies.getAccountManager().getSenderCertificate();
+ } else {
+ certificate = dependencies.getAccountManager().getSenderCertificateForPhoneNumberPrivacy();
+ }
+ } catch (IOException e) {
+ logger.warn("Failed to get sender certificate, ignoring: {}", e.getMessage());
+ return null;
+ }
+ // TODO cache for a day
+ return certificate;
}
private byte[] getSelfUnidentifiedAccessKey() {
switch (targetProfile.getUnidentifiedAccessMode()) {
case ENABLED:
- var theirProfileKey = profileKeyProvider.getProfileKey(recipient);
+ var theirProfileKey = account.getProfileStore().getProfileKey(recipient);
if (theirProfileKey == null) {
return null;
}
public Optional<UnidentifiedAccessPair> getAccessForSync() {
var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey();
- var selfUnidentifiedAccessCertificate = senderCertificateProvider.getSenderCertificate();
+ var selfUnidentifiedAccessCertificate = getSenderCertificate();
if (selfUnidentifiedAccessKey == null || selfUnidentifiedAccessCertificate == null) {
return Optional.absent();
public Optional<UnidentifiedAccessPair> getAccessFor(RecipientId recipient) {
var recipientUnidentifiedAccessKey = getTargetUnidentifiedAccessKey(recipient);
var selfUnidentifiedAccessKey = getSelfUnidentifiedAccessKey();
- var selfUnidentifiedAccessCertificate = senderCertificateProvider.getSenderCertificate();
+ var selfUnidentifiedAccessCertificate = getSenderCertificate();
if (recipientUnidentifiedAccessKey == null
|| selfUnidentifiedAccessKey == null
+++ /dev/null
-package org.asamk.signal.manager.helper;
-
-public interface UnidentifiedAccessSenderCertificateProvider {
-
- byte[] getSenderCertificate();
-}
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
+import org.asamk.signal.manager.groups.GroupPermission;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-public abstract class GroupInfo {
+public sealed abstract class GroupInfo permits GroupInfoV1, GroupInfoV2 {
public abstract GroupId getGroupId();
public abstract void setBlocked(boolean blocked);
- public abstract int getMessageExpirationTime();
+ public abstract int getMessageExpirationTimer();
public abstract boolean isAnnouncementGroup();
+ public abstract GroupPermission getPermissionAddMember();
+
+ public abstract GroupPermission getPermissionEditDetails();
+
+ public abstract GroupPermission getPermissionSendMessage();
+
public Set<RecipientId> getMembersWithout(RecipientId recipientId) {
return getMembers().stream().filter(member -> !member.equals(recipientId)).collect(Collectors.toSet());
}
import org.asamk.signal.manager.groups.GroupIdV1;
import org.asamk.signal.manager.groups.GroupIdV2;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
+import org.asamk.signal.manager.groups.GroupPermission;
import org.asamk.signal.manager.groups.GroupUtils;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import java.util.HashSet;
import java.util.Set;
-public class GroupInfoV1 extends GroupInfo {
+public final class GroupInfoV1 extends GroupInfo {
private final GroupIdV1 groupId;
}
@Override
- public int getMessageExpirationTime() {
+ public int getMessageExpirationTimer() {
return messageExpirationTime;
}
return false;
}
+ @Override
+ public GroupPermission getPermissionAddMember() {
+ return GroupPermission.EVERY_MEMBER;
+ }
+
+ @Override
+ public GroupPermission getPermissionEditDetails() {
+ return GroupPermission.EVERY_MEMBER;
+ }
+
+ @Override
+ public GroupPermission getPermissionSendMessage() {
+ return GroupPermission.EVERY_MEMBER;
+ }
+
public void addMembers(Collection<RecipientId> members) {
this.members.addAll(members);
}
import org.asamk.signal.manager.groups.GroupIdV2;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
+import org.asamk.signal.manager.groups.GroupPermission;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.signal.storageservice.protos.groups.AccessControl;
import java.util.Set;
import java.util.stream.Collectors;
-public class GroupInfoV2 extends GroupInfo {
+public final class GroupInfoV2 extends GroupInfo {
private final GroupIdV2 groupId;
private final GroupMasterKey masterKey;
}
@Override
- public int getMessageExpirationTime() {
+ public int getMessageExpirationTimer() {
return this.group != null && this.group.hasDisappearingMessagesTimer()
? this.group.getDisappearingMessagesTimer().getDuration()
: 0;
return this.group != null && this.group.getIsAnnouncementGroup() == EnabledState.ENABLED;
}
+ @Override
+ public GroupPermission getPermissionAddMember() {
+ final var accessControl = getAccessControl();
+ return accessControl == null ? GroupPermission.EVERY_MEMBER : toGroupPermission(accessControl.getMembers());
+ }
+
+ @Override
+ public GroupPermission getPermissionEditDetails() {
+ final var accessControl = getAccessControl();
+ return accessControl == null ? GroupPermission.EVERY_MEMBER : toGroupPermission(accessControl.getAttributes());
+ }
+
+ @Override
+ public GroupPermission getPermissionSendMessage() {
+ return isAnnouncementGroup() ? GroupPermission.ONLY_ADMINS : GroupPermission.EVERY_MEMBER;
+ }
+
public void setPermissionDenied(final boolean permissionDenied) {
this.permissionDenied = permissionDenied;
}
public boolean isPermissionDenied() {
return permissionDenied;
}
+
+ private AccessControl getAccessControl() {
+ if (this.group == null || !this.group.hasAccessControl()) {
+ return null;
+ }
+
+ return this.group.getAccessControl();
+ }
+
+ private static GroupPermission toGroupPermission(final AccessControl.AccessRequired permission) {
+ return switch (permission) {
+ case ADMINISTRATOR -> GroupPermission.ONLY_ADMINS;
+ default -> GroupPermission.EVERY_MEMBER;
+ };
+ }
}
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
final Saver saver
) {
final var groups = storage.groups.stream().map(g -> {
- if (g instanceof Storage.GroupV1) {
- final var g1 = (Storage.GroupV1) g;
+ if (g instanceof Storage.GroupV1 g1) {
final var members = g1.members.stream().map(m -> {
if (m.recipientId == null) {
return recipientResolver.resolveRecipient(new RecipientAddress(UuidUtil.parseOrNull(m.uuid),
}
final var groupFileLegacy = getGroupV2FileLegacy(group.getGroupId());
if (groupFileLegacy.exists()) {
- groupFileLegacy.delete();
+ try {
+ Files.delete(groupFileLegacy.toPath());
+ } catch (IOException e) {
+ logger.error("Failed to delete legacy group file {}: {}", groupFileLegacy, e.getMessage());
+ }
}
} catch (IOException e) {
logger.warn("Failed to cache group, ignoring: {}", e.getMessage());
synchronized (groups) {
var modified = false;
for (var group : this.groups.values()) {
- if (group instanceof GroupInfoV1) {
- var groupV1 = (GroupInfoV1) group;
+ if (group instanceof GroupInfoV1 groupV1) {
if (groupV1.isMember(toBeMergedRecipientId)) {
groupV1.removeMember(toBeMergedRecipientId);
groupV1.addMembers(List.of(recipientId));
private GroupInfoV1 getGroupV1ByV2IdLocked(GroupIdV2 groupIdV2) {
for (var g : groups.values()) {
- if (g instanceof GroupInfoV1) {
- final var gv1 = (GroupInfoV1) g;
+ if (g instanceof GroupInfoV1 gv1) {
if (groupIdV2.equals(gv1.getExpectedV2Id())) {
return gv1;
}
private Storage toStorageLocked() {
return new Storage(groups.values().stream().map(g -> {
- if (g instanceof GroupInfoV1) {
- final var g1 = (GroupInfoV1) g;
+ if (g instanceof GroupInfoV1 g1) {
return new Storage.GroupV1(g1.getGroupId().toBase64(),
g1.getExpectedV2Id().toBase64(),
g1.name,
g1.blocked,
g1.archived,
g1.members.stream()
- .map(m -> new Storage.GroupV1.Member(m.getId(), null, null))
+ .map(m -> new Storage.GroupV1.Member(m.id(), null, null))
.collect(Collectors.toList()));
}
}).collect(Collectors.toList()));
}
- public static class Storage {
-
- @JsonDeserialize(using = GroupsDeserializer.class)
- public List<Storage.Group> groups;
-
- // For deserialization
- public Storage() {
- }
-
- public Storage(final List<Storage.Group> groups) {
- this.groups = groups;
- }
-
- private abstract static class Group {
-
- }
-
- private static class GroupV1 extends Group {
-
- public String groupId;
- public String expectedV2Id;
- public String name;
- public String color;
- public int messageExpirationTime;
- public boolean blocked;
- public boolean archived;
+ public record Storage(@JsonDeserialize(using = GroupsDeserializer.class) List<Object> groups) {
- @JsonDeserialize(using = MembersDeserializer.class)
- @JsonSerialize(using = MembersSerializer.class)
- public List<Member> members;
+ private record GroupV1(
+ String groupId,
+ String expectedV2Id,
+ String name,
+ String color,
+ int messageExpirationTime,
+ boolean blocked,
+ boolean archived,
+ @JsonDeserialize(using = MembersDeserializer.class) @JsonSerialize(using = MembersSerializer.class) List<Member> members
+ ) {
- // For deserialization
- public GroupV1() {
- }
-
- public GroupV1(
- final String groupId,
- final String expectedV2Id,
- final String name,
- final String color,
- final int messageExpirationTime,
- final boolean blocked,
- final boolean archived,
- final List<Member> members
- ) {
- this.groupId = groupId;
- this.expectedV2Id = expectedV2Id;
- this.name = name;
- this.color = color;
- this.messageExpirationTime = messageExpirationTime;
- this.blocked = blocked;
- this.archived = archived;
- this.members = members;
- }
+ private record Member(Long recipientId, String uuid, String number) {}
- private static final class Member {
-
- public Long recipientId;
-
- public String uuid;
-
- public String number;
-
- Member(Long recipientId, final String uuid, final String number) {
- this.recipientId = recipientId;
- this.uuid = uuid;
- this.number = number;
- }
- }
-
- private static final class JsonRecipientAddress {
-
- public String uuid;
-
- public String number;
-
- // For deserialization
- public JsonRecipientAddress() {
- }
-
- JsonRecipientAddress(final String uuid, final String number) {
- this.uuid = uuid;
- this.number = number;
- }
- }
+ private record JsonRecipientAddress(String uuid, String number) {}
private static class MembersSerializer extends JsonSerializer<List<Member>> {
public void serialize(
final List<Member> value, final JsonGenerator jgen, final SerializerProvider provider
) throws IOException {
- jgen.writeStartArray(value.size());
+ jgen.writeStartArray(null, value.size());
for (var address : value) {
if (address.recipientId != null) {
jgen.writeNumber(address.recipientId);
}
}
- private static class GroupV2 extends Group {
-
- public String groupId;
- public String masterKey;
- public boolean blocked;
- public boolean permissionDenied;
-
- // For deserialization
- private GroupV2() {
- }
-
- public GroupV2(
- final String groupId, final String masterKey, final boolean blocked, final boolean permissionDenied
- ) {
- this.groupId = groupId;
- this.masterKey = masterKey;
- this.blocked = blocked;
- this.permissionDenied = permissionDenied;
- }
- }
-
+ private record GroupV2(String groupId, String masterKey, boolean blocked, boolean permissionDenied) {}
}
- private static class GroupsDeserializer extends JsonDeserializer<List<Storage.Group>> {
+ private static class GroupsDeserializer extends JsonDeserializer<List<Object>> {
@Override
- public List<Storage.Group> deserialize(
+ public List<Object> deserialize(
JsonParser jsonParser, DeserializationContext deserializationContext
) throws IOException {
- var groups = new ArrayList<Storage.Group>();
+ var groups = new ArrayList<>();
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
for (var n : node) {
- Storage.Group g;
+ Object g;
if (n.hasNonNull("masterKey")) {
// a v2 group
g = jsonParser.getCodec().treeToValue(n, Storage.GroupV2.class);
} catch (IOException e) {
throw new AssertionError("Failed to create identities path", e);
}
- return new File(identitiesPath, String.valueOf(recipientId.getId()));
+ return new File(identitiesPath, String.valueOf(recipientId.id()));
}
private IdentityInfo loadIdentityLocked(final RecipientId recipientId) {
try (var inputStream = new FileInputStream(file)) {
var storage = objectMapper.readValue(inputStream, IdentityStorage.class);
- var id = new IdentityKey(Base64.getDecoder().decode(storage.getIdentityKey()));
- var trustLevel = TrustLevel.fromInt(storage.getTrustLevel());
- var added = new Date(storage.getAddedTimestamp());
+ var id = new IdentityKey(Base64.getDecoder().decode(storage.identityKey()));
+ var trustLevel = TrustLevel.fromInt(storage.trustLevel());
+ var added = new Date(storage.addedTimestamp());
final var identityInfo = new IdentityInfo(recipientId, id, trustLevel, added);
cachedIdentities.put(recipientId, identityInfo);
}
}
- private static final class IdentityStorage {
-
- private String identityKey;
- private int trustLevel;
- private long addedTimestamp;
-
- // For deserialization
- private IdentityStorage() {
- }
-
- private IdentityStorage(final String identityKey, final int trustLevel, final long addedTimestamp) {
- this.identityKey = identityKey;
- this.trustLevel = trustLevel;
- this.addedTimestamp = addedTimestamp;
- }
-
- public String getIdentityKey() {
- return identityKey;
- }
-
- public int getTrustLevel() {
- return trustLevel;
- }
-
- public long getAddedTimestamp() {
- return addedTimestamp;
- }
- }
+ private record IdentityStorage(String identityKey, int trustLevel, long addedTimestamp) {}
}
return messageCachePath;
}
- var sender = String.valueOf(recipientId.getId());
+ var sender = String.valueOf(recipientId.id());
return new File(messageCachePath, sender.replace("/", "_"));
}
*/
public RecipientAddress(Optional<UUID> uuid, Optional<String> e164) {
uuid = uuid.isPresent() && uuid.get().equals(UuidUtil.UNKNOWN_UUID) ? Optional.empty() : uuid;
- if (!uuid.isPresent() && !e164.isPresent()) {
+ if (uuid.isEmpty() && e164.isEmpty()) {
throw new AssertionError("Must have either a UUID or E164 number!");
}
package org.asamk.signal.manager.storage.recipients;
-public class RecipientId {
-
- private final long id;
-
- RecipientId(final long id) {
- this.id = id;
- }
+public record RecipientId(long id) {
public static RecipientId of(long id) {
return new RecipientId(id);
}
-
- public long getId() {
- return id;
- }
-
- @Override
- public String toString() {
- return "RecipientId{" + "id=" + id + '}';
- }
-
- @Override
- public boolean equals(final Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- final RecipientId that = (RecipientId) o;
-
- return id == that.id;
- }
-
- @Override
- public int hashCode() {
- return (int) (id ^ (id >>> 32));
- }
}
.stream()
.map(Enum::name)
.collect(Collectors.toSet()));
- return new Storage.Recipient(pair.getKey().getId(),
+ return new Storage.Recipient(pair.getKey().id(),
recipient.getAddress().getNumber().orElse(null),
recipient.getAddress().getUuid().map(UUID::toString).orElse(null),
recipient.getProfileKey() == null
}
}
- private static class Storage {
-
- public List<Recipient> recipients;
-
- public long lastId;
-
- // For deserialization
- private Storage() {
- }
-
- public Storage(final List<Recipient> recipients, final long lastId) {
- this.recipients = recipients;
- this.lastId = lastId;
- }
-
- private static class Recipient {
-
- public long id;
- public String number;
- public String uuid;
- public String profileKey;
- public String profileKeyCredential;
- public Contact contact;
- public Profile profile;
-
- // For deserialization
- private Recipient() {
- }
-
- public Recipient(
- final long id,
- final String number,
- final String uuid,
- final String profileKey,
- final String profileKeyCredential,
- final Contact contact,
- final Profile profile
- ) {
- this.id = id;
- this.number = number;
- this.uuid = uuid;
- this.profileKey = profileKey;
- this.profileKeyCredential = profileKeyCredential;
- this.contact = contact;
- this.profile = profile;
- }
-
- private static class Contact {
-
- public String name;
- public String color;
- public int messageExpirationTime;
- public boolean blocked;
- public boolean archived;
-
- // For deserialization
- public Contact() {
- }
-
- public Contact(
- final String name,
- final String color,
- final int messageExpirationTime,
- final boolean blocked,
- final boolean archived
- ) {
- this.name = name;
- this.color = color;
- this.messageExpirationTime = messageExpirationTime;
- this.blocked = blocked;
- this.archived = archived;
- }
- }
-
- private static class Profile {
-
- public long lastUpdateTimestamp;
- public String givenName;
- public String familyName;
- public String about;
- public String aboutEmoji;
- public String avatarUrlPath;
- public String unidentifiedAccessMode;
- public Set<String> capabilities;
-
- // For deserialization
- private Profile() {
- }
-
- public Profile(
- final long lastUpdateTimestamp,
- final String givenName,
- final String familyName,
- final String about,
- final String aboutEmoji,
- final String avatarUrlPath,
- final String unidentifiedAccessMode,
- final Set<String> capabilities
- ) {
- this.lastUpdateTimestamp = lastUpdateTimestamp;
- this.givenName = givenName;
- this.familyName = familyName;
- this.about = about;
- this.aboutEmoji = aboutEmoji;
- this.avatarUrlPath = avatarUrlPath;
- this.unidentifiedAccessMode = unidentifiedAccessMode;
- this.capabilities = capabilities;
- }
- }
+ private record Storage(List<Recipient> recipients, long lastId) {
+
+ private record Recipient(
+ long id,
+ String number,
+ String uuid,
+ String profileKey,
+ String profileKeyCredential,
+ Storage.Recipient.Contact contact,
+ Storage.Recipient.Profile profile
+ ) {
+
+ private record Contact(
+ String name, String color, int messageExpirationTime, boolean blocked, boolean archived
+ ) {}
+
+ private record Profile(
+ long lastUpdateTimestamp,
+ String givenName,
+ String familyName,
+ String about,
+ String aboutEmoji,
+ String avatarUrlPath,
+ String unidentifiedAccessMode,
+ Set<String> capabilities
+ ) {}
}
}
return;
}
- logger.debug("Only to be merged recipient had sender keys, re-assigning to the new recipient.");
+ logger.debug("To be merged recipient had sender keys, re-assigning to the new recipient.");
for (var key : keys) {
final var toBeMergedSenderKey = loadSenderKeyLocked(key);
deleteSenderKeyLocked(key);
continue;
}
- final var newKey = new Key(recipientId, key.getDeviceId(), key.distributionId);
+ final var newKey = new Key(recipientId, key.deviceId(), key.distributionId);
final var senderKeyRecord = loadSenderKeyLocked(newKey);
if (senderKeyRecord != null) {
continue;
}
- storeSenderKeyLocked(newKey, senderKeyRecord);
+ storeSenderKeyLocked(newKey, toBeMergedSenderKey);
}
}
}
}
private List<Key> getKeysLocked(RecipientId recipientId) {
- final var files = senderKeysPath.listFiles((_file, s) -> s.startsWith(recipientId.getId() + "_"));
+ final var files = senderKeysPath.listFiles((_file, s) -> s.startsWith(recipientId.id() + "_"));
if (files == null) {
return List.of();
}
throw new AssertionError("Failed to create sender keys path", e);
}
return new File(senderKeysPath,
- key.getRecipientId().getId() + "_" + key.getDeviceId() + "_" + key.distributionId.toString());
+ key.recipientId().id() + "_" + key.deviceId() + "_" + key.distributionId.toString());
}
private SenderKeyRecord loadSenderKeyLocked(final Key key) {
}
}
- private static final class Key {
+ private record Key(RecipientId recipientId, int deviceId, UUID distributionId) {
- private final RecipientId recipientId;
- private final int deviceId;
- private final UUID distributionId;
-
- public Key(
- final RecipientId recipientId, final int deviceId, final UUID distributionId
- ) {
- this.recipientId = recipientId;
- this.deviceId = deviceId;
- this.distributionId = distributionId;
- }
-
- public RecipientId getRecipientId() {
- return recipientId;
- }
-
- public int getDeviceId() {
- return deviceId;
- }
-
- public UUID getDistributionId() {
- return distributionId;
- }
-
- @Override
- public boolean equals(final Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- final Key key = (Key) o;
-
- if (deviceId != key.deviceId) return false;
- if (!recipientId.equals(key.recipientId)) return false;
- return distributionId.equals(key.distributionId);
- }
-
- @Override
- public int hashCode() {
- int result = recipientId.hashCode();
- result = 31 * result + deviceId;
- result = 31 * result + distributionId.hashCode();
- return result;
- }
}
}
synchronized (sharedSenderKeys) {
return sharedSenderKeys.get(distributionId)
.stream()
- .map(k -> new SignalProtocolAddress(addressResolver.resolveRecipientAddress(k.getRecipientId())
- .getIdentifier(), k.getDeviceId()))
+ .map(k -> new SignalProtocolAddress(addressResolver.resolveRecipientAddress(k.recipientId())
+ .getIdentifier(), k.deviceId()))
.collect(Collectors.toSet());
}
}
sharedSenderKeys.put(distributionId, new HashSet<>(entries) {
{
- entries.removeIf(e -> e.getRecipientId().equals(recipientId));
+ removeIf(e -> e.recipientId().equals(recipientId));
}
});
}
entries.stream()
.map(e -> e.recipientId.equals(toBeMergedRecipientId) ? new SenderKeySharedEntry(
recipientId,
- e.getDeviceId()) : e)
+ e.deviceId()) : e)
.collect(Collectors.toSet()));
}
saveLocked();
var storage = new Storage(sharedSenderKeys.entrySet().stream().flatMap(pair -> {
final var sharedWith = pair.getValue();
return sharedWith.stream()
- .map(entry -> new Storage.SharedSenderKey(entry.getRecipientId().getId(),
- entry.getDeviceId(),
+ .map(entry -> new Storage.SharedSenderKey(entry.recipientId().id(),
+ entry.deviceId(),
pair.getKey().asUuid().toString()));
}).collect(Collectors.toList()));
}
}
- private static class Storage {
+ private record Storage(List<SharedSenderKey> sharedSenderKeys) {
- public List<SharedSenderKey> sharedSenderKeys;
-
- // For deserialization
- private Storage() {
- }
-
- public Storage(final List<SharedSenderKey> sharedSenderKeys) {
- this.sharedSenderKeys = sharedSenderKeys;
- }
-
- private static class SharedSenderKey {
-
- public long recipientId;
- public int deviceId;
- public String distributionId;
-
- // For deserialization
- private SharedSenderKey() {
- }
-
- public SharedSenderKey(final long recipientId, final int deviceId, final String distributionId) {
- this.recipientId = recipientId;
- this.deviceId = deviceId;
- this.distributionId = distributionId;
- }
- }
+ private record SharedSenderKey(long recipientId, int deviceId, String distributionId) {}
}
- private static final class SenderKeySharedEntry {
-
- private final RecipientId recipientId;
- private final int deviceId;
-
- public SenderKeySharedEntry(
- final RecipientId recipientId, final int deviceId
- ) {
- this.recipientId = recipientId;
- this.deviceId = deviceId;
- }
-
- public RecipientId getRecipientId() {
- return recipientId;
- }
-
- public int getDeviceId() {
- return deviceId;
- }
-
- @Override
- public boolean equals(final Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- final SenderKeySharedEntry that = (SenderKeySharedEntry) o;
-
- if (deviceId != that.deviceId) return false;
- return recipientId.equals(that.recipientId);
- }
-
- @Override
- public int hashCode() {
- int result = recipientId.hashCode();
- result = 31 * result + deviceId;
- return result;
- }
- }
+ private record SenderKeySharedEntry(RecipientId recipientId, int deviceId) {}
}
synchronized (cachedSessions) {
return getKeysLocked(recipientId).stream()
// get all sessions for recipient except main device session
- .filter(key -> key.getDeviceId() != 1 && key.getRecipientId().equals(recipientId))
- .map(Key::getDeviceId)
+ .filter(key -> key.deviceId() != 1 && key.recipientId().equals(recipientId))
+ .map(Key::deviceId)
.collect(Collectors.toList());
}
}
.stream()
.flatMap(recipientId -> getKeysLocked(recipientId).stream())
.filter(key -> isActive(this.loadSessionLocked(key)))
- .map(key -> new SignalProtocolAddress(recipientIdToNameMap.get(key.recipientId), key.getDeviceId()))
+ .map(key -> new SignalProtocolAddress(recipientIdToNameMap.get(key.recipientId), key.deviceId()))
.collect(Collectors.toSet());
}
}
if (session == null) {
continue;
}
- final var newKey = new Key(recipientId, key.getDeviceId());
+ final var newKey = new Key(recipientId, key.deviceId());
storeSessionLocked(newKey, session);
}
}
}
private List<Key> getKeysLocked(RecipientId recipientId) {
- final var files = sessionsPath.listFiles((_file, s) -> s.startsWith(recipientId.getId() + "_"));
+ final var files = sessionsPath.listFiles((_file, s) -> s.startsWith(recipientId.id() + "_"));
if (files == null) {
return List.of();
}
} catch (IOException e) {
throw new AssertionError("Failed to create sessions path", e);
}
- return new File(sessionsPath, key.getRecipientId().getId() + "_" + key.getDeviceId());
+ return new File(sessionsPath, key.recipientId().id() + "_" + key.deviceId());
}
private SessionRecord loadSessionLocked(final Key key) {
&& record.getSessionVersion() == CiphertextMessage.CURRENT_VERSION;
}
- private static final class Key {
-
- private final RecipientId recipientId;
- private final int deviceId;
-
- public Key(final RecipientId recipientId, final int deviceId) {
- this.recipientId = recipientId;
- this.deviceId = deviceId;
- }
-
- public RecipientId getRecipientId() {
- return recipientId;
- }
-
- public int getDeviceId() {
- return deviceId;
- }
-
- @Override
- public boolean equals(final Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- final var key = (Key) o;
-
- if (deviceId != key.deviceId) return false;
- return recipientId.equals(key.recipientId);
- }
-
- @Override
- public int hashCode() {
- int result = recipientId.hashCode();
- result = 31 * result + deviceId;
- return result;
- }
- }
+ private record Key(RecipientId recipientId, int deviceId) {}
}
.collect(Collectors.toList()));
}
- public static class Storage {
+ public record Storage(List<Storage.Sticker> stickers) {
- public List<Storage.Sticker> stickers;
+ private record Sticker(String packId, String packKey, boolean installed) {
- // For deserialization
- private Storage() {
- }
-
- public Storage(final List<Sticker> stickers) {
- this.stickers = stickers;
- }
-
- private static class Sticker {
-
- public String packId;
- public String packKey;
- public boolean installed;
-
- // For deserialization
- private Sticker() {
- }
-
- public Sticker(final String packId, final String packKey, final boolean installed) {
- this.packId = packId;
- this.packKey = packKey;
- this.installed = installed;
- }
}
}
if (version >= 4) {
serverDeliveredTimestamp = in.readLong();
}
- Optional<SignalServiceAddress> addressOptional = sourceUuid == null && source.isEmpty()
+ Optional<SignalServiceAddress> addressOptional = sourceUuid == null
? Optional.absent()
: Optional.of(new SignalServiceAddress(sourceUuid, source));
return new SignalServiceEnvelope(type,
}
String[] parts = name.split("\0");
- switch (parts.length) {
- case 0:
- return new Pair<>(null, null);
- case 1:
- return new Pair<>(parts[0], null);
- default:
- return new Pair<>(parts[0], parts[1]);
- }
+ return switch (parts.length) {
+ case 0 -> new Pair<>(null, null);
+ case 1 -> new Pair<>(parts[0], null);
+ default -> new Pair<>(parts[0], parts[1]);
+ };
}
static String trimZeros(String str) {
var pack = parseStickerPack(rootPath, zip);
- if (pack.stickers == null) {
+ if (pack.stickers() == null) {
throw new StickerPackInvalidException("Must set a 'stickers' field.");
}
- if (pack.stickers.isEmpty()) {
+ if (pack.stickers().isEmpty()) {
throw new StickerPackInvalidException("Must include stickers.");
}
- var stickers = new ArrayList<SignalServiceStickerManifestUpload.StickerInfo>(pack.stickers.size());
- for (var sticker : pack.stickers) {
- if (sticker.file == null) {
+ var stickers = new ArrayList<SignalServiceStickerManifestUpload.StickerInfo>(pack.stickers().size());
+ for (var sticker : pack.stickers()) {
+ if (sticker.file() == null) {
throw new StickerPackInvalidException("Must set a 'file' field on each sticker.");
}
Pair<InputStream, Long> data;
try {
- data = getInputStreamAndLength(rootPath, zip, sticker.file);
+ data = getInputStreamAndLength(rootPath, zip, sticker.file());
} catch (IOException ignored) {
- throw new StickerPackInvalidException("Could not find find " + sticker.file);
+ throw new StickerPackInvalidException("Could not find find " + sticker.file());
}
- var contentType = sticker.contentType != null && !sticker.contentType.isEmpty()
- ? sticker.contentType
- : getContentType(rootPath, zip, sticker.file);
+ var contentType = sticker.contentType() != null && !sticker.contentType().isEmpty()
+ ? sticker.contentType()
+ : getContentType(rootPath, zip, sticker.file());
var stickerInfo = new SignalServiceStickerManifestUpload.StickerInfo(data.first(),
data.second(),
- Optional.fromNullable(sticker.emoji).or(""),
+ Optional.fromNullable(sticker.emoji()).or(""),
contentType);
stickers.add(stickerInfo);
}
SignalServiceStickerManifestUpload.StickerInfo cover = null;
- if (pack.cover != null) {
- if (pack.cover.file == null) {
+ if (pack.cover() != null) {
+ if (pack.cover().file() == null) {
throw new StickerPackInvalidException("Must set a 'file' field on the cover.");
}
Pair<InputStream, Long> data;
try {
- data = getInputStreamAndLength(rootPath, zip, pack.cover.file);
+ data = getInputStreamAndLength(rootPath, zip, pack.cover().file());
} catch (IOException ignored) {
- throw new StickerPackInvalidException("Could not find find " + pack.cover.file);
+ throw new StickerPackInvalidException("Could not find find " + pack.cover().file());
}
- var contentType = pack.cover.contentType != null && !pack.cover.contentType.isEmpty()
- ? pack.cover.contentType
- : getContentType(rootPath, zip, pack.cover.file);
+ var contentType = pack.cover().contentType() != null && !pack.cover().contentType().isEmpty() ? pack.cover()
+ .contentType() : getContentType(rootPath, zip, pack.cover().file());
cover = new SignalServiceStickerManifestUpload.StickerInfo(data.first(),
data.second(),
- Optional.fromNullable(pack.cover.emoji).or(""),
+ Optional.fromNullable(pack.cover().emoji()).or(""),
contentType);
}
- return new SignalServiceStickerManifestUpload(pack.title, pack.author, cover, stickers);
+ return new SignalServiceStickerManifestUpload(pack.title(), pack.author(), cover, stickers);
}
private static JsonStickerPack parseStickerPack(String rootPath, ZipFile zip) throws IOException {
Where <type> is according to DBus specification:
-* <s> : String
-* <ay> : Byte Array
-* <aay> : Array of Byte Arrays
-* <as> : String Array
-* <ax> : Array of signed 64 bit integer
-* <b> : Boolean (0|1)
-* <x> : Signed 64 bit integer
+* <a> : Array of ... (comma-separated list) (array:)
+* (...) : Struct (cannot be sent via `dbus-send`)
+* <b> : Boolean (false|true) (boolean:)
+* <i> : Signed 32-bit (int) integer (int32:)
+* <o> : DBusPath object (objpath:)
+* <s> : String (string:)
+* <x> : Signed 64-bit (long) integer (int64:)
+* <y> : Unsigned 8-bit (byte) integer (byte:)
* <> : no return value
+The final parenthetical value (such as "boolean:") is the type indicator used by `dbus-send`.
+
Exceptions are the names of the Java Exceptions returned in the body field. They typically contain an additional message with details. All Exceptions begin with "org.asamk.Signal.Error." which is omitted here for better readability.
Phone numbers always have the format +<countrycode><regional number>
== Methods
=== Control methods
-These methods are available if the daemon is started anonymously (without an explicit `-u USERNAME`).
-Requests are sent to `/org/asamk/Signal`; requests related to individual accounts are sent to
-`/org/asamk/Signal/_441234567890` where the + dialing code is replaced by an underscore (_).
+These methods are available if the daemon is started anonymously (without an explicit `-u USERNAME`).
+Requests are sent to `/org/asamk/Signal`; requests related to individual accounts are sent to
+`/org/asamk/Signal/_441234567890` where the + dialing code is replaced by an underscore (_).
Only `version()` is activated in single-user mode; the rest are disabled.
link() -> deviceLinkUri<s>::
* newDeviceName : Name to give new device (defaults to "cli" if no name is given)
* deviceLinkUri : URI of newly linked device
-Returns a URI of the form "tsdevice:/?uuid=...". This can be piped to a QR encoder to create a display that
+Returns a URI of the form "sgnl://linkdevice?uuid=...". This can be piped to a QR encoder to create a display that
can be captured by a Signal smartphone client. For example:
`dbus-send --session --dest=org.asamk.Signal --type=method_call --print-reply /org/asamk/Signal org.asamk.Signal.link string:"My secondary client"|tr '\n' '\0'|sed 's/.*string //g'|sed 's/\"//g'|qrencode -s10 -tANSI256`
-Exception: Failure
+Exceptions: Failure
listAccounts() -> accountList<as>::
* accountList : Array of all attached accounts in DBus object path form
Command fails if PIN was set after previous registration; use verifyWithPin instead.
-Exception: Failure, InvalidNumber
+Exceptions: Failure, InvalidNumber
verifyWithPin(number<s>, verificationCode<s>, pin<s>) -> <>::
* number : Phone number
* verificationCode : Code received from Signal after successful registration request
* pin : PIN you set with setPin command after verifying previous registration
-Exception: Failure, InvalidNumber
+Exceptions: Failure, InvalidNumber
version() -> version<s>::
* version : Version string of signal-cli
Exceptions: None
-=== Other methods
+=== Group control methods
+The following methods listen to the recipient's object path, which is constructed as follows:
+"/org/asamk/Signal/" + DBusNumber
+* DBusNumber : recipient's phone number, with underscore (_) replacing plus (+)
-updateGroup(groupId<ay>, newName<s>, members<as>, avatar<s>) -> groupId<ay>::
-* groupId : Byte array representing the internal group identifier
-* newName : New name of group (empty if unchanged)
-* members : String array of new members to be invited to group
-* avatar : Filename of avatar picture to be set for group (empty if none)
+createGroup(groupName<s>, members<as>, avatar<s>) -> groupId<ay>::
+* groupName : String representing the display name of the group
+* members : String array of new members to be invited to group
+* avatar : Filename of avatar picture to be set for group (empty if none)
+* groupId : Byte array representing the internal group identifier
-Exceptions: AttachmentInvalid, Failure, InvalidNumber, GroupNotFound
+Exceptions: AttachmentInvalid, Failure, InvalidNumber;
-updateProfile(name<s>, about<s>, aboutEmoji <s>, avatar<s>, remove<b>) -> <>::
-updateProfile(givenName<s>, familyName<s>, about<s>, aboutEmoji <s>, avatar<s>, remove<b>) -> <>::
-* name : Name for your own profile (empty if unchanged)
-* givenName : Given name for your own profile (empty if unchanged)
-* familyName : Family name for your own profile (empty if unchanged)
-* about : About message for profile (empty if unchanged)
-* aboutEmoji : Emoji for profile (empty if unchanged)
-* avatar : Filename of avatar picture for profile (empty if unchanged)
-* remove : Set to true if the existing avatar picture should be removed
+getGroup(groupId<ay>) -> objectPath<o>::
+* groupId : Byte array representing the internal group identifier
+* objectPath : DBusPath for the group
+
+getGroupMembers(groupId<ay>) -> members<as>::
+* groupId : Byte array representing the internal group identifier
+* members : String array with the phone numbers of all active members of a group
+
+Exceptions: None, if the group name is not found an empty array is returned
+
+joinGroup(inviteURI<s>) -> <>::
+* inviteURI : String starting with https://signal.group/#
+
+Behavior of this method depends on the `requirePermission` parameter of the `enableLink` method. If permission is required, `joinGroup` adds you to the requesting members list. Permission may be granted based on the group's `PermissionAddMember` property (`ONLY_ADMINS` or `EVERY_MEMBER`). If permission is not required, `joinGroup` admits you immediately to the group.
Exceptions: Failure
+listGroups() -> groups<a(oays)>::
+* groups : Array of Structs(objectPath, groupId, groupName)
+** objectPath : DBusPath
+** groupId : Byte array representing the internal group identifier
+** groupName : String representing the display name of the group
-setExpirationTimer(number<s>, expiration<i>) -> <>::
-* number : Phone number of recipient
-* expiration : int32 for the number of seconds before messages to this recipient disappear. Set to 0 to disable expiration.
+sendGroupMessage(message<s>, attachments<as>, groupId<ay>) -> timestamp<x>::
+* message : Text to send (can be UTF8)
+* attachments : String array of filenames to send as attachments (passed as filename, so need to be readable by the user signal-cli is running under)
+* groupId : Byte array representing the internal group identifier
+* timestamp : Long, can be used to identify the corresponding Signal reply
+
+Exceptions: GroupNotFound, Failure, AttachmentInvalid, InvalidGroupId
+
+sendGroupMessageReaction(emoji<s>, remove<b>, targetAuthor<s>, targetSentTimestamp<x>, groupId<ay>) -> timestamp<x>::
+* emoji : Unicode grapheme cluster of the emoji
+* remove : Boolean, whether a previously sent reaction (emoji) should be removed
+* targetAuthor : String with the phone number of the author of the message to which to react
+* targetSentTimestamp : Long representing timestamp of the message to which to react
+* groupId : Byte array representing the internal group identifier
+* timestamp : Long, can be used to identify the corresponding signal reply
+
+Exceptions: Failure, InvalidNumber, GroupNotFound, InvalidGroupId
+
+sendGroupRemoteDeleteMessage(targetSentTimestamp<x>, groupId<ay>) -> timestamp<x>::
+* targetSentTimestamp : Long representing timestamp of the message to delete
+* groupId : Byte array with base64 encoded group identifier
+* timestamp : Long, can be used to identify the corresponding signal reply
+
+Exceptions: Failure, GroupNotFound, InvalidGroupId
+
+=== Group methods
+The following methods listen to the group's object path, which can be obtained from the listGroups() method and is constructed as follows:
+"/org/asamk/Signal/" + DBusNumber + "/Groups/" + DBusGroupId
+* DBusNumber : recipient's phone number, with underscore (_) replacing plus (+)
+* DBusGroupId : groupId in base64 format, with underscore (_) replacing plus (+), equals (=), or slash (/)
+
+Groups have the following (case-sensitive) properties:
+* Id<ay> (read-only) : Byte array representing the internal group identifier
+* Name<s> : Display name of the group
+* Description<s> : Description of the group
+* Avatar<s> (write-only) : Filename of the avatar
+* IsBlocked<b> : true=member will not receive group messages; false=not blocked
+* IsMember<b> (read-only) : always true (object path exists only for group members)
+* IsAdmin<b> (read-only) : true=member has admin privileges; false=not admin
+* MessageExpirationTimer<i> : int32 representing message expiration time for group
+* Members<as> (read-only) : String array of group members' phone numbers
+* PendingMembers<as> (read-only) : String array of pending members' phone numbers
+* RequestingMembers<as> (read-only) : String array of requesting members' phone numbers
+* Admins<as> (read-only) : String array of admins' phone numbers
+* PermissionAddMember<s> : String representing who has permission to add members
+** ONLY_ADMINS, EVERY_MEMBER
+* PermissionEditDetails<s> : String representing who may edit group details
+** ONLY_ADMINS, EVERY_MEMBER
+* PermissionSendMessage<s> : String representing who post messages to group
+** ONLY_ADMINS, EVERY_MEMBER (note that ONLY_ADMINS is equivalent to IsAnnouncementGroup)
+* GroupInviteLink<s> (read-only) : String of the invitation link (starts with https://signal.group/#)
+
+To get a property, use (replacing `--session` with `--system` if needed):
+`dbus-send --session --dest=org.asamk.Signal --print-reply $OBJECT_PATH org.freedesktop.DBus.Properties.Get string:org.asamk.Signal.Group string:$PROPERTY_NAME`
+
+To set a property, use:
+`dbus-send --session --dest=org.asamk.Signal --print-reply $OBJECT_PATH org.freedesktop.DBus.Properties.Set string:org.asamk.Signal.Group string:$PROPERTY_NAME variant:$PROPERTY_TYPE:$PROPERTY_VALUE`
+
+To get all properties, use:
+`dbus-send --session --dest=org.asamk.Signal --print-reply $OBJECT_PATH org.freedesktop.DBus.Properties.GetAll string:org.asamk.Signal.Group`
+
+addAdmins(recipients<as>) -> <>::
+* recipients : String array of phone numbers
+
+Grant admin privileges to recipients.
Exceptions: Failure
-setContactBlocked(number<s>, block<b>) -> <>::
-* number : Phone number affected by method
-* block : 0=remove block , 1=blocked
+addMembers(recipients<as>) -> <>::
+* recipients : String array of phone numbers
-Messages from blocked numbers will no longer be forwarded via DBus.
+Add recipients to group if they are pending members; otherwise add recipients to list of requesting members.
-Exceptions: InvalidNumber
+Exceptions: Failure
-setGroupBlocked(groupId<ay>, block<b>) -> <>::
-* groupId : Byte array representing the internal group identifier
-* block : 0=remove block , 1=blocked
+disableLink() -> <>::
-Messages from blocked groups will no longer be forwarded via DBus.
+Disables the group's invitation link.
-Exceptions: GroupNotFound
+Exceptions: Failure
-joinGroup(inviteURI<s>) -> <>::
-* inviteURI : String starting with https://signal.group which is generated when you share a group link via Signal App
+enableLink(requiresApproval<b>) -> <>::
+* requiresApproval : true=add numbers using the link to the requesting members list
+
+Enables the group's invitation link.
+
+Exceptions: Failure
+
+quitGroup() -> <>::
+Exceptions: Failure, LastGroupAdmin
+
+removeAdmins(recipients<as>) -> <>::
+* recipients : String array of phone numbers
+
+Remove admin privileges from recipients.
+
+Exceptions: Failure
+
+removeMembers(recipients<as>) -> <>::
+* recipients : String array of phone numbers
+
+Remove recipients from group.
Exceptions: Failure
+resetLink() -> <>::
+
+Resets the group's invitation link to a new random URL starting with https://signal.group/#
+
+Exceptions: Failure
+
+=== Deprecated group control methods
+The following deprecated methods listen to the recipient's object path, which is constructed as follows:
+"/org/asamk/Signal/" + DBusNumber
+* DBusNumber : recipient's phone number, with underscore (_) replacing plus (+)
+
+getGroupIds() -> groupList<aay>::
+groupList : Array of Byte arrays representing the internal group identifiers
+
+All groups known are returned, regardless of their active or blocked status. To query that use isMember() and isGroupBlocked()
+
+Exceptions: None
+
+getGroupName(groupId<ay>) -> groupName<s>::
+* groupId : Byte array representing the internal group identifier
+* groupName : The display name of the group
+
+Exceptions: None, if the group name is not found an empty string is returned
+
+isGroupBlocked(groupId<ay>) -> isGroupBlocked<b>::
+* groupId : Byte array representing the internal group identifier
+* isGroupBlocked : true=group is blocked; false=group is not blocked
+
+Dbus will not forward messages from a group when you have blocked it.
+
+Exceptions: InvalidGroupId, Failure
+
+isMember(groupId<ay>) -> isMember<b>::
+* groupId : Byte array representing the internal group identifier
+* isMember : true=you are a group member; false=you are not a group member
+
+Note that this method does not raise an Exception for a non-existing/unknown group but will simply return 0 (false)
+
quitGroup(groupId<ay>) -> <>::
* groupId : Byte array representing the internal group identifier
Note that quitting a group will not remove the group from the getGroupIds command, but set it inactive which can be tested with isMember()
-Exceptions: GroupNotFound, Failure
+Exceptions: GroupNotFound, Failure, InvalidGroupId
-isMember(groupId<ay>) -> active<b>::
+setGroupBlocked(groupId<ay>, block<b>) -> <>::
* groupId : Byte array representing the internal group identifier
+* block : false=remove block , true=blocked
-Note that this method does not raise an Exception for a non-existing/unknown group but will simply return 0 (false)
+Messages from blocked groups will no longer be forwarded via DBus.
-sendEndSessionMessage(recipients<as>) -> <>::
-* recipients : Array of phone numbers
+Exceptions: GroupNotFound, InvalidGroupId
-Exceptions: Failure, InvalidNumber, UntrustedIdentity
+updateGroup(groupId<ay>, newName<s>, members<as>, avatar<s>) -> groupId<ay>::
+* groupId : Byte array representing the internal group identifier
+* newName : New name of group (empty if unchanged)
+* members : String array of new members to be invited to group
+* avatar : Filename of avatar picture to be set for group (empty if none)
-sendGroupMessage(message<s>, attachments<as>, groupId<ay>) -> timestamp<x>::
-* message : Text to send (can be UTF8)
-* attachments : String array of filenames to send as attachments (passed as filename, so need to be readable by the user signal-cli is running under)
-* groupId : Byte array representing the internal group identifier
-* timestamp : Can be used to identify the corresponding signal reply
+Exceptions: AttachmentInvalid, Failure, InvalidNumber, GroupNotFound
+
+=== Device control methods
+The following methods listen to the recipient's object path, which is constructed as follows:
+"/org/asamk/Signal/" + DBusNumber
+* DBusNumber : recipient's phone number, with underscore (_) replacing plus (+)
+
+addDevice(deviceUri<s>) -> <>::
+* deviceUri : URI in the form of "sgnl://linkdevice?uuid=..." (formerly "tsdevice:/?uuid=...") Normally displayed by a Signal desktop app, smartphone app, or another signal-cli instance using the `link` control method.
-Exceptions: GroupNotFound, Failure, AttachmentInvalid
+getDevice(deviceId<x>) -> devicePath<o>::
+* deviceId : Long representing a deviceId
+* devicePath : DBusPath object for the device
+
+Exceptions: DeviceNotFound
+
+listDevices() -> devices<a(oxs)>::
+* devices : Array of structs (objectPath, id, name)
+** objectPath : DBusPath representing the device's object path
+** id : Long representing the deviceId
+** name : String representing the device's name
+
+Exceptions: InvalidUri
sendContacts() -> <>::
Sends a synchronization request to the primary device (for group, contacts, ...). Only works if sent from a secondary device.
-Exception: Failure
+Exceptions: Failure
-sendNoteToSelfMessage(message<s>, attachments<as>) -> timestamp<x>::
-* message : Text to send (can be UTF8)
-* attachments : String array of filenames to send as attachments (passed as filename, so need to be readable by the user signal-cli is running under)
-* timestamp : Can be used to identify the corresponding signal reply
+=== Device methods and properties
+The following methods listen to the device's object path, which is constructed as follows:
+"/org/asamk/Signal/" + DBusNumber + "/Devices/" + deviceId
+* DBusNumber : recipient's phone number, with underscore (_) replacing plus (+)
+* deviceId : Long representing the device identifier (obtained from listDevices() method)
-Exceptions: Failure, AttachmentInvalid
+Devices have the following (case-sensitive) properties:
+* Id<x> (read-only) : Long representing the device identifier
+* Created<x> (read-only) : Long representing the number of milliseconds since the Unix epoch
+* LastSeen<x> (read-only) : Long representing the number of milliseconds since the Unix epoch
+* Name<s> : String representing the display name of the device
-sendMessage(message<s>, attachments<as>, recipient<s>) -> timestamp<x>::
-sendMessage(message<s>, attachments<as>, recipients<as>) -> timestamp<x>::
-* message : Text to send (can be UTF8)
-* attachments : String array of filenames to send as attachments (passed as filename, so need to be readable by the user signal-cli is running under)
-* recipient : Phone number of a single recipient
-* recipients : Array of phone numbers
-* timestamp : Can be used to identify the corresponding signal reply
+To get a property, use (replacing `--session` with `--system` if needed):
+`dbus-send --session --dest=org.asamk.Signal --print-reply $OBJECT_PATH org.freedesktop.DBus.Properties.Get string:org.asamk.Signal.Device string:$PROPERTY_NAME`
-Depending on the type of the recipient field this sends a message to one or multiple recipients.
+To set a property, use:
+`dbus-send --session --dest=org.asamk.Signal --print-reply $OBJECT_PATH org.freedesktop.DBus.Properties.Set string:org.asamk.Signal.Device string:$PROPERTY_NAME variant:$PROPERTY_TYPE:$PROPERTY_VALUE`
-Exceptions: AttachmentInvalid, Failure, InvalidNumber, UntrustedIdentity
+To get all properties, use:
+`dbus-send --session --dest=org.asamk.Signal --print-reply $OBJECT_PATH org.freedesktop.DBus.Properties.GetAll string:org.asamk.Signal.Device`
-sendTyping(recipient<s>, stop<b>) -> <>::
-* recipient : Phone number of a single recipient
-* targetSentTimestamp : True, if typing state should be stopped
+removeDevice() -> <>::
-Exceptions: Failure, GroupNotFound, UntrustedIdentity
+Exceptions: Failure
-sendReadReceipt(recipient<s>, targetSentTimestamp<ax>) -> <>::
-* recipient : Phone number of a single recipient
-* targetSentTimestamp : Array of Longs to identify the corresponding signal messages
+=== Other methods
-Exceptions: Failure, UntrustedIdentity
+getContactName(number<s>) -> name<s>::
+* number : Phone number
+* name : Contact's name in local storage (from the master device for a linked account, or the one set with setContactName); if not set, contact's profile name is used
-sendViewedReceipt(recipient<s>, targetSentTimestamp<ax>) -> <>::
-* recipient : Phone number of a single recipient
-* targetSentTimestamp : Array of Longs to identify the corresponding signal messages
+Exceptions: None
-Exceptions: Failure, UntrustedIdentity
+getContactNumber(name<s>) -> numbers<as>::
+* numbers : Array of phone number
+* name : Contact or profile name ("firstname lastname")
-sendGroupMessageReaction(emoji<s>, remove<b>, targetAuthor<s>, targetSentTimestamp<x>, groupId<ay>) -> timestamp<x>::
-* emoji : Unicode grapheme cluster of the emoji
-* remove : Boolean, whether a previously sent reaction (emoji) should be removed
-* targetAuthor : String with the phone number of the author of the message to which to react
-* targetSentTimestamp : Long representing timestamp of the message to which to react
-* groupId : Byte array with base64 encoded group identifier
-* timestamp : Long, can be used to identify the corresponding signal reply
+Searches contacts and known profiles for a given name and returns the list of all known numbers. May result in e.g. two entries if a contact and profile name is set.
+
+Exceptions: None
+
+getSelfNumber() -> number<s>::
+* number : Your phone number
+
+Exceptions: None
+
+isContactBlocked(number<s>) -> blocked<b>::
+* number : Phone number
+* blocked : true=blocked, false=not blocked
+
+For unknown numbers false is returned but no exception is raised.
+
+Exceptions: InvalidPhoneNumber
+
+isRegistered() -> result<b>::
+isRegistered(number<s>) -> result<b>::
+isRegistered(numbers<as>) -> results<ab>::
+* number : Phone number
+* numbers : String array of phone numbers
+* result : true=number is registered, false=number is not registered
+* results : Boolean array of results
+
+For unknown numbers, false is returned, but no exception is raised. If no number is given, returns true (indicating that you are registered).
-Exceptions: Failure, InvalidNumber, GroupNotFound
+Exceptions: InvalidNumber
+
+listNumbers() -> numbers<as>::
+* numbers : String array of all known numbers
+
+This is a concatenated list of all defined contacts as well of profiles known (e.g. peer group members or sender of received messages)
+
+Exceptions: None
+
+removePin() -> <>::
+
+Removes registration PIN protection.
+
+Exceptions: Failure
+
+sendEndSessionMessage(recipients<as>) -> <>::
+* recipients : Array of phone numbers
+
+Exceptions: Failure, InvalidNumber, UntrustedIdentity
+
+sendMessage(message<s>, attachments<as>, recipient<s>) -> timestamp<x>::
+sendMessage(message<s>, attachments<as>, recipients<as>) -> timestamp<x>::
+* message : Text to send (can be UTF8)
+* attachments : String array of filenames to send as attachments (passed as filename, so need to be readable by the user signal-cli is running under)
+* recipient : Phone number of a single recipient
+* recipients : String array of phone numbers
+* timestamp : Long, can be used to identify the corresponding Signal reply
+
+Depending on the type of the recipient field this sends a message to one or multiple recipients.
+
+Exceptions: AttachmentInvalid, Failure, InvalidNumber, UntrustedIdentity
sendMessageReaction(emoji<s>, remove<b>, targetAuthor<s>, targetSentTimestamp<x>, recipient<s>) -> timestamp<x>::
sendMessageReaction(emoji<s>, remove<b>, targetAuthor<s>, targetSentTimestamp<x>, recipients<as>) -> timestamp<x>::
* targetSentTimestamp : Long representing timestamp of the message to which to react
* recipient : String with the phone number of a single recipient
* recipients : Array of strings with phone numbers, should there be more recipients
-* timestamp : Long, can be used to identify the corresponding signal reply
+* timestamp : Long, can be used to identify the corresponding Signal reply
Depending on the type of the recipient(s) field this sends a reaction to one or multiple recipients.
Exceptions: Failure, InvalidNumber
-sendGroupRemoteDeleteMessage(targetSentTimestamp<x>, groupId<ay>) -> timestamp<x>::
-* targetSentTimestamp : Long representing timestamp of the message to delete
-* groupId : Byte array with base64 encoded group identifier
-* timestamp : Long, can be used to identify the corresponding signal reply
+sendNoteToSelfMessage(message<s>, attachments<as>) -> timestamp<x>::
+* message : Text to send (can be UTF8)
+* attachments : String array of filenames to send as attachments (passed as filename, so need to be readable by the user signal-cli is running under)
+* timestamp : Long, can be used to identify the corresponding Signal reply
+
+Exceptions: Failure, AttachmentInvalid
-Exceptions: Failure, GroupNotFound
+sendReadReceipt(recipient<s>, targetSentTimestamps<ax>) -> <>::
+* recipient : Phone number of a single recipient
+* targetSentTimestamps : Array of Longs to identify the corresponding Signal messages
+
+Exceptions: Failure, UntrustedIdentity
+
+sendViewedReceipt(recipient<s>, targetSentTimestamp<ax>) -> <>::
+* recipient : Phone number of a single recipient
+* targetSentTimestamp : Array of Longs to identify the corresponding signal messages
+
+Exceptions: Failure, UntrustedIdentity
sendRemoteDeleteMessage(targetSentTimestamp<x>, recipient<s>) -> timestamp<x>::
sendRemoteDeleteMessage(targetSentTimestamp<x>, recipients<as>) -> timestamp<x>::
Exceptions: Failure, InvalidNumber
-getContactName(number<s>) -> name<s>::
-* number : Phone number
-* name : Contact's name in local storage (from the master device for a linked account, or the one set with setContactName); if not set, contact's profile name is used
-
-setContactName(number<s>,name<>) -> <>::
-* number : Phone number
-* name : Name to be set in contacts (in local storage with signal-cli)
-
-getGroupIds() -> groupList<aay>::
-groupList : Array of Byte arrays representing the internal group identifiers
-
-All groups known are returned, regardless of their active or blocked status. To query that use isMember() and isGroupBlocked()
-
-getGroupName(groupId<ay>) -> groupName<s>::
-groupName : The display name of the group
-groupId : Byte array representing the internal group identifier
-
-Exceptions: None, if the group name is not found an empty string is returned
-
-getGroupMembers(groupId<ay>) -> members<as>::
-members : String array with the phone numbers of all active members of a group
-groupId : Byte array representing the internal group identifier
-
-Exceptions: None, if the group name is not found an empty array is returned
+sendTyping(recipient<s>, stop<b>) -> <>::
+* recipient : Phone number of a single recipient
+* targetSentTimestamp : True, if typing state should be stopped
-listNumbers() -> numbers<as>::
-numbers : String array of all known numbers
+Exceptions: Failure, GroupNotFound, UntrustedIdentity
-This is a concatenated list of all defined contacts as well of profiles known (e.g. peer group members or sender of received messages)
+setContactBlocked(number<s>, block<b>) -> <>::
+* number : Phone number affected by method
+* block : false=remove block, true=blocked
-getContactNumber(name<s>) -> numbers<as>::
-* numbers : Array of phone number
-* name : Contact or profile name ("firstname lastname")
+Messages from blocked numbers will no longer be forwarded via DBus.
-Searches contacts and known profiles for a given name and returns the list of all known numbers. May result in e.g. two entries if a contact and profile name is set.
+Exceptions: InvalidNumber
-isContactBlocked(number<s>) -> state<b>::
+setContactName(number<s>,name<>) -> <>::
* number : Phone number
-* state : 1=blocked, 0=not blocked
-
-Exceptions: None, for unknown numbers 0 (false) is returned
-
-isGroupBlocked(groupId<ay>) -> state<b>::
-* groupId : Byte array representing the internal group identifier
-* state : 1=blocked, 0=not blocked
-
-Exceptions: None, for unknown groups 0 (false) is returned
+* name : Name to be set in contacts (in local storage with signal-cli)
-removePin() -> <>::
+Exceptions: InvalidNumber, Failure
-Removes registration PIN protection.
+setExpirationTimer(number<s>, expiration<i>) -> <>::
+* number : Phone number of recipient
+* expiration : int32 for the number of seconds before messages to this recipient disappear. Set to 0 to disable expiration.
-Exception: Failure
+Exceptions: Failure, InvalidNumber
setPin(pin<s>) -> <>::
* pin : PIN you set after registration (resets after 7 days of inactivity)
Sets a registration lock PIN, to prevent others from registering your number.
-Exception: Failure
-
-version() -> version<s>::
-* version : Version string of signal-cli
-
-isRegistered() -> result<b>::
-isRegistered(number<s>) -> result<b>::
-isRegistered(numbers<as>) -> results<ab>::
-* number : Phone number
-* numbers : String array of phone numbers
-* result : true=number is registered, false=number is not registered
-* results : Boolean array of results
-
-Exception: InvalidNumber for an incorrectly formatted phone number. For unknown numbers, false is returned, but no exception is raised. If no number is given, returns whether you are registered (presumably true).
-
-addDevice(deviceUri<s>) -> <>::
-* deviceUri : URI in the form of tsdevice:/?uuid=... Normally received from Signal desktop or smartphone app
-
-Exception: InvalidUri
-
-listDevices() -> devices<as>::
-* devices : String array of linked devices
-
-Exception: Failure
-
-removeDevice(deviceId<i>) -> <>::
-* deviceId : Device ID to remove, obtained from listDevices() command
+Exceptions: Failure
-Exception: Failure
+submitRateLimitChallenge(challenge<s>, captcha<s>) -> <>::
+* challenge : The challenge token taken from the proof required error.
+* captcha : The captcha token from the solved captcha on the Signal website..
+Can be used to lift some rate-limits by solving a captcha.
-updateDeviceName(deviceName<s>) -> <>::
-* deviceName : New name
+Exception: IOErrorException
-Set a new name for this device (main or linked).
+updateProfile(name<s>, about<s>, aboutEmoji <s>, avatar<s>, remove<b>) -> <>::
+updateProfile(givenName<s>, familyName<s>, about<s>, aboutEmoji <s>, avatar<s>, remove<b>) -> <>::
+* name : Name for your own profile (empty if unchanged)
+* givenName : Given name for your own profile (empty if unchanged)
+* familyName : Family name for your own profile (empty if unchanged)
+* about : About message for profile (empty if unchanged)
+* aboutEmoji : Emoji for profile (empty if unchanged)
+* avatar : Filename of avatar picture for profile (empty if unchanged)
+* remove : Set to true if the existing avatar picture should be removed
-Exception: Failure
+Exceptions: Failure
uploadStickerPack(stickerPackPath<s>) -> url<s>::
* stickerPackPath : Path to the manifest.json file or a zip file in the same directory
* url : URL of sticker pack after successful upload
-Exception: Failure
+Exceptions: Failure
+
+version() -> version<s>::
+* version : Version string of signal-cli
+
+Exceptions: None
== Signals
+SyncMessageReceived (timestamp<x>, sender<s>, destination<s>, groupId<ay>, message<s>, attachments<as>)::
+* timestamp : Integer value that can be used to associate this e.g. with a sendMessage()
+* sender : Phone number of the sender
+* destination : DBus code for destination
+* groupId : Byte array representing the internal group identifier (empty when private message)
+* message : Message text
+* attachments : String array of filenames in the signal-cli storage (~/.local/share/signal-cli/attachments/)
-SyncMessageReceived (timestamp<x>, sender<s>, destination<s>, groupId<ay>,message<s>, attachments<as>)::
The sync message is received when the user sends a message from a linked device.
ReceiptReceived (timestamp<x>, sender<s>)::
* sender : Phone number of the sender
* groupId : Byte array representing the internal group identifier (empty when private message)
* message : Message text
-* attachments : String array of filenames for the attachments. These files are located in the signal-cli storage and the current user needs to have read access there
+* attachments : String array of filenames in the signal-cli storage (~/.local/share/signal-cli/attachments/)
This signal is received whenever we get a private message or a message is posted in a group we are an active member
=== link
Link to an existing device, instead of registering a new number.
-This shows a "tsdevice:/…" URI. If you want to connect to another signal-cli instance, you can just use this URI.
+This shows a "sgnl://linkdevice?uuid=..." URI. If you want to connect to another signal-cli instance, you can just use this URI.
If you want to link to an Android/iOS device, create a QR code with the URI (e.g. with qrencode) and scan that in the Signal app.
*-n* NAME, *--name* NAME::
*--uri* URI::
Specify the uri contained in the QR code shown by the new device.
-You will need the full uri enclosed in quotation marks, such as "tsdevice:/?uuid=....."
+You will need the full URI such as "sgnl://linkdevice?uuid=..." (formerly "tsdevice:/?uuid=...")
+Make sure to enclose it in quotation marks for shells.
=== listDevices
set -e
# To update graalvm config, set GRAALVM_HOME, e.g:
-# export GRAALVM_HOME=/usr/lib/jvm/java-11-graalvm
+# export GRAALVM_HOME=/usr/lib/jvm/java-17-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/'
NUMBER_1="$1"
NUMBER_2="$2"
TEST_PIN_1=456test_pin_foo123
-NATIVE=1
+NATIVE=0
PATH_TEST_CONFIG="$PWD/build/test-config"
PATH_MAIN="$PATH_TEST_CONFIG/main"
register() {
NUMBER=$1
PIN=$2
- echo -n "Enter a captcha token (https://signalcaptchas.org/registration/generate.html): "
+ echo -n "Enter a captcha token (https://signalcaptchas.org/staging/challenge/generate.html): "
read CAPTCHA
run_main -u "$NUMBER" register --captcha "$CAPTCHA"
echo -n "Enter validation code for ${NUMBER}: "
sleep 5
+
+## DBus
+#run_main -u "$NUMBER_1" --dbus send "$NUMBER_2" -m daemon_not_running || true
+#run_main daemon &
+#DAEMON_PID=$!
+#sleep 10
+#run_main -u "$NUMBER_1" --dbus send "$NUMBER_2" -m hii
+#run_main -u "$NUMBER_2" --dbus receive
+#kill "$DAEMON_PID"
+
+
# JSON-RPC
FIFO_FILE="${PATH_MAIN}/dbus-fifo"
run_main -u "$NUMBER_1" removeDevice -d 2
-## DBus
-#run_main -u "$NUMBER_1" --dbus send "$NUMBER_2" -m daemon_not_running
-#run_main daemon &
-#DAEMON_PID=$!
-#sleep 5
-#run_main -u "$NUMBER_1" --dbus send "$NUMBER_2" -m hii
-#run_main -u "$NUMBER_2" --dbus receive
-#kill "$DAEMON_PID"
-
## Unregister
run_main -u "$NUMBER_1" unregister
run_main -u "$NUMBER_2" unregister --delete-account
package org.asamk;
+import org.asamk.signal.commands.exceptions.IOErrorException;
import org.freedesktop.dbus.DBusPath;
+import org.freedesktop.dbus.Struct;
import org.freedesktop.dbus.annotations.DBusProperty;
+import org.freedesktop.dbus.annotations.Position;
import org.freedesktop.dbus.exceptions.DBusException;
import org.freedesktop.dbus.exceptions.DBusExecutionException;
import org.freedesktop.dbus.interfaces.DBusInterface;
void setContactBlocked(String number, boolean blocked) throws Error.InvalidNumber;
+ @Deprecated
void setGroupBlocked(byte[] groupId, boolean blocked) throws Error.GroupNotFound, Error.InvalidGroupId;
+ @Deprecated
List<byte[]> getGroupIds();
+ DBusPath getGroup(byte[] groupId);
+
+ List<StructGroup> listGroups();
+
+ @Deprecated
String getGroupName(byte[] groupId) throws Error.InvalidGroupId;
+ @Deprecated
List<String> getGroupMembers(byte[] groupId) throws Error.InvalidGroupId;
+ byte[] createGroup(
+ String name, List<String> members, String avatar
+ ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber;
+
+ @Deprecated
byte[] updateGroup(
byte[] groupId, String name, List<String> members, String avatar
) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber, Error.GroupNotFound, Error.InvalidGroupId;
DBusPath getDevice(long deviceId);
- List<DBusPath> listDevices() throws Error.Failure;
+ List<StructDevice> listDevices() throws Error.Failure;
DBusPath getThisDevice();
List<String> getContactNumber(final String name) throws Error.Failure;
+ @Deprecated
void quitGroup(final byte[] groupId) throws Error.GroupNotFound, Error.Failure, Error.InvalidGroupId;
boolean isContactBlocked(final String number) throws Error.InvalidNumber;
+ @Deprecated
boolean isGroupBlocked(final byte[] groupId) throws Error.InvalidGroupId;
+ @Deprecated
boolean isMember(final byte[] groupId) throws Error.InvalidGroupId;
byte[] joinGroup(final String groupLink) throws Error.Failure;
String uploadStickerPack(String stickerPackPath) throws Error.Failure;
+ void submitRateLimitChallenge(String challenge, String captchaString) throws IOErrorException;
+
class MessageReceived extends DBusSignal {
private final long timestamp;
}
}
- @DBusProperty(name = "Id", type = Integer.class, access = DBusProperty.Access.READ)
+ class StructDevice extends Struct {
+
+ @Position(0)
+ DBusPath objectPath;
+
+ @Position(1)
+ Long id;
+
+ @Position(2)
+ String name;
+
+ public StructDevice(final DBusPath objectPath, final Long id, final String name) {
+ this.objectPath = objectPath;
+ this.id = id;
+ this.name = name;
+ }
+
+ public DBusPath getObjectPath() {
+ return objectPath;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ @DBusProperty(name = "Id", type = Long.class, access = DBusProperty.Access.READ)
@DBusProperty(name = "Name", type = String.class)
@DBusProperty(name = "Created", type = String.class, access = DBusProperty.Access.READ)
@DBusProperty(name = "LastSeen", type = String.class, access = DBusProperty.Access.READ)
void removeDevice() throws Error.Failure;
}
+ class StructGroup extends Struct {
+
+ @Position(0)
+ DBusPath objectPath;
+
+ @Position(1)
+ byte[] id;
+
+ @Position(2)
+ String name;
+
+ public StructGroup(final DBusPath objectPath, final byte[] id, final String name) {
+ this.objectPath = objectPath;
+ this.id = id;
+ this.name = name;
+ }
+
+ public DBusPath getObjectPath() {
+ return objectPath;
+ }
+
+ public byte[] getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ @DBusProperty(name = "Id", type = Byte[].class, access = DBusProperty.Access.READ)
+ @DBusProperty(name = "Name", type = String.class)
+ @DBusProperty(name = "Description", type = String.class)
+ @DBusProperty(name = "Avatar", type = String.class, access = DBusProperty.Access.WRITE)
+ @DBusProperty(name = "IsBlocked", type = Boolean.class)
+ @DBusProperty(name = "IsMember", type = Boolean.class, access = DBusProperty.Access.READ)
+ @DBusProperty(name = "IsAdmin", type = Boolean.class, access = DBusProperty.Access.READ)
+ @DBusProperty(name = "MessageExpirationTimer", type = Integer.class)
+ @DBusProperty(name = "Members", type = String[].class, access = DBusProperty.Access.READ)
+ @DBusProperty(name = "PendingMembers", type = String[].class, access = DBusProperty.Access.READ)
+ @DBusProperty(name = "RequestingMembers", type = String[].class, access = DBusProperty.Access.READ)
+ @DBusProperty(name = "Admins", type = String[].class, access = DBusProperty.Access.READ)
+ @DBusProperty(name = "PermissionAddMember", type = String.class)
+ @DBusProperty(name = "PermissionEditDetails", type = String.class)
+ @DBusProperty(name = "PermissionSendMessage", type = String.class)
+ @DBusProperty(name = "GroupInviteLink", type = String.class, access = DBusProperty.Access.READ)
+ interface Group extends DBusInterface, Properties {
+
+ void quitGroup() throws Error.Failure, Error.LastGroupAdmin;
+
+ void addMembers(List<String> recipients) throws Error.Failure;
+
+ void removeMembers(List<String> recipients) throws Error.Failure;
+
+ void addAdmins(List<String> recipients) throws Error.Failure;
+
+ void removeAdmins(List<String> recipients) throws Error.Failure;
+
+ void resetLink() throws Error.Failure;
+
+ void disableLink() throws Error.Failure;
+
+ void enableLink(boolean requiresApproval) throws Error.Failure;
+ }
+
interface Error {
class AttachmentInvalid extends DBusExecutionException {
public AttachmentInvalid(final String message) {
- super(message);
+ super("Invalid attachment: " + message);
}
}
class InvalidUri extends DBusExecutionException {
public InvalidUri(final String message) {
- super(message);
+ super("Invalid uri: " + message);
}
}
class Failure extends DBusExecutionException {
+ public Failure(final Exception e) {
+ super("Failure: " + e.getMessage() + " (" + e.getClass().getSimpleName() + ")");
+ }
+
public Failure(final String message) {
- super(message);
+ super("Failure: " + message);
+ }
+ }
+
+ class DeviceNotFound extends DBusExecutionException {
+
+ public DeviceNotFound(final String message) {
+ super("Device not found: " + message);
}
}
class GroupNotFound extends DBusExecutionException {
public GroupNotFound(final String message) {
- super(message);
+ super("Group not found: " + message);
}
}
class InvalidGroupId extends DBusExecutionException {
public InvalidGroupId(final String message) {
- super(message);
+ super("Invalid group id: " + message);
+ }
+ }
+
+ class LastGroupAdmin extends DBusExecutionException {
+
+ public LastGroupAdmin(final String message) {
+ super("Last group admin: " + message);
}
}
class InvalidNumber extends DBusExecutionException {
public InvalidNumber(final String message) {
- super(message);
+ super("Invalid number: " + message);
}
}
class UntrustedIdentity extends DBusExecutionException {
public UntrustedIdentity(final String message) {
- super(message);
+ super("Untrusted identity: " + message);
}
}
}
String link(String newDeviceName) throws Error.Failure;
- public String version();
+ String version();
List<DBusPath> listAccounts();
public void handleMessage(SignalServiceEnvelope envelope, SignalServiceContent content, Throwable exception) {
final var object = new HashMap<String, Object>();
if (exception != null) {
- object.put("error", new JsonError(exception));
+ object.put("error", JsonError.from(exception));
}
if (envelope != null) {
- object.put("envelope", new JsonMessageEnvelope(envelope, content, exception, m));
+ object.put("envelope", JsonMessageEnvelope.from(envelope, content, exception, m));
}
jsonWriter.write(object);
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupUtils;
import org.asamk.signal.util.DateUtils;
-import org.asamk.signal.util.Util;
import org.slf4j.helpers.MessageFormatter;
import org.whispersystems.libsignal.protocol.DecryptionErrorMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
writer.println("Got receipt.");
} else if (envelope.isSignalMessage() || envelope.isPreKeySignalMessage() || envelope.isUnidentifiedSender()) {
if (exception != null) {
- if (exception instanceof UntrustedIdentityException) {
- var e = (UntrustedIdentityException) exception;
+ if (exception instanceof UntrustedIdentityException e) {
writer.println(
"The user’s key is untrusted, either the user has reinstalled Signal or a third party sent this message.");
final var recipientName = getLegacyIdentifier(m.resolveSignalServiceAddress(e.getSender()));
writer.println("Received sync message with verified identities:");
final var verifiedMessage = syncMessage.getVerified().get();
writer.println("- {}: {}", formatContact(verifiedMessage.getDestination()), verifiedMessage.getVerified());
- var safetyNumber = Util.formatSafetyNumber(m.computeSafetyNumber(verifiedMessage.getDestination(),
- verifiedMessage.getIdentityKey()));
- writer.indentedWriter().println(safetyNumber);
}
if (syncMessage.getConfiguration().isPresent()) {
writer.println("Received sync message with configuration:");
var group = m.getGroup(groupId);
if (group != null) {
- writer.println("Name: {}", group.getTitle());
+ writer.println("Name: {}", group.title());
} else {
writer.println("Name: <Unknown group>");
}
for (var groupId : CommandUtil.getGroupIds(groupIdStrings)) {
try {
m.setGroupBlocked(groupId, true);
+ } catch (NotMasterDeviceException e) {
+ throw new UserErrorException("This command doesn't work on linked devices.");
} catch (GroupNotFoundException e) {
logger.warn("Group not found {}: {}", groupId.toBase64(), e.getMessage());
} catch (IOException e) {
import java.io.IOException;
import java.util.List;
-import java.util.concurrent.TimeUnit;
public class DaemonCommand implements MultiLocalCommand {
final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
boolean ignoreAttachments = Boolean.TRUE.equals(ns.getBoolean("ignore-attachments"));
+ m.setIgnoreAttachments(ignoreAttachments);
DBusConnection.DBusBusType busType;
if (Boolean.TRUE.equals(ns.getBoolean("system"))) {
try (var conn = DBusConnection.getConnection(busType)) {
var objectPath = DbusConfig.getObjectPath();
- var t = run(conn, objectPath, m, outputWriter, ignoreAttachments);
+ var t = run(conn, objectPath, m, outputWriter);
conn.requestBusName(DbusConfig.getBusname());
try {
t.join();
+ synchronized (this) {
+ wait();
+ }
} catch (InterruptedException ignored) {
}
} catch (DBusException | IOException e) {
try (var conn = DBusConnection.getConnection(busType)) {
final var signalControl = new DbusSignalControlImpl(c, m -> {
+ m.setIgnoreAttachments(ignoreAttachments);
try {
final var objectPath = DbusConfig.getObjectPath(m.getSelfNumber());
- return run(conn, objectPath, m, outputWriter, ignoreAttachments);
+ return run(conn, objectPath, m, outputWriter);
} catch (DBusException e) {
logger.error("Failed to export object", e);
return null;
}
private Thread run(
- DBusConnection conn, String objectPath, Manager m, OutputWriter outputWriter, boolean ignoreAttachments
+ DBusConnection conn, String objectPath, Manager m, OutputWriter outputWriter
) throws DBusException {
final var signal = new DbusSignalImpl(m, conn, objectPath);
conn.exportObject(signal);
logger.info("Exported dbus object: " + objectPath);
- final var thread = new Thread(() -> {
- while (!Thread.interrupted()) {
- try {
- final var receiveMessageHandler = outputWriter instanceof JsonWriter
- ? new JsonDbusReceiveMessageHandler(m, (JsonWriter) outputWriter, conn, objectPath)
- : new DbusReceiveMessageHandler(m, (PlainTextWriter) outputWriter, conn, objectPath);
- m.receiveMessages(1, TimeUnit.HOURS, false, ignoreAttachments, receiveMessageHandler);
- break;
- } catch (IOException e) {
- logger.warn("Receiving messages failed, retrying", e);
- }
- }
- try {
- initThread.join();
- } catch (InterruptedException ignored) {
- }
- signal.close();
- });
-
- thread.start();
-
- return thread;
+ final var receiveMessageHandler = outputWriter instanceof JsonWriter ? new JsonDbusReceiveMessageHandler(m,
+ (JsonWriter) outputWriter,
+ conn,
+ objectPath) : new DbusReceiveMessageHandler(m, (PlainTextWriter) outputWriter, conn, objectPath);
+ m.addReceiveHandler(receiveMessageHandler);
+ return initThread;
}
}
}
// Output
- if (outputWriter instanceof JsonWriter) {
- final var jsonWriter = (JsonWriter) outputWriter;
+ if (outputWriter instanceof JsonWriter jsonWriter) {
var jsonUserStatuses = registered.entrySet().stream().map(entry -> {
final var number = entry.getValue().first();
}
}
- private static final class JsonUserStatus {
-
- public final String recipient;
-
- public final String number;
-
- public final String uuid;
-
- public final boolean isRegistered;
-
- public JsonUserStatus(String recipient, String number, String uuid, boolean isRegistered) {
- this.recipient = recipient;
- this.number = number;
- this.uuid = uuid;
- this.isRegistered = isRegistered;
- }
- }
+ private record JsonUserStatus(String recipient, String number, String uuid, boolean isRegistered) {}
}
try {
final var results = m.joinGroup(linkUrl);
var newGroupId = results.first();
- if (outputWriter instanceof JsonWriter) {
- final var writer = (JsonWriter) outputWriter;
+ if (outputWriter instanceof JsonWriter writer) {
if (!m.getGroup(newGroupId).isMember()) {
writer.write(Map.of("groupId", newGroupId.toBase64(), "onlyRequested", true));
} else {
writer.println("Joined group \"{}\"", newGroupId.toBase64());
}
}
- handleSendMessageResults(results.second().getResults());
+ handleSendMessageResults(results.second().results());
} catch (GroupPatchNotAcceptedException e) {
throw new UserErrorException("Failed to join group, maybe already a member");
} catch (IOException e) {
import java.io.InputStreamReader;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.TimeUnit;
public class JsonRpcDispatcherCommand implements LocalCommand {
final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
final boolean ignoreAttachments = Boolean.TRUE.equals(ns.getBoolean("ignore-attachments"));
+ m.setIgnoreAttachments(ignoreAttachments);
final var objectMapper = Util.createJsonObjectMapper();
final var jsonRpcSender = new JsonRpcSender((JsonWriter) outputWriter);
- final var receiveThread = receiveMessages(s -> jsonRpcSender.sendRequest(JsonRpcRequest.forNotification(
- "receive",
- objectMapper.valueToTree(s),
- null)), m, ignoreAttachments);
+ final var receiveMessageHandler = new JsonReceiveMessageHandler(m,
+ s -> jsonRpcSender.sendRequest(JsonRpcRequest.forNotification("receive",
+ objectMapper.valueToTree(s),
+ null)));
+ m.addReceiveHandler(receiveMessageHandler);
// Maybe this should be handled inside the Manager
while (!m.hasCaughtUpWithOldMessages()) {
jsonRpcReader.readRequests((method, params) -> handleRequest(m, objectMapper, method, params),
response -> logger.debug("Received unexpected response for id {}", response.getId()));
- receiveThread.interrupt();
- try {
- receiveThread.join();
- } catch (InterruptedException ignored) {
- }
+ m.removeReceiveHandler(receiveMessageHandler);
}
private JsonNode handleRequest(
}
command.handleCommand(requestParams, m, outputWriter);
}
-
- private Thread receiveMessages(
- JsonWriter jsonWriter, Manager m, boolean ignoreAttachments
- ) {
- final var thread = new Thread(() -> {
- while (!Thread.interrupted()) {
- try {
- final var receiveMessageHandler = new JsonReceiveMessageHandler(m, jsonWriter);
- m.receiveMessages(1, TimeUnit.HOURS, false, ignoreAttachments, receiveMessageHandler);
- break;
- } catch (IOException e) {
- logger.warn("Receiving messages failed, retrying", e);
- }
- }
- });
-
- thread.start();
-
- return thread;
- }
}
public interface JsonRpcLocalCommand extends JsonRpcCommand<Map<String, Object>>, LocalCommand {
default TypeReference<Map<String, Object>> getRequestType() {
- return new TypeReference<>() {
- };
+ return new TypeReference<>() {};
}
default void handleCommand(
}
/**
- * Namepace implementation, that defaults booleans to false and converts camel case keys to dashed strings
+ * Namespace implementation, that has plural handling for list arguments and converts camel case keys to dashed strings
*/
final class JsonRpcNamespace extends Namespace {
public void handleCommand(final Namespace ns, final Manager m, final OutputWriter outputWriter) {
var contacts = m.getContacts();
- if (outputWriter instanceof PlainTextWriter) {
- final var writer = (PlainTextWriter) outputWriter;
+ if (outputWriter instanceof PlainTextWriter writer) {
for (var c : contacts) {
final var contact = c.second();
writer.println("Number: {} Name: {} Blocked: {} Message expiration: {}",
}
}
- private static final class JsonContact {
-
- public final String number;
- public final String uuid;
- public final String name;
- public final boolean isBlocked;
- public final int messageExpirationTime;
-
- private JsonContact(
- final String number,
- final String uuid,
- final String name,
- final boolean isBlocked,
- final int messageExpirationTime
- ) {
- this.number = number;
- this.uuid = uuid;
- this.name = name;
- this.isBlocked = isBlocked;
- this.messageExpirationTime = messageExpirationTime;
- }
- }
+ private record JsonContact(String number, String uuid, String name, boolean isBlocked, int messageExpirationTime) {}
}
throw new IOErrorException("Failed to get linked devices: " + e.getMessage(), e);
}
- if (outputWriter instanceof PlainTextWriter) {
- final var writer = (PlainTextWriter) outputWriter;
+ if (outputWriter instanceof PlainTextWriter writer) {
for (var d : devices) {
- writer.println("- Device {}{}:", d.getId(), (d.isThisDevice() ? " (this device)" : ""));
+ writer.println("- Device {}{}:", d.id(), (d.isThisDevice() ? " (this device)" : ""));
writer.indent(w -> {
- w.println("Name: {}", d.getName());
- w.println("Created: {}", DateUtils.formatTimestamp(d.getCreated()));
- w.println("Last seen: {}", DateUtils.formatTimestamp(d.getLastSeen()));
+ w.println("Name: {}", d.name());
+ w.println("Created: {}", DateUtils.formatTimestamp(d.created()));
+ w.println("Last seen: {}", DateUtils.formatTimestamp(d.lastSeen()));
});
}
} else {
final var writer = (JsonWriter) outputWriter;
final var jsonDevices = devices.stream()
- .map(d -> new JsonDevice(d.getId(), d.getName(), d.getCreated(), d.getLastSeen()))
+ .map(d -> new JsonDevice(d.id(), d.name(), d.created(), d.lastSeen()))
.collect(Collectors.toList());
writer.write(jsonDevices);
}
}
- private static final class JsonDevice {
-
- public final long id;
- public final String name;
- public final long createdTimestamp;
- public final long lastSeenTimestamp;
-
- private JsonDevice(
- final long id, final String name, final long createdTimestamp, final long lastSeenTimestamp
- ) {
- this.id = id;
- this.name = name;
- this.createdTimestamp = createdTimestamp;
- this.lastSeenTimestamp = lastSeenTimestamp;
- }
- }
+ private record JsonDevice(long id, String name, long createdTimestamp, long lastSeenTimestamp) {}
}
PlainTextWriter writer, Group group, boolean detailed
) {
if (detailed) {
- final var groupInviteLink = group.getGroupInviteLinkUrl();
+ final var groupInviteLink = group.groupInviteLinkUrl();
writer.println(
"Id: {} Name: {} Description: {} Active: {} Blocked: {} Members: {} Pending members: {} Requesting members: {} Admins: {} Message expiration: {} Link: {}",
- group.getGroupId().toBase64(),
- group.getTitle(),
- group.getDescription(),
+ group.groupId().toBase64(),
+ group.title(),
+ group.description(),
group.isMember(),
group.isBlocked(),
- resolveMembers(group.getMembers()),
- resolveMembers(group.getPendingMembers()),
- resolveMembers(group.getRequestingMembers()),
- resolveMembers(group.getAdminMembers()),
- group.getMessageExpirationTime() == 0 ? "disabled" : group.getMessageExpirationTime() + "s",
+ resolveMembers(group.members()),
+ resolveMembers(group.pendingMembers()),
+ resolveMembers(group.requestingMembers()),
+ resolveMembers(group.adminMembers()),
+ group.messageExpirationTimer() == 0 ? "disabled" : group.messageExpirationTimer() + "s",
groupInviteLink == null ? '-' : groupInviteLink.getUrl());
} else {
writer.println("Id: {} Name: {} Active: {} Blocked: {}",
- group.getGroupId().toBase64(),
- group.getTitle(),
+ group.groupId().toBase64(),
+ group.title(),
group.isMember(),
group.isBlocked());
}
) throws CommandException {
final var groups = m.getGroups();
- if (outputWriter instanceof JsonWriter) {
- final var jsonWriter = (JsonWriter) outputWriter;
+ if (outputWriter instanceof JsonWriter jsonWriter) {
var jsonGroups = groups.stream().map(group -> {
- final var groupInviteLink = group.getGroupInviteLinkUrl();
+ final var groupInviteLink = group.groupInviteLinkUrl();
- return new JsonGroup(group.getGroupId().toBase64(),
- group.getTitle(),
- group.getDescription(),
+ return new JsonGroup(group.groupId().toBase64(),
+ group.title(),
+ group.description(),
group.isMember(),
group.isBlocked(),
- group.getMessageExpirationTime(),
- resolveJsonMembers(group.getMembers()),
- resolveJsonMembers(group.getPendingMembers()),
- resolveJsonMembers(group.getRequestingMembers()),
- resolveJsonMembers(group.getAdminMembers()),
+ group.messageExpirationTimer(),
+ resolveJsonMembers(group.members()),
+ resolveJsonMembers(group.pendingMembers()),
+ resolveJsonMembers(group.requestingMembers()),
+ resolveJsonMembers(group.adminMembers()),
+ group.permissionAddMember().name(),
+ group.permissionEditDetails().name(),
+ group.permissionSendMessage().name(),
groupInviteLink == null ? null : groupInviteLink.getUrl());
}).collect(Collectors.toList());
}
}
- private static final class JsonGroup {
-
- public final String id;
- public final String name;
- public final String description;
- public final boolean isMember;
- public final boolean isBlocked;
- public final int messageExpirationTime;
-
- public final Set<JsonGroupMember> members;
- public final Set<JsonGroupMember> pendingMembers;
- public final Set<JsonGroupMember> requestingMembers;
- public final Set<JsonGroupMember> admins;
- public final String groupInviteLink;
-
- public JsonGroup(
- String id,
- String name,
- String description,
- boolean isMember,
- boolean isBlocked,
- final int messageExpirationTime,
- Set<JsonGroupMember> members,
- Set<JsonGroupMember> pendingMembers,
- Set<JsonGroupMember> requestingMembers,
- Set<JsonGroupMember> admins,
- String groupInviteLink
- ) {
- this.id = id;
- this.name = name;
- this.description = description;
- this.isMember = isMember;
- this.isBlocked = isBlocked;
- this.messageExpirationTime = messageExpirationTime;
-
- this.members = members;
- this.pendingMembers = pendingMembers;
- this.requestingMembers = requestingMembers;
- this.admins = admins;
- this.groupInviteLink = groupInviteLink;
- }
- }
-
- private static final class JsonGroupMember {
-
- public final String number;
- public final String uuid;
-
- private JsonGroupMember(final String number, final String uuid) {
- this.number = number;
- this.uuid = uuid;
- }
- }
+ private record JsonGroup(
+ String id,
+ String name,
+ String description,
+ boolean isMember,
+ boolean isBlocked,
+ int messageExpirationTime,
+ Set<JsonGroupMember> members,
+ Set<JsonGroupMember> pendingMembers,
+ Set<JsonGroupMember> requestingMembers,
+ Set<JsonGroupMember> admins,
+ String permissionAddMember,
+ String permissionEditDetails,
+ String permissionSendMessage,
+ String groupInviteLink
+ ) {}
+
+ private record JsonGroupMember(String number, String uuid) {}
}
}
private static void printIdentityFingerprint(PlainTextWriter writer, Manager m, Identity theirId) {
- final SignalServiceAddress address = theirId.getRecipient().toSignalServiceAddress();
- var digits = Util.formatSafetyNumber(theirId.getSafetyNumber());
+ final SignalServiceAddress address = theirId.recipient().toSignalServiceAddress();
+ var digits = Util.formatSafetyNumber(theirId.safetyNumber());
writer.println("{}: {} Added: {} Fingerprint: {} Safety Number: {}",
address.getNumber().orNull(),
- theirId.getTrustLevel(),
- theirId.getDateAdded(),
+ theirId.trustLevel(),
+ theirId.dateAdded(),
Hex.toString(theirId.getFingerprint()),
digits);
}
identities = m.getIdentities(CommandUtil.getSingleRecipientIdentifier(number, m.getSelfNumber()));
}
- if (outputWriter instanceof PlainTextWriter) {
- final var writer = (PlainTextWriter) outputWriter;
+ if (outputWriter instanceof PlainTextWriter writer) {
for (var id : identities) {
printIdentityFingerprint(writer, m, id);
}
} else {
final var writer = (JsonWriter) outputWriter;
final var jsonIdentities = identities.stream().map(id -> {
- final var address = id.getRecipient().toSignalServiceAddress();
- var safetyNumber = Util.formatSafetyNumber(id.getSafetyNumber());
- var scannableSafetyNumber = id.getScannableSafetyNumber();
+ final var address = id.recipient().toSignalServiceAddress();
+ var safetyNumber = Util.formatSafetyNumber(id.safetyNumber());
+ var scannableSafetyNumber = id.scannableSafetyNumber();
return new JsonIdentity(address.getNumber().orNull(),
address.getUuid().toString(),
Hex.toString(id.getFingerprint()),
scannableSafetyNumber == null
? null
: Base64.getEncoder().encodeToString(scannableSafetyNumber),
- id.getTrustLevel().name(),
- id.getDateAdded().getTime());
+ id.trustLevel().name(),
+ id.dateAdded().getTime());
}).collect(Collectors.toList());
writer.write(jsonIdentities);
}
}
- private static final class JsonIdentity {
-
- public final String number;
- public final String uuid;
- public final String fingerprint;
- public final String safetyNumber;
- public final String scannableSafetyNumber;
- public final String trustLevel;
- public final long addedTimestamp;
-
- private JsonIdentity(
- final String number,
- final String uuid,
- final String fingerprint,
- final String safetyNumber,
- final String scannableSafetyNumber,
- final String trustLevel,
- final long addedTimestamp
- ) {
- this.number = number;
- this.uuid = uuid;
- this.fingerprint = fingerprint;
- this.safetyNumber = safetyNumber;
- this.scannableSafetyNumber = scannableSafetyNumber;
- this.trustLevel = trustLevel;
- this.addedTimestamp = addedTimestamp;
- }
- }
+ private record JsonIdentity(
+ String number,
+ String uuid,
+ String fingerprint,
+ String safetyNumber,
+ String scannableSafetyNumber,
+ String trustLevel,
+ long addedTimestamp
+ ) {}
}
try {
try {
final var results = m.quitGroup(groupId, groupAdmins);
- final var timestamp = results.getTimestamp();
+ final var timestamp = results.timestamp();
outputResult(outputWriter, timestamp);
- handleSendMessageResults(results.getResults());
+ handleSendMessageResults(results.results());
} catch (NotAGroupMemberException e) {
logger.info("User is not a group member");
}
}
private void outputResult(final OutputWriter outputWriter, final long timestamp) {
- if (outputWriter instanceof PlainTextWriter) {
- final var writer = (PlainTextWriter) outputWriter;
+ if (outputWriter instanceof PlainTextWriter writer) {
writer.println("{}", timestamp);
} else {
final var writer = (JsonWriter) outputWriter;
final Namespace ns, final Signal signal, DBusConnection dbusconnection, final OutputWriter outputWriter
) throws CommandException {
try {
- if (outputWriter instanceof JsonWriter) {
- final var jsonWriter = (JsonWriter) outputWriter;
+ if (outputWriter instanceof JsonWriter jsonWriter) {
dbusconnection.addSigHandler(Signal.MessageReceived.class, signal, messageReceived -> {
- var envelope = new JsonMessageEnvelope(messageReceived);
+ var envelope = JsonMessageEnvelope.from(messageReceived);
final var object = Map.of("envelope", envelope);
jsonWriter.write(object);
});
dbusconnection.addSigHandler(Signal.ReceiptReceived.class, signal, receiptReceived -> {
- var envelope = new JsonMessageEnvelope(receiptReceived);
+ var envelope = JsonMessageEnvelope.from(receiptReceived);
final var object = Map.of("envelope", envelope);
jsonWriter.write(object);
});
dbusconnection.addSigHandler(Signal.SyncMessageReceived.class, signal, syncReceived -> {
- var envelope = new JsonMessageEnvelope(syncReceived);
+ var envelope = JsonMessageEnvelope.from(syncReceived);
final var object = Map.of("envelope", envelope);
jsonWriter.write(object);
});
logger.error("Dbus client failed", e);
throw new UnexpectedErrorException("Dbus client failed", e);
}
+
+ double timeout = ns.getDouble("timeout");
+ long timeoutMilliseconds = timeout < 0 ? 10000 : (long) (timeout * 1000);
+
while (true) {
try {
- Thread.sleep(10000);
+ Thread.sleep(timeoutMilliseconds);
} catch (InterruptedException ignored) {
- return;
+ break;
+ }
+ if (timeout >= 0) {
+ break;
}
}
}
final Namespace ns, final Manager m, final OutputWriter outputWriter
) throws CommandException {
double timeout = ns.getDouble("timeout");
- var returnOnTimeout = true;
- if (timeout < 0) {
- returnOnTimeout = false;
- timeout = 3600;
- }
boolean ignoreAttachments = Boolean.TRUE.equals(ns.getBoolean("ignore-attachments"));
+ m.setIgnoreAttachments(ignoreAttachments);
try {
final var handler = outputWriter instanceof JsonWriter ? new JsonReceiveMessageHandler(m,
(JsonWriter) outputWriter) : new ReceiveMessageHandler(m, (PlainTextWriter) outputWriter);
- m.receiveMessages((long) (timeout * 1000),
- TimeUnit.MILLISECONDS,
- returnOnTimeout,
- ignoreAttachments,
- handler);
+ if (timeout < 0) {
+ m.receiveMessages(handler);
+ } else {
+ m.receiveMessages((long) (timeout * 1000), TimeUnit.MILLISECONDS, handler);
+ }
} catch (IOException e) {
throw new IOErrorException("Error while receiving messages: " + e.getMessage(), e);
}
} catch (CaptchaRequiredException e) {
String message;
if (captcha == null) {
- message = "Captcha required for verification, use --captcha CAPTCHA\n"
- + "To get the token, go to https://signalcaptchas.org/registration/generate.html\n"
- + "Check the developer tools (F12) console for a failed redirect to signalcaptcha://\n"
- + "Everything after signalcaptcha:// is the captcha token.";
+ message = """
+ Captcha required for verification, use --captcha CAPTCHA
+ To get the token, go to https://signalcaptchas.org/registration/generate.html
+ Check the developer tools (F12) console for a failed redirect to signalcaptcha://
+ Everything after signalcaptcha:// is the captcha token.""";
} else {
message = "Invalid captcha given.";
}
try {
final var results = m.sendRemoteDeleteMessage(targetTimestamp, recipientIdentifiers);
- outputResult(outputWriter, results.getTimestamp());
- ErrorUtils.handleSendMessageResults(results.getResults());
+ outputResult(outputWriter, results.timestamp());
+ ErrorUtils.handleSendMessageResults(results.results());
} catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
throw new UserErrorException(e.getMessage());
} catch (IOException e) {
}
private void outputResult(final OutputWriter outputWriter, final long timestamp) {
- if (outputWriter instanceof PlainTextWriter) {
- final var writer = (PlainTextWriter) outputWriter;
+ if (outputWriter instanceof PlainTextWriter writer) {
writer.println("{}", timestamp);
} else {
final var writer = (JsonWriter) outputWriter;
try {
var results = m.sendMessage(new Message(messageText, attachments), recipientIdentifiers);
- outputResult(outputWriter, results.getTimestamp());
- ErrorUtils.handleSendMessageResults(results.getResults());
+ outputResult(outputWriter, results.timestamp());
+ ErrorUtils.handleSendMessageResults(results.results());
} catch (AttachmentInvalidException | IOException e) {
throw new UnexpectedErrorException("Failed to send message: " + e.getMessage() + " (" + e.getClass()
.getSimpleName() + ")", e);
}
private void outputResult(final OutputWriter outputWriter, final long timestamp) {
- if (outputWriter instanceof PlainTextWriter) {
- final var writer = (PlainTextWriter) outputWriter;
+ if (outputWriter instanceof PlainTextWriter writer) {
writer.println("{}", timestamp);
} else {
final var writer = (JsonWriter) outputWriter;
CommandUtil.getSingleRecipientIdentifier(targetAuthor, m.getSelfNumber()),
targetTimestamp,
recipientIdentifiers);
- outputResult(outputWriter, results.getTimestamp());
- ErrorUtils.handleSendMessageResults(results.getResults());
+ outputResult(outputWriter, results.timestamp());
+ ErrorUtils.handleSendMessageResults(results.results());
} catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
throw new UserErrorException(e.getMessage());
} catch (IOException e) {
}
private void outputResult(final OutputWriter outputWriter, final long timestamp) {
- if (outputWriter instanceof PlainTextWriter) {
- final var writer = (PlainTextWriter) outputWriter;
+ if (outputWriter instanceof PlainTextWriter writer) {
writer.println("{}", timestamp);
} else {
final var writer = (JsonWriter) outputWriter;
for (var groupId : CommandUtil.getGroupIds(groupIdStrings)) {
try {
m.setGroupBlocked(groupId, false);
+ } catch (NotMasterDeviceException e) {
+ throw new UserErrorException("This command doesn't work on linked devices.");
} catch (GroupNotFoundException e) {
logger.warn("Unknown group id: {}", groupId);
} catch (IOException e) {
import org.asamk.signal.commands.exceptions.UserErrorException;
import org.asamk.signal.manager.AttachmentInvalidException;
import org.asamk.signal.manager.Manager;
+import org.asamk.signal.manager.api.UpdateGroup;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupLinkState;
import org.asamk.signal.manager.groups.GroupNotFoundException;
if (value == null) {
return null;
}
- switch (value) {
- case "enabled":
- return GroupLinkState.ENABLED;
- case "enabled-with-approval":
- case "enabledWithApproval":
- return GroupLinkState.ENABLED_WITH_APPROVAL;
- case "disabled":
- return GroupLinkState.DISABLED;
- default:
- throw new UserErrorException("Invalid group link state: " + value);
- }
+ return switch (value) {
+ case "enabled" -> GroupLinkState.ENABLED;
+ case "enabled-with-approval", "enabledWithApproval" -> GroupLinkState.ENABLED_WITH_APPROVAL;
+ case "disabled" -> GroupLinkState.DISABLED;
+ default -> throw new UserErrorException("Invalid group link state: " + value);
+ };
}
GroupPermission getGroupPermission(String value) throws UserErrorException {
if (value == null) {
return null;
}
- switch (value) {
- case "every-member":
- case "everyMember":
- return GroupPermission.EVERY_MEMBER;
- case "only-admins":
- case "onlyAdmins":
- return GroupPermission.ONLY_ADMINS;
- default:
- throw new UserErrorException("Invalid group permission: " + value);
- }
+ return switch (value) {
+ case "every-member", "everyMember" -> GroupPermission.EVERY_MEMBER;
+ case "only-admins", "onlyAdmins" -> GroupPermission.ONLY_ADMINS;
+ default -> throw new UserErrorException("Invalid group permission: " + value);
+ };
}
@Override
var results = m.createGroup(groupName,
groupMembers,
groupAvatar == null ? null : new File(groupAvatar));
- timestamp = results.second().getTimestamp();
- ErrorUtils.handleSendMessageResults(results.second().getResults());
+ timestamp = results.second().timestamp();
+ ErrorUtils.handleSendMessageResults(results.second().results());
groupId = results.first();
groupName = null;
groupMembers = null;
}
var results = m.updateGroup(groupId,
- groupName,
- groupDescription,
- groupMembers,
- groupRemoveMembers,
- groupAdmins,
- groupRemoveAdmins,
- groupResetLink,
- groupLinkState,
- groupAddMemberPermission,
- groupEditDetailsPermission,
- groupAvatar == null ? null : new File(groupAvatar),
- groupExpiration,
- groupSendMessagesPermission == null
- ? null
- : groupSendMessagesPermission == GroupPermission.ONLY_ADMINS);
+ UpdateGroup.newBuilder()
+ .withName(groupName)
+ .withDescription(groupDescription)
+ .withMembers(groupMembers)
+ .withRemoveMembers(groupRemoveMembers)
+ .withAdmins(groupAdmins)
+ .withRemoveAdmins(groupRemoveAdmins)
+ .withResetGroupLink(groupResetLink)
+ .withGroupLinkState(groupLinkState)
+ .withAddMemberPermission(groupAddMemberPermission)
+ .withEditDetailsPermission(groupEditDetailsPermission)
+ .withAvatarFile(groupAvatar == null ? null : new File(groupAvatar))
+ .withExpirationTimer(groupExpiration)
+ .withIsAnnouncementGroup(groupSendMessagesPermission == null
+ ? null
+ : groupSendMessagesPermission == GroupPermission.ONLY_ADMINS)
+ .build());
if (results != null) {
- timestamp = results.getTimestamp();
- ErrorUtils.handleSendMessageResults(results.getResults());
+ timestamp = results.timestamp();
+ ErrorUtils.handleSendMessageResults(results.results());
}
outputResult(outputWriter, timestamp, isNewGroup ? groupId : null);
} catch (AttachmentInvalidException e) {
}
private void outputResult(final OutputWriter outputWriter, final Long timestamp, final GroupId groupId) {
- if (outputWriter instanceof PlainTextWriter) {
- final var writer = (PlainTextWriter) outputWriter;
+ if (outputWriter instanceof PlainTextWriter writer) {
if (groupId != null) {
writer.println("Created new group: \"{}\"", groupId.toBase64());
}
try {
var url = m.uploadStickerPack(path);
- if (outputWriter instanceof PlainTextWriter) {
- final var writer = (PlainTextWriter) outputWriter;
+ if (outputWriter instanceof PlainTextWriter writer) {
writer.println("{}", url);
} else {
final var writer = (JsonWriter) outputWriter;
import org.asamk.signal.manager.api.SendGroupMessageResults;
import org.asamk.signal.manager.api.SendMessageResults;
import org.asamk.signal.manager.api.TypingAction;
+import org.asamk.signal.manager.api.UpdateGroup;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
-import org.asamk.signal.manager.groups.GroupLinkState;
import org.asamk.signal.manager.groups.GroupNotFoundException;
import org.asamk.signal.manager.groups.GroupPermission;
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
import org.freedesktop.dbus.connections.impl.DBusConnection;
import org.freedesktop.dbus.exceptions.DBusException;
import org.freedesktop.dbus.interfaces.DBusInterface;
-import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional;
@Override
public List<Device> getLinkedDevices() throws IOException {
final var thisDevice = signal.getThisDevice();
- return signal.listDevices().stream().map(devicePath -> {
- final var device = getRemoteObject(devicePath, Signal.Device.class).GetAll("org.asamk.Signal.Device");
+ return signal.listDevices().stream().map(d -> {
+ final var device = getRemoteObject(d.getObjectPath(),
+ Signal.Device.class).GetAll("org.asamk.Signal.Device");
return new Device((long) device.get("Id").getValue(),
(String) device.get("Name").getValue(),
(long) device.get("Created").getValue(),
(long) device.get("LastSeen").getValue(),
- thisDevice.equals(devicePath));
+ thisDevice.equals(d.getObjectPath()));
}).collect(Collectors.toList());
}
@Override
public List<Group> getGroups() {
- final var groupIds = signal.getGroupIds();
- return groupIds.stream().map(id -> getGroup(GroupId.unknownVersion(id))).collect(Collectors.toList());
+ final var groups = signal.listGroups();
+ return groups.stream().map(Signal.StructGroup::getObjectPath).map(this::getGroup).collect(Collectors.toList());
}
@Override
if (groupAdmins.size() > 0) {
throw new UnsupportedOperationException();
}
- signal.quitGroup(groupId.serialize());
+ final var group = getRemoteObject(signal.getGroup(groupId.serialize()), Signal.Group.class);
+ group.quitGroup();
return new SendGroupMessageResults(0, List.of());
}
public Pair<GroupId, SendGroupMessageResults> createGroup(
final String name, final Set<RecipientIdentifier.Single> members, final File avatarFile
) throws IOException, AttachmentInvalidException {
- final var newGroupId = signal.updateGroup(new byte[0],
- emptyIfNull(name),
+ final var newGroupId = signal.createGroup(emptyIfNull(name),
members.stream().map(RecipientIdentifier.Single::getIdentifier).collect(Collectors.toList()),
avatarFile == null ? "" : avatarFile.getPath());
return new Pair<>(GroupId.unknownVersion(newGroupId), new SendGroupMessageResults(0, List.of()));
@Override
public SendGroupMessageResults updateGroup(
- final GroupId groupId,
- final String name,
- final String description,
- final Set<RecipientIdentifier.Single> members,
- final Set<RecipientIdentifier.Single> removeMembers,
- final Set<RecipientIdentifier.Single> admins,
- final Set<RecipientIdentifier.Single> removeAdmins,
- final boolean resetGroupLink,
- final GroupLinkState groupLinkState,
- final GroupPermission addMemberPermission,
- final GroupPermission editDetailsPermission,
- final File avatarFile,
- final Integer expirationTimer,
- final Boolean isAnnouncementGroup
+ final GroupId groupId, final UpdateGroup updateGroup
) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException {
- signal.updateGroup(groupId.serialize(),
- emptyIfNull(name),
- members.stream().map(RecipientIdentifier.Single::getIdentifier).collect(Collectors.toList()),
- avatarFile == null ? "" : avatarFile.getPath());
+ final var group = getRemoteObject(signal.getGroup(groupId.serialize()), Signal.Group.class);
+ if (updateGroup.getName() != null) {
+ group.Set("org.asamk.Signal.Group", "Name", updateGroup.getName());
+ }
+ if (updateGroup.getDescription() != null) {
+ group.Set("org.asamk.Signal.Group", "Description", updateGroup.getDescription());
+ }
+ if (updateGroup.getAvatarFile() != null) {
+ group.Set("org.asamk.Signal.Group",
+ "Avatar",
+ updateGroup.getAvatarFile() == null ? "" : updateGroup.getAvatarFile().getPath());
+ }
+ if (updateGroup.getExpirationTimer() != null) {
+ group.Set("org.asamk.Signal.Group", "MessageExpirationTimer", updateGroup.getExpirationTimer());
+ }
+ if (updateGroup.getAddMemberPermission() != null) {
+ group.Set("org.asamk.Signal.Group", "PermissionAddMember", updateGroup.getAddMemberPermission().name());
+ }
+ if (updateGroup.getEditDetailsPermission() != null) {
+ group.Set("org.asamk.Signal.Group", "PermissionEditDetails", updateGroup.getEditDetailsPermission().name());
+ }
+ if (updateGroup.getIsAnnouncementGroup() != null) {
+ group.Set("org.asamk.Signal.Group",
+ "PermissionSendMessage",
+ updateGroup.getIsAnnouncementGroup()
+ ? GroupPermission.ONLY_ADMINS.name()
+ : GroupPermission.EVERY_MEMBER.name());
+ }
+ if (updateGroup.getMembers() != null) {
+ group.addMembers(updateGroup.getMembers()
+ .stream()
+ .map(RecipientIdentifier.Single::getIdentifier)
+ .collect(Collectors.toList()));
+ }
+ if (updateGroup.getRemoveMembers() != null) {
+ group.removeMembers(updateGroup.getRemoveMembers()
+ .stream()
+ .map(RecipientIdentifier.Single::getIdentifier)
+ .collect(Collectors.toList()));
+ }
+ if (updateGroup.getAdmins() != null) {
+ group.addAdmins(updateGroup.getAdmins()
+ .stream()
+ .map(RecipientIdentifier.Single::getIdentifier)
+ .collect(Collectors.toList()));
+ }
+ if (updateGroup.getRemoveAdmins() != null) {
+ group.removeAdmins(updateGroup.getRemoveAdmins()
+ .stream()
+ .map(RecipientIdentifier.Single::getIdentifier)
+ .collect(Collectors.toList()));
+ }
+ if (updateGroup.isResetGroupLink()) {
+ group.resetLink();
+ }
+ if (updateGroup.getGroupLinkState() != null) {
+ switch (updateGroup.getGroupLinkState()) {
+ case DISABLED -> group.disableLink();
+ case ENABLED -> group.enableLink(false);
+ case ENABLED_WITH_APPROVAL -> group.enableLink(true);
+ }
+ }
return new SendGroupMessageResults(0, List.of());
}
final Message message, final Set<RecipientIdentifier> recipients
) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException {
return handleMessage(recipients,
- numbers -> signal.sendMessage(message.getMessageText(), message.getAttachments(), numbers),
- () -> signal.sendNoteToSelfMessage(message.getMessageText(), message.getAttachments()),
- groupId -> signal.sendGroupMessage(message.getMessageText(), message.getAttachments(), groupId));
+ numbers -> signal.sendMessage(message.messageText(), message.attachments(), numbers),
+ () -> signal.sendNoteToSelfMessage(message.messageText(), message.attachments()),
+ groupId -> signal.sendGroupMessage(message.messageText(), message.attachments(), groupId));
}
@Override
public void setGroupBlocked(
final GroupId groupId, final boolean blocked
) throws GroupNotFoundException, IOException {
- signal.setGroupBlocked(groupId.serialize(), blocked);
+ setGroupProperty(groupId, "IsBlocked", blocked);
+ }
+
+ private void setGroupProperty(final GroupId groupId, final String propertyName, final boolean blocked) {
+ final var group = getRemoteObject(signal.getGroup(groupId.serialize()), Signal.Group.class);
+ group.Set("org.asamk.Signal.Group", propertyName, blocked);
}
@Override
signal.sendSyncRequest();
}
+ @Override
+ public void addReceiveHandler(final ReceiveMessageHandler handler) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeReceiveHandler(final ReceiveMessageHandler handler) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isReceiving() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void receiveMessages(final ReceiveMessageHandler handler) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public void receiveMessages(
- final long timeout,
- final TimeUnit unit,
- final boolean returnOnTimeout,
- final boolean ignoreAttachments,
- final ReceiveMessageHandler handler
+ final long timeout, final TimeUnit unit, final ReceiveMessageHandler handler
) throws IOException {
throw new UnsupportedOperationException();
}
+ @Override
+ public void setIgnoreAttachments(final boolean ignoreAttachments) {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public boolean hasCaughtUpWithOldMessages() {
throw new UnsupportedOperationException();
@Override
public Group getGroup(final GroupId groupId) {
- final var id = groupId.serialize();
- return new Group(groupId,
- signal.getGroupName(id),
- null,
- null,
- signal.getGroupMembers(id).stream().map(m -> new RecipientAddress(null, m)).collect(Collectors.toSet()),
- Set.of(),
- Set.of(),
- Set.of(),
- signal.isGroupBlocked(id),
- 0,
- false,
- signal.isMember(id));
+ final var groupPath = signal.getGroup(groupId.serialize());
+ return getGroup(groupPath);
+ }
+
+ @SuppressWarnings("unchecked")
+ private Group getGroup(final DBusPath groupPath) {
+ final var group = getRemoteObject(groupPath, Signal.Group.class).GetAll("org.asamk.Signal.Group");
+ final var id = (byte[]) group.get("Id").getValue();
+ try {
+ return new Group(GroupId.unknownVersion(id),
+ (String) group.get("Name").getValue(),
+ (String) group.get("Description").getValue(),
+ GroupInviteLinkUrl.fromUri((String) group.get("GroupInviteLink").getValue()),
+ ((List<String>) group.get("Members").getValue()).stream()
+ .map(m -> new RecipientAddress(null, m))
+ .collect(Collectors.toSet()),
+ ((List<String>) group.get("PendingMembers").getValue()).stream()
+ .map(m -> new RecipientAddress(null, m))
+ .collect(Collectors.toSet()),
+ ((List<String>) group.get("RequestingMembers").getValue()).stream()
+ .map(m -> new RecipientAddress(null, m))
+ .collect(Collectors.toSet()),
+ ((List<String>) group.get("Admins").getValue()).stream()
+ .map(m -> new RecipientAddress(null, m))
+ .collect(Collectors.toSet()),
+ (boolean) group.get("IsBlocked").getValue(),
+ (int) group.get("MessageExpirationTimer").getValue(),
+ GroupPermission.valueOf((String) group.get("PermissionAddMember").getValue()),
+ GroupPermission.valueOf((String) group.get("PermissionEditDetails").getValue()),
+ GroupPermission.valueOf((String) group.get("PermissionSendMessage").getValue()),
+ (boolean) group.get("IsMember").getValue(),
+ (boolean) group.get("IsAdmin").getValue());
+ } catch (GroupInviteLinkUrl.InvalidGroupLinkException | GroupInviteLinkUrl.UnknownGroupLinkVersionException e) {
+ throw new AssertionError(e);
+ }
}
@Override
throw new UnsupportedOperationException();
}
- @Override
- public String computeSafetyNumber(
- final SignalServiceAddress theirAddress, final IdentityKey theirIdentityKey
- ) {
- throw new UnsupportedOperationException();
- }
-
@Override
public SignalServiceAddress resolveSignalServiceAddress(final SignalServiceAddress address) {
return address;
}
@Override
+ @SuppressWarnings("unchecked")
public Map<String, Variant<?>> GetAll(final String interface_name) {
final var handler = getHandlerOptional(interface_name);
if (handler.isEmpty()) {
.getProperties()
.stream()
.filter(p -> p.getGetter() != null)
- .collect(Collectors.toMap(DbusProperty::getName, p -> new Variant<>(p.getGetter().get())));
+ .collect(Collectors.toMap(DbusProperty::getName, p -> {
+ final Object o = p.getGetter().get();
+ return o instanceof Variant ? (Variant<Object>) o : new Variant<>(o);
+ }));
}
}
this.setter = null;
}
+ public DbusProperty(final String name, final Consumer<T> setter) {
+ this.name = name;
+ this.getter = null;
+ this.setter = setter;
+ }
+
public String getName() {
return name;
}
import org.asamk.Signal;
import org.asamk.signal.BaseConfig;
+import org.asamk.signal.commands.exceptions.IOErrorException;
import org.asamk.signal.manager.AttachmentInvalidException;
import org.asamk.signal.manager.Manager;
import org.asamk.signal.manager.NotMasterDeviceException;
import org.asamk.signal.manager.api.Message;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.TypingAction;
+import org.asamk.signal.manager.api.UpdateGroup;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
+import org.asamk.signal.manager.groups.GroupLinkState;
import org.asamk.signal.manager.groups.GroupNotFoundException;
+import org.asamk.signal.manager.groups.GroupPermission;
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.freedesktop.dbus.connections.impl.DBusConnection;
import org.freedesktop.dbus.exceptions.DBusException;
import org.freedesktop.dbus.exceptions.DBusExecutionException;
+import org.freedesktop.dbus.types.Variant;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
private final String objectPath;
private DBusPath thisDevice;
- private final List<DBusPath> devices = new ArrayList<>();
+ private final List<StructDevice> devices = new ArrayList<>();
+ private final List<StructGroup> groups = new ArrayList<>();
public DbusSignalImpl(final Manager m, DBusConnection connection, final String objectPath) {
this.m = m;
public void initObjects() {
updateDevices();
+ updateGroups();
}
public void close() {
unExportDevices();
+ unExportGroups();
}
@Override
return m.getSelfNumber();
}
+ @Override
+ public void submitRateLimitChallenge(String challenge, String captchaString) throws IOErrorException {
+ final var captcha = captchaString == null ? null : captchaString.replace("signalcaptcha://", "");
+
+ try {
+ m.submitRateLimitRecaptchaChallenge(challenge, captcha);
+ } catch (IOException e) {
+ throw new IOErrorException("Submit challenge error: " + e.getMessage(), e);
+ }
+
+ }
+
@Override
public void addDevice(String uri) {
try {
@Override
public DBusPath getDevice(long deviceId) {
updateDevices();
- return new DBusPath(getDeviceObjectPath(objectPath, deviceId));
+ final var deviceOptional = devices.stream().filter(g -> g.getId().equals(deviceId)).findFirst();
+ if (deviceOptional.isEmpty()) {
+ throw new Error.DeviceNotFound("Device not found");
+ }
+ return deviceOptional.get().getObjectPath();
}
@Override
- public List<DBusPath> listDevices() {
+ public List<StructDevice> listDevices() {
updateDevices();
return this.devices;
}
- private void updateDevices() {
- List<org.asamk.signal.manager.api.Device> linkedDevices;
- try {
- linkedDevices = m.getLinkedDevices();
- } catch (IOException | Error.Failure e) {
- throw new Error.Failure("Failed to get linked devices: " + e.getMessage());
- }
-
- unExportDevices();
-
- linkedDevices.forEach(d -> {
- final var object = new DbusSignalDeviceImpl(d);
- final var deviceObjectPath = object.getObjectPath();
- try {
- connection.exportObject(object);
- } catch (DBusException e) {
- e.printStackTrace();
- }
- if (d.isThisDevice()) {
- thisDevice = new DBusPath(deviceObjectPath);
- }
- this.devices.add(new DBusPath(deviceObjectPath));
- });
- }
-
- private void unExportDevices() {
- this.devices.stream().map(DBusPath::getPath).forEach(connection::unExportObject);
- this.devices.clear();
- }
-
@Override
public DBusPath getThisDevice() {
updateDevices();
.map(RecipientIdentifier.class::cast)
.collect(Collectors.toSet()));
- checkSendMessageResults(results.getTimestamp(), results.getResults());
- return results.getTimestamp();
+ checkSendMessageResults(results.timestamp(), results.results());
+ return results.timestamp();
} catch (AttachmentInvalidException e) {
throw new Error.AttachmentInvalid(e.getMessage());
} catch (IOException e) {
- throw new Error.Failure(e.getMessage());
+ throw new Error.Failure(e);
} catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
throw new Error.GroupNotFound(e.getMessage());
}
getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
.map(RecipientIdentifier.class::cast)
.collect(Collectors.toSet()));
- checkSendMessageResults(results.getTimestamp(), results.getResults());
- return results.getTimestamp();
+ checkSendMessageResults(results.timestamp(), results.results());
+ return results.timestamp();
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
} catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
try {
final var results = m.sendRemoteDeleteMessage(targetSentTimestamp,
Set.of(new RecipientIdentifier.Group(getGroupId(groupId))));
- checkSendMessageResults(results.getTimestamp(), results.getResults());
- return results.getTimestamp();
+ checkSendMessageResults(results.timestamp(), results.results());
+ return results.timestamp();
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
} catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
getSingleRecipientIdentifiers(recipients, m.getSelfNumber()).stream()
.map(RecipientIdentifier.class::cast)
.collect(Collectors.toSet()));
- checkSendMessageResults(results.getTimestamp(), results.getResults());
- return results.getTimestamp();
+ checkSendMessageResults(results.timestamp(), results.results());
+ return results.timestamp();
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
} catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
try {
final var results = m.sendMessage(new Message(message, attachments),
Set.of(RecipientIdentifier.NoteToSelf.INSTANCE));
- checkSendMessageResults(results.getTimestamp(), results.getResults());
- return results.getTimestamp();
+ checkSendMessageResults(results.timestamp(), results.results());
+ return results.timestamp();
} catch (AttachmentInvalidException e) {
throw new Error.AttachmentInvalid(e.getMessage());
} catch (IOException e) {
public void sendEndSessionMessage(final List<String> recipients) {
try {
final var results = m.sendEndSessionMessage(getSingleRecipientIdentifiers(recipients, m.getSelfNumber()));
- checkSendMessageResults(results.getTimestamp(), results.getResults());
+ checkSendMessageResults(results.timestamp(), results.results());
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
}
try {
var results = m.sendMessage(new Message(message, attachments),
Set.of(new RecipientIdentifier.Group(getGroupId(groupId))));
- checkSendMessageResults(results.getTimestamp(), results.getResults());
- return results.getTimestamp();
+ checkSendMessageResults(results.timestamp(), results.results());
+ return results.timestamp();
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
} catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
getSingleRecipientIdentifier(targetAuthor, m.getSelfNumber()),
targetSentTimestamp,
Set.of(new RecipientIdentifier.Group(getGroupId(groupId))));
- checkSendMessageResults(results.getTimestamp(), results.getResults());
- return results.getTimestamp();
+ checkSendMessageResults(results.timestamp(), results.results());
+ return results.timestamp();
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
} catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
public void setGroupBlocked(final byte[] groupId, final boolean blocked) {
try {
m.setGroupBlocked(getGroupId(groupId), blocked);
+ } catch (NotMasterDeviceException e) {
+ throw new Error.Failure("This command doesn't work on linked devices.");
} catch (GroupNotFoundException e) {
throw new Error.GroupNotFound(e.getMessage());
} catch (IOException e) {
var groups = m.getGroups();
var ids = new ArrayList<byte[]>(groups.size());
for (var group : groups) {
- ids.add(group.getGroupId().serialize());
+ ids.add(group.groupId().serialize());
}
return ids;
}
+ @Override
+ public DBusPath getGroup(final byte[] groupId) {
+ updateGroups();
+ final var groupOptional = groups.stream().filter(g -> Arrays.equals(g.getId(), groupId)).findFirst();
+ if (groupOptional.isEmpty()) {
+ throw new Error.GroupNotFound("Group not found");
+ }
+ return groupOptional.get().getObjectPath();
+ }
+
+ @Override
+ public List<StructGroup> listGroups() {
+ updateGroups();
+ return groups;
+ }
+
@Override
public String getGroupName(final byte[] groupId) {
var group = m.getGroup(getGroupId(groupId));
- if (group == null || group.getTitle() == null) {
+ if (group == null || group.title() == null) {
return "";
} else {
- return group.getTitle();
+ return group.title();
}
}
if (group == null) {
return List.of();
} else {
- return group.getMembers().stream().map(RecipientAddress::getLegacyIdentifier).collect(Collectors.toList());
+ final var members = group.members();
+ return getRecipientStrings(members);
}
}
+ @Override
+ public byte[] createGroup(
+ final String name, final List<String> members, final String avatar
+ ) throws Error.AttachmentInvalid, Error.Failure, Error.InvalidNumber {
+ return updateGroup(new byte[0], name, members, avatar);
+ }
+
@Override
public byte[] updateGroup(byte[] groupId, String name, List<String> members, String avatar) {
try {
final var memberIdentifiers = getSingleRecipientIdentifiers(members, m.getSelfNumber());
if (groupId == null) {
final var results = m.createGroup(name, memberIdentifiers, avatar == null ? null : new File(avatar));
- checkSendMessageResults(results.second().getTimestamp(), results.second().getResults());
+ checkSendMessageResults(results.second().timestamp(), results.second().results());
return results.first().serialize();
} else {
final var results = m.updateGroup(getGroupId(groupId),
- name,
- null,
- memberIdentifiers,
- null,
- null,
- null,
- false,
- null,
- null,
- null,
- avatar == null ? null : new File(avatar),
- null,
- null);
+ UpdateGroup.newBuilder()
+ .withName(name)
+ .withMembers(memberIdentifiers)
+ .withAvatarFile(avatar == null ? null : new File(avatar))
+ .build());
if (results != null) {
- checkSendMessageResults(results.getTimestamp(), results.getResults());
+ checkSendMessageResults(results.timestamp(), results.results());
}
return groupId;
}
// all numbers the system knows
@Override
public List<String> listNumbers() {
- return Stream.concat(m.getIdentities().stream().map(Identity::getRecipient),
- m.getContacts().stream().map(Pair::first))
+ return Stream.concat(m.getIdentities().stream().map(Identity::recipient),
+ m.getContacts().stream().map(Pair::first))
.map(a -> a.getNumber().orElse(null))
.filter(Objects::nonNull)
.distinct()
}
// Try profiles if no contact name was found
for (var identity : m.getIdentities()) {
- final var address = identity.getRecipient();
+ final var address = identity.recipient();
var number = address.getNumber().orElse(null);
if (number != null) {
Profile profile = null;
throw new Error.Failure(message.toString());
}
+ private static List<String> getRecipientStrings(final Set<RecipientAddress> members) {
+ return members.stream().map(RecipientAddress::getLegacyIdentifier).collect(Collectors.toList());
+ }
+
private static Set<RecipientIdentifier.Single> getSingleRecipientIdentifiers(
final Collection<String> recipientStrings, final String localNumber
) throws DBusExecutionException {
return name.isEmpty() ? null : name;
}
+ private String emptyIfNull(final String string) {
+ return string == null ? "" : string;
+ }
+
private static String getDeviceObjectPath(String basePath, long deviceId) {
return basePath + "/Devices/" + deviceId;
}
+ private void updateDevices() {
+ List<org.asamk.signal.manager.api.Device> linkedDevices;
+ try {
+ linkedDevices = m.getLinkedDevices();
+ } catch (IOException e) {
+ throw new Error.Failure("Failed to get linked devices: " + e.getMessage());
+ }
+
+ unExportDevices();
+
+ linkedDevices.forEach(d -> {
+ final var object = new DbusSignalDeviceImpl(d);
+ final var deviceObjectPath = object.getObjectPath();
+ try {
+ connection.exportObject(object);
+ } catch (DBusException e) {
+ e.printStackTrace();
+ }
+ if (d.isThisDevice()) {
+ thisDevice = new DBusPath(deviceObjectPath);
+ }
+ this.devices.add(new StructDevice(new DBusPath(deviceObjectPath), d.id(), emptyIfNull(d.name())));
+ });
+ }
+
+ private void unExportDevices() {
+ this.devices.stream()
+ .map(StructDevice::getObjectPath)
+ .map(DBusPath::getPath)
+ .forEach(connection::unExportObject);
+ this.devices.clear();
+ }
+
+ private static String getGroupObjectPath(String basePath, byte[] groupId) {
+ return basePath + "/Groups/" + Base64.getEncoder()
+ .encodeToString(groupId)
+ .replace("+", "_")
+ .replace("/", "_")
+ .replace("=", "_");
+ }
+
+ private void updateGroups() {
+ List<org.asamk.signal.manager.api.Group> groups;
+ groups = m.getGroups();
+
+ unExportGroups();
+
+ groups.forEach(g -> {
+ final var object = new DbusSignalGroupImpl(g.groupId());
+ try {
+ connection.exportObject(object);
+ } catch (DBusException e) {
+ e.printStackTrace();
+ }
+ this.groups.add(new StructGroup(new DBusPath(object.getObjectPath()),
+ g.groupId().serialize(),
+ emptyIfNull(g.title())));
+ });
+ }
+
+ private void unExportGroups() {
+ this.groups.stream().map(StructGroup::getObjectPath).map(DBusPath::getPath).forEach(connection::unExportObject);
+ this.groups.clear();
+ }
+
public class DbusSignalDeviceImpl extends DbusProperties implements Signal.Device {
private final org.asamk.signal.manager.api.Device device;
public DbusSignalDeviceImpl(final org.asamk.signal.manager.api.Device device) {
- super();
super.addPropertiesHandler(new DbusInterfacePropertiesHandler("org.asamk.Signal.Device",
- List.of(new DbusProperty<>("Id", device::getId),
- new DbusProperty<>("Name",
- () -> device.getName() == null ? "" : device.getName(),
- this::setDeviceName),
- new DbusProperty<>("Created", device::getCreated),
- new DbusProperty<>("LastSeen", device::getLastSeen))));
+ List.of(new DbusProperty<>("Id", device::id),
+ new DbusProperty<>("Name", () -> emptyIfNull(device.name()), this::setDeviceName),
+ new DbusProperty<>("Created", device::created),
+ new DbusProperty<>("LastSeen", device::lastSeen))));
this.device = device;
}
@Override
public String getObjectPath() {
- return getDeviceObjectPath(objectPath, device.getId());
+ return getDeviceObjectPath(objectPath, device.id());
}
@Override
public void removeDevice() throws Error.Failure {
try {
- m.removeLinkedDevices(device.getId());
+ m.removeLinkedDevices(device.id());
updateDevices();
} catch (IOException e) {
throw new Error.Failure(e.getMessage());
}
}
}
+
+ public class DbusSignalGroupImpl extends DbusProperties implements Signal.Group {
+
+ private final GroupId groupId;
+
+ public DbusSignalGroupImpl(final GroupId groupId) {
+ this.groupId = groupId;
+ super.addPropertiesHandler(new DbusInterfacePropertiesHandler("org.asamk.Signal.Group",
+ List.of(new DbusProperty<>("Id", groupId::serialize),
+ new DbusProperty<>("Name", () -> emptyIfNull(getGroup().title()), this::setGroupName),
+ new DbusProperty<>("Description",
+ () -> emptyIfNull(getGroup().description()),
+ this::setGroupDescription),
+ new DbusProperty<>("Avatar", this::setGroupAvatar),
+ new DbusProperty<>("IsBlocked", () -> getGroup().isBlocked(), this::setIsBlocked),
+ new DbusProperty<>("IsMember", () -> getGroup().isMember()),
+ new DbusProperty<>("IsAdmin", () -> getGroup().isAdmin()),
+ new DbusProperty<>("MessageExpirationTimer",
+ () -> getGroup().messageExpirationTimer(),
+ this::setMessageExpirationTime),
+ new DbusProperty<>("Members",
+ () -> new Variant<>(getRecipientStrings(getGroup().members()), "as")),
+ new DbusProperty<>("PendingMembers",
+ () -> new Variant<>(getRecipientStrings(getGroup().pendingMembers()), "as")),
+ new DbusProperty<>("RequestingMembers",
+ () -> new Variant<>(getRecipientStrings(getGroup().requestingMembers()), "as")),
+ new DbusProperty<>("Admins",
+ () -> new Variant<>(getRecipientStrings(getGroup().adminMembers()), "as")),
+ new DbusProperty<>("PermissionAddMember",
+ () -> getGroup().permissionAddMember().name(),
+ this::setGroupPermissionAddMember),
+ new DbusProperty<>("PermissionEditDetails",
+ () -> getGroup().permissionEditDetails().name(),
+ this::setGroupPermissionEditDetails),
+ new DbusProperty<>("PermissionSendMessage",
+ () -> getGroup().permissionSendMessage().name(),
+ this::setGroupPermissionSendMessage),
+ new DbusProperty<>("GroupInviteLink", () -> {
+ final var groupInviteLinkUrl = getGroup().groupInviteLinkUrl();
+ return groupInviteLinkUrl == null ? "" : groupInviteLinkUrl.getUrl();
+ }))));
+ }
+
+ @Override
+ public String getObjectPath() {
+ return getGroupObjectPath(objectPath, groupId.serialize());
+ }
+
+ @Override
+ public void quitGroup() throws Error.Failure {
+ try {
+ m.quitGroup(groupId, Set.of());
+ } catch (GroupNotFoundException | NotAGroupMemberException e) {
+ throw new Error.GroupNotFound(e.getMessage());
+ } catch (IOException e) {
+ throw new Error.Failure(e.getMessage());
+ } catch (LastGroupAdminException e) {
+ throw new Error.LastGroupAdmin(e.getMessage());
+ }
+ }
+
+ @Override
+ public void addMembers(final List<String> recipients) throws Error.Failure {
+ final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber());
+ updateGroup(UpdateGroup.newBuilder().withMembers(memberIdentifiers).build());
+ }
+
+ @Override
+ public void removeMembers(final List<String> recipients) throws Error.Failure {
+ final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber());
+ updateGroup(UpdateGroup.newBuilder().withRemoveMembers(memberIdentifiers).build());
+ }
+
+ @Override
+ public void addAdmins(final List<String> recipients) throws Error.Failure {
+ final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber());
+ updateGroup(UpdateGroup.newBuilder().withAdmins(memberIdentifiers).build());
+ }
+
+ @Override
+ public void removeAdmins(final List<String> recipients) throws Error.Failure {
+ final var memberIdentifiers = getSingleRecipientIdentifiers(recipients, m.getSelfNumber());
+ updateGroup(UpdateGroup.newBuilder().withRemoveAdmins(memberIdentifiers).build());
+ }
+
+ @Override
+ public void resetLink() throws Error.Failure {
+ updateGroup(UpdateGroup.newBuilder().withResetGroupLink(true).build());
+ }
+
+ @Override
+ public void disableLink() throws Error.Failure {
+ updateGroup(UpdateGroup.newBuilder().withGroupLinkState(GroupLinkState.DISABLED).build());
+ }
+
+ @Override
+ public void enableLink(final boolean requiresApproval) throws Error.Failure {
+ updateGroup(UpdateGroup.newBuilder()
+ .withGroupLinkState(requiresApproval
+ ? GroupLinkState.ENABLED_WITH_APPROVAL
+ : GroupLinkState.ENABLED)
+ .build());
+ }
+
+ private org.asamk.signal.manager.api.Group getGroup() {
+ return m.getGroup(groupId);
+ }
+
+ private void setGroupName(final String name) {
+ updateGroup(UpdateGroup.newBuilder().withName(name).build());
+ }
+
+ private void setGroupDescription(final String description) {
+ updateGroup(UpdateGroup.newBuilder().withDescription(description).build());
+ }
+
+ private void setGroupAvatar(final String avatar) {
+ updateGroup(UpdateGroup.newBuilder().withAvatarFile(new File(avatar)).build());
+ }
+
+ private void setMessageExpirationTime(final int expirationTime) {
+ updateGroup(UpdateGroup.newBuilder().withExpirationTimer(expirationTime).build());
+ }
+
+ private void setGroupPermissionAddMember(final String permission) {
+ updateGroup(UpdateGroup.newBuilder().withAddMemberPermission(GroupPermission.valueOf(permission)).build());
+ }
+
+ private void setGroupPermissionEditDetails(final String permission) {
+ updateGroup(UpdateGroup.newBuilder()
+ .withEditDetailsPermission(GroupPermission.valueOf(permission))
+ .build());
+ }
+
+ private void setGroupPermissionSendMessage(final String permission) {
+ updateGroup(UpdateGroup.newBuilder()
+ .withIsAnnouncementGroup(GroupPermission.valueOf(permission) == GroupPermission.ONLY_ADMINS)
+ .build());
+ }
+
+ private void setIsBlocked(final boolean isBlocked) {
+ try {
+ m.setGroupBlocked(groupId, isBlocked);
+ } catch (NotMasterDeviceException e) {
+ throw new Error.Failure("This command doesn't work on linked devices.");
+ } catch (GroupNotFoundException e) {
+ throw new Error.GroupNotFound(e.getMessage());
+ } catch (IOException e) {
+ throw new Error.Failure(e.getMessage());
+ }
+ }
+
+ private void updateGroup(final UpdateGroup updateGroup) {
+ try {
+ m.updateGroup(groupId, updateGroup);
+ } catch (IOException e) {
+ throw new Error.Failure(e.getMessage());
+ } catch (GroupNotFoundException | NotAGroupMemberException | GroupSendingNotAllowedException e) {
+ throw new Error.GroupNotFound(e.getMessage());
+ } catch (AttachmentInvalidException e) {
+ throw new Error.AttachmentInvalid(e.getMessage());
+ }
+ }
+ }
}
package org.asamk.signal.json;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
-class JsonAttachment {
-
- @JsonProperty
- final String contentType;
-
- @JsonProperty
- final String filename;
-
- @JsonProperty
- final String id;
-
- @JsonProperty
- final Long size;
-
- JsonAttachment(SignalServiceAttachment attachment) {
- this.contentType = attachment.getContentType();
+record JsonAttachment(String contentType, String filename, String id, Long size) {
+ static JsonAttachment from(SignalServiceAttachment attachment) {
if (attachment.isPointer()) {
final var pointer = attachment.asPointer();
- this.id = pointer.getRemoteId().toString();
- this.filename = pointer.getFileName().orNull();
- this.size = pointer.getSize().transform(Integer::longValue).orNull();
+ final var id = pointer.getRemoteId().toString();
+ final var filename = pointer.getFileName().orNull();
+ final var size = pointer.getSize().transform(Integer::longValue).orNull();
+ return new JsonAttachment(attachment.getContentType(), filename, id, size);
} else {
final var stream = attachment.asStream();
- this.id = null;
- this.filename = stream.getFileName().orNull();
- this.size = stream.getLength();
+ final var filename = stream.getFileName().orNull();
+ final var size = stream.getLength();
+ return new JsonAttachment(attachment.getContentType(), filename, null, size);
}
}
- JsonAttachment(String filename) {
- this.filename = filename;
- this.contentType = null;
- this.id = null;
- this.size = null;
+ static JsonAttachment from(String filename) {
+ return new JsonAttachment(filename, null, null, null);
}
}
package org.asamk.signal.json;
import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
import org.whispersystems.signalservice.api.messages.calls.AnswerMessage;
import org.whispersystems.signalservice.api.messages.calls.BusyMessage;
import java.util.List;
-class JsonCallMessage {
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final OfferMessage offerMessage;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final AnswerMessage answerMessage;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final BusyMessage busyMessage;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final HangupMessage hangupMessage;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final List<IceUpdateMessage> iceUpdateMessages;
-
- JsonCallMessage(SignalServiceCallMessage callMessage) {
- this.offerMessage = callMessage.getOfferMessage().orNull();
- this.answerMessage = callMessage.getAnswerMessage().orNull();
- this.busyMessage = callMessage.getBusyMessage().orNull();
- this.hangupMessage = callMessage.getHangupMessage().orNull();
- this.iceUpdateMessages = callMessage.getIceUpdateMessages().orNull();
+record JsonCallMessage(
+ @JsonInclude(JsonInclude.Include.NON_NULL) OfferMessage offerMessage,
+ @JsonInclude(JsonInclude.Include.NON_NULL) AnswerMessage answerMessage,
+ @JsonInclude(JsonInclude.Include.NON_NULL) BusyMessage busyMessage,
+ @JsonInclude(JsonInclude.Include.NON_NULL) HangupMessage hangupMessage,
+ @JsonInclude(JsonInclude.Include.NON_NULL) List<IceUpdateMessage> iceUpdateMessages
+) {
+
+ static JsonCallMessage from(SignalServiceCallMessage callMessage) {
+ return new JsonCallMessage(callMessage.getOfferMessage().orNull(),
+ callMessage.getAnswerMessage().orNull(),
+ callMessage.getBusyMessage().orNull(),
+ callMessage.getHangupMessage().orNull(),
+ callMessage.getIceUpdateMessages().orNull());
}
}
package org.asamk.signal.json;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
import org.asamk.signal.util.Util;
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
-public class JsonContactAddress {
-
- @JsonProperty
- private final SharedContact.PostalAddress.Type type;
-
- @JsonProperty
- private final String label;
-
- @JsonProperty
- private final String street;
-
- @JsonProperty
- private final String pobox;
-
- @JsonProperty
- private final String neighborhood;
-
- @JsonProperty
- private final String city;
-
- @JsonProperty
- private final String region;
-
- @JsonProperty
- private final String postcode;
-
- @JsonProperty
- private final String country;
-
- public JsonContactAddress(SharedContact.PostalAddress address) {
- type = address.getType();
- label = Util.getStringIfNotBlank(address.getLabel());
- street = Util.getStringIfNotBlank(address.getStreet());
- pobox = Util.getStringIfNotBlank(address.getPobox());
- neighborhood = Util.getStringIfNotBlank(address.getNeighborhood());
- city = Util.getStringIfNotBlank(address.getCity());
- region = Util.getStringIfNotBlank(address.getRegion());
- postcode = Util.getStringIfNotBlank(address.getPostcode());
- country = Util.getStringIfNotBlank(address.getCountry());
+public record JsonContactAddress(
+ SharedContact.PostalAddress.Type type,
+ String label,
+ String street,
+ String pobox,
+ String neighborhood,
+ String city,
+ String region,
+ String postcode,
+ String country
+) {
+
+ static JsonContactAddress from(SharedContact.PostalAddress address) {
+ return new JsonContactAddress(address.getType(),
+ Util.getStringIfNotBlank(address.getLabel()),
+ Util.getStringIfNotBlank(address.getStreet()),
+ Util.getStringIfNotBlank(address.getPobox()),
+ Util.getStringIfNotBlank(address.getNeighborhood()),
+ Util.getStringIfNotBlank(address.getCity()),
+ Util.getStringIfNotBlank(address.getRegion()),
+ Util.getStringIfNotBlank(address.getPostcode()),
+ Util.getStringIfNotBlank(address.getCountry()));
}
}
package org.asamk.signal.json;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
-public class JsonContactAvatar {
-
- @JsonProperty
- private final JsonAttachment attachment;
-
- @JsonProperty
- private final boolean isProfile;
+public record JsonContactAvatar(JsonAttachment attachment, boolean isProfile) {
- public JsonContactAvatar(SharedContact.Avatar avatar) {
- attachment = new JsonAttachment(avatar.getAttachment());
- isProfile = avatar.isProfile();
+ static JsonContactAvatar from(SharedContact.Avatar avatar) {
+ return new JsonContactAvatar(JsonAttachment.from(avatar.getAttachment()), avatar.isProfile());
}
}
package org.asamk.signal.json;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
import org.asamk.signal.util.Util;
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
-public class JsonContactEmail {
-
- @JsonProperty
- private final String value;
-
- @JsonProperty
- private final SharedContact.Email.Type type;
-
- @JsonProperty
- private final String label;
+public record JsonContactEmail(String value, SharedContact.Email.Type type, String label) {
- public JsonContactEmail(SharedContact.Email email) {
- value = email.getValue();
- type = email.getType();
- label = Util.getStringIfNotBlank(email.getLabel());
+ static JsonContactEmail from(SharedContact.Email email) {
+ return new JsonContactEmail(email.getValue(), email.getType(), Util.getStringIfNotBlank(email.getLabel()));
}
}
package org.asamk.signal.json;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
import org.asamk.signal.util.Util;
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
-public class JsonContactName {
-
- @JsonProperty
- private final String display;
-
- @JsonProperty
- private final String given;
-
- @JsonProperty
- private final String family;
-
- @JsonProperty
- private final String prefix;
-
- @JsonProperty
- private final String suffix;
-
- @JsonProperty
- private final String middle;
-
- public JsonContactName(SharedContact.Name name) {
- display = Util.getStringIfNotBlank(name.getDisplay());
- given = Util.getStringIfNotBlank(name.getGiven());
- family = Util.getStringIfNotBlank(name.getFamily());
- prefix = Util.getStringIfNotBlank(name.getPrefix());
- suffix = Util.getStringIfNotBlank(name.getSuffix());
- middle = Util.getStringIfNotBlank(name.getMiddle());
+public record JsonContactName(
+ String display, String given, String family, String prefix, String suffix, String middle
+) {
+
+ static JsonContactName from(SharedContact.Name name) {
+ return new JsonContactName(Util.getStringIfNotBlank(name.getDisplay()),
+ Util.getStringIfNotBlank(name.getGiven()),
+ Util.getStringIfNotBlank(name.getFamily()),
+ Util.getStringIfNotBlank(name.getPrefix()),
+ Util.getStringIfNotBlank(name.getSuffix()),
+ Util.getStringIfNotBlank(name.getMiddle()));
}
}
package org.asamk.signal.json;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
import org.asamk.signal.util.Util;
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
-public class JsonContactPhone {
-
- @JsonProperty
- private final String value;
-
- @JsonProperty
- private final SharedContact.Phone.Type type;
-
- @JsonProperty
- private final String label;
+public record JsonContactPhone(String value, SharedContact.Phone.Type type, String label) {
- public JsonContactPhone(SharedContact.Phone phone) {
- value = phone.getValue();
- type = phone.getType();
- label = Util.getStringIfNotBlank(phone.getLabel());
+ static JsonContactPhone from(SharedContact.Phone phone) {
+ return new JsonContactPhone(phone.getValue(), phone.getType(), Util.getStringIfNotBlank(phone.getLabel()));
}
}
package org.asamk.signal.json;
import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
import org.asamk.Signal;
import org.asamk.signal.manager.Manager;
import java.util.List;
import java.util.stream.Collectors;
-class JsonDataMessage {
-
- @JsonProperty
- final long timestamp;
-
- @JsonProperty
- final String message;
-
- @JsonProperty
- final Integer expiresInSeconds;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final Boolean viewOnce;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final JsonReaction reaction;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final JsonQuote quote;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final List<JsonMention> mentions;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final List<JsonAttachment> attachments;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final JsonSticker sticker;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final JsonRemoteDelete remoteDelete;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final List<JsonSharedContact> contacts;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final JsonGroupInfo groupInfo;
-
- JsonDataMessage(SignalServiceDataMessage dataMessage, Manager m) {
- this.timestamp = dataMessage.getTimestamp();
+record JsonDataMessage(
+ long timestamp,
+ String message,
+ Integer expiresInSeconds,
+ @JsonInclude(JsonInclude.Include.NON_NULL) Boolean viewOnce,
+ @JsonInclude(JsonInclude.Include.NON_NULL) JsonReaction reaction,
+ @JsonInclude(JsonInclude.Include.NON_NULL) JsonQuote quote,
+ @JsonInclude(JsonInclude.Include.NON_NULL) List<JsonMention> mentions,
+ @JsonInclude(JsonInclude.Include.NON_NULL) List<JsonAttachment> attachments,
+ @JsonInclude(JsonInclude.Include.NON_NULL) JsonSticker sticker,
+ @JsonInclude(JsonInclude.Include.NON_NULL) JsonRemoteDelete remoteDelete,
+ @JsonInclude(JsonInclude.Include.NON_NULL) List<JsonSharedContact> contacts,
+ @JsonInclude(JsonInclude.Include.NON_NULL) JsonGroupInfo groupInfo
+) {
+
+ static JsonDataMessage from(SignalServiceDataMessage dataMessage, Manager m) {
+ final var timestamp = dataMessage.getTimestamp();
+ final JsonGroupInfo groupInfo;
if (dataMessage.getGroupContext().isPresent()) {
final var groupContext = dataMessage.getGroupContext().get();
if (groupContext.getGroupV1().isPresent()) {
- var groupInfo = groupContext.getGroupV1().get();
- this.groupInfo = new JsonGroupInfo(groupInfo);
+ var group = groupContext.getGroupV1().get();
+ groupInfo = JsonGroupInfo.from(group);
} else if (groupContext.getGroupV2().isPresent()) {
- var groupInfo = groupContext.getGroupV2().get();
- this.groupInfo = new JsonGroupInfo(groupInfo);
+ var group = groupContext.getGroupV2().get();
+ groupInfo = JsonGroupInfo.from(group);
} else {
- this.groupInfo = null;
+ groupInfo = null;
}
} else {
- this.groupInfo = null;
+ groupInfo = null;
}
- this.message = dataMessage.getBody().orNull();
- this.expiresInSeconds = dataMessage.getExpiresInSeconds();
- this.viewOnce = dataMessage.isViewOnce();
- this.reaction = dataMessage.getReaction().isPresent()
- ? new JsonReaction(dataMessage.getReaction().get(), m)
- : null;
- this.quote = dataMessage.getQuote().isPresent() ? new JsonQuote(dataMessage.getQuote().get(), m) : null;
+ final var message = dataMessage.getBody().orNull();
+ final var expiresInSeconds = dataMessage.getExpiresInSeconds();
+ final var viewOnce = dataMessage.isViewOnce();
+ final var reaction = dataMessage.getReaction().isPresent() ? JsonReaction.from(dataMessage.getReaction().get(),
+ m) : null;
+ final var quote = dataMessage.getQuote().isPresent() ? JsonQuote.from(dataMessage.getQuote().get(), m) : null;
+ final List<JsonMention> mentions;
if (dataMessage.getMentions().isPresent()) {
- this.mentions = dataMessage.getMentions()
+ mentions = dataMessage.getMentions()
.get()
.stream()
- .map(mention -> new JsonMention(mention, m))
+ .map(mention -> JsonMention.from(mention, m))
.collect(Collectors.toList());
} else {
- this.mentions = List.of();
+ mentions = List.of();
}
- remoteDelete = dataMessage.getRemoteDelete().isPresent() ? new JsonRemoteDelete(dataMessage.getRemoteDelete()
- .get()) : null;
+ final var remoteDelete = dataMessage.getRemoteDelete().isPresent()
+ ? JsonRemoteDelete.from(dataMessage.getRemoteDelete().get())
+ : null;
+ final List<JsonAttachment> attachments;
if (dataMessage.getAttachments().isPresent()) {
- this.attachments = dataMessage.getAttachments()
+ attachments = dataMessage.getAttachments()
.get()
.stream()
- .map(JsonAttachment::new)
+ .map(JsonAttachment::from)
.collect(Collectors.toList());
} else {
- this.attachments = List.of();
+ attachments = List.of();
}
- this.sticker = dataMessage.getSticker().isPresent() ? new JsonSticker(dataMessage.getSticker().get()) : null;
+ final var sticker = dataMessage.getSticker().isPresent()
+ ? JsonSticker.from(dataMessage.getSticker().get())
+ : null;
+ final List<JsonSharedContact> contacts;
if (dataMessage.getSharedContacts().isPresent()) {
- this.contacts = dataMessage.getSharedContacts()
+ contacts = dataMessage.getSharedContacts()
.get()
.stream()
- .map(JsonSharedContact::new)
+ .map(JsonSharedContact::from)
.collect(Collectors.toList());
} else {
- this.contacts = List.of();
+ contacts = List.of();
}
+ return new JsonDataMessage(timestamp,
+ message,
+ expiresInSeconds,
+ viewOnce,
+ reaction,
+ quote,
+ mentions,
+ attachments,
+ sticker,
+ remoteDelete,
+ contacts,
+ groupInfo);
}
- public JsonDataMessage(Signal.MessageReceived messageReceived) {
- timestamp = messageReceived.getTimestamp();
- message = messageReceived.getMessage();
- groupInfo = messageReceived.getGroupId().length > 0 ? new JsonGroupInfo(messageReceived.getGroupId()) : null;
- expiresInSeconds = null;
- viewOnce = null;
- remoteDelete = null;
- reaction = null; // TODO Replace these 5 with the proper commands
- quote = null;
- mentions = null;
- sticker = null;
- contacts = null;
- attachments = messageReceived.getAttachments().stream().map(JsonAttachment::new).collect(Collectors.toList());
+ static JsonDataMessage from(Signal.MessageReceived messageReceived) {
+ return new JsonDataMessage(messageReceived.getTimestamp(),
+ messageReceived.getMessage(),
+ // TODO Replace these with the proper commands
+ null,
+ null,
+ null,
+ null,
+ null,
+ messageReceived.getAttachments().stream().map(JsonAttachment::from).collect(Collectors.toList()),
+ null,
+ null,
+ null,
+ messageReceived.getGroupId().length > 0 ? JsonGroupInfo.from(messageReceived.getGroupId()) : null);
}
- public JsonDataMessage(Signal.SyncMessageReceived messageReceived) {
- timestamp = messageReceived.getTimestamp();
- message = messageReceived.getMessage();
- groupInfo = messageReceived.getGroupId().length > 0 ? new JsonGroupInfo(messageReceived.getGroupId()) : null;
- expiresInSeconds = null;
- viewOnce = null;
- remoteDelete = null;
- reaction = null; // TODO Replace these 5 with the proper commands
- quote = null;
- mentions = null;
- sticker = null;
- contacts = null;
- attachments = messageReceived.getAttachments().stream().map(JsonAttachment::new).collect(Collectors.toList());
+ static JsonDataMessage from(Signal.SyncMessageReceived messageReceived) {
+ return new JsonDataMessage(messageReceived.getTimestamp(),
+ messageReceived.getMessage(),
+ // TODO Replace these with the proper commands
+ null,
+ null,
+ null,
+ null,
+ null,
+ messageReceived.getAttachments().stream().map(JsonAttachment::from).collect(Collectors.toList()),
+ null,
+ null,
+ null,
+ messageReceived.getGroupId().length > 0 ? JsonGroupInfo.from(messageReceived.getGroupId()) : null);
}
}
package org.asamk.signal.json;
-import com.fasterxml.jackson.annotation.JsonProperty;
+public record JsonError(String message, String type) {
-public class JsonError {
-
- @JsonProperty
- final String message;
-
- @JsonProperty
- final String type;
-
- public JsonError(Throwable exception) {
- this.message = exception.getMessage();
- this.type = exception.getClass().getSimpleName();
+ public static JsonError from(Throwable exception) {
+ return new JsonError(exception.getMessage(), exception.getClass().getSimpleName());
}
}
package org.asamk.signal.json;
import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
import org.asamk.signal.manager.groups.GroupUtils;
import org.asamk.signal.util.Util;
import java.util.List;
import java.util.stream.Collectors;
-class JsonGroupInfo {
-
- @JsonProperty
- final String groupId;
-
- @JsonProperty
- final String type;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final String name;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final List<String> members;
-
- JsonGroupInfo(SignalServiceGroup groupInfo) {
- this.groupId = Base64.getEncoder().encodeToString(groupInfo.getGroupId());
- this.type = groupInfo.getType().toString();
- this.name = groupInfo.getName().orNull();
- if (groupInfo.getMembers().isPresent()) {
- this.members = groupInfo.getMembers()
- .get()
- .stream()
- .map(Util::getLegacyIdentifier)
- .collect(Collectors.toList());
- } else {
- this.members = null;
- }
+record JsonGroupInfo(
+ String groupId,
+ String type,
+ @JsonInclude(JsonInclude.Include.NON_NULL) String name,
+ @JsonInclude(JsonInclude.Include.NON_NULL) List<String> members
+) {
+
+ static JsonGroupInfo from(SignalServiceGroup groupInfo) {
+ return new JsonGroupInfo(Base64.getEncoder().encodeToString(groupInfo.getGroupId()),
+ groupInfo.getType().toString(),
+ groupInfo.getName().orNull(),
+ groupInfo.getMembers().isPresent() ? groupInfo.getMembers()
+ .get()
+ .stream()
+ .map(Util::getLegacyIdentifier)
+ .collect(Collectors.toList()) : null);
}
- JsonGroupInfo(SignalServiceGroupV2 groupInfo) {
- this.groupId = GroupUtils.getGroupIdV2(groupInfo.getMasterKey()).toBase64();
- this.type = groupInfo.hasSignedGroupChange() ? "UPDATE" : "DELIVER";
- this.members = null;
- this.name = null;
+ static JsonGroupInfo from(SignalServiceGroupV2 groupInfo) {
+ return new JsonGroupInfo(GroupUtils.getGroupIdV2(groupInfo.getMasterKey()).toBase64(),
+ groupInfo.hasSignedGroupChange() ? "UPDATE" : "DELIVER",
+ null,
+ null);
}
- JsonGroupInfo(byte[] groupId) {
- this.groupId = Base64.getEncoder().encodeToString(groupId);
- this.type = "DELIVER";
- this.members = null;
- this.name = null;
+ static JsonGroupInfo from(byte[] groupId) {
+ return new JsonGroupInfo(Base64.getEncoder().encodeToString(groupId), "DELIVER", null, null);
}
}
package org.asamk.signal.json;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
import org.asamk.signal.manager.Manager;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import static org.asamk.signal.util.Util.getLegacyIdentifier;
-public class JsonMention {
-
- @JsonProperty
- @Deprecated
- final String name;
-
- @JsonProperty
- final String number;
-
- @JsonProperty
- final String uuid;
-
- @JsonProperty
- final int start;
-
- @JsonProperty
- final int length;
+public record JsonMention(@Deprecated String name, String number, String uuid, int start, int length) {
- JsonMention(SignalServiceDataMessage.Mention mention, Manager m) {
+ static JsonMention from(SignalServiceDataMessage.Mention mention, Manager m) {
final var address = m.resolveSignalServiceAddress(new SignalServiceAddress(mention.getUuid()));
- this.name = getLegacyIdentifier(address);
- this.number = address.getNumber().orNull();
- this.uuid = address.getUuid().toString();
- this.start = mention.getStart();
- this.length = mention.getLength();
+ return new JsonMention(getLegacyIdentifier(address),
+ address.getNumber().orNull(),
+ address.getUuid().toString(),
+ mention.getStart(),
+ mention.getLength());
}
}
package org.asamk.signal.json;
import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
import org.asamk.Signal;
import org.asamk.signal.manager.Manager;
import static org.asamk.signal.util.Util.getLegacyIdentifier;
-public class JsonMessageEnvelope {
-
- @JsonProperty
- @Deprecated
- final String source;
-
- @JsonProperty
- final String sourceNumber;
-
- @JsonProperty
- final String sourceUuid;
-
- @JsonProperty
- final String sourceName;
-
- @JsonProperty
- final Integer sourceDevice;
-
- @JsonProperty
- final long timestamp;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final JsonDataMessage dataMessage;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final JsonSyncMessage syncMessage;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final JsonCallMessage callMessage;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final JsonReceiptMessage receiptMessage;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final JsonTypingMessage typingMessage;
-
- public JsonMessageEnvelope(
+public record JsonMessageEnvelope(
+ @Deprecated String source,
+ String sourceNumber,
+ String sourceUuid,
+ String sourceName,
+ Integer sourceDevice,
+ long timestamp,
+ @JsonInclude(JsonInclude.Include.NON_NULL) JsonDataMessage dataMessage,
+ @JsonInclude(JsonInclude.Include.NON_NULL) JsonSyncMessage syncMessage,
+ @JsonInclude(JsonInclude.Include.NON_NULL) JsonCallMessage callMessage,
+ @JsonInclude(JsonInclude.Include.NON_NULL) JsonReceiptMessage receiptMessage,
+ @JsonInclude(JsonInclude.Include.NON_NULL) JsonTypingMessage typingMessage
+) {
+
+ public static JsonMessageEnvelope from(
SignalServiceEnvelope envelope, SignalServiceContent content, Throwable exception, Manager m
) {
+ final String source;
+ final String sourceNumber;
+ final String sourceUuid;
+ final Integer sourceDevice;
if (!envelope.isUnidentifiedSender() && envelope.hasSourceUuid()) {
- var source = m.resolveSignalServiceAddress(envelope.getSourceAddress());
- this.source = getLegacyIdentifier(source);
- this.sourceNumber = source.getNumber().orNull();
- this.sourceUuid = source.getUuid().toString();
- this.sourceDevice = envelope.getSourceDevice();
+ final var sourceAddress = m.resolveSignalServiceAddress(envelope.getSourceAddress());
+ source = getLegacyIdentifier(sourceAddress);
+ sourceNumber = sourceAddress.getNumber().orNull();
+ sourceUuid = sourceAddress.getUuid().toString();
+ sourceDevice = envelope.getSourceDevice();
} else if (envelope.isUnidentifiedSender() && content != null) {
- final var source = m.resolveSignalServiceAddress(content.getSender());
- this.source = getLegacyIdentifier(source);
- this.sourceNumber = source.getNumber().orNull();
- this.sourceUuid = source.getUuid().toString();
- this.sourceDevice = content.getSenderDevice();
- } else if (exception instanceof UntrustedIdentityException) {
- var e = (UntrustedIdentityException) exception;
- final var source = m.resolveSignalServiceAddress(e.getSender());
- this.source = getLegacyIdentifier(source);
- this.sourceNumber = source.getNumber().orNull();
- this.sourceUuid = source.getUuid().toString();
- this.sourceDevice = e.getSenderDevice();
+ final var sender = m.resolveSignalServiceAddress(content.getSender());
+ source = getLegacyIdentifier(sender);
+ sourceNumber = sender.getNumber().orNull();
+ sourceUuid = sender.getUuid().toString();
+ sourceDevice = content.getSenderDevice();
+ } else if (exception instanceof UntrustedIdentityException e) {
+ final var sender = m.resolveSignalServiceAddress(e.getSender());
+ source = getLegacyIdentifier(sender);
+ sourceNumber = sender.getNumber().orNull();
+ sourceUuid = sender.getUuid().toString();
+ sourceDevice = e.getSenderDevice();
} else {
- this.source = null;
- this.sourceNumber = null;
- this.sourceUuid = null;
- this.sourceDevice = null;
+ source = null;
+ sourceNumber = null;
+ sourceUuid = null;
+ sourceDevice = null;
}
String name;
try {
- name = m.getContactOrProfileName(RecipientIdentifier.Single.fromString(this.source, m.getSelfNumber()));
+ name = m.getContactOrProfileName(RecipientIdentifier.Single.fromString(source, m.getSelfNumber()));
} catch (InvalidNumberException | NullPointerException e) {
name = null;
}
- this.sourceName = name;
- this.timestamp = envelope.getTimestamp();
+ final var sourceName = name;
+ final var timestamp = envelope.getTimestamp();
+ final JsonReceiptMessage receiptMessage;
if (envelope.isReceipt()) {
- this.receiptMessage = JsonReceiptMessage.deliveryReceipt(timestamp, List.of(timestamp));
+ receiptMessage = JsonReceiptMessage.deliveryReceipt(timestamp, List.of(timestamp));
} else if (content != null && content.getReceiptMessage().isPresent()) {
- this.receiptMessage = new JsonReceiptMessage(content.getReceiptMessage().get());
+ receiptMessage = JsonReceiptMessage.from(content.getReceiptMessage().get());
} else {
- this.receiptMessage = null;
+ receiptMessage = null;
}
- this.typingMessage = content != null && content.getTypingMessage().isPresent()
- ? new JsonTypingMessage(content.getTypingMessage().get())
- : null;
+ final var typingMessage = content != null && content.getTypingMessage().isPresent() ? JsonTypingMessage.from(
+ content.getTypingMessage().get()) : null;
- this.dataMessage = content != null && content.getDataMessage().isPresent()
- ? new JsonDataMessage(content.getDataMessage().get(), m)
+ final var dataMessage = content != null && content.getDataMessage().isPresent()
+ ? JsonDataMessage.from(content.getDataMessage().get(), m)
: null;
- this.syncMessage = content != null && content.getSyncMessage().isPresent()
- ? new JsonSyncMessage(content.getSyncMessage().get(), m)
+ final var syncMessage = content != null && content.getSyncMessage().isPresent()
+ ? JsonSyncMessage.from(content.getSyncMessage().get(), m)
: null;
- this.callMessage = content != null && content.getCallMessage().isPresent()
- ? new JsonCallMessage(content.getCallMessage().get())
+ final var callMessage = content != null && content.getCallMessage().isPresent()
+ ? JsonCallMessage.from(content.getCallMessage().get())
: null;
+
+ return new JsonMessageEnvelope(source,
+ sourceNumber,
+ sourceUuid,
+ sourceName,
+ sourceDevice,
+ timestamp,
+ dataMessage,
+ syncMessage,
+ callMessage,
+ receiptMessage,
+ typingMessage);
}
- public JsonMessageEnvelope(Signal.MessageReceived messageReceived) {
- source = messageReceived.getSender();
- sourceNumber = null;
- sourceUuid = null;
- sourceName = null;
- sourceDevice = null;
- timestamp = messageReceived.getTimestamp();
- receiptMessage = null;
- dataMessage = new JsonDataMessage(messageReceived);
- syncMessage = null;
- callMessage = null;
- typingMessage = null;
+ public static JsonMessageEnvelope from(Signal.MessageReceived messageReceived) {
+ return new JsonMessageEnvelope(messageReceived.getSource(),
+ null,
+ null,
+ null,
+ null,
+ messageReceived.getTimestamp(),
+ JsonDataMessage.from(messageReceived),
+ null,
+ null,
+ null,
+ null);
}
- public JsonMessageEnvelope(Signal.ReceiptReceived receiptReceived) {
- source = receiptReceived.getSender();
- sourceNumber = null;
- sourceUuid = null;
- sourceName = null;
- sourceDevice = null;
- timestamp = receiptReceived.getTimestamp();
- receiptMessage = JsonReceiptMessage.deliveryReceipt(timestamp, List.of(timestamp));
- dataMessage = null;
- syncMessage = null;
- callMessage = null;
- typingMessage = null;
+ public static JsonMessageEnvelope from(Signal.ReceiptReceived receiptReceived) {
+ return new JsonMessageEnvelope(receiptReceived.getSender(),
+ null,
+ null,
+ null,
+ null,
+ receiptReceived.getTimestamp(),
+ null,
+ null,
+ null,
+ JsonReceiptMessage.deliveryReceipt(receiptReceived.getTimestamp(),
+ List.of(receiptReceived.getTimestamp())),
+ null);
}
- public JsonMessageEnvelope(Signal.SyncMessageReceived messageReceived) {
- source = messageReceived.getSource();
- sourceNumber = null;
- sourceUuid = null;
- sourceName = null;
- sourceDevice = null;
- timestamp = messageReceived.getTimestamp();
- receiptMessage = null;
- dataMessage = null;
- syncMessage = new JsonSyncMessage(messageReceived);
- callMessage = null;
- typingMessage = null;
+ public static JsonMessageEnvelope from(Signal.SyncMessageReceived messageReceived) {
+ return new JsonMessageEnvelope(messageReceived.getSource(),
+ null,
+ null,
+ null,
+ null,
+ messageReceived.getTimestamp(),
+ null,
+ JsonSyncMessage.from(messageReceived),
+ null,
+ null,
+ null);
}
}
package org.asamk.signal.json;
import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
import org.asamk.signal.manager.Manager;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import static org.asamk.signal.util.Util.getLegacyIdentifier;
-public class JsonQuote {
-
- @JsonProperty
- final long id;
-
- @JsonProperty
- @Deprecated
- final String author;
-
- @JsonProperty
- final String authorNumber;
-
- @JsonProperty
- final String authorUuid;
-
- @JsonProperty
- final String text;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final List<JsonMention> mentions;
-
- @JsonProperty
- final List<JsonQuotedAttachment> attachments;
-
- JsonQuote(SignalServiceDataMessage.Quote quote, Manager m) {
- this.id = quote.getId();
+public record JsonQuote(
+ long id,
+ @Deprecated String author,
+ String authorNumber,
+ String authorUuid,
+ String text,
+ @JsonInclude(JsonInclude.Include.NON_NULL) List<JsonMention> mentions,
+ List<JsonQuotedAttachment> attachments
+) {
+
+ static JsonQuote from(SignalServiceDataMessage.Quote quote, Manager m) {
+ final var id = quote.getId();
final var address = m.resolveSignalServiceAddress(quote.getAuthor());
- this.author = getLegacyIdentifier(address);
- this.authorNumber = address.getNumber().orNull();
- this.authorUuid = address.getUuid().toString();
- this.text = quote.getText();
+ final var author = getLegacyIdentifier(address);
+ final var authorNumber = address.getNumber().orNull();
+ final var authorUuid = address.getUuid().toString();
+ final var text = quote.getText();
+ final List<JsonMention> mentions;
if (quote.getMentions() != null && quote.getMentions().size() > 0) {
- this.mentions = quote.getMentions()
+ mentions = quote.getMentions()
.stream()
- .map(quotedMention -> new JsonMention(quotedMention, m))
+ .map(quotedMention -> JsonMention.from(quotedMention, m))
.collect(Collectors.toList());
} else {
- this.mentions = null;
+ mentions = null;
}
+ final List<JsonQuotedAttachment> attachments;
if (quote.getAttachments().size() > 0) {
- this.attachments = quote.getAttachments()
- .stream()
- .map(JsonQuotedAttachment::new)
- .collect(Collectors.toList());
+ attachments = quote.getAttachments().stream().map(JsonQuotedAttachment::from).collect(Collectors.toList());
} else {
- this.attachments = new ArrayList<>();
+ attachments = new ArrayList<>();
}
+
+ return new JsonQuote(id, author, authorNumber, authorUuid, text, mentions, attachments);
}
}
package org.asamk.signal.json;
import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
-public class JsonQuotedAttachment {
+public record JsonQuotedAttachment(
+ String contentType, String filename, @JsonInclude(JsonInclude.Include.NON_NULL) JsonAttachment thumbnail
+) {
- @JsonProperty
- final String contentType;
-
- @JsonProperty
- final String filename;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final JsonAttachment thumbnail;
-
- JsonQuotedAttachment(SignalServiceDataMessage.Quote.QuotedAttachment quotedAttachment) {
- contentType = quotedAttachment.getContentType();
- filename = quotedAttachment.getFileName();
+ static JsonQuotedAttachment from(SignalServiceDataMessage.Quote.QuotedAttachment quotedAttachment) {
+ final var contentType = quotedAttachment.getContentType();
+ final var filename = quotedAttachment.getFileName();
+ final JsonAttachment thumbnail;
if (quotedAttachment.getThumbnail() != null) {
- thumbnail = new JsonAttachment(quotedAttachment.getThumbnail());
+ thumbnail = JsonAttachment.from(quotedAttachment.getThumbnail());
} else {
thumbnail = null;
}
+ return new JsonQuotedAttachment(contentType, filename, thumbnail);
}
}
package org.asamk.signal.json;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
import org.asamk.signal.manager.Manager;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Reaction;
import static org.asamk.signal.util.Util.getLegacyIdentifier;
-public class JsonReaction {
-
- @JsonProperty
- final String emoji;
-
- @JsonProperty
- @Deprecated
- final String targetAuthor;
-
- @JsonProperty
- final String targetAuthorNumber;
-
- @JsonProperty
- final String targetAuthorUuid;
-
- @JsonProperty
- final long targetSentTimestamp;
-
- @JsonProperty
- final boolean isRemove;
-
- JsonReaction(Reaction reaction, Manager m) {
- this.emoji = reaction.getEmoji();
+public record JsonReaction(
+ String emoji,
+ @Deprecated String targetAuthor,
+ String targetAuthorNumber,
+ String targetAuthorUuid,
+ long targetSentTimestamp,
+ boolean isRemove
+) {
+
+ static JsonReaction from(Reaction reaction, Manager m) {
+ final var emoji = reaction.getEmoji();
final var address = m.resolveSignalServiceAddress(reaction.getTargetAuthor());
- this.targetAuthor = getLegacyIdentifier(address);
- this.targetAuthorNumber = address.getNumber().orNull();
- this.targetAuthorUuid = address.getUuid().toString();
- this.targetSentTimestamp = reaction.getTargetSentTimestamp();
- this.isRemove = reaction.isRemove();
+ final var targetAuthor = getLegacyIdentifier(address);
+ final var targetAuthorNumber = address.getNumber().orNull();
+ final var targetAuthorUuid = address.getUuid().toString();
+ final var targetSentTimestamp = reaction.getTargetSentTimestamp();
+ final var isRemove = reaction.isRemove();
+ return new JsonReaction(emoji,
+ targetAuthor,
+ targetAuthorNumber,
+ targetAuthorUuid,
+ targetSentTimestamp,
+ isRemove);
}
}
package org.asamk.signal.json;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
import java.util.List;
-class JsonReceiptMessage {
-
- @JsonProperty
- final long when;
-
- @JsonProperty
- final boolean isDelivery;
-
- @JsonProperty
- final boolean isRead;
-
- @JsonProperty
- final boolean isViewed;
-
- @JsonProperty
- final List<Long> timestamps;
-
- JsonReceiptMessage(SignalServiceReceiptMessage receiptMessage) {
- this.when = receiptMessage.getWhen();
- this.isDelivery = receiptMessage.isDeliveryReceipt();
- this.isRead = receiptMessage.isReadReceipt();
- this.isViewed = receiptMessage.isViewedReceipt();
- this.timestamps = receiptMessage.getTimestamps();
- }
+record JsonReceiptMessage(long when, boolean isDelivery, boolean isRead, boolean isViewed, List<Long> timestamps) {
- private JsonReceiptMessage(
- final long when, final boolean isDelivery, final boolean isRead, final boolean isViewed, final List<Long> timestamps
- ) {
- this.when = when;
- this.isDelivery = isDelivery;
- this.isRead = isRead;
- this.isViewed = isViewed;
- this.timestamps = timestamps;
+ static JsonReceiptMessage from(SignalServiceReceiptMessage receiptMessage) {
+ final var when = receiptMessage.getWhen();
+ final var isDelivery = receiptMessage.isDeliveryReceipt();
+ final var isRead = receiptMessage.isReadReceipt();
+ final var isViewed = receiptMessage.isViewedReceipt();
+ final var timestamps = receiptMessage.getTimestamps();
+ return new JsonReceiptMessage(when, isDelivery, isRead, isViewed, timestamps);
}
static JsonReceiptMessage deliveryReceipt(final long when, final List<Long> timestamps) {
- return new JsonReceiptMessage(when, true, false, false, timestamps);
+ return new JsonReceiptMessage(when, true, false, false, false, timestamps);
}
}
package org.asamk.signal.json;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
-class JsonRemoteDelete {
-
- @JsonProperty
- final long timestamp;
+record JsonRemoteDelete(long timestamp) {
- JsonRemoteDelete(SignalServiceDataMessage.RemoteDelete remoteDelete) {
- this.timestamp = remoteDelete.getTargetSentTimestamp();
+ static JsonRemoteDelete from(SignalServiceDataMessage.RemoteDelete remoteDelete) {
+ return new JsonRemoteDelete(remoteDelete.getTargetSentTimestamp());
}
}
package org.asamk.signal.json;
import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
import java.util.List;
import java.util.stream.Collectors;
-public class JsonSharedContact {
-
- @JsonProperty
- final JsonContactName name;
-
- @JsonProperty
- final JsonContactAvatar avatar;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final List<JsonContactPhone> phone;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final List<JsonContactEmail> email;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final List<JsonContactAddress> address;
-
- @JsonProperty
- final String organization;
-
- public JsonSharedContact(SharedContact contact) {
- name = new JsonContactName(contact.getName());
- if (contact.getAvatar().isPresent()) {
- avatar = new JsonContactAvatar(contact.getAvatar().get());
- } else {
- avatar = null;
- }
-
- if (contact.getPhone().isPresent()) {
- phone = contact.getPhone().get().stream().map(JsonContactPhone::new).collect(Collectors.toList());
- } else {
- phone = null;
- }
-
- if (contact.getEmail().isPresent()) {
- email = contact.getEmail().get().stream().map(JsonContactEmail::new).collect(Collectors.toList());
- } else {
- email = null;
- }
-
- if (contact.getAddress().isPresent()) {
- address = contact.getAddress().get().stream().map(JsonContactAddress::new).collect(Collectors.toList());
- } else {
- address = null;
- }
-
- organization = contact.getOrganization().orNull();
+public record JsonSharedContact(
+ JsonContactName name,
+ JsonContactAvatar avatar,
+ @JsonInclude(JsonInclude.Include.NON_NULL) List<JsonContactPhone> phone,
+ @JsonInclude(JsonInclude.Include.NON_NULL) List<JsonContactEmail> email,
+ @JsonInclude(JsonInclude.Include.NON_NULL) List<JsonContactAddress> address,
+ String organization
+) {
+
+ static JsonSharedContact from(SharedContact contact) {
+ final var name = JsonContactName.from(contact.getName());
+ final var avatar = contact.getAvatar().isPresent() ? JsonContactAvatar.from(contact.getAvatar().get()) : null;
+
+ final var phone = contact.getPhone().isPresent() ? contact.getPhone()
+ .get()
+ .stream()
+ .map(JsonContactPhone::from)
+ .collect(Collectors.toList()) : null;
+
+ final var email = contact.getEmail().isPresent() ? contact.getEmail()
+ .get()
+ .stream()
+ .map(JsonContactEmail::from)
+ .collect(Collectors.toList()) : null;
+
+ final var address = contact.getAddress().isPresent() ? contact.getAddress()
+ .get()
+ .stream()
+ .map(JsonContactAddress::from)
+ .collect(Collectors.toList()) : null;
+
+ final var organization = contact.getOrganization().orNull();
+
+ return new JsonSharedContact(name, avatar, phone, email, address, organization);
}
}
package org.asamk.signal.json;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import java.util.Base64;
-public class JsonSticker {
-
- @JsonProperty
- final String packId;
-
- @JsonProperty
- final String packKey;
-
- @JsonProperty
- final int stickerId;
+public record JsonSticker(String packId, String packKey, int stickerId) {
- public JsonSticker(SignalServiceDataMessage.Sticker sticker) {
- this.packId = Base64.getEncoder().encodeToString(sticker.getPackId());
- this.packKey = Base64.getEncoder().encodeToString(sticker.getPackKey());
- this.stickerId = sticker.getStickerId();
+ static JsonSticker from(SignalServiceDataMessage.Sticker sticker) {
+ final var packId = Base64.getEncoder().encodeToString(sticker.getPackId());
+ final var packKey = Base64.getEncoder().encodeToString(sticker.getPackKey());
+ final var stickerId = sticker.getStickerId();
+ return new JsonSticker(packId, packKey, stickerId);
}
}
package org.asamk.signal.json;
-import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonUnwrapped;
import org.asamk.Signal;
import org.asamk.signal.manager.Manager;
import static org.asamk.signal.util.Util.getLegacyIdentifier;
-class JsonSyncDataMessage extends JsonDataMessage {
-
- @JsonProperty
- @Deprecated
- final String destination;
-
- @JsonProperty
- final String destinationNumber;
-
- @JsonProperty
- final String destinationUuid;
-
- JsonSyncDataMessage(SentTranscriptMessage transcriptMessage, Manager m) {
- super(transcriptMessage.getMessage(), m);
+record JsonSyncDataMessage(
+ @Deprecated String destination,
+ String destinationNumber,
+ String destinationUuid,
+ @JsonUnwrapped JsonDataMessage dataMessage
+) {
+ static JsonSyncDataMessage from(SentTranscriptMessage transcriptMessage, Manager m) {
if (transcriptMessage.getDestination().isPresent()) {
final var address = transcriptMessage.getDestination().get();
- this.destination = getLegacyIdentifier(address);
- this.destinationNumber = address.getNumber().orNull();
- this.destinationUuid = address.getUuid().toString();
+ return new JsonSyncDataMessage(getLegacyIdentifier(address),
+ address.getNumber().orNull(),
+ address.getUuid().toString(),
+ JsonDataMessage.from(transcriptMessage.getMessage(), m));
+
} else {
- this.destination = null;
- this.destinationNumber = null;
- this.destinationUuid = null;
+ return new JsonSyncDataMessage(null, null, null, JsonDataMessage.from(transcriptMessage.getMessage(), m));
}
}
- JsonSyncDataMessage(Signal.SyncMessageReceived messageReceived) {
- super(messageReceived);
- this.destination = messageReceived.getDestination();
- this.destinationNumber = null;
- this.destinationUuid = null;
+ static JsonSyncDataMessage from(Signal.SyncMessageReceived messageReceived) {
+ return new JsonSyncDataMessage(messageReceived.getDestination(),
+ null,
+ null,
+ JsonDataMessage.from(messageReceived));
}
}
package org.asamk.signal.json;
import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
import org.asamk.Signal;
import org.asamk.signal.manager.Manager;
REQUEST_SYNC
}
-class JsonSyncMessage {
+record JsonSyncMessage(
+ @JsonInclude(JsonInclude.Include.NON_NULL) JsonSyncDataMessage sentMessage,
+ @JsonInclude(JsonInclude.Include.NON_NULL) List<String> blockedNumbers,
+ @JsonInclude(JsonInclude.Include.NON_NULL) List<String> blockedGroupIds,
+ @JsonInclude(JsonInclude.Include.NON_NULL) List<JsonSyncReadMessage> readMessages,
+ @JsonInclude(JsonInclude.Include.NON_NULL) JsonSyncMessageType type
+) {
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final JsonSyncDataMessage sentMessage;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final List<String> blockedNumbers;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final List<String> blockedGroupIds;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final List<JsonSyncReadMessage> readMessages;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final JsonSyncMessageType type;
+ JsonSyncMessage(
+ final JsonSyncDataMessage sentMessage,
+ final List<String> blockedNumbers,
+ final List<String> blockedGroupIds,
+ final List<JsonSyncReadMessage> readMessages,
+ final JsonSyncMessageType type
+ ) {
+ this.sentMessage = sentMessage;
+ this.blockedNumbers = blockedNumbers;
+ this.blockedGroupIds = blockedGroupIds;
+ this.readMessages = readMessages;
+ this.type = type;
+ }
- JsonSyncMessage(SignalServiceSyncMessage syncMessage, Manager m) {
- this.sentMessage = syncMessage.getSent().isPresent()
- ? new JsonSyncDataMessage(syncMessage.getSent().get(), m)
- : null;
+ static JsonSyncMessage from(SignalServiceSyncMessage syncMessage, Manager m) {
+ final var sentMessage = syncMessage.getSent().isPresent() ? JsonSyncDataMessage.from(syncMessage.getSent()
+ .get(), m) : null;
+ final List<String> blockedNumbers;
+ final List<String> blockedGroupIds;
if (syncMessage.getBlockedList().isPresent()) {
final var base64 = Base64.getEncoder();
- this.blockedNumbers = syncMessage.getBlockedList()
+ blockedNumbers = syncMessage.getBlockedList()
.get()
.getAddresses()
.stream()
.map(Util::getLegacyIdentifier)
.collect(Collectors.toList());
- this.blockedGroupIds = syncMessage.getBlockedList()
+ blockedGroupIds = syncMessage.getBlockedList()
.get()
.getGroupIds()
.stream()
.map(base64::encodeToString)
.collect(Collectors.toList());
} else {
- this.blockedNumbers = null;
- this.blockedGroupIds = null;
- }
- if (syncMessage.getRead().isPresent()) {
- this.readMessages = syncMessage.getRead()
- .get()
- .stream()
- .map(JsonSyncReadMessage::new)
- .collect(Collectors.toList());
- } else {
- this.readMessages = null;
+ blockedNumbers = null;
+ blockedGroupIds = null;
}
+ final var readMessages = syncMessage.getRead().isPresent() ? syncMessage.getRead()
+ .get()
+ .stream()
+ .map(JsonSyncReadMessage::from)
+ .collect(Collectors.toList()) : null;
+
+ final JsonSyncMessageType type;
if (syncMessage.getContacts().isPresent()) {
- this.type = JsonSyncMessageType.CONTACTS_SYNC;
+ type = JsonSyncMessageType.CONTACTS_SYNC;
} else if (syncMessage.getGroups().isPresent()) {
- this.type = JsonSyncMessageType.GROUPS_SYNC;
+ type = JsonSyncMessageType.GROUPS_SYNC;
} else if (syncMessage.getRequest().isPresent()) {
- this.type = JsonSyncMessageType.REQUEST_SYNC;
+ type = JsonSyncMessageType.REQUEST_SYNC;
} else {
- this.type = null;
+ type = null;
}
+ return new JsonSyncMessage(sentMessage, blockedNumbers, blockedGroupIds, readMessages, type);
}
- JsonSyncMessage(Signal.SyncMessageReceived messageReceived) {
- this.sentMessage = new JsonSyncDataMessage(messageReceived);
- this.blockedNumbers = null;
- this.blockedGroupIds = null;
- this.readMessages = null;
- this.type = null;
+ static JsonSyncMessage from(Signal.SyncMessageReceived messageReceived) {
+ return new JsonSyncMessage(JsonSyncDataMessage.from(messageReceived), null, null, null, null);
}
}
package org.asamk.signal.json;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
import static org.asamk.signal.util.Util.getLegacyIdentifier;
-class JsonSyncReadMessage {
-
- @JsonProperty
- @Deprecated
- final String sender;
-
- @JsonProperty
- final String senderNumber;
-
- @JsonProperty
- final String senderUuid;
-
- @JsonProperty
- final long timestamp;
-
- public JsonSyncReadMessage(final ReadMessage readMessage) {
- final var sender = readMessage.getSender();
- this.sender = getLegacyIdentifier(sender);
- this.senderNumber = sender.getNumber().orNull();
- this.senderUuid = sender.getUuid().toString();
- this.timestamp = readMessage.getTimestamp();
+record JsonSyncReadMessage(
+ @Deprecated String sender, String senderNumber, String senderUuid, long timestamp
+) {
+
+ static JsonSyncReadMessage from(final ReadMessage readMessage) {
+ final var senderAddress = readMessage.getSender();
+ final var sender = getLegacyIdentifier(senderAddress);
+ final var senderNumber = senderAddress.getNumber().orNull();
+ final var senderUuid = senderAddress.getUuid().toString();
+ final var timestamp = readMessage.getTimestamp();
+ return new JsonSyncReadMessage(sender, senderNumber, senderUuid, timestamp);
}
}
package org.asamk.signal.json;
import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
import org.whispersystems.signalservice.api.messages.SignalServiceTypingMessage;
import java.util.Base64;
-class JsonTypingMessage {
+record JsonTypingMessage(
+ String action, long timestamp, @JsonInclude(JsonInclude.Include.NON_NULL) String groupId
+) {
- @JsonProperty
- final String action;
-
- @JsonProperty
- final long timestamp;
-
- @JsonProperty
- @JsonInclude(JsonInclude.Include.NON_NULL)
- final String groupId;
+ JsonTypingMessage(final String action, final long timestamp, final String groupId) {
+ this.action = action;
+ this.timestamp = timestamp;
+ this.groupId = groupId;
+ }
- JsonTypingMessage(SignalServiceTypingMessage typingMessage) {
- this.action = typingMessage.getAction().name();
- this.timestamp = typingMessage.getTimestamp();
+ static JsonTypingMessage from(SignalServiceTypingMessage typingMessage) {
+ final var action = typingMessage.getAction().name();
+ final var timestamp = typingMessage.getTimestamp();
final var encoder = Base64.getEncoder();
- this.groupId = typingMessage.getGroupId().transform(encoder::encodeToString).orNull();
+ final var groupId = typingMessage.getGroupId().transform(encoder::encodeToString).orNull();
+ return new JsonTypingMessage(action, timestamp, groupId);
}
}
import java.util.List;
-public class JsonRpcBulkMessage extends JsonRpcMessage {
+public final class JsonRpcBulkMessage extends JsonRpcMessage {
List<JsonNode> messages;
* Represents a JSON-RPC (bulk) request or (bulk) response.
* https://www.jsonrpc.org/specification
*/
-public abstract class JsonRpcMessage {
+public sealed abstract class JsonRpcMessage permits JsonRpcBulkMessage, JsonRpcRequest, JsonRpcResponse {
}
* Represents a JSON-RPC request.
* https://www.jsonrpc.org/specification#request_object
*/
-public class JsonRpcRequest extends JsonRpcMessage {
+public final class JsonRpcRequest extends JsonRpcMessage {
/**
* A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0".
* Represents a JSON-RPC response.
* https://www.jsonrpc.org/specification#response_object
*/
-public class JsonRpcResponse extends JsonRpcMessage {
+public final class JsonRpcResponse extends JsonRpcMessage {
/**
* A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0".
"CAPTCHA proof required for sending to \"%s\", available options \"%s\" with challenge token \"%s\", or wait \"%d\" seconds.\n"
+ (
failure.getOptions().contains(ProofRequiredException.Option.RECAPTCHA)
- ?
- "To get the captcha token, go to https://signalcaptchas.org/registration/generate.html\n"
- + "Check the developer tools (F12) console for a failed redirect to signalcaptcha://\n"
- + "Everything after signalcaptcha:// is the captcha token.\n"
- + "Use the following command to submit the captcha token:\n"
- + "signal-cli submitRateLimitChallenge --challenge CHALLENGE_TOKEN --captcha CAPTCHA_TOKEN"
+ ? """
+ To get the captcha token, go to https://signalcaptchas.org/challenge/generate.html
+ Check the developer tools (F12) console for a failed redirect to signalcaptcha://
+ Everything after signalcaptcha:// is the captcha token.
+ Use the following command to submit the captcha token:
+ signal-cli submitRateLimitChallenge --challenge CHALLENGE_TOKEN --captcha CAPTCHA_TOKEN"""
: ""
),
identifier,