Compare commits

..

6 Commits

16 changed files with 477 additions and 444 deletions

View File

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

View File

@@ -47,7 +47,7 @@ use types::provider_info::ProviderInfo;
use types::webxdc::WebxdcMessageInfo;
use self::types::{
chat::{BasicChat, JSONRPCChatVisibility, JSONRPCEncryptionModus, MuteDuration},
chat::{BasicChat, JSONRPCChatVisibility, MuteDuration},
location::JsonrpcLocation,
message::{
JSONRPCMessageListItem, MessageNotificationInfo, MessageSearchResult, MessageViewtype,
@@ -528,8 +528,6 @@ 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.
@@ -559,32 +557,6 @@ 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
@@ -1174,12 +1146,12 @@ impl CommandApi {
Ok(contacts)
}
async fn delete_contact(&self, account_id: u32, contact_id: u32) -> Result<()> {
async fn delete_contact(&self, account_id: u32, contact_id: u32) -> Result<bool> {
let ctx = self.get_context(account_id).await?;
let contact_id = ContactId::new(contact_id);
Contact::delete(&ctx, contact_id).await?;
Ok(())
Ok(true)
}
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, EncryptionModus};
use deltachat::chat::{Chat, ChatId};
use deltachat::constants::Chattype;
use deltachat::contact::{Contact, ContactId};
use deltachat::context::Context;
@@ -211,33 +211,3 @@ 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,16 +331,6 @@ 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()`.
@@ -740,8 +730,8 @@ export class RawClient {
}
public deleteContact(accountId: T.U32, contactId: T.U32): Promise<null> {
return (this._transport.request('delete_contact', [accountId, contactId] as RPC.Params)) as Promise<null>;
public deleteContact(accountId: T.U32, contactId: T.U32): Promise<boolean> {
return (this._transport.request('delete_contact', [accountId, contactId] as RPC.Params)) as Promise<boolean>;
}

View File

@@ -50,7 +50,6 @@ 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;})|({
@@ -196,4 +195,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,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];
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];

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')
context.deleteContact(id)
strictEqual(context.deleteContact(id), true, 'delete call succesful')
strictEqual(context.getContact(id), null, 'contact is gone')
})

View File

@@ -32,6 +32,7 @@ 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,
@@ -75,29 +76,6 @@ 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
@@ -437,7 +415,6 @@ 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,
@@ -446,7 +423,11 @@ impl ChatId {
if promote {
let mut msg = Message {
viewtype: Viewtype::Text,
text: Some(msg_text),
text: Some(
context
.stock_protection_msg(protect, ByContact::SelfName)
.await,
),
..Default::default()
};
msg.param.set_cmd(cmd);
@@ -455,7 +436,9 @@ impl ChatId {
add_info_msg_with_cmd(
context,
self,
&msg_text,
&context
.stock_protection_msg(protect, ByContact::YouOrName(from_id))
.await,
cmd,
create_smeared_timestamp(context).await,
None,
@@ -921,38 +904,6 @@ 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
@@ -1999,14 +1950,6 @@ 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 {
@@ -2807,7 +2750,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(), ContactId::SELF).await);
Some(stock_str::msg_add_member(context, contact.get_addr(), ByContact::SelfName).await);
msg.param.set_cmd(SystemMessage::MemberAddedToGroup);
msg.param.set(Param::Arg, contact.get_addr());
msg.param.set_int(Param::Arg2, from_handshake.into());
@@ -2940,13 +2883,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, ContactId::SELF).await);
Some(stock_str::msg_group_left(context, ByContact::SelfName).await);
} else {
msg.text = Some(
stock_str::msg_del_member(
context,
contact.get_addr(),
ContactId::SELF,
ByContact::SelfName,
)
.await,
);
@@ -3039,7 +2982,8 @@ 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, ContactId::SELF).await,
stock_str::msg_grp_name(context, &chat.name, &new_name, ByContact::SelfName)
.await,
);
msg.param.set_cmd(SystemMessage::GroupNameChanged);
if !chat.name.is_empty() {
@@ -3089,14 +3033,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, ContactId::SELF).await);
msg.text = Some(stock_str::msg_grp_img_deleted(context, ByContact::SelfName).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, ContactId::SELF).await);
msg.text = Some(stock_str::msg_grp_img_changed(context, ByContact::SelfName).await);
}
chat.update_param(context).await?;
if chat.is_promoted() && !chat.is_mailing_list() {
@@ -5401,6 +5345,35 @@ 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;
@@ -5712,27 +5685,4 @@ 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,6 +282,24 @@ 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,6 +83,7 @@ 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;
@@ -204,7 +205,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, ContactId::SELF).await);
msg.text = Some(stock_ephemeral_timer_changed(context, timer, ByContact::SelfName).await);
msg.param.set_cmd(SystemMessage::EphemeralTimerChanged);
if let Err(err) = send_msg(context, self, &mut msg).await {
error!(
@@ -220,7 +221,7 @@ impl ChatId {
pub(crate) async fn stock_ephemeral_timer_changed(
context: &Context,
timer: Timer,
from_id: ContactId,
from_id: ByContact,
) -> String {
match timer {
Timer::Disabled => stock_str::msg_ephemeral_timer_disabled(context, from_id).await,
@@ -637,7 +638,12 @@ mod tests {
let context = TestContext::new().await;
assert_eq!(
stock_ephemeral_timer_changed(&context, Timer::Disabled, ContactId::SELF).await,
stock_ephemeral_timer_changed(
&context,
Timer::Disabled,
ByContact::YouOrName(ContactId::SELF)
)
.await,
"You disabled message deletion timer."
);
@@ -645,7 +651,7 @@ mod tests {
stock_ephemeral_timer_changed(
&context,
Timer::Enabled { duration: 1 },
ContactId::SELF
ByContact::YouOrName(ContactId::SELF)
)
.await,
"You set message deletion timer to 1 s."
@@ -654,7 +660,7 @@ mod tests {
stock_ephemeral_timer_changed(
&context,
Timer::Enabled { duration: 30 },
ContactId::SELF
ByContact::YouOrName(ContactId::SELF)
)
.await,
"You set message deletion timer to 30 s."
@@ -663,7 +669,7 @@ mod tests {
stock_ephemeral_timer_changed(
&context,
Timer::Enabled { duration: 60 },
ContactId::SELF
ByContact::YouOrName(ContactId::SELF)
)
.await,
"You set message deletion timer to 1 minute."
@@ -672,7 +678,7 @@ mod tests {
stock_ephemeral_timer_changed(
&context,
Timer::Enabled { duration: 90 },
ContactId::SELF
ByContact::YouOrName(ContactId::SELF)
)
.await,
"You set message deletion timer to 1.5 minutes."
@@ -681,7 +687,7 @@ mod tests {
stock_ephemeral_timer_changed(
&context,
Timer::Enabled { duration: 30 * 60 },
ContactId::SELF
ByContact::YouOrName(ContactId::SELF)
)
.await,
"You set message deletion timer to 30 minutes."
@@ -690,7 +696,7 @@ mod tests {
stock_ephemeral_timer_changed(
&context,
Timer::Enabled { duration: 60 * 60 },
ContactId::SELF
ByContact::YouOrName(ContactId::SELF)
)
.await,
"You set message deletion timer to 1 hour."
@@ -699,7 +705,7 @@ mod tests {
stock_ephemeral_timer_changed(
&context,
Timer::Enabled { duration: 5400 },
ContactId::SELF
ByContact::YouOrName(ContactId::SELF)
)
.await,
"You set message deletion timer to 1.5 hours."
@@ -710,7 +716,7 @@ mod tests {
Timer::Enabled {
duration: 2 * 60 * 60
},
ContactId::SELF
ByContact::YouOrName(ContactId::SELF)
)
.await,
"You set message deletion timer to 2 hours."
@@ -721,7 +727,7 @@ mod tests {
Timer::Enabled {
duration: 24 * 60 * 60
},
ContactId::SELF
ByContact::YouOrName(ContactId::SELF)
)
.await,
"You set message deletion timer to 1 day."
@@ -732,7 +738,7 @@ mod tests {
Timer::Enabled {
duration: 2 * 24 * 60 * 60
},
ContactId::SELF
ByContact::YouOrName(ContactId::SELF)
)
.await,
"You set message deletion timer to 2 days."
@@ -743,7 +749,7 @@ mod tests {
Timer::Enabled {
duration: 7 * 24 * 60 * 60
},
ContactId::SELF
ByContact::YouOrName(ContactId::SELF)
)
.await,
"You set message deletion timer to 1 week."
@@ -754,7 +760,7 @@ mod tests {
Timer::Enabled {
duration: 4 * 7 * 24 * 60 * 60
},
ContactId::SELF
ByContact::YouOrName(ContactId::SELF)
)
.await,
"You set message deletion timer to 4 weeks."

View File

@@ -780,7 +780,8 @@ 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 = Vec::<(_, bool /* partially? */)>::with_capacity(msgs.len() + 1);
let mut uids_fetch_fully = Vec::with_capacity(msgs.len());
let mut uids_fetch_partially = Vec::with_capacity(msgs.len());
let mut uid_message_ids = BTreeMap::new();
let mut largest_uid_skipped = None;
@@ -839,11 +840,14 @@ impl Imap {
.await?
{
match download_limit {
Some(download_limit) => uids_fetch.push((
uid,
fetch_response.size.unwrap_or_default() > download_limit,
)),
None => uids_fetch.push((uid, false)),
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),
}
uid_message_ids.insert(uid, message_id);
} else {
@@ -851,37 +855,33 @@ impl Imap {
}
}
if !uids_fetch.is_empty() {
if !uids_fetch_fully.is_empty() || !uids_fetch_partially.is_empty() {
self.connectivity.set_working(context).await;
}
// Actually download messages.
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);
}
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);
// 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,7 +889,13 @@ 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(largest_uid_fetched, largest_uid_skipped.unwrap_or(0));
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 new_uid_next = largest_uid_without_errors + 1;
if new_uid_next > old_uid_next {

View File

@@ -8,7 +8,6 @@ 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::{
@@ -303,7 +302,6 @@ 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=?;"
),
@@ -850,39 +848,10 @@ 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,7 +9,6 @@ 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;
@@ -325,7 +324,7 @@ impl<'a> MimeFactory<'a> {
}
}
fn should_force_plaintext(&self, encryption_modus: &EncryptionModus) -> bool {
fn should_force_plaintext(&self) -> bool {
match &self.loaded {
Loaded::Message { chat } => {
if chat.is_protected() {
@@ -334,8 +333,6 @@ 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
@@ -605,11 +602,7 @@ impl<'a> MimeFactory<'a> {
let min_verified = self.min_verified();
let grpimage = self.grpimage();
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 force_plaintext = self.should_force_plaintext();
let skip_autocrypt = self.should_skip_autocrypt();
let e2ee_guaranteed = self.is_e2ee_guaranteed();
let encrypt_helper = EncryptHelper::new(context).await?;
@@ -651,34 +644,6 @@ 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,7 +51,6 @@ 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,6 +37,7 @@ 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).
@@ -933,7 +934,12 @@ async fn add_parts(
chat::add_info_msg(
context,
chat_id,
&stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await,
&stock_ephemeral_timer_changed(
context,
ephemeral_timer,
ByContact::YouOrName(from_id),
)
.await,
sort_timestamp,
)
.await?;
@@ -948,7 +954,10 @@ async fn add_parts(
}
if mime_parser.is_system_message == SystemMessage::EphemeralTimerChanged {
better_msg = Some(stock_ephemeral_timer_changed(context, ephemeral_timer, from_id).await);
better_msg = Some(
stock_ephemeral_timer_changed(context, ephemeral_timer, ByContact::YouOrName(from_id))
.await,
);
// Do not delete the system message itself.
//
@@ -996,7 +1005,11 @@ 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, from_id).await);
better_msg = Some(
context
.stock_protection_msg(new_status, ByContact::YouOrName(from_id))
.await,
);
}
}
}
@@ -1601,9 +1614,16 @@ 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, from_id).await)
Some(stock_str::msg_group_left(context, ByContact::YouOrName(from_id)).await)
} else {
Some(stock_str::msg_del_member(context, &removed_addr, from_id).await)
Some(
stock_str::msg_del_member(
context,
&removed_addr,
ByContact::YouOrName(from_id),
)
.await,
)
};
}
None => warn!(context, "removed {:?} has no contact_id", removed_addr),
@@ -1614,7 +1634,10 @@ async fn apply_group_changes(
.get_header(HeaderDef::ChatGroupMemberAdded)
.cloned()
{
better_msg = Some(stock_str::msg_add_member(context, &added_member, from_id).await);
better_msg = Some(
stock_str::msg_add_member(context, &added_member, ByContact::YouOrName(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
@@ -1636,8 +1659,15 @@ async fn apply_group_changes(
send_event_chat_modified = true;
}
better_msg =
Some(stock_str::msg_grp_name(context, old_name, grpname, from_id).await);
better_msg = Some(
stock_str::msg_grp_name(
context,
old_name,
grpname,
ByContact::YouOrName(from_id),
)
.await,
);
}
} else if let Some(value) = mime_parser.get_header(HeaderDef::ChatContent) {
if value == "group-avatar-changed" {
@@ -1645,12 +1675,14 @@ 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, from_id).await)
}
AvatarAction::Change(_) => {
Some(stock_str::msg_grp_img_changed(context, from_id).await)
}
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,
),
};
}
}

View File

@@ -10,7 +10,7 @@ use crate::provider::get_provider_by_domain;
use crate::sql::Sql;
use crate::tools::EmailAddress;
const DBVERSION: i32 = 69;
const DBVERSION: i32 = 68;
const VERSION_CFG: &str = "dbversion";
const TABLES: &str = include_str!("./tables.sql");
@@ -616,15 +616,6 @@ 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,6 +404,11 @@ pub enum StockMessage {
ProtectionDisabledBy = 161,
}
pub(crate) enum ByContact {
YouOrName(ContactId),
SelfName,
}
impl StockMessage {
/// Default untranslated strings for stock messages.
///
@@ -553,29 +558,45 @@ pub(crate) async fn msg_grp_name(
context: &Context,
from_group: impl AsRef<str>,
to_group: impl AsRef<str>,
by_contact: ContactId,
by_contact: ByContact,
) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouChangedGrpName)
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)
.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)
.replace3(context.get_config_name_and_addr().await),
}
}
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)
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)
.await
.replace1(by_contact.get_stock_name(context).await)
.replace1(context.get_config_name_and_addr().await),
}
}
@@ -586,7 +607,7 @@ pub(crate) async fn msg_grp_img_changed(context: &Context, by_contact: ContactId
pub(crate) async fn msg_add_member(
context: &Context,
added_member_addr: impl AsRef<str>,
by_contact: ContactId,
by_contact: ByContact,
) -> String {
let addr = added_member_addr.as_ref();
let who = match Contact::lookup_id_by_addr(context, addr, Origin::Unknown).await {
@@ -596,15 +617,23 @@ pub(crate) async fn msg_add_member(
.unwrap_or_else(|_| addr.to_string()),
_ => addr.to_string(),
};
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouAddMember)
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)
.await
.replace1(who)
} else {
translated(context, StockMessage::MsgAddMemberBy)
.await
.replace1(who)
.replace2(by_contact.get_stock_name(context).await)
.replace2(context.get_config_name_and_addr().await),
}
}
@@ -615,7 +644,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: ContactId,
by_contact: ByContact,
) -> String {
let addr = removed_member_addr.as_ref();
let who = match Contact::lookup_id_by_addr(context, addr, Origin::Unknown).await {
@@ -625,26 +654,41 @@ pub(crate) async fn msg_del_member(
.unwrap_or_else(|_| addr.to_string()),
_ => addr.to_string(),
};
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouDelMember)
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)
.await
.replace1(who)
} else {
translated(context, StockMessage::MsgDelMemberBy)
.await
.replace1(who)
.replace2(by_contact.get_stock_name(context).await)
.replace2(context.get_config_name_and_addr().await),
}
}
/// Stock string: `Group left.`.
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)
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)
.await
.replace1(by_contact.get_stock_name(context).await)
.replace1(context.get_config_name_and_addr().await),
}
}
@@ -691,13 +735,20 @@ 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: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouDeletedGrpImg).await
} else {
translated(context, StockMessage::MsgGrpImgDeletedBy)
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)
.await
.replace1(by_contact.get_stock_name(context).await)
.replace1(context.get_config_name_and_addr().await),
}
}
@@ -893,14 +944,21 @@ 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: ContactId,
by_contact: ByContact,
) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouDisabledEphemeralTimer).await
} else {
translated(context, StockMessage::MsgEphemeralTimerDisabledBy)
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)
.await
.replace1(by_contact.get_stock_name(context).await)
.replace1(context.get_config_name_and_addr().await),
}
}
@@ -908,61 +966,97 @@ pub(crate) async fn msg_ephemeral_timer_disabled(
pub(crate) async fn msg_ephemeral_timer_enabled(
context: &Context,
timer: impl AsRef<str>,
by_contact: ContactId,
by_contact: ByContact,
) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEnabledEphemeralTimer)
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)
.await
.replace1(timer)
} else {
translated(context, StockMessage::MsgEphemeralTimerEnabledBy)
.await
.replace1(timer)
.replace2(by_contact.get_stock_name(context).await)
.replace2(context.get_config_name_and_addr().await),
}
}
/// Stock string: `Message deletion timer is set to 1 minute.`.
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)
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)
.await
.replace1(by_contact.get_stock_name(context).await)
.replace1(context.get_config_name_and_addr().await),
}
}
/// Stock string: `Message deletion timer is set to 1 hour.`.
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)
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)
.await
.replace1(by_contact.get_stock_name(context).await)
.replace1(context.get_config_name_and_addr().await),
}
}
/// Stock string: `Message deletion timer is set to 1 day.`.
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)
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)
.await
.replace1(by_contact.get_stock_name(context).await)
.replace1(context.get_config_name_and_addr().await),
}
}
/// Stock string: `Message deletion timer is set to 1 week.`.
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)
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)
.await
.replace1(by_contact.get_stock_name(context).await)
.replace1(context.get_config_name_and_addr().await),
}
}
@@ -1004,24 +1098,38 @@ 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: ContactId) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::YouEnabledProtection).await
} else {
translated(context, StockMessage::ProtectionEnabledBy)
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)
.await
.replace1(by_contact.get_stock_name(context).await)
.replace1(context.get_config_name_and_addr().await),
}
}
/// Stock string: `Chat protection disabled.`.
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)
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)
.await
.replace1(by_contact.get_stock_name(context).await)
.replace1(context.get_config_name_and_addr().await),
}
}
@@ -1044,17 +1152,25 @@ 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: ContactId,
by_contact: ByContact,
) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerMinutes)
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)
.await
.replace1(minutes)
} else {
translated(context, StockMessage::MsgEphemeralTimerMinutesBy)
.await
.replace1(minutes)
.replace2(by_contact.get_stock_name(context).await)
.replace2(context.get_config_name_and_addr().await),
}
}
@@ -1062,17 +1178,25 @@ pub(crate) async fn msg_ephemeral_timer_minutes(
pub(crate) async fn msg_ephemeral_timer_hours(
context: &Context,
hours: impl AsRef<str>,
by_contact: ContactId,
by_contact: ByContact,
) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerHours)
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)
.await
.replace1(hours)
} else {
translated(context, StockMessage::MsgEphemeralTimerHoursBy)
.await
.replace1(hours)
.replace2(by_contact.get_stock_name(context).await)
.replace2(context.get_config_name_and_addr().await),
}
}
@@ -1080,17 +1204,25 @@ pub(crate) async fn msg_ephemeral_timer_hours(
pub(crate) async fn msg_ephemeral_timer_days(
context: &Context,
days: impl AsRef<str>,
by_contact: ContactId,
by_contact: ByContact,
) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerDays)
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)
.await
.replace1(days)
} else {
translated(context, StockMessage::MsgEphemeralTimerDaysBy)
.await
.replace1(days)
.replace2(by_contact.get_stock_name(context).await)
.replace2(context.get_config_name_and_addr().await),
}
}
@@ -1098,17 +1230,25 @@ pub(crate) async fn msg_ephemeral_timer_days(
pub(crate) async fn msg_ephemeral_timer_weeks(
context: &Context,
weeks: impl AsRef<str>,
by_contact: ContactId,
by_contact: ByContact,
) -> String {
if by_contact == ContactId::SELF {
translated(context, StockMessage::MsgYouEphemeralTimerWeeks)
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)
.await
.replace1(weeks)
} else {
translated(context, StockMessage::MsgEphemeralTimerWeeksBy)
.await
.replace1(weeks)
.replace2(by_contact.get_stock_name(context).await)
.replace2(context.get_config_name_and_addr().await),
}
}
@@ -1266,7 +1406,7 @@ impl Context {
pub(crate) async fn stock_protection_msg(
&self,
protect: ProtectionStatus,
from_id: ContactId,
from_id: ByContact,
) -> String {
match protect {
ProtectionStatus::Unprotected => protection_enabled(self, from_id).await,
@@ -1394,7 +1534,12 @@ 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", ContactId::SELF).await,
msg_add_member(
&t,
"alice@example.org",
ByContact::YouOrName(ContactId::SELF)
)
.await,
"You added member alice@example.org."
)
}
@@ -1406,7 +1551,12 @@ mod tests {
.await
.expect("failed to create contact");
assert_eq!(
msg_add_member(&t, "alice@example.org", ContactId::SELF).await,
msg_add_member(
&t,
"alice@example.org",
ByContact::YouOrName(ContactId::SELF)
)
.await,
"You added member Alice (alice@example.org)."
);
}
@@ -1423,11 +1573,27 @@ mod tests {
.expect("failed to create bob")
};
assert_eq!(
msg_add_member(&t, "alice@example.org", contact_id,).await,
msg_add_member(&t, "alice@example.org", ByContact::YouOrName(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;