mirror of
https://github.com/chatmail/core.git
synced 2026-04-02 05:22:14 +03:00
Compare commits
942 Commits
py-1.96.0
...
test_marks
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdf599ef95 | ||
|
|
4f25edb1a1 | ||
|
|
ded1634b7d | ||
|
|
635c73ffc6 | ||
|
|
fcc1fe73be | ||
|
|
fa278d50f7 | ||
|
|
2f02be4c64 | ||
|
|
7add1c116c | ||
|
|
124a0e90e1 | ||
|
|
1716cdf51c | ||
|
|
3fdcffb314 | ||
|
|
f033aae25c | ||
|
|
c42d942460 | ||
|
|
0ba8201797 | ||
|
|
87252ab053 | ||
|
|
53eec521dc | ||
|
|
238570a7b9 | ||
|
|
043ae48806 | ||
|
|
fb88f2e6ab | ||
|
|
5db867cd1b | ||
|
|
ec00c160c6 | ||
|
|
616eabc613 | ||
|
|
89b32e02c5 | ||
|
|
e985588c6c | ||
|
|
7ec3a1a9a2 | ||
|
|
19fa86b276 | ||
|
|
c4657991c8 | ||
|
|
484aebdb16 | ||
|
|
9c15cd5c8f | ||
|
|
8302d22622 | ||
|
|
034cde9289 | ||
|
|
02455d8485 | ||
|
|
35f50a8965 | ||
|
|
e04efdbd94 | ||
|
|
57445eedb1 | ||
|
|
a501f10756 | ||
|
|
5d80d4788c | ||
|
|
0c02886005 | ||
|
|
24856f3050 | ||
|
|
8e6434068e | ||
|
|
800d2b14a5 | ||
|
|
3a861d2f84 | ||
|
|
4ba00f7440 | ||
|
|
40fc61da4f | ||
|
|
eb0f896d57 | ||
|
|
71bb89fac1 | ||
|
|
b89199db54 | ||
|
|
e39429c2e3 | ||
|
|
17de3d3236 | ||
|
|
3177f9967d | ||
|
|
81418d8ee5 | ||
|
|
a2e7d914a0 | ||
|
|
4bf38c0e29 | ||
|
|
0079cd4766 | ||
|
|
2c3b2b8c2d | ||
|
|
52fa58a3ce | ||
|
|
32a7e5ed82 | ||
|
|
097113f01e | ||
|
|
1d42e4743f | ||
|
|
5ecdea47db | ||
|
|
5b92b6355e | ||
|
|
5eb7206b2d | ||
|
|
a566fd6301 | ||
|
|
3eadc86217 | ||
|
|
0a65081db0 | ||
|
|
dd57854ee3 | ||
|
|
b83b9db712 | ||
|
|
a59e72e7d8 | ||
|
|
fd358617f5 | ||
|
|
b26a351786 | ||
|
|
a32a3b8cca | ||
|
|
575b43d9a0 | ||
|
|
6c5654f584 | ||
|
|
0a5542a698 | ||
|
|
518bd19e96 | ||
|
|
edcc199461 | ||
|
|
961e3ad7e2 | ||
|
|
7a49e9401f | ||
|
|
3701936129 | ||
|
|
c02686b56e | ||
|
|
9a7ff9d2b1 | ||
|
|
f024909611 | ||
|
|
8db64726ea | ||
|
|
56f6d6849e | ||
|
|
cdd696db95 | ||
|
|
cbc18ee5a4 | ||
|
|
6b67f8bcfd | ||
|
|
14521cfc2d | ||
|
|
6fbcf67190 | ||
|
|
73e7ee5c13 | ||
|
|
90d2333818 | ||
|
|
8d80aea5c8 | ||
|
|
8936e9a416 | ||
|
|
0afd3d595f | ||
|
|
474faefca8 | ||
|
|
30fef395b4 | ||
|
|
5805f99acd | ||
|
|
5e4807b7ac | ||
|
|
11ca698139 | ||
|
|
b14e59c5f3 | ||
|
|
4d620afdb8 | ||
|
|
2bd7781276 | ||
|
|
a432303576 | ||
|
|
b7e939e4d9 | ||
|
|
2151a24b7f | ||
|
|
4411b883d7 | ||
|
|
7d3fcd9a3c | ||
|
|
0ac61690cf | ||
|
|
28d9bec0b4 | ||
|
|
a853b8283a | ||
|
|
6a394a0dc8 | ||
|
|
05e50ea787 | ||
|
|
02afacf989 | ||
|
|
c7de4f66e7 | ||
|
|
00791157e2 | ||
|
|
9e03f26992 | ||
|
|
de391155b1 | ||
|
|
db28c1bdc4 | ||
|
|
d71bf1c4be | ||
|
|
ef1970b742 | ||
|
|
4bb131e7e7 | ||
|
|
c9b8c5079b | ||
|
|
eec5ae96e8 | ||
|
|
4b94eadf5e | ||
|
|
52a1886937 | ||
|
|
9767f51c3d | ||
|
|
6674b888cc | ||
|
|
a5e6bd3e8e | ||
|
|
b6c24932a7 | ||
|
|
e39011a43b | ||
|
|
8b8dcf61ef | ||
|
|
3df5e6e9d3 | ||
|
|
fe60b2dd2d | ||
|
|
260dbbd36f | ||
|
|
7e5a8714a0 | ||
|
|
627cf20074 | ||
|
|
d73d56c399 | ||
|
|
731e90f0d5 | ||
|
|
e0a6c2ef54 | ||
|
|
c38ae31f31 | ||
|
|
c5408e0561 | ||
|
|
c1a2df91ac | ||
|
|
da85c2412e | ||
|
|
f5c334a1e4 | ||
|
|
3453aac27e | ||
|
|
d108f9b3e3 | ||
|
|
9c48bf9d13 | ||
|
|
e3014a349c | ||
|
|
04fa80b3bd | ||
|
|
d7c8fc624a | ||
|
|
9d88ef069e | ||
|
|
155dff2813 | ||
|
|
38d4ea8514 | ||
|
|
6f24874eb8 | ||
|
|
2d20812652 | ||
|
|
e866053070 | ||
|
|
81bacf9038 | ||
|
|
4e166b1b4a | ||
|
|
5762fbb9a7 | ||
|
|
2dc04220b8 | ||
|
|
b149df1993 | ||
|
|
e767d84bea | ||
|
|
fc019de18c | ||
|
|
c79ded1406 | ||
|
|
d1d43e889a | ||
|
|
7bdf79dee5 | ||
|
|
a6b2c25d42 | ||
|
|
210ec79872 | ||
|
|
9f81299de0 | ||
|
|
f0a2ca7815 | ||
|
|
50d83ff063 | ||
|
|
a2f1df052b | ||
|
|
0086232bbb | ||
|
|
8e9bb8b06e | ||
|
|
7e132b5383 | ||
|
|
8177070673 | ||
|
|
247bf5865d | ||
|
|
ad3c7136ec | ||
|
|
79026f93b6 | ||
|
|
eace8c5fee | ||
|
|
f8e86f4503 | ||
|
|
d1923d68a5 | ||
|
|
89696582ad | ||
|
|
992a6cbfc2 | ||
|
|
1450bf5483 | ||
|
|
9f804c379c | ||
|
|
10e8bcb73e | ||
|
|
7f2ccfb168 | ||
|
|
3fc67de35e | ||
|
|
8f0d07b93c | ||
|
|
0890b669fa | ||
|
|
c02c5ffe2c | ||
|
|
064f806d90 | ||
|
|
b6336ce7e9 | ||
|
|
aeadbb35f3 | ||
|
|
45817fcacd | ||
|
|
c8ce4ce5aa | ||
|
|
75670134d8 | ||
|
|
e3eb35e160 | ||
|
|
08c9deee04 | ||
|
|
a2e088b415 | ||
|
|
7af1a19491 | ||
|
|
d0a7e5f1b7 | ||
|
|
a82b09bfc2 | ||
|
|
e9668b3cfa | ||
|
|
bc9e347a80 | ||
|
|
b5187661ee | ||
|
|
059c673d00 | ||
|
|
76514178b6 | ||
|
|
0d7a3fe552 | ||
|
|
2cc85e6637 | ||
|
|
ade3d0d4eb | ||
|
|
ee81d61988 | ||
|
|
880ee63fed | ||
|
|
b0f77f7a33 | ||
|
|
cff4a9dce3 | ||
|
|
5dc20a5b98 | ||
|
|
0e06da22df | ||
|
|
5833a9b347 | ||
|
|
0ef8d57881 | ||
|
|
9e28ee95e5 | ||
|
|
fc64c33368 | ||
|
|
7c099c19c8 | ||
|
|
1b39be8a42 | ||
|
|
adb5bc77c4 | ||
|
|
c7b7fbaf78 | ||
|
|
42a18d4d0d | ||
|
|
d3f4654d4b | ||
|
|
999a9550f5 | ||
|
|
7f5217fc87 | ||
|
|
df96fbdcef | ||
|
|
edec80a917 | ||
|
|
439c57e3ac | ||
|
|
7bfff6c87c | ||
|
|
57f221dcc9 | ||
|
|
79212bee13 | ||
|
|
178e67a262 | ||
|
|
877b3551ae | ||
|
|
a616c69f9a | ||
|
|
df1c1addfb | ||
|
|
45abaff275 | ||
|
|
15c9efaa95 | ||
|
|
e86fbf5855 | ||
|
|
38a62d92ba | ||
|
|
c01a2f2c24 | ||
|
|
17acbca576 | ||
|
|
05a274a5f3 | ||
|
|
f07206bd6c | ||
|
|
92c7cc40d4 | ||
|
|
710cec1beb | ||
|
|
56d10f7c42 | ||
|
|
ef03a33b29 | ||
|
|
3b5227c42a | ||
|
|
604c4fcb71 | ||
|
|
4790ad0478 | ||
|
|
a1e19e2c41 | ||
|
|
eaa2ef5a44 | ||
|
|
2eeacb0f8a | ||
|
|
b920db12c7 | ||
|
|
73b90eee3e | ||
|
|
4637a28bf6 | ||
|
|
d0638c1542 | ||
|
|
788d3125a3 | ||
|
|
3c4ffc3550 | ||
|
|
840497d356 | ||
|
|
0dd87b0bae | ||
|
|
ada858f439 | ||
|
|
e2151e26ee | ||
|
|
446214fd7b | ||
|
|
9389e11007 | ||
|
|
5bdd49b451 | ||
|
|
42a7e91f05 | ||
|
|
44953d6bcc | ||
|
|
10066b2bc7 | ||
|
|
609fc67f0d | ||
|
|
641d102aba | ||
|
|
f65e1c1587 | ||
|
|
626ec5e793 | ||
|
|
85517abf58 | ||
|
|
75f65b06e8 | ||
|
|
f2b05ccc29 | ||
|
|
e88f21c010 | ||
|
|
ed8e2c4818 | ||
|
|
48fee4fc92 | ||
|
|
7586bcf45e | ||
|
|
870527de1e | ||
|
|
a34aeb240a | ||
|
|
6ee165fddc | ||
|
|
f2570945c6 | ||
|
|
8072f78058 | ||
|
|
8ae0ee5a67 | ||
|
|
a75d2b1c80 | ||
|
|
c48c2af7a1 | ||
|
|
490a14c5ef | ||
|
|
c9db41a7f6 | ||
|
|
dcce6ef50b | ||
|
|
7cf0820d2b | ||
|
|
0bae3caaff | ||
|
|
bca0b256c9 | ||
|
|
a53d30c459 | ||
|
|
7a9f497aa7 | ||
|
|
7a6bfae93b | ||
|
|
f9f9bc3efb | ||
|
|
904990bf91 | ||
|
|
b2266ffca1 | ||
|
|
bb9a3d4b8e | ||
|
|
ae19c9b331 | ||
|
|
7d2cca8633 | ||
|
|
c52b48b0f5 | ||
|
|
893794f4e7 | ||
|
|
032da4fb1a | ||
|
|
0de5125de8 | ||
|
|
cd3f1fe874 | ||
|
|
4f68e94fb3 | ||
|
|
b3ecbbc8b3 | ||
|
|
01653a881a | ||
|
|
e021a59b87 | ||
|
|
e565e19b42 | ||
|
|
41319c85c7 | ||
|
|
daf56804a5 | ||
|
|
6f7a43804d | ||
|
|
0ca76d36ef | ||
|
|
ec5789997a | ||
|
|
7a0d61bbb0 | ||
|
|
243f28f8a5 | ||
|
|
78577594d0 | ||
|
|
d3e2f38da0 | ||
|
|
05f0fe0a88 | ||
|
|
e11d7c0444 | ||
|
|
1c2461974d | ||
|
|
2a754744fe | ||
|
|
b413593c43 | ||
|
|
c73edd7e21 | ||
|
|
a34a69d8e4 | ||
|
|
020a9d33f6 | ||
|
|
19f6f89312 | ||
|
|
d56e05a11a | ||
|
|
c379a4e5a7 | ||
|
|
44c1efe4e4 | ||
|
|
ff0d675082 | ||
|
|
e1087b4145 | ||
|
|
a203cde400 | ||
|
|
00c14dd9f6 | ||
|
|
71d9716117 | ||
|
|
2e4f63a290 | ||
|
|
9dee725895 | ||
|
|
267263dab7 | ||
|
|
1d81457f38 | ||
|
|
0885cad089 | ||
|
|
19d7632be0 | ||
|
|
323535584b | ||
|
|
852adbe514 | ||
|
|
2a5fa9a0d3 | ||
|
|
4c78553d90 | ||
|
|
bb702a9342 | ||
|
|
6c8368fa23 | ||
|
|
1c875209f7 | ||
|
|
82da09760c | ||
|
|
ef44aa0bd0 | ||
|
|
af5a3235fd | ||
|
|
07c510c178 | ||
|
|
c367bb63d1 | ||
|
|
819d658531 | ||
|
|
48f098482e | ||
|
|
1a49bc85b8 | ||
|
|
51ee564246 | ||
|
|
7f313c803e | ||
|
|
0d7c33b1a9 | ||
|
|
14f3abb51e | ||
|
|
46143ac54f | ||
|
|
6a30c0a997 | ||
|
|
d1702e3081 | ||
|
|
fa198c3b5e | ||
|
|
151b34ea79 | ||
|
|
3e8687e464 | ||
|
|
a31ae5297a | ||
|
|
e7792a0c65 | ||
|
|
3c32de1859 | ||
|
|
6a3fe3db92 | ||
|
|
ac048c154d | ||
|
|
c9b2ad4ffa | ||
|
|
3f51a8ffc2 | ||
|
|
2129b2b7a0 | ||
|
|
386b5bb848 | ||
|
|
d8bd189175 | ||
|
|
3734fc25a7 | ||
|
|
817760a6ef | ||
|
|
315e944b69 | ||
|
|
48722a22c9 | ||
|
|
a8f018a208 | ||
|
|
05ddc13054 | ||
|
|
716504b833 | ||
|
|
187861c3b2 | ||
|
|
fa70d8da09 | ||
|
|
0b075ac762 | ||
|
|
cd293e6f49 | ||
|
|
a6c889ed5e | ||
|
|
ca1533b0e4 | ||
|
|
3267596a30 | ||
|
|
5f29b93970 | ||
|
|
d178c4a91a | ||
|
|
ff63ce0630 | ||
|
|
757b77786a | ||
|
|
7a78449950 | ||
|
|
7b44b26e9e | ||
|
|
3f5da7357f | ||
|
|
2a6a21c33a | ||
|
|
8ac972856c | ||
|
|
1f49fcc777 | ||
|
|
f2f5bfd17c | ||
|
|
059af398eb | ||
|
|
6044e5961b | ||
|
|
59f5cbe6f1 | ||
|
|
72e004c12b | ||
|
|
52a4b0c2b8 | ||
|
|
74abb82de2 | ||
|
|
e318f5c697 | ||
|
|
b62c61329a | ||
|
|
1e71d8dcc8 | ||
|
|
f342dc8196 | ||
|
|
87c333ff7a | ||
|
|
76893df5bd | ||
|
|
c8453e2c81 | ||
|
|
8ab4a4b82d | ||
|
|
e6fe814ada | ||
|
|
e171a69240 | ||
|
|
55cb11da07 | ||
|
|
3104edba0f | ||
|
|
3878389f2b | ||
|
|
356a064dd1 | ||
|
|
a0ba866d5b | ||
|
|
ede63cd6be | ||
|
|
9311d1fe44 | ||
|
|
11bfa2a813 | ||
|
|
05d6bde362 | ||
|
|
afcbbb3538 | ||
|
|
8fd117ee8c | ||
|
|
d031e1a7e9 | ||
|
|
e83fa8840b | ||
|
|
fcf73165ed | ||
|
|
c911f1262a | ||
|
|
ba3e4c5dff | ||
|
|
ae564ef702 | ||
|
|
7d3a591139 | ||
|
|
9b3f76ab5a | ||
|
|
dab8acc7d8 | ||
|
|
4eda53d5a1 | ||
|
|
8c0296ca67 | ||
|
|
7ef094325d | ||
|
|
2365862615 | ||
|
|
e30749e94c | ||
|
|
c20dea64cf | ||
|
|
faef8b679e | ||
|
|
9b3e21225c | ||
|
|
7640e3255f | ||
|
|
4c5ed3df2c | ||
|
|
39601be452 | ||
|
|
76aaecb8f2 | ||
|
|
4b2faeedab | ||
|
|
0087e3748c | ||
|
|
754ec33e4b | ||
|
|
9f8b74adf9 | ||
|
|
8916841e83 | ||
|
|
e7f21f41ee | ||
|
|
ba860a2b61 | ||
|
|
c349a5c75b | ||
|
|
d522b7ef1e | ||
|
|
800125c7a9 | ||
|
|
37f20c6889 | ||
|
|
5dfe7bea8e | ||
|
|
6067622c19 | ||
|
|
fac7b064b4 | ||
|
|
ef6f252842 | ||
|
|
b8da19e49f | ||
|
|
a483df8b20 | ||
|
|
41ccc13394 | ||
|
|
0978357c5f | ||
|
|
7935085e74 | ||
|
|
7a47c9e38b | ||
|
|
20124bfca0 | ||
|
|
eaeaa297c7 | ||
|
|
9adb9ab5f4 | ||
|
|
c4c4c977a6 | ||
|
|
7d508dcb52 | ||
|
|
773754d74f | ||
|
|
ed20a23297 | ||
|
|
4615c84f31 | ||
|
|
677136f4ab | ||
|
|
3c3710420b | ||
|
|
de268b8225 | ||
|
|
42c709e7b1 | ||
|
|
cf0349acc8 | ||
|
|
e43b36b61f | ||
|
|
ed24867309 | ||
|
|
3cf78749df | ||
|
|
badbf766bb | ||
|
|
5b265dbc1c | ||
|
|
0053e143e7 | ||
|
|
1c44135b41 | ||
|
|
5f883a4445 | ||
|
|
8dc6ff268d | ||
|
|
57d7df530b | ||
|
|
13b2fe8d30 | ||
|
|
d644988845 | ||
|
|
27c6cfc958 | ||
|
|
3b9a48ff5f | ||
|
|
a5354ded3f | ||
|
|
0b07dafe77 | ||
|
|
f0e900b885 | ||
|
|
f460043e87 | ||
|
|
8c6b804d73 | ||
|
|
790512d52a | ||
|
|
89c8d26968 | ||
|
|
6d9d31cad1 | ||
|
|
6642083f52 | ||
|
|
554090b03e | ||
|
|
1cde09c312 | ||
|
|
e215b4d919 | ||
|
|
5ae6c25394 | ||
|
|
68cd8dbc3e | ||
|
|
01fe88e337 | ||
|
|
2b8923931e | ||
|
|
8d119713bc | ||
|
|
07f2e28eca | ||
|
|
0e849609f4 | ||
|
|
120a7a3090 | ||
|
|
872cd10b8b | ||
|
|
7e673fee90 | ||
|
|
847611aed4 | ||
|
|
15674111b4 | ||
|
|
1b2feeb99c | ||
|
|
f2f211908d | ||
|
|
ac9e3c6260 | ||
|
|
58ba107d01 | ||
|
|
087b4289e5 | ||
|
|
3df5f6110c | ||
|
|
6efb2a2054 | ||
|
|
4f8593f46a | ||
|
|
9eb7a84d83 | ||
|
|
8e65e794bc | ||
|
|
ecc7758788 | ||
|
|
6e40fd8000 | ||
|
|
f4c674fa98 | ||
|
|
eba96eb904 | ||
|
|
3986bb6c4e | ||
|
|
10349b7be4 | ||
|
|
d8f5e81880 | ||
|
|
ea81d08c01 | ||
|
|
f69acaa13d | ||
|
|
754c7324f5 | ||
|
|
2b4e32d2cf | ||
|
|
9ed933f835 | ||
|
|
c4b3579c60 | ||
|
|
c5d1802346 | ||
|
|
d873f88b56 | ||
|
|
17781066a2 | ||
|
|
ac0fbaad21 | ||
|
|
8ac7f639d8 | ||
|
|
3ac1d30a3a | ||
|
|
1f420777af | ||
|
|
37a212ddc4 | ||
|
|
138e62e1ef | ||
|
|
5b12784589 | ||
|
|
c9ab9d59c2 | ||
|
|
ac15b3a5af | ||
|
|
468356b120 | ||
|
|
f0a28b9168 | ||
|
|
c8f0c6b5f6 | ||
|
|
e653531934 | ||
|
|
3444c2aadd | ||
|
|
7aa7548a51 | ||
|
|
5b3596987b | ||
|
|
1e5c90ed65 | ||
|
|
f694d2e150 | ||
|
|
9738d53a82 | ||
|
|
e1d9dac70c | ||
|
|
e6324e3a19 | ||
|
|
4489db76c9 | ||
|
|
67ffada4b3 | ||
|
|
9aaf5cf914 | ||
|
|
08af7419af | ||
|
|
035b711ee3 | ||
|
|
5ad25dedf8 | ||
|
|
de47aa8466 | ||
|
|
9a78bd6e3f | ||
|
|
08cbb66a55 | ||
|
|
824cf93494 | ||
|
|
00d2f2e7b4 | ||
|
|
968ad2859e | ||
|
|
11ca12e43c | ||
|
|
15fad5476e | ||
|
|
4e468fdf24 | ||
|
|
cb4b9fce30 | ||
|
|
a562348dfa | ||
|
|
bcef1c7a76 | ||
|
|
4bbb83826c | ||
|
|
b9dbf1873d | ||
|
|
45462fb47e | ||
|
|
bf4ad692df | ||
|
|
4e943d52e4 | ||
|
|
7082f9f882 | ||
|
|
4a982fe632 | ||
|
|
1e351bd05f | ||
|
|
f0d5bfd42f | ||
|
|
256ef7c5ec | ||
|
|
6e63555bc8 | ||
|
|
5432e108a1 | ||
|
|
3fcd17e6a5 | ||
|
|
c562d17925 | ||
|
|
cf1d6919bf | ||
|
|
4b15f960e1 | ||
|
|
f92b8dcec0 | ||
|
|
9734552da5 | ||
|
|
89b7ce4c4e | ||
|
|
6dc790f447 | ||
|
|
7d62df6f1a | ||
|
|
6f7bb8a777 | ||
|
|
942f64f04d | ||
|
|
8de7014eeb | ||
|
|
d73c4a92a7 | ||
|
|
e328de5293 | ||
|
|
93054ef87c | ||
|
|
2cd1da5222 | ||
|
|
ed24eac29c | ||
|
|
3de53a313f | ||
|
|
6d2b2ac5f9 | ||
|
|
76cf170708 | ||
|
|
06ead557dc | ||
|
|
7c343411b8 | ||
|
|
736950ab3f | ||
|
|
bad04f9a0b | ||
|
|
adf754ad32 | ||
|
|
2ebd3f54e6 | ||
|
|
be63e18ebf | ||
|
|
ba82ce2798 | ||
|
|
1f7ad78f40 | ||
|
|
3130fdc4f0 | ||
|
|
5922fb38da | ||
|
|
d1e3135331 | ||
|
|
d722b6ba19 | ||
|
|
a3fe105256 | ||
|
|
04f68fddd9 | ||
|
|
03c273e30f | ||
|
|
90c478e58d | ||
|
|
c3a0bb2b77 | ||
|
|
2cd63234c1 | ||
|
|
ccd0842df8 | ||
|
|
2a2db4f526 | ||
|
|
21f1439ad8 | ||
|
|
4cbcd3c606 | ||
|
|
1f14767fe9 | ||
|
|
0b53c35523 | ||
|
|
552a8044b0 | ||
|
|
cc96c436a9 | ||
|
|
585a6f15a6 | ||
|
|
f37e50cc71 | ||
|
|
3023d3c358 | ||
|
|
08562b645e | ||
|
|
edb1d2fb9e | ||
|
|
9645101de2 | ||
|
|
c1bbd6e766 | ||
|
|
72ca4c2e1f | ||
|
|
bccd79b6be | ||
|
|
109a27c9ef | ||
|
|
2fa2ade3ae | ||
|
|
0a4c8a40ba | ||
|
|
c49743d38c | ||
|
|
a9afc1e6ba | ||
|
|
aa6f5fd139 | ||
|
|
519f658c07 | ||
|
|
f5cb56fd86 | ||
|
|
c830db07ad | ||
|
|
9093702692 | ||
|
|
edd58b4b7a | ||
|
|
72432d65ba | ||
|
|
eb611a2855 | ||
|
|
8aa73ed6ae | ||
|
|
1224222984 | ||
|
|
3360c6aa96 | ||
|
|
bfddd3fc69 | ||
|
|
35cd81a75f | ||
|
|
f11fceb63a | ||
|
|
f14a28db54 | ||
|
|
cacc01bac0 | ||
|
|
fc386f4fa1 | ||
|
|
3743aaa16e | ||
|
|
22b4640b31 | ||
|
|
1dcda4989d | ||
|
|
e59768167a | ||
|
|
7e5becb5e5 | ||
|
|
b4f348ccea | ||
|
|
7a0dd24681 | ||
|
|
e5ae82252f | ||
|
|
49c45d1007 | ||
|
|
5502bff986 | ||
|
|
ee19789cac | ||
|
|
bad5a1de55 | ||
|
|
3cdbe213a3 | ||
|
|
3b5b1bf877 | ||
|
|
bcd9229ffe | ||
|
|
4df588668a | ||
|
|
de96500c1a | ||
|
|
9b881cdd19 | ||
|
|
2ccf39800d | ||
|
|
5a3065344e | ||
|
|
98b6b5e3f6 | ||
|
|
4d81fa6df5 | ||
|
|
8e8582e953 | ||
|
|
7f4c05e88f | ||
|
|
20e63659a1 | ||
|
|
c5af69db2b | ||
|
|
030241d1c3 | ||
|
|
4f01c43a93 | ||
|
|
ab94471e91 | ||
|
|
140aa68811 | ||
|
|
d9ef38e370 | ||
|
|
acf66116cd | ||
|
|
8e69125128 | ||
|
|
7402ca3568 | ||
|
|
d7b240f25c | ||
|
|
e5e4d3bed4 | ||
|
|
1a59302749 | ||
|
|
f0a5bbedb4 | ||
|
|
139665b3d6 | ||
|
|
ca23d59148 | ||
|
|
1635f00a62 | ||
|
|
29a4404257 | ||
|
|
24db29f526 | ||
|
|
e27b64274f | ||
|
|
46721bcf46 | ||
|
|
80cefdd1d5 | ||
|
|
9972f4da48 | ||
|
|
1ab5401501 | ||
|
|
0b60cc8341 | ||
|
|
85b4746516 | ||
|
|
d17ac9c83c | ||
|
|
e6ff513aac | ||
|
|
ffbfeab977 | ||
|
|
09db062958 | ||
|
|
ab7732d9ae | ||
|
|
46594ec707 | ||
|
|
18426561e3 | ||
|
|
aeb7e3a9e1 | ||
|
|
a77c46be20 | ||
|
|
a4be2cddf2 | ||
|
|
240b335181 | ||
|
|
53d6807e8d | ||
|
|
db38cc8891 | ||
|
|
4e2aeceeec | ||
|
|
9b04a04568 | ||
|
|
f2c97bda66 | ||
|
|
6c4d919828 | ||
|
|
f6a502a8e3 | ||
|
|
e5be023e4b | ||
|
|
47743bdcfa | ||
|
|
b0962363c2 | ||
|
|
8855ef72bc | ||
|
|
264727a414 | ||
|
|
98c16ddc4d | ||
|
|
c7691fbebe | ||
|
|
62f92d5b28 | ||
|
|
2ae9165bfb | ||
|
|
08de326930 | ||
|
|
b341cfd4d9 | ||
|
|
a76b018900 | ||
|
|
9783da5d8e | ||
|
|
6f0985dcaa | ||
|
|
0b44886b62 | ||
|
|
36991b5c8a | ||
|
|
afb7f89722 | ||
|
|
0248a36561 | ||
|
|
2e7470115c | ||
|
|
06b376d242 | ||
|
|
4b17813b9f | ||
|
|
960a7f82ef | ||
|
|
25be8ccd05 | ||
|
|
da6c68629d | ||
|
|
b63baf939e | ||
|
|
90d8e0cedc | ||
|
|
1c2d4c518e | ||
|
|
0c030e811f | ||
|
|
428ef11157 | ||
|
|
ee34b64f5d | ||
|
|
c1f9d8f7a1 | ||
|
|
996be5d247 | ||
|
|
7d45419724 | ||
|
|
c0ae5c0fb7 | ||
|
|
09042d12d4 | ||
|
|
7db147da14 | ||
|
|
1324b5da13 | ||
|
|
4ee14e6e77 | ||
|
|
e1d50757b3 | ||
|
|
9a447e8554 | ||
|
|
516a5e9c5f | ||
|
|
4744f5eecf | ||
|
|
33839b5667 | ||
|
|
43f2d64a6f | ||
|
|
13f30c3167 | ||
|
|
749f00766f | ||
|
|
d2cc343649 | ||
|
|
f20c3e08d4 | ||
|
|
ae4c7b635d | ||
|
|
b9f1f9c41e | ||
|
|
b46d40aa07 | ||
|
|
11a6991b5c | ||
|
|
fcf0cb5d69 | ||
|
|
a271baa1ae | ||
|
|
0194c7fcbc | ||
|
|
5ee6cba557 | ||
|
|
475d18bd37 | ||
|
|
75ed4fe398 | ||
|
|
d29b0baa25 | ||
|
|
ffd57772e9 | ||
|
|
e648e4fb29 | ||
|
|
ecab62a56b | ||
|
|
e21ea739d9 | ||
|
|
659bb08389 | ||
|
|
f8da264e2b | ||
|
|
db84317be0 | ||
|
|
e93dc33ef8 | ||
|
|
cb1a4291d0 | ||
|
|
1a745b24d7 | ||
|
|
7b7ce30fe3 | ||
|
|
9bc525f579 | ||
|
|
3fcbc03759 | ||
|
|
1bd53de1f7 | ||
|
|
037739c634 | ||
|
|
3150d2b94b | ||
|
|
91ab10084a | ||
|
|
96d2a7f0bf | ||
|
|
053c9372cb | ||
|
|
0bb231ad00 | ||
|
|
15db5adc7e | ||
|
|
146478e450 | ||
|
|
97192a8055 | ||
|
|
772514940c | ||
|
|
38efde6c98 | ||
|
|
056b8ba1e8 | ||
|
|
ccd4d46391 | ||
|
|
e3bf8265c4 | ||
|
|
f4ee86282e | ||
|
|
7b66eb8b9c | ||
|
|
a7861c2ea5 | ||
|
|
163678dfe3 | ||
|
|
e32b1341f8 | ||
|
|
f579ad79a2 | ||
|
|
a6d1b8b975 | ||
|
|
890c5596a9 | ||
|
|
7797580c7f | ||
|
|
7f4abc5285 | ||
|
|
e559c467c6 | ||
|
|
916915d430 | ||
|
|
46d3889b63 | ||
|
|
e39dc25e2a | ||
|
|
1a022d8905 | ||
|
|
3dc8888c6a | ||
|
|
3ff3046b68 | ||
|
|
910a5dd96a | ||
|
|
6b521c4c43 | ||
|
|
fe909fbe92 | ||
|
|
98c9139f3a | ||
|
|
03d9ee31ec | ||
|
|
ef1cc56439 | ||
|
|
47fcdef88c | ||
|
|
a1c2260f9b | ||
|
|
a4b01b83e7 | ||
|
|
b9e8edb3ef | ||
|
|
b95861c378 | ||
|
|
758ae185ba | ||
|
|
f60d9a51d4 | ||
|
|
d81579730e | ||
|
|
b1c6c40fa7 | ||
|
|
d8bc3769a5 | ||
|
|
a73fbf7232 | ||
|
|
b6b2f453a0 | ||
|
|
aa14015919 | ||
|
|
7551c84c4f | ||
|
|
434e53e922 | ||
|
|
b5d238f7f4 | ||
|
|
e5c9fea52d | ||
|
|
cd15a0e966 | ||
|
|
895c723d4e | ||
|
|
c7176d6bc8 | ||
|
|
b2939d3df3 | ||
|
|
54a157a629 | ||
|
|
427adefb42 | ||
|
|
f0dede26a3 | ||
|
|
36f85a6a5a | ||
|
|
137567554d | ||
|
|
72941e51fc | ||
|
|
836c016f97 | ||
|
|
e8ea9b7127 | ||
|
|
f80c78536f | ||
|
|
7877187894 | ||
|
|
a384a57979 | ||
|
|
86e1476dee | ||
|
|
81d0ecd8f6 | ||
|
|
6f329c9e96 | ||
|
|
0e2445c7a0 | ||
|
|
7ed947f598 | ||
|
|
b85f6ea6c3 | ||
|
|
f85f088d65 | ||
|
|
045472deac | ||
|
|
71cad4df58 | ||
|
|
48786522c8 | ||
|
|
0827f1b2f6 | ||
|
|
5973bb8610 | ||
|
|
00ca5132b4 | ||
|
|
191d203b24 | ||
|
|
06f96011d8 | ||
|
|
f2e292c702 | ||
|
|
832deb8e97 | ||
|
|
74a3c57222 | ||
|
|
4a19092db0 | ||
|
|
5c652d913a | ||
|
|
053213f50e | ||
|
|
342e946f49 | ||
|
|
d15ab1355b | ||
|
|
e47f4b8f80 | ||
|
|
637a4eb351 | ||
|
|
a233aeec4a | ||
|
|
d24104154f | ||
|
|
7fa2706d0e | ||
|
|
2a5365d46d | ||
|
|
72cc853420 | ||
|
|
eea111df6f | ||
|
|
130bea9e25 | ||
|
|
754242439a | ||
|
|
3fab9e4cec | ||
|
|
8b6290120e | ||
|
|
564aef2a2a | ||
|
|
5958324550 | ||
|
|
683fc1f081 | ||
|
|
9277b46620 | ||
|
|
261926222b | ||
|
|
35cfefd934 | ||
|
|
aa13374523 | ||
|
|
fd1cd39c7c | ||
|
|
3e6d1d5789 | ||
|
|
185d0bf3a3 | ||
|
|
837f3ce04c | ||
|
|
ad8d3e2444 |
11
.cargo/config.toml
Normal file
11
.cargo/config.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[env]
|
||||
# In unoptimised builds tokio tends to use a lot of stack space when
|
||||
# creating some complicated futures, tokio has an open issue for this:
|
||||
# https://github.com/tokio-rs/tokio/issues/2055. Some of our tests
|
||||
# manage to not fit in the default 2MiB stack anymore due to this, so
|
||||
# while the issue is not resolved we want to work around this.
|
||||
# Because compiling optimised builds takes a very long time we prefer
|
||||
# to avoid that. Setting this environment variable ensures that when
|
||||
# invoking `cargo test` threads are allowed to have a large enough
|
||||
# stack size without needing to use an optimised build.
|
||||
RUST_MIN_STACK = "8388608"
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -4,7 +4,7 @@
|
||||
|
||||
# This directory contains email messages verbatim, and changing CRLF to
|
||||
# LF will corrupt them.
|
||||
test-data/* text=false
|
||||
test-data/** text=false
|
||||
|
||||
# binary files should be detected by git, however, to be sure, you can add them here explicitly
|
||||
*.png binary
|
||||
|
||||
28
.github/mergeable.yml
vendored
28
.github/mergeable.yml
vendored
@@ -5,22 +5,22 @@ mergeable:
|
||||
validate:
|
||||
- do: or
|
||||
validate:
|
||||
- do: description
|
||||
must_include:
|
||||
regex: '#skip-changelog'
|
||||
- do: and
|
||||
validate:
|
||||
- do: dependent
|
||||
changed:
|
||||
file: 'src/**'
|
||||
required: ['CHANGELOG.md']
|
||||
- do: dependent
|
||||
changed:
|
||||
file: 'deltachat-ffi/src/**'
|
||||
required: ['CHANGELOG.md']
|
||||
- do: description
|
||||
must_include:
|
||||
regex: "#skip-changelog"
|
||||
- do: and
|
||||
validate:
|
||||
- do: dependent
|
||||
changed:
|
||||
file: "src/**"
|
||||
required: ["CHANGELOG.md"]
|
||||
- do: dependent
|
||||
changed:
|
||||
file: "deltachat-ffi/src/**"
|
||||
required: ["CHANGELOG.md"]
|
||||
fail:
|
||||
- do: checks
|
||||
status: 'action_required'
|
||||
status: "action_required"
|
||||
payload:
|
||||
title: Changelog might need an update
|
||||
summary: "Check if CHANGELOG.md needs an update or add #skip-changelog to the PR description."
|
||||
|
||||
210
.github/workflows/ci.yml
vendored
210
.github/workflows/ci.yml
vendored
@@ -1,51 +1,55 @@
|
||||
name: Rust CI
|
||||
|
||||
# Cancel previously started workflow runs
|
||||
# when the branch is updated.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- staging
|
||||
- trying
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Rustfmt and Clippy
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: 1.68.0
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install rustfmt and clippy
|
||||
run: rustup toolchain install $RUSTUP_TOOLCHAIN --profile minimal --component rustfmt --component clippy
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@v2
|
||||
- name: Run rustfmt
|
||||
run: cargo fmt --all -- --check
|
||||
- name: Run clippy
|
||||
run: scripts/clippy.sh
|
||||
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
cargo_deny:
|
||||
name: cargo deny
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: actions/checkout@v3
|
||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- run: rustup component add rustfmt
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@v1
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
arguments: --all-features --workspace
|
||||
command: check
|
||||
command-arguments: "-Dwarnings"
|
||||
|
||||
run_clippy:
|
||||
provider_database:
|
||||
name: Check provider database
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
components: clippy
|
||||
override: true
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@v1
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --workspace --tests --examples --benches --features repl -- -D warnings
|
||||
- uses: actions/checkout@v3
|
||||
- name: Check provider database
|
||||
run: scripts/update-provider-database.sh
|
||||
|
||||
docs:
|
||||
name: Rust doc comments
|
||||
@@ -54,110 +58,106 @@ jobs:
|
||||
RUSTDOCFLAGS: -Dwarnings
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
- name: Install rust stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
components: rust-docs
|
||||
override: true
|
||||
uses: actions/checkout@v3
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@v1
|
||||
uses: swatinem/rust-cache@v2
|
||||
- name: Rustdoc
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: doc
|
||||
args: --document-private-items --no-deps
|
||||
run: cargo doc --document-private-items --no-deps
|
||||
|
||||
build_and_test:
|
||||
name: Build and test
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Currently used Rust version, same as in `rust-toolchain` file.
|
||||
# Currently used Rust version.
|
||||
- os: ubuntu-latest
|
||||
rust: 1.61.0
|
||||
rust: 1.68.0
|
||||
python: 3.9
|
||||
- os: windows-latest
|
||||
rust: 1.61.0
|
||||
rust: 1.68.0
|
||||
python: false # Python bindings compilation on Windows is not supported.
|
||||
- os: macos-latest
|
||||
rust: 1.68.0
|
||||
python: 3.9
|
||||
|
||||
# Minimum Supported Rust Version = 1.56.1
|
||||
# Minimum Supported Rust Version = 1.64.0
|
||||
#
|
||||
# Minimum Supported Python Version = 3.7
|
||||
# This is the minimum version for which manylinux Python wheels are
|
||||
# built.
|
||||
- os: ubuntu-latest
|
||||
rust: 1.56.1
|
||||
rust: 1.64.0
|
||||
python: 3.7
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Install ${{ matrix.rust }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
- name: Install Rust ${{ matrix.rust }}
|
||||
run: rustup toolchain install --profile minimal ${{ matrix.rust }}
|
||||
- run: rustup override set ${{ matrix.rust }}
|
||||
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@v1
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@v2
|
||||
|
||||
- name: check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --all --bins --examples --tests --features repl --benches
|
||||
- name: Check
|
||||
run: cargo check --workspace --bins --examples --tests --benches
|
||||
|
||||
- name: tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all
|
||||
- name: Tests
|
||||
run: cargo test --workspace
|
||||
|
||||
- name: test cargo vendor
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: vendor
|
||||
- name: Test cargo vendor
|
||||
run: cargo vendor
|
||||
|
||||
- name: install python
|
||||
if: ${{ matrix.python }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
- name: Install python
|
||||
if: ${{ matrix.python }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
|
||||
- name: install tox
|
||||
if: ${{ matrix.python }}
|
||||
run: pip install tox
|
||||
- name: Install tox
|
||||
if: ${{ matrix.python }}
|
||||
run: pip install tox
|
||||
|
||||
- name: build C library
|
||||
if: ${{ matrix.python }}
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: -p deltachat_ffi --features jsonrpc
|
||||
- name: Build C library
|
||||
if: ${{ matrix.python }}
|
||||
run: cargo build -p deltachat_ffi --features jsonrpc
|
||||
|
||||
- name: run python tests
|
||||
if: ${{ matrix.python }}
|
||||
env:
|
||||
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
||||
DCC_RS_TARGET: debug
|
||||
DCC_RS_DEV: ${{ github.workspace }}
|
||||
working-directory: python
|
||||
run: tox -e lint,mypy,doc,py3
|
||||
- name: Run python tests
|
||||
if: ${{ matrix.python }}
|
||||
env:
|
||||
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
||||
DCC_RS_TARGET: debug
|
||||
DCC_RS_DEV: ${{ github.workspace }}
|
||||
working-directory: python
|
||||
run: tox -e lint,mypy,doc,py3
|
||||
|
||||
- name: install pypy
|
||||
if: ${{ matrix.python }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 'pypy${{ matrix.python }}'
|
||||
- name: Build deltachat-rpc-server
|
||||
if: ${{ matrix.python }}
|
||||
run: cargo build -p deltachat-rpc-server
|
||||
|
||||
- name: run pypy tests
|
||||
if: ${{ matrix.python }}
|
||||
env:
|
||||
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
||||
DCC_RS_TARGET: debug
|
||||
DCC_RS_DEV: ${{ github.workspace }}
|
||||
working-directory: python
|
||||
run: tox -e pypy3
|
||||
- name: Add deltachat-rpc-server to path
|
||||
if: ${{ matrix.python }}
|
||||
run: echo ${{ github.workspace }}/target/debug >> $GITHUB_PATH
|
||||
|
||||
- name: Run deltachat-rpc-client tests
|
||||
if: ${{ matrix.python }}
|
||||
env:
|
||||
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
||||
working-directory: deltachat-rpc-client
|
||||
run: tox -e py3,lint
|
||||
|
||||
- name: Install pypy
|
||||
if: ${{ matrix.python }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "pypy${{ matrix.python }}"
|
||||
|
||||
- name: Run pypy tests
|
||||
if: ${{ matrix.python }}
|
||||
env:
|
||||
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
||||
DCC_RS_TARGET: debug
|
||||
DCC_RS_DEV: ${{ github.workspace }}
|
||||
working-directory: python
|
||||
run: tox -e pypy3
|
||||
|
||||
106
.github/workflows/deltachat-rpc-server.yml
vendored
Normal file
106
.github/workflows/deltachat-rpc-server.yml
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
# Manually triggered action to build deltachat-rpc-server binaries.
|
||||
|
||||
name: Build deltachat-rpc-server binaries
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
# Build a version statically linked against musl libc
|
||||
# to avoid problems with glibc version incompatibility.
|
||||
build_linux:
|
||||
name: Cross-compile deltachat-rpc-server for x86_64, aarch64 and armv7 Linux
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build
|
||||
run: sh scripts/zig-rpc-server.sh
|
||||
|
||||
- name: Upload x86_64 binary
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: deltachat-rpc-server-x86_64
|
||||
path: target/x86_64-unknown-linux-musl/release/deltachat-rpc-server
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload aarch64 binary
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: deltachat-rpc-server-aarch64
|
||||
path: target/aarch64-unknown-linux-musl/release/deltachat-rpc-server
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload armv7 binary
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: deltachat-rpc-server-armv7
|
||||
path: target/armv7-unknown-linux-musleabihf/release/deltachat-rpc-server
|
||||
if-no-files-found: error
|
||||
|
||||
build_windows:
|
||||
name: Build deltachat-rpc-server for Windows
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-latest
|
||||
artifact: win32.exe
|
||||
path: deltachat-rpc-server.exe
|
||||
target: i686-pc-windows-msvc
|
||||
|
||||
- os: windows-latest
|
||||
artifact: win64.exe
|
||||
path: deltachat-rpc-server.exe
|
||||
target: x86_64-pc-windows-msvc
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup rust target
|
||||
run: rustup target add ${{ matrix.target }}
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release --package deltachat-rpc-server --target ${{ matrix.target }} --features vendored
|
||||
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: deltachat-rpc-server-${{ matrix.artifact }}
|
||||
path: target/${{ matrix.target}}/release/${{ matrix.path }}
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
name: Upload binaries to the release
|
||||
needs: ["build_linux", "build_windows"]
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- name: Download built binaries
|
||||
uses: "actions/download-artifact@v3"
|
||||
|
||||
- name: Compose dist/ directory
|
||||
run: |
|
||||
mkdir dist
|
||||
for x in x86_64 aarch64 armv7 win32.exe win64.exe; do
|
||||
mv "deltachat-rpc-server-$x"/* "dist/deltachat-rpc-server-$x"
|
||||
done
|
||||
|
||||
- name: List downloaded artifacts
|
||||
run: ls -l dist/
|
||||
|
||||
- name: Upload binaries to the GitHub release
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
run: |
|
||||
gh release upload ${{ github.ref_name }} \
|
||||
--repo ${{ github.repository }} \
|
||||
dist/*
|
||||
39
.github/workflows/jsonrpc-client-npm-package.yml
vendored
39
.github/workflows/jsonrpc-client-npm-package.yml
vendored
@@ -1,29 +1,28 @@
|
||||
name: 'jsonrpc js client build'
|
||||
name: "jsonrpc js client build"
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
- '!py-*'
|
||||
|
||||
- "*"
|
||||
- "!py-*"
|
||||
|
||||
jobs:
|
||||
pack-module:
|
||||
name: 'Package @deltachat/jsonrpc-client and upload to download.delta.chat'
|
||||
runs-on: ubuntu-18.04
|
||||
name: "Package @deltachat/jsonrpc-client and upload to download.delta.chat"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: install tree
|
||||
- name: Install tree
|
||||
run: sudo apt install tree
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: get tag
|
||||
node-version: "16"
|
||||
- name: Get tag
|
||||
id: tag
|
||||
uses: dawidd6/action-get-tag@v1
|
||||
continue-on-error: true
|
||||
- name: Get Pullrequest ID
|
||||
- name: Get Pull Request ID
|
||||
id: prepare
|
||||
run: |
|
||||
tag=${{ steps.tag.outputs.tag }}
|
||||
@@ -38,20 +37,20 @@ jobs:
|
||||
npm --version
|
||||
node --version
|
||||
echo $DELTACHAT_JSONRPC_TAR_GZ
|
||||
- name: install dependencies without running scripts
|
||||
- name: Install dependencies without running scripts
|
||||
run: |
|
||||
cd deltachat-jsonrpc/typescript
|
||||
npm install --ignore-scripts
|
||||
- name: package
|
||||
- name: Package
|
||||
shell: bash
|
||||
run: |
|
||||
cd deltachat-jsonrpc/typescript
|
||||
npm run build:tsc
|
||||
npm run build
|
||||
npm pack .
|
||||
ls -lah
|
||||
mv $(find deltachat-jsonrpc-client-*) $DELTACHAT_JSONRPC_TAR_GZ
|
||||
- name: Upload Prebuild
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: deltachat-jsonrpc-client.tgz
|
||||
path: deltachat-jsonrpc/typescript/${{ env.DELTACHAT_JSONRPC_TAR_GZ }}
|
||||
@@ -65,13 +64,13 @@ jobs:
|
||||
chmod 600 __TEMP_INPUT_KEY_FILE
|
||||
scp -o StrictHostKeyChecking=no -v -i __TEMP_INPUT_KEY_FILE -P "22" -r deltachat-jsonrpc/typescript/$DELTACHAT_JSONRPC_TAR_GZ "${{ secrets.USERNAME }}"@"download.delta.chat":"/var/www/html/download/node/preview/"
|
||||
continue-on-error: true
|
||||
- name: "Post links to details"
|
||||
- name: Post links to details
|
||||
if: steps.upload-preview.outcome == 'success'
|
||||
run: node ./node/scripts/postLinksToDetails.js
|
||||
env:
|
||||
URL: preview/${{ env.DELTACHAT_JSONRPC_TAR_GZ }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
MSG_CONTEXT: Download the deltachat-jsonrpc-client.tgz
|
||||
URL: preview/${{ env.DELTACHAT_JSONRPC_TAR_GZ }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
MSG_CONTEXT: Download the deltachat-jsonrpc-client.tgz
|
||||
# Upload to download.delta.chat/node/
|
||||
- name: Upload deltachat-jsonrpc-client build to download.delta.chat/node/
|
||||
if: ${{ steps.tag.outputs.tag }}
|
||||
|
||||
16
.github/workflows/jsonrpc.yml
vendored
16
.github/workflows/jsonrpc.yml
vendored
@@ -8,23 +8,19 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUST_MIN_STACK: "8388608"
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16.x
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Add Rust cache
|
||||
uses: Swatinem/rust-cache@v1.3.0
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: npm install
|
||||
run: |
|
||||
cd deltachat-jsonrpc/typescript
|
||||
@@ -39,6 +35,10 @@ jobs:
|
||||
npm run test
|
||||
env:
|
||||
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
||||
- name: make sure websocket server version still builds
|
||||
run: |
|
||||
cd deltachat-jsonrpc
|
||||
cargo build --bin deltachat-jsonrpc-server --features webserver
|
||||
- name: Run linter
|
||||
run: |
|
||||
cd deltachat-jsonrpc/typescript
|
||||
|
||||
39
.github/workflows/node-delete-preview.yml
vendored
39
.github/workflows/node-delete-preview.yml
vendored
@@ -7,26 +7,25 @@ on:
|
||||
|
||||
jobs:
|
||||
delete:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Get Pullrequest ID
|
||||
id: getid
|
||||
run: |
|
||||
export PULLREQUEST_ID=$(jq .number < $GITHUB_EVENT_PATH)
|
||||
echo ::set-output name=prid::$PULLREQUEST_ID
|
||||
- name: Renaming
|
||||
run: |
|
||||
# create empty file to copy it over the outdated deliverable on download.delta.chat
|
||||
echo "This preview build is outdated and has been removed." > empty
|
||||
cp empty deltachat-node-${{ steps.getid.outputs.prid }}.tar.gz
|
||||
- name: Replace builds with dummy files
|
||||
uses: horochx/deploy-via-scp@v1.0.1
|
||||
with:
|
||||
user: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
host: "download.delta.chat"
|
||||
port: 22
|
||||
local: "deltachat-node-${{ steps.getid.outputs.prid }}.tar.gz"
|
||||
remote: "/var/www/html/download/node/preview/"
|
||||
- name: Get Pull Request ID
|
||||
id: getid
|
||||
run: |
|
||||
export PULLREQUEST_ID=$(jq .number < $GITHUB_EVENT_PATH)
|
||||
echo "prid=$PULLREQUEST_ID" >> $GITHUB_OUTPUT
|
||||
- name: Renaming
|
||||
run: |
|
||||
# create empty file to copy it over the outdated deliverable on download.delta.chat
|
||||
echo "This preview build is outdated and has been removed." > empty
|
||||
cp empty deltachat-node-${{ steps.getid.outputs.prid }}.tar.gz
|
||||
- name: Replace builds with dummy files
|
||||
uses: horochx/deploy-via-scp@v1.0.1
|
||||
with:
|
||||
user: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
host: "download.delta.chat"
|
||||
port: 22
|
||||
local: "deltachat-node-${{ steps.getid.outputs.prid }}.tar.gz"
|
||||
remote: "/var/www/html/download/node/preview/"
|
||||
|
||||
40
.github/workflows/node-docs.yml
vendored
40
.github/workflows/node-docs.yml
vendored
@@ -9,26 +9,26 @@ jobs:
|
||||
generate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: npm install and generate documentation
|
||||
run: |
|
||||
cd node
|
||||
npm i --ignore-scripts
|
||||
npx typedoc
|
||||
mv docs js
|
||||
- name: npm install and generate documentation
|
||||
run: |
|
||||
cd node
|
||||
npm i --ignore-scripts
|
||||
npx typedoc
|
||||
mv docs js
|
||||
|
||||
- name: Upload
|
||||
uses: horochx/deploy-via-scp@v1.0.1
|
||||
with:
|
||||
user: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.KEY }}
|
||||
host: "delta.chat"
|
||||
port: 22
|
||||
local: "node/js"
|
||||
remote: "/var/www/html/"
|
||||
- name: Upload
|
||||
uses: horochx/deploy-via-scp@v1.0.1
|
||||
with:
|
||||
user: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.KEY }}
|
||||
host: "delta.chat"
|
||||
port: 22
|
||||
local: "node/js"
|
||||
remote: "/var/www/html/"
|
||||
|
||||
67
.github/workflows/node-package.yml
vendored
67
.github/workflows/node-package.yml
vendored
@@ -1,25 +1,24 @@
|
||||
name: 'node.js build'
|
||||
name: "node.js build"
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
- '!py-*'
|
||||
|
||||
- "*"
|
||||
- "!py-*"
|
||||
|
||||
jobs:
|
||||
prebuild:
|
||||
name: 'prebuild'
|
||||
name: Prebuild
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-18.04, macos-latest, windows-latest]
|
||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: "16"
|
||||
- name: System info
|
||||
run: |
|
||||
rustc -vV
|
||||
@@ -29,7 +28,7 @@ jobs:
|
||||
node --version
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
${{ env.APPDATA }}/npm-cache
|
||||
@@ -37,7 +36,7 @@ jobs:
|
||||
key: ${{ matrix.os }}-node-${{ hashFiles('**/package.json') }}
|
||||
|
||||
- name: Cache cargo index
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/
|
||||
@@ -58,28 +57,28 @@ jobs:
|
||||
tar -zcvf "${{ matrix.os }}.tar.gz" -C prebuilds .
|
||||
|
||||
- name: Upload Prebuild
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.os }}
|
||||
path: node/${{ matrix.os }}.tar.gz
|
||||
|
||||
pack-module:
|
||||
needs: prebuild
|
||||
name: 'Package deltachat-node and upload to download.delta.chat'
|
||||
runs-on: ubuntu-18.04
|
||||
name: Package deltachat-node and upload to download.delta.chat
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: install tree
|
||||
- name: Install tree
|
||||
run: sudo apt install tree
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: get tag
|
||||
node-version: "16"
|
||||
- name: Get tag
|
||||
id: tag
|
||||
uses: dawidd6/action-get-tag@v1
|
||||
continue-on-error: true
|
||||
- name: Get Pullrequest ID
|
||||
- name: Get Pull Request ID
|
||||
id: prepare
|
||||
run: |
|
||||
tag=${{ steps.tag.outputs.tag }}
|
||||
@@ -97,44 +96,44 @@ jobs:
|
||||
npm --version
|
||||
node --version
|
||||
echo $DELTACHAT_NODE_TAR_GZ
|
||||
- name: Download ubuntu prebuild
|
||||
- name: Download Ubuntu prebuild
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: ubuntu-18.04
|
||||
- name: Download macos prebuild
|
||||
name: ubuntu-20.04
|
||||
- name: Download macOS prebuild
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: macos-latest
|
||||
- name: Download windows prebuild
|
||||
- name: Download Windows prebuild
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: windows-latest
|
||||
- shell: bash
|
||||
run: |
|
||||
mkdir node/prebuilds
|
||||
tar -xvzf ubuntu-18.04/ubuntu-18.04.tar.gz -C node/prebuilds
|
||||
tar -xvzf ubuntu-20.04/ubuntu-20.04.tar.gz -C node/prebuilds
|
||||
tar -xvzf macos-latest/macos-latest.tar.gz -C node/prebuilds
|
||||
tar -xvzf windows-latest/windows-latest.tar.gz -C node/prebuilds
|
||||
tree node/prebuilds
|
||||
rm -rf ubuntu-18.04 macos-latest windows-latest
|
||||
- name: install dependencies without running scripts
|
||||
rm -rf ubuntu-20.04 macos-latest windows-latest
|
||||
- name: Install dependencies without running scripts
|
||||
run: |
|
||||
npm install --ignore-scripts
|
||||
- name: build constants
|
||||
- name: Build constants
|
||||
run: |
|
||||
npm run build:core:constants
|
||||
- name: build typescript part
|
||||
- name: Build TypeScript part
|
||||
run: |
|
||||
npm run build:bindings:ts
|
||||
- name: package
|
||||
- name: Package
|
||||
shell: bash
|
||||
run: |
|
||||
mv node/README.md README.md
|
||||
npm pack .
|
||||
ls -lah
|
||||
mv $(find deltachat-node-*) $DELTACHAT_NODE_TAR_GZ
|
||||
- name: Upload Prebuild
|
||||
uses: actions/upload-artifact@v1
|
||||
- name: Upload prebuild
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: deltachat-node.tgz
|
||||
path: ${{ env.DELTACHAT_NODE_TAR_GZ }}
|
||||
@@ -148,12 +147,12 @@ jobs:
|
||||
chmod 600 __TEMP_INPUT_KEY_FILE
|
||||
scp -o StrictHostKeyChecking=no -v -i __TEMP_INPUT_KEY_FILE -P "22" -r $DELTACHAT_NODE_TAR_GZ "${{ secrets.USERNAME }}"@"download.delta.chat":"/var/www/html/download/node/preview/"
|
||||
continue-on-error: true
|
||||
- name: "Post links to details"
|
||||
- name: Post links to details
|
||||
if: steps.upload-preview.outcome == 'success'
|
||||
run: node ./node/scripts/postLinksToDetails.js
|
||||
env:
|
||||
URL: preview/${{ env.DELTACHAT_NODE_TAR_GZ }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
URL: preview/${{ env.DELTACHAT_NODE_TAR_GZ }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Upload to download.delta.chat/node/
|
||||
- name: Upload deltachat-node build to download.delta.chat/node/
|
||||
if: ${{ steps.tag.outputs.tag }}
|
||||
|
||||
29
.github/workflows/node-tests.yml
vendored
29
.github/workflows/node-tests.yml
vendored
@@ -1,25 +1,30 @@
|
||||
name: 'node.js tests'
|
||||
name: "node.js tests"
|
||||
|
||||
# Cancel previously started workflow runs
|
||||
# when the branch is updated.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- staging
|
||||
- trying
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: 'tests'
|
||||
name: Tests
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-18.04, macos-latest, windows-latest]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: "16"
|
||||
- name: System info
|
||||
run: |
|
||||
rustc -vV
|
||||
@@ -29,7 +34,7 @@ jobs:
|
||||
node --version
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
${{ env.APPDATA }}/npm-cache
|
||||
@@ -37,7 +42,7 @@ jobs:
|
||||
key: ${{ matrix.os }}-node-${{ hashFiles('**/package.json') }}
|
||||
|
||||
- name: Cache cargo index
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/
|
||||
@@ -52,16 +57,20 @@ jobs:
|
||||
npm install --verbose
|
||||
|
||||
- name: Test
|
||||
timeout-minutes: 10
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
cd node
|
||||
npm run test
|
||||
env:
|
||||
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
||||
NODE_OPTIONS: "--force-node-api-uncaught-exceptions-policy=true"
|
||||
- name: Run tests on Windows, except lint
|
||||
timeout-minutes: 10
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
cd node
|
||||
npm run test:mocha
|
||||
env:
|
||||
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
||||
NODE_OPTIONS: "--force-node-api-uncaught-exceptions-policy=true"
|
||||
|
||||
25
.github/workflows/repl.yml
vendored
25
.github/workflows/repl.yml
vendored
@@ -11,22 +11,13 @@ jobs:
|
||||
name: Build REPL example
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.50.0
|
||||
override: true
|
||||
- name: Build
|
||||
run: cargo build -p deltachat-repl --features vendored
|
||||
|
||||
- name: build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --example repl --features repl,vendored
|
||||
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: repl.exe
|
||||
path: 'target/debug/examples/repl.exe'
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: repl.exe
|
||||
path: "target/debug/deltachat-repl.exe"
|
||||
|
||||
27
.github/workflows/upload-docs.yml
vendored
27
.github/workflows/upload-docs.yml
vendored
@@ -8,21 +8,18 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- name: Build the documentation with cargo
|
||||
run: |
|
||||
cargo doc --package deltachat --no-deps
|
||||
- name: Upload to rs.delta.chat
|
||||
uses: up9cloud/action-rsync@v1.3
|
||||
env:
|
||||
USER: ${{ secrets.USERNAME }}
|
||||
KEY: ${{ secrets.KEY }}
|
||||
HOST: "delta.chat"
|
||||
SOURCE: "target/doc"
|
||||
TARGET: "/var/www/html/rs/"
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build the documentation with cargo
|
||||
run: |
|
||||
cargo doc --package deltachat --no-deps --document-private-items
|
||||
- name: Upload to rs.delta.chat
|
||||
uses: up9cloud/action-rsync@v1.3
|
||||
env:
|
||||
USER: ${{ secrets.USERNAME }}
|
||||
KEY: ${{ secrets.KEY }}
|
||||
HOST: "delta.chat"
|
||||
SOURCE: "target/doc"
|
||||
TARGET: "/var/www/html/rs/"
|
||||
|
||||
27
.github/workflows/upload-ffi-docs.yml
vendored
27
.github/workflows/upload-ffi-docs.yml
vendored
@@ -8,21 +8,18 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- name: Build the documentation with cargo
|
||||
run: |
|
||||
cargo doc --package deltachat_ffi --no-deps
|
||||
- name: Upload to cffi.delta.chat
|
||||
uses: up9cloud/action-rsync@v1.3
|
||||
env:
|
||||
USER: ${{ secrets.USERNAME }}
|
||||
KEY: ${{ secrets.KEY }}
|
||||
HOST: "delta.chat"
|
||||
SOURCE: "target/doc"
|
||||
TARGET: "/var/www/html/cffi/"
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build the documentation with cargo
|
||||
run: |
|
||||
cargo doc --package deltachat_ffi --no-deps
|
||||
- name: Upload to cffi.delta.chat
|
||||
uses: up9cloud/action-rsync@v1.3
|
||||
env:
|
||||
USER: ${{ secrets.USERNAME }}
|
||||
KEY: ${{ secrets.KEY }}
|
||||
HOST: "delta.chat"
|
||||
SOURCE: "target/doc"
|
||||
TARGET: "/var/www/html/cffi/"
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -12,8 +12,8 @@ include
|
||||
*.db
|
||||
*.db-blobs
|
||||
|
||||
.tox
|
||||
python/.eggs
|
||||
python/.tox
|
||||
*.egg-info
|
||||
__pycache__
|
||||
python/src/deltachat/capi*.so
|
||||
|
||||
432
CHANGELOG.md
432
CHANGELOG.md
@@ -1,20 +1,424 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
### API-Changes
|
||||
## [Unreleased]
|
||||
|
||||
### Changes
|
||||
|
||||
### Fixes
|
||||
|
||||
|
||||
## [1.112.0] - 2023-03-23
|
||||
|
||||
### Changes
|
||||
- Increase MSRV to 1.64. #4167
|
||||
- Core takes care of stopping and re-starting IO itself where needed,
|
||||
e.g. during backup creation.
|
||||
It is no longer needed to call `dc_stop_io()`.
|
||||
`dc_start_io()` can now be called at any time without harm. #4138
|
||||
- Pick up system's light/dark mode in generated message HTML. #4150
|
||||
- More accurate `maybe_add_bcc_self` device message text. #4175
|
||||
- "Full message view" not needed because of footers that go to contact status. #4151
|
||||
- Support non-persistent configuration with `DELTACHAT_*` env. #4154
|
||||
- Print deltachat-repl errors with causes. #4166
|
||||
|
||||
### Fixes
|
||||
- Fix segmentation fault if `dc_context_unref()` is called during
|
||||
background process spawned by `dc_configure()` or `dc_imex()`
|
||||
or `dc_jsonrpc_instance_t` is unreferenced
|
||||
during handling the JSON-RPC request. #4153
|
||||
- Delete expired messages using multiple SQL requests. #4158
|
||||
- Do not emit "Failed to run incremental vacuum" warnings on success. #4160
|
||||
- Ability to send backup over network and QR code to setup second device #4007
|
||||
- Disable buffering during STARTTLS setup. #4190
|
||||
- Add `DC_EVENT_IMAP_INBOX_IDLE` event to wait until the account
|
||||
is ready for testing.
|
||||
It is used to fix race condition between fetching
|
||||
existing messages and starting the test. #4208
|
||||
|
||||
|
||||
## [1.111.0] - 2023-03-05
|
||||
|
||||
### Changes
|
||||
- Make smeared timestamp generation non-async. #4075
|
||||
- Set minimum TLS version to 1.2. #4096
|
||||
- Run `cargo-deny` in CI. #4101
|
||||
- Check provider database with CI. #4099
|
||||
- Switch to DEFERRED transactions #4100
|
||||
|
||||
### Fixes
|
||||
- Do not block async task executor while decrypting the messages. #4079
|
||||
- Housekeeping: delete the blobs backup dir #4123
|
||||
|
||||
### API-Changes
|
||||
- jsonrpc: add more advanced API to send a message. #4097
|
||||
- jsonrpc: add get webxdc blob API `getWebxdcBlob` #4070
|
||||
|
||||
|
||||
## 1.110.0
|
||||
|
||||
### Changes
|
||||
- use transaction in `Contact::add_or_lookup()` #4059
|
||||
- Organize the connection pool as a stack rather than a queue to ensure that
|
||||
connection page cache is reused more often.
|
||||
This speeds up tests by 28%, real usage will have lower speedup. #4065
|
||||
- Use transaction in `update_blocked_mailinglist_contacts`. #4058
|
||||
- Remove `Sql.get_conn()` interface in favor of `.call()` and `.transaction()`. #4055
|
||||
- Updated provider database.
|
||||
- Disable DKIM-Checks again #4076
|
||||
- Switch from "X.Y.Z" and "py-X.Y.Z" to "vX.Y.Z" tags. #4089
|
||||
- mimeparser: handle headers from the signed part of unencrypted signed message #4013
|
||||
|
||||
### Fixes
|
||||
- Start SQL transactions with IMMEDIATE behaviour rather than default DEFERRED one. #4063
|
||||
- Fix a problem with Gmail where (auto-)deleted messages would get archived instead of deleted.
|
||||
Move them to the Trash folder for Gmail which auto-deletes trashed messages in 30 days #3972
|
||||
- Clear config cache after backup import. This bug sometimes resulted in the import to seemingly work at first. #4067
|
||||
- Update timestamps in `param` columns with transactions. #4083
|
||||
|
||||
### API-Changes
|
||||
|
||||
|
||||
## 1.109.0
|
||||
|
||||
### Changes
|
||||
- deltachat-rpc-client: use `dataclass` for `Account`, `Chat`, `Contact` and `Message` #4042
|
||||
|
||||
### Fixes
|
||||
- deltachat-rpc-server: do not block stdin while processing the request. #4041
|
||||
deltachat-rpc-server now reads the next request as soon as previous request handler is spawned.
|
||||
- Enable `auto_vacuum` on all SQL connections. #2955
|
||||
- Replace `r2d2` connection pool with an own implementation. #4050 #4053 #4043 #4061
|
||||
This change improves reliability
|
||||
by closing all database connections immediately when the context is closed.
|
||||
|
||||
### API-Changes
|
||||
|
||||
- Remove `MimeMessage::from_bytes()` public interface. #4033
|
||||
- BREAKING Types: jsonrpc: `get_messages` now returns a map with `MessageLoadResult` instead of failing completely if one of the requested messages could not be loaded. #4038
|
||||
- Add `dc_msg_set_subject()`. C-FFI #4057
|
||||
- Mark python bindings as supporting typing according to PEP 561 #4045
|
||||
|
||||
|
||||
## 1.108.0
|
||||
|
||||
### Changes
|
||||
- Use read/write timeouts instead of per-command timeouts for SMTP #3985
|
||||
- Cache DNS results for SMTP connections #3985
|
||||
- Prefer TLS over STARTTLS during autoconfiguration #4021
|
||||
- Use SOCKS5 configuration for HTTP requests #4017
|
||||
- Show non-deltachat emails by default for new installations #4019
|
||||
- Re-enabled SMTP pipelining after disabling it in #4006
|
||||
|
||||
### Fixes
|
||||
- Fix Securejoin for multiple devices on a joining side #3982
|
||||
- python: handle NULL value returned from `dc_get_msg()` #4020
|
||||
Account.`get_message_by_id` may return `None` in this case.
|
||||
|
||||
### API-Changes
|
||||
- Remove bitflags from `get_chat_msgs()` interface #4022
|
||||
C interface is not changed.
|
||||
Rust and JSON-RPC API have `flags` integer argument
|
||||
replaced with two boolean flags `info_only` and `add_daymarker`.
|
||||
- jsonrpc: add API to check if the message is sent by a bot #3877
|
||||
|
||||
|
||||
## 1.107.1
|
||||
|
||||
### Changes
|
||||
- Log server security (TLS/STARTTLS/plain) type #4005
|
||||
|
||||
### Fixes
|
||||
- Disable SMTP pipelining #4006
|
||||
|
||||
|
||||
## 1.107.0
|
||||
|
||||
### Changes
|
||||
- Pipeline SMTP commands #3924
|
||||
- Cache DNS results for IMAP connections #3970
|
||||
|
||||
### Fixes
|
||||
- Securejoin: Fix adding and handling Autocrypt-Gossip headers #3914
|
||||
- fix verifier-by addr was empty string instead of None #3961
|
||||
- Emit DC_EVENT_MSGS_CHANGED for DC_CHAT_ID_ARCHIVED_LINK when the number of archived chats with
|
||||
unread messages increases #3959
|
||||
- Fix Peerstate comparison #3962
|
||||
- Log SOCKS5 configuration for IMAP like already done for SMTP #3964
|
||||
- Fix SOCKS5 usage for IMAP #3965
|
||||
- Exit from recently seen loop on interrupt channel errors to avoid busy looping #3966
|
||||
|
||||
### API-Changes
|
||||
- jsonrpc: add verified-by information to `Contact`-Object
|
||||
- Remove `attach_selfavatar` config #3951
|
||||
|
||||
### Changes
|
||||
- add debug logging support for webxdcs #3296
|
||||
|
||||
## 1.106.0
|
||||
|
||||
### Changes
|
||||
- Only send IncomingMsgBunch if there are more than 0 new messages #3941
|
||||
|
||||
### Fixes
|
||||
- fix: only send contact changed event for recently seen if it is relevant (not too old to matter) #3938
|
||||
- Immediately save `accounts.toml` if it was modified by a migration from absolute paths to relative paths #3943
|
||||
- Do not treat invalid email addresses as an exception #3942
|
||||
- Add timeouts to HTTP requests #3948
|
||||
|
||||
|
||||
## 1.105.0
|
||||
|
||||
### Changes
|
||||
- Validate signatures in try_decrypt() even if the message isn't encrypted #3859
|
||||
- Don't parse the message again after detached signatures validation #3862
|
||||
- Move format=flowed support to a separate crate #3869
|
||||
- cargo: bump quick-xml from 0.23.0 to 0.26.0 #3722
|
||||
- Add fuzzing tests #3853
|
||||
- Add mappings for some file types to Viewtype / MIME type #3881
|
||||
- Buffer IMAP client writes #3888
|
||||
- move `DC_CHAT_ID_ARCHIVED_LINK` to the top of chat lists
|
||||
and make `dc_get_fresh_msg_cnt()` work for `DC_CHAT_ID_ARCHIVED_LINK` #3918
|
||||
- make `dc_marknoticed_chat()` work for `DC_CHAT_ID_ARCHIVED_LINK` #3919
|
||||
- Update provider database
|
||||
|
||||
### API-Changes
|
||||
- jsonrpc: add python API for webxdc updates #3872
|
||||
- jsonrpc: add fresh message count to ChatListItemFetchResult::ArchiveLink
|
||||
- Add ffi functions to retrieve `verified by` information #3786
|
||||
- resultify `Message::get_filebytes()` #3925
|
||||
|
||||
### Fixes
|
||||
- Do not add an error if the message is encrypted but not signed #3860
|
||||
- Do not strip leading spaces from message lines #3867
|
||||
- Fix uncaught exception in JSON-RPC tests #3884
|
||||
- Fix STARTTLS connection and add a test for it #3907
|
||||
- Trigger reconnection when failing to fetch existing messages #3911
|
||||
- Do not retry fetching existing messages after failure, prevents infinite reconnection loop #3913
|
||||
- Ensure format=flowed formatting is always reversible on the receiver side #3880
|
||||
|
||||
|
||||
## 1.104.0
|
||||
|
||||
### Changes
|
||||
- Don't use deprecated `chrono` functions #3798
|
||||
- Document accounts manager #3837
|
||||
- If a classical-email-user sends an email to a group and adds new recipients,
|
||||
add the new recipients as group members #3781
|
||||
- Remove `pytest-async` plugin #3846
|
||||
- Only send the message about ephemeral timer change if the chat is promoted #3847
|
||||
- Use relative paths in `accounts.toml` #3838
|
||||
|
||||
### Fixes
|
||||
- Set read/write timeouts for IMAP over SOCKS5 #3833
|
||||
- Treat attached PGP keys as peer keys with mutual encryption preference #3832
|
||||
- fix migration of old databases #3842
|
||||
- Fix cargo clippy and doc errors after Rust update to 1.66 #3850
|
||||
- Don't send GroupNameChanged message if the group name doesn't change in terms of
|
||||
`improve_single_line_input()` #3852
|
||||
- Prefer encryption for the peer if the message is encrypted or signed with the known key #3849
|
||||
|
||||
|
||||
## 1.103.0
|
||||
|
||||
### Changes
|
||||
- Disable Autocrypt & Authres-checking for mailing lists,
|
||||
because they don't work well with mailing lists #3765
|
||||
- Refactor: Remove the remaining AsRef<str> #3669
|
||||
- Add more logging to `fetch_many_msgs` and refactor it #3811
|
||||
- Small speedup #3780
|
||||
- Log the reason when the message cannot be sent to the chat #3810
|
||||
- Add IMAP server ID line to the context info only when it is known #3814
|
||||
- Remove autogenerated typescript files #3815
|
||||
- Move functions that require an IMAP session from `Imap` to `Session`
|
||||
to reduce the number of code paths where IMAP session may not exist.
|
||||
Drop connection on error instead of trying to disconnect,
|
||||
potentially preventing IMAP task from getting stuck. #3812
|
||||
|
||||
### API-Changes
|
||||
- Add Python API to send reactions #3762
|
||||
- jsonrpc: add message errors to MessageObject #3788
|
||||
- jsonrpc: Add async Python client #3734
|
||||
|
||||
### Fixes
|
||||
- Make sure malformed messages will never block receiving further messages anymore #3771
|
||||
- strip leading/trailing whitespace from "Chat-Group-Name{,-Changed}:" headers content #3650
|
||||
- Assume all Thunderbird users prefer encryption #3774
|
||||
- refactor peerstate handling to ensure no duplicate peerstates #3776
|
||||
- Fetch messages in order of their INTERNALDATE (fixes reactions for Gmail f.e.) #3789
|
||||
- python: do not pass NULL to ffi.gc if the context can't be created #3818
|
||||
- Add read/write timeouts to IMAP sockets #3820
|
||||
- Add connection timeout to IMAP sockets #3828
|
||||
- Disable read timeout during IMAP IDLE #3826
|
||||
- Bots automatically accept mailing lists #3831
|
||||
|
||||
|
||||
## 1.102.0
|
||||
|
||||
### Changes
|
||||
|
||||
- If an email has multiple From addresses, handle this as if there was
|
||||
no From address, to prevent from forgery attacks. Also, improve
|
||||
handling of emails with invalid From addresses in general #3667
|
||||
|
||||
### API-Changes
|
||||
|
||||
### Fixes
|
||||
- fix detection of "All mail", "Trash", "Junk" etc folders. #3760
|
||||
- fetch messages sequentially to fix reactions on partially downloaded messages #3688
|
||||
- Fix a bug where one malformed message blocked receiving any further messages #3769
|
||||
|
||||
|
||||
## 1.101.0
|
||||
|
||||
### Changes
|
||||
- add `configured_inbox_folder` to account info #3748
|
||||
- `dc_delete_contact()` hides contacts if referenced #3751
|
||||
- add IMAP UIDs to message info #3755
|
||||
|
||||
### Fixes
|
||||
- improve IMAP logging, in particular fix incorrect "IMAP IDLE protocol
|
||||
timed out" message on network error during IDLE #3749
|
||||
- pop Recently Seen Loop event out of the queue when it is in the past
|
||||
to avoid busy looping #3753
|
||||
- fix build failures by going back to standard `async_zip` #3747
|
||||
|
||||
|
||||
## 1.100.0
|
||||
|
||||
### API-Changes
|
||||
- jsonrpc: add `miscSaveSticker` method
|
||||
|
||||
### Changes
|
||||
- add JSON-RPC stdio server `deltachat-rpc-server` and use it for JSON-RPC tests #3695
|
||||
- update rPGP from 0.8 to 0.9 #3737
|
||||
- jsonrpc: typescript client: use npm released deltachat fork of the tiny emitter package #3741
|
||||
- jsonrpc: show sticker image in quote #3744
|
||||
|
||||
|
||||
|
||||
## 1.99.0
|
||||
|
||||
### API-Changes
|
||||
- breaking jsonrpc: changed function naming
|
||||
- `autocryptInitiateKeyTransfer` -> `initiateAutocryptKeyTransfer`
|
||||
- `autocryptContinueKeyTransfer` -> `continueAutocryptKeyTransfer`
|
||||
- `chatlistGetFullChatById` -> `getFullChatById`
|
||||
- `messageGetMessage` -> `getMessage`
|
||||
- `messageGetMessages` -> `getMessages`
|
||||
- `messageGetNotificationInfo` -> `getMessageNotificationInfo`
|
||||
- `contactsGetContact` -> `getContact`
|
||||
- `contactsCreateContact` -> `createContact`
|
||||
- `contactsCreateChatByContactId` -> `createChatByContactId`
|
||||
- `contactsBlock` -> `blockContact`
|
||||
- `contactsUnblock` -> `unblockContact`
|
||||
- `contactsGetBlocked` -> `getBlockedContacts`
|
||||
- `contactsGetContactIds` -> `getContactIds`
|
||||
- `contactsGetContacts` -> `getContacts`
|
||||
- `contactsGetContactsByIds` -> `getContactsByIds`
|
||||
- `chatGetMedia` -> `getChatMedia`
|
||||
- `chatGetNeighboringMedia` -> `getNeighboringChatMedia`
|
||||
- `webxdcSendStatusUpdate` -> `sendWebxdcStatusUpdate`
|
||||
- `webxdcGetStatusUpdates` -> `getWebxdcStatusUpdates`
|
||||
- `messageGetWebxdcInfo` -> `getWebxdcInfo`
|
||||
- jsonrpc: changed method signature
|
||||
- `miscSendTextMessage(accountId, text, chatId)` -> `miscSendTextMessage(accountId, chatId, text)`
|
||||
- jsonrpc: add `SystemMessageType` to `Message`
|
||||
- cffi: add missing `DC_INFO_` constants
|
||||
- Add DC_EVENT_INCOMING_MSG_BUNCH event #3643
|
||||
- Python bindings: Make get_matching() only match the
|
||||
whole event name, e.g. events.get_matching("DC_EVENT_INCOMING_MSG")
|
||||
won't match DC_EVENT_INCOMING_MSG_BUNCH anymore #3643
|
||||
|
||||
|
||||
- Rust: Introduce a ContextBuilder #3698
|
||||
|
||||
### Changes
|
||||
- allow sender timestamp to be in the future, but not too much
|
||||
- Disable the new "Authentication-Results/DKIM checking" security feature
|
||||
until we have tested it a bit #3728
|
||||
- refactorings #3706
|
||||
|
||||
### Fixes
|
||||
- `dc_search_msgs()` returns unaccepted requests #3694
|
||||
- emit "contacts changed" event when the contact is no longer "seen recently" #3703
|
||||
- do not allow peerstate reset if DKIM check failed #3731
|
||||
|
||||
|
||||
## 1.98.0
|
||||
|
||||
### API-Changes
|
||||
- jsonrpc: typescript client: export constants under `C` enum, similar to how its exported from `deltachat-node` #3681
|
||||
- added reactions support #3644
|
||||
- jsonrpc: reactions: added reactions to `Message` type and the `sendReaction()` method #3686
|
||||
|
||||
### Changes
|
||||
- simplify `UPSERT` queries #3676
|
||||
|
||||
### Fixes
|
||||
|
||||
|
||||
## 1.97.0
|
||||
|
||||
### API-Changes
|
||||
- jsonrpc: add function: #3641, #3645, #3653
|
||||
- `getChatContacts()`
|
||||
- `createGroupChat()`
|
||||
- `createBroadcastList()`
|
||||
- `setChatName()`
|
||||
- `setChatProfileImage()`
|
||||
- `downloadFullMessage()`
|
||||
- `lookupContactIdByAddr()`
|
||||
- `sendVideochatInvitation()`
|
||||
- `searchMessages()`
|
||||
- `messageIdsToSearchResults()`
|
||||
- `setChatVisibility()`
|
||||
- `getChatEphemeralTimer()`
|
||||
- `setChatEphemeralTimer()`
|
||||
- `getLocations()`
|
||||
- `getAccountFileSize()`
|
||||
- `estimateAutoDeletionCount()`
|
||||
- `setStockStrings()`
|
||||
- `exportSelfKeys()`
|
||||
- `importSelfKeys()`
|
||||
- `sendSticker()`
|
||||
- `changeContactName()`
|
||||
- `deleteContact()`
|
||||
- `joinSecurejoin()`
|
||||
- `stopIoForAllAccounts()`
|
||||
- `startIoForAllAccounts()`
|
||||
- `startIo()`
|
||||
- `stopIo()`
|
||||
- `exportBackup()`
|
||||
- `importBackup()`
|
||||
- `getMessageHtml()` #3671
|
||||
- `miscGetStickerFolder` and `miscGetStickers` #3672
|
||||
- breaking: jsonrpc: remove function `messageListGetMessageIds()`, it is replaced by `getMessageIds()` and `getMessageListItems()` the latter returns a new `MessageListItem` type, which is the now preferred way of using the message list.
|
||||
- jsonrpc: add type: #3641, #3645
|
||||
- `MessageSearchResult`
|
||||
- `Location`
|
||||
- jsonrpc: add `viewType` to quoted message(`MessageQuote` type) in `Message` object type #3651
|
||||
|
||||
|
||||
### Changes
|
||||
- Look at Authentication-Results. Don't accept Autocrypt key changes
|
||||
if they come with negative authentiation results while this contact
|
||||
sent emails with positive authentication results in the past. #3583
|
||||
- jsonrpc in cffi also sends events now #3662
|
||||
- jsonrpc: new format for events and better typescript autocompletion
|
||||
- Join all "[migration] vXX" log messages into one
|
||||
|
||||
### Fixes
|
||||
- share stock string translations across accounts created by the same account manager #3640
|
||||
- suppress welcome device messages after account import #3642
|
||||
- fix unix timestamp used for daymarker #3660
|
||||
|
||||
## 1.96.0
|
||||
|
||||
### Changes
|
||||
- jsonrpc js client:
|
||||
- Change package name from `deltachat-jsonrpc-client` to `@deltachat/jsonrpc-client`
|
||||
- remove relative file dependency to it from `deltachat-node` (because it did not work anyway and broke the nix build of desktop)
|
||||
- ci: add github ci action to upload it to our download server automaticaly on realease
|
||||
- ci: add github ci action to upload it to our download server automatically on release
|
||||
|
||||
## 1.95.0
|
||||
|
||||
@@ -91,7 +495,7 @@
|
||||
- Auto accept contact requests if `Config::Bot` is set for a client #3567
|
||||
- Don't prepend the subject to chat messages in mailinglists
|
||||
- fix `set_core_version.py` script to also update version in `deltachat-jsonrpc/typescript/package.json` #3585
|
||||
- Reject webxcd-updates from contacts who are not group members #3568
|
||||
- Reject webxdc-updates from contacts who are not group members #3568
|
||||
|
||||
|
||||
## 1.93.0
|
||||
@@ -314,7 +718,7 @@
|
||||
|
||||
### Fixes
|
||||
- node: throw error when getting context with an invalid account id
|
||||
- node: throw error when instanciating a wrapper class on `null` (Context, Message, Chat, ChatList and so on)
|
||||
- node: throw error when instantiating a wrapper class on `null` (Context, Message, Chat, ChatList and so on)
|
||||
- use same contact-color if email address differ only in upper-/lowercase #3327
|
||||
- repair encrypted mails "mixed up" by Google Workspace "Append footer" function #3315
|
||||
|
||||
@@ -402,7 +806,7 @@
|
||||
- hopefully fix a bug where outgoing messages appear twice with Amazon SES #3077
|
||||
- do not delete messages without Message-IDs as duplicates #3095
|
||||
- assign replies from a different email address to the correct chat #3119
|
||||
- assing outgoing private replies to the correct chat #3177
|
||||
- assign outgoing private replies to the correct chat #3177
|
||||
- start ephemeral timer when seen status is synchronized via IMAP #3122
|
||||
- do not create empty contact requests with "setup changed" messages;
|
||||
instead, send a "setup changed" message into all chats we share with the peer #3187
|
||||
@@ -455,7 +859,7 @@
|
||||
- don't watch Sent folder by default #3025
|
||||
- use webxdc app name in chatlist/quotes/replies etc. #3027
|
||||
- make it possible to cancel message sending by removing the message #3034,
|
||||
this was previosuly removed in 1.71.0 #2939
|
||||
this was previously removed in 1.71.0 #2939
|
||||
- synchronize Seen flags only on watched folders to speed up
|
||||
folder scanning #3041
|
||||
- remove direct dependency on `byteorder` crate #3031
|
||||
@@ -1677,7 +2081,7 @@
|
||||
- #1043 avoid potential crashes in malformed From/Chat-Disposition... headers
|
||||
|
||||
- #1045 #1041 #1038 #1035 #1034 #1029 #1025 various cleanups and doc
|
||||
improvments
|
||||
improvements
|
||||
|
||||
## 1.0.0-beta.16
|
||||
|
||||
@@ -1756,7 +2160,7 @@
|
||||
- trigger reconnect more often on imap error states. Should fix an
|
||||
issue observed when trying to empty a folder. @hpk42
|
||||
|
||||
- un-split qr tests: we fixed qr-securejoin protocol flakyness
|
||||
- un-split qr tests: we fixed qr-securejoin protocol flakiness
|
||||
last weeks. @hpk42
|
||||
|
||||
## 1.0.0-beta.10
|
||||
@@ -1794,7 +2198,7 @@
|
||||
|
||||
- fix moving self-sent messages, thanks @r10s, @flub, @hpk42
|
||||
|
||||
- fix flakyness/sometimes-failing verified/join-protocols,
|
||||
- fix flakiness/sometimes-failing verified/join-protocols,
|
||||
thanks @flub, @r10s, @hpk42
|
||||
|
||||
- fix reply-to-encrypted message to keep encryption
|
||||
@@ -1813,7 +2217,7 @@
|
||||
|
||||
- fixes imap-protocol parsing bugs that lead to infinitely
|
||||
repeated crashing while trying to receive messages with
|
||||
a subjec that contained non-utf8. thanks @link2xt
|
||||
a subject that contained non-utf8. thanks @link2xt
|
||||
|
||||
- fixed logic to find encryption subkey -- previously
|
||||
delta chat would use the primary key for encryption
|
||||
@@ -1921,3 +2325,7 @@
|
||||
For a full list of changes, please see our closed Pull Requests:
|
||||
|
||||
https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
|
||||
|
||||
[unreleased]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.0...HEAD
|
||||
[1.111.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.110.0...v1.111.0
|
||||
[1.112.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.111.0...v1.112.0
|
||||
|
||||
3191
Cargo.lock
generated
3191
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
107
Cargo.toml
107
Cargo.toml
@@ -1,63 +1,79 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.96.0"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
version = "1.112.0"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.56"
|
||||
rust-version = "1.64"
|
||||
|
||||
[profile.dev]
|
||||
debug = 0
|
||||
panic = 'abort'
|
||||
opt-level = 1
|
||||
|
||||
[profile.test]
|
||||
opt-level = 0
|
||||
|
||||
# Always optimize dependencies.
|
||||
# This does not apply to crates in the workspace.
|
||||
# <https://doc.rust-lang.org/cargo/reference/profiles.html#overrides>
|
||||
[profile.dev.package."*"]
|
||||
opt-level = "z"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
panic = 'abort'
|
||||
opt-level = "z"
|
||||
|
||||
[patch.crates-io]
|
||||
default-net = { git = "https://github.com/dignifiedquire/default-net.git", branch="feat-android" }
|
||||
quinn-udp = { git = "https://github.com/quinn-rs/quinn", branch="main" }
|
||||
quinn-proto = { git = "https://github.com/quinn-rs/quinn", branch="main" }
|
||||
|
||||
[dependencies]
|
||||
deltachat_derive = { path = "./deltachat_derive" }
|
||||
format-flowed = { path = "./format-flowed" }
|
||||
ratelimit = { path = "./deltachat-ratelimit" }
|
||||
|
||||
ansi_term = { version = "0.12.1", optional = true }
|
||||
anyhow = "1"
|
||||
async-channel = "1.8.0"
|
||||
async-imap = { git = "https://github.com/async-email/async-imap", branch = "master", default-features = false, features = ["runtime-tokio"] }
|
||||
async-native-tls = { version = "0.4", default-features = false, features = ["runtime-tokio"] }
|
||||
async-smtp = { version = "0.5", default-features = false, features = ["smtp-transport", "socks5", "runtime-tokio"] }
|
||||
trust-dns-resolver = "0.21"
|
||||
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
|
||||
tokio-tar = { version = "0.3" } # TODO: integrate tokio into async-tar
|
||||
async-native-tls = { version = "0.5", default-features = false, features = ["runtime-tokio"] }
|
||||
async-smtp = { version = "0.9", default-features = false, features = ["runtime-tokio"] }
|
||||
async_zip = { version = "0.0.11", default-features = false, features = ["deflate", "fs"] }
|
||||
backtrace = "0.3"
|
||||
base64 = "0.13"
|
||||
base64 = "0.21"
|
||||
bitflags = "1.3"
|
||||
chrono = { version = "0.4", default-features=false, features = ["clock", "std"] }
|
||||
dirs = { version = "4", optional=true }
|
||||
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
|
||||
encoded-words = { git = "https://github.com/async-email/encoded-words", branch = "master" }
|
||||
escaper = "0.1"
|
||||
fast-socks5 = "0.8"
|
||||
futures = "0.3"
|
||||
futures-lite = "1.12.0"
|
||||
hex = "0.4.0"
|
||||
image = { version = "0.24.3", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||
humansize = "2"
|
||||
image = { version = "0.24.5", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||
# iroh = { version = "0.3.0", default-features = false }
|
||||
iroh = { git = 'https://github.com/n0-computer/iroh', branch = "main" }
|
||||
kamadak-exif = "0.5"
|
||||
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
|
||||
libc = "0.2"
|
||||
log = {version = "0.4.16", optional = true }
|
||||
mailparse = "0.13"
|
||||
native-tls = "0.2"
|
||||
num_cpus = "1.13"
|
||||
mailparse = "0.14"
|
||||
num_cpus = "1.15"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
once_cell = "1.13.1"
|
||||
percent-encoding = "2.0"
|
||||
pgp = { version = "0.8", default-features = false }
|
||||
once_cell = "1.17.0"
|
||||
percent-encoding = "2.2"
|
||||
parking_lot = "0.12"
|
||||
pgp = { version = "0.9", default-features = false }
|
||||
pretty_env_logger = { version = "0.4", optional = true }
|
||||
quick-xml = "0.23"
|
||||
r2d2 = "0.8"
|
||||
r2d2_sqlite = "0.20"
|
||||
qrcodegen = "1.7.0"
|
||||
quick-xml = "0.27"
|
||||
rand = "0.8"
|
||||
regex = "1.6"
|
||||
rusqlite = { version = "0.27", features = ["sqlcipher"] }
|
||||
regex = "1.7"
|
||||
reqwest = { version = "0.11.14", features = ["json"] }
|
||||
rusqlite = { version = "0.28", features = ["sqlcipher"] }
|
||||
rust-hsluv = "0.1"
|
||||
rustyline = { version = "10", optional = true }
|
||||
sanitize-filename = "0.4"
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
@@ -66,47 +82,43 @@ sha2 = "0.10"
|
||||
smallvec = "1"
|
||||
strum = "0.24"
|
||||
strum_macros = "0.24"
|
||||
tagger = "4.3.4"
|
||||
textwrap = "0.16.0"
|
||||
thiserror = "1"
|
||||
toml = "0.5"
|
||||
tokio-io-timeout = "1.2.0"
|
||||
tokio-stream = { version = "0.1.11", features = ["fs"] }
|
||||
tokio-tar = { version = "0.3" } # TODO: integrate tokio into async-tar
|
||||
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
|
||||
toml = "0.7"
|
||||
trust-dns-resolver = "0.22"
|
||||
url = "2"
|
||||
uuid = { version = "1", features = ["serde", "v4"] }
|
||||
fast-socks5 = "0.8"
|
||||
humansize = "1"
|
||||
qrcodegen = "1.7.0"
|
||||
tagger = "4.3.3"
|
||||
textwrap = "0.15.0"
|
||||
async-channel = "1.6.1"
|
||||
futures-lite = "1.12.0"
|
||||
tokio-stream = { version = "0.1.9", features = ["fs"] }
|
||||
reqwest = { version = "0.11.11", features = ["json"] }
|
||||
async_zip = { git = "https://github.com/dignifiedquire/rs-async-zip", branch = "main", default-features = false, features = ["deflate"] }
|
||||
|
||||
[dev-dependencies]
|
||||
ansi_term = "0.12.0"
|
||||
criterion = { version = "0.3.6", features = ["async_tokio"] }
|
||||
criterion = { version = "0.4.0", features = ["async_tokio"] }
|
||||
futures-lite = "1.12"
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.4"
|
||||
proptest = { version = "1", default-features = false, features = ["std"] }
|
||||
tempfile = "3"
|
||||
testdir = "0.7.2"
|
||||
tokio = { version = "1", features = ["parking_lot", "rt-multi-thread", "macros"] }
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"deltachat-ffi",
|
||||
"deltachat_derive",
|
||||
"deltachat-jsonrpc"
|
||||
"deltachat-jsonrpc",
|
||||
"deltachat-rpc-server",
|
||||
"deltachat-ratelimit",
|
||||
"deltachat-repl",
|
||||
"format-flowed",
|
||||
]
|
||||
|
||||
[[example]]
|
||||
name = "simple"
|
||||
path = "examples/simple.rs"
|
||||
required-features = ["repl"]
|
||||
|
||||
[[example]]
|
||||
name = "repl"
|
||||
path = "examples/repl/main.rs"
|
||||
required-features = ["repl"]
|
||||
|
||||
|
||||
[[bench]]
|
||||
@@ -133,14 +145,15 @@ harness = false
|
||||
name = "get_chatlist"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "send_events"
|
||||
harness = false
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
internals = []
|
||||
repl = ["internals", "rustyline", "log", "pretty_env_logger", "ansi_term", "dirs"]
|
||||
vendored = [
|
||||
"async-native-tls/vendored",
|
||||
"async-smtp/native-tls-vendored",
|
||||
"rusqlite/bundled-sqlcipher-vendored-openssl",
|
||||
"reqwest/native-tls-vendored"
|
||||
]
|
||||
nightly = ["pgp/nightly"]
|
||||
|
||||
57
README.md
57
README.md
@@ -19,10 +19,19 @@ $ curl https://sh.rustup.rs -sSf | sh
|
||||
Compile and run Delta Chat Core command line utility, using `cargo`:
|
||||
|
||||
```
|
||||
$ RUST_LOG=repl=info cargo run --example repl --features repl -- ~/deltachat-db
|
||||
$ RUST_LOG=deltachat_repl=info cargo run -p deltachat-repl -- ~/deltachat-db
|
||||
```
|
||||
where ~/deltachat-db is the database file. Delta Chat will create it if it does not exist.
|
||||
|
||||
Optionally, install `deltachat-repl` binary with
|
||||
```
|
||||
$ cargo install --path deltachat-repl/
|
||||
```
|
||||
and run as
|
||||
```
|
||||
$ deltachat-repl ~/deltachat-db
|
||||
```
|
||||
|
||||
Configure your account (if not already configured):
|
||||
|
||||
```
|
||||
@@ -104,7 +113,7 @@ $ cargo build -p deltachat_ffi --release
|
||||
|
||||
- `DCC_MIME_DEBUG`: if set outgoing and incoming message will be printed
|
||||
|
||||
- `RUST_LOG=repl=info,async_imap=trace,async_smtp=trace`: enable IMAP and
|
||||
- `RUST_LOG=deltachat_repl=info,async_imap=trace,async_smtp=trace`: enable IMAP and
|
||||
SMTP tracing in addition to info messages.
|
||||
|
||||
### Expensive tests
|
||||
@@ -115,20 +124,56 @@ use the `--ignored` argument to the test binary (not to cargo itself):
|
||||
$ cargo test -- --ignored
|
||||
```
|
||||
|
||||
### Fuzzing
|
||||
|
||||
Install [`cargo-bolero`](https://github.com/camshaft/bolero) with
|
||||
```sh
|
||||
$ cargo install cargo-bolero
|
||||
```
|
||||
|
||||
Run fuzzing tests with
|
||||
```sh
|
||||
$ cd fuzz
|
||||
$ cargo bolero test fuzz_mailparse --release=false -s NONE
|
||||
```
|
||||
|
||||
Corpus is created at `fuzz/fuzz_targets/corpus`,
|
||||
you can add initial inputs there.
|
||||
For `fuzz_mailparse` target corpus can be populated with
|
||||
`../test-data/message/*.eml`.
|
||||
|
||||
To run with AFL instead of libFuzzer:
|
||||
```sh
|
||||
$ cargo bolero test fuzz_format_flowed --release=false -e afl -s NONE
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- `vendored`: When using Openssl for TLS, this bundles a vendored version.
|
||||
- `nightly`: Enable nightly only performance and security related features.
|
||||
|
||||
## Update Provider Data
|
||||
|
||||
To add the updates from the
|
||||
[provider-db](https://github.com/deltachat/provider-db) to the core, run:
|
||||
|
||||
```
|
||||
./src/provider/update.py ../provider-db/_providers/ > src/provider/data.rs
|
||||
```
|
||||
|
||||
## Language bindings and frontend projects
|
||||
|
||||
Language bindings are available for:
|
||||
|
||||
- **C** \[[📂 source](./deltachat-ffi) | [📚 docs](https://c.delta.chat)\]
|
||||
- **Node.js** \[[📂 source](./node) | [📦 npm](https://www.npmjs.com/package/deltachat-node) | [📚 docs](https://js.delta.chat)\]
|
||||
- **Node.js**
|
||||
- over cffi (legacy): \[[📂 source](./node) | [📦 npm](https://www.npmjs.com/package/deltachat-node) | [📚 docs](https://js.delta.chat)\]
|
||||
- over jsonrpc built with napi.rs: \[[📂 source](https://github.com/deltachat/napi-jsonrpc) | [📦 npm](https://www.npmjs.com/package/@deltachat/napi-jsonrpc)\]
|
||||
- **Python** \[[📂 source](./python) | [📦 pypi](https://pypi.org/project/deltachat) | [📚 docs](https://py.delta.chat)\]
|
||||
- **Go** \[[📂 source](https://github.com/deltachat/go-deltachat/)\]
|
||||
- **Free Pascal** \[[📂 source](https://github.com/deltachat/deltachat-fp/)\]
|
||||
- **Go**
|
||||
- over jsonrpc: \[[📂 source](https://github.com/deltachat/deltachat-rpc-client-go/)\]
|
||||
- over cffi[^1]: \[[📂 source](https://github.com/deltachat/go-deltachat/)\]
|
||||
- **Free Pascal**[^1] \[[📂 source](https://github.com/deltachat/deltachat-fp/)\]
|
||||
- **Java** and **Swift** (contained in the Android/iOS repos)
|
||||
|
||||
The following "frontend" projects make use of the Rust-library
|
||||
@@ -140,3 +185,5 @@ or its language bindings:
|
||||
- [Pidgin](https://code.ur.gs/lupine/purple-plugin-delta/)
|
||||
- [Telepathy](https://code.ur.gs/lupine/telepathy-padfoot/)
|
||||
- several **Bots**
|
||||
|
||||
[^1]: Out of date / unmaintained, if you like those languages feel free to start maintaining them. If you have questions we'll help you, please ask in the issues.
|
||||
|
||||
BIN
assets/icon-archive.png
Normal file
BIN
assets/icon-archive.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
60
assets/icon-archive.svg
Normal file
60
assets/icon-archive.svg
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="60"
|
||||
height="60"
|
||||
viewBox="0 0 60 60"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="feather feather-archive"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="icon-archive.svg"
|
||||
inkscape:version="1.2.2 (b0a84865, 2022-12-01)"
|
||||
inkscape:export-filename="icon-archive.png"
|
||||
inkscape:export-xdpi="409.60001"
|
||||
inkscape:export-ydpi="409.60001"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs12" />
|
||||
<sodipodi:namedview
|
||||
id="namedview10"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="6.4597151"
|
||||
inkscape:cx="24.459283"
|
||||
inkscape:cy="32.509174"
|
||||
inkscape:window-width="1457"
|
||||
inkscape:window-height="860"
|
||||
inkscape:window-x="55"
|
||||
inkscape:window-y="38"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg8" />
|
||||
<g
|
||||
id="g846"
|
||||
transform="translate(0.558605,0.464417)">
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#808080;stroke-width:1.78186;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 38.749006,25.398867 V 38.843194 H 20.133784 V 25.398867"
|
||||
id="path847" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#808080;stroke-width:1.78186;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 18.065427,20.227972 h 22.751936 v 5.170894 H 18.065427 Z"
|
||||
id="path845" />
|
||||
<path
|
||||
style="fill:#ff0000;fill-opacity:1;stroke:#808080;stroke-width:1.78186;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 27.373036,29.535581 h 4.136718"
|
||||
id="line6" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -1,6 +1,7 @@
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use deltachat::contact::Contact;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::stock_str::StockStrings;
|
||||
use deltachat::Events;
|
||||
use tempfile::tempdir;
|
||||
|
||||
@@ -8,10 +9,12 @@ async fn address_book_benchmark(n: u32, read_count: u32) {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let id = 100;
|
||||
let context = Context::new(&dbfile, id, Events::new()).await.unwrap();
|
||||
let context = Context::new(&dbfile, id, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let book = (0..n)
|
||||
.map(|i| format!("Name {}\naddr{}@example.org\n", i, i))
|
||||
.map(|i| format!("Name {i}\naddr{i}@example.org\n"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("");
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use deltachat::accounts::Accounts;
|
||||
use std::path::PathBuf;
|
||||
use tempfile::tempdir;
|
||||
|
||||
async fn create_accounts(n: u32) {
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
use std::path::Path;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
use deltachat::chat::{self, ChatId};
|
||||
use deltachat::chatlist::Chatlist;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::stock_str::StockStrings;
|
||||
use deltachat::Events;
|
||||
|
||||
async fn get_chat_msgs_benchmark(dbfile: &Path, chats: &[ChatId]) {
|
||||
let id = 100;
|
||||
let context = Context::new(dbfile, id, Events::new()).await.unwrap();
|
||||
let context = Context::new(dbfile, id, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for c in chats.iter().take(10) {
|
||||
black_box(chat::get_chat_msgs(&context, *c, 0).await.ok());
|
||||
black_box(chat::get_chat_msgs(&context, *c).await.ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +25,7 @@ fn criterion_benchmark(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
let chats: Vec<_> = rt.block_on(async {
|
||||
let context = Context::new(Path::new(&path), 100, Events::new())
|
||||
let context = Context::new(Path::new(&path), 100, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap();
|
||||
let chatlist = Chatlist::try_load(&context, 0, None, None).await.unwrap();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::path::Path;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
use deltachat::chatlist::Chatlist;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::stock_str::StockStrings;
|
||||
use deltachat::Events;
|
||||
|
||||
async fn get_chat_list_benchmark(context: &Context) {
|
||||
@@ -16,7 +16,7 @@ fn criterion_benchmark(c: &mut Criterion) {
|
||||
if let Ok(path) = std::env::var("DELTACHAT_BENCHMARK_DATABASE") {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let context = rt.block_on(async {
|
||||
Context::new(Path::new(&path), 100, Events::new())
|
||||
Context::new(Path::new(&path), 100, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ use deltachat::{
|
||||
context::Context,
|
||||
imex::{imex, ImexMode},
|
||||
receive_imf::receive_imf,
|
||||
stock_str::StockStrings,
|
||||
Events,
|
||||
};
|
||||
use tempfile::tempdir;
|
||||
@@ -37,11 +38,66 @@ Hello {i}",
|
||||
context
|
||||
}
|
||||
|
||||
/// Receive 100 emails that remove charlie@example.com and add
|
||||
/// him back
|
||||
async fn recv_groupmembership_emails(context: Context) -> Context {
|
||||
for i in 0..50 {
|
||||
let imf_raw = format!(
|
||||
"Subject: Benchmark
|
||||
Message-ID: Gr.OssSYnOFkhR.{i}@testrun.org
|
||||
Date: Sat, 07 Dec 2019 19:00:27 +0000
|
||||
To: alice@example.com, b@example.com, c@example.com, d@example.com, e@example.com, f@example.com
|
||||
From: sender@testrun.org
|
||||
Chat-Version: 1.0
|
||||
Chat-Disposition-Notification-To: sender@testrun.org
|
||||
Chat-User-Avatar: 0
|
||||
Chat-Group-Member-Added: charlie@example.com
|
||||
In-Reply-To: Gr.OssSYnOFkhR.{i_dec}@testrun.org
|
||||
MIME-Version: 1.0
|
||||
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
Hello {i}",
|
||||
i = i,
|
||||
i_dec = i - 1,
|
||||
);
|
||||
receive_imf(&context, black_box(imf_raw.as_bytes()), false)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let imf_raw = format!(
|
||||
"Subject: Benchmark
|
||||
Message-ID: Gr.OssSYnOFkhR.{i}@testrun.org
|
||||
Date: Sat, 07 Dec 2019 19:00:27 +0000
|
||||
To: alice@example.com, b@example.com, c@example.com, d@example.com, e@example.com, f@example.com
|
||||
From: sender@testrun.org
|
||||
Chat-Version: 1.0
|
||||
Chat-Disposition-Notification-To: sender@testrun.org
|
||||
Chat-User-Avatar: 0
|
||||
Chat-Group-Member-Removed: charlie@example.com
|
||||
In-Reply-To: Gr.OssSYnOFkhR.{i_dec}@testrun.org
|
||||
MIME-Version: 1.0
|
||||
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
Hello {i}",
|
||||
i = i,
|
||||
i_dec = i - 1,
|
||||
);
|
||||
receive_imf(&context, black_box(imf_raw.as_bytes()), false)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
context
|
||||
}
|
||||
|
||||
async fn create_context() -> Context {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let id = 100;
|
||||
let context = Context::new(&dbfile, id, Events::new()).await.unwrap();
|
||||
let context = Context::new(dbfile.as_path(), id, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let backup: PathBuf = std::env::current_dir()
|
||||
.unwrap()
|
||||
@@ -49,7 +105,7 @@ async fn create_context() -> Context {
|
||||
|
||||
if backup.exists() {
|
||||
println!("Importing backup");
|
||||
imex(&context, ImexMode::ImportBackup, &backup, None)
|
||||
imex(&context, ImexMode::ImportBackup, backup.as_path(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@@ -80,6 +136,20 @@ fn criterion_benchmark(c: &mut Criterion) {
|
||||
}
|
||||
});
|
||||
});
|
||||
group.bench_function(
|
||||
"Receive 100 Chat-Group-Member-{Added|Removed} messages",
|
||||
|b| {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let context = rt.block_on(create_context());
|
||||
|
||||
b.to_async(&rt).iter(|| {
|
||||
let ctx = context.clone();
|
||||
async move {
|
||||
recv_groupmembership_emails(black_box(ctx)).await;
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::path::Path;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use deltachat::context::Context;
|
||||
use deltachat::stock_str::StockStrings;
|
||||
use deltachat::Events;
|
||||
use std::path::Path;
|
||||
|
||||
async fn search_benchmark(dbfile: impl AsRef<Path>) {
|
||||
let id = 100;
|
||||
let context = Context::new(dbfile.as_ref(), id, Events::new())
|
||||
let context = Context::new(dbfile.as_ref(), id, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
47
benches/send_events.rs
Normal file
47
benches/send_events.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
use deltachat::context::Context;
|
||||
use deltachat::stock_str::StockStrings;
|
||||
use deltachat::{info, Event, EventType, Events};
|
||||
use tempfile::tempdir;
|
||||
|
||||
async fn send_events_benchmark(context: &Context) {
|
||||
let emitter = context.get_event_emitter();
|
||||
for _i in 0..1_000_000 {
|
||||
info!(context, "interesting event...");
|
||||
}
|
||||
info!(context, "DONE");
|
||||
|
||||
loop {
|
||||
match emitter.recv().await.unwrap() {
|
||||
Event {
|
||||
typ: EventType::Info(info),
|
||||
..
|
||||
} if info.contains("DONE") => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
let context = rt.block_on(async {
|
||||
Context::new(&dbfile, 100, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.expect("failed to create context")
|
||||
});
|
||||
let executor = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
c.bench_function("Sending 1.000.000 events", |b| {
|
||||
b.to_async(&executor)
|
||||
.iter(|| send_events_benchmark(&context))
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
@@ -1,8 +1,7 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.96.0"
|
||||
version = "1.112.0"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
license = "MPL-2.0"
|
||||
@@ -18,18 +17,17 @@ crate-type = ["cdylib", "staticlib"]
|
||||
deltachat = { path = "../", default-features = false }
|
||||
deltachat-jsonrpc = { path = "../deltachat-jsonrpc", optional = true }
|
||||
libc = "0.2"
|
||||
human-panic = "1"
|
||||
human-panic = { version = "1", default-features = false }
|
||||
num-traits = "0.2"
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1", features = ["rt-multi-thread"] }
|
||||
anyhow = "1"
|
||||
thiserror = "1"
|
||||
rand = "0.7"
|
||||
once_cell = "1.13.1"
|
||||
rand = "0.8"
|
||||
once_cell = "1.17.0"
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
vendored = ["deltachat/vendored"]
|
||||
nightly = ["deltachat/nightly"]
|
||||
jsonrpc = ["deltachat-jsonrpc"]
|
||||
jsonrpc = ["dep:deltachat-jsonrpc"]
|
||||
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
|
||||
:root {
|
||||
--accent: hsl(0 0% 85%);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--accent: hsl(0 0% 25%);
|
||||
}
|
||||
}
|
||||
|
||||
/* the code snippet frame, defaults to white which tends to get badly readable in combination with explaining text around */
|
||||
div.fragment {
|
||||
background-color: #e0e0e0;
|
||||
background-color: var(--accent);
|
||||
border: 0;
|
||||
padding: 1em;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #e0e0e0;
|
||||
background-color: var(--accent);
|
||||
padding-left: .5em;
|
||||
padding-right: .5em;
|
||||
border-radius: 6px;
|
||||
|
||||
@@ -11,18 +11,20 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct _dc_context dc_context_t;
|
||||
typedef struct _dc_accounts dc_accounts_t;
|
||||
typedef struct _dc_array dc_array_t;
|
||||
typedef struct _dc_chatlist dc_chatlist_t;
|
||||
typedef struct _dc_chat dc_chat_t;
|
||||
typedef struct _dc_msg dc_msg_t;
|
||||
typedef struct _dc_contact dc_contact_t;
|
||||
typedef struct _dc_lot dc_lot_t;
|
||||
typedef struct _dc_provider dc_provider_t;
|
||||
typedef struct _dc_event dc_event_t;
|
||||
typedef struct _dc_context dc_context_t;
|
||||
typedef struct _dc_accounts dc_accounts_t;
|
||||
typedef struct _dc_array dc_array_t;
|
||||
typedef struct _dc_chatlist dc_chatlist_t;
|
||||
typedef struct _dc_chat dc_chat_t;
|
||||
typedef struct _dc_msg dc_msg_t;
|
||||
typedef struct _dc_reactions dc_reactions_t;
|
||||
typedef struct _dc_contact dc_contact_t;
|
||||
typedef struct _dc_lot dc_lot_t;
|
||||
typedef struct _dc_provider dc_provider_t;
|
||||
typedef struct _dc_event dc_event_t;
|
||||
typedef struct _dc_event_emitter dc_event_emitter_t;
|
||||
typedef struct _dc_jsonrpc_instance dc_jsonrpc_instance_t;
|
||||
typedef struct _dc_backup_provider dc_backup_provider_t;
|
||||
|
||||
// Alias for backwards compatibility, use dc_event_emitter_t instead.
|
||||
typedef struct _dc_event_emitter dc_accounts_event_emitter_t;
|
||||
@@ -611,7 +613,7 @@ int dc_all_work_done (dc_context_t* context);
|
||||
* While dc_configure() returns immediately,
|
||||
* the started configuration-job may take a while.
|
||||
*
|
||||
* During configuration, #DC_EVENT_CONFIGURE_PROGRESS events are emmited;
|
||||
* During configuration, #DC_EVENT_CONFIGURE_PROGRESS events are emitted;
|
||||
* they indicate a successful configuration as well as errors
|
||||
* and may be used to create a progress bar.
|
||||
*
|
||||
@@ -868,7 +870,7 @@ uint32_t dc_get_chat_id_by_contact_id (dc_context_t* context, uint32_t co
|
||||
* @param context The context object as returned from dc_context_new().
|
||||
* @param chat_id The chat ID to send the message to.
|
||||
* @param msg The message object to send to the chat defined by the chat ID.
|
||||
* On succcess, msg_id and state of the object are set up,
|
||||
* On success, msg_id and state of the object are set up,
|
||||
* The function does not take ownership of the object,
|
||||
* so you have to free it using dc_msg_unref() as usual.
|
||||
* @return The ID of the message that is being prepared.
|
||||
@@ -879,7 +881,7 @@ uint32_t dc_prepare_msg (dc_context_t* context, uint32_t ch
|
||||
/**
|
||||
* Send a message defined by a dc_msg_t object to a chat.
|
||||
*
|
||||
* Sends the event #DC_EVENT_MSGS_CHANGED on succcess.
|
||||
* Sends the event #DC_EVENT_MSGS_CHANGED on success.
|
||||
* However, this does not imply, the message really reached the recipient -
|
||||
* sending may be delayed e.g. due to network problems. However, from your
|
||||
* view, you're done with the message. Sooner or later it will find its way.
|
||||
@@ -908,7 +910,7 @@ uint32_t dc_prepare_msg (dc_context_t* context, uint32_t ch
|
||||
* @param chat_id The chat ID to send the message to.
|
||||
* If dc_prepare_msg() was called before, this parameter can be 0.
|
||||
* @param msg The message object to send to the chat defined by the chat ID.
|
||||
* On succcess, msg_id of the object is set up,
|
||||
* On success, msg_id of the object is set up,
|
||||
* The function does not take ownership of the object,
|
||||
* so you have to free it using dc_msg_unref() as usual.
|
||||
* @return The ID of the message that is about to be sent. 0 in case of errors.
|
||||
@@ -925,7 +927,7 @@ uint32_t dc_send_msg (dc_context_t* context, uint32_t ch
|
||||
* @param chat_id The chat ID to send the message to.
|
||||
* If dc_prepare_msg() was called before, this parameter can be 0.
|
||||
* @param msg The message object to send to the chat defined by the chat ID.
|
||||
* On succcess, msg_id of the object is set up,
|
||||
* On success, msg_id of the object is set up,
|
||||
* The function does not take ownership of the object,
|
||||
* so you have to free it using dc_msg_unref() as usual.
|
||||
* @return The ID of the message that is about to be sent. 0 in case of errors.
|
||||
@@ -936,7 +938,7 @@ uint32_t dc_send_msg_sync (dc_context_t* context, uint32
|
||||
/**
|
||||
* Send a simple text message a given chat.
|
||||
*
|
||||
* Sends the event #DC_EVENT_MSGS_CHANGED on succcess.
|
||||
* Sends the event #DC_EVENT_MSGS_CHANGED on success.
|
||||
* However, this does not imply, the message really reached the recipient -
|
||||
* sending may be delayed e.g. due to network problems. However, from your
|
||||
* view, you're done with the message. Sooner or later it will find its way.
|
||||
@@ -991,6 +993,34 @@ uint32_t dc_send_text_msg (dc_context_t* context, uint32_t ch
|
||||
uint32_t dc_send_videochat_invitation (dc_context_t* context, uint32_t chat_id);
|
||||
|
||||
|
||||
/**
|
||||
* Send a reaction to message.
|
||||
*
|
||||
* Reaction is a string of emojis separated by spaces. Reaction to a
|
||||
* single message can be sent multiple times. The last reaction
|
||||
* received overrides all previously received reactions. It is
|
||||
* possible to remove all reactions by sending an empty string.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object.
|
||||
* @param msg_id ID of the message you react to.
|
||||
* @param reaction A string consisting of emojis separated by spaces.
|
||||
* @return The ID of the message sent out or 0 for errors.
|
||||
*/
|
||||
uint32_t dc_send_reaction (dc_context_t* context, uint32_t msg_id, char *reaction);
|
||||
|
||||
|
||||
/**
|
||||
* Get a structure with reactions to the message.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object.
|
||||
* @param msg_id The message ID to get reactions for.
|
||||
* @return A structure with all reactions to the message.
|
||||
*/
|
||||
dc_reactions_t* dc_get_msg_reactions (dc_context_t *context, int msg_id);
|
||||
|
||||
|
||||
/**
|
||||
* A webxdc instance sends a status update to its other members.
|
||||
*
|
||||
@@ -1117,7 +1147,7 @@ void dc_set_draft (dc_context_t* context, uint32_t ch
|
||||
* // not now and not when this code is executed again
|
||||
* dc_add_device_msg(context, "update-123", NULL);
|
||||
* } else {
|
||||
* // welcome message was not added now, this is an oder installation,
|
||||
* // welcome message was not added now, this is an older installation,
|
||||
* // add a changelog
|
||||
* dc_add_device_msg(context, "update-123", changelog_msg);
|
||||
* }
|
||||
@@ -1130,7 +1160,7 @@ uint32_t dc_add_device_msg (dc_context_t* context, const char*
|
||||
|
||||
/**
|
||||
* Check if a device-message with a given label was ever added.
|
||||
* Device-messages can be added dc_add_device_msg().
|
||||
* Device-messages can be added with dc_add_device_msg().
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object.
|
||||
@@ -1198,7 +1228,11 @@ int dc_get_msg_cnt (dc_context_t* context, uint32_t ch
|
||||
* Get the number of _fresh_ messages in a chat.
|
||||
* Typically used to implement a badge with a number in the chatlist.
|
||||
*
|
||||
* If the specified chat is muted,
|
||||
* As muted archived chats are not unarchived automatically,
|
||||
* a similar information is needed for the @ref dc_get_chatlist() "archive link" as well:
|
||||
* here, the number of archived chats containing fresh messages is returned.
|
||||
*
|
||||
* If the specified chat is muted or the @ref dc_get_chatlist() "archive link",
|
||||
* the UI should show the badge counter "less obtrusive",
|
||||
* e.g. using "gray" instead of "red" color.
|
||||
*
|
||||
@@ -2027,8 +2061,9 @@ char* dc_get_contact_encrinfo (dc_context_t* context, uint32_t co
|
||||
|
||||
|
||||
/**
|
||||
* Delete a contact. The contact is deleted from the local device. It may happen that this is not
|
||||
* possible as the contact is in use. In this case, the contact can be blocked.
|
||||
* Delete a contact so that it disappears from the corresponding lists.
|
||||
* Depending on whether there are ongoing chats, deletion is done by physical deletion or hiding.
|
||||
* The contact is deleted from the local device.
|
||||
*
|
||||
* May result in a #DC_EVENT_CONTACTS_CHANGED event.
|
||||
*
|
||||
@@ -2066,8 +2101,7 @@ dc_contact_t* dc_get_contact (dc_context_t* context, uint32_t co
|
||||
|
||||
/**
|
||||
* Import/export things.
|
||||
* During backup import/export IO must not be started,
|
||||
* if needed stop IO using dc_accounts_stop_io() or dc_stop_io() first.
|
||||
*
|
||||
* What to do is defined by the _what_ parameter which may be one of the following:
|
||||
*
|
||||
* - **DC_IMEX_EXPORT_BACKUP** (11) - Export a backup to the directory given as `param1`
|
||||
@@ -2261,6 +2295,7 @@ void dc_stop_ongoing_process (dc_context_t* context);
|
||||
#define DC_QR_FPR_MISMATCH 220 // id=contact
|
||||
#define DC_QR_FPR_WITHOUT_ADDR 230 // test1=formatted fingerprint
|
||||
#define DC_QR_ACCOUNT 250 // text1=domain
|
||||
#define DC_QR_BACKUP 251
|
||||
#define DC_QR_WEBRTC_INSTANCE 260 // text1=domain, text2=instance pattern
|
||||
#define DC_QR_ADDR 320 // id=contact
|
||||
#define DC_QR_TEXT 330 // text1=text
|
||||
@@ -2286,7 +2321,7 @@ void dc_stop_ongoing_process (dc_context_t* context);
|
||||
* ask whether to verify the contact;
|
||||
* if so, start the protocol with dc_join_securejoin().
|
||||
*
|
||||
* - DC_QR_ASK_VERIFYGROUP withdc_lot_t::text1=Group name:
|
||||
* - DC_QR_ASK_VERIFYGROUP with dc_lot_t::text1=Group name:
|
||||
* ask whether to join the group;
|
||||
* if so, start the protocol with dc_join_securejoin().
|
||||
*
|
||||
@@ -2306,6 +2341,10 @@ void dc_stop_ongoing_process (dc_context_t* context);
|
||||
* ask the user if they want to create an account on the given domain,
|
||||
* if so, call dc_set_config_from_qr() and then dc_configure().
|
||||
*
|
||||
* - DC_QR_BACKUP:
|
||||
* ask the user if they want to set up a new device.
|
||||
* If so, pass the qr-code to dc_receive_backup().
|
||||
*
|
||||
* - DC_QR_WEBRTC_INSTANCE with dc_lot_t::text1=domain:
|
||||
* ask the user if they want to use the given service for video chats;
|
||||
* if so, call dc_set_config_from_qr().
|
||||
@@ -2596,6 +2635,117 @@ char* dc_get_last_error (dc_context_t* context);
|
||||
void dc_str_unref (char* str);
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_backup_provider_t
|
||||
*
|
||||
* Set up another device.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates an object for sending a backup to another device.
|
||||
*
|
||||
* The backup is sent to through a peer-to-peer channel which is bootstrapped
|
||||
* by a QR-code. The backup contains the entire state of the account
|
||||
* including credentials. This can be used to setup a new device.
|
||||
*
|
||||
* This is a blocking call as some preparations are made like e.g. exporting
|
||||
* the database. Once this function returns, the backup is being offered to
|
||||
* remote devices. To wait until one device received the backup, use
|
||||
* dc_backup_provider_wait(). Alternatively abort the operation using
|
||||
* dc_stop_ongoing_process().
|
||||
*
|
||||
* During execution of the job #DC_EVENT_IMEX_PROGRESS is sent out to indicate
|
||||
* state and progress.
|
||||
*
|
||||
* @memberof dc_backup_provider_t
|
||||
* @param context The context.
|
||||
* @return Opaque object for sending the backup.
|
||||
* On errors, NULL is returned and dc_get_last_error() returns an error that
|
||||
* should be shown to the user.
|
||||
*/
|
||||
dc_backup_provider_t* dc_backup_provider_new (dc_context_t* context);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the QR code text that will offer the backup to other devices.
|
||||
*
|
||||
* The QR code contains a ticket which will validate the backup and provide
|
||||
* authentication for both the provider and the recipient.
|
||||
*
|
||||
* The scanning device should call the scanned text to dc_check_qr(). If
|
||||
* dc_check_qr() returns DC_QR_BACKUP, the backup transfer can be started using
|
||||
* dc_get_backup().
|
||||
*
|
||||
* @memberof dc_backup_provider_t
|
||||
* @param backup_provider The backup provider object as created by
|
||||
* dc_backup_provider_new().
|
||||
* @return The text that should be put in the QR code.
|
||||
* On errors an empty string is returned, NULL is never returned.
|
||||
* the returned string must be released using dc_str_unref() after usage.
|
||||
*/
|
||||
char* dc_backup_provider_get_qr (const dc_backup_provider_t* backup_provider);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the QR code SVG image that will offer the backup to other devices.
|
||||
*
|
||||
* This works like dc_backup_provider_qr() but returns the text of a rendered
|
||||
* SVG image containing the QR code.
|
||||
*
|
||||
* @memberof dc_backup_provider_t
|
||||
* @param backup_provider The backup provider object as created by
|
||||
* dc_backup_provider_new().
|
||||
* @return The QR code rendered as SVG.
|
||||
* On errors an empty string is returned, NULL is never returned.
|
||||
* the returned string must be released using dc_str_unref() after usage.
|
||||
*/
|
||||
char* dc_backup_provider_get_qr_svg (const dc_backup_provider_t* backup_provider);
|
||||
|
||||
/**
|
||||
* Waits for the sending to finish.
|
||||
*
|
||||
* This is a blocking call and should only be called once.
|
||||
*
|
||||
* @memberof dc_backup_provider_t
|
||||
* @param backup_provider The backup provider object as created by
|
||||
* dc_backup_provider_new(). If NULL is given nothing is done.
|
||||
*/
|
||||
void dc_backup_provider_wait (dc_backup_provider_t* backup_provider);
|
||||
|
||||
/**
|
||||
* Frees a dc_backup_provider_t object.
|
||||
*
|
||||
* @memberof dc_backup_provider_t
|
||||
* @param backup_provider The backup provider object as created by
|
||||
* dc_backup_provider_new().
|
||||
*/
|
||||
void dc_backup_provider_unref (dc_backup_provider_t* backup_provider);
|
||||
|
||||
/**
|
||||
* Gets a backup offered by a dc_backup_provider_t object on another device.
|
||||
*
|
||||
* This function is called on a device that scanned the QR code offered by
|
||||
* dc_backup_sender_qr() or dc_backup_sender_qr_svg(). Typically this is a
|
||||
* different device than that which provides the backup.
|
||||
*
|
||||
* This call will block while the backup is being transferred and only
|
||||
* complete on success or failure. Use dc_stop_ongoing_process() to abort it
|
||||
* early.
|
||||
*
|
||||
* During execution of the job #DC_EVENT_IMEX_PROGRESS is sent out to indicate
|
||||
* state and progress. The process is finished when the event emits either 0
|
||||
* or 1000, 0 means it failed and 1000 means it succeeded. These events are
|
||||
* for showing progress and informational only, success and failure is also
|
||||
* shown in the return code of this function.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context.
|
||||
* @param qr The qr code text, dc_check_qr() must have returned DC_QR_BACKUP
|
||||
* on this text.
|
||||
* @return 0=failure, 1=success.
|
||||
*/
|
||||
int dc_receive_backup (dc_context_t* context, const char* qr);
|
||||
|
||||
/**
|
||||
* @class dc_accounts_t
|
||||
*
|
||||
@@ -3150,7 +3300,7 @@ dc_lot_t* dc_chatlist_get_summary (const dc_chatlist_t* chatlist, siz
|
||||
* it takes the chat ID and the message ID as returned by dc_chatlist_get_chat_id() and dc_chatlist_get_msg_id()
|
||||
* as arguments. The chatlist object itself is not needed directly.
|
||||
*
|
||||
* This maybe useful if you convert the complete object into a different represenation
|
||||
* This maybe useful if you convert the complete object into a different representation
|
||||
* as done e.g. in the node-bindings.
|
||||
* If you have access to the chatlist object in some way, using this function is not recommended,
|
||||
* use dc_chatlist_get_summary() in this case instead.
|
||||
@@ -4068,9 +4218,19 @@ int dc_msg_get_info_type (const dc_msg_t* msg);
|
||||
|
||||
|
||||
// DC_INFO* uses the same values as SystemMessage in rust-land
|
||||
#define DC_INFO_PROTECTION_ENABLED 11
|
||||
#define DC_INFO_PROTECTION_DISABLED 12
|
||||
|
||||
#define DC_INFO_UNKNOWN 0
|
||||
#define DC_INFO_GROUP_NAME_CHANGED 2
|
||||
#define DC_INFO_GROUP_IMAGE_CHANGED 3
|
||||
#define DC_INFO_MEMBER_ADDED_TO_GROUP 4
|
||||
#define DC_INFO_MEMBER_REMOVED_FROM_GROUP 5
|
||||
#define DC_INFO_AUTOCRYPT_SETUP_MESSAGE 6
|
||||
#define DC_INFO_SECURE_JOIN_MESSAGE 7
|
||||
#define DC_INFO_LOCATIONSTREAMING_ENABLED 8
|
||||
#define DC_INFO_LOCATION_ONLY 9
|
||||
#define DC_INFO_EPHEMERAL_TIMER_CHANGED 10
|
||||
#define DC_INFO_PROTECTION_ENABLED 11
|
||||
#define DC_INFO_PROTECTION_DISABLED 12
|
||||
#define DC_INFO_WEBXDC_INFO_MESSAGE 32
|
||||
|
||||
/**
|
||||
* Check if a message is still in creation. A message is in creation between
|
||||
@@ -4283,6 +4443,18 @@ void dc_msg_set_text (dc_msg_t* msg, const char* text);
|
||||
void dc_msg_set_html (dc_msg_t* msg, const char* html);
|
||||
|
||||
|
||||
/**
|
||||
* Sets the email's subject. If it's empty, a default subject
|
||||
* will be used (e.g. `Message from Alice` or `Re: <last subject>`).
|
||||
* This does not alter any information in the database.
|
||||
*
|
||||
* @memberof dc_msg_t
|
||||
* @param msg The message object.
|
||||
* @param subject The new subject.
|
||||
*/
|
||||
void dc_msg_set_subject (dc_msg_t* msg, const char* subject);
|
||||
|
||||
|
||||
/**
|
||||
* Set different sender name for a message.
|
||||
* This overrides the name set by the dc_set_config()-option `displayname`.
|
||||
@@ -4695,6 +4867,37 @@ int dc_contact_is_blocked (const dc_contact_t* contact);
|
||||
int dc_contact_is_verified (dc_contact_t* contact);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return the address that verified a contact
|
||||
*
|
||||
* The UI may use this in addition to a checkmark showing the verification status
|
||||
*
|
||||
* @memberof dc_contact_t
|
||||
* @param contact The contact object.
|
||||
* @return
|
||||
* A string containing the verifiers address. If it is the same address as the contact itself,
|
||||
* we verified the contact ourself. If it is an empty string, we don't have verifier
|
||||
* information or the contact is not verified.
|
||||
*/
|
||||
char* dc_contact_get_verifier_addr (dc_contact_t* contact);
|
||||
|
||||
|
||||
/**
|
||||
* Return the `ContactId` that verified a contact
|
||||
*
|
||||
* The UI may use this in addition to a checkmark showing the verification status
|
||||
*
|
||||
* @memberof dc_contact_t
|
||||
* @param contact The contact object.
|
||||
* @return
|
||||
* The `ContactId` of the verifiers address. If it is the same address as the contact itself,
|
||||
* we verified the contact ourself. If it is 0, we don't have verifier information or
|
||||
* the contact is not verified.
|
||||
*/
|
||||
uint32_t dc_contact_get_verifier_id (dc_contact_t* contact);
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_provider_t
|
||||
*
|
||||
@@ -4882,7 +5085,49 @@ uint32_t dc_lot_get_id (const dc_lot_t* lot);
|
||||
* @param lot The lot object.
|
||||
* @return The timestamp as defined by the creator of the object. 0 if there is not timestamp or on errors.
|
||||
*/
|
||||
int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_reactions_t
|
||||
*
|
||||
* An object representing all reactions for a single message.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns array of contacts which reacted to the given message.
|
||||
*
|
||||
* @memberof dc_reactions_t
|
||||
* @param reactions The object containing message reactions.
|
||||
* @return array of contact IDs. Use dc_array_get_cnt() to get array length and
|
||||
* dc_array_get_id() to get the IDs. Should be freed using `dc_array_unref()` after usage.
|
||||
*/
|
||||
dc_array_t* dc_reactions_get_contacts(dc_reactions_t* reactions);
|
||||
|
||||
|
||||
/**
|
||||
* Returns a string containing space-separated reactions of a single contact.
|
||||
*
|
||||
* @memberof dc_reactions_t
|
||||
* @param reactions The object containing message reactions.
|
||||
* @param contact_id ID of the contact.
|
||||
* @return Space-separated list of emoji sequences, which could be empty.
|
||||
* Returned string should not be modified and should be freed
|
||||
* with dc_str_unref() after usage.
|
||||
*/
|
||||
char* dc_reactions_get_by_contact_id(dc_reactions_t* reactions, uint32_t contact_id);
|
||||
|
||||
|
||||
/**
|
||||
* Frees an object containing message reactions.
|
||||
*
|
||||
* Reactions objects are created by dc_get_msg_reactions().
|
||||
*
|
||||
* @memberof dc_reactions_t
|
||||
* @param reactions The object to free.
|
||||
* If NULL is given, nothing is done.
|
||||
*/
|
||||
void dc_reactions_unref (dc_reactions_t* reactions);
|
||||
|
||||
|
||||
/**
|
||||
@@ -5460,6 +5705,14 @@ void dc_event_unref(dc_event_t* event);
|
||||
*/
|
||||
#define DC_EVENT_IMAP_MESSAGE_MOVED 105
|
||||
|
||||
/**
|
||||
* Emitted before going into IDLE on the Inbox folder.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 0
|
||||
*/
|
||||
#define DC_EVENT_IMAP_INBOX_IDLE 106
|
||||
|
||||
/**
|
||||
* Emitted when a new blob file was successfully written
|
||||
*
|
||||
@@ -5533,6 +5786,15 @@ void dc_event_unref(dc_event_t* event);
|
||||
#define DC_EVENT_MSGS_CHANGED 2000
|
||||
|
||||
|
||||
/**
|
||||
* Message reactions changed.
|
||||
*
|
||||
* @param data1 (int) chat_id ID of the chat affected by the changes.
|
||||
* @param data2 (int) msg_id ID of the message for which reactions were changed.
|
||||
*/
|
||||
#define DC_EVENT_REACTIONS_CHANGED 2001
|
||||
|
||||
|
||||
/**
|
||||
* There is a fresh message. Typically, the user will show an notification
|
||||
* when receiving this message.
|
||||
@@ -5544,6 +5806,17 @@ void dc_event_unref(dc_event_t* event);
|
||||
*/
|
||||
#define DC_EVENT_INCOMING_MSG 2005
|
||||
|
||||
/**
|
||||
* Downloading a bunch of messages just finished. This is an
|
||||
* event to allow the UI to only show one notification per message bunch,
|
||||
* instead of cluttering the user with many notifications.
|
||||
* For each of the msg_ids, an additional #DC_EVENT_INCOMING_MSG event was emitted before.
|
||||
*
|
||||
* @param data1 0
|
||||
* @param data2 (char*) msg_ids, a json object with the message ids.
|
||||
*/
|
||||
#define DC_EVENT_INCOMING_MSG_BUNCH 2006
|
||||
|
||||
|
||||
/**
|
||||
* Messages were marked noticed or seen.
|
||||
@@ -5674,7 +5947,7 @@ void dc_event_unref(dc_event_t* event);
|
||||
* @param data2 (int) The progress as:
|
||||
* 300=vg-/vc-request received, typically shown as "bob@addr joins".
|
||||
* 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
|
||||
* 800=vg-member-added-received received, shown as "bob@addr securely joined GROUP", only sent for the verified-group-protocol.
|
||||
* 800=contact added to chat, shown as "bob@addr securely joined GROUP". Only for the verified-group-protocol.
|
||||
* 1000=Protocol finished for this contact.
|
||||
*/
|
||||
#define DC_EVENT_SECUREJOIN_INVITER_PROGRESS 2060
|
||||
@@ -6192,7 +6465,7 @@ void dc_event_unref(dc_event_t* event);
|
||||
///
|
||||
/// Used in status messages.
|
||||
///
|
||||
/// @deperecated 2022-09-10
|
||||
/// @deprecated 2022-09-10
|
||||
#define DC_STR_EPHEMERAL_MINUTE 77
|
||||
|
||||
/// "Message deletion timer is set to 1 hour."
|
||||
@@ -6474,7 +6747,7 @@ void dc_event_unref(dc_event_t* event);
|
||||
|
||||
/// "You changed your email address from %1$s to %2$s.
|
||||
/// If you now send a message to a group, contacts there will automatically
|
||||
/// replace the old with your new address.\n\nIt's highly advised to set up
|
||||
/// replace the old with your new address.\n\n It's highly advised to set up
|
||||
/// your old email provider to forward all emails to your new email address.
|
||||
/// Otherwise you might miss messages of contacts who did not get your new
|
||||
/// address yet." + the link to the AEAP blog post
|
||||
@@ -6725,6 +6998,11 @@ void dc_event_unref(dc_event_t* event);
|
||||
/// `%1$s` will be replaced by name and address of the contact.
|
||||
#define DC_STR_PROTECTION_DISABLED_BY_OTHER 161
|
||||
|
||||
/// "Scan to set up second device for %1$s"
|
||||
///
|
||||
/// `%1$s` will be replaced by name and address of the account.
|
||||
#define DC_STR_BACKUP_TRANSFER_QR 162
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::chat::ChatItem;
|
||||
use crate::constants::DC_MSG_ID_DAYMARKER;
|
||||
use crate::contact::ContactId;
|
||||
use crate::location::Location;
|
||||
use crate::message::MsgId;
|
||||
|
||||
@@ -7,6 +8,7 @@ use crate::message::MsgId;
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum dc_array_t {
|
||||
MsgIds(Vec<MsgId>),
|
||||
ContactIds(Vec<ContactId>),
|
||||
Chat(Vec<ChatItem>),
|
||||
Locations(Vec<Location>),
|
||||
Uint(Vec<u32>),
|
||||
@@ -16,6 +18,7 @@ impl dc_array_t {
|
||||
pub(crate) fn get_id(&self, index: usize) -> u32 {
|
||||
match self {
|
||||
Self::MsgIds(array) => array[index].to_u32(),
|
||||
Self::ContactIds(array) => array[index].to_u32(),
|
||||
Self::Chat(array) => match array[index] {
|
||||
ChatItem::Message { msg_id } => msg_id.to_u32(),
|
||||
ChatItem::DayMarker { .. } => DC_MSG_ID_DAYMARKER,
|
||||
@@ -28,6 +31,7 @@ impl dc_array_t {
|
||||
pub(crate) fn get_timestamp(&self, index: usize) -> Option<i64> {
|
||||
match self {
|
||||
Self::MsgIds(_) => None,
|
||||
Self::ContactIds(_) => None,
|
||||
Self::Chat(array) => array.get(index).and_then(|item| match item {
|
||||
ChatItem::Message { .. } => None,
|
||||
ChatItem::DayMarker { timestamp } => Some(*timestamp),
|
||||
@@ -40,6 +44,7 @@ impl dc_array_t {
|
||||
pub(crate) fn get_marker(&self, index: usize) -> Option<&str> {
|
||||
match self {
|
||||
Self::MsgIds(_) => None,
|
||||
Self::ContactIds(_) => None,
|
||||
Self::Chat(_) => None,
|
||||
Self::Locations(array) => array
|
||||
.get(index)
|
||||
@@ -60,6 +65,7 @@ impl dc_array_t {
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
match self {
|
||||
Self::MsgIds(array) => array.len(),
|
||||
Self::ContactIds(array) => array.len(),
|
||||
Self::Chat(array) => array.len(),
|
||||
Self::Locations(array) => array.len(),
|
||||
Self::Uint(array) => array.len(),
|
||||
@@ -83,6 +89,12 @@ impl From<Vec<MsgId>> for dc_array_t {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ContactId>> for dc_array_t {
|
||||
fn from(array: Vec<ContactId>) -> Self {
|
||||
dc_array_t::ContactIds(array)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ChatItem>> for dc_array_t {
|
||||
fn from(array: Vec<ChatItem>) -> Self {
|
||||
dc_array_t::Chat(array)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,12 @@
|
||||
//! # Legacy generic return values for C API.
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::Error;
|
||||
|
||||
use crate::message::MessageState;
|
||||
use crate::qr::Qr;
|
||||
use crate::summary::{Summary, SummaryPrefix};
|
||||
use anyhow::Error;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// An object containing a set of values.
|
||||
/// The meaning of the values is defined by the function returning the object.
|
||||
@@ -12,6 +14,8 @@ use std::borrow::Cow;
|
||||
/// eg. by chatlist.get_summary() or dc_msg_get_summary().
|
||||
///
|
||||
/// *Lot* is used in the meaning *heap* here.
|
||||
// The QR code grew too large. So be it.
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug)]
|
||||
pub enum Lot {
|
||||
Summary(Summary),
|
||||
@@ -20,20 +24,15 @@ pub enum Lot {
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Meaning {
|
||||
#[default]
|
||||
None = 0,
|
||||
Text1Draft = 1,
|
||||
Text1Username = 2,
|
||||
Text1Self = 3,
|
||||
}
|
||||
|
||||
impl Default for Meaning {
|
||||
fn default() -> Self {
|
||||
Meaning::None
|
||||
}
|
||||
}
|
||||
|
||||
impl Lot {
|
||||
pub fn get_text1(&self) -> Option<&str> {
|
||||
match self {
|
||||
@@ -50,6 +49,7 @@ impl Lot {
|
||||
Qr::FprMismatch { .. } => None,
|
||||
Qr::FprWithoutAddr { fingerprint, .. } => Some(fingerprint),
|
||||
Qr::Account { domain } => Some(domain),
|
||||
Qr::Backup { .. } => None,
|
||||
Qr::WebrtcInstance { domain, .. } => Some(domain),
|
||||
Qr::Addr { draft, .. } => draft.as_deref(),
|
||||
Qr::Url { url } => Some(url),
|
||||
@@ -101,6 +101,7 @@ impl Lot {
|
||||
Qr::FprMismatch { .. } => LotState::QrFprMismatch,
|
||||
Qr::FprWithoutAddr { .. } => LotState::QrFprWithoutAddr,
|
||||
Qr::Account { .. } => LotState::QrAccount,
|
||||
Qr::Backup { .. } => LotState::QrBackup,
|
||||
Qr::WebrtcInstance { .. } => LotState::QrWebrtcInstance,
|
||||
Qr::Addr { .. } => LotState::QrAddr,
|
||||
Qr::Url { .. } => LotState::QrUrl,
|
||||
@@ -125,6 +126,7 @@ impl Lot {
|
||||
Qr::FprMismatch { contact_id } => contact_id.unwrap_or_default().to_u32(),
|
||||
Qr::FprWithoutAddr { .. } => Default::default(),
|
||||
Qr::Account { .. } => Default::default(),
|
||||
Qr::Backup { .. } => Default::default(),
|
||||
Qr::WebrtcInstance { .. } => Default::default(),
|
||||
Qr::Addr { contact_id, .. } => contact_id.to_u32(),
|
||||
Qr::Url { .. } => Default::default(),
|
||||
@@ -149,9 +151,9 @@ impl Lot {
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LotState {
|
||||
// Default
|
||||
#[default]
|
||||
Undefined = 0,
|
||||
|
||||
// Qr States
|
||||
@@ -173,6 +175,8 @@ pub enum LotState {
|
||||
/// text1=domain
|
||||
QrAccount = 250,
|
||||
|
||||
QrBackup = 251,
|
||||
|
||||
/// text1=domain, text2=instance pattern
|
||||
QrWebrtcInstance = 260,
|
||||
|
||||
@@ -213,12 +217,6 @@ pub enum LotState {
|
||||
MsgOutMdnRcvd = 28,
|
||||
}
|
||||
|
||||
impl Default for LotState {
|
||||
fn default() -> Self {
|
||||
LotState::Undefined
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MessageState> for LotState {
|
||||
fn from(s: MessageState) -> Self {
|
||||
use MessageState::*;
|
||||
|
||||
@@ -287,9 +287,10 @@ fn as_path_unicode<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use libc::{free, strcmp};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_os_str_to_c_string_cwd() {
|
||||
let some_dir = std::env::current_dir().unwrap();
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.96.0"
|
||||
version = "1.112.0"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2021"
|
||||
default-run = "deltachat-jsonrpc-server"
|
||||
license = "MPL-2.0"
|
||||
@@ -19,21 +18,25 @@ num-traits = "0.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tempfile = "3.3.0"
|
||||
log = "0.4"
|
||||
async-channel = { version = "1.6.1" }
|
||||
futures = { version = "0.3.24" }
|
||||
serde_json = "1.0.85"
|
||||
yerpc = { version = "^0.3.1", features = ["anyhow_expose"] }
|
||||
typescript-type-def = { version = "0.5.3", features = ["json_value"] }
|
||||
tokio = { version = "1.19.2" }
|
||||
async-channel = { version = "1.8.0" }
|
||||
futures = { version = "0.3.26" }
|
||||
serde_json = "1.0.91"
|
||||
yerpc = { version = "0.4.3", features = ["anyhow_expose"] }
|
||||
typescript-type-def = { version = "0.5.5", features = ["json_value"] }
|
||||
tokio = { version = "1.25.0" }
|
||||
sanitize-filename = "0.4"
|
||||
walkdir = "2.3.2"
|
||||
base64 = "0.21"
|
||||
|
||||
# optional dependencies
|
||||
axum = { version = "0.5.9", optional = true, features = ["ws"] }
|
||||
env_logger = { version = "0.9.0", optional = true }
|
||||
axum = { version = "0.6.11", optional = true, features = ["ws"] }
|
||||
env_logger = { version = "0.10.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.19.2", features = ["full", "rt-multi-thread"] }
|
||||
tokio = { version = "1.25.0", features = ["full", "rt-multi-thread"] }
|
||||
|
||||
|
||||
[features]
|
||||
default = []
|
||||
webserver = ["env_logger", "axum", "tokio/full", "yerpc/support-axum"]
|
||||
default = ["vendored"]
|
||||
webserver = ["dep:env_logger", "dep:axum", "tokio/full", "yerpc/support-axum"]
|
||||
vendored = ["deltachat/vendored"]
|
||||
|
||||
@@ -35,7 +35,7 @@ The server can be configured with environment variables:
|
||||
|`DC_PORT`|`20808`|port to listen on|
|
||||
|`DC_ACCOUNTS_PATH`|`./accounts`|path to storage directory|
|
||||
|
||||
If you are targetting other architectures (like KaiOS or Android), the webserver binary can be cross-compiled easily with [rust-cross](https://github.com/cross-rs/cross):
|
||||
If you are targeting other architectures (like KaiOS or Android), the webserver binary can be cross-compiled easily with [rust-cross](https://github.com/cross-rs/cross):
|
||||
|
||||
```sh
|
||||
cross build --features=webserver --target armv7-linux-androideabi --release
|
||||
|
||||
@@ -4,142 +4,387 @@ use serde_json::{json, Value};
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
pub fn event_to_json_rpc_notification(event: Event) -> Value {
|
||||
let (field1, field2): (Value, Value) = match &event.typ {
|
||||
// events with a single string in field1
|
||||
EventType::Info(txt)
|
||||
| EventType::SmtpConnected(txt)
|
||||
| EventType::ImapConnected(txt)
|
||||
| EventType::SmtpMessageSent(txt)
|
||||
| EventType::ImapMessageDeleted(txt)
|
||||
| EventType::ImapMessageMoved(txt)
|
||||
| EventType::NewBlobFile(txt)
|
||||
| EventType::DeletedBlobFile(txt)
|
||||
| EventType::Warning(txt)
|
||||
| EventType::Error(txt)
|
||||
| EventType::ErrorSelfNotInGroup(txt) => (json!(txt), Value::Null),
|
||||
EventType::ImexFileWritten(path) => (json!(path.to_str()), Value::Null),
|
||||
// single number
|
||||
EventType::MsgsNoticed(chat_id) | EventType::ChatModified(chat_id) => {
|
||||
(json!(chat_id), Value::Null)
|
||||
}
|
||||
EventType::ImexProgress(progress) => (json!(progress), Value::Null),
|
||||
// both fields contain numbers
|
||||
EventType::MsgsChanged { chat_id, msg_id }
|
||||
| EventType::IncomingMsg { chat_id, msg_id }
|
||||
| EventType::MsgDelivered { chat_id, msg_id }
|
||||
| EventType::MsgFailed { chat_id, msg_id }
|
||||
| EventType::MsgRead { chat_id, msg_id } => (json!(chat_id), json!(msg_id)),
|
||||
EventType::ChatEphemeralTimerModified { chat_id, timer } => (json!(chat_id), json!(timer)),
|
||||
EventType::SecurejoinInviterProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
}
|
||||
| EventType::SecurejoinJoinerProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
} => (json!(contact_id), json!(progress)),
|
||||
// field 1 number or null
|
||||
EventType::ContactsChanged(maybe_number) | EventType::LocationChanged(maybe_number) => (
|
||||
match maybe_number {
|
||||
Some(number) => json!(number),
|
||||
None => Value::Null,
|
||||
},
|
||||
Value::Null,
|
||||
),
|
||||
// number and maybe string
|
||||
EventType::ConfigureProgress { progress, comment } => (
|
||||
json!(progress),
|
||||
match comment {
|
||||
Some(content) => json!(content),
|
||||
None => Value::Null,
|
||||
},
|
||||
),
|
||||
EventType::ConnectivityChanged => (Value::Null, Value::Null),
|
||||
EventType::SelfavatarChanged => (Value::Null, Value::Null),
|
||||
EventType::WebxdcStatusUpdate {
|
||||
msg_id,
|
||||
status_update_serial,
|
||||
} => (json!(msg_id), json!(status_update_serial)),
|
||||
EventType::WebxdcInstanceDeleted { msg_id } => (json!(msg_id), Value::Null),
|
||||
};
|
||||
|
||||
let id: EventTypeName = event.typ.into();
|
||||
let id: JSONRPCEventType = event.typ.into();
|
||||
json!({
|
||||
"id": id,
|
||||
"event": id,
|
||||
"contextId": event.id,
|
||||
"field1": field1,
|
||||
"field2": field2
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
pub enum EventTypeName {
|
||||
Info,
|
||||
SmtpConnected,
|
||||
ImapConnected,
|
||||
SmtpMessageSent,
|
||||
ImapMessageDeleted,
|
||||
ImapMessageMoved,
|
||||
NewBlobFile,
|
||||
DeletedBlobFile,
|
||||
Warning,
|
||||
Error,
|
||||
ErrorSelfNotInGroup,
|
||||
MsgsChanged,
|
||||
IncomingMsg,
|
||||
MsgsNoticed,
|
||||
MsgDelivered,
|
||||
MsgFailed,
|
||||
MsgRead,
|
||||
ChatModified,
|
||||
ChatEphemeralTimerModified,
|
||||
ContactsChanged,
|
||||
LocationChanged,
|
||||
ConfigureProgress,
|
||||
ImexProgress,
|
||||
ImexFileWritten,
|
||||
SecurejoinInviterProgress,
|
||||
SecurejoinJoinerProgress,
|
||||
#[serde(tag = "type", rename = "Event")]
|
||||
pub enum JSONRPCEventType {
|
||||
/// The library-user may write an informational string to the log.
|
||||
///
|
||||
/// This event should *not* be reported to the end-user using a popup or something like
|
||||
/// that.
|
||||
Info {
|
||||
msg: String,
|
||||
},
|
||||
|
||||
/// Emitted when SMTP connection is established and login was successful.
|
||||
SmtpConnected {
|
||||
msg: String,
|
||||
},
|
||||
|
||||
/// Emitted when IMAP connection is established and login was successful.
|
||||
ImapConnected {
|
||||
msg: String,
|
||||
},
|
||||
|
||||
/// Emitted when a message was successfully sent to the SMTP server.
|
||||
SmtpMessageSent {
|
||||
msg: String,
|
||||
},
|
||||
|
||||
/// Emitted when an IMAP message has been marked as deleted
|
||||
ImapMessageDeleted {
|
||||
msg: String,
|
||||
},
|
||||
|
||||
/// Emitted when an IMAP message has been moved
|
||||
ImapMessageMoved {
|
||||
msg: String,
|
||||
},
|
||||
|
||||
/// Emitted before going into IDLE on the Inbox folder.
|
||||
ImapInboxIdle,
|
||||
|
||||
/// Emitted when an new file in the $BLOBDIR was created
|
||||
NewBlobFile {
|
||||
file: String,
|
||||
},
|
||||
|
||||
/// Emitted when an file in the $BLOBDIR was deleted
|
||||
DeletedBlobFile {
|
||||
file: String,
|
||||
},
|
||||
|
||||
/// The library-user should write a warning string to the log.
|
||||
///
|
||||
/// This event should *not* be reported to the end-user using a popup or something like
|
||||
/// that.
|
||||
Warning {
|
||||
msg: String,
|
||||
},
|
||||
|
||||
/// The library-user should report an error to the end-user.
|
||||
///
|
||||
/// As most things are asynchronous, things may go wrong at any time and the user
|
||||
/// should not be disturbed by a dialog or so. Instead, use a bubble or so.
|
||||
///
|
||||
/// However, for ongoing processes (eg. configure())
|
||||
/// or for functions that are expected to fail (eg. autocryptContinueKeyTransfer())
|
||||
/// it might be better to delay showing these events until the function has really
|
||||
/// failed (returned false). It should be sufficient to report only the *last* error
|
||||
/// in a messasge box then.
|
||||
Error {
|
||||
msg: String,
|
||||
},
|
||||
|
||||
/// An action cannot be performed because the user is not in the group.
|
||||
/// Reported eg. after a call to
|
||||
/// setChatName(), setChatProfileImage(),
|
||||
/// addContactToChat(), removeContactFromChat(),
|
||||
/// and messages sending functions.
|
||||
ErrorSelfNotInGroup {
|
||||
msg: String,
|
||||
},
|
||||
|
||||
/// Messages or chats changed. One or more messages or chats changed for various
|
||||
/// reasons in the database:
|
||||
/// - Messages sent, received or removed
|
||||
/// - Chats created, deleted or archived
|
||||
/// - A draft has been set
|
||||
///
|
||||
/// `chatId` is set if only a single chat is affected by the changes, otherwise 0.
|
||||
/// `msgId` is set if only a single message is affected by the changes, otherwise 0.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MsgsChanged {
|
||||
chat_id: u32,
|
||||
msg_id: u32,
|
||||
},
|
||||
|
||||
/// Reactions for the message changed.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ReactionsChanged {
|
||||
chat_id: u32,
|
||||
msg_id: u32,
|
||||
contact_id: u32,
|
||||
},
|
||||
|
||||
/// There is a fresh message. Typically, the user will show an notification
|
||||
/// when receiving this message.
|
||||
///
|
||||
/// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
IncomingMsg {
|
||||
chat_id: u32,
|
||||
msg_id: u32,
|
||||
},
|
||||
|
||||
/// Downloading a bunch of messages just finished. This is an experimental
|
||||
/// event to allow the UI to only show one notification per message bunch,
|
||||
/// instead of cluttering the user with many notifications.
|
||||
///
|
||||
/// msg_ids contains the message ids.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
IncomingMsgBunch {
|
||||
msg_ids: Vec<u32>,
|
||||
},
|
||||
|
||||
/// Messages were seen or noticed.
|
||||
/// chat id is always set.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MsgsNoticed {
|
||||
chat_id: u32,
|
||||
},
|
||||
|
||||
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
|
||||
/// DC_STATE_OUT_DELIVERED, see `Message.state`.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MsgDelivered {
|
||||
chat_id: u32,
|
||||
msg_id: u32,
|
||||
},
|
||||
|
||||
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
|
||||
/// DC_STATE_OUT_FAILED, see `Message.state`.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MsgFailed {
|
||||
chat_id: u32,
|
||||
msg_id: u32,
|
||||
},
|
||||
|
||||
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
|
||||
/// DC_STATE_OUT_MDN_RCVD, see `Message.state`.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MsgRead {
|
||||
chat_id: u32,
|
||||
msg_id: u32,
|
||||
},
|
||||
|
||||
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
|
||||
/// Or the verify state of a chat has changed.
|
||||
/// See setChatName(), setChatProfileImage(), addContactToChat()
|
||||
/// and removeContactFromChat().
|
||||
///
|
||||
/// This event does not include ephemeral timer modification, which
|
||||
/// is a separate event.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ChatModified {
|
||||
chat_id: u32,
|
||||
},
|
||||
|
||||
/// Chat ephemeral timer changed.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ChatEphemeralTimerModified {
|
||||
chat_id: u32,
|
||||
timer: u32,
|
||||
},
|
||||
|
||||
/// Contact(s) created, renamed, blocked or deleted.
|
||||
///
|
||||
/// @param data1 (int) If set, this is the contact_id of an added contact that should be selected.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ContactsChanged {
|
||||
contact_id: Option<u32>,
|
||||
},
|
||||
|
||||
/// Location of one or more contact has changed.
|
||||
///
|
||||
/// @param data1 (u32) contact_id of the contact for which the location has changed.
|
||||
/// If the locations of several contacts have been changed,
|
||||
/// this parameter is set to `None`.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
LocationChanged {
|
||||
contact_id: Option<u32>,
|
||||
},
|
||||
|
||||
/// Inform about the configuration progress started by configure().
|
||||
ConfigureProgress {
|
||||
/// Progress.
|
||||
///
|
||||
/// 0=error, 1-999=progress in permille, 1000=success and done
|
||||
progress: usize,
|
||||
|
||||
/// Progress comment or error, something to display to the user.
|
||||
comment: Option<String>,
|
||||
},
|
||||
|
||||
/// Inform about the import/export progress started by imex().
|
||||
///
|
||||
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
|
||||
/// @param data2 0
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ImexProgress {
|
||||
progress: usize,
|
||||
},
|
||||
|
||||
/// A file has been exported. A file has been written by imex().
|
||||
/// This event may be sent multiple times by a single call to imex().
|
||||
///
|
||||
/// A typical purpose for a handler of this event may be to make the file public to some system
|
||||
/// services.
|
||||
///
|
||||
/// @param data2 0
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ImexFileWritten {
|
||||
path: String,
|
||||
},
|
||||
|
||||
/// Progress information of a secure-join handshake from the view of the inviter
|
||||
/// (Alice, the person who shows the QR code).
|
||||
///
|
||||
/// These events are typically sent after a joiner has scanned the QR code
|
||||
/// generated by getChatSecurejoinQrCodeSvg().
|
||||
///
|
||||
/// @param data1 (int) ID of the contact that wants to join.
|
||||
/// @param data2 (int) Progress as:
|
||||
/// 300=vg-/vc-request received, typically shown as "bob@addr joins".
|
||||
/// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
|
||||
/// 800=vg-member-added-received received, shown as "bob@addr securely joined GROUP", only sent for the verified-group-protocol.
|
||||
/// 1000=Protocol finished for this contact.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
SecurejoinInviterProgress {
|
||||
contact_id: u32,
|
||||
progress: usize,
|
||||
},
|
||||
|
||||
/// Progress information of a secure-join handshake from the view of the joiner
|
||||
/// (Bob, the person who scans the QR code).
|
||||
/// The events are typically sent while secureJoin(), which
|
||||
/// may take some time, is executed.
|
||||
/// @param data1 (int) ID of the inviting contact.
|
||||
/// @param data2 (int) Progress as:
|
||||
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
|
||||
/// (Bob has verified alice and waits until Alice does the same for him)
|
||||
#[serde(rename_all = "camelCase")]
|
||||
SecurejoinJoinerProgress {
|
||||
contact_id: u32,
|
||||
progress: usize,
|
||||
},
|
||||
|
||||
/// The connectivity to the server changed.
|
||||
/// This means that you should refresh the connectivity view
|
||||
/// and possibly the connectivtiy HTML; see getConnectivity() and
|
||||
/// getConnectivityHtml() for details.
|
||||
ConnectivityChanged,
|
||||
|
||||
SelfavatarChanged,
|
||||
WebxdcStatusUpdate,
|
||||
WebXdInstanceDeleted,
|
||||
|
||||
#[serde(rename_all = "camelCase")]
|
||||
WebxdcStatusUpdate {
|
||||
msg_id: u32,
|
||||
status_update_serial: u32,
|
||||
},
|
||||
|
||||
/// Inform that a message containing a webxdc instance has been deleted
|
||||
#[serde(rename_all = "camelCase")]
|
||||
WebxdcInstanceDeleted {
|
||||
msg_id: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<EventType> for EventTypeName {
|
||||
impl From<EventType> for JSONRPCEventType {
|
||||
fn from(event: EventType) -> Self {
|
||||
use EventTypeName::*;
|
||||
use JSONRPCEventType::*;
|
||||
match event {
|
||||
EventType::Info(_) => Info,
|
||||
EventType::SmtpConnected(_) => SmtpConnected,
|
||||
EventType::ImapConnected(_) => ImapConnected,
|
||||
EventType::SmtpMessageSent(_) => SmtpMessageSent,
|
||||
EventType::ImapMessageDeleted(_) => ImapMessageDeleted,
|
||||
EventType::ImapMessageMoved(_) => ImapMessageMoved,
|
||||
EventType::NewBlobFile(_) => NewBlobFile,
|
||||
EventType::DeletedBlobFile(_) => DeletedBlobFile,
|
||||
EventType::Warning(_) => Warning,
|
||||
EventType::Error(_) => Error,
|
||||
EventType::ErrorSelfNotInGroup(_) => ErrorSelfNotInGroup,
|
||||
EventType::MsgsChanged { .. } => MsgsChanged,
|
||||
EventType::IncomingMsg { .. } => IncomingMsg,
|
||||
EventType::MsgsNoticed(_) => MsgsNoticed,
|
||||
EventType::MsgDelivered { .. } => MsgDelivered,
|
||||
EventType::MsgFailed { .. } => MsgFailed,
|
||||
EventType::MsgRead { .. } => MsgRead,
|
||||
EventType::ChatModified(_) => ChatModified,
|
||||
EventType::ChatEphemeralTimerModified { .. } => ChatEphemeralTimerModified,
|
||||
EventType::ContactsChanged(_) => ContactsChanged,
|
||||
EventType::LocationChanged(_) => LocationChanged,
|
||||
EventType::ConfigureProgress { .. } => ConfigureProgress,
|
||||
EventType::ImexProgress(_) => ImexProgress,
|
||||
EventType::ImexFileWritten(_) => ImexFileWritten,
|
||||
EventType::SecurejoinInviterProgress { .. } => SecurejoinInviterProgress,
|
||||
EventType::SecurejoinJoinerProgress { .. } => SecurejoinJoinerProgress,
|
||||
EventType::Info(msg) => Info { msg },
|
||||
EventType::SmtpConnected(msg) => SmtpConnected { msg },
|
||||
EventType::ImapConnected(msg) => ImapConnected { msg },
|
||||
EventType::SmtpMessageSent(msg) => SmtpMessageSent { msg },
|
||||
EventType::ImapMessageDeleted(msg) => ImapMessageDeleted { msg },
|
||||
EventType::ImapMessageMoved(msg) => ImapMessageMoved { msg },
|
||||
EventType::ImapInboxIdle => ImapInboxIdle,
|
||||
EventType::NewBlobFile(file) => NewBlobFile { file },
|
||||
EventType::DeletedBlobFile(file) => DeletedBlobFile { file },
|
||||
EventType::Warning(msg) => Warning { msg },
|
||||
EventType::Error(msg) => Error { msg },
|
||||
EventType::ErrorSelfNotInGroup(msg) => ErrorSelfNotInGroup { msg },
|
||||
EventType::MsgsChanged { chat_id, msg_id } => MsgsChanged {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
EventType::ReactionsChanged {
|
||||
chat_id,
|
||||
msg_id,
|
||||
contact_id,
|
||||
} => ReactionsChanged {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
contact_id: contact_id.to_u32(),
|
||||
},
|
||||
EventType::IncomingMsg { chat_id, msg_id } => IncomingMsg {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
EventType::IncomingMsgBunch { msg_ids } => IncomingMsgBunch {
|
||||
msg_ids: msg_ids.into_iter().map(|id| id.to_u32()).collect(),
|
||||
},
|
||||
EventType::MsgsNoticed(chat_id) => MsgsNoticed {
|
||||
chat_id: chat_id.to_u32(),
|
||||
},
|
||||
EventType::MsgDelivered { chat_id, msg_id } => MsgDelivered {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
EventType::MsgFailed { chat_id, msg_id } => MsgFailed {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
EventType::MsgRead { chat_id, msg_id } => MsgRead {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
EventType::ChatModified(chat_id) => ChatModified {
|
||||
chat_id: chat_id.to_u32(),
|
||||
},
|
||||
EventType::ChatEphemeralTimerModified { chat_id, timer } => {
|
||||
ChatEphemeralTimerModified {
|
||||
chat_id: chat_id.to_u32(),
|
||||
timer: timer.to_u32(),
|
||||
}
|
||||
}
|
||||
EventType::ContactsChanged(contact) => ContactsChanged {
|
||||
contact_id: contact.map(|c| c.to_u32()),
|
||||
},
|
||||
EventType::LocationChanged(contact) => LocationChanged {
|
||||
contact_id: contact.map(|c| c.to_u32()),
|
||||
},
|
||||
EventType::ConfigureProgress { progress, comment } => {
|
||||
ConfigureProgress { progress, comment }
|
||||
}
|
||||
EventType::ImexProgress(progress) => ImexProgress { progress },
|
||||
EventType::ImexFileWritten(path) => ImexFileWritten {
|
||||
path: path.to_str().unwrap_or_default().to_owned(),
|
||||
},
|
||||
EventType::SecurejoinInviterProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
} => SecurejoinInviterProgress {
|
||||
contact_id: contact_id.to_u32(),
|
||||
progress,
|
||||
},
|
||||
EventType::SecurejoinJoinerProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
} => SecurejoinJoinerProgress {
|
||||
contact_id: contact_id.to_u32(),
|
||||
progress,
|
||||
},
|
||||
EventType::ConnectivityChanged => ConnectivityChanged,
|
||||
EventType::SelfavatarChanged => SelfavatarChanged,
|
||||
EventType::WebxdcStatusUpdate { .. } => WebxdcStatusUpdate,
|
||||
EventType::WebxdcInstanceDeleted { .. } => WebXdInstanceDeleted,
|
||||
EventType::WebxdcStatusUpdate {
|
||||
msg_id,
|
||||
status_update_serial,
|
||||
} => WebxdcStatusUpdate {
|
||||
msg_id: msg_id.to_u32(),
|
||||
status_update_serial: status_update_serial.to_u32(),
|
||||
},
|
||||
EventType::WebxdcInstanceDeleted { msg_id } => WebxdcInstanceDeleted {
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,7 +398,8 @@ fn generate_events_ts_types_definition() {
|
||||
root_namespace: None,
|
||||
..typescript_type_def::DefinitionFileOptions::default()
|
||||
};
|
||||
typescript_type_def::write_definition_file::<_, EventTypeName>(&mut buf, options).unwrap();
|
||||
typescript_type_def::write_definition_file::<_, JSONRPCEventType>(&mut buf, options)
|
||||
.unwrap();
|
||||
String::from_utf8(buf).unwrap()
|
||||
};
|
||||
std::fs::write("typescript/generated/events.ts", events).unwrap();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use deltachat::chat::{self, get_chat_contacts};
|
||||
use deltachat::chat::{self, get_chat_contacts, ChatVisibility};
|
||||
use deltachat::chat::{Chat, ChatId};
|
||||
use deltachat::constants::Chattype;
|
||||
use deltachat::contact::{Contact, ContactId};
|
||||
@@ -37,7 +37,7 @@ pub struct FullChat {
|
||||
ephemeral_timer: u32, //TODO look if there are more important properties in newer core versions
|
||||
can_send: bool,
|
||||
was_seen_recently: bool,
|
||||
mailing_list_address: String,
|
||||
mailing_list_address: Option<String>,
|
||||
}
|
||||
|
||||
impl FullChat {
|
||||
@@ -81,7 +81,7 @@ impl FullChat {
|
||||
false
|
||||
};
|
||||
|
||||
let mailing_list_address = chat.get_mailinglist_addr().to_string();
|
||||
let mailing_list_address = chat.get_mailinglist_addr().map(|s| s.to_string());
|
||||
|
||||
Ok(FullChat {
|
||||
id: chat_id,
|
||||
@@ -193,3 +193,21 @@ impl MuteDuration {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, TypeDef)]
|
||||
#[serde(rename = "ChatVisibility")]
|
||||
pub enum JSONRPCChatVisibility {
|
||||
Normal,
|
||||
Archived,
|
||||
Pinned,
|
||||
}
|
||||
|
||||
impl JSONRPCChatVisibility {
|
||||
pub fn into_core_type(self) -> ChatVisibility {
|
||||
match self {
|
||||
JSONRPCChatVisibility::Normal => ChatVisibility::Normal,
|
||||
JSONRPCChatVisibility::Archived => ChatVisibility::Archived,
|
||||
JSONRPCChatVisibility::Pinned => ChatVisibility::Pinned,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,12 +48,10 @@ pub enum ChatListItemFetchResult {
|
||||
dm_chat_contact: Option<u32>,
|
||||
was_seen_recently: bool,
|
||||
},
|
||||
ArchiveLink,
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Error {
|
||||
id: u32,
|
||||
error: String,
|
||||
},
|
||||
ArchiveLink { fresh_message_counter: usize },
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Error { id: u32, error: String },
|
||||
}
|
||||
|
||||
pub(crate) async fn get_chat_list_item_by_id(
|
||||
@@ -66,8 +64,12 @@ pub(crate) async fn get_chat_list_item_by_id(
|
||||
_ => Some(MsgId::new(entry.1)),
|
||||
};
|
||||
|
||||
let fresh_message_counter = chat_id.get_fresh_msg_cnt(ctx).await?;
|
||||
|
||||
if chat_id.is_archived_link() {
|
||||
return Ok(ChatListItemFetchResult::ArchiveLink);
|
||||
return Ok(ChatListItemFetchResult::ArchiveLink {
|
||||
fresh_message_counter,
|
||||
});
|
||||
}
|
||||
|
||||
let chat = Chat::load_from_db(ctx, chat_id).await?;
|
||||
@@ -111,7 +113,6 @@ pub(crate) async fn get_chat_list_item_by_id(
|
||||
(None, false)
|
||||
};
|
||||
|
||||
let fresh_message_counter = chat_id.get_fresh_msg_cnt(ctx).await?;
|
||||
let color = color_int_to_hex_string(chat.get_color(ctx).await?);
|
||||
|
||||
Ok(ChatListItemFetchResult::ChatListItem {
|
||||
|
||||
@@ -20,6 +20,10 @@ pub struct ContactObject {
|
||||
name_and_addr: String,
|
||||
is_blocked: bool,
|
||||
is_verified: bool,
|
||||
/// the address that verified this contact
|
||||
verifier_addr: Option<String>,
|
||||
/// the id of the contact that verified this contact
|
||||
verifier_id: Option<u32>,
|
||||
/// the contact's last seen timestamp
|
||||
last_seen: i64,
|
||||
was_seen_recently: bool,
|
||||
@@ -36,6 +40,18 @@ impl ContactObject {
|
||||
};
|
||||
let is_verified = contact.is_verified(context).await? == VerifiedStatus::BidirectVerified;
|
||||
|
||||
let (verifier_addr, verifier_id) = if is_verified {
|
||||
(
|
||||
contact.get_verifier_addr(context).await?,
|
||||
contact
|
||||
.get_verifier_id(context)
|
||||
.await?
|
||||
.map(|contact_id| contact_id.to_u32()),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
Ok(ContactObject {
|
||||
address: contact.get_addr().to_owned(),
|
||||
color: color_int_to_hex_string(contact.get_color()),
|
||||
@@ -48,6 +64,8 @@ impl ContactObject {
|
||||
name_and_addr: contact.get_name_n_addr(),
|
||||
is_blocked: contact.is_blocked(),
|
||||
is_verified,
|
||||
verifier_addr,
|
||||
verifier_id,
|
||||
last_seen: contact.last_seen(),
|
||||
was_seen_recently: contact.was_seen_recently(),
|
||||
})
|
||||
|
||||
47
deltachat-jsonrpc/src/api/types/location.rs
Normal file
47
deltachat-jsonrpc/src/api/types/location.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use deltachat::location::Location;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(rename = "Location", rename_all = "camelCase")]
|
||||
pub struct JsonrpcLocation {
|
||||
pub location_id: u32,
|
||||
pub is_independent: bool,
|
||||
pub latitude: f64,
|
||||
pub longitude: f64,
|
||||
pub accuracy: f64,
|
||||
pub timestamp: i64,
|
||||
pub contact_id: u32,
|
||||
pub msg_id: u32,
|
||||
pub chat_id: u32,
|
||||
pub marker: Option<String>,
|
||||
}
|
||||
|
||||
impl From<Location> for JsonrpcLocation {
|
||||
fn from(location: Location) -> Self {
|
||||
let Location {
|
||||
location_id,
|
||||
independent,
|
||||
latitude,
|
||||
longitude,
|
||||
accuracy,
|
||||
timestamp,
|
||||
contact_id,
|
||||
msg_id,
|
||||
chat_id,
|
||||
marker,
|
||||
} = location;
|
||||
Self {
|
||||
location_id,
|
||||
is_independent: independent != 0,
|
||||
latitude,
|
||||
longitude,
|
||||
accuracy,
|
||||
timestamp,
|
||||
contact_id: contact_id.to_u32(),
|
||||
msg_id,
|
||||
chat_id: chat_id.to_u32(),
|
||||
marker,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use deltachat::chat::Chat;
|
||||
use deltachat::chat::ChatItem;
|
||||
use deltachat::constants::Chattype;
|
||||
use deltachat::contact::Contact;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::download;
|
||||
use deltachat::message::Message;
|
||||
use deltachat::message::MsgId;
|
||||
use deltachat::message::Viewtype;
|
||||
use deltachat::reaction::get_msg_reactions;
|
||||
use num_traits::cast::ToPrimitive;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -13,8 +16,16 @@ use typescript_type_def::TypeDef;
|
||||
|
||||
use super::color_int_to_hex_string;
|
||||
use super::contact::ContactObject;
|
||||
use super::reactions::JSONRPCReactions;
|
||||
use super::webxdc::WebxdcMessageInfo;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(rename_all = "camelCase", tag = "variant")]
|
||||
pub enum MessageLoadResult {
|
||||
Message(MessageObject),
|
||||
LoadingError { error: String },
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(rename = "Message", rename_all = "camelCase")]
|
||||
pub struct MessageObject {
|
||||
@@ -30,6 +41,9 @@ pub struct MessageObject {
|
||||
view_type: MessageViewtype,
|
||||
state: u32,
|
||||
|
||||
/// An error text, if there is one.
|
||||
error: Option<String>,
|
||||
|
||||
timestamp: i64,
|
||||
sort_timestamp: i64,
|
||||
received_timestamp: i64,
|
||||
@@ -42,6 +56,12 @@ pub struct MessageObject {
|
||||
is_info: bool,
|
||||
is_forwarded: bool,
|
||||
|
||||
/// True if the message was sent by a bot.
|
||||
is_bot: bool,
|
||||
|
||||
/// when is_info is true this describes what type of system message it is
|
||||
system_message_type: SystemMessageType,
|
||||
|
||||
duration: i32,
|
||||
dimensions_height: i32,
|
||||
dimensions_width: i32,
|
||||
@@ -62,6 +82,8 @@ pub struct MessageObject {
|
||||
webxdc_info: Option<WebxdcMessageInfo>,
|
||||
|
||||
download_state: DownloadState,
|
||||
|
||||
reactions: Option<JSONRPCReactions>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
@@ -79,6 +101,7 @@ enum MessageQuote {
|
||||
override_sender_name: Option<String>,
|
||||
image: Option<String>,
|
||||
is_forwarded: bool,
|
||||
view_type: MessageViewtype,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -93,7 +116,7 @@ impl MessageObject {
|
||||
|
||||
let sender_contact = Contact::load_from_db(context, message.get_from_id()).await?;
|
||||
let sender = ContactObject::try_from_dc_contact(context, sender_contact).await?;
|
||||
let file_bytes = message.get_filebytes(context).await;
|
||||
let file_bytes = message.get_filebytes(context).await?.unwrap_or_default();
|
||||
let override_sender_name = message.get_override_sender_name();
|
||||
|
||||
let webxdc_info = if message.get_viewtype() == Viewtype::Webxdc {
|
||||
@@ -118,6 +141,7 @@ impl MessageObject {
|
||||
override_sender_name: quote.get_override_sender_name(),
|
||||
image: if quote.get_viewtype() == Viewtype::Image
|
||||
|| quote.get_viewtype() == Viewtype::Gif
|
||||
|| quote.get_viewtype() == Viewtype::Sticker
|
||||
{
|
||||
match quote.get_file(context) {
|
||||
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
||||
@@ -127,6 +151,7 @@ impl MessageObject {
|
||||
None
|
||||
},
|
||||
is_forwarded: quote.is_forwarded(),
|
||||
view_type: quote.get_viewtype().into(),
|
||||
})
|
||||
}
|
||||
None => Some(MessageQuote::JustText { text: quoted_text }),
|
||||
@@ -135,6 +160,13 @@ impl MessageObject {
|
||||
None
|
||||
};
|
||||
|
||||
let reactions = get_msg_reactions(context, msg_id).await?;
|
||||
let reactions = if reactions.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(reactions.into())
|
||||
};
|
||||
|
||||
Ok(MessageObject {
|
||||
id: msg_id.to_u32(),
|
||||
chat_id: message.get_chat_id().to_u32(),
|
||||
@@ -149,6 +181,7 @@ impl MessageObject {
|
||||
.get_state()
|
||||
.to_u32()
|
||||
.ok_or_else(|| anyhow!("state conversion to number failed"))?,
|
||||
error: message.error(),
|
||||
|
||||
timestamp: message.get_timestamp(),
|
||||
sort_timestamp: message.get_sort_timestamp(),
|
||||
@@ -160,6 +193,8 @@ impl MessageObject {
|
||||
is_setupmessage: message.is_setupmessage(),
|
||||
is_info: message.is_info(),
|
||||
is_forwarded: message.is_forwarded(),
|
||||
is_bot: message.is_bot(),
|
||||
system_message_type: message.get_info_type().into(),
|
||||
|
||||
duration: message.get_duration(),
|
||||
dimensions_height: message.get_height(),
|
||||
@@ -189,6 +224,8 @@ impl MessageObject {
|
||||
webxdc_info,
|
||||
|
||||
download_state,
|
||||
|
||||
reactions,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -288,6 +325,61 @@ impl From<download::DownloadState> for DownloadState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
pub enum SystemMessageType {
|
||||
Unknown,
|
||||
GroupNameChanged,
|
||||
GroupImageChanged,
|
||||
MemberAddedToGroup,
|
||||
MemberRemovedFromGroup,
|
||||
AutocryptSetupMessage,
|
||||
SecurejoinMessage,
|
||||
LocationStreamingEnabled,
|
||||
LocationOnly,
|
||||
|
||||
/// Chat ephemeral message timer is changed.
|
||||
EphemeralTimerChanged,
|
||||
|
||||
// Chat protection state changed
|
||||
ChatProtectionEnabled,
|
||||
ChatProtectionDisabled,
|
||||
|
||||
/// Self-sent-message that contains only json used for multi-device-sync;
|
||||
/// if possible, we attach that to other messages as for locations.
|
||||
MultiDeviceSync,
|
||||
|
||||
// Sync message that contains a json payload
|
||||
// sent to the other webxdc instances
|
||||
// These messages are not shown in the chat.
|
||||
WebxdcStatusUpdate,
|
||||
|
||||
/// Webxdc info added with `info` set in `send_webxdc_status_update()`.
|
||||
WebxdcInfoMessage,
|
||||
}
|
||||
|
||||
impl From<deltachat::mimeparser::SystemMessage> for SystemMessageType {
|
||||
fn from(system_message_type: deltachat::mimeparser::SystemMessage) -> Self {
|
||||
use deltachat::mimeparser::SystemMessage;
|
||||
match system_message_type {
|
||||
SystemMessage::Unknown => SystemMessageType::Unknown,
|
||||
SystemMessage::GroupNameChanged => SystemMessageType::GroupNameChanged,
|
||||
SystemMessage::GroupImageChanged => SystemMessageType::GroupImageChanged,
|
||||
SystemMessage::MemberAddedToGroup => SystemMessageType::MemberAddedToGroup,
|
||||
SystemMessage::MemberRemovedFromGroup => SystemMessageType::MemberRemovedFromGroup,
|
||||
SystemMessage::AutocryptSetupMessage => SystemMessageType::AutocryptSetupMessage,
|
||||
SystemMessage::SecurejoinMessage => SystemMessageType::SecurejoinMessage,
|
||||
SystemMessage::LocationStreamingEnabled => SystemMessageType::LocationStreamingEnabled,
|
||||
SystemMessage::LocationOnly => SystemMessageType::LocationOnly,
|
||||
SystemMessage::EphemeralTimerChanged => SystemMessageType::EphemeralTimerChanged,
|
||||
SystemMessage::ChatProtectionEnabled => SystemMessageType::ChatProtectionEnabled,
|
||||
SystemMessage::ChatProtectionDisabled => SystemMessageType::ChatProtectionDisabled,
|
||||
SystemMessage::MultiDeviceSync => SystemMessageType::MultiDeviceSync,
|
||||
SystemMessage::WebxdcStatusUpdate => SystemMessageType::WebxdcStatusUpdate,
|
||||
SystemMessage::WebxdcInfoMessage => SystemMessageType::WebxdcInfoMessage,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageNotificationInfo {
|
||||
@@ -345,3 +437,80 @@ impl MessageNotificationInfo {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageSearchResult {
|
||||
id: u32,
|
||||
author_profile_image: Option<String>,
|
||||
author_name: String,
|
||||
author_color: String,
|
||||
chat_name: Option<String>,
|
||||
message: String,
|
||||
timestamp: i64,
|
||||
}
|
||||
|
||||
impl MessageSearchResult {
|
||||
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<Self> {
|
||||
let message = Message::load_from_db(context, msg_id).await?;
|
||||
let chat = Chat::load_from_db(context, message.get_chat_id()).await?;
|
||||
let sender = Contact::load_from_db(context, message.get_from_id()).await?;
|
||||
|
||||
let profile_image = match sender.get_profile_image(context).await? {
|
||||
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
id: msg_id.to_u32(),
|
||||
author_profile_image: profile_image,
|
||||
author_name: sender.get_display_name().to_owned(),
|
||||
author_color: color_int_to_hex_string(sender.get_color()),
|
||||
chat_name: if chat.get_type() == Chattype::Single {
|
||||
Some(chat.get_name().to_owned())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
message: message.get_text().unwrap_or_default(),
|
||||
timestamp: message.get_timestamp(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(rename_all = "camelCase", rename = "MessageListItem", tag = "kind")]
|
||||
pub enum JSONRPCMessageListItem {
|
||||
Message {
|
||||
msg_id: u32,
|
||||
},
|
||||
|
||||
/// Day marker, separating messages that correspond to different
|
||||
/// days according to local time.
|
||||
DayMarker {
|
||||
/// Marker timestamp, for day markers, in unix milliseconds
|
||||
timestamp: i64,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<ChatItem> for JSONRPCMessageListItem {
|
||||
fn from(item: ChatItem) -> Self {
|
||||
match item {
|
||||
ChatItem::Message { msg_id } => JSONRPCMessageListItem::Message {
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
ChatItem::DayMarker { timestamp } => JSONRPCMessageListItem::DayMarker { timestamp },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, TypeDef)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageData {
|
||||
pub text: Option<String>,
|
||||
pub html: Option<String>,
|
||||
pub viewtype: Option<MessageViewtype>,
|
||||
pub file: Option<String>,
|
||||
pub location: Option<(f64, f64)>,
|
||||
pub override_sender_name: Option<String>,
|
||||
pub quoted_message_id: Option<u32>,
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
use deltachat::qr::Qr;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
pub mod account;
|
||||
pub mod chat;
|
||||
pub mod chat_list;
|
||||
pub mod contact;
|
||||
pub mod location;
|
||||
pub mod message;
|
||||
pub mod provider_info;
|
||||
pub mod qr;
|
||||
pub mod reactions;
|
||||
pub mod webxdc;
|
||||
|
||||
pub fn color_int_to_hex_string(color: u32) -> String {
|
||||
format!("{:#08x}", color).replace("0x", "#")
|
||||
format!("{color:#08x}").replace("0x", "#")
|
||||
}
|
||||
|
||||
fn maybe_empty_string_to_option(string: String) -> Option<String> {
|
||||
@@ -21,213 +20,3 @@ fn maybe_empty_string_to_option(string: String) -> Option<String> {
|
||||
Some(string)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(rename = "Qr", rename_all = "camelCase")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum QrObject {
|
||||
AskVerifyContact {
|
||||
contact_id: u32,
|
||||
fingerprint: String,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
AskVerifyGroup {
|
||||
grpname: String,
|
||||
grpid: String,
|
||||
contact_id: u32,
|
||||
fingerprint: String,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
FprOk {
|
||||
contact_id: u32,
|
||||
},
|
||||
FprMismatch {
|
||||
contact_id: Option<u32>,
|
||||
},
|
||||
FprWithoutAddr {
|
||||
fingerprint: String,
|
||||
},
|
||||
Account {
|
||||
domain: String,
|
||||
},
|
||||
WebrtcInstance {
|
||||
domain: String,
|
||||
instance_pattern: String,
|
||||
},
|
||||
Addr {
|
||||
contact_id: u32,
|
||||
draft: Option<String>,
|
||||
},
|
||||
Url {
|
||||
url: String,
|
||||
},
|
||||
Text {
|
||||
text: String,
|
||||
},
|
||||
WithdrawVerifyContact {
|
||||
contact_id: u32,
|
||||
fingerprint: String,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
WithdrawVerifyGroup {
|
||||
grpname: String,
|
||||
grpid: String,
|
||||
contact_id: u32,
|
||||
fingerprint: String,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
ReviveVerifyContact {
|
||||
contact_id: u32,
|
||||
fingerprint: String,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
ReviveVerifyGroup {
|
||||
grpname: String,
|
||||
grpid: String,
|
||||
contact_id: u32,
|
||||
fingerprint: String,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
Login {
|
||||
address: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<Qr> for QrObject {
|
||||
fn from(qr: Qr) -> Self {
|
||||
match qr {
|
||||
Qr::AskVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::AskVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::AskVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::AskVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::FprOk { contact_id } => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
QrObject::FprOk { contact_id }
|
||||
}
|
||||
Qr::FprMismatch { contact_id } => {
|
||||
let contact_id = contact_id.map(|contact_id| contact_id.to_u32());
|
||||
QrObject::FprMismatch { contact_id }
|
||||
}
|
||||
Qr::FprWithoutAddr { fingerprint } => QrObject::FprWithoutAddr { fingerprint },
|
||||
Qr::Account { domain } => QrObject::Account { domain },
|
||||
Qr::WebrtcInstance {
|
||||
domain,
|
||||
instance_pattern,
|
||||
} => QrObject::WebrtcInstance {
|
||||
domain,
|
||||
instance_pattern,
|
||||
},
|
||||
Qr::Addr { contact_id, draft } => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
QrObject::Addr { contact_id, draft }
|
||||
}
|
||||
Qr::Url { url } => QrObject::Url { url },
|
||||
Qr::Text { text } => QrObject::Text { text },
|
||||
Qr::WithdrawVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::WithdrawVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::WithdrawVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::WithdrawVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::ReviveVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::ReviveVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::ReviveVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::ReviveVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::Login { address, .. } => QrObject::Login { address },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use typescript_type_def::TypeDef;
|
||||
pub struct ProviderInfo {
|
||||
pub before_login_hint: String,
|
||||
pub overview_page: String,
|
||||
pub status: u32, // in reality this is an enum, but for simlicity and because it gets converted into a number anyway, we use an u32 here.
|
||||
pub status: u32, // in reality this is an enum, but for simplicity and because it gets converted into a number anyway, we use an u32 here.
|
||||
}
|
||||
|
||||
impl ProviderInfo {
|
||||
|
||||
219
deltachat-jsonrpc/src/api/types/qr.rs
Normal file
219
deltachat-jsonrpc/src/api/types/qr.rs
Normal file
@@ -0,0 +1,219 @@
|
||||
use deltachat::qr::Qr;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(rename = "Qr", rename_all = "camelCase")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum QrObject {
|
||||
AskVerifyContact {
|
||||
contact_id: u32,
|
||||
fingerprint: String,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
AskVerifyGroup {
|
||||
grpname: String,
|
||||
grpid: String,
|
||||
contact_id: u32,
|
||||
fingerprint: String,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
FprOk {
|
||||
contact_id: u32,
|
||||
},
|
||||
FprMismatch {
|
||||
contact_id: Option<u32>,
|
||||
},
|
||||
FprWithoutAddr {
|
||||
fingerprint: String,
|
||||
},
|
||||
Account {
|
||||
domain: String,
|
||||
},
|
||||
Backup {
|
||||
ticket: String,
|
||||
},
|
||||
WebrtcInstance {
|
||||
domain: String,
|
||||
instance_pattern: String,
|
||||
},
|
||||
Addr {
|
||||
contact_id: u32,
|
||||
draft: Option<String>,
|
||||
},
|
||||
Url {
|
||||
url: String,
|
||||
},
|
||||
Text {
|
||||
text: String,
|
||||
},
|
||||
WithdrawVerifyContact {
|
||||
contact_id: u32,
|
||||
fingerprint: String,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
WithdrawVerifyGroup {
|
||||
grpname: String,
|
||||
grpid: String,
|
||||
contact_id: u32,
|
||||
fingerprint: String,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
ReviveVerifyContact {
|
||||
contact_id: u32,
|
||||
fingerprint: String,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
ReviveVerifyGroup {
|
||||
grpname: String,
|
||||
grpid: String,
|
||||
contact_id: u32,
|
||||
fingerprint: String,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
Login {
|
||||
address: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<Qr> for QrObject {
|
||||
fn from(qr: Qr) -> Self {
|
||||
match qr {
|
||||
Qr::AskVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::AskVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::AskVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::AskVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::FprOk { contact_id } => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
QrObject::FprOk { contact_id }
|
||||
}
|
||||
Qr::FprMismatch { contact_id } => {
|
||||
let contact_id = contact_id.map(|contact_id| contact_id.to_u32());
|
||||
QrObject::FprMismatch { contact_id }
|
||||
}
|
||||
Qr::FprWithoutAddr { fingerprint } => QrObject::FprWithoutAddr { fingerprint },
|
||||
Qr::Account { domain } => QrObject::Account { domain },
|
||||
Qr::Backup { ticket } => QrObject::Backup {
|
||||
ticket: ticket.to_string(),
|
||||
},
|
||||
Qr::WebrtcInstance {
|
||||
domain,
|
||||
instance_pattern,
|
||||
} => QrObject::WebrtcInstance {
|
||||
domain,
|
||||
instance_pattern,
|
||||
},
|
||||
Qr::Addr { contact_id, draft } => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
QrObject::Addr { contact_id, draft }
|
||||
}
|
||||
Qr::Url { url } => QrObject::Url { url },
|
||||
Qr::Text { text } => QrObject::Text { text },
|
||||
Qr::WithdrawVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::WithdrawVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::WithdrawVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::WithdrawVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::ReviveVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::ReviveVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::ReviveVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::ReviveVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::Login { address, .. } => QrObject::Login { address },
|
||||
}
|
||||
}
|
||||
}
|
||||
47
deltachat-jsonrpc/src/api/types/reactions.rs
Normal file
47
deltachat-jsonrpc/src/api/types/reactions.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use deltachat::reaction::Reactions;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
/// Structure representing all reactions to a particular message.
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(rename = "Reactions", rename_all = "camelCase")]
|
||||
pub struct JSONRPCReactions {
|
||||
/// Map from a contact to it's reaction to message.
|
||||
reactions_by_contact: BTreeMap<u32, Vec<String>>,
|
||||
/// Unique reactions and their count
|
||||
reactions: BTreeMap<String, u32>,
|
||||
}
|
||||
|
||||
impl From<Reactions> for JSONRPCReactions {
|
||||
fn from(reactions: Reactions) -> Self {
|
||||
let mut reactions_by_contact: BTreeMap<u32, Vec<String>> = BTreeMap::new();
|
||||
let mut unique_reactions: BTreeMap<String, u32> = BTreeMap::new();
|
||||
|
||||
for contact_id in reactions.contacts() {
|
||||
let reaction = reactions.get(contact_id);
|
||||
if reaction.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let emojis: Vec<String> = reaction
|
||||
.emojis()
|
||||
.into_iter()
|
||||
.map(|emoji| emoji.to_owned())
|
||||
.collect();
|
||||
reactions_by_contact.insert(contact_id.to_u32(), emojis.clone());
|
||||
for emoji in emojis {
|
||||
if let Some(x) = unique_reactions.get_mut(&emoji) {
|
||||
*x += 1;
|
||||
} else {
|
||||
unique_reactions.insert(emoji, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JSONRPCReactions {
|
||||
reactions_by_contact,
|
||||
reactions: unique_reactions,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,13 @@ pub use yerpc;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::api::{Accounts, CommandApi};
|
||||
use async_channel::unbounded;
|
||||
use futures::StreamExt;
|
||||
use tempfile::TempDir;
|
||||
use yerpc::{RpcClient, RpcSession};
|
||||
|
||||
use super::api::{Accounts, CommandApi};
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn basic_json_rpc_functionality() -> anyhow::Result<()> {
|
||||
let tmp_dir = TempDir::new().unwrap().path().into();
|
||||
@@ -36,7 +37,7 @@ mod tests {
|
||||
let response = r#"{"jsonrpc":"2.0","id":1,"result":1}"#;
|
||||
session.handle_incoming(request).await;
|
||||
let result = receiver.next().await;
|
||||
println!("{:?}", result);
|
||||
println!("{result:?}");
|
||||
assert_eq!(result, Some(response.to_owned()));
|
||||
}
|
||||
{
|
||||
@@ -44,7 +45,7 @@ mod tests {
|
||||
let response = r#"{"jsonrpc":"2.0","id":2,"result":[1]}"#;
|
||||
session.handle_incoming(request).await;
|
||||
let result = receiver.next().await;
|
||||
println!("{:?}", result);
|
||||
println!("{result:?}");
|
||||
assert_eq!(result, Some(response.to_owned()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use axum::{extract::ws::WebSocketUpgrade, response::Response, routing::get, Extension, Router};
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use axum::{extract::ws::WebSocketUpgrade, response::Response, routing::get, Extension, Router};
|
||||
use yerpc::axum::handle_ws_rpc;
|
||||
use yerpc::{RpcClient, RpcSession};
|
||||
|
||||
|
||||
1
deltachat-jsonrpc/typescript/.gitignore
vendored
1
deltachat-jsonrpc/typescript/.gitignore
vendored
@@ -6,3 +6,4 @@ yarn.lock
|
||||
package-lock.json
|
||||
docs
|
||||
accounts
|
||||
generated
|
||||
|
||||
@@ -4,3 +4,9 @@ docs
|
||||
coverage
|
||||
yarn*
|
||||
package-lock.json
|
||||
.prettierignore
|
||||
example.html
|
||||
report_api_coverage.mjs
|
||||
scripts
|
||||
dist/example
|
||||
dist/test
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DeltaChat, DeltaChatEvent } from "../deltachat.js";
|
||||
import { DcEvent, DeltaChat } from "../deltachat.js";
|
||||
|
||||
var SELECTED_ACCOUNT = 0;
|
||||
|
||||
@@ -7,7 +7,7 @@ window.addEventListener("DOMContentLoaded", (_event) => {
|
||||
SELECTED_ACCOUNT = Number(id);
|
||||
window.dispatchEvent(new Event("account-changed"));
|
||||
};
|
||||
console.log('launch run script...')
|
||||
console.log("launch run script...");
|
||||
run().catch((err) => console.error("run failed", err));
|
||||
});
|
||||
|
||||
@@ -16,13 +16,13 @@ async function run() {
|
||||
const $side = document.getElementById("side")!;
|
||||
const $head = document.getElementById("header")!;
|
||||
|
||||
const client = new DeltaChat('ws://localhost:20808/ws')
|
||||
const client = new DeltaChat("ws://localhost:20808/ws");
|
||||
|
||||
;(window as any).client = client.rpc;
|
||||
(window as any).client = client.rpc;
|
||||
|
||||
client.on("ALL", event => {
|
||||
onIncomingEvent(event)
|
||||
})
|
||||
client.on("ALL", (accountId, event) => {
|
||||
onIncomingEvent(accountId, event);
|
||||
});
|
||||
|
||||
window.addEventListener("account-changed", async (_event: Event) => {
|
||||
listChatsForSelectedAccount();
|
||||
@@ -31,9 +31,9 @@ async function run() {
|
||||
await Promise.all([loadAccountsInHeader(), listChatsForSelectedAccount()]);
|
||||
|
||||
async function loadAccountsInHeader() {
|
||||
console.log('load accounts')
|
||||
console.log("load accounts");
|
||||
const accounts = await client.rpc.getAllAccounts();
|
||||
console.log('accounts loaded', accounts)
|
||||
console.log("accounts loaded", accounts);
|
||||
for (const account of accounts) {
|
||||
if (account.type === "Configured") {
|
||||
write(
|
||||
@@ -48,14 +48,14 @@ async function run() {
|
||||
`<a href="#">
|
||||
${account.id}: (unconfigured)
|
||||
</a> `
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function listChatsForSelectedAccount() {
|
||||
clear($main);
|
||||
const selectedAccount = SELECTED_ACCOUNT
|
||||
const selectedAccount = SELECTED_ACCOUNT;
|
||||
const info = await client.rpc.getAccountInfo(selectedAccount);
|
||||
if (info.type !== "Configured") {
|
||||
return write($main, "Account is not configured");
|
||||
@@ -68,34 +68,35 @@ async function run() {
|
||||
null
|
||||
);
|
||||
for (const [chatId, _messageId] of chats) {
|
||||
const chat = await client.rpc.chatlistGetFullChatById(
|
||||
selectedAccount,
|
||||
chatId
|
||||
);
|
||||
const chat = await client.rpc.getFullChatById(selectedAccount, chatId);
|
||||
write($main, `<h3>${chat.name}</h3>`);
|
||||
const messageIds = await client.rpc.messageListGetMessageIds(
|
||||
const messageIds = await client.rpc.getMessageIds(
|
||||
selectedAccount,
|
||||
chatId,
|
||||
0
|
||||
false,
|
||||
false
|
||||
);
|
||||
const messages = await client.rpc.messageGetMessages(
|
||||
const messages = await client.rpc.getMessages(
|
||||
selectedAccount,
|
||||
messageIds
|
||||
);
|
||||
for (const [_messageId, message] of Object.entries(messages)) {
|
||||
write($main, `<p>${message.text}</p>`);
|
||||
if (message.variant === "message")
|
||||
write($main, `<p>${message.text}</p>`);
|
||||
else write($main, `<p>loading error: ${message.error}</p>`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onIncomingEvent(event: DeltaChatEvent) {
|
||||
function onIncomingEvent(accountId: number, event: DcEvent) {
|
||||
write(
|
||||
$side,
|
||||
`
|
||||
<p class="message">
|
||||
[<strong>${event.id}</strong> on account ${event.contextId}]<br>
|
||||
<em>f1:</em> ${JSON.stringify(event.field1)}<br>
|
||||
<em>f2:</em> ${JSON.stringify(event.field2)}
|
||||
[<strong>${event.type}</strong> on account ${accountId}]<br>
|
||||
<em>f1:</em> ${JSON.stringify(
|
||||
Object.assign({}, event, { type: undefined })
|
||||
)}
|
||||
</p>`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,24 +3,27 @@ import { DeltaChat } from "../dist/deltachat.js";
|
||||
run().catch(console.error);
|
||||
|
||||
async function run() {
|
||||
const delta = new DeltaChat('ws://localhost:20808/ws');
|
||||
const delta = new DeltaChat("ws://localhost:20808/ws");
|
||||
delta.on("event", (event) => {
|
||||
console.log("event", event.data);
|
||||
});
|
||||
|
||||
const email = process.argv[2]
|
||||
const password = process.argv[3]
|
||||
if (!email || !password) throw new Error('USAGE: node node-add-account.js <EMAILADDRESS> <PASSWORD>')
|
||||
console.log(`creating acccount for ${email}`)
|
||||
const id = await delta.rpc.addAccount()
|
||||
console.log(`created account id ${id}`)
|
||||
const email = process.argv[2];
|
||||
const password = process.argv[3];
|
||||
if (!email || !password)
|
||||
throw new Error(
|
||||
"USAGE: node node-add-account.js <EMAILADDRESS> <PASSWORD>"
|
||||
);
|
||||
console.log(`creating account for ${email}`);
|
||||
const id = await delta.rpc.addAccount();
|
||||
console.log(`created account id ${id}`);
|
||||
await delta.rpc.setConfig(id, "addr", email);
|
||||
await delta.rpc.setConfig(id, "mail_pw", password);
|
||||
console.log('configuration updated')
|
||||
await delta.rpc.configure(id)
|
||||
console.log('account configured!')
|
||||
console.log("configuration updated");
|
||||
await delta.rpc.configure(id);
|
||||
console.log("account configured!");
|
||||
|
||||
const accounts = await delta.rpc.getAllAccounts();
|
||||
console.log("accounts", accounts);
|
||||
console.log("waiting for events...")
|
||||
console.log("waiting for events...");
|
||||
}
|
||||
|
||||
@@ -10,5 +10,5 @@ async function run() {
|
||||
|
||||
const accounts = await delta.rpc.getAllAccounts();
|
||||
console.log("accounts", accounts);
|
||||
console.log("waiting for events...")
|
||||
console.log("waiting for events...");
|
||||
}
|
||||
|
||||
0
deltachat-jsonrpc/typescript/generated/.gitkeep
Normal file
0
deltachat-jsonrpc/typescript/generated/.gitkeep
Normal file
@@ -1,623 +0,0 @@
|
||||
// AUTO-GENERATED by yerpc-derive
|
||||
|
||||
import * as T from "./types.js"
|
||||
import * as RPC from "./jsonrpc.js"
|
||||
|
||||
type RequestMethod = (method: string, params?: RPC.Params) => Promise<unknown>;
|
||||
type NotificationMethod = (method: string, params?: RPC.Params) => void;
|
||||
|
||||
interface Transport {
|
||||
request: RequestMethod,
|
||||
notification: NotificationMethod
|
||||
}
|
||||
|
||||
export class RawClient {
|
||||
constructor(private _transport: Transport) {}
|
||||
|
||||
/**
|
||||
* Check if an email address is valid.
|
||||
*/
|
||||
public checkEmailValidity(email: string): Promise<boolean> {
|
||||
return (this._transport.request('check_email_validity', [email] as RPC.Params)) as Promise<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get general system info.
|
||||
*/
|
||||
public getSystemInfo(): Promise<Record<string,string>> {
|
||||
return (this._transport.request('get_system_info', [] as RPC.Params)) as Promise<Record<string,string>>;
|
||||
}
|
||||
|
||||
|
||||
public addAccount(): Promise<T.U32> {
|
||||
return (this._transport.request('add_account', [] as RPC.Params)) as Promise<T.U32>;
|
||||
}
|
||||
|
||||
|
||||
public removeAccount(accountId: T.U32): Promise<null> {
|
||||
return (this._transport.request('remove_account', [accountId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public getAllAccountIds(): Promise<(T.U32)[]> {
|
||||
return (this._transport.request('get_all_account_ids', [] as RPC.Params)) as Promise<(T.U32)[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select account id for internally selected state.
|
||||
* TODO: Likely this is deprecated as all methods take an account id now.
|
||||
*/
|
||||
public selectAccount(id: T.U32): Promise<null> {
|
||||
return (this._transport.request('select_account', [id] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the selected account id of the internal state..
|
||||
* TODO: Likely this is deprecated as all methods take an account id now.
|
||||
*/
|
||||
public getSelectedAccountId(): Promise<(T.U32|null)> {
|
||||
return (this._transport.request('get_selected_account_id', [] as RPC.Params)) as Promise<(T.U32|null)>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all configured accounts.
|
||||
*/
|
||||
public getAllAccounts(): Promise<(T.Account)[]> {
|
||||
return (this._transport.request('get_all_accounts', [] as RPC.Params)) as Promise<(T.Account)[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get top-level info for an account.
|
||||
*/
|
||||
public getAccountInfo(accountId: T.U32): Promise<T.Account> {
|
||||
return (this._transport.request('get_account_info', [accountId] as RPC.Params)) as Promise<T.Account>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns provider for the given domain.
|
||||
*
|
||||
* This function looks up domain in offline database.
|
||||
*
|
||||
* For compatibility, email address can be passed to this function
|
||||
* instead of the domain.
|
||||
*/
|
||||
public getProviderInfo(accountId: T.U32, email: string): Promise<(T.ProviderInfo|null)> {
|
||||
return (this._transport.request('get_provider_info', [accountId, email] as RPC.Params)) as Promise<(T.ProviderInfo|null)>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the context is already configured.
|
||||
*/
|
||||
public isConfigured(accountId: T.U32): Promise<boolean> {
|
||||
return (this._transport.request('is_configured', [accountId] as RPC.Params)) as Promise<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get system info for an account.
|
||||
*/
|
||||
public getInfo(accountId: T.U32): Promise<Record<string,string>> {
|
||||
return (this._transport.request('get_info', [accountId] as RPC.Params)) as Promise<Record<string,string>>;
|
||||
}
|
||||
|
||||
|
||||
public setConfig(accountId: T.U32, key: string, value: (string|null)): Promise<null> {
|
||||
return (this._transport.request('set_config', [accountId, key, value] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public batchSetConfig(accountId: T.U32, config: Record<string,(string|null)>): Promise<null> {
|
||||
return (this._transport.request('batch_set_config', [accountId, config] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set configuration values from a QR code. (technically from the URI that is stored in the qrcode)
|
||||
* Before this function is called, `checkQr()` should confirm the type of the
|
||||
* QR code is `account` or `webrtcInstance`.
|
||||
*
|
||||
* Internally, the function will call dc_set_config() with the appropriate keys,
|
||||
*/
|
||||
public setConfigFromQr(accountId: T.U32, qrContent: string): Promise<null> {
|
||||
return (this._transport.request('set_config_from_qr', [accountId, qrContent] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public checkQr(accountId: T.U32, qrContent: string): Promise<T.Qr> {
|
||||
return (this._transport.request('check_qr', [accountId, qrContent] as RPC.Params)) as Promise<T.Qr>;
|
||||
}
|
||||
|
||||
|
||||
public getConfig(accountId: T.U32, key: string): Promise<(string|null)> {
|
||||
return (this._transport.request('get_config', [accountId, key] as RPC.Params)) as Promise<(string|null)>;
|
||||
}
|
||||
|
||||
|
||||
public batchGetConfig(accountId: T.U32, keys: (string)[]): Promise<Record<string,(string|null)>> {
|
||||
return (this._transport.request('batch_get_config', [accountId, keys] as RPC.Params)) as Promise<Record<string,(string|null)>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures this account with the currently set parameters.
|
||||
* Setup the credential config before calling this.
|
||||
*/
|
||||
public configure(accountId: T.U32): Promise<null> {
|
||||
return (this._transport.request('configure', [accountId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal an ongoing process to stop.
|
||||
*/
|
||||
public stopOngoingProcess(accountId: T.U32): Promise<null> {
|
||||
return (this._transport.request('stop_ongoing_process', [accountId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message IDs of all _fresh_ messages of any chat.
|
||||
* Typically used for implementing notification summaries
|
||||
* or badge counters e.g. on the app icon.
|
||||
* The list is already sorted and starts with the most recent fresh message.
|
||||
*
|
||||
* Messages belonging to muted chats or to the contact requests are not returned;
|
||||
* these messages should not be notified
|
||||
* and also badge counters should not include these messages.
|
||||
*
|
||||
* To get the number of fresh messages for a single chat, muted or not,
|
||||
* use `get_fresh_msg_cnt()`.
|
||||
*/
|
||||
public getFreshMsgs(accountId: T.U32): Promise<(T.U32)[]> {
|
||||
return (this._transport.request('get_fresh_msgs', [accountId] as RPC.Params)) as Promise<(T.U32)[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of _fresh_ messages in a chat.
|
||||
* Typically used to implement a badge with a number in the chatlist.
|
||||
*
|
||||
* If the specified chat is muted,
|
||||
* the UI should show the badge counter "less obtrusive",
|
||||
* e.g. using "gray" instead of "red" color.
|
||||
*/
|
||||
public getFreshMsgCnt(accountId: T.U32, chatId: T.U32): Promise<T.Usize> {
|
||||
return (this._transport.request('get_fresh_msg_cnt', [accountId, chatId] as RPC.Params)) as Promise<T.Usize>;
|
||||
}
|
||||
|
||||
|
||||
public autocryptInitiateKeyTransfer(accountId: T.U32): Promise<string> {
|
||||
return (this._transport.request('autocrypt_initiate_key_transfer', [accountId] as RPC.Params)) as Promise<string>;
|
||||
}
|
||||
|
||||
|
||||
public autocryptContinueKeyTransfer(accountId: T.U32, messageId: T.U32, setupCode: string): Promise<null> {
|
||||
return (this._transport.request('autocrypt_continue_key_transfer', [accountId, messageId, setupCode] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public getChatlistEntries(accountId: T.U32, listFlags: (T.U32|null), queryString: (string|null), queryContactId: (T.U32|null)): Promise<(T.ChatListEntry)[]> {
|
||||
return (this._transport.request('get_chatlist_entries', [accountId, listFlags, queryString, queryContactId] as RPC.Params)) as Promise<(T.ChatListEntry)[]>;
|
||||
}
|
||||
|
||||
|
||||
public getChatlistItemsByEntries(accountId: T.U32, entries: (T.ChatListEntry)[]): Promise<Record<T.U32,T.ChatListItemFetchResult>> {
|
||||
return (this._transport.request('get_chatlist_items_by_entries', [accountId, entries] as RPC.Params)) as Promise<Record<T.U32,T.ChatListItemFetchResult>>;
|
||||
}
|
||||
|
||||
|
||||
public chatlistGetFullChatById(accountId: T.U32, chatId: T.U32): Promise<T.FullChat> {
|
||||
return (this._transport.request('chatlist_get_full_chat_by_id', [accountId, chatId] as RPC.Params)) as Promise<T.FullChat>;
|
||||
}
|
||||
|
||||
/**
|
||||
* get basic info about a chat,
|
||||
* use chatlist_get_full_chat_by_id() instead if you need more information
|
||||
*/
|
||||
public getBasicChatInfo(accountId: T.U32, chatId: T.U32): Promise<T.BasicChat> {
|
||||
return (this._transport.request('get_basic_chat_info', [accountId, chatId] as RPC.Params)) as Promise<T.BasicChat>;
|
||||
}
|
||||
|
||||
|
||||
public acceptChat(accountId: T.U32, chatId: T.U32): Promise<null> {
|
||||
return (this._transport.request('accept_chat', [accountId, chatId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public blockChat(accountId: T.U32, chatId: T.U32): Promise<null> {
|
||||
return (this._transport.request('block_chat', [accountId, chatId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a chat.
|
||||
*
|
||||
* Messages are deleted from the device and the chat database entry is deleted.
|
||||
* After that, the event #DC_EVENT_MSGS_CHANGED is posted.
|
||||
*
|
||||
* Things that are _not done_ implicitly:
|
||||
*
|
||||
* - Messages are **not deleted from the server**.
|
||||
* - The chat or the contact is **not blocked**, so new messages from the user/the group may appear as a contact request
|
||||
* and the user may create the chat again.
|
||||
* - **Groups are not left** - this would
|
||||
* be unexpected as (1) deleting a normal chat also does not prevent new mails
|
||||
* from arriving, (2) leaving a group requires sending a message to
|
||||
* all group members - especially for groups not used for a longer time, this is
|
||||
* really unexpected when deletion results in contacting all members again,
|
||||
* (3) only leaving groups is also a valid usecase.
|
||||
*
|
||||
* To leave a chat explicitly, use dc_remove_contact_from_chat() with
|
||||
* chat_id=DC_CONTACT_ID_SELF)
|
||||
*/
|
||||
public deleteChat(accountId: T.U32, chatId: T.U32): Promise<null> {
|
||||
return (this._transport.request('delete_chat', [accountId, chatId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get encryption info for a chat.
|
||||
* Get a multi-line encryption info, containing encryption preferences of all members.
|
||||
* Can be used to find out why messages sent to group are not encrypted.
|
||||
*
|
||||
* returns Multi-line text
|
||||
*/
|
||||
public getChatEncryptionInfo(accountId: T.U32, chatId: T.U32): Promise<string> {
|
||||
return (this._transport.request('get_chat_encryption_info', [accountId, chatId] as RPC.Params)) as Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get QR code (text and SVG) that will offer an Setup-Contact or Verified-Group invitation.
|
||||
* The QR code is compatible to the OPENPGP4FPR format
|
||||
* so that a basic fingerprint comparison also works e.g. with OpenKeychain.
|
||||
*
|
||||
* The scanning device will pass the scanned content to `checkQr()` then;
|
||||
* if `checkQr()` returns `askVerifyContact` or `askVerifyGroup`
|
||||
* an out-of-band-verification can be joined using dc_join_securejoin()
|
||||
*
|
||||
* chat_id: If set to a group-chat-id,
|
||||
* the Verified-Group-Invite protocol is offered in the QR code;
|
||||
* works for protected groups as well as for normal groups.
|
||||
* If not set, the Setup-Contact protocol is offered in the QR code.
|
||||
* See https://countermitm.readthedocs.io/en/latest/new.html
|
||||
* for details about both protocols.
|
||||
*
|
||||
* return format: `[code, svg]`
|
||||
*/
|
||||
public getChatSecurejoinQrCodeSvg(accountId: T.U32, chatId: (T.U32|null)): Promise<[string,string]> {
|
||||
return (this._transport.request('get_chat_securejoin_qr_code_svg', [accountId, chatId] as RPC.Params)) as Promise<[string,string]>;
|
||||
}
|
||||
|
||||
|
||||
public leaveGroup(accountId: T.U32, chatId: T.U32): Promise<null> {
|
||||
return (this._transport.request('leave_group', [accountId, chatId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a member from a group.
|
||||
*
|
||||
* If the group is already _promoted_ (any message was sent to the group),
|
||||
* all group members are informed by a special status message that is sent automatically by this function.
|
||||
*
|
||||
* Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent.
|
||||
*/
|
||||
public removeContactFromChat(accountId: T.U32, chatId: T.U32, contactId: T.U32): Promise<null> {
|
||||
return (this._transport.request('remove_contact_from_chat', [accountId, chatId, contactId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a member to a group.
|
||||
*
|
||||
* If the group is already _promoted_ (any message was sent to the group),
|
||||
* all group members are informed by a special status message that is sent automatically by this function.
|
||||
*
|
||||
* If the group has group protection enabled, only verified contacts can be added to the group.
|
||||
*
|
||||
* Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent.
|
||||
*/
|
||||
public addContactToChat(accountId: T.U32, chatId: T.U32, contactId: T.U32): Promise<null> {
|
||||
return (this._transport.request('add_contact_to_chat', [accountId, chatId, contactId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public addDeviceMessage(accountId: T.U32, label: string, text: string): Promise<T.U32> {
|
||||
return (this._transport.request('add_device_message', [accountId, label, text] as RPC.Params)) as Promise<T.U32>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all messages in a chat as _noticed_.
|
||||
* _Noticed_ messages are no longer _fresh_ and do not count as being unseen
|
||||
* but are still waiting for being marked as "seen" using markseen_msgs()
|
||||
* (IMAP/MDNs is not done for noticed messages).
|
||||
*
|
||||
* Calling this function usually results in the event #DC_EVENT_MSGS_NOTICED.
|
||||
* See also markseen_msgs().
|
||||
*/
|
||||
public marknoticedChat(accountId: T.U32, chatId: T.U32): Promise<null> {
|
||||
return (this._transport.request('marknoticed_chat', [accountId, chatId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public getFirstUnreadMessageOfChat(accountId: T.U32, chatId: T.U32): Promise<(T.U32|null)> {
|
||||
return (this._transport.request('get_first_unread_message_of_chat', [accountId, chatId] as RPC.Params)) as Promise<(T.U32|null)>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set mute duration of a chat.
|
||||
*
|
||||
* The UI can then call is_chat_muted() when receiving a new message
|
||||
* to decide whether it should trigger an notification.
|
||||
*
|
||||
* Muted chats should not sound or vibrate
|
||||
* and should not show a visual notification in the system area.
|
||||
* Moreover, muted chats should be excluded from global badge counter
|
||||
* (get_fresh_msgs() skips muted chats therefore)
|
||||
* and the in-app, per-chat badge counter should use a less obtrusive color.
|
||||
*
|
||||
* Sends out #DC_EVENT_CHAT_MODIFIED.
|
||||
*/
|
||||
public setChatMuteDuration(accountId: T.U32, chatId: T.U32, duration: T.MuteDuration): Promise<null> {
|
||||
return (this._transport.request('set_chat_mute_duration', [accountId, chatId, duration] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the chat is currently muted (can be changed by set_chat_mute_duration()).
|
||||
*
|
||||
* This is available as a standalone function outside of fullchat, because it might be only needed for notification
|
||||
*/
|
||||
public isChatMuted(accountId: T.U32, chatId: T.U32): Promise<boolean> {
|
||||
return (this._transport.request('is_chat_muted', [accountId, chatId] as RPC.Params)) as Promise<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark messages as presented to the user.
|
||||
* Typically, UIs call this function on scrolling through the message list,
|
||||
* when the messages are presented at least for a little moment.
|
||||
* The concrete action depends on the type of the chat and on the users settings
|
||||
* (dc_msgs_presented() may be a better name therefore, but well. :)
|
||||
*
|
||||
* - For normal chats, the IMAP state is updated, MDN is sent
|
||||
* (if set_config()-options `mdns_enabled` is set)
|
||||
* and the internal state is changed to @ref DC_STATE_IN_SEEN to reflect these actions.
|
||||
*
|
||||
* - For contact requests, no IMAP or MDNs is done
|
||||
* and the internal state is not changed therefore.
|
||||
* See also marknoticed_chat().
|
||||
*
|
||||
* Moreover, timer is started for incoming ephemeral messages.
|
||||
* This also happens for contact requests chats.
|
||||
*
|
||||
* One #DC_EVENT_MSGS_NOTICED event is emitted per modified chat.
|
||||
*/
|
||||
public markseenMsgs(accountId: T.U32, msgIds: (T.U32)[]): Promise<null> {
|
||||
return (this._transport.request('markseen_msgs', [accountId, msgIds] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public messageListGetMessageIds(accountId: T.U32, chatId: T.U32, flags: T.U32): Promise<(T.U32)[]> {
|
||||
return (this._transport.request('message_list_get_message_ids', [accountId, chatId, flags] as RPC.Params)) as Promise<(T.U32)[]>;
|
||||
}
|
||||
|
||||
|
||||
public messageGetMessage(accountId: T.U32, messageId: T.U32): Promise<T.Message> {
|
||||
return (this._transport.request('message_get_message', [accountId, messageId] as RPC.Params)) as Promise<T.Message>;
|
||||
}
|
||||
|
||||
|
||||
public messageGetMessages(accountId: T.U32, messageIds: (T.U32)[]): Promise<Record<T.U32,T.Message>> {
|
||||
return (this._transport.request('message_get_messages', [accountId, messageIds] as RPC.Params)) as Promise<Record<T.U32,T.Message>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch info desktop needs for creating a notification for a message
|
||||
*/
|
||||
public messageGetNotificationInfo(accountId: T.U32, messageId: T.U32): Promise<T.MessageNotificationInfo> {
|
||||
return (this._transport.request('message_get_notification_info', [accountId, messageId] as RPC.Params)) as Promise<T.MessageNotificationInfo>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete messages. The messages are deleted on the current device and
|
||||
* on the IMAP server.
|
||||
*/
|
||||
public deleteMessages(accountId: T.U32, messageIds: (T.U32)[]): Promise<null> {
|
||||
return (this._transport.request('delete_messages', [accountId, messageIds] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an informational text for a single message. The text is multiline and may
|
||||
* contain e.g. the raw text of the message.
|
||||
*
|
||||
* The max. text returned is typically longer (about 100000 characters) than the
|
||||
* max. text returned by dc_msg_get_text() (about 30000 characters).
|
||||
*/
|
||||
public getMessageInfo(accountId: T.U32, messageId: T.U32): Promise<string> {
|
||||
return (this._transport.request('get_message_info', [accountId, messageId] as RPC.Params)) as Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single contact options by ID.
|
||||
*/
|
||||
public contactsGetContact(accountId: T.U32, contactId: T.U32): Promise<T.Contact> {
|
||||
return (this._transport.request('contacts_get_contact', [accountId, contactId] as RPC.Params)) as Promise<T.Contact>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single contact as a result of an explicit user action.
|
||||
*
|
||||
* Returns contact id of the created or existing contact
|
||||
*/
|
||||
public contactsCreateContact(accountId: T.U32, email: string, name: (string|null)): Promise<T.U32> {
|
||||
return (this._transport.request('contacts_create_contact', [accountId, email, name] as RPC.Params)) as Promise<T.U32>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns contact id of the created or existing DM chat with that contact
|
||||
*/
|
||||
public contactsCreateChatByContactId(accountId: T.U32, contactId: T.U32): Promise<T.U32> {
|
||||
return (this._transport.request('contacts_create_chat_by_contact_id', [accountId, contactId] as RPC.Params)) as Promise<T.U32>;
|
||||
}
|
||||
|
||||
|
||||
public contactsBlock(accountId: T.U32, contactId: T.U32): Promise<null> {
|
||||
return (this._transport.request('contacts_block', [accountId, contactId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public contactsUnblock(accountId: T.U32, contactId: T.U32): Promise<null> {
|
||||
return (this._transport.request('contacts_unblock', [accountId, contactId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public contactsGetBlocked(accountId: T.U32): Promise<(T.Contact)[]> {
|
||||
return (this._transport.request('contacts_get_blocked', [accountId] as RPC.Params)) as Promise<(T.Contact)[]>;
|
||||
}
|
||||
|
||||
|
||||
public contactsGetContactIds(accountId: T.U32, listFlags: T.U32, query: (string|null)): Promise<(T.U32)[]> {
|
||||
return (this._transport.request('contacts_get_contact_ids', [accountId, listFlags, query] as RPC.Params)) as Promise<(T.U32)[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of contacts.
|
||||
* (formerly called getContacts2 in desktop)
|
||||
*/
|
||||
public contactsGetContacts(accountId: T.U32, listFlags: T.U32, query: (string|null)): Promise<(T.Contact)[]> {
|
||||
return (this._transport.request('contacts_get_contacts', [accountId, listFlags, query] as RPC.Params)) as Promise<(T.Contact)[]>;
|
||||
}
|
||||
|
||||
|
||||
public contactsGetContactsByIds(accountId: T.U32, ids: (T.U32)[]): Promise<Record<T.U32,T.Contact>> {
|
||||
return (this._transport.request('contacts_get_contacts_by_ids', [accountId, ids] as RPC.Params)) as Promise<Record<T.U32,T.Contact>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get encryption info for a contact.
|
||||
* Get a multi-line encryption info, containing your fingerprint and the
|
||||
* fingerprint of the contact, used e.g. to compare the fingerprints for a simple out-of-band verification.
|
||||
*/
|
||||
public getContactEncryptionInfo(accountId: T.U32, contactId: T.U32): Promise<string> {
|
||||
return (this._transport.request('get_contact_encryption_info', [accountId, contactId] as RPC.Params)) as Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all message IDs of the given types in a chat.
|
||||
* Typically used to show a gallery.
|
||||
*
|
||||
* The list is already sorted and starts with the oldest message.
|
||||
* Clients should not try to re-sort the list as this would be an expensive action
|
||||
* and would result in inconsistencies between clients.
|
||||
*
|
||||
* Setting `chat_id` to `None` (`null` in typescript) means get messages with media
|
||||
* from any chat of the currently used account.
|
||||
*/
|
||||
public chatGetMedia(accountId: T.U32, chatId: (T.U32|null), messageType: T.Viewtype, orMessageType2: (T.Viewtype|null), orMessageType3: (T.Viewtype|null)): Promise<(T.U32)[]> {
|
||||
return (this._transport.request('chat_get_media', [accountId, chatId, messageType, orMessageType2, orMessageType3] as RPC.Params)) as Promise<(T.U32)[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search next/previous message based on a given message and a list of types.
|
||||
* Typically used to implement the "next" and "previous" buttons
|
||||
* in a gallery or in a media player.
|
||||
*
|
||||
* one combined call for getting chat::get_next_media for both directions
|
||||
* the manual chat::get_next_media in only one direction is not exposed by the jsonrpc yet
|
||||
*/
|
||||
public chatGetNeighboringMedia(accountId: T.U32, msgId: T.U32, messageType: T.Viewtype, orMessageType2: (T.Viewtype|null), orMessageType3: (T.Viewtype|null)): Promise<[(T.U32|null),(T.U32|null)]> {
|
||||
return (this._transport.request('chat_get_neighboring_media', [accountId, msgId, messageType, orMessageType2, orMessageType3] as RPC.Params)) as Promise<[(T.U32|null),(T.U32|null)]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the network likely has come back.
|
||||
* or just that the network conditions might have changed
|
||||
*/
|
||||
public maybeNetwork(): Promise<null> {
|
||||
return (this._transport.request('maybe_network', [] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current connectivity, i.e. whether the device is connected to the IMAP server.
|
||||
* One of:
|
||||
* - DC_CONNECTIVITY_NOT_CONNECTED (1000-1999): Show e.g. the string "Not connected" or a red dot
|
||||
* - DC_CONNECTIVITY_CONNECTING (2000-2999): Show e.g. the string "Connecting…" or a yellow dot
|
||||
* - DC_CONNECTIVITY_WORKING (3000-3999): Show e.g. the string "Getting new messages" or a spinning wheel
|
||||
* - DC_CONNECTIVITY_CONNECTED (>=4000): Show e.g. the string "Connected" or a green dot
|
||||
*
|
||||
* We don't use exact values but ranges here so that we can split up
|
||||
* states into multiple states in the future.
|
||||
*
|
||||
* Meant as a rough overview that can be shown
|
||||
* e.g. in the title of the main screen.
|
||||
*
|
||||
* If the connectivity changes, a #DC_EVENT_CONNECTIVITY_CHANGED will be emitted.
|
||||
*/
|
||||
public getConnectivity(accountId: T.U32): Promise<T.U32> {
|
||||
return (this._transport.request('get_connectivity', [accountId] as RPC.Params)) as Promise<T.U32>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an overview of the current connectivity, and possibly more statistics.
|
||||
* Meant to give the user more insight about the current status than
|
||||
* the basic connectivity info returned by get_connectivity(); show this
|
||||
* e.g., if the user taps on said basic connectivity info.
|
||||
*
|
||||
* If this page changes, a #DC_EVENT_CONNECTIVITY_CHANGED will be emitted.
|
||||
*
|
||||
* This comes as an HTML from the core so that we can easily improve it
|
||||
* and the improvement instantly reaches all UIs.
|
||||
*/
|
||||
public getConnectivityHtml(accountId: T.U32): Promise<string> {
|
||||
return (this._transport.request('get_connectivity_html', [accountId] as RPC.Params)) as Promise<string>;
|
||||
}
|
||||
|
||||
|
||||
public webxdcSendStatusUpdate(accountId: T.U32, instanceMsgId: T.U32, updateStr: string, description: string): Promise<null> {
|
||||
return (this._transport.request('webxdc_send_status_update', [accountId, instanceMsgId, updateStr, description] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public webxdcGetStatusUpdates(accountId: T.U32, instanceMsgId: T.U32, lastKnownSerial: T.U32): Promise<string> {
|
||||
return (this._transport.request('webxdc_get_status_updates', [accountId, instanceMsgId, lastKnownSerial] as RPC.Params)) as Promise<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get info from a webxdc message
|
||||
*/
|
||||
public messageGetWebxdcInfo(accountId: T.U32, instanceMsgId: T.U32): Promise<T.WebxdcMessageInfo> {
|
||||
return (this._transport.request('message_get_webxdc_info', [accountId, instanceMsgId] as RPC.Params)) as Promise<T.WebxdcMessageInfo>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward messages to another chat.
|
||||
*
|
||||
* All types of messages can be forwarded,
|
||||
* however, they will be flagged as such (dc_msg_is_forwarded() is set).
|
||||
*
|
||||
* Original sender, info-state and webxdc updates are not forwarded on purpose.
|
||||
*/
|
||||
public forwardMessages(accountId: T.U32, messageIds: (T.U32)[], chatId: T.U32): Promise<null> {
|
||||
return (this._transport.request('forward_messages', [accountId, messageIds, chatId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
public removeDraft(accountId: T.U32, chatId: T.U32): Promise<null> {
|
||||
return (this._transport.request('remove_draft', [accountId, chatId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get draft for a chat, if any.
|
||||
*/
|
||||
public getDraft(accountId: T.U32, chatId: T.U32): Promise<(T.Message|null)> {
|
||||
return (this._transport.request('get_draft', [accountId, chatId] as RPC.Params)) as Promise<(T.Message|null)>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the messageid of the sent message
|
||||
*/
|
||||
public miscSendTextMessage(accountId: T.U32, text: string, chatId: T.U32): Promise<T.U32> {
|
||||
return (this._transport.request('misc_send_text_message', [accountId, text, chatId] as RPC.Params)) as Promise<T.U32>;
|
||||
}
|
||||
|
||||
|
||||
public miscSendMsg(accountId: T.U32, chatId: T.U32, text: (string|null), file: (string|null), location: ([T.F64,T.F64]|null), quotedMessageId: (T.U32|null)): Promise<[T.U32,T.Message]> {
|
||||
return (this._transport.request('misc_send_msg', [accountId, chatId, text, file, location, quotedMessageId] as RPC.Params)) as Promise<[T.U32,T.Message]>;
|
||||
}
|
||||
|
||||
|
||||
public miscSetDraft(accountId: T.U32, chatId: T.U32, text: (string|null), file: (string|null), quotedMessageId: (T.U32|null)): Promise<null> {
|
||||
return (this._transport.request('misc_set_draft', [accountId, chatId, text, file, quotedMessageId] as RPC.Params)) as Promise<null>;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
// AUTO-GENERATED by typescript-type-def
|
||||
|
||||
export type EventTypeName=("Info"|"SmtpConnected"|"ImapConnected"|"SmtpMessageSent"|"ImapMessageDeleted"|"ImapMessageMoved"|"NewBlobFile"|"DeletedBlobFile"|"Warning"|"Error"|"ErrorSelfNotInGroup"|"MsgsChanged"|"IncomingMsg"|"MsgsNoticed"|"MsgDelivered"|"MsgFailed"|"MsgRead"|"ChatModified"|"ChatEphemeralTimerModified"|"ContactsChanged"|"LocationChanged"|"ConfigureProgress"|"ImexProgress"|"ImexFileWritten"|"SecurejoinInviterProgress"|"SecurejoinJoinerProgress"|"ConnectivityChanged"|"SelfavatarChanged"|"WebxdcStatusUpdate"|"WebXdInstanceDeleted");
|
||||
@@ -1,10 +0,0 @@
|
||||
// AUTO-GENERATED by typescript-type-def
|
||||
|
||||
export type JSONValue=(null|boolean|number|string|(JSONValue)[]|{[key:string]:JSONValue;});
|
||||
export type Params=((JSONValue)[]|Record<string,JSONValue>);
|
||||
export type U32=number;
|
||||
export type Request={"jsonrpc":"2.0";"method":string;"params"?:Params;"id"?:U32;};
|
||||
export type I32=number;
|
||||
export type Error={"code":I32;"message":string;"data"?:JSONValue;};
|
||||
export type Response={"jsonrpc":"2.0";"id":(U32|null);"result"?:JSONValue;"error"?:Error;};
|
||||
export type Message=(Request|Response);
|
||||
@@ -1,150 +0,0 @@
|
||||
// AUTO-GENERATED by typescript-type-def
|
||||
|
||||
export type U32=number;
|
||||
export type Account=(({"type":"Configured";}&{"id":U32;"displayName":(string|null);"addr":(string|null);"profileImage":(string|null);"color":string;})|({"type":"Unconfigured";}&{"id":U32;}));
|
||||
export type ProviderInfo={"beforeLoginHint":string;"overviewPage":string;"status":U32;};
|
||||
export type Qr=(({"type":"askVerifyContact";}&{"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"askVerifyGroup";}&{"grpname":string;"grpid":string;"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"fprOk";}&{"contact_id":U32;})|({"type":"fprMismatch";}&{"contact_id":(U32|null);})|({"type":"fprWithoutAddr";}&{"fingerprint":string;})|({"type":"account";}&{"domain":string;})|({"type":"webrtcInstance";}&{"domain":string;"instance_pattern":string;})|({"type":"addr";}&{"contact_id":U32;"draft":(string|null);})|({"type":"url";}&{"url":string;})|({"type":"text";}&{"text":string;})|({"type":"withdrawVerifyContact";}&{"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"withdrawVerifyGroup";}&{"grpname":string;"grpid":string;"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"reviveVerifyContact";}&{"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"reviveVerifyGroup";}&{"grpname":string;"grpid":string;"contact_id":U32;"fingerprint":string;"invitenumber":string;"authcode":string;})|({"type":"login";}&{"address":string;}));
|
||||
export type Usize=number;
|
||||
export type ChatListEntry=[U32,U32];
|
||||
export type I64=number;
|
||||
export type ChatListItemFetchResult=(({"type":"ChatListItem";}&{"id":U32;"name":string;"avatarPath":(string|null);"color":string;"lastUpdated":(I64|null);"summaryText1":string;"summaryText2":string;"summaryStatus":U32;"isProtected":boolean;"isGroup":boolean;"freshMessageCounter":Usize;"isSelfTalk":boolean;"isDeviceTalk":boolean;"isSendingLocation":boolean;"isSelfInGroup":boolean;"isArchived":boolean;"isPinned":boolean;"isMuted":boolean;"isContactRequest":boolean;
|
||||
/**
|
||||
* true when chat is a broadcastlist
|
||||
*/
|
||||
"isBroadcast":boolean;
|
||||
/**
|
||||
* contact id if this is a dm chat (for view profile entry in context menu)
|
||||
*/
|
||||
"dmChatContact":(U32|null);"wasSeenRecently":boolean;})|{"type":"ArchiveLink";}|({"type":"Error";}&{"id":U32;"error":string;}));
|
||||
export type Contact={"address":string;"color":string;"authName":string;"status":string;"displayName":string;"id":U32;"name":string;"profileImage":(string|null);"nameAndAddr":string;"isBlocked":boolean;"isVerified":boolean;
|
||||
/**
|
||||
* the contact's last seen timestamp
|
||||
*/
|
||||
"lastSeen":I64;"wasSeenRecently":boolean;};
|
||||
export type FullChat={"id":U32;"name":string;"isProtected":boolean;"profileImage":(string|null);"archived":boolean;"chatType":U32;"isUnpromoted":boolean;"isSelfTalk":boolean;"contacts":(Contact)[];"contactIds":(U32)[];"color":string;"freshMessageCounter":Usize;"isContactRequest":boolean;"isDeviceChat":boolean;"selfInGroup":boolean;"isMuted":boolean;"ephemeralTimer":U32;"canSend":boolean;"wasSeenRecently":boolean;"mailingListAddress":string;};
|
||||
|
||||
/**
|
||||
* cheaper version of fullchat, omits:
|
||||
* - contacts
|
||||
* - contact_ids
|
||||
* - fresh_message_counter
|
||||
* - ephemeral_timer
|
||||
* - self_in_group
|
||||
* - was_seen_recently
|
||||
* - can_send
|
||||
*
|
||||
* used when you only need the basic metadata of a chat like type, name, profile picture
|
||||
*/
|
||||
export type BasicChat=
|
||||
/**
|
||||
* cheaper version of fullchat, omits:
|
||||
* - contacts
|
||||
* - contact_ids
|
||||
* - fresh_message_counter
|
||||
* - ephemeral_timer
|
||||
* - self_in_group
|
||||
* - was_seen_recently
|
||||
* - can_send
|
||||
*
|
||||
* used when you only need the basic metadata of a chat like type, name, profile picture
|
||||
*/
|
||||
{"id":U32;"name":string;"isProtected":boolean;"profileImage":(string|null);"archived":boolean;"chatType":U32;"isUnpromoted":boolean;"isSelfTalk":boolean;"color":string;"isContactRequest":boolean;"isDeviceChat":boolean;"isMuted":boolean;};
|
||||
export type MuteDuration=("NotMuted"|"Forever"|{"Until":I64;});
|
||||
export type MessageQuote=(({"kind":"JustText";}&{"text":string;})|({"kind":"WithMessage";}&{"text":string;"messageId":U32;"authorDisplayName":string;"authorDisplayColor":string;"overrideSenderName":(string|null);"image":(string|null);"isForwarded":boolean;}));
|
||||
export type Viewtype=("Unknown"|
|
||||
/**
|
||||
* Text message.
|
||||
*/
|
||||
"Text"|
|
||||
/**
|
||||
* Image message.
|
||||
* If the image is an animated GIF, the type `Viewtype.Gif` should be used.
|
||||
*/
|
||||
"Image"|
|
||||
/**
|
||||
* Animated GIF message.
|
||||
*/
|
||||
"Gif"|
|
||||
/**
|
||||
* Message containing a sticker, similar to image.
|
||||
* If possible, the ui should display the image without borders in a transparent way.
|
||||
* A click on a sticker will offer to install the sticker set in some future.
|
||||
*/
|
||||
"Sticker"|
|
||||
/**
|
||||
* Message containing an Audio file.
|
||||
*/
|
||||
"Audio"|
|
||||
/**
|
||||
* A voice message that was directly recorded by the user.
|
||||
* For all other audio messages, the type `Viewtype.Audio` should be used.
|
||||
*/
|
||||
"Voice"|
|
||||
/**
|
||||
* Video messages.
|
||||
*/
|
||||
"Video"|
|
||||
/**
|
||||
* Message containing any file, eg. a PDF.
|
||||
*/
|
||||
"File"|
|
||||
/**
|
||||
* Message is an invitation to a videochat.
|
||||
*/
|
||||
"VideochatInvitation"|
|
||||
/**
|
||||
* Message is an webxdc instance.
|
||||
*/
|
||||
"Webxdc");
|
||||
export type I32=number;
|
||||
export type U64=number;
|
||||
export type WebxdcMessageInfo={
|
||||
/**
|
||||
* The name of the app.
|
||||
*
|
||||
* Defaults to the filename if not set in the manifest.
|
||||
*/
|
||||
"name":string;
|
||||
/**
|
||||
* App icon file name.
|
||||
* Defaults to an standard icon if nothing is set in the manifest.
|
||||
*
|
||||
* To get the file, use dc_msg_get_webxdc_blob(). (not yet in jsonrpc, use rust api or cffi for it)
|
||||
*
|
||||
* App icons should should be square,
|
||||
* the implementations will add round corners etc. as needed.
|
||||
*/
|
||||
"icon":string;
|
||||
/**
|
||||
* if the Webxdc represents a document, then this is the name of the document
|
||||
*/
|
||||
"document":(string|null);
|
||||
/**
|
||||
* short string describing the state of the app,
|
||||
* sth. as "2 votes", "Highscore: 123",
|
||||
* can be changed by the apps
|
||||
*/
|
||||
"summary":(string|null);
|
||||
/**
|
||||
* URL where the source code of the Webxdc and other information can be found;
|
||||
* defaults to an empty string.
|
||||
* Implementations may offer an menu or a button to open this URL.
|
||||
*/
|
||||
"sourceCodeUrl":(string|null);
|
||||
/**
|
||||
* True if full internet access should be granted to the app.
|
||||
*/
|
||||
"internetAccess":boolean;};
|
||||
export type DownloadState=("Done"|"Available"|"Failure"|"InProgress");
|
||||
export type Message={"id":U32;"chatId":U32;"fromId":U32;"quote":(MessageQuote|null);"parentId":(U32|null);"text":(string|null);"hasLocation":boolean;"hasHtml":boolean;"viewType":Viewtype;"state":U32;"timestamp":I64;"sortTimestamp":I64;"receivedTimestamp":I64;"hasDeviatingTimestamp":boolean;"subject":string;"showPadlock":boolean;"isSetupmessage":boolean;"isInfo":boolean;"isForwarded":boolean;"duration":I32;"dimensionsHeight":I32;"dimensionsWidth":I32;"videochatType":(U32|null);"videochatUrl":(string|null);"overrideSenderName":(string|null);"sender":Contact;"setupCodeBegin":(string|null);"file":(string|null);"fileMime":(string|null);"fileBytes":U64;"fileName":(string|null);"webxdcInfo":(WebxdcMessageInfo|null);"downloadState":DownloadState;};
|
||||
export type MessageNotificationInfo={"id":U32;"chatId":U32;"accountId":U32;"image":(string|null);"imageMimeType":(string|null);"chatName":string;"chatProfileImage":(string|null);
|
||||
/**
|
||||
* also known as summary_text1
|
||||
*/
|
||||
"summaryPrefix":(string|null);
|
||||
/**
|
||||
* also known as summary_text2
|
||||
*/
|
||||
"summaryText":string;};
|
||||
export type F64=number;
|
||||
export type __AllTyps=[string,boolean,Record<string,string>,U32,U32,null,(U32)[],U32,null,(U32|null),(Account)[],U32,Account,U32,string,(ProviderInfo|null),U32,boolean,U32,Record<string,string>,U32,string,(string|null),null,U32,Record<string,(string|null)>,null,U32,string,null,U32,string,Qr,U32,string,(string|null),U32,(string)[],Record<string,(string|null)>,U32,null,U32,null,U32,(U32)[],U32,U32,Usize,U32,string,U32,U32,string,null,U32,(U32|null),(string|null),(U32|null),(ChatListEntry)[],U32,(ChatListEntry)[],Record<U32,ChatListItemFetchResult>,U32,U32,FullChat,U32,U32,BasicChat,U32,U32,null,U32,U32,null,U32,U32,null,U32,U32,string,U32,(U32|null),[string,string],U32,U32,null,U32,U32,U32,null,U32,U32,U32,null,U32,string,string,U32,U32,U32,null,U32,U32,(U32|null),U32,U32,MuteDuration,null,U32,U32,boolean,U32,(U32)[],null,U32,U32,U32,(U32)[],U32,U32,Message,U32,(U32)[],Record<U32,Message>,U32,U32,MessageNotificationInfo,U32,(U32)[],null,U32,U32,string,U32,U32,Contact,U32,string,(string|null),U32,U32,U32,U32,U32,U32,null,U32,U32,null,U32,(Contact)[],U32,U32,(string|null),(U32)[],U32,U32,(string|null),(Contact)[],U32,(U32)[],Record<U32,Contact>,U32,U32,string,U32,(U32|null),Viewtype,(Viewtype|null),(Viewtype|null),(U32)[],U32,U32,Viewtype,(Viewtype|null),(Viewtype|null),[(U32|null),(U32|null)],null,U32,U32,U32,string,U32,U32,string,string,null,U32,U32,U32,string,U32,U32,WebxdcMessageInfo,U32,(U32)[],U32,null,U32,U32,null,U32,U32,(Message|null),U32,string,U32,U32,U32,U32,(string|null),(string|null),([F64,F64]|null),(U32|null),[U32,Message],U32,U32,(string|null),(string|null),(U32|null),null];
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"author": "Delta Chat Developers (ML) <delta@codespeak.net>",
|
||||
"dependencies": {
|
||||
"@deltachat/tiny-emitter": "3.0.0",
|
||||
"isomorphic-ws": "^4.0.1",
|
||||
"tiny-emitter": "git+https://github.com/Simon-Laux/tiny-emitter.git",
|
||||
"yerpc": "^0.3.3"
|
||||
"yerpc": "^0.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.2.21",
|
||||
@@ -14,7 +14,7 @@
|
||||
"c8": "^7.10.0",
|
||||
"chai": "^4.3.4",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"esbuild": "^0.14.11",
|
||||
"esbuild": "^0.17.9",
|
||||
"http-server": "^14.1.1",
|
||||
"mocha": "^9.1.1",
|
||||
"node-fetch": "^2.6.1",
|
||||
@@ -24,28 +24,36 @@
|
||||
"typescript": "^4.5.5",
|
||||
"ws": "^8.5.0"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/deltachat.js",
|
||||
"require": "./dist/deltachat.cjs"
|
||||
}
|
||||
},
|
||||
"license": "MPL-2.0",
|
||||
"main": "dist/deltachat.js",
|
||||
"name": "@deltachat/jsonrpc-client",
|
||||
"scripts": {
|
||||
"build": "run-s generate-bindings build:tsc build:bundle",
|
||||
"build": "run-s generate-bindings extract-constants build:tsc build:bundle build:cjs",
|
||||
"build:bundle": "esbuild --format=esm --bundle dist/deltachat.js --outfile=dist/deltachat.bundle.js",
|
||||
"build:cjs": "esbuild --format=cjs --bundle --packages=external dist/deltachat.js --outfile=dist/deltachat.cjs",
|
||||
"build:tsc": "tsc",
|
||||
"docs": "typedoc --out docs deltachat.ts",
|
||||
"example": "run-s build example:build example:start",
|
||||
"example:build": "esbuild --bundle dist/example/example.js --outfile=dist/example.bundle.js",
|
||||
"example:dev": "esbuild example/example.ts --bundle --outfile=dist/example.bundle.js --servedir=.",
|
||||
"example:start": "http-server .",
|
||||
"extract-constants": "node ./scripts/generate-constants.js",
|
||||
"generate-bindings": "cargo test",
|
||||
"prettier:check": "prettier --check **.ts",
|
||||
"prettier:fix": "prettier --write **.ts",
|
||||
"prettier:check": "prettier --check .",
|
||||
"prettier:fix": "prettier --write .",
|
||||
"test": "run-s test:prepare test:run-coverage test:report-coverage",
|
||||
"test:prepare": "cargo build --features webserver --bin deltachat-jsonrpc-server",
|
||||
"test:prepare": "cargo build --package deltachat-rpc-server --bin deltachat-rpc-server",
|
||||
"test:report-coverage": "node report_api_coverage.mjs",
|
||||
"test:run": "mocha dist/test",
|
||||
"test:run-coverage": "COVERAGE=1 NODE_OPTIONS=--enable-source-maps c8 --include 'dist/*' -r text -r html -r json mocha dist/test"
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "1.96.0"
|
||||
}
|
||||
"version": "1.112.0"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { readFileSync } from "fs";
|
||||
// only checks for the coverge of the api functions in bindings.ts for now
|
||||
// only checks for the coverage of the api functions in bindings.ts for now
|
||||
const generatedFile = "typescript/generated/client.ts";
|
||||
const json = JSON.parse(readFileSync("./coverage/coverage-final.json"));
|
||||
const jsonCoverage =
|
||||
|
||||
54
deltachat-jsonrpc/typescript/scripts/generate-constants.js
Executable file
54
deltachat-jsonrpc/typescript/scripts/generate-constants.js
Executable file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env node
|
||||
import { readFileSync, writeFileSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from "path";
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const data = [];
|
||||
const header = resolve(__dirname, "../../../deltachat-ffi/deltachat.h");
|
||||
|
||||
console.log("Generating constants...");
|
||||
|
||||
const header_data = readFileSync(header, "UTF-8");
|
||||
const regex = /^#define\s+(\w+)\s+(\w+)/gm;
|
||||
let match;
|
||||
while (null != (match = regex.exec(header_data))) {
|
||||
const key = match[1];
|
||||
const value = parseInt(match[2]);
|
||||
if (!isNaN(value)) {
|
||||
data.push({ key, value });
|
||||
}
|
||||
}
|
||||
|
||||
const constants = data
|
||||
.filter(
|
||||
({ key }) => key.toUpperCase()[0] === key[0] // check if define name is uppercase
|
||||
)
|
||||
.sort((lhs, rhs) => {
|
||||
if (lhs.key < rhs.key) return -1;
|
||||
else if (lhs.key > rhs.key) return 1;
|
||||
return 0;
|
||||
})
|
||||
.filter(({ key }) => {
|
||||
// filter out what we don't need it
|
||||
return !(
|
||||
key.startsWith("DC_EVENT_") ||
|
||||
key.startsWith("DC_IMEX_") ||
|
||||
key.startsWith("DC_CHAT_VISIBILITY") ||
|
||||
key.startsWith("DC_DOWNLOAD") ||
|
||||
key.startsWith("DC_INFO_") ||
|
||||
(key.startsWith("DC_MSG") && !key.startsWith("DC_MSG_ID")) ||
|
||||
key.startsWith("DC_QR_")
|
||||
);
|
||||
})
|
||||
.map((row) => {
|
||||
return ` ${row.key}: ${row.value}`;
|
||||
})
|
||||
.join(",\n");
|
||||
|
||||
writeFileSync(
|
||||
resolve(__dirname, "../generated/constants.ts"),
|
||||
`// Generated!\n\nexport enum C {\n${constants.replace(/:/g, " =")},\n}\n`
|
||||
);
|
||||
@@ -1,40 +1,59 @@
|
||||
import * as T from "../generated/types.js";
|
||||
import * as RPC from "../generated/jsonrpc.js";
|
||||
import { RawClient } from "../generated/client.js";
|
||||
import { EventTypeName } from "../generated/events.js";
|
||||
import { Event } from "../generated/events.js";
|
||||
import { WebsocketTransport, BaseTransport, Request } from "yerpc";
|
||||
import { TinyEmitter } from "tiny-emitter";
|
||||
import { TinyEmitter } from "@deltachat/tiny-emitter";
|
||||
|
||||
export type DeltaChatEvent = {
|
||||
id: EventTypeName;
|
||||
type DCWireEvent<T extends Event> = {
|
||||
event: T;
|
||||
contextId: number;
|
||||
field1: any;
|
||||
field2: any;
|
||||
};
|
||||
export type Events = Record<
|
||||
EventTypeName | "ALL",
|
||||
(event: DeltaChatEvent) => void
|
||||
>;
|
||||
// export type Events = Record<
|
||||
// Event["type"] | "ALL",
|
||||
// (event: DeltaChatEvent<Event>) => void
|
||||
// >;
|
||||
|
||||
type Events = { ALL: (accountId: number, event: Event) => void } & {
|
||||
[Property in Event["type"]]: (
|
||||
accountId: number,
|
||||
event: Extract<Event, { type: Property }>
|
||||
) => void;
|
||||
};
|
||||
|
||||
type ContextEvents = { ALL: (event: Event) => void } & {
|
||||
[Property in Event["type"]]: (
|
||||
event: Extract<Event, { type: Property }>
|
||||
) => void;
|
||||
};
|
||||
|
||||
export type DcEvent = Event;
|
||||
export type DcEventType<T extends Event["type"]> = Extract<Event, { type: T }>;
|
||||
|
||||
export class BaseDeltaChat<
|
||||
Transport extends BaseTransport<any>
|
||||
> extends TinyEmitter<Events> {
|
||||
rpc: RawClient;
|
||||
account?: T.Account;
|
||||
private contextEmitters: TinyEmitter<Events>[] = [];
|
||||
private contextEmitters: { [key: number]: TinyEmitter<ContextEvents> } = {};
|
||||
constructor(public transport: Transport) {
|
||||
super();
|
||||
this.rpc = new RawClient(this.transport);
|
||||
this.transport.on("request", (request: Request) => {
|
||||
const method = request.method;
|
||||
if (method === "event") {
|
||||
const event = request.params! as DeltaChatEvent;
|
||||
this.emit(event.id, event);
|
||||
this.emit("ALL", event);
|
||||
const event = request.params! as DCWireEvent<Event>;
|
||||
//@ts-ignore
|
||||
this.emit(event.event.type, event.contextId, event.event as any);
|
||||
this.emit("ALL", event.contextId, event.event as any);
|
||||
|
||||
if (this.contextEmitters[event.contextId]) {
|
||||
this.contextEmitters[event.contextId].emit(event.id, event);
|
||||
this.contextEmitters[event.contextId].emit("ALL", event);
|
||||
this.contextEmitters[event.contextId].emit(
|
||||
event.event.type,
|
||||
//@ts-ignore
|
||||
event.event as any
|
||||
);
|
||||
this.contextEmitters[event.contextId].emit("ALL", event.event);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -70,8 +89,39 @@ export class DeltaChat extends BaseDeltaChat<WebsocketTransport> {
|
||||
if (typeof opts === "string") opts = { url: opts };
|
||||
if (opts) opts = { ...DEFAULT_OPTS, ...opts };
|
||||
else opts = { ...DEFAULT_OPTS };
|
||||
const transport = new WebsocketTransport(opts.url)
|
||||
const transport = new WebsocketTransport(opts.url);
|
||||
super(transport);
|
||||
this.opts = opts;
|
||||
}
|
||||
}
|
||||
|
||||
export class StdioDeltaChat extends BaseDeltaChat<StdioTransport> {
|
||||
close() {}
|
||||
constructor(input: any, output: any) {
|
||||
const transport = new StdioTransport(input, output);
|
||||
super(transport);
|
||||
}
|
||||
}
|
||||
|
||||
export class StdioTransport extends BaseTransport {
|
||||
constructor(public input: any, public output: any) {
|
||||
super();
|
||||
|
||||
var buffer = "";
|
||||
this.output.on("data", (data: any) => {
|
||||
buffer += data.toString();
|
||||
while (buffer.includes("\n")) {
|
||||
const n = buffer.indexOf("\n");
|
||||
const line = buffer.substring(0, n);
|
||||
const message = JSON.parse(line);
|
||||
this._onmessage(message);
|
||||
buffer = buffer.substring(n + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_send(message: any): void {
|
||||
const serialized = JSON.stringify(message);
|
||||
this.input.write(serialized + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,3 +4,4 @@ export * from "../generated/events.js";
|
||||
export { RawClient } from "../generated/client.js";
|
||||
export * from "./client.js";
|
||||
export * as yerpc from "yerpc";
|
||||
export { C } from "../generated/constants.js";
|
||||
|
||||
@@ -2,12 +2,9 @@ import { strictEqual } from "assert";
|
||||
import chai, { assert, expect } from "chai";
|
||||
import chaiAsPromised from "chai-as-promised";
|
||||
chai.use(chaiAsPromised);
|
||||
import { DeltaChat } from "../deltachat.js";
|
||||
import { StdioDeltaChat as DeltaChat } from "../deltachat.js";
|
||||
|
||||
import {
|
||||
RpcServerHandle,
|
||||
startServer,
|
||||
} from "./test_base.js";
|
||||
import { RpcServerHandle, startServer } from "./test_base.js";
|
||||
|
||||
describe("basic tests", () => {
|
||||
let serverHandle: RpcServerHandle;
|
||||
@@ -15,11 +12,9 @@ describe("basic tests", () => {
|
||||
|
||||
before(async () => {
|
||||
serverHandle = await startServer();
|
||||
// make sure server is up by the time we continue
|
||||
await new Promise((res) => setTimeout(res, 100));
|
||||
dc = new DeltaChat(serverHandle.url)
|
||||
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout);
|
||||
// dc.on("ALL", (event) => {
|
||||
//console.log("event", event);
|
||||
//console.log("event", event);
|
||||
// });
|
||||
});
|
||||
|
||||
@@ -58,7 +53,7 @@ describe("basic tests", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
describe("account managment", () => {
|
||||
describe("account management", () => {
|
||||
it("should create account", async () => {
|
||||
const res = await dc.rpc.addAccount();
|
||||
assert((await dc.rpc.getAllAccountIds()).length === 1);
|
||||
@@ -78,27 +73,27 @@ describe("basic tests", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("contact managment", function () {
|
||||
describe("contact management", function () {
|
||||
let accountId: number;
|
||||
before(async () => {
|
||||
accountId = await dc.rpc.addAccount();
|
||||
});
|
||||
it("should block and unblock contact", async function () {
|
||||
const contactId = await dc.rpc.contactsCreateContact(
|
||||
const contactId = await dc.rpc.createContact(
|
||||
accountId,
|
||||
"example@delta.chat",
|
||||
null
|
||||
);
|
||||
expect((await dc.rpc.contactsGetContact(accountId, contactId)).isBlocked).to.be
|
||||
expect((await dc.rpc.getContact(accountId, contactId)).isBlocked).to.be
|
||||
.false;
|
||||
await dc.rpc.contactsBlock(accountId, contactId);
|
||||
expect((await dc.rpc.contactsGetContact(accountId, contactId)).isBlocked).to.be
|
||||
await dc.rpc.blockContact(accountId, contactId);
|
||||
expect((await dc.rpc.getContact(accountId, contactId)).isBlocked).to.be
|
||||
.true;
|
||||
expect(await dc.rpc.contactsGetBlocked(accountId)).to.have.length(1);
|
||||
await dc.rpc.contactsUnblock(accountId, contactId);
|
||||
expect((await dc.rpc.contactsGetContact(accountId, contactId)).isBlocked).to.be
|
||||
expect(await dc.rpc.getBlockedContacts(accountId)).to.have.length(1);
|
||||
await dc.rpc.unblockContact(accountId, contactId);
|
||||
expect((await dc.rpc.getContact(accountId, contactId)).isBlocked).to.be
|
||||
.false;
|
||||
expect(await dc.rpc.contactsGetBlocked(accountId)).to.have.length(0);
|
||||
expect(await dc.rpc.getBlockedContacts(accountId)).to.have.length(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -108,38 +103,44 @@ describe("basic tests", () => {
|
||||
accountId = await dc.rpc.addAccount();
|
||||
});
|
||||
|
||||
it("set and retrive", async function () {
|
||||
it("set and retrieve", async function () {
|
||||
await dc.rpc.setConfig(accountId, "addr", "valid@email");
|
||||
assert((await dc.rpc.getConfig(accountId, "addr")) == "valid@email");
|
||||
});
|
||||
it("set invalid key should throw", async function () {
|
||||
await expect(dc.rpc.setConfig(accountId, "invalid_key", "some value")).to.be
|
||||
.eventually.rejected;
|
||||
await expect(dc.rpc.setConfig(accountId, "invalid_key", "some value")).to
|
||||
.be.eventually.rejected;
|
||||
});
|
||||
it("get invalid key should throw", async function () {
|
||||
await expect(dc.rpc.getConfig(accountId, "invalid_key")).to.be.eventually
|
||||
.rejected;
|
||||
});
|
||||
it("set and retrive ui.*", async function () {
|
||||
it("set and retrieve ui.*", async function () {
|
||||
await dc.rpc.setConfig(accountId, "ui.chat_bg", "color:red");
|
||||
assert((await dc.rpc.getConfig(accountId, "ui.chat_bg")) == "color:red");
|
||||
});
|
||||
it("set and retrive (batch)", async function () {
|
||||
it("set and retrieve (batch)", async function () {
|
||||
const config = { addr: "valid@email", mail_pw: "1234" };
|
||||
await dc.rpc.batchSetConfig(accountId, config);
|
||||
const retrieved = await dc.rpc.batchGetConfig(accountId, Object.keys(config));
|
||||
const retrieved = await dc.rpc.batchGetConfig(
|
||||
accountId,
|
||||
Object.keys(config)
|
||||
);
|
||||
expect(retrieved).to.deep.equal(config);
|
||||
});
|
||||
it("set and retrive ui.* (batch)", async function () {
|
||||
it("set and retrieve ui.* (batch)", async function () {
|
||||
const config = {
|
||||
"ui.chat_bg": "color:green",
|
||||
"ui.enter_key_sends": "true",
|
||||
};
|
||||
await dc.rpc.batchSetConfig(accountId, config);
|
||||
const retrieved = await dc.rpc.batchGetConfig(accountId, Object.keys(config));
|
||||
const retrieved = await dc.rpc.batchGetConfig(
|
||||
accountId,
|
||||
Object.keys(config)
|
||||
);
|
||||
expect(retrieved).to.deep.equal(config);
|
||||
});
|
||||
it("set and retrive mixed(ui and core) (batch)", async function () {
|
||||
it("set and retrieve mixed(ui and core) (batch)", async function () {
|
||||
const config = {
|
||||
"ui.chat_bg": "color:yellow",
|
||||
"ui.enter_key_sends": "false",
|
||||
@@ -147,7 +148,10 @@ describe("basic tests", () => {
|
||||
mail_pw: "123456",
|
||||
};
|
||||
await dc.rpc.batchSetConfig(accountId, config);
|
||||
const retrieved = await dc.rpc.batchGetConfig(accountId, Object.keys(config));
|
||||
const retrieved = await dc.rpc.batchGetConfig(
|
||||
accountId,
|
||||
Object.keys(config)
|
||||
);
|
||||
expect(retrieved).to.deep.equal(config);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { assert, expect } from "chai";
|
||||
import { DeltaChat, DeltaChatEvent, EventTypeName } from "../deltachat.js";
|
||||
import {
|
||||
RpcServerHandle,
|
||||
createTempUser,
|
||||
startServer,
|
||||
} from "./test_base.js";
|
||||
import { StdioDeltaChat as DeltaChat, DcEvent } from "../deltachat.js";
|
||||
import { RpcServerHandle, createTempUser, startServer } from "./test_base.js";
|
||||
|
||||
const EVENT_TIMEOUT = 20000
|
||||
const EVENT_TIMEOUT = 20000;
|
||||
|
||||
describe("online tests", function () {
|
||||
let serverHandle: RpcServerHandle;
|
||||
@@ -16,7 +12,7 @@ describe("online tests", function () {
|
||||
let accountId1: number, accountId2: number;
|
||||
|
||||
before(async function () {
|
||||
this.timeout(12000)
|
||||
this.timeout(60000);
|
||||
if (!process.env.DCC_NEW_TMP_EMAIL) {
|
||||
if (process.env.COVERAGE && !process.env.COVERAGE_OFFLINE) {
|
||||
console.error(
|
||||
@@ -26,21 +22,21 @@ describe("online tests", function () {
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(
|
||||
"Missing DCC_NEW_TMP_EMAIL environment variable!, skip intergration tests"
|
||||
"Missing DCC_NEW_TMP_EMAIL environment variable!, skip integration tests"
|
||||
);
|
||||
this.skip();
|
||||
}
|
||||
serverHandle = await startServer();
|
||||
dc = new DeltaChat(serverHandle.url)
|
||||
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout);
|
||||
|
||||
dc.on("ALL", ({ id, contextId }) => {
|
||||
if (id !== "Info") console.log(contextId, id);
|
||||
dc.on("ALL", (contextId, { type }) => {
|
||||
if (type !== "Info") console.log(contextId, type);
|
||||
});
|
||||
|
||||
account1 = await createTempUser(process.env.DCC_NEW_TMP_EMAIL);
|
||||
if (!account1 || !account1.email || !account1.password) {
|
||||
console.log(
|
||||
"We didn't got back an account from the api, skip intergration tests"
|
||||
"We didn't got back an account from the api, skip integration tests"
|
||||
);
|
||||
this.skip();
|
||||
}
|
||||
@@ -48,7 +44,7 @@ describe("online tests", function () {
|
||||
account2 = await createTempUser(process.env.DCC_NEW_TMP_EMAIL);
|
||||
if (!account2 || !account2.email || !account2.password) {
|
||||
console.log(
|
||||
"We didn't got back an account2 from the api, skip intergration tests"
|
||||
"We didn't got back an account2 from the api, skip integration tests"
|
||||
);
|
||||
this.skip();
|
||||
}
|
||||
@@ -74,72 +70,74 @@ describe("online tests", function () {
|
||||
addr: account2.email,
|
||||
mail_pw: account2.password,
|
||||
});
|
||||
await dc.rpc.configure(accountId2)
|
||||
await dc.rpc.configure(accountId2);
|
||||
accountsConfigured = true;
|
||||
});
|
||||
|
||||
it("send and recieve text message", async function () {
|
||||
it("send and receive text message", async function () {
|
||||
if (!accountsConfigured) {
|
||||
this.skip();
|
||||
}
|
||||
this.timeout(15000);
|
||||
|
||||
const contactId = await dc.rpc.contactsCreateContact(
|
||||
const contactId = await dc.rpc.createContact(
|
||||
accountId1,
|
||||
account2.email,
|
||||
null
|
||||
);
|
||||
const chatId = await dc.rpc.contactsCreateChatByContactId(accountId1, contactId);
|
||||
const chatId = await dc.rpc.createChatByContactId(accountId1, contactId);
|
||||
const eventPromise = Promise.race([
|
||||
waitForEvent(dc, "MsgsChanged", accountId2),
|
||||
waitForEvent(dc, "IncomingMsg", accountId2),
|
||||
]);
|
||||
|
||||
await dc.rpc.miscSendTextMessage(accountId1, "Hello", chatId);
|
||||
const { field1: chatIdOnAccountB } = await eventPromise;
|
||||
await dc.rpc.miscSendTextMessage(accountId1, chatId, "Hello");
|
||||
const { chatId: chatIdOnAccountB } = await eventPromise;
|
||||
await dc.rpc.acceptChat(accountId2, chatIdOnAccountB);
|
||||
const messageList = await dc.rpc.messageListGetMessageIds(
|
||||
const messageList = await dc.rpc.getMessageIds(
|
||||
accountId2,
|
||||
chatIdOnAccountB,
|
||||
0
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
expect(messageList).have.length(1);
|
||||
const message = await dc.rpc.messageGetMessage(accountId2, messageList[0]);
|
||||
const message = await dc.rpc.getMessage(accountId2, messageList[0]);
|
||||
expect(message.text).equal("Hello");
|
||||
});
|
||||
|
||||
it("send and recieve text message roundtrip, encrypted on answer onwards", async function () {
|
||||
it("send and receive text message roundtrip, encrypted on answer onwards", async function () {
|
||||
if (!accountsConfigured) {
|
||||
this.skip();
|
||||
}
|
||||
this.timeout(10000);
|
||||
|
||||
// send message from A to B
|
||||
const contactId = await dc.rpc.contactsCreateContact(
|
||||
const contactId = await dc.rpc.createContact(
|
||||
accountId1,
|
||||
account2.email,
|
||||
null
|
||||
);
|
||||
const chatId = await dc.rpc.contactsCreateChatByContactId(accountId1, contactId);
|
||||
const chatId = await dc.rpc.createChatByContactId(accountId1, contactId);
|
||||
const eventPromise = Promise.race([
|
||||
waitForEvent(dc, "MsgsChanged", accountId2),
|
||||
waitForEvent(dc, "IncomingMsg", accountId2),
|
||||
]);
|
||||
dc.rpc.miscSendTextMessage(accountId1, "Hello2", chatId);
|
||||
dc.rpc.miscSendTextMessage(accountId1, chatId, "Hello2");
|
||||
// wait for message from A
|
||||
console.log("wait for message from A");
|
||||
|
||||
const event = await eventPromise;
|
||||
const { field1: chatIdOnAccountB } = event;
|
||||
const { chatId: chatIdOnAccountB } = event;
|
||||
|
||||
await dc.rpc.acceptChat(accountId2, chatIdOnAccountB);
|
||||
const messageList = await dc.rpc.messageListGetMessageIds(
|
||||
const messageList = await dc.rpc.getMessageIds(
|
||||
accountId2,
|
||||
chatIdOnAccountB,
|
||||
0
|
||||
false,
|
||||
false
|
||||
);
|
||||
const message = await dc.rpc.messageGetMessage(
|
||||
const message = await dc.rpc.getMessage(
|
||||
accountId2,
|
||||
messageList.reverse()[0]
|
||||
);
|
||||
@@ -149,14 +147,14 @@ describe("online tests", function () {
|
||||
waitForEvent(dc, "MsgsChanged", accountId1),
|
||||
waitForEvent(dc, "IncomingMsg", accountId1),
|
||||
]);
|
||||
dc.rpc.miscSendTextMessage(accountId2, "super secret message", chatId);
|
||||
dc.rpc.miscSendTextMessage(accountId2, chatId, "super secret message");
|
||||
// Check if answer arives at A and if it is encrypted
|
||||
await eventPromise2;
|
||||
|
||||
const messageId = (
|
||||
await dc.rpc.messageListGetMessageIds(accountId1, chatId, 0)
|
||||
await dc.rpc.getMessageIds(accountId1, chatId, false, false)
|
||||
).reverse()[0];
|
||||
const message2 = await dc.rpc.messageGetMessage(accountId1, messageId);
|
||||
const message2 = await dc.rpc.getMessage(accountId1, messageId);
|
||||
expect(message2.text).equal("super secret message");
|
||||
expect(message2.showPadlock).equal(true);
|
||||
});
|
||||
@@ -179,22 +177,22 @@ describe("online tests", function () {
|
||||
});
|
||||
});
|
||||
|
||||
async function waitForEvent(
|
||||
async function waitForEvent<T extends DcEvent["type"]>(
|
||||
dc: DeltaChat,
|
||||
eventType: EventTypeName,
|
||||
eventType: T,
|
||||
accountId: number,
|
||||
timeout: number = EVENT_TIMEOUT
|
||||
): Promise<DeltaChatEvent> {
|
||||
): Promise<Extract<DcEvent, { type: T }>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const rejectTimeout = setTimeout(
|
||||
() => reject(new Error('Timeout reached before event came in')),
|
||||
() => reject(new Error("Timeout reached before event came in")),
|
||||
timeout
|
||||
)
|
||||
const callback = (event: DeltaChatEvent) => {
|
||||
if (event.contextId == accountId) {
|
||||
);
|
||||
const callback = (contextId: number, event: DcEvent) => {
|
||||
if (contextId == accountId) {
|
||||
dc.off(eventType, callback);
|
||||
clearTimeout(rejectTimeout)
|
||||
resolve(event);
|
||||
clearTimeout(rejectTimeout);
|
||||
resolve(event as any);
|
||||
}
|
||||
};
|
||||
dc.on(eventType, callback);
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
import { tmpdir } from "os";
|
||||
import { join, resolve } from "path";
|
||||
import { mkdtemp, rm } from "fs/promises";
|
||||
import { existsSync } from "fs";
|
||||
import { spawn, exec } from "child_process";
|
||||
import fetch from "node-fetch";
|
||||
|
||||
export const RPC_SERVER_PORT = 20808;
|
||||
import { Readable, Writable } from "node:stream";
|
||||
|
||||
export type RpcServerHandle = {
|
||||
url: string,
|
||||
close: () => Promise<void>
|
||||
}
|
||||
stdin: Writable;
|
||||
stdout: Readable;
|
||||
close: () => Promise<void>;
|
||||
};
|
||||
|
||||
export async function startServer(port: number = RPC_SERVER_PORT): Promise<RpcServerHandle> {
|
||||
export async function startServer(): Promise<RpcServerHandle> {
|
||||
const tmpDir = await mkdtemp(join(tmpdir(), "deltachat-jsonrpc-test"));
|
||||
|
||||
const pathToServerBinary = resolve(join(await getTargetDir(), "debug/deltachat-jsonrpc-server"));
|
||||
console.log('using server binary: ' + pathToServerBinary);
|
||||
|
||||
if (!existsSync(pathToServerBinary)) {
|
||||
throw new Error(
|
||||
"server executable does not exist, you need to build it first" +
|
||||
"\nserver executable not found at " +
|
||||
pathToServerBinary
|
||||
);
|
||||
}
|
||||
const pathToServerBinary = resolve(
|
||||
join(await getTargetDir(), "debug/deltachat-rpc-server")
|
||||
);
|
||||
|
||||
const server = spawn(pathToServerBinary, {
|
||||
cwd: tmpDir,
|
||||
env: {
|
||||
RUST_LOG: process.env.RUST_LOG || "info",
|
||||
DC_PORT: '' + port
|
||||
RUST_MIN_STACK: "8388608",
|
||||
},
|
||||
});
|
||||
|
||||
server.on("error", (err) => {
|
||||
throw new Error(
|
||||
"Failed to start server executable " +
|
||||
pathToServerBinary +
|
||||
", make sure you built it first."
|
||||
);
|
||||
});
|
||||
let shouldClose = false;
|
||||
|
||||
server.on("exit", () => {
|
||||
@@ -43,12 +43,10 @@ export async function startServer(port: number = RPC_SERVER_PORT): Promise<RpcSe
|
||||
});
|
||||
|
||||
server.stderr.pipe(process.stderr);
|
||||
server.stdout.pipe(process.stdout)
|
||||
|
||||
const url = `ws://localhost:${port}/ws`
|
||||
|
||||
return {
|
||||
url,
|
||||
stdin: server.stdin,
|
||||
stdout: server.stdout,
|
||||
close: async () => {
|
||||
shouldClose = true;
|
||||
if (!server.kill()) {
|
||||
@@ -61,13 +59,13 @@ export async function startServer(port: number = RPC_SERVER_PORT): Promise<RpcSe
|
||||
|
||||
export async function createTempUser(url: string) {
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"cache-control": "no-cache",
|
||||
},
|
||||
});
|
||||
if (!response.ok) throw new Error('Received invalid response')
|
||||
return response.json();
|
||||
if (!response.ok) throw new Error("Received invalid response");
|
||||
return response.json();
|
||||
}
|
||||
|
||||
function getTargetDir(): Promise<string> {
|
||||
@@ -91,4 +89,3 @@ function getTargetDir(): Promise<string> {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
8
deltachat-ratelimit/Cargo.toml
Normal file
8
deltachat-ratelimit/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "ratelimit"
|
||||
version = "1.0.0"
|
||||
description = "Token bucket implementation"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[dependencies]
|
||||
@@ -7,7 +7,7 @@
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Ratelimit {
|
||||
pub struct Ratelimit {
|
||||
/// Time of the last update.
|
||||
last_update: SystemTime,
|
||||
|
||||
@@ -25,7 +25,7 @@ impl Ratelimit {
|
||||
/// Returns a new rate limiter with the given constraints.
|
||||
///
|
||||
/// Rate limiter will allow to send no more than `quota` messages within duration `window`.
|
||||
pub(crate) fn new(window: Duration, quota: f64) -> Self {
|
||||
pub fn new(window: Duration, quota: f64) -> Self {
|
||||
Self::new_at(window, quota, SystemTime::now())
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ impl Ratelimit {
|
||||
/// Returns true if can send another message now.
|
||||
///
|
||||
/// This method takes mutable reference
|
||||
pub(crate) fn can_send(&self) -> bool {
|
||||
pub fn can_send(&self) -> bool {
|
||||
self.can_send_at(SystemTime::now())
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ impl Ratelimit {
|
||||
/// It is possible to send message even if over quota, e.g. if the message sending is initiated
|
||||
/// by the user and should not be rate limited. However, sending messages when over quota
|
||||
/// further postpones the time when it will be allowed to send low priority messages.
|
||||
pub(crate) fn send(&mut self) {
|
||||
pub fn send(&mut self) {
|
||||
self.send_at(SystemTime::now())
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ impl Ratelimit {
|
||||
}
|
||||
|
||||
/// Calculates the time until `can_send` will return `true`.
|
||||
pub(crate) fn until_can_send(&self) -> Duration {
|
||||
pub fn until_can_send(&self) -> Duration {
|
||||
self.until_can_send_at(SystemTime::now())
|
||||
}
|
||||
}
|
||||
20
deltachat-repl/Cargo.toml
Normal file
20
deltachat-repl/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "deltachat-repl"
|
||||
version = "1.112.0"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ansi_term = "0.12.1"
|
||||
anyhow = "1"
|
||||
deltachat = { path = "..", features = ["internals"]}
|
||||
dirs = "4"
|
||||
log = "0.4.16"
|
||||
pretty_env_logger = "0.4"
|
||||
rusqlite = "0.28"
|
||||
rustyline = "11"
|
||||
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
vendored = ["deltachat/vendored"]
|
||||
@@ -20,6 +20,7 @@ use deltachat::log::LogExt;
|
||||
use deltachat::message::{self, Message, MessageState, MsgId, Viewtype};
|
||||
use deltachat::peerstate::*;
|
||||
use deltachat::qr::*;
|
||||
use deltachat::reaction::send_reaction;
|
||||
use deltachat::receive_imf::*;
|
||||
use deltachat::sql;
|
||||
use deltachat::tools::*;
|
||||
@@ -28,13 +29,13 @@ use tokio::fs;
|
||||
|
||||
/// Reset database tables.
|
||||
/// Argument is a bitmask, executing single or multiple actions in one call.
|
||||
/// e.g. bitmask 7 triggers actions definded with bits 1, 2 and 4.
|
||||
/// e.g. bitmask 7 triggers actions defined with bits 1, 2 and 4.
|
||||
async fn reset_tables(context: &Context, bits: i32) {
|
||||
println!("Resetting tables ({})...", bits);
|
||||
println!("Resetting tables ({bits})...");
|
||||
if 0 != bits & 1 {
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM jobs;", paramsv![])
|
||||
.execute("DELETE FROM jobs;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
println!("(1) Jobs reset.");
|
||||
@@ -42,7 +43,7 @@ async fn reset_tables(context: &Context, bits: i32) {
|
||||
if 0 != bits & 2 {
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM acpeerstates;", paramsv![])
|
||||
.execute("DELETE FROM acpeerstates;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
println!("(2) Peerstates reset.");
|
||||
@@ -50,7 +51,7 @@ async fn reset_tables(context: &Context, bits: i32) {
|
||||
if 0 != bits & 4 {
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM keypairs;", paramsv![])
|
||||
.execute("DELETE FROM keypairs;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
println!("(4) Private keypairs reset.");
|
||||
@@ -58,36 +59,36 @@ async fn reset_tables(context: &Context, bits: i32) {
|
||||
if 0 != bits & 8 {
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM contacts WHERE id>9;", paramsv![])
|
||||
.execute("DELETE FROM contacts WHERE id>9;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM chats WHERE id>9;", paramsv![])
|
||||
.execute("DELETE FROM chats WHERE id>9;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM chats_contacts;", paramsv![])
|
||||
.execute("DELETE FROM chats_contacts;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM msgs WHERE id>9;", paramsv![])
|
||||
.execute("DELETE FROM msgs WHERE id>9;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
context
|
||||
.sql()
|
||||
.execute(
|
||||
"DELETE FROM config WHERE keyname LIKE 'imap.%' OR keyname LIKE 'configured%';",
|
||||
paramsv![],
|
||||
(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
context.sql().config_cache().write().await.clear();
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM leftgrps;", paramsv![])
|
||||
.execute("DELETE FROM leftgrps;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
println!("(8) Rest but server config reset.");
|
||||
@@ -100,7 +101,7 @@ async fn poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<
|
||||
let data = read_file(context, filename).await?;
|
||||
|
||||
if let Err(err) = receive_imf(context, &data, false).await {
|
||||
println!("receive_imf errored: {:?}", err);
|
||||
println!("receive_imf errored: {err:?}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -147,7 +148,7 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool {
|
||||
let name = name_f.to_string_lossy();
|
||||
if name.ends_with(".eml") {
|
||||
let path_plus_name = format!("{}/{}", &real_spec, name);
|
||||
println!("Import: {}", path_plus_name);
|
||||
println!("Import: {path_plus_name}");
|
||||
if poke_eml_file(context, path_plus_name).await.is_ok() {
|
||||
read_cnt += 1
|
||||
}
|
||||
@@ -167,7 +168,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
.await
|
||||
.expect("invalid contact");
|
||||
let contact_name = if let Some(name) = msg.get_override_sender_name() {
|
||||
format!("~{}", name)
|
||||
format!("~{name}")
|
||||
} else {
|
||||
contact.get_display_name().to_string()
|
||||
};
|
||||
@@ -222,7 +223,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
"[WEBXDC: {}, icon={}, document={}, summary={}, source_code_url={}]",
|
||||
info.name, info.icon, info.document, info.summary, info.source_code_url
|
||||
),
|
||||
Err(err) => format!("[get_webxdc_info() failed: {}]", err),
|
||||
Err(err) => format!("[get_webxdc_info() failed: {err}]"),
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
@@ -335,6 +336,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
has-backup\n\
|
||||
export-backup\n\
|
||||
import-backup <backup-file>\n\
|
||||
send-backup\n\
|
||||
receive-backup <qr>\n\
|
||||
export-keys\n\
|
||||
import-keys\n\
|
||||
export-setup\n\
|
||||
@@ -407,6 +410,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
resend <msg-id>\n\
|
||||
markseen <msg-id>\n\
|
||||
delmsg <msg-id>\n\
|
||||
react <msg-id> [<reaction>]\n\
|
||||
===========================Contact commands==\n\
|
||||
listcontacts [<query>]\n\
|
||||
listverified [<query>]\n\
|
||||
@@ -433,10 +437,9 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
),
|
||||
},
|
||||
"initiate-key-transfer" => match initiate_key_transfer(&context).await {
|
||||
Ok(setup_code) => println!(
|
||||
"Setup code for the transferred setup message: {}",
|
||||
setup_code,
|
||||
),
|
||||
Ok(setup_code) => {
|
||||
println!("Setup code for the transferred setup message: {setup_code}",)
|
||||
}
|
||||
Err(err) => bail!("Failed to generate setup code: {}", err),
|
||||
},
|
||||
"get-setupcodebegin" => {
|
||||
@@ -485,6 +488,17 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
"send-backup" => {
|
||||
let provider = BackupProvider::prepare(&context).await?;
|
||||
let qr = provider.qr();
|
||||
println!("QR code: {}", format_backup(&qr)?);
|
||||
provider.await?;
|
||||
}
|
||||
"receive-backup" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <qr> is missing.");
|
||||
let qr = check_qr(&context, arg1).await?;
|
||||
deltachat::imex::get_backup(&context, qr).await?;
|
||||
}
|
||||
"export-keys" => {
|
||||
let dir = dirs::home_dir().unwrap_or_default();
|
||||
imex(&context, ImexMode::ExportSelfKeys, dir.as_ref(), None).await?;
|
||||
@@ -526,7 +540,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
ensure!(!arg1.is_empty(), "Argument <key> missing.");
|
||||
let key = config::Config::from_str(arg1)?;
|
||||
let val = context.get_config(key).await;
|
||||
println!("{}={:?}", key, val);
|
||||
println!("{key}={val:?}");
|
||||
}
|
||||
"info" => {
|
||||
println!("{:#?}", context.get_info().await);
|
||||
@@ -538,7 +552,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
match context.get_connectivity_html().await {
|
||||
Ok(html) => {
|
||||
fs::write(&file, html).await?;
|
||||
println!("Report written to: {:#?}", file);
|
||||
println!("Report written to: {file:#?}");
|
||||
}
|
||||
Err(err) => {
|
||||
bail!("Failed to get connectivity html: {}", err);
|
||||
@@ -552,7 +566,11 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
sql::housekeeping(&context).await.ok_or_log(&context);
|
||||
}
|
||||
"listchats" | "listarchived" | "chats" => {
|
||||
let listflags = if arg0 == "listarchived" { 0x01 } else { 0 };
|
||||
let listflags = if arg0 == "listarchived" {
|
||||
DC_GCL_ARCHIVED_ONLY
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let time_start = std::time::SystemTime::now();
|
||||
let chatlist = Chatlist::try_load(
|
||||
&context,
|
||||
@@ -607,7 +625,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
"{}{}{} [{}]{}",
|
||||
summary
|
||||
.prefix
|
||||
.map_or_else(String::new, |prefix| format!("{}: ", prefix)),
|
||||
.map_or_else(String::new, |prefix| format!("{prefix}: ")),
|
||||
summary.text,
|
||||
statestr,
|
||||
×tr,
|
||||
@@ -625,8 +643,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
if location::is_sending_locations_to_chat(&context, None).await? {
|
||||
println!("Location streaming enabled.");
|
||||
}
|
||||
println!("{} chats", cnt);
|
||||
println!("{:?} to create this list", time_needed);
|
||||
println!("{cnt} chats");
|
||||
println!("{time_needed:?} to create this list");
|
||||
}
|
||||
"chat" => {
|
||||
if sel_chat.is_none() && arg1.is_empty() {
|
||||
@@ -634,7 +652,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
}
|
||||
if !arg1.is_empty() {
|
||||
let id = ChatId::new(arg1.parse()?);
|
||||
println!("Selecting chat {}", id);
|
||||
println!("Selecting chat {id}");
|
||||
sel_chat = Some(Chat::load_from_db(&context, id).await?);
|
||||
*chat_id = id;
|
||||
}
|
||||
@@ -643,8 +661,15 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
let sel_chat = sel_chat.as_ref().unwrap();
|
||||
|
||||
let time_start = std::time::SystemTime::now();
|
||||
let msglist =
|
||||
chat::get_chat_msgs(&context, sel_chat.get_id(), DC_GCM_ADDDAYMARKER).await?;
|
||||
let msglist = chat::get_chat_msgs_ex(
|
||||
&context,
|
||||
sel_chat.get_id(),
|
||||
chat::MessageListOptions {
|
||||
info_only: false,
|
||||
add_daymarker: true,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let time_needed = time_start.elapsed().unwrap_or_default();
|
||||
|
||||
let msglist: Vec<MsgId> = msglist
|
||||
@@ -680,7 +705,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
},
|
||||
match sel_chat.get_profile_image(&context).await? {
|
||||
Some(icon) => match icon.to_str() {
|
||||
Some(icon) => format!(" Icon: {}", icon),
|
||||
Some(icon) => format!(" Icon: {icon}"),
|
||||
_ => " Icon: Err".to_string(),
|
||||
},
|
||||
_ => "".to_string(),
|
||||
@@ -706,8 +731,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
let time_noticed_needed = time_noticed_start.elapsed().unwrap_or_default();
|
||||
|
||||
println!(
|
||||
"{:?} to create this list, {:?} to mark all messages as noticed.",
|
||||
time_needed, time_noticed_needed
|
||||
"{time_needed:?} to create this list, {time_noticed_needed:?} to mark all messages as noticed."
|
||||
);
|
||||
}
|
||||
"createchat" => {
|
||||
@@ -715,26 +739,26 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
let contact_id = ContactId::new(arg1.parse()?);
|
||||
let chat_id = ChatId::create_for_contact(&context, contact_id).await?;
|
||||
|
||||
println!("Single#{} created successfully.", chat_id,);
|
||||
println!("Single#{chat_id} created successfully.",);
|
||||
}
|
||||
"creategroup" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <name> missing.");
|
||||
let chat_id =
|
||||
chat::create_group_chat(&context, ProtectionStatus::Unprotected, arg1).await?;
|
||||
|
||||
println!("Group#{} created successfully.", chat_id);
|
||||
println!("Group#{chat_id} created successfully.");
|
||||
}
|
||||
"createbroadcast" => {
|
||||
let chat_id = chat::create_broadcast_list(&context).await?;
|
||||
|
||||
println!("Broadcast#{} created successfully.", chat_id);
|
||||
println!("Broadcast#{chat_id} created successfully.");
|
||||
}
|
||||
"createprotected" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <name> missing.");
|
||||
let chat_id =
|
||||
chat::create_group_chat(&context, ProtectionStatus::Protected, arg1).await?;
|
||||
|
||||
println!("Group#{} created and protected successfully.", chat_id);
|
||||
println!("Group#{chat_id} created and protected successfully.");
|
||||
}
|
||||
"addmember" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected");
|
||||
@@ -764,7 +788,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
chat::set_chat_name(
|
||||
&context,
|
||||
sel_chat.as_ref().unwrap().get_id(),
|
||||
format!("{} {}", arg1, arg2).trim(),
|
||||
format!("{arg1} {arg2}").trim(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -858,7 +882,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
if continue_streaming {
|
||||
println!("Success, streaming should be continued.");
|
||||
} else {
|
||||
println!("Success, streaming can be stoppped.");
|
||||
println!("Success, streaming can be stopped.");
|
||||
}
|
||||
}
|
||||
"dellocations" => {
|
||||
@@ -868,7 +892,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
ensure!(!arg1.is_empty(), "No message text given.");
|
||||
|
||||
let msg = format!("{} {}", arg1, arg2);
|
||||
let msg = format!("{arg1} {arg2}");
|
||||
|
||||
chat::send_text_msg(&context, sel_chat.as_ref().unwrap().get_id(), msg).await?;
|
||||
}
|
||||
@@ -910,7 +934,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
chat::send_msg(&context, sel_chat.as_ref().unwrap().get_id(), &mut msg).await?;
|
||||
}
|
||||
"sendsyncmsg" => match context.send_sync_msg().await? {
|
||||
Some(msg_id) => println!("sync message sent as {}.", msg_id),
|
||||
Some(msg_id) => println!("sync message sent as {msg_id}."),
|
||||
None => println!("sync message not needed."),
|
||||
},
|
||||
"sendupdate" => {
|
||||
@@ -930,7 +954,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
"listmsgs" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <query> missing.");
|
||||
|
||||
let query = format!("{} {}", arg1, arg2).trim().to_string();
|
||||
let query = format!("{arg1} {arg2}").trim().to_string();
|
||||
let chat = sel_chat.as_ref().map(|sel_chat| sel_chat.get_id());
|
||||
let time_start = std::time::SystemTime::now();
|
||||
let msglist = context.search_msgs(chat, &query).await?;
|
||||
@@ -948,7 +972,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
},
|
||||
query,
|
||||
);
|
||||
println!("{:?} to create this list", time_needed);
|
||||
println!("{time_needed:?} to create this list");
|
||||
}
|
||||
"draft" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
@@ -994,9 +1018,9 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
println!("{} images or videos: ", images.len());
|
||||
for (i, data) in images.iter().enumerate() {
|
||||
if 0 == i {
|
||||
print!("{}", data);
|
||||
print!("{data}");
|
||||
} else {
|
||||
print!(", {}", data);
|
||||
print!(", {data}");
|
||||
}
|
||||
}
|
||||
println!();
|
||||
@@ -1067,12 +1091,12 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||
let id = MsgId::new(arg1.parse()?);
|
||||
let res = message::get_msg_info(&context, id).await?;
|
||||
println!("{}", res);
|
||||
println!("{res}");
|
||||
}
|
||||
"download" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||
let id = MsgId::new(arg1.parse()?);
|
||||
println!("Scheduling download for {:?}", id);
|
||||
println!("Scheduling download for {id:?}");
|
||||
id.download_full(&context).await?;
|
||||
}
|
||||
"html" => {
|
||||
@@ -1083,7 +1107,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
.join(format!("msg-{}.html", id.to_u32()));
|
||||
let html = id.get_html(&context).await?.unwrap_or_default();
|
||||
fs::write(&file, html).await?;
|
||||
println!("HTML written to: {:#?}", file);
|
||||
println!("HTML written to: {file:#?}");
|
||||
}
|
||||
"listfresh" => {
|
||||
let msglist = context.get_fresh_msgs().await?;
|
||||
@@ -1121,6 +1145,12 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
ids[0] = MsgId::new(arg1.parse()?);
|
||||
message::delete_msgs(&context, &ids).await?;
|
||||
}
|
||||
"react" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||
let msg_id = MsgId::new(arg1.parse()?);
|
||||
let reaction = arg2;
|
||||
send_reaction(&context, msg_id, reaction).await?;
|
||||
}
|
||||
"listcontacts" | "contacts" | "listverified" => {
|
||||
let contacts = Contact::get_all(
|
||||
&context,
|
||||
@@ -1139,7 +1169,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
ensure!(!arg1.is_empty(), "Arguments [<name>] <addr> expected.");
|
||||
|
||||
if !arg2.is_empty() {
|
||||
let book = format!("{}\n{}", arg1, arg2);
|
||||
let book = format!("{arg1}\n{arg2}");
|
||||
Contact::add_address_book(&context, &book).await?;
|
||||
} else {
|
||||
Contact::create(&context, "", arg1).await?;
|
||||
@@ -1166,10 +1196,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
let chatlist = Chatlist::try_load(&context, 0, None, Some(contact_id)).await?;
|
||||
let chatlist_cnt = chatlist.len();
|
||||
if chatlist_cnt > 0 {
|
||||
res += &format!(
|
||||
"\n\n{} chats shared with Contact#{}: ",
|
||||
chatlist_cnt, contact_id,
|
||||
);
|
||||
res += &format!("\n\n{chatlist_cnt} chats shared with Contact#{contact_id}: ",);
|
||||
for i in 0..chatlist_cnt {
|
||||
if 0 != i {
|
||||
res += ", ";
|
||||
@@ -1179,7 +1206,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
}
|
||||
}
|
||||
|
||||
println!("{}", res);
|
||||
println!("{res}");
|
||||
}
|
||||
"delcontact" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
|
||||
@@ -1203,13 +1230,13 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
"checkqr" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <qr-content> missing.");
|
||||
let qr = check_qr(&context, arg1).await?;
|
||||
println!("qr={:?}", qr);
|
||||
println!("qr={qr:?}");
|
||||
}
|
||||
"setqr" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <qr-content> missing.");
|
||||
match set_config_from_qr(&context, arg1).await {
|
||||
Ok(()) => println!("Config set from QR code, you can now call 'configure'"),
|
||||
Err(err) => println!("Cannot set config from QR code: {:?}", err),
|
||||
Err(err) => println!("Cannot set config from QR code: {err:?}"),
|
||||
}
|
||||
}
|
||||
"providerinfo" => {
|
||||
@@ -1219,7 +1246,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
.await?;
|
||||
match provider::get_provider_info(&context, arg1, socks5_enabled).await {
|
||||
Some(info) => {
|
||||
println!("Information for provider belonging to {}:", arg1);
|
||||
println!("Information for provider belonging to {arg1}:");
|
||||
println!("status: {}", info.status as u32);
|
||||
println!("before_login_hint: {}", info.before_login_hint);
|
||||
println!("after_login_hint: {}", info.after_login_hint);
|
||||
@@ -1229,7 +1256,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
}
|
||||
}
|
||||
None => {
|
||||
println!("No information for provider belonging to {} found.", arg1);
|
||||
println!("No information for provider belonging to {arg1} found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1238,7 +1265,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
|
||||
if let Ok(buf) = read_file(&context, &arg1).await {
|
||||
let (width, height) = get_filemeta(&buf)?;
|
||||
println!("width={}, height={}", width, height);
|
||||
println!("width={width}, height={height}");
|
||||
} else {
|
||||
bail!("Command failed.");
|
||||
}
|
||||
@@ -1249,8 +1276,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
let device_cnt = message::estimate_deletion_cnt(&context, false, seconds).await?;
|
||||
let server_cnt = message::estimate_deletion_cnt(&context, true, seconds).await?;
|
||||
println!(
|
||||
"estimated count of messages older than {} seconds:\non device: {}\non server: {}",
|
||||
seconds, device_cnt, server_cnt
|
||||
"estimated count of messages older than {seconds} seconds:\non device: {device_cnt}\non server: {server_cnt}"
|
||||
);
|
||||
}
|
||||
"" => (),
|
||||
@@ -20,6 +20,7 @@ use deltachat::context::*;
|
||||
use deltachat::oauth2::*;
|
||||
use deltachat::qr_code_generator::get_securejoin_qr_svg;
|
||||
use deltachat::securejoin::*;
|
||||
use deltachat::stock_str::StockStrings;
|
||||
use deltachat::{EventType, Events};
|
||||
use log::{error, info, warn};
|
||||
use rustyline::completion::{Completer, FilenameCompleter, Pair};
|
||||
@@ -66,8 +67,19 @@ fn receive_event(event: EventType) {
|
||||
info!(
|
||||
"{}",
|
||||
yellow.paint(format!(
|
||||
"Received MSGS_CHANGED(chat_id={}, msg_id={})",
|
||||
chat_id, msg_id,
|
||||
"Received MSGS_CHANGED(chat_id={chat_id}, msg_id={msg_id})",
|
||||
))
|
||||
);
|
||||
}
|
||||
EventType::ReactionsChanged {
|
||||
chat_id,
|
||||
msg_id,
|
||||
contact_id,
|
||||
} => {
|
||||
info!(
|
||||
"{}",
|
||||
yellow.paint(format!(
|
||||
"Received REACTIONS_CHANGED(chat_id={chat_id}, msg_id={msg_id}, contact_id={contact_id})"
|
||||
))
|
||||
);
|
||||
}
|
||||
@@ -77,7 +89,7 @@ fn receive_event(event: EventType) {
|
||||
EventType::LocationChanged(contact) => {
|
||||
info!(
|
||||
"{}",
|
||||
yellow.paint(format!("Received LOCATION_CHANGED(contact={:?})", contact))
|
||||
yellow.paint(format!("Received LOCATION_CHANGED(contact={contact:?})"))
|
||||
);
|
||||
}
|
||||
EventType::ConfigureProgress { progress, comment } => {
|
||||
@@ -85,21 +97,20 @@ fn receive_event(event: EventType) {
|
||||
info!(
|
||||
"{}",
|
||||
yellow.paint(format!(
|
||||
"Received CONFIGURE_PROGRESS({} ‰, {})",
|
||||
progress, comment
|
||||
"Received CONFIGURE_PROGRESS({progress} ‰, {comment})"
|
||||
))
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
"{}",
|
||||
yellow.paint(format!("Received CONFIGURE_PROGRESS({} ‰)", progress))
|
||||
yellow.paint(format!("Received CONFIGURE_PROGRESS({progress} ‰)"))
|
||||
);
|
||||
}
|
||||
}
|
||||
EventType::ImexProgress(progress) => {
|
||||
info!(
|
||||
"{}",
|
||||
yellow.paint(format!("Received IMEX_PROGRESS({} ‰)", progress))
|
||||
yellow.paint(format!("Received IMEX_PROGRESS({progress} ‰)"))
|
||||
);
|
||||
}
|
||||
EventType::ImexFileWritten(file) => {
|
||||
@@ -111,7 +122,7 @@ fn receive_event(event: EventType) {
|
||||
EventType::ChatModified(chat) => {
|
||||
info!(
|
||||
"{}",
|
||||
yellow.paint(format!("Received CHAT_MODIFIED({})", chat))
|
||||
yellow.paint(format!("Received CHAT_MODIFIED({chat})"))
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
@@ -141,13 +152,15 @@ impl Completer for DcHelper {
|
||||
}
|
||||
}
|
||||
|
||||
const IMEX_COMMANDS: [&str; 12] = [
|
||||
const IMEX_COMMANDS: [&str; 14] = [
|
||||
"initiate-key-transfer",
|
||||
"get-setupcodebegin",
|
||||
"continue-key-transfer",
|
||||
"has-backup",
|
||||
"export-backup",
|
||||
"import-backup",
|
||||
"send-backup",
|
||||
"receive-backup",
|
||||
"export-keys",
|
||||
"import-keys",
|
||||
"export-setup",
|
||||
@@ -207,7 +220,7 @@ const CHAT_COMMANDS: [&str; 36] = [
|
||||
"accept",
|
||||
"blockchat",
|
||||
];
|
||||
const MESSAGE_COMMANDS: [&str; 8] = [
|
||||
const MESSAGE_COMMANDS: [&str; 9] = [
|
||||
"listmsgs",
|
||||
"msginfo",
|
||||
"listfresh",
|
||||
@@ -216,6 +229,7 @@ const MESSAGE_COMMANDS: [&str; 8] = [
|
||||
"markseen",
|
||||
"delmsg",
|
||||
"download",
|
||||
"react",
|
||||
];
|
||||
const CONTACT_COMMANDS: [&str; 9] = [
|
||||
"listcontacts",
|
||||
@@ -298,7 +312,7 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
|
||||
println!("Error: Bad arguments, expected [db-name].");
|
||||
bail!("No db-name specified");
|
||||
}
|
||||
let context = Context::new(Path::new(&args[1]), 0, Events::new()).await?;
|
||||
let context = Context::new(Path::new(&args[1]), 0, Events::new(), StockStrings::new()).await?;
|
||||
|
||||
let events = context.get_event_emitter();
|
||||
tokio::task::spawn(async move {
|
||||
@@ -338,8 +352,8 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
// TODO: ignore "set mail_pw"
|
||||
rl.add_history_entry(line.as_str());
|
||||
let contine = Handle::current().block_on(async {
|
||||
rl.add_history_entry(line.as_str())?;
|
||||
let should_continue = Handle::current().block_on(async {
|
||||
match handle_cmd(line.trim(), ctx.clone(), &mut selected_chat).await {
|
||||
Ok(ExitResult::Continue) => true,
|
||||
Ok(ExitResult::Exit) => {
|
||||
@@ -347,13 +361,13 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
|
||||
false
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error: {}", err);
|
||||
println!("Error: {err:#}");
|
||||
true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if !contine {
|
||||
if !should_continue {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -362,7 +376,7 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error: {}", err);
|
||||
println!("Error: {err:#}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -429,9 +443,9 @@ async fn handle_cmd(
|
||||
if arg0 == "getbadqr" && qr.len() > 40 {
|
||||
qr.replace_range(12..22, "0000000000")
|
||||
}
|
||||
println!("{}", qr);
|
||||
println!("{qr}");
|
||||
let output = Command::new("qrencode")
|
||||
.args(&["-t", "ansiutf8", qr.as_str(), "-o", "-"])
|
||||
.args(["-t", "ansiutf8", qr.as_str(), "-o", "-"])
|
||||
.output()
|
||||
.expect("failed to execute process");
|
||||
io::stdout().write_all(&output.stdout).unwrap();
|
||||
@@ -445,7 +459,7 @@ async fn handle_cmd(
|
||||
match get_securejoin_qr_svg(&ctx, group).await {
|
||||
Ok(svg) => {
|
||||
fs::write(&file, svg).await?;
|
||||
println!("QR code svg written to: {:#?}", file);
|
||||
println!("QR code svg written to: {file:#?}");
|
||||
}
|
||||
Err(err) => {
|
||||
bail!("Failed to get QR code svg: {}", err);
|
||||
41
deltachat-rpc-client/README.md
Normal file
41
deltachat-rpc-client/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Delta Chat RPC python client
|
||||
|
||||
RPC client connects to standalone Delta Chat RPC server `deltachat-rpc-server`
|
||||
and provides asynchronous interface to it.
|
||||
|
||||
## Getting started
|
||||
|
||||
To use Delta Chat RPC client, first build a `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
|
||||
Install it anywhere in your `PATH`.
|
||||
|
||||
## Testing
|
||||
|
||||
1. Build `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
|
||||
2. Run `PATH="../target/debug:$PATH" tox`.
|
||||
|
||||
Additional arguments to `tox` are passed to pytest, e.g. `tox -- -s` does not capture test output.
|
||||
|
||||
## Using in REPL
|
||||
|
||||
Setup a development environment:
|
||||
```
|
||||
$ tox --devenv env
|
||||
$ . env/bin/activate
|
||||
```
|
||||
|
||||
It is recommended to use IPython, because it supports using `await` directly
|
||||
from the REPL.
|
||||
|
||||
```
|
||||
$ pip install ipython
|
||||
$ PATH="../target/debug:$PATH" ipython
|
||||
...
|
||||
In [1]: from deltachat_rpc_client import *
|
||||
In [2]: rpc = Rpc()
|
||||
In [3]: await rpc.start()
|
||||
In [4]: dc = DeltaChat(rpc)
|
||||
In [5]: system_info = await dc.get_system_info()
|
||||
In [6]: system_info["level"]
|
||||
Out[6]: 'awesome'
|
||||
In [7]: await rpc.close()
|
||||
```
|
||||
26
deltachat-rpc-client/examples/echobot.py
Executable file
26
deltachat-rpc-client/examples/echobot.py
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Minimal echo bot example.
|
||||
|
||||
it will echo back any text send to it, it also will print to console all Delta Chat core events.
|
||||
Pass --help to the CLI to see available options.
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
from deltachat_rpc_client import events, run_bot_cli
|
||||
|
||||
hooks = events.HookCollection()
|
||||
|
||||
|
||||
@hooks.on(events.RawEvent)
|
||||
async def log_event(event):
|
||||
print(event)
|
||||
|
||||
|
||||
@hooks.on(events.NewMessage)
|
||||
async def echo(event):
|
||||
snapshot = event.message_snapshot
|
||||
await snapshot.chat.send_text(snapshot.text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(run_bot_cli(hooks))
|
||||
74
deltachat-rpc-client/examples/echobot_advanced.py
Normal file
74
deltachat-rpc-client/examples/echobot_advanced.py
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Advanced echo bot example.
|
||||
|
||||
it will echo back any message that has non-empty text and also supports the /help command.
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from deltachat_rpc_client import Bot, DeltaChat, EventType, Rpc, events
|
||||
|
||||
hooks = events.HookCollection()
|
||||
|
||||
|
||||
@hooks.on(events.RawEvent)
|
||||
async def log_event(event):
|
||||
if event.type == EventType.INFO:
|
||||
logging.info(event.msg)
|
||||
elif event.type == EventType.WARNING:
|
||||
logging.warning(event.msg)
|
||||
|
||||
|
||||
@hooks.on(events.RawEvent(EventType.ERROR))
|
||||
async def log_error(event):
|
||||
logging.error(event.msg)
|
||||
|
||||
|
||||
@hooks.on(events.MemberListChanged)
|
||||
async def on_memberlist_changed(event):
|
||||
logging.info("member %s was %s", event.member, "added" if event.member_added else "removed")
|
||||
|
||||
|
||||
@hooks.on(events.GroupImageChanged)
|
||||
async def on_group_image_changed(event):
|
||||
logging.info("group image %s", "deleted" if event.image_deleted else "changed")
|
||||
|
||||
|
||||
@hooks.on(events.GroupNameChanged)
|
||||
async def on_group_name_changed(event):
|
||||
logging.info("group name changed, old name: %s", event.old_name)
|
||||
|
||||
|
||||
@hooks.on(events.NewMessage(func=lambda e: not e.command))
|
||||
async def echo(event):
|
||||
snapshot = event.message_snapshot
|
||||
if snapshot.text or snapshot.file:
|
||||
await snapshot.chat.send_message(text=snapshot.text, file=snapshot.file)
|
||||
|
||||
|
||||
@hooks.on(events.NewMessage(command="/help"))
|
||||
async def help_command(event):
|
||||
snapshot = event.message_snapshot
|
||||
await snapshot.chat.send_text("Send me any message and I will echo it back")
|
||||
|
||||
|
||||
async def main():
|
||||
async with Rpc() as rpc:
|
||||
deltachat = DeltaChat(rpc)
|
||||
system_info = await deltachat.get_system_info()
|
||||
logging.info("Running deltachat core %s", system_info.deltachat_core_version)
|
||||
|
||||
accounts = await deltachat.get_all_accounts()
|
||||
account = accounts[0] if accounts else await deltachat.add_account()
|
||||
|
||||
bot = Bot(account, hooks)
|
||||
if not await bot.is_configured():
|
||||
# Save a reference to avoid garbage collection of the task.
|
||||
_configure_task = asyncio.create_task(bot.configure(email=sys.argv[1], password=sys.argv[2]))
|
||||
await bot.run_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
asyncio.run(main())
|
||||
57
deltachat-rpc-client/examples/echobot_no_hooks.py
Normal file
57
deltachat-rpc-client/examples/echobot_no_hooks.py
Normal file
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Example echo bot without using hooks
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from deltachat_rpc_client import DeltaChat, EventType, Rpc
|
||||
|
||||
|
||||
async def main():
|
||||
async with Rpc() as rpc:
|
||||
deltachat = DeltaChat(rpc)
|
||||
system_info = await deltachat.get_system_info()
|
||||
logging.info("Running deltachat core %s", system_info["deltachat_core_version"])
|
||||
|
||||
accounts = await deltachat.get_all_accounts()
|
||||
account = accounts[0] if accounts else await deltachat.add_account()
|
||||
|
||||
await account.set_config("bot", "1")
|
||||
if not await account.is_configured():
|
||||
logging.info("Account is not configured, configuring")
|
||||
await account.set_config("addr", sys.argv[1])
|
||||
await account.set_config("mail_pw", sys.argv[2])
|
||||
await account.configure()
|
||||
logging.info("Configured")
|
||||
else:
|
||||
logging.info("Account is already configured")
|
||||
await deltachat.start_io()
|
||||
|
||||
async def process_messages():
|
||||
for message in await account.get_fresh_messages_in_arrival_order():
|
||||
snapshot = await message.get_snapshot()
|
||||
if not snapshot.is_bot and not snapshot.is_info:
|
||||
await snapshot.chat.send_text(snapshot.text)
|
||||
await snapshot.message.mark_seen()
|
||||
|
||||
# Process old messages.
|
||||
await process_messages()
|
||||
|
||||
while True:
|
||||
event = await account.wait_for_event()
|
||||
if event["type"] == EventType.INFO:
|
||||
logging.info("%s", event["msg"])
|
||||
elif event["type"] == EventType.WARNING:
|
||||
logging.warning("%s", event["msg"])
|
||||
elif event["type"] == EventType.ERROR:
|
||||
logging.error("%s", event["msg"])
|
||||
elif event["type"] == EventType.INCOMING_MSG:
|
||||
logging.info("Got an incoming message")
|
||||
await process_messages()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
asyncio.run(main())
|
||||
60
deltachat-rpc-client/pyproject.toml
Normal file
60
deltachat-rpc-client/pyproject.toml
Normal file
@@ -0,0 +1,60 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=45", "setuptools_scm[toml]>=6.2"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat-rpc-client"
|
||||
description = "Python client for Delta Chat core JSON-RPC interface"
|
||||
dependencies = [
|
||||
"aiohttp",
|
||||
"aiodns"
|
||||
]
|
||||
dynamic = [
|
||||
"version"
|
||||
]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
deltachat_rpc_client = [
|
||||
"py.typed"
|
||||
]
|
||||
|
||||
[project.entry-points.pytest11]
|
||||
"deltachat_rpc_client.pytestplugin" = "deltachat_rpc_client.pytestplugin"
|
||||
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
|
||||
[tool.ruff]
|
||||
select = [
|
||||
"E", "W", # pycodestyle
|
||||
"F", # Pyflakes
|
||||
"N", # pep8-naming
|
||||
"I", # isort
|
||||
|
||||
"ARG", # flake8-unused-arguments
|
||||
"B", # flake8-bugbear
|
||||
"C4", # flake8-comprehensions
|
||||
"COM", # flake8-commas
|
||||
"DTZ", # flake8-datetimez
|
||||
"ICN", # flake8-import-conventions
|
||||
"ISC", # flake8-implicit-str-concat
|
||||
"PIE", # flake8-pie
|
||||
"PT", # flake8-pytest-style
|
||||
"RET", # flake8-return
|
||||
"SIM", # flake8-simplify
|
||||
"TCH", # flake8-type-checking
|
||||
"TID", # flake8-tidy-imports
|
||||
"YTT", # flake8-2020
|
||||
|
||||
"ERA", # eradicate
|
||||
|
||||
"PLC", # Pylint Convention
|
||||
"PLE", # Pylint Error
|
||||
"PLW", # Pylint Warning
|
||||
|
||||
"RUF006" # asyncio-dangling-task
|
||||
]
|
||||
line-length = 120
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
25
deltachat-rpc-client/src/deltachat_rpc_client/__init__.py
Normal file
25
deltachat-rpc-client/src/deltachat_rpc_client/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""Delta Chat asynchronous high-level API"""
|
||||
from ._utils import AttrDict, run_bot_cli, run_client_cli
|
||||
from .account import Account
|
||||
from .chat import Chat
|
||||
from .client import Bot, Client
|
||||
from .const import EventType
|
||||
from .contact import Contact
|
||||
from .deltachat import DeltaChat
|
||||
from .message import Message
|
||||
from .rpc import Rpc
|
||||
|
||||
__all__ = [
|
||||
"Account",
|
||||
"AttrDict",
|
||||
"Bot",
|
||||
"Chat",
|
||||
"Client",
|
||||
"Contact",
|
||||
"DeltaChat",
|
||||
"EventType",
|
||||
"Message",
|
||||
"Rpc",
|
||||
"run_bot_cli",
|
||||
"run_client_cli",
|
||||
]
|
||||
170
deltachat-rpc-client/src/deltachat_rpc_client/_utils.py
Normal file
170
deltachat-rpc-client/src/deltachat_rpc_client/_utils.py
Normal file
@@ -0,0 +1,170 @@
|
||||
import argparse
|
||||
import asyncio
|
||||
import re
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, Callable, Iterable, Optional, Tuple, Type, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .client import Client
|
||||
from .events import EventFilter
|
||||
|
||||
|
||||
def _camel_to_snake(name: str) -> str:
|
||||
name = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
|
||||
name = re.sub("__([A-Z])", r"_\1", name)
|
||||
name = re.sub("([a-z0-9])([A-Z])", r"\1_\2", name)
|
||||
return name.lower()
|
||||
|
||||
|
||||
def _to_attrdict(obj):
|
||||
if isinstance(obj, AttrDict):
|
||||
return obj
|
||||
if isinstance(obj, dict):
|
||||
return AttrDict(obj)
|
||||
if isinstance(obj, list):
|
||||
return [_to_attrdict(elem) for elem in obj]
|
||||
return obj
|
||||
|
||||
|
||||
class AttrDict(dict):
|
||||
"""Dictionary that allows accessing values using the "dot notation" as attributes."""
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__({_camel_to_snake(key): _to_attrdict(value) for key, value in dict(*args, **kwargs).items()})
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr in self:
|
||||
return self[attr]
|
||||
raise AttributeError("Attribute not found: " + str(attr))
|
||||
|
||||
def __setattr__(self, attr, val):
|
||||
if attr in self:
|
||||
raise AttributeError("Attribute-style access is read only")
|
||||
super().__setattr__(attr, val)
|
||||
|
||||
|
||||
async def run_client_cli(
|
||||
hooks: Optional[Iterable[Tuple[Callable, Union[type, "EventFilter"]]]] = None,
|
||||
argv: Optional[list] = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Run a simple command line app, using the given hooks.
|
||||
|
||||
Extra keyword arguments are passed to the internal Rpc object.
|
||||
"""
|
||||
from .client import Client
|
||||
|
||||
await _run_cli(Client, hooks, argv, **kwargs)
|
||||
|
||||
|
||||
async def run_bot_cli(
|
||||
hooks: Optional[Iterable[Tuple[Callable, Union[type, "EventFilter"]]]] = None,
|
||||
argv: Optional[list] = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
"""Run a simple bot command line using the given hooks.
|
||||
|
||||
Extra keyword arguments are passed to the internal Rpc object.
|
||||
"""
|
||||
from .client import Bot
|
||||
|
||||
await _run_cli(Bot, hooks, argv, **kwargs)
|
||||
|
||||
|
||||
async def _run_cli(
|
||||
client_type: Type["Client"],
|
||||
hooks: Optional[Iterable[Tuple[Callable, Union[type, "EventFilter"]]]] = None,
|
||||
argv: Optional[list] = None,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
from .deltachat import DeltaChat
|
||||
from .rpc import Rpc
|
||||
|
||||
if argv is None:
|
||||
argv = sys.argv
|
||||
|
||||
parser = argparse.ArgumentParser(prog=argv[0] if argv else None)
|
||||
parser.add_argument(
|
||||
"accounts_dir",
|
||||
help="accounts folder (default: current working directory)",
|
||||
nargs="?",
|
||||
)
|
||||
parser.add_argument("--email", action="store", help="email address")
|
||||
parser.add_argument("--password", action="store", help="password")
|
||||
args = parser.parse_args(argv[1:])
|
||||
|
||||
async with Rpc(accounts_dir=args.accounts_dir, **kwargs) as rpc:
|
||||
deltachat = DeltaChat(rpc)
|
||||
core_version = (await deltachat.get_system_info()).deltachat_core_version
|
||||
accounts = await deltachat.get_all_accounts()
|
||||
account = accounts[0] if accounts else await deltachat.add_account()
|
||||
|
||||
client = client_type(account, hooks)
|
||||
client.logger.debug("Running deltachat core %s", core_version)
|
||||
if not await client.is_configured():
|
||||
assert args.email, "Account is not configured and email must be provided"
|
||||
assert args.password, "Account is not configured and password must be provided"
|
||||
# Save a reference to avoid garbage collection of the task.
|
||||
_configure_task = asyncio.create_task(client.configure(email=args.email, password=args.password))
|
||||
await client.run_forever()
|
||||
|
||||
|
||||
def extract_addr(text: str) -> str:
|
||||
"""extract email address from the given text."""
|
||||
match = re.match(r".*\((.+@.+)\)", text)
|
||||
if match:
|
||||
text = match.group(1)
|
||||
text = text.rstrip(".")
|
||||
return text.strip()
|
||||
|
||||
|
||||
def parse_system_image_changed(text: str) -> Optional[Tuple[str, bool]]:
|
||||
"""return image changed/deleted info from parsing the given system message text."""
|
||||
text = text.lower()
|
||||
match = re.match(r"group image (changed|deleted) by (.+).", text)
|
||||
if match:
|
||||
action, actor = match.groups()
|
||||
return (extract_addr(actor), action == "deleted")
|
||||
return None
|
||||
|
||||
|
||||
def parse_system_title_changed(text: str) -> Optional[Tuple[str, str]]:
|
||||
text = text.lower()
|
||||
match = re.match(r'group name changed from "(.+)" to ".+" by (.+).', text)
|
||||
if match:
|
||||
old_title, actor = match.groups()
|
||||
return (extract_addr(actor), old_title)
|
||||
return None
|
||||
|
||||
|
||||
def parse_system_add_remove(text: str) -> Optional[Tuple[str, str, str]]:
|
||||
"""return add/remove info from parsing the given system message text.
|
||||
|
||||
returns a (action, affected, actor) tuple.
|
||||
"""
|
||||
# You removed member a@b.
|
||||
# You added member a@b.
|
||||
# Member Me (x@y) removed by a@b.
|
||||
# Member x@y added by a@b
|
||||
# Member With space (tmp1@x.org) removed by tmp2@x.org.
|
||||
# Member With space (tmp1@x.org) removed by Another member (tmp2@x.org).",
|
||||
# Group left by some one (tmp1@x.org).
|
||||
# Group left by tmp1@x.org.
|
||||
text = text.lower()
|
||||
|
||||
match = re.match(r"member (.+) (removed|added) by (.+)", text)
|
||||
if match:
|
||||
affected, action, actor = match.groups()
|
||||
return action, extract_addr(affected), extract_addr(actor)
|
||||
|
||||
match = re.match(r"you (removed|added) member (.+)", text)
|
||||
if match:
|
||||
action, affected = match.groups()
|
||||
return action, extract_addr(affected), "me"
|
||||
|
||||
if text.startswith("group left by "):
|
||||
addr = extract_addr(text[13:])
|
||||
if addr:
|
||||
return "removed", addr, addr
|
||||
|
||||
return None
|
||||
245
deltachat-rpc-client/src/deltachat_rpc_client/account.py
Normal file
245
deltachat-rpc-client/src/deltachat_rpc_client/account.py
Normal file
@@ -0,0 +1,245 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, List, Optional, Tuple, Union
|
||||
|
||||
from ._utils import AttrDict
|
||||
from .chat import Chat
|
||||
from .const import ChatlistFlag, ContactFlag, SpecialContactId
|
||||
from .contact import Contact
|
||||
from .message import Message
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .deltachat import DeltaChat
|
||||
from .rpc import Rpc
|
||||
|
||||
|
||||
@dataclass
|
||||
class Account:
|
||||
"""Delta Chat account."""
|
||||
|
||||
manager: "DeltaChat"
|
||||
id: int
|
||||
|
||||
@property
|
||||
def _rpc(self) -> "Rpc":
|
||||
return self.manager.rpc
|
||||
|
||||
async def wait_for_event(self) -> AttrDict:
|
||||
"""Wait until the next event and return it."""
|
||||
return AttrDict(await self._rpc.wait_for_event(self.id))
|
||||
|
||||
async def remove(self) -> None:
|
||||
"""Remove the account."""
|
||||
await self._rpc.remove_account(self.id)
|
||||
|
||||
async def start_io(self) -> None:
|
||||
"""Start the account I/O."""
|
||||
await self._rpc.start_io(self.id)
|
||||
|
||||
async def stop_io(self) -> None:
|
||||
"""Stop the account I/O."""
|
||||
await self._rpc.stop_io(self.id)
|
||||
|
||||
async def get_info(self) -> AttrDict:
|
||||
"""Return dictionary of this account configuration parameters."""
|
||||
return AttrDict(await self._rpc.get_info(self.id))
|
||||
|
||||
async def get_size(self) -> int:
|
||||
"""Get the combined filesize of an account in bytes."""
|
||||
return await self._rpc.get_account_file_size(self.id)
|
||||
|
||||
async def is_configured(self) -> bool:
|
||||
"""Return True if this account is configured."""
|
||||
return await self._rpc.is_configured(self.id)
|
||||
|
||||
async def set_config(self, key: str, value: Optional[str] = None) -> None:
|
||||
"""Set configuration value."""
|
||||
await self._rpc.set_config(self.id, key, value)
|
||||
|
||||
async def get_config(self, key: str) -> Optional[str]:
|
||||
"""Get configuration value."""
|
||||
return await self._rpc.get_config(self.id, key)
|
||||
|
||||
async def update_config(self, **kwargs) -> None:
|
||||
"""update config values."""
|
||||
for key, value in kwargs.items():
|
||||
await self.set_config(key, value)
|
||||
|
||||
async def set_avatar(self, img_path: Optional[str] = None) -> None:
|
||||
"""Set self avatar.
|
||||
|
||||
Passing None will discard the currently set avatar.
|
||||
"""
|
||||
await self.set_config("selfavatar", img_path)
|
||||
|
||||
async def get_avatar(self) -> Optional[str]:
|
||||
"""Get self avatar."""
|
||||
return await self.get_config("selfavatar")
|
||||
|
||||
async def configure(self) -> None:
|
||||
"""Configure an account."""
|
||||
await self._rpc.configure(self.id)
|
||||
|
||||
async def create_contact(self, obj: Union[int, str, Contact], name: Optional[str] = None) -> Contact:
|
||||
"""Create a new Contact or return an existing one.
|
||||
|
||||
Calling this method will always result in the same
|
||||
underlying contact id. If there already is a Contact
|
||||
with that e-mail address, it is unblocked and its display
|
||||
name is updated if specified.
|
||||
|
||||
:param obj: email-address or contact id.
|
||||
:param name: (optional) display name for this contact.
|
||||
"""
|
||||
if isinstance(obj, int):
|
||||
obj = Contact(self, obj)
|
||||
if isinstance(obj, Contact):
|
||||
obj = (await obj.get_snapshot()).address
|
||||
return Contact(self, await self._rpc.create_contact(self.id, obj, name))
|
||||
|
||||
def get_contact_by_id(self, contact_id: int) -> Contact:
|
||||
"""Return Contact instance for the given contact ID."""
|
||||
return Contact(self, contact_id)
|
||||
|
||||
async def get_contact_by_addr(self, address: str) -> Optional[Contact]:
|
||||
"""Check if an e-mail address belongs to a known and unblocked contact."""
|
||||
contact_id = await self._rpc.lookup_contact_id_by_addr(self.id, address)
|
||||
return contact_id and Contact(self, contact_id)
|
||||
|
||||
async def get_blocked_contacts(self) -> List[AttrDict]:
|
||||
"""Return a list with snapshots of all blocked contacts."""
|
||||
contacts = await self._rpc.get_blocked_contacts(self.id)
|
||||
return [AttrDict(contact=Contact(self, contact["id"]), **contact) for contact in contacts]
|
||||
|
||||
async def get_contacts(
|
||||
self,
|
||||
query: Optional[str] = None,
|
||||
with_self: bool = False,
|
||||
verified_only: bool = False,
|
||||
snapshot: bool = False,
|
||||
) -> Union[List[Contact], List[AttrDict]]:
|
||||
"""Get a filtered list of contacts.
|
||||
|
||||
:param query: if a string is specified, only return contacts
|
||||
whose name or e-mail matches query.
|
||||
:param with_self: if True the self-contact is also included if it matches the query.
|
||||
:param only_verified: if True only return verified contacts.
|
||||
:param snapshot: If True return a list of contact snapshots instead of Contact instances.
|
||||
"""
|
||||
flags = 0
|
||||
if verified_only:
|
||||
flags |= ContactFlag.VERIFIED_ONLY
|
||||
if with_self:
|
||||
flags |= ContactFlag.ADD_SELF
|
||||
|
||||
if snapshot:
|
||||
contacts = await self._rpc.get_contacts(self.id, flags, query)
|
||||
return [AttrDict(contact=Contact(self, contact["id"]), **contact) for contact in contacts]
|
||||
contacts = await self._rpc.get_contact_ids(self.id, flags, query)
|
||||
return [Contact(self, contact_id) for contact_id in contacts]
|
||||
|
||||
@property
|
||||
def self_contact(self) -> Contact:
|
||||
"""This account's identity as a Contact."""
|
||||
return Contact(self, SpecialContactId.SELF)
|
||||
|
||||
async def get_chatlist(
|
||||
self,
|
||||
query: Optional[str] = None,
|
||||
contact: Optional[Contact] = None,
|
||||
archived_only: bool = False,
|
||||
for_forwarding: bool = False,
|
||||
no_specials: bool = False,
|
||||
alldone_hint: bool = False,
|
||||
snapshot: bool = False,
|
||||
) -> Union[List[Chat], List[AttrDict]]:
|
||||
"""Return list of chats.
|
||||
|
||||
:param query: if a string is specified only chats matching this query are returned.
|
||||
:param contact: if a contact is specified only chats including this contact are returned.
|
||||
:param archived_only: if True only archived chats are returned.
|
||||
:param for_forwarding: if True the chat list is sorted with "Saved messages" at the top
|
||||
and without "Device chat" and contact requests.
|
||||
:param no_specials: if True archive link is not added to the list.
|
||||
:param alldone_hint: if True the "all done hint" special chat will be added to the list
|
||||
as needed.
|
||||
:param snapshot: If True return a list of chat snapshots instead of Chat instances.
|
||||
"""
|
||||
flags = 0
|
||||
if archived_only:
|
||||
flags |= ChatlistFlag.ARCHIVED_ONLY
|
||||
if for_forwarding:
|
||||
flags |= ChatlistFlag.FOR_FORWARDING
|
||||
if no_specials:
|
||||
flags |= ChatlistFlag.NO_SPECIALS
|
||||
if alldone_hint:
|
||||
flags |= ChatlistFlag.ADD_ALLDONE_HINT
|
||||
|
||||
entries = await self._rpc.get_chatlist_entries(self.id, flags, query, contact and contact.id)
|
||||
if not snapshot:
|
||||
return [Chat(self, entry[0]) for entry in entries]
|
||||
|
||||
items = await self._rpc.get_chatlist_items_by_entries(self.id, entries)
|
||||
chats = []
|
||||
for item in items.values():
|
||||
item["chat"] = Chat(self, item["id"])
|
||||
chats.append(AttrDict(item))
|
||||
return chats
|
||||
|
||||
async def create_group(self, name: str, protect: bool = False) -> Chat:
|
||||
"""Create a new group chat.
|
||||
|
||||
After creation, the group has only self-contact as member and is in unpromoted state.
|
||||
"""
|
||||
return Chat(self, await self._rpc.create_group_chat(self.id, name, protect))
|
||||
|
||||
def get_chat_by_id(self, chat_id: int) -> Chat:
|
||||
"""Return the Chat instance with the given ID."""
|
||||
return Chat(self, chat_id)
|
||||
|
||||
async def secure_join(self, qrdata: str) -> Chat:
|
||||
"""Continue a Setup-Contact or Verified-Group-Invite protocol started on
|
||||
another device.
|
||||
|
||||
The function returns immediately and the handshake runs in background, sending
|
||||
and receiving several messages.
|
||||
Subsequent calls of `secure_join()` will abort previous, unfinished handshakes.
|
||||
See https://countermitm.readthedocs.io/en/latest/new.html for protocol details.
|
||||
|
||||
:param qrdata: The text of the scanned QR code.
|
||||
"""
|
||||
return Chat(self, await self._rpc.secure_join(self.id, qrdata))
|
||||
|
||||
async def get_qr_code(self) -> Tuple[str, str]:
|
||||
"""Get Setup-Contact QR Code text and SVG data.
|
||||
|
||||
this data needs to be transferred to another Delta Chat account
|
||||
in a second channel, typically used by mobiles with QRcode-show + scan UX.
|
||||
"""
|
||||
return await self._rpc.get_chat_securejoin_qr_code_svg(self.id, None)
|
||||
|
||||
def get_message_by_id(self, msg_id: int) -> Message:
|
||||
"""Return the Message instance with the given ID."""
|
||||
return Message(self, msg_id)
|
||||
|
||||
async def mark_seen_messages(self, messages: List[Message]) -> None:
|
||||
"""Mark the given set of messages as seen."""
|
||||
await self._rpc.markseen_msgs(self.id, [msg.id for msg in messages])
|
||||
|
||||
async def delete_messages(self, messages: List[Message]) -> None:
|
||||
"""Delete messages (local and remote)."""
|
||||
await self._rpc.delete_messages(self.id, [msg.id for msg in messages])
|
||||
|
||||
async def get_fresh_messages(self) -> List[Message]:
|
||||
"""Return the list of fresh messages, newest messages first.
|
||||
|
||||
This call is intended for displaying notifications.
|
||||
If you are writing a bot, use `get_fresh_messages_in_arrival_order()` instead,
|
||||
to process oldest messages first.
|
||||
"""
|
||||
fresh_msg_ids = await self._rpc.get_fresh_msgs(self.id)
|
||||
return [Message(self, msg_id) for msg_id in fresh_msg_ids]
|
||||
|
||||
async def get_fresh_messages_in_arrival_order(self) -> List[Message]:
|
||||
"""Return fresh messages list sorted in the order of their arrival, with ascending IDs."""
|
||||
fresh_msg_ids = sorted(await self._rpc.get_fresh_msgs(self.id))
|
||||
return [Message(self, msg_id) for msg_id in fresh_msg_ids]
|
||||
254
deltachat-rpc-client/src/deltachat_rpc_client/chat.py
Normal file
254
deltachat-rpc-client/src/deltachat_rpc_client/chat.py
Normal file
@@ -0,0 +1,254 @@
|
||||
import calendar
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from ._utils import AttrDict
|
||||
from .const import ChatVisibility, ViewType
|
||||
from .contact import Contact
|
||||
from .message import Message
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from datetime import datetime
|
||||
|
||||
from .account import Account
|
||||
from .rpc import Rpc
|
||||
|
||||
|
||||
@dataclass
|
||||
class Chat:
|
||||
"""Chat object which manages members and through which you can send and retrieve messages."""
|
||||
|
||||
account: "Account"
|
||||
id: int
|
||||
|
||||
@property
|
||||
def _rpc(self) -> "Rpc":
|
||||
return self.account._rpc
|
||||
|
||||
async def delete(self) -> None:
|
||||
"""Delete this chat and all its messages.
|
||||
|
||||
Note:
|
||||
|
||||
- does not delete messages on server
|
||||
- the chat or contact is not blocked, new message will arrive
|
||||
"""
|
||||
await self._rpc.delete_chat(self.account.id, self.id)
|
||||
|
||||
async def block(self) -> None:
|
||||
"""Block this chat."""
|
||||
await self._rpc.block_chat(self.account.id, self.id)
|
||||
|
||||
async def accept(self) -> None:
|
||||
"""Accept this contact request chat."""
|
||||
await self._rpc.accept_chat(self.account.id, self.id)
|
||||
|
||||
async def leave(self) -> None:
|
||||
"""Leave this chat."""
|
||||
await self._rpc.leave_group(self.account.id, self.id)
|
||||
|
||||
async def mute(self, duration: Optional[int] = None) -> None:
|
||||
"""Mute this chat, if a duration is not provided the chat is muted forever.
|
||||
|
||||
:param duration: mute duration from now in seconds. Must be greater than zero.
|
||||
"""
|
||||
if duration is not None:
|
||||
assert duration > 0, "Invalid duration"
|
||||
dur: Union[str, dict] = {"Until": duration}
|
||||
else:
|
||||
dur = "Forever"
|
||||
await self._rpc.set_chat_mute_duration(self.account.id, self.id, dur)
|
||||
|
||||
async def unmute(self) -> None:
|
||||
"""Unmute this chat."""
|
||||
await self._rpc.set_chat_mute_duration(self.account.id, self.id, "NotMuted")
|
||||
|
||||
async def pin(self) -> None:
|
||||
"""Pin this chat."""
|
||||
await self._rpc.set_chat_visibility(self.account.id, self.id, ChatVisibility.PINNED)
|
||||
|
||||
async def unpin(self) -> None:
|
||||
"""Unpin this chat."""
|
||||
await self._rpc.set_chat_visibility(self.account.id, self.id, ChatVisibility.NORMAL)
|
||||
|
||||
async def archive(self) -> None:
|
||||
"""Archive this chat."""
|
||||
await self._rpc.set_chat_visibility(self.account.id, self.id, ChatVisibility.ARCHIVED)
|
||||
|
||||
async def unarchive(self) -> None:
|
||||
"""Unarchive this chat."""
|
||||
await self._rpc.set_chat_visibility(self.account.id, self.id, ChatVisibility.NORMAL)
|
||||
|
||||
async def set_name(self, name: str) -> None:
|
||||
"""Set name of this chat."""
|
||||
await self._rpc.set_chat_name(self.account.id, self.id, name)
|
||||
|
||||
async def set_ephemeral_timer(self, timer: int) -> None:
|
||||
"""Set ephemeral timer of this chat."""
|
||||
await self._rpc.set_chat_ephemeral_timer(self.account.id, self.id, timer)
|
||||
|
||||
async def get_encryption_info(self) -> str:
|
||||
"""Return encryption info for this chat."""
|
||||
return await self._rpc.get_chat_encryption_info(self.account.id, self.id)
|
||||
|
||||
async def get_qr_code(self) -> Tuple[str, str]:
|
||||
"""Get Join-Group QR code text and SVG data."""
|
||||
return await self._rpc.get_chat_securejoin_qr_code_svg(self.account.id, self.id)
|
||||
|
||||
async def get_basic_snapshot(self) -> AttrDict:
|
||||
"""Get a chat snapshot with basic info about this chat."""
|
||||
info = await self._rpc.get_basic_chat_info(self.account.id, self.id)
|
||||
return AttrDict(chat=self, **info)
|
||||
|
||||
async def get_full_snapshot(self) -> AttrDict:
|
||||
"""Get a full snapshot of this chat."""
|
||||
info = await self._rpc.get_full_chat_by_id(self.account.id, self.id)
|
||||
return AttrDict(chat=self, **info)
|
||||
|
||||
async def send_message(
|
||||
self,
|
||||
text: Optional[str] = None,
|
||||
html: Optional[str] = None,
|
||||
viewtype: Optional[ViewType] = None,
|
||||
file: Optional[str] = None,
|
||||
location: Optional[Tuple[float, float]] = None,
|
||||
override_sender_name: Optional[str] = None,
|
||||
quoted_msg: Optional[Union[int, Message]] = None,
|
||||
) -> Message:
|
||||
"""Send a message and return the resulting Message instance."""
|
||||
if isinstance(quoted_msg, Message):
|
||||
quoted_msg = quoted_msg.id
|
||||
|
||||
draft = {
|
||||
"text": text,
|
||||
"html": html,
|
||||
"viewtype": viewtype,
|
||||
"file": file,
|
||||
"location": location,
|
||||
"overrideSenderName": override_sender_name,
|
||||
"quotedMsg": quoted_msg,
|
||||
}
|
||||
msg_id = await self._rpc.send_msg(self.account.id, self.id, draft)
|
||||
return Message(self.account, msg_id)
|
||||
|
||||
async def send_text(self, text: str) -> Message:
|
||||
"""Send a text message and return the resulting Message instance."""
|
||||
msg_id = await self._rpc.misc_send_text_message(self.account.id, self.id, text)
|
||||
return Message(self.account, msg_id)
|
||||
|
||||
async def send_videochat_invitation(self) -> Message:
|
||||
"""Send a videochat invitation and return the resulting Message instance."""
|
||||
msg_id = await self._rpc.send_videochat_invitation(self.account.id, self.id)
|
||||
return Message(self.account, msg_id)
|
||||
|
||||
async def send_sticker(self, path: str) -> Message:
|
||||
"""Send an sticker and return the resulting Message instance."""
|
||||
msg_id = await self._rpc.send_sticker(self.account.id, self.id, path)
|
||||
return Message(self.account, msg_id)
|
||||
|
||||
async def forward_messages(self, messages: List[Message]) -> None:
|
||||
"""Forward a list of messages to this chat."""
|
||||
msg_ids = [msg.id for msg in messages]
|
||||
await self._rpc.forward_messages(self.account.id, msg_ids, self.id)
|
||||
|
||||
async def set_draft(
|
||||
self,
|
||||
text: Optional[str] = None,
|
||||
file: Optional[str] = None,
|
||||
quoted_msg: Optional[int] = None,
|
||||
) -> None:
|
||||
"""Set draft message."""
|
||||
if isinstance(quoted_msg, Message):
|
||||
quoted_msg = quoted_msg.id
|
||||
await self._rpc.misc_set_draft(self.account.id, self.id, text, file, quoted_msg)
|
||||
|
||||
async def remove_draft(self) -> None:
|
||||
"""Remove draft message."""
|
||||
await self._rpc.remove_draft(self.account.id, self.id)
|
||||
|
||||
async def get_draft(self) -> Optional[AttrDict]:
|
||||
"""Get draft message."""
|
||||
snapshot = await self._rpc.get_draft(self.account.id, self.id)
|
||||
if not snapshot:
|
||||
return None
|
||||
snapshot = AttrDict(snapshot)
|
||||
snapshot["chat"] = Chat(self.account, snapshot.chat_id)
|
||||
snapshot["sender"] = Contact(self.account, snapshot.from_id)
|
||||
snapshot["message"] = Message(self.account, snapshot.id)
|
||||
return snapshot
|
||||
|
||||
async def get_messages(self, info_only: bool = False, add_daymarker: bool = False) -> List[Message]:
|
||||
"""get the list of messages in this chat."""
|
||||
msgs = await self._rpc.get_message_ids(self.account.id, self.id, info_only, add_daymarker)
|
||||
return [Message(self.account, msg_id) for msg_id in msgs]
|
||||
|
||||
async def get_fresh_message_count(self) -> int:
|
||||
"""Get number of fresh messages in this chat"""
|
||||
return await self._rpc.get_fresh_msg_cnt(self.account.id, self.id)
|
||||
|
||||
async def mark_noticed(self) -> None:
|
||||
"""Mark all messages in this chat as noticed."""
|
||||
await self._rpc.marknoticed_chat(self.account.id, self.id)
|
||||
|
||||
async def add_contact(self, *contact: Union[int, str, Contact]) -> None:
|
||||
"""Add contacts to this group."""
|
||||
for cnt in contact:
|
||||
if isinstance(cnt, str):
|
||||
contact_id = (await self.account.create_contact(cnt)).id
|
||||
elif not isinstance(cnt, int):
|
||||
contact_id = cnt.id
|
||||
else:
|
||||
contact_id = cnt
|
||||
await self._rpc.add_contact_to_chat(self.account.id, self.id, contact_id)
|
||||
|
||||
async def remove_contact(self, *contact: Union[int, str, Contact]) -> None:
|
||||
"""Remove members from this group."""
|
||||
for cnt in contact:
|
||||
if isinstance(cnt, str):
|
||||
contact_id = (await self.account.create_contact(cnt)).id
|
||||
elif not isinstance(cnt, int):
|
||||
contact_id = cnt.id
|
||||
else:
|
||||
contact_id = cnt
|
||||
await self._rpc.remove_contact_from_chat(self.account.id, self.id, contact_id)
|
||||
|
||||
async def get_contacts(self) -> List[Contact]:
|
||||
"""Get the contacts belonging to this chat.
|
||||
|
||||
For single/direct chats self-address is not included.
|
||||
"""
|
||||
contacts = await self._rpc.get_chat_contacts(self.account.id, self.id)
|
||||
return [Contact(self.account, contact_id) for contact_id in contacts]
|
||||
|
||||
async def set_image(self, path: str) -> None:
|
||||
"""Set profile image of this chat.
|
||||
|
||||
:param path: Full path of the image to use as the group image.
|
||||
"""
|
||||
await self._rpc.set_chat_profile_image(self.account.id, self.id, path)
|
||||
|
||||
async def remove_image(self) -> None:
|
||||
"""Remove profile image of this chat."""
|
||||
await self._rpc.set_chat_profile_image(self.account.id, self.id, None)
|
||||
|
||||
async def get_locations(
|
||||
self,
|
||||
contact: Optional[Contact] = None,
|
||||
timestamp_from: Optional["datetime"] = None,
|
||||
timestamp_to: Optional["datetime"] = None,
|
||||
) -> List[AttrDict]:
|
||||
"""Get list of location snapshots for the given contact in the given timespan."""
|
||||
time_from = calendar.timegm(timestamp_from.utctimetuple()) if timestamp_from else 0
|
||||
time_to = calendar.timegm(timestamp_to.utctimetuple()) if timestamp_to else 0
|
||||
contact_id = contact.id if contact else 0
|
||||
|
||||
result = await self._rpc.get_locations(self.account.id, self.id, contact_id, time_from, time_to)
|
||||
locations = []
|
||||
contacts: Dict[int, Contact] = {}
|
||||
for loc in result:
|
||||
location = AttrDict(loc)
|
||||
location["chat"] = self
|
||||
location["contact"] = contacts.setdefault(location.contact_id, Contact(self.account, location.contact_id))
|
||||
location["message"] = Message(self.account, location.msg_id)
|
||||
locations.append(location)
|
||||
return locations
|
||||
205
deltachat-rpc-client/src/deltachat_rpc_client/client.py
Normal file
205
deltachat-rpc-client/src/deltachat_rpc_client/client.py
Normal file
@@ -0,0 +1,205 @@
|
||||
"""Event loop implementations offering high level event handling/hooking."""
|
||||
import inspect
|
||||
import logging
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
Coroutine,
|
||||
Dict,
|
||||
Iterable,
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
Type,
|
||||
Union,
|
||||
)
|
||||
|
||||
from ._utils import (
|
||||
AttrDict,
|
||||
parse_system_add_remove,
|
||||
parse_system_image_changed,
|
||||
parse_system_title_changed,
|
||||
)
|
||||
from .const import COMMAND_PREFIX, EventType, SystemMessageType
|
||||
from .events import (
|
||||
EventFilter,
|
||||
GroupImageChanged,
|
||||
GroupNameChanged,
|
||||
MemberListChanged,
|
||||
NewMessage,
|
||||
RawEvent,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from deltachat_rpc_client.account import Account
|
||||
|
||||
|
||||
class Client:
|
||||
"""Simple Delta Chat client that listen to events of a single account."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
account: "Account",
|
||||
hooks: Optional[Iterable[Tuple[Callable, Union[type, EventFilter]]]] = None,
|
||||
logger: Optional[logging.Logger] = None,
|
||||
) -> None:
|
||||
self.account = account
|
||||
self.logger = logger or logging
|
||||
self._hooks: Dict[type, Set[tuple]] = {}
|
||||
self._should_process_messages = 0
|
||||
self.add_hooks(hooks or [])
|
||||
|
||||
def add_hooks(self, hooks: Iterable[Tuple[Callable, Union[type, EventFilter]]]) -> None:
|
||||
for hook, event in hooks:
|
||||
self.add_hook(hook, event)
|
||||
|
||||
def add_hook(self, hook: Callable, event: Union[type, EventFilter] = RawEvent) -> None:
|
||||
"""Register hook for the given event filter."""
|
||||
if isinstance(event, type):
|
||||
event = event()
|
||||
assert isinstance(event, EventFilter)
|
||||
self._should_process_messages += int(
|
||||
isinstance(
|
||||
event,
|
||||
(NewMessage, MemberListChanged, GroupImageChanged, GroupNameChanged),
|
||||
),
|
||||
)
|
||||
self._hooks.setdefault(type(event), set()).add((hook, event))
|
||||
|
||||
def remove_hook(self, hook: Callable, event: Union[type, EventFilter]) -> None:
|
||||
"""Unregister hook from the given event filter."""
|
||||
if isinstance(event, type):
|
||||
event = event()
|
||||
self._should_process_messages -= int(
|
||||
isinstance(
|
||||
event,
|
||||
(NewMessage, MemberListChanged, GroupImageChanged, GroupNameChanged),
|
||||
),
|
||||
)
|
||||
self._hooks.get(type(event), set()).remove((hook, event))
|
||||
|
||||
async def is_configured(self) -> bool:
|
||||
return await self.account.is_configured()
|
||||
|
||||
async def configure(self, email: str, password: str, **kwargs) -> None:
|
||||
await self.account.set_config("addr", email)
|
||||
await self.account.set_config("mail_pw", password)
|
||||
for key, value in kwargs.items():
|
||||
await self.account.set_config(key, value)
|
||||
await self.account.configure()
|
||||
self.logger.debug("Account configured")
|
||||
|
||||
async def run_forever(self) -> None:
|
||||
"""Process events forever."""
|
||||
await self.run_until(lambda _: False)
|
||||
|
||||
async def run_until(self, func: Callable[[AttrDict], Union[bool, Coroutine]]) -> AttrDict:
|
||||
"""Process events until the given callable evaluates to True.
|
||||
|
||||
The callable should accept an AttrDict object representing the
|
||||
last processed event. The event is returned when the callable
|
||||
evaluates to True.
|
||||
"""
|
||||
self.logger.debug("Listening to incoming events...")
|
||||
if await self.is_configured():
|
||||
await self.account.start_io()
|
||||
await self._process_messages() # Process old messages.
|
||||
while True:
|
||||
event = await self.account.wait_for_event()
|
||||
event["type"] = EventType(event.type)
|
||||
event["account"] = self.account
|
||||
await self._on_event(event)
|
||||
if event.type == EventType.INCOMING_MSG:
|
||||
await self._process_messages()
|
||||
|
||||
stop = func(event)
|
||||
if inspect.isawaitable(stop):
|
||||
stop = await stop
|
||||
if stop:
|
||||
return event
|
||||
|
||||
async def _on_event(self, event: AttrDict, filter_type: Type[EventFilter] = RawEvent) -> None:
|
||||
for hook, evfilter in self._hooks.get(filter_type, []):
|
||||
if await evfilter.filter(event):
|
||||
try:
|
||||
await hook(event)
|
||||
except Exception as ex:
|
||||
self.logger.exception(ex)
|
||||
|
||||
async def _parse_command(self, event: AttrDict) -> None:
|
||||
cmds = [hook[1].command for hook in self._hooks.get(NewMessage, []) if hook[1].command]
|
||||
parts = event.message_snapshot.text.split(maxsplit=1)
|
||||
payload = parts[1] if len(parts) > 1 else ""
|
||||
cmd = parts.pop(0)
|
||||
|
||||
if "@" in cmd:
|
||||
suffix = "@" + (await self.account.self_contact.get_snapshot()).address
|
||||
if cmd.endswith(suffix):
|
||||
cmd = cmd[: -len(suffix)]
|
||||
else:
|
||||
return
|
||||
|
||||
parts = cmd.split("_")
|
||||
_payload = payload
|
||||
while parts:
|
||||
_cmd = "_".join(parts)
|
||||
if _cmd in cmds:
|
||||
break
|
||||
_payload = (parts.pop() + " " + _payload).rstrip()
|
||||
|
||||
if parts:
|
||||
cmd = _cmd
|
||||
payload = _payload
|
||||
|
||||
event["command"], event["payload"] = cmd, payload
|
||||
|
||||
async def _on_new_msg(self, snapshot: AttrDict) -> None:
|
||||
event = AttrDict(command="", payload="", message_snapshot=snapshot)
|
||||
if not snapshot.is_info and snapshot.text.startswith(COMMAND_PREFIX):
|
||||
await self._parse_command(event)
|
||||
await self._on_event(event, NewMessage)
|
||||
|
||||
async def _handle_info_msg(self, snapshot: AttrDict) -> None:
|
||||
event = AttrDict(message_snapshot=snapshot)
|
||||
|
||||
img_changed = parse_system_image_changed(snapshot.text)
|
||||
if img_changed:
|
||||
_, event["image_deleted"] = img_changed
|
||||
await self._on_event(event, GroupImageChanged)
|
||||
return
|
||||
|
||||
title_changed = parse_system_title_changed(snapshot.text)
|
||||
if title_changed:
|
||||
_, event["old_name"] = title_changed
|
||||
await self._on_event(event, GroupNameChanged)
|
||||
return
|
||||
|
||||
members_changed = parse_system_add_remove(snapshot.text)
|
||||
if members_changed:
|
||||
action, event["member"], _ = members_changed
|
||||
event["member_added"] = action == "added"
|
||||
await self._on_event(event, MemberListChanged)
|
||||
return
|
||||
|
||||
self.logger.warning(
|
||||
"ignoring unsupported system message id=%s text=%s",
|
||||
snapshot.id,
|
||||
snapshot.text,
|
||||
)
|
||||
|
||||
async def _process_messages(self) -> None:
|
||||
if self._should_process_messages:
|
||||
for message in await self.account.get_fresh_messages_in_arrival_order():
|
||||
snapshot = await message.get_snapshot()
|
||||
await self._on_new_msg(snapshot)
|
||||
if snapshot.is_info and snapshot.system_message_type != SystemMessageType.WEBXDC_INFO_MESSAGE:
|
||||
await self._handle_info_msg(snapshot)
|
||||
await snapshot.message.mark_seen()
|
||||
|
||||
|
||||
class Bot(Client):
|
||||
"""Simple bot implementation that listent to events of a single account."""
|
||||
|
||||
async def configure(self, email: str, password: str, **kwargs) -> None:
|
||||
kwargs.setdefault("bot", "1")
|
||||
await super().configure(email, password, **kwargs)
|
||||
123
deltachat-rpc-client/src/deltachat_rpc_client/const.py
Normal file
123
deltachat-rpc-client/src/deltachat_rpc_client/const.py
Normal file
@@ -0,0 +1,123 @@
|
||||
from enum import Enum, IntEnum
|
||||
|
||||
COMMAND_PREFIX = "/"
|
||||
|
||||
|
||||
class ContactFlag(IntEnum):
|
||||
VERIFIED_ONLY = 0x01
|
||||
ADD_SELF = 0x02
|
||||
|
||||
|
||||
class ChatlistFlag(IntEnum):
|
||||
ARCHIVED_ONLY = 0x01
|
||||
NO_SPECIALS = 0x02
|
||||
ADD_ALLDONE_HINT = 0x04
|
||||
FOR_FORWARDING = 0x08
|
||||
|
||||
|
||||
class SpecialContactId(IntEnum):
|
||||
SELF = 1
|
||||
INFO = 2 # centered messages as "member added", used in all chats
|
||||
DEVICE = 5 # messages "update info" in the device-chat
|
||||
LAST_SPECIAL = 9
|
||||
|
||||
|
||||
class EventType(str, Enum):
|
||||
"""Core event types"""
|
||||
|
||||
INFO = "Info"
|
||||
SMTP_CONNECTED = "SmtpConnected"
|
||||
IMAP_CONNECTED = "ImapConnected"
|
||||
SMTP_MESSAGE_SENT = "SmtpMessageSent"
|
||||
IMAP_MESSAGE_DELETED = "ImapMessageDeleted"
|
||||
IMAP_MESSAGE_MOVED = "ImapMessageMoved"
|
||||
IMAP_INBOX_IDLE = "ImapInboxIdle"
|
||||
NEW_BLOB_FILE = "NewBlobFile"
|
||||
DELETED_BLOB_FILE = "DeletedBlobFile"
|
||||
WARNING = "Warning"
|
||||
ERROR = "Error"
|
||||
ERROR_SELF_NOT_IN_GROUP = "ErrorSelfNotInGroup"
|
||||
MSGS_CHANGED = "MsgsChanged"
|
||||
REACTIONS_CHANGED = "ReactionsChanged"
|
||||
INCOMING_MSG = "IncomingMsg"
|
||||
INCOMING_MSG_BUNCH = "IncomingMsgBunch"
|
||||
MSGS_NOTICED = "MsgsNoticed"
|
||||
MSG_DELIVERED = "MsgDelivered"
|
||||
MSG_FAILED = "MsgFailed"
|
||||
MSG_READ = "MsgRead"
|
||||
CHAT_MODIFIED = "ChatModified"
|
||||
CHAT_EPHEMERAL_TIMER_MODIFIED = "ChatEphemeralTimerModified"
|
||||
CONTACTS_CHANGED = "ContactsChanged"
|
||||
LOCATION_CHANGED = "LocationChanged"
|
||||
CONFIGURE_PROGRESS = "ConfigureProgress"
|
||||
IMEX_PROGRESS = "ImexProgress"
|
||||
IMEX_FILE_WRITTEN = "ImexFileWritten"
|
||||
SECUREJOIN_INVITER_PROGRESS = "SecurejoinInviterProgress"
|
||||
SECUREJOIN_JOINER_PROGRESS = "SecurejoinJoinerProgress"
|
||||
CONNECTIVITY_CHANGED = "ConnectivityChanged"
|
||||
SELFAVATAR_CHANGED = "SelfavatarChanged"
|
||||
WEBXDC_STATUS_UPDATE = "WebxdcStatusUpdate"
|
||||
WEBXDC_INSTANCE_DELETED = "WebxdcInstanceDeleted"
|
||||
|
||||
|
||||
class ChatType(IntEnum):
|
||||
"""Chat types"""
|
||||
|
||||
UNDEFINED = 0
|
||||
SINGLE = 100
|
||||
GROUP = 120
|
||||
MAILINGLIST = 140
|
||||
BROADCAST = 160
|
||||
|
||||
|
||||
class ChatVisibility(str, Enum):
|
||||
"""Chat visibility types"""
|
||||
|
||||
NORMAL = "Normal"
|
||||
ARCHIVED = "Archived"
|
||||
PINNED = "Pinned"
|
||||
|
||||
|
||||
class DownloadState(str, Enum):
|
||||
"""Message download state"""
|
||||
|
||||
DONE = "Done"
|
||||
AVAILABLE = "Available"
|
||||
FAILURE = "Failure"
|
||||
IN_PROGRESS = "InProgress"
|
||||
|
||||
|
||||
class ViewType(str, Enum):
|
||||
"""Message view type."""
|
||||
|
||||
UNKNOWN = "Unknown"
|
||||
TEXT = "Text"
|
||||
IMAGE = "Image"
|
||||
GIF = "Gif"
|
||||
STICKER = "Sticker"
|
||||
AUDIO = "Audio"
|
||||
VOICE = "Voice"
|
||||
VIDEO = "Video"
|
||||
FILE = "File"
|
||||
VIDEOCHAT_INVITATION = "VideochatInvitation"
|
||||
WEBXDC = "Webxdc"
|
||||
|
||||
|
||||
class SystemMessageType(str, Enum):
|
||||
"""System message type."""
|
||||
|
||||
UNKNOWN = "Unknown"
|
||||
GROUP_NAME_CHANGED = "GroupNameChanged"
|
||||
GROUP_IMAGE_CHANGED = "GroupImageChanged"
|
||||
MEMBER_ADDED_TO_GROUP = "MemberAddedToGroup"
|
||||
MEMBER_REMOVED_FROM_GROUP = "MemberRemovedFromGroup"
|
||||
AUTOCRYPT_SETUP_MESSAGE = "AutocryptSetupMessage"
|
||||
SECUREJOIN_MESSAGE = "SecurejoinMessage"
|
||||
LOCATION_STREAMING_ENABLED = "LocationStreamingEnabled"
|
||||
LOCATION_ONLY = "LocationOnly"
|
||||
CHAT_PROTECTION_ENABLED = "ChatProtectionEnabled"
|
||||
CHAT_PROTECTION_DISABLED = "ChatProtectionDisabled"
|
||||
WEBXDC_STATUS_UPDATE = "WebxdcStatusUpdate"
|
||||
EPHEMERAL_TIMER_CHANGED = "EphemeralTimerChanged"
|
||||
MULTI_DEVICE_SYNC = "MultiDeviceSync"
|
||||
WEBXDC_INFO_MESSAGE = "WebxdcInfoMessage"
|
||||
62
deltachat-rpc-client/src/deltachat_rpc_client/contact.py
Normal file
62
deltachat-rpc-client/src/deltachat_rpc_client/contact.py
Normal file
@@ -0,0 +1,62 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ._utils import AttrDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .account import Account
|
||||
from .chat import Chat
|
||||
from .rpc import Rpc
|
||||
|
||||
|
||||
@dataclass
|
||||
class Contact:
|
||||
"""
|
||||
Contact API.
|
||||
|
||||
Essentially a wrapper for RPC, account ID and a contact ID.
|
||||
"""
|
||||
|
||||
account: "Account"
|
||||
id: int
|
||||
|
||||
@property
|
||||
def _rpc(self) -> "Rpc":
|
||||
return self.account._rpc
|
||||
|
||||
async def block(self) -> None:
|
||||
"""Block contact."""
|
||||
await self._rpc.block_contact(self.account.id, self.id)
|
||||
|
||||
async def unblock(self) -> None:
|
||||
"""Unblock contact."""
|
||||
await self._rpc.unblock_contact(self.account.id, self.id)
|
||||
|
||||
async def delete(self) -> None:
|
||||
"""Delete contact."""
|
||||
await self._rpc.delete_contact(self.account.id, self.id)
|
||||
|
||||
async def set_name(self, name: str) -> None:
|
||||
"""Change the name of this contact."""
|
||||
await self._rpc.change_contact_name(self.account.id, self.id, name)
|
||||
|
||||
async def get_encryption_info(self) -> str:
|
||||
"""Get a multi-line encryption info, containing your fingerprint and
|
||||
the fingerprint of the contact.
|
||||
"""
|
||||
return await self._rpc.get_contact_encryption_info(self.account.id, self.id)
|
||||
|
||||
async def get_snapshot(self) -> AttrDict:
|
||||
"""Return a dictionary with a snapshot of all contact properties."""
|
||||
snapshot = AttrDict(await self._rpc.get_contact(self.account.id, self.id))
|
||||
snapshot["contact"] = self
|
||||
return snapshot
|
||||
|
||||
async def create_chat(self) -> "Chat":
|
||||
"""Create or get an existing 1:1 chat for this contact."""
|
||||
from .chat import Chat
|
||||
|
||||
return Chat(
|
||||
self.account,
|
||||
await self._rpc.create_chat_by_contact_id(self.account.id, self.id),
|
||||
)
|
||||
49
deltachat-rpc-client/src/deltachat_rpc_client/deltachat.py
Normal file
49
deltachat-rpc-client/src/deltachat_rpc_client/deltachat.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from typing import TYPE_CHECKING, Dict, List
|
||||
|
||||
from ._utils import AttrDict
|
||||
from .account import Account
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .rpc import Rpc
|
||||
|
||||
|
||||
class DeltaChat:
|
||||
"""
|
||||
Delta Chat accounts manager.
|
||||
This is the root of the object oriented API.
|
||||
"""
|
||||
|
||||
def __init__(self, rpc: "Rpc") -> None:
|
||||
self.rpc = rpc
|
||||
|
||||
async def add_account(self) -> Account:
|
||||
"""Create a new account database."""
|
||||
account_id = await self.rpc.add_account()
|
||||
return Account(self, account_id)
|
||||
|
||||
async def get_all_accounts(self) -> List[Account]:
|
||||
"""Return a list of all available accounts."""
|
||||
account_ids = await self.rpc.get_all_account_ids()
|
||||
return [Account(self, account_id) for account_id in account_ids]
|
||||
|
||||
async def start_io(self) -> None:
|
||||
"""Start the I/O of all accounts."""
|
||||
await self.rpc.start_io_for_all_accounts()
|
||||
|
||||
async def stop_io(self) -> None:
|
||||
"""Stop the I/O of all accounts."""
|
||||
await self.rpc.stop_io_for_all_accounts()
|
||||
|
||||
async def maybe_network(self) -> None:
|
||||
"""Indicate that the network likely has come back or just that the network
|
||||
conditions might have changed.
|
||||
"""
|
||||
await self.rpc.maybe_network()
|
||||
|
||||
async def get_system_info(self) -> AttrDict:
|
||||
"""Get information about the Delta Chat core in this system."""
|
||||
return AttrDict(await self.rpc.get_system_info())
|
||||
|
||||
async def set_translations(self, translations: Dict[str, str]) -> None:
|
||||
"""Set stock translation strings."""
|
||||
await self.rpc.set_stock_strings(translations)
|
||||
286
deltachat-rpc-client/src/deltachat_rpc_client/events.py
Normal file
286
deltachat-rpc-client/src/deltachat_rpc_client/events.py
Normal file
@@ -0,0 +1,286 @@
|
||||
"""High-level classes for event processing and filtering."""
|
||||
import inspect
|
||||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Callable, Iterable, Iterator, Optional, Set, Tuple, Union
|
||||
|
||||
from .const import EventType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ._utils import AttrDict
|
||||
|
||||
|
||||
def _tuple_of(obj, type_: type) -> tuple:
|
||||
if not obj:
|
||||
return ()
|
||||
if isinstance(obj, type_):
|
||||
obj = (obj,)
|
||||
|
||||
if not all(isinstance(elem, type_) for elem in obj):
|
||||
raise TypeError()
|
||||
return tuple(obj)
|
||||
|
||||
|
||||
class EventFilter(ABC):
|
||||
"""The base event filter.
|
||||
|
||||
:param func: A Callable (async or not) function that should accept the event as input
|
||||
parameter, and return a bool value indicating whether the event
|
||||
should be dispatched or not.
|
||||
"""
|
||||
|
||||
def __init__(self, func: Optional[Callable] = None):
|
||||
self.func = func
|
||||
|
||||
@abstractmethod
|
||||
def __hash__(self) -> int:
|
||||
"""Object's unique hash"""
|
||||
|
||||
@abstractmethod
|
||||
def __eq__(self, other) -> bool:
|
||||
"""Return True if two event filters are equal."""
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
async def _call_func(self, event) -> bool:
|
||||
if not self.func:
|
||||
return True
|
||||
res = self.func(event)
|
||||
if inspect.isawaitable(res):
|
||||
return await res
|
||||
return res
|
||||
|
||||
@abstractmethod
|
||||
async def filter(self, event):
|
||||
"""Return True-like value if the event passed the filter and should be
|
||||
used, or False-like value otherwise.
|
||||
"""
|
||||
|
||||
|
||||
class RawEvent(EventFilter):
|
||||
"""Matches raw core events.
|
||||
|
||||
:param types: The types of event to match.
|
||||
:param func: A Callable (async or not) function that should accept the event as input
|
||||
parameter, and return a bool value indicating whether the event
|
||||
should be dispatched or not.
|
||||
"""
|
||||
|
||||
def __init__(self, types: Union[None, EventType, Iterable[EventType]] = None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
try:
|
||||
self.types = _tuple_of(types, EventType)
|
||||
except TypeError as err:
|
||||
raise TypeError(f"Invalid event type given: {types}") from err
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.types, self.func))
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
if isinstance(other, RawEvent):
|
||||
return (self.types, self.func) == (other.types, other.func)
|
||||
return False
|
||||
|
||||
async def filter(self, event: "AttrDict") -> bool:
|
||||
if self.types and event.type not in self.types:
|
||||
return False
|
||||
return await self._call_func(event)
|
||||
|
||||
|
||||
class NewMessage(EventFilter):
|
||||
"""Matches whenever a new message arrives.
|
||||
|
||||
Warning: registering a handler for this event will cause the messages
|
||||
to be marked as read. Its usage is mainly intended for bots.
|
||||
|
||||
:param pattern: if set, this Pattern will be used to filter the message by its text
|
||||
content.
|
||||
:param command: If set, only match messages with the given command (ex. /help).
|
||||
Setting this property implies `is_info==False`.
|
||||
:param is_bot: If set to True only match messages sent by bots, if set to None
|
||||
match messages from bots and users. If omitted or set to False
|
||||
only messages from users will be matched.
|
||||
:param is_info: If set to True only match info/system messages, if set to False
|
||||
only match messages that are not info/system messages. If omitted
|
||||
info/system messages as well as normal messages will be matched.
|
||||
:param func: A Callable (async or not) function that should accept the event as input
|
||||
parameter, and return a bool value indicating whether the event
|
||||
should be dispatched or not.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
pattern: Union[
|
||||
None,
|
||||
str,
|
||||
Callable[[str], bool],
|
||||
re.Pattern,
|
||||
] = None,
|
||||
command: Optional[str] = None,
|
||||
is_bot: Optional[bool] = False,
|
||||
is_info: Optional[bool] = None,
|
||||
func: Optional[Callable[["AttrDict"], bool]] = None,
|
||||
) -> None:
|
||||
super().__init__(func=func)
|
||||
self.is_bot = is_bot
|
||||
self.is_info = is_info
|
||||
if command is not None and not isinstance(command, str):
|
||||
raise TypeError("Invalid command")
|
||||
self.command = command
|
||||
if self.is_info and self.command:
|
||||
raise AttributeError("Can not use command and is_info at the same time.")
|
||||
if isinstance(pattern, str):
|
||||
pattern = re.compile(pattern)
|
||||
if isinstance(pattern, re.Pattern):
|
||||
self.pattern: Optional[Callable] = pattern.match
|
||||
elif not pattern or callable(pattern):
|
||||
self.pattern = pattern
|
||||
else:
|
||||
raise TypeError("Invalid pattern type")
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.pattern, self.command, self.is_bot, self.is_info, self.func))
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
if isinstance(other, NewMessage):
|
||||
return (
|
||||
self.pattern,
|
||||
self.command,
|
||||
self.is_bot,
|
||||
self.is_info,
|
||||
self.func,
|
||||
) == (
|
||||
other.pattern,
|
||||
other.command,
|
||||
other.is_bot,
|
||||
other.is_info,
|
||||
other.func,
|
||||
)
|
||||
return False
|
||||
|
||||
async def filter(self, event: "AttrDict") -> bool:
|
||||
if self.is_bot is not None and self.is_bot != event.message_snapshot.is_bot:
|
||||
return False
|
||||
if self.is_info is not None and self.is_info != event.message_snapshot.is_info:
|
||||
return False
|
||||
if self.command and self.command != event.command:
|
||||
return False
|
||||
if self.pattern:
|
||||
match = self.pattern(event.message_snapshot.text)
|
||||
if inspect.isawaitable(match):
|
||||
match = await match
|
||||
if not match:
|
||||
return False
|
||||
return await super()._call_func(event)
|
||||
|
||||
|
||||
class MemberListChanged(EventFilter):
|
||||
"""Matches when a group member is added or removed.
|
||||
|
||||
Warning: registering a handler for this event will cause the messages
|
||||
to be marked as read. Its usage is mainly intended for bots.
|
||||
|
||||
:param added: If set to True only match if a member was added, if set to False
|
||||
only match if a member was removed. If omitted both, member additions
|
||||
and removals, will be matched.
|
||||
:param func: A Callable (async or not) function that should accept the event as input
|
||||
parameter, and return a bool value indicating whether the event
|
||||
should be dispatched or not.
|
||||
"""
|
||||
|
||||
def __init__(self, added: Optional[bool] = None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.added = added
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.added, self.func))
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
if isinstance(other, MemberListChanged):
|
||||
return (self.added, self.func) == (other.added, other.func)
|
||||
return False
|
||||
|
||||
async def filter(self, event: "AttrDict") -> bool:
|
||||
if self.added is not None and self.added != event.member_added:
|
||||
return False
|
||||
return await self._call_func(event)
|
||||
|
||||
|
||||
class GroupImageChanged(EventFilter):
|
||||
"""Matches when the group image is changed.
|
||||
|
||||
Warning: registering a handler for this event will cause the messages
|
||||
to be marked as read. Its usage is mainly intended for bots.
|
||||
|
||||
:param deleted: If set to True only match if the image was deleted, if set to False
|
||||
only match if a new image was set. If omitted both, image changes and
|
||||
removals, will be matched.
|
||||
:param func: A Callable (async or not) function that should accept the event as input
|
||||
parameter, and return a bool value indicating whether the event
|
||||
should be dispatched or not.
|
||||
"""
|
||||
|
||||
def __init__(self, deleted: Optional[bool] = None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.deleted = deleted
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.deleted, self.func))
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
if isinstance(other, GroupImageChanged):
|
||||
return (self.deleted, self.func) == (other.deleted, other.func)
|
||||
return False
|
||||
|
||||
async def filter(self, event: "AttrDict") -> bool:
|
||||
if self.deleted is not None and self.deleted != event.image_deleted:
|
||||
return False
|
||||
return await self._call_func(event)
|
||||
|
||||
|
||||
class GroupNameChanged(EventFilter):
|
||||
"""Matches when the group name is changed.
|
||||
|
||||
Warning: registering a handler for this event will cause the messages
|
||||
to be marked as read. Its usage is mainly intended for bots.
|
||||
|
||||
:param func: A Callable (async or not) function that should accept the event as input
|
||||
parameter, and return a bool value indicating whether the event
|
||||
should be dispatched or not.
|
||||
"""
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((GroupNameChanged, self.func))
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
if isinstance(other, GroupNameChanged):
|
||||
return self.func == other.func
|
||||
return False
|
||||
|
||||
async def filter(self, event: "AttrDict") -> bool:
|
||||
return await self._call_func(event)
|
||||
|
||||
|
||||
class HookCollection:
|
||||
"""
|
||||
Helper class to collect event hooks that can later be added to a Delta Chat client.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._hooks: Set[Tuple[Callable, Union[type, EventFilter]]] = set()
|
||||
|
||||
def __iter__(self) -> Iterator[Tuple[Callable, Union[type, EventFilter]]]:
|
||||
return iter(self._hooks)
|
||||
|
||||
def on(self, event: Union[type, EventFilter]) -> Callable: # noqa
|
||||
"""Register decorated function as listener for the given event."""
|
||||
if isinstance(event, type):
|
||||
event = event()
|
||||
assert isinstance(event, EventFilter), "Invalid event filter"
|
||||
|
||||
def _decorator(func) -> Callable:
|
||||
self._hooks.add((func, event))
|
||||
return func
|
||||
|
||||
return _decorator
|
||||
52
deltachat-rpc-client/src/deltachat_rpc_client/message.py
Normal file
52
deltachat-rpc-client/src/deltachat_rpc_client/message.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
from ._utils import AttrDict
|
||||
from .contact import Contact
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .account import Account
|
||||
from .rpc import Rpc
|
||||
|
||||
|
||||
@dataclass
|
||||
class Message:
|
||||
"""Delta Chat Message object."""
|
||||
|
||||
account: "Account"
|
||||
id: int
|
||||
|
||||
@property
|
||||
def _rpc(self) -> "Rpc":
|
||||
return self.account._rpc
|
||||
|
||||
async def send_reaction(self, *reaction: str):
|
||||
"""Send a reaction to this message."""
|
||||
await self._rpc.send_reaction(self.account.id, self.id, reaction)
|
||||
|
||||
async def get_snapshot(self) -> AttrDict:
|
||||
"""Get a snapshot with the properties of this message."""
|
||||
from .chat import Chat
|
||||
|
||||
snapshot = AttrDict(await self._rpc.get_message(self.account.id, self.id))
|
||||
snapshot["chat"] = Chat(self.account, snapshot.chat_id)
|
||||
snapshot["sender"] = Contact(self.account, snapshot.from_id)
|
||||
snapshot["message"] = self
|
||||
return snapshot
|
||||
|
||||
async def mark_seen(self) -> None:
|
||||
"""Mark the message as seen."""
|
||||
await self._rpc.markseen_msgs(self.account.id, [self.id])
|
||||
|
||||
async def send_webxdc_status_update(self, update: Union[dict, str], description: str) -> None:
|
||||
"""Send a webxdc status update. This message must be a webxdc."""
|
||||
if not isinstance(update, str):
|
||||
update = json.dumps(update)
|
||||
await self._rpc.send_webxdc_status_update(self.account.id, self.id, update, description)
|
||||
|
||||
async def get_webxdc_status_updates(self, last_known_serial: int = 0) -> list:
|
||||
return json.loads(await self._rpc.get_webxdc_status_updates(self.account.id, self.id, last_known_serial))
|
||||
|
||||
async def get_webxdc_info(self) -> dict:
|
||||
return await self._rpc.get_webxdc_info(self.account.id, self.id)
|
||||
1
deltachat-rpc-client/src/deltachat_rpc_client/py.typed
Normal file
1
deltachat-rpc-client/src/deltachat_rpc_client/py.typed
Normal file
@@ -0,0 +1 @@
|
||||
# PEP 561 marker file. See https://peps.python.org/pep-0561/
|
||||
114
deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py
Normal file
114
deltachat-rpc-client/src/deltachat_rpc_client/pytestplugin.py
Normal file
@@ -0,0 +1,114 @@
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from typing import AsyncGenerator, List, Optional
|
||||
|
||||
import aiohttp
|
||||
import pytest_asyncio
|
||||
|
||||
from . import Account, AttrDict, Bot, Client, DeltaChat, EventType, Message
|
||||
from .rpc import Rpc
|
||||
|
||||
|
||||
async def get_temp_credentials() -> dict:
|
||||
url = os.getenv("DCC_NEW_TMP_EMAIL")
|
||||
assert url, "Failed to get online account, DCC_NEW_TMP_EMAIL is not set"
|
||||
|
||||
# Replace default 5 minute timeout with a 1 minute timeout.
|
||||
timeout = aiohttp.ClientTimeout(total=60)
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(url, timeout=timeout) as response:
|
||||
return json.loads(await response.text())
|
||||
|
||||
|
||||
class ACFactory:
|
||||
def __init__(self, deltachat: DeltaChat) -> None:
|
||||
self.deltachat = deltachat
|
||||
|
||||
async def get_unconfigured_account(self) -> Account:
|
||||
return await self.deltachat.add_account()
|
||||
|
||||
async def get_unconfigured_bot(self) -> Bot:
|
||||
return Bot(await self.get_unconfigured_account())
|
||||
|
||||
async def new_preconfigured_account(self) -> Account:
|
||||
"""Make a new account with configuration options set, but configuration not started."""
|
||||
credentials = await get_temp_credentials()
|
||||
account = await self.get_unconfigured_account()
|
||||
await account.set_config("addr", credentials["email"])
|
||||
await account.set_config("mail_pw", credentials["password"])
|
||||
assert not await account.is_configured()
|
||||
return account
|
||||
|
||||
async def new_configured_account(self) -> Account:
|
||||
account = await self.new_preconfigured_account()
|
||||
await account.configure()
|
||||
assert await account.is_configured()
|
||||
return account
|
||||
|
||||
async def new_configured_bot(self) -> Bot:
|
||||
credentials = await get_temp_credentials()
|
||||
bot = await self.get_unconfigured_bot()
|
||||
await bot.configure(credentials["email"], credentials["password"])
|
||||
return bot
|
||||
|
||||
async def get_online_account(self) -> Account:
|
||||
account = await self.new_configured_account()
|
||||
await account.start_io()
|
||||
while True:
|
||||
event = await account.wait_for_event()
|
||||
print(event)
|
||||
if event.type == EventType.IMAP_INBOX_IDLE:
|
||||
break
|
||||
return account
|
||||
|
||||
async def get_online_accounts(self, num: int) -> List[Account]:
|
||||
return await asyncio.gather(*[self.get_online_account() for _ in range(num)])
|
||||
|
||||
async def send_message(
|
||||
self,
|
||||
to_account: Account,
|
||||
from_account: Optional[Account] = None,
|
||||
text: Optional[str] = None,
|
||||
file: Optional[str] = None,
|
||||
group: Optional[str] = None,
|
||||
) -> Message:
|
||||
if not from_account:
|
||||
from_account = (await self.get_online_accounts(1))[0]
|
||||
to_contact = await from_account.create_contact(await to_account.get_config("addr"))
|
||||
if group:
|
||||
to_chat = await from_account.create_group(group)
|
||||
await to_chat.add_contact(to_contact)
|
||||
else:
|
||||
to_chat = await to_contact.create_chat()
|
||||
return await to_chat.send_message(text=text, file=file)
|
||||
|
||||
async def process_message(
|
||||
self,
|
||||
to_client: Client,
|
||||
from_account: Optional[Account] = None,
|
||||
text: Optional[str] = None,
|
||||
file: Optional[str] = None,
|
||||
group: Optional[str] = None,
|
||||
) -> AttrDict:
|
||||
await self.send_message(
|
||||
to_account=to_client.account,
|
||||
from_account=from_account,
|
||||
text=text,
|
||||
file=file,
|
||||
group=group,
|
||||
)
|
||||
|
||||
return await to_client.run_until(lambda e: e.type == EventType.INCOMING_MSG)
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def rpc(tmp_path) -> AsyncGenerator:
|
||||
rpc_server = Rpc(accounts_dir=str(tmp_path / "accounts"))
|
||||
async with rpc_server:
|
||||
yield rpc_server
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def acfactory(rpc) -> AsyncGenerator:
|
||||
yield ACFactory(DeltaChat(rpc))
|
||||
102
deltachat-rpc-client/src/deltachat_rpc_client/rpc.py
Normal file
102
deltachat-rpc-client/src/deltachat_rpc_client/rpc.py
Normal file
@@ -0,0 +1,102 @@
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
|
||||
class JsonRpcError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Rpc:
|
||||
def __init__(self, accounts_dir: Optional[str] = None, **kwargs):
|
||||
"""The given arguments will be passed to asyncio.create_subprocess_exec()"""
|
||||
if accounts_dir:
|
||||
kwargs["env"] = {
|
||||
**kwargs.get("env", os.environ),
|
||||
"DC_ACCOUNTS_PATH": str(accounts_dir),
|
||||
}
|
||||
|
||||
self._kwargs = kwargs
|
||||
self.process: asyncio.subprocess.Process
|
||||
self.id: int
|
||||
self.event_queues: Dict[int, asyncio.Queue]
|
||||
# Map from request ID to `asyncio.Future` returning the response.
|
||||
self.request_events: Dict[int, asyncio.Future]
|
||||
self.reader_task: asyncio.Task
|
||||
|
||||
async def start(self) -> None:
|
||||
self.process = await asyncio.create_subprocess_exec(
|
||||
"deltachat-rpc-server",
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
**self._kwargs,
|
||||
)
|
||||
self.id = 0
|
||||
self.event_queues = {}
|
||||
self.request_events = {}
|
||||
self.reader_task = asyncio.create_task(self.reader_loop())
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Terminate RPC server process and wait until the reader loop finishes."""
|
||||
self.process.terminate()
|
||||
await self.reader_task
|
||||
|
||||
async def __aenter__(self):
|
||||
await self.start()
|
||||
return self
|
||||
|
||||
async def __aexit__(self, _exc_type, _exc, _tb):
|
||||
await self.close()
|
||||
|
||||
async def reader_loop(self) -> None:
|
||||
while True:
|
||||
line = await self.process.stdout.readline() # noqa
|
||||
if not line: # EOF
|
||||
break
|
||||
response = json.loads(line)
|
||||
if "id" in response:
|
||||
fut = self.request_events.pop(response["id"])
|
||||
fut.set_result(response)
|
||||
elif response["method"] == "event":
|
||||
# An event notification.
|
||||
params = response["params"]
|
||||
account_id = params["contextId"]
|
||||
if account_id not in self.event_queues:
|
||||
self.event_queues[account_id] = asyncio.Queue()
|
||||
await self.event_queues[account_id].put(params["event"])
|
||||
else:
|
||||
print(response)
|
||||
|
||||
async def wait_for_event(self, account_id: int) -> Optional[dict]:
|
||||
"""Waits for the next event from the given account and returns it."""
|
||||
if account_id in self.event_queues:
|
||||
return await self.event_queues[account_id].get()
|
||||
return None
|
||||
|
||||
def __getattr__(self, attr: str):
|
||||
async def method(*args, **kwargs) -> Any:
|
||||
self.id += 1
|
||||
request_id = self.id
|
||||
|
||||
assert not (args and kwargs), "Mixing positional and keyword arguments"
|
||||
|
||||
request = {
|
||||
"jsonrpc": "2.0",
|
||||
"method": attr,
|
||||
"params": kwargs or args,
|
||||
"id": self.id,
|
||||
}
|
||||
data = (json.dumps(request) + "\n").encode()
|
||||
self.process.stdin.write(data) # noqa
|
||||
loop = asyncio.get_running_loop()
|
||||
fut = loop.create_future()
|
||||
self.request_events[request_id] = fut
|
||||
response = await fut
|
||||
if "error" in response:
|
||||
raise JsonRpcError(response["error"])
|
||||
if "result" in response:
|
||||
return response["result"]
|
||||
return None
|
||||
|
||||
return method
|
||||
305
deltachat-rpc-client/tests/test_something.py
Normal file
305
deltachat-rpc-client/tests/test_something.py
Normal file
@@ -0,0 +1,305 @@
|
||||
import asyncio
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from deltachat_rpc_client import EventType, events
|
||||
from deltachat_rpc_client.rpc import JsonRpcError
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_system_info(rpc) -> None:
|
||||
system_info = await rpc.get_system_info()
|
||||
assert "arch" in system_info
|
||||
assert "deltachat_core_version" in system_info
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_sleep(rpc) -> None:
|
||||
"""Test that long-running task does not block short-running task from completion."""
|
||||
sleep_5_task = asyncio.create_task(rpc.sleep(5.0))
|
||||
sleep_3_task = asyncio.create_task(rpc.sleep(3.0))
|
||||
done, pending = await asyncio.wait([sleep_5_task, sleep_3_task], return_when=asyncio.FIRST_COMPLETED)
|
||||
assert sleep_3_task in done
|
||||
assert sleep_5_task in pending
|
||||
sleep_5_task.cancel()
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_email_address_validity(rpc) -> None:
|
||||
valid_addresses = [
|
||||
"email@example.com",
|
||||
"36aa165ae3406424e0c61af17700f397cad3fe8ab83d682d0bddf3338a5dd52e@yggmail@yggmail",
|
||||
]
|
||||
invalid_addresses = ["email@", "example.com", "emai221"]
|
||||
|
||||
for addr in valid_addresses:
|
||||
assert await rpc.check_email_validity(addr)
|
||||
for addr in invalid_addresses:
|
||||
assert not await rpc.check_email_validity(addr)
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_acfactory(acfactory) -> None:
|
||||
account = await acfactory.new_configured_account()
|
||||
while True:
|
||||
event = await account.wait_for_event()
|
||||
if event.type == EventType.CONFIGURE_PROGRESS:
|
||||
assert event.progress != 0 # Progress 0 indicates error.
|
||||
if event.progress == 1000: # Success
|
||||
break
|
||||
else:
|
||||
print(event)
|
||||
print("Successful configuration")
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_configure_starttls(acfactory) -> None:
|
||||
account = await acfactory.new_preconfigured_account()
|
||||
|
||||
# Use STARTTLS
|
||||
await account.set_config("mail_security", "2")
|
||||
await account.set_config("send_security", "2")
|
||||
await account.configure()
|
||||
assert await account.is_configured()
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_account(acfactory) -> None:
|
||||
alice, bob = await acfactory.get_online_accounts(2)
|
||||
|
||||
bob_addr = await bob.get_config("addr")
|
||||
alice_contact_bob = await alice.create_contact(bob_addr, "Bob")
|
||||
alice_chat_bob = await alice_contact_bob.create_chat()
|
||||
await alice_chat_bob.send_text("Hello!")
|
||||
|
||||
while True:
|
||||
event = await bob.wait_for_event()
|
||||
if event.type == EventType.INCOMING_MSG:
|
||||
chat_id = event.chat_id
|
||||
msg_id = event.msg_id
|
||||
break
|
||||
|
||||
message = bob.get_message_by_id(msg_id)
|
||||
snapshot = await message.get_snapshot()
|
||||
assert snapshot.chat_id == chat_id
|
||||
assert snapshot.text == "Hello!"
|
||||
await bob.mark_seen_messages([message])
|
||||
|
||||
assert alice != bob
|
||||
assert repr(alice)
|
||||
assert (await alice.get_info()).level
|
||||
assert await alice.get_size()
|
||||
assert await alice.is_configured()
|
||||
assert not await alice.get_avatar()
|
||||
assert await alice.get_contact_by_addr(bob_addr) == alice_contact_bob
|
||||
assert await alice.get_contacts()
|
||||
assert await alice.get_contacts(snapshot=True)
|
||||
assert alice.self_contact
|
||||
assert await alice.get_chatlist()
|
||||
assert await alice.get_chatlist(snapshot=True)
|
||||
assert await alice.get_qr_code()
|
||||
await alice.get_fresh_messages()
|
||||
await alice.get_fresh_messages_in_arrival_order()
|
||||
|
||||
group = await alice.create_group("test group")
|
||||
await group.add_contact(alice_contact_bob)
|
||||
group_msg = await group.send_message(text="hello")
|
||||
assert group_msg == alice.get_message_by_id(group_msg.id)
|
||||
assert group == alice.get_chat_by_id(group.id)
|
||||
await alice.delete_messages([group_msg])
|
||||
|
||||
await alice.set_config("selfstatus", "test")
|
||||
assert await alice.get_config("selfstatus") == "test"
|
||||
await alice.update_config(selfstatus="test2")
|
||||
assert await alice.get_config("selfstatus") == "test2"
|
||||
|
||||
assert not await alice.get_blocked_contacts()
|
||||
await alice_contact_bob.block()
|
||||
blocked_contacts = await alice.get_blocked_contacts()
|
||||
assert blocked_contacts
|
||||
assert blocked_contacts[0].contact == alice_contact_bob
|
||||
|
||||
await bob.remove()
|
||||
await alice.stop_io()
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_chat(acfactory) -> None:
|
||||
alice, bob = await acfactory.get_online_accounts(2)
|
||||
|
||||
bob_addr = await bob.get_config("addr")
|
||||
alice_contact_bob = await alice.create_contact(bob_addr, "Bob")
|
||||
alice_chat_bob = await alice_contact_bob.create_chat()
|
||||
await alice_chat_bob.send_text("Hello!")
|
||||
|
||||
while True:
|
||||
event = await bob.wait_for_event()
|
||||
if event.type == EventType.INCOMING_MSG:
|
||||
chat_id = event.chat_id
|
||||
msg_id = event.msg_id
|
||||
break
|
||||
message = bob.get_message_by_id(msg_id)
|
||||
snapshot = await message.get_snapshot()
|
||||
assert snapshot.chat_id == chat_id
|
||||
assert snapshot.text == "Hello!"
|
||||
bob_chat_alice = bob.get_chat_by_id(chat_id)
|
||||
|
||||
assert alice_chat_bob != bob_chat_alice
|
||||
assert repr(alice_chat_bob)
|
||||
await alice_chat_bob.delete()
|
||||
await bob_chat_alice.accept()
|
||||
await bob_chat_alice.block()
|
||||
bob_chat_alice = await snapshot.sender.create_chat()
|
||||
await bob_chat_alice.mute()
|
||||
await bob_chat_alice.unmute()
|
||||
await bob_chat_alice.pin()
|
||||
await bob_chat_alice.unpin()
|
||||
await bob_chat_alice.archive()
|
||||
await bob_chat_alice.unarchive()
|
||||
with pytest.raises(JsonRpcError): # can't set name for 1:1 chats
|
||||
await bob_chat_alice.set_name("test")
|
||||
await bob_chat_alice.set_ephemeral_timer(300)
|
||||
await bob_chat_alice.get_encryption_info()
|
||||
|
||||
group = await alice.create_group("test group")
|
||||
await group.add_contact(alice_contact_bob)
|
||||
await group.get_qr_code()
|
||||
|
||||
snapshot = await group.get_basic_snapshot()
|
||||
assert snapshot.name == "test group"
|
||||
await group.set_name("new name")
|
||||
snapshot = await group.get_full_snapshot()
|
||||
assert snapshot.name == "new name"
|
||||
|
||||
msg = await group.send_message(text="hi")
|
||||
assert (await msg.get_snapshot()).text == "hi"
|
||||
await group.forward_messages([msg])
|
||||
|
||||
await group.set_draft(text="test draft")
|
||||
draft = await group.get_draft()
|
||||
assert draft.text == "test draft"
|
||||
await group.remove_draft()
|
||||
assert not await group.get_draft()
|
||||
|
||||
assert await group.get_messages()
|
||||
await group.get_fresh_message_count()
|
||||
await group.mark_noticed()
|
||||
assert await group.get_contacts()
|
||||
await group.remove_contact(alice_chat_bob)
|
||||
await group.get_locations()
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_contact(acfactory) -> None:
|
||||
alice, bob = await acfactory.get_online_accounts(2)
|
||||
|
||||
bob_addr = await bob.get_config("addr")
|
||||
alice_contact_bob = await alice.create_contact(bob_addr, "Bob")
|
||||
|
||||
assert alice_contact_bob == alice.get_contact_by_id(alice_contact_bob.id)
|
||||
assert repr(alice_contact_bob)
|
||||
await alice_contact_bob.block()
|
||||
await alice_contact_bob.unblock()
|
||||
await alice_contact_bob.set_name("new name")
|
||||
await alice_contact_bob.get_encryption_info()
|
||||
snapshot = await alice_contact_bob.get_snapshot()
|
||||
assert snapshot.address == bob_addr
|
||||
await alice_contact_bob.create_chat()
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_message(acfactory) -> None:
|
||||
alice, bob = await acfactory.get_online_accounts(2)
|
||||
|
||||
bob_addr = await bob.get_config("addr")
|
||||
alice_contact_bob = await alice.create_contact(bob_addr, "Bob")
|
||||
alice_chat_bob = await alice_contact_bob.create_chat()
|
||||
await alice_chat_bob.send_text("Hello!")
|
||||
|
||||
while True:
|
||||
event = await bob.wait_for_event()
|
||||
if event.type == EventType.INCOMING_MSG:
|
||||
chat_id = event.chat_id
|
||||
msg_id = event.msg_id
|
||||
break
|
||||
|
||||
message = bob.get_message_by_id(msg_id)
|
||||
snapshot = await message.get_snapshot()
|
||||
assert snapshot.chat_id == chat_id
|
||||
assert snapshot.text == "Hello!"
|
||||
assert not snapshot.is_bot
|
||||
assert repr(message)
|
||||
|
||||
with pytest.raises(JsonRpcError): # chat is not accepted
|
||||
await snapshot.chat.send_text("hi")
|
||||
await snapshot.chat.accept()
|
||||
await snapshot.chat.send_text("hi")
|
||||
|
||||
await message.mark_seen()
|
||||
await message.send_reaction("😎")
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_is_bot(acfactory) -> None:
|
||||
"""Test that we can recognize messages submitted by bots."""
|
||||
alice, bob = await acfactory.get_online_accounts(2)
|
||||
|
||||
bob_addr = await bob.get_config("addr")
|
||||
alice_contact_bob = await alice.create_contact(bob_addr, "Bob")
|
||||
alice_chat_bob = await alice_contact_bob.create_chat()
|
||||
|
||||
# Alice becomes a bot.
|
||||
await alice.set_config("bot", "1")
|
||||
await alice_chat_bob.send_text("Hello!")
|
||||
|
||||
while True:
|
||||
event = await bob.wait_for_event()
|
||||
if event.type == EventType.INCOMING_MSG:
|
||||
msg_id = event.msg_id
|
||||
message = bob.get_message_by_id(msg_id)
|
||||
snapshot = await message.get_snapshot()
|
||||
assert snapshot.chat_id == event.chat_id
|
||||
assert snapshot.text == "Hello!"
|
||||
assert snapshot.is_bot
|
||||
break
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_bot(acfactory) -> None:
|
||||
mock = MagicMock()
|
||||
user = (await acfactory.get_online_accounts(1))[0]
|
||||
bot = await acfactory.new_configured_bot()
|
||||
bot2 = await acfactory.new_configured_bot()
|
||||
|
||||
assert await bot.is_configured()
|
||||
assert await bot.account.get_config("bot") == "1"
|
||||
|
||||
hook = lambda e: mock.hook(e.msg_id) and None, events.RawEvent(EventType.INCOMING_MSG)
|
||||
bot.add_hook(*hook)
|
||||
event = await acfactory.process_message(from_account=user, to_client=bot, text="Hello!")
|
||||
snapshot = await bot.account.get_message_by_id(event.msg_id).get_snapshot()
|
||||
assert not snapshot.is_bot
|
||||
mock.hook.assert_called_once_with(event.msg_id)
|
||||
bot.remove_hook(*hook)
|
||||
|
||||
def track(e):
|
||||
mock.hook(e.message_snapshot.id)
|
||||
|
||||
mock.hook.reset_mock()
|
||||
hook = track, events.NewMessage(r"hello")
|
||||
bot.add_hook(*hook)
|
||||
bot.add_hook(track, events.NewMessage(command="/help"))
|
||||
event = await acfactory.process_message(from_account=user, to_client=bot, text="hello")
|
||||
mock.hook.assert_called_with(event.msg_id)
|
||||
event = await acfactory.process_message(from_account=user, to_client=bot, text="hello!")
|
||||
mock.hook.assert_called_with(event.msg_id)
|
||||
await acfactory.process_message(from_account=bot2.account, to_client=bot, text="hello")
|
||||
assert len(mock.hook.mock_calls) == 2 # bot messages are ignored between bots
|
||||
await acfactory.process_message(from_account=user, to_client=bot, text="hey!")
|
||||
assert len(mock.hook.mock_calls) == 2
|
||||
bot.remove_hook(*hook)
|
||||
|
||||
mock.hook.reset_mock()
|
||||
await acfactory.process_message(from_account=user, to_client=bot, text="hello")
|
||||
event = await acfactory.process_message(from_account=user, to_client=bot, text="/help")
|
||||
mock.hook.assert_called_once_with(event.msg_id)
|
||||
47
deltachat-rpc-client/tests/test_webxdc.py
Normal file
47
deltachat-rpc-client/tests/test_webxdc.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import pytest
|
||||
from deltachat_rpc_client import EventType
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_webxdc(acfactory) -> None:
|
||||
alice, bob = await acfactory.get_online_accounts(2)
|
||||
|
||||
bob_addr = await bob.get_config("addr")
|
||||
alice_contact_bob = await alice.create_contact(bob_addr, "Bob")
|
||||
alice_chat_bob = await alice_contact_bob.create_chat()
|
||||
await alice_chat_bob.send_message(text="Let's play chess!", file="../test-data/webxdc/chess.xdc")
|
||||
|
||||
while True:
|
||||
event = await bob.wait_for_event()
|
||||
if event.type == EventType.INCOMING_MSG:
|
||||
bob_chat_alice = bob.get_chat_by_id(event.chat_id)
|
||||
message = bob.get_message_by_id(event.msg_id)
|
||||
break
|
||||
|
||||
webxdc_info = await message.get_webxdc_info()
|
||||
assert webxdc_info == {
|
||||
"document": None,
|
||||
"icon": "icon.png",
|
||||
"internetAccess": False,
|
||||
"name": "Chess Board",
|
||||
"sourceCodeUrl": None,
|
||||
"summary": None,
|
||||
}
|
||||
|
||||
status_updates = await message.get_webxdc_status_updates()
|
||||
assert status_updates == []
|
||||
|
||||
await bob_chat_alice.accept()
|
||||
await message.send_webxdc_status_update({"payload": 42}, "")
|
||||
await message.send_webxdc_status_update({"payload": "Second update"}, "description")
|
||||
|
||||
status_updates = await message.get_webxdc_status_updates()
|
||||
assert status_updates == [
|
||||
{"payload": 42, "serial": 1, "max_serial": 2},
|
||||
{"payload": "Second update", "serial": 2, "max_serial": 2},
|
||||
]
|
||||
|
||||
status_updates = await message.get_webxdc_status_updates(1)
|
||||
assert status_updates == [
|
||||
{"payload": "Second update", "serial": 2, "max_serial": 2},
|
||||
]
|
||||
33
deltachat-rpc-client/tox.ini
Normal file
33
deltachat-rpc-client/tox.ini
Normal file
@@ -0,0 +1,33 @@
|
||||
[tox]
|
||||
isolated_build = true
|
||||
envlist =
|
||||
py3
|
||||
lint
|
||||
|
||||
[testenv]
|
||||
commands =
|
||||
pytest {posargs}
|
||||
setenv =
|
||||
# Avoid stack overflow when Rust core is built without optimizations.
|
||||
RUST_MIN_STACK=8388608
|
||||
passenv =
|
||||
DCC_NEW_TMP_EMAIL
|
||||
deps =
|
||||
pytest
|
||||
pytest-asyncio
|
||||
pytest-timeout
|
||||
aiohttp
|
||||
aiodns
|
||||
|
||||
[testenv:lint]
|
||||
skipsdist = True
|
||||
skip_install = True
|
||||
deps =
|
||||
ruff
|
||||
black
|
||||
commands =
|
||||
black --quiet --check --diff src/ examples/ tests/
|
||||
ruff src/ examples/ tests/
|
||||
|
||||
[pytest]
|
||||
timeout = 60
|
||||
26
deltachat-rpc-server/Cargo.toml
Normal file
26
deltachat-rpc-server/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "1.112.0"
|
||||
description = "DeltaChat JSON-RPC server"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
license = "MPL-2.0"
|
||||
|
||||
keywords = ["deltachat", "chat", "openpgp", "email", "encryption"]
|
||||
categories = ["cryptography", "std", "email"]
|
||||
|
||||
[dependencies]
|
||||
deltachat-jsonrpc = { path = "../deltachat-jsonrpc", default-features = false }
|
||||
|
||||
anyhow = "1"
|
||||
env_logger = { version = "0.10.0" }
|
||||
futures-lite = "1.12.0"
|
||||
log = "0.4"
|
||||
serde_json = "1.0.91"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tokio = { version = "1.25.0", features = ["io-std"] }
|
||||
yerpc = { version = "0.4.0", features = ["anyhow_expose"] }
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
vendored = ["deltachat-jsonrpc/vendored"]
|
||||
34
deltachat-rpc-server/README.md
Normal file
34
deltachat-rpc-server/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Delta Chat RPC server
|
||||
|
||||
This program provides a [JSON-RPC 2.0](https://www.jsonrpc.org/specification) interface to DeltaChat
|
||||
over standard I/O.
|
||||
|
||||
## Install
|
||||
|
||||
To download binary pre-builds check the [releases page](https://github.com/deltachat/deltachat-core-rust/releases).
|
||||
Rename the downloaded binary to `deltachat-rpc-server` and add it to your `PATH`.
|
||||
|
||||
To install from source run:
|
||||
|
||||
```sh
|
||||
cargo install --git https://github.com/deltachat/deltachat-core-rust/ deltachat-rpc-server
|
||||
```
|
||||
|
||||
The `deltachat-rpc-server` executable will be installed into `$HOME/.cargo/bin` that should be available
|
||||
in your `PATH`.
|
||||
|
||||
## Usage
|
||||
|
||||
To use just run `deltachat-rpc-server` command. The accounts folder will be created in the current
|
||||
working directory unless `DC_ACCOUNTS_PATH` is set:
|
||||
|
||||
```sh
|
||||
export DC_ACCOUNTS_PATH=$HOME/delta/
|
||||
deltachat-rpc-server
|
||||
```
|
||||
|
||||
The common use case for this program is to create bindings to use Delta Chat core from programming
|
||||
languages other than Rust, for example:
|
||||
|
||||
1. Python: https://github.com/deltachat/deltachat-core-rust/tree/master/deltachat-rpc-client/
|
||||
2. Go: https://github.com/deltachat/deltachat-rpc-client-go/
|
||||
71
deltachat-rpc-server/src/main.rs
Normal file
71
deltachat-rpc-server/src/main.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
///! Delta Chat core RPC server.
|
||||
///!
|
||||
///! It speaks JSON Lines over stdio.
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use deltachat_jsonrpc::api::events::event_to_json_rpc_notification;
|
||||
use deltachat_jsonrpc::api::{Accounts, CommandApi};
|
||||
use futures_lite::stream::StreamExt;
|
||||
use tokio::io::{self, AsyncBufReadExt, BufReader};
|
||||
use tokio::task::JoinHandle;
|
||||
use yerpc::{RpcClient, RpcSession};
|
||||
|
||||
#[tokio::main(flavor = "multi_thread")]
|
||||
async fn main() -> Result<()> {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
|
||||
let path = std::env::var("DC_ACCOUNTS_PATH").unwrap_or_else(|_| "accounts".to_string());
|
||||
log::info!("Starting with accounts directory `{}`.", path);
|
||||
let accounts = Accounts::new(PathBuf::from(&path)).await?;
|
||||
let events = accounts.get_event_emitter();
|
||||
|
||||
log::info!("Creating JSON-RPC API.");
|
||||
let state = CommandApi::new(accounts);
|
||||
|
||||
let (client, mut out_receiver) = RpcClient::new();
|
||||
let session = RpcSession::new(client.clone(), state);
|
||||
|
||||
// Events task converts core events to JSON-RPC notifications.
|
||||
let events_task: JoinHandle<Result<()>> = tokio::spawn(async move {
|
||||
while let Some(event) = events.recv().await {
|
||||
let event = event_to_json_rpc_notification(event);
|
||||
client.send_notification("event", Some(event)).await?;
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// Send task prints JSON responses to stdout.
|
||||
let send_task: JoinHandle<anyhow::Result<()>> = tokio::spawn(async move {
|
||||
while let Some(message) = out_receiver.next().await {
|
||||
let message = serde_json::to_string(&message)?;
|
||||
log::trace!("RPC send {}", message);
|
||||
println!("{message}");
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// Receiver task reads JSON requests from stdin.
|
||||
let recv_task: JoinHandle<anyhow::Result<()>> = tokio::spawn(async move {
|
||||
let stdin = io::stdin();
|
||||
let mut lines = BufReader::new(stdin).lines();
|
||||
while let Some(message) = lines.next_line().await? {
|
||||
log::trace!("RPC recv {}", message);
|
||||
let session = session.clone();
|
||||
tokio::spawn(async move {
|
||||
session.handle_incoming(&message).await;
|
||||
});
|
||||
}
|
||||
log::info!("EOF reached on stdin");
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// Wait for the end of stdin.
|
||||
recv_task.await??;
|
||||
|
||||
// Shutdown the server.
|
||||
send_task.abort();
|
||||
events_task.abort();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_derive"
|
||||
version = "2.0.0"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
license = "MPL-2.0"
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#![recursion_limit = "128"]
|
||||
extern crate proc_macro;
|
||||
|
||||
use crate::proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
// For now, assume (not check) that these macroses are applied to enum without
|
||||
use crate::proc_macro::TokenStream;
|
||||
|
||||
// For now, assume (not check) that these macros are applied to enum without
|
||||
// data. If this assumption is violated, compiler error will point to
|
||||
// generated code, which is not very user-friendly.
|
||||
|
||||
|
||||
84
deny.toml
Normal file
84
deny.toml
Normal file
@@ -0,0 +1,84 @@
|
||||
[advisories]
|
||||
unmaintained = "allow"
|
||||
ignore = [
|
||||
"RUSTSEC-2020-0071",
|
||||
|
||||
# Only affects windows if using non-default allocator (and unmaintained).
|
||||
"RUSTSEC-2021-0145",
|
||||
]
|
||||
|
||||
[bans]
|
||||
# Accept some duplicate versions, ideally we work towards this list
|
||||
# becoming empty. Adding versions forces us to revisit this at least
|
||||
# when upgrading.
|
||||
skip = [
|
||||
{ name = "base64", version = "<0.21" },
|
||||
{ name = "block-buffer", version = "<0.10" },
|
||||
{ name = "darling", version = "<0.14" },
|
||||
{ name = "darling_core", version = "<0.14" },
|
||||
{ name = "darling_macro", version = "<0.14" },
|
||||
{ name = "digest", version = "<0.10" },
|
||||
{ name = "env_logger", version = "<0.10" },
|
||||
{ name = "getrandom", version = "<0.2" },
|
||||
{ name = "hermit-abi", version = "<0.3" },
|
||||
{ name = "humantime", version = "<2.1" },
|
||||
{ name = "idna", version = "<0.3" },
|
||||
{ name = "nom", version = "<7.1" },
|
||||
{ name = "quick-error", version = "<2.0" },
|
||||
{ name = "rand", version = "<0.8" },
|
||||
{ name = "rand_chacha", version = "<0.3" },
|
||||
{ name = "rand_core", version = "<0.6" },
|
||||
{ name = "sha2", version = "<0.10" },
|
||||
{ name = "time", version = "<0.3" },
|
||||
{ name = "version_check", version = "<0.9" },
|
||||
{ name = "wasi", version = "<0.11" },
|
||||
{ name = "windows-sys", version = "<0.45" },
|
||||
{ name = "windows_x86_64_msvc", version = "<0.42" },
|
||||
{ name = "windows_x86_64_gnu", version = "<0.42" },
|
||||
{ name = "windows_i686_msvc", version = "<0.42" },
|
||||
{ name = "windows_i686_gnu", version = "<0.42" },
|
||||
{ name = "windows_aarch64_msvc", version = "<0.42" },
|
||||
{ name = "unicode-xid", version = "<0.2.4" },
|
||||
{ name = "syn", version = "<1.0" },
|
||||
{ name = "quote", version = "<1.0" },
|
||||
{ name = "proc-macro2", version = "<1.0" },
|
||||
{ name = "portable-atomic", version = "<1.0" },
|
||||
{ name = "spin", version = "<0.9.6" },
|
||||
{ name = "convert_case", version = "0.4.0" },
|
||||
{ name = "clap_lex", version = "0.2.4" },
|
||||
{ name = "clap", version = "3.2.23" },
|
||||
]
|
||||
|
||||
|
||||
[licenses]
|
||||
allow = [
|
||||
"0BSD",
|
||||
"Apache-2.0",
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"BSL-1.0", # Boost Software License 1.0
|
||||
"CC0-1.0",
|
||||
"ISC",
|
||||
"MIT",
|
||||
"MPL-2.0",
|
||||
"OpenSSL",
|
||||
"Unicode-DFS-2016",
|
||||
"Zlib",
|
||||
]
|
||||
|
||||
[[licenses.clarify]]
|
||||
name = "ring"
|
||||
expression = "MIT AND ISC AND OpenSSL"
|
||||
license-files = [
|
||||
{ path = "LICENSE", hash = 0xbd0eed23 },
|
||||
]
|
||||
|
||||
[sources.allow-org]
|
||||
# Organisations which we allow git sources from.
|
||||
github = [
|
||||
"async-email",
|
||||
"deltachat",
|
||||
"n0-computer",
|
||||
"quinn-rs",
|
||||
"dignifiedquire",
|
||||
]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user