From 3cc5b90235cb449bb475cfa39306d169fefed8b0 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Wed, 7 Oct 2026 23:44:15 +0200 Subject: [PATCH] api: new dc_msg_get_summarytext_without_prefix() --- deltachat-ffi/deltachat.h | 9 ++++- deltachat-ffi/src/lib.rs | 21 ++++++------ python/src/deltachat/message.py | 2 +- src/summary.rs | 59 +++++++++++++++++++++++++++------ 4 files changed, 68 insertions(+), 23 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 874a3b63c..fe06906f5 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -4293,11 +4293,18 @@ dc_lot_t* dc_msg_get_summary (const dc_msg_t* msg, const dc_cha * @memberof dc_msg_t * @param msg The message object. * @param approx_characters A rough length of the expected string. + * @param full_summary 1=return full summary with all details, + * 0=skip type and "forwarded" prefixes as they're rendered by the UI alerady, + * used for pinned messages that show e.g. an image and do not need the type in the summary therefore. * @return A summary for the given messages. * The returned string must be released using dc_str_unref(). * Returns an empty string on errors, never returns NULL. */ -char* dc_msg_get_summarytext (const dc_msg_t* msg, int approx_characters); +char* dc_msg_get_summary_text (const dc_msg_t* msg, int approx_characters, int with_emoji); + + +// depreacted 2026-10-07, the old variant took a string length as second parameter, which is converted to true. +#define dc_msg_get_summarytext(a, b) dc_msg_get_summary_text((a), (b), true) /** diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 301204f50..cf7977af0 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -12,7 +12,6 @@ extern crate human_panic; use std::collections::BTreeMap; -use std::convert::TryFrom; use std::fmt::Write; use std::future::Future; use std::mem::ManuallyDrop; @@ -3474,24 +3473,24 @@ pub unsafe extern "C" fn dc_msg_get_summary( } #[unsafe(no_mangle)] -pub unsafe extern "C" fn dc_msg_get_summarytext( +pub unsafe extern "C" fn dc_msg_get_summary_text( msg: *mut dc_msg_t, approx_characters: libc::c_int, + full_summary: libc::c_int, ) -> *mut libc::c_char { if msg.is_null() { - eprintln!("ignoring careless call to dc_msg_get_summarytext()"); + eprintln!("ignoring careless call to dc_msg_get_summary_text()"); return "".strdup(); } let ffi_msg = unsafe { &mut *msg }; - let summary = block_on(ffi_msg.message.get_summary(&ffi_msg.context, None)) - .context("dc_msg_get_summarytext failed") - .log_err(&ffi_msg.context) - .unwrap_or_default(); - match usize::try_from(approx_characters) { - Ok(chars) => summary.truncated_text(chars).strdup(), - Err(_) => summary.text.strdup(), - } + block_on(ffi_msg.message.get_summary_text_ext( + &ffi_msg.context, + approx_characters as usize, + full_summary != 0, + full_summary != 0, + )) + .strdup() } #[unsafe(no_mangle)] diff --git a/python/src/deltachat/message.py b/python/src/deltachat/message.py index c89ad0a3f..9052e2493 100644 --- a/python/src/deltachat/message.py +++ b/python/src/deltachat/message.py @@ -188,7 +188,7 @@ class Message: def get_summarytext(self, width: int) -> str: """Get a message summary as a single line of text. Typically used for notifications.""" - return from_dc_charpointer(lib.dc_msg_get_summarytext(self._dc_msg, width)) + return from_dc_charpointer(lib.dc_msg_get_summary_text(self._dc_msg, width, True)) @props.with_doc def time_sent(self): diff --git a/src/summary.rs b/src/summary.rs index ff5e215ba..ef0dc3952 100644 --- a/src/summary.rs +++ b/src/summary.rs @@ -138,19 +138,35 @@ impl Summary { } impl Message { - /// Returns a summary text. + /// Returns a summary text with emoji and "Forwarded:" prefixes. + /// This is the standard summary to be used in chatlists, notifications etc. pub(crate) async fn get_summary_text(&self, context: &Context) -> String { - let summary = self.get_summary_text_without_prefix(context).await; - - if self.is_forwarded() { - format!("{}: {}", stock_str::forwarded(context), summary) - } else { - summary - } + let approx_chars = 0; + let with_forwarded = true; + let with_emoji = true; + self.get_summary_text_ext(context, approx_chars, with_forwarded, with_emoji) + .await } - /// Returns a summary text without "Forwarded:" prefix. + /// Returns a summary text with emoji prefixes but without "Forwarded:" prefix. + /// Used for shorter reaction summaries as "USER reactes 👋 to SUMMARY" async fn get_summary_text_without_prefix(&self, context: &Context) -> String { + let approx_chars = 0; + let with_forwarded = false; + let with_emoji = true; + self.get_summary_text_ext(context, approx_chars, with_forwarded, with_emoji) + .await + } + + /// Returns a summary text with optional "Forwarded:" and emoji prefixes. + /// If approx_chars is >0, the string is truncated at about that position. + pub async fn get_summary_text_ext( + &self, + context: &Context, + approx_chars: usize, + with_forwarded: bool, + with_emoji: bool, + ) -> String { let (emoji, type_name, type_file, append_text); let viewtype = match self .param @@ -264,7 +280,11 @@ impl Message { } }; - let text = self.text.clone(); + let text = if approx_chars > 0 { + truncate(&self.text, approx_chars).to_string() + } else { + self.text.clone() + }; let summary = if let Some(type_file) = type_file { if append_text && !text.is_empty() { @@ -286,12 +306,19 @@ impl Message { "".to_string() }; + let emoji = emoji.filter(|_| with_emoji); let summary = if let Some(emoji) = emoji { format!("{emoji} {summary}") } else { summary }; + let summary = if with_forwarded && self.is_forwarded() { + format!("{}: {}", stock_str::forwarded(context), summary) + } else { + summary + }; + summary.split_whitespace().collect::>().join(" ") } } @@ -336,6 +363,10 @@ mod tests { msg.set_file_and_deduplicate(&d, &file, Some("foo.jpg"), None) .unwrap(); assert_summary_texts(&msg, ctx, "📷 Image").await; // file names are not added for images + assert_eq!( + msg.get_summary_text_ext(ctx, 500, false, false).await, + "Image" + ); let file = write_file_to_blobdir(&d).await; let mut msg = Message::new(Viewtype::Image); @@ -343,6 +374,10 @@ mod tests { msg.set_file_and_deduplicate(&d, &file, Some("foo.jpg"), None) .unwrap(); assert_summary_texts(&msg, ctx, "📷 bla bla").await; // type is visible by emoji if text is set + assert_eq!( + msg.get_summary_text_ext(ctx, 500, false, false).await, + "bla bla" + ); let file = write_file_to_blobdir(&d).await; let mut msg = Message::new(Viewtype::Video); @@ -473,6 +508,10 @@ mod tests { msg.get_summary_text_without_prefix(ctx).await, "📎 foo.bar \u{2013} bla bla" ); // skipping prefix used for reactions summaries + assert_eq!( + msg.get_summary_text_ext(ctx, 500, false, false).await, + "foo.bar \u{2013} bla bla" + ); d.assert_warn("Not a valid DeltaChat vCard").await; } }