Compare commits

..
Author SHA1 Message Date
link2xt 4d31be3165 feat: do not copy Chat-Version header into unprotected part 2025-10-02 11:19:00 +00:00
41 changed files with 248 additions and 437 deletions
+2 -2
View File
@@ -139,12 +139,12 @@ jobs:
- name: Tests
env:
RUST_BACKTRACE: 1
run: cargo nextest run --workspace --locked
run: cargo nextest run --workspace
- name: Doc-Tests
env:
RUST_BACKTRACE: 1
run: cargo test --workspace --locked --doc
run: cargo test --workspace --doc
- name: Test cargo vendor
run: cargo vendor
+4 -1
View File
@@ -24,7 +24,10 @@ jobs:
show-progress: false
persist-credentials: false
- uses: DeterminateSystems/nix-installer-action@main
- run: nix fmt flake.nix -- --check
- run: nix fmt
# Check that formatting does not change anything.
- run: git diff --exit-code
build:
name: nix build
+5 -47
View File
@@ -1,46 +1,5 @@
# Changelog
## [2.17.0] - 2025-10-04
### API-Changes
- [**breaking**] Remove deprecated verified_one_on_one_chats config.
### CI
- Require that Cargo.lock is up to date.
- Fix CI checking Nix formatting.
### Documentation
- Comment about outdated timespan.
- Clarify CALL events ([#7188](https://github.com/chatmail/core/pull/7188)).
- Add docs for JS `BaseDeltaChat`.
### Features / Changes
- Make `text/calendar` alternative available as an attachment.
- Better summary for calls.
- Add strings 'You left the channel.' and 'Scan to join Channel' ([#7266](https://github.com/chatmail/core/pull/7266)).
- Stock strings for calls.
- ffi: Add DC_STR_CANT_DECRYPT_OUTGOING_MSGS define.
### Fixes
- Prefer last part in `multipart/alternative`.
- Prefetch messages in limited batches ([#6915](https://github.com/chatmail/core/pull/6915)).
- Forward calls as text messages.
- Consistent spelling of "canceled" with a single "l".
- Lowercase "call" in "Missed call" and similar strings.
### Refactor
- Return the reason when failing to place calls.
### Tests
- Test reception of `multipart/alternative` with `text/calendar`.
## [2.16.0] - 2025-10-01
### API-Changes
@@ -1805,7 +1764,7 @@ This reverts commit 6f22ce2722b51773d7fbb0d89e4764f963cafd91..
### Fixes
- Reset quota on configured address change ([#5908](https://github.com/chatmail/core/pull/5908)).
- Do not emit progress 1000 when configuration is canceled.
- Do not emit progress 1000 when configuration is cancelled.
- Assume file extensions are 32 chars max and don't contain whitespace ([#5338](https://github.com/chatmail/core/pull/5338)).
- Re-add tokens.foreign_id column ([#6038](https://github.com/chatmail/core/pull/6038)).
@@ -4253,7 +4212,7 @@ Bugfix release attempting to fix the [iOS build error](https://github.com/chatma
- Recreate `smtp` table with AUTOINCREMENT `id` ([#4390](https://github.com/chatmail/core/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/chatmail/core/pull/4377)).
- Delete `smtp` rows when message sending is canceled ([#4391](https://github.com/chatmail/core/pull/4391)).
- Delete `smtp` rows when message sending is cancelled ([#4391](https://github.com/chatmail/core/pull/4391)).
### Refactor
@@ -4264,7 +4223,7 @@ Bugfix release attempting to fix the [iOS build error](https://github.com/chatma
### Fixes
- Fetch at most 100 existing messages even if EXISTS was not received.
- Delete `smtp` rows when message sending is canceled.
- Delete `smtp` rows when message sending is cancelled.
### Changes
@@ -4351,14 +4310,14 @@ Bugfix release attempting to fix the [iOS build error](https://github.com/chatma
## [1.112.3] - 2023-03-30
### Fixes
- `transfer::get_backup` now frees ongoing process when canceled. #4249
- `transfer::get_backup` now frees ongoing process when cancelled. #4249
## [1.112.2] - 2023-03-30
### Changes
- Update iroh, remove `default-net` from `[patch.crates-io]` section.
- transfer backup: Connect to multiple provider addresses concurrently. This should speed up connection time significantly on the getter side. #4240
- Make sure BackupProvider is canceled on drop (or `dc_backup_provider_unref`). The BackupProvider will now always finish with an IMEX event of 1000 or 0, previously it would sometimes finished with 1000 (success) when it really was 0 (failure). #4242
- Make sure BackupProvider is cancelled on drop (or `dc_backup_provider_unref`). The BackupProvider will now always finish with an IMEX event of 1000 or 0, previously it would sometimes finished with 1000 (success) when it really was 0 (failure). #4242
### Fixes
- Do not return media from trashed messages in the "All media" view. #4247
@@ -6856,4 +6815,3 @@ https://github.com/chatmail/core/pulls?q=is%3Apr+is%3Aclosed
[2.14.0]: https://github.com/chatmail/core/compare/v2.13.0..v2.14.0
[2.15.0]: https://github.com/chatmail/core/compare/v2.14.0..v2.15.0
[2.16.0]: https://github.com/chatmail/core/compare/v2.15.0..v2.16.0
[2.17.0]: https://github.com/chatmail/core/compare/v2.16.0..v2.17.0
+1 -1
View File
@@ -44,7 +44,7 @@ If you want to contribute a code, follow this guide.
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 canceled"
- `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`"
Generated
+8 -8
View File
@@ -1289,7 +1289,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "2.17.0"
version = "2.16.0"
dependencies = [
"anyhow",
"async-broadcast",
@@ -1399,7 +1399,7 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc"
version = "2.17.0"
version = "2.16.0"
dependencies = [
"anyhow",
"async-channel 2.5.0",
@@ -1421,7 +1421,7 @@ dependencies = [
[[package]]
name = "deltachat-repl"
version = "2.17.0"
version = "2.16.0"
dependencies = [
"anyhow",
"deltachat",
@@ -1437,7 +1437,7 @@ dependencies = [
[[package]]
name = "deltachat-rpc-server"
version = "2.17.0"
version = "2.16.0"
dependencies = [
"anyhow",
"deltachat",
@@ -1466,7 +1466,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "2.17.0"
version = "2.16.0"
dependencies = [
"anyhow",
"deltachat",
@@ -5289,11 +5289,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sdp"
version = "0.8.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd277015eada44a0bb810a4b84d3bf6e810573fa62fb442f457edf6a1087a69"
checksum = "32c374dceda16965d541c8800ce9cc4e1c14acfd661ddf7952feeedc3411e5c6"
dependencies = [
"rand 0.8.5",
"rand 0.9.0",
"substring",
"thiserror 1.0.69",
"url",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "2.17.0"
version = "2.16.0"
edition = "2024"
license = "MPL-2.0"
rust-version = "1.85"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "2.17.0"
version = "2.16.0"
description = "Deltachat FFI"
edition = "2018"
readme = "README.md"
+18 -72
View File
@@ -1222,7 +1222,7 @@ uint32_t dc_init_webxdc_integration (dc_context_t* context, uint32_t c
* Possible actions during ringing:
*
* - caller cancels the call using dc_end_call():
* callee receives #DC_EVENT_CALL_ENDED and has a "Missed call"
* callee receives #DC_EVENT_CALL_ENDED and has a "Missed Call"
*
* - callee accepts using dc_accept_incoming_call():
* caller receives #DC_EVENT_OUTGOING_CALL_ACCEPTED.
@@ -1230,20 +1230,19 @@ uint32_t dc_init_webxdc_integration (dc_context_t* context, uint32_t c
*
* - callee declines using dc_end_call():
* caller receives #DC_EVENT_CALL_ENDED and has a "Declinced Call".
* callee's other devices receive #DC_EVENT_CALL_ENDED and have a "Canceled Call",
* callee's other devices receive #DC_EVENT_CALL_ENDED and have a "Cancelled Call",
*
* - callee is already in a call:
* what to do depends on the capabilities of UI to handle calls.
* if UI cannot handle multiple calls, an easy approach would be to decline the new call automatically
* and make that visble to the user in the call, e.g. by a notification
* in this case, UI may decide to show a notification instead of ringing.
* otherwise, this is same as timeout
*
* - timeout:
* after 1 minute without action,
* caller and callee receive #DC_EVENT_CALL_ENDED
* to prevent endless ringing of callee
* in case caller got offline without being able to send cancellation message.
* for caller, this is a "Canceled call";
* for callee, this is a "Missed call"
* for caller, this is a "Cancelled Call";
* for callee, this is a "Missed Call"
*
* Actions during the call:
*
@@ -1253,13 +1252,6 @@ uint32_t dc_init_webxdc_integration (dc_context_t* context, uint32_t c
* - callee ends the call using dc_end_call():
* caller receives #DC_EVENT_CALL_ENDED
*
* Contact request handling:
*
* - placing or accepting calls implies accepting contact requests
*
* - ending a call does not accept a contact request;
* instead, the call will timeout on all affected devices.
*
* Note, that the events are for updating the call screen,
* possible status messages are added and updated as usual, including the known events.
* In the UI, the sorted chatlist is used as an overview about calls as well as messages.
@@ -1287,7 +1279,6 @@ uint32_t dc_place_outgoing_call (dc_context_t* context, uint32_t ch
* either #DC_EVENT_OUTGOING_CALL_ACCEPTED or #DC_EVENT_INCOMING_CALL_ACCEPTED.
*
* If the call is already accepted or ended, nothing happens.
* If the chat is a contact request, it is accepted implicitly.
*
* @memberof dc_context_t
* @param context The context object.
@@ -1308,12 +1299,7 @@ uint32_t dc_place_outgoing_call (dc_context_t* context, uint32_t ch
* Unaccepted calls ended by the callee are a "decline".
* If the call was accepted, this is a "hangup".
*
* All participant devices get informed about the ended call via #DC_EVENT_CALL_ENDED unless they are contact requests.
* For contact requests, the call times out on all other affected devices.
*
* If the message ID is wrong or does not exist for whatever reasons, nothing happens.
* Therefore, and for resilience, UI should remove the call UI directly when calling
* this function and not only on the event.
* All participant devices get informed about the ended call via #DC_EVENT_CALL_ENDED.
*
* If the call is already ended, nothing happens.
*
@@ -5728,18 +5714,6 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
/**
* Message indicating an incoming or outgoing call.
*
* These messages are created by dc_place_outgoing_call()
* and should be rendered by UI similar to text messages,
* maybe with some "phone icon" at the side.
*
* The message text is updated as needed
* and UI will be informed via #DC_EVENT_MSGS_CHANGED as usual.
*
* Do not start ringing when seeing this message;
* the mesage may belong e.g. to an old missed call.
*
* Instead, ringing should start on the event #DC_EVENT_INCOMING_CALL
*/
#define DC_MSG_CALL 71
@@ -6747,8 +6721,7 @@ void dc_event_unref(dc_event_t* event);
*
* Together with this event,
* a message of type #DC_MSG_CALL is added to the corresponding chat;
* this message is announced and updated by the usual event as #DC_EVENT_MSGS_CHANGED,
* there is usually no need to take care of this message from any of the CALL events.
* this message is announced and updated by the usual even as #DC_EVENT_MSGS_CHANGED.
*
* If user takes action, dc_accept_incoming_call() or dc_end_call() should be called.
*
@@ -6765,7 +6738,8 @@ void dc_event_unref(dc_event_t* event);
* The callee accepted an incoming call on this or another device using dc_accept_incoming_call().
* The caller gets the event #DC_EVENT_OUTGOING_CALL_ACCEPTED at the same time.
*
* UI usually only takes action in case call UI was opened before, otherwise the event should be ignored.
* The event is sent unconditionally when the corresponding message is received.
* UI should only take action in case call UI was opened before, otherwise the event should be ignored.
*
* @param data1 (int) msg_id ID of the message referring to the call
*/
@@ -6774,7 +6748,8 @@ void dc_event_unref(dc_event_t* event);
/**
* A call placed using dc_place_outgoing_call() was accepted by the callee using dc_accept_incoming_call().
*
* UI usually only takes action in case call UI was opened before, otherwise the event should be ignored.
* The event is sent unconditionally when the corresponding message is received.
* UI should only take action in case call UI was opened before, otherwise the event should be ignored.
*
* @param data1 (int) msg_id ID of the message referring to the call
* @param data2 (char*) accept_call_info, text passed to dc_accept_incoming_call()
@@ -6782,10 +6757,11 @@ void dc_event_unref(dc_event_t* event);
#define DC_EVENT_OUTGOING_CALL_ACCEPTED 2570
/**
* An incoming or outgoing call was ended using dc_end_call() on this or another device, by caller or callee.
* An incoming or outgoing call was ended using dc_end_call().
* Moreover, the event is sent when the call was not accepted within 1 minute timeout.
*
* UI usually only takes action in case call UI was opened before, otherwise the event should be ignored.
* The event is sent unconditionally when the corresponding message is received.
* UI should only take action in case call UI was opened before, otherwise the event should be ignored.
*
* @param data1 (int) msg_id ID of the message referring to the call
*/
@@ -7585,7 +7561,7 @@ void dc_event_unref(dc_event_t* event);
/// Used in status messages.
#define DC_STR_REMOVE_MEMBER_BY_OTHER 131
/// "You left the group."
/// "You left."
///
/// Used in status messages.
#define DC_STR_GROUP_LEFT_BY_YOU 132
@@ -7808,12 +7784,6 @@ void dc_event_unref(dc_event_t* event);
/// `%1$s` will be replaced by the provider's domain.
#define DC_STR_INVALID_UNENCRYPTED_MAIL 174
/// "⚠️ It seems you are using Delta Chat on multiple devices that cannot decrypt each other's outgoing messages. To fix this, on the older device use \"Settings / Add Second Device\" and follow the instructions."
///
/// Added to the device chat if could not decrypt a new outgoing message (i.e. not when fetching
/// existing messages). But no more than once a day.
#define DC_STR_CANT_DECRYPT_OUTGOING_MSGS 175
/// "You reacted %1$s to '%2$s'"
///
/// `%1$s` will be replaced by the reaction, usually an emoji
@@ -7850,32 +7820,8 @@ void dc_event_unref(dc_event_t* event);
/// "❤️ Seems you're enjoying Delta Chat!"… (donation request device message)
#define DC_STR_DONATION_REQUEST 193
/// "Outgoing call"
#define DC_STR_OUTGOING_CALL 194
/// "Incoming call"
#define DC_STR_INCOMING_CALL 195
/// "Declined call"
#define DC_STR_DECLINED_CALL 196
/// "Canceled call"
#define DC_STR_CANCELED_CALL 197
/// "Missed call"
#define DC_STR_MISSED_CALL 198
/// "You left the channel."
///
/// Used in status messages.
#define DC_STR_CHANNEL_LEFT_BY_YOU 200
/// "Scan to join channel %1$s"
///
/// Subtitle for channel join qrcode svg image generated by the core.
///
/// `%1$s` will be replaced with the channel name.
#define DC_STR_SECURE_JOIN_CHANNEL_QR_DESC 201
/// "Contact". Deprecated, currently unused.
#define DC_STR_CONTACT 200
/**
* @}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc"
version = "2.17.0"
version = "2.16.0"
description = "DeltaChat JSON-RPC API"
edition = "2021"
license = "MPL-2.0"
+3 -3
View File
@@ -1809,13 +1809,13 @@ impl CommandApi {
/// Offers a backup for remote devices to retrieve.
///
/// Can be canceled by stopping the ongoing process. Success or failure can be tracked
/// Can be cancelled by stopping the ongoing process. Success or failure can be tracked
/// via the `ImexProgress` event which should either reach `1000` for success or `0` for
/// failure.
///
/// This **stops IO** while it is running.
///
/// Returns once a remote device has retrieved the backup, or is canceled.
/// Returns once a remote device has retrieved the backup, or is cancelled.
async fn provide_backup(&self, account_id: u32) -> Result<()> {
let ctx = self.get_context(account_id).await?;
@@ -1881,7 +1881,7 @@ impl CommandApi {
/// This retrieves the backup from a remote device over the network and imports it into
/// the current device.
///
/// Can be canceled by stopping the ongoing process.
/// Can be cancelled by stopping the ongoing process.
///
/// Do not forget to call start_io on the account after a successful import,
/// otherwise it will not connect to the email server.
+6 -6
View File
@@ -20,7 +20,7 @@ pub struct JsonrpcCallInfo {
/// Call state.
///
/// For example, if the call is accepted, active, canceled, declined etc.
/// For example, if the call is accepted, active, cancelled, declined etc.
pub state: JsonrpcCallState,
}
@@ -68,13 +68,13 @@ pub enum JsonrpcCallState {
/// that was declined before the timeout.
Declined,
/// Outgoing call that has been canceled on our side
/// Outgoing call that has been cancelled on our side
/// before receiving a response.
///
/// Incoming calls cannot be canceled,
/// on the receiver side canceled calls
/// Incoming calls cannot be cancelled,
/// on the receiver side cancelled calls
/// usually result in missed calls.
Canceled,
Cancelled,
}
impl JsonrpcCallState {
@@ -87,7 +87,7 @@ impl JsonrpcCallState {
CallState::Completed { duration } => JsonrpcCallState::Completed { duration },
CallState::Missed => JsonrpcCallState::Missed,
CallState::Declined => JsonrpcCallState::Declined,
CallState::Canceled => JsonrpcCallState::Canceled,
CallState::Cancelled => JsonrpcCallState::Cancelled,
};
Ok(jsonrpc_call_state)
+1 -1
View File
@@ -54,5 +54,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "2.17.0"
"version": "2.16.0"
}
@@ -35,10 +35,6 @@ export class BaseDeltaChat<
constructor(
public transport: Transport,
/**
* Whether to start calling {@linkcode RawClient.getNextEvent}
* and emitting the respective events on this class.
*/
startEventLoop: boolean,
) {
super();
@@ -48,9 +44,6 @@ export class BaseDeltaChat<
}
}
/**
* @see the constructor's `startEventLoop`
*/
async eventLoop(): Promise<void> {
while (true) {
const event = await this.rpc.getNextEvent();
@@ -69,17 +62,10 @@ export class BaseDeltaChat<
}
}
/**
* @deprecated use {@linkcode BaseDeltaChat.rpc.getAllAccounts} instead.
*/
async listAccounts(): Promise<T.Account[]> {
return await this.rpc.getAllAccounts();
}
/**
* A convenience function to listen on events binned by `account_id`
* (see {@linkcode RawClient.getAllAccounts}).
*/
getContextEvents(account_id: number) {
if (this.contextEmitters[account_id]) {
return this.contextEmitters[account_id];
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "deltachat-repl"
version = "2.17.0"
version = "2.16.0"
license = "MPL-2.0"
edition = "2021"
repository = "https://github.com/chatmail/core"
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat-rpc-client"
version = "2.17.0"
version = "2.16.0"
description = "Python client for Delta Chat core JSON-RPC interface"
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -28,7 +28,9 @@ class ACFactory:
def get_unconfigured_account(self) -> Account:
"""Create a new unconfigured account."""
return self.deltachat.add_account()
account = self.deltachat.add_account()
account.set_config("verified_one_on_one_chats", "1")
return account
def get_unconfigured_bot(self) -> Bot:
"""Create a new unconfigured bot."""
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "2.17.0"
version = "2.16.0"
description = "DeltaChat JSON-RPC server"
edition = "2021"
readme = "README.md"
@@ -15,5 +15,5 @@
},
"type": "module",
"types": "index.d.ts",
"version": "2.17.0"
"version": "2.16.0"
}
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat"
version = "2.17.0"
version = "2.16.0"
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
readme = "README.rst"
requires-python = ">=3.8"
+1 -1
View File
@@ -1 +1 @@
2025-10-04
2025-10-01
+24 -28
View File
@@ -46,9 +46,9 @@ const STUN_PORT: u16 = 3478;
///
/// It is used to distinguish "ended" calls
/// that are rejected by us from the calls
/// canceled by the other side
/// cancelled by the other side
/// immediately after ringing started.
const CALL_CANCELED_TIMESTAMP: Param = Param::Arg2;
const CALL_CANCELLED_TIMESTAMP: Param = Param::Arg2;
/// Information about the status of a call.
#[derive(Debug, Default)]
@@ -123,14 +123,14 @@ impl CallInfo {
}
/// Returns true if the call is missed
/// because the caller canceled it
/// because the caller cancelled it
/// explicitly before ringing stopped.
///
/// For outgoing calls this means
/// the receiver has rejected the call
/// explicitly.
pub fn is_canceled(&self) -> bool {
self.msg.param.exists(CALL_CANCELED_TIMESTAMP)
pub fn is_cancelled(&self) -> bool {
self.msg.param.exists(CALL_CANCELLED_TIMESTAMP)
}
async fn mark_as_ended(&mut self, context: &Context) -> Result<()> {
@@ -139,17 +139,17 @@ impl CallInfo {
Ok(())
}
/// Explicitly mark the call as canceled.
/// Explicitly mark the call as cancelled.
///
/// For incoming calls this should be called
/// when "call ended" message is received
/// from the caller before we picked up the call.
/// In this case the call becomes "missed" early
/// before the ringing timeout.
async fn mark_as_canceled(&mut self, context: &Context) -> Result<()> {
async fn mark_as_cancelled(&mut self, context: &Context) -> Result<()> {
let now = time();
self.msg.param.set_i64(CALL_ENDED_TIMESTAMP, now);
self.msg.param.set_i64(CALL_CANCELED_TIMESTAMP, now);
self.msg.param.set_i64(CALL_CANCELLED_TIMESTAMP, now);
self.msg.update_param(context).await?;
Ok(())
}
@@ -183,11 +183,7 @@ impl Context {
place_call_info: String,
) -> Result<MsgId> {
let chat = Chat::load_from_db(self, chat_id).await?;
ensure!(
chat.typ == Chattype::Single,
"Can only place calls in 1:1 chats"
);
ensure!(!chat.is_self_talk(), "Cannot call self");
ensure!(chat.typ == Chattype::Single && !chat.is_self_talk());
let mut call = Message {
viewtype: Viewtype::Call,
@@ -259,8 +255,8 @@ impl Context {
call.mark_as_ended(self).await?;
call.update_text(self, "Declined call").await?;
} else {
call.mark_as_canceled(self).await?;
call.update_text(self, "Canceled call").await?;
call.mark_as_cancelled(self).await?;
call.update_text(self, "Cancelled call").await?;
}
} else {
call.mark_as_ended(self).await?;
@@ -294,11 +290,11 @@ impl Context {
let mut call = context.load_call_by_id(call_id).await?;
if !call.is_accepted() && !call.is_ended() {
if call.is_incoming() {
call.mark_as_canceled(&context).await?;
call.mark_as_cancelled(&context).await?;
call.update_text(&context, "Missed call").await?;
} else {
call.mark_as_ended(&context).await?;
call.update_text(&context, "Canceled call").await?;
call.update_text(&context, "Cancelled call").await?;
}
context.emit_msgs_changed(call.msg.chat_id, call_id);
context.emit_event(EventType::CallEnded {
@@ -390,14 +386,14 @@ impl Context {
call.mark_as_ended(self).await?;
call.update_text(self, "Declined call").await?;
} else {
call.mark_as_canceled(self).await?;
call.mark_as_cancelled(self).await?;
call.update_text(self, "Missed call").await?;
}
} else {
// outgoing
if from_id == ContactId::SELF {
call.mark_as_canceled(self).await?;
call.update_text(self, "Canceled call").await?;
call.mark_as_cancelled(self).await?;
call.update_text(self, "Cancelled call").await?;
} else {
call.mark_as_ended(self).await?;
call.update_text(self, "Declined call").await?;
@@ -487,13 +483,13 @@ pub enum CallState {
/// that was declined before the timeout.
Declined,
/// Outgoing call that has been canceled on our side
/// Outgoing call that has been cancelled on our side
/// before receiving a response.
///
/// Incoming calls cannot be canceled,
/// on the receiver side canceled calls
/// Incoming calls cannot be cancelled,
/// on the receiver side cancelled calls
/// usually result in missed calls.
Canceled,
Cancelled,
}
/// Returns call state given the message ID.
@@ -508,8 +504,8 @@ pub async fn call_state(context: &Context, msg_id: MsgId) -> Result<CallState> {
} else {
CallState::Active
}
} else if call.is_canceled() {
// Call was explicitly canceled
} else if call.is_cancelled() {
// Call was explicitly cancelled
// by the caller before we picked it up.
CallState::Missed
} else if call.is_ended() {
@@ -527,8 +523,8 @@ pub async fn call_state(context: &Context, msg_id: MsgId) -> Result<CallState> {
} else {
CallState::Active
}
} else if call.is_canceled() {
CallState::Canceled
} else if call.is_cancelled() {
CallState::Cancelled
} else if call.is_ended() || call.is_stale() {
CallState::Declined
} else {
+4 -40
View File
@@ -1,5 +1,4 @@
use super::*;
use crate::chat::forward_msgs;
use crate::config::Config;
use crate::test_utils::{TestContext, TestContextManager};
@@ -369,7 +368,7 @@ async fn test_caller_cancels_call() -> Result<()> {
// Alice changes their mind before Bob picks up
alice.end_call(alice_call.id).await?;
assert_text(&alice, alice_call.id, "Canceled call").await?;
assert_text(&alice, alice_call.id, "Cancelled call").await?;
alice
.evtracker
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
@@ -377,18 +376,18 @@ async fn test_caller_cancels_call() -> Result<()> {
let sent3 = alice.pop_sent_msg().await;
assert_eq!(
call_state(&alice, alice_call.id).await?,
CallState::Canceled
CallState::Cancelled
);
alice2.recv_msg_trash(&sent3).await;
assert_text(&alice2, alice2_call.id, "Canceled call").await?;
assert_text(&alice2, alice2_call.id, "Cancelled call").await?;
alice2
.evtracker
.get_matching(|evt| matches!(evt, EventType::CallEnded { .. }))
.await;
assert_eq!(
call_state(&alice2, alice2_call.id).await?,
CallState::Canceled
CallState::Cancelled
);
// Bob receives the ending message
@@ -399,11 +398,6 @@ async fn test_caller_cancels_call() -> Result<()> {
.await;
assert_eq!(call_state(&bob, bob_call.id).await?, CallState::Missed);
// Test that message summary says it is a missed call.
let bob_call_msg = Message::load_from_db(&bob, bob_call.id).await?;
let summary = bob_call_msg.get_summary(&bob, None).await?;
assert_eq!(summary.text, "📞 Missed call");
bob2.recv_msg_trash(&sent3).await;
assert_text(&bob2, bob2_call.id, "Missed call").await?;
bob2.evtracker
@@ -499,33 +493,3 @@ async fn test_sdp_has_video() {
assert_eq!(sdp_has_video(PLACE_INFO).unwrap(), false);
assert_eq!(sdp_has_video(PLACE_INFO_VIDEO).unwrap(), true);
}
/// Tests that calls are forwarded as text messages.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_forward_call() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
let charlie = &tcm.charlie().await;
let alice_bob_chat = alice.create_chat(bob).await;
let alice_msg_id = alice
.place_outgoing_call(alice_bob_chat.id, PLACE_INFO.to_string())
.await
.context("Failed to place a call")?;
let alice_call = Message::load_from_db(alice, alice_msg_id).await?;
let _alice_sent_call = alice.pop_sent_msg().await;
assert_eq!(alice_call.viewtype, Viewtype::Call);
let alice_charlie_chat = alice.create_chat(charlie).await;
forward_msgs(alice, &[alice_call.id], alice_charlie_chat.id).await?;
let alice_forwarded_call = alice.pop_sent_msg().await;
let alice_forwarded_call_msg = alice_forwarded_call.load_from_db().await;
assert_eq!(alice_forwarded_call_msg.viewtype, Viewtype::Text);
let charlie_forwarded_call = charlie.recv_msg(&alice_forwarded_call).await;
assert_eq!(charlie_forwarded_call.viewtype, Viewtype::Text);
Ok(())
}
+5 -15
View File
@@ -4207,7 +4207,7 @@ pub async fn remove_contact_from_chat(
if chat.typ == Chattype::Group && chat.is_promoted() {
let addr = contact.get_addr();
let res = send_member_removal_msg(context, &chat, contact_id, addr).await;
let res = send_member_removal_msg(context, chat_id, contact_id, addr).await;
if contact_id == ContactId::SELF {
res?;
@@ -4231,7 +4231,7 @@ pub async fn remove_contact_from_chat(
// For incoming broadcast channels, it's not possible to remove members,
// but it's possible to leave:
let self_addr = context.get_primary_self_addr().await?;
send_member_removal_msg(context, &chat, contact_id, &self_addr).await?;
send_member_removal_msg(context, chat_id, contact_id, &self_addr).await?;
} else {
bail!("Cannot remove members from non-group chats.");
}
@@ -4241,18 +4241,14 @@ pub async fn remove_contact_from_chat(
async fn send_member_removal_msg(
context: &Context,
chat: &Chat,
chat_id: ChatId,
contact_id: ContactId,
addr: &str,
) -> Result<MsgId> {
let mut msg = Message::new(Viewtype::Text);
if contact_id == ContactId::SELF {
if chat.typ == Chattype::InBroadcast {
msg.text = stock_str::msg_you_left_broadcast(context).await;
} else {
msg.text = stock_str::msg_group_left_local(context, ContactId::SELF).await;
}
msg.text = stock_str::msg_group_left_local(context, ContactId::SELF).await;
} else {
msg.text = stock_str::msg_del_member_local(context, contact_id, ContactId::SELF).await;
}
@@ -4262,7 +4258,7 @@ async fn send_member_removal_msg(
msg.param
.set(Param::ContactAddedRemoved, contact_id.to_u32());
send_msg(context, chat.id, &mut msg).await
send_msg(context, chat_id, &mut msg).await
}
async fn set_group_explicitly_left(context: &Context, grpid: &str) -> Result<()> {
@@ -4450,10 +4446,6 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId)
.set_int(Param::Forwarded, src_msg_id.to_u32() as i32);
}
if msg.get_viewtype() == Viewtype::Call {
msg.viewtype = Viewtype::Text;
}
msg.param.remove(Param::GuaranteeE2ee);
msg.param.remove(Param::ForcePlaintext);
msg.param.remove(Param::Cmd);
@@ -4463,8 +4455,6 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId)
msg.param.remove(Param::WebxdcSummary);
msg.param.remove(Param::WebxdcSummaryTimestamp);
msg.param.remove(Param::IsEdited);
msg.param.remove(Param::WebrtcRoom);
msg.param.remove(Param::WebrtcAccepted);
msg.in_reply_to = None;
// do not leak data as group names; a default subject is generated by mimefactory
+5 -2
View File
@@ -3026,7 +3026,7 @@ async fn test_leave_broadcast() -> Result<()> {
}
/// Tests that if Bob leaves a broadcast channel with one device,
/// the other device shows a correct info message "You left the channel.".
/// the other device shows a correct info message "You left.".
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_leave_broadcast_multidevice() -> Result<()> {
let mut tcm = TestContextManager::new();
@@ -3061,7 +3061,10 @@ async fn test_leave_broadcast_multidevice() -> Result<()> {
assert_eq!(rcvd.chat_id, bob1_hello.chat_id);
assert!(rcvd.is_info());
assert_eq!(rcvd.get_info_type(), SystemMessage::MemberRemovedFromGroup);
assert_eq!(rcvd.text, stock_str::msg_you_left_broadcast(bob1).await);
assert_eq!(
rcvd.text,
stock_str::msg_group_left_local(bob1, ContactId::SELF).await
);
Ok(())
}
+10
View File
@@ -413,6 +413,16 @@ pub enum Config {
#[strum(props(default = "172800"))]
GossipPeriod,
/// Deprecated 2025-07. Feature flag for verified 1:1 chats; the UI should set it
/// to 1 if it supports verified 1:1 chats.
/// Regardless of this setting, `chat.is_protected()` returns true while the key is verified,
/// and when the key changes, an info message is posted into the chat.
/// 0=Nothing else happens when the key changes.
/// 1=After the key changed, `can_send()` returns false
/// until `chat_id.accept()` is called.
#[strum(props(default = "0"))]
VerifiedOneOnOneChats,
/// Row ID of the key in the `keypairs` table
/// used for signatures, encryption to self and included in `Autocrypt` header.
KeyId,
+1 -1
View File
@@ -137,7 +137,7 @@ impl Context {
let res = self
.inner_configure(param)
.race(cancel_channel.recv().map(|_| Err(format_err!("Canceled"))))
.race(cancel_channel.recv().map(|_| Err(format_err!("Cancelled"))))
.await;
self.free_ongoing().await;
-1
View File
@@ -98,7 +98,6 @@ pub(crate) const DC_RESEND_USER_AVATAR_DAYS: i64 = 14;
// reference is the release date.
// as not all system get speedy updates,
// do not use too small value that will annoy users checking for nonexistent updates.
// "90 days" has proven to be too short at some point (user were informed but there was no update)
pub(crate) const DC_OUTDATED_WARNING_DAYS: i64 = 183;
/// messages that should be deleted get this chat_id; the messages are deleted from the working thread later then. This is also needed as rfc724_mid should be preset as long as the message is not deleted on the server (otherwise it is downloaded again)
+6
View File
@@ -1054,6 +1054,12 @@ impl Context {
"gossip_period",
self.get_config_int(Config::GossipPeriod).await?.to_string(),
);
res.insert(
"verified_one_on_one_chats", // deprecated 2025-07
self.get_config_bool(Config::VerifiedOneOnOneChats)
.await?
.to_string(),
);
res.insert(
"webxdc_realtime_enabled",
self.get_config_bool(Config::WebxdcRealtimeEnabled)
+6 -38
View File
@@ -566,38 +566,10 @@ impl Imap {
}
session.new_mail = false;
let mut read_cnt = 0;
loop {
let (n, fetch_more) = self
.fetch_new_msg_batch(context, session, folder, folder_meaning)
.await?;
read_cnt += n;
if !fetch_more {
return Ok(read_cnt > 0);
}
}
}
/// Returns number of messages processed and whether the function should be called again.
async fn fetch_new_msg_batch(
&mut self,
context: &Context,
session: &mut Session,
folder: &str,
folder_meaning: FolderMeaning,
) -> Result<(usize, bool)> {
let uid_validity = get_uidvalidity(context, folder).await?;
let old_uid_next = get_uid_next(context, folder).await?;
info!(
context,
"fetch_new_msg_batch({folder}): UIDVALIDITY={uid_validity}, UIDNEXT={old_uid_next}."
);
let uids_to_prefetch = 500;
let msgs = session
.prefetch(old_uid_next, uids_to_prefetch)
.await
.context("prefetch")?;
let msgs = session.prefetch(old_uid_next).await.context("prefetch")?;
let read_cnt = msgs.len();
let download_limit = context.download_limit().await?;
@@ -757,8 +729,7 @@ impl Imap {
largest_uid_fetched
};
let actually_download_messages_future = async {
let sender = sender;
let actually_download_messages_future = async move {
let mut uids_fetch_in_batch = Vec::with_capacity(max(uids_fetch.len(), 1));
let mut fetch_partially = false;
uids_fetch.push((0, !uids_fetch.last().unwrap_or(&(0, false)).1));
@@ -793,17 +764,14 @@ impl Imap {
// if the message has arrived after selecting mailbox
// and determining its UIDNEXT and before prefetch.
let mut new_uid_next = largest_uid_fetched + 1;
let fetch_more = fetch_res.is_ok() && {
let prefetch_uid_next = old_uid_next + uids_to_prefetch;
if fetch_res.is_ok() {
// If we have successfully fetched all messages we planned during prefetch,
// then we have covered at least the range between old UIDNEXT
// and UIDNEXT of the mailbox at the time of selecting it.
new_uid_next = max(new_uid_next, min(prefetch_uid_next, mailbox_uid_next));
new_uid_next = max(new_uid_next, mailbox_uid_next);
new_uid_next = max(new_uid_next, largest_uid_skipped.unwrap_or(0) + 1);
prefetch_uid_next < mailbox_uid_next
};
}
if new_uid_next > old_uid_next {
set_uid_next(context, folder, new_uid_next).await?;
}
@@ -820,7 +788,7 @@ impl Imap {
// establish a new session if this one is broken.
fetch_res?;
Ok((read_cnt, fetch_more))
Ok(read_cnt > 0)
}
/// Read the recipients from old emails sent by the user and add them as contacts.
+13 -6
View File
@@ -110,16 +110,14 @@ impl Session {
Ok(list)
}
/// Prefetch `n_uids` messages starting from `uid_next`. Returns a list of fetch results in the
/// order of ascending delivery time to the server (INTERNALDATE).
/// Prefetch all messages greater than or equal to `uid_next`. Returns a list of fetch results
/// in the order of ascending delivery time to the server (INTERNALDATE).
pub(crate) async fn prefetch(
&mut self,
uid_next: u32,
n_uids: u32,
) -> Result<Vec<(u32, async_imap::types::Fetch)>> {
let uid_last = uid_next.saturating_add(n_uids - 1);
// fetch messages with larger UID than the last one seen
let set = format!("{uid_next}:{uid_last}");
let set = format!("{uid_next}:*");
let mut list = self
.uid_fetch(set, PREFETCH_FLAGS)
.await
@@ -128,7 +126,16 @@ impl Session {
let mut msgs = BTreeMap::new();
while let Some(msg) = list.try_next().await? {
if let Some(msg_uid) = msg.uid {
msgs.insert((msg.internal_date(), msg_uid), msg);
// If the mailbox is not empty, results always include
// at least one UID, even if last_seen_uid+1 is past
// the last UID in the mailbox. It happens because
// uid:* is interpreted the same way as *:uid.
// See <https://tools.ietf.org/html/rfc3501#page-61> for
// standard reference. Therefore, sometimes we receive
// already seen messages and have to filter them out.
if msg_uid >= uid_next {
msgs.insert((msg.internal_date(), msg_uid), msg);
}
}
}
+63 -44
View File
@@ -928,56 +928,75 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_export_and_import_backup() -> Result<()> {
let backup_dir = tempfile::tempdir().unwrap();
for set_verified_oneonone_chats in [true, false] {
let backup_dir = tempfile::tempdir().unwrap();
let context1 = TestContext::new_alice().await;
assert!(context1.is_configured().await?);
let context1 = TestContext::new_alice().await;
assert!(context1.is_configured().await?);
if set_verified_oneonone_chats {
context1
.set_config_bool(Config::VerifiedOneOnOneChats, true)
.await?;
}
let context2 = TestContext::new().await;
assert!(!context2.is_configured().await?);
assert!(has_backup(&context2, backup_dir.path()).await.is_err());
let context2 = TestContext::new().await;
assert!(!context2.is_configured().await?);
assert!(has_backup(&context2, backup_dir.path()).await.is_err());
// export from context1
assert!(
imex(&context1, ImexMode::ExportBackup, backup_dir.path(), None)
// export from context1
assert!(
imex(&context1, ImexMode::ExportBackup, backup_dir.path(), None)
.await
.is_ok()
);
let _event = context1
.evtracker
.get_matching(|evt| matches!(evt, EventType::ImexProgress(1000)))
.await;
// import to context2
let backup = has_backup(&context2, backup_dir.path()).await?;
// Import of unencrypted backup with incorrect "foobar" backup passphrase fails.
assert!(
imex(
&context2,
ImexMode::ImportBackup,
backup.as_ref(),
Some("foobar".to_string())
)
.await
.is_ok()
);
let _event = context1
.evtracker
.get_matching(|evt| matches!(evt, EventType::ImexProgress(1000)))
.await;
.is_err()
);
// import to context2
let backup = has_backup(&context2, backup_dir.path()).await?;
assert!(
imex(&context2, ImexMode::ImportBackup, backup.as_ref(), None)
.await
.is_ok()
);
let _event = context2
.evtracker
.get_matching(|evt| matches!(evt, EventType::ImexProgress(1000)))
.await;
// Import of unencrypted backup with incorrect "foobar" backup passphrase fails.
assert!(
imex(
&context2,
ImexMode::ImportBackup,
backup.as_ref(),
Some("foobar".to_string())
)
.await
.is_err()
);
assert!(
imex(&context2, ImexMode::ImportBackup, backup.as_ref(), None)
.await
.is_ok()
);
let _event = context2
.evtracker
.get_matching(|evt| matches!(evt, EventType::ImexProgress(1000)))
.await;
assert!(context2.is_configured().await?);
assert_eq!(
context2.get_config(Config::Addr).await?,
Some("alice@example.org".to_string())
);
assert!(context2.is_configured().await?);
assert_eq!(
context2.get_config(Config::Addr).await?,
Some("alice@example.org".to_string())
);
assert_eq!(
context2
.get_config_bool(Config::VerifiedOneOnOneChats)
.await?,
false
);
assert_eq!(
context1
.get_config_bool(Config::VerifiedOneOnOneChats)
.await?,
set_verified_oneonone_chats
);
}
Ok(())
}
+4 -4
View File
@@ -242,7 +242,7 @@ impl BackupProvider {
if let Err(err) = Self::handle_connection(context.clone(), conn, auth_token, dbfile).race(
async {
cancel_token.recv().await.ok();
Err(format_err!("Backup transfer canceled"))
Err(format_err!("Backup transfer cancelled"))
}
).race(
async {
@@ -262,12 +262,12 @@ impl BackupProvider {
}
},
_ = cancel_token.recv() => {
info!(context, "Backup transfer canceled by the user, stopping accept loop.");
info!(context, "Backup transfer cancelled by the user, stopping accept loop.");
context.emit_event(EventType::ImexProgress(0));
break;
}
_ = drop_token.cancelled() => {
info!(context, "Backup transfer canceled by dropping the provider, stopping accept loop.");
info!(context, "Backup transfer cancelled by dropping the provider, stopping accept loop.");
context.emit_event(EventType::ImexProgress(0));
break;
}
@@ -364,7 +364,7 @@ pub async fn get_backup(context: &Context, qr: Qr) -> Result<()> {
let res = get_backup2(context, node_addr, auth_token)
.race(async {
cancel_token.recv().await.ok();
Err(format_err!("Backup reception canceled"))
Err(format_err!("Backup reception cancelled"))
})
.await;
if let Err(ref res) = res {
-1
View File
@@ -1019,7 +1019,6 @@ impl MimeFactory {
| "in-reply-to"
| "references"
| "auto-submitted"
| "chat-version"
| "autocrypt-setup-message" => {
unprotected_headers.push(header.clone());
}
+2 -12
View File
@@ -1,6 +1,6 @@
//! # QR code generation module.
use anyhow::{Result, bail};
use anyhow::Result;
use base64::Engine as _;
use qrcodegen::{QrCode, QrCodeEcc};
@@ -108,18 +108,8 @@ async fn generate_join_group_qr_code(context: &Context, chat_id: ChatId) -> Resu
None => None,
};
let qrcode_description = match chat.typ {
crate::constants::Chattype::Group => {
stock_str::secure_join_group_qr_description(context, &chat).await
}
crate::constants::Chattype::OutBroadcast => {
stock_str::secure_join_broadcast_qr_description(context, &chat).await
}
_ => bail!("Unexpected chat type {}", chat.typ),
};
inner_generate_secure_join_qr_code(
&qrcode_description,
&stock_str::secure_join_group_qr_description(context, &chat).await,
&securejoin::get_securejoin_qr(context, Some(chat_id)).await?,
&color_int_to_hex_string(chat.get_color(context).await?),
avatar,
+2 -1
View File
@@ -3532,7 +3532,8 @@ async fn apply_in_broadcast_changes(
// The only member added/removed message that is ever sent is "I left.",
// so, this is the only case we need to handle here
if from_id == ContactId::SELF {
better_msg.get_or_insert(stock_str::msg_you_left_broadcast(context).await);
better_msg
.get_or_insert(stock_str::msg_group_left_local(context, ContactId::SELF).await);
}
}
+10
View File
@@ -72,6 +72,11 @@ async fn test_setup_contact_ex(case: SetupContactCase) {
}
_ => alice_auto_submitted_hdr = "Auto-Submitted: auto-replied",
};
for t in [&alice, &bob] {
t.set_config_bool(Config::VerifiedOneOnOneChats, true)
.await
.unwrap();
}
assert_eq!(
Chatlist::try_load(&alice, 0, None, None)
@@ -697,6 +702,11 @@ async fn test_lost_contact_confirm() {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
for t in [&alice, &bob] {
t.set_config_bool(Config::VerifiedOneOnOneChats, true)
.await
.unwrap();
}
let qr = get_securejoin_qr(&alice, None).await.unwrap();
join_securejoin(&bob.ctx, &qr).await.unwrap();
+3 -61
View File
@@ -279,7 +279,7 @@ pub enum StockMessage {
#[strum(props(fallback = "Member %1$s removed by %2$s."))]
MsgDelMemberBy = 131,
#[strum(props(fallback = "You left the group."))]
#[strum(props(fallback = "You left."))]
MsgYouLeftGroup = 132,
#[strum(props(fallback = "Group left by %1$s."))]
@@ -424,27 +424,6 @@ Help keeping us to keep Delta Chat independent and make it more awesome in the f
https://delta.chat/donate"))]
DonationRequest = 193,
#[strum(props(fallback = "Outgoing call"))]
OutgoingCall = 194,
#[strum(props(fallback = "Incoming call"))]
IncomingCall = 195,
#[strum(props(fallback = "Declined call"))]
DeclinedCall = 196,
#[strum(props(fallback = "Canceled call"))]
CanceledCall = 197,
#[strum(props(fallback = "Missed call"))]
MissedCall = 198,
#[strum(props(fallback = "You left the channel."))]
MsgYouLeftBroadcast = 200,
#[strum(props(fallback = "Scan to join channel %1$s"))]
SecureJoinBrodcastQRDescription = 201,
}
impl StockMessage {
@@ -717,7 +696,7 @@ pub(crate) async fn msg_group_left_remote(context: &Context) -> String {
translated(context, StockMessage::MsgILeftGroup).await
}
/// Stock string: `You left the group.` or `Group left by %1$s.`.
/// Stock string: `You left.` or `Group left by %1$s.`.
pub(crate) async fn msg_group_left_local(context: &Context, by_contact: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouLeftGroup).await
@@ -728,11 +707,6 @@ pub(crate) async fn msg_group_left_local(context: &Context, by_contact: ContactI
}
}
/// Stock string: `You left the channel.`
pub(crate) async fn msg_you_left_broadcast(context: &Context) -> String {
translated(context, StockMessage::MsgYouLeftBroadcast).await
}
/// Stock string: `You reacted %1$s to "%2$s"` or `%1$s reacted %2$s to "%3$s"`.
pub(crate) async fn msg_reacted(
context: &Context,
@@ -827,31 +801,6 @@ pub(crate) async fn donation_request(context: &Context) -> String {
translated(context, StockMessage::DonationRequest).await
}
/// Stock string: `Outgoing call`.
pub(crate) async fn outgoing_call(context: &Context) -> String {
translated(context, StockMessage::OutgoingCall).await
}
/// Stock string: `Incoming call`.
pub(crate) async fn incoming_call(context: &Context) -> String {
translated(context, StockMessage::IncomingCall).await
}
/// Stock string: `Declined call`.
pub(crate) async fn declined_call(context: &Context) -> String {
translated(context, StockMessage::DeclinedCall).await
}
/// Stock string: `Canceled call`.
pub(crate) async fn canceled_call(context: &Context) -> String {
translated(context, StockMessage::CanceledCall).await
}
/// Stock string: `Missed call`.
pub(crate) async fn missed_call(context: &Context) -> String {
translated(context, StockMessage::MissedCall).await
}
/// Stock string: `Scan to chat with %1$s`.
pub(crate) async fn setup_contact_qr_description(
context: &Context,
@@ -868,20 +817,13 @@ pub(crate) async fn setup_contact_qr_description(
.replace1(&name)
}
/// Stock string: `Scan to join group %1$s`.
/// Stock string: `Scan to join %1$s`.
pub(crate) async fn secure_join_group_qr_description(context: &Context, chat: &Chat) -> String {
translated(context, StockMessage::SecureJoinGroupQRDescription)
.await
.replace1(chat.get_name())
}
/// Stock string: `Scan to join channel %1$s`.
pub(crate) async fn secure_join_broadcast_qr_description(context: &Context, chat: &Chat) -> String {
translated(context, StockMessage::SecureJoinBrodcastQRDescription)
.await
.replace1(chat.get_name())
}
/// Stock string: `%1$s verified.`.
#[allow(dead_code)]
pub(crate) async fn contact_verified(context: &Context, contact: &Contact) -> String {
+4 -15
View File
@@ -4,7 +4,6 @@ use std::borrow::Cow;
use std::fmt;
use std::str;
use crate::calls::{CallState, call_state};
use crate::chat::Chat;
use crate::constants::Chattype;
use crate::contact::{Contact, ContactId};
@@ -235,21 +234,11 @@ impl Message {
append_text = true;
}
Viewtype::Call => {
let call_state = call_state(context, self.id)
.await
.unwrap_or(CallState::Alerting);
emoji = Some("📞");
type_name = Some(match call_state {
CallState::Alerting | CallState::Active | CallState::Completed { .. } => {
if self.from_id == ContactId::SELF {
stock_str::outgoing_call(context).await
} else {
stock_str::incoming_call(context).await
}
}
CallState::Missed => stock_str::missed_call(context).await,
CallState::Declined => stock_str::declined_call(context).await,
CallState::Canceled => stock_str::canceled_call(context).await,
type_name = Some(if self.from_id == ContactId::SELF {
"Outgoing call".to_string()
} else {
"Incoming call".to_string()
});
type_file = None;
append_text = false
+24
View File
@@ -35,6 +35,7 @@ async fn check_verified_oneonone_chat_protection_not_broken(by_classical_email:
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
enable_verified_oneonone_chats(&[&alice, &bob]).await;
tcm.execute_securejoin(&alice, &bob).await;
@@ -88,6 +89,7 @@ async fn test_create_verified_oneonone_chat() -> Result<()> {
let alice = tcm.alice().await;
let bob = tcm.bob().await;
let fiona = tcm.fiona().await;
enable_verified_oneonone_chats(&[&alice, &bob, &fiona]).await;
tcm.execute_securejoin(&alice, &bob).await;
tcm.execute_securejoin(&bob, &fiona).await;
@@ -149,6 +151,7 @@ async fn test_create_verified_oneonone_chat() -> Result<()> {
drop(fiona);
let fiona_new = tcm.unconfigured().await;
enable_verified_oneonone_chats(&[&fiona_new]).await;
fiona_new.configure_addr("fiona@example.net").await;
e2ee::ensure_secret_key_exists(&fiona_new).await?;
@@ -178,6 +181,7 @@ async fn test_missing_key_reexecute_securejoin() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
enable_verified_oneonone_chats(&[alice, bob]).await;
let chat_id = tcm.execute_securejoin(bob, alice).await;
let chat = Chat::load_from_db(bob, chat_id).await?;
assert!(chat.is_protected());
@@ -202,6 +206,7 @@ async fn test_create_unverified_oneonone_chat() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
enable_verified_oneonone_chats(&[&alice, &bob]).await;
// A chat with an unknown contact should be created unprotected
let chat = alice.create_chat(&bob).await;
@@ -241,6 +246,7 @@ async fn test_degrade_verified_oneonone_chat() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
enable_verified_oneonone_chats(&[&alice, &bob]).await;
mark_as_verified(&alice, &bob).await;
@@ -355,6 +361,7 @@ async fn test_mdn_doesnt_disable_verification() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
enable_verified_oneonone_chats(&[&alice, &bob]).await;
bob.set_config_bool(Config::MdnsEnabled, true).await?;
// Alice & Bob verify each other
@@ -379,6 +386,7 @@ async fn test_outgoing_mua_msg() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
enable_verified_oneonone_chats(&[&alice, &bob]).await;
mark_as_verified(&alice, &bob).await;
mark_as_verified(&bob, &alice).await;
@@ -415,6 +423,7 @@ async fn test_outgoing_encrypted_msg() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
enable_verified_oneonone_chats(&[alice]).await;
mark_as_verified(alice, bob).await;
let chat_id = alice.create_chat(bob).await.id;
@@ -440,6 +449,7 @@ async fn test_reply() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
enable_verified_oneonone_chats(&[&alice, &bob]).await;
if verified {
mark_as_verified(&alice, &bob).await;
@@ -482,6 +492,7 @@ async fn test_message_from_old_dc_setup() -> Result<()> {
let alice = &tcm.alice().await;
let bob_old = &tcm.unconfigured().await;
enable_verified_oneonone_chats(&[alice, bob_old]).await;
bob_old.configure_addr("bob@example.net").await;
mark_as_verified(bob_old, alice).await;
let chat = bob_old.create_chat(alice).await;
@@ -492,6 +503,7 @@ async fn test_message_from_old_dc_setup() -> Result<()> {
tcm.section("Bob reinstalls DC");
let bob = &tcm.bob().await;
enable_verified_oneonone_chats(&[bob]).await;
mark_as_verified(alice, bob).await;
mark_as_verified(bob, alice).await;
@@ -523,6 +535,7 @@ async fn test_verify_then_verify_again() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
enable_verified_oneonone_chats(&[&alice, &bob]).await;
mark_as_verified(&alice, &bob).await;
mark_as_verified(&bob, &alice).await;
@@ -533,6 +546,7 @@ async fn test_verify_then_verify_again() -> Result<()> {
tcm.section("Bob reinstalls DC");
drop(bob);
let bob_new = tcm.unconfigured().await;
enable_verified_oneonone_chats(&[&bob_new]).await;
bob_new.configure_addr("bob@example.net").await;
e2ee::ensure_secret_key_exists(&bob_new).await?;
@@ -585,6 +599,7 @@ async fn test_verified_member_added_reordering() -> Result<()> {
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
let fiona = &tcm.fiona().await;
enable_verified_oneonone_chats(&[alice, bob, fiona]).await;
let alice_fiona_contact_id = alice.add_or_lookup_contact_id(fiona).await;
@@ -636,6 +651,7 @@ async fn test_no_unencrypted_name_if_encrypted() -> Result<()> {
bob.set_config(Config::Displayname, Some("Bob Smith"))
.await?;
if verified {
enable_verified_oneonone_chats(&[&bob]).await;
mark_as_verified(&bob, &alice).await;
} else {
tcm.send_recv_accept(&alice, &bob, "hi").await;
@@ -866,3 +882,11 @@ async fn assert_verified(this: &TestContext, other: &TestContext, protected: Pro
protected == ProtectionStatus::Protected
);
}
async fn enable_verified_oneonone_chats(test_contexts: &[&TestContext]) {
for t in test_contexts {
t.set_config_bool(Config::VerifiedOneOnOneChats, true)
.await
.unwrap()
}
}
+1 -2
View File
@@ -437,8 +437,7 @@ async fn test_maybe_warn_on_outdated() {
let t = TestContext::new().await;
let timestamp_now: i64 = time();
// in about 3 months, the app should not be outdated.
// "90 days" has proven to be too short at some point - user were informed but there was no update
// in about 3 months, the app should not be outdated
maybe_warn_on_outdated(
&t,
timestamp_now + 90 * 24 * 60 * 60,
@@ -2,7 +2,7 @@ Group#Chat#10: Group chat [3 member(s)]
--------------------------------------------------------------------------------
Msg#10: info (Contact#Contact#Info): Messages are end-to-end encrypted. [NOTICED][INFO]
Msg#11🔒: (Contact#Contact#10): Hi! I created a group. [FRESH]
Msg#12🔒: Me (Contact#Contact#Self): You left the group. [INFO] √
Msg#12🔒: Me (Contact#Contact#Self): You left. [INFO] √
Msg#13🔒: (Contact#Contact#10): Member charlie@example.net added by alice@example.org. [FRESH][INFO]
Msg#14🔒: (Contact#Contact#10): What a silence! [FRESH]
--------------------------------------------------------------------------------