mirror of
https://github.com/chatmail/core.git
synced 2026-09-20 20:19:11 +03:00
Compare commits
468 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2d8ced169 | ||
|
|
75a80fbb65 | ||
|
|
58b5f5d0f4 | ||
|
|
51b6d50a22 | ||
|
|
61d73433ea | ||
|
|
4232b8b20d | ||
|
|
f4992a3b78 | ||
|
|
187c5f4949 | ||
|
|
4bf42c0af9 | ||
|
|
b0e5b08531 | ||
|
|
49c30ecc75 | ||
|
|
506a78b5d4 | ||
|
|
a516829171 | ||
|
|
5ebab3d859 | ||
|
|
def14306dd | ||
|
|
d004c74a1a | ||
|
|
401626d674 | ||
|
|
477290972a | ||
|
|
77dd9075ca | ||
|
|
0b0398561d | ||
|
|
3ff8011676 | ||
|
|
38d6cf2bcd | ||
|
|
120bf11f4c | ||
|
|
5d02e92c11 | ||
|
|
76e3356e76 | ||
|
|
61dcc630fe | ||
|
|
d89d3ac9cd | ||
|
|
be693aacd9 | ||
|
|
3a95717783 | ||
|
|
504cb4b924 | ||
|
|
46d45faf7e | ||
|
|
70a01a6813 | ||
|
|
3d61e0f349 | ||
|
|
f162749dfe | ||
|
|
370cc5c1fd | ||
|
|
08b2374fcf | ||
|
|
5176a9c633 | ||
|
|
4d5ebe9ff4 | ||
|
|
322dd11d99 | ||
|
|
2f6f48c2a9 | ||
|
|
0517beef61 | ||
|
|
693c404266 | ||
|
|
a344cab046 | ||
|
|
96f051a2f0 | ||
|
|
9a33555604 | ||
|
|
3af45c306a | ||
|
|
709c56a889 | ||
|
|
7421e67ab6 | ||
|
|
49496756e4 | ||
|
|
ea3ca3a213 | ||
|
|
6c47e86380 | ||
|
|
fe25e93d6c | ||
|
|
0b2ff5d0e2 | ||
|
|
48888898ee | ||
|
|
c9880d0ba6 | ||
|
|
d2286254b3 | ||
|
|
589a628cda | ||
|
|
3df1e3712a | ||
|
|
f09f8fd8a4 | ||
|
|
357c6e74d5 | ||
|
|
484d56f24d | ||
|
|
8f3337d971 | ||
|
|
1283b986df | ||
|
|
33b258302b | ||
|
|
307ff9def1 | ||
|
|
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 |
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.98.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@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
|
||||
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@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
|
||||
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@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
|
||||
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@b6ff580856c41316412a0b9b60540fbc6f8c82cc
|
||||
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@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
|
||||
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@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
|
||||
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 }}
|
||||
|
||||
|
||||
55
.github/workflows/deltachat-rpc-server.yml
vendored
55
.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
|
||||
@@ -370,6 +370,14 @@ jobs:
|
||||
- name: List artifacts
|
||||
run: ls -l dist/
|
||||
|
||||
- name: Check that the wheel metadata reads back
|
||||
run: |
|
||||
echo 'You can check a local `nix build` on non-Mac machines against the following checksums.'
|
||||
sha256sum dist/*.whl
|
||||
mkdir tagcheck
|
||||
cp dist/*.whl tagcheck/
|
||||
nix run --inputs-from . nixpkgs#python3Packages.wheel -- tags tagcheck/*.whl
|
||||
|
||||
- name: Upload binaries to the GitHub release
|
||||
if: github.event_name == 'release'
|
||||
env:
|
||||
@@ -382,7 +390,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@dc37677b2e1c63e2034f94d8a5b11f265b73ba33
|
||||
|
||||
publish_npm_package:
|
||||
name: Build & Publish npm prebuilds and deltachat-rpc-server
|
||||
@@ -397,11 +405,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 +521,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'
|
||||
|
||||
2
.github/workflows/dev-version.yml
vendored
2
.github/workflows/dev-version.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
name: Check that current version ends with -dev
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
|
||||
20
.github/workflows/jsonrpc-client-npm-package.yml
vendored
20
.github/workflows/jsonrpc-client-npm-package.yml
vendored
@@ -17,31 +17,29 @@ 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
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
run: npm install --ignore-scripts
|
||||
|
||||
- name: Package
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
run: |
|
||||
npm run build
|
||||
npm pack .
|
||||
|
||||
- name: Publish
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
run: npm publish --provenance deltachat-jsonrpc-client-* --access public
|
||||
|
||||
21
.github/workflows/jsonrpc.yml
vendored
21
.github/workflows/jsonrpc.yml
vendored
@@ -16,27 +16,30 @@ 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@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
cache-bin: false
|
||||
- name: npm install
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
run: npm install
|
||||
- name: Build TypeScript, run Rust tests, generate bindings
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
run: npm run build
|
||||
- name: Run integration tests
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
run: npm run test
|
||||
env:
|
||||
CHATMAIL_DOMAIN: ${{ vars.CHATMAIL_DOMAIN }}
|
||||
- name: Run linter
|
||||
working-directory: deltachat-jsonrpc/typescript
|
||||
working-directory: deltachat-jsonrpc-bindings/typescript
|
||||
run: npm run prettier:check
|
||||
|
||||
17
.github/workflows/nix.yml
vendored
17
.github/workflows/nix.yml
vendored
@@ -5,11 +5,13 @@ on:
|
||||
paths:
|
||||
- flake.nix
|
||||
- flake.lock
|
||||
- nix/**
|
||||
- .github/workflows/nix.yml
|
||||
push:
|
||||
paths:
|
||||
- flake.nix
|
||||
- flake.lock
|
||||
- nix/**
|
||||
- .github/workflows/nix.yml
|
||||
branches:
|
||||
- main
|
||||
@@ -21,12 +23,12 @@ 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
|
||||
- run: nix fmt flake.nix -- --check
|
||||
- uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0
|
||||
- run: nix fmt flake.nix nix/ -- --check
|
||||
|
||||
build:
|
||||
name: nix build
|
||||
@@ -63,7 +65,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 +81,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 +102,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@dc37677b2e1c63e2034f94d8a5b11f265b73ba33
|
||||
|
||||
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
|
||||
|
||||
30
.github/workflows/upload-docs.yml
vendored
30
.github/workflows/upload-docs.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
url: https://rs.delta.chat/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
mkdir -p "$HOME/.ssh"
|
||||
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.RS_DOCS_SSH_USER }}@rs.delta.chat:/var/www/html/rs.delta.chat/"
|
||||
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
|
||||
@@ -36,12 +36,12 @@ jobs:
|
||||
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
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.PY_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/result/html/ "${{ secrets.PY_DOCS_SSH_USER }}@py.delta.chat:/var/www/html/py.delta.chat"
|
||||
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
|
||||
@@ -58,12 +58,12 @@ jobs:
|
||||
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
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.C_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/result/html/ "${{ secrets.C_DOCS_SSH_USER }}@c.delta.chat:/var/www/html/c.delta.chat"
|
||||
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
|
||||
@@ -81,18 +81,18 @@ jobs:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./deltachat-jsonrpc/typescript
|
||||
working-directory: ./deltachat-jsonrpc-bindings/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
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
mkdir -p "$HOME/.ssh"
|
||||
echo "${{ secrets.JS_JSONRPC_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/deltachat-jsonrpc/typescript/docs/ "${{ secrets.JS_JSONRPC_DOCS_SSH_USER }}@js.jsonrpc.delta.chat:/var/www/html/js.jsonrpc.delta.chat/"
|
||||
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/deltachat-jsonrpc-bindings/typescript/docs/ "${{ secrets.JS_JSONRPC_DOCS_SSH_USER }}@js.jsonrpc.delta.chat:"
|
||||
|
||||
build-cffi:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
url: https://cffi.delta.chat/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
show-progress: false
|
||||
persist-credentials: false
|
||||
@@ -125,4 +125,4 @@ jobs:
|
||||
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:/var/www/html/cffi.delta.chat/"
|
||||
rsync -avzh --delete -e "ssh -i $HOME/.ssh/key -o StrictHostKeyChecking=no" $GITHUB_WORKSPACE/target/doc/ "${{ secrets.CFFI_DOCS_SSH_USER }}@delta.chat:"
|
||||
|
||||
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@3dc1ecc9bcb9e94e9b2c709687979e1298497054 # v0.6.2
|
||||
|
||||
690
CHANGELOG.md
690
CHANGELOG.md
@@ -1,5 +1,684 @@
|
||||
# Changelog
|
||||
|
||||
## [2.60.0] - 2026-09-11
|
||||
|
||||
### API-Changes
|
||||
|
||||
- [**breaking**] remove a relay immediately instead of unpublishing it.
|
||||
- `set_transport_unpublished()` is removed: UIs call `delete_transport()` when the user removes a relay.
|
||||
- `list_transports_ex()` and the `TransportListEntry` type are removed: use `list_transports()`.
|
||||
- `delete_transport()` no longer refuses to remove the primary transport: it refuses only to remove the last one and re-elects the sending transport as needed.
|
||||
- `TransportsModified` is now also emitted on the device modifying the transports, not only on devices applying the synced change.
|
||||
- [**breaking**] do not load webxdc icon if it has too large dimensions.
|
||||
- `get_webxdc_blob()` may fail to load `icon.png` or `icon.jpg` if image dimensions are too large.
|
||||
Fixing the issue discovered by https://github.com/Sergei768
|
||||
- Generate JSON-RPC headers at build time ([#8350](https://github.com/chatmail/core/pull/8350)).
|
||||
- Generate Qt JSON-RPC bindings ([#8330](https://github.com/chatmail/core/pull/8330)).
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Introduce keyupdate messages informing contacts about relay changes.
|
||||
- Remove `Final-Recipient` from MDNs (and keyupdates).
|
||||
- Carry all published relay addresses in securejoin links ([#8591](https://github.com/chatmail/core/pull/8591)).
|
||||
- Use display name for contacts in encryption info ([#8609](https://github.com/chatmail/core/pull/8609)).
|
||||
- Do not create device messages for IMAP authentication errors.
|
||||
- Delete avatars referred to by parameters of special contacts.
|
||||
- Import `Autocrypt-Gossip` keys without checking the addresses.
|
||||
- Ignore `Chat-Disposition-Notification-To` value.
|
||||
- Increase `sys.msgsize_max_recommended` to match chatmail relay message size limit.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Do not try to load profile image from param for self.
|
||||
- Send legacy securejoin key requests as `multipart/mixed` so they are not rejected by chatmail relays.
|
||||
- rpc: avoid hang when requests race a dying rpc-server.
|
||||
- Take `timestamp_rcvd` into account in `estimate_deletion_cnt`.
|
||||
- Reliably complete configuration with progress=1000 or progress=0.
|
||||
- Make `create_send_msg_jobs` actually return row IDs.
|
||||
- Don't notify of missed call from blocked user.
|
||||
- Trash MDNs that reference no message early.
|
||||
- Return no relay address for key-contacts without an address.
|
||||
- Do not emit events in `set_profile_image()` if contact avatar is unchanged.
|
||||
- Remove `Original-Recipient` field from MDNs.
|
||||
- ffi: support custom allocators in event string getters.
|
||||
- Start checking column documentation in CI and add comment for `transports.add_timestamp`.
|
||||
- Sanitize `version_string` we got from the wire ([#8582](https://github.com/chatmail/core/pull/8582))
|
||||
- RUSTSEC-2026-0258 ([#8603](https://github.com/chatmail/core/pull/8603)).
|
||||
|
||||
### Build system
|
||||
|
||||
- Use `--locked` in `scripts/clippy.sh`.
|
||||
- Produce correct wheel metadata.
|
||||
|
||||
### Documentation
|
||||
|
||||
- Always suggest using `--locked` with "cargo install".
|
||||
- JSON-RPC: clarify when `reactions` is `None`.
|
||||
- Fix async-imap and async-smtp URLs in README.md ([#8637](https://github.com/chatmail/core/pull/8637)).
|
||||
- Update the timeout value in `DC_EVENT_CALL_ENDED` description.
|
||||
|
||||
### Refactor
|
||||
|
||||
- Don't store email address in location KML. ([#8615](https://github.com/chatmail/core/pull/8615)).
|
||||
- Turn `DC_CHAT_ID_*` into `ChatId::*` associated constants.
|
||||
- Turn `DC_MSG_ID_*` into `MsgId::*` associated constants.
|
||||
- Don't include email addresses in export filenames ([#8626](https://github.com/chatmail/core/pull/8626)).
|
||||
- Make `create_send_msg_jobs()` private.
|
||||
- Rename `automatic_relay_management` to autorelay.
|
||||
- Extract shared pieces for non-chat messages.
|
||||
- Remove unused functions from the tools module.
|
||||
- Remove the code to set own avatar in `set_profile_image()`.
|
||||
- Move pgp tests to submodule.
|
||||
- Split `flake.nix` into multiple files.
|
||||
- Use `&[..]` instead of `&Vec<..>`.
|
||||
|
||||
### Tests
|
||||
|
||||
- [**breaking**] rename rpc fixtures to disambiguate from ffi fixtures.
|
||||
- Test `dc_send_msg_sync()`.
|
||||
- Print which error/warning was expected if it does not arrive.
|
||||
|
||||
### CI
|
||||
|
||||
- Update Rust to 1.98.1.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Add script to show the sizes of futures (async Rust) ([#8536](https://github.com/chatmail/core/pull/8536)).
|
||||
- deps: bump zizmorcore/zizmor-action from 0.6.1 to 0.6.2.
|
||||
- deps: bump swatinem/rust-cache from 2.9.1 to 2.9.2.
|
||||
- deps: bump pypa/gh-action-pypi-publish from 1.14.1 to 1.14.2.
|
||||
- deps: bump taiki-e/install-action from 2.85.1 to 2.86.7.
|
||||
- cargo: bump futures from 0.3.33 to 0.3.34.
|
||||
- cargo: bump thiserror from 2.0.19 to 2.0.20.
|
||||
- cargo: bump syn from 3.0.3 to 3.0.4.
|
||||
- cargo: bump log from 0.4.33 to 0.4.34.
|
||||
- cargo: bump mail-builder from 0.4.4 to 0.5.0.
|
||||
- cargo: bump blake3 from 1.8.5 to 1.8.7.
|
||||
- cargo: bump http-body-util from 0.1.3 to 0.1.5.
|
||||
- cargo: bump uuid from 1.20.0 to 1.25.0.
|
||||
- cargo: bump data-encoding from 2.11.0 to 2.11.1.
|
||||
- bump chacha20 0.10.1 to 0.10.2.
|
||||
|
||||
## [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
|
||||
@@ -8103,3 +8782,14 @@ https://github.com/chatmail/core/pulls?q=is%3Apr+is%3Aclosed
|
||||
[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
|
||||
[2.60.0]: https://github.com/chatmail/core/compare/v2.59.0..v2.60.0
|
||||
|
||||
@@ -2,45 +2,59 @@ cmake_minimum_required(VERSION 3.16)
|
||||
project(deltachat LANGUAGES C)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
option(WITH_JSONRPC_BINDINGS "Generate jsonrpc bindings" OFF)
|
||||
|
||||
find_program(CARGO cargo)
|
||||
|
||||
if(APPLE)
|
||||
set(DYNAMIC_EXT "dylib")
|
||||
set(DYNAMIC_EXT "dylib")
|
||||
elseif(UNIX)
|
||||
set(DYNAMIC_EXT "so")
|
||||
set(DYNAMIC_EXT "so")
|
||||
else()
|
||||
set(DYNAMIC_EXT "dll")
|
||||
set(DYNAMIC_EXT "dll")
|
||||
endif()
|
||||
|
||||
if(DEFINED ENV{CARGO_BUILD_TARGET})
|
||||
set(ARCH_DIR "$ENV{CARGO_BUILD_TARGET}")
|
||||
set(CARGO_OUT_DIR "${CMAKE_BINARY_DIR}/target/$ENV{CARGO_BUILD_TARGET}/release")
|
||||
else()
|
||||
set(ARCH_DIR "./")
|
||||
set(CARGO_OUT_DIR "${CMAKE_BINARY_DIR}/target/release")
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.a"
|
||||
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.${DYNAMIC_EXT}"
|
||||
"${CMAKE_BINARY_DIR}/target/release/pkgconfig/deltachat.pc"
|
||||
COMMAND
|
||||
PREFIX=${CMAKE_INSTALL_PREFIX}
|
||||
LIBDIR=${CMAKE_INSTALL_FULL_LIBDIR}
|
||||
INCLUDEDIR=${CMAKE_INSTALL_FULL_INCLUDEDIR}
|
||||
${CARGO} build --target-dir=${CMAKE_BINARY_DIR}/target --release
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/deltachat-ffi
|
||||
)
|
||||
if(WITH_JSONRPC_BINDINGS)
|
||||
set(JSONRPC_ARGS --package deltachat-jsonrpc-bindings)
|
||||
endif()
|
||||
|
||||
add_custom_target(
|
||||
lib_deltachat
|
||||
ALL
|
||||
DEPENDS
|
||||
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.a"
|
||||
"${CMAKE_BINARY_DIR}/target/release/libdeltachat.${DYNAMIC_EXT}"
|
||||
"${CMAKE_BINARY_DIR}/target/release/pkgconfig/deltachat.pc"
|
||||
lib_deltachat
|
||||
ALL
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E env
|
||||
CARGO_TARGET_DIR="${CMAKE_BINARY_DIR}/target"
|
||||
PREFIX="${CMAKE_INSTALL_PREFIX}"
|
||||
LIBDIR="${CMAKE_INSTALL_FULL_LIBDIR}"
|
||||
INCLUDEDIR="${CMAKE_INSTALL_FULL_INCLUDEDIR}"
|
||||
${CARGO} build --release --package deltachat_ffi ${JSONRPC_ARGS}
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
)
|
||||
|
||||
install(FILES "deltachat-ffi/deltachat.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/libdeltachat.a" DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/libdeltachat.${DYNAMIC_EXT}" DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(FILES "${CMAKE_BINARY_DIR}/target/${ARCH_DIR}/release/pkgconfig/deltachat.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
||||
install(FILES "deltachat-ffi/deltachat.h" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
install(FILES "${CARGO_OUT_DIR}/libdeltachat.a" DESTINATION "${CMAKE_INSTALL_LIBDIR}")
|
||||
install(FILES "${CARGO_OUT_DIR}/libdeltachat.${DYNAMIC_EXT}" DESTINATION "${CMAKE_INSTALL_LIBDIR}")
|
||||
install(FILES "${CARGO_OUT_DIR}/pkgconfig/deltachat.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
|
||||
|
||||
if(WITH_JSONRPC_BINDINGS)
|
||||
install(
|
||||
FILES
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deltachat-jsonrpc-bindings/qt/generated/types.hpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deltachat-jsonrpc-bindings/qt/generated/client.hpp"
|
||||
DESTINATION
|
||||
"${CMAKE_INSTALL_INCLUDEDIR}/deltachat-jsonrpc/generated"
|
||||
)
|
||||
install(
|
||||
FILES
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deltachat-jsonrpc-bindings/qt/deltachat-jsonrpc/cffi_client.hpp"
|
||||
DESTINATION
|
||||
"${CMAKE_INSTALL_INCLUDEDIR}/deltachat-jsonrpc"
|
||||
)
|
||||
endif()
|
||||
|
||||
734
Cargo.lock
generated
734
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
23
Cargo.toml
23
Cargo.toml
@@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "2.49.0"
|
||||
version = "2.60.0"
|
||||
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"
|
||||
@@ -70,7 +70,7 @@ iroh-gossip = { version = "0.35", default-features = false, features = ["net"] }
|
||||
iroh = { version = "0.35", default-features = false }
|
||||
kamadak-exif = "0.6.1"
|
||||
libc = { workspace = true }
|
||||
mail-builder = { version = "0.4.4", default-features = false }
|
||||
mail-builder = { version = "0.5.0", default-features = false }
|
||||
mailparse = { workspace = true }
|
||||
mime = "0.3.17"
|
||||
num_cpus = "1.17"
|
||||
@@ -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"
|
||||
@@ -130,6 +129,7 @@ members = [
|
||||
"deltachat-ffi",
|
||||
"deltachat_derive",
|
||||
"deltachat-jsonrpc",
|
||||
"deltachat-jsonrpc-bindings",
|
||||
"deltachat-rpc-server",
|
||||
"deltachat-ratelimit",
|
||||
"deltachat-repl",
|
||||
@@ -184,6 +184,7 @@ base64 = "0.22"
|
||||
chrono = { version = "0.4.44", default-features = false }
|
||||
deltachat-contact-tools = { path = "deltachat-contact-tools" }
|
||||
deltachat-jsonrpc = { path = "deltachat-jsonrpc", default-features = false }
|
||||
deltachat-jsonrpc-bindings = { path = "deltachat-jsonrpc-bindings", default-features = false }
|
||||
deltachat = { path = ".", default-features = false }
|
||||
futures = "0.3.32"
|
||||
futures-lite = "2.6.1"
|
||||
@@ -203,7 +204,7 @@ thiserror = "2"
|
||||
tokio = "1"
|
||||
tokio-util = "0.7.18"
|
||||
tracing-subscriber = "0.3"
|
||||
yerpc = "0.6.4"
|
||||
yerpc = "0.7"
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
|
||||
@@ -21,8 +21,8 @@ The following protocols are handled without requiring API users to know much abo
|
||||
|
||||
- secure TLS setup with DNS caching and shadowsocks/proxy support
|
||||
|
||||
- robust [SMTP](https://github.com/chatmail/async-imap)
|
||||
and [IMAP](https://github.com/chatmail/async-smtp) handling
|
||||
- robust [SMTP](https://github.com/chatmail/async-smtp)
|
||||
and [IMAP](https://github.com/chatmail/async-imap) handling
|
||||
|
||||
- safe and interoperable [MIME parsing](https://github.com/staktrace/mailparse)
|
||||
and [MIME building](https://github.com/stalwartlabs/mail-builder).
|
||||
@@ -167,7 +167,7 @@ $ cargo test -- --ignored
|
||||
|
||||
Install [`cargo-bolero`](https://github.com/camshaft/bolero) with
|
||||
```sh
|
||||
$ cargo install cargo-bolero
|
||||
$ cargo install --locked cargo-bolero
|
||||
```
|
||||
|
||||
Run fuzzing tests with
|
||||
|
||||
27
STYLE.md
27
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
|
||||
@@ -161,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.49.0"
|
||||
version = "2.60.0"
|
||||
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;
|
||||
@@ -390,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
|
||||
@@ -426,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)
|
||||
@@ -500,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()`,
|
||||
@@ -520,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
|
||||
@@ -546,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.
|
||||
@@ -606,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
|
||||
@@ -647,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.
|
||||
@@ -685,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,
|
||||
@@ -723,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,
|
||||
@@ -931,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);
|
||||
@@ -1235,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.
|
||||
@@ -1396,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
|
||||
|
||||
|
||||
/**
|
||||
@@ -1417,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.
|
||||
*/
|
||||
@@ -1472,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);
|
||||
|
||||
@@ -1644,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,
|
||||
@@ -1662,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
|
||||
@@ -1691,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
|
||||
@@ -2129,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.
|
||||
*
|
||||
@@ -2691,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);
|
||||
|
||||
@@ -2827,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
|
||||
|
||||
/**
|
||||
@@ -3633,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
|
||||
@@ -3726,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.
|
||||
*
|
||||
@@ -3743,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
|
||||
@@ -3757,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.
|
||||
@@ -3824,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
|
||||
@@ -3865,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
|
||||
@@ -4028,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).
|
||||
@@ -4242,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.
|
||||
@@ -4415,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.
|
||||
@@ -4558,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.
|
||||
@@ -4617,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
|
||||
|
||||
|
||||
/**
|
||||
@@ -4934,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().
|
||||
*
|
||||
@@ -4973,6 +4879,20 @@ uint32_t dc_msg_get_original_msg_id (const dc_msg_t* msg);
|
||||
*/
|
||||
uint32_t dc_msg_get_saved_msg_id (const dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* Check if the message is pinned.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
int dc_msg_is_pinned (const dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* @class dc_contact_t
|
||||
*
|
||||
@@ -5256,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
|
||||
*
|
||||
@@ -5612,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.
|
||||
*/
|
||||
@@ -5668,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.
|
||||
@@ -5769,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
|
||||
* @{
|
||||
@@ -5891,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().
|
||||
@@ -6377,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.
|
||||
@@ -6423,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
|
||||
@@ -6667,7 +6462,7 @@ void dc_event_unref(dc_event_t* event);
|
||||
|
||||
/**
|
||||
* An incoming or outgoing call was ended using dc_end_call() on this or another device, by caller or callee.
|
||||
* Moreover, the event is sent when the call was not accepted within 1 minute timeout.
|
||||
* Moreover, the event is sent when the call was not accepted within two minutes.
|
||||
*
|
||||
* UI usually only takes action in case call UI was opened before, otherwise the event should be ignored.
|
||||
*
|
||||
@@ -6679,9 +6474,10 @@ void dc_event_unref(dc_event_t* event);
|
||||
* Transport relay added/deleted or default has changed.
|
||||
* UI should update the list.
|
||||
*
|
||||
* The event is emitted when the transports are modified on another device
|
||||
* using the JSON-RPC calls `add_or_update_transport`, `add_transport_from_qr`, `delete_transport`,
|
||||
* `set_transport_unpublished` or `set_config(configured_addr)`.
|
||||
* The event is emitted on the device modifying the transports
|
||||
* as well as on other devices applying the synced change,
|
||||
* for the JSON-RPC calls `add_or_update_transport`, `add_transport_from_qr`,
|
||||
* `delete_transport` or `set_config(configured_addr)`.
|
||||
*/
|
||||
#define DC_EVENT_TRANSPORTS_MODIFIED 2600
|
||||
|
||||
@@ -6695,14 +6491,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")
|
||||
*/
|
||||
@@ -6710,61 +6498,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
|
||||
*
|
||||
@@ -6940,10 +6673,7 @@ void dc_event_unref(dc_event_t* event);
|
||||
/// Used as the name for the corresponding chatlist entry.
|
||||
#define DC_STR_ARCHIVEDCHATS 40
|
||||
|
||||
/// "Cannot login as %1$s."
|
||||
///
|
||||
/// Used in error strings.
|
||||
/// - %1$s will be replaced by the failing login name
|
||||
/// @deprecated 2026-08-24
|
||||
#define DC_STR_CANNOT_LOGIN 60
|
||||
|
||||
/// "Location streaming enabled."
|
||||
@@ -6988,7 +6718,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
|
||||
@@ -7037,11 +6767,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"
|
||||
@@ -7101,11 +6827,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.
|
||||
@@ -7437,6 +7158,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.
|
||||
@@ -7532,6 +7277,15 @@ 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
|
||||
|
||||
/// @deprecated 2026-08-31
|
||||
#define DC_STR_PHASING_OUT 245
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::chat::ChatItem;
|
||||
use crate::constants::DC_MSG_ID_DAYMARKER;
|
||||
use crate::contact::ContactId;
|
||||
use crate::location::Location;
|
||||
use crate::message::MsgId;
|
||||
@@ -21,7 +20,7 @@ impl dc_array_t {
|
||||
Self::ContactIds(array) => array[index].to_u32(),
|
||||
Self::Chat(array) => match array[index] {
|
||||
ChatItem::Message { msg_id } => msg_id.to_u32(),
|
||||
ChatItem::DayMarker { .. } => DC_MSG_ID_DAYMARKER,
|
||||
ChatItem::DayMarker { .. } => MsgId::DAYMARKER.to_u32(),
|
||||
},
|
||||
Self::Locations(array) => array[index].location_id,
|
||||
Self::Uint(array) => array[index],
|
||||
|
||||
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]
|
||||
|
||||
16
deltachat-jsonrpc-bindings/Cargo.toml
Normal file
16
deltachat-jsonrpc-bindings/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc-bindings"
|
||||
version = "2.60.0"
|
||||
description = "Autogenerate DeltaChat JSON-RPC API bindings at build time"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/chatmail/core"
|
||||
|
||||
[build-dependencies]
|
||||
deltachat-jsonrpc = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
|
||||
[features]
|
||||
default = ["vendored"]
|
||||
vendored = ["deltachat-jsonrpc/vendored"]
|
||||
7
deltachat-jsonrpc-bindings/build.rs
Normal file
7
deltachat-jsonrpc-bindings/build.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
use deltachat_jsonrpc::api::{write_qt_bindings, write_ts_bindings};
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
write_ts_bindings(Path::new("typescript/generated"));
|
||||
write_qt_bindings(Path::new("qt/generated"), "deltachat");
|
||||
}
|
||||
1
deltachat-jsonrpc-bindings/qt/.gitignore
vendored
Normal file
1
deltachat-jsonrpc-bindings/qt/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
generated
|
||||
111
deltachat-jsonrpc-bindings/qt/deltachat-jsonrpc/cffi_client.hpp
Normal file
111
deltachat-jsonrpc-bindings/qt/deltachat-jsonrpc/cffi_client.hpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
#include "deltachat.h"
|
||||
#include "generated/client.hpp"
|
||||
#include "generated/types.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace deltachat {
|
||||
|
||||
class CffiTransport : public Transport {
|
||||
using CompletionHandler = Transport::CompletionHandler;
|
||||
|
||||
public:
|
||||
explicit CffiTransport(dc_accounts_t *accounts)
|
||||
: jsonrpc_(dc_jsonrpc_init(accounts)) {
|
||||
if (!jsonrpc_)
|
||||
std::abort();
|
||||
thread_ = std::thread([this] { run(); });
|
||||
}
|
||||
|
||||
~CffiTransport() override {
|
||||
done_ = true;
|
||||
// Unblock dc_jsonrpc_next_response by sending a dummy request
|
||||
if (jsonrpc_)
|
||||
dc_jsonrpc_request(
|
||||
jsonrpc_,
|
||||
"{\"jsonrpc\":\"2.0\",\"id\":0,\"method\":\"get_system_info\"}");
|
||||
if (thread_.joinable())
|
||||
thread_.join();
|
||||
std::lock_guard lk(mu_);
|
||||
for (auto &[id, cb] : pending_) {
|
||||
cb(Result<QJsonValue>::error(-32060, "Transport destructed"));
|
||||
}
|
||||
pending_.clear();
|
||||
if (jsonrpc_)
|
||||
dc_jsonrpc_unref(jsonrpc_);
|
||||
}
|
||||
|
||||
virtual void send(const QString method, const QJsonValue params,
|
||||
CompletionHandler onCompleted) override {
|
||||
uint32_t id = next_id_++;
|
||||
QJsonObject envelope{
|
||||
{"jsonrpc", "2.0"},
|
||||
{"id", static_cast<qint64>(id)},
|
||||
{"method", method},
|
||||
{"params", params},
|
||||
};
|
||||
|
||||
{
|
||||
std::lock_guard lk(mu_);
|
||||
pending_[id] = std::move(onCompleted);
|
||||
}
|
||||
|
||||
QByteArray json = QJsonDocument(envelope).toJson(QJsonDocument::Compact);
|
||||
dc_jsonrpc_request(jsonrpc_, json.constData());
|
||||
}
|
||||
|
||||
private:
|
||||
void run() {
|
||||
while (!done_) {
|
||||
char *raw_json = dc_jsonrpc_next_response(jsonrpc_);
|
||||
if (!raw_json) {
|
||||
break;
|
||||
}
|
||||
QByteArray json{raw_json};
|
||||
dc_str_unref(raw_json);
|
||||
if (done_)
|
||||
break;
|
||||
|
||||
QJsonObject obj = QJsonDocument::fromJson(json).object();
|
||||
|
||||
if (!obj["id"].isDouble()) {
|
||||
qCritical() << "No valid rpc id in" << QString{json};
|
||||
continue;
|
||||
}
|
||||
uint32_t id = static_cast<uint32_t>(obj["id"].toInt());
|
||||
|
||||
CompletionHandler cb;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mu_);
|
||||
if (auto nh = pending_.extract(id)) {
|
||||
cb = std::move(nh.mapped());
|
||||
} else {
|
||||
qCritical() << "Could not map response" << QString{json};
|
||||
continue;
|
||||
}
|
||||
}
|
||||
cb(parseResult(obj));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
dc_jsonrpc_instance_t *jsonrpc_;
|
||||
std::thread thread_;
|
||||
std::mutex mu_;
|
||||
std::atomic<uint32_t> next_id_{1};
|
||||
std::atomic<bool> done_{false};
|
||||
std::unordered_map<uint32_t, CompletionHandler> pending_;
|
||||
};
|
||||
|
||||
class CffiDeltaChat : public RawClient {
|
||||
public:
|
||||
explicit CffiDeltaChat(dc_accounts_t *accounts)
|
||||
: RawClient(std::make_unique<CffiTransport>(accounts)) {}
|
||||
};
|
||||
|
||||
} // namespace deltachat
|
||||
Q_DECLARE_METATYPE(deltachat::CffiDeltaChat *)
|
||||
1
deltachat-jsonrpc-bindings/src/lib.rs
Normal file
1
deltachat-jsonrpc-bindings/src/lib.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -54,5 +54,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "2.49.0"
|
||||
"version": "2.60.0"
|
||||
}
|
||||
@@ -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"]>(
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "2.49.0"
|
||||
version = "2.60.0"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/chatmail/core"
|
||||
|
||||
|
||||
@@ -5,31 +5,30 @@ 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::constants::DC_MSG_ID_DAYMARKER;
|
||||
use deltachat::contact::{may_be_valid_addr, Contact, ContactId, Origin};
|
||||
use deltachat::config::{Config, get_all_ui_config_keys};
|
||||
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, 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 +36,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 +52,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,8 +63,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::login_param::TransportListEntry;
|
||||
use crate::api::types::appversions::JsonrpcAppSource;
|
||||
use crate::api::types::chat_list::{ChatListItemFetchResult, get_chat_list_item_by_id};
|
||||
use crate::api::types::qr::{QrObject, SecurejoinSource, SecurejoinUiPath};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -157,7 +153,7 @@ impl CommandApi {
|
||||
}
|
||||
}
|
||||
|
||||
#[rpc(all_positional, ts_outdir = "typescript/generated")]
|
||||
#[rpc(all_positional)]
|
||||
impl CommandApi {
|
||||
/// Test function.
|
||||
async fn sleep(&self, delay: f64) {
|
||||
@@ -330,12 +326,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 +335,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?;
|
||||
@@ -527,7 +502,6 @@ impl CommandApi {
|
||||
/// 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 set whether contacts see this transport.
|
||||
async fn add_or_update_transport(
|
||||
&self,
|
||||
account_id: u32,
|
||||
@@ -552,24 +526,8 @@ 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::delete_transport()] 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()
|
||||
.map(|t| t.param.into())
|
||||
.collect();
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
async fn list_transports_ex(&self, account_id: u32) -> Result<Vec<TransportListEntry>> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let res = ctx
|
||||
.list_transports()
|
||||
@@ -580,33 +538,17 @@ impl CommandApi {
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Removes the transport with the specified email address
|
||||
/// (i.e. [EnteredLoginParam::addr]).
|
||||
/// Removes a transport.
|
||||
/// UIs should call this function when the user removes a relay.
|
||||
///
|
||||
/// The last transport cannot be removed.
|
||||
/// If the removed transport was the one used for sending,
|
||||
/// another one is chosen automatically.
|
||||
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.
|
||||
///
|
||||
/// 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.
|
||||
async fn set_transport_unpublished(
|
||||
&self,
|
||||
account_id: u32,
|
||||
addr: String,
|
||||
unpublished: bool,
|
||||
) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
ctx.set_transport_unpublished(&addr, unpublished).await
|
||||
}
|
||||
|
||||
/// Signal an ongoing process to stop.
|
||||
async fn stop_ongoing_process(&self, account_id: u32) -> Result<()> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
@@ -735,10 +677,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,
|
||||
@@ -850,7 +801,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,
|
||||
@@ -934,7 +885,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?;
|
||||
@@ -1015,7 +965,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
|
||||
@@ -1072,7 +1022,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)
|
||||
@@ -1097,9 +1049,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?;
|
||||
@@ -1344,7 +1293,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.
|
||||
///
|
||||
@@ -1366,26 +1315,37 @@ 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.
|
||||
///
|
||||
/// * `add_daymarker` - If `true`, add day markers as `DC_MSG_ID_DAYMARKER` to the result,
|
||||
/// 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 `MsgId::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.
|
||||
async fn get_message_ids(
|
||||
&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
|
||||
@@ -1393,7 +1353,7 @@ impl CommandApi {
|
||||
.map(|chat_item| -> u32 {
|
||||
match chat_item {
|
||||
deltachat::chat::ChatItem::Message { msg_id } => msg_id.to_u32(),
|
||||
deltachat::chat::ChatItem::DayMarker { .. } => DC_MSG_ID_DAYMARKER,
|
||||
deltachat::chat::ChatItem::DayMarker { .. } => MsgId::DAYMARKER.to_u32(),
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
@@ -1417,21 +1377,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
|
||||
@@ -1494,12 +1457,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,
|
||||
@@ -1507,7 +1490,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
|
||||
@@ -1868,25 +1851,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(
|
||||
@@ -2071,13 +2040,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.
|
||||
@@ -2106,6 +2072,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,
|
||||
@@ -2128,6 +2109,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
|
||||
// ---------------------------------------------
|
||||
@@ -2213,6 +2227,9 @@ impl CommandApi {
|
||||
/// Get blob encoded as base64 from a webxdc message
|
||||
///
|
||||
/// path is the path of the file within webxdc archive
|
||||
///
|
||||
/// If the file is `icon.png` or `icon.jpg`,
|
||||
/// loading it may fail if dimensions are unexpectedly large.
|
||||
async fn get_webxdc_blob(
|
||||
&self,
|
||||
account_id: u32,
|
||||
@@ -2223,7 +2240,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))
|
||||
}
|
||||
|
||||
@@ -2359,6 +2376,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,
|
||||
@@ -2370,19 +2388,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,
|
||||
@@ -2395,6 +2410,7 @@ impl CommandApi {
|
||||
}
|
||||
|
||||
/// Returns reactions to the message.
|
||||
/// `None` when there are no reactions.
|
||||
async fn get_message_reactions(
|
||||
&self,
|
||||
account_id: u32,
|
||||
@@ -2726,6 +2742,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.
|
||||
@@ -467,9 +478,9 @@ pub enum EventType {
|
||||
///
|
||||
/// UI should update the list.
|
||||
///
|
||||
/// This event is emitted when transport
|
||||
/// synchronization messages arrives,
|
||||
/// but not when the UI modifies the transport list by itself.
|
||||
/// The event is emitted on the device modifying
|
||||
/// the transports as well as on other devices
|
||||
/// applying the synced change.
|
||||
TransportsModified,
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,16 +4,6 @@ use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use yerpc::TypeDef;
|
||||
|
||||
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TransportListEntry {
|
||||
/// The login data entered by the user.
|
||||
pub param: EnteredLoginParam,
|
||||
/// Whether this transport is set to 'unpublished'.
|
||||
/// See `set_transport_unpublished` / `setTransportUnpublished` for details.
|
||||
pub is_unpublished: bool,
|
||||
}
|
||||
|
||||
/// Login parameters entered by the user.
|
||||
///
|
||||
/// Usually it will be enough to only set `addr` and `password`,
|
||||
@@ -33,6 +23,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,19 +56,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 {
|
||||
fn from(transport: dc::TransportListEntry) -> Self {
|
||||
TransportListEntry {
|
||||
param: transport.param.into(),
|
||||
is_unpublished: transport.is_unpublished,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<dc::EnteredLoginParam> for EnteredLoginParam {
|
||||
@@ -85,6 +68,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 +77,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 +87,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 +103,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 +150,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,9 @@ pub struct MessageObject {
|
||||
|
||||
saved_message_id: Option<u32>,
|
||||
|
||||
is_pinned: bool,
|
||||
|
||||
/// `None` when there are no reactions.
|
||||
reactions: Option<JsonrpcReactions>,
|
||||
|
||||
vcard_contact: Option<VcardContact>,
|
||||
@@ -263,6 +266,7 @@ impl MessageObject {
|
||||
.await?
|
||||
.map(|id| id.to_u32()),
|
||||
|
||||
is_pinned: message.is_pinned(),
|
||||
reactions,
|
||||
|
||||
vcard_contact: vcard_contacts.first().cloned(),
|
||||
@@ -287,8 +291,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 +394,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 +429,8 @@ pub enum SystemMessageType {
|
||||
|
||||
CallAccepted,
|
||||
CallEnded,
|
||||
MessagePinned,
|
||||
MessageUnpinned,
|
||||
}
|
||||
|
||||
impl From<deltachat::mimeparser::SystemMessage> for SystemMessageType {
|
||||
@@ -456,6 +460,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 +734,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?;
|
||||
|
||||
@@ -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.49.0"
|
||||
version = "2.60.0"
|
||||
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,14 +221,14 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
||||
},
|
||||
statestr,
|
||||
downloadstate,
|
||||
&temp2,
|
||||
temp2,
|
||||
);
|
||||
}
|
||||
|
||||
async fn log_msglist(context: &Context, msglist: &[MsgId]) -> Result<()> {
|
||||
let mut lines_out = 0;
|
||||
for &msg_id in msglist {
|
||||
if msg_id == MsgId::new(DC_MSG_ID_DAYMARKER) {
|
||||
if msg_id == MsgId::DAYMARKER {
|
||||
println!(
|
||||
"--------------------------------------------------------------------------------"
|
||||
);
|
||||
@@ -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,
|
||||
},
|
||||
)
|
||||
@@ -634,7 +630,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
.into_iter()
|
||||
.map(|x| match x {
|
||||
ChatItem::Message { msg_id } => msg_id,
|
||||
ChatItem::DayMarker { .. } => MsgId::new(DC_MSG_ID_DAYMARKER),
|
||||
ChatItem::DayMarker { .. } => MsgId::DAYMARKER,
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -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.49.0"
|
||||
version = "2.60.0"
|
||||
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.
|
||||
|
||||
@@ -183,7 +192,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 +228,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 +350,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 +398,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]
|
||||
@@ -463,16 +468,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)
|
||||
@@ -495,3 +490,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}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
@@ -38,8 +39,15 @@ class RpcMethod:
|
||||
"params": args,
|
||||
"id": request_id,
|
||||
}
|
||||
self.rpc.request_results[request_id] = queue = Queue()
|
||||
self.rpc.request_queue.put(request)
|
||||
queue: Queue = Queue()
|
||||
# Register before testing for shutdown, so that either the reader loop
|
||||
# finds this request while draining, or the test below catches it here.
|
||||
# Testing first would race with the reader loop finishing in between.
|
||||
self.rpc.request_results[request_id] = queue
|
||||
if self.rpc.request_queue_closed:
|
||||
self.rpc._fail_request(request_id)
|
||||
else:
|
||||
self.rpc.request_queue.put(request)
|
||||
|
||||
def rpc_future():
|
||||
"""Wait for the request to receive a result."""
|
||||
@@ -78,6 +86,10 @@ class Rpc:
|
||||
# Map from request ID to a Queue which provides a single result
|
||||
self.request_results: dict[int, Queue]
|
||||
self.request_queue: Queue[Any]
|
||||
# Emulates `request_queue.shutdown(immediate=False)`, which needs Python 3.13:
|
||||
# https://github.com/python/cpython/blob/v3.13.0/Lib/queue.py#L236-L257
|
||||
# Note that `request_queue_closed` is set by the reader loop.
|
||||
self.request_queue_closed: bool
|
||||
self.closing: bool
|
||||
self.reader_thread: Thread
|
||||
self.writer_thread: Thread
|
||||
@@ -107,6 +119,7 @@ class Rpc:
|
||||
self.event_queues = {}
|
||||
self.request_results = {}
|
||||
self.request_queue = Queue()
|
||||
self.request_queue_closed = False
|
||||
self.closing = False
|
||||
self.reader_thread = Thread(target=self.reader_loop)
|
||||
self.reader_thread.start()
|
||||
@@ -123,6 +136,8 @@ class Rpc:
|
||||
# The reader_loop already saw EOF on stdout, so the process
|
||||
# has exited and stderr is available.
|
||||
stderr = self.process.stderr.read().decode(errors="replace").strip()
|
||||
self.closing = True
|
||||
self._shutdown_loops()
|
||||
if stderr:
|
||||
raise JsonRpcError(f"RPC server failed to start: {stderr}") from e
|
||||
raise JsonRpcError(f"RPC server startup check failed: {e}") from e
|
||||
@@ -135,11 +150,31 @@ class Rpc:
|
||||
"""Terminate RPC server process and wait until the reader loop finishes."""
|
||||
self.closing = True
|
||||
self.stop_io_for_all_accounts()
|
||||
# Let `events_loop` stop cleanly on `closing` before the pipe goes away,
|
||||
# otherwise it might exit through an "RPC server closed" error instead.
|
||||
self.events_thread.join()
|
||||
self.process.stdin.close()
|
||||
self.reader_thread.join()
|
||||
self._shutdown_loops()
|
||||
|
||||
def _shutdown_loops(self) -> None:
|
||||
"""Close the server pipe and wait for the loop threads to finish.
|
||||
|
||||
The writer blocks on an empty request queue,
|
||||
so it needs the sentinel to notice the shutdown.
|
||||
"""
|
||||
with contextlib.suppress(BrokenPipeError):
|
||||
# An exited server may leave data unflushed,
|
||||
# which close() would try to write out again.
|
||||
self.process.stdin.close()
|
||||
self.request_queue.put(None)
|
||||
self.reader_thread.join()
|
||||
self.writer_thread.join()
|
||||
self.events_thread.join()
|
||||
|
||||
def _fail_request(self, request_id: int) -> None:
|
||||
"""Answer a registered request with an error, unless it was answered already."""
|
||||
queue = self.request_results.pop(request_id, None)
|
||||
if queue is not None:
|
||||
queue.put({"error": {"code": -32000, "message": "RPC server closed"}})
|
||||
|
||||
def __enter__(self):
|
||||
self.start()
|
||||
@@ -162,9 +197,11 @@ class Rpc:
|
||||
# Log an exception if the reader loop dies.
|
||||
logging.exception("Exception in the reader loop")
|
||||
finally:
|
||||
# Unblock any pending requests when the server closes stdout.
|
||||
for _request_id, queue in self.request_results.items():
|
||||
queue.put({"error": {"code": -32000, "message": "RPC server closed"}})
|
||||
# Shut the request queue first, so that requests registered from now
|
||||
# on are failed by their caller, then answer the pending ones here.
|
||||
self.request_queue_closed = True
|
||||
for request_id in list(self.request_results):
|
||||
self._fail_request(request_id)
|
||||
|
||||
def writer_loop(self) -> None:
|
||||
"""Writer loop ensuring only a single thread writes requests."""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -16,7 +17,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 +34,41 @@ 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()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("version", ["2.24.0"])
|
||||
def test_qr_setup_contact_multitransport(acf, alice_and_remote_bob, version) -> None:
|
||||
"""Test other-core Bob profile can do securejoin with Alice on current core, with multiple transports."""
|
||||
alice, alice_contact_bob, remote_eval = alice_and_remote_bob(version)
|
||||
relay_qr = acf.get_account_qr()
|
||||
alice.add_transport_from_qr(relay_qr)
|
||||
alice.add_transport_from_qr(relay_qr)
|
||||
|
||||
qr_code = alice.get_qr_code()
|
||||
remote_eval(f"bob.secure_join({qr_code!r})")
|
||||
alice.wait_for_securejoin_inviter_success()
|
||||
|
||||
# Test that Alice verified Bob's profile.
|
||||
alice_contact_bob_snapshot = alice_contact_bob.get_snapshot()
|
||||
assert alice_contact_bob_snapshot.is_verified
|
||||
|
||||
remote_eval("bob.wait_for_securejoin_joiner_success()")
|
||||
|
||||
# Test that Bob verified Alice's profile.
|
||||
assert remote_eval("bob_contact_alice.get_snapshot().is_verified")
|
||||
|
||||
|
||||
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,14 +76,64 @@ 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']()")
|
||||
|
||||
assert new_account.get_config("addr") == remote_eval("bob.get_config('addr')")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("replace_relay", [False, True], ids=["add", "replace"])
|
||||
def test_keyupdate_against_core_2_48_march_2026(acf, alice_and_remote_bob, replace_relay):
|
||||
"""Test 2.48 Bob learns a relay change of Alice from a keyupdate, and is shown nothing."""
|
||||
alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.48.0")
|
||||
|
||||
def bob_sees():
|
||||
return remote_eval(
|
||||
"{'chats': len(bob.get_chatlist()),"
|
||||
" 'fresh': len(bob._rpc.get_fresh_msgs(bob.id)),"
|
||||
" 'contacts': len(bob.get_contacts()),"
|
||||
" 'alice_chat': bob._rpc.get_chat_id_by_contact_id(bob.id, bob_contact_alice.id) or 0}",
|
||||
)
|
||||
|
||||
# Keyupdates go to contacts who plausibly hold our key:
|
||||
# an accepted chat alone is not enough, a message must have flowed.
|
||||
alice_chat = alice_contact_bob.create_chat()
|
||||
alice.set_config("keyupdate_debounce", "1")
|
||||
old_addr = alice.get_config("configured_addr")
|
||||
alice_chat.send_text("hi")
|
||||
assert remote_eval("bob.wait_for_incoming_msg().get_snapshot().text") == "hi"
|
||||
before = bob_sees()
|
||||
|
||||
# Certificate merging keeps the newest direct key signature,
|
||||
# and signature timestamps have one-second resolution:
|
||||
# without waiting, the re-signed key can tie with the copy Bob holds, keeping his.
|
||||
time.sleep(2)
|
||||
alice.add_transport_from_qr(acf.get_account_qr())
|
||||
(new_addr,) = [t["addr"] for t in alice.list_transports() if t["addr"] != old_addr]
|
||||
if replace_relay:
|
||||
alice.delete_transport(old_addr)
|
||||
alice.bring_online()
|
||||
|
||||
# The 2.48 core has no encryption enforcement, but the keyupdate MDN without
|
||||
# referenced message keeps it invisible; merging happens before the trashing.
|
||||
for _ in range(60):
|
||||
if new_addr in remote_eval("bob_contact_alice.get_encryption_info()"):
|
||||
break
|
||||
time.sleep(1)
|
||||
else:
|
||||
pytest.fail("Bob never received the keyupdate")
|
||||
|
||||
# It also leaves no trace: no chat with Alice, no message anywhere,
|
||||
# and no address-contact for the address it was sent from.
|
||||
assert bob_sees() == before
|
||||
|
||||
if replace_relay:
|
||||
remote_eval("bob_contact_alice.create_chat().send_text('hello after replacement')")
|
||||
assert alice.wait_for_incoming_msg().get_snapshot().text == "hello after replacement"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import urllib.parse
|
||||
|
||||
import pytest
|
||||
|
||||
from deltachat_rpc_client import EventType
|
||||
@@ -5,17 +7,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
|
||||
|
||||
@@ -24,56 +20,23 @@ def test_add_second_address(acfactory) -> None:
|
||||
|
||||
first_addr = account.list_transports()[0]["addr"]
|
||||
second_addr = account.list_transports()[1]["addr"]
|
||||
third_addr = account.list_transports()[2]["addr"]
|
||||
|
||||
# Cannot delete the first address.
|
||||
with pytest.raises(JsonRpcError):
|
||||
account.delete_transport(first_addr)
|
||||
assert account.get_config("configured_addr") == first_addr
|
||||
account.delete_transport(first_addr)
|
||||
assert len(account.list_transports()) == 2
|
||||
assert account.get_config("configured_addr") != first_addr
|
||||
|
||||
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)
|
||||
account.delete_transport(third_addr)
|
||||
|
||||
|
||||
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 +51,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):
|
||||
@@ -105,8 +68,6 @@ def test_change_address(acfactory) -> None:
|
||||
alice_vcard = alice.self_contact.make_vcard()
|
||||
assert old_alice_addr not in alice_vcard
|
||||
assert new_alice_addr in alice_vcard
|
||||
with pytest.raises(JsonRpcError):
|
||||
alice.delete_transport(new_alice_addr)
|
||||
alice.start_io()
|
||||
|
||||
alice_chat_bob.send_text("Hello again!")
|
||||
@@ -120,18 +81,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 +108,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,22 +125,24 @@ def test_transport_synchronization(acfactory, log) -> None:
|
||||
if "scheduler is running" in ev.msg:
|
||||
return
|
||||
|
||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||
def wait_transports(ac, n):
|
||||
while len(ac.list_transports()) != n:
|
||||
ac.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
||||
|
||||
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)
|
||||
wait_transports(ac1_clone, 2)
|
||||
wait_for_io_started(ac1_clone)
|
||||
assert len(ac1.list_transports()) == 2
|
||||
assert len(ac1_clone.list_transports()) == 2
|
||||
|
||||
ac1_clone.add_transport_from_qr(qr)
|
||||
ac1.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
||||
wait_transports(ac1, 3)
|
||||
wait_for_io_started(ac1)
|
||||
assert len(ac1.list_transports()) == 3
|
||||
assert len(ac1_clone.list_transports()) == 3
|
||||
|
||||
log.section("ac1 clone removes second transport")
|
||||
@@ -218,24 +150,17 @@ def test_transport_synchronization(acfactory, log) -> None:
|
||||
addr3 = transport3["addr"]
|
||||
ac1_clone.delete_transport(transport2["addr"])
|
||||
|
||||
ac1.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
||||
wait_transports(ac1, 2)
|
||||
wait_for_io_started(ac1)
|
||||
[transport1, transport3] = ac1.list_transports()
|
||||
|
||||
log.section("ac1 changes the primary transport")
|
||||
log.section("ac1 changes the sending 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
|
||||
|
||||
log.section("ac1 removes the first transport")
|
||||
ac1.delete_transport(transport1["addr"])
|
||||
|
||||
ac1_clone.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
||||
wait_transports(ac1_clone, 1)
|
||||
wait_for_io_started(ac1_clone)
|
||||
[transport3] = ac1_clone.list_transports()
|
||||
assert transport3["addr"] == addr3
|
||||
@@ -248,15 +173,16 @@ 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.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
||||
ac1_transports = ac1.list_transports()
|
||||
assert len(ac1_transports) == 2
|
||||
[transport1, transport2] = ac1_transports
|
||||
@@ -266,11 +192,12 @@ def test_transport_sync_new_as_primary(acfactory, log) -> None:
|
||||
|
||||
log.section("ac1 changes the primary transport")
|
||||
ac1.set_config("configured_addr", transport2["addr"])
|
||||
ac1.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
||||
|
||||
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 +207,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 +223,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 +239,18 @@ def test_transport_limit(acfactory) -> None:
|
||||
account.add_transport_from_qr(qr)
|
||||
|
||||
second_addr = account.list_transports()[1]["addr"]
|
||||
|
||||
account.delete_transport(second_addr)
|
||||
|
||||
# test that adding a transport after deleting one works again
|
||||
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 +285,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()
|
||||
@@ -370,7 +298,6 @@ def test_remove_primary_transport(acfactory, log) -> None:
|
||||
|
||||
log.section("Alice sets up second transport")
|
||||
[transport1, transport2] = alice.list_transports()
|
||||
alice.set_config("configured_addr", transport2["addr"])
|
||||
|
||||
bob_chat.send_text("Hello!")
|
||||
msg1 = alice.wait_for_incoming_msg().get_snapshot()
|
||||
@@ -378,6 +305,7 @@ def test_remove_primary_transport(acfactory, log) -> None:
|
||||
|
||||
log.section("Alice removes the primary relay")
|
||||
alice.delete_transport(transport1["addr"])
|
||||
assert alice.get_config("configured_addr") == transport2["addr"]
|
||||
alice.stop_io()
|
||||
alice.start_io()
|
||||
|
||||
@@ -386,3 +314,33 @@ def test_remove_primary_transport(acfactory, log) -> None:
|
||||
assert msg2.text == "Hello again!"
|
||||
assert msg2.chat.get_basic_snapshot().chat_type == ChatType.SINGLE
|
||||
assert msg2.chat == alice.create_chat(bob)
|
||||
|
||||
|
||||
def test_qr_works_after_removing_primary_transport(acf, log) -> None:
|
||||
log.section("Alice setups an account and adds two additional relays")
|
||||
alice = acf.new_configured_account()
|
||||
relay_qr = acf.get_account_qr()
|
||||
alice.add_transport_from_qr(relay_qr)
|
||||
alice.add_transport_from_qr(relay_qr)
|
||||
|
||||
first_addr = alice.list_transports()[0]["addr"]
|
||||
second_addr = alice.list_transports()[1]["addr"]
|
||||
third_addr = alice.list_transports()[2]["addr"]
|
||||
|
||||
log.section("Alice creates a QR code")
|
||||
chat_qr = alice.get_qr_code()
|
||||
chat_qr_unquoted = urllib.parse.unquote(chat_qr)
|
||||
assert f"&a={first_addr}" in chat_qr_unquoted
|
||||
assert f"&r={third_addr},{second_addr}" in chat_qr_unquoted
|
||||
|
||||
log.section("Alice removes first and second transport")
|
||||
alice.set_config("configured_addr", third_addr)
|
||||
alice.delete_transport(first_addr)
|
||||
alice.delete_transport(second_addr)
|
||||
|
||||
log.section("Bob scans the QR code, which still works")
|
||||
alice.bring_online()
|
||||
bob = acf.get_online_account()
|
||||
bob.secure_join(chat_qr)
|
||||
alice.wait_for_securejoin_inviter_success()
|
||||
bob.wait_for_securejoin_joiner_success()
|
||||
|
||||
@@ -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()
|
||||
@@ -685,31 +746,14 @@ def test_early_failure(tmp_path) -> None:
|
||||
with pytest.raises(JsonRpcError, match="invalid_dir"):
|
||||
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
|
||||
# Requests issued after the server exited must fail immediately
|
||||
# instead of waiting forever for the finished reader loop.
|
||||
with pytest.raises(JsonRpcError, match="RPC server closed"):
|
||||
rpc.get_system_info()
|
||||
|
||||
|
||||
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 +783,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 +822,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 +846,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 +896,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 +932,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 +961,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 +985,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 +1008,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 +1030,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 +1053,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 +1080,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()
|
||||
|
||||
@@ -1059,9 +1108,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()
|
||||
|
||||
@@ -1076,9 +1125,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)
|
||||
@@ -1091,6 +1140,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)
|
||||
@@ -1106,8 +1156,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()
|
||||
|
||||
@@ -1207,8 +1257,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")
|
||||
@@ -1231,11 +1281,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)
|
||||
@@ -1265,8 +1317,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)
|
||||
@@ -1302,8 +1354,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!")
|
||||
@@ -1321,7 +1373,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
|
||||
@@ -1329,7 +1381,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")
|
||||
@@ -1365,17 +1417,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()
|
||||
|
||||
132
deltachat-rpc-client/tests/test_sql_schema_docs.py
Normal file
132
deltachat-rpc-client/tests/test_sql_schema_docs.py
Normal file
@@ -0,0 +1,132 @@
|
||||
"""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)
|
||||
|
||||
|
||||
# Tables where every column carries a comment in docs/schema.sql.
|
||||
# Opt-in: document a table's columns, then add it here to lock it in.
|
||||
FULLY_DOCUMENTED_TABLES = {
|
||||
"contacts",
|
||||
"imap_markseen",
|
||||
"multi_device_sync",
|
||||
"transports",
|
||||
}
|
||||
|
||||
|
||||
def undocumented_columns(sql, tables):
|
||||
result = []
|
||||
table = None
|
||||
documented = False
|
||||
for line in sql.splitlines():
|
||||
code, _, comment = line.strip().partition("--")
|
||||
code = code.strip()
|
||||
if code.startswith("CREATE TABLE "):
|
||||
name = code.removeprefix("CREATE TABLE ").partition("(")[0].strip()
|
||||
table = name if name in tables else None
|
||||
assert not table or code.endswith("("), f"{code}: want one column per line"
|
||||
elif code.startswith(")"):
|
||||
table = None
|
||||
elif table and code and not re.match(r"(UNIQUE|PRIMARY|FOREIGN|CHECK)\b", code):
|
||||
column = re.match(r"(\w+) (?!INTEGER PRIMARY KEY)", code)
|
||||
if column and not documented and not comment:
|
||||
result.append(f"{table}.{column.group(1)}")
|
||||
documented = bool(comment) and not code
|
||||
return result
|
||||
|
||||
|
||||
def test_documented_tables_stay_documented():
|
||||
missing = undocumented_columns(DOC_PATH.read_text(), FULLY_DOCUMENTED_TABLES)
|
||||
assert not missing, "columns without a comment in docs/schema.sql:\n" + "\n".join(missing)
|
||||
|
||||
|
||||
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,8 +18,10 @@ def test_webxdc(acfactory) -> None:
|
||||
"sourceCodeUrl": None,
|
||||
"summary": None,
|
||||
"selfAddr": webxdc_info["selfAddr"],
|
||||
"isAppSender": False,
|
||||
"isBroadcast": False,
|
||||
"sendUpdateInterval": 1000,
|
||||
"sendUpdateMaxSize": 18874368,
|
||||
"sendUpdateMaxSize": 2**20 * (30 - 1) * 3 // 4,
|
||||
}
|
||||
|
||||
status_updates = message.get_webxdc_status_updates()
|
||||
@@ -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.49.0"
|
||||
version = "2.60.0"
|
||||
description = "DeltaChat JSON-RPC server"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
readme = "README.md"
|
||||
license = "MPL-2.0"
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Rename the downloaded binary to `deltachat-rpc-server` and add it to your `PATH`
|
||||
To install from source run:
|
||||
|
||||
```sh
|
||||
cargo install --git https://github.com/chatmail/core/ deltachat-rpc-server
|
||||
cargo install --locked --git https://github.com/chatmail/core/ deltachat-rpc-server
|
||||
```
|
||||
|
||||
The `deltachat-rpc-server` executable will be installed into `$HOME/.cargo/bin` that should be available
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "index.d.ts",
|
||||
"version": "2.49.0"
|
||||
"version": "2.60.0"
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ for (const { folder_name, package_name } of platform_package_names) {
|
||||
|
||||
if (is_local) {
|
||||
package_json.peerDependencies["@deltachat/jsonrpc-client"] =
|
||||
`file:${join(expected_cwd, "/../../deltachat-jsonrpc/typescript")}`;
|
||||
`file:${join(expected_cwd, "/../../deltachat-jsonrpc-bindings/typescript")}`;
|
||||
} else {
|
||||
package_json.peerDependencies["@deltachat/jsonrpc-client"] = "*";
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { ENV_VAR_NAME } from "./const.js";
|
||||
|
||||
const cargoInstallCommand =
|
||||
"cargo install --git https://github.com/chatmail/core deltachat-rpc-server";
|
||||
"cargo install --locked --git https://github.com/chatmail/core deltachat-rpc-server";
|
||||
|
||||
export function NPM_NOT_FOUND_SUPPORTED_PLATFORM_ERROR(package_name) {
|
||||
return `deltachat-rpc-server not found:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user