mirror of
https://github.com/chatmail/core.git
synced 2026-08-18 06:36:29 +03:00
Compare commits
464 Commits
link2xt/de
...
link2xt/st
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d374ce3d87 | ||
|
|
0119db85ad | ||
|
|
b1c3615431 | ||
|
|
aa95d87568 | ||
|
|
07e18c64af | ||
|
|
e322fdf157 | ||
|
|
3dce06b02b | ||
|
|
40faf829d3 | ||
|
|
fbbe56c8ff | ||
|
|
ce7c8dbca1 | ||
|
|
0f8c91570b | ||
|
|
feb777f704 | ||
|
|
28a1a1da96 | ||
|
|
7add59fa76 | ||
|
|
e0494b0b37 | ||
|
|
412ed15b8b | ||
|
|
c1e961d37f | ||
|
|
1edbfa1024 | ||
|
|
56a99c227a | ||
|
|
31c37848ec | ||
|
|
ec4d195814 | ||
|
|
1a959c392b | ||
|
|
e94e33f59a | ||
|
|
e2b0eb1fb9 | ||
|
|
3363f63316 | ||
|
|
6beced4f51 | ||
|
|
cc229372e4 | ||
|
|
292bd9f58d | ||
|
|
998892395a | ||
|
|
9f5fbcdcae | ||
|
|
d89a972a2c | ||
|
|
ae5b27fb69 | ||
|
|
1a41e42bda | ||
|
|
c680432c23 | ||
|
|
4623d89528 | ||
|
|
5ec1d65294 | ||
|
|
8df4fe1c17 | ||
|
|
c3563f73e4 | ||
|
|
e15820fbd5 | ||
|
|
68ce93c420 | ||
|
|
fd8c56894a | ||
|
|
97932fda07 | ||
|
|
cd42efb36d | ||
|
|
2cacdbfd4b | ||
|
|
67437c946b | ||
|
|
6934aa26ae | ||
|
|
c3bc26d52f | ||
|
|
805e555a99 | ||
|
|
137ac9a027 | ||
|
|
a7632fe356 | ||
|
|
476fbf72e3 | ||
|
|
a6f33d7474 | ||
|
|
bd0c0b6b4b | ||
|
|
1247d5da36 | ||
|
|
93d9a4be1c | ||
|
|
b13b150997 | ||
|
|
bd846c6e43 | ||
|
|
547d22a3e3 | ||
|
|
7990e17859 | ||
|
|
487d33f4d7 | ||
|
|
e45667b547 | ||
|
|
5eab324751 | ||
|
|
15387690c8 | ||
|
|
ba35814d34 | ||
|
|
2ac217ddd4 | ||
|
|
8e60ed7d89 | ||
|
|
fab974f31e | ||
|
|
154e47dace | ||
|
|
99d202a973 | ||
|
|
ae02b6dcfc | ||
|
|
e1f5858fa3 | ||
|
|
abfc1b9a76 | ||
|
|
77d2d037f2 | ||
|
|
057d6c0c5e | ||
|
|
21e08d7e95 | ||
|
|
03c1ea9942 | ||
|
|
cf5f87c81b | ||
|
|
7021a3e899 | ||
|
|
a794e5f359 | ||
|
|
f7ffb924bf | ||
|
|
61f7107d0f | ||
|
|
710e33bc48 | ||
|
|
2df5ea038b | ||
|
|
dc267fe1fb | ||
|
|
62d3f3877f | ||
|
|
fb4fdeb470 | ||
|
|
db13d08f6c | ||
|
|
9476f9ec7f | ||
|
|
020e477662 | ||
|
|
3d50f3c9a3 | ||
|
|
35555ca753 | ||
|
|
0bb3d88bc6 | ||
|
|
e74ffb6e93 | ||
|
|
bbcfa5e40e | ||
|
|
7223d7ed74 | ||
|
|
733bec326a | ||
|
|
2d7af15124 | ||
|
|
00e1d00dfa | ||
|
|
856ea29c47 | ||
|
|
6a07a2b222 | ||
|
|
411ad16ea9 | ||
|
|
afb1d0dba8 | ||
|
|
df4197df56 | ||
|
|
9b7b32a536 | ||
|
|
682baff1b9 | ||
|
|
850ac0831f | ||
|
|
e2dd9b06e2 | ||
|
|
cc41a9ea27 | ||
|
|
dff8fdb293 | ||
|
|
85c407ab05 | ||
|
|
891a7f905d | ||
|
|
55ede9f2f6 | ||
|
|
7e5c1e8389 | ||
|
|
758fb9daa0 | ||
|
|
cd4850668f | ||
|
|
98a902b2de | ||
|
|
071358c605 | ||
|
|
82861a6903 | ||
|
|
ed6a335e9e | ||
|
|
234e491172 | ||
|
|
6a981ebef3 | ||
|
|
7ec888f48a | ||
|
|
eccd5ce03a | ||
|
|
3c171d539b | ||
|
|
0a71e181e4 | ||
|
|
1ac1917172 | ||
|
|
7e257aa25f | ||
|
|
b448694006 | ||
|
|
461a328915 | ||
|
|
0e4574cd41 | ||
|
|
3a913dfb07 | ||
|
|
bdd9d96844 | ||
|
|
4e2238a38f | ||
|
|
8c53f86fbc | ||
|
|
ca1fe8e352 | ||
|
|
61898b4789 | ||
|
|
5c1e695237 | ||
|
|
1721c0316a | ||
|
|
398ddac0b9 | ||
|
|
35d7b4e21c | ||
|
|
03725c5f32 | ||
|
|
a0c96f4ce1 | ||
|
|
b432d08aa8 | ||
|
|
85db29bdfb | ||
|
|
23c216c84b | ||
|
|
d34dd40aa0 | ||
|
|
ebd1c20997 | ||
|
|
0c40b52ec8 | ||
|
|
aadd11a771 | ||
|
|
17b32a17f5 | ||
|
|
355ab8b3bc | ||
|
|
e2b67d20fb | ||
|
|
0560c13ae5 | ||
|
|
1a24bf612a | ||
|
|
492741ac60 | ||
|
|
d940f96a5c | ||
|
|
e5c920f1a1 | ||
|
|
5d0e5efc94 | ||
|
|
94fc4f7550 | ||
|
|
ce2d237ffd | ||
|
|
f2d11305bd | ||
|
|
5adf7454ea | ||
|
|
079fb352e3 | ||
|
|
823b0741df | ||
|
|
6322758482 | ||
|
|
90ac058043 | ||
|
|
212aaaad77 | ||
|
|
31b2d7961a | ||
|
|
2b6515e3b5 | ||
|
|
cac8c06c78 | ||
|
|
446cdabd21 | ||
|
|
d9ab7a7a43 | ||
|
|
e5d56a3b70 | ||
|
|
ee1095be9d | ||
|
|
520ede1fa5 | ||
|
|
89a1f55a87 | ||
|
|
90e6fa055f | ||
|
|
093ebe537f | ||
|
|
fa91b168a9 | ||
|
|
8c12804257 | ||
|
|
5beef1a2d0 | ||
|
|
cc08d5de81 | ||
|
|
ba1063ad0f | ||
|
|
30bdcd6809 | ||
|
|
5eb5d22d7b | ||
|
|
a22ba021e5 | ||
|
|
6e633eebe1 | ||
|
|
a14e617059 | ||
|
|
b4471f2434 | ||
|
|
abaa48d0a1 | ||
|
|
1b2ec2e88c | ||
|
|
f6e819d2a5 | ||
|
|
abb84efc37 | ||
|
|
09d5d0bddf | ||
|
|
b38277c294 | ||
|
|
975fd8aee3 | ||
|
|
5a54e18fee | ||
|
|
8b80ac146f | ||
|
|
d1f4e59d82 | ||
|
|
0a82d73eb5 | ||
|
|
c026910fa7 | ||
|
|
2aba6aa339 | ||
|
|
93a6f35ef7 | ||
|
|
cd38aae4d6 | ||
|
|
3f371b3895 | ||
|
|
8a1d97d925 | ||
|
|
8f51c7b9d5 | ||
|
|
fd4bf98776 | ||
|
|
81dc56877c | ||
|
|
58b3f0e89b | ||
|
|
b860583f8d | ||
|
|
d39b79f6fc | ||
|
|
ab03fe3040 | ||
|
|
246376259e | ||
|
|
44bdd5ef0c | ||
|
|
d844f29c68 | ||
|
|
07d6b89b85 | ||
|
|
2bcfbe99fa | ||
|
|
3e10cf2c07 | ||
|
|
c7a399e3ca | ||
|
|
14b9577c39 | ||
|
|
f961a49906 | ||
|
|
b98b32317c | ||
|
|
b94c177997 | ||
|
|
15059ad8d7 | ||
|
|
293b524373 | ||
|
|
edb8a87cf8 | ||
|
|
9f2de665fc | ||
|
|
906e99a6f7 | ||
|
|
b215bc6d16 | ||
|
|
6cd5b21a26 | ||
|
|
87c1fb2118 | ||
|
|
e8b94781a5 | ||
|
|
07231f28ae | ||
|
|
64f0d2352c | ||
|
|
93bf3d6ebb | ||
|
|
f05336f793 | ||
|
|
8df028e9a8 | ||
|
|
40309ce857 | ||
|
|
c91608e9f1 | ||
|
|
207c2e6e4c | ||
|
|
c0705a8d92 | ||
|
|
18ce5a02cc | ||
|
|
e87e269f98 | ||
|
|
4bb557cf53 | ||
|
|
9b4503e3f5 | ||
|
|
4428382433 | ||
|
|
8c56b63f21 | ||
|
|
50e83f2072 | ||
|
|
4a94a34c6d | ||
|
|
bd6c9908e4 | ||
|
|
dd5ec9621b | ||
|
|
92d522473f | ||
|
|
049fd4f355 | ||
|
|
7ea637c930 | ||
|
|
3a1b0d4679 | ||
|
|
7a60c79301 | ||
|
|
0bc7849e8a | ||
|
|
dfea8b0134 | ||
|
|
376c819374 | ||
|
|
02827406f3 | ||
|
|
9d9f61d9eb | ||
|
|
a2816d7bd3 | ||
|
|
9719aa1415 | ||
|
|
5733a783fb | ||
|
|
c26b6a017c | ||
|
|
bb6a478430 | ||
|
|
480248a168 | ||
|
|
627f98915d | ||
|
|
24848c0265 | ||
|
|
98123afb62 | ||
|
|
16b65c59bf | ||
|
|
bafb5f71ad | ||
|
|
784a6abb3b | ||
|
|
e91f2718dd | ||
|
|
961cacd795 | ||
|
|
602922c1d5 | ||
|
|
03b10ea83a | ||
|
|
72f5dafc68 | ||
|
|
b8bce444ec | ||
|
|
85308b9263 | ||
|
|
a6a733c636 | ||
|
|
6d0d9fe0b2 | ||
|
|
a017663be5 | ||
|
|
d0696504d1 | ||
|
|
32e4a74050 | ||
|
|
f0992070be | ||
|
|
02f5dba620 | ||
|
|
ac3c5c55e4 | ||
|
|
219dbd8c42 | ||
|
|
6e70fbf824 | ||
|
|
7a39a0c8ff | ||
|
|
4eda257cfa | ||
|
|
354edb042b | ||
|
|
4bdc3c29ed | ||
|
|
439216c12c | ||
|
|
0bb4c3d073 | ||
|
|
64f65886f6 | ||
|
|
8658f938e5 | ||
|
|
f75b4abefe | ||
|
|
f2f2dd42ed | ||
|
|
8132f32e91 | ||
|
|
3d28951885 | ||
|
|
bc2d624a80 | ||
|
|
fa8fcaaa2b | ||
|
|
31c74d82bd | ||
|
|
6fb2f27831 | ||
|
|
58a09df49a | ||
|
|
ca70fb9b3a | ||
|
|
045b586569 | ||
|
|
18e1ecbb94 | ||
|
|
6fdee2b92d | ||
|
|
9ebd4769f5 | ||
|
|
741d1beed8 | ||
|
|
ac8b2d2fca | ||
|
|
d75c05e717 | ||
|
|
35ceb51ffc | ||
|
|
8492f84b13 | ||
|
|
e713c231eb | ||
|
|
80c9ca44ca | ||
|
|
6e04993e75 | ||
|
|
4529ed2240 | ||
|
|
b5ebd6f686 | ||
|
|
4d537544ef | ||
|
|
4a16c0c3dd | ||
|
|
4c01802982 | ||
|
|
4b528e426b | ||
|
|
585de7d18b | ||
|
|
0598fdcab3 | ||
|
|
903e736fa2 | ||
|
|
f20907d597 | ||
|
|
804590c7f3 | ||
|
|
62d4cf4ed8 | ||
|
|
0d772d4dba | ||
|
|
408afa5656 | ||
|
|
1a6249c10f | ||
|
|
daea820fe5 | ||
|
|
b806efa096 | ||
|
|
0580056b62 | ||
|
|
63f96d9138 | ||
|
|
1204a94252 | ||
|
|
73e8bee120 | ||
|
|
8c927c7f86 | ||
|
|
7f9c184659 | ||
|
|
287d730556 | ||
|
|
82bb77b056 | ||
|
|
aa1f129a48 | ||
|
|
0ad58f7a59 | ||
|
|
6d61f7e071 | ||
|
|
fa68c1f0e4 | ||
|
|
5f1d54100f | ||
|
|
25cd7b65fd | ||
|
|
63596a4940 | ||
|
|
8bc84e13de | ||
|
|
ba8c39ff5b | ||
|
|
7de58f5329 | ||
|
|
1fd4a19e56 | ||
|
|
1ab6645bbc | ||
|
|
c17d067a1a | ||
|
|
3aeb2d44b7 | ||
|
|
d069e75cd8 | ||
|
|
ad5e904d1c | ||
|
|
38affa2c62 | ||
|
|
6dfc6f8780 | ||
|
|
8cca0cf75d | ||
|
|
b81f50be8f | ||
|
|
970222f376 | ||
|
|
83e31a5f17 | ||
|
|
31fabb24df | ||
|
|
66df0d2a3c | ||
|
|
5a6b1c62dd | ||
|
|
18d878378f | ||
|
|
3c25e4b726 | ||
|
|
8cd06bb785 | ||
|
|
bb816ff398 | ||
|
|
9fcb26c849 | ||
|
|
d9474a678e | ||
|
|
f1e1a240ac | ||
|
|
82924952fb | ||
|
|
7daa6cc8d9 | ||
|
|
b8cfee7e9e | ||
|
|
03fc2d26ee | ||
|
|
942172a31a | ||
|
|
04c0e7da16 | ||
|
|
4178671839 | ||
|
|
49e8065b4c | ||
|
|
1c24ad91eb | ||
|
|
8f7777b843 | ||
|
|
caeddcd57b | ||
|
|
418fd92d6d | ||
|
|
a70924a1d6 | ||
|
|
8e91eecb3d | ||
|
|
89f948028d | ||
|
|
9adb71bf8f | ||
|
|
fe2ba05804 | ||
|
|
072fc34c77 | ||
|
|
cb00bd7043 | ||
|
|
f766c11075 | ||
|
|
89e450894d | ||
|
|
6ef1f7d52b | ||
|
|
33dc3d20ad | ||
|
|
b8a0c37efe | ||
|
|
ad7f873c68 | ||
|
|
3236c8bbf4 | ||
|
|
dab7ca19fe | ||
|
|
520cd0ede8 | ||
|
|
5d5deedec3 | ||
|
|
f33e21ccb9 | ||
|
|
00c06c490b | ||
|
|
8b58b16cb5 | ||
|
|
d6971ee4ac | ||
|
|
e3bf6bf352 | ||
|
|
4b81cd2fc8 | ||
|
|
be920bf3bf | ||
|
|
602f0a088e | ||
|
|
a2bb8962cb | ||
|
|
795fe9a38b | ||
|
|
60bc4011f7 | ||
|
|
f552cf93b4 | ||
|
|
f75a7986b5 | ||
|
|
3b8f1934f3 | ||
|
|
c8716f50aa | ||
|
|
d2097d3523 | ||
|
|
1219cbe1a3 | ||
|
|
bc48b17e93 | ||
|
|
7233b4b811 | ||
|
|
d1e0088201 | ||
|
|
a5e41b0b49 | ||
|
|
2f76fd98dd | ||
|
|
6235f2a01a | ||
|
|
ec5117a6c2 | ||
|
|
d6e3a8829b | ||
|
|
2340818488 | ||
|
|
f175d2fed9 | ||
|
|
d318bbb0f4 | ||
|
|
a0f14a5978 | ||
|
|
7e49033f92 | ||
|
|
626ac8161a | ||
|
|
28cce5e31d | ||
|
|
3b87e27f34 | ||
|
|
24b21c0588 | ||
|
|
eb666d4cc3 | ||
|
|
ef265689dd | ||
|
|
49223792f9 | ||
|
|
920da083d1 | ||
|
|
8f1bf963b4 | ||
|
|
e33d50b4e0 | ||
|
|
f1dc03a4ee | ||
|
|
5d90cc7a2a | ||
|
|
68e630eb82 | ||
|
|
ef718bb869 | ||
|
|
f1860f90d4 | ||
|
|
a947f4296f | ||
|
|
8c3139f7a2 | ||
|
|
3dd7defaa1 | ||
|
|
3096dd6027 | ||
|
|
ee62d2d281 | ||
|
|
6095971f67 | ||
|
|
32ff5b7a6b | ||
|
|
b87805ab24 | ||
|
|
c8716ad85a | ||
|
|
4dd0ba2c72 | ||
|
|
a24248a90b | ||
|
|
af16fc9038 |
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -15,6 +15,6 @@ updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
interval: "monthly"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
75
.github/workflows/ci.yml
vendored
75
.github/workflows/ci.yml
vendored
@@ -20,10 +20,10 @@ permissions: {}
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
RUST_VERSION: 1.94.0
|
||||
RUST_VERSION: 1.97.1
|
||||
|
||||
# Minimum Supported Rust Version
|
||||
MSRV: 1.88.0
|
||||
MSRV: 1.89.0
|
||||
|
||||
jobs:
|
||||
lint_rust:
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -40,7 +40,10 @@ jobs:
|
||||
- run: rustup override set $RUST_VERSION
|
||||
shell: bash
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
- name: Run rustfmt
|
||||
run: cargo fmt --all -- --check
|
||||
- name: Run clippy
|
||||
@@ -55,30 +58,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: EmbarkStudios/cargo-deny-action@3fd3802e88374d3fe9159b834c7714ec57d6c979
|
||||
- uses: EmbarkStudios/cargo-deny-action@3c6349835b2b7b196a839186cb8b78e02f7b5f25
|
||||
with:
|
||||
arguments: --workspace --all-features --locked
|
||||
command: check
|
||||
command-arguments: "-Dwarnings"
|
||||
|
||||
provider_database:
|
||||
name: Check provider database
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- name: Install rustfmt
|
||||
run: rustup component add --toolchain stable-x86_64-unknown-linux-gnu rustfmt
|
||||
- name: Check provider database
|
||||
run: scripts/update-provider-database.sh
|
||||
|
||||
docs:
|
||||
name: Rust doc comments
|
||||
runs-on: ubuntu-latest
|
||||
@@ -86,12 +75,15 @@ jobs:
|
||||
env:
|
||||
RUSTDOCFLAGS: -Dwarnings
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
- name: Rustdoc
|
||||
run: cargo doc --document-private-items --no-deps
|
||||
|
||||
@@ -122,7 +114,7 @@ jobs:
|
||||
shell: bash
|
||||
if: matrix.rust == 'latest'
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -134,10 +126,13 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@69e777b377e4ec209ddad9426ae3e0c1008b0ef3
|
||||
uses: taiki-e/install-action@3d7d7cd5ac7f994c1892ae0c06165095b9139094
|
||||
with:
|
||||
tool: nextest
|
||||
|
||||
@@ -162,16 +157,19 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
|
||||
- name: Build C library
|
||||
run: cargo build -p deltachat_ffi
|
||||
run: cargo build -p deltachat_ffi --locked
|
||||
|
||||
- name: Upload C library
|
||||
uses: actions/upload-artifact@v7
|
||||
@@ -188,16 +186,19 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
- name: Cache rust cargo artifacts
|
||||
uses: swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
|
||||
- name: Build deltachat-rpc-server
|
||||
run: cargo build -p deltachat-rpc-server
|
||||
run: cargo build -p deltachat-rpc-server --locked
|
||||
|
||||
- name: Upload deltachat-rpc-server
|
||||
uses: actions/upload-artifact@v7
|
||||
@@ -211,7 +212,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -237,7 +238,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -287,7 +288,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -299,7 +300,7 @@ jobs:
|
||||
path: target/debug
|
||||
|
||||
- name: Install python
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v7.0.0
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
|
||||
@@ -341,13 +342,13 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install python
|
||||
uses: actions/setup-python@v6
|
||||
uses: actions/setup-python@v7.0.0
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
|
||||
|
||||
47
.github/workflows/deltachat-rpc-server.yml
vendored
47
.github/workflows/deltachat-rpc-server.yml
vendored
@@ -30,11 +30,11 @@ jobs:
|
||||
arch: [aarch64, armv7l, armv6l, i686, x86_64]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
|
||||
|
||||
- name: Build deltachat-rpc-server binaries
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-linux
|
||||
@@ -54,11 +54,11 @@ jobs:
|
||||
arch: [aarch64, armv7l, armv6l, i686, x86_64]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
|
||||
|
||||
- name: Build deltachat-rpc-server wheels
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-linux-wheel
|
||||
@@ -78,11 +78,11 @@ jobs:
|
||||
arch: [win32, win64]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
|
||||
|
||||
- name: Build deltachat-rpc-server binaries
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}
|
||||
@@ -102,11 +102,11 @@ jobs:
|
||||
arch: [win32, win64]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
|
||||
|
||||
- name: Build deltachat-rpc-server wheels
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-wheel
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -153,11 +153,11 @@ jobs:
|
||||
arch: [arm64-v8a, armeabi-v7a]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
|
||||
|
||||
- name: Build deltachat-rpc-server binaries
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-android
|
||||
@@ -177,11 +177,11 @@ jobs:
|
||||
arch: [arm64-v8a, armeabi-v7a]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
|
||||
|
||||
- name: Build deltachat-rpc-server wheels
|
||||
run: nix build .#deltachat-rpc-server-${{ matrix.arch }}-android-wheel
|
||||
@@ -204,11 +204,11 @@ jobs:
|
||||
contents: write
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
|
||||
|
||||
- name: Download Linux aarch64 binary
|
||||
uses: actions/download-artifact@v7
|
||||
@@ -382,7 +382,7 @@ jobs:
|
||||
|
||||
- name: Publish deltachat-rpc-server to PyPI
|
||||
if: github.event_name == 'release'
|
||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e
|
||||
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247
|
||||
|
||||
publish_npm_package:
|
||||
name: Build & Publish npm prebuilds and deltachat-rpc-server
|
||||
@@ -397,11 +397,11 @@ jobs:
|
||||
# Needed to publish the binaries to the release.
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@v7.0.0
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
@@ -513,15 +513,12 @@ jobs:
|
||||
deltachat-rpc-server/npm-package/*.tgz
|
||||
|
||||
# Configure Node.js for publishing.
|
||||
- uses: actions/setup-node@v6
|
||||
# Check <https://docs.npmjs.com/trusted-publishers> for the version requirements.
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
# Ensure npm 11.5.1 or later is installed.
|
||||
# It is needed for <https://docs.npmjs.com/trusted-publishers>
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
package-manager-cache: false # never use caching in release builds
|
||||
|
||||
- name: Publish npm packets for prebuilds and `@deltachat/stdio-rpc-server`
|
||||
if: github.event_name == 'release'
|
||||
|
||||
4
.github/workflows/dependabot.yml
vendored
4
.github/workflows/dependabot.yml
vendored
@@ -10,11 +10,11 @@ permissions:
|
||||
jobs:
|
||||
dependabot:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.actor == 'dependabot[bot]' }}
|
||||
if: github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == github.event.pull_request.head.repo.full_name
|
||||
steps:
|
||||
- name: Dependabot metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata@v2.4.0
|
||||
uses: dependabot/fetch-metadata@v3.0.0
|
||||
with:
|
||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
- name: Approve a PR
|
||||
|
||||
23
.github/workflows/dev-version.yml
vendored
Normal file
23
.github/workflows/dev-version.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# Check that PRs are made against the -dev version.
|
||||
#
|
||||
# If this fails, push commit to update the version to -dev to main.
|
||||
|
||||
name: Check for -dev version
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check_dev_version:
|
||||
name: Check that current version ends with -dev
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run version-checking script
|
||||
run: scripts/check-dev-version.py
|
||||
14
.github/workflows/jsonrpc-client-npm-package.yml
vendored
14
.github/workflows/jsonrpc-client-npm-package.yml
vendored
@@ -17,20 +17,18 @@ jobs:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
# Configure Node.js for publishing.
|
||||
# Check <https://docs.npmjs.com/trusted-publishers> for the version requirements.
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
# Ensure npm 11.5.1 or later is installed.
|
||||
# It is needed for <https://docs.npmjs.com/trusted-publishers>
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
package-manager-cache: false # never use caching in release builds
|
||||
|
||||
- name: Install dependencies without running scripts
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
|
||||
13
.github/workflows/jsonrpc.yml
vendored
13
.github/workflows/jsonrpc.yml
vendored
@@ -16,16 +16,19 @@ jobs:
|
||||
build_and_test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v6
|
||||
- name: Use Node.js 24
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: 18.x
|
||||
node-version: 24
|
||||
- name: Add Rust cache
|
||||
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
- name: npm install
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
run: npm install
|
||||
|
||||
13
.github/workflows/nix.yml
vendored
13
.github/workflows/nix.yml
vendored
@@ -21,11 +21,11 @@ jobs:
|
||||
name: check flake formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
|
||||
- run: nix fmt flake.nix -- --check
|
||||
|
||||
build:
|
||||
@@ -63,7 +63,6 @@ jobs:
|
||||
- deltachat-rpc-server-armv7l-linux-wheel
|
||||
- deltachat-rpc-server-i686-linux
|
||||
- deltachat-rpc-server-i686-linux-wheel
|
||||
- deltachat-rpc-server-source
|
||||
- deltachat-rpc-server-win32
|
||||
- deltachat-rpc-server-win32-wheel
|
||||
- deltachat-rpc-server-win64
|
||||
@@ -80,11 +79,11 @@ jobs:
|
||||
#- deltachat-rpc-server-x86_64-android
|
||||
#- deltachat-rpc-server-x86-android
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
|
||||
- run: nix build .#${{ matrix.installable }}
|
||||
|
||||
build-macos:
|
||||
@@ -101,9 +100,9 @@ jobs:
|
||||
# because of <https://github.com/NixOS/nixpkgs/issues/413910>.
|
||||
# - deltachat-rpc-server-aarch64-darwin
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
|
||||
- run: nix build .#${{ matrix.installable }}
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -47,4 +47,4 @@ jobs:
|
||||
name: python-package-distributions
|
||||
path: dist/
|
||||
- name: Publish deltachat-rpc-client to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e
|
||||
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247
|
||||
|
||||
4
.github/workflows/repl.yml
vendored
4
.github/workflows/repl.yml
vendored
@@ -14,11 +14,11 @@ jobs:
|
||||
name: Build REPL example
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
|
||||
- name: Build
|
||||
run: nix build .#deltachat-repl-win64
|
||||
- name: Upload binary
|
||||
|
||||
69
.github/workflows/upload-docs.yml
vendored
69
.github/workflows/upload-docs.yml
vendored
@@ -1,19 +1,21 @@
|
||||
name: Build & deploy documentation on rs.delta.chat, c.delta.chat, and py.delta.chat
|
||||
name: Build & deploy documentation on rs.delta.chat, c.delta.chat, py.delta.chat and cffi.delta.chat
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- build_jsonrpc_docs_ci
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build-rs:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: rs.delta.chat
|
||||
url: https://rs.delta.chat/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -23,64 +25,74 @@ jobs:
|
||||
- name: Upload to rs.delta.chat
|
||||
run: |
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.KEY }}" > "$HOME/.ssh/key"
|
||||
echo "${{ secrets.RS_DOCS_SSH_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 }}@rs.delta.chat:/var/www/html/rs/"
|
||||
rsync -avzh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/target/doc/ "${{ secrets.RS_DOCS_SSH_USER }}@rs.delta.chat:"
|
||||
|
||||
build-python:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: py.delta.chat
|
||||
url: https://py.delta.chat/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
|
||||
- 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"
|
||||
echo "${{ secrets.PY_DOCS_SSH_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"
|
||||
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/result/html/ "${{ secrets.PY_DOCS_SSH_USER }}@py.delta.chat:"
|
||||
|
||||
build-c:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: c.delta.chat
|
||||
url: https://c.delta.chat/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
||||
- uses: cachix/install-nix-action@2126ae7fc54c9df00dd18f7f18754393182c73cd # v31.9.1
|
||||
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
|
||||
- name: Build C documentation
|
||||
run: nix build .#docs
|
||||
- name: Upload to c.delta.chat
|
||||
run: |
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.CODESPEAK_KEY }}" > "$HOME/.ssh/key"
|
||||
echo "${{ secrets.C_DOCS_SSH_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"
|
||||
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/result/html/ "${{ secrets.C_DOCS_SSH_USER }}@c.delta.chat:"
|
||||
|
||||
build-ts:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: js.jsonrpc.delta.chat
|
||||
url: https://js.jsonrpc.delta.chat/
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./deltachat-jsonrpc/typescript
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
fetch-depth: 0 # Fetch history to calculate VCS version number.
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: 24
|
||||
- name: npm install
|
||||
run: npm install
|
||||
- name: npm run build
|
||||
@@ -90,6 +102,27 @@ jobs:
|
||||
- name: Upload to js.jsonrpc.delta.chat
|
||||
run: |
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.KEY }}" > "$HOME/.ssh/key"
|
||||
echo "${{ secrets.JS_JSONRPC_DOCS_SSH_KEY }}" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
rsync -avzh -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/deltachat-jsonrpc/typescript/docs/ "${{ secrets.USERNAME }}@js.jsonrpc.delta.chat:/var/www/html/js-jsonrpc/"
|
||||
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/deltachat-jsonrpc/typescript/docs/ "${{ secrets.JS_JSONRPC_DOCS_SSH_USER }}@js.jsonrpc.delta.chat:"
|
||||
|
||||
build-cffi:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: cffi.delta.chat
|
||||
url: https://cffi.delta.chat/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: 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.CFFI_DOCS_SSH_KEY }}" > "$HOME/.ssh/key"
|
||||
chmod 600 "$HOME/.ssh/key"
|
||||
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/target/doc/ "${{ secrets.CFFI_DOCS_SSH_USER }}@delta.chat:"
|
||||
|
||||
31
.github/workflows/upload-ffi-docs.yml
vendored
31
.github/workflows/upload-ffi-docs.yml
vendored
@@ -1,31 +0,0 @@
|
||||
# GitHub Actions workflow
|
||||
# to build `deltachat_ffi` crate documentation
|
||||
# and upload it to <https://cffi.delta.chat/>
|
||||
|
||||
name: Build & Deploy Documentation on cffi.delta.chat
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: 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/"
|
||||
4
.github/workflows/zizmor-scan.yml
vendored
4
.github/workflows/zizmor-scan.yml
vendored
@@ -18,9 +18,9 @@ jobs:
|
||||
actions: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run zizmor
|
||||
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
|
||||
uses: zizmorcore/zizmor-action@6fc4b006235f201fdab3722e17240ab420d580e5 # v0.6.1
|
||||
|
||||
708
CHANGELOG.md
708
CHANGELOG.md
@@ -1,5 +1,701 @@
|
||||
# Changelog
|
||||
|
||||
## [2.59.0] - 2026-08-14
|
||||
|
||||
### API-Changes
|
||||
|
||||
- [**breaking**] Remove deprecated `dc_chat_is_protected()`.
|
||||
- Deprecate `dc_chat_get_info_json()` ([#8580](https://github.com/chatmail/core/pull/8580))
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Add stock strings for being added/removed from group ([#8562](https://github.com/chatmail/core/pull/8562)).
|
||||
- Client version information ([#8557](https://github.com/chatmail/core/pull/8557)).
|
||||
- Remove hidden headers.
|
||||
- Stop creating info messages for old broadcast lists.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Filtered reactions are info, not error in device chat.
|
||||
- Send MDNs to self even if MDNs are disabled.
|
||||
- Send HTTP requests in origin not absolute form.
|
||||
|
||||
### Documentation
|
||||
|
||||
- json-rpc: improve `reactions_by_contact` doc.
|
||||
- Do not refer to `is_chat_protected()`.
|
||||
- Do not talk about verified chats in securejoin QR-scanning functions.
|
||||
- Add SQL schema documentation.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Fix nightly clippy warnings.
|
||||
- cargo: bump astral-tokio-tar from 0.6.3 to 0.6.4.
|
||||
- cargo: bump bytes from 1.12.0 to 1.12.1.
|
||||
- FFI: don't swallow but log errors in three places.
|
||||
|
||||
### Refactor
|
||||
|
||||
- Remove `MessengerMessage`.
|
||||
- Stop setting chats.protected column explicitly.
|
||||
- Merge `msg_group_left_local` into `msg_del_member_local` ([#8575](https://github.com/chatmail/core/pull/8575)).
|
||||
- Rename `_ex()` -> `_ext()`.
|
||||
- mimefactory: add Encryption enum.
|
||||
|
||||
### Tests
|
||||
|
||||
- Fix flakyness of iroh tests by sending "forever" so that late swarm-joins still make the test work.
|
||||
- Move iroh tests into separate module.
|
||||
- Provide complete test isolation by not re-using account addresses.
|
||||
- Avoid another source of random failures with `direct_imap` failing to connect on first try.
|
||||
- Remove all cache-related logic in the FFI pytest plugin.
|
||||
- Add a CI-failing check that documented sql schema matches real one.
|
||||
- Abort early if DNS to chatmail domain does not work and nicer pytest startup header.
|
||||
- Load test data through the `data` fixture.
|
||||
- Allow to run the test suite against underscore-domain relays.
|
||||
|
||||
## [2.58.0] - 2026-08-10
|
||||
|
||||
### API-Changes
|
||||
|
||||
- [**breaking**] remove getPushState() and core's internal tracking of it
|
||||
- [**breaking**] remove `dc_chatlist_get_context()`, because it was easy to misuse and likely led to crashes ([#8503](https://github.com/chatmail/core/pull/8503))
|
||||
- instead, store reference-counted Context in `dc_msg_t`, `dc_contact_t` and `dc_chatlist_t`
|
||||
- add "pinned messages" API.
|
||||
|
||||
### Build system
|
||||
|
||||
- update all crates to Rust 2024 edition.
|
||||
|
||||
### CI
|
||||
|
||||
- update github actions monthly instead of weekly.
|
||||
|
||||
### Documentation
|
||||
|
||||
- clarify `ChatId::do_set_draft()` docs.
|
||||
- add missing slash to ConnectionSecurity::Starttls doc comment.
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- send Autocrypt pgp key in MDNs occassionally and when relaylist changes.
|
||||
- reduce unncessary gossipping of keys in group chats.
|
||||
- stop requiring XDELTAPUSH capability for push notifications.
|
||||
- prepare basic multi-relay onboarding ([#8444](https://github.com/chatmail/core/pull/8444))
|
||||
- collect ICE servers from all relays.
|
||||
- send messages to 5 relays instead of the newest 3 ones.
|
||||
- allow to send reactions in broadcast channels ([#8450](https://github.com/chatmail/core/pull/8450)).
|
||||
- allow only default reactions in channels broadcast ([#8545](https://github.com/chatmail/core/pull/8545)).
|
||||
- resend pinned state in broadcast channels ([#8549](https://github.com/chatmail/core/pull/8549)).
|
||||
|
||||
### Fixes
|
||||
|
||||
- **The primary transport is not synchronized between devices anymore.**
|
||||
- Don't warn about correct EXIF orientation values. ([#8483](https://github.com/chatmail/core/pull/8483)).
|
||||
- deltachat-rpc-client: don't depend on execnet for importing pytest plugin, remove deprecated "py" usage.
|
||||
- send MDNs to all authentic relays of a contact, not just whatever `get_addr()` returns..
|
||||
- mark `as_path()` function unsafe.
|
||||
- python: create event emitter when EventThread is initialized.
|
||||
- Don't download pre-message again if it is known already ([#8488](https://github.com/chatmail/core/pull/8488)).
|
||||
- recognize self addresses in various places (instead of just the "primary").
|
||||
- fix multi relay connectivity view ([#8550](https://github.com/chatmail/core/pull/8550)).
|
||||
- ensure same-second primary transport change propagates correctly.
|
||||
- invalidate `configured_addr` cache before sending transport sync message.
|
||||
- prevent transport de-synchronization because of early fetch cancellation.
|
||||
- improve connectivity HTML if quota info has an error.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- bump version to 2.58.0-dev.
|
||||
- deps: bump actions/setup-python from 6 to 6.3.0.
|
||||
- deps: bump zizmorcore/zizmor-action from 0.5.7 to 0.6.0.
|
||||
- cargo: bump futures from 0.3.32 to 0.3.33.
|
||||
- cargo: bump tokio from 1.52.3 to 1.53.0.
|
||||
- cargo: bump regex from 1.12.4 to 1.13.1.
|
||||
- disable "large futures" lint again.
|
||||
- cargo: bump tokio-util from 0.7.18 to 0.7.19.
|
||||
- deps: bump zizmorcore/zizmor-action from 0.6.0 to 0.6.1.
|
||||
- deps: bump taiki-e/install-action from 2.83.4 to 2.85.1.
|
||||
- cargo: bump `serde_json` from 1.0.150 to 1.0.151.
|
||||
- deps: bump pypa/gh-action-pypi-publish from 1.14.0 to 1.14.1.
|
||||
- deps: bump actions/setup-python from 6.3.0 to 7.0.0.
|
||||
- cargo: introduce syn 3 dependency.
|
||||
- cargo: bump anyhow from 1.0.103 to 1.0.104.
|
||||
- cargo: bump serde from 1.0.228 to 1.0.229.
|
||||
- cargo: bump thiserror from 2.0.18 to 2.0.19.
|
||||
- cargo: bump libc from 0.2.186 to 0.2.189.
|
||||
|
||||
### Performance
|
||||
|
||||
- Box::pin iroh::endpoint::Builder::bind in order to reduce memory usage.
|
||||
|
||||
### Refactor
|
||||
|
||||
- use the new regex! macro.
|
||||
- Remove FolderMeaning and `target_folder` ([#8456](https://github.com/chatmail/core/pull/8456)).
|
||||
- Unify naming of direct/single/1:1/normal chats ([#8442](https://github.com/chatmail/core/pull/8442)).
|
||||
- un-nest `prepare_msg_blob`.
|
||||
- do not clean `imap_send` table on transport change.
|
||||
- mark enabled ephemeral timer duration as NonZero.
|
||||
- reduce the scope of unsafe in `dc_context_unref()`.
|
||||
- mimefactory: separate rendering of message payload and sendable message.
|
||||
|
||||
### Tests
|
||||
|
||||
- fix flaky `test_markseen_message_and_mdn` test.
|
||||
- fix flaky `test_no_markseen_in_team_profile` ([#8500](https://github.com/chatmail/core/pull/8500)).
|
||||
- Add `test_bcc_self`.
|
||||
- Add test for unencrypted headers ([#8538](https://github.com/chatmail/core/pull/8538)).
|
||||
- Assert log warnings and errors ([#8457](https://github.com/chatmail/core/pull/8457)).
|
||||
|
||||
## [2.57.0] - 2026-07-25
|
||||
|
||||
### API-Changes
|
||||
|
||||
- [**breaking**] remove heartbeat push notifications.
|
||||
- [**breaking**] remove provider-db handling and provider lookup APIs.
|
||||
- provider lookup APIs were removed from CFFI and JSON-RPC.
|
||||
|
||||
also removes offline provider database code and generated provider data,
|
||||
provider-specific fields in configure/transport paths, and REPL providerinfo.
|
||||
|
||||
### Documentation
|
||||
|
||||
- remove oauth2 from standards.
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- accept messages from key contacts with forged From address.
|
||||
- enable TLS certificate compression.
|
||||
- read SMTP recipient limit from relay IMAP metadata.
|
||||
|
||||
### Fixes
|
||||
|
||||
- fixup CI failures.
|
||||
- never merge outer To headers if standard header protection is used.
|
||||
- Re-add oauth2 to serialized structs ([#8464](https://github.com/chatmail/core/pull/8464)).
|
||||
- migrate transports configured on 2.56 to also have a oauth:false flag.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- bump version to 2.57.0-dev.
|
||||
- deps: bump actions/setup-node from 6 to 7.
|
||||
- deps: bump cachix/install-nix-action from 31.10.6 to 31.11.0.
|
||||
- deps: bump EmbarkStudios/cargo-deny-action from 2.0.20 to 2.1.1.
|
||||
- deps: bump taiki-e/install-action from 2.82.10 to 2.83.4.
|
||||
- cargo: bump quinn-proto from 0.11.14 to 0.11.16.
|
||||
|
||||
## [2.56.0] - 2026-07-21
|
||||
|
||||
### API-Changes
|
||||
|
||||
- [**breaking**] remove all oauth support and drop DC_LP_AUTH flags.
|
||||
- removed oauth2 module, dc_get_oauth2_url FFI function, DC_LP_AUTH flags and configured/serverflags, and the oauth2 parameter/field from SMTP/IMAP clients, JSON-RPC interfaces, and CLI tools.
|
||||
|
||||
also contains regenerated provider data after dropping oauth in the update script.
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- do not set backup_time in exported databases.
|
||||
|
||||
### Fixes
|
||||
|
||||
- revert 207c2e6e4c1bec43204c3b8a46fcbbff67d54b3f because some users reported problems with it.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- bump version to 2.56.0-dev.
|
||||
|
||||
## [2.55.0] - 2026-07-20
|
||||
|
||||
Minor release to fix CI because releasing 2.54.0 failed.
|
||||
|
||||
### CI
|
||||
|
||||
- Update Node version to 24.
|
||||
|
||||
## [2.54.0] - 2026-07-20
|
||||
|
||||
### API-Changes
|
||||
|
||||
- [**breaking**] Deprecate `is_chatmail`.
|
||||
- UIs should not behave differently for chatmail relays than for classical email servers; most usages of `is_chatmail` can be replaced by `force_encryption`.
|
||||
- [**breaking**] `delete_transport()` must not be used by UIs anymore. Instead, `set_transport_unpublished()` must be called when a user clicks on "Remove".
|
||||
- [**breaking**] `list_transports()` doesn't return unpublished relays anymore.
|
||||
- UIs should use `list_transports()` rather than `list_transports_ex()`, because unpublished transports count as removed from the user point of view, and should not be shown in the relay list anymore.
|
||||
- deltachat-rpc-client: add `Account.set_transport_unpublished()`.
|
||||
- Add `MsgReadCountChanged` event.
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Implement support for populating and maintaining a list of default relays ([#8341](https://github.com/chatmail/core/pull/8341)).
|
||||
- Remove hidden relays automatically ([#8402](https://github.com/chatmail/core/pull/8402)).
|
||||
- Automatically remove oldest unpublished relay in order to make space when the user wants to add more; don't allow more than 5 relays overall ([#8428](https://github.com/chatmail/core/pull/8428)).
|
||||
- Add silent group changes messages as InNoticed, not InSeen.
|
||||
- Remove `?emailaddress` argument from autoconfig URL that is not using a dedicated domain.
|
||||
- Remove `imap::Session::sync_seen_flags()` ([#7742](https://github.com/chatmail/core/pull/7742)).
|
||||
- Use CAPABILITY response code if IMAP LOGIN command returns it.
|
||||
- Increase max idle timeout for iroh backup receiver to 60 seconds.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Request MDNs for resent channel messages.
|
||||
- Make pre-messages w/o text want MDNs ([#8004](https://github.com/chatmail/core/pull/8004)).
|
||||
- Make truncated edited messages have HTML for receivers ([#8249](https://github.com/chatmail/core/pull/8249)).
|
||||
- Un-escape message footer marks in full messages (`get_html`) ([#8427](https://github.com/chatmail/core/pull/8427)).
|
||||
- Hide synced chat if we only know its visibility ([#8343](https://github.com/chatmail/core/pull/8343)).
|
||||
- Tombstone MDN before sending it ([#8252](https://github.com/chatmail/core/pull/8252)).
|
||||
- Recreate `imap_markseen` with `PRIMARY KEY` constraint.
|
||||
- Rerun the full securejoin protocol if the address was outdated ([#8358](https://github.com/chatmail/core/pull/8358)).
|
||||
- Return early from `receive_imf` to not tombstone Iroh-Node-Addr message if webxdc instance isn't found ([#8372](https://github.com/chatmail/core/pull/8372)).
|
||||
- Replace `last_added_location_id` with `last_added_location_timestamp`.
|
||||
- Do not put locations into pre-messages.
|
||||
- RUSTSEC-2026-0204 ([#8403](https://github.com/chatmail/core/pull/8403)).
|
||||
- Ensure public key signatures are not in the past compared to the public key.
|
||||
- Do not bubble up errors in IMAP candidate loop.
|
||||
- Do not log errors if full message is not available on any transport.
|
||||
- Apply reactions that arrived before the message at later time ([#8415](https://github.com/chatmail/core/pull/8415)).
|
||||
|
||||
### Performance
|
||||
|
||||
- Add timestamp to `msgs_index7` and speed up `Chatlist::try_load()` ([#7848](https://github.com/chatmail/core/pull/7848)).
|
||||
|
||||
### CI
|
||||
|
||||
- Update Rust to 1.97.1.
|
||||
- rrsync prepends the restricted upload path, we need to leave it out ([#8405](https://github.com/chatmail/core/pull/8405)).
|
||||
|
||||
### Documentation
|
||||
|
||||
- Update STYLE.md: macros should be used only when necessary ([#8410](https://github.com/chatmail/core/pull/8410)).
|
||||
- `create_group_chat_unencrypted()` may lead to chat split on the first device.
|
||||
|
||||
### Refactor
|
||||
|
||||
- Deprecate unused `SkipAutocrypt` param.
|
||||
- Remove commented out `RenderedEmail.envelope`.
|
||||
- Remove the ability to send messages with non-standard header protection.
|
||||
- Make `crate::pgp::symm_encrypt_message` non-async.
|
||||
- Move `ensure_secret_key_exists` into key.rs.
|
||||
- Improve comment ([#8366](https://github.com/chatmail/core/pull/8366)).
|
||||
- Remove `set_modseq()` function.
|
||||
- Remove unnecessary reference in format string.
|
||||
- Label the loop iterating over the candidates.
|
||||
- Remove `GROUP BY c.id` from chatlist queries.
|
||||
|
||||
### Tests
|
||||
|
||||
- securejoin: Check that "vc-{,request-}pubkey" messages don't contain displayname.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- bump version to 2.54.0-dev.
|
||||
- deps: bump taiki-e/install-action from 2.81.1 to 2.81.8.
|
||||
- deps: bump taiki-e/install-action from 2.81.8 to 2.81.11.
|
||||
- update rPGP from 0.19.0 to 0.20.0.
|
||||
- update astral-tokio-tar from 0.6.2 to 0.6.3.
|
||||
- deps: bump anyhow to 1.0.103.
|
||||
- deps: bump actions/checkout from 6 to 7.
|
||||
- cargo: bump syn from 2.0.117 to 2.0.118.
|
||||
- cargo: bump quote from 1.0.45 to 1.0.46.
|
||||
- cargo: bump bytes from 1.11.1 to 1.12.0.
|
||||
- cargo: bump regex from 1.12.3 to 1.12.4.
|
||||
- cargo: bump log from 0.4.31 to 0.4.33.
|
||||
- cargo: bump hyper from 1.9.0 to 1.10.1.
|
||||
- deps: bump zizmorcore/zizmor-action from 0.5.6 to 0.5.7.
|
||||
- cargo: bump chrono from 0.4.44 to 0.4.45.
|
||||
- update quick-xml to 0.41.0.
|
||||
- cargo: bump brotli from 8.0.2 to 8.0.4.
|
||||
- cargo: bump smallvec from 1.15.1 to 1.15.2.
|
||||
- deps: bump taiki-e/install-action from 2.81.11 to 2.82.6.
|
||||
- update yanked spin@0.9.8 and spin@0.10.0.
|
||||
- deps: bump taiki-e/install-action from 2.82.6 to 2.82.10.
|
||||
- update async-imap to 0.11.3.
|
||||
|
||||
## [2.53.0] - 2026-06-15
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Make quality of images sent in chats more consistent between images with different aspect ratio.
|
||||
- `MsgId::get_html`: Make only one db query.
|
||||
- Do not log the recipient list for sent messages.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Do not trash pre-messages without text but with a webxdc update.
|
||||
- Don't send or process webxdc status updates in pre-messages.
|
||||
- Ignore SecureJoin messages from blocked contacts ([#8295](https://github.com/chatmail/core/pull/8295)).
|
||||
- Do not abort IMAP connection if setting the push token fails.
|
||||
|
||||
### Documentation
|
||||
|
||||
- STYLE.md: Require to list columns explicitly in `INSERT` statements.
|
||||
|
||||
### Build system
|
||||
|
||||
- nix: switch to the "master" branch for naersk.
|
||||
- flake.nix: Use hostPlatform.rust.rustcTarget instead of hardcoding it.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Bump version to 2.52.0-dev.
|
||||
- deps: bump taiki-e/install-action from 2.79.10 to 2.81.1.
|
||||
- deps: bump EmbarkStudios/cargo-deny-action from 2.0.19 to 2.0.20.
|
||||
- Bump version to 2.53.0-dev.
|
||||
|
||||
### Refactor
|
||||
|
||||
- Move the definition of the `target_wh`-variable.
|
||||
- Remove timesmearing.
|
||||
|
||||
### Tests
|
||||
|
||||
- Print multiline chat descriptions with debug formatter.
|
||||
- `exec_securejoin_qr_multi_device()`: Make inviter devices receive each other messages.
|
||||
- Fixup the tests after removing timesmearing.
|
||||
- Remove timeout from `pop_sent_msg_ex()`.
|
||||
|
||||
## [2.52.0] - 2026-06-09
|
||||
|
||||
### Fixes
|
||||
|
||||
- Update the channel title after joining if the QR code included a wrong title ([#8260](https://github.com/chatmail/core/pull/8260)).
|
||||
- Don't send removal message to contact that hasn't been a chat member ([#8298](https://github.com/chatmail/core/pull/8298)).
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Add cryptography-related statistics (`number_of_transports`, `key_version`, `key_algorithm`, `pubkey_size`, `number_of_keys`) ([#8293](https://github.com/chatmail/core/pull/8293), [#8297](https://github.com/chatmail/core/pull/8297)).
|
||||
- Add IMAP folder to `Context::get_info()` ([#8285](https://github.com/chatmail/core/pull/8285)).
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Update preloaded DNS cache.
|
||||
- Use default aws-lc-rs cryptography provider for rustls.
|
||||
- Add exception for unmaintained proc-macro-error2 to deny.toml.
|
||||
- cargo: bump `pin-project` from 1.1.11 to 1.1.13.
|
||||
- cargo: bump `tokio` from 1.52.1 to 1.52.3.
|
||||
- cargo: bump `log` from 0.4.29 to 0.4.30.
|
||||
- cargo: bump `serde_json` from 1.0.149 to 1.0.150.
|
||||
- deps: bump EmbarkStudios/cargo-deny-action from 2.0.18 to 2.0.19.
|
||||
- deps: bump taiki-e/install-action from 2.79.2 to 2.79.10.
|
||||
|
||||
### Build system
|
||||
|
||||
- nix: fix windows cross-compilation by adding pthreads includes.
|
||||
|
||||
### Refactor
|
||||
|
||||
- Remove support for building "source" packages for deltachat-rpc-server.
|
||||
|
||||
## [2.51.0] - 2026-05-29
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Follow certificate check parameter in autoconfig.
|
||||
- Immediately remove all encrypted messages from the server in single-device mode.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix syntax error in `only_fetch_mvbox` migration 150 resulting in failure to upgrade for `only_fetch_mvbox` users.
|
||||
- Do not try to resolve proxy IPv6 addresses in square brackets.
|
||||
- Do not fail to receive post-message with status updates for deleted webxdc.
|
||||
- Don't make message `OutDelivered` after successful resending to new broadcast member.
|
||||
|
||||
### Build system
|
||||
|
||||
- nix: fix downloads from crates.io in nix builds.
|
||||
|
||||
### Documentation
|
||||
|
||||
- Fix reference in `delete_expired_imap_messages` comment.
|
||||
|
||||
### Refactor
|
||||
|
||||
- Remove `pre_encrypt_mime_hook`.
|
||||
- Make `should_delete_all_downloaded_messages` non-async.
|
||||
|
||||
### Tests
|
||||
|
||||
- Test IPv6 addresses in HTTP(S) proxies.
|
||||
- Test `bcc_self` in `test_delete_expired_imap_messages`.
|
||||
- Test encrypted messages in `test_delete_expired_imap_messages`.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Bump version to 2.51.0-dev.
|
||||
- deps: bump zizmorcore/zizmor-action from 0.5.3 to 0.5.6.
|
||||
- deps: bump taiki-e/install-action from 2.78.1 to 2.79.2.
|
||||
|
||||
## [2.50.0] - 2026-05-22
|
||||
|
||||
### API-Changes
|
||||
|
||||
- Add JSON-RPC APIs for location streaming.
|
||||
- [**breaking**] Remove unused config `smtp_certificate_checks`.
|
||||
- Deprecate old server config keys that were replaced by `add_or_update_transport()`.
|
||||
- [**breaking**] remove `dc_delete_all_locations`.
|
||||
- [**breaking**] Remove unused `info_only` option when loading a chatlist ([#8171](https://github.com/chatmail/core/pull/8171)).
|
||||
- [**breaking**] location: avoid repeating module name in function names
|
||||
- [**breaking**] deltachat-rpc-client: remove deprecated `get_fresh_messages_in_arrival_order()`.
|
||||
- Remove unused `set_draft_vcard()` JSON-RPC API.
|
||||
- Remove mostly-unused `sign_unencrypted` config ([#8190](https://github.com/chatmail/core/pull/8190)).
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- [**breaking**] Remove `mvbox_move` and `only_fetch_mvbox` configs. Transports have `folder` configuration to watch the folder other than `INBOX` as a replacement for `only_fetch_mvbox`. Non-chatmail transports no longer watch mvbox, each transport watches exactly one folder now.
|
||||
- Add `force_encryption` config to ignore incoming unencrypted messages and enforce encryption for outgoing messages.
|
||||
- Remove "Delete Messages from Server" (`delete_server_after`) config ([#8240](https://github.com/chatmail/core/pull/8240)).
|
||||
- Remove `show_emails` config.
|
||||
- Remove non-sticker heuristics and `force_sticker()`. UIs should make sure not to send images from gallery such as screenshots as stickers.
|
||||
- Enable PQC (Post-Quantum Cryptography) support for OpenPGP. We do not generate PQC keys yet, this step is needed for forward compatibility.
|
||||
- Resend the last 10 messages to new broadcast member ([#8151](https://github.com/chatmail/core/pull/8151)).
|
||||
- Allow TLS connections with invalid certificate if the key is unchanged.
|
||||
- Add `is_app_sender` and `is_broadcast` contexts for webxdc.
|
||||
- Increase the resolution-limit `WORSE_AVATAR_SIZE` from 128 to 256.
|
||||
- Change multiplier to 7/8 when scaling down avatars.
|
||||
- Add error cause to connectivity view for IMAP errors.
|
||||
- Remove the largely-unused ability to send multiple reactions to one message ([#8131](https://github.com/chatmail/core/pull/8131)).
|
||||
- Don't show non-delivery-notfications in broadcast channels ([#8159](https://github.com/chatmail/core/pull/8159)).
|
||||
- Adapt quota warning to automatic cleanup.
|
||||
- Remove `Content-Description` and `Content-Disposition` from `multipart/encrypted` parts.
|
||||
- Log all connection attempt errors instead of the first one.
|
||||
- Remove workaround for old filtermail (part of chatmail relay) which expected exact number of newlines in OpenPGP messages.
|
||||
- Remove key fingerprint from `Context.get_info()`.
|
||||
- Mask local part of email addresses in `used_transport_settings`.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Trash no-op messages about self being added to groups.
|
||||
- `decide_chat_assignment`: Log correct `post_msg_exists` value.
|
||||
- Don't send `Chat-Group-Name*` headers for InBroadcast-s.
|
||||
- Restart io on transport deletion.
|
||||
- Never remove primary transport when applying `SyncTransports` message.
|
||||
- Set Param::GuaranteeE2ee before preparing message blob ([#8090](https://github.com/chatmail/core/pull/8090)).
|
||||
- `fetch_single_msg()`: Lock `fetch_msgs_mutex` before fetching.
|
||||
- Set dir to "auto" in body tag when converting plain-text to HTML ([#8227](https://github.com/chatmail/core/pull/8227)).
|
||||
- Scale up contacts messaged in groups to `IncomingTo`.
|
||||
- Do not sort prefetched messages by INTERNALDATE.
|
||||
- Don't resort re-sent message to the bottom ([#8145](https://github.com/chatmail/core/pull/8145)).
|
||||
- Ensure that message being sent is added to the bottom ([#8027](https://github.com/chatmail/core/pull/8027)).
|
||||
- Don't receive message if a deletion request was received before ([#8143](https://github.com/chatmail/core/pull/8143)).
|
||||
- Emit `MsgsChanged`, not `IncomingMsg`, for messages only having special parts ([#8157](https://github.com/chatmail/core/pull/8157)).
|
||||
- Generate new pre-message `Message-ID` when forwarding.
|
||||
- use correct dir converting plaintext to HTML ([#8248](https://github.com/chatmail/core/pull/8248)).
|
||||
- hide connectivity HTML quota if not supported.
|
||||
- Delete pre-messages on the server for single-device chatmail transports ([#8240](https://github.com/chatmail/core/pull/8240)).
|
||||
|
||||
### Build system
|
||||
|
||||
- Upgrade rustls-webpki to 0.103.12.
|
||||
- Remove coredeps `Dockerfile`.
|
||||
- Increase MSRV to 1.89.
|
||||
|
||||
### CI
|
||||
|
||||
- Remove Concourse CI pipelines.
|
||||
- Update Rust to 1.95.0.
|
||||
- Do not store Rust cache from PRs.
|
||||
- Set cache-bin to "false" for `swatinem/rust-cache` action.
|
||||
- Use `--locked` flag with `cargo build`.
|
||||
- Upgrade `cargo-deny-action` to v2.0.17.
|
||||
|
||||
### Documentation
|
||||
|
||||
- Update `echobot_no_hooks.py` example.
|
||||
- Discourage `into()`, `try_into()` and `parse()` ([#8180](https://github.com/chatmail/core/pull/8180)).
|
||||
- Remove outdated comment about "quota warning" device message.
|
||||
- Update README.md: Use ci-chatmail instead of nine ([#8238](https://github.com/chatmail/core/pull/8238)).
|
||||
- spec: remove AEAP section.
|
||||
|
||||
### Performance
|
||||
|
||||
- Enable `clippy::large_futures` lint.
|
||||
- Stop sending locations concurrently.
|
||||
- Set location for all accounts in parallel.
|
||||
- `is_self_addr()`: Employ the config cache to optimize for `ConfiguredAddr` passed.
|
||||
|
||||
### Refactor
|
||||
|
||||
- Get rid of `MessageState::{OutPreparing,OutMdnRcvd}` in the db.
|
||||
- Make HTML parser non-async.
|
||||
- Replace `HashSet` with `BTreeSet`.
|
||||
- Rename `EnteredLoginParam::load()` and save() to `load_legacy()` and `save_legacy()`.
|
||||
- Remove unnecessary async block in `dc_set_location`.
|
||||
- Remove unused Authentication-Results parsing ([#8172](https://github.com/chatmail/core/pull/8172)).
|
||||
- Remove mostly-unused function `get_secondary_self_addrs()` ([#8173](https://github.com/chatmail/core/pull/8173)).
|
||||
- Use `self_fingerprint()` where it makes sense ([#8174](https://github.com/chatmail/core/pull/8174)).
|
||||
- Split `is_sending_locations_to_chat()` into two functions.
|
||||
- Use regular functions rather than FromStr impls ([#8178](https://github.com/chatmail/core/pull/8178)).
|
||||
- Make `Fingerprint` not implement `Display` ([#8177](https://github.com/chatmail/core/pull/8177)).
|
||||
- Don't temporarily extend `signatures` for signed-only messages.
|
||||
- Use some more let..else.
|
||||
- Remove outdated comment.
|
||||
- Un-nest `handle_edit_delete`.
|
||||
- Drop support for replacing partial download stubs.
|
||||
|
||||
### Tests
|
||||
|
||||
- Use `displayname` instead of `show_emails` for config cache test.
|
||||
- Remove unused test data related to Authentication-Result parsing ([#8175](https://github.com/chatmail/core/pull/8175)).
|
||||
- `EventTracker::get_matching_opt`: Return the first matching event, not last.
|
||||
- Set email addresses explicitly for the test accounts.
|
||||
- Use encrypted messages in more tests.
|
||||
- Add `TestContext.allow_unencrypted()`.
|
||||
- Online test for legacy Secure-Join key request.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- cargo: bump rand from 0.9.2 to 0.9.3.
|
||||
- deps: bump taiki-e/install-action from 2.64.0 to 2.74.0.
|
||||
- add exception for RUSTSEC-2026-0097.
|
||||
- deps: bump swatinem/rust-cache from 2.8.2 to 2.9.1.
|
||||
- cargo: upgrade rand 0.8.5 to rand 0.8.6.
|
||||
- deps: bump zizmorcore/zizmor-action from 0.5.2 to 0.5.3.
|
||||
- deps: bump pypa/gh-action-pypi-publish from 1.13.0 to 1.14.0.
|
||||
- update provider database.
|
||||
- cargo: update rustls-webpki to 0.103.13.
|
||||
- cargo: bump openssl from 0.10.72 to 0.10.78.
|
||||
- Apply rustmft after the previous commit.
|
||||
- json-rpc: deprecate `send_sticker` ([#8189](https://github.com/chatmail/core/pull/8189)).
|
||||
- deps: bump cachix/install-nix-action from 31.9.1 to 31.10.5.
|
||||
- deps: bump taiki-e/install-action from 2.75.10 to 2.75.19.
|
||||
- update astral-tokio-tar from 0.6.0 to 0.6.1.
|
||||
- add exceptions for hickory-proto 0.25.2 in deny.toml.
|
||||
- cargo: bump blake3 from 1.8.3 to 1.8.5.
|
||||
- deny.toml: add cpufeatures duplicate dependency exception.
|
||||
- cargo: bump hyper from 1.8.1 to 1.9.0.
|
||||
- cargo: bump tokio from 1.50.0 to 1.52.1.
|
||||
- cargo: bump libc from 0.2.184 to 0.2.186.
|
||||
- cargo: bump colorutils-rs from 0.7.6 to 0.8.0.
|
||||
- cargo: bump data-encoding from 2.10.0 to 2.11.0.
|
||||
- cargo: bump openssl from 0.10.78 to 0.10.79.
|
||||
- deps: bump taiki-e/install-action from 2.75.19 to 2.77.1.
|
||||
- deps: bump cachix/install-nix-action from 31.10.5 to 31.10.6.
|
||||
- allow passing arguments to scripts/clippy.sh.
|
||||
- clippy::useless-borrows-in-formatting fixes.
|
||||
- update zerocopy from 0.7.32 to 0.7.35.
|
||||
- upgrade astral-tokio-tar to 0.6.2 ([#8255](https://github.com/chatmail/core/pull/8255)).
|
||||
- deps: bump EmbarkStudios/cargo-deny-action from 2.0.17 to 2.0.18.
|
||||
- cargo: bump openssl from 0.10.79 to 0.10.80.
|
||||
- deps: bump taiki-e/install-action from 2.77.1 to 2.78.1.
|
||||
|
||||
## [2.49.0] - 2026-04-13
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Flipped Exif orientations ([#8057](https://github.com/chatmail/core/pull/8057)).
|
||||
|
||||
### Fixes
|
||||
|
||||
- Determine whether a message is an own message by looking at signature. multiple devices can temporarly have different sets of self addresses, and still need to properly recognize incoming versus outgoing messages. Disclaimer: some LLM tooling was initially involved but i went over everything by hand, and also addressed review comments..
|
||||
- Mark a message as delivered only after it has been fully sent out ([#8062](https://github.com/chatmail/core/pull/8062)).
|
||||
- Do not create 1:1 chat on second device when scanning a QR code.
|
||||
- Do not URL-encode proxy hostnames.
|
||||
- Assign webxdc updates from post-message to webxdc instance.
|
||||
- Let search also return hidden contacts if search value is an email address.
|
||||
- Add missing `extern "C"` to `dc_array_is_independent`.
|
||||
- Make start messages stick to the top of the chat.
|
||||
- For bots, wait with emitting IncomingMsg until the Post-Msg arrived ([#8104](https://github.com/chatmail/core/pull/8104)).
|
||||
- Trash message about group name change from non-member.
|
||||
|
||||
### API-Changes
|
||||
|
||||
- [**breaking**] remove `dc_msg_force_plaintext`.
|
||||
- @deltachat/stdio-rpc-server: also export a class.
|
||||
|
||||
### CI
|
||||
|
||||
- Make sure `-dev` version suffix is not forgotten after release.
|
||||
|
||||
### Documentation
|
||||
|
||||
- Document that events are broadcasted to all event emitters.
|
||||
- Fix broken link for i-d "Common PGP/MIME Message Mangling".
|
||||
|
||||
### Refactor
|
||||
|
||||
- ignore ForcePlaintext in saved messages chat.
|
||||
- @deltachat/stdio-rpc-server: make `getRPCServerPath` and `startDeltaChat` synchronous.
|
||||
- @deltachat/stdio-rpc-server: remove `await` from README example.
|
||||
- less nested `remove_contact_from_chat`.
|
||||
|
||||
### Tests
|
||||
|
||||
- Add test for `tweak_sort_timestamp()`.
|
||||
- Test that messages are only marked as delivered after being fully sent out ([#8077](https://github.com/chatmail/core/pull/8077)).
|
||||
- Fix flaky `test_no_old_msg_is_fresh`: Wait for incoming message before sending outgoing one.
|
||||
- Use TestContextManager in `test_keep_member_list_if_possibly_nomember`.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- cargo: bump chrono from 0.4.43 to 0.4.44.
|
||||
- cargo: bump tracing-subscriber from 0.3.22 to 0.3.23.
|
||||
- cargo: bump tempfile from 3.26.0 to 3.27.0.
|
||||
- cargo: bump pin-project from 1.1.10 to 1.1.11.
|
||||
- cargo: bump tokio from 1.49.0 to 1.50.0.
|
||||
- cargo: bump libc from 0.2.182 to 0.2.183.
|
||||
- cargo: bump quote from 1.0.44 to 1.0.45.
|
||||
- cargo: bump image from 0.25.9 to 0.25.10.
|
||||
- cargo: bump proptest from 1.10.0 to 1.11.0.
|
||||
- deps: bump dependabot/fetch-metadata from 2.4.0 to 3.0.0.
|
||||
- bump version to 2.49.0-dev.
|
||||
|
||||
## [2.48.0] - 2026-03-30
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix reordering problems in multi-relay setups by not sorting received messages below the last seen one.
|
||||
- Always sort "Messages are end-to-end encrypted" notice to the beginning.
|
||||
- Make Message-ID of pre-messages stable across resends ([#8007](https://github.com/chatmail/core/pull/8007)).
|
||||
- Delete `imap_markseen` entries not corresponding to any `imap` rows.
|
||||
- Cleanup `imap` and `imap_sync` records without transport in housekeeping.
|
||||
- When receiving MDN, mark all preceding messages as noticed, even having same timestamp ([#7928](https://github.com/chatmail/core/pull/7928)).
|
||||
- Remove migration 108 preventing upgrades from core 1.86.0 to the latest version.
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Improve IMAP loop logs.
|
||||
- Add decryption error to the device message about outgoing message decryption failure.
|
||||
- Log received message sort timestamp.
|
||||
|
||||
### Performance
|
||||
|
||||
- Move sorting outside of SQL query in `store_seen_flags_on_imap`.
|
||||
|
||||
### API-Changes
|
||||
|
||||
- Add JSON-RPC API `markfresh_chat()`.
|
||||
- ffi: Correctly declare `dc_event_channel_new()` as having no params ([#7831](https://github.com/chatmail/core/pull/7831)).
|
||||
|
||||
### Refactor
|
||||
|
||||
- Remove `wal_checkpoint_mutex`, lock `write_mutex` before getting sql connection instead.
|
||||
- Replace async `RwLock` with sync `RwLock` for stock strings.
|
||||
- Cleanup remaining Autocrypt Setup Message processing in `mimeparser`.
|
||||
- SecureJoin: do not check for self address in forwarding protection.
|
||||
- Fix clippy warnings.
|
||||
|
||||
### CI
|
||||
|
||||
- Update {c,py}.delta.chat website deployments.
|
||||
- Use environments for {rs,cffi,js.jsonrpc}.delta.chat deployments.
|
||||
- Fix https://docs.zizmor.sh/audits/#bot-conditions.
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add SQL performance tips to STYLE.md.
|
||||
|
||||
### Tests
|
||||
|
||||
- Remove `test_old_message_5`.
|
||||
- Do not rely on loading newest chat in `load_imf_email()`.
|
||||
- Use `load_imf_email()` more.
|
||||
- The message is sorted correctly in the chat even if it arrives late.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- cargo: update rustls-webpki to 0.103.10.
|
||||
|
||||
## [2.47.0] - 2026-03-24
|
||||
|
||||
### Fixes
|
||||
@@ -7984,3 +8680,15 @@ https://github.com/chatmail/core/pulls?q=is%3Apr+is%3Aclosed
|
||||
[2.45.0]: https://github.com/chatmail/core/compare/v2.44.0..v2.45.0
|
||||
[2.46.0]: https://github.com/chatmail/core/compare/v2.45.0..v2.46.0
|
||||
[2.47.0]: https://github.com/chatmail/core/compare/v2.46.0..v2.47.0
|
||||
[2.48.0]: https://github.com/chatmail/core/compare/v2.47.0..v2.48.0
|
||||
[2.49.0]: https://github.com/chatmail/core/compare/v2.48.0..v2.49.0
|
||||
[2.50.0]: https://github.com/chatmail/core/compare/v2.49.0..v2.50.0
|
||||
[2.51.0]: https://github.com/chatmail/core/compare/v2.50.0..v2.51.0
|
||||
[2.52.0]: https://github.com/chatmail/core/compare/v2.51.0..v2.52.0
|
||||
[2.53.0]: https://github.com/chatmail/core/compare/v2.52.0..v2.53.0
|
||||
[2.54.0]: https://github.com/chatmail/core/compare/v2.53.0..v2.54.0
|
||||
[2.55.0]: https://github.com/chatmail/core/compare/v2.54.0..v2.55.0
|
||||
[2.56.0]: https://github.com/chatmail/core/compare/v2.55.0..v2.56.0
|
||||
[2.57.0]: https://github.com/chatmail/core/compare/v2.56.0..v2.57.0
|
||||
[2.58.0]: https://github.com/chatmail/core/compare/v2.57.0..v2.58.0
|
||||
[2.59.0]: https://github.com/chatmail/core/compare/v2.58.0..v2.59.0
|
||||
|
||||
724
Cargo.lock
generated
724
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
21
Cargo.toml
21
Cargo.toml
@@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "2.48.0-dev"
|
||||
version = "2.60.0-dev"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.88"
|
||||
rust-version = "1.89"
|
||||
repository = "https://github.com/chatmail/core"
|
||||
|
||||
[profile.dev]
|
||||
@@ -44,7 +44,7 @@ ratelimit = { path = "./deltachat-ratelimit" }
|
||||
anyhow = { workspace = true }
|
||||
async-broadcast = "0.7.2"
|
||||
async-channel = { workspace = true }
|
||||
async-imap = { version = "0.11.1", default-features = false, features = ["runtime-tokio", "compress"] }
|
||||
async-imap = { version = "0.11.3", default-features = false, features = ["runtime-tokio", "compress"] }
|
||||
async-native-tls = { version = "0.6", default-features = false, features = ["runtime-tokio"] }
|
||||
async-smtp = { version = "0.10.2", default-features = false, features = ["runtime-tokio"] }
|
||||
async_zip = { version = "0.0.18", default-features = false, features = ["deflate", "tokio-fs"] }
|
||||
@@ -53,7 +53,7 @@ blake3 = "1.8.2"
|
||||
brotli = { version = "8", default-features=false, features = ["std"] }
|
||||
bytes = "1"
|
||||
chrono = { workspace = true, features = ["alloc", "clock", "std"] }
|
||||
colorutils-rs = { version = "0.7.5", default-features = false }
|
||||
colorutils-rs = { version = "0.8.0", default-features = false }
|
||||
data-encoding = "2.9.0"
|
||||
escaper = "0.1"
|
||||
fast-socks5 = "1"
|
||||
@@ -78,10 +78,10 @@ num-derive = "0.4"
|
||||
num-traits = { workspace = true }
|
||||
parking_lot = "0.12.4"
|
||||
percent-encoding = "2.3"
|
||||
pgp = { version = "0.19.0", default-features = false }
|
||||
pgp = { version = "0.20.0", features = ["draft-pqc"], default-features = false }
|
||||
pin-project = "1"
|
||||
qrcodegen = "1.7.0"
|
||||
quick-xml = { version = "0.39", features = ["escape-html"] }
|
||||
quick-xml = { version = "0.41", features = ["escape-html"] }
|
||||
rand-old = { package = "rand", version = "0.8" }
|
||||
rand = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
@@ -89,7 +89,6 @@ rusqlite = { workspace = true, features = ["sqlcipher"] }
|
||||
sanitize-filename = { workspace = true }
|
||||
sdp = "0.17.1"
|
||||
serde_json = { workspace = true }
|
||||
serde_urlencoded = "0.7.1"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
sha-1 = "0.10"
|
||||
sha2 = "0.10"
|
||||
@@ -101,9 +100,9 @@ tagger = "4.3.4"
|
||||
textwrap = "0.16.2"
|
||||
thiserror = { workspace = true }
|
||||
tokio-io-timeout = "1.2.1"
|
||||
tokio-rustls = { version = "0.26.2", default-features = false }
|
||||
tokio-rustls = { version = "0.26.2", default-features = false, features = ["tls12", "brotli"] }
|
||||
tokio-stream = { version = "0.1.17", features = ["fs"] }
|
||||
astral-tokio-tar = { version = "0.6", default-features = false }
|
||||
astral-tokio-tar = { version = "0.6.3", default-features = false }
|
||||
tokio-util = { workspace = true }
|
||||
tokio = { workspace = true, features = ["fs", "rt-multi-thread", "macros"] }
|
||||
toml = "0.9"
|
||||
@@ -181,7 +180,7 @@ harness = false
|
||||
anyhow = "1"
|
||||
async-channel = "2.5.0"
|
||||
base64 = "0.22"
|
||||
chrono = { version = "0.4.43", default-features = false }
|
||||
chrono = { version = "0.4.44", default-features = false }
|
||||
deltachat-contact-tools = { path = "deltachat-contact-tools" }
|
||||
deltachat-jsonrpc = { path = "deltachat-jsonrpc", default-features = false }
|
||||
deltachat = { path = ".", default-features = false }
|
||||
@@ -198,7 +197,7 @@ rusqlite = "0.37"
|
||||
sanitize-filename = "0.6"
|
||||
serde = "1.0"
|
||||
serde_json = "1"
|
||||
tempfile = "3.25.0"
|
||||
tempfile = "3.27.0"
|
||||
thiserror = "2"
|
||||
tokio = "1"
|
||||
tokio-util = "0.7.18"
|
||||
|
||||
33
STYLE.md
33
STYLE.md
@@ -59,6 +59,13 @@ If column is already declared without `NOT NULL`, use `IFNULL` function to provi
|
||||
Use `HAVING COUNT(*) > 0` clause
|
||||
to [prevent aggregate functions such as `MIN` and `MAX` from returning `NULL`](https://stackoverflow.com/questions/66527856/aggregate-functions-max-etc-return-null-instead-of-no-rows).
|
||||
|
||||
List columns explicitly in `INSERT` statements:
|
||||
```
|
||||
INSERT OR IGNORE INTO download (rfc724_mid, msg_id) VALUES (?,0);
|
||||
```
|
||||
Otherwise if a new column with default value is added in a future DB version, an upgraded DB can't
|
||||
be used with the old code, e.g. after transferring a DB from a device running a newer version.
|
||||
|
||||
Don't delete unused columns too early, but maybe after several months/releases, unused columns are
|
||||
still used by older versions, so deleting them breaks downgrading the core or importing a backup in
|
||||
an older version. Also don't change the column type, consider adding a new column with another name
|
||||
@@ -68,6 +75,12 @@ keyword doesn't help here.
|
||||
Consider adding context to `anyhow` errors for SQL statements using `.context()` so that it's
|
||||
possible to understand from logs which statement failed. See [Errors](#errors) for more info.
|
||||
|
||||
When changing complex SQL queries, test them on a new database with `EXPLAIN QUERY PLAN`
|
||||
to make sure that indexes are used and large tables are not going to be scanned.
|
||||
Never run `ANALYZE` on the databases,
|
||||
this makes query planner unpredictable
|
||||
and may make performance significantly worse: <https://github.com/chatmail/core/issues/6585>
|
||||
|
||||
## Errors
|
||||
|
||||
Delta Chat core mostly uses [`anyhow`](https://docs.rs/anyhow/) errors.
|
||||
@@ -155,3 +168,23 @@ are documented.
|
||||
|
||||
Follow Rust guidelines for the documentation comments:
|
||||
<https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#summary-sentence>
|
||||
|
||||
## Do not use `into()`, `try_into()` or `parse()`
|
||||
|
||||
For internal types, implementing `From`, `TryFrom` or `FromStr` is discouraged.
|
||||
Instead, a `new()` function is recommended.
|
||||
|
||||
For external types, prefer using `Type::from()`, `Type::try_from()` or `Type::from_str()`
|
||||
over `into()`, `try_into()` or `parse()`.
|
||||
|
||||
Calling `into()`, `try_into()` or `parse()`
|
||||
creates an indirection,
|
||||
which is hard to follow for people who are not familiar with Rust,
|
||||
or who are not using rust-analyzer.
|
||||
|
||||
## Use macros only when really needed
|
||||
|
||||
Macros can be hard to read for people unfamiliar with Rust,
|
||||
and can have surprising effects like evaluating arguments multiple times.
|
||||
Therefore, macros should only be used when really needed;
|
||||
using functions is usually better.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "deltachat-contact-tools"
|
||||
version = "0.0.0" # No semver-stable versioning
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
description = "Contact-related tools, like parsing vcards and sanitizing name and address. Meant for internal use in the deltachat crate."
|
||||
license = "MPL-2.0"
|
||||
|
||||
|
||||
@@ -29,17 +29,16 @@
|
||||
|
||||
use std::fmt;
|
||||
use std::ops::Deref;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use anyhow::bail;
|
||||
use anyhow::Result;
|
||||
use regex::Regex;
|
||||
use anyhow::bail;
|
||||
use regex::regex;
|
||||
|
||||
mod vcard;
|
||||
pub use vcard::{make_vcard, parse_vcard, VcardContact};
|
||||
pub use vcard::{VcardContact, make_vcard, parse_vcard};
|
||||
|
||||
/// Valid contact address.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ContactAddress(String);
|
||||
|
||||
impl Deref for ContactAddress {
|
||||
@@ -88,9 +87,7 @@ impl rusqlite::types::ToSql for ContactAddress {
|
||||
/// - Removes special characters from the name, see [`sanitize_name()`]
|
||||
/// - Removes the name if it is equal to the address by setting it to ""
|
||||
pub fn sanitize_name_and_addr(name: &str, addr: &str) -> (String, String) {
|
||||
static ADDR_WITH_NAME_REGEX: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new("(.*)<(.*)>").unwrap());
|
||||
let (name, addr) = if let Some(captures) = ADDR_WITH_NAME_REGEX.captures(addr.as_ref()) {
|
||||
let (name, addr) = if let Some(captures) = regex!("(.*)<(.*)>").captures(addr.as_ref()) {
|
||||
(
|
||||
if name.is_empty() {
|
||||
captures.get(1).map_or("", |m| m.as_str())
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use anyhow::Context as _;
|
||||
use anyhow::Result;
|
||||
use chrono::DateTime;
|
||||
use chrono::NaiveDateTime;
|
||||
use regex::Regex;
|
||||
use regex::regex;
|
||||
|
||||
use crate::sanitize_name_and_addr;
|
||||
|
||||
@@ -210,9 +208,7 @@ pub fn parse_vcard(vcard: &str) -> Vec<VcardContact> {
|
||||
}
|
||||
|
||||
// Remove line folding, see https://datatracker.ietf.org/doc/html/rfc6350#section-3.2
|
||||
static NEWLINE_AND_SPACE_OR_TAB: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new("\r?\n[\t ]").unwrap());
|
||||
let unfolded_lines = NEWLINE_AND_SPACE_OR_TAB.replace_all(vcard, "");
|
||||
let unfolded_lines = regex!("\r?\n[\t ]").replace_all(vcard, "");
|
||||
|
||||
let mut lines = unfolded_lines.lines().peekable();
|
||||
let mut contacts = Vec::new();
|
||||
|
||||
@@ -220,7 +220,10 @@ END:VCARD
|
||||
assert_eq!(contacts[0].addr, "bob@example.org".to_string());
|
||||
assert_eq!(contacts[0].authname, "Bob".to_string());
|
||||
assert_eq!(contacts[0].key, None);
|
||||
assert_eq!(contacts[0].profile_image.as_deref().unwrap(), "/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAL8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q==");
|
||||
assert_eq!(
|
||||
contacts[0].profile_image.as_deref().unwrap(),
|
||||
"/9j/4AAQSkZJRgABAQAAAQABAAD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYAAAAAAQwAABtbnRyUkdCIFhZWiAAAAAAAAAAAAAAAABhY3NwAAAAAAAAAAAAAAAAL8bRuAJYoZUYrI4ZY3VWwxw4Ay28AAGBISScmf/2Q=="
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +247,10 @@ END:VCARD",
|
||||
assert_eq!(contacts.len(), 1);
|
||||
assert_eq!(&contacts[0].addr, "alice@example.org");
|
||||
assert_eq!(&contacts[0].authname, "Alice Wonderland");
|
||||
assert_eq!(contacts[0].key.as_ref().unwrap(), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
||||
assert_eq!(
|
||||
contacts[0].key.as_ref().unwrap(),
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
||||
);
|
||||
assert!(contacts[0].timestamp.is_err());
|
||||
assert_eq!(contacts[0].profile_image, None);
|
||||
}
|
||||
@@ -272,9 +278,15 @@ END:VCARD",
|
||||
assert_eq!(contacts.len(), 1);
|
||||
assert_eq!(&contacts[0].addr, "alice@example.org");
|
||||
assert_eq!(&contacts[0].authname, "Alice");
|
||||
assert_eq!(contacts[0].key.as_ref().unwrap(), "xsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa==");
|
||||
assert_eq!(
|
||||
contacts[0].key.as_ref().unwrap(),
|
||||
"xsaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=="
|
||||
);
|
||||
assert!(contacts[0].timestamp.is_err());
|
||||
assert_eq!(contacts[0].profile_image.as_ref().unwrap(), "/9aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Z");
|
||||
assert_eq!(
|
||||
contacts[0].profile_image.as_ref().unwrap(),
|
||||
"/9aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/Z"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "2.48.0-dev"
|
||||
version = "2.60.0-dev"
|
||||
description = "Deltachat FFI"
|
||||
edition = "2018"
|
||||
edition = "2024"
|
||||
readme = "README.md"
|
||||
license = "MPL-2.0"
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ typedef struct _dc_chat dc_chat_t;
|
||||
typedef struct _dc_msg dc_msg_t;
|
||||
typedef struct _dc_contact dc_contact_t;
|
||||
typedef struct _dc_lot dc_lot_t;
|
||||
typedef struct _dc_provider dc_provider_t;
|
||||
typedef struct _dc_event dc_event_t;
|
||||
typedef struct _dc_event_emitter dc_event_emitter_t;
|
||||
typedef struct _dc_event_channel dc_event_channel_t;
|
||||
@@ -364,18 +363,14 @@ uint32_t dc_get_id (dc_context_t* context);
|
||||
* To get these events, you have to create an event emitter using this function
|
||||
* and call dc_get_next_event() on the emitter.
|
||||
*
|
||||
* Events are broadcasted to all existing event emitters.
|
||||
* Events emitted before creation of event emitter
|
||||
* are not available to event emitter.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object as created by dc_context_new().
|
||||
* @return Returns the event emitter, NULL on errors.
|
||||
* Must be freed using dc_event_emitter_unref() after usage.
|
||||
*
|
||||
* Note: Use only one event emitter per context.
|
||||
* The result of having multiple event emitters is unspecified.
|
||||
* Currently events are broadcasted to all existing event emitters,
|
||||
* but previous versions delivered events to only one event emitter
|
||||
* and this behavior may change again in the future.
|
||||
* Events emitted before creation of event emitter
|
||||
* may or may not be available to event emitter.
|
||||
*/
|
||||
dc_event_emitter_t* dc_get_event_emitter(dc_context_t* context);
|
||||
|
||||
@@ -394,27 +389,9 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
/**
|
||||
* Configure the context. The configuration is handled by key=value pairs as:
|
||||
*
|
||||
* - `addr` = Email address to use for configuration.
|
||||
* If dc_configure() fails this is not the email address actually in use.
|
||||
* Use `configured_addr` to find out the email address actually in use.
|
||||
* - `configured_addr` = Email address actually in use.
|
||||
* - `configured_addr` = Email address in use.
|
||||
* Unless for testing, do not set this value using dc_set_config().
|
||||
* Instead, set `addr` and call dc_configure().
|
||||
* - `mail_server` = IMAP-server, guessed if left out
|
||||
* - `mail_user` = IMAP-username, guessed if left out
|
||||
* - `mail_pw` = IMAP-password (always needed)
|
||||
* - `mail_port` = IMAP-port, guessed if left out
|
||||
* - `mail_security`= IMAP-socket, one of @ref DC_SOCKET, defaults to #DC_SOCKET_AUTO
|
||||
* - `send_server` = SMTP-server, guessed if left out
|
||||
* - `send_user` = SMTP-user, guessed if left out
|
||||
* - `send_pw` = SMTP-password, guessed if left out
|
||||
* - `send_port` = SMTP-port, guessed if left out
|
||||
* - `send_security`= SMTP-socket, one of @ref DC_SOCKET, defaults to #DC_SOCKET_AUTO
|
||||
* - `server_flags` = IMAP-/SMTP-flags as a combination of @ref DC_LP flags, guessed if left out
|
||||
* - `proxy_enabled` = Proxy enabled. Disabled by default.
|
||||
* - `proxy_url` = Proxy URL. May contain multiple URLs separated by newline, but only the first one is used.
|
||||
* - `imap_certificate_checks` = how to check IMAP certificates, one of the @ref DC_CERTCK flags, defaults to #DC_CERTCK_AUTO (0)
|
||||
* - `smtp_certificate_checks` = deprecated option, should be set to the same value as `imap_certificate_checks` but ignored by the new core
|
||||
* - `displayname` = Own name to use when sending messages. MUAs are allowed to spread this way e.g. using CC, defaults to empty
|
||||
* - `selfstatus` = Own status to display, e.g. in e-mail footers, defaults to empty
|
||||
* - `selfavatar` = File containing avatar. Will immediately be copied to the
|
||||
@@ -430,31 +407,11 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* 1=send a copy of outgoing messages to self (default).
|
||||
* Sending messages to self is needed for a proper multi-account setup,
|
||||
* however, on the other hand, may lead to unwanted notifications in non-delta clients.
|
||||
* - `mvbox_move` = 1=detect chat messages,
|
||||
* move them to the `DeltaChat` folder,
|
||||
* and watch the `DeltaChat` folder for updates (default),
|
||||
* 0=do not move chat-messages
|
||||
* - `only_fetch_mvbox` = 1=Do not fetch messages from folders other than the
|
||||
* `DeltaChat` folder. Messages will still be fetched from the
|
||||
* spam folder.
|
||||
* 0=watch all folders normally (default)
|
||||
* - `show_emails` = DC_SHOW_EMAILS_OFF (0)=
|
||||
* show direct replies to chats only,
|
||||
* DC_SHOW_EMAILS_ACCEPTED_CONTACTS (1)=
|
||||
* also show all mails of confirmed contacts,
|
||||
* DC_SHOW_EMAILS_ALL (2)=
|
||||
* also show mails of unconfirmed contacts (default).
|
||||
* - `delete_device_after` = 0=do not delete messages from device automatically (default),
|
||||
* >=1=seconds, after which messages are deleted automatically from the device.
|
||||
* Messages in the "saved messages" chat (see dc_chat_is_self_talk()) are skipped.
|
||||
* Messages are deleted whether they were seen or not, the UI should clearly point that out.
|
||||
* See also dc_estimate_deletion_cnt().
|
||||
* - `delete_server_after` = 0=do not delete messages from server automatically (default),
|
||||
* 1=delete messages directly after receiving from server, mvbox is skipped.
|
||||
* >1=seconds, after which messages are deleted automatically from the server, mvbox is used as defined.
|
||||
* "Saved messages" are deleted from the server as well as
|
||||
* e-mails matching the `show_emails` settings above, the UI should clearly point that out.
|
||||
* See also dc_estimate_deletion_cnt().
|
||||
* - `media_quality` = DC_MEDIA_QUALITY_BALANCED (0) =
|
||||
* good outgoing images/videos/voice quality at reasonable sizes (default)
|
||||
* DC_MEDIA_QUALITY_WORSE (1)
|
||||
@@ -504,7 +461,10 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* - `gossip_period` = How often to gossip Autocrypt keys in chats with multiple recipients, in
|
||||
* seconds. 2 days by default.
|
||||
* This is not supposed to be changed by UIs and only used for testing.
|
||||
* - `is_chatmail` = 1 if the the server is a chatmail server, 0 otherwise.
|
||||
* - `is_chatmail` = (deprecated) 1 if the the server is a chatmail server, 0 otherwise.
|
||||
* This is deprecated, UIs should not behave differently
|
||||
* for chatmail relays and classical email servers.
|
||||
* Most usages in UIs can be replaced by `force_encryption`.
|
||||
* - `is_muted` = Whether a context is muted by the user.
|
||||
* Muted contexts should not sound, vibrate or show notifications.
|
||||
* In contrast to `dc_set_chat_mute_duration()`,
|
||||
@@ -524,7 +484,28 @@ char* dc_get_blobdir (const dc_context_t* context);
|
||||
* 0 = Everybody (except explicitly blocked contacts),
|
||||
* 1 = Contacts (default, does not include contact requests),
|
||||
* 2 = Nobody (calls never result in a notification).
|
||||
* - `force_encryption` = 1 (default) to force encryption, 0 to allow unencrypted messages.
|
||||
*
|
||||
* Also, there are configs that are only needed
|
||||
* if you want to use the deprecated dc_configure() API, such as:
|
||||
*
|
||||
* - `addr` = Email address to use for configuration.
|
||||
* If dc_configure() fails this is not the email address actually in use.
|
||||
* Use `configured_addr` to find out the email address actually in use.
|
||||
* - `mail_server` = IMAP-server, guessed if left out
|
||||
* - `mail_user` = IMAP-username, guessed if left out
|
||||
* - `mail_pw` = IMAP-password (always needed)
|
||||
* - `mail_port` = IMAP-port, guessed if left out
|
||||
* - `mail_security`= IMAP-socket, one of @ref DC_SOCKET, defaults to #DC_SOCKET_AUTO
|
||||
* - `send_server` = SMTP-server, guessed if left out
|
||||
* - `send_user` = SMTP-user, guessed if left out
|
||||
* - `send_pw` = SMTP-password, guessed if left out
|
||||
* - `send_port` = SMTP-port, guessed if left out
|
||||
* - `send_security`= SMTP-socket, one of @ref DC_SOCKET, defaults to #DC_SOCKET_AUTO
|
||||
* - `proxy_enabled` = Proxy enabled. Disabled by default.
|
||||
* - `proxy_url` = Proxy URL. May contain multiple URLs separated by newline, but only the first one is used.
|
||||
* - `imap_certificate_checks` = how to check IMAP and SMTP certificates, one of the @ref DC_CERTCK flags, defaults to #DC_CERTCK_AUTO (0)
|
||||
|
||||
* If you want to retrieve a value, use dc_get_config().
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
@@ -550,9 +531,6 @@ int dc_set_config (dc_context_t* context, const char*
|
||||
* an error (no warning as it should be shown to the user) is logged but the attachment is sent anyway.
|
||||
* - `sys.config_keys` = get a space-separated list of all config-keys available.
|
||||
* The config-keys are the keys that can be passed to the parameter `key` of this function.
|
||||
* - `quota_exceeding` = 0: quota is unknown or in normal range;
|
||||
* >=80: quota is about to exceed, the value is the concrete percentage,
|
||||
* a device message is added when that happens, however, that value may still be interesting for bots.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object. For querying system values, this can be NULL.
|
||||
@@ -610,36 +588,7 @@ int dc_set_config_from_qr (dc_context_t* context, const char* qr);
|
||||
char* dc_get_info (const dc_context_t* context);
|
||||
|
||||
|
||||
/**
|
||||
* Get URL that can be used to initiate an OAuth2 authorization.
|
||||
*
|
||||
* If an OAuth2 authorization is possible for a given e-mail address,
|
||||
* this function returns the URL that should be opened in a browser.
|
||||
*
|
||||
* If the user authorizes access,
|
||||
* the given redirect_uri is called by the provider.
|
||||
* It's up to the UI to handle this call.
|
||||
*
|
||||
* The provider will attach some parameters to the URL,
|
||||
* most important the parameter `code` that should be set as the `mail_pw`.
|
||||
* With `server_flags` set to #DC_LP_AUTH_OAUTH2,
|
||||
* dc_configure() can be called as usual afterwards.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object.
|
||||
* @param addr E-mail address the user has entered.
|
||||
* In case the user selects a different e-mail address during
|
||||
* authorization, this is corrected in dc_configure()
|
||||
* @param redirect_uri URL that will get `code` that is used as `mail_pw` then.
|
||||
* Not all URLs are allowed here, however, the following should work:
|
||||
* `chat.delta:/PATH`, `http://localhost:PORT/PATH`,
|
||||
* `https://localhost:PORT/PATH`, `urn:ietf:wg:oauth:2.0:oob`
|
||||
* (the latter just displays the code the user can copy+paste then)
|
||||
* @return URL that can be opened in the browser to start OAuth2.
|
||||
* Returned strings must be released using dc_str_unref().
|
||||
* If OAuth2 is not possible for the given e-mail address, NULL is returned.
|
||||
*/
|
||||
char* dc_get_oauth2_url (dc_context_t* context, const char* addr, const char* redirect_uri);
|
||||
|
||||
|
||||
|
||||
#define DC_CONNECTIVITY_NOT_CONNECTED 1000
|
||||
@@ -651,13 +600,10 @@ char* dc_get_oauth2_url (dc_context_t* context, const char*
|
||||
/**
|
||||
* Get the current connectivity, i.e. whether the device is connected to the IMAP server.
|
||||
* One of:
|
||||
* - DC_CONNECTIVITY_NOT_CONNECTED (1000-1999): Show e.g. the string "Not connected" or a red dot
|
||||
* - DC_CONNECTIVITY_CONNECTING (2000-2999): Show e.g. the string "Connecting…" or a yellow dot
|
||||
* - DC_CONNECTIVITY_WORKING (3000-3999): Show e.g. the string "Getting new messages" or a spinning wheel
|
||||
* - DC_CONNECTIVITY_CONNECTED (>=4000): Show e.g. the string "Connected" or a green dot
|
||||
*
|
||||
* We don't use exact values but ranges here so that we can split up
|
||||
* states into multiple states in the future.
|
||||
* - DC_CONNECTIVITY_NOT_CONNECTED (1000): Show e.g. the string "Not connected" or a red dot
|
||||
* - DC_CONNECTIVITY_CONNECTING (2000): Show e.g. the string "Connecting…" or a yellow dot
|
||||
* - DC_CONNECTIVITY_WORKING (3000): Show e.g. the string "Getting new messages" or a spinning wheel
|
||||
* - DC_CONNECTIVITY_CONNECTED (4000): Show e.g. the string "Connected" or a green dot
|
||||
*
|
||||
* Meant as a rough overview that can be shown
|
||||
* e.g. in the title of the main screen.
|
||||
@@ -689,28 +635,16 @@ int dc_get_connectivity (dc_context_t* context);
|
||||
char* dc_get_connectivity_html (dc_context_t* context);
|
||||
|
||||
|
||||
#define DC_PUSH_NOT_CONNECTED 0
|
||||
#define DC_PUSH_HEARTBEAT 1
|
||||
#define DC_PUSH_CONNECTED 2
|
||||
|
||||
/**
|
||||
* Get the current push notification state.
|
||||
* One of:
|
||||
* - DC_PUSH_NOT_CONNECTED
|
||||
* - DC_PUSH_HEARTBEAT
|
||||
* - DC_PUSH_CONNECTED
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object.
|
||||
* @return Push notification state.
|
||||
*/
|
||||
int dc_get_push_state (dc_context_t* context);
|
||||
|
||||
|
||||
// connect
|
||||
|
||||
/**
|
||||
* Configure a context.
|
||||
*
|
||||
* This way of configuring a context is deprecated,
|
||||
* and does not allow to configure multiple transports.
|
||||
* If you can, use the JSON-RPC API (../deltachat-jsonrpc/src/api.rs)
|
||||
* `add_or_update_transport()`/`addOrUpdateTransport()` instead.
|
||||
*
|
||||
* During configuration IO must not be started,
|
||||
* if needed stop IO using dc_accounts_stop_io() or dc_stop_io() first.
|
||||
* If the context is already configured,
|
||||
@@ -727,7 +661,7 @@ int dc_get_push_state (dc_context_t* context);
|
||||
* to get the full configuration from well-known URLs.
|
||||
*
|
||||
* - If _more_ options as `mail_server`, `mail_port`, `send_server`,
|
||||
* `send_port`, `send_user` or `server_flags` are specified,
|
||||
* `send_port` or `send_user` are specified,
|
||||
* **autoconfigure/autodiscover is skipped**.
|
||||
*
|
||||
* While dc_configure() returns immediately,
|
||||
@@ -935,14 +869,14 @@ uint32_t dc_create_chat_by_contact_id (dc_context_t* context, uint32_t co
|
||||
|
||||
|
||||
/**
|
||||
* Check, if there is a normal chat with a given contact.
|
||||
* Check, if there is a single chat with a given contact.
|
||||
* To get the chat messages, use dc_get_chat_msgs().
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object as returned from dc_context_new().
|
||||
* @param contact_id The contact ID to check.
|
||||
* @return If there is a normal chat with the given contact_id, this chat_id is
|
||||
* returned. If there is no normal chat with the contact_id, the function
|
||||
* @return If there is a single chat with the given contact_id, this chat_id is
|
||||
* returned. If there is no single chat with the contact_id, the function
|
||||
* returns 0.
|
||||
*/
|
||||
uint32_t dc_get_chat_id_by_contact_id (dc_context_t* context, uint32_t contact_id);
|
||||
@@ -1239,7 +1173,7 @@ uint32_t dc_init_webxdc_integration (dc_context_t* context, uint32_t c
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object.
|
||||
* @param chat_id The chat to place a call for.
|
||||
* This needs to be a one-to-one chat.
|
||||
* This needs to be a single chat.
|
||||
* @param place_call_info any data that other devices receive
|
||||
* in #DC_EVENT_INCOMING_CALL.
|
||||
* @param has_video Whether the call has video initially.
|
||||
@@ -1400,7 +1334,6 @@ dc_msg_t* dc_get_draft (dc_context_t* context, uint32_t ch
|
||||
|
||||
|
||||
#define DC_GCM_ADDDAYMARKER 0x01
|
||||
#define DC_GCM_INFO_ONLY 0x02
|
||||
|
||||
|
||||
/**
|
||||
@@ -1421,7 +1354,6 @@ dc_msg_t* dc_get_draft (dc_context_t* context, uint32_t ch
|
||||
* @param flags If set to DC_GCM_ADDDAYMARKER, the marker DC_MSG_ID_DAYMARKER will
|
||||
* be added before each day (regarding the local timezone). Set this to 0 if you do not want this behaviour.
|
||||
* The day marker timestamp is the midnight one for the corresponding (following) day in the local timezone.
|
||||
* If set to DC_GCM_INFO_ONLY, only system messages will be returned, can be combined with DC_GCM_ADDDAYMARKER.
|
||||
* @param marker1before Deprecated, set this to 0.
|
||||
* @return Array of message IDs, must be dc_array_unref()'d when no longer used.
|
||||
*/
|
||||
@@ -1476,17 +1408,16 @@ dc_chatlist_t* dc_get_similar_chatlist (dc_context_t* context, uint32_t ch
|
||||
|
||||
/**
|
||||
* Estimate the number of messages that will be deleted
|
||||
* by the dc_set_config()-options `delete_device_after` or `delete_server_after`.
|
||||
* by the dc_set_config()-option `delete_device_after`.
|
||||
* This is typically used to show the estimated impact to the user
|
||||
* before actually enabling deletion of old messages.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object as returned from dc_context_new().
|
||||
* @param from_server 1=Estimate deletion count for server, 0=Estimate deletion count for device
|
||||
* @param from_server Deprecated, pass 0 here
|
||||
* @param seconds Count messages older than the given number of seconds.
|
||||
* @return Number of messages that are older than the given number of seconds.
|
||||
* This includes e-mails downloaded due to the `show_emails` option.
|
||||
* Messages in the "saved messages" folder are not counted as they will not be deleted automatically.
|
||||
* Messages in the "Saved Messages" chat are not counted as they will not be deleted automatically.
|
||||
*/
|
||||
int dc_estimate_deletion_cnt (dc_context_t* context, int from_server, int64_t seconds);
|
||||
|
||||
@@ -1648,7 +1579,7 @@ void dc_set_chat_visibility (dc_context_t* context, uint32_t ch
|
||||
* - The chat or the contact is **not blocked**, so new messages from the user/the group may appear
|
||||
* and the user may create the chat again.
|
||||
* - **Groups are not left** - this would
|
||||
* be unexpected as (1) deleting a normal chat also does not prevent new mails
|
||||
* be unexpected as (1) deleting a single chat also does not prevent new mails
|
||||
* from arriving, (2) leaving a group requires sending a message to
|
||||
* all group members - especially for groups not used for a longer time, this is
|
||||
* really unexpected when deletion results in contacting all members again,
|
||||
@@ -1666,7 +1597,7 @@ void dc_delete_chat (dc_context_t* context, uint32_t ch
|
||||
/**
|
||||
* Block a chat.
|
||||
*
|
||||
* Blocking 1:1 chats blocks the corresponding contact. Blocking
|
||||
* Blocking single chats blocks the corresponding contact. Blocking
|
||||
* mailing lists creates a pseudo-contact in the list of blocked
|
||||
* contacts, so blocked mailing lists can be discovered and unblocked
|
||||
* the same way as the contacts. Blocking group chats deletes the
|
||||
@@ -1695,7 +1626,7 @@ void dc_accept_chat (dc_context_t* context, uint32_t ch
|
||||
/**
|
||||
* Get the contact IDs belonging to a chat.
|
||||
*
|
||||
* - for normal chats, the function always returns exactly one contact,
|
||||
* - for single chats, the function always returns exactly one contact,
|
||||
* DC_CONTACT_ID_SELF is returned only for SELF-chats.
|
||||
*
|
||||
* - for group chats all members are returned, DC_CONTACT_ID_SELF is returned
|
||||
@@ -2133,7 +2064,7 @@ int dc_resend_msgs (dc_context_t* context, const uint3
|
||||
* The concrete action depends on the type of the chat and on the users settings
|
||||
* (dc_msgs_presented() may be a better name therefore, but well. :)
|
||||
*
|
||||
* - For normal chats, the IMAP state is updated, MDN is sent
|
||||
* - For single chats, the IMAP state is updated, MDN is sent
|
||||
* (if dc_set_config()-options `mdns_enabled` is set)
|
||||
* and the internal state is changed to @ref DC_STATE_IN_SEEN to reflect these actions.
|
||||
*
|
||||
@@ -2695,7 +2626,6 @@ char* dc_get_securejoin_qr_svg (dc_context_t* context, uint32_
|
||||
* to dc_check_qr().
|
||||
* @return The chat ID of the joined chat, the UI may redirect to the this chat.
|
||||
* On errors, 0 is returned, however, most errors will happen during handshake later on.
|
||||
* A returned chat ID does not guarantee that the chat is protected or the belonging contact is verified.
|
||||
*/
|
||||
uint32_t dc_join_securejoin (dc_context_t* context, const char* qr);
|
||||
|
||||
@@ -2831,19 +2761,6 @@ int dc_set_location (dc_context_t* context, double latit
|
||||
dc_array_t* dc_get_locations (dc_context_t* context, uint32_t chat_id, uint32_t contact_id, int64_t timestamp_begin, int64_t timestamp_end);
|
||||
|
||||
|
||||
/**
|
||||
* Delete all locations on the current device.
|
||||
* Locations already sent cannot be deleted.
|
||||
*
|
||||
* Typically results in the event #DC_EVENT_LOCATION_CHANGED
|
||||
* with contact_id set to 0.
|
||||
*
|
||||
* @memberof dc_context_t
|
||||
* @param context The context object.
|
||||
*/
|
||||
void dc_delete_all_locations (dc_context_t* context);
|
||||
|
||||
|
||||
// misc
|
||||
|
||||
/**
|
||||
@@ -3323,18 +3240,14 @@ void dc_accounts_set_push_device_token (dc_accounts_t* accounts, const
|
||||
* This is similar to dc_get_event_emitter(), which, however,
|
||||
* must not be called for accounts handled by the account manager.
|
||||
*
|
||||
* Events are broadcasted to all existing event emitters.
|
||||
* Events emitted before creation of event emitter
|
||||
* are not available to event emitter.
|
||||
*
|
||||
* @memberof dc_accounts_t
|
||||
* @param accounts The account manager as created by dc_accounts_new().
|
||||
* @return Returns the event emitter, NULL on errors.
|
||||
* Must be freed using dc_event_emitter_unref() after usage.
|
||||
*
|
||||
* Note: Use only one event emitter per account manager.
|
||||
* The result of having multiple event emitters is unspecified.
|
||||
* Currently events are broadcasted to all existing event emitters,
|
||||
* but previous versions delivered events to only one event emitter
|
||||
* and this behavior may change again in the future.
|
||||
* Events emitted before creation of event emitter
|
||||
* are not available to event emitter.
|
||||
*/
|
||||
dc_event_emitter_t* dc_accounts_get_event_emitter (dc_accounts_t* accounts);
|
||||
|
||||
@@ -3641,19 +3554,11 @@ dc_lot_t* dc_chatlist_get_summary (const dc_chatlist_t* chatlist, siz
|
||||
dc_lot_t* dc_chatlist_get_summary2 (dc_context_t* context, uint32_t chat_id, uint32_t msg_id);
|
||||
|
||||
|
||||
/**
|
||||
* Helper function to get the associated context object.
|
||||
*
|
||||
* @memberof dc_chatlist_t
|
||||
* @param chatlist The chatlist object to empty.
|
||||
* @return The context object associated with the chatlist. NULL if none or on errors.
|
||||
*/
|
||||
dc_context_t* dc_chatlist_get_context (dc_chatlist_t* chatlist);
|
||||
|
||||
|
||||
/**
|
||||
* Get info summary for a chat, in JSON format.
|
||||
*
|
||||
* @deprecated 2026-08-13, use dedicated dc_chat_get_*() getters or jsonrpc
|
||||
*
|
||||
* The returned JSON string has the following key/values:
|
||||
*
|
||||
* id: chat id
|
||||
@@ -3734,7 +3639,7 @@ char* dc_chat_get_mailinglist_addr (const dc_chat_t* chat);
|
||||
|
||||
|
||||
/**
|
||||
* Get name of a chat. For one-to-one chats, this is the name of the contact.
|
||||
* Get name of a chat. For single chats, this is the name of the contact.
|
||||
* For group chats, this is the name given e.g. to dc_create_group_chat() or
|
||||
* received by a group-creation message.
|
||||
*
|
||||
@@ -3751,7 +3656,7 @@ char* dc_chat_get_name (const dc_chat_t* chat);
|
||||
* Get the chat's profile image.
|
||||
* For groups, this is the image set by any group member
|
||||
* using dc_set_chat_profile_image().
|
||||
* For normal chats, this is the image set by each remote user on their own
|
||||
* For single chats, this is the image set by each remote user on their own
|
||||
* using dc_set_config(context, "selfavatar", image).
|
||||
*
|
||||
* @memberof dc_chat_t
|
||||
@@ -3765,7 +3670,7 @@ char* dc_chat_get_profile_image (const dc_chat_t* chat);
|
||||
|
||||
/**
|
||||
* Get a color for the chat.
|
||||
* For 1:1 chats, the color is calculated from the contact's e-mail address.
|
||||
* For single chats, the color is calculated from the contact's e-mail address.
|
||||
* Otherwise, the chat name is used.
|
||||
* The color can be used for an fallback avatar with white initials
|
||||
* as well as for headlines in bubbles of group chats.
|
||||
@@ -3832,7 +3737,7 @@ int dc_chat_is_unpromoted (const dc_chat_t* chat);
|
||||
|
||||
|
||||
/**
|
||||
* Check if a chat is a self talk. Self talks are normal chats with
|
||||
* Check if a chat is a self talk. Self talks are single chats with
|
||||
* the only contact DC_CONTACT_ID_SELF.
|
||||
*
|
||||
* @memberof dc_chat_t
|
||||
@@ -3873,23 +3778,12 @@ int dc_chat_is_device_talk (const dc_chat_t* chat);
|
||||
int dc_chat_can_send (const dc_chat_t* chat);
|
||||
|
||||
|
||||
/**
|
||||
* Deprecated, always returns 0.
|
||||
*
|
||||
* @memberof dc_chat_t
|
||||
* @param chat The chat object.
|
||||
* @return Always 0.
|
||||
* @deprecated 2025-09-09
|
||||
*/
|
||||
int dc_chat_is_protected (const dc_chat_t* chat);
|
||||
|
||||
|
||||
/**
|
||||
* Check if the chat is encrypted.
|
||||
*
|
||||
* 1:1 chats with key-contacts and group chats with key-contacts
|
||||
* Single chats with key-contacts and group chats with key-contacts
|
||||
* are encrypted.
|
||||
* 1:1 chats with emails contacts and ad-hoc groups
|
||||
* Single chats with emails contacts and ad-hoc groups
|
||||
* created for email threads are not encrypted.
|
||||
*
|
||||
* @memberof dc_chat_t
|
||||
@@ -4036,8 +3930,6 @@ int dc_msg_get_viewtype (const dc_msg_t* msg);
|
||||
* Marked as read on IMAP and MDN may be sent. Use dc_markseen_msgs() to mark messages as being seen.
|
||||
*
|
||||
* Outgoing message states:
|
||||
* - @ref DC_STATE_OUT_PREPARING - For files which need time to be prepared before they can be sent,
|
||||
* the message enters this state before @ref DC_STATE_OUT_PENDING. Deprecated.
|
||||
* - @ref DC_STATE_OUT_DRAFT - Message saved as draft using dc_set_draft()
|
||||
* - @ref DC_STATE_OUT_PENDING - The user has pressed the "send" button but the
|
||||
* message is not yet sent and is pending in some way. Maybe we're offline (no checkmark).
|
||||
@@ -4250,6 +4142,8 @@ char* dc_msg_get_webxdc_blob (const dc_msg_t* msg, const char*
|
||||
* true if the Webxdc should get internet access;
|
||||
* this is the case i.e. for experimental maps integration.
|
||||
* - self_addr: address to be used for `window.webxdc.selfAddr` in JS land.
|
||||
* - is_app_sender: Define if the local user is the one who initially shared the webxdc application in the chat.
|
||||
* - is_broadcast: Define if the app runs in a broadcasting context.
|
||||
* - send_update_interval: Milliseconds to wait before calling `sendUpdate()` again since the last call.
|
||||
* Should be exposed to `webxdc.sendUpdateInterval` in JS land.
|
||||
* - send_update_max_size: Maximum number of bytes accepted for a serialized update object.
|
||||
@@ -4423,16 +4317,16 @@ char* dc_msg_get_summarytext (const dc_msg_t* msg, int approx_c
|
||||
* display name, or NULL.
|
||||
*
|
||||
* If this returns non-NULL, put a `~` before the override-sender-name and show the
|
||||
* override-sender-name and the sender's avatar even in 1:1 chats.
|
||||
* override-sender-name and the sender's avatar even in single chats.
|
||||
*
|
||||
* In mailing lists, sender display name and sender address do not always belong together.
|
||||
* In this case, this function gives you the name that should actually be shown over the message.
|
||||
*
|
||||
* Also, sometimes, we need to indicate a different sender in 1:1 chats:
|
||||
* Also, sometimes, we need to indicate a different sender in single chats:
|
||||
* Suppose that our user writes an e-mail to support@delta.chat, which forwards to
|
||||
* Bob <bob@delta.chat>, and Bob replies.
|
||||
*
|
||||
* Then, Bob's reply is shown in our 1:1 chat with support@delta.chat and the override-sender-name is
|
||||
* Then, Bob's reply is shown in our single chat with support@delta.chat and the override-sender-name is
|
||||
* set to `Bob`. The UI should show the sender name as `~Bob` and show the avatar, just
|
||||
* as in group messages. If the user then taps on the avatar, they can see that this message
|
||||
* comes from bob@delta.chat.
|
||||
@@ -4566,6 +4460,7 @@ int dc_msg_is_info (const dc_msg_t* msg);
|
||||
* - DC_INFO_WEBXDC_INFO_MESSAGE (32) - Info-message created by webxdc app sending `update.info`
|
||||
* - DC_INFO_CHAT_E2EE (50) - Info-message for "Chat is end-to-end-encrypted"
|
||||
* - DC_INFO_GROUP_DESCRIPTION_CHANGED (70) - Info-message "Description changed", UI should open the profile with the description
|
||||
* - DC_INFO_MESSAGE_PINNED (71) - Message pinned, UI should scroll to the pinned message returned by dc_msg_get_parent()
|
||||
*
|
||||
* For the messages that refer to a CONTACT,
|
||||
* dc_msg_get_info_contact_id() returns the contact ID.
|
||||
@@ -4625,6 +4520,7 @@ uint32_t dc_msg_get_info_contact_id (const dc_msg_t* msg);
|
||||
#define DC_INFO_WEBXDC_INFO_MESSAGE 32
|
||||
#define DC_INFO_CHAT_E2EE 50
|
||||
#define DC_INFO_GROUP_DESCRIPTION_CHANGED 70
|
||||
#define DC_INFO_MESSAGE_PINNED 71
|
||||
|
||||
|
||||
/**
|
||||
@@ -4942,6 +4838,8 @@ dc_msg_t* dc_msg_get_quoted_msg (const dc_msg_t* msg);
|
||||
* Used for Webxdc-info-messages
|
||||
* to jump to the corresponding instance that created the info message.
|
||||
*
|
||||
* For Pinned-info-messages, this refers to the pinned message.
|
||||
*
|
||||
* For quotes, please use the more specialized
|
||||
* dc_msg_get_quoted_text() and dc_msg_get_quoted_msg().
|
||||
*
|
||||
@@ -4983,14 +4881,17 @@ uint32_t dc_msg_get_saved_msg_id (const dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* Force the message to be sent in plain text.
|
||||
* Check if the message is pinned.
|
||||
*
|
||||
* This API is for bots, there is no need to expose it in the UI.
|
||||
* Pinned messages should be marked by a pin needle in the UI.
|
||||
* To pin messages or get all pinned messages, use jsonrpc's "setPinnedMessageState" and "getPinnedMessages".
|
||||
*
|
||||
* @memberof dc_msg_t
|
||||
* @param msg The message object.
|
||||
* @return 1=message is pinned, 0=message not pinned.
|
||||
*/
|
||||
void dc_msg_force_plaintext (dc_msg_t* msg);
|
||||
int dc_msg_is_pinned (const dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_contact_t
|
||||
@@ -5275,98 +5176,6 @@ int dc_contact_is_key_contact (dc_contact_t* contact);
|
||||
uint32_t dc_contact_get_verifier_id (dc_contact_t* contact);
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_provider_t
|
||||
*
|
||||
* Opaque object containing information about one single e-mail provider.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Create a provider struct for the given e-mail address by local lookup.
|
||||
*
|
||||
* Lookup is done from the local database by extracting the domain from the e-mail address.
|
||||
* Therefore the provider for custom domains cannot be identified.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param context The context object.
|
||||
* @param email The user's e-mail address to extract the provider info form.
|
||||
* @return A dc_provider_t struct which can be used with the dc_provider_get_*
|
||||
* accessor functions. If no provider info is found, NULL will be
|
||||
* returned.
|
||||
*/
|
||||
dc_provider_t* dc_provider_new_from_email (const dc_context_t* context, const char* email);
|
||||
|
||||
|
||||
/**
|
||||
* Create a provider struct for the given e-mail address by local lookup.
|
||||
*
|
||||
* DNS lookup is not used anymore and this function is deprecated.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param context The context object.
|
||||
* @param email The user's e-mail address to extract the provider info form.
|
||||
* @return A dc_provider_t struct which can be used with the dc_provider_get_*
|
||||
* accessor functions. If no provider info is found, NULL will be
|
||||
* returned.
|
||||
* @deprecated 2025-10-17 use dc_provider_new_from_email() instead.
|
||||
*/
|
||||
dc_provider_t* dc_provider_new_from_email_with_dns (const dc_context_t* context, const char* email);
|
||||
|
||||
|
||||
/**
|
||||
* URL of the overview page.
|
||||
*
|
||||
* This URL allows linking to the providers page on providers.delta.chat.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param provider The dc_provider_t struct.
|
||||
* @return A string with a fully-qualified URL,
|
||||
* if there is no such URL, an empty string is returned, NULL is never returned.
|
||||
* The returned value must be released using dc_str_unref().
|
||||
*/
|
||||
char* dc_provider_get_overview_page (const dc_provider_t* provider);
|
||||
|
||||
|
||||
/**
|
||||
* Get hints to be shown to the user on the login screen.
|
||||
* Depending on the @ref DC_PROVIDER_STATUS returned by dc_provider_get_status(),
|
||||
* the UI may want to highlight the hint.
|
||||
*
|
||||
* Moreover, the UI should display a "More information" link
|
||||
* that forwards to the URL returned by dc_provider_get_overview_page().
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param provider The dc_provider_t struct.
|
||||
* @return A string with the hint to show to the user, may contain multiple lines,
|
||||
* if there is no such hint, an empty string is returned, NULL is never returned.
|
||||
* The returned value must be released using dc_str_unref().
|
||||
*/
|
||||
char* dc_provider_get_before_login_hint (const dc_provider_t* provider);
|
||||
|
||||
|
||||
/**
|
||||
* Whether DC works with this provider.
|
||||
*
|
||||
* Can be one of #DC_PROVIDER_STATUS_OK,
|
||||
* #DC_PROVIDER_STATUS_PREPARATION or #DC_PROVIDER_STATUS_BROKEN.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param provider The dc_provider_t struct.
|
||||
* @return The status as a constant number.
|
||||
*/
|
||||
int dc_provider_get_status (const dc_provider_t* provider);
|
||||
|
||||
|
||||
/**
|
||||
* Free the provider info struct.
|
||||
*
|
||||
* @memberof dc_provider_t
|
||||
* @param provider The dc_provider_t struct.
|
||||
*/
|
||||
void dc_provider_unref (dc_provider_t* provider);
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_lot_t
|
||||
*
|
||||
@@ -5631,13 +5440,6 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
*/
|
||||
#define DC_STATE_IN_SEEN 16
|
||||
|
||||
/**
|
||||
* Outgoing message being prepared. See dc_msg_get_state() for details.
|
||||
*
|
||||
* @deprecated 2024-12-07
|
||||
*/
|
||||
#define DC_STATE_OUT_PREPARING 18
|
||||
|
||||
/**
|
||||
* Outgoing message drafted. See dc_msg_get_state() for details.
|
||||
*/
|
||||
@@ -5687,7 +5489,7 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
#define DC_CHAT_TYPE_UNDEFINED 0
|
||||
|
||||
/**
|
||||
* A one-to-one chat with a single contact.
|
||||
* A single chat with a single contact.
|
||||
*
|
||||
* dc_get_chat_contacts() contains one record for the user.
|
||||
* DC_CONTACT_ID_SELF is added _only_ for a self talk.
|
||||
@@ -5788,48 +5590,13 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
|
||||
* @}
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @defgroup DC_LP DC_LP
|
||||
*
|
||||
* Flags for configuring IMAP and SMTP servers.
|
||||
* These flags are optional
|
||||
* and may be set together with the username, password etc.
|
||||
* via dc_set_config() using the key "server_flags".
|
||||
*
|
||||
* @addtogroup DC_LP
|
||||
* @{
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Force OAuth2 authorization. This flag does not skip automatic configuration.
|
||||
* Before calling dc_configure() with DC_LP_AUTH_OAUTH2 set,
|
||||
* the user has to confirm access at the URL returned by dc_get_oauth2_url().
|
||||
*/
|
||||
#define DC_LP_AUTH_OAUTH2 0x2
|
||||
|
||||
|
||||
/**
|
||||
* Force NORMAL authorization, this is the default.
|
||||
* If this flag is set, automatic configuration is skipped.
|
||||
*/
|
||||
#define DC_LP_AUTH_NORMAL 0x4
|
||||
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
#define DC_LP_AUTH_FLAGS (DC_LP_AUTH_OAUTH2|DC_LP_AUTH_NORMAL) // if none of these flags are set, the default is chosen
|
||||
|
||||
/**
|
||||
* @defgroup DC_CERTCK DC_CERTCK
|
||||
*
|
||||
* These constants configure TLS certificate checks for IMAP and SMTP connections.
|
||||
*
|
||||
* These constants are set via dc_set_config()
|
||||
* using keys "imap_certificate_checks" and "smtp_certificate_checks".
|
||||
* using key "imap_certificate_checks".
|
||||
*
|
||||
* @addtogroup DC_CERTCK
|
||||
* @{
|
||||
@@ -5910,6 +5677,7 @@ void dc_jsonrpc_unref(dc_jsonrpc_instance_t* jsonrpc_instance);
|
||||
* - getAccountFileSize()
|
||||
* - importVcard(), parseVcard(), makeVcard()
|
||||
* - sendWebxdcRealtimeData, sendWebxdcRealtimeAdvertisement(), leaveWebxdcRealtime()
|
||||
* - setPinnedMessageState(), getPinnedMessages()
|
||||
*
|
||||
* @memberof dc_jsonrpc_instance_t
|
||||
* @param jsonrpc_instance jsonrpc instance as returned from dc_jsonrpc_init().
|
||||
@@ -5979,21 +5747,14 @@ void dc_event_channel_unref(dc_event_channel_t* event_channel);
|
||||
* To get these events, you have to create an event emitter using this function
|
||||
* and call dc_get_next_event() on the emitter.
|
||||
*
|
||||
* This is similar to dc_get_event_emitter(), which, however,
|
||||
* must not be called for accounts handled by the account manager.
|
||||
* Events are broadcasted to all existing event emitters.
|
||||
* Events emitted before creation of event emitter
|
||||
* are not available to event emitter.
|
||||
*
|
||||
* @memberof dc_event_channel_t
|
||||
* @param The event channel.
|
||||
* @return Returns the event emitter, NULL on errors.
|
||||
* Must be freed using dc_event_emitter_unref() after usage.
|
||||
*
|
||||
* Note: Use only one event emitter per account manager / event channel.
|
||||
* The result of having multiple event emitters is unspecified.
|
||||
* Currently events are broadcasted to all existing event emitters,
|
||||
* but previous versions delivered events to only one event emitter
|
||||
* and this behavior may change again in the future.
|
||||
* Events emitted before creation of event emitter
|
||||
* are not available to event emitter.
|
||||
*/
|
||||
dc_event_emitter_t* dc_event_channel_get_event_emitter(dc_event_channel_t* event_channel);
|
||||
|
||||
@@ -6403,6 +6164,15 @@ void dc_event_unref(dc_event_t* event);
|
||||
*/
|
||||
#define DC_EVENT_MSG_DELETED 2016
|
||||
|
||||
/**
|
||||
* Like @ref DC_EVENT_MSG_READ, but also fires on subsequent MDNs,
|
||||
* if there are multiple receivers, i.e. in groups and channels.
|
||||
*
|
||||
* @param data1 (int) chat_id
|
||||
* @param data2 (int) msg_id
|
||||
*/
|
||||
#define DC_EVENT_MSG_READ_COUNT_CHANGED 2018
|
||||
|
||||
|
||||
/**
|
||||
* Chat changed. The name or the image of a chat group was changed or members were added or removed.
|
||||
@@ -6449,8 +6219,7 @@ void dc_event_unref(dc_event_t* event);
|
||||
* Location of one or more contact has changed.
|
||||
*
|
||||
* @param data1 (int) contact_id of the contact for which the location has changed.
|
||||
* If the locations of several contacts have been changed,
|
||||
* e.g. after calling dc_delete_all_locations(), this parameter is set to 0.
|
||||
* If the locations of several contacts have been changed, this parameter is set to 0.
|
||||
* @param data2 0
|
||||
*/
|
||||
#define DC_EVENT_LOCATION_CHANGED 2035
|
||||
@@ -6721,14 +6490,6 @@ void dc_event_unref(dc_event_t* event);
|
||||
#define DC_EVENT_DATA2_IS_STRING(e) ((e)==DC_EVENT_CONFIGURE_PROGRESS || (e)==DC_EVENT_IMEX_FILE_WRITTEN || ((e)>=100 && (e)<=499))
|
||||
|
||||
|
||||
/*
|
||||
* Values for dc_get|set_config("show_emails")
|
||||
*/
|
||||
#define DC_SHOW_EMAILS_OFF 0
|
||||
#define DC_SHOW_EMAILS_ACCEPTED_CONTACTS 1
|
||||
#define DC_SHOW_EMAILS_ALL 2
|
||||
|
||||
|
||||
/*
|
||||
* Values for dc_get|set_config("media_quality")
|
||||
*/
|
||||
@@ -6736,61 +6497,6 @@ void dc_event_unref(dc_event_t* event);
|
||||
#define DC_MEDIA_QUALITY_WORSE 1
|
||||
|
||||
|
||||
/**
|
||||
* @defgroup DC_PROVIDER_STATUS DC_PROVIDER_STATUS
|
||||
*
|
||||
* These constants are used as return values for dc_provider_get_status().
|
||||
*
|
||||
* @addtogroup DC_PROVIDER_STATUS
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provider works out-of-the-box.
|
||||
* This provider status is returned for provider where the login
|
||||
* works by just entering the name or the e-mail address.
|
||||
*
|
||||
* - There is no need for the user to do any special things
|
||||
* (enable IMAP or so) in the provider's web interface or at other places.
|
||||
* - There is no need for the user to enter advanced settings;
|
||||
* server, port etc. are known by the core.
|
||||
*
|
||||
* The status is returned by dc_provider_get_status().
|
||||
*/
|
||||
#define DC_PROVIDER_STATUS_OK 1
|
||||
|
||||
/**
|
||||
* Provider works, but there are preparations needed.
|
||||
*
|
||||
* - The user has to do some special things as "Enable IMAP in the web interface",
|
||||
* what exactly, is described in the string returned by dc_provider_get_before_login_hints()
|
||||
* and, typically more detailed, in the page linked by dc_provider_get_overview_page().
|
||||
* - There is no need for the user to enter advanced settings;
|
||||
* server, port etc. should be known by the core.
|
||||
*
|
||||
* The status is returned by dc_provider_get_status().
|
||||
*/
|
||||
#define DC_PROVIDER_STATUS_PREPARATION 2
|
||||
|
||||
/**
|
||||
* Provider is not working.
|
||||
* This provider status is returned for providers
|
||||
* that are known to not work with Delta Chat.
|
||||
* The UI should block logging in with this provider.
|
||||
*
|
||||
* More information about that is typically provided
|
||||
* in the string returned by dc_provider_get_before_login_hints()
|
||||
* and in the page linked by dc_provider_get_overview_page().
|
||||
*
|
||||
* The status is returned by dc_provider_get_status().
|
||||
*/
|
||||
#define DC_PROVIDER_STATUS_BROKEN 3
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @defgroup DC_CHAT_VISIBILITY DC_CHAT_VISIBILITY
|
||||
*
|
||||
@@ -7014,7 +6720,7 @@ void dc_event_unref(dc_event_t* event);
|
||||
|
||||
/// "Message from %1$s"
|
||||
///
|
||||
/// Used in subjects of outgoing messages in one-to-one chats.
|
||||
/// Used in subjects of outgoing messages in single chats.
|
||||
/// - %1$s will be replaced by the name of the sender,
|
||||
/// this is the dc_set_config()-option `displayname` or `addr`
|
||||
#define DC_STR_SUBJECT_FOR_NEW_CONTACT 73
|
||||
@@ -7063,11 +6769,7 @@ void dc_event_unref(dc_event_t* event);
|
||||
/// Used in message summary text for notifications and chatlist.
|
||||
#define DC_STR_FORWARDED 97
|
||||
|
||||
/// "Quota exceeding, already %1$s%% used."
|
||||
///
|
||||
/// Used as device message text.
|
||||
///
|
||||
/// `%1$s` will be replaced by the percentage used
|
||||
/// @deprecated 2026-04-25
|
||||
#define DC_STR_QUOTA_EXCEEDING_MSG_BODY 98
|
||||
|
||||
/// "Multi Device Synchronization"
|
||||
@@ -7127,11 +6829,6 @@ void dc_event_unref(dc_event_t* event);
|
||||
/// `%1$s` will be replaced by a possibly more detailed, typically english, error description.
|
||||
#define DC_STR_ERROR 112
|
||||
|
||||
/// "Not supported by your provider."
|
||||
///
|
||||
/// Used in the connectivity view.
|
||||
#define DC_STR_NOT_SUPPORTED_BY_PROVIDER 113
|
||||
|
||||
/// "Messages"
|
||||
///
|
||||
/// Used as a subtitle in quota context; can be plural always.
|
||||
@@ -7463,6 +7160,30 @@ void dc_event_unref(dc_event_t* event);
|
||||
/// `%1$s` will be replaced by name of the removed contact.
|
||||
#define DC_STR_REMOVE_MEMBER 178
|
||||
|
||||
/// "You were removed by %1$s."
|
||||
///
|
||||
/// `%1$s` will be replaced by name of the contact who did the action.
|
||||
///
|
||||
/// Used in status messages.
|
||||
#define DC_STR_REMOVE_YOU_BY 179
|
||||
|
||||
/// "You were added by %1$s."
|
||||
///
|
||||
/// `%1$s` will be replaced by name of the contact who did the action.
|
||||
///
|
||||
/// Used in status messages.
|
||||
#define DC_STR_ADD_YOU_BY 180
|
||||
|
||||
/// "You were removed."
|
||||
///
|
||||
/// Used in status messages.
|
||||
#define DC_STR_REMOVE_YOU 181
|
||||
|
||||
/// "You were added."
|
||||
///
|
||||
/// Used in status messages.
|
||||
#define DC_STR_ADD_YOU 182
|
||||
|
||||
/// "Establishing connection, please wait…"
|
||||
///
|
||||
/// Used as info message.
|
||||
@@ -7558,6 +7279,19 @@ void dc_event_unref(dc_event_t* event);
|
||||
/// Used when creating text for the "Encryption Info" dialogs.
|
||||
#define DC_STR_MESSAGES_ARE_E2EE 242
|
||||
|
||||
/// "You pinned a message."
|
||||
#define DC_STR_MESSAGE_PINNED_BY_YOU 243
|
||||
|
||||
/// "Message pinned by %1$s."
|
||||
#define DC_STR_MESSAGE_PINNED_BY_OTHER 244
|
||||
|
||||
/// "Phasing out"
|
||||
///
|
||||
/// Used in connectivity view to flag unpublished relays.
|
||||
/// This should match the wording used for relay deletion confirmation,
|
||||
/// saying "Before deletion, it will be gradually phased out so your contacts can switch over smoothly"
|
||||
#define DC_STR_PHASING_OUT 245
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -230,7 +230,6 @@ pub enum LotState {
|
||||
MsgInFresh = 10,
|
||||
MsgInNoticed = 13,
|
||||
MsgInSeen = 16,
|
||||
MsgOutPreparing = 18,
|
||||
MsgOutDraft = 19,
|
||||
MsgOutPending = 20,
|
||||
MsgOutFailed = 24,
|
||||
@@ -246,7 +245,6 @@ impl From<MessageState> for LotState {
|
||||
InFresh => LotState::MsgInFresh,
|
||||
InNoticed => LotState::MsgInNoticed,
|
||||
InSeen => LotState::MsgInSeen,
|
||||
OutPreparing => LotState::MsgOutPreparing,
|
||||
OutDraft => LotState::MsgOutDraft,
|
||||
OutPending => LotState::MsgOutPending,
|
||||
OutFailed => LotState::MsgOutFailed,
|
||||
|
||||
@@ -17,13 +17,15 @@ use std::ptr;
|
||||
/// }
|
||||
/// ```
|
||||
unsafe fn dc_strdup(s: *const libc::c_char) -> *mut libc::c_char {
|
||||
let ret: *mut libc::c_char = if !s.is_null() {
|
||||
libc::strdup(s)
|
||||
} else {
|
||||
libc::calloc(1, 1) as *mut libc::c_char
|
||||
};
|
||||
assert!(!ret.is_null());
|
||||
ret
|
||||
unsafe {
|
||||
let ret: *mut libc::c_char = if !s.is_null() {
|
||||
libc::strdup(s)
|
||||
} else {
|
||||
libc::calloc(1, 1) as *mut libc::c_char
|
||||
};
|
||||
assert!(!ret.is_null());
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
/// Error type for the [OsStrExt] trait
|
||||
@@ -164,34 +166,40 @@ pub(crate) trait Strdup {
|
||||
/// This function will panic when the original string contains an
|
||||
/// interior null byte as this can not be represented in raw C
|
||||
/// strings.
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char;
|
||||
fn strdup(&self) -> *mut libc::c_char;
|
||||
}
|
||||
|
||||
impl Strdup for str {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
let tmp = CString::new_lossy(self);
|
||||
dc_strdup(tmp.as_ptr())
|
||||
fn strdup(&self) -> *mut libc::c_char {
|
||||
unsafe {
|
||||
let tmp = CString::new_lossy(self);
|
||||
dc_strdup(tmp.as_ptr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Strdup for String {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
fn strdup(&self) -> *mut libc::c_char {
|
||||
let s: &str = self;
|
||||
s.strdup()
|
||||
}
|
||||
}
|
||||
|
||||
impl Strdup for std::path::Path {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
let tmp = self.to_c_string().unwrap_or_else(|_| CString::default());
|
||||
dc_strdup(tmp.as_ptr())
|
||||
fn strdup(&self) -> *mut libc::c_char {
|
||||
unsafe {
|
||||
let tmp = self.to_c_string().unwrap_or_else(|_| CString::default());
|
||||
dc_strdup(tmp.as_ptr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Strdup for [u8] {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
let tmp = CString::new_lossy(self);
|
||||
dc_strdup(tmp.as_ptr())
|
||||
fn strdup(&self) -> *mut libc::c_char {
|
||||
unsafe {
|
||||
let tmp = CString::new_lossy(self);
|
||||
dc_strdup(tmp.as_ptr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,15 +217,15 @@ pub(crate) trait OptStrdup {
|
||||
/// Allocate a new raw C `*char` version of this string, or NULL.
|
||||
///
|
||||
/// See [Strdup::strdup] for details.
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char;
|
||||
fn strdup(&self) -> *mut libc::c_char;
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> OptStrdup for Option<T> {
|
||||
unsafe fn strdup(&self) -> *mut libc::c_char {
|
||||
fn strdup(&self) -> *mut libc::c_char {
|
||||
match self {
|
||||
Some(s) => {
|
||||
let tmp = CString::new_lossy(s.as_ref());
|
||||
dc_strdup(tmp.as_ptr())
|
||||
unsafe { dc_strdup(tmp.as_ptr()) }
|
||||
}
|
||||
None => ptr::null_mut(),
|
||||
}
|
||||
@@ -255,20 +263,18 @@ pub(crate) fn to_opt_string_lossy(s: *const libc::c_char) -> Option<String> {
|
||||
///
|
||||
/// [Path]: std::path::Path
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub(crate) fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
pub(crate) unsafe fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
assert!(!s.is_null(), "cannot be used on null pointers");
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
unsafe {
|
||||
let c_str = std::ffi::CStr::from_ptr(s).to_bytes();
|
||||
let os_str = std::ffi::OsStr::from_bytes(c_str);
|
||||
std::path::Path::new(os_str)
|
||||
}
|
||||
let c_str = unsafe { std::ffi::CStr::from_ptr(s) }.to_bytes();
|
||||
let os_str = std::ffi::OsStr::from_bytes(c_str);
|
||||
std::path::Path::new(os_str)
|
||||
}
|
||||
|
||||
// as_path() implementation for windows, documented above.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
as_path_unicode(s)
|
||||
pub(crate) unsafe fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
unsafe { as_path_unicode(s) }
|
||||
}
|
||||
|
||||
// Implementation for as_path() on Windows.
|
||||
@@ -276,7 +282,7 @@ pub(crate) fn as_path<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
// Having this as a separate function means it can be tested on unix
|
||||
// too.
|
||||
#[allow(dead_code)]
|
||||
fn as_path_unicode<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
unsafe fn as_path_unicode<'a>(s: *const libc::c_char) -> &'a std::path::Path {
|
||||
assert!(!s.is_null(), "cannot be used on null pointers");
|
||||
|
||||
let cstr = unsafe { CStr::from_ptr(s) };
|
||||
@@ -364,14 +370,20 @@ mod tests {
|
||||
fn test_as_path() {
|
||||
let some_path = CString::new("/some/path").unwrap();
|
||||
let ptr = some_path.as_ptr();
|
||||
assert_eq!(as_path(ptr), std::ffi::OsString::from("/some/path"))
|
||||
assert_eq!(
|
||||
unsafe { as_path(ptr) },
|
||||
std::ffi::OsString::from("/some/path")
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_as_path_unicode_fn() {
|
||||
let some_path = CString::new("/some/path").unwrap();
|
||||
let ptr = some_path.as_ptr();
|
||||
assert_eq!(as_path_unicode(ptr), std::ffi::OsString::from("/some/path"));
|
||||
assert_eq!(
|
||||
unsafe { as_path_unicode(ptr) },
|
||||
std::ffi::OsString::from("/some/path")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "2.48.0-dev"
|
||||
version = "2.60.0-dev"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/chatmail/core"
|
||||
|
||||
|
||||
@@ -5,31 +5,31 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||
use anyhow::{Context, Result, anyhow, bail, ensure};
|
||||
use deltachat::EventEmitter;
|
||||
pub use deltachat::accounts::Accounts;
|
||||
use deltachat::blob::BlobObject;
|
||||
use deltachat::calls::ice_servers;
|
||||
use deltachat::chat::{
|
||||
self, add_contact_to_chat, forward_msgs, forward_msgs_2ctx, get_chat_media, get_chat_msgs,
|
||||
get_chat_msgs_ex, markfresh_chat, marknoticed_all_chats, marknoticed_chat,
|
||||
remove_contact_from_chat, Chat, ChatId, ChatItem, MessageListOptions,
|
||||
self, Chat, ChatId, ChatItem, MessageListOptions, add_contact_to_chat, forward_msgs,
|
||||
forward_msgs_2ctx, get_chat_media, get_chat_msgs, get_chat_msgs_ext, markfresh_chat,
|
||||
marknoticed_all_chats, marknoticed_chat, remove_contact_from_chat,
|
||||
};
|
||||
use deltachat::chatlist::Chatlist;
|
||||
use deltachat::config::{get_all_ui_config_keys, Config};
|
||||
use deltachat::config::{Config, get_all_ui_config_keys};
|
||||
use deltachat::constants::DC_MSG_ID_DAYMARKER;
|
||||
use deltachat::contact::{may_be_valid_addr, Contact, ContactId, Origin};
|
||||
use deltachat::contact::{Contact, ContactId, Origin, may_be_valid_addr};
|
||||
use deltachat::context::get_info;
|
||||
use deltachat::ephemeral::Timer;
|
||||
use deltachat::imex;
|
||||
use deltachat::location;
|
||||
use deltachat::message::{
|
||||
self, delete_msgs_ex, get_existing_msg_ids, get_msg_read_receipt_count, get_msg_read_receipts,
|
||||
markseen_msgs, Message, MessageState, MsgId, Viewtype,
|
||||
self, Message, MessageState, MsgId, Viewtype, delete_msgs_ext, dont_truncate_long_messages,
|
||||
get_existing_msg_ids, get_msg_read_receipt_count, get_msg_read_receipts, markseen_msgs,
|
||||
};
|
||||
use deltachat::peer_channels::{
|
||||
leave_webxdc_realtime, send_webxdc_realtime_advertisement, send_webxdc_realtime_data,
|
||||
};
|
||||
use deltachat::provider::get_provider_info;
|
||||
use deltachat::qr::{self, Qr};
|
||||
use deltachat::qr_code_generator::{create_qr_svg, generate_backup_qr, get_securejoin_qr_svg};
|
||||
use deltachat::reaction::{get_msg_reactions, send_reaction};
|
||||
@@ -37,10 +37,9 @@ use deltachat::securejoin;
|
||||
use deltachat::stock_str::StockMessage;
|
||||
use deltachat::storage_usage::{get_blobdir_storage_usage, get_storage_usage};
|
||||
use deltachat::webxdc::StatusUpdateSerial;
|
||||
use deltachat::EventEmitter;
|
||||
use sanitize_filename::is_sanitized;
|
||||
use tokio::fs;
|
||||
use tokio::sync::{watch, Mutex, RwLock};
|
||||
use tokio::sync::{Mutex, RwLock, watch};
|
||||
use types::login_param::EnteredLoginParam;
|
||||
use yerpc::rpc;
|
||||
|
||||
@@ -54,8 +53,6 @@ use types::contact::{ContactObject, VcardContact};
|
||||
use types::events::Event;
|
||||
use types::http::HttpResponse;
|
||||
use types::message::{MessageData, MessageObject, MessageReadReceipt};
|
||||
use types::notify_state::JsonrpcNotifyState;
|
||||
use types::provider_info::ProviderInfo;
|
||||
use types::reactions::JsonrpcReactions;
|
||||
use types::webxdc::WebxdcMessageInfo;
|
||||
|
||||
@@ -67,7 +64,8 @@ use self::types::{
|
||||
JsonrpcMessageListItem, MessageNotificationInfo, MessageSearchResult, MessageViewtype,
|
||||
},
|
||||
};
|
||||
use crate::api::types::chat_list::{get_chat_list_item_by_id, ChatListItemFetchResult};
|
||||
use crate::api::types::appversions::JsonrpcAppSource;
|
||||
use crate::api::types::chat_list::{ChatListItemFetchResult, get_chat_list_item_by_id};
|
||||
use crate::api::types::login_param::TransportListEntry;
|
||||
use crate::api::types::qr::{QrObject, SecurejoinSource, SecurejoinUiPath};
|
||||
|
||||
@@ -330,12 +328,6 @@ impl CommandApi {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current push notification state.
|
||||
async fn get_push_state(&self, account_id: u32) -> Result<JsonrpcNotifyState> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
Ok(ctx.push_state().await.into())
|
||||
}
|
||||
|
||||
/// Get the combined filesize of an account in bytes
|
||||
async fn get_account_file_size(&self, account_id: u32) -> Result<u64> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
@@ -345,21 +337,6 @@ impl CommandApi {
|
||||
Ok(dbfile + total_size)
|
||||
}
|
||||
|
||||
/// Returns provider for the given domain.
|
||||
///
|
||||
/// This function looks up domain in offline database.
|
||||
///
|
||||
/// For compatibility, email address can be passed to this function
|
||||
/// instead of the domain.
|
||||
async fn get_provider_info(
|
||||
&self,
|
||||
_account_id: u32,
|
||||
email: String,
|
||||
) -> Result<Option<ProviderInfo>> {
|
||||
let provider_info = get_provider_info(email.split('@').next_back().unwrap_or(""));
|
||||
Ok(ProviderInfo::from_dc_type(provider_info))
|
||||
}
|
||||
|
||||
/// Checks if the context is already configured.
|
||||
async fn is_configured(&self, account_id: u32) -> Result<bool> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
@@ -526,7 +503,7 @@ impl CommandApi {
|
||||
/// - [Self::add_transport_from_qr()] to add a transport
|
||||
/// from a server encoded in a QR code.
|
||||
/// - [Self::list_transports()] to get a list of all configured transports.
|
||||
/// - [Self::delete_transport()] to remove a transport.
|
||||
/// - [Self::set_transport_unpublished()] to remove a transport.
|
||||
/// - [Self::set_transport_unpublished()] to set whether contacts see this transport.
|
||||
async fn add_or_update_transport(
|
||||
&self,
|
||||
@@ -552,23 +529,31 @@ impl CommandApi {
|
||||
|
||||
/// Returns the list of all email accounts that are used as a transport in the current profile.
|
||||
/// Use [Self::add_or_update_transport()] to add or change a transport
|
||||
/// and [Self::delete_transport()] to delete a transport.
|
||||
/// Use [Self::list_transports_ex()] to additionally query
|
||||
/// whether the transports are marked as 'unpublished'.
|
||||
/// and [Self::set_transport_unpublished()] to remove a transport.
|
||||
async fn list_transports(&self, account_id: u32) -> Result<Vec<EnteredLoginParam>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let res = ctx
|
||||
.list_transports()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter(|t| !t.is_unpublished)
|
||||
.map(|t| t.param.into())
|
||||
.collect();
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Deprecated 2026-06: This is not needed by UI implementations anymore,
|
||||
/// because unpublished relays now count as removed from the user point of view,
|
||||
/// and must not be shown in the list of relays.
|
||||
/// This means that UIs should use `list_transports()` instead of this function.
|
||||
///
|
||||
/// Returns the list of all email accounts that are used as a transport in the current profile.
|
||||
///
|
||||
/// As opposed to `list_transports()`, this function also returns unpublished transports,
|
||||
/// and for each returned transport it returns the information whether or not is `unpublished`.
|
||||
///
|
||||
/// Use [Self::add_or_update_transport()] to add or change a transport
|
||||
/// and [Self::delete_transport()] to delete a transport.
|
||||
/// and [Self::set_transport_unpublished()] to change whether a transport is 'published'.
|
||||
async fn list_transports_ex(&self, account_id: u32) -> Result<Vec<TransportListEntry>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let res = ctx
|
||||
@@ -580,23 +565,31 @@ impl CommandApi {
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Removes the transport with the specified email address
|
||||
/// (i.e. [EnteredLoginParam::addr]).
|
||||
/// Immediately deletes a transport, potentially causing messages not to arrive.
|
||||
/// This must ONLY be used by the automated tests.
|
||||
/// UI implementations must use [`Self::set_transport_unpublished`] instead.
|
||||
async fn delete_transport(&self, account_id: u32, addr: String) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
ctx.delete_transport(&addr).await
|
||||
}
|
||||
|
||||
/// Change whether the transport is unpublished.
|
||||
/// UIs should call this function when the user clicks on "Remove".
|
||||
/// Core will keep listening on this transport for some time,
|
||||
/// and automatically remove it once it is no longer needed.
|
||||
///
|
||||
/// Unpublished transports are not advertised to contacts,
|
||||
/// and self-sent messages are not sent there,
|
||||
/// so that we don't cause extra messages to the corresponding inbox,
|
||||
/// but can still receive messages from contacts who don't know our new transport addresses yet.
|
||||
///
|
||||
/// The default is false, but when the user updates from a version that didn't have this flag,
|
||||
/// existing secondary transports are set to unpublished,
|
||||
/// so that an existing transport address doesn't suddenly get spammed with a lot of messages.
|
||||
/// When more transports are added by [`Self::add_or_update_transport()`] or [`Self::add_transport_from_qr`],
|
||||
/// the least recently needed unpublished transport is automatically removed
|
||||
/// if this is necessary in order to stay below the maximum number of allowed relays.
|
||||
/// Also, unpublished transports that are not used to receive any new messages for a time defined by
|
||||
/// [`UNPUBLISHED_TRANSPORT_KEEP_TIME`] are automatically removed.
|
||||
///
|
||||
/// [`UNPUBLISHED_TRANSPORT_KEEP_TIME`]: deltachat::sql::UNPUBLISHED_TRANSPORT_KEEP_TIME
|
||||
async fn set_transport_unpublished(
|
||||
&self,
|
||||
account_id: u32,
|
||||
@@ -678,7 +671,7 @@ impl CommandApi {
|
||||
ChatId::new(chat_id).get_fresh_msg_cnt(&ctx).await
|
||||
}
|
||||
|
||||
/// Gets messages to be processed by the bot and returns their IDs.
|
||||
/// (deprecated) Gets messages to be processed by the bot and returns their IDs.
|
||||
///
|
||||
/// Only messages with database ID higher than `last_msg_id` config value
|
||||
/// are returned. After processing the messages, the bot should
|
||||
@@ -686,6 +679,13 @@ impl CommandApi {
|
||||
/// or manually updating the value to avoid getting already
|
||||
/// processed messages.
|
||||
///
|
||||
/// Deprecated 2026-04: This returns the message's id as soon as the first part arrives,
|
||||
/// even if it is not fully downloaded yet.
|
||||
/// The bot needs to wait for the message to be fully downloaded.
|
||||
/// Since this is usually not the desired behavior,
|
||||
/// bots should instead use the #DC_EVENT_INCOMING_MSG / [`types::events::EventType::IncomingMsg`]
|
||||
/// event for getting notified about new messages.
|
||||
///
|
||||
/// [`markseen_msgs`]: Self::markseen_msgs
|
||||
async fn get_next_msgs(&self, account_id: u32) -> Result<Vec<u32>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
@@ -698,7 +698,7 @@ impl CommandApi {
|
||||
Ok(msg_ids)
|
||||
}
|
||||
|
||||
/// Waits for messages to be processed by the bot and returns their IDs.
|
||||
/// (deprecated) Waits for messages to be processed by the bot and returns their IDs.
|
||||
///
|
||||
/// This function is similar to [`get_next_msgs`],
|
||||
/// but waits for internal new message notification before returning.
|
||||
@@ -709,6 +709,13 @@ impl CommandApi {
|
||||
/// To shutdown the bot, stopping I/O can be used to interrupt
|
||||
/// pending or next `wait_next_msgs` call.
|
||||
///
|
||||
/// Deprecated 2026-04: This returns the message's id as soon as the first part arrives,
|
||||
/// even if it is not fully downloaded yet.
|
||||
/// The bot needs to wait for the message to be fully downloaded.
|
||||
/// Since this is usually not the desired behavior,
|
||||
/// bots should instead use the #DC_EVENT_INCOMING_MSG / [`types::events::EventType::IncomingMsg`]
|
||||
/// event for getting notified about new messages.
|
||||
///
|
||||
/// [`get_next_msgs`]: Self::get_next_msgs
|
||||
async fn wait_next_msgs(&self, account_id: u32) -> Result<Vec<u32>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
@@ -721,10 +728,19 @@ impl CommandApi {
|
||||
Ok(msg_ids)
|
||||
}
|
||||
|
||||
/// Estimate the number of messages that will be deleted
|
||||
/// by the set_config()-options `delete_device_after` or `delete_server_after`.
|
||||
/// Estimates the number of messages that will be deleted
|
||||
/// by the `set_config()`-option `delete_device_after`.
|
||||
///
|
||||
/// This is typically used to show the estimated impact to the user
|
||||
/// before actually enabling deletion of old messages.
|
||||
///
|
||||
/// Messages in the "Saved Messages" chat are not counted as they will not be deleted automatically.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `from_server`: Deprecated, pass `false` here
|
||||
/// - `seconds`: Count messages older than the given number of seconds.
|
||||
///
|
||||
/// Returns the number of messages that are older than the given number of seconds.
|
||||
async fn estimate_auto_deletion_count(
|
||||
&self,
|
||||
account_id: u32,
|
||||
@@ -836,7 +852,7 @@ impl CommandApi {
|
||||
/// - The chat or the contact is **not blocked**, so new messages from the user/the group may appear as a contact request
|
||||
/// and the user may create the chat again.
|
||||
/// - **Groups are not left** - this would
|
||||
/// be unexpected as (1) deleting a normal chat also does not prevent new mails
|
||||
/// be unexpected as (1) deleting a single chat also does not prevent new mails
|
||||
/// from arriving, (2) leaving a group requires sending a message to
|
||||
/// all group members - especially for groups not used for a longer time, this is
|
||||
/// really unexpected when deletion results in contacting all members again,
|
||||
@@ -920,7 +936,6 @@ impl CommandApi {
|
||||
/// to `check_qr()`.
|
||||
///
|
||||
/// **returns**: The chat ID of the joined chat, the UI may redirect to the this chat.
|
||||
/// A returned chat ID does not guarantee that the chat is protected or the belonging contact is verified.
|
||||
///
|
||||
async fn secure_join(&self, account_id: u32, qr: String) -> Result<u32> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
@@ -1001,7 +1016,7 @@ impl CommandApi {
|
||||
|
||||
/// Get the contact IDs belonging to a chat.
|
||||
///
|
||||
/// - for normal chats, the function always returns exactly one contact,
|
||||
/// - for single chats, the function always returns exactly one contact,
|
||||
/// DC_CONTACT_ID_SELF is returned only for SELF-chats.
|
||||
///
|
||||
/// - for group chats all members are returned, DC_CONTACT_ID_SELF is returned
|
||||
@@ -1058,7 +1073,9 @@ impl CommandApi {
|
||||
/// Create a new unencrypted group chat.
|
||||
///
|
||||
/// Same as [`Self::create_group_chat`], but the chat is unencrypted and can only have
|
||||
/// address-contacts.
|
||||
/// address-contacts. NB: Chats with similar names and the same members are merged on other
|
||||
/// devices, but usually users don't create such chats and look up the existing one instead, so
|
||||
/// chat split on the first device is acceptable.
|
||||
async fn create_group_chat_unencrypted(&self, account_id: u32, name: String) -> Result<u32> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
chat::create_group_unencrypted(&ctx, &name)
|
||||
@@ -1083,9 +1100,6 @@ impl CommandApi {
|
||||
/// because the word "channel" already appears a lot in the code,
|
||||
/// which would make it hard to grep for it.
|
||||
///
|
||||
/// After creation, the chat contains no recipients and is in _unpromoted_ state;
|
||||
/// see [`CommandApi::create_group_chat`] for more information on the unpromoted state.
|
||||
///
|
||||
/// Returns the created chat's id.
|
||||
async fn create_broadcast(&self, account_id: u32, chat_name: String) -> Result<u32> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
@@ -1330,7 +1344,7 @@ impl CommandApi {
|
||||
/// The concrete action depends on the type of the chat and on the users settings
|
||||
/// (dc_msgs_presented() may be a better name therefore, but well. :)
|
||||
///
|
||||
/// - For normal chats, the IMAP state is updated, MDN is sent
|
||||
/// - For single chats, the IMAP state is updated, MDN is sent
|
||||
/// (if set_config()-options `mdns_enabled` is set)
|
||||
/// and the internal state is changed to @ref DC_STATE_IN_SEEN to reflect these actions.
|
||||
///
|
||||
@@ -1352,8 +1366,22 @@ impl CommandApi {
|
||||
markseen_msgs(&ctx, msg_ids.into_iter().map(MsgId::new).collect()).await
|
||||
}
|
||||
|
||||
/// Returns all messages of a particular chat.
|
||||
/// Get all message IDs belonging to a chat.
|
||||
///
|
||||
/// The list is already sorted and starts with the oldest message.
|
||||
/// Clients should not try to re-sort the list as this would be an expensive action
|
||||
/// and would result in inconsistencies between clients.
|
||||
/// Note that the messages are not necessarily sorted by their ID or by their displayed timestamp;
|
||||
/// UIs need to handle both the case of descending message IDs
|
||||
/// and of decreasing timestamps.
|
||||
///
|
||||
/// Optionally, 'daymarkers' added to the ID array may help to
|
||||
/// implement virtual lists.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * chat_id The chat ID of which the messages IDs should be queried.
|
||||
/// * _info_only: Deprecated, pass `false` here.
|
||||
/// * `add_daymarker` - If `true`, add day markers as `DC_MSG_ID_DAYMARKER` to the result,
|
||||
/// e.g. [1234, 1237, 9, 1239]. The day marker timestamp is the midnight one for the
|
||||
/// corresponding (following) day in the local timezone.
|
||||
@@ -1361,17 +1389,14 @@ impl CommandApi {
|
||||
&self,
|
||||
account_id: u32,
|
||||
chat_id: u32,
|
||||
info_only: bool,
|
||||
_info_only: bool,
|
||||
add_daymarker: bool,
|
||||
) -> Result<Vec<u32>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let msg = get_chat_msgs_ex(
|
||||
let msg = get_chat_msgs_ext(
|
||||
&ctx,
|
||||
ChatId::new(chat_id),
|
||||
MessageListOptions {
|
||||
info_only,
|
||||
add_daymarker,
|
||||
},
|
||||
MessageListOptions { add_daymarker },
|
||||
)
|
||||
.await?;
|
||||
Ok(msg
|
||||
@@ -1403,21 +1428,24 @@ impl CommandApi {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all messages belonging to a chat.
|
||||
///
|
||||
/// Similar to `get_message_ids` / `getMessageIds`,
|
||||
/// see that function for details.
|
||||
/// The difference is that this function here returns a list of `MessageListItem`,
|
||||
/// which is an enum of a message or a daymarker.
|
||||
async fn get_message_list_items(
|
||||
&self,
|
||||
account_id: u32,
|
||||
chat_id: u32,
|
||||
info_only: bool,
|
||||
_info_only: bool,
|
||||
add_daymarker: bool,
|
||||
) -> Result<Vec<JsonrpcMessageListItem>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let msg = get_chat_msgs_ex(
|
||||
let msg = get_chat_msgs_ext(
|
||||
&ctx,
|
||||
ChatId::new(chat_id),
|
||||
MessageListOptions {
|
||||
info_only,
|
||||
add_daymarker,
|
||||
},
|
||||
MessageListOptions { add_daymarker },
|
||||
)
|
||||
.await?;
|
||||
Ok(msg
|
||||
@@ -1441,6 +1469,15 @@ impl CommandApi {
|
||||
MsgId::new(message_id).get_html(&ctx).await
|
||||
}
|
||||
|
||||
/// Opt out of truncating long messages when loading.
|
||||
///
|
||||
/// Should be used by the UIs that can handle long text messages.
|
||||
async fn dont_truncate_long_messages(&self, account_id: u32) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
dont_truncate_long_messages(&ctx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// get multiple messages in one call,
|
||||
/// if loading one message fails the error is stored in the result object in it's place.
|
||||
///
|
||||
@@ -1480,12 +1517,32 @@ impl CommandApi {
|
||||
MessageNotificationInfo::from_msg_id(&ctx, MsgId::new(message_id)).await
|
||||
}
|
||||
|
||||
/// Sets the "pinned" state for a message.
|
||||
async fn set_pinned_message_state(
|
||||
&self,
|
||||
account_id: u32,
|
||||
message_id: u32,
|
||||
pinned_state: bool,
|
||||
) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
deltachat::pinned_messages::set_pinned_state(&ctx, MsgId::new(message_id), pinned_state)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns all pinned messages of a chat.
|
||||
async fn get_pinned_messages(&self, account_id: u32, chat_id: u32) -> Result<Vec<u32>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let msg_ids =
|
||||
deltachat::pinned_messages::get_pinned_messages(&ctx, ChatId::new(chat_id)).await?;
|
||||
Ok(msg_ids.into_iter().map(|id| id.to_u32()).collect())
|
||||
}
|
||||
|
||||
/// Delete messages. The messages are deleted on the current device and
|
||||
/// on the IMAP server.
|
||||
async fn delete_messages(&self, account_id: u32, message_ids: Vec<u32>) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let msgs: Vec<MsgId> = message_ids.into_iter().map(MsgId::new).collect();
|
||||
delete_msgs_ex(&ctx, &msgs, false).await
|
||||
delete_msgs_ext(&ctx, &msgs, false).await
|
||||
}
|
||||
|
||||
/// Delete messages. The messages are deleted on the current device,
|
||||
@@ -1493,7 +1550,7 @@ impl CommandApi {
|
||||
async fn delete_messages_for_all(&self, account_id: u32, message_ids: Vec<u32>) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let msgs: Vec<MsgId> = message_ids.into_iter().map(MsgId::new).collect();
|
||||
delete_msgs_ex(&ctx, &msgs, true).await
|
||||
delete_msgs_ext(&ctx, &msgs, true).await
|
||||
}
|
||||
|
||||
/// Get an informational text for a single message. The text is multiline and may
|
||||
@@ -1854,25 +1911,11 @@ impl CommandApi {
|
||||
deltachat::contact::make_vcard(&ctx, &contacts).await
|
||||
}
|
||||
|
||||
/// Sets vCard containing the given contacts to the message draft.
|
||||
async fn set_draft_vcard(
|
||||
&self,
|
||||
account_id: u32,
|
||||
msg_id: u32,
|
||||
contacts: Vec<u32>,
|
||||
) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let contacts: Vec<_> = contacts.iter().map(|&c| ContactId::new(c)).collect();
|
||||
let mut msg = Message::load_from_db(&ctx, MsgId::new(msg_id)).await?;
|
||||
msg.make_vcard(&ctx, &contacts).await?;
|
||||
msg.get_chat_id().set_draft(&ctx, Some(&mut msg)).await
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// chat
|
||||
// ---------------------------------------------
|
||||
|
||||
/// Returns the [`ChatId`] for the 1:1 chat with `contact_id` if it exists.
|
||||
/// Returns the [`ChatId`] for the single chat with `contact_id` if it exists.
|
||||
///
|
||||
/// If it does not exist, `None` is returned.
|
||||
async fn get_chat_id_by_contact_id(
|
||||
@@ -2057,13 +2100,10 @@ impl CommandApi {
|
||||
|
||||
/// Get the current connectivity, i.e. whether the device is connected to the IMAP server.
|
||||
/// One of:
|
||||
/// - DC_CONNECTIVITY_NOT_CONNECTED (1000-1999): Show e.g. the string "Not connected" or a red dot
|
||||
/// - DC_CONNECTIVITY_CONNECTING (2000-2999): Show e.g. the string "Connecting…" or a yellow dot
|
||||
/// - DC_CONNECTIVITY_WORKING (3000-3999): Show e.g. the string "Getting new messages" or a spinning wheel
|
||||
/// - DC_CONNECTIVITY_CONNECTED (>=4000): Show e.g. the string "Connected" or a green dot
|
||||
///
|
||||
/// We don't use exact values but ranges here so that we can split up
|
||||
/// states into multiple states in the future.
|
||||
/// - DC_CONNECTIVITY_NOT_CONNECTED (1000): Show e.g. the string "Not connected" or a red dot
|
||||
/// - DC_CONNECTIVITY_CONNECTING (2000): Show e.g. the string "Connecting…" or a yellow dot
|
||||
/// - DC_CONNECTIVITY_WORKING (3000): Show e.g. the string "Getting new messages" or a spinning wheel
|
||||
/// - DC_CONNECTIVITY_CONNECTED (4000): Show e.g. the string "Connected" or a green dot
|
||||
///
|
||||
/// Meant as a rough overview that can be shown
|
||||
/// e.g. in the title of the main screen.
|
||||
@@ -2092,6 +2132,21 @@ impl CommandApi {
|
||||
// locations
|
||||
// ---------------------------------------------
|
||||
|
||||
/// Sets current location.
|
||||
///
|
||||
/// Returns true if location streaming is currently
|
||||
/// enabled and locations should be updated.
|
||||
///
|
||||
/// Location is represented as latitude and longitude in degrees
|
||||
/// and horizontal accuracy in meters.
|
||||
async fn set_location(&self, latitude: f64, longitude: f64, accuracy: f64) -> Result<bool> {
|
||||
self.accounts
|
||||
.read()
|
||||
.await
|
||||
.set_location(latitude, longitude, accuracy)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_locations(
|
||||
&self,
|
||||
account_id: u32,
|
||||
@@ -2114,6 +2169,39 @@ impl CommandApi {
|
||||
Ok(locations.into_iter().map(|l| l.into()).collect())
|
||||
}
|
||||
|
||||
/// Enables location streaming in chat identified by `chat_id` for `seconds` seconds.
|
||||
///
|
||||
/// Pass 0 as the number of seconds to disable location streaming in the chat.
|
||||
async fn send_locations_to_chat(
|
||||
&self,
|
||||
account_id: u32,
|
||||
chat_id: u32,
|
||||
seconds: i64,
|
||||
) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let chat_id = ChatId::new(chat_id);
|
||||
location::send_to_chat(&ctx, chat_id, seconds).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns whether any chat is sending locations.
|
||||
async fn is_sending_locations(&self, account_id: u32) -> Result<bool> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
location::is_sending(&ctx).await
|
||||
}
|
||||
|
||||
/// Returns whether `chat_id` is sending locations.
|
||||
async fn is_sending_locations_to_chat(&self, account_id: u32, chat_id: u32) -> Result<bool> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let chat_id = ChatId::new(chat_id);
|
||||
location::is_sending_to_chat(&ctx, chat_id).await
|
||||
}
|
||||
|
||||
/// Stops sending locations to all chats.
|
||||
async fn stop_sending_locations(&self) -> Result<()> {
|
||||
self.accounts.read().await.stop_sending_locations().await
|
||||
}
|
||||
|
||||
// ---------------------------------------------
|
||||
// webxdc
|
||||
// ---------------------------------------------
|
||||
@@ -2209,7 +2297,7 @@ impl CommandApi {
|
||||
let message = Message::load_from_db(&ctx, MsgId::new(instance_msg_id)).await?;
|
||||
let blob = message.get_webxdc_blob(&ctx, &path).await?;
|
||||
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
Ok(general_purpose::STANDARD_NO_PAD.encode(blob))
|
||||
}
|
||||
|
||||
@@ -2345,6 +2433,7 @@ impl CommandApi {
|
||||
chat::resend_msgs(&ctx, &message_ids).await
|
||||
}
|
||||
|
||||
/// @deprecated as of 2026-04; use `send_msg` with `Viewtype::Sticker` instead.
|
||||
async fn send_sticker(
|
||||
&self,
|
||||
account_id: u32,
|
||||
@@ -2356,19 +2445,16 @@ impl CommandApi {
|
||||
let mut msg = Message::new(Viewtype::Sticker);
|
||||
msg.set_file_and_deduplicate(&ctx, Path::new(&sticker_path), None, None)?;
|
||||
|
||||
// JSON-rpc does not need heuristics to turn [Viewtype::Sticker] into [Viewtype::Image]
|
||||
msg.force_sticker();
|
||||
|
||||
let message_id = deltachat::chat::send_msg(&ctx, ChatId::new(chat_id), &mut msg).await?;
|
||||
Ok(message_id.to_u32())
|
||||
}
|
||||
|
||||
/// Send a reaction to message.
|
||||
/// Sends a reaction to message.
|
||||
///
|
||||
/// Reaction is a string of emojis separated by spaces. Reaction to a
|
||||
/// single message can be sent multiple times. The last reaction
|
||||
/// received overrides all previously received reactions. It is
|
||||
/// possible to remove all reactions by sending an empty string.
|
||||
/// A reaction is a string that represents an emoji.
|
||||
/// You can call this function again to change the emoji;
|
||||
/// the last sent reaction overrides all previously sent reactions.
|
||||
/// It is possible to remove the reaction by sending an empty string.
|
||||
async fn send_reaction(
|
||||
&self,
|
||||
account_id: u32,
|
||||
@@ -2712,6 +2798,39 @@ impl CommandApi {
|
||||
Err(anyhow!("chat with id {chat_id} doesn't have draft message"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get version information of a specific client and source
|
||||
/// across all configured accounts and transports.
|
||||
///
|
||||
/// Returns the source with the highest `version_integer`.
|
||||
/// If no matching version information is available at all, `None` is returned.
|
||||
///
|
||||
/// UIs shall call the function after a reasonable time after app start,
|
||||
/// when most relays have reported the information they have, say 30 seconds.
|
||||
/// After that, once a day.
|
||||
/// (it is accepted if by the simple approach an update message is delayed.
|
||||
/// an event was considered, but that seemed more complex for few benefit:
|
||||
/// as we do not know if "late" relays will report "better" versions,
|
||||
/// also there we would work with timeouts etc.)
|
||||
///
|
||||
/// If the reported `version_integer` is larger than the running app version,
|
||||
/// the UI shall report to the user, that an update is available,
|
||||
/// and, if possible, offer a direct update by the given URL.
|
||||
///
|
||||
/// Security note: consumers need to verify themselves
|
||||
/// that downloaded app files are valid before installing them.
|
||||
async fn get_app_version(
|
||||
&self,
|
||||
client_id: String,
|
||||
source_id: String,
|
||||
) -> Result<Option<JsonrpcAppSource>> {
|
||||
let accounts = self.accounts.read().await;
|
||||
Ok(
|
||||
deltachat::appversions::get_app_version(&accounts, &client_id, &source_id)
|
||||
.await?
|
||||
.map(JsonrpcAppSource::from_core_type),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions (to prevent code duplication)
|
||||
|
||||
31
deltachat-jsonrpc/src/api/types/appversions.rs
Normal file
31
deltachat-jsonrpc/src/api/types/appversions.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use deltachat::appversions::AppSource;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
/// Version information of a single source of a client, eg. "gplay" or "fdroid".
|
||||
#[derive(Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "AppSource", rename_all = "camelCase")]
|
||||
pub struct JsonrpcAppSource {
|
||||
/// Always increasing version number.
|
||||
pub version_integer: u32,
|
||||
|
||||
/// Version string that should be shown to the user.
|
||||
/// UI must not linkify the string
|
||||
/// as it may be interpreted like a phone number or an IP address.
|
||||
pub version_string: String,
|
||||
|
||||
/// Where to download that version.
|
||||
/// Security note: consumers need to verify themselves
|
||||
/// that downloaded app files are valid before installing them.
|
||||
pub download_url: String,
|
||||
}
|
||||
|
||||
impl JsonrpcAppSource {
|
||||
pub fn from_core_type(source: AppSource) -> Self {
|
||||
JsonrpcAppSource {
|
||||
version_integer: source.version_integer,
|
||||
version_string: source.version_string,
|
||||
download_url: source.download_url,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
|
||||
use deltachat::calls::{call_state, CallState};
|
||||
use deltachat::calls::{CallState, call_state};
|
||||
use deltachat::context::Context;
|
||||
use deltachat::message::MsgId;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::{bail, Context as _, Result};
|
||||
use deltachat::chat::{self, get_chat_contacts, get_past_chat_contacts, ChatVisibility};
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use deltachat::chat::{self, ChatVisibility, get_chat_contacts, get_past_chat_contacts};
|
||||
use deltachat::chat::{Chat, ChatId};
|
||||
use deltachat::constants::Chattype;
|
||||
use deltachat::contact::{Contact, ContactId};
|
||||
|
||||
@@ -4,7 +4,7 @@ use deltachat::chatlist::get_last_message_for_chat;
|
||||
use deltachat::constants::*;
|
||||
use deltachat::contact::Contact;
|
||||
use deltachat::{
|
||||
chat::{get_chat_contacts, ChatVisibility},
|
||||
chat::{ChatVisibility, get_chat_contacts},
|
||||
chatlist::Chatlist,
|
||||
};
|
||||
use num_traits::cast::ToPrimitive;
|
||||
|
||||
@@ -203,6 +203,17 @@ pub enum EventType {
|
||||
msg_id: u32,
|
||||
},
|
||||
|
||||
/// Like [`EventType::MsgRead`], but also fires on subsequent MDNs,
|
||||
/// if there are multiple receivers, i.e. in groups and channels.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
MsgReadCountChanged {
|
||||
/// ID of the chat which the message belongs to.
|
||||
chat_id: u32,
|
||||
|
||||
/// ID of the message that was read.
|
||||
msg_id: u32,
|
||||
},
|
||||
|
||||
/// A single message was deleted.
|
||||
///
|
||||
/// This event means that the message will no longer appear in the messagelist.
|
||||
@@ -546,6 +557,10 @@ impl From<CoreEventType> for EventType {
|
||||
chat_id: chat_id.to_u32(),
|
||||
msg_id: msg_id.to_u32(),
|
||||
},
|
||||
CoreEventType::MsgReadCountChanged { chat_id, msg_id } => MsgReadCountChanged {
|
||||
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(),
|
||||
|
||||
@@ -16,7 +16,7 @@ pub struct HttpResponse {
|
||||
|
||||
impl From<CoreHttpResponse> for HttpResponse {
|
||||
fn from(response: CoreHttpResponse) -> Self {
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
let blob = general_purpose::STANDARD_NO_PAD.encode(response.blob);
|
||||
let mimetype = response.mimetype;
|
||||
let encoding = response.encoding;
|
||||
|
||||
@@ -33,6 +33,12 @@ pub struct EnteredLoginParam {
|
||||
/// Imap server port.
|
||||
pub imap_port: Option<u16>,
|
||||
|
||||
/// IMAP server folder.
|
||||
///
|
||||
/// Defaults to "INBOX" if not set.
|
||||
/// Should not be an empty string.
|
||||
pub imap_folder: Option<String>,
|
||||
|
||||
/// Imap socket security.
|
||||
pub imap_security: Option<Socket>,
|
||||
|
||||
@@ -60,10 +66,6 @@ pub struct EnteredLoginParam {
|
||||
/// invalid hostnames.
|
||||
/// Default: Automatic
|
||||
pub certificate_checks: Option<EnteredCertificateChecks>,
|
||||
|
||||
/// If true, login via OAUTH2 (not recommended anymore).
|
||||
/// Default: false
|
||||
pub oauth2: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<dc::TransportListEntry> for TransportListEntry {
|
||||
@@ -85,6 +87,7 @@ impl From<dc::EnteredLoginParam> for EnteredLoginParam {
|
||||
password: param.imap.password,
|
||||
imap_server: param.imap.server.into_option(),
|
||||
imap_port: param.imap.port.into_option(),
|
||||
imap_folder: param.imap.folder.into_option(),
|
||||
imap_security: imap_security.into_option(),
|
||||
imap_user: param.imap.user.into_option(),
|
||||
smtp_server: param.smtp.server.into_option(),
|
||||
@@ -93,7 +96,6 @@ impl From<dc::EnteredLoginParam> for EnteredLoginParam {
|
||||
smtp_user: param.smtp.user.into_option(),
|
||||
smtp_password: param.smtp.password.into_option(),
|
||||
certificate_checks: certificate_checks.into_option(),
|
||||
oauth2: param.oauth2.into_option(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,14 +106,15 @@ impl TryFrom<EnteredLoginParam> for dc::EnteredLoginParam {
|
||||
fn try_from(param: EnteredLoginParam) -> Result<Self> {
|
||||
Ok(Self {
|
||||
addr: param.addr,
|
||||
imap: dc::EnteredServerLoginParam {
|
||||
imap: dc::EnteredImapLoginParam {
|
||||
server: param.imap_server.unwrap_or_default(),
|
||||
port: param.imap_port.unwrap_or_default(),
|
||||
folder: param.imap_folder.unwrap_or_default(),
|
||||
security: param.imap_security.unwrap_or_default().into(),
|
||||
user: param.imap_user.unwrap_or_default(),
|
||||
password: param.password,
|
||||
},
|
||||
smtp: dc::EnteredServerLoginParam {
|
||||
smtp: dc::EnteredSmtpLoginParam {
|
||||
server: param.smtp_server.unwrap_or_default(),
|
||||
port: param.smtp_port.unwrap_or_default(),
|
||||
security: param.smtp_security.unwrap_or_default().into(),
|
||||
@@ -119,7 +122,7 @@ impl TryFrom<EnteredLoginParam> for dc::EnteredLoginParam {
|
||||
password: param.smtp_password.unwrap_or_default(),
|
||||
},
|
||||
certificate_checks: param.certificate_checks.unwrap_or_default().into(),
|
||||
oauth2: param.oauth2.unwrap_or_default(),
|
||||
oauth2: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -166,9 +169,8 @@ impl From<Socket> for dc::Socket {
|
||||
#[derive(Serialize, Deserialize, TypeDef, schemars::JsonSchema, Default, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum EnteredCertificateChecks {
|
||||
/// `Automatic` means that provider database setting should be taken.
|
||||
/// If there is no provider database setting for certificate checks,
|
||||
/// check certificates strictly.
|
||||
/// `Automatic` means strict certificate checks,
|
||||
/// unless a legacy-domain override disables them.
|
||||
#[default]
|
||||
Automatic,
|
||||
|
||||
|
||||
@@ -103,6 +103,8 @@ pub struct MessageObject {
|
||||
|
||||
saved_message_id: Option<u32>,
|
||||
|
||||
is_pinned: bool,
|
||||
|
||||
reactions: Option<JsonrpcReactions>,
|
||||
|
||||
vcard_contact: Option<VcardContact>,
|
||||
@@ -263,6 +265,7 @@ impl MessageObject {
|
||||
.await?
|
||||
.map(|id| id.to_u32()),
|
||||
|
||||
is_pinned: message.is_pinned(),
|
||||
reactions,
|
||||
|
||||
vcard_contact: vcard_contacts.first().cloned(),
|
||||
@@ -287,8 +290,6 @@ pub enum MessageViewtype {
|
||||
Gif,
|
||||
|
||||
/// Message containing a sticker, similar to image.
|
||||
/// NB: When sending, the message viewtype may be changed to `Image` by some heuristics like
|
||||
/// checking for transparent pixels. Use `Message::force_sticker()` to disable them.
|
||||
///
|
||||
/// 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.
|
||||
@@ -392,11 +393,11 @@ pub enum SystemMessageType {
|
||||
LocationOnly,
|
||||
InvalidUnencryptedMail,
|
||||
|
||||
/// 1:1 chats info message telling that SecureJoin has started and the user should wait for it
|
||||
/// Single chats info message telling that SecureJoin has started and the user should wait for it
|
||||
/// to complete.
|
||||
SecurejoinWait,
|
||||
|
||||
/// 1:1 chats info message telling that SecureJoin is still running, but the user may already
|
||||
/// Single chats info message telling that SecureJoin is still running, but the user may already
|
||||
/// send messages.
|
||||
SecurejoinWaitTimeout,
|
||||
|
||||
@@ -427,6 +428,8 @@ pub enum SystemMessageType {
|
||||
|
||||
CallAccepted,
|
||||
CallEnded,
|
||||
MessagePinned,
|
||||
MessageUnpinned,
|
||||
}
|
||||
|
||||
impl From<deltachat::mimeparser::SystemMessage> for SystemMessageType {
|
||||
@@ -456,6 +459,8 @@ impl From<deltachat::mimeparser::SystemMessage> for SystemMessageType {
|
||||
SystemMessage::SecurejoinWaitTimeout => SystemMessageType::SecurejoinWaitTimeout,
|
||||
SystemMessage::CallAccepted => SystemMessageType::CallAccepted,
|
||||
SystemMessage::CallEnded => SystemMessageType::CallEnded,
|
||||
SystemMessage::MessagePinned => SystemMessageType::MessagePinned,
|
||||
SystemMessage::MessageUnpinned => SystemMessageType::MessageUnpinned,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -728,9 +733,9 @@ 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 }
|
||||
}
|
||||
deltachat::ephemeral::Timer::Enabled { duration } => EphemeralTimer::Enabled {
|
||||
duration: duration.get(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod account;
|
||||
pub mod appversions;
|
||||
pub mod calls;
|
||||
pub mod chat;
|
||||
pub mod chat_list;
|
||||
@@ -8,8 +9,6 @@ pub mod http;
|
||||
pub mod location;
|
||||
pub mod login_param;
|
||||
pub mod message;
|
||||
pub mod notify_state;
|
||||
pub mod provider_info;
|
||||
pub mod qr;
|
||||
pub mod reactions;
|
||||
pub mod webxdc;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
use deltachat::push::NotifyState;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename = "NotifyState")]
|
||||
pub enum JsonrpcNotifyState {
|
||||
/// Not subscribed to push notifications.
|
||||
NotConnected,
|
||||
|
||||
/// Subscribed to heartbeat push notifications.
|
||||
Heartbeat,
|
||||
|
||||
/// Subscribed to push notifications for new messages.
|
||||
Connected,
|
||||
}
|
||||
|
||||
impl From<NotifyState> for JsonrpcNotifyState {
|
||||
fn from(state: NotifyState) -> Self {
|
||||
match state {
|
||||
NotifyState::NotConnected => Self::NotConnected,
|
||||
NotifyState::Heartbeat => Self::Heartbeat,
|
||||
NotifyState::Connected => Self::Connected,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -236,9 +236,10 @@ impl From<Qr> for QrObject {
|
||||
invitenumber,
|
||||
authcode,
|
||||
is_v3,
|
||||
..
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
let fingerprint = fingerprint.human_readable();
|
||||
QrObject::AskVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
@@ -255,9 +256,10 @@ impl From<Qr> for QrObject {
|
||||
invitenumber,
|
||||
authcode,
|
||||
is_v3,
|
||||
..
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
let fingerprint = fingerprint.human_readable();
|
||||
QrObject::AskVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
@@ -276,9 +278,10 @@ impl From<Qr> for QrObject {
|
||||
authcode,
|
||||
invitenumber,
|
||||
is_v3,
|
||||
..
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
let fingerprint = fingerprint.human_readable();
|
||||
QrObject::AskJoinBroadcast {
|
||||
name,
|
||||
grpid,
|
||||
@@ -321,7 +324,7 @@ impl From<Qr> for QrObject {
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
let fingerprint = fingerprint.human_readable();
|
||||
QrObject::WithdrawVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
@@ -338,7 +341,7 @@ impl From<Qr> for QrObject {
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
let fingerprint = fingerprint.human_readable();
|
||||
QrObject::WithdrawVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
@@ -357,7 +360,7 @@ impl From<Qr> for QrObject {
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
let fingerprint = fingerprint.human_readable();
|
||||
QrObject::WithdrawJoinBroadcast {
|
||||
name,
|
||||
grpid,
|
||||
@@ -374,7 +377,7 @@ impl From<Qr> for QrObject {
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
let fingerprint = fingerprint.human_readable();
|
||||
QrObject::ReviveVerifyContact {
|
||||
contact_id,
|
||||
fingerprint,
|
||||
@@ -391,7 +394,7 @@ impl From<Qr> for QrObject {
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
let fingerprint = fingerprint.human_readable();
|
||||
QrObject::ReviveVerifyGroup {
|
||||
grpname,
|
||||
grpid,
|
||||
@@ -410,7 +413,7 @@ impl From<Qr> for QrObject {
|
||||
authcode,
|
||||
} => {
|
||||
let contact_id = contact_id.to_u32();
|
||||
let fingerprint = fingerprint.to_string();
|
||||
let fingerprint = fingerprint.human_readable();
|
||||
QrObject::ReviveJoinBroadcast {
|
||||
name,
|
||||
grpid,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use deltachat::contact::ContactId;
|
||||
use deltachat::reaction::Reactions;
|
||||
use serde::Serialize;
|
||||
use typescript_type_def::TypeDef;
|
||||
@@ -24,6 +23,11 @@ pub struct JsonrpcReaction {
|
||||
#[serde(rename = "Reactions", rename_all = "camelCase")]
|
||||
pub struct JsonrpcReactions {
|
||||
/// Map from a contact to it's reaction to message.
|
||||
///
|
||||
/// There is only a single reaction per contact,
|
||||
/// but this contains a list of reactions for historical reasons.
|
||||
///
|
||||
/// For channels subscribers, this map is empty or contains `ContactId::SELF` only.
|
||||
reactions_by_contact: BTreeMap<u32, Vec<String>>,
|
||||
/// Unique reactions and their count, sorted in descending order.
|
||||
reactions: Vec<JsonrpcReaction>,
|
||||
@@ -31,42 +35,25 @@ pub struct JsonrpcReactions {
|
||||
|
||||
impl From<Reactions> for JsonrpcReactions {
|
||||
fn from(reactions: Reactions) -> Self {
|
||||
let mut reactions_by_contact: BTreeMap<u32, Vec<String>> = BTreeMap::new();
|
||||
let reactions_by_contact: BTreeMap<u32, Vec<String>> = reactions
|
||||
.by_contact
|
||||
.iter()
|
||||
.map(|(key, value)| (key.to_u32(), vec![value.as_str().to_string()]))
|
||||
.collect();
|
||||
|
||||
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)
|
||||
}
|
||||
let reactions = reactions
|
||||
.frequencies
|
||||
.into_iter()
|
||||
.map(|entry| JsonrpcReaction {
|
||||
emoji: entry.reaction.as_str().to_string(),
|
||||
count: entry.count,
|
||||
is_from_self: entry.is_from_self,
|
||||
})
|
||||
.collect();
|
||||
|
||||
JsonrpcReactions {
|
||||
reactions_by_contact,
|
||||
reactions: reactions_v,
|
||||
reactions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@ pub struct WebxdcMessageInfo {
|
||||
internet_access: bool,
|
||||
/// Address to be used for `window.webxdc.selfAddr` in JS land.
|
||||
self_addr: String,
|
||||
/// Define if the local user is the one who initially shared the webxdc application in the chat.
|
||||
is_app_sender: bool,
|
||||
/// Define if the app runs in a broadcasting context.
|
||||
is_broadcast: bool,
|
||||
/// Milliseconds to wait before calling `sendUpdate()` again since the last call.
|
||||
/// Should be exposed to `window.sendUpdateInterval` in JS land.
|
||||
send_update_interval: usize,
|
||||
@@ -60,6 +64,8 @@ impl WebxdcMessageInfo {
|
||||
request_integration: _,
|
||||
internet_access,
|
||||
self_addr,
|
||||
is_app_sender,
|
||||
is_broadcast,
|
||||
send_update_interval,
|
||||
send_update_max_size,
|
||||
} = message.get_webxdc_info(context).await?;
|
||||
@@ -72,6 +78,8 @@ impl WebxdcMessageInfo {
|
||||
source_code_url: maybe_empty_string_to_option(source_code_url),
|
||||
internet_access,
|
||||
self_addr,
|
||||
is_app_sender,
|
||||
is_broadcast,
|
||||
send_update_interval,
|
||||
send_update_max_size,
|
||||
})
|
||||
|
||||
@@ -85,7 +85,7 @@ mod tests {
|
||||
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":""}]}"#;
|
||||
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":""}]}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","id":2,"result":null}"#;
|
||||
session.handle_incoming(request).await;
|
||||
let result = receiver.recv().await?;
|
||||
|
||||
@@ -54,5 +54,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "2.48.0-dev"
|
||||
"version": "2.60.0-dev"
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ const constants = data
|
||||
key.startsWith("DC_CERTCK_") ||
|
||||
key.startsWith("DC_SOCKET_") ||
|
||||
key.startsWith("DC_LP_AUTH_") ||
|
||||
key.startsWith("DC_PUSH_") ||
|
||||
key.startsWith("DC_TEXT1_") ||
|
||||
key.startsWith("DC_CHAT_TYPE")
|
||||
);
|
||||
|
||||
@@ -148,23 +148,6 @@ describe("online tests", function () {
|
||||
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"]>(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "ratelimit"
|
||||
version = "1.0.0"
|
||||
description = "Token bucket implementation"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "deltachat-repl"
|
||||
version = "2.48.0-dev"
|
||||
version = "2.60.0-dev"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
repository = "https://github.com/chatmail/core"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -5,9 +5,10 @@ use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{bail, ensure, Result};
|
||||
use anyhow::{Result, bail, ensure};
|
||||
use deltachat::chat::{self, Chat, ChatId, ChatItem, ChatVisibility, MuteDuration};
|
||||
use deltachat::chatlist::*;
|
||||
use deltachat::config;
|
||||
use deltachat::constants::*;
|
||||
use deltachat::contact::*;
|
||||
use deltachat::context::*;
|
||||
@@ -24,7 +25,6 @@ use deltachat::reaction::send_reaction;
|
||||
use deltachat::receive_imf::*;
|
||||
use deltachat::sql;
|
||||
use deltachat::tools::*;
|
||||
use deltachat::{config, provider};
|
||||
use tokio::fs;
|
||||
|
||||
/// Reset database tables.
|
||||
@@ -122,7 +122,7 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool {
|
||||
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);
|
||||
let path_plus_name = format!("{real_spec}/{name}");
|
||||
println!("Import: {path_plus_name}");
|
||||
if poke_eml_file(context, Path::new(&path_plus_name))
|
||||
.await
|
||||
@@ -133,11 +133,11 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
eprintln!("Import: Cannot open directory \"{}\".", &real_spec);
|
||||
eprintln!("Import: Cannot open directory {real_spec:?}.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
println!("Import: {} items read from \"{}\".", read_cnt, &real_spec);
|
||||
println!("Import: {read_cnt} items read from {real_spec:?}.");
|
||||
if read_cnt > 0 {
|
||||
context.emit_msgs_changed_without_ids();
|
||||
}
|
||||
@@ -179,7 +179,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
msg.get_id(),
|
||||
if msg.get_showpadlock() { "🔒" } else { "" },
|
||||
if msg.has_location() { "📍" } else { "" },
|
||||
&contact_name,
|
||||
contact_name,
|
||||
contact_id,
|
||||
msgtext,
|
||||
if msg.has_html() { "[HAS-HTML]️" } else { "" },
|
||||
@@ -221,7 +221,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
},
|
||||
statestr,
|
||||
downloadstate,
|
||||
&temp2,
|
||||
temp2,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -319,7 +319,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
info\n\
|
||||
set <configuration-key> [<value>]\n\
|
||||
get <configuration-key>\n\
|
||||
oauth2\n\
|
||||
configure\n\
|
||||
connect\n\
|
||||
disconnect\n\
|
||||
@@ -345,7 +344,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
chatinfo\n\
|
||||
sendlocations <seconds>\n\
|
||||
setlocation <lat> <lng>\n\
|
||||
dellocations\n\
|
||||
getlocations [<contact-id>]\n\
|
||||
send <text>\n\
|
||||
send-sync <text>\n\
|
||||
@@ -397,7 +395,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
joinqr <qr-content>\n\
|
||||
setqr <qr-content>\n\
|
||||
createqrsvg <qr-content>\n\
|
||||
providerinfo <addr>\n\
|
||||
fileinfo <file>\n\
|
||||
estimatedeletion <seconds>\n\
|
||||
clear -- clear screen\n\
|
||||
@@ -562,7 +559,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
.map_or_else(String::new, |prefix| format!("{prefix}: ")),
|
||||
summary.text,
|
||||
statestr,
|
||||
×tr,
|
||||
timestr,
|
||||
if chat.is_sending_locations() {
|
||||
"📍"
|
||||
} else {
|
||||
@@ -574,7 +571,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
);
|
||||
}
|
||||
}
|
||||
if location::is_sending_locations_to_chat(&context, None).await? {
|
||||
if location::is_sending(&context).await? {
|
||||
println!("Location streaming enabled.");
|
||||
}
|
||||
println!("{cnt} chats");
|
||||
@@ -619,11 +616,10 @@ 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_ex(
|
||||
let msglist = chat::get_chat_msgs_ext(
|
||||
&context,
|
||||
sel_chat.get_id(),
|
||||
chat::MessageListOptions {
|
||||
info_only: false,
|
||||
add_daymarker: true,
|
||||
},
|
||||
)
|
||||
@@ -782,11 +778,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
|
||||
println!(
|
||||
"Location streaming: {}",
|
||||
location::is_sending_locations_to_chat(
|
||||
&context,
|
||||
Some(sel_chat.as_ref().unwrap().get_id())
|
||||
)
|
||||
.await?,
|
||||
location::is_sending_to_chat(&context, sel_chat.as_ref().unwrap().get_id()).await?,
|
||||
);
|
||||
}
|
||||
"getlocations" => {
|
||||
@@ -826,12 +818,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
ensure!(!arg1.is_empty(), "No timeout given.");
|
||||
|
||||
let seconds = arg1.parse()?;
|
||||
location::send_locations_to_chat(
|
||||
&context,
|
||||
sel_chat.as_ref().unwrap().get_id(),
|
||||
seconds,
|
||||
)
|
||||
.await?;
|
||||
location::send_to_chat(&context, sel_chat.as_ref().unwrap().get_id(), seconds).await?;
|
||||
println!(
|
||||
"Locations will be sent to Chat#{} for {} seconds. Use 'setlocation <lat> <lng>' to play around.",
|
||||
sel_chat.as_ref().unwrap().get_id(),
|
||||
@@ -853,9 +840,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
println!("Success, streaming can be stopped.");
|
||||
}
|
||||
}
|
||||
"dellocations" => {
|
||||
location::delete_all(&context).await?;
|
||||
}
|
||||
"send" => {
|
||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||
ensure!(!arg1.is_empty(), "No message text given.");
|
||||
@@ -1219,42 +1203,21 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
fs::write(&file, svg).await?;
|
||||
println!("{file:#?} written.");
|
||||
}
|
||||
"providerinfo" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <addr> missing.");
|
||||
match provider::get_provider_info(arg1) {
|
||||
Some(info) => {
|
||||
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);
|
||||
println!("overview_page: {}", info.overview_page);
|
||||
for server in info.server.iter() {
|
||||
println!("server: {}:{}", server.hostname, server.port,);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
println!("No information for provider belonging to {arg1} found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
"fileinfo" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <file> missing.");
|
||||
|
||||
if let Ok(buf) = read_file(&context, Path::new(arg1)).await {
|
||||
let (width, height) = get_filemeta(&buf)?;
|
||||
println!("width={width}, height={height}");
|
||||
} else {
|
||||
let Ok(buf) = read_file(&context, Path::new(arg1)).await else {
|
||||
bail!("Command failed.");
|
||||
}
|
||||
};
|
||||
let (width, height) = get_filemeta(&buf)?;
|
||||
println!("width={width}, height={height}");
|
||||
}
|
||||
"estimatedeletion" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <seconds> missing");
|
||||
let seconds = arg1.parse()?;
|
||||
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} seconds:\non device: {device_cnt}\non server: {server_cnt}"
|
||||
);
|
||||
let from_server = false;
|
||||
let device_cnt = message::estimate_deletion_cnt(&context, from_server, seconds).await?;
|
||||
println!("estimated count of messages older than {seconds} seconds: {device_cnt}");
|
||||
}
|
||||
"" => (),
|
||||
_ => bail!("Unknown command: \"{arg0}\" type ? for help."),
|
||||
|
||||
@@ -9,14 +9,12 @@ extern crate deltachat;
|
||||
|
||||
use std::borrow::Cow::{self, Borrowed, Owned};
|
||||
|
||||
use anyhow::{bail, Error};
|
||||
use anyhow::{Error, bail};
|
||||
use deltachat::EventType;
|
||||
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 nu_ansi_term::Color;
|
||||
use rustyline::completion::{Completer, FilenameCompleter, Pair};
|
||||
@@ -162,11 +160,10 @@ const IMEX_COMMANDS: [&str; 10] = [
|
||||
"stop",
|
||||
];
|
||||
|
||||
const DB_COMMANDS: [&str; 11] = [
|
||||
const DB_COMMANDS: [&str; 10] = [
|
||||
"info",
|
||||
"set",
|
||||
"get",
|
||||
"oauth2",
|
||||
"configure",
|
||||
"connect",
|
||||
"disconnect",
|
||||
@@ -176,7 +173,7 @@ const DB_COMMANDS: [&str; 11] = [
|
||||
"housekeeping",
|
||||
];
|
||||
|
||||
const CHAT_COMMANDS: [&str; 40] = [
|
||||
const CHAT_COMMANDS: [&str; 39] = [
|
||||
"listchats",
|
||||
"listarchived",
|
||||
"start-realtime",
|
||||
@@ -194,7 +191,6 @@ const CHAT_COMMANDS: [&str; 40] = [
|
||||
"chatinfo",
|
||||
"sendlocations",
|
||||
"setlocation",
|
||||
"dellocations",
|
||||
"getlocations",
|
||||
"send",
|
||||
"send-sync",
|
||||
@@ -241,7 +237,7 @@ const CONTACT_COMMANDS: [&str; 9] = [
|
||||
"import-vcard",
|
||||
"make-vcard",
|
||||
];
|
||||
const MISC_COMMANDS: [&str; 14] = [
|
||||
const MISC_COMMANDS: [&str; 13] = [
|
||||
"getqr",
|
||||
"getqrsvg",
|
||||
"getbadqr",
|
||||
@@ -249,7 +245,6 @@ const MISC_COMMANDS: [&str; 14] = [
|
||||
"joinqr",
|
||||
"setqr",
|
||||
"createqrsvg",
|
||||
"providerinfo",
|
||||
"fileinfo",
|
||||
"estimatedeletion",
|
||||
"clear",
|
||||
@@ -271,10 +266,11 @@ impl Hinter for DcHelper {
|
||||
&CONTACT_COMMANDS[..],
|
||||
&MISC_COMMANDS[..],
|
||||
] {
|
||||
if let Some(entry) = cmds.iter().find(|el| el.starts_with(&line[..pos])) {
|
||||
if *entry != line && *entry != &line[..pos] {
|
||||
return Some(entry[pos..].to_owned());
|
||||
}
|
||||
if let Some(entry) = cmds.iter().find(|el| el.starts_with(&line[..pos]))
|
||||
&& *entry != line
|
||||
&& *entry != &line[..pos]
|
||||
{
|
||||
return Some(entry[pos..].to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -426,19 +422,6 @@ async fn handle_cmd(
|
||||
"configure" => {
|
||||
ctx.configure().await?;
|
||||
}
|
||||
"oauth2" => {
|
||||
if let Some(addr) = ctx.get_config(config::Config::Addr).await? {
|
||||
if let Some(oauth2_url) =
|
||||
get_oauth2_url(&ctx, &addr, "chat.delta:/com.b44t.messenger").await?
|
||||
{
|
||||
println!("Open the following url, set mail_pw to the generated token and server_flags to 2:\n{oauth2_url}");
|
||||
} else {
|
||||
println!("OAuth2 not available for {}.", &addr);
|
||||
}
|
||||
} else {
|
||||
println!("oauth2: set addr first.");
|
||||
}
|
||||
}
|
||||
"clear" => {
|
||||
println!("\n\n\n");
|
||||
print!("\x1b[1;1H\x1b[2J");
|
||||
|
||||
@@ -29,7 +29,7 @@ $ pip install .
|
||||
|
||||
1. Build `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
|
||||
2. Install tox `pip install -U tox`
|
||||
3. Run `CHATMAIL_DOMAIN=nine.testrun.org PATH="../target/debug:$PATH" tox`.
|
||||
3. Run `CHATMAIL_DOMAIN=ci-chatmail.testrun.org PATH="../target/debug:$PATH" tox`.
|
||||
|
||||
Additional arguments to `tox` are passed to pytest, e.g. `tox -- -s` does not capture test output.
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ 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"])
|
||||
logging.info(f"Running deltachat core {system_info['deltachat_core_version']}")
|
||||
|
||||
accounts = deltachat.get_all_accounts()
|
||||
account = accounts[0] if accounts else deltachat.add_account()
|
||||
@@ -21,36 +21,30 @@ def main():
|
||||
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()
|
||||
account.add_or_update_transport({"addr": sys.argv[1], "password": sys.argv[2]})
|
||||
logging.info("Configured")
|
||||
else:
|
||||
logging.info("Account is already configured")
|
||||
deltachat.start_io()
|
||||
|
||||
def process_messages():
|
||||
for message in account.get_next_messages():
|
||||
qr = account.get_qr_code()
|
||||
logging.info(f"Invite link: {qr}")
|
||||
while True:
|
||||
event = account.wait_for_event()
|
||||
if event.kind == EventType.INFO:
|
||||
logging.info(event["msg"])
|
||||
elif event.kind == EventType.WARNING:
|
||||
logging.warning(event["msg"])
|
||||
elif event.kind == EventType.ERROR:
|
||||
logging.error(event["msg"])
|
||||
elif event.kind == EventType.INCOMING_MSG:
|
||||
logging.info("Got an incoming message")
|
||||
message = account.get_message_by_id(event.msg_id)
|
||||
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)
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat-rpc-client"
|
||||
version = "2.48.0-dev"
|
||||
version = "2.60.0-dev"
|
||||
license = "MPL-2.0"
|
||||
description = "Python client for Delta Chat core JSON-RPC interface"
|
||||
classifiers = [
|
||||
|
||||
@@ -157,7 +157,7 @@ def parse_system_add_remove(text: str) -> Optional[Tuple[str, str, str]]:
|
||||
"""
|
||||
# You removed member a@b.
|
||||
# You added member a@b.
|
||||
# Member Me (x@y) removed by a@b.
|
||||
# You were removed by a@b.
|
||||
# Member x@y added by a@b
|
||||
# Member With space (tmp1@x.org) removed by tmp2@x.org.
|
||||
# Member With space (tmp1@x.org) removed by Another member (tmp2@x.org).",
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Optional, Union
|
||||
from warnings import warn
|
||||
|
||||
from ._utils import AttrDict, futuremethod
|
||||
from .chat import Chat
|
||||
@@ -36,6 +36,15 @@ class Account:
|
||||
if event_type is None or next_event.kind == event_type:
|
||||
return next_event
|
||||
|
||||
def wait_for_realtime_data(self, msg_id: int) -> bytes:
|
||||
"""Wait for the next realtime data received for the given webxdc message and return it."""
|
||||
logging.info(f"account {self.id}: waiting for realtime data for msg {msg_id}")
|
||||
while True:
|
||||
event = self.wait_for_event(EventType.WEBXDC_REALTIME_DATA)
|
||||
if event.msg_id == msg_id:
|
||||
logging.info(f"account {self.id}: got realtime data for msg {msg_id}: {event.data[:20]}")
|
||||
return bytes(event.data)
|
||||
|
||||
def clear_all_events(self):
|
||||
"""Remove all queued-up events for a given account.
|
||||
|
||||
@@ -134,6 +143,10 @@ class Account:
|
||||
"""Delete a transport."""
|
||||
self._rpc.delete_transport(self.id, addr)
|
||||
|
||||
def set_transport_unpublished(self, addr: str, unpublished: bool = True):
|
||||
"""Unpublish the transport."""
|
||||
self._rpc.set_transport_unpublished(self.id, addr, unpublished)
|
||||
|
||||
@futuremethod
|
||||
def list_transports(self):
|
||||
"""Return the list of all email accounts that are used as a transport in the current profile."""
|
||||
@@ -183,7 +196,7 @@ class Account:
|
||||
return [Contact(self, contact_id) for contact_id in contact_ids]
|
||||
|
||||
def create_chat(self, account: "Account") -> Chat:
|
||||
"""Create a 1:1 chat with another account."""
|
||||
"""Create a single chat with another account."""
|
||||
return self.create_contact(account).create_chat()
|
||||
|
||||
def get_device_chat(self) -> Chat:
|
||||
@@ -219,7 +232,7 @@ class Account:
|
||||
return [AttrDict(contact=Contact(self, contact["id"]), **contact) for contact in contacts]
|
||||
|
||||
def get_chat_by_contact(self, contact: Union[int, Contact]) -> Optional[Chat]:
|
||||
"""Return 1:1 chat for a contact if it exists."""
|
||||
"""Return single chat for a contact if it exists."""
|
||||
if isinstance(contact, Contact):
|
||||
assert contact.account == self
|
||||
contact_id = contact.id
|
||||
@@ -341,9 +354,6 @@ class Account:
|
||||
because the word "channel" already appears a lot in the code,
|
||||
which would make it hard to grep for it.
|
||||
|
||||
After creation, the chat contains no recipients and is in _unpromoted_ state;
|
||||
see `create_group()` for more information on the unpromoted state.
|
||||
|
||||
Returns the created chat.
|
||||
"""
|
||||
return Chat(self, self._rpc.create_broadcast(self.id, name))
|
||||
@@ -392,8 +402,7 @@ class Account:
|
||||
"""Return the list of fresh messages, newest messages first.
|
||||
|
||||
This call is intended for displaying notifications.
|
||||
If you are writing a bot, use `get_fresh_messages_in_arrival_order()` instead,
|
||||
to process oldest messages first.
|
||||
If you are writing a bot, process "incoming message" events instead.
|
||||
"""
|
||||
fresh_msg_ids = self._rpc.get_fresh_msgs(self.id)
|
||||
return [Message(self, msg_id) for msg_id in fresh_msg_ids]
|
||||
@@ -405,7 +414,15 @@ class Account:
|
||||
|
||||
@futuremethod
|
||||
def wait_next_messages(self) -> list[Message]:
|
||||
"""Wait for new messages and return a list of them."""
|
||||
"""(deprecated) Wait for new messages and return a list of them. Meant for bots.
|
||||
|
||||
Deprecated 2026-04: This returns the message's id as soon as the first part arrives,
|
||||
even if it is not fully downloaded yet.
|
||||
The bot needs to wait for the message to be fully downloaded.
|
||||
Since this is usually not the desired behavior,
|
||||
bots should instead use the `EventType.INCOMING_MSG`
|
||||
event for getting notified about new messages.
|
||||
"""
|
||||
next_msg_ids = yield self._rpc.wait_next_msgs.future(self.id)
|
||||
return [Message(self, msg_id) for msg_id in next_msg_ids]
|
||||
|
||||
@@ -455,16 +472,6 @@ class Account:
|
||||
"""Wait for reaction change event."""
|
||||
return self.wait_for_event(EventType.REACTIONS_CHANGED)
|
||||
|
||||
def get_fresh_messages_in_arrival_order(self) -> list[Message]:
|
||||
"""Return fresh messages list sorted in the order of their arrival, with ascending IDs."""
|
||||
warn(
|
||||
"get_fresh_messages_in_arrival_order is deprecated, use get_next_messages instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
fresh_msg_ids = sorted(self._rpc.get_fresh_msgs(self.id))
|
||||
return [Message(self, msg_id) for msg_id in fresh_msg_ids]
|
||||
|
||||
def export_backup(self, path, passphrase: str = "") -> None:
|
||||
"""Export backup."""
|
||||
self._rpc.export_backup(self.id, str(path), passphrase)
|
||||
@@ -487,3 +494,7 @@ class Account:
|
||||
"""Return ICE servers for WebRTC configuration."""
|
||||
ice_servers_json = self._rpc.ice_servers(self.id)
|
||||
return json.loads(ice_servers_json)
|
||||
|
||||
def is_sending_locations(self) -> bool:
|
||||
"""Return True if sending locations to any chat."""
|
||||
return self._rpc.is_sending_locations(self.id)
|
||||
|
||||
@@ -164,7 +164,7 @@ class Chat:
|
||||
return Message(self.account, msg_id)
|
||||
|
||||
def send_sticker(self, path: str) -> Message:
|
||||
"""Send an sticker and return the resulting Message instance."""
|
||||
"""Deprecated as of 2026-04; use `send_message` with `Viewtype.STICKER` instead."""
|
||||
msg_id = self._rpc.send_sticker(self.account.id, self.id, path)
|
||||
return Message(self.account, msg_id)
|
||||
|
||||
@@ -206,9 +206,9 @@ class Chat:
|
||||
snapshot["message"] = Message(self.account, snapshot.id)
|
||||
return snapshot
|
||||
|
||||
def get_messages(self, info_only: bool = False, add_daymarker: bool = False) -> list[Message]:
|
||||
def get_messages(self, add_daymarker: bool = False) -> list[Message]:
|
||||
"""Get the list of messages in this chat."""
|
||||
msgs = self._rpc.get_message_ids(self.account.id, self.id, info_only, add_daymarker)
|
||||
msgs = self._rpc.get_message_ids(self.account.id, self.id, False, add_daymarker)
|
||||
return [Message(self.account, msg_id) for msg_id in msgs]
|
||||
|
||||
def get_fresh_message_count(self) -> int:
|
||||
@@ -252,7 +252,7 @@ class Chat:
|
||||
def get_contacts(self) -> list[Contact]:
|
||||
"""Get the contacts belonging to this chat.
|
||||
|
||||
For single/direct chats self-address is not included.
|
||||
For single chats self-address is not included.
|
||||
"""
|
||||
contacts = self._rpc.get_chat_contacts(self.account.id, self.id)
|
||||
return [Contact(self.account, contact_id) for contact_id in contacts]
|
||||
@@ -277,6 +277,16 @@ class Chat:
|
||||
"""Remove profile image of this chat."""
|
||||
self._rpc.set_chat_profile_image(self.account.id, self.id, None)
|
||||
|
||||
def send_locations(self, seconds) -> None:
|
||||
"""Enable location streaming in the chat for the given number of seconds.
|
||||
|
||||
Pass 0 to disable location streaming."""
|
||||
self._rpc.send_locations_to_chat(self.account.id, self.id, seconds)
|
||||
|
||||
def is_sending_locations(self) -> bool:
|
||||
"""Return True if sending locations to this chat."""
|
||||
return self._rpc.is_sending_locations_to_chat(self.account.id, self.id)
|
||||
|
||||
def get_locations(
|
||||
self,
|
||||
contact: Optional[Contact] = None,
|
||||
|
||||
@@ -54,6 +54,7 @@ class EventType(str, Enum):
|
||||
MSG_DELIVERED = "MsgDelivered"
|
||||
MSG_FAILED = "MsgFailed"
|
||||
MSG_READ = "MsgRead"
|
||||
MSG_READ_COUNT_CHANGED = "MsgReadCountChanged"
|
||||
MSG_DELETED = "MsgDeleted"
|
||||
CHAT_MODIFIED = "ChatModified"
|
||||
CHAT_DELETED = "ChatDeleted"
|
||||
@@ -96,7 +97,7 @@ class ChatType(str, Enum):
|
||||
"""Chat type."""
|
||||
|
||||
SINGLE = "Single"
|
||||
"""1:1 chat, i.e. a direct chat with a single contact"""
|
||||
"""Single chat (a chat with a with a single contact)"""
|
||||
|
||||
GROUP = "Group"
|
||||
|
||||
@@ -190,7 +191,6 @@ class MessageState(IntEnum):
|
||||
IN_FRESH = 10
|
||||
IN_NOTICED = 13
|
||||
IN_SEEN = 16
|
||||
OUT_PREPARING = 18
|
||||
OUT_DRAFT = 19
|
||||
OUT_PENDING = 20
|
||||
OUT_FAILED = 24
|
||||
@@ -231,14 +231,6 @@ class KeyGenType(IntEnum):
|
||||
RSA4096 = 3
|
||||
|
||||
|
||||
# "Lp" means "login parameters"
|
||||
class LpAuthFlag(IntEnum):
|
||||
"""Authorization flags."""
|
||||
|
||||
OAUTH2 = 0x2
|
||||
NORMAL = 0x4
|
||||
|
||||
|
||||
class MediaQuality(IntEnum):
|
||||
"""Media quality setting."""
|
||||
|
||||
@@ -254,14 +246,6 @@ class ProviderStatus(IntEnum):
|
||||
BROKEN = 3
|
||||
|
||||
|
||||
class PushNotifyState(IntEnum):
|
||||
"""Push notifications state."""
|
||||
|
||||
NOT_CONNECTED = 0
|
||||
HEARTBEAT = 1
|
||||
CONNECTED = 2
|
||||
|
||||
|
||||
class ShowEmails(IntEnum):
|
||||
"""Show emails mode."""
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ class Contact:
|
||||
return snapshot
|
||||
|
||||
def create_chat(self) -> "Chat":
|
||||
"""Create or get an existing 1:1 chat for this contact."""
|
||||
"""Create or get an existing single chat for this contact."""
|
||||
from .chat import Chat
|
||||
|
||||
return Chat(
|
||||
|
||||
@@ -59,3 +59,11 @@ class DeltaChat:
|
||||
def set_translations(self, translations: dict[str, str]) -> None:
|
||||
"""Set stock translation strings."""
|
||||
self.rpc.set_stock_strings(translations)
|
||||
|
||||
def set_location(self, latitude, longitude, accuracy) -> bool:
|
||||
"""Set location, return True if location streaming should continue."""
|
||||
return self.rpc.set_location(latitude, longitude, accuracy)
|
||||
|
||||
def stop_sending_locations(self) -> None:
|
||||
"""Stop sending locations to all chats."""
|
||||
return self.rpc.stop_sending_locations()
|
||||
|
||||
@@ -25,7 +25,14 @@ class Message:
|
||||
return self.account._rpc
|
||||
|
||||
def send_reaction(self, *reaction: str) -> "Message":
|
||||
"""Send a reaction to this message."""
|
||||
"""
|
||||
Sends a reaction to message.
|
||||
|
||||
A reaction is a string that represents an emoji.
|
||||
You can call this function again to change the emoji;
|
||||
the last sent reaction overrides all previously sent reactions.
|
||||
It is possible to remove the reaction by sending an empty string.
|
||||
"""
|
||||
msg_id = self._rpc.send_reaction(self.account.id, self.id, reaction)
|
||||
return Message(self.account, msg_id)
|
||||
|
||||
|
||||
@@ -7,12 +7,13 @@ import os
|
||||
import pathlib
|
||||
import platform
|
||||
import random
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import AsyncGenerator, Optional
|
||||
import time
|
||||
import urllib.parse
|
||||
from typing import Iterator, Optional
|
||||
|
||||
import execnet
|
||||
import py
|
||||
import pytest
|
||||
|
||||
from . import Account, AttrDict, Bot, Chat, Client, DeltaChat, EventType, Message
|
||||
@@ -26,19 +27,42 @@ Currently this is "Messages are end-to-end encrypted."
|
||||
"""
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
# Run only in the xdist controller, before the workers exist.
|
||||
if not hasattr(config, "workerinput"):
|
||||
domain = os.environ.get("CHATMAIL_DOMAIN")
|
||||
if domain:
|
||||
check_chatmail_domain_and_warmup_dns_cache(domain)
|
||||
|
||||
|
||||
def check_chatmail_domain_and_warmup_dns_cache(domain):
|
||||
for i in range(6):
|
||||
try:
|
||||
socket.getaddrinfo(domain, 443)
|
||||
return
|
||||
except socket.gaierror as e:
|
||||
error = e
|
||||
logging.warning(f"DNS resolution of {domain} failed (attempt {i}): {e}")
|
||||
time.sleep(10)
|
||||
|
||||
pytest.exit(f"cannot resolve chatmail relay domain {domain}: {error}")
|
||||
|
||||
|
||||
def pytest_report_header():
|
||||
headers = [f"CHATMAIL_DOMAIN: {os.environ.get('CHATMAIL_DOMAIN')}"]
|
||||
for base in os.get_exec_path():
|
||||
fn = pathlib.Path(base).joinpath(base, "deltachat-rpc-server")
|
||||
if fn.exists():
|
||||
proc = subprocess.Popen([str(fn), "--version"], stderr=subprocess.PIPE)
|
||||
proc.wait()
|
||||
version = proc.stderr.read().decode().strip()
|
||||
return f"deltachat-rpc-server: {fn} [{version}]"
|
||||
headers.append(f"RPC-SERVER: {fn} [{version}]")
|
||||
break
|
||||
|
||||
return None
|
||||
return headers
|
||||
|
||||
|
||||
class ACFactory:
|
||||
class RPCAccountFactory:
|
||||
"""Test account factory."""
|
||||
|
||||
def __init__(self, deltachat: DeltaChat) -> None:
|
||||
@@ -104,7 +128,7 @@ class ACFactory:
|
||||
return ac_clone
|
||||
|
||||
def get_accepted_chat(self, ac1: Account, ac2: Account) -> Chat:
|
||||
"""Create a new 1:1 chat between ac1 and ac2 accepted on both sides.
|
||||
"""Create a new single chat between ac1 and ac2 accepted on both sides.
|
||||
|
||||
Returned chat is a chat with ac2 from ac1 point of view.
|
||||
"""
|
||||
@@ -151,7 +175,7 @@ class ACFactory:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def rpc(tmp_path) -> AsyncGenerator:
|
||||
def rpc(tmp_path) -> Iterator[Rpc]:
|
||||
"""RPC client fixture."""
|
||||
rpc_server = Rpc(accounts_dir=str(tmp_path / "accounts"))
|
||||
with rpc_server:
|
||||
@@ -165,20 +189,20 @@ def dc(rpc) -> DeltaChat:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def acfactory(dc) -> AsyncGenerator:
|
||||
def acf(dc) -> RPCAccountFactory:
|
||||
"""Return account factory fixture."""
|
||||
return ACFactory(dc)
|
||||
return RPCAccountFactory(dc)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def data():
|
||||
def rpcdata():
|
||||
"""Test data."""
|
||||
|
||||
class Data:
|
||||
def __init__(self) -> None:
|
||||
for path in reversed(py.path.local(__file__).parts()):
|
||||
datadir = path.join("test-data")
|
||||
if datadir.isdir():
|
||||
for path in pathlib.Path(__file__).parents:
|
||||
datadir = path / "test-data"
|
||||
if datadir.is_dir():
|
||||
self.path = datadir
|
||||
return
|
||||
raise Exception("Data path cannot be found")
|
||||
@@ -268,26 +292,33 @@ def get_core_python_env(tmp_path_factory):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def alice_and_remote_bob(tmp_path, acfactory, get_core_python_env):
|
||||
def alice_and_remote_bob(tmp_path, acf, get_core_python_env):
|
||||
"""return local Alice account, a contact to bob, and a remote 'eval' function for bob.
|
||||
|
||||
The 'eval' function allows to remote-execute arbitrary expressions
|
||||
that can use the `bob` online account, and the `bob_contact_alice`.
|
||||
"""
|
||||
from execnet import makegateway
|
||||
|
||||
def factory(core_version):
|
||||
python, rpc_server_path = get_core_python_env(core_version)
|
||||
gw = execnet.makegateway(f"popen//python={python}")
|
||||
gw = makegateway(f"popen//python={python}")
|
||||
|
||||
accounts_dir = str(tmp_path.joinpath("account1_venv1"))
|
||||
channel = gw.remote_exec(remote_bob_loop)
|
||||
cm = os.environ.get("CHATMAIL_DOMAIN")
|
||||
|
||||
# old cores need "ic=3" to accept
|
||||
# the self-signed cert of an underscore domain
|
||||
addr, password = acf.get_credentials()
|
||||
dclogin_qr = f"dclogin://{urllib.parse.quote(addr, safe='@')}?p={urllib.parse.quote(password)}&v=1"
|
||||
if os.environ["CHATMAIL_DOMAIN"].startswith("_"):
|
||||
dclogin_qr += "&ic=3"
|
||||
|
||||
# trigger getting an online account on bob's side
|
||||
channel.send((accounts_dir, str(rpc_server_path), cm))
|
||||
channel.send((accounts_dir, str(rpc_server_path), dclogin_qr))
|
||||
|
||||
# meanwhile get a local alice account
|
||||
alice = acfactory.get_online_account()
|
||||
alice = acf.get_online_account()
|
||||
channel.send(alice.self_contact.make_vcard())
|
||||
|
||||
# wait for bob to have started
|
||||
@@ -316,10 +347,8 @@ def remote_bob_loop(channel):
|
||||
import os
|
||||
|
||||
from deltachat_rpc_client import DeltaChat, Rpc
|
||||
from deltachat_rpc_client.pytestplugin import ACFactory
|
||||
|
||||
accounts_dir, rpc_server_path, chatmail_domain = channel.receive()
|
||||
os.environ["CHATMAIL_DOMAIN"] = chatmail_domain
|
||||
accounts_dir, rpc_server_path, dclogin_qr = channel.receive()
|
||||
|
||||
# older core versions don't support specifying rpc_server_path
|
||||
# so we can't just pass `rpc_server_path` argument to Rpc constructor
|
||||
@@ -330,8 +359,13 @@ def remote_bob_loop(channel):
|
||||
with rpc:
|
||||
dc = DeltaChat(rpc)
|
||||
channel.send(dc.rpc.get_system_info()["deltachat_core_version"])
|
||||
acfactory = ACFactory(dc)
|
||||
bob = acfactory.get_online_account()
|
||||
|
||||
# RPCAccountFactory would configure from a "dcaccount" QR,
|
||||
# which old cores cannot use on underscore domains
|
||||
bob = dc.add_account()
|
||||
bob.add_transport_from_qr(dclogin_qr)
|
||||
bob.bring_online()
|
||||
|
||||
alice_vcard = channel.receive()
|
||||
[alice_contact] = bob.import_vcard(alice_vcard)
|
||||
ns = {"bob": bob, "bob_contact_alice": alice_contact}
|
||||
|
||||
@@ -37,7 +37,11 @@ class DirectImap:
|
||||
host = user.rsplit("@")[-1]
|
||||
pw = self.account.get_config("mail_pw")
|
||||
|
||||
self.conn = MailBox(host, port, ssl_context=ssl.create_default_context())
|
||||
ssl_context = ssl.create_default_context()
|
||||
if host.startswith("_"):
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
self.conn = MailBox(host, port, ssl_context=ssl_context)
|
||||
self.conn.login(user, pw)
|
||||
|
||||
self.select_folder("INBOX")
|
||||
|
||||
@@ -5,16 +5,16 @@ from typing import TYPE_CHECKING
|
||||
from deltachat_rpc_client import EventType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from deltachat_rpc_client.pytestplugin import ACFactory
|
||||
from deltachat_rpc_client.pytestplugin import RPCAccountFactory
|
||||
|
||||
|
||||
def test_event_on_configuration(acfactory: ACFactory) -> None:
|
||||
def test_event_on_configuration(acf: RPCAccountFactory) -> None:
|
||||
"""
|
||||
Test if ACCOUNTS_ITEM_CHANGED event is emitted on configure
|
||||
"""
|
||||
|
||||
addr, password = acfactory.get_credentials()
|
||||
account = acfactory.get_unconfigured_account()
|
||||
addr, password = acf.get_credentials()
|
||||
account = acf.get_unconfigured_account()
|
||||
account.clear_all_events()
|
||||
assert not account.is_configured()
|
||||
future = account.add_or_update_transport.future({"addr": addr, "password": password})
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from deltachat_rpc_client import EventType, Message
|
||||
|
||||
|
||||
def test_calls(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_calls(acf) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
place_call_info = "offer"
|
||||
accept_call_info = "answer"
|
||||
@@ -35,14 +35,14 @@ def test_calls(acfactory) -> None:
|
||||
assert incoming_call_message.get_call_info().state.kind == "Completed"
|
||||
|
||||
|
||||
def test_video_call(acfactory) -> None:
|
||||
def test_video_call(acf) -> None:
|
||||
# Example from <https://datatracker.ietf.org/doc/rfc9143/>
|
||||
# with `s= ` replaced with `s=-`.
|
||||
#
|
||||
# `s=` cannot be empty according to RFC 3264,
|
||||
# so it is more clear as `s=-`.
|
||||
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
bob.create_chat(alice) # Accept the chat so incoming call causes a notification.
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
@@ -57,8 +57,8 @@ def test_video_call(acfactory) -> None:
|
||||
assert incoming_call_message.get_call_info().has_video
|
||||
|
||||
|
||||
def test_audio_call(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_audio_call(acf) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
bob.create_chat(alice) # Accept the chat so incoming call causes a notification.
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
@@ -73,15 +73,15 @@ def test_audio_call(acfactory) -> None:
|
||||
assert not incoming_call_message.get_call_info().has_video
|
||||
|
||||
|
||||
def test_ice_servers(acfactory) -> None:
|
||||
alice = acfactory.get_online_account()
|
||||
def test_ice_servers(acf) -> None:
|
||||
alice = acf.get_online_account()
|
||||
|
||||
ice_servers = alice.ice_servers()
|
||||
assert len(ice_servers) == 1
|
||||
|
||||
|
||||
def test_no_contact_request_call(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_no_contact_request_call(acf) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
alice_chat_bob = alice.create_chat(bob)
|
||||
alice_chat_bob.place_outgoing_call("offer", has_video_initially=True)
|
||||
@@ -101,8 +101,8 @@ def test_no_contact_request_call(acfactory) -> None:
|
||||
break
|
||||
|
||||
|
||||
def test_who_can_call_me_nobody(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_who_can_call_me_nobody(acf) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
# Bob sets "who can call me" to "nobody" (2)
|
||||
bob.set_config("who_can_call_me", "2")
|
||||
@@ -128,9 +128,9 @@ def test_who_can_call_me_nobody(acfactory) -> None:
|
||||
break
|
||||
|
||||
|
||||
def test_who_can_call_me_everybody(acfactory) -> None:
|
||||
def test_who_can_call_me_everybody(acf) -> None:
|
||||
"""Test that if "who can call me" setting is set to "everybody", calls arrive even in contact request chats."""
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
# Bob sets "who can call me" to "nobody" (0)
|
||||
bob.set_config("who_can_call_me", "0")
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
|
||||
from deltachat_rpc_client import Account, EventType, const
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from deltachat_rpc_client.pytestplugin import ACFactory
|
||||
from deltachat_rpc_client.pytestplugin import RPCAccountFactory
|
||||
|
||||
|
||||
def wait_for_chatlist_and_specific_item(account, chat_id):
|
||||
@@ -40,11 +40,11 @@ def wait_for_chatlist(account):
|
||||
break
|
||||
|
||||
|
||||
def test_delivery_status(acfactory: ACFactory) -> None:
|
||||
def test_delivery_status(acf: RPCAccountFactory) -> None:
|
||||
"""
|
||||
Test change status on chatlistitem when status changes (delivered, read)
|
||||
"""
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
alice_chat_bob = alice_contact_bob.create_chat()
|
||||
@@ -82,11 +82,12 @@ def test_delivery_status(acfactory: ACFactory) -> None:
|
||||
assert chat_item["summaryStatus"] == const.MessageState.OUT_MDN_RCVD
|
||||
|
||||
|
||||
def test_delivery_status_failed(acfactory: ACFactory) -> None:
|
||||
def test_delivery_status_failed(acf: RPCAccountFactory) -> None:
|
||||
"""
|
||||
Test change status on chatlistitem when status changes failed
|
||||
"""
|
||||
(alice,) = acfactory.get_online_accounts(1)
|
||||
(alice,) = acf.get_online_accounts(1)
|
||||
alice.set_config("force_encryption", "0")
|
||||
|
||||
invalid_contact = alice.create_contact("example@example.com", "invalid address")
|
||||
invalid_chat = alice.get_chat_by_id(alice._rpc.create_chat_by_contact_id(alice.id, invalid_contact.id))
|
||||
@@ -109,12 +110,12 @@ def test_delivery_status_failed(acfactory: ACFactory) -> None:
|
||||
assert failing_message.get_snapshot().state == const.MessageState.OUT_FAILED
|
||||
|
||||
|
||||
def test_download_on_demand(acfactory: ACFactory) -> None:
|
||||
def test_download_on_demand(acf: RPCAccountFactory, rpcdata) -> None:
|
||||
"""
|
||||
Test if download on demand emits chatlist update events.
|
||||
This is only needed for last message in chat, but finding that out is too expensive, so it's always emitted
|
||||
"""
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
alice_chat_bob = alice_contact_bob.create_chat()
|
||||
@@ -127,7 +128,7 @@ def test_download_on_demand(acfactory: ACFactory) -> None:
|
||||
msg.get_snapshot().chat.accept()
|
||||
bob.get_chat_by_id(chat_id).send_message(
|
||||
"Hello World, this message is bigger than 5 bytes",
|
||||
file="../test-data/image/screenshot.jpg",
|
||||
file=rpcdata.get_path("image/screenshot.jpg"),
|
||||
)
|
||||
|
||||
message = alice.wait_for_incoming_msg()
|
||||
@@ -143,8 +144,8 @@ def test_download_on_demand(acfactory: ACFactory) -> None:
|
||||
wait_for_chatlist_specific_item(alice, chat_id)
|
||||
|
||||
|
||||
def get_multi_account_test_setup(acfactory: ACFactory) -> [Account, Account, Account]:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def get_multi_account_test_setup(acf: RPCAccountFactory) -> [Account, Account, Account]:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
alice_chat_bob = alice_contact_bob.create_chat()
|
||||
@@ -160,12 +161,12 @@ def get_multi_account_test_setup(acfactory: ACFactory) -> [Account, Account, Acc
|
||||
return [alice, alice_second_device, bob, alice_chat_bob]
|
||||
|
||||
|
||||
def test_imap_sync_seen_msgs(acfactory: ACFactory) -> None:
|
||||
def test_imap_sync_seen_msgs(acf: RPCAccountFactory) -> None:
|
||||
"""
|
||||
Test that chatlist changed events are emitted for the second device
|
||||
when the message is marked as read on the first device
|
||||
"""
|
||||
alice, alice_second_device, bob, alice_chat_bob = get_multi_account_test_setup(acfactory)
|
||||
alice, alice_second_device, bob, alice_chat_bob = get_multi_account_test_setup(acf)
|
||||
|
||||
bob.create_chat(alice)
|
||||
|
||||
@@ -190,11 +191,11 @@ def test_imap_sync_seen_msgs(acfactory: ACFactory) -> None:
|
||||
wait_for_chatlist_specific_item(alice, alice_chat_bob.id)
|
||||
|
||||
|
||||
def test_multidevice_sync_chat(acfactory: ACFactory) -> None:
|
||||
def test_multidevice_sync_chat(acf: RPCAccountFactory) -> None:
|
||||
"""
|
||||
Test multidevice sync: syncing chat visibility and muting across multiple devices
|
||||
"""
|
||||
alice, alice_second_device, bob, alice_chat_bob = get_multi_account_test_setup(acfactory)
|
||||
alice, alice_second_device, bob, alice_chat_bob = get_multi_account_test_setup(acf)
|
||||
|
||||
alice_chat_bob.archive()
|
||||
wait_for_chatlist_specific_item(alice_second_device, alice_chat_bob.id)
|
||||
|
||||
@@ -16,7 +16,7 @@ def test_install_venv_and_use_other_core(tmp_path, get_core_python_env):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("version", ["2.24.0"])
|
||||
def test_qr_setup_contact(alice_and_remote_bob, version) -> None:
|
||||
def test_qr_setup_contact(acf, alice_and_remote_bob, version) -> None:
|
||||
"""Test other-core Bob profile can do securejoin with Alice on current core."""
|
||||
alice, alice_contact_bob, remote_eval = alice_and_remote_bob(version)
|
||||
|
||||
@@ -33,10 +33,19 @@ def test_qr_setup_contact(alice_and_remote_bob, version) -> None:
|
||||
# Test that Bob verified Alice's profile.
|
||||
assert remote_eval("bob_contact_alice.get_snapshot().is_verified")
|
||||
|
||||
# Test that Bob can also scan a QR code
|
||||
# of Alice for which the key is not known yet.
|
||||
# For the test above Bob already knew the key from a vCard.
|
||||
alice2 = acf.get_online_account()
|
||||
qr_code = alice2.get_qr_code()
|
||||
remote_eval(f"bob.secure_join({qr_code!r})")
|
||||
remote_eval("bob.wait_for_securejoin_joiner_success()")
|
||||
alice2.wait_for_securejoin_inviter_success()
|
||||
|
||||
|
||||
def test_send_and_receive_message(alice_and_remote_bob) -> None:
|
||||
"""Test other-core Bob profile can send a message to Alice on current core."""
|
||||
alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.20.0")
|
||||
alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.23.0")
|
||||
|
||||
remote_eval("bob_contact_alice.create_chat().send_text('hello')")
|
||||
|
||||
@@ -44,13 +53,13 @@ def test_send_and_receive_message(alice_and_remote_bob) -> None:
|
||||
assert msg.get_snapshot().text == "hello"
|
||||
|
||||
|
||||
def test_second_device(acfactory, alice_and_remote_bob) -> None:
|
||||
def test_second_device(acf, alice_and_remote_bob) -> None:
|
||||
"""Test setting up current version as a second device for old version."""
|
||||
_alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.20.0")
|
||||
_alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.23.0")
|
||||
|
||||
remote_eval("locals().setdefault('future', bob._rpc.provide_backup.future(bob.id))")
|
||||
qr = remote_eval("bob._rpc.get_backup_qr(bob.id)")
|
||||
new_account = acfactory.get_unconfigured_account()
|
||||
new_account = acf.get_unconfigured_account()
|
||||
new_account._rpc.get_backup(new_account.id, qr)
|
||||
remote_eval("locals()['future']()")
|
||||
|
||||
|
||||
@@ -1,104 +1,29 @@
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
|
||||
import pytest
|
||||
from imap_tools import AND, U
|
||||
|
||||
from deltachat_rpc_client import Contact, EventType, Message
|
||||
from deltachat_rpc_client import EventType
|
||||
|
||||
|
||||
def test_move_works(acfactory, direct_imap):
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
ac2_direct_imap = direct_imap(ac2)
|
||||
ac2_direct_imap.create_folder("DeltaChat")
|
||||
ac2.set_config("mvbox_move", "1")
|
||||
ac2.bring_online()
|
||||
|
||||
chat = ac1.create_chat(ac2)
|
||||
chat.send_text("message1")
|
||||
|
||||
# Message is moved to the movebox
|
||||
ac2.wait_for_event(EventType.IMAP_MESSAGE_MOVED)
|
||||
|
||||
# Message is downloaded
|
||||
msg = ac2.wait_for_incoming_msg().get_snapshot()
|
||||
assert msg.text == "message1"
|
||||
|
||||
|
||||
def test_reactions_for_a_reordering_move(acfactory, direct_imap):
|
||||
"""When a batch of messages is moved from Inbox to DeltaChat folder with a single MOVE command,
|
||||
their UIDs may be reordered (e.g. Gmail is known for that) which led to that messages were
|
||||
processed by receive_imf in the wrong order, and, particularly, reactions were processed before
|
||||
messages they refer to and thus dropped.
|
||||
"""
|
||||
(ac1,) = acfactory.get_online_accounts(1)
|
||||
|
||||
addr, password = acfactory.get_credentials()
|
||||
ac2 = acfactory.get_unconfigured_account()
|
||||
ac2.add_or_update_transport({"addr": addr, "password": password})
|
||||
ac2_direct_imap = direct_imap(ac2)
|
||||
ac2_direct_imap.create_folder("DeltaChat")
|
||||
ac2.set_config("mvbox_move", "1")
|
||||
assert ac2.is_configured()
|
||||
|
||||
ac2.bring_online()
|
||||
chat1 = acfactory.get_accepted_chat(ac1, ac2)
|
||||
ac2.stop_io()
|
||||
|
||||
logging.info("sending message + reaction from ac1 to ac2")
|
||||
msg1 = chat1.send_text("hi")
|
||||
msg1.wait_until_delivered()
|
||||
# It's is sad, but messages must differ in their INTERNALDATEs to be processed in the correct
|
||||
# order by DC, and most (if not all) mail servers provide only seconds precision.
|
||||
time.sleep(1.1)
|
||||
react_str = "\N{THUMBS UP SIGN}"
|
||||
msg1.send_reaction(react_str).wait_until_delivered()
|
||||
|
||||
logging.info("moving messages to ac2's DeltaChat folder in the reverse order")
|
||||
ac2_direct_imap = direct_imap(ac2)
|
||||
ac2_direct_imap.connect()
|
||||
for uid in sorted([m.uid for m in ac2_direct_imap.get_all_messages()], reverse=True):
|
||||
ac2_direct_imap.conn.move(uid, "DeltaChat")
|
||||
|
||||
logging.info("receiving messages by ac2")
|
||||
ac2.start_io()
|
||||
msg2 = Message(ac2, ac2.wait_for_reactions_changed().msg_id)
|
||||
assert msg2.get_snapshot().text == msg1.get_snapshot().text
|
||||
reactions = msg2.get_reactions()
|
||||
contacts = [Contact(ac2, int(i)) for i in reactions.reactions_by_contact]
|
||||
assert len(contacts) == 1
|
||||
assert contacts[0].get_snapshot().address == ac1.get_config("addr")
|
||||
assert list(reactions.reactions_by_contact.values())[0] == [react_str]
|
||||
|
||||
|
||||
def test_move_works_on_self_sent(acfactory, direct_imap):
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
|
||||
# Create and enable movebox.
|
||||
ac1_direct_imap = direct_imap(ac1)
|
||||
ac1_direct_imap.create_folder("DeltaChat")
|
||||
ac1.set_config("mvbox_move", "1")
|
||||
ac1.set_config("bcc_self", "1")
|
||||
ac1.bring_online()
|
||||
|
||||
chat = ac1.create_chat(ac2)
|
||||
chat.send_text("message1")
|
||||
ac1.wait_for_event(EventType.IMAP_MESSAGE_MOVED)
|
||||
chat.send_text("message2")
|
||||
ac1.wait_for_event(EventType.IMAP_MESSAGE_MOVED)
|
||||
chat.send_text("message3")
|
||||
ac1.wait_for_event(EventType.IMAP_MESSAGE_MOVED)
|
||||
|
||||
|
||||
def test_moved_markseen(acfactory, direct_imap):
|
||||
def test_moved_markseen(acf, direct_imap, log):
|
||||
"""Test that message already moved to DeltaChat folder is marked as seen."""
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
ac1 = acf.get_online_account()
|
||||
|
||||
addr, password = acf.get_credentials()
|
||||
ac2 = acf.get_unconfigured_account()
|
||||
ac2.add_or_update_transport({"addr": addr, "password": password})
|
||||
ac2.bring_online()
|
||||
|
||||
# Make sure that messages are not immediately auto-deleted on the server:
|
||||
ac1.set_config("bcc_self", "1")
|
||||
ac2.set_config("bcc_self", "1")
|
||||
|
||||
log.section("ac2: creating DeltaChat folder")
|
||||
ac2_direct_imap = direct_imap(ac2)
|
||||
ac2_direct_imap.create_folder("DeltaChat")
|
||||
ac2.set_config("mvbox_move", "1")
|
||||
ac2.set_config("delete_server_after", "0")
|
||||
ac2.set_config("sync_msgs", "0") # Do not send a sync message when accepting a contact request.
|
||||
|
||||
ac2.add_or_update_transport({"addr": addr, "password": password, "imapFolder": "DeltaChat"})
|
||||
ac2.bring_online()
|
||||
|
||||
ac2.stop_io()
|
||||
@@ -108,6 +33,7 @@ def test_moved_markseen(acfactory, direct_imap):
|
||||
idle2.wait_for_new_message()
|
||||
|
||||
# Emulate moving of the message to DeltaChat folder by Sieve rule.
|
||||
log.section("ac2: moving message into DeltaChat folder")
|
||||
ac2_direct_imap.conn.move(["*"], "DeltaChat")
|
||||
ac2_direct_imap.select_folder("DeltaChat")
|
||||
assert len(list(ac2_direct_imap.conn.fetch("*", mark_seen=False))) == 1
|
||||
@@ -131,58 +57,51 @@ def test_moved_markseen(acfactory, direct_imap):
|
||||
assert len(list(ac2_direct_imap.conn.fetch(AND(seen=True, uid=U(1, "*")), mark_seen=False))) == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mvbox_move", [True, False])
|
||||
def test_markseen_message_and_mdn(acfactory, direct_imap, mvbox_move):
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
def test_markseen_message_and_mdn(acf, direct_imap):
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
|
||||
for ac in ac1, ac2:
|
||||
ac.set_config("delete_server_after", "0")
|
||||
if mvbox_move:
|
||||
ac_direct_imap = direct_imap(ac)
|
||||
ac_direct_imap.create_folder("DeltaChat")
|
||||
ac.set_config("mvbox_move", "1")
|
||||
ac.bring_online()
|
||||
# Make sure that messages are not immediately auto-deleted on the server:
|
||||
ac1.set_config("bcc_self", "1")
|
||||
ac2.set_config("bcc_self", "1")
|
||||
|
||||
# Do not send BCC to self, we only want to test MDN on ac1.
|
||||
ac1.set_config("bcc_self", "0")
|
||||
|
||||
acfactory.get_accepted_chat(ac1, ac2).send_text("hi")
|
||||
acf.get_accepted_chat(ac1, ac2).send_text("hi")
|
||||
msg = ac2.wait_for_incoming_msg()
|
||||
msg.mark_seen()
|
||||
|
||||
if mvbox_move:
|
||||
rex = re.compile("Marked messages [0-9]+ in folder DeltaChat as seen.")
|
||||
else:
|
||||
rex = re.compile("Marked messages [0-9]+ in folder INBOX as seen.")
|
||||
rex = re.compile("Marked messages ([0-9,:]+) in folder INBOX as seen.")
|
||||
|
||||
# Each profile flags two messages but the logged UID set
|
||||
# covers a varying number of them, so just count UIDs mentioned.
|
||||
# We are not processing UID ranges, here we just care for two UIDs.
|
||||
for ac in ac1, ac2:
|
||||
while True:
|
||||
uids = set()
|
||||
while len(uids) < 2:
|
||||
event = ac.wait_for_event()
|
||||
if event.kind == EventType.INFO and rex.search(event.msg):
|
||||
break
|
||||
if event.kind == EventType.INFO and (match := rex.search(event.msg)):
|
||||
uids.update(re.split("[,:]", match.group(1)))
|
||||
|
||||
folder = "mvbox" if mvbox_move else "inbox"
|
||||
ac1_direct_imap = direct_imap(ac1)
|
||||
ac2_direct_imap = direct_imap(ac2)
|
||||
|
||||
ac1_direct_imap.select_config_folder(folder)
|
||||
ac2_direct_imap.select_config_folder(folder)
|
||||
ac1_direct_imap.select_folder("INBOX")
|
||||
ac2_direct_imap.select_folder("INBOX")
|
||||
|
||||
# Check that the mdn is marked as seen
|
||||
assert len(list(ac1_direct_imap.conn.fetch(AND(seen=True), mark_seen=False))) == 1
|
||||
# Check original message is marked as seen
|
||||
assert len(list(ac2_direct_imap.conn.fetch(AND(seen=True), mark_seen=False))) == 1
|
||||
# Check that the mdn and original message is marked as seen
|
||||
assert len(list(ac1_direct_imap.conn.fetch(AND(seen=True), mark_seen=False))) == 2
|
||||
assert len(list(ac2_direct_imap.conn.fetch(AND(seen=True), mark_seen=False))) == 2
|
||||
|
||||
|
||||
def test_trash_multiple_messages(acfactory, direct_imap, log):
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
def test_trash_multiple_messages(acf, direct_imap, log):
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
ac2.stop_io()
|
||||
|
||||
ac2.set_config("delete_server_after", "0")
|
||||
# Make sure that messages are not immediately auto-deleted on the server:
|
||||
ac2.set_config("bcc_self", "1")
|
||||
|
||||
ac2.set_config("sync_msgs", "0")
|
||||
|
||||
ac2.start_io()
|
||||
chat12 = acfactory.get_accepted_chat(ac1, ac2)
|
||||
chat12 = acf.get_accepted_chat(ac1, ac2)
|
||||
|
||||
log.section("ac1: sending 3 messages")
|
||||
texts = ["first", "second", "third"]
|
||||
|
||||
@@ -7,15 +7,25 @@ If you want to debug iroh at rust-trace/log level set
|
||||
RUST_LOG=iroh_net=trace,iroh_gossip=trace
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
|
||||
import pytest
|
||||
|
||||
from deltachat_rpc_client import EventType
|
||||
|
||||
# Relays on underscore domains advertise themselves as iroh relay
|
||||
# but serve a self-signed certificate that iroh's TLS stack rejects.
|
||||
# Skipping instead of xfailing keeps the run fast:
|
||||
# these tests only fail after waiting for realtime connections to time out.
|
||||
pytestmark = pytest.mark.skipif(
|
||||
os.environ.get("CHATMAIL_DOMAIN", "").startswith("_"),
|
||||
reason="iroh does not accept the self-signed certificate of an underscore domain",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def path_to_webxdc(request):
|
||||
@@ -35,7 +45,11 @@ def log(msg):
|
||||
logging.info(msg)
|
||||
|
||||
|
||||
def setup_realtime_webxdc(ac1, ac2, path_to_webxdc):
|
||||
# payload used to probe/establish realtime connectivity, filtered out by tests
|
||||
SETUP_DATA = b"realtime-setup"
|
||||
|
||||
|
||||
def setup_realtime_webxdc(ac1, ac2, path_to_webxdc, wait=True):
|
||||
assert ac1.get_config("webxdc_realtime_enabled") == "1"
|
||||
assert ac2.get_config("webxdc_realtime_enabled") == "1"
|
||||
ac1_ac2_chat = ac1.create_chat(ac2)
|
||||
@@ -52,45 +66,44 @@ def setup_realtime_webxdc(ac1, ac2, path_to_webxdc):
|
||||
|
||||
log("sending ac2 -> ac1 realtime advertisement and additional message")
|
||||
ac2_webxdc_msg.send_webxdc_realtime_advertisement()
|
||||
|
||||
if wait:
|
||||
wait_realtime_connected([(ac1_webxdc_msg, ac2_webxdc_msg)])
|
||||
return ac1_webxdc_msg, ac2_webxdc_msg
|
||||
|
||||
|
||||
def setup_thread_send_realtime_data(msg, data):
|
||||
def thread_run():
|
||||
for _i in range(10):
|
||||
msg.send_webxdc_realtime_data(data)
|
||||
time.sleep(1)
|
||||
@contextmanager
|
||||
def send_realtime_data_forever(msgs, data=None):
|
||||
stop = threading.Event()
|
||||
data = data or [SETUP_DATA] * len(msgs)
|
||||
|
||||
threading.Thread(target=thread_run, daemon=True).start()
|
||||
def thread_run(msg, payload):
|
||||
for i in itertools.count():
|
||||
msg.send_webxdc_realtime_data(payload(i) if callable(payload) else payload)
|
||||
if stop.wait(1):
|
||||
return
|
||||
|
||||
for msg_payload in zip(msgs, data, strict=True):
|
||||
threading.Thread(target=thread_run, args=msg_payload, daemon=True).start()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
stop.set()
|
||||
|
||||
|
||||
def wait_receive_realtime_data(msg_data_list):
|
||||
account = msg_data_list[0][0].account
|
||||
msg_data_list = msg_data_list[:]
|
||||
|
||||
log(f"account {account.id}: waiting for realtime data {msg_data_list}")
|
||||
while msg_data_list:
|
||||
event = account.wait_for_event()
|
||||
if event.kind == EventType.WEBXDC_REALTIME_DATA:
|
||||
for i, (msg, data) in enumerate(msg_data_list):
|
||||
if msg.id == event.msg_id:
|
||||
assert list(data) == event.data
|
||||
log(f"msg {msg.id}: got correct realtime data {data}")
|
||||
del msg_data_list[i]
|
||||
break
|
||||
def wait_realtime_connected(msg_pairs):
|
||||
with send_realtime_data_forever([sender for sender, _ in msg_pairs]):
|
||||
for _, receiver in msg_pairs:
|
||||
receiver.account.wait_for_realtime_data(receiver.id)
|
||||
|
||||
|
||||
def test_realtime_sequentially(acfactory, path_to_webxdc):
|
||||
def test_realtime_sequentially(acf, path_to_webxdc):
|
||||
"""Test two peers trying to establish connection sequentially."""
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
ac1.set_config("webxdc_realtime_enabled", "1")
|
||||
ac2.set_config("webxdc_realtime_enabled", "1")
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
ac1.create_chat(ac2)
|
||||
ac2.create_chat(ac1)
|
||||
|
||||
# share a webxdc app between ac1 and ac2
|
||||
ac1_webxdc_msg = acfactory.send_message(from_account=ac1, to_account=ac2, text="play", file=path_to_webxdc)
|
||||
ac1_webxdc_msg = acf.send_message(from_account=ac1, to_account=ac2, text="play", file=path_to_webxdc)
|
||||
ac2_webxdc_msg = ac2.wait_for_incoming_msg()
|
||||
snapshot = ac2_webxdc_msg.get_snapshot()
|
||||
assert snapshot.text == "play"
|
||||
@@ -98,7 +111,7 @@ def test_realtime_sequentially(acfactory, path_to_webxdc):
|
||||
# send iroh announcements sequentially
|
||||
log("sending ac1 -> ac2 realtime advertisement and additional message")
|
||||
ac1_webxdc_msg.send_webxdc_realtime_advertisement()
|
||||
acfactory.send_message(from_account=ac1, to_account=ac2, text="ping1")
|
||||
acf.send_message(from_account=ac1, to_account=ac2, text="ping1")
|
||||
|
||||
log("waiting for incoming message on ac2")
|
||||
snapshot = ac2.wait_for_incoming_msg().get_snapshot()
|
||||
@@ -106,7 +119,7 @@ def test_realtime_sequentially(acfactory, path_to_webxdc):
|
||||
|
||||
log("sending ac2 -> ac1 realtime advertisement and additional message")
|
||||
ac2_webxdc_msg.send_webxdc_realtime_advertisement()
|
||||
acfactory.send_message(from_account=ac2, to_account=ac1, text="ping2")
|
||||
acf.send_message(from_account=ac2, to_account=ac1, text="ping2")
|
||||
|
||||
log("waiting for incoming message on ac1")
|
||||
snapshot = ac1.wait_for_incoming_msg().get_snapshot()
|
||||
@@ -117,50 +130,27 @@ def test_realtime_sequentially(acfactory, path_to_webxdc):
|
||||
data = os.urandom(128000)
|
||||
ac1_webxdc_msg.send_webxdc_realtime_data(data)
|
||||
|
||||
log("ac2: waiting for realtime data")
|
||||
while 1:
|
||||
event = ac2.wait_for_event()
|
||||
if event.kind == EventType.WEBXDC_REALTIME_DATA:
|
||||
assert event.data == list(data)
|
||||
break
|
||||
assert ac2.wait_for_realtime_data(ac2_webxdc_msg.id) == data
|
||||
|
||||
|
||||
def test_realtime_simultaneously(acfactory, path_to_webxdc):
|
||||
def test_realtime_simultaneously(acf, path_to_webxdc):
|
||||
"""Test two peers trying to establish connection simultaneously."""
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
ac1.set_config("webxdc_realtime_enabled", "1")
|
||||
ac2.set_config("webxdc_realtime_enabled", "1")
|
||||
|
||||
ac1_webxdc_msg, ac2_webxdc_msg = setup_realtime_webxdc(ac1, ac2, path_to_webxdc)
|
||||
|
||||
setup_thread_send_realtime_data(ac1_webxdc_msg, [10])
|
||||
wait_receive_realtime_data([(ac2_webxdc_msg, [10])])
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
setup_realtime_webxdc(ac1, ac2, path_to_webxdc)
|
||||
|
||||
|
||||
def test_two_parallel_realtime_simultaneously(acfactory, path_to_webxdc):
|
||||
def test_two_parallel_realtime_simultaneously(acf, path_to_webxdc):
|
||||
"""Test two peers trying to establish connection simultaneously."""
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
ac1.set_config("webxdc_realtime_enabled", "1")
|
||||
ac2.set_config("webxdc_realtime_enabled", "1")
|
||||
|
||||
ac1_webxdc_msg, ac2_webxdc_msg = setup_realtime_webxdc(ac1, ac2, path_to_webxdc)
|
||||
ac1_webxdc_msg2, ac2_webxdc_msg2 = setup_realtime_webxdc(ac1, ac2, path_to_webxdc)
|
||||
|
||||
setup_thread_send_realtime_data(ac1_webxdc_msg, [10])
|
||||
setup_thread_send_realtime_data(ac1_webxdc_msg2, [20])
|
||||
setup_thread_send_realtime_data(ac2_webxdc_msg, [30])
|
||||
setup_thread_send_realtime_data(ac2_webxdc_msg2, [40])
|
||||
|
||||
wait_receive_realtime_data([(ac1_webxdc_msg, [30]), (ac1_webxdc_msg2, [40])])
|
||||
wait_receive_realtime_data([(ac2_webxdc_msg, [10]), (ac2_webxdc_msg2, [20])])
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
ac1_webxdc_msg, ac2_webxdc_msg = setup_realtime_webxdc(ac1, ac2, path_to_webxdc, wait=False)
|
||||
ac1_webxdc_msg2, ac2_webxdc_msg2 = setup_realtime_webxdc(ac1, ac2, path_to_webxdc, wait=False)
|
||||
wait_realtime_connected([(ac1_webxdc_msg, ac2_webxdc_msg), (ac2_webxdc_msg, ac1_webxdc_msg)])
|
||||
wait_realtime_connected([(ac1_webxdc_msg2, ac2_webxdc_msg2), (ac2_webxdc_msg2, ac1_webxdc_msg2)])
|
||||
|
||||
|
||||
def test_no_duplicate_messages(acfactory, path_to_webxdc):
|
||||
def test_no_duplicate_messages(acf, path_to_webxdc):
|
||||
"""Test that messages are received only once."""
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
ac1.set_config("webxdc_realtime_enabled", "1")
|
||||
ac2.set_config("webxdc_realtime_enabled", "1")
|
||||
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
ac1_ac2_chat = ac1.create_chat(ac2)
|
||||
|
||||
ac1_webxdc_msg = ac1_ac2_chat.send_message(text="webxdc", file=path_to_webxdc)
|
||||
@@ -174,50 +164,29 @@ def test_no_duplicate_messages(acfactory, path_to_webxdc):
|
||||
ac2_webxdc_msg.send_webxdc_realtime_data.future(b"foobar")
|
||||
ac2_webxdc_msg.send_webxdc_realtime_advertisement()
|
||||
|
||||
def thread_run():
|
||||
for i in range(10):
|
||||
data = str(i).encode()
|
||||
ac1_webxdc_msg.send_webxdc_realtime_data(data)
|
||||
time.sleep(1)
|
||||
|
||||
threading.Thread(target=thread_run, daemon=True).start()
|
||||
|
||||
event = ac2.wait_for_event(EventType.WEBXDC_REALTIME_DATA)
|
||||
n = int(bytes(event.data).decode())
|
||||
|
||||
event = ac2.wait_for_event(EventType.WEBXDC_REALTIME_DATA)
|
||||
assert int(bytes(event.data).decode()) > n
|
||||
with send_realtime_data_forever([ac1_webxdc_msg], data=[lambda i: str(i).encode()]):
|
||||
n = int(ac2.wait_for_realtime_data(ac2_webxdc_msg.id).decode())
|
||||
assert int(ac2.wait_for_realtime_data(ac2_webxdc_msg.id).decode()) > n
|
||||
|
||||
|
||||
def test_no_reordering(acfactory, path_to_webxdc):
|
||||
def test_no_reordering(acf, path_to_webxdc):
|
||||
"""Test that sending a lot of realtime messages does not result in reordering."""
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
ac1.set_config("webxdc_realtime_enabled", "1")
|
||||
ac2.set_config("webxdc_realtime_enabled", "1")
|
||||
|
||||
ac1_webxdc_msg, ac2_webxdc_msg = setup_realtime_webxdc(ac1, ac2, path_to_webxdc)
|
||||
|
||||
setup_thread_send_realtime_data(ac1_webxdc_msg, b"hello")
|
||||
wait_receive_realtime_data([(ac2_webxdc_msg, b"hello")])
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
ac1_webxdc_msg, ac2_webxdc_msg = setup_realtime_webxdc(ac1, ac2, path_to_webxdc, wait=True)
|
||||
|
||||
for i in range(200):
|
||||
ac1_webxdc_msg.send_webxdc_realtime_data([i])
|
||||
|
||||
for i in range(200):
|
||||
while 1:
|
||||
event = ac2.wait_for_event()
|
||||
if event.kind == EventType.WEBXDC_REALTIME_DATA and bytes(event.data) != b"hello":
|
||||
if event.data[0] == i:
|
||||
break
|
||||
pytest.fail("Reordering detected")
|
||||
# lingering SETUP_DATA payloads from the wait_realtime_connected() barrier may still arrive
|
||||
while (data := ac2.wait_for_realtime_data(ac2_webxdc_msg.id)) == SETUP_DATA:
|
||||
pass
|
||||
assert data == bytes([i]), "Reordering detected"
|
||||
|
||||
|
||||
def test_advertisement_after_chatting(acfactory, path_to_webxdc):
|
||||
def test_advertisement_after_chatting(acf, path_to_webxdc):
|
||||
"""Test that realtime advertisement is assigned to the correct message after chatting."""
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
ac1.set_config("webxdc_realtime_enabled", "1")
|
||||
ac2.set_config("webxdc_realtime_enabled", "1")
|
||||
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
ac1_ac2_chat = ac1.create_chat(ac2)
|
||||
ac1_webxdc_msg = ac1_ac2_chat.send_message(text="WebXDC", file=path_to_webxdc)
|
||||
ac2_webxdc_msg = ac2.wait_for_incoming_msg()
|
||||
@@ -236,17 +205,14 @@ def test_advertisement_after_chatting(acfactory, path_to_webxdc):
|
||||
assert event.msg_id == ac1_webxdc_msg.id
|
||||
|
||||
|
||||
def test_realtime_large_webxdc(acfactory, path_to_large_webxdc):
|
||||
def test_realtime_large_webxdc(acf, path_to_large_webxdc):
|
||||
"""Tests initializing realtime channel on a large webxdc.
|
||||
|
||||
This is a regression test for a bug that existed in version 2.42.0.
|
||||
Large webxdc is split into pre- and post- message,
|
||||
and this previously resulted in failure to initialize realtime.
|
||||
"""
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
ac1.set_config("webxdc_realtime_enabled", "1")
|
||||
ac2.set_config("webxdc_realtime_enabled", "1")
|
||||
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
ac2.create_chat(ac1)
|
||||
ac1_ac2_chat = ac1.create_chat(ac2)
|
||||
ac1_webxdc_msg = ac1_ac2_chat.send_message(text="realtime check", file=path_to_large_webxdc)
|
||||
|
||||
32
deltachat-rpc-client/tests/test_location.py
Normal file
32
deltachat-rpc-client/tests/test_location.py
Normal file
@@ -0,0 +1,32 @@
|
||||
def test_set_location(dc, acf) -> None:
|
||||
# Try setting location without any accounts.
|
||||
assert not dc.set_location(1.0, 2.0, 0.1)
|
||||
|
||||
# Create one account that does not stream,
|
||||
# set location.
|
||||
acf.new_configured_account()
|
||||
assert not dc.set_location(3.0, 4.0, 0.1)
|
||||
|
||||
|
||||
def test_send_locations_to_chat(dc, acf):
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
assert not alice.is_sending_locations()
|
||||
alice_chat_bob = alice.create_chat(bob)
|
||||
assert not alice_chat_bob.is_sending_locations()
|
||||
|
||||
# Test starting and stopping location streaming in a chat.
|
||||
alice_chat_bob.send_locations(3600)
|
||||
assert alice.is_sending_locations()
|
||||
assert alice_chat_bob.is_sending_locations()
|
||||
alice_chat_bob.send_locations(0)
|
||||
assert not alice.is_sending_locations()
|
||||
assert not alice_chat_bob.is_sending_locations()
|
||||
|
||||
# Test stop_sending_locations() for all accounts and chats.
|
||||
alice_chat_bob.send_locations(3600)
|
||||
assert alice.is_sending_locations()
|
||||
assert alice_chat_bob.is_sending_locations()
|
||||
dc.stop_sending_locations()
|
||||
assert not alice.is_sending_locations()
|
||||
assert not alice_chat_bob.is_sending_locations()
|
||||
@@ -4,62 +4,47 @@ from deltachat_rpc_client import EventType
|
||||
from deltachat_rpc_client.const import MessageState
|
||||
|
||||
|
||||
def test_bcc_self_delete_server_after_defaults(acfactory):
|
||||
"""Test default values for bcc_self and delete_server_after."""
|
||||
ac = acfactory.get_online_account()
|
||||
def test_bcc_self_is_enabled_when_setting_up_second_device(acf):
|
||||
ac = acf.get_online_account()
|
||||
|
||||
# Initially after getting online
|
||||
# the setting bcc_self is set to 0 because there is only one device
|
||||
# and delete_server_after is "1", meaning immediate deletion.
|
||||
assert ac.get_config("bcc_self") == "0"
|
||||
assert ac.get_config("delete_server_after") == "1"
|
||||
|
||||
# Setup a second device.
|
||||
ac_clone = ac.clone()
|
||||
ac_clone.bring_online()
|
||||
|
||||
# Second device setup
|
||||
# enables bcc_self and changes default delete_server_after.
|
||||
# Second device setup enables bcc_self.
|
||||
assert ac.get_config("bcc_self") == "1"
|
||||
assert ac.get_config("delete_server_after") == "0"
|
||||
|
||||
assert ac_clone.get_config("bcc_self") == "1"
|
||||
assert ac_clone.get_config("delete_server_after") == "0"
|
||||
|
||||
# Manually disabling bcc_self
|
||||
# also restores the default for delete_server_after.
|
||||
# Test manually disabling bcc_self
|
||||
ac.set_config("bcc_self", "0")
|
||||
assert ac.get_config("bcc_self") == "0"
|
||||
assert ac.get_config("delete_server_after") == "1"
|
||||
|
||||
# Cloning the account again enables bcc_self
|
||||
# Cloning the account again enables bcc_self again
|
||||
# even though it was manually disabled.
|
||||
ac_clone = ac.clone()
|
||||
assert ac.get_config("bcc_self") == "1"
|
||||
assert ac.get_config("delete_server_after") == "0"
|
||||
|
||||
|
||||
def test_one_account_send_bcc_setting(acfactory, log, direct_imap):
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
def test_one_account_send_bcc_setting(acf, log, direct_imap):
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
ac1_clone = ac1.clone()
|
||||
ac1_clone.bring_online()
|
||||
|
||||
log.section("send out message without bcc to ourselves")
|
||||
ac1.set_config("bcc_self", "0")
|
||||
chat = ac1.create_chat(ac2)
|
||||
self_addr = ac1.get_config("addr")
|
||||
other_addr = ac2.get_config("addr")
|
||||
|
||||
msg_out = chat.send_text("message1")
|
||||
assert not msg_out.get_snapshot().is_forwarded
|
||||
|
||||
# wait for send out (no BCC)
|
||||
ev = ac1.wait_for_event(EventType.SMTP_MESSAGE_SENT)
|
||||
ac1.wait_for_event(EventType.SMTP_MESSAGE_SENT)
|
||||
assert ac1.get_config("bcc_self") == "0"
|
||||
|
||||
assert self_addr not in ev.msg
|
||||
assert other_addr in ev.msg
|
||||
|
||||
log.section("ac1: setting bcc_self=1")
|
||||
ac1.set_config("bcc_self", "1")
|
||||
|
||||
@@ -67,20 +52,16 @@ def test_one_account_send_bcc_setting(acfactory, log, direct_imap):
|
||||
msg_out = chat.send_text("message2")
|
||||
|
||||
# wait for send out (BCC)
|
||||
ev = ac1.wait_for_event(EventType.SMTP_MESSAGE_SENT)
|
||||
ac1.wait_for_event(EventType.SMTP_MESSAGE_SENT)
|
||||
assert ac1.get_config("bcc_self") == "1"
|
||||
|
||||
# Second client receives only second message, but not the first.
|
||||
# Second client receives only the second message, but not the first.
|
||||
ev_msg = ac1_clone.wait_for_event(EventType.MSGS_CHANGED)
|
||||
assert ac1_clone.get_message_by_id(ev_msg.msg_id).get_snapshot().text == "Messages are end-to-end encrypted."
|
||||
|
||||
ev_msg = ac1_clone.wait_for_event(EventType.MSGS_CHANGED)
|
||||
assert ac1_clone.get_message_by_id(ev_msg.msg_id).get_snapshot().text == msg_out.get_snapshot().text
|
||||
|
||||
# now make sure we are sending message to ourselves too
|
||||
assert self_addr in ev.msg
|
||||
assert self_addr in ev.msg
|
||||
|
||||
# BCC-self messages are marked as seen by the sender device.
|
||||
while True:
|
||||
event = ac1.wait_for_event()
|
||||
@@ -94,9 +75,9 @@ def test_one_account_send_bcc_setting(acfactory, log, direct_imap):
|
||||
assert len(list(ac1_direct_imap.conn.fetch(AND(seen=True)))) == 1
|
||||
|
||||
|
||||
def test_multidevice_sync_seen(acfactory, log):
|
||||
def test_multidevice_sync_seen(acf, log):
|
||||
"""Test that message marked as seen on one device is marked as seen on another."""
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
ac1_clone = ac1.clone()
|
||||
ac1_clone.bring_online()
|
||||
|
||||
@@ -146,3 +127,34 @@ def test_multidevice_sync_seen(acfactory, log):
|
||||
assert ac1_clone_message.get_snapshot().state == MessageState.IN_SEEN
|
||||
# Test that the timer is started on the second device after synchronizing the seen status.
|
||||
assert "Expires: " in ac1_clone_message.get_info()
|
||||
|
||||
|
||||
def test_multidevice_sync_seen_mdns_off(acf, log):
|
||||
"""Test that MDNs to self are sent even if MDNs are disabled."""
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
ac1.set_config("mdns_enabled", "0")
|
||||
|
||||
ac1_clone = ac1.clone()
|
||||
ac1_clone.bring_online()
|
||||
|
||||
assert ac1.get_config("bcc_self") == "1"
|
||||
assert ac1.get_config("mdns_enabled") == "0"
|
||||
assert ac1_clone.get_config("bcc_self") == "1"
|
||||
assert ac1_clone.get_config("mdns_enabled") == "0"
|
||||
|
||||
ac1.create_chat(ac2)
|
||||
ac1_clone_chat = ac1_clone.create_chat(ac2)
|
||||
ac2_chat = ac2.create_chat(ac1)
|
||||
|
||||
log.section("Send a message from ac2 to ac1 and check that it's 'fresh'")
|
||||
ac2_chat.send_text("Hi")
|
||||
ac1_message = ac1.wait_for_incoming_msg()
|
||||
ac1_clone_message = ac1_clone.wait_for_incoming_msg()
|
||||
|
||||
ac1_message.mark_seen()
|
||||
assert ac1_message.get_snapshot().state == MessageState.IN_SEEN
|
||||
|
||||
log.section("ac1 clone detects that message is marked as seen")
|
||||
ev = ac1_clone.wait_for_event(EventType.MSGS_NOTICED)
|
||||
assert ev.chat_id == ac1_clone_chat.id
|
||||
assert ac1_clone_message.get_snapshot().state == MessageState.IN_SEEN
|
||||
|
||||
@@ -5,17 +5,11 @@ from deltachat_rpc_client.const import ChatType, DownloadState
|
||||
from deltachat_rpc_client.rpc import JsonRpcError
|
||||
|
||||
|
||||
def test_add_second_address(acfactory) -> None:
|
||||
account = acfactory.new_configured_account()
|
||||
def test_add_second_address(acf) -> None:
|
||||
account = acf.new_configured_account()
|
||||
assert len(account.list_transports()) == 1
|
||||
|
||||
# When the first transport is created,
|
||||
# mvbox_move and only_fetch_mvbox should be disabled.
|
||||
assert account.get_config("mvbox_move") == "0"
|
||||
assert account.get_config("only_fetch_mvbox") == "0"
|
||||
assert account.get_config("show_emails") == "2"
|
||||
|
||||
qr = acfactory.get_account_qr()
|
||||
qr = acf.get_account_qr()
|
||||
account.add_transport_from_qr(qr)
|
||||
assert len(account.list_transports()) == 2
|
||||
|
||||
@@ -32,48 +26,10 @@ def test_add_second_address(acfactory) -> None:
|
||||
account.delete_transport(second_addr)
|
||||
assert len(account.list_transports()) == 2
|
||||
|
||||
# Enabling mvbox_move or only_fetch_mvbox
|
||||
# is not allowed when multi-transport is enabled.
|
||||
for option in ["mvbox_move", "only_fetch_mvbox"]:
|
||||
with pytest.raises(JsonRpcError):
|
||||
account.set_config(option, "1")
|
||||
|
||||
# show_emails does not matter for multi-relay, can be set to anything
|
||||
account.set_config("show_emails", "0")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("key", ["mvbox_move", "only_fetch_mvbox"])
|
||||
def test_no_second_transport_with_mvbox(acfactory, key) -> None:
|
||||
"""Test that second transport cannot be configured if mvbox is used."""
|
||||
account = acfactory.new_configured_account()
|
||||
assert len(account.list_transports()) == 1
|
||||
|
||||
assert account.get_config("mvbox_move") == "0"
|
||||
assert account.get_config("only_fetch_mvbox") == "0"
|
||||
|
||||
qr = acfactory.get_account_qr()
|
||||
account.set_config(key, "1")
|
||||
|
||||
with pytest.raises(JsonRpcError):
|
||||
account.add_transport_from_qr(qr)
|
||||
|
||||
|
||||
def test_second_transport_without_classic_emails(acfactory) -> None:
|
||||
"""Test that second transport can be configured if classic emails are not fetched."""
|
||||
account = acfactory.new_configured_account()
|
||||
assert len(account.list_transports()) == 1
|
||||
|
||||
assert account.get_config("show_emails") == "2"
|
||||
|
||||
qr = acfactory.get_account_qr()
|
||||
account.set_config("show_emails", "0")
|
||||
|
||||
account.add_transport_from_qr(qr)
|
||||
|
||||
|
||||
def test_change_address(acfactory) -> None:
|
||||
def test_change_address(acf) -> None:
|
||||
"""Test Alice configuring a second transport and setting it as a primary one."""
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
bob_addr = bob.get_config("configured_addr")
|
||||
bob.create_chat(alice)
|
||||
@@ -88,7 +44,7 @@ def test_change_address(acfactory) -> None:
|
||||
old_alice_addr = alice.get_config("configured_addr")
|
||||
alice_vcard = alice.self_contact.make_vcard()
|
||||
assert old_alice_addr in alice_vcard
|
||||
qr = acfactory.get_account_qr()
|
||||
qr = acf.get_account_qr()
|
||||
alice.add_transport_from_qr(qr)
|
||||
new_alice_addr = alice.list_transports()[1]["addr"]
|
||||
with pytest.raises(JsonRpcError):
|
||||
@@ -120,18 +76,18 @@ def test_change_address(acfactory) -> None:
|
||||
assert sender_addr2 == new_alice_addr
|
||||
|
||||
|
||||
def test_download_on_demand(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_download_on_demand(acf, rpcdata) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
alice.set_config("download_limit", "1")
|
||||
|
||||
alice.stop_io()
|
||||
qr = acfactory.get_account_qr()
|
||||
qr = acf.get_account_qr()
|
||||
alice.add_transport_from_qr(qr)
|
||||
alice.start_io()
|
||||
|
||||
alice.create_chat(bob)
|
||||
chat_bob_alice = bob.create_chat(alice)
|
||||
chat_bob_alice.send_message(file="../test-data/image/screenshot.jpg")
|
||||
chat_bob_alice.send_message(file=rpcdata.get_path("image/screenshot.jpg"))
|
||||
msg = alice.wait_for_incoming_msg()
|
||||
snapshot = msg.get_snapshot()
|
||||
assert snapshot.download_state == DownloadState.AVAILABLE
|
||||
@@ -147,46 +103,15 @@ def test_download_on_demand(acfactory) -> None:
|
||||
assert msg.get_snapshot().download_state == dstate
|
||||
|
||||
|
||||
@pytest.mark.parametrize("is_chatmail", ["0", "1"])
|
||||
def test_mvbox_move_first_transport(acfactory, is_chatmail) -> None:
|
||||
"""Test that mvbox_move is disabled by default even for non-chatmail accounts.
|
||||
Disabling mvbox_move is required to be able to setup a second transport.
|
||||
"""
|
||||
account = acfactory.get_unconfigured_account()
|
||||
|
||||
account.set_config("fix_is_chatmail", "1")
|
||||
account.set_config("is_chatmail", is_chatmail)
|
||||
|
||||
# The default value when the setting is unset is "1".
|
||||
# This is not changed for compatibility with old databases
|
||||
# imported from backups.
|
||||
assert account.get_config("mvbox_move") == "1"
|
||||
|
||||
qr = acfactory.get_account_qr()
|
||||
account.add_transport_from_qr(qr)
|
||||
|
||||
# Once the first transport is set up,
|
||||
# mvbox_move is disabled.
|
||||
assert account.get_config("mvbox_move") == "0"
|
||||
assert account.get_config("is_chatmail") == is_chatmail
|
||||
|
||||
|
||||
def test_reconfigure_transport(acfactory) -> None:
|
||||
"""Test that reconfiguring the transport works
|
||||
even if settings not supported for multi-transport
|
||||
like mvbox_move are enabled."""
|
||||
account = acfactory.get_online_account()
|
||||
account.set_config("mvbox_move", "1")
|
||||
def test_reconfigure_transport(acf) -> None:
|
||||
"""Test that reconfiguring the transport works."""
|
||||
account = acf.get_online_account()
|
||||
|
||||
[transport] = account.list_transports()
|
||||
account.add_or_update_transport(transport)
|
||||
|
||||
# Reconfiguring the transport should not reset
|
||||
# the settings as if when configuring the first transport.
|
||||
assert account.get_config("mvbox_move") == "1"
|
||||
|
||||
|
||||
def test_transport_synchronization(acfactory, log) -> None:
|
||||
def test_transport_synchronization(acf, log) -> None:
|
||||
"""Test synchronization of transports between devices."""
|
||||
|
||||
def wait_for_io_started(ac):
|
||||
@@ -195,11 +120,11 @@ def test_transport_synchronization(acfactory, log) -> None:
|
||||
if "scheduler is running" in ev.msg:
|
||||
return
|
||||
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
ac1_clone = ac1.clone()
|
||||
ac1_clone.bring_online()
|
||||
|
||||
qr = acfactory.get_account_qr()
|
||||
qr = acf.get_account_qr()
|
||||
|
||||
ac1.add_transport_from_qr(qr)
|
||||
ac1_clone.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
||||
@@ -225,12 +150,9 @@ def test_transport_synchronization(acfactory, log) -> None:
|
||||
log.section("ac1 changes the primary transport")
|
||||
ac1.set_config("configured_addr", transport3["addr"])
|
||||
|
||||
# One event for updated `add_timestamp` of the new primary transport,
|
||||
# one event for the `configured_addr` update.
|
||||
ac1_clone.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
||||
ac1_clone.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
||||
[transport1, transport3] = ac1_clone.list_transports()
|
||||
assert ac1_clone.get_config("configured_addr") == addr3
|
||||
assert ac1_clone.get_config("configured_addr") == transport1["addr"]
|
||||
|
||||
log.section("ac1 removes the first transport")
|
||||
ac1.delete_transport(transport1["addr"])
|
||||
@@ -248,13 +170,13 @@ def test_transport_synchronization(acfactory, log) -> None:
|
||||
assert ac1_clone.wait_for_incoming_msg().get_snapshot().text == "Hello!"
|
||||
|
||||
|
||||
def test_transport_sync_new_as_primary(acfactory, log) -> None:
|
||||
"""Test synchronization of new transport as primary between devices."""
|
||||
ac1, bob = acfactory.get_online_accounts(2)
|
||||
def test_transport_sync_new_as_primary(acf, log) -> None:
|
||||
"""Test that a transport promoted on one device is usable on other devices."""
|
||||
ac1, bob = acf.get_online_accounts(2)
|
||||
ac1_clone = ac1.clone()
|
||||
ac1_clone.bring_online()
|
||||
|
||||
qr = acfactory.get_account_qr()
|
||||
qr = acf.get_account_qr()
|
||||
|
||||
ac1.add_transport_from_qr(qr)
|
||||
ac1_transports = ac1.list_transports()
|
||||
@@ -268,9 +190,9 @@ def test_transport_sync_new_as_primary(acfactory, log) -> None:
|
||||
ac1.set_config("configured_addr", transport2["addr"])
|
||||
|
||||
ac1_clone.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
||||
assert ac1_clone.get_config("configured_addr") == transport2["addr"]
|
||||
assert ac1_clone.get_config("configured_addr") == transport1["addr"]
|
||||
|
||||
log.section("ac1_clone receives a message via the new primary transport")
|
||||
log.section("ac1_clone receives a message via the new transport")
|
||||
ac1_chat = ac1.create_chat(bob)
|
||||
ac1_chat.send_text("Hello!")
|
||||
bob_chat_id = bob.wait_for_incoming_msg_event().chat_id
|
||||
@@ -280,12 +202,12 @@ def test_transport_sync_new_as_primary(acfactory, log) -> None:
|
||||
assert ac1_clone.wait_for_incoming_msg().get_snapshot().text == "hello back"
|
||||
|
||||
|
||||
def test_recognize_self_address(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_recognize_self_address(acf) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
bob_chat = bob.create_chat(alice)
|
||||
|
||||
qr = acfactory.get_account_qr()
|
||||
qr = acf.get_account_qr()
|
||||
alice.add_transport_from_qr(qr)
|
||||
|
||||
new_alice_addr = alice.list_transports()[1]["addr"]
|
||||
@@ -296,10 +218,10 @@ def test_recognize_self_address(acfactory) -> None:
|
||||
assert msg.chat == alice.create_chat(bob)
|
||||
|
||||
|
||||
def test_transport_limit(acfactory) -> None:
|
||||
def test_transport_limit(acf) -> None:
|
||||
"""Test transports limit."""
|
||||
account = acfactory.get_online_account()
|
||||
qr = acfactory.get_account_qr()
|
||||
account = acf.get_online_account()
|
||||
qr = acf.get_account_qr()
|
||||
|
||||
limit = 5
|
||||
|
||||
@@ -312,17 +234,28 @@ def test_transport_limit(acfactory) -> None:
|
||||
account.add_transport_from_qr(qr)
|
||||
|
||||
second_addr = account.list_transports()[1]["addr"]
|
||||
account.delete_transport(second_addr)
|
||||
third_addr = account.list_transports()[2]["addr"]
|
||||
|
||||
# test that adding a transport after deleting one works again
|
||||
# test that adding a transport after unpublishing one works again
|
||||
account.set_transport_unpublished(second_addr)
|
||||
account.add_transport_from_qr(qr)
|
||||
with pytest.raises(JsonRpcError):
|
||||
account.add_transport_from_qr(qr)
|
||||
|
||||
# UIs are not expected to delete transports directly,
|
||||
# but we still test that adding a transport
|
||||
# after deleting one instead of unpublishing works.
|
||||
account.delete_transport(third_addr)
|
||||
account.add_transport_from_qr(qr)
|
||||
with pytest.raises(JsonRpcError):
|
||||
account.add_transport_from_qr(qr)
|
||||
|
||||
|
||||
def test_message_info_imap_urls(acfactory) -> None:
|
||||
def test_message_info_imap_urls(acf) -> None:
|
||||
"""Test that message info contains IMAP URLs of where the message was received."""
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
qr = acfactory.get_account_qr()
|
||||
qr = acf.get_account_qr()
|
||||
for i in range(3):
|
||||
alice.add_transport_from_qr(qr)
|
||||
# Wait for all transports to go IDLE after adding each one.
|
||||
@@ -357,10 +290,10 @@ def test_message_info_imap_urls(acfactory) -> None:
|
||||
assert f"{new_alice_addr}/INBOX" in msg_info
|
||||
|
||||
|
||||
def test_remove_primary_transport(acfactory, log) -> None:
|
||||
def test_remove_primary_transport(acf, log) -> None:
|
||||
"""Test that after removing the primary relay, Alice can still receive messages."""
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
qr = acfactory.get_account_qr()
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
qr = acf.get_account_qr()
|
||||
|
||||
alice.add_transport_from_qr(qr)
|
||||
alice.bring_online()
|
||||
|
||||
@@ -7,8 +7,8 @@ from deltachat_rpc_client.const import ChatType
|
||||
from deltachat_rpc_client.rpc import JsonRpcError
|
||||
|
||||
|
||||
def test_qr_setup_contact(acfactory, tmp_path) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_qr_setup_contact(acf, tmp_path) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
qr_code = alice.get_qr_code()
|
||||
bob.secure_join(qr_code)
|
||||
@@ -31,7 +31,7 @@ def test_qr_setup_contact(acfactory, tmp_path) -> None:
|
||||
# backwards verification is not lost
|
||||
# because default key is not changed.
|
||||
logging.info("Bob 2 is created")
|
||||
bob2 = acfactory.new_configured_account()
|
||||
bob2 = acf.new_configured_account()
|
||||
bob2.export_self_keys(tmp_path)
|
||||
|
||||
logging.info("Bob tries to import a key")
|
||||
@@ -44,8 +44,8 @@ def test_qr_setup_contact(acfactory, tmp_path) -> None:
|
||||
assert bob_contact_alice_snapshot.is_verified
|
||||
|
||||
|
||||
def test_qr_setup_contact_svg(acfactory) -> None:
|
||||
alice = acfactory.new_configured_account()
|
||||
def test_qr_setup_contact_svg(acf) -> None:
|
||||
alice = acf.new_configured_account()
|
||||
_, _, domain = alice.get_config("addr").rpartition("@")
|
||||
|
||||
_qr_code, svg = alice.get_qr_code_svg()
|
||||
@@ -59,8 +59,8 @@ def test_qr_setup_contact_svg(acfactory) -> None:
|
||||
assert "Alice" in svg
|
||||
|
||||
|
||||
def test_qr_securejoin(acfactory):
|
||||
alice, bob, fiona = acfactory.get_online_accounts(3)
|
||||
def test_qr_securejoin(acf):
|
||||
alice, bob, fiona = acf.get_online_accounts(3)
|
||||
|
||||
# Setup second device for Alice
|
||||
# to test observing securejoin protocol.
|
||||
@@ -87,7 +87,7 @@ def test_qr_securejoin(acfactory):
|
||||
assert alice_contact_bob_snapshot.is_verified
|
||||
|
||||
snapshot = bob.wait_for_incoming_msg().get_snapshot()
|
||||
assert snapshot.text == "Member Me added by {}.".format(alice.get_config("addr"))
|
||||
assert snapshot.text == "You were added by {}.".format(alice.get_config("addr"))
|
||||
|
||||
# Test that Bob verified Alice's profile.
|
||||
bob_contact_alice = bob.create_contact(alice)
|
||||
@@ -111,8 +111,8 @@ def test_qr_securejoin(acfactory):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("all_devices_online", [True, False])
|
||||
def test_qr_securejoin_broadcast(acfactory, all_devices_online):
|
||||
alice, bob, fiona = acfactory.get_online_accounts(3)
|
||||
def test_qr_securejoin_broadcast(acf, all_devices_online):
|
||||
alice, bob, fiona = acf.get_online_accounts(3)
|
||||
|
||||
alice2 = alice.clone()
|
||||
bob2 = bob.clone()
|
||||
@@ -252,9 +252,9 @@ def test_qr_securejoin_broadcast(acfactory, all_devices_online):
|
||||
check_account(bob, bob.create_contact(alice), inviter_side=False, please_wait_info_msg=True)
|
||||
|
||||
|
||||
def test_qr_securejoin_contact_request(acfactory) -> None:
|
||||
def test_qr_securejoin_contact_request(acf) -> None:
|
||||
"""Alice invites Bob to a group when Bob's chat with Alice is in a contact request mode."""
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
alice_chat_bob = alice_contact_bob.create_chat()
|
||||
@@ -278,8 +278,8 @@ def test_qr_securejoin_contact_request(acfactory) -> None:
|
||||
assert bob_chat_alice.get_basic_snapshot().is_contact_request
|
||||
|
||||
|
||||
def test_qr_readreceipt(acfactory) -> None:
|
||||
alice, bob, charlie = acfactory.get_online_accounts(3)
|
||||
def test_qr_readreceipt(acf) -> None:
|
||||
alice, bob, charlie = acf.get_online_accounts(3)
|
||||
|
||||
logging.info("Bob and Charlie setup contact with Alice")
|
||||
qr_code = alice.get_qr_code()
|
||||
@@ -335,24 +335,24 @@ def test_qr_readreceipt(acfactory) -> None:
|
||||
assert not bob.get_chat_by_contact(bob_contact_charlie)
|
||||
|
||||
|
||||
def test_setup_contact_resetup(acfactory) -> None:
|
||||
def test_setup_contact_resetup(acf) -> None:
|
||||
"""Tests that setup contact works after Alice resets the device and changes the key."""
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
qr_code = alice.get_qr_code()
|
||||
bob.secure_join(qr_code)
|
||||
bob.wait_for_securejoin_joiner_success()
|
||||
|
||||
alice = acfactory.resetup_account(alice)
|
||||
alice = acf.resetup_account(alice)
|
||||
|
||||
qr_code = alice.get_qr_code()
|
||||
bob.secure_join(qr_code)
|
||||
bob.wait_for_securejoin_joiner_success()
|
||||
|
||||
|
||||
def test_verified_group_member_added_recovery(acfactory) -> None:
|
||||
def test_verified_group_member_added_recovery(acf) -> None:
|
||||
"""Tests verified group recovery by reverifying then removing and adding a member back."""
|
||||
ac1, ac2, ac3 = acfactory.get_online_accounts(3)
|
||||
ac1, ac2, ac3 = acf.get_online_accounts(3)
|
||||
|
||||
logging.info("ac1 creates a group")
|
||||
chat = ac1.create_group("Group")
|
||||
@@ -374,7 +374,7 @@ def test_verified_group_member_added_recovery(acfactory) -> None:
|
||||
ac3_contact_ac2_old = ac3.create_contact(ac2)
|
||||
|
||||
logging.info("ac2 logs in on a new device")
|
||||
ac2 = acfactory.resetup_account(ac2)
|
||||
ac2 = acf.resetup_account(ac2)
|
||||
|
||||
logging.info("ac2 reverifies with ac3")
|
||||
qr_code = ac3.get_qr_code()
|
||||
@@ -425,11 +425,11 @@ def test_verified_group_member_added_recovery(acfactory) -> None:
|
||||
assert ac1_contact_ac2_snapshot.verifier_id != ac1_contact_ac3.id
|
||||
|
||||
|
||||
def test_qr_join_chat_with_pending_bobstate_issue4894(acfactory):
|
||||
def test_qr_join_chat_with_pending_bobstate_issue4894(acf):
|
||||
"""Regression test for
|
||||
issue <https://github.com/chatmail/core/issues/4894>.
|
||||
"""
|
||||
ac1, ac2, ac3, ac4 = acfactory.get_online_accounts(4)
|
||||
ac1, ac2, ac3, ac4 = acf.get_online_accounts(4)
|
||||
|
||||
logging.info("ac3: verify with ac2")
|
||||
qr_code = ac2.get_qr_code()
|
||||
@@ -484,17 +484,17 @@ def test_qr_join_chat_with_pending_bobstate_issue4894(acfactory):
|
||||
return
|
||||
|
||||
|
||||
def test_qr_new_group_unblocked(acfactory):
|
||||
def test_qr_new_group_unblocked(acf):
|
||||
"""Regression test for a bug introduced in core v1.113.0.
|
||||
ac2 scans a verified group QR code created by ac1.
|
||||
This results in creation of a blocked 1:1 chat with ac1 on ac2,
|
||||
This results in creation of a blocked single chat with ac1 on ac2,
|
||||
but ac1 contact is not blocked on ac2.
|
||||
Then ac1 creates a group, adds ac2 there and promotes it by sending a message.
|
||||
ac2 should receive a message and create a contact request for the group.
|
||||
Due to a bug previously ac2 created a blocked group.
|
||||
"""
|
||||
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
ac1_chat = ac1.create_group("Group for joining")
|
||||
qr_code = ac1_chat.get_qr_code()
|
||||
ac2.secure_join(qr_code)
|
||||
@@ -513,11 +513,11 @@ def test_qr_new_group_unblocked(acfactory):
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="AEAP is disabled for now")
|
||||
def test_aeap_flow_verified(acfactory):
|
||||
def test_aeap_flow_verified(acf):
|
||||
"""Test that a new address is added to a contact when it changes its address."""
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
|
||||
addr, password = acfactory.get_credentials()
|
||||
addr, password = acf.get_credentials()
|
||||
|
||||
logging.info("ac1: create verified-group QR, ac2 scans and joins")
|
||||
chat = ac1.create_group("hello")
|
||||
@@ -555,8 +555,8 @@ def test_aeap_flow_verified(acfactory):
|
||||
assert addr in [contact.get_snapshot().address for contact in msg_in_2_snapshot.chat.get_contacts()]
|
||||
|
||||
|
||||
def test_gossip_verification(acfactory) -> None:
|
||||
alice, bob, carol = acfactory.get_online_accounts(3)
|
||||
def test_gossip_verification(acf) -> None:
|
||||
alice, bob, carol = acf.get_online_accounts(3)
|
||||
|
||||
# Bob verifies Alice.
|
||||
qr_code = alice.get_qr_code()
|
||||
@@ -605,13 +605,13 @@ def test_gossip_verification(acfactory) -> None:
|
||||
assert not carol_contact_alice_snapshot.is_verified
|
||||
|
||||
|
||||
def test_securejoin_after_contact_resetup(acfactory) -> None:
|
||||
def test_securejoin_after_contact_resetup(acf) -> None:
|
||||
"""
|
||||
Regression test for a bug that prevented joining verified group with a QR code
|
||||
if the group is already created and contains
|
||||
a contact with inconsistent (Autocrypt and verified keys exist but don't match) key state.
|
||||
"""
|
||||
ac1, ac2, ac3 = acfactory.get_online_accounts(3)
|
||||
ac1, ac2, ac3 = acf.get_online_accounts(3)
|
||||
|
||||
# ac3 creates protected group with ac1.
|
||||
ac3_chat = ac3.create_group("Group")
|
||||
@@ -623,7 +623,7 @@ def test_securejoin_after_contact_resetup(acfactory) -> None:
|
||||
|
||||
# ac1 waits for member added message and creates a QR code.
|
||||
snapshot = ac1.wait_for_incoming_msg().get_snapshot()
|
||||
assert snapshot.text == "Member Me added by {}.".format(ac3.get_config("addr"))
|
||||
assert snapshot.text == "You were added by {}.".format(ac3.get_config("addr"))
|
||||
ac1_qr_code = snapshot.chat.get_qr_code()
|
||||
|
||||
# ac2 verifies ac1
|
||||
@@ -636,7 +636,7 @@ def test_securejoin_after_contact_resetup(acfactory) -> None:
|
||||
assert ac2_contact_ac1.get_snapshot().is_verified
|
||||
|
||||
# ac1 resetups the account.
|
||||
ac1 = acfactory.resetup_account(ac1)
|
||||
ac1 = acf.resetup_account(ac1)
|
||||
ac2_contact_ac1 = ac2.create_contact(ac1, "")
|
||||
assert not ac2_contact_ac1.get_snapshot().is_verified
|
||||
|
||||
@@ -668,8 +668,8 @@ def test_securejoin_after_contact_resetup(acfactory) -> None:
|
||||
assert not ac2_contact_ac1.get_snapshot().is_verified
|
||||
|
||||
|
||||
def test_withdraw_securejoin_qr(acfactory):
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_withdraw_securejoin_qr(acf):
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
logging.info("Alice creates a group")
|
||||
alice_chat = alice.create_group("Group")
|
||||
@@ -682,7 +682,7 @@ def test_withdraw_securejoin_qr(acfactory):
|
||||
alice.clear_all_events()
|
||||
|
||||
snapshot = bob.wait_for_incoming_msg().get_snapshot()
|
||||
assert snapshot.text == "Member Me added by {}.".format(alice.get_config("addr"))
|
||||
assert snapshot.text == "You were added by {}.".format(alice.get_config("addr"))
|
||||
bob_chat.leave()
|
||||
|
||||
snapshot = alice.get_message_by_id(alice.wait_for_msgs_changed_event().msg_id).get_snapshot()
|
||||
@@ -704,3 +704,25 @@ def test_withdraw_securejoin_qr(acfactory):
|
||||
and "Ignoring RequestWithAuth message because of invalid auth code." in event.msg
|
||||
):
|
||||
break
|
||||
|
||||
|
||||
def test_qr_scan_updates_new_relay_address(acf):
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
bob_alice_chat = bob.secure_join(alice.get_qr_code())
|
||||
alice.wait_for_securejoin_inviter_success()
|
||||
bob.wait_for_securejoin_joiner_success()
|
||||
|
||||
for ac in [alice, bob]:
|
||||
old_addr = ac.get_config("configured_addr")
|
||||
ac.add_transport_from_qr(acf.get_account_qr())
|
||||
ac.set_config("configured_addr", ac.list_transports()[1]["addr"])
|
||||
ac.delete_transport(old_addr)
|
||||
|
||||
bob.secure_join(alice.get_qr_code())
|
||||
alice.wait_for_securejoin_inviter_success()
|
||||
bob.wait_for_securejoin_joiner_success()
|
||||
|
||||
bob_alice_chat.send_text("hi")
|
||||
snapshot = alice.wait_for_incoming_msg().get_snapshot()
|
||||
assert snapshot.text == "hi"
|
||||
|
||||
@@ -48,8 +48,8 @@ def test_email_address_validity(rpc) -> None:
|
||||
assert not rpc.check_email_validity(addr)
|
||||
|
||||
|
||||
def test_acfactory(acfactory) -> None:
|
||||
account = acfactory.new_configured_account()
|
||||
def test_acf(acf) -> None:
|
||||
account = acf.new_configured_account()
|
||||
while True:
|
||||
event = account.wait_for_event()
|
||||
if event.kind == EventType.CONFIGURE_PROGRESS:
|
||||
@@ -61,9 +61,9 @@ def test_acfactory(acfactory) -> None:
|
||||
logging.info("Successful configuration")
|
||||
|
||||
|
||||
def test_configure_starttls(acfactory) -> None:
|
||||
addr, password = acfactory.get_credentials()
|
||||
account = acfactory.get_unconfigured_account()
|
||||
def test_configure_starttls(acf) -> None:
|
||||
addr, password = acf.get_credentials()
|
||||
account = acf.get_unconfigured_account()
|
||||
account.add_or_update_transport(
|
||||
{
|
||||
"addr": addr,
|
||||
@@ -75,10 +75,10 @@ def test_configure_starttls(acfactory) -> None:
|
||||
assert account.is_configured()
|
||||
|
||||
|
||||
def test_lowercase_address(acfactory) -> None:
|
||||
addr, password = acfactory.get_credentials()
|
||||
def test_lowercase_address(acf) -> None:
|
||||
addr, password = acf.get_credentials()
|
||||
addr_upper = addr.upper()
|
||||
account = acfactory.get_unconfigured_account()
|
||||
account = acf.get_unconfigured_account()
|
||||
account.add_or_update_transport(
|
||||
{
|
||||
"addr": addr_upper,
|
||||
@@ -91,13 +91,21 @@ def test_lowercase_address(acfactory) -> None:
|
||||
assert account.list_transports()[0]["addr"] == addr
|
||||
|
||||
param = account.get_info()["used_transport_settings"]
|
||||
assert addr in param
|
||||
|
||||
domain = addr.rsplit("@")[-1]
|
||||
domain_upper = addr_upper.rsplit("@")[-1]
|
||||
assert domain in param
|
||||
assert domain_upper not in param
|
||||
|
||||
# Whole address should not appear in the info,
|
||||
# does not matter if uppercase or lowercase.
|
||||
assert addr not in param
|
||||
assert addr_upper not in param
|
||||
|
||||
|
||||
def test_configure_ip(acfactory) -> None:
|
||||
addr, password = acfactory.get_credentials()
|
||||
account = acfactory.get_unconfigured_account()
|
||||
def test_configure_ip(acf) -> None:
|
||||
addr, password = acf.get_credentials()
|
||||
account = acf.get_unconfigured_account()
|
||||
ip_address = socket.gethostbyname(addr.rsplit("@")[-1])
|
||||
|
||||
with pytest.raises(JsonRpcError):
|
||||
@@ -111,10 +119,10 @@ def test_configure_ip(acfactory) -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_configure_alternative_port(acfactory) -> None:
|
||||
def test_configure_alternative_port(acf) -> None:
|
||||
"""Test that configuration with alternative port 443 works."""
|
||||
addr, password = acfactory.get_credentials()
|
||||
account = acfactory.get_unconfigured_account()
|
||||
addr, password = acf.get_credentials()
|
||||
account = acf.get_unconfigured_account()
|
||||
account.add_or_update_transport(
|
||||
{
|
||||
"addr": addr,
|
||||
@@ -126,9 +134,9 @@ def test_configure_alternative_port(acfactory) -> None:
|
||||
assert account.is_configured()
|
||||
|
||||
|
||||
def test_list_transports(acfactory) -> None:
|
||||
addr, password = acfactory.get_credentials()
|
||||
account = acfactory.get_unconfigured_account()
|
||||
def test_list_transports(acf) -> None:
|
||||
addr, password = acf.get_credentials()
|
||||
account = acf.get_unconfigured_account()
|
||||
account.add_or_update_transport(
|
||||
{
|
||||
"addr": addr,
|
||||
@@ -144,8 +152,8 @@ def test_list_transports(acfactory) -> None:
|
||||
assert params["imapUser"] == addr
|
||||
|
||||
|
||||
def test_account(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_account(acf) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
bob_addr = bob.get_config("addr")
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
@@ -213,8 +221,32 @@ def test_account(acfactory) -> None:
|
||||
alice.stop_io()
|
||||
|
||||
|
||||
def test_chat(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_mark_fresh_vs_self_mdn(acf) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
bob.set_config("bcc_self", "1")
|
||||
|
||||
alice_contact_bob = alice.create_contact(bob)
|
||||
alice_chat = alice_contact_bob.create_chat()
|
||||
alice_chat.send_text("Hello!")
|
||||
|
||||
event = bob.wait_for_incoming_msg_event()
|
||||
chat_id = event.chat_id
|
||||
msg_id = event.msg_id
|
||||
|
||||
bob_chat = bob.get_chat_by_id(chat_id)
|
||||
message = bob.get_message_by_id(msg_id)
|
||||
bob_chat.accept()
|
||||
bob.mark_seen_messages([message])
|
||||
bob_chat.mark_fresh()
|
||||
assert bob_chat.get_fresh_message_count() == 1
|
||||
alice.wait_for_event(EventType.MSG_READ)
|
||||
alice_chat.send_text("You've read 'Hello!'")
|
||||
bob.wait_for_incoming_msg_event()
|
||||
assert bob_chat.get_fresh_message_count() == 2
|
||||
|
||||
|
||||
def test_chat(acf) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
alice_chat_bob = alice_contact_bob.create_chat()
|
||||
@@ -243,7 +275,7 @@ def test_chat(acfactory) -> None:
|
||||
bob_chat_alice.unpin()
|
||||
bob_chat_alice.archive()
|
||||
bob_chat_alice.unarchive()
|
||||
with pytest.raises(JsonRpcError): # can't set name for 1:1 chats
|
||||
with pytest.raises(JsonRpcError): # can't set name for single chats
|
||||
bob_chat_alice.set_name("test")
|
||||
bob_chat_alice.set_ephemeral_timer(300)
|
||||
bob_chat_alice.get_encryption_info()
|
||||
@@ -283,8 +315,8 @@ def test_chat(acfactory) -> None:
|
||||
group.get_locations()
|
||||
|
||||
|
||||
def test_contact(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_contact(acf) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
bob_addr = bob.get_config("addr")
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
@@ -300,8 +332,8 @@ def test_contact(acfactory) -> None:
|
||||
alice_contact_bob.create_chat()
|
||||
|
||||
|
||||
def test_message(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_message(acf) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
alice_chat_bob = alice_contact_bob.create_chat()
|
||||
@@ -331,8 +363,8 @@ def test_message(acfactory) -> None:
|
||||
assert reactions == snapshot.reactions
|
||||
|
||||
|
||||
def test_receive_imf_failure(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_receive_imf_failure(acf) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
alice_chat_bob = alice_contact_bob.create_chat()
|
||||
|
||||
@@ -348,7 +380,7 @@ def test_receive_imf_failure(acfactory) -> None:
|
||||
snapshot.text == "❌ Failed to receive a message:"
|
||||
" Condition failed: `!context.get_config_bool(Config::SimulateReceiveImfError).await?`."
|
||||
f" Core version {version}."
|
||||
" Please report this bug to delta@merlinux.eu or https://support.delta.chat/."
|
||||
" Please report this bug to delta@merlinux.eu or https://support.delta.chat/"
|
||||
)
|
||||
|
||||
# The failed message doesn't break the IMAP loop.
|
||||
@@ -360,8 +392,8 @@ def test_receive_imf_failure(acfactory) -> None:
|
||||
assert snapshot.error is None
|
||||
|
||||
|
||||
def test_selfavatar_sync(acfactory, data, log) -> None:
|
||||
alice = acfactory.get_online_account()
|
||||
def test_selfavatar_sync(acf, rpcdata, log) -> None:
|
||||
alice = acf.get_online_account()
|
||||
|
||||
log.section("Alice adds a second device")
|
||||
alice2 = alice.clone()
|
||||
@@ -370,7 +402,7 @@ def test_selfavatar_sync(acfactory, data, log) -> None:
|
||||
alice2.start_io()
|
||||
|
||||
log.section("First device changes avatar")
|
||||
image = data.get_path("image/avatar1000x1000.jpg")
|
||||
image = rpcdata.get_path("image/avatar1000x1000.jpg")
|
||||
alice.set_config("selfavatar", image)
|
||||
avatar_config = alice.get_config("selfavatar")
|
||||
avatar_hash = os.path.basename(avatar_config)
|
||||
@@ -385,9 +417,9 @@ def test_selfavatar_sync(acfactory, data, log) -> None:
|
||||
assert avatar_config != avatar_config2
|
||||
|
||||
|
||||
def test_dont_move_sync_msgs(acfactory, direct_imap):
|
||||
addr, password = acfactory.get_credentials()
|
||||
ac1 = acfactory.get_unconfigured_account()
|
||||
def test_dont_move_sync_msgs(acf, direct_imap):
|
||||
addr, password = acf.get_credentials()
|
||||
ac1 = acf.get_unconfigured_account()
|
||||
ac1.set_config("bcc_self", "1")
|
||||
ac1.set_config("fix_is_chatmail", "1")
|
||||
ac1.add_or_update_transport({"addr": addr, "password": password})
|
||||
@@ -416,8 +448,8 @@ def test_dont_move_sync_msgs(acfactory, direct_imap):
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def test_reaction_seen_on_another_dev(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_reaction_seen_on_another_dev(acf) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
alice2 = alice.clone()
|
||||
alice2.start_io()
|
||||
|
||||
@@ -442,9 +474,38 @@ def test_reaction_seen_on_another_dev(acfactory) -> None:
|
||||
assert chat_id == alice2_chat_bob.id
|
||||
|
||||
|
||||
def test_is_bot(acfactory) -> None:
|
||||
def test_2nd_device_events_when_msgs_are_seen(acf) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
alice2 = alice.clone()
|
||||
alice2.start_io()
|
||||
|
||||
# Get an accepted chat, otherwise alice2 won't be notified about the 2nd message.
|
||||
chat_alice2 = alice2.create_chat(bob)
|
||||
chat_id_alice2 = chat_alice2.get_basic_snapshot().id
|
||||
|
||||
chat_bob_alice = bob.create_chat(alice)
|
||||
chat_bob_alice.send_text("Hello!")
|
||||
msg_alice = alice.wait_for_incoming_msg()
|
||||
assert alice2.wait_for_incoming_msg_event().chat_id == chat_id_alice2
|
||||
chat_bob_alice.send_text("What's new?")
|
||||
assert alice2.wait_for_incoming_msg_event().chat_id == chat_id_alice2
|
||||
chat_alice2 = alice2.get_chat_by_id(chat_id_alice2)
|
||||
assert chat_alice2.get_fresh_message_count() == 2
|
||||
|
||||
msg_alice.mark_seen()
|
||||
assert alice2.wait_for_msgs_changed_event().chat_id == chat_id_alice2
|
||||
assert chat_alice2.get_fresh_message_count() == 1
|
||||
|
||||
msg_id = alice.wait_for_msgs_changed_event().msg_id
|
||||
msg = alice.get_message_by_id(msg_id)
|
||||
msg.mark_seen()
|
||||
assert alice2.wait_for_event(EventType.MSGS_NOTICED).chat_id == chat_id_alice2
|
||||
assert chat_alice2.get_fresh_message_count() == 0
|
||||
|
||||
|
||||
def test_is_bot(acf) -> None:
|
||||
"""Test that we can recognize messages submitted by bots."""
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
alice_chat_bob = alice_contact_bob.create_chat()
|
||||
@@ -458,18 +519,18 @@ def test_is_bot(acfactory) -> None:
|
||||
assert snapshot.is_bot
|
||||
|
||||
|
||||
def test_bot(acfactory) -> None:
|
||||
def test_bot(acf) -> None:
|
||||
mock = MagicMock()
|
||||
user = (acfactory.get_online_accounts(1))[0]
|
||||
bot = acfactory.new_configured_bot()
|
||||
bot2 = acfactory.new_configured_bot()
|
||||
user = (acf.get_online_accounts(1))[0]
|
||||
bot = acf.new_configured_bot()
|
||||
bot2 = acf.new_configured_bot()
|
||||
|
||||
assert bot.is_configured()
|
||||
assert bot.account.get_config("bot") == "1"
|
||||
|
||||
hook = lambda e: mock.hook(e.msg_id) and None, events.RawEvent(EventType.INCOMING_MSG)
|
||||
bot.add_hook(*hook)
|
||||
event = acfactory.process_message(from_account=user, to_client=bot, text="Hello!")
|
||||
event = acf.process_message(from_account=user, to_client=bot, text="Hello!")
|
||||
snapshot = bot.account.get_message_by_id(event.msg_id).get_snapshot()
|
||||
assert not snapshot.is_bot
|
||||
mock.hook.assert_called_once_with(event.msg_id)
|
||||
@@ -482,28 +543,28 @@ def test_bot(acfactory) -> None:
|
||||
hook = track, events.NewMessage(r"hello")
|
||||
bot.add_hook(*hook)
|
||||
bot.add_hook(track, events.NewMessage(command="/help"))
|
||||
event = acfactory.process_message(from_account=user, to_client=bot, text="hello")
|
||||
event = acf.process_message(from_account=user, to_client=bot, text="hello")
|
||||
mock.hook.assert_called_with(event.msg_id)
|
||||
event = acfactory.process_message(from_account=user, to_client=bot, text="hello!")
|
||||
event = acf.process_message(from_account=user, to_client=bot, text="hello!")
|
||||
mock.hook.assert_called_with(event.msg_id)
|
||||
acfactory.process_message(from_account=bot2.account, to_client=bot, text="hello")
|
||||
acf.process_message(from_account=bot2.account, to_client=bot, text="hello")
|
||||
assert len(mock.hook.mock_calls) == 2 # bot messages are ignored between bots
|
||||
acfactory.process_message(from_account=user, to_client=bot, text="hey!")
|
||||
acf.process_message(from_account=user, to_client=bot, text="hey!")
|
||||
assert len(mock.hook.mock_calls) == 2
|
||||
bot.remove_hook(*hook)
|
||||
|
||||
mock.hook.reset_mock()
|
||||
acfactory.process_message(from_account=user, to_client=bot, text="hello")
|
||||
event = acfactory.process_message(from_account=user, to_client=bot, text="/help")
|
||||
acf.process_message(from_account=user, to_client=bot, text="hello")
|
||||
event = acf.process_message(from_account=user, to_client=bot, text="/help")
|
||||
mock.hook.assert_called_once_with(event.msg_id)
|
||||
|
||||
|
||||
def test_wait_next_messages(acfactory) -> None:
|
||||
alice = acfactory.get_online_account()
|
||||
def test_wait_next_messages(acf) -> None:
|
||||
alice = acf.get_online_account()
|
||||
|
||||
# Create a bot account so it does not receive device messages in the beginning.
|
||||
addr, password = acfactory.get_credentials()
|
||||
bot = acfactory.get_unconfigured_account()
|
||||
addr, password = acf.get_credentials()
|
||||
bot = acf.get_unconfigured_account()
|
||||
bot.set_config("bot", "1")
|
||||
bot.add_or_update_transport({"addr": addr, "password": password})
|
||||
assert bot.is_configured()
|
||||
@@ -529,19 +590,19 @@ def test_wait_next_messages(acfactory) -> None:
|
||||
assert snapshot.text == "Hello!"
|
||||
|
||||
|
||||
def test_import_export_backup(acfactory, tmp_path) -> None:
|
||||
alice = acfactory.new_configured_account()
|
||||
def test_import_export_backup(acf, tmp_path) -> None:
|
||||
alice = acf.new_configured_account()
|
||||
alice.export_backup(tmp_path)
|
||||
|
||||
files = list(tmp_path.glob("*.tar"))
|
||||
alice2 = acfactory.get_unconfigured_account()
|
||||
alice2 = acf.get_unconfigured_account()
|
||||
alice2.import_backup(files[0])
|
||||
|
||||
assert alice2.manager.get_system_info()
|
||||
|
||||
|
||||
def test_import_export_online_all(acfactory, tmp_path, data, log) -> None:
|
||||
(ac1, some1) = acfactory.get_online_accounts(2)
|
||||
def test_import_export_online_all(acf, tmp_path, rpcdata, log) -> None:
|
||||
(ac1, some1) = acf.get_online_accounts(2)
|
||||
|
||||
log.section("create some chat content")
|
||||
some1_addr = some1.get_config("addr")
|
||||
@@ -549,7 +610,7 @@ def test_import_export_online_all(acfactory, tmp_path, data, log) -> None:
|
||||
chat1.send_text("msg1")
|
||||
assert len(ac1.get_contacts()) == 1
|
||||
|
||||
original_image_path = data.get_path("image/avatar64x64.png")
|
||||
original_image_path = rpcdata.get_path("image/avatar64x64.png")
|
||||
chat1.send_file(str(original_image_path))
|
||||
|
||||
# Add another 100KB file that ensures that the progress is smooth enough
|
||||
@@ -600,7 +661,7 @@ def test_import_export_online_all(acfactory, tmp_path, data, log) -> None:
|
||||
ac1.start_io()
|
||||
|
||||
log.section("get fresh empty account")
|
||||
ac2 = acfactory.get_unconfigured_account()
|
||||
ac2 = acf.get_unconfigured_account()
|
||||
|
||||
log.section("import backup and check it's proper")
|
||||
ac2.import_backup(files_written[0])
|
||||
@@ -637,8 +698,8 @@ def test_import_export_online_all(acfactory, tmp_path, data, log) -> None:
|
||||
assert len(list(backupdir.glob("*.tar"))) == 2
|
||||
|
||||
|
||||
def test_import_export_keys(acfactory, tmp_path) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_import_export_keys(acf, tmp_path) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
alice_chat_bob = alice.create_chat(bob)
|
||||
alice_chat_bob.send_text("Hello Bob!")
|
||||
@@ -650,7 +711,7 @@ def test_import_export_keys(acfactory, tmp_path) -> None:
|
||||
alice_keys_path = tmp_path / "alice_keys"
|
||||
alice_keys_path.mkdir()
|
||||
alice.export_self_keys(alice_keys_path)
|
||||
alice = acfactory.resetup_account(alice)
|
||||
alice = acf.resetup_account(alice)
|
||||
alice.import_self_keys(alice_keys_path)
|
||||
|
||||
snapshot.chat.accept()
|
||||
@@ -686,30 +747,8 @@ def test_early_failure(tmp_path) -> None:
|
||||
rpc.start()
|
||||
|
||||
|
||||
def test_provider_info(rpc) -> None:
|
||||
account_id = rpc.add_account()
|
||||
|
||||
provider_info = rpc.get_provider_info(account_id, "example.org")
|
||||
assert provider_info["id"] == "example.com"
|
||||
|
||||
provider_info = rpc.get_provider_info(account_id, "uep7oiw4ahtaizuloith.org")
|
||||
assert provider_info is None
|
||||
|
||||
# Test MX record resolution.
|
||||
# This previously resulted in Gmail provider
|
||||
# because MX record pointed to google.com domain,
|
||||
# but MX record resolution has been removed.
|
||||
provider_info = rpc.get_provider_info(account_id, "github.com")
|
||||
assert provider_info is None
|
||||
|
||||
# Disable MX record resolution.
|
||||
rpc.set_config(account_id, "proxy_enabled", "1")
|
||||
provider_info = rpc.get_provider_info(account_id, "github.com")
|
||||
assert provider_info is None
|
||||
|
||||
|
||||
def test_mdn_doesnt_break_autocrypt(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_mdn_doesnt_break_autocrypt(acf) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
|
||||
@@ -739,10 +778,10 @@ def test_mdn_doesnt_break_autocrypt(acfactory) -> None:
|
||||
|
||||
|
||||
@pytest.mark.parametrize("n_accounts", [3, 2])
|
||||
def test_download_limit_chat_assignment(acfactory, tmp_path, n_accounts):
|
||||
def test_download_limit_chat_assignment(acf, tmp_path, n_accounts):
|
||||
download_limit = 300000
|
||||
|
||||
alice, *others = acfactory.get_online_accounts(n_accounts)
|
||||
alice, *others = acf.get_online_accounts(n_accounts)
|
||||
bob = others[0]
|
||||
|
||||
alice_group = alice.create_group("test group")
|
||||
@@ -778,10 +817,10 @@ def test_download_limit_chat_assignment(acfactory, tmp_path, n_accounts):
|
||||
assert snapshot.chat == bob_group
|
||||
|
||||
|
||||
def test_download_small_msg_first(acfactory, tmp_path):
|
||||
def test_download_small_msg_first(acf, tmp_path):
|
||||
download_limit = 70000
|
||||
|
||||
alice, bob0 = acfactory.get_online_accounts(2)
|
||||
alice, bob0 = acf.get_online_accounts(2)
|
||||
bob1 = bob0.clone()
|
||||
bob1.set_config("download_limit", str(download_limit))
|
||||
|
||||
@@ -802,14 +841,14 @@ def test_download_small_msg_first(acfactory, tmp_path):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("delete_chat", [False, True])
|
||||
def test_delete_available_msg(acfactory, tmp_path, direct_imap, delete_chat):
|
||||
def test_delete_available_msg(acf, tmp_path, direct_imap, delete_chat):
|
||||
"""
|
||||
Tests `DownloadState.AVAILABLE` message deletion on the receiver side.
|
||||
Also tests pre- and post-message deletion on the sender side.
|
||||
"""
|
||||
# Min. UI setting as of v2.35
|
||||
download_limit = 163840
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
bob.set_config("download_limit", str(download_limit))
|
||||
# Avoid immediate deletion from the server
|
||||
alice.set_config("bcc_self", "1")
|
||||
@@ -852,8 +891,8 @@ def test_delete_available_msg(acfactory, tmp_path, direct_imap, delete_chat):
|
||||
break
|
||||
|
||||
|
||||
def test_delete_fully_downloaded_msg(acfactory, tmp_path, direct_imap):
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_delete_fully_downloaded_msg(acf, tmp_path, direct_imap):
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
# Avoid immediate deletion from the server
|
||||
bob.set_config("bcc_self", "1")
|
||||
|
||||
@@ -888,8 +927,8 @@ def test_delete_fully_downloaded_msg(acfactory, tmp_path, direct_imap):
|
||||
break
|
||||
|
||||
|
||||
def test_imap_autodelete_fully_downloaded_msg(acfactory, tmp_path, direct_imap):
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_imap_autodelete_fully_downloaded_msg(acf, tmp_path, direct_imap):
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
chat_alice = alice.create_chat(bob)
|
||||
path = tmp_path / "large"
|
||||
@@ -917,12 +956,12 @@ def test_imap_autodelete_fully_downloaded_msg(acfactory, tmp_path, direct_imap):
|
||||
break
|
||||
|
||||
|
||||
def test_markseen_contact_request(acfactory):
|
||||
def test_markseen_contact_request(acf):
|
||||
"""
|
||||
Test that seen status is synchronized for contact request messages
|
||||
even though read receipt is not sent.
|
||||
"""
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
# Bob sets up a second device.
|
||||
bob2 = bob.clone()
|
||||
@@ -941,11 +980,11 @@ def test_markseen_contact_request(acfactory):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("team_profile", [True, False])
|
||||
def test_no_markseen_in_team_profile(team_profile, acfactory):
|
||||
def test_no_markseen_in_team_profile(team_profile, acf):
|
||||
"""
|
||||
Test that seen status is synchronized iff `team_profile` isn't set.
|
||||
"""
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
if team_profile:
|
||||
bob.set_config("team_profile", "1")
|
||||
|
||||
@@ -964,6 +1003,11 @@ def test_no_markseen_in_team_profile(team_profile, acfactory):
|
||||
|
||||
message.mark_seen()
|
||||
|
||||
# The MDN is queued in `smtp_mdns`, which is drained only after the regular
|
||||
# `smtp` queue, so "Outgoing message" would otherwise overtake it on the wire.
|
||||
# Wait for the read receipt to reach Alice before queueing "Outgoing message".
|
||||
alice.wait_for_event(EventType.MSG_READ)
|
||||
|
||||
# Send a message and wait until it arrives
|
||||
# in order to wait until Bob2 gets the markseen message.
|
||||
# This also tests that outgoing messages
|
||||
@@ -981,11 +1025,11 @@ def test_no_markseen_in_team_profile(team_profile, acfactory):
|
||||
assert message2.get_snapshot().state == MessageState.IN_SEEN
|
||||
|
||||
|
||||
def test_read_receipt(acfactory):
|
||||
def test_read_receipt(acf):
|
||||
"""
|
||||
Test sending a read receipt and ensure it is attributed to the correct contact.
|
||||
"""
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
alice_chat_bob = alice.create_chat(bob)
|
||||
alice_contact_bob = alice.create_contact(bob)
|
||||
@@ -1004,15 +1048,15 @@ def test_read_receipt(acfactory):
|
||||
assert read_receipt_cnt == 1
|
||||
|
||||
|
||||
def test_get_http_response(acfactory):
|
||||
alice = acfactory.new_configured_account()
|
||||
def test_get_http_response(acf):
|
||||
alice = acf.new_configured_account()
|
||||
http_response = alice._rpc.get_http_response(alice.id, "https://example.org")
|
||||
assert http_response["mimetype"] == "text/html"
|
||||
assert b"<title>Example Domain</title>" in base64.b64decode((http_response["blob"] + "==").encode())
|
||||
|
||||
|
||||
def test_configured_imap_certificate_checks(acfactory):
|
||||
alice = acfactory.new_configured_account()
|
||||
def test_configured_imap_certificate_checks(acf):
|
||||
alice = acf.new_configured_account()
|
||||
|
||||
# Certificate checks should be configured (not None)
|
||||
assert "cert_strict" in alice.get_info().used_transport_settings
|
||||
@@ -1031,8 +1075,8 @@ def test_configured_imap_certificate_checks(acfactory):
|
||||
assert "cert_old_automatic" not in alice.get_info().used_transport_settings
|
||||
|
||||
|
||||
def test_no_old_msg_is_fresh(acfactory):
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
def test_no_old_msg_is_fresh(acf):
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
ac1_clone = ac1.clone()
|
||||
ac1_clone.start_io()
|
||||
|
||||
@@ -1047,6 +1091,7 @@ def test_no_old_msg_is_fresh(acfactory):
|
||||
assert ac1.create_chat(ac2).get_fresh_message_count() == 1
|
||||
assert len(list(ac1.get_fresh_messages())) == 1
|
||||
|
||||
ac1_clone.wait_for_incoming_msg_event()
|
||||
ac1.wait_for_event(EventType.IMAP_INBOX_IDLE)
|
||||
|
||||
logging.info("Send a message from ac1_clone to ac2 and check that ac1 marks the first message as 'noticed'")
|
||||
@@ -1058,9 +1103,9 @@ def test_no_old_msg_is_fresh(acfactory):
|
||||
assert len(list(ac1.get_fresh_messages())) == 0
|
||||
|
||||
|
||||
def test_rename_synchronization(acfactory):
|
||||
def test_rename_synchronization(acf):
|
||||
"""Test synchronization of contact renaming."""
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
alice2 = alice.clone()
|
||||
alice2.bring_online()
|
||||
|
||||
@@ -1075,9 +1120,9 @@ def test_rename_synchronization(acfactory):
|
||||
assert alice2_msg.sender.get_snapshot().display_name == "Bobby"
|
||||
|
||||
|
||||
def test_rename_group(acfactory):
|
||||
def test_rename_group(acf):
|
||||
"""Test renaming the group."""
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
alice_group = alice.create_group("Test group")
|
||||
alice_contact_bob = alice.create_contact(bob)
|
||||
@@ -1090,6 +1135,7 @@ def test_rename_group(acfactory):
|
||||
bob.wait_for_event(EventType.CHATLIST_ITEM_CHANGED)
|
||||
|
||||
for name in ["Baz", "Foo bar", "Xyzzy"]:
|
||||
time.sleep(1)
|
||||
alice_group.set_name(name)
|
||||
bob.wait_for_event(EventType.CHATLIST_ITEM_CHANGED)
|
||||
bob.wait_for_event(EventType.CHATLIST_ITEM_CHANGED)
|
||||
@@ -1105,8 +1151,8 @@ def test_get_all_accounts_deadlock(rpc):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("all_devices_online", [True, False])
|
||||
def test_leave_broadcast(acfactory, all_devices_online):
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_leave_broadcast(acf, all_devices_online):
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
bob2 = bob.clone()
|
||||
|
||||
@@ -1206,8 +1252,8 @@ def test_leave_broadcast(acfactory, all_devices_online):
|
||||
check_account(bob2, bob2.create_contact(alice), inviter_side=False)
|
||||
|
||||
|
||||
def test_leave_and_delete_group(acfactory, log):
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_leave_and_delete_group(acf, log):
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
log.section("Alice creates a group")
|
||||
alice_chat = alice.create_group("Group")
|
||||
@@ -1230,11 +1276,13 @@ def test_leave_and_delete_group(acfactory, log):
|
||||
alice.wait_for_event(EventType.CHAT_MODIFIED)
|
||||
|
||||
|
||||
def test_immediate_autodelete(acfactory, direct_imap, log):
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
|
||||
# "1" means delete immediately, while "0" means do not delete
|
||||
ac2.set_config("delete_server_after", "1")
|
||||
def test_immediate_autodelete(acf, direct_imap, log):
|
||||
"""
|
||||
`bcc_self` is off by default,
|
||||
so that messages are supposed to be immediately autodeleted
|
||||
"""
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
assert ac1.get_config("bcc_self") == "0"
|
||||
|
||||
log.section("ac1: create chat with ac2")
|
||||
chat1 = ac1.create_chat(ac2)
|
||||
@@ -1264,8 +1312,8 @@ def test_immediate_autodelete(acfactory, direct_imap, log):
|
||||
assert ev.msg_id == sent_msg.id
|
||||
|
||||
|
||||
def test_background_fetch(acfactory, dc):
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
def test_background_fetch(acf, dc):
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
ac1.stop_io()
|
||||
|
||||
ac1_chat = ac1.create_chat(ac2)
|
||||
@@ -1301,8 +1349,8 @@ def test_background_fetch(acfactory, dc):
|
||||
break
|
||||
|
||||
|
||||
def test_message_exists(acfactory):
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
def test_message_exists(acf):
|
||||
ac1, ac2 = acf.get_online_accounts(2)
|
||||
chat = ac1.create_chat(ac2)
|
||||
message1 = chat.send_text("Hello!")
|
||||
message2 = chat.send_text("Hello again!")
|
||||
@@ -1320,7 +1368,7 @@ def test_message_exists(acfactory):
|
||||
assert not message2.exists()
|
||||
|
||||
|
||||
def test_synchronize_member_list_on_group_rejoin(acfactory, log):
|
||||
def test_synchronize_member_list_on_group_rejoin(acf, log):
|
||||
"""
|
||||
Test that user recreates group member list when it joins the group again.
|
||||
ac1 creates a group with two other accounts: ac2 and ac3
|
||||
@@ -1328,7 +1376,7 @@ def test_synchronize_member_list_on_group_rejoin(acfactory, log):
|
||||
ac2 did not see that ac3 is removed, so it should rebuild member list from scratch.
|
||||
"""
|
||||
log.section("setting up accounts, accepted with each other")
|
||||
ac1, ac2, ac3 = accounts = acfactory.get_online_accounts(3)
|
||||
ac1, ac2, ac3 = accounts = acf.get_online_accounts(3)
|
||||
|
||||
log.section("ac1: creating group chat with 2 other members")
|
||||
chat = ac1.create_group("title1")
|
||||
@@ -1364,17 +1412,17 @@ def test_synchronize_member_list_on_group_rejoin(acfactory, log):
|
||||
assert msg.get_snapshot().chat.num_contacts() == 2
|
||||
|
||||
|
||||
def test_large_message(acfactory) -> None:
|
||||
def test_large_message(acf, rpcdata) -> None:
|
||||
"""
|
||||
Test sending large message without download limit set,
|
||||
so it is sent with pre-message but downloaded without user interaction.
|
||||
"""
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
alice_chat_bob = alice.create_chat(bob)
|
||||
alice_chat_bob.send_message(
|
||||
"Hello World, this message is bigger than 5 bytes",
|
||||
file="../test-data/image/screenshot.jpg",
|
||||
file=rpcdata.get_path("image/screenshot.jpg"),
|
||||
)
|
||||
|
||||
msg = bob.wait_for_incoming_msg()
|
||||
|
||||
96
deltachat-rpc-client/tests/test_sql_schema_docs.py
Normal file
96
deltachat-rpc-client/tests/test_sql_schema_docs.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""Test that docs/schema.sql matches the actual database schema."""
|
||||
|
||||
import re
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
DOC_PATH = Path(__file__).resolve().parents[2] / "docs" / "schema.sql"
|
||||
|
||||
|
||||
def strip_comments(sql):
|
||||
return re.sub(r"--[^\n]*", "", sql)
|
||||
|
||||
|
||||
def normalize(stmt):
|
||||
stmt = re.sub(r"\s+", " ", stmt).strip()
|
||||
stmt = stmt.replace("CREATE TABLE IF NOT EXISTS ", "CREATE TABLE ")
|
||||
return re.sub(r'"(\w+)"', r"\1", stmt)
|
||||
|
||||
|
||||
def split_table(body):
|
||||
items = []
|
||||
depth = 0
|
||||
current = ""
|
||||
for char in body:
|
||||
if char == "(":
|
||||
depth += 1
|
||||
elif char == ")":
|
||||
depth -= 1
|
||||
if char == "," and depth == 0:
|
||||
items.append(current.strip())
|
||||
current = ""
|
||||
else:
|
||||
current += char
|
||||
if current.strip():
|
||||
items.append(current.strip())
|
||||
return items
|
||||
|
||||
|
||||
def parse_schema(sql):
|
||||
objects = {}
|
||||
for raw_stmt in strip_comments(sql).split(";"):
|
||||
stmt = normalize(raw_stmt)
|
||||
if not stmt:
|
||||
continue
|
||||
match = re.match(r"CREATE TABLE (\w+) ?\((.*)\)( STRICT)?$", stmt)
|
||||
if match:
|
||||
name, body, strict = match.groups()
|
||||
objects[f"table {name}"] = {
|
||||
"items": sorted(split_table(body)),
|
||||
"strict": bool(strict),
|
||||
}
|
||||
continue
|
||||
match = re.match(r"CREATE (?:UNIQUE )?INDEX (\w+)", stmt)
|
||||
if match:
|
||||
objects[f"index {match.group(1)}"] = {"sql": stmt}
|
||||
continue
|
||||
objects[stmt[:60]] = {"sql": stmt}
|
||||
return objects
|
||||
|
||||
|
||||
def format_diff(documented, real):
|
||||
if "items" in documented and "items" in real:
|
||||
lines = []
|
||||
# disregards order
|
||||
for item in sorted(set(documented["items"]) - set(real["items"])):
|
||||
lines.append(f" documented but not in the database: {item}")
|
||||
for item in sorted(set(real["items"]) - set(documented["items"])):
|
||||
lines.append(f" in the database but not documented: {item}")
|
||||
if documented["strict"] != real["strict"]:
|
||||
lines.append(f" STRICT: documented={documented['strict']} actual={real['strict']}")
|
||||
return "\n".join(lines)
|
||||
return f" documented: {documented}\n actual: {real}"
|
||||
|
||||
|
||||
def read_database_schema(dbfile):
|
||||
with sqlite3.connect(f"file:{dbfile}?mode=ro", uri=True) as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT sql FROM sqlite_master WHERE sql IS NOT NULL AND name NOT LIKE 'sqlite_%'",
|
||||
).fetchall()
|
||||
return ";\n".join(row[0] for row in rows)
|
||||
|
||||
|
||||
def test_documented_schema_matches_database(acf):
|
||||
account = acf.get_unconfigured_account()
|
||||
real = parse_schema(read_database_schema(account.get_info()["database_dir"]))
|
||||
documented = parse_schema(DOC_PATH.read_text())
|
||||
|
||||
problems = []
|
||||
for name in sorted(real.keys() - documented.keys()):
|
||||
problems.append(f"{name} exists in the database but is not documented")
|
||||
for name in sorted(documented.keys() - real.keys()):
|
||||
problems.append(f"{name} is documented but does not exist in the database")
|
||||
for name in sorted(documented.keys() & real.keys()):
|
||||
if documented[name] != real[name]:
|
||||
problems.append(f"{name} differs:\n{format_diff(documented[name], real[name])}")
|
||||
assert not problems, "documented schema deviates from the database:\n" + "\n".join(problems)
|
||||
@@ -1,5 +1,5 @@
|
||||
def test_vcard(acfactory) -> None:
|
||||
alice, bob, fiona = acfactory.get_online_accounts(3)
|
||||
def test_vcard(acf) -> None:
|
||||
alice, bob, fiona = acf.get_online_accounts(3)
|
||||
|
||||
bob.create_chat(alice)
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
def test_webxdc(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_webxdc(acf, rpcdata) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
alice_chat_bob = alice_contact_bob.create_chat()
|
||||
alice_chat_bob.send_message(text="Let's play chess!", file="../test-data/webxdc/chess.xdc")
|
||||
alice_chat_bob.send_message(text="Let's play chess!", file=rpcdata.get_path("webxdc/chess.xdc"))
|
||||
|
||||
event = bob.wait_for_incoming_msg_event()
|
||||
bob_chat_alice = bob.get_chat_by_id(event.chat_id)
|
||||
@@ -18,6 +18,8 @@ def test_webxdc(acfactory) -> None:
|
||||
"sourceCodeUrl": None,
|
||||
"summary": None,
|
||||
"selfAddr": webxdc_info["selfAddr"],
|
||||
"isAppSender": False,
|
||||
"isBroadcast": False,
|
||||
"sendUpdateInterval": 1000,
|
||||
"sendUpdateMaxSize": 18874368,
|
||||
}
|
||||
@@ -41,12 +43,12 @@ def test_webxdc(acfactory) -> None:
|
||||
]
|
||||
|
||||
|
||||
def test_webxdc_insert_lots_of_updates(acfactory) -> None:
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
def test_webxdc_insert_lots_of_updates(acf, rpcdata) -> None:
|
||||
alice, bob = acf.get_online_accounts(2)
|
||||
|
||||
alice_contact_bob = alice.create_contact(bob, "Bob")
|
||||
alice_chat_bob = alice_contact_bob.create_chat()
|
||||
message = alice_chat_bob.send_message(text="Let's play chess!", file="../test-data/webxdc/chess.xdc")
|
||||
message = alice_chat_bob.send_message(text="Let's play chess!", file=rpcdata.get_path("webxdc/chess.xdc"))
|
||||
|
||||
for i in range(2000):
|
||||
message.send_webxdc_status_update({"payload": str(i)}, "description")
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "2.48.0-dev"
|
||||
version = "2.60.0-dev"
|
||||
description = "DeltaChat JSON-RPC server"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
readme = "README.md"
|
||||
license = "MPL-2.0"
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { startDeltaChat } from "@deltachat/stdio-rpc-server";
|
||||
import { C } from "@deltachat/jsonrpc-client";
|
||||
|
||||
async function main() {
|
||||
const dc = await startDeltaChat("deltachat-data");
|
||||
const dc = startDeltaChat("deltachat-data");
|
||||
console.log(await dc.rpc.getSystemInfo());
|
||||
dc.close()
|
||||
}
|
||||
|
||||
11
deltachat-rpc-server/npm-package/index.d.ts
vendored
11
deltachat-rpc-server/npm-package/index.d.ts
vendored
@@ -15,7 +15,7 @@ export interface SearchOptions {
|
||||
*/
|
||||
export function getRPCServerPath(
|
||||
options?: Partial<SearchOptions>
|
||||
): Promise<string>;
|
||||
): string;
|
||||
|
||||
|
||||
|
||||
@@ -33,8 +33,15 @@ export interface StartOptions {
|
||||
* @param directory directory for accounts folder
|
||||
* @param options
|
||||
*/
|
||||
export function startDeltaChat(directory: string, options?: Partial<SearchOptions & StartOptions> ): Promise<DeltaChatOverJsonRpcServer>
|
||||
export function startDeltaChat(directory: string, options?: Partial<SearchOptions & StartOptions> ): DeltaChatOverJsonRpcServer
|
||||
|
||||
export class DeltaChatOverJsonRpc extends StdioDeltaChat {
|
||||
constructor(
|
||||
directory: string,
|
||||
options?: Partial<SearchOptions & StartOptions>
|
||||
);
|
||||
readonly pathToServerBinary: string;
|
||||
}
|
||||
|
||||
export namespace FnTypes {
|
||||
export type getRPCServerPath = typeof getRPCServerPath
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//@ts-check
|
||||
import { spawn } from "node:child_process";
|
||||
import { stat } from "node:fs/promises";
|
||||
import { statSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import process from "node:process";
|
||||
import { ENV_VAR_NAME, PATH_EXECUTABLE_NAME } from "./src/const.js";
|
||||
@@ -36,7 +36,7 @@ function findRPCServerInNodeModules() {
|
||||
}
|
||||
|
||||
/** @type {import("./index").FnTypes.getRPCServerPath} */
|
||||
export async function getRPCServerPath(options = {}) {
|
||||
export function getRPCServerPath(options = {}) {
|
||||
const { takeVersionFromPATH, disableEnvPath } = {
|
||||
takeVersionFromPATH: false,
|
||||
disableEnvPath: false,
|
||||
@@ -45,7 +45,7 @@ export async function getRPCServerPath(options = {}) {
|
||||
// 1. check if it is set as env var
|
||||
if (process.env[ENV_VAR_NAME] && !disableEnvPath) {
|
||||
try {
|
||||
if (!(await stat(process.env[ENV_VAR_NAME])).isFile()) {
|
||||
if (!statSync(process.env[ENV_VAR_NAME]).isFile()) {
|
||||
throw new Error(
|
||||
`expected ${ENV_VAR_NAME} to point to the deltachat-rpc-server executable`
|
||||
);
|
||||
@@ -68,41 +68,49 @@ export async function getRPCServerPath(options = {}) {
|
||||
import { StdioDeltaChat } from "@deltachat/jsonrpc-client";
|
||||
|
||||
/** @type {import("./index").FnTypes.startDeltaChat} */
|
||||
export async function startDeltaChat(directory, options = {}) {
|
||||
const pathToServerBinary = await getRPCServerPath(options);
|
||||
const server = spawn(pathToServerBinary, {
|
||||
env: {
|
||||
RUST_LOG: process.env.RUST_LOG,
|
||||
DC_ACCOUNTS_PATH: directory,
|
||||
},
|
||||
stdio: ["pipe", "pipe", options.muteStdErr ? "ignore" : "inherit"],
|
||||
});
|
||||
|
||||
server.on("error", (err) => {
|
||||
throw new Error(FAILED_TO_START_SERVER_EXECUTABLE(pathToServerBinary, err));
|
||||
});
|
||||
let shouldClose = false;
|
||||
|
||||
server.on("exit", () => {
|
||||
if (shouldClose) {
|
||||
return;
|
||||
}
|
||||
throw new Error("Server quit");
|
||||
});
|
||||
|
||||
/** @type {import('./index').DeltaChatOverJsonRpcServer} */
|
||||
//@ts-expect-error
|
||||
const dc = new StdioDeltaChat(server.stdin, server.stdout, true);
|
||||
|
||||
dc.close = () => {
|
||||
shouldClose = true;
|
||||
if (!server.kill()) {
|
||||
console.log("server termination failed");
|
||||
}
|
||||
};
|
||||
|
||||
//@ts-expect-error
|
||||
dc.pathToServerBinary = pathToServerBinary;
|
||||
|
||||
return dc;
|
||||
export function startDeltaChat(directory, options = {}) {
|
||||
return new DeltaChatOverJsonRpc(directory, options);
|
||||
}
|
||||
|
||||
export class DeltaChatOverJsonRpc extends StdioDeltaChat {
|
||||
/**
|
||||
*
|
||||
* @param {string} directory
|
||||
* @param {Partial<import("./index").SearchOptions & import("./index").StartOptions>} options
|
||||
*/
|
||||
constructor(directory, options = {}) {
|
||||
const pathToServerBinary = getRPCServerPath(options);
|
||||
const server = spawn(pathToServerBinary, {
|
||||
env: {
|
||||
RUST_LOG: process.env.RUST_LOG,
|
||||
DC_ACCOUNTS_PATH: directory,
|
||||
},
|
||||
stdio: ["pipe", "pipe", options.muteStdErr ? "ignore" : "inherit"],
|
||||
});
|
||||
|
||||
server.on("error", (err) => {
|
||||
throw new Error(
|
||||
FAILED_TO_START_SERVER_EXECUTABLE(pathToServerBinary, err)
|
||||
);
|
||||
});
|
||||
let shouldClose = false;
|
||||
|
||||
server.on("exit", () => {
|
||||
if (shouldClose) {
|
||||
return;
|
||||
}
|
||||
throw new Error("Server quit");
|
||||
});
|
||||
|
||||
super(server.stdin, server.stdout, true);
|
||||
|
||||
this.close = () => {
|
||||
shouldClose = true;
|
||||
if (!server.kill()) {
|
||||
console.log("server termination failed");
|
||||
}
|
||||
};
|
||||
|
||||
this.pathToServerBinary = pathToServerBinary;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "index.d.ts",
|
||||
"version": "2.48.0-dev"
|
||||
"version": "2.60.0-dev"
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use deltachat::constants::DC_VERSION_STR;
|
||||
use deltachat_jsonrpc::api::{Accounts, CommandApi};
|
||||
use futures_lite::stream::StreamExt;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "deltachat-time"
|
||||
version = "1.0.0"
|
||||
description = "Time-related tools"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "deltachat_derive"
|
||||
version = "2.0.0"
|
||||
edition = "2018"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = "2"
|
||||
syn = "3"
|
||||
quote = "1"
|
||||
|
||||
@@ -14,7 +14,7 @@ pub fn to_sql_derive(input: TokenStream) -> TokenStream {
|
||||
let ast: syn::DeriveInput = syn::parse(input).unwrap();
|
||||
let name = &ast.ident;
|
||||
|
||||
let gen = quote! {
|
||||
let q = quote! {
|
||||
impl rusqlite::types::ToSql for #name {
|
||||
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
|
||||
let num = *self as i64;
|
||||
@@ -24,7 +24,7 @@ pub fn to_sql_derive(input: TokenStream) -> TokenStream {
|
||||
}
|
||||
}
|
||||
};
|
||||
gen.into()
|
||||
q.into()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(FromSql)]
|
||||
@@ -32,7 +32,7 @@ pub fn from_sql_derive(input: TokenStream) -> TokenStream {
|
||||
let ast: syn::DeriveInput = syn::parse(input).unwrap();
|
||||
let name = &ast.ident;
|
||||
|
||||
let gen = quote! {
|
||||
let q = quote! {
|
||||
impl rusqlite::types::FromSql for #name {
|
||||
fn column_result(col: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
|
||||
let inner = rusqlite::types::FromSql::column_result(col)?;
|
||||
@@ -44,5 +44,5 @@ pub fn from_sql_derive(input: TokenStream) -> TokenStream {
|
||||
}
|
||||
}
|
||||
};
|
||||
gen.into()
|
||||
q.into()
|
||||
}
|
||||
|
||||
37
deny.toml
37
deny.toml
@@ -18,12 +18,42 @@ ignore = [
|
||||
# this should be fixed by upgrading to iroh 1.0 once it is released.
|
||||
"RUSTSEC-2025-0134",
|
||||
|
||||
# Unmaintained proc-macro-error2
|
||||
# Transitive dependency of typescript-type-def 0.5.13.
|
||||
# <https://rustsec.org/advisories/RUSTSEC-2026-0173>
|
||||
"RUSTSEC-2026-0173",
|
||||
|
||||
# rustls-webpki v0.102.8
|
||||
# We cannot upgrade to >=0.103.10 because
|
||||
# it is a transitive dependency of iroh 0.35.0
|
||||
# which depends on ^0.102.
|
||||
# <https://rustsec.org/advisories/RUSTSEC-2026-0049>
|
||||
# <https://rustsec.org/advisories/RUSTSEC-2026-0098>
|
||||
# <https://rustsec.org/advisories/RUSTSEC-2026-0099>
|
||||
"RUSTSEC-2026-0049",
|
||||
"RUSTSEC-2026-0098",
|
||||
"RUSTSEC-2026-0099",
|
||||
|
||||
# Panic in CRL signature checks.
|
||||
# We do not check CRL and cannot update rustls-webpki 0.102.8
|
||||
# which is a dependency of iroh 0.35.0.
|
||||
# <https://rustsec.org/advisories/RUSTSEC-2026-0104>
|
||||
"RUSTSEC-2026-0104",
|
||||
|
||||
# hickory-proto 0.25.2 unbounded loop in DNSSEC code.
|
||||
# Dependency of iroh 0.35.0, cannot be updated as of 2026-05-02.
|
||||
# <https://rustsec.org/advisories/RUSTSEC-2026-0118>
|
||||
"RUSTSEC-2026-0118",
|
||||
|
||||
# hickory-proto 0.25.2 quadratic complexity issue.
|
||||
# Dependency of iroh 0.35.0, cannot be updated as of 2026-05-02.
|
||||
# <https://rustsec.org/advisories/RUSTSEC-2026-0119>
|
||||
"RUSTSEC-2026-0119",
|
||||
|
||||
# Timing side channel in ml-dsa dependency of rPGP.
|
||||
# We enable PQC for encryption rather than signatures.
|
||||
# <https://rustsec.org/advisories/RUSTSEC-2025-0144>
|
||||
"RUSTSEC-2025-0144",
|
||||
]
|
||||
|
||||
[bans]
|
||||
@@ -35,12 +65,15 @@ skip = [
|
||||
{ name = "async-channel", version = "1.9.0" },
|
||||
{ name = "bitflags", version = "1.3.2" },
|
||||
{ name = "constant_time_eq", version = "0.3.1" },
|
||||
{ name = "cpufeatures", version = "0.2.17" },
|
||||
{ name = "derive_more-impl", version = "1.0.0" },
|
||||
{ name = "derive_more", version = "1.0.0" },
|
||||
{ name = "event-listener", version = "2.5.3" },
|
||||
{ name = "getrandom", version = "0.2.12" },
|
||||
{ name = "getrandom", version = "0.3.3" },
|
||||
{ name = "heck", version = "0.4.1" },
|
||||
{ name = "http", version = "0.2.12" },
|
||||
{ name = "hybrid-array", version = "0.2.3" },
|
||||
{ name = "linux-raw-sys", version = "0.4.14" },
|
||||
{ name = "lru", version = "0.12.5" },
|
||||
{ name = "netlink-packet-route", version = "0.17.1" },
|
||||
@@ -48,14 +81,18 @@ skip = [
|
||||
{ name = "rand_chacha", version = "0.3.1" },
|
||||
{ name = "rand_core", version = "0.6.4" },
|
||||
{ name = "rand", version = "0.8.5" },
|
||||
{ name = "r-efi", version = "5.2.0" },
|
||||
{ name = "rustix", version = "0.38.44" },
|
||||
{ name = "rustls-webpki", version = "0.102.8" },
|
||||
{ name = "serdect", version = "0.2.0" },
|
||||
{ name = "snafu-derive", version = "0.8.5" },
|
||||
{ name = "snafu", version = "0.8.5" },
|
||||
{ name = "socket2", version = "0.5.9" },
|
||||
{ name = "spin", version = "0.9.8" },
|
||||
{ name = "strum_macros", version = "0.26.2" },
|
||||
{ name = "strum", version = "0.26.2" },
|
||||
{ name = "syn", version = "1.0.109" },
|
||||
{ name = "syn", version = "2.0.118" },
|
||||
{ name = "thiserror-impl", version = "1.0.69" },
|
||||
{ name = "thiserror", version = "1.0.69" },
|
||||
{ name = "toml_datetime", version = "0.6.11" },
|
||||
|
||||
783
docs/schema.sql
Normal file
783
docs/schema.sql
Normal file
@@ -0,0 +1,783 @@
|
||||
-- Commented SQLite database schema.
|
||||
--
|
||||
-- This file should only be used for documentation,
|
||||
-- do not run this SQL e.g. to create databases.
|
||||
-- This is because we want to be 100% sure
|
||||
-- that new users and users who run the migrations
|
||||
-- get the same database schema.
|
||||
--
|
||||
-- This is a dump of the database schema using `sqlite3 dc.db .schema`,
|
||||
-- formatted, reordered and commented afterwards.
|
||||
-- Raw dump of the database schema does not have comments
|
||||
-- for deprecated columns and columns added by migrations.
|
||||
|
||||
CREATE TABLE config (
|
||||
id INTEGER PRIMARY KEY,
|
||||
keyname TEXT UNIQUE,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX config_index1 ON config (keyname);
|
||||
|
||||
CREATE TABLE contacts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
-- Name of the contact as set by the user.
|
||||
name TEXT DEFAULT '',
|
||||
|
||||
-- Normalized name for search.
|
||||
name_normalized TEXT,
|
||||
|
||||
-- Email address last seen as the From address for this contact.
|
||||
-- For address-contacts that have empty "fingerprint" this should never change.
|
||||
-- For key-contacts this address may change when a new signed message is received.
|
||||
--
|
||||
-- This is the address messages are sent to unless the key
|
||||
-- advertises a different set of addresses, in which case
|
||||
-- this address should be ignored
|
||||
-- (and not appended to the list of addresses advertised in the key).
|
||||
addr TEXT DEFAULT '' COLLATE NOCASE,
|
||||
|
||||
-- The origin or source of the contact,
|
||||
-- e.g. whether the contact was added from some chat
|
||||
-- or via SecureJoin.
|
||||
origin INTEGER DEFAULT 0,
|
||||
|
||||
-- True if the contact is blocked.
|
||||
-- Unlike the chat, contact is either blocked or not,
|
||||
-- there is no third "contact request" state.
|
||||
blocked INTEGER DEFAULT 0,
|
||||
|
||||
-- Timestamp of the last time any message was received from this contact.
|
||||
last_seen INTEGER DEFAULT 0,
|
||||
|
||||
-- Key-value parameters.
|
||||
param TEXT DEFAULT '',
|
||||
|
||||
-- Name of the contact as sent by the contact itself.
|
||||
authname TEXT DEFAULT '',
|
||||
|
||||
-- Timestamp of the last time we have sent our avatar to this contact.
|
||||
-- Used to decide whether to send the avatar.
|
||||
-- Normally avatars are resent after 14 days.
|
||||
-- This column is reset to 0 when avatar is changed.
|
||||
selfavatar_sent INTEGER DEFAULT 0,
|
||||
|
||||
-- Last seen message signature from this contact, also known as bio in the UI.
|
||||
status TEXT DEFAULT '',
|
||||
|
||||
-- True if the contact is a bot.
|
||||
is_bot INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
-- OpenPGP key fingerprint for "key-contacts",
|
||||
-- empty string for "address-contacts".
|
||||
fingerprint TEXT NOT NULL DEFAULT '',
|
||||
|
||||
-- ID of the contact that has "introduced" us to this contact
|
||||
-- by sharing the key with a verified attribute or in a "verified" chat.
|
||||
verifier INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE INDEX contacts_index1 ON contacts (name COLLATE NOCASE);
|
||||
CREATE INDEX contacts_index2 ON contacts (addr COLLATE NOCASE);
|
||||
CREATE INDEX contacts_fingerprint_index ON contacts (fingerprint);
|
||||
|
||||
CREATE TABLE chats (
|
||||
-- Chat ID 0 should never be used as it is used as a sentinel value in some APIs.
|
||||
--
|
||||
-- Chat IDs 1 to 9, including 9, are reserved.
|
||||
-- The first proper chat gets ID 10, but may not exist if it is deleted.
|
||||
--
|
||||
-- Chat ID 3 is the trash chat and this chat ID is assigned to deleted messages
|
||||
-- to create "tombstones".
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
-- Chat type, e.g. 100 for single chat, 120 for a group etc.
|
||||
-- Check the Chattype enumeration in the code for concrete values.
|
||||
type INTEGER DEFAULT 0,
|
||||
|
||||
name TEXT DEFAULT '',
|
||||
|
||||
-- Normalized name for search.
|
||||
name_normalized TEXT,
|
||||
|
||||
-- 0 for visible chat, 1 for hidden and 2 for contact request.
|
||||
-- 1 does not necessarily mean that the contact is blocked.
|
||||
blocked INTEGER DEFAULT 0,
|
||||
|
||||
-- Chat-Group-ID header for encrypted groups and channels.
|
||||
-- Empty for unencrypted groups and single chats.
|
||||
grpid TEXT DEFAULT '',
|
||||
|
||||
-- Key-value parameters.
|
||||
param TEXT DEFAULT '',
|
||||
|
||||
-- Chat visibility.
|
||||
-- 0 for normal, 1 for archived and 2 for pinned chats.
|
||||
archived INTEGER DEFAULT 0,
|
||||
|
||||
locations_send_begin INTEGER DEFAULT 0,
|
||||
locations_send_until INTEGER DEFAULT 0,
|
||||
locations_last_sent INTEGER DEFAULT 0,
|
||||
|
||||
-- Time when the chat was created.
|
||||
-- Used for sorting in the chatlist when the chat has no messages.
|
||||
created_timestamp INTEGER DEFAULT 0,
|
||||
|
||||
-- 0 if the chat is not muted.
|
||||
-- -1 if the chat is muted forever.
|
||||
-- Otherwise the timestamp until which the chat is muted.
|
||||
muted_until INTEGER DEFAULT 0,
|
||||
|
||||
-- Disappearing messages timer.
|
||||
-- 0 means the timer is disabled.
|
||||
ephemeral_timer INTEGER,
|
||||
|
||||
-- Deprecated, but still used to send Chat-Verified headers
|
||||
-- for existing protected chats.
|
||||
-- All new chats are created as "not protected".
|
||||
protected INTEGER DEFAULT 0,
|
||||
|
||||
gossiped_timestamp INTEGER DEFAULT 0, -- deprecated 2025-04-08, replaced with gossip_timestamp table
|
||||
|
||||
-- Unused columns, drafts are now tracked as separate
|
||||
-- messages with a special msgs.state value.
|
||||
draft_timestamp INTEGER DEFAULT 0,
|
||||
draft_txt TEXT DEFAULT ''
|
||||
);
|
||||
CREATE INDEX chats_index1 ON chats (grpid);
|
||||
CREATE INDEX chats_index2 ON chats (archived);
|
||||
CREATE INDEX chats_index3 ON chats (locations_send_until);
|
||||
CREATE INDEX chats_index4 ON chats (name);
|
||||
|
||||
CREATE TABLE chats_descriptions (
|
||||
chat_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
description TEXT NOT NULL DEFAULT ''
|
||||
) STRICT;
|
||||
|
||||
-- Chat member lists.
|
||||
-- Saved messages has only self.
|
||||
-- Single chats have only the contact, but not self.
|
||||
-- Groups have self if we are part of the group.
|
||||
CREATE TABLE chats_contacts (
|
||||
chat_id INTEGER,
|
||||
contact_id INTEGER,
|
||||
add_timestamp NOT NULL DEFAULT 0,
|
||||
remove_timestamp NOT NULL DEFAULT 0,
|
||||
UNIQUE(chat_id, contact_id)
|
||||
);
|
||||
CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);
|
||||
CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);
|
||||
|
||||
-- This table contains "message bubbles" that are normally visible
|
||||
-- and "tombstones" that are put into the trash chat
|
||||
-- or have a "hidden" column set to the true value.
|
||||
--
|
||||
-- Tombstones are used to avoid downloading and processing
|
||||
-- the same messages twice, e.g. when the message is deleted
|
||||
-- but another copy of it arrives later.
|
||||
CREATE TABLE msgs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
-- The messages may be split into pre- and post-message.
|
||||
-- Pre-messages have a Chat-Post-Message-ID header
|
||||
-- which contains the Message-ID of the post-message.
|
||||
-- Post-messages have a Chat-Is-Post-Message header.
|
||||
--
|
||||
-- For outgoing messages that are split into pre-message
|
||||
-- and post-message, rfc724_mid contains the Message-ID
|
||||
-- of the post-message, and pre_rfc724_mid
|
||||
-- contains the Message-ID of the pre-message.
|
||||
--
|
||||
-- For incoming messages, when a pre-message arrives,
|
||||
-- rfc724_mid is set to the Message-ID of the post-message,
|
||||
-- and pre_rfc724_mid is set to the Message-ID of the arrived pre-messsage.
|
||||
-- For other messages rfc724_mid is taken from the Message-ID
|
||||
-- and pre_rfc724_mid is set to empty string.
|
||||
|
||||
-- Message-ID as defined in RFC 724 (now replaced by RFC 5322)
|
||||
rfc724_mid TEXT DEFAULT '',
|
||||
-- Message-ID of the pre-message.
|
||||
pre_rfc724_mid TEXT DEFAULT '',
|
||||
|
||||
-- Chat ID.
|
||||
--
|
||||
-- Chat ID 3 is the trash chat, messages with this ID are tombstones.
|
||||
chat_id INTEGER DEFAULT 0,
|
||||
|
||||
-- Contact ID of the message author.
|
||||
from_id INTEGER DEFAULT 0,
|
||||
|
||||
-- Mostly unused Contact ID of the first recipient or 0.
|
||||
-- For info messages set to ContactID::INFO (2).
|
||||
--
|
||||
-- If to_id is set to 2, the message must be displayed
|
||||
-- as an info message even if from_id is not set to 2.
|
||||
--
|
||||
-- Info messages generated by Webxdc status updates
|
||||
-- have from_id set to the contact ID of the sender
|
||||
-- and to_id set to 2.
|
||||
-- from_id is then used to collapse series of updates
|
||||
-- into a single message (see test_webxdc_info_msg_cleanup_series),
|
||||
-- while to_id of 2 makes sure the message is displayed
|
||||
-- as an info message.
|
||||
to_id INTEGER DEFAULT 0,
|
||||
|
||||
-- Message viewtype.
|
||||
-- 10 is a text message,
|
||||
-- 20 is an image etc.
|
||||
type INTEGER DEFAULT 0,
|
||||
|
||||
-- Message state,
|
||||
-- e.g. 10 for fresh incoming messages,
|
||||
-- 26 for outgoing delivered message,
|
||||
-- 28 for outgoing message that got a read receipt.
|
||||
--
|
||||
-- Messages sent by self, but arriving from a second device
|
||||
-- are still considered "outgoing".
|
||||
state INTEGER DEFAULT 0,
|
||||
|
||||
-- Size of the attachment for file parts, otherwise 0.
|
||||
bytes INTEGER DEFAULT 0,
|
||||
|
||||
txt TEXT DEFAULT '', -- Message text for display.
|
||||
txt_normalized TEXT, -- Message text normalized for search.
|
||||
txt_raw TEXT DEFAULT '', -- deprecated 2025-03-29
|
||||
|
||||
-- Key-value parameters.
|
||||
param TEXT DEFAULT '',
|
||||
|
||||
-- For messages bookmarked into the Saved Messages chat,
|
||||
-- ID of the original message, making it possible to jump to it.
|
||||
starred INTEGER DEFAULT 0,
|
||||
|
||||
-- True if the message is pinned.
|
||||
pinned INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
timestamp INTEGER DEFAULT 0, -- Timestamp of the message used for sorting.
|
||||
timestamp_sent INTEGER DEFAULT 0, -- Timestamp of the message as sent in the Date header.
|
||||
timestamp_rcvd INTEGER DEFAULT 0,
|
||||
|
||||
-- True if the message should not be displayed.
|
||||
-- Most messages that should not be displayed
|
||||
-- better go to the trash chat (ID 3),
|
||||
-- but at least reactions are hidden
|
||||
-- so they still belong to the chat
|
||||
-- and can be marked as noticed when the chat is opened.
|
||||
hidden INTEGER DEFAULT 0,
|
||||
|
||||
-- mime_headers column actually contains BLOBs, i.e. it may
|
||||
-- contain non-UTF8 MIME messages. TEXT was a bad choice, but
|
||||
-- thanks to SQLite 3 being dynamically typed, there is no need to
|
||||
-- change column type.
|
||||
mime_headers TEXT,
|
||||
|
||||
-- True if mime_headers column is compressed with Brotli.
|
||||
mime_compressed INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
mime_modified INTEGER DEFAULT 0,
|
||||
|
||||
mime_in_reply_to TEXT,
|
||||
mime_references TEXT,
|
||||
|
||||
-- Location ID for POI locations manually placed on the map.
|
||||
location_id INTEGER DEFAULT 0,
|
||||
|
||||
error TEXT DEFAULT '',
|
||||
|
||||
-- Timer value in seconds. For incoming messages this
|
||||
-- timer starts when message is read, so we want to have
|
||||
-- the value stored here until the timer starts.
|
||||
ephemeral_timer INTEGER DEFAULT 0,
|
||||
|
||||
-- Timestamp indicating when the message should be
|
||||
-- deleted. It is convenient to store it here because UI
|
||||
-- needs this value to display how much time is left until
|
||||
-- the message is deleted.
|
||||
ephemeral_timestamp INTEGER DEFAULT 0,
|
||||
|
||||
subject TEXT DEFAULT '',
|
||||
|
||||
-- Download state for the message.
|
||||
-- 0 for most messages, meaning the message is fully downloaded.
|
||||
-- Otherwise 10 for the message available for download etc.
|
||||
download_state INTEGER DEFAULT 0,
|
||||
|
||||
-- Information extracted from Received headers
|
||||
-- as a plain text for debugging.
|
||||
hop_info TEXT,
|
||||
|
||||
-- True if the message is deleted on the server.
|
||||
-- If this is true and another copy of the message arrives
|
||||
-- it should be deleted from the server as well.
|
||||
deleted INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
--
|
||||
-- Unused columns.
|
||||
--
|
||||
|
||||
-- Always 1 for new messages.
|
||||
-- Previously:
|
||||
-- 0 if the message is a non-chat message,
|
||||
-- 1 if the message is a chat message,
|
||||
-- 2 if the message is a non-chat reply to a chat message
|
||||
msgrmsg INTEGER DEFAULT 1,
|
||||
|
||||
server_folder TEXT DEFAULT '', -- Deprecated column that was used before "imap" table, replaced by imap.folder
|
||||
server_uid INTEGER DEFAULT 0, -- Deprecated column that was used before "imap" table, replaced by imap.uid
|
||||
|
||||
-- Unused column formely used to mark the messages
|
||||
-- that should be moved to the dedicated IMAP folder.
|
||||
-- It was replaced with imap.target, which is also now
|
||||
-- used only to mark the messages on IMAP for deletion.
|
||||
move_state INTEGER DEFAULT 1
|
||||
);
|
||||
CREATE INDEX msgs_index1 ON msgs (rfc724_mid);
|
||||
CREATE INDEX msgs_index2 ON msgs (chat_id);
|
||||
CREATE INDEX msgs_index3 ON msgs (timestamp);
|
||||
CREATE INDEX msgs_index4 ON msgs (state);
|
||||
CREATE INDEX msgs_index5 ON msgs (starred);
|
||||
CREATE INDEX msgs_index6 ON msgs (location_id);
|
||||
CREATE INDEX msgs_index7 ON msgs (state, hidden, chat_id, timestamp);
|
||||
CREATE INDEX msgs_index8 ON msgs (ephemeral_timestamp);
|
||||
CREATE INDEX msgs_index9 ON msgs (pre_rfc724_mid);
|
||||
CREATE INDEX msgs_index10 ON msgs (pinned) WHERE pinned=1;
|
||||
|
||||
CREATE TABLE leftgrps (
|
||||
id INTEGER PRIMARY KEY,
|
||||
grpid TEXT DEFAULT ''
|
||||
);
|
||||
CREATE INDEX leftgrps_index1 ON leftgrps (grpid);
|
||||
|
||||
CREATE TABLE msgs_mdns (
|
||||
msg_id INTEGER,
|
||||
contact_id INTEGER,
|
||||
timestamp_sent INTEGER DEFAULT 0
|
||||
);
|
||||
CREATE INDEX msgs_mdns_index1 ON msgs_mdns (msg_id);
|
||||
|
||||
CREATE TABLE locations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
latitude REAL DEFAULT 0.0,
|
||||
longitude REAL DEFAULT 0.0,
|
||||
accuracy REAL DEFAULT 0.0,
|
||||
timestamp INTEGER DEFAULT 0,
|
||||
chat_id INTEGER DEFAULT 0,
|
||||
from_id INTEGER DEFAULT 0,
|
||||
|
||||
-- If true, the location is an independent POI
|
||||
-- and should not be part of the path.
|
||||
independent INTEGER DEFAULT 0
|
||||
);
|
||||
CREATE INDEX locations_index1 ON locations (from_id);
|
||||
CREATE INDEX locations_index2 ON locations (timestamp);
|
||||
|
||||
CREATE TABLE devmsglabels (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
label TEXT,
|
||||
msg_id INTEGER DEFAULT 0
|
||||
);
|
||||
CREATE INDEX devmsglabels_index1 ON devmsglabels (label);
|
||||
|
||||
-- Table to store QR code tokens for SecureJoin protocol.
|
||||
CREATE TABLE tokens (
|
||||
id INTEGER PRIMARY KEY,
|
||||
|
||||
-- Namespace is one of:
|
||||
-- 0 - unknown
|
||||
-- 100 - invite number
|
||||
-- 110 - auth
|
||||
namespc INTEGER NOT NULL,
|
||||
|
||||
foreign_key TEXT DEFAULT '' NOT NULL,
|
||||
token TEXT NOT NULL UNIQUE,
|
||||
timestamp INTEGER DEFAULT 0 NOT NULL
|
||||
) STRICT;
|
||||
|
||||
-- State of the scanner of the QR code (Bob) in SecureJoin protocol.
|
||||
CREATE TABLE bobstate (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
invite TEXT NOT NULL,
|
||||
next_step INTEGER NOT NULL,
|
||||
chat_id INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE smtp (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
rfc724_mid TEXT NOT NULL, -- Message-ID
|
||||
mime TEXT NOT NULL, -- SMTP payload
|
||||
msg_id INTEGER NOT NULL, -- ID of the message in `msgs` table
|
||||
recipients TEXT NOT NULL, -- List of recipients separated by space
|
||||
retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
|
||||
);
|
||||
|
||||
CREATE TABLE smtp_mdns (
|
||||
msg_id INTEGER NOT NULL, -- id of the message in msgs table which requested MDN (DEPRECATED 2024-06-21)
|
||||
from_id INTEGER NOT NULL, -- id of the contact that sent the message, MDN destination
|
||||
rfc724_mid TEXT NOT NULL, -- Message-ID header
|
||||
retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send MDN
|
||||
);
|
||||
CREATE TABLE smtp_status_updates (
|
||||
msg_id INTEGER NOT NULL UNIQUE, -- msg_id of the webxdc instance with pending updates
|
||||
first_serial INTEGER NOT NULL, -- id in msgs_status_updates
|
||||
last_serial INTEGER NOT NULL, -- id in msgs_status_updates
|
||||
descr TEXT NOT NULL -- text to send along with the updates
|
||||
);
|
||||
|
||||
-- Table of "sync items" to be grouped into sync messages
|
||||
-- and sent to own devices.
|
||||
CREATE TABLE multi_device_sync (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
-- JSON of the "sync item".
|
||||
item TEXT DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE TABLE reactions (
|
||||
msg_id INTEGER NOT NULL, -- id of the message reacted to
|
||||
contact_id INTEGER NOT NULL, -- id of the contact reacting to the message
|
||||
reaction TEXT DEFAULT '' NOT NULL, -- a sequence of emojis separated by spaces
|
||||
PRIMARY KEY(msg_id, contact_id),
|
||||
FOREIGN KEY(msg_id) REFERENCES msgs(id) ON DELETE CASCADE -- delete reactions when message is deleted
|
||||
FOREIGN KEY(contact_id) REFERENCES contacts(id) ON DELETE CASCADE -- delete reactions when contact is deleted
|
||||
);
|
||||
CREATE INDEX reactions_index1 ON reactions (msg_id);
|
||||
|
||||
CREATE TABLE pending_reactions (
|
||||
rfc724_mid TEXT NOT NULL,
|
||||
contact_id INTEGER NOT NULL,
|
||||
reaction TEXT NOT NULL,
|
||||
timestamp INTEGER NOT NULL,
|
||||
PRIMARY KEY(rfc724_mid, contact_id),
|
||||
FOREIGN KEY(contact_id) REFERENCES contacts(id) ON DELETE CASCADE
|
||||
) STRICT;
|
||||
|
||||
|
||||
-- accumulated reactions received from broadcast owner.
|
||||
-- pairs of reaction and their counts.
|
||||
-- these pairs are given to the UI (for non-broadcasts, the pairs are calculated from the `reactions` table)
|
||||
CREATE TABLE broadcasted_reactions (
|
||||
msg_id INTEGER NOT NULL DEFAULT 0,
|
||||
reaction TEXT NOT NULL DEFAULT '',
|
||||
count INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY(msg_id) REFERENCES msgs(id) ON DELETE CASCADE -- delete reactions when message is deleted
|
||||
) STRICT;
|
||||
CREATE INDEX broadcasted_reactions_index1 ON broadcasted_reactions (msg_id);
|
||||
|
||||
-- messages that received reactions from broadcast subscriber to broadcast owner.
|
||||
-- the broadcast owner will send them, accumulated by chat_id,
|
||||
-- to all other subscribers every some minutes, and then remove all entries with the chat_id processed.
|
||||
CREATE TABLE reactions_need_broadcast (
|
||||
chat_id INTEGER NOT NULL DEFAULT 0,
|
||||
msg_id INTEGER NOT NULL DEFAULT 0,
|
||||
UNIQUE (chat_id, msg_id),
|
||||
FOREIGN KEY(msg_id) REFERENCES msgs(id) ON DELETE CASCADE -- delete reactions when message is deleted
|
||||
) STRICT;
|
||||
CREATE INDEX reactions_need_broadcast_index1 ON reactions_need_broadcast (chat_id);
|
||||
|
||||
|
||||
CREATE TABLE connection_history (
|
||||
host TEXT NOT NULL, -- server hostname
|
||||
port INTEGER NOT NULL, -- server port
|
||||
alpn TEXT NOT NULL, -- ALPN such as smtp or imap
|
||||
addr TEXT NOT NULL, -- IP address
|
||||
timestamp INTEGER NOT NULL, -- timestamp of the most recent successful connection
|
||||
UNIQUE (host, port, alpn, addr)
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE dns_cache (
|
||||
hostname TEXT NOT NULL,
|
||||
address TEXT NOT NULL, -- IPv4 or IPv6 address
|
||||
timestamp INTEGER NOT NULL,
|
||||
UNIQUE (hostname, address)
|
||||
);
|
||||
|
||||
CREATE TABLE tls_spki (
|
||||
host TEXT NOT NULL UNIQUE,
|
||||
spki_hash TEXT NOT NULL, -- base64 of SPKI SHA-256 hash
|
||||
timestamp INTEGER NOT NULL -- timestamp of the last time we have seen this key
|
||||
) STRICT;
|
||||
CREATE INDEX tls_spki_index_timestamp ON tls_spki (timestamp);
|
||||
|
||||
CREATE TABLE http_cache (
|
||||
url TEXT PRIMARY KEY,
|
||||
expires INTEGER NOT NULL, -- When the cache entry is considered expired, timestamp in seconds.
|
||||
stale INTEGER NOT NULL, -- When the cache entry is considered stale, timestamp in seconds.
|
||||
blobname TEXT NOT NULL,
|
||||
mimetype TEXT NOT NULL DEFAULT '', -- MIME type extracted from Content-Type header.
|
||||
encoding TEXT NOT NULL DEFAULT '' -- Encoding from Content-Type header.
|
||||
) STRICT;
|
||||
|
||||
|
||||
|
||||
|
||||
-- Webxdc updates.
|
||||
CREATE TABLE msgs_status_updates (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
msg_id INTEGER,
|
||||
update_item TEXT DEFAULT '',
|
||||
uid TEXT UNIQUE,
|
||||
FOREIGN KEY(msg_id) REFERENCES msgs(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX msgs_status_updates_index1 ON msgs_status_updates (msg_id);
|
||||
CREATE INDEX msgs_status_updates_index2 ON msgs_status_updates (uid);
|
||||
|
||||
-- Webxdc realtime.
|
||||
CREATE TABLE iroh_gossip_peers (
|
||||
msg_id INTEGER not NULL,
|
||||
topic BLOB NOT NULL,
|
||||
public_key BLOB NOT NULL,
|
||||
relay_server TEXT, UNIQUE (topic, public_key),
|
||||
PRIMARY KEY(topic, public_key)
|
||||
) STRICT;
|
||||
|
||||
--
|
||||
-- OpenPGP.
|
||||
--
|
||||
|
||||
-- Storage for own private keys.
|
||||
-- Only one of the keys is used.
|
||||
--
|
||||
-- In the past mulitple keys could be imported,
|
||||
-- so this table can contain multiple keys for existing users.
|
||||
-- Used key is identified by the "key_id" config value.
|
||||
-- Other keys must never be used, even for decryption.
|
||||
CREATE TABLE keypairs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
-- OpenPGP private key stored as a binary blob.
|
||||
private_key UNIQUE NOT NULL,
|
||||
|
||||
-- Unused public key aka OpenPGP certificate.
|
||||
-- Stored only for compatibility.
|
||||
-- OpenPGP certificate is generated at runtime from the private key.
|
||||
public_key UNIQUE NOT NULL,
|
||||
|
||||
--
|
||||
-- Unused columns.
|
||||
--
|
||||
--
|
||||
-- Columns "addr", "is_default" and "created" were
|
||||
-- dropped in migration 107
|
||||
-- but added back for compatibility in migration 110.
|
||||
|
||||
addr TEXT DEFAULT '' COLLATE NOCASE,
|
||||
|
||||
-- Migrated into "key_id" config value in migration 107.
|
||||
is_default INTEGER DEFAULT 0,
|
||||
|
||||
created INTEGER DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE public_keys (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
fingerprint TEXT NOT NULL UNIQUE, -- Upper-case fingerprint of the key.
|
||||
public_key BLOB NOT NULL -- Binary key, not ASCII-armored
|
||||
) STRICT;
|
||||
CREATE INDEX public_key_index ON public_keys (fingerprint);
|
||||
|
||||
CREATE TABLE gossip_timestamp (
|
||||
chat_id INTEGER NOT NULL,
|
||||
fingerprint TEXT NOT NULL, -- Upper-case fingerprint of the key.
|
||||
timestamp INTEGER NOT NULL,
|
||||
UNIQUE (chat_id, fingerprint)
|
||||
) STRICT;
|
||||
CREATE INDEX gossip_timestamp_index ON gossip_timestamp (chat_id, fingerprint);
|
||||
|
||||
-- Timestamps of distributing own key in the Autocrypt header of MDNs.
|
||||
CREATE TABLE mdn_autocrypt_timestamp (
|
||||
fingerprint TEXT PRIMARY KEY NOT NULL, -- Upper-case fingerprint of the recipient key.
|
||||
attached_timestamp INTEGER NOT NULL
|
||||
) STRICT;
|
||||
|
||||
-- Passwords used to derive symmetric keys for broadcast lists aka channels.
|
||||
CREATE TABLE broadcast_secrets(
|
||||
chat_id INTEGER PRIMARY KEY NOT NULL,
|
||||
secret TEXT NOT NULL
|
||||
) STRICT;
|
||||
|
||||
|
||||
-- Candidate chatmail relays for automatic relay management.
|
||||
CREATE TABLE relay_candidates(
|
||||
host TEXT PRIMARY KEY NOT NULL,
|
||||
last_tried INTEGER NOT NULL DEFAULT 0 -- Timestamp of the last connection attempt.
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE transports (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
-- Email address associated with this transport.
|
||||
-- It uniquely identifies the transport.
|
||||
addr TEXT NOT NULL,
|
||||
|
||||
-- JSON with the settings entered by the user.
|
||||
-- The settings are not necessary entered manually,
|
||||
-- but can be entered by scanning the QR code.
|
||||
-- These settings are only used during the transport configuration process.
|
||||
entered_param TEXT NOT NULL,
|
||||
|
||||
-- JSON with the settings used to connect to the transport.
|
||||
-- These settings are derived from entered parameters
|
||||
-- and possibly autoconfiguration XML fetched over HTTPS.
|
||||
-- The settings stored here are known to have worked at least once,
|
||||
-- this is ensured during configuration.
|
||||
configured_param TEXT NOT NULL,
|
||||
|
||||
add_timestamp INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
-- True if the transport address is published
|
||||
-- by sending it in the public key signature.
|
||||
is_published INTEGER DEFAULT 1 NOT NULL,
|
||||
|
||||
-- Time when the transport was last used to receive a message.
|
||||
-- Used to remove the least recently used transport
|
||||
-- when a new transport is added and there are too many relays already.
|
||||
last_rcvd_timestamp INTEGER NOT NULL DEFAULT 0,
|
||||
UNIQUE(addr)
|
||||
);
|
||||
|
||||
CREATE TABLE removed_transports (
|
||||
addr TEXT NOT NULL,
|
||||
remove_timestamp INTEGER NOT NULL,
|
||||
UNIQUE(addr)
|
||||
) STRICT;
|
||||
|
||||
|
||||
CREATE TABLE imap (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
transport_id INTEGER NOT NULL, -- ID of the transport in the `transports` table.
|
||||
rfc724_mid TEXT NOT NULL, -- Message-ID header
|
||||
folder TEXT NOT NULL, -- IMAP folder
|
||||
target TEXT NOT NULL, -- Destination folder. Empty string means that the message shall be deleted.
|
||||
uid INTEGER NOT NULL, -- UID
|
||||
uidvalidity INTEGER NOT NULL,
|
||||
UNIQUE (transport_id, folder, uid, uidvalidity)
|
||||
) STRICT;
|
||||
CREATE INDEX imap_folder ON imap(transport_id, folder);
|
||||
CREATE INDEX imap_rfc724_mid ON imap(transport_id, rfc724_mid);
|
||||
CREATE INDEX imap_only_rfc724_mid ON imap(rfc724_mid);
|
||||
|
||||
CREATE TABLE imap_markseen (
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
FOREIGN KEY(id) REFERENCES imap(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE imap_sync (
|
||||
transport_id INTEGER NOT NULL, -- ID of the transport in the `transports` table.
|
||||
folder TEXT NOT NULL,
|
||||
uidvalidity INTEGER NOT NULL DEFAULT 0,
|
||||
uid_next INTEGER NOT NULL DEFAULT 0,
|
||||
modseq INTEGER NOT NULL DEFAULT 0,
|
||||
UNIQUE (transport_id, folder)
|
||||
) STRICT;
|
||||
CREATE INDEX imap_sync_index ON imap_sync(transport_id, folder);
|
||||
|
||||
CREATE TABLE download (
|
||||
rfc724_mid TEXT PRIMARY KEY,
|
||||
msg_id INTEGER NOT NULL DEFAULT 0
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE available_post_msgs (
|
||||
rfc724_mid TEXT PRIMARY KEY
|
||||
) STRICT;
|
||||
|
||||
--
|
||||
-- Statistics.
|
||||
--
|
||||
|
||||
CREATE TABLE stats_securejoin_sources(
|
||||
source INTEGER PRIMARY KEY,
|
||||
count INTEGER NOT NULL DEFAULT 0
|
||||
) STRICT;
|
||||
CREATE TABLE stats_securejoin_uipaths(
|
||||
uipath INTEGER PRIMARY KEY,
|
||||
count INTEGER NOT NULL DEFAULT 0
|
||||
) STRICT;
|
||||
CREATE TABLE stats_securejoin_invites(
|
||||
already_existed INTEGER NOT NULL,
|
||||
already_verified INTEGER NOT NULL,
|
||||
type TEXT NOT NULL
|
||||
) STRICT;
|
||||
CREATE TABLE stats_msgs(
|
||||
chattype INTEGER PRIMARY KEY,
|
||||
verified INTEGER NOT NULL DEFAULT 0,
|
||||
unverified_encrypted INTEGER NOT NULL DEFAULT 0,
|
||||
unencrypted INTEGER NOT NULL DEFAULT 0,
|
||||
only_to_self INTEGER NOT NULL DEFAULT 0,
|
||||
last_counted_msg_id INTEGER NOT NULL DEFAULT 0
|
||||
) STRICT;
|
||||
CREATE TABLE stats_sending_enabled_events(timestamp INTEGER NOT NULL) STRICT;
|
||||
CREATE TABLE stats_sending_disabled_events(timestamp INTEGER NOT NULL) STRICT;
|
||||
|
||||
-- Deprecated and unused tables.
|
||||
--
|
||||
-- We don't immediately drop unused tables,
|
||||
-- because we want users to be able to downgrade.
|
||||
|
||||
-- Deprecated table for Autocrypt peer states.
|
||||
-- It is replaced by the new "public_keys" table in migration 132
|
||||
-- that introduced "key contacts" which have a fixed fingerprint.
|
||||
CREATE TABLE acpeerstates (
|
||||
id INTEGER PRIMARY KEY,
|
||||
addr TEXT DEFAULT '' COLLATE NOCASE,
|
||||
last_seen INTEGER DEFAULT 0,
|
||||
last_seen_autocrypt INTEGER DEFAULT 0,
|
||||
public_key,
|
||||
prefer_encrypted INTEGER DEFAULT 0,
|
||||
gossip_timestamp INTEGER DEFAULT 0,
|
||||
gossip_key,
|
||||
public_key_fingerprint TEXT DEFAULT '',
|
||||
gossip_key_fingerprint TEXT DEFAULT '',
|
||||
verified_key,
|
||||
verified_key_fingerprint TEXT DEFAULT '',
|
||||
verifier TEXT DEFAULT '',
|
||||
secondary_verified_key,
|
||||
secondary_verified_key_fingerprint TEXT DEFAULT '',
|
||||
secondary_verifier TEXT DEFAULT '',
|
||||
backward_verified_key_id -- What we think the contact has as our verified key
|
||||
INTEGER,
|
||||
UNIQUE (addr) -- Only one peerstate per address
|
||||
);
|
||||
CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);
|
||||
CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);
|
||||
CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);
|
||||
CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);
|
||||
|
||||
-- Deprecated table previously used to upload sync messages using IMAP APPEND.
|
||||
-- Sync messages are sent over SMTP now as they should be sent to multiple transports.
|
||||
CREATE TABLE imap_send (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
mime TEXT NOT NULL, -- Message content
|
||||
msg_id INTEGER NOT NULL, -- ID of the message in the `msgs` table
|
||||
attempts INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
|
||||
);
|
||||
|
||||
-- Deprecated table used for a job system.
|
||||
CREATE TABLE jobs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
added_timestamp INTEGER,
|
||||
desired_timestamp INTEGER DEFAULT 0,
|
||||
action INTEGER,
|
||||
foreign_id INTEGER,
|
||||
param TEXT DEFAULT '',
|
||||
thread INTEGER DEFAULT 0,
|
||||
tries INTEGER DEFAULT 0
|
||||
);
|
||||
CREATE INDEX jobs_index1 ON jobs (desired_timestamp);
|
||||
|
||||
-- Backup of the keypairs left after migration 107.
|
||||
-- Not used by any code, it was only for a recovery
|
||||
-- in case migration 107 goes wrong.
|
||||
CREATE TABLE old_keypairs (
|
||||
id INTEGER PRIMARY KEY,
|
||||
addr TEXT DEFAULT '' COLLATE NOCASE,
|
||||
is_default INTEGER DEFAULT 0,
|
||||
private_key,
|
||||
public_key,
|
||||
created INTEGER DEFAULT 0
|
||||
);
|
||||
|
||||
-- Unused table, previously introduced for AEAP mechanism
|
||||
-- which is replaced by key contacts.
|
||||
CREATE TABLE sending_domains(
|
||||
domain TEXT PRIMARY KEY,
|
||||
dkim_works INTEGER DEFAULT 0
|
||||
);
|
||||
150
flake.lock
generated
150
flake.lock
generated
@@ -3,15 +3,19 @@
|
||||
"android": {
|
||||
"inputs": {
|
||||
"devshell": "devshell",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731356359,
|
||||
"narHash": "sha256-vYqJnu6jotmWpPT4DgzHVdvNIZcKZCIUqS8QaptsZA0=",
|
||||
"lastModified": 1779918845,
|
||||
"narHash": "sha256-FbpOOBg15L7X6NWWmTKbSdccnH59Jq53wWmAO37d2Q8=",
|
||||
"owner": "tadfisher",
|
||||
"repo": "android-nixpkgs",
|
||||
"rev": "c028ead7e88edb2e94cd7c90ee37593f63ae494a",
|
||||
"rev": "105c093afc8c8fbeea98f8e398403f93043eba17",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -28,11 +32,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1728330715,
|
||||
"narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=",
|
||||
"lastModified": 1768818222,
|
||||
"narHash": "sha256-460jc0+CZfyaO8+w8JNtlClB2n4ui1RbHfPTLkpwhU8=",
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef",
|
||||
"rev": "255a2b1725a20d060f566e4755dbf571bbbb5f76",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -43,15 +47,17 @@
|
||||
},
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763361733,
|
||||
"narHash": "sha256-ka7dpwH3HIXCyD2wl5F7cPLeRbqZoY2ullALsvxdPt8=",
|
||||
"lastModified": 1779876442,
|
||||
"narHash": "sha256-O25HomVNmdROO13PEQ3Ran8Hq5EsyLmVn8Gb8JvJtJE=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "6c8d48e3b0ae371b19ac1485744687b788e80193",
|
||||
"rev": "2eff81fc84390a35e1565395ae945d9394856824",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -65,29 +71,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1726560853,
|
||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1726560853,
|
||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -98,14 +86,19 @@
|
||||
},
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
"fenix": [
|
||||
"fenix"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1721727458,
|
||||
"narHash": "sha256-r/xppY958gmZ4oTfLiHN0ZGuQ+RSTijDblVgVLFi1mw=",
|
||||
"lastModified": 1780914171,
|
||||
"narHash": "sha256-NYoa+CvsCgayY356worC9g6QoZkKJmvrj6nVdEA2Lyk=",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"rev": "3fb418eaf352498f6b6c30592e3beb63df42ef11",
|
||||
"rev": "41b6e9efb62fac9c16d5617e0456a715928a2206",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -116,11 +109,11 @@
|
||||
},
|
||||
"nix-filter": {
|
||||
"locked": {
|
||||
"lastModified": 1730207686,
|
||||
"narHash": "sha256-SCHiL+1f7q9TAnxpasriP6fMarWE5H43t25F5/9e28I=",
|
||||
"lastModified": 1757882181,
|
||||
"narHash": "sha256-+cCxYIh2UNalTz364p+QYmWHs0P+6wDhiWR4jDIKQIU=",
|
||||
"owner": "numtide",
|
||||
"repo": "nix-filter",
|
||||
"rev": "776e68c1d014c3adde193a18db9d738458cd2ba4",
|
||||
"rev": "59c44d1909c72441144b93cf0f054be7fe764de5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -131,60 +124,16 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1731139594,
|
||||
"narHash": "sha256-IigrKK3vYRpUu+HEjPL/phrfh7Ox881er1UEsZvw9Q4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "76612b17c0ce71689921ca12d9ffdc9c23ce40b2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1762977756,
|
||||
"narHash": "sha256-4PqRErxfe+2toFJFgcRKZ0UI9NSIOJa+7RXVtBhy4KE=",
|
||||
"lastModified": 1779931091,
|
||||
"narHash": "sha256-gc8NEz7a++7OQPGvMv+zIjXCec1PO38XRXZRa3m97ew=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c5ae371f1a6a7fd27823bc500d9390b38c05fa55",
|
||||
"rev": "3052ddf0614791c1869384a868248be5607a309f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 0,
|
||||
"narHash": "sha256-Dqg6si5CqIzm87sp57j5nTaeBbWhHFaVyG7V6L8k3lY=",
|
||||
"path": "/nix/store/zq2axpgzd5kykk1v446rkffj3bxa2m2h-source",
|
||||
"type": "path"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"nixpkgs_4": {
|
||||
"locked": {
|
||||
"lastModified": 1747179050,
|
||||
"narHash": "sha256-qhFMmDkeJX9KJwr5H32f1r7Prs7XbQWtO0h3V0a0rFY=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "adaa24fbf46737f3f1b5497bf64bae750f82942e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"ref": "master",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -193,20 +142,20 @@
|
||||
"inputs": {
|
||||
"android": "android",
|
||||
"fenix": "fenix",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"flake-utils": "flake-utils",
|
||||
"naersk": "naersk",
|
||||
"nix-filter": "nix-filter",
|
||||
"nixpkgs": "nixpkgs_4"
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1762860488,
|
||||
"narHash": "sha256-rMfWMCOo/pPefM2We0iMBLi2kLBAnYoB9thi4qS7uk4=",
|
||||
"lastModified": 1779827300,
|
||||
"narHash": "sha256-J6pHxKoZzWCrAvOVInwBcYYWix/NWwM10Ad+i29Qc5s=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "2efc80078029894eec0699f62ec8d5c1a56af763",
|
||||
"rev": "c3af07ad84d68adc5e652e86f0c20009caa29014",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -230,21 +179,6 @@
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
||||
93
flake.nix
93
flake.nix
@@ -2,11 +2,16 @@
|
||||
description = "Chatmail core";
|
||||
inputs = {
|
||||
fenix.url = "github:nix-community/fenix";
|
||||
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
naersk.url = "github:nix-community/naersk";
|
||||
naersk.inputs.nixpkgs.follows = "nixpkgs";
|
||||
naersk.inputs.fenix.follows = "fenix";
|
||||
nix-filter.url = "github:numtide/nix-filter";
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
nixpkgs.url = "github:nixos/nixpkgs/master";
|
||||
android.url = "github:tadfisher/android-nixpkgs";
|
||||
android.inputs.nixpkgs.follows = "nixpkgs";
|
||||
android.inputs.flake-utils.follows = "flake-utils";
|
||||
};
|
||||
outputs = { self, nixpkgs, flake-utils, nix-filter, naersk, fenix, android }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
@@ -61,37 +66,15 @@
|
||||
];
|
||||
};
|
||||
|
||||
# Map from architecture name to rust targets and nixpkgs targets.
|
||||
# Map from architecture name to nixpkgs targets.
|
||||
arch2targets = {
|
||||
"x86_64-linux" = {
|
||||
rustTarget = "x86_64-unknown-linux-musl";
|
||||
crossTarget = "x86_64-unknown-linux-musl";
|
||||
};
|
||||
"armv7l-linux" = {
|
||||
rustTarget = "armv7-unknown-linux-musleabihf";
|
||||
crossTarget = "armv7l-unknown-linux-musleabihf";
|
||||
};
|
||||
"armv6l-linux" = {
|
||||
rustTarget = "arm-unknown-linux-musleabihf";
|
||||
crossTarget = "armv6l-unknown-linux-musleabihf";
|
||||
};
|
||||
"aarch64-linux" = {
|
||||
rustTarget = "aarch64-unknown-linux-musl";
|
||||
crossTarget = "aarch64-unknown-linux-musl";
|
||||
};
|
||||
"i686-linux" = {
|
||||
rustTarget = "i686-unknown-linux-musl";
|
||||
crossTarget = "i686-unknown-linux-musl";
|
||||
};
|
||||
|
||||
"x86_64-darwin" = {
|
||||
rustTarget = "x86_64-apple-darwin";
|
||||
crossTarget = "x86_64-darwin";
|
||||
};
|
||||
"aarch64-darwin" = {
|
||||
rustTarget = "aarch64-apple-darwin";
|
||||
crossTarget = "aarch64-darwin";
|
||||
};
|
||||
"x86_64-linux" = "x86_64-unknown-linux-musl";
|
||||
"armv7l-linux" = "armv7l-unknown-linux-musleabihf";
|
||||
"armv6l-linux" = "armv6l-unknown-linux-musleabihf";
|
||||
"aarch64-linux" = "aarch64-unknown-linux-musl";
|
||||
"i686-linux" = "i686-unknown-linux-musl";
|
||||
"x86_64-darwin" = "x86_64-darwin";
|
||||
"aarch64-darwin" = "aarch64-darwin";
|
||||
};
|
||||
cargoLock = {
|
||||
lockFile = ./Cargo.lock;
|
||||
@@ -108,10 +91,10 @@
|
||||
auditable = false; # Avoid cargo-auditable failures.
|
||||
doCheck = false; # Disable test as it requires network access.
|
||||
};
|
||||
pkgsWin64 = pkgs.pkgsCross.mingwW64;
|
||||
mkWin64RustPackage = packageName:
|
||||
let
|
||||
rustTarget = "x86_64-pc-windows-gnu";
|
||||
pkgsWin64 = pkgs.pkgsCross.mingwW64;
|
||||
rustTarget = pkgsWin64.stdenv.hostPlatform.rust.rustcTarget;
|
||||
toolchainWin = fenixPkgs.combine [
|
||||
fenixPkgs.stable.rustc
|
||||
fenixPkgs.stable.cargo
|
||||
@@ -133,6 +116,8 @@
|
||||
];
|
||||
depsBuildBuild = [
|
||||
pkgsWin64.stdenv.cc
|
||||
];
|
||||
buildInputs = [
|
||||
pkgsWin64.windows.pthreads
|
||||
];
|
||||
auditable = false; # Avoid cargo-auditable failures.
|
||||
@@ -140,21 +125,22 @@
|
||||
|
||||
CARGO_BUILD_TARGET = rustTarget;
|
||||
TARGET_CC = "${pkgsWin64.stdenv.cc}/bin/${pkgsWin64.stdenv.cc.targetPrefix}cc";
|
||||
CFLAGS_x86_64_pc_windows_gnu = "-I${pkgsWin64.windows.pthreads}/include";
|
||||
CARGO_BUILD_RUSTFLAGS = [
|
||||
"-C"
|
||||
"linker=${TARGET_CC}"
|
||||
"-L"
|
||||
"native=${pkgsWin64.windows.pthreads}/lib"
|
||||
];
|
||||
|
||||
CC = "${pkgsWin64.stdenv.cc}/bin/${pkgsWin64.stdenv.cc.targetPrefix}cc";
|
||||
LD = "${pkgsWin64.stdenv.cc}/bin/${pkgsWin64.stdenv.cc.targetPrefix}cc";
|
||||
};
|
||||
|
||||
pkgsWin32 = pkgs.pkgsCross.mingw32;
|
||||
mkWin32RustPackage = packageName:
|
||||
let
|
||||
rustTarget = "i686-pc-windows-gnu";
|
||||
in
|
||||
let
|
||||
pkgsWin32 = pkgs.pkgsCross.mingw32;
|
||||
rustTarget = pkgsWin32.stdenv.hostPlatform.rust.rustcTarget;
|
||||
toolchainWin = fenixPkgs.combine [
|
||||
fenixPkgs.stable.rustc
|
||||
fenixPkgs.stable.cargo
|
||||
@@ -180,7 +166,8 @@
|
||||
};
|
||||
})).overrideAttrs (oldAttr: {
|
||||
configureFlags = oldAttr.configureFlags ++ [
|
||||
"--disable-sjlj-exceptions --with-dwarf2"
|
||||
"--disable-sjlj-exceptions"
|
||||
"--with-dwarf2"
|
||||
];
|
||||
})
|
||||
);
|
||||
@@ -193,9 +180,12 @@
|
||||
src = pkgs.lib.cleanSource ./.;
|
||||
nativeBuildInputs = [
|
||||
pkgs.perl # Needed to build vendored OpenSSL.
|
||||
pkgs.nasm # aws-lc-sys requires it
|
||||
];
|
||||
depsBuildBuild = [
|
||||
winCC
|
||||
];
|
||||
buildInputs = [
|
||||
pkgsWin32.windows.pthreads
|
||||
];
|
||||
auditable = false; # Avoid cargo-auditable failures.
|
||||
@@ -203,9 +193,12 @@
|
||||
|
||||
CARGO_BUILD_TARGET = rustTarget;
|
||||
TARGET_CC = "${winCC}/bin/${winCC.targetPrefix}cc";
|
||||
CFLAGS_i686_pc_windows_gnu = "-I${pkgsWin32.windows.pthreads}/include";
|
||||
CARGO_BUILD_RUSTFLAGS = [
|
||||
"-C"
|
||||
"linker=${TARGET_CC}"
|
||||
"-L"
|
||||
"native=${pkgsWin32.windows.pthreads}/lib"
|
||||
];
|
||||
|
||||
CC = "${winCC}/bin/${winCC.targetPrefix}cc";
|
||||
@@ -214,14 +207,12 @@
|
||||
|
||||
mkCrossRustPackage = arch: packageName:
|
||||
let
|
||||
rustTarget = arch2targets."${arch}".rustTarget;
|
||||
crossTarget = arch2targets."${arch}".crossTarget;
|
||||
crossTarget = arch2targets."${arch}";
|
||||
pkgsCross = import nixpkgs {
|
||||
system = system;
|
||||
crossSystem.config = crossTarget;
|
||||
};
|
||||
in
|
||||
let
|
||||
rustTarget = pkgsCross.stdenv.hostPlatform.rust.rustcTarget;
|
||||
toolchain = fenixPkgs.combine [
|
||||
fenixPkgs.stable.rustc
|
||||
fenixPkgs.stable.cargo
|
||||
@@ -504,22 +495,6 @@
|
||||
'';
|
||||
};
|
||||
|
||||
# Source package for deltachat-rpc-server.
|
||||
# Fake package that downloads Linux version,
|
||||
# needed to install deltachat-rpc-server on Android with `pip`.
|
||||
deltachat-rpc-server-source =
|
||||
pkgs.stdenv.mkDerivation {
|
||||
pname = "deltachat-rpc-server-source";
|
||||
version = manifest.version;
|
||||
src = pkgs.lib.cleanSource ./.;
|
||||
nativeBuildInputs = [
|
||||
pkgs.python3
|
||||
pkgs.python3Packages.wheel
|
||||
];
|
||||
buildPhase = ''python3 scripts/wheel-rpc-server.py source deltachat_rpc_server-${manifest.version}.tar.gz'';
|
||||
installPhase = ''mkdir -p $out; cp -av deltachat_rpc_server-${manifest.version}.tar.gz $out'';
|
||||
};
|
||||
|
||||
deltachat-rpc-client =
|
||||
pkgs.python3Packages.buildPythonPackage {
|
||||
pname = "deltachat-rpc-client";
|
||||
@@ -562,7 +537,7 @@
|
||||
deltachat-python
|
||||
deltachat-rpc-client
|
||||
pkgs.python3Packages.breathe
|
||||
pkgs.python3Packages.sphinx_rtd_theme
|
||||
pkgs.python3Packages.sphinx-rtd-theme
|
||||
];
|
||||
nativeBuildInputs = [ pkgs.sphinx ];
|
||||
buildPhase = ''sphinx-build -b html -a python/doc/ dist/html'';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "format-flowed"
|
||||
version = "1.0.0"
|
||||
description = "format=flowed support"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
|
||||
keywords = ["email"]
|
||||
|
||||
@@ -78,11 +78,11 @@ pub fn format_flowed(text: &str) -> String {
|
||||
|
||||
let mut prefix = prefix.to_string();
|
||||
|
||||
if quote_depth > 0 {
|
||||
if let Some(s) = line.strip_prefix(' ') {
|
||||
line = s;
|
||||
prefix += " ";
|
||||
}
|
||||
if quote_depth > 0
|
||||
&& let Some(s) = line.strip_prefix(' ')
|
||||
{
|
||||
line = s;
|
||||
prefix += " ";
|
||||
}
|
||||
|
||||
result += &format_line_flowed(line, &prefix);
|
||||
@@ -224,8 +224,7 @@ mod tests {
|
||||
fn test_unformat_flowed() {
|
||||
let text = "this is a very long message that should be wrapped using format=flowed and \n\
|
||||
unwrapped on the receiver";
|
||||
let expected =
|
||||
"this is a very long message that should be wrapped using format=flowed and \
|
||||
let expected = "this is a very long message that should be wrapped using format=flowed and \
|
||||
unwrapped on the receiver";
|
||||
assert_eq!(unformat_flowed(text, false), expected);
|
||||
|
||||
@@ -255,8 +254,7 @@ mod tests {
|
||||
assert_eq!(format_flowed_quote(quote), expected);
|
||||
|
||||
let quote = "this is a very long quote that should be wrapped using format=flowed and unwrapped on the receiver";
|
||||
let expected =
|
||||
"> this is a very long quote that should be wrapped using format=flowed and \r\n\
|
||||
let expected = "> this is a very long quote that should be wrapped using format=flowed and \r\n\
|
||||
> unwrapped on the receiver";
|
||||
assert_eq!(format_flowed_quote(quote), expected);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name = "deltachat-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat"
|
||||
version = "2.48.0-dev"
|
||||
version = "2.60.0-dev"
|
||||
license = "MPL-2.0"
|
||||
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
|
||||
readme = "README.rst"
|
||||
|
||||
@@ -397,7 +397,7 @@ class Account:
|
||||
return (x for x in iter_array(dc_array, lambda x: Message.from_db(self, x)) if x is not None)
|
||||
|
||||
def create_chat(self, obj) -> Chat:
|
||||
"""Create a 1:1 chat with Account, Contact or e-mail address."""
|
||||
"""Create a single chat with Account, Contact or e-mail address."""
|
||||
return self.create_contact(obj).create_chat()
|
||||
|
||||
def create_group_chat(
|
||||
|
||||
@@ -74,7 +74,7 @@ class Chat:
|
||||
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_GROUP
|
||||
|
||||
def is_single(self) -> bool:
|
||||
"""Return True if this chat is a single/direct chat, False otherwise."""
|
||||
"""Return True if this chat is a single chat, False otherwise."""
|
||||
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_SINGLE
|
||||
|
||||
def is_mailinglist(self) -> bool:
|
||||
@@ -271,15 +271,6 @@ class Chat:
|
||||
sent out. This is the same object as was passed in, which
|
||||
has been modified with the new state of the core.
|
||||
"""
|
||||
if msg.is_out_preparing():
|
||||
assert msg.id != 0
|
||||
# get a fresh copy of dc_msg, the core needs it
|
||||
maybe_msg = Message.from_db(self.account, msg.id)
|
||||
if maybe_msg is not None:
|
||||
msg = maybe_msg
|
||||
else:
|
||||
raise ValueError("message does not exist")
|
||||
|
||||
sent_id = lib.dc_send_msg(self.account._dc_context, self.id, msg._dc_msg)
|
||||
if sent_id == 0:
|
||||
raise ValueError("message could not be sent")
|
||||
@@ -333,26 +324,6 @@ class Chat:
|
||||
raise ValueError("message could not be sent")
|
||||
return Message.from_db(self.account, sent_id)
|
||||
|
||||
def send_prepared(self, message):
|
||||
"""send a previously prepared message.
|
||||
|
||||
:param message: a :class:`Message` instance previously returned by
|
||||
:meth:`prepare_file`.
|
||||
:raises ValueError: if message can not be sent.
|
||||
:returns: a :class:`deltachat.message.Message` instance as sent out.
|
||||
"""
|
||||
assert message.id != 0 and message.is_out_preparing()
|
||||
# get a fresh copy of dc_msg, the core needs it
|
||||
msg = Message.from_db(self.account, message.id)
|
||||
|
||||
# pass 0 as chat-id because core-docs say it's ok when out-preparing
|
||||
sent_id = lib.dc_send_msg(self.account._dc_context, 0, msg._dc_msg)
|
||||
if sent_id == 0:
|
||||
raise ValueError("message could not be sent")
|
||||
assert sent_id == msg.id
|
||||
# modify message in place to avoid bad state for the caller
|
||||
msg._dc_msg = Message.from_db(self.account, sent_id)._dc_msg
|
||||
|
||||
def set_draft(self, message):
|
||||
"""set message as draft.
|
||||
|
||||
@@ -487,7 +458,7 @@ class Chat:
|
||||
"""Get group profile image.
|
||||
|
||||
For groups, this is the image set by any group member using
|
||||
set_chat_profile_image(). For normal chats, this is the image
|
||||
set_chat_profile_image(). For single chats, this is the image
|
||||
set by each remote user on their own using dc_set_config(context,
|
||||
"selfavatar", image).
|
||||
:returns: path to profile image, None if no profile image exists.
|
||||
|
||||
@@ -107,7 +107,7 @@ class Contact:
|
||||
return from_dc_charpointer(lib.dc_contact_get_status(self._dc_contact))
|
||||
|
||||
def create_chat(self):
|
||||
"""create or get an existing 1:1 chat object for the specified contact or contact id.
|
||||
"""create or get an existing single chat object for the specified contact or contact id.
|
||||
|
||||
:param contact: chat_id (int) or contact object.
|
||||
:returns: a :class:`deltachat.chat.Chat` object.
|
||||
|
||||
@@ -7,6 +7,7 @@ import imaplib
|
||||
import io
|
||||
import pathlib
|
||||
import ssl
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
from typing import List, TYPE_CHECKING
|
||||
|
||||
@@ -42,10 +43,22 @@ class DirectImap:
|
||||
host = user.rsplit("@")[-1]
|
||||
pw = self.account.get_config("mail_pw")
|
||||
|
||||
self.conn = MailBox(host, port, ssl_context=ssl.create_default_context())
|
||||
self.conn.login(user, pw)
|
||||
ssl_context = ssl.create_default_context()
|
||||
if host.startswith("_"):
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
|
||||
self.select_folder("INBOX")
|
||||
while True:
|
||||
try:
|
||||
self.conn = MailBox(host, port, ssl_context=ssl_context)
|
||||
self.conn.login(user, pw)
|
||||
self.select_folder("INBOX")
|
||||
return
|
||||
except (OSError, imaplib.IMAP4.abort) as e:
|
||||
# OSError covers ssl.SSLError, "abort" is a dropped connection;
|
||||
# permanent IMAP errors (login rejected etc) must not be retried.
|
||||
print(f"direct_imap connect to {host} failed ({e!r}), retrying")
|
||||
time.sleep(1)
|
||||
|
||||
def shutdown(self):
|
||||
try:
|
||||
|
||||
@@ -226,6 +226,10 @@ class EventThread(threading.Thread):
|
||||
|
||||
def __init__(self, account: Account) -> None:
|
||||
self.account = account
|
||||
self.event_emitter = ffi.gc(
|
||||
lib.dc_get_event_emitter(self.account._dc_context),
|
||||
lib.dc_event_emitter_unref,
|
||||
)
|
||||
super(EventThread, self).__init__(name="events")
|
||||
self.daemon = True
|
||||
self._marked_for_shutdown = False
|
||||
@@ -250,13 +254,9 @@ class EventThread(threading.Thread):
|
||||
def run(self) -> None:
|
||||
"""get and run events until shutdown."""
|
||||
with self.log_execution("EVENT THREAD"):
|
||||
event_emitter = ffi.gc(
|
||||
lib.dc_get_event_emitter(self.account._dc_context),
|
||||
lib.dc_event_emitter_unref,
|
||||
)
|
||||
while not self._marked_for_shutdown:
|
||||
with self.swallow_and_log_exception("Unexpected error in event thread"):
|
||||
event = lib.dc_get_next_event(event_emitter)
|
||||
event = lib.dc_get_next_event(self.event_emitter)
|
||||
if event == ffi.NULL or self._marked_for_shutdown:
|
||||
break
|
||||
self._process_event(event)
|
||||
|
||||
@@ -254,10 +254,6 @@ class Message:
|
||||
"""Quote setter."""
|
||||
lib.dc_msg_set_quote(self._dc_msg, quoted_message._dc_msg)
|
||||
|
||||
def force_plaintext(self) -> None:
|
||||
"""Force the message to be sent in plain text."""
|
||||
lib.dc_msg_force_plaintext(self._dc_msg)
|
||||
|
||||
@property
|
||||
def error(self) -> Optional[str]:
|
||||
"""Error message."""
|
||||
@@ -287,7 +283,7 @@ class Message:
|
||||
lib.dc_msg_set_override_sender_name(self._dc_msg, as_dc_charpointer(name))
|
||||
|
||||
def get_sender_chat(self):
|
||||
"""return the 1:1 chat with the sender of this message.
|
||||
"""return the single chat with the sender of this message.
|
||||
|
||||
:returns: :class:`deltachat.chat.Chat` instance
|
||||
"""
|
||||
@@ -355,17 +351,12 @@ class Message:
|
||||
def is_outgoing(self):
|
||||
"""Return True if Message is outgoing."""
|
||||
return lib.dc_msg_get_state(self._dc_msg) in (
|
||||
const.DC_STATE_OUT_PREPARING,
|
||||
const.DC_STATE_OUT_PENDING,
|
||||
const.DC_STATE_OUT_FAILED,
|
||||
const.DC_STATE_OUT_MDN_RCVD,
|
||||
const.DC_STATE_OUT_DELIVERED,
|
||||
)
|
||||
|
||||
def is_out_preparing(self):
|
||||
"""Return True if Message is outgoing, but its file is being prepared."""
|
||||
return self._msgstate == const.DC_STATE_OUT_PREPARING
|
||||
|
||||
def is_out_pending(self):
|
||||
"""Return True if Message is outgoing, but is pending (no single checkmark)."""
|
||||
return self._msgstate == const.DC_STATE_OUT_PENDING
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
"""Provider info class."""
|
||||
|
||||
from .capi import ffi, lib
|
||||
from .cutil import as_dc_charpointer, from_dc_charpointer
|
||||
|
||||
|
||||
class ProviderNotFoundError(Exception):
|
||||
"""The provider information was not found."""
|
||||
|
||||
|
||||
class Provider:
|
||||
"""
|
||||
Provider information.
|
||||
|
||||
:param domain: The email to get the provider info for.
|
||||
"""
|
||||
|
||||
def __init__(self, account, addr) -> None:
|
||||
provider = ffi.gc(
|
||||
lib.dc_provider_new_from_email(account._dc_context, as_dc_charpointer(addr)),
|
||||
lib.dc_provider_unref,
|
||||
)
|
||||
if provider == ffi.NULL:
|
||||
raise ProviderNotFoundError("Provider not found")
|
||||
self._provider = provider
|
||||
|
||||
@property
|
||||
def overview_page(self) -> str:
|
||||
"""URL to the overview page of the provider on providers.delta.chat."""
|
||||
return from_dc_charpointer(lib.dc_provider_get_overview_page(self._provider))
|
||||
|
||||
@property
|
||||
def get_before_login_hints(self) -> str:
|
||||
"""Should be shown to the user on login."""
|
||||
return from_dc_charpointer(lib.dc_provider_get_before_login_hint(self._provider))
|
||||
|
||||
@property
|
||||
def status(self) -> int:
|
||||
"""The status of the provider information.
|
||||
|
||||
This is one of the
|
||||
:attr:`deltachat.const.DC_PROVIDER_STATUS_OK`,
|
||||
:attr:`deltachat.const.DC_PROVIDER_STATUS_PREPARATION` or
|
||||
:attr:`deltachat.const.DC_PROVIDER_STATUS_BROKEN` constants.
|
||||
"""
|
||||
return lib.dc_provider_get_status(self._provider)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user