diff --git a/CHANGELOG.md b/CHANGELOG.md index 91774b5c6..0d7911a46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - added `DC_MSG_WEBXDC`, `dc_send_webxdc_status_update()`, `dc_get_webxdc_status_updates()`, `dc_msg_get_webxdc_blob()`, `dc_msg_get_webxdc_info()`, and `DC_EVENT_WEBXDC_STATUS_UPDATE` #2826 +- added `dc_msg_get_parent()` #2984 - Add `dc_msg_force_plaintext()` API for bots #2847 diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 47564bc98..e503becc1 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -3965,8 +3965,11 @@ int dc_msg_is_forwarded (const dc_msg_t* msg); * These messages are typically shown in the center of the chat view, * dc_msg_get_text() returns a descriptive text about what is going on. * + * For informational messages created by Webxdc apps, + * dc_msg_get_parent() usually returns the Webxdc instance; + * UIs can use that to scroll to the Webxdc app when the info is tapped. + * * There is no need to perform any action when seeing such a message - this is already done by the core. - * Typically, these messages are displayed in the center of the chat. * * @memberof dc_msg_t * @param msg The message object. @@ -4367,6 +4370,23 @@ char* dc_msg_get_quoted_text (const dc_msg_t* msg); */ dc_msg_t* dc_msg_get_quoted_msg (const dc_msg_t* msg); +/** + * Get parent message, if available. + * + * Used for Webxdc-info-messages + * to jump to the corresponding instance that created the info message. + * + * For quotes, please use the more specialized + * dc_msg_get_quoted_text() and dc_msg_get_quoted_msg(). + * + * @memberof dc_msg_t + * @param msg The message object. + * @return The parent message or NULL. + * Must be freed using dc_msg_unref() after usage. + */ +dc_msg_t* dc_msg_get_parent (const dc_msg_t* msg); + + /** * Force the message to be sent in plain text. * diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index ef9b4a8da..8846ab57f 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -3594,6 +3594,29 @@ pub unsafe extern "C" fn dc_msg_get_quoted_msg(msg: *const dc_msg_t) -> *mut dc_ } } +#[no_mangle] +pub unsafe extern "C" fn dc_msg_get_parent(msg: *const dc_msg_t) -> *mut dc_msg_t { + if msg.is_null() { + eprintln!("ignoring careless call to dc_msg_get_parent()"); + return ptr::null_mut(); + } + let ffi_msg: &MessageWrapper = &*msg; + let context = &*ffi_msg.context; + let res = block_on(async move { + ffi_msg + .message + .parent(context) + .await + .log_err(context, "failed to get parent message") + .unwrap_or(None) + }); + + match res { + Some(message) => Box::into_raw(Box::new(MessageWrapper { context, message })), + None => ptr::null_mut(), + } +} + #[no_mangle] pub unsafe extern "C" fn dc_msg_force_plaintext(msg: *mut dc_msg_t) { if msg.is_null() { diff --git a/src/chat.rs b/src/chat.rs index 54e8e62f6..db7ae3fea 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -443,6 +443,7 @@ impl ChatId { &msg_text, cmd, dc_create_smeared_timestamp(context).await, + None, ) .await?; } @@ -3391,6 +3392,7 @@ pub(crate) async fn add_info_msg_with_cmd( text: &str, cmd: SystemMessage, timestamp: i64, + parent: Option<&Message>, ) -> Result { let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device"); let ephemeral_timer = chat_id.get_ephemeral_timer(context).await?; @@ -3402,7 +3404,7 @@ pub(crate) async fn add_info_msg_with_cmd( let row_id = context.sql.insert( - "INSERT INTO msgs (chat_id,from_id,to_id,timestamp,type,state,txt,rfc724_mid,ephemeral_timer, param) VALUES (?,?,?, ?,?,?, ?,?,?, ?);", + "INSERT INTO msgs (chat_id,from_id,to_id,timestamp,type,state,txt,rfc724_mid,ephemeral_timer, param,mime_in_reply_to) VALUES (?,?,?, ?,?,?, ?,?,?, ?,?);", paramsv![ chat_id, DC_CONTACT_ID_INFO, @@ -3414,6 +3416,7 @@ pub(crate) async fn add_info_msg_with_cmd( rfc724_mid, ephemeral_timer, param.to_string(), + parent.map(|msg|msg.rfc724_mid.clone()).unwrap_or_default() ] ).await?; @@ -3429,7 +3432,15 @@ pub(crate) async fn add_info_msg( text: &str, timestamp: i64, ) -> Result { - add_info_msg_with_cmd(context, chat_id, text, SystemMessage::Unknown, timestamp).await + add_info_msg_with_cmd( + context, + chat_id, + text, + SystemMessage::Unknown, + timestamp, + None, + ) + .await } #[cfg(test)] @@ -4415,34 +4426,37 @@ mod tests { assert_eq!(msg.get_text().unwrap(), "foo info"); assert!(msg.is_info()); assert_eq!(msg.get_info_type(), SystemMessage::Unknown); + assert!(msg.parent(&t).await?.is_none()); + assert!(msg.quoted_message(&t).await?.is_none()); Ok(()) } #[async_std::test] - async fn test_add_info_msg_with_cmd() { + async fn test_add_info_msg_with_cmd() -> Result<()> { let t = TestContext::new().await; - let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo") - .await - .unwrap(); + let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "foo").await?; let msg_id = add_info_msg_with_cmd( &t, chat_id, "foo bar info", SystemMessage::EphemeralTimerChanged, 10000, + None, ) - .await - .unwrap(); + .await?; - let msg = Message::load_from_db(&t, msg_id).await.unwrap(); + let msg = Message::load_from_db(&t, msg_id).await?; assert_eq!(msg.get_chat_id(), chat_id); assert_eq!(msg.get_viewtype(), Viewtype::Text); assert_eq!(msg.get_text().unwrap(), "foo bar info"); assert!(msg.is_info()); assert_eq!(msg.get_info_type(), SystemMessage::EphemeralTimerChanged); + assert!(msg.parent(&t).await?.is_none()); + assert!(msg.quoted_message(&t).await?.is_none()); let msg2 = t.get_last_msg_in(chat_id).await; assert_eq!(msg.get_id(), msg2.get_id()); + Ok(()) } #[async_std::test] diff --git a/src/message.rs b/src/message.rs index a274c8a44..96fa3858d 100644 --- a/src/message.rs +++ b/src/message.rs @@ -830,7 +830,7 @@ impl Message { Ok(None) } - pub(crate) async fn parent(&self, context: &Context) -> Result> { + pub async fn parent(&self, context: &Context) -> Result> { if let Some(in_reply_to) = &self.in_reply_to { if let Some(msg_id) = rfc724_mid_exists(context, in_reply_to).await? { let msg = Message::load_from_db(context, msg_id).await?; diff --git a/src/webxdc.rs b/src/webxdc.rs index 0bd867ef2..ae704e93e 100644 --- a/src/webxdc.rs +++ b/src/webxdc.rs @@ -156,7 +156,15 @@ impl Context { }; if let Some(ref info) = status_update_item.info { - chat::add_info_msg(self, instance.chat_id, info.as_str(), timestamp).await?; + chat::add_info_msg_with_cmd( + self, + instance.chat_id, + info.as_str(), + SystemMessage::Unknown, + timestamp, + Some(instance), + ) + .await?; } if let Some(ref summary) = status_update_item.summary { @@ -1283,6 +1291,11 @@ sth_for_the = "future""# info_msg.get_text(), Some("this appears in-chat".to_string()) ); + assert_eq!( + info_msg.parent(&alice).await?.unwrap().id, + alice_instance.id + ); + assert!(info_msg.quoted_message(&alice).await?.is_none()); assert_eq!( alice .get_webxdc_status_updates(alice_instance.id, None) @@ -1302,6 +1315,8 @@ sth_for_the = "future""# info_msg.get_text(), Some("this appears in-chat".to_string()) ); + assert_eq!(info_msg.parent(&bob).await?.unwrap().id, bob_instance.id); + assert!(info_msg.quoted_message(&bob).await?.is_none()); assert_eq!( bob.get_webxdc_status_updates(bob_instance.id, None).await?, r#"[{"payload":"sth. else","info":"this appears in-chat"}]"# @@ -1320,6 +1335,11 @@ sth_for_the = "future""# info_msg.get_text(), Some("this appears in-chat".to_string()) ); + assert_eq!( + info_msg.parent(&alice2).await?.unwrap().id, + alice2_instance.id + ); + assert!(info_msg.quoted_message(&alice2).await?.is_none()); assert_eq!( alice2 .get_webxdc_status_updates(alice2_instance.id, None)