Compare commits

...

56 Commits

Author SHA1 Message Date
Hocuri
8d4af85cb1 Filter unconventional commits, they cluttered the changelog too much 2023-05-06 19:22:10 +02:00
Hocuri
f637278cb1 Make PR references clickable 2023-05-06 18:44:35 +02:00
Hocuri
a202771d36 Improve template: Always include lines that start with 'BREAKING CHANGE' 2023-05-06 18:41:48 +02:00
Hocuri
833d30123c feat: Commmmmmit
BREAKING CHANGE: this breaks everything
2023-05-06 17:47:16 +02:00
Hocuri
b6c0b5b66e Rename mergable check to "Conventional Commits" 2023-05-06 15:30:52 +02:00
Hocuri
f30a1a3865 chore: Add git-cliff as a changelog generation tool
Changelogs will be generated using commit messages by
`scripts/release.py`.

Both the commit message and the PR title should follow https://conventionalcommits.org. E.g. start with 'feat:' (for Features / Changes), 'fix:' (for Fixes), 'api:' (for API-Changes), 'api!:' for breaking API-Changes) 'refactor:' (for Refactor), 'perf:' (for Performance), 'test:' (for Tests), 'style:' (for Styling), 'chore:' (for Miscellaneous Tasks)
2023-05-06 15:20:58 +02:00
dependabot[bot]
3a25d6a44e Merge pull request #4362 from deltachat/dependabot/cargo/human-panic-1.1.4 2023-05-06 11:35:30 +00:00
dependabot[bot]
0688895022 Merge pull request #4360 from deltachat/dependabot/cargo/axum-0.6.18 2023-05-05 21:35:26 +00:00
dependabot[bot]
ce0e5416e6 cargo: bump human-panic from 1.1.3 to 1.1.4
Bumps [human-panic](https://github.com/rust-cli/human-panic) from 1.1.3 to 1.1.4.
- [Release notes](https://github.com/rust-cli/human-panic/releases)
- [Changelog](https://github.com/rust-cli/human-panic/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/human-panic/compare/v1.1.3...v1.1.4)

---
updated-dependencies:
- dependency-name: human-panic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-05 21:24:47 +00:00
link2xt
7918a6801e scripts/deny.sh: update package cache before running cargo deny 2023-05-05 21:23:10 +00:00
link2xt
6af631e8df Add JSON-RPC API to get reactions 2023-05-05 20:49:55 +00:00
dependabot[bot]
625ecaa9b5 cargo: bump axum from 0.6.12 to 0.6.18
Bumps [axum](https://github.com/tokio-rs/axum) from 0.6.12 to 0.6.18.
- [Release notes](https://github.com/tokio-rs/axum/releases)
- [Changelog](https://github.com/tokio-rs/axum/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/axum/compare/axum-v0.6.12...axum-v0.6.18)

---
updated-dependencies:
- dependency-name: axum
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-05 20:31:03 +00:00
dependabot[bot]
24fe1b9c15 Merge pull request #4364 from deltachat/dependabot/cargo/tokio-1.28.0 2023-05-05 20:29:57 +00:00
link2xt
ba36d09c70 Make the bots automatically accept group chat contact requests 2023-05-05 16:37:34 +00:00
link2xt
f57be7187e Update deny.toml 2023-05-05 13:23:44 +00:00
dependabot[bot]
6a00338f79 cargo: bump tokio from 1.27.0 to 1.28.0
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.27.0 to 1.28.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.27.0...tokio-1.28.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-05 10:48:24 +00:00
dependabot[bot]
16906210e1 Merge pull request #4354 from deltachat/dependabot/cargo/tokio-util-0.7.8 2023-05-05 10:47:12 +00:00
dependabot[bot]
f9b4540387 Merge pull request #4358 from deltachat/dependabot/cargo/syn-2.0.15 2023-05-05 10:46:48 +00:00
dependabot[bot]
9755438d0d Merge pull request #4366 from deltachat/dependabot/cargo/serde-1.0.160 2023-05-05 10:44:58 +00:00
dependabot[bot]
fe9534ed7d cargo: bump serde from 1.0.159 to 1.0.160
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.159 to 1.0.160.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.159...v1.0.160)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-03 10:55:01 +00:00
dependabot[bot]
134c9ada68 cargo: bump syn from 2.0.13 to 2.0.15
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.13 to 2.0.15.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.13...2.0.15)

---
updated-dependencies:
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-03 10:54:57 +00:00
dependabot[bot]
a3240452ff cargo: bump tokio-util from 0.7.7 to 0.7.8
Bumps [tokio-util](https://github.com/tokio-rs/tokio) from 0.7.7 to 0.7.8.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-util-0.7.7...tokio-util-0.7.8)

---
updated-dependencies:
- dependency-name: tokio-util
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-03 10:54:42 +00:00
dependabot[bot]
84beb6647d Merge pull request #4357 from deltachat/dependabot/cargo/serde_json-1.0.96 2023-05-03 10:53:51 +00:00
dependabot[bot]
ecf7e2d909 Merge pull request #4359 from deltachat/dependabot/cargo/libc-0.2.142 2023-05-03 10:10:06 +00:00
dependabot[bot]
fcfcf4bbf3 Merge pull request #4365 from deltachat/dependabot/cargo/quick-xml-0.28.2 2023-05-03 10:09:37 +00:00
dependabot[bot]
c62b6d77b7 Merge pull request #4368 from deltachat/dependabot/cargo/anyhow-1.0.71 2023-05-03 10:09:14 +00:00
dependabot[bot]
7e51b9686f cargo: bump quick-xml from 0.28.1 to 0.28.2
Bumps [quick-xml](https://github.com/tafia/quick-xml) from 0.28.1 to 0.28.2.
- [Release notes](https://github.com/tafia/quick-xml/releases)
- [Changelog](https://github.com/tafia/quick-xml/blob/master/Changelog.md)
- [Commits](https://github.com/tafia/quick-xml/compare/v0.28.1...v0.28.2)

---
updated-dependencies:
- dependency-name: quick-xml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-03 01:54:26 +00:00
dependabot[bot]
542ec4cac4 cargo: bump anyhow from 1.0.70 to 1.0.71
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.70 to 1.0.71.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.70...1.0.71)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-03 01:54:07 +00:00
dependabot[bot]
64b25d9ec0 cargo: bump serde_json from 1.0.95 to 1.0.96
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.95 to 1.0.96.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.95...v1.0.96)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-03 01:54:04 +00:00
dependabot[bot]
f91b6fbdf0 Merge pull request #4363 from deltachat/dependabot/cargo/reqwest-0.11.17 2023-05-03 01:53:12 +00:00
dependabot[bot]
41445a506e Merge pull request #4369 from deltachat/dependabot/cargo/futures-lite-1.13.0 2023-05-03 01:52:22 +00:00
dependabot[bot]
798db9d019 Merge pull request #4367 from deltachat/dependabot/cargo/tokio-stream-0.1.14 2023-05-02 23:48:34 +00:00
dependabot[bot]
2e860c32ab Merge pull request #4355 from deltachat/dependabot/cargo/uuid-1.3.2 2023-05-02 23:46:59 +00:00
dependabot[bot]
0e1faed6e5 cargo: bump futures-lite from 1.12.0 to 1.13.0
Bumps [futures-lite](https://github.com/smol-rs/futures-lite) from 1.12.0 to 1.13.0.
- [Release notes](https://github.com/smol-rs/futures-lite/releases)
- [Changelog](https://github.com/smol-rs/futures-lite/blob/master/CHANGELOG.md)
- [Commits](https://github.com/smol-rs/futures-lite/compare/v1.12.0...v1.13.0)

---
updated-dependencies:
- dependency-name: futures-lite
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 22:03:16 +00:00
dependabot[bot]
f5de3be977 cargo: bump tokio-stream from 0.1.12 to 0.1.14
Bumps [tokio-stream](https://github.com/tokio-rs/tokio) from 0.1.12 to 0.1.14.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Changelog](https://github.com/tokio-rs/tokio/blob/tokio-0.1.14/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-stream-0.1.12...tokio-0.1.14)

---
updated-dependencies:
- dependency-name: tokio-stream
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 22:02:36 +00:00
dependabot[bot]
4b0a30eb66 cargo: bump reqwest from 0.11.16 to 0.11.17
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.16 to 0.11.17.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.16...v0.11.17)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 22:00:52 +00:00
dependabot[bot]
7710467571 cargo: bump libc from 0.2.140 to 0.2.142
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.140 to 0.2.142.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.140...0.2.142)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 21:59:05 +00:00
dependabot[bot]
1687794b81 cargo: bump uuid from 1.3.0 to 1.3.2
Bumps [uuid](https://github.com/uuid-rs/uuid) from 1.3.0 to 1.3.2.
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.3.0...1.3.2)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 21:57:42 +00:00
link2xt
158541f05c Document deltachat_rpc_client installation 2023-04-29 13:53:57 +00:00
link2xt
eb28899cd0 Add 1.112.8 link to the changelog 2023-04-27 10:28:38 +00:00
link2xt
432046225a Fix 1.114.0 link in the changelog 2023-04-27 10:27:39 +00:00
Simon Laux
bf958ce6c1 More context for chat and chatlist errors (#3932) 2023-04-25 17:20:26 -04:00
Simon Laux
ea8ee4e67d jsonrpc: Remove message id from chatlist item (#3071)
Why? because desktop currently fetches the chatlist multiple times, even though it just needs the
chatlistitem for one chat.

Note: @r10s was worried before that exposing the method to get a single updated chatlistitem could
lead to race conditions where the chatlist item is newer than the chatlist order. But I don't think
this will change anything for desktop besides making it a little faster (because currently desktop
fetches the whole chatlist instead of just the entry it needs when an entry updates).
2023-04-25 17:20:26 -04:00
link2xt
edfdbbdc90 Release 1.114.0 2023-04-24 22:31:17 +00:00
Simon Laux
1b8bfef441 jsonrpc: make event loop in ts client optional (#4347)
* jsonrpc: make event loop in ts client optional

* jsonrpc: ts: fix tests and make specifying `startEventLoop` mandatory

* add changelog entry

* Update CHANGELOG.md
2023-04-24 23:18:55 +02:00
link2xt
514074de8b JSON-RPC: retrieve events via long polling
This way is more compatible to JSON-RPC libraries
that do not support receiving notifications from the server
and allows describing event types in the OpenRPC specification.

Event thread converting events to notifications in the FFI
is removed, so it is now possible to construct a dc_jsonrpc_instance_t
while still retrieving events via dc_event_emitter_t.
2023-04-22 16:42:59 +00:00
link2xt
e7aab5c67c Remove newline from release-date.in 2023-04-20 15:40:09 +00:00
link2xt
40484e875e deltachat-ffi: update read_url_blob for new .log_err() 2023-04-20 15:23:20 +00:00
link2xt
fc215ceb63 Merge v1.112.8 2023-04-20 15:09:55 +00:00
link2xt
2701c135db Prepare 1.112.8 2023-04-20 15:05:07 +00:00
link2xt
3a8df3e673 Do not use println! in JSON-RPC
This may break the output of deltachat-rpc-server
2023-04-20 14:15:18 +00:00
link2xt
fa95b269a5 Backport new set_core_version.py 2023-04-20 14:03:47 +00:00
B. Petersen
0e9f8c4726 describe dc_http_response_t, needed for doxygen's @memberof 2023-04-20 14:01:14 +00:00
link2xt
a8d4cbd5c1 Add C API to get HTTP responses 2023-04-20 14:01:14 +00:00
link2xt
f68a2fc387 JSON-RPC: return mimetype and encoding for HTTP blobs 2023-04-20 14:00:16 +00:00
link2xt
37503dd3e8 JSON-RPC: add get_http_blob API 2023-04-18 13:50:50 +00:00
50 changed files with 882 additions and 391 deletions

25
.github/mergeable.yml vendored
View File

@@ -1,26 +1,15 @@
version: 2
mergeable:
- when: pull_request.*
name: "Changelog check"
name: "Conventional Commits"
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"]
- do: title
begins_with:
match: ['feat', 'fix', 'api', 'refactor', 'perf', 'test', 'style', 'chore']
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."
title: PR title should follow conventional commits
summary: "PR title should follow https://conventionalcommits.org. E.g. start with 'feat:' (for Features / Changes), 'fix:' (for Fixes), 'api:' (for API-Changes), 'api!:' (for breaking API-Changes) 'refactor:' (for Refactor), 'perf:' (for Performance), 'test:' (for Tests), 'style:' (for Styling), 'chore:' (for Miscellaneous Tasks)"

View File

@@ -1,5 +1,29 @@
# Changelog
## Unreleased
### Changes
- BREAKING: jsonrpc:
- `get_chatlist_items_by_entries` now takes only chatids instead of `ChatListEntries`
- `get_chatlist_entries` now returns `Vec<u32>` of chatids instead of `ChatListEntries`
- JSON-RPC: add API to get reactions outside the message snapshot
### Fixes
- Make the bots automatically accept group chat contact requests. #4377
## [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
@@ -32,6 +56,12 @@
- 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
@@ -2411,4 +2441,6 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
[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.113.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.7...v1.113.0
[1.112.8]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.7...v1.112.8
[1.113.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.8...v1.113.0
[1.114.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.113.0...v1.114.0

173
Cargo.lock generated
View File

@@ -114,9 +114,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.70"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
dependencies = [
"backtrace",
]
@@ -270,7 +270,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.13",
"syn 2.0.15",
]
[[package]]
@@ -306,9 +306,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
version = "0.6.12"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f8ccfd9221ee7d1f3d4b33e1f8319b3a81ed8f61f2ea40b37b859794b4491"
checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39"
dependencies = [
"async-trait",
"axum-core",
@@ -341,9 +341,9 @@ dependencies = [
[[package]]
name = "axum-core"
version = "0.3.3"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2f958c80c248b34b9a877a643811be8dbca03ca5ba827f2b63baf3a81e5fc4e"
checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
dependencies = [
"async-trait",
"bytes",
@@ -1144,7 +1144,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.113.0"
version = "1.114.0"
dependencies = [
"ansi_term",
"anyhow",
@@ -1175,6 +1175,7 @@ dependencies = [
"libc",
"log",
"mailparse",
"mime",
"num-derive",
"num-traits",
"num_cpus",
@@ -1218,7 +1219,7 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc"
version = "1.113.0"
version = "1.114.0"
dependencies = [
"anyhow",
"async-channel",
@@ -1241,7 +1242,7 @@ dependencies = [
[[package]]
name = "deltachat-repl"
version = "1.113.0"
version = "1.114.0"
dependencies = [
"ansi_term",
"anyhow",
@@ -1256,7 +1257,7 @@ dependencies = [
[[package]]
name = "deltachat-rpc-server"
version = "1.113.0"
version = "1.114.0"
dependencies = [
"anyhow",
"deltachat",
@@ -1276,12 +1277,12 @@ name = "deltachat_derive"
version = "2.0.0"
dependencies = [
"quote",
"syn 2.0.13",
"syn 2.0.15",
]
[[package]]
name = "deltachat_ffi"
version = "1.113.0"
version = "1.114.0"
dependencies = [
"anyhow",
"deltachat",
@@ -2035,9 +2036,9 @@ checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]]
name = "futures-lite"
version = "1.12.0"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48"
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
dependencies = [
"fastrand",
"futures-core",
@@ -2056,7 +2057,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.13",
"syn 2.0.15",
]
[[package]]
@@ -2306,9 +2307,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "human-panic"
version = "1.1.3"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6557b29bbdc9d6c7a5cdbe2962e78eaf48115e8d55b0b62282956981c1f605"
checksum = "c16465f6227e18e5a64eba488245d7b2974d4db0c4404ca5a69b550defa18d0a"
dependencies = [
"backtrace",
"os_info",
@@ -2659,9 +2660,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.140"
version = "0.2.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "libm"
@@ -2806,9 +2807,9 @@ dependencies = [
[[package]]
name = "mime"
version = "0.3.16"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minimal-lexical"
@@ -3644,9 +3645,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.28.1"
version = "0.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5c1a97b1bc42b1d550bfb48d4262153fe400a12bab1511821736f7eac76d7e2"
checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1"
dependencies = [
"memchr",
]
@@ -3899,9 +3900,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "reqwest"
version = "0.11.16"
version = "0.11.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254"
checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91"
dependencies = [
"base64 0.21.0",
"bytes",
@@ -4279,9 +4280,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.159"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
dependencies = [
"serde_derive",
]
@@ -4306,20 +4307,20 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.159"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585"
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.13",
"syn 2.0.15",
]
[[package]]
name = "serde_json"
version = "1.0.95"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744"
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
dependencies = [
"itoa",
"ryu",
@@ -4619,9 +4620,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.13"
version = "2.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec"
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
dependencies = [
"proc-macro2",
"quote",
@@ -4751,7 +4752,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.13",
"syn 2.0.15",
]
[[package]]
@@ -4829,9 +4830,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.27.0"
version = "1.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001"
checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f"
dependencies = [
"autocfg",
"bytes",
@@ -4843,7 +4844,7 @@ dependencies = [
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.45.0",
"windows-sys 0.48.0",
]
[[package]]
@@ -4858,13 +4859,13 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "2.0.0"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce"
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.13",
"syn 2.0.15",
]
[[package]]
@@ -4894,9 +4895,9 @@ dependencies = [
[[package]]
name = "tokio-stream"
version = "0.1.12"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313"
checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
dependencies = [
"futures-core",
"pin-project-lite",
@@ -4932,9 +4933,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.7"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
dependencies = [
"bytes",
"futures-core",
@@ -5274,9 +5275,9 @@ checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
[[package]]
name = "uuid"
version = "1.3.0"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
checksum = "4dad5567ad0cf5b760e5665964bec1b47dfd077ba8a2544b513f3556d3d239a2"
dependencies = [
"getrandom 0.2.8",
"serde",
@@ -5502,12 +5503,12 @@ version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_gnullvm 0.42.1",
"windows_aarch64_msvc 0.42.1",
"windows_i686_gnu 0.42.1",
"windows_i686_msvc 0.42.1",
"windows_x86_64_gnu 0.42.1",
"windows_x86_64_gnullvm",
"windows_x86_64_gnullvm 0.42.1",
"windows_x86_64_msvc 0.42.1",
]
@@ -5517,7 +5518,16 @@ version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets",
"windows-targets 0.42.1",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.0",
]
[[package]]
@@ -5526,21 +5536,42 @@ version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_gnullvm 0.42.1",
"windows_aarch64_msvc 0.42.1",
"windows_i686_gnu 0.42.1",
"windows_i686_msvc 0.42.1",
"windows_x86_64_gnu 0.42.1",
"windows_x86_64_gnullvm",
"windows_x86_64_gnullvm 0.42.1",
"windows_x86_64_msvc 0.42.1",
]
[[package]]
name = "windows-targets"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
"windows_aarch64_gnullvm 0.48.0",
"windows_aarch64_msvc 0.48.0",
"windows_i686_gnu 0.48.0",
"windows_i686_msvc 0.48.0",
"windows_x86_64_gnu 0.48.0",
"windows_x86_64_gnullvm 0.48.0",
"windows_x86_64_msvc 0.48.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.32.0"
@@ -5553,6 +5584,12 @@ version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.32.0"
@@ -5565,6 +5602,12 @@ version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.32.0"
@@ -5577,6 +5620,12 @@ version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.32.0"
@@ -5589,12 +5638,24 @@ version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.32.0"
@@ -5607,6 +5668,12 @@ version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "winnow"
version = "0.4.1"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.113.0"
version = "1.114.0"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.65"
@@ -48,7 +48,7 @@ encoded-words = { git = "https://github.com/async-email/encoded-words", branch =
escaper = "0.1"
fast-socks5 = "0.8"
futures = "0.3"
futures-lite = "1.12.0"
futures-lite = "1.13.0"
hex = "0.4.0"
humansize = "2"
image = { version = "0.24.6", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
@@ -57,6 +57,7 @@ kamadak-exif = "0.5"
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
libc = "0.2"
mailparse = "0.14"
mime = "0.3.17"
num_cpus = "1.15"
num-derive = "0.3"
num-traits = "0.2"
@@ -69,7 +70,7 @@ qrcodegen = "1.7.0"
quick-xml = "0.28"
rand = "0.8"
regex = "1.7"
reqwest = { version = "0.11.16", features = ["json"] }
reqwest = { version = "0.11.17", features = ["json"] }
rusqlite = { version = "0.29", features = ["sqlcipher"] }
rust-hsluv = "0.1"
sanitize-filename = "0.4"
@@ -85,9 +86,9 @@ textwrap = "0.16.0"
thiserror = "1"
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
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-util = "0.7.7"
tokio-util = "0.7.8"
toml = "0.7"
trust-dns-resolver = "0.22"
url = "2"
@@ -96,7 +97,7 @@ uuid = { version = "1", features = ["serde", "v4"] }
[dev-dependencies]
ansi_term = "0.12.0"
criterion = { version = "0.4.0", features = ["async_tokio"] }
futures-lite = "1.12"
futures-lite = "1.13"
log = "0.4"
pretty_env_logger = "0.4"
proptest = { version = "1", default-features = false, features = ["std"] }

75
cliff.toml Normal file
View File

@@ -0,0 +1,75 @@
# configuration file for git-cliff
# see https://github.com/orhun/git-cliff#configuration-file
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# 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/PR/${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"},
# { 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 %}{{ commit.message | upper_first }}\
{% for footer in commit.footers %}{% if 'BREAKING CHANGE' in footer.token %}
{% raw %} {% endraw %}- {{ footer.value }}\
{% endif %}{% endfor %}\
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
footer = """
<!-- generated by git-cliff -->
"""

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.113.0"
version = "1.114.0"
description = "Deltachat FFI"
edition = "2018"
readme = "README.md"

View File

@@ -25,6 +25,7 @@ typedef struct _dc_event dc_event_t;
typedef struct _dc_event_emitter dc_event_emitter_t;
typedef struct _dc_jsonrpc_instance dc_jsonrpc_instance_t;
typedef struct _dc_backup_provider dc_backup_provider_t;
typedef struct _dc_http_response dc_http_response_t;
// Alias for backwards compatibility, use dc_event_emitter_t instead.
typedef struct _dc_event_emitter dc_accounts_event_emitter_t;
@@ -5127,6 +5128,72 @@ int dc_provider_get_status (const dc_provider_t* prov
void dc_provider_unref (dc_provider_t* provider);
/**
* Return an HTTP(S) GET response.
* This function can be used to download remote content for HTML emails.
*
* @memberof dc_context_t
* @param context The context object to take proxy settings from.
* @param url HTTP or HTTPS URL.
* @return The response must be released using dc_http_response_unref() after usage.
* NULL is returned on errors.
*/
dc_http_response_t* dc_get_http_response (const dc_context_t* context, const char* url);
/**
* @class dc_http_response_t
*
* An object containing an HTTP(S) GET response.
* Created by dc_get_http_response().
*/
/**
* Returns HTTP response MIME type as a string, e.g. "text/plain" or "text/html".
*
* @memberof dc_http_response_t
* @param response HTTP response as returned by dc_get_http_response().
* @return The string which must be released using dc_str_unref() after usage. May be NULL.
*/
char* dc_http_response_get_mimetype (const dc_http_response_t* response);
/**
* Returns HTTP response encoding, e.g. "utf-8".
*
* @memberof dc_http_response_t
* @param response HTTP response as returned by dc_get_http_response().
* @return The string which must be released using dc_str_unref() after usage. May be NULL.
*/
char* dc_http_response_get_encoding (const dc_http_response_t* response);
/**
* Returns HTTP response contents.
*
* @memberof dc_http_response_t
* @param response HTTP response as returned by dc_get_http_response().
* @return The blob which must be released using dc_str_unref() after usage. NULL is never returned.
*/
uint8_t* dc_http_response_get_blob (const dc_http_response_t* response);
/**
* Returns HTTP response content size.
*
* @memberof dc_http_response_t
* @param response HTTP response as returned by dc_get_http_response().
* @return The blob size.
*/
size_t dc_http_response_get_size (const dc_http_response_t* response);
/**
* Free an HTTP response object.
*
* @memberof dc_http_response_t
* @param response HTTP response as returned by dc_get_http_response().
*/
void dc_http_response_unref (const dc_http_response_t* response);
/**
* @class dc_lot_t
*
@@ -5604,7 +5671,6 @@ void dc_reactions_unref (dc_reactions_t* reactions);
*/
/**
* @class dc_jsonrpc_instance_t
*

View File

@@ -31,6 +31,7 @@ use deltachat::ephemeral::Timer as EphemeralTimer;
use deltachat::imex::BackupProvider;
use deltachat::key::DcKey;
use deltachat::message::MsgId;
use deltachat::net::read_url_blob;
use deltachat::qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg};
use deltachat::reaction::{get_msg_reactions, send_reaction, Reactions};
use deltachat::stock_str::StockMessage;
@@ -4572,6 +4573,96 @@ pub unsafe extern "C" fn dc_provider_unref(provider: *mut dc_provider_t) {
// 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
/// Reader-writer lock wrapper for accounts manager to guarantee thread safety when using
@@ -4876,7 +4967,6 @@ pub unsafe extern "C" fn dc_accounts_get_event_emitter(
#[cfg(feature = "jsonrpc")]
mod jsonrpc {
use deltachat_jsonrpc::api::CommandApi;
use deltachat_jsonrpc::events::event_to_json_rpc_notification;
use deltachat_jsonrpc::yerpc::{OutReceiver, RpcClient, RpcSession};
use super::*;
@@ -4884,7 +4974,6 @@ mod jsonrpc {
pub struct dc_jsonrpc_instance_t {
receiver: OutReceiver,
handle: RpcSession<CommandApi>,
event_thread: JoinHandle<Result<(), anyhow::Error>>,
}
#[no_mangle]
@@ -4897,28 +4986,12 @@ mod jsonrpc {
}
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 (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 {
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,
};
let instance = dc_jsonrpc_instance_t { receiver, handle };
Box::into_raw(Box::new(instance))
}
@@ -4929,7 +5002,6 @@ mod jsonrpc {
eprintln!("ignoring careless call to dc_jsonrpc_unref()");
return;
}
(*jsonrpc_instance).event_thread.abort();
drop(Box::from_raw(jsonrpc_instance));
}

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc"
version = "1.113.0"
version = "1.114.0"
description = "DeltaChat JSON-RPC API"
edition = "2021"
default-run = "deltachat-jsonrpc-server"
@@ -20,20 +20,20 @@ tempfile = "3.3.0"
log = "0.4"
async-channel = { version = "1.8.0" }
futures = { version = "0.3.28" }
serde_json = "1.0.95"
serde_json = "1.0.96"
yerpc = { version = "0.4.3", features = ["anyhow_expose"] }
typescript-type-def = { version = "0.5.5", features = ["json_value"] }
tokio = { version = "1.27.0" }
tokio = { version = "1.28.0" }
sanitize-filename = "0.4"
walkdir = "2.3.3"
base64 = "0.21"
# optional dependencies
axum = { version = "0.6.12", optional = true, features = ["ws"] }
axum = { version = "0.6.18", optional = true, features = ["ws"] }
env_logger = { version = "0.10.0", optional = true }
[dev-dependencies]
tokio = { version = "1.27.0", features = ["full", "rt-multi-thread"] }
tokio = { version = "1.28.0", features = ["full", "rt-multi-thread"] }
[features]

View File

@@ -1,19 +1,28 @@
use deltachat::{Event, EventType};
use deltachat::{Event as CoreEvent, EventType as CoreEventType};
use serde::Serialize;
use serde_json::{json, Value};
use typescript_type_def::TypeDef;
pub fn event_to_json_rpc_notification(event: Event) -> Value {
let id: JSONRPCEventType = event.typ.into();
json!({
"event": id,
"contextId": event.id,
})
#[derive(Serialize, TypeDef)]
pub struct Event {
/// Event payload.
event: EventType,
/// Account ID.
context_id: u32,
}
impl From<CoreEvent> for Event {
fn from(event: CoreEvent) -> Self {
Event {
event: event.typ.into(),
context_id: event.id,
}
}
}
#[derive(Serialize, TypeDef)]
#[serde(tag = "type", rename = "Event")]
pub enum JSONRPCEventType {
#[serde(tag = "type")]
pub enum EventType {
/// The library-user may write an informational string to the log.
///
/// This event should *not* be reported to the end-user using a popup or something like
@@ -286,27 +295,27 @@ pub enum JSONRPCEventType {
},
}
impl From<EventType> for JSONRPCEventType {
fn from(event: EventType) -> Self {
use JSONRPCEventType::*;
impl From<CoreEventType> for EventType {
fn from(event: CoreEventType) -> Self {
use EventType::*;
match event {
EventType::Info(msg) => Info { msg },
EventType::SmtpConnected(msg) => SmtpConnected { msg },
EventType::ImapConnected(msg) => ImapConnected { msg },
EventType::SmtpMessageSent(msg) => SmtpMessageSent { msg },
EventType::ImapMessageDeleted(msg) => ImapMessageDeleted { msg },
EventType::ImapMessageMoved(msg) => ImapMessageMoved { msg },
EventType::ImapInboxIdle => ImapInboxIdle,
EventType::NewBlobFile(file) => NewBlobFile { file },
EventType::DeletedBlobFile(file) => DeletedBlobFile { file },
EventType::Warning(msg) => Warning { msg },
EventType::Error(msg) => Error { msg },
EventType::ErrorSelfNotInGroup(msg) => ErrorSelfNotInGroup { msg },
EventType::MsgsChanged { chat_id, msg_id } => MsgsChanged {
CoreEventType::Info(msg) => Info { msg },
CoreEventType::SmtpConnected(msg) => SmtpConnected { msg },
CoreEventType::ImapConnected(msg) => ImapConnected { msg },
CoreEventType::SmtpMessageSent(msg) => SmtpMessageSent { msg },
CoreEventType::ImapMessageDeleted(msg) => ImapMessageDeleted { msg },
CoreEventType::ImapMessageMoved(msg) => ImapMessageMoved { msg },
CoreEventType::ImapInboxIdle => ImapInboxIdle,
CoreEventType::NewBlobFile(file) => NewBlobFile { file },
CoreEventType::DeletedBlobFile(file) => DeletedBlobFile { file },
CoreEventType::Warning(msg) => Warning { msg },
CoreEventType::Error(msg) => Error { msg },
CoreEventType::ErrorSelfNotInGroup(msg) => ErrorSelfNotInGroup { msg },
CoreEventType::MsgsChanged { chat_id, msg_id } => MsgsChanged {
chat_id: chat_id.to_u32(),
msg_id: msg_id.to_u32(),
},
EventType::ReactionsChanged {
CoreEventType::ReactionsChanged {
chat_id,
msg_id,
contact_id,
@@ -315,92 +324,76 @@ impl From<EventType> for JSONRPCEventType {
msg_id: msg_id.to_u32(),
contact_id: contact_id.to_u32(),
},
EventType::IncomingMsg { chat_id, msg_id } => IncomingMsg {
CoreEventType::IncomingMsg { chat_id, msg_id } => IncomingMsg {
chat_id: chat_id.to_u32(),
msg_id: msg_id.to_u32(),
},
EventType::IncomingMsgBunch { msg_ids } => IncomingMsgBunch {
CoreEventType::IncomingMsgBunch { msg_ids } => IncomingMsgBunch {
msg_ids: msg_ids.into_iter().map(|id| id.to_u32()).collect(),
},
EventType::MsgsNoticed(chat_id) => MsgsNoticed {
CoreEventType::MsgsNoticed(chat_id) => MsgsNoticed {
chat_id: chat_id.to_u32(),
},
EventType::MsgDelivered { chat_id, msg_id } => MsgDelivered {
CoreEventType::MsgDelivered { chat_id, msg_id } => MsgDelivered {
chat_id: chat_id.to_u32(),
msg_id: msg_id.to_u32(),
},
EventType::MsgFailed { chat_id, msg_id } => MsgFailed {
CoreEventType::MsgFailed { chat_id, msg_id } => MsgFailed {
chat_id: chat_id.to_u32(),
msg_id: msg_id.to_u32(),
},
EventType::MsgRead { chat_id, msg_id } => MsgRead {
CoreEventType::MsgRead { chat_id, msg_id } => MsgRead {
chat_id: chat_id.to_u32(),
msg_id: msg_id.to_u32(),
},
EventType::ChatModified(chat_id) => ChatModified {
CoreEventType::ChatModified(chat_id) => ChatModified {
chat_id: chat_id.to_u32(),
},
EventType::ChatEphemeralTimerModified { chat_id, timer } => {
CoreEventType::ChatEphemeralTimerModified { chat_id, timer } => {
ChatEphemeralTimerModified {
chat_id: chat_id.to_u32(),
timer: timer.to_u32(),
}
}
EventType::ContactsChanged(contact) => ContactsChanged {
CoreEventType::ContactsChanged(contact) => ContactsChanged {
contact_id: contact.map(|c| c.to_u32()),
},
EventType::LocationChanged(contact) => LocationChanged {
CoreEventType::LocationChanged(contact) => LocationChanged {
contact_id: contact.map(|c| c.to_u32()),
},
EventType::ConfigureProgress { progress, comment } => {
CoreEventType::ConfigureProgress { progress, comment } => {
ConfigureProgress { progress, comment }
}
EventType::ImexProgress(progress) => ImexProgress { progress },
EventType::ImexFileWritten(path) => ImexFileWritten {
CoreEventType::ImexProgress(progress) => ImexProgress { progress },
CoreEventType::ImexFileWritten(path) => ImexFileWritten {
path: path.to_str().unwrap_or_default().to_owned(),
},
EventType::SecurejoinInviterProgress {
CoreEventType::SecurejoinInviterProgress {
contact_id,
progress,
} => SecurejoinInviterProgress {
contact_id: contact_id.to_u32(),
progress,
},
EventType::SecurejoinJoinerProgress {
CoreEventType::SecurejoinJoinerProgress {
contact_id,
progress,
} => SecurejoinJoinerProgress {
contact_id: contact_id.to_u32(),
progress,
},
EventType::ConnectivityChanged => ConnectivityChanged,
EventType::SelfavatarChanged => SelfavatarChanged,
EventType::WebxdcStatusUpdate {
CoreEventType::ConnectivityChanged => ConnectivityChanged,
CoreEventType::SelfavatarChanged => SelfavatarChanged,
CoreEventType::WebxdcStatusUpdate {
msg_id,
status_update_serial,
} => WebxdcStatusUpdate {
msg_id: msg_id.to_u32(),
status_update_serial: status_update_serial.to_u32(),
},
EventType::WebxdcInstanceDeleted { msg_id } => WebxdcInstanceDeleted {
CoreEventType::WebxdcInstanceDeleted { msg_id } => WebxdcInstanceDeleted {
msg_id: msg_id.to_u32(),
},
}
}
}
#[cfg(test)]
#[test]
fn generate_events_ts_types_definition() {
let events = {
let mut buf = Vec::new();
let options = typescript_type_def::DefinitionFileOptions {
root_namespace: None,
..typescript_type_def::DefinitionFileOptions::default()
};
typescript_type_def::write_definition_file::<_, JSONRPCEventType>(&mut buf, options)
.unwrap();
String::from_utf8(buf).unwrap()
};
std::fs::write("typescript/generated/events.ts", events).unwrap();
}

View File

@@ -24,7 +24,7 @@ use deltachat::{
provider::get_provider_info,
qr,
qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg},
reaction::send_reaction,
reaction::{get_msg_reactions, send_reaction},
securejoin,
stock_str::StockMessage,
webxdc::StatusUpdateSerial,
@@ -41,13 +41,15 @@ pub mod types;
use num_traits::FromPrimitive;
use types::account::Account;
use types::chat::FullChat;
use types::chat_list::ChatListEntry;
use types::contact::ContactObject;
use types::http::HttpResponse;
use types::message::MessageData;
use types::message::MessageObject;
use types::provider_info::ProviderInfo;
use types::reactions::JSONRPCReactions;
use types::webxdc::WebxdcMessageInfo;
use self::events::Event;
use self::types::message::MessageLoadResult;
use self::types::{
chat::{BasicChat, JSONRPCChatVisibility, MuteDuration},
@@ -164,6 +166,16 @@ impl CommandApi {
get_info()
}
/// Get the next event.
async fn get_next_event(&self) -> Result<Event> {
let event_emitter = self.accounts.read().await.get_event_emitter();
event_emitter
.recv()
.await
.map(|event| event.into())
.context("event channel is closed")
}
// ---------------------------------------------
// Account Management
// ---------------------------------------------
@@ -205,8 +217,6 @@ impl CommandApi {
let context_option = self.accounts.read().await.get_account(id);
if let Some(ctx) = context_option {
accounts.push(Account::from_context(&ctx, id).await?)
} else {
println!("account with id {id} doesn't exist anymore");
}
}
Ok(accounts)
@@ -539,7 +549,7 @@ impl CommandApi {
list_flags: Option<u32>,
query_string: Option<String>,
query_contact_id: Option<u32>,
) -> Result<Vec<ChatListEntry>> {
) -> Result<Vec<u32>> {
let ctx = self.get_context(account_id).await?;
let list = Chatlist::try_load(
&ctx,
@@ -548,12 +558,9 @@ impl CommandApi {
query_contact_id.map(ContactId::new),
)
.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() {
l.push(ChatListEntry(
list.get_chat_id(i)?.to_u32(),
list.get_msg_id(i)?.unwrap_or_default().to_u32(),
));
l.push(list.get_chat_id(i)?.to_u32());
}
Ok(l)
}
@@ -561,19 +568,18 @@ impl CommandApi {
async fn get_chatlist_items_by_entries(
&self,
account_id: u32,
entries: Vec<ChatListEntry>,
entries: Vec<u32>,
) -> Result<HashMap<u32, ChatListItemFetchResult>> {
// todo custom json deserializer for ChatListEntry?
let ctx = self.get_context(account_id).await?;
let mut result: HashMap<u32, ChatListItemFetchResult> =
HashMap::with_capacity(entries.len());
for entry in entries.iter() {
for &entry in entries.iter() {
result.insert(
entry.0,
entry,
match get_chat_list_item_by_id(&ctx, entry).await {
Ok(res) => res,
Err(err) => ChatListItemFetchResult::Error {
id: entry.0,
id: entry,
error: format!("{err:#}"),
},
},
@@ -1658,6 +1664,15 @@ impl CommandApi {
Ok(general_purpose::STANDARD_NO_PAD.encode(blob))
}
/// Makes an HTTP GET request and returns a response.
///
/// `url` is the HTTP or HTTPS URL.
async fn get_http_response(&self, account_id: u32, url: String) -> Result<HttpResponse> {
let ctx = self.get_context(account_id).await?;
let response = deltachat::net::read_url_blob(&ctx, &url).await?.into();
Ok(response)
}
/// Forward messages to another chat.
///
/// All types of messages can be forwarded,
@@ -1707,6 +1722,21 @@ impl CommandApi {
Ok(message_id.to_u32())
}
/// Returns reactions to the message.
async fn get_message_reactions(
&self,
account_id: u32,
message_id: u32,
) -> Result<Option<JSONRPCReactions>> {
let ctx = self.get_context(account_id).await?;
let reactions = get_msg_reactions(&ctx, MsgId::new(message_id)).await?;
if reactions.is_empty() {
Ok(None)
} else {
Ok(Some(reactions.into()))
}
}
async fn send_msg(&self, account_id: u32, chat_id: u32, data: MessageData) -> Result<u32> {
let ctx = self.get_context(account_id).await?;
let mut message = Message::new(if let Some(viewtype) = data.viewtype {

View File

@@ -1,6 +1,6 @@
use std::time::{Duration, SystemTime};
use anyhow::{anyhow, bail, Result};
use anyhow::{anyhow, bail, Context as _, Result};
use deltachat::chat::{self, get_chat_contacts, ChatVisibility};
use deltachat::chat::{Chat, ChatId};
use deltachat::constants::Chattype;
@@ -53,7 +53,9 @@ impl FullChat {
contacts.push(
ContactObject::try_from_dc_contact(
context,
Contact::load_from_db(context, *contact_id).await?,
Contact::load_from_db(context, *contact_id)
.await
.context("failed to load contact")?,
)
.await?,
)
@@ -73,7 +75,8 @@ impl FullChat {
let was_seen_recently = if chat.get_type() == Chattype::Single {
match contact_ids.get(0) {
Some(contact) => Contact::load_from_db(context, *contact)
.await?
.await
.context("failed to load contact for was_seen_recently")?
.was_seen_recently(),
None => false,
}

View File

@@ -1,23 +1,18 @@
use anyhow::Result;
use anyhow::{Context, Result};
use deltachat::chat::{Chat, ChatId};
use deltachat::chatlist::get_last_message_for_chat;
use deltachat::constants::*;
use deltachat::contact::{Contact, ContactId};
use deltachat::{
chat::{get_chat_contacts, ChatVisibility},
chatlist::Chatlist,
};
use deltachat::{
chat::{Chat, ChatId},
message::MsgId,
};
use num_traits::cast::ToPrimitive;
use serde::{Deserialize, Serialize};
use serde::Serialize;
use typescript_type_def::TypeDef;
use super::color_int_to_hex_string;
#[derive(Deserialize, Serialize, TypeDef)]
pub struct ChatListEntry(pub u32, pub u32);
#[derive(Serialize, TypeDef)]
#[serde(tag = "type")]
pub enum ChatListItemFetchResult {
@@ -56,14 +51,9 @@ pub enum ChatListItemFetchResult {
pub(crate) async fn get_chat_list_item_by_id(
ctx: &deltachat::context::Context,
entry: &ChatListEntry,
entry: u32,
) -> Result<ChatListItemFetchResult> {
let chat_id = ChatId::new(entry.0);
let last_msgid = match entry.1 {
0 => None,
_ => Some(MsgId::new(entry.1)),
};
let chat_id = ChatId::new(entry);
let fresh_message_counter = chat_id.get_fresh_msg_cnt(ctx).await?;
if chat_id.is_archived_link() {
@@ -72,8 +62,12 @@ pub(crate) async fn get_chat_list_item_by_id(
});
}
let chat = Chat::load_from_db(ctx, chat_id).await?;
let summary = Chatlist::get_summary2(ctx, chat_id, last_msgid, Some(&chat)).await?;
let last_msgid = get_last_message_for_chat(ctx, chat_id).await?;
let chat = Chat::load_from_db(ctx, chat_id).await.context("chat")?;
let summary = Chatlist::get_summary2(ctx, chat_id, last_msgid, Some(&chat))
.await
.context("summary")?;
let summary_text1 = summary.prefix.map_or_else(String::new, |s| s.to_string());
let summary_text2 = summary.text.to_owned();
@@ -101,7 +95,8 @@ pub(crate) async fn get_chat_list_item_by_id(
let contact = chat_contacts.get(0);
let was_seen_recently = match contact {
Some(contact) => Contact::load_from_db(ctx, *contact)
.await?
.await
.context("contact")?
.was_seen_recently(),
None => false,
};

View File

@@ -0,0 +1,29 @@
use deltachat::net::HttpResponse as CoreHttpResponse;
use serde::Serialize;
use typescript_type_def::TypeDef;
#[derive(Serialize, TypeDef)]
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,
}
}
}

View File

@@ -2,6 +2,7 @@ pub mod account;
pub mod chat;
pub mod chat_list;
pub mod contact;
pub mod http;
pub mod location;
pub mod message;
pub mod provider_info;

View File

@@ -6,7 +6,6 @@ use yerpc::axum::handle_ws_rpc;
use yerpc::{RpcClient, RpcSession};
mod api;
use api::events::event_to_json_rpc_notification;
use api::{Accounts, CommandApi};
const DEFAULT_PORT: u16 = 20808;
@@ -44,12 +43,5 @@ async fn main() -> Result<(), std::io::Error> {
async fn handler(ws: WebSocketUpgrade, Extension(api): Extension<CommandApi>) -> Response {
let (client, out_receiver) = RpcClient::new();
let session = RpcSession::new(client.clone(), api.clone());
tokio::spawn(async move {
let events = api.accounts.read().await.get_event_emitter();
while let Some(event) = events.recv().await {
let event = event_to_json_rpc_notification(event);
client.send_notification("event", Some(event)).await.ok();
}
});
handle_ws_rpc(ws, out_receiver, session).await
}

View File

@@ -67,7 +67,7 @@ async function run() {
null,
null
);
for (const [chatId, _messageId] of chats) {
for (const chatId of chats) {
const chat = await client.rpc.getFullChatById(selectedAccount, chatId);
write($main, `<h3>${chat.name}</h3>`);
const messageIds = await client.rpc.getMessageIds(

View File

@@ -55,5 +55,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "1.113.0"
"version": "1.114.0"
}

View File

@@ -1,7 +1,6 @@
import * as T from "../generated/types.js";
import * as RPC from "../generated/jsonrpc.js";
import { RawClient } from "../generated/client.js";
import { Event } from "../generated/events.js";
import { WebsocketTransport, BaseTransport, Request } from "yerpc";
import { TinyEmitter } from "@deltachat/tiny-emitter";
@@ -36,27 +35,33 @@ export class BaseDeltaChat<
rpc: RawClient;
account?: T.Account;
private contextEmitters: { [key: number]: TinyEmitter<ContextEvents> } = {};
constructor(public transport: Transport) {
//@ts-ignore
private eventTask: Promise<void>;
constructor(public transport: Transport, startEventLoop: boolean) {
super();
this.rpc = new RawClient(this.transport);
this.transport.on("request", (request: Request) => {
const method = request.method;
if (method === "event") {
const event = request.params! as DCWireEvent<Event>;
//@ts-ignore
this.emit(event.event.type, event.contextId, event.event as any);
this.emit("ALL", event.contextId, event.event as any);
if (startEventLoop) {
this.eventTask = this.eventLoop();
}
}
if (this.contextEmitters[event.contextId]) {
this.contextEmitters[event.contextId].emit(
event.event.type,
//@ts-ignore
event.event as any
);
this.contextEmitters[event.contextId].emit("ALL", event.event);
}
async eventLoop(): Promise<void> {
while (true) {
const event = await this.rpc.getNextEvent();
this.emit(event.event.type, event.context_id, event.event as any);
this.emit("ALL", event.context_id, event.event as any);
if (this.contextEmitters[event.context_id]) {
this.contextEmitters[event.context_id].emit(
event.event.type,
//@ts-ignore
event.event as any
);
this.contextEmitters[event.context_id].emit("ALL", event.event as any);
}
});
}
}
async listAccounts(): Promise<T.Account[]> {
@@ -75,10 +80,12 @@ export class BaseDeltaChat<
export type Opts = {
url: string;
startEventLoop: boolean;
};
export const DEFAULT_OPTS: Opts = {
url: "ws://localhost:20808/ws",
startEventLoop: true,
};
export class DeltaChat extends BaseDeltaChat<WebsocketTransport> {
opts: Opts;
@@ -86,20 +93,24 @@ export class DeltaChat extends BaseDeltaChat<WebsocketTransport> {
this.transport.close();
}
constructor(opts?: Opts | string) {
if (typeof opts === "string") opts = { url: opts };
if (opts) opts = { ...DEFAULT_OPTS, ...opts };
else opts = { ...DEFAULT_OPTS };
if (typeof opts === "string") {
opts = { ...DEFAULT_OPTS, url: opts };
} else if (opts) {
opts = { ...DEFAULT_OPTS, ...opts };
} else {
opts = { ...DEFAULT_OPTS };
}
const transport = new WebsocketTransport(opts.url);
super(transport);
super(transport, opts.startEventLoop);
this.opts = opts;
}
}
export class StdioDeltaChat extends BaseDeltaChat<StdioTransport> {
close() {}
constructor(input: any, output: any) {
constructor(input: any, output: any, startEventLoop: boolean) {
const transport = new StdioTransport(input, output);
super(transport);
super(transport, startEventLoop);
}
}

View File

@@ -1,6 +1,5 @@
export * as RPC from "../generated/jsonrpc.js";
export * as T from "../generated/types.js";
export * from "../generated/events.js";
export { RawClient } from "../generated/client.js";
export * from "./client.js";
export * as yerpc from "yerpc";

View File

@@ -12,7 +12,7 @@ describe("basic tests", () => {
before(async () => {
serverHandle = await startServer();
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout);
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout, true);
// dc.on("ALL", (event) => {
//console.log("event", event);
// });

View File

@@ -27,7 +27,7 @@ describe("online tests", function () {
this.skip();
}
serverHandle = await startServer();
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout);
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout, true);
dc.on("ALL", (contextId, { type }) => {
if (type !== "Info") console.log(contextId, type);

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-repl"
version = "1.113.0"
version = "1.114.0"
license = "MPL-2.0"
edition = "2021"

View File

@@ -5,9 +5,23 @@ and provides asynchronous interface to it.
## Getting started
To use Delta Chat RPC client, first build a `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.
To use Delta Chat RPC client, first build a `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`
or download a prebuilt release.
Install it anywhere in your `PATH`.
[Create a virtual environment](https://docs.python.org/3/library/venv.html)
if you don't have one already and activate it.
```
$ python -m venv env
$ . env/bin/activate
```
Install `deltachat-rpc-client` from source:
```
$ cd deltachat-rpc-client
$ pip install .
```
## Testing
1. Build `deltachat-rpc-server` with `cargo build -p deltachat-rpc-server`.

View File

@@ -177,7 +177,7 @@ class Account:
entries = await self._rpc.get_chatlist_entries(self.id, flags, query, contact and contact.id)
if not snapshot:
return [Chat(self, entry[0]) for entry in entries]
return [Chat(self, entry) for entry in entries]
items = await self._rpc.get_chatlist_items_by_entries(self.id, entries)
chats = []

View File

@@ -1,6 +1,6 @@
import json
from dataclasses import dataclass
from typing import TYPE_CHECKING, Union
from typing import TYPE_CHECKING, Optional, Union
from ._utils import AttrDict
from .contact import Contact
@@ -35,6 +35,13 @@ class Message:
snapshot["message"] = self
return snapshot
async def get_reactions(self) -> Optional[AttrDict]:
"""Get message reactions."""
reactions = await self._rpc.get_message_reactions(self.account.id, self.id)
if reactions:
return AttrDict(reactions)
return None
async def mark_seen(self) -> None:
"""Mark the message as seen."""
await self._rpc.markseen_msgs(self.account.id, [self.id])

View File

@@ -23,7 +23,9 @@ class Rpc:
self.event_queues: Dict[int, asyncio.Queue]
# Map from request ID to `asyncio.Future` returning the response.
self.request_events: Dict[int, asyncio.Future]
self.closing: bool
self.reader_task: asyncio.Task
self.events_task: asyncio.Task
async def start(self) -> None:
self.process = await asyncio.create_subprocess_exec(
@@ -35,10 +37,15 @@ class Rpc:
self.id = 0
self.event_queues = {}
self.request_events = {}
self.closing = False
self.reader_task = asyncio.create_task(self.reader_loop())
self.events_task = asyncio.create_task(self.events_loop())
async def close(self) -> None:
"""Terminate RPC server process and wait until the reader loop finishes."""
self.closing = True
await self.stop_io_for_all_accounts()
await self.events_task
self.process.terminate()
await self.reader_task
@@ -58,21 +65,28 @@ class Rpc:
if "id" in response:
fut = self.request_events.pop(response["id"])
fut.set_result(response)
elif response["method"] == "event":
# An event notification.
params = response["params"]
account_id = params["contextId"]
if account_id not in self.event_queues:
self.event_queues[account_id] = asyncio.Queue()
await self.event_queues[account_id].put(params["event"])
else:
print(response)
async def get_queue(self, account_id: int) -> asyncio.Queue:
if account_id not in self.event_queues:
self.event_queues[account_id] = asyncio.Queue()
return self.event_queues[account_id]
async def events_loop(self) -> None:
"""Requests new events and distributes them between queues."""
while True:
if self.closing:
return
event = await self.get_next_event()
account_id = event["context_id"]
queue = await self.get_queue(account_id)
await queue.put(event["event"])
async def wait_for_event(self, account_id: int) -> Optional[dict]:
"""Waits for the next event from the given account and returns it."""
if account_id in self.event_queues:
return await self.event_queues[account_id].get()
return None
queue = await self.get_queue(account_id)
return await queue.get()
def __getattr__(self, attr: str):
async def method(*args, **kwargs) -> Any:

View File

@@ -239,6 +239,10 @@ async def test_message(acfactory) -> None:
await message.mark_seen()
await message.send_reaction("😎")
reactions = await message.get_reactions()
assert reactions
snapshot = await message.get_snapshot()
assert reactions == snapshot.reactions
@pytest.mark.asyncio()

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "1.113.0"
version = "1.114.0"
description = "DeltaChat JSON-RPC server"
edition = "2021"
readme = "README.md"
@@ -15,12 +15,12 @@ deltachat = { path = "..", default-features = false }
anyhow = "1"
env_logger = { version = "0.10.0" }
futures-lite = "1.12.0"
futures-lite = "1.13.0"
log = "0.4"
serde_json = "1.0.95"
serde_json = "1.0.96"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.27.0", features = ["io-std"] }
tokio-util = "0.7.7"
tokio = { version = "1.28.0", features = ["io-std"] }
tokio-util = "0.7.8"
yerpc = { version = "0.4.0", features = ["anyhow_expose"] }
[features]

View File

@@ -7,7 +7,6 @@ use std::sync::Arc;
use anyhow::{anyhow, Context as _, Result};
use deltachat::constants::DC_VERSION_STR;
use deltachat_jsonrpc::api::events::event_to_json_rpc_notification;
use deltachat_jsonrpc::api::{Accounts, CommandApi};
use futures_lite::stream::StreamExt;
use tokio::io::{self, AsyncBufReadExt, BufReader};
@@ -58,7 +57,6 @@ async fn main_impl() -> Result<()> {
let path = std::env::var("DC_ACCOUNTS_PATH").unwrap_or_else(|_| "accounts".to_string());
log::info!("Starting with accounts directory `{}`.", path);
let accounts = Accounts::new(PathBuf::from(&path)).await?;
let events = accounts.get_event_emitter();
log::info!("Creating JSON-RPC API.");
let accounts = Arc::new(RwLock::new(accounts));
@@ -68,21 +66,6 @@ async fn main_impl() -> Result<()> {
let session = RpcSession::new(client.clone(), state.clone());
let main_cancel = CancellationToken::new();
// Events task converts core events to JSON-RPC notifications.
let cancel = main_cancel.clone();
let events_task: JoinHandle<Result<()>> = tokio::spawn(async move {
let _cancel_guard = cancel.clone().drop_guard();
let mut r = Ok(());
while let Some(event) = events.recv().await {
if r.is_err() {
continue;
}
let event = event_to_json_rpc_notification(event);
r = client.send_notification("event", Some(event)).await;
}
Ok(())
});
// Send task prints JSON responses to stdout.
let cancel = main_cancel.clone();
let send_task: JoinHandle<anyhow::Result<()>> = tokio::spawn(async move {
@@ -148,16 +131,13 @@ async fn main_impl() -> Result<()> {
Ok(())
});
// See "Thread safety" section in deltachat-ffi/deltachat.h for explanation.
// NB: Events are drained by events_task.
main_cancel.cancelled().await;
accounts.read().await.stop_io().await;
drop(accounts);
drop(state);
let (r0, r1, r2, r3) = tokio::join!(events_task, send_task, sigterm_task, recv_task);
for r in [r0, r1, r2, r3] {
r??;
}
send_task.await??;
sigterm_task.await??;
recv_task.await??;
Ok(())
}

View File

@@ -49,12 +49,15 @@ skip = [
{ name = "syn", version = "1.0.109" },
{ name = "time", version = "<0.3" },
{ name = "wasi", version = "<0.11" },
{ name = "windows_aarch64_msvc", version = "<0.42" },
{ name = "windows_i686_gnu", version = "<0.42" },
{ name = "windows_i686_msvc", version = "<0.42" },
{ name = "windows-sys", version = "<0.45" },
{ name = "windows_x86_64_gnu", version = "<0.42" },
{ name = "windows_x86_64_msvc", version = "<0.42" },
{ name = "windows_aarch64_gnullvm", version = "<0.48" },
{ name = "windows_aarch64_msvc", version = "<0.48" },
{ name = "windows_i686_gnu", version = "<0.48" },
{ name = "windows_i686_msvc", version = "<0.48" },
{ name = "windows-sys", version = "<0.48" },
{ name = "windows-targets", version = "<0.48" },
{ name = "windows_x86_64_gnullvm", version = "<0.48" },
{ name = "windows_x86_64_gnu", version = "<0.48" },
{ name = "windows_x86_64_msvc", version = "<0.48" },
]

View File

@@ -60,5 +60,5 @@
"test:mocha": "mocha -r esm node/test/test.js --growl --reporter=spec --bail --exit"
},
"types": "node/dist/index.d.ts",
"version": "1.113.0"
"version": "1.114.0"
}

View File

@@ -1 +1 @@
2023-04-18
2023-04-24

View File

@@ -1,2 +1,6 @@
#!/bin/sh
# Update package cache without changing the lockfile.
cargo update --dry-run
cargo deny --workspace --all-features check -D warnings

39
scripts/release.py Normal file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env python3
import sys
import subprocess
import set_core_version
from argparse import ArgumentParser
# update the version
parser = ArgumentParser(prog="release")
parser.add_argument("newversion")
try:
newversion = parser.parse_args().newversion
except SystemExit:
newversion = None
set_core_version.set_version(newversion)
tag = "v" + newversion
# TODO would be nice to automatically checkout the correct branch
# update the changelog
print(f"Updating CHANGELOG.md using git cliff --unreleased --tag {newversion} --prepend CHANGELOG.md")
changelog = subprocess.run(["git", "cliff", "--unreleased", "--tag", newversion, "--prepend", "CHANGELOG.md"]).stdout.strip()
subprocess.run(["git", "add", "-A"])
subprocess.run(["git", "commit", "-m", f"chore(release): prepare for {tag}"])
subprocess.run(["git", "show"])
# create a tag
subprocess.run(
[
"git", "tag", "-a", tag, "-m", f"Release {tag}", "-m", changelog
]
)
subprocess.run(["git", "tag", "-v", tag])
print("Done!")
print(f"Now push the commit (git push origin {tag}).")

View File

@@ -9,7 +9,14 @@ import subprocess
from argparse import ArgumentParser
rex = re.compile(r'version = "(\S+)"')
json_list = ["package.json", "deltachat-jsonrpc/typescript/package.json"]
toml_list = [
"Cargo.toml",
"deltachat-ffi/Cargo.toml",
"deltachat-jsonrpc/Cargo.toml",
"deltachat-rpc-server/Cargo.toml",
"deltachat-repl/Cargo.toml",
]
def regex_matches(relpath, regex=rex):
p = pathlib.Path(relpath)
@@ -60,31 +67,16 @@ def update_package_json(relpath, newversion):
json.dump(json_data, f, sort_keys=True, indent=2)
f.write("\n")
def main():
parser = ArgumentParser(prog="set_core_version")
parser.add_argument("newversion")
json_list = ["package.json", "deltachat-jsonrpc/typescript/package.json"]
toml_list = [
"Cargo.toml",
"deltachat-ffi/Cargo.toml",
"deltachat-jsonrpc/Cargo.toml",
"deltachat-rpc-server/Cargo.toml",
"deltachat-repl/Cargo.toml",
]
try:
opts = parser.parse_args()
except SystemExit:
def set_version(newversion):
if newversion is None:
print()
for x in toml_list:
print(f"{x}: {read_toml_version(x)}")
for x in json_list:
print(f"{x}: {read_json_version(x)}")
print()
raise SystemExit("need argument: new version, example: 1.25.0")
raise SystemExit("need argument: newversion, example: 1.25.0")
newversion = opts.newversion
if newversion.count(".") < 2:
raise SystemExit("need at least two dots in version")
@@ -134,6 +126,17 @@ def main():
print(f" git push origin v{newversion}")
print("")
def main():
parser = ArgumentParser(prog="set_core_version")
parser.add_argument("newversion")
try:
newversion = parser.parse_args().newversion
except SystemExit:
newversion = None
set_version(newversion)
if __name__ == "__main__":
main()

View File

@@ -311,13 +311,17 @@ impl Chatlist {
};
let (lastmsg, lastcontact) = if let Some(lastmsg_id) = lastmsg_id {
let lastmsg = Message::load_from_db(context, lastmsg_id).await?;
let lastmsg = Message::load_from_db(context, lastmsg_id)
.await
.context("loading message failed")?;
if lastmsg.from_id == ContactId::SELF {
(Some(lastmsg), None)
} else {
match chat.typ {
Chattype::Group | Chattype::Broadcast | Chattype::Mailinglist => {
let lastcontact = Contact::load_from_db(context, lastmsg.from_id).await?;
let lastcontact = Contact::load_from_db(context, lastmsg.from_id)
.await
.context("loading contact failed")?;
(Some(lastmsg), Some(lastcontact))
}
Chattype::Single | Chattype::Undefined => (Some(lastmsg), None),
@@ -362,6 +366,25 @@ pub async fn get_archived_cnt(context: &Context) -> Result<usize> {
Ok(count)
}
/// Gets the last message of a chat, the message that would also be displayed in the ChatList
/// Used for passing to `deltachat::chatlist::Chatlist::get_summary2`
pub async fn get_last_message_for_chat(
context: &Context,
chat_id: ChatId,
) -> Result<Option<MsgId>> {
context
.sql
.query_get_value(
"SELECT id
FROM msgs
WHERE chat_id=?2
AND (hidden=0 OR state=?1)
ORDER BY timestamp DESC, id DESC LIMIT 1",
(MessageState::OutDraft, chat_id),
)
.await
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -11,7 +11,6 @@
mod auto_mozilla;
mod auto_outlook;
mod read_url;
mod server_params;
use anyhow::{bail, ensure, Context as _, Result};

View File

@@ -6,10 +6,10 @@ use std::str::FromStr;
use quick_xml::events::{BytesStart, Event};
use super::read_url::read_url;
use super::{Error, ServerParams};
use crate::context::Context;
use crate::login_param::LoginParam;
use crate::net::read_url;
use crate::provider::{Protocol, Socket};
#[derive(Debug)]

View File

@@ -7,9 +7,9 @@ use std::io::BufRead;
use quick_xml::events::Event;
use super::read_url::read_url;
use super::{Error, ServerParams};
use crate::context::Context;
use crate::net::read_url;
use crate::provider::{Protocol, Socket};
/// Result of parsing a single `Protocol` tag.

View File

@@ -1,44 +0,0 @@
use anyhow::{anyhow, format_err};
use crate::context::Context;
use crate::socks::Socks5Config;
pub async fn read_url(context: &Context, url: &str) -> anyhow::Result<String> {
match read_url_inner(context, url).await {
Ok(s) => {
info!(context, "Successfully read url {}", url);
Ok(s)
}
Err(e) => {
info!(context, "Can't read URL {}: {:#}", url, e);
Err(format_err!("Can't read URL {}: {:#}", url, e))
}
}
}
pub async fn read_url_inner(context: &Context, url: &str) -> anyhow::Result<String> {
let socks5_config = Socks5Config::from_database(&context.sql).await?;
let client = crate::http::get_client(socks5_config)?;
let mut url = url.to_string();
// Follow up to 10 http-redirects
for _i in 0..10 {
let response = client.get(&url).send().await?;
if response.status().is_redirection() {
let headers = response.headers();
let header = headers
.get_all("location")
.iter()
.last()
.ok_or_else(|| anyhow!("Redirection doesn't have a target location"))?
.to_str()?;
info!(context, "Following redirect to {}", header);
url = header.to_string();
continue;
}
return response.text().await.map_err(Into::into);
}
Err(format_err!("Followed 10 redirections"))
}

View File

@@ -7,6 +7,7 @@
//! `MsgId.get_html()` will return HTML -
//! this allows nice quoting, handling linebreaks properly etc.
use std::future::Future;
use std::pin::Pin;

View File

@@ -1,24 +0,0 @@
//! # HTTP module.
use std::time::Duration;
use anyhow::Result;
use crate::socks::Socks5Config;
const HTTP_TIMEOUT: Duration = Duration::from_secs(30);
pub(crate) fn get_client(socks5_config: Option<Socks5Config>) -> Result<reqwest::Client> {
let builder = reqwest::ClientBuilder::new().timeout(HTTP_TIMEOUT);
let builder = if let Some(socks5_config) = socks5_config {
let proxy = reqwest::Proxy::all(socks5_config.to_url())?;
builder.proxy(proxy)
} else {
// Disable usage of "system" proxy configured via environment variables.
// It is enabled by default in `reqwest`, see
// <https://docs.rs/reqwest/0.11.14/reqwest/struct.ClientBuilder.html#method.no_proxy>
// for documentation.
builder.no_proxy()
};
Ok(builder.build()?)
}

View File

@@ -63,7 +63,6 @@ mod decrypt;
pub mod download;
mod e2ee;
pub mod ephemeral;
mod http;
mod imap;
pub mod imex;
pub mod release;
@@ -100,7 +99,7 @@ mod dehtml;
mod authres;
mod color;
pub mod html;
mod net;
pub mod net;
pub mod plaintext;
pub mod summary;

View File

@@ -1,4 +1,4 @@
///! # Common network utilities.
//! # Common network utilities.
use std::net::{IpAddr, SocketAddr};
use std::pin::Pin;
use std::str::FromStr;
@@ -12,9 +12,12 @@ use tokio_io_timeout::TimeoutStream;
use crate::context::Context;
use crate::tools::time;
pub(crate) mod http;
pub(crate) mod session;
pub(crate) mod tls;
pub use http::{read_url, read_url_blob, Response as HttpResponse};
async fn connect_tcp_inner(addr: SocketAddr, timeout_val: Duration) -> Result<TcpStream> {
let tcp_stream = timeout(timeout_val, TcpStream::connect(addr))
.await

94
src/net/http.rs Normal file
View File

@@ -0,0 +1,94 @@
//! # HTTP module.
use std::time::Duration;
use anyhow::{anyhow, Result};
use mime::Mime;
use crate::context::Context;
use crate::socks::Socks5Config;
const HTTP_TIMEOUT: Duration = Duration::from_secs(30);
/// HTTP(S) GET response.
#[derive(Debug)]
pub struct Response {
/// Response body.
pub blob: Vec<u8>,
/// MIME type exntracted from the `Content-Type` header, if any.
pub mimetype: Option<String>,
/// Encoding extracted from the `Content-Type` header, if any.
pub encoding: Option<String>,
}
/// Retrieves the text contents of URL using HTTP GET request.
pub async fn read_url(context: &Context, url: &str) -> Result<String> {
Ok(read_url_inner(context, url).await?.text().await?)
}
/// Retrieves the binary contents of URL using HTTP GET request.
pub async fn read_url_blob(context: &Context, url: &str) -> Result<Response> {
let response = read_url_inner(context, url).await?;
let content_type = response
.headers()
.get(reqwest::header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.and_then(|value| value.parse::<Mime>().ok());
let mimetype = content_type
.as_ref()
.map(|mime| mime.essence_str().to_string());
let encoding = content_type.as_ref().and_then(|mime| {
mime.get_param(mime::CHARSET)
.map(|charset| charset.as_str().to_string())
});
let blob: Vec<u8> = response.bytes().await?.into();
Ok(Response {
blob,
mimetype,
encoding,
})
}
async fn read_url_inner(context: &Context, url: &str) -> Result<reqwest::Response> {
let socks5_config = Socks5Config::from_database(&context.sql).await?;
let client = get_client(socks5_config)?;
let mut url = url.to_string();
// Follow up to 10 http-redirects
for _i in 0..10 {
let response = client.get(&url).send().await?;
if response.status().is_redirection() {
let headers = response.headers();
let header = headers
.get_all("location")
.iter()
.last()
.ok_or_else(|| anyhow!("Redirection doesn't have a target location"))?
.to_str()?;
info!(context, "Following redirect to {}", header);
url = header.to_string();
continue;
}
return Ok(response);
}
Err(anyhow!("Followed 10 redirections"))
}
pub(crate) fn get_client(socks5_config: Option<Socks5Config>) -> Result<reqwest::Client> {
let builder = reqwest::ClientBuilder::new().timeout(HTTP_TIMEOUT);
let builder = if let Some(socks5_config) = socks5_config {
let proxy = reqwest::Proxy::all(socks5_config.to_url())?;
builder.proxy(proxy)
} else {
// Disable usage of "system" proxy configured via environment variables.
// It is enabled by default in `reqwest`, see
// <https://docs.rs/reqwest/0.11.14/reqwest/struct.ClientBuilder.html#method.no_proxy>
// for documentation.
builder.no_proxy()
};
Ok(builder.build()?)
}

View File

@@ -160,7 +160,7 @@ pub(crate) async fn get_oauth2_access_token(
// ... and POST
let socks5_config = Socks5Config::from_database(&context.sql).await?;
let client = crate::http::get_client(socks5_config)?;
let client = crate::net::http::get_client(socks5_config)?;
let response: Response = match client.post(post_url).form(&post_param).send().await {
Ok(resp) => match resp.json().await {
@@ -291,7 +291,7 @@ impl Oauth2 {
// "picture": "https://lh4.googleusercontent.com/-Gj5jh_9R0BY/AAAAAAAAAAI/AAAAAAAAAAA/IAjtjfjtjNA/photo.jpg"
// }
let socks5_config = Socks5Config::from_database(&context.sql).await.ok()?;
let client = match crate::http::get_client(socks5_config) {
let client = match crate::net::http::get_client(socks5_config) {
Ok(cl) => cl,
Err(err) => {
warn!(context, "failed to get HTTP client: {}", err);

View File

@@ -526,7 +526,7 @@ struct CreateAccountErrorResponse {
async fn set_account_from_qr(context: &Context, qr: &str) -> Result<()> {
let url_str = &qr[DCACCOUNT_SCHEME.len()..];
let socks5_config = Socks5Config::from_database(&context.sql).await?;
let response = crate::http::get_client(socks5_config)?
let response = crate::net::http::get_client(socks5_config)?
.post(url_str)
.send()
.await?;

View File

@@ -557,7 +557,13 @@ async fn add_parts(
blocked: Blocked::Request,
}) if is_bot => Blocked::Not,
Some(ChatIdBlocked { id: _, blocked }) => blocked,
None => Blocked::Request,
None => {
if is_bot {
Blocked::Not
} else {
Blocked::Request
}
}
};
if chat_id.is_none() {

View File

@@ -2980,6 +2980,17 @@ async fn test_auto_accept_for_bots() -> Result<()> {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_auto_accept_group_for_bots() -> Result<()> {
let t = TestContext::new_alice().await;
t.set_config(Config::Bot, Some("1")).await.unwrap();
receive_imf(&t, GRP_MAIL, false).await?;
let msg = t.get_last_msg().await;
let chat = chat::Chat::load_from_db(&t, msg.chat_id).await?;
assert!(!chat.is_contact_request());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_no_private_reply_to_blocked_account() -> Result<()> {
let mut tcm = TestContextManager::new();