mirror of
https://github.com/chatmail/core.git
synced 2026-04-02 05:22:14 +03:00
Compare commits
814 Commits
1.109.0
...
link2xt/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22aa1b7b12 | ||
|
|
087f6edd0c | ||
|
|
d6b7ee04a0 | ||
|
|
d5c5ff8b3f | ||
|
|
dc4396a699 | ||
|
|
a74b00c3f9 | ||
|
|
2fdb9f8b7e | ||
|
|
80fac3f1b8 | ||
|
|
17a6c88cc7 | ||
|
|
1ba69dbb9b | ||
|
|
ab1c7ebbe2 | ||
|
|
ee715da078 | ||
|
|
27e177dc05 | ||
|
|
7aac4bfc83 | ||
|
|
7b24f9b7a4 | ||
|
|
b36b902eeb | ||
|
|
30024abb6c | ||
|
|
1d9702e9e7 | ||
|
|
ee2eae63d6 | ||
|
|
cd477936b5 | ||
|
|
dbe9d7e34e | ||
|
|
49f143e0d5 | ||
|
|
9d7bdf369d | ||
|
|
a270db1d87 | ||
|
|
7c7cd9cc80 | ||
|
|
47d465e6e4 | ||
|
|
03d3e0578f | ||
|
|
440a442f30 | ||
|
|
1da52d7d1d | ||
|
|
4d74f625d3 | ||
|
|
0a94fbc735 | ||
|
|
9ef34890fa | ||
|
|
3e07f2c173 | ||
|
|
ee28298d7f | ||
|
|
62aed13880 | ||
|
|
bffe934acc | ||
|
|
87ffcaf03e | ||
|
|
2635146328 | ||
|
|
d727d85f6d | ||
|
|
81a7af10c7 | ||
|
|
4a6e94f8ab | ||
|
|
146fe50e20 | ||
|
|
9bf2850fb1 | ||
|
|
ba2c36548e | ||
|
|
d07c743cdc | ||
|
|
d70c1d48b5 | ||
|
|
a8e0cb9b5a | ||
|
|
6ea9a8988b | ||
|
|
45e35b3571 | ||
|
|
e43f9066d8 | ||
|
|
bba6c8f15a | ||
|
|
55aaec744a | ||
|
|
2f24eddb7d | ||
|
|
a33c91afa9 | ||
|
|
d52f2883cf | ||
|
|
b872953bc5 | ||
|
|
c1cb6eef08 | ||
|
|
200b808c27 | ||
|
|
d572d960e5 | ||
|
|
5db75128ba | ||
|
|
fbd2fc8ead | ||
|
|
bc73c16df7 | ||
|
|
0a50bad555 | ||
|
|
82c0058129 | ||
|
|
1bd307a26a | ||
|
|
740f43a2d6 | ||
|
|
c14f45a8f5 | ||
|
|
8269116dba | ||
|
|
db941ccf88 | ||
|
|
a464cbdfe6 | ||
|
|
ea4a0530b8 | ||
|
|
9d3b2d4844 | ||
|
|
c312280ab3 | ||
|
|
572b99a2e1 | ||
|
|
3992b5a063 | ||
|
|
b97cb4b55e | ||
|
|
64c218f1ea | ||
|
|
deed790950 | ||
|
|
b33ae3cd0f | ||
|
|
9480699362 | ||
|
|
94c190e844 | ||
|
|
578e47666f | ||
|
|
7eeced50d1 | ||
|
|
46e127ad27 | ||
|
|
4891849e28 | ||
|
|
e0dd83d538 | ||
|
|
aac8bb950c | ||
|
|
bf21796bc0 | ||
|
|
9cbf413064 | ||
|
|
1b57eb4d8d | ||
|
|
5152e702bd | ||
|
|
c80f1a1997 | ||
|
|
88759c815b | ||
|
|
9c68fac4b6 | ||
|
|
8e17e400b3 | ||
|
|
dae3857db8 | ||
|
|
695f71e124 | ||
|
|
2d30afd212 | ||
|
|
5fe94e8bce | ||
|
|
1351f71632 | ||
|
|
d42322b38b | ||
|
|
ce6876c418 | ||
|
|
2a6b7d9766 | ||
|
|
fa1924da2b | ||
|
|
d5214eb192 | ||
|
|
c47324d671 | ||
|
|
3f8ec5ec56 | ||
|
|
fab504b54c | ||
|
|
dd32430ade | ||
|
|
eb943625a6 | ||
|
|
32ac4a01ca | ||
|
|
f01a9d7d5c | ||
|
|
a5db7104c2 | ||
|
|
18aeb14003 | ||
|
|
4ad2d6e340 | ||
|
|
ce9cd54993 | ||
|
|
23f540f9f9 | ||
|
|
f994b2d8e4 | ||
|
|
6e42b85a36 | ||
|
|
d69e42377d | ||
|
|
de9330b52f | ||
|
|
01d1c4c04b | ||
|
|
7d98978269 | ||
|
|
5024f48609 | ||
|
|
e975568122 | ||
|
|
1f71c69325 | ||
|
|
b80ec8507c | ||
|
|
3a3f3542d9 | ||
|
|
657c5fa947 | ||
|
|
7d0b25c209 | ||
|
|
8d26303cad | ||
|
|
0d8a76593a | ||
|
|
7b49fb2eb6 | ||
|
|
efa37dd283 | ||
|
|
323e44da04 | ||
|
|
70efd0f10a | ||
|
|
fcec81b4c1 | ||
|
|
dd806b2d88 | ||
|
|
5659c1b9c2 | ||
|
|
d538d29b94 | ||
|
|
b4209fac2e | ||
|
|
4d6dfa120e | ||
|
|
f92108be1d | ||
|
|
00cb72f04d | ||
|
|
92e34d67e6 | ||
|
|
65bff8339f | ||
|
|
768f8175e6 | ||
|
|
c3f352aff1 | ||
|
|
5ac2d1b8cb | ||
|
|
8214b2b8c1 | ||
|
|
53ab8a3b35 | ||
|
|
cbe1671104 | ||
|
|
0d0e223238 | ||
|
|
4767f1ce74 | ||
|
|
1a62b6d77f | ||
|
|
915008d474 | ||
|
|
9646766793 | ||
|
|
e948ec3256 | ||
|
|
9ab9d2eb7b | ||
|
|
437f8c48c4 | ||
|
|
e6d9a49187 | ||
|
|
33a014eea4 | ||
|
|
9be871ccf6 | ||
|
|
6eb8abe535 | ||
|
|
91bf87fa80 | ||
|
|
a2599ef08a | ||
|
|
22d0a4bb32 | ||
|
|
7a160033b6 | ||
|
|
3442748be7 | ||
|
|
d451bcfbe3 | ||
|
|
b2993242e4 | ||
|
|
5eaa9eeed2 | ||
|
|
3ed2ac8f0c | ||
|
|
0145203f7b | ||
|
|
59588b319e | ||
|
|
f917c7de6b | ||
|
|
84888fa4c4 | ||
|
|
e0b1644488 | ||
|
|
4beba8ce3c | ||
|
|
bc521a685d | ||
|
|
33caa0f499 | ||
|
|
033ce41c0f | ||
|
|
88a62e1f6e | ||
|
|
dd30f6ab7d | ||
|
|
140d116d98 | ||
|
|
d96b783909 | ||
|
|
572c7f2efb | ||
|
|
bcd6c226f6 | ||
|
|
bae61746f8 | ||
|
|
31f2766074 | ||
|
|
b06c8baa9c | ||
|
|
1e479fe4a3 | ||
|
|
8ea8ee02ed | ||
|
|
55bc556bcf | ||
|
|
3b6d21301b | ||
|
|
472195c7d9 | ||
|
|
afb8b5ce55 | ||
|
|
de3c82ef43 | ||
|
|
4255ae4c2d | ||
|
|
4b4e2f700e | ||
|
|
81fde5c680 | ||
|
|
5340a7d033 | ||
|
|
fc82d728fc | ||
|
|
136e9179e9 | ||
|
|
31e19ca56c | ||
|
|
f2b02b7bb0 | ||
|
|
646ace8e7a | ||
|
|
a2495716b6 | ||
|
|
0f579c6415 | ||
|
|
3eddc9164c | ||
|
|
dd29fae49b | ||
|
|
5b435d11c7 | ||
|
|
b9b0d20e8d | ||
|
|
c68a2e3820 | ||
|
|
c7ad0b1f4f | ||
|
|
0dd9e3a77e | ||
|
|
d27e3d085e | ||
|
|
10b2aa5350 | ||
|
|
3a29a555bf | ||
|
|
f024f396bf | ||
|
|
24d52c5909 | ||
|
|
f9dc8edbcb | ||
|
|
2e6f98f4e4 | ||
|
|
50431d8cfe | ||
|
|
55fcd589db | ||
|
|
081178d623 | ||
|
|
92d5857150 | ||
|
|
bb45c249a3 | ||
|
|
8796e0472a | ||
|
|
3bd16ba045 | ||
|
|
4b7ff6f003 | ||
|
|
53449ea5b3 | ||
|
|
3b381c4862 | ||
|
|
ce729263a5 | ||
|
|
67480999c0 | ||
|
|
fb8b9f60ce | ||
|
|
9ed36d4e05 | ||
|
|
e3c01d76c4 | ||
|
|
cb7f96449d | ||
|
|
e4f4dacaf0 | ||
|
|
9fc1fe74ad | ||
|
|
991089d98e | ||
|
|
c7a250da31 | ||
|
|
c5b6bad956 | ||
|
|
c4534ff621 | ||
|
|
b2c299fa82 | ||
|
|
68aa15950a | ||
|
|
4a593a8d7e | ||
|
|
2328ba54be | ||
|
|
e216dfd655 | ||
|
|
86472aba2c | ||
|
|
15ce54edfb | ||
|
|
b7bbb3ee9a | ||
|
|
d3236e79fd | ||
|
|
aa212b2b7e | ||
|
|
a0c51b3c3a | ||
|
|
e50d7724e3 | ||
|
|
0d30e66dda | ||
|
|
5c3df7e452 | ||
|
|
3efbe5b1ef | ||
|
|
52cfba06ea | ||
|
|
afba63603e | ||
|
|
3a25d6a44e | ||
|
|
0688895022 | ||
|
|
ce0e5416e6 | ||
|
|
7918a6801e | ||
|
|
6af631e8df | ||
|
|
625ecaa9b5 | ||
|
|
24fe1b9c15 | ||
|
|
ba36d09c70 | ||
|
|
f57be7187e | ||
|
|
6a00338f79 | ||
|
|
16906210e1 | ||
|
|
f9b4540387 | ||
|
|
9755438d0d | ||
|
|
fe9534ed7d | ||
|
|
134c9ada68 | ||
|
|
a3240452ff | ||
|
|
84beb6647d | ||
|
|
ecf7e2d909 | ||
|
|
fcfcf4bbf3 | ||
|
|
c62b6d77b7 | ||
|
|
7e51b9686f | ||
|
|
542ec4cac4 | ||
|
|
64b25d9ec0 | ||
|
|
f91b6fbdf0 | ||
|
|
41445a506e | ||
|
|
798db9d019 | ||
|
|
2e860c32ab | ||
|
|
0e1faed6e5 | ||
|
|
f5de3be977 | ||
|
|
4b0a30eb66 | ||
|
|
7710467571 | ||
|
|
1687794b81 | ||
|
|
158541f05c | ||
|
|
eb28899cd0 | ||
|
|
432046225a | ||
|
|
bf958ce6c1 | ||
|
|
ea8ee4e67d | ||
|
|
edfdbbdc90 | ||
|
|
1b8bfef441 | ||
|
|
514074de8b | ||
|
|
e7aab5c67c | ||
|
|
40484e875e | ||
|
|
fc215ceb63 | ||
|
|
2701c135db | ||
|
|
3a8df3e673 | ||
|
|
fa95b269a5 | ||
|
|
0e9f8c4726 | ||
|
|
a8d4cbd5c1 | ||
|
|
f68a2fc387 | ||
|
|
ede4e8109e | ||
|
|
663df6bdfd | ||
|
|
d97bdd9fd0 | ||
|
|
a806a218bf | ||
|
|
903633f422 | ||
|
|
3c774b02e5 | ||
|
|
4aae48b0a1 | ||
|
|
a8b790a5db | ||
|
|
6a6ceb6875 | ||
|
|
37503dd3e8 | ||
|
|
3f615c8de6 | ||
|
|
2ef5f2eb52 | ||
|
|
f267f6f756 | ||
|
|
538db53887 | ||
|
|
21349abed8 | ||
|
|
d2fb2bb2ca | ||
|
|
0b832fb9de | ||
|
|
179b9ba2cb | ||
|
|
9150e9fb38 | ||
|
|
4716fcef94 | ||
|
|
2b7ee85e30 | ||
|
|
2b8888350b | ||
|
|
4dfe34eedc | ||
|
|
430a71288f | ||
|
|
350509d5d1 | ||
|
|
5403fd849c | ||
|
|
fa87d2e225 | ||
|
|
28a13e98a6 | ||
|
|
b369a30544 | ||
|
|
318ed4e6e1 | ||
|
|
c6a64e8d93 | ||
|
|
efd6937dfa | ||
|
|
36bf1fe3f6 | ||
|
|
4da0e9ac64 | ||
|
|
436766f002 | ||
|
|
d4f2507288 | ||
|
|
28fd27476f | ||
|
|
619b849ce7 | ||
|
|
f1eeb1df8c | ||
|
|
cecc080931 | ||
|
|
11e6a325a2 | ||
|
|
eed8e08145 | ||
|
|
36bec9c295 | ||
|
|
c8988f5a55 | ||
|
|
86c18fb3ae | ||
|
|
2ba4381c39 | ||
|
|
185a0193cc | ||
|
|
1b00334281 | ||
|
|
0eb2f5bf52 | ||
|
|
78d1aa46a1 | ||
|
|
26403a1599 | ||
|
|
a1f112470e | ||
|
|
2ab7a37d85 | ||
|
|
4b933ed2ef | ||
|
|
0d1c12115d | ||
|
|
3bced80b23 | ||
|
|
f46f3e939e | ||
|
|
9d8e836fdd | ||
|
|
132d34db5c | ||
|
|
16edb7b35a | ||
|
|
044478a044 | ||
|
|
f6ea48d666 | ||
|
|
6ed3ed1617 | ||
|
|
9c9c401e66 | ||
|
|
72031edfbe | ||
|
|
41456aa2ab | ||
|
|
a70f29381a | ||
|
|
3b0ad73732 | ||
|
|
8c302648bb | ||
|
|
01b888c341 | ||
|
|
f8e87a8b6c | ||
|
|
01af83946c | ||
|
|
386a9ad0b0 | ||
|
|
c6c20d8f3c | ||
|
|
40b072711e | ||
|
|
2469964a44 | ||
|
|
40e0924768 | ||
|
|
e016440fb3 | ||
|
|
a37aaa5585 | ||
|
|
1ee551d53e | ||
|
|
2341eed796 | ||
|
|
49cfd79505 | ||
|
|
9ac0d1a1ce | ||
|
|
6046dfabe9 | ||
|
|
c2269bf777 | ||
|
|
b81a958021 | ||
|
|
9e37a5643c | ||
|
|
50a213c2e1 | ||
|
|
c6e9fbd501 | ||
|
|
720b5aa7c1 | ||
|
|
7be7640d3f | ||
|
|
e4d97278ee | ||
|
|
793ad82d42 | ||
|
|
2b4d846f0e | ||
|
|
379eadab4f | ||
|
|
53a41fcffe | ||
|
|
5e597e1f09 | ||
|
|
c9879f863b | ||
|
|
e1b260d0ec | ||
|
|
480b05063d | ||
|
|
8477f7b472 | ||
|
|
91ae4eab06 | ||
|
|
f6d27516cb | ||
|
|
7b86681cb2 | ||
|
|
1de815441a | ||
|
|
d6426dc1b6 | ||
|
|
44a36025b5 | ||
|
|
00017ab653 | ||
|
|
411adc861e | ||
|
|
c5b3939ddb | ||
|
|
a9e3ea56ec | ||
|
|
6a0f17a983 | ||
|
|
28e6f457b1 | ||
|
|
fdf46054e2 | ||
|
|
47a3ee1ff1 | ||
|
|
a0b7b4b5c4 | ||
|
|
b9bc0a4047 | ||
|
|
e57abf92f3 | ||
|
|
b56a7bc139 | ||
|
|
c401780c15 | ||
|
|
df96a1daac | ||
|
|
0a2200c2c8 | ||
|
|
61b8d04418 | ||
|
|
fd7cc83537 | ||
|
|
f24843fbb1 | ||
|
|
6c57bc9438 | ||
|
|
ae5f72cf4f | ||
|
|
3e65b6f3a6 | ||
|
|
a4a53d5299 | ||
|
|
4223cac7a5 | ||
|
|
0073a09da6 | ||
|
|
e612927c5d | ||
|
|
aff951440c | ||
|
|
ef63e01632 | ||
|
|
c4cf0f12c9 | ||
|
|
68635be8a2 | ||
|
|
776df7505e | ||
|
|
d6fdc7cb67 | ||
|
|
91c10b3ac6 | ||
|
|
82ace72527 | ||
|
|
ea87c78d34 | ||
|
|
585b8ece58 | ||
|
|
a2927a6586 | ||
|
|
943c8a1ab3 | ||
|
|
3400f5641e | ||
|
|
5068648960 | ||
|
|
5be558ea68 | ||
|
|
fc25bba514 | ||
|
|
20b326415a | ||
|
|
1bf5064039 | ||
|
|
edf0c02bc8 | ||
|
|
1d42907114 | ||
|
|
070d832580 | ||
|
|
0dfec83b0f | ||
|
|
c84155cbd4 | ||
|
|
eb5ddf270f | ||
|
|
fb093253c6 | ||
|
|
1864be5c55 | ||
|
|
7138d44083 | ||
|
|
04daff0608 | ||
|
|
801250a9e0 | ||
|
|
20319b5426 | ||
|
|
9cca34bba5 | ||
|
|
530981119e | ||
|
|
6d0327d057 | ||
|
|
580ce5a9e9 | ||
|
|
6f327c950d | ||
|
|
92ad843ff2 | ||
|
|
a8059c6bff | ||
|
|
4b468a25fe | ||
|
|
1e135b649c | ||
|
|
40d32f2d0c | ||
|
|
c9ec087cd8 | ||
|
|
84d79e1479 | ||
|
|
83af248068 | ||
|
|
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 | ||
|
|
f2570945c6 | ||
|
|
8072f78058 | ||
|
|
8ae0ee5a67 | ||
|
|
a75d2b1c80 | ||
|
|
c48c2af7a1 | ||
|
|
490a14c5ef | ||
|
|
dcce6ef50b | ||
|
|
7cf0820d2b | ||
|
|
0bae3caaff | ||
|
|
bca0b256c9 | ||
|
|
a53d30c459 | ||
|
|
7a9f497aa7 | ||
|
|
f9f9bc3efb | ||
|
|
904990bf91 | ||
|
|
b2266ffca1 | ||
|
|
bb9a3d4b8e | ||
|
|
e565e19b42 | ||
|
|
41319c85c7 | ||
|
|
daf56804a5 | ||
|
|
6f7a43804d | ||
|
|
0ca76d36ef | ||
|
|
ec5789997a | ||
|
|
7a0d61bbb0 | ||
|
|
1c2461974d | ||
|
|
2a754744fe | ||
|
|
b413593c43 | ||
|
|
c73edd7e21 | ||
|
|
a34a69d8e4 | ||
|
|
020a9d33f6 | ||
|
|
19f6f89312 | ||
|
|
d56e05a11a | ||
|
|
c379a4e5a7 | ||
|
|
44c1efe4e4 | ||
|
|
ff0d675082 | ||
|
|
e1087b4145 | ||
|
|
323535584b | ||
|
|
852adbe514 | ||
|
|
4c78553d90 | ||
|
|
a31ae5297a | ||
|
|
e7792a0c65 | ||
|
|
3c32de1859 | ||
|
|
6a3fe3db92 | ||
|
|
ac048c154d | ||
|
|
3f51a8ffc2 | ||
|
|
2129b2b7a0 | ||
|
|
3734fc25a7 | ||
|
|
05ddc13054 | ||
|
|
716504b833 | ||
|
|
187861c3b2 | ||
|
|
0b075ac762 | ||
|
|
a6c889ed5e | ||
|
|
ca1533b0e4 | ||
|
|
3267596a30 | ||
|
|
5f29b93970 | ||
|
|
2a6a21c33a | ||
|
|
059af398eb | ||
|
|
6044e5961b |
8
.gitattributes
vendored
8
.gitattributes
vendored
@@ -2,6 +2,14 @@
|
||||
# ensures this even if the user has not set core.autocrlf.
|
||||
* text=auto
|
||||
|
||||
# Checkout JavaScript files with LF line endings
|
||||
# to prevent `prettier` from reporting errors on Windows.
|
||||
*.js eol=lf
|
||||
*.jsx eol=lf
|
||||
*.ts eol=lf
|
||||
*.tsx eol=lf
|
||||
*.json eol=lf
|
||||
|
||||
# This directory contains email messages verbatim, and changing CRLF to
|
||||
# LF will corrupt them.
|
||||
test-data/** text=false
|
||||
|
||||
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -5,5 +5,5 @@ updates:
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
commit-message:
|
||||
prefix: "cargo"
|
||||
prefix: "chore(cargo)"
|
||||
open-pull-requests-limit: 50
|
||||
|
||||
26
.github/mergeable.yml
vendored
26
.github/mergeable.yml
vendored
@@ -1,26 +0,0 @@
|
||||
version: 2
|
||||
mergeable:
|
||||
- when: pull_request.*
|
||||
name: "Changelog check"
|
||||
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']
|
||||
fail:
|
||||
- do: checks
|
||||
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."
|
||||
303
.github/workflows/ci.yml
vendored
303
.github/workflows/ci.yml
vendored
@@ -1,36 +1,66 @@
|
||||
# GitHub Actions workflow to
|
||||
# lint Rust and Python code
|
||||
# and run Rust tests, Python tests and async Python tests.
|
||||
|
||||
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
|
||||
- stable
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
lint_rust:
|
||||
name: Lint Rust
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: 1.72.0
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: cargo fmt --all -- --check
|
||||
|
||||
run_clippy:
|
||||
name: Clippy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install clippy
|
||||
run: rustup toolchain install 1.67.1 --component clippy
|
||||
- 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
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: 1.67.1
|
||||
run: scripts/clippy.sh
|
||||
- name: Check
|
||||
run: cargo check --workspace --all-targets --all-features
|
||||
|
||||
# Check with musl libc target which is used for `deltachat-rpc-server` releases.
|
||||
- name: Check musl
|
||||
run: scripts/zig-musl-check.sh
|
||||
|
||||
cargo_deny:
|
||||
name: cargo deny
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||
with:
|
||||
arguments: --all-features --workspace
|
||||
command: check
|
||||
command-arguments: "-Dwarnings"
|
||||
|
||||
provider_database:
|
||||
name: Check provider database
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Check provider database
|
||||
run: scripts/update-provider-database.sh
|
||||
|
||||
docs:
|
||||
name: Rust doc comments
|
||||
@@ -45,97 +75,206 @@ jobs:
|
||||
- name: Rustdoc
|
||||
run: cargo doc --document-private-items --no-deps
|
||||
|
||||
build_and_test:
|
||||
name: Build and test
|
||||
rust_tests:
|
||||
name: Rust tests
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
rust: 1.68.2
|
||||
- os: windows-latest
|
||||
rust: 1.68.2
|
||||
- os: macos-latest
|
||||
rust: 1.68.2
|
||||
|
||||
# Minimum Supported Rust Version = 1.65.0
|
||||
#
|
||||
# Minimum Supported Python Version = 3.7
|
||||
# This is the minimum version for which manylinux Python wheels are
|
||||
# built.
|
||||
- os: ubuntu-latest
|
||||
rust: 1.65.0
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- 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@v2
|
||||
|
||||
- name: Tests
|
||||
run: cargo test --workspace
|
||||
|
||||
- name: Test cargo vendor
|
||||
run: cargo vendor
|
||||
|
||||
c_library:
|
||||
name: Build C library
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@v2
|
||||
|
||||
- name: Build C library
|
||||
run: cargo build -p deltachat_ffi --features jsonrpc
|
||||
|
||||
- name: Upload C library
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.os }}-libdeltachat.a
|
||||
path: target/debug/libdeltachat.a
|
||||
retention-days: 1
|
||||
|
||||
rpc_server:
|
||||
name: Build deltachat-rpc-server
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@v2
|
||||
|
||||
- name: Build deltachat-rpc-server
|
||||
run: cargo build -p deltachat-rpc-server
|
||||
|
||||
- name: Upload deltachat-rpc-server
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.os }}-deltachat-rpc-server
|
||||
path: target/debug/deltachat-rpc-server
|
||||
retention-days: 1
|
||||
|
||||
python_lint:
|
||||
name: Python lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install tox
|
||||
run: pip install tox
|
||||
|
||||
- name: Lint Python bindings
|
||||
working-directory: python
|
||||
run: tox -e lint
|
||||
|
||||
- name: Lint deltachat-rpc-client
|
||||
working-directory: deltachat-rpc-client
|
||||
run: tox -e lint
|
||||
|
||||
python_tests:
|
||||
name: Python tests
|
||||
needs: ["c_library", "python_lint"]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Currently used Rust version.
|
||||
- os: ubuntu-latest
|
||||
rust: 1.64.0
|
||||
python: 3.9
|
||||
- os: windows-latest
|
||||
rust: 1.64.0
|
||||
python: false # Python bindings compilation on Windows is not supported.
|
||||
python: 3.11
|
||||
- os: macos-latest
|
||||
python: 3.11
|
||||
|
||||
# PyPy tests
|
||||
- os: ubuntu-latest
|
||||
python: pypy3.9
|
||||
- os: macos-latest
|
||||
python: pypy3.9
|
||||
|
||||
# Minimum Supported Rust Version = 1.63.0
|
||||
#
|
||||
# Minimum Supported Python Version = 3.7
|
||||
# This is the minimum version for which manylinux Python wheels are
|
||||
# built.
|
||||
# built. Test it with minimum supported Rust version.
|
||||
- os: ubuntu-latest
|
||||
rust: 1.63.0
|
||||
python: 3.7
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust ${{ matrix.rust }}
|
||||
run: rustup toolchain install ${{ matrix.rust }}
|
||||
- run: rustup override set ${{ matrix.rust }}
|
||||
- name: Download libdeltachat.a
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.os }}-libdeltachat.a
|
||||
path: target/debug
|
||||
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@v2
|
||||
- name: Install python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
|
||||
- name: Check
|
||||
run: cargo check --workspace --bins --examples --tests --benches
|
||||
- name: Install tox
|
||||
run: pip install tox
|
||||
|
||||
- name: Tests
|
||||
run: cargo test --workspace
|
||||
- name: Run python tests
|
||||
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 mypy,doc,py
|
||||
|
||||
- name: Test cargo vendor
|
||||
run: cargo vendor
|
||||
aysnc_python_tests:
|
||||
name: Async Python tests
|
||||
needs: ["python_lint", "rpc_server"]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
python: 3.11
|
||||
- os: macos-latest
|
||||
python: 3.11
|
||||
|
||||
- name: Install python
|
||||
if: ${{ matrix.python }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
# PyPy tests
|
||||
- os: ubuntu-latest
|
||||
python: pypy3.9
|
||||
- os: macos-latest
|
||||
python: pypy3.9
|
||||
|
||||
- name: Install tox
|
||||
if: ${{ matrix.python }}
|
||||
run: pip install tox
|
||||
# Minimum Supported Python Version = 3.8
|
||||
#
|
||||
# Python 3.7 has at least one known bug related to starting subprocesses
|
||||
# in asyncio programs: <https://bugs.python.org/issue35621>
|
||||
- os: ubuntu-latest
|
||||
python: 3.8
|
||||
|
||||
- name: Build C library
|
||||
if: ${{ matrix.python }}
|
||||
run: cargo build -p deltachat_ffi --features jsonrpc
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- 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 python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
|
||||
- name: Build deltachat-rpc-server
|
||||
if: ${{ matrix.python }}
|
||||
run: cargo build -p deltachat-rpc-server
|
||||
- name: Install tox
|
||||
run: pip install tox
|
||||
|
||||
- name: Add deltachat-rpc-server to path
|
||||
if: ${{ matrix.python }}
|
||||
run: echo ${{ github.workspace }}/target/debug >> $GITHUB_PATH
|
||||
- name: Download deltachat-rpc-server
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.os }}-deltachat-rpc-server
|
||||
path: target/debug
|
||||
|
||||
- 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: Make deltachat-rpc-server executable
|
||||
run: chmod +x target/debug/deltachat-rpc-server
|
||||
|
||||
- name: Install pypy
|
||||
if: ${{ matrix.python }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 'pypy${{ matrix.python }}'
|
||||
- name: Add deltachat-rpc-server to path
|
||||
run: echo ${{ github.workspace }}/target/debug >> $GITHUB_PATH
|
||||
|
||||
- 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: Run deltachat-rpc-client tests
|
||||
env:
|
||||
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
||||
working-directory: deltachat-rpc-client
|
||||
run: tox -e py
|
||||
|
||||
138
.github/workflows/deltachat-rpc-server.yml
vendored
Normal file
138
.github/workflows/deltachat-rpc-server.yml
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
# GitHub Actions workflow
|
||||
# to build `deltachat-rpc-server` binaries
|
||||
# and upload them to the release.
|
||||
#
|
||||
# The workflow is automatically triggered on releases.
|
||||
# It can also be triggered manually
|
||||
# to produce binary artifacts for testing.
|
||||
|
||||
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, i686, 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 i686 binary
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: deltachat-rpc-server-i686
|
||||
path: target/i686-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
|
||||
|
||||
build_macos:
|
||||
name: Build deltachat-rpc-server for macOS
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Setup rust target
|
||||
run: rustup target add x86_64-apple-darwin
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release --package deltachat-rpc-server --target x86_64-apple-darwin --features vendored
|
||||
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: deltachat-rpc-server-x86_64-macos
|
||||
path: target/x86_64-apple-darwin/release/deltachat-rpc-server
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
name: Upload binaries to the release
|
||||
needs: ["build_linux", "build_windows", "build_macos"]
|
||||
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 i686 aarch64 armv7 win32.exe win64.exe x86_64-macos; 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/*
|
||||
3
.github/workflows/dependabot.yml
vendored
3
.github/workflows/dependabot.yml
vendored
@@ -1,3 +1,6 @@
|
||||
# GitHub Actions workflow
|
||||
# to automatically approve PRs made by Dependabot.
|
||||
|
||||
name: Dependabot auto-approve
|
||||
on: pull_request
|
||||
|
||||
|
||||
28
.github/workflows/jsonrpc-client-npm-package.yml
vendored
28
.github/workflows/jsonrpc-client-npm-package.yml
vendored
@@ -1,16 +1,15 @@
|
||||
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
|
||||
run: sudo apt install tree
|
||||
@@ -18,12 +17,12 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
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 }}
|
||||
@@ -39,13 +38,12 @@ jobs:
|
||||
node --version
|
||||
echo $DELTACHAT_JSONRPC_TAR_GZ
|
||||
- name: Install dependencies without running scripts
|
||||
run: |
|
||||
cd deltachat-jsonrpc/typescript
|
||||
npm install --ignore-scripts
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm install --ignore-scripts
|
||||
- name: Package
|
||||
shell: bash
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: |
|
||||
cd deltachat-jsonrpc/typescript
|
||||
npm run build
|
||||
npm pack .
|
||||
ls -lah
|
||||
@@ -69,9 +67,9 @@ jobs:
|
||||
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 }}
|
||||
|
||||
25
.github/workflows/jsonrpc.yml
vendored
25
.github/workflows/jsonrpc.yml
vendored
@@ -22,24 +22,19 @@ jobs:
|
||||
- name: Add Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: npm install
|
||||
run: |
|
||||
cd deltachat-jsonrpc/typescript
|
||||
npm install
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm install
|
||||
- name: Build TypeScript, run Rust tests, generate bindings
|
||||
run: |
|
||||
cd deltachat-jsonrpc/typescript
|
||||
npm run build
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm run build
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
cd deltachat-jsonrpc/typescript
|
||||
npm run test
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: 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
|
||||
working-directory: deltachat-jsonrpc
|
||||
run: cargo build --bin deltachat-jsonrpc-server --features webserver
|
||||
- name: Run linter
|
||||
run: |
|
||||
cd deltachat-jsonrpc/typescript
|
||||
npm run prettier:check
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm run prettier:check
|
||||
|
||||
32
.github/workflows/node-delete-preview.yml
vendored
32
.github/workflows/node-delete-preview.yml
vendored
@@ -1,32 +0,0 @@
|
||||
# documentation: https://github.com/deltachat/sysadmin/tree/master/download.delta.chat
|
||||
name: Delete node PR previews
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
delete:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Get Pullrequest 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/"
|
||||
45
.github/workflows/node-docs.yml
vendored
45
.github/workflows/node-docs.yml
vendored
@@ -1,3 +1,8 @@
|
||||
# GitHub Actions workflow to build
|
||||
# Node.js bindings documentation
|
||||
# and upload it to the web server.
|
||||
# Built documentation is available at <https://js.delta.chat/>
|
||||
|
||||
name: Generate & upload node.js documentation
|
||||
|
||||
on:
|
||||
@@ -9,26 +14,26 @@ jobs:
|
||||
generate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v3
|
||||
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
|
||||
working-directory: node
|
||||
run: |
|
||||
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/"
|
||||
|
||||
109
.github/workflows/node-package.yml
vendored
109
.github/workflows/node-package.yml
vendored
@@ -1,11 +1,10 @@
|
||||
name: 'node.js build'
|
||||
name: "node.js build"
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
- '!py-*'
|
||||
|
||||
- "*"
|
||||
- "!py-*"
|
||||
|
||||
jobs:
|
||||
prebuild:
|
||||
@@ -13,13 +12,13 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-18.04, macos-latest, windows-latest]
|
||||
os: [macos-latest, windows-latest]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: "16"
|
||||
- name: System info
|
||||
run: |
|
||||
rustc -vV
|
||||
@@ -47,13 +46,12 @@ jobs:
|
||||
|
||||
- name: Install dependencies & build
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd node
|
||||
npm install --verbose
|
||||
working-directory: node
|
||||
run: npm install --verbose
|
||||
|
||||
- name: Build Prebuild
|
||||
working-directory: node
|
||||
run: |
|
||||
cd node
|
||||
npm run prebuildify
|
||||
tar -zcvf "${{ matrix.os }}.tar.gz" -C prebuilds .
|
||||
|
||||
@@ -63,10 +61,81 @@ jobs:
|
||||
name: ${{ matrix.os }}
|
||||
path: node/${{ matrix.os }}.tar.gz
|
||||
|
||||
prebuild-linux:
|
||||
name: Prebuild Linux
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Build Linux prebuilds inside a container with old glibc for backwards compatibility.
|
||||
# Debian 10 contained glibc 2.28 at the time of the writing (2023-06-04): https://packages.debian.org/buster/libc6
|
||||
# Ubuntu 18.04 is at the End of Standard Support since June 2023, but it contains glibc 2.27,
|
||||
# so we are using it to support Ubuntu 18.04 setups that are still not upgraded.
|
||||
container: ubuntu:18.04
|
||||
steps:
|
||||
# Working directory is owned by 1001:1001 by default.
|
||||
# Change it to our user.
|
||||
- name: Change working directory owner
|
||||
run: chown root:root .
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16"
|
||||
- run: apt-get update
|
||||
|
||||
# Python is needed for node-gyp
|
||||
- name: Install curl, python and compilers
|
||||
run: apt-get install -y curl build-essential python3
|
||||
- name: Install Rust
|
||||
run: |
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||
- name: System info
|
||||
run: |
|
||||
rustc -vV
|
||||
rustup -vV
|
||||
cargo -vV
|
||||
npm --version
|
||||
node --version
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
${{ env.APPDATA }}/npm-cache
|
||||
~/.npm
|
||||
key: ${{ matrix.os }}-node-${{ hashFiles('**/package.json') }}
|
||||
|
||||
- name: Cache cargo index
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ matrix.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}-2
|
||||
|
||||
- name: Install dependencies & build
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
working-directory: node
|
||||
run: npm install --verbose
|
||||
|
||||
- name: Build Prebuild
|
||||
working-directory: node
|
||||
run: |
|
||||
npm run prebuildify
|
||||
tar -zcvf "linux.tar.gz" -C prebuilds .
|
||||
|
||||
- name: Upload Prebuild
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: linux
|
||||
path: node/linux.tar.gz
|
||||
|
||||
pack-module:
|
||||
needs: prebuild
|
||||
needs: [prebuild, prebuild-linux]
|
||||
name: Package deltachat-node and upload to download.delta.chat
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install tree
|
||||
run: sudo apt install tree
|
||||
@@ -74,12 +143,12 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16'
|
||||
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,10 +166,10 @@ jobs:
|
||||
npm --version
|
||||
node --version
|
||||
echo $DELTACHAT_NODE_TAR_GZ
|
||||
- name: Download Ubuntu prebuild
|
||||
- name: Download Linux prebuild
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
name: ubuntu-18.04
|
||||
name: linux
|
||||
- name: Download macOS prebuild
|
||||
uses: actions/download-artifact@v1
|
||||
with:
|
||||
@@ -112,11 +181,11 @@ jobs:
|
||||
- shell: bash
|
||||
run: |
|
||||
mkdir node/prebuilds
|
||||
tar -xvzf ubuntu-18.04/ubuntu-18.04.tar.gz -C node/prebuilds
|
||||
tar -xvzf linux/linux.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
|
||||
rm -rf linux macos-latest windows-latest
|
||||
- name: Install dependencies without running scripts
|
||||
run: |
|
||||
npm install --ignore-scripts
|
||||
@@ -152,8 +221,8 @@ jobs:
|
||||
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 }}
|
||||
|
||||
40
.github/workflows/node-tests.yml
vendored
40
.github/workflows/node-tests.yml
vendored
@@ -1,11 +1,19 @@
|
||||
name: 'node.js tests'
|
||||
# GitHub Actions workflow
|
||||
# to test Node.js bindings.
|
||||
|
||||
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:
|
||||
@@ -13,13 +21,13 @@ jobs:
|
||||
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@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version: "16"
|
||||
- name: System info
|
||||
run: |
|
||||
rustc -vV
|
||||
@@ -47,25 +55,13 @@ jobs:
|
||||
|
||||
- name: Install dependencies & build
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd node
|
||||
npm install --verbose
|
||||
working-directory: node
|
||||
run: npm install --verbose
|
||||
|
||||
- name: Test
|
||||
timeout-minutes: 10
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
cd node
|
||||
npm run test
|
||||
working-directory: node
|
||||
run: 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'
|
||||
NODE_OPTIONS: "--force-node-api-uncaught-exceptions-policy=true"
|
||||
|
||||
19
.github/workflows/repl.yml
vendored
19
.github/workflows/repl.yml
vendored
@@ -1,4 +1,5 @@
|
||||
# Manually triggered action to build a Windows repl.exe which users can
|
||||
# Manually triggered GitHub Actions workflow
|
||||
# to build a Windows repl.exe which users can
|
||||
# download to debug complex bugs.
|
||||
|
||||
name: Build Windows REPL .exe
|
||||
@@ -11,13 +12,13 @@ jobs:
|
||||
name: Build REPL example
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build
|
||||
run: cargo build -p deltachat-repl --features vendored
|
||||
- name: Build
|
||||
run: cargo build -p deltachat-repl --features vendored
|
||||
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: repl.exe
|
||||
path: 'target/debug/deltachat-repl.exe'
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: repl.exe
|
||||
path: "target/debug/deltachat-repl.exe"
|
||||
|
||||
26
.github/workflows/upload-docs.yml
vendored
26
.github/workflows/upload-docs.yml
vendored
@@ -8,20 +8,18 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- 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/"
|
||||
|
||||
30
.github/workflows/upload-ffi-docs.yml
vendored
30
.github/workflows/upload-ffi-docs.yml
vendored
@@ -1,3 +1,7 @@
|
||||
# GitHub Actions workflow
|
||||
# to build `deltachat_fii` crate documentation
|
||||
# and upload it to <https://cffi.delta.chat/>
|
||||
|
||||
name: Build & Deploy Documentation on cffi.delta.chat
|
||||
|
||||
on:
|
||||
@@ -8,20 +12,18 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- 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/"
|
||||
|
||||
- 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/"
|
||||
|
||||
546
CHANGELOG.md
546
CHANGELOG.md
@@ -1,5 +1,502 @@
|
||||
# Changelog
|
||||
|
||||
## [1.120.0] - 2023-08-28
|
||||
|
||||
### API-Changes
|
||||
|
||||
- jsonrpc: Add `resend_messages`.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Update async-imap to 0.9.1 to fix memory leak.
|
||||
- Delete messages from SMTP queue only on user demand ([#4579](https://github.com/deltachat/deltachat-core-rust/pull/4579)).
|
||||
- Do not send images without transparency as stickers ([#4611](https://github.com/deltachat/deltachat-core-rust/pull/4611)).
|
||||
- `prepare_msg_blob()`: do not use the image if it has Exif metadata but the image cannot be recoded.
|
||||
|
||||
### Refactor
|
||||
|
||||
- Hide accounts.rs constants from public API.
|
||||
- Hide pgp module from public API.
|
||||
|
||||
### Build system
|
||||
|
||||
- Update to Zig 0.11.0.
|
||||
- Update to Rust 1.72.0.
|
||||
|
||||
### CI
|
||||
|
||||
- Run on push to stable branch.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- python: Fix lint errors.
|
||||
- python: Fix `ruff` 0.0.286 warnings.
|
||||
- Fix beta clippy warnings.
|
||||
|
||||
## [1.119.1] - 2023-08-06
|
||||
|
||||
Bugfix release attempting to fix the [iOS build error](https://github.com/deltachat/deltachat-core-rust/issues/4610).
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Guess message viewtype from "application/octet-stream" attachment extension ([#4378](https://github.com/deltachat/deltachat-core-rust/pull/4378)).
|
||||
|
||||
### Fixes
|
||||
|
||||
- Update `xattr` from 1.0.0 to 1.0.1 to fix UnsupportedPlatformError import.
|
||||
|
||||
### Tests
|
||||
|
||||
- webxdc: Ensure unknown WebXDC update properties do not result in an error.
|
||||
|
||||
## [1.119.0] - 2023-08-03
|
||||
|
||||
### Fixes
|
||||
|
||||
- imap: Avoid IMAP move loops when DeltaChat folder is aliased.
|
||||
- imap: Do not resync IMAP after initial configuration.
|
||||
|
||||
- webxdc: Accept WebXDC updates in mailing lists.
|
||||
- webxdc: Base64-encode WebXDC updates to prevent corruption of large unencrypted WebXDC updates.
|
||||
- webxdc: Delete old webxdc status updates during housekeeping.
|
||||
|
||||
- Return valid MsgId from `receive_imf()` when the message is replaced.
|
||||
- Emit MsgsChanged event with correct chat id for replaced messages.
|
||||
|
||||
- deltachat-rpc-server: Update tokio-tar to fix backup import.
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- deltachat-rpc-client: Add `MSG_DELETED` constant.
|
||||
- Make `dc_msg_get_filename()` return the original attachment filename ([#4309](https://github.com/deltachat/deltachat-core-rust/pull/4309)).
|
||||
|
||||
### API-Changes
|
||||
|
||||
- deltachat-rpc-client: Add `Account.{import,export}_backup` methods.
|
||||
- deltachat-jsonrpc: Make `MessageObject.text` non-optional.
|
||||
|
||||
### Documentation
|
||||
|
||||
- Update default value for `show_emails` in `dc_set_config()` documentation.
|
||||
|
||||
### Refactor
|
||||
|
||||
- Improve IMAP logs.
|
||||
|
||||
### Tests
|
||||
|
||||
- Add basic import/export test for async python.
|
||||
- Add `test_webxdc_download_on_demand`.
|
||||
- Add tests for deletion of webxdc status-updates.
|
||||
|
||||
## [1.118.0] - 2023-07-07
|
||||
|
||||
### API-Changes
|
||||
|
||||
- [**breaking**] Remove `Contact::load_from_db()` in favor of `Contact::get_by_id()`.
|
||||
- Add `Contact::get_by_id_optional()` API.
|
||||
- [**breaking**] Make `Message.text` non-optional.
|
||||
- [**breaking**] Replace `message::get_msg_info()` with `MsgId.get_info()`.
|
||||
- Move `handle_mdn` and `handle_ndn` to mimeparser and make them private.
|
||||
Previously `handle_mdn` was erroneously exposed in the public API.
|
||||
- python: flatten the API of `deltachat` module.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Use different member added/removal messages locally and on the network.
|
||||
- Update tokio to 1.29.1 to fix core panic after sending 29 offline messages ([#4414](https://github.com/deltachat/deltachat-core-rust/issues/4414)).
|
||||
- Make SVG avatar image work on more platforms (use `xlink:href`).
|
||||
- Preserve indentation when converting plaintext to HTML.
|
||||
- Do not run simplify() on dehtml() output.
|
||||
- Rewrite member added/removed messages even if the change is not allowed PR ([#4529](https://github.com/deltachat/deltachat-core-rust/pull/4529)).
|
||||
|
||||
### Documentation
|
||||
|
||||
- Document how to regenerate Node.js constants before the release.
|
||||
|
||||
### Build system
|
||||
|
||||
- git-cliff: Do not fail if commit.footers is undefined.
|
||||
|
||||
### Other
|
||||
|
||||
- Dependency updates.
|
||||
- Update MPL 2.0 license text.
|
||||
- Add LICENSE file to deltachat-rpc-client.
|
||||
- deltachat-rpc-client: Add Trove classifiers.
|
||||
- python: Change bindings status to production/stable.
|
||||
|
||||
### Tests
|
||||
|
||||
- Add `make-python-testenv.sh` script.
|
||||
|
||||
## [1.117.0] - 2023-06-15
|
||||
|
||||
### Features
|
||||
|
||||
- New group membership update algorithm.
|
||||
|
||||
New algorithm improves group consistency
|
||||
in cases of missing messages,
|
||||
restored old backups and replies from classic MUAs.
|
||||
|
||||
- Add `DC_EVENT_MSG_DELETED` event.
|
||||
|
||||
This event notifies the UI about the message
|
||||
being deleted from the messagelist, e.g. when the message expires
|
||||
or the user deletes it.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Emit `DC_EVENT_MSGS_CHANGED` without IDs when the message expires.
|
||||
|
||||
Specifying msg IDs that cannot be loaded in the event payload
|
||||
results in an error when the UI tries to load the message.
|
||||
Instead, emit an event without IDs
|
||||
to make the UI reload the whole messagelist.
|
||||
|
||||
- Ignore address case when comparing the `To:` field to `Autocrypt-Gossip:`.
|
||||
|
||||
This bug resulted in failure to propagate verification
|
||||
if the contact list already contained a new verified group member
|
||||
with a non-lowercase address.
|
||||
|
||||
- dehtml: skip links with empty text.
|
||||
|
||||
Links like `<a href="https://delta.chat/"></a>` in HTML mails are now skipped
|
||||
instead of being converted to a link without a label like `[](https://delta.chat/)`.
|
||||
|
||||
- dehtml: Do not insert unnecessary newlines when parsing `<p>` tags.
|
||||
|
||||
- Update from yanked `libc` 0.2.145 to 0.2.146.
|
||||
- Update to async-imap 0.9.0 to remove deprecated `ouroboros` dependency.
|
||||
|
||||
### API-Changes
|
||||
|
||||
- Emit `DC_EVENT_MSGS_CHANGED` per chat when messages are deleted.
|
||||
|
||||
Previously a single event with zero chat ID was emitted.
|
||||
|
||||
- python: make `Contact.is_verified()` return bool.
|
||||
|
||||
- rust: add API endpoint `get_status_update` ([#4468](https://github.com/deltachat/deltachat-core-rust/pull/4468)).
|
||||
|
||||
- rust: make `WebxdcManifest` type public.
|
||||
|
||||
### Build system
|
||||
|
||||
- Use Rust 1.70.0 to compile deltachat-rpc-server releases.
|
||||
- Disable unused `brotli` feature `ffi-api` and use 1 codegen-units for release builds to reduce the size of the binaries.
|
||||
|
||||
### CI
|
||||
|
||||
- Run `cargo check` with musl libc.
|
||||
- concourse: Install devpi in a virtual environment.
|
||||
- Remove [mergeable](https://mergeable.us/) configuration.
|
||||
|
||||
### Documentation
|
||||
|
||||
- README: mark napi.rs bindings as experimental. CFFI bindings are not legacy and are the recommended Node.js bindings currently.
|
||||
- CONTRIBUTING: document how conventional commits interact with squash merges.
|
||||
|
||||
### Refactor
|
||||
|
||||
- Rename `MimeMessage.header` into `MimeMessage.headers`.
|
||||
|
||||
- Derive `Default` trait for `WebxdcManifest`.
|
||||
|
||||
### Tests
|
||||
|
||||
- Regression test for case-sensitive comparison of gossip header to contact address.
|
||||
- Multiple new group consistency tests in Rust.
|
||||
- python: Replace legacy `tmpdir` fixture with `tmp_path`.
|
||||
|
||||
## [1.116.0] - 2023-06-05
|
||||
|
||||
### API-Changes
|
||||
|
||||
- Add `dc_jsonrpc_blocking_call()`.
|
||||
|
||||
### Changes
|
||||
|
||||
- Generate OpenRPC definitions for JSON-RPC.
|
||||
- Add more context to message loading errors.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Build deltachat-node prebuilds on Debian 10.
|
||||
|
||||
### Documentation
|
||||
|
||||
- Document release process in `RELEASE.md`.
|
||||
- Add contributing guidelines `CONTRIBUTING.md`.
|
||||
- Update instructions for python devenv.
|
||||
- python: Document pytest fixtures.
|
||||
|
||||
### Tests
|
||||
|
||||
- python: Make `test_mdn_asymmetric` less flaky.
|
||||
- Make `test_group_with_removed_message_id` less flaky.
|
||||
- Add golden tests infrastructure ([#4395](https://github.com/deltachat/deltachat-core-rust/pull/4395)).
|
||||
|
||||
### Build system
|
||||
|
||||
- git-cliff: Changelog generation improvements.
|
||||
- `set_core_version.py`: Expect release date in the changelog.
|
||||
|
||||
### CI
|
||||
|
||||
- Require Python 3.8 for deltachat-rpc-client.
|
||||
- mergeable: Allow PR titles to start with "ci" and "build".
|
||||
- Remove incorrect comment.
|
||||
- dependabot: Use `chore` prefix for dependency updates.
|
||||
- Remove broken `node-delete-preview.yml` workflow.
|
||||
- Add top comments to GH Actions workflows.
|
||||
- Run node.js lint on Windows.
|
||||
- Update clippy to 1.70.0.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Remove release.toml.
|
||||
- gitattributes: Configure LF line endings for JavaScript files.
|
||||
- Update dependencies
|
||||
|
||||
## [1.112.10] - 2023-06-01
|
||||
|
||||
### Fixes
|
||||
|
||||
- Disable `fetch_existing_msgs` setting by default.
|
||||
- Update `h2` to fix RUSTSEC-2023-0034.
|
||||
|
||||
## [1.115.0] - 2023-05-12
|
||||
|
||||
### JSON-RPC API Changes
|
||||
|
||||
- Sort reactions in descending order ([#4388](https://github.com/deltachat/deltachat-core-rust/pull/4388)).
|
||||
- Add API to get reactions outside the message snapshot.
|
||||
- `get_chatlist_items_by_entries` now takes only chatids instead of `ChatListEntries`.
|
||||
- `get_chatlist_entries` now returns `Vec<u32>` of chatids instead of `ChatListEntries`.
|
||||
- `JSONRPCReactions.reactions` is now a `Vec<JSONRPCReaction>` with unique reactions and their count, sorted in descending order.
|
||||
- `Event`: `context_id` property is now called `contextId`.
|
||||
- Expand `MessageSearchResult`:
|
||||
- Always include `chat_name`(not an option anymore).
|
||||
- Add `author_id`, `chat_type`, `chat_color`, `is_chat_protected`, `is_chat_contact_request`, `is_chat_archived`.
|
||||
- `author_name` now contains the overridden sender name.
|
||||
- `ChatListItemFetchResult` gets new properties: `summary_preview_image`, `last_message_type` and `last_message_id`
|
||||
- New `MessageReadReceipt` type and `get_message_read_receipts(account_id, message_id)` jsonrpc method.
|
||||
|
||||
### API Changes
|
||||
|
||||
- New rust API `send_webxdc_status_update_struct` to send a `StatusUpdateItem`.
|
||||
- Add `get_msg_read_receipts(context, msg_id)` - get the contacts that send read receipts for a message.
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Build deltachat-rpc-server releases for x86\_64 macOS.
|
||||
- Generate changelogs using git-cliff ([#4393](https://github.com/deltachat/deltachat-core-rust/pull/4393), [#4396](https://github.com/deltachat/deltachat-core-rust/pull/4396)).
|
||||
- Improve SMTP logging.
|
||||
- Do not cut incoming text if "bot" config is set.
|
||||
|
||||
### Fixes
|
||||
|
||||
- JSON-RPC: typescript client: fix types of events in event emitter ([#4373](https://github.com/deltachat/deltachat-core-rust/pull/4373)).
|
||||
- Fetch at most 100 existing messages even if EXISTS was not received ([#4383](https://github.com/deltachat/deltachat-core-rust/pull/4383)).
|
||||
- Don't put a double dot at the end of error messages ([#4398](https://github.com/deltachat/deltachat-core-rust/pull/4398)).
|
||||
- Recreate `smtp` table with AUTOINCREMENT `id` ([#4390](https://github.com/deltachat/deltachat-core-rust/pull/4390)).
|
||||
- Do not return an error from `send_msg_to_smtp` if retry limit is exceeded.
|
||||
- Make the bots automatically accept group chat contact requests ([#4377](https://github.com/deltachat/deltachat-core-rust/pull/4377)).
|
||||
- Delete `smtp` rows when message sending is cancelled ([#4391](https://github.com/deltachat/deltachat-core-rust/pull/4391)).
|
||||
|
||||
### Refactor
|
||||
|
||||
- Iterate over `msg_ids` without .iter().
|
||||
|
||||
## [1.112.9] - 2023-05-12
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fetch at most 100 existing messages even if EXISTS was not received.
|
||||
- Delete `smtp` rows when message sending is cancelled.
|
||||
|
||||
### Changes
|
||||
|
||||
- Improve SMTP logging.
|
||||
|
||||
## [1.114.0] - 2023-04-24
|
||||
|
||||
### Changes
|
||||
- JSON-RPC: Use long polling instead of server-sent notifications to retrieve events.
|
||||
This better corresponds to JSON-RPC 2.0 server-client distinction
|
||||
and is expected to simplify writing new bindings
|
||||
because dispatching events can be done on higher level.
|
||||
- JSON-RPC: TS: Client now has a mandatory argument whether you want to start listening for events.
|
||||
|
||||
### Fixes
|
||||
- JSON-RPC: do not print to stdout on failure to find an account.
|
||||
|
||||
|
||||
## [1.113.0] - 2023-04-18
|
||||
|
||||
### Added
|
||||
- New JSON-RPC API `can_send()`.
|
||||
- New `dc_get_next_msgs()` and `dc_wait_next_msgs()` C APIs.
|
||||
New `get_next_msgs()` and `wait_next_msgs()` JSON-RPC API.
|
||||
These APIs can be used by bots to get all unprocessed messages
|
||||
in the order of their arrival and wait for them without relying on events.
|
||||
- New Python bindings API `Account.wait_next_incoming_message()`.
|
||||
- New Python bindings APIs `Message.is_from_self()` and `Message.is_from_device()`.
|
||||
|
||||
### Changes
|
||||
- Increase MSRV to 1.65.0. #4236
|
||||
- Remove upper limit on the attachment size. #4253
|
||||
- Update rPGP to 0.10.1. #4236
|
||||
- Compress HTML emails stored in the `mime_headers` column of the database.
|
||||
- Strip BIDI characters in system messages, files, group names and contact names. #3479
|
||||
- Use release date instead of the provider database update date in `maybe_add_time_based_warnings()`.
|
||||
- Gracefully terminate `deltachat-rpc-server` on Ctrl+C (`SIGINT`), `SIGTERM` and EOF.
|
||||
- Async Python API `get_fresh_messages_in_arrival_order()` is deprecated
|
||||
in favor of `get_next_msgs()` and `wait_next_msgs()`.
|
||||
- Remove metadata from avatars and JPEG images before sending. #4037
|
||||
- Recode PNG and other supported image formats to JPEG if they are > 500K in size. #4037
|
||||
|
||||
### Fixes
|
||||
- Don't let blocking be bypassed using groups. #4316
|
||||
- Show a warning if quota list is empty. #4261
|
||||
- Do not reset status on other devices when sending signed reaction messages. #3692
|
||||
- Update `accounts.toml` atomically.
|
||||
- Fix python bindings README documentation on installing the bindings from source.
|
||||
- Remove confusing log line "ignoring unsolicited response Recent(…)". #3934
|
||||
|
||||
## [1.112.8] - 2023-04-20
|
||||
|
||||
### Changes
|
||||
- Add `get_http_response` JSON-RPC API.
|
||||
- Add C API to get HTTP responses.
|
||||
|
||||
## [1.112.7] - 2023-04-17
|
||||
|
||||
### Fixes
|
||||
|
||||
- Updated `async-imap` to v0.8.0 to fix erroneous EOF detection in long IMAP responses.
|
||||
|
||||
## [1.112.6] - 2023-04-04
|
||||
|
||||
### Changes
|
||||
|
||||
- Add a device message after backup transfer #4301
|
||||
|
||||
### Fixed
|
||||
|
||||
- Updated `iroh` from 0.4.0 to 0.4.1 to fix transfer of large accounts with many blob files.
|
||||
|
||||
## [1.112.5] - 2023-04-02
|
||||
|
||||
### Fixes
|
||||
|
||||
- Run SQL database migrations after receiving a backup from the network. #4287
|
||||
|
||||
## [1.112.4] - 2023-03-31
|
||||
|
||||
### Fixes
|
||||
- Fix call to `auditwheel` in `scripts/run_all.sh`.
|
||||
|
||||
## [1.112.3] - 2023-03-30
|
||||
|
||||
### Fixes
|
||||
- `transfer::get_backup` now frees ongoing process when cancelled. #4249
|
||||
|
||||
## [1.112.2] - 2023-03-30
|
||||
|
||||
### Changes
|
||||
- Update iroh, remove `default-net` from `[patch.crates-io]` section.
|
||||
- transfer backup: Connect to multiple provider addresses concurrently. This should speed up connection time significantly on the getter side. #4240
|
||||
- Make sure BackupProvider is cancelled on drop (or `dc_backup_provider_unref`). The BackupProvider will now always finish with an IMEX event of 1000 or 0, previously it would sometimes finished with 1000 (success) when it really was 0 (failure). #4242
|
||||
|
||||
### Fixes
|
||||
- Do not return media from trashed messages in the "All media" view. #4247
|
||||
|
||||
## [1.112.1] - 2023-03-27
|
||||
|
||||
### Changes
|
||||
- Add support for `--version` argument to `deltachat-rpc-server`. #4224
|
||||
It can be used to check the installed version without starting the server.
|
||||
|
||||
### Fixes
|
||||
- deltachat-rpc-client: fix bug in `Chat.send_message()`: invalid `MessageData` field `quotedMsg` instead of `quotedMsgId`
|
||||
- `receive_imf`: Mark special messages as seen. Exactly: delivery reports, webxdc status updates. #4230
|
||||
|
||||
|
||||
## [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
|
||||
@@ -29,6 +526,7 @@
|
||||
- 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
|
||||
@@ -60,7 +558,7 @@
|
||||
|
||||
### Fixes
|
||||
- Securejoin: Fix adding and handling Autocrypt-Gossip headers #3914
|
||||
- fix verifier-by addr was empty string intead of None #3961
|
||||
- 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
|
||||
@@ -86,7 +584,6 @@
|
||||
- Do not treat invalid email addresses as an exception #3942
|
||||
- Add timeouts to HTTP requests #3948
|
||||
|
||||
|
||||
## 1.105.0
|
||||
|
||||
### Changes
|
||||
@@ -161,7 +658,7 @@
|
||||
- jsonrpc: Add async Python client #3734
|
||||
|
||||
### Fixes
|
||||
- Make sure malformed messsages will never block receiving further messages anymore #3771
|
||||
- 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
|
||||
@@ -172,7 +669,6 @@
|
||||
- Disable read timeout during IMAP IDLE #3826
|
||||
- Bots automatically accept mailing lists #3831
|
||||
|
||||
|
||||
## 1.102.0
|
||||
|
||||
### Changes
|
||||
@@ -313,7 +809,7 @@
|
||||
- `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 prefered way of using the message list.
|
||||
- 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`
|
||||
@@ -339,7 +835,7 @@
|
||||
- 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
|
||||
|
||||
@@ -416,7 +912,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
|
||||
@@ -639,7 +1135,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
|
||||
|
||||
@@ -727,7 +1223,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
|
||||
@@ -780,7 +1276,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
|
||||
@@ -2002,7 +2498,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
|
||||
|
||||
@@ -2081,7 +2577,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
|
||||
@@ -2119,7 +2615,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
|
||||
@@ -2138,7 +2634,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
|
||||
@@ -2246,3 +2742,25 @@
|
||||
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
|
||||
|
||||
[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
|
||||
[1.112.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.0...v1.112.1
|
||||
[1.112.2]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.1...v1.112.2
|
||||
[1.112.3]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.2...v1.112.3
|
||||
[1.112.4]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.3...v1.112.4
|
||||
[1.112.5]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.4...v1.112.5
|
||||
[1.112.6]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.5...v1.112.6
|
||||
[1.112.7]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.6...v1.112.7
|
||||
[1.112.8]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.7...v1.112.8
|
||||
[1.112.9]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.8...v1.112.9
|
||||
[1.112.10]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.9...v1.112.10
|
||||
[1.113.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.9...v1.113.0
|
||||
[1.114.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.113.0...v1.114.0
|
||||
[1.115.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.114.0...v1.115.0
|
||||
[1.116.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.115.0...v1.116.0
|
||||
[1.117.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.116.0...v1.117.0
|
||||
[1.118.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.117.0...v1.118.0
|
||||
[1.119.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.118.0...v1.119.0
|
||||
[1.119.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.119.0...v1.119.1
|
||||
[1.120.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.119.1...v1.120.0
|
||||
|
||||
92
CONTRIBUTING.md
Normal file
92
CONTRIBUTING.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# Contributing guidelines
|
||||
|
||||
## Reporting bugs
|
||||
|
||||
If you found a bug, [report it on GitHub](https://github.com/deltachat/deltachat-core-rust/issues).
|
||||
If the bug you found is specific to
|
||||
[Android](https://github.com/deltachat/deltachat-android/issues),
|
||||
[iOS](https://github.com/deltachat/deltachat-ios/issues) or
|
||||
[Desktop](https://github.com/deltachat/deltachat-desktop/issues),
|
||||
report it to the corresponding repository.
|
||||
|
||||
## Proposing features
|
||||
|
||||
If you have a feature request, create a new topic on the [forum](https://support.delta.chat/).
|
||||
|
||||
## Contributing code
|
||||
|
||||
If you want to contribute a code, [open a Pull Request](https://github.com/deltachat/deltachat-core-rust/pulls).
|
||||
|
||||
If you have write access to the repository,
|
||||
push a branch named `<username>/<feature>`
|
||||
so it is clear who is responsible for the branch,
|
||||
and open a PR proposing to merge the change.
|
||||
Otherwise fork the repository and create a branch in your fork.
|
||||
|
||||
You can find the list of good first issues
|
||||
and a link to this guide
|
||||
on the contributing page: <https://github.com/deltachat/deltachat-core-rust/contribute>
|
||||
|
||||
### Coding conventions
|
||||
|
||||
We format the code using `rustfmt`. Run `cargo fmt` prior to committing the code.
|
||||
Run `scripts/clippy.sh` to check the code for common mistakes with [Clippy].
|
||||
|
||||
Commit messages follow the [Conventional Commits] notation.
|
||||
We use [git-cliff] to generate the changelog from commit messages before the release.
|
||||
|
||||
With **`git cliff --unreleased`**, you can check how the changelog entry for your commit will look.
|
||||
|
||||
The following prefix types are used:
|
||||
- `feat`: Features, e.g. "feat: Pause IO for BackupProvider". If you are unsure what's the category of your commit, you can often just use `feat`.
|
||||
- `fix`: Bug fixes, e.g. "fix: delete `smtp` rows when message sending is cancelled"
|
||||
- `api`: API changes, e.g. "api(rust): add `get_msg_read_receipts(context, msg_id)`"
|
||||
- `refactor`: Refactorings, e.g. "refactor: iterate over `msg_ids` without `.iter()`"
|
||||
- `perf`: Performance improvements, e.g. "perf: improve SQLite performance with `PRAGMA synchronous=normal`"
|
||||
- `test`: Test changes and improvements to the testing framework.
|
||||
- `build`: Build system and tool configuration changes, e.g. "build(git-cliff): put "ci" commits into "CI" section of changelog"
|
||||
- `ci`: CI configuration changes, e.g. "ci: limit artifact retention time for `libdeltachat.a` to 1 day"
|
||||
- `docs`: Documentation changes, e.g. "docs: add contributing guidelines"
|
||||
- `chore`: miscellaneous tasks, e.g. "chore: add `.DS_Store` to `.gitignore`"
|
||||
|
||||
Release preparation commits are marked as "chore(release): prepare for vX.Y.Z".
|
||||
|
||||
If you intend to squash merge the PR from the web interface,
|
||||
make sure the PR title follows the conventional commits notation
|
||||
as it will end up being a commit title.
|
||||
Otherwise make sure each commit title follows the conventional commit notation.
|
||||
|
||||
#### Breaking Changes
|
||||
|
||||
Use a `!` to mark breaking changes, e.g. "api!: Remove `dc_chat_can_send`".
|
||||
|
||||
Alternatively, breaking changes can go into the commit description, e.g.:
|
||||
|
||||
```
|
||||
fix: Fix race condition and db corruption when a message was received during backup
|
||||
|
||||
BREAKING CHANGE: You have to call `dc_stop_io()`/`dc_start_io()` before/after `dc_imex(DC_IMEX_EXPORT_BACKUP)`
|
||||
```
|
||||
|
||||
#### Multiple Changes in one PR
|
||||
|
||||
If you have multiple changes in one PR, create multiple conventional commits, and then do a rebase merge. Otherwise, you should usually do a squash merge.
|
||||
|
||||
[Clippy]: https://doc.rust-lang.org/clippy/
|
||||
[Conventional Commits]: https://www.conventionalcommits.org/
|
||||
[git-cliff]: https://git-cliff.org/
|
||||
|
||||
### Reviewing
|
||||
|
||||
Once a PR has an approval and passes CI, it can be merged.
|
||||
|
||||
PRs from a branch created in the main repository, i.e. authored by those who have write access, are merged by their authors.
|
||||
This is to ensure that PRs are merged as intended by the author,
|
||||
e.g. as a squash merge, by rebasing from the web interface or manually from the command line.
|
||||
|
||||
If you do not have access to the repository and created a PR from a fork,
|
||||
ask the maintainers to merge the PR and say how it should be merged.
|
||||
|
||||
## Other ways to contribute
|
||||
|
||||
For other ways to contribute, refer to the [website](https://delta.chat/en/contribute).
|
||||
3370
Cargo.lock
generated
3370
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
70
Cargo.toml
70
Cargo.toml
@@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.109.0"
|
||||
version = "1.120.0"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.63"
|
||||
rust-version = "1.65"
|
||||
|
||||
[profile.dev]
|
||||
debug = 0
|
||||
@@ -17,11 +17,17 @@ opt-level = 0
|
||||
# This does not apply to crates in the workspace.
|
||||
# <https://doc.rust-lang.org/cargo/reference/profiles.html#overrides>
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
opt-level = "z"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
panic = 'abort'
|
||||
opt-level = "z"
|
||||
codegen-units = 1
|
||||
|
||||
[patch.crates-io]
|
||||
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" }
|
||||
@@ -30,42 +36,43 @@ ratelimit = { path = "./deltachat-ratelimit" }
|
||||
|
||||
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.8", default-features = false, features = ["runtime-tokio"] }
|
||||
async_zip = { version = "0.0.9", default-features = false, features = ["deflate"] }
|
||||
async-imap = { version = "0.9.1", default-features = false, features = ["runtime-tokio"] }
|
||||
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.12", default-features = false, features = ["deflate", "fs"] }
|
||||
backtrace = "0.3"
|
||||
base64 = "0.21"
|
||||
bitflags = "1.3"
|
||||
brotli = { version = "3.3", default-features=false, features = ["std"] }
|
||||
chrono = { version = "0.4", default-features=false, features = ["clock", "std"] }
|
||||
crossbeam-queue = "0.3"
|
||||
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"
|
||||
futures-lite = "1.13.0"
|
||||
hex = "0.4.0"
|
||||
humansize = "2"
|
||||
image = { version = "0.24.5", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||
image = { version = "0.24.6", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||
iroh = { version = "0.4.1", default-features = false }
|
||||
kamadak-exif = "0.5"
|
||||
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
|
||||
libc = "0.2"
|
||||
mailparse = "0.14"
|
||||
native-tls = "0.2"
|
||||
num_cpus = "1.15"
|
||||
num-derive = "0.3"
|
||||
mime = "0.3.17"
|
||||
num_cpus = "1.16"
|
||||
num-derive = "0.4"
|
||||
num-traits = "0.2"
|
||||
once_cell = "1.17.0"
|
||||
percent-encoding = "2.2"
|
||||
pgp = { version = "0.9", default-features = false }
|
||||
pretty_env_logger = { version = "0.4", optional = true }
|
||||
once_cell = "1.18.0"
|
||||
percent-encoding = "2.3"
|
||||
parking_lot = "0.12"
|
||||
pgp = { version = "0.10", default-features = false }
|
||||
pretty_env_logger = { version = "0.5", optional = true }
|
||||
qrcodegen = "1.7.0"
|
||||
quick-xml = "0.27"
|
||||
quick-xml = "0.29"
|
||||
rand = "0.8"
|
||||
regex = "1.7"
|
||||
reqwest = { version = "0.11.14", features = ["json"] }
|
||||
rusqlite = { version = "0.28", features = ["sqlcipher"] }
|
||||
regex = "1.8"
|
||||
reqwest = { version = "0.11.18", features = ["json"] }
|
||||
rusqlite = { version = "0.29", features = ["sqlcipher"] }
|
||||
rust-hsluv = "0.1"
|
||||
sanitize-filename = "0.4"
|
||||
serde_json = "1.0"
|
||||
@@ -73,15 +80,16 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
sha-1 = "0.10"
|
||||
sha2 = "0.10"
|
||||
smallvec = "1"
|
||||
strum = "0.24"
|
||||
strum_macros = "0.24"
|
||||
strum = "0.25"
|
||||
strum_macros = "0.25"
|
||||
tagger = "4.3.4"
|
||||
textwrap = "0.16.0"
|
||||
thiserror = "1"
|
||||
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"] }
|
||||
tokio-io-timeout = "1.2.0"
|
||||
tokio-stream = { version = "0.1.14", features = ["fs"] }
|
||||
tokio-tar = { version = "0.3" } # TODO: integrate tokio into async-tar
|
||||
tokio-util = "0.7.8"
|
||||
toml = "0.7"
|
||||
trust-dns-resolver = "0.22"
|
||||
url = "2"
|
||||
@@ -89,13 +97,15 @@ uuid = { version = "1", features = ["serde", "v4"] }
|
||||
|
||||
[dev-dependencies]
|
||||
ansi_term = "0.12.0"
|
||||
criterion = { version = "0.4.0", features = ["async_tokio"] }
|
||||
futures-lite = "1.12"
|
||||
criterion = { version = "0.5.1", features = ["async_tokio"] }
|
||||
futures-lite = "1.13"
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.4"
|
||||
pretty_env_logger = "0.5"
|
||||
proptest = { version = "1", default-features = false, features = ["std"] }
|
||||
tempfile = "3"
|
||||
testdir = "0.8.0"
|
||||
tokio = { version = "1", features = ["parking_lot", "rt-multi-thread", "macros"] }
|
||||
pretty_assertions = "1.3.0"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -361,7 +361,7 @@ Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
|
||||
12
README.md
12
README.md
@@ -19,7 +19,7 @@ $ curl https://sh.rustup.rs -sSf | sh
|
||||
Compile and run Delta Chat Core command line utility, using `cargo`:
|
||||
|
||||
```
|
||||
$ RUST_LOG=repl=info cargo run -p deltachat-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.
|
||||
|
||||
@@ -113,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
|
||||
@@ -167,10 +167,12 @@ Language bindings are available for:
|
||||
|
||||
- **C** \[[📂 source](./deltachat-ffi) | [📚 docs](https://c.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)\]
|
||||
- over cffi: \[[📂 source](./node) | [📦 npm](https://www.npmjs.com/package/deltachat-node) | [📚 docs](https://js.delta.chat)\]
|
||||
- over jsonrpc built with napi.rs (experimental): \[[📂 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**[^1] \[[📂 source](https://github.com/deltachat/go-deltachat/)\]
|
||||
- **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)
|
||||
|
||||
|
||||
21
RELEASE.md
Normal file
21
RELEASE.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Releasing a new version of DeltaChat core
|
||||
|
||||
For example, to release version 1.116.0 of the core, do the following steps.
|
||||
|
||||
1. Resolve all [blocker issues](https://github.com/deltachat/deltachat-core-rust/labels/blocker).
|
||||
|
||||
2. Run `npm run build:core:constants` in the root of the repository
|
||||
and commit generated `node/constants.js`, `node/events.js` and `node/lib/constants.js`.
|
||||
|
||||
3. Update the changelog: `git cliff --unreleased --tag 1.116.0 --prepend CHANGELOG.md` or `git cliff -u -t 1.116.0 -p CHANGELOG.md`.
|
||||
|
||||
4. Update the version by running `scripts/set_core_version.py 1.116.0`.
|
||||
|
||||
5. Commit the changes as `chore(release): prepare for 1.116.0`.
|
||||
Optionally, use a separate branch like `prep-1.116.0` for this commit and open a PR for review.
|
||||
|
||||
6. Tag the release: `git tag -a v1.116.0`.
|
||||
|
||||
7. Push the release tag: `git push origin v1.116.0`.
|
||||
|
||||
8. Create a GitHub release: `gh release create v1.116.0 -n ''`.
|
||||
79
cliff.toml
Normal file
79
cliff.toml
Normal file
@@ -0,0 +1,79 @@
|
||||
# configuration file for git-cliff
|
||||
# see https://git-cliff.org/docs/configuration/
|
||||
|
||||
|
||||
[git]
|
||||
# parse the commits based on https://www.conventionalcommits.org
|
||||
conventional_commits = true
|
||||
# filter out the commits that are not conventional
|
||||
filter_unconventional = false
|
||||
# process each line of a commit as an individual commit
|
||||
split_commits = false
|
||||
# regex for preprocessing the commit messages
|
||||
commit_preprocessors = [
|
||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/deltachat/deltachat-core-rust/pull/${2}))"}, # replace pull request / issue numbers
|
||||
]
|
||||
# regex for parsing and grouping commits
|
||||
commit_parsers = [
|
||||
{ message = "^feat", group = "Features / Changes"},
|
||||
{ message = "^fix", group = "Fixes"},
|
||||
{ message = "^api", group = "API-Changes" },
|
||||
{ message = "^refactor", group = "Refactor"},
|
||||
{ message = "^perf", group = "Performance"},
|
||||
{ message = "^test", group = "Tests"},
|
||||
{ message = "^style", group = "Styling"},
|
||||
{ message = "^chore\\(release\\): prepare for", skip = true},
|
||||
{ message = "^chore", group = "Miscellaneous Tasks"},
|
||||
{ message = "^build", group = "Build system"},
|
||||
{ message = "^docs", group = "Documentation"},
|
||||
{ message = "^ci", group = "CI"},
|
||||
{ message = ".*", group = "Other"},
|
||||
# { body = ".*security", group = "Security"},
|
||||
]
|
||||
# protect breaking changes from being skipped due to matching a skipping commit_parser
|
||||
protect_breaking_commits = true
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
filter_commits = true
|
||||
# glob pattern for matching git tags
|
||||
tag_pattern = "v[0-9]*"
|
||||
# regex for skipping tags
|
||||
#skip_tags = "v0.1.0-beta.1"
|
||||
# regex for ignoring tags
|
||||
ignore_tags = ""
|
||||
# sort the tags topologically
|
||||
topo_order = false
|
||||
# sort the commits inside sections by oldest/newest order
|
||||
sort_commits = "oldest"
|
||||
# limit the number of commits included in the changelog.
|
||||
# limit_commits = 42
|
||||
|
||||
|
||||
[changelog]
|
||||
# changelog header
|
||||
header = """
|
||||
# Changelog\n
|
||||
"""
|
||||
# template for the changelog body
|
||||
# https://tera.netlify.app/docs/#introduction
|
||||
body = """
|
||||
{% if version %}\
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% else %}\
|
||||
## [unreleased]
|
||||
{% endif %}\
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {% if commit.breaking %}[**breaking**] {% endif %}\
|
||||
{% if commit.scope %}{{ commit.scope }}: {% endif %}\
|
||||
{{ commit.message | upper_first }}.\
|
||||
{% if commit.footers is defined %}\
|
||||
{% for footer in commit.footers %}{% if 'BREAKING CHANGE' in footer.token %}
|
||||
{% raw %} {% endraw %}- {{ footer.value }}\
|
||||
{% endif %}{% endfor %}\
|
||||
{% endif%}\
|
||||
{% endfor %}
|
||||
{% endfor %}\n
|
||||
"""
|
||||
# remove the leading and trailing whitespace from the template
|
||||
trim = true
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.109.0"
|
||||
version = "1.120.0"
|
||||
description = "Deltachat FFI"
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
@@ -17,17 +17,18 @@ 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.17.0"
|
||||
rand = "0.8"
|
||||
once_cell = "1.18.0"
|
||||
yerpc = { version = "0.5.1", features = ["anyhow_expose"] }
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
vendored = ["deltachat/vendored"]
|
||||
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;
|
||||
|
||||
@@ -24,6 +24,8 @@ 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;
|
||||
typedef struct _dc_http_response dc_http_response_t;
|
||||
|
||||
// Alias for backwards compatibility, use dc_event_emitter_t instead.
|
||||
typedef struct _dc_event_emitter dc_accounts_event_emitter_t;
|
||||
@@ -71,7 +73,6 @@ typedef struct _dc_event_emitter dc_accounts_event_emitter_t;
|
||||
*
|
||||
* The example above uses "pthreads",
|
||||
* however, you can also use anything else for thread handling.
|
||||
* All deltachat-core functions, unless stated otherwise, are thread-safe.
|
||||
*
|
||||
* Now you can **configure the context:**
|
||||
*
|
||||
@@ -141,6 +142,72 @@ typedef struct _dc_event_emitter dc_accounts_event_emitter_t;
|
||||
* ~~~
|
||||
*
|
||||
*
|
||||
* ## Thread safety
|
||||
*
|
||||
* All deltachat-core functions, unless stated otherwise, are thread-safe.
|
||||
* In particular, it is safe to pass the same dc_context_t pointer
|
||||
* to multiple functions running concurrently in different threads.
|
||||
*
|
||||
* All the functions are guaranteed not to use the reference passed to them
|
||||
* after returning. If the function spawns a long-running process,
|
||||
* such as dc_configure() or dc_imex(), it will ensure that the objects
|
||||
* passed to them are not deallocated as long as they are needed.
|
||||
* For example, it is safe to call dc_imex(context, ...) and
|
||||
* call dc_context_unref(context) immediately after return from dc_imex().
|
||||
* It is however **not safe** to call dc_context_unref(context) concurrently
|
||||
* until dc_imex() returns, because dc_imex() may have not increased
|
||||
* the reference count of dc_context_t yet.
|
||||
*
|
||||
* This means that the context may be still in use after
|
||||
* dc_context_unref() call.
|
||||
* For example, it is possible to start the import/export process,
|
||||
* call dc_context_unref(context) immediately after
|
||||
* and observe #DC_EVENT_IMEX_PROGRESS events via the event emitter.
|
||||
* Once dc_get_next_event() returns NULL,
|
||||
* it is safe to terminate the application.
|
||||
*
|
||||
* It is recommended to create dc_context_t in the main thread
|
||||
* and only call dc_context_unref() once other threads that may use it,
|
||||
* such as the event loop thread, are terminated.
|
||||
* Common mistake is to use dc_context_unref() as a way
|
||||
* to cause dc_get_next_event() return NULL and terminate event loop this way.
|
||||
* If event loop thread is inside a function taking dc_context_t
|
||||
* as an argument at the moment dc_context_unref() is called on the main thread,
|
||||
* the behavior is undefined.
|
||||
*
|
||||
* Recommended way to safely terminate event loop
|
||||
* and shutdown the application is
|
||||
* to use a boolean variable
|
||||
* indicating that the event loop should stop
|
||||
* and check it in the event loop thread
|
||||
* every time before calling dc_get_next_event().
|
||||
* To terminate the event loop, main thread should:
|
||||
* 1. Notify background threads,
|
||||
* such as event loop (blocking in dc_get_next_event())
|
||||
* and message processing loop (blocking in dc_wait_next_msgs()),
|
||||
* that they should terminate by atomically setting the
|
||||
* boolean flag in the memory
|
||||
* shared between the main thread and background loop threads.
|
||||
* 2. Call dc_stop_io() or dc_accounts_stop_io(), depending
|
||||
* on whether a single account or account manager is used.
|
||||
* Stopping I/O is guaranteed to emit at least one event
|
||||
* and interrupt the event loop even if it was blocked on dc_get_next_event().
|
||||
* Stopping I/O is guaranteed to interrupt a single dc_wait_next_msgs().
|
||||
* 3. Wait until the event loop thread notices the flag,
|
||||
* exits the event loop and terminates.
|
||||
* 4. Call dc_context_unref() or dc_accounts_unref().
|
||||
* 5. Keep calling dc_get_next_event() in a loop until it returns NULL,
|
||||
* indicating that the contexts are deallocated.
|
||||
* 6. Terminate the application.
|
||||
*
|
||||
* When using C API via FFI in runtimes that use automatic memory management,
|
||||
* such as CPython, JVM or Node.js, take care to ensure the correct
|
||||
* shutdown order and avoid calling dc_context_unref() or dc_accounts_unref()
|
||||
* on the objects still in use in other threads,
|
||||
* e.g. by keeping a reference to the wrapper object.
|
||||
* The details depend on the runtime being used.
|
||||
*
|
||||
*
|
||||
* ## Class reference
|
||||
*
|
||||
* For a class reference, see the "Classes" link atop.
|
||||
@@ -234,6 +301,19 @@ dc_context_t* dc_context_new_closed (const char* dbfile);
|
||||
int dc_context_open (dc_context_t *context, const char* passphrase);
|
||||
|
||||
|
||||
/**
|
||||
* Changes the passphrase on the open database.
|
||||
* Existing database must already be encrypted and the passphrase cannot be NULL or empty.
|
||||
* It is impossible to encrypt unencrypted database with this method and vice versa.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object.
|
||||
* @param passphrase The new passphrase.
|
||||
* @return 1 on success, 0 on error.
|
||||
*/
|
||||
int dc_context_change_passphrase (dc_context_t* context, const char* passphrase);
|
||||
|
||||
|
||||
/**
|
||||
* Returns 1 if database is open.
|
||||
*
|
||||
@@ -353,11 +433,11 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* 0=watch all folders normally (default)
|
||||
* changes require restarting IO by calling dc_stop_io() and then dc_start_io().
|
||||
* - `show_emails` = DC_SHOW_EMAILS_OFF (0)=
|
||||
* show direct replies to chats only (default),
|
||||
* show direct replies to chats only,
|
||||
* DC_SHOW_EMAILS_ACCEPTED_CONTACTS (1)=
|
||||
* also show all mails of confirmed contacts,
|
||||
* DC_SHOW_EMAILS_ALL (2)=
|
||||
* also show mails of unconfirmed contacts.
|
||||
* also show mails of unconfirmed contacts (default).
|
||||
* - `key_gen_type` = DC_KEY_GEN_DEFAULT (0)=
|
||||
* generate recommended key type (default),
|
||||
* DC_KEY_GEN_RSA2048 (1)=
|
||||
@@ -394,8 +474,19 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* If no type is prefixed, the videochat is handled completely in a browser.
|
||||
* - `bot` = Set to "1" if this is a bot.
|
||||
* Prevents adding the "Device messages" and "Saved messages" chats,
|
||||
* adds Auto-Submitted header to outgoing messages
|
||||
* and accepts contact requests automatically (calling dc_accept_chat() is not needed for bots).
|
||||
* adds Auto-Submitted header to outgoing messages,
|
||||
* accepts contact requests automatically (calling dc_accept_chat() is not needed for bots)
|
||||
* and does not cut large incoming text messages.
|
||||
* - `last_msg_id` = database ID of the last message processed by the bot.
|
||||
* This ID and IDs below it are guaranteed not to be returned
|
||||
* by dc_get_next_msgs() and dc_wait_next_msgs().
|
||||
* The value is updated automatically
|
||||
* when dc_markseen_msgs() is called,
|
||||
* but the bot can also set it manually if it processed
|
||||
* the message but does not want to mark it as seen.
|
||||
* For most bots calling `dc_markseen_msgs()` is the
|
||||
* recommended way to update this value
|
||||
* even for self-sent messages.
|
||||
* - `fetch_existing_msgs` = 1=fetch most recent existing messages on configure (default),
|
||||
* 0=do not fetch existing messages on configure.
|
||||
* In both cases, existing recipients are added to the contact database.
|
||||
@@ -612,7 +703,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.
|
||||
*
|
||||
@@ -869,7 +960,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.
|
||||
@@ -880,7 +971,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.
|
||||
@@ -909,7 +1000,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.
|
||||
@@ -926,7 +1017,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.
|
||||
@@ -937,7 +1028,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.
|
||||
@@ -1146,7 +1237,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);
|
||||
* }
|
||||
@@ -1243,6 +1334,20 @@ int dc_get_msg_cnt (dc_context_t* context, uint32_t ch
|
||||
int dc_get_fresh_msg_cnt (dc_context_t* context, uint32_t chat_id);
|
||||
|
||||
|
||||
/**
|
||||
* Returns a list of similar chats.
|
||||
*
|
||||
* @warning This is an experimental API which may change or be removed in the future.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object as returned from dc_context_new().
|
||||
* @param chat_id The ID of the chat for which to find similar chats.
|
||||
* @return The list of similar chats.
|
||||
* On errors, NULL is returned.
|
||||
* Must be freed using dc_chatlist_unref() when no longer used.
|
||||
*/
|
||||
dc_chatlist_t* dc_get_similar_chatlist (dc_context_t* context, uint32_t chat_id);
|
||||
|
||||
|
||||
/**
|
||||
* Estimate the number of messages that will be deleted
|
||||
@@ -1282,6 +1387,56 @@ int dc_estimate_deletion_cnt (dc_context_t* context, int from_ser
|
||||
dc_array_t* dc_get_fresh_msgs (dc_context_t* context);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the message IDs of all messages of any chat
|
||||
* with a database ID higher than `last_msg_id` config value.
|
||||
*
|
||||
* This function is intended for use by bots.
|
||||
* Self-sent messages, device messages,
|
||||
* messages from contact requests
|
||||
* and muted chats are included,
|
||||
* but messages from explicitly blocked contacts
|
||||
* and chats are ignored.
|
||||
*
|
||||
* This function may be called as a part of event loop
|
||||
* triggered by DC_EVENT_INCOMING_MSG if you are only interested
|
||||
* in the incoming messages.
|
||||
* Otherwise use a separate message processing loop
|
||||
* calling dc_wait_next_msgs() in a separate thread.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object as returned from dc_context_new().
|
||||
* @return An array of message IDs, must be dc_array_unref()'d when no longer used.
|
||||
* On errors, the list is empty. NULL is never returned.
|
||||
*/
|
||||
dc_array_t* dc_get_next_msgs (dc_context_t* context);
|
||||
|
||||
|
||||
/**
|
||||
* Waits for notification of new messages
|
||||
* and returns an array of new message IDs.
|
||||
* See the documentation for dc_get_next_msgs()
|
||||
* for the details of return value.
|
||||
*
|
||||
* This function waits for internal notification of
|
||||
* a new message in the database and returns afterwards.
|
||||
* Notification is also sent when I/O is started
|
||||
* to allow processing new messages
|
||||
* and when I/O is stopped using dc_stop_io() or dc_accounts_stop_io()
|
||||
* to allow for manual interruption of the message processing loop.
|
||||
* The function may return an empty array if there are
|
||||
* no messages after notification,
|
||||
* which may happen on start or if the message is quickly deleted
|
||||
* after adding it to the database.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object as returned from dc_context_new().
|
||||
* @return An array of message IDs, must be dc_array_unref()'d when no longer used.
|
||||
* On errors, the list is empty. NULL is never returned.
|
||||
*/
|
||||
dc_array_t* dc_wait_next_msgs (dc_context_t* context);
|
||||
|
||||
|
||||
/**
|
||||
* Mark all messages in a chat as _noticed_.
|
||||
* _Noticed_ messages are no longer _fresh_ and do not count as being unseen
|
||||
@@ -1881,6 +2036,11 @@ int dc_resend_msgs (dc_context_t* context, const uint3
|
||||
* Moreover, timer is started for incoming ephemeral messages.
|
||||
* This also happens for contact requests chats.
|
||||
*
|
||||
* This function updates last_msg_id configuration value
|
||||
* to the maximum of the current value and IDs passed to this function.
|
||||
* Bots which mark messages as seen can rely on this side effect
|
||||
* to avoid updating last_msg_id value manually.
|
||||
*
|
||||
* One #DC_EVENT_MSGS_NOTICED event is emitted per modified chat.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
@@ -2100,8 +2260,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`
|
||||
@@ -2295,6 +2454,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
|
||||
@@ -2320,7 +2480,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().
|
||||
*
|
||||
@@ -2340,6 +2500,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().
|
||||
@@ -2630,6 +2794,123 @@ 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.
|
||||
*
|
||||
* If the provider has not yet finished, as indicated by
|
||||
* dc_backup_provider_wait() or the #DC_EVENT_IMEX_PROGRESS event with value
|
||||
* of 0 (failed) or 1000 (succeeded), this will also abort any in-progress
|
||||
* transfer. If this aborts the provider a #DC_EVENT_IMEX_PROGRESS event with
|
||||
* value 0 (failed) will be emitted.
|
||||
*
|
||||
* @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
|
||||
*
|
||||
@@ -3184,7 +3465,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.
|
||||
@@ -3724,16 +4005,17 @@ char* dc_msg_get_text (const dc_msg_t* msg);
|
||||
*/
|
||||
char* dc_msg_get_subject (const dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* Find out full path, file name and extension of the file associated with a
|
||||
* message.
|
||||
* Find out full path of the file associated with a message.
|
||||
*
|
||||
* Typically files are associated with images, videos, audios, documents.
|
||||
* Plain text messages do not have a file.
|
||||
* File name may be mangled. To obtain the original attachment filename use dc_msg_get_filename().
|
||||
*
|
||||
* @memberof dc_msg_t
|
||||
* @param msg The message object.
|
||||
* @return The full path, the file name, and the extension of the file associated with the message.
|
||||
* @return The full path (with file name and extension) of the file associated with the message.
|
||||
* If there is no file associated with the message, an empty string is returned.
|
||||
* NULL is never returned and the returned value must be released using dc_str_unref().
|
||||
*/
|
||||
@@ -3741,14 +4023,13 @@ char* dc_msg_get_file (const dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* Get a base file name without the path. The base file name includes the extension; the path
|
||||
* is not returned. To get the full path, use dc_msg_get_file().
|
||||
* Get an original attachment filename, with extension but without the path. To get the full path,
|
||||
* use dc_msg_get_file().
|
||||
*
|
||||
* @memberof dc_msg_t
|
||||
* @param msg The message object.
|
||||
* @return The base file name plus the extension without part. If there is no file
|
||||
* associated with the message, an empty string is returned. The returned
|
||||
* value must be released using dc_str_unref().
|
||||
* @return The attachment filename. If there is no file associated with the message, an empty string
|
||||
* is returned. The returned value must be released using dc_str_unref().
|
||||
*/
|
||||
char* dc_msg_get_filename (const dc_msg_t* msg);
|
||||
|
||||
@@ -4875,6 +5156,72 @@ int dc_provider_get_status (const dc_provider_t* prov
|
||||
void dc_provider_unref (dc_provider_t* provider);
|
||||
|
||||
|
||||
/**
|
||||
* Return an HTTP(S) GET response.
|
||||
* This function can be used to download remote content for HTML emails.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object to take proxy settings from.
|
||||
* @param url HTTP or HTTPS URL.
|
||||
* @return The response must be released using dc_http_response_unref() after usage.
|
||||
* NULL is returned on errors.
|
||||
*/
|
||||
dc_http_response_t* dc_get_http_response (const dc_context_t* context, const char* url);
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_http_response_t
|
||||
*
|
||||
* An object containing an HTTP(S) GET response.
|
||||
* Created by dc_get_http_response().
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Returns HTTP response MIME type as a string, e.g. "text/plain" or "text/html".
|
||||
*
|
||||
* @memberof dc_http_response_t
|
||||
* @param response HTTP response as returned by dc_get_http_response().
|
||||
* @return The string which must be released using dc_str_unref() after usage. May be NULL.
|
||||
*/
|
||||
char* dc_http_response_get_mimetype (const dc_http_response_t* response);
|
||||
|
||||
/**
|
||||
* Returns HTTP response encoding, e.g. "utf-8".
|
||||
*
|
||||
* @memberof dc_http_response_t
|
||||
* @param response HTTP response as returned by dc_get_http_response().
|
||||
* @return The string which must be released using dc_str_unref() after usage. May be NULL.
|
||||
*/
|
||||
char* dc_http_response_get_encoding (const dc_http_response_t* response);
|
||||
|
||||
/**
|
||||
* Returns HTTP response contents.
|
||||
*
|
||||
* @memberof dc_http_response_t
|
||||
* @param response HTTP response as returned by dc_get_http_response().
|
||||
* @return The blob which must be released using dc_str_unref() after usage. NULL is never returned.
|
||||
*/
|
||||
uint8_t* dc_http_response_get_blob (const dc_http_response_t* response);
|
||||
|
||||
/**
|
||||
* Returns HTTP response content size.
|
||||
*
|
||||
* @memberof dc_http_response_t
|
||||
* @param response HTTP response as returned by dc_get_http_response().
|
||||
* @return The blob size.
|
||||
*/
|
||||
size_t dc_http_response_get_size (const dc_http_response_t* response);
|
||||
|
||||
/**
|
||||
* Free an HTTP response object.
|
||||
*
|
||||
* @memberof dc_http_response_t
|
||||
* @param response HTTP response as returned by dc_get_http_response().
|
||||
*/
|
||||
void dc_http_response_unref (const dc_http_response_t* response);
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_lot_t
|
||||
*
|
||||
@@ -5352,7 +5699,6 @@ void dc_reactions_unref (dc_reactions_t* reactions);
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_jsonrpc_instance_t
|
||||
*
|
||||
@@ -5401,6 +5747,18 @@ void dc_jsonrpc_request(dc_jsonrpc_instance_t* jsonrpc_instance, const char* req
|
||||
*/
|
||||
char* dc_jsonrpc_next_response(dc_jsonrpc_instance_t* jsonrpc_instance);
|
||||
|
||||
/**
|
||||
* Make a JSON-RPC call and return a response.
|
||||
*
|
||||
* @memberof dc_jsonrpc_instance_t
|
||||
* @param jsonrpc_instance jsonrpc instance as returned from dc_jsonrpc_init().
|
||||
* @param method JSON-RPC method name, e.g. `check_email_validity`.
|
||||
* @param params JSON-RPC method parameters, e.g. `["alice@example.org"]`.
|
||||
* @return JSON-RPC response as string, must be freed using dc_str_unref() after usage.
|
||||
* On error, NULL is returned.
|
||||
*/
|
||||
char* dc_jsonrpc_blocking_call(dc_jsonrpc_instance_t* jsonrpc_instance, const char *method, const char *params);
|
||||
|
||||
/**
|
||||
* @class dc_event_emitter_t
|
||||
*
|
||||
@@ -5589,6 +5947,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
|
||||
*
|
||||
@@ -5740,6 +6106,15 @@ void dc_event_unref(dc_event_t* event);
|
||||
#define DC_EVENT_MSG_READ 2015
|
||||
|
||||
|
||||
/**
|
||||
* A single message is deleted.
|
||||
*
|
||||
* @param data1 (int) chat_id
|
||||
* @param data2 (int) msg_id
|
||||
*/
|
||||
#define DC_EVENT_MSG_DELETED 2016
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -6341,7 +6716,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."
|
||||
@@ -6623,7 +6998,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
|
||||
@@ -6874,6 +7249,16 @@ 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
|
||||
|
||||
/// "Account transferred to your second device."
|
||||
///
|
||||
/// Used as a device message after a successful backup transfer.
|
||||
#define DC_STR_BACKUP_TRANSFER_MSG_BODY 163
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,8 @@ use crate::summary::{Summary, SummaryPrefix};
|
||||
/// 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),
|
||||
@@ -47,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),
|
||||
@@ -98,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,
|
||||
@@ -122,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(),
|
||||
@@ -170,6 +175,8 @@ pub enum LotState {
|
||||
/// text1=domain
|
||||
QrAccount = 250,
|
||||
|
||||
QrBackup = 251,
|
||||
|
||||
/// text1=domain, text2=instance pattern
|
||||
QrWebrtcInstance = 260,
|
||||
|
||||
|
||||
1
deltachat-jsonrpc/.gitignore
vendored
1
deltachat-jsonrpc/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
openrpc/openrpc.json
|
||||
accounts/
|
||||
|
||||
.cargo
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.109.0"
|
||||
version = "1.120.0"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
edition = "2021"
|
||||
default-run = "deltachat-jsonrpc-server"
|
||||
@@ -15,26 +15,29 @@ required-features = ["webserver"]
|
||||
anyhow = "1"
|
||||
deltachat = { path = ".." }
|
||||
num-traits = "0.2"
|
||||
schemars = "0.8.11"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tempfile = "3.3.0"
|
||||
tempfile = "3.6.0"
|
||||
log = "0.4"
|
||||
async-channel = { version = "1.8.0" }
|
||||
futures = { version = "0.3.26" }
|
||||
serde_json = "1.0.91"
|
||||
yerpc = { version = "^0.4.0", features = ["anyhow_expose"] }
|
||||
futures = { version = "0.3.28" }
|
||||
serde_json = "1.0.99"
|
||||
yerpc = { version = "0.5.1", features = ["anyhow_expose", "openrpc"] }
|
||||
typescript-type-def = { version = "0.5.5", features = ["json_value"] }
|
||||
tokio = { version = "1.25.0" }
|
||||
tokio = { version = "1.29.1" }
|
||||
sanitize-filename = "0.4"
|
||||
walkdir = "2.3.2"
|
||||
walkdir = "2.3.3"
|
||||
base64 = "0.21"
|
||||
|
||||
# optional dependencies
|
||||
axum = { version = "0.5.9", optional = true, features = ["ws"] }
|
||||
axum = { version = "0.6.18", optional = true, features = ["ws"] }
|
||||
env_logger = { version = "0.10.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.25.0", features = ["full", "rt-multi-thread"] }
|
||||
tokio = { version = "1.29.1", 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,6 +4,8 @@ use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||
pub use deltachat::accounts::Accounts;
|
||||
use deltachat::message::get_msg_read_receipts;
|
||||
use deltachat::qr::Qr;
|
||||
use deltachat::{
|
||||
chat::{
|
||||
self, add_contact_to_chat, forward_msgs, get_chat_media, get_chat_msgs, get_chat_msgs_ex,
|
||||
@@ -17,23 +19,21 @@ use deltachat::{
|
||||
context::get_info,
|
||||
ephemeral::Timer,
|
||||
imex, location,
|
||||
message::{
|
||||
self, delete_msgs, get_msg_info, markseen_msgs, Message, MessageState, MsgId, Viewtype,
|
||||
},
|
||||
message::{self, delete_msgs, markseen_msgs, Message, MessageState, MsgId, Viewtype},
|
||||
provider::get_provider_info,
|
||||
qr,
|
||||
qr_code_generator::get_securejoin_qr_svg,
|
||||
reaction::send_reaction,
|
||||
qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg},
|
||||
reaction::{get_msg_reactions, send_reaction},
|
||||
securejoin,
|
||||
stock_str::StockMessage,
|
||||
webxdc::StatusUpdateSerial,
|
||||
};
|
||||
use sanitize_filename::is_sanitized;
|
||||
use tokio::{fs, sync::RwLock};
|
||||
use tokio::fs;
|
||||
use tokio::sync::{watch, Mutex, RwLock};
|
||||
use walkdir::WalkDir;
|
||||
use yerpc::rpc;
|
||||
|
||||
pub mod events;
|
||||
pub mod types;
|
||||
|
||||
use num_traits::FromPrimitive;
|
||||
@@ -41,8 +41,11 @@ use types::account::Account;
|
||||
use types::chat::FullChat;
|
||||
use types::chat_list::ChatListEntry;
|
||||
use types::contact::ContactObject;
|
||||
use types::message::MessageObject;
|
||||
use types::events::Event;
|
||||
use types::http::HttpResponse;
|
||||
use types::message::{MessageData, MessageObject, MessageReadReceipt};
|
||||
use types::provider_info::ProviderInfo;
|
||||
use types::reactions::JSONRPCReactions;
|
||||
use types::webxdc::WebxdcMessageInfo;
|
||||
|
||||
use self::types::message::MessageLoadResult;
|
||||
@@ -56,21 +59,45 @@ use self::types::{
|
||||
use crate::api::types::chat_list::{get_chat_list_item_by_id, ChatListItemFetchResult};
|
||||
use crate::api::types::qr::QrObject;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AccountState {
|
||||
/// The Qr code for current [`CommandApi::provide_backup`] call.
|
||||
///
|
||||
/// If there currently is a call to [`CommandApi::provide_backup`] this will be
|
||||
/// `Pending` or `Ready`, otherwise `NoProvider`.
|
||||
backup_provider_qr: watch::Sender<ProviderQr>,
|
||||
}
|
||||
|
||||
impl Default for AccountState {
|
||||
fn default() -> Self {
|
||||
let (tx, _rx) = watch::channel(ProviderQr::NoProvider);
|
||||
Self {
|
||||
backup_provider_qr: tx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CommandApi {
|
||||
pub(crate) accounts: Arc<RwLock<Accounts>>,
|
||||
|
||||
states: Arc<Mutex<BTreeMap<u32, AccountState>>>,
|
||||
}
|
||||
|
||||
impl CommandApi {
|
||||
pub fn new(accounts: Accounts) -> Self {
|
||||
CommandApi {
|
||||
accounts: Arc::new(RwLock::new(accounts)),
|
||||
states: Arc::new(Mutex::new(BTreeMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn from_arc(accounts: Arc<RwLock<Accounts>>) -> Self {
|
||||
CommandApi { accounts }
|
||||
CommandApi {
|
||||
accounts,
|
||||
states: Arc::new(Mutex::new(BTreeMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_context(&self, id: u32) -> Result<deltachat::context::Context> {
|
||||
@@ -82,9 +109,45 @@ impl CommandApi {
|
||||
.ok_or_else(|| anyhow!("account with id {} not found", id))?;
|
||||
Ok(sc)
|
||||
}
|
||||
|
||||
async fn with_state<F, T>(&self, id: u32, with_state: F) -> T
|
||||
where
|
||||
F: FnOnce(&AccountState) -> T,
|
||||
{
|
||||
let mut states = self.states.lock().await;
|
||||
let state = states.entry(id).or_insert_with(Default::default);
|
||||
with_state(state)
|
||||
}
|
||||
|
||||
async fn inner_get_backup_qr(&self, account_id: u32) -> Result<Qr> {
|
||||
let mut receiver = self
|
||||
.with_state(account_id, |state| state.backup_provider_qr.subscribe())
|
||||
.await;
|
||||
|
||||
let val: ProviderQr = receiver.borrow_and_update().clone();
|
||||
match val {
|
||||
ProviderQr::NoProvider => bail!("No backup being provided"),
|
||||
ProviderQr::Pending => loop {
|
||||
if receiver.changed().await.is_err() {
|
||||
bail!("No backup being provided (account state dropped)");
|
||||
}
|
||||
let val: ProviderQr = receiver.borrow().clone();
|
||||
match val {
|
||||
ProviderQr::NoProvider => bail!("No backup being provided"),
|
||||
ProviderQr::Pending => continue,
|
||||
ProviderQr::Ready(qr) => break Ok(qr),
|
||||
};
|
||||
},
|
||||
ProviderQr::Ready(qr) => Ok(qr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rpc(all_positional, ts_outdir = "typescript/generated")]
|
||||
#[rpc(
|
||||
all_positional,
|
||||
ts_outdir = "typescript/generated",
|
||||
openrpc_outdir = "openrpc"
|
||||
)]
|
||||
impl CommandApi {
|
||||
/// Test function.
|
||||
async fn sleep(&self, delay: f64) {
|
||||
@@ -105,6 +168,16 @@ impl CommandApi {
|
||||
get_info()
|
||||
}
|
||||
|
||||
/// Get the next event.
|
||||
async fn get_next_event(&self) -> Result<Event> {
|
||||
let event_emitter = self.accounts.read().await.get_event_emitter();
|
||||
event_emitter
|
||||
.recv()
|
||||
.await
|
||||
.map(|event| event.into())
|
||||
.context("event channel is closed")
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// Account Management
|
||||
// ---------------------------------------------
|
||||
@@ -114,7 +187,13 @@ impl CommandApi {
|
||||
}
|
||||
|
||||
async fn remove_account(&self, account_id: u32) -> Result<()> {
|
||||
self.accounts.write().await.remove_account(account_id).await
|
||||
self.accounts
|
||||
.write()
|
||||
.await
|
||||
.remove_account(account_id)
|
||||
.await?;
|
||||
self.states.lock().await.remove(&account_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_all_account_ids(&self) -> Vec<u32> {
|
||||
@@ -140,8 +219,6 @@ impl CommandApi {
|
||||
let context_option = self.accounts.read().await.get_account(id);
|
||||
if let Some(ctx) = context_option {
|
||||
accounts.push(Account::from_context(&ctx, id).await?)
|
||||
} else {
|
||||
println!("account with id {id} doesn't exist anymore");
|
||||
}
|
||||
}
|
||||
Ok(accounts)
|
||||
@@ -388,6 +465,49 @@ impl CommandApi {
|
||||
ChatId::new(chat_id).get_fresh_msg_cnt(&ctx).await
|
||||
}
|
||||
|
||||
/// Gets messages to be processed by the bot and returns their IDs.
|
||||
///
|
||||
/// Only messages with database ID higher than `last_msg_id` config value
|
||||
/// are returned. After processing the messages, the bot should
|
||||
/// update `last_msg_id` by calling [`markseen_msgs`]
|
||||
/// or manually updating the value to avoid getting already
|
||||
/// processed messages.
|
||||
///
|
||||
/// [`markseen_msgs`]: Self::markseen_msgs
|
||||
async fn get_next_msgs(&self, account_id: u32) -> Result<Vec<u32>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let msg_ids = ctx
|
||||
.get_next_msgs()
|
||||
.await?
|
||||
.iter()
|
||||
.map(|msg_id| msg_id.to_u32())
|
||||
.collect();
|
||||
Ok(msg_ids)
|
||||
}
|
||||
|
||||
/// Waits for messages to be processed by the bot and returns their IDs.
|
||||
///
|
||||
/// This function is similar to [`get_next_msgs`],
|
||||
/// but waits for internal new message notification before returning.
|
||||
/// New message notification is sent when new message is added to the database,
|
||||
/// on initialization, when I/O is started and when I/O is stopped.
|
||||
/// This allows bots to use `wait_next_msgs` in a loop to process
|
||||
/// old messages after initialization and during the bot runtime.
|
||||
/// To shutdown the bot, stopping I/O can be used to interrupt
|
||||
/// pending or next `wait_next_msgs` call.
|
||||
///
|
||||
/// [`get_next_msgs`]: Self::get_next_msgs
|
||||
async fn wait_next_msgs(&self, account_id: u32) -> Result<Vec<u32>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let msg_ids = ctx
|
||||
.wait_next_msgs()
|
||||
.await?
|
||||
.iter()
|
||||
.map(|msg_id| msg_id.to_u32())
|
||||
.collect();
|
||||
Ok(msg_ids)
|
||||
}
|
||||
|
||||
/// Estimate the number of messages that will be deleted
|
||||
/// by the set_config()-options `delete_device_after` or `delete_server_after`.
|
||||
/// This is typically used to show the estimated impact to the user
|
||||
@@ -431,7 +551,7 @@ impl CommandApi {
|
||||
list_flags: Option<u32>,
|
||||
query_string: Option<String>,
|
||||
query_contact_id: Option<u32>,
|
||||
) -> Result<Vec<ChatListEntry>> {
|
||||
) -> Result<Vec<u32>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let list = Chatlist::try_load(
|
||||
&ctx,
|
||||
@@ -440,6 +560,22 @@ impl CommandApi {
|
||||
query_contact_id.map(ContactId::new),
|
||||
)
|
||||
.await?;
|
||||
let mut l: Vec<u32> = Vec::with_capacity(list.len());
|
||||
for i in 0..list.len() {
|
||||
l.push(list.get_chat_id(i)?.to_u32());
|
||||
}
|
||||
Ok(l)
|
||||
}
|
||||
|
||||
/// Returns chats similar to the given one.
|
||||
async fn get_similar_chatlist_entries(
|
||||
&self,
|
||||
account_id: u32,
|
||||
chat_id: u32,
|
||||
) -> Result<Vec<ChatListEntry>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let chat_id = ChatId::new(chat_id);
|
||||
let list = chat_id.get_similar_chatlist(&ctx).await?;
|
||||
let mut l: Vec<ChatListEntry> = Vec::with_capacity(list.len());
|
||||
for i in 0..list.len() {
|
||||
l.push(ChatListEntry(
|
||||
@@ -453,19 +589,18 @@ impl CommandApi {
|
||||
async fn get_chatlist_items_by_entries(
|
||||
&self,
|
||||
account_id: u32,
|
||||
entries: Vec<ChatListEntry>,
|
||||
entries: Vec<u32>,
|
||||
) -> Result<HashMap<u32, ChatListItemFetchResult>> {
|
||||
// todo custom json deserializer for ChatListEntry?
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let mut result: HashMap<u32, ChatListItemFetchResult> =
|
||||
HashMap::with_capacity(entries.len());
|
||||
for entry in entries.iter() {
|
||||
for &entry in entries.iter() {
|
||||
result.insert(
|
||||
entry.0,
|
||||
entry,
|
||||
match get_chat_list_item_by_id(&ctx, entry).await {
|
||||
Ok(res) => res,
|
||||
Err(err) => ChatListItemFetchResult::Error {
|
||||
id: entry.0,
|
||||
id: entry,
|
||||
error: format!("{err:#}"),
|
||||
},
|
||||
},
|
||||
@@ -784,7 +919,7 @@ impl CommandApi {
|
||||
) -> Result<u32> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.set_text(Some(text));
|
||||
msg.set_text(text);
|
||||
let message_id =
|
||||
deltachat::chat::add_device_msg(&ctx, Some(&label), Some(&mut msg)).await?;
|
||||
Ok(message_id.to_u32())
|
||||
@@ -879,6 +1014,11 @@ impl CommandApi {
|
||||
/// Moreover, timer is started for incoming ephemeral messages.
|
||||
/// This also happens for contact requests chats.
|
||||
///
|
||||
/// This function updates `last_msg_id` configuration value
|
||||
/// to the maximum of the current value and IDs passed to this function.
|
||||
/// Bots which mark messages as seen can rely on this side effect
|
||||
/// to avoid updating `last_msg_id` value manually.
|
||||
///
|
||||
/// One #DC_EVENT_MSGS_NOTICED event is emitted per modified chat.
|
||||
async fn markseen_msgs(&self, account_id: u32, msg_ids: Vec<u32>) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
@@ -997,7 +1137,25 @@ impl CommandApi {
|
||||
/// max. text returned by dc_msg_get_text() (about 30000 characters).
|
||||
async fn get_message_info(&self, account_id: u32, message_id: u32) -> Result<String> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
get_msg_info(&ctx, MsgId::new(message_id)).await
|
||||
MsgId::new(message_id).get_info(&ctx).await
|
||||
}
|
||||
|
||||
/// Returns contacts that sent read receipts and the time of reading.
|
||||
async fn get_message_read_receipts(
|
||||
&self,
|
||||
account_id: u32,
|
||||
message_id: u32,
|
||||
) -> Result<Vec<MessageReadReceipt>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let receipts = get_msg_read_receipts(&ctx, MsgId::new(message_id))
|
||||
.await?
|
||||
.iter()
|
||||
.map(|(contact_id, ts)| MessageReadReceipt {
|
||||
contact_id: contact_id.to_u32(),
|
||||
timestamp: *ts,
|
||||
})
|
||||
.collect();
|
||||
Ok(receipts)
|
||||
}
|
||||
|
||||
/// Asks the core to start downloading a message fully.
|
||||
@@ -1017,17 +1175,17 @@ impl CommandApi {
|
||||
}
|
||||
|
||||
/// Search messages containing the given query string.
|
||||
/// Searching can be done globally (chat_id=0) or in a specified chat only (chat_id set).
|
||||
/// Searching can be done globally (chat_id=None) or in a specified chat only (chat_id set).
|
||||
///
|
||||
/// Global chat results are typically displayed using dc_msg_get_summary(), chat
|
||||
/// search results may just hilite the corresponding messages and present a
|
||||
/// Global search results are typically displayed using dc_msg_get_summary(), chat
|
||||
/// search results may just highlight the corresponding messages and present a
|
||||
/// prev/next button.
|
||||
///
|
||||
/// For global search, result is limited to 1000 messages,
|
||||
/// this allows incremental search done fast.
|
||||
/// So, when getting exactly 1000 results, the result may be truncated;
|
||||
/// the UIs may display sth. as "1000+ messages found" in this case.
|
||||
/// Chat search (if a chat_id is set) is not limited.
|
||||
/// For the global search, the result is limited to 1000 messages,
|
||||
/// this allows an incremental search done fast.
|
||||
/// So, when getting exactly 1000 messages, the result actually may be truncated;
|
||||
/// the UIs may display sth. like "1000+ messages found" in this case.
|
||||
/// The chat search (if chat_id is set) is not limited.
|
||||
async fn search_messages(
|
||||
&self,
|
||||
account_id: u32,
|
||||
@@ -1200,7 +1358,7 @@ impl CommandApi {
|
||||
) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let contact_id = ContactId::new(contact_id);
|
||||
let contact = Contact::load_from_db(&ctx, contact_id).await?;
|
||||
let contact = Contact::get_by_id(&ctx, contact_id).await?;
|
||||
let addr = contact.get_addr();
|
||||
Contact::create(&ctx, &name, addr).await?;
|
||||
Ok(())
|
||||
@@ -1324,16 +1482,13 @@ impl CommandApi {
|
||||
passphrase: Option<String>,
|
||||
) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
ctx.stop_io().await;
|
||||
let result = imex::imex(
|
||||
imex::imex(
|
||||
&ctx,
|
||||
imex::ImexMode::ExportBackup,
|
||||
destination.as_ref(),
|
||||
passphrase,
|
||||
)
|
||||
.await;
|
||||
ctx.start_io().await;
|
||||
result
|
||||
.await
|
||||
}
|
||||
|
||||
async fn import_backup(
|
||||
@@ -1352,6 +1507,75 @@ impl CommandApi {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Offers a backup for remote devices to retrieve.
|
||||
///
|
||||
/// Can be cancelled by stopping the ongoing process. Success or failure can be tracked
|
||||
/// via the `ImexProgress` event which should either reach `1000` for success or `0` for
|
||||
/// failure.
|
||||
///
|
||||
/// This **stops IO** while it is running.
|
||||
///
|
||||
/// Returns once a remote device has retrieved the backup, or is cancelled.
|
||||
async fn provide_backup(&self, account_id: u32) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
self.with_state(account_id, |state| {
|
||||
state.backup_provider_qr.send_replace(ProviderQr::Pending);
|
||||
})
|
||||
.await;
|
||||
|
||||
let provider = imex::BackupProvider::prepare(&ctx).await?;
|
||||
self.with_state(account_id, |state| {
|
||||
state
|
||||
.backup_provider_qr
|
||||
.send_replace(ProviderQr::Ready(provider.qr()));
|
||||
})
|
||||
.await;
|
||||
|
||||
provider.await
|
||||
}
|
||||
|
||||
/// Returns the text of the QR code for the running [`CommandApi::provide_backup`].
|
||||
///
|
||||
/// This QR code text can be used in [`CommandApi::get_backup`] on a second device to
|
||||
/// retrieve the backup and setup this second device.
|
||||
///
|
||||
/// This call will fail if there is currently no concurrent call to
|
||||
/// [`CommandApi::provide_backup`]. This call may block if the QR code is not yet
|
||||
/// ready.
|
||||
async fn get_backup_qr(&self, account_id: u32) -> Result<String> {
|
||||
let qr = self.inner_get_backup_qr(account_id).await?;
|
||||
qr::format_backup(&qr)
|
||||
}
|
||||
|
||||
/// Returns the rendered QR code for the running [`CommandApi::provide_backup`].
|
||||
///
|
||||
/// This QR code can be used in [`CommandApi::get_backup`] on a second device to
|
||||
/// retrieve the backup and setup this second device.
|
||||
///
|
||||
/// This call will fail if there is currently no concurrent call to
|
||||
/// [`CommandApi::provide_backup`]. This call may block if the QR code is not yet
|
||||
/// ready.
|
||||
///
|
||||
/// Returns the QR code rendered as an SVG image.
|
||||
async fn get_backup_qr_svg(&self, account_id: u32) -> Result<String> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let qr = self.inner_get_backup_qr(account_id).await?;
|
||||
generate_backup_qr(&ctx, &qr).await
|
||||
}
|
||||
|
||||
/// Gets a backup from a remote provider.
|
||||
///
|
||||
/// This retrieves the backup from a remote device over the network and imports it into
|
||||
/// the current device.
|
||||
///
|
||||
/// Can be cancelled by stopping the ongoing process.
|
||||
async fn get_backup(&self, account_id: u32, qr_text: String) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let qr = qr::check_qr(&ctx, &qr_text).await?;
|
||||
imex::get_backup(&ctx, qr).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// connectivity
|
||||
// ---------------------------------------------
|
||||
@@ -1462,6 +1686,32 @@ impl CommandApi {
|
||||
WebxdcMessageInfo::get_for_message(&ctx, MsgId::new(instance_msg_id)).await
|
||||
}
|
||||
|
||||
/// Get blob encoded as base64 from a webxdc message
|
||||
///
|
||||
/// path is the path of the file within webxdc archive
|
||||
async fn get_webxdc_blob(
|
||||
&self,
|
||||
account_id: u32,
|
||||
instance_msg_id: u32,
|
||||
path: String,
|
||||
) -> Result<String> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let message = Message::load_from_db(&ctx, MsgId::new(instance_msg_id)).await?;
|
||||
let blob = message.get_webxdc_blob(&ctx, &path).await?;
|
||||
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
Ok(general_purpose::STANDARD_NO_PAD.encode(blob))
|
||||
}
|
||||
|
||||
/// Makes an HTTP GET request and returns a response.
|
||||
///
|
||||
/// `url` is the HTTP or HTTPS URL.
|
||||
async fn get_http_response(&self, account_id: u32, url: String) -> Result<HttpResponse> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let response = deltachat::net::read_url_blob(&ctx, &url).await?.into();
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Forward messages to another chat.
|
||||
///
|
||||
/// All types of messages can be forwarded,
|
||||
@@ -1479,6 +1729,20 @@ impl CommandApi {
|
||||
forward_msgs(&ctx, &message_ids, ChatId::new(chat_id)).await
|
||||
}
|
||||
|
||||
/// Resend messages and make information available for newly added chat members.
|
||||
/// Resending sends out the original message, however, recipients and webxdc-status may differ.
|
||||
/// Clients that already have the original message can still ignore the resent message as
|
||||
/// they have tracked the state by dedicated updates.
|
||||
///
|
||||
/// Some messages cannot be resent, eg. info-messages, drafts, already pending messages or messages that are not sent by SELF.
|
||||
///
|
||||
/// message_ids all message IDs that should be resend. All messages must belong to the same chat.
|
||||
async fn resend_messages(&self, account_id: u32, message_ids: Vec<u32>) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let message_ids: Vec<MsgId> = message_ids.into_iter().map(MsgId::new).collect();
|
||||
chat::resend_msgs(&ctx, &message_ids).await
|
||||
}
|
||||
|
||||
async fn send_sticker(
|
||||
&self,
|
||||
account_id: u32,
|
||||
@@ -1511,6 +1775,70 @@ impl CommandApi {
|
||||
Ok(message_id.to_u32())
|
||||
}
|
||||
|
||||
/// Returns reactions to the message.
|
||||
async fn get_message_reactions(
|
||||
&self,
|
||||
account_id: u32,
|
||||
message_id: u32,
|
||||
) -> Result<Option<JSONRPCReactions>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let reactions = get_msg_reactions(&ctx, MsgId::new(message_id)).await?;
|
||||
if reactions.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(reactions.into()))
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_msg(&self, account_id: u32, chat_id: u32, data: MessageData) -> Result<u32> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let mut message = Message::new(if let Some(viewtype) = data.viewtype {
|
||||
viewtype.into()
|
||||
} else if data.file.is_some() {
|
||||
Viewtype::File
|
||||
} else {
|
||||
Viewtype::Text
|
||||
});
|
||||
message.set_text(data.text.unwrap_or_default());
|
||||
if data.html.is_some() {
|
||||
message.set_html(data.html);
|
||||
}
|
||||
if data.override_sender_name.is_some() {
|
||||
message.set_override_sender_name(data.override_sender_name);
|
||||
}
|
||||
if let Some(file) = data.file {
|
||||
message.set_file(file, None);
|
||||
}
|
||||
if let Some((latitude, longitude)) = data.location {
|
||||
message.set_location(latitude, longitude);
|
||||
}
|
||||
if let Some(id) = data.quoted_message_id {
|
||||
message
|
||||
.set_quote(
|
||||
&ctx,
|
||||
Some(
|
||||
&Message::load_from_db(&ctx, MsgId::new(id))
|
||||
.await
|
||||
.context("message to quote could not be loaded")?,
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
let msg_id = chat::send_msg(&ctx, ChatId::new(chat_id), &mut message)
|
||||
.await?
|
||||
.to_u32();
|
||||
Ok(msg_id)
|
||||
}
|
||||
|
||||
/// Checks if messages can be sent to a given chat.
|
||||
async fn can_send(&self, account_id: u32, chat_id: u32) -> Result<bool> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let chat_id = ChatId::new(chat_id);
|
||||
let chat = Chat::load_from_db(&ctx, chat_id).await?;
|
||||
let can_send = chat.can_send(&ctx).await?;
|
||||
Ok(can_send)
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// functions for the composer
|
||||
// the composer is the message input field
|
||||
@@ -1652,7 +1980,7 @@ impl CommandApi {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.set_text(Some(text));
|
||||
msg.set_text(text);
|
||||
|
||||
let message_id = deltachat::chat::send_msg(&ctx, ChatId::new(chat_id), &mut msg).await?;
|
||||
Ok(message_id.to_u32())
|
||||
@@ -1675,9 +2003,7 @@ impl CommandApi {
|
||||
} else {
|
||||
Viewtype::Text
|
||||
});
|
||||
if text.is_some() {
|
||||
message.set_text(text);
|
||||
}
|
||||
message.set_text(text.unwrap_or_default());
|
||||
if let Some(file) = file {
|
||||
message.set_file(file, None);
|
||||
}
|
||||
@@ -1721,9 +2047,7 @@ impl CommandApi {
|
||||
} else {
|
||||
Viewtype::Text
|
||||
});
|
||||
if text.is_some() {
|
||||
draft.set_text(text);
|
||||
}
|
||||
draft.set_text(text.unwrap_or_default());
|
||||
if let Some(file) = file {
|
||||
draft.set_file(file, None);
|
||||
}
|
||||
@@ -1780,3 +2104,15 @@ async fn get_config(
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a QR code for a BackupProvider is currently available.
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone, Debug)]
|
||||
enum ProviderQr {
|
||||
/// There is no provider, asking for a QR is an error.
|
||||
NoProvider,
|
||||
/// There is a provider, the QR code is pending.
|
||||
Pending,
|
||||
/// There is a provider and QR code.
|
||||
Ready(Qr),
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use typescript_type_def::TypeDef;
|
||||
|
||||
use super::color_int_to_hex_string;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Account {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use deltachat::chat::{self, get_chat_contacts, ChatVisibility};
|
||||
use deltachat::chat::{Chat, ChatId};
|
||||
use deltachat::constants::Chattype;
|
||||
@@ -13,7 +13,7 @@ use typescript_type_def::TypeDef;
|
||||
use super::color_int_to_hex_string;
|
||||
use super::contact::ContactObject;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FullChat {
|
||||
id: u32,
|
||||
@@ -53,7 +53,9 @@ impl FullChat {
|
||||
contacts.push(
|
||||
ContactObject::try_from_dc_contact(
|
||||
context,
|
||||
Contact::load_from_db(context, *contact_id).await?,
|
||||
Contact::get_by_id(context, *contact_id)
|
||||
.await
|
||||
.context("failed to load contact")?,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
@@ -72,8 +74,9 @@ impl FullChat {
|
||||
|
||||
let was_seen_recently = if chat.get_type() == Chattype::Single {
|
||||
match contact_ids.get(0) {
|
||||
Some(contact) => Contact::load_from_db(context, *contact)
|
||||
.await?
|
||||
Some(contact) => Contact::get_by_id(context, *contact)
|
||||
.await
|
||||
.context("failed to load contact for was_seen_recently")?
|
||||
.was_seen_recently(),
|
||||
None => false,
|
||||
}
|
||||
@@ -89,10 +92,7 @@ impl FullChat {
|
||||
is_protected: chat.is_protected(),
|
||||
profile_image, //BLOBS ?
|
||||
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
|
||||
chat_type: chat
|
||||
.get_type()
|
||||
.to_u32()
|
||||
.ok_or_else(|| anyhow!("unknown chat type id"))?, // TODO get rid of this unwrap?
|
||||
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
|
||||
is_unpromoted: chat.is_unpromoted(),
|
||||
is_self_talk: chat.is_self_talk(),
|
||||
contacts,
|
||||
@@ -121,7 +121,7 @@ impl FullChat {
|
||||
/// - can_send
|
||||
///
|
||||
/// used when you only need the basic metadata of a chat like type, name, profile picture
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BasicChat {
|
||||
id: u32,
|
||||
@@ -155,10 +155,7 @@ impl BasicChat {
|
||||
is_protected: chat.is_protected(),
|
||||
profile_image, //BLOBS ?
|
||||
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
|
||||
chat_type: chat
|
||||
.get_type()
|
||||
.to_u32()
|
||||
.ok_or_else(|| anyhow!("unknown chat type id"))?, // TODO get rid of this unwrap?
|
||||
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
|
||||
is_unpromoted: chat.is_unpromoted(),
|
||||
is_self_talk: chat.is_self_talk(),
|
||||
color,
|
||||
@@ -169,7 +166,7 @@ impl BasicChat {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, TypeDef)]
|
||||
#[derive(Clone, Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
|
||||
pub enum MuteDuration {
|
||||
NotMuted,
|
||||
Forever,
|
||||
@@ -194,7 +191,7 @@ impl MuteDuration {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, TypeDef)]
|
||||
#[derive(Clone, Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "ChatVisibility")]
|
||||
pub enum JSONRPCChatVisibility {
|
||||
Normal,
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use deltachat::chat::{Chat, ChatId};
|
||||
use deltachat::chatlist::get_last_message_for_chat;
|
||||
use deltachat::constants::*;
|
||||
use deltachat::contact::{Contact, ContactId};
|
||||
use deltachat::{
|
||||
chat::{get_chat_contacts, ChatVisibility},
|
||||
chatlist::Chatlist,
|
||||
};
|
||||
use deltachat::{
|
||||
chat::{Chat, ChatId},
|
||||
message::MsgId,
|
||||
};
|
||||
use num_traits::cast::ToPrimitive;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::color_int_to_hex_string;
|
||||
use super::message::MessageViewtype;
|
||||
|
||||
#[derive(Deserialize, Serialize, TypeDef)]
|
||||
#[derive(Deserialize, Serialize, TypeDef, schemars::JsonSchema)]
|
||||
pub struct ChatListEntry(pub u32, pub u32);
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ChatListItemFetchResult {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -31,6 +30,8 @@ pub enum ChatListItemFetchResult {
|
||||
summary_text1: String,
|
||||
summary_text2: String,
|
||||
summary_status: u32,
|
||||
/// showing preview if last chat message is image
|
||||
summary_preview_image: Option<String>,
|
||||
is_protected: bool,
|
||||
is_group: bool,
|
||||
fresh_message_counter: usize,
|
||||
@@ -47,6 +48,8 @@ pub enum ChatListItemFetchResult {
|
||||
/// contact id if this is a dm chat (for view profile entry in context menu)
|
||||
dm_chat_contact: Option<u32>,
|
||||
was_seen_recently: bool,
|
||||
last_message_type: Option<MessageViewtype>,
|
||||
last_message_id: Option<u32>,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ArchiveLink { fresh_message_counter: usize },
|
||||
@@ -56,14 +59,9 @@ pub enum ChatListItemFetchResult {
|
||||
|
||||
pub(crate) async fn get_chat_list_item_by_id(
|
||||
ctx: &deltachat::context::Context,
|
||||
entry: &ChatListEntry,
|
||||
entry: u32,
|
||||
) -> Result<ChatListItemFetchResult> {
|
||||
let chat_id = ChatId::new(entry.0);
|
||||
let last_msgid = match entry.1 {
|
||||
0 => None,
|
||||
_ => Some(MsgId::new(entry.1)),
|
||||
};
|
||||
|
||||
let chat_id = ChatId::new(entry);
|
||||
let fresh_message_counter = chat_id.get_fresh_msg_cnt(ctx).await?;
|
||||
|
||||
if chat_id.is_archived_link() {
|
||||
@@ -72,12 +70,18 @@ pub(crate) async fn get_chat_list_item_by_id(
|
||||
});
|
||||
}
|
||||
|
||||
let chat = Chat::load_from_db(ctx, chat_id).await?;
|
||||
let summary = Chatlist::get_summary2(ctx, chat_id, last_msgid, Some(&chat)).await?;
|
||||
let last_msgid = get_last_message_for_chat(ctx, chat_id).await?;
|
||||
|
||||
let chat = Chat::load_from_db(ctx, chat_id).await.context("chat")?;
|
||||
let summary = Chatlist::get_summary2(ctx, chat_id, last_msgid, Some(&chat))
|
||||
.await
|
||||
.context("summary")?;
|
||||
|
||||
let summary_text1 = summary.prefix.map_or_else(String::new, |s| s.to_string());
|
||||
let summary_text2 = summary.text.to_owned();
|
||||
|
||||
let summary_preview_image = summary.thumbnail_path;
|
||||
|
||||
let visibility = chat.get_visibility();
|
||||
|
||||
let avatar_path = chat
|
||||
@@ -85,12 +89,15 @@ pub(crate) async fn get_chat_list_item_by_id(
|
||||
.await?
|
||||
.map(|path| path.to_str().unwrap_or("invalid/path").to_owned());
|
||||
|
||||
let last_updated = match last_msgid {
|
||||
let (last_updated, message_type) = match last_msgid {
|
||||
Some(id) => {
|
||||
let last_message = deltachat::message::Message::load_from_db(ctx, id).await?;
|
||||
Some(last_message.get_timestamp() * 1000)
|
||||
(
|
||||
Some(last_message.get_timestamp() * 1000),
|
||||
Some(last_message.get_viewtype().into()),
|
||||
)
|
||||
}
|
||||
None => None,
|
||||
None => (None, None),
|
||||
};
|
||||
|
||||
let chat_contacts = get_chat_contacts(ctx, chat_id).await?;
|
||||
@@ -100,8 +107,9 @@ pub(crate) async fn get_chat_list_item_by_id(
|
||||
let (dm_chat_contact, was_seen_recently) = if chat.get_type() == Chattype::Single {
|
||||
let contact = chat_contacts.get(0);
|
||||
let was_seen_recently = match contact {
|
||||
Some(contact) => Contact::load_from_db(ctx, *contact)
|
||||
.await?
|
||||
Some(contact) => Contact::get_by_id(ctx, *contact)
|
||||
.await
|
||||
.context("contact")?
|
||||
.was_seen_recently(),
|
||||
None => false,
|
||||
};
|
||||
@@ -124,6 +132,7 @@ pub(crate) async fn get_chat_list_item_by_id(
|
||||
summary_text1,
|
||||
summary_text2,
|
||||
summary_status: summary.state.to_u32().expect("impossible"), // idea and a function to transform the constant to strings? or return string enum
|
||||
summary_preview_image,
|
||||
is_protected: chat.is_protected(),
|
||||
is_group: chat.get_type() == Chattype::Group,
|
||||
fresh_message_counter,
|
||||
@@ -138,5 +147,7 @@ pub(crate) async fn get_chat_list_item_by_id(
|
||||
is_broadcast: chat.get_type() == Chattype::Broadcast,
|
||||
dm_chat_contact,
|
||||
was_seen_recently,
|
||||
last_message_type: message_type,
|
||||
last_message_id: last_msgid.map(|id| id.to_u32()),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use typescript_type_def::TypeDef;
|
||||
|
||||
use super::color_int_to_hex_string;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "Contact", rename_all = "camelCase")]
|
||||
pub struct ContactObject {
|
||||
address: String,
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
use deltachat::{Event, EventType};
|
||||
use deltachat::{Event as CoreEvent, EventType as CoreEventType};
|
||||
use serde::Serialize;
|
||||
use serde_json::{json, Value};
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
pub fn event_to_json_rpc_notification(event: Event) -> Value {
|
||||
let id: JSONRPCEventType = event.typ.into();
|
||||
json!({
|
||||
"event": id,
|
||||
"contextId": event.id,
|
||||
})
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Event {
|
||||
/// Event payload.
|
||||
event: EventType,
|
||||
|
||||
/// Account ID.
|
||||
context_id: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[serde(tag = "type", rename = "Event")]
|
||||
pub enum JSONRPCEventType {
|
||||
impl From<CoreEvent> for Event {
|
||||
fn from(event: CoreEvent) -> Self {
|
||||
Event {
|
||||
event: event.typ.into(),
|
||||
context_id: event.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum EventType {
|
||||
/// 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
|
||||
@@ -47,6 +57,9 @@ pub enum JSONRPCEventType {
|
||||
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,
|
||||
@@ -161,6 +174,13 @@ pub enum JSONRPCEventType {
|
||||
msg_id: u32,
|
||||
},
|
||||
|
||||
/// A single message is deleted.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MsgDeleted {
|
||||
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()
|
||||
@@ -283,26 +303,27 @@ pub enum JSONRPCEventType {
|
||||
},
|
||||
}
|
||||
|
||||
impl From<EventType> for JSONRPCEventType {
|
||||
fn from(event: EventType) -> Self {
|
||||
use JSONRPCEventType::*;
|
||||
impl From<CoreEventType> for EventType {
|
||||
fn from(event: CoreEventType) -> Self {
|
||||
use EventType::*;
|
||||
match event {
|
||||
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::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 {
|
||||
CoreEventType::Info(msg) => Info { msg },
|
||||
CoreEventType::SmtpConnected(msg) => SmtpConnected { msg },
|
||||
CoreEventType::ImapConnected(msg) => ImapConnected { msg },
|
||||
CoreEventType::SmtpMessageSent(msg) => SmtpMessageSent { msg },
|
||||
CoreEventType::ImapMessageDeleted(msg) => ImapMessageDeleted { msg },
|
||||
CoreEventType::ImapMessageMoved(msg) => ImapMessageMoved { msg },
|
||||
CoreEventType::ImapInboxIdle => ImapInboxIdle,
|
||||
CoreEventType::NewBlobFile(file) => NewBlobFile { file },
|
||||
CoreEventType::DeletedBlobFile(file) => DeletedBlobFile { file },
|
||||
CoreEventType::Warning(msg) => Warning { msg },
|
||||
CoreEventType::Error(msg) => Error { msg },
|
||||
CoreEventType::ErrorSelfNotInGroup(msg) => ErrorSelfNotInGroup { msg },
|
||||
CoreEventType::MsgsChanged { chat_id, msg_id } => MsgsChanged {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
EventType::ReactionsChanged {
|
||||
CoreEventType::ReactionsChanged {
|
||||
chat_id,
|
||||
msg_id,
|
||||
contact_id,
|
||||
@@ -311,92 +332,80 @@ impl From<EventType> for JSONRPCEventType {
|
||||
msg_id: msg_id.to_u32(),
|
||||
contact_id: contact_id.to_u32(),
|
||||
},
|
||||
EventType::IncomingMsg { chat_id, msg_id } => IncomingMsg {
|
||||
CoreEventType::IncomingMsg { chat_id, msg_id } => IncomingMsg {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
EventType::IncomingMsgBunch { msg_ids } => IncomingMsgBunch {
|
||||
CoreEventType::IncomingMsgBunch { msg_ids } => IncomingMsgBunch {
|
||||
msg_ids: msg_ids.into_iter().map(|id| id.to_u32()).collect(),
|
||||
},
|
||||
EventType::MsgsNoticed(chat_id) => MsgsNoticed {
|
||||
CoreEventType::MsgsNoticed(chat_id) => MsgsNoticed {
|
||||
chat_id: chat_id.to_u32(),
|
||||
},
|
||||
EventType::MsgDelivered { chat_id, msg_id } => MsgDelivered {
|
||||
CoreEventType::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 {
|
||||
CoreEventType::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 {
|
||||
CoreEventType::MsgRead { chat_id, msg_id } => MsgRead {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
EventType::ChatModified(chat_id) => ChatModified {
|
||||
CoreEventType::MsgDeleted { chat_id, msg_id } => MsgDeleted {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
CoreEventType::ChatModified(chat_id) => ChatModified {
|
||||
chat_id: chat_id.to_u32(),
|
||||
},
|
||||
EventType::ChatEphemeralTimerModified { chat_id, timer } => {
|
||||
CoreEventType::ChatEphemeralTimerModified { chat_id, timer } => {
|
||||
ChatEphemeralTimerModified {
|
||||
chat_id: chat_id.to_u32(),
|
||||
timer: timer.to_u32(),
|
||||
}
|
||||
}
|
||||
EventType::ContactsChanged(contact) => ContactsChanged {
|
||||
CoreEventType::ContactsChanged(contact) => ContactsChanged {
|
||||
contact_id: contact.map(|c| c.to_u32()),
|
||||
},
|
||||
EventType::LocationChanged(contact) => LocationChanged {
|
||||
CoreEventType::LocationChanged(contact) => LocationChanged {
|
||||
contact_id: contact.map(|c| c.to_u32()),
|
||||
},
|
||||
EventType::ConfigureProgress { progress, comment } => {
|
||||
CoreEventType::ConfigureProgress { progress, comment } => {
|
||||
ConfigureProgress { progress, comment }
|
||||
}
|
||||
EventType::ImexProgress(progress) => ImexProgress { progress },
|
||||
EventType::ImexFileWritten(path) => ImexFileWritten {
|
||||
CoreEventType::ImexProgress(progress) => ImexProgress { progress },
|
||||
CoreEventType::ImexFileWritten(path) => ImexFileWritten {
|
||||
path: path.to_str().unwrap_or_default().to_owned(),
|
||||
},
|
||||
EventType::SecurejoinInviterProgress {
|
||||
CoreEventType::SecurejoinInviterProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
} => SecurejoinInviterProgress {
|
||||
contact_id: contact_id.to_u32(),
|
||||
progress,
|
||||
},
|
||||
EventType::SecurejoinJoinerProgress {
|
||||
CoreEventType::SecurejoinJoinerProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
} => SecurejoinJoinerProgress {
|
||||
contact_id: contact_id.to_u32(),
|
||||
progress,
|
||||
},
|
||||
EventType::ConnectivityChanged => ConnectivityChanged,
|
||||
EventType::SelfavatarChanged => SelfavatarChanged,
|
||||
EventType::WebxdcStatusUpdate {
|
||||
CoreEventType::ConnectivityChanged => ConnectivityChanged,
|
||||
CoreEventType::SelfavatarChanged => SelfavatarChanged,
|
||||
CoreEventType::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 {
|
||||
CoreEventType::WebxdcInstanceDeleted { msg_id } => WebxdcInstanceDeleted {
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn generate_events_ts_types_definition() {
|
||||
let events = {
|
||||
let mut buf = Vec::new();
|
||||
let options = typescript_type_def::DefinitionFileOptions {
|
||||
root_namespace: None,
|
||||
..typescript_type_def::DefinitionFileOptions::default()
|
||||
};
|
||||
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();
|
||||
}
|
||||
29
deltachat-jsonrpc/src/api/types/http.rs
Normal file
29
deltachat-jsonrpc/src/api/types/http.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use deltachat::net::HttpResponse as CoreHttpResponse;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
pub struct HttpResponse {
|
||||
/// base64-encoded response body.
|
||||
blob: String,
|
||||
|
||||
/// MIME type, e.g. "text/plain" or "text/html".
|
||||
mimetype: Option<String>,
|
||||
|
||||
/// Encoding, e.g. "utf-8".
|
||||
encoding: Option<String>,
|
||||
}
|
||||
|
||||
impl From<CoreHttpResponse> for HttpResponse {
|
||||
fn from(response: CoreHttpResponse) -> Self {
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
let blob = general_purpose::STANDARD_NO_PAD.encode(response.blob);
|
||||
let mimetype = response.mimetype;
|
||||
let encoding = response.encoding;
|
||||
HttpResponse {
|
||||
blob,
|
||||
mimetype,
|
||||
encoding,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use deltachat::location::Location;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "Location", rename_all = "camelCase")]
|
||||
pub struct JsonrpcLocation {
|
||||
pub location_id: u32,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::{Context as _, Result};
|
||||
use deltachat::chat::Chat;
|
||||
use deltachat::chat::ChatItem;
|
||||
use deltachat::constants::Chattype;
|
||||
use deltachat::chat::ChatVisibility;
|
||||
use deltachat::contact::Contact;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::download;
|
||||
@@ -10,8 +10,7 @@ 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;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::color_int_to_hex_string;
|
||||
@@ -19,14 +18,14 @@ use super::contact::ContactObject;
|
||||
use super::reactions::JSONRPCReactions;
|
||||
use super::webxdc::WebxdcMessageInfo;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase", tag = "variant")]
|
||||
pub enum MessageLoadResult {
|
||||
Message(MessageObject),
|
||||
LoadingError { error: String },
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "Message", rename_all = "camelCase")]
|
||||
pub struct MessageObject {
|
||||
id: u32,
|
||||
@@ -35,7 +34,7 @@ pub struct MessageObject {
|
||||
quote: Option<MessageQuote>,
|
||||
parent_id: Option<u32>,
|
||||
|
||||
text: Option<String>,
|
||||
text: String,
|
||||
has_location: bool,
|
||||
has_html: bool,
|
||||
view_type: MessageViewtype,
|
||||
@@ -86,7 +85,7 @@ pub struct MessageObject {
|
||||
reactions: Option<JSONRPCReactions>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(tag = "kind")]
|
||||
enum MessageQuote {
|
||||
JustText {
|
||||
@@ -114,8 +113,12 @@ impl MessageObject {
|
||||
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<Self> {
|
||||
let message = Message::load_from_db(context, msg_id).await?;
|
||||
|
||||
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 sender_contact = Contact::get_by_id(context, message.get_from_id())
|
||||
.await
|
||||
.context("failed to load sender contact")?;
|
||||
let sender = ContactObject::try_from_dc_contact(context, sender_contact)
|
||||
.await
|
||||
.context("failed to load sender contact object")?;
|
||||
let file_bytes = message.get_filebytes(context).await?.unwrap_or_default();
|
||||
let override_sender_name = message.get_override_sender_name();
|
||||
|
||||
@@ -132,7 +135,9 @@ impl MessageObject {
|
||||
let quote = if let Some(quoted_text) = message.quoted_text() {
|
||||
match message.quoted_message(context).await? {
|
||||
Some(quote) => {
|
||||
let quote_author = Contact::load_from_db(context, quote.get_from_id()).await?;
|
||||
let quote_author = Contact::get_by_id(context, quote.get_from_id())
|
||||
.await
|
||||
.context("failed to load quote author contact")?;
|
||||
Some(MessageQuote::WithMessage {
|
||||
text: quoted_text,
|
||||
message_id: quote.get_id().to_u32(),
|
||||
@@ -160,7 +165,9 @@ impl MessageObject {
|
||||
None
|
||||
};
|
||||
|
||||
let reactions = get_msg_reactions(context, msg_id).await?;
|
||||
let reactions = get_msg_reactions(context, msg_id)
|
||||
.await
|
||||
.context("failed to load message reactions")?;
|
||||
let reactions = if reactions.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -180,7 +187,7 @@ impl MessageObject {
|
||||
state: message
|
||||
.get_state()
|
||||
.to_u32()
|
||||
.ok_or_else(|| anyhow!("state conversion to number failed"))?,
|
||||
.context("state conversion to number failed")?,
|
||||
error: message.error(),
|
||||
|
||||
timestamp: message.get_timestamp(),
|
||||
@@ -203,7 +210,7 @@ impl MessageObject {
|
||||
videochat_type: match message.get_videochat_type() {
|
||||
Some(vct) => Some(
|
||||
vct.to_u32()
|
||||
.ok_or_else(|| anyhow!("state conversion to number failed"))?,
|
||||
.context("videochat type conversion to number failed")?,
|
||||
),
|
||||
None => None,
|
||||
},
|
||||
@@ -230,7 +237,7 @@ impl MessageObject {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, TypeDef)]
|
||||
#[derive(Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "Viewtype")]
|
||||
pub enum MessageViewtype {
|
||||
Unknown,
|
||||
@@ -306,7 +313,7 @@ impl From<MessageViewtype> for Viewtype {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
pub enum DownloadState {
|
||||
Done,
|
||||
Available,
|
||||
@@ -325,7 +332,7 @@ impl From<download::DownloadState> for DownloadState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
pub enum SystemMessageType {
|
||||
Unknown,
|
||||
GroupNameChanged,
|
||||
@@ -380,7 +387,7 @@ impl From<deltachat::mimeparser::SystemMessage> for SystemMessageType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageNotificationInfo {
|
||||
id: u32,
|
||||
@@ -438,14 +445,22 @@ impl MessageNotificationInfo {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageSearchResult {
|
||||
id: u32,
|
||||
author_profile_image: Option<String>,
|
||||
/// if sender name if overridden it will show it as ~alias
|
||||
author_name: String,
|
||||
author_color: String,
|
||||
chat_name: Option<String>,
|
||||
author_id: u32,
|
||||
chat_profile_image: Option<String>,
|
||||
chat_color: String,
|
||||
chat_name: String,
|
||||
chat_type: u32,
|
||||
is_chat_protected: bool,
|
||||
is_chat_contact_request: bool,
|
||||
is_chat_archived: bool,
|
||||
message: String,
|
||||
timestamp: i64,
|
||||
}
|
||||
@@ -454,30 +469,44 @@ 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 sender = Contact::get_by_id(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,
|
||||
};
|
||||
let chat_profile_image = match chat.get_profile_image(context).await? {
|
||||
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let author_name = if let Some(name) = message.get_override_sender_name() {
|
||||
format!("~{name}")
|
||||
} else {
|
||||
sender.get_display_name().to_owned()
|
||||
};
|
||||
let chat_color = color_int_to_hex_string(chat.get_color(context).await?);
|
||||
|
||||
Ok(Self {
|
||||
id: msg_id.to_u32(),
|
||||
author_profile_image: profile_image,
|
||||
author_name: sender.get_display_name().to_owned(),
|
||||
author_name,
|
||||
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(),
|
||||
author_id: sender.id.to_u32(),
|
||||
chat_name: chat.get_name().to_owned(),
|
||||
chat_color,
|
||||
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
|
||||
chat_profile_image,
|
||||
is_chat_protected: chat.is_protected(),
|
||||
is_chat_contact_request: chat.is_contact_request(),
|
||||
is_chat_archived: chat.get_visibility() == ChatVisibility::Archived,
|
||||
message: message.get_text(),
|
||||
timestamp: message.get_timestamp(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase", rename = "MessageListItem", tag = "kind")]
|
||||
pub enum JSONRPCMessageListItem {
|
||||
Message {
|
||||
@@ -502,3 +531,22 @@ impl From<ChatItem> for JSONRPCMessageListItem {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[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>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageReadReceipt {
|
||||
pub contact_id: u32,
|
||||
pub timestamp: i64,
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ pub mod account;
|
||||
pub mod chat;
|
||||
pub mod chat_list;
|
||||
pub mod contact;
|
||||
pub mod events;
|
||||
pub mod http;
|
||||
pub mod location;
|
||||
pub mod message;
|
||||
pub mod provider_info;
|
||||
|
||||
@@ -3,12 +3,12 @@ use num_traits::cast::ToPrimitive;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
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 {
|
||||
|
||||
@@ -2,7 +2,7 @@ use deltachat::qr::Qr;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "Qr", rename_all = "camelCase")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum QrObject {
|
||||
@@ -32,6 +32,9 @@ pub enum QrObject {
|
||||
Account {
|
||||
domain: String,
|
||||
},
|
||||
Backup {
|
||||
ticket: String,
|
||||
},
|
||||
WebrtcInstance {
|
||||
domain: String,
|
||||
instance_pattern: String,
|
||||
@@ -126,6 +129,9 @@ impl From<Qr> for QrObject {
|
||||
}
|
||||
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,
|
||||
|
||||
@@ -1,23 +1,37 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use deltachat::contact::ContactId;
|
||||
use deltachat::reaction::Reactions;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
/// A single reaction emoji.
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "Reaction", rename_all = "camelCase")]
|
||||
pub struct JSONRPCReaction {
|
||||
/// Emoji.
|
||||
emoji: String,
|
||||
|
||||
/// Emoji frequency.
|
||||
count: usize,
|
||||
|
||||
/// True if we reacted with this emoji.
|
||||
is_from_self: bool,
|
||||
}
|
||||
|
||||
/// Structure representing all reactions to a particular message.
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[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>,
|
||||
/// Unique reactions and their count, sorted in descending order.
|
||||
reactions: Vec<JSONRPCReaction>,
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -30,18 +44,29 @@ impl From<Reactions> for JSONRPCReactions {
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let self_reactions = reactions_by_contact.get(&ContactId::SELF.to_u32());
|
||||
|
||||
let mut reactions_v = Vec::new();
|
||||
for (emoji, count) in reactions.emoji_sorted_by_frequency() {
|
||||
let is_from_self = if let Some(self_reactions) = self_reactions {
|
||||
self_reactions.contains(&emoji)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let reaction = JSONRPCReaction {
|
||||
emoji,
|
||||
count,
|
||||
is_from_self,
|
||||
};
|
||||
reactions_v.push(reaction)
|
||||
}
|
||||
|
||||
JSONRPCReactions {
|
||||
reactions_by_contact,
|
||||
reactions: unique_reactions,
|
||||
reactions: reactions_v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use typescript_type_def::TypeDef;
|
||||
|
||||
use super::maybe_empty_string_to_option;
|
||||
|
||||
#[derive(Serialize, TypeDef)]
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "WebxdcMessageInfo", rename_all = "camelCase")]
|
||||
pub struct WebxdcMessageInfo {
|
||||
/// The name of the app.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
pub mod api;
|
||||
pub use api::events;
|
||||
pub use yerpc;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -6,7 +6,6 @@ use yerpc::axum::handle_ws_rpc;
|
||||
use yerpc::{RpcClient, RpcSession};
|
||||
|
||||
mod api;
|
||||
use api::events::event_to_json_rpc_notification;
|
||||
use api::{Accounts, CommandApi};
|
||||
|
||||
const DEFAULT_PORT: u16 = 20808;
|
||||
@@ -44,12 +43,5 @@ async fn main() -> Result<(), std::io::Error> {
|
||||
async fn handler(ws: WebSocketUpgrade, Extension(api): Extension<CommandApi>) -> Response {
|
||||
let (client, out_receiver) = RpcClient::new();
|
||||
let session = RpcSession::new(client.clone(), api.clone());
|
||||
tokio::spawn(async move {
|
||||
let events = api.accounts.read().await.get_event_emitter();
|
||||
while let Some(event) = events.recv().await {
|
||||
let event = event_to_json_rpc_notification(event);
|
||||
client.send_notification("event", Some(event)).await.ok();
|
||||
}
|
||||
});
|
||||
handle_ws_rpc(ws, out_receiver, session).await
|
||||
}
|
||||
|
||||
@@ -4,3 +4,9 @@ docs
|
||||
coverage
|
||||
yarn*
|
||||
package-lock.json
|
||||
.prettierignore
|
||||
example.html
|
||||
report_api_coverage.mjs
|
||||
scripts
|
||||
dist/example
|
||||
dist/test
|
||||
@@ -67,7 +67,7 @@ async function run() {
|
||||
null,
|
||||
null
|
||||
);
|
||||
for (const [chatId, _messageId] of chats) {
|
||||
for (const chatId of chats) {
|
||||
const chat = await client.rpc.getFullChatById(selectedAccount, chatId);
|
||||
write($main, `<h3>${chat.name}</h3>`);
|
||||
const messageIds = await client.rpc.getMessageIds(
|
||||
|
||||
@@ -14,7 +14,7 @@ async function run() {
|
||||
throw new Error(
|
||||
"USAGE: node node-add-account.js <EMAILADDRESS> <PASSWORD>"
|
||||
);
|
||||
console.log(`creating acccount for ${email}`);
|
||||
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);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"dependencies": {
|
||||
"@deltachat/tiny-emitter": "3.0.0",
|
||||
"isomorphic-ws": "^4.0.1",
|
||||
"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,12 +24,19 @@
|
||||
"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 extract-constants 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",
|
||||
@@ -48,5 +55,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "1.109.0"
|
||||
"version": "1.120.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 =
|
||||
|
||||
@@ -1,34 +1,28 @@
|
||||
import * as T from "../generated/types.js";
|
||||
import { EventType } from "../generated/types.js";
|
||||
import * as RPC from "../generated/jsonrpc.js";
|
||||
import { RawClient } from "../generated/client.js";
|
||||
import { Event } from "../generated/events.js";
|
||||
import { WebsocketTransport, BaseTransport, Request } from "yerpc";
|
||||
import { TinyEmitter } from "@deltachat/tiny-emitter";
|
||||
|
||||
type DCWireEvent<T extends Event> = {
|
||||
event: T;
|
||||
contextId: number;
|
||||
};
|
||||
// export type Events = Record<
|
||||
// Event["type"] | "ALL",
|
||||
// (event: DeltaChatEvent<Event>) => void
|
||||
// >;
|
||||
|
||||
type Events = { ALL: (accountId: number, event: Event) => void } & {
|
||||
[Property in Event["type"]]: (
|
||||
type Events = { ALL: (accountId: number, event: EventType) => void } & {
|
||||
[Property in EventType["type"]]: (
|
||||
accountId: number,
|
||||
event: Extract<Event, { type: Property }>
|
||||
event: Extract<EventType, { type: Property }>
|
||||
) => void;
|
||||
};
|
||||
|
||||
type ContextEvents = { ALL: (event: Event) => void } & {
|
||||
[Property in Event["type"]]: (
|
||||
event: Extract<Event, { type: Property }>
|
||||
type ContextEvents = { ALL: (event: EventType) => void } & {
|
||||
[Property in EventType["type"]]: (
|
||||
event: Extract<EventType, { type: Property }>
|
||||
) => void;
|
||||
};
|
||||
|
||||
export type DcEvent = Event;
|
||||
export type DcEventType<T extends Event["type"]> = Extract<Event, { type: T }>;
|
||||
export type DcEvent = EventType;
|
||||
export type DcEventType<T extends EventType["type"]> = Extract<
|
||||
EventType,
|
||||
{ type: T }
|
||||
>;
|
||||
|
||||
export class BaseDeltaChat<
|
||||
Transport extends BaseTransport<any>
|
||||
@@ -36,27 +30,34 @@ export class BaseDeltaChat<
|
||||
rpc: RawClient;
|
||||
account?: T.Account;
|
||||
private contextEmitters: { [key: number]: TinyEmitter<ContextEvents> } = {};
|
||||
constructor(public transport: Transport) {
|
||||
|
||||
//@ts-ignore
|
||||
private eventTask: Promise<void>;
|
||||
|
||||
constructor(public transport: Transport, startEventLoop: boolean) {
|
||||
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 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 (startEventLoop) {
|
||||
this.eventTask = this.eventLoop();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.contextEmitters[event.contextId]) {
|
||||
this.contextEmitters[event.contextId].emit(
|
||||
event.event.type,
|
||||
//@ts-ignore
|
||||
event.event as any
|
||||
);
|
||||
this.contextEmitters[event.contextId].emit("ALL", event.event);
|
||||
}
|
||||
async eventLoop(): Promise<void> {
|
||||
while (true) {
|
||||
const event = await this.rpc.getNextEvent();
|
||||
//@ts-ignore
|
||||
this.emit(event.event.type, event.contextId, event.event);
|
||||
this.emit("ALL", event.contextId, event.event);
|
||||
|
||||
if (this.contextEmitters[event.contextId]) {
|
||||
this.contextEmitters[event.contextId].emit(
|
||||
event.event.type,
|
||||
//@ts-ignore
|
||||
event.event as any
|
||||
);
|
||||
this.contextEmitters[event.contextId].emit("ALL", event.event as any);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async listAccounts(): Promise<T.Account[]> {
|
||||
@@ -75,10 +76,12 @@ export class BaseDeltaChat<
|
||||
|
||||
export type Opts = {
|
||||
url: string;
|
||||
startEventLoop: boolean;
|
||||
};
|
||||
|
||||
export const DEFAULT_OPTS: Opts = {
|
||||
url: "ws://localhost:20808/ws",
|
||||
startEventLoop: true,
|
||||
};
|
||||
export class DeltaChat extends BaseDeltaChat<WebsocketTransport> {
|
||||
opts: Opts;
|
||||
@@ -86,20 +89,24 @@ export class DeltaChat extends BaseDeltaChat<WebsocketTransport> {
|
||||
this.transport.close();
|
||||
}
|
||||
constructor(opts?: Opts | string) {
|
||||
if (typeof opts === "string") opts = { url: opts };
|
||||
if (opts) opts = { ...DEFAULT_OPTS, ...opts };
|
||||
else opts = { ...DEFAULT_OPTS };
|
||||
if (typeof opts === "string") {
|
||||
opts = { ...DEFAULT_OPTS, url: opts };
|
||||
} else if (opts) {
|
||||
opts = { ...DEFAULT_OPTS, ...opts };
|
||||
} else {
|
||||
opts = { ...DEFAULT_OPTS };
|
||||
}
|
||||
const transport = new WebsocketTransport(opts.url);
|
||||
super(transport);
|
||||
super(transport, opts.startEventLoop);
|
||||
this.opts = opts;
|
||||
}
|
||||
}
|
||||
|
||||
export class StdioDeltaChat extends BaseDeltaChat<StdioTransport> {
|
||||
close() {}
|
||||
constructor(input: any, output: any) {
|
||||
constructor(input: any, output: any, startEventLoop: boolean) {
|
||||
const transport = new StdioTransport(input, output);
|
||||
super(transport);
|
||||
super(transport, startEventLoop);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export * as RPC from "../generated/jsonrpc.js";
|
||||
export * as T from "../generated/types.js";
|
||||
export * from "../generated/events.js";
|
||||
export { RawClient } from "../generated/client.js";
|
||||
export * from "./client.js";
|
||||
export * as yerpc from "yerpc";
|
||||
|
||||
@@ -12,7 +12,7 @@ describe("basic tests", () => {
|
||||
|
||||
before(async () => {
|
||||
serverHandle = await startServer();
|
||||
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout);
|
||||
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout, true);
|
||||
// dc.on("ALL", (event) => {
|
||||
//console.log("event", event);
|
||||
// });
|
||||
@@ -53,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);
|
||||
@@ -73,7 +73,7 @@ describe("basic tests", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("contact managment", function () {
|
||||
describe("contact management", function () {
|
||||
let accountId: number;
|
||||
before(async () => {
|
||||
accountId = await dc.rpc.addAccount();
|
||||
@@ -103,7 +103,7 @@ 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");
|
||||
});
|
||||
@@ -115,11 +115,11 @@ describe("basic tests", () => {
|
||||
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(
|
||||
@@ -128,7 +128,7 @@ describe("basic tests", () => {
|
||||
);
|
||||
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",
|
||||
@@ -140,7 +140,7 @@ describe("basic tests", () => {
|
||||
);
|
||||
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",
|
||||
|
||||
@@ -22,12 +22,12 @@ 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.stdin, serverHandle.stdout);
|
||||
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout, true);
|
||||
|
||||
dc.on("ALL", (contextId, { type }) => {
|
||||
if (type !== "Info") console.log(contextId, type);
|
||||
@@ -36,7 +36,7 @@ describe("online tests", function () {
|
||||
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();
|
||||
}
|
||||
@@ -44,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,7 +74,7 @@ describe("online tests", function () {
|
||||
accountsConfigured = true;
|
||||
});
|
||||
|
||||
it("send and recieve text message", async function () {
|
||||
it("send and receive text message", async function () {
|
||||
if (!accountsConfigured) {
|
||||
this.skip();
|
||||
}
|
||||
@@ -106,7 +106,7 @@ describe("online tests", function () {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
[package]
|
||||
name = "deltachat-repl"
|
||||
version = "1.109.0"
|
||||
version = "1.120.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 = "10"
|
||||
dirs = "5"
|
||||
log = "0.4.19"
|
||||
pretty_env_logger = "0.5"
|
||||
rusqlite = "0.29"
|
||||
rustyline = "12"
|
||||
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -29,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})...");
|
||||
if 0 != bits & 1 {
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM jobs;", paramsv![])
|
||||
.execute("DELETE FROM jobs;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
println!("(1) Jobs reset.");
|
||||
@@ -43,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.");
|
||||
@@ -51,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.");
|
||||
@@ -59,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.");
|
||||
@@ -199,7 +199,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
if msg.has_location() { "📍" } else { "" },
|
||||
&contact_name,
|
||||
contact_id,
|
||||
msgtext.unwrap_or_default(),
|
||||
msgtext,
|
||||
if msg.has_html() { "[HAS-HTML]️" } else { "" },
|
||||
if msg.get_from_id() == ContactId::SELF {
|
||||
""
|
||||
@@ -336,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\
|
||||
@@ -486,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?;
|
||||
@@ -550,7 +563,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
context.maybe_network().await;
|
||||
}
|
||||
"housekeeping" => {
|
||||
sql::housekeeping(&context).await.ok_or_log(&context);
|
||||
sql::housekeeping(&context).await.log_err(&context).ok();
|
||||
}
|
||||
"listchats" | "listarchived" | "chats" => {
|
||||
let listflags = if arg0 == "listarchived" {
|
||||
@@ -792,15 +805,30 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
}
|
||||
"chatinfo" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
let sel_chat_id = sel_chat.as_ref().unwrap().get_id();
|
||||
|
||||
let contacts =
|
||||
chat::get_chat_contacts(&context, sel_chat.as_ref().unwrap().get_id()).await?;
|
||||
let contacts = chat::get_chat_contacts(&context, sel_chat_id).await?;
|
||||
println!("Memberlist:");
|
||||
|
||||
log_contactlist(&context, &contacts).await?;
|
||||
println!("{} contacts", contacts.len());
|
||||
|
||||
let similar_chats = sel_chat_id.get_similar_chat_ids(&context).await?;
|
||||
if !similar_chats.is_empty() {
|
||||
println!("Similar chats: ");
|
||||
for (similar_chat_id, metric) in similar_chats {
|
||||
let similar_chat = Chat::load_from_db(&context, similar_chat_id).await?;
|
||||
println!(
|
||||
"{} (#{}) {:.1}",
|
||||
similar_chat.name,
|
||||
similar_chat_id,
|
||||
100.0 * metric
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
println!(
|
||||
"{} contacts\nLocation streaming: {}",
|
||||
contacts.len(),
|
||||
"Location streaming: {}",
|
||||
location::is_sending_locations_to_chat(
|
||||
&context,
|
||||
Some(sel_chat.as_ref().unwrap().get_id())
|
||||
@@ -869,7 +897,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" => {
|
||||
@@ -899,9 +927,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
Viewtype::File
|
||||
});
|
||||
msg.set_file(arg1, None);
|
||||
if !arg2.is_empty() {
|
||||
msg.set_text(Some(arg2.to_string()));
|
||||
}
|
||||
msg.set_text(arg2.to_string());
|
||||
chat::send_msg(&context, sel_chat.as_ref().unwrap().get_id(), &mut msg).await?;
|
||||
}
|
||||
"sendhtml" => {
|
||||
@@ -913,11 +939,11 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.set_html(Some(html.to_string()));
|
||||
msg.set_text(Some(if arg2.is_empty() {
|
||||
msg.set_text(if arg2.is_empty() {
|
||||
path.file_name().unwrap().to_string_lossy().to_string()
|
||||
} else {
|
||||
arg2.to_string()
|
||||
}));
|
||||
});
|
||||
chat::send_msg(&context, sel_chat.as_ref().unwrap().get_id(), &mut msg).await?;
|
||||
}
|
||||
"sendsyncmsg" => match context.send_sync_msg().await? {
|
||||
@@ -966,7 +992,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
|
||||
if !arg1.is_empty() {
|
||||
let mut draft = Message::new(Viewtype::Text);
|
||||
draft.set_text(Some(arg1.to_string()));
|
||||
draft.set_text(arg1.to_string());
|
||||
sel_chat
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
@@ -990,7 +1016,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
"Please specify text to add as device message."
|
||||
);
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.set_text(Some(arg1.to_string()));
|
||||
msg.set_text(arg1.to_string());
|
||||
chat::add_device_msg(&context, None, Some(&mut msg)).await?;
|
||||
}
|
||||
"listmedia" => {
|
||||
@@ -1077,7 +1103,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
"msginfo" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||
let id = MsgId::new(arg1.parse()?);
|
||||
let res = message::get_msg_info(&context, id).await?;
|
||||
let res = id.get_info(&context).await?;
|
||||
println!("{res}");
|
||||
}
|
||||
"download" => {
|
||||
|
||||
@@ -152,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",
|
||||
@@ -350,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) => {
|
||||
@@ -359,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;
|
||||
}
|
||||
}
|
||||
@@ -374,7 +376,7 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error: {err}");
|
||||
println!("Error: {err:#}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
373
deltachat-rpc-client/LICENSE
Normal file
373
deltachat-rpc-client/LICENSE
Normal file
@@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
@@ -5,9 +5,23 @@ 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`.
|
||||
To use Delta Chat RPC client, first build a `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`
|
||||
or download a prebuilt release.
|
||||
Install it anywhere in your `PATH`.
|
||||
|
||||
[Create a virtual environment](https://docs.python.org/3/library/venv.html)
|
||||
if you don't have one already and activate it.
|
||||
```
|
||||
$ python -m venv env
|
||||
$ . env/bin/activate
|
||||
```
|
||||
|
||||
Install `deltachat-rpc-client` from source:
|
||||
```
|
||||
$ cd deltachat-rpc-client
|
||||
$ pip install .
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
1. Build `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
|
||||
|
||||
@@ -6,7 +6,7 @@ import asyncio
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from deltachat_rpc_client import DeltaChat, EventType, Rpc
|
||||
from deltachat_rpc_client import DeltaChat, EventType, Rpc, SpecialContactId
|
||||
|
||||
|
||||
async def main():
|
||||
@@ -30,9 +30,9 @@ async def main():
|
||||
await deltachat.start_io()
|
||||
|
||||
async def process_messages():
|
||||
for message in await account.get_fresh_messages_in_arrival_order():
|
||||
for message in await account.get_next_messages():
|
||||
snapshot = await message.get_snapshot()
|
||||
if not snapshot.is_bot and not snapshot.is_info:
|
||||
if snapshot.from_id != SpecialContactId.SELF and not snapshot.is_bot and not snapshot.is_info:
|
||||
await snapshot.chat.send_text(snapshot.text)
|
||||
await snapshot.message.mark_seen()
|
||||
|
||||
|
||||
@@ -6,8 +6,22 @@ build-backend = "setuptools.build_meta"
|
||||
name = "deltachat-rpc-client"
|
||||
description = "Python client for Delta Chat core JSON-RPC interface"
|
||||
dependencies = [
|
||||
"aiohttp",
|
||||
"aiodns"
|
||||
"aiohttp"
|
||||
]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Framework :: AsyncIO",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
|
||||
"Operating System :: POSIX :: Linux",
|
||||
"Operating System :: MacOS :: MacOS X",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Topic :: Communications :: Chat",
|
||||
"Topic :: Communications :: Email"
|
||||
]
|
||||
dynamic = [
|
||||
"version"
|
||||
@@ -21,11 +35,42 @@ deltachat_rpc_client = [
|
||||
[project.entry-points.pytest11]
|
||||
"deltachat_rpc_client.pytestplugin" = "deltachat_rpc_client.pytestplugin"
|
||||
|
||||
[tool.setuptools_scm]
|
||||
root = ".."
|
||||
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
|
||||
[tool.ruff]
|
||||
select = ["E", "F", "W", "N", "YTT", "B", "C4", "ISC", "ICN", "PT", "RET", "SIM", "TID", "ARG", "DTZ", "ERA", "PLC", "PLE", "PLW", "PIE", "COM"]
|
||||
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]
|
||||
|
||||
@@ -3,7 +3,7 @@ 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 .const import EventType, SpecialContactId
|
||||
from .contact import Contact
|
||||
from .deltachat import DeltaChat
|
||||
from .message import Message
|
||||
@@ -19,6 +19,7 @@ __all__ = [
|
||||
"DeltaChat",
|
||||
"EventType",
|
||||
"Message",
|
||||
"SpecialContactId",
|
||||
"Rpc",
|
||||
"run_bot_cli",
|
||||
"run_client_cli",
|
||||
|
||||
@@ -27,7 +27,7 @@ def _to_attrdict(obj):
|
||||
|
||||
|
||||
class AttrDict(dict):
|
||||
"""Dictionary that allows accessing values usin the "dot notation" as attributes."""
|
||||
"""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()})
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
from typing import TYPE_CHECKING, List, Optional, Tuple, Union
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, List, Optional, Tuple, Union
|
||||
from warnings import warn
|
||||
|
||||
from ._utils import AttrDict
|
||||
from .chat import Chat
|
||||
from .const import ChatlistFlag, ContactFlag, SpecialContactId
|
||||
from .contact import Contact
|
||||
from .message import Message
|
||||
from .rpc import Rpc
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .deltachat import DeltaChat
|
||||
from .rpc import Rpc
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -20,7 +21,7 @@ class Account:
|
||||
id: int
|
||||
|
||||
@property
|
||||
def _rpc(self) -> Rpc:
|
||||
def _rpc(self) -> "Rpc":
|
||||
return self.manager.rpc
|
||||
|
||||
async def wait_for_event(self) -> AttrDict:
|
||||
@@ -158,7 +159,7 @@ class Account:
|
||||
: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 withot "Device chat" and contact requests.
|
||||
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.
|
||||
@@ -176,7 +177,7 @@ class Account:
|
||||
|
||||
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]
|
||||
return [Chat(self, entry) for entry in entries]
|
||||
|
||||
items = await self._rpc.get_chatlist_items_by_entries(self.id, entries)
|
||||
chats = []
|
||||
@@ -239,7 +240,30 @@ class Account:
|
||||
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_next_messages(self) -> List[Message]:
|
||||
"""Return a list of next messages."""
|
||||
next_msg_ids = await self._rpc.get_next_msgs(self.id)
|
||||
return [Message(self, msg_id) for msg_id in next_msg_ids]
|
||||
|
||||
async def wait_next_messages(self) -> List[Message]:
|
||||
"""Wait for new messages and return a list of them."""
|
||||
next_msg_ids = await self._rpc.wait_next_msgs(self.id)
|
||||
return [Message(self, msg_id) for msg_id in next_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."""
|
||||
warn(
|
||||
"get_fresh_messages_in_arrival_order is deprecated, use get_next_messages instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
fresh_msg_ids = sorted(await self._rpc.get_fresh_msgs(self.id))
|
||||
return [Message(self, msg_id) for msg_id in fresh_msg_ids]
|
||||
|
||||
async def export_backup(self, path, passphrase: str = "") -> None:
|
||||
"""Export backup."""
|
||||
await self._rpc.export_backup(self.id, str(path), passphrase)
|
||||
|
||||
async def import_backup(self, path, passphrase: str = "") -> None:
|
||||
"""Import backup."""
|
||||
await self._rpc.import_backup(self.id, str(path), passphrase)
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import calendar
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from ._utils import AttrDict
|
||||
from .const import ChatVisibility
|
||||
from .const import ChatVisibility, ViewType
|
||||
from .contact import Contact
|
||||
from .message import Message
|
||||
from .rpc import Rpc
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from datetime import datetime
|
||||
|
||||
from .account import Account
|
||||
from .rpc import Rpc
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -21,7 +22,7 @@ class Chat:
|
||||
id: int
|
||||
|
||||
@property
|
||||
def _rpc(self) -> Rpc:
|
||||
def _rpc(self) -> "Rpc":
|
||||
return self.account._rpc
|
||||
|
||||
async def delete(self) -> None:
|
||||
@@ -104,18 +105,34 @@ class Chat:
|
||||
info = await self._rpc.get_full_chat_by_id(self.account.id, self.id)
|
||||
return AttrDict(chat=self, **info)
|
||||
|
||||
async def can_send(self) -> bool:
|
||||
"""Return true if messages can be sent to the chat."""
|
||||
return await self._rpc.can_send(self.account.id, self.id)
|
||||
|
||||
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
|
||||
|
||||
msg_id, _ = await self._rpc.misc_send_msg(self.account.id, self.id, text, file, location, quoted_msg)
|
||||
draft = {
|
||||
"text": text,
|
||||
"html": html,
|
||||
"viewtype": viewtype,
|
||||
"file": file,
|
||||
"location": location,
|
||||
"overrideSenderName": override_sender_name,
|
||||
"quotedMessageId": 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:
|
||||
@@ -181,19 +198,23 @@ class Chat:
|
||||
"""Add contacts to this group."""
|
||||
for cnt in contact:
|
||||
if isinstance(cnt, str):
|
||||
cnt = (await self.account.create_contact(cnt)).id
|
||||
contact_id = (await self.account.create_contact(cnt)).id
|
||||
elif not isinstance(cnt, int):
|
||||
cnt = cnt.id
|
||||
await self._rpc.add_contact_to_chat(self.account.id, self.id, cnt)
|
||||
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):
|
||||
cnt = (await self.account.create_contact(cnt)).id
|
||||
contact_id = (await self.account.create_contact(cnt)).id
|
||||
elif not isinstance(cnt, int):
|
||||
cnt = cnt.id
|
||||
await self._rpc.remove_contact_from_chat(self.account.id, self.id, cnt)
|
||||
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.
|
||||
@@ -217,8 +238,8 @@ class Chat:
|
||||
async def get_locations(
|
||||
self,
|
||||
contact: Optional[Contact] = None,
|
||||
timestamp_from: Optional[datetime] = None,
|
||||
timestamp_to: Optional[datetime] = 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
|
||||
@@ -229,9 +250,9 @@ class Chat:
|
||||
locations = []
|
||||
contacts: Dict[int, Contact] = {}
|
||||
for loc in result:
|
||||
loc = AttrDict(loc)
|
||||
loc["chat"] = self
|
||||
loc["contact"] = contacts.setdefault(loc.contact_id, Contact(self.account, loc.contact_id))
|
||||
loc["message"] = Message(self.account, loc.msg_id)
|
||||
locations.append(loc)
|
||||
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
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import inspect
|
||||
import logging
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
Coroutine,
|
||||
Dict,
|
||||
@@ -13,15 +14,13 @@ from typing import (
|
||||
Union,
|
||||
)
|
||||
|
||||
from deltachat_rpc_client.account import Account
|
||||
|
||||
from ._utils import (
|
||||
AttrDict,
|
||||
parse_system_add_remove,
|
||||
parse_system_image_changed,
|
||||
parse_system_title_changed,
|
||||
)
|
||||
from .const import COMMAND_PREFIX, EventType, SystemMessageType
|
||||
from .const import COMMAND_PREFIX, EventType, SpecialContactId, SystemMessageType
|
||||
from .events import (
|
||||
EventFilter,
|
||||
GroupImageChanged,
|
||||
@@ -31,13 +30,16 @@ from .events import (
|
||||
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,
|
||||
account: "Account",
|
||||
hooks: Optional[Iterable[Tuple[Callable, Union[type, EventFilter]]]] = None,
|
||||
logger: Optional[logging.Logger] = None,
|
||||
) -> None:
|
||||
@@ -187,9 +189,10 @@ class Client:
|
||||
|
||||
async def _process_messages(self) -> None:
|
||||
if self._should_process_messages:
|
||||
for message in await self.account.get_fresh_messages_in_arrival_order():
|
||||
for message in await self.account.get_next_messages():
|
||||
snapshot = await message.get_snapshot()
|
||||
await self._on_new_msg(snapshot)
|
||||
if snapshot.from_id not in [SpecialContactId.SELF, SpecialContactId.DEVICE]:
|
||||
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()
|
||||
|
||||
@@ -31,6 +31,7 @@ class EventType(str, Enum):
|
||||
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"
|
||||
@@ -44,6 +45,7 @@ class EventType(str, Enum):
|
||||
MSG_DELIVERED = "MsgDelivered"
|
||||
MSG_FAILED = "MsgFailed"
|
||||
MSG_READ = "MsgRead"
|
||||
MSG_DELETED = "MsgDeleted"
|
||||
CHAT_MODIFIED = "ChatModified"
|
||||
CHAT_EPHEMERAL_TIMER_MODIFIED = "ChatEphemeralTimerModified"
|
||||
CONTACTS_CHANGED = "ContactsChanged"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ._utils import AttrDict
|
||||
from .rpc import Rpc
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .account import Account
|
||||
from .chat import Chat
|
||||
from .rpc import Rpc
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -21,7 +21,7 @@ class Contact:
|
||||
id: int
|
||||
|
||||
@property
|
||||
def _rpc(self) -> Rpc:
|
||||
def _rpc(self) -> "Rpc":
|
||||
return self.account._rpc
|
||||
|
||||
async def block(self) -> None:
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from typing import Dict, List
|
||||
from typing import TYPE_CHECKING, Dict, List
|
||||
|
||||
from ._utils import AttrDict
|
||||
from .account import Account
|
||||
from .rpc import Rpc
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .rpc import Rpc
|
||||
|
||||
|
||||
class DeltaChat:
|
||||
@@ -11,7 +13,7 @@ class DeltaChat:
|
||||
This is the root of the object oriented API.
|
||||
"""
|
||||
|
||||
def __init__(self, rpc: Rpc) -> None:
|
||||
def __init__(self, rpc: "Rpc") -> None:
|
||||
self.rpc = rpc
|
||||
|
||||
async def add_account(self) -> Account:
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
import inspect
|
||||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Callable, Iterable, Iterator, Optional, Set, Tuple, Union
|
||||
from typing import TYPE_CHECKING, Callable, Iterable, Iterator, Optional, Set, Tuple, Union
|
||||
|
||||
from ._utils import AttrDict
|
||||
from .const import EventType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ._utils import AttrDict
|
||||
|
||||
|
||||
def _tuple_of(obj, type_: type) -> tuple:
|
||||
if not obj:
|
||||
@@ -80,7 +82,7 @@ class RawEvent(EventFilter):
|
||||
return (self.types, self.func) == (other.types, other.func)
|
||||
return False
|
||||
|
||||
async def filter(self, event: AttrDict) -> bool:
|
||||
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)
|
||||
@@ -118,7 +120,7 @@ class NewMessage(EventFilter):
|
||||
command: Optional[str] = None,
|
||||
is_bot: Optional[bool] = False,
|
||||
is_info: Optional[bool] = None,
|
||||
func: Optional[Callable[[AttrDict], bool]] = None,
|
||||
func: Optional[Callable[["AttrDict"], bool]] = None,
|
||||
) -> None:
|
||||
super().__init__(func=func)
|
||||
self.is_bot = is_bot
|
||||
@@ -157,7 +159,7 @@ class NewMessage(EventFilter):
|
||||
)
|
||||
return False
|
||||
|
||||
async def filter(self, event: AttrDict) -> bool:
|
||||
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:
|
||||
@@ -199,7 +201,7 @@ class MemberListChanged(EventFilter):
|
||||
return (self.added, self.func) == (other.added, other.func)
|
||||
return False
|
||||
|
||||
async def filter(self, event: AttrDict) -> bool:
|
||||
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)
|
||||
@@ -231,7 +233,7 @@ class GroupImageChanged(EventFilter):
|
||||
return (self.deleted, self.func) == (other.deleted, other.func)
|
||||
return False
|
||||
|
||||
async def filter(self, event: AttrDict) -> bool:
|
||||
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)
|
||||
@@ -256,7 +258,7 @@ class GroupNameChanged(EventFilter):
|
||||
return self.func == other.func
|
||||
return False
|
||||
|
||||
async def filter(self, event: AttrDict) -> bool:
|
||||
async def filter(self, event: "AttrDict") -> bool:
|
||||
return await self._call_func(event)
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import json
|
||||
from typing import TYPE_CHECKING, Union
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
|
||||
from ._utils import AttrDict
|
||||
from .contact import Contact
|
||||
from .rpc import Rpc
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .account import Account
|
||||
from .rpc import Rpc
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -18,7 +18,7 @@ class Message:
|
||||
id: int
|
||||
|
||||
@property
|
||||
def _rpc(self) -> Rpc:
|
||||
def _rpc(self) -> "Rpc":
|
||||
return self.account._rpc
|
||||
|
||||
async def send_reaction(self, *reaction: str):
|
||||
@@ -35,6 +35,13 @@ class Message:
|
||||
snapshot["message"] = self
|
||||
return snapshot
|
||||
|
||||
async def get_reactions(self) -> Optional[AttrDict]:
|
||||
"""Get message reactions."""
|
||||
reactions = await self._rpc.get_message_reactions(self.account.id, self.id)
|
||||
if reactions:
|
||||
return AttrDict(reactions)
|
||||
return None
|
||||
|
||||
async def mark_seen(self) -> None:
|
||||
"""Mark the message as seen."""
|
||||
await self._rpc.markseen_msgs(self.account.id, [self.id])
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from typing import AsyncGenerator, List, Optional
|
||||
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import pytest_asyncio
|
||||
|
||||
@@ -16,9 +16,8 @@ async def get_temp_credentials() -> dict:
|
||||
|
||||
# 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())
|
||||
async with aiohttp.ClientSession() as session, session.post(url, timeout=timeout) as response:
|
||||
return json.loads(await response.text())
|
||||
|
||||
|
||||
class ACFactory:
|
||||
@@ -55,6 +54,11 @@ class ACFactory:
|
||||
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]:
|
||||
|
||||
@@ -23,7 +23,9 @@ class Rpc:
|
||||
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.closing: bool
|
||||
self.reader_task: asyncio.Task
|
||||
self.events_task: asyncio.Task
|
||||
|
||||
async def start(self) -> None:
|
||||
self.process = await asyncio.create_subprocess_exec(
|
||||
@@ -35,10 +37,15 @@ class Rpc:
|
||||
self.id = 0
|
||||
self.event_queues = {}
|
||||
self.request_events = {}
|
||||
self.closing = False
|
||||
self.reader_task = asyncio.create_task(self.reader_loop())
|
||||
self.events_task = asyncio.create_task(self.events_loop())
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Terminate RPC server process and wait until the reader loop finishes."""
|
||||
self.closing = True
|
||||
await self.stop_io_for_all_accounts()
|
||||
await self.events_task
|
||||
self.process.terminate()
|
||||
await self.reader_task
|
||||
|
||||
@@ -58,33 +65,38 @@ class Rpc:
|
||||
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 get_queue(self, account_id: int) -> asyncio.Queue:
|
||||
if account_id not in self.event_queues:
|
||||
self.event_queues[account_id] = asyncio.Queue()
|
||||
return self.event_queues[account_id]
|
||||
|
||||
async def events_loop(self) -> None:
|
||||
"""Requests new events and distributes them between queues."""
|
||||
while True:
|
||||
if self.closing:
|
||||
return
|
||||
event = await self.get_next_event()
|
||||
account_id = event["contextId"]
|
||||
queue = await self.get_queue(account_id)
|
||||
await queue.put(event["event"])
|
||||
|
||||
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
|
||||
queue = await self.get_queue(account_id)
|
||||
return await queue.get()
|
||||
|
||||
def __getattr__(self, attr: str):
|
||||
async def method(*args, **kwargs) -> Any:
|
||||
async def method(*args) -> 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,
|
||||
"params": args,
|
||||
"id": self.id,
|
||||
}
|
||||
data = (json.dumps(request) + "\n").encode()
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import asyncio
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
import asyncio
|
||||
|
||||
from deltachat_rpc_client import EventType, events
|
||||
from deltachat_rpc_client.rpc import JsonRpcError
|
||||
|
||||
@@ -99,8 +98,18 @@ async def test_account(acfactory) -> None:
|
||||
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()
|
||||
assert await alice.get_fresh_messages()
|
||||
assert await alice.get_next_messages()
|
||||
|
||||
# Test sending empty message.
|
||||
assert len(await bob.wait_next_messages()) == 0
|
||||
await alice_chat_bob.send_text("")
|
||||
messages = await bob.wait_next_messages()
|
||||
assert len(messages) == 1
|
||||
message = messages[0]
|
||||
snapshot = await message.get_snapshot()
|
||||
assert snapshot.text == ""
|
||||
await bob.mark_seen_messages([message])
|
||||
|
||||
group = await alice.create_group("test group")
|
||||
await group.add_contact(alice_contact_bob)
|
||||
@@ -148,7 +157,9 @@ async def test_chat(acfactory) -> None:
|
||||
assert alice_chat_bob != bob_chat_alice
|
||||
assert repr(alice_chat_bob)
|
||||
await alice_chat_bob.delete()
|
||||
assert not await bob_chat_alice.can_send()
|
||||
await bob_chat_alice.accept()
|
||||
assert await bob_chat_alice.can_send()
|
||||
await bob_chat_alice.block()
|
||||
bob_chat_alice = await snapshot.sender.create_chat()
|
||||
await bob_chat_alice.mute()
|
||||
@@ -238,6 +249,10 @@ async def test_message(acfactory) -> None:
|
||||
|
||||
await message.mark_seen()
|
||||
await message.send_reaction("😎")
|
||||
reactions = await message.get_reactions()
|
||||
assert reactions
|
||||
snapshot = await message.get_snapshot()
|
||||
assert reactions == snapshot.reactions
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
@@ -304,3 +319,39 @@ async def test_bot(acfactory) -> None:
|
||||
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)
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_wait_next_messages(acfactory) -> None:
|
||||
alice = await acfactory.new_configured_account()
|
||||
|
||||
# Create a bot account so it does not receive device messages in the beginning.
|
||||
bot = await acfactory.new_preconfigured_account()
|
||||
await bot.set_config("bot", "1")
|
||||
await bot.configure()
|
||||
|
||||
# There are no old messages and the call returns immediately.
|
||||
assert not await bot.wait_next_messages()
|
||||
|
||||
# Bot starts waiting for messages.
|
||||
next_messages_task = asyncio.create_task(bot.wait_next_messages())
|
||||
|
||||
bot_addr = await bot.get_config("addr")
|
||||
alice_contact_bot = await alice.create_contact(bot_addr, "Bob")
|
||||
alice_chat_bot = await alice_contact_bot.create_chat()
|
||||
await alice_chat_bot.send_text("Hello!")
|
||||
|
||||
next_messages = await next_messages_task
|
||||
assert len(next_messages) == 1
|
||||
snapshot = await next_messages[0].get_snapshot()
|
||||
assert snapshot.text == "Hello!"
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
async def test_import_export(acfactory, tmp_path) -> None:
|
||||
alice = await acfactory.new_configured_account()
|
||||
await alice.export_backup(tmp_path)
|
||||
|
||||
files = list(tmp_path.glob("*.tar"))
|
||||
alice2 = await acfactory.get_unconfigured_account()
|
||||
await alice2.import_backup(files[0])
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import pytest
|
||||
|
||||
from deltachat_rpc_client import EventType
|
||||
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ passenv =
|
||||
deps =
|
||||
pytest
|
||||
pytest-asyncio
|
||||
pytest-timeout
|
||||
aiohttp
|
||||
aiodns
|
||||
|
||||
@@ -25,5 +26,8 @@ deps =
|
||||
ruff
|
||||
black
|
||||
commands =
|
||||
black --check --diff src/ examples/ tests/
|
||||
black --quiet --check --diff src/ examples/ tests/
|
||||
ruff src/ examples/ tests/
|
||||
|
||||
[pytest]
|
||||
timeout = 60
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "1.109.0"
|
||||
version = "1.120.0"
|
||||
description = "DeltaChat JSON-RPC server"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
@@ -9,17 +9,20 @@ license = "MPL-2.0"
|
||||
keywords = ["deltachat", "chat", "openpgp", "email", "encryption"]
|
||||
categories = ["cryptography", "std", "email"]
|
||||
|
||||
[[bin]]
|
||||
name = "deltachat-rpc-server"
|
||||
|
||||
[dependencies]
|
||||
deltachat-jsonrpc = { path = "../deltachat-jsonrpc" }
|
||||
deltachat-jsonrpc = { path = "../deltachat-jsonrpc", default-features = false }
|
||||
deltachat = { path = "..", default-features = false }
|
||||
|
||||
anyhow = "1"
|
||||
env_logger = { version = "0.10.0" }
|
||||
futures-lite = "1.12.0"
|
||||
futures-lite = "1.13.0"
|
||||
log = "0.4"
|
||||
serde_json = "1.0.91"
|
||||
serde_json = "1.0.99"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tokio = { version = "1.25.0", features = ["io-std"] }
|
||||
yerpc = { version = "0.4.0", features = ["anyhow_expose"] }
|
||||
tokio = { version = "1.29.1", features = ["io-std"] }
|
||||
tokio-util = "0.7.8"
|
||||
yerpc = { version = "0.5.1", features = ["anyhow_expose"] }
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
vendored = ["deltachat-jsonrpc/vendored"]
|
||||
|
||||
@@ -5,10 +5,13 @@ over standard I/O.
|
||||
|
||||
## Install
|
||||
|
||||
To install run:
|
||||
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 --path ../deltachat-rpc-server
|
||||
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
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
///! 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(())
|
||||
}
|
||||
143
deltachat-rpc-server/src/main.rs
Normal file
143
deltachat-rpc-server/src/main.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
//! Delta Chat core RPC server.
|
||||
//!
|
||||
//! It speaks JSON Lines over stdio.
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use deltachat::constants::DC_VERSION_STR;
|
||||
use deltachat_jsonrpc::api::{Accounts, CommandApi};
|
||||
use futures_lite::stream::StreamExt;
|
||||
use tokio::io::{self, AsyncBufReadExt, BufReader};
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
use tokio::signal::unix as signal_unix;
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use yerpc::{RpcClient, RpcSession};
|
||||
|
||||
#[tokio::main(flavor = "multi_thread")]
|
||||
async fn main() {
|
||||
let r = main_impl().await;
|
||||
// From tokio documentation:
|
||||
// "For technical reasons, stdin is implemented by using an ordinary blocking read on a separate
|
||||
// thread, and it is impossible to cancel that read. This can make shutdown of the runtime hang
|
||||
// until the user presses enter."
|
||||
std::process::exit(if r.is_ok() { 0 } else { 1 });
|
||||
}
|
||||
|
||||
async fn main_impl() -> Result<()> {
|
||||
let mut args = env::args_os();
|
||||
let _program_name = args.next().context("no command line arguments found")?;
|
||||
if let Some(first_arg) = args.next() {
|
||||
if first_arg.to_str() == Some("--version") {
|
||||
if let Some(arg) = args.next() {
|
||||
return Err(anyhow!("Unrecognized argument {:?}", arg));
|
||||
}
|
||||
eprintln!("{}", &*DC_VERSION_STR);
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(anyhow!("Unrecognized option {:?}", first_arg));
|
||||
}
|
||||
}
|
||||
if let Some(arg) = args.next() {
|
||||
return Err(anyhow!("Unrecognized argument {:?}", arg));
|
||||
}
|
||||
|
||||
// Install signal handlers early so that the shutdown is graceful starting from here.
|
||||
let _ctrl_c = tokio::signal::ctrl_c();
|
||||
#[cfg(target_family = "unix")]
|
||||
let mut sigterm = signal_unix::signal(signal_unix::SignalKind::terminate())?;
|
||||
|
||||
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?;
|
||||
|
||||
log::info!("Creating JSON-RPC API.");
|
||||
let accounts = Arc::new(RwLock::new(accounts));
|
||||
let state = CommandApi::from_arc(accounts.clone());
|
||||
|
||||
let (client, mut out_receiver) = RpcClient::new();
|
||||
let session = RpcSession::new(client.clone(), state.clone());
|
||||
let main_cancel = CancellationToken::new();
|
||||
|
||||
// Send task prints JSON responses to stdout.
|
||||
let cancel = main_cancel.clone();
|
||||
let send_task: JoinHandle<anyhow::Result<()>> = tokio::spawn(async move {
|
||||
let _cancel_guard = cancel.clone().drop_guard();
|
||||
loop {
|
||||
let message = tokio::select! {
|
||||
_ = cancel.cancelled() => break,
|
||||
message = out_receiver.next() => match message {
|
||||
None => break,
|
||||
Some(message) => serde_json::to_string(&message)?,
|
||||
}
|
||||
};
|
||||
log::trace!("RPC send {}", message);
|
||||
println!("{message}");
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let cancel = main_cancel.clone();
|
||||
let sigterm_task: JoinHandle<anyhow::Result<()>> = tokio::spawn(async move {
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
let _cancel_guard = cancel.clone().drop_guard();
|
||||
tokio::select! {
|
||||
_ = cancel.cancelled() => (),
|
||||
_ = sigterm.recv() => {
|
||||
log::info!("got SIGTERM");
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = cancel;
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// Receiver task reads JSON requests from stdin.
|
||||
let cancel = main_cancel.clone();
|
||||
let recv_task: JoinHandle<anyhow::Result<()>> = tokio::spawn(async move {
|
||||
let _cancel_guard = cancel.clone().drop_guard();
|
||||
let stdin = io::stdin();
|
||||
let mut lines = BufReader::new(stdin).lines();
|
||||
|
||||
loop {
|
||||
let message = tokio::select! {
|
||||
_ = cancel.cancelled() => break,
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
log::info!("got ctrl-c event");
|
||||
break;
|
||||
}
|
||||
message = lines.next_line() => match message? {
|
||||
None => {
|
||||
log::info!("EOF reached on stdin");
|
||||
break;
|
||||
}
|
||||
Some(message) => message,
|
||||
}
|
||||
};
|
||||
log::trace!("RPC recv {}", message);
|
||||
let session = session.clone();
|
||||
tokio::spawn(async move {
|
||||
session.handle_incoming(&message).await;
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
main_cancel.cancelled().await;
|
||||
accounts.read().await.stop_io().await;
|
||||
drop(accounts);
|
||||
drop(state);
|
||||
send_task.await??;
|
||||
sigterm_task.await??;
|
||||
recv_task.await??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -8,5 +8,5 @@ license = "MPL-2.0"
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = "1"
|
||||
syn = "2"
|
||||
quote = "1"
|
||||
|
||||
@@ -5,7 +5,7 @@ use quote::quote;
|
||||
|
||||
use crate::proc_macro::TokenStream;
|
||||
|
||||
// For now, assume (not check) that these macroses are applied to enum without
|
||||
// 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.
|
||||
|
||||
|
||||
100
deny.toml
Normal file
100
deny.toml
Normal file
@@ -0,0 +1,100 @@
|
||||
[advisories]
|
||||
unmaintained = "allow"
|
||||
ignore = [
|
||||
"RUSTSEC-2020-0071",
|
||||
"RUSTSEC-2022-0093",
|
||||
|
||||
# Exponential CPU time usage for TLS certificate processing in webpki.
|
||||
# It is only used for backup transfer, so does not affect IMAP and SMTP connections.
|
||||
# Waiting for `iroh` to update dependencies.
|
||||
"RUSTSEC-2023-0052",
|
||||
]
|
||||
|
||||
[bans]
|
||||
# Accept some duplicate versions, ideally we work towards this list
|
||||
# becoming empty. Adding versions forces us to revisit this at least
|
||||
# when upgrading.
|
||||
# Please keep this list alphabetically sorted.
|
||||
skip = [
|
||||
{ name = "ahash", version = "0.7.6" },
|
||||
{ name = "base16ct", version = "0.1.1" },
|
||||
{ name = "base64", version = "<0.21" },
|
||||
{ name = "bitflags", version = "1.3.2" },
|
||||
{ name = "block-buffer", version = "<0.10" },
|
||||
{ name = "convert_case", version = "0.4.0" },
|
||||
{ name = "curve25519-dalek", version = "3.2.0" },
|
||||
{ name = "darling_core", version = "<0.14" },
|
||||
{ name = "darling_macro", version = "<0.14" },
|
||||
{ name = "darling", version = "<0.14" },
|
||||
{ name = "der", version = "0.6.1" },
|
||||
{ name = "digest", version = "<0.10" },
|
||||
{ name = "ed25519-dalek", version = "1.0.1" },
|
||||
{ name = "ed25519", version = "1.5.3" },
|
||||
{ name = "getrandom", version = "<0.2" },
|
||||
{ name = "hashbrown", version = "<0.14.0" },
|
||||
{ name = "idna", version = "<0.3" },
|
||||
{ name = "indexmap", version = "<2.0.0" },
|
||||
{ name = "linux-raw-sys", version = "0.3.8" },
|
||||
{ name = "num-derive", version = "0.3.3" },
|
||||
{ name = "pem-rfc7468", version = "0.6.0" },
|
||||
{ name = "pkcs8", version = "0.9.0" },
|
||||
{ name = "quick-error", version = "<2.0" },
|
||||
{ name = "rand_chacha", version = "<0.3" },
|
||||
{ name = "rand_core", version = "<0.6" },
|
||||
{ name = "rand", version = "<0.8" },
|
||||
{ name = "redox_syscall", version = "0.2.16" },
|
||||
{ name = "regex-syntax", version = "0.6.29" },
|
||||
{ name = "rustix", version = "0.37.21" },
|
||||
{ name = "sec1", version = "0.3.0" },
|
||||
{ name = "sha2", version = "<0.10" },
|
||||
{ name = "signature", version = "1.6.4" },
|
||||
{ name = "socket2", version = "0.4.9" },
|
||||
{ name = "spin", version = "<0.9.6" },
|
||||
{ name = "spki", version = "0.6.0" },
|
||||
{ name = "syn", version = "1.0.109" },
|
||||
{ name = "time", version = "<0.3" },
|
||||
{ name = "wasi", version = "<0.11" },
|
||||
{ name = "windows_aarch64_gnullvm", version = "<0.48" },
|
||||
{ name = "windows_aarch64_msvc", version = "<0.48" },
|
||||
{ name = "windows_i686_gnu", version = "<0.48" },
|
||||
{ name = "windows_i686_msvc", version = "<0.48" },
|
||||
{ name = "windows-sys", version = "<0.48" },
|
||||
{ name = "windows-targets", version = "<0.48" },
|
||||
{ name = "windows_x86_64_gnullvm", version = "<0.48" },
|
||||
{ name = "windows", version = "0.32.0" },
|
||||
{ name = "windows_x86_64_gnu", version = "<0.48" },
|
||||
{ name = "windows_x86_64_msvc", version = "<0.48" },
|
||||
{ name = "winreg", version = "0.10.1" },
|
||||
]
|
||||
|
||||
|
||||
[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",
|
||||
"quinn-rs",
|
||||
]
|
||||
@@ -66,13 +66,13 @@ Note that usually a mail is signed by a key that has a UID matching the from add
|
||||
|
||||
#### Upsides:
|
||||
- With this approach, it's easy to switch to a model where the info about the transition is encoded in the PGP key. Since the key is gossiped, the information about the transition will spread virally.
|
||||
- Faster transation: If you send a message to e.g. "Delta Chat Dev", all members of the "sub-group" "delta android" will know of your transition.
|
||||
- Faster transition: If you send a message to e.g. "Delta Chat Dev", all members of the "sub-group" "delta android" will know of your transition.
|
||||
|
||||
### Alternatives and old discussions/plans:
|
||||
|
||||
- Change the contact instead of rewriting the group member lists. This seems to call for more trouble since we will end up with multiple contacts having the same email address.
|
||||
|
||||
- If needed, we could add a header a) indicating that the sender did an address transition or b) listing all the secondary (old) addresses. For now, there is no big enough benefit to warrant introducing another header and its processing on the receiver side (including all the neccessary checks and handling of error cases). Instead, we only check for the `Chat-Version` header to prevent accidental transitions when an MUA user sends a message from another email address with the same key.
|
||||
- If needed, we could add a header a) indicating that the sender did an address transition or b) listing all the secondary (old) addresses. For now, there is no big enough benefit to warrant introducing another header and its processing on the receiver side (including all the necessary checks and handling of error cases). Instead, we only check for the `Chat-Version` header to prevent accidental transitions when an MUA user sends a message from another email address with the same key.
|
||||
|
||||
- The condition for a transition temporarily was:
|
||||
|
||||
@@ -107,7 +107,7 @@ The most obvious alternative would be to create a new contact with the new addre
|
||||
|
||||
#### Upsides:
|
||||
- With this approach, it's easier to switch to a model where the info about the transition is encoded in the PGP key. Since the key is gossiped, the information about the transition will spread virally.
|
||||
- (Also, less important: Slightly faster transation: If you send a message to e.g. "Delta Chat Dev", all members of the "sub-group" "delta android" will know of your transition.)
|
||||
- (Also, less important: Slightly faster transition: If you send a message to e.g. "Delta Chat Dev", all members of the "sub-group" "delta android" will know of your transition.)
|
||||
- It's easier to implement (if too many problems turn up, we can still switch to another approach and didn't wast that much development time.)
|
||||
|
||||
[full messages](https://github.com/deltachat/deltachat-core-rust/pull/2896#discussion_r852002161)
|
||||
@@ -124,4 +124,4 @@ Other
|
||||
Notes during implementing
|
||||
========================
|
||||
|
||||
- As far as I understand the code, unencrypted messages are unsigned. So, the transition only works if both sides have the other side's key.
|
||||
- As far as I understand the code, unencrypted messages are unsigned. So, the transition only works if both sides have the other side's key.
|
||||
|
||||
@@ -62,5 +62,5 @@ Notes/Questions:
|
||||
|
||||
and design such that we can cover all imap flags.
|
||||
|
||||
- It might not be neccessary to keep needs_send_mdn state in this table
|
||||
- It might not be necessary to keep needs_send_mdn state in this table
|
||||
if this can be decided rather when we succeed with mark_seen/mark_delete.
|
||||
|
||||
1919
fuzz/Cargo.lock
generated
1919
fuzz/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -124,7 +124,7 @@ $ npm run test
|
||||
```
|
||||
|
||||
(when using [fnm](https://github.com/Schniz/fnm) instead of nvm, you can select the architecture)
|
||||
If your node and electron are already build for arm64 you can also try bulding for arm:
|
||||
If your node and electron are already build for arm64 you can also try building for arm:
|
||||
|
||||
```
|
||||
$ fnm install 16 --arch arm64
|
||||
@@ -182,7 +182,7 @@ this example can also be found in the examples folder [examples/send_message.js]
|
||||
|
||||
### Generating Docs
|
||||
|
||||
We are curently migrating to automaticaly generated documentation.
|
||||
We are currently migrating to automatically generated documentation.
|
||||
You can find the old documentation at [old_docs](./old_docs).
|
||||
|
||||
to generate the documentation, run:
|
||||
|
||||
@@ -37,6 +37,7 @@ module.exports = {
|
||||
DC_EVENT_ERROR: 400,
|
||||
DC_EVENT_ERROR_SELF_NOT_IN_GROUP: 410,
|
||||
DC_EVENT_IMAP_CONNECTED: 102,
|
||||
DC_EVENT_IMAP_INBOX_IDLE: 106,
|
||||
DC_EVENT_IMAP_MESSAGE_DELETED: 104,
|
||||
DC_EVENT_IMAP_MESSAGE_MOVED: 105,
|
||||
DC_EVENT_IMEX_FILE_WRITTEN: 2052,
|
||||
@@ -47,6 +48,7 @@ module.exports = {
|
||||
DC_EVENT_LOCATION_CHANGED: 2035,
|
||||
DC_EVENT_MSGS_CHANGED: 2000,
|
||||
DC_EVENT_MSGS_NOTICED: 2008,
|
||||
DC_EVENT_MSG_DELETED: 2016,
|
||||
DC_EVENT_MSG_DELIVERED: 2010,
|
||||
DC_EVENT_MSG_FAILED: 2012,
|
||||
DC_EVENT_MSG_READ: 2015,
|
||||
@@ -112,6 +114,7 @@ module.exports = {
|
||||
DC_QR_ADDR: 320,
|
||||
DC_QR_ASK_VERIFYCONTACT: 200,
|
||||
DC_QR_ASK_VERIFYGROUP: 202,
|
||||
DC_QR_BACKUP: 251,
|
||||
DC_QR_ERROR: 400,
|
||||
DC_QR_FPR_MISMATCH: 220,
|
||||
DC_QR_FPR_OK: 210,
|
||||
@@ -149,6 +152,8 @@ module.exports = {
|
||||
DC_STR_AEAP_EXPLANATION_AND_LINK: 123,
|
||||
DC_STR_ARCHIVEDCHATS: 40,
|
||||
DC_STR_AUDIO: 11,
|
||||
DC_STR_BACKUP_TRANSFER_MSG_BODY: 163,
|
||||
DC_STR_BACKUP_TRANSFER_QR: 162,
|
||||
DC_STR_BAD_TIME_MSG_BODY: 85,
|
||||
DC_STR_BROADCAST_LIST: 115,
|
||||
DC_STR_CANNOT_LOGIN: 60,
|
||||
|
||||
@@ -8,6 +8,7 @@ module.exports = {
|
||||
103: 'DC_EVENT_SMTP_MESSAGE_SENT',
|
||||
104: 'DC_EVENT_IMAP_MESSAGE_DELETED',
|
||||
105: 'DC_EVENT_IMAP_MESSAGE_MOVED',
|
||||
106: 'DC_EVENT_IMAP_INBOX_IDLE',
|
||||
150: 'DC_EVENT_NEW_BLOB_FILE',
|
||||
151: 'DC_EVENT_DELETED_BLOB_FILE',
|
||||
300: 'DC_EVENT_WARNING',
|
||||
@@ -21,6 +22,7 @@ module.exports = {
|
||||
2010: 'DC_EVENT_MSG_DELIVERED',
|
||||
2012: 'DC_EVENT_MSG_FAILED',
|
||||
2015: 'DC_EVENT_MSG_READ',
|
||||
2016: 'DC_EVENT_MSG_DELETED',
|
||||
2020: 'DC_EVENT_CHAT_MODIFIED',
|
||||
2021: 'DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED',
|
||||
2030: 'DC_EVENT_CONTACTS_CHANGED',
|
||||
|
||||
@@ -2,7 +2,7 @@ import { join } from 'path'
|
||||
|
||||
/**
|
||||
* bindings are not typed yet.
|
||||
* if the availible function names are required they can be found inside of `../src/module.c`
|
||||
* if the available function names are required they can be found inside of `../src/module.c`
|
||||
*/
|
||||
export const bindings: any = require('node-gyp-build')(join(__dirname, '../'))
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ export enum C {
|
||||
DC_EVENT_ERROR = 400,
|
||||
DC_EVENT_ERROR_SELF_NOT_IN_GROUP = 410,
|
||||
DC_EVENT_IMAP_CONNECTED = 102,
|
||||
DC_EVENT_IMAP_INBOX_IDLE = 106,
|
||||
DC_EVENT_IMAP_MESSAGE_DELETED = 104,
|
||||
DC_EVENT_IMAP_MESSAGE_MOVED = 105,
|
||||
DC_EVENT_IMEX_FILE_WRITTEN = 2052,
|
||||
@@ -47,6 +48,7 @@ export enum C {
|
||||
DC_EVENT_LOCATION_CHANGED = 2035,
|
||||
DC_EVENT_MSGS_CHANGED = 2000,
|
||||
DC_EVENT_MSGS_NOTICED = 2008,
|
||||
DC_EVENT_MSG_DELETED = 2016,
|
||||
DC_EVENT_MSG_DELIVERED = 2010,
|
||||
DC_EVENT_MSG_FAILED = 2012,
|
||||
DC_EVENT_MSG_READ = 2015,
|
||||
@@ -112,6 +114,7 @@ export enum C {
|
||||
DC_QR_ADDR = 320,
|
||||
DC_QR_ASK_VERIFYCONTACT = 200,
|
||||
DC_QR_ASK_VERIFYGROUP = 202,
|
||||
DC_QR_BACKUP = 251,
|
||||
DC_QR_ERROR = 400,
|
||||
DC_QR_FPR_MISMATCH = 220,
|
||||
DC_QR_FPR_OK = 210,
|
||||
@@ -149,6 +152,8 @@ export enum C {
|
||||
DC_STR_AEAP_EXPLANATION_AND_LINK = 123,
|
||||
DC_STR_ARCHIVEDCHATS = 40,
|
||||
DC_STR_AUDIO = 11,
|
||||
DC_STR_BACKUP_TRANSFER_MSG_BODY = 163,
|
||||
DC_STR_BACKUP_TRANSFER_QR = 162,
|
||||
DC_STR_BAD_TIME_MSG_BODY = 85,
|
||||
DC_STR_BROADCAST_LIST = 115,
|
||||
DC_STR_CANNOT_LOGIN = 60,
|
||||
@@ -289,6 +294,7 @@ export const EventId2EventName: { [key: number]: string } = {
|
||||
103: 'DC_EVENT_SMTP_MESSAGE_SENT',
|
||||
104: 'DC_EVENT_IMAP_MESSAGE_DELETED',
|
||||
105: 'DC_EVENT_IMAP_MESSAGE_MOVED',
|
||||
106: 'DC_EVENT_IMAP_INBOX_IDLE',
|
||||
150: 'DC_EVENT_NEW_BLOB_FILE',
|
||||
151: 'DC_EVENT_DELETED_BLOB_FILE',
|
||||
300: 'DC_EVENT_WARNING',
|
||||
@@ -302,6 +308,7 @@ export const EventId2EventName: { [key: number]: string } = {
|
||||
2010: 'DC_EVENT_MSG_DELIVERED',
|
||||
2012: 'DC_EVENT_MSG_FAILED',
|
||||
2015: 'DC_EVENT_MSG_READ',
|
||||
2016: 'DC_EVENT_MSG_DELETED',
|
||||
2020: 'DC_EVENT_CHAT_MODIFIED',
|
||||
2021: 'DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED',
|
||||
2030: 'DC_EVENT_CONTACTS_CHANGED',
|
||||
|
||||
@@ -543,7 +543,7 @@ export class Context extends EventEmitter {
|
||||
|
||||
/**
|
||||
*
|
||||
* @deprectated please use `AccountManager.getSystemInfo()` instead
|
||||
* @deprecated please use `AccountManager.getSystemInfo()` instead
|
||||
*/
|
||||
static getSystemInfo() {
|
||||
return AccountManager.getSystemInfo()
|
||||
@@ -818,7 +818,7 @@ export class Context extends EventEmitter {
|
||||
* Must be given in number of seconds since 00:00 hours, Jan 1, 1970 UTC.
|
||||
* 0 for "all up to now".
|
||||
* @return Array of locations, NULL is never returned.
|
||||
* The array is sorted decending;
|
||||
* The array is sorted descending;
|
||||
* the first entry in the array is the location with the newest timestamp.
|
||||
*
|
||||
* Examples:
|
||||
|
||||
@@ -217,7 +217,7 @@ export class AccountManager extends EventEmitter {
|
||||
/** get information about the provider
|
||||
*
|
||||
* This function creates a temporary context to be standalone,
|
||||
* if posible use `Context.getProviderFromEmail` instead. (otherwise potential proxy settings are not used)
|
||||
* if possible use `Context.getProviderFromEmail` instead. (otherwise potential proxy settings are not used)
|
||||
* @deprecated
|
||||
*/
|
||||
static getProviderFromEmail(email: string) {
|
||||
|
||||
@@ -446,7 +446,8 @@ describe('Offline Tests with unconfigured account', function () {
|
||||
context.setChatProfileImage(chatId, imagePath)
|
||||
const blobPath = context.getChat(chatId).getProfileImage()
|
||||
expect(blobPath.startsWith(blobs)).to.be.true
|
||||
expect(blobPath.endsWith(image)).to.be.true
|
||||
expect(blobPath.includes('image')).to.be.true
|
||||
expect(blobPath.endsWith('.jpeg')).to.be.true
|
||||
|
||||
context.setChatProfileImage(chatId, null)
|
||||
expect(context.getChat(chatId).getProfileImage()).to.be.equal(
|
||||
@@ -769,7 +770,7 @@ describe('Integration tests', function () {
|
||||
this.beforeAll(async function () {
|
||||
if (!process.env.DCC_NEW_TMP_EMAIL) {
|
||||
console.log(
|
||||
'Missing DCC_NEW_TMP_EMAIL environment variable!, skip intergration tests'
|
||||
'Missing DCC_NEW_TMP_EMAIL environment variable!, skip integration tests'
|
||||
)
|
||||
this.skip()
|
||||
}
|
||||
@@ -777,7 +778,7 @@ describe('Integration tests', function () {
|
||||
account = await createTempUser(process.env.DCC_NEW_TMP_EMAIL)
|
||||
if (!account || !account.email || !account.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()
|
||||
}
|
||||
|
||||
@@ -60,5 +60,5 @@
|
||||
"test:mocha": "mocha -r esm node/test/test.js --growl --reporter=spec --bail --exit"
|
||||
},
|
||||
"types": "node/dist/index.d.ts",
|
||||
"version": "1.109.0"
|
||||
}
|
||||
"version": "1.120.0"
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
- use new experimental full-Rust Delta Chat core
|
||||
- support Autocrypt Setup Messages
|
||||
- remove synchronous events
|
||||
- use CircleCI for continous integration and packaging of Linux wheels
|
||||
- use CircleCI for continuous integration and packaging of Linux wheels
|
||||
- use docker image for building wheels
|
||||
- fix code documentation links
|
||||
|
||||
|
||||
@@ -357,7 +357,7 @@ Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
|
||||
@@ -12,17 +12,17 @@ a low-level Chat/Contact/Message API to user interfaces and bots.
|
||||
Installing pre-built packages (Linux-only)
|
||||
==========================================
|
||||
|
||||
If you have a Linux system you may try to install the ``deltachat`` binary "wheel" packages
|
||||
If you have a Linux system you may install the ``deltachat`` binary "wheel" packages
|
||||
without any "build-from-source" steps.
|
||||
Otherwise you need to `compile the Delta Chat bindings yourself`__.
|
||||
|
||||
__ sourceinstall_
|
||||
|
||||
We recommend to first `install virtualenv <https://virtualenv.pypa.io/en/stable/installation.html>`_,
|
||||
then create a fresh Python virtual environment and activate it in your shell::
|
||||
We recommend to first create a fresh Python virtual environment
|
||||
and activate it in your shell::
|
||||
|
||||
virtualenv env # or: python -m venv
|
||||
source env/bin/activate
|
||||
python -m venv env
|
||||
source env/bin/activate
|
||||
|
||||
Afterwards, invoking ``python`` or ``pip install`` only
|
||||
modifies files in your ``env`` directory and leaves
|
||||
@@ -40,16 +40,14 @@ To verify it worked::
|
||||
Running tests
|
||||
=============
|
||||
|
||||
Recommended way to run tests is using `tox <https://tox.wiki>`_.
|
||||
After successful binding installation you can install tox
|
||||
and run the tests::
|
||||
Recommended way to run tests is using `scripts/run-python-test.sh`
|
||||
script provided in the core repository.
|
||||
|
||||
pip install tox
|
||||
tox -e py3
|
||||
|
||||
This will run all "offline" tests and skip all functional
|
||||
This script compiles the library in debug mode and runs the tests using `tox`_.
|
||||
By default it will run all "offline" tests and skip all functional
|
||||
end-to-end tests that require accounts on real e-mail servers.
|
||||
|
||||
.. _`tox`: https://tox.wiki
|
||||
.. _livetests:
|
||||
|
||||
Running "live" tests with temporary accounts
|
||||
@@ -61,13 +59,34 @@ Please feel free to contact us through a github issue or by e-mail and we'll sen
|
||||
|
||||
export DCC_NEW_TMP_EMAIL=<URL you got from us>
|
||||
|
||||
With this account-creation setting, pytest runs create ephemeral e-mail accounts on the http://testrun.org server. These accounts exists only for one hour and then are removed completely.
|
||||
One hour is enough to invoke pytest and run all offline and online tests::
|
||||
With this account-creation setting, pytest runs create ephemeral e-mail accounts on the http://testrun.org server.
|
||||
These accounts are removed automatically as they expire.
|
||||
After setting the variable, either rerun `scripts/run-python-test.sh`
|
||||
or run offline and online tests with `tox` directly::
|
||||
|
||||
tox -e py3
|
||||
tox -e py
|
||||
|
||||
Each test run creates new accounts.
|
||||
|
||||
Developing the bindings
|
||||
-----------------------
|
||||
|
||||
If you want to develop or debug the bindings,
|
||||
you can create a testing development environment using `tox`::
|
||||
|
||||
export DCC_RS_DEV="$PWD"
|
||||
export DCC_RS_TARGET=debug
|
||||
tox -c python --devenv env -e py
|
||||
. env/bin/activate
|
||||
|
||||
Inside this environment the bindings are installed
|
||||
in editable mode (as if installed with `python -m pip install -e`)
|
||||
together with the testing dependencies like `pytest` and its plugins.
|
||||
|
||||
You can then edit the source code in the development tree
|
||||
and quickly run `pytest` manually without waiting for `tox`
|
||||
to recreating the virtual environment each time.
|
||||
|
||||
.. _sourceinstall:
|
||||
|
||||
Installing bindings from source
|
||||
@@ -89,20 +108,34 @@ To install the Delta Chat Python bindings make sure you have Python3 installed.
|
||||
E.g. on Debian-based systems `apt install python3 python3-pip
|
||||
python3-venv` should give you a usable python installation.
|
||||
|
||||
Ensure you are in the deltachat-core-rust/python directory, create the
|
||||
virtual environment with dependencies using tox
|
||||
and activate it in your shell::
|
||||
First, build the core library::
|
||||
|
||||
cd python
|
||||
tox --devenv env
|
||||
cargo build --release -p deltachat_ffi --features jsonrpc
|
||||
|
||||
`jsonrpc` feature is required even if not used by the bindings
|
||||
because `deltachat.h` includes JSON-RPC functions unconditionally.
|
||||
|
||||
Create the virtual environment and activate it:
|
||||
|
||||
python -m venv env
|
||||
source env/bin/activate
|
||||
|
||||
You should now be able to build the python bindings using the supplied script::
|
||||
Build and install the bindings:
|
||||
|
||||
python3 install_python_bindings.py
|
||||
export DCC_RS_DEV="$PWD"
|
||||
export DCC_RS_TARGET=release
|
||||
python -m pip install ./python
|
||||
|
||||
The core compilation and bindings building might take a while,
|
||||
depending on the speed of your machine.
|
||||
`DCC_RS_DEV` environment variable specifies the location of
|
||||
the core development tree. If this variable is not set,
|
||||
`libdeltachat` library and `deltachat.h` header are expected
|
||||
to be installed system-wide.
|
||||
|
||||
When `DCC_RS_DEV` is set, `DCC_RS_TARGET` specifies
|
||||
the build profile name to look up the artifacts
|
||||
in the target directory.
|
||||
In this case setting it can be skipped because
|
||||
`DCC_RS_TARGET=release` is the default.
|
||||
|
||||
Building manylinux based wheels
|
||||
===============================
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user