Compare commits

..

11 Commits

Author SHA1 Message Date
jikstra
41a87a0626 Use sql migration over direct tables.sql modification 2022-12-01 15:15:04 +01:00
jikstra
d5faf75f56 Fix test_encryption_modus() 2022-12-01 13:50:20 +01:00
jikstra
e3ff786eb7 Add {get, set}_chat_encryption_mods() methdos to jsonrpc 2022-11-29 23:37:30 +01:00
jikstra
540cc8b205 Fix warning of unused result 2022-11-29 23:34:46 +01:00
jikstra
7533f863d1 - Don't pass EncryptionModus by reference
- rename get_encryption_modus() to encryption_modus()
2022-11-18 03:06:40 +01:00
jikstra
bc4eea0c28 cargo fmt 2022-11-18 02:44:19 +01:00
jikstra
d8844a0524 Propagate encryption_mode from Chat to Message 2022-11-18 02:44:02 +01:00
jikstra
a714a4c2da - Add encryption_modus field for msgs table and expose getters/setters on
Message
- Mind the encryption modus in RenderedEmail
2022-11-18 01:26:08 +01:00
jikstra
cb03e93570 Start working on encryption_modus 2022-11-17 22:32:16 +01:00
iequidoo
25be8ccd05 fetch_new_messages(): fetch messages sequentially (#3688)
If we fetch messages out of order, then f.e. reactions don't work because if we process a reaction not yet having the corresponding message processed, the reaction is thrown away.
2022-11-17 15:51:59 -03:00
link2xt
da6c68629d jsonrpc: do not return a result from deleteContact()
It was always true anyway.
2022-11-17 18:36:38 +00:00
16 changed files with 444 additions and 477 deletions

View File

@@ -8,7 +8,7 @@
### Fixes
- fix detection of "All mail", "Trash", "Junk" etc folders. #3760
- fix info messages shown for non-dc MUA #3754
- fetch messages sequentially to fix reactions on partially downloaded messages #3688
## 1.101.0

View File

@@ -47,7 +47,7 @@ use types::provider_info::ProviderInfo;
use types::webxdc::WebxdcMessageInfo;
use self::types::{
chat::{BasicChat, JSONRPCChatVisibility, MuteDuration},
chat::{BasicChat, JSONRPCChatVisibility, JSONRPCEncryptionModus, MuteDuration},
location::JsonrpcLocation,
message::{
JSONRPCMessageListItem, MessageNotificationInfo, MessageSearchResult, MessageViewtype,
@@ -528,6 +528,8 @@ impl CommandApi {
ChatId::new(chat_id).get_encryption_info(&ctx).await
}
/// Get QR code (text and SVG) that will offer an Setup-Contact or Verified-Group invitation.
/// The QR code is compatible to the OPENPGP4FPR format
/// so that a basic fingerprint comparison also works e.g. with OpenKeychain.
@@ -557,6 +559,32 @@ impl CommandApi {
))
}
async fn set_chat_encryption_modus(
&self,
account_id: u32,
chat_id: u32,
encryption_modus: JSONRPCEncryptionModus
) -> Result<()> {
let ctx = self.get_context(account_id).await?;
let chat = ChatId::new(chat_id);
Ok(chat.set_encryption_modus(&ctx, encryption_modus.into_core_type()).await?)
}
async fn get_chat_encryption_modus(
&self,
account_id: u32,
chat_id: u32
) -> Result<Option<JSONRPCEncryptionModus>> {
let ctx = self.get_context(account_id).await?;
let chat = ChatId::new(chat_id);
Ok(
match chat.encryption_modus(&ctx).await? {
Some(encryption_modus) => Some(JSONRPCEncryptionModus::from_core_type(encryption_modus)),
None => None,
}
)
}
/// Continue a Setup-Contact or Verified-Group-Invite protocol
/// started on another device with `get_chat_securejoin_qr_code_svg()`.
/// This function is typically called when `check_qr()` returns
@@ -1146,12 +1174,12 @@ impl CommandApi {
Ok(contacts)
}
async fn delete_contact(&self, account_id: u32, contact_id: u32) -> Result<bool> {
async fn delete_contact(&self, account_id: u32, contact_id: u32) -> Result<()> {
let ctx = self.get_context(account_id).await?;
let contact_id = ContactId::new(contact_id);
Contact::delete(&ctx, contact_id).await?;
Ok(true)
Ok(())
}
async fn change_contact_name(

View File

@@ -2,7 +2,7 @@ use std::time::{Duration, SystemTime};
use anyhow::{anyhow, bail, Result};
use deltachat::chat::{self, get_chat_contacts, ChatVisibility};
use deltachat::chat::{Chat, ChatId};
use deltachat::chat::{Chat, ChatId, EncryptionModus};
use deltachat::constants::Chattype;
use deltachat::contact::{Contact, ContactId};
use deltachat::context::Context;
@@ -211,3 +211,33 @@ impl JSONRPCChatVisibility {
}
}
}
#[derive(Clone, Serialize, Deserialize, TypeDef)]
#[serde(rename = "EncryptionModus")]
pub enum JSONRPCEncryptionModus {
Opportunistic = 0,
ForcePlaintext = 1,
ForceEncrypted = 2,
ForceVerified = 3,
}
impl JSONRPCEncryptionModus {
pub fn into_core_type(self) -> EncryptionModus {
match self {
JSONRPCEncryptionModus::Opportunistic => EncryptionModus::Opportunistic,
JSONRPCEncryptionModus::ForcePlaintext => EncryptionModus::ForcePlaintext,
JSONRPCEncryptionModus::ForceEncrypted => EncryptionModus::ForceEncrypted,
JSONRPCEncryptionModus::ForceVerified => EncryptionModus::ForceVerified
}
}
pub fn from_core_type(core_encryption_modus: EncryptionModus) -> Self {
match core_encryption_modus {
EncryptionModus::Opportunistic => JSONRPCEncryptionModus::Opportunistic,
EncryptionModus::ForcePlaintext => JSONRPCEncryptionModus::ForcePlaintext,
EncryptionModus::ForceEncrypted => JSONRPCEncryptionModus::ForceEncrypted,
EncryptionModus::ForceVerified => JSONRPCEncryptionModus::ForceVerified
}
}
}

View File

@@ -331,6 +331,16 @@ export class RawClient {
return (this._transport.request('get_chat_securejoin_qr_code_svg', [accountId, chatId] as RPC.Params)) as Promise<[string,string]>;
}
public setChatEncryptionModus(accountId: T.U32, chatId: T.U32, encryptionModus: T.EncryptionModus): Promise<null> {
return (this._transport.request('set_chat_encryption_modus', [accountId, chatId, encryptionModus] as RPC.Params)) as Promise<null>;
}
public getChatEncryptionModus(accountId: T.U32, chatId: T.U32): Promise<(T.EncryptionModus|null)> {
return (this._transport.request('get_chat_encryption_modus', [accountId, chatId] as RPC.Params)) as Promise<(T.EncryptionModus|null)>;
}
/**
* Continue a Setup-Contact or Verified-Group-Invite protocol
* started on another device with `get_chat_securejoin_qr_code_svg()`.
@@ -730,8 +740,8 @@ export class RawClient {
}
public deleteContact(accountId: T.U32, contactId: T.U32): Promise<boolean> {
return (this._transport.request('delete_contact', [accountId, contactId] as RPC.Params)) as Promise<boolean>;
public deleteContact(accountId: T.U32, contactId: T.U32): Promise<null> {
return (this._transport.request('delete_contact', [accountId, contactId] as RPC.Params)) as Promise<null>;
}

View File

@@ -50,6 +50,7 @@ export type BasicChat=
* used when you only need the basic metadata of a chat like type, name, profile picture
*/
{"id":U32;"name":string;"isProtected":boolean;"profileImage":(string|null);"archived":boolean;"chatType":U32;"isUnpromoted":boolean;"isSelfTalk":boolean;"color":string;"isContactRequest":boolean;"isDeviceChat":boolean;"isMuted":boolean;};
export type EncryptionModus=("Opportunistic"|"ForcePlaintext"|"ForceEncrypted"|"ForceVerified");
export type ChatVisibility=("Normal"|"Archived"|"Pinned");
export type MuteDuration=("NotMuted"|"Forever"|{"Until":I64;});
export type MessageListItem=(({"kind":"message";}&{"msg_id":U32;})|({
@@ -195,4 +196,4 @@ export type MessageNotificationInfo={"id":U32;"chatId":U32;"accountId":U32;"imag
export type MessageSearchResult={"id":U32;"authorProfileImage":(string|null);"authorName":string;"authorColor":string;"chatName":(string|null);"message":string;"timestamp":I64;};
export type F64=number;
export type Location={"locationId":U32;"isIndependent":boolean;"latitude":F64;"longitude":F64;"accuracy":F64;"timestamp":I64;"contactId":U32;"msgId":U32;"chatId":U32;"marker":(string|null);};
export type __AllTyps=[string,boolean,Record<string,string>,U32,U32,null,(U32)[],U32,null,(U32|null),(Account)[],null,null,U32,null,U32,null,U32,Account,U32,U64,U32,string,(ProviderInfo|null),U32,boolean,U32,Record<string,string>,U32,string,(string|null),null,U32,Record<string,(string|null)>,null,U32,string,null,U32,string,Qr,U32,string,(string|null),U32,(string)[],Record<string,(string|null)>,Record<U32,string>,null,U32,null,U32,null,U32,string,(string|null),null,U32,string,(string|null),null,U32,(U32)[],U32,U32,Usize,U32,boolean,I64,Usize,U32,string,U32,U32,string,null,U32,(U32|null),(string|null),(U32|null),(ChatListEntry)[],U32,(ChatListEntry)[],Record<U32,ChatListItemFetchResult>,U32,U32,FullChat,U32,U32,BasicChat,U32,U32,null,U32,U32,null,U32,U32,null,U32,U32,string,U32,(U32|null),[string,string],U32,string,U32,U32,U32,null,U32,U32,U32,null,U32,U32,U32,null,U32,U32,(U32)[],U32,string,boolean,U32,U32,U32,U32,U32,string,null,U32,U32,(string|null),null,U32,U32,ChatVisibility,null,U32,U32,U32,null,U32,U32,U32,U32,string,string,U32,U32,U32,null,U32,U32,(U32|null),U32,U32,MuteDuration,null,U32,U32,boolean,U32,(U32)[],null,U32,U32,U32,(U32)[],U32,U32,U32,(MessageListItem)[],U32,U32,Message,U32,U32,(string|null),U32,(U32)[],Record<U32,Message>,U32,U32,MessageNotificationInfo,U32,(U32)[],null,U32,U32,string,U32,U32,null,U32,string,(U32|null),(U32)[],U32,(U32)[],Record<U32,MessageSearchResult>,U32,U32,Contact,U32,string,(string|null),U32,U32,U32,U32,U32,U32,null,U32,U32,null,U32,(Contact)[],U32,U32,(string|null),(U32)[],U32,U32,(string|null),(Contact)[],U32,(U32)[],Record<U32,Contact>,U32,U32,boolean,U32,U32,string,null,U32,U32,string,U32,string,(U32|null),U32,(U32|null),Viewtype,(Viewtype|null),(Viewtype|null),(U32)[],U32,U32,Viewtype,(Viewtype|null),(Viewtype|null),[(U32|null),(U32|null)],U32,string,(string|null),null,U32,string,(string|null),null,null,U32,U32,U32,string,U32,(U32|null),(U32|null),I64,I64,(Location)[],U32,U32,string,string,null,U32,U32,U32,string,U32,U32,WebxdcMessageInfo,U32,(U32)[],U32,null,U32,U32,string,U32,U32,U32,(string)[],U32,U32,U32,null,U32,U32,(Message|null),U32,U32,U32,U32,string,U32,U32,string,null,U32,Record<string,(string)[]>,U32,U32,string,U32,U32,U32,(string|null),(string|null),([F64,F64]|null),(U32|null),[U32,Message],U32,U32,(string|null),(string|null),(U32|null),null];
export type __AllTyps=[string,boolean,Record<string,string>,U32,U32,null,(U32)[],U32,null,(U32|null),(Account)[],null,null,U32,null,U32,null,U32,Account,U32,U64,U32,string,(ProviderInfo|null),U32,boolean,U32,Record<string,string>,U32,string,(string|null),null,U32,Record<string,(string|null)>,null,U32,string,null,U32,string,Qr,U32,string,(string|null),U32,(string)[],Record<string,(string|null)>,Record<U32,string>,null,U32,null,U32,null,U32,string,(string|null),null,U32,string,(string|null),null,U32,(U32)[],U32,U32,Usize,U32,boolean,I64,Usize,U32,string,U32,U32,string,null,U32,(U32|null),(string|null),(U32|null),(ChatListEntry)[],U32,(ChatListEntry)[],Record<U32,ChatListItemFetchResult>,U32,U32,FullChat,U32,U32,BasicChat,U32,U32,null,U32,U32,null,U32,U32,null,U32,U32,string,U32,(U32|null),[string,string],U32,U32,EncryptionModus,null,U32,U32,(EncryptionModus|null),U32,string,U32,U32,U32,null,U32,U32,U32,null,U32,U32,U32,null,U32,U32,(U32)[],U32,string,boolean,U32,U32,U32,U32,U32,string,null,U32,U32,(string|null),null,U32,U32,ChatVisibility,null,U32,U32,U32,null,U32,U32,U32,U32,string,string,U32,U32,U32,null,U32,U32,(U32|null),U32,U32,MuteDuration,null,U32,U32,boolean,U32,(U32)[],null,U32,U32,U32,(U32)[],U32,U32,U32,(MessageListItem)[],U32,U32,Message,U32,U32,(string|null),U32,(U32)[],Record<U32,Message>,U32,U32,MessageNotificationInfo,U32,(U32)[],null,U32,U32,string,U32,U32,null,U32,string,(U32|null),(U32)[],U32,(U32)[],Record<U32,MessageSearchResult>,U32,U32,Contact,U32,string,(string|null),U32,U32,U32,U32,U32,U32,null,U32,U32,null,U32,(Contact)[],U32,U32,(string|null),(U32)[],U32,U32,(string|null),(Contact)[],U32,(U32)[],Record<U32,Contact>,U32,U32,null,U32,U32,string,null,U32,U32,string,U32,string,(U32|null),U32,(U32|null),Viewtype,(Viewtype|null),(Viewtype|null),(U32)[],U32,U32,Viewtype,(Viewtype|null),(Viewtype|null),[(U32|null),(U32|null)],U32,string,(string|null),null,U32,string,(string|null),null,null,U32,U32,U32,string,U32,(U32|null),(U32|null),I64,I64,(Location)[],U32,U32,string,string,null,U32,U32,U32,string,U32,U32,WebxdcMessageInfo,U32,(U32)[],U32,null,U32,U32,string,U32,U32,U32,(string)[],U32,U32,U32,null,U32,U32,(Message|null),U32,U32,U32,U32,string,U32,U32,string,null,U32,Record<string,(string)[]>,U32,U32,string,U32,U32,U32,(string|null),(string|null),([F64,F64]|null),(U32|null),[U32,Message],U32,U32,(string|null),(string|null),(U32|null),null];

View File

@@ -616,7 +616,7 @@ describe('Offline Tests with unconfigured account', function () {
const id = context.createContact('someuser', 'someuser@site.com')
const contact = context.getContact(id)
strictEqual(contact.getId(), id, 'contact id matches')
strictEqual(context.deleteContact(id), true, 'delete call succesful')
context.deleteContact(id)
strictEqual(context.getContact(id), null, 'contact is gone')
})

View File

@@ -32,7 +32,6 @@ use crate::receive_imf::ReceivedMsg;
use crate::scheduler::InterruptInfo;
use crate::smtp::send_msg_to_smtp;
use crate::stock_str;
use crate::stock_str::ByContact;
use crate::tools::{
create_id, create_outgoing_rfc724_mid, create_smeared_timestamp, create_smeared_timestamps,
get_abs_path, gm2local_offset, improve_single_line_input, time, IsNoneOrEmpty,
@@ -76,6 +75,29 @@ pub enum ProtectionStatus {
Protected = 1,
}
#[derive(
Debug,
Display,
Clone,
Copy,
PartialEq,
Eq,
FromPrimitive,
ToPrimitive,
FromSql,
ToSql,
IntoStaticStr,
Serialize,
Deserialize,
)]
#[repr(u32)]
pub enum EncryptionModus {
Opportunistic = 0,
ForcePlaintext = 1,
ForceEncrypted = 2,
ForceVerified = 3,
}
impl Default for ProtectionStatus {
fn default() -> Self {
ProtectionStatus::Unprotected
@@ -415,6 +437,7 @@ impl ChatId {
promote: bool,
from_id: ContactId,
) -> Result<()> {
let msg_text = context.stock_protection_msg(protect, from_id).await;
let cmd = match protect {
ProtectionStatus::Protected => SystemMessage::ChatProtectionEnabled,
ProtectionStatus::Unprotected => SystemMessage::ChatProtectionDisabled,
@@ -423,11 +446,7 @@ impl ChatId {
if promote {
let mut msg = Message {
viewtype: Viewtype::Text,
text: Some(
context
.stock_protection_msg(protect, ByContact::SelfName)
.await,
),
text: Some(msg_text),
..Default::default()
};
msg.param.set_cmd(cmd);
@@ -436,9 +455,7 @@ impl ChatId {
add_info_msg_with_cmd(
context,
self,
&context
.stock_protection_msg(protect, ByContact::YouOrName(from_id))
.await,
&msg_text,
cmd,
create_smeared_timestamp(context).await,
None,
@@ -904,6 +921,38 @@ impl ChatId {
Ok(ret.trim().to_string())
}
/// This sets a protection modus for the chat and enforces that messages are only send if they
/// meet the encryption modus (ForcePlaintext, Opportunistic, ForceEncrypted, ForceVerified)
pub async fn set_encryption_modus(
self,
context: &Context,
modus: EncryptionModus,
) -> Result<()> {
context
.sql
.execute(
"UPDATE chats SET encryption_modus=? WHERE id=?;",
paramsv![modus, self],
)
.await?;
Ok(())
}
/// This sets a protection modus for the chat and enforces that messages are only send if they
/// meet the encryption modus (ForcePlaintext, Opportunistic, ForceEncrypted, ForceVerified)
pub async fn encryption_modus(self, context: &Context) -> Result<Option<EncryptionModus>> {
let encryption_modus: Option<EncryptionModus> = context
.sql
.query_get_value(
"SELECT encryption_modus FROM chats WHERE id=?;",
paramsv![self],
)
.await?;
Ok(encryption_modus)
}
/// Bad evil escape hatch.
///
/// Avoid using this, eventually types should be cleaned up enough
@@ -1950,6 +1999,14 @@ pub async fn is_contact_in_chat(
// the caller can get it from msg.chat_id. Forwards would need to
// be fixed for this somehow too.
pub async fn send_msg(context: &Context, chat_id: ChatId, msg: &mut Message) -> Result<MsgId> {
// Propagate same encryption_mode of chat to message in case messages doesn't yet have an
// encryption_mode
if let None = msg.encryption_modus(&context).await? {
if let Some(encryption_mode) = chat_id.encryption_modus(&context).await? {
msg.set_encryption_modus(&context, encryption_mode).await?;
}
}
if chat_id.is_unset() {
let forwards = msg.param.get(Param::PrepForwards);
if let Some(forwards) = forwards {
@@ -2750,7 +2807,7 @@ pub(crate) async fn add_contact_to_chat_ex(
msg.viewtype = Viewtype::Text;
msg.text =
Some(stock_str::msg_add_member(context, contact.get_addr(), ByContact::SelfName).await);
Some(stock_str::msg_add_member(context, contact.get_addr(), ContactId::SELF).await);
msg.param.set_cmd(SystemMessage::MemberAddedToGroup);
msg.param.set(Param::Arg, contact.get_addr());
msg.param.set_int(Param::Arg2, from_handshake.into());
@@ -2883,13 +2940,13 @@ pub async fn remove_contact_from_chat(
if contact.id == ContactId::SELF {
set_group_explicitly_left(context, &chat.grpid).await?;
msg.text =
Some(stock_str::msg_group_left(context, ByContact::SelfName).await);
Some(stock_str::msg_group_left(context, ContactId::SELF).await);
} else {
msg.text = Some(
stock_str::msg_del_member(
context,
contact.get_addr(),
ByContact::SelfName,
ContactId::SELF,
)
.await,
);
@@ -2982,8 +3039,7 @@ pub async fn set_chat_name(context: &Context, chat_id: ChatId, new_name: &str) -
if chat.is_promoted() && !chat.is_mailing_list() && chat.typ != Chattype::Broadcast {
msg.viewtype = Viewtype::Text;
msg.text = Some(
stock_str::msg_grp_name(context, &chat.name, &new_name, ByContact::SelfName)
.await,
stock_str::msg_grp_name(context, &chat.name, &new_name, ContactId::SELF).await,
);
msg.param.set_cmd(SystemMessage::GroupNameChanged);
if !chat.name.is_empty() {
@@ -3033,14 +3089,14 @@ pub async fn set_chat_profile_image(
if new_image.as_ref().is_empty() {
chat.param.remove(Param::ProfileImage);
msg.param.remove(Param::Arg);
msg.text = Some(stock_str::msg_grp_img_deleted(context, ByContact::SelfName).await);
msg.text = Some(stock_str::msg_grp_img_deleted(context, ContactId::SELF).await);
} else {
let mut image_blob =
BlobObject::new_from_path(context, Path::new(new_image.as_ref())).await?;
image_blob.recode_to_avatar_size(context).await?;
chat.param.set(Param::ProfileImage, image_blob.as_name());
msg.param.set(Param::Arg, image_blob.as_name());
msg.text = Some(stock_str::msg_grp_img_changed(context, ByContact::SelfName).await);
msg.text = Some(stock_str::msg_grp_img_changed(context, ContactId::SELF).await);
}
chat.update_param(context).await?;
if chat.is_promoted() && !chat.is_mailing_list() {
@@ -5345,35 +5401,6 @@ mod tests {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_info_message_wording() -> Result<()> {
let alice = TestContext::new_alice().await;
let alice_grp = create_group_chat(&alice, ProtectionStatus::Unprotected, "grp").await?;
add_contact_to_chat(
&alice,
alice_grp,
Contact::create(&alice, "", "bob@example.net").await?,
)
.await?;
alice.send_text(alice_grp, "alice->bob").await;
add_contact_to_chat(
&alice,
alice_grp,
Contact::create(&alice, "", "claire@example.org").await?,
)
.await?;
let sent2 = alice.pop_sent_msg().await;
let msg = Message::load_from_db(&alice, sent2.sender_msg_id).await?;
// For DC, info message reads "You added"; for non-DC MUA, info message should not read "You added"
let you_added = "You added";
assert!(msg.get_text().unwrap().contains(you_added));
assert!(!sent2.payload().contains(you_added));
assert!(sent2.payload().contains("added by"));
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_can_send_group() -> Result<()> {
let alice = TestContext::new_alice().await;
@@ -5685,4 +5712,27 @@ mod tests {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_encryption_modus() -> Result<()> {
let t = TestContext::new_alice().await;
let contact_fiona = Contact::create(&t, "", "fiona@example.net").await?;
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "Group").await?;
assert_eq!(
chat_id.encryption_modus(&t).await?,
Some(EncryptionModus::Opportunistic)
);
chat_id
.set_encryption_modus(&t, EncryptionModus::ForceEncrypted)
.await?;
assert_eq!(
chat_id.encryption_modus(&t).await?,
Some(EncryptionModus::ForceEncrypted)
);
Ok(())
}
}

View File

@@ -282,24 +282,6 @@ impl Context {
}
}
pub async fn get_config_name_and_addr(&self) -> String {
let name = self
.get_config(Config::Displayname)
.await
.unwrap_or_default()
.unwrap_or_default();
let addr = self
.get_config(Config::Addr)
.await
.unwrap_or_default()
.unwrap_or_else(|| "unconfigured".to_string());
if !name.is_empty() {
format!("{} ({})", name, addr)
} else {
addr
}
}
/// Set the given config key.
/// If `None` is passed as a value the value is cleared and set to the default if there is one.
pub async fn set_config(&self, key: Config, value: Option<&str>) -> Result<()> {

View File

@@ -83,7 +83,6 @@ use crate::message::{Message, MessageState, MsgId, Viewtype};
use crate::mimeparser::SystemMessage;
use crate::sql::{self, params_iter};
use crate::stock_str;
use crate::stock_str::ByContact;
use crate::tools::{duration_to_str, time};
use std::cmp::max;
@@ -205,7 +204,7 @@ impl ChatId {
}
self.inner_set_ephemeral_timer(context, timer).await?;
let mut msg = Message::new(Viewtype::Text);
msg.text = Some(stock_ephemeral_timer_changed(context, timer, ByContact::SelfName).await);
msg.text = Some(stock_ephemeral_timer_changed(context, timer, ContactId::SELF).await);
msg.param.set_cmd(SystemMessage::EphemeralTimerChanged);
if let Err(err) = send_msg(context, self, &mut msg).await {
error!(
@@ -221,7 +220,7 @@ impl ChatId {
pub(crate) async fn stock_ephemeral_timer_changed(
context: &Context,
timer: Timer,
from_id: ByContact,
from_id: ContactId,
) -> String {
match timer {
Timer::Disabled => stock_str::msg_ephemeral_timer_disabled(context, from_id).await,
@@ -638,12 +637,7 @@ mod tests {
let context = TestContext::new().await;
assert_eq!(
stock_ephemeral_timer_changed(
&context,
Timer::Disabled,
ByContact::YouOrName(ContactId::SELF)
)
.await,
stock_ephemeral_timer_changed(&context, Timer::Disabled, ContactId::SELF).await,
"You disabled message deletion timer."
);
@@ -651,7 +645,7 @@ mod tests {
stock_ephemeral_timer_changed(
&context,
Timer::Enabled { duration: 1 },
ByContact::YouOrName(ContactId::SELF)
ContactId::SELF
)
.await,
"You set message deletion timer to 1 s."
@@ -660,7 +654,7 @@ mod tests {
stock_ephemeral_timer_changed(
&context,
Timer::Enabled { duration: 30 },
ByContact::YouOrName(ContactId::SELF)
ContactId::SELF
)
.await,
"You set message deletion timer to 30 s."
@@ -669,7 +663,7 @@ mod tests {
stock_ephemeral_timer_changed(
&context,
Timer::Enabled { duration: 60 },
ByContact::YouOrName(ContactId::SELF)
ContactId::SELF
)
.await,
"You set message deletion timer to 1 minute."
@@ -678,7 +672,7 @@ mod tests {
stock_ephemeral_timer_changed(
&context,
Timer::Enabled { duration: 90 },
ByContact::YouOrName(ContactId::SELF)
ContactId::SELF
)
.await,
"You set message deletion timer to 1.5 minutes."
@@ -687,7 +681,7 @@ mod tests {
stock_ephemeral_timer_changed(
&context,
Timer::Enabled { duration: 30 * 60 },
ByContact::YouOrName(ContactId::SELF)
ContactId::SELF
)
.await,
"You set message deletion timer to 30 minutes."
@@ -696,7 +690,7 @@ mod tests {
stock_ephemeral_timer_changed(
&context,
Timer::Enabled { duration: 60 * 60 },
ByContact::YouOrName(ContactId::SELF)
ContactId::SELF
)
.await,
"You set message deletion timer to 1 hour."
@@ -705,7 +699,7 @@ mod tests {
stock_ephemeral_timer_changed(
&context,
Timer::Enabled { duration: 5400 },
ByContact::YouOrName(ContactId::SELF)
ContactId::SELF
)
.await,
"You set message deletion timer to 1.5 hours."
@@ -716,7 +710,7 @@ mod tests {
Timer::Enabled {
duration: 2 * 60 * 60
},
ByContact::YouOrName(ContactId::SELF)
ContactId::SELF
)
.await,
"You set message deletion timer to 2 hours."
@@ -727,7 +721,7 @@ mod tests {
Timer::Enabled {
duration: 24 * 60 * 60
},
ByContact::YouOrName(ContactId::SELF)
ContactId::SELF
)
.await,
"You set message deletion timer to 1 day."
@@ -738,7 +732,7 @@ mod tests {
Timer::Enabled {
duration: 2 * 24 * 60 * 60
},
ByContact::YouOrName(ContactId::SELF)
ContactId::SELF
)
.await,
"You set message deletion timer to 2 days."
@@ -749,7 +743,7 @@ mod tests {
Timer::Enabled {
duration: 7 * 24 * 60 * 60
},
ByContact::YouOrName(ContactId::SELF)
ContactId::SELF
)
.await,
"You set message deletion timer to 1 week."
@@ -760,7 +754,7 @@ mod tests {
Timer::Enabled {
duration: 4 * 7 * 24 * 60 * 60
},
ByContact::YouOrName(ContactId::SELF)
ContactId::SELF
)
.await,
"You set message deletion timer to 4 weeks."

View File

@@ -780,8 +780,7 @@ impl Imap {
let show_emails = ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await?)
.unwrap_or_default();
let download_limit = context.download_limit().await?;
let mut uids_fetch_fully = Vec::with_capacity(msgs.len());
let mut uids_fetch_partially = Vec::with_capacity(msgs.len());
let mut uids_fetch = Vec::<(_, bool /* partially? */)>::with_capacity(msgs.len() + 1);
let mut uid_message_ids = BTreeMap::new();
let mut largest_uid_skipped = None;
@@ -840,14 +839,11 @@ impl Imap {
.await?
{
match download_limit {
Some(download_limit) => {
if fetch_response.size.unwrap_or_default() > download_limit {
uids_fetch_partially.push(uid);
} else {
uids_fetch_fully.push(uid)
}
}
None => uids_fetch_fully.push(uid),
Some(download_limit) => uids_fetch.push((
uid,
fetch_response.size.unwrap_or_default() > download_limit,
)),
None => uids_fetch.push((uid, false)),
}
uid_message_ids.insert(uid, message_id);
} else {
@@ -855,33 +851,37 @@ impl Imap {
}
}
if !uids_fetch_fully.is_empty() || !uids_fetch_partially.is_empty() {
if !uids_fetch.is_empty() {
self.connectivity.set_working(context).await;
}
// Actually download messages.
let (largest_uid_fully_fetched, mut received_msgs) = self
.fetch_many_msgs(
context,
folder,
uids_fetch_fully,
&uid_message_ids,
false,
fetch_existing_msgs,
)
.await?;
let (largest_uid_partially_fetched, received_msgs_2) = self
.fetch_many_msgs(
context,
folder,
uids_fetch_partially,
&uid_message_ids,
true,
fetch_existing_msgs,
)
.await?;
received_msgs.extend(received_msgs_2);
let mut largest_uid_fetched: u32 = 0;
let mut received_msgs = Vec::with_capacity(uids_fetch.len());
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));
for (uid, fp) in uids_fetch {
if fp != fetch_partially {
let (largest_uid_fetched_in_batch, received_msgs_in_batch) = self
.fetch_many_msgs(
context,
folder,
uids_fetch_in_batch.split_off(0),
&uid_message_ids,
fetch_partially,
fetch_existing_msgs,
)
.await?;
received_msgs.extend(received_msgs_in_batch);
largest_uid_fetched = max(
largest_uid_fetched,
largest_uid_fetched_in_batch.unwrap_or(0),
);
fetch_partially = fp;
}
uids_fetch_in_batch.push(uid);
}
// determine which uid_next to use to update to
// receive_imf() returns an `Err` value only on recoverable errors, otherwise it just logs an error.
@@ -889,13 +889,7 @@ impl Imap {
// So: Update the uid_next to the largest uid that did NOT recoverably fail. Not perfect because if there was
// another message afterwards that succeeded, we will not retry. The upside is that we will not retry an infinite amount of times.
let largest_uid_without_errors = max(
max(
largest_uid_fully_fetched.unwrap_or(0),
largest_uid_partially_fetched.unwrap_or(0),
),
largest_uid_skipped.unwrap_or(0),
);
let largest_uid_without_errors = max(largest_uid_fetched, largest_uid_skipped.unwrap_or(0));
let new_uid_next = largest_uid_without_errors + 1;
if new_uid_next > old_uid_next {

View File

@@ -8,6 +8,7 @@ use deltachat_derive::{FromSql, ToSql};
use rusqlite::types::ValueRef;
use serde::{Deserialize, Serialize};
use crate::chat::EncryptionModus;
use crate::chat::{self, Chat, ChatId};
use crate::config::Config;
use crate::constants::{
@@ -302,6 +303,7 @@ impl Message {
" m.hidden AS hidden,",
" m.location_id AS location,",
" c.blocked AS blocked",
" m.encryption_modus as encryption_modus",
" FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id",
" WHERE m.id=?;"
),
@@ -848,10 +850,39 @@ impl Message {
}
/// Force the message to be sent in plain text.
/// Deprecated: use Message::set_encryption_modus(EncryptionModus::ForcePlaintext)
pub fn force_plaintext(&mut self) {
self.param.set_int(Param::ForcePlaintext, 1);
}
pub async fn set_encryption_modus(
&mut self,
context: &Context,
encryption_modus: EncryptionModus,
) -> Result<()> {
context
.sql
.execute(
"UPDATE msgs SET encryption_modus=? WHERE id=?;",
paramsv![encryption_modus, self.id],
)
.await?;
Ok(())
}
pub async fn encryption_modus(&self, context: &Context) -> Result<Option<EncryptionModus>> {
let encryption_modus: Option<EncryptionModus> = context
.sql
.query_get_value(
"SELECT encryption_modus FROM msgs WHERE id=?;",
paramsv![self.id],
)
.await?;
Ok(encryption_modus)
}
pub async fn update_param(&self, context: &Context) -> Result<()> {
context
.sql

View File

@@ -9,6 +9,7 @@ use tokio::fs;
use crate::blob::BlobObject;
use crate::chat::Chat;
use crate::chat::EncryptionModus;
use crate::config::Config;
use crate::constants::{Chattype, DC_FROM_HANDSHAKE};
use crate::contact::Contact;
@@ -324,7 +325,7 @@ impl<'a> MimeFactory<'a> {
}
}
fn should_force_plaintext(&self) -> bool {
fn should_force_plaintext(&self, encryption_modus: &EncryptionModus) -> bool {
match &self.loaded {
Loaded::Message { chat } => {
if chat.is_protected() {
@@ -333,6 +334,8 @@ impl<'a> MimeFactory<'a> {
// encryption may disclose recipients;
// this is probably a worse issue than not opportunistically (!) encrypting
true
} else if encryption_modus == &EncryptionModus::ForcePlaintext {
true
} else {
self.msg
.param
@@ -602,7 +605,11 @@ impl<'a> MimeFactory<'a> {
let min_verified = self.min_verified();
let grpimage = self.grpimage();
let force_plaintext = self.should_force_plaintext();
let encryption_modus = match self.msg.encryption_modus(context).await? {
Some(encryption_modus) => encryption_modus,
None => EncryptionModus::Opportunistic,
};
let force_plaintext = self.should_force_plaintext(&encryption_modus);
let skip_autocrypt = self.should_skip_autocrypt();
let e2ee_guaranteed = self.is_e2ee_guaranteed();
let encrypt_helper = EncryptHelper::new(context).await?;
@@ -644,6 +651,34 @@ impl<'a> MimeFactory<'a> {
encrypt_helper.should_encrypt(context, e2ee_guaranteed, &peerstates)?;
let is_encrypted = should_encrypt && !force_plaintext;
// Ensure we fulfill encryption_modus
match encryption_modus {
EncryptionModus::Opportunistic => {}
EncryptionModus::ForcePlaintext => {
ensure!(
!is_encrypted,
"EncryptionModus is ForcePlaintext but message is encrypted"
);
}
EncryptionModus::ForceEncrypted => {
ensure!(
is_encrypted,
"EncryptionModus is ForceEncrypted but message is unencrypted"
);
}
EncryptionModus::ForceVerified => {
let chat_is_protected = if let Loaded::Message { chat } = &self.loaded {
chat.is_protected()
} else {
false
};
ensure!(
is_encrypted && chat_is_protected,
"EncryptionModus is ForceVerified but chat is not protected"
);
}
};
let message = if parts.is_empty() {
// Single part, render as regular message.
main_part

View File

@@ -51,6 +51,7 @@ pub enum Param {
ErroneousE2ee = b'e',
/// For Messages: force unencrypted message, a value from `ForcePlaintext` enum.
/// Deprecated: Use EncryptionModus::ForcePlaintext
ForcePlaintext = b'u',
/// For Messages: do not include Autocrypt header.

View File

@@ -37,7 +37,6 @@ use crate::reaction::{set_msg_reaction, Reaction};
use crate::securejoin::{self, handle_securejoin_handshake, observe_securejoin_on_other_device};
use crate::sql;
use crate::stock_str;
use crate::stock_str::ByContact;
use crate::tools::{create_id, extract_grpid_from_rfc724_mid, smeared_time};
/// This is the struct that is returned after receiving one email (aka MIME message).
@@ -934,12 +933,7 @@ async fn add_parts(
chat::add_info_msg(
context,
chat_id,
&stock_ephemeral_timer_changed(
context,
ephemeral_timer,
ByContact::YouOrName(from_id),
)
.await,
&stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await,
sort_timestamp,
)
.await?;
@@ -954,10 +948,7 @@ async fn add_parts(
}
if mime_parser.is_system_message == SystemMessage::EphemeralTimerChanged {
better_msg = Some(
stock_ephemeral_timer_changed(context, ephemeral_timer, ByContact::YouOrName(from_id))
.await,
);
better_msg = Some(stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await);
// Do not delete the system message itself.
//
@@ -1005,11 +996,7 @@ async fn add_parts(
// do not return an error as this would result in retrying the message
}
}
better_msg = Some(
context
.stock_protection_msg(new_status, ByContact::YouOrName(from_id))
.await,
);
better_msg = Some(context.stock_protection_msg(new_status, from_id).await);
}
}
}
@@ -1614,16 +1601,9 @@ async fn apply_group_changes(
match removed_id {
Some(contact_id) => {
better_msg = if contact_id == from_id {
Some(stock_str::msg_group_left(context, ByContact::YouOrName(from_id)).await)
Some(stock_str::msg_group_left(context, from_id).await)
} else {
Some(
stock_str::msg_del_member(
context,
&removed_addr,
ByContact::YouOrName(from_id),
)
.await,
)
Some(stock_str::msg_del_member(context, &removed_addr, from_id).await)
};
}
None => warn!(context, "removed {:?} has no contact_id", removed_addr),
@@ -1634,10 +1614,7 @@ async fn apply_group_changes(
.get_header(HeaderDef::ChatGroupMemberAdded)
.cloned()
{
better_msg = Some(
stock_str::msg_add_member(context, &added_member, ByContact::YouOrName(from_id))
.await,
);
better_msg = Some(stock_str::msg_add_member(context, &added_member, from_id).await);
recreate_member_list = true;
} else if let Some(old_name) = mime_parser.get_header(HeaderDef::ChatGroupNameChanged) {
if let Some(grpname) = mime_parser
@@ -1659,15 +1636,8 @@ async fn apply_group_changes(
send_event_chat_modified = true;
}
better_msg = Some(
stock_str::msg_grp_name(
context,
old_name,
grpname,
ByContact::YouOrName(from_id),
)
.await,
);
better_msg =
Some(stock_str::msg_grp_name(context, old_name, grpname, from_id).await);
}
} else if let Some(value) = mime_parser.get_header(HeaderDef::ChatContent) {
if value == "group-avatar-changed" {
@@ -1675,14 +1645,12 @@ async fn apply_group_changes(
// this is just an explicit message containing the group-avatar,
// apart from that, the group-avatar is send along with various other messages
better_msg = match avatar_action {
AvatarAction::Delete => Some(
stock_str::msg_grp_img_deleted(context, ByContact::YouOrName(from_id))
.await,
),
AvatarAction::Change(_) => Some(
stock_str::msg_grp_img_changed(context, ByContact::YouOrName(from_id))
.await,
),
AvatarAction::Delete => {
Some(stock_str::msg_grp_img_deleted(context, from_id).await)
}
AvatarAction::Change(_) => {
Some(stock_str::msg_grp_img_changed(context, from_id).await)
}
};
}
}

View File

@@ -10,7 +10,7 @@ use crate::provider::get_provider_by_domain;
use crate::sql::Sql;
use crate::tools::EmailAddress;
const DBVERSION: i32 = 68;
const DBVERSION: i32 = 69;
const VERSION_CFG: &str = "dbversion";
const TABLES: &str = include_str!("./tables.sql");
@@ -616,6 +616,15 @@ CREATE INDEX smtp_messageid ON imap(rfc724_mid);
)
.await?;
}
if dbversion < 94 {
sql.execute_migration(
r#"
ALTER TABLE chats ADD COLUMN encryption_modus INTEGER DEFAULT 0;
ALTER TABLE msgs ADD COLUMN encryption_modus INTEGER DEFAULT 0;"#,
94,
)
.await?;
}
let new_version = sql
.get_raw_config_int(VERSION_CFG)

View File

@@ -404,11 +404,6 @@ pub enum StockMessage {
ProtectionDisabledBy = 161,
}
pub(crate) enum ByContact {
YouOrName(ContactId),
SelfName,
}
impl StockMessage {
/// Default untranslated strings for stock messages.
///
@@ -558,45 +553,29 @@ pub(crate) async fn msg_grp_name(
context: &Context,
from_group: impl AsRef<str>,
to_group: impl AsRef<str>,
by_contact: ByContact,
by_contact: ContactId,
) -> String {
match by_contact {
ByContact::YouOrName(by_contact) => {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouChangedGrpName)
.await
.replace1(from_group)
.replace2(to_group)
} else {
translated(context, StockMessage::MsgGrpNameChangedBy)
.await
.replace1(from_group)
.replace2(to_group)
.replace3(by_contact.get_stock_name(context).await)
}
}
ByContact::SelfName => translated(context, StockMessage::MsgGrpNameChangedBy)
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouChangedGrpName)
.await
.replace1(from_group)
.replace2(to_group)
.replace3(context.get_config_name_and_addr().await),
} else {
translated(context, StockMessage::MsgGrpNameChangedBy)
.await
.replace1(from_group)
.replace2(to_group)
.replace3(by_contact.get_stock_name(context).await)
}
}
pub(crate) async fn msg_grp_img_changed(context: &Context, by_contact: ByContact) -> String {
match by_contact {
ByContact::YouOrName(by_contact) => {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouChangedGrpImg).await
} else {
translated(context, StockMessage::MsgGrpImgChangedBy)
.await
.replace1(by_contact.get_stock_name(context).await)
}
}
ByContact::SelfName => translated(context, StockMessage::MsgGrpImgChangedBy)
pub(crate) async fn msg_grp_img_changed(context: &Context, by_contact: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouChangedGrpImg).await
} else {
translated(context, StockMessage::MsgGrpImgChangedBy)
.await
.replace1(context.get_config_name_and_addr().await),
.replace1(by_contact.get_stock_name(context).await)
}
}
@@ -607,7 +586,7 @@ pub(crate) async fn msg_grp_img_changed(context: &Context, by_contact: ByContact
pub(crate) async fn msg_add_member(
context: &Context,
added_member_addr: impl AsRef<str>,
by_contact: ByContact,
by_contact: ContactId,
) -> String {
let addr = added_member_addr.as_ref();
let who = match Contact::lookup_id_by_addr(context, addr, Origin::Unknown).await {
@@ -617,23 +596,15 @@ pub(crate) async fn msg_add_member(
.unwrap_or_else(|_| addr.to_string()),
_ => addr.to_string(),
};
match by_contact {
ByContact::YouOrName(by_contact) => {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouAddMember)
.await
.replace1(who)
} else {
translated(context, StockMessage::MsgAddMemberBy)
.await
.replace1(who)
.replace2(by_contact.get_stock_name(context).await)
}
}
ByContact::SelfName => translated(context, StockMessage::MsgAddMemberBy)
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouAddMember)
.await
.replace1(who)
.replace2(context.get_config_name_and_addr().await),
} else {
translated(context, StockMessage::MsgAddMemberBy)
.await
.replace1(who)
.replace2(by_contact.get_stock_name(context).await)
}
}
@@ -644,7 +615,7 @@ pub(crate) async fn msg_add_member(
pub(crate) async fn msg_del_member(
context: &Context,
removed_member_addr: impl AsRef<str>,
by_contact: ByContact,
by_contact: ContactId,
) -> String {
let addr = removed_member_addr.as_ref();
let who = match Contact::lookup_id_by_addr(context, addr, Origin::Unknown).await {
@@ -654,41 +625,26 @@ pub(crate) async fn msg_del_member(
.unwrap_or_else(|_| addr.to_string()),
_ => addr.to_string(),
};
match by_contact {
ByContact::YouOrName(by_contact) => {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouDelMember)
.await
.replace1(who)
} else {
translated(context, StockMessage::MsgDelMemberBy)
.await
.replace1(who)
.replace2(by_contact.get_stock_name(context).await)
}
}
ByContact::SelfName => translated(context, StockMessage::MsgDelMemberBy)
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouDelMember)
.await
.replace1(who)
.replace2(context.get_config_name_and_addr().await),
} else {
translated(context, StockMessage::MsgDelMemberBy)
.await
.replace1(who)
.replace2(by_contact.get_stock_name(context).await)
}
}
/// Stock string: `Group left.`.
pub(crate) async fn msg_group_left(context: &Context, by_contact: ByContact) -> String {
match by_contact {
ByContact::YouOrName(by_contact) => {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouLeftGroup).await
} else {
translated(context, StockMessage::MsgGroupLeftBy)
.await
.replace1(by_contact.get_stock_name(context).await)
}
}
ByContact::SelfName => translated(context, StockMessage::MsgGroupLeftBy)
pub(crate) async fn msg_group_left(context: &Context, by_contact: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouLeftGroup).await
} else {
translated(context, StockMessage::MsgGroupLeftBy)
.await
.replace1(context.get_config_name_and_addr().await),
.replace1(by_contact.get_stock_name(context).await)
}
}
@@ -735,20 +691,13 @@ pub(crate) async fn read_rcpt_mail_body(context: &Context, message: impl AsRef<s
}
/// Stock string: `Group image deleted.`.
pub(crate) async fn msg_grp_img_deleted(context: &Context, by_contact: ByContact) -> String {
match by_contact {
ByContact::YouOrName(by_contact) => {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouDeletedGrpImg).await
} else {
translated(context, StockMessage::MsgGrpImgDeletedBy)
.await
.replace1(by_contact.get_stock_name(context).await)
}
}
ByContact::SelfName => translated(context, StockMessage::MsgGrpImgDeletedBy)
pub(crate) async fn msg_grp_img_deleted(context: &Context, by_contact: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouDeletedGrpImg).await
} else {
translated(context, StockMessage::MsgGrpImgDeletedBy)
.await
.replace1(context.get_config_name_and_addr().await),
.replace1(by_contact.get_stock_name(context).await)
}
}
@@ -944,21 +893,14 @@ pub(crate) async fn failed_sending_to(context: &Context, name: impl AsRef<str>)
/// Stock string: `Message deletion timer is disabled.`.
pub(crate) async fn msg_ephemeral_timer_disabled(
context: &Context,
by_contact: ByContact,
by_contact: ContactId,
) -> String {
match by_contact {
ByContact::YouOrName(by_contact) => {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouDisabledEphemeralTimer).await
} else {
translated(context, StockMessage::MsgEphemeralTimerDisabledBy)
.await
.replace1(by_contact.get_stock_name(context).await)
}
}
ByContact::SelfName => translated(context, StockMessage::MsgEphemeralTimerDisabledBy)
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouDisabledEphemeralTimer).await
} else {
translated(context, StockMessage::MsgEphemeralTimerDisabledBy)
.await
.replace1(context.get_config_name_and_addr().await),
.replace1(by_contact.get_stock_name(context).await)
}
}
@@ -966,97 +908,61 @@ pub(crate) async fn msg_ephemeral_timer_disabled(
pub(crate) async fn msg_ephemeral_timer_enabled(
context: &Context,
timer: impl AsRef<str>,
by_contact: ByContact,
by_contact: ContactId,
) -> String {
match by_contact {
ByContact::YouOrName(by_contact) => {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEnabledEphemeralTimer)
.await
.replace1(timer)
} else {
translated(context, StockMessage::MsgEphemeralTimerEnabledBy)
.await
.replace1(timer)
.replace2(by_contact.get_stock_name(context).await)
}
}
ByContact::SelfName => translated(context, StockMessage::MsgEphemeralTimerEnabledBy)
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEnabledEphemeralTimer)
.await
.replace1(timer)
.replace2(context.get_config_name_and_addr().await),
} else {
translated(context, StockMessage::MsgEphemeralTimerEnabledBy)
.await
.replace1(timer)
.replace2(by_contact.get_stock_name(context).await)
}
}
/// Stock string: `Message deletion timer is set to 1 minute.`.
pub(crate) async fn msg_ephemeral_timer_minute(context: &Context, by_contact: ByContact) -> String {
match by_contact {
ByContact::YouOrName(by_contact) => {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerMinute).await
} else {
translated(context, StockMessage::MsgEphemeralTimerMinuteBy)
.await
.replace1(by_contact.get_stock_name(context).await)
}
}
ByContact::SelfName => translated(context, StockMessage::MsgEphemeralTimerMinuteBy)
pub(crate) async fn msg_ephemeral_timer_minute(context: &Context, by_contact: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerMinute).await
} else {
translated(context, StockMessage::MsgEphemeralTimerMinuteBy)
.await
.replace1(context.get_config_name_and_addr().await),
.replace1(by_contact.get_stock_name(context).await)
}
}
/// Stock string: `Message deletion timer is set to 1 hour.`.
pub(crate) async fn msg_ephemeral_timer_hour(context: &Context, by_contact: ByContact) -> String {
match by_contact {
ByContact::YouOrName(by_contact) => {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerHour).await
} else {
translated(context, StockMessage::MsgEphemeralTimerHourBy)
.await
.replace1(by_contact.get_stock_name(context).await)
}
}
ByContact::SelfName => translated(context, StockMessage::MsgEphemeralTimerHourBy)
pub(crate) async fn msg_ephemeral_timer_hour(context: &Context, by_contact: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerHour).await
} else {
translated(context, StockMessage::MsgEphemeralTimerHourBy)
.await
.replace1(context.get_config_name_and_addr().await),
.replace1(by_contact.get_stock_name(context).await)
}
}
/// Stock string: `Message deletion timer is set to 1 day.`.
pub(crate) async fn msg_ephemeral_timer_day(context: &Context, by_contact: ByContact) -> String {
match by_contact {
ByContact::YouOrName(by_contact) => {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerDay).await
} else {
translated(context, StockMessage::MsgEphemeralTimerDayBy)
.await
.replace1(by_contact.get_stock_name(context).await)
}
}
ByContact::SelfName => translated(context, StockMessage::MsgEphemeralTimerDayBy)
pub(crate) async fn msg_ephemeral_timer_day(context: &Context, by_contact: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerDay).await
} else {
translated(context, StockMessage::MsgEphemeralTimerDayBy)
.await
.replace1(context.get_config_name_and_addr().await),
.replace1(by_contact.get_stock_name(context).await)
}
}
/// Stock string: `Message deletion timer is set to 1 week.`.
pub(crate) async fn msg_ephemeral_timer_week(context: &Context, by_contact: ByContact) -> String {
match by_contact {
ByContact::YouOrName(by_contact) => {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerWeek).await
} else {
translated(context, StockMessage::MsgEphemeralTimerWeekBy)
.await
.replace1(by_contact.get_stock_name(context).await)
}
}
ByContact::SelfName => translated(context, StockMessage::MsgEphemeralTimerWeekBy)
pub(crate) async fn msg_ephemeral_timer_week(context: &Context, by_contact: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerWeek).await
} else {
translated(context, StockMessage::MsgEphemeralTimerWeekBy)
.await
.replace1(context.get_config_name_and_addr().await),
.replace1(by_contact.get_stock_name(context).await)
}
}
@@ -1098,38 +1004,24 @@ pub(crate) async fn error_no_network(context: &Context) -> String {
}
/// Stock string: `Chat protection enabled.`.
pub(crate) async fn protection_enabled(context: &Context, by_contact: ByContact) -> String {
match by_contact {
ByContact::YouOrName(by_contact) => {
if by_contact == ContactId::SELF {
translated(context, StockMessage::YouEnabledProtection).await
} else {
translated(context, StockMessage::ProtectionEnabledBy)
.await
.replace1(by_contact.get_stock_name(context).await)
}
}
ByContact::SelfName => translated(context, StockMessage::ProtectionEnabledBy)
pub(crate) async fn protection_enabled(context: &Context, by_contact: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::YouEnabledProtection).await
} else {
translated(context, StockMessage::ProtectionEnabledBy)
.await
.replace1(context.get_config_name_and_addr().await),
.replace1(by_contact.get_stock_name(context).await)
}
}
/// Stock string: `Chat protection disabled.`.
pub(crate) async fn protection_disabled(context: &Context, by_contact: ByContact) -> String {
match by_contact {
ByContact::YouOrName(by_contact) => {
if by_contact == ContactId::SELF {
translated(context, StockMessage::YouDisabledProtection).await
} else {
translated(context, StockMessage::ProtectionDisabledBy)
.await
.replace1(by_contact.get_stock_name(context).await)
}
}
ByContact::SelfName => translated(context, StockMessage::ProtectionDisabledBy)
pub(crate) async fn protection_disabled(context: &Context, by_contact: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::YouDisabledProtection).await
} else {
translated(context, StockMessage::ProtectionDisabledBy)
.await
.replace1(context.get_config_name_and_addr().await),
.replace1(by_contact.get_stock_name(context).await)
}
}
@@ -1152,25 +1044,17 @@ pub(crate) async fn delete_server_turned_off(context: &Context) -> String {
pub(crate) async fn msg_ephemeral_timer_minutes(
context: &Context,
minutes: impl AsRef<str>,
by_contact: ByContact,
by_contact: ContactId,
) -> String {
match by_contact {
ByContact::YouOrName(by_contact) => {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerMinutes)
.await
.replace1(minutes)
} else {
translated(context, StockMessage::MsgEphemeralTimerMinutesBy)
.await
.replace1(minutes)
.replace2(by_contact.get_stock_name(context).await)
}
}
ByContact::SelfName => translated(context, StockMessage::MsgEphemeralTimerMinutesBy)
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerMinutes)
.await
.replace1(minutes)
.replace2(context.get_config_name_and_addr().await),
} else {
translated(context, StockMessage::MsgEphemeralTimerMinutesBy)
.await
.replace1(minutes)
.replace2(by_contact.get_stock_name(context).await)
}
}
@@ -1178,25 +1062,17 @@ pub(crate) async fn msg_ephemeral_timer_minutes(
pub(crate) async fn msg_ephemeral_timer_hours(
context: &Context,
hours: impl AsRef<str>,
by_contact: ByContact,
by_contact: ContactId,
) -> String {
match by_contact {
ByContact::YouOrName(by_contact) => {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerHours)
.await
.replace1(hours)
} else {
translated(context, StockMessage::MsgEphemeralTimerHoursBy)
.await
.replace1(hours)
.replace2(by_contact.get_stock_name(context).await)
}
}
ByContact::SelfName => translated(context, StockMessage::MsgEphemeralTimerHoursBy)
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerHours)
.await
.replace1(hours)
.replace2(context.get_config_name_and_addr().await),
} else {
translated(context, StockMessage::MsgEphemeralTimerHoursBy)
.await
.replace1(hours)
.replace2(by_contact.get_stock_name(context).await)
}
}
@@ -1204,25 +1080,17 @@ pub(crate) async fn msg_ephemeral_timer_hours(
pub(crate) async fn msg_ephemeral_timer_days(
context: &Context,
days: impl AsRef<str>,
by_contact: ByContact,
by_contact: ContactId,
) -> String {
match by_contact {
ByContact::YouOrName(by_contact) => {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerDays)
.await
.replace1(days)
} else {
translated(context, StockMessage::MsgEphemeralTimerDaysBy)
.await
.replace1(days)
.replace2(by_contact.get_stock_name(context).await)
}
}
ByContact::SelfName => translated(context, StockMessage::MsgEphemeralTimerDaysBy)
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerDays)
.await
.replace1(days)
.replace2(context.get_config_name_and_addr().await),
} else {
translated(context, StockMessage::MsgEphemeralTimerDaysBy)
.await
.replace1(days)
.replace2(by_contact.get_stock_name(context).await)
}
}
@@ -1230,25 +1098,17 @@ pub(crate) async fn msg_ephemeral_timer_days(
pub(crate) async fn msg_ephemeral_timer_weeks(
context: &Context,
weeks: impl AsRef<str>,
by_contact: ByContact,
by_contact: ContactId,
) -> String {
match by_contact {
ByContact::YouOrName(by_contact) => {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerWeeks)
.await
.replace1(weeks)
} else {
translated(context, StockMessage::MsgEphemeralTimerWeeksBy)
.await
.replace1(weeks)
.replace2(by_contact.get_stock_name(context).await)
}
}
ByContact::SelfName => translated(context, StockMessage::MsgEphemeralTimerWeeksBy)
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerWeeks)
.await
.replace1(weeks)
.replace2(context.get_config_name_and_addr().await),
} else {
translated(context, StockMessage::MsgEphemeralTimerWeeksBy)
.await
.replace1(weeks)
.replace2(by_contact.get_stock_name(context).await)
}
}
@@ -1406,7 +1266,7 @@ impl Context {
pub(crate) async fn stock_protection_msg(
&self,
protect: ProtectionStatus,
from_id: ByContact,
from_id: ContactId,
) -> String {
match protect {
ProtectionStatus::Unprotected => protection_enabled(self, from_id).await,
@@ -1534,12 +1394,7 @@ mod tests {
async fn test_stock_system_msg_add_member_by_me() {
let t = TestContext::new().await;
assert_eq!(
msg_add_member(
&t,
"alice@example.org",
ByContact::YouOrName(ContactId::SELF)
)
.await,
msg_add_member(&t, "alice@example.org", ContactId::SELF).await,
"You added member alice@example.org."
)
}
@@ -1551,12 +1406,7 @@ mod tests {
.await
.expect("failed to create contact");
assert_eq!(
msg_add_member(
&t,
"alice@example.org",
ByContact::YouOrName(ContactId::SELF)
)
.await,
msg_add_member(&t, "alice@example.org", ContactId::SELF).await,
"You added member Alice (alice@example.org)."
);
}
@@ -1573,27 +1423,11 @@ mod tests {
.expect("failed to create bob")
};
assert_eq!(
msg_add_member(&t, "alice@example.org", ByContact::YouOrName(contact_id)).await,
msg_add_member(&t, "alice@example.org", contact_id,).await,
"Member Alice (alice@example.org) added by Bob (bob@example.com)."
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_stock_system_msg_add_member_by_self_name() -> Result<()> {
let t = TestContext::new_bob().await;
assert_eq!(
msg_add_member(&t, "alice@example.org", ByContact::SelfName).await,
"Member alice@example.org added by bob@example.net."
);
t.set_config(Config::Displayname, Some("Bobby")).await?;
assert_eq!(
msg_add_member(&t, "alice@example.org", ByContact::SelfName).await,
"Member alice@example.org added by Bobby (bob@example.net)."
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_quota_exceeding_stock_str() -> Result<()> {
let t = TestContext::new().await;