Compare commits

...

713 commits

Author SHA1 Message Date
AsamK
f6d81e3c05 Update gradle
Some checks failed
signal-cli CI / build (21) (push) Has been cancelled
signal-cli CI / build (24) (push) Has been cancelled
signal-cli CI / build-graalvm (push) Has been cancelled
signal-cli CI / build-client (macos) (push) Has been cancelled
signal-cli CI / build-client (ubuntu) (push) Has been cancelled
signal-cli CI / build-client (windows) (push) Has been cancelled
CodeQL / Analyse (push) Has been cancelled
2025-08-17 17:35:59 +02:00
AsamK
42f10670b6 Replace deprecated groovy utils 2025-08-17 17:35:14 +02:00
AsamK
b453d7a0b9 Add new svr2 mrenclave 2025-08-02 12:05:01 +02:00
AsamK
f9a36c6e04 Fix send parameters to be all camel case
Fixes #1814
2025-07-16 20:59:26 +02:00
AsamK
be48afb2b5 Fix container build 2025-07-16 20:56:02 +02:00
AsamK
a0960fcabd Prepare next release 2025-07-16 20:55:50 +02:00
AsamK
dbc454ba9e Bump version to 0.13.18 2025-07-16 19:40:10 +02:00
AsamK
2225e69277 Update sqlite-jdbc 2025-07-16 19:37:03 +02:00
AsamK
783201d12e Fix incorrect error message 2025-07-16 19:17:21 +02:00
AsamK
3e981d66e9 Fix null pointer regression 2025-07-14 18:52:41 +02:00
AsamK
7c7fc76a64 Add support for sending view once messages
Closes #1812
2025-07-14 16:42:06 +02:00
AsamK
c924d5c03a Update libsignal-service-java 2025-07-14 16:21:47 +02:00
AsamK
dc787be17b Build rust json-rpc client in CI 2025-07-12 11:57:23 +02:00
AsamK
3d4070a139 Compile UnixStream support only on unix systems 2025-07-12 11:42:12 +02:00
AsamK
dbdff83132 Update README
Fixes #1803
2025-07-12 11:09:24 +02:00
AsamK
4ce194afe2 Add missing username parameter to getUserStatus command in json-rpc client 2025-07-12 11:03:54 +02:00
AsamK
ca33249170 Handle rate limit exception correctly when querying usernames
Fixes #1797
2025-07-12 11:03:28 +02:00
AsamK
a96626c468 Update to rust 2024 edition 2025-07-12 10:16:57 +02:00
AsamK
d54be747da Remove unused dependency 2025-07-12 10:15:27 +02:00
AsamK
ff846bc678 Fix clippy warnings 2025-07-12 10:05:57 +02:00
AsamK
1b7f755590 Update dependencies 2025-07-12 10:05:14 +02:00
AsamK
887ed3bb44 Show better error message when sending fails due to missing pre keys 2025-07-08 17:35:17 +02:00
AsamK
3180eba836 Exit if account check fails at startup
Fixes #1804
2025-07-08 17:34:04 +02:00
AsamK
cb06cbdcca Shut down when dbus daemon connection goes away unexpectedly
Fixes #1800
2025-06-29 11:22:30 +02:00
AsamK
069325af47 Extend shutdown request with optional error 2025-06-29 11:22:30 +02:00
AsamK
e7ca02f1fb Prepare next release 2025-06-29 11:22:30 +02:00
AsamK
fa9bb3c210 Bump version to 0.13.17 2025-06-28 14:57:20 +02:00
AsamK
e6113d4d96 Update libsignal-service-java 2025-06-28 14:35:56 +02:00
AsamK
6cc3a6f561 Update dependencies 2025-06-25 00:20:42 +02:00
AsamK
70c79eac01 Keep all unhandled fields of remote storage record
Fixes #1792
2025-06-24 23:13:00 +02:00
AsamK
5dc66f839d Close attachment input streams after upload
Fixes #1790
2025-06-10 19:36:52 +02:00
AsamK
a0d5744c49 Improve behavior when pin data doesn't exist on the server 2025-06-08 16:22:03 +02:00
AsamK
6b60a6d5a5 Fix NPR when loading an inactive group
Fixes #1786
2025-06-08 14:48:25 +02:00
AsamK
0257344940 Prepare next release 2025-06-08 14:47:20 +02:00
AsamK
17cd99be59 Bump version to 0.13.16 2025-06-07 16:58:00 +02:00
AsamK
2f8328847c Update dependencies 2025-06-07 16:14:55 +02:00
AsamK
7e9727aa38 Update tests 2025-06-07 16:14:55 +02:00
AsamK
bf87fcc652 Ensure messages are created with a unique timestamp
Fixes #1783
2025-06-03 22:22:51 +02:00
AsamK
6b46314eab Update dependencies 2025-06-03 21:59:38 +02:00
AsamK
e89803464b Update libsignal-service 2025-06-01 21:51:03 +02:00
AsamK
a9bb8d9aae Update gradle 2025-06-01 16:11:21 +02:00
AsamK
74909408c4 Add missing reflect config
Fixes #1768
2025-05-10 10:18:03 +02:00
AsamK
bb124a922d Prepare next release 2025-05-08 22:56:55 +02:00
AsamK
56e11d0857 Update codeql v3 2025-05-08 22:55:26 +02:00
AsamK
d0d0021f57 Bump version to 0.13.15 2025-05-08 21:49:51 +02:00
AsamK
7aafb05995 Update dependencies 2025-05-08 21:31:17 +02:00
AsamK
e594f3b237 Update libsignal-service 2025-05-08 21:31:17 +02:00
AsamK
bb86830a61 Add compatibility flag for graalvm native build 2025-05-08 20:42:13 +02:00
AsamK
bcc1eadc7d Remove unused e164 field from account record 2025-05-08 20:36:27 +02:00
AsamK
4fd9e55c3c Enable native access required by Java 24
Fixes #1765
2025-05-08 20:04:49 +02:00
AsamK
a2900085c9 Use java 24 in CI 2025-05-08 19:37:57 +02:00
AsamK
5e11cf1c50 Update gradle 2025-05-08 19:37:41 +02:00
AsamK
4e455d85d6 Add more logging to register 2025-04-26 09:04:05 +02:00
AsamK
1e685c7cab Extend merge/split logging 2025-04-09 20:44:10 +02:00
AsamK
ce813e4529 Update client dependencies 2025-04-08 16:24:57 +02:00
AsamK
bd7948e246 Prepare next release 2025-04-08 16:24:30 +02:00
AsamK
b998f322f5 Bump version to 0.13.14 2025-04-06 20:10:46 +02:00
AsamK
db2182aa7d Update libsignal-service 2025-04-06 20:02:02 +02:00
AsamK
69a9b30732 Update libsignal-service 2025-03-31 14:56:56 +02:00
AsamK
3dc8844cb4 Update libraries 2025-03-31 09:19:20 +02:00
AsamK
adb6787d5b Refresh prekeys when receiving message with invalid key id 2025-03-31 09:11:28 +02:00
AsamK
14b07be0dc Always renew session when failing to decrypt message 2025-03-31 09:11:05 +02:00
AsamK
6befda7ef1 Update graalvm build tools 2025-03-22 10:55:16 +01:00
AsamK
67302eb9c3 Replace cached envelopes when moving
Fixes #1730
2025-03-18 18:20:44 +01:00
AsamK
f18015ff2e f 2025-03-18 18:13:38 +01:00
AsamK
1295ef69ca Use record patterns 2025-03-16 22:07:29 +01:00
AsamK
f26a0d2891 Update libsignal-service 2025-03-16 22:06:58 +01:00
AsamK
2b150112ff Remove previous prekeys when importing legacy prekeys 2025-03-16 12:22:21 +01:00
AsamK
7aede7c17f Remove previous prekeys when importing legacy prekeys 2025-03-16 12:18:59 +01:00
AsamK
b92cbc6a7c Exclude libsignal-client testing libraries 2025-03-04 10:04:34 +01:00
AsamK
68b7416e57 Update gradle wrapper 2025-03-04 08:32:56 +01:00
AsamK
4feba68afd Replace deprecated gradle api 2025-03-04 08:29:05 +01:00
AsamK
4eb34c7a93 Update apt repository before installing packages 2025-02-28 09:46:45 +01:00
AsamK
26fd3e379a Prepare next release 2025-02-28 09:46:28 +01:00
AsamK
93d281e712 Bump version to 0.13.13 2025-02-28 09:36:27 +01:00
AsamK
985af6e445 Update libsignal-service 2025-02-28 09:32:17 +01:00
AsamK
5693d871f7 Update dependencies 2025-02-28 09:32:17 +01:00
AsamK
dba8cf7a6f Update reflect-config 2025-02-27 18:01:21 +01:00
AsamK
141d3326ab Add in-memory cache to KeyValueStore 2025-02-27 17:21:31 +01:00
AsamK
d3d2caac5a Tweak hikari config 2025-02-27 17:21:31 +01:00
AsamK
e1f4dae5c2 Show better error message when receiving an empty JSON RPC line
Fixes #1715
2025-02-27 11:39:31 +01:00
AsamK
cf5c943127 Remove legacy SVR2 enclave 2025-02-27 11:34:23 +01:00
AsamK
ed79e0b377 Check if required quote-author parameter is missing
Fixes #1716
2025-02-27 11:14:54 +01:00
Enguerran P.
a089a5ef04 Update README > landline procedure
Update landline (`--voice`) registration procedure.

This closes #1666 (🤘)
2025-02-07 23:16:46 +01:00
AsamK
90145655f4 Update CHANGELOG.md 2025-02-07 19:15:55 +01:00
AsamK
3cd07ae9cd Set libsignal network proxy to match java proxy
Fixes #1523
2025-02-07 18:30:10 +01:00
AsamK
8aa71c132f Fix log message 2025-02-07 18:30:10 +01:00
AsamK
b579935846 Update README 2025-02-07 18:30:10 +01:00
AsamK
dfa886fae9 Update dependencies 2025-02-07 18:30:10 +01:00
AsamK
f04f789231 Update gradle action 2025-01-31 16:45:47 +01:00
AsamK
a6ec71dc31 Add --mobilecoin-address as alias to updateProfile
Closes #1638
2025-01-30 20:18:07 +01:00
AsamK
47d65586cd Improve handling of unknown storage records
Fixes #1696
2025-01-30 19:57:44 +01:00
AsamK
b8d8413a22 Fix creating builder from contact
Fixes #1678
2025-01-23 17:12:40 +01:00
AsamK
5e16123632 Extend updateContact command with nick given/family name and note 2025-01-23 17:11:33 +01:00
AsamK
d57442bd2a Prepare next release 2025-01-19 13:26:12 +01:00
AsamK
70313c45a9 Update reflect-config.json
Fixes #1686
2025-01-19 13:25:08 +01:00
AsamK
f14c204764 Bump version to 0.13.12 2025-01-18 20:53:46 +01:00
AsamK
71d3b83a1c Update user agent 2025-01-18 20:24:01 +01:00
AsamK
148bf7dee2 Add man page to build tar file
Fixes #1660
2025-01-18 20:07:41 +01:00
AsamK
2d1ba6b4ca Extend man Makefile 2025-01-18 19:56:23 +01:00
AsamK
055a8ee8b9 Add general description for global/subcommand arguments
Fixes #1649
2025-01-18 16:59:25 +01:00
AsamK
407a20d4bb Update client dependencies 2025-01-18 16:53:09 +01:00
AsamK
05cd6aee6a Add version and group to all modules 2025-01-18 16:30:28 +01:00
AsamK
a1378507b2 Rename lib to libsignal-cli 2025-01-18 16:30:07 +01:00
AsamK
78cd0b13de Update dependencies 2025-01-18 16:08:43 +01:00
AsamK
c25468a71e Update reflect-config.json 2025-01-17 16:09:39 +01:00
AsamK
a5d2e1ea23 Use getRawQuery to prevent double decoding the query
Fixes #1682
2025-01-17 16:03:00 +01:00
AsamK
6acf16ef4e Improve tests 2025-01-14 23:12:45 +01:00
AsamK
e11e093020 Enable sqlite WAL journal_mode
Related #1670
2025-01-14 22:35:45 +01:00
AsamK
74c2604dc8 Set sqlite PRAGMA via Url 2025-01-14 22:31:36 +01:00
AsamK
e4af0be0ad Use existing connection to read configuration during storage sync 2025-01-14 21:33:12 +01:00
AsamK
5ac5938c8b Reduce log level of invalid sync contact address
Fixes #1663
2025-01-14 20:41:04 +01:00
AsamK
94269744ad Improve final address when merging recipients 2025-01-14 20:30:06 +01:00
AsamK
7a25ae5b9c Fix reading nickname from storage record
Fixes #1664
2025-01-14 20:30:06 +01:00
AsamK
cbd92654cf Store pni correctly in storage record 2025-01-14 20:30:06 +01:00
AsamK
bd95373a70 Parse unregisteredAtTimestamp correctly
Fixes #1651
Fixes #1646
2025-01-14 20:30:06 +01:00
AsamK
d982633215 Update dependencies 2025-01-14 20:30:06 +01:00
AsamK
f91ca82902 Prepare next release 2025-01-14 20:30:06 +01:00
Slayer
c55ee85c5c Fixing RW connections deadlock on SQLite
Without this change we're getting a connection in the same thread we hold one already.
2025-01-14 12:21:12 +01:00
AsamK
a3776c88bd Bump version to 0.13.11 2024-12-26 20:31:28 +01:00
AsamK
4a781656b4 Update dependencies 2024-12-26 20:23:08 +01:00
AsamK
11d38f29ef Improve splitting of long message bodies 2024-12-26 20:15:19 +01:00
AsamK
22a0ff976a Update libsignal-service 2024-12-26 20:14:53 +01:00
AsamK
c05b47e4d0 Delete storage id of unregistered recipients after remote update 2024-12-25 17:19:33 +01:00
AsamK
ac145e6a27 Ignore destination if it's an empty uuid
Fixes #1643
2024-12-25 16:23:36 +01:00
AsamK
f00b8523d9 Update dependencies 2024-12-15 21:18:02 +01:00
AsamK
c3f8d68ceb Create account entropy pool instead of master key 2024-12-15 21:14:40 +01:00
AsamK
9d92a3e06b Update libsignal-service 2024-12-15 21:13:59 +01:00
AsamK
f2df600d38 Update CHANGELOG.md
Fixes #1641
2024-12-01 10:12:46 +01:00
AsamK
24d344fda4 Prepare next release 2024-11-30 16:32:31 +01:00
AsamK
0a296e77a0 Bump version to 0.13.10 2024-11-30 16:14:53 +01:00
AsamK
ba147a48f8 Update reflect config 2024-11-30 15:45:26 +01:00
AsamK
77a5c454b7 Improve documentation for recipient arguments
Fixes #1639
2024-11-29 21:10:46 +01:00
AsamK
2c68b5a9e1 Add support for using PNI as recipient 2024-11-29 21:10:46 +01:00
AsamK
68c9d84d19 Update libsignal-service
Fixes #1633
2024-11-24 13:04:04 +01:00
AsamK
fe752e0c79 Add simplification for single recipient reactions 2024-11-24 11:51:54 +01:00
AsamK
26b5a4c582 Small code improvement 2024-11-23 23:57:23 +01:00
AsamK
10ee295ea3 Fix receiving shared contacts in graalvm mode
Fixes #1629
2024-11-23 23:57:23 +01:00
AsamK
6a5ea5fc01 Workaround issue with invalid address in recipient store 2024-11-23 23:57:23 +01:00
AsamK
ff6cb5262a Update libsignal-service
Support for storage encryption v2 and account entropy pool

Fixes #1632
2024-11-23 23:57:23 +01:00
AsamK
f2005593ec Reformat files 2024-11-23 22:35:06 +01:00
AsamK
3533500b73 Update dependencies 2024-11-23 22:35:06 +01:00
AsamK
e5251ae158 Switch to using toml version catalogs 2024-11-21 21:17:33 +01:00
AsamK
a5e272be3f Prepare next release 2024-10-28 23:45:03 +01:00
AsamK
277652e3f2 Bump version to 0.13.9 2024-10-28 23:37:26 +01:00
AsamK
181086aefe Update graalvm config
Fixes #1612
2024-10-28 10:58:14 +01:00
AsamK
a1d552698a Fix verifyAccount
Fixes #1614
2024-10-28 10:42:26 +01:00
AsamK
1b959608c3 Simplify verification code request 2024-10-28 10:35:18 +01:00
AsamK
acddef6e35 Prepare next release 2024-10-26 15:02:28 +02:00
AsamK
0e77870b59 Bump version to 0.13.7 2024-10-26 15:01:05 +02:00
AsamK
0a287b0b3e Reformat files 2024-10-26 13:10:33 +02:00
AsamK
5a4f4ba6db Implement message expiration timer version
Fixes #1605
2024-10-26 13:08:21 +02:00
AsamK
5171107a29 Run CI with Java 23 2024-10-25 17:22:47 +02:00
AsamK
47e6fc1769 Update dependencies 2024-10-25 17:20:07 +02:00
AsamK
9afd4e4328 Update libsignal-service 2024-10-25 17:20:07 +02:00
Jailson Dias
eac2a47163 add group info on json message 2024-10-25 17:19:17 +02:00
Jailson Dias
5646f65195 add received and delivered timestamps on json message 2024-10-25 17:19:17 +02:00
AsamK
fab1b96c21 Upload text attachment also if there no other attachments
Fixes #1598
2024-09-29 09:43:51 +02:00
AsamK
91eacc18c2 Refactor attachment upload 2024-09-29 09:43:26 +02:00
AsamK
7f1fc932ad Add constant for MAX_MESSAGE_BODY_SIZE 2024-09-29 09:43:05 +02:00
AsamK
304c44064d Prepare next release 2024-09-28 22:14:26 +02:00
AsamK
946c483f35 Bump version to 0.13.7 2024-09-28 22:13:24 +02:00
AsamK
69d691f416 Update graalvm reflect-config
Fixes #1590
2024-09-28 20:41:58 +02:00
AsamK
a6ab8f7e80 Remove v2 suffix from secure value recovery 2024-09-28 20:41:58 +02:00
AsamK
19dc2d446b Update libsignal-service 2024-09-28 20:41:58 +02:00
AsamK
8524037900 Update gradle 2024-09-28 12:13:49 +02:00
AsamK
dbbc3fbd71 Remove unnecessary proto conversion 2024-09-24 17:28:24 +02:00
AsamK
c6e93126fa Fix truncating cdsi table
Fixes #1587
2024-09-12 23:09:20 +02:00
AsamK
352699c4b6 Update artifact CI actions 2024-09-11 17:50:01 +02:00
AsamK
bea2772491 Update graalvm buildtools 2024-09-10 19:16:42 +02:00
AsamK
bab8ddf35a Update slf4j 2024-09-09 18:31:47 +02:00
AsamK
1d5d16f57e Adapt signal_jni file names in graalvm config 2024-09-09 18:31:34 +02:00
AsamK
6f9e9e9302 Prepare next release 2024-09-08 19:24:18 +02:00
AsamK
20add0f27b Downgrade dbus-java 2024-09-08 19:24:18 +02:00
AsamK
eca3c6fa30 Replace deprecated DBusMap 2024-09-08 19:24:18 +02:00
AsamK
a0d1b081ff Bump version to 0.13.6 2024-09-08 18:59:22 +02:00
AsamK
d852c60c37 Update dependencies 2024-09-08 18:55:23 +02:00
AsamK
2b5451f74a Update client dependencies 2024-09-08 18:51:56 +02:00
AsamK
b322716215 Update CHANGELOG.md 2024-09-08 10:18:26 +02:00
AsamK
41726d339f Update libsignal-service-java 2024-09-08 10:18:17 +02:00
AsamK
52bb92dce4 Call getter instead of using cached instance variable 2024-09-08 10:15:02 +02:00
AsamK
65adfdd6f5 Add signal CDN 3 2024-09-08 10:15:02 +02:00
AsamK
d8b1a2fffe Fix stripping the correct identifiers when merging recipients 2024-09-08 09:24:51 +02:00
AsamK
ea436ecb64 Fix possible db dead lock
Fixes #1483
2024-09-08 09:22:52 +02:00
AsamK
82fbde4f19 Adapt code style 2024-09-08 09:00:39 +02:00
AsamK
19b15e68e4 Improve addDevice error message
Fixes #1573
2024-09-08 09:00:34 +02:00
AsamK
b51f849fe6 Send sync message for read/viewed receipt messages
Fixes #1570
2024-09-08 08:51:33 +02:00
AsamK
7cc0ef1c70 Improve error message and log output for failed jsonrpc commands 2024-09-08 08:30:31 +02:00
AsamK
485c4fd467 Improve daemon deprecation message 2024-09-08 08:30:08 +02:00
AsamK
bda395191b Don't set previousE164s param if cdsi token is empty
Fixes #1576
2024-09-08 08:29:54 +02:00
AsamK
cb129db95e Update libsignal-service 2024-08-20 17:39:39 +02:00
AsamK
130cee9906 Fix sending to groups with non sender key capable members
Regression in 0.13.5

Fixes #1568
2024-08-20 17:13:57 +02:00
AsamK
6bdc9a4b16 Update gradle 2024-08-20 17:10:03 +02:00
AsamK
a69a9b7b0e Add method to update group endorsements 2024-08-20 17:10:03 +02:00
AsamK
6ea373fbd5 Prepare next release 2024-08-20 17:10:03 +02:00
Ingmar Lippert
62da514850
Add more specificity to addDevice (#1561) 2024-08-20 16:59:52 +02:00
AsamK
fe3934171d Bump version to 0.13.5 2024-07-25 22:47:16 +02:00
AsamK
3d11221732 Update jackson 2024-07-25 22:43:55 +02:00
AsamK
e961488b1a Update man page 2024-07-25 22:40:46 +02:00
AsamK
2db3d3259e Use UploadSpec for attachment uploads 2024-07-25 22:31:35 +02:00
AsamK
e51b1ee23a Update libsignal-service 2024-07-25 16:27:24 +02:00
AsamK
5ff66728e3 Update libsignal-service 2024-06-26 15:38:55 +02:00
AsamK
baf7b74a61 Prepare next release 2024-06-06 11:15:02 +02:00
AsamK
c716f94649 Bump version to 0.13.4 2024-06-06 10:47:42 +02:00
AsamK
8b355918e8 Use jvm running gradle if it's compatible with targetCompatibility 2024-06-06 10:40:21 +02:00
AsamK
67012b40b1 Update user agent 2024-06-06 10:16:53 +02:00
AsamK
fd402b52c8 Update doc 2024-06-06 10:16:40 +02:00
AsamK
5a97b9e134 Update groups when using listGroups command
Fixes #1517
2024-06-06 10:07:20 +02:00
AsamK
17596795c2 Only store profile keys for group history if none is known yet 2024-06-06 10:07:20 +02:00
AsamK
10b9c264fd Update libsignal-service
Fixes #1530
2024-06-06 10:07:20 +02:00
AsamK
a2b002ac5e Add java 22 to CI 2024-06-06 09:12:40 +02:00
AsamK
6d764db114 Update dependencies 2024-06-02 13:26:17 +02:00
AsamK
777cfbb69f Update gradle wrapper 2024-06-02 13:25:09 +02:00
AsamK
9781c56571 Improve username update error message
Fixes #1535
2024-05-24 16:09:07 +02:00
AsamK
04cf54263e Fix getUserStatus command with only username parameter
Related #1535
2024-05-23 12:46:15 +02:00
AsamK
6baf0eac13 Fix type parsing in JSON RPC mode
Fixes #1533
2024-05-21 20:28:16 +02:00
AsamK
fb21a42cce Update graalvm build tools 2024-05-18 22:13:29 +02:00
AsamK
53d7e0f08b Handle all possible identifiers of a RecipientAddress
Fixes #1516
2024-05-17 18:02:05 +02:00
AsamK
8f756cd90c Update libsignal-service 2024-05-09 21:32:34 +02:00
Sebastian Scheibner
fb81bf1d05
Update README.md 2024-05-09 21:20:38 +02:00
AsamK
04726f005c Save account file after setting username link
Fixes #1515
2024-05-01 09:08:00 +02:00
AsamK
09e3e7f335 Rotate storageId after setting username 2024-05-01 09:07:39 +02:00
AsamK
90b1e4bc02 Delete username link when deleting username 2024-05-01 09:04:03 +02:00
AsamK
3f31f1a8a6 Update metainfo 2024-04-21 09:34:06 +02:00
dependabot[bot]
3cd8e323c9
Bump rustls from 0.21.10 to 0.21.11 in /client (#1511)
Bumps [rustls](https://github.com/rustls/rustls) from 0.21.10 to 0.21.11.
- [Release notes](https://github.com/rustls/rustls/releases)
- [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustls/rustls/compare/v/0.21.10...v/0.21.11)

---
updated-dependencies:
- dependency-name: rustls
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-20 13:53:36 +02:00
AsamK
7060faf5d3 Prepare next release 2024-04-19 19:25:27 +02:00
AsamK
c9f2cca024 Bump version to 0.13.3 2024-04-19 19:21:07 +02:00
AsamK
f0054372b8 Add timestamp to account file
Closes #1498
2024-04-19 17:25:43 +02:00
AsamK
0a82c51b79 Update dependencies 2024-04-19 17:17:32 +02:00
AsamK
cef83d962c Fix missing null check 2024-04-19 17:07:29 +02:00
AsamK
c1775913b9 Implement dbus support for listIdentities
Fixes #195
2024-04-19 17:07:18 +02:00
AsamK
e4c5144fbf Add more details to listContacts command
Fixes #1502
2024-04-17 21:26:16 +02:00
AsamK
8aeaf927e6 Add missing parts for new nick name and note columns 2024-04-17 21:21:28 +02:00
AsamK
7e0d4c9b89 Store profile phone number sharing mode and discoverable state 2024-04-17 21:08:09 +02:00
AsamK
71de8e63cc Cache newly created session record
Fixes #1481
2024-04-15 19:23:50 +02:00
AsamK
e456d06cb0 Add aci,pni to API RecipientAddress 2024-04-15 19:23:50 +02:00
AsamK
e0cd5b987e Add handling for new nickname and note fields 2024-04-15 19:23:50 +02:00
Stephen Brennan
e5ebb732cb
Document the unit of "start" and "length" for mentions and text styles (#1505)
The unit of UTF-16 code units is not necessarily obvious for users of
languages that index strings by Unicode code points. Provide a pointer
to an FAQ entry as well:

https://github.com/AsamK/signal-cli/wiki/FAQ#string-indexing-units

Closes #1504

Signed-off-by: Stephen Brennan <stephen@brennan.io>
2024-04-13 20:26:15 +02:00
AsamK
419beee29a Update libsignal-service-java 2024-04-06 14:04:56 +02:00
AsamK
d4e1f9b7f1 Remove unnecessary config field 2024-04-06 14:04:56 +02:00
AsamK
edce33ae15 Disable java 22 until gradle supports it 2024-04-06 14:04:56 +02:00
dependabot[bot]
95f9e18de2
Bump h2 from 0.3.24 to 0.3.26 in /client (#1501)
Bumps [h2](https://github.com/hyperium/h2) from 0.3.24 to 0.3.26.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.26/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.24...v0.3.26)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-06 14:04:48 +02:00
AsamK
95d4b7d012 Update gradle wrapper 2024-04-06 12:34:55 +02:00
AsamK
17c24b3ff2 Update libsignal-service-java 2024-03-27 22:58:13 +01:00
AsamK
be0e8ddd8a Add reregister to tests 2024-03-27 22:58:13 +01:00
AsamK
49cc9cd9f8 Update CHANGELOG.md 2024-03-23 15:00:02 +01:00
AsamK
c85c995fef Prepare next release 2024-03-23 10:34:45 +01:00
AsamK
dda23e76ac Bump version to 0.13.2 2024-03-23 09:57:15 +01:00
AsamK
95e70b9d15 Add Java 22 to CI 2024-03-23 09:50:59 +01:00
AsamK
abddf24752 Update user agent 2024-03-23 09:50:59 +01:00
AsamK
d356d92b5e Extend getUserStatus command for usernames 2024-03-22 10:54:42 +01:00
AsamK
8b4f377cf1 Update dependencies 2024-03-22 10:04:57 +01:00
dependabot[bot]
323a801600
Bump mio from 0.8.10 to 0.8.11 in /client (#1488)
Bumps [mio](https://github.com/tokio-rs/mio) from 0.8.10 to 0.8.11.
- [Release notes](https://github.com/tokio-rs/mio/releases)
- [Changelog](https://github.com/tokio-rs/mio/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/mio/compare/v0.8.10...v0.8.11)

---
updated-dependencies:
- dependency-name: mio
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-09 13:08:46 +01:00
AsamK
3372992dc2 Add appstream metainfo file 2024-03-09 11:35:36 +01:00
AsamK
ee39733978 Update libsignal-service 2024-03-07 17:44:19 +01:00
AsamK
5e17fe8414 Fix typo 2024-02-27 19:35:38 +01:00
AsamK
aebe64571d Prepare next release 2024-02-27 18:20:21 +01:00
AsamK
0cc2da690a Bump version to 0.13.1 2024-02-27 18:18:30 +01:00
AsamK
2424fc1f53 Add register parameter to force reregistration 2024-02-27 18:12:43 +01:00
AsamK
2e4cd0eddc Only retry messages after identity was trusted
Fixes #1477
2024-02-27 17:52:21 +01:00
AsamK
189b21dbde Improve error message if captcha is rejected by server
Fixes #1328
2024-02-26 22:13:57 +01:00
AsamK
e77d9e3d60 Update libsignal-service 2024-02-26 22:07:36 +01:00
AsamK
df76aa9919 Default number sharing to NOBODY
Matches the official apps behavior.

Closes #1472
2024-02-26 18:27:09 +01:00
AsamK
378ac23c6c Update account attributes after setting a pin
Ensures that the recovery password gets set immediately.

Related to #1447
2024-02-26 18:23:37 +01:00
AsamK
fc2ae856d2 Adapt account record processor for linked devices 2024-02-25 19:41:34 +01:00
AsamK
57164ad7fb Prevent crash when receiving already migrated group v1 from storage
Fixes #1471
2024-02-25 19:41:10 +01:00
AsamK
6c44662496 Allow overriding user agent string
Not recommended, as it could lead to issues with newer Signal protocol changes.

Fixes #1476
2024-02-25 18:27:20 +01:00
AsamK
22ac3cb50f Removing linked devices only works on the primary device 2024-02-25 18:12:36 +01:00
AsamK
b76964f219 Improve warning message 2024-02-25 17:54:18 +01:00
AsamK
2f3c064462 Update documentation 2024-02-25 17:46:27 +01:00
AsamK
c9002d9481 Ignore failure when uploading PNI prekeys
Can happen if PNI identity key hasn't been sent to the server yet.
2024-02-25 17:46:09 +01:00
AsamK
83d471818d Allow setting a username with explicit descriminator
Fixes #1469
2024-02-22 20:00:00 +01:00
AsamK
0bb2a64781 Add missing field handling in account record processor 2024-02-22 20:00:00 +01:00
AsamK
08ba774b71 Update graalvm buildtools 2024-02-20 17:37:59 +01:00
AsamK
59c1f4eed2 Show information when requesting voice verification without SMS verification
Fixes #1373
2024-02-20 17:37:31 +01:00
AsamK
f1e3b5c9cc Add uniqueness check to db migration 2024-02-20 17:05:56 +01:00
AsamK
7bc7242f08 Add uniqueness check to db migration 2024-02-20 11:03:32 +01:00
Viktor Szépe
6f407ab509
Fix typos (#1459) 2024-02-19 08:20:24 +01:00
AsamK
db9acaf4ff Set snapshot version 2024-02-18 22:14:09 +01:00
AsamK
7821815429 Bump version to 0.13.0 2024-02-18 22:01:41 +01:00
AsamK
cd88e896fa Fix missing unique constraint on storage_id table 2024-02-18 22:01:41 +01:00
AsamK
67fd10b978 Update dependencies 2024-02-18 21:43:02 +01:00
AsamK
9051f68ba6 Enable profile sharing when accepting message request 2024-02-18 21:26:36 +01:00
AsamK
3b3377e6e9 Update CHANGELOG 2024-02-18 21:06:17 +01:00
AsamK
fb99ff5284 Document creating and sending to usernames 2024-02-18 21:04:50 +01:00
AsamK
ed40d116b7 Fix sending to username 2024-02-18 20:58:55 +01:00
AsamK
d84362eb0f Add sendMessageRequestResponse command 2024-02-18 20:48:16 +01:00
AsamK
2c0ad7feb7 Fix issue with prekey update 2024-02-18 20:48:16 +01:00
AsamK
7206b4da25 Fix use of aci instead of serviceId
Fixes #1455
2024-02-18 20:48:01 +01:00
AsamK
fd671576a4 Fix issues with receiving message to PNI address 2024-02-18 20:46:07 +01:00
AsamK
6cd57312a1 Add parameter to configure phone number privacy 2024-02-18 20:42:41 +01:00
AsamK
25258db55d Restructure pre key refresh to be more robust 2024-02-18 16:32:50 +01:00
AsamK
91ed49e019 Includ pni signatures if necessary 2024-02-18 16:31:34 +01:00
AsamK
bd792f5b7f Update libsignal-service 2024-02-18 15:16:41 +01:00
AsamK
ed4b1e8f02 Improve sql query 2024-02-10 12:24:13 +01:00
AsamK
e78146eb67 Fix saving username link components 2024-02-10 11:54:32 +01:00
AsamK
9f6b6cb657 Implement account reregistration with recovery password 2024-02-10 11:54:06 +01:00
AsamK
fb85e1a068 Update dependencies 2024-02-09 22:51:03 +01:00
AsamK
c78299e7bc Update gradle wrapper 2024-02-09 22:11:50 +01:00
AsamK
9a5e29e015 Update CHANGELOG.md 2024-02-09 22:11:01 +01:00
AsamK
7cf3a989bf Add command to retrieve avatars and stickers
Fixes #1125
2024-02-09 22:10:46 +01:00
AsamK
d486563099 Ignore invalid ACI/PNI in recipient table 2024-02-09 18:34:32 +01:00
AsamK
be699cbd85 Fix potential dead lock in recipient store 2024-02-09 18:07:30 +01:00
AsamK
4e61f2b2e5 Use app id as dbus name for flatpak version 2024-02-09 18:07:30 +01:00
AsamK
1bf703b012 Add --bus-name option to use different D-Bus bus name 2024-02-09 18:07:30 +01:00
AsamK
a48fdaf8b7 Bump version to 0.12.8 2024-02-09 18:07:30 +01:00
AsamK
a0f33ac942 Update reflect config 2024-02-09 18:07:30 +01:00
AsamK
bea659f948 Add icon 2024-02-05 16:58:24 +01:00
AsamK
7fb263fdf0 Update dependencies 2024-02-01 17:54:26 +01:00
AsamK
3be1c24b1b Update client dependencies 2024-01-31 21:17:23 +01:00
AsamK
0b33cb55b8 Adapt listContacts to only show registered users
Fixes #1395
2024-01-31 20:49:12 +01:00
AsamK
00e71ed0fc Improve scrubbing of sensitive identifiers
Fixes #1189
2024-01-31 20:33:01 +01:00
AsamK
a40810e33e Update CHANGELOG.md 2024-01-30 19:00:33 +01:00
AsamK
15c9d04703 Add missing command to json-rpc client 2024-01-30 18:58:27 +01:00
AsamK
e13dcdc85a Prevent empty username 2024-01-30 17:59:59 +01:00
AsamK
2ab5b2817e Deprecate daemon mode without explicit mode flag 2024-01-30 17:44:51 +01:00
AsamK
080c14d111 Clean old prekeys only after server message queue is empty 2024-01-30 17:21:29 +01:00
AsamK
d1e32f2b11 Improve some log output 2024-01-29 18:21:44 +01:00
AsamK
0840e0dedb Add CDSI recipients refresh job 2024-01-28 23:04:34 +01:00
AsamK
888d6bf091 Split unregistered recipients 2024-01-28 23:04:34 +01:00
AsamK
e2f308a57a Store profile sharing for group v2 2024-01-28 22:38:41 +01:00
AsamK
76fe6ad799 Recreate recipient database with aci column 2024-01-28 22:38:41 +01:00
AsamK
90df256e85 Fix storage sync issues 2024-01-28 22:38:41 +01:00
AsamK
f92466f6be Store recipient unregistered state 2024-01-28 22:38:41 +01:00
AsamK
6a5dcd00b2 Implement remote storage sync
Closes #604
2024-01-28 22:38:41 +01:00
AsamK
6c65de8ddf Update libsignal-service 2024-01-26 23:32:29 +01:00
AsamK
f696097301 Improve JobExecutor 2024-01-25 21:47:40 +01:00
AsamK
7d3db03d4a Update dbusjava 2024-01-25 21:03:25 +01:00
AsamK
78c87e501e Improve log level 2024-01-25 21:00:16 +01:00
AsamK
2974b466aa Update sqlite 2024-01-22 18:40:46 +01:00
AsamK
ca1a852b44 Handle mentions when receiving from dbus 2024-01-22 18:40:39 +01:00
dependabot[bot]
ac2343b142
Bump h2 from 0.3.21 to 0.3.24 in /client (#1418)
Bumps [h2](https://github.com/hyperium/h2) from 0.3.21 to 0.3.24.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.24/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.21...v0.3.24)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 09:18:20 +01:00
AsamK
be9efb9a25 Update libsignal-service-java 2024-01-14 11:42:48 +01:00
AsamK
30e8e36635 Add --unrestricted-unidentified-sender to updateAccount command 2024-01-13 16:00:55 +01:00
AsamK
3290a5bf4d Add --notify-self parmeter
Fixes #1087
2024-01-12 18:09:42 +01:00
AsamK
c07ba14fc6 Update dependencies 2024-01-04 18:09:36 +01:00
AsamK
c6e9e19858 Update libsignal-service-java 2024-01-04 18:00:19 +01:00
morph027
375bdb7948
add file.encoding=UTF-8 to native builds, refs #1105 (#1404)
Signed-off-by: morph027 <stefan.heitmueller@gmx.com>
2023-12-19 18:48:54 +01:00
AsamK
5e7899675e Update CHANGELOG.md 2023-12-15 19:53:35 +01:00
AsamK
328ba0202f Bump version to 0.12.7 2023-12-15 19:10:56 +01:00
AsamK
59cbbde835 Update CHANGELOG.md 2023-12-15 19:05:13 +01:00
AsamK
caa4fa0180 Fix inspection issues 2023-12-15 17:32:30 +01:00
AsamK
fc6a4b78eb Update libsignal-service-java
Uses new device link endpoint

Fixes #1399
2023-12-14 18:07:13 +01:00
AsamK
2729772adb Fix release pipeline 2023-12-11 19:11:26 +01:00
AsamK
70e0f7027e Bump version to 0.12.6 2023-12-11 18:32:25 +01:00
AsamK
4dcdffda0a Use a new SVR2 enclave. 2023-12-10 19:30:36 +01:00
AsamK
2535cd9c2c Update libsignal-service-java 2023-12-10 18:57:05 +01:00
AsamK
d3eef6c820 Update dependencies 2023-12-06 18:12:17 +01:00
AsamK
35fb1d60af Update gradle wrapper 2023-12-06 18:11:27 +01:00
AsamK
089ec22666 Fix overlapping sqlite sessions in migration
Fixes #1394
2023-12-06 18:09:02 +01:00
AsamK
66077f317b Add missing capabilities for linking account
Fixes #1386
2023-11-28 19:12:53 +01:00
AsamK
5904bfa92b Update CHANGELOG 2023-11-21 21:14:16 +01:00
AsamK
2c1d7a803f Set snapshot version 2023-11-21 21:10:44 +01:00
AsamK
6d5b8b6a7b Bump version to 0.12.5 2023-11-21 21:07:54 +01:00
AsamK
b88f8c205e Update dependencies 2023-11-21 21:02:47 +01:00
AsamK
ce4afbe4c2 Update libsignal-service-java
Fixes #1384
2023-11-21 21:02:47 +01:00
AsamK
fcaecef961 Improve source serviceId handling 2023-11-21 19:59:06 +01:00
AsamK
81d2e315e3 Prevent groupV2Operations from being null 2023-11-21 17:17:26 +01:00
AsamK
fddf982832 Fix receiving contact address
Fixes #1385
2023-11-21 17:09:21 +01:00
AsamK
3602ef9be9 Add -u flag to send to username 2023-11-21 17:09:21 +01:00
AsamK
37c65ca6b4 Implement username links 2023-11-21 17:09:21 +01:00
AsamK
77f284661b Fix storing of username 2023-11-12 19:33:40 +01:00
AsamK
0d60c4d464 Refactor group v2 migration 2023-11-12 19:33:40 +01:00
AsamK
f06eeb01b9 Refactor getGroup method 2023-11-12 15:21:29 +01:00
AsamK
fbcc1cfb50 Refactor KeyValueStore 2023-11-12 14:24:26 +01:00
AsamK
26cef99cdf Don't store self profile key in recipient store 2023-11-12 13:13:58 +01:00
AsamK
5cac7feabe Add who am I job 2023-11-12 12:12:02 +01:00
AsamK
d844e0f396 Add download profile avatar job 2023-11-12 11:59:48 +01:00
AsamK
ed11bf6368 Make JobExecutor execute jobs asynchronously 2023-11-12 11:51:38 +01:00
AsamK
eaa6b7cf57 Check whoAmI after regular CDSI sync 2023-11-11 18:12:22 +01:00
AsamK
f8ea631b03 Refactor trusted recipient resolver 2023-11-11 17:24:45 +01:00
AsamK
7b0744ec75 Add --hide parameter to removeContact command 2023-11-11 11:39:33 +01:00
AsamK
9f4a2b3e26 Convert Contact to record 2023-11-11 11:09:24 +01:00
AsamK
9741c93ce9 Move all dbus interaction to dbus package 2023-11-10 15:44:42 +01:00
AsamK
7e9940be4a Refactor DaemonCommand 2023-11-10 15:32:45 +01:00
AsamK
c0aa338d7c Reorder static final modifier 2023-11-10 14:03:13 +01:00
AsamK
b20978e08e Remove now unnecessary SuccessExitStatus from systemd units 2023-11-09 23:26:38 +01:00
AsamK
5d33f71d4d Use improved shutdown for receive command 2023-11-09 19:23:11 +01:00
AsamK
1058e33f12 Use improved shutdown for daemon command 2023-11-09 19:23:11 +01:00
AsamK
b7fedff511 Use improved shutdown for jsonRpc command 2023-11-09 19:23:11 +01:00
AsamK
f252597002 Add infrastructure for better shutdown handling 2023-11-09 19:23:11 +01:00
AsamK
cbbfc4ea6e Rename exportManager method 2023-11-09 18:54:17 +01:00
AsamK
6b04197eaa Refactor getManagerFromQuery method 2023-11-09 18:54:17 +01:00
AsamK
3027ba2cf1 Refactor log config 2023-11-09 15:55:43 +01:00
AsamK
19e9f31afd Remove now unnecessary try/catch 2023-11-09 09:22:46 +01:00
AsamK
5a1d2580cb Use a single PushServiceSocket 2023-11-09 09:22:46 +01:00
AsamK
cb5cace8da Update libsignal-service 2023-11-09 09:22:46 +01:00
AsamK
e734c125ad Ignore quotes without author
Fixes #1369
2023-11-08 12:40:34 +01:00
AsamK
d351f64fb1 Configure log LevelChangePropagator 2023-11-08 12:05:55 +01:00
AsamK
e61f587bfc Reduce use of printStackTrace 2023-11-07 09:28:29 +01:00
AsamK
4e8f0a41c7 Update dependencies 2023-11-07 09:11:36 +01:00
AsamK
4fbc97c92a Register java exit handlers in graalvm native build 2023-11-07 08:54:05 +01:00
AsamK
1e35ac380e Refactor DaemonCommand 2023-11-07 08:54:05 +01:00
AsamK
85b0647a3e Extract getReceiveConfig to utils 2023-11-07 08:54:05 +01:00
spezialist1
7b899d1853
Add example syntax for a mixed text style (#1367) 2023-11-06 11:58:11 +01:00
AsamK
699b21f066 Handle rate limit error in JSON-RPC mode 2023-11-05 16:20:33 +01:00
AsamK
8d423adb4d Use safety numbers with ACI by default 2023-11-05 11:59:09 +01:00
AsamK
506dcfa6c0 Update README.md 2023-11-04 11:32:42 +01:00
AsamK
91e0d5164b Ensure profile key is stored in profileKeyStore
Fixes #1362
2023-11-03 20:31:35 +01:00
AsamK
67d8ffcde5 Initialize database before registering 2023-11-03 20:19:41 +01:00
AsamK
44c9aded65 Don't check self number in recipients refresh 2023-11-03 20:01:05 +01:00
AsamK
f1ccfc0361 Initialize pre key offsets when creating new account 2023-11-03 20:00:26 +01:00
AsamK
cdef9c435c Check if account is already registered before attempting verification 2023-11-03 19:59:39 +01:00
AsamK
ed8ac5b84c Use new threads API 2023-10-24 17:36:32 +02:00
AsamK
8d55dfb66b Use pattern matching switch cases 2023-10-24 17:36:32 +02:00
AsamK
80c1a6d2af Switch to Java 21 2023-10-24 17:36:32 +02:00
AsamK
895740755d Remove included by default native-image component 2023-10-23 18:01:49 +02:00
AsamK
90601b7b6b Fix man page generation 2023-10-22 21:33:15 +02:00
AsamK
cc2ffbb4da Bump version to 0.12.4 2023-10-22 20:49:22 +02:00
AsamK
0c24064939 Bump version to 0.12.4 2023-10-22 20:42:25 +02:00
AsamK
7859084be8 Fix tests 2023-10-22 20:42:25 +02:00
AsamK
101c217ef8 Update captcha help text 2023-10-22 20:42:25 +02:00
AsamK
7887a5a613 Prevent unnecessary compile warnings 2023-10-22 20:42:25 +02:00
AsamK
800ff09a37 Update dependencies 2023-10-22 20:42:25 +02:00
AsamK
20f8fa2ebd Prevent ConcurrentModificationException
Fixes #1351
2023-10-22 20:42:25 +02:00
AsamK
314159c273 Update libsignal-service-java 2023-10-22 20:42:25 +02:00
Vladimir Glinskikh
bf16885278
Bump upgrade gradle from 8.3 to 8.4 (#1353) 2023-10-21 12:49:28 +02:00
AsamK
3ae6f7ab7c Update graalvm reflect-config 2023-10-19 23:31:41 +02:00
AsamK
e829dd8b5c Update graalvm reflect-config
Fixes #1352
2023-10-19 23:18:07 +02:00
dependabot[bot]
62c71eaafa
Bump rustix from 0.37.23 to 0.37.25 in /client (#1350)
Bumps [rustix](https://github.com/bytecodealliance/rustix) from 0.37.23 to 0.37.25.
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.37.23...v0.37.25)

---
updated-dependencies:
- dependency-name: rustix
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-19 08:01:22 +02:00
AsamK
24ec25ac62 Fix typo 2023-10-18 22:57:51 +02:00
Pēteris Caune
19b5c76154
Fix typo (#1348) 2023-10-18 07:47:10 +02:00
AsamK
2852b2ea6d Extend log scrubber for dbus serviceIds 2023-10-17 23:05:45 +02:00
AsamK
fc2e9bbfae Fix inspections 2023-10-17 20:34:21 +02:00
AsamK
d51dd7ae57 Use .isEmpty() for checking lists and strings 2023-10-17 20:34:21 +02:00
AsamK
9ba70c1808 Update bouncycastle dependency 2023-10-17 19:48:22 +02:00
AsamK
9ec942ea1d Update workflow permission 2023-10-17 19:34:42 +02:00
AsamK
b9e644269b Use gradle-build-action for caching and dependency submission 2023-10-17 19:22:09 +02:00
AsamK
5b56445741 Update github checkout action 2023-10-17 19:21:41 +02:00
AsamK
1ed5148624 Bump version to 0.12.3 2023-10-17 15:24:56 +02:00
AsamK
fb7c63c507 Reduce sqlite logging 2023-10-17 15:21:18 +02:00
AsamK
2c5edbc981 Add cache for serviceId to recipient id/address mapping 2023-10-17 15:20:14 +02:00
AsamK
1addffe622 Store username aci link in recipient store 2023-10-17 14:58:15 +02:00
AsamK
733c14bbc8 Ignore invalid recipient numbers 2023-10-17 14:19:32 +02:00
AsamK
ca2e6adedb Update dependencies 2023-10-17 14:18:35 +02:00
AsamK
5cc20ace1f Ignore failures from SVR v1 pin 2023-10-17 13:34:09 +02:00
AsamK
400dcf2899 Refactor creating linked account files 2023-10-16 19:01:31 +02:00
AsamK
dd3326f038 Do a full recipients refresh every day 2023-10-16 19:01:31 +02:00
AsamK
505de39d2a Use partial cdsi request only for a maximum of 3 numbers
Fixes #1239
2023-10-16 19:01:08 +02:00
AsamK
24069c8277 Update documentation 2023-10-15 22:37:46 +02:00
AsamK
bb78e9aaeb Update libsignal-service-java 2023-10-15 22:37:01 +02:00
AsamK
5c39344cff Implement full CDSI refresh 2023-10-15 22:36:45 +02:00
AsamK
7cd24a74af Improve handling of CDSI resource exhaustion 2023-10-15 19:02:59 +02:00
AsamK
a675631965 Resolve self address after setting new PNI 2023-10-15 18:53:24 +02:00
AsamK
33c4e17c0d Implement change phone number
Closes #1240
2023-10-14 23:37:23 +02:00
AsamK
56ee173d03 Implement exit code for rate limiting error
Closes #1240
2023-10-13 19:36:58 +02:00
AsamK
af4709255a Fix creation of key_value table for new accounts 2023-10-13 12:54:30 +02:00
AsamK
527e1aefc9 Refactor generateSignedPreKeyRecord 2023-10-12 21:15:19 +02:00
AsamK
a66dd0dc79 Implement EditMessageReceived signal for dbus 2023-10-10 20:21:40 +02:00
AsamK
7b5b5776f0 Add --quote-attachment paramter to send command
Fixes #1318
2023-10-10 19:54:45 +02:00
AsamK
91ab0b12b0 Rename pre key id offset field 2023-10-09 19:13:45 +02:00
AsamK
c2ea7045f5 Prevent deleting self recipient 2023-10-09 19:13:45 +02:00
AsamK
af3cae5f3d Restructure account save file 2023-10-09 19:13:45 +02:00
AsamK
54c3b19052 Support serviceId based safety number for scannable safety numbers 2023-10-06 21:42:28 +02:00
AsamK
04de0010b5 Update libsignal-service-java 2023-10-06 19:58:09 +02:00
AsamK
66161b74a6 Move aci/pni to account data object 2023-10-06 19:53:32 +02:00
AsamK
889ef66784 Update release pipeline
The default libsignal-client jar now contains Linux/macOS/Window native library
2023-10-06 18:55:13 +02:00
AsamK
fae83f8d22 Extend json-rpc man page 2023-10-05 22:18:36 +02:00
AsamK
6f4d538832 Execute JSON-RPC requests in parallel 2023-10-05 22:18:13 +02:00
AsamK
a0c345185b Move metadata to db 2023-10-04 20:19:07 +02:00
AsamK
c0f771684d Move configuration store to db 2023-10-04 20:19:07 +02:00
AsamK
90ec01bfbf Only output username if set 2023-10-04 17:10:16 +02:00
AsamK
7f83bfefd6 Reorganize SignalAccount 2023-10-02 12:09:41 +02:00
AsamK
310aadbdc0 Fix handling edit message 2023-10-01 17:06:07 +02:00
AsamK
6b2de3a24d Reduce hikari logging noise 2023-10-01 16:30:14 +02:00
AsamK
002a87d3ba Add pre key cleanup and improve refresh 2023-10-01 16:29:41 +02:00
AsamK
6f63346905 Fix log statement 2023-09-30 22:32:45 +02:00
AsamK
735766669e Convert classes to records 2023-09-30 22:26:08 +02:00
AsamK
0dda8b405e Bump version to 0.12.2 2023-09-30 21:36:12 +02:00
AsamK
47626b2af4 Update libsignal-service 2023-09-30 21:28:02 +02:00
AsamK
fed2c94431 Update libsignal-service 2023-09-24 18:03:38 +02:00
AsamK
da7428d6bd Switch to zulu java distribution 2023-09-24 17:49:00 +02:00
AsamK
dced7a14c8 Remove jetbrains annotation 2023-09-23 17:48:03 +02:00
AsamK
f1d735f93d Update libsignal-service 2023-09-23 16:21:19 +02:00
AsamK
73b4239744 Add libsignal_client_path property to override libsignal-client jar fiel 2023-09-23 12:37:23 +02:00
AsamK
67747253e8 Improve message cache behavior in case of io error 2023-09-23 12:25:30 +02:00
AsamK
0bdc400a30 Update java version in ci workflow 2023-09-23 11:54:30 +02:00
AsamK
b12a016f3a Update graalvm build tools 2023-09-23 11:52:49 +02:00
AsamK
fd851ba6cb Fix batch response in case of empty responses list
This adapts the implementation to the JSON-RPC specification.
2023-09-23 11:52:37 +02:00
AsamK
b2a32666e9 Store serviceId in protocol stores as TEXT
ServiceIds are no longer just UUIDs, they can now have prefixes like "PNI:"
2023-09-23 11:51:50 +02:00
AsamK
a7744e837c Improve robustness in receiving messages 2023-09-16 12:00:55 +02:00
AsamK
e5aa10a730 Update README 2023-09-16 11:57:43 +02:00
AsamK
e626cc0390 Update dependencies 2023-09-08 19:19:15 +02:00
AsamK
4d93415485 Update graalvm buildtools 2023-09-08 17:54:07 +02:00
AsamK
ed746c389c Update libsignal-service 2023-09-08 17:53:49 +02:00
AsamK
a7a5947a1b Implement multi-account mode for jsonRpc command
Fixes #1319
2023-09-01 12:11:38 +02:00
AsamK
c8daef5113 Add --receive-mode parameter to jsonRpc command 2023-09-01 12:08:56 +02:00
AsamK
ee195c966e Update libsignal-service 2023-09-01 11:31:41 +02:00
AsamK
6ecda07577 Bump version to 0.12.1 2023-08-26 18:05:51 +02:00
AsamK
26fea2d6a0 Update dependencies 2023-08-26 18:05:26 +02:00
AsamK
87e79bceaa Fix output format of jsonrpc receive notification
Fixes #1313
2023-08-26 17:17:25 +02:00
AsamK
bc3bdbbf21 Improve identites listing 2023-08-26 16:33:39 +02:00
AsamK
884fa2748e Ensure uniqueness of dbus identity names 2023-08-22 11:56:27 +02:00
AsamK
4a37227b95 Improve logging 2023-08-22 11:52:22 +02:00
AsamK
a9f1944636 Show better error message when using addDevice on a linked device 2023-08-21 17:23:16 +02:00
AsamK
6d23eb3bf6 Show better error message when using addDevice on a linked device 2023-08-21 17:14:04 +02:00
AsamK
3d13c69e41 Extend logging 2023-08-21 17:02:12 +02:00
AsamK
fb8624f630 Fix incorrect sql query 2023-08-21 17:00:15 +02:00
AsamK
795c5f6a04 Fix npr when upgrading old accounts 2023-08-20 22:37:25 +02:00
AsamK
98a8c991e2 Update gradle wrapper 2023-08-20 22:36:29 +02:00
AsamK
e867c57af8 Add addStickerPack command 2023-08-20 22:36:18 +02:00
AsamK
133e2cc222 Document listStickerPacks command 2023-08-20 19:41:00 +02:00
AsamK
e0a326f15b Fix prekey log output 2023-08-20 19:40:42 +02:00
AsamK
b3f550ee68 Unsubscribe in client after timeout 2023-08-17 21:06:55 +02:00
AsamK
29923cf930 Bump version to 0.12.0 2023-08-11 00:43:19 +02:00
AsamK
9a63f97a19 Update dependencies 2023-08-11 00:43:19 +02:00
AsamK
2d73ba6735 Update graalvm reflect config 2023-08-11 00:36:22 +02:00
AsamK
8037fb2d66 Switch to jsonrpsee
Fixes #1275
2023-08-11 00:36:22 +02:00
AsamK
edbf803a98 Update dependencies 2023-08-11 00:36:22 +02:00
AsamK
b51c791629 Adapt receive subscription notification to have payload in result field 2023-08-11 00:36:22 +02:00
AsamK
6cbd583746 Update CHANGELOG.md 2023-08-09 21:23:10 +02:00
AsamK
b55d75ef99 Update dependencies 2023-08-09 21:14:17 +02:00
AsamK
5e692df08c Ignore groovy deprecations 2023-08-09 21:13:58 +02:00
AsamK
e19ad40023 Update graalvm build tools 2023-08-09 21:01:46 +02:00
AsamK
934697af28 Update libsignal-service-java 2023-08-09 20:48:52 +02:00
AsamK
ca088bcc33 Extend tests 2023-08-09 20:48:52 +02:00
AsamK
1559d28ed8 Fix linking when preKeyCount is not available 2023-08-09 20:48:52 +02:00
AsamK
376a1704df Add missing handling for SecureValueRecovery.RestoreResponse.Missing response 2023-08-09 19:38:29 +02:00
AsamK
68dbf27b2f Update staging key backup service id 2023-08-09 19:37:15 +02:00
AsamK
6bde5960aa Fix issues with pni parsing 2023-08-07 21:03:39 +02:00
AsamK
86e1079195 Update libsignal-service 2023-08-06 19:09:11 +02:00
AsamK
409a707baa Update libsignal-service 2023-07-20 20:58:49 +02:00
AsamK
86f50e0355 Add support for SVR2 2023-07-14 20:28:34 +02:00
AsamK
02d4cb4a14 Update libsignal-service-java 2023-07-14 19:25:32 +02:00
Benjamin Loison
2487fff44a
Remove unnecessary spaces in README.md and add sh syntax highlighting (#1270) 2023-07-05 10:13:21 +02:00
AsamK
ffeae1a95a Store last resort kyber pre key from PniChangeNumber message 2023-07-01 14:06:35 +02:00
AsamK
e57e5b090e Update libsignal-service-java 2023-06-24 01:18:47 +02:00
AsamK
c8e35991b9 Update dependencies 2023-06-23 21:48:02 +02:00
AsamK
0ebfd989d1 Refactor ACI/PNI store handling 2023-06-18 17:54:52 +02:00
AsamK
306e38c9ee Implement support for kyber pre keys 2023-06-17 22:32:05 +02:00
AsamK
4e5c859aab Update logback 2023-06-17 17:22:26 +02:00
AsamK
ac815f7598 Fix json deserialization for request params
Fixes #1261
2023-06-17 12:59:55 +02:00
AsamK
a3f7de89f1 Update graalvm buildtools 2023-06-17 12:21:15 +02:00
AsamK
b70a43aeb5 Update graalvm config 2023-06-17 10:44:02 +02:00
Adimarantis
8726c4ede0
Update identities after trust (#1264) 2023-06-17 09:21:28 +02:00
AsamK
0c5993c0ad Add support for invalid pre key failure when sending message 2023-06-11 17:47:48 +02:00
AsamK
da25b2a763 Add missing return to app command handling 2023-06-11 17:17:04 +02:00
AsamK
aad4b53524 Update dependencies 2023-06-11 17:16:38 +02:00
AsamK
3d5c440aa2 Improve uuid/number handling 2023-06-11 16:39:54 +02:00
AsamK
4f8da7819e Reformat code 2023-06-05 19:30:18 +02:00
Adimarantis
a96c4938b1
Dbus identities (#1259)
* Dbus Identities and Trust

* Update src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java

* PR feedback updates

* Documentation and consistent case sensitivity

* doc for listIdentities and getIdentity
2023-06-05 19:26:00 +02:00
AsamK
c62a1e829f Remove duplicate address resolving 2023-06-03 16:11:21 +02:00
AsamK
106af6a801 Refactor trust command implementation 2023-06-03 16:10:47 +02:00
AsamK
2ae5297f7d Refactor App init method 2023-06-02 22:52:45 +02:00
AsamK
a7db3a5610 Extract dbus client command handling 2023-06-02 22:43:18 +02:00
AsamK
d0d3e20713 Add helper method to create valid dbus object path 2023-06-02 21:29:58 +02:00
AsamK
c852bd8a85 Remove libsignal logs on console, when log-file is given 2023-05-30 13:03:00 +02:00
AsamK
7499b41d6b Remove old methods/classes from graalvm config 2023-05-30 13:02:09 +02:00
AsamK
ee12805d05 Bump version to 0.11.11 2023-05-24 23:05:58 +02:00
AsamK
dacb48b4f4 Update sqlite-jdbc 2023-05-24 23:05:58 +02:00
AsamK
90f82b6d4c Update libsignal-service 2023-05-24 22:40:04 +02:00
AsamK
227f87e1ce Fallback to legacy send if group send fails due to invalid prekey 2023-05-23 19:34:13 +02:00
AsamK
83c75acd0a Log signal-cli version on startup 2023-05-23 19:34:13 +02:00
AsamK
ff162cb44d Refactor addDeviceLink method 2023-05-23 19:34:13 +02:00
AsamK
e5a67d6ce1 Refactor manager lib package structure 2023-05-23 19:34:13 +02:00
exquo
5197212d3f
Fix TextStyle doc example typo (#1251) 2023-05-22 17:35:34 +02:00
AsamK
8c1b5d54f7 Reduce log output of third party libs
Fixes #1064
2023-05-21 11:49:26 +02:00
AsamK
8a0005d900 Remove unused code 2023-05-21 11:18:18 +02:00
AsamK
a754eb6faf Better logging for registration failure 2023-05-21 11:15:49 +02:00
AsamK
c9082a63f0 Update user agent version 2023-05-21 11:05:59 +02:00
AsamK
314b3cafbb Refactor selfNumber in send command 2023-05-20 13:01:54 +02:00
AsamK
91700ce995 Implement textStyles for sending and receiving
Fixes #1250
2023-05-20 12:49:57 +02:00
AsamK
145a2f1179 Fix json deserialization in legacy stores
Fixes #1248
2023-05-18 11:08:57 +02:00
AsamK
6e61bc2000 Update dependencies 2023-05-18 11:00:22 +02:00
AsamK
9da6f3a702 Include all libsignal-client libs in native build
Fixes #1237
2023-05-17 19:51:57 +02:00
AsamK
760934d5a5 Fix deleting old unregistered recipient
Fixes #1242
2023-05-16 23:09:07 +02:00
AsamK
a3bc754e80 Fix migration of legacy recipient storage 2023-05-16 22:50:27 +02:00
AsamK
ae652b1294 Update graalvm buildtools 2023-05-16 22:49:59 +02:00
AsamK
ab01867235 Switch CI to temurin distribution 2023-05-11 20:20:51 +02:00
AsamK
6b0b36a555 Bump version to 0.11.10 2023-05-11 19:36:26 +02:00
AsamK
ee63a27fe9 Use Java 20 in CI build 2023-05-11 19:19:09 +02:00
AsamK
5f0c49d653 Update dependencies 2023-05-11 19:18:48 +02:00
AsamK
8a31b7f2c1 Implement editing of previous messages 2023-05-11 19:10:29 +02:00
AsamK
72390e595d Update libsignal-service-java 2023-05-11 17:45:23 +02:00
AsamK
e0e8e11443 Validate incoming messages before handling them 2023-04-23 19:57:06 +02:00
AsamK
f7f882e834 Use modern switch syntax 2023-04-23 19:55:23 +02:00
AsamK
60ad582012 Store envelopes using envelope proto 2023-04-23 19:39:36 +02:00
AsamK
f6aebb5917 Bump version to 0.11.9.1 2023-04-23 10:19:25 +02:00
AsamK
3a4ad96a00 Fix build with java 20
- previous fix required a jdk 17 to be installed as well
2023-04-22 19:19:00 +02:00
AsamK
e0ed651e53 Bump version to 0.11.9 2023-04-22 17:54:49 +02:00
AsamK
c703644dbe Fix build with java 20 2023-04-22 12:44:42 +02:00
AsamK
da4cc7dc6b Announce support for gift badges if linked device
Fixes #1226
2023-04-16 21:19:17 +02:00
AsamK
842f13b2fc Update libsignal-service-java 2023-04-16 21:19:17 +02:00
AsamK
d2251ccd14 Remove old libzkgroup fallback code 2023-04-16 21:09:24 +02:00
AsamK
6a3ebfd4c8 Update graalvm buildtools native 2023-04-13 18:08:52 +02:00
AsamK
ff803eaf45 Update gradle wrapper 2023-04-13 18:08:01 +02:00
AsamK
c93480692c Bump version to 0.11.8 2023-04-05 11:40:13 +02:00
AsamK
542a3f36a4 Fix issue with unknown identity serviceId
Fixes #1215
2023-04-05 11:25:53 +02:00
AsamK
24a8c528d0 Filter null from db result 2023-04-05 11:22:43 +02:00
AsamK
780c69d804 Reduce use of unknown serviceIds 2023-04-05 11:10:46 +02:00
AsamK
22c948166a Add labels to Containerfile 2023-04-04 12:02:24 +02:00
AsamK
64436bc9ab Refresh username after registering 2023-04-04 11:43:50 +02:00
AsamK
6106e1878b Update README.md 2023-04-03 19:00:56 +02:00
AsamK
c788c5a40e Add capabilities and read receipt handling to provisioning 2023-04-03 19:00:27 +02:00
AsamK
9f60ed534a Implement support for usernames 2023-04-03 19:00:05 +02:00
AsamK
03f193b34c Fix deleting old group in dbus mode
Fixes #1192
2023-04-02 19:25:49 +02:00
signals-from-outer-space
db42f61cbb Added missing file attachment attributes in JsonAttachment output
Closes #1217
Fixes #1216
2023-04-02 18:49:42 +02:00
exquo
49591aedb4
Document accepting group invitations (#1218) 2023-04-02 17:58:54 +02:00
AsamK
21aa2b2a7f Update dependencies 2023-04-01 12:35:46 +02:00
AsamK
276ecef300 Update libsignal-service-java
- Use session based number verification and registration
2023-04-01 12:19:53 +02:00
AsamK
20b3563f21 Update gradle wrapper 2023-03-18 21:06:59 +01:00
cedb
15630356e1 Change content-type check to check contains
So far it was doing an equals check, but a string like "application/json; charset=utf-8"
is similarly valid. And some clients like OkHttp actually automatically add the
charset.

Closes #1152
2023-03-03 18:11:56 +01:00
AsamK
f1fd528483 Fix sending large text messages to multiple recipients
Fixes #1177
2023-03-03 18:06:31 +01:00
AsamK
75d7270d5a Add verbose exception log for send error 2023-03-03 17:58:02 +01:00
AsamK
3206639778 Scrub E164 in dbus path
Related #1189
2023-02-19 20:18:05 +01:00
AsamK
2e63c3b4f7 Update CHANGELOG.md 2023-02-19 16:05:14 +01:00
AsamK
d63405d170 Bump version 2023-02-19 14:59:33 +01:00
AsamK
91e0db185c Fix remove recipient method
Fixes #1183
2023-02-19 14:21:16 +01:00
AsamK
f97543eecd Update gradle 2023-02-19 14:21:16 +01:00
AsamK
3606fb67bb Update libsignal-service-java 2023-02-19 14:21:16 +01:00
AsamK
426b59d13e Add dependencyResolutionManagement 2023-02-19 13:58:33 +01:00
AsamK
d94ec8e144 Update dependencies 2023-02-19 13:28:44 +01:00
Marvin A. Ruder
4186349bba
Bump sqlite-jdbc from 3.40.0.0 to 3.40.1.0 (#1178)
* Should fix #1129
2023-02-06 18:20:32 +01:00
AsamK
7816325e63 Fix issue with missing pni identity key
Fixes #1176
2023-02-05 12:49:12 +01:00
AsamK
15da210de7 Print text styles in plain text output 2023-01-27 21:46:39 +01:00
AsamK
a4f7632981 Update dependencies 2023-01-27 21:44:41 +01:00
AsamK
a1b16a9118 Update CONTRIBUTING.md 2023-01-27 21:25:40 +01:00
AsamK
c100b504dc Document submitRateLimitChallenge 2023-01-21 19:42:49 +01:00
AsamK
161ec9f828 Update dependencies 2023-01-07 12:14:47 +01:00
AsamK
210466e7d9 Allow JSON-RPC commands without account param if only one account exists 2022-12-30 13:50:40 +01:00
AsamK
0702159596 Bump version 2022-12-18 20:09:15 +01:00
AsamK
8d7e533196 Update dependencies 2022-12-18 20:05:53 +01:00
AsamK
47feda6ae4 Restrict workflow permissions 2022-12-04 23:16:53 +01:00
AsamK
44c945f45d Update dependencies 2022-11-24 17:31:11 +01:00
ced-b
35def4445d
Fix handling of attachments in JSON RPC (#1109)
* Fix handling of attachments in JSON RPC

It turns out that using a custom serializer on an
input stream did not work well. For one the stream seems
to be getting closed before the JSON gets written. But
also the method for writing it was throwing an
UnsupportedOperationException further down in Jackson.

The above simplifies the matter by simply outputting the
Base64 string first and then setting it on the model.

* Add missing files to attachment fix

Co-authored-by: cedb <cedb@keylimebox.org>
2022-11-24 17:29:45 +01:00
ced-b
3e60303b90
Add alive check (#1107)
Adds a simple HTTP endpoint that can be used by the container
environment to see if the app is started and available.

Co-authored-by: cedb <cedb@keylimebox.org>
2022-11-22 07:58:34 +01:00
AsamK
b6e9dfa97d Add fallback locale for voice verification
Fixes #1101
2022-11-20 11:27:33 +01:00
AsamK
5771bb858f Allow using data URIs for updateGroup/updateProfile avatars
Fixes #1082
2022-11-14 19:31:40 +01:00
AsamK
dcaf1cc189 Bump version 2022-11-09 19:00:59 +01:00
AsamK
5e1fc79c33 Fix SignalAccount initialization
Fixes #1092
2022-11-08 17:18:24 +01:00
AsamK
8997d7f91f Update README.md 2022-11-08 17:18:22 +01:00
AsamK
54a08f560e Bump version 2022-11-07 20:12:41 +01:00
AsamK
ccb37c00f6 Update dependencies 2022-11-07 19:43:15 +01:00
AsamK
6281cbfd5f Catch all exceptions when reading session record
Fixes #1083
2022-11-03 15:55:12 +01:00
AsamK
6502f3f487 Publish docker image to ghcr 2022-11-03 15:33:28 +01:00
AsamK
00535c9a42 Package native file as executable 2022-11-03 15:18:59 +01:00
AsamK
e6cf11cb3d Add missing check to httpAddres 2022-11-03 15:10:17 +01:00
AsamK
c5eb0fd351 Rework release workflow 2022-11-03 13:31:37 +01:00
AsamK
a780be70dd Add http endpoint events with SSE 2022-11-03 00:03:37 +01:00
AsamK
1d98e5307a Handle missing separator in query string parser 2022-11-02 23:16:38 +01:00
AsamK
36abb8ae8f Add check for exact path match
HttpExchange just checks startsWith, so would also match /api/v1/rpcfoobar
2022-11-02 21:17:28 +01:00
AsamK
0c4642aa20 Fix length for empty response to prevent chunked stream
The java HttpExchange expects length -1 to send Content-length: 0 ...
2022-11-02 21:15:08 +01:00
AsamK
1b029b765f Extract http endpoint handler function 2022-11-02 21:13:52 +01:00
AsamK
9563496efb Update CHANGELOG.md 2022-11-02 17:47:52 +01:00
AsamK
c628e27d2e Update man page 2022-11-02 17:46:20 +01:00
ced-b
1ad0e94b64
Exposing Signal CLI as HTTP Server (#1078)
* Add initial proof of concept for http server

* Add support for registration commands

* Add support  for MultiLocalCommands

* Improve handling of HTTP responses

Makes it so that responses area all uniformly JSON and wrapped
into the proper response envelope.

* Add caching for workflows

* Run http server with daemon command

This fits the existing command line API better

* Wrap the existing JSON RPC handler in HTTP Service

This is a redesign of earlier attempts to make an HTTP service. Fixing
that service turned out that it would have to be a copy of the
SignalJsonRpcDispatcherHandler. So instead of copy pasting all the
code the existing service is simply being wrapped.

* Switch http server to use command handler

* Clean up and simplification

* Pass full InetSocketAddress

* Minor fixes and improvements

Based on code review.

Co-authored-by: cedb <cedb@keylimebox.org>
2022-11-02 17:44:12 +01:00
AsamK
43face8ead Small improvements 2022-11-01 22:48:47 +01:00
ced-b
2e4d346bc8
Add command to get an attachment (#1080)
* Add command to get an attachment

* Refactor retrieving of attachments to use StreamDetails

* Refactor AttachmentCommand to GetAttachmentCommand

* Minor improvements to GetAttachmentCommand

* Use JSON serializer to serialize binary data

Serializing the stream is better for memory handling than
loading the whole thing into the file.

* Clean up unneeded class

* Added command to doc

Co-authored-by: cedb <cedb@keylimebox.org>
2022-11-01 22:47:43 +01:00
AsamK
bf76c04664 Refactor JsonRpcReader to for handling a single message 2022-11-01 18:06:40 +01:00
AsamK
ae678871ec Extract JSON-RPC command handler 2022-11-01 17:10:15 +01:00
AsamK
9620eb06ac Update libsignal-service 2022-11-01 13:58:09 +01:00
AsamK
9096229637 Improve behavior with synchronous and asynchronous receivers 2022-11-01 13:58:09 +01:00
AsamK
eec3d782d3 Add caching for workflows 2022-10-31 16:40:39 +01:00
AsamK
43a7478791 Update reflect-config.json 2022-10-31 15:29:18 +01:00
AsamK
175057e781 Implement receive command for JSON-RPC mode
The command returns a list of messages, as soon as the timeout is reached
after the last message has been received or the maximum number of messages
has been received.
2022-10-31 11:56:25 +01:00
AsamK
de2bfc7f79 Add optional message limit for receive command 2022-10-31 11:17:52 +01:00
AsamK
5ed9db4f08 Implement replying to stories 2022-10-30 18:18:21 +01:00
AsamK
fea19c9e20 Implement reacting to stories 2022-10-30 14:41:02 +01:00
AsamK
207764e0be Add option to disable adding message to send log 2022-10-30 11:00:25 +01:00
AsamK
49aaff2bbe Increase sqlite busy timeout 2022-10-29 13:54:06 +02:00
AsamK
25e84f2f5d Add account to plain text output
Fixes #1075
2022-10-29 13:49:14 +02:00
AsamK
e63f2fafb9 Add color to listContacts output
Fixes #1072
2022-10-28 20:12:11 +02:00
AsamK
0b5a063b62 Use complete address instead of only identifier for retry envelope
Fixes #1074
2022-10-28 18:13:25 +02:00
AsamK
e0c2f58e8d Store attachments with a file extension
Taken from the filename if present, otherwise guessed from the contentType
2022-10-22 22:00:31 +02:00
AsamK
0084a2e722 Update reflect-config.json 2022-10-22 17:43:15 +02:00
AsamK
7ff1500122 Improve logging in prekey store 2022-10-22 17:43:15 +02:00
AsamK
7805622f07 Send long text messages as attachment instead
This matches the behavior of the official clients.
2022-10-22 17:43:15 +02:00
AsamK
a8e68dce3a Extract MimeUtils 2022-10-22 17:43:15 +02:00
AsamK
b9eee539bd Add PNI to recipients 2022-10-21 22:02:33 +02:00
AsamK
e450f36e81 Don't output caption if it's empty 2022-10-21 16:22:09 +02:00
AsamK
ae221e0447 Disable graalvm toolchain detection if GRAALVM_HOME is set 2022-10-21 10:34:08 +02:00
AsamK
2a1be0bd85 Improve graalvm native build wih resource autodetect 2022-10-21 10:34:08 +02:00
AsamK
af324eeca5 Bump version 2022-10-19 20:35:53 +02:00
AsamK
eb71fd1a5a Add java 19 to CI 2022-10-19 20:34:42 +02:00
AsamK
e4a4788d5e Add native graalvm build to release 2022-10-19 20:34:16 +02:00
AsamK
47b6fe7dbe Enable story capability
Only receiving is supported
2022-10-19 19:11:37 +02:00
AsamK
9ffacfe90e Add --ignore-stories flag to prevent receiving story messages 2022-10-19 19:11:37 +02:00
AsamK
3f7d8c60b9 Add workaround for #1045 2022-10-19 17:51:12 +02:00
AsamK
2e5d8fe561 Add build graalvm native step for CI 2022-10-19 15:46:58 +02:00
AsamK
7188c75351 Update reflect-config 2022-10-19 15:36:17 +02:00
AsamK
316c35b258 Add additional logging for reading message cache 2022-10-19 11:02:10 +02:00
AsamK
9da42e27f1 Update workflow actions 2022-10-18 18:11:35 +02:00
AsamK
0aee7ff552 Update graalvm buildtools 2022-10-18 18:11:35 +02:00
AsamK
228713ebb5 Reset pre key offset if it somehow gets corrupted
Fixes #1055
2022-10-18 17:55:51 +02:00
AsamK
266129c61b Update reflect-config 2022-10-18 17:38:46 +02:00
AsamK
3522e43617 Update dependencies 2022-10-16 20:47:51 +02:00
AsamK
45a5795c9c Handle PniChangeNumber 2022-10-16 20:07:33 +02:00
AsamK
94d79692df Update to clap 4 2022-10-09 12:30:25 +02:00
AsamK
58bb4b5358 Update client dependencies 2022-10-09 12:18:43 +02:00
AsamK
51fef48016 Refactor resolve recipient 2022-10-08 17:42:03 +02:00
AsamK
7ab013cee9 Do recipient merge in one transaction 2022-10-08 17:42:03 +02:00
AsamK
f2b334b57a Refactor check for registered users 2022-10-08 17:42:03 +02:00
AsamK
7eb7ee44f2 Refactor RecipientAddress 2022-10-08 17:42:03 +02:00
AsamK
34cc64f8ce Ensure self profile key is always stored in profile store
Fixes #1040
2022-10-07 21:51:01 +02:00
AsamK
ca5951861a Fix issue when receiving invalid message from invalid sender 2022-10-07 21:50:07 +02:00
AsamK
605f31d1ad Improve handling of group join messages 2022-10-07 21:17:45 +02:00
AsamK
30167d81e6 Add missing check for story group context 2022-10-07 21:17:45 +02:00
AsamK
a247b444e5 Fix typo 2022-10-07 21:17:45 +02:00
AsamK
1424a2980f Approve join requests instead of just adding the member 2022-10-07 21:17:45 +02:00
AsamK
01e1115806 Refresh group before updating 2022-10-07 21:17:45 +02:00
AsamK
c9c8af42c2 Implement refuse group join requests 2022-10-07 19:49:43 +02:00
AsamK
489fb2ac22 Improve error message when joining a group with already pending admin approval 2022-10-07 19:31:27 +02:00
AsamK
a708025a16 GraalVM agent formatting changes 2022-10-07 19:30:34 +02:00
AsamK
31429b7faf Bump version 2022-10-07 17:24:29 +02:00
Benjamin Schmid
9c5235c273
fix(GraalVM): explictly declare symbols causing GraalVM compiler failure (#1037)
Fixes #1016
2022-10-07 17:16:32 +02:00
AsamK
8e717e00b1 Add missing urgent column to projection
Fixes #1031
2022-10-07 12:09:43 +02:00
AsamK
9a4693136d Fix update from old versions without PNI
Fixes #1032
2022-10-07 12:04:22 +02:00
AsamK
cf0110ab95 Update libsignal-service
Fixes #1030
2022-10-06 21:56:40 +02:00
AsamK
abc5d5e863 Bump version 2022-10-06 17:59:59 +02:00
technillogue
0c74338c9c Bump user agent version
Closes #1024
2022-10-06 17:57:48 +02:00
AsamK
1dd22132ff Update libsignal-service 2022-10-06 17:55:16 +02:00
AsamK
76c400d2c3 Show better error message when signal-cli version is outdated 2022-10-06 17:55:16 +02:00
ETL
d9472ec19c
Update user agent (#1023)
Recent changes from Signal caused DeprecatedVersionException to occur.

Changing the user agent debugged the receiving (sending still erroring)
2022-10-06 08:03:25 +02:00
364 changed files with 23791 additions and 9909 deletions

View file

@ -1,6 +1,14 @@
name: signal-cli CI
on: [ push, pull_request, workflow_call ]
on:
push:
branches:
- '**'
pull_request:
workflow_call:
permissions:
contents: write # to fetch code (actions/checkout) and submit dependency graph (gradle/gradle-build-action)
jobs:
build:
@ -8,20 +16,81 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
java: [ '17', '18' ]
java: [ '21', '24' ]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v1
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: ${{ matrix.java }}
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
dependency-graph: generate-and-submit
- name: Install asciidoc
run: sudo apt update && sudo apt --no-install-recommends install -y asciidoc-base
- name: Build with Gradle
run: ./gradlew build
run: ./gradlew --no-daemon build
- name: Build man page
run: |
cd man
make install
- name: Add man page to archive
run: |
version=$(tar tf build/distributions/signal-cli-*.tar | head -n1 | sed 's|signal-cli-\([^/]*\)/.*|\1|')
echo $version
tar --transform="flags=r;s|man|signal-cli-${version}/man|" -rf build/distributions/signal-cli-${version}.tar man/man{1,5}
- name: Compress archive
run: gzip -n -9 build/distributions/signal-cli-*.tar
- name: Archive production artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: signal-cli-archive-${{ matrix.java }}
path: build/distributions/signal-cli-*.tar.gz
build-graalvm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: graalvm/setup-graalvm@v1
with:
version: 'latest'
java-version: '21'
cache: 'gradle'
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build with Gradle
run: ./gradlew --no-daemon nativeCompile
- name: Archive production artifacts
uses: actions/upload-artifact@v4
with:
name: signal-cli-native
path: build/native/nativeCompile/signal-cli
build-client:
strategy:
matrix:
os:
- ubuntu
- macos
- windows
runs-on: ${{ matrix.os }}-latest
defaults:
run:
working-directory: ./client
steps:
- uses: actions/checkout@v4
- name: Install rust
run: rustup default stable
- name: Build client
run: cargo build --release --verbose
- name: Archive production artifacts
uses: actions/upload-artifact@v4
with:
name: signal-cli-client-${{ matrix.os }}
path: |
client/target/release/signal-cli-client
client/target/release/signal-cli-client.exe

View file

@ -9,6 +9,10 @@ on:
schedule:
- cron: '0 7 * * 4'
permissions:
contents: read # to fetch code (actions/checkout)
security-events: write
jobs:
analyse:
name: Analyse
@ -17,12 +21,13 @@ jobs:
steps:
- name: Setup Java JDK
uses: actions/setup-java@v1
uses: actions/setup-java@v3
with:
java-version: 17
distribution: 'zulu'
java-version: 21
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
@ -30,7 +35,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v3
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
@ -38,7 +43,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -52,4 +57,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v3

257
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,257 @@
name: release
on:
push:
tags:
- v*
permissions:
contents: write # to fetch code (actions/checkout) and create release
env:
IMAGE_NAME: signal-cli
IMAGE_REGISTRY: ghcr.io/asamk
REGISTRY_USER: ${{ github.actor }}
REGISTRY_PASSWORD: ${{ github.token }}
jobs:
ci_wf:
permissions:
contents: write
uses: AsamK/signal-cli/.github/workflows/ci.yml@master
# ${{ github.repository }} not accepted here
lib_to_jar:
needs: ci_wf
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
signal_cli_version: ${{ steps.cli_ver.outputs.version }}
release_id: ${{ steps.create_release.outputs.id }}
steps:
- name: Download signal-cli build from CI workflow
uses: actions/download-artifact@v4
- name: Get signal-cli version
id: cli_ver
run: |
ver="${GITHUB_REF_NAME#v}"
echo "version=${ver}" >> $GITHUB_OUTPUT
- name: Extract archive
run: |
tree .
ARCHIVE_DIR=$(ls signal-cli-archive-*/ -d | tail -n1)
tar -xzf ./"${ARCHIVE_DIR}"/*.tar.gz
mv ./"${ARCHIVE_DIR}"/*.tar.gz signal-cli-${{ steps.cli_ver.outputs.version }}.tar.gz
rm -rf signal-cli-archive-*/
# - name: Get signal-client jar version
# id: lib_ver
# run: |
# JAR_PREFIX=libsignal-client-
# jar_file=$(find ./signal-cli-*/lib/ -name "$JAR_PREFIX*.jar")
# jar_version=$(echo "$jar_file" | xargs basename | sed "s/$JAR_PREFIX//; s/.jar//")
# echo "$jar_version"
# echo "signal_client_version=${jar_version}" >> $GITHUB_OUTPUT
#
# - name: Download signal-client builds
# env:
# RELEASES_URL: https://github.com/signalapp/libsignal/releases/download/
# FILE_NAMES: signal_jni.dll libsignal_jni.dylib
# SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }}
# run: |
# for file_name in $FILE_NAMES; do
# curl -sOL "${RELEASES_URL}/v${SIGNAL_CLIENT_VER}/${file_name}" # note: added v
# done
# tree .
- name: Compress native app
env:
SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.version }}
run: |
chmod +x signal-cli-native/signal-cli
tar -czf signal-cli-${SIGNAL_CLI_VER}-Linux-native.tar.gz -C signal-cli-native signal-cli
rm -rf signal-cli-native/
# - name: Replace Windows lib
# env:
# SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.version }}
# SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }}
# run: |
# mv signal_jni.dll libsignal_jni.so
# zip -u ./signal-cli-*/lib/libsignal-client-${SIGNAL_CLIENT_VER}.jar ./libsignal_jni.so
# tar -czf signal-cli-${SIGNAL_CLI_VER}-Windows.tar.gz signal-cli-*/
#
# - name: Replace macOS lib
# env:
# SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.version }}
# SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }}
# run: |
# jar_file=./signal-cli-*/lib/libsignal-client-${SIGNAL_CLIENT_VER}.jar
# zip -d $jar_file libsignal_jni.so
# zip $jar_file libsignal_jni.dylib
# tar -czf signal-cli-${SIGNAL_CLI_VER}-macOS.tar.gz signal-cli-*/
- name: Create release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ steps.cli_ver.outputs.version }} # note: added `v`
release_name: v${{ steps.cli_ver.outputs.version }} # note: added `v`
draft: true
- name: Upload archive
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}.tar.gz
asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}.tar.gz
asset_content_type: application/x-compressed-tar # .tar.gz
# - name: Upload Linux archive
# uses: actions/upload-release-asset@v1
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# with:
# upload_url: ${{ steps.create_release.outputs.upload_url }}
# asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux.tar.gz
# asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux.tar.gz
# asset_content_type: application/x-compressed-tar # .tar.gz
- name: Upload Linux native archive
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux-native.tar.gz
asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-Linux-native.tar.gz
asset_content_type: application/x-compressed-tar # .tar.gz
# - name: Upload windows archive
# uses: actions/upload-release-asset@v1
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# with:
# upload_url: ${{ steps.create_release.outputs.upload_url }}
# asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-Windows.tar.gz
# asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-Windows.tar.gz
# asset_content_type: application/x-compressed-tar # .tar.gz
#
# - name: Upload macos archive
# uses: actions/upload-release-asset@v1
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# with:
# upload_url: ${{ steps.create_release.outputs.upload_url }}
# asset_path: signal-cli-${{ steps.cli_ver.outputs.version }}-macOS.tar.gz
# asset_name: signal-cli-${{ steps.cli_ver.outputs.version }}-macOS.tar.gz
# asset_content_type: application/x-compressed-tar # .tar.gz
build-container:
needs: ci_wf
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Download signal-cli build from CI workflow
uses: actions/download-artifact@v4
- name: Get signal-cli version
id: cli_ver
run: |
ver="${GITHUB_REF_NAME#v}"
echo "version=${ver}" >> $GITHUB_OUTPUT
- name: Move archive file
run: |
ARCHIVE_DIR=$(ls signal-cli-archive-*/ -d | tail -n1)
tar xf ./"${ARCHIVE_DIR}"/*.tar.gz
rm -r signal-cli-archive-* signal-cli-native
mkdir -p build/install/
mv ./signal-cli-"${GITHUB_REF_NAME#v}"/ build/install/signal-cli
- name: Build Image
id: build_image
uses: redhat-actions/buildah-build@v2
with:
image: ${{ env.IMAGE_NAME }}
tags: latest ${{ github.sha }} ${{ steps.cli_ver.outputs.version }}
containerfiles:
./Containerfile
oci: true
- name: Push To GHCR
uses: redhat-actions/push-to-registry@v2
id: push
with:
image: ${{ steps.build_image.outputs.image }}
tags: ${{ steps.build_image.outputs.tags }}
registry: ${{ env.IMAGE_REGISTRY }}
username: ${{ env.REGISTRY_USER }}
password: ${{ env.REGISTRY_PASSWORD }}
- name: Echo outputs
run: |
echo "${{ toJSON(steps.push.outputs) }}"
build-container-native:
needs: ci_wf
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Download signal-cli build from CI workflow
uses: actions/download-artifact@v4
- name: Get signal-cli version
id: cli_ver
run: |
ver="${GITHUB_REF_NAME#v}"
echo "version=${ver}" >> $GITHUB_OUTPUT
- name: Move archive file
run: |
mkdir -p build/native/nativeCompile/
chmod +x ./signal-cli-native/signal-cli
mv ./signal-cli-native/signal-cli build/native/nativeCompile/
- name: Build Image
id: build_image
uses: redhat-actions/buildah-build@v2
with:
image: ${{ env.IMAGE_NAME }}
tags: latest-native ${{ github.sha }}-native ${{ steps.cli_ver.outputs.version }}-native
containerfiles:
./native.Containerfile
oci: true
- name: Push To GHCR
uses: redhat-actions/push-to-registry@v2
id: push
with:
image: ${{ steps.build_image.outputs.image }}
tags: ${{ steps.build_image.outputs.tags }}
registry: ${{ env.IMAGE_REGISTRY }}
username: ${{ env.REGISTRY_USER }}
password: ${{ env.REGISTRY_PASSWORD }}
- name: Echo outputs
run: |
echo "${{ toJSON(steps.push.outputs) }}"

View file

@ -1,172 +0,0 @@
name: repackage-native-libs
on:
push:
tags:
- v*
jobs:
ci_wf:
uses: AsamK/signal-cli/.github/workflows/ci.yml@master
# ${{ github.repository }} not accpeted here
lib_to_jar:
needs: ci_wf
runs-on: ubuntu-latest
outputs:
signal_cli_version: ${{ steps.cli_ver.outputs.signal_cli_version }}
release_id: ${{ steps.create_release.outputs.id }}
steps:
- name: Download signal-cli build from CI workflow
uses: actions/download-artifact@v2
- name: Get signal-cli version
id: cli_ver
run: |
#echo ${GITHUB_REF#refs/tag/}
tree .
mv ./$(ls */ -d | tail -n1)/*.tar.gz .
ver=$(ls ./*.tar.gz | xargs basename | sed -E 's/signal-cli-(.*).tar.gz/\1/')
echo $ver
echo "::set-output name=signal_cli_version::${ver}"
tar -xzf ./*.tar.gz
- name: Get signal-client jar version
id: lib_ver
run: |
JAR_PREFIX=libsignal-client-
jar_file=$(find ./signal-cli-*/lib/ -name "$JAR_PREFIX*.jar")
jar_version=$(echo "$jar_file" | xargs basename | sed "s/$JAR_PREFIX//; s/.jar//")
echo "$jar_version"
echo "::set-output name=signal_client_version::$jar_version"
- name: Download signal-client builds
env:
RELEASES_URL: https://github.com/signalapp/libsignal/releases/download/
FILE_NAMES: signal_jni.dll libsignal_jni.dylib
SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }}
run: |
for file_name in $FILE_NAMES; do
curl -sOL "${RELEASES_URL}/v${SIGNAL_CLIENT_VER}/${file_name}" # note: added v
done
tree .
- name: Replace Windows lib
env:
SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.signal_cli_version }}
SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }}
run: |
mv signal_jni.dll libsignal_jni.so
zip -u ./signal-cli-${SIGNAL_CLI_VER}/lib/libsignal-client-${SIGNAL_CLIENT_VER}.jar ./libsignal_jni.so
tar -czf signal-cli-${SIGNAL_CLI_VER}-Windows.tar.gz signal-cli-${SIGNAL_CLI_VER}/
- name: Replace macOS lib
env:
SIGNAL_CLI_VER: ${{ steps.cli_ver.outputs.signal_cli_version }}
SIGNAL_CLIENT_VER: ${{ steps.lib_ver.outputs.signal_client_version }}
run: |
jar_file=./signal-cli-${SIGNAL_CLI_VER}/lib/libsignal-client-${SIGNAL_CLIENT_VER}.jar
zip -d "$jar_file" libsignal_jni.so
zip "$jar_file" libsignal_jni.dylib
tar -czf signal-cli-${SIGNAL_CLI_VER}-macOS.tar.gz signal-cli-${SIGNAL_CLI_VER}/
- name: Create release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ steps.cli_ver.outputs.signal_cli_version }} # note: added `v`
release_name: v${{ steps.cli_ver.outputs.signal_cli_version }} # note: added `v`
draft: true
- name: Upload Linux archive
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}.tar.gz
asset_name: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}-Linux.tar.gz
asset_content_type: application/x-compressed-tar # .tar.gz
- name: Upload windows archive
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}-Windows.tar.gz
asset_name: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}-Windows.tar.gz
asset_content_type: application/x-compressed-tar # .tar.gz
- name: Upload macos archive
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}-macOS.tar.gz
asset_name: signal-cli-${{ steps.cli_ver.outputs.signal_cli_version }}-macOS.tar.gz
asset_content_type: application/x-compressed-tar # .tar.gz
run_repackaged:
needs:
- lib_to_jar
strategy:
matrix:
runner:
- windows-latest
- macos-latest
runs-on: ${{ matrix.runner }}
defaults:
run:
shell: bash # Explicit for windows
env:
JAVA_VERSION: 18
steps:
- name: Download the release file
env:
SIGNAL_CLI_VER: ${{ needs.lib_to_jar.outputs.signal_cli_version }}
RELEASE_ID: ${{ needs.lib_to_jar.outputs.release_id }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
file_name=signal-cli-${SIGNAL_CLI_VER}-${RUNNER_OS}.tar.gz
echo "$file_name"
assets_json=$(curl -s \
-H "Authorization: Bearer $GITHUB_TOKEN" \
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets")
asset_dl_url=$(echo "$assets_json" | jq -r ".[] | select (.name == \"$file_name\") | .url")
echo "$asset_dl_url"
curl -sLOJ \
-H 'Accept: application/octet-stream' \
-H "Authorization: Bearer $GITHUB_TOKEN" \
"$asset_dl_url"
tar -xzf "$file_name"
- name: Set up JDK for running signal-cli executable
uses: actions/setup-java@v1
with:
java-version: ${{ env.JAVA_VERSION }}
- name: Run signal-cli
run: |
cd signal-cli-*/bin
if [[ "$RUNNER_OS" == 'Windows' ]]; then
EXECUTABLE_SUFFIX=".bat"
fi
./signal-cli${EXECUTABLE_SUFFIX} listAccounts

View file

@ -5,8 +5,8 @@
<option name="GENERATE_FINAL_LOCALS" value="true" />
<option name="GENERATE_FINAL_PARAMETERS" value="true" />
<option name="ALIGN_MULTILINE_TEXT_BLOCKS" value="true" />
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="50" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="50" />
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="com" withSubpackages="true" static="false" />
@ -54,6 +54,9 @@
<option name="TERNARY_OPERATION_WRAP" value="5" />
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
<option name="ARRAY_INITIALIZER_WRAP" value="5" />
<option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="true" />
<option name="ARRAY_INITIALIZER_RBRACE_ON_NEXT_LINE" value="true" />
<option name="ENUM_CONSTANTS_WRAP" value="2" />
</codeStyleSettings>
<codeStyleSettings language="XML">

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,18 @@
# Question
If you have a question you can ask it in the [GitHub discussions page](https://github.com/AsamK/signal-cli/discussions)
# Report a bug
- Search [existing issues](https://github.com/AsamK/signal-cli/issues?q=is%3Aissue) if it has been reported already
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/AsamK/signal-cli/issues/new).
- Be sure to include a **title and clear description**, as much relevant information as possible.
- Specify the signal-cli, JDK and OS version you're using. (and libsignal-client version, if self-compiled)
- Run the failing command with `--verbose` flag to get a more detailed log output and include that in the bug report
- If you're unable to find an open issue addressing the
problem, [open a new one](https://github.com/AsamK/signal-cli/issues/new).
- Be sure to include a **title and clear description**, as much relevant information as possible.
- Specify the versions of signal-cli, libsignal-client (if self-compiled), JDK and OS you're using
- Specify if it's the normal java or the graalvm native version.
- Run the failing command with `--verbose` flag to get a more detailed log output and include that in the bug report
# Pull request
- Code style should match the existing code, IntelliJ users can use the auto formatter
- Separate PRs should be opened for each implemented feature or bug fix

11
Containerfile Normal file
View file

@ -0,0 +1,11 @@
FROM docker.io/azul/zulu-openjdk:21-jre-headless
LABEL org.opencontainers.image.source=https://github.com/AsamK/signal-cli
LABEL org.opencontainers.image.description="signal-cli provides an unofficial commandline, dbus and JSON-RPC interface for the Signal messenger."
LABEL org.opencontainers.image.licenses=GPL-3.0-only
RUN useradd signal-cli --system --create-home --home-dir /var/lib/signal-cli
ADD build/install/signal-cli /opt/signal-cli
USER signal-cli
ENTRYPOINT ["/opt/signal-cli/bin/signal-cli", "--config=/var/lib/signal-cli"]

View file

@ -1,15 +1,19 @@
# signal-cli
signal-cli is a commandline interface
for [libsignal-service-java](https://github.com/WhisperSystems/libsignal-service-java). It supports registering,
verifying, sending and receiving messages. To be able to link to an existing Signal-Android/signal-cli instance,
signal-cli uses a [patched libsignal-service-java](https://github.com/AsamK/libsignal-service-java), because
libsignal-service-java does not yet
support [provisioning as a linked device](https://github.com/WhisperSystems/libsignal-service-java/pull/21). For
registering you need a phone number where you can receive SMS or incoming calls.
signal-cli is a commandline interface for the [Signal messenger](https://signal.org/).
It supports registering, verifying, sending and receiving messages.
signal-cli uses a [patched libsignal-service-java](https://github.com/Turasa/libsignal-service-java),
extracted from the [Signal-Android source code](https://github.com/signalapp/Signal-Android/tree/main/libsignal-service).
For registering you need a phone number where you can receive SMS or incoming calls.
signal-cli is primarily intended to be used on servers to notify admins of important events. For this use-case, it has a daemon mode with D-BUS
interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-dbus.5.adoc)) and JSON-RPC interface ([documentation](https://github.com/AsamK/signal-cli/wiki/JSON-RPC-service)). For the JSON-RPC interface there's also a simple [example client](https://github.com/AsamK/signal-cli/tree/master/client), written in Rust.
signal-cli is primarily intended to be used on servers to notify admins of important events.
For this use-case, it has a daemon mode with JSON-RPC interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-jsonrpc.5.adoc))
and D-BUS interface ([man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli-dbus.5.adoc)) .
For the JSON-RPC interface there's also a simple [example client](https://github.com/AsamK/signal-cli/tree/master/client), written in Rust.
signal-cli needs to be kept up-to-date to keep up with Signal-Server changes.
The official Signal clients expire after three months and then the Signal-Server can make incompatible changes.
So signal-cli releases older than three months may not work correctly.
## Installation
@ -19,7 +23,7 @@ Windows. There's also a [docker image and some Linux packages](https://github.co
System requirements:
- at least Java Runtime Environment (JRE) 17
- at least Java Runtime Environment (JRE) 21
- native library: libsignal-client
The native libs are bundled for x86_64 Linux (with recent enough glibc), Windows and MacOS. For other
@ -32,15 +36,14 @@ See [latest version](https://github.com/AsamK/signal-cli/releases).
```sh
export VERSION=<latest version, format "x.y.z">
wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}"-Linux.tar.gz
sudo tar xf signal-cli-"${VERSION}"-Linux.tar.gz -C /opt
wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz
sudo tar xf signal-cli-"${VERSION}".tar.gz -C /opt
sudo ln -sf /opt/signal-cli-"${VERSION}"/bin/signal-cli /usr/local/bin/
```
You can find further instructions on the Wiki:
- [Quickstart](https://github.com/AsamK/signal-cli/wiki/Quickstart)
- [DBus Service](https://github.com/AsamK/signal-cli/wiki/DBus-service)
## Usage
@ -54,10 +57,17 @@ of all country codes.)
* Register a number (with SMS verification)
signal-cli -a ACCOUNT register
signal-cli -a ACCOUNT register
You can register Signal using a land line number. In this case you can skip SMS verification process and jump directly
to the voice call verification by adding the `--voice` switch at the end of above register command.
You can register Signal using a landline number. In this case, you need to follow the procedure below:
* Attempt a SMS verification process first (`signal-cli -a ACCOUNT register`)
* You will get an error `400 (InvalidTransportModeException)`, this is normal
* Wait 60 seconds
* Attempt a voice call verification by adding the `--voice` switch and wait for the call:
```sh
signal-cli -a ACCOUNT register --voice
```
Registering may require solving a CAPTCHA
challenge: [Registration with captcha](https://github.com/AsamK/signal-cli/wiki/Registration-with-captcha)
@ -65,19 +75,27 @@ of all country codes.)
* Verify the number using the code received via SMS or voice, optionally add `--pin PIN_CODE` if you've added a pin code
to your account
signal-cli -a ACCOUNT verify CODE
signal-cli -a ACCOUNT verify CODE
* Send a message
signal-cli -a ACCOUNT send -m "This is a message" RECIPIENT
```sh
signal-cli -a ACCOUNT send -m "This is a message" RECIPIENT
```
* Send a message to a username, usernames need to be prefixed with `u:`
```sh
signal-cli -a ACCOUNT send -m "This is a message" u:USERNAME.000
```
* Pipe the message content from another process.
uname -a | signal-cli -a ACCOUNT send --message-from-stdin RECIPIENT
uname -a | signal-cli -a ACCOUNT send --message-from-stdin RECIPIENT
* Receive messages
signal-cli -a ACCOUNT receive
signal-cli -a ACCOUNT receive
**Hint**: The Signal protocol expects that incoming messages are regularly received (using `daemon` or `receive`
command). This is required for the encryption to work efficiently and for getting updates to groups, expiration timer
@ -87,8 +105,8 @@ and other features.
The password and cryptographic keys are created when registering and stored in the current users home directory:
$XDG_DATA_HOME/signal-cli/data/
$HOME/.local/share/signal-cli/data/
$XDG_DATA_HOME/signal-cli/data/
$HOME/.local/share/signal-cli/data/
## Building
@ -97,44 +115,45 @@ version installed, you can replace `./gradlew` with `gradle` in the following st
1. Checkout the source somewhere on your filesystem with
git clone https://github.com/AsamK/signal-cli.git
git clone https://github.com/AsamK/signal-cli.git
2. Execute Gradle:
./gradlew build
./gradlew build
2a. Create shell wrapper in *build/install/signal-cli/bin*:
./gradlew installDist
./gradlew installDist
2b. Create tar file in *build/distributions*:
./gradlew distTar
./gradlew distTar
2c. Create a fat tar file in *build/libs/signal-cli-fat*:
./gradlew fatJar
./gradlew fatJar
2d. Compile and run signal-cli:
./gradlew run --args="--help"
```sh
./gradlew run --args="--help"
```
### Building a native binary with GraalVM (EXPERIMENTAL)
It is possible to build a native binary with [GraalVM](https://www.graalvm.org). This is still experimental and will not
work in all situations.
1. [Install GraalVM and setup the enviroment](https://www.graalvm.org/docs/getting-started/#install-graalvm)
2. [Install prerequisites](https://www.graalvm.org/reference-manual/native-image/#prerequisites)
3. Execute Gradle:
1. [Install GraalVM and setup the environment](https://www.graalvm.org/docs/getting-started/#install-graalvm)
2. Execute Gradle:
./gradlew nativeCompile
./gradlew nativeCompile
The binary is available at *build/native/nativeCompile/signal-cli*
## FAQ and Troubleshooting
For frequently asked questions and issues have a look at the [wiki](https://github.com/AsamK/signal-cli/wiki/FAQ)
For frequently asked questions and issues have a look at the [wiki](https://github.com/AsamK/signal-cli/wiki/FAQ).
## License

View file

@ -3,43 +3,94 @@ plugins {
application
eclipse
`check-lib-versions`
id("org.graalvm.buildtools.native") version "0.9.14"
id("org.graalvm.buildtools.native") version "0.10.6"
}
version = "0.11.1"
allprojects {
group = "org.asamk"
version = "0.13.19-SNAPSHOT"
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
if (!JavaVersion.current().isCompatibleWith(targetCompatibility)) {
toolchain {
languageVersion.set(JavaLanguageVersion.of(targetCompatibility.majorVersion))
}
}
}
application {
mainClass.set("org.asamk.signal.Main")
applicationDefaultJvmArgs = listOf("--enable-native-access=ALL-UNNAMED")
}
graalvmNative {
binaries {
this["main"].run {
buildArgs.add("--install-exit-handlers")
buildArgs.add("-Dfile.encoding=UTF-8")
buildArgs.add("-J-Dfile.encoding=UTF-8")
buildArgs.add("-march=compatibility")
resources.autodetect()
configurationFileDirectories.from(file("graalvm-config-dir"))
buildArgs.add("--report-unsupported-elements-at-runtime")
if (System.getenv("GRAALVM_HOME") == null) {
toolchainDetection.set(true)
javaLauncher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(21))
})
} else {
toolchainDetection.set(false)
}
}
}
}
repositories {
mavenLocal()
mavenCentral()
val artifactType = Attribute.of("artifactType", String::class.java)
val minified = Attribute.of("minified", Boolean::class.javaObjectType)
dependencies {
attributesSchema {
attribute(minified)
}
artifactTypes.getByName("jar") {
attributes.attribute(minified, false)
}
}
configurations.runtimeClasspath.configure {
attributes {
attribute(minified, true)
}
}
val excludePatterns = mapOf(
"libsignal-client" to setOf(
"libsignal_jni_testing_amd64.so",
"signal_jni_testing_amd64.dll",
"libsignal_jni_testing_amd64.dylib",
"libsignal_jni_testing_aarch64.dylib",
)
)
dependencies {
implementation("org.bouncycastle", "bcprov-jdk15on", "1.70")
implementation("com.fasterxml.jackson.core", "jackson-databind", "2.13.4")
implementation("net.sourceforge.argparse4j", "argparse4j", "0.9.0")
implementation("com.github.hypfvieh", "dbus-java-transport-native-unixsocket", "4.2.1")
implementation("org.slf4j", "slf4j-api", "2.0.3")
implementation("ch.qos.logback", "logback-classic", "1.4.3")
implementation("org.slf4j", "jul-to-slf4j", "2.0.3")
implementation(project(":lib"))
registerTransform(JarFileExcluder::class) {
from.attribute(minified, false).attribute(artifactType, "jar")
to.attribute(minified, true).attribute(artifactType, "jar")
parameters {
excludeFilesByArtifact = excludePatterns
}
}
implementation(libs.bouncycastle)
implementation(libs.jackson.databind)
implementation(libs.argparse4j)
implementation(libs.dbusjava)
implementation(libs.slf4j.api)
implementation(libs.slf4j.jul)
implementation(libs.logback)
implementation(project(":libsignal-cli"))
}
configurations {
@ -63,12 +114,13 @@ tasks.withType<Jar> {
attributes(
"Implementation-Title" to project.name,
"Implementation-Version" to project.version,
"Main-Class" to application.mainClass.get()
"Main-Class" to application.mainClass.get(),
"Enable-Native-Access" to "ALL-UNNAMED",
)
}
}
task("fatJar", type = Jar::class) {
tasks.register("fatJar", type = Jar::class) {
archiveBaseName.set("${project.name}-fat")
exclude(
"META-INF/*.SF",
@ -77,9 +129,11 @@ task("fatJar", type = Jar::class) {
"META-INF/NOTICE*",
"META-INF/LICENSE*",
"META-INF/INDEX.LIST",
"**/module-info.class"
"**/module-info.class",
)
duplicatesStrategy = DuplicatesStrategy.WARN
from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
doFirst {
from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
}
with(tasks.jar.get())
}

View file

@ -1,7 +1,19 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
plugins {
`kotlin-dsl`
}
tasks.named<KotlinCompilationTask<KotlinJvmCompilerOptions>>("compileKotlin").configure {
compilerOptions.jvmTarget.set(JvmTarget.JVM_17)
}
java {
targetCompatibility = JavaVersion.VERSION_17
}
repositories {
mavenCentral()
}

View file

@ -1,10 +1,10 @@
import groovy.util.XmlSlurper
import groovy.util.slurpersupport.GPathResult
import org.codehaus.groovy.runtime.ResourceGroovyMethods
@file:Suppress("DEPRECATION")
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Dependency
import javax.xml.parsers.DocumentBuilderFactory
class CheckLibVersionsPlugin : Plugin<Project> {
override fun apply(project: Project) {
@ -26,15 +26,15 @@ class CheckLibVersionsPlugin : Plugin<Project> {
val name = dependency.name
val metaDataUrl = "https://repo1.maven.org/maven2/$path/$name/maven-metadata.xml"
try {
val url = ResourceGroovyMethods.toURL(metaDataUrl)
val metaDataText = ResourceGroovyMethods.getText(url)
val metadata = XmlSlurper().parseText(metaDataText)
val newest = (metadata.getProperty("versioning") as GPathResult).getProperty("latest")
val dbf = DocumentBuilderFactory.newInstance()
val db = dbf.newDocumentBuilder()
val doc = db.parse(metaDataUrl);
val newest = doc.getElementsByTagName("latest").item(0).textContent
if (version != newest.toString()) {
println("UPGRADE {\"group\": \"$group\", \"name\": \"$name\", \"current\": \"$version\", \"latest\": \"$newest\"}")
}
} catch (e: Throwable) {
logger.debug("Unable to download or parse $metaDataUrl: $e.message")
logger.debug("Unable to download or parse {}: {}", metaDataUrl, e.message)
}
}
}

View file

@ -0,0 +1,53 @@
import org.gradle.api.artifacts.transform.*
import org.gradle.api.file.FileSystemLocation
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
@CacheableTransform
abstract class JarFileExcluder : TransformAction<JarFileExcluder.Parameters> {
interface Parameters : TransformParameters {
@get:Input
var excludeFilesByArtifact: Map<String, Set<String>>
}
@get:PathSensitive(PathSensitivity.NAME_ONLY)
@get:InputArtifact
abstract val inputArtifact: Provider<FileSystemLocation>
override
fun transform(outputs: TransformOutputs) {
val fileName = inputArtifact.get().asFile.name
for (entry in parameters.excludeFilesByArtifact) {
if (fileName.startsWith(entry.key)) {
val nameWithoutExtension = fileName.substring(0, fileName.lastIndexOf("."))
excludeFiles(inputArtifact.get().asFile, entry.value, outputs.file("${nameWithoutExtension}.jar"))
return
}
}
outputs.file(inputArtifact)
}
private fun excludeFiles(artifact: File, excludeFiles: Set<String>, jarFile: File) {
ZipInputStream(FileInputStream(artifact)).use { input ->
ZipOutputStream(FileOutputStream(jarFile)).use { output ->
var entry = input.nextEntry
while (entry != null) {
if (!excludeFiles.contains(entry.name)) {
output.putNextEntry(entry)
input.copyTo(output)
output.closeEntry()
}
entry = input.nextEntry
}
}
}
}
}

1788
client/Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,25 +1,22 @@
[package]
name = "signal-cli-client"
version = "0.0.1"
edition = "2021"
edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1"
clap = { version = "3", features = ["cargo", "derive"] }
jsonrpc-core = "18"
jsonrpc-core-client = "18"
jsonrpc-client-transports = { version = "18", default-features = false, features = [
"ipc",
] }
jsonrpc-derive = "18"
jsonrpc-server-utils = "18"
log = "0.4"
clap = { version = "4", features = ["cargo", "derive", "wrap_help"] }
serde = "1"
serde_json = "1"
tokio = { version = "1", features = ["rt", "macros", "net"] }
[patch.crates-io]
jsonrpc-client-transports = { git = "https://github.com/AsamK/jsonrpc", branch = "client_subscribe_named_params" }
jsonrpc-derive = { git = "https://github.com/AsamK/jsonrpc", branch = "client_subscribe_named_params" }
tokio = { version = "1", features = ["rt", "macros", "net", "rt-multi-thread"] }
jsonrpsee = { version = "0.25", features = [
"macros",
"async-client",
"http-client",
] }
bytes = "1"
tokio-util = "0.7"
futures-util = "0.3"
thiserror = "2"

View file

@ -1,221 +1,306 @@
use clap::{crate_version, ArgEnum, Parser, Subcommand};
use std::{ffi::OsString, net::SocketAddr};
use clap::{crate_version, Parser, Subcommand, ValueEnum};
/// JSON-RPC client for signal-cli
#[derive(Parser, Debug)]
#[clap(rename_all = "kebab-case", version=crate_version!())]
#[command(rename_all = "kebab-case", version = crate_version!())]
pub struct Cli {
/// Account to use (for daemon in multi-account mode)
#[clap(short = 'a', long)]
#[arg(short = 'a', long)]
pub account: Option<String>,
/// TCP host and port of signal-cli daemon
#[clap(long)]
#[arg(long, conflicts_with = "json_rpc_http")]
pub json_rpc_tcp: Option<Option<SocketAddr>>,
/// UNIX socket address and port of signal-cli daemon
#[clap(long)]
#[cfg(unix)]
#[arg(long, conflicts_with = "json_rpc_tcp")]
pub json_rpc_socket: Option<Option<OsString>>,
#[clap(arg_enum, long, default_value_t = OutputTypes::Json)]
pub output: OutputTypes,
/// HTTP URL of signal-cli daemon
#[arg(long, conflicts_with = "json_rpc_socket")]
pub json_rpc_http: Option<Option<String>>,
#[clap(long)]
#[arg(long)]
pub verbose: bool,
#[clap(subcommand)]
#[command(subcommand)]
pub command: CliCommands,
}
#[derive(ArgEnum, Clone, Debug)]
#[clap(rename_all = "kebab-case")]
pub enum OutputTypes {
PlainText,
Json,
}
#[allow(clippy::large_enum_variant)]
#[derive(Subcommand, Debug)]
#[clap(rename_all = "camelCase", version=crate_version!())]
#[command(rename_all = "camelCase", version = crate_version!())]
pub enum CliCommands {
AddDevice {
#[clap(long)]
#[arg(long)]
uri: String,
},
#[clap(rename_all = "kebab-case")]
AddStickerPack {
#[arg(long)]
uri: String,
},
#[command(rename_all = "kebab-case")]
Block {
recipient: Vec<String>,
#[clap(short = 'g', long)]
#[arg(short = 'g', long)]
group_id: Vec<String>,
},
DeleteLocalAccountData {
#[clap(long = "ignore-registered")]
#[arg(long = "ignore-registered")]
ignore_registered: Option<bool>,
},
FinishChangeNumber {
number: String,
#[arg(short = 'v', long = "verification-code")]
verification_code: String,
#[arg(short = 'p', long)]
pin: Option<String>,
},
GetAttachment {
#[arg(long)]
id: String,
#[arg(long)]
recipient: Option<String>,
#[arg(short = 'g', long = "group-id")]
group_id: Option<String>,
},
GetAvatar {
#[arg(long)]
contact: Option<String>,
#[arg(long)]
profile: Option<String>,
#[arg(short = 'g', long = "group-id")]
group_id: Option<String>,
},
GetSticker {
#[arg(long = "pack-id")]
pack_id: String,
#[arg(long = "sticker-id")]
sticker_id: u32,
},
GetUserStatus {
recipient: Vec<String>,
#[arg(long)]
username: Vec<String>,
},
JoinGroup {
#[clap(long)]
#[arg(long)]
uri: String,
},
Link {
#[clap(short = 'n', long)]
#[arg(short = 'n', long)]
name: String,
},
ListAccounts,
ListContacts {
recipient: Vec<String>,
#[clap(short = 'a', long = "all-recipients")]
#[arg(short = 'a', long = "all-recipients")]
all_recipients: bool,
#[clap(long, parse(try_from_str))]
#[arg(long)]
blocked: Option<bool>,
#[clap(long)]
#[arg(long)]
name: Option<String>,
},
ListDevices,
ListGroups {
#[clap(short = 'd', long)]
#[arg(short = 'd', long)]
detailed: bool,
#[clap(short = 'g', long = "group-id")]
#[arg(short = 'g', long = "group-id")]
group_id: Vec<String>,
},
ListIdentities {
#[clap(short = 'n', long)]
#[arg(short = 'n', long)]
number: Option<String>,
},
ListStickerPacks,
QuitGroup {
#[clap(short = 'g', long = "group-id")]
#[arg(short = 'g', long = "group-id")]
group_id: String,
#[clap(long)]
#[arg(long)]
delete: bool,
#[clap(long)]
#[arg(long)]
admin: Vec<String>,
},
Receive {
#[clap(short = 't', long, default_value_t = 3.0)]
#[arg(short = 't', long, default_value_t = 3.0)]
timeout: f64,
},
Register {
#[clap(short = 'v', long)]
#[arg(short = 'v', long)]
voice: bool,
#[clap(long)]
#[arg(long)]
captcha: Option<String>,
},
RemoveContact {
recipient: String,
#[clap(long)]
#[arg(long)]
forget: bool,
#[arg(long)]
hide: bool,
},
RemoveDevice {
#[clap(short = 'd', long = "device-id")]
#[arg(short = 'd', long = "device-id")]
device_id: u32,
},
RemovePin,
RemoteDelete {
#[clap(short = 't', long = "target-timestamp")]
#[arg(short = 't', long = "target-timestamp")]
target_timestamp: u64,
recipient: Vec<String>,
#[clap(short = 'g', long = "group-id")]
#[arg(short = 'g', long = "group-id")]
group_id: Vec<String>,
#[clap(long = "note-to-self")]
#[arg(long = "note-to-self")]
note_to_self: bool,
},
#[clap(rename_all = "kebab-case")]
#[command(rename_all = "kebab-case")]
Send {
recipient: Vec<String>,
#[clap(short = 'g', long)]
#[arg(short = 'g', long)]
group_id: Vec<String>,
#[clap(long)]
#[arg(long)]
note_to_self: bool,
#[clap(short = 'e', long)]
#[arg(short = 'e', long)]
end_session: bool,
#[clap(short = 'm', long)]
#[arg(short = 'm', long)]
message: Option<String>,
#[clap(short = 'a', long)]
#[arg(short = 'a', long)]
attachment: Vec<String>,
#[clap(long)]
#[arg(long)]
view_once: bool,
#[arg(long)]
mention: Vec<String>,
#[clap(long)]
#[arg(long)]
text_style: Vec<String>,
#[arg(long)]
quote_timestamp: Option<u64>,
#[clap(long)]
#[arg(long)]
quote_author: Option<String>,
#[clap(long)]
#[arg(long)]
quote_message: Option<String>,
#[clap(long)]
#[arg(long)]
quote_mention: Vec<String>,
#[clap(long)]
#[arg(long)]
quote_text_style: Vec<String>,
#[arg(long)]
quote_attachment: Vec<String>,
#[arg(long)]
preview_url: Option<String>,
#[arg(long)]
preview_title: Option<String>,
#[arg(long)]
preview_description: Option<String>,
#[arg(long)]
preview_image: Option<String>,
#[arg(long)]
sticker: Option<String>,
#[arg(long)]
story_timestamp: Option<u64>,
#[arg(long)]
story_author: Option<String>,
#[arg(long)]
edit_timestamp: Option<u64>,
},
SendContacts,
SendPaymentNotification {
recipient: String,
#[clap(long)]
#[arg(long)]
receipt: String,
#[clap(long)]
#[arg(long)]
note: String,
},
SendReaction {
recipient: Vec<String>,
#[clap(short = 'g', long = "group-id")]
#[arg(short = 'g', long = "group-id")]
group_id: Vec<String>,
#[clap(long = "note-to-self")]
#[arg(long = "note-to-self")]
note_to_self: bool,
#[clap(short = 'e', long)]
#[arg(short = 'e', long)]
emoji: String,
#[clap(short = 'a', long = "target-author")]
#[arg(short = 'a', long = "target-author")]
target_author: String,
#[clap(short = 't', long = "target-timestamp")]
#[arg(short = 't', long = "target-timestamp")]
target_timestamp: u64,
#[clap(short = 'r', long)]
#[arg(short = 'r', long)]
remove: bool,
#[arg(long)]
story: bool,
},
SendReceipt {
recipient: String,
#[clap(short = 't', long = "target-timestamp")]
#[arg(short = 't', long = "target-timestamp")]
target_timestamp: Vec<u64>,
#[clap(arg_enum, long)]
#[arg(value_enum, long)]
r#type: ReceiptType,
},
SendSyncRequest,
SendTyping {
recipient: Vec<String>,
#[clap(short = 'g', long = "group-id")]
#[arg(short = 'g', long = "group-id")]
group_id: Vec<String>,
#[clap(short = 's', long)]
#[arg(short = 's', long)]
stop: bool,
},
SendMessageRequestResponse {
recipient: Vec<String>,
#[arg(short = 'g', long = "group-id")]
group_id: Vec<String>,
r#type: MessageRequestResponseType,
},
SetPin {
pin: String,
},
StartChangeNumber {
number: String,
#[arg(short = 'v', long)]
voice: bool,
#[arg(long)]
captcha: Option<String>,
},
SubmitRateLimitChallenge {
challenge: String,
captcha: String,
@ -223,118 +308,124 @@ pub enum CliCommands {
Trust {
recipient: String,
#[clap(short = 'a', long = "trust-all-known-keys")]
#[arg(short = 'a', long = "trust-all-known-keys")]
trust_all_known_keys: bool,
#[clap(short = 'v', long = "verified-safety-number")]
#[arg(short = 'v', long = "verified-safety-number")]
verified_safety_number: Option<String>,
},
#[clap(rename_all = "kebab-case")]
#[command(rename_all = "kebab-case")]
Unblock {
recipient: Vec<String>,
#[clap(short = 'g', long)]
#[arg(short = 'g', long)]
group_id: Vec<String>,
},
Unregister {
#[clap(long = "delete-account")]
#[arg(long = "delete-account")]
delete_account: bool,
},
UpdateAccount {
#[clap(short = 'n', long = "device-name")]
#[arg(short = 'n', long = "device-name")]
device_name: Option<String>,
#[arg(long = "unrestricted-unidentified-sender")]
unrestricted_unidentified_sender: Option<bool>,
#[arg(long = "discoverable-by-number")]
discoverable_by_number: Option<bool>,
#[arg(long = "number-sharing")]
number_sharing: Option<bool>,
},
UpdateConfiguration {
#[clap(long = "read-receipts", parse(try_from_str))]
#[arg(long = "read-receipts")]
read_receipts: Option<bool>,
#[clap(long = "unidentified-delivery-indicators", parse(try_from_str))]
#[arg(long = "unidentified-delivery-indicators")]
unidentified_delivery_indicators: Option<bool>,
#[clap(long = "typing-indicators", parse(try_from_str))]
#[arg(long = "typing-indicators")]
typing_indicators: Option<bool>,
#[clap(long = "link-previews", parse(try_from_str))]
#[arg(long = "link-previews")]
link_previews: Option<bool>,
},
UpdateContact {
recipient: String,
#[clap(short = 'e', long)]
#[arg(short = 'e', long)]
expiration: Option<u32>,
#[clap(short = 'n', long)]
#[arg(short = 'n', long)]
name: Option<String>,
},
UpdateGroup {
#[clap(short = 'g', long = "group-id")]
#[arg(short = 'g', long = "group-id")]
group_id: Option<String>,
#[clap(short = 'n', long)]
#[arg(short = 'n', long)]
name: Option<String>,
#[clap(short = 'd', long)]
#[arg(short = 'd', long)]
description: Option<String>,
#[clap(short = 'a', long)]
#[arg(short = 'a', long)]
avatar: Option<String>,
#[clap(short = 'm', long)]
#[arg(short = 'm', long)]
member: Vec<String>,
#[clap(short = 'r', long = "remove-member")]
#[arg(short = 'r', long = "remove-member")]
remove_member: Vec<String>,
#[clap(long)]
#[arg(long)]
admin: Vec<String>,
#[clap(long = "remove-admin")]
#[arg(long = "remove-admin")]
remove_admin: Vec<String>,
#[clap(long)]
#[arg(long)]
ban: Vec<String>,
#[clap(long)]
#[arg(long)]
unban: Vec<String>,
#[clap(long = "reset-link")]
#[arg(long = "reset-link")]
reset_link: bool,
#[clap(arg_enum, long)]
#[arg(value_enum, long)]
link: Option<LinkState>,
#[clap(arg_enum, long = "set-permission-add-member")]
#[arg(value_enum, long = "set-permission-add-member")]
set_permission_add_member: Option<GroupPermission>,
#[clap(arg_enum, long = "set-permission-edit-details")]
#[arg(value_enum, long = "set-permission-edit-details")]
set_permission_edit_details: Option<GroupPermission>,
#[clap(arg_enum, long = "set-permission-send-messages")]
#[arg(value_enum, long = "set-permission-send-messages")]
set_permission_send_messages: Option<GroupPermission>,
#[clap(short = 'e', long)]
#[arg(short = 'e', long)]
expiration: Option<u32>,
},
UpdateProfile {
#[clap(long = "given-name")]
#[arg(long = "given-name")]
given_name: Option<String>,
#[clap(long = "family-name")]
#[arg(long = "family-name")]
family_name: Option<String>,
#[clap(long)]
#[arg(long)]
about: Option<String>,
#[clap(long = "about-emoji")]
#[arg(long = "about-emoji")]
about_emoji: Option<String>,
#[clap(long = "mobile-coin-address")]
#[arg(long = "mobile-coin-address", visible_alias = "mobilecoin-address")]
mobile_coin_address: Option<String>,
#[clap(long)]
#[arg(long)]
avatar: Option<String>,
#[clap(long = "remove-avatar")]
#[arg(long = "remove-avatar")]
remove_avatar: bool,
},
UploadStickerPack {
@ -343,30 +434,37 @@ pub enum CliCommands {
Verify {
verification_code: String,
#[clap(short = 'p', long)]
#[arg(short = 'p', long)]
pin: Option<String>,
},
Version,
}
#[derive(ArgEnum, Clone, Debug)]
#[clap(rename_all = "kebab-case")]
#[derive(ValueEnum, Clone, Debug)]
#[value(rename_all = "kebab-case")]
pub enum ReceiptType {
Read,
Viewed,
}
#[derive(ArgEnum, Clone, Debug)]
#[clap(rename_all = "kebab-case")]
#[derive(ValueEnum, Clone, Debug)]
#[value(rename_all = "kebab-case")]
pub enum LinkState {
Enabled,
EnabledWithApproval,
Disabled,
}
#[derive(ArgEnum, Clone, Debug)]
#[clap(rename_all = "kebab-case")]
#[derive(ValueEnum, Clone, Debug)]
#[value(rename_all = "kebab-case")]
pub enum GroupPermission {
EveryMember,
OnlyAdmins,
}
#[derive(ValueEnum, Clone, Debug)]
#[value(rename_all = "kebab-case")]
pub enum MessageRequestResponseType {
Accept,
Delete,
}

View file

@ -1,49 +1,102 @@
use std::path::Path;
use jsonrpc_client_transports::{transports::ipc, RpcError};
use jsonrpc_core::serde::Deserialize;
use jsonrpc_derive::rpc;
use jsonrpsee::async_client::ClientBuilder;
use jsonrpsee::core::client::{Error, SubscriptionClientT};
use jsonrpsee::http_client::HttpClientBuilder;
use jsonrpsee::proc_macros::rpc;
use serde::Deserialize;
use serde_json::Value;
use tokio::net::ToSocketAddrs;
pub type SignalCliClient = gen_client::Client;
#[rpc(client, params = "named")]
#[rpc(client)]
pub trait Rpc {
#[rpc(name = "addDevice", params = "named")]
fn add_device(&self, account: Option<String>, uri: String) -> Result<Value>;
#[method(name = "addDevice", param_kind = map)]
async fn add_device(
&self,
account: Option<String>,
uri: String,
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "block", params = "named")]
#[method(name = "addStickerPack", param_kind = map)]
async fn add_sticker_pack(
&self,
account: Option<String>,
uri: String,
) -> Result<Value, ErrorObjectOwned>;
#[method(name = "block", param_kind = map)]
fn block(
&self,
account: Option<String>,
recipients: Vec<String>,
#[allow(non_snake_case)] groupIds: Vec<String>,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "deleteLocalAccountData", params = "named")]
#[method(name = "deleteLocalAccountData", param_kind = map)]
fn delete_local_account_data(
&self,
account: Option<String>,
#[allow(non_snake_case)] ignoreRegistered: Option<bool>,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "getUserStatus", params = "named")]
fn get_user_status(&self, account: Option<String>, recipients: Vec<String>) -> Result<Value>;
#[method(name = "getAttachment", param_kind = map)]
fn get_attachment(
&self,
account: Option<String>,
id: String,
recipient: Option<String>,
#[allow(non_snake_case)] groupId: Option<String>,
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "joinGroup", params = "named")]
fn join_group(&self, account: Option<String>, uri: String) -> Result<Value>;
#[method(name = "getAvatar", param_kind = map)]
fn get_avatar(
&self,
account: Option<String>,
contact: Option<String>,
profile: Option<String>,
#[allow(non_snake_case)] groupId: Option<String>,
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "finishLink", params = "named")]
#[method(name = "getSticker", param_kind = map)]
fn get_sticker(
&self,
account: Option<String>,
#[allow(non_snake_case)] packId: String,
#[allow(non_snake_case)] stickerId: u32,
) -> Result<Value, ErrorObjectOwned>;
#[method(name = "getUserStatus", param_kind = map)]
fn get_user_status(
&self,
account: Option<String>,
recipients: Vec<String>,
usernames: Vec<String>,
) -> Result<Value, ErrorObjectOwned>;
#[method(name = "joinGroup", param_kind = map)]
fn join_group(&self, account: Option<String>, uri: String) -> Result<Value, ErrorObjectOwned>;
#[allow(non_snake_case)]
#[method(name = "finishChangeNumber", param_kind = map)]
fn finish_change_number(
&self,
account: Option<String>,
number: String,
verificationCode: String,
pin: Option<String>,
) -> Result<Value, ErrorObjectOwned>;
#[method(name = "finishLink", param_kind = map)]
fn finish_link(
&self,
#[allow(non_snake_case)] deviceLinkUri: String,
#[allow(non_snake_case)] deviceName: String,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "listAccounts", params = "named")]
fn list_accounts(&self) -> Result<Value>;
#[method(name = "listAccounts", param_kind = map)]
fn list_accounts(&self) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "listContacts", params = "named")]
#[method(name = "listContacts", param_kind = map)]
fn list_contacts(
&self,
account: Option<String>,
@ -51,60 +104,65 @@ pub trait Rpc {
#[allow(non_snake_case)] allRecipients: bool,
blocked: Option<bool>,
name: Option<String>,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "listDevices", params = "named")]
fn list_devices(&self, account: Option<String>) -> Result<Value>;
#[method(name = "listDevices", param_kind = map)]
fn list_devices(&self, account: Option<String>) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "listGroups", params = "named")]
#[method(name = "listGroups", param_kind = map)]
fn list_groups(
&self,
account: Option<String>,
#[allow(non_snake_case)] groupIds: Vec<String>,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "listIdentities", params = "named")]
fn list_identities(&self, account: Option<String>, number: Option<String>) -> Result<Value>;
#[method(name = "listIdentities", param_kind = map)]
fn list_identities(
&self,
account: Option<String>,
number: Option<String>,
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "listStickerPacks", params = "named")]
fn list_sticker_packs(&self, account: Option<String>) -> Result<Value>;
#[method(name = "listStickerPacks", param_kind = map)]
fn list_sticker_packs(&self, account: Option<String>) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "quitGroup", params = "named")]
#[method(name = "quitGroup", param_kind = map)]
fn quit_group(
&self,
account: Option<String>,
#[allow(non_snake_case)] groupId: String,
delete: bool,
admins: Vec<String>,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "register", params = "named")]
#[method(name = "register", param_kind = map)]
fn register(
&self,
account: Option<String>,
voice: bool,
captcha: Option<String>,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "removeContact", params = "named")]
#[method(name = "removeContact", param_kind = map)]
fn remove_contact(
&self,
account: Option<String>,
recipient: String,
forget: bool,
) -> Result<Value>;
hide: bool,
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "removeDevice", params = "named")]
#[method(name = "removeDevice", param_kind = map)]
fn remove_device(
&self,
account: Option<String>,
#[allow(non_snake_case)] deviceId: u32,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "removePin", params = "named")]
fn remove_pin(&self, account: Option<String>) -> Result<Value>;
#[method(name = "removePin", param_kind = map)]
fn remove_pin(&self, account: Option<String>) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "remoteDelete", params = "named")]
#[method(name = "remoteDelete", param_kind = map)]
fn remote_delete(
&self,
account: Option<String>,
@ -112,39 +170,51 @@ pub trait Rpc {
recipients: Vec<String>,
#[allow(non_snake_case)] groupIds: Vec<String>,
#[allow(non_snake_case)] noteToSelf: bool,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "send", params = "named")]
#[allow(non_snake_case)]
#[method(name = "send", param_kind = map)]
fn send(
&self,
account: Option<String>,
recipients: Vec<String>,
#[allow(non_snake_case)] groupIds: Vec<String>,
#[allow(non_snake_case)] noteToSelf: bool,
#[allow(non_snake_case)] endSession: bool,
groupIds: Vec<String>,
noteToSelf: bool,
endSession: bool,
message: String,
attachments: Vec<String>,
viewOnce: bool,
mentions: Vec<String>,
#[allow(non_snake_case)] quoteTimestamp: Option<u64>,
#[allow(non_snake_case)] quoteAuthor: Option<String>,
#[allow(non_snake_case)] quoteMessage: Option<String>,
#[allow(non_snake_case)] quoteMention: Vec<String>,
textStyle: Vec<String>,
quoteTimestamp: Option<u64>,
quoteAuthor: Option<String>,
quoteMessage: Option<String>,
quoteMention: Vec<String>,
quoteTextStyle: Vec<String>,
quoteAttachment: Vec<String>,
previewUrl: Option<String>,
previewTitle: Option<String>,
previewDescription: Option<String>,
previewImage: Option<String>,
sticker: Option<String>,
) -> Result<Value>;
storyTimestamp: Option<u64>,
storyAuthor: Option<String>,
editTimestamp: Option<u64>,
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "sendContacts", params = "named")]
fn send_contacts(&self, account: Option<String>) -> Result<Value>;
#[method(name = "sendContacts", param_kind = map)]
fn send_contacts(&self, account: Option<String>) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "sendPaymentNotification", params = "named")]
#[method(name = "sendPaymentNotification", param_kind = map)]
fn send_payment_notification(
&self,
account: Option<String>,
recipient: String,
receipt: String,
note: String,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "sendReaction", params = "named")]
#[method(name = "sendReaction", param_kind = map)]
fn send_reaction(
&self,
account: Option<String>,
@ -155,94 +225,117 @@ pub trait Rpc {
#[allow(non_snake_case)] targetAuthor: String,
#[allow(non_snake_case)] targetTimestamp: u64,
remove: bool,
) -> Result<Value>;
story: bool,
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "sendReceipt", params = "named")]
#[method(name = "sendReceipt", param_kind = map)]
fn send_receipt(
&self,
account: Option<String>,
recipient: String,
#[allow(non_snake_case)] targetTimestamps: Vec<u64>,
r#type: String,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "sendSyncRequest", params = "named")]
fn send_sync_request(&self, account: Option<String>) -> Result<Value>;
#[method(name = "sendSyncRequest", param_kind = map)]
fn send_sync_request(&self, account: Option<String>) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "sendTyping", params = "named")]
#[method(name = "sendTyping", param_kind = map)]
fn send_typing(
&self,
account: Option<String>,
recipients: Vec<String>,
#[allow(non_snake_case)] groupIds: Vec<String>,
stop: bool,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "setPin", params = "named")]
fn set_pin(&self, account: Option<String>, pin: String) -> Result<Value>;
#[method(name = "sendMessageRequestResponse", param_kind = map)]
fn send_message_request_response(
&self,
account: Option<String>,
recipients: Vec<String>,
#[allow(non_snake_case)] groupIds: Vec<String>,
r#type: String,
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "submitRateLimitChallenge", params = "named")]
#[method(name = "setPin", param_kind = map)]
fn set_pin(&self, account: Option<String>, pin: String) -> Result<Value, ErrorObjectOwned>;
#[method(name = "submitRateLimitChallenge", param_kind = map)]
fn submit_rate_limit_challenge(
&self,
account: Option<String>,
challenge: String,
captcha: String,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "startLink", params = "named")]
fn start_link(&self, account: Option<String>) -> Result<JsonLink>;
#[method(name = "startChangeNumber", param_kind = map)]
fn start_change_number(
&self,
account: Option<String>,
number: String,
voice: bool,
captcha: Option<String>,
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "trust", params = "named")]
#[method(name = "startLink", param_kind = map)]
fn start_link(&self, account: Option<String>) -> Result<JsonLink, ErrorObjectOwned>;
#[method(name = "trust", param_kind = map)]
fn trust(
&self,
account: Option<String>,
recipient: String,
#[allow(non_snake_case)] trustAllKnownKeys: bool,
#[allow(non_snake_case)] verifiedSafetyNumber: Option<String>,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "unblock", params = "named")]
#[method(name = "unblock", param_kind = map)]
fn unblock(
&self,
account: Option<String>,
recipients: Vec<String>,
#[allow(non_snake_case)] groupIds: Vec<String>,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "unregister", params = "named")]
#[method(name = "unregister", param_kind = map)]
fn unregister(
&self,
account: Option<String>,
#[allow(non_snake_case)] deleteAccount: bool,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "updateAccount", params = "named")]
#[allow(non_snake_case)]
#[method(name = "updateAccount", param_kind = map)]
fn update_account(
&self,
account: Option<String>,
#[allow(non_snake_case)] deviceName: Option<String>,
) -> Result<Value>;
deviceName: Option<String>,
unrestrictedUnidentifiedSender: Option<bool>,
discoverableByNumber: Option<bool>,
numberSharing: Option<bool>,
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "updateConfiguration", params = "named")]
#[method(name = "updateConfiguration", param_kind = map)]
fn update_configuration(
&self,
account: Option<String>,
#[allow(non_snake_case)] readReceiptes: Option<bool>,
#[allow(non_snake_case)] readReceipts: Option<bool>,
#[allow(non_snake_case)] unidentifiedDeliveryIndicators: Option<bool>,
#[allow(non_snake_case)] typingIndicators: Option<bool>,
#[allow(non_snake_case)] linkPreviews: Option<bool>,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "updateContact", params = "named")]
#[method(name = "updateContact", param_kind = map)]
fn update_contact(
&self,
account: Option<String>,
recipient: String,
name: Option<String>,
expiration: Option<u32>,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "updateGroup", params = "named")]
#[method(name = "updateGroup", param_kind = map)]
fn update_group(
&self,
account: Option<String>,
@ -262,9 +355,9 @@ pub trait Rpc {
#[allow(non_snake_case)] setPermissionEditDetails: Option<String>,
#[allow(non_snake_case)] setPermissionSendMessages: Option<String>,
expiration: Option<u32>,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "updateProfile", params = "named")]
#[method(name = "updateProfile", param_kind = map)]
fn update_profile(
&self,
account: Option<String>,
@ -275,32 +368,33 @@ pub trait Rpc {
#[allow(non_snake_case)] mobileCoinAddress: Option<String>,
avatar: Option<String>,
#[allow(non_snake_case)] removeAvatar: bool,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "uploadStickerPack", params = "named")]
fn upload_sticker_pack(&self, account: Option<String>, path: String) -> Result<Value>;
#[method(name = "uploadStickerPack", param_kind = map)]
fn upload_sticker_pack(
&self,
account: Option<String>,
path: String,
) -> Result<Value, ErrorObjectOwned>;
#[rpc(name = "verify", params = "named")]
#[method(name = "verify", param_kind = map)]
fn verify(
&self,
account: Option<String>,
#[allow(non_snake_case)] verificationCode: String,
pin: Option<String>,
) -> Result<Value>;
) -> Result<Value, ErrorObjectOwned>;
#[pubsub(
subscription = "receive",
subscribe,
name = "subscribeReceive",
params = "named"
#[subscription(
name = "subscribeReceive" => "receive",
unsubscribe = "unsubscribeReceive",
item = Value,
param_kind = map
)]
fn subscribe_receive(&self, _: Self::Metadata, _: Subscriber<Value>, account: Option<String>);
async fn subscribe_receive(&self, account: Option<String>) -> SubscriptionResult;
#[pubsub(subscription = "receive", unsubscribe, name = "unsubscribeReceive")]
fn unsubscribe_receive(&self, _: Option<Self::Metadata>, _: SubscriptionId) -> Result<bool>;
#[rpc(name = "version")]
fn version(&self) -> Result<Value>;
#[method(name = "version")]
fn version(&self) -> Result<Value, ErrorObjectOwned>;
}
#[derive(Deserialize)]
@ -309,10 +403,23 @@ pub struct JsonLink {
pub device_link_uri: String,
}
pub async fn connect_tcp(tcp: impl ToSocketAddrs) -> Result<SignalCliClient, RpcError> {
super::tcp::connect::<_, SignalCliClient>(tcp).await
pub async fn connect_tcp(
tcp: impl ToSocketAddrs,
) -> Result<impl SubscriptionClientT, std::io::Error> {
let (sender, receiver) = super::transports::tcp::connect(tcp).await?;
Ok(ClientBuilder::default().build_with_tokio(sender, receiver))
}
pub async fn connect_unix(socket_path: impl AsRef<Path>) -> Result<SignalCliClient, RpcError> {
ipc::connect::<_, SignalCliClient>(socket_path).await
#[cfg(unix)]
pub async fn connect_unix(
socket_path: impl AsRef<Path>,
) -> Result<impl SubscriptionClientT, std::io::Error> {
let (sender, receiver) = super::transports::ipc::connect(socket_path).await?;
Ok(ClientBuilder::default().build_with_tokio(sender, receiver))
}
pub async fn connect_http(uri: &str) -> Result<impl SubscriptionClientT + use<>, Error> {
HttpClientBuilder::default().build(uri)
}

View file

@ -1,66 +1,85 @@
use clap::StructOpt;
use jsonrpc_client_transports::{RpcError, TypedSubscriptionStream};
use jsonrpc_core::{futures_util::StreamExt, Value};
use std::{path::PathBuf, time::Duration};
use clap::Parser;
use jsonrpsee::core::client::{Error as RpcError, Subscription, SubscriptionClientT};
use serde_json::{Error, Value};
use tokio::{select, time::sleep};
use crate::cli::{GroupPermission, LinkState};
use cli::Cli;
use crate::cli::{CliCommands, GroupPermission, LinkState};
use crate::jsonrpc::RpcClient;
mod cli;
#[allow(clippy::too_many_arguments)]
#[allow(non_snake_case, clippy::too_many_arguments)]
mod jsonrpc;
mod tcp;
mod transports;
const DEFAULT_TCP: &str = "127.0.0.1:7583";
const DEFAULT_SOCKET_SUFFIX: &str = "signal-cli/socket";
const DEFAULT_HTTP: &str = "http://localhost:8080/api/v1/rpc";
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let cli = cli::Cli::parse();
let client = connect(&cli)
.await
.map_err(|e| anyhow::anyhow!("Failed to connect to socket: {e}"))?;
let result = connect(cli).await;
let result = match cli.command {
cli::CliCommands::Receive { timeout } => {
let mut stream = client
.subscribe_receive(cli.account)
.map_err(|e| anyhow::anyhow!("JSON-RPC command failed: {:?}", e))?;
match result {
Ok(Value::Null) => {}
Ok(v) => println!("{v}"),
Err(e) => return Err(anyhow::anyhow!("JSON-RPC command failed: {e:?}")),
}
Ok(())
}
async fn handle_command(
cli: Cli,
client: impl SubscriptionClientT + Sync,
) -> Result<Value, RpcError> {
match cli.command {
CliCommands::Receive { timeout } => {
let mut stream = client.subscribe_receive(cli.account).await?;
{
while let Some(v) = stream_next(timeout, &mut stream).await {
let v = v.map_err(|e| anyhow::anyhow!("JSON-RPC command failed: {:?}", e))?;
let v = v?;
println!("{v}");
}
}
return Ok(());
stream.unsubscribe().await?;
Ok(Value::Null)
}
cli::CliCommands::AddDevice { uri } => client.add_device(cli.account, uri).await,
cli::CliCommands::Block {
CliCommands::AddDevice { uri } => client.add_device(cli.account, uri).await,
CliCommands::Block {
recipient,
group_id,
} => client.block(cli.account, recipient, group_id).await,
cli::CliCommands::DeleteLocalAccountData { ignore_registered } => {
CliCommands::DeleteLocalAccountData { ignore_registered } => {
client
.delete_local_account_data(cli.account, ignore_registered)
.await
}
cli::CliCommands::GetUserStatus { recipient } => {
client.get_user_status(cli.account, recipient).await
CliCommands::GetUserStatus {
recipient,
username,
} => {
client
.get_user_status(cli.account, recipient, username)
.await
}
cli::CliCommands::JoinGroup { uri } => client.join_group(cli.account, uri).await,
cli::CliCommands::Link { name } => {
CliCommands::JoinGroup { uri } => client.join_group(cli.account, uri).await,
CliCommands::Link { name } => {
let url = client
.start_link(cli.account)
.await
.map_err(|e| anyhow::anyhow!("JSON-RPC command startLink failed: {e:?}",))?
.map_err(|e| RpcError::Custom(format!("JSON-RPC command startLink failed: {e:?}")))?
.device_link_uri;
println!("{}", url);
println!("{url}");
client.finish_link(url, name).await
}
cli::CliCommands::ListAccounts => client.list_accounts().await,
cli::CliCommands::ListContacts {
CliCommands::ListAccounts => client.list_accounts().await,
CliCommands::ListContacts {
recipient,
all_recipients,
blocked,
@ -70,16 +89,14 @@ async fn main() -> Result<(), anyhow::Error> {
.list_contacts(cli.account, recipient, all_recipients, blocked, name)
.await
}
cli::CliCommands::ListDevices => client.list_devices(cli.account).await,
cli::CliCommands::ListGroups {
CliCommands::ListDevices => client.list_devices(cli.account).await,
CliCommands::ListGroups {
detailed: _,
group_id,
} => client.list_groups(cli.account, group_id).await,
cli::CliCommands::ListIdentities { number } => {
client.list_identities(cli.account, number).await
}
cli::CliCommands::ListStickerPacks => client.list_sticker_packs(cli.account).await,
cli::CliCommands::QuitGroup {
CliCommands::ListIdentities { number } => client.list_identities(cli.account, number).await,
CliCommands::ListStickerPacks => client.list_sticker_packs(cli.account).await,
CliCommands::QuitGroup {
group_id,
delete,
admin,
@ -88,17 +105,23 @@ async fn main() -> Result<(), anyhow::Error> {
.quit_group(cli.account, group_id, delete, admin)
.await
}
cli::CliCommands::Register { voice, captcha } => {
CliCommands::Register { voice, captcha } => {
client.register(cli.account, voice, captcha).await
}
cli::CliCommands::RemoveContact { recipient, forget } => {
client.remove_contact(cli.account, recipient, forget).await
CliCommands::RemoveContact {
recipient,
forget,
hide,
} => {
client
.remove_contact(cli.account, recipient, forget, hide)
.await
}
cli::CliCommands::RemoveDevice { device_id } => {
CliCommands::RemoveDevice { device_id } => {
client.remove_device(cli.account, device_id).await
}
cli::CliCommands::RemovePin => client.remove_pin(cli.account).await,
cli::CliCommands::RemoteDelete {
CliCommands::RemovePin => client.remove_pin(cli.account).await,
CliCommands::RemoteDelete {
target_timestamp,
recipient,
group_id,
@ -114,19 +137,30 @@ async fn main() -> Result<(), anyhow::Error> {
)
.await
}
cli::CliCommands::Send {
CliCommands::Send {
recipient,
group_id,
note_to_self,
end_session,
message,
attachment,
view_once,
mention,
text_style,
quote_timestamp,
quote_author,
quote_message,
quote_mention,
quote_text_style,
quote_attachment,
preview_url,
preview_title,
preview_description,
preview_image,
sticker,
story_timestamp,
story_author,
edit_timestamp,
} => {
client
.send(
@ -137,17 +171,28 @@ async fn main() -> Result<(), anyhow::Error> {
end_session,
message.unwrap_or_default(),
attachment,
view_once,
mention,
text_style,
quote_timestamp,
quote_author,
quote_message,
quote_mention,
quote_text_style,
quote_attachment,
preview_url,
preview_title,
preview_description,
preview_image,
sticker,
story_timestamp,
story_author,
edit_timestamp,
)
.await
}
cli::CliCommands::SendContacts => client.send_contacts(cli.account).await,
cli::CliCommands::SendPaymentNotification {
CliCommands::SendContacts => client.send_contacts(cli.account).await,
CliCommands::SendPaymentNotification {
recipient,
receipt,
note,
@ -156,7 +201,7 @@ async fn main() -> Result<(), anyhow::Error> {
.send_payment_notification(cli.account, recipient, receipt, note)
.await
}
cli::CliCommands::SendReaction {
CliCommands::SendReaction {
recipient,
group_id,
note_to_self,
@ -164,6 +209,7 @@ async fn main() -> Result<(), anyhow::Error> {
target_author,
target_timestamp,
remove,
story,
} => {
client
.send_reaction(
@ -175,10 +221,11 @@ async fn main() -> Result<(), anyhow::Error> {
target_author,
target_timestamp,
remove,
story,
)
.await
}
cli::CliCommands::SendReceipt {
CliCommands::SendReceipt {
recipient,
target_timestamp,
r#type,
@ -195,8 +242,8 @@ async fn main() -> Result<(), anyhow::Error> {
)
.await
}
cli::CliCommands::SendSyncRequest => client.send_sync_request(cli.account).await,
cli::CliCommands::SendTyping {
CliCommands::SendSyncRequest => client.send_sync_request(cli.account).await,
CliCommands::SendTyping {
recipient,
group_id,
stop,
@ -205,13 +252,13 @@ async fn main() -> Result<(), anyhow::Error> {
.send_typing(cli.account, recipient, group_id, stop)
.await
}
cli::CliCommands::SetPin { pin } => client.set_pin(cli.account, pin).await,
cli::CliCommands::SubmitRateLimitChallenge { challenge, captcha } => {
CliCommands::SetPin { pin } => client.set_pin(cli.account, pin).await,
CliCommands::SubmitRateLimitChallenge { challenge, captcha } => {
client
.submit_rate_limit_challenge(cli.account, challenge, captcha)
.await
}
cli::CliCommands::Trust {
CliCommands::Trust {
recipient,
trust_all_known_keys,
verified_safety_number,
@ -225,17 +272,30 @@ async fn main() -> Result<(), anyhow::Error> {
)
.await
}
cli::CliCommands::Unblock {
CliCommands::Unblock {
recipient,
group_id,
} => client.unblock(cli.account, recipient, group_id).await,
cli::CliCommands::Unregister { delete_account } => {
CliCommands::Unregister { delete_account } => {
client.unregister(cli.account, delete_account).await
}
cli::CliCommands::UpdateAccount { device_name } => {
client.update_account(cli.account, device_name).await
CliCommands::UpdateAccount {
device_name,
unrestricted_unidentified_sender,
discoverable_by_number,
number_sharing,
} => {
client
.update_account(
cli.account,
device_name,
unrestricted_unidentified_sender,
discoverable_by_number,
number_sharing,
)
.await
}
cli::CliCommands::UpdateConfiguration {
CliCommands::UpdateConfiguration {
read_receipts,
unidentified_delivery_indicators,
typing_indicators,
@ -251,7 +311,7 @@ async fn main() -> Result<(), anyhow::Error> {
)
.await
}
cli::CliCommands::UpdateContact {
CliCommands::UpdateContact {
recipient,
expiration,
name,
@ -260,7 +320,7 @@ async fn main() -> Result<(), anyhow::Error> {
.update_contact(cli.account, recipient, name, expiration)
.await
}
cli::CliCommands::UpdateGroup {
CliCommands::UpdateGroup {
group_id,
name,
description,
@ -313,7 +373,7 @@ async fn main() -> Result<(), anyhow::Error> {
)
.await
}
cli::CliCommands::UpdateProfile {
CliCommands::UpdateProfile {
given_name,
family_name,
about,
@ -335,47 +395,126 @@ async fn main() -> Result<(), anyhow::Error> {
)
.await
}
cli::CliCommands::UploadStickerPack { path } => {
CliCommands::UploadStickerPack { path } => {
client.upload_sticker_pack(cli.account, path).await
}
cli::CliCommands::Verify {
CliCommands::Verify {
verification_code,
pin,
} => client.verify(cli.account, verification_code, pin).await,
cli::CliCommands::Version => client.version().await,
};
result
.map(|v| println!("{v}"))
.map_err(|e| anyhow::anyhow!("JSON-RPC command failed: {e:?}",))?;
Ok(())
CliCommands::Version => client.version().await,
CliCommands::AddStickerPack { uri } => client.add_sticker_pack(cli.account, uri).await,
CliCommands::FinishChangeNumber {
number,
verification_code,
pin,
} => {
client
.finish_change_number(cli.account, number, verification_code, pin)
.await
}
CliCommands::GetAttachment {
id,
recipient,
group_id,
} => {
client
.get_attachment(cli.account, id, recipient, group_id)
.await
}
CliCommands::GetAvatar {
contact,
profile,
group_id,
} => {
client
.get_avatar(cli.account, contact, profile, group_id)
.await
}
CliCommands::GetSticker {
pack_id,
sticker_id,
} => client.get_sticker(cli.account, pack_id, sticker_id).await,
CliCommands::StartChangeNumber {
number,
voice,
captcha,
} => {
client
.start_change_number(cli.account, number, voice, captcha)
.await
}
CliCommands::SendMessageRequestResponse {
recipient,
group_id,
r#type,
} => {
client
.send_message_request_response(
cli.account,
recipient,
group_id,
match r#type {
cli::MessageRequestResponseType::Accept => "accept".to_owned(),
cli::MessageRequestResponseType::Delete => "delete".to_owned(),
},
)
.await
}
}
}
async fn connect(cli: &cli::Cli) -> Result<jsonrpc::SignalCliClient, RpcError> {
if let Some(tcp) = cli.json_rpc_tcp {
async fn connect(cli: Cli) -> Result<Value, RpcError> {
if let Some(http) = &cli.json_rpc_http {
let uri = if let Some(uri) = http {
uri
} else {
DEFAULT_HTTP
};
let client = jsonrpc::connect_http(uri)
.await
.map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?;
handle_command(cli, client).await
} else if let Some(tcp) = cli.json_rpc_tcp {
let socket_addr = tcp.unwrap_or_else(|| DEFAULT_TCP.parse().unwrap());
jsonrpc::connect_tcp(socket_addr).await
let client = jsonrpc::connect_tcp(socket_addr)
.await
.map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?;
handle_command(cli, client).await
} else {
let socket_path = cli
.json_rpc_socket
.clone()
.unwrap_or(None)
.or_else(|| {
std::env::var_os("XDG_RUNTIME_DIR").map(|runtime_dir| {
PathBuf::from(runtime_dir)
.join(DEFAULT_SOCKET_SUFFIX)
.into()
#[cfg(windows)]
{
Err(RpcError::Custom("Invalid socket".into()))
}
#[cfg(unix)]
{
let socket_path = cli
.json_rpc_socket
.clone()
.unwrap_or(None)
.or_else(|| {
std::env::var_os("XDG_RUNTIME_DIR").map(|runtime_dir| {
PathBuf::from(runtime_dir)
.join(DEFAULT_SOCKET_SUFFIX)
.into()
})
})
})
.unwrap_or_else(|| ("/run".to_owned() + DEFAULT_SOCKET_SUFFIX).into());
jsonrpc::connect_unix(socket_path).await
.unwrap_or_else(|| ("/run".to_owned() + DEFAULT_SOCKET_SUFFIX).into());
let client = jsonrpc::connect_unix(socket_path)
.await
.map_err(|e| RpcError::Custom(format!("Failed to connect to socket: {e}")))?;
handle_command(cli, client).await
}
}
}
async fn stream_next(
timeout: f64,
stream: &mut TypedSubscriptionStream<Value>,
) -> Option<Result<Value, RpcError>> {
stream: &mut Subscription<Value>,
) -> Option<Result<Value, Error>> {
if timeout < 0.0 {
stream.next().await
} else {

View file

@ -1,29 +0,0 @@
use jsonrpc_client_transports::{transports::duplex, RpcChannel, RpcError};
use jsonrpc_core::futures_util::{SinkExt, StreamExt, TryStreamExt};
use jsonrpc_server_utils::{codecs::StreamCodec, tokio_util::codec::Decoder};
use tokio::net::{TcpStream, ToSocketAddrs};
/// Connect to a JSON-RPC TCP server.
pub async fn connect<S: ToSocketAddrs, Client: From<RpcChannel>>(
socket: S,
) -> Result<Client, RpcError> {
let connection = TcpStream::connect(socket)
.await
.map_err(|e| RpcError::Other(Box::new(e)))?;
let (sink, stream) = StreamCodec::stream_incoming().framed(connection).split();
let sink = sink.sink_map_err(|e| RpcError::Other(Box::new(e)));
let stream = stream.map_err(|e| log::error!("TCP stream error: {}", e));
let (client, sender) = duplex(
Box::pin(sink),
Box::pin(
stream
.take_while(|x| std::future::ready(x.is_ok()))
.map(|x| x.expect("Stream is closed upon first error.")),
),
);
tokio::spawn(client);
Ok(sender.into())
}

View file

@ -0,0 +1,23 @@
use std::io::Error;
use std::path::Path;
use futures_util::stream::StreamExt;
use jsonrpsee::core::client::{TransportReceiverT, TransportSenderT};
use tokio::net::UnixStream;
use tokio_util::codec::Decoder;
use super::stream_codec::StreamCodec;
use super::{Receiver, Sender};
/// Connect to a JSON-RPC Unix Socket server.
pub async fn connect(
socket: impl AsRef<Path>,
) -> Result<(impl TransportSenderT + Send, impl TransportReceiverT + Send), Error> {
let connection = UnixStream::connect(socket).await?;
let (sink, stream) = StreamCodec::stream_incoming().framed(connection).split();
let sender = Sender { inner: sink };
let receiver = Receiver { inner: stream };
Ok((sender, receiver))
}

View file

@ -0,0 +1,60 @@
use futures_util::{stream::StreamExt, Sink, SinkExt, Stream};
use jsonrpsee::core::client::{ReceivedMessage, TransportReceiverT, TransportSenderT};
use thiserror::Error;
#[cfg(unix)]
pub mod ipc;
mod stream_codec;
pub mod tcp;
#[derive(Debug, Error)]
enum Errors {
#[error("Other: {0}")]
Other(String),
#[error("Closed")]
Closed,
}
struct Sender<T: Send + Sink<String>> {
inner: T,
}
impl<T: Send + Sink<String, Error = impl std::error::Error> + Unpin + 'static> TransportSenderT
for Sender<T>
{
type Error = Errors;
async fn send(&mut self, body: String) -> Result<(), Self::Error> {
self.inner
.send(body)
.await
.map_err(|e| Errors::Other(format!("{e:?}")))?;
Ok(())
}
async fn close(&mut self) -> Result<(), Self::Error> {
self.inner
.close()
.await
.map_err(|e| Errors::Other(format!("{e:?}")))?;
Ok(())
}
}
struct Receiver<T: Send + Stream> {
inner: T,
}
impl<T: Send + Stream<Item = Result<String, std::io::Error>> + Unpin + 'static> TransportReceiverT
for Receiver<T>
{
type Error = Errors;
async fn receive(&mut self) -> Result<ReceivedMessage, Self::Error> {
match self.inner.next().await {
None => Err(Errors::Closed),
Some(Ok(msg)) => Ok(ReceivedMessage::Text(msg)),
Some(Err(e)) => Err(Errors::Other(format!("{e:?}"))),
}
}
}

View file

@ -0,0 +1,61 @@
use bytes::BytesMut;
use std::{io, str};
use tokio_util::codec::{Decoder, Encoder};
type Separator = u8;
/// Stream codec for streaming protocols (ipc, tcp)
#[derive(Debug, Default)]
pub struct StreamCodec {
incoming_separator: Separator,
outgoing_separator: Separator,
}
impl StreamCodec {
/// Default codec with streaming input data. Input can be both enveloped and not.
pub fn stream_incoming() -> Self {
StreamCodec::new(b'\n', b'\n')
}
/// New custom stream codec
pub fn new(incoming_separator: Separator, outgoing_separator: Separator) -> Self {
StreamCodec {
incoming_separator,
outgoing_separator,
}
}
}
impl Decoder for StreamCodec {
type Item = String;
type Error = io::Error;
fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<Self::Item>> {
if let Some(i) = buf
.as_ref()
.iter()
.position(|&b| b == self.incoming_separator)
{
let line = buf.split_to(i);
let _ = buf.split_to(1);
match str::from_utf8(line.as_ref()) {
Ok(s) => Ok(Some(s.to_string())),
Err(_) => Err(io::Error::other("invalid UTF-8")),
}
} else {
Ok(None)
}
}
}
impl Encoder<String> for StreamCodec {
type Error = io::Error;
fn encode(&mut self, msg: String, buf: &mut BytesMut) -> io::Result<()> {
let mut payload = msg.into_bytes();
payload.push(self.outgoing_separator);
buf.extend_from_slice(&payload);
Ok(())
}
}

View file

@ -0,0 +1,22 @@
use std::io::Error;
use futures_util::stream::StreamExt;
use jsonrpsee::core::client::{TransportReceiverT, TransportSenderT};
use tokio::net::{TcpStream, ToSocketAddrs};
use tokio_util::codec::Decoder;
use super::stream_codec::StreamCodec;
use super::{Receiver, Sender};
/// Connect to a JSON-RPC TCP server.
pub async fn connect(
socket: impl ToSocketAddrs,
) -> Result<(impl TransportSenderT + Send, impl TransportReceiverT + Send), Error> {
let connection = TcpStream::connect(socket).await?;
let (sink, stream) = StreamCodec::stream_incoming().framed(connection).split();
let sender = Sender { inner: sink };
let receiver = Receiver { inner: stream };
Ok((sender, receiver))
}

View file

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="console-application">
<id>org.asamk.SignalCli</id>
<name>signal-cli</name>
<summary>Use Signal messenger in terminal</summary>
<developer id="org.asamk">
<name>AsamK</name>
</developer>
<icon type="stock">org.asamk.SignalCli</icon>
<keywords>
<keyword>signal</keyword>
<keyword>signal-cli</keyword>
<keyword>messenger</keyword>
<keyword>messaging</keyword>
</keywords>
<url type="bugtracker">https://github.com/AsamK/signal-cli/issues</url>
<url type="homepage">https://github.com/AsamK/signal-cli</url>
<url type="donation">https://github.com/sponsors/AsamK</url>
<url type="faq">https://github.com/AsamK/signal-cli/discussions</url>
<url type="vcs-browser">https://github.com/AsamK/signal-cli</url>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0-only</project_license>
<description>
<p>
signal-cli is an unofficial commandline interface for the Signal Messenger.
It supports many Signal functions, including registering, verifying, sending and receiving messages.
For registering you need a phone number where you can receive SMS or incoming calls.
Alternatively signal-cli can be linked to an existing App account.
</p>
</description>
<categories>
<category>Utility</category>
<category>Java</category>
</categories>
<provides>
<binary>signal-cli</binary>
</provides>
<content_rating type="oars-1.1">
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="0.13.18" date="2025-07-16">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.18</url>
</release>
<release version="0.13.17" date="2025-06-28">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.17</url>
</release>
<release version="0.13.16" date="2025-06-07">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.16</url>
</release>
<release version="0.13.15" date="2025-05-08">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.15</url>
</release>
<release version="0.13.14" date="2025-04-06">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.14</url>
</release>
<release version="0.13.13" date="2025-02-28">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.13</url>
</release>
<release version="0.13.12" date="2025-01-18">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.12</url>
</release>
<release version="0.13.11" date="2024-12-26">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.11</url>
</release>
<release version="0.13.10" date="2024-11-30">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.10</url>
</release>
<release version="0.13.9" date="2024-10-28">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.9</url>
</release>
<release version="0.13.8" date="2024-10-26">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.8</url>
</release>
<release version="0.13.7" date="2024-09-28">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.7</url>
</release>
<release version="0.13.6" date="2024-09-08">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.6</url>
</release>
<release version="0.13.5" date="2024-07-25">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.5</url>
</release>
<release version="0.13.4" date="2024-06-06">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.4</url>
</release>
<release version="0.13.3" date="2024-04-19">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.3</url>
</release>
<release version="0.13.2" date="2024-03-23">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.2</url>
</release>
<release version="0.13.1" date="2024-02-27">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.1</url>
</release>
<release version="0.13.0" date="2024-02-18">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.13.0</url>
</release>
<release version="0.12.8" date="2024-02-08">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.12.8</url>
</release>
<release version="0.12.7" date="2023-12-15">
<url type="details">https://github.com/AsamK/signal-cli/releases/tag/v0.12.7</url>
</release>
</releases>
</component>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="128" height="128" version="1.1" viewBox="0 0 33.867 33.867" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(-32.279 -138.64)">
<g transform="matrix(.45526 0 0 .45526 33.984 140.17)">
<path d="m33.468 66.938c-18.454 0-33.468-15.014-33.468-33.469s15.014-33.469 33.468-33.469c18.455 0 33.469 15.014 33.469 33.469 0 5.621-1.421 11.161-4.116 16.076l4.608 17.2-16.849-4.516c-5.172 3.084-11.069 4.709-17.112 4.709z" fill="#fff"/>
<path d="m33.468 67.184c-18.454 0-33.468-15.014-33.468-33.469s15.014-33.469 33.468-33.469c18.455 0 33.469 15.014 33.469 33.469 0 5.621-1.421 11.161-4.116 16.076l4.608 17.2-16.849-4.516c-5.172 3.084-11.069 4.709-17.112 4.709zm0-62.938c-16.249 0-29.468 13.22-29.468 29.469s13.219 29.469 29.468 29.469c5.582 0 11.021-1.574 15.729-4.554l0.74-0.468 11.835 3.171-3.243-12.1 0.419-0.72c2.609-4.484 3.988-9.602 3.988-14.799 0-16.248-13.219-29.468-29.468-29.468z"/>
<path d="m25.515 45.296q-2.3937 0-4.2817-0.97772-1.8543-0.97772-2.9332-3.0343-1.0451-2.0566-1.0451-5.2595 0-3.3377 1.1126-5.428 1.1126-2.0903 3.0006-3.068 1.9217-0.97772 4.3492-0.97772 1.3823 0 2.6634 0.30343 1.2812 0.26972 2.0903 0.67429l-0.91029 2.4612q-0.80915-0.30343-1.888-0.57315t-2.0229-0.26972q-5.3269 0-5.3269 6.844 0 3.2703 1.2812 5.0235 1.3149 1.7194 3.8772 1.7194 1.4834 0 2.596-0.30343 1.1463-0.30343 2.0903-0.74172v2.6297q-0.91029 0.472-2.0229 0.708-1.0789 0.26972-2.6297 0.26972zm11.901-0.33714h-2.9669v-25.623h2.9669zm7.2486-24.848q0.67429 0 1.18 0.472 0.53943 0.43829 0.53943 1.416 0 0.94401-0.53943 1.416-0.50572 0.472-1.18 0.472-0.74172 0-1.2474-0.472-0.50572-0.472-0.50572-1.416 0-0.97772 0.50572-1.416 0.50572-0.472 1.2474-0.472zm1.4497 6.7766v18.071h-2.9669v-18.071z" aria-label="cli"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -32,8 +32,6 @@ RestrictAddressFamilies=AF_INET AF_INET6
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
# JVM always exits with 143 in reaction to SIGTERM signal
SuccessExitStatus=143
StandardInput=socket
StandardOutput=journal
StandardError=journal

View file

@ -8,11 +8,9 @@ After=network-online.target
[Service]
Type=dbus
Environment="SIGNAL_CLI_OPTS=-Xms2m"
ExecStart=%dir%/bin/signal-cli --config /var/lib/signal-cli daemon --system
ExecStart=%dir%/bin/signal-cli --config /var/lib/signal-cli daemon --dbus-system
User=signal-cli
BusName=org.asamk.Signal
# JVM always exits with 143 in reaction to SIGTERM signal
SuccessExitStatus=143
[Install]
Alias=dbus-org.asamk.Signal.service

View file

@ -8,11 +8,9 @@ After=network-online.target
[Service]
Type=dbus
Environment="SIGNAL_CLI_OPTS=-Xms2m"
ExecStart=%dir%/bin/signal-cli -a %I --config /var/lib/signal-cli daemon --system
ExecStart=%dir%/bin/signal-cli -a %I --config /var/lib/signal-cli daemon --dbus-system
User=signal-cli
BusName=org.asamk.Signal
# JVM always exits with 143 in reaction to SIGTERM signal
SuccessExitStatus=143
[Install]
Alias=dbus-org.asamk.Signal.service

View file

@ -1,12 +1,16 @@
[
{
"name":"[B"
},
{
"name":"[Z"
},
{
"name":"[[B"
},
{
"name":"com.sun.security.auth.module.UnixSystem",
"fields":[
{"name":"gid"},
{"name":"groups"},
{"name":"uid"},
{"name":"username"}
]
"fields":[{"name":"gid"}, {"name":"groups"}, {"name":"uid"}, {"name":"username"}]
},
{
"name":"java.lang.Boolean",
@ -14,14 +18,18 @@
},
{
"name":"java.lang.Class",
"methods":[{"name":"getCanonicalName","parameterTypes":[] }]
"methods":[{"name":"getCanonicalName","parameterTypes":[] }, {"name":"getClassLoader","parameterTypes":[] }]
},
{
"name":"java.lang.ClassLoader",
"methods":[
{"name":"getPlatformClassLoader","parameterTypes":[] },
{"name":"loadClass","parameterTypes":["java.lang.String"] }
]
"methods":[{"name":"getPlatformClassLoader","parameterTypes":[] }, {"name":"loadClass","parameterTypes":["java.lang.String"] }]
},
{
"name":"java.lang.ClassNotFoundException"
},
{
"name":"java.lang.Enum",
"methods":[{"name":"ordinal","parameterTypes":[] }]
},
{
"name":"java.lang.IllegalArgumentException",
@ -31,63 +39,104 @@
"name":"java.lang.IllegalStateException",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"java.lang.Long",
"methods":[{"name":"<init>","parameterTypes":["long"] }]
},
{
"name":"java.lang.NoClassDefFoundError"
},
{
"name":"java.lang.NoSuchMethodError"
},
{
"name":"java.lang.String"
},
{
"name":"java.lang.Thread",
"methods":[{"name":"currentThread","parameterTypes":[] }, {"name":"getStackTrace","parameterTypes":[] }]
},
{
"name":"java.lang.Throwable",
"methods":[
{"name":"getMessage","parameterTypes":[] },
{"name":"toString","parameterTypes":[] }
]
"methods":[{"name":"getMessage","parameterTypes":[] }, {"name":"setStackTrace","parameterTypes":["java.lang.StackTraceElement[]"] }, {"name":"toString","parameterTypes":[] }]
},
{
"name":"java.lang.UnsatisfiedLinkError",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"java.util.HashMap",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"java.util.Map",
"methods":[{"name":"get","parameterTypes":["java.lang.Object"] }, {"name":"put","parameterTypes":["java.lang.Object","java.lang.Object"] }, {"name":"remove","parameterTypes":["java.lang.Object"] }]
},
{
"name":"java.util.UUID",
"methods":[
{"name":"<init>","parameterTypes":["long","long"] },
{"name":"getLeastSignificantBits","parameterTypes":[] },
{"name":"getMostSignificantBits","parameterTypes":[] }
]
"methods":[{"name":"<init>","parameterTypes":["long","long"] }, {"name":"getLeastSignificantBits","parameterTypes":[] }, {"name":"getMostSignificantBits","parameterTypes":[] }]
},
{
"name":"jdk.internal.loader.ClassLoaders$AppClassLoader"
},
{
"name":"jdk.internal.loader.ClassLoaders$PlatformClassLoader"
},
{
"name":"org.asamk.signal.manager.storage.protocol.SignalProtocolStore",
"methods":[
{"name":"getIdentity","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress"] },
{"name":"getIdentityKeyPair","parameterTypes":[] },
{"name":"getLocalRegistrationId","parameterTypes":[] },
{"name":"isTrustedIdentity","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","org.signal.libsignal.protocol.IdentityKey","org.signal.libsignal.protocol.state.IdentityKeyStore$Direction"] },
{"name":"loadPreKey","parameterTypes":["int"] },
{"name":"loadSenderKey","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","java.util.UUID"] },
{"name":"loadSession","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress"] },
{"name":"loadSignedPreKey","parameterTypes":["int"] },
{"name":"removePreKey","parameterTypes":["int"] },
{"name":"saveIdentity","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","org.signal.libsignal.protocol.IdentityKey"] },
{"name":"storeSenderKey","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","java.util.UUID","org.signal.libsignal.protocol.groups.state.SenderKeyRecord"] },
{"name":"storeSession","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","org.signal.libsignal.protocol.state.SessionRecord"] }
]
"methods":[{"name":"getIdentity","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress"] }, {"name":"getIdentityKeyPair","parameterTypes":[] }, {"name":"getLocalRegistrationId","parameterTypes":[] }, {"name":"isTrustedIdentity","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","org.signal.libsignal.protocol.IdentityKey","org.signal.libsignal.protocol.state.IdentityKeyStore$Direction"] }, {"name":"loadKyberPreKey","parameterTypes":["int"] }, {"name":"loadPreKey","parameterTypes":["int"] }, {"name":"loadSenderKey","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","java.util.UUID"] }, {"name":"loadSession","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress"] }, {"name":"loadSignedPreKey","parameterTypes":["int"] }, {"name":"markKyberPreKeyUsed","parameterTypes":["int"] }, {"name":"removePreKey","parameterTypes":["int"] }, {"name":"saveIdentity","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","org.signal.libsignal.protocol.IdentityKey"] }, {"name":"storeSenderKey","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","java.util.UUID","org.signal.libsignal.protocol.groups.state.SenderKeyRecord"] }, {"name":"storeSession","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","org.signal.libsignal.protocol.state.SessionRecord"] }]
},
{
"name":"org.asamk.signal.manager.storage.senderKeys.SenderKeyStore",
"methods":[{"name":"loadSenderKey","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","java.util.UUID"] }, {"name":"storeSenderKey","parameterTypes":["org.signal.libsignal.protocol.SignalProtocolAddress","java.util.UUID","org.signal.libsignal.protocol.groups.state.SenderKeyRecord"] }]
},
{
"name":"org.graalvm.jniutils.JNIExceptionWrapperEntryPoints",
"methods":[{"name":"getClassName","parameterTypes":["java.lang.Class"] }]
},
{
"name":"org.signal.libsignal.internal.CompletableFuture",
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"complete","parameterTypes":["java.lang.Object"] }, {"name":"completeExceptionally","parameterTypes":["java.lang.Throwable"] }, {"name":"setCancellationId","parameterTypes":["long"] }]
},
{
"name":"org.signal.libsignal.internal.NativeHandleGuard$SimpleOwner",
"methods":[{"name":"unsafeNativeHandleWithoutGuard","parameterTypes":[] }]
},
{
"name":"org.signal.libsignal.net.CdsiLookupResponse",
"methods":[{"name":"<init>","parameterTypes":["java.util.Map","int"] }]
},
{
"name":"org.signal.libsignal.net.CdsiLookupResponse$Entry",
"methods":[{"name":"<init>","parameterTypes":["byte[]","byte[]"] }]
},
{
"name":"org.signal.libsignal.net.ChatService"
},
{
"name":"org.signal.libsignal.net.ChatService$DebugInfo"
},
{
"name":"org.signal.libsignal.net.ChatService$Response"
},
{
"name":"org.signal.libsignal.net.ChatService$ResponseAndDebugInfo"
},
{
"name":"org.signal.libsignal.net.NetworkException",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"org.signal.libsignal.net.RetryLaterException",
"methods":[{"name":"<init>","parameterTypes":["long"] }]
},
{
"name":"org.signal.libsignal.protocol.DuplicateMessageException",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"org.signal.libsignal.protocol.IdentityKey",
"methods":[
{"name":"<init>","parameterTypes":["long"] },
{"name":"<init>","parameterTypes":["byte[]"] },
{"name":"serialize","parameterTypes":[] }
]
"methods":[{"name":"<init>","parameterTypes":["long"] }, {"name":"<init>","parameterTypes":["byte[]"] }, {"name":"serialize","parameterTypes":[] }]
},
{
"name":"org.signal.libsignal.protocol.IdentityKeyPair",
@ -110,15 +159,16 @@
},
{
"name":"org.signal.libsignal.protocol.SignalProtocolAddress",
"methods":[
{"name":"<init>","parameterTypes":["long"] },
{"name":"<init>","parameterTypes":["java.lang.String","int"] }
]
"methods":[{"name":"<init>","parameterTypes":["long"] }, {"name":"<init>","parameterTypes":["java.lang.String","int"] }]
},
{
"name":"org.signal.libsignal.protocol.UntrustedIdentityException",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"org.signal.libsignal.protocol.fingerprint.FingerprintParsingException",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"org.signal.libsignal.protocol.groups.state.SenderKeyRecord",
"fields":[{"name":"unsafeHandle"}],
@ -155,10 +205,17 @@
},
{
"name":"org.signal.libsignal.protocol.state.IdentityKeyStore$Direction",
"fields":[
{"name":"RECEIVING"},
{"name":"SENDING"}
]
"fields":[{"name":"RECEIVING"}, {"name":"SENDING"}]
},
{
"name":"org.signal.libsignal.protocol.state.IdentityKeyStore$IdentityChange"
},
{
"name":"org.signal.libsignal.protocol.state.KyberPreKeyRecord",
"fields":[{"name":"unsafeHandle"}]
},
{
"name":"org.signal.libsignal.protocol.state.KyberPreKeyStore"
},
{
"name":"org.signal.libsignal.protocol.state.PreKeyRecord",
@ -170,10 +227,7 @@
{
"name":"org.signal.libsignal.protocol.state.SessionRecord",
"fields":[{"name":"unsafeHandle"}],
"methods":[
{"name":"<init>","parameterTypes":["long"] },
{"name":"<init>","parameterTypes":["byte[]"] }
]
"methods":[{"name":"<init>","parameterTypes":["long"] }, {"name":"<init>","parameterTypes":["byte[]"] }]
},
{
"name":"org.signal.libsignal.protocol.state.SessionStore"
@ -185,47 +239,66 @@
{
"name":"org.signal.libsignal.protocol.state.SignedPreKeyStore"
},
{
"name":"org.signal.libsignal.usernames.BadDiscriminatorCharacterException",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"org.signal.libsignal.usernames.BadNicknameCharacterException",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"org.signal.libsignal.usernames.CannotBeEmptyException",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"org.signal.libsignal.usernames.DiscriminatorCannotBeZeroException",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"org.signal.libsignal.usernames.MissingSeparatorException",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"org.signal.libsignal.usernames.NicknameTooLongException",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"org.signal.libsignal.usernames.NicknameTooShortException",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"org.signal.libsignal.zkgroup.InvalidInputException",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
},
{
"name":"org.sqlite.BusyHandler",
"methods":[{"name":"callback","parameterTypes":["int"] }]
},
{
"name":"org.sqlite.Collation",
"methods":[{"name":"xCompare","parameterTypes":["java.lang.String","java.lang.String"] }]
},
{
"name":"org.sqlite.Function",
"fields":[
{"name":"args"},
{"name":"context"},
{"name":"value"}
],
"fields":[{"name":"args"}, {"name":"context"}, {"name":"value"}],
"methods":[{"name":"xFunc","parameterTypes":[] }]
},
{
"name":"org.sqlite.Function$Aggregate",
"methods":[
{"name":"clone","parameterTypes":[] },
{"name":"xFinal","parameterTypes":[] },
{"name":"xStep","parameterTypes":[] }
]
"methods":[{"name":"clone","parameterTypes":[] }, {"name":"xFinal","parameterTypes":[] }, {"name":"xStep","parameterTypes":[] }]
},
{
"name":"org.sqlite.Function$Window",
"methods":[
{"name":"xInverse","parameterTypes":[] },
{"name":"xValue","parameterTypes":[] }
]
"methods":[{"name":"xInverse","parameterTypes":[] }, {"name":"xValue","parameterTypes":[] }]
},
{
"name":"org.sqlite.ProgressHandler"
"name":"org.sqlite.ProgressHandler",
"methods":[{"name":"progress","parameterTypes":[] }]
},
{
"name":"org.sqlite.core.DB",
"methods":[
{"name":"throwex","parameterTypes":[] },
{"name":"throwex","parameterTypes":["int"] }
]
"methods":[{"name":"onCommit","parameterTypes":["boolean"] }, {"name":"onUpdate","parameterTypes":["int","java.lang.String","java.lang.String","long"] }, {"name":"throwex","parameterTypes":[] }, {"name":"throwex","parameterTypes":["int"] }]
},
{
"name":"org.sqlite.core.DB$ProgressObserver",
@ -233,16 +306,7 @@
},
{
"name":"org.sqlite.core.NativeDB",
"fields":[
{"name":"busyHandler"},
{"name":"commitListener"},
{"name":"pointer"},
{"name":"progressHandler"},
{"name":"updateListener"}
],
"methods":[
{"name":"stringToUtf8ByteArray","parameterTypes":["java.lang.String"] },
{"name":"throwex","parameterTypes":["java.lang.String"] }
]
"fields":[{"name":"busyHandler"}, {"name":"commitListener"}, {"name":"pointer"}, {"name":"progressHandler"}, {"name":"updateListener"}],
"methods":[{"name":"stringToUtf8ByteArray","parameterTypes":["java.lang.String"] }, {"name":"throwex","parameterTypes":["java.lang.String"] }]
}
]

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,244 +1,226 @@
{
"resources":{
"includes":[
{
"pattern":"\\QMETA-INF/maven/org.xerial/sqlite-jdbc/pom.properties\\E"
},
{
"pattern":"\\QMETA-INF/services/ch.qos.logback.classic.spi.Configurator\\E"
},
{
"pattern":"\\QMETA-INF/services/java.sql.Driver\\E"
},
{
"pattern":"\\QMETA-INF/services/org.freedesktop.dbus.spi.transport.ITransportProvider\\E"
},
{
"pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AG\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AI\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AR\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AS\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AT\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AU\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AZ\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BB\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BD\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BE\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BM\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BO\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BR\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BS\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CA\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CH\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CI\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CL\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CN\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CO\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CR\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CZ\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_DE\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_DK\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_EC\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_EE\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_ES\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_FI\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_FR\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GB\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GR\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_HK\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_HR\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_HU\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_ID\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IL\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IN\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IR\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IT\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_JP\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LV\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MM\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MO\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MX\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MY\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_NG\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_NL\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_NZ\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PA\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PE\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PH\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PL\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_RO\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_RU\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SA\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SI\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SK\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_TH\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_TR\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_UA\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_UG\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_US\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_VE\\E"
},
{
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_XK\\E"
},
{
"pattern":"\\Qjni/x86_64-Linux/libjffi-1.2.so\\E"
},
{
"pattern":"\\Qlibsignal_jni.so\\E"
},
{
"pattern":"\\Qorg/asamk/signal/manager/config/ias.store\\E"
},
{
"pattern":"\\Qorg/asamk/signal/manager/config/whisper.store\\E"
},
{
"pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E"
},
{
"pattern":"\\Qorg/sqlite/native/Linux/x86_64/libsqlitejdbc.so\\E"
},
{
"pattern":"\\Qsqlite-jdbc.properties\\E"
},
{
"pattern":"com/google/i18n/phonenumbers/data/.*"
}
]},
"includes":[{
"pattern":"\\QMETA-INF/maven/org.xerial/sqlite-jdbc/pom.properties\\E"
}, {
"pattern":"\\QMETA-INF/services/ch.qos.logback.classic.spi.Configurator\\E"
}, {
"pattern":"\\QMETA-INF/services/com.sun.net.httpserver.spi.HttpServerProvider\\E"
}, {
"pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E"
}, {
"pattern":"\\QMETA-INF/services/java.net.spi.InetAddressResolverProvider\\E"
}, {
"pattern":"\\QMETA-INF/services/java.net.spi.URLStreamHandlerProvider\\E"
}, {
"pattern":"\\QMETA-INF/services/java.nio.channels.spi.SelectorProvider\\E"
}, {
"pattern":"\\QMETA-INF/services/java.nio.file.spi.FileTypeDetector\\E"
}, {
"pattern":"\\QMETA-INF/services/java.sql.Driver\\E"
}, {
"pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E"
}, {
"pattern":"\\QMETA-INF/services/java.util.spi.ResourceBundleControlProvider\\E"
}, {
"pattern":"\\QMETA-INF/services/kotlin.reflect.jvm.internal.impl.builtins.BuiltInsLoader\\E"
}, {
"pattern":"\\QMETA-INF/services/kotlin.reflect.jvm.internal.impl.resolve.ExternalOverridabilityCondition\\E"
}, {
"pattern":"\\QMETA-INF/services/kotlin.reflect.jvm.internal.impl.util.ModuleVisibilityHelper\\E"
}, {
"pattern":"\\QMETA-INF/services/org.freedesktop.dbus.spi.message.ISocketProvider\\E"
}, {
"pattern":"\\QMETA-INF/services/org.freedesktop.dbus.spi.transport.ITransportProvider\\E"
}, {
"pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AG\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AI\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AR\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AS\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AT\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AU\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_AZ\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BB\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BD\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BE\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BM\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BO\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BR\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_BS\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CA\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CH\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CI\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CL\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CN\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CO\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CR\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_CZ\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_DE\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_DK\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_EC\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_EE\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_ES\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_FI\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_FR\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GB\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_GR\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_HK\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_HR\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_HU\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_ID\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IL\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IN\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IR\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_IT\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_JP\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_LV\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MM\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MO\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MX\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_MY\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_NG\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_NL\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_NZ\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PA\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PE\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PH\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_PL\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_RO\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_RU\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SA\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SI\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_SK\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_TH\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_TR\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_UA\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_UG\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_US\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_VE\\E"
}, {
"pattern":"\\Qcom/google/i18n/phonenumbers/data/PhoneNumberMetadataProto_XK\\E"
}, {
"pattern":"\\Qjni/x86_64-Linux/libjffi-1.2.so\\E"
}, {
"pattern":"\\Qkotlin/annotation/annotation.kotlin_builtins\\E"
}, {
"pattern":"\\Qkotlin/collections/collections.kotlin_builtins\\E"
}, {
"pattern":"\\Qkotlin/coroutines/coroutines.kotlin_builtins\\E"
}, {
"pattern":"\\Qkotlin/internal/internal.kotlin_builtins\\E"
}, {
"pattern":"\\Qkotlin/jvm/jvm.kotlin_builtins\\E"
}, {
"pattern":"\\Qkotlin/kotlin.kotlin_builtins\\E"
}, {
"pattern":"\\Qkotlin/ranges/ranges.kotlin_builtins\\E"
}, {
"pattern":"\\Qkotlin/reflect/reflect.kotlin_builtins\\E"
}, {
"pattern":"\\Qlibsignal_jni.so\\E"
}, {
"pattern":"\\Qlibsignal_jni_aarch64.dylib\\E"
}, {
"pattern":"\\Qlibsignal_jni_amd64.dylib\\E"
}, {
"pattern":"\\Qlibsignal_jni_amd64.so\\E"
}, {
"pattern":"\\Qorg/asamk/signal/manager/config/ias.store\\E"
}, {
"pattern":"\\Qorg/asamk/signal/manager/config/whisper.store\\E"
}, {
"pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E"
}, {
"pattern":"\\Qorg/sqlite/native/Linux/x86_64/libsqlitejdbc.so\\E"
}, {
"pattern":"\\Qsignal_jni.dll\\E"
}, {
"pattern":"\\Qsignal_jni_amd64.dll\\E"
}, {
"pattern":"\\Qsqlite-jdbc.properties\\E"
}, {
"pattern":"com/google/i18n/phonenumbers/data/.*"
}, {
"pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt67b/nfc.nrm\\E"
}, {
"pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt67b/uprops.icu\\E"
}, {
"pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt72b/nfc.nrm\\E"
}, {
"pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt72b/uprops.icu\\E"
}, {
"pattern":"java.base:\\Qsun/net/idn/uidna.spp\\E"
}, {
"pattern":"java.base:\\Qsun/net/www/content-types.properties\\E"
}, {
"pattern":"java.base:\\Qsun/text/resources/LineBreakIteratorData\\E"
}]},
"bundles":[{
"name":"net.sourceforge.argparse4j.internal.ArgumentParserImpl",
"locales":[
"",
"en",
"und"
]
}]
"name":"net.sourceforge.argparse4j.internal.ArgumentParserImpl",
"locales":["", "de", "en", "und"]
}]
}

View file

@ -2,5 +2,7 @@
"types":[
],
"lambdaCapturingTypes":[
],
"proxies":[
]
}

17
gradle/libs.versions.toml Normal file
View file

@ -0,0 +1,17 @@
[versions]
slf4j = "2.0.17"
[libraries]
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.81"
jackson-databind = "com.fasterxml.jackson.core:jackson-databind:2.19.1"
argparse4j = "net.sourceforge.argparse4j:argparse4j:0.9.0"
dbusjava = "com.github.hypfvieh:dbus-java-transport-native-unixsocket:5.0.0"
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
slf4j-jul = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" }
logback = "ch.qos.logback:logback-classic:1.5.18"
signalservice = "com.github.turasa:signal-service-java:2.15.3_unofficial_127"
sqlite = "org.xerial:sqlite-jdbc:3.50.2.0"
hikari = "com.zaxxer:HikariCP:6.3.0"
junit-jupiter = "org.junit.jupiter:junit-jupiter:5.13.2"
junit-launcher = "org.junit.platform:junit-platform-launcher:1.13.2"

Binary file not shown.

View file

@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

43
gradlew vendored
View file

@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -80,13 +82,11 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@ -133,22 +133,29 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@ -193,16 +200,20 @@ if "$cygwin" || "$msys" ; then
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.

27
gradlew.bat vendored
View file

@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@ -26,6 +28,7 @@ if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@ -42,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@ -56,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell

View file

@ -4,23 +4,37 @@ plugins {
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
repositories {
mavenLocal()
mavenCentral()
}
val libsignalClientPath = project.findProperty("libsignal_client_path")?.toString()
dependencies {
implementation("com.github.turasa", "signal-service-java", "2.15.3_unofficial_58")
implementation("com.fasterxml.jackson.core", "jackson-databind", "2.13.4")
implementation("com.google.protobuf", "protobuf-javalite", "3.11.4")
implementation("org.bouncycastle", "bcprov-jdk15on", "1.70")
implementation("org.slf4j", "slf4j-api", "2.0.3")
implementation("org.xerial", "sqlite-jdbc", "3.39.3.0")
implementation("com.zaxxer", "HikariCP", "5.0.1")
if (libsignalClientPath == null) {
implementation(libs.signalservice)
} else {
implementation(libs.signalservice) {
exclude(group = "org.signal", module = "libsignal-client")
}
implementation(files(libsignalClientPath))
}
implementation(libs.jackson.databind)
implementation(libs.bouncycastle)
implementation(libs.slf4j.api)
implementation(libs.sqlite)
implementation(libs.hikari)
testImplementation(libs.junit.jupiter)
testRuntimeOnly(libs.junit.launcher)
}
tasks.named<Test>("test") {
useJUnitPlatform()
}
configurations {

View file

@ -1,55 +0,0 @@
package org.asamk.signal.manager;
import org.asamk.signal.manager.util.IOUtils;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class AttachmentStore {
private final File attachmentsPath;
public AttachmentStore(final File attachmentsPath) {
this.attachmentsPath = attachmentsPath;
}
public void storeAttachmentPreview(
final SignalServiceAttachmentRemoteId attachmentId, final AttachmentStorer storer
) throws IOException {
storeAttachment(getAttachmentPreviewFile(attachmentId), storer);
}
public void storeAttachment(
final SignalServiceAttachmentRemoteId attachmentId, final AttachmentStorer storer
) throws IOException {
storeAttachment(getAttachmentFile(attachmentId), storer);
}
private void storeAttachment(final File attachmentFile, final AttachmentStorer storer) throws IOException {
createAttachmentsDir();
try (OutputStream output = new FileOutputStream(attachmentFile)) {
storer.store(output);
}
}
private File getAttachmentPreviewFile(SignalServiceAttachmentRemoteId attachmentId) {
return new File(attachmentsPath, attachmentId.toString() + ".preview");
}
public File getAttachmentFile(SignalServiceAttachmentRemoteId attachmentId) {
return new File(attachmentsPath, attachmentId.toString());
}
private void createAttachmentsDir() throws IOException {
IOUtils.createPrivateDirectories(attachmentsPath);
}
@FunctionalInterface
public interface AttachmentStorer {
void store(OutputStream outputStream) throws IOException;
}
}

View file

@ -1,17 +0,0 @@
package org.asamk.signal.manager;
import org.asamk.signal.manager.helper.Context;
import org.asamk.signal.manager.jobs.Job;
public class JobExecutor {
private final Context context;
public JobExecutor(final Context context) {
this.context = context;
}
public void enqueueJob(Job job) {
job.run(context);
}
}

View file

@ -1,22 +1,45 @@
package org.asamk.signal.manager;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import org.asamk.signal.manager.api.AlreadyReceivingException;
import org.asamk.signal.manager.api.AttachmentInvalidException;
import org.asamk.signal.manager.api.CaptchaRejectedException;
import org.asamk.signal.manager.api.CaptchaRequiredException;
import org.asamk.signal.manager.api.Configuration;
import org.asamk.signal.manager.api.Device;
import org.asamk.signal.manager.api.DeviceLimitExceededException;
import org.asamk.signal.manager.api.DeviceLinkUrl;
import org.asamk.signal.manager.api.Group;
import org.asamk.signal.manager.api.GroupId;
import org.asamk.signal.manager.api.GroupInviteLinkUrl;
import org.asamk.signal.manager.api.GroupNotFoundException;
import org.asamk.signal.manager.api.GroupSendingNotAllowedException;
import org.asamk.signal.manager.api.Identity;
import org.asamk.signal.manager.api.IdentityVerificationCode;
import org.asamk.signal.manager.api.InactiveGroupLinkException;
import org.asamk.signal.manager.api.IncorrectPinException;
import org.asamk.signal.manager.api.InvalidDeviceLinkException;
import org.asamk.signal.manager.api.InvalidStickerException;
import org.asamk.signal.manager.api.InvalidUsernameException;
import org.asamk.signal.manager.api.LastGroupAdminException;
import org.asamk.signal.manager.api.Message;
import org.asamk.signal.manager.api.MessageEnvelope;
import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException;
import org.asamk.signal.manager.api.NotAGroupMemberException;
import org.asamk.signal.manager.api.NotPrimaryDeviceException;
import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.api.PendingAdminApprovalException;
import org.asamk.signal.manager.api.PinLockMissingException;
import org.asamk.signal.manager.api.PinLockedException;
import org.asamk.signal.manager.api.RateLimitException;
import org.asamk.signal.manager.api.ReceiveConfig;
import org.asamk.signal.manager.api.Recipient;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.SendGroupMessageResults;
import org.asamk.signal.manager.api.SendMessageResults;
import org.asamk.signal.manager.api.StickerPack;
import org.asamk.signal.manager.api.StickerPackId;
import org.asamk.signal.manager.api.StickerPackInvalidException;
import org.asamk.signal.manager.api.StickerPackUrl;
import org.asamk.signal.manager.api.TypingAction;
@ -24,20 +47,16 @@ import org.asamk.signal.manager.api.UnregisteredRecipientException;
import org.asamk.signal.manager.api.UpdateGroup;
import org.asamk.signal.manager.api.UpdateProfile;
import org.asamk.signal.manager.api.UserStatus;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
import org.asamk.signal.manager.groups.GroupNotFoundException;
import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.storage.recipients.Profile;
import org.asamk.signal.manager.storage.recipients.Recipient;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import org.asamk.signal.manager.api.UsernameLinkUrl;
import org.asamk.signal.manager.api.UsernameStatus;
import org.asamk.signal.manager.api.VerificationMethodNotAvailableException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.io.InputStream;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
@ -48,7 +67,22 @@ import java.util.Set;
public interface Manager extends Closeable {
static boolean isValidNumber(final String e164Number, final String countryCode) {
return PhoneNumberFormatter.isValidNumber(e164Number, countryCode);
return PhoneNumberUtil.getInstance().isPossibleNumber(e164Number, countryCode);
}
static boolean isSignalClientAvailable() {
final Logger logger = LoggerFactory.getLogger(Manager.class);
try {
try {
org.signal.libsignal.internal.Native.UuidCiphertext_CheckValidContents(new byte[0]);
} catch (Exception e) {
logger.trace("Expected exception when checking libsignal-client: {}", e.getMessage());
}
return true;
} catch (UnsatisfiedLinkError e) {
logger.warn("Failed to call libsignal-client: {}", e.getMessage());
return false;
}
}
String getSelfNumber();
@ -60,13 +94,20 @@ public interface Manager extends Closeable {
* @return A map of numbers to canonicalized number and uuid. If a number is not registered the uuid is null.
* @throws IOException if it's unable to get the contacts to check if they're registered
*/
Map<String, UserStatus> getUserStatus(Set<String> numbers) throws IOException;
Map<String, UserStatus> getUserStatus(Set<String> numbers) throws IOException, RateLimitException;
void updateAccountAttributes(String deviceName) throws IOException;
Map<String, UsernameStatus> getUsernameStatus(Set<String> usernames) throws IOException;
void updateAccountAttributes(
String deviceName,
Boolean unrestrictedUnidentifiedSender,
final Boolean discoverableByNumber,
final Boolean numberSharing
) throws IOException;
Configuration getConfiguration();
void updateConfiguration(Configuration configuration) throws IOException, NotPrimaryDeviceException;
void updateConfiguration(Configuration configuration) throws NotPrimaryDeviceException;
/**
* Update the user's profile.
@ -74,60 +115,99 @@ public interface Manager extends Closeable {
*/
void updateProfile(UpdateProfile updateProfile) throws IOException;
String getUsername();
UsernameLinkUrl getUsernameLink();
/**
* Set a username for the account.
* If the username is null, it will be deleted.
*/
void setUsername(String username) throws IOException, InvalidUsernameException;
/**
* Set a username for the account.
* If the username is null, it will be deleted.
*/
void deleteUsername() throws IOException;
void startChangeNumber(
String newNumber,
boolean voiceVerification,
String captcha
) throws RateLimitException, IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, NotPrimaryDeviceException, VerificationMethodNotAvailableException;
void finishChangeNumber(
String newNumber,
String verificationCode,
String pin
) throws IncorrectPinException, PinLockedException, IOException, NotPrimaryDeviceException, PinLockMissingException;
void unregister() throws IOException;
void deleteAccount() throws IOException;
void submitRateLimitRecaptchaChallenge(String challenge, String captcha) throws IOException;
void submitRateLimitRecaptchaChallenge(
String challenge,
String captcha
) throws IOException, CaptchaRejectedException;
List<Device> getLinkedDevices() throws IOException;
void removeLinkedDevices(int deviceId) throws IOException;
void removeLinkedDevices(int deviceId) throws IOException, NotPrimaryDeviceException;
void addDeviceLink(URI linkUri) throws IOException, InvalidDeviceLinkException;
void addDeviceLink(DeviceLinkUrl linkUri) throws IOException, InvalidDeviceLinkException, NotPrimaryDeviceException, DeviceLimitExceededException;
void setRegistrationLockPin(Optional<String> pin) throws IOException, NotPrimaryDeviceException;
Profile getRecipientProfile(RecipientIdentifier.Single recipient) throws IOException, UnregisteredRecipientException;
List<Group> getGroups();
SendGroupMessageResults quitGroup(
GroupId groupId, Set<RecipientIdentifier.Single> groupAdmins
GroupId groupId,
Set<RecipientIdentifier.Single> groupAdmins
) throws GroupNotFoundException, IOException, NotAGroupMemberException, LastGroupAdminException, UnregisteredRecipientException;
void deleteGroup(GroupId groupId) throws IOException;
Pair<GroupId, SendGroupMessageResults> createGroup(
String name, Set<RecipientIdentifier.Single> members, File avatarFile
String name,
Set<RecipientIdentifier.Single> members,
String avatarFile
) throws IOException, AttachmentInvalidException, UnregisteredRecipientException;
SendGroupMessageResults updateGroup(
final GroupId groupId, final UpdateGroup updateGroup
final GroupId groupId,
final UpdateGroup updateGroup
) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException, UnregisteredRecipientException;
Pair<GroupId, SendGroupMessageResults> joinGroup(
GroupInviteLinkUrl inviteLinkUrl
) throws IOException, InactiveGroupLinkException;
) throws IOException, InactiveGroupLinkException, PendingAdminApprovalException;
SendMessageResults sendTypingMessage(
TypingAction action, Set<RecipientIdentifier> recipients
TypingAction action,
Set<RecipientIdentifier> recipients
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
SendMessageResults sendReadReceipt(
RecipientIdentifier.Single sender, List<Long> messageIds
) throws IOException;
SendMessageResults sendReadReceipt(RecipientIdentifier.Single sender, List<Long> messageIds);
SendMessageResults sendViewedReceipt(
RecipientIdentifier.Single sender, List<Long> messageIds
) throws IOException;
SendMessageResults sendViewedReceipt(RecipientIdentifier.Single sender, List<Long> messageIds);
SendMessageResults sendMessage(
Message message, Set<RecipientIdentifier> recipients
Message message,
Set<RecipientIdentifier> recipients,
boolean notifySelf
) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException;
SendMessageResults sendEditMessage(
Message message,
Set<RecipientIdentifier> recipients,
long editTargetTimestamp
) throws IOException, AttachmentInvalidException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException, InvalidStickerException;
SendMessageResults sendRemoteDeleteMessage(
long targetSentTimestamp, Set<RecipientIdentifier> recipients
long targetSentTimestamp,
Set<RecipientIdentifier> recipients
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
SendMessageResults sendMessageReaction(
@ -135,36 +215,54 @@ public interface Manager extends Closeable {
boolean remove,
RecipientIdentifier.Single targetAuthor,
long targetSentTimestamp,
Set<RecipientIdentifier> recipients
Set<RecipientIdentifier> recipients,
final boolean isStory
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException;
SendMessageResults sendPaymentNotificationMessage(
byte[] receipt, String note, RecipientIdentifier.Single recipient
byte[] receipt,
String note,
RecipientIdentifier.Single recipient
) throws IOException;
SendMessageResults sendEndSessionMessage(Set<RecipientIdentifier.Single> recipients) throws IOException;
SendMessageResults sendMessageRequestResponse(
MessageEnvelope.Sync.MessageRequestResponse.Type type,
Set<RecipientIdentifier> recipientIdentifiers
);
void hideRecipient(RecipientIdentifier.Single recipient);
void deleteRecipient(RecipientIdentifier.Single recipient);
void deleteContact(RecipientIdentifier.Single recipient);
void setContactName(
RecipientIdentifier.Single recipient, String givenName, final String familyName
) throws NotPrimaryDeviceException, IOException, UnregisteredRecipientException;
final RecipientIdentifier.Single recipient,
final String givenName,
final String familyName,
final String nickGivenName,
final String nickFamilyName,
final String note
) throws NotPrimaryDeviceException, UnregisteredRecipientException;
void setContactsBlocked(
Collection<RecipientIdentifier.Single> recipient, boolean blocked
Collection<RecipientIdentifier.Single> recipient,
boolean blocked
) throws NotPrimaryDeviceException, IOException, UnregisteredRecipientException;
void setGroupsBlocked(
Collection<GroupId> groupId, boolean blocked
Collection<GroupId> groupId,
boolean blocked
) throws GroupNotFoundException, IOException, NotPrimaryDeviceException;
/**
* Change the expiration timer for a contact
*/
void setExpirationTimer(
RecipientIdentifier.Single recipient, int messageExpirationTimer
RecipientIdentifier.Single recipient,
int messageExpirationTimer
) throws IOException, UnregisteredRecipientException;
/**
@ -175,6 +273,8 @@ public interface Manager extends Closeable {
*/
StickerPackUrl uploadStickerPack(File path) throws IOException, StickerPackInvalidException;
void installStickerPack(StickerPackUrl url) throws IOException;
List<StickerPack> getStickerPacks();
void requestAllSyncData() throws IOException;
@ -200,17 +300,16 @@ public interface Manager extends Closeable {
/**
* Receive new messages from server, returns if no new message arrive in a timespan of timeout.
*/
void receiveMessages(Duration timeout, ReceiveMessageHandler handler) throws IOException;
void receiveMessages(
Optional<Duration> timeout,
Optional<Integer> maxMessages,
ReceiveMessageHandler handler
) throws IOException, AlreadyReceivingException;
/**
* Receive new messages from server, returns only if the thread is interrupted.
*/
void receiveMessages(ReceiveMessageHandler handler) throws IOException;
void stopReceiveMessages();
void setReceiveConfig(ReceiveConfig receiveConfig);
boolean hasCaughtUpWithOldMessages();
boolean isContactBlocked(RecipientIdentifier.Single recipient);
void sendContacts() throws IOException;
@ -231,33 +330,13 @@ public interface Manager extends Closeable {
List<Identity> getIdentities(RecipientIdentifier.Single recipient);
/**
* Trust this the identity with this fingerprint
* Trust this the identity with this fingerprint/safetyNumber
*
* @param recipient account of the identity
* @param fingerprint Fingerprint
* @param recipient account of the identity
*/
boolean trustIdentityVerified(
RecipientIdentifier.Single recipient, byte[] fingerprint
) throws UnregisteredRecipientException;
/**
* Trust this the identity with this safety number
*
* @param recipient account of the identity
* @param safetyNumber Safety number
*/
boolean trustIdentityVerifiedSafetyNumber(
RecipientIdentifier.Single recipient, String safetyNumber
) throws UnregisteredRecipientException;
/**
* Trust this the identity with this scannable safety number
*
* @param recipient account of the identity
* @param safetyNumber Scannable safety number
*/
boolean trustIdentityVerifiedSafetyNumber(
RecipientIdentifier.Single recipient, byte[] safetyNumber
RecipientIdentifier.Single recipient,
IdentityVerificationCode verificationCode
) throws UnregisteredRecipientException;
/**
@ -271,8 +350,18 @@ public interface Manager extends Closeable {
void addClosedListener(Runnable listener);
InputStream retrieveAttachment(final String id) throws IOException;
InputStream retrieveContactAvatar(final RecipientIdentifier.Single recipient) throws IOException, UnregisteredRecipientException;
InputStream retrieveProfileAvatar(final RecipientIdentifier.Single recipient) throws IOException, UnregisteredRecipientException;
InputStream retrieveGroupAvatar(final GroupId groupId) throws IOException;
InputStream retrieveSticker(final StickerPackId stickerPackId, final int stickerId) throws IOException;
@Override
void close() throws IOException;
void close();
interface ReceiveMessageHandler {

View file

@ -1,5 +1,7 @@
package org.asamk.signal.manager;
import org.asamk.signal.manager.internal.LibSignalLogger;
public class ManagerLogger {
public static void initLogger() {

View file

@ -3,7 +3,10 @@ package org.asamk.signal.manager;
import org.asamk.signal.manager.api.CaptchaRequiredException;
import org.asamk.signal.manager.api.IncorrectPinException;
import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException;
import org.asamk.signal.manager.api.PinLockMissingException;
import org.asamk.signal.manager.api.PinLockedException;
import org.asamk.signal.manager.api.RateLimitException;
import org.asamk.signal.manager.api.VerificationMethodNotAvailableException;
import java.io.Closeable;
import java.io.IOException;
@ -11,12 +14,15 @@ import java.io.IOException;
public interface RegistrationManager extends Closeable {
void register(
boolean voiceVerification, String captcha
) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException;
boolean voiceVerification,
String captcha,
final boolean forceRegister
) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethodNotAvailableException;
void verifyAccount(
String verificationCode, String pin
) throws IOException, PinLockedException, IncorrectPinException;
String verificationCode,
String pin
) throws IOException, PinLockedException, IncorrectPinException, PinLockMissingException;
void deleteLocalAccountData() throws IOException;

View file

@ -1,250 +0,0 @@
/*
Copyright (C) 2015-2022 AsamK and contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.asamk.signal.manager;
import org.asamk.signal.manager.api.CaptchaRequiredException;
import org.asamk.signal.manager.api.IncorrectPinException;
import org.asamk.signal.manager.api.NonNormalizedPhoneNumberException;
import org.asamk.signal.manager.api.PinLockedException;
import org.asamk.signal.manager.api.UpdateProfile;
import org.asamk.signal.manager.config.ServiceConfig;
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
import org.asamk.signal.manager.helper.AccountFileUpdater;
import org.asamk.signal.manager.helper.PinHelper;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.util.NumberVerificationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
import org.whispersystems.signalservice.api.push.ACI;
import org.whispersystems.signalservice.api.push.PNI;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.internal.ServiceResponse;
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
import org.whispersystems.signalservice.internal.util.DynamicCredentialsProvider;
import java.io.IOException;
import java.util.function.Consumer;
import static org.asamk.signal.manager.config.ServiceConfig.capabilities;
class RegistrationManagerImpl implements RegistrationManager {
private final static Logger logger = LoggerFactory.getLogger(RegistrationManagerImpl.class);
private SignalAccount account;
private final PathConfig pathConfig;
private final ServiceEnvironmentConfig serviceEnvironmentConfig;
private final String userAgent;
private final Consumer<Manager> newManagerListener;
private final SignalServiceAccountManager accountManager;
private final PinHelper pinHelper;
private final AccountFileUpdater accountFileUpdater;
RegistrationManagerImpl(
SignalAccount account,
PathConfig pathConfig,
ServiceEnvironmentConfig serviceEnvironmentConfig,
String userAgent,
Consumer<Manager> newManagerListener,
AccountFileUpdater accountFileUpdater
) {
this.account = account;
this.pathConfig = pathConfig;
this.accountFileUpdater = accountFileUpdater;
this.serviceEnvironmentConfig = serviceEnvironmentConfig;
this.userAgent = userAgent;
this.newManagerListener = newManagerListener;
GroupsV2Operations groupsV2Operations;
try {
groupsV2Operations = new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration()),
ServiceConfig.GROUP_MAX_SIZE);
} catch (Throwable ignored) {
groupsV2Operations = null;
}
this.accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
new DynamicCredentialsProvider(
// Using empty UUID, because registering doesn't work otherwise
null, null, account.getNumber(), account.getPassword(), SignalServiceAddress.DEFAULT_DEVICE_ID),
userAgent,
groupsV2Operations,
ServiceConfig.AUTOMATIC_NETWORK_RETRY);
final var keyBackupService = accountManager.getKeyBackupService(ServiceConfig.getIasKeyStore(),
serviceEnvironmentConfig.getKeyBackupConfig().getEnclaveName(),
serviceEnvironmentConfig.getKeyBackupConfig().getServiceId(),
serviceEnvironmentConfig.getKeyBackupConfig().getMrenclave(),
10);
final var fallbackKeyBackupServices = serviceEnvironmentConfig.getFallbackKeyBackupConfigs()
.stream()
.map(config -> accountManager.getKeyBackupService(ServiceConfig.getIasKeyStore(),
config.getEnclaveName(),
config.getServiceId(),
config.getMrenclave(),
10))
.toList();
this.pinHelper = new PinHelper(keyBackupService, fallbackKeyBackupServices);
}
@Override
public void register(
boolean voiceVerification, String captcha
) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException {
if (account.isRegistered()
&& account.getServiceEnvironment() != null
&& account.getServiceEnvironment() != serviceEnvironmentConfig.getType()) {
throw new IOException("Account is registered in another environment: " + account.getServiceEnvironment());
}
if (account.getAci() != null && attemptReactivateAccount()) {
return;
}
NumberVerificationUtils.requestVerificationCode(accountManager, captcha, voiceVerification);
}
@Override
public void verifyAccount(
String verificationCode, String pin
) throws IOException, PinLockedException, IncorrectPinException {
final var result = NumberVerificationUtils.verifyNumber(verificationCode,
pin,
pinHelper,
this::verifyAccountWithCode);
final var response = result.first();
final var masterKey = result.second();
if (masterKey == null) {
pin = null;
}
//accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
final var aci = ACI.parseOrNull(response.getUuid());
final var pni = PNI.parseOrNull(response.getPni());
account.finishRegistration(aci, pni, masterKey, pin);
accountFileUpdater.updateAccountIdentifiers(account.getNumber(), aci);
ManagerImpl m = null;
try {
m = new ManagerImpl(account, pathConfig, accountFileUpdater, serviceEnvironmentConfig, userAgent);
account = null;
m.refreshPreKeys();
if (response.isStorageCapable()) {
m.retrieveRemoteStorage();
}
// Set an initial empty profile so user can be added to groups
try {
m.updateProfile(UpdateProfile.newBuilder().build());
} catch (NoClassDefFoundError e) {
logger.warn("Failed to set default profile: {}", e.getMessage());
}
if (newManagerListener != null) {
newManagerListener.accept(m);
m = null;
}
} finally {
if (m != null) {
m.close();
}
}
}
@Override
public void deleteLocalAccountData() throws IOException {
account.deleteAccountData();
accountFileUpdater.removeAccount();
account = null;
}
@Override
public boolean isRegistered() {
return account.isRegistered();
}
private boolean attemptReactivateAccount() {
try {
final var accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
account.getCredentialsProvider(),
userAgent,
null,
ServiceConfig.AUTOMATIC_NETWORK_RETRY);
accountManager.setAccountAttributes(null,
account.getLocalRegistrationId(),
true,
null,
account.getRegistrationLock(),
account.getSelfUnidentifiedAccessKey(),
account.isUnrestrictedUnidentifiedAccess(),
capabilities,
account.isDiscoverableByPhoneNumber(),
account.getEncryptedDeviceName(),
account.getLocalPniRegistrationId());
account.setRegistered(true);
logger.info("Reactivated existing account, verify is not necessary.");
if (newManagerListener != null) {
final var m = new ManagerImpl(account,
pathConfig,
accountFileUpdater,
serviceEnvironmentConfig,
userAgent);
account = null;
newManagerListener.accept(m);
}
return true;
} catch (IOException e) {
logger.debug("Failed to reactivate account");
}
return false;
}
private ServiceResponse<VerifyAccountResponse> verifyAccountWithCode(
final String verificationCode, final String registrationLock
) {
if (registrationLock == null) {
return accountManager.verifyAccount(verificationCode,
account.getLocalRegistrationId(),
true,
account.getSelfUnidentifiedAccessKey(),
account.isUnrestrictedUnidentifiedAccess(),
ServiceConfig.capabilities,
account.isDiscoverableByPhoneNumber(),
account.getLocalPniRegistrationId());
} else {
return accountManager.verifyAccountWithRegistrationLockPin(verificationCode,
account.getLocalRegistrationId(),
true,
registrationLock,
account.getSelfUnidentifiedAccessKey(),
account.isUnrestrictedUnidentifiedAccess(),
ServiceConfig.capabilities,
account.isDiscoverableByPhoneNumber(),
account.getLocalPniRegistrationId());
}
}
@Override
public void close() {
if (account != null) {
account.close();
account = null;
}
}
}

View file

@ -0,0 +1,8 @@
package org.asamk.signal.manager;
import org.asamk.signal.manager.api.TrustNewIdentity;
public record Settings(TrustNewIdentity trustNewIdentity, boolean disableMessageSendLog) {
public static final Settings DEFAULT = new Settings(TrustNewIdentity.ON_FIRST_USE, false);
}

View file

@ -2,16 +2,22 @@ package org.asamk.signal.manager;
import org.asamk.signal.manager.api.AccountCheckException;
import org.asamk.signal.manager.api.NotRegisteredException;
import org.asamk.signal.manager.api.Pair;
import org.asamk.signal.manager.api.ServiceEnvironment;
import org.asamk.signal.manager.config.ServiceConfig;
import org.asamk.signal.manager.config.ServiceEnvironment;
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
import org.asamk.signal.manager.internal.AccountFileUpdaterImpl;
import org.asamk.signal.manager.internal.ManagerImpl;
import org.asamk.signal.manager.internal.MultiAccountManagerImpl;
import org.asamk.signal.manager.internal.PathConfig;
import org.asamk.signal.manager.internal.ProvisioningManagerImpl;
import org.asamk.signal.manager.internal.RegistrationManagerImpl;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.accounts.AccountsStore;
import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
import org.asamk.signal.manager.util.KeyUtils;
import org.signal.libsignal.protocol.util.KeyHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
import java.io.File;
import java.io.IOException;
@ -27,27 +33,27 @@ public class SignalAccountFiles {
private final ServiceEnvironment serviceEnvironment;
private final ServiceEnvironmentConfig serviceEnvironmentConfig;
private final String userAgent;
private final TrustNewIdentity trustNewIdentity;
private final Settings settings;
private final AccountsStore accountsStore;
public SignalAccountFiles(
final File settingsPath,
final ServiceEnvironment serviceEnvironment,
final String userAgent,
final TrustNewIdentity trustNewIdentity
final Settings settings
) throws IOException {
this.pathConfig = PathConfig.createDefault(settingsPath);
this.serviceEnvironment = serviceEnvironment;
this.serviceEnvironmentConfig = ServiceConfig.getServiceEnvironmentConfig(this.serviceEnvironment, userAgent);
this.userAgent = userAgent;
this.trustNewIdentity = trustNewIdentity;
this.settings = settings;
this.accountsStore = new AccountsStore(pathConfig.dataPath(), serviceEnvironment, accountPath -> {
if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) {
return null;
}
try {
return SignalAccount.load(pathConfig.dataPath(), accountPath, false, trustNewIdentity);
return SignalAccount.load(pathConfig.dataPath(), accountPath, false, settings);
} catch (Exception e) {
return null;
}
@ -58,19 +64,28 @@ public class SignalAccountFiles {
return accountsStore.getAllNumbers();
}
public MultiAccountManager initMultiAccountManager() throws IOException {
final var managers = accountsStore.getAllAccounts().parallelStream().map(a -> {
public MultiAccountManager initMultiAccountManager() throws IOException, AccountCheckException {
final var managerPairs = accountsStore.getAllAccounts().parallelStream().map(a -> {
try {
return initManager(a.number(), a.path());
} catch (NotRegisteredException | IOException | AccountCheckException e) {
return new Pair<Manager, Throwable>(initManager(a.number(), a.path()), null);
} catch (NotRegisteredException e) {
logger.warn("Ignoring {}: {} ({})", a.number(), e.getMessage(), e.getClass().getSimpleName());
return null;
} catch (Throwable e) {
} catch (AccountCheckException | IOException e) {
logger.error("Failed to load {}: {} ({})", a.number(), e.getMessage(), e.getClass().getSimpleName());
throw e;
return new Pair<Manager, Throwable>(null, e);
}
}).filter(Objects::nonNull).toList();
for (final var pair : managerPairs) {
if (pair.second() instanceof IOException e) {
throw e;
} else if (pair.second() instanceof AccountCheckException e) {
throw e;
}
}
final var managers = managerPairs.stream().map(Pair::first).toList();
return new MultiAccountManagerImpl(managers, this);
}
@ -80,7 +95,8 @@ public class SignalAccountFiles {
}
private Manager initManager(
String number, String accountPath
String number,
String accountPath
) throws IOException, NotRegisteredException, AccountCheckException {
if (accountPath == null) {
throw new NotRegisteredException();
@ -89,7 +105,7 @@ public class SignalAccountFiles {
throw new NotRegisteredException();
}
var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, trustNewIdentity);
var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, settings);
if (!number.equals(account.getNumber())) {
account.close();
throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());
@ -114,6 +130,9 @@ public class SignalAccountFiles {
try {
manager.checkAccountState();
} catch (DeprecatedVersionException e) {
manager.close();
throw new AccountCheckException("signal-cli version is too old for the Signal-Server, please update.");
} catch (IOException e) {
manager.close();
throw new AccountCheckException("Error while checking account " + number + ": " + e.getMessage(), e);
@ -144,15 +163,14 @@ public class SignalAccountFiles {
}
public RegistrationManager initRegistrationManager(
String number, Consumer<Manager> newManagerListener
String number,
Consumer<Manager> newManagerListener
) throws IOException {
final var accountPath = accountsStore.getPathByNumber(number);
if (accountPath == null || !SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) {
final var newAccountPath = accountPath == null ? accountsStore.addAccount(number, null) : accountPath;
var aciIdentityKey = KeyUtils.generateIdentityKeyPair();
var pniIdentityKey = KeyUtils.generateIdentityKeyPair();
var registrationId = KeyHelper.generateRegistrationId(false);
var pniRegistrationId = KeyHelper.generateRegistrationId(false);
var profileKey = KeyUtils.createProfileKey();
var account = SignalAccount.create(pathConfig.dataPath(),
@ -161,10 +179,9 @@ public class SignalAccountFiles {
serviceEnvironment,
aciIdentityKey,
pniIdentityKey,
registrationId,
pniRegistrationId,
profileKey,
trustNewIdentity);
settings);
account.initDatabase();
return new RegistrationManagerImpl(account,
pathConfig,
@ -174,11 +191,12 @@ public class SignalAccountFiles {
new AccountFileUpdaterImpl(accountsStore, newAccountPath));
}
var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, trustNewIdentity);
var account = SignalAccount.load(pathConfig.dataPath(), accountPath, true, settings);
if (!number.equals(account.getNumber())) {
account.close();
throw new IOException("Number in account file doesn't match expected number: " + account.getNumber());
}
account.initDatabase();
return new RegistrationManagerImpl(account,
pathConfig,

View file

@ -1,234 +0,0 @@
package org.asamk.signal.manager;
import org.asamk.signal.manager.config.ServiceConfig;
import org.asamk.signal.manager.config.ServiceEnvironmentConfig;
import org.signal.libsignal.metadata.certificate.CertificateValidator;
import org.signal.libsignal.zkgroup.profiles.ClientZkProfileOperations;
import org.whispersystems.signalservice.api.KeyBackupService;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.SignalServiceDataStore;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.SignalSessionLock;
import org.whispersystems.signalservice.api.SignalWebSocket;
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher;
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.services.ProfileService;
import org.whispersystems.signalservice.api.util.CredentialsProvider;
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
import org.whispersystems.signalservice.api.websocket.WebSocketFactory;
import org.whispersystems.signalservice.internal.websocket.WebSocketConnection;
import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.function.Supplier;
import static org.asamk.signal.manager.config.ServiceConfig.capabilities;
public class SignalDependencies {
private final Object LOCK = new Object();
private final ServiceEnvironmentConfig serviceEnvironmentConfig;
private final String userAgent;
private final CredentialsProvider credentialsProvider;
private final SignalServiceDataStore dataStore;
private final ExecutorService executor;
private final SignalSessionLock sessionLock;
private SignalServiceAccountManager accountManager;
private GroupsV2Api groupsV2Api;
private GroupsV2Operations groupsV2Operations;
private ClientZkOperations clientZkOperations;
private SignalWebSocket signalWebSocket;
private SignalServiceMessageReceiver messageReceiver;
private SignalServiceMessageSender messageSender;
private KeyBackupService keyBackupService;
private ProfileService profileService;
private SignalServiceCipher cipher;
SignalDependencies(
final ServiceEnvironmentConfig serviceEnvironmentConfig,
final String userAgent,
final CredentialsProvider credentialsProvider,
final SignalServiceDataStore dataStore,
final ExecutorService executor,
final SignalSessionLock sessionLock
) {
this.serviceEnvironmentConfig = serviceEnvironmentConfig;
this.userAgent = userAgent;
this.credentialsProvider = credentialsProvider;
this.dataStore = dataStore;
this.executor = executor;
this.sessionLock = sessionLock;
}
public void resetAfterAddressChange() {
this.messageSender = null;
this.cipher = null;
}
public ServiceEnvironmentConfig getServiceEnvironmentConfig() {
return serviceEnvironmentConfig;
}
public SignalServiceAccountManager getAccountManager() {
return getOrCreate(() -> accountManager,
() -> accountManager = new SignalServiceAccountManager(serviceEnvironmentConfig.getSignalServiceConfiguration(),
credentialsProvider,
userAgent,
getGroupsV2Operations(),
ServiceConfig.AUTOMATIC_NETWORK_RETRY));
}
public SignalServiceAccountManager createUnauthenticatedAccountManager(String number, String password) {
return new SignalServiceAccountManager(getServiceEnvironmentConfig().getSignalServiceConfiguration(),
null,
null,
number,
SignalServiceAddress.DEFAULT_DEVICE_ID,
password,
userAgent,
ServiceConfig.AUTOMATIC_NETWORK_RETRY,
ServiceConfig.GROUP_MAX_SIZE);
}
public GroupsV2Api getGroupsV2Api() {
return getOrCreate(() -> groupsV2Api, () -> groupsV2Api = getAccountManager().getGroupsV2Api());
}
public GroupsV2Operations getGroupsV2Operations() {
return getOrCreate(() -> groupsV2Operations,
() -> groupsV2Operations = capabilities.isGv2()
? new GroupsV2Operations(ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration()),
ServiceConfig.GROUP_MAX_SIZE)
: null);
}
private ClientZkOperations getClientZkOperations() {
return getOrCreate(() -> clientZkOperations,
() -> clientZkOperations = capabilities.isGv2()
? ClientZkOperations.create(serviceEnvironmentConfig.getSignalServiceConfiguration())
: null);
}
private ClientZkProfileOperations getClientZkProfileOperations() {
final var clientZkOperations = getClientZkOperations();
return clientZkOperations == null ? null : clientZkOperations.getProfileOperations();
}
public SignalWebSocket getSignalWebSocket() {
return getOrCreate(() -> signalWebSocket, () -> {
final var timer = new UptimeSleepTimer();
final var healthMonitor = new SignalWebSocketHealthMonitor(timer);
final var webSocketFactory = new WebSocketFactory() {
@Override
public WebSocketConnection createWebSocket() {
return new WebSocketConnection("normal",
serviceEnvironmentConfig.getSignalServiceConfiguration(),
Optional.of(credentialsProvider),
userAgent,
healthMonitor);
}
@Override
public WebSocketConnection createUnidentifiedWebSocket() {
return new WebSocketConnection("unidentified",
serviceEnvironmentConfig.getSignalServiceConfiguration(),
Optional.empty(),
userAgent,
healthMonitor);
}
};
signalWebSocket = new SignalWebSocket(webSocketFactory);
healthMonitor.monitor(signalWebSocket);
});
}
public SignalServiceMessageReceiver getMessageReceiver() {
return getOrCreate(() -> messageReceiver,
() -> messageReceiver = new SignalServiceMessageReceiver(serviceEnvironmentConfig.getSignalServiceConfiguration(),
credentialsProvider,
userAgent,
getClientZkProfileOperations(),
ServiceConfig.AUTOMATIC_NETWORK_RETRY));
}
public SignalServiceMessageSender getMessageSender() {
return getOrCreate(() -> messageSender,
() -> messageSender = new SignalServiceMessageSender(serviceEnvironmentConfig.getSignalServiceConfiguration(),
credentialsProvider,
dataStore,
sessionLock,
userAgent,
getSignalWebSocket(),
Optional.empty(),
getClientZkProfileOperations(),
executor,
ServiceConfig.MAX_ENVELOPE_SIZE,
ServiceConfig.AUTOMATIC_NETWORK_RETRY));
}
public KeyBackupService getKeyBackupService() {
return getOrCreate(() -> keyBackupService,
() -> keyBackupService = getAccountManager().getKeyBackupService(ServiceConfig.getIasKeyStore(),
serviceEnvironmentConfig.getKeyBackupConfig().getEnclaveName(),
serviceEnvironmentConfig.getKeyBackupConfig().getServiceId(),
serviceEnvironmentConfig.getKeyBackupConfig().getMrenclave(),
10));
}
public Collection<KeyBackupService> getFallbackKeyBackupServices() {
return serviceEnvironmentConfig.getFallbackKeyBackupConfigs()
.stream()
.map(config -> getAccountManager().getKeyBackupService(ServiceConfig.getIasKeyStore(),
config.getEnclaveName(),
config.getServiceId(),
config.getMrenclave(),
10))
.toList();
}
public ProfileService getProfileService() {
return getOrCreate(() -> profileService,
() -> profileService = new ProfileService(getClientZkProfileOperations(),
getMessageReceiver(),
getSignalWebSocket()));
}
public SignalServiceCipher getCipher() {
return getOrCreate(() -> cipher, () -> {
final var certificateValidator = new CertificateValidator(serviceEnvironmentConfig.getUnidentifiedSenderTrustRoot());
final var address = new SignalServiceAddress(credentialsProvider.getAci(), credentialsProvider.getE164());
final var deviceId = credentialsProvider.getDeviceId();
cipher = new SignalServiceCipher(address, deviceId, dataStore.aci(), sessionLock, certificateValidator);
});
}
private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
var value = supplier.get();
if (value != null) {
return value;
}
synchronized (LOCK) {
value = supplier.get();
if (value != null) {
return value;
}
creator.call();
return supplier.get();
}
}
private interface Callable {
void call();
}
}

View file

@ -1,196 +0,0 @@
package org.asamk.signal.manager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.SignalWebSocket;
import org.whispersystems.signalservice.api.util.Preconditions;
import org.whispersystems.signalservice.api.util.SleepTimer;
import org.whispersystems.signalservice.api.websocket.HealthMonitor;
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState;
import org.whispersystems.signalservice.internal.websocket.WebSocketConnection;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import io.reactivex.rxjava3.schedulers.Schedulers;
/**
* Monitors the health of the identified and unidentified WebSockets. If either one appears to be
* unhealthy, will trigger restarting both.
* <p>
* The monitor is also responsible for sending heartbeats/keep-alive messages to prevent
* timeouts.
*/
final class SignalWebSocketHealthMonitor implements HealthMonitor {
private final static Logger logger = LoggerFactory.getLogger(SignalWebSocketHealthMonitor.class);
private static final long KEEP_ALIVE_SEND_CADENCE = TimeUnit.SECONDS.toMillis(WebSocketConnection.KEEPALIVE_TIMEOUT_SECONDS);
private static final long MAX_TIME_SINCE_SUCCESSFUL_KEEP_ALIVE = KEEP_ALIVE_SEND_CADENCE * 3;
private SignalWebSocket signalWebSocket;
private final SleepTimer sleepTimer;
private volatile KeepAliveSender keepAliveSender;
private final HealthState identified = new HealthState();
private final HealthState unidentified = new HealthState();
public SignalWebSocketHealthMonitor(SleepTimer sleepTimer) {
this.sleepTimer = sleepTimer;
}
public void monitor(SignalWebSocket signalWebSocket) {
Preconditions.checkNotNull(signalWebSocket);
Preconditions.checkArgument(this.signalWebSocket == null, "monitor can only be called once");
this.signalWebSocket = signalWebSocket;
//noinspection ResultOfMethodCallIgnored
signalWebSocket.getWebSocketState()
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.computation())
.distinctUntilChanged()
.subscribe(s -> onStateChange(s, identified));
//noinspection ResultOfMethodCallIgnored
signalWebSocket.getUnidentifiedWebSocketState()
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.computation())
.distinctUntilChanged()
.subscribe(s -> onStateChange(s, unidentified));
}
private synchronized void onStateChange(WebSocketConnectionState connectionState, HealthState healthState) {
switch (connectionState) {
case CONNECTED -> logger.debug("WebSocket is now connected");
case AUTHENTICATION_FAILED -> logger.debug("WebSocket authentication failed");
case FAILED -> logger.debug("WebSocket connection failed");
}
healthState.needsKeepAlive = connectionState == WebSocketConnectionState.CONNECTED;
if (keepAliveSender == null && isKeepAliveNecessary()) {
keepAliveSender = new KeepAliveSender();
keepAliveSender.start();
} else if (keepAliveSender != null && !isKeepAliveNecessary()) {
keepAliveSender.shutdown();
keepAliveSender = null;
}
}
@Override
public void onKeepAliveResponse(long sentTimestamp, boolean isIdentifiedWebSocket) {
if (isIdentifiedWebSocket) {
identified.lastKeepAliveReceived = System.currentTimeMillis();
} else {
unidentified.lastKeepAliveReceived = System.currentTimeMillis();
}
}
@Override
public void onMessageError(int status, boolean isIdentifiedWebSocket) {
if (status == 409) {
HealthState healthState = (isIdentifiedWebSocket ? identified : unidentified);
if (healthState.mismatchErrorTracker.addSample(System.currentTimeMillis())) {
logger.warn("Received too many mismatch device errors, forcing new websockets.");
signalWebSocket.forceNewWebSockets();
signalWebSocket.connect();
}
}
}
private boolean isKeepAliveNecessary() {
return identified.needsKeepAlive || unidentified.needsKeepAlive;
}
private static class HealthState {
private final HttpErrorTracker mismatchErrorTracker = new HttpErrorTracker(5, TimeUnit.MINUTES.toMillis(1));
private volatile boolean needsKeepAlive;
private volatile long lastKeepAliveReceived;
}
/**
* Sends periodic heartbeats/keep-alives over both WebSockets to prevent connection timeouts. If
* either WebSocket fails 3 times to get a return heartbeat both are forced to be recreated.
*/
private class KeepAliveSender extends Thread {
private volatile boolean shouldKeepRunning = true;
public void run() {
identified.lastKeepAliveReceived = System.currentTimeMillis();
unidentified.lastKeepAliveReceived = System.currentTimeMillis();
while (shouldKeepRunning && isKeepAliveNecessary()) {
try {
sleepTimer.sleep(KEEP_ALIVE_SEND_CADENCE);
if (shouldKeepRunning && isKeepAliveNecessary()) {
long keepAliveRequiredSinceTime = System.currentTimeMillis()
- MAX_TIME_SINCE_SUCCESSFUL_KEEP_ALIVE;
if (identified.lastKeepAliveReceived < keepAliveRequiredSinceTime
|| unidentified.lastKeepAliveReceived < keepAliveRequiredSinceTime) {
logger.warn("Missed keep alives, identified last: "
+ identified.lastKeepAliveReceived
+ " unidentified last: "
+ unidentified.lastKeepAliveReceived
+ " needed by: "
+ keepAliveRequiredSinceTime);
signalWebSocket.forceNewWebSockets();
signalWebSocket.connect();
} else {
signalWebSocket.sendKeepAlive();
}
}
} catch (Throwable e) {
logger.warn("Error occured in KeepAliveSender, ignoring ...", e);
}
}
}
public void shutdown() {
shouldKeepRunning = false;
}
}
private final static class HttpErrorTracker {
private final long[] timestamps;
private final long errorTimeRange;
public HttpErrorTracker(int samples, long errorTimeRange) {
this.timestamps = new long[samples];
this.errorTimeRange = errorTimeRange;
}
public synchronized boolean addSample(long now) {
long errorsMustBeAfter = now - errorTimeRange;
int count = 1;
int minIndex = 0;
for (int i = 0; i < timestamps.length; i++) {
if (timestamps[i] < errorsMustBeAfter) {
timestamps[i] = 0;
} else if (timestamps[i] != 0) {
count++;
}
if (timestamps[i] < timestamps[minIndex]) {
minIndex = i;
}
}
timestamps[minIndex] = now;
if (count >= timestamps.length) {
Arrays.fill(timestamps, 0);
return true;
}
return false;
}
}
}

View file

@ -8,18 +8,18 @@ public class RenewSessionAction implements HandleAction {
private final RecipientId recipientId;
private final ServiceId serviceId;
private final ServiceId accountId;
public RenewSessionAction(final RecipientId recipientId, final ServiceId serviceId) {
public RenewSessionAction(final RecipientId recipientId, final ServiceId serviceId, final ServiceId accountId) {
this.recipientId = recipientId;
this.serviceId = serviceId;
this.accountId = accountId;
}
@Override
public void execute(Context context) throws Throwable {
context.getAccount().getAciSessionStore().archiveSessions(serviceId);
if (!recipientId.equals(context.getAccount().getSelfRecipientId())) {
context.getSendHelper().sendNullMessage(recipientId);
}
context.getAccount().getAccountData(accountId).getSessionStore().archiveSessions(serviceId);
context.getSendHelper().sendNullMessage(recipientId);
}
@Override

View file

@ -13,7 +13,9 @@ public class ResendMessageAction implements HandleAction {
private final MessageSendLogEntry messageSendLogEntry;
public ResendMessageAction(
final RecipientId recipientId, final long timestamp, final MessageSendLogEntry messageSendLogEntry
final RecipientId recipientId,
final long timestamp,
final MessageSendLogEntry messageSendLogEntry
) {
this.recipientId = recipientId;
this.timestamp = timestamp;

View file

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

View file

@ -1,6 +1,6 @@
package org.asamk.signal.manager.actions;
import org.asamk.signal.manager.groups.GroupIdV1;
import org.asamk.signal.manager.api.GroupIdV1;
import org.asamk.signal.manager.helper.Context;
import org.asamk.signal.manager.storage.recipients.RecipientId;

View file

@ -1,6 +1,6 @@
package org.asamk.signal.manager.actions;
import org.asamk.signal.manager.groups.GroupIdV1;
import org.asamk.signal.manager.api.GroupIdV1;
import org.asamk.signal.manager.helper.Context;
import org.asamk.signal.manager.storage.recipients.RecipientId;

View file

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

View file

@ -15,7 +15,9 @@ public class SendReceiptAction implements HandleAction {
private final List<Long> timestamps = new ArrayList<>();
public SendReceiptAction(
final RecipientId recipientId, final SignalServiceReceiptMessage.Type type, final long timestamp
final RecipientId recipientId,
final SignalServiceReceiptMessage.Type type,
final long timestamp
) {
this.recipientId = recipientId;
this.type = type;

View file

@ -1,40 +1,34 @@
package org.asamk.signal.manager.actions;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.api.GroupId;
import org.asamk.signal.manager.helper.Context;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.signal.libsignal.metadata.ProtocolException;
import org.signal.libsignal.protocol.message.CiphertextMessage;
import org.signal.libsignal.protocol.message.DecryptionErrorMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import org.whispersystems.signalservice.internal.push.Envelope;
import java.util.Optional;
public class SendRetryMessageRequestAction implements HandleAction {
private final RecipientId recipientId;
private final ServiceId serviceId;
private final ProtocolException protocolException;
private final SignalServiceEnvelope envelope;
public SendRetryMessageRequestAction(
final RecipientId recipientId,
final ServiceId serviceId,
final ProtocolException protocolException,
final SignalServiceEnvelope envelope
) {
this.recipientId = recipientId;
this.serviceId = serviceId;
this.protocolException = protocolException;
this.envelope = envelope;
}
@Override
public void execute(Context context) throws Throwable {
context.getAccount().getAciSessionStore().archiveSessions(serviceId);
int senderDevice = protocolException.getSenderDevice();
Optional<GroupId> groupId = protocolException.getGroupId().isPresent() ? Optional.of(GroupId.unknownVersion(
protocolException.getGroupId().get())) : Optional.empty();
@ -47,7 +41,9 @@ public class SendRetryMessageRequestAction implements HandleAction {
envelopeType = messageContent.getType();
} else {
originalContent = envelope.getContent();
envelopeType = envelopeTypeToCiphertextMessageType(envelope.getType());
envelopeType = envelope.getType() == null
? CiphertextMessage.WHISPER_TYPE
: envelopeTypeToCiphertextMessageType(envelope.getType());
}
DecryptionErrorMessage decryptionErrorMessage = DecryptionErrorMessage.forOriginalMessage(originalContent,
@ -59,10 +55,14 @@ public class SendRetryMessageRequestAction implements HandleAction {
}
private static int envelopeTypeToCiphertextMessageType(int envelopeType) {
return switch (envelopeType) {
case SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE -> CiphertextMessage.PREKEY_TYPE;
case SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER_VALUE -> CiphertextMessage.SENDERKEY_TYPE;
case SignalServiceProtos.Envelope.Type.PLAINTEXT_CONTENT_VALUE -> CiphertextMessage.PLAINTEXT_CONTENT_TYPE;
final var type = Envelope.Type.fromValue(envelopeType);
if (type == null) {
return CiphertextMessage.WHISPER_TYPE;
}
return switch (type) {
case PREKEY_BUNDLE -> CiphertextMessage.PREKEY_TYPE;
case UNIDENTIFIED_SENDER -> CiphertextMessage.SENDERKEY_TYPE;
case PLAINTEXT_CONTENT -> CiphertextMessage.PLAINTEXT_CONTENT_TYPE;
default -> CiphertextMessage.WHISPER_TYPE;
};
}

View file

@ -0,0 +1,21 @@
package org.asamk.signal.manager.actions;
import org.asamk.signal.manager.helper.Context;
import org.asamk.signal.manager.jobs.SyncStorageJob;
public class SyncStorageDataAction implements HandleAction {
private static final SyncStorageDataAction INSTANCE = new SyncStorageDataAction();
private SyncStorageDataAction() {
}
public static SyncStorageDataAction create() {
return INSTANCE;
}
@Override
public void execute(Context context) throws Throwable {
context.getJobExecutor().enqueueJob(new SyncStorageJob());
}
}

View file

@ -0,0 +1,12 @@
package org.asamk.signal.manager.api;
public class AlreadyReceivingException extends Exception {
public AlreadyReceivingException(String message) {
super(message);
}
public AlreadyReceivingException(String message, Exception e) {
super(message, e);
}
}

View file

@ -0,0 +1,16 @@
package org.asamk.signal.manager.api;
public class CaptchaRejectedException extends Exception {
public CaptchaRejectedException() {
super("Captcha rejected");
}
public CaptchaRejectedException(final String message) {
super(message);
}
public CaptchaRejectedException(final String message, final Throwable cause) {
super(message, cause);
}
}

View file

@ -2,6 +2,13 @@ package org.asamk.signal.manager.api;
public class CaptchaRequiredException extends Exception {
private long nextAttemptTimestamp;
public CaptchaRequiredException(final long nextAttemptTimestamp) {
super("Captcha required");
this.nextAttemptTimestamp = nextAttemptTimestamp;
}
public CaptchaRequiredException(final String message) {
super(message);
}
@ -9,4 +16,8 @@ public class CaptchaRequiredException extends Exception {
public CaptchaRequiredException(final String message, final Throwable cause) {
super(message, cause);
}
public long getNextAttemptTimestamp() {
return nextAttemptTimestamp;
}
}

View file

@ -0,0 +1,193 @@
package org.asamk.signal.manager.api;
import org.whispersystems.signalservice.internal.util.Util;
public record Contact(
String givenName,
String familyName,
String nickName,
String nickNameGivenName,
String nickNameFamilyName,
String note,
String color,
int messageExpirationTime,
int messageExpirationTimeVersion,
long muteUntil,
boolean hideStory,
boolean isBlocked,
boolean isArchived,
boolean isProfileSharingEnabled,
boolean isHidden,
Long unregisteredTimestamp
) {
private Contact(final Builder builder) {
this(builder.givenName,
builder.familyName,
builder.nickName,
builder.nickNameGivenName,
builder.nickNameFamilyName,
builder.note,
builder.color,
builder.messageExpirationTime,
builder.messageExpirationTimeVersion,
builder.muteUntil,
builder.hideStory,
builder.isBlocked,
builder.isArchived,
builder.isProfileSharingEnabled,
builder.isHidden,
builder.unregisteredTimestamp);
}
public static Builder newBuilder() {
return new Builder();
}
public static Builder newBuilder(final Contact copy) {
Builder builder = new Builder();
builder.givenName = copy.givenName();
builder.familyName = copy.familyName();
builder.nickName = copy.nickName();
builder.nickNameGivenName = copy.nickNameGivenName();
builder.nickNameFamilyName = copy.nickNameFamilyName();
builder.note = copy.note();
builder.color = copy.color();
builder.messageExpirationTime = copy.messageExpirationTime();
builder.messageExpirationTimeVersion = copy.messageExpirationTimeVersion();
builder.muteUntil = copy.muteUntil();
builder.hideStory = copy.hideStory();
builder.isBlocked = copy.isBlocked();
builder.isArchived = copy.isArchived();
builder.isProfileSharingEnabled = copy.isProfileSharingEnabled();
builder.isHidden = copy.isHidden();
builder.unregisteredTimestamp = copy.unregisteredTimestamp();
return builder;
}
public String getName() {
final var noGivenName = Util.isEmpty(givenName);
final var noFamilyName = Util.isEmpty(familyName);
if (noGivenName && noFamilyName) {
return "";
} else if (noGivenName) {
return familyName;
} else if (noFamilyName) {
return givenName;
}
return givenName + " " + familyName;
}
public static final class Builder {
private String givenName;
private String familyName;
private String nickName;
private String nickNameGivenName;
private String nickNameFamilyName;
private String note;
private String color;
private int messageExpirationTime;
private int messageExpirationTimeVersion = 1;
private long muteUntil;
private boolean hideStory;
private boolean isBlocked;
private boolean isArchived;
private boolean isProfileSharingEnabled;
private boolean isHidden;
private Long unregisteredTimestamp;
private Builder() {
}
public static Builder newBuilder() {
return new Builder();
}
public Builder withGivenName(final String val) {
givenName = val;
return this;
}
public Builder withFamilyName(final String val) {
familyName = val;
return this;
}
public Builder withNickName(final String val) {
nickName = val;
return this;
}
public Builder withNickNameGivenName(final String val) {
nickNameGivenName = val;
return this;
}
public Builder withNickNameFamilyName(final String val) {
nickNameFamilyName = val;
return this;
}
public Builder withNote(final String val) {
note = val;
return this;
}
public Builder withColor(final String val) {
color = val;
return this;
}
public Builder withMessageExpirationTime(final int val) {
messageExpirationTime = val;
return this;
}
public Builder withMessageExpirationTimeVersion(final int val) {
messageExpirationTimeVersion = val;
return this;
}
public Builder withMuteUntil(final long val) {
muteUntil = val;
return this;
}
public Builder withHideStory(final boolean val) {
hideStory = val;
return this;
}
public Builder withIsBlocked(final boolean val) {
isBlocked = val;
return this;
}
public Builder withIsArchived(final boolean val) {
isArchived = val;
return this;
}
public Builder withIsProfileSharingEnabled(final boolean val) {
isProfileSharingEnabled = val;
return this;
}
public Builder withIsHidden(final boolean val) {
isHidden = val;
return this;
}
public Builder withUnregisteredTimestamp(final Long val) {
unregisteredTimestamp = val;
return this;
}
public Contact build() {
return new Contact(this);
}
}
}

View file

@ -0,0 +1,12 @@
package org.asamk.signal.manager.api;
public class DeviceLimitExceededException extends Exception {
public DeviceLimitExceededException(final String message) {
super(message);
}
public DeviceLimitExceededException(final String message, final Throwable cause) {
super(message, cause);
}
}

View file

@ -1,9 +1,7 @@
package org.asamk.signal.manager;
package org.asamk.signal.manager.api;
import org.asamk.signal.manager.api.InvalidDeviceLinkException;
import org.asamk.signal.manager.util.Utils;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.ecc.Curve;
import org.signal.libsignal.protocol.ecc.ECPublicKey;
import java.net.URI;
@ -14,9 +12,9 @@ import java.util.Base64;
import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
public record DeviceLinkInfo(String deviceIdentifier, ECPublicKey deviceKey) {
public record DeviceLinkUrl(String deviceIdentifier, ECPublicKey deviceKey) {
public static DeviceLinkInfo parseDeviceLinkUri(URI linkUri) throws InvalidDeviceLinkException {
public static DeviceLinkUrl parseDeviceLinkUri(URI linkUri) throws InvalidDeviceLinkException {
final var rawQuery = linkUri.getRawQuery();
if (isEmpty(rawQuery)) {
throw new RuntimeException("Invalid device link uri");
@ -38,12 +36,12 @@ public record DeviceLinkInfo(String deviceIdentifier, ECPublicKey deviceKey) {
}
ECPublicKey deviceKey;
try {
deviceKey = Curve.decodePoint(publicKeyBytes, 0);
deviceKey = new ECPublicKey(publicKeyBytes);
} catch (InvalidKeyException e) {
throw new InvalidDeviceLinkException("Invalid device link", e);
}
return new DeviceLinkInfo(deviceIdentifier, deviceKey);
return new DeviceLinkUrl(deviceIdentifier, deviceKey);
}
public URI createDeviceLinkUri() {

View file

@ -1,11 +1,7 @@
package org.asamk.signal.manager.api;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
import org.asamk.signal.manager.groups.GroupPermission;
import org.asamk.signal.manager.helper.RecipientAddressResolver;
import org.asamk.signal.manager.storage.groups.GroupInfo;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import java.util.Set;
@ -31,7 +27,9 @@ public record Group(
) {
public static Group from(
final GroupInfo groupInfo, final RecipientAddressResolver recipientStore, final RecipientId selfRecipientId
final GroupInfo groupInfo,
final RecipientAddressResolver recipientStore,
final RecipientId selfRecipientId
) {
return new Group(groupInfo.getGroupId(),
groupInfo.getTitle(),
@ -40,22 +38,27 @@ public record Group(
groupInfo.getMembers()
.stream()
.map(recipientStore::resolveRecipientAddress)
.map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress)
.collect(Collectors.toSet()),
groupInfo.getPendingMembers()
.stream()
.map(recipientStore::resolveRecipientAddress)
.map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress)
.collect(Collectors.toSet()),
groupInfo.getRequestingMembers()
.stream()
.map(recipientStore::resolveRecipientAddress)
.map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress)
.collect(Collectors.toSet()),
groupInfo.getAdminMembers()
.stream()
.map(recipientStore::resolveRecipientAddress)
.map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress)
.collect(Collectors.toSet()),
groupInfo.getBannedMembers()
.stream()
.map(recipientStore::resolveRecipientAddress)
.map(org.asamk.signal.manager.storage.recipients.RecipientAddress::toApiRecipientAddress)
.collect(Collectors.toSet()),
groupInfo.isBlocked(),
groupInfo.getMessageExpirationTimer(),

View file

@ -1,4 +1,4 @@
package org.asamk.signal.manager.groups;
package org.asamk.signal.manager.api;
import java.util.Arrays;
import java.util.Base64;

View file

@ -1,4 +1,4 @@
package org.asamk.signal.manager.groups;
package org.asamk.signal.manager.api;
public class GroupIdFormatException extends Exception {

View file

@ -1,4 +1,4 @@
package org.asamk.signal.manager.groups;
package org.asamk.signal.manager.api;
import java.util.Base64;

View file

@ -1,4 +1,4 @@
package org.asamk.signal.manager.groups;
package org.asamk.signal.manager.api;
import java.util.Base64;

View file

@ -1,17 +1,18 @@
package org.asamk.signal.manager.groups;
import com.google.protobuf.ByteString;
package org.asamk.signal.manager.api;
import org.asamk.signal.manager.groups.GroupLinkPassword;
import org.signal.core.util.Base64;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.signal.storageservice.protos.groups.GroupInviteLink;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.whispersystems.util.Base64UrlSafe;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import okio.ByteString;
public final class GroupInviteLinkUrl {
private static final String GROUP_URL_HOST = "signal.group";
@ -23,7 +24,7 @@ public final class GroupInviteLinkUrl {
public static GroupInviteLinkUrl forGroup(GroupMasterKey groupMasterKey, DecryptedGroup group) {
return new GroupInviteLinkUrl(groupMasterKey,
GroupLinkPassword.fromBytes(group.getInviteLinkPassword().toByteArray()));
GroupLinkPassword.fromBytes(group.inviteLinkPassword.toByteArray()));
}
/**
@ -38,30 +39,27 @@ public final class GroupInviteLinkUrl {
}
try {
if (!"/".equals(uri.getPath()) && uri.getPath().length() > 0) {
if (!"/".equals(uri.getPath()) && !uri.getPath().isEmpty()) {
throw new InvalidGroupLinkException("No path was expected in uri");
}
var encoding = uri.getFragment();
if (encoding == null || encoding.length() == 0) {
if (encoding == null || encoding.isEmpty()) {
throw new InvalidGroupLinkException("No reference was in the uri");
}
var bytes = Base64UrlSafe.decodePaddingAgnostic(encoding);
var groupInviteLink = GroupInviteLink.parseFrom(bytes);
var bytes = Base64.decode(encoding);
GroupInviteLink groupInviteLink = GroupInviteLink.ADAPTER.decode(bytes);
switch (groupInviteLink.getContentsCase()) {
case V1CONTENTS -> {
var groupInviteLinkContentsV1 = groupInviteLink.getV1Contents();
var groupMasterKey = new GroupMasterKey(groupInviteLinkContentsV1.getGroupMasterKey()
.toByteArray());
var password = GroupLinkPassword.fromBytes(groupInviteLinkContentsV1.getInviteLinkPassword()
.toByteArray());
if (groupInviteLink.v1Contents != null) {
var groupInviteLinkContentsV1 = groupInviteLink.v1Contents;
var groupMasterKey = new GroupMasterKey(groupInviteLinkContentsV1.groupMasterKey.toByteArray());
var password = GroupLinkPassword.fromBytes(groupInviteLinkContentsV1.inviteLinkPassword.toByteArray());
return new GroupInviteLinkUrl(groupMasterKey, password);
}
default -> throw new UnknownGroupLinkVersionException("Url contains no known group link content");
return new GroupInviteLinkUrl(groupMasterKey, password);
} else {
throw new UnknownGroupLinkVersionException("Url contains no known group link content");
}
} catch (InvalidInputException | IOException e) {
throw new InvalidGroupLinkException(e);
@ -92,13 +90,12 @@ public final class GroupInviteLinkUrl {
}
private static String createUrl(GroupMasterKey groupMasterKey, GroupLinkPassword password) {
var groupInviteLink = GroupInviteLink.newBuilder()
.setV1Contents(GroupInviteLink.GroupInviteLinkContentsV1.newBuilder()
.setGroupMasterKey(ByteString.copyFrom(groupMasterKey.serialize()))
.setInviteLinkPassword(ByteString.copyFrom(password.serialize())))
.build();
var groupInviteLink = new GroupInviteLink.Builder().v1Contents(new GroupInviteLink.GroupInviteLinkContentsV1.Builder().groupMasterKey(
ByteString.of(groupMasterKey.serialize()))
.inviteLinkPassword(ByteString.of(password.serialize()))
.build()).build();
var encoding = Base64UrlSafe.encodeBytesWithoutPadding(groupInviteLink.toByteArray());
var encoding = Base64.encodeUrlSafeWithoutPadding(groupInviteLink.encode());
return GROUP_URL_PREFIX + encoding;
}
@ -115,7 +112,7 @@ public final class GroupInviteLinkUrl {
return password;
}
public final static class InvalidGroupLinkException extends Exception {
public static final class InvalidGroupLinkException extends Exception {
public InvalidGroupLinkException(String message) {
super(message);
@ -126,7 +123,7 @@ public final class GroupInviteLinkUrl {
}
}
public final static class UnknownGroupLinkVersionException extends Exception {
public static final class UnknownGroupLinkVersionException extends Exception {
public UnknownGroupLinkVersionException(String message) {
super(message);

View file

@ -1,4 +1,4 @@
package org.asamk.signal.manager.groups;
package org.asamk.signal.manager.api;
public enum GroupLinkState {
ENABLED,

View file

@ -1,4 +1,4 @@
package org.asamk.signal.manager.groups;
package org.asamk.signal.manager.api;
public class GroupNotFoundException extends Exception {

View file

@ -1,4 +1,4 @@
package org.asamk.signal.manager.groups;
package org.asamk.signal.manager.api;
public enum GroupPermission {
EVERY_MEMBER,

View file

@ -1,4 +1,4 @@
package org.asamk.signal.manager.groups;
package org.asamk.signal.manager.api;
public class GroupSendingNotAllowedException extends Exception {

View file

@ -1,18 +1,10 @@
package org.asamk.signal.manager.api;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.signal.libsignal.protocol.IdentityKey;
public record Identity(
RecipientAddress recipient,
IdentityKey identityKey,
byte[] fingerprint,
String safetyNumber,
byte[] scannableSafetyNumber,
TrustLevel trustLevel,
long dateAddedTimestamp
) {
public byte[] getFingerprint() {
return identityKey.getPublicKey().serialize();
}
}
) {}

View file

@ -0,0 +1,28 @@
package org.asamk.signal.manager.api;
import org.signal.libsignal.protocol.util.Hex;
import java.util.Base64;
import java.util.Locale;
public sealed interface IdentityVerificationCode {
record Fingerprint(byte[] fingerprint) implements IdentityVerificationCode {}
record SafetyNumber(String safetyNumber) implements IdentityVerificationCode {}
record ScannableSafetyNumber(byte[] safetyNumber) implements IdentityVerificationCode {}
static IdentityVerificationCode parse(String code) throws Exception {
code = code.replaceAll(" ", "");
if (code.length() == 66) {
final var fingerprintBytes = Hex.fromStringCondensed(code.toLowerCase(Locale.ROOT));
return new Fingerprint(fingerprintBytes);
} else if (code.length() == 60) {
return new SafetyNumber(code);
} else {
final var scannableSafetyNumber = Base64.getDecoder().decode(code);
return new ScannableSafetyNumber(scannableSafetyNumber);
}
}
}

View file

@ -2,6 +2,10 @@ package org.asamk.signal.manager.api;
public class InvalidNumberException extends Exception {
public InvalidNumberException(String message) {
super(message);
}
InvalidNumberException(String message, Throwable e) {
super(message, e);
}

View file

@ -0,0 +1,12 @@
package org.asamk.signal.manager.api;
public class InvalidUsernameException extends Exception {
public InvalidUsernameException(final String message) {
super(message);
}
public InvalidUsernameException(final String message, final Throwable cause) {
super(message, cause);
}
}

View file

@ -1,4 +1,4 @@
package org.asamk.signal.manager.groups;
package org.asamk.signal.manager.api;
public class LastGroupAdminException extends Exception {

View file

@ -6,17 +6,32 @@ import java.util.Optional;
public record Message(
String messageText,
List<String> attachments,
boolean viewOnce,
List<Mention> mentions,
Optional<Quote> quote,
Optional<Sticker> sticker,
List<Preview> previews
List<Preview> previews,
Optional<StoryReply> storyReply,
List<TextStyle> textStyles
) {
public record Mention(RecipientIdentifier.Single recipient, int start, int length) {}
public record Quote(long timestamp, RecipientIdentifier.Single author, String message, List<Mention> mentions) {}
public record Quote(
long timestamp,
RecipientIdentifier.Single author,
String message,
List<Mention> mentions,
List<TextStyle> textStyles,
List<Attachment> attachments
) {
public record Attachment(String contentType, String filename, String preview) {}
}
public record Sticker(byte[] packId, int stickerId) {}
public record Preview(String url, String title, String description, Optional<String> image) {}
public record StoryReply(long timestamp, RecipientIdentifier.Single author) {}
}

View file

@ -1,15 +1,14 @@
package org.asamk.signal.manager.api;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupUtils;
import org.asamk.signal.manager.helper.RecipientAddressResolver;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.signal.libsignal.metadata.ProtocolException;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceEditMessage;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
import org.whispersystems.signalservice.api.messages.SignalServiceGroupContext;
@ -33,8 +32,10 @@ import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptM
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
import org.whispersystems.signalservice.api.messages.multidevice.ViewOnceOpenMessage;
import org.whispersystems.signalservice.api.messages.multidevice.ViewedMessage;
import org.whispersystems.signalservice.api.push.ServiceId;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@ -50,6 +51,7 @@ public record MessageEnvelope(
Optional<Receipt> receipt,
Optional<Typing> typing,
Optional<Data> data,
Optional<Edit> edit,
Optional<Sync> sync,
Optional<Call> call,
Optional<Story> story
@ -114,7 +116,8 @@ public record MessageEnvelope(
Optional<Sticker> sticker,
List<SharedContact> sharedContacts,
List<Mention> mentions,
List<Preview> previews
List<Preview> previews,
List<TextStyle> textStyles
) {
static Data from(
@ -138,7 +141,9 @@ public record MessageEnvelope(
dataMessage.isProfileKeyUpdate(),
dataMessage.getProfileKey().isPresent(),
dataMessage.getReaction().map(r -> Reaction.from(r, recipientResolver, addressResolver)),
dataMessage.getQuote().map(q -> Quote.from(q, recipientResolver, addressResolver, fileProvider)),
dataMessage.getQuote()
.filter(q -> q.getAuthor() != null && q.getAuthor().isValid())
.map(q -> Quote.from(q, recipientResolver, addressResolver, fileProvider)),
dataMessage.getPayment().map(p -> p.getPaymentNotification().isPresent() ? Payment.from(p) : null),
dataMessage.getAttachments()
.map(a -> a.stream().map(as -> Attachment.from(as, fileProvider)).toList())
@ -155,6 +160,9 @@ public record MessageEnvelope(
.orElse(List.of()),
dataMessage.getPreviews()
.map(a -> a.stream().map(preview -> Preview.from(preview, fileProvider)).toList())
.orElse(List.of()),
dataMessage.getBodyRanges()
.map(a -> a.stream().filter(r -> r.style != null).map(TextStyle::from).toList())
.orElse(List.of()));
}
@ -184,7 +192,7 @@ public record MessageEnvelope(
RecipientAddressResolver addressResolver
) {
return new StoryContext(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
storyContext.getAuthorServiceId())), storyContext.getSentTimestamp());
storyContext.getAuthorServiceId())).toApiRecipientAddress(), storyContext.getSentTimestamp());
}
}
@ -205,7 +213,8 @@ public record MessageEnvelope(
RecipientAddressResolver addressResolver
) {
return new Reaction(reaction.getTargetSentTimestamp(),
addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(reaction.getTargetAuthor())),
addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(reaction.getTargetAuthor()))
.toApiRecipientAddress(),
reaction.getEmoji(),
reaction.isRemove());
}
@ -216,7 +225,8 @@ public record MessageEnvelope(
RecipientAddress author,
Optional<String> text,
List<Mention> mentions,
List<Attachment> attachments
List<Attachment> attachments,
List<TextStyle> textStyles
) {
static Quote from(
@ -226,8 +236,9 @@ public record MessageEnvelope(
final AttachmentFileProvider fileProvider
) {
return new Quote(quote.getId(),
addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(quote.getAuthor())),
Optional.ofNullable(quote.getText()),
addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(quote.getAuthor()))
.toApiRecipientAddress(),
Optional.of(quote.getText()),
quote.getMentions() == null
? List.of()
: quote.getMentions()
@ -236,7 +247,14 @@ public record MessageEnvelope(
.toList(),
quote.getAttachments() == null
? List.of()
: quote.getAttachments().stream().map(a -> Attachment.from(a, fileProvider)).toList());
: 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());
}
}
@ -255,9 +273,8 @@ public record MessageEnvelope(
RecipientResolver recipientResolver,
RecipientAddressResolver addressResolver
) {
return new Mention(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(mention.getServiceId())),
mention.getStart(),
mention.getLength());
return new Mention(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(mention.getServiceId()))
.toApiRecipientAddress(), mention.getStart(), mention.getLength());
}
}
@ -278,44 +295,51 @@ public record MessageEnvelope(
boolean isBorderless
) {
static Attachment from(SignalServiceAttachment attachment, AttachmentFileProvider fileProvider) {
if (attachment.isPointer()) {
final var a = attachment.asPointer();
return new Attachment(Optional.of(a.getRemoteId().toString()),
Optional.of(fileProvider.getFile(a.getRemoteId())),
static Attachment from(SignalServiceAttachment signalAttachment, AttachmentFileProvider fileProvider) {
if (signalAttachment.isPointer()) {
final var a = signalAttachment.asPointer();
final var attachmentFile = fileProvider.getFile(a);
return new Attachment(Optional.of(attachmentFile.getName()),
Optional.of(attachmentFile),
a.getFileName(),
a.getContentType(),
a.getUploadTimestamp() == 0 ? Optional.empty() : Optional.of(a.getUploadTimestamp()),
a.getSize().map(Integer::longValue),
a.getPreview(),
Optional.empty(),
a.getCaption(),
a.getCaption().map(c -> c.isEmpty() ? null : c),
a.getWidth() == 0 ? Optional.empty() : Optional.of(a.getWidth()),
a.getHeight() == 0 ? Optional.empty() : Optional.of(a.getHeight()),
a.getVoiceNote(),
a.isGif(),
a.isBorderless());
} else {
final var a = attachment.asStream();
return new Attachment(Optional.empty(),
Optional.empty(),
a.getFileName(),
a.getContentType(),
a.getUploadTimestamp() == 0 ? Optional.empty() : Optional.of(a.getUploadTimestamp()),
Optional.of(a.getLength()),
a.getPreview(),
Optional.empty(),
a.getCaption(),
a.getWidth() == 0 ? Optional.empty() : Optional.of(a.getWidth()),
a.getHeight() == 0 ? Optional.empty() : Optional.of(a.getHeight()),
a.getVoiceNote(),
a.isGif(),
a.isBorderless());
Attachment attachment = null;
try (final var a = signalAttachment.asStream()) {
attachment = new Attachment(Optional.empty(),
Optional.empty(),
a.getFileName(),
a.getContentType(),
a.getUploadTimestamp() == 0 ? Optional.empty() : Optional.of(a.getUploadTimestamp()),
Optional.of(a.getLength()),
a.getPreview(),
Optional.empty(),
a.getCaption(),
a.getWidth() == 0 ? Optional.empty() : Optional.of(a.getWidth()),
a.getHeight() == 0 ? Optional.empty() : Optional.of(a.getHeight()),
a.getVoiceNote(),
a.isGif(),
a.isBorderless());
return attachment;
} catch (IOException e) {
return attachment;
}
}
}
static Attachment from(
SignalServiceDataMessage.Quote.QuotedAttachment a, final AttachmentFileProvider fileProvider
SignalServiceDataMessage.Quote.QuotedAttachment a,
final AttachmentFileProvider fileProvider
) {
return new Attachment(Optional.empty(),
Optional.empty(),
@ -367,7 +391,7 @@ public record MessageEnvelope(
}
public record Name(
Optional<String> display,
Optional<String> nickname,
Optional<String> given,
Optional<String> family,
Optional<String> prefix,
@ -376,7 +400,7 @@ public record MessageEnvelope(
) {
static Name from(org.whispersystems.signalservice.api.messages.shared.SharedContact.Name name) {
return new Name(name.getDisplay(),
return new Name(name.getNickname(),
name.getGiven(),
name.getFamily(),
name.getPrefix(),
@ -458,15 +482,15 @@ public record MessageEnvelope(
) {
static Address from(org.whispersystems.signalservice.api.messages.shared.SharedContact.PostalAddress address) {
return new Address(Address.Type.from(address.getType()),
return new Address(Type.from(address.getType()),
address.getLabel(),
address.getLabel(),
address.getLabel(),
address.getLabel(),
address.getLabel(),
address.getLabel(),
address.getLabel(),
address.getLabel());
address.getStreet(),
address.getPobox(),
address.getNeighborhood(),
address.getCity(),
address.getRegion(),
address.getPostcode(),
address.getCountry());
}
public enum Type {
@ -487,9 +511,7 @@ public record MessageEnvelope(
public record Preview(String title, String description, long date, String url, Optional<Attachment> image) {
static Preview from(
SignalServicePreview preview, final AttachmentFileProvider fileProvider
) {
static Preview from(SignalServicePreview preview, final AttachmentFileProvider fileProvider) {
return new Preview(preview.getTitle(),
preview.getDescription(),
preview.getDate(),
@ -497,6 +519,20 @@ public record MessageEnvelope(
preview.getImage().map(as -> Attachment.from(as, fileProvider)));
}
}
}
public record Edit(long targetSentTimestamp, Data dataMessage) {
public static Edit from(
final SignalServiceEditMessage editMessage,
RecipientResolver recipientResolver,
RecipientAddressResolver addressResolver,
final AttachmentFileProvider fileProvider
) {
return new Edit(editMessage.getTargetSentTimestamp(),
Data.from(editMessage.getDataMessage(), recipientResolver, addressResolver, fileProvider));
}
}
public record Sync(
@ -540,6 +576,7 @@ public record MessageEnvelope(
Optional<RecipientAddress> destination,
Set<RecipientAddress> recipients,
Optional<Data> message,
Optional<Edit> editMessage,
Optional<Story> story
) {
@ -552,13 +589,17 @@ public record MessageEnvelope(
return new Sent(sentMessage.getTimestamp(),
sentMessage.getExpirationStartTimestamp(),
sentMessage.getDestination()
.map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d))),
.map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d))
.toApiRecipientAddress()),
sentMessage.getRecipients()
.stream()
.map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d)))
.map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d))
.toApiRecipientAddress())
.collect(Collectors.toSet()),
sentMessage.getDataMessage()
.map(message -> Data.from(message, recipientResolver, addressResolver, fileProvider)),
sentMessage.getEditMessage()
.map(message -> Edit.from(message, recipientResolver, addressResolver, fileProvider)),
sentMessage.getStoryMessage().map(s -> Story.from(s, fileProvider)));
}
}
@ -570,10 +611,12 @@ public record MessageEnvelope(
RecipientResolver recipientResolver,
RecipientAddressResolver addressResolver
) {
return new Blocked(blockedListMessage.getAddresses()
.stream()
.map(d -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(d)))
.toList(), blockedListMessage.getGroupIds().stream().map(GroupId::unknownVersion).toList());
return new Blocked(blockedListMessage.individuals.stream()
.map(d -> new RecipientAddress(d.getAci() == null ? null : d.getAci().toString(),
null,
d.getE164(),
null))
.toList(), blockedListMessage.groupIds.stream().map(GroupId::unknownVersion).toList());
}
}
@ -584,8 +627,8 @@ public record MessageEnvelope(
RecipientResolver recipientResolver,
RecipientAddressResolver addressResolver
) {
return new Read(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender())),
readMessage.getTimestamp());
return new Read(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender()))
.toApiRecipientAddress(), readMessage.getTimestamp());
}
}
@ -596,8 +639,8 @@ public record MessageEnvelope(
RecipientResolver recipientResolver,
RecipientAddressResolver addressResolver
) {
return new Viewed(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender())),
readMessage.getTimestamp());
return new Viewed(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(readMessage.getSender()))
.toApiRecipientAddress(), readMessage.getTimestamp());
}
}
@ -609,7 +652,7 @@ public record MessageEnvelope(
RecipientAddressResolver addressResolver
) {
return new ViewOnceOpen(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
readMessage.getSender())), readMessage.getTimestamp());
readMessage.getSender())).toApiRecipientAddress(), readMessage.getTimestamp());
}
}
@ -637,7 +680,8 @@ public record MessageEnvelope(
return new MessageRequestResponse(Type.from(messageRequestResponse.getType()),
messageRequestResponse.getGroupId().map(GroupId::unknownVersion),
messageRequestResponse.getPerson()
.map(p -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(p))));
.map(p -> addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(p))
.toApiRecipientAddress()));
}
public enum Type {
@ -646,7 +690,9 @@ public record MessageEnvelope(
DELETE,
BLOCK,
BLOCK_AND_DELETE,
UNBLOCK_AND_ACCEPT;
UNBLOCK_AND_ACCEPT,
SPAM,
BLOCK_AND_SPAM;
static Type from(MessageRequestResponseMessage.Type type) {
return switch (type) {
@ -656,6 +702,8 @@ public record MessageEnvelope(
case BLOCK -> BLOCK;
case BLOCK_AND_DELETE -> BLOCK_AND_DELETE;
case UNBLOCK_AND_ACCEPT -> UNBLOCK_AND_ACCEPT;
case SPAM -> SPAM;
case BLOCK_AND_SPAM -> BLOCK_AND_SPAM;
};
}
}
@ -671,7 +719,8 @@ public record MessageEnvelope(
Optional<Hangup> hangup,
Optional<Busy> busy,
List<IceUpdate> iceUpdate,
Optional<Opaque> opaque
Optional<Opaque> opaque,
boolean isUrgent
) {
public static Call from(final SignalServiceCallMessage callMessage) {
@ -685,16 +734,14 @@ public record MessageEnvelope(
callMessage.getIceUpdateMessages()
.map(m -> m.stream().map(IceUpdate::from).toList())
.orElse(List.of()),
callMessage.getOpaqueMessage().map(Opaque::from));
callMessage.getOpaqueMessage().map(Opaque::from),
callMessage.isUrgent());
}
public record Offer(long id, String sdp, Type type, byte[] opaque) {
public record Offer(long id, Type type, byte[] opaque) {
static Offer from(OfferMessage offerMessage) {
return new Offer(offerMessage.getId(),
offerMessage.getSdp(),
Type.from(offerMessage.getType()),
offerMessage.getOpaque());
return new Offer(offerMessage.getId(), Type.from(offerMessage.getType()), offerMessage.getOpaque());
}
public enum Type {
@ -710,10 +757,10 @@ public record MessageEnvelope(
}
}
public record Answer(long id, String sdp, byte[] opaque) {
public record Answer(long id, byte[] opaque) {
static Answer from(AnswerMessage answerMessage) {
return new Answer(answerMessage.getId(), answerMessage.getSdp(), answerMessage.getOpaque());
return new Answer(answerMessage.getId(), answerMessage.getOpaque());
}
}
@ -724,13 +771,12 @@ public record MessageEnvelope(
}
}
public record Hangup(long id, Type type, int deviceId, boolean isLegacy) {
public record Hangup(long id, Type type, int deviceId) {
static Hangup from(HangupMessage hangupMessage) {
return new Hangup(hangupMessage.getId(),
Type.from(hangupMessage.getType()),
hangupMessage.getDeviceId(),
hangupMessage.isLegacy());
hangupMessage.getDeviceId());
}
public enum Type {
@ -752,10 +798,10 @@ public record MessageEnvelope(
}
}
public record IceUpdate(long id, String sdp, byte[] opaque) {
public record IceUpdate(long id, byte[] opaque) {
static IceUpdate from(IceUpdateMessage iceUpdateMessage) {
return new IceUpdate(iceUpdateMessage.getId(), iceUpdateMessage.getSdp(), iceUpdateMessage.getOpaque());
return new IceUpdate(iceUpdateMessage.getId(), iceUpdateMessage.getOpaque());
}
}
@ -786,9 +832,7 @@ public record MessageEnvelope(
Optional<TextAttachment> textAttachment
) {
public static Story from(
SignalServiceStoryMessage storyMessage, final AttachmentFileProvider fileProvider
) {
public static Story from(SignalServiceStoryMessage storyMessage, final AttachmentFileProvider fileProvider) {
return new Story(storyMessage.getAllowsReplies().orElse(false),
storyMessage.getGroupContext().map(c -> GroupUtils.getGroupIdV2(c.getMasterKey())),
storyMessage.getFileAttachment().map(f -> Data.Attachment.from(f, fileProvider)),
@ -806,7 +850,8 @@ public record MessageEnvelope(
) {
static TextAttachment from(
SignalServiceTextAttachment textAttachment, final AttachmentFileProvider fileProvider
SignalServiceTextAttachment textAttachment,
final AttachmentFileProvider fileProvider
) {
return new TextAttachment(textAttachment.getText(),
textAttachment.getStyle().map(Style::from),
@ -858,8 +903,9 @@ public record MessageEnvelope(
final AttachmentFileProvider fileProvider,
Exception exception
) {
final var source = !envelope.isUnidentifiedSender() && envelope.hasSourceUuid()
? recipientResolver.resolveRecipient(envelope.getSourceAddress())
final var serviceId = envelope.getSourceServiceId().map(ServiceId::parseOrNull).orElse(null);
final var source = !envelope.isUnidentifiedSender() && serviceId != null
? recipientResolver.resolveRecipient(serviceId)
: envelope.isUnidentifiedSender() && content != null
? recipientResolver.resolveRecipient(content.getSender())
: exception instanceof ProtocolException e
@ -874,6 +920,7 @@ public record MessageEnvelope(
Optional<Receipt> receipt;
Optional<Typing> typing;
Optional<Data> data;
Optional<Edit> edit;
Optional<Sync> sync;
Optional<Call> call;
Optional<Story> story;
@ -882,6 +929,7 @@ public record MessageEnvelope(
typing = content.getTypingMessage().map(Typing::from);
data = content.getDataMessage()
.map(dataMessage -> Data.from(dataMessage, recipientResolver, addressResolver, fileProvider));
edit = content.getEditMessage().map(s -> Edit.from(s, recipientResolver, addressResolver, fileProvider));
sync = content.getSyncMessage().map(s -> Sync.from(s, recipientResolver, addressResolver, fileProvider));
call = content.getCallMessage().map(Call::from);
story = content.getStoryMessage().map(s -> Story.from(s, fileProvider));
@ -891,6 +939,7 @@ public record MessageEnvelope(
List.of(envelope.getTimestamp()))) : Optional.empty();
typing = Optional.empty();
data = Optional.empty();
edit = Optional.empty();
sync = Optional.empty();
call = Optional.empty();
story = Optional.empty();
@ -898,7 +947,7 @@ public record MessageEnvelope(
return new MessageEnvelope(source == null
? Optional.empty()
: Optional.of(addressResolver.resolveRecipientAddress(source)),
: Optional.of(addressResolver.resolveRecipientAddress(source).toApiRecipientAddress()),
sourceDevice,
envelope.getTimestamp(),
envelope.getServerReceivedTimestamp(),
@ -907,6 +956,7 @@ public record MessageEnvelope(
receipt,
typing,
data,
edit,
sync,
call,
story);
@ -914,6 +964,6 @@ public record MessageEnvelope(
public interface AttachmentFileProvider {
File getFile(SignalServiceAttachmentRemoteId attachmentRemoteId);
File getFile(SignalServiceAttachmentPointer pointer);
}
}

View file

@ -1,4 +1,4 @@
package org.asamk.signal.manager.groups;
package org.asamk.signal.manager.api;
public class NotAGroupMemberException extends Exception {

View file

@ -0,0 +1,12 @@
package org.asamk.signal.manager.api;
public class PendingAdminApprovalException extends Exception {
public PendingAdminApprovalException(final String message) {
super(message);
}
public PendingAdminApprovalException(final String message, final Throwable cause) {
super(message, cause);
}
}

View file

@ -3,5 +3,16 @@ package org.asamk.signal.manager.api;
public enum PhoneNumberSharingMode {
EVERYBODY,
CONTACTS,
NOBODY,
NOBODY;
public static PhoneNumberSharingMode valueOfOrNull(String value) {
if (value == null) {
return null;
}
try {
return valueOf(value);
} catch (IllegalArgumentException ignored) {
return null;
}
}
}

View file

@ -0,0 +1,3 @@
package org.asamk.signal.manager.api;
public class PinLockMissingException extends Exception {}

View file

@ -1,4 +1,4 @@
package org.asamk.signal.manager.storage.recipients;
package org.asamk.signal.manager.api;
import org.whispersystems.signalservice.internal.util.Util;
@ -26,6 +26,8 @@ public class Profile {
private final Set<Capability> capabilities;
private final PhoneNumberSharingMode phoneNumberSharingMode;
public Profile(
final long lastUpdateTimestamp,
final String givenName,
@ -35,7 +37,8 @@ public class Profile {
final String avatarUrlPath,
final byte[] mobileCoinAddress,
final UnidentifiedAccessMode unidentifiedAccessMode,
final Set<Capability> capabilities
final Set<Capability> capabilities,
final PhoneNumberSharingMode phoneNumberSharingMode
) {
this.lastUpdateTimestamp = lastUpdateTimestamp;
this.givenName = givenName;
@ -46,6 +49,7 @@ public class Profile {
this.mobileCoinAddress = mobileCoinAddress;
this.unidentifiedAccessMode = unidentifiedAccessMode;
this.capabilities = capabilities;
this.phoneNumberSharingMode = phoneNumberSharingMode;
}
private Profile(final Builder builder) {
@ -58,6 +62,7 @@ public class Profile {
mobileCoinAddress = builder.mobileCoinAddress;
unidentifiedAccessMode = builder.unidentifiedAccessMode;
capabilities = builder.capabilities;
phoneNumberSharingMode = builder.phoneNumberSharingMode;
}
public static Builder newBuilder() {
@ -136,13 +141,17 @@ public class Profile {
return capabilities;
}
public PhoneNumberSharingMode getPhoneNumberSharingMode() {
return phoneNumberSharingMode;
}
public enum UnidentifiedAccessMode {
UNKNOWN,
DISABLED,
ENABLED,
UNRESTRICTED;
static UnidentifiedAccessMode valueOfOrUnknown(String value) {
public static UnidentifiedAccessMode valueOfOrUnknown(String value) {
try {
return valueOf(value);
} catch (IllegalArgumentException ignored) {
@ -153,11 +162,9 @@ public class Profile {
public enum Capability {
storage,
gv1Migration,
senderKey,
announcementGroup;
storageServiceEncryptionV2Capability;
static Capability valueOfOrNull(String value) {
public static Capability valueOfOrNull(String value) {
try {
return valueOf(value);
} catch (IllegalArgumentException ignored) {
@ -203,6 +210,7 @@ public class Profile {
private byte[] mobileCoinAddress;
private UnidentifiedAccessMode unidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN;
private Set<Capability> capabilities = Collections.emptySet();
private PhoneNumberSharingMode phoneNumberSharingMode;
private long lastUpdateTimestamp = 0;
private Builder() {
@ -243,6 +251,11 @@ public class Profile {
return this;
}
public Builder withPhoneNumberSharingMode(final PhoneNumberSharingMode val) {
phoneNumberSharingMode = val;
return this;
}
public Profile build() {
return new Profile(this);
}

View file

@ -31,12 +31,12 @@ public class ProofRequiredException extends Exception {
}
public enum Option {
RECAPTCHA,
CAPTCHA,
PUSH_CHALLENGE;
static Option from(org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException.Option option) {
return switch (option) {
case RECAPTCHA -> RECAPTCHA;
case CAPTCHA -> CAPTCHA;
case PUSH_CHALLENGE -> PUSH_CHALLENGE;
};
}

View file

@ -0,0 +1,15 @@
package org.asamk.signal.manager.api;
public class RateLimitException extends Exception {
private final long nextAttemptTimestamp;
public RateLimitException(final long nextAttemptTimestamp) {
super("Rate limit");
this.nextAttemptTimestamp = nextAttemptTimestamp;
}
public long getNextAttemptTimestamp() {
return nextAttemptTimestamp;
}
}

View file

@ -1,3 +1,3 @@
package org.asamk.signal.manager.api;
public record ReceiveConfig(boolean ignoreAttachments, boolean sendReadReceipts) {}
public record ReceiveConfig(boolean ignoreAttachments, boolean ignoreStories, boolean sendReadReceipts) {}

View file

@ -0,0 +1,166 @@
package org.asamk.signal.manager.api;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import java.util.Objects;
public class Recipient {
private final RecipientId recipientId;
private final RecipientAddress address;
private final Contact contact;
private final ProfileKey profileKey;
private final ExpiringProfileKeyCredential expiringProfileKeyCredential;
private final Profile profile;
private final Boolean discoverable;
public Recipient(
final RecipientId recipientId,
final RecipientAddress address,
final Contact contact,
final ProfileKey profileKey,
final ExpiringProfileKeyCredential expiringProfileKeyCredential,
final Profile profile,
final Boolean discoverable
) {
this.recipientId = recipientId;
this.address = address;
this.contact = contact;
this.profileKey = profileKey;
this.expiringProfileKeyCredential = expiringProfileKeyCredential;
this.profile = profile;
this.discoverable = discoverable;
}
private Recipient(final Builder builder) {
recipientId = builder.recipientId;
address = builder.address;
contact = builder.contact;
profileKey = builder.profileKey;
expiringProfileKeyCredential = builder.expiringProfileKeyCredential;
profile = builder.profile;
discoverable = builder.discoverable;
}
public static Builder newBuilder() {
return new Builder();
}
public static Builder newBuilder(final Recipient copy) {
Builder builder = new Builder();
builder.recipientId = copy.getRecipientId();
builder.address = copy.getAddress();
builder.contact = copy.getContact();
builder.profileKey = copy.getProfileKey();
builder.expiringProfileKeyCredential = copy.getExpiringProfileKeyCredential();
builder.profile = copy.getProfile();
return builder;
}
public RecipientId getRecipientId() {
return recipientId;
}
public RecipientAddress getAddress() {
return address;
}
public Contact getContact() {
return contact;
}
public ProfileKey getProfileKey() {
return profileKey;
}
public ExpiringProfileKeyCredential getExpiringProfileKeyCredential() {
return expiringProfileKeyCredential;
}
public Profile getProfile() {
return profile;
}
public Boolean getDiscoverable() {
return discoverable;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Recipient recipient = (Recipient) o;
return Objects.equals(recipientId, recipient.recipientId)
&& Objects.equals(address, recipient.address)
&& Objects.equals(contact, recipient.contact)
&& Objects.equals(profileKey, recipient.profileKey)
&& Objects.equals(expiringProfileKeyCredential, recipient.expiringProfileKeyCredential)
&& Objects.equals(profile, recipient.profile);
}
@Override
public int hashCode() {
return Objects.hash(recipientId, address, contact, profileKey, expiringProfileKeyCredential, profile);
}
public static final class Builder {
private RecipientId recipientId;
private RecipientAddress address;
private Contact contact;
private ProfileKey profileKey;
private ExpiringProfileKeyCredential expiringProfileKeyCredential;
private Profile profile;
private Boolean discoverable;
private Builder() {
}
public Builder withRecipientId(final RecipientId val) {
recipientId = val;
return this;
}
public Builder withAddress(final RecipientAddress val) {
address = val;
return this;
}
public Builder withContact(final Contact val) {
contact = val;
return this;
}
public Builder withProfileKey(final ProfileKey val) {
profileKey = val;
return this;
}
public Builder withExpiringProfileKeyCredential(final ExpiringProfileKeyCredential val) {
expiringProfileKeyCredential = val;
return this;
}
public Builder withProfile(final Profile val) {
profile = val;
return this;
}
public Builder withDiscoverable(final Boolean val) {
discoverable = val;
return this;
}
public Recipient build() {
return new Recipient(this);
}
}
}

View file

@ -0,0 +1,76 @@
package org.asamk.signal.manager.api;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.util.Optional;
import java.util.UUID;
public record RecipientAddress(
Optional<String> aci, Optional<String> pni, Optional<String> number, Optional<String> username
) {
public static final UUID UNKNOWN_UUID = UuidUtil.UNKNOWN_UUID;
/**
* Construct a RecipientAddress.
*
* @param aci The ACI of the user, if available.
* @param pni The PNI of the user, if available.
* @param number The phone number of the user, if available.
*/
public RecipientAddress {
if (aci.isEmpty() && pni.isEmpty() && number.isEmpty() && username.isEmpty()) {
throw new AssertionError("Must have either a ACI, PNI, username or E164 number!");
}
}
public RecipientAddress(String e164) {
this(null, null, e164, null);
}
public RecipientAddress(UUID uuid) {
this(uuid.toString(), null, null, null);
}
public RecipientAddress(String aci, String pni, String e164, String username) {
this(Optional.ofNullable(aci),
Optional.ofNullable(pni),
Optional.ofNullable(e164),
Optional.ofNullable(username));
}
public Optional<UUID> uuid() {
return aci.map(UUID::fromString);
}
public String getIdentifier() {
if (aci.isPresent()) {
return aci.get();
} else if (number.isPresent()) {
return number.get();
} else if (pni.isPresent()) {
return pni.get();
} else if (username.isPresent()) {
return username.get();
} else {
throw new AssertionError("Given the checks in the constructor, this should not be possible.");
}
}
public String getLegacyIdentifier() {
if (number.isPresent()) {
return number.get();
} else {
return getIdentifier();
}
}
public boolean matches(RecipientAddress other) {
return (aci.isPresent() && other.aci.isPresent() && aci.get().equals(other.aci.get()))
|| (
pni.isPresent() && other.pni.isPresent() && pni.get().equals(other.pni.get())
)
|| (number.isPresent() && other.number.isPresent() && number.get().equals(other.number.get()))
|| (username.isPresent() && other.username.isPresent() && username.get().equals(other.username.get()));
}
}

View file

@ -1,10 +1,8 @@
package org.asamk.signal.manager.api;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.manager.util.PhoneNumberFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.util.UUID;
@ -26,27 +24,39 @@ public sealed interface RecipientIdentifier {
sealed interface Single extends RecipientIdentifier {
static Single fromString(String identifier, String localNumber) throws InvalidNumberException {
try {
if (UuidUtil.isUuid(identifier)) {
return new Uuid(UUID.fromString(identifier));
}
final var normalizedNumber = PhoneNumberFormatter.formatNumber(identifier, localNumber);
if (!normalizedNumber.equals(identifier)) {
final Logger logger = LoggerFactory.getLogger(RecipientIdentifier.class);
logger.debug("Normalized number {} to {}.", identifier, normalizedNumber);
}
return new Number(normalizedNumber);
} catch (org.whispersystems.signalservice.api.util.InvalidNumberException e) {
throw new InvalidNumberException(e.getMessage(), e);
if (UuidUtil.isUuid(identifier)) {
return new Uuid(UUID.fromString(identifier));
}
if (identifier.startsWith("PNI:")) {
final var pni = identifier.substring(4);
if (!UuidUtil.isUuid(pni)) {
throw new InvalidNumberException("Invalid PNI");
}
return new Pni(UUID.fromString(pni));
}
if (identifier.startsWith("u:")) {
return new Username(identifier.substring(2));
}
final var normalizedNumber = PhoneNumberFormatter.formatNumber(identifier, localNumber);
if (!normalizedNumber.equals(identifier)) {
final Logger logger = LoggerFactory.getLogger(RecipientIdentifier.class);
logger.debug("Normalized number {} to {}.", identifier, normalizedNumber);
}
return new Number(normalizedNumber);
}
static Single fromAddress(RecipientAddress address) {
if (address.number().isPresent()) {
return new Number(address.number().get());
} else if (address.uuid().isPresent()) {
return new Uuid(address.uuid().get());
} else if (address.aci().isPresent()) {
return new Uuid(UUID.fromString(address.aci().get()));
} else if (address.pni().isPresent()) {
return new Pni(UUID.fromString(address.pni().get().substring(4)));
} else if (address.username().isPresent()) {
return new Username(address.username().get());
}
throw new AssertionError("RecipientAddress without identifier");
}
@ -67,6 +77,19 @@ public sealed interface RecipientIdentifier {
}
}
record Pni(UUID pni) implements Single {
@Override
public String getIdentifier() {
return "PNI:" + pni.toString();
}
@Override
public RecipientAddress toPartialRecipientAddress() {
return new RecipientAddress(null, getIdentifier(), null, null);
}
}
record Number(String number) implements Single {
@Override
@ -76,7 +99,20 @@ public sealed interface RecipientIdentifier {
@Override
public RecipientAddress toPartialRecipientAddress() {
return new RecipientAddress(null, number);
return new RecipientAddress(number);
}
}
record Username(String username) implements Single {
@Override
public String getIdentifier() {
return "u:" + username;
}
@Override
public RecipientAddress toPartialRecipientAddress() {
return new RecipientAddress(null, null, null, username);
}
}

View file

@ -1,9 +1,7 @@
package org.asamk.signal.manager.api;
import org.asamk.signal.manager.helper.RecipientAddressResolver;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
import org.signal.libsignal.protocol.IdentityKey;
public record SendMessageResult(
RecipientAddress address,
@ -12,29 +10,12 @@ public record SendMessageResult(
boolean isUnregisteredFailure,
boolean isIdentityFailure,
boolean isRateLimitFailure,
ProofRequiredException proofRequiredFailure
ProofRequiredException proofRequiredFailure,
boolean isInvalidPreKeyFailure
) {
public static SendMessageResult success(RecipientAddress address) {
return new SendMessageResult(address, true, false, false, false, false, null);
}
public static SendMessageResult networkFailure(RecipientAddress address) {
return new SendMessageResult(address, false, true, false, false, false, null);
}
public static SendMessageResult unregisteredFailure(RecipientAddress address) {
return new SendMessageResult(address, false, false, true, false, false, null);
}
public static SendMessageResult identityFailure(RecipientAddress address, IdentityKey identityKey) {
return new SendMessageResult(address, false, false, false, true, false, null);
}
public static SendMessageResult proofRequiredFailure(
RecipientAddress address, ProofRequiredException proofRequiredException
) {
return new SendMessageResult(address, false, true, false, false, false, proofRequiredException);
return new SendMessageResult(address, false, false, true, false, false, null, false);
}
public static SendMessageResult from(
@ -43,7 +24,7 @@ public record SendMessageResult(
RecipientAddressResolver addressResolver
) {
return new SendMessageResult(addressResolver.resolveRecipientAddress(recipientResolver.resolveRecipient(
sendMessageResult.getAddress())),
sendMessageResult.getAddress())).toApiRecipientAddress(),
sendMessageResult.isSuccess(),
sendMessageResult.isNetworkFailure(),
sendMessageResult.isUnregisteredFailure(),
@ -51,6 +32,7 @@ public record SendMessageResult(
sendMessageResult.getRateLimitFailure() != null || sendMessageResult.getProofRequiredFailure() != null,
sendMessageResult.getProofRequiredFailure() == null
? null
: new ProofRequiredException(sendMessageResult.getProofRequiredFailure()));
: new ProofRequiredException(sendMessageResult.getProofRequiredFailure()),
sendMessageResult.isInvalidPreKeyFailure());
}
}

View file

@ -19,4 +19,11 @@ public record SendMessageResults(long timestamp, Map<RecipientIdentifier, List<S
.allMatch(identityFailure -> identityFailure)
&& results.values().stream().mapToInt(List::size).sum() > 0;
}
public boolean hasOnlyRateLimitFailure() {
return results.values()
.stream()
.flatMap(res -> res.stream().map(SendMessageResult::isRateLimitFailure))
.allMatch(r -> r) && results.values().stream().mapToInt(List::size).sum() > 0;
}
}

View file

@ -1,4 +1,4 @@
package org.asamk.signal.manager.config;
package org.asamk.signal.manager.api;
public enum ServiceEnvironment {
LIVE,

View file

@ -1,5 +1,7 @@
package org.asamk.signal.manager.api;
import org.whispersystems.signalservice.internal.util.Hex;
import java.util.Arrays;
public class StickerPackId {
@ -32,4 +34,9 @@ public class StickerPackId {
public int hashCode() {
return Arrays.hashCode(id);
}
@Override
public String toString() {
return "StickerPackId{" + Hex.toStringCondensed(id) + '}';
}
}

View file

@ -11,10 +11,7 @@ import java.nio.charset.StandardCharsets;
import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
public final class StickerPackUrl {
private final StickerPackId packId;
private final byte[] packKey;
public record StickerPackUrl(StickerPackId packId, byte[] packKey) {
/**
* @throws InvalidStickerPackLinkException If url cannot be parsed.
@ -22,7 +19,7 @@ public final class StickerPackUrl {
public static StickerPackUrl fromUri(URI uri) throws InvalidStickerPackLinkException {
final var rawQuery = uri.getRawFragment();
if (isEmpty(rawQuery)) {
throw new RuntimeException("Invalid sticker pack uri");
throw new InvalidStickerPackLinkException("Invalid sticker pack uri");
}
var query = Utils.getQueryMap(rawQuery);
@ -48,11 +45,6 @@ public final class StickerPackUrl {
return new StickerPackUrl(packId, packKey);
}
public StickerPackUrl(final StickerPackId packId, final byte[] packKey) {
this.packId = packId;
this.packKey = packKey;
}
public URI getUrl() {
try {
return new URI("https",
@ -67,15 +59,7 @@ public final class StickerPackUrl {
}
}
public StickerPackId getPackId() {
return packId;
}
public byte[] getPackKey() {
return packKey;
}
public final static class InvalidStickerPackLinkException extends Exception {
public static final class InvalidStickerPackLinkException extends Exception {
public InvalidStickerPackLinkException(String message) {
super(message);

View file

@ -0,0 +1,63 @@
package org.asamk.signal.manager.api;
import org.whispersystems.signalservice.internal.push.BodyRange;
public record TextStyle(Style style, Integer start, Integer length) {
public enum Style {
NONE,
BOLD,
ITALIC,
SPOILER,
STRIKETHROUGH,
MONOSPACE;
static Style fromInternal(BodyRange.Style style) {
if (style == null) {
return NONE;
}
return switch (style) {
case NONE -> NONE;
case BOLD -> BOLD;
case ITALIC -> ITALIC;
case SPOILER -> SPOILER;
case STRIKETHROUGH -> STRIKETHROUGH;
case MONOSPACE -> MONOSPACE;
};
}
public static Style from(String style) {
return switch (style) {
case "NONE" -> NONE;
case "BOLD" -> BOLD;
case "ITALIC" -> ITALIC;
case "SPOILER" -> SPOILER;
case "STRIKETHROUGH" -> STRIKETHROUGH;
case "MONOSPACE" -> MONOSPACE;
default -> null;
};
}
BodyRange.Style toBodyRangeStyle() {
return switch (this) {
case NONE -> BodyRange.Style.NONE;
case BOLD -> BodyRange.Style.BOLD;
case ITALIC -> BodyRange.Style.ITALIC;
case SPOILER -> BodyRange.Style.SPOILER;
case STRIKETHROUGH -> BodyRange.Style.STRIKETHROUGH;
case MONOSPACE -> BodyRange.Style.MONOSPACE;
};
}
}
static TextStyle from(BodyRange bodyRange) {
return new TextStyle(Style.fromInternal(bodyRange.style), bodyRange.start, bodyRange.length);
}
public BodyRange toBodyRange() {
return new BodyRange.Builder().start(this.start())
.length(this.length())
.style(this.style().toBodyRangeStyle())
.build();
}
}

View file

@ -1,7 +1,6 @@
package org.asamk.signal.manager.api;
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord;
public enum TrustLevel {
UNTRUSTED,
@ -17,15 +16,6 @@ public enum TrustLevel {
return TrustLevel.cachedValues[i];
}
public static TrustLevel fromIdentityState(ContactRecord.IdentityState identityState) {
return switch (identityState) {
case DEFAULT -> TRUSTED_UNVERIFIED;
case UNVERIFIED -> UNTRUSTED;
case VERIFIED -> TRUSTED_VERIFIED;
case UNRECOGNIZED -> null;
};
}
public static TrustLevel fromVerifiedState(VerifiedMessage.VerifiedState verifiedState) {
return switch (verifiedState) {
case DEFAULT -> TRUSTED_UNVERIFIED;

View file

@ -1,4 +1,4 @@
package org.asamk.signal.manager.storage.identities;
package org.asamk.signal.manager.api;
public enum TrustNewIdentity {
ALWAYS,

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