fix: For bots, wait with emitting IncomingMsg until the Post-Msg arrived (#8104)

I used some AI to draft a first version of this, and then reworked it.

This is one of multiple possibilities to fix
https://github.com/chatmail/core/issues/8041: For bots, the IncomingMsg
event is not emitted when the pre-message arrives, only when the
post-message arrives. Also, post-messages are downloaded immediately,
not after all the small messages are downloaded.

The `get_next_msgs()` API is deprecated. Instead, bots need to listen to
the IncomingMsg event in order to be notified about new events. Is this
acceptable for bots?

THE PROBLEM THAT WAS SOLVED BY THIS:

With pre-messages, it's hard for bots to wait for the message to be fully downloaded and then process it.

Up until now, bots used get_next_msgs() to query the unprocessed messages, then set last_msg_id after processing a message to that they won't process it again.

But this will now also return messages that were not fully downloaded.

ALTERNATIVES:

In the following, I will explain
the alternatives, and for why it's not so easy to just make the
`get_next_msgs()` API work. If it's not understandable, I'm happy to
elaborate more.

Core can't just completely ignore the pre-message for two reasons:
- If a post-message containing a Webxdc arrives later, and some webxdc updates arrive in the meantime, then these updates will be lost.
- The post-message doesn't contain the text (reasoning was to avoid duplicate text for people who didn't upgrade yet during the 2.43.0 rollout)

There are multiple solutions:
- Add the message as hidden in the database when the pre-message arrives.
  - When the post-message arrives and we want to make it available for bots, we need to update the msg_id because of how the `get_next_msgs()` API works. This means that we need to update all webxdc updates that reference this msg_id.
  - Alternatively, we could make webxdc's reference the rfc724_mid instead of the msg_id, so that we don't need stable msg_ids anymore
  - Alternatively, we could deprecate `get_next_msgs()`, and ask bots to use plain events for message processing again. It's not that bad; worst case, the bot crashes and then forgets to react to some messages, but the user will just try again. And if some message makes the bot crash, then it might actually be good not to try and process it again.
- Store the pre-message text and `PostMsgMetadata` (or alternatively, the whole mime) in a new database table. Wait with processing it until the post-message arrives.

Additionally, the logic that small messages are downloaded before post-messages should be disabled for bots, in order to prevent reordering.
This commit is contained in:
Hocuri
2026-04-10 21:10:46 +02:00
committed by GitHub
parent d6971ee4ac
commit 8b58b16cb5
6 changed files with 120 additions and 10 deletions

View File

@@ -5,12 +5,14 @@ use pretty_assertions::assert_eq;
use crate::EventType;
use crate::chat;
use crate::chat::send_msg;
use crate::config::Config;
use crate::contact;
use crate::download::{DownloadState, PRE_MSG_ATTACHMENT_SIZE_THRESHOLD, PostMsgMetadata};
use crate::message::{Message, MessageState, Viewtype, delete_msgs, markseen_msgs};
use crate::mimeparser::MimeMessage;
use crate::param::Param;
use crate::reaction::{get_msg_reactions, send_reaction};
use crate::receive_imf::receive_imf;
use crate::summary::assert_summary_texts;
use crate::test_utils::TestContextManager;
use crate::tests::pre_messages::util::{
@@ -795,3 +797,46 @@ async fn test_chatlist_event_on_post_msg_download() -> Result<()> {
Ok(())
}
#[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;
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; (PRE_MSG_ATTACHMENT_SIZE_THRESHOLD + 1) as usize],
)
.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(())
}