diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 3687ea9be..8bc5724a6 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -305,7 +305,7 @@ char* dc_get_blobdir (const dc_context_t* context); * DC_SHOW_EMAILS_ACCEPTED_CONTACTS (1)= * also show all mails of confirmed contacts, * DC_SHOW_EMAILS_ALL (2)= - * also show mails of unconfirmed contacts in the deaddrop. + * also show mails of unconfirmed contacts. * - `key_gen_type` = DC_KEY_GEN_DEFAULT (0)= * generate recommended key type (default), * DC_KEY_GEN_RSA2048 (1)= @@ -682,12 +682,6 @@ int dc_preconfigure_keypair (dc_context_t* context, const cha * * By default, the function adds some special entries to the list. * These special entries can be identified by the ID returned by dc_chatlist_get_chat_id(): - * - DC_CHAT_ID_DEADDROP (1) - this special chat is present if there are - * messages from addresses that have no relationship to the configured account. - * The last of these messages is represented by DC_CHAT_ID_DEADDROP and you can retrieve details - * about it with dc_chatlist_get_msg_id(). Typically, the UI asks the user "Do you want to chat with NAME?" - * and offers the options "Start chat", "Block" or "Not now". - * Call dc_decide_on_contact_request() when the user selected one of these options. * - DC_CHAT_ID_ARCHIVED_LINK (6) - this special chat is present if the user has * archived _any_ chat using dc_set_chat_visibility(). The UI should show a link as * "Show archived chats", if the user clicks this item, the UI should show a @@ -705,10 +699,10 @@ int dc_preconfigure_keypair (dc_context_t* context, const cha * the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are _any_ archived * chats * - the flag DC_GCL_FOR_FORWARDING sorts "Saved messages" to the top of the chatlist - * and hides the "Device chat" and the deaddrop. + * and hides the "Device chat" and contact requests. * typically used on forwarding, may be combined with DC_GCL_NO_SPECIALS * to also hide the archive link. - * - if the flag DC_GCL_NO_SPECIALS is set, deaddrop and archive link are not added + * - if the flag DC_GCL_NO_SPECIALS is set, archive link is not added * to the list (may be used e.g. for selecting chats on forwarding, the flag is * not needed when DC_GCL_ARCHIVED_ONLY is already set) * - if the flag DC_GCL_ADD_ALLDONE_HINT is set, DC_CHAT_ID_ALLDONE_HINT @@ -728,42 +722,12 @@ dc_chatlist_t* dc_get_chatlist (dc_context_t* context, int flags, // handle chats -/** - * Create a normal chat or a group chat by a messages ID that comes typically - * from the deaddrop, DC_CHAT_ID_DEADDROP (1). - * - * If the given message ID already belongs to a normal chat or to a group chat, - * the chat ID of this chat is returned and no new chat is created. - * If a new chat is created, the given message ID is moved to this chat, however, - * there may be more messages moved to the chat from the deaddrop. To get the - * chat messages, use dc_get_chat_msgs(). - * - * If the user is asked before creation, he should be - * asked whether he wants to chat with the _contact_ belonging to the message; - * the group names may be really weird when taken from the subject of implicit - * groups and this may look confusing. - * - * Moreover, this function also scales up the origin of the contact belonging - * to the message and, depending on the contacts origin, messages from the - * same group may be shown or not - so, all in all, it is fine to show the - * contact name only. - * - * @deprecated Deprecated 2021-02-07, use dc_decide_on_contact_request() instead - * @memberof dc_context_t - * @param context The context object as returned from dc_context_new(). - * @param msg_id The message ID to create the chat for. - * @return The created or reused chat ID on success. 0 on errors. - */ -uint32_t dc_create_chat_by_msg_id (dc_context_t* context, uint32_t msg_id); - - /** * Create a normal chat with a single user. To create group chats, * see dc_create_group_chat(). * * If a chat already exists, this ID is returned, otherwise a new chat is created; - * this new chat may already contain messages, e.g. from the deaddrop, to get the - * chat messages, use dc_get_chat_msgs(). + * to get the chat messages, use dc_get_chat_msgs(). * * @memberof dc_context_t * @param context The context object as returned from dc_context_new(). @@ -1135,7 +1099,7 @@ int dc_estimate_deletion_cnt (dc_context_t* context, int from_ser * or badge counters eg. on the app-icon. * The list is already sorted and starts with the most recent fresh message. * - * Messages belonging to muted chats or to the deaddrop are not returned; + * Messages belonging to muted chats or to the contact requests are not returned; * these messages should not be notified * and also badge counters should not include these messages. * @@ -1161,8 +1125,7 @@ dc_array_t* dc_get_fresh_msgs (dc_context_t* context); * * @memberof dc_context_t * @param context The context object as returned from dc_context_new(). - * @param chat_id The chat ID of which all messages should be marked as being noticed - * (this also works for the virtual chat ID DC_CHAT_ID_DEADDROP). + * @param chat_id The chat ID of which all messages should be marked as being noticed. */ void dc_marknoticed_chat (dc_context_t* context, uint32_t chat_id); @@ -1271,6 +1234,31 @@ void dc_set_chat_visibility (dc_context_t* context, uint32_t ch */ void dc_delete_chat (dc_context_t* context, uint32_t chat_id); +/** + * Block a chat. + * + * Blocking 1:1 chats blocks the corresponding contact. Blocking + * mailing lists creates a pseudo-contact in the list of blocked + * contacts, so blocked mailing lists can be discovered and unblocked + * the same way as the contacts. Blocking group chats deletes the + * chat without blocking any contacts, so it may pop up again later. + * + * @memberof dc_context_t + * @param context The context object as returned from dc_context_new(). + * @param chat_id The ID of the chat to block. + */ +void dc_block_chat (dc_context_t* context, uint32_t chat_id); + +/** + * Accept a contact request chat. + * + * Use it to accept "contact request" chats as indicated by dc_chat_is_contact_request(). + * + * @memberof dc_context_t + * @param context The context object as returned from dc_context_new(). + * @param chat_id The ID of the chat to accept. + */ +void dc_accept_chat (dc_context_t* context, uint32_t chat_id); /** * Get contact IDs belonging to a chat. @@ -1282,8 +1270,6 @@ void dc_delete_chat (dc_context_t* context, uint32_t ch * explicitly as it may happen that oneself gets removed from a still existing * group * - * - for the deaddrop, the list is empty - * * - for mailing lists, the behavior is not documented currently, we will decide on that later. * for now, the UI should not show the list for mailing lists. * (we do not know all members and there is not always a global mailing list address, @@ -1631,25 +1617,6 @@ void dc_delete_msgs (dc_context_t* context, const uint3 void dc_forward_msgs (dc_context_t* context, const uint32_t* msg_ids, int msg_cnt, uint32_t chat_id); -/** - * Mark all messages sent by the given contact as _noticed_. - * This function is typically used to ignore a user in the deaddrop temporarily ("Not now" button). - * - * The contact is expected to belong to the deaddrop; - * only one #DC_EVENT_MSGS_NOTICED with chat_id=DC_CHAT_ID_DEADDROP may be emitted. - * - * See also dc_marknoticed_chat() and dc_markseen_msgs() - * - * @deprecated Deprecated 2021-02-07, use dc_decide_on_contact_request() if the user just hit "Not now" on a button in the deaddrop, - * dc_marknoticed_chat() if the user has entered a chat - * and dc_markseen_msgs() if the user actually _saw_ a message. - * @memberof dc_context_t - * @param context The context object. - * @param contact_id The contact ID of which all messages should be marked as noticed. - */ -void dc_marknoticed_contact (dc_context_t* context, uint32_t contact_id); - - /** * Mark messages as presented to the user. * Typically, UIs call this function on scrolling through the chatlist, @@ -1661,12 +1628,12 @@ void dc_marknoticed_contact (dc_context_t* context, uint32_t co * (if dc_set_config()-options `mdns_enabled` is set) * and the internal state is changed to DC_STATE_IN_SEEN to reflect these actions. * - * - For the deaddrop, no IMAP or MNDs is done - * and the internal change is not changed therefore. + * - For contact requests, no IMAP or MDNs is done + * and the internal state is not changed therefore. * See also dc_marknoticed_chat(). * * Moreover, timer is started for incoming ephemeral messages. - * This also happens for messages in the deaddrop. + * This also happens for contact requests chats. * * One #DC_EVENT_MSGS_NOTICED event is emitted per modified chat. * @@ -1693,53 +1660,6 @@ void dc_markseen_msgs (dc_context_t* context, const uint3 dc_msg_t* dc_get_msg (dc_context_t* context, uint32_t msg_id); -#define DC_DECISION_START_CHAT 0 -#define DC_DECISION_BLOCK 1 -#define DC_DECISION_NOT_NOW 2 - - -/** - * Call this when the user decided about a deaddrop message ("Do you want to chat with NAME?"). - * - * Possible decisions are: - * - DC_DECISION_START_CHAT (0) - * - This will create a new chat and return the chat id. - * - DC_DECISION_BLOCK (1) - * - This will block the sender. - * - When a new message from the sender arrives, - * that will not result in a new contact request. - * - The blocked sender will be returned by dc_get_blocked_contacts() - * typically, the UI offers an option to unblock senders from there. - * - DC_DECISION_NOT_NOW (2) - * - This will mark all messages from this sender as noticed. - * - That the contact request is removed from the chat list. - * - When a new message from the sender arrives, - * a new contact request with the new message will pop up in the chatlist. - * - The contact request stays available in the explicit deaddrop. - * - If the contact request is already noticed, nothing happens. - * - * If the message belongs to a mailing list, - * the function makes sure that all messages - * from the mailing list are blocked or marked as noticed. - * - * The user should be asked whether they want to chat with the _contact_ belonging to the message; - * the group names may be really weird when taken from the subject of implicit (= ad-hoc) - * groups and this may look confusing. Moreover, this function also scales up the origin of the contact. - * - * If the chat belongs to a mailing list, you can also ask - * "Would you like to read MAILING LIST NAME?" - * (use dc_msg_get_real_chat_id() to get the chat-id for the contact request - * and then dc_chat_is_mailing_list(), dc_chat_get_name() and so on) - * - * @memberof dc_context_t - * @param context The context object. - * @param msg_id ID of Message to decide on. - * @param decision One of the DC_DECISION_* values. - * @return The chat id of the created chat, if any. - */ -uint32_t dc_decide_on_contact_request (dc_context_t* context, uint32_t msg_id, int decision); - - // handle contacts /** @@ -2884,15 +2804,9 @@ int dc_array_search_id (const dc_array_t* array, uint32_t * and for each messages that is scrolled into view, dc_get_msg() is called then. * * Why no listflags? - * Without listflags, dc_get_chatlist() adds the deaddrop - * and the archive "link" automatically as needed. + * Without listflags, dc_get_chatlist() adds + * the archive "link" automatically as needed. * The UI can just render these items differently then. - * Although the deaddrop link is currently always the first entry - * and only present on new messages, - * there is the rough idea that it can be optionally always present - * and sorted into the list by date. - * Rendering the deaddrop in the described way - * would not add extra work in the UI then. */ @@ -3033,7 +2947,6 @@ char* dc_chat_get_info_json (dc_context_t* context, size_t chat */ -#define DC_CHAT_ID_DEADDROP 1 // virtual chat showing all messages belonging to chats flagged with chats.blocked=2 #define DC_CHAT_ID_TRASH 3 // messages that should be deleted get this chat_id; the messages are deleted from the working thread later then. This is also needed as rfc724_mid should be preset as long as the message is not deleted on the server (otherwise it is downloaded again) #define DC_CHAT_ID_ARCHIVED_LINK 6 // only an indicator in a chatlist #define DC_CHAT_ID_ALLDONE_HINT 7 // only an indicator in a chatlist @@ -3060,7 +2973,6 @@ void dc_chat_unref (dc_chat_t* chat); * Get chat ID. The chat ID is the ID under which the chat is filed in the database. * * Special IDs: - * - DC_CHAT_ID_DEADDROP (1) - Virtual chat containing messages which senders are not confirmed by the user. * - DC_CHAT_ID_ARCHIVED_LINK (6) - A link at the end of the chatlist, if present the UI should show the button "Archived chats"- * * "Normal" chat IDs are larger than these special IDs (larger than DC_CHAT_ID_LAST_SPECIAL). @@ -3152,6 +3064,25 @@ uint32_t dc_chat_get_color (const dc_chat_t* chat); int dc_chat_get_visibility (const dc_chat_t* chat); +/** + * Check if a chat is a contact request chat. + * + * UI should display such chats with a [New] badge in the chatlist. + * + * When such chat is opened, user should be presented with a set of + * options instead of the message composition area, for example: + * - Accept chat (dc_accept_chat()) + * - Block chat (dc_block_chat()) + * - Delete chat (dc_delete_chat()) + * + * @memberof dc_chat_t + * @param chat The chat object. + * @return 1=chat is a contact request chat + * 0=chat is not a contact request chat + */ +int dc_chat_is_contact_request (const dc_chat_t* chat); + + /** * Check if a group chat is still unpromoted. * @@ -3205,7 +3136,7 @@ int dc_chat_is_device_talk (const dc_chat_t* chat); /** * Check if messages can be sent to a given chat. - * This is not true e.g. for the deaddrop or for the device-talk, cmp. dc_chat_is_device_talk(). + * This is not true e.g. for contact requests or for the device-talk, cmp. dc_chat_is_device_talk(). * * Calling dc_send_msg() for these chats will fail * and the UI may decide to hide input controls therefore. @@ -3349,9 +3280,6 @@ uint32_t dc_msg_get_from_id (const dc_msg_t* msg); /** * Get the ID of chat the message belongs to. * To get details about the chat, pass the returned ID to dc_get_chat(). - * If a message is still in the deaddrop, the ID DC_CHAT_ID_DEADDROP is returned - * although internally another ID is used. - * (to get that internal id, use dc_msg_get_real_chat_id()) * * @memberof dc_msg_t * @param msg The message object. @@ -3360,19 +3288,6 @@ uint32_t dc_msg_get_from_id (const dc_msg_t* msg); uint32_t dc_msg_get_chat_id (const dc_msg_t* msg); -/** - * Get the ID of chat the message belongs to. - * To get details about the chat, pass the returned ID to dc_get_chat(). - * In contrast to dc_msg_get_chat_id(), this function returns the chat-id also - * for messages in the deaddrop. - * - * @memberof dc_msg_t - * @param msg The message object. - * @return The ID of the chat the message belongs to, 0 on errors. - */ -uint32_t dc_msg_get_real_chat_id (const dc_msg_t* msg); - - /** * Get the type of the message. * @@ -5405,11 +5320,6 @@ void dc_event_unref(dc_event_t* event); /// Used in summaries. #define DC_STR_VOICEMESSAGE 7 -/// "Contact requests" -/// -/// Used as the name for the corresponding chat. -#define DC_STR_DEADDROP 8 - /// "Image" /// /// Used in summaries. diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 8d8b14a05..572060343 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -642,23 +642,6 @@ pub unsafe extern "C" fn dc_get_chatlist( }) } -#[no_mangle] -pub unsafe extern "C" fn dc_create_chat_by_msg_id(context: *mut dc_context_t, msg_id: u32) -> u32 { - if context.is_null() { - eprintln!("ignoring careless call to dc_create_chat_by_msg_id()"); - return 0; - } - let ctx = &*context; - - block_on(async move { - chat::create_by_msg_id(&ctx, MsgId::new(msg_id)) - .await - .log_err(ctx, "Failed to create chat from msg_id") - .map(|id| id.to_u32()) - .unwrap_or(0) - }) -} - #[no_mangle] pub unsafe extern "C" fn dc_create_chat_by_contact_id( context: *mut dc_context_t, @@ -1175,8 +1158,39 @@ pub unsafe extern "C" fn dc_delete_chat(context: *mut dc_context_t, chat_id: u32 ChatId::new(chat_id) .delete(&ctx) .await - .log_err(ctx, "Failed chat delete") - .unwrap_or(()) + .ok_or_log_msg(ctx, "Failed chat delete"); + }) +} + +#[no_mangle] +pub unsafe extern "C" fn dc_block_chat(context: *mut dc_context_t, chat_id: u32) { + if context.is_null() { + eprintln!("ignoring careless call to dc_block_chat()"); + return; + } + let ctx = &*context; + + block_on(async move { + ChatId::new(chat_id) + .block(&ctx) + .await + .ok_or_log_msg(ctx, "Failed chat block"); + }) +} + +#[no_mangle] +pub unsafe extern "C" fn dc_accept_chat(context: *mut dc_context_t, chat_id: u32) { + if context.is_null() { + eprintln!("ignoring careless call to dc_accept_chat()"); + return; + } + let ctx = &*context; + + block_on(async move { + ChatId::new(chat_id) + .accept(&ctx) + .await + .ok_or_log_msg(ctx, "Failed chat accept"); }) } @@ -1569,17 +1583,6 @@ pub unsafe extern "C" fn dc_forward_msgs( }) } -#[no_mangle] -pub unsafe extern "C" fn dc_marknoticed_contact(context: *mut dc_context_t, contact_id: u32) { - if context.is_null() { - eprintln!("ignoring careless call to dc_marknoticed_contact()"); - return; - } - let ctx = &*context; - - block_on(Contact::mark_noticed(&ctx, contact_id)) -} - #[no_mangle] pub unsafe extern "C" fn dc_markseen_msgs( context: *mut dc_context_t, @@ -2530,6 +2533,16 @@ pub unsafe extern "C" fn dc_chat_get_visibility(chat: *mut dc_chat_t) -> libc::c } } +#[no_mangle] +pub unsafe extern "C" fn dc_chat_is_contact_request(chat: *mut dc_chat_t) -> libc::c_int { + if chat.is_null() { + eprintln!("ignoring careless call to dc_chat_is_contact_request()"); + return 0; + } + let ffi_chat = &*chat; + ffi_chat.chat.is_contact_request() as libc::c_int +} + #[no_mangle] pub unsafe extern "C" fn dc_chat_is_unpromoted(chat: *mut dc_chat_t) -> libc::c_int { if chat.is_null() { @@ -2731,16 +2744,6 @@ pub unsafe extern "C" fn dc_msg_get_chat_id(msg: *mut dc_msg_t) -> u32 { ffi_msg.message.get_chat_id().to_u32() } -#[no_mangle] -pub unsafe extern "C" fn dc_msg_get_real_chat_id(msg: *mut dc_msg_t) -> u32 { - if msg.is_null() { - eprintln!("ignoring careless call to dc_msg_get_real_chat_id()"); - return 0; - } - let ffi_msg = &*msg; - ffi_msg.message.get_real_chat_id().to_u32() -} - #[no_mangle] pub unsafe extern "C" fn dc_msg_get_viewtype(msg: *mut dc_msg_t) -> libc::c_int { if msg.is_null() { @@ -3096,32 +3099,6 @@ pub unsafe extern "C" fn dc_msg_get_videochat_url(msg: *mut dc_msg_t) -> *mut li .strdup() } -#[no_mangle] -pub unsafe extern "C" fn dc_decide_on_contact_request( - context: *mut dc_context_t, - msg_id: u32, - decision: libc::c_int, -) -> u32 { - if context.is_null() || msg_id <= constants::DC_MSG_ID_LAST_SPECIAL as u32 { - eprintln!("ignoring careless call to dc_decide_on_contact_request()"); - } - let ctx = &*context; - - match from_prim(decision) { - None => { - warn!(ctx, "{} is not a valid decision, ignoring", decision); - 0 - } - Some(d) => block_on(message::decide_on_contact_request( - ctx, - MsgId::new(msg_id), - d, - )) - .unwrap_or_default() - .to_u32(), - } -} - #[no_mangle] pub unsafe extern "C" fn dc_msg_get_videochat_type(msg: *mut dc_msg_t) -> libc::c_int { if msg.is_null() { diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index 3beb15a9c..3b0e797d2 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -17,7 +17,7 @@ use deltachat::imex::*; use deltachat::location; use deltachat::log::LogExt; use deltachat::lot::LotState; -use deltachat::message::{self, ContactRequestDecision, Message, MessageState, MsgId}; +use deltachat::message::{self, Message, MessageState, MsgId}; use deltachat::peerstate::*; use deltachat::qr::*; use deltachat::sql; @@ -389,10 +389,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu protect \n\ unprotect \n\ delchat \n\ - ===========================Contact requests==\n\ - decidestartchat \n\ - decideblock \n\ - decidenotnow \n\ + accept \n\ + decline \n\ ===========================Message commands==\n\ listmsgs \n\ msginfo \n\ @@ -547,7 +545,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu for i in (0..cnt).rev() { let chat = Chat::load_from_db(&context, chatlist.get_chat_id(i)).await?; println!( - "{}#{}: {} [{} fresh] {}{}{}", + "{}#{}: {} [{} fresh] {}{}{}{}", chat_prefix(&chat), chat.get_id(), chat.get_name(), @@ -559,6 +557,11 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu ChatVisibility::Pinned => "📌", }, if chat.is_protected() { "🛡️" } else { "" }, + if chat.is_contact_request() { + "🆕" + } else { + "" + }, ); let lot = chatlist.get_summary(&context, i, Some(&chat)).await?; let statestr = if chat.visibility == ChatVisibility::Archived { @@ -689,35 +692,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu println!("Single#{} created successfully.", chat_id,); } - "decidestartchat" | "createchatbymsg" => { - ensure!(!arg1.is_empty(), "Argument missing"); - let msg_id = MsgId::new(arg1.parse()?); - match message::decide_on_contact_request( - &context, - msg_id, - ContactRequestDecision::StartChat, - ) - .await - { - Some(chat_id) => { - let chat = Chat::load_from_db(&context, chat_id).await?; - println!("{}#{} created successfully.", chat_prefix(&chat), chat_id); - } - None => println!("Cannot crate chat."), - } - } - "decidenotnow" => { - ensure!(!arg1.is_empty(), "Argument missing"); - let msg_id = MsgId::new(arg1.parse()?); - message::decide_on_contact_request(&context, msg_id, ContactRequestDecision::NotNow) - .await; - } - "decideblock" => { - ensure!(!arg1.is_empty(), "Argument missing"); - let msg_id = MsgId::new(arg1.parse()?); - message::decide_on_contact_request(&context, msg_id, ContactRequestDecision::Block) - .await; - } "creategroup" => { ensure!(!arg1.is_empty(), "Argument missing."); let chat_id = @@ -1034,6 +1008,16 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu let chat_id = ChatId::new(arg1.parse()?); chat_id.delete(&context).await?; } + "accept" => { + ensure!(!arg1.is_empty(), "Argument missing."); + let chat_id = ChatId::new(arg1.parse()?); + chat_id.accept(&context).await?; + } + "blockchat" => { + ensure!(!arg1.is_empty(), "Argument missing."); + let chat_id = ChatId::new(arg1.parse()?); + chat_id.block(&context).await?; + } "msginfo" => { ensure!(!arg1.is_empty(), "Argument missing."); let id = MsgId::new(arg1.parse()?); diff --git a/examples/repl/main.rs b/examples/repl/main.rs index 59bda2e84..4838d2ef5 100644 --- a/examples/repl/main.rs +++ b/examples/repl/main.rs @@ -167,14 +167,11 @@ const DB_COMMANDS: [&str; 10] = [ "housekeeping", ]; -const CHAT_COMMANDS: [&str; 34] = [ +const CHAT_COMMANDS: [&str; 33] = [ "listchats", "listarchived", "chat", "createchat", - "decidestartchat", - "decideblock", - "decidenotnow", "creategroup", "createverified", "addmember", @@ -202,6 +199,8 @@ const CHAT_COMMANDS: [&str; 34] = [ "protect", "unprotect", "delchat", + "accept", + "blockchat", ]; const MESSAGE_COMMANDS: [&str; 6] = [ "listmsgs", diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 8e718fb7b..cbda1412b 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -330,9 +330,6 @@ class Account(object): """ Create a 1:1 chat with Account, Contact or e-mail address. """ return self.create_contact(obj).create_chat() - def _create_chat_by_message_id(self, msg_id): - return Chat(self, lib.dc_create_chat_by_msg_id(self._dc_context, msg_id)) - def create_group_chat(self, name, contacts=None, verified=False): """ create a new group chat object. @@ -367,9 +364,6 @@ class Account(object): chatlist.append(Chat(self, chat_id)) return chatlist - def get_deaddrop_chat(self): - return Chat(self, const.DC_CHAT_ID_DEADDROP) - def get_device_chat(self): return Contact(self, const.DC_CONTACT_ID_DEVICE).create_chat() diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index d645a48b3..4924fee15 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -50,6 +50,14 @@ class Chat(object): """ lib.dc_delete_chat(self.account._dc_context, self.id) + def block(self): + """Block this chat.""" + lib.dc_block_chat(self.account._dc_context, self.id) + + def accept(self): + """Accept this contact request chat.""" + lib.dc_accept_chat(self.account._dc_context, self.id) + # ------ chat status/metadata API ------------------------------ def is_group(self): @@ -59,13 +67,6 @@ class Chat(object): """ return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_GROUP - def is_deaddrop(self): - """ return true if this chat is a deaddrop chat. - - :returns: True if chat is the deaddrop chat, False otherwise. - """ - return self.id == const.DC_CHAT_ID_DEADDROP - def is_muted(self): """ return true if this chat is muted. @@ -73,6 +74,13 @@ class Chat(object): """ return lib.dc_chat_is_muted(self._dc_chat) + def is_contact_request(self): + """ return True if this chat is a contact request chat. + + :returns: True if chat is a contact request chat, False otherwise. + """ + return lib.dc_chat_is_contact_request(self._dc_chat) + def is_promoted(self): """ return True if this chat is promoted, i.e. the member contacts are aware of their membership, @@ -84,7 +92,7 @@ class Chat(object): def can_send(self): """Check if messages can be sent to a give chat. - This is not true eg. for the deaddrop or for the device-talk + This is not true eg. for the contact requests or for the device-talk :returns: True if the chat is writable, False otherwise """ diff --git a/python/src/deltachat/hookspec.py b/python/src/deltachat/hookspec.py index fa58d5f15..27ce6b085 100644 --- a/python/src/deltachat/hookspec.py +++ b/python/src/deltachat/hookspec.py @@ -43,7 +43,7 @@ class PerAccount: @account_hookspec def ac_incoming_message(self, message): - """ Called on any incoming message (to deaddrop or chat). """ + """ Called on any incoming message (both existing chats and contact requests). """ @account_hookspec def ac_outgoing_message(self, message): diff --git a/python/src/deltachat/message.py b/python/src/deltachat/message.py index cb4646e29..ff82677c1 100644 --- a/python/src/deltachat/message.py +++ b/python/src/deltachat/message.py @@ -61,16 +61,13 @@ class Message(object): def create_chat(self): """ create or get an existing chat (group) object for this message. - If the message is a deaddrop contact request + If the message is a contact request the sender will become an accepted contact. :returns: a :class:`deltachat.chat.Chat` object. """ - from .chat import Chat - chat_id = lib.dc_create_chat_by_msg_id(self.account._dc_context, self.id) - ctx = self.account._dc_context - self._dc_msg = ffi.gc(lib.dc_get_msg(ctx, self.id), lib.dc_msg_unref) - return Chat(self.account, chat_id) + self.chat.accept() + return self.chat @props.with_doc def id(self): diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 24dd7ce55..77101b37e 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -909,12 +909,11 @@ class TestOnlineAccount: msg_in = ac2.get_message_by_id(msg_out.id) assert msg_in.text == "message2" - lp.sec("ac2: check that the message arrive in deaddrop") + lp.sec("ac2: check that the message arrived in a chat") chat2 = msg_in.chat assert msg_in in chat2.get_messages() assert not msg_in.is_forwarded() - assert chat2.is_deaddrop() - assert chat2 == ac2.get_deaddrop_chat() + assert chat2.is_contact_request() lp.sec("ac2: create new chat and forward message to it") chat3 = ac2.create_group_chat("newgroup") @@ -979,16 +978,16 @@ class TestOnlineAccount: assert not msg2.is_forwarded() assert msg2.get_sender_contact().display_name == ac1.get_config("displayname") - lp.sec("check the message arrived in contact-requests/deaddrop") + lp.sec("check the message arrived in contact request chat") chat2 = msg2.chat assert msg2 in chat2.get_messages() - assert chat2.is_deaddrop() + assert chat2.is_contact_request() assert chat2.count_fresh_messages() == 1 assert msg2.time_received >= msg1.time_sent lp.sec("create new chat with contact and verify it's proper") chat2b = msg2.create_chat() - assert not chat2b.is_deaddrop() + assert not chat2b.is_contact_request() assert chat2b.count_fresh_messages() == 1 lp.sec("mark chat as noticed") @@ -1853,7 +1852,7 @@ class TestOnlineAccount: lp.sec("ac2: wait for receiving message and avatar from ac1") msg2 = ac2._evtracker.wait_next_messages_changed() - assert msg2.chat.is_deaddrop() + assert msg2.chat.is_contact_request() received_path = msg2.get_sender_contact().get_profile_image() assert open(received_path, "rb").read() == open(p, "rb").read() @@ -1928,6 +1927,8 @@ class TestOnlineAccount: ev = in_list.get(timeout=10) assert ev.action == "chat-modified" ev = in_list.get(timeout=10) + assert ev.action == "chat-modified" + ev = in_list.get(timeout=10) assert ev.action == "added" assert ev.message.get_sender_contact().addr == ac1_addr assert ev.contact.addr == "devnull@testrun.org" diff --git a/src/chat.rs b/src/chat.rs index e1ce28de3..7583eca15 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -17,9 +17,9 @@ use crate::color::str_to_color; use crate::config::Config; use crate::constants::{ Blocked, Chattype, Viewtype, DC_CHAT_ID_ALLDONE_HINT, DC_CHAT_ID_ARCHIVED_LINK, - DC_CHAT_ID_DEADDROP, DC_CHAT_ID_LAST_SPECIAL, DC_CHAT_ID_TRASH, DC_CONTACT_ID_DEVICE, - DC_CONTACT_ID_INFO, DC_CONTACT_ID_LAST_SPECIAL, DC_CONTACT_ID_SELF, DC_GCM_ADDDAYMARKER, - DC_GCM_INFO_ONLY, DC_RESEND_USER_AVATAR_DAYS, + DC_CHAT_ID_LAST_SPECIAL, DC_CHAT_ID_TRASH, DC_CONTACT_ID_DEVICE, DC_CONTACT_ID_INFO, + DC_CONTACT_ID_LAST_SPECIAL, DC_CONTACT_ID_SELF, DC_GCM_ADDDAYMARKER, DC_GCM_INFO_ONLY, + DC_RESEND_USER_AVATAR_DAYS, }; use crate::contact::{addr_cmp, Contact, Origin, VerifiedStatus}; use crate::context::Context; @@ -113,15 +113,6 @@ impl ChatId { (0..=DC_CHAT_ID_LAST_SPECIAL.0).contains(&self.0) } - /// Chat ID which represents the deaddrop chat. - /// - /// This is a virtual chat showing all messages belonging to chats - /// flagged with [Blocked::Deaddrop]. Usually the UI will show - /// these messages as contact requests. - pub fn is_deaddrop(self) -> bool { - self == DC_CHAT_ID_DEADDROP - } - /// Chat ID for messages which need to be deleted. /// /// Messages which should be deleted get this chat ID and are @@ -180,14 +171,11 @@ impl ChatId { /// /// This should be used when **a user action** creates a chat 1:1, it ensures the chat /// exists and is unblocked and scales the [`Contact`]'s origin. - /// - /// If a chat was in the deaddrop unblocking is how it becomes a normal chat and it will - /// look to the user like the chat was newly created. pub async fn create_for_contact(context: &Context, contact_id: u32) -> Result { let chat_id = match ChatIdBlocked::lookup_by_contact(context, contact_id).await? { Some(chat) => { if chat.blocked != Blocked::Not { - chat.id.unblock(context).await; + chat.id.unblock(context).await?; } chat.id } @@ -227,23 +215,90 @@ impl ChatId { Ok(()) } - pub async fn set_blocked(self, context: &Context, new_blocked: Blocked) -> bool { + /// Updates chat blocked status. + /// + /// Returns true if the value was modified. + async fn set_blocked(self, context: &Context, new_blocked: Blocked) -> Result { if self.is_special() { - warn!(context, "ignoring setting of Block-status for {}", self); - return false; + bail!("ignoring setting of Block-status for {}", self); } - context + let count = context .sql .execute( - "UPDATE chats SET blocked=? WHERE id=?;", + "UPDATE chats SET blocked=?1 WHERE id=?2 AND blocked != ?1", paramsv![new_blocked, self], ) - .await - .is_ok() + .await?; + Ok(count > 0) } - pub async fn unblock(self, context: &Context) { - self.set_blocked(context, Blocked::Not).await; + /// Blocks the chat as a result of explicit user action. + pub async fn block(self, context: &Context) -> Result<()> { + let chat = Chat::load_from_db(context, self).await?; + + match chat.typ { + Chattype::Undefined => bail!("Can't block chat of undefined chattype"), + Chattype::Single => { + for contact_id in get_chat_contacts(context, self).await? { + if contact_id != DC_CONTACT_ID_SELF { + info!( + context, + "Blocking the contact {} to block 1:1 chat", contact_id + ); + Contact::block(context, contact_id).await?; + } + } + } + Chattype::Group => { + info!(context, "Can't block groups yet, deleting the chat"); + self.delete(context).await?; + } + Chattype::Mailinglist => { + if self.set_blocked(context, Blocked::Manually).await? { + context.emit_event(EventType::ChatModified(self)); + } + } + } + + Ok(()) + } + + /// Unblocks the chat. + pub async fn unblock(self, context: &Context) -> Result<()> { + self.set_blocked(context, Blocked::Not).await?; + Ok(()) + } + + /// Accept the contact request. + /// + /// Unblocks the chat and scales up origin of contacts. + pub async fn accept(self, context: &Context) -> Result<()> { + let chat = Chat::load_from_db(context, self).await?; + + match chat.typ { + Chattype::Undefined => bail!("Can't accept chat of undefined chattype"), + Chattype::Single | Chattype::Group => { + // User has "created a chat" with all these contacts. + // + // Previously accepting a chat literally created a chat because unaccepted chats + // went to "contact requests" list rather than normal chatlist. + for contact_id in get_chat_contacts(context, self).await? { + if contact_id != DC_CONTACT_ID_SELF { + Contact::scaleup_origin_by_id(context, contact_id, Origin::CreateChat) + .await; + } + } + } + Chattype::Mailinglist => { + // If the message is from a mailing list, the contacts are not counted as "known" + } + } + + if self.set_blocked(context, Blocked::Not).await? { + context.emit_event(EventType::ChatModified(self)); + } + + Ok(()) } /// Sets protection without sending a message. @@ -580,27 +635,13 @@ impl ChatId { /// Returns number of messages in a chat. pub async fn get_msg_cnt(self, context: &Context) -> Result { - let count = if self.is_deaddrop() { - context - .sql - .count( - "SELECT COUNT(*) - FROM msgs - WHERE hidden=0 - AND from_id!=? - AND chat_id IN (SELECT id FROM chats WHERE blocked=?)", - paramsv![DC_CONTACT_ID_INFO, Blocked::Deaddrop], - ) - .await? - } else { - context - .sql - .count( - "SELECT COUNT(*) FROM msgs WHERE hidden=0 AND chat_id=?", - paramsv![self], - ) - .await? - }; + let count = context + .sql + .count( + "SELECT COUNT(*) FROM msgs WHERE hidden=0 AND chat_id=?", + paramsv![self], + ) + .await?; Ok(count as usize) } @@ -615,32 +656,17 @@ impl ChatId { // the times are average, no matter if there are fresh messages or not - // and have to be multiplied by the number of items shown at once on the chatlist, // so savings up to 2 seconds are possible on older devices - newer ones will feel "snappier" :) - let count = if self.is_deaddrop() { - context - .sql - .count( - "SELECT COUNT(*) - FROM msgs - WHERE state=? - AND hidden=0 - AND from_id!=? - AND chat_id IN (SELECT id FROM chats WHERE blocked=?)", - paramsv![MessageState::InFresh, DC_CONTACT_ID_INFO, Blocked::Deaddrop], - ) - .await? - } else { - context - .sql - .count( - "SELECT COUNT(*) + let count = context + .sql + .count( + "SELECT COUNT(*) FROM msgs WHERE state=? AND hidden=0 AND chat_id=?;", - paramsv![MessageState::InFresh, self], - ) - .await? - }; + paramsv![MessageState::InFresh, self], + ) + .await?; Ok(count as usize) } @@ -778,9 +804,7 @@ impl ChatId { impl std::fmt::Display for ChatId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if self.is_deaddrop() { - write!(f, "Chat#Deadrop") - } else if self.is_trash() { + if self.is_trash() { write!(f, "Chat#Trash") } else if self.is_archived_link() { write!(f, "Chat#ArchivedLink") @@ -867,9 +891,7 @@ impl Chat { .await .context(format!("Failed loading chat {} from database", chat_id))?; - if chat.id.is_deaddrop() { - chat.name = stock_str::dead_drop(context).await; - } else if chat.id.is_archived_link() { + if chat.id.is_archived_link() { let tempname = stock_str::archived_chats(context).await; let cnt = dc_get_archived_cnt(context).await?; chat.name = format!("{} ({})", tempname, cnt); @@ -918,6 +940,7 @@ impl Chat { !self.id.is_special() && !self.is_device_talk() && !self.is_mailing_list() + && !self.is_contact_request() && (self.typ == Chattype::Single || is_contact_in_chat(context, self.id, DC_CONTACT_ID_SELF).await) } @@ -1019,6 +1042,14 @@ impl Chat { self.visibility } + /// Returns true if chat is a contact request. + /// + /// Messages cannot be sent to such chat and read receipts are not + /// sent until the chat is manually unblocked. + pub fn is_contact_request(&self) -> bool { + self.blocked == Blocked::Request + } + pub fn is_unpromoted(&self) -> bool { self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 } @@ -1353,58 +1384,12 @@ pub struct ChatInfo { /// Ephemeral message timer. pub ephemeral_timer: EphemeralTimer, // ToDo: - // - [ ] deaddrop, // - [ ] summary, // - [ ] lastUpdated, // - [ ] freshMessageCounter, // - [ ] email } -/// Create a chat from a message ID. -/// -/// Typically you'd do this for a message ID found in the -/// [DC_CHAT_ID_DEADDROP] which turns the chat the message belongs to -/// into a normal chat. The chat can be a 1:1 chat or a group chat -/// and all messages belonging to the chat will be moved from the -/// deaddrop to the normal chat. -/// -/// In reality the messages already belong to this chat as receive_imf -/// always creates chat IDs appropriately, so this function really -/// only unblocks the chat and "scales up" the origin of the contact -/// the message is from. -/// -/// If prompting the user before calling this function, they should be -/// asked whether they want to chat with the **contact** the message -/// is from and **not** the group name since this can be really weird -/// and confusing when taken from subject of implicit groups. -/// -/// # Returns -/// -/// The "created" chat ID is returned. -pub async fn create_by_msg_id(context: &Context, msg_id: MsgId) -> Result { - let msg = Message::load_from_db(context, msg_id).await?; - let chat = Chat::load_from_db(context, msg.chat_id).await?; - ensure!( - !chat.id.is_special(), - "Message can not belong to a special chat" - ); - if chat.blocked != Blocked::Not { - chat.id.unblock(context).await; - - // Sending with 0s as data since multiple messages may have changed. - context.emit_event(EventType::MsgsChanged { - chat_id: ChatId::new(0), - msg_id: MsgId::new(0), - }); - } - - // If the message is from a mailing list, the contacts are not counted as "known" - if !chat.is_mailing_list() { - Contact::scaleup_origin_by_id(context, msg.from_id, Origin::CreateChat).await; - } - Ok(chat.id) -} - pub(crate) async fn update_saved_messages_icon(context: &Context) -> Result<()> { // if there is no saved-messages chat, there is nothing to update. this is no error. if let Some(chat_id) = ChatId::lookup_by_contact(context, DC_CONTACT_ID_SELF).await? { @@ -1942,27 +1927,7 @@ pub async fn get_chat_msgs( Ok(ret) }; - let items = if chat_id.is_deaddrop() { - context - .sql - .query_map( - "SELECT m.id AS id, m.timestamp AS timestamp - FROM msgs m - LEFT JOIN chats - ON m.chat_id=chats.id - LEFT JOIN contacts - ON m.from_id=contacts.id - WHERE m.from_id!=? - AND m.hidden=0 - AND chats.blocked=2 - AND contacts.blocked=0 - ORDER BY m.timestamp,m.id;", - paramsv![DC_CONTACT_ID_INFO], - process_row, - process_rows, - ) - .await? - } else if (flags & DC_GCM_INFO_ONLY) != 0 { + let items = if (flags & DC_GCM_INFO_ONLY) != 0 { context .sql .query_map( @@ -2021,31 +1986,6 @@ pub(crate) async fn marknoticed_chat_if_older_than( } pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()> { - // for the virtual deaddrop chat-id, - // mark all messages that will appear in the deaddrop as noticed - if chat_id.is_deaddrop() { - if context - .sql - .execute( - "UPDATE msgs - SET state=?1 - WHERE state=?2 - AND hidden=0 - AND chat_id IN (SELECT id FROM chats WHERE blocked=?3);", - paramsv![ - MessageState::InNoticed, - MessageState::InFresh, - Blocked::Deaddrop - ], - ) - .await? - > 0 - { - context.emit_event(EventType::MsgsNoticed(chat_id)); - } - return Ok(()); - } - // "WHERE" below uses the index `(state, hidden, chat_id)`, see get_fresh_msg_cnt() for reasoning // the additional SELECT statement may speed up things as no write-blocking is needed. let exists = context @@ -2177,13 +2117,6 @@ pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Result Result<()> { + async fn test_contact_request_fresh_messages() -> Result<()> { let t = TestContext::new_alice().await; let chats = Chatlist::try_load(&t, 0, None, None).await?; @@ -4043,10 +3965,14 @@ mod tests { let chats = Chatlist::try_load(&t, 0, None, None).await?; assert_eq!(chats.len(), 1); - assert_eq!(chats.get_chat_id(0), DC_CHAT_ID_DEADDROP); - assert_eq!(DC_CHAT_ID_DEADDROP.get_msg_cnt(&t).await?, 1); - assert_eq!(DC_CHAT_ID_DEADDROP.get_fresh_msg_cnt(&t).await?, 1); - let msgs = get_chat_msgs(&t, DC_CHAT_ID_DEADDROP, 0, None).await?; + let chat_id = chats.get_chat_id(0); + assert!(Chat::load_from_db(&t, chat_id) + .await + .unwrap() + .is_contact_request()); + assert_eq!(chat_id.get_msg_cnt(&t).await?, 1); + assert_eq!(chat_id.get_fresh_msg_cnt(&t).await?, 1); + let msgs = get_chat_msgs(&t, chat_id, 0, None).await?; assert_eq!(msgs.len(), 1); let msg_id = match msgs.first().unwrap() { ChatItem::Message { msg_id } => *msg_id, @@ -4054,21 +3980,21 @@ mod tests { }; let msg = message::Message::load_from_db(&t, msg_id).await?; assert_eq!(msg.state, MessageState::InFresh); - assert_eq!(t.get_fresh_msgs().await?.len(), 0); // deaddrop is excluded from global badge - marknoticed_chat(&t, DC_CHAT_ID_DEADDROP).await?; + // Contact requests are excluded from global badge. + assert_eq!(t.get_fresh_msgs().await?.len(), 0); let chats = Chatlist::try_load(&t, 0, None, None).await?; - assert_eq!(chats.len(), 0); + assert_eq!(chats.len(), 1); let msg = message::Message::load_from_db(&t, msg_id).await?; - assert_eq!(msg.state, MessageState::InNoticed); + assert_eq!(msg.state, MessageState::InFresh); assert_eq!(t.get_fresh_msgs().await?.len(), 0); Ok(()) } #[async_std::test] - async fn test_classic_deaddrop_chat() -> Result<()> { + async fn test_classic_email_chat() -> Result<()> { let alice = TestContext::new_alice().await; // Alice enables receiving classic emails. @@ -4092,10 +4018,11 @@ mod tests { ) .await?; - // There is one message in the contact requests chat. - assert_eq!(DC_CHAT_ID_DEADDROP.get_fresh_msg_cnt(&alice).await?, 1); + let msg = alice.get_last_msg().await; + let chat_id = msg.chat_id; + assert_eq!(chat_id.get_fresh_msg_cnt(&alice).await?, 1); - let msgs = get_chat_msgs(&alice, DC_CHAT_ID_DEADDROP, 0, None).await?; + let msgs = get_chat_msgs(&alice, chat_id, 0, None).await?; assert_eq!(msgs.len(), 1); // Alice disables receiving classic emails. @@ -4104,10 +4031,10 @@ mod tests { .await .unwrap(); - // Already received classic email should still be in the contact requests. - assert_eq!(DC_CHAT_ID_DEADDROP.get_fresh_msg_cnt(&alice).await?, 1); + // Already received classic email should still be in the chat. + assert_eq!(chat_id.get_fresh_msg_cnt(&alice).await?, 1); - let msgs = get_chat_msgs(&alice, DC_CHAT_ID_DEADDROP, 0, None).await?; + let msgs = get_chat_msgs(&alice, chat_id, 0, None).await?; assert_eq!(msgs.len(), 1); Ok(()) diff --git a/src/chatlist.rs b/src/chatlist.rs index 8603214fb..0fc6f1ffd 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -4,9 +4,9 @@ use anyhow::{bail, ensure, Result}; use crate::chat::{update_special_chat_names, Chat, ChatId, ChatVisibility}; use crate::constants::{ - Chattype, DC_CHAT_ID_ALLDONE_HINT, DC_CHAT_ID_ARCHIVED_LINK, DC_CHAT_ID_DEADDROP, - DC_CONTACT_ID_DEVICE, DC_CONTACT_ID_SELF, DC_CONTACT_ID_UNDEFINED, DC_GCL_ADD_ALLDONE_HINT, - DC_GCL_ARCHIVED_ONLY, DC_GCL_FOR_FORWARDING, DC_GCL_NO_SPECIALS, + Chattype, DC_CHAT_ID_ALLDONE_HINT, DC_CHAT_ID_ARCHIVED_LINK, DC_CONTACT_ID_DEVICE, + DC_CONTACT_ID_SELF, DC_CONTACT_ID_UNDEFINED, DC_GCL_ADD_ALLDONE_HINT, DC_GCL_ARCHIVED_ONLY, + DC_GCL_FOR_FORWARDING, DC_GCL_NO_SPECIALS, }; use crate::contact::Contact; use crate::context::Context; @@ -34,11 +34,8 @@ use crate::stock_str; /// and for each messages that is scrolled into view, dc_get_msg() is called then. /// /// Why no listflags? -/// Without listflags, dc_get_chatlist() adds the deaddrop and the archive "link" automatically as needed. -/// The UI can just render these items differently then. Although the deaddrop link is currently always the -/// first entry and only present on new messages, there is the rough idea that it can be optionally always -/// present and sorted into the list by date. Rendering the deaddrop in the described way -/// would not add extra work in the UI then. +/// Without listflags, dc_get_chatlist() adds the archive "link" automatically as needed. +/// The UI can just render these items differently then. #[derive(Debug)] pub struct Chatlist { /// Stores pairs of `chat_id, message_id` @@ -58,12 +55,6 @@ impl Chatlist { /// /// By default, the function adds some special entries to the list. /// These special entries can be identified by the ID returned by chatlist.get_chat_id(): - /// - DC_CHAT_ID_DEADDROP (1) - this special chat is present if there are - /// messages from addresses that have no relationship to the configured account. - /// The last of these messages is represented by DC_CHAT_ID_DEADDROP and you can retrieve details - /// about it with chatlist.get_msg_id(). Typically, the UI asks the user "Do you want to chat with NAME?" - /// and offers the options "Start chat", "Block" and "Not now"; - /// The decision should be passed to dc_decide_on_contact_request(). /// - DC_CHAT_ID_ARCHIVED_LINK (6) - this special chat is present if the user has /// archived *any* chat using dc_set_chat_visibility(). The UI should show a link as /// "Show archived chats", if the user clicks this item, the UI should show a @@ -79,9 +70,9 @@ impl Chatlist { /// the pseudo-chat DC_CHAT_ID_ARCHIVED_LINK is added if there are *any* archived /// chats /// - the flag DC_GCL_FOR_FORWARDING sorts "Saved messages" to the top of the chatlist - /// and hides the device-chat, + /// and hides the device-chat and contact requests /// typically used on forwarding, may be combined with DC_GCL_NO_SPECIALS - /// - if the flag DC_GCL_NO_SPECIALS is set, deaddrop and archive link are not added + /// - if the flag DC_GCL_NO_SPECIALS is set, archive link is not added /// to the list (may be used eg. for selecting chats on forwarding, the flag is /// not needed when DC_GCL_ARCHIVED_ONLY is already set) /// - if the flag DC_GCL_ADD_ALLDONE_HINT is set, DC_CHAT_ID_ALLDONE_HINT @@ -136,13 +127,8 @@ impl Chatlist { // timestamp // - the list starts with the newest chats // - // nb: the query currently shows messages from blocked - // contacts in groups. however, for normal-groups, this is - // okay as the message is also returned by dc_get_chat_msgs() - // (otherwise it would be hard to follow conversations, wa and - // tg do the same) for the deaddrop, however, they should - // really be hidden, however, _currently_ the deaddrop is not - // shown at all permanent in the chatlist. + // The query shows messages from blocked contacts in + // groups. Otherwise it would be hard to follow conversations. let mut ids = if let Some(query_contact_id) = query_contact_id { // show chats shared with a given contact context.sql.query_map( @@ -157,7 +143,7 @@ impl Chatlist { AND (hidden=0 OR state=?1) ORDER BY timestamp DESC, id DESC LIMIT 1) WHERE c.id>9 - AND c.blocked=0 + AND c.blocked!=1 AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?2) GROUP BY c.id ORDER BY c.archived=?3 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", @@ -184,7 +170,7 @@ impl Chatlist { AND (hidden=0 OR state=?) ORDER BY timestamp DESC, id DESC LIMIT 1) WHERE c.id>9 - AND c.blocked=0 + AND c.blocked!=1 AND c.archived=1 GROUP BY c.id ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", @@ -218,7 +204,7 @@ impl Chatlist { AND (hidden=0 OR state=?1) ORDER BY timestamp DESC, id DESC LIMIT 1) WHERE c.id>9 AND c.id!=?2 - AND c.blocked=0 + AND c.blocked!=1 AND c.name LIKE ?3 GROUP BY c.id ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", @@ -236,7 +222,7 @@ impl Chatlist { } else { ChatId::new(0) }; - let mut ids = context.sql.query_map( + let ids = context.sql.query_map( "SELECT c.id, m.id FROM chats c LEFT JOIN msgs m @@ -248,22 +234,15 @@ impl Chatlist { AND (hidden=0 OR state=?1) ORDER BY timestamp DESC, id DESC LIMIT 1) WHERE c.id>9 AND c.id!=?2 - AND c.blocked=0 - AND NOT c.archived=?3 + AND (c.blocked=0 OR (c.blocked=2 AND NOT ?3)) + AND NOT c.archived=?4 GROUP BY c.id - ORDER BY c.id=?4 DESC, c.archived=?5 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", - paramsv![MessageState::OutDraft, skip_id, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned], + ORDER BY c.id=?5 DESC, c.archived=?6 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", + paramsv![MessageState::OutDraft, skip_id, flag_for_forwarding, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned], process_row, process_rows, ).await?; if !flag_no_specials { - if let Some(last_deaddrop_fresh_msg_id) = - get_last_deaddrop_fresh_msg(context).await? - { - if !flag_for_forwarding { - ids.insert(0, (DC_CHAT_ID_DEADDROP, Some(last_deaddrop_fresh_msg_id))); - } - } add_archived_link_item = true; } ids @@ -407,28 +386,6 @@ pub async fn dc_get_archived_cnt(context: &Context) -> Result { Ok(count) } -async fn get_last_deaddrop_fresh_msg(context: &Context) -> Result> { - // We have an index over the state-column, this should be - // sufficient as there are typically only few fresh messages. - let id = context - .sql - .query_get_value( - concat!( - "SELECT m.id", - " FROM msgs m", - " LEFT JOIN chats c", - " ON c.id=m.chat_id", - " WHERE m.state=10", - " AND m.hidden=0", - " AND c.blocked=2", - " ORDER BY m.timestamp DESC, m.id DESC;" - ), - paramsv![], - ) - .await?; - Ok(id) -} - #[cfg(test)] mod tests { use super::*; @@ -436,8 +393,6 @@ mod tests { use crate::chat::{create_group_chat, get_chat_contacts, ProtectionStatus}; use crate::constants::Viewtype; use crate::dc_receive_imf::dc_receive_imf; - use crate::message; - use crate::message::ContactRequestDecision; use crate::stock_str::StockMessage; use crate::test_utils::TestContext; @@ -547,7 +502,7 @@ mod tests { async fn test_search_single_chat() -> anyhow::Result<()> { let t = TestContext::new_alice().await; - // receive a one-to-one-message, accept contact request + // receive a one-to-one-message dc_receive_imf( &t, b"From: Bob Authname \n\ @@ -565,15 +520,13 @@ mod tests { .await?; let chats = Chatlist::try_load(&t, 0, Some("Bob Authname"), None).await?; - assert_eq!(chats.len(), 0); + // Contact request should be searchable + assert_eq!(chats.len(), 1); let msg = t.get_last_msg().await; - assert_eq!(msg.get_chat_id(), DC_CHAT_ID_DEADDROP); + let chat_id = msg.get_chat_id(); + chat_id.accept(&t).await.unwrap(); - let chat_id = - message::decide_on_contact_request(&t, msg.get_id(), ContactRequestDecision::StartChat) - .await - .unwrap(); let contacts = get_chat_contacts(&t, chat_id).await?; let contact_id = *contacts.first().unwrap(); let chat = Chat::load_from_db(&t, chat_id).await?; @@ -611,7 +564,7 @@ mod tests { async fn test_search_single_chat_without_authname() -> anyhow::Result<()> { let t = TestContext::new_alice().await; - // receive a one-to-one-message without authname set, accept contact request + // receive a one-to-one-message without authname set dc_receive_imf( &t, b"From: bob@example.org\n\ @@ -629,10 +582,8 @@ mod tests { .await?; let msg = t.get_last_msg().await; - let chat_id = - message::decide_on_contact_request(&t, msg.get_id(), ContactRequestDecision::StartChat) - .await - .unwrap(); + let chat_id = msg.get_chat_id(); + chat_id.accept(&t).await.unwrap(); let contacts = get_chat_contacts(&t, chat_id).await?; let contact_id = *contacts.first().unwrap(); let chat = Chat::load_from_db(&t, chat_id).await?; diff --git a/src/constants.rs b/src/constants.rs index c4e194766..6f32d899a 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -25,7 +25,7 @@ pub static DC_VERSION_STR: Lazy = Lazy::new(|| env!("CARGO_PKG_VERSION") pub enum Blocked { Not = 0, Manually = 1, - Deaddrop = 2, + Request = 2, } impl Default for Blocked { @@ -123,8 +123,6 @@ pub const DC_RESEND_USER_AVATAR_DAYS: i64 = 14; // do not use too small value that will annoy users checking for nonexistant updates. pub const DC_OUTDATED_WARNING_DAYS: i64 = 365; -/// virtual chat showing all messages belonging to chats flagged with chats.blocked=2 -pub const DC_CHAT_ID_DEADDROP: ChatId = ChatId::new(1); /// messages that should be deleted get this chat_id; the messages are deleted from the working thread later then. This is also needed as rfc724_mid should be preset as long as the message is not deleted on the server (otherwise it is downloaded again) pub const DC_CHAT_ID_TRASH: ChatId = ChatId::new(3); /// only an indicator in a chatlist @@ -404,7 +402,7 @@ mod tests { assert_eq!(Blocked::Not, Blocked::default()); assert_eq!(Blocked::Not, Blocked::from_i32(0).unwrap()); assert_eq!(Blocked::Manually, Blocked::from_i32(1).unwrap()); - assert_eq!(Blocked::Deaddrop, Blocked::from_i32(2).unwrap()); + assert_eq!(Blocked::Request, Blocked::from_i32(2).unwrap()); } #[test] diff --git a/src/contact.rs b/src/contact.rs index a021b80b5..9539d7232 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -14,8 +14,8 @@ use crate::chat::ChatId; use crate::color::str_to_color; use crate::config::Config; use crate::constants::{ - Blocked, Chattype, DC_CHAT_ID_DEADDROP, DC_CONTACT_ID_DEVICE, DC_CONTACT_ID_DEVICE_ADDR, - DC_CONTACT_ID_LAST_SPECIAL, DC_CONTACT_ID_SELF, DC_GCL_ADD_SELF, DC_GCL_VERIFIED_ONLY, + Blocked, Chattype, DC_CONTACT_ID_DEVICE, DC_CONTACT_ID_DEVICE_ADDR, DC_CONTACT_ID_LAST_SPECIAL, + DC_CONTACT_ID_SELF, DC_GCL_ADD_SELF, DC_GCL_VERIFIED_ONLY, }; use crate::context::Context; use crate::dc_tools::{dc_get_abs_path, improve_single_line_input, EmailAddress}; @@ -277,20 +277,15 @@ impl Contact { } /// Mark messages from a contact as noticed. - /// The contact is expected to belong to the deaddrop, - /// therefore, DC_EVENT_MSGS_NOTICED(DC_CHAT_ID_DEADDROP) is emitted. - pub async fn mark_noticed(context: &Context, id: u32) { - if context + pub async fn mark_noticed(context: &Context, id: u32) -> Result<()> { + context .sql .execute( "UPDATE msgs SET state=? WHERE from_id=? AND state=?;", paramsv![MessageState::InNoticed, id as i32, MessageState::InFresh], ) - .await - .is_ok() - { - context.emit_event(EventType::MsgsNoticed(DC_CHAT_ID_DEADDROP)); - } + .await?; + Ok(()) } /// Check if an e-mail address belongs to a known and unblocked contact. @@ -1200,7 +1195,7 @@ WHERE type=? AND id IN ( .await .is_ok() { - Contact::mark_noticed(context, contact_id).await; + Contact::mark_noticed(context, contact_id).await?; context.emit_event(EventType::ContactsChanged(Some(contact_id))); } @@ -1208,7 +1203,7 @@ WHERE type=? AND id IN ( // if the contact is a mailinglist address explicitly created to allow unblocking if !new_blocking && contact.origin == Origin::MailinglistAddress { if let Ok((chat_id, _, _)) = chat::get_chat_id_by_grpid(context, contact.addr).await { - chat_id.set_blocked(context, Blocked::Not).await; + chat_id.unblock(context).await?; } } } diff --git a/src/context.rs b/src/context.rs index ee12b6d04..51bec68df 100644 --- a/src/context.rs +++ b/src/context.rs @@ -279,8 +279,8 @@ impl Context { let l2 = LoginParam::from_database(self, "configured_").await?; let displayname = self.get_config(Config::Displayname).await?; let chats = get_chat_cnt(self).await? as usize; - let real_msgs = message::get_real_msg_cnt(self).await as usize; - let deaddrop_msgs = message::get_deaddrop_msg_cnt(self).await as usize; + let unblocked_msgs = message::get_unblocked_msg_cnt(self).await as usize; + let request_msgs = message::get_request_msg_cnt(self).await as usize; let contacts = Contact::get_real_cnt(self).await? as usize; let is_configured = self.get_config_int(Config::Configured).await?; let dbversion = self @@ -336,8 +336,8 @@ impl Context { // insert values res.insert("bot", self.get_config_int(Config::Bot).await?.to_string()); res.insert("number_of_chats", chats.to_string()); - res.insert("number_of_chat_messages", real_msgs.to_string()); - res.insert("messages_in_contact_requests", deaddrop_msgs.to_string()); + res.insert("number_of_chat_messages", unblocked_msgs.to_string()); + res.insert("messages_in_contact_requests", request_msgs.to_string()); res.insert("number_of_contacts", contacts.to_string()); res.insert("database_dir", self.get_dbfile().display().to_string()); res.insert("database_version", dbversion.to_string()); @@ -422,7 +422,7 @@ impl Context { Ok(res) } - /// Get a list of fresh, unmuted messages in any chat but deaddrop. + /// Get a list of fresh, unmuted messages in unblocked chats. /// /// The list starts with the most recent message /// and is typically used to show notifications. diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 351fe9c8d..fbcf9524c 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -351,9 +351,6 @@ pub async fn from_field_to_contact_id( context, "mail has an empty From header: {:?}", from_address_list ); - // if there is no from given, from_id stays 0 which is just fine. These messages - // are very rare, however, we have to add them to the database (they go to the - // "deaddrop" chat) to avoid a re-download from the server. See also [**] Ok((0, false, Origin::Unknown)) } @@ -463,8 +460,6 @@ async fn add_parts( .await .unwrap_or_default(); - // get the chat_id - a chat_id here is no indicator that the chat is displayed in the normal list, - // it might also be blocked and displayed in the deaddrop as a result if chat_id.is_unset() && mime_parser.failure_report.is_some() { *chat_id = DC_CHAT_ID_TRASH; info!(context, "Message belongs to an NDN (TRASH)",); @@ -487,7 +482,7 @@ async fn add_parts( id: _, blocked: Blocked::Not, }) => Blocked::Not, - _ => Blocked::Deaddrop, + _ => Blocked::Request, }; let (new_chat_id, new_chat_id_blocked) = create_or_lookup_group( @@ -509,7 +504,7 @@ async fn add_parts( && chat_id_blocked != Blocked::Not && create_blocked == Blocked::Not { - new_chat_id.unblock(context).await; + new_chat_id.unblock(context).await?; chat_id_blocked = Blocked::Not; } } @@ -582,7 +577,7 @@ async fn add_parts( let create_blocked = if from_id == to_id { Blocked::Not } else { - Blocked::Deaddrop + Blocked::Request }; if let Some(chat) = test_normal_chat { @@ -599,7 +594,7 @@ async fn add_parts( } if !chat_id.is_unset() && Blocked::Not != chat_id_blocked { if Blocked::Not == create_blocked { - chat_id.unblock(context).await; + chat_id.unblock(context).await?; chat_id_blocked = Blocked::Not; } else if parent.is_some() { // we do not want any chat to be created implicitly. Because of the origin-scale-up, @@ -631,13 +626,13 @@ async fn add_parts( && show_emails != ShowEmails::All { state = MessageState::InNoticed; - } else if fetching_existing_messages && Blocked::Deaddrop == chat_id_blocked { + } else if fetching_existing_messages && Blocked::Request == chat_id_blocked { // The fetched existing message should be shown in the chatlist-contact-request because // a new user won't find the contact request in the menu state = MessageState::InFresh; } - let is_spam = (chat_id_blocked == Blocked::Deaddrop) + let is_spam = (chat_id_blocked == Blocked::Request) && !incoming_origin.is_known() && (is_dc_message == MessengerMessage::No) && context.is_spam_folder(server_folder).await?; @@ -723,7 +718,7 @@ async fn add_parts( chat_id_blocked = new_chat_id_blocked; // automatically unblock chat when the user sends a message if !chat_id.is_unset() && chat_id_blocked != Blocked::Not { - new_chat_id.unblock(context).await; + new_chat_id.unblock(context).await?; chat_id_blocked = Blocked::Not; } } @@ -731,7 +726,7 @@ async fn add_parts( let create_blocked = if !Contact::is_blocked_load(context, to_id).await { Blocked::Not } else { - Blocked::Deaddrop + Blocked::Request }; if let Ok(chat) = ChatIdBlocked::get_for_contact(context, to_id, create_blocked).await @@ -744,7 +739,7 @@ async fn add_parts( && Blocked::Not != chat_id_blocked && Blocked::Not == create_blocked { - chat_id.unblock(context).await; + chat_id.unblock(context).await?; chat_id_blocked = Blocked::Not; } } @@ -766,7 +761,7 @@ async fn add_parts( } if !chat_id.is_unset() && Blocked::Not != chat_id_blocked { - chat_id.unblock(context).await; + chat_id.unblock(context).await?; chat_id_blocked = Blocked::Not; } } @@ -1689,14 +1684,14 @@ async fn create_or_lookup_mailinglist( Chattype::Mailinglist, &listid, &name, - Blocked::Deaddrop, + Blocked::Request, ProtectionStatus::Unprotected, ) .await { Ok(chat_id) => { chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await; - (chat_id, Blocked::Deaddrop) + (chat_id, Blocked::Request) } Err(e) => { warn!( @@ -1706,7 +1701,7 @@ async fn create_or_lookup_mailinglist( &listid, e.to_string() ); - (ChatId::new(0), Blocked::Deaddrop) + (ChatId::new(0), Blocked::Request) } } } else { @@ -2159,9 +2154,8 @@ mod tests { use crate::chat::{get_chat_msgs, ChatItem, ChatVisibility}; use crate::chatlist::Chatlist; - use crate::constants::{DC_CHAT_ID_DEADDROP, DC_CONTACT_ID_INFO, DC_GCL_NO_SPECIALS}; - use crate::message::ContactRequestDecision::*; - use crate::message::{ContactRequestDecision, Message}; + use crate::constants::{DC_CONTACT_ID_INFO, DC_GCL_NO_SPECIALS}; + use crate::message::Message; use crate::test_utils::{get_chat_msg, TestContext}; #[test] @@ -2322,12 +2316,12 @@ mod tests { .unwrap(); let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 1); - assert!(chats.get_chat_id(0).is_deaddrop()); - let chat_id = chat::create_by_msg_id(&t, chats.get_msg_id(0).unwrap().unwrap()) - .await - .unwrap(); + let chat_id = chats.get_chat_id(0); assert!(!chat_id.is_special()); let chat = chat::Chat::load_from_db(&t, chat_id).await.unwrap(); + assert!(chat.is_contact_request()); + chat_id.accept(&t).await.unwrap(); + let chat = chat::Chat::load_from_db(&t, chat_id).await.unwrap(); assert_eq!(chat.typ, Chattype::Single); assert_eq!(chat.name, "Bob"); assert_eq!(chat::get_chat_contacts(&t, chat_id).await.unwrap().len(), 1); @@ -2358,9 +2352,7 @@ mod tests { .unwrap(); let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 2); - let chat_id = chat::create_by_msg_id(&t, chats.get_msg_id(0).unwrap().unwrap()) - .await - .unwrap(); + let chat_id = chats.get_chat_id(0); let chat = chat::Chat::load_from_db(&t, chat_id).await.unwrap(); assert_eq!(chat.typ, Chattype::Group); assert_eq!(chat.name, "group with Alice, Bob and Claire"); @@ -2375,13 +2367,13 @@ mod tests { .await .unwrap(); - // adhoc-group with unknown contacts with show_emails=all will show up in the deaddrop + // adhoc-group with unknown contacts with show_emails=all will show up in a single chat let chats = Chatlist::try_load(&t, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 1); - assert!(chats.get_chat_id(0).is_deaddrop()); - let chat_id = chat::create_by_msg_id(&t, chats.get_msg_id(0).unwrap().unwrap()) - .await - .unwrap(); + let chat_id = chats.get_chat_id(0); + let chat = chat::Chat::load_from_db(&t, chat_id).await.unwrap(); + assert!(chat.is_contact_request()); + chat_id.accept(&t).await.unwrap(); let chat = chat::Chat::load_from_db(&t, chat_id).await.unwrap(); assert_eq!(chat.typ, Chattype::Group); assert_eq!(chat.name, "group with Alice, Bob and Claire"); @@ -2526,8 +2518,8 @@ mod tests { #[async_std::test] async fn test_no_from() { // if there is no from given, from_id stays 0 which is just fine. These messages - // are very rare, however, we have to add them to the database (they go to the - // "deaddrop" chat) to avoid a re-download from the server. See also [**] + // are very rare, however, we have to add them to the database + // to avoid a re-download from the server. let t = TestContext::new_alice().await; let context = &t; @@ -2912,9 +2904,8 @@ mod tests { let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 1); - let chat_id = chat::create_by_msg_id(&t.ctx, chats.get_msg_id(0).unwrap().unwrap()) - .await - .unwrap(); + let chat_id = chats.get_chat_id(0); + chat_id.accept(&t).await.unwrap(); let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); assert!(chat.is_mailing_list()); @@ -2985,9 +2976,8 @@ mod tests { .await .unwrap(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); - let chat_id = chat::create_by_msg_id(&t.ctx, chats.get_msg_id(0).unwrap().unwrap()) - .await - .unwrap(); + let chat_id = chats.get_chat_id(0); + chat_id.accept(&t).await.unwrap(); let chat = Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); assert_eq!(chat.name, "delta-dev"); @@ -2997,8 +2987,7 @@ mod tests { } #[async_std::test] - async fn test_mailing_list_decide_block() { - let deaddrop = DC_CHAT_ID_DEADDROP; + async fn test_block_mailing_list() { let t = TestContext::new_alice().await; t.ctx .set_config(Config::ShowEmails, Some("2")) @@ -3010,36 +2999,31 @@ mod tests { .unwrap(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 1); - assert_eq!(chats.get_chat_id(0), deaddrop); // Test that the message is shown in the deaddrop + let chat_id = chats.get_chat_id(0); + let chat = Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); + assert!(chat.is_contact_request()); - let msg = get_chat_msg(&t, deaddrop, 0, 1).await; - - // Answer "Block" on the contact request - message::decide_on_contact_request(&t.ctx, msg.get_id(), Block).await; + // Block the contact request. + chat_id.block(&t).await.unwrap(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 0); // Test that the message disappeared - let msgs = chat::get_chat_msgs(&t.ctx, deaddrop, 0, None) - .await - .unwrap(); - assert_eq!(msgs.len(), 0); - dc_receive_imf(&t.ctx, DC_MAILINGLIST2, "INBOX", 1, false) + dc_receive_imf(&t.ctx, DC_MAILINGLIST2, "INBOX", 2, false) .await .unwrap(); // Test that the mailing list stays disappeared let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 0); // Test that the message is not shown - let msgs = chat::get_chat_msgs(&t.ctx, deaddrop, 0, None) - .await - .unwrap(); - assert_eq!(msgs.len(), 0); + + // Both messages are in the same blocked chat. + let msgs = chat::get_chat_msgs(&t.ctx, chat_id, 0, None).await.unwrap(); + assert_eq!(msgs.len(), 2); } #[async_std::test] async fn test_mailing_list_decide_block_then_unblock() { - let deaddrop = DC_CHAT_ID_DEADDROP; let t = TestContext::new_alice().await; t.set_config(Config::ShowEmails, Some("2")).await.unwrap(); @@ -3049,16 +3033,16 @@ mod tests { let blocked = Contact::get_all_blocked(&t).await.unwrap(); assert_eq!(blocked.len(), 0); - // Answer "Block" on the contact request, - // this should add one blocked contact and deaddrop should be empty again - let msg = get_chat_msg(&t, deaddrop, 0, 1).await; - message::decide_on_contact_request(&t, msg.get_id(), Block).await; + // Block the contact request, this should add one blocked contact. + let msg = t.get_last_msg().await; + msg.chat_id.block(&t).await.unwrap(); + let blocked = Contact::get_all_blocked(&t).await.unwrap(); assert_eq!(blocked.len(), 1); - let msgs = chat::get_chat_msgs(&t, deaddrop, 0, None).await.unwrap(); - assert_eq!(msgs.len(), 0); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); + assert_eq!(chats.len(), 0); // Test that the message is not shown - // Unblock contact and check if the next message arrives in real chat + // Unblock contact and check if the next message arrives in a chat Contact::unblock(&t, *blocked.first().unwrap()) .await .unwrap(); @@ -3069,16 +3053,12 @@ mod tests { .await .unwrap(); let msg = t.get_last_msg().await; - assert_ne!(msg.chat_id, deaddrop); let msgs = chat::get_chat_msgs(&t, msg.chat_id, 0, None).await.unwrap(); assert_eq!(msgs.len(), 2); - let msgs = chat::get_chat_msgs(&t, deaddrop, 0, None).await.unwrap(); - assert_eq!(msgs.len(), 0); } #[async_std::test] async fn test_mailing_list_decide_not_now() { - let deaddrop = DC_CHAT_ID_DEADDROP; let t = TestContext::new_alice().await; t.ctx .set_config(Config::ShowEmails, Some("2")) @@ -3089,33 +3069,31 @@ mod tests { .await .unwrap(); - let msg = get_chat_msg(&t, deaddrop, 0, 1).await; + let msg = t.get_last_msg().await; + let chat_id = msg.get_chat_id(); - // Answer "Not now" on the contact request - message::decide_on_contact_request(&t.ctx, msg.get_id(), NotNow).await; + // Open the chat and go back + chat::marknoticed_chat(&t.ctx, chat_id).await.unwrap(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); - assert_eq!(chats.len(), 0); // Test that the message disappeared - let msgs = chat::get_chat_msgs(&t.ctx, deaddrop, 0, None) - .await - .unwrap(); - assert_eq!(msgs.len(), 1); // ...but is still shown in the deaddrop + assert_eq!(chats.len(), 1); // Test that chat is still in the chatlist + let msgs = chat::get_chat_msgs(&t.ctx, chat_id, 0, None).await.unwrap(); + assert_eq!(msgs.len(), 1); // ...and contains 1 message dc_receive_imf(&t.ctx, DC_MAILINGLIST2, "INBOX", 1, false) .await .unwrap(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); - assert_eq!(chats.len(), 1); // Test that the new mailing list message is shown again - let msgs = chat::get_chat_msgs(&t.ctx, deaddrop, 0, None) - .await - .unwrap(); + assert_eq!(chats.len(), 1); // Test that the new mailing list message got into the same chat + let msgs = chat::get_chat_msgs(&t.ctx, chat_id, 0, None).await.unwrap(); assert_eq!(msgs.len(), 2); + let chat = Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); + assert!(chat.is_contact_request()); } #[async_std::test] async fn test_mailing_list_decide_accept() { - let deaddrop = DC_CHAT_ID_DEADDROP; let t = TestContext::new_alice().await; t.ctx .set_config(Config::ShowEmails, Some("2")) @@ -3126,15 +3104,13 @@ mod tests { .await .unwrap(); - let msg = get_chat_msg(&t, deaddrop, 0, 1).await; - - // Answer "Start chat" on the contact request - message::decide_on_contact_request(&t.ctx, msg.get_id(), StartChat).await; + let msg = t.get_last_msg().await; + let chat_id = msg.get_chat_id(); + chat_id.accept(&t).await.unwrap(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 1); // Test that the message is shown - let chat_id = chats.get_chat_id(0); - assert_ne!(chat_id, deaddrop); + assert!(!chat_id.is_special()); dc_receive_imf(&t.ctx, DC_MAILINGLIST2, "INBOX", 1, false) .await @@ -3168,10 +3144,7 @@ mod tests { .await .unwrap(); let msg = t.get_last_msg().await; - let chat_id = - message::decide_on_contact_request(&t, msg.id, ContactRequestDecision::StartChat) - .await - .unwrap(); + let chat_id = msg.get_chat_id(); let chat = Chat::load_from_db(&t, chat_id).await.unwrap(); assert_eq!(chat.typ, Chattype::Mailinglist); assert_eq!(chat.grpid, "mylist@bar.org"); @@ -3236,7 +3209,7 @@ mod tests { let msg = t.get_last_msg().await; let chat = Chat::load_from_db(&t, msg.chat_id).await.unwrap(); assert_eq!(chat.typ, Chattype::Mailinglist); - assert_eq!(chat.blocked, Blocked::Deaddrop); + assert_eq!(chat.blocked, Blocked::Request); assert_eq!( chat.grpid, "399fc0402f1b154b67965632e.100761.list-id.mcsv.net" @@ -3266,7 +3239,7 @@ mod tests { assert!(msg.has_html()); let chat = Chat::load_from_db(&t, msg.chat_id).await.unwrap(); assert_eq!(chat.typ, Chattype::Mailinglist); - assert_eq!(chat.blocked, Blocked::Deaddrop); + assert_eq!(chat.blocked, Blocked::Request); assert_eq!(chat.grpid, "1234ABCD-123LMNO.mailing.dhl.de"); assert_eq!(chat.name, "DHL Paket"); } @@ -3293,7 +3266,7 @@ mod tests { assert!(msg.has_html()); let chat = Chat::load_from_db(&t, msg.chat_id).await.unwrap(); assert_eq!(chat.typ, Chattype::Mailinglist); - assert_eq!(chat.blocked, Blocked::Deaddrop); + assert_eq!(chat.blocked, Blocked::Request); assert_eq!(chat.grpid, "dpdde.mxmail.service.dpd.de"); assert_eq!(chat.name, "DPD"); } @@ -3302,8 +3275,6 @@ mod tests { async fn test_mailing_list_with_mimepart_footer() { let t = TestContext::new_alice().await; t.set_config(Config::ShowEmails, Some("2")).await.unwrap(); - let deaddrop = DC_CHAT_ID_DEADDROP; - assert_eq!(get_chat_msgs(&t, deaddrop, 0, None).await.unwrap().len(), 0); // the mailing list message contains two top-level texts. // the second text is a footer that is added by some mailing list software @@ -3326,13 +3297,12 @@ mod tests { ); assert!(msg.has_html()); let chat = Chat::load_from_db(&t, msg.chat_id).await.unwrap(); - assert_eq!(get_chat_msgs(&t, deaddrop, 0, None).await.unwrap().len(), 1); assert_eq!( get_chat_msgs(&t, msg.chat_id, 0, None).await.unwrap().len(), 1 ); assert_eq!(chat.typ, Chattype::Mailinglist); - assert_eq!(chat.blocked, Blocked::Deaddrop); + assert_eq!(chat.blocked, Blocked::Request); assert_eq!(chat.grpid, "intern.lists.abc.de"); assert_eq!(chat.name, "Intern"); } @@ -3539,7 +3509,7 @@ YEAAAAAA!. assert_eq!(msg.chat_id, reply_msg.chat_id); // Make sure we looked at real chat ID and do not just - // test that both messages got into deaddrop. + // test that both messages got into the same virtual chat. assert!(!msg.chat_id.is_special()); } @@ -3791,8 +3761,9 @@ YEAAAAAA!. .await .unwrap() .unwrap(); - chat::create_by_msg_id(&claire, msg_id).await.unwrap(); + let msg = Message::load_from_db(&claire, msg_id).await.unwrap(); + msg.chat_id.accept(&claire).await.unwrap(); assert_eq!(msg.get_subject(), "i have a question"); assert!(msg.get_text().unwrap().contains("hi support!")); let chat = Chat::load_from_db(&claire, msg.chat_id).await.unwrap(); @@ -3919,9 +3890,8 @@ YEAAAAAA!. ) .await .unwrap(); - let chat_id = chat::create_by_msg_id(&t, t.get_last_msg().await.id) - .await - .unwrap(); + let chat_id = t.get_last_msg().await.chat_id; + chat_id.accept(&t).await.unwrap(); let msg = get_chat_msg(&t, chat_id, 0, 1).await; // Make sure that the message is actually in the chat assert!(!msg.chat_id.is_special()); assert_eq!(msg.text.unwrap(), "Hi – hello"); @@ -4093,7 +4063,6 @@ Message content", // Outgoing email should create a chat. let msg = alice.get_last_msg().await; - assert_ne!(msg.chat_id, DC_CHAT_ID_DEADDROP); assert_eq!(msg.get_text().unwrap(), "Subj – Message content"); } diff --git a/src/job.rs b/src/job.rs index 7e53996e5..2b7da407a 100644 --- a/src/job.rs +++ b/src/job.rs @@ -13,9 +13,8 @@ use itertools::Itertools; use rand::{thread_rng, Rng}; use crate::blob::BlobObject; -use crate::chat::{self, Chat, ChatId, ChatIdBlocked, ChatItem}; +use crate::chat::{self, ChatId}; use crate::config::Config; -use crate::constants::{Blocked, Chattype, DC_CHAT_ID_DEADDROP}; use crate::contact::{normalize_name, Contact, Modifier, Origin}; use crate::context::Context; use crate::dc_tools::{dc_delete_file, dc_read_file, time}; @@ -711,40 +710,6 @@ impl Job { } } - // Make sure that if there now is a chat with a contact (created by an outgoing - // message), then group contact requests from this contact should also be unblocked. - // See . - for item in job_try!(chat::get_chat_msgs(context, DC_CHAT_ID_DEADDROP, 0, None).await) { - if let ChatItem::Message { msg_id } = item { - let msg = match Message::load_from_db(context, msg_id).await { - Err(e) => { - warn!(context, "can't get msg: {:#}", e); - return Status::RetryLater; - } - Ok(m) => m, - }; - let chat = match Chat::load_from_db(context, msg.chat_id).await { - Err(e) => { - warn!(context, "can't get chat: {:#}", e); - return Status::RetryLater; - } - Ok(c) => c, - }; - match chat.typ { - Chattype::Group | Chattype::Mailinglist => { - if let Ok(Some(one_to_one_chat)) = - ChatIdBlocked::lookup_by_contact(context, msg.from_id).await - { - if one_to_one_chat.blocked == Blocked::Not { - chat.id.unblock(context).await; - } - } - } - Chattype::Single | Chattype::Undefined => {} - } - } - } - info!(context, "Done fetching existing messages."); Status::Finished(Ok(())) } diff --git a/src/message.rs b/src/message.rs index a711ff114..3c7ca9c29 100644 --- a/src/message.rs +++ b/src/message.rs @@ -13,9 +13,9 @@ use serde::{Deserialize, Serialize}; use crate::chat::{self, Chat, ChatId}; use crate::config::Config; use crate::constants::{ - Blocked, Chattype, VideochatType, Viewtype, DC_CHAT_ID_DEADDROP, DC_CHAT_ID_TRASH, - DC_CONTACT_ID_INFO, DC_CONTACT_ID_LAST_SPECIAL, DC_CONTACT_ID_SELF, DC_MAX_GET_INFO_LEN, - DC_MAX_GET_TEXT_LEN, DC_MSG_ID_LAST_SPECIAL, + Blocked, Chattype, VideochatType, Viewtype, DC_CHAT_ID_TRASH, DC_CONTACT_ID_INFO, + DC_CONTACT_ID_LAST_SPECIAL, DC_CONTACT_ID_SELF, DC_MAX_GET_INFO_LEN, DC_MAX_GET_TEXT_LEN, + DC_MSG_ID_LAST_SPECIAL, }; use crate::contact::{Contact, Origin}; use crate::context::Context; @@ -109,7 +109,7 @@ impl MsgId { Ok(Some(ConfiguredInboxFolder)) } } else { - // Blocked/deaddrop message in the spam folder, leave it there + // Blocked or contact request message in the spam folder, leave it there Ok(None) }; } @@ -520,19 +520,8 @@ impl Message { self.from_id } - /// get the chat-id, - /// if the message is a contact request, the DC_CHAT_ID_DEADDROP is returned. + /// Returns the chat ID. pub fn get_chat_id(&self) -> ChatId { - if self.chat_blocked != Blocked::Not { - DC_CHAT_ID_DEADDROP - } else { - self.chat_id - } - } - - /// get the chat-id, also when the message is still a contact request. - /// DC_CHAT_ID_DEADDROP is never returned. - pub fn get_real_chat_id(&self) -> ChatId { self.chat_id } @@ -959,13 +948,6 @@ impl Message { } } -#[derive(Display, Debug, FromPrimitive)] -pub enum ContactRequestDecision { - StartChat = 0, - Block = 1, - NotNow = 2, -} - #[derive( Debug, Clone, @@ -1147,80 +1129,6 @@ impl Lot { } } -/// Call this when the user decided about a deaddrop message ("Do you want to chat with NAME?"). -/// -/// If the decision is `StartChat`, this will create a new chat and return the chat id. -/// If the decision is `Block`, this will usually block the sender. -/// If the decision is `NotNow`, this will usually mark all messages from this sender as read. -/// -/// If the message belongs to a mailing list, makes sure that all messages from this mailing list are -/// blocked or marked as noticed. -/// -/// The user should be asked whether they want to chat with the _contact_ belonging to the message; -/// the group names may be really weird when taken from the subject of implicit (= ad-hoc) -/// groups and this may look confusing. Moreover, this function also scales up the origin of the contact. -/// -/// If the chat belongs to a mailing list, you can also ask -/// "Would you like to read MAILING LIST NAME in Delta Chat?" -/// (use `Message.get_real_chat_id()` to get the chat-id for the contact request -/// and then `Chat.is_mailing_list()`, `Chat.get_name()` and so on) -pub async fn decide_on_contact_request( - context: &Context, - msg_id: MsgId, - decision: ContactRequestDecision, -) -> Option { - let msg = match Message::load_from_db(context, msg_id).await { - Ok(m) => m, - Err(e) => { - warn!(context, "Can't load message: {}", e); - return None; - } - }; - - let chat = match Chat::load_from_db(context, msg.chat_id).await { - Ok(c) => c, - Err(e) => { - warn!(context, "Can't load chat: {}", e); - return None; - } - }; - - let mut created_chat_id = None; - use ContactRequestDecision::*; - match (decision, chat.is_mailing_list()) { - (StartChat, _) => match chat::create_by_msg_id(context, msg.id).await { - Ok(id) => created_chat_id = Some(id), - Err(e) => warn!(context, "decide_on_contact_request error: {}", e), - }, - - (Block, false) => { - if let Err(e) = Contact::block(context, msg.from_id).await { - warn!(context, "Can't block contact: {}", e); - } - } - (Block, true) => { - if !msg.chat_id.set_blocked(context, Blocked::Manually).await { - warn!(context, "Block mailing list failed.") - } - } - - (NotNow, false) => Contact::mark_noticed(context, msg.from_id).await, - (NotNow, true) => { - if let Err(e) = chat::marknoticed_chat(context, msg.chat_id).await { - warn!(context, "Marknoticed failed: {}", e) - } - } - } - - // Multiple chats may have changed, so send 0s - // (performance is not so important because this function is not called very often) - context.emit_event(EventType::MsgsChanged { - chat_id: ChatId::new(0), - msg_id: MsgId::new(0), - }); - created_chat_id -} - pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> Result { let msg = Message::load_from_db(context, msg_id).await?; let rawtxt: Option = context @@ -1907,8 +1815,8 @@ async fn ndn_maybe_add_info_msg( Ok(()) } -/// The number of messages assigned to real chat (!=deaddrop, !=trash) -pub async fn get_real_msg_cnt(context: &Context) -> usize { +/// The number of messages assigned to unblocked chats +pub async fn get_unblocked_msg_cnt(context: &Context) -> usize { match context .sql .count( @@ -1921,13 +1829,14 @@ pub async fn get_real_msg_cnt(context: &Context) -> usize { { Ok(res) => res, Err(err) => { - error!(context, "dc_get_real_msg_cnt() failed. {}", err); + error!(context, "dc_get_unblocked_msg_cnt() failed. {}", err); 0 } } } -pub async fn get_deaddrop_msg_cnt(context: &Context) -> usize { +/// Returns the number of messages in contact request chats. +pub async fn get_request_msg_cnt(context: &Context) -> usize { match context .sql .count( @@ -1940,7 +1849,7 @@ pub async fn get_deaddrop_msg_cnt(context: &Context) -> usize { { Ok(res) => res, Err(err) => { - error!(context, "dc_get_deaddrop_msg_cnt() failed. {}", err); + error!(context, "dc_get_request_msg_cnt() failed. {}", err); 0 } } @@ -2099,7 +2008,7 @@ mod tests { ]; // These are the same as above, but all messages in Spam stay in Spam - const COMBINATIONS_DEADDROP: &[(&str, bool, bool, &str)] = &[ + const COMBINATIONS_REQUEST: &[(&str, bool, bool, &str)] = &[ ("INBOX", false, false, "INBOX"), ("INBOX", false, true, "INBOX"), ("INBOX", true, false, "INBOX"), @@ -2132,8 +2041,8 @@ mod tests { } #[async_std::test] - async fn test_needs_move_incoming_deaddrop() { - for (folder, mvbox_move, chat_msg, expected_destination) in COMBINATIONS_DEADDROP { + async fn test_needs_move_incoming_request() { + for (folder, mvbox_move, chat_msg, expected_destination) in COMBINATIONS_REQUEST { check_needs_move_combination( folder, *mvbox_move, @@ -2682,10 +2591,7 @@ mod tests { // check chat-id of this message let msg = alice.get_last_msg().await; - assert!(msg.get_chat_id().is_deaddrop()); - assert!(msg.get_chat_id().is_special()); - assert!(!msg.get_real_chat_id().is_deaddrop()); - assert!(!msg.get_real_chat_id().is_special()); + assert!(!msg.get_chat_id().is_special()); assert_eq!(msg.get_text().unwrap(), "hello".to_string()); } @@ -2746,33 +2652,31 @@ mod tests { msg.set_text(Some("this is the text!".to_string())); // alice sends to bob, - // bob does not know alice yet and messages go to bob's deaddrop assert_eq!(Chatlist::try_load(&bob, 0, None, None).await?.len(), 0); bob.recv_msg(&alice.send_msg(alice_chat.id, &mut msg).await) .await; let msg1 = bob.get_last_msg().await; + let bob_chat_id = msg1.chat_id; bob.recv_msg(&alice.send_msg(alice_chat.id, &mut msg).await) .await; let msg2 = bob.get_last_msg().await; assert_eq!(msg1.chat_id, msg2.chat_id); - assert_ne!(msg1.chat_id, DC_CHAT_ID_DEADDROP); let chats = Chatlist::try_load(&bob, 0, None, None).await?; assert_eq!(chats.len(), 1); - assert_eq!(chats.get_chat_id(0), DC_CHAT_ID_DEADDROP); - let msgs = chat::get_chat_msgs(&bob, DC_CHAT_ID_DEADDROP, 0, None).await?; + let msgs = chat::get_chat_msgs(&bob, bob_chat_id, 0, None).await?; assert_eq!(msgs.len(), 2); assert_eq!(bob.get_fresh_msgs().await?.len(), 0); - // that has no effect in deaddrop + // that has no effect in contact request markseen_msgs(&bob, vec![msg1.id, msg2.id]).await?; assert_eq!(Chatlist::try_load(&bob, 0, None, None).await?.len(), 1); - let msgs = chat::get_chat_msgs(&bob, DC_CHAT_ID_DEADDROP, 0, None).await?; + let bob_chat = Chat::load_from_db(&bob, bob_chat_id).await?; + assert_eq!(bob_chat.blocked, Blocked::Request); + + let msgs = chat::get_chat_msgs(&bob, bob_chat_id, 0, None).await?; assert_eq!(msgs.len(), 2); - let bob_chat_id = - decide_on_contact_request(&bob, msg2.get_id(), ContactRequestDecision::StartChat) - .await - .unwrap(); + bob_chat_id.accept(&bob).await.unwrap(); // bob sends to alice, // alice knows bob and messages appear in normal chat diff --git a/src/mimefactory.rs b/src/mimefactory.rs index f51917b84..58f989242 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -1806,9 +1806,8 @@ mod tests { let chats = Chatlist::try_load(context, 0, None, None).await.unwrap(); - let chat_id = chat::create_by_msg_id(context, chats.get_msg_id(0).unwrap().unwrap()) - .await - .unwrap(); + let chat_id = chats.get_chat_id(0); + chat_id.accept(context).await.unwrap(); let mut new_msg = Message::new(Viewtype::Text); new_msg.set_text(Some("Hi".to_string())); diff --git a/src/mimeparser.rs b/src/mimeparser.rs index c697f91bf..595580ecb 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -2793,7 +2793,7 @@ On 2020-10-25, Bob wrote: assert_eq!(msg.viewtype, Viewtype::Image); assert_eq!(msg.error(), None); assert_eq!(msg.is_dc_message, MessengerMessage::No); - assert_eq!(msg.chat_blocked, Blocked::Deaddrop); + assert_eq!(msg.chat_blocked, Blocked::Request); assert_eq!(msg.state, MessageState::InFresh); assert_eq!(msg.get_filebytes(&t).await, 2115); assert!(msg.get_file(&t).is_some()); diff --git a/src/peerstate.rs b/src/peerstate.rs index 5ec14cdf7..ecf0a7bf1 100644 --- a/src/peerstate.rs +++ b/src/peerstate.rs @@ -267,10 +267,9 @@ impl Peerstate { .query_get_value("SELECT id FROM contacts WHERE addr=?;", paramsv![self.addr]) .await? { - let chat_id = - ChatIdBlocked::get_for_contact(context, contact_id, Blocked::Deaddrop) - .await? - .id; + let chat_id = ChatIdBlocked::get_for_contact(context, contact_id, Blocked::Request) + .await? + .id; let msg = stock_str::contact_setup_changed(context, self.addr.clone()).await; diff --git a/src/qr.rs b/src/qr.rs index ff0af3c3c..66ac98570 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -156,7 +156,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot { .map(|(id, _)| id) .unwrap_or_default(); - if let Ok(chat) = ChatIdBlocked::get_for_contact(context, lot.id, Blocked::Deaddrop) + if let Ok(chat) = ChatIdBlocked::get_for_contact(context, lot.id, Blocked::Request) .await .log_err(context, "Failed to create (new) chat for contact") { diff --git a/src/securejoin.rs b/src/securejoin.rs index 0c7b868f6..20e344a72 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -501,7 +501,7 @@ pub(crate) async fn handle_securejoin_handshake( ) })?; if chat.blocked != Blocked::Not { - chat.id.unblock(context).await; + chat.id.unblock(context).await?; } chat.id }; @@ -796,7 +796,7 @@ pub(crate) async fn observe_securejoin_on_other_device( ) })?; if chat.blocked != Blocked::Not { - chat.id.unblock(context).await; + chat.id.unblock(context).await?; } chat.id }; diff --git a/src/stock_str.rs b/src/stock_str.rs index 4db2c8a9f..a4e2776a7 100644 --- a/src/stock_str.rs +++ b/src/stock_str.rs @@ -39,9 +39,6 @@ pub enum StockMessage { #[strum(props(fallback = "Voice message"))] VoiceMessage = 7, - #[strum(props(fallback = "Contact requests"))] - DeadDrop = 8, - #[strum(props(fallback = "Image"))] Image = 9, @@ -360,11 +357,6 @@ pub(crate) async fn voice_message(context: &Context) -> String { translated(context, StockMessage::VoiceMessage).await } -/// Stock string: `Contact requests`. -pub(crate) async fn dead_drop(context: &Context) -> String { - translated(context, StockMessage::DeadDrop).await -} - /// Stock string: `Image`. pub(crate) async fn image(context: &Context) -> String { translated(context, StockMessage::Image).await