diff --git a/src/context.rs b/src/context.rs index adcb3b2a7..8e35b5541 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1146,6 +1146,12 @@ ORDER BY m.timestamp DESC,m.id DESC", /// /// Blocked contacts and chats are excluded, /// but self-sent messages and contact requests are included in the results. + /// + /// Note that this returns the pre-message's id as soon as it arrives. + /// The bot needs to wait for the post-message by itself if it wants to use this API + /// to get fully downloaded messages. + /// If the bot doesn't want this, then it should instead use the [`EventType::IncomingMsg`] + /// event for getting notified about new messages. pub async fn get_next_msgs(&self) -> Result> { let last_msg_id = match self.get_config(Config::LastMsgId).await? { Some(s) => MsgId::new(s.parse()?), diff --git a/src/imap.rs b/src/imap.rs index e85d10332..afe7cfb29 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -730,10 +730,19 @@ impl Imap { info!(context, "{message_id:?} is a post-message."); available_post_msgs.push(message_id.clone()); - if download_limit.is_none_or(|download_limit| size <= download_limit) { - download_later.push(message_id.clone()); + let is_bot = context.get_config_bool(Config::Bot).await?; + if is_bot && download_limit.is_none_or(|download_limit| size <= download_limit) + { + uids_fetch.push(uid); + uid_message_ids.insert(uid, message_id); + } else { + if download_limit.is_none_or(|download_limit| size <= download_limit) { + // Download later after all the small messages are downloaded, + // so that large messages don't delay receiving small messages + download_later.push(message_id.clone()); + } + largest_uid_skipped = Some(uid); } - largest_uid_skipped = Some(uid); } else { info!(context, "{message_id:?} is not a post-message."); if download_limit.is_none_or(|download_limit| size <= download_limit) { diff --git a/src/receive_imf.rs b/src/receive_imf.rs index 86e66da11..cc0da0b57 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -1069,7 +1069,12 @@ UPDATE msgs SET state=? WHERE let fresh = received_msg.state == MessageState::InFresh && mime_parser.is_system_message != SystemMessage::CallAccepted && mime_parser.is_system_message != SystemMessage::CallEnded; - let important = mime_parser.incoming && fresh && !is_old_contact_request; + let is_bot = context.get_config_bool(Config::Bot).await?; + let is_pre_message = matches!(mime_parser.pre_message, PreMessageMode::Pre { .. }); + let skip_bot_notify = is_bot && is_pre_message; + let important = + mime_parser.incoming && fresh && !is_old_contact_request && !skip_bot_notify; + for msg_id in &received_msg.msg_ids { chat_id.emit_msg_event(context, *msg_id, important); } @@ -1300,6 +1305,7 @@ async fn decide_chat_assignment( } ); pre_message_exists + // TODO send incoming msg event } else if let PreMessageMode::Pre { post_msg_rfc724_mid, .. @@ -2573,7 +2579,22 @@ WHERE id=? ), ) .await?; - context.emit_msgs_changed(original_msg.chat_id, original_msg.id); + + if context.get_config_bool(Config::Bot).await? { + if original_msg.hidden { + // No need to emit an event about the changed message + } else if !original_msg.chat_id.is_trash() { + let fresh = original_msg.state == MessageState::InFresh; + let important = mime_parser.incoming && fresh; + + original_msg + .chat_id + .emit_msg_event(context, original_msg.id, important); + context.new_msgs_notify.notify_one(); + } + } else { + context.emit_msgs_changed(original_msg.chat_id, original_msg.id); + } Ok(()) } diff --git a/src/tests/pre_messages.rs b/src/tests/pre_messages.rs index a0387ddee..af4167c58 100644 --- a/src/tests/pre_messages.rs +++ b/src/tests/pre_messages.rs @@ -1,4 +1,5 @@ mod additional_text; +mod bots_tests; mod forward_and_save; mod legacy; mod receiving; diff --git a/src/tests/pre_messages/bots_tests.rs b/src/tests/pre_messages/bots_tests.rs new file mode 100644 index 000000000..b57934ea8 --- /dev/null +++ b/src/tests/pre_messages/bots_tests.rs @@ -0,0 +1,55 @@ +use anyhow::Result; +use pretty_assertions::assert_eq; + +use crate::EventType; +use crate::config::Config; +use crate::download::DownloadState; +use crate::message::Viewtype; +use crate::receive_imf::receive_imf; +use crate::test_utils::TestContextManager; +use crate::tests::pre_messages::util::send_large_file_message; + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_bot_pre_message_notifications() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = tcm.alice().await; + let bob = tcm.bob().await; + + // Configure Bob as a bot + bob.set_config_bool(Config::Bot, true).await?; + + let alice_group_id = alice.create_group_with_members("test group", &[&bob]).await; + + let (pre_message, post_message, _alice_msg_id) = send_large_file_message( + &alice, + alice_group_id, + Viewtype::File, + &vec![0u8; 1_000_000], + ) + .await?; + + // Bob receives pre-message + bob.evtracker.clear_events(); + receive_imf(&bob, pre_message.payload().as_bytes(), false).await?; + + // Verify Bob does NOT get an IncomingMsg event for the pre-message + assert!( + bob.evtracker + .get_matching_opt(&bob, |e| matches!(e, EventType::IncomingMsg { .. })) + .await + .is_none() + ); + + // Bob receives post-message + receive_imf(&bob, post_message.payload().as_bytes(), false).await?; + + // Verify Bob DOES get an IncomingMsg event for the complete message + bob.evtracker + .get_matching(|e| matches!(e, EventType::IncomingMsg { .. })) + .await; + + let msg = bob.get_last_msg().await; + assert_eq!(msg.download_state, DownloadState::Done); + + Ok(()) +}