mirror of
https://github.com/chatmail/core.git
synced 2026-04-05 06:52:10 +03:00
Compare commits
463 Commits
v1.112.5
...
link2xt/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b06d31190 | ||
|
|
e72d527d88 | ||
|
|
f5c36043f6 | ||
|
|
b227ff87dc | ||
|
|
676f311f97 | ||
|
|
f02299c06c | ||
|
|
ed781af52c | ||
|
|
67043177a9 | ||
|
|
49cc5fb673 | ||
|
|
68c95dee17 | ||
|
|
9bd7ab7280 | ||
|
|
7a359f6318 | ||
|
|
38b31aa88d | ||
|
|
e12e026bd8 | ||
|
|
212fbc125c | ||
|
|
2939de013b | ||
|
|
0562e23ee0 | ||
|
|
a8551510cd | ||
|
|
087f6edd0c | ||
|
|
d6b7ee04a0 | ||
|
|
d5c5ff8b3f | ||
|
|
dc4396a699 | ||
|
|
a74b00c3f9 | ||
|
|
2fdb9f8b7e | ||
|
|
80fac3f1b8 | ||
|
|
17a6c88cc7 | ||
|
|
1ba69dbb9b | ||
|
|
ab1c7ebbe2 | ||
|
|
ee715da078 | ||
|
|
27e177dc05 | ||
|
|
7aac4bfc83 | ||
|
|
7b24f9b7a4 | ||
|
|
b36b902eeb | ||
|
|
30024abb6c | ||
|
|
1d9702e9e7 | ||
|
|
ee2eae63d6 | ||
|
|
cd477936b5 | ||
|
|
dbe9d7e34e | ||
|
|
49f143e0d5 | ||
|
|
9d7bdf369d | ||
|
|
a270db1d87 | ||
|
|
7c7cd9cc80 | ||
|
|
47d465e6e4 | ||
|
|
03d3e0578f | ||
|
|
440a442f30 | ||
|
|
1da52d7d1d | ||
|
|
4d74f625d3 | ||
|
|
0a94fbc735 | ||
|
|
9ef34890fa | ||
|
|
3e07f2c173 | ||
|
|
ee28298d7f | ||
|
|
62aed13880 | ||
|
|
bffe934acc | ||
|
|
87ffcaf03e | ||
|
|
2635146328 | ||
|
|
d727d85f6d | ||
|
|
81a7af10c7 | ||
|
|
4a6e94f8ab | ||
|
|
146fe50e20 | ||
|
|
9bf2850fb1 | ||
|
|
ba2c36548e | ||
|
|
d07c743cdc | ||
|
|
d70c1d48b5 | ||
|
|
a8e0cb9b5a | ||
|
|
6ea9a8988b | ||
|
|
45e35b3571 | ||
|
|
e43f9066d8 | ||
|
|
bba6c8f15a | ||
|
|
55aaec744a | ||
|
|
2f24eddb7d | ||
|
|
a33c91afa9 | ||
|
|
d52f2883cf | ||
|
|
b872953bc5 | ||
|
|
c1cb6eef08 | ||
|
|
200b808c27 | ||
|
|
d572d960e5 | ||
|
|
5db75128ba | ||
|
|
fbd2fc8ead | ||
|
|
bc73c16df7 | ||
|
|
0a50bad555 | ||
|
|
82c0058129 | ||
|
|
1bd307a26a | ||
|
|
740f43a2d6 | ||
|
|
c14f45a8f5 | ||
|
|
8269116dba | ||
|
|
db941ccf88 | ||
|
|
a464cbdfe6 | ||
|
|
ea4a0530b8 | ||
|
|
9d3b2d4844 | ||
|
|
c312280ab3 | ||
|
|
572b99a2e1 | ||
|
|
3992b5a063 | ||
|
|
b97cb4b55e | ||
|
|
64c218f1ea | ||
|
|
deed790950 | ||
|
|
b33ae3cd0f | ||
|
|
9480699362 | ||
|
|
94c190e844 | ||
|
|
578e47666f | ||
|
|
7eeced50d1 | ||
|
|
46e127ad27 | ||
|
|
4891849e28 | ||
|
|
e0dd83d538 | ||
|
|
aac8bb950c | ||
|
|
bf21796bc0 | ||
|
|
9cbf413064 | ||
|
|
1b57eb4d8d | ||
|
|
5152e702bd | ||
|
|
c80f1a1997 | ||
|
|
88759c815b | ||
|
|
9c68fac4b6 | ||
|
|
8e17e400b3 | ||
|
|
dae3857db8 | ||
|
|
695f71e124 | ||
|
|
2d30afd212 | ||
|
|
5fe94e8bce | ||
|
|
1351f71632 | ||
|
|
d42322b38b | ||
|
|
ce6876c418 | ||
|
|
2a6b7d9766 | ||
|
|
fa1924da2b | ||
|
|
d5214eb192 | ||
|
|
c47324d671 | ||
|
|
3f8ec5ec56 | ||
|
|
fab504b54c | ||
|
|
dd32430ade | ||
|
|
eb943625a6 | ||
|
|
32ac4a01ca | ||
|
|
f01a9d7d5c | ||
|
|
a5db7104c2 | ||
|
|
18aeb14003 | ||
|
|
4ad2d6e340 | ||
|
|
ce9cd54993 | ||
|
|
23f540f9f9 | ||
|
|
f994b2d8e4 | ||
|
|
6e42b85a36 | ||
|
|
d69e42377d | ||
|
|
de9330b52f | ||
|
|
01d1c4c04b | ||
|
|
7d98978269 | ||
|
|
5024f48609 | ||
|
|
e975568122 | ||
|
|
1f71c69325 | ||
|
|
b80ec8507c | ||
|
|
3a3f3542d9 | ||
|
|
657c5fa947 | ||
|
|
7d0b25c209 | ||
|
|
8d26303cad | ||
|
|
0d8a76593a | ||
|
|
7b49fb2eb6 | ||
|
|
efa37dd283 | ||
|
|
323e44da04 | ||
|
|
70efd0f10a | ||
|
|
fcec81b4c1 | ||
|
|
dd806b2d88 | ||
|
|
5659c1b9c2 | ||
|
|
d538d29b94 | ||
|
|
b4209fac2e | ||
|
|
4d6dfa120e | ||
|
|
f92108be1d | ||
|
|
00cb72f04d | ||
|
|
92e34d67e6 | ||
|
|
65bff8339f | ||
|
|
768f8175e6 | ||
|
|
c3f352aff1 | ||
|
|
5ac2d1b8cb | ||
|
|
8214b2b8c1 | ||
|
|
53ab8a3b35 | ||
|
|
cbe1671104 | ||
|
|
0d0e223238 | ||
|
|
4767f1ce74 | ||
|
|
1a62b6d77f | ||
|
|
915008d474 | ||
|
|
9646766793 | ||
|
|
e948ec3256 | ||
|
|
9ab9d2eb7b | ||
|
|
437f8c48c4 | ||
|
|
e6d9a49187 | ||
|
|
33a014eea4 | ||
|
|
9be871ccf6 | ||
|
|
6eb8abe535 | ||
|
|
91bf87fa80 | ||
|
|
a2599ef08a | ||
|
|
22d0a4bb32 | ||
|
|
7a160033b6 | ||
|
|
3442748be7 | ||
|
|
d451bcfbe3 | ||
|
|
b2993242e4 | ||
|
|
5eaa9eeed2 | ||
|
|
3ed2ac8f0c | ||
|
|
0145203f7b | ||
|
|
59588b319e | ||
|
|
f917c7de6b | ||
|
|
84888fa4c4 | ||
|
|
e0b1644488 | ||
|
|
4beba8ce3c | ||
|
|
bc521a685d | ||
|
|
33caa0f499 | ||
|
|
033ce41c0f | ||
|
|
88a62e1f6e | ||
|
|
dd30f6ab7d | ||
|
|
140d116d98 | ||
|
|
d96b783909 | ||
|
|
572c7f2efb | ||
|
|
bcd6c226f6 | ||
|
|
bae61746f8 | ||
|
|
31f2766074 | ||
|
|
b06c8baa9c | ||
|
|
1e479fe4a3 | ||
|
|
8ea8ee02ed | ||
|
|
55bc556bcf | ||
|
|
3b6d21301b | ||
|
|
472195c7d9 | ||
|
|
afb8b5ce55 | ||
|
|
de3c82ef43 | ||
|
|
4255ae4c2d | ||
|
|
4b4e2f700e | ||
|
|
81fde5c680 | ||
|
|
5340a7d033 | ||
|
|
fc82d728fc | ||
|
|
136e9179e9 | ||
|
|
31e19ca56c | ||
|
|
f2b02b7bb0 | ||
|
|
646ace8e7a | ||
|
|
a2495716b6 | ||
|
|
0f579c6415 | ||
|
|
3eddc9164c | ||
|
|
dd29fae49b | ||
|
|
5b435d11c7 | ||
|
|
b9b0d20e8d | ||
|
|
c68a2e3820 | ||
|
|
c7ad0b1f4f | ||
|
|
0dd9e3a77e | ||
|
|
d27e3d085e | ||
|
|
10b2aa5350 | ||
|
|
3a29a555bf | ||
|
|
f024f396bf | ||
|
|
24d52c5909 | ||
|
|
f9dc8edbcb | ||
|
|
2e6f98f4e4 | ||
|
|
50431d8cfe | ||
|
|
55fcd589db | ||
|
|
081178d623 | ||
|
|
92d5857150 | ||
|
|
bb45c249a3 | ||
|
|
8796e0472a | ||
|
|
3bd16ba045 | ||
|
|
4b7ff6f003 | ||
|
|
53449ea5b3 | ||
|
|
3b381c4862 | ||
|
|
ce729263a5 | ||
|
|
67480999c0 | ||
|
|
fb8b9f60ce | ||
|
|
9ed36d4e05 | ||
|
|
e3c01d76c4 | ||
|
|
cb7f96449d | ||
|
|
e4f4dacaf0 | ||
|
|
9fc1fe74ad | ||
|
|
991089d98e | ||
|
|
c7a250da31 | ||
|
|
c5b6bad956 | ||
|
|
c4534ff621 | ||
|
|
b2c299fa82 | ||
|
|
68aa15950a | ||
|
|
4a593a8d7e | ||
|
|
2328ba54be | ||
|
|
e216dfd655 | ||
|
|
86472aba2c | ||
|
|
15ce54edfb | ||
|
|
b7bbb3ee9a | ||
|
|
d3236e79fd | ||
|
|
aa212b2b7e | ||
|
|
a0c51b3c3a | ||
|
|
e50d7724e3 | ||
|
|
0d30e66dda | ||
|
|
5c3df7e452 | ||
|
|
3efbe5b1ef | ||
|
|
52cfba06ea | ||
|
|
afba63603e | ||
|
|
3a25d6a44e | ||
|
|
0688895022 | ||
|
|
ce0e5416e6 | ||
|
|
7918a6801e | ||
|
|
6af631e8df | ||
|
|
625ecaa9b5 | ||
|
|
24fe1b9c15 | ||
|
|
ba36d09c70 | ||
|
|
f57be7187e | ||
|
|
6a00338f79 | ||
|
|
16906210e1 | ||
|
|
f9b4540387 | ||
|
|
9755438d0d | ||
|
|
fe9534ed7d | ||
|
|
134c9ada68 | ||
|
|
a3240452ff | ||
|
|
84beb6647d | ||
|
|
ecf7e2d909 | ||
|
|
fcfcf4bbf3 | ||
|
|
c62b6d77b7 | ||
|
|
7e51b9686f | ||
|
|
542ec4cac4 | ||
|
|
64b25d9ec0 | ||
|
|
f91b6fbdf0 | ||
|
|
41445a506e | ||
|
|
798db9d019 | ||
|
|
2e860c32ab | ||
|
|
0e1faed6e5 | ||
|
|
f5de3be977 | ||
|
|
4b0a30eb66 | ||
|
|
7710467571 | ||
|
|
1687794b81 | ||
|
|
158541f05c | ||
|
|
eb28899cd0 | ||
|
|
432046225a | ||
|
|
bf958ce6c1 | ||
|
|
ea8ee4e67d | ||
|
|
edfdbbdc90 | ||
|
|
1b8bfef441 | ||
|
|
514074de8b | ||
|
|
e7aab5c67c | ||
|
|
40484e875e | ||
|
|
fc215ceb63 | ||
|
|
2701c135db | ||
|
|
3a8df3e673 | ||
|
|
fa95b269a5 | ||
|
|
0e9f8c4726 | ||
|
|
a8d4cbd5c1 | ||
|
|
f68a2fc387 | ||
|
|
ede4e8109e | ||
|
|
663df6bdfd | ||
|
|
d97bdd9fd0 | ||
|
|
a806a218bf | ||
|
|
903633f422 | ||
|
|
3c774b02e5 | ||
|
|
4aae48b0a1 | ||
|
|
a8b790a5db | ||
|
|
6a6ceb6875 | ||
|
|
37503dd3e8 | ||
|
|
3f615c8de6 | ||
|
|
2ef5f2eb52 | ||
|
|
f267f6f756 | ||
|
|
538db53887 | ||
|
|
21349abed8 | ||
|
|
d2fb2bb2ca | ||
|
|
0b832fb9de | ||
|
|
179b9ba2cb | ||
|
|
9150e9fb38 | ||
|
|
4716fcef94 | ||
|
|
2b7ee85e30 | ||
|
|
2b8888350b | ||
|
|
4dfe34eedc | ||
|
|
430a71288f | ||
|
|
350509d5d1 | ||
|
|
5403fd849c | ||
|
|
fa87d2e225 | ||
|
|
28a13e98a6 | ||
|
|
b369a30544 | ||
|
|
318ed4e6e1 | ||
|
|
c6a64e8d93 | ||
|
|
efd6937dfa | ||
|
|
36bf1fe3f6 | ||
|
|
4da0e9ac64 | ||
|
|
436766f002 | ||
|
|
d4f2507288 | ||
|
|
28fd27476f | ||
|
|
619b849ce7 | ||
|
|
f1eeb1df8c | ||
|
|
cecc080931 | ||
|
|
11e6a325a2 | ||
|
|
eed8e08145 | ||
|
|
36bec9c295 | ||
|
|
c8988f5a55 | ||
|
|
86c18fb3ae | ||
|
|
2ba4381c39 | ||
|
|
185a0193cc | ||
|
|
1b00334281 | ||
|
|
0eb2f5bf52 | ||
|
|
78d1aa46a1 | ||
|
|
26403a1599 | ||
|
|
a1f112470e | ||
|
|
2ab7a37d85 | ||
|
|
4b933ed2ef | ||
|
|
0d1c12115d | ||
|
|
3bced80b23 | ||
|
|
f46f3e939e | ||
|
|
9d8e836fdd | ||
|
|
132d34db5c | ||
|
|
16edb7b35a | ||
|
|
044478a044 | ||
|
|
f6ea48d666 | ||
|
|
6ed3ed1617 | ||
|
|
9c9c401e66 | ||
|
|
72031edfbe | ||
|
|
41456aa2ab | ||
|
|
a70f29381a | ||
|
|
3b0ad73732 | ||
|
|
8c302648bb | ||
|
|
01b888c341 | ||
|
|
f8e87a8b6c | ||
|
|
01af83946c | ||
|
|
386a9ad0b0 | ||
|
|
c6c20d8f3c | ||
|
|
40b072711e | ||
|
|
2469964a44 | ||
|
|
40e0924768 | ||
|
|
e016440fb3 | ||
|
|
a37aaa5585 | ||
|
|
1ee551d53e | ||
|
|
2341eed796 | ||
|
|
49cfd79505 | ||
|
|
9ac0d1a1ce | ||
|
|
6046dfabe9 | ||
|
|
c2269bf777 | ||
|
|
b81a958021 | ||
|
|
9e37a5643c | ||
|
|
50a213c2e1 | ||
|
|
c6e9fbd501 | ||
|
|
720b5aa7c1 | ||
|
|
7be7640d3f | ||
|
|
e4d97278ee | ||
|
|
793ad82d42 | ||
|
|
2b4d846f0e | ||
|
|
379eadab4f | ||
|
|
53a41fcffe | ||
|
|
5e597e1f09 | ||
|
|
e1b260d0ec | ||
|
|
480b05063d | ||
|
|
8477f7b472 | ||
|
|
91ae4eab06 | ||
|
|
7b86681cb2 | ||
|
|
1de815441a | ||
|
|
44a36025b5 | ||
|
|
00017ab653 | ||
|
|
411adc861e | ||
|
|
c5b3939ddb | ||
|
|
a9e3ea56ec | ||
|
|
6a0f17a983 | ||
|
|
28e6f457b1 | ||
|
|
fdf46054e2 | ||
|
|
47a3ee1ff1 | ||
|
|
a0b7b4b5c4 | ||
|
|
b9bc0a4047 | ||
|
|
e57abf92f3 | ||
|
|
b56a7bc139 | ||
|
|
c401780c15 | ||
|
|
df96a1daac | ||
|
|
0a2200c2c8 | ||
|
|
61b8d04418 | ||
|
|
fd7cc83537 | ||
|
|
ae5f72cf4f | ||
|
|
3e65b6f3a6 | ||
|
|
a4a53d5299 | ||
|
|
4223cac7a5 | ||
|
|
0073a09da6 | ||
|
|
c4cf0f12c9 | ||
|
|
68635be8a2 | ||
|
|
776df7505e | ||
|
|
91c10b3ac6 | ||
|
|
82ace72527 | ||
|
|
585b8ece58 | ||
|
|
3400f5641e | ||
|
|
5068648960 | ||
|
|
1bf5064039 |
8
.gitattributes
vendored
8
.gitattributes
vendored
@@ -2,6 +2,14 @@
|
|||||||
# ensures this even if the user has not set core.autocrlf.
|
# ensures this even if the user has not set core.autocrlf.
|
||||||
* text=auto
|
* text=auto
|
||||||
|
|
||||||
|
# Checkout JavaScript files with LF line endings
|
||||||
|
# to prevent `prettier` from reporting errors on Windows.
|
||||||
|
*.js eol=lf
|
||||||
|
*.jsx eol=lf
|
||||||
|
*.ts eol=lf
|
||||||
|
*.tsx eol=lf
|
||||||
|
*.json eol=lf
|
||||||
|
|
||||||
# This directory contains email messages verbatim, and changing CRLF to
|
# This directory contains email messages verbatim, and changing CRLF to
|
||||||
# LF will corrupt them.
|
# LF will corrupt them.
|
||||||
test-data/** text=false
|
test-data/** text=false
|
||||||
|
|||||||
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -5,5 +5,5 @@ updates:
|
|||||||
schedule:
|
schedule:
|
||||||
interval: "monthly"
|
interval: "monthly"
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: "cargo"
|
prefix: "chore(cargo)"
|
||||||
open-pull-requests-limit: 50
|
open-pull-requests-limit: 50
|
||||||
|
|||||||
26
.github/mergeable.yml
vendored
26
.github/mergeable.yml
vendored
@@ -1,26 +0,0 @@
|
|||||||
version: 2
|
|
||||||
mergeable:
|
|
||||||
- when: pull_request.*
|
|
||||||
name: "Changelog check"
|
|
||||||
validate:
|
|
||||||
- do: or
|
|
||||||
validate:
|
|
||||||
- do: description
|
|
||||||
must_include:
|
|
||||||
regex: "#skip-changelog"
|
|
||||||
- do: and
|
|
||||||
validate:
|
|
||||||
- do: dependent
|
|
||||||
changed:
|
|
||||||
file: "src/**"
|
|
||||||
required: ["CHANGELOG.md"]
|
|
||||||
- do: dependent
|
|
||||||
changed:
|
|
||||||
file: "deltachat-ffi/src/**"
|
|
||||||
required: ["CHANGELOG.md"]
|
|
||||||
fail:
|
|
||||||
- do: checks
|
|
||||||
status: "action_required"
|
|
||||||
payload:
|
|
||||||
title: Changelog might need an update
|
|
||||||
summary: "Check if CHANGELOG.md needs an update or add #skip-changelog to the PR description."
|
|
||||||
215
.github/workflows/ci.yml
vendored
215
.github/workflows/ci.yml
vendored
@@ -1,3 +1,7 @@
|
|||||||
|
# GitHub Actions workflow to
|
||||||
|
# lint Rust and Python code
|
||||||
|
# and run Rust tests, Python tests and async Python tests.
|
||||||
|
|
||||||
name: Rust CI
|
name: Rust CI
|
||||||
|
|
||||||
# Cancel previously started workflow runs
|
# Cancel previously started workflow runs
|
||||||
@@ -11,16 +15,17 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- stable
|
||||||
|
|
||||||
env:
|
env:
|
||||||
RUSTFLAGS: -Dwarnings
|
RUSTFLAGS: -Dwarnings
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint_rust:
|
||||||
name: Rustfmt and Clippy
|
name: Lint Rust
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
RUSTUP_TOOLCHAIN: 1.68.0
|
RUSTUP_TOOLCHAIN: 1.72.0
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Install rustfmt and clippy
|
- name: Install rustfmt and clippy
|
||||||
@@ -31,6 +36,12 @@ jobs:
|
|||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
- name: Run clippy
|
- name: Run clippy
|
||||||
run: scripts/clippy.sh
|
run: scripts/clippy.sh
|
||||||
|
- name: Check
|
||||||
|
run: cargo check --workspace --all-targets --all-features
|
||||||
|
|
||||||
|
# Check with musl libc target which is used for `deltachat-rpc-server` releases.
|
||||||
|
- name: Check musl
|
||||||
|
run: scripts/zig-musl-check.sh
|
||||||
|
|
||||||
cargo_deny:
|
cargo_deny:
|
||||||
name: cargo deny
|
name: cargo deny
|
||||||
@@ -64,34 +75,28 @@ jobs:
|
|||||||
- name: Rustdoc
|
- name: Rustdoc
|
||||||
run: cargo doc --document-private-items --no-deps
|
run: cargo doc --document-private-items --no-deps
|
||||||
|
|
||||||
build_and_test:
|
rust_tests:
|
||||||
name: Build and test
|
name: Rust tests
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
# Currently used Rust version.
|
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
rust: 1.68.0
|
rust: 1.68.2
|
||||||
python: 3.9
|
|
||||||
- os: windows-latest
|
- os: windows-latest
|
||||||
rust: 1.68.0
|
rust: 1.68.2
|
||||||
python: false # Python bindings compilation on Windows is not supported.
|
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
rust: 1.68.0
|
rust: 1.68.2
|
||||||
python: 3.9
|
|
||||||
|
|
||||||
# Minimum Supported Rust Version = 1.64.0
|
# Minimum Supported Rust Version = 1.65.0
|
||||||
#
|
#
|
||||||
# Minimum Supported Python Version = 3.7
|
# Minimum Supported Python Version = 3.7
|
||||||
# This is the minimum version for which manylinux Python wheels are
|
# This is the minimum version for which manylinux Python wheels are
|
||||||
# built.
|
# built.
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
rust: 1.64.0
|
rust: 1.65.0
|
||||||
python: 3.7
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@master
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install Rust ${{ matrix.rust }}
|
- name: Install Rust ${{ matrix.rust }}
|
||||||
run: rustup toolchain install --profile minimal ${{ matrix.rust }}
|
run: rustup toolchain install --profile minimal ${{ matrix.rust }}
|
||||||
@@ -100,64 +105,176 @@ jobs:
|
|||||||
- name: Cache rust cargo artifacts
|
- name: Cache rust cargo artifacts
|
||||||
uses: swatinem/rust-cache@v2
|
uses: swatinem/rust-cache@v2
|
||||||
|
|
||||||
- name: Check
|
|
||||||
run: cargo check --workspace --bins --examples --tests --benches
|
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
run: cargo test --workspace
|
run: cargo test --workspace
|
||||||
|
|
||||||
- name: Test cargo vendor
|
- name: Test cargo vendor
|
||||||
run: cargo vendor
|
run: cargo vendor
|
||||||
|
|
||||||
|
c_library:
|
||||||
|
name: Build C library
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, macos-latest]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Cache rust cargo artifacts
|
||||||
|
uses: swatinem/rust-cache@v2
|
||||||
|
|
||||||
|
- name: Build C library
|
||||||
|
run: cargo build -p deltachat_ffi --features jsonrpc
|
||||||
|
|
||||||
|
- name: Upload C library
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.os }}-libdeltachat.a
|
||||||
|
path: target/debug/libdeltachat.a
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
rpc_server:
|
||||||
|
name: Build deltachat-rpc-server
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, macos-latest]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Cache rust cargo artifacts
|
||||||
|
uses: swatinem/rust-cache@v2
|
||||||
|
|
||||||
|
- name: Build deltachat-rpc-server
|
||||||
|
run: cargo build -p deltachat-rpc-server
|
||||||
|
|
||||||
|
- name: Upload deltachat-rpc-server
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.os }}-deltachat-rpc-server
|
||||||
|
path: target/debug/deltachat-rpc-server
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
python_lint:
|
||||||
|
name: Python lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install tox
|
||||||
|
run: pip install tox
|
||||||
|
|
||||||
|
- name: Lint Python bindings
|
||||||
|
working-directory: python
|
||||||
|
run: tox -e lint
|
||||||
|
|
||||||
|
- name: Lint deltachat-rpc-client
|
||||||
|
working-directory: deltachat-rpc-client
|
||||||
|
run: tox -e lint
|
||||||
|
|
||||||
|
python_tests:
|
||||||
|
name: Python tests
|
||||||
|
needs: ["c_library", "python_lint"]
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
# Currently used Rust version.
|
||||||
|
- os: ubuntu-latest
|
||||||
|
python: 3.11
|
||||||
|
- os: macos-latest
|
||||||
|
python: 3.11
|
||||||
|
|
||||||
|
# PyPy tests
|
||||||
|
- os: ubuntu-latest
|
||||||
|
python: pypy3.9
|
||||||
|
- os: macos-latest
|
||||||
|
python: pypy3.9
|
||||||
|
|
||||||
|
# Minimum Supported Python Version = 3.7
|
||||||
|
# This is the minimum version for which manylinux Python wheels are
|
||||||
|
# built. Test it with minimum supported Rust version.
|
||||||
|
- os: ubuntu-latest
|
||||||
|
python: 3.7
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Download libdeltachat.a
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.os }}-libdeltachat.a
|
||||||
|
path: target/debug
|
||||||
|
|
||||||
- name: Install python
|
- name: Install python
|
||||||
if: ${{ matrix.python }}
|
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python }}
|
python-version: ${{ matrix.python }}
|
||||||
|
|
||||||
- name: Install tox
|
- name: Install tox
|
||||||
if: ${{ matrix.python }}
|
|
||||||
run: pip install tox
|
run: pip install tox
|
||||||
|
|
||||||
- name: Build C library
|
|
||||||
if: ${{ matrix.python }}
|
|
||||||
run: cargo build -p deltachat_ffi --features jsonrpc
|
|
||||||
|
|
||||||
- name: Run python tests
|
- name: Run python tests
|
||||||
if: ${{ matrix.python }}
|
|
||||||
env:
|
env:
|
||||||
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
||||||
DCC_RS_TARGET: debug
|
DCC_RS_TARGET: debug
|
||||||
DCC_RS_DEV: ${{ github.workspace }}
|
DCC_RS_DEV: ${{ github.workspace }}
|
||||||
working-directory: python
|
working-directory: python
|
||||||
run: tox -e lint,mypy,doc,py3
|
run: tox -e mypy,doc,py
|
||||||
|
|
||||||
- name: Build deltachat-rpc-server
|
aysnc_python_tests:
|
||||||
if: ${{ matrix.python }}
|
name: Async Python tests
|
||||||
run: cargo build -p deltachat-rpc-server
|
needs: ["python_lint", "rpc_server"]
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
python: 3.11
|
||||||
|
- os: macos-latest
|
||||||
|
python: 3.11
|
||||||
|
|
||||||
|
# PyPy tests
|
||||||
|
- os: ubuntu-latest
|
||||||
|
python: pypy3.9
|
||||||
|
- os: macos-latest
|
||||||
|
python: pypy3.9
|
||||||
|
|
||||||
|
# Minimum Supported Python Version = 3.8
|
||||||
|
#
|
||||||
|
# Python 3.7 has at least one known bug related to starting subprocesses
|
||||||
|
# in asyncio programs: <https://bugs.python.org/issue35621>
|
||||||
|
- os: ubuntu-latest
|
||||||
|
python: 3.8
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python }}
|
||||||
|
|
||||||
|
- name: Install tox
|
||||||
|
run: pip install tox
|
||||||
|
|
||||||
|
- name: Download deltachat-rpc-server
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.os }}-deltachat-rpc-server
|
||||||
|
path: target/debug
|
||||||
|
|
||||||
|
- name: Make deltachat-rpc-server executable
|
||||||
|
run: chmod +x target/debug/deltachat-rpc-server
|
||||||
|
|
||||||
- name: Add deltachat-rpc-server to path
|
- name: Add deltachat-rpc-server to path
|
||||||
if: ${{ matrix.python }}
|
|
||||||
run: echo ${{ github.workspace }}/target/debug >> $GITHUB_PATH
|
run: echo ${{ github.workspace }}/target/debug >> $GITHUB_PATH
|
||||||
|
|
||||||
- name: Run deltachat-rpc-client tests
|
- name: Run deltachat-rpc-client tests
|
||||||
if: ${{ matrix.python }}
|
|
||||||
env:
|
env:
|
||||||
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
||||||
working-directory: deltachat-rpc-client
|
working-directory: deltachat-rpc-client
|
||||||
run: tox -e py3,lint
|
run: tox -e py
|
||||||
|
|
||||||
- name: Install pypy
|
|
||||||
if: ${{ matrix.python }}
|
|
||||||
uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: "pypy${{ matrix.python }}"
|
|
||||||
|
|
||||||
- name: Run pypy tests
|
|
||||||
if: ${{ matrix.python }}
|
|
||||||
env:
|
|
||||||
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
|
||||||
DCC_RS_TARGET: debug
|
|
||||||
DCC_RS_DEV: ${{ github.workspace }}
|
|
||||||
working-directory: python
|
|
||||||
run: tox -e pypy3
|
|
||||||
|
|||||||
40
.github/workflows/deltachat-rpc-server.yml
vendored
40
.github/workflows/deltachat-rpc-server.yml
vendored
@@ -1,4 +1,10 @@
|
|||||||
# Manually triggered action to build deltachat-rpc-server binaries.
|
# GitHub Actions workflow
|
||||||
|
# to build `deltachat-rpc-server` binaries
|
||||||
|
# and upload them to the release.
|
||||||
|
#
|
||||||
|
# The workflow is automatically triggered on releases.
|
||||||
|
# It can also be triggered manually
|
||||||
|
# to produce binary artifacts for testing.
|
||||||
|
|
||||||
name: Build deltachat-rpc-server binaries
|
name: Build deltachat-rpc-server binaries
|
||||||
|
|
||||||
@@ -15,7 +21,7 @@ jobs:
|
|||||||
# Build a version statically linked against musl libc
|
# Build a version statically linked against musl libc
|
||||||
# to avoid problems with glibc version incompatibility.
|
# to avoid problems with glibc version incompatibility.
|
||||||
build_linux:
|
build_linux:
|
||||||
name: Cross-compile deltachat-rpc-server for x86_64, aarch64 and armv7 Linux
|
name: Cross-compile deltachat-rpc-server for x86_64, i686, aarch64 and armv7 Linux
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -30,6 +36,13 @@ jobs:
|
|||||||
path: target/x86_64-unknown-linux-musl/release/deltachat-rpc-server
|
path: target/x86_64-unknown-linux-musl/release/deltachat-rpc-server
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload i686 binary
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: deltachat-rpc-server-i686
|
||||||
|
path: target/i686-unknown-linux-musl/release/deltachat-rpc-server
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload aarch64 binary
|
- name: Upload aarch64 binary
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
@@ -77,9 +90,28 @@ jobs:
|
|||||||
path: target/${{ matrix.target}}/release/${{ matrix.path }}
|
path: target/${{ matrix.target}}/release/${{ matrix.path }}
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
|
build_macos:
|
||||||
|
name: Build deltachat-rpc-server for macOS
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup rust target
|
||||||
|
run: rustup target add x86_64-apple-darwin
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cargo build --release --package deltachat-rpc-server --target x86_64-apple-darwin --features vendored
|
||||||
|
|
||||||
|
- name: Upload binary
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: deltachat-rpc-server-x86_64-macos
|
||||||
|
path: target/x86_64-apple-darwin/release/deltachat-rpc-server
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
name: Upload binaries to the release
|
name: Upload binaries to the release
|
||||||
needs: ["build_linux", "build_windows"]
|
needs: ["build_linux", "build_windows", "build_macos"]
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-latest"
|
||||||
@@ -90,7 +122,7 @@ jobs:
|
|||||||
- name: Compose dist/ directory
|
- name: Compose dist/ directory
|
||||||
run: |
|
run: |
|
||||||
mkdir dist
|
mkdir dist
|
||||||
for x in x86_64 aarch64 armv7 win32.exe win64.exe; do
|
for x in x86_64 i686 aarch64 armv7 win32.exe win64.exe x86_64-macos; do
|
||||||
mv "deltachat-rpc-server-$x"/* "dist/deltachat-rpc-server-$x"
|
mv "deltachat-rpc-server-$x"/* "dist/deltachat-rpc-server-$x"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
3
.github/workflows/dependabot.yml
vendored
3
.github/workflows/dependabot.yml
vendored
@@ -1,3 +1,6 @@
|
|||||||
|
# GitHub Actions workflow
|
||||||
|
# to automatically approve PRs made by Dependabot.
|
||||||
|
|
||||||
name: Dependabot auto-approve
|
name: Dependabot auto-approve
|
||||||
on: pull_request
|
on: pull_request
|
||||||
|
|
||||||
|
|||||||
@@ -38,13 +38,12 @@ jobs:
|
|||||||
node --version
|
node --version
|
||||||
echo $DELTACHAT_JSONRPC_TAR_GZ
|
echo $DELTACHAT_JSONRPC_TAR_GZ
|
||||||
- name: Install dependencies without running scripts
|
- name: Install dependencies without running scripts
|
||||||
run: |
|
working-directory: deltachat-jsonrpc/typescript
|
||||||
cd deltachat-jsonrpc/typescript
|
run: npm install --ignore-scripts
|
||||||
npm install --ignore-scripts
|
|
||||||
- name: Package
|
- name: Package
|
||||||
shell: bash
|
shell: bash
|
||||||
|
working-directory: deltachat-jsonrpc/typescript
|
||||||
run: |
|
run: |
|
||||||
cd deltachat-jsonrpc/typescript
|
|
||||||
npm run build
|
npm run build
|
||||||
npm pack .
|
npm pack .
|
||||||
ls -lah
|
ls -lah
|
||||||
|
|||||||
25
.github/workflows/jsonrpc.yml
vendored
25
.github/workflows/jsonrpc.yml
vendored
@@ -22,24 +22,19 @@ jobs:
|
|||||||
- name: Add Rust cache
|
- name: Add Rust cache
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
- name: npm install
|
- name: npm install
|
||||||
run: |
|
working-directory: deltachat-jsonrpc/typescript
|
||||||
cd deltachat-jsonrpc/typescript
|
run: npm install
|
||||||
npm install
|
|
||||||
- name: Build TypeScript, run Rust tests, generate bindings
|
- name: Build TypeScript, run Rust tests, generate bindings
|
||||||
run: |
|
working-directory: deltachat-jsonrpc/typescript
|
||||||
cd deltachat-jsonrpc/typescript
|
run: npm run build
|
||||||
npm run build
|
|
||||||
- name: Run integration tests
|
- name: Run integration tests
|
||||||
run: |
|
working-directory: deltachat-jsonrpc/typescript
|
||||||
cd deltachat-jsonrpc/typescript
|
run: npm run test
|
||||||
npm run test
|
|
||||||
env:
|
env:
|
||||||
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
||||||
- name: make sure websocket server version still builds
|
- name: make sure websocket server version still builds
|
||||||
run: |
|
working-directory: deltachat-jsonrpc
|
||||||
cd deltachat-jsonrpc
|
run: cargo build --bin deltachat-jsonrpc-server --features webserver
|
||||||
cargo build --bin deltachat-jsonrpc-server --features webserver
|
|
||||||
- name: Run linter
|
- name: Run linter
|
||||||
run: |
|
working-directory: deltachat-jsonrpc/typescript
|
||||||
cd deltachat-jsonrpc/typescript
|
run: npm run prettier:check
|
||||||
npm run prettier:check
|
|
||||||
|
|||||||
31
.github/workflows/node-delete-preview.yml
vendored
31
.github/workflows/node-delete-preview.yml
vendored
@@ -1,31 +0,0 @@
|
|||||||
# documentation: https://github.com/deltachat/sysadmin/tree/master/download.delta.chat
|
|
||||||
name: Delete node PR previews
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [closed]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
delete:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Get Pull Request ID
|
|
||||||
id: getid
|
|
||||||
run: |
|
|
||||||
export PULLREQUEST_ID=$(jq .number < $GITHUB_EVENT_PATH)
|
|
||||||
echo "prid=$PULLREQUEST_ID" >> $GITHUB_OUTPUT
|
|
||||||
- name: Renaming
|
|
||||||
run: |
|
|
||||||
# create empty file to copy it over the outdated deliverable on download.delta.chat
|
|
||||||
echo "This preview build is outdated and has been removed." > empty
|
|
||||||
cp empty deltachat-node-${{ steps.getid.outputs.prid }}.tar.gz
|
|
||||||
- name: Replace builds with dummy files
|
|
||||||
uses: horochx/deploy-via-scp@v1.0.1
|
|
||||||
with:
|
|
||||||
user: ${{ secrets.USERNAME }}
|
|
||||||
key: ${{ secrets.SSH_KEY }}
|
|
||||||
host: "download.delta.chat"
|
|
||||||
port: 22
|
|
||||||
local: "deltachat-node-${{ steps.getid.outputs.prid }}.tar.gz"
|
|
||||||
remote: "/var/www/html/download/node/preview/"
|
|
||||||
7
.github/workflows/node-docs.yml
vendored
7
.github/workflows/node-docs.yml
vendored
@@ -1,3 +1,8 @@
|
|||||||
|
# GitHub Actions workflow to build
|
||||||
|
# Node.js bindings documentation
|
||||||
|
# and upload it to the web server.
|
||||||
|
# Built documentation is available at <https://js.delta.chat/>
|
||||||
|
|
||||||
name: Generate & upload node.js documentation
|
name: Generate & upload node.js documentation
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -17,8 +22,8 @@ jobs:
|
|||||||
node-version: 16.x
|
node-version: 16.x
|
||||||
|
|
||||||
- name: npm install and generate documentation
|
- name: npm install and generate documentation
|
||||||
|
working-directory: node
|
||||||
run: |
|
run: |
|
||||||
cd node
|
|
||||||
npm i --ignore-scripts
|
npm i --ignore-scripts
|
||||||
npx typedoc
|
npx typedoc
|
||||||
mv docs js
|
mv docs js
|
||||||
|
|||||||
92
.github/workflows/node-package.yml
vendored
92
.github/workflows/node-package.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
os: [macos-latest, windows-latest]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@@ -46,13 +46,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Install dependencies & build
|
- name: Install dependencies & build
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
run: |
|
working-directory: node
|
||||||
cd node
|
run: npm install --verbose
|
||||||
npm install --verbose
|
|
||||||
|
|
||||||
- name: Build Prebuild
|
- name: Build Prebuild
|
||||||
|
working-directory: node
|
||||||
run: |
|
run: |
|
||||||
cd node
|
|
||||||
npm run prebuildify
|
npm run prebuildify
|
||||||
tar -zcvf "${{ matrix.os }}.tar.gz" -C prebuilds .
|
tar -zcvf "${{ matrix.os }}.tar.gz" -C prebuilds .
|
||||||
|
|
||||||
@@ -62,10 +61,81 @@ jobs:
|
|||||||
name: ${{ matrix.os }}
|
name: ${{ matrix.os }}
|
||||||
path: node/${{ matrix.os }}.tar.gz
|
path: node/${{ matrix.os }}.tar.gz
|
||||||
|
|
||||||
|
prebuild-linux:
|
||||||
|
name: Prebuild Linux
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
# Build Linux prebuilds inside a container with old glibc for backwards compatibility.
|
||||||
|
# Debian 10 contained glibc 2.28 at the time of the writing (2023-06-04): https://packages.debian.org/buster/libc6
|
||||||
|
# Ubuntu 18.04 is at the End of Standard Support since June 2023, but it contains glibc 2.27,
|
||||||
|
# so we are using it to support Ubuntu 18.04 setups that are still not upgraded.
|
||||||
|
container: ubuntu:18.04
|
||||||
|
steps:
|
||||||
|
# Working directory is owned by 1001:1001 by default.
|
||||||
|
# Change it to our user.
|
||||||
|
- name: Change working directory owner
|
||||||
|
run: chown root:root .
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: "16"
|
||||||
|
- run: apt-get update
|
||||||
|
|
||||||
|
# Python is needed for node-gyp
|
||||||
|
- name: Install curl, python and compilers
|
||||||
|
run: apt-get install -y curl build-essential python3
|
||||||
|
- name: Install Rust
|
||||||
|
run: |
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||||
|
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
|
||||||
|
- name: System info
|
||||||
|
run: |
|
||||||
|
rustc -vV
|
||||||
|
rustup -vV
|
||||||
|
cargo -vV
|
||||||
|
npm --version
|
||||||
|
node --version
|
||||||
|
|
||||||
|
- name: Cache node modules
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
${{ env.APPDATA }}/npm-cache
|
||||||
|
~/.npm
|
||||||
|
key: ${{ matrix.os }}-node-${{ hashFiles('**/package.json') }}
|
||||||
|
|
||||||
|
- name: Cache cargo index
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry/
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ matrix.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}-2
|
||||||
|
|
||||||
|
- name: Install dependencies & build
|
||||||
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
|
working-directory: node
|
||||||
|
run: npm install --verbose
|
||||||
|
|
||||||
|
- name: Build Prebuild
|
||||||
|
working-directory: node
|
||||||
|
run: |
|
||||||
|
npm run prebuildify
|
||||||
|
tar -zcvf "linux.tar.gz" -C prebuilds .
|
||||||
|
|
||||||
|
- name: Upload Prebuild
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: linux
|
||||||
|
path: node/linux.tar.gz
|
||||||
|
|
||||||
pack-module:
|
pack-module:
|
||||||
needs: prebuild
|
needs: [prebuild, prebuild-linux]
|
||||||
name: Package deltachat-node and upload to download.delta.chat
|
name: Package deltachat-node and upload to download.delta.chat
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Install tree
|
- name: Install tree
|
||||||
run: sudo apt install tree
|
run: sudo apt install tree
|
||||||
@@ -96,10 +166,10 @@ jobs:
|
|||||||
npm --version
|
npm --version
|
||||||
node --version
|
node --version
|
||||||
echo $DELTACHAT_NODE_TAR_GZ
|
echo $DELTACHAT_NODE_TAR_GZ
|
||||||
- name: Download Ubuntu prebuild
|
- name: Download Linux prebuild
|
||||||
uses: actions/download-artifact@v1
|
uses: actions/download-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: ubuntu-20.04
|
name: linux
|
||||||
- name: Download macOS prebuild
|
- name: Download macOS prebuild
|
||||||
uses: actions/download-artifact@v1
|
uses: actions/download-artifact@v1
|
||||||
with:
|
with:
|
||||||
@@ -111,11 +181,11 @@ jobs:
|
|||||||
- shell: bash
|
- shell: bash
|
||||||
run: |
|
run: |
|
||||||
mkdir node/prebuilds
|
mkdir node/prebuilds
|
||||||
tar -xvzf ubuntu-20.04/ubuntu-20.04.tar.gz -C node/prebuilds
|
tar -xvzf linux/linux.tar.gz -C node/prebuilds
|
||||||
tar -xvzf macos-latest/macos-latest.tar.gz -C node/prebuilds
|
tar -xvzf macos-latest/macos-latest.tar.gz -C node/prebuilds
|
||||||
tar -xvzf windows-latest/windows-latest.tar.gz -C node/prebuilds
|
tar -xvzf windows-latest/windows-latest.tar.gz -C node/prebuilds
|
||||||
tree node/prebuilds
|
tree node/prebuilds
|
||||||
rm -rf ubuntu-20.04 macos-latest windows-latest
|
rm -rf linux macos-latest windows-latest
|
||||||
- name: Install dependencies without running scripts
|
- name: Install dependencies without running scripts
|
||||||
run: |
|
run: |
|
||||||
npm install --ignore-scripts
|
npm install --ignore-scripts
|
||||||
|
|||||||
23
.github/workflows/node-tests.yml
vendored
23
.github/workflows/node-tests.yml
vendored
@@ -1,3 +1,6 @@
|
|||||||
|
# GitHub Actions workflow
|
||||||
|
# to test Node.js bindings.
|
||||||
|
|
||||||
name: "node.js tests"
|
name: "node.js tests"
|
||||||
|
|
||||||
# Cancel previously started workflow runs
|
# Cancel previously started workflow runs
|
||||||
@@ -52,25 +55,13 @@ jobs:
|
|||||||
|
|
||||||
- name: Install dependencies & build
|
- name: Install dependencies & build
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
run: |
|
working-directory: node
|
||||||
cd node
|
run: npm install --verbose
|
||||||
npm install --verbose
|
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
if: runner.os != 'Windows'
|
working-directory: node
|
||||||
run: |
|
run: npm run test
|
||||||
cd node
|
|
||||||
npm run test
|
|
||||||
env:
|
|
||||||
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
|
||||||
NODE_OPTIONS: "--force-node-api-uncaught-exceptions-policy=true"
|
|
||||||
- name: Run tests on Windows, except lint
|
|
||||||
timeout-minutes: 10
|
|
||||||
if: runner.os == 'Windows'
|
|
||||||
run: |
|
|
||||||
cd node
|
|
||||||
npm run test:mocha
|
|
||||||
env:
|
env:
|
||||||
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
DCC_NEW_TMP_EMAIL: ${{ secrets.DCC_NEW_TMP_EMAIL }}
|
||||||
NODE_OPTIONS: "--force-node-api-uncaught-exceptions-policy=true"
|
NODE_OPTIONS: "--force-node-api-uncaught-exceptions-policy=true"
|
||||||
|
|||||||
3
.github/workflows/repl.yml
vendored
3
.github/workflows/repl.yml
vendored
@@ -1,4 +1,5 @@
|
|||||||
# Manually triggered action to build a Windows repl.exe which users can
|
# Manually triggered GitHub Actions workflow
|
||||||
|
# to build a Windows repl.exe which users can
|
||||||
# download to debug complex bugs.
|
# download to debug complex bugs.
|
||||||
|
|
||||||
name: Build Windows REPL .exe
|
name: Build Windows REPL .exe
|
||||||
|
|||||||
4
.github/workflows/upload-ffi-docs.yml
vendored
4
.github/workflows/upload-ffi-docs.yml
vendored
@@ -1,3 +1,7 @@
|
|||||||
|
# GitHub Actions workflow
|
||||||
|
# to build `deltachat_fii` crate documentation
|
||||||
|
# and upload it to <https://cffi.delta.chat/>
|
||||||
|
|
||||||
name: Build & Deploy Documentation on cffi.delta.chat
|
name: Build & Deploy Documentation on cffi.delta.chat
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
|||||||
461
CHANGELOG.md
461
CHANGELOG.md
@@ -1,5 +1,444 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.122.0] - 2023-09-12
|
||||||
|
|
||||||
|
### API-Changes
|
||||||
|
|
||||||
|
- jsonrpc: Return only chat IDs for similar chats.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Reopen all connections on database passpharse change.
|
||||||
|
- Do not block new group chats if 1:1 chat is blocked.
|
||||||
|
- Improve group membership consistency algorithm ([#3782](https://github.com/deltachat/deltachat-core-rust/pull/3782))([#4624](https://github.com/deltachat/deltachat-core-rust/pull/4624)).
|
||||||
|
- Forbid membership changes from possible non-members ([#3782](https://github.com/deltachat/deltachat-core-rust/pull/3782)).
|
||||||
|
- `ChatId::parent_query()`: Don't filter out OutPending and OutFailed messages.
|
||||||
|
|
||||||
|
### Build system
|
||||||
|
|
||||||
|
- Update to OpenSSL 3.0.
|
||||||
|
- Bump webpki from 0.22.0 to 0.22.1.
|
||||||
|
- python: Add link to Mastodon into projects.urls.
|
||||||
|
|
||||||
|
### Features / Changes
|
||||||
|
|
||||||
|
- Add RSA-4096 key generation support.
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- pgp: Add constants for encryption algorithm and hash.
|
||||||
|
|
||||||
|
## [1.121.0] - 2023-09-06
|
||||||
|
|
||||||
|
### API-Changes
|
||||||
|
|
||||||
|
- Add `dc_context_change_passphrase()`.
|
||||||
|
- Add `Message.set_file_from_bytes()` API.
|
||||||
|
- Add experimental API to get similar chats.
|
||||||
|
|
||||||
|
### Build system
|
||||||
|
|
||||||
|
- Build node packages on Ubuntu 18.04 instead of Debian 10.
|
||||||
|
This reduces the requirement for glibc version from 2.28 to 2.27.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Allow membership changes by a MUA if we're not in the group ([#4624](https://github.com/deltachat/deltachat-core-rust/pull/4624)).
|
||||||
|
- Save mime headers for messages not signed with a known key ([#4557](https://github.com/deltachat/deltachat-core-rust/pull/4557)).
|
||||||
|
- Return from `dc_get_chatlist(DC_GCL_FOR_FORWARDING)` only chats where we can send ([#4616](https://github.com/deltachat/deltachat-core-rust/pull/4616)).
|
||||||
|
- Do not allow dots at the end of email addresses.
|
||||||
|
- deltachat-rpc-client: Remove `aiodns` optional dependency from required dependencies.
|
||||||
|
`aiodns` depends on `pycares` which [fails to install in Termux](https://github.com/saghul/aiodns/issues/98).
|
||||||
|
|
||||||
|
## [1.120.0] - 2023-08-28
|
||||||
|
|
||||||
|
### API-Changes
|
||||||
|
|
||||||
|
- jsonrpc: Add `resend_messages`.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Update async-imap to 0.9.1 to fix memory leak.
|
||||||
|
- Delete messages from SMTP queue only on user demand ([#4579](https://github.com/deltachat/deltachat-core-rust/pull/4579)).
|
||||||
|
- Do not send images without transparency as stickers ([#4611](https://github.com/deltachat/deltachat-core-rust/pull/4611)).
|
||||||
|
- `prepare_msg_blob()`: do not use the image if it has Exif metadata but the image cannot be recoded.
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Hide accounts.rs constants from public API.
|
||||||
|
- Hide pgp module from public API.
|
||||||
|
|
||||||
|
### Build system
|
||||||
|
|
||||||
|
- Update to Zig 0.11.0.
|
||||||
|
- Update to Rust 1.72.0.
|
||||||
|
|
||||||
|
### CI
|
||||||
|
|
||||||
|
- Run on push to stable branch.
|
||||||
|
|
||||||
|
### Miscellaneous Tasks
|
||||||
|
|
||||||
|
- python: Fix lint errors.
|
||||||
|
- python: Fix `ruff` 0.0.286 warnings.
|
||||||
|
- Fix beta clippy warnings.
|
||||||
|
|
||||||
|
## [1.119.1] - 2023-08-06
|
||||||
|
|
||||||
|
Bugfix release attempting to fix the [iOS build error](https://github.com/deltachat/deltachat-core-rust/issues/4610).
|
||||||
|
|
||||||
|
### Features / Changes
|
||||||
|
|
||||||
|
- Guess message viewtype from "application/octet-stream" attachment extension ([#4378](https://github.com/deltachat/deltachat-core-rust/pull/4378)).
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Update `xattr` from 1.0.0 to 1.0.1 to fix UnsupportedPlatformError import.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
- webxdc: Ensure unknown WebXDC update properties do not result in an error.
|
||||||
|
|
||||||
|
## [1.119.0] - 2023-08-03
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- imap: Avoid IMAP move loops when DeltaChat folder is aliased.
|
||||||
|
- imap: Do not resync IMAP after initial configuration.
|
||||||
|
|
||||||
|
- webxdc: Accept WebXDC updates in mailing lists.
|
||||||
|
- webxdc: Base64-encode WebXDC updates to prevent corruption of large unencrypted WebXDC updates.
|
||||||
|
- webxdc: Delete old webxdc status updates during housekeeping.
|
||||||
|
|
||||||
|
- Return valid MsgId from `receive_imf()` when the message is replaced.
|
||||||
|
- Emit MsgsChanged event with correct chat id for replaced messages.
|
||||||
|
|
||||||
|
- deltachat-rpc-server: Update tokio-tar to fix backup import.
|
||||||
|
|
||||||
|
### Features / Changes
|
||||||
|
|
||||||
|
- deltachat-rpc-client: Add `MSG_DELETED` constant.
|
||||||
|
- Make `dc_msg_get_filename()` return the original attachment filename ([#4309](https://github.com/deltachat/deltachat-core-rust/pull/4309)).
|
||||||
|
|
||||||
|
### API-Changes
|
||||||
|
|
||||||
|
- deltachat-rpc-client: Add `Account.{import,export}_backup` methods.
|
||||||
|
- deltachat-jsonrpc: Make `MessageObject.text` non-optional.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Update default value for `show_emails` in `dc_set_config()` documentation.
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Improve IMAP logs.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
- Add basic import/export test for async python.
|
||||||
|
- Add `test_webxdc_download_on_demand`.
|
||||||
|
- Add tests for deletion of webxdc status-updates.
|
||||||
|
|
||||||
|
## [1.118.0] - 2023-07-07
|
||||||
|
|
||||||
|
### API-Changes
|
||||||
|
|
||||||
|
- [**breaking**] Remove `Contact::load_from_db()` in favor of `Contact::get_by_id()`.
|
||||||
|
- Add `Contact::get_by_id_optional()` API.
|
||||||
|
- [**breaking**] Make `Message.text` non-optional.
|
||||||
|
- [**breaking**] Replace `message::get_msg_info()` with `MsgId.get_info()`.
|
||||||
|
- Move `handle_mdn` and `handle_ndn` to mimeparser and make them private.
|
||||||
|
Previously `handle_mdn` was erroneously exposed in the public API.
|
||||||
|
- python: flatten the API of `deltachat` module.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Use different member added/removal messages locally and on the network.
|
||||||
|
- Update tokio to 1.29.1 to fix core panic after sending 29 offline messages ([#4414](https://github.com/deltachat/deltachat-core-rust/issues/4414)).
|
||||||
|
- Make SVG avatar image work on more platforms (use `xlink:href`).
|
||||||
|
- Preserve indentation when converting plaintext to HTML.
|
||||||
|
- Do not run simplify() on dehtml() output.
|
||||||
|
- Rewrite member added/removed messages even if the change is not allowed PR ([#4529](https://github.com/deltachat/deltachat-core-rust/pull/4529)).
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Document how to regenerate Node.js constants before the release.
|
||||||
|
|
||||||
|
### Build system
|
||||||
|
|
||||||
|
- git-cliff: Do not fail if commit.footers is undefined.
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
- Dependency updates.
|
||||||
|
- Update MPL 2.0 license text.
|
||||||
|
- Add LICENSE file to deltachat-rpc-client.
|
||||||
|
- deltachat-rpc-client: Add Trove classifiers.
|
||||||
|
- python: Change bindings status to production/stable.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
- Add `make-python-testenv.sh` script.
|
||||||
|
|
||||||
|
## [1.117.0] - 2023-06-15
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- New group membership update algorithm.
|
||||||
|
|
||||||
|
New algorithm improves group consistency
|
||||||
|
in cases of missing messages,
|
||||||
|
restored old backups and replies from classic MUAs.
|
||||||
|
|
||||||
|
- Add `DC_EVENT_MSG_DELETED` event.
|
||||||
|
|
||||||
|
This event notifies the UI about the message
|
||||||
|
being deleted from the messagelist, e.g. when the message expires
|
||||||
|
or the user deletes it.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Emit `DC_EVENT_MSGS_CHANGED` without IDs when the message expires.
|
||||||
|
|
||||||
|
Specifying msg IDs that cannot be loaded in the event payload
|
||||||
|
results in an error when the UI tries to load the message.
|
||||||
|
Instead, emit an event without IDs
|
||||||
|
to make the UI reload the whole messagelist.
|
||||||
|
|
||||||
|
- Ignore address case when comparing the `To:` field to `Autocrypt-Gossip:`.
|
||||||
|
|
||||||
|
This bug resulted in failure to propagate verification
|
||||||
|
if the contact list already contained a new verified group member
|
||||||
|
with a non-lowercase address.
|
||||||
|
|
||||||
|
- dehtml: skip links with empty text.
|
||||||
|
|
||||||
|
Links like `<a href="https://delta.chat/"></a>` in HTML mails are now skipped
|
||||||
|
instead of being converted to a link without a label like `[](https://delta.chat/)`.
|
||||||
|
|
||||||
|
- dehtml: Do not insert unnecessary newlines when parsing `<p>` tags.
|
||||||
|
|
||||||
|
- Update from yanked `libc` 0.2.145 to 0.2.146.
|
||||||
|
- Update to async-imap 0.9.0 to remove deprecated `ouroboros` dependency.
|
||||||
|
|
||||||
|
### API-Changes
|
||||||
|
|
||||||
|
- Emit `DC_EVENT_MSGS_CHANGED` per chat when messages are deleted.
|
||||||
|
|
||||||
|
Previously a single event with zero chat ID was emitted.
|
||||||
|
|
||||||
|
- python: make `Contact.is_verified()` return bool.
|
||||||
|
|
||||||
|
- rust: add API endpoint `get_status_update` ([#4468](https://github.com/deltachat/deltachat-core-rust/pull/4468)).
|
||||||
|
|
||||||
|
- rust: make `WebxdcManifest` type public.
|
||||||
|
|
||||||
|
### Build system
|
||||||
|
|
||||||
|
- Use Rust 1.70.0 to compile deltachat-rpc-server releases.
|
||||||
|
- Disable unused `brotli` feature `ffi-api` and use 1 codegen-units for release builds to reduce the size of the binaries.
|
||||||
|
|
||||||
|
### CI
|
||||||
|
|
||||||
|
- Run `cargo check` with musl libc.
|
||||||
|
- concourse: Install devpi in a virtual environment.
|
||||||
|
- Remove [mergeable](https://mergeable.us/) configuration.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- README: mark napi.rs bindings as experimental. CFFI bindings are not legacy and are the recommended Node.js bindings currently.
|
||||||
|
- CONTRIBUTING: document how conventional commits interact with squash merges.
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Rename `MimeMessage.header` into `MimeMessage.headers`.
|
||||||
|
|
||||||
|
- Derive `Default` trait for `WebxdcManifest`.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
- Regression test for case-sensitive comparison of gossip header to contact address.
|
||||||
|
- Multiple new group consistency tests in Rust.
|
||||||
|
- python: Replace legacy `tmpdir` fixture with `tmp_path`.
|
||||||
|
|
||||||
|
## [1.116.0] - 2023-06-05
|
||||||
|
|
||||||
|
### API-Changes
|
||||||
|
|
||||||
|
- Add `dc_jsonrpc_blocking_call()`.
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- Generate OpenRPC definitions for JSON-RPC.
|
||||||
|
- Add more context to message loading errors.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Build deltachat-node prebuilds on Debian 10.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Document release process in `RELEASE.md`.
|
||||||
|
- Add contributing guidelines `CONTRIBUTING.md`.
|
||||||
|
- Update instructions for python devenv.
|
||||||
|
- python: Document pytest fixtures.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
- python: Make `test_mdn_asymmetric` less flaky.
|
||||||
|
- Make `test_group_with_removed_message_id` less flaky.
|
||||||
|
- Add golden tests infrastructure ([#4395](https://github.com/deltachat/deltachat-core-rust/pull/4395)).
|
||||||
|
|
||||||
|
### Build system
|
||||||
|
|
||||||
|
- git-cliff: Changelog generation improvements.
|
||||||
|
- `set_core_version.py`: Expect release date in the changelog.
|
||||||
|
|
||||||
|
### CI
|
||||||
|
|
||||||
|
- Require Python 3.8 for deltachat-rpc-client.
|
||||||
|
- mergeable: Allow PR titles to start with "ci" and "build".
|
||||||
|
- Remove incorrect comment.
|
||||||
|
- dependabot: Use `chore` prefix for dependency updates.
|
||||||
|
- Remove broken `node-delete-preview.yml` workflow.
|
||||||
|
- Add top comments to GH Actions workflows.
|
||||||
|
- Run node.js lint on Windows.
|
||||||
|
- Update clippy to 1.70.0.
|
||||||
|
|
||||||
|
### Miscellaneous Tasks
|
||||||
|
|
||||||
|
- Remove release.toml.
|
||||||
|
- gitattributes: Configure LF line endings for JavaScript files.
|
||||||
|
- Update dependencies
|
||||||
|
|
||||||
|
## [1.112.10] - 2023-06-01
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Disable `fetch_existing_msgs` setting by default.
|
||||||
|
- Update `h2` to fix RUSTSEC-2023-0034.
|
||||||
|
|
||||||
|
## [1.115.0] - 2023-05-12
|
||||||
|
|
||||||
|
### JSON-RPC API Changes
|
||||||
|
|
||||||
|
- Sort reactions in descending order ([#4388](https://github.com/deltachat/deltachat-core-rust/pull/4388)).
|
||||||
|
- Add API to get reactions outside the message snapshot.
|
||||||
|
- `get_chatlist_items_by_entries` now takes only chatids instead of `ChatListEntries`.
|
||||||
|
- `get_chatlist_entries` now returns `Vec<u32>` of chatids instead of `ChatListEntries`.
|
||||||
|
- `JSONRPCReactions.reactions` is now a `Vec<JSONRPCReaction>` with unique reactions and their count, sorted in descending order.
|
||||||
|
- `Event`: `context_id` property is now called `contextId`.
|
||||||
|
- Expand `MessageSearchResult`:
|
||||||
|
- Always include `chat_name`(not an option anymore).
|
||||||
|
- Add `author_id`, `chat_type`, `chat_color`, `is_chat_protected`, `is_chat_contact_request`, `is_chat_archived`.
|
||||||
|
- `author_name` now contains the overridden sender name.
|
||||||
|
- `ChatListItemFetchResult` gets new properties: `summary_preview_image`, `last_message_type` and `last_message_id`
|
||||||
|
- New `MessageReadReceipt` type and `get_message_read_receipts(account_id, message_id)` jsonrpc method.
|
||||||
|
|
||||||
|
### API Changes
|
||||||
|
|
||||||
|
- New rust API `send_webxdc_status_update_struct` to send a `StatusUpdateItem`.
|
||||||
|
- Add `get_msg_read_receipts(context, msg_id)` - get the contacts that send read receipts for a message.
|
||||||
|
|
||||||
|
### Features / Changes
|
||||||
|
|
||||||
|
- Build deltachat-rpc-server releases for x86\_64 macOS.
|
||||||
|
- Generate changelogs using git-cliff ([#4393](https://github.com/deltachat/deltachat-core-rust/pull/4393), [#4396](https://github.com/deltachat/deltachat-core-rust/pull/4396)).
|
||||||
|
- Improve SMTP logging.
|
||||||
|
- Do not cut incoming text if "bot" config is set.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- JSON-RPC: typescript client: fix types of events in event emitter ([#4373](https://github.com/deltachat/deltachat-core-rust/pull/4373)).
|
||||||
|
- Fetch at most 100 existing messages even if EXISTS was not received ([#4383](https://github.com/deltachat/deltachat-core-rust/pull/4383)).
|
||||||
|
- Don't put a double dot at the end of error messages ([#4398](https://github.com/deltachat/deltachat-core-rust/pull/4398)).
|
||||||
|
- Recreate `smtp` table with AUTOINCREMENT `id` ([#4390](https://github.com/deltachat/deltachat-core-rust/pull/4390)).
|
||||||
|
- Do not return an error from `send_msg_to_smtp` if retry limit is exceeded.
|
||||||
|
- Make the bots automatically accept group chat contact requests ([#4377](https://github.com/deltachat/deltachat-core-rust/pull/4377)).
|
||||||
|
- Delete `smtp` rows when message sending is cancelled ([#4391](https://github.com/deltachat/deltachat-core-rust/pull/4391)).
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Iterate over `msg_ids` without .iter().
|
||||||
|
|
||||||
|
## [1.112.9] - 2023-05-12
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Fetch at most 100 existing messages even if EXISTS was not received.
|
||||||
|
- Delete `smtp` rows when message sending is cancelled.
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- Improve SMTP logging.
|
||||||
|
|
||||||
|
## [1.114.0] - 2023-04-24
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- JSON-RPC: Use long polling instead of server-sent notifications to retrieve events.
|
||||||
|
This better corresponds to JSON-RPC 2.0 server-client distinction
|
||||||
|
and is expected to simplify writing new bindings
|
||||||
|
because dispatching events can be done on higher level.
|
||||||
|
- JSON-RPC: TS: Client now has a mandatory argument whether you want to start listening for events.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- JSON-RPC: do not print to stdout on failure to find an account.
|
||||||
|
|
||||||
|
|
||||||
|
## [1.113.0] - 2023-04-18
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- New JSON-RPC API `can_send()`.
|
||||||
|
- New `dc_get_next_msgs()` and `dc_wait_next_msgs()` C APIs.
|
||||||
|
New `get_next_msgs()` and `wait_next_msgs()` JSON-RPC API.
|
||||||
|
These APIs can be used by bots to get all unprocessed messages
|
||||||
|
in the order of their arrival and wait for them without relying on events.
|
||||||
|
- New Python bindings API `Account.wait_next_incoming_message()`.
|
||||||
|
- New Python bindings APIs `Message.is_from_self()` and `Message.is_from_device()`.
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Increase MSRV to 1.65.0. #4236
|
||||||
|
- Remove upper limit on the attachment size. #4253
|
||||||
|
- Update rPGP to 0.10.1. #4236
|
||||||
|
- Compress HTML emails stored in the `mime_headers` column of the database.
|
||||||
|
- Strip BIDI characters in system messages, files, group names and contact names. #3479
|
||||||
|
- Use release date instead of the provider database update date in `maybe_add_time_based_warnings()`.
|
||||||
|
- Gracefully terminate `deltachat-rpc-server` on Ctrl+C (`SIGINT`), `SIGTERM` and EOF.
|
||||||
|
- Async Python API `get_fresh_messages_in_arrival_order()` is deprecated
|
||||||
|
in favor of `get_next_msgs()` and `wait_next_msgs()`.
|
||||||
|
- Remove metadata from avatars and JPEG images before sending. #4037
|
||||||
|
- Recode PNG and other supported image formats to JPEG if they are > 500K in size. #4037
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Don't let blocking be bypassed using groups. #4316
|
||||||
|
- Show a warning if quota list is empty. #4261
|
||||||
|
- Do not reset status on other devices when sending signed reaction messages. #3692
|
||||||
|
- Update `accounts.toml` atomically.
|
||||||
|
- Fix python bindings README documentation on installing the bindings from source.
|
||||||
|
- Remove confusing log line "ignoring unsolicited response Recent(…)". #3934
|
||||||
|
|
||||||
|
## [1.112.8] - 2023-04-20
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Add `get_http_response` JSON-RPC API.
|
||||||
|
- Add C API to get HTTP responses.
|
||||||
|
|
||||||
|
## [1.112.7] - 2023-04-17
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Updated `async-imap` to v0.8.0 to fix erroneous EOF detection in long IMAP responses.
|
||||||
|
|
||||||
|
## [1.112.6] - 2023-04-04
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- Add a device message after backup transfer #4301
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Updated `iroh` from 0.4.0 to 0.4.1 to fix transfer of large accounts with many blob files.
|
||||||
|
|
||||||
## [1.112.5] - 2023-04-02
|
## [1.112.5] - 2023-04-02
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
@@ -20,8 +459,8 @@
|
|||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
- Update iroh, remove `default-net` from `[patch.crates-io]` section.
|
- Update iroh, remove `default-net` from `[patch.crates-io]` section.
|
||||||
- transfer backup: Connect to mutliple provider addresses concurrently. This should speed up connection time significantly on the getter side. #4240
|
- transfer backup: Connect to multiple provider addresses concurrently. This should speed up connection time significantly on the getter side. #4240
|
||||||
- Make sure BackupProvider is cancelled on drop (or dc_backup_provider_unref). The BackupProvider will now alaway finish with an IMEX event of 1000 or 0, previoulsy it would sometimes finishe with 1000 (success) when it really was 0 (failure). #4242
|
- Make sure BackupProvider is cancelled on drop (or `dc_backup_provider_unref`). The BackupProvider will now always finish with an IMEX event of 1000 or 0, previously it would sometimes finished with 1000 (success) when it really was 0 (failure). #4242
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Do not return media from trashed messages in the "All media" view. #4247
|
- Do not return media from trashed messages in the "All media" view. #4247
|
||||||
@@ -195,7 +634,6 @@
|
|||||||
- Do not treat invalid email addresses as an exception #3942
|
- Do not treat invalid email addresses as an exception #3942
|
||||||
- Add timeouts to HTTP requests #3948
|
- Add timeouts to HTTP requests #3948
|
||||||
|
|
||||||
|
|
||||||
## 1.105.0
|
## 1.105.0
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
@@ -281,7 +719,6 @@
|
|||||||
- Disable read timeout during IMAP IDLE #3826
|
- Disable read timeout during IMAP IDLE #3826
|
||||||
- Bots automatically accept mailing lists #3831
|
- Bots automatically accept mailing lists #3831
|
||||||
|
|
||||||
|
|
||||||
## 1.102.0
|
## 1.102.0
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
@@ -2363,3 +2800,19 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
|
|||||||
[1.112.3]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.2...v1.112.3
|
[1.112.3]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.2...v1.112.3
|
||||||
[1.112.4]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.3...v1.112.4
|
[1.112.4]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.3...v1.112.4
|
||||||
[1.112.5]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.4...v1.112.5
|
[1.112.5]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.4...v1.112.5
|
||||||
|
[1.112.6]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.5...v1.112.6
|
||||||
|
[1.112.7]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.6...v1.112.7
|
||||||
|
[1.112.8]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.7...v1.112.8
|
||||||
|
[1.112.9]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.8...v1.112.9
|
||||||
|
[1.112.10]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.9...v1.112.10
|
||||||
|
[1.113.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.9...v1.113.0
|
||||||
|
[1.114.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.113.0...v1.114.0
|
||||||
|
[1.115.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.114.0...v1.115.0
|
||||||
|
[1.116.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.115.0...v1.116.0
|
||||||
|
[1.117.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.116.0...v1.117.0
|
||||||
|
[1.118.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.117.0...v1.118.0
|
||||||
|
[1.119.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.118.0...v1.119.0
|
||||||
|
[1.119.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.119.0...v1.119.1
|
||||||
|
[1.120.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.119.1...v1.120.0
|
||||||
|
[1.121.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.120.0...v1.121.0
|
||||||
|
[1.122.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.121.0...v1.122.0
|
||||||
|
|||||||
92
CONTRIBUTING.md
Normal file
92
CONTRIBUTING.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# Contributing guidelines
|
||||||
|
|
||||||
|
## Reporting bugs
|
||||||
|
|
||||||
|
If you found a bug, [report it on GitHub](https://github.com/deltachat/deltachat-core-rust/issues).
|
||||||
|
If the bug you found is specific to
|
||||||
|
[Android](https://github.com/deltachat/deltachat-android/issues),
|
||||||
|
[iOS](https://github.com/deltachat/deltachat-ios/issues) or
|
||||||
|
[Desktop](https://github.com/deltachat/deltachat-desktop/issues),
|
||||||
|
report it to the corresponding repository.
|
||||||
|
|
||||||
|
## Proposing features
|
||||||
|
|
||||||
|
If you have a feature request, create a new topic on the [forum](https://support.delta.chat/).
|
||||||
|
|
||||||
|
## Contributing code
|
||||||
|
|
||||||
|
If you want to contribute a code, [open a Pull Request](https://github.com/deltachat/deltachat-core-rust/pulls).
|
||||||
|
|
||||||
|
If you have write access to the repository,
|
||||||
|
push a branch named `<username>/<feature>`
|
||||||
|
so it is clear who is responsible for the branch,
|
||||||
|
and open a PR proposing to merge the change.
|
||||||
|
Otherwise fork the repository and create a branch in your fork.
|
||||||
|
|
||||||
|
You can find the list of good first issues
|
||||||
|
and a link to this guide
|
||||||
|
on the contributing page: <https://github.com/deltachat/deltachat-core-rust/contribute>
|
||||||
|
|
||||||
|
### Coding conventions
|
||||||
|
|
||||||
|
We format the code using `rustfmt`. Run `cargo fmt` prior to committing the code.
|
||||||
|
Run `scripts/clippy.sh` to check the code for common mistakes with [Clippy].
|
||||||
|
|
||||||
|
Commit messages follow the [Conventional Commits] notation.
|
||||||
|
We use [git-cliff] to generate the changelog from commit messages before the release.
|
||||||
|
|
||||||
|
With **`git cliff --unreleased`**, you can check how the changelog entry for your commit will look.
|
||||||
|
|
||||||
|
The following prefix types are used:
|
||||||
|
- `feat`: Features, e.g. "feat: Pause IO for BackupProvider". If you are unsure what's the category of your commit, you can often just use `feat`.
|
||||||
|
- `fix`: Bug fixes, e.g. "fix: delete `smtp` rows when message sending is cancelled"
|
||||||
|
- `api`: API changes, e.g. "api(rust): add `get_msg_read_receipts(context, msg_id)`"
|
||||||
|
- `refactor`: Refactorings, e.g. "refactor: iterate over `msg_ids` without `.iter()`"
|
||||||
|
- `perf`: Performance improvements, e.g. "perf: improve SQLite performance with `PRAGMA synchronous=normal`"
|
||||||
|
- `test`: Test changes and improvements to the testing framework.
|
||||||
|
- `build`: Build system and tool configuration changes, e.g. "build(git-cliff): put "ci" commits into "CI" section of changelog"
|
||||||
|
- `ci`: CI configuration changes, e.g. "ci: limit artifact retention time for `libdeltachat.a` to 1 day"
|
||||||
|
- `docs`: Documentation changes, e.g. "docs: add contributing guidelines"
|
||||||
|
- `chore`: miscellaneous tasks, e.g. "chore: add `.DS_Store` to `.gitignore`"
|
||||||
|
|
||||||
|
Release preparation commits are marked as "chore(release): prepare for vX.Y.Z".
|
||||||
|
|
||||||
|
If you intend to squash merge the PR from the web interface,
|
||||||
|
make sure the PR title follows the conventional commits notation
|
||||||
|
as it will end up being a commit title.
|
||||||
|
Otherwise make sure each commit title follows the conventional commit notation.
|
||||||
|
|
||||||
|
#### Breaking Changes
|
||||||
|
|
||||||
|
Use a `!` to mark breaking changes, e.g. "api!: Remove `dc_chat_can_send`".
|
||||||
|
|
||||||
|
Alternatively, breaking changes can go into the commit description, e.g.:
|
||||||
|
|
||||||
|
```
|
||||||
|
fix: Fix race condition and db corruption when a message was received during backup
|
||||||
|
|
||||||
|
BREAKING CHANGE: You have to call `dc_stop_io()`/`dc_start_io()` before/after `dc_imex(DC_IMEX_EXPORT_BACKUP)`
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Multiple Changes in one PR
|
||||||
|
|
||||||
|
If you have multiple changes in one PR, create multiple conventional commits, and then do a rebase merge. Otherwise, you should usually do a squash merge.
|
||||||
|
|
||||||
|
[Clippy]: https://doc.rust-lang.org/clippy/
|
||||||
|
[Conventional Commits]: https://www.conventionalcommits.org/
|
||||||
|
[git-cliff]: https://git-cliff.org/
|
||||||
|
|
||||||
|
### Reviewing
|
||||||
|
|
||||||
|
Once a PR has an approval and passes CI, it can be merged.
|
||||||
|
|
||||||
|
PRs from a branch created in the main repository, i.e. authored by those who have write access, are merged by their authors.
|
||||||
|
This is to ensure that PRs are merged as intended by the author,
|
||||||
|
e.g. as a squash merge, by rebasing from the web interface or manually from the command line.
|
||||||
|
|
||||||
|
If you do not have access to the repository and created a PR from a fork,
|
||||||
|
ask the maintainers to merge the PR and say how it should be merged.
|
||||||
|
|
||||||
|
## Other ways to contribute
|
||||||
|
|
||||||
|
For other ways to contribute, refer to the [website](https://delta.chat/en/contribute).
|
||||||
2227
Cargo.lock
generated
2227
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
55
Cargo.toml
55
Cargo.toml
@@ -1,9 +1,9 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat"
|
name = "deltachat"
|
||||||
version = "1.112.5"
|
version = "1.122.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
rust-version = "1.64"
|
rust-version = "1.65"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
debug = 0
|
debug = 0
|
||||||
@@ -23,6 +23,7 @@ opt-level = "z"
|
|||||||
lto = true
|
lto = true
|
||||||
panic = 'abort'
|
panic = 'abort'
|
||||||
opt-level = "z"
|
opt-level = "z"
|
||||||
|
codegen-units = 1
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
quinn-udp = { git = "https://github.com/quinn-rs/quinn", branch="main" }
|
quinn-udp = { git = "https://github.com/quinn-rs/quinn", branch="main" }
|
||||||
@@ -35,42 +36,43 @@ ratelimit = { path = "./deltachat-ratelimit" }
|
|||||||
|
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
async-channel = "1.8.0"
|
async-channel = "1.8.0"
|
||||||
async-imap = { git = "https://github.com/async-email/async-imap", branch = "master", default-features = false, features = ["runtime-tokio"] }
|
async-imap = { version = "0.9.1", default-features = false, features = ["runtime-tokio"] }
|
||||||
async-native-tls = { version = "0.5", default-features = false, features = ["runtime-tokio"] }
|
async-native-tls = { version = "0.5", default-features = false, features = ["runtime-tokio"] }
|
||||||
async-smtp = { version = "0.9", default-features = false, features = ["runtime-tokio"] }
|
async-smtp = { version = "0.9", default-features = false, features = ["runtime-tokio"] }
|
||||||
async_zip = { version = "0.0.11", default-features = false, features = ["deflate", "fs"] }
|
async_zip = { version = "0.0.12", default-features = false, features = ["deflate", "fs"] }
|
||||||
backtrace = "0.3"
|
backtrace = "0.3"
|
||||||
base64 = "0.21"
|
base64 = "0.21"
|
||||||
bitflags = "1.3"
|
brotli = { version = "3.3", default-features=false, features = ["std"] }
|
||||||
chrono = { version = "0.4", default-features=false, features = ["clock", "std"] }
|
chrono = { version = "0.4", default-features=false, features = ["clock", "std"] }
|
||||||
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
|
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
|
||||||
encoded-words = { git = "https://github.com/async-email/encoded-words", branch = "master" }
|
encoded-words = { git = "https://github.com/async-email/encoded-words", branch = "master" }
|
||||||
escaper = "0.1"
|
escaper = "0.1"
|
||||||
fast-socks5 = "0.8"
|
fast-socks5 = "0.8"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
futures-lite = "1.12.0"
|
futures-lite = "1.13.0"
|
||||||
hex = "0.4.0"
|
hex = "0.4.0"
|
||||||
humansize = "2"
|
humansize = "2"
|
||||||
image = { version = "0.24.5", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
image = { version = "0.24.6", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
|
||||||
iroh = { version = "0.4.0", default-features = false }
|
iroh = { version = "0.4.1", default-features = false }
|
||||||
kamadak-exif = "0.5"
|
kamadak-exif = "0.5"
|
||||||
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
|
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
mailparse = "0.14"
|
mailparse = "0.14"
|
||||||
num_cpus = "1.15"
|
mime = "0.3.17"
|
||||||
num-derive = "0.3"
|
num_cpus = "1.16"
|
||||||
|
num-derive = "0.4"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
once_cell = "1.17.0"
|
once_cell = "1.18.0"
|
||||||
percent-encoding = "2.2"
|
percent-encoding = "2.3"
|
||||||
parking_lot = "0.12"
|
parking_lot = "0.12"
|
||||||
pgp = { version = "0.9", default-features = false }
|
pgp = { version = "0.10", default-features = false }
|
||||||
pretty_env_logger = { version = "0.4", optional = true }
|
pretty_env_logger = { version = "0.5", optional = true }
|
||||||
qrcodegen = "1.7.0"
|
qrcodegen = "1.7.0"
|
||||||
quick-xml = "0.27"
|
quick-xml = "0.29"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
regex = "1.7"
|
regex = "1.8"
|
||||||
reqwest = { version = "0.11.14", features = ["json"] }
|
reqwest = { version = "0.11.18", features = ["json"] }
|
||||||
rusqlite = { version = "0.28", features = ["sqlcipher"] }
|
rusqlite = { version = "0.29", features = ["sqlcipher"] }
|
||||||
rust-hsluv = "0.1"
|
rust-hsluv = "0.1"
|
||||||
sanitize-filename = "0.4"
|
sanitize-filename = "0.4"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
@@ -78,16 +80,16 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
sha-1 = "0.10"
|
sha-1 = "0.10"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
smallvec = "1"
|
smallvec = "1"
|
||||||
strum = "0.24"
|
strum = "0.25"
|
||||||
strum_macros = "0.24"
|
strum_macros = "0.25"
|
||||||
tagger = "4.3.4"
|
tagger = "4.3.4"
|
||||||
textwrap = "0.16.0"
|
textwrap = "0.16.0"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
|
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
|
||||||
tokio-io-timeout = "1.2.0"
|
tokio-io-timeout = "1.2.0"
|
||||||
tokio-stream = { version = "0.1.11", features = ["fs"] }
|
tokio-stream = { version = "0.1.14", features = ["fs"] }
|
||||||
tokio-tar = { version = "0.3" } # TODO: integrate tokio into async-tar
|
tokio-tar = { version = "0.3" } # TODO: integrate tokio into async-tar
|
||||||
tokio-util = "0.7.7"
|
tokio-util = "0.7.8"
|
||||||
toml = "0.7"
|
toml = "0.7"
|
||||||
trust-dns-resolver = "0.22"
|
trust-dns-resolver = "0.22"
|
||||||
url = "2"
|
url = "2"
|
||||||
@@ -95,14 +97,15 @@ uuid = { version = "1", features = ["serde", "v4"] }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ansi_term = "0.12.0"
|
ansi_term = "0.12.0"
|
||||||
criterion = { version = "0.4.0", features = ["async_tokio"] }
|
criterion = { version = "0.5.1", features = ["async_tokio"] }
|
||||||
futures-lite = "1.12"
|
futures-lite = "1.13"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
pretty_env_logger = "0.4"
|
pretty_env_logger = "0.5"
|
||||||
proptest = { version = "1", default-features = false, features = ["std"] }
|
proptest = { version = "1", default-features = false, features = ["std"] }
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
testdir = "0.7.2"
|
testdir = "0.8.0"
|
||||||
tokio = { version = "1", features = ["parking_lot", "rt-multi-thread", "macros"] }
|
tokio = { version = "1", features = ["parking_lot", "rt-multi-thread", "macros"] }
|
||||||
|
pretty_assertions = "1.3.0"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -361,7 +361,7 @@ Exhibit A - Source Code Form License Notice
|
|||||||
|
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
If it is not possible or desirable to put the notice in a particular
|
If it is not possible or desirable to put the notice in a particular
|
||||||
file, then You may include the notice in a location (such as a LICENSE
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
|||||||
@@ -167,8 +167,8 @@ Language bindings are available for:
|
|||||||
|
|
||||||
- **C** \[[📂 source](./deltachat-ffi) | [📚 docs](https://c.delta.chat)\]
|
- **C** \[[📂 source](./deltachat-ffi) | [📚 docs](https://c.delta.chat)\]
|
||||||
- **Node.js**
|
- **Node.js**
|
||||||
- over cffi (legacy): \[[📂 source](./node) | [📦 npm](https://www.npmjs.com/package/deltachat-node) | [📚 docs](https://js.delta.chat)\]
|
- over cffi: \[[📂 source](./node) | [📦 npm](https://www.npmjs.com/package/deltachat-node) | [📚 docs](https://js.delta.chat)\]
|
||||||
- over jsonrpc built with napi.rs: \[[📂 source](https://github.com/deltachat/napi-jsonrpc) | [📦 npm](https://www.npmjs.com/package/@deltachat/napi-jsonrpc)\]
|
- over jsonrpc built with napi.rs (experimental): \[[📂 source](https://github.com/deltachat/napi-jsonrpc) | [📦 npm](https://www.npmjs.com/package/@deltachat/napi-jsonrpc)\]
|
||||||
- **Python** \[[📂 source](./python) | [📦 pypi](https://pypi.org/project/deltachat) | [📚 docs](https://py.delta.chat)\]
|
- **Python** \[[📂 source](./python) | [📦 pypi](https://pypi.org/project/deltachat) | [📚 docs](https://py.delta.chat)\]
|
||||||
- **Go**
|
- **Go**
|
||||||
- over jsonrpc: \[[📂 source](https://github.com/deltachat/deltachat-rpc-client-go/)\]
|
- over jsonrpc: \[[📂 source](https://github.com/deltachat/deltachat-rpc-client-go/)\]
|
||||||
|
|||||||
21
RELEASE.md
Normal file
21
RELEASE.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Releasing a new version of DeltaChat core
|
||||||
|
|
||||||
|
For example, to release version 1.116.0 of the core, do the following steps.
|
||||||
|
|
||||||
|
1. Resolve all [blocker issues](https://github.com/deltachat/deltachat-core-rust/labels/blocker).
|
||||||
|
|
||||||
|
2. Run `npm run build:core:constants` in the root of the repository
|
||||||
|
and commit generated `node/constants.js`, `node/events.js` and `node/lib/constants.js`.
|
||||||
|
|
||||||
|
3. Update the changelog: `git cliff --unreleased --tag 1.116.0 --prepend CHANGELOG.md` or `git cliff -u -t 1.116.0 -p CHANGELOG.md`.
|
||||||
|
|
||||||
|
4. Update the version by running `scripts/set_core_version.py 1.116.0`.
|
||||||
|
|
||||||
|
5. Commit the changes as `chore(release): prepare for 1.116.0`.
|
||||||
|
Optionally, use a separate branch like `prep-1.116.0` for this commit and open a PR for review.
|
||||||
|
|
||||||
|
6. Tag the release: `git tag -a v1.116.0`.
|
||||||
|
|
||||||
|
7. Push the release tag: `git push origin v1.116.0`.
|
||||||
|
|
||||||
|
8. Create a GitHub release: `gh release create v1.116.0 -n ''`.
|
||||||
79
cliff.toml
Normal file
79
cliff.toml
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# configuration file for git-cliff
|
||||||
|
# see https://git-cliff.org/docs/configuration/
|
||||||
|
|
||||||
|
|
||||||
|
[git]
|
||||||
|
# parse the commits based on https://www.conventionalcommits.org
|
||||||
|
conventional_commits = true
|
||||||
|
# filter out the commits that are not conventional
|
||||||
|
filter_unconventional = false
|
||||||
|
# process each line of a commit as an individual commit
|
||||||
|
split_commits = false
|
||||||
|
# regex for preprocessing the commit messages
|
||||||
|
commit_preprocessors = [
|
||||||
|
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/deltachat/deltachat-core-rust/pull/${2}))"}, # replace pull request / issue numbers
|
||||||
|
]
|
||||||
|
# regex for parsing and grouping commits
|
||||||
|
commit_parsers = [
|
||||||
|
{ message = "^feat", group = "Features / Changes"},
|
||||||
|
{ message = "^fix", group = "Fixes"},
|
||||||
|
{ message = "^api", group = "API-Changes" },
|
||||||
|
{ message = "^refactor", group = "Refactor"},
|
||||||
|
{ message = "^perf", group = "Performance"},
|
||||||
|
{ message = "^test", group = "Tests"},
|
||||||
|
{ message = "^style", group = "Styling"},
|
||||||
|
{ message = "^chore\\(release\\): prepare for", skip = true},
|
||||||
|
{ message = "^chore", group = "Miscellaneous Tasks"},
|
||||||
|
{ message = "^build", group = "Build system"},
|
||||||
|
{ message = "^docs", group = "Documentation"},
|
||||||
|
{ message = "^ci", group = "CI"},
|
||||||
|
{ message = ".*", group = "Other"},
|
||||||
|
# { body = ".*security", group = "Security"},
|
||||||
|
]
|
||||||
|
# protect breaking changes from being skipped due to matching a skipping commit_parser
|
||||||
|
protect_breaking_commits = true
|
||||||
|
# filter out the commits that are not matched by commit parsers
|
||||||
|
filter_commits = true
|
||||||
|
# glob pattern for matching git tags
|
||||||
|
tag_pattern = "v[0-9]*"
|
||||||
|
# regex for skipping tags
|
||||||
|
#skip_tags = "v0.1.0-beta.1"
|
||||||
|
# regex for ignoring tags
|
||||||
|
ignore_tags = ""
|
||||||
|
# sort the tags topologically
|
||||||
|
topo_order = false
|
||||||
|
# sort the commits inside sections by oldest/newest order
|
||||||
|
sort_commits = "oldest"
|
||||||
|
# limit the number of commits included in the changelog.
|
||||||
|
# limit_commits = 42
|
||||||
|
|
||||||
|
|
||||||
|
[changelog]
|
||||||
|
# changelog header
|
||||||
|
header = """
|
||||||
|
# Changelog\n
|
||||||
|
"""
|
||||||
|
# template for the changelog body
|
||||||
|
# https://tera.netlify.app/docs/#introduction
|
||||||
|
body = """
|
||||||
|
{% if version %}\
|
||||||
|
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||||
|
{% else %}\
|
||||||
|
## [unreleased]
|
||||||
|
{% endif %}\
|
||||||
|
{% for group, commits in commits | group_by(attribute="group") %}
|
||||||
|
### {{ group | upper_first }}
|
||||||
|
{% for commit in commits %}
|
||||||
|
- {% if commit.breaking %}[**breaking**] {% endif %}\
|
||||||
|
{% if commit.scope %}{{ commit.scope }}: {% endif %}\
|
||||||
|
{{ commit.message | upper_first }}.\
|
||||||
|
{% if commit.footers is defined %}\
|
||||||
|
{% for footer in commit.footers %}{% if 'BREAKING CHANGE' in footer.token %}
|
||||||
|
{% raw %} {% endraw %}- {{ footer.value }}\
|
||||||
|
{% endif %}{% endfor %}\
|
||||||
|
{% endif%}\
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}\n
|
||||||
|
"""
|
||||||
|
# remove the leading and trailing whitespace from the template
|
||||||
|
trim = true
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat_ffi"
|
name = "deltachat_ffi"
|
||||||
version = "1.112.5"
|
version = "1.122.0"
|
||||||
description = "Deltachat FFI"
|
description = "Deltachat FFI"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -24,7 +24,8 @@ tokio = { version = "1", features = ["rt-multi-thread"] }
|
|||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
once_cell = "1.17.0"
|
once_cell = "1.18.0"
|
||||||
|
yerpc = { version = "0.5.1", features = ["anyhow_expose"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["vendored"]
|
default = ["vendored"]
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ typedef struct _dc_event dc_event_t;
|
|||||||
typedef struct _dc_event_emitter dc_event_emitter_t;
|
typedef struct _dc_event_emitter dc_event_emitter_t;
|
||||||
typedef struct _dc_jsonrpc_instance dc_jsonrpc_instance_t;
|
typedef struct _dc_jsonrpc_instance dc_jsonrpc_instance_t;
|
||||||
typedef struct _dc_backup_provider dc_backup_provider_t;
|
typedef struct _dc_backup_provider dc_backup_provider_t;
|
||||||
|
typedef struct _dc_http_response dc_http_response_t;
|
||||||
|
|
||||||
// Alias for backwards compatibility, use dc_event_emitter_t instead.
|
// Alias for backwards compatibility, use dc_event_emitter_t instead.
|
||||||
typedef struct _dc_event_emitter dc_accounts_event_emitter_t;
|
typedef struct _dc_event_emitter dc_accounts_event_emitter_t;
|
||||||
@@ -181,12 +182,17 @@ typedef struct _dc_event_emitter dc_accounts_event_emitter_t;
|
|||||||
* and check it in the event loop thread
|
* and check it in the event loop thread
|
||||||
* every time before calling dc_get_next_event().
|
* every time before calling dc_get_next_event().
|
||||||
* To terminate the event loop, main thread should:
|
* To terminate the event loop, main thread should:
|
||||||
* 1. Notify event loop that it should terminate by atomically setting the
|
* 1. Notify background threads,
|
||||||
* boolean flag in the memory shared between the main thread and event loop.
|
* such as event loop (blocking in dc_get_next_event())
|
||||||
|
* and message processing loop (blocking in dc_wait_next_msgs()),
|
||||||
|
* that they should terminate by atomically setting the
|
||||||
|
* boolean flag in the memory
|
||||||
|
* shared between the main thread and background loop threads.
|
||||||
* 2. Call dc_stop_io() or dc_accounts_stop_io(), depending
|
* 2. Call dc_stop_io() or dc_accounts_stop_io(), depending
|
||||||
* on whether a single account or account manager is used.
|
* on whether a single account or account manager is used.
|
||||||
* Stopping I/O is guaranteed to emit at least one event
|
* Stopping I/O is guaranteed to emit at least one event
|
||||||
* and interrupt the event loop even if it was blocked on dc_get_next_event().
|
* and interrupt the event loop even if it was blocked on dc_get_next_event().
|
||||||
|
* Stopping I/O is guaranteed to interrupt a single dc_wait_next_msgs().
|
||||||
* 3. Wait until the event loop thread notices the flag,
|
* 3. Wait until the event loop thread notices the flag,
|
||||||
* exits the event loop and terminates.
|
* exits the event loop and terminates.
|
||||||
* 4. Call dc_context_unref() or dc_accounts_unref().
|
* 4. Call dc_context_unref() or dc_accounts_unref().
|
||||||
@@ -295,6 +301,19 @@ dc_context_t* dc_context_new_closed (const char* dbfile);
|
|||||||
int dc_context_open (dc_context_t *context, const char* passphrase);
|
int dc_context_open (dc_context_t *context, const char* passphrase);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the passphrase on the open database.
|
||||||
|
* Existing database must already be encrypted and the passphrase cannot be NULL or empty.
|
||||||
|
* It is impossible to encrypt unencrypted database with this method and vice versa.
|
||||||
|
*
|
||||||
|
* @memberof dc_context_t
|
||||||
|
* @param context The context object.
|
||||||
|
* @param passphrase The new passphrase.
|
||||||
|
* @return 1 on success, 0 on error.
|
||||||
|
*/
|
||||||
|
int dc_context_change_passphrase (dc_context_t* context, const char* passphrase);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns 1 if database is open.
|
* Returns 1 if database is open.
|
||||||
*
|
*
|
||||||
@@ -414,17 +433,19 @@ char* dc_get_blobdir (const dc_context_t* context);
|
|||||||
* 0=watch all folders normally (default)
|
* 0=watch all folders normally (default)
|
||||||
* changes require restarting IO by calling dc_stop_io() and then dc_start_io().
|
* changes require restarting IO by calling dc_stop_io() and then dc_start_io().
|
||||||
* - `show_emails` = DC_SHOW_EMAILS_OFF (0)=
|
* - `show_emails` = DC_SHOW_EMAILS_OFF (0)=
|
||||||
* show direct replies to chats only (default),
|
* show direct replies to chats only,
|
||||||
* DC_SHOW_EMAILS_ACCEPTED_CONTACTS (1)=
|
* DC_SHOW_EMAILS_ACCEPTED_CONTACTS (1)=
|
||||||
* also show all mails of confirmed contacts,
|
* also show all mails of confirmed contacts,
|
||||||
* DC_SHOW_EMAILS_ALL (2)=
|
* DC_SHOW_EMAILS_ALL (2)=
|
||||||
* also show mails of unconfirmed contacts.
|
* also show mails of unconfirmed contacts (default).
|
||||||
* - `key_gen_type` = DC_KEY_GEN_DEFAULT (0)=
|
* - `key_gen_type` = DC_KEY_GEN_DEFAULT (0)=
|
||||||
* generate recommended key type (default),
|
* generate recommended key type (default),
|
||||||
* DC_KEY_GEN_RSA2048 (1)=
|
* DC_KEY_GEN_RSA2048 (1)=
|
||||||
* generate RSA 2048 keypair
|
* generate RSA 2048 keypair
|
||||||
* DC_KEY_GEN_ED25519 (2)=
|
* DC_KEY_GEN_ED25519 (2)=
|
||||||
* generate Ed25519 keypair
|
* generate Curve25519 keypair
|
||||||
|
* DC_KEY_GEN_RSA4096 (3)=
|
||||||
|
* generate RSA 4096 keypair
|
||||||
* - `save_mime_headers` = 1=save mime headers
|
* - `save_mime_headers` = 1=save mime headers
|
||||||
* and make dc_get_mime_headers() work for subsequent calls,
|
* and make dc_get_mime_headers() work for subsequent calls,
|
||||||
* 0=do not save mime headers (default)
|
* 0=do not save mime headers (default)
|
||||||
@@ -455,8 +476,19 @@ char* dc_get_blobdir (const dc_context_t* context);
|
|||||||
* If no type is prefixed, the videochat is handled completely in a browser.
|
* If no type is prefixed, the videochat is handled completely in a browser.
|
||||||
* - `bot` = Set to "1" if this is a bot.
|
* - `bot` = Set to "1" if this is a bot.
|
||||||
* Prevents adding the "Device messages" and "Saved messages" chats,
|
* Prevents adding the "Device messages" and "Saved messages" chats,
|
||||||
* adds Auto-Submitted header to outgoing messages
|
* adds Auto-Submitted header to outgoing messages,
|
||||||
* and accepts contact requests automatically (calling dc_accept_chat() is not needed for bots).
|
* accepts contact requests automatically (calling dc_accept_chat() is not needed for bots)
|
||||||
|
* and does not cut large incoming text messages.
|
||||||
|
* - `last_msg_id` = database ID of the last message processed by the bot.
|
||||||
|
* This ID and IDs below it are guaranteed not to be returned
|
||||||
|
* by dc_get_next_msgs() and dc_wait_next_msgs().
|
||||||
|
* The value is updated automatically
|
||||||
|
* when dc_markseen_msgs() is called,
|
||||||
|
* but the bot can also set it manually if it processed
|
||||||
|
* the message but does not want to mark it as seen.
|
||||||
|
* For most bots calling `dc_markseen_msgs()` is the
|
||||||
|
* recommended way to update this value
|
||||||
|
* even for self-sent messages.
|
||||||
* - `fetch_existing_msgs` = 1=fetch most recent existing messages on configure (default),
|
* - `fetch_existing_msgs` = 1=fetch most recent existing messages on configure (default),
|
||||||
* 0=do not fetch existing messages on configure.
|
* 0=do not fetch existing messages on configure.
|
||||||
* In both cases, existing recipients are added to the contact database.
|
* In both cases, existing recipients are added to the contact database.
|
||||||
@@ -1143,6 +1175,24 @@ int dc_send_webxdc_status_update (dc_context_t* context, uint32_t msg_id, const
|
|||||||
*/
|
*/
|
||||||
char* dc_get_webxdc_status_updates (dc_context_t* context, uint32_t msg_id, uint32_t serial);
|
char* dc_get_webxdc_status_updates (dc_context_t* context, uint32_t msg_id, uint32_t serial);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces webxdc app with a new version.
|
||||||
|
*
|
||||||
|
* On the JavaScript side this API could be used like this:
|
||||||
|
* ```
|
||||||
|
* window.webxdc.replaceWebxdc(blob);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @memberof dc_context_t
|
||||||
|
* @param context The context object.
|
||||||
|
* @param msg_id The ID of the WebXDC message to be replaced.
|
||||||
|
* @param blob New blob to replace WebXDC with.
|
||||||
|
* @param n Blob size.
|
||||||
|
*/
|
||||||
|
void dc_replace_webxdc(dc_context_t* context, uint32_t msg_id, uint8_t *blob, size_t n);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save a draft for a chat in the database.
|
* Save a draft for a chat in the database.
|
||||||
*
|
*
|
||||||
@@ -1304,6 +1354,20 @@ int dc_get_msg_cnt (dc_context_t* context, uint32_t ch
|
|||||||
int dc_get_fresh_msg_cnt (dc_context_t* context, uint32_t chat_id);
|
int dc_get_fresh_msg_cnt (dc_context_t* context, uint32_t chat_id);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of similar chats.
|
||||||
|
*
|
||||||
|
* @warning This is an experimental API which may change or be removed in the future.
|
||||||
|
*
|
||||||
|
* @memberof dc_context_t
|
||||||
|
* @param context The context object as returned from dc_context_new().
|
||||||
|
* @param chat_id The ID of the chat for which to find similar chats.
|
||||||
|
* @return The list of similar chats.
|
||||||
|
* On errors, NULL is returned.
|
||||||
|
* Must be freed using dc_chatlist_unref() when no longer used.
|
||||||
|
*/
|
||||||
|
dc_chatlist_t* dc_get_similar_chatlist (dc_context_t* context, uint32_t chat_id);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Estimate the number of messages that will be deleted
|
* Estimate the number of messages that will be deleted
|
||||||
@@ -1343,6 +1407,56 @@ int dc_estimate_deletion_cnt (dc_context_t* context, int from_ser
|
|||||||
dc_array_t* dc_get_fresh_msgs (dc_context_t* context);
|
dc_array_t* dc_get_fresh_msgs (dc_context_t* context);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the message IDs of all messages of any chat
|
||||||
|
* with a database ID higher than `last_msg_id` config value.
|
||||||
|
*
|
||||||
|
* This function is intended for use by bots.
|
||||||
|
* Self-sent messages, device messages,
|
||||||
|
* messages from contact requests
|
||||||
|
* and muted chats are included,
|
||||||
|
* but messages from explicitly blocked contacts
|
||||||
|
* and chats are ignored.
|
||||||
|
*
|
||||||
|
* This function may be called as a part of event loop
|
||||||
|
* triggered by DC_EVENT_INCOMING_MSG if you are only interested
|
||||||
|
* in the incoming messages.
|
||||||
|
* Otherwise use a separate message processing loop
|
||||||
|
* calling dc_wait_next_msgs() in a separate thread.
|
||||||
|
*
|
||||||
|
* @memberof dc_context_t
|
||||||
|
* @param context The context object as returned from dc_context_new().
|
||||||
|
* @return An array of message IDs, must be dc_array_unref()'d when no longer used.
|
||||||
|
* On errors, the list is empty. NULL is never returned.
|
||||||
|
*/
|
||||||
|
dc_array_t* dc_get_next_msgs (dc_context_t* context);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for notification of new messages
|
||||||
|
* and returns an array of new message IDs.
|
||||||
|
* See the documentation for dc_get_next_msgs()
|
||||||
|
* for the details of return value.
|
||||||
|
*
|
||||||
|
* This function waits for internal notification of
|
||||||
|
* a new message in the database and returns afterwards.
|
||||||
|
* Notification is also sent when I/O is started
|
||||||
|
* to allow processing new messages
|
||||||
|
* and when I/O is stopped using dc_stop_io() or dc_accounts_stop_io()
|
||||||
|
* to allow for manual interruption of the message processing loop.
|
||||||
|
* The function may return an empty array if there are
|
||||||
|
* no messages after notification,
|
||||||
|
* which may happen on start or if the message is quickly deleted
|
||||||
|
* after adding it to the database.
|
||||||
|
*
|
||||||
|
* @memberof dc_context_t
|
||||||
|
* @param context The context object as returned from dc_context_new().
|
||||||
|
* @return An array of message IDs, must be dc_array_unref()'d when no longer used.
|
||||||
|
* On errors, the list is empty. NULL is never returned.
|
||||||
|
*/
|
||||||
|
dc_array_t* dc_wait_next_msgs (dc_context_t* context);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark all messages in a chat as _noticed_.
|
* Mark all messages in a chat as _noticed_.
|
||||||
* _Noticed_ messages are no longer _fresh_ and do not count as being unseen
|
* _Noticed_ messages are no longer _fresh_ and do not count as being unseen
|
||||||
@@ -1942,6 +2056,11 @@ int dc_resend_msgs (dc_context_t* context, const uint3
|
|||||||
* Moreover, timer is started for incoming ephemeral messages.
|
* Moreover, timer is started for incoming ephemeral messages.
|
||||||
* This also happens for contact requests chats.
|
* This also happens for contact requests chats.
|
||||||
*
|
*
|
||||||
|
* This function updates last_msg_id configuration value
|
||||||
|
* to the maximum of the current value and IDs passed to this function.
|
||||||
|
* Bots which mark messages as seen can rely on this side effect
|
||||||
|
* to avoid updating last_msg_id value manually.
|
||||||
|
*
|
||||||
* One #DC_EVENT_MSGS_NOTICED event is emitted per modified chat.
|
* One #DC_EVENT_MSGS_NOTICED event is emitted per modified chat.
|
||||||
*
|
*
|
||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
@@ -2183,6 +2302,7 @@ dc_contact_t* dc_get_contact (dc_context_t* context, uint32_t co
|
|||||||
*
|
*
|
||||||
* - **DC_IMEX_IMPORT_SELF_KEYS** (2) - Import private keys found in the directory given as `param1`.
|
* - **DC_IMEX_IMPORT_SELF_KEYS** (2) - Import private keys found in the directory given as `param1`.
|
||||||
* The last imported key is made the default keys unless its name contains the string `legacy`. Public keys are not imported.
|
* The last imported key is made the default keys unless its name contains the string `legacy`. Public keys are not imported.
|
||||||
|
* If `param1` is a filename, import the private key from the file and make it the default.
|
||||||
*
|
*
|
||||||
* While dc_imex() returns immediately, the started job may take a while,
|
* While dc_imex() returns immediately, the started job may take a while,
|
||||||
* you can stop it using dc_stop_ongoing_process(). During execution of the job,
|
* you can stop it using dc_stop_ongoing_process(). During execution of the job,
|
||||||
@@ -3906,16 +4026,17 @@ char* dc_msg_get_text (const dc_msg_t* msg);
|
|||||||
*/
|
*/
|
||||||
char* dc_msg_get_subject (const dc_msg_t* msg);
|
char* dc_msg_get_subject (const dc_msg_t* msg);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find out full path, file name and extension of the file associated with a
|
* Find out full path of the file associated with a message.
|
||||||
* message.
|
|
||||||
*
|
*
|
||||||
* Typically files are associated with images, videos, audios, documents.
|
* Typically files are associated with images, videos, audios, documents.
|
||||||
* Plain text messages do not have a file.
|
* Plain text messages do not have a file.
|
||||||
|
* File name may be mangled. To obtain the original attachment filename use dc_msg_get_filename().
|
||||||
*
|
*
|
||||||
* @memberof dc_msg_t
|
* @memberof dc_msg_t
|
||||||
* @param msg The message object.
|
* @param msg The message object.
|
||||||
* @return The full path, the file name, and the extension of the file associated with the message.
|
* @return The full path (with file name and extension) of the file associated with the message.
|
||||||
* If there is no file associated with the message, an empty string is returned.
|
* If there is no file associated with the message, an empty string is returned.
|
||||||
* NULL is never returned and the returned value must be released using dc_str_unref().
|
* NULL is never returned and the returned value must be released using dc_str_unref().
|
||||||
*/
|
*/
|
||||||
@@ -3923,14 +4044,13 @@ char* dc_msg_get_file (const dc_msg_t* msg);
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a base file name without the path. The base file name includes the extension; the path
|
* Get an original attachment filename, with extension but without the path. To get the full path,
|
||||||
* is not returned. To get the full path, use dc_msg_get_file().
|
* use dc_msg_get_file().
|
||||||
*
|
*
|
||||||
* @memberof dc_msg_t
|
* @memberof dc_msg_t
|
||||||
* @param msg The message object.
|
* @param msg The message object.
|
||||||
* @return The base file name plus the extension without part. If there is no file
|
* @return The attachment filename. If there is no file associated with the message, an empty string
|
||||||
* associated with the message, an empty string is returned. The returned
|
* is returned. The returned value must be released using dc_str_unref().
|
||||||
* value must be released using dc_str_unref().
|
|
||||||
*/
|
*/
|
||||||
char* dc_msg_get_filename (const dc_msg_t* msg);
|
char* dc_msg_get_filename (const dc_msg_t* msg);
|
||||||
|
|
||||||
@@ -5057,6 +5177,72 @@ int dc_provider_get_status (const dc_provider_t* prov
|
|||||||
void dc_provider_unref (dc_provider_t* provider);
|
void dc_provider_unref (dc_provider_t* provider);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an HTTP(S) GET response.
|
||||||
|
* This function can be used to download remote content for HTML emails.
|
||||||
|
*
|
||||||
|
* @memberof dc_context_t
|
||||||
|
* @param context The context object to take proxy settings from.
|
||||||
|
* @param url HTTP or HTTPS URL.
|
||||||
|
* @return The response must be released using dc_http_response_unref() after usage.
|
||||||
|
* NULL is returned on errors.
|
||||||
|
*/
|
||||||
|
dc_http_response_t* dc_get_http_response (const dc_context_t* context, const char* url);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class dc_http_response_t
|
||||||
|
*
|
||||||
|
* An object containing an HTTP(S) GET response.
|
||||||
|
* Created by dc_get_http_response().
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns HTTP response MIME type as a string, e.g. "text/plain" or "text/html".
|
||||||
|
*
|
||||||
|
* @memberof dc_http_response_t
|
||||||
|
* @param response HTTP response as returned by dc_get_http_response().
|
||||||
|
* @return The string which must be released using dc_str_unref() after usage. May be NULL.
|
||||||
|
*/
|
||||||
|
char* dc_http_response_get_mimetype (const dc_http_response_t* response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns HTTP response encoding, e.g. "utf-8".
|
||||||
|
*
|
||||||
|
* @memberof dc_http_response_t
|
||||||
|
* @param response HTTP response as returned by dc_get_http_response().
|
||||||
|
* @return The string which must be released using dc_str_unref() after usage. May be NULL.
|
||||||
|
*/
|
||||||
|
char* dc_http_response_get_encoding (const dc_http_response_t* response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns HTTP response contents.
|
||||||
|
*
|
||||||
|
* @memberof dc_http_response_t
|
||||||
|
* @param response HTTP response as returned by dc_get_http_response().
|
||||||
|
* @return The blob which must be released using dc_str_unref() after usage. NULL is never returned.
|
||||||
|
*/
|
||||||
|
uint8_t* dc_http_response_get_blob (const dc_http_response_t* response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns HTTP response content size.
|
||||||
|
*
|
||||||
|
* @memberof dc_http_response_t
|
||||||
|
* @param response HTTP response as returned by dc_get_http_response().
|
||||||
|
* @return The blob size.
|
||||||
|
*/
|
||||||
|
size_t dc_http_response_get_size (const dc_http_response_t* response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free an HTTP response object.
|
||||||
|
*
|
||||||
|
* @memberof dc_http_response_t
|
||||||
|
* @param response HTTP response as returned by dc_get_http_response().
|
||||||
|
*/
|
||||||
|
void dc_http_response_unref (const dc_http_response_t* response);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class dc_lot_t
|
* @class dc_lot_t
|
||||||
*
|
*
|
||||||
@@ -5534,7 +5720,6 @@ void dc_reactions_unref (dc_reactions_t* reactions);
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class dc_jsonrpc_instance_t
|
* @class dc_jsonrpc_instance_t
|
||||||
*
|
*
|
||||||
@@ -5583,6 +5768,18 @@ void dc_jsonrpc_request(dc_jsonrpc_instance_t* jsonrpc_instance, const char* req
|
|||||||
*/
|
*/
|
||||||
char* dc_jsonrpc_next_response(dc_jsonrpc_instance_t* jsonrpc_instance);
|
char* dc_jsonrpc_next_response(dc_jsonrpc_instance_t* jsonrpc_instance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a JSON-RPC call and return a response.
|
||||||
|
*
|
||||||
|
* @memberof dc_jsonrpc_instance_t
|
||||||
|
* @param jsonrpc_instance jsonrpc instance as returned from dc_jsonrpc_init().
|
||||||
|
* @param method JSON-RPC method name, e.g. `check_email_validity`.
|
||||||
|
* @param params JSON-RPC method parameters, e.g. `["alice@example.org"]`.
|
||||||
|
* @return JSON-RPC response as string, must be freed using dc_str_unref() after usage.
|
||||||
|
* On error, NULL is returned.
|
||||||
|
*/
|
||||||
|
char* dc_jsonrpc_blocking_call(dc_jsonrpc_instance_t* jsonrpc_instance, const char *method, const char *params);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class dc_event_emitter_t
|
* @class dc_event_emitter_t
|
||||||
*
|
*
|
||||||
@@ -5930,6 +6127,15 @@ void dc_event_unref(dc_event_t* event);
|
|||||||
#define DC_EVENT_MSG_READ 2015
|
#define DC_EVENT_MSG_READ 2015
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single message is deleted.
|
||||||
|
*
|
||||||
|
* @param data1 (int) chat_id
|
||||||
|
* @param data2 (int) msg_id
|
||||||
|
*/
|
||||||
|
#define DC_EVENT_MSG_DELETED 2016
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chat changed. The name or the image of a chat group was changed or members were added or removed.
|
* Chat changed. The name or the image of a chat group was changed or members were added or removed.
|
||||||
* Or the verify state of a chat has changed.
|
* Or the verify state of a chat has changed.
|
||||||
@@ -6108,6 +6314,7 @@ void dc_event_unref(dc_event_t* event);
|
|||||||
#define DC_KEY_GEN_DEFAULT 0
|
#define DC_KEY_GEN_DEFAULT 0
|
||||||
#define DC_KEY_GEN_RSA2048 1
|
#define DC_KEY_GEN_RSA2048 1
|
||||||
#define DC_KEY_GEN_ED25519 2
|
#define DC_KEY_GEN_ED25519 2
|
||||||
|
#define DC_KEY_GEN_RSA4096 3
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -7069,6 +7276,11 @@ void dc_event_unref(dc_event_t* event);
|
|||||||
/// `%1$s` will be replaced by name and address of the account.
|
/// `%1$s` will be replaced by name and address of the account.
|
||||||
#define DC_STR_BACKUP_TRANSFER_QR 162
|
#define DC_STR_BACKUP_TRANSFER_QR 162
|
||||||
|
|
||||||
|
/// "Account transferred to your second device."
|
||||||
|
///
|
||||||
|
/// Used as a device message after a successful backup transfer.
|
||||||
|
#define DC_STR_BACKUP_TRANSFER_MSG_BODY 163
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @}
|
* @}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -31,11 +31,12 @@ use deltachat::ephemeral::Timer as EphemeralTimer;
|
|||||||
use deltachat::imex::BackupProvider;
|
use deltachat::imex::BackupProvider;
|
||||||
use deltachat::key::DcKey;
|
use deltachat::key::DcKey;
|
||||||
use deltachat::message::MsgId;
|
use deltachat::message::MsgId;
|
||||||
|
use deltachat::net::read_url_blob;
|
||||||
use deltachat::qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg};
|
use deltachat::qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg};
|
||||||
use deltachat::reaction::{get_msg_reactions, send_reaction, Reactions};
|
use deltachat::reaction::{get_msg_reactions, send_reaction, Reactions};
|
||||||
use deltachat::stock_str::StockMessage;
|
use deltachat::stock_str::StockMessage;
|
||||||
use deltachat::stock_str::StockStrings;
|
use deltachat::stock_str::StockStrings;
|
||||||
use deltachat::webxdc::StatusUpdateSerial;
|
use deltachat::webxdc::{replace_webxdc, StatusUpdateSerial};
|
||||||
use deltachat::*;
|
use deltachat::*;
|
||||||
use deltachat::{accounts::Accounts, log::LogExt};
|
use deltachat::{accounts::Accounts, log::LogExt};
|
||||||
use num_traits::{FromPrimitive, ToPrimitive};
|
use num_traits::{FromPrimitive, ToPrimitive};
|
||||||
@@ -160,11 +161,30 @@ pub unsafe extern "C" fn dc_context_open(
|
|||||||
let ctx = &*context;
|
let ctx = &*context;
|
||||||
let passphrase = to_string_lossy(passphrase);
|
let passphrase = to_string_lossy(passphrase);
|
||||||
block_on(ctx.open(passphrase))
|
block_on(ctx.open(passphrase))
|
||||||
.log_err(ctx, "dc_context_open() failed")
|
.context("dc_context_open() failed")
|
||||||
|
.log_err(ctx)
|
||||||
.map(|b| b as libc::c_int)
|
.map(|b| b as libc::c_int)
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_context_change_passphrase(
|
||||||
|
context: *mut dc_context_t,
|
||||||
|
passphrase: *const libc::c_char,
|
||||||
|
) -> libc::c_int {
|
||||||
|
if context.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_context_change_passphrase()");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ctx = &*context;
|
||||||
|
let passphrase = to_string_lossy(passphrase);
|
||||||
|
block_on(ctx.change_passphrase(passphrase))
|
||||||
|
.context("dc_context_change_passphrase() failed")
|
||||||
|
.log_err(ctx)
|
||||||
|
.is_ok() as libc::c_int
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_context_is_open(context: *mut dc_context_t) -> libc::c_int {
|
pub unsafe extern "C" fn dc_context_is_open(context: *mut dc_context_t) -> libc::c_int {
|
||||||
if context.is_null() {
|
if context.is_null() {
|
||||||
@@ -216,16 +236,18 @@ pub unsafe extern "C" fn dc_set_config(
|
|||||||
if key.starts_with("ui.") {
|
if key.starts_with("ui.") {
|
||||||
ctx.set_ui_config(&key, value.as_deref())
|
ctx.set_ui_config(&key, value.as_deref())
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("Can't set {key} to {value:?}"))
|
.with_context(|| format!("dc_set_config failed: Can't set {key} to {value:?}"))
|
||||||
.log_err(ctx, "dc_set_config() failed")
|
.log_err(ctx)
|
||||||
.is_ok() as libc::c_int
|
.is_ok() as libc::c_int
|
||||||
} else {
|
} else {
|
||||||
match config::Config::from_str(&key) {
|
match config::Config::from_str(&key) {
|
||||||
Ok(key) => ctx
|
Ok(key) => ctx
|
||||||
.set_config(key, value.as_deref())
|
.set_config(key, value.as_deref())
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("Can't set {key} to {value:?}"))
|
.with_context(|| {
|
||||||
.log_err(ctx, "dc_set_config() failed")
|
format!("dc_set_config() failed: Can't set {key} to {value:?}")
|
||||||
|
})
|
||||||
|
.log_err(ctx)
|
||||||
.is_ok() as libc::c_int,
|
.is_ok() as libc::c_int,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
warn!(ctx, "dc_set_config(): invalid key");
|
warn!(ctx, "dc_set_config(): invalid key");
|
||||||
@@ -253,7 +275,8 @@ pub unsafe extern "C" fn dc_get_config(
|
|||||||
if key.starts_with("ui.") {
|
if key.starts_with("ui.") {
|
||||||
ctx.get_ui_config(&key)
|
ctx.get_ui_config(&key)
|
||||||
.await
|
.await
|
||||||
.log_err(ctx, "Can't get ui-config")
|
.context("Can't get ui-config")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.strdup()
|
.strdup()
|
||||||
@@ -262,7 +285,8 @@ pub unsafe extern "C" fn dc_get_config(
|
|||||||
Ok(key) => ctx
|
Ok(key) => ctx
|
||||||
.get_config(key)
|
.get_config(key)
|
||||||
.await
|
.await
|
||||||
.log_err(ctx, "Can't get config")
|
.context("Can't get config")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.strdup(),
|
.strdup(),
|
||||||
@@ -414,7 +438,8 @@ pub unsafe extern "C" fn dc_get_oauth2_url(
|
|||||||
block_on(async move {
|
block_on(async move {
|
||||||
match oauth2::get_oauth2_url(ctx, &addr, &redirect)
|
match oauth2::get_oauth2_url(ctx, &addr, &redirect)
|
||||||
.await
|
.await
|
||||||
.log_err(ctx, "dc_get_oauth2_url failed")
|
.context("dc_get_oauth2_url failed")
|
||||||
|
.log_err(ctx)
|
||||||
{
|
{
|
||||||
Ok(Some(res)) => res.strdup(),
|
Ok(Some(res)) => res.strdup(),
|
||||||
Ok(None) | Err(_) => ptr::null_mut(),
|
Ok(None) | Err(_) => ptr::null_mut(),
|
||||||
@@ -423,7 +448,12 @@ pub unsafe extern "C" fn dc_get_oauth2_url(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_configure(ctx: Context) {
|
fn spawn_configure(ctx: Context) {
|
||||||
spawn(async move { ctx.configure().await.log_err(&ctx, "Configure failed") });
|
spawn(async move {
|
||||||
|
ctx.configure()
|
||||||
|
.await
|
||||||
|
.context("Configure failed")
|
||||||
|
.log_err(&ctx)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -448,7 +478,8 @@ pub unsafe extern "C" fn dc_is_configured(context: *mut dc_context_t) -> libc::c
|
|||||||
block_on(async move {
|
block_on(async move {
|
||||||
ctx.is_configured()
|
ctx.is_configured()
|
||||||
.await
|
.await
|
||||||
.log_err(ctx, "failed to get configured state")
|
.context("failed to get configured state")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or_default() as libc::c_int
|
.unwrap_or_default() as libc::c_int
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -514,6 +545,7 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int
|
|||||||
EventType::MsgDelivered { .. } => 2010,
|
EventType::MsgDelivered { .. } => 2010,
|
||||||
EventType::MsgFailed { .. } => 2012,
|
EventType::MsgFailed { .. } => 2012,
|
||||||
EventType::MsgRead { .. } => 2015,
|
EventType::MsgRead { .. } => 2015,
|
||||||
|
EventType::MsgDeleted { .. } => 2016,
|
||||||
EventType::ChatModified(_) => 2020,
|
EventType::ChatModified(_) => 2020,
|
||||||
EventType::ChatEphemeralTimerModified { .. } => 2021,
|
EventType::ChatEphemeralTimerModified { .. } => 2021,
|
||||||
EventType::ContactsChanged(_) => 2030,
|
EventType::ContactsChanged(_) => 2030,
|
||||||
@@ -561,6 +593,7 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
|
|||||||
| EventType::MsgDelivered { chat_id, .. }
|
| EventType::MsgDelivered { chat_id, .. }
|
||||||
| EventType::MsgFailed { chat_id, .. }
|
| EventType::MsgFailed { chat_id, .. }
|
||||||
| EventType::MsgRead { chat_id, .. }
|
| EventType::MsgRead { chat_id, .. }
|
||||||
|
| EventType::MsgDeleted { chat_id, .. }
|
||||||
| EventType::ChatModified(chat_id)
|
| EventType::ChatModified(chat_id)
|
||||||
| EventType::ChatEphemeralTimerModified { chat_id, .. } => chat_id.to_u32() as libc::c_int,
|
| EventType::ChatEphemeralTimerModified { chat_id, .. } => chat_id.to_u32() as libc::c_int,
|
||||||
EventType::ContactsChanged(id) | EventType::LocationChanged(id) => {
|
EventType::ContactsChanged(id) | EventType::LocationChanged(id) => {
|
||||||
@@ -618,7 +651,8 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
|
|||||||
| EventType::IncomingMsg { msg_id, .. }
|
| EventType::IncomingMsg { msg_id, .. }
|
||||||
| EventType::MsgDelivered { msg_id, .. }
|
| EventType::MsgDelivered { msg_id, .. }
|
||||||
| EventType::MsgFailed { msg_id, .. }
|
| EventType::MsgFailed { msg_id, .. }
|
||||||
| EventType::MsgRead { msg_id, .. } => msg_id.to_u32() as libc::c_int,
|
| EventType::MsgRead { msg_id, .. }
|
||||||
|
| EventType::MsgDeleted { msg_id, .. } => msg_id.to_u32() as libc::c_int,
|
||||||
EventType::SecurejoinInviterProgress { progress, .. }
|
EventType::SecurejoinInviterProgress { progress, .. }
|
||||||
| EventType::SecurejoinJoinerProgress { progress, .. } => *progress as libc::c_int,
|
| EventType::SecurejoinJoinerProgress { progress, .. } => *progress as libc::c_int,
|
||||||
EventType::ChatEphemeralTimerModified { timer, .. } => timer.to_u32() as libc::c_int,
|
EventType::ChatEphemeralTimerModified { timer, .. } => timer.to_u32() as libc::c_int,
|
||||||
@@ -661,6 +695,7 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
|
|||||||
| EventType::MsgDelivered { .. }
|
| EventType::MsgDelivered { .. }
|
||||||
| EventType::MsgFailed { .. }
|
| EventType::MsgFailed { .. }
|
||||||
| EventType::MsgRead { .. }
|
| EventType::MsgRead { .. }
|
||||||
|
| EventType::MsgDeleted { .. }
|
||||||
| EventType::ChatModified(_)
|
| EventType::ChatModified(_)
|
||||||
| EventType::ContactsChanged(_)
|
| EventType::ContactsChanged(_)
|
||||||
| EventType::LocationChanged(_)
|
| EventType::LocationChanged(_)
|
||||||
@@ -790,7 +825,8 @@ pub unsafe extern "C" fn dc_preconfigure_keypair(
|
|||||||
key::store_self_keypair(ctx, &keypair, key::KeyPairUse::Default).await?;
|
key::store_self_keypair(ctx, &keypair, key::KeyPairUse::Default).await?;
|
||||||
Ok::<_, anyhow::Error>(1)
|
Ok::<_, anyhow::Error>(1)
|
||||||
})
|
})
|
||||||
.log_err(ctx, "Failed to save keypair")
|
.context("Failed to save keypair")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -817,7 +853,8 @@ pub unsafe extern "C" fn dc_get_chatlist(
|
|||||||
block_on(async move {
|
block_on(async move {
|
||||||
match chatlist::Chatlist::try_load(ctx, flags as usize, qs.as_deref(), qi)
|
match chatlist::Chatlist::try_load(ctx, flags as usize, qs.as_deref(), qi)
|
||||||
.await
|
.await
|
||||||
.log_err(ctx, "Failed to get chatlist")
|
.context("Failed to get chatlist")
|
||||||
|
.log_err(ctx)
|
||||||
{
|
{
|
||||||
Ok(list) => {
|
Ok(list) => {
|
||||||
let ffi_list = ChatlistWrapper { context, list };
|
let ffi_list = ChatlistWrapper { context, list };
|
||||||
@@ -842,7 +879,8 @@ pub unsafe extern "C" fn dc_create_chat_by_contact_id(
|
|||||||
block_on(async move {
|
block_on(async move {
|
||||||
ChatId::create_for_contact(ctx, ContactId::new(contact_id))
|
ChatId::create_for_contact(ctx, ContactId::new(contact_id))
|
||||||
.await
|
.await
|
||||||
.log_err(ctx, "Failed to create chat from contact_id")
|
.context("Failed to create chat from contact_id")
|
||||||
|
.log_err(ctx)
|
||||||
.map(|id| id.to_u32())
|
.map(|id| id.to_u32())
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
})
|
})
|
||||||
@@ -862,7 +900,8 @@ pub unsafe extern "C" fn dc_get_chat_id_by_contact_id(
|
|||||||
block_on(async move {
|
block_on(async move {
|
||||||
ChatId::lookup_by_contact(ctx, ContactId::new(contact_id))
|
ChatId::lookup_by_contact(ctx, ContactId::new(contact_id))
|
||||||
.await
|
.await
|
||||||
.log_err(ctx, "Failed to get chat for contact_id")
|
.context("Failed to get chat for contact_id")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or_default() // unwraps the Result
|
.unwrap_or_default() // unwraps the Result
|
||||||
.map(|id| id.to_u32())
|
.map(|id| id.to_u32())
|
||||||
.unwrap_or(0) // unwraps the Option
|
.unwrap_or(0) // unwraps the Option
|
||||||
@@ -1004,7 +1043,8 @@ pub unsafe extern "C" fn dc_get_msg_reactions(
|
|||||||
let ctx = &*context;
|
let ctx = &*context;
|
||||||
|
|
||||||
let reactions = if let Ok(reactions) = block_on(get_msg_reactions(ctx, MsgId::new(msg_id)))
|
let reactions = if let Ok(reactions) = block_on(get_msg_reactions(ctx, MsgId::new(msg_id)))
|
||||||
.log_err(ctx, "failed dc_get_msg_reactions() call")
|
.context("failed dc_get_msg_reactions() call")
|
||||||
|
.log_err(ctx)
|
||||||
{
|
{
|
||||||
reactions
|
reactions
|
||||||
} else {
|
} else {
|
||||||
@@ -1032,7 +1072,8 @@ pub unsafe extern "C" fn dc_send_webxdc_status_update(
|
|||||||
&to_string_lossy(json),
|
&to_string_lossy(json),
|
||||||
&to_string_lossy(descr),
|
&to_string_lossy(descr),
|
||||||
))
|
))
|
||||||
.log_err(ctx, "Failed to send webxdc update")
|
.context("Failed to send webxdc update")
|
||||||
|
.log_err(ctx)
|
||||||
.is_ok() as libc::c_int
|
.is_ok() as libc::c_int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1056,6 +1097,32 @@ pub unsafe extern "C" fn dc_get_webxdc_status_updates(
|
|||||||
.strdup()
|
.strdup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_replace_webxdc(
|
||||||
|
context: *mut dc_context_t,
|
||||||
|
msg_id: u32,
|
||||||
|
blob: *const u8,
|
||||||
|
n: libc::size_t,
|
||||||
|
) {
|
||||||
|
if context.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_replace_webxdc()");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg_id = MsgId::new(msg_id);
|
||||||
|
let blob_slice = std::slice::from_raw_parts(blob, n);
|
||||||
|
|
||||||
|
let ctx = &*context;
|
||||||
|
|
||||||
|
block_on(async move {
|
||||||
|
replace_webxdc(ctx, msg_id, blob_slice)
|
||||||
|
.await
|
||||||
|
.context("Failed to replace WebXDC")
|
||||||
|
.log_err(ctx)
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_set_draft(
|
pub unsafe extern "C" fn dc_set_draft(
|
||||||
context: *mut dc_context_t,
|
context: *mut dc_context_t,
|
||||||
@@ -1219,6 +1286,30 @@ pub unsafe extern "C" fn dc_get_fresh_msg_cnt(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_get_similar_chatlist(
|
||||||
|
context: *mut dc_context_t,
|
||||||
|
chat_id: u32,
|
||||||
|
) -> *mut dc_chatlist_t {
|
||||||
|
if context.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_get_similar_chatlist()");
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
let ctx = &*context;
|
||||||
|
|
||||||
|
let chat_id = ChatId::new(chat_id);
|
||||||
|
match block_on(chat_id.get_similar_chatlist(ctx))
|
||||||
|
.context("failed to get similar chatlist")
|
||||||
|
.log_err(ctx)
|
||||||
|
{
|
||||||
|
Ok(list) => {
|
||||||
|
let ffi_list = ChatlistWrapper { context, list };
|
||||||
|
Box::into_raw(Box::new(ffi_list))
|
||||||
|
}
|
||||||
|
Err(_) => ptr::null_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_estimate_deletion_cnt(
|
pub unsafe extern "C" fn dc_estimate_deletion_cnt(
|
||||||
context: *mut dc_context_t,
|
context: *mut dc_context_t,
|
||||||
@@ -1251,7 +1342,8 @@ pub unsafe extern "C" fn dc_get_fresh_msgs(
|
|||||||
let arr = dc_array_t::from(
|
let arr = dc_array_t::from(
|
||||||
ctx.get_fresh_msgs()
|
ctx.get_fresh_msgs()
|
||||||
.await
|
.await
|
||||||
.log_err(ctx, "Failed to get fresh messages")
|
.context("Failed to get fresh messages")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|msg_id| msg_id.to_u32())
|
.map(|msg_id| msg_id.to_u32())
|
||||||
@@ -1261,6 +1353,50 @@ pub unsafe extern "C" fn dc_get_fresh_msgs(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_get_next_msgs(context: *mut dc_context_t) -> *mut dc_array::dc_array_t {
|
||||||
|
if context.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_get_next_msgs()");
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
let ctx = &*context;
|
||||||
|
|
||||||
|
let msg_ids = block_on(ctx.get_next_msgs())
|
||||||
|
.context("failed to get next messages")
|
||||||
|
.log_err(ctx)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let arr = dc_array_t::from(
|
||||||
|
msg_ids
|
||||||
|
.iter()
|
||||||
|
.map(|msg_id| msg_id.to_u32())
|
||||||
|
.collect::<Vec<u32>>(),
|
||||||
|
);
|
||||||
|
Box::into_raw(Box::new(arr))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_wait_next_msgs(
|
||||||
|
context: *mut dc_context_t,
|
||||||
|
) -> *mut dc_array::dc_array_t {
|
||||||
|
if context.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_wait_next_msgs()");
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
let ctx = &*context;
|
||||||
|
|
||||||
|
let msg_ids = block_on(ctx.wait_next_msgs())
|
||||||
|
.context("failed to wait for next messages")
|
||||||
|
.log_err(ctx)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let arr = dc_array_t::from(
|
||||||
|
msg_ids
|
||||||
|
.iter()
|
||||||
|
.map(|msg_id| msg_id.to_u32())
|
||||||
|
.collect::<Vec<u32>>(),
|
||||||
|
);
|
||||||
|
Box::into_raw(Box::new(arr))
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_marknoticed_chat(context: *mut dc_context_t, chat_id: u32) {
|
pub unsafe extern "C" fn dc_marknoticed_chat(context: *mut dc_context_t, chat_id: u32) {
|
||||||
if context.is_null() {
|
if context.is_null() {
|
||||||
@@ -1272,7 +1408,8 @@ pub unsafe extern "C" fn dc_marknoticed_chat(context: *mut dc_context_t, chat_id
|
|||||||
block_on(async move {
|
block_on(async move {
|
||||||
chat::marknoticed_chat(ctx, ChatId::new(chat_id))
|
chat::marknoticed_chat(ctx, ChatId::new(chat_id))
|
||||||
.await
|
.await
|
||||||
.log_err(ctx, "Failed marknoticed chat")
|
.context("Failed marknoticed chat")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or(())
|
.unwrap_or(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1414,7 +1551,8 @@ pub unsafe extern "C" fn dc_set_chat_visibility(
|
|||||||
ChatId::new(chat_id)
|
ChatId::new(chat_id)
|
||||||
.set_visibility(ctx, visibility)
|
.set_visibility(ctx, visibility)
|
||||||
.await
|
.await
|
||||||
.log_err(ctx, "Failed setting chat visibility")
|
.context("Failed setting chat visibility")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or(())
|
.unwrap_or(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1427,12 +1565,10 @@ pub unsafe extern "C" fn dc_delete_chat(context: *mut dc_context_t, chat_id: u32
|
|||||||
}
|
}
|
||||||
let ctx = &*context;
|
let ctx = &*context;
|
||||||
|
|
||||||
block_on(async move {
|
block_on(ChatId::new(chat_id).delete(ctx))
|
||||||
ChatId::new(chat_id)
|
.context("Failed chat delete")
|
||||||
.delete(ctx)
|
.log_err(ctx)
|
||||||
.await
|
.ok();
|
||||||
.ok_or_log_msg(ctx, "Failed chat delete");
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -1447,7 +1583,9 @@ pub unsafe extern "C" fn dc_block_chat(context: *mut dc_context_t, chat_id: u32)
|
|||||||
ChatId::new(chat_id)
|
ChatId::new(chat_id)
|
||||||
.block(ctx)
|
.block(ctx)
|
||||||
.await
|
.await
|
||||||
.ok_or_log_msg(ctx, "Failed chat block");
|
.context("Failed chat block")
|
||||||
|
.log_err(ctx)
|
||||||
|
.ok();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1463,7 +1601,9 @@ pub unsafe extern "C" fn dc_accept_chat(context: *mut dc_context_t, chat_id: u32
|
|||||||
ChatId::new(chat_id)
|
ChatId::new(chat_id)
|
||||||
.accept(ctx)
|
.accept(ctx)
|
||||||
.await
|
.await
|
||||||
.ok_or_log_msg(ctx, "Failed chat accept");
|
.context("Failed chat accept")
|
||||||
|
.log_err(ctx)
|
||||||
|
.ok();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1561,7 +1701,8 @@ pub unsafe extern "C" fn dc_create_group_chat(
|
|||||||
block_on(async move {
|
block_on(async move {
|
||||||
chat::create_group_chat(ctx, protect, &to_string_lossy(name))
|
chat::create_group_chat(ctx, protect, &to_string_lossy(name))
|
||||||
.await
|
.await
|
||||||
.log_err(ctx, "Failed to create group chat")
|
.context("Failed to create group chat")
|
||||||
|
.log_err(ctx)
|
||||||
.map(|id| id.to_u32())
|
.map(|id| id.to_u32())
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
})
|
})
|
||||||
@@ -1575,7 +1716,8 @@ pub unsafe extern "C" fn dc_create_broadcast_list(context: *mut dc_context_t) ->
|
|||||||
}
|
}
|
||||||
let ctx = &*context;
|
let ctx = &*context;
|
||||||
block_on(chat::create_broadcast_list(ctx))
|
block_on(chat::create_broadcast_list(ctx))
|
||||||
.log_err(ctx, "Failed to create broadcast list")
|
.context("Failed to create broadcast list")
|
||||||
|
.log_err(ctx)
|
||||||
.map(|id| id.to_u32())
|
.map(|id| id.to_u32())
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
@@ -1597,7 +1739,8 @@ pub unsafe extern "C" fn dc_is_contact_in_chat(
|
|||||||
ChatId::new(chat_id),
|
ChatId::new(chat_id),
|
||||||
ContactId::new(contact_id),
|
ContactId::new(contact_id),
|
||||||
))
|
))
|
||||||
.log_err(ctx, "is_contact_in_chat failed")
|
.context("is_contact_in_chat failed")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or_default() as libc::c_int
|
.unwrap_or_default() as libc::c_int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1618,7 +1761,8 @@ pub unsafe extern "C" fn dc_add_contact_to_chat(
|
|||||||
ChatId::new(chat_id),
|
ChatId::new(chat_id),
|
||||||
ContactId::new(contact_id),
|
ContactId::new(contact_id),
|
||||||
))
|
))
|
||||||
.log_err(ctx, "Failed to add contact")
|
.context("Failed to add contact")
|
||||||
|
.log_err(ctx)
|
||||||
.is_ok() as libc::c_int
|
.is_ok() as libc::c_int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1639,7 +1783,8 @@ pub unsafe extern "C" fn dc_remove_contact_from_chat(
|
|||||||
ChatId::new(chat_id),
|
ChatId::new(chat_id),
|
||||||
ContactId::new(contact_id),
|
ContactId::new(contact_id),
|
||||||
))
|
))
|
||||||
.log_err(ctx, "Failed to remove contact")
|
.context("Failed to remove contact")
|
||||||
|
.log_err(ctx)
|
||||||
.is_ok() as libc::c_int
|
.is_ok() as libc::c_int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1758,7 +1903,8 @@ pub unsafe extern "C" fn dc_get_chat_ephemeral_timer(
|
|||||||
// ignored when ephemeral timer value is used to construct
|
// ignored when ephemeral timer value is used to construct
|
||||||
// message headers.
|
// message headers.
|
||||||
block_on(async move { ChatId::new(chat_id).get_ephemeral_timer(ctx).await })
|
block_on(async move { ChatId::new(chat_id).get_ephemeral_timer(ctx).await })
|
||||||
.log_err(ctx, "Failed to get ephemeral timer")
|
.context("Failed to get ephemeral timer")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_u32()
|
.to_u32()
|
||||||
}
|
}
|
||||||
@@ -1779,7 +1925,8 @@ pub unsafe extern "C" fn dc_set_chat_ephemeral_timer(
|
|||||||
ChatId::new(chat_id)
|
ChatId::new(chat_id)
|
||||||
.set_ephemeral_timer(ctx, EphemeralTimer::from_u32(timer))
|
.set_ephemeral_timer(ctx, EphemeralTimer::from_u32(timer))
|
||||||
.await
|
.await
|
||||||
.log_err(ctx, "Failed to set ephemeral timer")
|
.context("Failed to set ephemeral timer")
|
||||||
|
.log_err(ctx)
|
||||||
.is_ok() as libc::c_int
|
.is_ok() as libc::c_int
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1794,13 +1941,10 @@ pub unsafe extern "C" fn dc_get_msg_info(
|
|||||||
return "".strdup();
|
return "".strdup();
|
||||||
}
|
}
|
||||||
let ctx = &*context;
|
let ctx = &*context;
|
||||||
|
let msg_id = MsgId::new(msg_id);
|
||||||
block_on(async move {
|
block_on(msg_id.get_info(ctx))
|
||||||
message::get_msg_info(ctx, MsgId::new(msg_id))
|
.unwrap_or_log_default(ctx, "failed to get msg id")
|
||||||
.await
|
.strdup()
|
||||||
.unwrap_or_log_default(ctx, "failed to get msg id")
|
|
||||||
.strdup()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -1855,7 +1999,8 @@ pub unsafe extern "C" fn dc_delete_msgs(
|
|||||||
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
|
let msg_ids = convert_and_prune_message_ids(msg_ids, msg_cnt);
|
||||||
|
|
||||||
block_on(message::delete_msgs(ctx, &msg_ids))
|
block_on(message::delete_msgs(ctx, &msg_ids))
|
||||||
.log_err(ctx, "failed dc_delete_msgs() call")
|
.context("failed dc_delete_msgs() call")
|
||||||
|
.log_err(ctx)
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1919,7 +2064,8 @@ pub unsafe extern "C" fn dc_markseen_msgs(
|
|||||||
let ctx = &*context;
|
let ctx = &*context;
|
||||||
|
|
||||||
block_on(message::markseen_msgs(ctx, msg_ids))
|
block_on(message::markseen_msgs(ctx, msg_ids))
|
||||||
.log_err(ctx, "failed dc_markseen_msgs() call")
|
.context("failed dc_markseen_msgs() call")
|
||||||
|
.log_err(ctx)
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1961,7 +2107,8 @@ pub unsafe extern "C" fn dc_download_full_msg(context: *mut dc_context_t, msg_id
|
|||||||
}
|
}
|
||||||
let ctx = &*context;
|
let ctx = &*context;
|
||||||
block_on(MsgId::new(msg_id).download_full(ctx))
|
block_on(MsgId::new(msg_id).download_full(ctx))
|
||||||
.log_err(ctx, "Failed to download message fully.")
|
.context("Failed to download message fully.")
|
||||||
|
.log_err(ctx)
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2009,7 +2156,8 @@ pub unsafe extern "C" fn dc_create_contact(
|
|||||||
let name = to_string_lossy(name);
|
let name = to_string_lossy(name);
|
||||||
|
|
||||||
block_on(Contact::create(ctx, &name, &to_string_lossy(addr)))
|
block_on(Contact::create(ctx, &name, &to_string_lossy(addr)))
|
||||||
.log_err(ctx, "Cannot create contact")
|
.context("Cannot create contact")
|
||||||
|
.log_err(ctx)
|
||||||
.map(|id| id.to_u32())
|
.map(|id| id.to_u32())
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
@@ -2086,7 +2234,8 @@ pub unsafe extern "C" fn dc_get_blocked_contacts(
|
|||||||
Box::into_raw(Box::new(dc_array_t::from(
|
Box::into_raw(Box::new(dc_array_t::from(
|
||||||
Contact::get_all_blocked(ctx)
|
Contact::get_all_blocked(ctx)
|
||||||
.await
|
.await
|
||||||
.log_err(ctx, "Can't get blocked contacts")
|
.context("Can't get blocked contacts")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|id| id.to_u32())
|
.map(|id| id.to_u32())
|
||||||
@@ -2111,11 +2260,15 @@ pub unsafe extern "C" fn dc_block_contact(
|
|||||||
if block == 0 {
|
if block == 0 {
|
||||||
Contact::unblock(ctx, contact_id)
|
Contact::unblock(ctx, contact_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_log_msg(ctx, "Can't unblock contact");
|
.context("Can't unblock contact")
|
||||||
|
.log_err(ctx)
|
||||||
|
.ok();
|
||||||
} else {
|
} else {
|
||||||
Contact::block(ctx, contact_id)
|
Contact::block(ctx, contact_id)
|
||||||
.await
|
.await
|
||||||
.ok_or_log_msg(ctx, "Can't block contact");
|
.context("Can't block contact")
|
||||||
|
.log_err(ctx)
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2188,7 +2341,8 @@ fn spawn_imex(ctx: Context, what: imex::ImexMode, param1: String, passphrase: Op
|
|||||||
spawn(async move {
|
spawn(async move {
|
||||||
imex::imex(&ctx, what, param1.as_ref(), passphrase)
|
imex::imex(&ctx, what, param1.as_ref(), passphrase)
|
||||||
.await
|
.await
|
||||||
.log_err(&ctx, "IMEX failed")
|
.context("IMEX failed")
|
||||||
|
.log_err(&ctx)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2374,7 +2528,8 @@ pub unsafe extern "C" fn dc_join_securejoin(
|
|||||||
securejoin::join_securejoin(ctx, &to_string_lossy(qr))
|
securejoin::join_securejoin(ctx, &to_string_lossy(qr))
|
||||||
.await
|
.await
|
||||||
.map(|chatid| chatid.to_u32())
|
.map(|chatid| chatid.to_u32())
|
||||||
.log_err(ctx, "failed dc_join_securejoin() call")
|
.context("failed dc_join_securejoin() call")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -2396,7 +2551,8 @@ pub unsafe extern "C" fn dc_send_locations_to_chat(
|
|||||||
ChatId::new(chat_id),
|
ChatId::new(chat_id),
|
||||||
seconds as i64,
|
seconds as i64,
|
||||||
))
|
))
|
||||||
.log_err(ctx, "Failed dc_send_locations_to_chat()")
|
.context("Failed dc_send_locations_to_chat()")
|
||||||
|
.log_err(ctx)
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2479,7 +2635,8 @@ pub unsafe extern "C" fn dc_delete_all_locations(context: *mut dc_context_t) {
|
|||||||
block_on(async move {
|
block_on(async move {
|
||||||
location::delete_all(ctx)
|
location::delete_all(ctx)
|
||||||
.await
|
.await
|
||||||
.log_err(ctx, "Failed to delete locations")
|
.context("Failed to delete locations")
|
||||||
|
.log_err(ctx)
|
||||||
.ok()
|
.ok()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2763,7 +2920,8 @@ pub unsafe extern "C" fn dc_chatlist_get_summary(
|
|||||||
.list
|
.list
|
||||||
.get_summary(ctx, index, maybe_chat)
|
.get_summary(ctx, index, maybe_chat)
|
||||||
.await
|
.await
|
||||||
.log_err(ctx, "get_summary failed")
|
.context("get_summary failed")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
Box::into_raw(Box::new(summary.into()))
|
Box::into_raw(Box::new(summary.into()))
|
||||||
})
|
})
|
||||||
@@ -2791,7 +2949,8 @@ pub unsafe extern "C" fn dc_chatlist_get_summary2(
|
|||||||
msg_id,
|
msg_id,
|
||||||
None,
|
None,
|
||||||
))
|
))
|
||||||
.log_err(ctx, "get_summary2 failed")
|
.context("get_summary2 failed")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
Box::into_raw(Box::new(summary.into()))
|
Box::into_raw(Box::new(summary.into()))
|
||||||
}
|
}
|
||||||
@@ -2974,7 +3133,8 @@ pub unsafe extern "C" fn dc_chat_can_send(chat: *mut dc_chat_t) -> libc::c_int {
|
|||||||
let ffi_chat = &*chat;
|
let ffi_chat = &*chat;
|
||||||
let ctx = &*ffi_chat.context;
|
let ctx = &*ffi_chat.context;
|
||||||
block_on(ffi_chat.chat.can_send(ctx))
|
block_on(ffi_chat.chat.can_send(ctx))
|
||||||
.log_err(ctx, "can_send failed")
|
.context("can_send failed")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or_default() as libc::c_int
|
.unwrap_or_default() as libc::c_int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3209,7 +3369,7 @@ pub unsafe extern "C" fn dc_msg_get_text(msg: *mut dc_msg_t) -> *mut libc::c_cha
|
|||||||
return "".strdup();
|
return "".strdup();
|
||||||
}
|
}
|
||||||
let ffi_msg = &*msg;
|
let ffi_msg = &*msg;
|
||||||
ffi_msg.message.get_text().unwrap_or_default().strdup()
|
ffi_msg.message.get_text().strdup()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -3419,7 +3579,8 @@ pub unsafe extern "C" fn dc_msg_get_summary(
|
|||||||
let ctx = &*ffi_msg.context;
|
let ctx = &*ffi_msg.context;
|
||||||
|
|
||||||
let summary = block_on(ffi_msg.message.get_summary(ctx, maybe_chat))
|
let summary = block_on(ffi_msg.message.get_summary(ctx, maybe_chat))
|
||||||
.log_err(ctx, "dc_msg_get_summary failed")
|
.context("dc_msg_get_summary failed")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
Box::into_raw(Box::new(summary.into()))
|
Box::into_raw(Box::new(summary.into()))
|
||||||
}
|
}
|
||||||
@@ -3437,7 +3598,8 @@ pub unsafe extern "C" fn dc_msg_get_summarytext(
|
|||||||
let ctx = &*ffi_msg.context;
|
let ctx = &*ffi_msg.context;
|
||||||
|
|
||||||
let summary = block_on(ffi_msg.message.get_summary(ctx, None))
|
let summary = block_on(ffi_msg.message.get_summary(ctx, None))
|
||||||
.log_err(ctx, "dc_msg_get_summarytext failed")
|
.context("dc_msg_get_summarytext failed")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
match usize::try_from(approx_characters) {
|
match usize::try_from(approx_characters) {
|
||||||
Ok(chars) => summary.truncated_text(chars).strdup(),
|
Ok(chars) => summary.truncated_text(chars).strdup(),
|
||||||
@@ -3592,7 +3754,7 @@ pub unsafe extern "C" fn dc_msg_set_text(msg: *mut dc_msg_t, text: *const libc::
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let ffi_msg = &mut *msg;
|
let ffi_msg = &mut *msg;
|
||||||
ffi_msg.message.set_text(to_opt_string_lossy(text))
|
ffi_msg.message.set_text(to_string_lossy(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -3704,7 +3866,9 @@ pub unsafe extern "C" fn dc_msg_latefiling_mediasize(
|
|||||||
.message
|
.message
|
||||||
.latefiling_mediasize(ctx, width, height, duration)
|
.latefiling_mediasize(ctx, width, height, duration)
|
||||||
})
|
})
|
||||||
.ok_or_log_msg(ctx, "Cannot set media size");
|
.context("Cannot set media size")
|
||||||
|
.log_err(ctx)
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -3743,7 +3907,8 @@ pub unsafe extern "C" fn dc_msg_set_quote(msg: *mut dc_msg_t, quote: *const dc_m
|
|||||||
.message
|
.message
|
||||||
.set_quote(&*ffi_msg.context, quote_msg)
|
.set_quote(&*ffi_msg.context, quote_msg)
|
||||||
.await
|
.await
|
||||||
.log_err(&*ffi_msg.context, "failed to set quote")
|
.context("failed to set quote")
|
||||||
|
.log_err(&*ffi_msg.context)
|
||||||
.ok();
|
.ok();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -3774,7 +3939,8 @@ pub unsafe extern "C" fn dc_msg_get_quoted_msg(msg: *const dc_msg_t) -> *mut dc_
|
|||||||
.message
|
.message
|
||||||
.quoted_message(context)
|
.quoted_message(context)
|
||||||
.await
|
.await
|
||||||
.log_err(context, "failed to get quoted message")
|
.context("failed to get quoted message")
|
||||||
|
.log_err(context)
|
||||||
.unwrap_or(None)
|
.unwrap_or(None)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3797,7 +3963,8 @@ pub unsafe extern "C" fn dc_msg_get_parent(msg: *const dc_msg_t) -> *mut dc_msg_
|
|||||||
.message
|
.message
|
||||||
.parent(context)
|
.parent(context)
|
||||||
.await
|
.await
|
||||||
.log_err(context, "failed to get parent message")
|
.context("failed to get parent message")
|
||||||
|
.log_err(context)
|
||||||
.unwrap_or(None)
|
.unwrap_or(None)
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3988,7 +4155,8 @@ pub unsafe extern "C" fn dc_contact_is_verified(contact: *mut dc_contact_t) -> l
|
|||||||
let ctx = &*ffi_contact.context;
|
let ctx = &*ffi_contact.context;
|
||||||
|
|
||||||
block_on(ffi_contact.contact.is_verified(ctx))
|
block_on(ffi_contact.contact.is_verified(ctx))
|
||||||
.log_err(ctx, "is_verified failed")
|
.context("is_verified failed")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or_default() as libc::c_int
|
.unwrap_or_default() as libc::c_int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4003,7 +4171,8 @@ pub unsafe extern "C" fn dc_contact_get_verifier_addr(
|
|||||||
let ffi_contact = &*contact;
|
let ffi_contact = &*contact;
|
||||||
let ctx = &*ffi_contact.context;
|
let ctx = &*ffi_contact.context;
|
||||||
block_on(ffi_contact.contact.get_verifier_addr(ctx))
|
block_on(ffi_contact.contact.get_verifier_addr(ctx))
|
||||||
.log_err(ctx, "failed to get verifier for contact")
|
.context("failed to get verifier for contact")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.strdup()
|
.strdup()
|
||||||
}
|
}
|
||||||
@@ -4017,7 +4186,8 @@ pub unsafe extern "C" fn dc_contact_get_verifier_id(contact: *mut dc_contact_t)
|
|||||||
let ffi_contact = &*contact;
|
let ffi_contact = &*contact;
|
||||||
let ctx = &*ffi_contact.context;
|
let ctx = &*ffi_contact.context;
|
||||||
let verifier_contact_id = block_on(ffi_contact.contact.get_verifier_id(ctx))
|
let verifier_contact_id = block_on(ffi_contact.contact.get_verifier_id(ctx))
|
||||||
.log_err(ctx, "failed to get verifier")
|
.context("failed to get verifier")
|
||||||
|
.log_err(ctx)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
@@ -4169,8 +4339,8 @@ pub unsafe extern "C" fn dc_backup_provider_new(
|
|||||||
provider,
|
provider,
|
||||||
})
|
})
|
||||||
.map(|ffi_provider| Box::into_raw(Box::new(ffi_provider)))
|
.map(|ffi_provider| Box::into_raw(Box::new(ffi_provider)))
|
||||||
.log_err(ctx, "BackupProvider failed")
|
|
||||||
.context("BackupProvider failed")
|
.context("BackupProvider failed")
|
||||||
|
.log_err(ctx)
|
||||||
.set_last_error(ctx)
|
.set_last_error(ctx)
|
||||||
.unwrap_or(ptr::null_mut())
|
.unwrap_or(ptr::null_mut())
|
||||||
}
|
}
|
||||||
@@ -4186,8 +4356,8 @@ pub unsafe extern "C" fn dc_backup_provider_get_qr(
|
|||||||
let ffi_provider = &*provider;
|
let ffi_provider = &*provider;
|
||||||
let ctx = &*ffi_provider.context;
|
let ctx = &*ffi_provider.context;
|
||||||
deltachat::qr::format_backup(&ffi_provider.provider.qr())
|
deltachat::qr::format_backup(&ffi_provider.provider.qr())
|
||||||
.log_err(ctx, "BackupProvider get_qr failed")
|
|
||||||
.context("BackupProvider get_qr failed")
|
.context("BackupProvider get_qr failed")
|
||||||
|
.log_err(ctx)
|
||||||
.set_last_error(ctx)
|
.set_last_error(ctx)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.strdup()
|
.strdup()
|
||||||
@@ -4205,8 +4375,8 @@ pub unsafe extern "C" fn dc_backup_provider_get_qr_svg(
|
|||||||
let ctx = &*ffi_provider.context;
|
let ctx = &*ffi_provider.context;
|
||||||
let provider = &ffi_provider.provider;
|
let provider = &ffi_provider.provider;
|
||||||
block_on(generate_backup_qr(ctx, &provider.qr()))
|
block_on(generate_backup_qr(ctx, &provider.qr()))
|
||||||
.log_err(ctx, "BackupProvider get_qr_svg failed")
|
|
||||||
.context("BackupProvider get_qr_svg failed")
|
.context("BackupProvider get_qr_svg failed")
|
||||||
|
.log_err(ctx)
|
||||||
.set_last_error(ctx)
|
.set_last_error(ctx)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.strdup()
|
.strdup()
|
||||||
@@ -4222,8 +4392,8 @@ pub unsafe extern "C" fn dc_backup_provider_wait(provider: *mut dc_backup_provid
|
|||||||
let ctx = &*ffi_provider.context;
|
let ctx = &*ffi_provider.context;
|
||||||
let provider = &mut ffi_provider.provider;
|
let provider = &mut ffi_provider.provider;
|
||||||
block_on(provider)
|
block_on(provider)
|
||||||
.log_err(ctx, "Failed to await BackupProvider")
|
|
||||||
.context("Failed to await BackupProvider")
|
.context("Failed to await BackupProvider")
|
||||||
|
.log_err(ctx)
|
||||||
.set_last_error(ctx)
|
.set_last_error(ctx)
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
@@ -4255,16 +4425,16 @@ pub unsafe extern "C" fn dc_receive_backup(
|
|||||||
// user from deallocating it by calling unref on the object while we are using it.
|
// user from deallocating it by calling unref on the object while we are using it.
|
||||||
fn receive_backup(ctx: Context, qr_text: String) -> libc::c_int {
|
fn receive_backup(ctx: Context, qr_text: String) -> libc::c_int {
|
||||||
let qr = match block_on(qr::check_qr(&ctx, &qr_text))
|
let qr = match block_on(qr::check_qr(&ctx, &qr_text))
|
||||||
.log_err(&ctx, "Invalid QR code")
|
|
||||||
.context("Invalid QR code")
|
.context("Invalid QR code")
|
||||||
|
.log_err(&ctx)
|
||||||
.set_last_error(&ctx)
|
.set_last_error(&ctx)
|
||||||
{
|
{
|
||||||
Ok(qr) => qr,
|
Ok(qr) => qr,
|
||||||
Err(_) => return 0,
|
Err(_) => return 0,
|
||||||
};
|
};
|
||||||
match block_on(imex::get_backup(&ctx, qr))
|
match block_on(imex::get_backup(&ctx, qr))
|
||||||
.log_err(&ctx, "Get backup failed")
|
|
||||||
.context("Get backup failed")
|
.context("Get backup failed")
|
||||||
|
.log_err(&ctx)
|
||||||
.set_last_error(&ctx)
|
.set_last_error(&ctx)
|
||||||
{
|
{
|
||||||
Ok(_) => 1,
|
Ok(_) => 1,
|
||||||
@@ -4316,8 +4486,8 @@ where
|
|||||||
///
|
///
|
||||||
/// ```no_compile
|
/// ```no_compile
|
||||||
/// some_dc_rust_api_call_returning_result()
|
/// some_dc_rust_api_call_returning_result()
|
||||||
/// .log_err(&context, "My API call failed")
|
|
||||||
/// .context("My API call failed")
|
/// .context("My API call failed")
|
||||||
|
/// .log_err(&context)
|
||||||
/// .set_last_error(&context)
|
/// .set_last_error(&context)
|
||||||
/// .unwrap_or_default()
|
/// .unwrap_or_default()
|
||||||
/// ```
|
/// ```
|
||||||
@@ -4404,7 +4574,8 @@ pub unsafe extern "C" fn dc_provider_new_from_email_with_dns(
|
|||||||
let socks5_enabled = block_on(async move {
|
let socks5_enabled = block_on(async move {
|
||||||
ctx.get_config_bool(config::Config::Socks5Enabled)
|
ctx.get_config_bool(config::Config::Socks5Enabled)
|
||||||
.await
|
.await
|
||||||
.log_err(ctx, "Can't get config")
|
.context("Can't get config")
|
||||||
|
.log_err(ctx)
|
||||||
});
|
});
|
||||||
|
|
||||||
match socks5_enabled {
|
match socks5_enabled {
|
||||||
@@ -4467,6 +4638,96 @@ pub unsafe extern "C" fn dc_provider_unref(provider: *mut dc_provider_t) {
|
|||||||
// this may change once we start localizing string.
|
// this may change once we start localizing string.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dc_http_response_t
|
||||||
|
|
||||||
|
pub type dc_http_response_t = net::HttpResponse;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_get_http_response(
|
||||||
|
context: *const dc_context_t,
|
||||||
|
url: *const libc::c_char,
|
||||||
|
) -> *mut dc_http_response_t {
|
||||||
|
if context.is_null() || url.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_get_http_response()");
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
|
||||||
|
let context = &*context;
|
||||||
|
let url = to_string_lossy(url);
|
||||||
|
if let Ok(response) = block_on(read_url_blob(context, &url))
|
||||||
|
.context("read_url_blob")
|
||||||
|
.log_err(context)
|
||||||
|
{
|
||||||
|
Box::into_raw(Box::new(response))
|
||||||
|
} else {
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_http_response_get_mimetype(
|
||||||
|
response: *const dc_http_response_t,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
if response.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_http_response_get_mimetype()");
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = &*response;
|
||||||
|
response.mimetype.strdup()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_http_response_get_encoding(
|
||||||
|
response: *const dc_http_response_t,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
if response.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_http_response_get_encoding()");
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = &*response;
|
||||||
|
response.encoding.strdup()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_http_response_get_blob(
|
||||||
|
response: *const dc_http_response_t,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
if response.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_http_response_get_blob()");
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = &*response;
|
||||||
|
let blob_len = response.blob.len();
|
||||||
|
let ptr = libc::malloc(blob_len);
|
||||||
|
libc::memcpy(ptr, response.blob.as_ptr() as *mut libc::c_void, blob_len);
|
||||||
|
ptr as *mut libc::c_char
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_http_response_get_size(
|
||||||
|
response: *const dc_http_response_t,
|
||||||
|
) -> libc::size_t {
|
||||||
|
if response.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_http_response_get_size()");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = &*response;
|
||||||
|
response.blob.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_http_response_unref(response: *mut dc_http_response_t) {
|
||||||
|
if response.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_http_response_unref()");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
drop(Box::from_raw(response));
|
||||||
|
}
|
||||||
|
|
||||||
// -- Accounts
|
// -- Accounts
|
||||||
|
|
||||||
/// Reader-writer lock wrapper for accounts manager to guarantee thread safety when using
|
/// Reader-writer lock wrapper for accounts manager to guarantee thread safety when using
|
||||||
@@ -4771,15 +5032,13 @@ pub unsafe extern "C" fn dc_accounts_get_event_emitter(
|
|||||||
#[cfg(feature = "jsonrpc")]
|
#[cfg(feature = "jsonrpc")]
|
||||||
mod jsonrpc {
|
mod jsonrpc {
|
||||||
use deltachat_jsonrpc::api::CommandApi;
|
use deltachat_jsonrpc::api::CommandApi;
|
||||||
use deltachat_jsonrpc::events::event_to_json_rpc_notification;
|
use deltachat_jsonrpc::yerpc::{OutReceiver, RpcClient, RpcServer, RpcSession};
|
||||||
use deltachat_jsonrpc::yerpc::{OutReceiver, RpcClient, RpcSession};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub struct dc_jsonrpc_instance_t {
|
pub struct dc_jsonrpc_instance_t {
|
||||||
receiver: OutReceiver,
|
receiver: OutReceiver,
|
||||||
handle: RpcSession<CommandApi>,
|
handle: RpcSession<CommandApi>,
|
||||||
event_thread: JoinHandle<Result<(), anyhow::Error>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -4792,28 +5051,12 @@ mod jsonrpc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let account_manager = &*account_manager;
|
let account_manager = &*account_manager;
|
||||||
let events = block_on(account_manager.read()).get_event_emitter();
|
|
||||||
let cmd_api = deltachat_jsonrpc::api::CommandApi::from_arc(account_manager.inner.clone());
|
let cmd_api = deltachat_jsonrpc::api::CommandApi::from_arc(account_manager.inner.clone());
|
||||||
|
|
||||||
let (request_handle, receiver) = RpcClient::new();
|
let (request_handle, receiver) = RpcClient::new();
|
||||||
let handle = RpcSession::new(request_handle.clone(), cmd_api);
|
let handle = RpcSession::new(request_handle, cmd_api);
|
||||||
|
|
||||||
let event_thread = spawn(async move {
|
let instance = dc_jsonrpc_instance_t { receiver, handle };
|
||||||
while let Some(event) = events.recv().await {
|
|
||||||
let event = event_to_json_rpc_notification(event);
|
|
||||||
request_handle
|
|
||||||
.send_notification("event", Some(event))
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
let res: Result<(), anyhow::Error> = Ok(());
|
|
||||||
res
|
|
||||||
});
|
|
||||||
|
|
||||||
let instance = dc_jsonrpc_instance_t {
|
|
||||||
receiver,
|
|
||||||
handle,
|
|
||||||
event_thread,
|
|
||||||
};
|
|
||||||
|
|
||||||
Box::into_raw(Box::new(instance))
|
Box::into_raw(Box::new(instance))
|
||||||
}
|
}
|
||||||
@@ -4824,7 +5067,6 @@ mod jsonrpc {
|
|||||||
eprintln!("ignoring careless call to dc_jsonrpc_unref()");
|
eprintln!("ignoring careless call to dc_jsonrpc_unref()");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
(*jsonrpc_instance).event_thread.abort();
|
|
||||||
drop(Box::from_raw(jsonrpc_instance));
|
drop(Box::from_raw(jsonrpc_instance));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4862,4 +5104,29 @@ mod jsonrpc {
|
|||||||
.map(|result| serde_json::to_string(&result).unwrap_or_default().strdup())
|
.map(|result| serde_json::to_string(&result).unwrap_or_default().strdup())
|
||||||
.unwrap_or(ptr::null_mut())
|
.unwrap_or(ptr::null_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn dc_jsonrpc_blocking_call(
|
||||||
|
jsonrpc_instance: *mut dc_jsonrpc_instance_t,
|
||||||
|
method: *const libc::c_char,
|
||||||
|
params: *const libc::c_char,
|
||||||
|
) -> *mut libc::c_char {
|
||||||
|
if jsonrpc_instance.is_null() {
|
||||||
|
eprintln!("ignoring careless call to dc_jsonrpc_blocking_call()");
|
||||||
|
return ptr::null_mut();
|
||||||
|
}
|
||||||
|
let api = &*jsonrpc_instance;
|
||||||
|
let method = to_string_lossy(method);
|
||||||
|
let params = to_string_lossy(params);
|
||||||
|
let params: Option<yerpc::Params> = match serde_json::from_str(¶ms) {
|
||||||
|
Ok(params) => Some(params),
|
||||||
|
Err(_) => None,
|
||||||
|
};
|
||||||
|
let params = params.map(yerpc::Params::into_value).unwrap_or_default();
|
||||||
|
let res = block_on(api.handle.server().handle_request(method, params));
|
||||||
|
match res {
|
||||||
|
Ok(res) => res.to_string().strdup(),
|
||||||
|
Err(_) => ptr::null_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
deltachat-jsonrpc/.gitignore
vendored
1
deltachat-jsonrpc/.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
openrpc/openrpc.json
|
||||||
accounts/
|
accounts/
|
||||||
|
|
||||||
.cargo
|
.cargo
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat-jsonrpc"
|
name = "deltachat-jsonrpc"
|
||||||
version = "1.112.5"
|
version = "1.122.0"
|
||||||
description = "DeltaChat JSON-RPC API"
|
description = "DeltaChat JSON-RPC API"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
default-run = "deltachat-jsonrpc-server"
|
default-run = "deltachat-jsonrpc-server"
|
||||||
@@ -15,25 +15,26 @@ required-features = ["webserver"]
|
|||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
deltachat = { path = ".." }
|
deltachat = { path = ".." }
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
|
schemars = "0.8.11"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tempfile = "3.3.0"
|
tempfile = "3.6.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
async-channel = { version = "1.8.0" }
|
async-channel = { version = "1.8.0" }
|
||||||
futures = { version = "0.3.26" }
|
futures = { version = "0.3.28" }
|
||||||
serde_json = "1.0.91"
|
serde_json = "1.0.99"
|
||||||
yerpc = { version = "0.4.3", features = ["anyhow_expose"] }
|
yerpc = { version = "0.5.1", features = ["anyhow_expose", "openrpc"] }
|
||||||
typescript-type-def = { version = "0.5.5", features = ["json_value"] }
|
typescript-type-def = { version = "0.5.5", features = ["json_value"] }
|
||||||
tokio = { version = "1.25.0" }
|
tokio = { version = "1.29.1" }
|
||||||
sanitize-filename = "0.4"
|
sanitize-filename = "0.4"
|
||||||
walkdir = "2.3.2"
|
walkdir = "2.3.3"
|
||||||
base64 = "0.21"
|
base64 = "0.21"
|
||||||
|
|
||||||
# optional dependencies
|
# optional dependencies
|
||||||
axum = { version = "0.6.11", optional = true, features = ["ws"] }
|
axum = { version = "0.6.18", optional = true, features = ["ws"] }
|
||||||
env_logger = { version = "0.10.0", optional = true }
|
env_logger = { version = "0.10.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "1.25.0", features = ["full", "rt-multi-thread"] }
|
tokio = { version = "1.29.1", features = ["full", "rt-multi-thread"] }
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use std::{collections::HashMap, str::FromStr};
|
|||||||
|
|
||||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||||
pub use deltachat::accounts::Accounts;
|
pub use deltachat::accounts::Accounts;
|
||||||
|
use deltachat::message::get_msg_read_receipts;
|
||||||
use deltachat::qr::Qr;
|
use deltachat::qr::Qr;
|
||||||
use deltachat::{
|
use deltachat::{
|
||||||
chat::{
|
chat::{
|
||||||
@@ -18,13 +19,11 @@ use deltachat::{
|
|||||||
context::get_info,
|
context::get_info,
|
||||||
ephemeral::Timer,
|
ephemeral::Timer,
|
||||||
imex, location,
|
imex, location,
|
||||||
message::{
|
message::{self, delete_msgs, markseen_msgs, Message, MessageState, MsgId, Viewtype},
|
||||||
self, delete_msgs, get_msg_info, markseen_msgs, Message, MessageState, MsgId, Viewtype,
|
|
||||||
},
|
|
||||||
provider::get_provider_info,
|
provider::get_provider_info,
|
||||||
qr,
|
qr,
|
||||||
qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg},
|
qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg},
|
||||||
reaction::send_reaction,
|
reaction::{get_msg_reactions, send_reaction},
|
||||||
securejoin,
|
securejoin,
|
||||||
stock_str::StockMessage,
|
stock_str::StockMessage,
|
||||||
webxdc::StatusUpdateSerial,
|
webxdc::StatusUpdateSerial,
|
||||||
@@ -35,17 +34,17 @@ use tokio::sync::{watch, Mutex, RwLock};
|
|||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
use yerpc::rpc;
|
use yerpc::rpc;
|
||||||
|
|
||||||
pub mod events;
|
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
use types::account::Account;
|
use types::account::Account;
|
||||||
use types::chat::FullChat;
|
use types::chat::FullChat;
|
||||||
use types::chat_list::ChatListEntry;
|
|
||||||
use types::contact::ContactObject;
|
use types::contact::ContactObject;
|
||||||
use types::message::MessageData;
|
use types::events::Event;
|
||||||
use types::message::MessageObject;
|
use types::http::HttpResponse;
|
||||||
|
use types::message::{MessageData, MessageObject, MessageReadReceipt};
|
||||||
use types::provider_info::ProviderInfo;
|
use types::provider_info::ProviderInfo;
|
||||||
|
use types::reactions::JSONRPCReactions;
|
||||||
use types::webxdc::WebxdcMessageInfo;
|
use types::webxdc::WebxdcMessageInfo;
|
||||||
|
|
||||||
use self::types::message::MessageLoadResult;
|
use self::types::message::MessageLoadResult;
|
||||||
@@ -143,7 +142,11 @@ impl CommandApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rpc(all_positional, ts_outdir = "typescript/generated")]
|
#[rpc(
|
||||||
|
all_positional,
|
||||||
|
ts_outdir = "typescript/generated",
|
||||||
|
openrpc_outdir = "openrpc"
|
||||||
|
)]
|
||||||
impl CommandApi {
|
impl CommandApi {
|
||||||
/// Test function.
|
/// Test function.
|
||||||
async fn sleep(&self, delay: f64) {
|
async fn sleep(&self, delay: f64) {
|
||||||
@@ -164,6 +167,16 @@ impl CommandApi {
|
|||||||
get_info()
|
get_info()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the next event.
|
||||||
|
async fn get_next_event(&self) -> Result<Event> {
|
||||||
|
let event_emitter = self.accounts.read().await.get_event_emitter();
|
||||||
|
event_emitter
|
||||||
|
.recv()
|
||||||
|
.await
|
||||||
|
.map(|event| event.into())
|
||||||
|
.context("event channel is closed")
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
// Account Management
|
// Account Management
|
||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
@@ -205,8 +218,6 @@ impl CommandApi {
|
|||||||
let context_option = self.accounts.read().await.get_account(id);
|
let context_option = self.accounts.read().await.get_account(id);
|
||||||
if let Some(ctx) = context_option {
|
if let Some(ctx) = context_option {
|
||||||
accounts.push(Account::from_context(&ctx, id).await?)
|
accounts.push(Account::from_context(&ctx, id).await?)
|
||||||
} else {
|
|
||||||
println!("account with id {id} doesn't exist anymore");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(accounts)
|
Ok(accounts)
|
||||||
@@ -453,6 +464,49 @@ impl CommandApi {
|
|||||||
ChatId::new(chat_id).get_fresh_msg_cnt(&ctx).await
|
ChatId::new(chat_id).get_fresh_msg_cnt(&ctx).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets messages to be processed by the bot and returns their IDs.
|
||||||
|
///
|
||||||
|
/// Only messages with database ID higher than `last_msg_id` config value
|
||||||
|
/// are returned. After processing the messages, the bot should
|
||||||
|
/// update `last_msg_id` by calling [`markseen_msgs`]
|
||||||
|
/// or manually updating the value to avoid getting already
|
||||||
|
/// processed messages.
|
||||||
|
///
|
||||||
|
/// [`markseen_msgs`]: Self::markseen_msgs
|
||||||
|
async fn get_next_msgs(&self, account_id: u32) -> Result<Vec<u32>> {
|
||||||
|
let ctx = self.get_context(account_id).await?;
|
||||||
|
let msg_ids = ctx
|
||||||
|
.get_next_msgs()
|
||||||
|
.await?
|
||||||
|
.iter()
|
||||||
|
.map(|msg_id| msg_id.to_u32())
|
||||||
|
.collect();
|
||||||
|
Ok(msg_ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Waits for messages to be processed by the bot and returns their IDs.
|
||||||
|
///
|
||||||
|
/// This function is similar to [`get_next_msgs`],
|
||||||
|
/// but waits for internal new message notification before returning.
|
||||||
|
/// New message notification is sent when new message is added to the database,
|
||||||
|
/// on initialization, when I/O is started and when I/O is stopped.
|
||||||
|
/// This allows bots to use `wait_next_msgs` in a loop to process
|
||||||
|
/// old messages after initialization and during the bot runtime.
|
||||||
|
/// To shutdown the bot, stopping I/O can be used to interrupt
|
||||||
|
/// pending or next `wait_next_msgs` call.
|
||||||
|
///
|
||||||
|
/// [`get_next_msgs`]: Self::get_next_msgs
|
||||||
|
async fn wait_next_msgs(&self, account_id: u32) -> Result<Vec<u32>> {
|
||||||
|
let ctx = self.get_context(account_id).await?;
|
||||||
|
let msg_ids = ctx
|
||||||
|
.wait_next_msgs()
|
||||||
|
.await?
|
||||||
|
.iter()
|
||||||
|
.map(|msg_id| msg_id.to_u32())
|
||||||
|
.collect();
|
||||||
|
Ok(msg_ids)
|
||||||
|
}
|
||||||
|
|
||||||
/// Estimate the number of messages that will be deleted
|
/// Estimate the number of messages that will be deleted
|
||||||
/// by the set_config()-options `delete_device_after` or `delete_server_after`.
|
/// by the set_config()-options `delete_device_after` or `delete_server_after`.
|
||||||
/// This is typically used to show the estimated impact to the user
|
/// This is typically used to show the estimated impact to the user
|
||||||
@@ -496,7 +550,7 @@ impl CommandApi {
|
|||||||
list_flags: Option<u32>,
|
list_flags: Option<u32>,
|
||||||
query_string: Option<String>,
|
query_string: Option<String>,
|
||||||
query_contact_id: Option<u32>,
|
query_contact_id: Option<u32>,
|
||||||
) -> Result<Vec<ChatListEntry>> {
|
) -> Result<Vec<u32>> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
let list = Chatlist::try_load(
|
let list = Chatlist::try_load(
|
||||||
&ctx,
|
&ctx,
|
||||||
@@ -505,32 +559,43 @@ impl CommandApi {
|
|||||||
query_contact_id.map(ContactId::new),
|
query_contact_id.map(ContactId::new),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let mut l: Vec<ChatListEntry> = Vec::with_capacity(list.len());
|
let mut l: Vec<u32> = Vec::with_capacity(list.len());
|
||||||
for i in 0..list.len() {
|
for i in 0..list.len() {
|
||||||
l.push(ChatListEntry(
|
l.push(list.get_chat_id(i)?.to_u32());
|
||||||
list.get_chat_id(i)?.to_u32(),
|
|
||||||
list.get_msg_id(i)?.unwrap_or_default().to_u32(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
Ok(l)
|
Ok(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns chats similar to the given one.
|
||||||
|
///
|
||||||
|
/// Experimental API, subject to change without notice.
|
||||||
|
async fn get_similar_chat_ids(&self, account_id: u32, chat_id: u32) -> Result<Vec<u32>> {
|
||||||
|
let ctx = self.get_context(account_id).await?;
|
||||||
|
let chat_id = ChatId::new(chat_id);
|
||||||
|
let list = chat_id
|
||||||
|
.get_similar_chat_ids(&ctx)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(chat_id, _metric)| chat_id.to_u32())
|
||||||
|
.collect();
|
||||||
|
Ok(list)
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_chatlist_items_by_entries(
|
async fn get_chatlist_items_by_entries(
|
||||||
&self,
|
&self,
|
||||||
account_id: u32,
|
account_id: u32,
|
||||||
entries: Vec<ChatListEntry>,
|
entries: Vec<u32>,
|
||||||
) -> Result<HashMap<u32, ChatListItemFetchResult>> {
|
) -> Result<HashMap<u32, ChatListItemFetchResult>> {
|
||||||
// todo custom json deserializer for ChatListEntry?
|
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
let mut result: HashMap<u32, ChatListItemFetchResult> =
|
let mut result: HashMap<u32, ChatListItemFetchResult> =
|
||||||
HashMap::with_capacity(entries.len());
|
HashMap::with_capacity(entries.len());
|
||||||
for entry in entries.iter() {
|
for &entry in entries.iter() {
|
||||||
result.insert(
|
result.insert(
|
||||||
entry.0,
|
entry,
|
||||||
match get_chat_list_item_by_id(&ctx, entry).await {
|
match get_chat_list_item_by_id(&ctx, entry).await {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
Err(err) => ChatListItemFetchResult::Error {
|
Err(err) => ChatListItemFetchResult::Error {
|
||||||
id: entry.0,
|
id: entry,
|
||||||
error: format!("{err:#}"),
|
error: format!("{err:#}"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -849,7 +914,7 @@ impl CommandApi {
|
|||||||
) -> Result<u32> {
|
) -> Result<u32> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
let mut msg = Message::new(Viewtype::Text);
|
let mut msg = Message::new(Viewtype::Text);
|
||||||
msg.set_text(Some(text));
|
msg.set_text(text);
|
||||||
let message_id =
|
let message_id =
|
||||||
deltachat::chat::add_device_msg(&ctx, Some(&label), Some(&mut msg)).await?;
|
deltachat::chat::add_device_msg(&ctx, Some(&label), Some(&mut msg)).await?;
|
||||||
Ok(message_id.to_u32())
|
Ok(message_id.to_u32())
|
||||||
@@ -944,6 +1009,11 @@ impl CommandApi {
|
|||||||
/// Moreover, timer is started for incoming ephemeral messages.
|
/// Moreover, timer is started for incoming ephemeral messages.
|
||||||
/// This also happens for contact requests chats.
|
/// This also happens for contact requests chats.
|
||||||
///
|
///
|
||||||
|
/// This function updates `last_msg_id` configuration value
|
||||||
|
/// to the maximum of the current value and IDs passed to this function.
|
||||||
|
/// Bots which mark messages as seen can rely on this side effect
|
||||||
|
/// to avoid updating `last_msg_id` value manually.
|
||||||
|
///
|
||||||
/// One #DC_EVENT_MSGS_NOTICED event is emitted per modified chat.
|
/// One #DC_EVENT_MSGS_NOTICED event is emitted per modified chat.
|
||||||
async fn markseen_msgs(&self, account_id: u32, msg_ids: Vec<u32>) -> Result<()> {
|
async fn markseen_msgs(&self, account_id: u32, msg_ids: Vec<u32>) -> Result<()> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
@@ -1062,7 +1132,25 @@ impl CommandApi {
|
|||||||
/// max. text returned by dc_msg_get_text() (about 30000 characters).
|
/// max. text returned by dc_msg_get_text() (about 30000 characters).
|
||||||
async fn get_message_info(&self, account_id: u32, message_id: u32) -> Result<String> {
|
async fn get_message_info(&self, account_id: u32, message_id: u32) -> Result<String> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
get_msg_info(&ctx, MsgId::new(message_id)).await
|
MsgId::new(message_id).get_info(&ctx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns contacts that sent read receipts and the time of reading.
|
||||||
|
async fn get_message_read_receipts(
|
||||||
|
&self,
|
||||||
|
account_id: u32,
|
||||||
|
message_id: u32,
|
||||||
|
) -> Result<Vec<MessageReadReceipt>> {
|
||||||
|
let ctx = self.get_context(account_id).await?;
|
||||||
|
let receipts = get_msg_read_receipts(&ctx, MsgId::new(message_id))
|
||||||
|
.await?
|
||||||
|
.iter()
|
||||||
|
.map(|(contact_id, ts)| MessageReadReceipt {
|
||||||
|
contact_id: contact_id.to_u32(),
|
||||||
|
timestamp: *ts,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(receipts)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asks the core to start downloading a message fully.
|
/// Asks the core to start downloading a message fully.
|
||||||
@@ -1082,17 +1170,17 @@ impl CommandApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Search messages containing the given query string.
|
/// Search messages containing the given query string.
|
||||||
/// Searching can be done globally (chat_id=0) or in a specified chat only (chat_id set).
|
/// Searching can be done globally (chat_id=None) or in a specified chat only (chat_id set).
|
||||||
///
|
///
|
||||||
/// Global chat results are typically displayed using dc_msg_get_summary(), chat
|
/// Global search results are typically displayed using dc_msg_get_summary(), chat
|
||||||
/// search results may just hilite the corresponding messages and present a
|
/// search results may just highlight the corresponding messages and present a
|
||||||
/// prev/next button.
|
/// prev/next button.
|
||||||
///
|
///
|
||||||
/// For global search, result is limited to 1000 messages,
|
/// For the global search, the result is limited to 1000 messages,
|
||||||
/// this allows incremental search done fast.
|
/// this allows an incremental search done fast.
|
||||||
/// So, when getting exactly 1000 results, the result may be truncated;
|
/// So, when getting exactly 1000 messages, the result actually may be truncated;
|
||||||
/// the UIs may display sth. as "1000+ messages found" in this case.
|
/// the UIs may display sth. like "1000+ messages found" in this case.
|
||||||
/// Chat search (if a chat_id is set) is not limited.
|
/// The chat search (if chat_id is set) is not limited.
|
||||||
async fn search_messages(
|
async fn search_messages(
|
||||||
&self,
|
&self,
|
||||||
account_id: u32,
|
account_id: u32,
|
||||||
@@ -1265,7 +1353,7 @@ impl CommandApi {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
let contact_id = ContactId::new(contact_id);
|
let contact_id = ContactId::new(contact_id);
|
||||||
let contact = Contact::load_from_db(&ctx, contact_id).await?;
|
let contact = Contact::get_by_id(&ctx, contact_id).await?;
|
||||||
let addr = contact.get_addr();
|
let addr = contact.get_addr();
|
||||||
Contact::create(&ctx, &name, addr).await?;
|
Contact::create(&ctx, &name, addr).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1610,6 +1698,15 @@ impl CommandApi {
|
|||||||
Ok(general_purpose::STANDARD_NO_PAD.encode(blob))
|
Ok(general_purpose::STANDARD_NO_PAD.encode(blob))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Makes an HTTP GET request and returns a response.
|
||||||
|
///
|
||||||
|
/// `url` is the HTTP or HTTPS URL.
|
||||||
|
async fn get_http_response(&self, account_id: u32, url: String) -> Result<HttpResponse> {
|
||||||
|
let ctx = self.get_context(account_id).await?;
|
||||||
|
let response = deltachat::net::read_url_blob(&ctx, &url).await?.into();
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
/// Forward messages to another chat.
|
/// Forward messages to another chat.
|
||||||
///
|
///
|
||||||
/// All types of messages can be forwarded,
|
/// All types of messages can be forwarded,
|
||||||
@@ -1627,6 +1724,20 @@ impl CommandApi {
|
|||||||
forward_msgs(&ctx, &message_ids, ChatId::new(chat_id)).await
|
forward_msgs(&ctx, &message_ids, ChatId::new(chat_id)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resend messages and make information available for newly added chat members.
|
||||||
|
/// Resending sends out the original message, however, recipients and webxdc-status may differ.
|
||||||
|
/// Clients that already have the original message can still ignore the resent message as
|
||||||
|
/// they have tracked the state by dedicated updates.
|
||||||
|
///
|
||||||
|
/// Some messages cannot be resent, eg. info-messages, drafts, already pending messages or messages that are not sent by SELF.
|
||||||
|
///
|
||||||
|
/// message_ids all message IDs that should be resend. All messages must belong to the same chat.
|
||||||
|
async fn resend_messages(&self, account_id: u32, message_ids: Vec<u32>) -> Result<()> {
|
||||||
|
let ctx = self.get_context(account_id).await?;
|
||||||
|
let message_ids: Vec<MsgId> = message_ids.into_iter().map(MsgId::new).collect();
|
||||||
|
chat::resend_msgs(&ctx, &message_ids).await
|
||||||
|
}
|
||||||
|
|
||||||
async fn send_sticker(
|
async fn send_sticker(
|
||||||
&self,
|
&self,
|
||||||
account_id: u32,
|
account_id: u32,
|
||||||
@@ -1659,6 +1770,21 @@ impl CommandApi {
|
|||||||
Ok(message_id.to_u32())
|
Ok(message_id.to_u32())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns reactions to the message.
|
||||||
|
async fn get_message_reactions(
|
||||||
|
&self,
|
||||||
|
account_id: u32,
|
||||||
|
message_id: u32,
|
||||||
|
) -> Result<Option<JSONRPCReactions>> {
|
||||||
|
let ctx = self.get_context(account_id).await?;
|
||||||
|
let reactions = get_msg_reactions(&ctx, MsgId::new(message_id)).await?;
|
||||||
|
if reactions.is_empty() {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
Ok(Some(reactions.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn send_msg(&self, account_id: u32, chat_id: u32, data: MessageData) -> Result<u32> {
|
async fn send_msg(&self, account_id: u32, chat_id: u32, data: MessageData) -> Result<u32> {
|
||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
let mut message = Message::new(if let Some(viewtype) = data.viewtype {
|
let mut message = Message::new(if let Some(viewtype) = data.viewtype {
|
||||||
@@ -1668,9 +1794,7 @@ impl CommandApi {
|
|||||||
} else {
|
} else {
|
||||||
Viewtype::Text
|
Viewtype::Text
|
||||||
});
|
});
|
||||||
if data.text.is_some() {
|
message.set_text(data.text.unwrap_or_default());
|
||||||
message.set_text(data.text);
|
|
||||||
}
|
|
||||||
if data.html.is_some() {
|
if data.html.is_some() {
|
||||||
message.set_html(data.html);
|
message.set_html(data.html);
|
||||||
}
|
}
|
||||||
@@ -1701,6 +1825,15 @@ impl CommandApi {
|
|||||||
Ok(msg_id)
|
Ok(msg_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if messages can be sent to a given chat.
|
||||||
|
async fn can_send(&self, account_id: u32, chat_id: u32) -> Result<bool> {
|
||||||
|
let ctx = self.get_context(account_id).await?;
|
||||||
|
let chat_id = ChatId::new(chat_id);
|
||||||
|
let chat = Chat::load_from_db(&ctx, chat_id).await?;
|
||||||
|
let can_send = chat.can_send(&ctx).await?;
|
||||||
|
Ok(can_send)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
// functions for the composer
|
// functions for the composer
|
||||||
// the composer is the message input field
|
// the composer is the message input field
|
||||||
@@ -1842,7 +1975,7 @@ impl CommandApi {
|
|||||||
let ctx = self.get_context(account_id).await?;
|
let ctx = self.get_context(account_id).await?;
|
||||||
|
|
||||||
let mut msg = Message::new(Viewtype::Text);
|
let mut msg = Message::new(Viewtype::Text);
|
||||||
msg.set_text(Some(text));
|
msg.set_text(text);
|
||||||
|
|
||||||
let message_id = deltachat::chat::send_msg(&ctx, ChatId::new(chat_id), &mut msg).await?;
|
let message_id = deltachat::chat::send_msg(&ctx, ChatId::new(chat_id), &mut msg).await?;
|
||||||
Ok(message_id.to_u32())
|
Ok(message_id.to_u32())
|
||||||
@@ -1865,9 +1998,7 @@ impl CommandApi {
|
|||||||
} else {
|
} else {
|
||||||
Viewtype::Text
|
Viewtype::Text
|
||||||
});
|
});
|
||||||
if text.is_some() {
|
message.set_text(text.unwrap_or_default());
|
||||||
message.set_text(text);
|
|
||||||
}
|
|
||||||
if let Some(file) = file {
|
if let Some(file) = file {
|
||||||
message.set_file(file, None);
|
message.set_file(file, None);
|
||||||
}
|
}
|
||||||
@@ -1911,9 +2042,7 @@ impl CommandApi {
|
|||||||
} else {
|
} else {
|
||||||
Viewtype::Text
|
Viewtype::Text
|
||||||
});
|
});
|
||||||
if text.is_some() {
|
draft.set_text(text.unwrap_or_default());
|
||||||
draft.set_text(text);
|
|
||||||
}
|
|
||||||
if let Some(file) = file {
|
if let Some(file) = file {
|
||||||
draft.set_file(file, None);
|
draft.set_file(file, None);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use typescript_type_def::TypeDef;
|
|||||||
|
|
||||||
use super::color_int_to_hex_string;
|
use super::color_int_to_hex_string;
|
||||||
|
|
||||||
#[derive(Serialize, TypeDef)]
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum Account {
|
pub enum Account {
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{bail, Context as _, Result};
|
||||||
use deltachat::chat::{self, get_chat_contacts, ChatVisibility};
|
use deltachat::chat::{self, get_chat_contacts, ChatVisibility};
|
||||||
use deltachat::chat::{Chat, ChatId};
|
use deltachat::chat::{Chat, ChatId};
|
||||||
use deltachat::constants::Chattype;
|
use deltachat::constants::Chattype;
|
||||||
@@ -13,7 +13,7 @@ use typescript_type_def::TypeDef;
|
|||||||
use super::color_int_to_hex_string;
|
use super::color_int_to_hex_string;
|
||||||
use super::contact::ContactObject;
|
use super::contact::ContactObject;
|
||||||
|
|
||||||
#[derive(Serialize, TypeDef)]
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct FullChat {
|
pub struct FullChat {
|
||||||
id: u32,
|
id: u32,
|
||||||
@@ -53,7 +53,9 @@ impl FullChat {
|
|||||||
contacts.push(
|
contacts.push(
|
||||||
ContactObject::try_from_dc_contact(
|
ContactObject::try_from_dc_contact(
|
||||||
context,
|
context,
|
||||||
Contact::load_from_db(context, *contact_id).await?,
|
Contact::get_by_id(context, *contact_id)
|
||||||
|
.await
|
||||||
|
.context("failed to load contact")?,
|
||||||
)
|
)
|
||||||
.await?,
|
.await?,
|
||||||
)
|
)
|
||||||
@@ -72,8 +74,9 @@ impl FullChat {
|
|||||||
|
|
||||||
let was_seen_recently = if chat.get_type() == Chattype::Single {
|
let was_seen_recently = if chat.get_type() == Chattype::Single {
|
||||||
match contact_ids.get(0) {
|
match contact_ids.get(0) {
|
||||||
Some(contact) => Contact::load_from_db(context, *contact)
|
Some(contact) => Contact::get_by_id(context, *contact)
|
||||||
.await?
|
.await
|
||||||
|
.context("failed to load contact for was_seen_recently")?
|
||||||
.was_seen_recently(),
|
.was_seen_recently(),
|
||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
@@ -89,10 +92,7 @@ impl FullChat {
|
|||||||
is_protected: chat.is_protected(),
|
is_protected: chat.is_protected(),
|
||||||
profile_image, //BLOBS ?
|
profile_image, //BLOBS ?
|
||||||
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
|
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
|
||||||
chat_type: chat
|
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
|
||||||
.get_type()
|
|
||||||
.to_u32()
|
|
||||||
.ok_or_else(|| anyhow!("unknown chat type id"))?, // TODO get rid of this unwrap?
|
|
||||||
is_unpromoted: chat.is_unpromoted(),
|
is_unpromoted: chat.is_unpromoted(),
|
||||||
is_self_talk: chat.is_self_talk(),
|
is_self_talk: chat.is_self_talk(),
|
||||||
contacts,
|
contacts,
|
||||||
@@ -121,7 +121,7 @@ impl FullChat {
|
|||||||
/// - can_send
|
/// - can_send
|
||||||
///
|
///
|
||||||
/// used when you only need the basic metadata of a chat like type, name, profile picture
|
/// used when you only need the basic metadata of a chat like type, name, profile picture
|
||||||
#[derive(Serialize, TypeDef)]
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct BasicChat {
|
pub struct BasicChat {
|
||||||
id: u32,
|
id: u32,
|
||||||
@@ -155,10 +155,7 @@ impl BasicChat {
|
|||||||
is_protected: chat.is_protected(),
|
is_protected: chat.is_protected(),
|
||||||
profile_image, //BLOBS ?
|
profile_image, //BLOBS ?
|
||||||
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
|
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
|
||||||
chat_type: chat
|
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
|
||||||
.get_type()
|
|
||||||
.to_u32()
|
|
||||||
.ok_or_else(|| anyhow!("unknown chat type id"))?, // TODO get rid of this unwrap?
|
|
||||||
is_unpromoted: chat.is_unpromoted(),
|
is_unpromoted: chat.is_unpromoted(),
|
||||||
is_self_talk: chat.is_self_talk(),
|
is_self_talk: chat.is_self_talk(),
|
||||||
color,
|
color,
|
||||||
@@ -169,7 +166,7 @@ impl BasicChat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, TypeDef)]
|
#[derive(Clone, Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
|
||||||
pub enum MuteDuration {
|
pub enum MuteDuration {
|
||||||
NotMuted,
|
NotMuted,
|
||||||
Forever,
|
Forever,
|
||||||
@@ -194,7 +191,7 @@ impl MuteDuration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, TypeDef)]
|
#[derive(Clone, Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
|
||||||
#[serde(rename = "ChatVisibility")]
|
#[serde(rename = "ChatVisibility")]
|
||||||
pub enum JSONRPCChatVisibility {
|
pub enum JSONRPCChatVisibility {
|
||||||
Normal,
|
Normal,
|
||||||
|
|||||||
@@ -1,24 +1,20 @@
|
|||||||
use anyhow::Result;
|
use anyhow::{Context, Result};
|
||||||
|
use deltachat::chat::{Chat, ChatId};
|
||||||
|
use deltachat::chatlist::get_last_message_for_chat;
|
||||||
use deltachat::constants::*;
|
use deltachat::constants::*;
|
||||||
use deltachat::contact::{Contact, ContactId};
|
use deltachat::contact::{Contact, ContactId};
|
||||||
use deltachat::{
|
use deltachat::{
|
||||||
chat::{get_chat_contacts, ChatVisibility},
|
chat::{get_chat_contacts, ChatVisibility},
|
||||||
chatlist::Chatlist,
|
chatlist::Chatlist,
|
||||||
};
|
};
|
||||||
use deltachat::{
|
|
||||||
chat::{Chat, ChatId},
|
|
||||||
message::MsgId,
|
|
||||||
};
|
|
||||||
use num_traits::cast::ToPrimitive;
|
use num_traits::cast::ToPrimitive;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Serialize;
|
||||||
use typescript_type_def::TypeDef;
|
use typescript_type_def::TypeDef;
|
||||||
|
|
||||||
use super::color_int_to_hex_string;
|
use super::color_int_to_hex_string;
|
||||||
|
use super::message::MessageViewtype;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, TypeDef)]
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
pub struct ChatListEntry(pub u32, pub u32);
|
|
||||||
|
|
||||||
#[derive(Serialize, TypeDef)]
|
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum ChatListItemFetchResult {
|
pub enum ChatListItemFetchResult {
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -31,6 +27,8 @@ pub enum ChatListItemFetchResult {
|
|||||||
summary_text1: String,
|
summary_text1: String,
|
||||||
summary_text2: String,
|
summary_text2: String,
|
||||||
summary_status: u32,
|
summary_status: u32,
|
||||||
|
/// showing preview if last chat message is image
|
||||||
|
summary_preview_image: Option<String>,
|
||||||
is_protected: bool,
|
is_protected: bool,
|
||||||
is_group: bool,
|
is_group: bool,
|
||||||
fresh_message_counter: usize,
|
fresh_message_counter: usize,
|
||||||
@@ -47,6 +45,8 @@ pub enum ChatListItemFetchResult {
|
|||||||
/// contact id if this is a dm chat (for view profile entry in context menu)
|
/// contact id if this is a dm chat (for view profile entry in context menu)
|
||||||
dm_chat_contact: Option<u32>,
|
dm_chat_contact: Option<u32>,
|
||||||
was_seen_recently: bool,
|
was_seen_recently: bool,
|
||||||
|
last_message_type: Option<MessageViewtype>,
|
||||||
|
last_message_id: Option<u32>,
|
||||||
},
|
},
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
ArchiveLink { fresh_message_counter: usize },
|
ArchiveLink { fresh_message_counter: usize },
|
||||||
@@ -56,14 +56,9 @@ pub enum ChatListItemFetchResult {
|
|||||||
|
|
||||||
pub(crate) async fn get_chat_list_item_by_id(
|
pub(crate) async fn get_chat_list_item_by_id(
|
||||||
ctx: &deltachat::context::Context,
|
ctx: &deltachat::context::Context,
|
||||||
entry: &ChatListEntry,
|
entry: u32,
|
||||||
) -> Result<ChatListItemFetchResult> {
|
) -> Result<ChatListItemFetchResult> {
|
||||||
let chat_id = ChatId::new(entry.0);
|
let chat_id = ChatId::new(entry);
|
||||||
let last_msgid = match entry.1 {
|
|
||||||
0 => None,
|
|
||||||
_ => Some(MsgId::new(entry.1)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let fresh_message_counter = chat_id.get_fresh_msg_cnt(ctx).await?;
|
let fresh_message_counter = chat_id.get_fresh_msg_cnt(ctx).await?;
|
||||||
|
|
||||||
if chat_id.is_archived_link() {
|
if chat_id.is_archived_link() {
|
||||||
@@ -72,12 +67,18 @@ pub(crate) async fn get_chat_list_item_by_id(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let chat = Chat::load_from_db(ctx, chat_id).await?;
|
let last_msgid = get_last_message_for_chat(ctx, chat_id).await?;
|
||||||
let summary = Chatlist::get_summary2(ctx, chat_id, last_msgid, Some(&chat)).await?;
|
|
||||||
|
let chat = Chat::load_from_db(ctx, chat_id).await.context("chat")?;
|
||||||
|
let summary = Chatlist::get_summary2(ctx, chat_id, last_msgid, Some(&chat))
|
||||||
|
.await
|
||||||
|
.context("summary")?;
|
||||||
|
|
||||||
let summary_text1 = summary.prefix.map_or_else(String::new, |s| s.to_string());
|
let summary_text1 = summary.prefix.map_or_else(String::new, |s| s.to_string());
|
||||||
let summary_text2 = summary.text.to_owned();
|
let summary_text2 = summary.text.to_owned();
|
||||||
|
|
||||||
|
let summary_preview_image = summary.thumbnail_path;
|
||||||
|
|
||||||
let visibility = chat.get_visibility();
|
let visibility = chat.get_visibility();
|
||||||
|
|
||||||
let avatar_path = chat
|
let avatar_path = chat
|
||||||
@@ -85,12 +86,15 @@ pub(crate) async fn get_chat_list_item_by_id(
|
|||||||
.await?
|
.await?
|
||||||
.map(|path| path.to_str().unwrap_or("invalid/path").to_owned());
|
.map(|path| path.to_str().unwrap_or("invalid/path").to_owned());
|
||||||
|
|
||||||
let last_updated = match last_msgid {
|
let (last_updated, message_type) = match last_msgid {
|
||||||
Some(id) => {
|
Some(id) => {
|
||||||
let last_message = deltachat::message::Message::load_from_db(ctx, id).await?;
|
let last_message = deltachat::message::Message::load_from_db(ctx, id).await?;
|
||||||
Some(last_message.get_timestamp() * 1000)
|
(
|
||||||
|
Some(last_message.get_timestamp() * 1000),
|
||||||
|
Some(last_message.get_viewtype().into()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
None => None,
|
None => (None, None),
|
||||||
};
|
};
|
||||||
|
|
||||||
let chat_contacts = get_chat_contacts(ctx, chat_id).await?;
|
let chat_contacts = get_chat_contacts(ctx, chat_id).await?;
|
||||||
@@ -100,8 +104,9 @@ pub(crate) async fn get_chat_list_item_by_id(
|
|||||||
let (dm_chat_contact, was_seen_recently) = if chat.get_type() == Chattype::Single {
|
let (dm_chat_contact, was_seen_recently) = if chat.get_type() == Chattype::Single {
|
||||||
let contact = chat_contacts.get(0);
|
let contact = chat_contacts.get(0);
|
||||||
let was_seen_recently = match contact {
|
let was_seen_recently = match contact {
|
||||||
Some(contact) => Contact::load_from_db(ctx, *contact)
|
Some(contact) => Contact::get_by_id(ctx, *contact)
|
||||||
.await?
|
.await
|
||||||
|
.context("contact")?
|
||||||
.was_seen_recently(),
|
.was_seen_recently(),
|
||||||
None => false,
|
None => false,
|
||||||
};
|
};
|
||||||
@@ -124,6 +129,7 @@ pub(crate) async fn get_chat_list_item_by_id(
|
|||||||
summary_text1,
|
summary_text1,
|
||||||
summary_text2,
|
summary_text2,
|
||||||
summary_status: summary.state.to_u32().expect("impossible"), // idea and a function to transform the constant to strings? or return string enum
|
summary_status: summary.state.to_u32().expect("impossible"), // idea and a function to transform the constant to strings? or return string enum
|
||||||
|
summary_preview_image,
|
||||||
is_protected: chat.is_protected(),
|
is_protected: chat.is_protected(),
|
||||||
is_group: chat.get_type() == Chattype::Group,
|
is_group: chat.get_type() == Chattype::Group,
|
||||||
fresh_message_counter,
|
fresh_message_counter,
|
||||||
@@ -138,5 +144,7 @@ pub(crate) async fn get_chat_list_item_by_id(
|
|||||||
is_broadcast: chat.get_type() == Chattype::Broadcast,
|
is_broadcast: chat.get_type() == Chattype::Broadcast,
|
||||||
dm_chat_contact,
|
dm_chat_contact,
|
||||||
was_seen_recently,
|
was_seen_recently,
|
||||||
|
last_message_type: message_type,
|
||||||
|
last_message_id: last_msgid.map(|id| id.to_u32()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use typescript_type_def::TypeDef;
|
|||||||
|
|
||||||
use super::color_int_to_hex_string;
|
use super::color_int_to_hex_string;
|
||||||
|
|
||||||
#[derive(Serialize, TypeDef)]
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
#[serde(rename = "Contact", rename_all = "camelCase")]
|
#[serde(rename = "Contact", rename_all = "camelCase")]
|
||||||
pub struct ContactObject {
|
pub struct ContactObject {
|
||||||
address: String,
|
address: String,
|
||||||
|
|||||||
@@ -1,19 +1,29 @@
|
|||||||
use deltachat::{Event, EventType};
|
use deltachat::{Event as CoreEvent, EventType as CoreEventType};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::{json, Value};
|
|
||||||
use typescript_type_def::TypeDef;
|
use typescript_type_def::TypeDef;
|
||||||
|
|
||||||
pub fn event_to_json_rpc_notification(event: Event) -> Value {
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
let id: JSONRPCEventType = event.typ.into();
|
#[serde(rename_all = "camelCase")]
|
||||||
json!({
|
pub struct Event {
|
||||||
"event": id,
|
/// Event payload.
|
||||||
"contextId": event.id,
|
event: EventType,
|
||||||
})
|
|
||||||
|
/// Account ID.
|
||||||
|
context_id: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, TypeDef)]
|
impl From<CoreEvent> for Event {
|
||||||
#[serde(tag = "type", rename = "Event")]
|
fn from(event: CoreEvent) -> Self {
|
||||||
pub enum JSONRPCEventType {
|
Event {
|
||||||
|
event: event.typ.into(),
|
||||||
|
context_id: event.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum EventType {
|
||||||
/// The library-user may write an informational string to the log.
|
/// The library-user may write an informational string to the log.
|
||||||
///
|
///
|
||||||
/// This event should *not* be reported to the end-user using a popup or something like
|
/// This event should *not* be reported to the end-user using a popup or something like
|
||||||
@@ -164,6 +174,13 @@ pub enum JSONRPCEventType {
|
|||||||
msg_id: u32,
|
msg_id: u32,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// A single message is deleted.
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
MsgDeleted {
|
||||||
|
chat_id: u32,
|
||||||
|
msg_id: u32,
|
||||||
|
},
|
||||||
|
|
||||||
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
|
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
|
||||||
/// Or the verify state of a chat has changed.
|
/// Or the verify state of a chat has changed.
|
||||||
/// See setChatName(), setChatProfileImage(), addContactToChat()
|
/// See setChatName(), setChatProfileImage(), addContactToChat()
|
||||||
@@ -286,27 +303,27 @@ pub enum JSONRPCEventType {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<EventType> for JSONRPCEventType {
|
impl From<CoreEventType> for EventType {
|
||||||
fn from(event: EventType) -> Self {
|
fn from(event: CoreEventType) -> Self {
|
||||||
use JSONRPCEventType::*;
|
use EventType::*;
|
||||||
match event {
|
match event {
|
||||||
EventType::Info(msg) => Info { msg },
|
CoreEventType::Info(msg) => Info { msg },
|
||||||
EventType::SmtpConnected(msg) => SmtpConnected { msg },
|
CoreEventType::SmtpConnected(msg) => SmtpConnected { msg },
|
||||||
EventType::ImapConnected(msg) => ImapConnected { msg },
|
CoreEventType::ImapConnected(msg) => ImapConnected { msg },
|
||||||
EventType::SmtpMessageSent(msg) => SmtpMessageSent { msg },
|
CoreEventType::SmtpMessageSent(msg) => SmtpMessageSent { msg },
|
||||||
EventType::ImapMessageDeleted(msg) => ImapMessageDeleted { msg },
|
CoreEventType::ImapMessageDeleted(msg) => ImapMessageDeleted { msg },
|
||||||
EventType::ImapMessageMoved(msg) => ImapMessageMoved { msg },
|
CoreEventType::ImapMessageMoved(msg) => ImapMessageMoved { msg },
|
||||||
EventType::ImapInboxIdle => ImapInboxIdle,
|
CoreEventType::ImapInboxIdle => ImapInboxIdle,
|
||||||
EventType::NewBlobFile(file) => NewBlobFile { file },
|
CoreEventType::NewBlobFile(file) => NewBlobFile { file },
|
||||||
EventType::DeletedBlobFile(file) => DeletedBlobFile { file },
|
CoreEventType::DeletedBlobFile(file) => DeletedBlobFile { file },
|
||||||
EventType::Warning(msg) => Warning { msg },
|
CoreEventType::Warning(msg) => Warning { msg },
|
||||||
EventType::Error(msg) => Error { msg },
|
CoreEventType::Error(msg) => Error { msg },
|
||||||
EventType::ErrorSelfNotInGroup(msg) => ErrorSelfNotInGroup { msg },
|
CoreEventType::ErrorSelfNotInGroup(msg) => ErrorSelfNotInGroup { msg },
|
||||||
EventType::MsgsChanged { chat_id, msg_id } => MsgsChanged {
|
CoreEventType::MsgsChanged { chat_id, msg_id } => MsgsChanged {
|
||||||
chat_id: chat_id.to_u32(),
|
chat_id: chat_id.to_u32(),
|
||||||
msg_id: msg_id.to_u32(),
|
msg_id: msg_id.to_u32(),
|
||||||
},
|
},
|
||||||
EventType::ReactionsChanged {
|
CoreEventType::ReactionsChanged {
|
||||||
chat_id,
|
chat_id,
|
||||||
msg_id,
|
msg_id,
|
||||||
contact_id,
|
contact_id,
|
||||||
@@ -315,92 +332,80 @@ impl From<EventType> for JSONRPCEventType {
|
|||||||
msg_id: msg_id.to_u32(),
|
msg_id: msg_id.to_u32(),
|
||||||
contact_id: contact_id.to_u32(),
|
contact_id: contact_id.to_u32(),
|
||||||
},
|
},
|
||||||
EventType::IncomingMsg { chat_id, msg_id } => IncomingMsg {
|
CoreEventType::IncomingMsg { chat_id, msg_id } => IncomingMsg {
|
||||||
chat_id: chat_id.to_u32(),
|
chat_id: chat_id.to_u32(),
|
||||||
msg_id: msg_id.to_u32(),
|
msg_id: msg_id.to_u32(),
|
||||||
},
|
},
|
||||||
EventType::IncomingMsgBunch { msg_ids } => IncomingMsgBunch {
|
CoreEventType::IncomingMsgBunch { msg_ids } => IncomingMsgBunch {
|
||||||
msg_ids: msg_ids.into_iter().map(|id| id.to_u32()).collect(),
|
msg_ids: msg_ids.into_iter().map(|id| id.to_u32()).collect(),
|
||||||
},
|
},
|
||||||
EventType::MsgsNoticed(chat_id) => MsgsNoticed {
|
CoreEventType::MsgsNoticed(chat_id) => MsgsNoticed {
|
||||||
chat_id: chat_id.to_u32(),
|
chat_id: chat_id.to_u32(),
|
||||||
},
|
},
|
||||||
EventType::MsgDelivered { chat_id, msg_id } => MsgDelivered {
|
CoreEventType::MsgDelivered { chat_id, msg_id } => MsgDelivered {
|
||||||
chat_id: chat_id.to_u32(),
|
chat_id: chat_id.to_u32(),
|
||||||
msg_id: msg_id.to_u32(),
|
msg_id: msg_id.to_u32(),
|
||||||
},
|
},
|
||||||
EventType::MsgFailed { chat_id, msg_id } => MsgFailed {
|
CoreEventType::MsgFailed { chat_id, msg_id } => MsgFailed {
|
||||||
chat_id: chat_id.to_u32(),
|
chat_id: chat_id.to_u32(),
|
||||||
msg_id: msg_id.to_u32(),
|
msg_id: msg_id.to_u32(),
|
||||||
},
|
},
|
||||||
EventType::MsgRead { chat_id, msg_id } => MsgRead {
|
CoreEventType::MsgRead { chat_id, msg_id } => MsgRead {
|
||||||
chat_id: chat_id.to_u32(),
|
chat_id: chat_id.to_u32(),
|
||||||
msg_id: msg_id.to_u32(),
|
msg_id: msg_id.to_u32(),
|
||||||
},
|
},
|
||||||
EventType::ChatModified(chat_id) => ChatModified {
|
CoreEventType::MsgDeleted { chat_id, msg_id } => MsgDeleted {
|
||||||
|
chat_id: chat_id.to_u32(),
|
||||||
|
msg_id: msg_id.to_u32(),
|
||||||
|
},
|
||||||
|
CoreEventType::ChatModified(chat_id) => ChatModified {
|
||||||
chat_id: chat_id.to_u32(),
|
chat_id: chat_id.to_u32(),
|
||||||
},
|
},
|
||||||
EventType::ChatEphemeralTimerModified { chat_id, timer } => {
|
CoreEventType::ChatEphemeralTimerModified { chat_id, timer } => {
|
||||||
ChatEphemeralTimerModified {
|
ChatEphemeralTimerModified {
|
||||||
chat_id: chat_id.to_u32(),
|
chat_id: chat_id.to_u32(),
|
||||||
timer: timer.to_u32(),
|
timer: timer.to_u32(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventType::ContactsChanged(contact) => ContactsChanged {
|
CoreEventType::ContactsChanged(contact) => ContactsChanged {
|
||||||
contact_id: contact.map(|c| c.to_u32()),
|
contact_id: contact.map(|c| c.to_u32()),
|
||||||
},
|
},
|
||||||
EventType::LocationChanged(contact) => LocationChanged {
|
CoreEventType::LocationChanged(contact) => LocationChanged {
|
||||||
contact_id: contact.map(|c| c.to_u32()),
|
contact_id: contact.map(|c| c.to_u32()),
|
||||||
},
|
},
|
||||||
EventType::ConfigureProgress { progress, comment } => {
|
CoreEventType::ConfigureProgress { progress, comment } => {
|
||||||
ConfigureProgress { progress, comment }
|
ConfigureProgress { progress, comment }
|
||||||
}
|
}
|
||||||
EventType::ImexProgress(progress) => ImexProgress { progress },
|
CoreEventType::ImexProgress(progress) => ImexProgress { progress },
|
||||||
EventType::ImexFileWritten(path) => ImexFileWritten {
|
CoreEventType::ImexFileWritten(path) => ImexFileWritten {
|
||||||
path: path.to_str().unwrap_or_default().to_owned(),
|
path: path.to_str().unwrap_or_default().to_owned(),
|
||||||
},
|
},
|
||||||
EventType::SecurejoinInviterProgress {
|
CoreEventType::SecurejoinInviterProgress {
|
||||||
contact_id,
|
contact_id,
|
||||||
progress,
|
progress,
|
||||||
} => SecurejoinInviterProgress {
|
} => SecurejoinInviterProgress {
|
||||||
contact_id: contact_id.to_u32(),
|
contact_id: contact_id.to_u32(),
|
||||||
progress,
|
progress,
|
||||||
},
|
},
|
||||||
EventType::SecurejoinJoinerProgress {
|
CoreEventType::SecurejoinJoinerProgress {
|
||||||
contact_id,
|
contact_id,
|
||||||
progress,
|
progress,
|
||||||
} => SecurejoinJoinerProgress {
|
} => SecurejoinJoinerProgress {
|
||||||
contact_id: contact_id.to_u32(),
|
contact_id: contact_id.to_u32(),
|
||||||
progress,
|
progress,
|
||||||
},
|
},
|
||||||
EventType::ConnectivityChanged => ConnectivityChanged,
|
CoreEventType::ConnectivityChanged => ConnectivityChanged,
|
||||||
EventType::SelfavatarChanged => SelfavatarChanged,
|
CoreEventType::SelfavatarChanged => SelfavatarChanged,
|
||||||
EventType::WebxdcStatusUpdate {
|
CoreEventType::WebxdcStatusUpdate {
|
||||||
msg_id,
|
msg_id,
|
||||||
status_update_serial,
|
status_update_serial,
|
||||||
} => WebxdcStatusUpdate {
|
} => WebxdcStatusUpdate {
|
||||||
msg_id: msg_id.to_u32(),
|
msg_id: msg_id.to_u32(),
|
||||||
status_update_serial: status_update_serial.to_u32(),
|
status_update_serial: status_update_serial.to_u32(),
|
||||||
},
|
},
|
||||||
EventType::WebxdcInstanceDeleted { msg_id } => WebxdcInstanceDeleted {
|
CoreEventType::WebxdcInstanceDeleted { msg_id } => WebxdcInstanceDeleted {
|
||||||
msg_id: msg_id.to_u32(),
|
msg_id: msg_id.to_u32(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[test]
|
|
||||||
fn generate_events_ts_types_definition() {
|
|
||||||
let events = {
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
let options = typescript_type_def::DefinitionFileOptions {
|
|
||||||
root_namespace: None,
|
|
||||||
..typescript_type_def::DefinitionFileOptions::default()
|
|
||||||
};
|
|
||||||
typescript_type_def::write_definition_file::<_, JSONRPCEventType>(&mut buf, options)
|
|
||||||
.unwrap();
|
|
||||||
String::from_utf8(buf).unwrap()
|
|
||||||
};
|
|
||||||
std::fs::write("typescript/generated/events.ts", events).unwrap();
|
|
||||||
}
|
|
||||||
29
deltachat-jsonrpc/src/api/types/http.rs
Normal file
29
deltachat-jsonrpc/src/api/types/http.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
use deltachat::net::HttpResponse as CoreHttpResponse;
|
||||||
|
use serde::Serialize;
|
||||||
|
use typescript_type_def::TypeDef;
|
||||||
|
|
||||||
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
|
pub struct HttpResponse {
|
||||||
|
/// base64-encoded response body.
|
||||||
|
blob: String,
|
||||||
|
|
||||||
|
/// MIME type, e.g. "text/plain" or "text/html".
|
||||||
|
mimetype: Option<String>,
|
||||||
|
|
||||||
|
/// Encoding, e.g. "utf-8".
|
||||||
|
encoding: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CoreHttpResponse> for HttpResponse {
|
||||||
|
fn from(response: CoreHttpResponse) -> Self {
|
||||||
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
|
let blob = general_purpose::STANDARD_NO_PAD.encode(response.blob);
|
||||||
|
let mimetype = response.mimetype;
|
||||||
|
let encoding = response.encoding;
|
||||||
|
HttpResponse {
|
||||||
|
blob,
|
||||||
|
mimetype,
|
||||||
|
encoding,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ use deltachat::location::Location;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use typescript_type_def::TypeDef;
|
use typescript_type_def::TypeDef;
|
||||||
|
|
||||||
#[derive(Serialize, TypeDef)]
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
#[serde(rename = "Location", rename_all = "camelCase")]
|
#[serde(rename = "Location", rename_all = "camelCase")]
|
||||||
pub struct JsonrpcLocation {
|
pub struct JsonrpcLocation {
|
||||||
pub location_id: u32,
|
pub location_id: u32,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use deltachat::chat::Chat;
|
use deltachat::chat::Chat;
|
||||||
use deltachat::chat::ChatItem;
|
use deltachat::chat::ChatItem;
|
||||||
use deltachat::constants::Chattype;
|
use deltachat::chat::ChatVisibility;
|
||||||
use deltachat::contact::Contact;
|
use deltachat::contact::Contact;
|
||||||
use deltachat::context::Context;
|
use deltachat::context::Context;
|
||||||
use deltachat::download;
|
use deltachat::download;
|
||||||
@@ -10,8 +10,7 @@ use deltachat::message::MsgId;
|
|||||||
use deltachat::message::Viewtype;
|
use deltachat::message::Viewtype;
|
||||||
use deltachat::reaction::get_msg_reactions;
|
use deltachat::reaction::get_msg_reactions;
|
||||||
use num_traits::cast::ToPrimitive;
|
use num_traits::cast::ToPrimitive;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use serde::Serialize;
|
|
||||||
use typescript_type_def::TypeDef;
|
use typescript_type_def::TypeDef;
|
||||||
|
|
||||||
use super::color_int_to_hex_string;
|
use super::color_int_to_hex_string;
|
||||||
@@ -19,14 +18,14 @@ use super::contact::ContactObject;
|
|||||||
use super::reactions::JSONRPCReactions;
|
use super::reactions::JSONRPCReactions;
|
||||||
use super::webxdc::WebxdcMessageInfo;
|
use super::webxdc::WebxdcMessageInfo;
|
||||||
|
|
||||||
#[derive(Serialize, TypeDef)]
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
#[serde(rename_all = "camelCase", tag = "variant")]
|
#[serde(rename_all = "camelCase", tag = "variant")]
|
||||||
pub enum MessageLoadResult {
|
pub enum MessageLoadResult {
|
||||||
Message(MessageObject),
|
Message(MessageObject),
|
||||||
LoadingError { error: String },
|
LoadingError { error: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, TypeDef)]
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
#[serde(rename = "Message", rename_all = "camelCase")]
|
#[serde(rename = "Message", rename_all = "camelCase")]
|
||||||
pub struct MessageObject {
|
pub struct MessageObject {
|
||||||
id: u32,
|
id: u32,
|
||||||
@@ -35,7 +34,7 @@ pub struct MessageObject {
|
|||||||
quote: Option<MessageQuote>,
|
quote: Option<MessageQuote>,
|
||||||
parent_id: Option<u32>,
|
parent_id: Option<u32>,
|
||||||
|
|
||||||
text: Option<String>,
|
text: String,
|
||||||
has_location: bool,
|
has_location: bool,
|
||||||
has_html: bool,
|
has_html: bool,
|
||||||
view_type: MessageViewtype,
|
view_type: MessageViewtype,
|
||||||
@@ -86,7 +85,7 @@ pub struct MessageObject {
|
|||||||
reactions: Option<JSONRPCReactions>,
|
reactions: Option<JSONRPCReactions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, TypeDef)]
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
#[serde(tag = "kind")]
|
#[serde(tag = "kind")]
|
||||||
enum MessageQuote {
|
enum MessageQuote {
|
||||||
JustText {
|
JustText {
|
||||||
@@ -114,8 +113,12 @@ impl MessageObject {
|
|||||||
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<Self> {
|
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<Self> {
|
||||||
let message = Message::load_from_db(context, msg_id).await?;
|
let message = Message::load_from_db(context, msg_id).await?;
|
||||||
|
|
||||||
let sender_contact = Contact::load_from_db(context, message.get_from_id()).await?;
|
let sender_contact = Contact::get_by_id(context, message.get_from_id())
|
||||||
let sender = ContactObject::try_from_dc_contact(context, sender_contact).await?;
|
.await
|
||||||
|
.context("failed to load sender contact")?;
|
||||||
|
let sender = ContactObject::try_from_dc_contact(context, sender_contact)
|
||||||
|
.await
|
||||||
|
.context("failed to load sender contact object")?;
|
||||||
let file_bytes = message.get_filebytes(context).await?.unwrap_or_default();
|
let file_bytes = message.get_filebytes(context).await?.unwrap_or_default();
|
||||||
let override_sender_name = message.get_override_sender_name();
|
let override_sender_name = message.get_override_sender_name();
|
||||||
|
|
||||||
@@ -132,7 +135,9 @@ impl MessageObject {
|
|||||||
let quote = if let Some(quoted_text) = message.quoted_text() {
|
let quote = if let Some(quoted_text) = message.quoted_text() {
|
||||||
match message.quoted_message(context).await? {
|
match message.quoted_message(context).await? {
|
||||||
Some(quote) => {
|
Some(quote) => {
|
||||||
let quote_author = Contact::load_from_db(context, quote.get_from_id()).await?;
|
let quote_author = Contact::get_by_id(context, quote.get_from_id())
|
||||||
|
.await
|
||||||
|
.context("failed to load quote author contact")?;
|
||||||
Some(MessageQuote::WithMessage {
|
Some(MessageQuote::WithMessage {
|
||||||
text: quoted_text,
|
text: quoted_text,
|
||||||
message_id: quote.get_id().to_u32(),
|
message_id: quote.get_id().to_u32(),
|
||||||
@@ -160,7 +165,9 @@ impl MessageObject {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let reactions = get_msg_reactions(context, msg_id).await?;
|
let reactions = get_msg_reactions(context, msg_id)
|
||||||
|
.await
|
||||||
|
.context("failed to load message reactions")?;
|
||||||
let reactions = if reactions.is_empty() {
|
let reactions = if reactions.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@@ -180,7 +187,7 @@ impl MessageObject {
|
|||||||
state: message
|
state: message
|
||||||
.get_state()
|
.get_state()
|
||||||
.to_u32()
|
.to_u32()
|
||||||
.ok_or_else(|| anyhow!("state conversion to number failed"))?,
|
.context("state conversion to number failed")?,
|
||||||
error: message.error(),
|
error: message.error(),
|
||||||
|
|
||||||
timestamp: message.get_timestamp(),
|
timestamp: message.get_timestamp(),
|
||||||
@@ -203,7 +210,7 @@ impl MessageObject {
|
|||||||
videochat_type: match message.get_videochat_type() {
|
videochat_type: match message.get_videochat_type() {
|
||||||
Some(vct) => Some(
|
Some(vct) => Some(
|
||||||
vct.to_u32()
|
vct.to_u32()
|
||||||
.ok_or_else(|| anyhow!("state conversion to number failed"))?,
|
.context("videochat type conversion to number failed")?,
|
||||||
),
|
),
|
||||||
None => None,
|
None => None,
|
||||||
},
|
},
|
||||||
@@ -230,7 +237,7 @@ impl MessageObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, TypeDef)]
|
#[derive(Serialize, Deserialize, TypeDef, schemars::JsonSchema)]
|
||||||
#[serde(rename = "Viewtype")]
|
#[serde(rename = "Viewtype")]
|
||||||
pub enum MessageViewtype {
|
pub enum MessageViewtype {
|
||||||
Unknown,
|
Unknown,
|
||||||
@@ -306,11 +313,12 @@ impl From<MessageViewtype> for Viewtype {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, TypeDef)]
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
pub enum DownloadState {
|
pub enum DownloadState {
|
||||||
Done,
|
Done,
|
||||||
Available,
|
Available,
|
||||||
Failure,
|
Failure,
|
||||||
|
Undecipherable,
|
||||||
InProgress,
|
InProgress,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,12 +328,13 @@ impl From<download::DownloadState> for DownloadState {
|
|||||||
download::DownloadState::Done => DownloadState::Done,
|
download::DownloadState::Done => DownloadState::Done,
|
||||||
download::DownloadState::Available => DownloadState::Available,
|
download::DownloadState::Available => DownloadState::Available,
|
||||||
download::DownloadState::Failure => DownloadState::Failure,
|
download::DownloadState::Failure => DownloadState::Failure,
|
||||||
|
download::DownloadState::Undecipherable => DownloadState::Undecipherable,
|
||||||
download::DownloadState::InProgress => DownloadState::InProgress,
|
download::DownloadState::InProgress => DownloadState::InProgress,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, TypeDef)]
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
pub enum SystemMessageType {
|
pub enum SystemMessageType {
|
||||||
Unknown,
|
Unknown,
|
||||||
GroupNameChanged,
|
GroupNameChanged,
|
||||||
@@ -380,7 +389,7 @@ impl From<deltachat::mimeparser::SystemMessage> for SystemMessageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, TypeDef)]
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MessageNotificationInfo {
|
pub struct MessageNotificationInfo {
|
||||||
id: u32,
|
id: u32,
|
||||||
@@ -438,14 +447,22 @@ impl MessageNotificationInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, TypeDef)]
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MessageSearchResult {
|
pub struct MessageSearchResult {
|
||||||
id: u32,
|
id: u32,
|
||||||
author_profile_image: Option<String>,
|
author_profile_image: Option<String>,
|
||||||
|
/// if sender name if overridden it will show it as ~alias
|
||||||
author_name: String,
|
author_name: String,
|
||||||
author_color: String,
|
author_color: String,
|
||||||
chat_name: Option<String>,
|
author_id: u32,
|
||||||
|
chat_profile_image: Option<String>,
|
||||||
|
chat_color: String,
|
||||||
|
chat_name: String,
|
||||||
|
chat_type: u32,
|
||||||
|
is_chat_protected: bool,
|
||||||
|
is_chat_contact_request: bool,
|
||||||
|
is_chat_archived: bool,
|
||||||
message: String,
|
message: String,
|
||||||
timestamp: i64,
|
timestamp: i64,
|
||||||
}
|
}
|
||||||
@@ -454,30 +471,44 @@ impl MessageSearchResult {
|
|||||||
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<Self> {
|
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<Self> {
|
||||||
let message = Message::load_from_db(context, msg_id).await?;
|
let message = Message::load_from_db(context, msg_id).await?;
|
||||||
let chat = Chat::load_from_db(context, message.get_chat_id()).await?;
|
let chat = Chat::load_from_db(context, message.get_chat_id()).await?;
|
||||||
let sender = Contact::load_from_db(context, message.get_from_id()).await?;
|
let sender = Contact::get_by_id(context, message.get_from_id()).await?;
|
||||||
|
|
||||||
let profile_image = match sender.get_profile_image(context).await? {
|
let profile_image = match sender.get_profile_image(context).await? {
|
||||||
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
let chat_profile_image = match chat.get_profile_image(context).await? {
|
||||||
|
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let author_name = if let Some(name) = message.get_override_sender_name() {
|
||||||
|
format!("~{name}")
|
||||||
|
} else {
|
||||||
|
sender.get_display_name().to_owned()
|
||||||
|
};
|
||||||
|
let chat_color = color_int_to_hex_string(chat.get_color(context).await?);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
id: msg_id.to_u32(),
|
id: msg_id.to_u32(),
|
||||||
author_profile_image: profile_image,
|
author_profile_image: profile_image,
|
||||||
author_name: sender.get_display_name().to_owned(),
|
author_name,
|
||||||
author_color: color_int_to_hex_string(sender.get_color()),
|
author_color: color_int_to_hex_string(sender.get_color()),
|
||||||
chat_name: if chat.get_type() == Chattype::Single {
|
author_id: sender.id.to_u32(),
|
||||||
Some(chat.get_name().to_owned())
|
chat_name: chat.get_name().to_owned(),
|
||||||
} else {
|
chat_color,
|
||||||
None
|
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
|
||||||
},
|
chat_profile_image,
|
||||||
message: message.get_text().unwrap_or_default(),
|
is_chat_protected: chat.is_protected(),
|
||||||
|
is_chat_contact_request: chat.is_contact_request(),
|
||||||
|
is_chat_archived: chat.get_visibility() == ChatVisibility::Archived,
|
||||||
|
message: message.get_text(),
|
||||||
timestamp: message.get_timestamp(),
|
timestamp: message.get_timestamp(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, TypeDef)]
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
#[serde(rename_all = "camelCase", rename = "MessageListItem", tag = "kind")]
|
#[serde(rename_all = "camelCase", rename = "MessageListItem", tag = "kind")]
|
||||||
pub enum JSONRPCMessageListItem {
|
pub enum JSONRPCMessageListItem {
|
||||||
Message {
|
Message {
|
||||||
@@ -503,7 +534,7 @@ impl From<ChatItem> for JSONRPCMessageListItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, TypeDef)]
|
#[derive(Deserialize, Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MessageData {
|
pub struct MessageData {
|
||||||
pub text: Option<String>,
|
pub text: Option<String>,
|
||||||
@@ -514,3 +545,10 @@ pub struct MessageData {
|
|||||||
pub override_sender_name: Option<String>,
|
pub override_sender_name: Option<String>,
|
||||||
pub quoted_message_id: Option<u32>,
|
pub quoted_message_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct MessageReadReceipt {
|
||||||
|
pub contact_id: u32,
|
||||||
|
pub timestamp: i64,
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ pub mod account;
|
|||||||
pub mod chat;
|
pub mod chat;
|
||||||
pub mod chat_list;
|
pub mod chat_list;
|
||||||
pub mod contact;
|
pub mod contact;
|
||||||
|
pub mod events;
|
||||||
|
pub mod http;
|
||||||
pub mod location;
|
pub mod location;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod provider_info;
|
pub mod provider_info;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use num_traits::cast::ToPrimitive;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use typescript_type_def::TypeDef;
|
use typescript_type_def::TypeDef;
|
||||||
|
|
||||||
#[derive(Serialize, TypeDef)]
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ProviderInfo {
|
pub struct ProviderInfo {
|
||||||
pub before_login_hint: String,
|
pub before_login_hint: String,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use deltachat::qr::Qr;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use typescript_type_def::TypeDef;
|
use typescript_type_def::TypeDef;
|
||||||
|
|
||||||
#[derive(Serialize, TypeDef)]
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
#[serde(rename = "Qr", rename_all = "camelCase")]
|
#[serde(rename = "Qr", rename_all = "camelCase")]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum QrObject {
|
pub enum QrObject {
|
||||||
|
|||||||
@@ -1,23 +1,37 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use deltachat::contact::ContactId;
|
||||||
use deltachat::reaction::Reactions;
|
use deltachat::reaction::Reactions;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use typescript_type_def::TypeDef;
|
use typescript_type_def::TypeDef;
|
||||||
|
|
||||||
|
/// A single reaction emoji.
|
||||||
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
|
#[serde(rename = "Reaction", rename_all = "camelCase")]
|
||||||
|
pub struct JSONRPCReaction {
|
||||||
|
/// Emoji.
|
||||||
|
emoji: String,
|
||||||
|
|
||||||
|
/// Emoji frequency.
|
||||||
|
count: usize,
|
||||||
|
|
||||||
|
/// True if we reacted with this emoji.
|
||||||
|
is_from_self: bool,
|
||||||
|
}
|
||||||
|
|
||||||
/// Structure representing all reactions to a particular message.
|
/// Structure representing all reactions to a particular message.
|
||||||
#[derive(Serialize, TypeDef)]
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
#[serde(rename = "Reactions", rename_all = "camelCase")]
|
#[serde(rename = "Reactions", rename_all = "camelCase")]
|
||||||
pub struct JSONRPCReactions {
|
pub struct JSONRPCReactions {
|
||||||
/// Map from a contact to it's reaction to message.
|
/// Map from a contact to it's reaction to message.
|
||||||
reactions_by_contact: BTreeMap<u32, Vec<String>>,
|
reactions_by_contact: BTreeMap<u32, Vec<String>>,
|
||||||
/// Unique reactions and their count
|
/// Unique reactions and their count, sorted in descending order.
|
||||||
reactions: BTreeMap<String, u32>,
|
reactions: Vec<JSONRPCReaction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Reactions> for JSONRPCReactions {
|
impl From<Reactions> for JSONRPCReactions {
|
||||||
fn from(reactions: Reactions) -> Self {
|
fn from(reactions: Reactions) -> Self {
|
||||||
let mut reactions_by_contact: BTreeMap<u32, Vec<String>> = BTreeMap::new();
|
let mut reactions_by_contact: BTreeMap<u32, Vec<String>> = BTreeMap::new();
|
||||||
let mut unique_reactions: BTreeMap<String, u32> = BTreeMap::new();
|
|
||||||
|
|
||||||
for contact_id in reactions.contacts() {
|
for contact_id in reactions.contacts() {
|
||||||
let reaction = reactions.get(contact_id);
|
let reaction = reactions.get(contact_id);
|
||||||
@@ -30,18 +44,29 @@ impl From<Reactions> for JSONRPCReactions {
|
|||||||
.map(|emoji| emoji.to_owned())
|
.map(|emoji| emoji.to_owned())
|
||||||
.collect();
|
.collect();
|
||||||
reactions_by_contact.insert(contact_id.to_u32(), emojis.clone());
|
reactions_by_contact.insert(contact_id.to_u32(), emojis.clone());
|
||||||
for emoji in emojis {
|
}
|
||||||
if let Some(x) = unique_reactions.get_mut(&emoji) {
|
|
||||||
*x += 1;
|
let self_reactions = reactions_by_contact.get(&ContactId::SELF.to_u32());
|
||||||
} else {
|
|
||||||
unique_reactions.insert(emoji, 1);
|
let mut reactions_v = Vec::new();
|
||||||
}
|
for (emoji, count) in reactions.emoji_sorted_by_frequency() {
|
||||||
}
|
let is_from_self = if let Some(self_reactions) = self_reactions {
|
||||||
|
self_reactions.contains(&emoji)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
let reaction = JSONRPCReaction {
|
||||||
|
emoji,
|
||||||
|
count,
|
||||||
|
is_from_self,
|
||||||
|
};
|
||||||
|
reactions_v.push(reaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONRPCReactions {
|
JSONRPCReactions {
|
||||||
reactions_by_contact,
|
reactions_by_contact,
|
||||||
reactions: unique_reactions,
|
reactions: reactions_v,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use typescript_type_def::TypeDef;
|
|||||||
|
|
||||||
use super::maybe_empty_string_to_option;
|
use super::maybe_empty_string_to_option;
|
||||||
|
|
||||||
#[derive(Serialize, TypeDef)]
|
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
|
||||||
#[serde(rename = "WebxdcMessageInfo", rename_all = "camelCase")]
|
#[serde(rename = "WebxdcMessageInfo", rename_all = "camelCase")]
|
||||||
pub struct WebxdcMessageInfo {
|
pub struct WebxdcMessageInfo {
|
||||||
/// The name of the app.
|
/// The name of the app.
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
pub mod api;
|
pub mod api;
|
||||||
pub use api::events;
|
|
||||||
pub use yerpc;
|
pub use yerpc;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use yerpc::axum::handle_ws_rpc;
|
|||||||
use yerpc::{RpcClient, RpcSession};
|
use yerpc::{RpcClient, RpcSession};
|
||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
use api::events::event_to_json_rpc_notification;
|
|
||||||
use api::{Accounts, CommandApi};
|
use api::{Accounts, CommandApi};
|
||||||
|
|
||||||
const DEFAULT_PORT: u16 = 20808;
|
const DEFAULT_PORT: u16 = 20808;
|
||||||
@@ -44,12 +43,5 @@ async fn main() -> Result<(), std::io::Error> {
|
|||||||
async fn handler(ws: WebSocketUpgrade, Extension(api): Extension<CommandApi>) -> Response {
|
async fn handler(ws: WebSocketUpgrade, Extension(api): Extension<CommandApi>) -> Response {
|
||||||
let (client, out_receiver) = RpcClient::new();
|
let (client, out_receiver) = RpcClient::new();
|
||||||
let session = RpcSession::new(client.clone(), api.clone());
|
let session = RpcSession::new(client.clone(), api.clone());
|
||||||
tokio::spawn(async move {
|
|
||||||
let events = api.accounts.read().await.get_event_emitter();
|
|
||||||
while let Some(event) = events.recv().await {
|
|
||||||
let event = event_to_json_rpc_notification(event);
|
|
||||||
client.send_notification("event", Some(event)).await.ok();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
handle_ws_rpc(ws, out_receiver, session).await
|
handle_ws_rpc(ws, out_receiver, session).await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ async function run() {
|
|||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
for (const [chatId, _messageId] of chats) {
|
for (const chatId of chats) {
|
||||||
const chat = await client.rpc.getFullChatById(selectedAccount, chatId);
|
const chat = await client.rpc.getFullChatById(selectedAccount, chatId);
|
||||||
write($main, `<h3>${chat.name}</h3>`);
|
write($main, `<h3>${chat.name}</h3>`);
|
||||||
const messageIds = await client.rpc.getMessageIds(
|
const messageIds = await client.rpc.getMessageIds(
|
||||||
|
|||||||
@@ -55,5 +55,5 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"types": "dist/deltachat.d.ts",
|
"types": "dist/deltachat.d.ts",
|
||||||
"version": "1.112.5"
|
"version": "1.122.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,28 @@
|
|||||||
import * as T from "../generated/types.js";
|
import * as T from "../generated/types.js";
|
||||||
|
import { EventType } from "../generated/types.js";
|
||||||
import * as RPC from "../generated/jsonrpc.js";
|
import * as RPC from "../generated/jsonrpc.js";
|
||||||
import { RawClient } from "../generated/client.js";
|
import { RawClient } from "../generated/client.js";
|
||||||
import { Event } from "../generated/events.js";
|
|
||||||
import { WebsocketTransport, BaseTransport, Request } from "yerpc";
|
import { WebsocketTransport, BaseTransport, Request } from "yerpc";
|
||||||
import { TinyEmitter } from "@deltachat/tiny-emitter";
|
import { TinyEmitter } from "@deltachat/tiny-emitter";
|
||||||
|
|
||||||
type DCWireEvent<T extends Event> = {
|
type Events = { ALL: (accountId: number, event: EventType) => void } & {
|
||||||
event: T;
|
[Property in EventType["type"]]: (
|
||||||
contextId: number;
|
|
||||||
};
|
|
||||||
// export type Events = Record<
|
|
||||||
// Event["type"] | "ALL",
|
|
||||||
// (event: DeltaChatEvent<Event>) => void
|
|
||||||
// >;
|
|
||||||
|
|
||||||
type Events = { ALL: (accountId: number, event: Event) => void } & {
|
|
||||||
[Property in Event["type"]]: (
|
|
||||||
accountId: number,
|
accountId: number,
|
||||||
event: Extract<Event, { type: Property }>
|
event: Extract<EventType, { type: Property }>
|
||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ContextEvents = { ALL: (event: Event) => void } & {
|
type ContextEvents = { ALL: (event: EventType) => void } & {
|
||||||
[Property in Event["type"]]: (
|
[Property in EventType["type"]]: (
|
||||||
event: Extract<Event, { type: Property }>
|
event: Extract<EventType, { type: Property }>
|
||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DcEvent = Event;
|
export type DcEvent = EventType;
|
||||||
export type DcEventType<T extends Event["type"]> = Extract<Event, { type: T }>;
|
export type DcEventType<T extends EventType["type"]> = Extract<
|
||||||
|
EventType,
|
||||||
|
{ type: T }
|
||||||
|
>;
|
||||||
|
|
||||||
export class BaseDeltaChat<
|
export class BaseDeltaChat<
|
||||||
Transport extends BaseTransport<any>
|
Transport extends BaseTransport<any>
|
||||||
@@ -36,27 +30,34 @@ export class BaseDeltaChat<
|
|||||||
rpc: RawClient;
|
rpc: RawClient;
|
||||||
account?: T.Account;
|
account?: T.Account;
|
||||||
private contextEmitters: { [key: number]: TinyEmitter<ContextEvents> } = {};
|
private contextEmitters: { [key: number]: TinyEmitter<ContextEvents> } = {};
|
||||||
constructor(public transport: Transport) {
|
|
||||||
|
//@ts-ignore
|
||||||
|
private eventTask: Promise<void>;
|
||||||
|
|
||||||
|
constructor(public transport: Transport, startEventLoop: boolean) {
|
||||||
super();
|
super();
|
||||||
this.rpc = new RawClient(this.transport);
|
this.rpc = new RawClient(this.transport);
|
||||||
this.transport.on("request", (request: Request) => {
|
if (startEventLoop) {
|
||||||
const method = request.method;
|
this.eventTask = this.eventLoop();
|
||||||
if (method === "event") {
|
}
|
||||||
const event = request.params! as DCWireEvent<Event>;
|
}
|
||||||
//@ts-ignore
|
|
||||||
this.emit(event.event.type, event.contextId, event.event as any);
|
|
||||||
this.emit("ALL", event.contextId, event.event as any);
|
|
||||||
|
|
||||||
if (this.contextEmitters[event.contextId]) {
|
async eventLoop(): Promise<void> {
|
||||||
this.contextEmitters[event.contextId].emit(
|
while (true) {
|
||||||
event.event.type,
|
const event = await this.rpc.getNextEvent();
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
event.event as any
|
this.emit(event.event.type, event.contextId, event.event);
|
||||||
);
|
this.emit("ALL", event.contextId, event.event);
|
||||||
this.contextEmitters[event.contextId].emit("ALL", event.event);
|
|
||||||
}
|
if (this.contextEmitters[event.contextId]) {
|
||||||
|
this.contextEmitters[event.contextId].emit(
|
||||||
|
event.event.type,
|
||||||
|
//@ts-ignore
|
||||||
|
event.event as any
|
||||||
|
);
|
||||||
|
this.contextEmitters[event.contextId].emit("ALL", event.event as any);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async listAccounts(): Promise<T.Account[]> {
|
async listAccounts(): Promise<T.Account[]> {
|
||||||
@@ -75,10 +76,12 @@ export class BaseDeltaChat<
|
|||||||
|
|
||||||
export type Opts = {
|
export type Opts = {
|
||||||
url: string;
|
url: string;
|
||||||
|
startEventLoop: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_OPTS: Opts = {
|
export const DEFAULT_OPTS: Opts = {
|
||||||
url: "ws://localhost:20808/ws",
|
url: "ws://localhost:20808/ws",
|
||||||
|
startEventLoop: true,
|
||||||
};
|
};
|
||||||
export class DeltaChat extends BaseDeltaChat<WebsocketTransport> {
|
export class DeltaChat extends BaseDeltaChat<WebsocketTransport> {
|
||||||
opts: Opts;
|
opts: Opts;
|
||||||
@@ -86,20 +89,24 @@ export class DeltaChat extends BaseDeltaChat<WebsocketTransport> {
|
|||||||
this.transport.close();
|
this.transport.close();
|
||||||
}
|
}
|
||||||
constructor(opts?: Opts | string) {
|
constructor(opts?: Opts | string) {
|
||||||
if (typeof opts === "string") opts = { url: opts };
|
if (typeof opts === "string") {
|
||||||
if (opts) opts = { ...DEFAULT_OPTS, ...opts };
|
opts = { ...DEFAULT_OPTS, url: opts };
|
||||||
else opts = { ...DEFAULT_OPTS };
|
} else if (opts) {
|
||||||
|
opts = { ...DEFAULT_OPTS, ...opts };
|
||||||
|
} else {
|
||||||
|
opts = { ...DEFAULT_OPTS };
|
||||||
|
}
|
||||||
const transport = new WebsocketTransport(opts.url);
|
const transport = new WebsocketTransport(opts.url);
|
||||||
super(transport);
|
super(transport, opts.startEventLoop);
|
||||||
this.opts = opts;
|
this.opts = opts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StdioDeltaChat extends BaseDeltaChat<StdioTransport> {
|
export class StdioDeltaChat extends BaseDeltaChat<StdioTransport> {
|
||||||
close() {}
|
close() {}
|
||||||
constructor(input: any, output: any) {
|
constructor(input: any, output: any, startEventLoop: boolean) {
|
||||||
const transport = new StdioTransport(input, output);
|
const transport = new StdioTransport(input, output);
|
||||||
super(transport);
|
super(transport, startEventLoop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
export * as RPC from "../generated/jsonrpc.js";
|
export * as RPC from "../generated/jsonrpc.js";
|
||||||
export * as T from "../generated/types.js";
|
export * as T from "../generated/types.js";
|
||||||
export * from "../generated/events.js";
|
|
||||||
export { RawClient } from "../generated/client.js";
|
export { RawClient } from "../generated/client.js";
|
||||||
export * from "./client.js";
|
export * from "./client.js";
|
||||||
export * as yerpc from "yerpc";
|
export * as yerpc from "yerpc";
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ describe("basic tests", () => {
|
|||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
serverHandle = await startServer();
|
serverHandle = await startServer();
|
||||||
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout);
|
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout, true);
|
||||||
// dc.on("ALL", (event) => {
|
// dc.on("ALL", (event) => {
|
||||||
//console.log("event", event);
|
//console.log("event", event);
|
||||||
// });
|
// });
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ describe("online tests", function () {
|
|||||||
this.skip();
|
this.skip();
|
||||||
}
|
}
|
||||||
serverHandle = await startServer();
|
serverHandle = await startServer();
|
||||||
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout);
|
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout, true);
|
||||||
|
|
||||||
dc.on("ALL", (contextId, { type }) => {
|
dc.on("ALL", (contextId, { type }) => {
|
||||||
if (type !== "Info") console.log(contextId, type);
|
if (type !== "Info") console.log(contextId, type);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat-repl"
|
name = "deltachat-repl"
|
||||||
version = "1.112.5"
|
version = "1.122.0"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@@ -8,11 +8,11 @@ edition = "2021"
|
|||||||
ansi_term = "0.12.1"
|
ansi_term = "0.12.1"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
deltachat = { path = "..", features = ["internals"]}
|
deltachat = { path = "..", features = ["internals"]}
|
||||||
dirs = "4"
|
dirs = "5"
|
||||||
log = "0.4.16"
|
log = "0.4.19"
|
||||||
pretty_env_logger = "0.4"
|
pretty_env_logger = "0.5"
|
||||||
rusqlite = "0.28"
|
rusqlite = "0.29"
|
||||||
rustyline = "11"
|
rustyline = "12"
|
||||||
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
|
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
|||||||
DownloadState::Available => " [⬇ Download available]",
|
DownloadState::Available => " [⬇ Download available]",
|
||||||
DownloadState::InProgress => " [⬇ Download in progress...]️",
|
DownloadState::InProgress => " [⬇ Download in progress...]️",
|
||||||
DownloadState::Failure => " [⬇ Download failed]",
|
DownloadState::Failure => " [⬇ Download failed]",
|
||||||
|
DownloadState::Undecipherable => " [⬇ Decryption failed]",
|
||||||
};
|
};
|
||||||
|
|
||||||
let temp2 = timestamp_to_str(msg.get_timestamp());
|
let temp2 = timestamp_to_str(msg.get_timestamp());
|
||||||
@@ -199,7 +200,7 @@ async fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
|
|||||||
if msg.has_location() { "📍" } else { "" },
|
if msg.has_location() { "📍" } else { "" },
|
||||||
&contact_name,
|
&contact_name,
|
||||||
contact_id,
|
contact_id,
|
||||||
msgtext.unwrap_or_default(),
|
msgtext,
|
||||||
if msg.has_html() { "[HAS-HTML]️" } else { "" },
|
if msg.has_html() { "[HAS-HTML]️" } else { "" },
|
||||||
if msg.get_from_id() == ContactId::SELF {
|
if msg.get_from_id() == ContactId::SELF {
|
||||||
""
|
""
|
||||||
@@ -563,7 +564,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
context.maybe_network().await;
|
context.maybe_network().await;
|
||||||
}
|
}
|
||||||
"housekeeping" => {
|
"housekeeping" => {
|
||||||
sql::housekeeping(&context).await.ok_or_log(&context);
|
sql::housekeeping(&context).await.log_err(&context).ok();
|
||||||
}
|
}
|
||||||
"listchats" | "listarchived" | "chats" => {
|
"listchats" | "listarchived" | "chats" => {
|
||||||
let listflags = if arg0 == "listarchived" {
|
let listflags = if arg0 == "listarchived" {
|
||||||
@@ -805,15 +806,30 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
}
|
}
|
||||||
"chatinfo" => {
|
"chatinfo" => {
|
||||||
ensure!(sel_chat.is_some(), "No chat selected.");
|
ensure!(sel_chat.is_some(), "No chat selected.");
|
||||||
|
let sel_chat_id = sel_chat.as_ref().unwrap().get_id();
|
||||||
|
|
||||||
let contacts =
|
let contacts = chat::get_chat_contacts(&context, sel_chat_id).await?;
|
||||||
chat::get_chat_contacts(&context, sel_chat.as_ref().unwrap().get_id()).await?;
|
|
||||||
println!("Memberlist:");
|
println!("Memberlist:");
|
||||||
|
|
||||||
log_contactlist(&context, &contacts).await?;
|
log_contactlist(&context, &contacts).await?;
|
||||||
|
println!("{} contacts", contacts.len());
|
||||||
|
|
||||||
|
let similar_chats = sel_chat_id.get_similar_chat_ids(&context).await?;
|
||||||
|
if !similar_chats.is_empty() {
|
||||||
|
println!("Similar chats: ");
|
||||||
|
for (similar_chat_id, metric) in similar_chats {
|
||||||
|
let similar_chat = Chat::load_from_db(&context, similar_chat_id).await?;
|
||||||
|
println!(
|
||||||
|
"{} (#{}) {:.1}",
|
||||||
|
similar_chat.name,
|
||||||
|
similar_chat_id,
|
||||||
|
100.0 * metric
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"{} contacts\nLocation streaming: {}",
|
"Location streaming: {}",
|
||||||
contacts.len(),
|
|
||||||
location::is_sending_locations_to_chat(
|
location::is_sending_locations_to_chat(
|
||||||
&context,
|
&context,
|
||||||
Some(sel_chat.as_ref().unwrap().get_id())
|
Some(sel_chat.as_ref().unwrap().get_id())
|
||||||
@@ -912,9 +928,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
Viewtype::File
|
Viewtype::File
|
||||||
});
|
});
|
||||||
msg.set_file(arg1, None);
|
msg.set_file(arg1, None);
|
||||||
if !arg2.is_empty() {
|
msg.set_text(arg2.to_string());
|
||||||
msg.set_text(Some(arg2.to_string()));
|
|
||||||
}
|
|
||||||
chat::send_msg(&context, sel_chat.as_ref().unwrap().get_id(), &mut msg).await?;
|
chat::send_msg(&context, sel_chat.as_ref().unwrap().get_id(), &mut msg).await?;
|
||||||
}
|
}
|
||||||
"sendhtml" => {
|
"sendhtml" => {
|
||||||
@@ -926,11 +940,11 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
|
|
||||||
let mut msg = Message::new(Viewtype::Text);
|
let mut msg = Message::new(Viewtype::Text);
|
||||||
msg.set_html(Some(html.to_string()));
|
msg.set_html(Some(html.to_string()));
|
||||||
msg.set_text(Some(if arg2.is_empty() {
|
msg.set_text(if arg2.is_empty() {
|
||||||
path.file_name().unwrap().to_string_lossy().to_string()
|
path.file_name().unwrap().to_string_lossy().to_string()
|
||||||
} else {
|
} else {
|
||||||
arg2.to_string()
|
arg2.to_string()
|
||||||
}));
|
});
|
||||||
chat::send_msg(&context, sel_chat.as_ref().unwrap().get_id(), &mut msg).await?;
|
chat::send_msg(&context, sel_chat.as_ref().unwrap().get_id(), &mut msg).await?;
|
||||||
}
|
}
|
||||||
"sendsyncmsg" => match context.send_sync_msg().await? {
|
"sendsyncmsg" => match context.send_sync_msg().await? {
|
||||||
@@ -979,7 +993,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
|
|
||||||
if !arg1.is_empty() {
|
if !arg1.is_empty() {
|
||||||
let mut draft = Message::new(Viewtype::Text);
|
let mut draft = Message::new(Viewtype::Text);
|
||||||
draft.set_text(Some(arg1.to_string()));
|
draft.set_text(arg1.to_string());
|
||||||
sel_chat
|
sel_chat
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -1003,7 +1017,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
"Please specify text to add as device message."
|
"Please specify text to add as device message."
|
||||||
);
|
);
|
||||||
let mut msg = Message::new(Viewtype::Text);
|
let mut msg = Message::new(Viewtype::Text);
|
||||||
msg.set_text(Some(arg1.to_string()));
|
msg.set_text(arg1.to_string());
|
||||||
chat::add_device_msg(&context, None, Some(&mut msg)).await?;
|
chat::add_device_msg(&context, None, Some(&mut msg)).await?;
|
||||||
}
|
}
|
||||||
"listmedia" => {
|
"listmedia" => {
|
||||||
@@ -1090,7 +1104,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
"msginfo" => {
|
"msginfo" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||||
let id = MsgId::new(arg1.parse()?);
|
let id = MsgId::new(arg1.parse()?);
|
||||||
let res = message::get_msg_info(&context, id).await?;
|
let res = id.get_info(&context).await?;
|
||||||
println!("{res}");
|
println!("{res}");
|
||||||
}
|
}
|
||||||
"download" => {
|
"download" => {
|
||||||
|
|||||||
373
deltachat-rpc-client/LICENSE
Normal file
373
deltachat-rpc-client/LICENSE
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
Mozilla Public License Version 2.0
|
||||||
|
==================================
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
--------------
|
||||||
|
|
||||||
|
1.1. "Contributor"
|
||||||
|
means each individual or legal entity that creates, contributes to
|
||||||
|
the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution"
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software"
|
||||||
|
means Source Code Form to which the initial Contributor has attached
|
||||||
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
--------------
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 6. Disclaimer of Warranty *
|
||||||
|
* ------------------------- *
|
||||||
|
* *
|
||||||
|
* Covered Software is provided under this License on an "as is" *
|
||||||
|
* basis, without warranty of any kind, either expressed, implied, or *
|
||||||
|
* statutory, including, without limitation, warranties that the *
|
||||||
|
* Covered Software is free of defects, merchantable, fit for a *
|
||||||
|
* particular purpose or non-infringing. The entire risk as to the *
|
||||||
|
* quality and performance of the Covered Software is with You. *
|
||||||
|
* Should any Covered Software prove defective in any respect, You *
|
||||||
|
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||||
|
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||||
|
* essential part of this License. No use of any Covered Software is *
|
||||||
|
* authorized under this License except under this disclaimer. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 7. Limitation of Liability *
|
||||||
|
* -------------------------- *
|
||||||
|
* *
|
||||||
|
* Under no circumstances and under no legal theory, whether tort *
|
||||||
|
* (including negligence), contract, or otherwise, shall any *
|
||||||
|
* Contributor, or anyone who distributes Covered Software as *
|
||||||
|
* permitted above, be liable to You for any direct, indirect, *
|
||||||
|
* special, incidental, or consequential damages of any character *
|
||||||
|
* including, without limitation, damages for lost profits, loss of *
|
||||||
|
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||||
|
* and all other commercial damages or losses, even if such party *
|
||||||
|
* shall have been informed of the possibility of such damages. This *
|
||||||
|
* limitation of liability shall not apply to liability for death or *
|
||||||
|
* personal injury resulting from such party's negligence to the *
|
||||||
|
* extent applicable law prohibits such limitation. Some *
|
||||||
|
* jurisdictions do not allow the exclusion or limitation of *
|
||||||
|
* incidental or consequential damages, so this exclusion and *
|
||||||
|
* limitation may not apply to You. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
||||||
@@ -5,9 +5,23 @@ and provides asynchronous interface to it.
|
|||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
To use Delta Chat RPC client, first build a `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
|
To use Delta Chat RPC client, first build a `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`
|
||||||
|
or download a prebuilt release.
|
||||||
Install it anywhere in your `PATH`.
|
Install it anywhere in your `PATH`.
|
||||||
|
|
||||||
|
[Create a virtual environment](https://docs.python.org/3/library/venv.html)
|
||||||
|
if you don't have one already and activate it.
|
||||||
|
```
|
||||||
|
$ python -m venv env
|
||||||
|
$ . env/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
Install `deltachat-rpc-client` from source:
|
||||||
|
```
|
||||||
|
$ cd deltachat-rpc-client
|
||||||
|
$ pip install .
|
||||||
|
```
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
1. Build `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
|
1. Build `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from deltachat_rpc_client import DeltaChat, EventType, Rpc
|
from deltachat_rpc_client import DeltaChat, EventType, Rpc, SpecialContactId
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
@@ -30,9 +30,9 @@ async def main():
|
|||||||
await deltachat.start_io()
|
await deltachat.start_io()
|
||||||
|
|
||||||
async def process_messages():
|
async def process_messages():
|
||||||
for message in await account.get_fresh_messages_in_arrival_order():
|
for message in await account.get_next_messages():
|
||||||
snapshot = await message.get_snapshot()
|
snapshot = await message.get_snapshot()
|
||||||
if not snapshot.is_bot and not snapshot.is_info:
|
if snapshot.from_id != SpecialContactId.SELF and not snapshot.is_bot and not snapshot.is_info:
|
||||||
await snapshot.chat.send_text(snapshot.text)
|
await snapshot.chat.send_text(snapshot.text)
|
||||||
await snapshot.message.mark_seen()
|
await snapshot.message.mark_seen()
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,22 @@ build-backend = "setuptools.build_meta"
|
|||||||
name = "deltachat-rpc-client"
|
name = "deltachat-rpc-client"
|
||||||
description = "Python client for Delta Chat core JSON-RPC interface"
|
description = "Python client for Delta Chat core JSON-RPC interface"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aiohttp",
|
"aiohttp"
|
||||||
"aiodns"
|
]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
"Framework :: AsyncIO",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
|
||||||
|
"Operating System :: POSIX :: Linux",
|
||||||
|
"Operating System :: MacOS :: MacOS X",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.8",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Topic :: Communications :: Chat",
|
||||||
|
"Topic :: Communications :: Email"
|
||||||
]
|
]
|
||||||
dynamic = [
|
dynamic = [
|
||||||
"version"
|
"version"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from ._utils import AttrDict, run_bot_cli, run_client_cli
|
|||||||
from .account import Account
|
from .account import Account
|
||||||
from .chat import Chat
|
from .chat import Chat
|
||||||
from .client import Bot, Client
|
from .client import Bot, Client
|
||||||
from .const import EventType
|
from .const import EventType, SpecialContactId
|
||||||
from .contact import Contact
|
from .contact import Contact
|
||||||
from .deltachat import DeltaChat
|
from .deltachat import DeltaChat
|
||||||
from .message import Message
|
from .message import Message
|
||||||
@@ -19,6 +19,7 @@ __all__ = [
|
|||||||
"DeltaChat",
|
"DeltaChat",
|
||||||
"EventType",
|
"EventType",
|
||||||
"Message",
|
"Message",
|
||||||
|
"SpecialContactId",
|
||||||
"Rpc",
|
"Rpc",
|
||||||
"run_bot_cli",
|
"run_bot_cli",
|
||||||
"run_client_cli",
|
"run_client_cli",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING, List, Optional, Tuple, Union
|
from typing import TYPE_CHECKING, List, Optional, Tuple, Union
|
||||||
|
from warnings import warn
|
||||||
|
|
||||||
from ._utils import AttrDict
|
from ._utils import AttrDict
|
||||||
from .chat import Chat
|
from .chat import Chat
|
||||||
@@ -176,7 +177,7 @@ class Account:
|
|||||||
|
|
||||||
entries = await self._rpc.get_chatlist_entries(self.id, flags, query, contact and contact.id)
|
entries = await self._rpc.get_chatlist_entries(self.id, flags, query, contact and contact.id)
|
||||||
if not snapshot:
|
if not snapshot:
|
||||||
return [Chat(self, entry[0]) for entry in entries]
|
return [Chat(self, entry) for entry in entries]
|
||||||
|
|
||||||
items = await self._rpc.get_chatlist_items_by_entries(self.id, entries)
|
items = await self._rpc.get_chatlist_items_by_entries(self.id, entries)
|
||||||
chats = []
|
chats = []
|
||||||
@@ -239,7 +240,30 @@ class Account:
|
|||||||
fresh_msg_ids = await self._rpc.get_fresh_msgs(self.id)
|
fresh_msg_ids = await self._rpc.get_fresh_msgs(self.id)
|
||||||
return [Message(self, msg_id) for msg_id in fresh_msg_ids]
|
return [Message(self, msg_id) for msg_id in fresh_msg_ids]
|
||||||
|
|
||||||
|
async def get_next_messages(self) -> List[Message]:
|
||||||
|
"""Return a list of next messages."""
|
||||||
|
next_msg_ids = await self._rpc.get_next_msgs(self.id)
|
||||||
|
return [Message(self, msg_id) for msg_id in next_msg_ids]
|
||||||
|
|
||||||
|
async def wait_next_messages(self) -> List[Message]:
|
||||||
|
"""Wait for new messages and return a list of them."""
|
||||||
|
next_msg_ids = await self._rpc.wait_next_msgs(self.id)
|
||||||
|
return [Message(self, msg_id) for msg_id in next_msg_ids]
|
||||||
|
|
||||||
async def get_fresh_messages_in_arrival_order(self) -> List[Message]:
|
async def get_fresh_messages_in_arrival_order(self) -> List[Message]:
|
||||||
"""Return fresh messages list sorted in the order of their arrival, with ascending IDs."""
|
"""Return fresh messages list sorted in the order of their arrival, with ascending IDs."""
|
||||||
|
warn(
|
||||||
|
"get_fresh_messages_in_arrival_order is deprecated, use get_next_messages instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
fresh_msg_ids = sorted(await self._rpc.get_fresh_msgs(self.id))
|
fresh_msg_ids = sorted(await self._rpc.get_fresh_msgs(self.id))
|
||||||
return [Message(self, msg_id) for msg_id in fresh_msg_ids]
|
return [Message(self, msg_id) for msg_id in fresh_msg_ids]
|
||||||
|
|
||||||
|
async def export_backup(self, path, passphrase: str = "") -> None:
|
||||||
|
"""Export backup."""
|
||||||
|
await self._rpc.export_backup(self.id, str(path), passphrase)
|
||||||
|
|
||||||
|
async def import_backup(self, path, passphrase: str = "") -> None:
|
||||||
|
"""Import backup."""
|
||||||
|
await self._rpc.import_backup(self.id, str(path), passphrase)
|
||||||
|
|||||||
@@ -105,6 +105,10 @@ class Chat:
|
|||||||
info = await self._rpc.get_full_chat_by_id(self.account.id, self.id)
|
info = await self._rpc.get_full_chat_by_id(self.account.id, self.id)
|
||||||
return AttrDict(chat=self, **info)
|
return AttrDict(chat=self, **info)
|
||||||
|
|
||||||
|
async def can_send(self) -> bool:
|
||||||
|
"""Return true if messages can be sent to the chat."""
|
||||||
|
return await self._rpc.can_send(self.account.id, self.id)
|
||||||
|
|
||||||
async def send_message(
|
async def send_message(
|
||||||
self,
|
self,
|
||||||
text: Optional[str] = None,
|
text: Optional[str] = None,
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from ._utils import (
|
|||||||
parse_system_image_changed,
|
parse_system_image_changed,
|
||||||
parse_system_title_changed,
|
parse_system_title_changed,
|
||||||
)
|
)
|
||||||
from .const import COMMAND_PREFIX, EventType, SystemMessageType
|
from .const import COMMAND_PREFIX, EventType, SpecialContactId, SystemMessageType
|
||||||
from .events import (
|
from .events import (
|
||||||
EventFilter,
|
EventFilter,
|
||||||
GroupImageChanged,
|
GroupImageChanged,
|
||||||
@@ -189,9 +189,10 @@ class Client:
|
|||||||
|
|
||||||
async def _process_messages(self) -> None:
|
async def _process_messages(self) -> None:
|
||||||
if self._should_process_messages:
|
if self._should_process_messages:
|
||||||
for message in await self.account.get_fresh_messages_in_arrival_order():
|
for message in await self.account.get_next_messages():
|
||||||
snapshot = await message.get_snapshot()
|
snapshot = await message.get_snapshot()
|
||||||
await self._on_new_msg(snapshot)
|
if snapshot.from_id not in [SpecialContactId.SELF, SpecialContactId.DEVICE]:
|
||||||
|
await self._on_new_msg(snapshot)
|
||||||
if snapshot.is_info and snapshot.system_message_type != SystemMessageType.WEBXDC_INFO_MESSAGE:
|
if snapshot.is_info and snapshot.system_message_type != SystemMessageType.WEBXDC_INFO_MESSAGE:
|
||||||
await self._handle_info_msg(snapshot)
|
await self._handle_info_msg(snapshot)
|
||||||
await snapshot.message.mark_seen()
|
await snapshot.message.mark_seen()
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class EventType(str, Enum):
|
|||||||
MSG_DELIVERED = "MsgDelivered"
|
MSG_DELIVERED = "MsgDelivered"
|
||||||
MSG_FAILED = "MsgFailed"
|
MSG_FAILED = "MsgFailed"
|
||||||
MSG_READ = "MsgRead"
|
MSG_READ = "MsgRead"
|
||||||
|
MSG_DELETED = "MsgDeleted"
|
||||||
CHAT_MODIFIED = "ChatModified"
|
CHAT_MODIFIED = "ChatModified"
|
||||||
CHAT_EPHEMERAL_TIMER_MODIFIED = "ChatEphemeralTimerModified"
|
CHAT_EPHEMERAL_TIMER_MODIFIED = "ChatEphemeralTimerModified"
|
||||||
CONTACTS_CHANGED = "ContactsChanged"
|
CONTACTS_CHANGED = "ContactsChanged"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING, Union
|
from typing import TYPE_CHECKING, Optional, Union
|
||||||
|
|
||||||
from ._utils import AttrDict
|
from ._utils import AttrDict
|
||||||
from .contact import Contact
|
from .contact import Contact
|
||||||
@@ -35,6 +35,13 @@ class Message:
|
|||||||
snapshot["message"] = self
|
snapshot["message"] = self
|
||||||
return snapshot
|
return snapshot
|
||||||
|
|
||||||
|
async def get_reactions(self) -> Optional[AttrDict]:
|
||||||
|
"""Get message reactions."""
|
||||||
|
reactions = await self._rpc.get_message_reactions(self.account.id, self.id)
|
||||||
|
if reactions:
|
||||||
|
return AttrDict(reactions)
|
||||||
|
return None
|
||||||
|
|
||||||
async def mark_seen(self) -> None:
|
async def mark_seen(self) -> None:
|
||||||
"""Mark the message as seen."""
|
"""Mark the message as seen."""
|
||||||
await self._rpc.markseen_msgs(self.account.id, [self.id])
|
await self._rpc.markseen_msgs(self.account.id, [self.id])
|
||||||
|
|||||||
@@ -16,9 +16,8 @@ async def get_temp_credentials() -> dict:
|
|||||||
|
|
||||||
# Replace default 5 minute timeout with a 1 minute timeout.
|
# Replace default 5 minute timeout with a 1 minute timeout.
|
||||||
timeout = aiohttp.ClientTimeout(total=60)
|
timeout = aiohttp.ClientTimeout(total=60)
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session, session.post(url, timeout=timeout) as response:
|
||||||
async with session.post(url, timeout=timeout) as response:
|
return json.loads(await response.text())
|
||||||
return json.loads(await response.text())
|
|
||||||
|
|
||||||
|
|
||||||
class ACFactory:
|
class ACFactory:
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ class Rpc:
|
|||||||
self.event_queues: Dict[int, asyncio.Queue]
|
self.event_queues: Dict[int, asyncio.Queue]
|
||||||
# Map from request ID to `asyncio.Future` returning the response.
|
# Map from request ID to `asyncio.Future` returning the response.
|
||||||
self.request_events: Dict[int, asyncio.Future]
|
self.request_events: Dict[int, asyncio.Future]
|
||||||
|
self.closing: bool
|
||||||
self.reader_task: asyncio.Task
|
self.reader_task: asyncio.Task
|
||||||
|
self.events_task: asyncio.Task
|
||||||
|
|
||||||
async def start(self) -> None:
|
async def start(self) -> None:
|
||||||
self.process = await asyncio.create_subprocess_exec(
|
self.process = await asyncio.create_subprocess_exec(
|
||||||
@@ -35,10 +37,15 @@ class Rpc:
|
|||||||
self.id = 0
|
self.id = 0
|
||||||
self.event_queues = {}
|
self.event_queues = {}
|
||||||
self.request_events = {}
|
self.request_events = {}
|
||||||
|
self.closing = False
|
||||||
self.reader_task = asyncio.create_task(self.reader_loop())
|
self.reader_task = asyncio.create_task(self.reader_loop())
|
||||||
|
self.events_task = asyncio.create_task(self.events_loop())
|
||||||
|
|
||||||
async def close(self) -> None:
|
async def close(self) -> None:
|
||||||
"""Terminate RPC server process and wait until the reader loop finishes."""
|
"""Terminate RPC server process and wait until the reader loop finishes."""
|
||||||
|
self.closing = True
|
||||||
|
await self.stop_io_for_all_accounts()
|
||||||
|
await self.events_task
|
||||||
self.process.terminate()
|
self.process.terminate()
|
||||||
await self.reader_task
|
await self.reader_task
|
||||||
|
|
||||||
@@ -58,33 +65,38 @@ class Rpc:
|
|||||||
if "id" in response:
|
if "id" in response:
|
||||||
fut = self.request_events.pop(response["id"])
|
fut = self.request_events.pop(response["id"])
|
||||||
fut.set_result(response)
|
fut.set_result(response)
|
||||||
elif response["method"] == "event":
|
|
||||||
# An event notification.
|
|
||||||
params = response["params"]
|
|
||||||
account_id = params["contextId"]
|
|
||||||
if account_id not in self.event_queues:
|
|
||||||
self.event_queues[account_id] = asyncio.Queue()
|
|
||||||
await self.event_queues[account_id].put(params["event"])
|
|
||||||
else:
|
else:
|
||||||
print(response)
|
print(response)
|
||||||
|
|
||||||
|
async def get_queue(self, account_id: int) -> asyncio.Queue:
|
||||||
|
if account_id not in self.event_queues:
|
||||||
|
self.event_queues[account_id] = asyncio.Queue()
|
||||||
|
return self.event_queues[account_id]
|
||||||
|
|
||||||
|
async def events_loop(self) -> None:
|
||||||
|
"""Requests new events and distributes them between queues."""
|
||||||
|
while True:
|
||||||
|
if self.closing:
|
||||||
|
return
|
||||||
|
event = await self.get_next_event()
|
||||||
|
account_id = event["contextId"]
|
||||||
|
queue = await self.get_queue(account_id)
|
||||||
|
await queue.put(event["event"])
|
||||||
|
|
||||||
async def wait_for_event(self, account_id: int) -> Optional[dict]:
|
async def wait_for_event(self, account_id: int) -> Optional[dict]:
|
||||||
"""Waits for the next event from the given account and returns it."""
|
"""Waits for the next event from the given account and returns it."""
|
||||||
if account_id in self.event_queues:
|
queue = await self.get_queue(account_id)
|
||||||
return await self.event_queues[account_id].get()
|
return await queue.get()
|
||||||
return None
|
|
||||||
|
|
||||||
def __getattr__(self, attr: str):
|
def __getattr__(self, attr: str):
|
||||||
async def method(*args, **kwargs) -> Any:
|
async def method(*args) -> Any:
|
||||||
self.id += 1
|
self.id += 1
|
||||||
request_id = self.id
|
request_id = self.id
|
||||||
|
|
||||||
assert not (args and kwargs), "Mixing positional and keyword arguments"
|
|
||||||
|
|
||||||
request = {
|
request = {
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"method": attr,
|
"method": attr,
|
||||||
"params": kwargs or args,
|
"params": args,
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
}
|
}
|
||||||
data = (json.dumps(request) + "\n").encode()
|
data = (json.dumps(request) + "\n").encode()
|
||||||
|
|||||||
@@ -98,8 +98,18 @@ async def test_account(acfactory) -> None:
|
|||||||
assert await alice.get_chatlist()
|
assert await alice.get_chatlist()
|
||||||
assert await alice.get_chatlist(snapshot=True)
|
assert await alice.get_chatlist(snapshot=True)
|
||||||
assert await alice.get_qr_code()
|
assert await alice.get_qr_code()
|
||||||
await alice.get_fresh_messages()
|
assert await alice.get_fresh_messages()
|
||||||
await alice.get_fresh_messages_in_arrival_order()
|
assert await alice.get_next_messages()
|
||||||
|
|
||||||
|
# Test sending empty message.
|
||||||
|
assert len(await bob.wait_next_messages()) == 0
|
||||||
|
await alice_chat_bob.send_text("")
|
||||||
|
messages = await bob.wait_next_messages()
|
||||||
|
assert len(messages) == 1
|
||||||
|
message = messages[0]
|
||||||
|
snapshot = await message.get_snapshot()
|
||||||
|
assert snapshot.text == ""
|
||||||
|
await bob.mark_seen_messages([message])
|
||||||
|
|
||||||
group = await alice.create_group("test group")
|
group = await alice.create_group("test group")
|
||||||
await group.add_contact(alice_contact_bob)
|
await group.add_contact(alice_contact_bob)
|
||||||
@@ -147,7 +157,9 @@ async def test_chat(acfactory) -> None:
|
|||||||
assert alice_chat_bob != bob_chat_alice
|
assert alice_chat_bob != bob_chat_alice
|
||||||
assert repr(alice_chat_bob)
|
assert repr(alice_chat_bob)
|
||||||
await alice_chat_bob.delete()
|
await alice_chat_bob.delete()
|
||||||
|
assert not await bob_chat_alice.can_send()
|
||||||
await bob_chat_alice.accept()
|
await bob_chat_alice.accept()
|
||||||
|
assert await bob_chat_alice.can_send()
|
||||||
await bob_chat_alice.block()
|
await bob_chat_alice.block()
|
||||||
bob_chat_alice = await snapshot.sender.create_chat()
|
bob_chat_alice = await snapshot.sender.create_chat()
|
||||||
await bob_chat_alice.mute()
|
await bob_chat_alice.mute()
|
||||||
@@ -237,6 +249,10 @@ async def test_message(acfactory) -> None:
|
|||||||
|
|
||||||
await message.mark_seen()
|
await message.mark_seen()
|
||||||
await message.send_reaction("😎")
|
await message.send_reaction("😎")
|
||||||
|
reactions = await message.get_reactions()
|
||||||
|
assert reactions
|
||||||
|
snapshot = await message.get_snapshot()
|
||||||
|
assert reactions == snapshot.reactions
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio()
|
@pytest.mark.asyncio()
|
||||||
@@ -303,3 +319,39 @@ async def test_bot(acfactory) -> None:
|
|||||||
await acfactory.process_message(from_account=user, to_client=bot, text="hello")
|
await acfactory.process_message(from_account=user, to_client=bot, text="hello")
|
||||||
event = await acfactory.process_message(from_account=user, to_client=bot, text="/help")
|
event = await acfactory.process_message(from_account=user, to_client=bot, text="/help")
|
||||||
mock.hook.assert_called_once_with(event.msg_id)
|
mock.hook.assert_called_once_with(event.msg_id)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio()
|
||||||
|
async def test_wait_next_messages(acfactory) -> None:
|
||||||
|
alice = await acfactory.new_configured_account()
|
||||||
|
|
||||||
|
# Create a bot account so it does not receive device messages in the beginning.
|
||||||
|
bot = await acfactory.new_preconfigured_account()
|
||||||
|
await bot.set_config("bot", "1")
|
||||||
|
await bot.configure()
|
||||||
|
|
||||||
|
# There are no old messages and the call returns immediately.
|
||||||
|
assert not await bot.wait_next_messages()
|
||||||
|
|
||||||
|
# Bot starts waiting for messages.
|
||||||
|
next_messages_task = asyncio.create_task(bot.wait_next_messages())
|
||||||
|
|
||||||
|
bot_addr = await bot.get_config("addr")
|
||||||
|
alice_contact_bot = await alice.create_contact(bot_addr, "Bob")
|
||||||
|
alice_chat_bot = await alice_contact_bot.create_chat()
|
||||||
|
await alice_chat_bot.send_text("Hello!")
|
||||||
|
|
||||||
|
next_messages = await next_messages_task
|
||||||
|
assert len(next_messages) == 1
|
||||||
|
snapshot = await next_messages[0].get_snapshot()
|
||||||
|
assert snapshot.text == "Hello!"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio()
|
||||||
|
async def test_import_export(acfactory, tmp_path) -> None:
|
||||||
|
alice = await acfactory.new_configured_account()
|
||||||
|
await alice.export_backup(tmp_path)
|
||||||
|
|
||||||
|
files = list(tmp_path.glob("*.tar"))
|
||||||
|
alice2 = await acfactory.get_unconfigured_account()
|
||||||
|
await alice2.import_backup(files[0])
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "deltachat-rpc-server"
|
name = "deltachat-rpc-server"
|
||||||
version = "1.112.5"
|
version = "1.122.0"
|
||||||
description = "DeltaChat JSON-RPC server"
|
description = "DeltaChat JSON-RPC server"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -15,12 +15,13 @@ deltachat = { path = "..", default-features = false }
|
|||||||
|
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
env_logger = { version = "0.10.0" }
|
env_logger = { version = "0.10.0" }
|
||||||
futures-lite = "1.12.0"
|
futures-lite = "1.13.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
serde_json = "1.0.91"
|
serde_json = "1.0.99"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tokio = { version = "1.25.0", features = ["io-std"] }
|
tokio = { version = "1.29.1", features = ["io-std"] }
|
||||||
yerpc = { version = "0.4.0", features = ["anyhow_expose"] }
|
tokio-util = "0.7.8"
|
||||||
|
yerpc = { version = "0.5.1", features = ["anyhow_expose"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["vendored"]
|
default = ["vendored"]
|
||||||
|
|||||||
@@ -1,20 +1,35 @@
|
|||||||
|
//! Delta Chat core RPC server.
|
||||||
|
//!
|
||||||
|
//! It speaks JSON Lines over stdio.
|
||||||
use std::env;
|
use std::env;
|
||||||
///! Delta Chat core RPC server.
|
|
||||||
///!
|
|
||||||
///! It speaks JSON Lines over stdio.
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use deltachat::constants::DC_VERSION_STR;
|
use deltachat::constants::DC_VERSION_STR;
|
||||||
use deltachat_jsonrpc::api::events::event_to_json_rpc_notification;
|
|
||||||
use deltachat_jsonrpc::api::{Accounts, CommandApi};
|
use deltachat_jsonrpc::api::{Accounts, CommandApi};
|
||||||
use futures_lite::stream::StreamExt;
|
use futures_lite::stream::StreamExt;
|
||||||
use tokio::io::{self, AsyncBufReadExt, BufReader};
|
use tokio::io::{self, AsyncBufReadExt, BufReader};
|
||||||
|
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
use tokio::signal::unix as signal_unix;
|
||||||
|
|
||||||
|
use tokio::sync::RwLock;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
use yerpc::{RpcClient, RpcSession};
|
use yerpc::{RpcClient, RpcSession};
|
||||||
|
|
||||||
#[tokio::main(flavor = "multi_thread")]
|
#[tokio::main(flavor = "multi_thread")]
|
||||||
async fn main() -> Result<()> {
|
async fn main() {
|
||||||
|
let r = main_impl().await;
|
||||||
|
// From tokio documentation:
|
||||||
|
// "For technical reasons, stdin is implemented by using an ordinary blocking read on a separate
|
||||||
|
// thread, and it is impossible to cancel that read. This can make shutdown of the runtime hang
|
||||||
|
// until the user presses enter."
|
||||||
|
std::process::exit(if r.is_ok() { 0 } else { 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn main_impl() -> Result<()> {
|
||||||
let mut args = env::args_os();
|
let mut args = env::args_os();
|
||||||
let _program_name = args.next().context("no command line arguments found")?;
|
let _program_name = args.next().context("no command line arguments found")?;
|
||||||
if let Some(first_arg) = args.next() {
|
if let Some(first_arg) = args.next() {
|
||||||
@@ -32,59 +47,97 @@ async fn main() -> Result<()> {
|
|||||||
return Err(anyhow!("Unrecognized argument {:?}", arg));
|
return Err(anyhow!("Unrecognized argument {:?}", arg));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Install signal handlers early so that the shutdown is graceful starting from here.
|
||||||
|
let _ctrl_c = tokio::signal::ctrl_c();
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
let mut sigterm = signal_unix::signal(signal_unix::SignalKind::terminate())?;
|
||||||
|
|
||||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||||
|
|
||||||
let path = std::env::var("DC_ACCOUNTS_PATH").unwrap_or_else(|_| "accounts".to_string());
|
let path = std::env::var("DC_ACCOUNTS_PATH").unwrap_or_else(|_| "accounts".to_string());
|
||||||
log::info!("Starting with accounts directory `{}`.", path);
|
log::info!("Starting with accounts directory `{}`.", path);
|
||||||
let accounts = Accounts::new(PathBuf::from(&path)).await?;
|
let accounts = Accounts::new(PathBuf::from(&path)).await?;
|
||||||
let events = accounts.get_event_emitter();
|
|
||||||
|
|
||||||
log::info!("Creating JSON-RPC API.");
|
log::info!("Creating JSON-RPC API.");
|
||||||
let state = CommandApi::new(accounts);
|
let accounts = Arc::new(RwLock::new(accounts));
|
||||||
|
let state = CommandApi::from_arc(accounts.clone());
|
||||||
|
|
||||||
let (client, mut out_receiver) = RpcClient::new();
|
let (client, mut out_receiver) = RpcClient::new();
|
||||||
let session = RpcSession::new(client.clone(), state);
|
let session = RpcSession::new(client.clone(), state.clone());
|
||||||
|
let main_cancel = CancellationToken::new();
|
||||||
// Events task converts core events to JSON-RPC notifications.
|
|
||||||
let events_task: JoinHandle<Result<()>> = tokio::spawn(async move {
|
|
||||||
while let Some(event) = events.recv().await {
|
|
||||||
let event = event_to_json_rpc_notification(event);
|
|
||||||
client.send_notification("event", Some(event)).await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send task prints JSON responses to stdout.
|
// Send task prints JSON responses to stdout.
|
||||||
|
let cancel = main_cancel.clone();
|
||||||
let send_task: JoinHandle<anyhow::Result<()>> = tokio::spawn(async move {
|
let send_task: JoinHandle<anyhow::Result<()>> = tokio::spawn(async move {
|
||||||
while let Some(message) = out_receiver.next().await {
|
let _cancel_guard = cancel.clone().drop_guard();
|
||||||
let message = serde_json::to_string(&message)?;
|
loop {
|
||||||
|
let message = tokio::select! {
|
||||||
|
_ = cancel.cancelled() => break,
|
||||||
|
message = out_receiver.next() => match message {
|
||||||
|
None => break,
|
||||||
|
Some(message) => serde_json::to_string(&message)?,
|
||||||
|
}
|
||||||
|
};
|
||||||
log::trace!("RPC send {}", message);
|
log::trace!("RPC send {}", message);
|
||||||
println!("{message}");
|
println!("{message}");
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let cancel = main_cancel.clone();
|
||||||
|
let sigterm_task: JoinHandle<anyhow::Result<()>> = tokio::spawn(async move {
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
{
|
||||||
|
let _cancel_guard = cancel.clone().drop_guard();
|
||||||
|
tokio::select! {
|
||||||
|
_ = cancel.cancelled() => (),
|
||||||
|
_ = sigterm.recv() => {
|
||||||
|
log::info!("got SIGTERM");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = cancel;
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
// Receiver task reads JSON requests from stdin.
|
// Receiver task reads JSON requests from stdin.
|
||||||
|
let cancel = main_cancel.clone();
|
||||||
let recv_task: JoinHandle<anyhow::Result<()>> = tokio::spawn(async move {
|
let recv_task: JoinHandle<anyhow::Result<()>> = tokio::spawn(async move {
|
||||||
|
let _cancel_guard = cancel.clone().drop_guard();
|
||||||
let stdin = io::stdin();
|
let stdin = io::stdin();
|
||||||
let mut lines = BufReader::new(stdin).lines();
|
let mut lines = BufReader::new(stdin).lines();
|
||||||
while let Some(message) = lines.next_line().await? {
|
|
||||||
|
loop {
|
||||||
|
let message = tokio::select! {
|
||||||
|
_ = cancel.cancelled() => break,
|
||||||
|
_ = tokio::signal::ctrl_c() => {
|
||||||
|
log::info!("got ctrl-c event");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
message = lines.next_line() => match message? {
|
||||||
|
None => {
|
||||||
|
log::info!("EOF reached on stdin");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Some(message) => message,
|
||||||
|
}
|
||||||
|
};
|
||||||
log::trace!("RPC recv {}", message);
|
log::trace!("RPC recv {}", message);
|
||||||
let session = session.clone();
|
let session = session.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
session.handle_incoming(&message).await;
|
session.handle_incoming(&message).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
log::info!("EOF reached on stdin");
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wait for the end of stdin.
|
main_cancel.cancelled().await;
|
||||||
|
accounts.read().await.stop_io().await;
|
||||||
|
drop(accounts);
|
||||||
|
drop(state);
|
||||||
|
send_task.await??;
|
||||||
|
sigterm_task.await??;
|
||||||
recv_task.await??;
|
recv_task.await??;
|
||||||
|
|
||||||
// Shutdown the server.
|
|
||||||
send_task.abort();
|
|
||||||
events_task.abort();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,5 +8,5 @@ license = "MPL-2.0"
|
|||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
syn = "1"
|
syn = "2"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
|
|||||||
53
deny.toml
53
deny.toml
@@ -2,9 +2,7 @@
|
|||||||
unmaintained = "allow"
|
unmaintained = "allow"
|
||||||
ignore = [
|
ignore = [
|
||||||
"RUSTSEC-2020-0071",
|
"RUSTSEC-2020-0071",
|
||||||
|
"RUSTSEC-2022-0093",
|
||||||
# Only affects windows if using non-default allocator (and unmaintained).
|
|
||||||
"RUSTSEC-2021-0145",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[bans]
|
[bans]
|
||||||
@@ -13,36 +11,55 @@ ignore = [
|
|||||||
# when upgrading.
|
# when upgrading.
|
||||||
# Please keep this list alphabetically sorted.
|
# Please keep this list alphabetically sorted.
|
||||||
skip = [
|
skip = [
|
||||||
|
{ name = "ahash", version = "0.7.6" },
|
||||||
|
{ name = "base16ct", version = "0.1.1" },
|
||||||
{ name = "base64", version = "<0.21" },
|
{ name = "base64", version = "<0.21" },
|
||||||
|
{ name = "bitflags", version = "1.3.2" },
|
||||||
{ name = "block-buffer", version = "<0.10" },
|
{ name = "block-buffer", version = "<0.10" },
|
||||||
{ name = "clap", version = "3.2.23" },
|
|
||||||
{ name = "clap_lex", version = "0.2.4" },
|
|
||||||
{ name = "convert_case", version = "0.4.0" },
|
{ name = "convert_case", version = "0.4.0" },
|
||||||
{ name = "darling", version = "<0.14" },
|
{ name = "curve25519-dalek", version = "3.2.0" },
|
||||||
{ name = "darling_core", version = "<0.14" },
|
{ name = "darling_core", version = "<0.14" },
|
||||||
{ name = "darling_macro", version = "<0.14" },
|
{ name = "darling_macro", version = "<0.14" },
|
||||||
|
{ name = "darling", version = "<0.14" },
|
||||||
|
{ name = "der", version = "0.6.1" },
|
||||||
{ name = "digest", version = "<0.10" },
|
{ name = "digest", version = "<0.10" },
|
||||||
{ name = "env_logger", version = "<0.10" },
|
{ name = "ed25519-dalek", version = "1.0.1" },
|
||||||
|
{ name = "ed25519", version = "1.5.3" },
|
||||||
{ name = "getrandom", version = "<0.2" },
|
{ name = "getrandom", version = "<0.2" },
|
||||||
{ name = "hermit-abi", version = "<0.3" },
|
{ name = "hashbrown", version = "<0.14.0" },
|
||||||
{ name = "humantime", version = "<2.1" },
|
|
||||||
{ name = "idna", version = "<0.3" },
|
{ name = "idna", version = "<0.3" },
|
||||||
{ name = "nom", version = "<7.1" },
|
{ name = "indexmap", version = "<2.0.0" },
|
||||||
|
{ name = "linux-raw-sys", version = "0.3.8" },
|
||||||
|
{ name = "num-derive", version = "0.3.3" },
|
||||||
|
{ name = "pem-rfc7468", version = "0.6.0" },
|
||||||
|
{ name = "pkcs8", version = "0.9.0" },
|
||||||
{ name = "quick-error", version = "<2.0" },
|
{ name = "quick-error", version = "<2.0" },
|
||||||
{ name = "rand", version = "<0.8" },
|
|
||||||
{ name = "rand_chacha", version = "<0.3" },
|
{ name = "rand_chacha", version = "<0.3" },
|
||||||
{ name = "rand_core", version = "<0.6" },
|
{ name = "rand_core", version = "<0.6" },
|
||||||
|
{ name = "rand", version = "<0.8" },
|
||||||
|
{ name = "redox_syscall", version = "0.2.16" },
|
||||||
|
{ name = "regex-syntax", version = "0.6.29" },
|
||||||
|
{ name = "rustix", version = "0.37.21" },
|
||||||
|
{ name = "sec1", version = "0.3.0" },
|
||||||
{ name = "sha2", version = "<0.10" },
|
{ name = "sha2", version = "<0.10" },
|
||||||
|
{ name = "signature", version = "1.6.4" },
|
||||||
|
{ name = "socket2", version = "0.4.9" },
|
||||||
{ name = "spin", version = "<0.9.6" },
|
{ name = "spin", version = "<0.9.6" },
|
||||||
|
{ name = "spki", version = "0.6.0" },
|
||||||
|
{ name = "syn", version = "1.0.109" },
|
||||||
{ name = "time", version = "<0.3" },
|
{ name = "time", version = "<0.3" },
|
||||||
{ name = "version_check", version = "<0.9" },
|
|
||||||
{ name = "wasi", version = "<0.11" },
|
{ name = "wasi", version = "<0.11" },
|
||||||
{ name = "windows-sys", version = "<0.45" },
|
{ name = "windows_aarch64_gnullvm", version = "<0.48" },
|
||||||
{ name = "windows_aarch64_msvc", version = "<0.42" },
|
{ name = "windows_aarch64_msvc", version = "<0.48" },
|
||||||
{ name = "windows_i686_gnu", version = "<0.42" },
|
{ name = "windows_i686_gnu", version = "<0.48" },
|
||||||
{ name = "windows_i686_msvc", version = "<0.42" },
|
{ name = "windows_i686_msvc", version = "<0.48" },
|
||||||
{ name = "windows_x86_64_gnu", version = "<0.42" },
|
{ name = "windows-sys", version = "<0.48" },
|
||||||
{ name = "windows_x86_64_msvc", version = "<0.42" },
|
{ name = "windows-targets", version = "<0.48" },
|
||||||
|
{ name = "windows_x86_64_gnullvm", version = "<0.48" },
|
||||||
|
{ name = "windows", version = "0.32.0" },
|
||||||
|
{ name = "windows_x86_64_gnu", version = "<0.48" },
|
||||||
|
{ name = "windows_x86_64_msvc", version = "<0.48" },
|
||||||
|
{ name = "winreg", version = "0.10.1" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1006
fuzz/Cargo.lock
generated
1006
fuzz/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -48,6 +48,7 @@ module.exports = {
|
|||||||
DC_EVENT_LOCATION_CHANGED: 2035,
|
DC_EVENT_LOCATION_CHANGED: 2035,
|
||||||
DC_EVENT_MSGS_CHANGED: 2000,
|
DC_EVENT_MSGS_CHANGED: 2000,
|
||||||
DC_EVENT_MSGS_NOTICED: 2008,
|
DC_EVENT_MSGS_NOTICED: 2008,
|
||||||
|
DC_EVENT_MSG_DELETED: 2016,
|
||||||
DC_EVENT_MSG_DELIVERED: 2010,
|
DC_EVENT_MSG_DELIVERED: 2010,
|
||||||
DC_EVENT_MSG_FAILED: 2012,
|
DC_EVENT_MSG_FAILED: 2012,
|
||||||
DC_EVENT_MSG_READ: 2015,
|
DC_EVENT_MSG_READ: 2015,
|
||||||
@@ -89,6 +90,7 @@ module.exports = {
|
|||||||
DC_KEY_GEN_DEFAULT: 0,
|
DC_KEY_GEN_DEFAULT: 0,
|
||||||
DC_KEY_GEN_ED25519: 2,
|
DC_KEY_GEN_ED25519: 2,
|
||||||
DC_KEY_GEN_RSA2048: 1,
|
DC_KEY_GEN_RSA2048: 1,
|
||||||
|
DC_KEY_GEN_RSA4096: 3,
|
||||||
DC_LP_AUTH_NORMAL: 4,
|
DC_LP_AUTH_NORMAL: 4,
|
||||||
DC_LP_AUTH_OAUTH2: 2,
|
DC_LP_AUTH_OAUTH2: 2,
|
||||||
DC_MEDIA_QUALITY_BALANCED: 0,
|
DC_MEDIA_QUALITY_BALANCED: 0,
|
||||||
@@ -151,6 +153,7 @@ module.exports = {
|
|||||||
DC_STR_AEAP_EXPLANATION_AND_LINK: 123,
|
DC_STR_AEAP_EXPLANATION_AND_LINK: 123,
|
||||||
DC_STR_ARCHIVEDCHATS: 40,
|
DC_STR_ARCHIVEDCHATS: 40,
|
||||||
DC_STR_AUDIO: 11,
|
DC_STR_AUDIO: 11,
|
||||||
|
DC_STR_BACKUP_TRANSFER_MSG_BODY: 163,
|
||||||
DC_STR_BACKUP_TRANSFER_QR: 162,
|
DC_STR_BACKUP_TRANSFER_QR: 162,
|
||||||
DC_STR_BAD_TIME_MSG_BODY: 85,
|
DC_STR_BAD_TIME_MSG_BODY: 85,
|
||||||
DC_STR_BROADCAST_LIST: 115,
|
DC_STR_BROADCAST_LIST: 115,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ module.exports = {
|
|||||||
2010: 'DC_EVENT_MSG_DELIVERED',
|
2010: 'DC_EVENT_MSG_DELIVERED',
|
||||||
2012: 'DC_EVENT_MSG_FAILED',
|
2012: 'DC_EVENT_MSG_FAILED',
|
||||||
2015: 'DC_EVENT_MSG_READ',
|
2015: 'DC_EVENT_MSG_READ',
|
||||||
|
2016: 'DC_EVENT_MSG_DELETED',
|
||||||
2020: 'DC_EVENT_CHAT_MODIFIED',
|
2020: 'DC_EVENT_CHAT_MODIFIED',
|
||||||
2021: 'DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED',
|
2021: 'DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED',
|
||||||
2030: 'DC_EVENT_CONTACTS_CHANGED',
|
2030: 'DC_EVENT_CONTACTS_CHANGED',
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export enum C {
|
|||||||
DC_EVENT_LOCATION_CHANGED = 2035,
|
DC_EVENT_LOCATION_CHANGED = 2035,
|
||||||
DC_EVENT_MSGS_CHANGED = 2000,
|
DC_EVENT_MSGS_CHANGED = 2000,
|
||||||
DC_EVENT_MSGS_NOTICED = 2008,
|
DC_EVENT_MSGS_NOTICED = 2008,
|
||||||
|
DC_EVENT_MSG_DELETED = 2016,
|
||||||
DC_EVENT_MSG_DELIVERED = 2010,
|
DC_EVENT_MSG_DELIVERED = 2010,
|
||||||
DC_EVENT_MSG_FAILED = 2012,
|
DC_EVENT_MSG_FAILED = 2012,
|
||||||
DC_EVENT_MSG_READ = 2015,
|
DC_EVENT_MSG_READ = 2015,
|
||||||
@@ -89,6 +90,7 @@ export enum C {
|
|||||||
DC_KEY_GEN_DEFAULT = 0,
|
DC_KEY_GEN_DEFAULT = 0,
|
||||||
DC_KEY_GEN_ED25519 = 2,
|
DC_KEY_GEN_ED25519 = 2,
|
||||||
DC_KEY_GEN_RSA2048 = 1,
|
DC_KEY_GEN_RSA2048 = 1,
|
||||||
|
DC_KEY_GEN_RSA4096 = 3,
|
||||||
DC_LP_AUTH_NORMAL = 4,
|
DC_LP_AUTH_NORMAL = 4,
|
||||||
DC_LP_AUTH_OAUTH2 = 2,
|
DC_LP_AUTH_OAUTH2 = 2,
|
||||||
DC_MEDIA_QUALITY_BALANCED = 0,
|
DC_MEDIA_QUALITY_BALANCED = 0,
|
||||||
@@ -151,6 +153,7 @@ export enum C {
|
|||||||
DC_STR_AEAP_EXPLANATION_AND_LINK = 123,
|
DC_STR_AEAP_EXPLANATION_AND_LINK = 123,
|
||||||
DC_STR_ARCHIVEDCHATS = 40,
|
DC_STR_ARCHIVEDCHATS = 40,
|
||||||
DC_STR_AUDIO = 11,
|
DC_STR_AUDIO = 11,
|
||||||
|
DC_STR_BACKUP_TRANSFER_MSG_BODY = 163,
|
||||||
DC_STR_BACKUP_TRANSFER_QR = 162,
|
DC_STR_BACKUP_TRANSFER_QR = 162,
|
||||||
DC_STR_BAD_TIME_MSG_BODY = 85,
|
DC_STR_BAD_TIME_MSG_BODY = 85,
|
||||||
DC_STR_BROADCAST_LIST = 115,
|
DC_STR_BROADCAST_LIST = 115,
|
||||||
@@ -306,6 +309,7 @@ export const EventId2EventName: { [key: number]: string } = {
|
|||||||
2010: 'DC_EVENT_MSG_DELIVERED',
|
2010: 'DC_EVENT_MSG_DELIVERED',
|
||||||
2012: 'DC_EVENT_MSG_FAILED',
|
2012: 'DC_EVENT_MSG_FAILED',
|
||||||
2015: 'DC_EVENT_MSG_READ',
|
2015: 'DC_EVENT_MSG_READ',
|
||||||
|
2016: 'DC_EVENT_MSG_DELETED',
|
||||||
2020: 'DC_EVENT_CHAT_MODIFIED',
|
2020: 'DC_EVENT_CHAT_MODIFIED',
|
||||||
2021: 'DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED',
|
2021: 'DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED',
|
||||||
2030: 'DC_EVENT_CONTACTS_CHANGED',
|
2030: 'DC_EVENT_CONTACTS_CHANGED',
|
||||||
|
|||||||
@@ -446,7 +446,8 @@ describe('Offline Tests with unconfigured account', function () {
|
|||||||
context.setChatProfileImage(chatId, imagePath)
|
context.setChatProfileImage(chatId, imagePath)
|
||||||
const blobPath = context.getChat(chatId).getProfileImage()
|
const blobPath = context.getChat(chatId).getProfileImage()
|
||||||
expect(blobPath.startsWith(blobs)).to.be.true
|
expect(blobPath.startsWith(blobs)).to.be.true
|
||||||
expect(blobPath.endsWith(image)).to.be.true
|
expect(blobPath.includes('image')).to.be.true
|
||||||
|
expect(blobPath.endsWith('.jpeg')).to.be.true
|
||||||
|
|
||||||
context.setChatProfileImage(chatId, null)
|
context.setChatProfileImage(chatId, null)
|
||||||
expect(context.getChat(chatId).getProfileImage()).to.be.equal(
|
expect(context.getChat(chatId).getProfileImage()).to.be.equal(
|
||||||
|
|||||||
@@ -60,5 +60,5 @@
|
|||||||
"test:mocha": "mocha -r esm node/test/test.js --growl --reporter=spec --bail --exit"
|
"test:mocha": "mocha -r esm node/test/test.js --growl --reporter=spec --bail --exit"
|
||||||
},
|
},
|
||||||
"types": "node/dist/index.d.ts",
|
"types": "node/dist/index.d.ts",
|
||||||
"version": "1.112.5"
|
"version": "1.122.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -357,7 +357,7 @@ Exhibit A - Source Code Form License Notice
|
|||||||
|
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
If it is not possible or desirable to put the notice in a particular
|
If it is not possible or desirable to put the notice in a particular
|
||||||
file, then You may include the notice in a location (such as a LICENSE
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
|||||||
@@ -12,17 +12,17 @@ a low-level Chat/Contact/Message API to user interfaces and bots.
|
|||||||
Installing pre-built packages (Linux-only)
|
Installing pre-built packages (Linux-only)
|
||||||
==========================================
|
==========================================
|
||||||
|
|
||||||
If you have a Linux system you may try to install the ``deltachat`` binary "wheel" packages
|
If you have a Linux system you may install the ``deltachat`` binary "wheel" packages
|
||||||
without any "build-from-source" steps.
|
without any "build-from-source" steps.
|
||||||
Otherwise you need to `compile the Delta Chat bindings yourself`__.
|
Otherwise you need to `compile the Delta Chat bindings yourself`__.
|
||||||
|
|
||||||
__ sourceinstall_
|
__ sourceinstall_
|
||||||
|
|
||||||
We recommend to first `install virtualenv <https://virtualenv.pypa.io/en/stable/installation.html>`_,
|
We recommend to first create a fresh Python virtual environment
|
||||||
then create a fresh Python virtual environment and activate it in your shell::
|
and activate it in your shell::
|
||||||
|
|
||||||
virtualenv env # or: python -m venv
|
python -m venv env
|
||||||
source env/bin/activate
|
source env/bin/activate
|
||||||
|
|
||||||
Afterwards, invoking ``python`` or ``pip install`` only
|
Afterwards, invoking ``python`` or ``pip install`` only
|
||||||
modifies files in your ``env`` directory and leaves
|
modifies files in your ``env`` directory and leaves
|
||||||
@@ -40,16 +40,14 @@ To verify it worked::
|
|||||||
Running tests
|
Running tests
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Recommended way to run tests is using `tox <https://tox.wiki>`_.
|
Recommended way to run tests is using `scripts/run-python-test.sh`
|
||||||
After successful binding installation you can install tox
|
script provided in the core repository.
|
||||||
and run the tests::
|
|
||||||
|
|
||||||
pip install tox
|
This script compiles the library in debug mode and runs the tests using `tox`_.
|
||||||
tox -e py3
|
By default it will run all "offline" tests and skip all functional
|
||||||
|
|
||||||
This will run all "offline" tests and skip all functional
|
|
||||||
end-to-end tests that require accounts on real e-mail servers.
|
end-to-end tests that require accounts on real e-mail servers.
|
||||||
|
|
||||||
|
.. _`tox`: https://tox.wiki
|
||||||
.. _livetests:
|
.. _livetests:
|
||||||
|
|
||||||
Running "live" tests with temporary accounts
|
Running "live" tests with temporary accounts
|
||||||
@@ -61,13 +59,34 @@ Please feel free to contact us through a github issue or by e-mail and we'll sen
|
|||||||
|
|
||||||
export DCC_NEW_TMP_EMAIL=<URL you got from us>
|
export DCC_NEW_TMP_EMAIL=<URL you got from us>
|
||||||
|
|
||||||
With this account-creation setting, pytest runs create ephemeral e-mail accounts on the http://testrun.org server. These accounts exists only for one hour and then are removed completely.
|
With this account-creation setting, pytest runs create ephemeral e-mail accounts on the http://testrun.org server.
|
||||||
One hour is enough to invoke pytest and run all offline and online tests::
|
These accounts are removed automatically as they expire.
|
||||||
|
After setting the variable, either rerun `scripts/run-python-test.sh`
|
||||||
|
or run offline and online tests with `tox` directly::
|
||||||
|
|
||||||
tox -e py3
|
tox -e py
|
||||||
|
|
||||||
Each test run creates new accounts.
|
Each test run creates new accounts.
|
||||||
|
|
||||||
|
Developing the bindings
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
If you want to develop or debug the bindings,
|
||||||
|
you can create a testing development environment using `tox`::
|
||||||
|
|
||||||
|
export DCC_RS_DEV="$PWD"
|
||||||
|
export DCC_RS_TARGET=debug
|
||||||
|
tox -c python --devenv env -e py
|
||||||
|
. env/bin/activate
|
||||||
|
|
||||||
|
Inside this environment the bindings are installed
|
||||||
|
in editable mode (as if installed with `python -m pip install -e`)
|
||||||
|
together with the testing dependencies like `pytest` and its plugins.
|
||||||
|
|
||||||
|
You can then edit the source code in the development tree
|
||||||
|
and quickly run `pytest` manually without waiting for `tox`
|
||||||
|
to recreating the virtual environment each time.
|
||||||
|
|
||||||
.. _sourceinstall:
|
.. _sourceinstall:
|
||||||
|
|
||||||
Installing bindings from source
|
Installing bindings from source
|
||||||
@@ -89,20 +108,34 @@ To install the Delta Chat Python bindings make sure you have Python3 installed.
|
|||||||
E.g. on Debian-based systems `apt install python3 python3-pip
|
E.g. on Debian-based systems `apt install python3 python3-pip
|
||||||
python3-venv` should give you a usable python installation.
|
python3-venv` should give you a usable python installation.
|
||||||
|
|
||||||
Ensure you are in the deltachat-core-rust/python directory, create the
|
First, build the core library::
|
||||||
virtual environment with dependencies using tox
|
|
||||||
and activate it in your shell::
|
|
||||||
|
|
||||||
cd python
|
cargo build --release -p deltachat_ffi --features jsonrpc
|
||||||
tox --devenv env
|
|
||||||
|
`jsonrpc` feature is required even if not used by the bindings
|
||||||
|
because `deltachat.h` includes JSON-RPC functions unconditionally.
|
||||||
|
|
||||||
|
Create the virtual environment and activate it:
|
||||||
|
|
||||||
|
python -m venv env
|
||||||
source env/bin/activate
|
source env/bin/activate
|
||||||
|
|
||||||
You should now be able to build the python bindings using the supplied script::
|
Build and install the bindings:
|
||||||
|
|
||||||
python3 install_python_bindings.py
|
export DCC_RS_DEV="$PWD"
|
||||||
|
export DCC_RS_TARGET=release
|
||||||
|
python -m pip install ./python
|
||||||
|
|
||||||
The core compilation and bindings building might take a while,
|
`DCC_RS_DEV` environment variable specifies the location of
|
||||||
depending on the speed of your machine.
|
the core development tree. If this variable is not set,
|
||||||
|
`libdeltachat` library and `deltachat.h` header are expected
|
||||||
|
to be installed system-wide.
|
||||||
|
|
||||||
|
When `DCC_RS_DEV` is set, `DCC_RS_TARGET` specifies
|
||||||
|
the build profile name to look up the artifacts
|
||||||
|
in the target directory.
|
||||||
|
In this case setting it can be skipped because
|
||||||
|
`DCC_RS_TARGET=release` is the default.
|
||||||
|
|
||||||
Building manylinux based wheels
|
Building manylinux based wheels
|
||||||
===============================
|
===============================
|
||||||
|
|||||||
@@ -2,34 +2,34 @@
|
|||||||
high level API reference
|
high level API reference
|
||||||
========================
|
========================
|
||||||
|
|
||||||
- :class:`deltachat.account.Account` (your main entry point, creates the
|
- :class:`deltachat.Account` (your main entry point, creates the
|
||||||
other classes)
|
other classes)
|
||||||
- :class:`deltachat.contact.Contact`
|
- :class:`deltachat.Contact`
|
||||||
- :class:`deltachat.chat.Chat`
|
- :class:`deltachat.Chat`
|
||||||
- :class:`deltachat.message.Message`
|
- :class:`deltachat.Message`
|
||||||
|
|
||||||
Account
|
Account
|
||||||
-------
|
-------
|
||||||
|
|
||||||
.. autoclass:: deltachat.account.Account
|
.. autoclass:: deltachat.Account
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|
||||||
Contact
|
Contact
|
||||||
-------
|
-------
|
||||||
|
|
||||||
.. autoclass:: deltachat.contact.Contact
|
.. autoclass:: deltachat.Contact
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
Chat
|
Chat
|
||||||
----
|
----
|
||||||
|
|
||||||
.. autoclass:: deltachat.chat.Chat
|
.. autoclass:: deltachat.Chat
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
Message
|
Message
|
||||||
-------
|
-------
|
||||||
|
|
||||||
.. autoclass:: deltachat.message.Message
|
.. autoclass:: deltachat.Message
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|||||||
@@ -32,25 +32,13 @@ class GroupTrackingPlugin:
|
|||||||
|
|
||||||
@account_hookimpl
|
@account_hookimpl
|
||||||
def ac_member_added(self, chat, contact, actor, message):
|
def ac_member_added(self, chat, contact, actor, message):
|
||||||
print(
|
print(f"ac_member_added {contact.addr} to chat {chat.id} from {actor or message.get_sender_contact().addr}")
|
||||||
"ac_member_added {} to chat {} from {}".format(
|
|
||||||
contact.addr,
|
|
||||||
chat.id,
|
|
||||||
actor or message.get_sender_contact().addr,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
for member in chat.get_contacts():
|
for member in chat.get_contacts():
|
||||||
print(f"chat member: {member.addr}")
|
print(f"chat member: {member.addr}")
|
||||||
|
|
||||||
@account_hookimpl
|
@account_hookimpl
|
||||||
def ac_member_removed(self, chat, contact, actor, message):
|
def ac_member_removed(self, chat, contact, actor, message):
|
||||||
print(
|
print(f"ac_member_removed {contact.addr} from chat {chat.id} by {actor or message.get_sender_contact().addr}")
|
||||||
"ac_member_removed {} from chat {} by {}".format(
|
|
||||||
contact.addr,
|
|
||||||
chat.id,
|
|
||||||
actor or message.get_sender_contact().addr,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv=None):
|
def main(argv=None):
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ def test_echo_quit_plugin(acfactory, lp):
|
|||||||
|
|
||||||
def test_group_tracking_plugin(acfactory, lp):
|
def test_group_tracking_plugin(acfactory, lp):
|
||||||
lp.sec("creating one group-tracking bot and two temp accounts")
|
lp.sec("creating one group-tracking bot and two temp accounts")
|
||||||
botproc = acfactory.run_bot_process(group_tracking, ffi=False)
|
botproc = acfactory.run_bot_process(group_tracking)
|
||||||
|
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
"""
|
|
||||||
setup a python binding development in-place install with cargo debug symbols.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
target = os.environ.get("DCC_RS_TARGET")
|
|
||||||
if target is None:
|
|
||||||
os.environ["DCC_RS_TARGET"] = target = "debug"
|
|
||||||
if "DCC_RS_DEV" not in os.environ:
|
|
||||||
dn = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
os.environ["DCC_RS_DEV"] = dn
|
|
||||||
|
|
||||||
cmd = ["cargo", "build", "-p", "deltachat_ffi", "--features", "jsonrpc"]
|
|
||||||
|
|
||||||
if target == "release":
|
|
||||||
os.environ["CARGO_PROFILE_RELEASE_LTO"] = "on"
|
|
||||||
cmd.append("--release")
|
|
||||||
|
|
||||||
print("running:", " ".join(cmd))
|
|
||||||
subprocess.check_call(cmd)
|
|
||||||
subprocess.check_call("rm -rf build/ src/deltachat/*.so src/deltachat/*.dylib src/deltachat/*.dll", shell=True)
|
|
||||||
|
|
||||||
if len(sys.argv) <= 1 or sys.argv[1] != "onlybuild":
|
|
||||||
subprocess.check_call([sys.executable, "-m", "pip", "install", "-e", "."])
|
|
||||||
@@ -11,10 +11,11 @@ authors = [
|
|||||||
{ name = "holger krekel, Floris Bruynooghe, Bjoern Petersen and contributors" },
|
{ name = "holger krekel, Floris Bruynooghe, Bjoern Petersen and contributors" },
|
||||||
]
|
]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 5 - Production/Stable",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
|
"License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
|
"Topic :: Communications :: Chat",
|
||||||
"Topic :: Communications :: Email",
|
"Topic :: Communications :: Email",
|
||||||
"Topic :: Software Development :: Libraries",
|
"Topic :: Software Development :: Libraries",
|
||||||
]
|
]
|
||||||
@@ -33,6 +34,7 @@ dynamic = [
|
|||||||
"Home" = "https://github.com/deltachat/deltachat-core-rust/"
|
"Home" = "https://github.com/deltachat/deltachat-core-rust/"
|
||||||
"Bug Tracker" = "https://github.com/deltachat/deltachat-core-rust/issues"
|
"Bug Tracker" = "https://github.com/deltachat/deltachat-core-rust/issues"
|
||||||
"Documentation" = "https://py.delta.chat/"
|
"Documentation" = "https://py.delta.chat/"
|
||||||
|
"Mastodon" = "https://chaos.social/@delta"
|
||||||
|
|
||||||
[project.entry-points.pytest11]
|
[project.entry-points.pytest11]
|
||||||
"deltachat.testplugin" = "deltachat.testplugin"
|
"deltachat.testplugin" = "deltachat.testplugin"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from array import array
|
|||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from email.utils import parseaddr
|
from email.utils import parseaddr
|
||||||
from threading import Event
|
from threading import Event
|
||||||
from typing import Any, Dict, Generator, List, Optional, Union, TYPE_CHECKING
|
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Union
|
||||||
|
|
||||||
from . import const, hookspec
|
from . import const, hookspec
|
||||||
from .capi import ffi, lib
|
from .capi import ffi, lib
|
||||||
@@ -376,6 +376,22 @@ class Account:
|
|||||||
dc_array = ffi.gc(lib.dc_get_fresh_msgs(self._dc_context), lib.dc_array_unref)
|
dc_array = ffi.gc(lib.dc_get_fresh_msgs(self._dc_context), lib.dc_array_unref)
|
||||||
return (x for x in iter_array(dc_array, lambda x: Message.from_db(self, x)) if x is not None)
|
return (x for x in iter_array(dc_array, lambda x: Message.from_db(self, x)) if x is not None)
|
||||||
|
|
||||||
|
def _wait_next_message_ids(self) -> List[int]:
|
||||||
|
"""Return IDs of all next messages from all chats."""
|
||||||
|
dc_array = ffi.gc(lib.dc_wait_next_msgs(self._dc_context), lib.dc_array_unref)
|
||||||
|
return [lib.dc_array_get_id(dc_array, i) for i in range(lib.dc_array_get_cnt(dc_array))]
|
||||||
|
|
||||||
|
def wait_next_incoming_message(self) -> Message:
|
||||||
|
"""Waits until the next incoming message
|
||||||
|
with ID higher than given is received and returns it."""
|
||||||
|
while True:
|
||||||
|
message_ids = self._wait_next_message_ids()
|
||||||
|
for msg_id in message_ids:
|
||||||
|
message = Message.from_db(self, msg_id)
|
||||||
|
if message and not message.is_from_self() and not message.is_from_device():
|
||||||
|
self.set_config("last_msg_id", str(msg_id))
|
||||||
|
return message
|
||||||
|
|
||||||
def create_chat(self, obj) -> Chat:
|
def create_chat(self, obj) -> Chat:
|
||||||
"""Create a 1:1 chat with Account, Contact or e-mail address."""
|
"""Create a 1:1 chat with Account, Contact or e-mail address."""
|
||||||
return self.create_contact(obj).create_chat()
|
return self.create_contact(obj).create_chat()
|
||||||
@@ -411,7 +427,7 @@ class Account:
|
|||||||
|
|
||||||
assert dc_chatlist != ffi.NULL
|
assert dc_chatlist != ffi.NULL
|
||||||
chatlist = []
|
chatlist = []
|
||||||
for i in range(0, lib.dc_chatlist_get_cnt(dc_chatlist)):
|
for i in range(lib.dc_chatlist_get_cnt(dc_chatlist)):
|
||||||
chat_id = lib.dc_chatlist_get_chat_id(dc_chatlist, i)
|
chat_id = lib.dc_chatlist_get_chat_id(dc_chatlist, i)
|
||||||
chatlist.append(Chat(self, chat_id))
|
chatlist.append(Chat(self, chat_id))
|
||||||
return chatlist
|
return chatlist
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ class Contact:
|
|||||||
"""Unblock this contact. Messages from this contact will be retrieved (again)."""
|
"""Unblock this contact. Messages from this contact will be retrieved (again)."""
|
||||||
return lib.dc_block_contact(self.account._dc_context, self.id, False)
|
return lib.dc_block_contact(self.account._dc_context, self.id, False)
|
||||||
|
|
||||||
def is_verified(self):
|
def is_verified(self) -> bool:
|
||||||
"""Return True if the contact is verified."""
|
"""Return True if the contact is verified."""
|
||||||
return lib.dc_contact_is_verified(self._dc_contact)
|
return lib.dc_contact_is_verified(self._dc_contact) == 2
|
||||||
|
|
||||||
def get_verifier(self, contact):
|
def get_verifier(self, contact):
|
||||||
"""Return the address of the contact that verified the contact."""
|
"""Return the address of the contact that verified the contact."""
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ def as_dc_charpointer(obj):
|
|||||||
|
|
||||||
|
|
||||||
def iter_array(dc_array_t, constructor: Callable[[int], T]) -> Generator[T, None, None]:
|
def iter_array(dc_array_t, constructor: Callable[[int], T]) -> Generator[T, None, None]:
|
||||||
for i in range(0, lib.dc_array_get_cnt(dc_array_t)):
|
for i in range(lib.dc_array_get_cnt(dc_array_t)):
|
||||||
yield constructor(lib.dc_array_get_id(dc_array_t, i))
|
yield constructor(lib.dc_array_get_id(dc_array_t, i))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ from contextlib import contextmanager
|
|||||||
from queue import Empty, Queue
|
from queue import Empty, Queue
|
||||||
|
|
||||||
from . import const
|
from . import const
|
||||||
|
from .account import Account
|
||||||
from .capi import ffi, lib
|
from .capi import ffi, lib
|
||||||
from .cutil import from_optional_dc_charpointer
|
from .cutil import from_optional_dc_charpointer
|
||||||
from .hookspec import account_hookimpl
|
from .hookspec import account_hookimpl
|
||||||
from .message import map_system_message
|
from .message import map_system_message
|
||||||
from .account import Account
|
|
||||||
|
|
||||||
|
|
||||||
def get_dc_event_name(integer, _DC_EVENTNAME_MAP={}):
|
def get_dc_event_name(integer, _DC_EVENTNAME_MAP={}):
|
||||||
|
|||||||
@@ -344,6 +344,16 @@ class Message:
|
|||||||
contact_id = lib.dc_msg_get_from_id(self._dc_msg)
|
contact_id = lib.dc_msg_get_from_id(self._dc_msg)
|
||||||
return Contact(self.account, contact_id)
|
return Contact(self.account, contact_id)
|
||||||
|
|
||||||
|
def is_from_self(self):
|
||||||
|
"""Return true if the message is sent by self."""
|
||||||
|
contact_id = lib.dc_msg_get_from_id(self._dc_msg)
|
||||||
|
return contact_id == const.DC_CONTACT_ID_SELF
|
||||||
|
|
||||||
|
def is_from_device(self):
|
||||||
|
"""Return true if the message is sent by the device."""
|
||||||
|
contact_id = lib.dc_msg_get_from_id(self._dc_msg)
|
||||||
|
return contact_id == const.DC_CONTACT_ID_DEVICE
|
||||||
|
|
||||||
#
|
#
|
||||||
# Message State query methods
|
# Message State query methods
|
||||||
#
|
#
|
||||||
@@ -476,6 +486,9 @@ class Message:
|
|||||||
dc_msg = ffi.gc(lib.dc_get_msg(self.account._dc_context, self.id), lib.dc_msg_unref)
|
dc_msg = ffi.gc(lib.dc_get_msg(self.account._dc_context, self.id), lib.dc_msg_unref)
|
||||||
return lib.dc_msg_get_download_state(dc_msg)
|
return lib.dc_msg_get_download_state(dc_msg)
|
||||||
|
|
||||||
|
def download_full(self) -> None:
|
||||||
|
lib.dc_download_full_msg(self.account._dc_context, self.id)
|
||||||
|
|
||||||
|
|
||||||
# some code for handling DC_MSG_* view types
|
# some code for handling DC_MSG_* view types
|
||||||
|
|
||||||
@@ -497,8 +510,7 @@ def get_viewtype_code_from_name(view_type_name):
|
|||||||
if code is not None:
|
if code is not None:
|
||||||
return code
|
return code
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"message typecode not found for {!r}, "
|
f"message typecode not found for {view_type_name!r}, available {list(_view_type_mapping.keys())!r}",
|
||||||
"available {!r}".format(view_type_name, list(_view_type_mapping.keys())),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import threading
|
|||||||
import time
|
import time
|
||||||
import weakref
|
import weakref
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from typing import Callable, List, Optional, Dict, Set
|
from typing import Callable, Dict, List, Optional, Set
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
@@ -137,6 +137,9 @@ def pytest_report_header(config, startdir):
|
|||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def testprocess(request):
|
def testprocess(request):
|
||||||
|
"""Return live account configuration manager.
|
||||||
|
|
||||||
|
The returned object is a :class:`TestProcess` object."""
|
||||||
return TestProcess(pytestconfig=request.config)
|
return TestProcess(pytestconfig=request.config)
|
||||||
|
|
||||||
|
|
||||||
@@ -231,6 +234,8 @@ def write_dict_to_dir(dic, target_dir):
|
|||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def data(request):
|
def data(request):
|
||||||
|
"""Test data."""
|
||||||
|
|
||||||
class Data:
|
class Data:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
# trying to find test data heuristically
|
# trying to find test data heuristically
|
||||||
@@ -614,6 +619,7 @@ class ACFactory:
|
|||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def acfactory(request, tmpdir, testprocess, data):
|
def acfactory(request, tmpdir, testprocess, data):
|
||||||
|
"""Account factory."""
|
||||||
am = ACFactory(request=request, tmpdir=tmpdir, testprocess=testprocess, data=data)
|
am = ACFactory(request=request, tmpdir=tmpdir, testprocess=testprocess, data=data)
|
||||||
yield am
|
yield am
|
||||||
if hasattr(request.node, "rep_call") and request.node.rep_call.failed:
|
if hasattr(request.node, "rep_call") and request.node.rep_call.failed:
|
||||||
@@ -679,11 +685,14 @@ class BotProcess:
|
|||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def tmp_db_path(tmpdir):
|
def tmp_db_path(tmpdir):
|
||||||
|
"""Return a path inside the temporary directory where the database can be created."""
|
||||||
return tmpdir.join("test.db").strpath
|
return tmpdir.join("test.db").strpath
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def lp():
|
def lp():
|
||||||
|
"""Log printer fixture."""
|
||||||
|
|
||||||
class Printer:
|
class Printer:
|
||||||
def sec(self, msg: str) -> None:
|
def sec(self, msg: str) -> None:
|
||||||
print()
|
print()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from queue import Queue
|
from queue import Queue
|
||||||
from threading import Event
|
from threading import Event
|
||||||
from typing import List, TYPE_CHECKING
|
from typing import TYPE_CHECKING, List
|
||||||
|
|
||||||
from .hookspec import Global, account_hookimpl
|
from .hookspec import Global, account_hookimpl
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,6 @@ class TestEmpty:
|
|||||||
def test_prepare_setup_measurings(self, acfactory):
|
def test_prepare_setup_measurings(self, acfactory):
|
||||||
acfactory.get_online_accounts(BENCH_NUM)
|
acfactory.get_online_accounts(BENCH_NUM)
|
||||||
|
|
||||||
@pytest.mark.parametrize("num", range(0, BENCH_NUM + 1))
|
@pytest.mark.parametrize("num", range(BENCH_NUM + 1))
|
||||||
def test_setup_online_accounts(self, acfactory, num):
|
def test_setup_online_accounts(self, acfactory, num):
|
||||||
acfactory.get_online_accounts(num)
|
acfactory.get_online_accounts(num)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import pytest
|
|||||||
import deltachat
|
import deltachat
|
||||||
|
|
||||||
|
|
||||||
def test_db_busy_error(acfactory, tmpdir):
|
def test_db_busy_error(acfactory):
|
||||||
starttime = time.time()
|
starttime = time.time()
|
||||||
log_lock = threading.RLock()
|
log_lock = threading.RLock()
|
||||||
|
|
||||||
|
|||||||
@@ -494,7 +494,7 @@ def test_multidevice_sync_seen(acfactory, lp):
|
|||||||
assert "Expires: " in ac1_clone_message.get_message_info()
|
assert "Expires: " in ac1_clone_message.get_message_info()
|
||||||
|
|
||||||
|
|
||||||
def test_see_new_verified_member_after_going_online(acfactory, tmpdir, lp):
|
def test_see_new_verified_member_after_going_online(acfactory, tmp_path, lp):
|
||||||
"""The test for the bug #3836:
|
"""The test for the bug #3836:
|
||||||
- Alice has two devices, the second is offline.
|
- Alice has two devices, the second is offline.
|
||||||
- Alice creates a verified group and sends a QR invitation to Bob.
|
- Alice creates a verified group and sends a QR invitation to Bob.
|
||||||
@@ -507,9 +507,10 @@ def test_see_new_verified_member_after_going_online(acfactory, tmpdir, lp):
|
|||||||
for ac in [ac1, ac1_offl]:
|
for ac in [ac1, ac1_offl]:
|
||||||
ac.set_config("bcc_self", "1")
|
ac.set_config("bcc_self", "1")
|
||||||
acfactory.bring_accounts_online()
|
acfactory.bring_accounts_online()
|
||||||
dir = tmpdir.mkdir("exportdir")
|
dir = tmp_path / "exportdir"
|
||||||
ac1.export_self_keys(dir.strpath)
|
dir.mkdir()
|
||||||
ac1_offl.import_self_keys(dir.strpath)
|
ac1.export_self_keys(str(dir))
|
||||||
|
ac1_offl.import_self_keys(str(dir))
|
||||||
ac1_offl.stop_io()
|
ac1_offl.stop_io()
|
||||||
|
|
||||||
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
|
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
|
||||||
@@ -541,7 +542,7 @@ def test_see_new_verified_member_after_going_online(acfactory, tmpdir, lp):
|
|||||||
ac1.set_config("bcc_self", "0")
|
ac1.set_config("bcc_self", "0")
|
||||||
|
|
||||||
|
|
||||||
def test_use_new_verified_group_after_going_online(acfactory, tmpdir, lp):
|
def test_use_new_verified_group_after_going_online(acfactory, tmp_path, lp):
|
||||||
"""Another test for the bug #3836:
|
"""Another test for the bug #3836:
|
||||||
- Bob has two devices, the second is offline.
|
- Bob has two devices, the second is offline.
|
||||||
- Alice creates a verified group and sends a QR invitation to Bob.
|
- Alice creates a verified group and sends a QR invitation to Bob.
|
||||||
@@ -556,9 +557,10 @@ def test_use_new_verified_group_after_going_online(acfactory, tmpdir, lp):
|
|||||||
for ac in [ac2, ac2_offl]:
|
for ac in [ac2, ac2_offl]:
|
||||||
ac.set_config("bcc_self", "1")
|
ac.set_config("bcc_self", "1")
|
||||||
acfactory.bring_accounts_online()
|
acfactory.bring_accounts_online()
|
||||||
dir = tmpdir.mkdir("exportdir")
|
dir = tmp_path / "exportdir"
|
||||||
ac2.export_self_keys(dir.strpath)
|
dir.mkdir()
|
||||||
ac2_offl.import_self_keys(dir.strpath)
|
ac2.export_self_keys(str(dir))
|
||||||
|
ac2_offl.import_self_keys(str(dir))
|
||||||
ac2_offl.stop_io()
|
ac2_offl.stop_io()
|
||||||
|
|
||||||
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
|
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
|
||||||
|
|||||||
@@ -2,18 +2,18 @@ import os
|
|||||||
import queue
|
import queue
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import base64
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from imap_tools import AND, U
|
from imap_tools import AND, U
|
||||||
|
|
||||||
from deltachat import const
|
import deltachat as dc
|
||||||
from deltachat.hookspec import account_hookimpl
|
from deltachat import account_hookimpl, Message
|
||||||
from deltachat.message import Message
|
|
||||||
from deltachat.tracker import ImexTracker
|
from deltachat.tracker import ImexTracker
|
||||||
|
|
||||||
|
|
||||||
def test_basic_imap_api(acfactory, tmpdir):
|
def test_basic_imap_api(acfactory, tmp_path):
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||||
chat12 = acfactory.get_accepted_chat(ac1, ac2)
|
chat12 = acfactory.get_accepted_chat(ac1, ac2)
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ def test_basic_imap_api(acfactory, tmpdir):
|
|||||||
imap2.mark_all_read()
|
imap2.mark_all_read()
|
||||||
assert imap2.get_unread_cnt() == 0
|
assert imap2.get_unread_cnt() == 0
|
||||||
|
|
||||||
imap2.dump_imap_structures(tmpdir, logfile=sys.stdout)
|
imap2.dump_imap_structures(tmp_path, logfile=sys.stdout)
|
||||||
imap2.shutdown()
|
imap2.shutdown()
|
||||||
|
|
||||||
|
|
||||||
@@ -36,29 +36,29 @@ def test_basic_imap_api(acfactory, tmpdir):
|
|||||||
def test_configure_generate_key(acfactory, lp):
|
def test_configure_generate_key(acfactory, lp):
|
||||||
# A slow test which will generate new keys.
|
# A slow test which will generate new keys.
|
||||||
acfactory.remove_preconfigured_keys()
|
acfactory.remove_preconfigured_keys()
|
||||||
ac1 = acfactory.new_online_configuring_account(key_gen_type=str(const.DC_KEY_GEN_RSA2048))
|
ac1 = acfactory.new_online_configuring_account(key_gen_type=str(dc.const.DC_KEY_GEN_RSA2048))
|
||||||
ac2 = acfactory.new_online_configuring_account(key_gen_type=str(const.DC_KEY_GEN_ED25519))
|
ac2 = acfactory.new_online_configuring_account(key_gen_type=str(dc.const.DC_KEY_GEN_ED25519))
|
||||||
acfactory.bring_accounts_online()
|
acfactory.bring_accounts_online()
|
||||||
chat = acfactory.get_accepted_chat(ac1, ac2)
|
chat = acfactory.get_accepted_chat(ac1, ac2)
|
||||||
|
|
||||||
lp.sec("ac1: send unencrypted message to ac2")
|
lp.sec("ac1: send unencrypted message to ac2")
|
||||||
chat.send_text("message1")
|
chat.send_text("message1")
|
||||||
lp.sec("ac2: waiting for message from ac1")
|
lp.sec("ac2: waiting for message from ac1")
|
||||||
msg_in = ac2._evtracker.wait_next_incoming_message()
|
msg_in = ac2.wait_next_incoming_message()
|
||||||
assert msg_in.text == "message1"
|
assert msg_in.text == "message1"
|
||||||
assert not msg_in.is_encrypted()
|
assert not msg_in.is_encrypted()
|
||||||
|
|
||||||
lp.sec("ac2: send encrypted message to ac1")
|
lp.sec("ac2: send encrypted message to ac1")
|
||||||
msg_in.chat.send_text("message2")
|
msg_in.chat.send_text("message2")
|
||||||
lp.sec("ac1: waiting for message from ac2")
|
lp.sec("ac1: waiting for message from ac2")
|
||||||
msg2_in = ac1._evtracker.wait_next_incoming_message()
|
msg2_in = ac1.wait_next_incoming_message()
|
||||||
assert msg2_in.text == "message2"
|
assert msg2_in.text == "message2"
|
||||||
assert msg2_in.is_encrypted()
|
assert msg2_in.is_encrypted()
|
||||||
|
|
||||||
lp.sec("ac1: send encrypted message to ac2")
|
lp.sec("ac1: send encrypted message to ac2")
|
||||||
msg2_in.chat.send_text("message3")
|
msg2_in.chat.send_text("message3")
|
||||||
lp.sec("ac2: waiting for message from ac1")
|
lp.sec("ac2: waiting for message from ac1")
|
||||||
msg3_in = ac2._evtracker.wait_next_incoming_message()
|
msg3_in = ac2.wait_next_incoming_message()
|
||||||
assert msg3_in.text == "message3"
|
assert msg3_in.text == "message3"
|
||||||
assert msg3_in.is_encrypted()
|
assert msg3_in.is_encrypted()
|
||||||
|
|
||||||
@@ -72,35 +72,37 @@ def test_configure_canceled(acfactory):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_configure_unref(tmpdir):
|
def test_configure_unref(tmp_path):
|
||||||
"""Test that removing the last reference to the context during ongoing configuration
|
"""Test that removing the last reference to the context during ongoing configuration
|
||||||
does not result in use-after-free."""
|
does not result in use-after-free."""
|
||||||
from deltachat.capi import ffi, lib
|
from deltachat.capi import ffi, lib
|
||||||
|
|
||||||
path = tmpdir.mkdir("test_configure_unref").join("dc.db").strpath
|
path = tmp_path / "test_configure_unref"
|
||||||
dc_context = lib.dc_context_new(ffi.NULL, path.encode("utf8"), ffi.NULL)
|
path.mkdir()
|
||||||
|
dc_context = lib.dc_context_new(ffi.NULL, str(path / "dc.db").encode("utf8"), ffi.NULL)
|
||||||
lib.dc_set_config(dc_context, "addr".encode("utf8"), "foo@x.testrun.org".encode("utf8"))
|
lib.dc_set_config(dc_context, "addr".encode("utf8"), "foo@x.testrun.org".encode("utf8"))
|
||||||
lib.dc_set_config(dc_context, "mail_pw".encode("utf8"), "abc".encode("utf8"))
|
lib.dc_set_config(dc_context, "mail_pw".encode("utf8"), "abc".encode("utf8"))
|
||||||
lib.dc_configure(dc_context)
|
lib.dc_configure(dc_context)
|
||||||
lib.dc_context_unref(dc_context)
|
lib.dc_context_unref(dc_context)
|
||||||
|
|
||||||
|
|
||||||
def test_export_import_self_keys(acfactory, tmpdir, lp):
|
def test_export_import_self_keys(acfactory, tmp_path, lp):
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
dir = tmpdir.mkdir("exportdir")
|
dir = tmp_path / "exportdir"
|
||||||
export_files = ac1.export_self_keys(dir.strpath)
|
dir.mkdir()
|
||||||
|
export_files = ac1.export_self_keys(str(dir))
|
||||||
assert len(export_files) == 2
|
assert len(export_files) == 2
|
||||||
for x in export_files:
|
for x in export_files:
|
||||||
assert x.startswith(dir.strpath)
|
assert x.startswith(str(dir))
|
||||||
(key_id,) = ac1._evtracker.get_info_regex_groups(r".*xporting.*KeyId\((.*)\).*")
|
(key_id,) = ac1._evtracker.get_info_regex_groups(r".*xporting.*KeyId\((.*)\).*")
|
||||||
ac1._evtracker.consume_events()
|
ac1._evtracker.consume_events()
|
||||||
|
|
||||||
lp.sec("exported keys (private and public)")
|
lp.sec("exported keys (private and public)")
|
||||||
for name in os.listdir(dir.strpath):
|
for name in dir.iterdir():
|
||||||
lp.indent(dir.strpath + os.sep + name)
|
lp.indent(str(dir / name))
|
||||||
lp.sec("importing into existing account")
|
lp.sec("importing into existing account")
|
||||||
ac2.import_self_keys(dir.strpath)
|
ac2.import_self_keys(str(dir))
|
||||||
(key_id2,) = ac2._evtracker.get_info_regex_groups(r".*stored.*KeyId\((.*)\).*")
|
(key_id2,) = ac2._evtracker.get_info_regex_groups(r".*stored.*KeyId\((.*)\).*")
|
||||||
assert key_id2 == key_id
|
assert key_id2 == key_id
|
||||||
|
|
||||||
@@ -156,62 +158,65 @@ def test_one_account_send_bcc_setting(acfactory, lp):
|
|||||||
assert len(list(ac1.direct_imap.conn.fetch(AND(seen=True)))) == 1
|
assert len(list(ac1.direct_imap.conn.fetch(AND(seen=True)))) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_send_file_twice_unicode_filename_mangling(tmpdir, acfactory, lp):
|
def test_send_file_twice_unicode_filename_mangling(tmp_path, acfactory, lp):
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||||
chat = acfactory.get_accepted_chat(ac1, ac2)
|
chat = acfactory.get_accepted_chat(ac1, ac2)
|
||||||
|
|
||||||
basename = "somedäüta.html.zip"
|
basename = "somedäüta"
|
||||||
p = os.path.join(tmpdir.strpath, basename)
|
ext = ".html.zip"
|
||||||
with open(p, "w") as f:
|
p = tmp_path / (basename + ext)
|
||||||
f.write("some data")
|
p.write_text("some data")
|
||||||
|
|
||||||
def send_and_receive_message():
|
def send_and_receive_message():
|
||||||
lp.sec("ac1: prepare and send attachment + text to ac2")
|
lp.sec("ac1: prepare and send attachment + text to ac2")
|
||||||
msg1 = Message.new_empty(ac1, "file")
|
msg1 = Message.new_empty(ac1, "file")
|
||||||
msg1.set_text("withfile")
|
msg1.set_text("withfile")
|
||||||
msg1.set_file(p)
|
msg1.set_file(str(p))
|
||||||
chat.send_msg(msg1)
|
chat.send_msg(msg1)
|
||||||
|
|
||||||
lp.sec("ac2: receive message")
|
lp.sec("ac2: receive message")
|
||||||
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG")
|
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG")
|
||||||
assert ev.data2 > const.DC_CHAT_ID_LAST_SPECIAL
|
assert ev.data2 > dc.const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
return ac2.get_message_by_id(ev.data2)
|
return ac2.get_message_by_id(ev.data2)
|
||||||
|
|
||||||
msg = send_and_receive_message()
|
msg = send_and_receive_message()
|
||||||
assert msg.text == "withfile"
|
assert msg.text == "withfile"
|
||||||
assert open(msg.filename).read() == "some data"
|
assert open(msg.filename).read() == "some data"
|
||||||
assert msg.filename.endswith(basename)
|
msg.filename.index(basename)
|
||||||
|
assert msg.filename.endswith(ext)
|
||||||
|
|
||||||
msg2 = send_and_receive_message()
|
msg2 = send_and_receive_message()
|
||||||
assert msg2.text == "withfile"
|
assert msg2.text == "withfile"
|
||||||
assert open(msg2.filename).read() == "some data"
|
assert open(msg2.filename).read() == "some data"
|
||||||
assert msg2.filename.endswith("html.zip")
|
msg2.filename.index(basename)
|
||||||
|
assert msg2.filename.endswith(ext)
|
||||||
assert msg.filename != msg2.filename
|
assert msg.filename != msg2.filename
|
||||||
|
|
||||||
|
|
||||||
def test_send_file_html_attachment(tmpdir, acfactory, lp):
|
def test_send_file_html_attachment(tmp_path, acfactory, lp):
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||||
chat = acfactory.get_accepted_chat(ac1, ac2)
|
chat = acfactory.get_accepted_chat(ac1, ac2)
|
||||||
|
|
||||||
basename = "test.html"
|
basename = "test"
|
||||||
|
ext = ".html"
|
||||||
content = "<html><body>text</body>data"
|
content = "<html><body>text</body>data"
|
||||||
|
|
||||||
p = os.path.join(tmpdir.strpath, basename)
|
p = tmp_path / (basename + ext)
|
||||||
with open(p, "w") as f:
|
# write wrong html to see if core tries to parse it
|
||||||
# write wrong html to see if core tries to parse it
|
# (it shouldn't as it's a file attachment)
|
||||||
# (it shouldn't as it's a file attachment)
|
p.write_text(content)
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
lp.sec("ac1: prepare and send attachment + text to ac2")
|
lp.sec("ac1: prepare and send attachment + text to ac2")
|
||||||
chat.send_file(p, mime_type="text/html")
|
chat.send_file(str(p), mime_type="text/html")
|
||||||
|
|
||||||
lp.sec("ac2: receive message")
|
lp.sec("ac2: receive message")
|
||||||
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG")
|
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG")
|
||||||
assert ev.data2 > const.DC_CHAT_ID_LAST_SPECIAL
|
assert ev.data2 > dc.const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
msg = ac2.get_message_by_id(ev.data2)
|
msg = ac2.get_message_by_id(ev.data2)
|
||||||
|
|
||||||
assert open(msg.filename).read() == content
|
assert open(msg.filename).read() == content
|
||||||
assert msg.filename.endswith(basename)
|
msg.filename.index(basename)
|
||||||
|
assert msg.filename.endswith(ext)
|
||||||
|
|
||||||
|
|
||||||
def test_html_message(acfactory, lp):
|
def test_html_message(acfactory, lp):
|
||||||
@@ -324,6 +329,59 @@ def test_webxdc_message(acfactory, data, lp):
|
|||||||
assert len(list(ac2.direct_imap.conn.fetch(AND(seen=True)))) == 1
|
assert len(list(ac2.direct_imap.conn.fetch(AND(seen=True)))) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_webxdc_huge_update(acfactory, data, lp):
|
||||||
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||||
|
chat = ac1.create_chat(ac2)
|
||||||
|
|
||||||
|
msg1 = Message.new_empty(ac1, "webxdc")
|
||||||
|
msg1.set_text("message1")
|
||||||
|
msg1.set_file(data.get_path("webxdc/minimal.xdc"))
|
||||||
|
msg1 = chat.send_msg(msg1)
|
||||||
|
assert msg1.is_webxdc()
|
||||||
|
assert msg1.filename
|
||||||
|
|
||||||
|
msg2 = ac2._evtracker.wait_next_incoming_message()
|
||||||
|
assert msg2.is_webxdc()
|
||||||
|
|
||||||
|
payload = "A" * 1000
|
||||||
|
assert msg1.send_status_update({"payload": payload}, "some test data")
|
||||||
|
ac2._evtracker.get_matching("DC_EVENT_WEBXDC_STATUS_UPDATE")
|
||||||
|
update = msg2.get_status_updates()[0]
|
||||||
|
assert update["payload"] == payload
|
||||||
|
|
||||||
|
|
||||||
|
def test_webxdc_download_on_demand(acfactory, data, lp):
|
||||||
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||||
|
acfactory.introduce_each_other([ac1, ac2])
|
||||||
|
chat = acfactory.get_accepted_chat(ac1, ac2)
|
||||||
|
|
||||||
|
msg1 = Message.new_empty(ac1, "webxdc")
|
||||||
|
msg1.set_text("message1")
|
||||||
|
msg1.set_file(data.get_path("webxdc/minimal.xdc"))
|
||||||
|
msg1 = chat.send_msg(msg1)
|
||||||
|
assert msg1.is_webxdc()
|
||||||
|
assert msg1.filename
|
||||||
|
|
||||||
|
msg2 = ac2._evtracker.wait_next_incoming_message()
|
||||||
|
assert msg2.is_webxdc()
|
||||||
|
|
||||||
|
lp.sec("ac2 sets download limit")
|
||||||
|
ac2.set_config("download_limit", "100")
|
||||||
|
assert msg1.send_status_update({"payload": base64.b64encode(os.urandom(50000))}, "some test data")
|
||||||
|
ac2_update = ac2._evtracker.wait_next_incoming_message()
|
||||||
|
assert ac2_update.download_state == dc.const.DC_DOWNLOAD_AVAILABLE
|
||||||
|
assert not msg2.get_status_updates()
|
||||||
|
|
||||||
|
ac2_update.download_full()
|
||||||
|
ac2._evtracker.get_matching("DC_EVENT_WEBXDC_STATUS_UPDATE")
|
||||||
|
assert msg2.get_status_updates()
|
||||||
|
|
||||||
|
# Get a event notifying that the message disappeared from the chat.
|
||||||
|
msgs_changed_event = ac2._evtracker.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||||
|
assert msgs_changed_event.data1 == msg2.chat.id
|
||||||
|
assert msgs_changed_event.data2 == 0
|
||||||
|
|
||||||
|
|
||||||
def test_mvbox_sentbox_threads(acfactory, lp):
|
def test_mvbox_sentbox_threads(acfactory, lp):
|
||||||
lp.sec("ac1: start with mvbox thread")
|
lp.sec("ac1: start with mvbox thread")
|
||||||
ac1 = acfactory.new_online_configuring_account(mvbox_move=True, sentbox_watch=True)
|
ac1 = acfactory.new_online_configuring_account(mvbox_move=True, sentbox_watch=True)
|
||||||
@@ -351,7 +409,42 @@ def test_move_works(acfactory):
|
|||||||
|
|
||||||
# Message is downloaded
|
# Message is downloaded
|
||||||
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG")
|
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG")
|
||||||
assert ev.data2 > const.DC_CHAT_ID_LAST_SPECIAL
|
assert ev.data2 > dc.const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
|
|
||||||
|
|
||||||
|
def test_move_avoids_loop(acfactory):
|
||||||
|
"""Test that the message is only moved once.
|
||||||
|
|
||||||
|
This is to avoid busy loop if moved message reappears in the Inbox
|
||||||
|
or some scanned folder later.
|
||||||
|
For example, this happens on servers that alias `INBOX.DeltaChat` to `DeltaChat` folder,
|
||||||
|
so the message moved to `DeltaChat` appears as a new message in the `INBOX.DeltaChat` folder.
|
||||||
|
We do not want to move this message from `INBOX.DeltaChat` to `DeltaChat` again.
|
||||||
|
"""
|
||||||
|
ac1 = acfactory.new_online_configuring_account()
|
||||||
|
ac2 = acfactory.new_online_configuring_account(mvbox_move=True)
|
||||||
|
acfactory.bring_accounts_online()
|
||||||
|
ac1_chat = acfactory.get_accepted_chat(ac1, ac2)
|
||||||
|
ac1_chat.send_text("Message 1")
|
||||||
|
|
||||||
|
# Message is moved to the DeltaChat folder and downloaded.
|
||||||
|
ac2_msg1 = ac2._evtracker.wait_next_incoming_message()
|
||||||
|
assert ac2_msg1.text == "Message 1"
|
||||||
|
|
||||||
|
# Move the message to the INBOX again.
|
||||||
|
ac2.direct_imap.select_folder("DeltaChat")
|
||||||
|
ac2.direct_imap.conn.move(["*"], "INBOX")
|
||||||
|
|
||||||
|
ac1_chat.send_text("Message 2")
|
||||||
|
ac2_msg2 = ac2._evtracker.wait_next_incoming_message()
|
||||||
|
assert ac2_msg2.text == "Message 2"
|
||||||
|
|
||||||
|
# Check that Message 1 is still in the INBOX folder
|
||||||
|
# and Message 2 is in the DeltaChat folder.
|
||||||
|
ac2.direct_imap.select_folder("INBOX")
|
||||||
|
assert len(ac2.direct_imap.get_all_messages()) == 1
|
||||||
|
ac2.direct_imap.select_folder("DeltaChat")
|
||||||
|
assert len(ac2.direct_imap.get_all_messages()) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_move_works_on_self_sent(acfactory):
|
def test_move_works_on_self_sent(acfactory):
|
||||||
@@ -396,9 +489,12 @@ def test_forward_messages(acfactory, lp):
|
|||||||
lp.sec("ac2: check new chat has a forwarded message")
|
lp.sec("ac2: check new chat has a forwarded message")
|
||||||
assert chat3.is_promoted()
|
assert chat3.is_promoted()
|
||||||
messages = chat3.get_messages()
|
messages = chat3.get_messages()
|
||||||
|
assert len(messages) == 1
|
||||||
msg = messages[-1]
|
msg = messages[-1]
|
||||||
assert msg.is_forwarded()
|
assert msg.is_forwarded()
|
||||||
ac2.delete_messages(messages)
|
ac2.delete_messages(messages)
|
||||||
|
ev = ac2._evtracker.get_matching("DC_EVENT_MSG_DELETED")
|
||||||
|
assert ev.data2 == messages[0].id
|
||||||
assert not chat3.get_messages()
|
assert not chat3.get_messages()
|
||||||
|
|
||||||
|
|
||||||
@@ -531,8 +627,8 @@ def test_send_and_receive_message_markseen(acfactory, lp):
|
|||||||
lp.step("1")
|
lp.step("1")
|
||||||
for _i in range(2):
|
for _i in range(2):
|
||||||
ev = ac1._evtracker.get_matching("DC_EVENT_MSG_READ")
|
ev = ac1._evtracker.get_matching("DC_EVENT_MSG_READ")
|
||||||
assert ev.data1 > const.DC_CHAT_ID_LAST_SPECIAL
|
assert ev.data1 > dc.const.DC_CHAT_ID_LAST_SPECIAL
|
||||||
assert ev.data2 > const.DC_MSG_ID_LAST_SPECIAL
|
assert ev.data2 > dc.const.DC_MSG_ID_LAST_SPECIAL
|
||||||
lp.step("2")
|
lp.step("2")
|
||||||
|
|
||||||
# Check that ac1 marks the read receipt as read.
|
# Check that ac1 marks the read receipt as read.
|
||||||
@@ -567,7 +663,8 @@ def test_moved_markseen(acfactory):
|
|||||||
|
|
||||||
with ac2.direct_imap.idle() as idle2:
|
with ac2.direct_imap.idle() as idle2:
|
||||||
ac2.start_io()
|
ac2.start_io()
|
||||||
msg = ac2._evtracker.wait_next_incoming_message()
|
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
|
||||||
|
msg = ac2.get_message_by_id(ev.data2)
|
||||||
|
|
||||||
# Accept the contact request.
|
# Accept the contact request.
|
||||||
msg.chat.accept()
|
msg.chat.accept()
|
||||||
@@ -702,7 +799,7 @@ def test_mdn_asymmetric(acfactory, lp):
|
|||||||
assert len(chat.get_messages()) == 1
|
assert len(chat.get_messages()) == 1
|
||||||
|
|
||||||
# Wait for the message to be marked as seen on IMAP.
|
# Wait for the message to be marked as seen on IMAP.
|
||||||
ac1._evtracker.get_info_contains("Marked messages 1 in folder DeltaChat as seen.")
|
ac1._evtracker.get_info_contains("Marked messages [0-9]+ in folder DeltaChat as seen.")
|
||||||
|
|
||||||
# MDN is received even though MDNs are already disabled
|
# MDN is received even though MDNs are already disabled
|
||||||
assert msg_out.is_out_mdn_received()
|
assert msg_out.is_out_mdn_received()
|
||||||
@@ -1203,7 +1300,7 @@ def test_quote_encrypted(acfactory, lp):
|
|||||||
assert msg_in.is_encrypted() == quoted_msg.is_encrypted()
|
assert msg_in.is_encrypted() == quoted_msg.is_encrypted()
|
||||||
|
|
||||||
|
|
||||||
def test_quote_attachment(tmpdir, acfactory, lp):
|
def test_quote_attachment(tmp_path, acfactory, lp):
|
||||||
"""Test that replies with an attachment and a quote are received correctly."""
|
"""Test that replies with an attachment and a quote are received correctly."""
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
@@ -1218,15 +1315,14 @@ def test_quote_attachment(tmpdir, acfactory, lp):
|
|||||||
assert received_message.text == "hi"
|
assert received_message.text == "hi"
|
||||||
|
|
||||||
basename = "attachment.txt"
|
basename = "attachment.txt"
|
||||||
p = os.path.join(tmpdir.strpath, basename)
|
p = tmp_path / basename
|
||||||
with open(p, "w") as f:
|
p.write_text("data to send")
|
||||||
f.write("data to send")
|
|
||||||
|
|
||||||
lp.sec("ac2 sends a reply to ac1")
|
lp.sec("ac2 sends a reply to ac1")
|
||||||
chat2 = received_message.create_chat()
|
chat2 = received_message.create_chat()
|
||||||
reply = Message.new_empty(ac2, "file")
|
reply = Message.new_empty(ac2, "file")
|
||||||
reply.set_text("message reply")
|
reply.set_text("message reply")
|
||||||
reply.set_file(p)
|
reply.set_file(str(p))
|
||||||
reply.quote = received_message
|
reply.quote = received_message
|
||||||
chat2.send_msg(reply)
|
chat2.send_msg(reply)
|
||||||
|
|
||||||
@@ -1333,7 +1429,7 @@ def test_send_and_receive_image(acfactory, lp, data):
|
|||||||
assert m == msg_in
|
assert m == msg_in
|
||||||
|
|
||||||
|
|
||||||
def test_reaction_to_partially_fetched_msg(acfactory, lp, tmpdir):
|
def test_reaction_to_partially_fetched_msg(acfactory, lp, tmp_path):
|
||||||
"""See https://github.com/deltachat/deltachat-core-rust/issues/3688 "Partially downloaded
|
"""See https://github.com/deltachat/deltachat-core-rust/issues/3688 "Partially downloaded
|
||||||
messages are received out of order".
|
messages are received out of order".
|
||||||
|
|
||||||
@@ -1368,10 +1464,9 @@ def test_reaction_to_partially_fetched_msg(acfactory, lp, tmpdir):
|
|||||||
lp.sec("sending small+large messages from ac1 to ac2")
|
lp.sec("sending small+large messages from ac1 to ac2")
|
||||||
msgs = []
|
msgs = []
|
||||||
msgs.append(chat.send_text("hi"))
|
msgs.append(chat.send_text("hi"))
|
||||||
path = tmpdir.join("large")
|
path = tmp_path / "large"
|
||||||
with open(path, "wb") as fout:
|
path.write_bytes(os.urandom(download_limit + 1))
|
||||||
fout.write(os.urandom(download_limit + 1))
|
msgs.append(chat.send_file(str(path)))
|
||||||
msgs.append(chat.send_file(path.strpath))
|
|
||||||
|
|
||||||
lp.sec("sending a reaction to the large message from ac1 to ac2")
|
lp.sec("sending a reaction to the large message from ac1 to ac2")
|
||||||
react_str = "\N{THUMBS UP SIGN}"
|
react_str = "\N{THUMBS UP SIGN}"
|
||||||
@@ -1384,7 +1479,7 @@ def test_reaction_to_partially_fetched_msg(acfactory, lp, tmpdir):
|
|||||||
lp.sec("wait for ac2 to receive a reaction")
|
lp.sec("wait for ac2 to receive a reaction")
|
||||||
msg2 = ac2._evtracker.wait_next_reactions_changed()
|
msg2 = ac2._evtracker.wait_next_reactions_changed()
|
||||||
assert msg2.get_sender_contact().addr == ac1_addr
|
assert msg2.get_sender_contact().addr == ac1_addr
|
||||||
assert msg2.download_state == const.DC_DOWNLOAD_AVAILABLE
|
assert msg2.download_state == dc.const.DC_DOWNLOAD_AVAILABLE
|
||||||
assert reactions_queue.get() == msg2
|
assert reactions_queue.get() == msg2
|
||||||
reactions = msg2.get_reactions()
|
reactions = msg2.get_reactions()
|
||||||
contacts = reactions.get_contacts()
|
contacts = reactions.get_contacts()
|
||||||
@@ -1430,7 +1525,7 @@ def test_reactions_for_a_reordering_move(acfactory, lp):
|
|||||||
assert reactions.get_by_contact(contacts[0]) == react_str
|
assert reactions.get_by_contact(contacts[0]) == react_str
|
||||||
|
|
||||||
|
|
||||||
def test_import_export_online_all(acfactory, tmpdir, data, lp):
|
def test_import_export_online_all(acfactory, tmp_path, data, lp):
|
||||||
(ac1,) = acfactory.get_online_accounts(1)
|
(ac1,) = acfactory.get_online_accounts(1)
|
||||||
|
|
||||||
lp.sec("create some chat content")
|
lp.sec("create some chat content")
|
||||||
@@ -1442,10 +1537,10 @@ def test_import_export_online_all(acfactory, tmpdir, data, lp):
|
|||||||
chat1.send_image(original_image_path)
|
chat1.send_image(original_image_path)
|
||||||
|
|
||||||
# Add another 100KB file that ensures that the progress is smooth enough
|
# Add another 100KB file that ensures that the progress is smooth enough
|
||||||
path = tmpdir.join("attachment.txt")
|
path = tmp_path / "attachment.txt"
|
||||||
with open(path, "w") as file:
|
with path.open("w") as file:
|
||||||
file.truncate(100000)
|
file.truncate(100000)
|
||||||
chat1.send_file(path.strpath)
|
chat1.send_file(str(path))
|
||||||
|
|
||||||
def assert_account_is_proper(ac):
|
def assert_account_is_proper(ac):
|
||||||
contacts = ac.get_contacts(query="some1")
|
contacts = ac.get_contacts(query="some1")
|
||||||
@@ -1463,12 +1558,13 @@ def test_import_export_online_all(acfactory, tmpdir, data, lp):
|
|||||||
|
|
||||||
assert_account_is_proper(ac1)
|
assert_account_is_proper(ac1)
|
||||||
|
|
||||||
backupdir = tmpdir.mkdir("backup")
|
backupdir = tmp_path / "backup"
|
||||||
|
backupdir.mkdir()
|
||||||
|
|
||||||
lp.sec(f"export all to {backupdir}")
|
lp.sec(f"export all to {backupdir}")
|
||||||
with ac1.temp_plugin(ImexTracker()) as imex_tracker:
|
with ac1.temp_plugin(ImexTracker()) as imex_tracker:
|
||||||
ac1.stop_io()
|
ac1.stop_io()
|
||||||
ac1.imex(backupdir.strpath, const.DC_IMEX_EXPORT_BACKUP)
|
ac1.imex(str(backupdir), dc.const.DC_IMEX_EXPORT_BACKUP)
|
||||||
|
|
||||||
# check progress events for export
|
# check progress events for export
|
||||||
assert imex_tracker.wait_progress(1, progress_upper_limit=249)
|
assert imex_tracker.wait_progress(1, progress_upper_limit=249)
|
||||||
@@ -1486,7 +1582,7 @@ def test_import_export_online_all(acfactory, tmpdir, data, lp):
|
|||||||
ac2 = acfactory.get_unconfigured_account()
|
ac2 = acfactory.get_unconfigured_account()
|
||||||
|
|
||||||
lp.sec("get latest backup file")
|
lp.sec("get latest backup file")
|
||||||
path2 = ac2.get_latest_backupfile(backupdir.strpath)
|
path2 = ac2.get_latest_backupfile(str(backupdir))
|
||||||
assert path2 == path
|
assert path2 == path
|
||||||
|
|
||||||
lp.sec("import backup and check it's proper")
|
lp.sec("import backup and check it's proper")
|
||||||
@@ -1504,10 +1600,10 @@ def test_import_export_online_all(acfactory, tmpdir, data, lp):
|
|||||||
|
|
||||||
lp.sec(f"Second-time export all to {backupdir}")
|
lp.sec(f"Second-time export all to {backupdir}")
|
||||||
ac1.stop_io()
|
ac1.stop_io()
|
||||||
path2 = ac1.export_all(backupdir.strpath)
|
path2 = ac1.export_all(str(backupdir))
|
||||||
assert os.path.exists(path2)
|
assert os.path.exists(path2)
|
||||||
assert path2 != path
|
assert path2 != path
|
||||||
assert ac2.get_latest_backupfile(backupdir.strpath) == path2
|
assert ac2.get_latest_backupfile(str(backupdir)) == path2
|
||||||
|
|
||||||
|
|
||||||
def test_ac_setup_message(acfactory, lp):
|
def test_ac_setup_message(acfactory, lp):
|
||||||
@@ -1583,6 +1679,69 @@ def test_qr_join_chat(acfactory, lp):
|
|||||||
ac1._evtracker.wait_securejoin_inviter_progress(1000)
|
ac1._evtracker.wait_securejoin_inviter_progress(1000)
|
||||||
|
|
||||||
|
|
||||||
|
def test_qr_new_group_unblocked(acfactory, lp):
|
||||||
|
"""Regression test for a bug intoduced 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,
|
||||||
|
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_chat = ac1.create_group_chat("Group for joining", verified=True)
|
||||||
|
qr = ac1_chat.get_join_qr()
|
||||||
|
ac2.qr_join_chat(qr)
|
||||||
|
|
||||||
|
ac1._evtracker.wait_securejoin_inviter_progress(1000)
|
||||||
|
|
||||||
|
ac1_new_chat = ac1.create_group_chat("Another group")
|
||||||
|
ac1_new_chat.add_contact(ac2)
|
||||||
|
ac1_new_chat.send_text("Hello!")
|
||||||
|
|
||||||
|
# Receive "Member added" message.
|
||||||
|
ac2._evtracker.wait_next_incoming_message()
|
||||||
|
|
||||||
|
# Receive "Hello!" message.
|
||||||
|
ac2_msg = ac2._evtracker.wait_next_incoming_message()
|
||||||
|
assert ac2_msg.text == "Hello!"
|
||||||
|
assert ac2_msg.chat.is_contact_request()
|
||||||
|
|
||||||
|
|
||||||
|
def test_qr_email_capitalization(acfactory, lp):
|
||||||
|
"""Regression test for a bug
|
||||||
|
that resulted in failure to propagate verification via gossip in a verified group
|
||||||
|
when the database already contained the contact with a different email address capitalization.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ac1, ac2, ac3 = acfactory.get_online_accounts(3)
|
||||||
|
|
||||||
|
# ac1 adds ac2 as a contact with an email address in uppercase.
|
||||||
|
ac2_addr_uppercase = ac2.get_config("addr").upper()
|
||||||
|
lp.sec(f"ac1 creates a contact for ac2 ({ac2_addr_uppercase})")
|
||||||
|
ac1.create_contact(ac2_addr_uppercase)
|
||||||
|
|
||||||
|
lp.sec("ac3 creates a verified group with a QR code")
|
||||||
|
chat = ac3.create_group_chat("hello", verified=True)
|
||||||
|
qr = chat.get_join_qr()
|
||||||
|
|
||||||
|
lp.sec("ac1 joins a verified group via a QR code")
|
||||||
|
ac1_chat = ac1.qr_join_chat(qr)
|
||||||
|
msg = ac1._evtracker.wait_next_incoming_message()
|
||||||
|
assert msg.text == "Member Me ({}) added by {}.".format(ac1.get_config("addr"), ac3.get_config("addr"))
|
||||||
|
assert len(ac1_chat.get_contacts()) == 2
|
||||||
|
|
||||||
|
lp.sec("ac2 joins a verified group via a QR code")
|
||||||
|
ac2.qr_join_chat(qr)
|
||||||
|
ac1._evtracker.wait_next_incoming_message()
|
||||||
|
|
||||||
|
# ac1 should see both ac3 and ac2 as verified.
|
||||||
|
assert len(ac1_chat.get_contacts()) == 3
|
||||||
|
for contact in ac1_chat.get_contacts():
|
||||||
|
assert contact.is_verified()
|
||||||
|
|
||||||
|
|
||||||
def test_set_get_contact_avatar(acfactory, data, lp):
|
def test_set_get_contact_avatar(acfactory, data, lp):
|
||||||
lp.sec("configuring ac1 and ac2")
|
lp.sec("configuring ac1 and ac2")
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||||
@@ -1799,15 +1958,15 @@ def test_connectivity(acfactory, lp):
|
|||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||||
ac1.set_config("scan_all_folders_debounce_secs", "0")
|
ac1.set_config("scan_all_folders_debounce_secs", "0")
|
||||||
|
|
||||||
ac1._evtracker.wait_for_connectivity(const.DC_CONNECTIVITY_CONNECTED)
|
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_CONNECTED)
|
||||||
|
|
||||||
lp.sec("Test stop_io() and start_io()")
|
lp.sec("Test stop_io() and start_io()")
|
||||||
ac1.stop_io()
|
ac1.stop_io()
|
||||||
ac1._evtracker.wait_for_connectivity(const.DC_CONNECTIVITY_NOT_CONNECTED)
|
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_NOT_CONNECTED)
|
||||||
|
|
||||||
ac1.start_io()
|
ac1.start_io()
|
||||||
ac1._evtracker.wait_for_connectivity(const.DC_CONNECTIVITY_CONNECTING)
|
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_CONNECTING)
|
||||||
ac1._evtracker.wait_for_connectivity_change(const.DC_CONNECTIVITY_CONNECTING, const.DC_CONNECTIVITY_CONNECTED)
|
ac1._evtracker.wait_for_connectivity_change(dc.const.DC_CONNECTIVITY_CONNECTING, dc.const.DC_CONNECTIVITY_CONNECTED)
|
||||||
|
|
||||||
lp.sec(
|
lp.sec(
|
||||||
"Test that after calling start_io(), maybe_network() and waiting for `all_work_done()`, "
|
"Test that after calling start_io(), maybe_network() and waiting for `all_work_done()`, "
|
||||||
@@ -1828,8 +1987,8 @@ def test_connectivity(acfactory, lp):
|
|||||||
|
|
||||||
ac2.create_chat(ac1).send_text("Hi 2")
|
ac2.create_chat(ac1).send_text("Hi 2")
|
||||||
|
|
||||||
ac1._evtracker.wait_for_connectivity_change(const.DC_CONNECTIVITY_CONNECTED, const.DC_CONNECTIVITY_WORKING)
|
ac1._evtracker.wait_for_connectivity_change(dc.const.DC_CONNECTIVITY_CONNECTED, dc.const.DC_CONNECTIVITY_WORKING)
|
||||||
ac1._evtracker.wait_for_connectivity_change(const.DC_CONNECTIVITY_WORKING, const.DC_CONNECTIVITY_CONNECTED)
|
ac1._evtracker.wait_for_connectivity_change(dc.const.DC_CONNECTIVITY_WORKING, dc.const.DC_CONNECTIVITY_CONNECTED)
|
||||||
|
|
||||||
msgs = ac1.create_chat(ac2).get_messages()
|
msgs = ac1.create_chat(ac2).get_messages()
|
||||||
assert len(msgs) == 2
|
assert len(msgs) == 2
|
||||||
@@ -1839,7 +1998,7 @@ def test_connectivity(acfactory, lp):
|
|||||||
|
|
||||||
ac1.maybe_network()
|
ac1.maybe_network()
|
||||||
while 1:
|
while 1:
|
||||||
assert ac1.get_connectivity() == const.DC_CONNECTIVITY_CONNECTED
|
assert ac1.get_connectivity() == dc.const.DC_CONNECTIVITY_CONNECTED
|
||||||
if ac1.all_work_done():
|
if ac1.all_work_done():
|
||||||
break
|
break
|
||||||
ac1._evtracker.get_matching("DC_EVENT_CONNECTIVITY_CHANGED")
|
ac1._evtracker.get_matching("DC_EVENT_CONNECTIVITY_CHANGED")
|
||||||
@@ -1854,7 +2013,7 @@ def test_connectivity(acfactory, lp):
|
|||||||
ac1.maybe_network()
|
ac1.maybe_network()
|
||||||
|
|
||||||
while 1:
|
while 1:
|
||||||
assert ac1.get_connectivity() == const.DC_CONNECTIVITY_CONNECTED
|
assert ac1.get_connectivity() == dc.const.DC_CONNECTIVITY_CONNECTED
|
||||||
if ac1.all_work_done():
|
if ac1.all_work_done():
|
||||||
break
|
break
|
||||||
ac1._evtracker.get_matching("DC_EVENT_CONNECTIVITY_CHANGED")
|
ac1._evtracker.get_matching("DC_EVENT_CONNECTIVITY_CHANGED")
|
||||||
@@ -1863,10 +2022,10 @@ def test_connectivity(acfactory, lp):
|
|||||||
|
|
||||||
ac1.set_config("configured_mail_pw", "abc")
|
ac1.set_config("configured_mail_pw", "abc")
|
||||||
ac1.stop_io()
|
ac1.stop_io()
|
||||||
ac1._evtracker.wait_for_connectivity(const.DC_CONNECTIVITY_NOT_CONNECTED)
|
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_NOT_CONNECTED)
|
||||||
ac1.start_io()
|
ac1.start_io()
|
||||||
ac1._evtracker.wait_for_connectivity(const.DC_CONNECTIVITY_CONNECTING)
|
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_CONNECTING)
|
||||||
ac1._evtracker.wait_for_connectivity(const.DC_CONNECTIVITY_NOT_CONNECTED)
|
ac1._evtracker.wait_for_connectivity(dc.const.DC_CONNECTIVITY_NOT_CONNECTED)
|
||||||
|
|
||||||
|
|
||||||
def test_fetch_deleted_msg(acfactory, lp):
|
def test_fetch_deleted_msg(acfactory, lp):
|
||||||
@@ -2349,9 +2508,9 @@ def test_archived_muted_chat(acfactory, lp):
|
|||||||
lp.sec("wait for ac2 to receive DC_EVENT_MSGS_CHANGED for DC_CHAT_ID_ARCHIVED_LINK")
|
lp.sec("wait for ac2 to receive DC_EVENT_MSGS_CHANGED for DC_CHAT_ID_ARCHIVED_LINK")
|
||||||
while 1:
|
while 1:
|
||||||
ev = ac2._evtracker.get_matching("DC_EVENT_MSGS_CHANGED")
|
ev = ac2._evtracker.get_matching("DC_EVENT_MSGS_CHANGED")
|
||||||
if ev.data1 == const.DC_CHAT_ID_ARCHIVED_LINK:
|
if ev.data1 == dc.const.DC_CHAT_ID_ARCHIVED_LINK:
|
||||||
assert ev.data2 == 0
|
assert ev.data2 == 0
|
||||||
archive = ac2.get_chat_by_id(const.DC_CHAT_ID_ARCHIVED_LINK)
|
archive = ac2.get_chat_by_id(dc.const.DC_CHAT_ID_ARCHIVED_LINK)
|
||||||
assert archive.count_fresh_messages() == 1
|
assert archive.count_fresh_messages() == 1
|
||||||
assert chat2.count_fresh_messages() == 1
|
assert chat2.count_fresh_messages() == 1
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -30,28 +30,28 @@ def wait_msgs_changed(account, msgs_list):
|
|||||||
|
|
||||||
|
|
||||||
class TestOnlineInCreation:
|
class TestOnlineInCreation:
|
||||||
def test_increation_not_blobdir(self, tmpdir, acfactory, lp):
|
def test_increation_not_blobdir(self, tmp_path, acfactory, lp):
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||||
chat = ac1.create_chat(ac2)
|
chat = ac1.create_chat(ac2)
|
||||||
|
|
||||||
lp.sec("Creating in-creation file outside of blobdir")
|
lp.sec("Creating in-creation file outside of blobdir")
|
||||||
assert tmpdir.strpath != ac1.get_blobdir()
|
assert str(tmp_path) != ac1.get_blobdir()
|
||||||
src = tmpdir.join("file.txt").ensure(file=1)
|
src = tmp_path / "file.txt"
|
||||||
|
src.touch()
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
chat.prepare_message_file(src.strpath)
|
chat.prepare_message_file(str(src))
|
||||||
|
|
||||||
def test_no_increation_copies_to_blobdir(self, tmpdir, acfactory, lp):
|
def test_no_increation_copies_to_blobdir(self, tmp_path, acfactory, lp):
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||||
chat = ac1.create_chat(ac2)
|
chat = ac1.create_chat(ac2)
|
||||||
|
|
||||||
lp.sec("Creating file outside of blobdir")
|
lp.sec("Creating file outside of blobdir")
|
||||||
assert tmpdir.strpath != ac1.get_blobdir()
|
assert str(tmp_path) != ac1.get_blobdir()
|
||||||
src = tmpdir.join("file.txt")
|
src = tmp_path / "file.txt"
|
||||||
src.write("hello there\n")
|
src.write_text("hello there\n")
|
||||||
chat.send_file(src.strpath)
|
msg = chat.send_file(str(src))
|
||||||
|
assert msg.filename.startswith(os.path.join(ac1.get_blobdir(), "file"))
|
||||||
blob_src = os.path.join(ac1.get_blobdir(), "file.txt")
|
assert msg.filename.endswith(".txt")
|
||||||
assert os.path.exists(blob_src), "file.txt not copied to blobdir"
|
|
||||||
|
|
||||||
def test_forward_increation(self, acfactory, data, lp):
|
def test_forward_increation(self, acfactory, data, lp):
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ from datetime import datetime, timedelta, timezone
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from deltachat import Account, const
|
import deltachat as dc
|
||||||
from deltachat.capi import ffi, lib
|
from deltachat.capi import ffi, lib
|
||||||
from deltachat.cutil import iter_array
|
from deltachat.cutil import iter_array
|
||||||
from deltachat.hookspec import account_hookimpl
|
|
||||||
from deltachat.message import Message
|
|
||||||
from deltachat.tracker import ImexFailed
|
from deltachat.tracker import ImexFailed
|
||||||
|
from deltachat import Account, account_hookimpl, Message
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@@ -52,18 +51,18 @@ def test_parse_system_add_remove(msgtext, res):
|
|||||||
|
|
||||||
|
|
||||||
class TestOfflineAccountBasic:
|
class TestOfflineAccountBasic:
|
||||||
def test_wrong_db(self, tmpdir):
|
def test_wrong_db(self, tmp_path):
|
||||||
p = tmpdir.join("hello.db")
|
p = tmp_path / "hello.db"
|
||||||
p.write("123")
|
p.write_text("123")
|
||||||
account = Account(p.strpath)
|
account = Account(str(p))
|
||||||
assert not account.is_open()
|
assert not account.is_open()
|
||||||
|
|
||||||
def test_os_name(self, tmpdir):
|
def test_os_name(self, tmp_path):
|
||||||
p = tmpdir.join("hello.db")
|
p = tmp_path / "hello.db"
|
||||||
# we can't easily test if os_name is used in X-Mailer
|
# we can't easily test if os_name is used in X-Mailer
|
||||||
# outgoing messages without a full Online test
|
# outgoing messages without a full Online test
|
||||||
# but we at least check Account accepts the arg
|
# but we at least check Account accepts the arg
|
||||||
ac1 = Account(p.strpath, os_name="solarpunk")
|
ac1 = Account(str(p), os_name="solarpunk")
|
||||||
ac1.get_info()
|
ac1.get_info()
|
||||||
|
|
||||||
def test_preconfigure_keypair(self, acfactory, data):
|
def test_preconfigure_keypair(self, acfactory, data):
|
||||||
@@ -299,13 +298,13 @@ class TestOfflineChat:
|
|||||||
assert not d["draft"] if chat.get_draft() is None else chat.get_draft()
|
assert not d["draft"] if chat.get_draft() is None else chat.get_draft()
|
||||||
|
|
||||||
def test_group_chat_creation_with_translation(self, ac1):
|
def test_group_chat_creation_with_translation(self, ac1):
|
||||||
ac1.set_stock_translation(const.DC_STR_GROUP_NAME_CHANGED_BY_YOU, "abc %1$s xyz %2$s")
|
ac1.set_stock_translation(dc.const.DC_STR_GROUP_NAME_CHANGED_BY_YOU, "abc %1$s xyz %2$s")
|
||||||
ac1._evtracker.consume_events()
|
ac1._evtracker.consume_events()
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ac1.set_stock_translation(const.DC_STR_FILE, "xyz %1$s")
|
ac1.set_stock_translation(dc.const.DC_STR_FILE, "xyz %1$s")
|
||||||
ac1._evtracker.get_matching("DC_EVENT_WARNING")
|
ac1._evtracker.get_matching("DC_EVENT_WARNING")
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ac1.set_stock_translation(const.DC_STR_CONTACT_NOT_VERIFIED, "xyz %2$s")
|
ac1.set_stock_translation(dc.const.DC_STR_CONTACT_NOT_VERIFIED, "xyz %2$s")
|
||||||
ac1._evtracker.get_matching("DC_EVENT_WARNING")
|
ac1._evtracker.get_matching("DC_EVENT_WARNING")
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ac1.set_stock_translation(500, "xyz %1$s")
|
ac1.set_stock_translation(500, "xyz %1$s")
|
||||||
@@ -481,6 +480,19 @@ class TestOfflineChat:
|
|||||||
contact2 = ac1.create_contact("display1 <x@example.org>", "real")
|
contact2 = ac1.create_contact("display1 <x@example.org>", "real")
|
||||||
assert contact2.name == "real"
|
assert contact2.name == "real"
|
||||||
|
|
||||||
|
def test_send_lots_of_offline_msgs(self, acfactory):
|
||||||
|
ac1 = acfactory.get_pseudo_configured_account()
|
||||||
|
ac1.set_config("configured_mail_server", "example.org")
|
||||||
|
ac1.set_config("configured_mail_user", "example.org")
|
||||||
|
ac1.set_config("configured_mail_pw", "example.org")
|
||||||
|
ac1.set_config("configured_send_server", "example.org")
|
||||||
|
ac1.set_config("configured_send_user", "example.org")
|
||||||
|
ac1.set_config("configured_send_pw", "example.org")
|
||||||
|
ac1.start_io()
|
||||||
|
chat = ac1.create_contact("some1@example.org", name="some1").create_chat()
|
||||||
|
for i in range(50):
|
||||||
|
chat.send_text("hello")
|
||||||
|
|
||||||
def test_create_chat_simple(self, acfactory):
|
def test_create_chat_simple(self, acfactory):
|
||||||
ac1 = acfactory.get_pseudo_configured_account()
|
ac1 = acfactory.get_pseudo_configured_account()
|
||||||
contact1 = ac1.create_contact("some1@example.org", name="some1")
|
contact1 = ac1.create_contact("some1@example.org", name="some1")
|
||||||
@@ -496,22 +508,22 @@ class TestOfflineChat:
|
|||||||
contact = msg.get_sender_contact()
|
contact = msg.get_sender_contact()
|
||||||
assert contact == ac1.get_self_contact()
|
assert contact == ac1.get_self_contact()
|
||||||
|
|
||||||
def test_import_export_on_unencrypted_acct(self, acfactory, tmpdir):
|
def test_import_export_on_unencrypted_acct(self, acfactory, tmp_path):
|
||||||
backupdir = tmpdir.mkdir("backup")
|
backupdir = tmp_path / "backup"
|
||||||
|
backupdir.mkdir()
|
||||||
ac1 = acfactory.get_pseudo_configured_account()
|
ac1 = acfactory.get_pseudo_configured_account()
|
||||||
chat = ac1.create_contact("some1 <some1@example.org>").create_chat()
|
chat = ac1.create_contact("some1 <some1@example.org>").create_chat()
|
||||||
# send a text message
|
# send a text message
|
||||||
msg = chat.send_text("msg1")
|
msg = chat.send_text("msg1")
|
||||||
# send a binary file
|
# send a binary file
|
||||||
bin = tmpdir.join("some.bin")
|
bin = tmp_path / "some.bin"
|
||||||
with bin.open("w") as f:
|
bin.write_bytes(b"\00123" * 10000)
|
||||||
f.write("\00123" * 10000)
|
msg = chat.send_file(str(bin))
|
||||||
msg = chat.send_file(bin.strpath)
|
|
||||||
contact = msg.get_sender_contact()
|
contact = msg.get_sender_contact()
|
||||||
assert contact == ac1.get_self_contact()
|
assert contact == ac1.get_self_contact()
|
||||||
assert not backupdir.listdir()
|
assert not list(backupdir.iterdir())
|
||||||
ac1.stop_io()
|
ac1.stop_io()
|
||||||
path = ac1.export_all(backupdir.strpath)
|
path = ac1.export_all(str(backupdir))
|
||||||
assert os.path.exists(path)
|
assert os.path.exists(path)
|
||||||
ac2 = acfactory.get_unconfigured_account()
|
ac2 = acfactory.get_unconfigured_account()
|
||||||
ac2.import_all(path)
|
ac2.import_all(path)
|
||||||
@@ -525,27 +537,27 @@ class TestOfflineChat:
|
|||||||
assert messages[0].text == "msg1"
|
assert messages[0].text == "msg1"
|
||||||
assert os.path.exists(messages[1].filename)
|
assert os.path.exists(messages[1].filename)
|
||||||
|
|
||||||
def test_import_export_on_encrypted_acct(self, acfactory, tmpdir):
|
def test_import_export_on_encrypted_acct(self, acfactory, tmp_path):
|
||||||
passphrase1 = "passphrase1"
|
passphrase1 = "passphrase1"
|
||||||
passphrase2 = "passphrase2"
|
passphrase2 = "passphrase2"
|
||||||
backupdir = tmpdir.mkdir("backup")
|
backupdir = tmp_path / "backup"
|
||||||
|
backupdir.mkdir()
|
||||||
ac1 = acfactory.get_pseudo_configured_account(passphrase=passphrase1)
|
ac1 = acfactory.get_pseudo_configured_account(passphrase=passphrase1)
|
||||||
|
|
||||||
chat = ac1.create_contact("some1 <some1@example.org>").create_chat()
|
chat = ac1.create_contact("some1 <some1@example.org>").create_chat()
|
||||||
# send a text message
|
# send a text message
|
||||||
msg = chat.send_text("msg1")
|
msg = chat.send_text("msg1")
|
||||||
# send a binary file
|
# send a binary file
|
||||||
bin = tmpdir.join("some.bin")
|
bin = tmp_path / "some.bin"
|
||||||
with bin.open("w") as f:
|
bin.write_bytes(b"\00123" * 10000)
|
||||||
f.write("\00123" * 10000)
|
msg = chat.send_file(str(bin))
|
||||||
msg = chat.send_file(bin.strpath)
|
|
||||||
contact = msg.get_sender_contact()
|
contact = msg.get_sender_contact()
|
||||||
assert contact == ac1.get_self_contact()
|
assert contact == ac1.get_self_contact()
|
||||||
|
|
||||||
assert not backupdir.listdir()
|
assert not list(backupdir.iterdir())
|
||||||
ac1.stop_io()
|
ac1.stop_io()
|
||||||
|
|
||||||
path = ac1.export_all(backupdir.strpath)
|
path = ac1.export_all(str(backupdir))
|
||||||
assert os.path.exists(path)
|
assert os.path.exists(path)
|
||||||
|
|
||||||
ac2 = acfactory.get_unconfigured_account(closed=True)
|
ac2 = acfactory.get_unconfigured_account(closed=True)
|
||||||
@@ -580,27 +592,27 @@ class TestOfflineChat:
|
|||||||
assert messages[0].text == "msg1"
|
assert messages[0].text == "msg1"
|
||||||
assert os.path.exists(messages[1].filename)
|
assert os.path.exists(messages[1].filename)
|
||||||
|
|
||||||
def test_import_export_with_passphrase(self, acfactory, tmpdir):
|
def test_import_export_with_passphrase(self, acfactory, tmp_path):
|
||||||
passphrase = "test_passphrase"
|
passphrase = "test_passphrase"
|
||||||
wrong_passphrase = "wrong_passprase"
|
wrong_passphrase = "wrong_passprase"
|
||||||
backupdir = tmpdir.mkdir("backup")
|
backupdir = tmp_path / "backup"
|
||||||
|
backupdir.mkdir()
|
||||||
ac1 = acfactory.get_pseudo_configured_account()
|
ac1 = acfactory.get_pseudo_configured_account()
|
||||||
|
|
||||||
chat = ac1.create_contact("some1 <some1@example.org>").create_chat()
|
chat = ac1.create_contact("some1 <some1@example.org>").create_chat()
|
||||||
# send a text message
|
# send a text message
|
||||||
msg = chat.send_text("msg1")
|
msg = chat.send_text("msg1")
|
||||||
# send a binary file
|
# send a binary file
|
||||||
bin = tmpdir.join("some.bin")
|
bin = tmp_path / "some.bin"
|
||||||
with bin.open("w") as f:
|
bin.write_bytes(b"\00123" * 10000)
|
||||||
f.write("\00123" * 10000)
|
msg = chat.send_file(str(bin))
|
||||||
msg = chat.send_file(bin.strpath)
|
|
||||||
contact = msg.get_sender_contact()
|
contact = msg.get_sender_contact()
|
||||||
assert contact == ac1.get_self_contact()
|
assert contact == ac1.get_self_contact()
|
||||||
|
|
||||||
assert not backupdir.listdir()
|
assert not list(backupdir.iterdir())
|
||||||
ac1.stop_io()
|
ac1.stop_io()
|
||||||
|
|
||||||
path = ac1.export_all(backupdir.strpath, passphrase)
|
path = ac1.export_all(str(backupdir), passphrase)
|
||||||
assert os.path.exists(path)
|
assert os.path.exists(path)
|
||||||
|
|
||||||
ac2 = acfactory.get_unconfigured_account()
|
ac2 = acfactory.get_unconfigured_account()
|
||||||
@@ -619,7 +631,7 @@ class TestOfflineChat:
|
|||||||
assert messages[0].text == "msg1"
|
assert messages[0].text == "msg1"
|
||||||
assert os.path.exists(messages[1].filename)
|
assert os.path.exists(messages[1].filename)
|
||||||
|
|
||||||
def test_import_encrypted_bak_into_encrypted_acct(self, acfactory, tmpdir):
|
def test_import_encrypted_bak_into_encrypted_acct(self, acfactory, tmp_path):
|
||||||
"""
|
"""
|
||||||
Test that account passphrase isn't lost if backup failed to be imported.
|
Test that account passphrase isn't lost if backup failed to be imported.
|
||||||
See https://github.com/deltachat/deltachat-core-rust/issues/3379
|
See https://github.com/deltachat/deltachat-core-rust/issues/3379
|
||||||
@@ -627,24 +639,24 @@ class TestOfflineChat:
|
|||||||
acct_passphrase = "passphrase1"
|
acct_passphrase = "passphrase1"
|
||||||
bak_passphrase = "passphrase2"
|
bak_passphrase = "passphrase2"
|
||||||
wrong_passphrase = "wrong_passprase"
|
wrong_passphrase = "wrong_passprase"
|
||||||
backupdir = tmpdir.mkdir("backup")
|
backupdir = tmp_path / "backup"
|
||||||
|
backupdir.mkdir()
|
||||||
|
|
||||||
ac1 = acfactory.get_pseudo_configured_account()
|
ac1 = acfactory.get_pseudo_configured_account()
|
||||||
chat = ac1.create_contact("some1 <some1@example.org>").create_chat()
|
chat = ac1.create_contact("some1 <some1@example.org>").create_chat()
|
||||||
# send a text message
|
# send a text message
|
||||||
msg = chat.send_text("msg1")
|
msg = chat.send_text("msg1")
|
||||||
# send a binary file
|
# send a binary file
|
||||||
bin = tmpdir.join("some.bin")
|
bin = tmp_path / "some.bin"
|
||||||
with bin.open("w") as f:
|
bin.write_bytes(b"\00123" * 10000)
|
||||||
f.write("\00123" * 10000)
|
msg = chat.send_file(str(bin))
|
||||||
msg = chat.send_file(bin.strpath)
|
|
||||||
contact = msg.get_sender_contact()
|
contact = msg.get_sender_contact()
|
||||||
assert contact == ac1.get_self_contact()
|
assert contact == ac1.get_self_contact()
|
||||||
|
|
||||||
assert not backupdir.listdir()
|
assert not list(backupdir.iterdir())
|
||||||
ac1.stop_io()
|
ac1.stop_io()
|
||||||
|
|
||||||
path = ac1.export_all(backupdir.strpath, bak_passphrase)
|
path = ac1.export_all(str(backupdir), bak_passphrase)
|
||||||
assert os.path.exists(path)
|
assert os.path.exists(path)
|
||||||
|
|
||||||
ac2 = acfactory.get_unconfigured_account(closed=True)
|
ac2 = acfactory.get_unconfigured_account(closed=True)
|
||||||
@@ -805,7 +817,7 @@ class TestOfflineChat:
|
|||||||
|
|
||||||
lp.sec("check message count of only system messages (without daymarkers)")
|
lp.sec("check message count of only system messages (without daymarkers)")
|
||||||
dc_array = ffi.gc(
|
dc_array = ffi.gc(
|
||||||
lib.dc_get_chat_msgs(ac1._dc_context, chat.id, const.DC_GCM_INFO_ONLY, 0),
|
lib.dc_get_chat_msgs(ac1._dc_context, chat.id, dc.const.DC_GCM_INFO_ONLY, 0),
|
||||||
lib.dc_array_unref,
|
lib.dc_array_unref,
|
||||||
)
|
)
|
||||||
assert len(list(iter_array(dc_array, lambda x: x))) == 2
|
assert len(list(iter_array(dc_array, lambda x: x))) == 2
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import os
|
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
|
|
||||||
from deltachat import capi, const, cutil, register_global_plugin
|
import deltachat as dc
|
||||||
|
from deltachat import capi, cutil, register_global_plugin
|
||||||
from deltachat.capi import ffi, lib
|
from deltachat.capi import ffi, lib
|
||||||
from deltachat.hookspec import global_hookimpl
|
from deltachat.hookspec import global_hookimpl
|
||||||
from deltachat.testplugin import (
|
from deltachat.testplugin import (
|
||||||
@@ -9,6 +9,7 @@ from deltachat.testplugin import (
|
|||||||
create_dict_from_files_in_path,
|
create_dict_from_files_in_path,
|
||||||
write_dict_to_dir,
|
write_dict_to_dir,
|
||||||
)
|
)
|
||||||
|
from deltachat.cutil import from_optional_dc_charpointer
|
||||||
|
|
||||||
# from deltachat.account import EventLogger
|
# from deltachat.account import EventLogger
|
||||||
|
|
||||||
@@ -64,16 +65,17 @@ class TestACSetup:
|
|||||||
assert pc._account2state[ac1] == pc.IDLEREADY
|
assert pc._account2state[ac1] == pc.IDLEREADY
|
||||||
assert pc._account2state[ac2] == pc.IDLEREADY
|
assert pc._account2state[ac2] == pc.IDLEREADY
|
||||||
|
|
||||||
def test_store_and_retrieve_configured_account_cache(self, acfactory, tmpdir):
|
def test_store_and_retrieve_configured_account_cache(self, acfactory, tmp_path):
|
||||||
ac1 = acfactory.get_pseudo_configured_account()
|
ac1 = acfactory.get_pseudo_configured_account()
|
||||||
holder = acfactory._acsetup.testprocess
|
holder = acfactory._acsetup.testprocess
|
||||||
assert holder.cache_maybe_store_configured_db_files(ac1)
|
assert holder.cache_maybe_store_configured_db_files(ac1)
|
||||||
assert not holder.cache_maybe_store_configured_db_files(ac1)
|
assert not holder.cache_maybe_store_configured_db_files(ac1)
|
||||||
acdir = tmpdir.mkdir("newaccount")
|
acdir = tmp_path / "newaccount"
|
||||||
|
acdir.mkdir()
|
||||||
addr = ac1.get_config("addr")
|
addr = ac1.get_config("addr")
|
||||||
target_db_path = acdir.join("db").strpath
|
target_db_path = acdir / "db"
|
||||||
assert holder.cache_maybe_retrieve_configured_db_files(addr, target_db_path)
|
assert holder.cache_maybe_retrieve_configured_db_files(addr, str(target_db_path))
|
||||||
assert len(os.listdir(acdir)) >= 2
|
assert sum(1 for _ in acdir.iterdir()) >= 2
|
||||||
|
|
||||||
|
|
||||||
def test_liveconfig_caching(acfactory, monkeypatch):
|
def test_liveconfig_caching(acfactory, monkeypatch):
|
||||||
@@ -111,40 +113,40 @@ def test_dc_close_events(acfactory):
|
|||||||
shutdowns.get(timeout=2)
|
shutdowns.get(timeout=2)
|
||||||
|
|
||||||
|
|
||||||
def test_wrong_db(tmpdir):
|
def test_wrong_db(tmp_path):
|
||||||
p = tmpdir.join("hello.db")
|
p = tmp_path / "hello.db"
|
||||||
# write an invalid database file
|
# write an invalid database file
|
||||||
p.write("x123" * 10)
|
p.write_bytes(b"x123" * 10)
|
||||||
|
|
||||||
context = lib.dc_context_new(ffi.NULL, p.strpath.encode("ascii"), ffi.NULL)
|
context = lib.dc_context_new(ffi.NULL, str(p).encode("ascii"), ffi.NULL)
|
||||||
assert not lib.dc_context_is_open(context)
|
assert not lib.dc_context_is_open(context)
|
||||||
|
|
||||||
|
|
||||||
def test_empty_blobdir(tmpdir):
|
def test_empty_blobdir(tmp_path):
|
||||||
db_fname = tmpdir.join("hello.db")
|
db_fname = tmp_path / "hello.db"
|
||||||
# Apparently some client code expects this to be the same as passing NULL.
|
# Apparently some client code expects this to be the same as passing NULL.
|
||||||
ctx = ffi.gc(
|
ctx = ffi.gc(
|
||||||
lib.dc_context_new(ffi.NULL, db_fname.strpath.encode("ascii"), b""),
|
lib.dc_context_new(ffi.NULL, str(db_fname).encode("ascii"), b""),
|
||||||
lib.dc_context_unref,
|
lib.dc_context_unref,
|
||||||
)
|
)
|
||||||
assert ctx != ffi.NULL
|
assert ctx != ffi.NULL
|
||||||
|
|
||||||
|
|
||||||
def test_event_defines():
|
def test_event_defines():
|
||||||
assert const.DC_EVENT_INFO == 100
|
assert dc.const.DC_EVENT_INFO == 100
|
||||||
assert const.DC_CONTACT_ID_SELF
|
assert dc.const.DC_CONTACT_ID_SELF
|
||||||
|
|
||||||
|
|
||||||
def test_sig():
|
def test_sig():
|
||||||
sig = capi.lib.dc_event_has_string_data
|
sig = capi.lib.dc_event_has_string_data
|
||||||
assert not sig(const.DC_EVENT_MSGS_CHANGED)
|
assert not sig(dc.const.DC_EVENT_MSGS_CHANGED)
|
||||||
assert sig(const.DC_EVENT_INFO)
|
assert sig(dc.const.DC_EVENT_INFO)
|
||||||
assert sig(const.DC_EVENT_WARNING)
|
assert sig(dc.const.DC_EVENT_WARNING)
|
||||||
assert sig(const.DC_EVENT_ERROR)
|
assert sig(dc.const.DC_EVENT_ERROR)
|
||||||
assert sig(const.DC_EVENT_SMTP_CONNECTED)
|
assert sig(dc.const.DC_EVENT_SMTP_CONNECTED)
|
||||||
assert sig(const.DC_EVENT_IMAP_CONNECTED)
|
assert sig(dc.const.DC_EVENT_IMAP_CONNECTED)
|
||||||
assert sig(const.DC_EVENT_SMTP_MESSAGE_SENT)
|
assert sig(dc.const.DC_EVENT_SMTP_MESSAGE_SENT)
|
||||||
assert sig(const.DC_EVENT_IMEX_FILE_WRITTEN)
|
assert sig(dc.const.DC_EVENT_IMEX_FILE_WRITTEN)
|
||||||
|
|
||||||
|
|
||||||
def test_markseen_invalid_message_ids(acfactory):
|
def test_markseen_invalid_message_ids(acfactory):
|
||||||
@@ -173,10 +175,10 @@ def test_provider_info_none():
|
|||||||
assert lib.dc_provider_new_from_email(ctx, cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL
|
assert lib.dc_provider_new_from_email(ctx, cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL
|
||||||
|
|
||||||
|
|
||||||
def test_get_info_open(tmpdir):
|
def test_get_info_open(tmp_path):
|
||||||
db_fname = tmpdir.join("test.db")
|
db_fname = tmp_path / "test.db"
|
||||||
ctx = ffi.gc(
|
ctx = ffi.gc(
|
||||||
lib.dc_context_new(ffi.NULL, db_fname.strpath.encode("ascii"), ffi.NULL),
|
lib.dc_context_new(ffi.NULL, str(db_fname).encode("ascii"), ffi.NULL),
|
||||||
lib.dc_context_unref,
|
lib.dc_context_unref,
|
||||||
)
|
)
|
||||||
info = cutil.from_dc_charpointer(lib.dc_get_info(ctx))
|
info = cutil.from_dc_charpointer(lib.dc_get_info(ctx))
|
||||||
@@ -215,3 +217,19 @@ def test_logged_ac_process_ffi_failure(acfactory):
|
|||||||
assert "ac_process_ffi_event" in res
|
assert "ac_process_ffi_event" in res
|
||||||
assert "ZeroDivisionError" in res
|
assert "ZeroDivisionError" in res
|
||||||
assert "Traceback" in res
|
assert "Traceback" in res
|
||||||
|
|
||||||
|
|
||||||
|
def test_jsonrpc_blocking_call(tmp_path):
|
||||||
|
accounts_fname = tmp_path / "accounts"
|
||||||
|
accounts = ffi.gc(
|
||||||
|
lib.dc_accounts_new(ffi.NULL, str(accounts_fname).encode("ascii")),
|
||||||
|
lib.dc_accounts_unref,
|
||||||
|
)
|
||||||
|
jsonrpc = ffi.gc(lib.dc_jsonrpc_init(accounts), lib.dc_jsonrpc_unref)
|
||||||
|
res = from_optional_dc_charpointer(
|
||||||
|
lib.dc_jsonrpc_blocking_call(jsonrpc, b"check_email_validity", b'["alice@example.org"]'),
|
||||||
|
)
|
||||||
|
assert res == "true"
|
||||||
|
|
||||||
|
res = from_optional_dc_charpointer(lib.dc_jsonrpc_blocking_call(jsonrpc, b"check_email_validity", b'["alice"]'))
|
||||||
|
assert res == "false"
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ deps =
|
|||||||
pygments
|
pygments
|
||||||
restructuredtext_lint
|
restructuredtext_lint
|
||||||
commands =
|
commands =
|
||||||
black --quiet --check --diff setup.py install_python_bindings.py src/deltachat examples/ tests/
|
black --quiet --check --diff setup.py src/deltachat examples/ tests/
|
||||||
ruff src/deltachat tests/ examples/
|
ruff src/deltachat tests/ examples/
|
||||||
rst-lint --encoding 'utf-8' README.rst
|
rst-lint --encoding 'utf-8' README.rst
|
||||||
|
|
||||||
|
|||||||
1
release-date.in
Normal file
1
release-date.in
Normal file
@@ -0,0 +1 @@
|
|||||||
|
2023-09-12
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
pre-release-commit-message = "chore({{crate_name}}): release {{version}}"
|
|
||||||
pro-release-commit-message = "chore({{crate_name}}): starting development cycle for {{next_version}}"
|
|
||||||
no-dev-version = true
|
|
||||||
@@ -18,6 +18,10 @@ and an own build machine.
|
|||||||
- `remote_tests_rust.sh` rsyncs to the build machine and runs
|
- `remote_tests_rust.sh` rsyncs to the build machine and runs
|
||||||
`run-rust-test.sh` remotely on the build machine.
|
`run-rust-test.sh` remotely on the build machine.
|
||||||
|
|
||||||
|
- `make-python-testenv.sh` creates or updates local python test development environment.
|
||||||
|
Reusing the same environment is faster than running `run-python-test.sh` which always
|
||||||
|
recreates environment from scratch and runs additional lints.
|
||||||
|
|
||||||
- `run-doxygen.sh` generates C-docs which are then uploaded to https://c.delta.chat/
|
- `run-doxygen.sh` generates C-docs which are then uploaded to https://c.delta.chat/
|
||||||
|
|
||||||
- `run_all.sh` builds Python wheels
|
- `run_all.sh` builds Python wheels
|
||||||
|
|||||||
@@ -12,10 +12,15 @@ where `secret.yml` contains the following secrets:
|
|||||||
```
|
```
|
||||||
c.delta.chat:
|
c.delta.chat:
|
||||||
private_key: |
|
private_key: |
|
||||||
-----BEGIN RSA PRIVATE KEY-----
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
...
|
...
|
||||||
-----END RSA PRIVATE KEY-----
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
devpi:
|
devpi:
|
||||||
login: dc
|
login: dc
|
||||||
password: ...
|
password: ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Secrets can be read from the password manager:
|
||||||
|
```
|
||||||
|
fly -t b1 set-pipeline -c docs_wheels.yml -p docs_wheels -l <(pass show delta/b1.delta.chat/secret.yml)
|
||||||
|
```
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user