mirror of
https://github.com/chatmail/core.git
synced 2026-04-02 05:22:14 +03:00
Compare commits
3336 Commits
1.61.0
...
link2xt/sq
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5820c4ce95 | ||
|
|
12ba33d9d4 | ||
|
|
60a7bbc9b5 | ||
|
|
e9f434b562 | ||
|
|
2423cb8175 | ||
|
|
65c9e72bf4 | ||
|
|
ea4d954c77 | ||
|
|
43523a96a2 | ||
|
|
2e2fa9e74f | ||
|
|
e43ffb20a1 | ||
|
|
2f0f247e70 | ||
|
|
5bda4f0c26 | ||
|
|
d39c8a3a19 | ||
|
|
e465415039 | ||
|
|
5cef77b8e6 | ||
|
|
60e733c30c | ||
|
|
8b98816eb9 | ||
|
|
50165b3e35 | ||
|
|
0be8b5a5c4 | ||
|
|
451bb6e9db | ||
|
|
83196d4cb5 | ||
|
|
0003e55ad5 | ||
|
|
02014eda6c | ||
|
|
f1c6cd69e9 | ||
|
|
ace281ff6c | ||
|
|
c9edd525e0 | ||
|
|
3f35b442c3 | ||
|
|
87e9365016 | ||
|
|
9806509f4a | ||
|
|
91600a34b6 | ||
|
|
d16351d207 | ||
|
|
4caf638201 | ||
|
|
375fcbd63c | ||
|
|
6ff3a2cf7c | ||
|
|
a890fe3a9a | ||
|
|
2b8bf29fce | ||
|
|
26400a9e4e | ||
|
|
f8b9bb9083 | ||
|
|
42f9047a54 | ||
|
|
6433a3a5f3 | ||
|
|
4b6a03c904 | ||
|
|
ff3df01d98 | ||
|
|
cdc99854b2 | ||
|
|
e7072bcb75 | ||
|
|
7950bde3c6 | ||
|
|
a259669c98 | ||
|
|
603e6be9b4 | ||
|
|
a78c484467 | ||
|
|
e78f07b343 | ||
|
|
8abf10aacb | ||
|
|
2fef4acdd6 | ||
|
|
de27be3a36 | ||
|
|
c62e8539a1 | ||
|
|
22c0aef9c0 | ||
|
|
87805bc36d | ||
|
|
99c4d24eab | ||
|
|
7bf9c4a2d9 | ||
|
|
304e902fce | ||
|
|
0155d93622 | ||
|
|
ebd097bdbe | ||
|
|
a11d01f8a3 | ||
|
|
38491b694b | ||
|
|
e702c1a8ca | ||
|
|
1adea3c678 | ||
|
|
9af812a3e7 | ||
|
|
36bdf8a67e | ||
|
|
20b30fc70a | ||
|
|
e59ff6ca74 | ||
|
|
0e5db36205 | ||
|
|
7960944b14 | ||
|
|
71c2383cbe | ||
|
|
5f5b272726 | ||
|
|
b34fe8f118 | ||
|
|
810be4f6c7 | ||
|
|
1ebbe26ebb | ||
|
|
0f5d5dd2b2 | ||
|
|
473dbe01af | ||
|
|
069ed7afa6 | ||
|
|
9313ece3cd | ||
|
|
900168c68c | ||
|
|
0bd137b4e5 | ||
|
|
75da205ff6 | ||
|
|
67e5fbbfe3 | ||
|
|
570daf42ec | ||
|
|
fcbbb91cde | ||
|
|
c3a7fc4c8d | ||
|
|
4b4c57a480 | ||
|
|
b95d58208c | ||
|
|
c468eb088e | ||
|
|
de37135ed6 | ||
|
|
33777d8759 | ||
|
|
8cc348bfa4 | ||
|
|
76bbd5fd72 | ||
|
|
eaed2381e7 | ||
|
|
6198ed0ef5 | ||
|
|
9f4af679a3 | ||
|
|
e158b889c9 | ||
|
|
9f7defa8da | ||
|
|
e9d7fe0561 | ||
|
|
7d7289bd51 | ||
|
|
ebdc52247c | ||
|
|
36bb4a7a32 | ||
|
|
c0832af634 | ||
|
|
b6db0152b0 | ||
|
|
bc7fd4495b | ||
|
|
e67e86422f | ||
|
|
2030de11d9 | ||
|
|
2c5a0cac5f | ||
|
|
251917e602 | ||
|
|
273719ae7c | ||
|
|
e639b58c6f | ||
|
|
5addfa8d1d | ||
|
|
02d68332c7 | ||
|
|
97abb9a0a9 | ||
|
|
d0e0cfafef | ||
|
|
f630b5fb39 | ||
|
|
d9bab938d5 | ||
|
|
215ec14b20 | ||
|
|
ea728e9b62 | ||
|
|
2af9ff1d01 | ||
|
|
7502234686 | ||
|
|
863a386d0f | ||
|
|
e4b49dfdef | ||
|
|
612aa1431e | ||
|
|
781d3abdb9 | ||
|
|
78d01933ad | ||
|
|
1a1467f7cf | ||
|
|
8d09291d1e | ||
|
|
4ccd2b8d02 | ||
|
|
794596ec69 | ||
|
|
3a787519b3 | ||
|
|
c03e163ed2 | ||
|
|
6cee295a5d | ||
|
|
f0be7daae9 | ||
|
|
0b279ec84e | ||
|
|
e919de78a3 | ||
|
|
6ea675a12f | ||
|
|
b970ebe67a | ||
|
|
3c4c701f9b | ||
|
|
01ac9c8b90 | ||
|
|
f6de23738d | ||
|
|
ddc2704278 | ||
|
|
3d2b164c05 | ||
|
|
2094bc3135 | ||
|
|
acff8205e2 | ||
|
|
255400028a | ||
|
|
d7615b223f | ||
|
|
00fbf540c4 | ||
|
|
288eccf722 | ||
|
|
99ee769580 | ||
|
|
345759d653 | ||
|
|
db0143f01a | ||
|
|
4da0c19766 | ||
|
|
08247a5d37 | ||
|
|
ceadd8928e | ||
|
|
c3d96814ca | ||
|
|
c2953623b9 | ||
|
|
1907d1859e | ||
|
|
a1970e998f | ||
|
|
1e9baefca0 | ||
|
|
e16322d99d | ||
|
|
ecfe3898c6 | ||
|
|
5499ca52bf | ||
|
|
4e8979f7c8 | ||
|
|
417db31098 | ||
|
|
cd9f6c3d5b | ||
|
|
07870a6d69 | ||
|
|
b08a4d6fcf | ||
|
|
b3a82b416f | ||
|
|
4e5d7fb821 | ||
|
|
1d73f97ef3 | ||
|
|
f5601e7683 | ||
|
|
0000c09ad3 | ||
|
|
a83884d7e9 | ||
|
|
9e00e8627f | ||
|
|
85c9622675 | ||
|
|
30432d8fa5 | ||
|
|
8b9f19be70 | ||
|
|
39c317e211 | ||
|
|
36ab7bdf47 | ||
|
|
f8f0ca08da | ||
|
|
2a0a05d03c | ||
|
|
7bc2f0cb6b | ||
|
|
4355bd77a9 | ||
|
|
f0091696c2 | ||
|
|
d2e86c5852 | ||
|
|
d4a505b52e | ||
|
|
08a30031eb | ||
|
|
44686d6caa | ||
|
|
9862d40f89 | ||
|
|
256c8c13f1 | ||
|
|
0b3a56c3c4 | ||
|
|
89024bbf37 | ||
|
|
cf16671d8d | ||
|
|
671feb68a4 | ||
|
|
ccd5158109 | ||
|
|
0a18e32d62 | ||
|
|
e9fadc0785 | ||
|
|
cfa13f0669 | ||
|
|
89e43c6678 | ||
|
|
8a67797cb1 | ||
|
|
b29bc19ef4 | ||
|
|
e765066f05 | ||
|
|
67aa785a9e | ||
|
|
c88c26426d | ||
|
|
b4e9a9764f | ||
|
|
06e79e8926 | ||
|
|
9427f7b587 | ||
|
|
bce22edfe3 | ||
|
|
656d4ed506 | ||
|
|
5e3fcafb3a | ||
|
|
660cfd4f01 | ||
|
|
7a1270f861 | ||
|
|
b35b893351 | ||
|
|
f45f9263db | ||
|
|
8289dc92ed | ||
|
|
862107c708 | ||
|
|
778660a8c9 | ||
|
|
6e55f0c6e3 | ||
|
|
3b0e740c17 | ||
|
|
2dd87b6b5e | ||
|
|
cdcacf2f83 | ||
|
|
51aaaf2e8d | ||
|
|
e6438f9981 | ||
|
|
9135cffaa4 | ||
|
|
73492ca4bc | ||
|
|
fe3c1f69c3 | ||
|
|
31ee3feb57 | ||
|
|
f4ed63c54c | ||
|
|
8f88cdd826 | ||
|
|
9933a4268f | ||
|
|
8a54c228fd | ||
|
|
b5f2c747e0 | ||
|
|
ba35e83db2 | ||
|
|
61a2c551fc | ||
|
|
20c91ba2fa | ||
|
|
969f8b916b | ||
|
|
b7b7a7e95d | ||
|
|
455b108a6c | ||
|
|
645ca7741b | ||
|
|
36643c551d | ||
|
|
0fcdee8857 | ||
|
|
26ae686687 | ||
|
|
b94bd9a659 | ||
|
|
f15e7d43e3 | ||
|
|
05c256dd5b | ||
|
|
37295f6967 | ||
|
|
dfdbb91f0a | ||
|
|
72f93dca7a | ||
|
|
ec2cf31cfa | ||
|
|
ecd4d2afe0 | ||
|
|
ec9d104cf3 | ||
|
|
11214c7d1f | ||
|
|
fba27ff884 | ||
|
|
f8907e3c83 | ||
|
|
f1688d2b3f | ||
|
|
693045b542 | ||
|
|
14dfb9abec | ||
|
|
c8ed3ed73b | ||
|
|
bce5203eeb | ||
|
|
74c0c2cc38 | ||
|
|
4f25072352 | ||
|
|
91c3a39134 | ||
|
|
ae94b2a7b3 | ||
|
|
3b013a1017 | ||
|
|
80aab220b6 | ||
|
|
34c3e44b9d | ||
|
|
78d304443a | ||
|
|
d6c24eb9f6 | ||
|
|
f7fd1ef2bf | ||
|
|
af7bf5bd2b | ||
|
|
ea666f1098 | ||
|
|
5bb80f94c7 | ||
|
|
2f29c56a36 | ||
|
|
de86b8a96e | ||
|
|
060c9c8aa1 | ||
|
|
727428a965 | ||
|
|
df455bbcf5 | ||
|
|
946eea4c9e | ||
|
|
5cbc87369e | ||
|
|
5cdd5e0564 | ||
|
|
f493d6bb40 | ||
|
|
8e073b9c3e | ||
|
|
ea2a692d18 | ||
|
|
1b7c5be9c5 | ||
|
|
f7903df805 | ||
|
|
d2c61dc90e | ||
|
|
7b68098785 | ||
|
|
48f2ea717e | ||
|
|
cb3f03fd39 | ||
|
|
06f1fe18d6 | ||
|
|
1dbf924c6a | ||
|
|
3f6814f421 | ||
|
|
782828ac4f | ||
|
|
bd3759d55e | ||
|
|
672993e69e | ||
|
|
987bdaf237 | ||
|
|
7cf382a3b8 | ||
|
|
19dce9ddfa | ||
|
|
0afc0dd65a | ||
|
|
73d612a07d | ||
|
|
3b1529ef81 | ||
|
|
15187c0adb | ||
|
|
c5f31c3d03 | ||
|
|
2c17e78347 | ||
|
|
4ee646ce0b | ||
|
|
1f7b4a74fa | ||
|
|
4bc90701cc | ||
|
|
490deb9347 | ||
|
|
28d9484a13 | ||
|
|
e67e684ee0 | ||
|
|
6cfe3e6a97 | ||
|
|
99ac524905 | ||
|
|
2faf7fdb78 | ||
|
|
6a8ea8a083 | ||
|
|
e0e56cd831 | ||
|
|
bbc6febb72 | ||
|
|
7f7f42d721 | ||
|
|
589236c27b | ||
|
|
c16c5e0802 | ||
|
|
36cab40ac1 | ||
|
|
4186d78305 | ||
|
|
06cccb77f8 | ||
|
|
1895f4c556 | ||
|
|
849a873e61 | ||
|
|
b5c0372c99 | ||
|
|
1ba9b69849 | ||
|
|
6345a4f5b3 | ||
|
|
382fc75b1e | ||
|
|
92fc9ea971 | ||
|
|
de7ac2a240 | ||
|
|
7b0e5adaee | ||
|
|
406b59501b | ||
|
|
d5da2bed75 | ||
|
|
924d5b9377 | ||
|
|
bb47299ee4 | ||
|
|
20065d3daa | ||
|
|
ccb267beab | ||
|
|
32bcb59601 | ||
|
|
c708c44f0a | ||
|
|
9415a71f9d | ||
|
|
1fd42f2c53 | ||
|
|
1e52502ab3 | ||
|
|
a144d7e4f3 | ||
|
|
e855b79f9c | ||
|
|
2f8a8f9f50 | ||
|
|
b9a58bf625 | ||
|
|
c8075e53d2 | ||
|
|
ff54cf24a1 | ||
|
|
af0833e821 | ||
|
|
da11542322 | ||
|
|
3bcdd1770a | ||
|
|
4dc596e646 | ||
|
|
2e69210825 | ||
|
|
625887d249 | ||
|
|
b7c34b7794 | ||
|
|
941cf38a3e | ||
|
|
7f61896ec8 | ||
|
|
b14b49cbf0 | ||
|
|
6de3510a5d | ||
|
|
dea519095c | ||
|
|
3f8ca0cee9 | ||
|
|
1b998da57a | ||
|
|
772747d42d | ||
|
|
3998258afb | ||
|
|
4e86de98c4 | ||
|
|
2a497989e9 | ||
|
|
361b19e455 | ||
|
|
c036b26ae5 | ||
|
|
dcf6ffef12 | ||
|
|
865ede39fe | ||
|
|
a27e84ad89 | ||
|
|
b83bd26325 | ||
|
|
44227d7b86 | ||
|
|
6bcf022523 | ||
|
|
ccec26ffa7 | ||
|
|
83e159e42f | ||
|
|
cbabd4219e | ||
|
|
548afe3153 | ||
|
|
35c5f42b35 | ||
|
|
b9ff8b1d6c | ||
|
|
bb6a20dc11 | ||
|
|
e97955f5a0 | ||
|
|
35bd56ffea | ||
|
|
78affb766e | ||
|
|
9b1704e3b2 | ||
|
|
55cdbdc085 | ||
|
|
58620988d7 | ||
|
|
467f313091 | ||
|
|
091578573a | ||
|
|
62c1237024 | ||
|
|
8d41d02397 | ||
|
|
fce3f80654 | ||
|
|
2a0a51bea0 | ||
|
|
91d94d5920 | ||
|
|
c59f21230d | ||
|
|
828cc1fbd1 | ||
|
|
57f4958fc6 | ||
|
|
3aeb57b4df | ||
|
|
1b85614db9 | ||
|
|
57ecf49eb1 | ||
|
|
f279b0d1e5 | ||
|
|
32071297e6 | ||
|
|
1d98c38ff3 | ||
|
|
c09e0e2b65 | ||
|
|
0c8f967391 | ||
|
|
aca34379e0 | ||
|
|
1edd7045be | ||
|
|
c784c499c2 | ||
|
|
36c751bcc3 | ||
|
|
8a14a84bec | ||
|
|
b00703cec2 | ||
|
|
05e783564f | ||
|
|
330fb02486 | ||
|
|
1447ab8dac | ||
|
|
d574ee4edb | ||
|
|
814fe953a9 | ||
|
|
280f13b8cf | ||
|
|
a96b44a482 | ||
|
|
4286d248e9 | ||
|
|
116537019b | ||
|
|
8b37b8c1fd | ||
|
|
63b4339ca0 | ||
|
|
fdd239f61f | ||
|
|
5ca5d95c5e | ||
|
|
3fcad50924 | ||
|
|
8e40540d24 | ||
|
|
04d22bb84d | ||
|
|
5415f1bfa1 | ||
|
|
ff3bf4791a | ||
|
|
eebea216cb | ||
|
|
fbcd7f46b8 | ||
|
|
846278b18e | ||
|
|
2f2b1e18bf | ||
|
|
073c250fa4 | ||
|
|
1f336f89a6 | ||
|
|
a47fec7f6c | ||
|
|
084434d3b4 | ||
|
|
ebfbc11973 | ||
|
|
9cc9579b2d | ||
|
|
7beccd9dbc | ||
|
|
0e195bc7a2 | ||
|
|
f89efd5fce | ||
|
|
48d278fca9 | ||
|
|
c84effdaa1 | ||
|
|
e9601ef138 | ||
|
|
44c5cd5526 | ||
|
|
1c9662a8f2 | ||
|
|
5d08b2ce33 | ||
|
|
bb9d7d7ef3 | ||
|
|
766bb5c8aa | ||
|
|
84144659cf | ||
|
|
1394137436 | ||
|
|
998614b923 | ||
|
|
5b346397b8 | ||
|
|
1f99269002 | ||
|
|
160cbe8125 | ||
|
|
b9fa05c3bb | ||
|
|
4287a4d3ad | ||
|
|
37d2aafb26 | ||
|
|
4332170691 | ||
|
|
9a7c0f4737 | ||
|
|
9e7e172a7b | ||
|
|
71fbaf572a | ||
|
|
2ab29e5bfa | ||
|
|
85f8f910b9 | ||
|
|
b779d08d7f | ||
|
|
3b5634f14b | ||
|
|
f91ba357cf | ||
|
|
616faff96b | ||
|
|
5e6869403e | ||
|
|
7ff7d82959 | ||
|
|
9b751c1865 | ||
|
|
d1d31096e0 | ||
|
|
30f8522626 | ||
|
|
d3c221e061 | ||
|
|
8a421224f8 | ||
|
|
7dfce71ac9 | ||
|
|
35ba97f76a | ||
|
|
41921eaf3d | ||
|
|
03221ea86c | ||
|
|
b50761e4d1 | ||
|
|
40dea771cc | ||
|
|
e011f8f42f | ||
|
|
09d4b4354a | ||
|
|
ab151654fb | ||
|
|
ea9556b1b9 | ||
|
|
3dc6fd5c10 | ||
|
|
f39acbc037 | ||
|
|
005f7ff07e | ||
|
|
144ca7c171 | ||
|
|
7012b99d73 | ||
|
|
72bacd56f7 | ||
|
|
cc75038ccc | ||
|
|
f4810125e3 | ||
|
|
acf1faf151 | ||
|
|
255fbe94f7 | ||
|
|
b5d1eba28e | ||
|
|
1509978738 | ||
|
|
607b9e55a9 | ||
|
|
7c4c980409 | ||
|
|
b8ad3ec1b1 | ||
|
|
87dd33f66e | ||
|
|
7d8d13759a | ||
|
|
2b4f2a9171 | ||
|
|
8e869de350 | ||
|
|
b0ef082b2a | ||
|
|
bf8e74198d | ||
|
|
e77805471c | ||
|
|
45a8004b33 | ||
|
|
990f4dce9b | ||
|
|
0f36197c54 | ||
|
|
890a2bcc15 | ||
|
|
224355e83a | ||
|
|
c6ea4e389a | ||
|
|
678142b3fb | ||
|
|
ae6f83cd21 | ||
|
|
626b2be1fe | ||
|
|
ac5c789c75 | ||
|
|
ce2878f1e8 | ||
|
|
d4162899b4 | ||
|
|
e900d50e38 | ||
|
|
a438a4746a | ||
|
|
cfb819506f | ||
|
|
b86b915f40 | ||
|
|
ad5a5ad3db | ||
|
|
74081d8a36 | ||
|
|
34a434f07c | ||
|
|
dc944d8ca7 | ||
|
|
b06a7e7197 | ||
|
|
fa61d90115 | ||
|
|
7977c9ab44 | ||
|
|
6273a7d54e | ||
|
|
4d1a9c2aa1 | ||
|
|
b26ded423b | ||
|
|
e4b6eba5d7 | ||
|
|
bc225024a1 | ||
|
|
e616ecf160 | ||
|
|
f93562c6bf | ||
|
|
ac39c3699b | ||
|
|
091bc1ab13 | ||
|
|
fcbb66a788 | ||
|
|
ab2bc3bfb2 | ||
|
|
42dd6f9d08 | ||
|
|
465bcd46f8 | ||
|
|
cc88a6cb58 | ||
|
|
ba8f1bfcfd | ||
|
|
d4d6ced957 | ||
|
|
0b664e75cb | ||
|
|
1a4c2953f7 | ||
|
|
765c95de39 | ||
|
|
b2ea8f54df | ||
|
|
d7aecabcaa | ||
|
|
9ca049051c | ||
|
|
790509676f | ||
|
|
ce016eb567 | ||
|
|
57e34abe98 | ||
|
|
fd92b7c455 | ||
|
|
0ee68d1dfc | ||
|
|
1856c622a1 | ||
|
|
0a48a2effa | ||
|
|
543864f0f5 | ||
|
|
0fe94e47cc | ||
|
|
fc09210aea | ||
|
|
3e194969c0 | ||
|
|
391cffb454 | ||
|
|
47486f8bab | ||
|
|
620e363ce6 | ||
|
|
0c2276775d | ||
|
|
ad51a7cd85 | ||
|
|
28952789a4 | ||
|
|
14adcdb517 | ||
|
|
cc80590488 | ||
|
|
48416289ac | ||
|
|
003a27f625 | ||
|
|
bff4a2259f | ||
|
|
9adf856705 | ||
|
|
2215de5285 | ||
|
|
013467d6c6 | ||
|
|
1f52b8af2f | ||
|
|
ce32f76265 | ||
|
|
836f65376c | ||
|
|
99940dd28c | ||
|
|
ffeb801b58 | ||
|
|
339bbcf070 | ||
|
|
7b83bddc2d | ||
|
|
5549733a0b | ||
|
|
4e21917c0e | ||
|
|
939b4b2aab | ||
|
|
fd0770859d | ||
|
|
d5854fb3c9 | ||
|
|
3aa22a27cc | ||
|
|
10b1a2f5f5 | ||
|
|
a28a34773c | ||
|
|
7c744d14d7 | ||
|
|
6c68f2eb7e | ||
|
|
eb2d2b7313 | ||
|
|
2e50abedaa | ||
|
|
ee53136ed2 | ||
|
|
e923983dca | ||
|
|
f4753862f1 | ||
|
|
16b40f3a19 | ||
|
|
9cd3a7550b | ||
|
|
d840a7e6b9 | ||
|
|
d875691955 | ||
|
|
c600bfa8ca | ||
|
|
973ffa1a64 | ||
|
|
caffc3d93c | ||
|
|
a4dcf656f3 | ||
|
|
7bb5d48966 | ||
|
|
71b7b0b393 | ||
|
|
bd02eea66b | ||
|
|
cdcb10fb58 | ||
|
|
6cd7296001 | ||
|
|
168021523f | ||
|
|
03f2635296 | ||
|
|
e3b08fa92b | ||
|
|
79cebe66de | ||
|
|
0619e2a129 | ||
|
|
64a81e4f61 | ||
|
|
0431ae53ca | ||
|
|
89c873acd0 | ||
|
|
2e70cf9388 | ||
|
|
2efd0461d1 | ||
|
|
196a34684d | ||
|
|
402fd6850c | ||
|
|
72515f440d | ||
|
|
045d919cdc | ||
|
|
9b9108320e | ||
|
|
5efb100f12 | ||
|
|
b747dd6ae8 | ||
|
|
ed2bc9e44d | ||
|
|
9e1a2149fa | ||
|
|
22b6d8c17b | ||
|
|
3876846410 | ||
|
|
a93c79e001 | ||
|
|
6aae0276da | ||
|
|
1d80659bc3 | ||
|
|
94d5e86d4f | ||
|
|
aecf7729d8 | ||
|
|
f130d537b7 | ||
|
|
f30f862e7e | ||
|
|
81e1164358 | ||
|
|
542bd4cbb8 | ||
|
|
771b57778e | ||
|
|
9be56a5e56 | ||
|
|
da744958c2 | ||
|
|
f6bda1e480 | ||
|
|
d2e24534c7 | ||
|
|
df6f974eca | ||
|
|
2f5c6b5e16 | ||
|
|
97176b13f1 | ||
|
|
18bb7e58be | ||
|
|
c2bab44bdd | ||
|
|
53bb8a9831 | ||
|
|
1b66120e7d | ||
|
|
1478f321ae | ||
|
|
5fb92c78ad | ||
|
|
a0a792b821 | ||
|
|
3feb0e648d | ||
|
|
fa5358a5bf | ||
|
|
7399a398a7 | ||
|
|
25a78aceb9 | ||
|
|
66708454dd | ||
|
|
bb5e3d11d8 | ||
|
|
ff54db2e5f | ||
|
|
434d8fc35f | ||
|
|
12eb813bc3 | ||
|
|
88d5576150 | ||
|
|
af35e4adeb | ||
|
|
eaeacb8848 | ||
|
|
f00e68e142 | ||
|
|
113356a24e | ||
|
|
b89c134e7f | ||
|
|
3748794048 | ||
|
|
ccca12176e | ||
|
|
c89dd331f7 | ||
|
|
fa81ed5f39 | ||
|
|
6c34f6b8d9 | ||
|
|
4f21a5691d | ||
|
|
b2a839971b | ||
|
|
8ad99be322 | ||
|
|
4e771e8727 | ||
|
|
e725bdfb2b | ||
|
|
ac557f73b3 | ||
|
|
0ba3501a46 | ||
|
|
b1fe12881e | ||
|
|
c1eb33c0da | ||
|
|
03bb92c942 | ||
|
|
b0da5a54cc | ||
|
|
44e056e210 | ||
|
|
48a8680ba4 | ||
|
|
b73bcc2c22 | ||
|
|
cff42936aa | ||
|
|
d3b04004b4 | ||
|
|
5cd92f10ef | ||
|
|
22a3ab983b | ||
|
|
83d2e6b8b4 | ||
|
|
4e979c5880 | ||
|
|
71e1089139 | ||
|
|
349c154a99 | ||
|
|
54410dbe49 | ||
|
|
4e08bb7b05 | ||
|
|
934ca6a7d7 | ||
|
|
e878caebe3 | ||
|
|
088eda2983 | ||
|
|
418cd24979 | ||
|
|
b4fe9e3eec | ||
|
|
c13bbd05cd | ||
|
|
58330fe8b2 | ||
|
|
680d024b05 | ||
|
|
defcd5764b | ||
|
|
e87f785a0a | ||
|
|
0227bbc305 | ||
|
|
d05afec289 | ||
|
|
64035d3ecb | ||
|
|
21e0bb28ad | ||
|
|
c6358169ad | ||
|
|
955f4fbb19 | ||
|
|
df7c44ae42 | ||
|
|
8573649bf7 | ||
|
|
52c46c6dca | ||
|
|
54ea3ec5d6 | ||
|
|
1632035784 | ||
|
|
b239535964 | ||
|
|
0751cc50b9 | ||
|
|
da5d844ec4 | ||
|
|
2775fd1fcf | ||
|
|
a87635dcf4 | ||
|
|
e30517e62c | ||
|
|
a54f3c4b31 | ||
|
|
bda6cea0ce | ||
|
|
6fece09ed7 | ||
|
|
3917c6b2f0 | ||
|
|
96a89b5bdc | ||
|
|
b55027fe71 | ||
|
|
eacbb82399 | ||
|
|
ee279f84ad | ||
|
|
26959d5b75 | ||
|
|
ff5005fa93 | ||
|
|
8f316e12d5 | ||
|
|
5f00fc4e27 | ||
|
|
f279730b0f | ||
|
|
5a5f8b03d1 | ||
|
|
5e73e9cd72 | ||
|
|
129de9182f | ||
|
|
37383c10ac | ||
|
|
09798df7a0 | ||
|
|
b360225e08 | ||
|
|
09d5e44b13 | ||
|
|
8ba89c0fa1 | ||
|
|
f984a27379 | ||
|
|
425a2310fe | ||
|
|
95571be278 | ||
|
|
7bf44a237e | ||
|
|
a119b24eeb | ||
|
|
47dbac9b50 | ||
|
|
a49282727b | ||
|
|
0d22fc7ac1 | ||
|
|
275791595c | ||
|
|
1040bc551f | ||
|
|
5aa0205c80 | ||
|
|
210a4ebcbe | ||
|
|
7fc2b06b3f | ||
|
|
1177c19a43 | ||
|
|
8a2417f32d | ||
|
|
a154347834 | ||
|
|
d51adf2aa0 | ||
|
|
a5f0c1613e | ||
|
|
7c4bcf9004 | ||
|
|
f3fb0dc5fe | ||
|
|
24ea90bd68 | ||
|
|
ef3e94c7e1 | ||
|
|
883832f78d | ||
|
|
1f03267273 | ||
|
|
8bc2ce1c30 | ||
|
|
2ae92c06e3 | ||
|
|
4a6a214f3c | ||
|
|
c2d7011aa7 | ||
|
|
c0195ab23f | ||
|
|
e4e50d0e81 | ||
|
|
573746ce54 | ||
|
|
6b2df13cdb | ||
|
|
3166b44580 | ||
|
|
e500485c21 | ||
|
|
2dd44d5f89 | ||
|
|
f656cb29be | ||
|
|
59e5a63d5f | ||
|
|
53230b6eb0 | ||
|
|
80ca59f152 | ||
|
|
eb624e43c0 | ||
|
|
532e9cb09a | ||
|
|
ef4d2a7ed0 | ||
|
|
5daa6274e8 | ||
|
|
6d2ac30461 | ||
|
|
d109a1b27f | ||
|
|
33a203d56e | ||
|
|
a19811f379 | ||
|
|
f23023961e | ||
|
|
b463a0566e | ||
|
|
38d5743c06 | ||
|
|
6990312051 | ||
|
|
a7cf51868b | ||
|
|
815c1b9c49 | ||
|
|
88bba83383 | ||
|
|
b1d517398d | ||
|
|
4e5b41f150 | ||
|
|
56b2361f01 | ||
|
|
968cc65323 | ||
|
|
d0ee21e6dc | ||
|
|
a1345f2542 | ||
|
|
f290fe0871 | ||
|
|
c41687586c | ||
|
|
59a3bc0ff4 | ||
|
|
aa78e82fed | ||
|
|
d4e670d5e9 | ||
|
|
4553c6521f | ||
|
|
a42a6ca18c | ||
|
|
e72d527d88 | ||
|
|
f5c36043f6 | ||
|
|
b227ff87dc | ||
|
|
676f311f97 | ||
|
|
061d091c97 | ||
|
|
e7617f0abd | ||
|
|
790e867af0 | ||
|
|
f02299c06c | ||
|
|
ed781af52c | ||
|
|
67043177a9 | ||
|
|
49cc5fb673 | ||
|
|
68c95dee17 | ||
|
|
9bd7ab7280 | ||
|
|
7a359f6318 | ||
|
|
38b31aa88d | ||
|
|
e12e026bd8 | ||
|
|
212fbc125c | ||
|
|
2939de013b | ||
|
|
4a0585404a | ||
|
|
0562e23ee0 | ||
|
|
dcbf5996c2 | ||
|
|
a8551510cd | ||
|
|
087f6edd0c | ||
|
|
d6b7ee04a0 | ||
|
|
d5c5ff8b3f | ||
|
|
dc4396a699 | ||
|
|
a74b00c3f9 | ||
|
|
2fdb9f8b7e | ||
|
|
80fac3f1b8 | ||
|
|
17a6c88cc7 | ||
|
|
1ba69dbb9b | ||
|
|
ab1c7ebbe2 | ||
|
|
ee715da078 | ||
|
|
27e177dc05 | ||
|
|
7aac4bfc83 | ||
|
|
7b24f9b7a4 | ||
|
|
b36b902eeb | ||
|
|
f7a47e60cd | ||
|
|
30024abb6c | ||
|
|
1d9702e9e7 | ||
|
|
ee2eae63d6 | ||
|
|
cd477936b5 | ||
|
|
2587ebbacd | ||
|
|
dbe9d7e34e | ||
|
|
4815e9b990 | ||
|
|
f05b0ddf04 | ||
|
|
f94d34c94b | ||
|
|
e9811fb6da | ||
|
|
178fc1736d | ||
|
|
54d632adaf | ||
|
|
ae939e79da | ||
|
|
49f143e0d5 | ||
|
|
9d7bdf369d | ||
|
|
e12ef805a9 | ||
|
|
b36acb2dc0 | ||
|
|
1b883ae3fa | ||
|
|
72c94e1037 | ||
|
|
a270db1d87 | ||
|
|
0a4c993bb8 | ||
|
|
ec56134583 | ||
|
|
d8bf1c1691 | ||
|
|
8ac1754e18 | ||
|
|
e1f1143919 | ||
|
|
d9e38289c4 | ||
|
|
486050d0b8 | ||
|
|
828c90ac3d | ||
|
|
ab09ecce7e | ||
|
|
aebad2eb10 | ||
|
|
2a39a85d9e | ||
|
|
cb0270baa7 | ||
|
|
99302c9598 | ||
|
|
88ae653760 | ||
|
|
6881f9d70f | ||
|
|
60ddbe5729 | ||
|
|
5e5e557c8b | ||
|
|
bc8023644c | ||
|
|
7c7cd9cc80 | ||
|
|
83ef25e7de | ||
|
|
47d465e6e4 | ||
|
|
03d3e0578f | ||
|
|
440a442f30 | ||
|
|
1da52d7d1d | ||
|
|
9a7d1faf75 | ||
|
|
4d74f625d3 | ||
|
|
0a94fbc735 | ||
|
|
9ef34890fa | ||
|
|
3e07f2c173 | ||
|
|
ee28298d7f | ||
|
|
e59c4ee858 | ||
|
|
62aed13880 | ||
|
|
bffe934acc | ||
|
|
a520f0268f | ||
|
|
5e3b1fa540 | ||
|
|
95f29f7b63 | ||
|
|
87ffcaf03e | ||
|
|
2635146328 | ||
|
|
d727d85f6d | ||
|
|
20513475ef | ||
|
|
81a7af10c7 | ||
|
|
4a6e94f8ab | ||
|
|
146fe50e20 | ||
|
|
9bf2850fb1 | ||
|
|
ba2c36548e | ||
|
|
3b806320ec | ||
|
|
d07c743cdc | ||
|
|
c857d6e1bd | ||
|
|
94cd9a713f | ||
|
|
7676473ebd | ||
|
|
8c778b3f5c | ||
|
|
a66f8bd9fc | ||
|
|
95b2a15930 | ||
|
|
0179ec2da9 | ||
|
|
8f2313bb2a | ||
|
|
488a3d1118 | ||
|
|
9094df7bc7 | ||
|
|
16aad3fa67 | ||
|
|
3b47c3f21d | ||
|
|
987ce58926 | ||
|
|
20c88743df | ||
|
|
03395b95cb | ||
|
|
53f04a134a | ||
|
|
885f26ea8c | ||
|
|
3ab181fdf8 | ||
|
|
d70c1d48b5 | ||
|
|
a8e0cb9b5a | ||
|
|
6ea9a8988b | ||
|
|
45e35b3571 | ||
|
|
e12044e6af | ||
|
|
954067eb6d | ||
|
|
e43f9066d8 | ||
|
|
aecbebd566 | ||
|
|
3111bcde5e | ||
|
|
e6cffd537e | ||
|
|
70000d9ebb | ||
|
|
d95843b0bf | ||
|
|
13e766bc37 | ||
|
|
c34edc582e | ||
|
|
8eee389c09 | ||
|
|
8ed6d4d709 | ||
|
|
60bacbec47 | ||
|
|
af013559de | ||
|
|
b784415c57 | ||
|
|
85739ba6ad | ||
|
|
a02a593f47 | ||
|
|
bba6c8f15a | ||
|
|
67f28f501a | ||
|
|
9b9703a48e | ||
|
|
c55a3d3873 | ||
|
|
55aaec744a | ||
|
|
f27d304f3b | ||
|
|
2f24eddb7d | ||
|
|
6d51d19f01 | ||
|
|
170968dfc2 | ||
|
|
a33c91afa9 | ||
|
|
f930576fd1 | ||
|
|
d52f2883cf | ||
|
|
b872953bc5 | ||
|
|
d797de7a8d | ||
|
|
acc7bb00c5 | ||
|
|
8fb8a877be | ||
|
|
b96028cd87 | ||
|
|
682e241edb | ||
|
|
c1cb6eef08 | ||
|
|
3a63628f1f | ||
|
|
3705616cd9 | ||
|
|
200b808c27 | ||
|
|
d572d960e5 | ||
|
|
b8fcb660ad | ||
|
|
5673294623 | ||
|
|
7062bb0502 | ||
|
|
5db75128ba | ||
|
|
fbd2fc8ead | ||
|
|
bc73c16df7 | ||
|
|
659cffe0cc | ||
|
|
a1663a98e0 | ||
|
|
3de1dbc9e4 | ||
|
|
6d37e8601e | ||
|
|
0a50bad555 | ||
|
|
82c0058129 | ||
|
|
1bd307a26a | ||
|
|
740f43a2d6 | ||
|
|
d762753103 | ||
|
|
a020d5ccce | ||
|
|
c14f45a8f5 | ||
|
|
8269116dba | ||
|
|
1e28ea9bb0 | ||
|
|
17f2d33731 | ||
|
|
db941ccf88 | ||
|
|
a464cbdfe6 | ||
|
|
976797d4cf | ||
|
|
31e3169433 | ||
|
|
d2b15cb629 | ||
|
|
9cd000c4f2 | ||
|
|
ea4a0530b8 | ||
|
|
243c035b03 | ||
|
|
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 | ||
|
|
44953d6bcc | ||
|
|
10066b2bc7 | ||
|
|
609fc67f0d | ||
|
|
641d102aba | ||
|
|
f65e1c1587 | ||
|
|
626ec5e793 | ||
|
|
85517abf58 | ||
|
|
75f65b06e8 | ||
|
|
f2b05ccc29 | ||
|
|
e88f21c010 | ||
|
|
ed8e2c4818 | ||
|
|
48fee4fc92 | ||
|
|
7586bcf45e | ||
|
|
870527de1e | ||
|
|
a34aeb240a | ||
|
|
6ee165fddc | ||
|
|
f2570945c6 | ||
|
|
8072f78058 | ||
|
|
8ae0ee5a67 | ||
|
|
a75d2b1c80 | ||
|
|
c48c2af7a1 | ||
|
|
490a14c5ef | ||
|
|
c9db41a7f6 | ||
|
|
dcce6ef50b | ||
|
|
7cf0820d2b | ||
|
|
0bae3caaff | ||
|
|
bca0b256c9 | ||
|
|
a53d30c459 | ||
|
|
7a9f497aa7 | ||
|
|
7a6bfae93b | ||
|
|
f9f9bc3efb | ||
|
|
904990bf91 | ||
|
|
b2266ffca1 | ||
|
|
bb9a3d4b8e | ||
|
|
ae19c9b331 | ||
|
|
7d2cca8633 | ||
|
|
c52b48b0f5 | ||
|
|
893794f4e7 | ||
|
|
032da4fb1a | ||
|
|
0de5125de8 | ||
|
|
cd3f1fe874 | ||
|
|
4f68e94fb3 | ||
|
|
b3ecbbc8b3 | ||
|
|
01653a881a | ||
|
|
e021a59b87 | ||
|
|
e565e19b42 | ||
|
|
41319c85c7 | ||
|
|
daf56804a5 | ||
|
|
6f7a43804d | ||
|
|
0ca76d36ef | ||
|
|
ec5789997a | ||
|
|
7a0d61bbb0 | ||
|
|
243f28f8a5 | ||
|
|
78577594d0 | ||
|
|
d3e2f38da0 | ||
|
|
05f0fe0a88 | ||
|
|
e11d7c0444 | ||
|
|
1c2461974d | ||
|
|
2a754744fe | ||
|
|
b413593c43 | ||
|
|
c73edd7e21 | ||
|
|
a34a69d8e4 | ||
|
|
020a9d33f6 | ||
|
|
19f6f89312 | ||
|
|
d56e05a11a | ||
|
|
c379a4e5a7 | ||
|
|
44c1efe4e4 | ||
|
|
ff0d675082 | ||
|
|
e1087b4145 | ||
|
|
a203cde400 | ||
|
|
00c14dd9f6 | ||
|
|
71d9716117 | ||
|
|
2e4f63a290 | ||
|
|
9dee725895 | ||
|
|
267263dab7 | ||
|
|
1d81457f38 | ||
|
|
0885cad089 | ||
|
|
19d7632be0 | ||
|
|
323535584b | ||
|
|
852adbe514 | ||
|
|
2a5fa9a0d3 | ||
|
|
4c78553d90 | ||
|
|
bb702a9342 | ||
|
|
6c8368fa23 | ||
|
|
1c875209f7 | ||
|
|
82da09760c | ||
|
|
ef44aa0bd0 | ||
|
|
af5a3235fd | ||
|
|
07c510c178 | ||
|
|
c367bb63d1 | ||
|
|
819d658531 | ||
|
|
48f098482e | ||
|
|
1a49bc85b8 | ||
|
|
51ee564246 | ||
|
|
7f313c803e | ||
|
|
0d7c33b1a9 | ||
|
|
14f3abb51e | ||
|
|
46143ac54f | ||
|
|
6a30c0a997 | ||
|
|
d1702e3081 | ||
|
|
fa198c3b5e | ||
|
|
151b34ea79 | ||
|
|
3e8687e464 | ||
|
|
a31ae5297a | ||
|
|
e7792a0c65 | ||
|
|
3c32de1859 | ||
|
|
6a3fe3db92 | ||
|
|
ac048c154d | ||
|
|
c9b2ad4ffa | ||
|
|
3f51a8ffc2 | ||
|
|
2129b2b7a0 | ||
|
|
386b5bb848 | ||
|
|
d8bd189175 | ||
|
|
3734fc25a7 | ||
|
|
817760a6ef | ||
|
|
315e944b69 | ||
|
|
48722a22c9 | ||
|
|
a8f018a208 | ||
|
|
05ddc13054 | ||
|
|
716504b833 | ||
|
|
187861c3b2 | ||
|
|
fa70d8da09 | ||
|
|
0b075ac762 | ||
|
|
cd293e6f49 | ||
|
|
a6c889ed5e | ||
|
|
ca1533b0e4 | ||
|
|
3267596a30 | ||
|
|
5f29b93970 | ||
|
|
d178c4a91a | ||
|
|
ff63ce0630 | ||
|
|
757b77786a | ||
|
|
7a78449950 | ||
|
|
7b44b26e9e | ||
|
|
3f5da7357f | ||
|
|
2a6a21c33a | ||
|
|
8ac972856c | ||
|
|
1f49fcc777 | ||
|
|
f2f5bfd17c | ||
|
|
059af398eb | ||
|
|
6044e5961b | ||
|
|
59f5cbe6f1 | ||
|
|
72e004c12b | ||
|
|
52a4b0c2b8 | ||
|
|
74abb82de2 | ||
|
|
e318f5c697 | ||
|
|
b62c61329a | ||
|
|
1e71d8dcc8 | ||
|
|
f342dc8196 | ||
|
|
87c333ff7a | ||
|
|
76893df5bd | ||
|
|
c8453e2c81 | ||
|
|
8ab4a4b82d | ||
|
|
e6fe814ada | ||
|
|
e171a69240 | ||
|
|
55cb11da07 | ||
|
|
3104edba0f | ||
|
|
3878389f2b | ||
|
|
356a064dd1 | ||
|
|
a0ba866d5b | ||
|
|
ede63cd6be | ||
|
|
9311d1fe44 | ||
|
|
11bfa2a813 | ||
|
|
05d6bde362 | ||
|
|
afcbbb3538 | ||
|
|
8fd117ee8c | ||
|
|
d031e1a7e9 | ||
|
|
e83fa8840b | ||
|
|
fcf73165ed | ||
|
|
c911f1262a | ||
|
|
ba3e4c5dff | ||
|
|
ae564ef702 | ||
|
|
7d3a591139 | ||
|
|
9b3f76ab5a | ||
|
|
dab8acc7d8 | ||
|
|
4eda53d5a1 | ||
|
|
8c0296ca67 | ||
|
|
7ef094325d | ||
|
|
2365862615 | ||
|
|
e30749e94c | ||
|
|
c20dea64cf | ||
|
|
faef8b679e | ||
|
|
9b3e21225c | ||
|
|
7640e3255f | ||
|
|
4c5ed3df2c | ||
|
|
39601be452 | ||
|
|
76aaecb8f2 | ||
|
|
4b2faeedab | ||
|
|
0087e3748c | ||
|
|
754ec33e4b | ||
|
|
9f8b74adf9 | ||
|
|
8916841e83 | ||
|
|
e7f21f41ee | ||
|
|
ba860a2b61 | ||
|
|
c349a5c75b | ||
|
|
d522b7ef1e | ||
|
|
800125c7a9 | ||
|
|
37f20c6889 | ||
|
|
5dfe7bea8e | ||
|
|
6067622c19 | ||
|
|
fac7b064b4 | ||
|
|
ef6f252842 | ||
|
|
b8da19e49f | ||
|
|
a483df8b20 | ||
|
|
41ccc13394 | ||
|
|
0978357c5f | ||
|
|
7935085e74 | ||
|
|
7a47c9e38b | ||
|
|
20124bfca0 | ||
|
|
eaeaa297c7 | ||
|
|
9adb9ab5f4 | ||
|
|
c4c4c977a6 | ||
|
|
7d508dcb52 | ||
|
|
773754d74f | ||
|
|
ed20a23297 | ||
|
|
4615c84f31 | ||
|
|
677136f4ab | ||
|
|
3c3710420b | ||
|
|
de268b8225 | ||
|
|
42c709e7b1 | ||
|
|
cf0349acc8 | ||
|
|
e43b36b61f | ||
|
|
ed24867309 | ||
|
|
3cf78749df | ||
|
|
badbf766bb | ||
|
|
5b265dbc1c | ||
|
|
0053e143e7 | ||
|
|
1c44135b41 | ||
|
|
5f883a4445 | ||
|
|
8dc6ff268d | ||
|
|
57d7df530b | ||
|
|
13b2fe8d30 | ||
|
|
d644988845 | ||
|
|
27c6cfc958 | ||
|
|
3b9a48ff5f | ||
|
|
a5354ded3f | ||
|
|
0b07dafe77 | ||
|
|
f0e900b885 | ||
|
|
f460043e87 | ||
|
|
8c6b804d73 | ||
|
|
790512d52a | ||
|
|
89c8d26968 | ||
|
|
6d9d31cad1 | ||
|
|
6642083f52 | ||
|
|
554090b03e | ||
|
|
1cde09c312 | ||
|
|
e215b4d919 | ||
|
|
5ae6c25394 | ||
|
|
68cd8dbc3e | ||
|
|
01fe88e337 | ||
|
|
2b8923931e | ||
|
|
8d119713bc | ||
|
|
07f2e28eca | ||
|
|
0e849609f4 | ||
|
|
120a7a3090 | ||
|
|
872cd10b8b | ||
|
|
7e673fee90 | ||
|
|
847611aed4 | ||
|
|
15674111b4 | ||
|
|
1b2feeb99c | ||
|
|
f2f211908d | ||
|
|
ac9e3c6260 | ||
|
|
58ba107d01 | ||
|
|
087b4289e5 | ||
|
|
3df5f6110c | ||
|
|
6efb2a2054 | ||
|
|
4f8593f46a | ||
|
|
9eb7a84d83 | ||
|
|
8e65e794bc | ||
|
|
ecc7758788 | ||
|
|
6e40fd8000 | ||
|
|
f4c674fa98 | ||
|
|
eba96eb904 | ||
|
|
3986bb6c4e | ||
|
|
10349b7be4 | ||
|
|
d8f5e81880 | ||
|
|
ea81d08c01 | ||
|
|
f69acaa13d | ||
|
|
754c7324f5 | ||
|
|
2b4e32d2cf | ||
|
|
9ed933f835 | ||
|
|
c4b3579c60 | ||
|
|
c5d1802346 | ||
|
|
d873f88b56 | ||
|
|
17781066a2 | ||
|
|
ac0fbaad21 | ||
|
|
8ac7f639d8 | ||
|
|
3ac1d30a3a | ||
|
|
1f420777af | ||
|
|
37a212ddc4 | ||
|
|
138e62e1ef | ||
|
|
5b12784589 | ||
|
|
c9ab9d59c2 | ||
|
|
ac15b3a5af | ||
|
|
468356b120 | ||
|
|
f0a28b9168 | ||
|
|
c8f0c6b5f6 | ||
|
|
e653531934 | ||
|
|
3444c2aadd | ||
|
|
7aa7548a51 | ||
|
|
5b3596987b | ||
|
|
1e5c90ed65 | ||
|
|
f694d2e150 | ||
|
|
9738d53a82 | ||
|
|
e1d9dac70c | ||
|
|
e6324e3a19 | ||
|
|
4489db76c9 | ||
|
|
67ffada4b3 | ||
|
|
9aaf5cf914 | ||
|
|
08af7419af | ||
|
|
035b711ee3 | ||
|
|
5ad25dedf8 | ||
|
|
de47aa8466 | ||
|
|
9a78bd6e3f | ||
|
|
08cbb66a55 | ||
|
|
824cf93494 | ||
|
|
00d2f2e7b4 | ||
|
|
968ad2859e | ||
|
|
11ca12e43c | ||
|
|
15fad5476e | ||
|
|
4e468fdf24 | ||
|
|
cb4b9fce30 | ||
|
|
a562348dfa | ||
|
|
bcef1c7a76 | ||
|
|
4bbb83826c | ||
|
|
b9dbf1873d | ||
|
|
45462fb47e | ||
|
|
bf4ad692df | ||
|
|
4e943d52e4 | ||
|
|
7082f9f882 | ||
|
|
4a982fe632 | ||
|
|
1e351bd05f | ||
|
|
f0d5bfd42f | ||
|
|
256ef7c5ec | ||
|
|
6e63555bc8 | ||
|
|
5432e108a1 | ||
|
|
3fcd17e6a5 | ||
|
|
c562d17925 | ||
|
|
cf1d6919bf | ||
|
|
4b15f960e1 | ||
|
|
f92b8dcec0 | ||
|
|
9734552da5 | ||
|
|
89b7ce4c4e | ||
|
|
6dc790f447 | ||
|
|
7d62df6f1a | ||
|
|
6f7bb8a777 | ||
|
|
942f64f04d | ||
|
|
8de7014eeb | ||
|
|
d73c4a92a7 | ||
|
|
e328de5293 | ||
|
|
93054ef87c | ||
|
|
2cd1da5222 | ||
|
|
ed24eac29c | ||
|
|
3de53a313f | ||
|
|
6d2b2ac5f9 | ||
|
|
76cf170708 | ||
|
|
06ead557dc | ||
|
|
7c343411b8 | ||
|
|
736950ab3f | ||
|
|
bad04f9a0b | ||
|
|
adf754ad32 | ||
|
|
2ebd3f54e6 | ||
|
|
be63e18ebf | ||
|
|
ba82ce2798 | ||
|
|
1f7ad78f40 | ||
|
|
3130fdc4f0 | ||
|
|
5922fb38da | ||
|
|
d1e3135331 | ||
|
|
d722b6ba19 | ||
|
|
a3fe105256 | ||
|
|
04f68fddd9 | ||
|
|
03c273e30f | ||
|
|
90c478e58d | ||
|
|
c3a0bb2b77 | ||
|
|
2cd63234c1 | ||
|
|
ccd0842df8 | ||
|
|
2a2db4f526 | ||
|
|
21f1439ad8 | ||
|
|
4cbcd3c606 | ||
|
|
1f14767fe9 | ||
|
|
0b53c35523 | ||
|
|
552a8044b0 | ||
|
|
cc96c436a9 | ||
|
|
585a6f15a6 | ||
|
|
f37e50cc71 | ||
|
|
3023d3c358 | ||
|
|
08562b645e | ||
|
|
edb1d2fb9e | ||
|
|
9645101de2 | ||
|
|
c1bbd6e766 | ||
|
|
72ca4c2e1f | ||
|
|
bccd79b6be | ||
|
|
109a27c9ef | ||
|
|
2fa2ade3ae | ||
|
|
0a4c8a40ba | ||
|
|
c49743d38c | ||
|
|
a9afc1e6ba | ||
|
|
aa6f5fd139 | ||
|
|
519f658c07 | ||
|
|
f5cb56fd86 | ||
|
|
c830db07ad | ||
|
|
9093702692 | ||
|
|
edd58b4b7a | ||
|
|
72432d65ba | ||
|
|
eb611a2855 | ||
|
|
8aa73ed6ae | ||
|
|
1224222984 | ||
|
|
3360c6aa96 | ||
|
|
bfddd3fc69 | ||
|
|
35cd81a75f | ||
|
|
f11fceb63a | ||
|
|
f14a28db54 | ||
|
|
cacc01bac0 | ||
|
|
fc386f4fa1 | ||
|
|
3743aaa16e | ||
|
|
22b4640b31 | ||
|
|
1dcda4989d | ||
|
|
e59768167a | ||
|
|
7e5becb5e5 | ||
|
|
b4f348ccea | ||
|
|
7a0dd24681 | ||
|
|
e5ae82252f | ||
|
|
49c45d1007 | ||
|
|
5502bff986 | ||
|
|
ee19789cac | ||
|
|
bad5a1de55 | ||
|
|
3cdbe213a3 | ||
|
|
3b5b1bf877 | ||
|
|
bcd9229ffe | ||
|
|
4df588668a | ||
|
|
de96500c1a | ||
|
|
9b881cdd19 | ||
|
|
2ccf39800d | ||
|
|
5a3065344e | ||
|
|
98b6b5e3f6 | ||
|
|
4d81fa6df5 | ||
|
|
8e8582e953 | ||
|
|
7f4c05e88f | ||
|
|
20e63659a1 | ||
|
|
c5af69db2b | ||
|
|
030241d1c3 | ||
|
|
4f01c43a93 | ||
|
|
ab94471e91 | ||
|
|
140aa68811 | ||
|
|
d9ef38e370 | ||
|
|
acf66116cd | ||
|
|
8e69125128 | ||
|
|
7402ca3568 | ||
|
|
d7b240f25c | ||
|
|
e5e4d3bed4 | ||
|
|
1a59302749 | ||
|
|
f0a5bbedb4 | ||
|
|
139665b3d6 | ||
|
|
ca23d59148 | ||
|
|
1635f00a62 | ||
|
|
29a4404257 | ||
|
|
24db29f526 | ||
|
|
e27b64274f | ||
|
|
46721bcf46 | ||
|
|
80cefdd1d5 | ||
|
|
9972f4da48 | ||
|
|
1ab5401501 | ||
|
|
0b60cc8341 | ||
|
|
85b4746516 | ||
|
|
d17ac9c83c | ||
|
|
e6ff513aac | ||
|
|
ffbfeab977 | ||
|
|
09db062958 | ||
|
|
ab7732d9ae | ||
|
|
46594ec707 | ||
|
|
18426561e3 | ||
|
|
aeb7e3a9e1 | ||
|
|
a77c46be20 | ||
|
|
a4be2cddf2 | ||
|
|
240b335181 | ||
|
|
53d6807e8d | ||
|
|
db38cc8891 | ||
|
|
4e2aeceeec | ||
|
|
9b04a04568 | ||
|
|
f2c97bda66 | ||
|
|
6c4d919828 | ||
|
|
f6a502a8e3 | ||
|
|
e5be023e4b | ||
|
|
47743bdcfa | ||
|
|
b0962363c2 | ||
|
|
8855ef72bc | ||
|
|
264727a414 | ||
|
|
98c16ddc4d | ||
|
|
c7691fbebe | ||
|
|
62f92d5b28 | ||
|
|
2ae9165bfb | ||
|
|
08de326930 | ||
|
|
b341cfd4d9 | ||
|
|
a76b018900 | ||
|
|
9783da5d8e | ||
|
|
6f0985dcaa | ||
|
|
0b44886b62 | ||
|
|
36991b5c8a | ||
|
|
afb7f89722 | ||
|
|
0248a36561 | ||
|
|
2e7470115c | ||
|
|
06b376d242 | ||
|
|
4b17813b9f | ||
|
|
960a7f82ef | ||
|
|
25be8ccd05 | ||
|
|
da6c68629d | ||
|
|
b63baf939e | ||
|
|
90d8e0cedc | ||
|
|
1c2d4c518e | ||
|
|
0c030e811f | ||
|
|
428ef11157 | ||
|
|
ee34b64f5d | ||
|
|
c1f9d8f7a1 | ||
|
|
996be5d247 | ||
|
|
7d45419724 | ||
|
|
c0ae5c0fb7 | ||
|
|
09042d12d4 | ||
|
|
7db147da14 | ||
|
|
1324b5da13 | ||
|
|
4ee14e6e77 | ||
|
|
e1d50757b3 | ||
|
|
9a447e8554 | ||
|
|
516a5e9c5f | ||
|
|
4744f5eecf | ||
|
|
33839b5667 | ||
|
|
43f2d64a6f | ||
|
|
13f30c3167 | ||
|
|
749f00766f | ||
|
|
d2cc343649 | ||
|
|
f20c3e08d4 | ||
|
|
ae4c7b635d | ||
|
|
b9f1f9c41e | ||
|
|
b46d40aa07 | ||
|
|
11a6991b5c | ||
|
|
fcf0cb5d69 | ||
|
|
a271baa1ae | ||
|
|
0194c7fcbc | ||
|
|
5ee6cba557 | ||
|
|
475d18bd37 | ||
|
|
75ed4fe398 | ||
|
|
d29b0baa25 | ||
|
|
ffd57772e9 | ||
|
|
e648e4fb29 | ||
|
|
ecab62a56b | ||
|
|
e21ea739d9 | ||
|
|
659bb08389 | ||
|
|
f8da264e2b | ||
|
|
db84317be0 | ||
|
|
e93dc33ef8 | ||
|
|
cb1a4291d0 | ||
|
|
1a745b24d7 | ||
|
|
7b7ce30fe3 | ||
|
|
9bc525f579 | ||
|
|
3fcbc03759 | ||
|
|
1bd53de1f7 | ||
|
|
037739c634 | ||
|
|
3150d2b94b | ||
|
|
91ab10084a | ||
|
|
96d2a7f0bf | ||
|
|
053c9372cb | ||
|
|
0bb231ad00 | ||
|
|
15db5adc7e | ||
|
|
146478e450 | ||
|
|
97192a8055 | ||
|
|
772514940c | ||
|
|
38efde6c98 | ||
|
|
056b8ba1e8 | ||
|
|
ccd4d46391 | ||
|
|
e3bf8265c4 | ||
|
|
f4ee86282e | ||
|
|
7b66eb8b9c | ||
|
|
a7861c2ea5 | ||
|
|
163678dfe3 | ||
|
|
e32b1341f8 | ||
|
|
f579ad79a2 | ||
|
|
a6d1b8b975 | ||
|
|
890c5596a9 | ||
|
|
7797580c7f | ||
|
|
7f4abc5285 | ||
|
|
e559c467c6 | ||
|
|
916915d430 | ||
|
|
46d3889b63 | ||
|
|
e39dc25e2a | ||
|
|
1a022d8905 | ||
|
|
3dc8888c6a | ||
|
|
3ff3046b68 | ||
|
|
910a5dd96a | ||
|
|
6b521c4c43 | ||
|
|
fe909fbe92 | ||
|
|
98c9139f3a | ||
|
|
03d9ee31ec | ||
|
|
ef1cc56439 | ||
|
|
47fcdef88c | ||
|
|
a1c2260f9b | ||
|
|
a4b01b83e7 | ||
|
|
b9e8edb3ef | ||
|
|
b95861c378 | ||
|
|
758ae185ba | ||
|
|
f60d9a51d4 | ||
|
|
d81579730e | ||
|
|
b1c6c40fa7 | ||
|
|
d8bc3769a5 | ||
|
|
a73fbf7232 | ||
|
|
b6b2f453a0 | ||
|
|
aa14015919 | ||
|
|
7551c84c4f | ||
|
|
434e53e922 | ||
|
|
b5d238f7f4 | ||
|
|
e5c9fea52d | ||
|
|
cd15a0e966 | ||
|
|
895c723d4e | ||
|
|
c7176d6bc8 | ||
|
|
b2939d3df3 | ||
|
|
54a157a629 | ||
|
|
427adefb42 | ||
|
|
f0dede26a3 | ||
|
|
36f85a6a5a | ||
|
|
137567554d | ||
|
|
72941e51fc | ||
|
|
836c016f97 | ||
|
|
e8ea9b7127 | ||
|
|
f80c78536f | ||
|
|
7877187894 | ||
|
|
a384a57979 | ||
|
|
86e1476dee | ||
|
|
81d0ecd8f6 | ||
|
|
6f329c9e96 | ||
|
|
0e2445c7a0 | ||
|
|
7ed947f598 | ||
|
|
b85f6ea6c3 | ||
|
|
f85f088d65 | ||
|
|
045472deac | ||
|
|
71cad4df58 | ||
|
|
48786522c8 | ||
|
|
0827f1b2f6 | ||
|
|
5973bb8610 | ||
|
|
00ca5132b4 | ||
|
|
191d203b24 | ||
|
|
06f96011d8 | ||
|
|
f2e292c702 | ||
|
|
832deb8e97 | ||
|
|
74a3c57222 | ||
|
|
4a19092db0 | ||
|
|
5c652d913a | ||
|
|
053213f50e | ||
|
|
342e946f49 | ||
|
|
d15ab1355b | ||
|
|
e47f4b8f80 | ||
|
|
637a4eb351 | ||
|
|
a233aeec4a | ||
|
|
d24104154f | ||
|
|
7fa2706d0e | ||
|
|
2a5365d46d | ||
|
|
72cc853420 | ||
|
|
eea111df6f | ||
|
|
130bea9e25 | ||
|
|
754242439a | ||
|
|
3fab9e4cec | ||
|
|
8b6290120e | ||
|
|
564aef2a2a | ||
|
|
5958324550 | ||
|
|
683fc1f081 | ||
|
|
9277b46620 | ||
|
|
261926222b | ||
|
|
35cfefd934 | ||
|
|
aa13374523 | ||
|
|
fd1cd39c7c | ||
|
|
3e6d1d5789 | ||
|
|
185d0bf3a3 | ||
|
|
837f3ce04c | ||
|
|
c661619263 | ||
|
|
b2f7a7bb2e | ||
|
|
1965866813 | ||
|
|
110f56777d | ||
|
|
37c6001b6c | ||
|
|
9a9c91e591 | ||
|
|
17276179e7 | ||
|
|
335d780f3e | ||
|
|
450d113993 | ||
|
|
cd6d181bbc | ||
|
|
c92c6a24a0 | ||
|
|
ffe7216194 | ||
|
|
474eb7cbc8 | ||
|
|
252b528f40 | ||
|
|
a4357712bf | ||
|
|
62afd3d4c3 | ||
|
|
569628a202 | ||
|
|
c75bc66560 | ||
|
|
ad8d3e2444 | ||
|
|
56cd875fbd | ||
|
|
06873ed04f | ||
|
|
5b518e588f | ||
|
|
d3f2db2326 | ||
|
|
e619d9690d | ||
|
|
40dc182295 | ||
|
|
3cf1aad551 | ||
|
|
7cff681234 | ||
|
|
54b10106bd | ||
|
|
484aa54ed6 | ||
|
|
417bddfa16 | ||
|
|
1e4e799f2e | ||
|
|
33b18e3014 | ||
|
|
96ce8eb851 | ||
|
|
8de5e964e0 | ||
|
|
43150195d4 | ||
|
|
065c7af9a0 | ||
|
|
949370ad63 | ||
|
|
4b91a88bc9 | ||
|
|
d04a0c8f2f | ||
|
|
6f6618d46f | ||
|
|
90eb39439a | ||
|
|
9813265ef4 | ||
|
|
8acbf9babf | ||
|
|
97d7c20549 | ||
|
|
4eddd7f616 | ||
|
|
217217bd2b | ||
|
|
37eeb55a5b | ||
|
|
d32da8e4d5 | ||
|
|
da3052fdc2 | ||
|
|
97982ef93a | ||
|
|
25bff21edd | ||
|
|
b2b22c8b85 | ||
|
|
5663b1c539 | ||
|
|
c68a4543db | ||
|
|
88dc8a389a | ||
|
|
e7b6c689ce | ||
|
|
6ec9b0a0b0 | ||
|
|
682ec563c8 | ||
|
|
8eb4d9bf08 | ||
|
|
977a8cf33f | ||
|
|
db496b82fb | ||
|
|
9d578884f9 | ||
|
|
84ae27744a | ||
|
|
54df44d930 | ||
|
|
f79b16c244 | ||
|
|
e7957b1661 | ||
|
|
7d97ab2731 | ||
|
|
8be5ca6c30 | ||
|
|
fdf91b772e | ||
|
|
d297aa70bf | ||
|
|
4877f56752 | ||
|
|
839021f9eb | ||
|
|
8533057881 | ||
|
|
6aa5c60963 | ||
|
|
f548b248eb | ||
|
|
5e06568ac6 | ||
|
|
9707f13a42 | ||
|
|
82d36e604c | ||
|
|
c9fc353b60 | ||
|
|
8b2ece63ab | ||
|
|
14045a6162 | ||
|
|
c0d1c97490 | ||
|
|
50e53f2c82 | ||
|
|
95a9f1e2f8 | ||
|
|
355f18184c | ||
|
|
120a96cd8b | ||
|
|
64b534fc61 | ||
|
|
0887acf1bf | ||
|
|
142c02b425 | ||
|
|
3f6fbdbd21 | ||
|
|
25ea739e4d | ||
|
|
df1e95ef18 | ||
|
|
5f97cf9de1 | ||
|
|
cb003cb21d | ||
|
|
3ce636fa9a | ||
|
|
39b5c5b946 | ||
|
|
29a4dd20dd | ||
|
|
f38df31509 | ||
|
|
4fc9257f65 | ||
|
|
31a2f8c878 | ||
|
|
544df73b01 | ||
|
|
f49dce4f95 | ||
|
|
a54ef74c3f | ||
|
|
750dcb500e | ||
|
|
15e598b4a3 | ||
|
|
25d255f3a9 | ||
|
|
75960e8782 | ||
|
|
ab52f8c55d | ||
|
|
88be304b5b | ||
|
|
40fb02a00f | ||
|
|
5a4b12c914 | ||
|
|
bf5edfa3b3 | ||
|
|
64dd2f4af6 | ||
|
|
52736f2b36 | ||
|
|
64515786be | ||
|
|
52a8ec48b7 | ||
|
|
d72cf3fb43 | ||
|
|
5920c5c136 | ||
|
|
a60da6deac | ||
|
|
aef19cb0e0 | ||
|
|
cddd38cdff | ||
|
|
9d5bce9b7e | ||
|
|
9f2100deee | ||
|
|
5f779ca9b2 | ||
|
|
9926804f1b | ||
|
|
294d8862e4 | ||
|
|
d09be1f7e3 | ||
|
|
ed5fc820c2 | ||
|
|
248d9600c5 | ||
|
|
cfadf20d08 | ||
|
|
32eb016ee7 | ||
|
|
2e009d1327 | ||
|
|
797b9fb087 | ||
|
|
e5c255e011 | ||
|
|
39ae44dbf0 | ||
|
|
c9a1ebf257 | ||
|
|
f5c5429fe8 | ||
|
|
8c70393c90 | ||
|
|
37cb16b95c | ||
|
|
fe420ac559 | ||
|
|
50e1866572 | ||
|
|
88402288f9 | ||
|
|
68a15725d2 | ||
|
|
64b4d421c7 | ||
|
|
dde5223929 | ||
|
|
6ae278f735 | ||
|
|
0b2c3ee163 | ||
|
|
62a0ce29e9 | ||
|
|
91b345abfe | ||
|
|
2f3f5a34b4 | ||
|
|
d9003f344e | ||
|
|
6b9aac5234 | ||
|
|
02a3c5d308 | ||
|
|
50c398c2cc | ||
|
|
4a6a08578f | ||
|
|
4f2c68e5a4 | ||
|
|
a797961a25 | ||
|
|
e149cd7afe | ||
|
|
874d103a8d | ||
|
|
b85a369341 | ||
|
|
522040810d | ||
|
|
e60164b5f3 | ||
|
|
9f4646e8bd | ||
|
|
5a9e18ed72 | ||
|
|
d40960bcfd | ||
|
|
ae8e81ceb2 | ||
|
|
a74c850031 | ||
|
|
ece5eb065a | ||
|
|
7598c50dba | ||
|
|
5078ca6d8e | ||
|
|
ddf9f0cd93 | ||
|
|
75f0537181 | ||
|
|
c6a47e359f | ||
|
|
51aead6b58 | ||
|
|
d738371848 | ||
|
|
6cabb32aa5 | ||
|
|
3e2af8537c | ||
|
|
26e802cf0f | ||
|
|
a467ca22fb | ||
|
|
b376790b78 | ||
|
|
6d4fecb274 | ||
|
|
14421c6e00 | ||
|
|
290ee20e63 | ||
|
|
997fb4061a | ||
|
|
92b38cebe4 | ||
|
|
8ebe86d9e9 | ||
|
|
84cabbcb7e | ||
|
|
f23fa1c9d3 | ||
|
|
5053a22f96 | ||
|
|
0291094c62 | ||
|
|
6220724bf4 | ||
|
|
102f6d9719 | ||
|
|
aad94f12c1 | ||
|
|
065ef7ab38 | ||
|
|
008e78a022 | ||
|
|
548335082b | ||
|
|
a36885e886 | ||
|
|
0d1afe0938 | ||
|
|
7969249d89 | ||
|
|
52fc973ead | ||
|
|
96f53f5cd2 | ||
|
|
f234bc19a1 | ||
|
|
fcdf766769 | ||
|
|
525b04e69e | ||
|
|
377fa01e98 | ||
|
|
7ce291fac5 | ||
|
|
b88042a902 | ||
|
|
dc29ede6e3 | ||
|
|
becbb03d80 | ||
|
|
bc604d4c24 | ||
|
|
10f3ad6122 | ||
|
|
0ed3480258 | ||
|
|
8033966a70 | ||
|
|
2361042ede | ||
|
|
f1ded231ed | ||
|
|
fa54e8a8ac | ||
|
|
252ef4dbfb | ||
|
|
d2d788662a | ||
|
|
82454243fa | ||
|
|
d947479a60 | ||
|
|
8a4e07c83e | ||
|
|
776a3ecd1f | ||
|
|
1ae67c4549 | ||
|
|
7b369c9107 | ||
|
|
1823ee04ee | ||
|
|
5c0447ee29 | ||
|
|
29eb2cc68a | ||
|
|
1a171ad494 | ||
|
|
c0a17df344 | ||
|
|
e993b37f1e | ||
|
|
e280ca9db8 | ||
|
|
78f9383332 | ||
|
|
a96a4362cd | ||
|
|
1fb6d1b59d | ||
|
|
f94d7d9ea5 | ||
|
|
8eb04de748 | ||
|
|
521d14a6e0 | ||
|
|
e334b781fb | ||
|
|
d286872782 | ||
|
|
db28349703 | ||
|
|
e8ff020543 | ||
|
|
3a971315dc | ||
|
|
a37a38c79a | ||
|
|
b8cadaf8e6 | ||
|
|
05abaa8461 | ||
|
|
13e724c8ea | ||
|
|
0a51db3005 | ||
|
|
4f02c811a3 | ||
|
|
0d1d1a25da | ||
|
|
cd27c143c3 | ||
|
|
fcded63653 | ||
|
|
303c4f1f6d | ||
|
|
925b3e254c | ||
|
|
558850e68f | ||
|
|
ce47942ba3 | ||
|
|
aaf3a17ada | ||
|
|
1d568076df | ||
|
|
e2b3339475 | ||
|
|
80efaa0dfa | ||
|
|
24d967d6f4 | ||
|
|
ed5bbf6882 | ||
|
|
aa3974abaf | ||
|
|
8f8c375758 | ||
|
|
12dd092133 | ||
|
|
93b0a3c854 | ||
|
|
c90a358674 | ||
|
|
b6cd49c825 | ||
|
|
a89b405e16 | ||
|
|
e1e5803067 | ||
|
|
9e0decb6cb | ||
|
|
8ae3449a43 | ||
|
|
dac5460da0 | ||
|
|
df0513f4f4 | ||
|
|
d9535213dc | ||
|
|
a320817ee5 | ||
|
|
17aab01eaa | ||
|
|
d3e6cc5acb | ||
|
|
723d1828ec | ||
|
|
ef85b4c919 | ||
|
|
3bb12e4c3c | ||
|
|
eb29fdce63 | ||
|
|
3246dedbd8 | ||
|
|
1d22ca7d4d | ||
|
|
cd23abf19b | ||
|
|
3b420c7b43 | ||
|
|
16e0f0e986 | ||
|
|
d5c488cc7e | ||
|
|
62b50c87d4 | ||
|
|
3b63d40352 | ||
|
|
25fd404273 | ||
|
|
8cee14fa3a | ||
|
|
0f34ca8962 | ||
|
|
7def6e70ba | ||
|
|
82c190a0c5 | ||
|
|
7e5c22b6c7 | ||
|
|
c20c3db0ef | ||
|
|
64abe54b15 | ||
|
|
ba74a40b6d | ||
|
|
ba0f5ee81d | ||
|
|
eebd219414 | ||
|
|
27fd0dbaec | ||
|
|
b7af53c7d9 | ||
|
|
c762834e05 | ||
|
|
0725fe38f8 | ||
|
|
73341394ee | ||
|
|
9549aca48b | ||
|
|
9c41f0fdb9 | ||
|
|
1d522edb01 | ||
|
|
b7d2828f60 | ||
|
|
deece15648 | ||
|
|
d06683489f | ||
|
|
307063ade0 | ||
|
|
ed00adbecc | ||
|
|
2aaa850e25 | ||
|
|
8859da42c6 | ||
|
|
2968c2919c | ||
|
|
33b10fa719 | ||
|
|
6d189dab98 | ||
|
|
380d7e66b5 | ||
|
|
eb80fa4eb0 | ||
|
|
88b470e8e5 | ||
|
|
99f8785475 | ||
|
|
47db1349a9 | ||
|
|
1f8b04bd42 | ||
|
|
d790a5fba9 | ||
|
|
2fc0a0964b | ||
|
|
715664273b | ||
|
|
a9f707c398 | ||
|
|
8168bcece5 | ||
|
|
c9beaa2aa1 | ||
|
|
b238c7e743 | ||
|
|
e9511ebfc3 | ||
|
|
a786a1427d | ||
|
|
961612370d | ||
|
|
bd5b9573f6 | ||
|
|
e603a10ab4 | ||
|
|
984346d682 | ||
|
|
f4cecb61bc | ||
|
|
cffa101419 | ||
|
|
1d7d201b5b | ||
|
|
c0e4e12138 | ||
|
|
5a85255be9 | ||
|
|
60d3960f3a | ||
|
|
7bcb03f1ec | ||
|
|
01ef053a11 | ||
|
|
8988c775fe | ||
|
|
c8bfa98b6b | ||
|
|
34053c7608 | ||
|
|
785667ec07 | ||
|
|
9a0a3c4b00 | ||
|
|
e56c261c73 | ||
|
|
32a9db6922 | ||
|
|
77498c17a5 | ||
|
|
54c07f89f2 | ||
|
|
cd4d265055 | ||
|
|
81ee69010d | ||
|
|
138d5b7a02 | ||
|
|
95a54a43ff | ||
|
|
ca59cbc898 | ||
|
|
21f72ad845 | ||
|
|
40fd98d580 | ||
|
|
0422d751d8 | ||
|
|
e44f68db83 | ||
|
|
88cbf42c39 | ||
|
|
45157f79a3 | ||
|
|
f5157392b6 | ||
|
|
f631ec3a7c | ||
|
|
a9b2750ec9 | ||
|
|
7fbdb42695 | ||
|
|
026dd4480e | ||
|
|
b373e7acba | ||
|
|
d2ca54c167 | ||
|
|
e070284a09 | ||
|
|
e4e02ea3c0 | ||
|
|
a947980eb6 | ||
|
|
f11c3dd3e3 | ||
|
|
394067be63 | ||
|
|
2494613583 | ||
|
|
77c60e7450 | ||
|
|
04dd2d93d0 | ||
|
|
5e5710ecce | ||
|
|
c1b33a66c4 | ||
|
|
0e3165d724 | ||
|
|
3e12eab0b5 | ||
|
|
87365e4a43 | ||
|
|
3e16a47ff2 | ||
|
|
874054148e | ||
|
|
5e39a13bf6 | ||
|
|
720c1b5eca | ||
|
|
c7c1a04c6a | ||
|
|
fd5b224ba0 | ||
|
|
0b80ade3ae | ||
|
|
e1c3e95307 | ||
|
|
904e8966c0 | ||
|
|
3a10f0155f | ||
|
|
4c9cc4f3d4 | ||
|
|
48f2c4e14b | ||
|
|
f41df327a9 | ||
|
|
3f9e3038b7 | ||
|
|
c75c95afa9 | ||
|
|
d4e0009b89 | ||
|
|
b97b374487 | ||
|
|
e27345e489 | ||
|
|
032e644b2b | ||
|
|
b7ac81701a | ||
|
|
d59aa35b2f | ||
|
|
4c7c4e2a81 | ||
|
|
521fa58b75 | ||
|
|
a2e5c60683 | ||
|
|
5ef152fd84 | ||
|
|
e2ba338923 | ||
|
|
aae4f0bb7b | ||
|
|
43e3f8f08b | ||
|
|
9cc2fd555f | ||
|
|
c10dc7b25b | ||
|
|
9e1770316a | ||
|
|
0e595c9801 | ||
|
|
6ae9e43183 | ||
|
|
18126b42cb | ||
|
|
2b233fd810 | ||
|
|
a4f5d2b9b2 | ||
|
|
d29c09caf3 | ||
|
|
bc809986e7 | ||
|
|
5ee2f3696d | ||
|
|
df5eb546e7 | ||
|
|
684351c753 | ||
|
|
a8342e37b9 | ||
|
|
3b6fc9959f | ||
|
|
3ffc985968 | ||
|
|
369609b26c | ||
|
|
d033dcf395 | ||
|
|
5ef2a85c10 | ||
|
|
8c0bc9080c | ||
|
|
6bf2c5415f | ||
|
|
2f31033a88 | ||
|
|
ceaed0f552 | ||
|
|
e9963ecc0d | ||
|
|
9ef0b43c36 | ||
|
|
801d636eb5 | ||
|
|
dfbfd4fe74 | ||
|
|
7455989729 | ||
|
|
8b2b9e1093 | ||
|
|
a8cf05ea5d | ||
|
|
969508ae36 | ||
|
|
f581ecc805 | ||
|
|
a63464765c | ||
|
|
e9fe8ce118 | ||
|
|
1fa892c239 | ||
|
|
1afbbbc737 | ||
|
|
66c4de2607 | ||
|
|
213e67dea2 | ||
|
|
86c884fe1e | ||
|
|
a4d5c8cd2f | ||
|
|
330665afbe | ||
|
|
be1d87c3c3 | ||
|
|
14ab3c8651 | ||
|
|
9c04ed483e | ||
|
|
a8a5e184ab | ||
|
|
c571595980 | ||
|
|
80f2ccc9ed | ||
|
|
b2e9e57859 | ||
|
|
3c75b36148 | ||
|
|
bcd8e330cb | ||
|
|
f75f8ad76d | ||
|
|
574b78cf31 | ||
|
|
92f0e8472b | ||
|
|
69bd5d2ab0 | ||
|
|
6eaf04107d | ||
|
|
d80fdb20c7 | ||
|
|
f618c87ee5 | ||
|
|
0721c22073 | ||
|
|
aba066b4d1 | ||
|
|
2562c726e6 | ||
|
|
6e3ec71c10 | ||
|
|
2932c1ed35 | ||
|
|
149b31a960 | ||
|
|
f1d09e4127 | ||
|
|
10bdbc95cd | ||
|
|
26c38070ec | ||
|
|
494a7f1db9 | ||
|
|
bba721654b | ||
|
|
36a17b0592 | ||
|
|
b7294d46cf | ||
|
|
d8977b5046 | ||
|
|
963bb7f7cf | ||
|
|
7d3a08599e | ||
|
|
345a4bc504 | ||
|
|
7bfdf2e2f5 | ||
|
|
d9ac5d88e9 | ||
|
|
2cf11bb2ea | ||
|
|
e29d008914 | ||
|
|
de91063fbe | ||
|
|
3d95272707 | ||
|
|
3c20d0902e | ||
|
|
f2c1e5c6e5 | ||
|
|
feb354725a | ||
|
|
35c0434dc7 | ||
|
|
918ee47c79 | ||
|
|
a8ab7c9c04 | ||
|
|
332892b468 | ||
|
|
6392300311 | ||
|
|
16d201faca | ||
|
|
ea0cf67f98 | ||
|
|
612132b7c8 | ||
|
|
26470c6047 | ||
|
|
93d3522f67 | ||
|
|
c6d901d799 | ||
|
|
4880f9ff32 | ||
|
|
aaa42a3412 | ||
|
|
3e5e852e20 | ||
|
|
0a3f44bd73 | ||
|
|
d4fed5f5f7 | ||
|
|
dce7b90fc2 | ||
|
|
4f94bdff3f | ||
|
|
ce1f2a6fd4 | ||
|
|
da292bb9b2 | ||
|
|
326a75d0e8 | ||
|
|
e47860bc2e | ||
|
|
6212151562 | ||
|
|
8c2b9f9901 | ||
|
|
e9a733a789 | ||
|
|
b2fe723570 | ||
|
|
33ba8dabe0 | ||
|
|
0842e54f52 | ||
|
|
08d34e41c6 | ||
|
|
e93c9f74c9 | ||
|
|
1ab81256e9 | ||
|
|
cb19de57bb | ||
|
|
e678e7df8f | ||
|
|
8487eefe46 | ||
|
|
86da1aa429 | ||
|
|
48b580b59e | ||
|
|
8b568d796e | ||
|
|
4b5af85094 | ||
|
|
8d0be06f45 | ||
|
|
26ae8accd4 | ||
|
|
321e3e27de | ||
|
|
7d26968bb3 | ||
|
|
83464a882e | ||
|
|
1e94ad25e1 | ||
|
|
a3ba19db96 | ||
|
|
d9e9c849e1 | ||
|
|
c162c23d9e | ||
|
|
90fd1c300f | ||
|
|
902a9cc812 | ||
|
|
c51e1805fa | ||
|
|
7a2b9e85e7 | ||
|
|
547c40cd52 | ||
|
|
e2d631097d | ||
|
|
cc55be0b0a | ||
|
|
64927190bd | ||
|
|
24515126fe | ||
|
|
7a56a93028 | ||
|
|
ea7fc3a171 | ||
|
|
ae36a26045 | ||
|
|
d6c9f5c64b | ||
|
|
c4f4f4295b | ||
|
|
a997322efb | ||
|
|
799688af76 | ||
|
|
260e95d027 | ||
|
|
f9ee70aa2e | ||
|
|
50f13cb84b | ||
|
|
fc7e08bb49 | ||
|
|
06ed3e5dfd | ||
|
|
4d792ad57b | ||
|
|
4fa78bfca0 | ||
|
|
2012833cb3 | ||
|
|
e48eef7e32 | ||
|
|
74ac9c3a92 | ||
|
|
a907d789d6 | ||
|
|
fc46c0b49c | ||
|
|
fef7862045 | ||
|
|
d9441a6bdd | ||
|
|
332cb0896b | ||
|
|
d1b0c28924 | ||
|
|
dce958aac4 | ||
|
|
438940219e | ||
|
|
f28fcec81d | ||
|
|
586d027f86 | ||
|
|
bd4fb7486d | ||
|
|
f9cd2b8f36 | ||
|
|
62e22236b7 | ||
|
|
8b157f427a | ||
|
|
f165c1d9b0 | ||
|
|
500e2d62a0 | ||
|
|
a06e8677ac | ||
|
|
b4d5783928 | ||
|
|
3ce7f45503 | ||
|
|
b436c2761a | ||
|
|
b586d3bb0e | ||
|
|
63688a2f95 | ||
|
|
379cb1b2e0 | ||
|
|
78429492f1 | ||
|
|
9875047674 | ||
|
|
5014b0a9cb | ||
|
|
ef841b1aa3 | ||
|
|
368f27ffbc | ||
|
|
0e50bc1443 | ||
|
|
7c4a6ddcdf | ||
|
|
7ab6d95b6c | ||
|
|
6c32b89906 | ||
|
|
056e659a20 | ||
|
|
62baff665c | ||
|
|
7c5eb0ae37 | ||
|
|
36bce6c468 | ||
|
|
65df02163d | ||
|
|
19a32cdfd3 | ||
|
|
d708f386a1 | ||
|
|
0f837f4bed | ||
|
|
242e8e2bb3 | ||
|
|
1d56b24b67 | ||
|
|
bb9138708a | ||
|
|
34f5510f1f | ||
|
|
6c6d47c89c | ||
|
|
196075c031 | ||
|
|
2e5e8f73c6 | ||
|
|
ada5d38272 | ||
|
|
c4b0f773db | ||
|
|
276daf631e | ||
|
|
fb19b58147 | ||
|
|
13a5e3cf6f | ||
|
|
1caf3caf1b | ||
|
|
564370f79a | ||
|
|
24e749a2c9 | ||
|
|
cccdc51ad4 | ||
|
|
99ddce6c3e | ||
|
|
f68088cfb5 | ||
|
|
c8f56d748a | ||
|
|
a43fc47bb6 | ||
|
|
8c1bfac53b | ||
|
|
97853c3660 | ||
|
|
f304a30193 | ||
|
|
7eadca3959 | ||
|
|
8aa6decbf9 | ||
|
|
7cf4bcaca2 | ||
|
|
9ccd9c3e0e | ||
|
|
c6773a6303 | ||
|
|
e858a32aa1 | ||
|
|
99f2680e2c | ||
|
|
7a9a323bac | ||
|
|
62aa234352 | ||
|
|
0cb9e7922a | ||
|
|
e73107006e | ||
|
|
ca389cc6fc | ||
|
|
60ec7f0cbf | ||
|
|
d342d59e65 | ||
|
|
2690fa2da5 | ||
|
|
e411c394ca | ||
|
|
d69f3ba225 | ||
|
|
739807b1a9 | ||
|
|
d029ea7f3f | ||
|
|
11098cb869 | ||
|
|
f6807d6b22 | ||
|
|
7fc9bacf54 | ||
|
|
57ea4c1d92 | ||
|
|
bcdd15ef3a | ||
|
|
5f939c3123 | ||
|
|
2446fc44ad | ||
|
|
9ba8dd91df | ||
|
|
10e1cdbc52 | ||
|
|
46eceb38d5 | ||
|
|
81de882e2f | ||
|
|
593e07cdff | ||
|
|
8ca54f616e | ||
|
|
f7f899f0a4 | ||
|
|
05a3c0c89b | ||
|
|
f21691c122 | ||
|
|
836e26d8d0 | ||
|
|
8a7c1fe4cb | ||
|
|
7f43d3bb37 | ||
|
|
11b975ab19 | ||
|
|
315e4215d9 | ||
|
|
e35e6c44cf | ||
|
|
260cb78e3a | ||
|
|
a1f04d2129 | ||
|
|
9b562eebcd | ||
|
|
1d175c4557 | ||
|
|
f755070080 | ||
|
|
1c6c72a0fe | ||
|
|
1755f2ea3d | ||
|
|
498cc6c80b | ||
|
|
8d3227a92b | ||
|
|
c6d855084e | ||
|
|
827b3f8aeb | ||
|
|
fb95573000 | ||
|
|
f026bd455f | ||
|
|
f0b92a5757 | ||
|
|
d7c6f1e63b | ||
|
|
1e9e308df3 | ||
|
|
c9a70f149d | ||
|
|
d4ff47b6ac | ||
|
|
8b4b241403 | ||
|
|
6dcd6947d7 | ||
|
|
327328412a | ||
|
|
42f9ef00b9 | ||
|
|
8c2ea0fa26 | ||
|
|
14e9afaf42 | ||
|
|
a3a101641a | ||
|
|
8bd93fe495 | ||
|
|
56df22bca7 | ||
|
|
5f32a6738a | ||
|
|
1c081935fb | ||
|
|
8fd4d00776 | ||
|
|
7d04ea58c3 | ||
|
|
2cc84a0f0d | ||
|
|
8f715532cb | ||
|
|
5a77df7cc5 | ||
|
|
59658f2b0b | ||
|
|
0b983906da | ||
|
|
cd1f164d18 | ||
|
|
e2a6ac6625 | ||
|
|
8e8c10c438 | ||
|
|
7ff25f282e | ||
|
|
b8dc608032 | ||
|
|
d7e699320b | ||
|
|
ef333da770 | ||
|
|
575a389b08 | ||
|
|
9bc0824be6 | ||
|
|
b656a60234 | ||
|
|
bd988d805c | ||
|
|
7ad7ccb8fe | ||
|
|
e30c535f18 | ||
|
|
de7706f622 | ||
|
|
de20e4c9dd | ||
|
|
41f9314e2a | ||
|
|
2280ce349a | ||
|
|
7aa05e1c9f | ||
|
|
69d174c9e8 | ||
|
|
3c38fa6b70 | ||
|
|
728c8b4663 | ||
|
|
fab9563cd5 | ||
|
|
20fe2473e1 | ||
|
|
3815062c11 | ||
|
|
581ea9fda0 | ||
|
|
bfa641cea8 | ||
|
|
29c58efeb3 | ||
|
|
27eb82c556 | ||
|
|
652d67a20f | ||
|
|
ce0984f02f | ||
|
|
b3e3b1e245 | ||
|
|
c69ee180af | ||
|
|
bba3a25371 | ||
|
|
095b358aca | ||
|
|
833e5f46cc | ||
|
|
3e0ce0e07a | ||
|
|
1f31dd12fc | ||
|
|
f63efc29bf | ||
|
|
3e394f14e8 | ||
|
|
ff6ffa1656 | ||
|
|
304c259a57 | ||
|
|
630754b52e | ||
|
|
afd8c0d879 | ||
|
|
6316ee7c9b | ||
|
|
b6b8d11881 | ||
|
|
8753fd5887 | ||
|
|
5aaafb5ac1 | ||
|
|
a043557c44 | ||
|
|
4af4914e32 | ||
|
|
e35b3f1e80 | ||
|
|
ff8859b9db | ||
|
|
937ff5a378 | ||
|
|
f3a716fac6 | ||
|
|
72659580de | ||
|
|
30cb0cbcfd | ||
|
|
4136217249 | ||
|
|
246cae5d9e | ||
|
|
12313543ca | ||
|
|
87e3dead14 | ||
|
|
9af36460c2 | ||
|
|
2e2d881e01 | ||
|
|
db58946312 | ||
|
|
9a02a58273 | ||
|
|
147f5c1e0d | ||
|
|
f0ca50ba27 | ||
|
|
83137b5968 | ||
|
|
12823c2213 | ||
|
|
0b810d7d65 | ||
|
|
7aebdc9b7b | ||
|
|
6859b651a8 | ||
|
|
d47680733b | ||
|
|
273a38d781 | ||
|
|
93d1162caf | ||
|
|
8d550a66a3 | ||
|
|
4c58e05be3 | ||
|
|
500563054e | ||
|
|
2a11f8f59d | ||
|
|
9e7bdc579e | ||
|
|
61af0c9ac4 | ||
|
|
91f02ad553 | ||
|
|
b20d3dfc10 | ||
|
|
3e7666021c | ||
|
|
ae58bceeb9 | ||
|
|
6665b9e13c | ||
|
|
367a9705e9 | ||
|
|
8d3a1e84c3 | ||
|
|
d009835210 | ||
|
|
83a664ca68 | ||
|
|
d98d1857a4 | ||
|
|
33a514aa54 | ||
|
|
0aefdc85e6 | ||
|
|
3ff0964f02 | ||
|
|
cf33db3dcb | ||
|
|
dae80cbe35 | ||
|
|
f8b4ef26b3 | ||
|
|
db991453b0 | ||
|
|
c11ce4c8d4 | ||
|
|
92e300cb9f | ||
|
|
d210e0bffe | ||
|
|
4d4968f358 | ||
|
|
8dfede148a | ||
|
|
6d125028f5 | ||
|
|
7ff3cf4af0 | ||
|
|
bb3353397d | ||
|
|
572260ec29 | ||
|
|
0b1faa0523 | ||
|
|
3b6c3e10d7 | ||
|
|
d3909a5483 | ||
|
|
645fd10446 | ||
|
|
ee7e29fb3a | ||
|
|
bdc3a4d24c | ||
|
|
88ccda139e | ||
|
|
63e78bae37 | ||
|
|
b50f211c28 | ||
|
|
5efbd9c7f5 | ||
|
|
7f1d2ea11f | ||
|
|
46eb391a1b | ||
|
|
b166cc5bf4 | ||
|
|
1d0f6aad95 | ||
|
|
764aa71770 | ||
|
|
21e9206a77 | ||
|
|
5f29977c50 | ||
|
|
ee4c9bc01e | ||
|
|
4a8259cb12 | ||
|
|
a6441d70f0 | ||
|
|
01db8d0130 | ||
|
|
8ad9db5572 | ||
|
|
607cd23014 | ||
|
|
220758d244 | ||
|
|
7ab71bb468 | ||
|
|
a74377b620 | ||
|
|
c9effa3c06 | ||
|
|
8b8102334b | ||
|
|
5dedb86472 | ||
|
|
d93f77f991 | ||
|
|
5409bc575e | ||
|
|
576d31206d | ||
|
|
86e3297414 | ||
|
|
ab4a947456 | ||
|
|
5ce2581b4c | ||
|
|
c0d6c6b882 | ||
|
|
2bc8c967b1 | ||
|
|
fee08f0eeb | ||
|
|
078c3d05d7 | ||
|
|
7f97768c56 | ||
|
|
ecd548a7aa | ||
|
|
62efb0795b | ||
|
|
53f042ee08 | ||
|
|
6ce97bd0cd | ||
|
|
c29149e74c | ||
|
|
487f7593ce | ||
|
|
6b3b33d2a0 | ||
|
|
2d70ccc2bf | ||
|
|
e90fc9504a | ||
|
|
5108314c03 | ||
|
|
c1e144d4db | ||
|
|
f2b86a1c0f | ||
|
|
9583d41446 | ||
|
|
e594064f93 | ||
|
|
7c52fd95ec | ||
|
|
d0d5ac6e87 | ||
|
|
416bf3a829 | ||
|
|
cc78347293 | ||
|
|
27e4ae992a | ||
|
|
a1767dc153 | ||
|
|
eb610c27bf | ||
|
|
016fb2ceb2 | ||
|
|
5c571520a0 | ||
|
|
ddefd2cf09 | ||
|
|
30a3eeece8 | ||
|
|
5919388588 | ||
|
|
2cc738f481 | ||
|
|
babd405928 | ||
|
|
e885857875 | ||
|
|
a1d57a2645 | ||
|
|
a28aecd4d1 | ||
|
|
a3f1ff2827 | ||
|
|
c4d1a639b0 | ||
|
|
8732b7a55c | ||
|
|
1b9148f28e | ||
|
|
e0129c3b43 | ||
|
|
60d41022ea | ||
|
|
dd4f2ac671 | ||
|
|
eebb2a3b68 | ||
|
|
0d62069b67 | ||
|
|
56cf2e6596 | ||
|
|
59bd5481b9 | ||
|
|
6c8da526a0 | ||
|
|
13bc8b78d7 | ||
|
|
c7c68094d9 | ||
|
|
84f54b10dc | ||
|
|
cebc9e3e91 | ||
|
|
5b131cf77c | ||
|
|
ce6ec64069 | ||
|
|
1379f8a055 | ||
|
|
53d049e5f5 | ||
|
|
4968f72dfb | ||
|
|
b24a0ed8fd | ||
|
|
c810347c7c | ||
|
|
cf8c4142c7 | ||
|
|
f71901b5f9 | ||
|
|
4419f9c4e7 | ||
|
|
4e5982b682 | ||
|
|
39e1510e64 | ||
|
|
64206160cc | ||
|
|
6376659348 | ||
|
|
43b2a4ad27 | ||
|
|
eaf06bb239 | ||
|
|
5e26b5bfdc | ||
|
|
60fbb6df5a | ||
|
|
3e60ee9d3e | ||
|
|
54e79409e6 | ||
|
|
31d113207b | ||
|
|
3a014477e7 | ||
|
|
90d8c8baf5 | ||
|
|
1dee17f980 | ||
|
|
6aeb21d3af | ||
|
|
4747ae2f1c | ||
|
|
7968f55191 | ||
|
|
33aa3556d2 | ||
|
|
b8b7563fca | ||
|
|
0d3f90770e | ||
|
|
51a4f0aa76 | ||
|
|
ebb89e20b4 | ||
|
|
8fc60e321b | ||
|
|
8ef6b6089f | ||
|
|
5b5b26122e | ||
|
|
300f5be4f3 | ||
|
|
c5d47ffcb0 | ||
|
|
3b7b8ea0f1 | ||
|
|
59739ee5c9 | ||
|
|
63207eb681 | ||
|
|
65f09c238b | ||
|
|
bb97d842df | ||
|
|
4dba5ab5f9 | ||
|
|
4c0e46fd44 | ||
|
|
ee3b40a59a | ||
|
|
e511b87955 | ||
|
|
53f51ad312 | ||
|
|
3878c4f041 | ||
|
|
499e4d3242 | ||
|
|
b5d0907090 | ||
|
|
6613fa67ee | ||
|
|
41ec380b55 | ||
|
|
7fb305e898 | ||
|
|
d4255a4979 | ||
|
|
b21dcd17b7 | ||
|
|
0caea85d16 | ||
|
|
42e0fb5eb9 | ||
|
|
6061d71492 | ||
|
|
dbd8814d2c | ||
|
|
a3562c5940 | ||
|
|
49b07c1c6a | ||
|
|
51d220f1e0 | ||
|
|
9f81a94d86 | ||
|
|
f6098fc931 | ||
|
|
6e3c2fc839 | ||
|
|
30e616f74f | ||
|
|
5e29cae81a |
11
.cargo/config.toml
Normal file
11
.cargo/config.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[env]
|
||||
# In unoptimised builds tokio tends to use a lot of stack space when
|
||||
# creating some complicated futures, tokio has an open issue for this:
|
||||
# https://github.com/tokio-rs/tokio/issues/2055. Some of our tests
|
||||
# manage to not fit in the default 2MiB stack anymore due to this, so
|
||||
# while the issue is not resolved we want to work around this.
|
||||
# Because compiling optimised builds takes a very long time we prefer
|
||||
# to avoid that. Setting this environment variable ensures that when
|
||||
# invoking `cargo test` threads are allowed to have a large enough
|
||||
# stack size without needing to use an optimised build.
|
||||
RUST_MIN_STACK = "8388608"
|
||||
10
.gitattributes
vendored
10
.gitattributes
vendored
@@ -2,9 +2,17 @@
|
||||
# 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
|
||||
test-data/** text=false
|
||||
|
||||
# binary files should be detected by git, however, to be sure, you can add them here explicitly
|
||||
*.png binary
|
||||
|
||||
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -3,7 +3,7 @@ updates:
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: "monthly"
|
||||
commit-message:
|
||||
prefix: "cargo"
|
||||
open-pull-requests-limit: 10
|
||||
prefix: "chore(cargo)"
|
||||
open-pull-requests-limit: 50
|
||||
|
||||
357
.github/workflows/ci.yml
vendored
357
.github/workflows/ci.yml
vendored
@@ -1,44 +1,67 @@
|
||||
# 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
|
||||
- staging
|
||||
- trying
|
||||
- main
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
|
||||
jobs:
|
||||
lint_rust:
|
||||
name: Lint Rust
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTUP_TOOLCHAIN: 1.77.1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
- name: Install rustfmt and clippy
|
||||
run: rustup toolchain install $RUSTUP_TOOLCHAIN --profile minimal --component rustfmt --component clippy
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@v2
|
||||
- name: Run rustfmt
|
||||
run: cargo fmt --all -- --check
|
||||
- name: Run clippy
|
||||
run: scripts/clippy.sh
|
||||
- name: Check
|
||||
run: cargo check --workspace --all-targets --all-features
|
||||
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
cargo_deny:
|
||||
name: cargo deny
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- run: rustup component add rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
show-progress: false
|
||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
arguments: --all-features --workspace
|
||||
command: check
|
||||
command-arguments: "-Dwarnings"
|
||||
|
||||
run_clippy:
|
||||
provider_database:
|
||||
name: Check provider database
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
toolchain: stable
|
||||
components: clippy
|
||||
override: true
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --workspace --tests --examples
|
||||
show-progress: false
|
||||
- name: Check provider database
|
||||
run: scripts/update-provider-database.sh
|
||||
|
||||
docs:
|
||||
name: Rust doc comments
|
||||
@@ -46,109 +69,229 @@ jobs:
|
||||
env:
|
||||
RUSTDOCFLAGS: -Dwarnings
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
- name: Install rust stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
components: rust-docs
|
||||
override: true
|
||||
show-progress: false
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@v1
|
||||
uses: swatinem/rust-cache@v2
|
||||
- name: Rustdoc
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: doc
|
||||
args: --document-private-items --no-deps
|
||||
run: cargo doc --document-private-items --no-deps
|
||||
|
||||
build_and_test:
|
||||
name: Build and test
|
||||
rust_tests:
|
||||
name: Rust tests
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# Currently used Rust version, same as in `rust-toolchain` file.
|
||||
- os: ubuntu-latest
|
||||
rust: 1.54.0
|
||||
python: 3.9
|
||||
rust: 1.77.1
|
||||
- os: windows-latest
|
||||
rust: 1.54.0
|
||||
python: false # Python bindings compilation on Windows is not supported.
|
||||
rust: 1.77.1
|
||||
- os: macos-latest
|
||||
rust: 1.77.1
|
||||
|
||||
# Minimum Supported Rust Version = 1.48.0
|
||||
# This is the Debian "bullseye" release version of Rust.
|
||||
#
|
||||
# Minimum Supported Python Version = 3.7
|
||||
# This is the minimum version for which manylinux Python wheels are
|
||||
# built.
|
||||
# Minimum Supported Rust Version = 1.77.0
|
||||
- os: ubuntu-latest
|
||||
rust: 1.48.0
|
||||
python: 3.7
|
||||
rust: 1.77.0
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- name: Install ${{ matrix.rust }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
- name: Install Rust ${{ matrix.rust }}
|
||||
run: rustup toolchain install --profile minimal ${{ matrix.rust }}
|
||||
- run: rustup override set ${{ matrix.rust }}
|
||||
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cargo/registry
|
||||
key: ${{ matrix.os }}-${{ matrix.rust }}-cargo-registry-${{ hashFiles('**/Cargo.toml') }}
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@v2
|
||||
|
||||
- name: Cache cargo index
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cargo/git
|
||||
key: ${{ matrix.os }}-${{ matrix.rust }}-cargo-index-${{ hashFiles('**/Cargo.toml') }}
|
||||
- name: Tests
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
run: cargo test --workspace
|
||||
|
||||
- name: Cache cargo build
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: target
|
||||
key: ${{ matrix.os }}-${{ matrix.rust }}-cargo-build-target-${{ hashFiles('**/Cargo.toml') }}
|
||||
- name: Test cargo vendor
|
||||
run: cargo vendor
|
||||
|
||||
- name: check
|
||||
uses: actions-rs/cargo@v1
|
||||
env:
|
||||
RUSTFLAGS: -D warnings
|
||||
with:
|
||||
command: check
|
||||
args: --all --bins --examples --tests --features repl
|
||||
c_library:
|
||||
name: Build C library
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- name: tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@v2
|
||||
|
||||
- name: install python
|
||||
if: ${{ matrix.python }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
- name: Build C library
|
||||
run: cargo build -p deltachat_ffi --features jsonrpc
|
||||
|
||||
- name: install tox
|
||||
if: ${{ matrix.python }}
|
||||
run: pip install tox
|
||||
- name: Upload C library
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.os }}-libdeltachat.a
|
||||
path: target/debug/libdeltachat.a
|
||||
retention-days: 1
|
||||
|
||||
- name: build C library
|
||||
if: ${{ matrix.python }}
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: -p deltachat_ffi
|
||||
rpc_server:
|
||||
name: Build deltachat-rpc-server
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- 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,doc,py3
|
||||
- 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@v4
|
||||
with:
|
||||
name: ${{ matrix.os }}-deltachat-rpc-server
|
||||
path: ${{ matrix.os == 'windows-latest' && 'target/debug/deltachat-rpc-server.exe' || 'target/debug/deltachat-rpc-server' }}
|
||||
retention-days: 1
|
||||
|
||||
python_lint:
|
||||
name: Python lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- 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
|
||||
|
||||
cffi_python_tests:
|
||||
name: CFFI Python tests
|
||||
needs: ["c_library", "python_lint"]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Currently used Rust version.
|
||||
- os: ubuntu-latest
|
||||
python: 3.12
|
||||
- os: macos-latest
|
||||
python: 3.12
|
||||
|
||||
# PyPy tests
|
||||
- os: ubuntu-latest
|
||||
python: pypy3.10
|
||||
- os: macos-latest
|
||||
python: pypy3.10
|
||||
|
||||
# Minimum Supported Python Version = 3.7
|
||||
# This is the minimum version for which manylinux Python wheels are
|
||||
# built. Test it with minimum supported Rust version.
|
||||
- os: ubuntu-latest
|
||||
python: 3.7
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- name: Download libdeltachat.a
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.os }}-libdeltachat.a
|
||||
path: target/debug
|
||||
|
||||
- name: Install python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
|
||||
- name: Install tox
|
||||
run: pip install tox
|
||||
|
||||
- name: Run python tests
|
||||
env:
|
||||
CHATMAIL_DOMAIN: ${{ secrets.CHATMAIL_DOMAIN }}
|
||||
DCC_RS_TARGET: debug
|
||||
DCC_RS_DEV: ${{ github.workspace }}
|
||||
working-directory: python
|
||||
run: tox -e mypy,doc,py
|
||||
|
||||
rpc_python_tests:
|
||||
name: JSON-RPC Python tests
|
||||
needs: ["python_lint", "rpc_server"]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
python: 3.12
|
||||
- os: macos-latest
|
||||
python: 3.12
|
||||
- os: windows-latest
|
||||
python: 3.12
|
||||
|
||||
# PyPy tests
|
||||
- os: ubuntu-latest
|
||||
python: pypy3.10
|
||||
- os: macos-latest
|
||||
python: pypy3.10
|
||||
|
||||
# Minimum Supported Python Version = 3.7
|
||||
- os: ubuntu-latest
|
||||
python: 3.7
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- name: Install python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
|
||||
- name: Install tox
|
||||
run: pip install tox
|
||||
|
||||
- name: Download deltachat-rpc-server
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.os }}-deltachat-rpc-server
|
||||
path: target/debug
|
||||
|
||||
- name: Make deltachat-rpc-server executable
|
||||
if: ${{ matrix.os != 'windows-latest' }}
|
||||
run: chmod +x target/debug/deltachat-rpc-server
|
||||
|
||||
- name: Add deltachat-rpc-server to path
|
||||
if: ${{ matrix.os != 'windows-latest' }}
|
||||
run: echo ${{ github.workspace }}/target/debug >> $GITHUB_PATH
|
||||
|
||||
- name: Add deltachat-rpc-server to path
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
run: |
|
||||
"${{ github.workspace }}/target/debug" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
|
||||
- name: Run deltachat-rpc-client tests
|
||||
env:
|
||||
CHATMAIL_DOMAIN: ${{ secrets.CHATMAIL_DOMAIN }}
|
||||
working-directory: deltachat-rpc-client
|
||||
run: tox -e py
|
||||
|
||||
268
.github/workflows/deltachat-rpc-server.yml
vendored
Normal file
268
.github/workflows/deltachat-rpc-server.yml
vendored
Normal file
@@ -0,0 +1,268 @@
|
||||
# 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: Linux
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [aarch64, armv7l, armv6l, i686, x86_64]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- name: Build deltachat-rpc-server binaries
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-linux
|
||||
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: deltachat-rpc-server-${{ matrix.arch }}-linux
|
||||
path: result/bin/deltachat-rpc-server
|
||||
if-no-files-found: error
|
||||
|
||||
build_windows:
|
||||
name: Windows
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [win32, win64]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- name: Build deltachat-rpc-server binaries
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}
|
||||
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: deltachat-rpc-server-${{ matrix.arch }}
|
||||
path: result/bin/deltachat-rpc-server.exe
|
||||
if-no-files-found: error
|
||||
|
||||
build_macos:
|
||||
name: macOS
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [x86_64, aarch64]
|
||||
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- name: Setup rust target
|
||||
run: rustup target add ${{ matrix.arch }}-apple-darwin
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release --package deltachat-rpc-server --target ${{ matrix.arch }}-apple-darwin --features vendored
|
||||
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: deltachat-rpc-server-${{ matrix.arch }}-macos
|
||||
path: target/${{ matrix.arch }}-apple-darwin/release/deltachat-rpc-server
|
||||
if-no-files-found: error
|
||||
|
||||
build_android:
|
||||
name: Android
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [arm64-v8a, armeabi-v7a]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- name: Build deltachat-rpc-server binaries
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-android
|
||||
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: deltachat-rpc-server-${{ matrix.arch }}-android
|
||||
path: result/bin/deltachat-rpc-server
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
name: Build wheels and upload binaries to the release
|
||||
needs: ["build_linux", "build_windows", "build_macos"]
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/p/deltachat-rpc-server
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- name: Download Linux aarch64 binary
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: deltachat-rpc-server-aarch64-linux
|
||||
path: deltachat-rpc-server-aarch64-linux.d
|
||||
|
||||
- name: Download Linux armv7l binary
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: deltachat-rpc-server-armv7l-linux
|
||||
path: deltachat-rpc-server-armv7l-linux.d
|
||||
|
||||
- name: Download Linux armv6l binary
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: deltachat-rpc-server-armv6l-linux
|
||||
path: deltachat-rpc-server-armv6l-linux.d
|
||||
|
||||
- name: Download Linux i686 binary
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: deltachat-rpc-server-i686-linux
|
||||
path: deltachat-rpc-server-i686-linux.d
|
||||
|
||||
- name: Download Linux x86_64 binary
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: deltachat-rpc-server-x86_64-linux
|
||||
path: deltachat-rpc-server-x86_64-linux.d
|
||||
|
||||
- name: Download Win32 binary
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: deltachat-rpc-server-win32
|
||||
path: deltachat-rpc-server-win32.d
|
||||
|
||||
- name: Download Win64 binary
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: deltachat-rpc-server-win64
|
||||
path: deltachat-rpc-server-win64.d
|
||||
|
||||
- name: Download macOS binary for x86_64
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: deltachat-rpc-server-x86_64-macos
|
||||
path: deltachat-rpc-server-x86_64-macos.d
|
||||
|
||||
- name: Download macOS binary for aarch64
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: deltachat-rpc-server-aarch64-macos
|
||||
path: deltachat-rpc-server-aarch64-macos.d
|
||||
|
||||
- name: Download Android binary for arm64-v8a
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: deltachat-rpc-server-arm64-v8a-android
|
||||
path: deltachat-rpc-server-arm64-v8a-android.d
|
||||
|
||||
- name: Download Android binary for armeabi-v7a
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: deltachat-rpc-server-armeabi-v7a-android
|
||||
path: deltachat-rpc-server-armeabi-v7a-android.d
|
||||
|
||||
- name: Create bin/ directory
|
||||
run: |
|
||||
mkdir -p bin
|
||||
mv deltachat-rpc-server-aarch64-linux.d/deltachat-rpc-server bin/deltachat-rpc-server-aarch64-linux
|
||||
mv deltachat-rpc-server-armv7l-linux.d/deltachat-rpc-server bin/deltachat-rpc-server-armv7l-linux
|
||||
mv deltachat-rpc-server-armv6l-linux.d/deltachat-rpc-server bin/deltachat-rpc-server-armv6l-linux
|
||||
mv deltachat-rpc-server-i686-linux.d/deltachat-rpc-server bin/deltachat-rpc-server-i686-linux
|
||||
mv deltachat-rpc-server-x86_64-linux.d/deltachat-rpc-server bin/deltachat-rpc-server-x86_64-linux
|
||||
mv deltachat-rpc-server-win32.d/deltachat-rpc-server.exe bin/deltachat-rpc-server-win32.exe
|
||||
mv deltachat-rpc-server-win64.d/deltachat-rpc-server.exe bin/deltachat-rpc-server-win64.exe
|
||||
mv deltachat-rpc-server-x86_64-macos.d/deltachat-rpc-server bin/deltachat-rpc-server-x86_64-macos
|
||||
mv deltachat-rpc-server-aarch64-macos.d/deltachat-rpc-server bin/deltachat-rpc-server-aarch64-macos
|
||||
mv deltachat-rpc-server-arm64-v8a-android.d/deltachat-rpc-server bin/deltachat-rpc-server-arm64-v8a-android
|
||||
mv deltachat-rpc-server-armeabi-v7a-android.d/deltachat-rpc-server bin/deltachat-rpc-server-armeabi-v7a-android
|
||||
|
||||
- name: List binaries
|
||||
run: ls -l bin/
|
||||
|
||||
# Python 3.11 is needed for tomllib used in scripts/wheel-rpc-server.py
|
||||
- name: Install python 3.12
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.12
|
||||
|
||||
- name: Install wheel
|
||||
run: pip install wheel
|
||||
|
||||
- name: Build deltachat-rpc-server Python wheels and source package
|
||||
run: |
|
||||
mkdir -p dist
|
||||
nix build .#deltachat-rpc-server-x86_64-linux-wheel
|
||||
cp result/*.whl dist/
|
||||
nix build .#deltachat-rpc-server-armv7l-linux-wheel
|
||||
cp result/*.whl dist/
|
||||
nix build .#deltachat-rpc-server-armv6l-linux-wheel
|
||||
cp result/*.whl dist/
|
||||
nix build .#deltachat-rpc-server-aarch64-linux-wheel
|
||||
cp result/*.whl dist/
|
||||
nix build .#deltachat-rpc-server-i686-linux-wheel
|
||||
cp result/*.whl dist/
|
||||
nix build .#deltachat-rpc-server-win64-wheel
|
||||
cp result/*.whl dist/
|
||||
nix build .#deltachat-rpc-server-win32-wheel
|
||||
cp result/*.whl dist/
|
||||
nix build .#deltachat-rpc-server-source
|
||||
cp result/*.tar.gz dist/
|
||||
python3 scripts/wheel-rpc-server.py x86_64-darwin bin/deltachat-rpc-server-x86_64-macos
|
||||
python3 scripts/wheel-rpc-server.py aarch64-darwin bin/deltachat-rpc-server-aarch64-macos
|
||||
mv *.whl dist/
|
||||
|
||||
- name: List artifacts
|
||||
run: ls -l dist/
|
||||
|
||||
- name: Upload binaries to the GitHub release
|
||||
if: github.event_name == 'release'
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
run: |
|
||||
gh release upload ${{ github.ref_name }} \
|
||||
--repo ${{ github.repository }} \
|
||||
bin/* dist/*
|
||||
|
||||
- name: Publish deltachat-rpc-client to PyPI
|
||||
if: github.event_name == 'release'
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
24
.github/workflows/dependabot.yml
vendored
Normal file
24
.github/workflows/dependabot.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# GitHub Actions workflow
|
||||
# to automatically approve PRs made by Dependabot.
|
||||
|
||||
name: Dependabot auto-approve
|
||||
on: pull_request
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
dependabot:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||
steps:
|
||||
- name: Dependabot metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata@v1.1.1
|
||||
with:
|
||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
- name: Approve a PR
|
||||
run: gh pr review --approve "$PR_URL"
|
||||
env:
|
||||
PR_URL: ${{github.event.pull_request.html_url}}
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
82
.github/workflows/jsonrpc-client-npm-package.yml
vendored
Normal file
82
.github/workflows/jsonrpc-client-npm-package.yml
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
name: "jsonrpc js client build"
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
- "!py-*"
|
||||
|
||||
jobs:
|
||||
pack-module:
|
||||
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
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18"
|
||||
- name: Get tag
|
||||
id: tag
|
||||
uses: dawidd6/action-get-tag@v1
|
||||
continue-on-error: true
|
||||
- name: Get Pull Request ID
|
||||
id: prepare
|
||||
run: |
|
||||
tag=${{ steps.tag.outputs.tag }}
|
||||
if [ -z "$tag" ]; then
|
||||
node -e "console.log('DELTACHAT_JSONRPC_TAR_GZ=deltachat-jsonrpc-client-' + '${{ github.ref }}'.split('/')[2] + '.tar.gz')" >> $GITHUB_ENV
|
||||
else
|
||||
echo "DELTACHAT_JSONRPC_TAR_GZ=deltachat-jsonrpc-client-${{ steps.tag.outputs.tag }}.tar.gz" >> $GITHUB_ENV
|
||||
echo "No preview will be uploaded this time, but the $tag release"
|
||||
fi
|
||||
- name: System info
|
||||
run: |
|
||||
npm --version
|
||||
node --version
|
||||
echo $DELTACHAT_JSONRPC_TAR_GZ
|
||||
- name: Install dependencies without running scripts
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm install --ignore-scripts
|
||||
- name: Package
|
||||
shell: bash
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: |
|
||||
npm run build
|
||||
npm pack .
|
||||
ls -lah
|
||||
mv $(find deltachat-jsonrpc-client-*) $DELTACHAT_JSONRPC_TAR_GZ
|
||||
- name: Upload Prebuild
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: deltachat-jsonrpc-client.tgz
|
||||
path: deltachat-jsonrpc/typescript/${{ env.DELTACHAT_JSONRPC_TAR_GZ }}
|
||||
# Upload to download.delta.chat/node/preview/
|
||||
- name: Upload deltachat-jsonrpc-client preview to download.delta.chat/node/preview/
|
||||
if: ${{ ! steps.tag.outputs.tag }}
|
||||
id: upload-preview
|
||||
shell: bash
|
||||
run: |
|
||||
echo -e "${{ secrets.SSH_KEY }}" >__TEMP_INPUT_KEY_FILE
|
||||
chmod 600 __TEMP_INPUT_KEY_FILE
|
||||
scp -o StrictHostKeyChecking=no -v -i __TEMP_INPUT_KEY_FILE -P "22" -r deltachat-jsonrpc/typescript/$DELTACHAT_JSONRPC_TAR_GZ "${{ secrets.USERNAME }}"@"download.delta.chat":"/var/www/html/download/node/preview/"
|
||||
continue-on-error: true
|
||||
- name: Post links to details
|
||||
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
|
||||
# Upload to download.delta.chat/node/
|
||||
- name: Upload deltachat-jsonrpc-client build to download.delta.chat/node/
|
||||
if: ${{ steps.tag.outputs.tag }}
|
||||
id: upload
|
||||
shell: bash
|
||||
run: |
|
||||
echo -e "${{ secrets.SSH_KEY }}" >__TEMP_INPUT_KEY_FILE
|
||||
chmod 600 __TEMP_INPUT_KEY_FILE
|
||||
scp -o StrictHostKeyChecking=no -v -i __TEMP_INPUT_KEY_FILE -P "22" -r deltachat-jsonrpc/typescript/$DELTACHAT_JSONRPC_TAR_GZ "${{ secrets.USERNAME }}"@"download.delta.chat":"/var/www/html/download/node/"
|
||||
42
.github/workflows/jsonrpc.yml
vendored
Normal file
42
.github/workflows/jsonrpc.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: JSON-RPC API Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUST_MIN_STACK: "8388608"
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
- name: Add Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: npm install
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm install
|
||||
- name: Build TypeScript, run Rust tests, generate bindings
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm run build
|
||||
- name: Run integration tests
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm run test
|
||||
env:
|
||||
CHATMAIL_DOMAIN: ${{ secrets.CHATMAIL_DOMAIN }}
|
||||
- name: make sure websocket server version still builds
|
||||
working-directory: deltachat-jsonrpc
|
||||
run: cargo build --bin deltachat-jsonrpc-server --features webserver
|
||||
- name: Run linter
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm run prettier:check
|
||||
41
.github/workflows/node-docs.yml
vendored
Normal file
41
.github/workflows/node-docs.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# 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:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
generate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
|
||||
- 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/"
|
||||
235
.github/workflows/node-package.yml
vendored
Normal file
235
.github/workflows/node-package.yml
vendored
Normal file
@@ -0,0 +1,235 @@
|
||||
name: "node.js build"
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
- "!py-*"
|
||||
|
||||
jobs:
|
||||
prebuild:
|
||||
name: Prebuild
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18"
|
||||
- name: System info
|
||||
run: |
|
||||
rustc -vV
|
||||
rustup -vV
|
||||
cargo -vV
|
||||
npm --version
|
||||
node --version
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{ env.APPDATA }}/npm-cache
|
||||
~/.npm
|
||||
key: ${{ matrix.os }}-node-${{ hashFiles('**/package.json') }}
|
||||
|
||||
- name: Cache cargo index
|
||||
uses: actions/cache@v4
|
||||
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 "${{ matrix.os }}.tar.gz" -C prebuilds .
|
||||
|
||||
- name: Upload Prebuild
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
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: https://packages.debian.org/buster/libc6
|
||||
container: debian:10
|
||||
steps:
|
||||
# Working directory is owned by 1001:1001 by default.
|
||||
# Change it to our user.
|
||||
- name: Change working directory owner
|
||||
run: chown root:root .
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18"
|
||||
- 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@v4
|
||||
with:
|
||||
path: |
|
||||
${{ env.APPDATA }}/npm-cache
|
||||
~/.npm
|
||||
key: ${{ matrix.os }}-node-${{ hashFiles('**/package.json') }}
|
||||
|
||||
- name: Cache cargo index
|
||||
uses: actions/cache@v4
|
||||
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@v4
|
||||
with:
|
||||
name: linux
|
||||
path: node/linux.tar.gz
|
||||
|
||||
pack-module:
|
||||
needs: [prebuild, prebuild-linux]
|
||||
name: Package deltachat-node and upload to download.delta.chat
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install tree
|
||||
run: sudo apt install tree
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18"
|
||||
- name: Get tag
|
||||
id: tag
|
||||
uses: dawidd6/action-get-tag@v1
|
||||
continue-on-error: true
|
||||
- name: Get Pull Request ID
|
||||
id: prepare
|
||||
run: |
|
||||
tag=${{ steps.tag.outputs.tag }}
|
||||
if [ -z "$tag" ]; then
|
||||
node -e "console.log('DELTACHAT_NODE_TAR_GZ=deltachat-node-' + '${{ github.ref }}'.split('/')[2] + '.tar.gz')" >> $GITHUB_ENV
|
||||
else
|
||||
echo "DELTACHAT_NODE_TAR_GZ=deltachat-node-${{ steps.tag.outputs.tag }}.tar.gz" >> $GITHUB_ENV
|
||||
echo "No preview will be uploaded this time, but the $tag release"
|
||||
fi
|
||||
- name: System info
|
||||
run: |
|
||||
rustc -vV
|
||||
rustup -vV
|
||||
cargo -vV
|
||||
npm --version
|
||||
node --version
|
||||
echo $DELTACHAT_NODE_TAR_GZ
|
||||
- name: Download Linux prebuild
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: linux
|
||||
- name: Download macOS prebuild
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: macos-latest
|
||||
- name: Download Windows prebuild
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: windows-latest
|
||||
- shell: bash
|
||||
run: |
|
||||
mkdir node/prebuilds
|
||||
tar -xvzf linux.tar.gz -C node/prebuilds
|
||||
tar -xvzf macos-latest.tar.gz -C node/prebuilds
|
||||
tar -xvzf windows-latest.tar.gz -C node/prebuilds
|
||||
tree node/prebuilds
|
||||
rm -f linux.tar.gz macos-latest.tar.gz windows-latest.tar.gz
|
||||
- name: Install dependencies without running scripts
|
||||
run: |
|
||||
npm install --ignore-scripts
|
||||
- name: Build constants
|
||||
run: |
|
||||
npm run build:core:constants
|
||||
- name: Build TypeScript part
|
||||
run: |
|
||||
npm run build:bindings:ts
|
||||
- name: Package
|
||||
shell: bash
|
||||
run: |
|
||||
mv node/README.md README.md
|
||||
npm pack .
|
||||
ls -lah
|
||||
mv $(find deltachat-node-*) $DELTACHAT_NODE_TAR_GZ
|
||||
- name: Upload prebuild
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: deltachat-node.tgz
|
||||
path: ${{ env.DELTACHAT_NODE_TAR_GZ }}
|
||||
# Upload to download.delta.chat/node/preview/
|
||||
- name: Upload deltachat-node preview to download.delta.chat/node/preview/
|
||||
if: ${{ ! steps.tag.outputs.tag }}
|
||||
id: upload-preview
|
||||
shell: bash
|
||||
run: |
|
||||
echo -e "${{ secrets.SSH_KEY }}" >__TEMP_INPUT_KEY_FILE
|
||||
chmod 600 __TEMP_INPUT_KEY_FILE
|
||||
scp -o StrictHostKeyChecking=no -v -i __TEMP_INPUT_KEY_FILE -P "22" -r $DELTACHAT_NODE_TAR_GZ "${{ secrets.USERNAME }}"@"download.delta.chat":"/var/www/html/download/node/preview/"
|
||||
continue-on-error: true
|
||||
- name: Post links to details
|
||||
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 }}
|
||||
# Upload to download.delta.chat/node/
|
||||
- name: Upload deltachat-node build to download.delta.chat/node/
|
||||
if: ${{ steps.tag.outputs.tag }}
|
||||
id: upload
|
||||
shell: bash
|
||||
run: |
|
||||
echo -e "${{ secrets.SSH_KEY }}" >__TEMP_INPUT_KEY_FILE
|
||||
chmod 600 __TEMP_INPUT_KEY_FILE
|
||||
scp -o StrictHostKeyChecking=no -v -i __TEMP_INPUT_KEY_FILE -P "22" -r $DELTACHAT_NODE_TAR_GZ "${{ secrets.USERNAME }}"@"download.delta.chat":"/var/www/html/download/node/"
|
||||
68
.github/workflows/node-tests.yml
vendored
Normal file
68
.github/workflows/node-tests.yml
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
# 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:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Tests
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "18"
|
||||
- name: System info
|
||||
run: |
|
||||
rustc -vV
|
||||
rustup -vV
|
||||
cargo -vV
|
||||
npm --version
|
||||
node --version
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{ env.APPDATA }}/npm-cache
|
||||
~/.npm
|
||||
key: ${{ matrix.os }}-node-${{ hashFiles('**/package.json') }}
|
||||
|
||||
- name: Cache cargo index
|
||||
uses: actions/cache@v4
|
||||
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: Test
|
||||
timeout-minutes: 10
|
||||
working-directory: node
|
||||
run: npm run test
|
||||
env:
|
||||
CHATMAIL_DOMAIN: ${{ secrets.CHATMAIL_DOMAIN }}
|
||||
NODE_OPTIONS: "--force-node-api-uncaught-exceptions-policy=true"
|
||||
47
.github/workflows/publish-deltachat-rpc-client-pypi.yml
vendored
Normal file
47
.github/workflows/publish-deltachat-rpc-client-pypi.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Publish deltachat-rpc-client to PyPI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build distribution
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
- name: Install pypa/build
|
||||
run: python3 -m pip install build
|
||||
- name: Build a binary wheel and a source tarball
|
||||
working-directory: deltachat-rpc-client
|
||||
run: python3 -m build
|
||||
- name: Store the distribution packages
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: deltachat-rpc-client/dist/
|
||||
|
||||
publish-to-pypi:
|
||||
name: Publish Python distribution to PyPI
|
||||
if: github.event_name == 'release'
|
||||
needs:
|
||||
- build
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/p/deltachat-rpc-client
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Download all the dists
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: dist/
|
||||
- name: Publish deltachat-rpc-client to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
36
.github/workflows/repl.yml
vendored
36
.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
|
||||
@@ -9,24 +10,17 @@ on:
|
||||
jobs:
|
||||
build_repl:
|
||||
name: Build REPL example
|
||||
runs-on: windows-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.50.0
|
||||
override: true
|
||||
|
||||
- name: build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --example repl --features repl,vendored
|
||||
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: repl.exe
|
||||
path: 'target/debug/examples/repl.exe'
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
- name: Build
|
||||
run: nix build .#deltachat-repl-win64
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: repl.exe
|
||||
path: "result/bin/deltachat-repl.exe"
|
||||
|
||||
64
.github/workflows/upload-docs.yml
vendored
Normal file
64
.github/workflows/upload-docs.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
name: Build & Deploy Documentation on rs.delta.chat, c.delta.chat, py.delta.chat
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build-rs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
- 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/"
|
||||
|
||||
build-python:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
- name: Build Python documentation
|
||||
run: nix build .#python-docs
|
||||
- name: Upload to py.delta.chat
|
||||
run: |
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.CODESPEAK_KEY }}" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
rsync -avzh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/result/html/ "delta@py.delta.chat:/home/delta/build/master"
|
||||
|
||||
build-c:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
||||
- uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
- name: Build C documentation
|
||||
run: nix build .#docs
|
||||
- name: Upload to py.delta.chat
|
||||
run: |
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.CODESPEAK_KEY }}" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
rsync -avzh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/result/html/ "delta@c.delta.chat:/home/delta/build-c/master"
|
||||
28
.github/workflows/upload-ffi-docs.yml
vendored
Normal file
28
.github/workflows/upload-ffi-docs.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# 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:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
- name: Build the documentation with cargo
|
||||
run: |
|
||||
cargo doc --package deltachat_ffi --no-deps
|
||||
- name: Upload to cffi.delta.chat
|
||||
run: |
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.KEY }}" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
rsync -avzh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/target/doc/ "${{ secrets.USERNAME }}@delta.chat:/var/www/html/cffi/"
|
||||
24
.gitignore
vendored
24
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
/build
|
||||
/dist
|
||||
|
||||
# ignore vi temporaries
|
||||
*~
|
||||
@@ -12,12 +13,15 @@ include
|
||||
*.db
|
||||
*.db-blobs
|
||||
|
||||
.tox
|
||||
python/.eggs
|
||||
python/.tox
|
||||
*.egg-info
|
||||
__pycache__
|
||||
python/src/deltachat/capi*.so
|
||||
python/.venv/
|
||||
python/venv/
|
||||
venv/
|
||||
env/
|
||||
|
||||
python/liveconfig*
|
||||
|
||||
@@ -29,3 +33,21 @@ deltachat-ffi/xml
|
||||
|
||||
coverage/
|
||||
.DS_Store
|
||||
.vscode/launch.json
|
||||
python/accounts.txt
|
||||
python/all-testaccounts.txt
|
||||
tmp/
|
||||
|
||||
# from deltachat-node
|
||||
node_modules/
|
||||
node/build/
|
||||
node/dist/
|
||||
node/prebuilds/
|
||||
node/.nyc_output/
|
||||
|
||||
# Nix symlink.
|
||||
result
|
||||
|
||||
# direnv
|
||||
.envrc
|
||||
.direnv
|
||||
56
.npmignore
Normal file
56
.npmignore
Normal file
@@ -0,0 +1,56 @@
|
||||
.circleci/
|
||||
.gitmodules
|
||||
node/.nyc_output/
|
||||
.travis.yml
|
||||
appveyor.yml
|
||||
node/build/
|
||||
node/README.md
|
||||
rustfmt.toml
|
||||
spec.md
|
||||
test-data/
|
||||
build2/
|
||||
node_modules
|
||||
.git
|
||||
.idea/
|
||||
.pytest_cache
|
||||
CMakeLists.txt
|
||||
README.md
|
||||
contrib/
|
||||
node/ci_scripts/
|
||||
coverage/
|
||||
node/.circleci
|
||||
node/appveyor.yml
|
||||
ci/
|
||||
ci_scripts/
|
||||
python/
|
||||
target
|
||||
proptest-regressions
|
||||
deltachat-ffi/Doxyfile
|
||||
scripts
|
||||
webxdc.md
|
||||
standards.md
|
||||
draft/
|
||||
node/examples/
|
||||
# deltachat-core-rust/assets # don't exclude assets, otherwise it won't build
|
||||
node/images/
|
||||
node/test/
|
||||
node/windows.md
|
||||
node/*.tar.gz
|
||||
node/old_docs.md
|
||||
.vscode/
|
||||
.github/
|
||||
node/.prettierrc.yml
|
||||
|
||||
deltachat-jsonrpc/TODO.md
|
||||
deltachat-jsonrpc/README.MD
|
||||
deltachat-jsonrpc/.gitignore
|
||||
deltachat-jsonrpc/typescript/.gitignore
|
||||
deltachat-jsonrpc/typescript/.prettierignore
|
||||
deltachat-jsonrpc/typescript/accounts/
|
||||
deltachat-jsonrpc/typescript/index.html
|
||||
deltachat-jsonrpc/typescript/node-demo.js
|
||||
deltachat-jsonrpc/typescript/report_api_coverage.mjs
|
||||
deltachat-jsonrpc/typescript/test
|
||||
deltachat-jsonrpc/typescript/example.ts
|
||||
|
||||
.DS_Store
|
||||
2594
CHANGELOG.md
2594
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -4,26 +4,24 @@ include(GNUInstallDirs)
|
||||
|
||||
find_program(CARGO cargo)
|
||||
|
||||
if(APPLE)
|
||||
set(DYNAMIC_EXT "dylib")
|
||||
elseif(UNIX)
|
||||
set(DYNAMIC_EXT "so")
|
||||
else()
|
||||
set(DYNAMIC_EXT "dll")
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
"target/release/libdeltachat.a"
|
||||
"target/release/libdeltachat.so"
|
||||
"target/release/pkgconfig/deltachat.pc"
|
||||
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.a"
|
||||
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.${DYNAMIC_EXT}"
|
||||
"${CMAKE_BINARY_DIR}/target/release/pkgconfig/deltachat.pc"
|
||||
COMMAND
|
||||
PREFIX=${CMAKE_INSTALL_PREFIX}
|
||||
LIBDIR=${CMAKE_INSTALL_FULL_LIBDIR}
|
||||
INCLUDEDIR=${CMAKE_INSTALL_FULL_INCLUDEDIR}
|
||||
${CARGO} build --release --no-default-features
|
||||
|
||||
# Build in `deltachat-ffi` directory instead of using
|
||||
# `--package deltachat_ffi` to avoid feature resolver version
|
||||
# "1" bug which makes `--no-default-features` affect only
|
||||
# `deltachat`, but not `deltachat-ffi` package.
|
||||
#
|
||||
# We can't enable version "2" resolver [1] because it is not
|
||||
# stable yet on rust 1.50.0.
|
||||
#
|
||||
# [1] https://doc.rust-lang.org/nightly/cargo/reference/features.html#resolver-version-2-command-line-flags
|
||||
${CARGO} build --target-dir=${CMAKE_BINARY_DIR}/target --release --no-default-features --features jsonrpc
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/deltachat-ffi
|
||||
)
|
||||
|
||||
@@ -31,12 +29,12 @@ add_custom_target(
|
||||
lib_deltachat
|
||||
ALL
|
||||
DEPENDS
|
||||
"target/release/libdeltachat.a"
|
||||
"target/release/libdeltachat.so"
|
||||
"target/release/pkgconfig/deltachat.pc"
|
||||
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.a"
|
||||
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.${DYNAMIC_EXT}"
|
||||
"${CMAKE_BINARY_DIR}/target/release/pkgconfig/deltachat.pc"
|
||||
)
|
||||
|
||||
install(FILES "deltachat-ffi/deltachat.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
install(FILES "target/release/libdeltachat.a" DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(FILES "target/release/libdeltachat.so" DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(FILES "target/release/pkgconfig/deltachat.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
install(FILES "${CMAKE_BINARY_DIR}/target/release/libdeltachat.a" DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(FILES "${CMAKE_BINARY_DIR}/target/release/libdeltachat.${DYNAMIC_EXT}" DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(FILES "${CMAKE_BINARY_DIR}/target/release/pkgconfig/deltachat.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
|
||||
126
CONTRIBUTING.md
Normal file
126
CONTRIBUTING.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# 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/
|
||||
|
||||
### Errors
|
||||
|
||||
Delta Chat core mostly uses [`anyhow`](https://docs.rs/anyhow/) errors.
|
||||
When using [`Context`](https://docs.rs/anyhow/latest/anyhow/trait.Context.html),
|
||||
capitalize it but do not add a full stop as the contexts will be separated by `:`.
|
||||
For example:
|
||||
```
|
||||
.with_context(|| format!("Unable to trash message {msg_id}"))
|
||||
```
|
||||
|
||||
All errors should be handled in one of these ways:
|
||||
- With `if let Err() =` (incl. logging them into `warn!()`/`err!()`).
|
||||
- With `.log_err().ok()`.
|
||||
- Bubbled up with `?`.
|
||||
|
||||
`backtrace` feature is enabled for `anyhow` crate
|
||||
and `debug = 1` option is set in the test profile.
|
||||
This allows to run `RUST_BACKTRACE=1 cargo test`
|
||||
and get a backtrace with line numbers in resultified tests
|
||||
which return `anyhow::Result`.
|
||||
|
||||
### Logging
|
||||
|
||||
For logging, use `info!`, `warn!` and `error!` macros.
|
||||
Log messages should be capitalized and have a full stop in the end. For example:
|
||||
```
|
||||
info!(context, "Ignoring addition of {added_addr:?} to {chat_id}.");
|
||||
```
|
||||
|
||||
Format anyhow errors with `{:#}` to print all the contexts like this:
|
||||
```
|
||||
error!(context, "Failed to set selfavatar timestamp: {err:#}.");
|
||||
```
|
||||
|
||||
### 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).
|
||||
5253
Cargo.lock
generated
5253
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
185
Cargo.toml
185
Cargo.toml
@@ -1,108 +1,141 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "1.61.0"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
version = "1.137.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.77"
|
||||
repository = "https://github.com/deltachat/deltachat-core-rust"
|
||||
|
||||
[profile.dev]
|
||||
debug = 0
|
||||
panic = 'abort'
|
||||
opt-level = 1
|
||||
|
||||
[profile.test]
|
||||
# Make anyhow `backtrace` feature useful.
|
||||
# With `debug = 0` there are no line numbers in the backtrace
|
||||
# produced with RUST_BACKTRACE=1.
|
||||
debug = 1
|
||||
opt-level = 0
|
||||
|
||||
# Always optimize dependencies.
|
||||
# This does not apply to crates in the workspace.
|
||||
# <https://doc.rust-lang.org/cargo/reference/profiles.html#overrides>
|
||||
[profile.dev.package."*"]
|
||||
opt-level = "z"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
panic = 'abort'
|
||||
opt-level = "z"
|
||||
codegen-units = 1
|
||||
strip = true
|
||||
|
||||
[dependencies]
|
||||
deltachat_derive = { path = "./deltachat_derive" }
|
||||
deltachat-time = { path = "./deltachat-time" }
|
||||
format-flowed = { path = "./format-flowed" }
|
||||
ratelimit = { path = "./deltachat-ratelimit" }
|
||||
|
||||
ansi_term = { version = "0.12.1", optional = true }
|
||||
anyhow = "1"
|
||||
async-imap = { git = "https://github.com/async-email/async-imap" }
|
||||
async-native-tls = { version = "0.3" }
|
||||
async-smtp = { git = "https://github.com/async-email/async-smtp", branch="master", features = ["socks5"] }
|
||||
async-std-resolver = "0.20"
|
||||
async-std = { version = "1", features = ["unstable"] }
|
||||
async-tar = "0.3"
|
||||
async-trait = "0.1"
|
||||
async-channel = "2.0.0"
|
||||
async-imap = { version = "0.9.7", 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.13"
|
||||
base64 = "0.21"
|
||||
brotli = { version = "4", default-features=false, features = ["std"] }
|
||||
bitflags = "1.3"
|
||||
byteorder = "1.3"
|
||||
chrono = "0.4"
|
||||
dirs = { version = "4", optional=true }
|
||||
bstr = { version = "1.4.0", default-features=false, features = ["std", "alloc"] }
|
||||
chrono = { version = "0.4", default-features=false, features = ["clock", "std"] }
|
||||
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
|
||||
encoded-words = { git = "https://github.com/async-email/encoded-words", branch="master" }
|
||||
encoded-words = { git = "https://github.com/async-email/encoded-words", branch = "master" }
|
||||
escaper = "0.1"
|
||||
fast-socks5 = "0.9"
|
||||
fd-lock = "4"
|
||||
futures = "0.3"
|
||||
futures-lite = "2.3.0"
|
||||
hex = "0.4.0"
|
||||
image = { version = "0.23.5", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||
indexmap = "1.7"
|
||||
itertools = "0.10"
|
||||
hickory-resolver = "0.24"
|
||||
humansize = "2"
|
||||
image = { version = "0.25.1", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||
iroh = { version = "0.4.2", default-features = false }
|
||||
kamadak-exif = "0.5"
|
||||
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
|
||||
libc = "0.2"
|
||||
log = {version = "0.4.8", optional = true }
|
||||
mailparse = "0.13"
|
||||
native-tls = "0.2"
|
||||
num_cpus = "1.13"
|
||||
num-derive = "0.3"
|
||||
mailparse = "0.14"
|
||||
mime = "0.3.17"
|
||||
num_cpus = "1.16"
|
||||
num-derive = "0.4"
|
||||
num-traits = "0.2"
|
||||
once_cell = "1.8.0"
|
||||
percent-encoding = "2.0"
|
||||
pgp = { version = "0.7", default-features = false }
|
||||
pretty_env_logger = { version = "0.4", optional = true }
|
||||
quick-xml = "0.22"
|
||||
r2d2 = "0.8"
|
||||
r2d2_sqlite = "0.18"
|
||||
rand = "0.7"
|
||||
regex = "1.5"
|
||||
rusqlite = "0.25"
|
||||
once_cell = "1.18.0"
|
||||
percent-encoding = "2.3"
|
||||
parking_lot = "0.12"
|
||||
pgp = { version = "0.11", default-features = false }
|
||||
pin-project = "1"
|
||||
pretty_env_logger = { version = "0.5", optional = true }
|
||||
qrcodegen = "1.7.0"
|
||||
quick-xml = "0.31"
|
||||
quoted_printable = "0.5"
|
||||
rand = "0.8"
|
||||
regex = "1.10"
|
||||
reqwest = { version = "0.12.2", features = ["json"] }
|
||||
rusqlite = { version = "0.31", features = ["sqlcipher"] }
|
||||
rust-hsluv = "0.1"
|
||||
rustyline = { version = "9.0", optional = true }
|
||||
sanitize-filename = "0.3"
|
||||
serde_json = "1.0"
|
||||
sanitize-filename = "0.5"
|
||||
serde_json = "1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
sha-1 = "0.9"
|
||||
sha2 = "0.9"
|
||||
sha-1 = "0.10"
|
||||
sha2 = "0.10"
|
||||
smallvec = "1"
|
||||
stop-token = "0.2"
|
||||
strum = "0.21"
|
||||
strum_macros = "0.21"
|
||||
surf = { version = "2.3", default-features = false, features = ["h1-client"] }
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
tagger = "4.3.4"
|
||||
textwrap = "0.16.1"
|
||||
thiserror = "1"
|
||||
toml = "0.5"
|
||||
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
|
||||
tokio-io-timeout = "1.2.0"
|
||||
tokio-stream = { version = "0.1.15", features = ["fs"] }
|
||||
tokio-tar = { version = "0.3" } # TODO: integrate tokio into async-tar
|
||||
tokio-util = "0.7.9"
|
||||
toml = "0.8"
|
||||
url = "2"
|
||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||
fast-socks5 = "0.4"
|
||||
humansize = "1"
|
||||
uuid = { version = "1", features = ["serde", "v4"] }
|
||||
|
||||
# Pin OpenSSL to 3.1 releases.
|
||||
# OpenSSL 3.2 has a regression tracked at <https://github.com/openssl/openssl/issues/23376>
|
||||
# which results in broken `deltachat-rpc-server` binaries when cross-compiled using Zig toolchain.
|
||||
# See <https://github.com/deltachat/deltachat-core-rust/issues/5206> for Delta Chat issue.
|
||||
# According to <https://www.openssl.org/policies/releasestrat.html>
|
||||
# 3.1 branch will be supported until 2025-03-14.
|
||||
openssl-src = "~300.1"
|
||||
|
||||
[dev-dependencies]
|
||||
ansi_term = "0.12.0"
|
||||
async-std = { version = "1", features = ["unstable", "attributes"] }
|
||||
criterion = "0.3"
|
||||
futures-lite = "1.12"
|
||||
anyhow = { version = "1", features = ["backtrace"] } # Enable `backtrace` feature in tests.
|
||||
criterion = { version = "0.5.1", features = ["async_tokio"] }
|
||||
futures-lite = "2.3.0"
|
||||
log = "0.4"
|
||||
pretty_assertions = "1.0"
|
||||
pretty_env_logger = "0.4"
|
||||
proptest = "1"
|
||||
pretty_env_logger = "0.5"
|
||||
proptest = { version = "1", default-features = false, features = ["std"] }
|
||||
tempfile = "3"
|
||||
testdir = "0.9.0"
|
||||
tokio = { version = "1", features = ["parking_lot", "rt-multi-thread", "macros"] }
|
||||
pretty_assertions = "1.3.0"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"deltachat-ffi",
|
||||
"deltachat_derive",
|
||||
"deltachat-jsonrpc",
|
||||
"deltachat-rpc-server",
|
||||
"deltachat-ratelimit",
|
||||
"deltachat-repl",
|
||||
"deltachat-time",
|
||||
"format-flowed",
|
||||
]
|
||||
|
||||
[[example]]
|
||||
name = "simple"
|
||||
path = "examples/simple.rs"
|
||||
required-features = ["repl"]
|
||||
|
||||
[[example]]
|
||||
name = "repl"
|
||||
path = "examples/repl/main.rs"
|
||||
required-features = ["repl"]
|
||||
|
||||
|
||||
[[bench]]
|
||||
name = "create_account"
|
||||
harness = false
|
||||
@@ -115,9 +148,27 @@ harness = false
|
||||
name = "search_msgs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "receive_emails"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "get_chat_msgs"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "get_chatlist"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "send_events"
|
||||
harness = false
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
internals = []
|
||||
repl = ["internals", "rustyline", "log", "pretty_env_logger", "ansi_term", "dirs"]
|
||||
vendored = ["async-native-tls/vendored", "async-smtp/native-tls-vendored", "rusqlite/bundled"]
|
||||
nightly = ["pgp/nightly"]
|
||||
vendored = [
|
||||
"async-native-tls/vendored",
|
||||
"rusqlite/bundled-sqlcipher-vendored-openssl",
|
||||
"reqwest/native-tls-vendored"
|
||||
]
|
||||
|
||||
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
|
||||
|
||||
80
README.md
80
README.md
@@ -1,8 +1,16 @@
|
||||
# Delta Chat Rust
|
||||
<p align="center">
|
||||
<img alt="Delta Chat Logo" height="200px" src="https://raw.githubusercontent.com/deltachat/deltachat-pages/master/assets/blog/rust-delta.png">
|
||||
</p>
|
||||
|
||||
> Deltachat-core written in Rust
|
||||
<p align="center">
|
||||
<a href="https://github.com/deltachat/deltachat-core-rust/actions/workflows/ci.yml">
|
||||
<img alt="Rust CI" src="https://github.com/deltachat/deltachat-core-rust/actions/workflows/ci.yml/badge.svg">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
[](https://github.com/deltachat/deltachat-core-rust/actions/workflows/ci.yml)
|
||||
<p align="center">
|
||||
The core library for Delta Chat, written in Rust
|
||||
</p>
|
||||
|
||||
## Installing Rust and Cargo
|
||||
|
||||
@@ -12,15 +20,26 @@ To download and install the official compiler for the Rust programming language,
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
```
|
||||
|
||||
> On Windows, you may need to also install **Perl** to be able to compile deltachat-core.
|
||||
|
||||
## Using the CLI client
|
||||
|
||||
Compile and run Delta Chat Core command line utility, using `cargo`:
|
||||
|
||||
```
|
||||
$ RUST_LOG=repl=info cargo run --example repl --features repl -- ~/deltachat-db
|
||||
$ cargo run -p deltachat-repl -- ~/deltachat-db
|
||||
```
|
||||
where ~/deltachat-db is the database file. Delta Chat will create it if it does not exist.
|
||||
|
||||
Optionally, install `deltachat-repl` binary with
|
||||
```
|
||||
$ cargo install --path deltachat-repl/
|
||||
```
|
||||
and run as
|
||||
```
|
||||
$ deltachat-repl ~/deltachat-db
|
||||
```
|
||||
|
||||
Configure your account (if not already configured):
|
||||
|
||||
```
|
||||
@@ -100,12 +119,9 @@ $ cargo build -p deltachat_ffi --release
|
||||
|
||||
## Debugging environment variables
|
||||
|
||||
- `DCC_IMAP_DEBUG`: if set IMAP protocol commands and responses will be
|
||||
printed
|
||||
|
||||
- `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=async_imap=trace,async_smtp=trace`: enable IMAP and
|
||||
SMTP tracing in addition to info messages.
|
||||
|
||||
### Expensive tests
|
||||
@@ -116,20 +132,56 @@ use the `--ignored` argument to the test binary (not to cargo itself):
|
||||
$ cargo test -- --ignored
|
||||
```
|
||||
|
||||
### Fuzzing
|
||||
|
||||
Install [`cargo-bolero`](https://github.com/camshaft/bolero) with
|
||||
```sh
|
||||
$ cargo install cargo-bolero
|
||||
```
|
||||
|
||||
Run fuzzing tests with
|
||||
```sh
|
||||
$ cd fuzz
|
||||
$ cargo bolero test fuzz_mailparse --release=false -s NONE
|
||||
```
|
||||
|
||||
Corpus is created at `fuzz/fuzz_targets/corpus`,
|
||||
you can add initial inputs there.
|
||||
For `fuzz_mailparse` target corpus can be populated with
|
||||
`../test-data/message/*.eml`.
|
||||
|
||||
To run with AFL instead of libFuzzer:
|
||||
```sh
|
||||
$ cargo bolero test fuzz_format_flowed --release=false -e afl -s NONE
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- `vendored`: When using Openssl for TLS, this bundles a vendored version.
|
||||
- `nightly`: Enable nightly only performance and security related features.
|
||||
|
||||
## Update Provider Data
|
||||
|
||||
To add the updates from the
|
||||
[provider-db](https://github.com/deltachat/provider-db) to the core, run:
|
||||
|
||||
```
|
||||
./src/provider/update.py ../provider-db/_providers/ > src/provider/data.rs
|
||||
```
|
||||
|
||||
## Language bindings and frontend projects
|
||||
|
||||
Language bindings are available for:
|
||||
|
||||
- [C](https://c.delta.chat)
|
||||
- [Node.js](https://www.npmjs.com/package/deltachat-node)
|
||||
- [Python](https://py.delta.chat)
|
||||
- [Go](https://github.com/deltachat/go-deltachat/)
|
||||
- [Free Pascal](https://github.com/deltachat/deltachat-fp/)
|
||||
- **C** \[[📂 source](./deltachat-ffi) | [📚 docs](https://c.delta.chat)\]
|
||||
- **Node.js**
|
||||
- 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**
|
||||
- over jsonrpc: \[[📂 source](https://github.com/deltachat/deltachat-rpc-client-go/)\]
|
||||
- over cffi[^1]: \[[📂 source](https://github.com/deltachat/go-deltachat/)\]
|
||||
- **Free Pascal**[^1] \[[📂 source](https://github.com/deltachat/deltachat-fp/)\]
|
||||
- **Java** and **Swift** (contained in the Android/iOS repos)
|
||||
|
||||
The following "frontend" projects make use of the Rust-library
|
||||
@@ -141,3 +193,5 @@ or its language bindings:
|
||||
- [Pidgin](https://code.ur.gs/lupine/purple-plugin-delta/)
|
||||
- [Telepathy](https://code.ur.gs/lupine/telepathy-padfoot/)
|
||||
- several **Bots**
|
||||
|
||||
[^1]: Out of date / unmaintained, if you like those languages feel free to start maintaining them. If you have questions we'll help you, please ask in the issues.
|
||||
|
||||
24
RELEASE.md
Normal file
24
RELEASE.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# 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. add a link to compare previous with current version to the end of CHANGELOG.md:
|
||||
`[1.116.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.115.2...v1.116.0`
|
||||
|
||||
5. Update the version by running `scripts/set_core_version.py 1.116.0`.
|
||||
|
||||
6. 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.
|
||||
|
||||
7. Tag the release: `git tag -a v1.116.0`.
|
||||
|
||||
8. Push the release tag: `git push origin v1.116.0`.
|
||||
|
||||
9. Create a GitHub release: `gh release create v1.116.0 -n ''`.
|
||||
BIN
assets/icon-archive.png
Normal file
BIN
assets/icon-archive.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
60
assets/icon-archive.svg
Normal file
60
assets/icon-archive.svg
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="60"
|
||||
height="60"
|
||||
viewBox="0 0 60 60"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="feather feather-archive"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="icon-archive.svg"
|
||||
inkscape:version="1.2.2 (b0a84865, 2022-12-01)"
|
||||
inkscape:export-filename="icon-archive.png"
|
||||
inkscape:export-xdpi="409.60001"
|
||||
inkscape:export-ydpi="409.60001"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs12" />
|
||||
<sodipodi:namedview
|
||||
id="namedview10"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="6.4597151"
|
||||
inkscape:cx="24.459283"
|
||||
inkscape:cy="32.509174"
|
||||
inkscape:window-width="1457"
|
||||
inkscape:window-height="860"
|
||||
inkscape:window-x="55"
|
||||
inkscape:window-y="38"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg8" />
|
||||
<g
|
||||
id="g846"
|
||||
transform="translate(0.558605,0.464417)">
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#808080;stroke-width:1.78186;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 38.749006,25.398867 V 38.843194 H 20.133784 V 25.398867"
|
||||
id="path847" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#808080;stroke-width:1.78186;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 18.065427,20.227972 h 22.751936 v 5.170894 H 18.065427 Z"
|
||||
id="path845" />
|
||||
<path
|
||||
style="fill:#ff0000;fill-opacity:1;stroke:#808080;stroke-width:1.78186;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 27.373036,29.535581 h 4.136718"
|
||||
id="line6" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
BIN
assets/icon-webxdc.png
Normal file
BIN
assets/icon-webxdc.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.9 KiB |
181
assets/icon-webxdc.svg
Normal file
181
assets/icon-webxdc.svg
Normal file
@@ -0,0 +1,181 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="80mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 80 297"
|
||||
version="1.1"
|
||||
id="svg71"
|
||||
inkscape:version="1.0.2 (e86c8708, 2021-01-15)"
|
||||
sodipodi:docname="icon-webxdc.svg"
|
||||
inkscape:export-filename="C:\Users\user\OneDrive - BFW-Leipzig\Documents\LogoDC\finalohnerand.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<metadata
|
||||
id="metadata856">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
id="namedview73"
|
||||
pagecolor="#767676"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:zoom="1.4142136"
|
||||
inkscape:cx="-90.271136"
|
||||
inkscape:cy="-1233.1209"
|
||||
inkscape:window-width="1864"
|
||||
inkscape:window-height="1027"
|
||||
inkscape:window-x="56"
|
||||
inkscape:window-y="25"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:snap-global="false"
|
||||
showguides="false"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:document-rotation="0"
|
||||
units="px">
|
||||
<sodipodi:guide
|
||||
position="-154.76097,641.11689"
|
||||
orientation="0,-1"
|
||||
id="guide21118" />
|
||||
<sodipodi:guide
|
||||
position="-60.286487,633.36619"
|
||||
orientation="0,-1"
|
||||
id="guide21120" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs68">
|
||||
<linearGradient
|
||||
id="linearGradient4375">
|
||||
<stop
|
||||
style="stop-color:#364e59;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4377" />
|
||||
<stop
|
||||
style="stop-color:#364e59;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop4379" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="fill:#1a1a1a;stroke:#000000;stroke-width:0.167903"
|
||||
id="rect880"
|
||||
width="79.8321"
|
||||
height="79.8321"
|
||||
x="-64.03286"
|
||||
y="-375.9097"
|
||||
ry="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3799-2"
|
||||
d="m -24.089585,-372.59579 c -19.986026,0.24336 -36.196903,16.666 -36.196903,36.67011 0,20.00409 16.210877,36.03233 36.196903,35.78912 19.0024236,-0.076 14.5340713,-10.6146 35.538854,-0.85693 -11.50627538,-17.97454 0.390097,-20.36737 0.658079,-35.81316 0,-20.00411 -16.2108788,-36.03235 -36.196911,-35.78914 z"
|
||||
style="fill:#364e59;fill-opacity:1;stroke:none;stroke-width:1.93355"
|
||||
sodipodi:nodetypes="sscccs" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -54.193871,-325.26419 Z"
|
||||
id="path3846" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -49.397951,-326.67773 Z"
|
||||
id="path3848" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m -49.397951,-326.67773 v 0 0"
|
||||
id="path3850" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.01;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m -51.35133,-325.0334 -7.964067,5.98895 z"
|
||||
id="path3965" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path11037"
|
||||
d="m -24.089585,-372.19891 c -19.986026,0.24156 -36.196903,16.54352 -36.196903,36.40062 0,7.86524 2.543315,15.1113 6.857155,20.97971 6.577146,8.94734 11.123515,9.77363 11.123515,9.77363 1.343237,1.78324 10.270932,4.3223 10.270932,4.3223 l 16.791727,-70.86654 -0.468369,-0.33457 c 0.458597,0.26445 0.428277,-0.27515 -8.378035,-0.27515 z"
|
||||
style="fill:#7cc5cc;fill-opacity:1;stroke:none;stroke-width:1.92643"
|
||||
sodipodi:nodetypes="sssccccss" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M -49.944239,-310.69957 Z"
|
||||
id="path13674" />
|
||||
<g
|
||||
id="g15178"
|
||||
transform="matrix(0.79975737,0,0,0.79975737,53.088959,-63.716396)">
|
||||
<rect
|
||||
style="fill:#364e59;fill-opacity:1;stroke-width:0.01;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="rect15072"
|
||||
width="29.897917"
|
||||
height="6.8791666"
|
||||
x="-334.4964"
|
||||
y="-154.51025"
|
||||
transform="rotate(45)" />
|
||||
<rect
|
||||
style="fill:#364e59;fill-opacity:1;stroke-width:0.01;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="rect15072-5"
|
||||
width="29.897917"
|
||||
height="6.8791666"
|
||||
x="147.63107"
|
||||
y="-334.4964"
|
||||
transform="rotate(-45)"
|
||||
inkscape:transform-center-x="-0.74835017"
|
||||
inkscape:transform-center-y="0.37417525" />
|
||||
</g>
|
||||
<g
|
||||
id="g22468"
|
||||
transform="translate(3.3033974)">
|
||||
<g
|
||||
id="g15178-0"
|
||||
transform="matrix(-0.79975737,0,0,0.79975737,-103.11028,-63.716404)"
|
||||
style="fill:#7cc5cc;fill-opacity:1">
|
||||
<rect
|
||||
style="fill:#7cc5cc;fill-opacity:1;stroke-width:0.01;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="rect15072-2"
|
||||
width="29.897917"
|
||||
height="6.8791666"
|
||||
x="-334.4964"
|
||||
y="-154.51025"
|
||||
transform="rotate(45)" />
|
||||
<rect
|
||||
style="fill:#7cc5cc;fill-opacity:1;stroke-width:0.01;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="rect15072-5-5"
|
||||
width="29.897917"
|
||||
height="6.8791666"
|
||||
x="147.63107"
|
||||
y="-334.4964"
|
||||
transform="rotate(-45)"
|
||||
inkscape:transform-center-x="-0.74835017"
|
||||
inkscape:transform-center-y="0.37417525" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.5 KiB |
10
assets/qrcode_logo_footer.svg
Normal file
10
assets/qrcode_logo_footer.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-weight:bold;font-size:24.4118px;line-height:1.25;font-family:sans-serif;fill:#aaaaaa;fill-opacity:1;stroke:none;stroke-width:0.915439"
|
||||
x="42.325161"
|
||||
y="23.32255"
|
||||
id="text72398">get.delta.chat</text>
|
||||
<path
|
||||
id="path84310"
|
||||
style="opacity:0.25;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.915439"
|
||||
d="M 17.13769,0.00129321 C 7.6753075,0.11650893 0,7.8915283 0,17.362467 c 0,9.47094 7.6753075,17.059745 17.13769,16.944599 8.99669,-0.03598 6.880074,-5.025654 16.824785,-0.405885 -5.447648,-8.510047 0.184241,-9.642482 0.311117,-16.955289 0,-9.4709395 -7.673512,-17.0597453 -17.135895,-16.94459879 z M 17.0769,4.9986797 c 1.84214,0 3.447355,0.253959 4.815003,0.7616693 1.381603,0.5076411 2.072253,1.207862 2.072253,2.0990711 0,0.4286855 -0.167495,0.7836052 -0.50242,1.0656242 -0.334921,0.2819844 -0.724544,0.4237724 -1.171121,0.4237724 -0.641952,0 -1.396532,-0.3909376 -2.261778,-1.169353 C 19.14963,7.3898036 18.402555,6.83791 17.788507,6.5220182 17.188416,6.1950547 16.484552,6.0321266 15.675129,6.0321266 c -1.032717,0 -1.883352,0.1854523 -2.553215,0.5578447 -0.655913,0.372254 -0.98517,0.8460916 -0.98517,1.4214436 0,0.5414792 0.272815,1.0495355 0.817093,1.5233385 0.544275,0.4738026 1.946291,1.3367446 4.207097,2.5889976 2.414319,1.342419 4.117377,2.390985 5.108232,3.146807 1.004795,0.755857 1.821505,1.675853 2.449514,2.758846 0.628002,1.082993 0.942253,2.227607 0.942253,3.434674 0,2.120834 -0.929555,3.993314 -2.785656,5.617786 -1.84214,1.613228 -3.99694,2.41915 -6.467082,2.41915 -2.246845,0 -4.145607,-0.647976 -5.694677,-1.945312 -1.5490699,-1.297336 -2.3225722,-3.028063 -2.3225722,-5.194049 0,-2.087031 0.8506345,-3.83094 2.5532182,-5.229825 1.716541,-1.398884 3.824203,-2.245599 6.322256,-2.538897 -0.697774,-0.631749 -1.668763,-1.387225 -2.910816,-2.267155 -1.367648,-0.970199 -2.287914,-1.73045 -2.762402,-2.283243 -0.474491,-0.5640381 -0.711618,-1.1795944 -0.711618,-1.8451814 0,-0.9927581 0.572093,-1.7710351 1.716451,-2.3351077 1.144362,-0.5753173 2.636724,-0.8635642 4.478865,-0.8635642 z m 1.110327,10.3738083 c -4.005262,0.5302 -6.007576,2.75279 -6.007576,6.667322 0,2.01932 0.49495,3.587291 1.485805,4.704157 1.004806,1.116832 2.169696,1.675299 3.495479,1.675299 1.381602,0 2.520072,-0.535632 3.413229,-1.60738 0.893168,-1.082959 1.339187,-2.545264 1.339187,-4.384079 0,-2.662348 -1.242022,-5.013441 -3.726124,-7.055319 z" />
|
||||
BIN
assets/root-certificates/letsencrypt/isrgrootx1.der
Normal file
BIN
assets/root-certificates/letsencrypt/isrgrootx1.der
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 121 KiB |
@@ -1,19 +1,20 @@
|
||||
use async_std::task::block_on;
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use deltachat::contact::Contact;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::stock_str::StockStrings;
|
||||
use deltachat::Events;
|
||||
use tempfile::tempdir;
|
||||
|
||||
async fn address_book_benchmark(n: u32, read_count: u32) {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let id = 100;
|
||||
let context = Context::new("FakeOS".into(), dbfile.into(), id)
|
||||
let context = Context::new(&dbfile, id, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let book = (0..n)
|
||||
.map(|i| format!("Name {}\naddr{}@example.org\n", i, i))
|
||||
.map(|i| format!("Name {i}\naddr{i}@example.org\n"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("");
|
||||
|
||||
@@ -26,12 +27,16 @@ async fn address_book_benchmark(n: u32, read_count: u32) {
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
c.bench_function("create 500 contacts", |b| {
|
||||
b.iter(|| block_on(async { address_book_benchmark(black_box(500), black_box(0)).await }))
|
||||
b.to_async(&rt)
|
||||
.iter(|| async { address_book_benchmark(black_box(500), black_box(0)).await })
|
||||
});
|
||||
|
||||
c.bench_function("create 100 contacts and read it 1000 times", |b| {
|
||||
b.iter(|| block_on(async { address_book_benchmark(black_box(100), black_box(1000)).await }))
|
||||
b.to_async(&rt)
|
||||
.iter(|| async { address_book_benchmark(black_box(100), black_box(1000)).await })
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use async_std::path::PathBuf;
|
||||
use async_std::task::block_on;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use deltachat::accounts::Accounts;
|
||||
use tempfile::tempdir;
|
||||
|
||||
async fn create_accounts(n: u32) {
|
||||
let dir = tempdir().unwrap();
|
||||
let p: PathBuf = dir.path().join("accounts").into();
|
||||
let p: PathBuf = dir.path().join("accounts");
|
||||
|
||||
let accounts = Accounts::new("my_os".into(), p.clone()).await.unwrap();
|
||||
let writable = true;
|
||||
let mut accounts = Accounts::new(p.clone(), writable).await.unwrap();
|
||||
|
||||
for expected_id in 2..n {
|
||||
let id = accounts.add_account().await.unwrap();
|
||||
@@ -18,7 +19,8 @@ async fn create_accounts(n: u32) {
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("create 1 account", |b| {
|
||||
b.iter(|| block_on(async { create_accounts(black_box(1)).await }))
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
b.to_async(&rt).iter(|| create_accounts(black_box(1)))
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
46
benches/get_chat_msgs.rs
Normal file
46
benches/get_chat_msgs.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use std::path::Path;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use deltachat::chat::{self, ChatId};
|
||||
use deltachat::chatlist::Chatlist;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::stock_str::StockStrings;
|
||||
use deltachat::Events;
|
||||
|
||||
async fn get_chat_msgs_benchmark(dbfile: &Path, chats: &[ChatId]) {
|
||||
let id = 100;
|
||||
let context = Context::new(dbfile, id, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for c in chats.iter().take(10) {
|
||||
black_box(chat::get_chat_msgs(&context, *c).await.ok());
|
||||
}
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
// To enable this benchmark, set `DELTACHAT_BENCHMARK_DATABASE` to some large database with many
|
||||
// messages, such as your primary account.
|
||||
if let Ok(path) = std::env::var("DELTACHAT_BENCHMARK_DATABASE") {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
let chats: Vec<_> = rt.block_on(async {
|
||||
let context = Context::new(Path::new(&path), 100, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap();
|
||||
let chatlist = Chatlist::try_load(&context, 0, None, None).await.unwrap();
|
||||
let len = chatlist.len();
|
||||
(0..len).map(|i| chatlist.get_chat_id(i).unwrap()).collect()
|
||||
});
|
||||
|
||||
c.bench_function("chat::get_chat_msgs (load messages from 10 chats)", |b| {
|
||||
b.to_async(&rt)
|
||||
.iter(|| get_chat_msgs_benchmark(black_box(path.as_ref()), black_box(&chats)))
|
||||
});
|
||||
} else {
|
||||
println!("env var not set: DELTACHAT_BENCHMARK_DATABASE");
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
33
benches/get_chatlist.rs
Normal file
33
benches/get_chatlist.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use std::path::Path;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use deltachat::chatlist::Chatlist;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::stock_str::StockStrings;
|
||||
use deltachat::Events;
|
||||
|
||||
async fn get_chat_list_benchmark(context: &Context) {
|
||||
Chatlist::try_load(context, 0, None, None).await.unwrap();
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
// To enable this benchmark, set `DELTACHAT_BENCHMARK_DATABASE` to some large database with many
|
||||
// messages, such as your primary account.
|
||||
if let Ok(path) = std::env::var("DELTACHAT_BENCHMARK_DATABASE") {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let context = rt.block_on(async {
|
||||
Context::new(Path::new(&path), 100, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap()
|
||||
});
|
||||
c.bench_function("chatlist:try_load (Get Chatlist)", |b| {
|
||||
b.to_async(&rt)
|
||||
.iter(|| get_chat_list_benchmark(black_box(&context)))
|
||||
});
|
||||
} else {
|
||||
println!("env var not set: DELTACHAT_BENCHMARK_DATABASE");
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
157
benches/receive_emails.rs
Normal file
157
benches/receive_emails.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use deltachat::{
|
||||
config::Config,
|
||||
context::Context,
|
||||
imex::{imex, ImexMode},
|
||||
receive_imf::receive_imf,
|
||||
stock_str::StockStrings,
|
||||
Events,
|
||||
};
|
||||
use tempfile::tempdir;
|
||||
|
||||
async fn recv_all_emails(context: Context) -> Context {
|
||||
for i in 0..100 {
|
||||
let imf_raw = format!(
|
||||
"Subject: Benchmark
|
||||
Message-ID: Mr.OssSYnOFkhR.{i}@testrun.org
|
||||
Date: Sat, 07 Dec 2019 19:00:27 +0000
|
||||
To: alice@example.com
|
||||
From: sender@testrun.org
|
||||
Chat-Version: 1.0
|
||||
Chat-Disposition-Notification-To: sender@testrun.org
|
||||
Chat-User-Avatar: 0
|
||||
In-Reply-To: Mr.OssSYnOFkhR.{i_dec}@testrun.org
|
||||
MIME-Version: 1.0
|
||||
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
Hello {i}",
|
||||
i = i,
|
||||
i_dec = i - 1,
|
||||
);
|
||||
receive_imf(&context, black_box(imf_raw.as_bytes()), false)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
context
|
||||
}
|
||||
|
||||
/// Receive 100 emails that remove charlie@example.com and add
|
||||
/// him back
|
||||
async fn recv_groupmembership_emails(context: Context) -> Context {
|
||||
for i in 0..50 {
|
||||
let imf_raw = format!(
|
||||
"Subject: Benchmark
|
||||
Message-ID: Gr.OssSYnOFkhR.{i}@testrun.org
|
||||
Date: Sat, 07 Dec 2019 19:00:27 +0000
|
||||
To: alice@example.com, b@example.com, c@example.com, d@example.com, e@example.com, f@example.com
|
||||
From: sender@testrun.org
|
||||
Chat-Version: 1.0
|
||||
Chat-Disposition-Notification-To: sender@testrun.org
|
||||
Chat-User-Avatar: 0
|
||||
Chat-Group-Member-Added: charlie@example.com
|
||||
In-Reply-To: Gr.OssSYnOFkhR.{i_dec}@testrun.org
|
||||
MIME-Version: 1.0
|
||||
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
Hello {i}",
|
||||
i = i,
|
||||
i_dec = i - 1,
|
||||
);
|
||||
receive_imf(&context, black_box(imf_raw.as_bytes()), false)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let imf_raw = format!(
|
||||
"Subject: Benchmark
|
||||
Message-ID: Gr.OssSYnOFkhR.{i}@testrun.org
|
||||
Date: Sat, 07 Dec 2019 19:00:27 +0000
|
||||
To: alice@example.com, b@example.com, c@example.com, d@example.com, e@example.com, f@example.com
|
||||
From: sender@testrun.org
|
||||
Chat-Version: 1.0
|
||||
Chat-Disposition-Notification-To: sender@testrun.org
|
||||
Chat-User-Avatar: 0
|
||||
Chat-Group-Member-Removed: charlie@example.com
|
||||
In-Reply-To: Gr.OssSYnOFkhR.{i_dec}@testrun.org
|
||||
MIME-Version: 1.0
|
||||
|
||||
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
|
||||
|
||||
Hello {i}",
|
||||
i = i,
|
||||
i_dec = i - 1,
|
||||
);
|
||||
receive_imf(&context, black_box(imf_raw.as_bytes()), false)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
context
|
||||
}
|
||||
|
||||
async fn create_context() -> Context {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let id = 100;
|
||||
let context = Context::new(dbfile.as_path(), id, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let backup: PathBuf = std::env::current_dir()
|
||||
.unwrap()
|
||||
.join("delta-chat-backup.tar");
|
||||
|
||||
if backup.exists() {
|
||||
println!("Importing backup");
|
||||
imex(&context, ImexMode::ImportBackup, backup.as_path(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let addr = "alice@example.com";
|
||||
context.set_config(Config::Addr, Some(addr)).await.unwrap();
|
||||
context
|
||||
.set_config(Config::ConfiguredAddr, Some(addr))
|
||||
.await
|
||||
.unwrap();
|
||||
context
|
||||
.set_config(Config::Configured, Some("1"))
|
||||
.await
|
||||
.unwrap();
|
||||
context
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("Receive messages");
|
||||
group.bench_function("Receive 100 simple text msgs", |b| {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let context = rt.block_on(create_context());
|
||||
|
||||
b.to_async(&rt).iter(|| {
|
||||
let ctx = context.clone();
|
||||
async move {
|
||||
recv_all_emails(black_box(ctx)).await;
|
||||
}
|
||||
});
|
||||
});
|
||||
group.bench_function(
|
||||
"Receive 100 Chat-Group-Member-{Added|Removed} messages",
|
||||
|b| {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let context = rt.block_on(create_context());
|
||||
|
||||
b.to_async(&rt).iter(|| {
|
||||
let ctx = context.clone();
|
||||
async move {
|
||||
recv_groupmembership_emails(black_box(ctx)).await;
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
@@ -1,12 +1,13 @@
|
||||
use async_std::task::block_on;
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use deltachat::context::Context;
|
||||
use std::path::Path;
|
||||
|
||||
async fn search_benchmark(path: impl AsRef<Path>) {
|
||||
let dbfile = path.as_ref();
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use deltachat::context::Context;
|
||||
use deltachat::stock_str::StockStrings;
|
||||
use deltachat::Events;
|
||||
|
||||
async fn search_benchmark(dbfile: impl AsRef<Path>) {
|
||||
let id = 100;
|
||||
let context = Context::new("FakeOS".into(), dbfile.into(), id)
|
||||
let context = Context::new(dbfile.as_ref(), id, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -19,9 +20,13 @@ fn criterion_benchmark(c: &mut Criterion) {
|
||||
// To enable this benchmark, set `DELTACHAT_BENCHMARK_DATABASE` to some large database with many
|
||||
// messages, such as your primary account.
|
||||
if let Ok(path) = std::env::var("DELTACHAT_BENCHMARK_DATABASE") {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
c.bench_function("search hello", |b| {
|
||||
b.iter(|| block_on(async { search_benchmark(black_box(&path)).await }))
|
||||
b.to_async(&rt).iter(|| search_benchmark(black_box(&path)))
|
||||
});
|
||||
} else {
|
||||
println!("env var not set: DELTACHAT_BENCHMARK_DATABASE");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
47
benches/send_events.rs
Normal file
47
benches/send_events.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
use deltachat::context::Context;
|
||||
use deltachat::stock_str::StockStrings;
|
||||
use deltachat::{info, Event, EventType, Events};
|
||||
use tempfile::tempdir;
|
||||
|
||||
async fn send_events_benchmark(context: &Context) {
|
||||
let emitter = context.get_event_emitter();
|
||||
for _i in 0..1_000_000 {
|
||||
info!(context, "interesting event...");
|
||||
}
|
||||
info!(context, "DONE");
|
||||
|
||||
loop {
|
||||
match emitter.recv().await.unwrap() {
|
||||
Event {
|
||||
typ: EventType::Info(info),
|
||||
..
|
||||
} if info.contains("DONE") => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
let context = rt.block_on(async {
|
||||
Context::new(&dbfile, 100, Events::new(), StockStrings::new())
|
||||
.await
|
||||
.expect("failed to create context")
|
||||
});
|
||||
let executor = tokio::runtime::Runtime::new().unwrap();
|
||||
|
||||
c.bench_function("Sending 1.000.000 events", |b| {
|
||||
b.to_async(&executor)
|
||||
.iter(|| send_events_benchmark(&context))
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
93
cliff.toml
Normal file
93
cliff.toml
Normal file
@@ -0,0 +1,93 @@
|
||||
# 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://keats.github.io/tera/docs/#templates
|
||||
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
|
||||
footer = """
|
||||
{% for release in releases -%}
|
||||
{% if release.version -%}
|
||||
{% if release.previous.version -%}
|
||||
[{{ release.version | trim_start_matches(pat="v") }}]: \
|
||||
https://github.com/deltachat/deltachat-core-rust\
|
||||
/compare/{{ release.previous.version }}..{{ release.version }}
|
||||
{% endif -%}
|
||||
{% else -%}
|
||||
[unreleased]: https://github.com/deltachat/deltachat-core-rust\
|
||||
/compare/{{ release.previous.version }}..HEAD
|
||||
{% endif -%}
|
||||
{% endfor %}
|
||||
"""
|
||||
@@ -1,8 +1,7 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.61.0"
|
||||
version = "1.137.2"
|
||||
description = "Deltachat FFI"
|
||||
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
license = "MPL-2.0"
|
||||
@@ -16,17 +15,20 @@ crate-type = ["cdylib", "staticlib"]
|
||||
|
||||
[dependencies]
|
||||
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"
|
||||
async-std = "1"
|
||||
tokio = { version = "1", features = ["rt-multi-thread"] }
|
||||
anyhow = "1"
|
||||
thiserror = "1"
|
||||
rand = "0.7"
|
||||
rand = "0.8"
|
||||
once_cell = "1.18.0"
|
||||
yerpc = { version = "0.5.1", features = ["anyhow_expose"] }
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
vendored = ["deltachat/vendored"]
|
||||
nightly = ["deltachat/nightly"]
|
||||
jsonrpc = ["dep:deltachat-jsonrpc"]
|
||||
|
||||
|
||||
@@ -846,7 +846,7 @@ EXCLUDE_PATTERNS =
|
||||
# exclude all test directories use the pattern */test/*
|
||||
|
||||
######################################################
|
||||
EXCLUDE_SYMBOLS = dc_aheader_t dc_apeerstate_t dc_e2ee_helper_t dc_imap_t dc_job*_t dc_key_t dc_keyring_t dc_loginparam_t dc_mime*_t
|
||||
EXCLUDE_SYMBOLS = dc_aheader_t dc_apeerstate_t dc_e2ee_helper_t dc_imap_t dc_job*_t dc_key_t dc_loginparam_t dc_mime*_t
|
||||
EXCLUDE_SYMBOLS += dc_saxparser_t dc_simplify_t dc_smtp_t dc_sqlite3_t dc_strbuilder_t dc_param_t dc_hash_t dc_hashelem_t
|
||||
EXCLUDE_SYMBOLS += _dc_* jsmn*
|
||||
######################################################
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<tab type="hierarchy" visible="no" title="" intro=""/>
|
||||
<tab type="classmembers" visible="no" title="" intro=""/>
|
||||
</tab>
|
||||
<tab type="modules" visible="yes" title="Constants" intro="Here is a list of constants:"/>
|
||||
<tab type="topics" visible="yes" title="Constants" intro="Here is a list of constants:"/>
|
||||
<tab type="pages" visible="yes" title="" intro=""/>
|
||||
<tab type="namespaces" visible="yes" title="">
|
||||
<tab type="namespacelist" visible="yes" title="" intro=""/>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Delta Chat C Interface
|
||||
|
||||
## Installation
|
||||
|
||||
see `Installing libdeltachat system wide` in [../README.md](../README.md)
|
||||
|
||||
## Documentation
|
||||
|
||||
To generate the C Interface documentation,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
|
||||
@@ -28,8 +27,9 @@ fn main() {
|
||||
);
|
||||
|
||||
fs::create_dir_all(target_path.join("pkgconfig")).unwrap();
|
||||
fs::File::create(target_path.join("pkgconfig").join("deltachat.pc"))
|
||||
.unwrap()
|
||||
.write_all(pkg_config.as_bytes())
|
||||
.unwrap();
|
||||
fs::write(
|
||||
target_path.join("pkgconfig").join("deltachat.pc"),
|
||||
pkg_config.as_bytes(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
use crate::chat::ChatItem;
|
||||
use crate::constants::{DC_MSG_ID_DAYMARKER, DC_MSG_ID_MARKER1};
|
||||
use crate::constants::DC_MSG_ID_DAYMARKER;
|
||||
use crate::contact::ContactId;
|
||||
use crate::location::Location;
|
||||
use crate::message::MsgId;
|
||||
|
||||
@@ -7,6 +8,7 @@ use crate::message::MsgId;
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum dc_array_t {
|
||||
MsgIds(Vec<MsgId>),
|
||||
ContactIds(Vec<ContactId>),
|
||||
Chat(Vec<ChatItem>),
|
||||
Locations(Vec<Location>),
|
||||
Uint(Vec<u32>),
|
||||
@@ -16,9 +18,9 @@ impl dc_array_t {
|
||||
pub(crate) fn get_id(&self, index: usize) -> u32 {
|
||||
match self {
|
||||
Self::MsgIds(array) => array[index].to_u32(),
|
||||
Self::ContactIds(array) => array[index].to_u32(),
|
||||
Self::Chat(array) => match array[index] {
|
||||
ChatItem::Message { msg_id } => msg_id.to_u32(),
|
||||
ChatItem::Marker1 => DC_MSG_ID_MARKER1,
|
||||
ChatItem::DayMarker { .. } => DC_MSG_ID_DAYMARKER,
|
||||
},
|
||||
Self::Locations(array) => array[index].location_id,
|
||||
@@ -29,9 +31,9 @@ impl dc_array_t {
|
||||
pub(crate) fn get_timestamp(&self, index: usize) -> Option<i64> {
|
||||
match self {
|
||||
Self::MsgIds(_) => None,
|
||||
Self::ContactIds(_) => None,
|
||||
Self::Chat(array) => array.get(index).and_then(|item| match item {
|
||||
ChatItem::Message { .. } => None,
|
||||
ChatItem::Marker1 { .. } => None,
|
||||
ChatItem::DayMarker { timestamp } => Some(*timestamp),
|
||||
}),
|
||||
Self::Locations(array) => array.get(index).map(|location| location.timestamp),
|
||||
@@ -42,6 +44,7 @@ impl dc_array_t {
|
||||
pub(crate) fn get_marker(&self, index: usize) -> Option<&str> {
|
||||
match self {
|
||||
Self::MsgIds(_) => None,
|
||||
Self::ContactIds(_) => None,
|
||||
Self::Chat(_) => None,
|
||||
Self::Locations(array) => array
|
||||
.get(index)
|
||||
@@ -62,6 +65,7 @@ impl dc_array_t {
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
match self {
|
||||
Self::MsgIds(array) => array.len(),
|
||||
Self::ContactIds(array) => array.len(),
|
||||
Self::Chat(array) => array.len(),
|
||||
Self::Locations(array) => array.len(),
|
||||
Self::Uint(array) => array.len(),
|
||||
@@ -85,6 +89,12 @@ impl From<Vec<MsgId>> for dc_array_t {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ContactId>> for dc_array_t {
|
||||
fn from(array: Vec<ContactId>) -> Self {
|
||||
dc_array_t::ContactIds(array)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ChatItem>> for dc_array_t {
|
||||
fn from(array: Vec<ChatItem>) -> Self {
|
||||
dc_array_t::Chat(array)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
255
deltachat-ffi/src/lot.rs
Normal file
255
deltachat-ffi/src/lot.rs
Normal file
@@ -0,0 +1,255 @@
|
||||
//! # Legacy generic return values for C API.
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::Error;
|
||||
|
||||
use crate::message::MessageState;
|
||||
use crate::qr::Qr;
|
||||
use crate::summary::{Summary, SummaryPrefix};
|
||||
|
||||
/// An object containing a set of values.
|
||||
/// The meaning of the values is defined by the function returning the object.
|
||||
/// Lot objects are created
|
||||
/// 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),
|
||||
Qr(Qr),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Meaning {
|
||||
#[default]
|
||||
None = 0,
|
||||
Text1Draft = 1,
|
||||
Text1Username = 2,
|
||||
Text1Self = 3,
|
||||
}
|
||||
|
||||
impl Lot {
|
||||
pub fn get_text1(&self) -> Option<&str> {
|
||||
match self {
|
||||
Self::Summary(summary) => match &summary.prefix {
|
||||
None => None,
|
||||
Some(SummaryPrefix::Draft(text)) => Some(text),
|
||||
Some(SummaryPrefix::Username(username)) => Some(username),
|
||||
Some(SummaryPrefix::Me(text)) => Some(text),
|
||||
},
|
||||
Self::Qr(qr) => match qr {
|
||||
Qr::AskVerifyContact { .. } => None,
|
||||
Qr::AskVerifyGroup { grpname, .. } => Some(grpname),
|
||||
Qr::FprOk { .. } => None,
|
||||
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),
|
||||
Qr::Text { text } => Some(text),
|
||||
Qr::WithdrawVerifyContact { .. } => None,
|
||||
Qr::WithdrawVerifyGroup { grpname, .. } => Some(grpname),
|
||||
Qr::ReviveVerifyContact { .. } => None,
|
||||
Qr::ReviveVerifyGroup { grpname, .. } => Some(grpname),
|
||||
Qr::Login { address, .. } => Some(address),
|
||||
},
|
||||
Self::Error(err) => Some(err),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_text2(&self) -> Option<Cow<str>> {
|
||||
match self {
|
||||
Self::Summary(summary) => Some(summary.truncated_text(160)),
|
||||
Self::Qr(_) => None,
|
||||
Self::Error(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_text1_meaning(&self) -> Meaning {
|
||||
match self {
|
||||
Self::Summary(summary) => match &summary.prefix {
|
||||
None => Meaning::None,
|
||||
Some(SummaryPrefix::Draft(_text)) => Meaning::Text1Draft,
|
||||
Some(SummaryPrefix::Username(_username)) => Meaning::Text1Username,
|
||||
Some(SummaryPrefix::Me(_text)) => Meaning::Text1Self,
|
||||
},
|
||||
Self::Qr(qr) => match qr {
|
||||
Qr::Addr {
|
||||
draft: Some(_draft),
|
||||
..
|
||||
} => Meaning::Text1Draft,
|
||||
_ => Meaning::None,
|
||||
},
|
||||
Self::Error(_err) => Meaning::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_state(&self) -> LotState {
|
||||
match self {
|
||||
Self::Summary(summary) => summary.state.into(),
|
||||
Self::Qr(qr) => match qr {
|
||||
Qr::AskVerifyContact { .. } => LotState::QrAskVerifyContact,
|
||||
Qr::AskVerifyGroup { .. } => LotState::QrAskVerifyGroup,
|
||||
Qr::FprOk { .. } => LotState::QrFprOk,
|
||||
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,
|
||||
Qr::Text { .. } => LotState::QrText,
|
||||
Qr::WithdrawVerifyContact { .. } => LotState::QrWithdrawVerifyContact,
|
||||
Qr::WithdrawVerifyGroup { .. } => LotState::QrWithdrawVerifyGroup,
|
||||
Qr::ReviveVerifyContact { .. } => LotState::QrReviveVerifyContact,
|
||||
Qr::ReviveVerifyGroup { .. } => LotState::QrReviveVerifyGroup,
|
||||
Qr::Login { .. } => LotState::QrLogin,
|
||||
},
|
||||
Self::Error(_err) => LotState::QrError,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> u32 {
|
||||
match self {
|
||||
Self::Summary(_) => Default::default(),
|
||||
Self::Qr(qr) => match qr {
|
||||
Qr::AskVerifyContact { contact_id, .. } => contact_id.to_u32(),
|
||||
Qr::AskVerifyGroup { .. } => Default::default(),
|
||||
Qr::FprOk { contact_id } => contact_id.to_u32(),
|
||||
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(),
|
||||
Qr::Text { .. } => Default::default(),
|
||||
Qr::WithdrawVerifyContact { contact_id, .. } => contact_id.to_u32(),
|
||||
Qr::WithdrawVerifyGroup { .. } => Default::default(),
|
||||
Qr::ReviveVerifyContact { contact_id, .. } => contact_id.to_u32(),
|
||||
Qr::ReviveVerifyGroup { .. } => Default::default(),
|
||||
Qr::Login { .. } => Default::default(),
|
||||
},
|
||||
Self::Error(_) => Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_timestamp(&self) -> i64 {
|
||||
match self {
|
||||
Self::Summary(summary) => summary.timestamp,
|
||||
Self::Qr(_) => Default::default(),
|
||||
Self::Error(_) => Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LotState {
|
||||
#[default]
|
||||
Undefined = 0,
|
||||
|
||||
// Qr States
|
||||
/// id=contact
|
||||
QrAskVerifyContact = 200,
|
||||
|
||||
/// text1=groupname
|
||||
QrAskVerifyGroup = 202,
|
||||
|
||||
/// id=contact
|
||||
QrFprOk = 210,
|
||||
|
||||
/// id=contact
|
||||
QrFprMismatch = 220,
|
||||
|
||||
/// text1=formatted fingerprint
|
||||
QrFprWithoutAddr = 230,
|
||||
|
||||
/// text1=domain
|
||||
QrAccount = 250,
|
||||
|
||||
QrBackup = 251,
|
||||
|
||||
/// text1=domain, text2=instance pattern
|
||||
QrWebrtcInstance = 260,
|
||||
|
||||
/// id=contact
|
||||
QrAddr = 320,
|
||||
|
||||
/// text1=text
|
||||
QrText = 330,
|
||||
|
||||
/// text1=URL
|
||||
QrUrl = 332,
|
||||
|
||||
/// text1=error string
|
||||
QrError = 400,
|
||||
|
||||
QrWithdrawVerifyContact = 500,
|
||||
|
||||
/// text1=groupname
|
||||
QrWithdrawVerifyGroup = 502,
|
||||
|
||||
QrReviveVerifyContact = 510,
|
||||
|
||||
/// text1=groupname
|
||||
QrReviveVerifyGroup = 512,
|
||||
|
||||
/// text1=email_address
|
||||
QrLogin = 520,
|
||||
|
||||
// Message States
|
||||
MsgInFresh = 10,
|
||||
MsgInNoticed = 13,
|
||||
MsgInSeen = 16,
|
||||
MsgOutPreparing = 18,
|
||||
MsgOutDraft = 19,
|
||||
MsgOutPending = 20,
|
||||
MsgOutFailed = 24,
|
||||
MsgOutDelivered = 26,
|
||||
MsgOutMdnRcvd = 28,
|
||||
}
|
||||
|
||||
impl From<MessageState> for LotState {
|
||||
fn from(s: MessageState) -> Self {
|
||||
use MessageState::*;
|
||||
match s {
|
||||
Undefined => LotState::Undefined,
|
||||
InFresh => LotState::MsgInFresh,
|
||||
InNoticed => LotState::MsgInNoticed,
|
||||
InSeen => LotState::MsgInSeen,
|
||||
OutPreparing => LotState::MsgOutPreparing,
|
||||
OutDraft => LotState::MsgOutDraft,
|
||||
OutPending => LotState::MsgOutPending,
|
||||
OutFailed => LotState::MsgOutFailed,
|
||||
OutDelivered => LotState::MsgOutDelivered,
|
||||
OutMdnRcvd => LotState::MsgOutMdnRcvd,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Summary> for Lot {
|
||||
fn from(summary: Summary) -> Self {
|
||||
Lot::Summary(summary)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Qr> for Lot {
|
||||
fn from(qr: Qr) -> Self {
|
||||
Lot::Qr(qr)
|
||||
}
|
||||
}
|
||||
|
||||
// Make it easy to convert errors into the final `Lot`.
|
||||
impl From<Error> for Lot {
|
||||
fn from(error: Error) -> Self {
|
||||
Lot::Error(error.to_string())
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ pub(crate) enum CStringError {
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use deltachat::dc_tools::{dc_strdup, OsStrExt};
|
||||
/// use deltachat::tools::{dc_strdup, OsStrExt};
|
||||
/// let path = std::path::Path::new("/some/path");
|
||||
/// let path_c = path.to_c_string().unwrap();
|
||||
/// unsafe {
|
||||
@@ -287,9 +287,10 @@ fn as_path_unicode<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use libc::{free, strcmp};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_os_str_to_c_string_cwd() {
|
||||
let some_dir = std::env::current_dir().unwrap();
|
||||
|
||||
4
deltachat-jsonrpc/.gitignore
vendored
Normal file
4
deltachat-jsonrpc/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
openrpc/openrpc.json
|
||||
accounts/
|
||||
|
||||
.cargo
|
||||
44
deltachat-jsonrpc/Cargo.toml
Normal file
44
deltachat-jsonrpc/Cargo.toml
Normal file
@@ -0,0 +1,44 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "1.137.2"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
edition = "2021"
|
||||
default-run = "deltachat-jsonrpc-server"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/deltachat/deltachat-core-rust"
|
||||
|
||||
[[bin]]
|
||||
name = "deltachat-jsonrpc-server"
|
||||
path = "src/webserver.rs"
|
||||
required-features = ["webserver"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
deltachat = { path = ".." }
|
||||
num-traits = "0.2"
|
||||
schemars = "0.8.13"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tempfile = "3.10.1"
|
||||
log = "0.4"
|
||||
async-channel = { version = "2.0.0" }
|
||||
futures = { version = "0.3.30" }
|
||||
serde_json = "1"
|
||||
yerpc = { version = "0.5.2", features = ["anyhow_expose", "openrpc"] }
|
||||
typescript-type-def = { version = "0.5.8", features = ["json_value"] }
|
||||
tokio = { version = "1.37.0" }
|
||||
sanitize-filename = "0.5"
|
||||
walkdir = "2.5.0"
|
||||
base64 = "0.21"
|
||||
|
||||
# optional dependencies
|
||||
axum = { version = "0.7", optional = true, features = ["ws"] }
|
||||
env_logger = { version = "0.10.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.37.0", features = ["full", "rt-multi-thread"] }
|
||||
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
webserver = ["dep:env_logger", "dep:axum", "tokio/full", "yerpc/support-axum"]
|
||||
vendored = ["deltachat/vendored"]
|
||||
123
deltachat-jsonrpc/README.md
Normal file
123
deltachat-jsonrpc/README.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# deltachat-jsonrpc
|
||||
|
||||
This crate provides a [JSON-RPC 2.0](https://www.jsonrpc.org/specification) interface to DeltaChat.
|
||||
|
||||
The JSON-RPC API is exposed in two fashions:
|
||||
|
||||
* A executable that exposes the JSON-RPC API through a [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) server running on localhost.
|
||||
* The JSON-RPC API can also be called through the [C FFI](../deltachat-ffi). The C FFI needs to be built with the `jsonrpc` feature. It will then expose the functions `dc_jsonrpc_init`, `dc_jsonrpc_request`, `dc_jsonrpc_next_response` and `dc_jsonrpc_unref`. See the docs in the [header file](../deltachat-ffi/deltachat.h) for details.
|
||||
|
||||
We also include a JavaScript and TypeScript client for the JSON-RPC API. The source for this is in the [`typescript`](typescript) folder. The client can easily be used with the WebSocket server to build DeltaChat apps for web browsers or Node.js. See the [examples](typescript/example) for details.
|
||||
|
||||
## Usage
|
||||
|
||||
#### Running the WebSocket server
|
||||
|
||||
From within this folder, you can start the WebSocket server with the following command:
|
||||
|
||||
```sh
|
||||
cargo run --features webserver
|
||||
```
|
||||
|
||||
If you want to use the server in a production setup, first build it in release mode:
|
||||
|
||||
```sh
|
||||
cargo build --features webserver --release
|
||||
```
|
||||
You will then find the `deltachat-jsonrpc-server` executable in your `target/release` folder.
|
||||
|
||||
The executable currently does not support any command-line arguments. By default, once started it will accept WebSocket connections on `ws://localhost:20808/ws`. It will store the persistent configuration and databases in a `./accounts` folder relative to the directory from where it is started.
|
||||
|
||||
The server can be configured with environment variables:
|
||||
|
||||
|variable|default|description|
|
||||
|-|-|-|
|
||||
|`DC_PORT`|`20808`|port to listen on|
|
||||
|`DC_ACCOUNTS_PATH`|`./accounts`|path to storage directory|
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
#### Using the TypeScript/JavaScript client
|
||||
|
||||
The package includes a JavaScript/TypeScript client which is partially auto-generated through the JSON-RPC library used by this crate ([yerpc](https://github.com/Frando/yerpc/)). Find the source in the [`typescript`](typescript) folder.
|
||||
|
||||
To use it locally, first install the dependencies and compile the TypeScript code to JavaScript:
|
||||
```sh
|
||||
cd typescript
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
The JavaScript client is not yet published on NPM (but will likely be soon). Currently, it is recommended to vendor the bundled build. After running `npm run build` as documented above, there will be a file `dist/deltachat.bundle.js`. This is an ESM module containing all dependencies. Copy this file to your project and import the DeltaChat class.
|
||||
|
||||
|
||||
```typescript
|
||||
import { DeltaChat } from './deltachat.bundle.js'
|
||||
const dc = new DeltaChat('ws://localhost:20808/ws')
|
||||
const accounts = await dc.rpc.getAllAccounts()
|
||||
console.log('accounts', accounts)
|
||||
```
|
||||
|
||||
A script is included to build autogenerated documentation, which includes all RPC methods:
|
||||
```sh
|
||||
cd typescript
|
||||
npm run docs
|
||||
```
|
||||
Then open the [`typescript/docs`](typescript/docs) folder in a web browser.
|
||||
|
||||
## Development
|
||||
|
||||
#### Running the example app
|
||||
|
||||
We include a small demo web application that talks to the WebSocket server. It can be used for testing. Feel invited to expand this.
|
||||
|
||||
```sh
|
||||
cd typescript
|
||||
npm run build
|
||||
npm run example:build
|
||||
npm run example:start
|
||||
```
|
||||
Then, open [`http://localhost:8080/example.html`](http://localhost:8080/example.html) in a web browser.
|
||||
|
||||
Run `npm run example:dev` to live-rebuild the example app when files changes.
|
||||
|
||||
### Testing
|
||||
|
||||
The crate includes both a basic Rust smoke test and more featureful integration tests that use the TypeScript client.
|
||||
|
||||
#### Rust tests
|
||||
|
||||
To run the Rust test, use this command:
|
||||
|
||||
```
|
||||
cargo test
|
||||
```
|
||||
|
||||
#### TypeScript tests
|
||||
|
||||
```
|
||||
cd typescript
|
||||
npm run test
|
||||
```
|
||||
|
||||
This will build the `deltachat-jsonrpc-server` binary and then run a test suite against the WebSocket server.
|
||||
|
||||
The test suite includes some tests that need online connectivity and a way to create test email accounts. To run these tests, talk to DeltaChat developers to get a token for the `testrun.org` service, or use a local instance of [`mailadm`](https://github.com/deltachat/docker-mailadm).
|
||||
|
||||
Then, set the `CHATMAIL_DOMAIN` environment variable to your testing email server domain.
|
||||
|
||||
```
|
||||
CHATMAIL_DOMAIN=chat.example.org npm run test
|
||||
```
|
||||
|
||||
#### Test Coverage
|
||||
|
||||
Running `npm run test` will report test coverage. For the coverage to be accurate the online tests need to be run.
|
||||
|
||||
> If you are offline and want to see the coverage results anyway (even though they are inaccurate), you can bypass the errors of the online tests by setting the `COVERAGE_OFFLINE=1` environment variable.
|
||||
|
||||
A summary of the coverage will be reported in the terminal after the test run. Open `coverage/index.html` in a web browser for a detailed report.
|
||||
28
deltachat-jsonrpc/TODO.md
Normal file
28
deltachat-jsonrpc/TODO.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# TODO
|
||||
|
||||
- [ ] different test type to simulate two devices: to test autocrypt_initiate_key_transfer & autocrypt_continue_key_transfer
|
||||
|
||||
## MVP - Websocket server&client
|
||||
|
||||
For kaiOS and other experiments, like a deltachat "web" over network from an android phone.
|
||||
|
||||
- [ ] coverage for a majority of the API
|
||||
- [ ] Blobs served
|
||||
- [ ] Blob upload (for attachments, setting profile-picture, importing backup and so on)
|
||||
- [ ] other way blobs can be addressed when using websocket vs. jsonrpc over dc-node
|
||||
- [ ] Web push API? At least some kind of notification hook closure this lib can accept.
|
||||
|
||||
### Other Ideas for the Websocket server
|
||||
|
||||
- [ ] make sure there can only be one connection at a time to the ws
|
||||
- why? , it could give problems if its commanded from multiple connections
|
||||
- [ ] encrypted connection?
|
||||
- [ ] authenticated connection?
|
||||
- [ ] Look into unit-testing for the proc macros?
|
||||
- [ ] proc macro taking over doc comments to generated typescript file
|
||||
|
||||
## Desktop Apis
|
||||
|
||||
Incomplete todo for desktop api porting, just some remainders for points that might need more work:
|
||||
|
||||
- [ ] manual start/stop io functions in the api for context and accounts, so "not syncing all accounts" can still be done in desktop -> webserver should then not do start io on all accounts by default
|
||||
2155
deltachat-jsonrpc/src/api.rs
Normal file
2155
deltachat-jsonrpc/src/api.rs
Normal file
File diff suppressed because it is too large
Load Diff
45
deltachat-jsonrpc/src/api/types/account.rs
Normal file
45
deltachat-jsonrpc/src/api/types/account.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use anyhow::Result;
|
||||
use deltachat::config::Config;
|
||||
use deltachat::contact::{Contact, ContactId};
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::color_int_to_hex_string;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum Account {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Configured {
|
||||
id: u32,
|
||||
display_name: Option<String>,
|
||||
addr: Option<String>,
|
||||
// size: u32,
|
||||
profile_image: Option<String>, // TODO: This needs to be converted to work with blob http server.
|
||||
color: String,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Unconfigured { id: u32 },
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub async fn from_context(ctx: &deltachat::context::Context, id: u32) -> Result<Self> {
|
||||
if ctx.is_configured().await? {
|
||||
let display_name = ctx.get_config(Config::Displayname).await?;
|
||||
let addr = ctx.get_config(Config::Addr).await?;
|
||||
let profile_image = ctx.get_config(Config::Selfavatar).await?;
|
||||
let color = color_int_to_hex_string(
|
||||
Contact::get_by_id(ctx, ContactId::SELF).await?.get_color(),
|
||||
);
|
||||
Ok(Account::Configured {
|
||||
id,
|
||||
display_name,
|
||||
addr,
|
||||
profile_image,
|
||||
color,
|
||||
})
|
||||
} else {
|
||||
Ok(Account::Unconfigured { id })
|
||||
}
|
||||
}
|
||||
}
|
||||
237
deltachat-jsonrpc/src/api/types/chat.rs
Normal file
237
deltachat-jsonrpc/src/api/types/chat.rs
Normal file
@@ -0,0 +1,237 @@
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use deltachat::chat::{self, get_chat_contacts, ChatVisibility};
|
||||
use deltachat::chat::{Chat, ChatId};
|
||||
use deltachat::constants::Chattype;
|
||||
use deltachat::contact::{Contact, ContactId};
|
||||
use deltachat::context::Context;
|
||||
use num_traits::cast::ToPrimitive;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::color_int_to_hex_string;
|
||||
use super::contact::ContactObject;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FullChat {
|
||||
id: u32,
|
||||
name: String,
|
||||
|
||||
/// True if the chat is protected.
|
||||
///
|
||||
/// UI should display a green checkmark
|
||||
/// in the chat title,
|
||||
/// in the chat profile title and
|
||||
/// in the chatlist item
|
||||
/// if chat protection is enabled.
|
||||
/// UI should also display a green checkmark
|
||||
/// in the contact profile
|
||||
/// if 1:1 chat with this contact exists and is protected.
|
||||
is_protected: bool,
|
||||
profile_image: Option<String>, //BLOBS ?
|
||||
archived: bool,
|
||||
// subtitle - will be moved to frontend because it uses translation functions
|
||||
chat_type: u32,
|
||||
is_unpromoted: bool,
|
||||
is_self_talk: bool,
|
||||
contacts: Vec<ContactObject>,
|
||||
contact_ids: Vec<u32>,
|
||||
color: String,
|
||||
fresh_message_counter: usize,
|
||||
// is_group - please check over chat.type in frontend instead
|
||||
is_contact_request: bool,
|
||||
is_protection_broken: bool,
|
||||
is_device_chat: bool,
|
||||
self_in_group: bool,
|
||||
is_muted: bool,
|
||||
ephemeral_timer: u32, //TODO look if there are more important properties in newer core versions
|
||||
can_send: bool,
|
||||
was_seen_recently: bool,
|
||||
mailing_list_address: Option<String>,
|
||||
}
|
||||
|
||||
impl FullChat {
|
||||
pub async fn try_from_dc_chat_id(context: &Context, chat_id: u32) -> Result<Self> {
|
||||
let rust_chat_id = ChatId::new(chat_id);
|
||||
let chat = Chat::load_from_db(context, rust_chat_id).await?;
|
||||
|
||||
let contact_ids = get_chat_contacts(context, rust_chat_id).await?;
|
||||
|
||||
let mut contacts = Vec::with_capacity(contact_ids.len());
|
||||
|
||||
for contact_id in &contact_ids {
|
||||
contacts.push(
|
||||
ContactObject::try_from_dc_contact(
|
||||
context,
|
||||
Contact::get_by_id(context, *contact_id)
|
||||
.await
|
||||
.context("failed to load contact")?,
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
|
||||
let profile_image = match chat.get_profile_image(context).await? {
|
||||
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let color = color_int_to_hex_string(chat.get_color(context).await?);
|
||||
let fresh_message_counter = rust_chat_id.get_fresh_msg_cnt(context).await?;
|
||||
let ephemeral_timer = rust_chat_id.get_ephemeral_timer(context).await?.to_u32();
|
||||
|
||||
let can_send = chat.can_send(context).await?;
|
||||
|
||||
let was_seen_recently = if chat.get_type() == Chattype::Single {
|
||||
match contact_ids.first() {
|
||||
Some(contact) => Contact::get_by_id(context, *contact)
|
||||
.await
|
||||
.context("failed to load contact for was_seen_recently")?
|
||||
.was_seen_recently(),
|
||||
None => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let mailing_list_address = chat.get_mailinglist_addr().map(|s| s.to_string());
|
||||
|
||||
Ok(FullChat {
|
||||
id: chat_id,
|
||||
name: chat.name.clone(),
|
||||
is_protected: chat.is_protected(),
|
||||
profile_image, //BLOBS ?
|
||||
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
|
||||
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,
|
||||
contact_ids: contact_ids.iter().map(|id| id.to_u32()).collect(),
|
||||
color,
|
||||
fresh_message_counter,
|
||||
is_contact_request: chat.is_contact_request(),
|
||||
is_protection_broken: chat.is_protection_broken(),
|
||||
is_device_chat: chat.is_device_talk(),
|
||||
self_in_group: contact_ids.contains(&ContactId::SELF),
|
||||
is_muted: chat.is_muted(),
|
||||
ephemeral_timer,
|
||||
can_send,
|
||||
was_seen_recently,
|
||||
mailing_list_address,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// cheaper version of fullchat, omits:
|
||||
/// - contacts
|
||||
/// - contact_ids
|
||||
/// - fresh_message_counter
|
||||
/// - ephemeral_timer
|
||||
/// - self_in_group
|
||||
/// - was_seen_recently
|
||||
/// - can_send
|
||||
///
|
||||
/// used when you only need the basic metadata of a chat like type, name, profile picture
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BasicChat {
|
||||
id: u32,
|
||||
name: String,
|
||||
|
||||
/// True if the chat is protected.
|
||||
///
|
||||
/// UI should display a green checkmark
|
||||
/// in the chat title,
|
||||
/// in the chat profile title and
|
||||
/// in the chatlist item
|
||||
/// if chat protection is enabled.
|
||||
/// UI should also display a green checkmark
|
||||
/// in the contact profile
|
||||
/// if 1:1 chat with this contact exists and is protected.
|
||||
is_protected: bool,
|
||||
profile_image: Option<String>, //BLOBS ?
|
||||
archived: bool,
|
||||
chat_type: u32,
|
||||
is_unpromoted: bool,
|
||||
is_self_talk: bool,
|
||||
color: String,
|
||||
is_contact_request: bool,
|
||||
is_protection_broken: bool,
|
||||
is_device_chat: bool,
|
||||
is_muted: bool,
|
||||
}
|
||||
|
||||
impl BasicChat {
|
||||
pub async fn try_from_dc_chat_id(context: &Context, chat_id: u32) -> Result<Self> {
|
||||
let rust_chat_id = ChatId::new(chat_id);
|
||||
let chat = Chat::load_from_db(context, rust_chat_id).await?;
|
||||
|
||||
let profile_image = match chat.get_profile_image(context).await? {
|
||||
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
||||
None => None,
|
||||
};
|
||||
let color = color_int_to_hex_string(chat.get_color(context).await?);
|
||||
|
||||
Ok(BasicChat {
|
||||
id: chat_id,
|
||||
name: chat.name.clone(),
|
||||
is_protected: chat.is_protected(),
|
||||
profile_image, //BLOBS ?
|
||||
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
|
||||
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,
|
||||
is_contact_request: chat.is_contact_request(),
|
||||
is_protection_broken: chat.is_protection_broken(),
|
||||
is_device_chat: chat.is_device_talk(),
|
||||
is_muted: chat.is_muted(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum MuteDuration {
|
||||
NotMuted,
|
||||
Forever,
|
||||
Until { duration: i64 },
|
||||
}
|
||||
|
||||
impl MuteDuration {
|
||||
pub fn try_into_core_type(self) -> Result<chat::MuteDuration> {
|
||||
match self {
|
||||
MuteDuration::NotMuted => Ok(chat::MuteDuration::NotMuted),
|
||||
MuteDuration::Forever => Ok(chat::MuteDuration::Forever),
|
||||
MuteDuration::Until { duration } => {
|
||||
if duration <= 0 {
|
||||
bail!("failed to read mute duration")
|
||||
}
|
||||
|
||||
Ok(SystemTime::now()
|
||||
.checked_add(Duration::from_secs(duration as u64))
|
||||
.map_or(chat::MuteDuration::Forever, chat::MuteDuration::Until))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "ChatVisibility")]
|
||||
pub enum JSONRPCChatVisibility {
|
||||
Normal,
|
||||
Archived,
|
||||
Pinned,
|
||||
}
|
||||
|
||||
impl JSONRPCChatVisibility {
|
||||
pub fn into_core_type(self) -> ChatVisibility {
|
||||
match self {
|
||||
JSONRPCChatVisibility::Normal => ChatVisibility::Normal,
|
||||
JSONRPCChatVisibility::Archived => ChatVisibility::Archived,
|
||||
JSONRPCChatVisibility::Pinned => ChatVisibility::Pinned,
|
||||
}
|
||||
}
|
||||
}
|
||||
150
deltachat-jsonrpc/src/api/types/chat_list.rs
Normal file
150
deltachat-jsonrpc/src/api/types/chat_list.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
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 num_traits::cast::ToPrimitive;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::color_int_to_hex_string;
|
||||
use super::message::MessageViewtype;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum ChatListItemFetchResult {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ChatListItem {
|
||||
id: u32,
|
||||
name: String,
|
||||
avatar_path: Option<String>,
|
||||
color: String,
|
||||
last_updated: Option<i64>,
|
||||
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,
|
||||
is_self_talk: bool,
|
||||
is_device_talk: bool,
|
||||
is_sending_location: bool,
|
||||
is_self_in_group: bool,
|
||||
is_archived: bool,
|
||||
is_pinned: bool,
|
||||
is_muted: bool,
|
||||
is_contact_request: bool,
|
||||
/// true when chat is a broadcastlist
|
||||
is_broadcast: bool,
|
||||
/// 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 },
|
||||
#[serde(rename_all = "camelCase")]
|
||||
Error { id: u32, error: String },
|
||||
}
|
||||
|
||||
pub(crate) async fn get_chat_list_item_by_id(
|
||||
ctx: &deltachat::context::Context,
|
||||
entry: u32,
|
||||
) -> Result<ChatListItemFetchResult> {
|
||||
let chat_id = ChatId::new(entry);
|
||||
let fresh_message_counter = chat_id.get_fresh_msg_cnt(ctx).await?;
|
||||
|
||||
if chat_id.is_archived_link() {
|
||||
return Ok(ChatListItemFetchResult::ArchiveLink {
|
||||
fresh_message_counter,
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
.get_profile_image(ctx)
|
||||
.await?
|
||||
.map(|path| path.to_str().unwrap_or("invalid/path").to_owned());
|
||||
|
||||
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_viewtype().into()),
|
||||
)
|
||||
}
|
||||
None => (None, None),
|
||||
};
|
||||
|
||||
let chat_contacts = get_chat_contacts(ctx, chat_id).await?;
|
||||
|
||||
let self_in_group = chat_contacts.contains(&ContactId::SELF);
|
||||
|
||||
let (dm_chat_contact, was_seen_recently) = if chat.get_type() == Chattype::Single {
|
||||
let contact = chat_contacts.first();
|
||||
let was_seen_recently = match contact {
|
||||
Some(contact) => Contact::get_by_id(ctx, *contact)
|
||||
.await
|
||||
.context("contact")?
|
||||
.was_seen_recently(),
|
||||
None => false,
|
||||
};
|
||||
(
|
||||
contact.map(|contact_id| contact_id.to_u32()),
|
||||
was_seen_recently,
|
||||
)
|
||||
} else {
|
||||
(None, false)
|
||||
};
|
||||
|
||||
let color = color_int_to_hex_string(chat.get_color(ctx).await?);
|
||||
|
||||
Ok(ChatListItemFetchResult::ChatListItem {
|
||||
id: chat_id.to_u32(),
|
||||
name: chat.get_name().to_owned(),
|
||||
avatar_path,
|
||||
color,
|
||||
last_updated,
|
||||
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,
|
||||
is_self_talk: chat.is_self_talk(),
|
||||
is_device_talk: chat.is_device_talk(),
|
||||
is_self_in_group: self_in_group,
|
||||
is_sending_location: chat.is_sending_locations(),
|
||||
is_archived: visibility == ChatVisibility::Archived,
|
||||
is_pinned: visibility == ChatVisibility::Pinned,
|
||||
is_muted: chat.is_muted(),
|
||||
is_contact_request: chat.is_contact_request(),
|
||||
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()),
|
||||
})
|
||||
}
|
||||
89
deltachat-jsonrpc/src/api/types/contact.rs
Normal file
89
deltachat-jsonrpc/src/api/types/contact.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use anyhow::Result;
|
||||
use deltachat::context::Context;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::color_int_to_hex_string;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "Contact", rename_all = "camelCase")]
|
||||
pub struct ContactObject {
|
||||
address: String,
|
||||
color: String,
|
||||
auth_name: String,
|
||||
status: String,
|
||||
display_name: String,
|
||||
id: u32,
|
||||
name: String,
|
||||
profile_image: Option<String>, // BLOBS
|
||||
name_and_addr: String,
|
||||
is_blocked: bool,
|
||||
|
||||
/// True if the contact can be added to verified groups.
|
||||
///
|
||||
/// If this is true
|
||||
/// UI should display green checkmark after the contact name
|
||||
/// in contact list items,
|
||||
/// in chat member list items
|
||||
/// and in profiles if no chat with the contact exist.
|
||||
is_verified: bool,
|
||||
|
||||
/// True if the contact profile title should have a green checkmark.
|
||||
///
|
||||
/// This indicates whether 1:1 chat has a green checkmark
|
||||
/// or will have a green checkmark if created.
|
||||
is_profile_verified: bool,
|
||||
|
||||
/// The ID of the contact that verified this contact.
|
||||
///
|
||||
/// If this is present,
|
||||
/// display a green checkmark and "Introduced by ..."
|
||||
/// string followed by the verifier contact name and address
|
||||
/// in the contact profile.
|
||||
verifier_id: Option<u32>,
|
||||
|
||||
/// the contact's last seen timestamp
|
||||
last_seen: i64,
|
||||
was_seen_recently: bool,
|
||||
|
||||
/// If the contact is a bot.
|
||||
is_bot: bool,
|
||||
}
|
||||
|
||||
impl ContactObject {
|
||||
pub async fn try_from_dc_contact(
|
||||
context: &Context,
|
||||
contact: deltachat::contact::Contact,
|
||||
) -> Result<Self> {
|
||||
let profile_image = match contact.get_profile_image(context).await? {
|
||||
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
||||
None => None,
|
||||
};
|
||||
let is_verified = contact.is_verified(context).await?;
|
||||
let is_profile_verified = contact.is_profile_verified(context).await?;
|
||||
|
||||
let verifier_id = contact
|
||||
.get_verifier_id(context)
|
||||
.await?
|
||||
.map(|contact_id| contact_id.to_u32());
|
||||
|
||||
Ok(ContactObject {
|
||||
address: contact.get_addr().to_owned(),
|
||||
color: color_int_to_hex_string(contact.get_color()),
|
||||
auth_name: contact.get_authname().to_owned(),
|
||||
status: contact.get_status().to_owned(),
|
||||
display_name: contact.get_display_name().to_owned(),
|
||||
id: contact.id.to_u32(),
|
||||
name: contact.get_name().to_owned(),
|
||||
profile_image, //BLOBS
|
||||
name_and_addr: contact.get_name_n_addr(),
|
||||
is_blocked: contact.is_blocked(),
|
||||
is_verified,
|
||||
is_profile_verified,
|
||||
verifier_id,
|
||||
last_seen: contact.last_seen(),
|
||||
was_seen_recently: contact.was_seen_recently(),
|
||||
is_bot: contact.is_bot(),
|
||||
})
|
||||
}
|
||||
}
|
||||
362
deltachat-jsonrpc/src/api/types/events.rs
Normal file
362
deltachat-jsonrpc/src/api/types/events.rs
Normal file
@@ -0,0 +1,362 @@
|
||||
use deltachat::{Event as CoreEvent, EventType as CoreEventType};
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Event {
|
||||
/// Event payload.
|
||||
event: EventType,
|
||||
|
||||
/// Account ID.
|
||||
context_id: u32,
|
||||
}
|
||||
|
||||
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 = "kind")]
|
||||
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
|
||||
/// that.
|
||||
Info { msg: String },
|
||||
|
||||
/// Emitted when SMTP connection is established and login was successful.
|
||||
SmtpConnected { msg: String },
|
||||
|
||||
/// Emitted when IMAP connection is established and login was successful.
|
||||
ImapConnected { msg: String },
|
||||
|
||||
/// Emitted when a message was successfully sent to the SMTP server.
|
||||
SmtpMessageSent { msg: String },
|
||||
|
||||
/// Emitted when an IMAP message has been marked as deleted
|
||||
ImapMessageDeleted { msg: String },
|
||||
|
||||
/// Emitted when an IMAP message has been moved
|
||||
ImapMessageMoved { msg: String },
|
||||
|
||||
/// Emitted before going into IDLE on the Inbox folder.
|
||||
ImapInboxIdle,
|
||||
|
||||
/// Emitted when an new file in the $BLOBDIR was created
|
||||
NewBlobFile { file: String },
|
||||
|
||||
/// Emitted when an file in the $BLOBDIR was deleted
|
||||
DeletedBlobFile { file: String },
|
||||
|
||||
/// The library-user should write a warning string to the log.
|
||||
///
|
||||
/// This event should *not* be reported to the end-user using a popup or something like
|
||||
/// that.
|
||||
Warning { msg: String },
|
||||
|
||||
/// The library-user should report an error to the end-user.
|
||||
///
|
||||
/// As most things are asynchronous, things may go wrong at any time and the user
|
||||
/// should not be disturbed by a dialog or so. Instead, use a bubble or so.
|
||||
///
|
||||
/// However, for ongoing processes (eg. configure())
|
||||
/// or for functions that are expected to fail (eg. autocryptContinueKeyTransfer())
|
||||
/// it might be better to delay showing these events until the function has really
|
||||
/// failed (returned false). It should be sufficient to report only the *last* error
|
||||
/// in a messasge box then.
|
||||
Error { msg: String },
|
||||
|
||||
/// An action cannot be performed because the user is not in the group.
|
||||
/// Reported eg. after a call to
|
||||
/// setChatName(), setChatProfileImage(),
|
||||
/// addContactToChat(), removeContactFromChat(),
|
||||
/// and messages sending functions.
|
||||
ErrorSelfNotInGroup { msg: String },
|
||||
|
||||
/// Messages or chats changed. One or more messages or chats changed for various
|
||||
/// reasons in the database:
|
||||
/// - Messages sent, received or removed
|
||||
/// - Chats created, deleted or archived
|
||||
/// - A draft has been set
|
||||
///
|
||||
/// `chatId` is set if only a single chat is affected by the changes, otherwise 0.
|
||||
/// `msgId` is set if only a single message is affected by the changes, otherwise 0.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MsgsChanged { chat_id: u32, msg_id: u32 },
|
||||
|
||||
/// Reactions for the message changed.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ReactionsChanged {
|
||||
chat_id: u32,
|
||||
msg_id: u32,
|
||||
contact_id: u32,
|
||||
},
|
||||
|
||||
/// There is a fresh message. Typically, the user will show an notification
|
||||
/// when receiving this message.
|
||||
///
|
||||
/// There is no extra #DC_EVENT_MSGS_CHANGED event sent together with this event.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
IncomingMsg { chat_id: u32, msg_id: u32 },
|
||||
|
||||
/// Downloading a bunch of messages just finished. This is an
|
||||
/// event to allow the UI to only show one notification per message bunch,
|
||||
/// instead of cluttering the user with many notifications.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
IncomingMsgBunch,
|
||||
|
||||
/// Messages were seen or noticed.
|
||||
/// chat id is always set.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MsgsNoticed { chat_id: u32 },
|
||||
|
||||
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
|
||||
/// DC_STATE_OUT_DELIVERED, see `Message.state`.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MsgDelivered { chat_id: u32, msg_id: u32 },
|
||||
|
||||
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
|
||||
/// DC_STATE_OUT_FAILED, see `Message.state`.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MsgFailed { chat_id: u32, msg_id: u32 },
|
||||
|
||||
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
|
||||
/// DC_STATE_OUT_MDN_RCVD, see `Message.state`.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MsgRead { chat_id: u32, msg_id: u32 },
|
||||
|
||||
/// 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()
|
||||
/// and removeContactFromChat().
|
||||
///
|
||||
/// This event does not include ephemeral timer modification, which
|
||||
/// is a separate event.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ChatModified { chat_id: u32 },
|
||||
|
||||
/// Chat ephemeral timer changed.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ChatEphemeralTimerModified { chat_id: u32, timer: u32 },
|
||||
|
||||
/// Contact(s) created, renamed, blocked or deleted.
|
||||
///
|
||||
/// @param data1 (int) If set, this is the contact_id of an added contact that should be selected.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ContactsChanged { contact_id: Option<u32> },
|
||||
|
||||
/// Location of one or more contact has changed.
|
||||
///
|
||||
/// @param data1 (u32) contact_id of the contact for which the location has changed.
|
||||
/// If the locations of several contacts have been changed,
|
||||
/// this parameter is set to `None`.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
LocationChanged { contact_id: Option<u32> },
|
||||
|
||||
/// Inform about the configuration progress started by configure().
|
||||
ConfigureProgress {
|
||||
/// Progress.
|
||||
///
|
||||
/// 0=error, 1-999=progress in permille, 1000=success and done
|
||||
progress: usize,
|
||||
|
||||
/// Progress comment or error, something to display to the user.
|
||||
comment: Option<String>,
|
||||
},
|
||||
|
||||
/// Inform about the import/export progress started by imex().
|
||||
///
|
||||
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
|
||||
/// @param data2 0
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ImexProgress { progress: usize },
|
||||
|
||||
/// A file has been exported. A file has been written by imex().
|
||||
/// This event may be sent multiple times by a single call to imex().
|
||||
///
|
||||
/// A typical purpose for a handler of this event may be to make the file public to some system
|
||||
/// services.
|
||||
///
|
||||
/// @param data2 0
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ImexFileWritten { path: String },
|
||||
|
||||
/// Progress information of a secure-join handshake from the view of the inviter
|
||||
/// (Alice, the person who shows the QR code).
|
||||
///
|
||||
/// These events are typically sent after a joiner has scanned the QR code
|
||||
/// generated by getChatSecurejoinQrCodeSvg().
|
||||
///
|
||||
/// @param data1 (int) ID of the contact that wants to join.
|
||||
/// @param data2 (int) Progress as:
|
||||
/// 300=vg-/vc-request received, typically shown as "bob@addr joins".
|
||||
/// 600=vg-/vc-request-with-auth received, vg-member-added/vc-contact-confirm sent, typically shown as "bob@addr verified".
|
||||
/// 800=vg-member-added-received received, shown as "bob@addr securely joined GROUP", only sent for the verified-group-protocol.
|
||||
/// 1000=Protocol finished for this contact.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
SecurejoinInviterProgress { contact_id: u32, progress: usize },
|
||||
|
||||
/// Progress information of a secure-join handshake from the view of the joiner
|
||||
/// (Bob, the person who scans the QR code).
|
||||
/// The events are typically sent while secureJoin(), which
|
||||
/// may take some time, is executed.
|
||||
/// @param data1 (int) ID of the inviting contact.
|
||||
/// @param data2 (int) Progress as:
|
||||
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
|
||||
/// (Bob has verified alice and waits until Alice does the same for him)
|
||||
#[serde(rename_all = "camelCase")]
|
||||
SecurejoinJoinerProgress { contact_id: u32, progress: usize },
|
||||
|
||||
/// The connectivity to the server changed.
|
||||
/// This means that you should refresh the connectivity view
|
||||
/// and possibly the connectivtiy HTML; see getConnectivity() and
|
||||
/// getConnectivityHtml() for details.
|
||||
ConnectivityChanged,
|
||||
|
||||
/// Deprecated by `ConfigSynced`.
|
||||
SelfavatarChanged,
|
||||
|
||||
/// A multi-device synced config value changed. Maybe the app needs to refresh smth. For
|
||||
/// uniformity this is emitted on the source device too. The value isn't here, otherwise it
|
||||
/// would be logged which might not be good for privacy.
|
||||
ConfigSynced {
|
||||
/// Configuration key.
|
||||
key: String,
|
||||
},
|
||||
|
||||
#[serde(rename_all = "camelCase")]
|
||||
WebxdcStatusUpdate {
|
||||
msg_id: u32,
|
||||
status_update_serial: u32,
|
||||
},
|
||||
|
||||
/// Inform that a message containing a webxdc instance has been deleted
|
||||
#[serde(rename_all = "camelCase")]
|
||||
WebxdcInstanceDeleted { msg_id: u32 },
|
||||
|
||||
/// Tells that the Background fetch was completed (or timed out).
|
||||
/// This event acts as a marker, when you reach this event you can be sure
|
||||
/// that all events emitted during the background fetch were processed.
|
||||
///
|
||||
/// This event is only emitted by the account manager
|
||||
AccountsBackgroundFetchDone,
|
||||
}
|
||||
|
||||
impl From<CoreEventType> for EventType {
|
||||
fn from(event: CoreEventType) -> Self {
|
||||
use EventType::*;
|
||||
match event {
|
||||
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(),
|
||||
},
|
||||
CoreEventType::ReactionsChanged {
|
||||
chat_id,
|
||||
msg_id,
|
||||
contact_id,
|
||||
} => ReactionsChanged {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
contact_id: contact_id.to_u32(),
|
||||
},
|
||||
CoreEventType::IncomingMsg { chat_id, msg_id } => IncomingMsg {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
CoreEventType::IncomingMsgBunch => IncomingMsgBunch,
|
||||
CoreEventType::MsgsNoticed(chat_id) => MsgsNoticed {
|
||||
chat_id: chat_id.to_u32(),
|
||||
},
|
||||
CoreEventType::MsgDelivered { chat_id, msg_id } => MsgDelivered {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
CoreEventType::MsgFailed { chat_id, msg_id } => MsgFailed {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
CoreEventType::MsgRead { chat_id, msg_id } => MsgRead {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
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(),
|
||||
},
|
||||
CoreEventType::ChatEphemeralTimerModified { chat_id, timer } => {
|
||||
ChatEphemeralTimerModified {
|
||||
chat_id: chat_id.to_u32(),
|
||||
timer: timer.to_u32(),
|
||||
}
|
||||
}
|
||||
CoreEventType::ContactsChanged(contact) => ContactsChanged {
|
||||
contact_id: contact.map(|c| c.to_u32()),
|
||||
},
|
||||
CoreEventType::LocationChanged(contact) => LocationChanged {
|
||||
contact_id: contact.map(|c| c.to_u32()),
|
||||
},
|
||||
CoreEventType::ConfigureProgress { progress, comment } => {
|
||||
ConfigureProgress { progress, comment }
|
||||
}
|
||||
CoreEventType::ImexProgress(progress) => ImexProgress { progress },
|
||||
CoreEventType::ImexFileWritten(path) => ImexFileWritten {
|
||||
path: path.to_str().unwrap_or_default().to_owned(),
|
||||
},
|
||||
CoreEventType::SecurejoinInviterProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
} => SecurejoinInviterProgress {
|
||||
contact_id: contact_id.to_u32(),
|
||||
progress,
|
||||
},
|
||||
CoreEventType::SecurejoinJoinerProgress {
|
||||
contact_id,
|
||||
progress,
|
||||
} => SecurejoinJoinerProgress {
|
||||
contact_id: contact_id.to_u32(),
|
||||
progress,
|
||||
},
|
||||
CoreEventType::ConnectivityChanged => ConnectivityChanged,
|
||||
CoreEventType::SelfavatarChanged => SelfavatarChanged,
|
||||
CoreEventType::ConfigSynced { key } => ConfigSynced {
|
||||
key: key.to_string(),
|
||||
},
|
||||
CoreEventType::WebxdcStatusUpdate {
|
||||
msg_id,
|
||||
status_update_serial,
|
||||
} => WebxdcStatusUpdate {
|
||||
msg_id: msg_id.to_u32(),
|
||||
status_update_serial: status_update_serial.to_u32(),
|
||||
},
|
||||
CoreEventType::WebxdcInstanceDeleted { msg_id } => WebxdcInstanceDeleted {
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
CoreEventType::AccountsBackgroundFetchDone => AccountsBackgroundFetchDone,
|
||||
}
|
||||
}
|
||||
}
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
47
deltachat-jsonrpc/src/api/types/location.rs
Normal file
47
deltachat-jsonrpc/src/api/types/location.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use deltachat::location::Location;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "Location", rename_all = "camelCase")]
|
||||
pub struct JsonrpcLocation {
|
||||
pub location_id: u32,
|
||||
pub is_independent: bool,
|
||||
pub latitude: f64,
|
||||
pub longitude: f64,
|
||||
pub accuracy: f64,
|
||||
pub timestamp: i64,
|
||||
pub contact_id: u32,
|
||||
pub msg_id: u32,
|
||||
pub chat_id: u32,
|
||||
pub marker: Option<String>,
|
||||
}
|
||||
|
||||
impl From<Location> for JsonrpcLocation {
|
||||
fn from(location: Location) -> Self {
|
||||
let Location {
|
||||
location_id,
|
||||
independent,
|
||||
latitude,
|
||||
longitude,
|
||||
accuracy,
|
||||
timestamp,
|
||||
contact_id,
|
||||
msg_id,
|
||||
chat_id,
|
||||
marker,
|
||||
} = location;
|
||||
Self {
|
||||
location_id,
|
||||
is_independent: independent != 0,
|
||||
latitude,
|
||||
longitude,
|
||||
accuracy,
|
||||
timestamp,
|
||||
contact_id: contact_id.to_u32(),
|
||||
msg_id,
|
||||
chat_id: chat_id.to_u32(),
|
||||
marker,
|
||||
}
|
||||
}
|
||||
}
|
||||
657
deltachat-jsonrpc/src/api/types/message.rs
Normal file
657
deltachat-jsonrpc/src/api/types/message.rs
Normal file
@@ -0,0 +1,657 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use deltachat::chat::Chat;
|
||||
use deltachat::chat::ChatItem;
|
||||
use deltachat::chat::ChatVisibility;
|
||||
use deltachat::contact::Contact;
|
||||
use deltachat::context::Context;
|
||||
use deltachat::download;
|
||||
use deltachat::message::Message;
|
||||
use deltachat::message::MsgId;
|
||||
use deltachat::message::Viewtype;
|
||||
use deltachat::reaction::get_msg_reactions;
|
||||
use num_traits::cast::ToPrimitive;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::color_int_to_hex_string;
|
||||
use super::contact::ContactObject;
|
||||
use super::reactions::JSONRPCReactions;
|
||||
use super::webxdc::WebxdcMessageInfo;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase", tag = "kind")]
|
||||
pub enum MessageLoadResult {
|
||||
Message(MessageObject),
|
||||
LoadingError { error: String },
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "Message", rename_all = "camelCase")]
|
||||
pub struct MessageObject {
|
||||
id: u32,
|
||||
chat_id: u32,
|
||||
from_id: u32,
|
||||
quote: Option<MessageQuote>,
|
||||
parent_id: Option<u32>,
|
||||
|
||||
text: String,
|
||||
has_location: bool,
|
||||
has_html: bool,
|
||||
view_type: MessageViewtype,
|
||||
state: u32,
|
||||
|
||||
/// An error text, if there is one.
|
||||
error: Option<String>,
|
||||
|
||||
timestamp: i64,
|
||||
sort_timestamp: i64,
|
||||
received_timestamp: i64,
|
||||
has_deviating_timestamp: bool,
|
||||
|
||||
// summary - use/create another function if you need it
|
||||
subject: String,
|
||||
show_padlock: bool,
|
||||
is_setupmessage: bool,
|
||||
is_info: bool,
|
||||
is_forwarded: bool,
|
||||
|
||||
/// True if the message was sent by a bot.
|
||||
is_bot: bool,
|
||||
|
||||
/// when is_info is true this describes what type of system message it is
|
||||
system_message_type: SystemMessageType,
|
||||
|
||||
duration: i32,
|
||||
dimensions_height: i32,
|
||||
dimensions_width: i32,
|
||||
|
||||
videochat_type: Option<u32>,
|
||||
videochat_url: Option<String>,
|
||||
|
||||
override_sender_name: Option<String>,
|
||||
sender: ContactObject,
|
||||
|
||||
setup_code_begin: Option<String>,
|
||||
|
||||
file: Option<String>,
|
||||
file_mime: Option<String>,
|
||||
file_bytes: u64,
|
||||
file_name: Option<String>,
|
||||
|
||||
webxdc_info: Option<WebxdcMessageInfo>,
|
||||
|
||||
download_state: DownloadState,
|
||||
|
||||
reactions: Option<JSONRPCReactions>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(tag = "kind")]
|
||||
enum MessageQuote {
|
||||
JustText {
|
||||
text: String,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
WithMessage {
|
||||
text: String,
|
||||
message_id: u32,
|
||||
author_display_name: String,
|
||||
author_display_color: String,
|
||||
override_sender_name: Option<String>,
|
||||
image: Option<String>,
|
||||
is_forwarded: bool,
|
||||
view_type: MessageViewtype,
|
||||
},
|
||||
}
|
||||
|
||||
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::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();
|
||||
|
||||
let webxdc_info = if message.get_viewtype() == Viewtype::Webxdc {
|
||||
Some(WebxdcMessageInfo::get_for_message(context, msg_id).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let parent_id = message.parent(context).await?.map(|m| m.get_id().to_u32());
|
||||
|
||||
let download_state = message.download_state().into();
|
||||
|
||||
let quote = if let Some(quoted_text) = message.quoted_text() {
|
||||
match message.quoted_message(context).await? {
|
||||
Some(quote) => {
|
||||
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(),
|
||||
author_display_name: quote_author.get_display_name().to_owned(),
|
||||
author_display_color: color_int_to_hex_string(quote_author.get_color()),
|
||||
override_sender_name: quote.get_override_sender_name(),
|
||||
image: if quote.get_viewtype() == Viewtype::Image
|
||||
|| quote.get_viewtype() == Viewtype::Gif
|
||||
|| quote.get_viewtype() == Viewtype::Sticker
|
||||
{
|
||||
match quote.get_file(context) {
|
||||
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
||||
None => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
},
|
||||
is_forwarded: quote.is_forwarded(),
|
||||
view_type: quote.get_viewtype().into(),
|
||||
})
|
||||
}
|
||||
None => Some(MessageQuote::JustText { text: quoted_text }),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let reactions = get_msg_reactions(context, msg_id)
|
||||
.await
|
||||
.context("failed to load message reactions")?;
|
||||
let reactions = if reactions.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(reactions.into())
|
||||
};
|
||||
|
||||
Ok(MessageObject {
|
||||
id: msg_id.to_u32(),
|
||||
chat_id: message.get_chat_id().to_u32(),
|
||||
from_id: message.get_from_id().to_u32(),
|
||||
quote,
|
||||
parent_id,
|
||||
text: message.get_text(),
|
||||
has_location: message.has_location(),
|
||||
has_html: message.has_html(),
|
||||
view_type: message.get_viewtype().into(),
|
||||
state: message
|
||||
.get_state()
|
||||
.to_u32()
|
||||
.context("state conversion to number failed")?,
|
||||
error: message.error(),
|
||||
|
||||
timestamp: message.get_timestamp(),
|
||||
sort_timestamp: message.get_sort_timestamp(),
|
||||
received_timestamp: message.get_received_timestamp(),
|
||||
has_deviating_timestamp: message.has_deviating_timestamp(),
|
||||
|
||||
subject: message.get_subject().to_owned(),
|
||||
show_padlock: message.get_showpadlock(),
|
||||
is_setupmessage: message.is_setupmessage(),
|
||||
is_info: message.is_info(),
|
||||
is_forwarded: message.is_forwarded(),
|
||||
is_bot: message.is_bot(),
|
||||
system_message_type: message.get_info_type().into(),
|
||||
|
||||
duration: message.get_duration(),
|
||||
dimensions_height: message.get_height(),
|
||||
dimensions_width: message.get_width(),
|
||||
|
||||
videochat_type: match message.get_videochat_type() {
|
||||
Some(vct) => Some(
|
||||
vct.to_u32()
|
||||
.context("videochat type conversion to number failed")?,
|
||||
),
|
||||
None => None,
|
||||
},
|
||||
videochat_url: message.get_videochat_url(),
|
||||
|
||||
override_sender_name,
|
||||
sender,
|
||||
|
||||
setup_code_begin: message.get_setupcodebegin(context).await,
|
||||
|
||||
file: match message.get_file(context) {
|
||||
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
||||
None => None,
|
||||
}, //BLOBS
|
||||
file_mime: message.get_filemime(),
|
||||
file_bytes,
|
||||
file_name: message.get_filename(),
|
||||
webxdc_info,
|
||||
|
||||
download_state,
|
||||
|
||||
reactions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "Viewtype")]
|
||||
pub enum MessageViewtype {
|
||||
Unknown,
|
||||
|
||||
/// Text message.
|
||||
Text,
|
||||
|
||||
/// Image message.
|
||||
/// If the image is an animated GIF, the type `Viewtype.Gif` should be used.
|
||||
Image,
|
||||
|
||||
/// Animated GIF message.
|
||||
Gif,
|
||||
|
||||
/// Message containing a sticker, similar to image.
|
||||
/// If possible, the ui should display the image without borders in a transparent way.
|
||||
/// A click on a sticker will offer to install the sticker set in some future.
|
||||
Sticker,
|
||||
|
||||
/// Message containing an Audio file.
|
||||
Audio,
|
||||
|
||||
/// A voice message that was directly recorded by the user.
|
||||
/// For all other audio messages, the type `Viewtype.Audio` should be used.
|
||||
Voice,
|
||||
|
||||
/// Video messages.
|
||||
Video,
|
||||
|
||||
/// Message containing any file, eg. a PDF.
|
||||
File,
|
||||
|
||||
/// Message is an invitation to a videochat.
|
||||
VideochatInvitation,
|
||||
|
||||
/// Message is an webxdc instance.
|
||||
Webxdc,
|
||||
}
|
||||
|
||||
impl From<Viewtype> for MessageViewtype {
|
||||
fn from(viewtype: Viewtype) -> Self {
|
||||
match viewtype {
|
||||
Viewtype::Unknown => MessageViewtype::Unknown,
|
||||
Viewtype::Text => MessageViewtype::Text,
|
||||
Viewtype::Image => MessageViewtype::Image,
|
||||
Viewtype::Gif => MessageViewtype::Gif,
|
||||
Viewtype::Sticker => MessageViewtype::Sticker,
|
||||
Viewtype::Audio => MessageViewtype::Audio,
|
||||
Viewtype::Voice => MessageViewtype::Voice,
|
||||
Viewtype::Video => MessageViewtype::Video,
|
||||
Viewtype::File => MessageViewtype::File,
|
||||
Viewtype::VideochatInvitation => MessageViewtype::VideochatInvitation,
|
||||
Viewtype::Webxdc => MessageViewtype::Webxdc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MessageViewtype> for Viewtype {
|
||||
fn from(viewtype: MessageViewtype) -> Self {
|
||||
match viewtype {
|
||||
MessageViewtype::Unknown => Viewtype::Unknown,
|
||||
MessageViewtype::Text => Viewtype::Text,
|
||||
MessageViewtype::Image => Viewtype::Image,
|
||||
MessageViewtype::Gif => Viewtype::Gif,
|
||||
MessageViewtype::Sticker => Viewtype::Sticker,
|
||||
MessageViewtype::Audio => Viewtype::Audio,
|
||||
MessageViewtype::Voice => Viewtype::Voice,
|
||||
MessageViewtype::Video => Viewtype::Video,
|
||||
MessageViewtype::File => Viewtype::File,
|
||||
MessageViewtype::VideochatInvitation => Viewtype::VideochatInvitation,
|
||||
MessageViewtype::Webxdc => Viewtype::Webxdc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
pub enum DownloadState {
|
||||
Done,
|
||||
Available,
|
||||
Failure,
|
||||
Undecipherable,
|
||||
InProgress,
|
||||
}
|
||||
|
||||
impl From<download::DownloadState> for DownloadState {
|
||||
fn from(state: download::DownloadState) -> Self {
|
||||
match state {
|
||||
download::DownloadState::Done => DownloadState::Done,
|
||||
download::DownloadState::Available => DownloadState::Available,
|
||||
download::DownloadState::Failure => DownloadState::Failure,
|
||||
download::DownloadState::Undecipherable => DownloadState::Undecipherable,
|
||||
download::DownloadState::InProgress => DownloadState::InProgress,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
pub enum SystemMessageType {
|
||||
Unknown,
|
||||
GroupNameChanged,
|
||||
GroupImageChanged,
|
||||
MemberAddedToGroup,
|
||||
MemberRemovedFromGroup,
|
||||
AutocryptSetupMessage,
|
||||
SecurejoinMessage,
|
||||
LocationStreamingEnabled,
|
||||
LocationOnly,
|
||||
InvalidUnencryptedMail,
|
||||
|
||||
/// Chat ephemeral message timer is changed.
|
||||
EphemeralTimerChanged,
|
||||
|
||||
// Chat protection state changed
|
||||
ChatProtectionEnabled,
|
||||
ChatProtectionDisabled,
|
||||
|
||||
/// Self-sent-message that contains only json used for multi-device-sync;
|
||||
/// if possible, we attach that to other messages as for locations.
|
||||
MultiDeviceSync,
|
||||
|
||||
// Sync message that contains a json payload
|
||||
// sent to the other webxdc instances
|
||||
// These messages are not shown in the chat.
|
||||
WebxdcStatusUpdate,
|
||||
|
||||
/// Webxdc info added with `info` set in `send_webxdc_status_update()`.
|
||||
WebxdcInfoMessage,
|
||||
}
|
||||
|
||||
impl From<deltachat::mimeparser::SystemMessage> for SystemMessageType {
|
||||
fn from(system_message_type: deltachat::mimeparser::SystemMessage) -> Self {
|
||||
use deltachat::mimeparser::SystemMessage;
|
||||
match system_message_type {
|
||||
SystemMessage::Unknown => SystemMessageType::Unknown,
|
||||
SystemMessage::GroupNameChanged => SystemMessageType::GroupNameChanged,
|
||||
SystemMessage::GroupImageChanged => SystemMessageType::GroupImageChanged,
|
||||
SystemMessage::MemberAddedToGroup => SystemMessageType::MemberAddedToGroup,
|
||||
SystemMessage::MemberRemovedFromGroup => SystemMessageType::MemberRemovedFromGroup,
|
||||
SystemMessage::AutocryptSetupMessage => SystemMessageType::AutocryptSetupMessage,
|
||||
SystemMessage::SecurejoinMessage => SystemMessageType::SecurejoinMessage,
|
||||
SystemMessage::LocationStreamingEnabled => SystemMessageType::LocationStreamingEnabled,
|
||||
SystemMessage::LocationOnly => SystemMessageType::LocationOnly,
|
||||
SystemMessage::EphemeralTimerChanged => SystemMessageType::EphemeralTimerChanged,
|
||||
SystemMessage::ChatProtectionEnabled => SystemMessageType::ChatProtectionEnabled,
|
||||
SystemMessage::ChatProtectionDisabled => SystemMessageType::ChatProtectionDisabled,
|
||||
SystemMessage::MultiDeviceSync => SystemMessageType::MultiDeviceSync,
|
||||
SystemMessage::WebxdcStatusUpdate => SystemMessageType::WebxdcStatusUpdate,
|
||||
SystemMessage::WebxdcInfoMessage => SystemMessageType::WebxdcInfoMessage,
|
||||
SystemMessage::InvalidUnencryptedMail => SystemMessageType::InvalidUnencryptedMail,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageNotificationInfo {
|
||||
id: u32,
|
||||
chat_id: u32,
|
||||
account_id: u32,
|
||||
|
||||
image: Option<String>,
|
||||
image_mime_type: Option<String>,
|
||||
|
||||
chat_name: String,
|
||||
chat_profile_image: Option<String>,
|
||||
|
||||
/// also known as summary_text1
|
||||
summary_prefix: Option<String>,
|
||||
/// also known as summary_text2
|
||||
summary_text: String,
|
||||
}
|
||||
|
||||
impl MessageNotificationInfo {
|
||||
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 image = if matches!(
|
||||
message.get_viewtype(),
|
||||
Viewtype::Image | Viewtype::Gif | Viewtype::Sticker
|
||||
) {
|
||||
message
|
||||
.get_file(context)
|
||||
.map(|path_buf| path_buf.to_str().map(|s| s.to_owned()))
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let chat_profile_image = chat
|
||||
.get_profile_image(context)
|
||||
.await?
|
||||
.map(|path_buf| path_buf.to_str().map(|s| s.to_owned()))
|
||||
.unwrap_or_default();
|
||||
|
||||
let summary = message.get_summary(context, Some(&chat)).await?;
|
||||
|
||||
Ok(MessageNotificationInfo {
|
||||
id: msg_id.to_u32(),
|
||||
chat_id: message.get_chat_id().to_u32(),
|
||||
account_id: context.get_id(),
|
||||
image,
|
||||
image_mime_type: message.get_filemime(),
|
||||
chat_name: chat.name,
|
||||
chat_profile_image,
|
||||
summary_prefix: summary.prefix.map(|s| s.to_string()),
|
||||
summary_text: summary.text,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
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,
|
||||
}
|
||||
|
||||
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::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,
|
||||
author_color: color_int_to_hex_string(sender.get_color()),
|
||||
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, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase", rename = "MessageListItem", tag = "kind")]
|
||||
pub enum JSONRPCMessageListItem {
|
||||
Message {
|
||||
msg_id: u32,
|
||||
},
|
||||
|
||||
/// Day marker, separating messages that correspond to different
|
||||
/// days according to local time.
|
||||
DayMarker {
|
||||
/// Marker timestamp, for day markers, in unix milliseconds
|
||||
timestamp: i64,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<ChatItem> for JSONRPCMessageListItem {
|
||||
fn from(item: ChatItem) -> Self {
|
||||
match item {
|
||||
ChatItem::Message { msg_id } => JSONRPCMessageListItem::Message {
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
ChatItem::DayMarker { timestamp } => JSONRPCMessageListItem::DayMarker { timestamp },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, 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>,
|
||||
}
|
||||
|
||||
impl MessageData {
|
||||
pub(crate) async fn create_message(self, context: &Context) -> Result<Message> {
|
||||
let mut message = Message::new(if let Some(viewtype) = self.viewtype {
|
||||
viewtype.into()
|
||||
} else if self.file.is_some() {
|
||||
Viewtype::File
|
||||
} else {
|
||||
Viewtype::Text
|
||||
});
|
||||
message.set_text(self.text.unwrap_or_default());
|
||||
if self.html.is_some() {
|
||||
message.set_html(self.html);
|
||||
}
|
||||
if self.override_sender_name.is_some() {
|
||||
message.set_override_sender_name(self.override_sender_name);
|
||||
}
|
||||
if let Some(file) = self.file {
|
||||
message.set_file(file, None);
|
||||
}
|
||||
if let Some((latitude, longitude)) = self.location {
|
||||
message.set_location(latitude, longitude);
|
||||
}
|
||||
if let Some(id) = self.quoted_message_id {
|
||||
message
|
||||
.set_quote(
|
||||
context,
|
||||
Some(
|
||||
&Message::load_from_db(context, MsgId::new(id))
|
||||
.await
|
||||
.context("message to quote could not be loaded")?,
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok(message)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageReadReceipt {
|
||||
pub contact_id: u32,
|
||||
pub timestamp: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MessageInfo {
|
||||
rawtext: String,
|
||||
ephemeral_timer: EphemeralTimer,
|
||||
/// When message is ephemeral this contains the timestamp of the message expiry
|
||||
ephemeral_timestamp: Option<i64>,
|
||||
error: Option<String>,
|
||||
rfc724_mid: String,
|
||||
server_urls: Vec<String>,
|
||||
hop_info: Option<String>,
|
||||
}
|
||||
|
||||
impl MessageInfo {
|
||||
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<Self> {
|
||||
let message = Message::load_from_db(context, msg_id).await?;
|
||||
let rawtext = msg_id.rawtext(context).await?;
|
||||
let ephemeral_timer = message.get_ephemeral_timer().into();
|
||||
let ephemeral_timestamp = match message.get_ephemeral_timer() {
|
||||
deltachat::ephemeral::Timer::Disabled => None,
|
||||
deltachat::ephemeral::Timer::Enabled { .. } => Some(message.get_ephemeral_timestamp()),
|
||||
};
|
||||
|
||||
let server_urls =
|
||||
MsgId::get_info_server_urls(context, message.rfc724_mid().to_owned()).await?;
|
||||
|
||||
let hop_info = msg_id.hop_info(context).await?;
|
||||
|
||||
Ok(Self {
|
||||
rawtext,
|
||||
ephemeral_timer,
|
||||
ephemeral_timestamp,
|
||||
error: message.error(),
|
||||
rfc724_mid: message.rfc724_mid().to_owned(),
|
||||
server_urls,
|
||||
hop_info,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize, TypeDef, schemars::JsonSchema,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase", tag = "variant")]
|
||||
pub enum EphemeralTimer {
|
||||
/// Timer is disabled.
|
||||
Disabled,
|
||||
|
||||
/// Timer is enabled.
|
||||
Enabled {
|
||||
/// Timer duration in seconds.
|
||||
///
|
||||
/// The value cannot be 0.
|
||||
duration: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<deltachat::ephemeral::Timer> for EphemeralTimer {
|
||||
fn from(value: deltachat::ephemeral::Timer) -> Self {
|
||||
match value {
|
||||
deltachat::ephemeral::Timer::Disabled => EphemeralTimer::Disabled,
|
||||
deltachat::ephemeral::Timer::Enabled { duration } => {
|
||||
EphemeralTimer::Enabled { duration }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
deltachat-jsonrpc/src/api/types/mod.rs
Normal file
24
deltachat-jsonrpc/src/api/types/mod.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
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;
|
||||
pub mod qr;
|
||||
pub mod reactions;
|
||||
pub mod webxdc;
|
||||
|
||||
pub fn color_int_to_hex_string(color: u32) -> String {
|
||||
format!("{color:#08x}").replace("0x", "#")
|
||||
}
|
||||
|
||||
fn maybe_empty_string_to_option(string: String) -> Option<String> {
|
||||
if string.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(string)
|
||||
}
|
||||
}
|
||||
25
deltachat-jsonrpc/src/api/types/provider_info.rs
Normal file
25
deltachat-jsonrpc/src/api/types/provider_info.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use deltachat::provider::Provider;
|
||||
use num_traits::cast::ToPrimitive;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ProviderInfo {
|
||||
/// Unique ID, corresponding to provider database filename.
|
||||
pub id: String,
|
||||
pub before_login_hint: String,
|
||||
pub overview_page: String,
|
||||
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 {
|
||||
pub fn from_dc_type(provider: Option<&Provider>) -> Option<Self> {
|
||||
provider.map(|p| ProviderInfo {
|
||||
id: p.id.to_owned(),
|
||||
before_login_hint: p.before_login_hint.to_owned(),
|
||||
overview_page: p.overview_page.to_owned(),
|
||||
status: p.status.to_u32().unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
219
deltachat-jsonrpc/src/api/types/qr.rs
Normal file
219
deltachat-jsonrpc/src/api/types/qr.rs
Normal file
@@ -0,0 +1,219 @@
|
||||
use deltachat::qr::Qr;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "Qr", rename_all = "camelCase")]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum QrObject {
|
||||
AskVerifyContact {
|
||||
contact_id: u32,
|
||||
fingerprint: String,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
AskVerifyGroup {
|
||||
grpname: String,
|
||||
grpid: String,
|
||||
contact_id: u32,
|
||||
fingerprint: String,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
FprOk {
|
||||
contact_id: u32,
|
||||
},
|
||||
FprMismatch {
|
||||
contact_id: Option<u32>,
|
||||
},
|
||||
FprWithoutAddr {
|
||||
fingerprint: String,
|
||||
},
|
||||
Account {
|
||||
domain: String,
|
||||
},
|
||||
Backup {
|
||||
ticket: String,
|
||||
},
|
||||
WebrtcInstance {
|
||||
domain: String,
|
||||
instance_pattern: String,
|
||||
},
|
||||
Addr {
|
||||
contact_id: u32,
|
||||
draft: Option<String>,
|
||||
},
|
||||
Url {
|
||||
url: String,
|
||||
},
|
||||
Text {
|
||||
text: String,
|
||||
},
|
||||
WithdrawVerifyContact {
|
||||
contact_id: u32,
|
||||
fingerprint: String,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
WithdrawVerifyGroup {
|
||||
grpname: String,
|
||||
grpid: String,
|
||||
contact_id: u32,
|
||||
fingerprint: String,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
ReviveVerifyContact {
|
||||
contact_id: u32,
|
||||
fingerprint: String,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
ReviveVerifyGroup {
|
||||
grpname: String,
|
||||
grpid: String,
|
||||
contact_id: u32,
|
||||
fingerprint: String,
|
||||
invitenumber: String,
|
||||
authcode: String,
|
||||
},
|
||||
Login {
|
||||
address: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<Qr> for QrObject {
|
||||
fn from(qr: Qr) -> Self {
|
||||
match qr {
|
||||
Qr::AskVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::AskVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::AskVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::AskVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::FprOk { contact_id } => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
QrObject::FprOk { contact_id }
|
||||
}
|
||||
Qr::FprMismatch { contact_id } => {
|
||||
let contact_id = contact_id.map(|contact_id| contact_id.to_u32());
|
||||
QrObject::FprMismatch { contact_id }
|
||||
}
|
||||
Qr::FprWithoutAddr { fingerprint } => QrObject::FprWithoutAddr { fingerprint },
|
||||
Qr::Account { domain } => QrObject::Account { domain },
|
||||
Qr::Backup { ticket } => QrObject::Backup {
|
||||
ticket: ticket.to_string(),
|
||||
},
|
||||
Qr::WebrtcInstance {
|
||||
domain,
|
||||
instance_pattern,
|
||||
} => QrObject::WebrtcInstance {
|
||||
domain,
|
||||
instance_pattern,
|
||||
},
|
||||
Qr::Addr { contact_id, draft } => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
QrObject::Addr { contact_id, draft }
|
||||
}
|
||||
Qr::Url { url } => QrObject::Url { url },
|
||||
Qr::Text { text } => QrObject::Text { text },
|
||||
Qr::WithdrawVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::WithdrawVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::WithdrawVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::WithdrawVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::ReviveVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::ReviveVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::ReviveVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
QrObject::ReviveVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
contact_id,
|
||||
fingerprint,
|
||||
invitenumber,
|
||||
authcode,
|
||||
}
|
||||
}
|
||||
Qr::Login { address, .. } => QrObject::Login { address },
|
||||
}
|
||||
}
|
||||
}
|
||||
72
deltachat-jsonrpc/src/api/types/reactions.rs
Normal file
72
deltachat-jsonrpc/src/api/types/reactions.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
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, 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, 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();
|
||||
|
||||
for contact_id in reactions.contacts() {
|
||||
let reaction = reactions.get(contact_id);
|
||||
if reaction.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let emojis: Vec<String> = reaction
|
||||
.emojis()
|
||||
.into_iter()
|
||||
.map(|emoji| emoji.to_owned())
|
||||
.collect();
|
||||
reactions_by_contact.insert(contact_id.to_u32(), emojis.clone());
|
||||
}
|
||||
|
||||
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: reactions_v,
|
||||
}
|
||||
}
|
||||
}
|
||||
64
deltachat-jsonrpc/src/api/types/webxdc.rs
Normal file
64
deltachat-jsonrpc/src/api/types/webxdc.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use deltachat::{
|
||||
context::Context,
|
||||
message::{Message, MsgId},
|
||||
webxdc::WebxdcInfo,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
use super::maybe_empty_string_to_option;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "WebxdcMessageInfo", rename_all = "camelCase")]
|
||||
pub struct WebxdcMessageInfo {
|
||||
/// The name of the app.
|
||||
///
|
||||
/// Defaults to the filename if not set in the manifest.
|
||||
name: String,
|
||||
/// App icon file name.
|
||||
/// Defaults to an standard icon if nothing is set in the manifest.
|
||||
///
|
||||
/// To get the file, use dc_msg_get_webxdc_blob(). (not yet in jsonrpc, use rust api or cffi for it)
|
||||
///
|
||||
/// App icons should should be square,
|
||||
/// the implementations will add round corners etc. as needed.
|
||||
icon: String,
|
||||
/// if the Webxdc represents a document, then this is the name of the document
|
||||
document: Option<String>,
|
||||
/// short string describing the state of the app,
|
||||
/// sth. as "2 votes", "Highscore: 123",
|
||||
/// can be changed by the apps
|
||||
summary: Option<String>,
|
||||
/// URL where the source code of the Webxdc and other information can be found;
|
||||
/// defaults to an empty string.
|
||||
/// Implementations may offer an menu or a button to open this URL.
|
||||
source_code_url: Option<String>,
|
||||
/// True if full internet access should be granted to the app.
|
||||
internet_access: bool,
|
||||
}
|
||||
|
||||
impl WebxdcMessageInfo {
|
||||
pub async fn get_for_message(
|
||||
context: &Context,
|
||||
instance_message_id: MsgId,
|
||||
) -> anyhow::Result<Self> {
|
||||
let message = Message::load_from_db(context, instance_message_id).await?;
|
||||
let WebxdcInfo {
|
||||
name,
|
||||
icon,
|
||||
document,
|
||||
summary,
|
||||
source_code_url,
|
||||
internet_access,
|
||||
} = message.get_webxdc_info(context).await?;
|
||||
|
||||
Ok(Self {
|
||||
name,
|
||||
icon,
|
||||
document: maybe_empty_string_to_option(document),
|
||||
summary: maybe_empty_string_to_option(summary),
|
||||
source_code_url: maybe_empty_string_to_option(source_code_url),
|
||||
internet_access,
|
||||
})
|
||||
}
|
||||
}
|
||||
94
deltachat-jsonrpc/src/lib.rs
Normal file
94
deltachat-jsonrpc/src/lib.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
pub mod api;
|
||||
pub use yerpc;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use async_channel::unbounded;
|
||||
use futures::StreamExt;
|
||||
use tempfile::TempDir;
|
||||
use yerpc::{RpcClient, RpcSession};
|
||||
|
||||
use super::api::{Accounts, CommandApi};
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn basic_json_rpc_functionality() -> anyhow::Result<()> {
|
||||
let tmp_dir = TempDir::new().unwrap().path().into();
|
||||
let writable = true;
|
||||
let accounts = Accounts::new(tmp_dir, writable).await?;
|
||||
let api = CommandApi::new(accounts);
|
||||
|
||||
let (sender, receiver) = unbounded::<String>();
|
||||
|
||||
let (client, mut rx) = RpcClient::new();
|
||||
let session = RpcSession::new(client, api);
|
||||
tokio::spawn({
|
||||
async move {
|
||||
while let Some(message) = rx.next().await {
|
||||
let message = serde_json::to_string(&message)?;
|
||||
sender.send(message).await?;
|
||||
}
|
||||
let res: Result<(), anyhow::Error> = Ok(());
|
||||
res
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
let request = r#"{"jsonrpc":"2.0","method":"add_account","params":[],"id":1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","id":1,"result":1}"#;
|
||||
session.handle_incoming(request).await;
|
||||
let result = receiver.recv().await?;
|
||||
println!("{result:?}");
|
||||
assert_eq!(result, response.to_owned());
|
||||
}
|
||||
{
|
||||
let request = r#"{"jsonrpc":"2.0","method":"get_all_account_ids","params":[],"id":2}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","id":2,"result":[1]}"#;
|
||||
session.handle_incoming(request).await;
|
||||
let result = receiver.recv().await?;
|
||||
println!("{result:?}");
|
||||
assert_eq!(result, response.to_owned());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_batch_set_config() -> anyhow::Result<()> {
|
||||
let tmp_dir = TempDir::new().unwrap().path().into();
|
||||
let writable = true;
|
||||
let accounts = Accounts::new(tmp_dir, writable).await?;
|
||||
let api = CommandApi::new(accounts);
|
||||
|
||||
let (sender, receiver) = unbounded::<String>();
|
||||
|
||||
let (client, mut rx) = RpcClient::new();
|
||||
let session = RpcSession::new(client, api);
|
||||
tokio::spawn({
|
||||
async move {
|
||||
while let Some(message) = rx.next().await {
|
||||
let message = serde_json::to_string(&message)?;
|
||||
sender.send(message).await?;
|
||||
}
|
||||
let res: Result<(), anyhow::Error> = Ok(());
|
||||
res
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
let request = r#"{"jsonrpc":"2.0","method":"add_account","params":[],"id":1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","id":1,"result":1}"#;
|
||||
session.handle_incoming(request).await;
|
||||
let result = receiver.recv().await?;
|
||||
assert_eq!(result, response.to_owned());
|
||||
}
|
||||
{
|
||||
let request = r#"{"jsonrpc":"2.0","method":"batch_set_config","id":2,"params":[1,{"addr":"","mail_user":"","mail_pw":"","mail_server":"","mail_port":"","mail_security":"","imap_certificate_checks":"","send_user":"","send_pw":"","send_server":"","send_port":"","send_security":"","smtp_certificate_checks":"","socks5_enabled":"0","socks5_host":"","socks5_port":"","socks5_user":"","socks5_password":""}]}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","id":2,"result":null}"#;
|
||||
session.handle_incoming(request).await;
|
||||
let result = receiver.recv().await?;
|
||||
assert_eq!(result, response.to_owned());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
46
deltachat-jsonrpc/src/webserver.rs
Normal file
46
deltachat-jsonrpc/src/webserver.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use axum::{extract::ws::WebSocketUpgrade, response::Response, routing::get, Extension, Router};
|
||||
use yerpc::axum::handle_ws_rpc;
|
||||
use yerpc::{RpcClient, RpcSession};
|
||||
|
||||
mod api;
|
||||
use api::{Accounts, CommandApi};
|
||||
|
||||
const DEFAULT_PORT: u16 = 20808;
|
||||
|
||||
#[tokio::main(flavor = "multi_thread")]
|
||||
async fn main() -> Result<(), std::io::Error> {
|
||||
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());
|
||||
let port = std::env::var("DC_PORT")
|
||||
.map(|port| port.parse::<u16>().expect("DC_PORT must be a number"))
|
||||
.unwrap_or(DEFAULT_PORT);
|
||||
log::info!("Starting with accounts directory `{path}`.");
|
||||
let writable = true;
|
||||
let accounts = Accounts::new(PathBuf::from(&path), writable).await.unwrap();
|
||||
let state = CommandApi::new(accounts);
|
||||
|
||||
let app = Router::new()
|
||||
.route("/ws", get(handler))
|
||||
.layer(Extension(state.clone()));
|
||||
|
||||
tokio::spawn(async move {
|
||||
state.accounts.write().await.start_io().await;
|
||||
});
|
||||
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], port));
|
||||
log::info!("JSON-RPC WebSocket server listening on {}", addr);
|
||||
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handler(ws: WebSocketUpgrade, Extension(api): Extension<CommandApi>) -> Response {
|
||||
let (client, out_receiver) = RpcClient::new();
|
||||
let session = RpcSession::new(client.clone(), api.clone());
|
||||
handle_ws_rpc(ws, out_receiver, session).await
|
||||
}
|
||||
9
deltachat-jsonrpc/typescript/.gitignore
vendored
Normal file
9
deltachat-jsonrpc/typescript/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
node_modules
|
||||
dist
|
||||
test_dist
|
||||
coverage
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
docs
|
||||
accounts
|
||||
generated
|
||||
12
deltachat-jsonrpc/typescript/.npmignore
Normal file
12
deltachat-jsonrpc/typescript/.npmignore
Normal file
@@ -0,0 +1,12 @@
|
||||
node_modules
|
||||
accounts
|
||||
docs
|
||||
coverage
|
||||
yarn*
|
||||
package-lock.json
|
||||
.prettierignore
|
||||
example.html
|
||||
report_api_coverage.mjs
|
||||
scripts
|
||||
dist/example
|
||||
dist/test
|
||||
3
deltachat-jsonrpc/typescript/.prettierignore
Normal file
3
deltachat-jsonrpc/typescript/.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
||||
coverage
|
||||
dist
|
||||
generated
|
||||
1
deltachat-jsonrpc/typescript/deltachat.ts
Normal file
1
deltachat-jsonrpc/typescript/deltachat.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./src/lib.js";
|
||||
56
deltachat-jsonrpc/typescript/example.html
Normal file
56
deltachat-jsonrpc/typescript/example.html
Normal file
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>DeltaChat JSON-RPC example</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: monospace;
|
||||
background: black;
|
||||
color: grey;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 1fr;
|
||||
grid-template-areas: "a a" "b c";
|
||||
}
|
||||
.message {
|
||||
color: red;
|
||||
}
|
||||
#header {
|
||||
grid-area: a;
|
||||
color: white;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
#header a {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
#main {
|
||||
grid-area: b;
|
||||
color: green;
|
||||
}
|
||||
#main h2,
|
||||
#main h3 {
|
||||
color: blue;
|
||||
}
|
||||
#side {
|
||||
grid-area: c;
|
||||
color: #777;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
<script type="module" src="dist/example.bundle.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>DeltaChat JSON-RPC example</h1>
|
||||
<div class="grid">
|
||||
<div id="header"></div>
|
||||
<div id="main"></div>
|
||||
<div id="side"><h2>log</h2></div>
|
||||
</div>
|
||||
<p>
|
||||
Tip: open the dev console and use the client with
|
||||
<code>window.client</code>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
109
deltachat-jsonrpc/typescript/example/example.ts
Normal file
109
deltachat-jsonrpc/typescript/example/example.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { DcEvent, DeltaChat } from "../deltachat.js";
|
||||
|
||||
var SELECTED_ACCOUNT = 0;
|
||||
|
||||
window.addEventListener("DOMContentLoaded", (_event) => {
|
||||
(window as any).selectDeltaAccount = (id: string) => {
|
||||
SELECTED_ACCOUNT = Number(id);
|
||||
window.dispatchEvent(new Event("account-changed"));
|
||||
};
|
||||
console.log("launch run script...");
|
||||
run().catch((err) => console.error("run failed", err));
|
||||
});
|
||||
|
||||
async function run() {
|
||||
const $main = document.getElementById("main")!;
|
||||
const $side = document.getElementById("side")!;
|
||||
const $head = document.getElementById("header")!;
|
||||
|
||||
const client = new DeltaChat("ws://localhost:20808/ws");
|
||||
|
||||
(window as any).client = client.rpc;
|
||||
|
||||
client.on("ALL", (accountId, event) => {
|
||||
onIncomingEvent(accountId, event);
|
||||
});
|
||||
|
||||
window.addEventListener("account-changed", async (_event: Event) => {
|
||||
listChatsForSelectedAccount();
|
||||
});
|
||||
|
||||
await Promise.all([loadAccountsInHeader(), listChatsForSelectedAccount()]);
|
||||
|
||||
async function loadAccountsInHeader() {
|
||||
console.log("load accounts");
|
||||
const accounts = await client.rpc.getAllAccounts();
|
||||
console.log("accounts loaded", accounts);
|
||||
for (const account of accounts) {
|
||||
if (account.kind === "Configured") {
|
||||
write(
|
||||
$head,
|
||||
`<a href="#" onclick="selectDeltaAccount(${account.id})">
|
||||
${account.id}: ${account.addr!}
|
||||
</a> `
|
||||
);
|
||||
} else {
|
||||
write(
|
||||
$head,
|
||||
`<a href="#">
|
||||
${account.id}: (unconfigured)
|
||||
</a> `
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function listChatsForSelectedAccount() {
|
||||
clear($main);
|
||||
const selectedAccount = SELECTED_ACCOUNT;
|
||||
const info = await client.rpc.getAccountInfo(selectedAccount);
|
||||
if (info.kind !== "Configured") {
|
||||
return write($main, "Account is not configured");
|
||||
}
|
||||
write($main, `<h2>${info.addr!}</h2>`);
|
||||
const chats = await client.rpc.getChatlistEntries(
|
||||
selectedAccount,
|
||||
0,
|
||||
null,
|
||||
null
|
||||
);
|
||||
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(
|
||||
selectedAccount,
|
||||
chatId,
|
||||
false,
|
||||
false
|
||||
);
|
||||
const messages = await client.rpc.getMessages(
|
||||
selectedAccount,
|
||||
messageIds
|
||||
);
|
||||
for (const [_messageId, message] of Object.entries(messages)) {
|
||||
if (message.kind === "message") write($main, `<p>${message.text}</p>`);
|
||||
else write($main, `<p>loading error: ${message.error}</p>`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onIncomingEvent(accountId: number, event: DcEvent) {
|
||||
write(
|
||||
$side,
|
||||
`
|
||||
<p class="message">
|
||||
[<strong>${event.kind}</strong> on account ${accountId}]<br>
|
||||
<em>f1:</em> ${JSON.stringify(
|
||||
Object.assign({}, event, { kind: undefined })
|
||||
)}
|
||||
</p>`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function write(el: HTMLElement, html: string) {
|
||||
el.innerHTML += html;
|
||||
}
|
||||
function clear(el: HTMLElement) {
|
||||
el.innerHTML = "";
|
||||
}
|
||||
29
deltachat-jsonrpc/typescript/example/node-add-account.js
Normal file
29
deltachat-jsonrpc/typescript/example/node-add-account.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { DeltaChat } from "../dist/deltachat.js";
|
||||
|
||||
run().catch(console.error);
|
||||
|
||||
async function run() {
|
||||
const delta = new DeltaChat("ws://localhost:20808/ws");
|
||||
delta.on("event", (event) => {
|
||||
console.log("event", event.data);
|
||||
});
|
||||
|
||||
const email = process.argv[2];
|
||||
const password = process.argv[3];
|
||||
if (!email || !password)
|
||||
throw new Error(
|
||||
"USAGE: node node-add-account.js <EMAILADDRESS> <PASSWORD>"
|
||||
);
|
||||
console.log(`creating account for ${email}`);
|
||||
const id = await delta.rpc.addAccount();
|
||||
console.log(`created account id ${id}`);
|
||||
await delta.rpc.setConfig(id, "addr", email);
|
||||
await delta.rpc.setConfig(id, "mail_pw", password);
|
||||
console.log("configuration updated");
|
||||
await delta.rpc.configure(id);
|
||||
console.log("account configured!");
|
||||
|
||||
const accounts = await delta.rpc.getAllAccounts();
|
||||
console.log("accounts", accounts);
|
||||
console.log("waiting for events...");
|
||||
}
|
||||
14
deltachat-jsonrpc/typescript/example/node-demo.js
Normal file
14
deltachat-jsonrpc/typescript/example/node-demo.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { DeltaChat } from "../dist/deltachat.js";
|
||||
|
||||
run().catch(console.error);
|
||||
|
||||
async function run() {
|
||||
const delta = new DeltaChat();
|
||||
delta.on("event", (event) => {
|
||||
console.log("event", event.data);
|
||||
});
|
||||
|
||||
const accounts = await delta.rpc.getAllAccounts();
|
||||
console.log("accounts", accounts);
|
||||
console.log("waiting for events...");
|
||||
}
|
||||
0
deltachat-jsonrpc/typescript/generated/.gitkeep
Normal file
0
deltachat-jsonrpc/typescript/generated/.gitkeep
Normal file
57
deltachat-jsonrpc/typescript/package.json
Normal file
57
deltachat-jsonrpc/typescript/package.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"author": "Delta Chat Developers (ML) <delta@codespeak.net>",
|
||||
"dependencies": {
|
||||
"@deltachat/tiny-emitter": "3.0.0",
|
||||
"isomorphic-ws": "^4.0.1",
|
||||
"yerpc": "^0.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.2.21",
|
||||
"@types/chai-as-promised": "^7.1.5",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/ws": "^7.2.4",
|
||||
"c8": "^7.10.0",
|
||||
"chai": "^4.3.4",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"esbuild": "^0.17.9",
|
||||
"http-server": "^14.1.1",
|
||||
"mocha": "^9.1.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.6.2",
|
||||
"typedoc": "^0.23.2",
|
||||
"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:cjs",
|
||||
"build:bundle": "esbuild --format=esm --bundle dist/deltachat.js --outfile=dist/deltachat.bundle.js",
|
||||
"build:cjs": "esbuild --format=cjs --bundle --packages=external dist/deltachat.js --outfile=dist/deltachat.cjs",
|
||||
"build:tsc": "tsc",
|
||||
"docs": "typedoc --out docs deltachat.ts",
|
||||
"example": "run-s build example:build example:start",
|
||||
"example:build": "esbuild --bundle dist/example/example.js --outfile=dist/example.bundle.js",
|
||||
"example:dev": "esbuild example/example.ts --bundle --outfile=dist/example.bundle.js --servedir=.",
|
||||
"example:start": "http-server .",
|
||||
"extract-constants": "node ./scripts/generate-constants.js",
|
||||
"generate-bindings": "cargo test",
|
||||
"prettier:check": "prettier --check .",
|
||||
"prettier:fix": "prettier --write .",
|
||||
"test": "run-s test:prepare test:run-coverage test:report-coverage",
|
||||
"test:prepare": "cargo build --package deltachat-rpc-server --bin deltachat-rpc-server",
|
||||
"test:report-coverage": "node report_api_coverage.mjs",
|
||||
"test:run": "mocha dist/test",
|
||||
"test:run-coverage": "COVERAGE=1 NODE_OPTIONS=--enable-source-maps c8 --include 'dist/*' -r text -r html -r json mocha dist/test"
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "1.137.2"
|
||||
}
|
||||
28
deltachat-jsonrpc/typescript/report_api_coverage.mjs
Normal file
28
deltachat-jsonrpc/typescript/report_api_coverage.mjs
Normal file
@@ -0,0 +1,28 @@
|
||||
import { readFileSync } from "fs";
|
||||
// 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 =
|
||||
json[Object.keys(json).find((k) => k.includes(generatedFile))];
|
||||
const fnMap = Object.keys(jsonCoverage.fnMap).map(
|
||||
(key) => jsonCoverage.fnMap[key]
|
||||
);
|
||||
const htmlCoverage = readFileSync(
|
||||
"./coverage/" + generatedFile + ".html",
|
||||
"utf8"
|
||||
);
|
||||
const uncoveredLines = htmlCoverage
|
||||
.split("\n")
|
||||
.filter((line) => line.includes(`"function not covered"`));
|
||||
const uncoveredFunctions = uncoveredLines.map(
|
||||
(line) => />([\w_]+)\(/.exec(line)[1]
|
||||
);
|
||||
console.log(
|
||||
"\nUncovered api functions:\n" +
|
||||
uncoveredFunctions
|
||||
.map((uF) => fnMap.find(({ name }) => name === uF))
|
||||
.map(
|
||||
({ name, line }) => `.${name.padEnd(40)} (${generatedFile}:${line})`
|
||||
)
|
||||
.join("\n")
|
||||
);
|
||||
54
deltachat-jsonrpc/typescript/scripts/generate-constants.js
Executable file
54
deltachat-jsonrpc/typescript/scripts/generate-constants.js
Executable file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env node
|
||||
import { readFileSync, writeFileSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from "path";
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const data = [];
|
||||
const header = resolve(__dirname, "../../../deltachat-ffi/deltachat.h");
|
||||
|
||||
console.log("Generating constants...");
|
||||
|
||||
const header_data = readFileSync(header, "UTF-8");
|
||||
const regex = /^#define\s+(\w+)\s+(\w+)/gm;
|
||||
let match;
|
||||
while (null != (match = regex.exec(header_data))) {
|
||||
const key = match[1];
|
||||
const value = parseInt(match[2]);
|
||||
if (!isNaN(value)) {
|
||||
data.push({ key, value });
|
||||
}
|
||||
}
|
||||
|
||||
const constants = data
|
||||
.filter(
|
||||
({ key }) => key.toUpperCase()[0] === key[0] // check if define name is uppercase
|
||||
)
|
||||
.sort((lhs, rhs) => {
|
||||
if (lhs.key < rhs.key) return -1;
|
||||
else if (lhs.key > rhs.key) return 1;
|
||||
return 0;
|
||||
})
|
||||
.filter(({ key }) => {
|
||||
// filter out what we don't need it
|
||||
return !(
|
||||
key.startsWith("DC_EVENT_") ||
|
||||
key.startsWith("DC_IMEX_") ||
|
||||
key.startsWith("DC_CHAT_VISIBILITY") ||
|
||||
key.startsWith("DC_DOWNLOAD") ||
|
||||
key.startsWith("DC_INFO_") ||
|
||||
(key.startsWith("DC_MSG") && !key.startsWith("DC_MSG_ID")) ||
|
||||
key.startsWith("DC_QR_")
|
||||
);
|
||||
})
|
||||
.map((row) => {
|
||||
return ` ${row.key}: ${row.value}`;
|
||||
})
|
||||
.join(",\n");
|
||||
|
||||
writeFileSync(
|
||||
resolve(__dirname, "../generated/constants.ts"),
|
||||
`// Generated!\n\nexport enum C {\n${constants.replace(/:/g, " =")},\n}\n`
|
||||
);
|
||||
134
deltachat-jsonrpc/typescript/src/client.ts
Normal file
134
deltachat-jsonrpc/typescript/src/client.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
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 { WebsocketTransport, BaseTransport, Request } from "yerpc";
|
||||
import { TinyEmitter } from "@deltachat/tiny-emitter";
|
||||
|
||||
type Events = { ALL: (accountId: number, event: EventType) => void } & {
|
||||
[Property in EventType["kind"]]: (
|
||||
accountId: number,
|
||||
event: Extract<EventType, { kind: Property }>
|
||||
) => void;
|
||||
};
|
||||
|
||||
type ContextEvents = { ALL: (event: EventType) => void } & {
|
||||
[Property in EventType["kind"]]: (
|
||||
event: Extract<EventType, { kind: Property }>
|
||||
) => void;
|
||||
};
|
||||
|
||||
export type DcEvent = EventType;
|
||||
export type DcEventType<T extends EventType["kind"]> = Extract<
|
||||
EventType,
|
||||
{ kind: T }
|
||||
>;
|
||||
|
||||
export class BaseDeltaChat<
|
||||
Transport extends BaseTransport<any>
|
||||
> extends TinyEmitter<Events> {
|
||||
rpc: RawClient;
|
||||
account?: T.Account;
|
||||
private contextEmitters: { [key: number]: TinyEmitter<ContextEvents> } = {};
|
||||
|
||||
//@ts-ignore
|
||||
private eventTask: Promise<void>;
|
||||
|
||||
constructor(public transport: Transport, startEventLoop: boolean) {
|
||||
super();
|
||||
this.rpc = new RawClient(this.transport);
|
||||
if (startEventLoop) {
|
||||
this.eventTask = this.eventLoop();
|
||||
}
|
||||
}
|
||||
|
||||
async eventLoop(): Promise<void> {
|
||||
while (true) {
|
||||
const event = await this.rpc.getNextEvent();
|
||||
//@ts-ignore
|
||||
this.emit(event.event.kind, event.contextId, event.event);
|
||||
this.emit("ALL", event.contextId, event.event);
|
||||
|
||||
if (this.contextEmitters[event.contextId]) {
|
||||
this.contextEmitters[event.contextId].emit(
|
||||
event.event.kind,
|
||||
//@ts-ignore
|
||||
event.event as any
|
||||
);
|
||||
this.contextEmitters[event.contextId].emit("ALL", event.event as any);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async listAccounts(): Promise<T.Account[]> {
|
||||
return await this.rpc.getAllAccounts();
|
||||
}
|
||||
|
||||
getContextEvents(account_id: number) {
|
||||
if (this.contextEmitters[account_id]) {
|
||||
return this.contextEmitters[account_id];
|
||||
} else {
|
||||
this.contextEmitters[account_id] = new TinyEmitter();
|
||||
return this.contextEmitters[account_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
close() {
|
||||
this.transport.close();
|
||||
}
|
||||
constructor(opts?: Opts | string) {
|
||||
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, opts.startEventLoop);
|
||||
this.opts = opts;
|
||||
}
|
||||
}
|
||||
|
||||
export class StdioDeltaChat extends BaseDeltaChat<StdioTransport> {
|
||||
close() {}
|
||||
constructor(input: any, output: any, startEventLoop: boolean) {
|
||||
const transport = new StdioTransport(input, output);
|
||||
super(transport, startEventLoop);
|
||||
}
|
||||
}
|
||||
|
||||
export class StdioTransport extends BaseTransport {
|
||||
constructor(public input: any, public output: any) {
|
||||
super();
|
||||
|
||||
var buffer = "";
|
||||
this.output.on("data", (data: any) => {
|
||||
buffer += data.toString();
|
||||
while (buffer.includes("\n")) {
|
||||
const n = buffer.indexOf("\n");
|
||||
const line = buffer.substring(0, n);
|
||||
const message = JSON.parse(line);
|
||||
this._onmessage(message);
|
||||
buffer = buffer.substring(n + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_send(message: any): void {
|
||||
const serialized = JSON.stringify(message);
|
||||
this.input.write(serialized + "\n");
|
||||
}
|
||||
}
|
||||
6
deltachat-jsonrpc/typescript/src/lib.ts
Normal file
6
deltachat-jsonrpc/typescript/src/lib.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * as RPC from "../generated/jsonrpc.js";
|
||||
export * as T from "../generated/types.js";
|
||||
export { RawClient } from "../generated/client.js";
|
||||
export * from "./client.js";
|
||||
export * as yerpc from "yerpc";
|
||||
export { C } from "../generated/constants.js";
|
||||
161
deltachat-jsonrpc/typescript/test/basic.ts
Normal file
161
deltachat-jsonrpc/typescript/test/basic.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { strictEqual } from "assert";
|
||||
import chai, { assert, expect } from "chai";
|
||||
import chaiAsPromised from "chai-as-promised";
|
||||
chai.use(chaiAsPromised);
|
||||
import { StdioDeltaChat as DeltaChat } from "../deltachat.js";
|
||||
|
||||
import { RpcServerHandle, startServer } from "./test_base.js";
|
||||
|
||||
describe("basic tests", () => {
|
||||
let serverHandle: RpcServerHandle;
|
||||
let dc: DeltaChat;
|
||||
|
||||
before(async () => {
|
||||
serverHandle = await startServer();
|
||||
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout, true);
|
||||
// dc.on("ALL", (event) => {
|
||||
//console.log("event", event);
|
||||
// });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
dc && dc.close();
|
||||
await serverHandle.close();
|
||||
});
|
||||
|
||||
it("check email address validity", async () => {
|
||||
const validAddresses = [
|
||||
"email@example.com",
|
||||
"36aa165ae3406424e0c61af17700f397cad3fe8ab83d682d0bddf3338a5dd52e@yggmail@yggmail",
|
||||
];
|
||||
const invalidAddresses = ["email@", "example.com", "emai221"];
|
||||
|
||||
expect(
|
||||
await Promise.all(
|
||||
validAddresses.map((email) => dc.rpc.checkEmailValidity(email))
|
||||
)
|
||||
).to.not.contain(false);
|
||||
|
||||
expect(
|
||||
await Promise.all(
|
||||
invalidAddresses.map((email) => dc.rpc.checkEmailValidity(email))
|
||||
)
|
||||
).to.not.contain(true);
|
||||
});
|
||||
|
||||
it("system info", async () => {
|
||||
const systemInfo = await dc.rpc.getSystemInfo();
|
||||
expect(systemInfo).to.contain.keys([
|
||||
"arch",
|
||||
"num_cpus",
|
||||
"deltachat_core_version",
|
||||
"sqlite_version",
|
||||
]);
|
||||
});
|
||||
|
||||
describe("account management", () => {
|
||||
it("should create account", async () => {
|
||||
const res = await dc.rpc.addAccount();
|
||||
assert((await dc.rpc.getAllAccountIds()).length === 1);
|
||||
});
|
||||
|
||||
it("should remove the account again", async () => {
|
||||
await dc.rpc.removeAccount((await dc.rpc.getAllAccountIds())[0]);
|
||||
assert((await dc.rpc.getAllAccountIds()).length === 0);
|
||||
});
|
||||
|
||||
it("should create multiple accounts", async () => {
|
||||
await dc.rpc.addAccount();
|
||||
await dc.rpc.addAccount();
|
||||
await dc.rpc.addAccount();
|
||||
await dc.rpc.addAccount();
|
||||
assert((await dc.rpc.getAllAccountIds()).length === 4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("contact management", function () {
|
||||
let accountId: number;
|
||||
before(async () => {
|
||||
accountId = await dc.rpc.addAccount();
|
||||
});
|
||||
it("should block and unblock contact", async function () {
|
||||
// Cannot send sync messages to self as we do not have a self address.
|
||||
await dc.rpc.setConfig(accountId, "sync_msgs", "0");
|
||||
|
||||
const contactId = await dc.rpc.createContact(
|
||||
accountId,
|
||||
"example@delta.chat",
|
||||
null
|
||||
);
|
||||
expect((await dc.rpc.getContact(accountId, contactId)).isBlocked).to.be
|
||||
.false;
|
||||
await dc.rpc.blockContact(accountId, contactId);
|
||||
expect((await dc.rpc.getContact(accountId, contactId)).isBlocked).to.be
|
||||
.true;
|
||||
expect(await dc.rpc.getBlockedContacts(accountId)).to.have.length(1);
|
||||
await dc.rpc.unblockContact(accountId, contactId);
|
||||
expect((await dc.rpc.getContact(accountId, contactId)).isBlocked).to.be
|
||||
.false;
|
||||
expect(await dc.rpc.getBlockedContacts(accountId)).to.have.length(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("configuration", function () {
|
||||
let accountId: number;
|
||||
before(async () => {
|
||||
accountId = await dc.rpc.addAccount();
|
||||
});
|
||||
|
||||
it("set and retrieve", async function () {
|
||||
await dc.rpc.setConfig(accountId, "addr", "valid@email");
|
||||
assert((await dc.rpc.getConfig(accountId, "addr")) == "valid@email");
|
||||
});
|
||||
it("set invalid key should throw", async function () {
|
||||
await expect(dc.rpc.setConfig(accountId, "invalid_key", "some value")).to
|
||||
.be.eventually.rejected;
|
||||
});
|
||||
it("get invalid key should throw", async function () {
|
||||
await expect(dc.rpc.getConfig(accountId, "invalid_key")).to.be.eventually
|
||||
.rejected;
|
||||
});
|
||||
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 retrieve (batch)", async function () {
|
||||
const config = { addr: "valid@email", mail_pw: "1234" };
|
||||
await dc.rpc.batchSetConfig(accountId, config);
|
||||
const retrieved = await dc.rpc.batchGetConfig(
|
||||
accountId,
|
||||
Object.keys(config)
|
||||
);
|
||||
expect(retrieved).to.deep.equal(config);
|
||||
});
|
||||
it("set and retrieve ui.* (batch)", async function () {
|
||||
const config = {
|
||||
"ui.chat_bg": "color:green",
|
||||
"ui.enter_key_sends": "true",
|
||||
};
|
||||
await dc.rpc.batchSetConfig(accountId, config);
|
||||
const retrieved = await dc.rpc.batchGetConfig(
|
||||
accountId,
|
||||
Object.keys(config)
|
||||
);
|
||||
expect(retrieved).to.deep.equal(config);
|
||||
});
|
||||
it("set and retrieve mixed(ui and core) (batch)", async function () {
|
||||
const config = {
|
||||
"ui.chat_bg": "color:yellow",
|
||||
"ui.enter_key_sends": "false",
|
||||
addr: "valid2@email",
|
||||
mail_pw: "123456",
|
||||
};
|
||||
await dc.rpc.batchSetConfig(accountId, config);
|
||||
const retrieved = await dc.rpc.batchGetConfig(
|
||||
accountId,
|
||||
Object.keys(config)
|
||||
);
|
||||
expect(retrieved).to.deep.equal(config);
|
||||
});
|
||||
});
|
||||
});
|
||||
200
deltachat-jsonrpc/typescript/test/online.ts
Normal file
200
deltachat-jsonrpc/typescript/test/online.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { assert, expect } from "chai";
|
||||
import { StdioDeltaChat as DeltaChat, DcEvent } from "../deltachat.js";
|
||||
import { RpcServerHandle, createTempUser, startServer } from "./test_base.js";
|
||||
|
||||
const EVENT_TIMEOUT = 20000;
|
||||
|
||||
describe("online tests", function () {
|
||||
let serverHandle: RpcServerHandle;
|
||||
let dc: DeltaChat;
|
||||
let account1: { email: string; password: string };
|
||||
let account2: { email: string; password: string };
|
||||
let accountId1: number, accountId2: number;
|
||||
|
||||
before(async function () {
|
||||
this.timeout(60000);
|
||||
if (!process.env.CHATMAIL_DOMAIN) {
|
||||
if (process.env.COVERAGE && !process.env.COVERAGE_OFFLINE) {
|
||||
console.error(
|
||||
"CAN NOT RUN COVERAGE correctly: Missing CHATMAIL_DOMAIN environment variable!\n\n",
|
||||
"You can set COVERAGE_OFFLINE=1 to circumvent this check and skip the online tests, but those coverage results will be wrong, because some functions can only be tested in the online test"
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(
|
||||
"Missing CHATMAIL_DOMAIN environment variable!, skip integration tests"
|
||||
);
|
||||
this.skip();
|
||||
}
|
||||
serverHandle = await startServer();
|
||||
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout, true);
|
||||
|
||||
dc.on("ALL", (contextId, { kind }) => {
|
||||
if (kind !== "Info") console.log(contextId, kind);
|
||||
});
|
||||
|
||||
account1 = createTempUser(process.env.CHATMAIL_DOMAIN);
|
||||
if (!account1 || !account1.email || !account1.password) {
|
||||
console.log(
|
||||
"We didn't got back an account from the api, skip integration tests"
|
||||
);
|
||||
this.skip();
|
||||
}
|
||||
|
||||
account2 = createTempUser(process.env.CHATMAIL_DOMAIN);
|
||||
if (!account2 || !account2.email || !account2.password) {
|
||||
console.log(
|
||||
"We didn't got back an account2 from the api, skip integration tests"
|
||||
);
|
||||
this.skip();
|
||||
}
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
dc && dc.close();
|
||||
serverHandle && (await serverHandle.close());
|
||||
});
|
||||
|
||||
let accountsConfigured = false;
|
||||
|
||||
it("configure test accounts", async function () {
|
||||
this.timeout(40000);
|
||||
|
||||
accountId1 = await dc.rpc.addAccount();
|
||||
await dc.rpc.setConfig(accountId1, "addr", account1.email);
|
||||
await dc.rpc.setConfig(accountId1, "mail_pw", account1.password);
|
||||
await dc.rpc.configure(accountId1);
|
||||
|
||||
accountId2 = await dc.rpc.addAccount();
|
||||
await dc.rpc.batchSetConfig(accountId2, {
|
||||
addr: account2.email,
|
||||
mail_pw: account2.password,
|
||||
});
|
||||
await dc.rpc.configure(accountId2);
|
||||
accountsConfigured = true;
|
||||
});
|
||||
|
||||
it("send and receive text message", async function () {
|
||||
if (!accountsConfigured) {
|
||||
this.skip();
|
||||
}
|
||||
this.timeout(15000);
|
||||
|
||||
const contactId = await dc.rpc.createContact(
|
||||
accountId1,
|
||||
account2.email,
|
||||
null
|
||||
);
|
||||
const chatId = await dc.rpc.createChatByContactId(accountId1, contactId);
|
||||
const eventPromise = Promise.race([
|
||||
waitForEvent(dc, "MsgsChanged", accountId2),
|
||||
waitForEvent(dc, "IncomingMsg", accountId2),
|
||||
]);
|
||||
|
||||
await dc.rpc.miscSendTextMessage(accountId1, chatId, "Hello");
|
||||
const { chatId: chatIdOnAccountB } = await eventPromise;
|
||||
await dc.rpc.acceptChat(accountId2, chatIdOnAccountB);
|
||||
const messageList = await dc.rpc.getMessageIds(
|
||||
accountId2,
|
||||
chatIdOnAccountB,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
expect(messageList).have.length(1);
|
||||
const message = await dc.rpc.getMessage(accountId2, messageList[0]);
|
||||
expect(message.text).equal("Hello");
|
||||
});
|
||||
|
||||
it("send and receive text message roundtrip, encrypted on answer onwards", async function () {
|
||||
if (!accountsConfigured) {
|
||||
this.skip();
|
||||
}
|
||||
this.timeout(10000);
|
||||
|
||||
// send message from A to B
|
||||
const contactId = await dc.rpc.createContact(
|
||||
accountId1,
|
||||
account2.email,
|
||||
null
|
||||
);
|
||||
const chatId = await dc.rpc.createChatByContactId(accountId1, contactId);
|
||||
const eventPromise = Promise.race([
|
||||
waitForEvent(dc, "MsgsChanged", accountId2),
|
||||
waitForEvent(dc, "IncomingMsg", accountId2),
|
||||
]);
|
||||
dc.rpc.miscSendTextMessage(accountId1, chatId, "Hello2");
|
||||
// wait for message from A
|
||||
console.log("wait for message from A");
|
||||
|
||||
const event = await eventPromise;
|
||||
const { chatId: chatIdOnAccountB } = event;
|
||||
|
||||
await dc.rpc.acceptChat(accountId2, chatIdOnAccountB);
|
||||
const messageList = await dc.rpc.getMessageIds(
|
||||
accountId2,
|
||||
chatIdOnAccountB,
|
||||
false,
|
||||
false
|
||||
);
|
||||
const message = await dc.rpc.getMessage(
|
||||
accountId2,
|
||||
messageList.reverse()[0]
|
||||
);
|
||||
expect(message.text).equal("Hello2");
|
||||
// Send message back from B to A
|
||||
const eventPromise2 = Promise.race([
|
||||
waitForEvent(dc, "MsgsChanged", accountId1),
|
||||
waitForEvent(dc, "IncomingMsg", accountId1),
|
||||
]);
|
||||
dc.rpc.miscSendTextMessage(accountId2, chatId, "super secret message");
|
||||
// Check if answer arrives at A and if it is encrypted
|
||||
await eventPromise2;
|
||||
|
||||
const messageId = (
|
||||
await dc.rpc.getMessageIds(accountId1, chatId, false, false)
|
||||
).reverse()[0];
|
||||
const message2 = await dc.rpc.getMessage(accountId1, messageId);
|
||||
expect(message2.text).equal("super secret message");
|
||||
expect(message2.showPadlock).equal(true);
|
||||
});
|
||||
|
||||
it("get provider info for example.com", async () => {
|
||||
const acc = await dc.rpc.addAccount();
|
||||
const info = await dc.rpc.getProviderInfo(acc, "example.com");
|
||||
expect(info).to.be.not.null;
|
||||
expect(info?.overviewPage).to.equal(
|
||||
"https://providers.delta.chat/example-com"
|
||||
);
|
||||
expect(info?.status).to.equal(3);
|
||||
});
|
||||
|
||||
it("get provider info - domain and email should give same result", async () => {
|
||||
const acc = await dc.rpc.addAccount();
|
||||
const info_domain = await dc.rpc.getProviderInfo(acc, "example.com");
|
||||
const info_email = await dc.rpc.getProviderInfo(acc, "hi@example.com");
|
||||
expect(info_email).to.deep.equal(info_domain);
|
||||
});
|
||||
});
|
||||
|
||||
async function waitForEvent<T extends DcEvent["kind"]>(
|
||||
dc: DeltaChat,
|
||||
eventType: T,
|
||||
accountId: number,
|
||||
timeout: number = EVENT_TIMEOUT
|
||||
): Promise<Extract<DcEvent, { kind: T }>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const rejectTimeout = setTimeout(
|
||||
() => reject(new Error("Timeout reached before event came in")),
|
||||
timeout
|
||||
);
|
||||
const callback = (contextId: number, event: DcEvent) => {
|
||||
if (contextId == accountId) {
|
||||
dc.off(eventType, callback);
|
||||
clearTimeout(rejectTimeout);
|
||||
resolve(event as any);
|
||||
}
|
||||
};
|
||||
dc.on(eventType, callback);
|
||||
});
|
||||
}
|
||||
89
deltachat-jsonrpc/typescript/test/test_base.ts
Normal file
89
deltachat-jsonrpc/typescript/test/test_base.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { tmpdir } from "os";
|
||||
import { join, resolve } from "path";
|
||||
import { mkdtemp, rm } from "fs/promises";
|
||||
import { spawn, exec } from "child_process";
|
||||
import { Readable, Writable } from "node:stream";
|
||||
|
||||
export type RpcServerHandle = {
|
||||
stdin: Writable;
|
||||
stdout: Readable;
|
||||
close: () => Promise<void>;
|
||||
};
|
||||
|
||||
export async function startServer(): Promise<RpcServerHandle> {
|
||||
const tmpDir = await mkdtemp(join(tmpdir(), "deltachat-jsonrpc-test"));
|
||||
|
||||
const pathToServerBinary = resolve(
|
||||
join(await getTargetDir(), "debug/deltachat-rpc-server")
|
||||
);
|
||||
|
||||
const server = spawn(pathToServerBinary, {
|
||||
cwd: tmpDir,
|
||||
env: {
|
||||
RUST_LOG: process.env.RUST_LOG || "info",
|
||||
RUST_MIN_STACK: "8388608",
|
||||
},
|
||||
});
|
||||
|
||||
server.on("error", (err) => {
|
||||
throw new Error(
|
||||
"Failed to start server executable " +
|
||||
pathToServerBinary +
|
||||
", make sure you built it first."
|
||||
);
|
||||
});
|
||||
let shouldClose = false;
|
||||
|
||||
server.on("exit", () => {
|
||||
if (shouldClose) {
|
||||
return;
|
||||
}
|
||||
throw new Error("Server quit");
|
||||
});
|
||||
|
||||
server.stderr.pipe(process.stderr);
|
||||
|
||||
return {
|
||||
stdin: server.stdin,
|
||||
stdout: server.stdout,
|
||||
close: async () => {
|
||||
shouldClose = true;
|
||||
if (!server.kill()) {
|
||||
console.log("server termination failed");
|
||||
}
|
||||
await rm(tmpDir, { recursive: true });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createTempUser(chatmailDomain: String) {
|
||||
const charset = "2345789acdefghjkmnpqrstuvwxyz";
|
||||
let user = "ci-";
|
||||
for (let i = 0; i < 6; i++) {
|
||||
user += charset[Math.floor(Math.random() * charset.length)];
|
||||
}
|
||||
const email = user + "@" + chatmailDomain;
|
||||
return { email: email, password: user + "$" + user };
|
||||
}
|
||||
|
||||
function getTargetDir(): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(
|
||||
"cargo metadata --no-deps --format-version 1",
|
||||
(error, stdout, _stderr) => {
|
||||
if (error) {
|
||||
console.log("error", error);
|
||||
reject(error);
|
||||
} else {
|
||||
try {
|
||||
const json = JSON.parse(stdout);
|
||||
resolve(json.target_directory);
|
||||
} catch (error) {
|
||||
console.log("json error", error);
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
20
deltachat-jsonrpc/typescript/tsconfig.json
Normal file
20
deltachat-jsonrpc/typescript/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"alwaysStrict": true,
|
||||
"strict": true,
|
||||
"sourceMap": true,
|
||||
"strictNullChecks": true,
|
||||
"rootDir": ".",
|
||||
"outDir": "dist",
|
||||
"lib": ["ES2017", "dom"],
|
||||
"target": "ES2017",
|
||||
"module": "es2020",
|
||||
"declaration": true,
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["*.ts", "example/*.ts", "test/*.ts"],
|
||||
"compileOnSave": false
|
||||
}
|
||||
8
deltachat-ratelimit/Cargo.toml
Normal file
8
deltachat-ratelimit/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "ratelimit"
|
||||
version = "1.0.0"
|
||||
description = "Token bucket implementation"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[dependencies]
|
||||
134
deltachat-ratelimit/src/lib.rs
Normal file
134
deltachat-ratelimit/src/lib.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
//! # Rate limiting module.
|
||||
//!
|
||||
//! This module contains implementation of token bucket policy.
|
||||
//! Its primary use is preventing Delta Chat from sending too many messages, especially automatic,
|
||||
//! such as read receipts.
|
||||
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Ratelimit {
|
||||
/// Time of the last update.
|
||||
last_update: SystemTime,
|
||||
|
||||
/// Number of messages sent within the time window ending at `last_update`.
|
||||
current_value: f64,
|
||||
|
||||
/// Time window size.
|
||||
window: Duration,
|
||||
|
||||
/// Number of messages allowed to send within the time window.
|
||||
quota: f64,
|
||||
}
|
||||
|
||||
impl Ratelimit {
|
||||
/// Returns a new rate limiter with the given constraints.
|
||||
///
|
||||
/// Rate limiter will allow to send no more than `quota` messages within duration `window`.
|
||||
pub fn new(window: Duration, quota: f64) -> Self {
|
||||
Self::new_at(window, quota, SystemTime::now())
|
||||
}
|
||||
|
||||
/// Returns a new rate limiter with given current time for testing purposes.
|
||||
fn new_at(window: Duration, quota: f64, now: SystemTime) -> Self {
|
||||
Self {
|
||||
last_update: now,
|
||||
current_value: 0.0,
|
||||
window,
|
||||
quota,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns current number of sent messages.
|
||||
fn current_value_at(&self, now: SystemTime) -> f64 {
|
||||
let rate: f64 = self.quota / self.window.as_secs_f64();
|
||||
let elapsed = now
|
||||
.duration_since(self.last_update)
|
||||
.unwrap_or(Duration::ZERO)
|
||||
.as_secs_f64();
|
||||
f64::max(0.0, self.current_value - rate * elapsed)
|
||||
}
|
||||
|
||||
/// Returns true if it is allowed to send a message.
|
||||
fn can_send_at(&self, now: SystemTime) -> bool {
|
||||
self.current_value_at(now) + 1.0 <= self.quota
|
||||
}
|
||||
|
||||
/// Returns true if can send another message now.
|
||||
///
|
||||
/// This method takes mutable reference
|
||||
pub fn can_send(&self) -> bool {
|
||||
self.can_send_at(SystemTime::now())
|
||||
}
|
||||
|
||||
fn send_at(&mut self, now: SystemTime) {
|
||||
self.current_value = f64::min(self.quota, self.current_value_at(now) + 1.0);
|
||||
self.last_update = now;
|
||||
}
|
||||
|
||||
/// Increases current usage value.
|
||||
///
|
||||
/// It is possible to send message even if over quota, e.g. if the message sending is initiated
|
||||
/// by the user and should not be rate limited. However, sending messages when over quota
|
||||
/// further postpones the time when it will be allowed to send low priority messages.
|
||||
pub fn send(&mut self) {
|
||||
self.send_at(SystemTime::now())
|
||||
}
|
||||
|
||||
fn until_can_send_at(&self, now: SystemTime) -> Duration {
|
||||
let current_value = self.current_value_at(now);
|
||||
if current_value + 1.0 <= self.quota {
|
||||
Duration::ZERO
|
||||
} else {
|
||||
let requirement = current_value + 1.0 - self.quota;
|
||||
let rate = self.quota / self.window.as_secs_f64();
|
||||
Duration::from_secs_f64(requirement / rate)
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the time until `can_send` will return `true`.
|
||||
pub fn until_can_send(&self) -> Duration {
|
||||
self.until_can_send_at(SystemTime::now())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_ratelimit() {
|
||||
let now = SystemTime::now();
|
||||
|
||||
let mut ratelimit = Ratelimit::new_at(Duration::new(60, 0), 3.0, now);
|
||||
assert!(ratelimit.can_send_at(now));
|
||||
|
||||
// Send burst of 3 messages.
|
||||
ratelimit.send_at(now);
|
||||
assert!(ratelimit.can_send_at(now));
|
||||
ratelimit.send_at(now);
|
||||
assert!(ratelimit.can_send_at(now));
|
||||
ratelimit.send_at(now);
|
||||
|
||||
// Can't send more messages now.
|
||||
assert!(!ratelimit.can_send_at(now));
|
||||
|
||||
// Can send one more message 20 seconds later.
|
||||
assert_eq!(ratelimit.until_can_send_at(now), Duration::from_secs(20));
|
||||
let now = now + Duration::from_secs(20);
|
||||
assert!(ratelimit.can_send_at(now));
|
||||
ratelimit.send_at(now);
|
||||
assert!(!ratelimit.can_send_at(now));
|
||||
|
||||
// Send one more message anyway, over quota.
|
||||
ratelimit.send_at(now);
|
||||
|
||||
// Always can send another message after 20 seconds,
|
||||
// leaky bucket never overflows.
|
||||
let now = now + Duration::from_secs(20);
|
||||
assert!(ratelimit.can_send_at(now));
|
||||
|
||||
// Test that we don't panic if time appears to move backwards
|
||||
assert!(!ratelimit.can_send_at(now - Duration::from_secs(20)));
|
||||
}
|
||||
}
|
||||
21
deltachat-repl/Cargo.toml
Normal file
21
deltachat-repl/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "deltachat-repl"
|
||||
version = "1.137.2"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/deltachat/deltachat-core-rust"
|
||||
|
||||
[dependencies]
|
||||
ansi_term = "0.12.1"
|
||||
anyhow = "1"
|
||||
deltachat = { path = "..", features = ["internals"]}
|
||||
dirs = "5"
|
||||
log = "0.4.21"
|
||||
pretty_env_logger = "0.5"
|
||||
rusqlite = "0.31"
|
||||
rustyline = "14"
|
||||
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
vendored = ["deltachat/vendored"]
|
||||
@@ -1,9 +1,11 @@
|
||||
#![allow(clippy::format_push_string)]
|
||||
extern crate dirs;
|
||||
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{bail, ensure, Result};
|
||||
use async_std::path::Path;
|
||||
use deltachat::chat::{
|
||||
self, Chat, ChatId, ChatItem, ChatVisibility, MuteDuration, ProtectionStatus,
|
||||
};
|
||||
@@ -11,38 +13,30 @@ use deltachat::chatlist::*;
|
||||
use deltachat::constants::*;
|
||||
use deltachat::contact::*;
|
||||
use deltachat::context::*;
|
||||
use deltachat::dc_receive_imf::*;
|
||||
use deltachat::dc_tools::*;
|
||||
use deltachat::download::DownloadState;
|
||||
use deltachat::imex::*;
|
||||
use deltachat::location;
|
||||
use deltachat::log::LogExt;
|
||||
use deltachat::message::{self, Message, MessageState, MsgId};
|
||||
use deltachat::message::{self, Message, MessageState, MsgId, Viewtype};
|
||||
use deltachat::mimeparser::SystemMessage;
|
||||
use deltachat::peerstate::*;
|
||||
use deltachat::qr::*;
|
||||
use deltachat::reaction::send_reaction;
|
||||
use deltachat::receive_imf::*;
|
||||
use deltachat::sql;
|
||||
use deltachat::EventType;
|
||||
use deltachat::tools::*;
|
||||
use deltachat::{config, provider};
|
||||
use std::fs;
|
||||
use std::time::{Duration, SystemTime};
|
||||
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![])
|
||||
.await
|
||||
.unwrap();
|
||||
println!("(1) Jobs reset.");
|
||||
}
|
||||
println!("Resetting tables ({bits})...");
|
||||
if 0 != bits & 2 {
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM acpeerstates;", paramsv![])
|
||||
.execute("DELETE FROM acpeerstates;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
println!("(2) Peerstates reset.");
|
||||
@@ -50,7 +44,7 @@ async fn reset_tables(context: &Context, bits: i32) {
|
||||
if 0 != bits & 4 {
|
||||
context
|
||||
.sql()
|
||||
.execute("DELETE FROM keypairs;", paramsv![])
|
||||
.execute("DELETE FROM keypairs;", ())
|
||||
.await
|
||||
.unwrap();
|
||||
println!("(4) Private keypairs reset.");
|
||||
@@ -58,51 +52,49 @@ 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.");
|
||||
}
|
||||
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
context.emit_msgs_changed_without_ids();
|
||||
}
|
||||
|
||||
async fn poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<()> {
|
||||
let data = dc_read_file(context, filename).await?;
|
||||
let data = read_file(context, filename).await?;
|
||||
|
||||
if let Err(err) = dc_receive_imf(context, &data, "import", 0, false).await {
|
||||
println!("dc_receive_imf errored: {:?}", err);
|
||||
if let Err(err) = receive_imf(context, &data, false).await {
|
||||
println!("receive_imf errored: {err:?}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -131,42 +123,34 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool {
|
||||
}
|
||||
real_spec = rs.unwrap();
|
||||
}
|
||||
if let Some(suffix) = dc_get_filesuffix_lc(&real_spec) {
|
||||
if let Some(suffix) = get_filesuffix_lc(&real_spec) {
|
||||
if suffix == "eml" && poke_eml_file(context, &real_spec).await.is_ok() {
|
||||
read_cnt += 1
|
||||
}
|
||||
} else {
|
||||
/* import a directory */
|
||||
let dir_name = std::path::Path::new(&real_spec);
|
||||
let dir = std::fs::read_dir(dir_name);
|
||||
if dir.is_err() {
|
||||
error!(context, "Import: Cannot open directory \"{}\".", &real_spec,);
|
||||
return false;
|
||||
} else {
|
||||
let dir = dir.unwrap();
|
||||
for entry in dir {
|
||||
if entry.is_err() {
|
||||
break;
|
||||
}
|
||||
let entry = entry.unwrap();
|
||||
let dir = fs::read_dir(dir_name).await;
|
||||
if let Ok(mut dir) = dir {
|
||||
while let Ok(Some(entry)) = dir.next_entry().await {
|
||||
let name_f = entry.file_name();
|
||||
let name = name_f.to_string_lossy();
|
||||
if name.ends_with(".eml") {
|
||||
let path_plus_name = format!("{}/{}", &real_spec, name);
|
||||
println!("Import: {}", path_plus_name);
|
||||
println!("Import: {path_plus_name}");
|
||||
if poke_eml_file(context, path_plus_name).await.is_ok() {
|
||||
read_cnt += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!(context, "Import: Cannot open directory \"{}\".", &real_spec);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
println!("Import: {} items read from \"{}\".", read_cnt, &real_spec);
|
||||
if read_cnt > 0 {
|
||||
context.emit_event(EventType::MsgsChanged {
|
||||
chat_id: ChatId::new(0),
|
||||
msg_id: MsgId::new(0),
|
||||
});
|
||||
context.emit_msgs_changed_without_ids();
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -176,7 +160,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
.await
|
||||
.expect("invalid contact");
|
||||
let contact_name = if let Some(name) = msg.get_override_sender_name() {
|
||||
format!("~{}", name)
|
||||
format!("~{name}")
|
||||
} else {
|
||||
contact.get_display_name().to_string()
|
||||
};
|
||||
@@ -195,9 +179,10 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
DownloadState::Available => " [⬇ Download available]",
|
||||
DownloadState::InProgress => " [⬇ Download in progress...]️",
|
||||
DownloadState::Failure => " [⬇ Download failed]",
|
||||
DownloadState::Undecipherable => " [⬇ Decryption failed]",
|
||||
};
|
||||
|
||||
let temp2 = dc_timestamp_to_str(msg.get_timestamp());
|
||||
let temp2 = timestamp_to_str(msg.get_timestamp());
|
||||
let msgtext = msg.get_text();
|
||||
println!(
|
||||
"{}{}{}{}: {} (Contact#{}): {} {}{}{}{}{}{}{} [{}]",
|
||||
@@ -207,9 +192,9 @@ 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() == 1 {
|
||||
if msg.get_from_id() == ContactId::SELF {
|
||||
""
|
||||
} else if msg.get_state() == MessageState::InSeen {
|
||||
"[SEEN]"
|
||||
@@ -218,13 +203,31 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
} else {
|
||||
"[FRESH]"
|
||||
},
|
||||
if msg.is_info() { "[INFO]" } else { "" },
|
||||
if msg.is_info() {
|
||||
if msg.get_info_type() == SystemMessage::ChatProtectionEnabled {
|
||||
"[INFO 🛡️]"
|
||||
} else if msg.get_info_type() == SystemMessage::ChatProtectionDisabled {
|
||||
"[INFO 🛡️❌]"
|
||||
} else {
|
||||
"[INFO]"
|
||||
}
|
||||
} else {
|
||||
""
|
||||
},
|
||||
if msg.get_viewtype() == Viewtype::VideochatInvitation {
|
||||
format!(
|
||||
"[VIDEOCHAT-INVITATION: {}, type={}]",
|
||||
msg.get_videochat_url().unwrap_or_default(),
|
||||
msg.get_videochat_type().unwrap_or_default()
|
||||
)
|
||||
} else if msg.get_viewtype() == Viewtype::Webxdc {
|
||||
match msg.get_webxdc_info(context).await {
|
||||
Ok(info) => format!(
|
||||
"[WEBXDC: {}, icon={}, document={}, summary={}, source_code_url={}]",
|
||||
info.name, info.icon, info.document, info.summary, info.source_code_url
|
||||
),
|
||||
Err(err) => format!("[get_webxdc_info() failed: {err}]"),
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
},
|
||||
@@ -267,41 +270,31 @@ async fn log_msglist(context: &Context, msglist: &[MsgId]) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn log_contactlist(context: &Context, contacts: &[u32]) -> Result<()> {
|
||||
async fn log_contactlist(context: &Context, contacts: &[ContactId]) -> Result<()> {
|
||||
for contact_id in contacts {
|
||||
let line;
|
||||
let mut line2 = "".to_string();
|
||||
let contact = Contact::get_by_id(context, *contact_id).await?;
|
||||
let name = contact.get_display_name();
|
||||
let addr = contact.get_addr();
|
||||
let verified_state = contact.is_verified(context).await?;
|
||||
let verified_str = if VerifiedStatus::Unverified != verified_state {
|
||||
if verified_state == VerifiedStatus::BidirectVerified {
|
||||
" √√"
|
||||
} else {
|
||||
" √"
|
||||
}
|
||||
let verified_str = if contact.is_verified(context).await? {
|
||||
" √"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
line = format!(
|
||||
let line = format!(
|
||||
"{}{} <{}>",
|
||||
if !name.is_empty() {
|
||||
&name
|
||||
name
|
||||
} else {
|
||||
"<name unset>"
|
||||
},
|
||||
verified_str,
|
||||
if !addr.is_empty() {
|
||||
&addr
|
||||
} else {
|
||||
"addr unset"
|
||||
}
|
||||
if !addr.is_empty() { addr } else { "addr unset" }
|
||||
);
|
||||
let peerstate = Peerstate::from_addr(context, &addr)
|
||||
let peerstate = Peerstate::from_addr(context, addr)
|
||||
.await
|
||||
.expect("peerstate error");
|
||||
if peerstate.is_some() && *contact_id != 1 {
|
||||
if peerstate.is_some() && *contact_id != ContactId::SELF {
|
||||
line2 = format!(
|
||||
", prefer-encrypt={}",
|
||||
peerstate.as_ref().unwrap().prefer_encrypt
|
||||
@@ -341,9 +334,13 @@ 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\
|
||||
dump <filename>\n\n
|
||||
read <filename>\n\n
|
||||
poke [<eml-file>|<folder>|<addr> <key-file>]\n\
|
||||
reset <flags>\n\
|
||||
stop\n\
|
||||
@@ -386,6 +383,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
sendsticker <file> [<text>]\n\
|
||||
sendfile <file> [<text>]\n\
|
||||
sendhtml <file for html-part> [<text for plain-part>]\n\
|
||||
sendsyncmsg\n\
|
||||
sendupdate <msg-id> <json status update>\n\
|
||||
videochat\n\
|
||||
draft [<text>]\n\
|
||||
devicemsg <text>\n\
|
||||
@@ -396,8 +395,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
unpin <chat-id>\n\
|
||||
mute <chat-id> [<seconds>]\n\
|
||||
unmute <chat-id>\n\
|
||||
protect <chat-id>\n\
|
||||
unprotect <chat-id>\n\
|
||||
delchat <chat-id>\n\
|
||||
accept <chat-id>\n\
|
||||
decline <chat-id>\n\
|
||||
@@ -408,8 +405,10 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
html <msg-id>\n\
|
||||
listfresh\n\
|
||||
forward <msg-id> <chat-id>\n\
|
||||
resend <msg-id>\n\
|
||||
markseen <msg-id>\n\
|
||||
delmsg <msg-id>\n\
|
||||
react <msg-id> [<reaction>]\n\
|
||||
===========================Contact commands==\n\
|
||||
listcontacts [<query>]\n\
|
||||
listverified [<query>]\n\
|
||||
@@ -422,11 +421,12 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
listblocked\n\
|
||||
======================================Misc.==\n\
|
||||
getqr [<chat-id>]\n\
|
||||
getqrsvg [<chat-id>]\n\
|
||||
getbadqr\n\
|
||||
checkqr <qr-content>\n\
|
||||
joinqr <qr-content>\n\
|
||||
setqr <qr-content>\n\
|
||||
providerinfo <addr>\n\
|
||||
event <event-id to test>\n\
|
||||
fileinfo <file>\n\
|
||||
estimatedeletion <seconds>\n\
|
||||
clear -- clear screen\n\
|
||||
@@ -435,10 +435,9 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
),
|
||||
},
|
||||
"initiate-key-transfer" => match initiate_key_transfer(&context).await {
|
||||
Ok(setup_code) => println!(
|
||||
"Setup code for the transferred setup message: {}",
|
||||
setup_code,
|
||||
),
|
||||
Ok(setup_code) => {
|
||||
println!("Setup code for the transferred setup message: {setup_code}",)
|
||||
}
|
||||
Err(err) => bail!("Failed to generate setup code: {}", err),
|
||||
},
|
||||
"get-setupcodebegin" => {
|
||||
@@ -468,32 +467,63 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
}
|
||||
"export-backup" => {
|
||||
let dir = dirs::home_dir().unwrap_or_default();
|
||||
imex(&context, ImexMode::ExportBackup, dir.as_ref()).await?;
|
||||
imex(
|
||||
&context,
|
||||
ImexMode::ExportBackup,
|
||||
dir.as_ref(),
|
||||
Some(arg2.to_string()),
|
||||
)
|
||||
.await?;
|
||||
println!("Exported to {}.", dir.to_string_lossy());
|
||||
}
|
||||
"import-backup" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <backup-file> missing.");
|
||||
imex(&context, ImexMode::ImportBackup, arg1.as_ref()).await?;
|
||||
imex(
|
||||
&context,
|
||||
ImexMode::ImportBackup,
|
||||
arg1.as_ref(),
|
||||
Some(arg2.to_string()),
|
||||
)
|
||||
.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()).await?;
|
||||
imex(&context, ImexMode::ExportSelfKeys, dir.as_ref(), None).await?;
|
||||
println!("Exported to {}.", dir.to_string_lossy());
|
||||
}
|
||||
"import-keys" => {
|
||||
imex(&context, ImexMode::ImportSelfKeys, arg1.as_ref()).await?;
|
||||
imex(&context, ImexMode::ImportSelfKeys, arg1.as_ref(), None).await?;
|
||||
}
|
||||
"export-setup" => {
|
||||
let setup_code = create_setup_code(&context);
|
||||
let file_name = blobdir.join("autocrypt-setup-message.html");
|
||||
let file_content = render_setup_file(&context, &setup_code).await?;
|
||||
async_std::fs::write(&file_name, file_content).await?;
|
||||
fs::write(&file_name, file_content).await?;
|
||||
println!(
|
||||
"Setup message written to: {}\nSetup code: {}",
|
||||
file_name.display(),
|
||||
&setup_code,
|
||||
);
|
||||
}
|
||||
"dump" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <filename> missing.");
|
||||
serialize_database(&context, arg1).await?;
|
||||
}
|
||||
"read" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <filename> missing.");
|
||||
deserialize_database(&context, arg1).await?;
|
||||
}
|
||||
"poke" => {
|
||||
ensure!(poke_spec(&context, Some(arg1)).await, "Poke failed");
|
||||
}
|
||||
@@ -516,7 +546,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
ensure!(!arg1.is_empty(), "Argument <key> missing.");
|
||||
let key = config::Config::from_str(arg1)?;
|
||||
let val = context.get_config(key).await;
|
||||
println!("{}={:?}", key, val);
|
||||
println!("{key}={val:?}");
|
||||
}
|
||||
"info" => {
|
||||
println!("{:#?}", context.get_info().await);
|
||||
@@ -527,8 +557,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
.join("connectivity.html");
|
||||
match context.get_connectivity_html().await {
|
||||
Ok(html) => {
|
||||
fs::write(&file, html)?;
|
||||
println!("Report written to: {:#?}", file);
|
||||
fs::write(&file, html).await?;
|
||||
println!("Report written to: {file:#?}");
|
||||
}
|
||||
Err(err) => {
|
||||
bail!("Failed to get connectivity html: {}", err);
|
||||
@@ -539,10 +569,14 @@ 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" { 0x01 } else { 0 };
|
||||
let listflags = if arg0 == "listarchived" {
|
||||
DC_GCL_ARCHIVED_ONLY
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let time_start = std::time::SystemTime::now();
|
||||
let chatlist = Chatlist::try_load(
|
||||
&context,
|
||||
@@ -560,7 +594,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
);
|
||||
|
||||
for i in (0..cnt).rev() {
|
||||
let chat = Chat::load_from_db(&context, chatlist.get_chat_id(i)).await?;
|
||||
let chat = Chat::load_from_db(&context, chatlist.get_chat_id(i)?).await?;
|
||||
println!(
|
||||
"{}#{}: {} [{} fresh] {}{}{}{}",
|
||||
chat_prefix(&chat),
|
||||
@@ -592,12 +626,12 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
_ => "",
|
||||
}
|
||||
};
|
||||
let timestr = dc_timestamp_to_str(summary.timestamp);
|
||||
let timestr = timestamp_to_str(summary.timestamp);
|
||||
println!(
|
||||
"{}{}{} [{}]{}",
|
||||
summary
|
||||
.prefix
|
||||
.map_or_else(String::new, |prefix| format!("{}: ", prefix)),
|
||||
.map_or_else(String::new, |prefix| format!("{prefix}: ")),
|
||||
summary.text,
|
||||
statestr,
|
||||
×tr,
|
||||
@@ -615,8 +649,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
if location::is_sending_locations_to_chat(&context, None).await? {
|
||||
println!("Location streaming enabled.");
|
||||
}
|
||||
println!("{} chats", cnt);
|
||||
println!("{:?} to create this list", time_needed);
|
||||
println!("{cnt} chats");
|
||||
println!("{time_needed:?} to create this list");
|
||||
}
|
||||
"chat" => {
|
||||
if sel_chat.is_none() && arg1.is_empty() {
|
||||
@@ -624,7 +658,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
}
|
||||
if !arg1.is_empty() {
|
||||
let id = ChatId::new(arg1.parse()?);
|
||||
println!("Selecting chat {}", id);
|
||||
println!("Selecting chat {id}");
|
||||
sel_chat = Some(Chat::load_from_db(&context, id).await?);
|
||||
*chat_id = id;
|
||||
}
|
||||
@@ -633,15 +667,21 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
let sel_chat = sel_chat.as_ref().unwrap();
|
||||
|
||||
let time_start = std::time::SystemTime::now();
|
||||
let msglist =
|
||||
chat::get_chat_msgs(&context, sel_chat.get_id(), DC_GCM_ADDDAYMARKER, None).await?;
|
||||
let msglist = chat::get_chat_msgs_ex(
|
||||
&context,
|
||||
sel_chat.get_id(),
|
||||
chat::MessageListOptions {
|
||||
info_only: false,
|
||||
add_daymarker: true,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let time_needed = time_start.elapsed().unwrap_or_default();
|
||||
|
||||
let msglist: Vec<MsgId> = msglist
|
||||
.into_iter()
|
||||
.map(|x| match x {
|
||||
ChatItem::Message { msg_id } => msg_id,
|
||||
ChatItem::Marker1 => MsgId::new(DC_MSG_ID_MARKER1),
|
||||
ChatItem::DayMarker { .. } => MsgId::new(DC_MSG_ID_DAYMARKER),
|
||||
})
|
||||
.collect();
|
||||
@@ -671,7 +711,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
},
|
||||
match sel_chat.get_profile_image(&context).await? {
|
||||
Some(icon) => match icon.to_str() {
|
||||
Some(icon) => format!(" Icon: {}", icon),
|
||||
Some(icon) => format!(" Icon: {icon}"),
|
||||
_ => " Icon: Err".to_string(),
|
||||
},
|
||||
_ => "".to_string(),
|
||||
@@ -697,41 +737,40 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
let time_noticed_needed = time_noticed_start.elapsed().unwrap_or_default();
|
||||
|
||||
println!(
|
||||
"{:?} to create this list, {:?} to mark all messages as noticed.",
|
||||
time_needed, time_noticed_needed
|
||||
"{time_needed:?} to create this list, {time_noticed_needed:?} to mark all messages as noticed."
|
||||
);
|
||||
}
|
||||
"createchat" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
|
||||
let contact_id: u32 = arg1.parse()?;
|
||||
let contact_id = ContactId::new(arg1.parse()?);
|
||||
let chat_id = ChatId::create_for_contact(&context, contact_id).await?;
|
||||
|
||||
println!("Single#{} created successfully.", chat_id,);
|
||||
println!("Single#{chat_id} created successfully.",);
|
||||
}
|
||||
"creategroup" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <name> missing.");
|
||||
let chat_id =
|
||||
chat::create_group_chat(&context, ProtectionStatus::Unprotected, arg1).await?;
|
||||
|
||||
println!("Group#{} created successfully.", chat_id);
|
||||
println!("Group#{chat_id} created successfully.");
|
||||
}
|
||||
"createbroadcast" => {
|
||||
let chat_id = chat::create_broadcast_list(&context).await?;
|
||||
|
||||
println!("Broadcast#{} created successfully.", chat_id);
|
||||
println!("Broadcast#{chat_id} created successfully.");
|
||||
}
|
||||
"createprotected" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <name> missing.");
|
||||
let chat_id =
|
||||
chat::create_group_chat(&context, ProtectionStatus::Protected, arg1).await?;
|
||||
|
||||
println!("Group#{} created and protected successfully.", chat_id);
|
||||
println!("Group#{chat_id} created and protected successfully.");
|
||||
}
|
||||
"addmember" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected");
|
||||
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
|
||||
|
||||
let contact_id_0: u32 = arg1.parse()?;
|
||||
let contact_id_0 = ContactId::new(arg1.parse()?);
|
||||
chat::add_contact_to_chat(&context, sel_chat.as_ref().unwrap().get_id(), contact_id_0)
|
||||
.await?;
|
||||
println!("Contact added to chat.");
|
||||
@@ -739,7 +778,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
"removemember" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
|
||||
let contact_id_1: u32 = arg1.parse()?;
|
||||
let contact_id_1 = ContactId::new(arg1.parse()?);
|
||||
chat::remove_contact_from_chat(
|
||||
&context,
|
||||
sel_chat.as_ref().unwrap().get_id(),
|
||||
@@ -752,7 +791,12 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
"groupname" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
ensure!(!arg1.is_empty(), "Argument <name> missing.");
|
||||
chat::set_chat_name(&context, sel_chat.as_ref().unwrap().get_id(), arg1).await?;
|
||||
chat::set_chat_name(
|
||||
&context,
|
||||
sel_chat.as_ref().unwrap().get_id(),
|
||||
format!("{arg1} {arg2}").trim(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("Chat name set");
|
||||
}
|
||||
@@ -767,15 +811,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())
|
||||
@@ -801,7 +860,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
println!(
|
||||
"Loc#{}: {}: lat={} lng={} acc={} Chat#{} Contact#{} {} {}",
|
||||
location.location_id,
|
||||
dc_timestamp_to_str(location.timestamp),
|
||||
timestamp_to_str(location.timestamp),
|
||||
location.latitude,
|
||||
location.longitude,
|
||||
location.accuracy,
|
||||
@@ -840,11 +899,11 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
let latitude = arg1.parse()?;
|
||||
let longitude = arg2.parse()?;
|
||||
|
||||
let continue_streaming = location::set(&context, latitude, longitude, 0.).await;
|
||||
let continue_streaming = location::set(&context, latitude, longitude, 0.).await?;
|
||||
if continue_streaming {
|
||||
println!("Success, streaming should be continued.");
|
||||
} else {
|
||||
println!("Success, streaming can be stoppped.");
|
||||
println!("Success, streaming can be stopped.");
|
||||
}
|
||||
}
|
||||
"dellocations" => {
|
||||
@@ -854,7 +913,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
ensure!(!arg1.is_empty(), "No message text given.");
|
||||
|
||||
let msg = format!("{} {}", arg1, arg2);
|
||||
let msg = format!("{arg1} {arg2}");
|
||||
|
||||
chat::send_text_msg(&context, sel_chat.as_ref().unwrap().get_id(), msg).await?;
|
||||
}
|
||||
@@ -874,27 +933,39 @@ 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" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
ensure!(!arg1.is_empty(), "No html-file given.");
|
||||
let path: &Path = arg1.as_ref();
|
||||
let html = &*fs::read(&path)?;
|
||||
let html = &*fs::read(&path).await?;
|
||||
let html = String::from_utf8_lossy(html);
|
||||
|
||||
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? {
|
||||
Some(msg_id) => println!("sync message sent as {msg_id}."),
|
||||
None => println!("sync message not needed."),
|
||||
},
|
||||
"sendupdate" => {
|
||||
ensure!(
|
||||
!arg1.is_empty() && !arg2.is_empty(),
|
||||
"Arguments <msg-id> <json status update> expected"
|
||||
);
|
||||
let msg_id = MsgId::new(arg1.parse()?);
|
||||
context
|
||||
.send_webxdc_status_update(msg_id, arg2, "this is a webxdc status update")
|
||||
.await?;
|
||||
}
|
||||
"videochat" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
chat::send_videochat_invitation(&context, sel_chat.as_ref().unwrap().get_id()).await?;
|
||||
@@ -902,21 +973,32 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
"listmsgs" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <query> missing.");
|
||||
|
||||
let query = format!("{arg1} {arg2}").trim().to_string();
|
||||
let chat = sel_chat.as_ref().map(|sel_chat| sel_chat.get_id());
|
||||
let time_start = std::time::SystemTime::now();
|
||||
let msglist = context.search_msgs(chat, arg1).await?;
|
||||
let msglist = context.search_msgs(chat, &query).await?;
|
||||
let time_needed = time_start.elapsed().unwrap_or_default();
|
||||
|
||||
log_msglist(&context, &msglist).await?;
|
||||
println!("{} messages.", msglist.len());
|
||||
println!("{:?} to create this list", time_needed);
|
||||
println!(
|
||||
"{}{} messages for {}search of \"{}\"",
|
||||
msglist.len(),
|
||||
if msglist.len() == 1000 { "+" } else { "" },
|
||||
if chat.is_none() {
|
||||
"global "
|
||||
} else {
|
||||
"in-chat-"
|
||||
},
|
||||
query,
|
||||
);
|
||||
println!("{time_needed:?} to create this list");
|
||||
}
|
||||
"draft" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
|
||||
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()
|
||||
@@ -940,15 +1022,13 @@ 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" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
|
||||
let images = chat::get_chat_media(
|
||||
&context,
|
||||
sel_chat.as_ref().unwrap().get_id(),
|
||||
sel_chat.map(|c| c.id),
|
||||
Viewtype::Image,
|
||||
Viewtype::Gif,
|
||||
Viewtype::Video,
|
||||
@@ -957,9 +1037,9 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
println!("{} images or videos: ", images.len());
|
||||
for (i, data) in images.iter().enumerate() {
|
||||
if 0 == i {
|
||||
print!("{}", data);
|
||||
print!("{data}");
|
||||
} else {
|
||||
print!(", {}", data);
|
||||
print!(", {data}");
|
||||
}
|
||||
}
|
||||
println!();
|
||||
@@ -997,20 +1077,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
};
|
||||
chat::set_muted(&context, chat_id, duration).await?;
|
||||
}
|
||||
"protect" | "unprotect" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
|
||||
let chat_id = ChatId::new(arg1.parse()?);
|
||||
chat_id
|
||||
.set_protection(
|
||||
&context,
|
||||
match arg0 {
|
||||
"protect" => ProtectionStatus::Protected,
|
||||
"unprotect" => ProtectionStatus::Unprotected,
|
||||
_ => unreachable!("arg0={:?}", arg0),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
"delchat" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
|
||||
let chat_id = ChatId::new(arg1.parse()?);
|
||||
@@ -1029,13 +1095,13 @@ 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?;
|
||||
println!("{}", res);
|
||||
let res = id.get_info(&context).await?;
|
||||
println!("{res}");
|
||||
}
|
||||
"download" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||
let id = MsgId::new(arg1.parse()?);
|
||||
println!("Scheduling download for {:?}", id);
|
||||
println!("Scheduling download for {id:?}");
|
||||
id.download_full(&context).await?;
|
||||
}
|
||||
"html" => {
|
||||
@@ -1045,8 +1111,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
.unwrap_or_default()
|
||||
.join(format!("msg-{}.html", id.to_u32()));
|
||||
let html = id.get_html(&context).await?.unwrap_or_default();
|
||||
fs::write(&file, html)?;
|
||||
println!("HTML written to: {:#?}", file);
|
||||
fs::write(&file, html).await?;
|
||||
println!("HTML written to: {file:#?}");
|
||||
}
|
||||
"listfresh" => {
|
||||
let msglist = context.get_fresh_msgs().await?;
|
||||
@@ -1065,6 +1131,13 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
msg_ids[0] = MsgId::new(arg1.parse()?);
|
||||
chat::forward_msgs(&context, &msg_ids, chat_id).await?;
|
||||
}
|
||||
"resend" => {
|
||||
ensure!(!arg1.is_empty(), "Arguments <msg-id> expected");
|
||||
|
||||
let mut msg_ids = [MsgId::new(0); 1];
|
||||
msg_ids[0] = MsgId::new(arg1.parse()?);
|
||||
chat::resend_msgs(&context, &msg_ids).await?;
|
||||
}
|
||||
"markseen" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||
let mut msg_ids = vec![MsgId::new(0)];
|
||||
@@ -1077,6 +1150,12 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
ids[0] = MsgId::new(arg1.parse()?);
|
||||
message::delete_msgs(&context, &ids).await?;
|
||||
}
|
||||
"react" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||
let msg_id = MsgId::new(arg1.parse()?);
|
||||
let reaction = arg2;
|
||||
send_reaction(&context, msg_id, reaction).await?;
|
||||
}
|
||||
"listcontacts" | "contacts" | "listverified" => {
|
||||
let contacts = Contact::get_all(
|
||||
&context,
|
||||
@@ -1095,7 +1174,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
ensure!(!arg1.is_empty(), "Arguments [<name>] <addr> expected.");
|
||||
|
||||
if !arg2.is_empty() {
|
||||
let book = format!("{}\n{}", arg1, arg2);
|
||||
let book = format!("{arg1}\n{arg2}");
|
||||
Contact::add_address_book(&context, &book).await?;
|
||||
} else {
|
||||
Contact::create(&context, "", arg1).await?;
|
||||
@@ -1104,7 +1183,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
"contactinfo" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
|
||||
|
||||
let contact_id: u32 = arg1.parse()?;
|
||||
let contact_id = ContactId::new(arg1.parse()?);
|
||||
let contact = Contact::get_by_id(&context, contact_id).await?;
|
||||
let name_n_addr = contact.get_name_n_addr();
|
||||
|
||||
@@ -1122,33 +1201,30 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
let chatlist = Chatlist::try_load(&context, 0, None, Some(contact_id)).await?;
|
||||
let chatlist_cnt = chatlist.len();
|
||||
if chatlist_cnt > 0 {
|
||||
res += &format!(
|
||||
"\n\n{} chats shared with Contact#{}: ",
|
||||
chatlist_cnt, contact_id,
|
||||
);
|
||||
res += &format!("\n\n{chatlist_cnt} chats shared with Contact#{contact_id}: ",);
|
||||
for i in 0..chatlist_cnt {
|
||||
if 0 != i {
|
||||
res += ", ";
|
||||
}
|
||||
let chat = Chat::load_from_db(&context, chatlist.get_chat_id(i)).await?;
|
||||
let chat = Chat::load_from_db(&context, chatlist.get_chat_id(i)?).await?;
|
||||
res += &format!("{}#{}", chat_prefix(&chat), chat.get_id());
|
||||
}
|
||||
}
|
||||
|
||||
println!("{}", res);
|
||||
println!("{res}");
|
||||
}
|
||||
"delcontact" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
|
||||
Contact::delete(&context, arg1.parse()?).await?;
|
||||
Contact::delete(&context, ContactId::new(arg1.parse()?)).await?;
|
||||
}
|
||||
"block" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
|
||||
let contact_id = arg1.parse()?;
|
||||
let contact_id = ContactId::new(arg1.parse()?);
|
||||
Contact::block(&context, contact_id).await?;
|
||||
}
|
||||
"unblock" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <contact-id> missing.");
|
||||
let contact_id = arg1.parse()?;
|
||||
let contact_id = ContactId::new(arg1.parse()?);
|
||||
Contact::unblock(&context, contact_id).await?;
|
||||
}
|
||||
"listblocked" => {
|
||||
@@ -1158,20 +1234,14 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
}
|
||||
"checkqr" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <qr-content> missing.");
|
||||
let res = check_qr(&context, arg1).await;
|
||||
println!(
|
||||
"state={}, id={}, text1={:?}, text2={:?}",
|
||||
res.get_state(),
|
||||
res.get_id(),
|
||||
res.get_text1(),
|
||||
res.get_text2()
|
||||
);
|
||||
let qr = check_qr(&context, arg1).await?;
|
||||
println!("qr={qr:?}");
|
||||
}
|
||||
"setqr" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <qr-content> missing.");
|
||||
match set_config_from_qr(&context, arg1).await {
|
||||
Ok(()) => println!("Config set from QR code, you can now call 'configure'"),
|
||||
Err(err) => println!("Cannot set config from QR code: {:?}", err),
|
||||
Err(err) => println!("Cannot set config from QR code: {err:?}"),
|
||||
}
|
||||
}
|
||||
"providerinfo" => {
|
||||
@@ -1179,9 +1249,9 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
let socks5_enabled = context
|
||||
.get_config_bool(config::Config::Socks5Enabled)
|
||||
.await?;
|
||||
match provider::get_provider_info(arg1, socks5_enabled).await {
|
||||
match provider::get_provider_info(&context, arg1, socks5_enabled).await {
|
||||
Some(info) => {
|
||||
println!("Information for provider belonging to {}:", arg1);
|
||||
println!("Information for provider belonging to {arg1}:");
|
||||
println!("status: {}", info.status as u32);
|
||||
println!("before_login_hint: {}", info.before_login_hint);
|
||||
println!("after_login_hint: {}", info.after_login_hint);
|
||||
@@ -1191,27 +1261,16 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
}
|
||||
}
|
||||
None => {
|
||||
println!("No information for provider belonging to {} found.", arg1);
|
||||
println!("No information for provider belonging to {arg1} found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: implement this again, unclear how to match this through though, without writing a parser.
|
||||
// "event" => {
|
||||
// ensure!(!arg1.is_empty(), "Argument <id> missing.");
|
||||
// let event = arg1.parse()?;
|
||||
// let event = EventType::from_u32(event).ok_or(format_err!("EventType::from_u32({})", event))?;
|
||||
// let r = context.emit_event(event, 0 as libc::uintptr_t, 0 as libc::uintptr_t);
|
||||
// println!(
|
||||
// "Sending event {:?}({}), received value {}.",
|
||||
// event, event as usize, r,
|
||||
// );
|
||||
// }
|
||||
"fileinfo" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <file> missing.");
|
||||
|
||||
if let Ok(buf) = dc_read_file(&context, &arg1).await {
|
||||
let (width, height) = dc_get_filemeta(&buf)?;
|
||||
println!("width={}, height={}", width, height);
|
||||
if let Ok(buf) = read_file(&context, &arg1).await {
|
||||
let (width, height) = get_filemeta(&buf)?;
|
||||
println!("width={width}, height={height}");
|
||||
} else {
|
||||
bail!("Command failed.");
|
||||
}
|
||||
@@ -1222,8 +1281,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
let device_cnt = message::estimate_deletion_cnt(&context, false, seconds).await?;
|
||||
let server_cnt = message::estimate_deletion_cnt(&context, true, seconds).await?;
|
||||
println!(
|
||||
"estimated count of messages older than {} seconds:\non device: {}\non server: {}",
|
||||
seconds, device_cnt, server_cnt
|
||||
"estimated count of messages older than {seconds} seconds:\non device: {device_cnt}\non server: {server_cnt}"
|
||||
);
|
||||
}
|
||||
"" => (),
|
||||
@@ -13,16 +13,15 @@ use std::process::Command;
|
||||
|
||||
use ansi_term::Color;
|
||||
use anyhow::{bail, Error};
|
||||
use async_std::path::Path;
|
||||
use deltachat::chat::ChatId;
|
||||
use deltachat::config;
|
||||
use deltachat::context::*;
|
||||
use deltachat::oauth2::*;
|
||||
use deltachat::qr_code_generator::get_securejoin_qr_svg;
|
||||
use deltachat::securejoin::*;
|
||||
use deltachat::EventType;
|
||||
use log::{error, info, warn};
|
||||
use rustyline::completion::{Completer, FilenameCompleter, Pair};
|
||||
use rustyline::config::OutputStreamType;
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
|
||||
use rustyline::hint::{Hinter, HistoryHinter};
|
||||
@@ -30,6 +29,8 @@ use rustyline::validate::Validator;
|
||||
use rustyline::{
|
||||
Cmd, CompletionType, Config, Context as RustyContext, EditMode, Editor, Helper, KeyEvent,
|
||||
};
|
||||
use tokio::fs;
|
||||
use tokio::runtime::Handle;
|
||||
|
||||
mod cmdline;
|
||||
use self::cmdline::*;
|
||||
@@ -64,8 +65,19 @@ fn receive_event(event: EventType) {
|
||||
info!(
|
||||
"{}",
|
||||
yellow.paint(format!(
|
||||
"Received MSGS_CHANGED(chat_id={}, msg_id={})",
|
||||
chat_id, msg_id,
|
||||
"Received MSGS_CHANGED(chat_id={chat_id}, msg_id={msg_id})",
|
||||
))
|
||||
);
|
||||
}
|
||||
EventType::ReactionsChanged {
|
||||
chat_id,
|
||||
msg_id,
|
||||
contact_id,
|
||||
} => {
|
||||
info!(
|
||||
"{}",
|
||||
yellow.paint(format!(
|
||||
"Received REACTIONS_CHANGED(chat_id={chat_id}, msg_id={msg_id}, contact_id={contact_id})"
|
||||
))
|
||||
);
|
||||
}
|
||||
@@ -75,7 +87,7 @@ fn receive_event(event: EventType) {
|
||||
EventType::LocationChanged(contact) => {
|
||||
info!(
|
||||
"{}",
|
||||
yellow.paint(format!("Received LOCATION_CHANGED(contact={:?})", contact))
|
||||
yellow.paint(format!("Received LOCATION_CHANGED(contact={contact:?})"))
|
||||
);
|
||||
}
|
||||
EventType::ConfigureProgress { progress, comment } => {
|
||||
@@ -83,21 +95,20 @@ fn receive_event(event: EventType) {
|
||||
info!(
|
||||
"{}",
|
||||
yellow.paint(format!(
|
||||
"Received CONFIGURE_PROGRESS({} ‰, {})",
|
||||
progress, comment
|
||||
"Received CONFIGURE_PROGRESS({progress} ‰, {comment})"
|
||||
))
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
"{}",
|
||||
yellow.paint(format!("Received CONFIGURE_PROGRESS({} ‰)", progress))
|
||||
yellow.paint(format!("Received CONFIGURE_PROGRESS({progress} ‰)"))
|
||||
);
|
||||
}
|
||||
}
|
||||
EventType::ImexProgress(progress) => {
|
||||
info!(
|
||||
"{}",
|
||||
yellow.paint(format!("Received IMEX_PROGRESS({} ‰)", progress))
|
||||
yellow.paint(format!("Received IMEX_PROGRESS({progress} ‰)"))
|
||||
);
|
||||
}
|
||||
EventType::ImexFileWritten(file) => {
|
||||
@@ -109,7 +120,7 @@ fn receive_event(event: EventType) {
|
||||
EventType::ChatModified(chat) => {
|
||||
info!(
|
||||
"{}",
|
||||
yellow.paint(format!("Received CHAT_MODIFIED({})", chat))
|
||||
yellow.paint(format!("Received CHAT_MODIFIED({chat})"))
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
@@ -139,13 +150,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",
|
||||
@@ -167,7 +180,7 @@ const DB_COMMANDS: [&str; 10] = [
|
||||
"housekeeping",
|
||||
];
|
||||
|
||||
const CHAT_COMMANDS: [&str; 34] = [
|
||||
const CHAT_COMMANDS: [&str; 36] = [
|
||||
"listchats",
|
||||
"listarchived",
|
||||
"chat",
|
||||
@@ -188,6 +201,8 @@ const CHAT_COMMANDS: [&str; 34] = [
|
||||
"sendimage",
|
||||
"sendfile",
|
||||
"sendhtml",
|
||||
"sendsyncmsg",
|
||||
"sendupdate",
|
||||
"videochat",
|
||||
"draft",
|
||||
"listmedia",
|
||||
@@ -203,14 +218,16 @@ const CHAT_COMMANDS: [&str; 34] = [
|
||||
"accept",
|
||||
"blockchat",
|
||||
];
|
||||
const MESSAGE_COMMANDS: [&str; 7] = [
|
||||
const MESSAGE_COMMANDS: [&str; 9] = [
|
||||
"listmsgs",
|
||||
"msginfo",
|
||||
"listfresh",
|
||||
"forward",
|
||||
"resend",
|
||||
"markseen",
|
||||
"delmsg",
|
||||
"download",
|
||||
"react",
|
||||
];
|
||||
const CONTACT_COMMANDS: [&str; 9] = [
|
||||
"listcontacts",
|
||||
@@ -223,11 +240,12 @@ const CONTACT_COMMANDS: [&str; 9] = [
|
||||
"unblock",
|
||||
"listblocked",
|
||||
];
|
||||
const MISC_COMMANDS: [&str; 10] = [
|
||||
const MISC_COMMANDS: [&str; 11] = [
|
||||
"getqr",
|
||||
"getqrsvg",
|
||||
"getbadqr",
|
||||
"checkqr",
|
||||
"event",
|
||||
"joinqr",
|
||||
"fileinfo",
|
||||
"clear",
|
||||
"exit",
|
||||
@@ -279,8 +297,8 @@ impl Highlighter for DcHelper {
|
||||
self.highlighter.highlight(line, pos)
|
||||
}
|
||||
|
||||
fn highlight_char(&self, line: &str, pos: usize) -> bool {
|
||||
self.highlighter.highlight_char(line, pos)
|
||||
fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool {
|
||||
self.highlighter.highlight_char(line, pos, forced)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,10 +310,13 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
|
||||
println!("Error: Bad arguments, expected [db-name].");
|
||||
bail!("No db-name specified");
|
||||
}
|
||||
let context = Context::new("CLI".into(), Path::new(&args[1]).to_path_buf(), 0).await?;
|
||||
let context = ContextBuilder::new(args[1].clone().into())
|
||||
.with_id(1)
|
||||
.open()
|
||||
.await?;
|
||||
|
||||
let events = context.get_event_emitter();
|
||||
async_std::task::spawn(async move {
|
||||
tokio::task::spawn(async move {
|
||||
while let Some(event) = events.recv().await {
|
||||
receive_event(event.typ);
|
||||
}
|
||||
@@ -307,17 +328,17 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
|
||||
.history_ignore_space(true)
|
||||
.completion_type(CompletionType::List)
|
||||
.edit_mode(EditMode::Emacs)
|
||||
.output_stream(OutputStreamType::Stdout)
|
||||
.build();
|
||||
let mut selected_chat = ChatId::default();
|
||||
let (reader_s, reader_r) = async_std::channel::bounded(100);
|
||||
let input_loop = async_std::task::spawn_blocking(move || {
|
||||
|
||||
let ctx = context.clone();
|
||||
let input_loop = tokio::task::spawn_blocking(move || {
|
||||
let h = DcHelper {
|
||||
completer: FilenameCompleter::new(),
|
||||
highlighter: MatchingBracketHighlighter::new(),
|
||||
hinter: HistoryHinter {},
|
||||
};
|
||||
let mut rl = Editor::with_config(config);
|
||||
let mut rl = Editor::with_config(config)?;
|
||||
rl.set_helper(Some(h));
|
||||
rl.bind_sequence(KeyEvent::alt('N'), Cmd::HistorySearchForward);
|
||||
rl.bind_sequence(KeyEvent::alt('P'), Cmd::HistorySearchBackward);
|
||||
@@ -332,17 +353,31 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
|
||||
match readline {
|
||||
Ok(line) => {
|
||||
// TODO: ignore "set mail_pw"
|
||||
rl.add_history_entry(line.as_str());
|
||||
async_std::task::block_on(reader_s.send(line)).unwrap();
|
||||
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) => {
|
||||
println!("Exiting ...");
|
||||
false
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error: {err:#}");
|
||||
true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if !should_continue {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => {
|
||||
println!("Exiting...");
|
||||
drop(reader_s);
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Error: {}", err);
|
||||
drop(reader_s);
|
||||
println!("Error: {err:#}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -353,15 +388,8 @@ async fn start(args: Vec<String>) -> Result<(), Error> {
|
||||
Ok::<_, Error>(())
|
||||
});
|
||||
|
||||
while let Ok(line) = reader_r.recv().await {
|
||||
match handle_cmd(line.trim(), context.clone(), &mut selected_chat).await {
|
||||
Ok(ExitResult::Continue) => {}
|
||||
Ok(ExitResult::Exit) => break,
|
||||
Err(err) => println!("Error: {}", err),
|
||||
}
|
||||
}
|
||||
context.stop_io().await;
|
||||
input_loop.await?;
|
||||
input_loop.await??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -394,7 +422,7 @@ async fn handle_cmd(
|
||||
"oauth2" => {
|
||||
if let Some(addr) = ctx.get_config(config::Config::Addr).await? {
|
||||
let oauth2_url =
|
||||
dc_get_oauth2_url(&ctx, &addr, "chat.delta:/com.b44t.messenger").await?;
|
||||
get_oauth2_url(&ctx, &addr, "chat.delta:/com.b44t.messenger").await?;
|
||||
if oauth2_url.is_none() {
|
||||
println!("OAuth2 not available for {}.", &addr);
|
||||
} else {
|
||||
@@ -410,25 +438,39 @@ async fn handle_cmd(
|
||||
}
|
||||
"getqr" | "getbadqr" => {
|
||||
ctx.start_io().await;
|
||||
let group = arg1.parse::<u32>().ok().map(|id| ChatId::new(id));
|
||||
let mut qr = dc_get_securejoin_qr(&ctx, group).await?;
|
||||
let group = arg1.parse::<u32>().ok().map(ChatId::new);
|
||||
let mut qr = get_securejoin_qr(&ctx, group).await?;
|
||||
if !qr.is_empty() {
|
||||
if arg0 == "getbadqr" && qr.len() > 40 {
|
||||
qr.replace_range(12..22, "0000000000")
|
||||
}
|
||||
println!("{}", qr);
|
||||
println!("{qr}");
|
||||
let output = Command::new("qrencode")
|
||||
.args(&["-t", "ansiutf8", qr.as_str(), "-o", "-"])
|
||||
.args(["-t", "ansiutf8", qr.as_str(), "-o", "-"])
|
||||
.output()
|
||||
.expect("failed to execute process");
|
||||
io::stdout().write_all(&output.stdout).unwrap();
|
||||
io::stderr().write_all(&output.stderr).unwrap();
|
||||
}
|
||||
}
|
||||
"getqrsvg" => {
|
||||
ctx.start_io().await;
|
||||
let group = arg1.parse::<u32>().ok().map(ChatId::new);
|
||||
let file = dirs::home_dir().unwrap_or_default().join("qr.svg");
|
||||
match get_securejoin_qr_svg(&ctx, group).await {
|
||||
Ok(svg) => {
|
||||
fs::write(&file, svg).await?;
|
||||
println!("QR code svg written to: {file:#?}");
|
||||
}
|
||||
Err(err) => {
|
||||
bail!("Failed to get QR code svg: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
"joinqr" => {
|
||||
ctx.start_io().await;
|
||||
if !arg0.is_empty() {
|
||||
dc_join_securejoin(&ctx, arg1).await?;
|
||||
join_securejoin(&ctx, arg1).await?;
|
||||
}
|
||||
}
|
||||
"exit" | "quit" => return Ok(ExitResult::Exit),
|
||||
@@ -438,11 +480,15 @@ async fn handle_cmd(
|
||||
Ok(ExitResult::Continue)
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Error> {
|
||||
let _ = pretty_env_logger::try_init();
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
pretty_env_logger::formatted_timed_builder()
|
||||
.parse_default_env()
|
||||
.filter_module("deltachat_repl", log::LevelFilter::Info)
|
||||
.init();
|
||||
|
||||
let args = std::env::args().collect();
|
||||
async_std::task::block_on(async move { start(args).await })?;
|
||||
start(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
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.
|
||||
50
deltachat-rpc-client/README.md
Normal file
50
deltachat-rpc-client/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Delta Chat RPC python client
|
||||
|
||||
RPC client connects to standalone Delta Chat RPC server `deltachat-rpc-server`
|
||||
and provides asynchronous interface to it.
|
||||
|
||||
## Getting started
|
||||
|
||||
To use Delta Chat RPC client, first build a `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`
|
||||
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`.
|
||||
2. Run `CHATMAIL_DOMAIN=nine.testrun.org PATH="../target/debug:$PATH" tox`.
|
||||
|
||||
Additional arguments to `tox` are passed to pytest, e.g. `tox -- -s` does not capture test output.
|
||||
|
||||
## Using in REPL
|
||||
|
||||
Setup a development environment:
|
||||
```
|
||||
$ tox --devenv env
|
||||
$ . env/bin/activate
|
||||
```
|
||||
|
||||
```
|
||||
$ python
|
||||
>>> from deltachat_rpc_client import *
|
||||
>>> rpc = Rpc()
|
||||
>>> rpc.start()
|
||||
>>> dc = DeltaChat(rpc)
|
||||
>>> system_info = dc.get_system_info()
|
||||
>>> system_info["level"]
|
||||
'awesome'
|
||||
>>> rpc.close()
|
||||
```
|
||||
24
deltachat-rpc-client/examples/echobot.py
Executable file
24
deltachat-rpc-client/examples/echobot.py
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Minimal echo bot example.
|
||||
|
||||
it will echo back any text send to it, it also will print to console all Delta Chat core events.
|
||||
Pass --help to the CLI to see available options.
|
||||
"""
|
||||
from deltachat_rpc_client import events, run_bot_cli
|
||||
|
||||
hooks = events.HookCollection()
|
||||
|
||||
|
||||
@hooks.on(events.RawEvent)
|
||||
def log_event(event):
|
||||
print(event)
|
||||
|
||||
|
||||
@hooks.on(events.NewMessage)
|
||||
def echo(event):
|
||||
snapshot = event.message_snapshot
|
||||
snapshot.chat.send_text(snapshot.text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_bot_cli(hooks)
|
||||
74
deltachat-rpc-client/examples/echobot_advanced.py
Executable file
74
deltachat-rpc-client/examples/echobot_advanced.py
Executable file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Advanced echo bot example.
|
||||
|
||||
it will echo back any message that has non-empty text and also supports the /help command.
|
||||
"""
|
||||
import logging
|
||||
import sys
|
||||
from threading import Thread
|
||||
|
||||
from deltachat_rpc_client import Bot, DeltaChat, EventType, Rpc, events
|
||||
|
||||
hooks = events.HookCollection()
|
||||
|
||||
|
||||
@hooks.on(events.RawEvent)
|
||||
def log_event(event):
|
||||
if event.kind == EventType.INFO:
|
||||
logging.info(event.msg)
|
||||
elif event.kind == EventType.WARNING:
|
||||
logging.warning(event.msg)
|
||||
|
||||
|
||||
@hooks.on(events.RawEvent(EventType.ERROR))
|
||||
def log_error(event):
|
||||
logging.error(event.msg)
|
||||
|
||||
|
||||
@hooks.on(events.MemberListChanged)
|
||||
def on_memberlist_changed(event):
|
||||
logging.info("member %s was %s", event.member, "added" if event.member_added else "removed")
|
||||
|
||||
|
||||
@hooks.on(events.GroupImageChanged)
|
||||
def on_group_image_changed(event):
|
||||
logging.info("group image %s", "deleted" if event.image_deleted else "changed")
|
||||
|
||||
|
||||
@hooks.on(events.GroupNameChanged)
|
||||
def on_group_name_changed(event):
|
||||
logging.info("group name changed, old name: %s", event.old_name)
|
||||
|
||||
|
||||
@hooks.on(events.NewMessage(func=lambda e: not e.command))
|
||||
def echo(event):
|
||||
snapshot = event.message_snapshot
|
||||
if snapshot.text or snapshot.file:
|
||||
snapshot.chat.send_message(text=snapshot.text, file=snapshot.file)
|
||||
|
||||
|
||||
@hooks.on(events.NewMessage(command="/help"))
|
||||
def help_command(event):
|
||||
snapshot = event.message_snapshot
|
||||
snapshot.chat.send_text("Send me any message and I will echo it back")
|
||||
|
||||
|
||||
def main():
|
||||
with Rpc() as rpc:
|
||||
deltachat = DeltaChat(rpc)
|
||||
system_info = deltachat.get_system_info()
|
||||
logging.info("Running deltachat core %s", system_info.deltachat_core_version)
|
||||
|
||||
accounts = deltachat.get_all_accounts()
|
||||
account = accounts[0] if accounts else deltachat.add_account()
|
||||
|
||||
bot = Bot(account, hooks)
|
||||
if not bot.is_configured():
|
||||
configure_thread = Thread(run=bot.configure, kwargs={"email": sys.argv[1], "password": sys.argv[2]})
|
||||
configure_thread.start()
|
||||
bot.run_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
main()
|
||||
56
deltachat-rpc-client/examples/echobot_no_hooks.py
Executable file
56
deltachat-rpc-client/examples/echobot_no_hooks.py
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Example echo bot without using hooks
|
||||
"""
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from deltachat_rpc_client import DeltaChat, EventType, Rpc, SpecialContactId
|
||||
|
||||
|
||||
def main():
|
||||
with Rpc() as rpc:
|
||||
deltachat = DeltaChat(rpc)
|
||||
system_info = deltachat.get_system_info()
|
||||
logging.info("Running deltachat core %s", system_info["deltachat_core_version"])
|
||||
|
||||
accounts = deltachat.get_all_accounts()
|
||||
account = accounts[0] if accounts else deltachat.add_account()
|
||||
|
||||
account.set_config("bot", "1")
|
||||
if not account.is_configured():
|
||||
logging.info("Account is not configured, configuring")
|
||||
account.set_config("addr", sys.argv[1])
|
||||
account.set_config("mail_pw", sys.argv[2])
|
||||
account.configure()
|
||||
logging.info("Configured")
|
||||
else:
|
||||
logging.info("Account is already configured")
|
||||
deltachat.start_io()
|
||||
|
||||
def process_messages():
|
||||
for message in account.get_next_messages():
|
||||
snapshot = message.get_snapshot()
|
||||
if snapshot.from_id != SpecialContactId.SELF and not snapshot.is_bot and not snapshot.is_info:
|
||||
snapshot.chat.send_text(snapshot.text)
|
||||
snapshot.message.mark_seen()
|
||||
|
||||
# Process old messages.
|
||||
process_messages()
|
||||
|
||||
while True:
|
||||
event = account.wait_for_event()
|
||||
if event["kind"] == EventType.INFO:
|
||||
logging.info("%s", event["msg"])
|
||||
elif event["kind"] == EventType.WARNING:
|
||||
logging.warning("%s", event["msg"])
|
||||
elif event["kind"] == EventType.ERROR:
|
||||
logging.error("%s", event["msg"])
|
||||
elif event["kind"] == EventType.INCOMING_MSG:
|
||||
logging.info("Got an incoming message")
|
||||
process_messages()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
main()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user