diff --git a/deltachat-jsonrpc/src/api.rs b/deltachat-jsonrpc/src/api.rs index 8fa77eb9d..7f1d57b48 100644 --- a/deltachat-jsonrpc/src/api.rs +++ b/deltachat-jsonrpc/src/api.rs @@ -10,8 +10,9 @@ pub use deltachat::accounts::Accounts; use deltachat::blob::BlobObject; use deltachat::calls::ice_servers; use deltachat::chat::{ - self, add_contact_to_chat, forward_msgs, get_chat_media, get_chat_msgs, get_chat_msgs_ex, - marknoticed_chat, remove_contact_from_chat, Chat, ChatId, ChatItem, MessageListOptions, + self, add_contact_to_chat, forward_msgs, forward_msgs_2ctx, get_chat_media, get_chat_msgs, + get_chat_msgs_ex, marknoticed_chat, remove_contact_from_chat, Chat, ChatId, ChatItem, + MessageListOptions, }; use deltachat::chatlist::Chatlist; use deltachat::config::Config; @@ -2208,6 +2209,27 @@ impl CommandApi { forward_msgs(&ctx, &message_ids, ChatId::new(chat_id)).await } + /// Forward messages to a chat in another account. + /// See [`Self::forward_messages`] for more info. + async fn forward_messages_to_account( + &self, + src_account_id: u32, + src_message_ids: Vec, + dst_account_id: u32, + dst_chat_id: u32, + ) -> Result<()> { + let src_ctx = self.get_context(src_account_id).await?; + let dst_ctx = self.get_context(dst_account_id).await?; + let src_message_ids: Vec = src_message_ids.into_iter().map(MsgId::new).collect(); + forward_msgs_2ctx( + &src_ctx, + &src_message_ids, + &dst_ctx, + ChatId::new(dst_chat_id), + ) + .await + } + /// Resend messages and make information available for newly added chat members. /// Resending sends out the original message, however, recipients and webxdc-status may differ. /// Clients that already have the original message can still ignore the resent message as diff --git a/src/chat.rs b/src/chat.rs index b08bd17af..44baa0e3b 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -4203,6 +4203,16 @@ pub async fn set_chat_profile_image( /// Forwards multiple messages to a chat. pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) -> Result<()> { + forward_msgs_2ctx(context, msg_ids, context, chat_id).await +} + +/// Forwards multiple messages to a chat in another context. +pub async fn forward_msgs_2ctx( + ctx_src: &Context, + msg_ids: &[MsgId], + ctx_dst: &Context, + chat_id: ChatId, +) -> Result<()> { ensure!(!msg_ids.is_empty(), "empty msgs_ids: nothing to forward"); ensure!(!chat_id.is_special(), "can not forward to special chat"); @@ -4210,16 +4220,16 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) let mut curr_timestamp: i64; chat_id - .unarchive_if_not_muted(context, MessageState::Undefined) + .unarchive_if_not_muted(ctx_dst, MessageState::Undefined) .await?; - let mut chat = Chat::load_from_db(context, chat_id).await?; - if let Some(reason) = chat.why_cant_send(context).await? { + let mut chat = Chat::load_from_db(ctx_dst, chat_id).await?; + if let Some(reason) = chat.why_cant_send(ctx_dst).await? { bail!("cannot send to {chat_id}: {reason}"); } - curr_timestamp = create_smeared_timestamps(context, msg_ids.len()); + curr_timestamp = create_smeared_timestamps(ctx_dst, msg_ids.len()); let mut msgs = Vec::with_capacity(msg_ids.len()); for id in msg_ids { - let ts: i64 = context + let ts: i64 = ctx_src .sql .query_get_value("SELECT timestamp FROM msgs WHERE id=?", (id,)) .await? @@ -4229,7 +4239,7 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) msgs.sort_unstable(); for (_, id) in msgs { let src_msg_id: MsgId = id; - let mut msg = Message::load_from_db(context, src_msg_id).await?; + let mut msg = Message::load_from_db(ctx_src, src_msg_id).await?; if msg.state == MessageState::OutDraft { bail!("cannot forward drafts."); } @@ -4264,16 +4274,16 @@ pub async fn forward_msgs(context: &Context, msg_ids: &[MsgId], chat_id: ChatId) msg.state = MessageState::OutPending; msg.rfc724_mid = create_outgoing_rfc724_mid(); msg.timestamp_sort = curr_timestamp; - chat.prepare_msg_raw(context, &mut msg, None).await?; + chat.prepare_msg_raw(ctx_dst, &mut msg, None).await?; curr_timestamp += 1; - if !create_send_msg_jobs(context, &mut msg).await?.is_empty() { - context.scheduler.interrupt_smtp().await; + if !create_send_msg_jobs(ctx_dst, &mut msg).await?.is_empty() { + ctx_dst.scheduler.interrupt_smtp().await; } created_msgs.push(msg.id); } for msg_id in created_msgs { - context.emit_msgs_changed(chat_id, msg_id); + ctx_dst.emit_msgs_changed(chat_id, msg_id); } Ok(()) } diff --git a/src/chat/chat_tests.rs b/src/chat/chat_tests.rs index 325ce41f3..a93ab2179 100644 --- a/src/chat/chat_tests.rs +++ b/src/chat/chat_tests.rs @@ -5240,6 +5240,44 @@ async fn test_send_delete_request_no_encryption() -> Result<()> { Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_forward_msgs_2ctx() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = &tcm.alice().await; + let bob = &tcm.bob().await; + + let alice_chat = alice.create_chat(bob).await; + let alice_sent = alice.send_text(alice_chat.id, "hi").await; + let bob_alice_msg = bob.recv_msg(&alice_sent).await; + let bob_chat_id = bob_alice_msg.chat_id; + bob_chat_id.accept(bob).await?; + + let bob_text = "Hi, did you know we're using the same device so i have access to your profile?"; + let bob_sent = bob.send_text(bob_chat_id, bob_text).await; + alice.recv_msg(&bob_sent).await; + let alice_chat_len = alice_chat.id.get_msg_cnt(alice).await?; + + forward_msgs_2ctx( + bob, + &[bob_alice_msg.id, bob_sent.sender_msg_id], + alice, + alice_chat.id, + ) + .await?; + assert_eq!(alice_chat.id.get_msg_cnt(alice).await?, alice_chat_len + 2); + let msg = alice.get_last_msg().await; + assert!(msg.is_forwarded()); + assert_eq!(msg.text, bob_text); + assert_eq!(msg.from_id, ContactId::SELF); + + let sent = alice.pop_sent_msg().await; + let msg = bob.recv_msg(&sent).await; + assert!(msg.is_forwarded()); + assert_eq!(msg.text, bob_text); + assert_eq!(msg.from_id, bob_alice_msg.from_id); + Ok(()) +} + /// Tests that in multi-device setup /// second device learns the key of a contact /// via Autocrypt-Gossip in 1:1 chats.