From 58d5ab9e8728d01a295f1b9f74c51a9d1dc44d28 Mon Sep 17 00:00:00 2001 From: link2xt Date: Thu, 27 Aug 2026 14:40:38 +0000 Subject: [PATCH] fix: do not mark call message as seen when accepting or declining a call When the message is marked as seen, an MDN (read receipt) is sent to self and receiving such MDN marks all messages in the chat above the call message as noticed. Calls may be accepted or declined from the notification, so user may accept or decline a call without ever seeing a call message in the chat. --- src/calls.rs | 17 ++++++++++--- src/calls/calls_tests.rs | 53 ++++++++++++++++++---------------------- src/message.rs | 12 +++++++++ 3 files changed, 50 insertions(+), 32 deletions(-) diff --git a/src/calls.rs b/src/calls.rs index 4a779a7bc8..7f76428af0 100644 --- a/src/calls.rs +++ b/src/calls.rs @@ -11,7 +11,7 @@ use crate::context::{Context, WeakContext}; use crate::events::EventType; use crate::headerdef::HeaderDef; use crate::log::warn; -use crate::message::{Message, MsgId, Viewtype, markseen_msgs}; +use crate::message::{Message, MsgId, Viewtype}; use crate::mimeparser::{MimeMessage, SystemMessage}; use crate::net::dns::lookup_host_with_cache; use crate::param::Param; @@ -247,7 +247,10 @@ impl Context { if chat.is_contact_request() { chat.id.accept(self).await?; } - markseen_msgs(self, vec![call_id]).await?; + call_id + .mark_as_noticed(self) + .await + .context("Failed to mark incoming call as noticed")?; // send an acceptance message around: to the caller as well as to the other devices of the callee let mut msg = Message { @@ -283,7 +286,10 @@ impl Context { if !call.is_accepted() { if call.is_incoming() { call.mark_as_ended(self).await?; - markseen_msgs(self, vec![call_id]).await?; + call_id + .mark_as_noticed(self) + .await + .context("Failed to mark incoming call as noticed")?; let declined_call_str = stock_str::declined_call(self); call.update_text(self, &declined_call_str).await?; } else { @@ -463,6 +469,11 @@ impl Context { if call.is_incoming() { if from_id == ContactId::SELF { call.mark_as_ended(self).await?; + call.msg + .id + .mark_as_noticed(self) + .await + .context("Failed to mark incoming call as noticed")?; let declined_call_str = stock_str::declined_call(self); call.update_text(self, &declined_call_str).await?; } else { diff --git a/src/calls/calls_tests.rs b/src/calls/calls_tests.rs index 491561a265..2bcc56ab91 100644 --- a/src/calls/calls_tests.rs +++ b/src/calls/calls_tests.rs @@ -2,7 +2,7 @@ use super::*; use crate::chat::forward_msgs; use crate::config::Config; use crate::contact::Contact; -use crate::message::MessageState; +use crate::message::{MessageState, markseen_msgs}; use crate::receive_imf::receive_imf; use crate::test_utils; use crate::test_utils::{ @@ -117,20 +117,18 @@ async fn accept_call() -> Result { bob2_call, } = setup_call().await?; - // Bob accepts the incoming call + // Bob accepts the incoming call. + // Accepting the call marks the message as noticed, but does not send MDN. + // Otherwise second device of Bob will assume Bob has opened the chat + // and mark all messages above the call as noticed. bob.accept_incoming_call(bob_call.id, ACCEPT_INFO.to_string()) .await?; - assert_eq!(bob_call.id.get_state(&bob).await?, MessageState::InSeen); - // Bob sends an MDN to Alice. + assert_eq!(bob_call.id.get_state(&bob).await?, MessageState::InNoticed); assert_eq!( - bob.sql - .count( - "SELECT COUNT(*) FROM smtp_mdns WHERE msg_id=? AND from_id=?", - (bob_call.id, bob_call.from_id) - ) - .await?, - 1 + bob.sql.count("SELECT COUNT(*) FROM smtp_mdns", ()).await?, + 0 ); + assert_text(&bob, bob_call.id, "Incoming video call").await?; bob.evtracker .get_matching(|evt| { @@ -232,19 +230,14 @@ async fn test_accept_call_callee_ends() -> Result<()> { bob2_call, .. } = accept_call().await?; - assert_eq!(bob_call.id.get_state(&bob).await?, MessageState::InSeen); + assert_eq!(bob_call.id.get_state(&bob).await?, MessageState::InNoticed); // Bob has accepted the call and also ends it bob.end_call(bob_call.id).await?; - // Bob sends an MDN to Alice. + assert_eq!(bob_call.id.get_state(&bob).await?, MessageState::InNoticed); assert_eq!( - bob.sql - .count( - "SELECT COUNT(*) FROM smtp_mdns WHERE msg_id=? AND from_id=?", - (bob_call.id, bob_call.from_id) - ) - .await?, - 1 + bob.sql.count("SELECT COUNT(*) FROM smtp_mdns", ()).await?, + 0 ); assert_text(&bob, bob_call.id, "Incoming video call\n<1 minute").await?; bob.evtracker @@ -372,16 +365,14 @@ async fn test_callee_rejects_call() -> Result<()> { // Bob has accepted Alice before, but does not want to talk with Alice bob.end_call(bob_call.id).await?; - assert_eq!(bob_call.id.get_state(&bob).await?, MessageState::InSeen); - // Bob sends an MDN to Alice. + + // The call may be declined from a notification. + // Declining the call does not mean that Bob has seen the + // call message in the chat or opened the chat. + assert_eq!(bob_call.id.get_state(&bob).await?, MessageState::InNoticed); assert_eq!( - bob.sql - .count( - "SELECT COUNT(*) FROM smtp_mdns WHERE msg_id=? AND from_id=?", - (bob_call.id, bob_call.from_id) - ) - .await?, - 1 + bob.sql.count("SELECT COUNT(*) FROM smtp_mdns", ()).await?, + 0 ); assert_text(&bob, bob_call.id, "Declined call").await?; bob.evtracker @@ -389,6 +380,8 @@ async fn test_callee_rejects_call() -> Result<()> { .await; let sent3 = bob.pop_sent_msg().await; assert_eq!(call_state(&bob, bob_call.id).await?, CallState::Declined); + let bob_call = Message::load_from_db(&bob, bob_call.id).await?; + assert_eq!(bob_call.state, MessageState::InNoticed); bob2.recv_msg_trash(&sent3).await; assert_text(&bob2, bob2_call.id, "Declined call").await?; @@ -396,6 +389,8 @@ async fn test_callee_rejects_call() -> Result<()> { .get_matching(|evt| matches!(evt, EventType::CallEnded { .. })) .await; assert_eq!(call_state(&bob2, bob2_call.id).await?, CallState::Declined); + let bob2_call = Message::load_from_db(&bob2, bob2_call.id).await?; + assert_eq!(bob2_call.state, MessageState::InNoticed); // Alice receives decline message alice.recv_msg_trash(&sent3).await; diff --git a/src/message.rs b/src/message.rs index f6def15ada..891c89567c 100644 --- a/src/message.rs +++ b/src/message.rs @@ -172,6 +172,18 @@ SELECT ?1, rfc724_mid, pre_rfc724_mid, timestamp, ?, ? FROM msgs WHERE id=?1 Ok(true) } + /// Marks incoming message as noticed if it is fresh. + pub(crate) async fn mark_as_noticed(self, context: &Context) -> Result<()> { + context + .sql + .execute( + "UPDATE msgs SET state=? WHERE id=? AND state=?", + (MessageState::InNoticed, self, MessageState::InFresh), + ) + .await?; + Ok(()) + } + /// Bad evil escape hatch. /// /// Avoid using this, eventually types should be cleaned up enough