mirror of
https://github.com/AsamK/signal-cli
synced 2025-09-02 04:20:38 +00:00
Compare commits
1561 commits
Author | SHA1 | Date | |
---|---|---|---|
|
f6d81e3c05 | ||
|
42f10670b6 | ||
|
b453d7a0b9 | ||
|
f9a36c6e04 | ||
|
be48afb2b5 | ||
|
a0960fcabd | ||
|
dbc454ba9e | ||
|
2225e69277 | ||
|
783201d12e | ||
|
3e981d66e9 | ||
|
7c7fc76a64 | ||
|
c924d5c03a | ||
|
dc787be17b | ||
|
3d4070a139 | ||
|
dbdff83132 | ||
|
4ce194afe2 | ||
|
ca33249170 | ||
|
a96626c468 | ||
|
d54be747da | ||
|
ff846bc678 | ||
|
1b7f755590 | ||
|
887ed3bb44 | ||
|
3180eba836 | ||
|
cb06cbdcca | ||
|
069325af47 | ||
|
e7ca02f1fb | ||
|
fa9bb3c210 | ||
|
e6113d4d96 | ||
|
6cc3a6f561 | ||
|
70c79eac01 | ||
|
5dc66f839d | ||
|
a0d5744c49 | ||
|
6b60a6d5a5 | ||
|
0257344940 | ||
|
17cd99be59 | ||
|
2f8328847c | ||
|
7e9727aa38 | ||
|
bf87fcc652 | ||
|
6b46314eab | ||
|
e89803464b | ||
|
a9bb8d9aae | ||
|
74909408c4 | ||
|
bb124a922d | ||
|
56e11d0857 | ||
|
d0d0021f57 | ||
|
7aafb05995 | ||
|
e594f3b237 | ||
|
bb86830a61 | ||
|
bcc1eadc7d | ||
|
4fd9e55c3c | ||
|
a2900085c9 | ||
|
5e11cf1c50 | ||
|
4e455d85d6 | ||
|
1e685c7cab | ||
|
ce813e4529 | ||
|
bd7948e246 | ||
|
b998f322f5 | ||
|
db2182aa7d | ||
|
69a9b30732 | ||
|
3dc8844cb4 | ||
|
adb6787d5b | ||
|
14b07be0dc | ||
|
6befda7ef1 | ||
|
67302eb9c3 | ||
|
f18015ff2e | ||
|
1295ef69ca | ||
|
f26a0d2891 | ||
|
2b150112ff | ||
|
7aede7c17f | ||
|
b92cbc6a7c | ||
|
68b7416e57 | ||
|
4feba68afd | ||
|
4eb34c7a93 | ||
|
26fd3e379a | ||
|
93d281e712 | ||
|
985af6e445 | ||
|
5693d871f7 | ||
|
dba8cf7a6f | ||
|
141d3326ab | ||
|
d3d2caac5a | ||
|
e1f4dae5c2 | ||
|
cf5c943127 | ||
|
ed79e0b377 | ||
|
a089a5ef04 | ||
|
90145655f4 | ||
|
3cd07ae9cd | ||
|
8aa71c132f | ||
|
b579935846 | ||
|
dfa886fae9 | ||
|
f04f789231 | ||
|
a6ec71dc31 | ||
|
47d65586cd | ||
|
b8d8413a22 | ||
|
5e16123632 | ||
|
d57442bd2a | ||
|
70313c45a9 | ||
|
f14c204764 | ||
|
71d3b83a1c | ||
|
148bf7dee2 | ||
|
2d1ba6b4ca | ||
|
055a8ee8b9 | ||
|
407a20d4bb | ||
|
05cd6aee6a | ||
|
a1378507b2 | ||
|
78cd0b13de | ||
|
c25468a71e | ||
|
a5d2e1ea23 | ||
|
6acf16ef4e | ||
|
e11e093020 | ||
|
74c2604dc8 | ||
|
e4af0be0ad | ||
|
5ac5938c8b | ||
|
94269744ad | ||
|
7a25ae5b9c | ||
|
cbd92654cf | ||
|
bd95373a70 | ||
|
d982633215 | ||
|
f91ca82902 | ||
|
c55ee85c5c | ||
|
a3776c88bd | ||
|
4a781656b4 | ||
|
11d38f29ef | ||
|
22a0ff976a | ||
|
c05b47e4d0 | ||
|
ac145e6a27 | ||
|
f00b8523d9 | ||
|
c3f8d68ceb | ||
|
9d92a3e06b | ||
|
f2df600d38 | ||
|
24d344fda4 | ||
|
0a296e77a0 | ||
|
ba147a48f8 | ||
|
77a5c454b7 | ||
|
2c68b5a9e1 | ||
|
68c9d84d19 | ||
|
fe752e0c79 | ||
|
26b5a4c582 | ||
|
10ee295ea3 | ||
|
6a5ea5fc01 | ||
|
ff6cb5262a | ||
|
f2005593ec | ||
|
3533500b73 | ||
|
e5251ae158 | ||
|
a5e272be3f | ||
|
277652e3f2 | ||
|
181086aefe | ||
|
a1d552698a | ||
|
1b959608c3 | ||
|
acddef6e35 | ||
|
0e77870b59 | ||
|
0a287b0b3e | ||
|
5a4f4ba6db | ||
|
5171107a29 | ||
|
47e6fc1769 | ||
|
9afd4e4328 | ||
|
eac2a47163 | ||
|
5646f65195 | ||
|
fab1b96c21 | ||
|
91eacc18c2 | ||
|
7f1fc932ad | ||
|
304c44064d | ||
|
946c483f35 | ||
|
69d691f416 | ||
|
a6ab8f7e80 | ||
|
19dc2d446b | ||
|
8524037900 | ||
|
dbbc3fbd71 | ||
|
c6e93126fa | ||
|
352699c4b6 | ||
|
bea2772491 | ||
|
bab8ddf35a | ||
|
1d5d16f57e | ||
|
6f9e9e9302 | ||
|
20add0f27b | ||
|
eca3c6fa30 | ||
|
a0d1b081ff | ||
|
d852c60c37 | ||
|
2b5451f74a | ||
|
b322716215 | ||
|
41726d339f | ||
|
52bb92dce4 | ||
|
65adfdd6f5 | ||
|
d8b1a2fffe | ||
|
ea436ecb64 | ||
|
82fbde4f19 | ||
|
19b15e68e4 | ||
|
b51f849fe6 | ||
|
7cc0ef1c70 | ||
|
485c4fd467 | ||
|
bda395191b | ||
|
cb129db95e | ||
|
130cee9906 | ||
|
6bdc9a4b16 | ||
|
a69a9b7b0e | ||
|
6ea373fbd5 | ||
|
62da514850 | ||
|
fe3934171d | ||
|
3d11221732 | ||
|
e961488b1a | ||
|
2db3d3259e | ||
|
e51b1ee23a | ||
|
5ff66728e3 | ||
|
baf7b74a61 | ||
|
c716f94649 | ||
|
8b355918e8 | ||
|
67012b40b1 | ||
|
fd402b52c8 | ||
|
5a97b9e134 | ||
|
17596795c2 | ||
|
10b9c264fd | ||
|
a2b002ac5e | ||
|
6d764db114 | ||
|
777cfbb69f | ||
|
9781c56571 | ||
|
04cf54263e | ||
|
6baf0eac13 | ||
|
fb21a42cce | ||
|
53d7e0f08b | ||
|
8f756cd90c | ||
|
fb81bf1d05 | ||
|
04726f005c | ||
|
09e3e7f335 | ||
|
90b1e4bc02 | ||
|
3f31f1a8a6 | ||
|
3cd8e323c9 | ||
|
7060faf5d3 | ||
|
c9f2cca024 | ||
|
f0054372b8 | ||
|
0a82c51b79 | ||
|
cef83d962c | ||
|
c1775913b9 | ||
|
e4c5144fbf | ||
|
8aeaf927e6 | ||
|
7e0d4c9b89 | ||
|
71de8e63cc | ||
|
e456d06cb0 | ||
|
e0cd5b987e | ||
|
e5ebb732cb | ||
|
419beee29a | ||
|
d4e1f9b7f1 | ||
|
edce33ae15 | ||
|
95f9e18de2 | ||
|
95d4b7d012 | ||
|
17c24b3ff2 | ||
|
be0e8ddd8a | ||
|
49cc9cd9f8 | ||
|
c85c995fef | ||
|
dda23e76ac | ||
|
95e70b9d15 | ||
|
abddf24752 | ||
|
d356d92b5e | ||
|
8b4f377cf1 | ||
|
323a801600 | ||
|
3372992dc2 | ||
|
ee39733978 | ||
|
5e17fe8414 | ||
|
aebe64571d | ||
|
0cc2da690a | ||
|
2424fc1f53 | ||
|
2e4cd0eddc | ||
|
189b21dbde | ||
|
e77d9e3d60 | ||
|
df76aa9919 | ||
|
378ac23c6c | ||
|
fc2ae856d2 | ||
|
57164ad7fb | ||
|
6c44662496 | ||
|
22ac3cb50f | ||
|
b76964f219 | ||
|
2f3c064462 | ||
|
c9002d9481 | ||
|
83d471818d | ||
|
0bb2a64781 | ||
|
08ba774b71 | ||
|
59c1f4eed2 | ||
|
f1e3b5c9cc | ||
|
7bc7242f08 | ||
|
6f407ab509 | ||
|
db9acaf4ff | ||
|
7821815429 | ||
|
cd88e896fa | ||
|
67fd10b978 | ||
|
9051f68ba6 | ||
|
3b3377e6e9 | ||
|
fb99ff5284 | ||
|
ed40d116b7 | ||
|
d84362eb0f | ||
|
2c0ad7feb7 | ||
|
7206b4da25 | ||
|
fd671576a4 | ||
|
6cd57312a1 | ||
|
25258db55d | ||
|
91ed49e019 | ||
|
bd792f5b7f | ||
|
ed4b1e8f02 | ||
|
e78146eb67 | ||
|
9f6b6cb657 | ||
|
fb85e1a068 | ||
|
c78299e7bc | ||
|
9a5e29e015 | ||
|
7cf3a989bf | ||
|
d486563099 | ||
|
be699cbd85 | ||
|
4e61f2b2e5 | ||
|
1bf703b012 | ||
|
a48fdaf8b7 | ||
|
a0f33ac942 | ||
|
bea659f948 | ||
|
7fb263fdf0 | ||
|
3be1c24b1b | ||
|
0b33cb55b8 | ||
|
00e71ed0fc | ||
|
a40810e33e | ||
|
15c9d04703 | ||
|
e13dcdc85a | ||
|
2ab5b2817e | ||
|
080c14d111 | ||
|
d1e32f2b11 | ||
|
0840e0dedb | ||
|
888d6bf091 | ||
|
e2f308a57a | ||
|
76fe6ad799 | ||
|
90df256e85 | ||
|
f92466f6be | ||
|
6a5dcd00b2 | ||
|
6c65de8ddf | ||
|
f696097301 | ||
|
7d3db03d4a | ||
|
78c87e501e | ||
|
2974b466aa | ||
|
ca1a852b44 | ||
|
ac2343b142 | ||
|
be9efb9a25 | ||
|
30e8e36635 | ||
|
3290a5bf4d | ||
|
c07ba14fc6 | ||
|
c6e9e19858 | ||
|
375bdb7948 | ||
|
5e7899675e | ||
|
328ba0202f | ||
|
59cbbde835 | ||
|
caa4fa0180 | ||
|
fc6a4b78eb | ||
|
2729772adb | ||
|
70e0f7027e | ||
|
4dcdffda0a | ||
|
2535cd9c2c | ||
|
d3eef6c820 | ||
|
35fb1d60af | ||
|
089ec22666 | ||
|
66077f317b | ||
|
5904bfa92b | ||
|
2c1d7a803f | ||
|
6d5b8b6a7b | ||
|
b88f8c205e | ||
|
ce4afbe4c2 | ||
|
fcaecef961 | ||
|
81d2e315e3 | ||
|
fddf982832 | ||
|
3602ef9be9 | ||
|
37c65ca6b4 | ||
|
77f284661b | ||
|
0d60c4d464 | ||
|
f06eeb01b9 | ||
|
fbcc1cfb50 | ||
|
26cef99cdf | ||
|
5cac7feabe | ||
|
d844e0f396 | ||
|
ed11bf6368 | ||
|
eaa6b7cf57 | ||
|
f8ea631b03 | ||
|
7b0744ec75 | ||
|
9f4a2b3e26 | ||
|
9741c93ce9 | ||
|
7e9940be4a | ||
|
c0aa338d7c | ||
|
b20978e08e | ||
|
5d33f71d4d | ||
|
1058e33f12 | ||
|
b7fedff511 | ||
|
f252597002 | ||
|
cbbfc4ea6e | ||
|
6b04197eaa | ||
|
3027ba2cf1 | ||
|
19e9f31afd | ||
|
5a1d2580cb | ||
|
cb5cace8da | ||
|
e734c125ad | ||
|
d351f64fb1 | ||
|
e61f587bfc | ||
|
4e8f0a41c7 | ||
|
4fbc97c92a | ||
|
1e35ac380e | ||
|
85b0647a3e | ||
|
7b899d1853 | ||
|
699b21f066 | ||
|
8d423adb4d | ||
|
506dcfa6c0 | ||
|
91e0d5164b | ||
|
67d8ffcde5 | ||
|
44c9aded65 | ||
|
f1ccfc0361 | ||
|
cdef9c435c | ||
|
ed8ac5b84c | ||
|
8d55dfb66b | ||
|
80c1a6d2af | ||
|
895740755d | ||
|
90601b7b6b | ||
|
cc2ffbb4da | ||
|
0c24064939 | ||
|
7859084be8 | ||
|
101c217ef8 | ||
|
7887a5a613 | ||
|
800ff09a37 | ||
|
20f8fa2ebd | ||
|
314159c273 | ||
|
bf16885278 | ||
|
3ae6f7ab7c | ||
|
e829dd8b5c | ||
|
62c71eaafa | ||
|
24ec25ac62 | ||
|
19b5c76154 | ||
|
2852b2ea6d | ||
|
fc2e9bbfae | ||
|
d51dd7ae57 | ||
|
9ba70c1808 | ||
|
9ec942ea1d | ||
|
b9e644269b | ||
|
5b56445741 | ||
|
1ed5148624 | ||
|
fb7c63c507 | ||
|
2c5edbc981 | ||
|
1addffe622 | ||
|
733c14bbc8 | ||
|
ca2e6adedb | ||
|
5cc20ace1f | ||
|
400dcf2899 | ||
|
dd3326f038 | ||
|
505de39d2a | ||
|
24069c8277 | ||
|
bb78e9aaeb | ||
|
5c39344cff | ||
|
7cd24a74af | ||
|
a675631965 | ||
|
33c4e17c0d | ||
|
56ee173d03 | ||
|
af4709255a | ||
|
527e1aefc9 | ||
|
a66dd0dc79 | ||
|
7b5b5776f0 | ||
|
91ab0b12b0 | ||
|
c2ea7045f5 | ||
|
af3cae5f3d | ||
|
54c3b19052 | ||
|
04de0010b5 | ||
|
66161b74a6 | ||
|
889ef66784 | ||
|
fae83f8d22 | ||
|
6f4d538832 | ||
|
a0c345185b | ||
|
c0f771684d | ||
|
90ec01bfbf | ||
|
7f83bfefd6 | ||
|
310aadbdc0 | ||
|
6b2de3a24d | ||
|
002a87d3ba | ||
|
6f63346905 | ||
|
735766669e | ||
|
0dda8b405e | ||
|
47626b2af4 | ||
|
fed2c94431 | ||
|
da7428d6bd | ||
|
dced7a14c8 | ||
|
f1d735f93d | ||
|
73b4239744 | ||
|
67747253e8 | ||
|
0bdc400a30 | ||
|
b12a016f3a | ||
|
fd851ba6cb | ||
|
b2a32666e9 | ||
|
a7744e837c | ||
|
e5aa10a730 | ||
|
e626cc0390 | ||
|
4d93415485 | ||
|
ed746c389c | ||
|
a7a5947a1b | ||
|
c8daef5113 | ||
|
ee195c966e | ||
|
6ecda07577 | ||
|
26fea2d6a0 | ||
|
87e79bceaa | ||
|
bc3bdbbf21 | ||
|
884fa2748e | ||
|
4a37227b95 | ||
|
a9f1944636 | ||
|
6d23eb3bf6 | ||
|
3d13c69e41 | ||
|
fb8624f630 | ||
|
795c5f6a04 | ||
|
98a8c991e2 | ||
|
e867c57af8 | ||
|
133e2cc222 | ||
|
e0a326f15b | ||
|
b3f550ee68 | ||
|
29923cf930 | ||
|
9a63f97a19 | ||
|
2d73ba6735 | ||
|
8037fb2d66 | ||
|
edbf803a98 | ||
|
b51c791629 | ||
|
6cbd583746 | ||
|
b55d75ef99 | ||
|
5e692df08c | ||
|
e19ad40023 | ||
|
934697af28 | ||
|
ca088bcc33 | ||
|
1559d28ed8 | ||
|
376a1704df | ||
|
68dbf27b2f | ||
|
6bde5960aa | ||
|
86e1079195 | ||
|
409a707baa | ||
|
86f50e0355 | ||
|
02d4cb4a14 | ||
|
2487fff44a | ||
|
ffeae1a95a | ||
|
e57e5b090e | ||
|
c8e35991b9 | ||
|
0ebfd989d1 | ||
|
306e38c9ee | ||
|
4e5c859aab | ||
|
ac815f7598 | ||
|
a3f7de89f1 | ||
|
b70a43aeb5 | ||
|
8726c4ede0 | ||
|
0c5993c0ad | ||
|
da25b2a763 | ||
|
aad4b53524 | ||
|
3d5c440aa2 | ||
|
4f8da7819e | ||
|
a96c4938b1 | ||
|
c62a1e829f | ||
|
106af6a801 | ||
|
2ae5297f7d | ||
|
a7db3a5610 | ||
|
d0d3e20713 | ||
|
c852bd8a85 | ||
|
7499b41d6b | ||
|
ee12805d05 | ||
|
dacb48b4f4 | ||
|
90f82b6d4c | ||
|
227f87e1ce | ||
|
83c75acd0a | ||
|
ff162cb44d | ||
|
e5a67d6ce1 | ||
|
5197212d3f | ||
|
8c1b5d54f7 | ||
|
8a0005d900 | ||
|
a754eb6faf | ||
|
c9082a63f0 | ||
|
314b3cafbb | ||
|
91700ce995 | ||
|
145a2f1179 | ||
|
6e61bc2000 | ||
|
9da6f3a702 | ||
|
760934d5a5 | ||
|
a3bc754e80 | ||
|
ae652b1294 | ||
|
ab01867235 | ||
|
6b0b36a555 | ||
|
ee63a27fe9 | ||
|
5f0c49d653 | ||
|
8a31b7f2c1 | ||
|
72390e595d | ||
|
e0e8e11443 | ||
|
f7f882e834 | ||
|
60ad582012 | ||
|
f6aebb5917 | ||
|
3a4ad96a00 | ||
|
e0ed651e53 | ||
|
c703644dbe | ||
|
da4cc7dc6b | ||
|
842f13b2fc | ||
|
d2251ccd14 | ||
|
6a3ebfd4c8 | ||
|
ff803eaf45 | ||
|
c93480692c | ||
|
542a3f36a4 | ||
|
24a8c528d0 | ||
|
780c69d804 | ||
|
22c948166a | ||
|
64436bc9ab | ||
|
6106e1878b | ||
|
c788c5a40e | ||
|
9f60ed534a | ||
|
03f193b34c | ||
|
db42f61cbb | ||
|
49591aedb4 | ||
|
21aa2b2a7f | ||
|
276ecef300 | ||
|
20b3563f21 | ||
|
15630356e1 | ||
|
f1fd528483 | ||
|
75d7270d5a | ||
|
3206639778 | ||
|
2e63c3b4f7 | ||
|
d63405d170 | ||
|
91e0db185c | ||
|
f97543eecd | ||
|
3606fb67bb | ||
|
426b59d13e | ||
|
d94ec8e144 | ||
|
4186349bba | ||
|
7816325e63 | ||
|
15da210de7 | ||
|
a4f7632981 | ||
|
a1b16a9118 | ||
|
c100b504dc | ||
|
161ec9f828 | ||
|
210466e7d9 | ||
|
0702159596 | ||
|
8d7e533196 | ||
|
47feda6ae4 | ||
|
44c945f45d | ||
|
35def4445d | ||
|
3e60303b90 | ||
|
b6e9dfa97d | ||
|
5771bb858f | ||
|
dcaf1cc189 | ||
|
5e1fc79c33 | ||
|
8997d7f91f | ||
|
54a08f560e | ||
|
ccb37c00f6 | ||
|
6281cbfd5f | ||
|
6502f3f487 | ||
|
00535c9a42 | ||
|
e6cf11cb3d | ||
|
c5eb0fd351 | ||
|
a780be70dd | ||
|
1d98e5307a | ||
|
36abb8ae8f | ||
|
0c4642aa20 | ||
|
1b029b765f | ||
|
9563496efb | ||
|
c628e27d2e | ||
|
1ad0e94b64 | ||
|
43face8ead | ||
|
2e4d346bc8 | ||
|
bf76c04664 | ||
|
ae678871ec | ||
|
9620eb06ac | ||
|
9096229637 | ||
|
eec3d782d3 | ||
|
43a7478791 | ||
|
175057e781 | ||
|
de2bfc7f79 | ||
|
5ed9db4f08 | ||
|
fea19c9e20 | ||
|
207764e0be | ||
|
49aaff2bbe | ||
|
25e84f2f5d | ||
|
e63f2fafb9 | ||
|
0b5a063b62 | ||
|
e0c2f58e8d | ||
|
0084a2e722 | ||
|
7ff1500122 | ||
|
7805622f07 | ||
|
a8e68dce3a | ||
|
b9eee539bd | ||
|
e450f36e81 | ||
|
ae221e0447 | ||
|
2a1be0bd85 | ||
|
af324eeca5 | ||
|
eb71fd1a5a | ||
|
e4a4788d5e | ||
|
47b6fe7dbe | ||
|
9ffacfe90e | ||
|
3f7d8c60b9 | ||
|
2e5d8fe561 | ||
|
7188c75351 | ||
|
316c35b258 | ||
|
9da42e27f1 | ||
|
0aee7ff552 | ||
|
228713ebb5 | ||
|
266129c61b | ||
|
3522e43617 | ||
|
45a5795c9c | ||
|
94d79692df | ||
|
58bb4b5358 | ||
|
51fef48016 | ||
|
7ab013cee9 | ||
|
f2b334b57a | ||
|
7eb7ee44f2 | ||
|
34cc64f8ce | ||
|
ca5951861a | ||
|
605f31d1ad | ||
|
30167d81e6 | ||
|
a247b444e5 | ||
|
1424a2980f | ||
|
01e1115806 | ||
|
c9c8af42c2 | ||
|
489fb2ac22 | ||
|
a708025a16 | ||
|
31429b7faf | ||
|
9c5235c273 | ||
|
8e717e00b1 | ||
|
9a4693136d | ||
|
cf0110ab95 | ||
|
abc5d5e863 | ||
|
0c74338c9c | ||
|
1dd22132ff | ||
|
76c400d2c3 | ||
|
d9472ec19c | ||
|
9d821fb892 | ||
|
8c6553ce73 | ||
|
1d4ed23394 | ||
|
c1fb44a145 | ||
|
333aacf4ab | ||
|
5b5827f885 | ||
|
6fbbf38a84 | ||
|
c1004fb4f8 | ||
|
de8ddb2a2b | ||
|
6feff1e42b | ||
|
ed3992d993 | ||
|
60ed2c292f | ||
|
1d77153a2b | ||
|
2e8e81a926 | ||
|
c7aa9834a9 | ||
|
ac8925b2bd | ||
|
25fb1b08bc | ||
|
adcc88823f | ||
|
280d8d7f10 | ||
|
04fa046815 | ||
|
11a06d6b33 | ||
|
38c9fe9cb5 | ||
|
2c586266ff | ||
|
4120630309 | ||
|
1ea4309a2a | ||
|
08dc65350f | ||
|
0c4a037dde | ||
|
dc8b83a110 | ||
|
484daa4c69 | ||
|
65c9a2e185 | ||
|
46adc1af98 | ||
|
7da2e1b262 | ||
|
9a698929f4 | ||
|
862c2fec87 | ||
|
9d534dc7bb | ||
|
a25043e5d4 | ||
|
cd77d3d410 | ||
|
0d72c03fa5 | ||
|
173a2416fa | ||
|
71fff4b14f | ||
|
007acf8c6d | ||
|
6843643aad | ||
|
64f6a1f072 | ||
|
b09811c1d4 | ||
|
a593051512 | ||
|
81e36d4f9b | ||
|
597ac9b504 | ||
|
525f04d446 | ||
|
d8a5244a88 | ||
|
d69b9ea394 | ||
|
7a42737287 | ||
|
1cc21834e2 | ||
|
956cb97331 | ||
|
72293d8d87 | ||
|
de583475f9 | ||
|
ed7d023581 | ||
|
457b093dce | ||
|
dcde9fbe8e | ||
|
f4346e3f0a | ||
|
9da2a00403 | ||
|
1c7d1861d6 | ||
|
9f7979314f | ||
|
dca1d479e8 | ||
|
c586f58286 | ||
|
9553b1ef00 | ||
|
a4db5d616a | ||
|
c1dc44d4fd | ||
|
6bbbccb9ac | ||
|
280bdbefdc | ||
|
abebffb2cd | ||
|
adce64bc21 | ||
|
4ea3d94d07 | ||
|
aaa6412469 | ||
|
c487929bcd | ||
|
0b63e78d2b | ||
|
e9e66e1005 | ||
|
7bb690e58e | ||
|
26620f3137 | ||
|
c8cd36bde8 | ||
|
7bf06aef5e | ||
|
0e65e67077 | ||
|
936a68433d | ||
|
8236696492 | ||
|
1757e939e1 | ||
|
5afe9c5337 | ||
|
5b5a1718e9 | ||
|
51c2352d67 | ||
|
3ad87e1362 | ||
|
cb5e3c6bf7 | ||
|
63e94a9fb4 | ||
|
badbb55ef2 | ||
|
27dbc671e0 | ||
|
fd92a96e1a | ||
|
8828b60288 | ||
|
e03c48e0ae | ||
|
425e451237 | ||
|
d8d859ab02 | ||
|
b178c7c67a | ||
|
0f701df91f | ||
|
b292de8ae6 | ||
|
87016f6ecf | ||
|
145962597c | ||
|
9ad24614cb | ||
|
ad65eb7ea2 | ||
|
ce7aeb02bc | ||
|
5b1c6c0d64 | ||
|
69e952738b | ||
|
621d822a6c | ||
|
3abb641c7c | ||
|
995eaa6e7c | ||
|
cf07512d24 | ||
|
5a63b5419f | ||
|
34c0968f5e | ||
|
bf75d9b4e0 | ||
|
3666531f8b | ||
|
7587a60387 | ||
|
b18991b9fb | ||
|
2ecddba375 | ||
|
5f941004f5 | ||
|
496cd5e621 | ||
|
470aeadbd9 | ||
|
06e2811012 | ||
|
be28d13d0d | ||
|
cf1626ea31 | ||
|
b1e56faab2 | ||
|
06a9884e99 | ||
|
7ac6c9a170 | ||
|
53f47d42fc | ||
|
2d60f98e93 | ||
|
bb9d44811c | ||
|
376fcba9ec | ||
|
e844abcad1 | ||
|
c3425979dd | ||
|
68eb6b6bb4 | ||
|
f0fe1fac66 | ||
|
477e6516ed | ||
|
22add1cbee | ||
|
55dde93811 | ||
|
656282459c | ||
|
35679216f7 | ||
|
a80e18169a | ||
|
5837a6982b | ||
|
275be59c25 | ||
|
a0c304d3ca | ||
|
f968d9bf3b | ||
|
0309c85c6a | ||
|
59b2d2f5a1 | ||
|
a48601b028 | ||
|
406faa1bbb | ||
|
8f85d164ff | ||
|
6893e91190 | ||
|
945ff44de3 | ||
|
2a20e70aab | ||
|
69f1d0c213 | ||
|
2c15995289 | ||
|
af71115767 | ||
|
02a018462b | ||
|
8dc82a30af | ||
|
c8f819cb94 | ||
|
d27a12a6cf | ||
|
86e5ef7382 | ||
|
b5d578e483 | ||
|
9eb97746c1 | ||
|
fd25ef539f | ||
|
4f5d5afc4f | ||
|
0686fd8e68 | ||
|
942999b7b4 | ||
|
3c2fa65e05 | ||
|
90df362c35 | ||
|
bf2a83755e | ||
|
6f8784a1c9 | ||
|
8b75504822 | ||
|
ec945cd227 | ||
|
f3b2df62da | ||
|
2c44b65e9f | ||
|
1ffb6d6907 | ||
|
69fe3986cf | ||
|
abde122a35 | ||
|
7261129609 | ||
|
3f582e9c2e | ||
|
832604e763 | ||
|
ccce539843 | ||
|
90c787f8e2 | ||
|
cf0cc50e32 | ||
|
bb3b9692e3 | ||
|
0e0ef402fc | ||
|
b0bb602eb5 | ||
|
d690b35ed9 | ||
|
f207c2abc3 | ||
|
796f4d0d96 | ||
|
8985cc2656 | ||
|
7a06d3959e | ||
|
292ef0f2da | ||
|
0476895c3d | ||
|
ff6b733cd0 | ||
|
1db7f8d76e | ||
|
6dabf4550b | ||
|
fd8ba66509 | ||
|
8966535efe | ||
|
76f27449df | ||
|
38ade4e985 | ||
|
8e773d92c1 | ||
|
5cccf52103 | ||
|
3040da99c4 | ||
|
b9f66248ac | ||
|
285bfafdc1 | ||
|
aadd908464 | ||
|
73d44ba3fe | ||
|
8c6b909365 | ||
|
641856ebb4 | ||
|
be0993c5d8 | ||
|
e5a8cdb056 | ||
|
e284b99076 | ||
|
380c892e24 | ||
|
2e74acaabe | ||
|
95cc0ae7fd | ||
|
3491782912 | ||
|
ffaa9d2ed3 | ||
|
d812c249ba | ||
|
238455ad6c | ||
|
ede0dfeef4 | ||
|
67146f9cc7 | ||
|
e5537dc4db | ||
|
5d23b1ed9d | ||
|
166bec0f8d | ||
|
d51b957ada | ||
|
4ffb93129d | ||
|
9439de1c15 | ||
|
80befec589 | ||
|
a3c5cfd2f4 | ||
|
7d935749aa | ||
|
4310059e6a | ||
|
7b2b258b1d | ||
|
c7e9e19a8a | ||
|
382469f5fd | ||
|
fe9989197f | ||
|
a01d6b085a | ||
|
c8cc428e3f | ||
|
365323f574 | ||
|
3587d1c397 | ||
|
8a5f98dac6 | ||
|
99eef05084 | ||
|
beb3adcc72 | ||
|
e70463d7b8 | ||
|
404063a080 | ||
|
5d83e149b3 | ||
|
65ff7a43db | ||
|
9af3e2ca90 | ||
|
69ea12b956 | ||
|
541ac777bc | ||
|
5b76d7458f | ||
|
6a188a67dd | ||
|
805f976d9e | ||
|
00cda598c8 | ||
|
332780b1a6 | ||
|
ffcda46c31 | ||
|
f5e5dd551d | ||
|
c7a7d00da5 | ||
|
c88c92086e | ||
|
a7ecb9e10f | ||
|
d28b6522d5 | ||
|
a738fb725b | ||
|
592491ab4e | ||
|
1e8c5eb522 | ||
|
e3fcc9b3ba | ||
|
fba7a6a75c | ||
|
3b81ba3596 | ||
|
f7b2916618 | ||
|
db154df4a4 | ||
|
c66f92d488 | ||
|
a0593f4031 | ||
|
ceb6084eda | ||
|
7c9839b114 | ||
|
6dc8435fa1 | ||
|
7e803dfc46 | ||
|
f03df0a8c0 | ||
|
88ea61e28f | ||
|
8cd8016c3d | ||
|
6fcec33ab6 | ||
|
bddff2cb88 | ||
|
c2801c447d | ||
|
f9ecaa8ad6 | ||
|
9a72733c4f | ||
|
4aa8e3f2f2 | ||
|
0da37d0c90 | ||
|
5a3afed7a8 | ||
|
00b3be044e | ||
|
172011234b | ||
|
71e0c3f80c | ||
|
fa3c79828a | ||
|
dc2470d046 | ||
|
baed2b7ead | ||
|
1f48ce1f39 | ||
|
c134f1b78e | ||
|
d21f9cf029 | ||
|
e7896235a2 | ||
|
1c9d16e199 | ||
|
6bdfa11b66 | ||
|
d1ccd11fcb | ||
|
fac241ea69 | ||
|
a57a2bb326 | ||
|
d248f249e3 | ||
|
4f50668fe3 | ||
|
d9c8711eb0 | ||
|
4999487476 | ||
|
62687d103f | ||
|
06e93b84da | ||
|
ceafe96e81 | ||
|
b8ac75313f | ||
|
f0d9a33d9a | ||
|
0bd142ab6b | ||
|
c3f0539526 | ||
|
e9caf21bd3 | ||
|
10df4338b1 | ||
|
bbe74ef020 | ||
|
9c811ecc02 | ||
|
8867a7b9ee | ||
|
c3a9022bec | ||
|
cb346fdec4 | ||
|
a53bb1c4c6 | ||
|
c9928043b2 | ||
|
16bf7af297 | ||
|
a5013dd08d | ||
|
32c2fae2f7 | ||
|
124d10d6f8 | ||
|
6c1013b024 | ||
|
c8b9ab1911 | ||
|
861f47d734 | ||
|
e92d37e3a5 | ||
|
e83f43065e | ||
|
6ffeb7c431 | ||
|
855d06dc76 | ||
|
f65f0e3873 | ||
|
f487af5b0c | ||
|
7e7e4150e1 | ||
|
5cd5697aea | ||
|
d13d150fe1 | ||
|
415b65d208 | ||
|
c9dffe47f6 | ||
|
05ccfef93c | ||
|
f58f85ef60 | ||
|
fc8b6d0fcb | ||
|
382d8d22d0 | ||
|
fa5c09d23b | ||
|
53b84bad02 | ||
|
89f568dd1f | ||
|
8aab644db9 | ||
|
398cddaf29 | ||
|
c73c58723c | ||
|
b78573021d | ||
|
b7005884fd | ||
|
79cc225869 | ||
|
4a1af0786c | ||
|
6261934dda | ||
|
d3490facc8 | ||
|
62dd2ee49e | ||
|
9c67aabebb | ||
|
64af4914ee | ||
|
2879b31aba | ||
|
793df99bef | ||
|
c30eba92cc | ||
|
62dd5a169f | ||
|
4baf0849a6 | ||
|
81a11dc977 | ||
|
7706a02e1b | ||
|
25314b1d17 | ||
|
54d8ee2a40 | ||
|
dc8abd0032 | ||
|
2b0989adfe | ||
|
9c86baf0ea | ||
|
27a09fd408 | ||
|
08f6dbae05 | ||
|
b81a83eec5 | ||
|
e7a4bc5ef1 | ||
|
e495afcff5 | ||
|
5dd602614c | ||
|
b5eef3ccad | ||
|
c60c8e998f | ||
|
510965589b | ||
|
82bb4f22f0 | ||
|
8a216e3245 | ||
|
e22cc457ae | ||
|
a20bbe1246 | ||
|
510dc89e92 | ||
|
fa9401d186 | ||
|
32818a8608 | ||
|
eec7aec069 | ||
|
2ab42ca547 | ||
|
9075cc1a30 | ||
|
b615a4b04d | ||
|
31dec5a666 | ||
|
782f96b580 | ||
|
659761f006 | ||
|
3cf7721cd7 | ||
|
d783df510a | ||
|
1e87240db8 | ||
|
f39983f78a | ||
|
204aa31885 | ||
|
cb70209c60 | ||
|
f884175748 | ||
|
1fae09433d | ||
|
ce7aa580b6 | ||
|
ce70a623c2 | ||
|
9cb1409918 | ||
|
fc5af35a04 | ||
|
4e69b34efe | ||
|
69b7e73063 | ||
|
95a27c8ec4 | ||
|
06aeeaa6e6 | ||
|
e83e9ae313 | ||
|
f69d9e64aa | ||
|
b07200342a | ||
|
9b102c49d0 | ||
|
004293362e | ||
|
fc0a9b4102 | ||
|
5c389c875d | ||
|
430c155f7e | ||
|
f5ba7894ae | ||
|
3636023cb8 | ||
|
d4b9356c5c | ||
|
bff0030aed | ||
|
f5089789fb | ||
|
f57db857da | ||
|
cf31ad6ccf | ||
|
0e56d1c32a | ||
|
1c27723083 | ||
|
3b685190a8 | ||
|
ea7f4845e8 | ||
|
4a3b0e5124 | ||
|
5648714641 | ||
|
18ad9fbb4e | ||
|
f094cd6806 | ||
|
997b3c6a2a | ||
|
e977f38bdd | ||
|
15c66684c1 | ||
|
a95886c491 | ||
|
09730b474b | ||
|
bfddb40d73 | ||
|
07742843df | ||
|
abd0e71814 | ||
|
d82f4a8717 | ||
|
6501ffcdac | ||
|
997b4f0c3f | ||
|
b5d4a5000b | ||
|
cadcc6c8ef | ||
|
179855272a | ||
|
c56a8df9b2 | ||
|
7829a8d631 | ||
|
d4838bd646 | ||
|
26594dd0ee | ||
|
0709c0caf8 | ||
|
76ceac4d54 | ||
|
8b83992e95 | ||
|
778adacb80 | ||
|
1548ce9c79 | ||
|
9839be48f3 | ||
|
6f5e72119e | ||
|
c9f5550d18 | ||
|
f44b148946 | ||
|
593cd7d8ca | ||
|
d72b838560 | ||
|
b91c162159 | ||
|
1a81bbecbb | ||
|
7c9fd9d0fb | ||
|
4acab9043c | ||
|
ba817e2ae4 | ||
|
375c9d60cf | ||
|
1c4a32fef4 | ||
|
df8dd54791 | ||
|
e78463ea0a | ||
|
d47574351e | ||
|
8bee08fd96 | ||
|
1ca0e75ef1 | ||
|
982e887c9f | ||
|
d622967192 | ||
|
6c29d90503 | ||
|
e562daa1f3 | ||
|
8e2bb1d393 | ||
|
11b3758416 | ||
|
12e85ec671 | ||
|
627a587952 | ||
|
cbff7217c1 | ||
|
882e45de55 | ||
|
f48593f265 | ||
|
62d8873a92 | ||
|
fbafa75fe2 | ||
|
e3d5ebaa9e | ||
|
2196ac6975 | ||
|
74e576c907 | ||
|
6ac4af4974 | ||
|
50e5acdf52 | ||
|
eee140f74f | ||
|
1856e79a50 | ||
|
a17262d9ff | ||
|
0e6644a890 | ||
|
2044a7d7a5 | ||
|
e3c37a0239 | ||
|
537b704951 | ||
|
656ca6b5e4 | ||
|
2e01a05e71 | ||
|
299671480f | ||
|
5a2e37a6e2 | ||
|
ac18006abb | ||
|
35622ac684 | ||
|
891c05210e | ||
|
43bcc95713 | ||
|
b903102407 | ||
|
e83bfb9e03 | ||
|
0d0978011d | ||
|
626406a43c | ||
|
1f0c2d5c78 | ||
|
7a3522dc01 | ||
|
32150b1aaa | ||
|
5743cf4455 | ||
|
8bcd8d87d2 | ||
|
85c5caeaca | ||
|
634437d22d | ||
|
8bc6c0abcb | ||
|
debbaa81ba | ||
|
e532a24cf8 | ||
|
cd3741d236 | ||
|
7f64a9812c | ||
|
944c3327ee | ||
|
6ee0a95aa2 | ||
|
7106a997cf | ||
|
95792be9bc | ||
|
ca52c01031 | ||
|
467a48bac5 | ||
|
cd7172ee57 | ||
|
23a006c311 | ||
|
8c661c23be | ||
|
6c3106db5d | ||
|
6dd1a21606 | ||
|
9a9dd3b217 | ||
|
4f67ac674b | ||
|
0a5e836ab6 | ||
|
a7c9995655 | ||
|
e3752e733a | ||
|
76942ea458 | ||
|
5bbfd32598 | ||
|
73e137137d | ||
|
610e32aa52 | ||
|
b745f1f902 | ||
|
a18d6b3fe4 | ||
|
11c90fa032 | ||
|
70fc2381d3 | ||
|
893b7f7f9d | ||
|
b77d820661 | ||
|
19f7b5d78d | ||
|
ef2a013db3 | ||
|
af292d8f0e | ||
|
47143a90e1 | ||
|
e00eaf10e8 | ||
|
89d498f87d | ||
|
7089912fb0 | ||
|
dbfa8bb66b | ||
|
28f735741a | ||
|
b810e303ec | ||
|
7ea3900854 | ||
|
0c070b9719 | ||
|
0bbd75c469 | ||
|
b24fa98e3c | ||
|
7b809c6547 | ||
|
5d8d0dcad6 | ||
|
6826e3cb49 | ||
|
b09677a46c | ||
|
a8bbdb54d0 | ||
|
6c00054407 | ||
|
15e8029715 | ||
|
641dc7577c | ||
|
8f781c019f | ||
|
48b2c7ff8a | ||
|
f449a242e4 | ||
|
17d00d6ca8 | ||
|
54f7bc5d79 | ||
|
dde98e9b06 | ||
|
8dced20b0f | ||
|
b1ebdc8343 | ||
|
07893546d0 | ||
|
9e72248e1b | ||
|
8cc4877627 | ||
|
dedbafb99a | ||
|
f1d6890da4 | ||
|
ca3871e223 | ||
|
a0f04a9bc1 | ||
|
2d068997c5 | ||
|
f40c351662 | ||
|
0b2d37fe68 | ||
|
5e50a4cc4a | ||
|
7e223dc228 | ||
|
609ebf024b | ||
|
ec3d2346ef | ||
|
87406e2cdb | ||
|
d1e760f447 | ||
|
795b73df87 | ||
|
0001a9b300 | ||
|
39c4cf24b8 | ||
|
a257a2f7b5 | ||
|
1ce1ae91be | ||
|
a70dfdc8b8 | ||
|
15b16f3dc3 | ||
|
cfc818872f | ||
|
2a0bba62be | ||
|
9567835434 | ||
|
79f1ca5491 | ||
|
d3a4a3a99e | ||
|
bfd6b605e9 | ||
|
b8a0901fbd | ||
|
ab40344567 | ||
|
1d87befdab | ||
|
30690785fd | ||
|
f445cfb5c1 | ||
|
2016cf2a5f | ||
|
109ce26780 | ||
|
5c3fc44d00 | ||
|
761baf7206 | ||
|
81ee2c2d2b | ||
|
ea633efc9c | ||
|
78f22c7020 | ||
|
7170a68571 | ||
|
03589f858b | ||
|
3de30e166f | ||
|
b972522d74 | ||
|
a91e3f762e | ||
|
4ebacd0e1f | ||
|
dd0effc10c | ||
|
8e8eed7b06 | ||
|
06e9f8ba64 | ||
|
c1d3a647a2 | ||
|
726237cfaa | ||
|
dc92d824d3 | ||
|
4fd3657f3d | ||
|
da29cdfe10 | ||
|
db756bfe10 | ||
|
726103d138 | ||
|
ea11f3b1fd | ||
|
03a5483791 | ||
|
3fc5bec149 | ||
|
21d62c4557 | ||
|
2727487902 | ||
|
fa44f8b6a8 | ||
|
a312f87529 | ||
|
2ed19f5eaa | ||
|
a851b5e655 | ||
|
0455d3e8a8 | ||
|
19c004e987 | ||
|
7364f0f7cf | ||
|
3643d57d0e | ||
|
8217541d5e | ||
|
ab95e635ce | ||
|
0bc2141245 | ||
|
6cb867cbeb | ||
|
56ac648922 | ||
|
6eb486e858 | ||
|
a4e34e600e | ||
|
e188121733 | ||
|
f43acc5c60 | ||
|
1bd60eea81 | ||
|
b7f05a1c80 | ||
|
3d361d54bb | ||
|
3357945a5a | ||
|
5427fa0132 | ||
|
57617accb2 | ||
|
451b4c8433 | ||
|
9e3c9db5c0 | ||
|
36475bb632 | ||
|
312c6c8bb2 | ||
|
4f578c2645 | ||
|
7afb1347b4 | ||
|
0091c1cf26 | ||
|
c79860b493 | ||
|
530ef51ba7 | ||
|
5b8c0c4e2d | ||
|
a1014ba39c | ||
|
624fa4fda4 | ||
|
4e123a2dc3 | ||
|
224d8194cc | ||
|
a96bd91770 | ||
|
08962fd1dc | ||
|
a05a24873a | ||
|
deb4ecd04f | ||
|
2ef59d692a | ||
|
8a0c6cae15 | ||
|
afb22deada | ||
|
ccc380f575 | ||
|
f77519445c | ||
|
9f5347964b | ||
|
3ad3b2c966 | ||
|
357e278f48 | ||
|
0a63dd1db4 | ||
|
bda9528392 | ||
|
c5c3720f80 | ||
|
b9b4d846d3 | ||
|
3935ebf81b | ||
|
05abb3f9f6 | ||
|
2ad18342a8 | ||
|
7832afd819 | ||
|
b39f7c1807 | ||
|
4f9c0172ef | ||
|
caf16231aa | ||
|
8f4d89e2f7 | ||
|
ea035db94f | ||
|
b5f0a26084 | ||
|
1bf848cde1 | ||
|
be4b683a41 | ||
|
9944b666b0 | ||
|
2935b96070 | ||
|
a7b414a870 | ||
|
2ea56a1b67 | ||
|
52739140ef | ||
|
253757cb31 | ||
|
631f10c916 | ||
|
1d2c7a479d | ||
|
8d0de67530 | ||
|
9ca89b3dfa | ||
|
86711b0e5c | ||
|
17d6b256e8 | ||
|
ef2ba0b8e0 | ||
|
4860caef63 | ||
|
8249f852f9 | ||
|
221d937eec | ||
|
c6395b9f35 | ||
|
de273586b4 | ||
|
03c30519b1 | ||
|
6c33a89f82 | ||
|
237abe431b | ||
|
9f3276d7e3 | ||
|
d0ce3c1543 | ||
|
129a846cc1 | ||
|
9eeba88bd0 | ||
|
0dc33e1892 | ||
|
3ed776e4cc | ||
|
40fc712581 | ||
|
630803c3d1 | ||
|
0797217588 | ||
|
23df85ff90 | ||
|
c276055302 | ||
|
a811d1a05a | ||
|
7d802fb8c5 | ||
|
4eaec83594 | ||
|
c72aeed8bb | ||
|
4adb11dada | ||
|
69149b16c1 | ||
|
383e7db360 | ||
|
644aacf595 | ||
|
b4311c7b76 | ||
|
de3b077a6f | ||
|
a709447536 | ||
|
cb596c15f1 | ||
|
d50db1d671 | ||
|
6f4b171683 | ||
|
eae516a9a7 | ||
|
c3c1802b4d | ||
|
80e15ad54e | ||
|
f4ed9a01b7 | ||
|
804ad34a08 | ||
|
70690fef36 | ||
|
a80e26896a | ||
|
c440520ea6 | ||
|
a28ad7195c | ||
|
3b29add396 | ||
|
5a2c4b8dfd | ||
|
46a4c2c0d0 | ||
|
dd4225dcb1 | ||
|
a2debdb234 | ||
|
c588130491 | ||
|
690ce2829f | ||
|
03bf043959 | ||
|
d4b4bd8603 | ||
|
14c8f07b8d | ||
|
fe25ae275b | ||
|
06404667a1 | ||
|
e1134d832a | ||
|
b31e97dd2d | ||
|
9e061c8667 | ||
|
ca86c421eb | ||
|
a97bbf8608 | ||
|
c0f5ff8805 | ||
|
9a775171b5 | ||
|
e6ea5d55f8 | ||
|
8d985d84fb | ||
|
54a2a95fb5 | ||
|
fc76c9badf | ||
|
cc40c437d1 | ||
|
5938d54784 | ||
|
20681b8d89 | ||
|
5760a53831 | ||
|
c95897d17b | ||
|
96d316b1dd | ||
|
9bb935b11f | ||
|
9bba7a85ab | ||
|
a643609ed2 | ||
|
6bd857ad8b | ||
|
0624d6a808 | ||
|
783c88cb6a | ||
|
f0834c7a47 | ||
|
90f5cd79c9 | ||
|
c9fa28d844 | ||
|
bc47c0d5d6 | ||
|
38267fa2a1 | ||
|
e74be0c345 | ||
|
6c8a1ff3d3 | ||
|
263fdceb94 | ||
|
51db5495c0 | ||
|
9244d1e8a8 | ||
|
0c7da68d98 | ||
|
88d81c7a63 | ||
|
00339b1abe | ||
|
1c5de83370 | ||
|
a475bc50e9 | ||
|
f1e5fc6c0b | ||
|
425626ef94 | ||
|
a52f6a6657 | ||
|
bbdd6a8910 | ||
|
b738f5740c | ||
|
9e6a353427 | ||
|
22f19c4067 | ||
|
5c754b6f5d | ||
|
6a82029ab4 | ||
|
caabde4acf | ||
|
ff998fce57 | ||
|
e11e020886 | ||
|
9942d967a4 | ||
|
67f62947c6 | ||
|
58db3cbd53 | ||
|
548c313b4c | ||
|
83d5d53d8a | ||
|
8957a08453 | ||
|
445e8592c4 | ||
|
9912da9546 | ||
|
6be0b2da77 | ||
|
17608ce522 | ||
|
c49b05cd75 | ||
|
2617757edb | ||
|
4d17e50323 | ||
|
49c4b762b6 | ||
|
1098b64711 | ||
|
1fd62ee342 | ||
|
98dee97cc6 | ||
|
c10910e466 | ||
|
f6061f95de | ||
|
591c0fe8a3 | ||
|
4f2261e86f | ||
|
d267974223 | ||
|
6bfcba4940 | ||
|
4acc9a96e3 | ||
|
6a1b7dc597 | ||
|
6d016bcfc9 |
489 changed files with 56158 additions and 7667 deletions
86
.github/workflows/ci.yml
vendored
86
.github/workflows/ci.yml
vendored
|
@ -1,6 +1,14 @@
|
||||||
name: signal-cli CI
|
name: signal-cli CI
|
||||||
|
|
||||||
on: [push, pull_request]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
|
pull_request:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write # to fetch code (actions/checkout) and submit dependency graph (gradle/gradle-build-action)
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
@ -8,13 +16,81 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
java: [ '11', '14' ]
|
java: [ '21', '24' ]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v4
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
|
distribution: 'zulu'
|
||||||
java-version: ${{ matrix.java }}
|
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
|
- 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@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
|
||||||
|
|
24
.github/workflows/codeql-analysis.yml
vendored
24
.github/workflows/codeql-analysis.yml
vendored
|
@ -2,13 +2,17 @@ name: "CodeQL"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master, ]
|
branches: [ master ]
|
||||||
pull_request:
|
pull_request:
|
||||||
# The branches below must be a subset of the branches above
|
# The branches below must be a subset of the branches above
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 7 * * 4'
|
- cron: '0 7 * * 4'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
security-events: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyse:
|
analyse:
|
||||||
name: Analyse
|
name: Analyse
|
||||||
|
@ -17,25 +21,21 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Setup Java JDK
|
- name: Setup Java JDK
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
distribution: 'zulu'
|
||||||
|
java-version: 21
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# We must fetch at least the immediate parents so that if this is
|
# We must fetch at least the immediate parents so that if this is
|
||||||
# a pull request then we can checkout the head.
|
# a pull request then we can checkout the head.
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|
||||||
# If this run was triggered by a pull request event, then checkout
|
|
||||||
# the head of the pull request instead of the merge commit.
|
|
||||||
- run: git checkout HEAD^2
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- 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
|
# Override language selection by uncommenting this and choosing your languages
|
||||||
# with:
|
# with:
|
||||||
# languages: go, javascript, csharp, python, cpp, java
|
# languages: go, javascript, csharp, python, cpp, java
|
||||||
|
@ -43,7 +43,7 @@ jobs:
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# 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)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v1
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://git.io/JvXDl
|
# 📚 https://git.io/JvXDl
|
||||||
|
@ -57,4 +57,4 @@ jobs:
|
||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v1
|
uses: github/codeql-action/analyze@v3
|
||||||
|
|
257
.github/workflows/release.yml
vendored
Normal file
257
.github/workflows/release.yml
vendored
Normal 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) }}"
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -10,3 +10,6 @@ local.properties
|
||||||
.project
|
.project
|
||||||
.settings/
|
.settings/
|
||||||
out/
|
out/
|
||||||
|
.DS_Store
|
||||||
|
/bin/
|
||||||
|
/test-config/
|
||||||
|
|
35
.idea/codeStyles/Project.xml
generated
35
.idea/codeStyles/Project.xml
generated
|
@ -4,8 +4,9 @@
|
||||||
<JavaCodeStyleSettings>
|
<JavaCodeStyleSettings>
|
||||||
<option name="GENERATE_FINAL_LOCALS" value="true" />
|
<option name="GENERATE_FINAL_LOCALS" value="true" />
|
||||||
<option name="GENERATE_FINAL_PARAMETERS" value="true" />
|
<option name="GENERATE_FINAL_PARAMETERS" value="true" />
|
||||||
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="50" />
|
<option name="ALIGN_MULTILINE_TEXT_BLOCKS" value="true" />
|
||||||
<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">
|
<option name="IMPORT_LAYOUT_TABLE">
|
||||||
<value>
|
<value>
|
||||||
<package name="com" withSubpackages="true" static="false" />
|
<package name="com" withSubpackages="true" static="false" />
|
||||||
|
@ -26,21 +27,45 @@
|
||||||
<emptyLine />
|
<emptyLine />
|
||||||
</value>
|
</value>
|
||||||
</option>
|
</option>
|
||||||
|
<option name="RECORD_COMPONENTS_WRAP" value="5" />
|
||||||
|
<option name="NEW_LINE_AFTER_LPAREN_IN_RECORD_HEADER" value="true" />
|
||||||
|
<option name="RPAREN_ON_NEW_LINE_IN_RECORD_HEADER" value="true" />
|
||||||
<option name="JD_P_AT_EMPTY_LINES" value="false" />
|
<option name="JD_P_AT_EMPTY_LINES" value="false" />
|
||||||
</JavaCodeStyleSettings>
|
</JavaCodeStyleSettings>
|
||||||
<XML>
|
<JetCodeStyleSettings>
|
||||||
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
</XML>
|
</JetCodeStyleSettings>
|
||||||
<codeStyleSettings language="JAVA">
|
<codeStyleSettings language="JAVA">
|
||||||
|
<option name="RIGHT_MARGIN" value="120" />
|
||||||
|
<option name="KEEP_LINE_BREAKS" value="false" />
|
||||||
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
|
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
|
||||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="1" />
|
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="1" />
|
||||||
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
|
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
|
||||||
|
<option name="CALL_PARAMETERS_WRAP" value="5" />
|
||||||
|
<option name="METHOD_PARAMETERS_WRAP" value="5" />
|
||||||
|
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="METHOD_CALL_CHAIN_WRAP" value="5" />
|
||||||
|
<option name="PARENTHESES_EXPRESSION_LPAREN_WRAP" value="true" />
|
||||||
|
<option name="PARENTHESES_EXPRESSION_RPAREN_WRAP" value="true" />
|
||||||
|
<option name="BINARY_OPERATION_WRAP" value="5" />
|
||||||
|
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="TERNARY_OPERATION_WRAP" value="5" />
|
||||||
|
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="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>
|
||||||
<codeStyleSettings language="XML">
|
<codeStyleSettings language="XML">
|
||||||
<arrangement>
|
<arrangement>
|
||||||
<rules />
|
<rules />
|
||||||
</arrangement>
|
</arrangement>
|
||||||
</codeStyleSettings>
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="kotlin">
|
||||||
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
</codeStyleSettings>
|
||||||
</code_scheme>
|
</code_scheme>
|
||||||
</component>
|
</component>
|
1179
CHANGELOG.md
1179
CHANGELOG.md
File diff suppressed because it is too large
Load diff
18
CONTRIBUTING.md
Normal file
18
CONTRIBUTING.md
Normal file
|
@ -0,0 +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 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
11
Containerfile
Normal 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"]
|
4
FUNDING.yml
Normal file
4
FUNDING.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
github: AsamK
|
||||||
|
liberapay: asamk
|
||||||
|
ko_fi: asamk
|
||||||
|
#bitcoin: bc1qykae53fry8a8ycgdzgv0rlxfc959hmmllvz698
|
124
README.md
124
README.md
|
@ -1,70 +1,117 @@
|
||||||
# signal-cli
|
# 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.
|
signal-cli is a commandline interface for the [Signal messenger](https://signal.org/).
|
||||||
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 slave device](https://github.com/WhisperSystems/libsignal-service-java/pull/21).
|
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.
|
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 dbus interface, that can be used to send messages from any programming language that has dbus bindings.
|
|
||||||
|
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
|
## Installation
|
||||||
|
|
||||||
You can [build signal-cli](#building) yourself, or use the [provided binary files](https://github.com/AsamK/signal-cli/releases/latest), which should work on Linux, macOS and Windows. For Arch Linux there is also a [package in AUR](https://aur.archlinux.org/packages/signal-cli/) and there is a [FreeBSD port](https://www.freshports.org/net-im/signal-cli) available as well. You need to have at least JRE 11 installed, to run signal-cli.
|
You can [build signal-cli](#building) yourself or use
|
||||||
|
the [provided binary files](https://github.com/AsamK/signal-cli/releases/latest), which should work on Linux, macOS and
|
||||||
|
Windows. There's also a [docker image and some Linux packages](https://github.com/AsamK/signal-cli/wiki/Binary-distributions) provided by the community.
|
||||||
|
|
||||||
|
System requirements:
|
||||||
|
|
||||||
|
- 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
|
||||||
|
systems/architectures
|
||||||
|
see: [Provide native lib for libsignal](https://github.com/AsamK/signal-cli/wiki/Provide-native-lib-for-libsignal)
|
||||||
|
|
||||||
### Install system-wide on Linux
|
### Install system-wide on Linux
|
||||||
|
|
||||||
See [latest version](https://github.com/AsamK/signal-cli/releases).
|
See [latest version](https://github.com/AsamK/signal-cli/releases).
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
export VERSION=<latest version, format "x.y.z">
|
export VERSION=<latest version, format "x.y.z">
|
||||||
wget https://github.com/AsamK/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz
|
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 tar xf signal-cli-"${VERSION}".tar.gz -C /opt
|
||||||
sudo ln -sf /opt/signal-cli-"${VERSION}"/bin/signal-cli /usr/local/bin/
|
sudo ln -sf /opt/signal-cli-"${VERSION}"/bin/signal-cli /usr/local/bin/
|
||||||
```
|
```
|
||||||
|
|
||||||
You can find further instructions on the Wiki:
|
You can find further instructions on the Wiki:
|
||||||
|
|
||||||
- [Quickstart](https://github.com/AsamK/signal-cli/wiki/Quickstart)
|
- [Quickstart](https://github.com/AsamK/signal-cli/wiki/Quickstart)
|
||||||
- [DBus Service](https://github.com/AsamK/signal-cli/wiki/DBus-service)
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Important: The USERNAME (your phone number) must include the country calling code, i.e. the number must start with a "+" sign. (See [Wikipedia](https://en.wikipedia.org/wiki/List_of_country_calling_codes) for a list of all country codes.)
|
For a complete usage overview please read
|
||||||
|
the [man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli.1.adoc) and
|
||||||
|
the [wiki](https://github.com/AsamK/signal-cli/wiki).
|
||||||
|
|
||||||
|
Important: The ACCOUNT is your phone number in international format and must include the country calling code. Hence it
|
||||||
|
should start with a "+" sign. (See [Wikipedia](https://en.wikipedia.org/wiki/List_of_country_calling_codes) for a list
|
||||||
|
of all country codes.)
|
||||||
|
|
||||||
* Register a number (with SMS verification)
|
* Register a number (with SMS verification)
|
||||||
|
|
||||||
signal-cli -u USERNAME 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:
|
||||||
|
|
||||||
* 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
|
```sh
|
||||||
|
signal-cli -a ACCOUNT register --voice
|
||||||
|
```
|
||||||
|
|
||||||
signal-cli -u USERNAME verify CODE
|
Registering may require solving a CAPTCHA
|
||||||
|
challenge: [Registration with captcha](https://github.com/AsamK/signal-cli/wiki/Registration-with-captcha)
|
||||||
|
|
||||||
|
* 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
|
||||||
|
|
||||||
* Send a message
|
* Send a message
|
||||||
|
|
||||||
signal-cli -u USERNAME 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.
|
* Pipe the message content from another process.
|
||||||
|
|
||||||
uname -a | signal-cli -u USERNAME send RECIPIENT
|
uname -a | signal-cli -a ACCOUNT send --message-from-stdin RECIPIENT
|
||||||
|
|
||||||
* Receive messages
|
* Receive messages
|
||||||
|
|
||||||
signal-cli -u USERNAME receive
|
signal-cli -a ACCOUNT receive
|
||||||
|
|
||||||
For more information read the [man page](https://github.com/AsamK/signal-cli/blob/master/man/signal-cli.1.adoc) and the [wiki](https://github.com/AsamK/signal-cli/wiki).
|
**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
|
||||||
|
and other features.
|
||||||
|
|
||||||
## Storage
|
## Storage
|
||||||
|
|
||||||
The password and cryptographic keys are created when registering and stored in the current users home directory:
|
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/
|
||||||
For legacy users, the old config directories are used as a fallback:
|
|
||||||
|
|
||||||
$HOME/.config/signal/data/
|
|
||||||
|
|
||||||
$HOME/.config/textsecure/data/
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
This project uses [Gradle](http://gradle.org) for building and maintaining
|
This project uses [Gradle](http://gradle.org) for building and maintaining dependencies. If you have a recent gradle
|
||||||
dependencies. If you have a recent gradle version installed, you can replace `./gradlew` with `gradle` in the following steps.
|
version installed, you can replace `./gradlew` with `gradle` in the following steps.
|
||||||
|
|
||||||
1. Checkout the source somewhere on your filesystem with
|
1. Checkout the source somewhere on your filesystem with
|
||||||
|
|
||||||
|
@ -74,16 +121,39 @@ dependencies. If you have a recent gradle version installed, you can replace `./
|
||||||
|
|
||||||
./gradlew build
|
./gradlew build
|
||||||
|
|
||||||
3. Create shell wrapper in *build/install/signal-cli/bin*:
|
2a. Create shell wrapper in *build/install/signal-cli/bin*:
|
||||||
|
|
||||||
./gradlew installDist
|
./gradlew installDist
|
||||||
|
|
||||||
4. Create tar file in *build/distributions*:
|
2b. Create tar file in *build/distributions*:
|
||||||
|
|
||||||
./gradlew distTar
|
./gradlew distTar
|
||||||
|
|
||||||
## Troubleshooting
|
2c. Create a fat tar file in *build/libs/signal-cli-fat*:
|
||||||
If you use a version of the Oracle JRE and get an InvalidKeyException you need to enable unlimited strength crypto. See https://stackoverflow.com/questions/6481627/java-security-illegal-key-size-or-default-parameters for instructions.
|
|
||||||
|
./gradlew fatJar
|
||||||
|
|
||||||
|
2d. Compile and run signal-cli:
|
||||||
|
|
||||||
|
```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 environment](https://www.graalvm.org/docs/getting-started/#install-graalvm)
|
||||||
|
2. Execute Gradle:
|
||||||
|
|
||||||
|
./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).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
76
build.gradle
76
build.gradle
|
@ -1,76 +0,0 @@
|
||||||
apply plugin: 'java'
|
|
||||||
apply plugin: 'application'
|
|
||||||
apply plugin: 'eclipse'
|
|
||||||
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
|
||||||
|
|
||||||
mainClassName = 'org.asamk.signal.Main'
|
|
||||||
|
|
||||||
version = '0.6.12'
|
|
||||||
|
|
||||||
compileJava.options.encoding = 'UTF-8'
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenLocal()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation 'com.github.turasa:signal-service-java:2.15.3_unofficial_15'
|
|
||||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.67'
|
|
||||||
implementation 'net.sourceforge.argparse4j:argparse4j:0.8.1'
|
|
||||||
implementation 'com.github.hypfvieh:dbus-java:3.2.3'
|
|
||||||
implementation 'org.slf4j:slf4j-nop:1.7.30'
|
|
||||||
}
|
|
||||||
|
|
||||||
jar {
|
|
||||||
manifest {
|
|
||||||
attributes(
|
|
||||||
'Implementation-Title': project.name,
|
|
||||||
'Implementation-Version': project.version,
|
|
||||||
'Main-Class': project.mainClassName,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run {
|
|
||||||
if (project.hasProperty("appArgs")) {
|
|
||||||
// allow passing command-line arguments to the main application e.g.:
|
|
||||||
// $ gradle run -PappArgs="['-u', '+...', 'daemon', '--json']"
|
|
||||||
args Eval.me(appArgs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find any 3rd party libraries which have released new versions
|
|
||||||
// to the central Maven repo since we last upgraded.
|
|
||||||
task checkLibVersions {
|
|
||||||
doLast {
|
|
||||||
def checked = [:]
|
|
||||||
allprojects {
|
|
||||||
configurations.each { configuration ->
|
|
||||||
configuration.allDependencies.each { dependency ->
|
|
||||||
def version = dependency.version
|
|
||||||
if (!checked[dependency]) {
|
|
||||||
def group = dependency.group
|
|
||||||
def path = group.replace('.', '/')
|
|
||||||
def name = dependency.name
|
|
||||||
def url = "https://repo1.maven.org/maven2/$path/$name/maven-metadata.xml"
|
|
||||||
try {
|
|
||||||
def metadata = new XmlSlurper().parseText(url.toURL().text)
|
|
||||||
def newest = metadata.versioning.latest;
|
|
||||||
if ("$version" != "$newest") {
|
|
||||||
println "UPGRADE {\"group\": \"$group\", \"name\": \"$name\", \"current\": \"$version\", \"latest\": \"$newest\"}"
|
|
||||||
}
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
logger.debug "Unable to download $url: $e.message"
|
|
||||||
} catch (org.xml.sax.SAXParseException e) {
|
|
||||||
logger.debug "Unable to parse $url: $e.message"
|
|
||||||
}
|
|
||||||
checked[dependency] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
139
build.gradle.kts
Normal file
139
build.gradle.kts
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
plugins {
|
||||||
|
java
|
||||||
|
application
|
||||||
|
eclipse
|
||||||
|
`check-lib-versions`
|
||||||
|
id("org.graalvm.buildtools.native") version "0.10.6"
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
group = "org.asamk"
|
||||||
|
version = "0.13.19-SNAPSHOT"
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
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"))
|
||||||
|
if (System.getenv("GRAALVM_HOME") == null) {
|
||||||
|
toolchainDetection.set(true)
|
||||||
|
javaLauncher.set(javaToolchains.launcherFor {
|
||||||
|
languageVersion.set(JavaLanguageVersion.of(21))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
toolchainDetection.set(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
implementation {
|
||||||
|
resolutionStrategy.failOnVersionConflict()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
tasks.withType<AbstractArchiveTask>().configureEach {
|
||||||
|
isPreserveFileTimestamps = false
|
||||||
|
isReproducibleFileOrder = true
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<JavaCompile> {
|
||||||
|
options.encoding = "UTF-8"
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<Jar> {
|
||||||
|
manifest {
|
||||||
|
attributes(
|
||||||
|
"Implementation-Title" to project.name,
|
||||||
|
"Implementation-Version" to project.version,
|
||||||
|
"Main-Class" to application.mainClass.get(),
|
||||||
|
"Enable-Native-Access" to "ALL-UNNAMED",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("fatJar", type = Jar::class) {
|
||||||
|
archiveBaseName.set("${project.name}-fat")
|
||||||
|
exclude(
|
||||||
|
"META-INF/*.SF",
|
||||||
|
"META-INF/*.DSA",
|
||||||
|
"META-INF/*.RSA",
|
||||||
|
"META-INF/NOTICE*",
|
||||||
|
"META-INF/LICENSE*",
|
||||||
|
"META-INF/INDEX.LIST",
|
||||||
|
"**/module-info.class",
|
||||||
|
)
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.WARN
|
||||||
|
doFirst {
|
||||||
|
from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
|
||||||
|
}
|
||||||
|
with(tasks.jar.get())
|
||||||
|
}
|
28
buildSrc/build.gradle.kts
Normal file
28
buildSrc/build.gradle.kts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
gradlePlugin {
|
||||||
|
plugins {
|
||||||
|
register("check-lib-versions") {
|
||||||
|
id = "check-lib-versions"
|
||||||
|
implementationClass = "CheckLibVersionsPlugin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
buildSrc/src/main/kotlin/CheckLibVersionsPlugin.kt
Normal file
40
buildSrc/src/main/kotlin/CheckLibVersionsPlugin.kt
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
@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) {
|
||||||
|
project.task("checkLibVersions") {
|
||||||
|
description =
|
||||||
|
"Find any 3rd party libraries which have released new versions to the central Maven repo since we last upgraded."
|
||||||
|
doLast {
|
||||||
|
project.configurations.flatMap { it.allDependencies }
|
||||||
|
.toSet()
|
||||||
|
.forEach { checkDependency(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Task.checkDependency(dependency: Dependency) {
|
||||||
|
val version = dependency.version
|
||||||
|
val group = dependency.group
|
||||||
|
val path = group?.replace(".", "/") ?: ""
|
||||||
|
val name = dependency.name
|
||||||
|
val metaDataUrl = "https://repo1.maven.org/maven2/$path/$name/maven-metadata.xml"
|
||||||
|
try {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
buildSrc/src/main/kotlin/ExcludeFileFromJar.kt
Normal file
53
buildSrc/src/main/kotlin/ExcludeFileFromJar.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
client/.gitignore
vendored
Normal file
1
client/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target/
|
1756
client/Cargo.lock
generated
Normal file
1756
client/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
22
client/Cargo.toml
Normal file
22
client/Cargo.toml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
[package]
|
||||||
|
name = "signal-cli-client"
|
||||||
|
version = "0.0.1"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1"
|
||||||
|
clap = { version = "4", features = ["cargo", "derive", "wrap_help"] }
|
||||||
|
serde = "1"
|
||||||
|
serde_json = "1"
|
||||||
|
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"
|
470
client/src/cli.rs
Normal file
470
client/src/cli.rs
Normal file
|
@ -0,0 +1,470 @@
|
||||||
|
use std::{ffi::OsString, net::SocketAddr};
|
||||||
|
|
||||||
|
use clap::{crate_version, Parser, Subcommand, ValueEnum};
|
||||||
|
|
||||||
|
/// JSON-RPC client for signal-cli
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(rename_all = "kebab-case", version = crate_version!())]
|
||||||
|
pub struct Cli {
|
||||||
|
/// Account to use (for daemon in multi-account mode)
|
||||||
|
#[arg(short = 'a', long)]
|
||||||
|
pub account: Option<String>,
|
||||||
|
|
||||||
|
/// TCP host and port of signal-cli daemon
|
||||||
|
#[arg(long, conflicts_with = "json_rpc_http")]
|
||||||
|
pub json_rpc_tcp: Option<Option<SocketAddr>>,
|
||||||
|
|
||||||
|
/// UNIX socket address and port of signal-cli daemon
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[arg(long, conflicts_with = "json_rpc_tcp")]
|
||||||
|
pub json_rpc_socket: Option<Option<OsString>>,
|
||||||
|
|
||||||
|
/// HTTP URL of signal-cli daemon
|
||||||
|
#[arg(long, conflicts_with = "json_rpc_socket")]
|
||||||
|
pub json_rpc_http: Option<Option<String>>,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
pub verbose: bool,
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub command: CliCommands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
#[command(rename_all = "camelCase", version = crate_version!())]
|
||||||
|
pub enum CliCommands {
|
||||||
|
AddDevice {
|
||||||
|
#[arg(long)]
|
||||||
|
uri: String,
|
||||||
|
},
|
||||||
|
AddStickerPack {
|
||||||
|
#[arg(long)]
|
||||||
|
uri: String,
|
||||||
|
},
|
||||||
|
#[command(rename_all = "kebab-case")]
|
||||||
|
Block {
|
||||||
|
recipient: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(short = 'g', long)]
|
||||||
|
group_id: Vec<String>,
|
||||||
|
},
|
||||||
|
DeleteLocalAccountData {
|
||||||
|
#[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 {
|
||||||
|
#[arg(long)]
|
||||||
|
uri: String,
|
||||||
|
},
|
||||||
|
Link {
|
||||||
|
#[arg(short = 'n', long)]
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
ListAccounts,
|
||||||
|
ListContacts {
|
||||||
|
recipient: Vec<String>,
|
||||||
|
#[arg(short = 'a', long = "all-recipients")]
|
||||||
|
all_recipients: bool,
|
||||||
|
#[arg(long)]
|
||||||
|
blocked: Option<bool>,
|
||||||
|
#[arg(long)]
|
||||||
|
name: Option<String>,
|
||||||
|
},
|
||||||
|
ListDevices,
|
||||||
|
ListGroups {
|
||||||
|
#[arg(short = 'd', long)]
|
||||||
|
detailed: bool,
|
||||||
|
#[arg(short = 'g', long = "group-id")]
|
||||||
|
group_id: Vec<String>,
|
||||||
|
},
|
||||||
|
ListIdentities {
|
||||||
|
#[arg(short = 'n', long)]
|
||||||
|
number: Option<String>,
|
||||||
|
},
|
||||||
|
ListStickerPacks,
|
||||||
|
QuitGroup {
|
||||||
|
#[arg(short = 'g', long = "group-id")]
|
||||||
|
group_id: String,
|
||||||
|
#[arg(long)]
|
||||||
|
delete: bool,
|
||||||
|
#[arg(long)]
|
||||||
|
admin: Vec<String>,
|
||||||
|
},
|
||||||
|
Receive {
|
||||||
|
#[arg(short = 't', long, default_value_t = 3.0)]
|
||||||
|
timeout: f64,
|
||||||
|
},
|
||||||
|
Register {
|
||||||
|
#[arg(short = 'v', long)]
|
||||||
|
voice: bool,
|
||||||
|
#[arg(long)]
|
||||||
|
captcha: Option<String>,
|
||||||
|
},
|
||||||
|
RemoveContact {
|
||||||
|
recipient: String,
|
||||||
|
#[arg(long)]
|
||||||
|
forget: bool,
|
||||||
|
#[arg(long)]
|
||||||
|
hide: bool,
|
||||||
|
},
|
||||||
|
RemoveDevice {
|
||||||
|
#[arg(short = 'd', long = "device-id")]
|
||||||
|
device_id: u32,
|
||||||
|
},
|
||||||
|
RemovePin,
|
||||||
|
RemoteDelete {
|
||||||
|
#[arg(short = 't', long = "target-timestamp")]
|
||||||
|
target_timestamp: u64,
|
||||||
|
|
||||||
|
recipient: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(short = 'g', long = "group-id")]
|
||||||
|
group_id: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(long = "note-to-self")]
|
||||||
|
note_to_self: bool,
|
||||||
|
},
|
||||||
|
#[command(rename_all = "kebab-case")]
|
||||||
|
Send {
|
||||||
|
recipient: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(short = 'g', long)]
|
||||||
|
group_id: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
note_to_self: bool,
|
||||||
|
|
||||||
|
#[arg(short = 'e', long)]
|
||||||
|
end_session: bool,
|
||||||
|
|
||||||
|
#[arg(short = 'm', long)]
|
||||||
|
message: Option<String>,
|
||||||
|
|
||||||
|
#[arg(short = 'a', long)]
|
||||||
|
attachment: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
view_once: bool,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
mention: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
text_style: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
quote_timestamp: Option<u64>,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
quote_author: Option<String>,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
quote_message: Option<String>,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
quote_mention: Vec<String>,
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
receipt: String,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
note: String,
|
||||||
|
},
|
||||||
|
SendReaction {
|
||||||
|
recipient: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(short = 'g', long = "group-id")]
|
||||||
|
group_id: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(long = "note-to-self")]
|
||||||
|
note_to_self: bool,
|
||||||
|
|
||||||
|
#[arg(short = 'e', long)]
|
||||||
|
emoji: String,
|
||||||
|
|
||||||
|
#[arg(short = 'a', long = "target-author")]
|
||||||
|
target_author: String,
|
||||||
|
|
||||||
|
#[arg(short = 't', long = "target-timestamp")]
|
||||||
|
target_timestamp: u64,
|
||||||
|
|
||||||
|
#[arg(short = 'r', long)]
|
||||||
|
remove: bool,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
story: bool,
|
||||||
|
},
|
||||||
|
SendReceipt {
|
||||||
|
recipient: String,
|
||||||
|
|
||||||
|
#[arg(short = 't', long = "target-timestamp")]
|
||||||
|
target_timestamp: Vec<u64>,
|
||||||
|
|
||||||
|
#[arg(value_enum, long)]
|
||||||
|
r#type: ReceiptType,
|
||||||
|
},
|
||||||
|
SendSyncRequest,
|
||||||
|
SendTyping {
|
||||||
|
recipient: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(short = 'g', long = "group-id")]
|
||||||
|
group_id: Vec<String>,
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
},
|
||||||
|
Trust {
|
||||||
|
recipient: String,
|
||||||
|
|
||||||
|
#[arg(short = 'a', long = "trust-all-known-keys")]
|
||||||
|
trust_all_known_keys: bool,
|
||||||
|
|
||||||
|
#[arg(short = 'v', long = "verified-safety-number")]
|
||||||
|
verified_safety_number: Option<String>,
|
||||||
|
},
|
||||||
|
#[command(rename_all = "kebab-case")]
|
||||||
|
Unblock {
|
||||||
|
recipient: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(short = 'g', long)]
|
||||||
|
group_id: Vec<String>,
|
||||||
|
},
|
||||||
|
Unregister {
|
||||||
|
#[arg(long = "delete-account")]
|
||||||
|
delete_account: bool,
|
||||||
|
},
|
||||||
|
UpdateAccount {
|
||||||
|
#[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 {
|
||||||
|
#[arg(long = "read-receipts")]
|
||||||
|
read_receipts: Option<bool>,
|
||||||
|
|
||||||
|
#[arg(long = "unidentified-delivery-indicators")]
|
||||||
|
unidentified_delivery_indicators: Option<bool>,
|
||||||
|
|
||||||
|
#[arg(long = "typing-indicators")]
|
||||||
|
typing_indicators: Option<bool>,
|
||||||
|
|
||||||
|
#[arg(long = "link-previews")]
|
||||||
|
link_previews: Option<bool>,
|
||||||
|
},
|
||||||
|
UpdateContact {
|
||||||
|
recipient: String,
|
||||||
|
|
||||||
|
#[arg(short = 'e', long)]
|
||||||
|
expiration: Option<u32>,
|
||||||
|
|
||||||
|
#[arg(short = 'n', long)]
|
||||||
|
name: Option<String>,
|
||||||
|
},
|
||||||
|
UpdateGroup {
|
||||||
|
#[arg(short = 'g', long = "group-id")]
|
||||||
|
group_id: Option<String>,
|
||||||
|
|
||||||
|
#[arg(short = 'n', long)]
|
||||||
|
name: Option<String>,
|
||||||
|
|
||||||
|
#[arg(short = 'd', long)]
|
||||||
|
description: Option<String>,
|
||||||
|
|
||||||
|
#[arg(short = 'a', long)]
|
||||||
|
avatar: Option<String>,
|
||||||
|
|
||||||
|
#[arg(short = 'm', long)]
|
||||||
|
member: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(short = 'r', long = "remove-member")]
|
||||||
|
remove_member: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
admin: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(long = "remove-admin")]
|
||||||
|
remove_admin: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
ban: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
unban: Vec<String>,
|
||||||
|
|
||||||
|
#[arg(long = "reset-link")]
|
||||||
|
reset_link: bool,
|
||||||
|
|
||||||
|
#[arg(value_enum, long)]
|
||||||
|
link: Option<LinkState>,
|
||||||
|
|
||||||
|
#[arg(value_enum, long = "set-permission-add-member")]
|
||||||
|
set_permission_add_member: Option<GroupPermission>,
|
||||||
|
|
||||||
|
#[arg(value_enum, long = "set-permission-edit-details")]
|
||||||
|
set_permission_edit_details: Option<GroupPermission>,
|
||||||
|
|
||||||
|
#[arg(value_enum, long = "set-permission-send-messages")]
|
||||||
|
set_permission_send_messages: Option<GroupPermission>,
|
||||||
|
|
||||||
|
#[arg(short = 'e', long)]
|
||||||
|
expiration: Option<u32>,
|
||||||
|
},
|
||||||
|
UpdateProfile {
|
||||||
|
#[arg(long = "given-name")]
|
||||||
|
given_name: Option<String>,
|
||||||
|
|
||||||
|
#[arg(long = "family-name")]
|
||||||
|
family_name: Option<String>,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
about: Option<String>,
|
||||||
|
|
||||||
|
#[arg(long = "about-emoji")]
|
||||||
|
about_emoji: Option<String>,
|
||||||
|
|
||||||
|
#[arg(long = "mobile-coin-address", visible_alias = "mobilecoin-address")]
|
||||||
|
mobile_coin_address: Option<String>,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
avatar: Option<String>,
|
||||||
|
|
||||||
|
#[arg(long = "remove-avatar")]
|
||||||
|
remove_avatar: bool,
|
||||||
|
},
|
||||||
|
UploadStickerPack {
|
||||||
|
path: String,
|
||||||
|
},
|
||||||
|
Verify {
|
||||||
|
verification_code: String,
|
||||||
|
|
||||||
|
#[arg(short = 'p', long)]
|
||||||
|
pin: Option<String>,
|
||||||
|
},
|
||||||
|
Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ValueEnum, Clone, Debug)]
|
||||||
|
#[value(rename_all = "kebab-case")]
|
||||||
|
pub enum ReceiptType {
|
||||||
|
Read,
|
||||||
|
Viewed,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ValueEnum, Clone, Debug)]
|
||||||
|
#[value(rename_all = "kebab-case")]
|
||||||
|
pub enum LinkState {
|
||||||
|
Enabled,
|
||||||
|
EnabledWithApproval,
|
||||||
|
Disabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
}
|
425
client/src/jsonrpc.rs
Normal file
425
client/src/jsonrpc.rs
Normal file
|
@ -0,0 +1,425 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
#[rpc(client)]
|
||||||
|
pub trait Rpc {
|
||||||
|
#[method(name = "addDevice", param_kind = map)]
|
||||||
|
async fn add_device(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
uri: String,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[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, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "deleteLocalAccountData", param_kind = map)]
|
||||||
|
fn delete_local_account_data(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
#[allow(non_snake_case)] ignoreRegistered: Option<bool>,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[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>;
|
||||||
|
|
||||||
|
#[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>;
|
||||||
|
|
||||||
|
#[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, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "listAccounts", param_kind = map)]
|
||||||
|
fn list_accounts(&self) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "listContacts", param_kind = map)]
|
||||||
|
fn list_contacts(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
recipients: Vec<String>,
|
||||||
|
#[allow(non_snake_case)] allRecipients: bool,
|
||||||
|
blocked: Option<bool>,
|
||||||
|
name: Option<String>,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "listDevices", param_kind = map)]
|
||||||
|
fn list_devices(&self, account: Option<String>) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "listGroups", param_kind = map)]
|
||||||
|
fn list_groups(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
#[allow(non_snake_case)] groupIds: Vec<String>,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "listIdentities", param_kind = map)]
|
||||||
|
fn list_identities(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
number: Option<String>,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "listStickerPacks", param_kind = map)]
|
||||||
|
fn list_sticker_packs(&self, account: Option<String>) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[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, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "register", param_kind = map)]
|
||||||
|
fn register(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
voice: bool,
|
||||||
|
captcha: Option<String>,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "removeContact", param_kind = map)]
|
||||||
|
fn remove_contact(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
recipient: String,
|
||||||
|
forget: bool,
|
||||||
|
hide: bool,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "removeDevice", param_kind = map)]
|
||||||
|
fn remove_device(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
#[allow(non_snake_case)] deviceId: u32,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "removePin", param_kind = map)]
|
||||||
|
fn remove_pin(&self, account: Option<String>) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "remoteDelete", param_kind = map)]
|
||||||
|
fn remote_delete(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
#[allow(non_snake_case)] targetTimestamp: u64,
|
||||||
|
recipients: Vec<String>,
|
||||||
|
#[allow(non_snake_case)] groupIds: Vec<String>,
|
||||||
|
#[allow(non_snake_case)] noteToSelf: bool,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[method(name = "send", param_kind = map)]
|
||||||
|
fn send(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
recipients: Vec<String>,
|
||||||
|
groupIds: Vec<String>,
|
||||||
|
noteToSelf: bool,
|
||||||
|
endSession: bool,
|
||||||
|
message: String,
|
||||||
|
attachments: Vec<String>,
|
||||||
|
viewOnce: bool,
|
||||||
|
mentions: 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>,
|
||||||
|
storyTimestamp: Option<u64>,
|
||||||
|
storyAuthor: Option<String>,
|
||||||
|
editTimestamp: Option<u64>,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "sendContacts", param_kind = map)]
|
||||||
|
fn send_contacts(&self, account: Option<String>) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "sendPaymentNotification", param_kind = map)]
|
||||||
|
fn send_payment_notification(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
recipient: String,
|
||||||
|
receipt: String,
|
||||||
|
note: String,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "sendReaction", param_kind = map)]
|
||||||
|
fn send_reaction(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
recipients: Vec<String>,
|
||||||
|
#[allow(non_snake_case)] groupIds: Vec<String>,
|
||||||
|
#[allow(non_snake_case)] noteToSelf: bool,
|
||||||
|
emoji: String,
|
||||||
|
#[allow(non_snake_case)] targetAuthor: String,
|
||||||
|
#[allow(non_snake_case)] targetTimestamp: u64,
|
||||||
|
remove: bool,
|
||||||
|
story: bool,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[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, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "sendSyncRequest", param_kind = map)]
|
||||||
|
fn send_sync_request(&self, account: Option<String>) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[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, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[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>;
|
||||||
|
|
||||||
|
#[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, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "startChangeNumber", param_kind = map)]
|
||||||
|
fn start_change_number(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
number: String,
|
||||||
|
voice: bool,
|
||||||
|
captcha: Option<String>,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[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, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "unblock", param_kind = map)]
|
||||||
|
fn unblock(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
recipients: Vec<String>,
|
||||||
|
#[allow(non_snake_case)] groupIds: Vec<String>,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "unregister", param_kind = map)]
|
||||||
|
fn unregister(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
#[allow(non_snake_case)] deleteAccount: bool,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[method(name = "updateAccount", param_kind = map)]
|
||||||
|
fn update_account(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
deviceName: Option<String>,
|
||||||
|
unrestrictedUnidentifiedSender: Option<bool>,
|
||||||
|
discoverableByNumber: Option<bool>,
|
||||||
|
numberSharing: Option<bool>,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "updateConfiguration", param_kind = map)]
|
||||||
|
fn update_configuration(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
#[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, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "updateContact", param_kind = map)]
|
||||||
|
fn update_contact(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
recipient: String,
|
||||||
|
name: Option<String>,
|
||||||
|
expiration: Option<u32>,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "updateGroup", param_kind = map)]
|
||||||
|
fn update_group(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
#[allow(non_snake_case)] groupId: Option<String>,
|
||||||
|
name: Option<String>,
|
||||||
|
description: Option<String>,
|
||||||
|
avatar: Option<String>,
|
||||||
|
member: Vec<String>,
|
||||||
|
#[allow(non_snake_case)] removeMember: Vec<String>,
|
||||||
|
admin: Vec<String>,
|
||||||
|
#[allow(non_snake_case)] removeAdmin: Vec<String>,
|
||||||
|
ban: Vec<String>,
|
||||||
|
unban: Vec<String>,
|
||||||
|
#[allow(non_snake_case)] resetLink: bool,
|
||||||
|
#[allow(non_snake_case)] link: Option<String>,
|
||||||
|
#[allow(non_snake_case)] setPermissionAddMember: Option<String>,
|
||||||
|
#[allow(non_snake_case)] setPermissionEditDetails: Option<String>,
|
||||||
|
#[allow(non_snake_case)] setPermissionSendMessages: Option<String>,
|
||||||
|
expiration: Option<u32>,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "updateProfile", param_kind = map)]
|
||||||
|
fn update_profile(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
#[allow(non_snake_case)] givenName: Option<String>,
|
||||||
|
#[allow(non_snake_case)] familyName: Option<String>,
|
||||||
|
about: Option<String>,
|
||||||
|
#[allow(non_snake_case)] aboutEmoji: Option<String>,
|
||||||
|
#[allow(non_snake_case)] mobileCoinAddress: Option<String>,
|
||||||
|
avatar: Option<String>,
|
||||||
|
#[allow(non_snake_case)] removeAvatar: bool,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "uploadStickerPack", param_kind = map)]
|
||||||
|
fn upload_sticker_pack(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
path: String,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[method(name = "verify", param_kind = map)]
|
||||||
|
fn verify(
|
||||||
|
&self,
|
||||||
|
account: Option<String>,
|
||||||
|
#[allow(non_snake_case)] verificationCode: String,
|
||||||
|
pin: Option<String>,
|
||||||
|
) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
|
||||||
|
#[subscription(
|
||||||
|
name = "subscribeReceive" => "receive",
|
||||||
|
unsubscribe = "unsubscribeReceive",
|
||||||
|
item = Value,
|
||||||
|
param_kind = map
|
||||||
|
)]
|
||||||
|
async fn subscribe_receive(&self, account: Option<String>) -> SubscriptionResult;
|
||||||
|
|
||||||
|
#[method(name = "version")]
|
||||||
|
fn version(&self) -> Result<Value, ErrorObjectOwned>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct JsonLink {
|
||||||
|
pub device_link_uri: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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)
|
||||||
|
}
|
526
client/src/main.rs
Normal file
526
client/src/main.rs
Normal file
|
@ -0,0 +1,526 @@
|
||||||
|
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 cli::Cli;
|
||||||
|
|
||||||
|
use crate::cli::{CliCommands, GroupPermission, LinkState};
|
||||||
|
use crate::jsonrpc::RpcClient;
|
||||||
|
|
||||||
|
mod cli;
|
||||||
|
#[allow(non_snake_case, clippy::too_many_arguments)]
|
||||||
|
mod jsonrpc;
|
||||||
|
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 result = connect(cli).await;
|
||||||
|
|
||||||
|
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?;
|
||||||
|
println!("{v}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.unsubscribe().await?;
|
||||||
|
Ok(Value::Null)
|
||||||
|
}
|
||||||
|
CliCommands::AddDevice { uri } => client.add_device(cli.account, uri).await,
|
||||||
|
CliCommands::Block {
|
||||||
|
recipient,
|
||||||
|
group_id,
|
||||||
|
} => client.block(cli.account, recipient, group_id).await,
|
||||||
|
CliCommands::DeleteLocalAccountData { ignore_registered } => {
|
||||||
|
client
|
||||||
|
.delete_local_account_data(cli.account, ignore_registered)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CliCommands::GetUserStatus {
|
||||||
|
recipient,
|
||||||
|
username,
|
||||||
|
} => {
|
||||||
|
client
|
||||||
|
.get_user_status(cli.account, recipient, username)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CliCommands::JoinGroup { uri } => client.join_group(cli.account, uri).await,
|
||||||
|
CliCommands::Link { name } => {
|
||||||
|
let url = client
|
||||||
|
.start_link(cli.account)
|
||||||
|
.await
|
||||||
|
.map_err(|e| RpcError::Custom(format!("JSON-RPC command startLink failed: {e:?}")))?
|
||||||
|
.device_link_uri;
|
||||||
|
println!("{url}");
|
||||||
|
client.finish_link(url, name).await
|
||||||
|
}
|
||||||
|
CliCommands::ListAccounts => client.list_accounts().await,
|
||||||
|
CliCommands::ListContacts {
|
||||||
|
recipient,
|
||||||
|
all_recipients,
|
||||||
|
blocked,
|
||||||
|
name,
|
||||||
|
} => {
|
||||||
|
client
|
||||||
|
.list_contacts(cli.account, recipient, all_recipients, blocked, name)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CliCommands::ListDevices => client.list_devices(cli.account).await,
|
||||||
|
CliCommands::ListGroups {
|
||||||
|
detailed: _,
|
||||||
|
group_id,
|
||||||
|
} => client.list_groups(cli.account, group_id).await,
|
||||||
|
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,
|
||||||
|
} => {
|
||||||
|
client
|
||||||
|
.quit_group(cli.account, group_id, delete, admin)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CliCommands::Register { voice, captcha } => {
|
||||||
|
client.register(cli.account, voice, captcha).await
|
||||||
|
}
|
||||||
|
CliCommands::RemoveContact {
|
||||||
|
recipient,
|
||||||
|
forget,
|
||||||
|
hide,
|
||||||
|
} => {
|
||||||
|
client
|
||||||
|
.remove_contact(cli.account, recipient, forget, hide)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CliCommands::RemoveDevice { device_id } => {
|
||||||
|
client.remove_device(cli.account, device_id).await
|
||||||
|
}
|
||||||
|
CliCommands::RemovePin => client.remove_pin(cli.account).await,
|
||||||
|
CliCommands::RemoteDelete {
|
||||||
|
target_timestamp,
|
||||||
|
recipient,
|
||||||
|
group_id,
|
||||||
|
note_to_self,
|
||||||
|
} => {
|
||||||
|
client
|
||||||
|
.remote_delete(
|
||||||
|
cli.account,
|
||||||
|
target_timestamp,
|
||||||
|
recipient,
|
||||||
|
group_id,
|
||||||
|
note_to_self,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
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(
|
||||||
|
cli.account,
|
||||||
|
recipient,
|
||||||
|
group_id,
|
||||||
|
note_to_self,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
CliCommands::SendContacts => client.send_contacts(cli.account).await,
|
||||||
|
CliCommands::SendPaymentNotification {
|
||||||
|
recipient,
|
||||||
|
receipt,
|
||||||
|
note,
|
||||||
|
} => {
|
||||||
|
client
|
||||||
|
.send_payment_notification(cli.account, recipient, receipt, note)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CliCommands::SendReaction {
|
||||||
|
recipient,
|
||||||
|
group_id,
|
||||||
|
note_to_self,
|
||||||
|
emoji,
|
||||||
|
target_author,
|
||||||
|
target_timestamp,
|
||||||
|
remove,
|
||||||
|
story,
|
||||||
|
} => {
|
||||||
|
client
|
||||||
|
.send_reaction(
|
||||||
|
cli.account,
|
||||||
|
recipient,
|
||||||
|
group_id,
|
||||||
|
note_to_self,
|
||||||
|
emoji,
|
||||||
|
target_author,
|
||||||
|
target_timestamp,
|
||||||
|
remove,
|
||||||
|
story,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CliCommands::SendReceipt {
|
||||||
|
recipient,
|
||||||
|
target_timestamp,
|
||||||
|
r#type,
|
||||||
|
} => {
|
||||||
|
client
|
||||||
|
.send_receipt(
|
||||||
|
cli.account,
|
||||||
|
recipient,
|
||||||
|
target_timestamp,
|
||||||
|
match r#type {
|
||||||
|
cli::ReceiptType::Read => "read".to_owned(),
|
||||||
|
cli::ReceiptType::Viewed => "viewed".to_owned(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CliCommands::SendSyncRequest => client.send_sync_request(cli.account).await,
|
||||||
|
CliCommands::SendTyping {
|
||||||
|
recipient,
|
||||||
|
group_id,
|
||||||
|
stop,
|
||||||
|
} => {
|
||||||
|
client
|
||||||
|
.send_typing(cli.account, recipient, group_id, stop)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CliCommands::SetPin { pin } => client.set_pin(cli.account, pin).await,
|
||||||
|
CliCommands::SubmitRateLimitChallenge { challenge, captcha } => {
|
||||||
|
client
|
||||||
|
.submit_rate_limit_challenge(cli.account, challenge, captcha)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CliCommands::Trust {
|
||||||
|
recipient,
|
||||||
|
trust_all_known_keys,
|
||||||
|
verified_safety_number,
|
||||||
|
} => {
|
||||||
|
client
|
||||||
|
.trust(
|
||||||
|
cli.account,
|
||||||
|
recipient,
|
||||||
|
trust_all_known_keys,
|
||||||
|
verified_safety_number,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CliCommands::Unblock {
|
||||||
|
recipient,
|
||||||
|
group_id,
|
||||||
|
} => client.unblock(cli.account, recipient, group_id).await,
|
||||||
|
CliCommands::Unregister { delete_account } => {
|
||||||
|
client.unregister(cli.account, delete_account).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
|
||||||
|
}
|
||||||
|
CliCommands::UpdateConfiguration {
|
||||||
|
read_receipts,
|
||||||
|
unidentified_delivery_indicators,
|
||||||
|
typing_indicators,
|
||||||
|
link_previews,
|
||||||
|
} => {
|
||||||
|
client
|
||||||
|
.update_configuration(
|
||||||
|
cli.account,
|
||||||
|
read_receipts,
|
||||||
|
unidentified_delivery_indicators,
|
||||||
|
typing_indicators,
|
||||||
|
link_previews,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CliCommands::UpdateContact {
|
||||||
|
recipient,
|
||||||
|
expiration,
|
||||||
|
name,
|
||||||
|
} => {
|
||||||
|
client
|
||||||
|
.update_contact(cli.account, recipient, name, expiration)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CliCommands::UpdateGroup {
|
||||||
|
group_id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
avatar,
|
||||||
|
member,
|
||||||
|
remove_member,
|
||||||
|
admin,
|
||||||
|
remove_admin,
|
||||||
|
ban,
|
||||||
|
unban,
|
||||||
|
reset_link,
|
||||||
|
link,
|
||||||
|
set_permission_add_member,
|
||||||
|
set_permission_edit_details,
|
||||||
|
set_permission_send_messages,
|
||||||
|
expiration,
|
||||||
|
} => {
|
||||||
|
client
|
||||||
|
.update_group(
|
||||||
|
cli.account,
|
||||||
|
group_id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
avatar,
|
||||||
|
member,
|
||||||
|
remove_member,
|
||||||
|
admin,
|
||||||
|
remove_admin,
|
||||||
|
ban,
|
||||||
|
unban,
|
||||||
|
reset_link,
|
||||||
|
link.map(|link| match link {
|
||||||
|
LinkState::Enabled => "enabled".to_owned(),
|
||||||
|
LinkState::EnabledWithApproval => "enabledWithApproval".to_owned(),
|
||||||
|
LinkState::Disabled => "disabled".to_owned(),
|
||||||
|
}),
|
||||||
|
set_permission_add_member.map(|p| match p {
|
||||||
|
GroupPermission::EveryMember => "everyMember".to_owned(),
|
||||||
|
GroupPermission::OnlyAdmins => "onlyAdmins".to_owned(),
|
||||||
|
}),
|
||||||
|
set_permission_edit_details.map(|p| match p {
|
||||||
|
GroupPermission::EveryMember => "everyMember".to_owned(),
|
||||||
|
GroupPermission::OnlyAdmins => "onlyAdmins".to_owned(),
|
||||||
|
}),
|
||||||
|
set_permission_send_messages.map(|p| match p {
|
||||||
|
GroupPermission::EveryMember => "everyMember".to_owned(),
|
||||||
|
GroupPermission::OnlyAdmins => "onlyAdmins".to_owned(),
|
||||||
|
}),
|
||||||
|
expiration,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CliCommands::UpdateProfile {
|
||||||
|
given_name,
|
||||||
|
family_name,
|
||||||
|
about,
|
||||||
|
about_emoji,
|
||||||
|
mobile_coin_address,
|
||||||
|
avatar,
|
||||||
|
remove_avatar,
|
||||||
|
} => {
|
||||||
|
client
|
||||||
|
.update_profile(
|
||||||
|
cli.account,
|
||||||
|
given_name,
|
||||||
|
family_name,
|
||||||
|
about,
|
||||||
|
about_emoji,
|
||||||
|
mobile_coin_address,
|
||||||
|
avatar,
|
||||||
|
remove_avatar,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
CliCommands::UploadStickerPack { path } => {
|
||||||
|
client.upload_sticker_pack(cli.account, path).await
|
||||||
|
}
|
||||||
|
CliCommands::Verify {
|
||||||
|
verification_code,
|
||||||
|
pin,
|
||||||
|
} => client.verify(cli.account, verification_code, pin).await,
|
||||||
|
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) -> 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());
|
||||||
|
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 {
|
||||||
|
#[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());
|
||||||
|
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 Subscription<Value>,
|
||||||
|
) -> Option<Result<Value, Error>> {
|
||||||
|
if timeout < 0.0 {
|
||||||
|
stream.next().await
|
||||||
|
} else {
|
||||||
|
select! {
|
||||||
|
v = stream.next() => v,
|
||||||
|
_= sleep(Duration::from_millis((timeout * 1000.0) as u64)) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
client/src/transports/ipc.rs
Normal file
23
client/src/transports/ipc.rs
Normal 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))
|
||||||
|
}
|
60
client/src/transports/mod.rs
Normal file
60
client/src/transports/mod.rs
Normal 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:?}"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
client/src/transports/stream_codec.rs
Normal file
61
client/src/transports/stream_codec.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
22
client/src/transports/tcp.rs
Normal file
22
client/src/transports/tcp.rs
Normal 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))
|
||||||
|
}
|
112
data/org.asamk.SignalCli.metainfo.xml
Normal file
112
data/org.asamk.SignalCli.metainfo.xml
Normal 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>
|
10
data/org.asamk.SignalCli.svg
Normal file
10
data/org.asamk.SignalCli.svg
Normal 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 |
46
data/signal-cli-socket.service
Normal file
46
data/signal-cli-socket.service
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Send secure messages to Signal clients
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
Requires=signal-cli-socket.socket
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
CapabilityBoundingSet=
|
||||||
|
Environment="SIGNAL_CLI_OPTS=-Xms2m"
|
||||||
|
# Update 'ReadWritePaths' if you change the config path here
|
||||||
|
ExecStart=%dir%/bin/signal-cli --config /var/lib/signal-cli daemon
|
||||||
|
LockPersonality=true
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateDevices=true
|
||||||
|
PrivateIPC=true
|
||||||
|
PrivateTmp=true
|
||||||
|
PrivateUsers=true
|
||||||
|
ProcSubset=pid
|
||||||
|
ProtectClock=true
|
||||||
|
ProtectControlGroups=true
|
||||||
|
ProtectHome=true
|
||||||
|
ProtectHostname=true
|
||||||
|
ProtectKernelLogs=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectProc=invisible
|
||||||
|
ProtectSystem=strict
|
||||||
|
# Profile pictures and attachments to upload must be located here for the service to access them
|
||||||
|
ReadWritePaths=/var/lib/signal-cli
|
||||||
|
RemoveIPC=true
|
||||||
|
RestrictAddressFamilies=AF_INET AF_INET6
|
||||||
|
RestrictNamespaces=true
|
||||||
|
RestrictRealtime=true
|
||||||
|
RestrictSUIDSGID=true
|
||||||
|
StandardInput=socket
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
SystemCallArchitectures=native
|
||||||
|
SystemCallFilter=~@debug @mount @obsolete @privileged @resources
|
||||||
|
UMask=0077
|
||||||
|
# Create the user and home directory with 'useradd -r -U -s /usr/sbin/nologin -m -b /var/lib signal-cli'
|
||||||
|
User=signal-cli
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
Also=signal-cli-socket.socket
|
||||||
|
WantedBy=default.target
|
13
data/signal-cli-socket.socket
Normal file
13
data/signal-cli-socket.socket
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Send secure messages to Signal clients
|
||||||
|
|
||||||
|
[Socket]
|
||||||
|
ListenStream=%t/signal-cli/socket
|
||||||
|
SocketUser=root
|
||||||
|
# Add yourself to the signal-cli group to talk with the service
|
||||||
|
# Run 'usermod -aG signal-cli yourusername'
|
||||||
|
SocketGroup=signal-cli
|
||||||
|
SocketMode=0660
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=sockets.target
|
|
@ -8,7 +8,7 @@ After=network-online.target
|
||||||
[Service]
|
[Service]
|
||||||
Type=dbus
|
Type=dbus
|
||||||
Environment="SIGNAL_CLI_OPTS=-Xms2m"
|
Environment="SIGNAL_CLI_OPTS=-Xms2m"
|
||||||
ExecStart=%dir%/bin/signal-cli -u %number% --config /var/lib/signal-cli daemon --system
|
ExecStart=%dir%/bin/signal-cli --config /var/lib/signal-cli daemon --dbus-system
|
||||||
User=signal-cli
|
User=signal-cli
|
||||||
BusName=org.asamk.Signal
|
BusName=org.asamk.Signal
|
||||||
|
|
1
data/signal-cli.sysusers.conf
Normal file
1
data/signal-cli.sysusers.conf
Normal file
|
@ -0,0 +1 @@
|
||||||
|
u signal-cli - "Signal messaging service" /var/lib/signal-cli
|
5
data/signal-cli.tmpfiles.conf
Normal file
5
data/signal-cli.tmpfiles.conf
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
d /var/lib/signal-cli 0755 signal-cli signal-cli -
|
||||||
|
d /var/lib/signal-cli/data 0700 signal-cli signal-cli -
|
||||||
|
d /var/lib/signal-cli/attachments 0750 signal-cli signal-cli -
|
||||||
|
d /var/lib/signal-cli/avatars 0750 signal-cli signal-cli -
|
||||||
|
d /var/lib/signal-cli/stickers 0750 signal-cli signal-cli -
|
|
@ -8,9 +8,9 @@ After=network-online.target
|
||||||
[Service]
|
[Service]
|
||||||
Type=dbus
|
Type=dbus
|
||||||
Environment="SIGNAL_CLI_OPTS=-Xms2m"
|
Environment="SIGNAL_CLI_OPTS=-Xms2m"
|
||||||
ExecStart=%dir%/bin/signal-cli -u %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
|
User=signal-cli
|
||||||
BusName=org.asamk.Signal
|
BusName=org.asamk.Signal
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
Alias=dbus-org.asamk.Signal.service
|
||||||
|
|
312
graalvm-config-dir/jni-config.json
Normal file
312
graalvm-config-dir/jni-config.json
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name":"[B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"[Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"[[B"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"com.sun.security.auth.module.UnixSystem",
|
||||||
|
"fields":[{"name":"gid"}, {"name":"groups"}, {"name":"uid"}, {"name":"username"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.lang.Boolean",
|
||||||
|
"methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.lang.Class",
|
||||||
|
"methods":[{"name":"getCanonicalName","parameterTypes":[] }, {"name":"getClassLoader","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.lang.ClassLoader",
|
||||||
|
"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",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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":"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":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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":"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":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.signal.libsignal.protocol.IdentityKeyPair",
|
||||||
|
"methods":[{"name":"serialize","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.signal.libsignal.protocol.InvalidKeyException",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.signal.libsignal.protocol.InvalidKeyIdException"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.signal.libsignal.protocol.InvalidMessageException",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.signal.libsignal.protocol.NoSessionException",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.signal.libsignal.protocol.SignalProtocolAddress",
|
||||||
|
"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"}],
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["long"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.signal.libsignal.protocol.groups.state.SenderKeyStore"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.signal.libsignal.protocol.logging.Log",
|
||||||
|
"methods":[{"name":"log","parameterTypes":["int","java.lang.String","java.lang.String"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.signal.libsignal.protocol.message.PlaintextContent",
|
||||||
|
"fields":[{"name":"unsafeHandle"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.signal.libsignal.protocol.message.PreKeySignalMessage",
|
||||||
|
"fields":[{"name":"unsafeHandle"}],
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["long"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.signal.libsignal.protocol.message.SenderKeyMessage",
|
||||||
|
"fields":[{"name":"unsafeHandle"}],
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["long"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.signal.libsignal.protocol.message.SignalMessage",
|
||||||
|
"fields":[{"name":"unsafeHandle"}],
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["long"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.signal.libsignal.protocol.state.IdentityKeyStore"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.signal.libsignal.protocol.state.IdentityKeyStore$Direction",
|
||||||
|
"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",
|
||||||
|
"fields":[{"name":"unsafeHandle"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.signal.libsignal.protocol.state.PreKeyStore"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.signal.libsignal.protocol.state.SessionRecord",
|
||||||
|
"fields":[{"name":"unsafeHandle"}],
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["long"] }, {"name":"<init>","parameterTypes":["byte[]"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.signal.libsignal.protocol.state.SessionStore"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.signal.libsignal.protocol.state.SignedPreKeyRecord",
|
||||||
|
"fields":[{"name":"unsafeHandle"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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"}],
|
||||||
|
"methods":[{"name":"xFunc","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.sqlite.Function$Aggregate",
|
||||||
|
"methods":[{"name":"clone","parameterTypes":[] }, {"name":"xFinal","parameterTypes":[] }, {"name":"xStep","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.sqlite.Function$Window",
|
||||||
|
"methods":[{"name":"xInverse","parameterTypes":[] }, {"name":"xValue","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.sqlite.ProgressHandler",
|
||||||
|
"methods":[{"name":"progress","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"org.sqlite.core.DB",
|
||||||
|
"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",
|
||||||
|
"methods":[{"name":"progress","parameterTypes":["int","int"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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"] }]
|
||||||
|
}
|
||||||
|
]
|
8
graalvm-config-dir/predefined-classes-config.json
Normal file
8
graalvm-config-dir/predefined-classes-config.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"type":"agent-extracted",
|
||||||
|
"classes":[
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
26
graalvm-config-dir/proxy-config.json
Normal file
26
graalvm-config-dir/proxy-config.json
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"interfaces":["java.sql.Connection"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"interfaces":["org.asamk.Signal"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"interfaces":["org.asamk.Signal$Configuration"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"interfaces":["org.asamk.Signal$Device"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"interfaces":["org.asamk.Signal$Group"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"interfaces":["org.asamk.Signal$Identity"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"interfaces":["org.asamk.SignalControl"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"interfaces":["org.freedesktop.dbus.interfaces.DBus"]
|
||||||
|
}
|
||||||
|
]
|
3277
graalvm-config-dir/reflect-config.json
Normal file
3277
graalvm-config-dir/reflect-config.json
Normal file
File diff suppressed because it is too large
Load diff
226
graalvm-config-dir/resource-config.json
Normal file
226
graalvm-config-dir/resource-config.json
Normal file
|
@ -0,0 +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/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":["", "de", "en", "und"]
|
||||||
|
}]
|
||||||
|
}
|
8
graalvm-config-dir/serialization-config.json
Normal file
8
graalvm-config-dir/serialization-config.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"types":[
|
||||||
|
],
|
||||||
|
"lambdaCapturingTypes":[
|
||||||
|
],
|
||||||
|
"proxies":[
|
||||||
|
]
|
||||||
|
}
|
17
gradle/libs.versions.toml
Normal file
17
gradle/libs.versions.toml
Normal 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"
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,5 +1,7 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
298
gradlew
vendored
298
gradlew
vendored
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright 2015 the original author or authors.
|
# Copyright © 2015 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -15,81 +15,115 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
##
|
#
|
||||||
## Gradle start up script for UN*X
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
##
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/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/.
|
||||||
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
# Resolve links: $0 may be a link
|
||||||
PRG="$0"
|
app_path=$0
|
||||||
# Need this for relative symlinks.
|
|
||||||
while [ -h "$PRG" ] ; do
|
# Need this for daisy-chained symlinks.
|
||||||
ls=`ls -ld "$PRG"`
|
while
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
[ -h "$app_path" ]
|
||||||
PRG="$link"
|
do
|
||||||
else
|
ls=$( ls -ld "$app_path" )
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
link=${ls#*' -> '}
|
||||||
fi
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
SAVED="`pwd`"
|
|
||||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
|
||||||
APP_HOME="`pwd -P`"
|
|
||||||
cd "$SAVED" >/dev/null
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
# This is normally unused
|
||||||
APP_BASE_NAME=`basename "$0"`
|
# 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.
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
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.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD=maximum
|
||||||
|
|
||||||
warn () {
|
warn () {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
die () {
|
die () {
|
||||||
echo
|
echo
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
exit 1
|
exit 1
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
# OS specific support (must be 'true' or 'false').
|
||||||
cygwin=false
|
cygwin=false
|
||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
nonstop=false
|
nonstop=false
|
||||||
case "`uname`" in
|
case "$( uname )" in #(
|
||||||
CYGWIN* )
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
cygwin=true
|
Darwin* ) darwin=true ;; #(
|
||||||
;;
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
Darwin* )
|
NONSTOP* ) nonstop=true ;;
|
||||||
darwin=true
|
|
||||||
;;
|
|
||||||
MINGW* )
|
|
||||||
msys=true
|
|
||||||
;;
|
|
||||||
NONSTOP* )
|
|
||||||
nonstop=true
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH="\\\"\\\""
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
else
|
else
|
||||||
JAVACMD="$JAVA_HOME/bin/java"
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
fi
|
fi
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
@ -98,88 +132,120 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD="java"
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
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
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
case $MAX_FD in #(
|
||||||
if [ $? -eq 0 ] ; then
|
max*)
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
# shellcheck disable=SC2039,SC3045
|
||||||
fi
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
ulimit -n $MAX_FD
|
warn "Could not query maximum file descriptor limit"
|
||||||
if [ $? -ne 0 ] ; then
|
esac
|
||||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
case $MAX_FD in #(
|
||||||
fi
|
'' | soft) :;; #(
|
||||||
else
|
*)
|
||||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
fi
|
# shellcheck disable=SC2039,SC3045
|
||||||
fi
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
# For Darwin, add options to specify how the application appears in the dock
|
|
||||||
if $darwin; then
|
|
||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
|
||||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
|
||||||
SEP=""
|
|
||||||
for dir in $ROOTDIRSRAW ; do
|
|
||||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
|
||||||
SEP="|"
|
|
||||||
done
|
|
||||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
|
||||||
# Add a user-defined pattern to the cygpath arguments
|
|
||||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
|
||||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
|
||||||
fi
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
i=0
|
|
||||||
for arg in "$@" ; do
|
|
||||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
|
||||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
|
||||||
|
|
||||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
|
||||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
|
||||||
else
|
|
||||||
eval `echo args$i`="\"$arg\""
|
|
||||||
fi
|
|
||||||
i=`expr $i + 1`
|
|
||||||
done
|
|
||||||
case $i in
|
|
||||||
0) set -- ;;
|
|
||||||
1) set -- "$args0" ;;
|
|
||||||
2) set -- "$args0" "$args1" ;;
|
|
||||||
3) set -- "$args0" "$args1" "$args2" ;;
|
|
||||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
|
||||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
|
||||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
|
||||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
|
||||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
|
||||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Escape application args
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
save () {
|
# * args from the command line
|
||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
# * the main class name
|
||||||
echo " "
|
# * -classpath
|
||||||
}
|
# * -D...appname settings
|
||||||
APP_ARGS=`save "$@"`
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# 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" \
|
||||||
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|
37
gradlew.bat
vendored
37
gradlew.bat
vendored
|
@ -13,6 +13,8 @@
|
||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
@ -26,6 +28,7 @@ if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
@ -56,32 +59,34 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@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
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
exit /b 1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
59
lib/build.gradle.kts
Normal file
59
lib/build.gradle.kts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
plugins {
|
||||||
|
`java-library`
|
||||||
|
`check-lib-versions`
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
|
|
||||||
|
toolchain {
|
||||||
|
languageVersion.set(JavaLanguageVersion.of(21))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val libsignalClientPath = project.findProperty("libsignal_client_path")?.toString()
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
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 {
|
||||||
|
implementation {
|
||||||
|
resolutionStrategy.failOnVersionConflict()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<AbstractArchiveTask>().configureEach {
|
||||||
|
isPreserveFileTimestamps = false
|
||||||
|
isReproducibleFileOrder = true
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<JavaCompile> {
|
||||||
|
options.encoding = "UTF-8"
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.jar {
|
||||||
|
manifest {
|
||||||
|
attributes("Automatic-Module-Name" to "org.asamk.signal.manager")
|
||||||
|
}
|
||||||
|
}
|
373
lib/src/main/java/org/asamk/signal/manager/Manager.java
Normal file
373
lib/src/main/java/org/asamk/signal/manager/Manager.java
Normal file
|
@ -0,0 +1,373 @@
|
||||||
|
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;
|
||||||
|
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.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.io.InputStream;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public interface Manager extends Closeable {
|
||||||
|
|
||||||
|
static boolean isValidNumber(final String e164Number, final String 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();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used for checking a set of phone numbers for registration on Signal
|
||||||
|
*
|
||||||
|
* @param numbers The set of phone number in question
|
||||||
|
* @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, RateLimitException;
|
||||||
|
|
||||||
|
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 NotPrimaryDeviceException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the user's profile.
|
||||||
|
* If a field is null, the previous value will be kept.
|
||||||
|
*/
|
||||||
|
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, CaptchaRejectedException;
|
||||||
|
|
||||||
|
List<Device> getLinkedDevices() throws IOException;
|
||||||
|
|
||||||
|
void removeLinkedDevices(int deviceId) throws IOException, NotPrimaryDeviceException;
|
||||||
|
|
||||||
|
void addDeviceLink(DeviceLinkUrl linkUri) throws IOException, InvalidDeviceLinkException, NotPrimaryDeviceException, DeviceLimitExceededException;
|
||||||
|
|
||||||
|
void setRegistrationLockPin(Optional<String> pin) throws IOException, NotPrimaryDeviceException;
|
||||||
|
|
||||||
|
List<Group> getGroups();
|
||||||
|
|
||||||
|
SendGroupMessageResults quitGroup(
|
||||||
|
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,
|
||||||
|
String avatarFile
|
||||||
|
) throws IOException, AttachmentInvalidException, UnregisteredRecipientException;
|
||||||
|
|
||||||
|
SendGroupMessageResults updateGroup(
|
||||||
|
final GroupId groupId,
|
||||||
|
final UpdateGroup updateGroup
|
||||||
|
) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException, GroupSendingNotAllowedException, UnregisteredRecipientException;
|
||||||
|
|
||||||
|
Pair<GroupId, SendGroupMessageResults> joinGroup(
|
||||||
|
GroupInviteLinkUrl inviteLinkUrl
|
||||||
|
) throws IOException, InactiveGroupLinkException, PendingAdminApprovalException;
|
||||||
|
|
||||||
|
SendMessageResults sendTypingMessage(
|
||||||
|
TypingAction action,
|
||||||
|
Set<RecipientIdentifier> recipients
|
||||||
|
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
|
||||||
|
|
||||||
|
SendMessageResults sendReadReceipt(RecipientIdentifier.Single sender, List<Long> messageIds);
|
||||||
|
|
||||||
|
SendMessageResults sendViewedReceipt(RecipientIdentifier.Single sender, List<Long> messageIds);
|
||||||
|
|
||||||
|
SendMessageResults sendMessage(
|
||||||
|
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
|
||||||
|
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException;
|
||||||
|
|
||||||
|
SendMessageResults sendMessageReaction(
|
||||||
|
String emoji,
|
||||||
|
boolean remove,
|
||||||
|
RecipientIdentifier.Single targetAuthor,
|
||||||
|
long targetSentTimestamp,
|
||||||
|
Set<RecipientIdentifier> recipients,
|
||||||
|
final boolean isStory
|
||||||
|
) throws IOException, NotAGroupMemberException, GroupNotFoundException, GroupSendingNotAllowedException, UnregisteredRecipientException;
|
||||||
|
|
||||||
|
SendMessageResults sendPaymentNotificationMessage(
|
||||||
|
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(
|
||||||
|
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
|
||||||
|
) throws NotPrimaryDeviceException, IOException, UnregisteredRecipientException;
|
||||||
|
|
||||||
|
void setGroupsBlocked(
|
||||||
|
Collection<GroupId> groupId,
|
||||||
|
boolean blocked
|
||||||
|
) throws GroupNotFoundException, IOException, NotPrimaryDeviceException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the expiration timer for a contact
|
||||||
|
*/
|
||||||
|
void setExpirationTimer(
|
||||||
|
RecipientIdentifier.Single recipient,
|
||||||
|
int messageExpirationTimer
|
||||||
|
) throws IOException, UnregisteredRecipientException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload the sticker pack from path.
|
||||||
|
*
|
||||||
|
* @param path Path can be a path to a manifest.json file or to a zip file that contains a manifest.json file
|
||||||
|
* @return if successful, returns the URL to install the sticker pack in the signal app
|
||||||
|
*/
|
||||||
|
StickerPackUrl uploadStickerPack(File path) throws IOException, StickerPackInvalidException;
|
||||||
|
|
||||||
|
void installStickerPack(StickerPackUrl url) throws IOException;
|
||||||
|
|
||||||
|
List<StickerPack> getStickerPacks();
|
||||||
|
|
||||||
|
void requestAllSyncData() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a handler to receive new messages.
|
||||||
|
* Will start receiving messages from server, if not already started.
|
||||||
|
*/
|
||||||
|
default void addReceiveHandler(ReceiveMessageHandler handler) {
|
||||||
|
addReceiveHandler(handler, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addReceiveHandler(ReceiveMessageHandler handler, final boolean isWeakListener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a handler to receive new messages.
|
||||||
|
* Will stop receiving messages from server, if this was the last registered receiver.
|
||||||
|
*/
|
||||||
|
void removeReceiveHandler(ReceiveMessageHandler handler);
|
||||||
|
|
||||||
|
boolean isReceiving();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receive new messages from server, returns if no new message arrive in a timespan of timeout.
|
||||||
|
*/
|
||||||
|
void receiveMessages(
|
||||||
|
Optional<Duration> timeout,
|
||||||
|
Optional<Integer> maxMessages,
|
||||||
|
ReceiveMessageHandler handler
|
||||||
|
) throws IOException, AlreadyReceivingException;
|
||||||
|
|
||||||
|
void stopReceiveMessages();
|
||||||
|
|
||||||
|
void setReceiveConfig(ReceiveConfig receiveConfig);
|
||||||
|
|
||||||
|
boolean isContactBlocked(RecipientIdentifier.Single recipient);
|
||||||
|
|
||||||
|
void sendContacts() throws IOException;
|
||||||
|
|
||||||
|
List<Recipient> getRecipients(
|
||||||
|
boolean onlyContacts,
|
||||||
|
Optional<Boolean> blocked,
|
||||||
|
Collection<RecipientIdentifier.Single> address,
|
||||||
|
Optional<String> name
|
||||||
|
);
|
||||||
|
|
||||||
|
String getContactOrProfileName(RecipientIdentifier.Single recipient);
|
||||||
|
|
||||||
|
Group getGroup(GroupId groupId);
|
||||||
|
|
||||||
|
List<Identity> getIdentities();
|
||||||
|
|
||||||
|
List<Identity> getIdentities(RecipientIdentifier.Single recipient);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trust this the identity with this fingerprint/safetyNumber
|
||||||
|
*
|
||||||
|
* @param recipient account of the identity
|
||||||
|
*/
|
||||||
|
boolean trustIdentityVerified(
|
||||||
|
RecipientIdentifier.Single recipient,
|
||||||
|
IdentityVerificationCode verificationCode
|
||||||
|
) throws UnregisteredRecipientException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trust all keys of this identity without verification
|
||||||
|
*
|
||||||
|
* @param recipient account of the identity
|
||||||
|
*/
|
||||||
|
boolean trustIdentityAllKeys(RecipientIdentifier.Single recipient) throws UnregisteredRecipientException;
|
||||||
|
|
||||||
|
void addAddressChangedListener(Runnable listener);
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
interface ReceiveMessageHandler {
|
||||||
|
|
||||||
|
ReceiveMessageHandler EMPTY = (envelope, e) -> {
|
||||||
|
};
|
||||||
|
|
||||||
|
void handleMessage(MessageEnvelope envelope, Throwable e);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package org.asamk.signal.manager;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.internal.LibSignalLogger;
|
||||||
|
|
||||||
|
public class ManagerLogger {
|
||||||
|
|
||||||
|
public static void initLogger() {
|
||||||
|
LibSignalLogger.initLogger();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package org.asamk.signal.manager;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public interface MultiAccountManager extends AutoCloseable {
|
||||||
|
|
||||||
|
List<String> getAccountNumbers();
|
||||||
|
|
||||||
|
List<Manager> getManagers();
|
||||||
|
|
||||||
|
void addOnManagerAddedHandler(Consumer<Manager> handler);
|
||||||
|
|
||||||
|
void addOnManagerRemovedHandler(Consumer<Manager> handler);
|
||||||
|
|
||||||
|
Manager getManager(String phoneNumber);
|
||||||
|
|
||||||
|
URI getNewProvisioningDeviceLinkUri() throws TimeoutException, IOException;
|
||||||
|
|
||||||
|
ProvisioningManager getProvisioningManagerFor(URI deviceLinkUri);
|
||||||
|
|
||||||
|
RegistrationManager getNewRegistrationManager(String account) throws IOException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void close();
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.asamk.signal.manager;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.api.UserAlreadyExistsException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
public interface ProvisioningManager {
|
||||||
|
|
||||||
|
URI getDeviceLinkUri() throws TimeoutException, IOException;
|
||||||
|
|
||||||
|
String finishDeviceLink(String deviceName) throws IOException, TimeoutException, UserAlreadyExistsException;
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
public interface RegistrationManager extends Closeable {
|
||||||
|
|
||||||
|
void register(
|
||||||
|
boolean voiceVerification,
|
||||||
|
String captcha,
|
||||||
|
final boolean forceRegister
|
||||||
|
) throws IOException, CaptchaRequiredException, NonNormalizedPhoneNumberException, RateLimitException, VerificationMethodNotAvailableException;
|
||||||
|
|
||||||
|
void verifyAccount(
|
||||||
|
String verificationCode,
|
||||||
|
String pin
|
||||||
|
) throws IOException, PinLockedException, IncorrectPinException, PinLockMissingException;
|
||||||
|
|
||||||
|
void deleteLocalAccountData() throws IOException;
|
||||||
|
|
||||||
|
boolean isRegistered();
|
||||||
|
}
|
8
lib/src/main/java/org/asamk/signal/manager/Settings.java
Normal file
8
lib/src/main/java/org/asamk/signal/manager/Settings.java
Normal 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);
|
||||||
|
}
|
|
@ -0,0 +1,208 @@
|
||||||
|
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.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.util.KeyUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.DeprecatedVersionException;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class SignalAccountFiles {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(MultiAccountManager.class);
|
||||||
|
|
||||||
|
private final PathConfig pathConfig;
|
||||||
|
private final ServiceEnvironment serviceEnvironment;
|
||||||
|
private final ServiceEnvironmentConfig serviceEnvironmentConfig;
|
||||||
|
private final String userAgent;
|
||||||
|
private final Settings settings;
|
||||||
|
private final AccountsStore accountsStore;
|
||||||
|
|
||||||
|
public SignalAccountFiles(
|
||||||
|
final File settingsPath,
|
||||||
|
final ServiceEnvironment serviceEnvironment,
|
||||||
|
final String userAgent,
|
||||||
|
final Settings settings
|
||||||
|
) throws IOException {
|
||||||
|
this.pathConfig = PathConfig.createDefault(settingsPath);
|
||||||
|
this.serviceEnvironment = serviceEnvironment;
|
||||||
|
this.serviceEnvironmentConfig = ServiceConfig.getServiceEnvironmentConfig(this.serviceEnvironment, userAgent);
|
||||||
|
this.userAgent = userAgent;
|
||||||
|
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, settings);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getAllLocalAccountNumbers() throws IOException {
|
||||||
|
return accountsStore.getAllNumbers();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultiAccountManager initMultiAccountManager() throws IOException, AccountCheckException {
|
||||||
|
final var managerPairs = accountsStore.getAllAccounts().parallelStream().map(a -> {
|
||||||
|
try {
|
||||||
|
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 (AccountCheckException | IOException e) {
|
||||||
|
logger.error("Failed to load {}: {} ({})", a.number(), e.getMessage(), e.getClass().getSimpleName());
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Manager initManager(String number) throws IOException, NotRegisteredException, AccountCheckException {
|
||||||
|
final var accountPath = accountsStore.getPathByNumber(number);
|
||||||
|
return this.initManager(number, accountPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Manager initManager(
|
||||||
|
String number,
|
||||||
|
String accountPath
|
||||||
|
) throws IOException, NotRegisteredException, AccountCheckException {
|
||||||
|
if (accountPath == null) {
|
||||||
|
throw new NotRegisteredException();
|
||||||
|
}
|
||||||
|
if (!SignalAccount.accountFileExists(pathConfig.dataPath(), accountPath)) {
|
||||||
|
throw new NotRegisteredException();
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!account.isRegistered()) {
|
||||||
|
account.close();
|
||||||
|
throw new NotRegisteredException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (account.getServiceEnvironment() != null && account.getServiceEnvironment() != serviceEnvironment) {
|
||||||
|
throw new IOException("Account is registered in another environment: " + account.getServiceEnvironment());
|
||||||
|
}
|
||||||
|
|
||||||
|
account.initDatabase();
|
||||||
|
|
||||||
|
final var manager = new ManagerImpl(account,
|
||||||
|
pathConfig,
|
||||||
|
new AccountFileUpdaterImpl(accountsStore, accountPath),
|
||||||
|
serviceEnvironmentConfig,
|
||||||
|
userAgent);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (account.getServiceEnvironment() == null) {
|
||||||
|
account.setServiceEnvironment(serviceEnvironment);
|
||||||
|
accountsStore.updateAccount(accountPath, account.getNumber(), account.getAci());
|
||||||
|
}
|
||||||
|
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProvisioningManager initProvisioningManager() {
|
||||||
|
return initProvisioningManager(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProvisioningManager initProvisioningManager(Consumer<Manager> newManagerListener) {
|
||||||
|
return new ProvisioningManagerImpl(pathConfig,
|
||||||
|
serviceEnvironmentConfig,
|
||||||
|
userAgent,
|
||||||
|
newManagerListener,
|
||||||
|
accountsStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegistrationManager initRegistrationManager(String number) throws IOException {
|
||||||
|
return initRegistrationManager(number, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegistrationManager initRegistrationManager(
|
||||||
|
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 profileKey = KeyUtils.createProfileKey();
|
||||||
|
var account = SignalAccount.create(pathConfig.dataPath(),
|
||||||
|
newAccountPath,
|
||||||
|
number,
|
||||||
|
serviceEnvironment,
|
||||||
|
aciIdentityKey,
|
||||||
|
pniIdentityKey,
|
||||||
|
profileKey,
|
||||||
|
settings);
|
||||||
|
account.initDatabase();
|
||||||
|
|
||||||
|
return new RegistrationManagerImpl(account,
|
||||||
|
pathConfig,
|
||||||
|
serviceEnvironmentConfig,
|
||||||
|
userAgent,
|
||||||
|
newManagerListener,
|
||||||
|
new AccountFileUpdaterImpl(accountsStore, newAccountPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
serviceEnvironmentConfig,
|
||||||
|
userAgent,
|
||||||
|
newManagerListener,
|
||||||
|
new AccountFileUpdaterImpl(accountsStore, accountPath));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package org.asamk.signal.manager.actions;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.helper.Context;
|
||||||
|
|
||||||
|
public interface HandleAction {
|
||||||
|
|
||||||
|
void execute(Context context) throws Throwable;
|
||||||
|
|
||||||
|
default void mergeOther(HandleAction action) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.asamk.signal.manager.actions;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.helper.Context;
|
||||||
|
|
||||||
|
public class RefreshPreKeysAction implements HandleAction {
|
||||||
|
|
||||||
|
private static final RefreshPreKeysAction INSTANCE = new RefreshPreKeysAction();
|
||||||
|
|
||||||
|
private RefreshPreKeysAction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RefreshPreKeysAction create() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Context context) throws Throwable {
|
||||||
|
context.getPreKeyHelper().refreshPreKeysIfNecessary();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package org.asamk.signal.manager.actions;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.helper.Context;
|
||||||
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
|
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||||
|
|
||||||
|
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, final ServiceId accountId) {
|
||||||
|
this.recipientId = recipientId;
|
||||||
|
this.serviceId = serviceId;
|
||||||
|
this.accountId = accountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Context context) throws Throwable {
|
||||||
|
context.getAccount().getAccountData(accountId).getSessionStore().archiveSessions(serviceId);
|
||||||
|
context.getSendHelper().sendNullMessage(recipientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
final RenewSessionAction that = (RenewSessionAction) o;
|
||||||
|
|
||||||
|
return recipientId.equals(that.recipientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return recipientId.hashCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package org.asamk.signal.manager.actions;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.helper.Context;
|
||||||
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
|
import org.asamk.signal.manager.storage.sendLog.MessageSendLogEntry;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class ResendMessageAction implements HandleAction {
|
||||||
|
|
||||||
|
private final RecipientId recipientId;
|
||||||
|
private final long timestamp;
|
||||||
|
private final MessageSendLogEntry messageSendLogEntry;
|
||||||
|
|
||||||
|
public ResendMessageAction(
|
||||||
|
final RecipientId recipientId,
|
||||||
|
final long timestamp,
|
||||||
|
final MessageSendLogEntry messageSendLogEntry
|
||||||
|
) {
|
||||||
|
this.recipientId = recipientId;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.messageSendLogEntry = messageSendLogEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Context context) throws Throwable {
|
||||||
|
context.getSendHelper().resendMessage(recipientId, timestamp, messageSendLogEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
final ResendMessageAction that = (ResendMessageAction) o;
|
||||||
|
return timestamp == that.timestamp
|
||||||
|
&& recipientId.equals(that.recipientId)
|
||||||
|
&& messageSendLogEntry.equals(that.messageSendLogEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(recipientId, timestamp, messageSendLogEntry);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.asamk.signal.manager.actions;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.helper.Context;
|
||||||
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
|
|
||||||
|
public class RetrieveProfileAction implements HandleAction {
|
||||||
|
|
||||||
|
private final RecipientId recipientId;
|
||||||
|
|
||||||
|
public RetrieveProfileAction(final RecipientId recipientId) {
|
||||||
|
this.recipientId = recipientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Context context) throws Throwable {
|
||||||
|
context.getProfileHelper().refreshRecipientProfile(recipientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
final RetrieveProfileAction that = (RetrieveProfileAction) o;
|
||||||
|
|
||||||
|
return recipientId.equals(that.recipientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return recipientId.hashCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package org.asamk.signal.manager.actions;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.api.GroupIdV1;
|
||||||
|
import org.asamk.signal.manager.helper.Context;
|
||||||
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
|
|
||||||
|
public class SendGroupInfoAction implements HandleAction {
|
||||||
|
|
||||||
|
private final RecipientId recipientId;
|
||||||
|
private final GroupIdV1 groupId;
|
||||||
|
|
||||||
|
public SendGroupInfoAction(final RecipientId recipientId, final GroupIdV1 groupId) {
|
||||||
|
this.recipientId = recipientId;
|
||||||
|
this.groupId = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Context context) throws Throwable {
|
||||||
|
context.getGroupHelper().sendGroupInfoMessage(groupId, recipientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
final var that = (SendGroupInfoAction) o;
|
||||||
|
|
||||||
|
if (!recipientId.equals(that.recipientId)) return false;
|
||||||
|
return groupId.equals(that.groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
var result = recipientId.hashCode();
|
||||||
|
result = 31 * result + groupId.hashCode();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package org.asamk.signal.manager.actions;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.api.GroupIdV1;
|
||||||
|
import org.asamk.signal.manager.helper.Context;
|
||||||
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
|
|
||||||
|
public class SendGroupInfoRequestAction implements HandleAction {
|
||||||
|
|
||||||
|
private final RecipientId recipientId;
|
||||||
|
private final GroupIdV1 groupId;
|
||||||
|
|
||||||
|
public SendGroupInfoRequestAction(final RecipientId recipientId, final GroupIdV1 groupId) {
|
||||||
|
this.recipientId = recipientId;
|
||||||
|
this.groupId = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Context context) throws Throwable {
|
||||||
|
context.getGroupHelper().sendGroupInfoRequest(groupId, recipientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
final var that = (SendGroupInfoRequestAction) o;
|
||||||
|
|
||||||
|
if (!recipientId.equals(that.recipientId)) return false;
|
||||||
|
return groupId.equals(that.groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
var result = recipientId.hashCode();
|
||||||
|
result = 31 * result + groupId.hashCode();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.asamk.signal.manager.actions;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.helper.Context;
|
||||||
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class SendProfileKeyAction implements HandleAction {
|
||||||
|
|
||||||
|
private final RecipientId recipientId;
|
||||||
|
|
||||||
|
public SendProfileKeyAction(final RecipientId recipientId) {
|
||||||
|
this.recipientId = recipientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Context context) throws Throwable {
|
||||||
|
context.getSendHelper().sendProfileKey(recipientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
final SendProfileKeyAction that = (SendProfileKeyAction) o;
|
||||||
|
return recipientId.equals(that.recipientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(recipientId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package org.asamk.signal.manager.actions;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.helper.Context;
|
||||||
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
|
import org.whispersystems.signalservice.api.messages.SignalServiceReceiptMessage;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class SendReceiptAction implements HandleAction {
|
||||||
|
|
||||||
|
private final RecipientId recipientId;
|
||||||
|
private final SignalServiceReceiptMessage.Type type;
|
||||||
|
private final List<Long> timestamps = new ArrayList<>();
|
||||||
|
|
||||||
|
public SendReceiptAction(
|
||||||
|
final RecipientId recipientId,
|
||||||
|
final SignalServiceReceiptMessage.Type type,
|
||||||
|
final long timestamp
|
||||||
|
) {
|
||||||
|
this.recipientId = recipientId;
|
||||||
|
this.type = type;
|
||||||
|
this.timestamps.add(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Context context) throws Throwable {
|
||||||
|
final var receiptMessage = new SignalServiceReceiptMessage(type, timestamps, System.currentTimeMillis());
|
||||||
|
|
||||||
|
context.getSendHelper().sendReceiptMessage(receiptMessage, recipientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mergeOther(final HandleAction action) {
|
||||||
|
if (action instanceof SendReceiptAction sendReceiptAction) {
|
||||||
|
this.timestamps.addAll(sendReceiptAction.timestamps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
final SendReceiptAction that = (SendReceiptAction) o;
|
||||||
|
// Using only recipientId and type here on purpose
|
||||||
|
return recipientId.equals(that.recipientId) && type == that.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
// Using only recipientId and type here on purpose
|
||||||
|
return Objects.hash(recipientId, type);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package org.asamk.signal.manager.actions;
|
||||||
|
|
||||||
|
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.internal.push.Envelope;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class SendRetryMessageRequestAction implements HandleAction {
|
||||||
|
|
||||||
|
private final RecipientId recipientId;
|
||||||
|
private final ProtocolException protocolException;
|
||||||
|
private final SignalServiceEnvelope envelope;
|
||||||
|
|
||||||
|
public SendRetryMessageRequestAction(
|
||||||
|
final RecipientId recipientId,
|
||||||
|
final ProtocolException protocolException,
|
||||||
|
final SignalServiceEnvelope envelope
|
||||||
|
) {
|
||||||
|
this.recipientId = recipientId;
|
||||||
|
this.protocolException = protocolException;
|
||||||
|
this.envelope = envelope;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Context context) throws Throwable {
|
||||||
|
int senderDevice = protocolException.getSenderDevice();
|
||||||
|
Optional<GroupId> groupId = protocolException.getGroupId().isPresent() ? Optional.of(GroupId.unknownVersion(
|
||||||
|
protocolException.getGroupId().get())) : Optional.empty();
|
||||||
|
|
||||||
|
byte[] originalContent;
|
||||||
|
int envelopeType;
|
||||||
|
if (protocolException.getUnidentifiedSenderMessageContent().isPresent()) {
|
||||||
|
final var messageContent = protocolException.getUnidentifiedSenderMessageContent().get();
|
||||||
|
originalContent = messageContent.getContent();
|
||||||
|
envelopeType = messageContent.getType();
|
||||||
|
} else {
|
||||||
|
originalContent = envelope.getContent();
|
||||||
|
envelopeType = envelope.getType() == null
|
||||||
|
? CiphertextMessage.WHISPER_TYPE
|
||||||
|
: envelopeTypeToCiphertextMessageType(envelope.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
DecryptionErrorMessage decryptionErrorMessage = DecryptionErrorMessage.forOriginalMessage(originalContent,
|
||||||
|
envelopeType,
|
||||||
|
envelope.getTimestamp(),
|
||||||
|
senderDevice);
|
||||||
|
|
||||||
|
context.getSendHelper().sendRetryReceipt(decryptionErrorMessage, recipientId, groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int envelopeTypeToCiphertextMessageType(int envelopeType) {
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
final SendRetryMessageRequestAction that = (SendRetryMessageRequestAction) o;
|
||||||
|
|
||||||
|
if (!recipientId.equals(that.recipientId)) return false;
|
||||||
|
if (!protocolException.equals(that.protocolException)) return false;
|
||||||
|
return envelope.equals(that.envelope);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = recipientId.hashCode();
|
||||||
|
result = 31 * result + protocolException.hashCode();
|
||||||
|
result = 31 * result + envelope.hashCode();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.asamk.signal.manager.actions;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.helper.Context;
|
||||||
|
|
||||||
|
public class SendSyncBlockedListAction implements HandleAction {
|
||||||
|
|
||||||
|
private static final SendSyncBlockedListAction INSTANCE = new SendSyncBlockedListAction();
|
||||||
|
|
||||||
|
private SendSyncBlockedListAction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SendSyncBlockedListAction create() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Context context) throws Throwable {
|
||||||
|
context.getSyncHelper().sendBlockedList();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.asamk.signal.manager.actions;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.helper.Context;
|
||||||
|
|
||||||
|
public class SendSyncConfigurationAction implements HandleAction {
|
||||||
|
|
||||||
|
private static final SendSyncConfigurationAction INSTANCE = new SendSyncConfigurationAction();
|
||||||
|
|
||||||
|
private SendSyncConfigurationAction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SendSyncConfigurationAction create() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Context context) throws Throwable {
|
||||||
|
context.getSyncHelper().sendConfigurationMessage();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.asamk.signal.manager.actions;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.helper.Context;
|
||||||
|
|
||||||
|
public class SendSyncContactsAction implements HandleAction {
|
||||||
|
|
||||||
|
private static final SendSyncContactsAction INSTANCE = new SendSyncContactsAction();
|
||||||
|
|
||||||
|
private SendSyncContactsAction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SendSyncContactsAction create() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Context context) throws Throwable {
|
||||||
|
context.getSyncHelper().sendContacts();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.asamk.signal.manager.actions;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.helper.Context;
|
||||||
|
|
||||||
|
public class SendSyncGroupsAction implements HandleAction {
|
||||||
|
|
||||||
|
private static final SendSyncGroupsAction INSTANCE = new SendSyncGroupsAction();
|
||||||
|
|
||||||
|
private SendSyncGroupsAction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SendSyncGroupsAction create() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Context context) throws Throwable {
|
||||||
|
context.getSyncHelper().sendGroups();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.asamk.signal.manager.actions;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.helper.Context;
|
||||||
|
|
||||||
|
public class SendSyncKeysAction implements HandleAction {
|
||||||
|
|
||||||
|
private static final SendSyncKeysAction INSTANCE = new SendSyncKeysAction();
|
||||||
|
|
||||||
|
private SendSyncKeysAction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SendSyncKeysAction create() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Context context) throws Throwable {
|
||||||
|
context.getSyncHelper().sendKeysMessage();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.asamk.signal.manager.actions;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.helper.Context;
|
||||||
|
|
||||||
|
public class UpdateAccountAttributesAction implements HandleAction {
|
||||||
|
|
||||||
|
private static final UpdateAccountAttributesAction INSTANCE = new UpdateAccountAttributesAction();
|
||||||
|
|
||||||
|
private UpdateAccountAttributesAction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UpdateAccountAttributesAction create() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Context context) throws Throwable {
|
||||||
|
context.getAccountHelper().updateAccountAttributes();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
public class AccountCheckException extends Exception {
|
||||||
|
|
||||||
|
public AccountCheckException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountCheckException(String message, Exception e) {
|
||||||
|
super(message, e);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.asamk.signal.manager;
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
public class AttachmentInvalidException extends Exception {
|
public class AttachmentInvalidException extends Exception {
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CaptchaRequiredException(final String message, final Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getNextAttemptTimestamp() {
|
||||||
|
return nextAttemptTimestamp;
|
||||||
|
}
|
||||||
|
}
|
24
lib/src/main/java/org/asamk/signal/manager/api/Color.java
Normal file
24
lib/src/main/java/org/asamk/signal/manager/api/Color.java
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
public record Color(int color) {
|
||||||
|
|
||||||
|
public int alpha() {
|
||||||
|
return color >>> 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int red() {
|
||||||
|
return (color >> 16) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int green() {
|
||||||
|
return (color >> 8) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int blue() {
|
||||||
|
return color & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toHexColor() {
|
||||||
|
return String.format("#%08x", color);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.storage.configuration.ConfigurationStore;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public record Configuration(
|
||||||
|
Optional<Boolean> readReceipts,
|
||||||
|
Optional<Boolean> unidentifiedDeliveryIndicators,
|
||||||
|
Optional<Boolean> typingIndicators,
|
||||||
|
Optional<Boolean> linkPreviews
|
||||||
|
) {
|
||||||
|
|
||||||
|
public static Configuration from(final ConfigurationStore configurationStore) {
|
||||||
|
return new Configuration(Optional.ofNullable(configurationStore.getReadReceipts()),
|
||||||
|
Optional.ofNullable(configurationStore.getUnidentifiedDeliveryIndicators()),
|
||||||
|
Optional.ofNullable(configurationStore.getTypingIndicators()),
|
||||||
|
Optional.ofNullable(configurationStore.getLinkPreviews()));
|
||||||
|
}
|
||||||
|
}
|
193
lib/src/main/java/org/asamk/signal/manager/api/Contact.java
Normal file
193
lib/src/main/java/org/asamk/signal/manager/api/Contact.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
public record Device(int id, String name, long created, long lastSeen, boolean isThisDevice) {}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.util.Utils;
|
||||||
|
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||||
|
import org.signal.libsignal.protocol.ecc.ECPublicKey;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import static org.whispersystems.signalservice.internal.util.Util.isEmpty;
|
||||||
|
|
||||||
|
public record DeviceLinkUrl(String deviceIdentifier, ECPublicKey deviceKey) {
|
||||||
|
|
||||||
|
public static DeviceLinkUrl parseDeviceLinkUri(URI linkUri) throws InvalidDeviceLinkException {
|
||||||
|
final var rawQuery = linkUri.getRawQuery();
|
||||||
|
if (isEmpty(rawQuery)) {
|
||||||
|
throw new RuntimeException("Invalid device link uri");
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = Utils.getQueryMap(rawQuery);
|
||||||
|
var deviceIdentifier = query.get("uuid");
|
||||||
|
var publicKeyEncoded = query.get("pub_key");
|
||||||
|
|
||||||
|
if (isEmpty(deviceIdentifier) || isEmpty(publicKeyEncoded)) {
|
||||||
|
throw new InvalidDeviceLinkException("Invalid device link uri");
|
||||||
|
}
|
||||||
|
|
||||||
|
final byte[] publicKeyBytes;
|
||||||
|
try {
|
||||||
|
publicKeyBytes = Base64.getDecoder().decode(publicKeyEncoded);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new InvalidDeviceLinkException("Invalid device link uri", e);
|
||||||
|
}
|
||||||
|
ECPublicKey deviceKey;
|
||||||
|
try {
|
||||||
|
deviceKey = new ECPublicKey(publicKeyBytes);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
throw new InvalidDeviceLinkException("Invalid device link", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DeviceLinkUrl(deviceIdentifier, deviceKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public URI createDeviceLinkUri() {
|
||||||
|
final var deviceKeyString = Base64.getEncoder().encodeToString(deviceKey.serialize()).replace("=", "");
|
||||||
|
try {
|
||||||
|
return new URI("sgnl://linkdevice?uuid="
|
||||||
|
+ URLEncoder.encode(deviceIdentifier, StandardCharsets.UTF_8)
|
||||||
|
+ "&pub_key="
|
||||||
|
+ URLEncoder.encode(deviceKeyString, StandardCharsets.UTF_8));
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
lib/src/main/java/org/asamk/signal/manager/api/Group.java
Normal file
71
lib/src/main/java/org/asamk/signal/manager/api/Group.java
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
import org.asamk.signal.manager.helper.RecipientAddressResolver;
|
||||||
|
import org.asamk.signal.manager.storage.groups.GroupInfo;
|
||||||
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public record Group(
|
||||||
|
GroupId groupId,
|
||||||
|
String title,
|
||||||
|
String description,
|
||||||
|
GroupInviteLinkUrl groupInviteLinkUrl,
|
||||||
|
Set<RecipientAddress> members,
|
||||||
|
Set<RecipientAddress> pendingMembers,
|
||||||
|
Set<RecipientAddress> requestingMembers,
|
||||||
|
Set<RecipientAddress> adminMembers,
|
||||||
|
Set<RecipientAddress> bannedMembers,
|
||||||
|
boolean isBlocked,
|
||||||
|
int messageExpirationTimer,
|
||||||
|
GroupPermission permissionAddMember,
|
||||||
|
GroupPermission permissionEditDetails,
|
||||||
|
GroupPermission permissionSendMessage,
|
||||||
|
boolean isMember,
|
||||||
|
boolean isAdmin
|
||||||
|
) {
|
||||||
|
|
||||||
|
public static Group from(
|
||||||
|
final GroupInfo groupInfo,
|
||||||
|
final RecipientAddressResolver recipientStore,
|
||||||
|
final RecipientId selfRecipientId
|
||||||
|
) {
|
||||||
|
return new Group(groupInfo.getGroupId(),
|
||||||
|
groupInfo.getTitle(),
|
||||||
|
groupInfo.getDescription(),
|
||||||
|
groupInfo.getGroupInviteLink(),
|
||||||
|
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(),
|
||||||
|
groupInfo.getPermissionAddMember(),
|
||||||
|
groupInfo.getPermissionEditDetails(),
|
||||||
|
groupInfo.getPermissionSendMessage(),
|
||||||
|
groupInfo.isMember(selfRecipientId),
|
||||||
|
groupInfo.isAdmin(selfRecipientId));
|
||||||
|
}
|
||||||
|
}
|
62
lib/src/main/java/org/asamk/signal/manager/api/GroupId.java
Normal file
62
lib/src/main/java/org/asamk/signal/manager/api/GroupId.java
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
public abstract sealed class GroupId permits GroupIdV1, GroupIdV2 {
|
||||||
|
|
||||||
|
private final byte[] id;
|
||||||
|
|
||||||
|
public static GroupIdV1 v1(byte[] id) {
|
||||||
|
return new GroupIdV1(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GroupIdV2 v2(byte[] id) {
|
||||||
|
return new GroupIdV2(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GroupId unknownVersion(byte[] id) {
|
||||||
|
if (id.length == 16) {
|
||||||
|
return new GroupIdV1(id);
|
||||||
|
} else if (id.length == 32) {
|
||||||
|
return new GroupIdV2(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AssertionError("Invalid group id of size " + id.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GroupId fromBase64(String id) throws GroupIdFormatException {
|
||||||
|
try {
|
||||||
|
return unknownVersion(java.util.Base64.getDecoder().decode(id));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new GroupIdFormatException(id, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected GroupId(final byte[] id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] serialize() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toBase64() {
|
||||||
|
return Base64.getEncoder().encodeToString(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
final var groupId = (GroupId) o;
|
||||||
|
|
||||||
|
return Arrays.equals(id, groupId.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Arrays.hashCode(id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
public class GroupIdFormatException extends Exception {
|
||||||
|
|
||||||
|
public GroupIdFormatException(String groupId, Throwable e) {
|
||||||
|
super("Failed to decode groupId (must be base64) \"" + groupId + "\": " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import static org.asamk.signal.manager.util.KeyUtils.getSecretBytes;
|
||||||
|
|
||||||
|
public final class GroupIdV1 extends GroupId {
|
||||||
|
|
||||||
|
public static GroupIdV1 createRandom() {
|
||||||
|
return new GroupIdV1(getSecretBytes(16));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GroupIdV1 fromBase64(String groupId) {
|
||||||
|
return new GroupIdV1(Base64.getDecoder().decode(groupId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public GroupIdV1(final byte[] id) {
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
public final class GroupIdV2 extends GroupId {
|
||||||
|
|
||||||
|
public static GroupIdV2 fromBase64(String groupId) {
|
||||||
|
return new GroupIdV2(Base64.getDecoder().decode(groupId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public GroupIdV2(final byte[] id) {
|
||||||
|
super(id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
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 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";
|
||||||
|
private static final String GROUP_URL_PREFIX = "https://" + GROUP_URL_HOST + "/#";
|
||||||
|
|
||||||
|
private final GroupMasterKey groupMasterKey;
|
||||||
|
private final GroupLinkPassword password;
|
||||||
|
private final String url;
|
||||||
|
|
||||||
|
public static GroupInviteLinkUrl forGroup(GroupMasterKey groupMasterKey, DecryptedGroup group) {
|
||||||
|
return new GroupInviteLinkUrl(groupMasterKey,
|
||||||
|
GroupLinkPassword.fromBytes(group.inviteLinkPassword.toByteArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null iff not a group url.
|
||||||
|
* @throws InvalidGroupLinkException If group url, but cannot be parsed.
|
||||||
|
*/
|
||||||
|
public static GroupInviteLinkUrl fromUri(String urlString) throws InvalidGroupLinkException, UnknownGroupLinkVersionException {
|
||||||
|
var uri = getGroupUrl(urlString);
|
||||||
|
|
||||||
|
if (uri == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!"/".equals(uri.getPath()) && !uri.getPath().isEmpty()) {
|
||||||
|
throw new InvalidGroupLinkException("No path was expected in uri");
|
||||||
|
}
|
||||||
|
|
||||||
|
var encoding = uri.getFragment();
|
||||||
|
|
||||||
|
if (encoding == null || encoding.isEmpty()) {
|
||||||
|
throw new InvalidGroupLinkException("No reference was in the uri");
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = Base64.decode(encoding);
|
||||||
|
GroupInviteLink groupInviteLink = GroupInviteLink.ADAPTER.decode(bytes);
|
||||||
|
|
||||||
|
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);
|
||||||
|
} else {
|
||||||
|
throw new UnknownGroupLinkVersionException("Url contains no known group link content");
|
||||||
|
}
|
||||||
|
} catch (InvalidInputException | IOException e) {
|
||||||
|
throw new InvalidGroupLinkException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@link URI} if the host name matches.
|
||||||
|
*/
|
||||||
|
private static URI getGroupUrl(String urlString) {
|
||||||
|
try {
|
||||||
|
var url = new URI(urlString);
|
||||||
|
|
||||||
|
if (!"https".equalsIgnoreCase(url.getScheme()) && !"sgnl".equalsIgnoreCase(url.getScheme())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GROUP_URL_HOST.equalsIgnoreCase(url.getHost()) ? url : null;
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private GroupInviteLinkUrl(GroupMasterKey groupMasterKey, GroupLinkPassword password) {
|
||||||
|
this.groupMasterKey = groupMasterKey;
|
||||||
|
this.password = password;
|
||||||
|
this.url = createUrl(groupMasterKey, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String createUrl(GroupMasterKey groupMasterKey, GroupLinkPassword password) {
|
||||||
|
var groupInviteLink = new GroupInviteLink.Builder().v1Contents(new GroupInviteLink.GroupInviteLinkContentsV1.Builder().groupMasterKey(
|
||||||
|
ByteString.of(groupMasterKey.serialize()))
|
||||||
|
.inviteLinkPassword(ByteString.of(password.serialize()))
|
||||||
|
.build()).build();
|
||||||
|
|
||||||
|
var encoding = Base64.encodeUrlSafeWithoutPadding(groupInviteLink.encode());
|
||||||
|
|
||||||
|
return GROUP_URL_PREFIX + encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GroupMasterKey getGroupMasterKey() {
|
||||||
|
return groupMasterKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GroupLinkPassword getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class InvalidGroupLinkException extends Exception {
|
||||||
|
|
||||||
|
public InvalidGroupLinkException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidGroupLinkException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class UnknownGroupLinkVersionException extends Exception {
|
||||||
|
|
||||||
|
public UnknownGroupLinkVersionException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
public enum GroupLinkState {
|
||||||
|
ENABLED,
|
||||||
|
ENABLED_WITH_APPROVAL,
|
||||||
|
DISABLED,
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
public class GroupNotFoundException extends Exception {
|
||||||
|
|
||||||
|
public GroupNotFoundException(GroupId groupId) {
|
||||||
|
super("Group not found: " + groupId.toBase64());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
public enum GroupPermission {
|
||||||
|
EVERY_MEMBER,
|
||||||
|
ONLY_ADMINS,
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
public class GroupSendingNotAllowedException extends Exception {
|
||||||
|
|
||||||
|
public GroupSendingNotAllowedException(GroupId groupId, String groupName) {
|
||||||
|
super("User is not allowed to send message to group: " + groupName + " (" + groupId.toBase64() + ")");
|
||||||
|
}
|
||||||
|
}
|
10
lib/src/main/java/org/asamk/signal/manager/api/Identity.java
Normal file
10
lib/src/main/java/org/asamk/signal/manager/api/Identity.java
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
public record Identity(
|
||||||
|
RecipientAddress recipient,
|
||||||
|
byte[] fingerprint,
|
||||||
|
String safetyNumber,
|
||||||
|
byte[] scannableSafetyNumber,
|
||||||
|
TrustLevel trustLevel,
|
||||||
|
long dateAddedTimestamp
|
||||||
|
) {}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
public class InactiveGroupLinkException extends Exception {
|
||||||
|
|
||||||
|
public InactiveGroupLinkException(final String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InactiveGroupLinkException(final String message, final Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
public class IncorrectPinException extends Exception {
|
||||||
|
|
||||||
|
private final int triesRemaining;
|
||||||
|
|
||||||
|
public IncorrectPinException(int triesRemaining) {
|
||||||
|
this.triesRemaining = triesRemaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTriesRemaining() {
|
||||||
|
return triesRemaining;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
public class InvalidDeviceLinkException extends Exception {
|
||||||
|
|
||||||
|
public InvalidDeviceLinkException(final String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidDeviceLinkException(final String message, final Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
public class InvalidStickerException extends Exception {
|
||||||
|
|
||||||
|
public InvalidStickerException(final String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidStickerException(final String message, final Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
public class LastGroupAdminException extends Exception {
|
||||||
|
|
||||||
|
public LastGroupAdminException(GroupId groupId, String groupName) {
|
||||||
|
super("User is last admin in group: " + groupName + " (" + groupId.toBase64() + ")");
|
||||||
|
}
|
||||||
|
}
|
37
lib/src/main/java/org/asamk/signal/manager/api/Message.java
Normal file
37
lib/src/main/java/org/asamk/signal/manager/api/Message.java
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package org.asamk.signal.manager.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
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) {}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue