Compare commits

..

6 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
48 changed files with 287 additions and 699 deletions

View File

@@ -5,11 +5,11 @@ mergeable:
validate:
- do: title
begins_with:
match: ['feat', 'fix', 'api', 'refactor', 'perf', 'test', 'style', 'chore', 'cargo', 'build', 'ci', 'docs']
match: ['feat', 'fix', 'api', 'refactor', 'perf', 'test', 'style', 'chore']
fail:
- do: checks
status: "action_required"
payload:
title: PR title should follow conventional commits
summary: "PR title should follow https://conventionalcommits.org. See https://github.com/deltachat/deltachat-core-rust/blob/master/CONTRIBUTING.md for details."
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

@@ -222,6 +222,7 @@ jobs:
fail-fast: false
matrix:
include:
# Currently used Rust version.
- os: ubuntu-latest
python: 3.11
- os: macos-latest
@@ -233,12 +234,11 @@ jobs:
- 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>
# 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.8
python: 3.7
runs-on: ${{ matrix.os }}
steps:

View File

@@ -84,28 +84,9 @@ jobs:
path: target/${{ matrix.target}}/release/${{ matrix.path }}
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:
name: Upload binaries to the release
needs: ["build_linux", "build_windows", "build_macos"]
needs: ["build_linux", "build_windows"]
permissions:
contents: write
runs-on: "ubuntu-latest"
@@ -116,7 +97,7 @@ jobs:
- name: Compose dist/ directory
run: |
mkdir dist
for x in x86_64 i686 aarch64 armv7 win32.exe win64.exe x86_64-macos; do
for x in x86_64 i686 aarch64 armv7 win32.exe win64.exe; do
mv "deltachat-rpc-server-$x"/* "dist/deltachat-rpc-server-$x"
done

View File

@@ -1,58 +1,15 @@
# Changelog
## [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.
## 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
- Improve SMTP logging.
## [1.114.0] - 2023-04-24
@@ -2485,7 +2442,5 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
[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.113.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.112.9...v1.113.0
[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
[1.115.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.114.0...v1.115.0

View File

@@ -1,70 +0,0 @@
# 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).
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".
#### 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/
## Other ways to contribute
For other ways to contribute, refer to the [website](https://delta.chat/en/contribute).

52
Cargo.lock generated
View File

@@ -60,9 +60,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "1.0.1"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
@@ -1144,7 +1144,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.115.0"
version = "1.114.0"
dependencies = [
"ansi_term",
"anyhow",
@@ -1219,7 +1219,7 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc"
version = "1.115.0"
version = "1.114.0"
dependencies = [
"anyhow",
"async-channel",
@@ -1242,7 +1242,7 @@ dependencies = [
[[package]]
name = "deltachat-repl"
version = "1.115.0"
version = "1.114.0"
dependencies = [
"ansi_term",
"anyhow",
@@ -1257,7 +1257,7 @@ dependencies = [
[[package]]
name = "deltachat-rpc-server"
version = "1.115.0"
version = "1.114.0"
dependencies = [
"anyhow",
"deltachat",
@@ -1282,7 +1282,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.115.0"
version = "1.114.0"
dependencies = [
"anyhow",
"deltachat",
@@ -1295,7 +1295,6 @@ dependencies = [
"serde_json",
"thiserror",
"tokio",
"yerpc",
]
[[package]]
@@ -1423,9 +1422,9 @@ dependencies = [
[[package]]
name = "dirs"
version = "5.0.1"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd"
dependencies = [
"dirs-sys",
]
@@ -1442,14 +1441,13 @@ dependencies = [
[[package]]
name = "dirs-sys"
version = "0.4.1"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.48.0",
"windows-sys 0.45.0",
]
[[package]]
@@ -3155,12 +3153,6 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "os_info"
version = "3.6.0"
@@ -3611,7 +3603,7 @@ dependencies = [
"rand 0.8.5",
"rand_chacha 0.3.1",
"rand_xorshift",
"regex-syntax 0.6.29",
"regex-syntax",
"unarray",
]
@@ -3882,13 +3874,13 @@ dependencies = [
[[package]]
name = "regex"
version = "1.8.1"
version = "1.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.7.1",
"regex-syntax",
]
[[package]]
@@ -3897,7 +3889,7 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax 0.6.29",
"regex-syntax",
]
[[package]]
@@ -3906,12 +3898,6 @@ version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
[[package]]
name = "reqwest"
version = "0.11.17"
@@ -5755,9 +5741,9 @@ dependencies = [
[[package]]
name = "yerpc"
version = "0.4.4"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2c26a804eaa30c1ff1a296dc6dd1a7d7c622750dafcd0d6b2ed5e3c5c3beb22"
checksum = "b6a0257f42e6bdc187f37074723b6094da3502cee21ae517b3c54d2c37d506e7"
dependencies = [
"anyhow",
"async-channel",

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.115.0"
version = "1.114.0"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.65"
@@ -69,7 +69,7 @@ pretty_env_logger = { version = "0.4", optional = true }
qrcodegen = "1.7.0"
quick-xml = "0.28"
rand = "0.8"
regex = "1.8"
regex = "1.7"
reqwest = { version = "0.11.17", features = ["json"] }
rusqlite = { version = "0.29", features = ["sqlcipher"] }
rust-hsluv = "0.1"

View File

@@ -1,18 +0,0 @@
# 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. Update the changelog: `git cliff --unreleased --tag 1.116.0 --prepend CHANGELOG.md` or `git cliff -u -t 1.116.0 -p CHANGELOG.md`.
3. Update the version by running `scripts/set_core_version.py 1.116.0`.
4. 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.
5. Tag the release: `git tag -a v1.116.0`.
6. Push the release tag: `git push origin v1.116.0`.
7. Create a GitHub release: `gh release create v1.116.0 -n ''`.

View File

@@ -1,17 +1,17 @@
# configuration file for git-cliff
# see https://git-cliff.org/docs/configuration/
# 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 = false
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/pull/${2}))"}, # replace pull request / issue numbers
{ 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 = [
@@ -24,10 +24,6 @@ commit_parsers = [
{ 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
@@ -64,9 +60,7 @@ body = """
{% 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.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
{% for footer in commit.footers %}{% if 'BREAKING CHANGE' in footer.token %}
{% raw %} {% endraw %}- {{ footer.value }}\
{% endif %}{% endfor %}\
@@ -75,3 +69,7 @@ body = """
"""
# 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.115.0"
version = "1.114.0"
description = "Deltachat FFI"
edition = "2018"
readme = "README.md"
@@ -25,7 +25,6 @@ anyhow = "1"
thiserror = "1"
rand = "0.8"
once_cell = "1.17.0"
yerpc = { version = "0.4.4", features = ["anyhow_expose"] }
[features]
default = ["vendored"]

View File

@@ -461,9 +461,8 @@ char* dc_get_blobdir (const dc_context_t* context);
* If no type is prefixed, the videochat is handled completely in a browser.
* - `bot` = Set to "1" if this is a bot.
* Prevents adding the "Device messages" and "Saved messages" chats,
* adds Auto-Submitted header to outgoing messages,
* accepts contact requests automatically (calling dc_accept_chat() is not needed for bots)
* and does not cut large incoming text messages.
* adds Auto-Submitted header to outgoing messages
* and accepts contact requests automatically (calling dc_accept_chat() is not needed for bots).
* - `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().
@@ -5720,18 +5719,6 @@ void dc_jsonrpc_request(dc_jsonrpc_instance_t* jsonrpc_instance, const char* req
*/
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
*

View File

@@ -4967,7 +4967,7 @@ pub unsafe extern "C" fn dc_accounts_get_event_emitter(
#[cfg(feature = "jsonrpc")]
mod jsonrpc {
use deltachat_jsonrpc::api::CommandApi;
use deltachat_jsonrpc::yerpc::{OutReceiver, RpcClient, RpcServer, RpcSession};
use deltachat_jsonrpc::yerpc::{OutReceiver, RpcClient, RpcSession};
use super::*;
@@ -5039,29 +5039,4 @@ mod jsonrpc {
.map(|result| serde_json::to_string(&result).unwrap_or_default().strdup())
.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(&params) {
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(),
}
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc"
version = "1.115.0"
version = "1.114.0"
description = "DeltaChat JSON-RPC API"
edition = "2021"
default-run = "deltachat-jsonrpc-server"
@@ -21,7 +21,7 @@ log = "0.4"
async-channel = { version = "1.8.0" }
futures = { version = "0.3.28" }
serde_json = "1.0.96"
yerpc = { version = "0.4.4", features = ["anyhow_expose"] }
yerpc = { version = "0.4.3", features = ["anyhow_expose"] }
typescript-type-def = { version = "0.5.5", features = ["json_value"] }
tokio = { version = "1.28.0" }
sanitize-filename = "0.4"

View File

@@ -3,7 +3,6 @@ use serde::Serialize;
use typescript_type_def::TypeDef;
#[derive(Serialize, TypeDef)]
#[serde(rename_all = "camelCase")]
pub struct Event {
/// Event payload.
event: EventType,

View File

@@ -4,7 +4,6 @@ use std::{collections::HashMap, str::FromStr};
use anyhow::{anyhow, bail, ensure, Context, Result};
pub use deltachat::accounts::Accounts;
use deltachat::message::get_msg_read_receipts;
use deltachat::qr::Qr;
use deltachat::{
chat::{
@@ -36,19 +35,21 @@ use tokio::sync::{watch, Mutex, RwLock};
use walkdir::WalkDir;
use yerpc::rpc;
pub mod events;
pub mod types;
use num_traits::FromPrimitive;
use types::account::Account;
use types::chat::FullChat;
use types::contact::ContactObject;
use types::events::Event;
use types::http::HttpResponse;
use types::message::{MessageData, MessageObject, MessageReadReceipt};
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},
@@ -1118,24 +1119,6 @@ impl CommandApi {
get_msg_info(&ctx, MsgId::new(message_id)).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.
/// This function is typically called when the user hits the "Download" button
/// that is shown by the UI in case `download_state` is `'Available'` or `'Failure'`

View File

@@ -1,6 +1,6 @@
use std::time::{Duration, SystemTime};
use anyhow::{bail, Context as _, 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;
@@ -92,7 +92,10 @@ impl FullChat {
is_protected: chat.is_protected(),
profile_image, //BLOBS ?
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
chat_type: chat
.get_type()
.to_u32()
.ok_or_else(|| anyhow!("unknown chat type id"))?, // TODO get rid of this unwrap?
is_unpromoted: chat.is_unpromoted(),
is_self_talk: chat.is_self_talk(),
contacts,
@@ -155,7 +158,10 @@ impl BasicChat {
is_protected: chat.is_protected(),
profile_image, //BLOBS ?
archived: chat.get_visibility() == chat::ChatVisibility::Archived,
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
chat_type: chat
.get_type()
.to_u32()
.ok_or_else(|| anyhow!("unknown chat type id"))?, // TODO get rid of this unwrap?
is_unpromoted: chat.is_unpromoted(),
is_self_talk: chat.is_self_talk(),
color,

View File

@@ -12,7 +12,6 @@ use serde::Serialize;
use typescript_type_def::TypeDef;
use super::color_int_to_hex_string;
use super::message::MessageViewtype;
#[derive(Serialize, TypeDef)]
#[serde(tag = "type")]
@@ -27,8 +26,6 @@ pub enum ChatListItemFetchResult {
summary_text1: String,
summary_text2: String,
summary_status: u32,
/// showing preview if last chat message is image
summary_preview_image: Option<String>,
is_protected: bool,
is_group: bool,
fresh_message_counter: usize,
@@ -45,8 +42,6 @@ pub enum ChatListItemFetchResult {
/// contact id if this is a dm chat (for view profile entry in context menu)
dm_chat_contact: Option<u32>,
was_seen_recently: bool,
last_message_type: Option<MessageViewtype>,
last_message_id: Option<u32>,
},
#[serde(rename_all = "camelCase")]
ArchiveLink { fresh_message_counter: usize },
@@ -77,8 +72,6 @@ pub(crate) async fn get_chat_list_item_by_id(
let summary_text1 = summary.prefix.map_or_else(String::new, |s| s.to_string());
let summary_text2 = summary.text.to_owned();
let summary_preview_image = summary.thumbnail_path;
let visibility = chat.get_visibility();
let avatar_path = chat
@@ -86,15 +79,12 @@ pub(crate) async fn get_chat_list_item_by_id(
.await?
.map(|path| path.to_str().unwrap_or("invalid/path").to_owned());
let (last_updated, message_type) = match last_msgid {
let last_updated = match last_msgid {
Some(id) => {
let last_message = deltachat::message::Message::load_from_db(ctx, id).await?;
(
Some(last_message.get_timestamp() * 1000),
Some(last_message.get_viewtype().into()),
)
Some(last_message.get_timestamp() * 1000)
}
None => (None, None),
None => None,
};
let chat_contacts = get_chat_contacts(ctx, chat_id).await?;
@@ -129,7 +119,6 @@ pub(crate) async fn get_chat_list_item_by_id(
summary_text1,
summary_text2,
summary_status: summary.state.to_u32().expect("impossible"), // idea and a function to transform the constant to strings? or return string enum
summary_preview_image,
is_protected: chat.is_protected(),
is_group: chat.get_type() == Chattype::Group,
fresh_message_counter,
@@ -144,7 +133,5 @@ pub(crate) async fn get_chat_list_item_by_id(
is_broadcast: chat.get_type() == Chattype::Broadcast,
dm_chat_contact,
was_seen_recently,
last_message_type: message_type,
last_message_id: last_msgid.map(|id| id.to_u32()),
})
}

View File

@@ -1,7 +1,7 @@
use anyhow::{Context as _, Result};
use anyhow::{anyhow, Result};
use deltachat::chat::Chat;
use deltachat::chat::ChatItem;
use deltachat::chat::ChatVisibility;
use deltachat::constants::Chattype;
use deltachat::contact::Contact;
use deltachat::context::Context;
use deltachat::download;
@@ -114,12 +114,8 @@ impl MessageObject {
pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result<Self> {
let message = Message::load_from_db(context, msg_id).await?;
let sender_contact = Contact::load_from_db(context, message.get_from_id())
.await
.context("failed to load sender contact")?;
let sender = ContactObject::try_from_dc_contact(context, sender_contact)
.await
.context("failed to load sender contact object")?;
let sender_contact = Contact::load_from_db(context, message.get_from_id()).await?;
let sender = ContactObject::try_from_dc_contact(context, sender_contact).await?;
let file_bytes = message.get_filebytes(context).await?.unwrap_or_default();
let override_sender_name = message.get_override_sender_name();
@@ -136,9 +132,7 @@ impl MessageObject {
let quote = if let Some(quoted_text) = message.quoted_text() {
match message.quoted_message(context).await? {
Some(quote) => {
let quote_author = Contact::load_from_db(context, quote.get_from_id())
.await
.context("failed to load quote author contact")?;
let quote_author = Contact::load_from_db(context, quote.get_from_id()).await?;
Some(MessageQuote::WithMessage {
text: quoted_text,
message_id: quote.get_id().to_u32(),
@@ -166,9 +160,7 @@ impl MessageObject {
None
};
let reactions = get_msg_reactions(context, msg_id)
.await
.context("failed to load message reactions")?;
let reactions = get_msg_reactions(context, msg_id).await?;
let reactions = if reactions.is_empty() {
None
} else {
@@ -188,7 +180,7 @@ impl MessageObject {
state: message
.get_state()
.to_u32()
.context("state conversion to number failed")?,
.ok_or_else(|| anyhow!("state conversion to number failed"))?,
error: message.error(),
timestamp: message.get_timestamp(),
@@ -211,7 +203,7 @@ impl MessageObject {
videochat_type: match message.get_videochat_type() {
Some(vct) => Some(
vct.to_u32()
.context("videochat type conversion to number failed")?,
.ok_or_else(|| anyhow!("state conversion to number failed"))?,
),
None => None,
},
@@ -451,17 +443,9 @@ impl MessageNotificationInfo {
pub struct MessageSearchResult {
id: u32,
author_profile_image: Option<String>,
/// if sender name if overridden it will show it as ~alias
author_name: String,
author_color: String,
author_id: u32,
chat_profile_image: Option<String>,
chat_color: String,
chat_name: String,
chat_type: u32,
is_chat_protected: bool,
is_chat_contact_request: bool,
is_chat_archived: bool,
chat_name: Option<String>,
message: String,
timestamp: i64,
}
@@ -476,31 +460,17 @@ impl MessageSearchResult {
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
None => None,
};
let chat_profile_image = match chat.get_profile_image(context).await? {
Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()),
None => None,
};
let author_name = if let Some(name) = message.get_override_sender_name() {
format!("~{name}")
} else {
sender.get_display_name().to_owned()
};
let chat_color = color_int_to_hex_string(chat.get_color(context).await?);
Ok(Self {
id: msg_id.to_u32(),
author_profile_image: profile_image,
author_name,
author_name: sender.get_display_name().to_owned(),
author_color: color_int_to_hex_string(sender.get_color()),
author_id: sender.id.to_u32(),
chat_name: chat.get_name().to_owned(),
chat_color,
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
chat_profile_image,
is_chat_protected: chat.is_protected(),
is_chat_contact_request: chat.is_contact_request(),
is_chat_archived: chat.get_visibility() == ChatVisibility::Archived,
chat_name: if chat.get_type() == Chattype::Single {
Some(chat.get_name().to_owned())
} else {
None
},
message: message.get_text().unwrap_or_default(),
timestamp: message.get_timestamp(),
})
@@ -544,10 +514,3 @@ pub struct MessageData {
pub override_sender_name: Option<String>,
pub quoted_message_id: Option<u32>,
}
#[derive(Serialize, TypeDef)]
#[serde(rename_all = "camelCase")]
pub struct MessageReadReceipt {
pub contact_id: u32,
pub timestamp: i64,
}

View File

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

View File

@@ -1,37 +1,23 @@
use std::collections::BTreeMap;
use deltachat::contact::ContactId;
use deltachat::reaction::Reactions;
use serde::Serialize;
use typescript_type_def::TypeDef;
/// A single reaction emoji.
#[derive(Serialize, TypeDef)]
#[serde(rename = "Reaction", rename_all = "camelCase")]
pub struct JSONRPCReaction {
/// Emoji.
emoji: String,
/// Emoji frequency.
count: usize,
/// True if we reacted with this emoji.
is_from_self: bool,
}
/// Structure representing all reactions to a particular message.
#[derive(Serialize, TypeDef)]
#[serde(rename = "Reactions", rename_all = "camelCase")]
pub struct JSONRPCReactions {
/// Map from a contact to it's reaction to message.
reactions_by_contact: BTreeMap<u32, Vec<String>>,
/// Unique reactions and their count, sorted in descending order.
reactions: Vec<JSONRPCReaction>,
/// Unique reactions and their count
reactions: BTreeMap<String, u32>,
}
impl From<Reactions> for JSONRPCReactions {
fn from(reactions: Reactions) -> Self {
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() {
let reaction = reactions.get(contact_id);
@@ -44,29 +30,18 @@ impl From<Reactions> for JSONRPCReactions {
.map(|emoji| emoji.to_owned())
.collect();
reactions_by_contact.insert(contact_id.to_u32(), emojis.clone());
}
let self_reactions = reactions_by_contact.get(&ContactId::SELF.to_u32());
let mut reactions_v = Vec::new();
for (emoji, count) in reactions.emoji_sorted_by_frequency() {
let is_from_self = if let Some(self_reactions) = self_reactions {
self_reactions.contains(&emoji)
} else {
false
};
let reaction = JSONRPCReaction {
emoji,
count,
is_from_self,
};
reactions_v.push(reaction)
for emoji in emojis {
if let Some(x) = unique_reactions.get_mut(&emoji) {
*x += 1;
} else {
unique_reactions.insert(emoji, 1);
}
}
}
JSONRPCReactions {
reactions_by_contact,
reactions: reactions_v,
reactions: unique_reactions,
}
}
}

View File

@@ -1,4 +1,5 @@
pub mod api;
pub use api::events;
pub use yerpc;
#[cfg(test)]

View File

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

View File

@@ -1,28 +1,33 @@
import * as T from "../generated/types.js";
import { EventType } from "../generated/types.js";
import * as RPC from "../generated/jsonrpc.js";
import { RawClient } from "../generated/client.js";
import { WebsocketTransport, BaseTransport, Request } from "yerpc";
import { TinyEmitter } from "@deltachat/tiny-emitter";
type Events = { ALL: (accountId: number, event: EventType) => void } & {
[Property in EventType["type"]]: (
type DCWireEvent<T extends Event> = {
event: T;
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,
event: Extract<EventType, { type: Property }>
event: Extract<Event, { type: Property }>
) => void;
};
type ContextEvents = { ALL: (event: EventType) => void } & {
[Property in EventType["type"]]: (
event: Extract<EventType, { type: Property }>
type ContextEvents = { ALL: (event: Event) => void } & {
[Property in Event["type"]]: (
event: Extract<Event, { type: Property }>
) => void;
};
export type DcEvent = EventType;
export type DcEventType<T extends EventType["type"]> = Extract<
EventType,
{ type: T }
>;
export type DcEvent = Event;
export type DcEventType<T extends Event["type"]> = Extract<Event, { type: T }>;
export class BaseDeltaChat<
Transport extends BaseTransport<any>
@@ -45,17 +50,16 @@ export class BaseDeltaChat<
async eventLoop(): Promise<void> {
while (true) {
const event = await this.rpc.getNextEvent();
//@ts-ignore
this.emit(event.event.type, event.contextId, event.event);
this.emit("ALL", event.contextId, event.event);
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.contextId]) {
this.contextEmitters[event.contextId].emit(
if (this.contextEmitters[event.context_id]) {
this.contextEmitters[event.context_id].emit(
event.event.type,
//@ts-ignore
event.event as any
);
this.contextEmitters[event.contextId].emit("ALL", event.event as any);
this.contextEmitters[event.context_id].emit("ALL", event.event as any);
}
}
}

View File

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

View File

@@ -79,7 +79,7 @@ class Rpc:
if self.closing:
return
event = await self.get_next_event()
account_id = event["contextId"]
account_id = event["context_id"]
queue = await self.get_queue(account_id)
await queue.put(event["event"])

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "1.115.0"
version = "1.114.0"
description = "DeltaChat JSON-RPC server"
edition = "2021"
readme = "README.md"

View File

@@ -41,7 +41,6 @@ skip = [
{ 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 = "sec1", version = "0.3.0" },
{ name = "sha2", version = "<0.10" },
{ name = "signature", version = "1.6.4" },

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.115.0"
"version": "1.114.0"
}

View File

@@ -74,9 +74,7 @@ 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
tox -c python --devenv env
. env/bin/activate
Inside this environment the bindings are installed

View File

@@ -703,7 +703,7 @@ def test_mdn_asymmetric(acfactory, lp):
assert len(chat.get_messages()) == 1
# Wait for the message to be marked as seen on IMAP.
ac1._evtracker.get_info_contains("Marked messages [0-9]+ in folder DeltaChat as seen.")
ac1._evtracker.get_info_contains("Marked messages 1 in folder DeltaChat as seen.")
# MDN is received even though MDNs are already disabled
assert msg_out.is_out_mdn_received()

View File

@@ -9,7 +9,6 @@ from deltachat.testplugin import (
create_dict_from_files_in_path,
write_dict_to_dir,
)
from deltachat.cutil import from_optional_dc_charpointer
# from deltachat.account import EventLogger
@@ -216,19 +215,3 @@ def test_logged_ac_process_ffi_failure(acfactory):
assert "ac_process_ffi_event" in res
assert "ZeroDivisionError" in res
assert "Traceback" in res
def test_jsonrpc_blocking_call(tmpdir):
accounts_fname = tmpdir.join("accounts")
accounts = ffi.gc(
lib.dc_accounts_new(ffi.NULL, accounts_fname.strpath.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"

View File

@@ -1 +1 @@
2023-05-12
2023-04-24

3
release.toml Normal file
View File

@@ -0,0 +1,3 @@
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

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

@@ -7,10 +7,16 @@ import pathlib
import re
import subprocess
from argparse import ArgumentParser
from pathlib import Path
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)
@@ -61,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")
@@ -96,14 +87,22 @@ def main():
today = datetime.date.today().isoformat()
if "alpha" not in newversion:
changelog_name = "CHANGELOG.md"
changelog_tmpname = changelog_name + ".tmp"
changelog_tmp = open(changelog_tmpname, "w")
found = False
for line in Path("CHANGELOG.md").open():
if line == f"## [{newversion}] - {today}\n":
for line in open(changelog_name):
## 1.25.0
if line == f"## [{newversion}]\n":
line = f"## [{newversion}] - {today}\n"
found = True
changelog_tmp.write(line)
if not found:
raise SystemExit(
f"{changelog_name} contains no entry for version: {newversion}"
)
changelog_tmp.close()
os.rename(changelog_tmpname, changelog_name)
for toml_filename in toml_list:
replace_toml_version(toml_filename, newversion)
@@ -121,15 +120,22 @@ def main():
subprocess.call(["git", "add", "-u"])
# subprocess.call(["cargo", "update", "-p", "deltachat"])
print("After commit, make sure to:")
print()
print("after commit, on master make sure to: ")
print("")
print(f" git tag -a v{newversion}")
print(f" git push origin v{newversion}")
print(f" gh release create v{newversion} -n ''")
print()
print("Merge release branch into `master` if the release")
print("is made on a stable branch.")
print()
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__":

View File

@@ -5147,11 +5147,9 @@ mod tests {
// Alice has an SMTP-server replacing the `Message-ID:`-header (as done eg. by outlook.com).
let sent_msg = alice.pop_sent_msg().await;
let msg = sent_msg.payload();
assert_eq!(msg.match_indices("Message-ID: <Gr.").count(), 1);
assert_eq!(msg.match_indices("References: <Gr.").count(), 1);
assert_eq!(msg.match_indices("Gr.").count(), 2);
let msg = msg.replace("Message-ID: <Gr.", "Message-ID: <XXX");
assert_eq!(msg.match_indices("Message-ID: <Gr.").count(), 0);
assert_eq!(msg.match_indices("References: <Gr.").count(), 1);
assert_eq!(msg.match_indices("Gr.").count(), 1);
// Bob receives this message, he may detect group by `References:`- or `Chat-Group:`-header
receive_imf(&bob, msg.as_bytes(), false).await.unwrap();

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

@@ -1324,8 +1324,8 @@ impl Imap {
// Fetch last DC_FETCH_EXISTING_MSGS_COUNT (100) messages.
// Sequence numbers are sequential. If there are 1000 messages in the inbox,
// we can fetch the sequence numbers 900-1000 and get the last 100 messages.
let first = cmp::max(1, exists - DC_FETCH_EXISTING_MSGS_COUNT + 1);
let set = format!("{first}:{exists}");
let first = cmp::max(1, exists - DC_FETCH_EXISTING_MSGS_COUNT);
let set = format!("{first}:*");
let mut list = session
.fetch(&set, PREFETCH_FLAGS)
.await

View File

@@ -537,9 +537,7 @@ impl Message {
/// Returns the size of the file in bytes, if applicable.
pub async fn get_filebytes(&self, context: &Context) -> Result<Option<u64>> {
if let Some(path) = self.param.get_path(Param::File, context)? {
Ok(Some(get_filebytes(context, &path).await.with_context(
|| format!("failed to get {} size in bytes", path.display()),
)?))
Ok(Some(get_filebytes(context, &path).await?))
} else {
Ok(None)
}
@@ -1088,26 +1086,6 @@ impl MessageState {
}
}
/// Returns contacts that sent read receipts and the time of reading.
pub async fn get_msg_read_receipts(
context: &Context,
msg_id: MsgId,
) -> Result<Vec<(ContactId, i64)>> {
context
.sql
.query_map(
"SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?",
(msg_id,),
|row| {
let contact_id: ContactId = row.get(0)?;
let ts: i64 = row.get(1)?;
Ok((contact_id, ts))
},
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
)
.await
}
/// Returns detailed message information in a multi-line text form.
pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> Result<String> {
let msg = Message::load_from_db(context, msg_id).await?;
@@ -1421,8 +1399,8 @@ pub async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Result<Vec<u8
/// by moving them to the trash chat
/// and scheduling for deletion on IMAP.
pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
for &msg_id in msg_ids {
let msg = Message::load_from_db(context, msg_id).await?;
for msg_id in msg_ids.iter() {
let msg = Message::load_from_db(context, *msg_id).await?;
if msg.location_id > 0 {
delete_poi_location(context, msg.location_id).await?;
}
@@ -1432,7 +1410,7 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
.with_context(|| format!("Unable to trash message {msg_id}"))?;
if msg.viewtype == Viewtype::Webxdc {
context.emit_event(EventType::WebxdcInstanceDeleted { msg_id });
context.emit_event(EventType::WebxdcInstanceDeleted { msg_id: *msg_id });
}
let target = context.get_delete_msgs_target().await?;
@@ -1452,7 +1430,7 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
.map(|dl| dl.msg_id);
if let Some(id) = logging_xdc_id {
if id == msg_id {
if id == *msg_id {
set_debug_logging_xdc(context, None).await?;
}
}

View File

@@ -15,7 +15,6 @@ use once_cell::sync::Lazy;
use crate::aheader::{Aheader, EncryptPreference};
use crate::blob::BlobObject;
use crate::config::Config;
use crate::constants::{DC_DESIRED_TEXT_LINES, DC_DESIRED_TEXT_LINE_LEN};
use crate::contact::{addr_cmp, addr_normalize, ContactId};
use crate::context::Context;
@@ -1115,22 +1114,15 @@ impl MimeMessage {
(simplified_txt, top_quote)
};
let is_bot = context.get_config_bool(Config::Bot).await?;
let simplified_txt = if is_bot {
simplified_txt
} else {
// Truncate text if it has too many lines
let (simplified_txt, was_truncated) = truncate_by_lines(
simplified_txt,
DC_DESIRED_TEXT_LINES,
DC_DESIRED_TEXT_LINE_LEN,
);
if was_truncated {
self.is_mime_modified = was_truncated;
}
simplified_txt
};
// Truncate text if it has too many lines
let (simplified_txt, was_truncated) = truncate_by_lines(
simplified_txt,
DC_DESIRED_TEXT_LINES,
DC_DESIRED_TEXT_LINE_LEN,
);
if was_truncated {
self.is_mime_modified = was_truncated;
}
if !simplified_txt.is_empty() || simplified_quote.is_some() {
let mut part = Part {
@@ -3298,37 +3290,24 @@ On 2020-10-25, Bob wrote:
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_mime_modified_large_plain() -> Result<()> {
async fn test_mime_modified_large_plain() {
let t = TestContext::new_alice().await;
static REPEAT_TXT: &str = "this text with 42 chars is just repeated.\n";
static REPEAT_CNT: usize = 2000; // results in a text of 84k, should be more than DC_DESIRED_TEXT_LEN
let long_txt = format!("From: alice@c.de\n\n{}", REPEAT_TXT.repeat(REPEAT_CNT));
let mimemsg = MimeMessage::from_bytes(&t, long_txt.as_ref(), None)
.await
.unwrap();
assert_eq!(long_txt.matches("just repeated").count(), REPEAT_CNT);
assert!(long_txt.len() > DC_DESIRED_TEXT_LEN);
{
let mimemsg = MimeMessage::from_bytes(&t, long_txt.as_ref(), None).await?;
assert!(mimemsg.is_mime_modified);
assert!(
mimemsg.parts[0].msg.matches("just repeated").count()
<= DC_DESIRED_TEXT_LEN / REPEAT_TXT.len()
);
assert!(mimemsg.parts[0].msg.len() <= DC_DESIRED_TEXT_LEN + DC_ELLIPSIS.len());
}
t.set_config(Config::Bot, Some("1")).await?;
{
let mimemsg = MimeMessage::from_bytes(&t, long_txt.as_ref(), None).await?;
assert!(!mimemsg.is_mime_modified);
assert_eq!(
format!("{}\n", mimemsg.parts[0].msg),
REPEAT_TXT.repeat(REPEAT_CNT)
);
}
Ok(())
assert!(mimemsg.is_mime_modified);
assert!(
mimemsg.parts[0].msg.matches("just repeated").count()
<= DC_DESIRED_TEXT_LEN / REPEAT_TXT.len()
);
assert!(mimemsg.parts[0].msg.len() <= DC_DESIRED_TEXT_LEN + DC_ELLIPSIS.len());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]

View File

@@ -14,7 +14,6 @@
//! possible to remove all reactions by sending an empty string as a reaction,
//! even though RFC 9078 requires at least one emoji to be sent.
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::fmt;
@@ -117,9 +116,10 @@ impl Reactions {
pub fn is_empty(&self) -> bool {
self.reactions.is_empty()
}
}
/// Returns a map from emojis to their frequencies.
pub fn emoji_frequencies(&self) -> BTreeMap<String, usize> {
impl fmt::Display for Reactions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut emoji_frequencies: BTreeMap<String, usize> = BTreeMap::new();
for reaction in self.reactions.values() {
for emoji in reaction.emojis() {
@@ -129,30 +129,6 @@ impl Reactions {
.or_insert(1);
}
}
emoji_frequencies
}
/// Returns a vector of emojis
/// sorted in descending order of frequencies.
///
/// This function can be used to display the reactions in
/// the message bubble in the UIs.
pub fn emoji_sorted_by_frequency(&self) -> Vec<(String, usize)> {
let mut emoji_frequencies: Vec<(String, usize)> =
self.emoji_frequencies().into_iter().collect();
emoji_frequencies.sort_by(|(a, a_count), (b, b_count)| {
match a_count.cmp(b_count).reverse() {
Ordering::Equal => a.cmp(b),
other => other,
}
});
emoji_frequencies
}
}
impl fmt::Display for Reactions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let emoji_frequencies = self.emoji_sorted_by_frequency();
let mut first = true;
for (emoji, frequency) in emoji_frequencies {
if !first {
@@ -505,11 +481,6 @@ Content-Disposition: reaction\n\
let reactions = get_msg_reactions(&alice, alice_msg.sender_msg_id).await?;
assert_eq!(reactions.to_string(), "👍2 😀1");
assert_eq!(
reactions.emoji_sorted_by_frequency(),
vec![("👍".to_string(), 2), ("😀".to_string(), 1)]
);
Ok(())
}

View File

@@ -2084,7 +2084,7 @@ async fn check_verified_properties(
) -> Result<()> {
let contact = Contact::load_from_db(context, from_id).await?;
ensure!(mimeparser.was_encrypted(), "This message is not encrypted");
ensure!(mimeparser.was_encrypted(), "This message is not encrypted.");
if mimeparser.get_header(HeaderDef::ChatVerified).is_none() {
// we do not fail here currently, this would exclude (a) non-deltas
@@ -2117,7 +2117,7 @@ async fn check_verified_properties(
if let Some(peerstate) = peerstate {
ensure!(
peerstate.has_verified_key(&mimeparser.signatures),
"The message was sent with non-verified encryption"
"The message was sent with non-verified encryption."
);
}
}

View File

@@ -639,8 +639,6 @@ async fn simple_imap_loop(
.await;
}
const THIRTY_YEARS: u64 = 30 * 365 * 24 * 60 * 60;
async fn smtp_loop(
ctx: Context,
started: oneshot::Sender<()>,
@@ -667,14 +665,7 @@ async fn smtp_loop(
loop {
if let Err(err) = send_smtp_messages(&ctx, &mut connection).await {
warn!(ctx, "send_smtp_messages failed: {:#}", err);
timeout = Some(timeout.map_or(30, |timeout: u64| {
let new_timeout = timeout.saturating_mul(3);
if new_timeout >= THIRTY_YEARS {
THIRTY_YEARS
} else {
new_timeout
}
}))
timeout = Some(timeout.map_or(30, |timeout: u64| timeout.saturating_mul(3)))
} else {
let duration_until_can_send = ctx.ratelimit.read().await.until_can_send();
if !duration_until_can_send.is_zero() {

View File

@@ -545,11 +545,11 @@ pub(crate) async fn send_msg_to_smtp(
.execute("DELETE FROM smtp WHERE id=?", (rowid,))
.await
.context("failed to remove message with exceeded retry limit from smtp table")?;
return Ok(());
bail!("Number of retries exceeded the limit");
}
info!(
context,
"Try number {retries} to send message {msg_id} (entry {rowid}) over SMTP"
"Try number {} to send message {} over SMTP", retries, msg_id
);
let recipients_list = recipients
@@ -573,13 +573,8 @@ pub(crate) async fn send_msg_to_smtp(
{
info!(
context,
"Sending of message {msg_id} (entry {rowid}) was cancelled by the user."
"Sending of message {} was cancelled by the user.", msg_id
);
context
.sql
.execute("DELETE FROM smtp WHERE id=?", (rowid,))
.await
.context("failed to remove cancelled message from smtp table")?;
return Ok(());
}
@@ -651,8 +646,6 @@ pub(crate) async fn send_smtp_messages(context: &Context, connection: &mut Smtp)
},
)
.await?;
info!(context, "Selected rows from SMTP queue: {rowids:?}.");
for rowid in rowids {
send_msg_to_smtp(context, connection, rowid)
.await

View File

@@ -64,11 +64,9 @@ impl Smtp {
if let Some(ref mut transport) = self.transport {
transport.send(mail).await.map_err(Error::SmtpSend)?;
let info_msg = format!(
"Message len={message_len_bytes} was SMTP-sent to {recipients_display}"
);
info!(context, "{info_msg}.");
context.emit_event(EventType::SmtpMessageSent(info_msg));
context.emit_event(EventType::SmtpMessageSent(format!(
"Message len={message_len_bytes} was smtp-sent to {recipients_display}"
)));
self.last_success = Some(std::time::SystemTime::now());
} else {
warn!(

View File

@@ -711,24 +711,6 @@ CREATE INDEX smtp_messageid ON imap(rfc724_mid);
)
.await?;
}
if dbversion < 101 {
// Recreate `smtp` table with autoincrement.
// rfc724_mid index is not recreated, because it is not used.
sql.execute_migration(
"DROP TABLE smtp;
CREATE TABLE smtp (
id INTEGER PRIMARY KEY AUTOINCREMENT,
rfc724_mid TEXT NOT NULL, -- Message-ID
mime TEXT NOT NULL, -- SMTP payload
msg_id INTEGER NOT NULL, -- ID of the message in `msgs` table
recipients TEXT NOT NULL, -- List of recipients separated by space
retries INTEGER NOT NULL DEFAULT 0 -- Number of failed attempts to send the message
);
",
101,
)
.await?;
}
let new_version = sql
.get_raw_config_int(VERSION_CFG)

View File

@@ -50,9 +50,6 @@ pub struct Summary {
/// Message state.
pub state: MessageState,
/// Message preview image path
pub thumbnail_path: Option<String>,
}
impl Summary {
@@ -93,22 +90,11 @@ impl Summary {
text = stock_str::reply_noun(context).await
}
let thumbnail_path = if msg.viewtype == Viewtype::Image
|| msg.viewtype == Viewtype::Gif
|| msg.viewtype == Viewtype::Sticker
{
msg.get_file(context)
.and_then(|path| path.to_str().map(|p| p.to_owned()))
} else {
None
};
Self {
prefix,
text,
timestamp: msg.get_timestamp(),
state: msg.state,
thumbnail_path,
}
}

View File

@@ -4,7 +4,6 @@ use std::convert::TryFrom;
use std::path::Path;
use anyhow::{anyhow, bail, ensure, format_err, Result};
use deltachat_derive::FromSql;
use lettre_email::mime;
use lettre_email::PartBuilder;
@@ -144,25 +143,17 @@ struct StatusUpdates {
/// Update items as sent on the wire and as stored in the database.
#[derive(Debug, Serialize, Deserialize)]
pub struct StatusUpdateItem {
/// The playload of the status update.
pub payload: Value,
pub(crate) struct StatusUpdateItem {
pub(crate) payload: Value,
/// Optional short info message that will be displayed in the chat.
/// For example "Alice added an item" or "Bob voted for option x".
#[serde(skip_serializing_if = "Option::is_none")]
pub info: Option<String>,
pub(crate) info: Option<String>,
/// The new name of the editing document.
/// This is not needed if the webxdc doesn't edit documents.
#[serde(skip_serializing_if = "Option::is_none")]
pub document: Option<String>,
pub(crate) document: Option<String>,
/// Optional summary of the status update which will be shown next to the
/// app icon. This should be short and can be something like "8 votes"
/// for a voting app.
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
pub(crate) summary: Option<String>,
}
/// Update items as passed to the UIs.
@@ -239,14 +230,14 @@ impl Context {
let valid = match async_zip::read::fs::ZipFileReader::new(path).await {
Ok(archive) => {
if find_zip_entry(archive.file(), "index.html").is_none() {
warn!(self, "{} misses index.html", filename);
info!(self, "{} misses index.html", filename);
false
} else {
true
}
}
Err(_) => {
warn!(self, "{} cannot be opened as zip-file", filename);
info!(self, "{} cannot be opened as zip-file", filename);
false
}
};
@@ -298,11 +289,23 @@ impl Context {
async fn create_status_update_record(
&self,
instance: &mut Message,
status_update_item: StatusUpdateItem,
update_str: &str,
timestamp: i64,
can_info_msg: bool,
from_id: ContactId,
) -> Result<StatusUpdateSerial> {
let update_str = strip_rtlo_characters(update_str.trim());
if update_str.is_empty() {
bail!("create_status_update_record: empty update.");
}
let status_update_item: StatusUpdateItem =
if let Ok(item) = serde_json::from_str::<StatusUpdateItem>(&update_str) {
item
} else {
bail!("create_status_update_record: no valid update item.");
};
if can_info_msg {
if let Some(ref info) = status_update_item.info {
if let Some(info_msg_id) =
@@ -401,26 +404,6 @@ impl Context {
instance_msg_id: MsgId,
update_str: &str,
descr: &str,
) -> Result<()> {
let status_update_item: StatusUpdateItem =
if let Ok(item) = serde_json::from_str::<StatusUpdateItem>(update_str) {
item
} else {
bail!("create_status_update_record: no valid update item.");
};
self.send_webxdc_status_update_struct(instance_msg_id, status_update_item, descr)
.await?;
Ok(())
}
/// Sends a status update for an webxdc instance.
/// Also see [Self::send_webxdc_status_update]
pub async fn send_webxdc_status_update_struct(
&self,
instance_msg_id: MsgId,
status_update: StatusUpdateItem,
descr: &str,
) -> Result<()> {
let mut instance = Message::load_from_db(self, instance_msg_id).await?;
if instance.viewtype != Viewtype::Webxdc {
@@ -437,10 +420,10 @@ impl Context {
MessageState::Undefined | MessageState::OutPreparing | MessageState::OutDraft
);
let status_update_serial: StatusUpdateSerial = self
let status_update_serial = self
.create_status_update_record(
&mut instance,
status_update,
update_str,
create_smeared_timestamp(self),
send_now,
ContactId::SELF,
@@ -569,7 +552,7 @@ impl Context {
for update_item in updates.updates {
self.create_status_update_record(
&mut instance,
update_item,
&serde_json::to_string(&update_item)?,
timestamp,
can_info_msg,
from_id,
@@ -818,8 +801,6 @@ impl Message {
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
use crate::chat::{
add_contact_to_chat, create_broadcast_list, create_group_chat, forward_msgs,
@@ -1254,12 +1235,7 @@ mod tests {
let update_id1 = t
.create_status_update_record(
&mut instance,
StatusUpdateItem {
payload: json!({"foo": "bar"}),
info: None,
document: None,
summary: None,
},
"\n\n{\"payload\": {\"foo\":\"bar\"}}\n",
1640178619,
true,
ContactId::SELF,
@@ -1272,15 +1248,19 @@ mod tests {
);
assert!(t
.send_webxdc_status_update(instance.id, "\n\n\n", "")
.create_status_update_record(&mut instance, "\n\n\n", 1640178619, true, ContactId::SELF)
.await
.is_err());
assert!(t
.send_webxdc_status_update(instance.id, "bad json", "")
.create_status_update_record(
&mut instance,
"bad json",
1640178619,
true,
ContactId::SELF
)
.await
.is_err());
assert_eq!(
t.get_webxdc_status_updates(instance.id, StatusUpdateSerial(0))
.await?,
@@ -1290,12 +1270,7 @@ mod tests {
let update_id2 = t
.create_status_update_record(
&mut instance,
StatusUpdateItem {
payload: json!({"foo2": "bar2"}),
info: None,
document: None,
summary: None,
},
r#"{"payload" : { "foo2":"bar2"}}"#,
1640178619,
true,
ContactId::SELF,
@@ -1307,12 +1282,7 @@ mod tests {
);
t.create_status_update_record(
&mut instance,
StatusUpdateItem {
payload: Value::Bool(true),
info: None,
document: None,
summary: None,
},
r#"{"payload":true}"#,
1640178619,
true,
ContactId::SELF,
@@ -1326,12 +1296,15 @@ mod tests {
{"payload":true,"serial":3,"max_serial":3}]"#
);
t.send_webxdc_status_update(
instance.id,
r#"{"payload" : 1, "sender": "that is not used"}"#,
"",
)
.await?;
let _update_id3 = t
.create_status_update_record(
&mut instance,
r#"{"payload" : 1, "sender": "that is not used"}"#,
1640178619,
true,
ContactId::SELF,
)
.await?;
assert_eq!(
t.get_webxdc_status_updates(instance.id, update_id2).await?,
r#"[{"payload":true,"serial":3,"max_serial":4},