Compare commits

..

2 Commits

Author SHA1 Message Date
iequidoo
db56e69d4b feat: Don't mark MDNs as IMAP-seen
Marking MDNs as seen is useless, they shouldn't be displayed by any MUA.
2026-05-05 13:50:14 -03:00
iequidoo
65d07d18a5 fix: Avoid auto-marking DSNs as seen; the user may want to see them in another MUA 2026-05-05 12:02:52 -03:00
5 changed files with 20 additions and 38 deletions

View File

@@ -68,12 +68,11 @@ def test_markseen_message_and_mdn(acfactory, direct_imap):
msg.mark_seen()
rex = re.compile("Marked messages [0-9]+ in folder INBOX as seen.")
for ac in ac1, ac2:
while True:
event = ac.wait_for_event()
if event.kind == EventType.INFO and rex.search(event.msg):
break
while True:
event = ac2.wait_for_event()
if event.kind == EventType.INFO and rex.search(event.msg):
break
ac1.wait_for_msg(EventType.MSGS_CHANGED)
ac1_direct_imap = direct_imap(ac1)
ac2_direct_imap = direct_imap(ac2)
@@ -81,8 +80,8 @@ def test_markseen_message_and_mdn(acfactory, direct_imap):
ac1_direct_imap.select_folder("INBOX")
ac2_direct_imap.select_folder("INBOX")
# Check that the mdn is marked as seen
assert len(list(ac1_direct_imap.conn.fetch(AND(seen=True), mark_seen=False))) == 1
# Check that the MDN isn't marked as seen.
assert len(list(ac1_direct_imap.conn.fetch(AND(seen=True), mark_seen=False))) == 0
# Check original message is marked as seen
assert len(list(ac2_direct_imap.conn.fetch(AND(seen=True), mark_seen=False))) == 1

View File

@@ -419,9 +419,6 @@ def test_send_and_receive_message_markseen(acfactory, lp):
assert ev.data2 > dc.const.DC_MSG_ID_LAST_SPECIAL
lp.step("2")
# Check that ac1 marks the read receipt as read.
ac1._evtracker.get_info_contains("Marked messages .* in folder INBOX as seen.")
assert msg1.is_out_mdn_received()
assert msg3.is_out_mdn_received()
@@ -506,11 +503,16 @@ def test_mdn_asymmetric(acfactory, lp):
ac1.set_config("mdns_enabled", "1")
ac2.set_config("mdns_enabled", "1")
ac1.set_config("bcc_self", "1")
lp.sec("sending text message from ac1 to ac2")
msg_out = chat.send_text("message1")
assert len(chat.get_messages()) == 1 + E2EE_INFO_MSGS
# Wait for the message to be marked as seen on IMAP.
ac1._evtracker.get_info_contains("Marked messages [0-9]+ in folder INBOX as seen.")
lp.sec("disable ac1 MDNs")
ac1.set_config("mdns_enabled", "0")
@@ -522,13 +524,14 @@ def test_mdn_asymmetric(acfactory, lp):
lp.sec("ac2: mark incoming message as seen")
ac2.mark_seen_messages([msg])
# Wait for the message to be marked as seen on IMAP.
ac2._evtracker.get_info_contains("Marked messages [0-9]+ in folder INBOX as seen.")
lp.sec("ac1: waiting for incoming activity")
assert len(chat.get_messages()) == 1 + E2EE_INFO_MSGS
# Wait for the message to be marked as seen on IMAP.
ac1._evtracker.get_info_contains("Marked messages [0-9]+ in folder INBOX as seen.")
# MDN is received even though MDNs are already disabled
ac1._evtracker.get_matching("DC_EVENT_MSG_READ")
assert msg_out.is_out_mdn_received()

View File

@@ -930,11 +930,6 @@ UPDATE config SET value=? WHERE keyname='configured_addr' AND value!=?1
.await?;
context.scheduler.interrupt_inbox().await;
}
if target.is_none() && !mime_parser.mdn_reports.is_empty() && mime_parser.has_chat_version()
{
// This is a Delta Chat MDN. Mark as read.
markseen_on_imap_table(context, rfc724_mid_orig).await?;
}
if !mime_parser.incoming && !context.get_config_bool(Config::TeamProfile).await? {
let mut updated_chats = BTreeMap::new();
let mut archived_chats_maybe_noticed = false;
@@ -1162,8 +1157,9 @@ async fn decide_chat_assignment(
info!(context, "Message is an MDN (TRASH).");
true
} else if mime_parser.delivery_report.is_some() {
// Auto-marking DSNs as IMAP-seen should be avoided because the user may want to see them in
// another MUA.
info!(context, "Message is a DSN (TRASH).");
markseen_on_imap_table(context, rfc724_mid).await.ok();
true
} else if mime_parser.get_header(HeaderDef::ChatEdit).is_some()
|| mime_parser.get_header(HeaderDef::ChatDelete).is_some()

View File

@@ -29,7 +29,6 @@ const STATISTICS_BOT_VCARD: &str = include_str!("../assets/statistics-bot.vcf");
const SENDING_INTERVAL_SECONDS: i64 = 3600 * 24 * 7; // 1 week
// const SENDING_INTERVAL_SECONDS: i64 = 60; // 1 minute (for testing)
const MESSAGE_STATS_UPDATE_INTERVAL_SECONDS: i64 = 4 * 60; // 4 minutes (less than the lowest ephemeral messages timeout)
const KEY_CREATE_TIMESTAMP_RESOLUTION: u32 = 3600 * 24 * 7 * 4; // 4 weeks
#[derive(Serialize)]
struct Statistics {
@@ -349,13 +348,7 @@ async fn get_stats(context: &Context) -> Result<String> {
let key_create_timestamps: Vec<u32> = load_self_public_keyring(context)
.await?
.iter()
.map(|k| {
k.created_at()
.as_secs()
.div_ceil(KEY_CREATE_TIMESTAMP_RESOLUTION)
.checked_mul(KEY_CREATE_TIMESTAMP_RESOLUTION)
.unwrap_or(0)
})
.map(|k| k.created_at().as_secs())
.collect();
let sending_enabled_timestamps =

View File

@@ -486,15 +486,6 @@ async fn test_stats_key_creation_timestamp() -> Result<()> {
// Alice uses a pregenerated key. It was created at this timestamp:
const ALICE_KEY_CREATION_TIME: u128 = 1582855645;
// The key creation time's resolution is reduced in order to prevent deanonymization:
const CENSORED_KEY_CREATION_TIME: u128 = 1584576000;
assert!(CENSORED_KEY_CREATION_TIME.is_multiple_of(KEY_CREATE_TIMESTAMP_RESOLUTION as u128));
assert!(CENSORED_KEY_CREATION_TIME > ALICE_KEY_CREATION_TIME);
assert!(
CENSORED_KEY_CREATION_TIME - ALICE_KEY_CREATION_TIME
< KEY_CREATE_TIMESTAMP_RESOLUTION as u128
);
let alice = &TestContext::new_alice().await;
alice.set_config_bool(Config::StatsSending, true).await?;
@@ -504,7 +495,7 @@ async fn test_stats_key_creation_timestamp() -> Result<()> {
assert_eq!(
key_create_timestamps,
&vec![Value::Number(
Number::from_u128(CENSORED_KEY_CREATION_TIME).unwrap()
Number::from_u128(ALICE_KEY_CREATION_TIME).unwrap()
)]
);