mirror of
https://github.com/AsamK/signal-cli.git
synced 2026-09-23 06:39:04 +00:00
Merge branch 'AsamK:master' into android-sqlite-classifier-native
This commit is contained in:
commit
a2ff271c5d
20
CHANGELOG.md
20
CHANGELOG.md
@ -2,6 +2,26 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- `send --attachment-dimensions` and `--attachment-blurhash` to set the placeholder shown before an attachment is downloaded
|
||||
|
||||
## [0.14.8] - 2026-09-10
|
||||
|
||||
### Added
|
||||
|
||||
- Add terminateGroup command to terminate a group for everyone
|
||||
- Include isVoiceNote in receive JSON and JSON-RPC attachment payloads
|
||||
|
||||
### Improved
|
||||
|
||||
- Prevent more storage sync loops
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix graalvm issue with image dimension probing
|
||||
- Fix issue with sticker storage sync
|
||||
|
||||
## [0.14.7] - 2026-08-01
|
||||
|
||||
### Added
|
||||
|
||||
@ -5,12 +5,12 @@ plugins {
|
||||
application
|
||||
eclipse
|
||||
`check-lib-versions`
|
||||
id("org.graalvm.buildtools.native") version "1.1.9"
|
||||
id("org.graalvm.buildtools.native") version "1.1.12"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
group = "org.asamk"
|
||||
version = "0.14.8"
|
||||
version = "0.14.9-SNAPSHOT"
|
||||
}
|
||||
|
||||
java {
|
||||
|
||||
363
client/Cargo.lock
generated
363
client/Cargo.lock
generated
@ -54,19 +54,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
version = "0.1.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||
checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -83,21 +83,21 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.1"
|
||||
version = "2.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.1"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
|
||||
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.60"
|
||||
version = "1.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
|
||||
checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
@ -117,9 +117,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.1"
|
||||
version = "4.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
|
||||
checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@ -127,9 +127,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.0"
|
||||
version = "4.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||
checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@ -140,14 +140,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.1"
|
||||
version = "4.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
|
||||
checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -164,9 +164,9 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
version = "4.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
|
||||
checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"memchr",
|
||||
@ -190,13 +190,13 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -217,9 +217,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
@ -238,53 +238,53 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.32"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
|
||||
checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.32"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.32"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
|
||||
checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.32"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
|
||||
checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.32"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
||||
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.3"
|
||||
version = "3.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
|
||||
checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.32"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-macro",
|
||||
@ -307,9 +307,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.13"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
|
||||
checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
@ -326,9 +326,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.0"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@ -338,9 +338,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
|
||||
checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"itoa",
|
||||
@ -348,9 +348,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||
checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
@ -358,9 +358,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.3"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
|
||||
checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@ -377,9 +377,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.9.0"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
|
||||
checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
@ -434,9 +434,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
|
||||
checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"potential_utf",
|
||||
@ -448,9 +448,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale_core"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
|
||||
checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
@ -461,9 +461,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
|
||||
checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_normalizer_data",
|
||||
@ -475,16 +475,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer_data"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
|
||||
checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
|
||||
checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_collections",
|
||||
"icu_locale_core",
|
||||
"icu_properties_data",
|
||||
@ -495,15 +496,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties_data"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
|
||||
checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "2.2.0"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
|
||||
checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locale_core",
|
||||
@ -527,9 +528,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "idna_adapter"
|
||||
version = "1.2.1"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
|
||||
checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
|
||||
dependencies = [
|
||||
"icu_normalizer",
|
||||
"icu_properties",
|
||||
@ -537,9 +538,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
version = "2.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
@ -598,7 +599,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -632,7 +633,7 @@ dependencies = [
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
"thiserror 2.0.20",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tower",
|
||||
@ -656,7 +657,7 @@ dependencies = [
|
||||
"rustls-platform-verifier",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
"thiserror 2.0.20",
|
||||
"tokio",
|
||||
"tower",
|
||||
"url",
|
||||
@ -672,7 +673,7 @@ dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -684,14 +685,14 @@ dependencies = [
|
||||
"http",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
"thiserror 2.0.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.185"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
@ -701,27 +702,27 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
||||
checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.2.0"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
|
||||
checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
@ -754,22 +755,22 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.11"
|
||||
version = "1.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517"
|
||||
checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.11"
|
||||
version = "1.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6"
|
||||
checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -780,9 +781,9 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
|
||||
checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
|
||||
dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
@ -798,18 +799,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@ -830,9 +831,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.2"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
|
||||
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
@ -849,9 +850,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.38"
|
||||
version = "0.23.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21"
|
||||
checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
@ -864,9 +865,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.8.3"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
|
||||
checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pki-types",
|
||||
@ -876,9 +877,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.14.0"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
|
||||
checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
|
||||
dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
@ -912,9 +913,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.13"
|
||||
version = "0.103.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||
checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
@ -964,9 +965,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
@ -974,29 +975,29 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.149"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@ -1007,13 +1008,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "signal-cli-client"
|
||||
version = "0.0.1"
|
||||
version = "0.14.8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -1022,7 +1023,7 @@ dependencies = [
|
||||
"jsonrpsee",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
"thiserror 2.0.20",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
@ -1035,15 +1036,15 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.3"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
||||
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
@ -1069,9 +1070,20 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
version = "2.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1092,7 +1104,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1116,11 +1128,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
version = "2.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.18",
|
||||
"thiserror-impl 2.0.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1131,25 +1143,25 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.18"
|
||||
version = "2.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.3"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
|
||||
checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"zerovec",
|
||||
@ -1157,9 +1169,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.52.0"
|
||||
version = "1.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776"
|
||||
checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
@ -1172,20 +1184,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.7.0"
|
||||
version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
|
||||
checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.4"
|
||||
version = "0.26.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
|
||||
checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"tokio",
|
||||
@ -1193,9 +1205,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.18"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
|
||||
checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
@ -1204,13 +1216,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.18"
|
||||
version = "0.7.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
|
||||
checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"libc",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
@ -1226,9 +1239,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.25.11+spec-1.1.0"
|
||||
version = "0.25.13+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b"
|
||||
checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
@ -1238,9 +1251,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
version = "1.1.3+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
|
||||
checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
@ -1290,7 +1303,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1375,14 +1388,14 @@ version = "0.26.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e"
|
||||
dependencies = [
|
||||
"webpki-root-certs 1.0.7",
|
||||
"webpki-root-certs 1.0.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-root-certs"
|
||||
version = "1.0.7"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c"
|
||||
checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
@ -1561,24 +1574,24 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.1"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5"
|
||||
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.3"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
||||
checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.2"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
|
||||
checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
@ -1593,15 +1606,15 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.119",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
|
||||
checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
@ -1614,21 +1627,21 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.119",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.2"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.4"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
|
||||
checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke",
|
||||
@ -1637,9 +1650,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.6"
|
||||
version = "0.11.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
|
||||
checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
@ -1648,17 +1661,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.11.3"
|
||||
version = "0.11.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
|
||||
checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 3.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "signal-cli-client"
|
||||
version = "0.0.1"
|
||||
version = "0.14.8"
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -45,6 +45,9 @@
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="0.14.8" date="2026-09-10">
|
||||
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.14.8</url>
|
||||
</release>
|
||||
<release version="0.14.7" date="2026-08-01">
|
||||
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.14.7</url>
|
||||
</release>
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
[versions]
|
||||
slf4j = "2.0.18"
|
||||
coroutines = "1.10.2"
|
||||
junit = "6.1.2"
|
||||
micronaut-json-schema = "2.1.0"
|
||||
micronaut-core = "5.1.10"
|
||||
signal-service = "2.15.3_unofficial_152"
|
||||
slf4j = "2.0.19"
|
||||
coroutines = "1.11.0"
|
||||
junit = "6.1.3"
|
||||
micronaut-json-schema = "2.2.0"
|
||||
micronaut-core = "5.2.0"
|
||||
signal-service = "2.15.3_unofficial_153"
|
||||
|
||||
[libraries]
|
||||
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.85"
|
||||
jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.22.1"
|
||||
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.85.2"
|
||||
jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.22.2"
|
||||
argparse4j = "net.sourceforge.argparse4j:argparse4j:0.9.0"
|
||||
dbusjava = "com.github.hypfvieh:dbus-java-transport-native-unixsocket:5.0.0"
|
||||
zxing = "com.google.zxing:core:3.5.4"
|
||||
@ -18,11 +18,11 @@ micronaut-json-schema-generator = { module = "io.micronaut.jsonschema:micronaut-
|
||||
micronaut-inject-java = { module = "io.micronaut:micronaut-inject-java", version.ref = "micronaut-core" }
|
||||
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
||||
slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" }
|
||||
logback = "ch.qos.logback:logback-classic:1.6.1"
|
||||
logback = "ch.qos.logback:logback-classic:1.6.3"
|
||||
|
||||
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
|
||||
signalnetwork = { module = "com.github.turasa:signal-network", version.ref = "signal-service" }
|
||||
sqlite = "org.xerial:sqlite-jdbc:3.53.2.1"
|
||||
sqlite = "org.xerial:sqlite-jdbc:3.53.4.0"
|
||||
hikari = "com.zaxxer:HikariCP:7.1.0"
|
||||
junit-jupiter-bom = { module = "org.junit:junit-bom", version.ref = "junit" }
|
||||
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
|
||||
networkTimeout=10000
|
||||
retries=0
|
||||
retryBackOffMs=500
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
package org.asamk.signal.manager.api;
|
||||
|
||||
import org.signal.libsignal.net.BadRequestError;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class BadRequestException extends IOException {
|
||||
|
||||
private final BadRequestError error;
|
||||
|
||||
public BadRequestException(final BadRequestError error) {
|
||||
super(error.toString());
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public BadRequestError getError() {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,8 @@ import java.util.Optional;
|
||||
public record Message(
|
||||
String messageText,
|
||||
List<String> attachments,
|
||||
List<AttachmentDimensions> attachmentDimensions,
|
||||
List<String> attachmentBlurHashes,
|
||||
boolean viewOnce,
|
||||
boolean voiceNote,
|
||||
List<Mention> mentions,
|
||||
@ -17,6 +19,8 @@ public record Message(
|
||||
boolean urgent
|
||||
) {
|
||||
|
||||
public record AttachmentDimensions(int width, int height) {}
|
||||
|
||||
public record Mention(RecipientIdentifier.Single recipient, int start, int length) {}
|
||||
|
||||
public record Quote(
|
||||
|
||||
@ -267,19 +267,19 @@ public record MessageEnvelope(
|
||||
quote.getMentions() == null
|
||||
? List.of()
|
||||
: quote.getMentions()
|
||||
.stream()
|
||||
.map(m -> Mention.from(m, recipientResolver, addressResolver))
|
||||
.toList(),
|
||||
.stream()
|
||||
.map(m -> Mention.from(m, recipientResolver, addressResolver))
|
||||
.toList(),
|
||||
quote.getAttachments() == null
|
||||
? List.of()
|
||||
: quote.getAttachments().stream().map(a -> Attachment.from(a, fileProvider)).toList(),
|
||||
quote.getBodyRanges() == null
|
||||
? List.of()
|
||||
: quote.getBodyRanges()
|
||||
.stream()
|
||||
.filter(r -> r.style != null)
|
||||
.map(TextStyle::from)
|
||||
.toList());
|
||||
.stream()
|
||||
.filter(r -> r.style != null)
|
||||
.map(TextStyle::from)
|
||||
.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@ -598,7 +598,7 @@ public record MessageEnvelope(
|
||||
Boolean.TRUE.equals(pinnedMessage.getForever())
|
||||
? -1
|
||||
: pinnedMessage.getPinDurationInSeconds() == null
|
||||
? 0
|
||||
? 0
|
||||
: pinnedMessage.getPinDurationInSeconds());
|
||||
}
|
||||
}
|
||||
@ -1034,14 +1034,14 @@ public record MessageEnvelope(
|
||||
final var source = !envelope.isUnidentifiedSender() && serviceId != null
|
||||
? recipientResolver.resolveRecipient(serviceId)
|
||||
: envelope.isUnidentifiedSender() && content != null
|
||||
? recipientResolver.resolveRecipient(content.getSender())
|
||||
? recipientResolver.resolveRecipient(content.getSender())
|
||||
: exception instanceof ProtocolException e
|
||||
? recipientResolver.resolveRecipient(e.getSender())
|
||||
? recipientResolver.resolveRecipient(e.getSender())
|
||||
: null;
|
||||
final var sourceDevice = envelope.hasSourceDevice()
|
||||
? envelope.getSourceDevice()
|
||||
: content != null
|
||||
? content.getSenderDevice()
|
||||
? content.getSenderDevice()
|
||||
: exception instanceof ProtocolException e ? e.getSenderDevice() : 0;
|
||||
|
||||
Optional<Receipt> receipt;
|
||||
|
||||
@ -30,8 +30,8 @@ class LiveConfig {
|
||||
private static final byte[] UNIDENTIFIED_SENDER_TRUST_ROOT2 = Base64.getDecoder()
|
||||
.decode("BUkY0I+9+oPgDCn4+Ac6Iu813yvqkDr/ga8DzLxFxuk6");
|
||||
private static final String CDSI_MRENCLAVE = "0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57";
|
||||
private static final String SVR2_MRENCLAVE = "ced8217b26228e4b210c985786999d095c4958a94faf37b14acaf25c4cbb02a4";
|
||||
private static final String SVR2_MRENCLAVE_LEGACY = "1240acbd4aa26974184844c8a46b1022d3957ac8a76c1fd8f5b1a15141ee0708";
|
||||
private static final String SVR2_MRENCLAVE = "fdbbacdc0c043d0d53fe1440f62728de0386f45ab0a275bd8f99e03a02af355e";
|
||||
private static final String SVR2_MRENCLAVE_LEGACY = "ced8217b26228e4b210c985786999d095c4958a94faf37b14acaf25c4cbb02a4";
|
||||
|
||||
private static final String URL = "https://chat.signal.org";
|
||||
private static final String CDN_URL = "https://cdn.signal.org";
|
||||
@ -49,10 +49,10 @@ class LiveConfig {
|
||||
private static final byte[] zkGroupServerPublicParams = Base64.getDecoder()
|
||||
.decode("AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P+NameAZYOD12qRkxosQQP5uux6B2nRyZ7sAV54DgFyLiRcq1FvwKw2EPQdk4HDoePrO/RNUbyNddnM/mMgj4FW65xCoT1LmjrIjsv/Ggdlx46ueczhMgtBunx1/w8k8V+l8LVZ8gAT6wkU5J+DPQalQguMg12Jzug3q4TbdHiGCmD9EunCwOmsLuLJkz6EcSYXtrlDEnAM+hicw7iergYLLlMXpfTdGxJCWJmP4zqUFeTTmsmhsjGBt7NiEB/9pFFEB3pSbf4iiUukw63Eo8Aqnf4iwob6X1QviCWuc8t0LUlT9vALgh/f2DPVOOmR0RW6bgRvc7DSF20V/omg+YBw==");
|
||||
private static final byte[] genericServerPublicParams = Base64.getDecoder()
|
||||
.decode("AByD873dTilmOSG0TjKrvpeaKEsUmIO8Vx9BeMmftwUs9v7ikPwM8P3OHyT0+X3EUMZrSe9VUp26Wai51Q9I8mdk0hX/yo7CeFGJyzoOqn8e/i4Ygbn5HoAyXJx5eXfIbqpc0bIxzju4H/HOQeOpt6h742qii5u/cbwOhFZCsMIbElZTaeU+BWMBQiZHIGHT5IE0qCordQKZ5iPZom0HeFa8Yq0ShuEyAl0WINBiY6xE3H/9WnvzXBbMuuk//eRxXgzO8ieCeK8FwQNxbfXqZm6Ro1cMhCOF3u7xoX83QhpN");
|
||||
.decode("AeCO67P9mIv1yUHkdeZ9JF789GDbox61GvTqq3S4kYc1ADUWxWHQygU390tv1oRWt9WjkdZlU7mKkifF59ftjE+2ZlMmxns6I+ySiLpR8FEmfu+TGpVp3zYTjNV93obJJTyBCCsSHVETCyQRbKdCyb5TMa6LGrvcZaX0Q/VAavhuNA/m4kSiRMgSnYrUjGhVekdDnF+7xioo4wvFnxjIDh7uJQrYOWD6MloNGX7St5gbysTuQQ7i/HI38b9V8x8mKazuDSXxB//BKGZx/XHkK8cHX+QK1MPxYUVM1/CBI5oW");
|
||||
|
||||
private static final byte[] backupServerPublicParams = Base64.getDecoder()
|
||||
.decode("AJwNSU55fsFCbgaxGRD11wO1juAs8Yr5GF8FPlGzzvdJJIKH5/4CC7ZJSOe3yL2vturVaRU2Cx0n751Vt8wkj1bozK3CBV1UokxV09GWf+hdVImLGjXGYLLhnI1J2TWEe7iWHyb553EEnRb5oxr9n3lUbNAJuRmFM7hrr0Al0F0wrDD4S8lo2mGaXe0MJCOM166F8oYRQqpFeEHfiLnxA1O8ZLh7vMdv4g9jI5phpRBTsJ5IjiJrWeP0zdIGHEssUeprDZ9OUJ14m0v61eYJMKsf59Bn+mAT2a7YfB+Don9O");
|
||||
.decode("AZwNSU55fsFCbgaxGRD11wO1juAs8Yr5GF8FPlGzzvdJJIKH5/4CC7ZJSOe3yL2vturVaRU2Cx0n751Vt8wkj1Y4pyiScu0/S10n647ipo+iq97JZQv+UOlwH8ThyNlGT5DfxXCwTqivxHuXvZpuezPgHk5Gxl5aC6xuNxOnwmFlmu4CeSgdhW8+Pp0vAJOQ1MsU2D0+/kzI+tU94nB3tybY/Ao1AcGW2q41uKQbnOJUWwmQaFT6s+xTISgzsg7CPox6oORGX8rnyk/9lic3DbGsUHctIVpMAl/ogJBb4aYC");
|
||||
|
||||
private static final Environment LIBSIGNAL_NET_ENV = Environment.PRODUCTION;
|
||||
|
||||
|
||||
@ -30,8 +30,8 @@ class StagingConfig {
|
||||
private static final byte[] UNIDENTIFIED_SENDER_TRUST_ROOT2 = Base64.getDecoder()
|
||||
.decode("BYhU6tPjqP46KGZEzRs1OL4U39V5dlPJ/X09ha4rErkm");
|
||||
private static final String CDSI_MRENCLAVE = "0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57";
|
||||
private static final String SVR2_MRENCLAVE = "3c699f4975aaa3d172c0aad042f94f031b2b03e10b9c19a45116a01693d83302";
|
||||
private static final String SVR2_MRENCLAVE_LEGACY = "97f151f6ed078edbbfd72fa9cae694dcc08353f1f5e8d9ccd79a971b10ffc535";
|
||||
private static final String SVR2_MRENCLAVE = "0ff2d7d4efbe7cfc24ac069a16fba898928dbe6c40d500c8b6da55733c727d6e";
|
||||
private static final String SVR2_MRENCLAVE_LEGACY = "3c699f4975aaa3d172c0aad042f94f031b2b03e10b9c19a45116a01693d83302";
|
||||
|
||||
private static final String URL = "https://chat.staging.signal.org";
|
||||
private static final String CDN_URL = "https://cdn-staging.signal.org";
|
||||
@ -49,10 +49,10 @@ class StagingConfig {
|
||||
private static final byte[] zkGroupServerPublicParams = Base64.getDecoder()
|
||||
.decode("ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUjlENAErBme1YHmOSpU6tr6doJ66dPzVAWIanmO/5mgjNEDeK7DDqQdB1xd03HT2Qs2TxY3kCK8aAb/0iM0HQiXjxZ9HIgYhbtvGEnDKW5ILSUydqH/KBhW4Pb0jZWnqN/YgbWDKeJxnDbYcUob5ZY5Lt5ZCMKuaGUvCJRrCtuugSMaqjowCGRempsDdJEt+cMaalhZ6gczklJB/IbdwENW9KeVFPoFNFzhxWUIS5ML9riVYhAtE6JE5jX0xiHNVIIPthb458cfA8daR0nYfYAUKogQArm0iBezOO+mPk5vCNWI+wwkyFCqNDXz/qxl1gAntuCJtSfq9OC3NkdhQlgYQ==");
|
||||
private static final byte[] genericServerPublicParams = Base64.getDecoder()
|
||||
.decode("AHILOIrFPXX9laLbalbA9+L1CXpSbM/bTJXZGZiuyK1JaI6dK5FHHWL6tWxmHKYAZTSYmElmJ5z2A5YcirjO/yfoemE03FItyaf8W1fE4p14hzb5qnrmfXUSiAIVrhaXVwIwSzH6RL/+EO8jFIjJ/YfExfJ8aBl48CKHgu1+A6kWynhttonvWWx6h7924mIzW0Czj2ROuh4LwQyZypex4GuOPW8sgIT21KNZaafgg+KbV7XM1x1tF3XA17B4uGUaDbDw2O+nR1+U5p6qHPzmJ7ggFjSN6Utu+35dS1sS0P9N");
|
||||
.decode("AYhaw+NbxtNLo/RlGFEsHd904hW38LpPJ59jYJlNmT4wwtyOq4xzCs/MyXsfRbIsAYhQjDnpE0rhFtWkMcn/kV740SISwFfpPHunrtZ9h0YWz5QNNbI5I3DRGUjhKXgMU7J7s7qOr0fdms+g0e+L9FMSjJLobDkOngp/m0B5TsxTyqLscJ5VyU69Cj8txImTfHMCKrYphYfRHO78RwPoz2g2tGUAzEbKHm12OgDna2qutkE5TvYqwZczvgZyLVHdHXpvdyOlEdv4afVyWkI7u/S0XYDonIJoHlxqJoTSepZR");
|
||||
|
||||
private static final byte[] backupServerPublicParams = Base64.getDecoder()
|
||||
.decode("AHYrGb9IfugAAJiPKp+mdXUx+OL9zBolPYHYQz6GI1gWjpEu5me3zVNSvmYY4zWboZHif+HG1sDHSuvwFd0QszSwuSF4X4kRP3fJREdTZ5MCR0n55zUppTwfHRW2S4sdQ0JGz7YDQIJCufYSKh0pGNEHL6hv79Agrdnr4momr3oXdnkpVBIp3HWAQ6IbXQVSG18X36GaicI1vdT0UFmTwU2KTneluC2eyL9c5ff8PcmiS+YcLzh0OKYQXB5ZfQ06d6DiINvDQLy75zcfUOniLAj0lGJiHxGczin/RXisKSR8");
|
||||
.decode("AXYrGb9IfugAAJiPKp+mdXUx+OL9zBolPYHYQz6GI1gWjpEu5me3zVNSvmYY4zWboZHif+HG1sDHSuvwFd0QszS6h3nZ6vRdM/IYGK+cLynw3ucWo7idf3zjOG3b6JnGT/z7XYCr6HuOGkWH4DQWCH98hxVZMGOgmT8DCQoqebQb3oK1yrwEglRWmtI01KhRg9RGUKoQiwuej1JZEY8uaG4Uz9n1cVODJ1iuByhNqGHo+KfI4iWhjtx2AnhYqHViQ3CMd4ASGBJtic9UTFVk/4vegVIy0wfYsAmViftzK6t4");
|
||||
|
||||
private static final Network.Environment LIBSIGNAL_NET_ENV = Network.Environment.STAGING;
|
||||
|
||||
|
||||
@ -602,6 +602,7 @@ public class AccountHelper {
|
||||
account.setMultiDevice(devices.size() > 1);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void migrateRegistrationPin() throws IOException {
|
||||
var masterKey = account.getOrCreatePinMasterKey();
|
||||
|
||||
@ -609,6 +610,7 @@ public class AccountHelper {
|
||||
handleResponseException(dependencies.getAccountApi().enableRegistrationLock(masterKey));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setRegistrationPin(String pin) throws IOException {
|
||||
var masterKey = account.getOrCreatePinMasterKey();
|
||||
|
||||
@ -619,6 +621,7 @@ public class AccountHelper {
|
||||
updateAccountAttributes();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void removeRegistrationPin() throws IOException {
|
||||
// Remove KBS Pin
|
||||
context.getPinHelper().removeRegistrationLockPin();
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package org.asamk.signal.manager.helper;
|
||||
|
||||
import org.asamk.signal.manager.api.AttachmentInvalidException;
|
||||
import org.asamk.signal.manager.api.Message.AttachmentDimensions;
|
||||
import org.asamk.signal.manager.config.ServiceConfig;
|
||||
import org.asamk.signal.manager.internal.SignalDependencies;
|
||||
import org.asamk.signal.manager.storage.AttachmentStore;
|
||||
@ -52,9 +53,11 @@ public class AttachmentHelper {
|
||||
|
||||
public List<SignalServiceAttachment> uploadAttachments(
|
||||
final List<String> attachments,
|
||||
final List<AttachmentDimensions> dimensions,
|
||||
final List<String> blurHashes,
|
||||
boolean voiceNote
|
||||
) throws AttachmentInvalidException, IOException {
|
||||
final var attachmentStreams = createAttachmentStreams(attachments, voiceNote);
|
||||
final var attachmentStreams = createAttachmentStreams(attachments, dimensions, blurHashes, voiceNote);
|
||||
|
||||
try {
|
||||
// Upload attachments here, so we only upload once even for multiple recipients
|
||||
@ -71,26 +74,31 @@ public class AttachmentHelper {
|
||||
}
|
||||
|
||||
public List<SignalServiceAttachment> uploadAttachments(final List<String> attachments) throws AttachmentInvalidException, IOException {
|
||||
return uploadAttachments(attachments, false);
|
||||
return uploadAttachments(attachments, List.of(), List.of(), false);
|
||||
}
|
||||
|
||||
private List<SignalServiceAttachmentStream> createAttachmentStreams(
|
||||
List<String> attachments,
|
||||
List<AttachmentDimensions> dimensions,
|
||||
List<String> blurHashes,
|
||||
boolean voiceNote
|
||||
) throws AttachmentInvalidException, IOException {
|
||||
if (attachments == null) {
|
||||
return null;
|
||||
}
|
||||
final var signalServiceAttachments = new ArrayList<SignalServiceAttachmentStream>(attachments.size());
|
||||
for (var attachment : attachments) {
|
||||
final var attachmentStream = getAttachmentStream(attachment, voiceNote);
|
||||
signalServiceAttachments.add(attachmentStream);
|
||||
for (var i = 0; i < attachments.size(); i++) {
|
||||
final var size = i < dimensions.size() ? dimensions.get(i) : null;
|
||||
final var blurHash = i < blurHashes.size() && !blurHashes.get(i).isEmpty() ? blurHashes.get(i) : null;
|
||||
signalServiceAttachments.add(getAttachmentStream(attachments.get(i), size, blurHash, voiceNote));
|
||||
}
|
||||
return signalServiceAttachments;
|
||||
}
|
||||
|
||||
private SignalServiceAttachmentStream getAttachmentStream(
|
||||
final String attachment,
|
||||
final AttachmentDimensions dimensions,
|
||||
final String blurHash,
|
||||
final boolean voiceNote
|
||||
) throws AttachmentInvalidException {
|
||||
try {
|
||||
@ -116,6 +124,8 @@ public class AttachmentHelper {
|
||||
return AttachmentUtils.createAttachmentStream(streamDetails,
|
||||
streamDetailsAndFileName.second(),
|
||||
voiceNote,
|
||||
dimensions,
|
||||
blurHash,
|
||||
uploadSpec);
|
||||
} catch (IOException e) {
|
||||
throw new AttachmentInvalidException(attachment, e);
|
||||
@ -130,7 +140,7 @@ public class AttachmentHelper {
|
||||
}
|
||||
|
||||
public SignalServiceAttachmentPointer uploadAttachment(String attachment) throws IOException, AttachmentInvalidException {
|
||||
final var attachmentStream = getAttachmentStream(attachment, false);
|
||||
final var attachmentStream = getAttachmentStream(attachment, null, null, false);
|
||||
return uploadAttachment(attachmentStream);
|
||||
}
|
||||
|
||||
|
||||
@ -55,7 +55,7 @@ public class ContactHelper {
|
||||
final var version = contact == null
|
||||
? 1
|
||||
: contact.messageExpirationTimeVersion() == Integer.MAX_VALUE
|
||||
? Integer.MAX_VALUE
|
||||
? Integer.MAX_VALUE
|
||||
: contact.messageExpirationTimeVersion() + 1;
|
||||
account.getContactStore()
|
||||
.storeContact(recipientId,
|
||||
|
||||
@ -758,8 +758,14 @@ class GroupV2Helper {
|
||||
var authCredentialResponse = groupApiCredentials.get(todaySeconds);
|
||||
final var aci = getSelfAci();
|
||||
final var pni = getSelfPni();
|
||||
final var authCredentialSalt = context.getAccount().getAuthCredentialSalt();
|
||||
return dependencies.getGroupsV2Api()
|
||||
.getGroupsV2AuthorizationString(aci, pni, todaySeconds, groupSecretParams, authCredentialResponse);
|
||||
.getGroupsV2AuthorizationString(aci,
|
||||
pni,
|
||||
authCredentialSalt,
|
||||
todaySeconds,
|
||||
groupSecretParams,
|
||||
authCredentialResponse);
|
||||
}
|
||||
|
||||
private ACI getSelfAci() {
|
||||
|
||||
@ -103,8 +103,16 @@ public final class ProfileHelper {
|
||||
return getRecipientProfiles(recipientIds, false);
|
||||
}
|
||||
|
||||
public void refreshRecipientProfile(RecipientId recipientId) {
|
||||
getRecipientProfile(recipientId, true);
|
||||
public boolean refreshRecipientProfile(RecipientId recipientId) {
|
||||
try {
|
||||
blockingGetProfile(retrieveProfile(recipientId, SignalServiceProfile.RequestType.PROFILE, false));
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
logger.warn("Failed to retrieve profile for {}, ignoring: {}",
|
||||
context.getRecipientHelper().resolveSignalServiceAddress(recipientId).getIdentifier(),
|
||||
e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshRecipientProfiles(Collection<RecipientId> recipientIds) {
|
||||
@ -193,11 +201,9 @@ public final class ProfileHelper {
|
||||
if (uploadProfile) {
|
||||
final var streamDetails = avatar != null && avatar.isPresent()
|
||||
? Utils.createStreamDetails(avatar.get())
|
||||
.first()
|
||||
: forceUploadAvatar && avatar == null
|
||||
? context.getAvatarStore()
|
||||
.retrieveProfileAvatar(account.getSelfRecipientAddress())
|
||||
: null;
|
||||
.first()
|
||||
: forceUploadAvatar && avatar == null ? context.getAvatarStore()
|
||||
.retrieveProfileAvatar(account.getSelfRecipientAddress()) : null;
|
||||
try (streamDetails) {
|
||||
final var avatarUploadParams = streamDetails != null
|
||||
? AvatarUploadParams.forAvatar(streamDetails)
|
||||
|
||||
@ -637,11 +637,11 @@ public class SendHelper {
|
||||
Set<RecipientId> senderKeyTargets = groupInfo.getDistributionId() == null || groupSendEndorsements == null
|
||||
? Set.of()
|
||||
: targetRecipientIds.stream()
|
||||
.filter(s -> this.isSenderKeyCapable(s,
|
||||
addressesMap.get(s),
|
||||
unidentifiedAccessesMap.get(s),
|
||||
groupSendEndorsements))
|
||||
.collect(Collectors.toSet());
|
||||
.filter(s -> this.isSenderKeyCapable(s,
|
||||
addressesMap.get(s),
|
||||
unidentifiedAccessesMap.get(s),
|
||||
groupSendEndorsements))
|
||||
.collect(Collectors.toSet());
|
||||
if (senderKeyTargets.size() < 2) {
|
||||
logger.debug("Too few sender-key-capable users ({}). Doing all legacy sends.", senderKeyTargets.size());
|
||||
senderKeyTargets = Set.of();
|
||||
@ -705,11 +705,11 @@ public class SendHelper {
|
||||
final var expirationMs = Instant.ofEpochMilli(groupSendEndorsementsExpirationMs);
|
||||
final var groupSendTokens = groupSendEndorsements != null && groupSecretParams != null
|
||||
? legacyTargets.stream()
|
||||
.map(groupSendEndorsements::get)
|
||||
.map(endorsement -> Optional.ofNullable(endorsement)
|
||||
.map(e -> e.toFullToken(groupSecretParams, expirationMs))
|
||||
.orElse(null))
|
||||
.toList()
|
||||
.map(groupSendEndorsements::get)
|
||||
.map(endorsement -> Optional.ofNullable(endorsement)
|
||||
.map(e -> e.toFullToken(groupSecretParams, expirationMs))
|
||||
.orElse(null))
|
||||
.toList()
|
||||
: null;
|
||||
final var sealedSenderAccesses = SealedSenderAccess.forFanOutGroupSend(groupSendTokens,
|
||||
senderCertificate,
|
||||
|
||||
@ -46,6 +46,7 @@ import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.asamk.signal.manager.util.Utils.handleResponseException;
|
||||
@ -88,6 +89,7 @@ public class StorageHelper {
|
||||
final var result = storageServiceRepository.getStorageManifestIfDifferentVersion(storageKey,
|
||||
localManifestVersion);
|
||||
final var fetchedRemoteManifest = result instanceof ManifestIfDifferentVersionResult.DifferentVersion;
|
||||
final Set<StorageId> identityConflictsPendingRepair = new HashSet<>();
|
||||
|
||||
var needsForcePush = false;
|
||||
final var remoteManifest = switch (result) {
|
||||
@ -116,13 +118,16 @@ public class StorageHelper {
|
||||
|
||||
if (remoteManifest.version > localManifestVersion) {
|
||||
logger.trace("Remote version was newer, reading records.");
|
||||
needsForcePush = readDataFromStorage(storageKey, localManifest, remoteManifest);
|
||||
needsForcePush = readDataFromStorage(storageKey,
|
||||
localManifest,
|
||||
remoteManifest,
|
||||
identityConflictsPendingRepair);
|
||||
} else if (remoteManifest.version < localManifest.version) {
|
||||
logger.debug("Remote storage manifest version was older. User might have switched accounts.");
|
||||
}
|
||||
logger.trace("Done reading data from remote storage");
|
||||
|
||||
readRecordsWithPreviouslyUnknownTypes(storageKey, remoteManifest);
|
||||
readRecordsWithPreviouslyUnknownTypes(storageKey, remoteManifest, identityConflictsPendingRepair);
|
||||
}
|
||||
|
||||
logger.trace("Adding missing storageIds to local data");
|
||||
@ -153,7 +158,8 @@ public class StorageHelper {
|
||||
needsMultiDeviceSync = writeToStorage(storageKey,
|
||||
remoteManifest,
|
||||
needsForcePush,
|
||||
fetchedRemoteManifest);
|
||||
fetchedRemoteManifest,
|
||||
identityConflictsPendingRepair);
|
||||
} catch (RetryLaterException e) {
|
||||
// TODO retry later
|
||||
return;
|
||||
@ -198,7 +204,8 @@ public class StorageHelper {
|
||||
private boolean readDataFromStorage(
|
||||
final StorageKey storageKey,
|
||||
final SignalStorageManifest localManifest,
|
||||
final SignalStorageManifest remoteManifest
|
||||
final SignalStorageManifest remoteManifest,
|
||||
final Set<StorageId> identityConflictsPendingRepair
|
||||
) throws IOException {
|
||||
var needsForcePush = false;
|
||||
try (final var connection = account.getAccountDatabase().getConnection()) {
|
||||
@ -225,7 +232,9 @@ public class StorageHelper {
|
||||
remoteOnlyRecords.size());
|
||||
}
|
||||
|
||||
final var listListPair = processKnownRecords(connection, remoteOnlyRecords);
|
||||
final var listListPair = processKnownRecords(connection,
|
||||
remoteOnlyRecords,
|
||||
identityConflictsPendingRepair);
|
||||
final var unknownInserts = listListPair.first();
|
||||
final var updatedStorageIds = listListPair.second();
|
||||
final var oldUnregisteredLocalOnlyIds = new HashSet<>(idDifference.localOnlyIds());
|
||||
@ -268,7 +277,8 @@ public class StorageHelper {
|
||||
|
||||
private void readRecordsWithPreviouslyUnknownTypes(
|
||||
final StorageKey storageKey,
|
||||
final SignalStorageManifest remoteManifest
|
||||
final SignalStorageManifest remoteManifest,
|
||||
final Set<StorageId> identityConflictsPendingRepair
|
||||
) throws IOException {
|
||||
try (final var connection = account.getAccountDatabase().getConnection()) {
|
||||
connection.setAutoCommit(false);
|
||||
@ -282,9 +292,8 @@ public class StorageHelper {
|
||||
|
||||
logger.debug("Found {} of the known-unknowns remotely.", remote.size());
|
||||
|
||||
processKnownRecords(connection, remote);
|
||||
account.getUnknownStorageIdStore()
|
||||
.deleteUnknownStorageIds(connection, remote.stream().map(SignalStorageRecord::getId).toList());
|
||||
processKnownRecords(connection, remote, identityConflictsPendingRepair);
|
||||
account.getUnknownStorageIdStore().deleteUnknownStorageIds(connection, knownUnknownIds);
|
||||
}
|
||||
connection.commit();
|
||||
} catch (SQLException e) {
|
||||
@ -296,7 +305,8 @@ public class StorageHelper {
|
||||
final StorageKey storageKey,
|
||||
final SignalStorageManifest remoteManifest,
|
||||
final boolean needsForcePush,
|
||||
final boolean fetchedRemoteManifest
|
||||
final boolean fetchedRemoteManifest,
|
||||
final Set<StorageId> identityConflictsPendingRepair
|
||||
) throws IOException, RetryLaterException {
|
||||
final WriteOperationResult remoteWriteOperation;
|
||||
try (final var connection = account.getAccountDatabase().getConnection()) {
|
||||
@ -306,10 +316,8 @@ public class StorageHelper {
|
||||
var idDifference = findIdDifference(remoteManifest.storageIds, localStorageIds);
|
||||
logger.debug("ID Difference :: {}", idDifference);
|
||||
|
||||
final var unknownOnlyLocal = idDifference.localOnlyIds()
|
||||
.stream()
|
||||
.filter(id -> !KNOWN_TYPES.contains(id.getType()))
|
||||
.toList();
|
||||
final var unknownStorageIds = account.getUnknownStorageIdStore().getUnknownStorageIds(connection);
|
||||
final var unknownOnlyLocal = findUnknownOnlyLocalStorageIds(idDifference.localOnlyIds(), unknownStorageIds);
|
||||
|
||||
if (!unknownOnlyLocal.isEmpty()) {
|
||||
logger.debug("Storage ids with unknown type: {} to delete", unknownOnlyLocal.size());
|
||||
@ -338,6 +346,15 @@ public class StorageHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
final var onlyIdentityConflictsPendingRepair = containsOnlyIdentityConflictsPendingRepair(remoteWriteOperation,
|
||||
identityConflictsPendingRepair);
|
||||
if (onlyIdentityConflictsPendingRepair) {
|
||||
logger.warn(
|
||||
"Deferring remote write until the profile fetch says whose identity key is correct. WriteOperationResult :: {}",
|
||||
remoteWriteOperation);
|
||||
return false;
|
||||
}
|
||||
|
||||
final var loopCheck = storageSyncLoopDetector.onWriteAttempt(remoteWriteOperation,
|
||||
fetchedRemoteManifest,
|
||||
false);
|
||||
@ -386,6 +403,22 @@ public class StorageHelper {
|
||||
}
|
||||
}
|
||||
|
||||
static List<StorageId> findUnknownOnlyLocalStorageIds(
|
||||
final List<StorageId> localOnlyStorageIds,
|
||||
final Set<StorageId> unknownStorageIds
|
||||
) {
|
||||
return localOnlyStorageIds.stream().filter(unknownStorageIds::contains).toList();
|
||||
}
|
||||
|
||||
static boolean containsOnlyIdentityConflictsPendingRepair(
|
||||
final WriteOperationResult writeOperation,
|
||||
final Set<StorageId> identityConflictsPendingRepair
|
||||
) {
|
||||
return writeOperation.deletes().isEmpty() && !writeOperation.inserts().isEmpty() && writeOperation.inserts()
|
||||
.stream()
|
||||
.allMatch(record -> identityConflictsPendingRepair.contains(record.getId()));
|
||||
}
|
||||
|
||||
private void forcePushToStorage(
|
||||
final StorageKey storageServiceKey
|
||||
) throws IOException, RetryLaterException {
|
||||
@ -705,7 +738,8 @@ public class StorageHelper {
|
||||
|
||||
private Pair<List<StorageId>, List<StorageId>> processKnownRecords(
|
||||
final Connection connection,
|
||||
List<SignalStorageRecord> records
|
||||
List<SignalStorageRecord> records,
|
||||
final Set<StorageId> identityConflictsPendingRepair
|
||||
) throws SQLException {
|
||||
final var unknownRecords = new ArrayList<StorageId>();
|
||||
final var processedRecords = new ArrayList<StorageId>();
|
||||
@ -713,7 +747,10 @@ public class StorageHelper {
|
||||
final var accountRecordProcessor = new AccountRecordProcessor(account, connection, context.getJobExecutor());
|
||||
final var groupV1RecordProcessor = new GroupV1RecordProcessor(account, connection);
|
||||
final var groupV2RecordProcessor = new GroupV2RecordProcessor(account, connection);
|
||||
final var contactRecordProcessor = new ContactRecordProcessor(account, connection, context.getJobExecutor());
|
||||
final var contactRecordProcessor = new ContactRecordProcessor(account,
|
||||
connection,
|
||||
context.getJobExecutor(),
|
||||
identityConflictsPendingRepair);
|
||||
final var stickerPackRecordProcessor = new StickerPackRecordProcessor(account, connection);
|
||||
|
||||
final var contactRecords = records.stream()
|
||||
|
||||
@ -300,7 +300,7 @@ public class ManagerImpl implements Manager {
|
||||
final var profile = serviceId == null
|
||||
? null
|
||||
: context.getProfileHelper()
|
||||
.getRecipientProfile(account.getRecipientResolver().resolveRecipient(serviceId));
|
||||
.getRecipientProfile(account.getRecipientResolver().resolveRecipient(serviceId));
|
||||
return new UserStatus(number.isEmpty() ? null : number,
|
||||
serviceId == null ? null : serviceId.getRawUuid(),
|
||||
profile != null
|
||||
@ -327,7 +327,7 @@ public class ManagerImpl implements Manager {
|
||||
final var profile = serviceId == null
|
||||
? null
|
||||
: context.getProfileHelper()
|
||||
.getRecipientProfile(account.getRecipientResolver().resolveRecipient(serviceId));
|
||||
.getRecipientProfile(account.getRecipientResolver().resolveRecipient(serviceId));
|
||||
return new UsernameStatus(username,
|
||||
serviceId == null ? null : serviceId.getRawUuid(),
|
||||
profile != null
|
||||
@ -715,10 +715,7 @@ public class ManagerImpl implements Manager {
|
||||
)) {
|
||||
final var result = notifySelf
|
||||
? context.getSendHelper()
|
||||
.sendMessage(messageBuilder,
|
||||
account.getSelfRecipientId(),
|
||||
editTargetTimestamp,
|
||||
urgent)
|
||||
.sendMessage(messageBuilder, account.getSelfRecipientId(), editTargetTimestamp, urgent)
|
||||
: context.getSendHelper().sendSelfMessage(messageBuilder, editTargetTimestamp);
|
||||
results.put(recipient, List.of(toSendMessageResult(result)));
|
||||
} else if (recipient instanceof RecipientIdentifier.Single single) {
|
||||
@ -973,7 +970,10 @@ public class ManagerImpl implements Manager {
|
||||
}
|
||||
if (!message.attachments().isEmpty()) {
|
||||
final var uploadedAttachments = context.getAttachmentHelper()
|
||||
.uploadAttachments(message.attachments(), message.voiceNote());
|
||||
.uploadAttachments(message.attachments(),
|
||||
message.attachmentDimensions(),
|
||||
message.attachmentBlurHashes(),
|
||||
message.voiceNote());
|
||||
if (!additionalAttachments.isEmpty()) {
|
||||
additionalAttachments.addAll(uploadedAttachments);
|
||||
messageBuilder.withAttachments(additionalAttachments);
|
||||
@ -1041,7 +1041,7 @@ public class ManagerImpl implements Manager {
|
||||
final var previews = new ArrayList<SignalServicePreview>(message.previews().size());
|
||||
for (final var p : message.previews()) {
|
||||
final var image = p.image().isPresent() ? context.getAttachmentHelper()
|
||||
.uploadAttachment(p.image().get()) : null;
|
||||
.uploadAttachment(p.image().get()) : null;
|
||||
previews.add(new SignalServicePreview(p.url(),
|
||||
p.title(),
|
||||
p.description(),
|
||||
|
||||
@ -9,9 +9,15 @@ public class DownloadProfileJob implements Job {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DownloadProfileJob.class);
|
||||
private final RecipientAddress address;
|
||||
private final boolean resolveIdentityKeyConflict;
|
||||
|
||||
public DownloadProfileJob(RecipientAddress address) {
|
||||
this(address, false);
|
||||
}
|
||||
|
||||
public DownloadProfileJob(RecipientAddress address, boolean resolveIdentityKeyConflict) {
|
||||
this.address = address;
|
||||
this.resolveIdentityKeyConflict = resolveIdentityKeyConflict;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -19,6 +25,9 @@ public class DownloadProfileJob implements Job {
|
||||
logger.trace("Refreshing profile for {}", address);
|
||||
final var account = context.getAccount();
|
||||
final var recipientId = account.getRecipientStore().resolveRecipient(address);
|
||||
context.getProfileHelper().refreshRecipientProfile(recipientId);
|
||||
final var refreshed = context.getProfileHelper().refreshRecipientProfile(recipientId);
|
||||
if (refreshed && resolveIdentityKeyConflict) {
|
||||
context.getJobExecutor().enqueueJob(new SyncStorageJob());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -959,7 +959,7 @@ public class SignalAccount implements Closeable {
|
||||
profile.isUnrestrictedUnidentifiedAccess()
|
||||
? Profile.UnidentifiedAccessMode.UNRESTRICTED
|
||||
: profile.getUnidentifiedAccess() != null
|
||||
? Profile.UnidentifiedAccessMode.ENABLED
|
||||
? Profile.UnidentifiedAccessMode.ENABLED
|
||||
: Profile.UnidentifiedAccessMode.DISABLED,
|
||||
capabilities,
|
||||
null);
|
||||
|
||||
@ -884,9 +884,9 @@ public class GroupStore {
|
||||
final var members = membersString == null
|
||||
? Set.<RecipientId>of()
|
||||
: Arrays.stream(membersString.split(","))
|
||||
.map(Integer::valueOf)
|
||||
.map(recipientIdCreator::create)
|
||||
.collect(Collectors.toSet());
|
||||
.map(Integer::valueOf)
|
||||
.map(recipientIdCreator::create)
|
||||
.collect(Collectors.toSet());
|
||||
final var expirationTime = resultSet.getInt("expiration_time");
|
||||
final var blocked = resultSet.getBoolean("blocked");
|
||||
final var blockedAt = resultSet.getLong("blocked_at");
|
||||
|
||||
@ -115,7 +115,7 @@ public class LegacyJsonIdentityKeyStore {
|
||||
var trustLevel = trustedKey.hasNonNull("trustLevel") ? TrustLevel.fromInt(trustedKey.get(
|
||||
"trustLevel").asInt()) : TrustLevel.TRUSTED_UNVERIFIED;
|
||||
var added = trustedKey.hasNonNull("addedTimestamp") ? new Date(trustedKey.get("addedTimestamp")
|
||||
.asLong()) : new Date();
|
||||
.asLong()) : new Date();
|
||||
identities.add(new LegacyIdentityInfo(address, id, trustLevel, added));
|
||||
} catch (InvalidKeyException e) {
|
||||
logger.warn("Error while decoding key for {}: {}", trustedKeyName, e.getMessage());
|
||||
|
||||
@ -1791,9 +1791,9 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
|
||||
profileCapabilities == null
|
||||
? Set.of()
|
||||
: Arrays.stream(profileCapabilities.split(","))
|
||||
.map(Profile.Capability::valueOfOrNull)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet()),
|
||||
.map(Profile.Capability::valueOfOrNull)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet()),
|
||||
PhoneNumberSharingMode.valueOfOrNull(resultSet.getString("profile_phone_number_sharing")));
|
||||
}
|
||||
|
||||
|
||||
@ -321,10 +321,10 @@ public class MessageSendLogStore implements AutoCloseable {
|
||||
return content.dataMessage == null
|
||||
? null
|
||||
: content.dataMessage.group != null && content.dataMessage.group.id != null
|
||||
? content.dataMessage.group.id.toByteArray()
|
||||
? content.dataMessage.group.id.toByteArray()
|
||||
: content.dataMessage.groupV2 != null && content.dataMessage.groupV2.masterKey != null
|
||||
? GroupUtils.getGroupIdV2(new GroupMasterKey(content.dataMessage.groupV2.masterKey.toByteArray()))
|
||||
.serialize()
|
||||
? GroupUtils.getGroupIdV2(new GroupMasterKey(content.dataMessage.groupV2.masterKey.toByteArray()))
|
||||
.serialize()
|
||||
: null;
|
||||
} catch (InvalidInputException e) {
|
||||
logger.warn("Failed to parse groupId id from content");
|
||||
|
||||
@ -27,6 +27,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import okio.ByteString;
|
||||
@ -47,11 +48,18 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||
private final SignalAccount account;
|
||||
private final Connection connection;
|
||||
private final JobExecutor jobExecutor;
|
||||
private final Set<StorageId> identityConflictsPendingRepair;
|
||||
|
||||
public ContactRecordProcessor(SignalAccount account, Connection connection, final JobExecutor jobExecutor) {
|
||||
public ContactRecordProcessor(
|
||||
SignalAccount account,
|
||||
Connection connection,
|
||||
final JobExecutor jobExecutor,
|
||||
final Set<StorageId> identityConflictsPendingRepair
|
||||
) {
|
||||
this.account = account;
|
||||
this.connection = connection;
|
||||
this.jobExecutor = jobExecutor;
|
||||
this.identityConflictsPendingRepair = identityConflictsPendingRepair;
|
||||
this.selfAci = account.getAci();
|
||||
this.selfPni = account.getPni();
|
||||
this.selfNumber = account.getNumber();
|
||||
@ -80,6 +88,29 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||
return unregisteredAtTimestamp > 0 && aci != null && pni == null && e164.isEmpty();
|
||||
}
|
||||
|
||||
static boolean shouldUseRemoteIdentityKey(
|
||||
final boolean isPrimaryDevice,
|
||||
final boolean statesDiffer,
|
||||
final int remoteIdentityKeySize,
|
||||
final int localIdentityKeySize,
|
||||
final long localUnregisteredAtTimestamp,
|
||||
final boolean unrepairableIdentityKeyConflict
|
||||
) {
|
||||
return remoteIdentityKeySize > 0 && (
|
||||
statesDiffer || localIdentityKeySize == 0 || localUnregisteredAtTimestamp > 0 || (
|
||||
unrepairableIdentityKeyConflict && !isPrimaryDevice
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String describeRecord(final SignalContactRecord record) {
|
||||
final var proto = record.getProto();
|
||||
final var aci = ACI.parseOrNull(proto.aci, proto.aciBinary);
|
||||
final var pni = PNI.parseOrNull(proto.pni, proto.pniBinary);
|
||||
return "[" + firstNonNull(aci, pni) + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Error cases:
|
||||
* - You can't have a contact record without an ACI or PNI.
|
||||
@ -154,32 +185,29 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||
final var remoteIdentityKeySize = remote.identityKey.size();
|
||||
final var localIdentityKeySize = local.identityKey.size();
|
||||
final var statesDiffer = remote.identityState != local.identityState;
|
||||
final var identityKeysExistAndConflict = remoteIdentityKeySize > 0
|
||||
&& localIdentityKeySize > 0
|
||||
&& !remote.identityKey.equals(local.identityKey);
|
||||
final var conflictAci = firstNonNull(localAci, remoteAci);
|
||||
final var unrepairableIdentityKeyConflict = identityKeysExistAndConflict && conflictAci == null;
|
||||
|
||||
if (remoteIdentityKeySize > 0 && (!account.isPrimaryDevice() || statesDiffer || localIdentityKeySize == 0)) {
|
||||
if (shouldUseRemoteIdentityKey(account.isPrimaryDevice(),
|
||||
statesDiffer,
|
||||
remoteIdentityKeySize,
|
||||
localIdentityKeySize,
|
||||
local.unregisteredAtTimestamp,
|
||||
unrepairableIdentityKeyConflict)) {
|
||||
identityState = remote.identityState;
|
||||
identityKey = remote.identityKey;
|
||||
} else {
|
||||
identityState = local.identityState;
|
||||
// Only use local's identity key if:
|
||||
// 1. Contact has ACI or PNI
|
||||
// 2. Remote also has an identity key (if remote size=0, respect that decision)
|
||||
if (hasLocalIdentity && localIdentityKeySize > 0 && remoteIdentityKeySize > 0) {
|
||||
if (hasLocalIdentity && localIdentityKeySize > 0) {
|
||||
identityKey = local.identityKey;
|
||||
} else {
|
||||
identityKey = ByteString.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
if (localAci != null
|
||||
&& local.identityKey.size() > 0
|
||||
&& remote.identityKey.size() > 0
|
||||
&& !local.identityKey.equals(remote.identityKey)) {
|
||||
logger.debug("The local and remote identity keys do not match for {}. Enqueueing a profile fetch.",
|
||||
localAci);
|
||||
final var address = getRecipientAddress(local);
|
||||
jobExecutor.enqueueJob(new DownloadProfileJob(address));
|
||||
}
|
||||
|
||||
PNI pni;
|
||||
String e164;
|
||||
if (account.isPrimaryDevice()) {
|
||||
@ -213,6 +241,17 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||
e164 = firstNonEmpty(remote.e164, local.e164);
|
||||
}
|
||||
|
||||
if (identityKeysExistAndConflict) {
|
||||
if (conflictAci != null) {
|
||||
logger.debug("Identity keys conflict for {}. Enqueueing a profile fetch.", conflictAci);
|
||||
jobExecutor.enqueueJob(new DownloadProfileJob(new RecipientAddress(conflictAci, pni, e164), true));
|
||||
} else {
|
||||
logger.debug("Identity keys conflict for {}. No ACI, so no profile fetch is possible.", localPni);
|
||||
}
|
||||
} else if (identityKey.size() > 0 && remoteIdentityKeySize == 0) {
|
||||
logger.debug("Remote identity key is missing for {}. Keeping ours.", firstNonNull(localAci, localPni));
|
||||
}
|
||||
|
||||
final var remoteProfileKey = remote.profileKey.size() == 0
|
||||
|| KeyUtils.profileKeyOrNull(remote.profileKey.toByteArray()) == null
|
||||
? ByteString.EMPTY
|
||||
@ -237,7 +276,9 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||
.hideStory(remote.hideStory)
|
||||
.unregisteredAtTimestamp(remote.unregisteredAtTimestamp)
|
||||
.hidden(remote.hidden)
|
||||
.pniSignatureVerified(remote.pniSignatureVerified || local.pniSignatureVerified)
|
||||
.pniSignatureVerified((remote.pniSignatureVerified || local.pniSignatureVerified)
|
||||
&& pni != null
|
||||
&& pni.isValid())
|
||||
.nickname(remote.nickname)
|
||||
.note(remote.note)
|
||||
.avatarColor(remote.avatarColor);
|
||||
@ -272,6 +313,9 @@ public class ContactRecordProcessor extends DefaultStorageRecordProcessor<Signal
|
||||
|
||||
final var matchesLocal = doProtosMatch(merged, local);
|
||||
if (matchesLocal) {
|
||||
if (identityKeysExistAndConflict && conflictAci != null) {
|
||||
identityConflictsPendingRepair.add(localRecord.getId());
|
||||
}
|
||||
return localRecord;
|
||||
}
|
||||
|
||||
|
||||
@ -84,8 +84,15 @@ abstract class DefaultStorageRecordProcessor<E extends SignalRecord<?>> implemen
|
||||
return Collections.unmodifiableSet(updatedStorageIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional extra identifying detail about a record, included in every log line for it.
|
||||
*/
|
||||
protected String describeRecord(E record) {
|
||||
return "";
|
||||
}
|
||||
|
||||
private void debug(StorageId i, E record, String message) {
|
||||
logger.debug("[{}][{}] {}", i, record.getClass().getSimpleName(), message);
|
||||
logger.debug("[{}][{}]{} {}", i, record.getClass().getSimpleName(), describeRecord(record), message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -20,6 +20,8 @@ public final class StorageSyncLoopDetector {
|
||||
private final LeakyBucket rateBucket = new LeakyBucket(100,
|
||||
Duration.ofMinutes(10).toMillis(),
|
||||
new InMemoryBucketState());
|
||||
private boolean lastAttemptChargedContent;
|
||||
private boolean lastAttemptChargedRate;
|
||||
|
||||
public StorageSyncLoopDetector(final BooleanSupplier isMultiDevice) {
|
||||
this.isMultiDevice = isMultiDevice;
|
||||
@ -39,6 +41,8 @@ public final class StorageSyncLoopDetector {
|
||||
final boolean isRetry,
|
||||
final long now
|
||||
) {
|
||||
lastAttemptChargedContent = false;
|
||||
lastAttemptChargedRate = false;
|
||||
if (!isMultiDevice.getAsBoolean() || isRetry) {
|
||||
return Decision.Allowed.INSTANCE;
|
||||
}
|
||||
@ -56,9 +60,11 @@ public final class StorageSyncLoopDetector {
|
||||
|
||||
if (chargeContent) {
|
||||
contentBucket.use(now);
|
||||
lastAttemptChargedContent = true;
|
||||
}
|
||||
if (fetchedRemoteManifest) {
|
||||
rateBucket.use(now);
|
||||
lastAttemptChargedRate = true;
|
||||
}
|
||||
if (fingerprint != null) {
|
||||
remember(fingerprint);
|
||||
@ -72,8 +78,14 @@ public final class StorageSyncLoopDetector {
|
||||
}
|
||||
|
||||
synchronized void onWriteFailed(final long now) {
|
||||
contentBucket.refund(now);
|
||||
rateBucket.refund(now);
|
||||
if (lastAttemptChargedContent) {
|
||||
contentBucket.refund(now);
|
||||
lastAttemptChargedContent = false;
|
||||
}
|
||||
if (lastAttemptChargedRate) {
|
||||
rateBucket.refund(now);
|
||||
lastAttemptChargedRate = false;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void onConverged() {
|
||||
|
||||
@ -100,7 +100,8 @@ public final class StorageSyncModels {
|
||||
final var builder = SignalContactRecord.Companion.newBuilder(recipient.getStorageRecord())
|
||||
.e164(address.number().orElse(""))
|
||||
.username(address.username().orElse(""))
|
||||
.pniSignatureVerified(recipient.isPniSignatureVerified())
|
||||
.pniSignatureVerified(address.pni().map(PNI::isValid).orElse(false)
|
||||
&& recipient.isPniSignatureVerified())
|
||||
.profileKey(recipient.getProfileKey() == null
|
||||
? ByteString.EMPTY
|
||||
: ByteString.of(recipient.getProfileKey().serialize()));
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package org.asamk.signal.manager.util;
|
||||
|
||||
import org.asamk.signal.manager.api.Message.AttachmentDimensions;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
|
||||
@ -27,16 +28,21 @@ public class AttachmentUtils {
|
||||
StreamDetails streamDetails,
|
||||
Optional<String> name,
|
||||
boolean voiceNote,
|
||||
AttachmentDimensions dimensions,
|
||||
String blurHash,
|
||||
ResumableUploadSpec resumableUploadSpec
|
||||
) throws ResumeLocationInvalidException, IOException {
|
||||
final var uploadTimestamp = System.currentTimeMillis();
|
||||
final var probedStream = probeImageDimensions(streamDetails);
|
||||
final var probedStream = dimensions != null
|
||||
? new ProbedStream(streamDetails.getStream(), dimensions.width(), dimensions.height())
|
||||
: probeImageDimensions(streamDetails);
|
||||
return SignalServiceAttachmentStream.newStreamBuilder()
|
||||
.withStream(probedStream.inputStream())
|
||||
.withContentType(streamDetails.getContentType())
|
||||
.withLength(streamDetails.getLength())
|
||||
.withFileName(name.orElse(null))
|
||||
.withVoiceNote(voiceNote)
|
||||
.withBlurHash(blurHash)
|
||||
.withWidth(probedStream.width())
|
||||
.withHeight(probedStream.height())
|
||||
.withUploadTimestamp(uploadTimestamp)
|
||||
@ -50,7 +56,7 @@ public class AttachmentUtils {
|
||||
Optional<String> name,
|
||||
ResumableUploadSpec resumableUploadSpec
|
||||
) throws ResumeLocationInvalidException, IOException {
|
||||
return createAttachmentStream(streamDetails, name, false, resumableUploadSpec);
|
||||
return createAttachmentStream(streamDetails, name, false, null, null, resumableUploadSpec);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -78,10 +78,8 @@ public class StickerUtils {
|
||||
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.ofNullable(pack.cover().emoji()).orElse(""),
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package org.asamk.signal.manager.util;
|
||||
|
||||
import org.asamk.signal.manager.api.BadRequestException;
|
||||
import org.asamk.signal.manager.api.Pair;
|
||||
import org.signal.core.models.ServiceId;
|
||||
import org.signal.libsignal.net.BadRequestError;
|
||||
@ -194,8 +195,8 @@ public class Utils {
|
||||
}
|
||||
} else if (result instanceof RequestResult.RetryableNetworkError e) {
|
||||
throw e.getNetworkError();
|
||||
} else if (result instanceof RequestResult.NonSuccess) {
|
||||
throw new AssertionError();
|
||||
} else if (result instanceof RequestResult.NonSuccess<?> e) {
|
||||
throw new BadRequestException(e.getError());
|
||||
}
|
||||
throw new IllegalStateException("Unexpected value: " + result);
|
||||
}
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
package org.asamk.signal.manager.helper;
|
||||
|
||||
import org.asamk.signal.manager.syncStorage.WriteOperationResult;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.whispersystems.signalservice.api.storage.SignalStorageRecord;
|
||||
import org.whispersystems.signalservice.api.storage.StorageId;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.StorageRecord;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class StorageHelperTest {
|
||||
|
||||
@Test
|
||||
void removesKnownTypeLocalOnlyIdsStoredAsUnknown() {
|
||||
final var staleStickerPackId = StorageId.forStickerPack(new byte[]{1});
|
||||
final var localContactId = StorageId.forContact(new byte[]{2});
|
||||
|
||||
final var result = StorageHelper.findUnknownOnlyLocalStorageIds(List.of(staleStickerPackId, localContactId),
|
||||
Set.of(staleStickerPackId));
|
||||
|
||||
assertEquals(List.of(staleStickerPackId), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void defersWritesContainingOnlyIdentityConflictsPendingRepair() {
|
||||
final var pendingId = StorageId.forContact(new byte[]{1});
|
||||
final var otherId = StorageId.forContact(new byte[]{2});
|
||||
final var pendingRecord = record(pendingId);
|
||||
final var otherRecord = record(otherId);
|
||||
|
||||
assertTrue(StorageHelper.containsOnlyIdentityConflictsPendingRepair(write(List.of(pendingRecord)),
|
||||
Set.of(pendingId)));
|
||||
assertFalse(StorageHelper.containsOnlyIdentityConflictsPendingRepair(write(List.of(pendingRecord, otherRecord)),
|
||||
Set.of(pendingId)));
|
||||
assertFalse(StorageHelper.containsOnlyIdentityConflictsPendingRepair(write(List.of()), Set.of(pendingId)));
|
||||
assertFalse(StorageHelper.containsOnlyIdentityConflictsPendingRepair(new WriteOperationResult(null,
|
||||
List.of(pendingRecord),
|
||||
List.of(new byte[]{3})), Set.of(pendingId)));
|
||||
}
|
||||
|
||||
private static SignalStorageRecord record(final StorageId id) {
|
||||
return new SignalStorageRecord(id, new StorageRecord.Builder().build());
|
||||
}
|
||||
|
||||
private static WriteOperationResult write(final List<SignalStorageRecord> inserts) {
|
||||
return new WriteOperationResult(null, inserts, List.of());
|
||||
}
|
||||
}
|
||||
@ -23,6 +23,13 @@ class StorageRecordProcessorTest {
|
||||
assertFalse(ContactRecordProcessor.shouldSplitForStorageSync(1, aci, null, "+12025550123"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void linkedDeviceUsesRemoteIdentityKeyForUnrepairableConflict() {
|
||||
assertTrue(ContactRecordProcessor.shouldUseRemoteIdentityKey(false, false, 33, 33, 0, true));
|
||||
assertFalse(ContactRecordProcessor.shouldUseRemoteIdentityKey(true, false, 33, 33, 0, true));
|
||||
assertFalse(ContactRecordProcessor.shouldUseRemoteIdentityKey(false, false, 33, 33, 0, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void keepsOlderLocalStickerDeletion() {
|
||||
assertTrue(StickerPackRecordProcessor.shouldKeepLocalDeletion(200, 100));
|
||||
|
||||
@ -65,6 +65,29 @@ class StorageSyncLoopDetectorTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void failedWriteRefundsOnlyBucketsChargedByLatestAttempt() {
|
||||
final var contentDetector = new StorageSyncLoopDetector(() -> true);
|
||||
final var repeatedWrite = writeWithInsert(1);
|
||||
for (var index = 0; index < 4; index++) {
|
||||
assertAllowed(contentDetector.onWriteAttempt(repeatedWrite, true, false, NOW));
|
||||
}
|
||||
final var deleteOnlyWrite = new WriteOperationResult(null, List.of(), List.of(new byte[]{1}));
|
||||
assertAllowed(contentDetector.onWriteAttempt(deleteOnlyWrite, true, false, NOW));
|
||||
contentDetector.onWriteFailed(NOW);
|
||||
assertEquals(new StorageSyncLoopDetector.Decision.Denied(StorageSyncLoopDetector.Cause.REPEATED_PAYLOAD, 3),
|
||||
contentDetector.onWriteAttempt(repeatedWrite, true, false, NOW));
|
||||
|
||||
final var rateDetector = new StorageSyncLoopDetector(() -> true);
|
||||
for (var index = 0; index < 100; index++) {
|
||||
assertAllowed(rateDetector.onWriteAttempt(deleteOnlyWrite, true, false, NOW));
|
||||
}
|
||||
assertAllowed(rateDetector.onWriteAttempt(deleteOnlyWrite, false, false, NOW));
|
||||
rateDetector.onWriteFailed(NOW);
|
||||
assertEquals(new StorageSyncLoopDetector.Decision.Denied(StorageSyncLoopDetector.Cause.WRITE_RATE, 100),
|
||||
rateDetector.onWriteAttempt(deleteOnlyWrite, true, false, NOW));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rateBucketLimitsDeleteOnlyWrites() {
|
||||
final var detector = new StorageSyncLoopDetector(() -> true);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package org.asamk.signal.manager.util;
|
||||
|
||||
import org.asamk.signal.manager.api.Message.AttachmentDimensions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.whispersystems.signalservice.api.util.StreamDetails;
|
||||
|
||||
@ -43,6 +44,34 @@ class AttachmentUtilsTest {
|
||||
assertArrayEquals(bytes, attachment.getInputStream().readAllBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAttachmentStream_setsSuppliedDimensionsAndBlurHash() throws Exception {
|
||||
final var bytes = "opaque video bytes".getBytes();
|
||||
final var blurHash = "LEHV6nWB2yk8pyo0adR*.7kCMdnj";
|
||||
final var details = new StreamDetails(new ByteArrayInputStream(bytes), "video/mp4", bytes.length);
|
||||
final var attachment = AttachmentUtils.createAttachmentStream(details,
|
||||
Optional.of("clip.mp4"), false, new AttachmentDimensions(1080, 1920), blurHash, null);
|
||||
|
||||
assertEquals(1080, attachment.getWidth());
|
||||
assertEquals(1920, attachment.getHeight());
|
||||
assertEquals(Optional.of(blurHash), attachment.getBlurHash());
|
||||
assertArrayEquals(bytes, attachment.getInputStream().readAllBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAttachmentStream_skipsProbingWhenDimensionsSupplied() throws Exception {
|
||||
final var imageBytes = pngBytes(37, 21);
|
||||
final var stream = new ByteArrayInputStream(imageBytes);
|
||||
final var details = new StreamDetails(stream, "image/png", imageBytes.length);
|
||||
final var attachment = AttachmentUtils.createAttachmentStream(details,
|
||||
Optional.of("meme.png"), false, new AttachmentDimensions(100, 200), null, null);
|
||||
|
||||
assertEquals(imageBytes.length, stream.available());
|
||||
assertEquals(100, attachment.getWidth());
|
||||
assertEquals(200, attachment.getHeight());
|
||||
assertArrayEquals(imageBytes, attachment.getInputStream().readAllBytes());
|
||||
}
|
||||
|
||||
private static byte[] pngBytes(final int width, final int height) throws Exception {
|
||||
final var image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
final var out = new ByteArrayOutputStream();
|
||||
|
||||
@ -1 +1 @@
|
||||
0.99.1
|
||||
0.102.1
|
||||
|
||||
@ -247,7 +247,8 @@ Remove the registration lock pin.
|
||||
Link to an existing device, instead of registering a new number.
|
||||
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.
|
||||
If you want to link to an Android/iOS device, scan the QR code that signal-cli prints in the terminal with the Signal app.
|
||||
If signal-cli is not running in a terminal, only the URI is printed and you can create a QR code from it yourself (e.g. with qrencode).
|
||||
|
||||
*-n* NAME, *--name* NAME::
|
||||
Optionally specify a name to describe this new device.
|
||||
@ -344,6 +345,15 @@ Data URI encoded attachments must follow the RFC 2397.
|
||||
Additionally a file name can be added:
|
||||
e.g.: `data:<MIME-TYPE>;filename=<FILENAME>;base64,<BASE64 ENCODED DATA>`
|
||||
|
||||
*--attachment-dimensions* [WIDTHxHEIGHT [WIDTHxHEIGHT ...]]::
|
||||
Specify the displayed dimensions (after rotation) of the attachment at the same position in `--attachment`.
|
||||
Use an empty string to skip one; image dimensions are otherwise detected automatically.
|
||||
|
||||
*--attachment-blurhash* [BLURHASH [BLURHASH ...]]::
|
||||
Specify a BlurHash (https://blurha.sh) for the attachment at the same position in `--attachment`, which clients show while it's downloading.
|
||||
Use an empty string to skip one.
|
||||
e.g.: `--attachment a.jpg clip.mp4 --attachment-dimensions '' 1080x1920 --attachment-blurhash '' 'LEHV6nWB2yk8pyo0adR*.7kCMdnj'`
|
||||
|
||||
*--view-once*::
|
||||
Send the message as a view once message.
|
||||
A conformant client will only allow the receiver to view the message once.
|
||||
|
||||
@ -8,7 +8,7 @@ public class BaseConfig {
|
||||
public static final String PROJECT_VERSION = BaseConfig.class.getPackage().getImplementationVersion();
|
||||
|
||||
static final String USER_AGENT_SIGNAL_ANDROID = Optional.ofNullable(System.getenv("SIGNAL_CLI_USER_AGENT"))
|
||||
.orElse("Signal-Android/8.21.1");
|
||||
.orElse("Signal-Android/8.27.0");
|
||||
static final String USER_AGENT_SIGNAL_CLI = PROJECT_NAME == null
|
||||
? "signal-cli"
|
||||
: PROJECT_NAME + "/" + PROJECT_VERSION;
|
||||
|
||||
@ -36,6 +36,7 @@ import static org.asamk.signal.util.SendMessageResultUtils.outputResult;
|
||||
public class SendCommand implements JsonRpcLocalCommand {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SendCommand.class);
|
||||
private static final String BLURHASH_DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@ -112,6 +113,13 @@ public class SendCommand implements JsonRpcLocalCommand {
|
||||
subparser.addArgument("--voice-note")
|
||||
.action(Arguments.storeTrue())
|
||||
.help("Mark audio attachments as voice notes. Voice notes are displayed inline in Signal clients.");
|
||||
subparser.addArgument("--attachment-dimensions")
|
||||
.nargs("*")
|
||||
.help("Specify displayed WIDTHxHEIGHT for each attachment in order. Use an empty string to skip one.");
|
||||
subparser.addArgument("--attachment-blurhash")
|
||||
.nargs("*")
|
||||
.help("Specify a BlurHash (https://blurha.sh) for each attachment in order, which clients show "
|
||||
+ "while it's downloading. Use an empty string to skip one.");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -175,6 +183,13 @@ public class SendCommand implements JsonRpcLocalCommand {
|
||||
final var viewOnce = Boolean.TRUE.equals(ns.getBoolean("view-once"));
|
||||
final var voiceNote = Boolean.TRUE.equals(ns.getBoolean("voice-note"));
|
||||
|
||||
final var dimensionStrings = ns.<String>getList("attachment-dimensions");
|
||||
final var attachmentDimensions = dimensionStrings == null
|
||||
? List.<Message.AttachmentDimensions>of()
|
||||
: parseAttachmentDimensions(dimensionStrings);
|
||||
final var blurHashes = ns.<String>getList("attachment-blurhash");
|
||||
final var attachmentBlurHashes = blurHashes == null ? List.<String>of() : parseAttachmentBlurHashes(blurHashes);
|
||||
|
||||
final var selfNumber = m.getSelfNumber();
|
||||
|
||||
final var mentionStrings = ns.<String>getList("mention");
|
||||
@ -249,6 +264,8 @@ public class SendCommand implements JsonRpcLocalCommand {
|
||||
try {
|
||||
final var message = new Message(messageText,
|
||||
attachments,
|
||||
attachmentDimensions,
|
||||
attachmentBlurHashes,
|
||||
viewOnce,
|
||||
voiceNote,
|
||||
mentions,
|
||||
@ -280,6 +297,46 @@ public class SendCommand implements JsonRpcLocalCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private List<Message.AttachmentDimensions> parseAttachmentDimensions(
|
||||
final List<String> dimensionStrings
|
||||
) throws UserErrorException {
|
||||
final var dimensionPattern = Pattern.compile("([1-9]\\d*)x([1-9]\\d*)");
|
||||
final var dimensions = new ArrayList<Message.AttachmentDimensions>();
|
||||
for (final var dimension : dimensionStrings) {
|
||||
if (dimension.isEmpty()) {
|
||||
dimensions.add(null);
|
||||
continue;
|
||||
}
|
||||
final var matcher = dimensionPattern.matcher(dimension);
|
||||
if (!matcher.matches()) {
|
||||
throw new UserErrorException("Invalid attachment dimensions syntax ("
|
||||
+ dimension
|
||||
+ ") expected 'WIDTHxHEIGHT'");
|
||||
}
|
||||
dimensions.add(new Message.AttachmentDimensions(Integer.parseInt(matcher.group(1)),
|
||||
Integer.parseInt(matcher.group(2))));
|
||||
}
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
private List<String> parseAttachmentBlurHashes(final List<String> blurHashes) throws UserErrorException {
|
||||
for (final var blurHash : blurHashes) {
|
||||
if (!blurHash.isEmpty() && !isValidBlurHash(blurHash)) {
|
||||
throw new UserErrorException("Invalid attachment BlurHash (" + blurHash + ")");
|
||||
}
|
||||
}
|
||||
return blurHashes;
|
||||
}
|
||||
|
||||
// Same check the BlurHash decoders make: the first digit fixes the length.
|
||||
private static boolean isValidBlurHash(final String blurHash) {
|
||||
if (blurHash.length() < 6) {
|
||||
return false;
|
||||
}
|
||||
final var sizeFlag = BLURHASH_DIGITS.indexOf(blurHash.charAt(0));
|
||||
return blurHash.length() == 4 + 2 * (sizeFlag % 9 + 1) * (sizeFlag / 9 + 1);
|
||||
}
|
||||
|
||||
private List<Message.Mention> parseMentions(
|
||||
final String selfNumber,
|
||||
final List<String> mentionStrings
|
||||
|
||||
@ -41,7 +41,9 @@ public class SendPollVoteCommand implements JsonRpcLocalCommand {
|
||||
.help("If self is part of recipients/groups send a normal message, not a sync message.")
|
||||
.action(Arguments.storeTrue());
|
||||
|
||||
subparser.addArgument("--poll-author").help("Specify the number of the author of the poll message.");
|
||||
subparser.addArgument("--poll-author")
|
||||
.help("Specify the number of the author of the poll message.")
|
||||
.required(true);
|
||||
subparser.addArgument("--poll-timestamp")
|
||||
.type(long.class)
|
||||
.help("Specify the timestamp of the original poll message.")
|
||||
|
||||
@ -242,6 +242,8 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
|
||||
try {
|
||||
final var message = new Message(messageText,
|
||||
attachments,
|
||||
List.of(),
|
||||
List.of(),
|
||||
false,
|
||||
false,
|
||||
List.of(),
|
||||
@ -409,6 +411,8 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
|
||||
try {
|
||||
final var message = new Message(messageText,
|
||||
attachments,
|
||||
List.of(),
|
||||
List.of(),
|
||||
false,
|
||||
false,
|
||||
List.of(),
|
||||
@ -456,6 +460,8 @@ public class DbusSignalImpl implements Signal, AutoCloseable {
|
||||
try {
|
||||
final var message = new Message(messageText,
|
||||
attachments,
|
||||
List.of(),
|
||||
List.of(),
|
||||
false,
|
||||
false,
|
||||
List.of(),
|
||||
|
||||
@ -3239,6 +3239,9 @@
|
||||
"allDeclaredMethods": true,
|
||||
"allDeclaredConstructors": true
|
||||
},
|
||||
{
|
||||
"type": "org.asamk.signal.json.JsonGroupCallUpdate"
|
||||
},
|
||||
{
|
||||
"type": "org.asamk.signal.json.JsonGroupInfo",
|
||||
"allDeclaredFields": true,
|
||||
@ -6190,6 +6193,22 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.internal.LinkedDeviceInternal_ReturnConverter",
|
||||
"jniAccessible": true,
|
||||
"methods": [
|
||||
{
|
||||
"name": "fromNative",
|
||||
"parameterTypes": [
|
||||
"java.lang.Object",
|
||||
"java.lang.Object",
|
||||
"java.lang.Object",
|
||||
"java.lang.Object",
|
||||
"java.lang.Object"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.internal.NativeHandleGuard$SimpleOwner",
|
||||
"jniAccessible": true,
|
||||
@ -6234,6 +6253,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.ChallengeOption",
|
||||
"jniAccessible": true,
|
||||
"fields": [
|
||||
{
|
||||
"name": "CAPTCHA"
|
||||
},
|
||||
{
|
||||
"name": "PUSH_CHALLENGE"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.ChatConnection$ListenerBridge",
|
||||
"jniAccessible": true,
|
||||
@ -6357,6 +6388,46 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.DeviceIdNotFoundException",
|
||||
"jniAccessible": true,
|
||||
"methods": [
|
||||
{
|
||||
"name": "<init>",
|
||||
"parameterTypes": [
|
||||
"java.lang.String"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.MismatchedDeviceException",
|
||||
"jniAccessible": true,
|
||||
"methods": [
|
||||
{
|
||||
"name": "<init>",
|
||||
"parameterTypes": [
|
||||
"java.lang.String",
|
||||
"org.signal.libsignal.net.MismatchedDeviceException$Entry[]"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.MismatchedDeviceException$Entry",
|
||||
"jniAccessible": true,
|
||||
"methods": [
|
||||
{
|
||||
"name": "<init>",
|
||||
"parameterTypes": [
|
||||
"byte[]",
|
||||
"int[]",
|
||||
"int[]",
|
||||
"int[]"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.NetworkException",
|
||||
"jniAccessible": true,
|
||||
@ -6369,6 +6440,33 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.RateLimitChallengeException",
|
||||
"jniAccessible": true,
|
||||
"methods": [
|
||||
{
|
||||
"name": "<init>",
|
||||
"parameterTypes": [
|
||||
"java.lang.String",
|
||||
"java.lang.String",
|
||||
"org.signal.libsignal.net.ChallengeOption[]",
|
||||
"long"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.RequestUnauthorizedException",
|
||||
"jniAccessible": true,
|
||||
"methods": [
|
||||
{
|
||||
"name": "<init>",
|
||||
"parameterTypes": [
|
||||
"java.lang.String"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.RetryLaterException",
|
||||
"jniAccessible": true,
|
||||
@ -6381,6 +6479,42 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.ServerSideErrorException",
|
||||
"jniAccessible": true,
|
||||
"methods": [
|
||||
{
|
||||
"name": "<init>",
|
||||
"parameterTypes": [
|
||||
"java.lang.String"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.ServiceIdNotFoundException",
|
||||
"jniAccessible": true,
|
||||
"methods": [
|
||||
{
|
||||
"name": "<init>",
|
||||
"parameterTypes": [
|
||||
"java.lang.String"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.TimeoutException",
|
||||
"jniAccessible": true,
|
||||
"methods": [
|
||||
{
|
||||
"name": "<init>",
|
||||
"parameterTypes": [
|
||||
"java.lang.String"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.TransportFailureException",
|
||||
"jniAccessible": true,
|
||||
@ -6393,6 +6527,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.UnexpectedResponseException",
|
||||
"jniAccessible": true,
|
||||
"methods": [
|
||||
{
|
||||
"name": "<init>",
|
||||
"parameterTypes": [
|
||||
"java.lang.String"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.UploadForm",
|
||||
"jniAccessible": true,
|
||||
@ -6408,6 +6554,18 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.UploadTooLargeException",
|
||||
"jniAccessible": true,
|
||||
"methods": [
|
||||
{
|
||||
"name": "<init>",
|
||||
"parameterTypes": [
|
||||
"java.lang.String"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.signal.libsignal.net.internal.BridgeChatListener",
|
||||
"jniAccessible": true
|
||||
@ -6492,6 +6650,13 @@
|
||||
"parameterTypes": [
|
||||
"java.lang.String"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "<init>",
|
||||
"parameterTypes": [
|
||||
"org.signal.libsignal.protocol.SignalProtocolAddress",
|
||||
"java.lang.String"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -9779,6 +9944,9 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "org.whispersystems.signalservice.internal.storage.protos.AccountRecord$UnreadBadgeType"
|
||||
},
|
||||
{
|
||||
"type": "org.whispersystems.signalservice.internal.storage.protos.AccountRecord$UsernameLink",
|
||||
"allDeclaredFields": true
|
||||
@ -10968,6 +11136,12 @@
|
||||
{
|
||||
"glob": "net/sourceforge/argparse4j/internal/ArgumentParserImpl_*.properties"
|
||||
},
|
||||
{
|
||||
"glob": "net/sourceforge/argparse4j/internal/ArgumentParserImpl_de.properties"
|
||||
},
|
||||
{
|
||||
"glob": "net/sourceforge/argparse4j/internal/ArgumentParserImpl_de_DE.properties"
|
||||
},
|
||||
{
|
||||
"glob": "net/sourceforge/argparse4j/internal/ArgumentParserImpl_en.properties"
|
||||
},
|
||||
|
||||
@ -0,0 +1,117 @@
|
||||
package org.asamk.signal.commands;
|
||||
|
||||
import net.sourceforge.argparse4j.ArgumentParsers;
|
||||
import net.sourceforge.argparse4j.DefaultSettings;
|
||||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
|
||||
import org.asamk.signal.commands.exceptions.UserErrorException;
|
||||
import org.asamk.signal.manager.Manager;
|
||||
import org.asamk.signal.manager.api.Message;
|
||||
import org.asamk.signal.manager.api.SendMessageResults;
|
||||
import org.asamk.signal.output.JsonWriter;
|
||||
import org.asamk.signal.testutil.ManagerMock;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class SendCommandParsingTest {
|
||||
|
||||
private static final List<String> ATTACHMENTS = List.of("photo.jpg", "portrait.mp4", "other.mp4");
|
||||
private static final String BLUR_HASH = "LEHV6nWB2yk8pyo0adR*.7kCMdnj";
|
||||
|
||||
@Test
|
||||
void cliPreservesEmptyAndMissingDimensionPositions() throws Exception {
|
||||
final var parser = ArgumentParsers.newFor("signal-cli", DefaultSettings.VERSION_0_9_0_DEFAULT_SETTINGS)
|
||||
.includeArgumentNamesAsKeysInResult(true).build();
|
||||
new SendCommand().attachToSubparser(parser.addSubparsers().addParser("send"));
|
||||
final var message = send(parser.parseArgs(new String[]{"send", "--note-to-self",
|
||||
"-a", "photo.jpg", "portrait.mp4", "other.mp4", "--attachment-dimensions", "", "1080x1920"}));
|
||||
|
||||
assertEquals(ATTACHMENTS, message.attachments());
|
||||
assertEquals(Arrays.asList(null, new Message.AttachmentDimensions(1080, 1920)),
|
||||
message.attachmentDimensions());
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonRpcPassesDimensionsIntoTheSentMessage() throws Exception {
|
||||
final var message = send(namespace(List.of("", "1920x1080")));
|
||||
assertEquals(ATTACHMENTS, message.attachments());
|
||||
assertEquals(Arrays.asList(null, new Message.AttachmentDimensions(1920, 1080)),
|
||||
message.attachmentDimensions());
|
||||
}
|
||||
|
||||
@Test
|
||||
void cliPreservesBlurHashPositions() throws Exception {
|
||||
final var parser = ArgumentParsers.newFor("signal-cli", DefaultSettings.VERSION_0_9_0_DEFAULT_SETTINGS)
|
||||
.includeArgumentNamesAsKeysInResult(true).build();
|
||||
new SendCommand().attachToSubparser(parser.addSubparsers().addParser("send"));
|
||||
final var message = send(parser.parseArgs(new String[]{"send", "--note-to-self",
|
||||
"-a", "photo.jpg", "portrait.mp4", "other.mp4", "--attachment-blurhash", "", BLUR_HASH}));
|
||||
|
||||
assertEquals(List.of("", BLUR_HASH), message.attachmentBlurHashes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonRpcPassesBlurHashesWithDimensions() throws Exception {
|
||||
final var message = send(new JsonRpcNamespace(Map.of("noteToSelf", true, "attachments", ATTACHMENTS,
|
||||
"attachmentDimensions", List.of("", "1920x1080"), "attachmentBlurhash", List.of("", BLUR_HASH))));
|
||||
|
||||
assertEquals(Arrays.asList(null, new Message.AttachmentDimensions(1920, 1080)),
|
||||
message.attachmentDimensions());
|
||||
assertEquals(List.of("", BLUR_HASH), message.attachmentBlurHashes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void omittedDimensionsKeepExistingBehavior() throws Exception {
|
||||
final var message = send(new JsonRpcNamespace(Map.of("noteToSelf", true, "attachments", ATTACHMENTS)));
|
||||
assertEquals(List.of(), message.attachmentDimensions());
|
||||
assertEquals(List.of(), message.attachmentBlurHashes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsMalformedDimensionsBeforeSending() {
|
||||
for (final var value : List.of("0x1080", "1920x0", "-1x2", "1.5x2", "1920")) {
|
||||
assertThrows(UserErrorException.class, () -> send(namespace(List.of(value))), value);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsMalformedBlurHashesBeforeSending() {
|
||||
// Wrong length for its size digit, an unknown size digit, too short.
|
||||
for (final var value : List.of(BLUR_HASH + "0", "!" + BLUR_HASH.substring(1), "00000")) {
|
||||
assertThrows(UserErrorException.class, () -> send(blurHashNamespace(List.of(value))), value);
|
||||
}
|
||||
}
|
||||
|
||||
private JsonRpcNamespace blurHashNamespace(List<String> blurHashes) {
|
||||
return new JsonRpcNamespace(Map.of("noteToSelf", true, "attachments", ATTACHMENTS,
|
||||
"attachmentBlurhash", blurHashes));
|
||||
}
|
||||
|
||||
private JsonRpcNamespace namespace(List<String> dimensions) {
|
||||
return new JsonRpcNamespace(Map.of("noteToSelf", true, "attachments", ATTACHMENTS,
|
||||
"attachmentDimensions", dimensions));
|
||||
}
|
||||
|
||||
private Message send(Namespace namespace) throws Exception {
|
||||
final var captured = new AtomicReference<Message>();
|
||||
final var delegate = ManagerMock.create("+15551234567");
|
||||
final var manager = (Manager) Proxy.newProxyInstance(Manager.class.getClassLoader(),
|
||||
new Class<?>[]{Manager.class}, (proxy, method, args) -> {
|
||||
if (method.getName().equals("sendMessage")) {
|
||||
captured.set((Message) args[0]);
|
||||
return new SendMessageResults(1, Map.of());
|
||||
}
|
||||
return method.invoke(delegate, args);
|
||||
});
|
||||
new SendCommand().handleCommand(namespace, manager, (JsonWriter) ignored -> {});
|
||||
return captured.get();
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user