use std::borrow::Cow;
use std::fmt;
use crate::chat::Chat;
use crate::constants::Chattype;
use crate::contact::{Contact, ContactId};
use crate::context::Context;
use crate::message::{Message, MessageState, Viewtype};
use crate::mimeparser::SystemMessage;
use crate::stock_str;
use crate::stock_str::msg_reacted;
use crate::tools::truncate;
use anyhow::Result;
#[derive(Debug)]
pub enum SummaryPrefix {
Username(String),
Draft(String),
Me(String),
}
impl fmt::Display for SummaryPrefix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SummaryPrefix::Username(username) => write!(f, "{username}"),
SummaryPrefix::Draft(text) => write!(f, "{text}"),
SummaryPrefix::Me(text) => write!(f, "{text}"),
}
}
}
#[derive(Debug, Default)]
pub struct Summary {
pub prefix: Option<SummaryPrefix>,
pub text: String,
pub timestamp: i64,
pub state: MessageState,
pub thumbnail_path: Option<String>,
}
impl Summary {
pub async fn new_with_reaction_details(
context: &Context,
msg: &Message,
chat: &Chat,
contact: Option<&Contact>,
) -> Result<Summary> {
if let Some((reaction_msg, reaction_contact_id, reaction)) = chat
.get_last_reaction_if_newer_than(context, msg.timestamp_sort)
.await?
{
let summary = reaction_msg.get_summary_text_without_prefix(context).await;
return Ok(Summary {
prefix: None,
text: msg_reacted(context, reaction_contact_id, &reaction, &summary).await,
timestamp: msg.get_timestamp(), state: msg.state, thumbnail_path: None,
});
}
Self::new(context, msg, chat, contact).await
}
pub async fn new(
context: &Context,
msg: &Message,
chat: &Chat,
contact: Option<&Contact>,
) -> Result<Summary> {
let prefix = if msg.state == MessageState::OutDraft {
Some(SummaryPrefix::Draft(stock_str::draft(context).await))
} else if msg.from_id == ContactId::SELF {
if msg.is_info() || chat.is_self_talk() {
None
} else {
Some(SummaryPrefix::Me(stock_str::self_msg(context).await))
}
} else {
match chat.typ {
Chattype::Group | Chattype::Broadcast | Chattype::Mailinglist => {
if msg.is_info() || contact.is_none() {
None
} else {
msg.get_override_sender_name()
.or_else(|| contact.map(|contact| msg.get_sender_name(contact)))
.map(SummaryPrefix::Username)
}
}
Chattype::Single => None,
}
};
let mut text = msg.get_summary_text(context).await;
if text.is_empty() && msg.quoted_text().is_some() {
text = stock_str::reply_noun(context).await
}
let thumbnail_path = if msg.viewtype == Viewtype::Image
|| msg.viewtype == Viewtype::Gif
|| msg.viewtype == Viewtype::Sticker
{
msg.get_file(context)
.and_then(|path| path.to_str().map(|p| p.to_owned()))
} else {
None
};
Ok(Summary {
prefix,
text,
timestamp: msg.get_timestamp(),
state: msg.state,
thumbnail_path,
})
}
pub fn truncated_text(&self, approx_chars: usize) -> Cow<str> {
truncate(&self.text, approx_chars)
}
}
impl Message {
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).await, summary)
} else {
summary
}
}
async fn get_summary_text_without_prefix(&self, context: &Context) -> String {
let (emoji, type_name, type_file, append_text);
match self.viewtype {
Viewtype::Image => {
emoji = Some("📷");
type_name = Some(stock_str::image(context).await);
type_file = None;
append_text = true;
}
Viewtype::Gif => {
emoji = None;
type_name = Some(stock_str::gif(context).await);
type_file = None;
append_text = true;
}
Viewtype::Sticker => {
emoji = None;
type_name = Some(stock_str::sticker(context).await);
type_file = None;
append_text = true;
}
Viewtype::Video => {
emoji = Some("🎥");
type_name = Some(stock_str::video(context).await);
type_file = None;
append_text = true;
}
Viewtype::Voice => {
emoji = Some("🎤");
type_name = Some(stock_str::voice_message(context).await);
type_file = None;
append_text = true;
}
Viewtype::Audio => {
emoji = Some("🎵");
type_name = Some(stock_str::audio(context).await);
type_file = self.get_filename();
append_text = true
}
Viewtype::File => {
if self.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
emoji = None;
type_name = Some(stock_str::ac_setup_msg_subject(context).await);
type_file = None;
append_text = false;
} else {
emoji = Some("📎");
type_name = Some(stock_str::file(context).await);
type_file = self.get_filename();
append_text = true
}
}
Viewtype::VideochatInvitation => {
emoji = None;
type_name = Some(stock_str::videochat_invitation(context).await);
type_file = None;
append_text = false;
}
Viewtype::Webxdc => {
emoji = None;
type_name = None;
type_file = Some(
self.get_webxdc_info(context)
.await
.map(|info| info.name)
.unwrap_or_else(|_| "ErrWebxdcName".to_string()),
);
append_text = true;
}
Viewtype::Text | Viewtype::Unknown => {
emoji = None;
if self.param.get_cmd() == SystemMessage::LocationOnly {
type_name = Some(stock_str::location(context).await);
type_file = None;
append_text = false;
} else {
type_name = None;
type_file = None;
append_text = true;
}
}
};
let text = self.text.clone();
let summary = if let Some(type_file) = type_file {
if append_text && !text.is_empty() {
format!("{type_file} – {text}")
} else {
type_file
}
} else if append_text && !text.is_empty() {
if emoji.is_some() {
text
} else if let Some(type_name) = type_name {
format!("{type_name} – {text}")
} else {
text
}
} else if let Some(type_name) = type_name {
type_name
} else {
"".to_string()
};
let summary = if let Some(emoji) = emoji {
format!("{emoji} {summary}")
} else {
summary
};
summary.split_whitespace().collect::<Vec<&str>>().join(" ")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::param::Param;
use crate::test_utils as test;
async fn assert_summary_texts(msg: &Message, ctx: &Context, expected: &str) {
assert_eq!(msg.get_summary_text(ctx).await, expected);
assert_eq!(msg.get_summary_text_without_prefix(ctx).await, expected);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_summary_text() {
let d = test::TestContext::new().await;
let ctx = &d.ctx;
let some_text = " bla \t\n\tbla\n\t".to_string();
let mut msg = Message::new(Viewtype::Text);
msg.set_text(some_text.to_string());
assert_summary_texts(&msg, ctx, "bla bla").await; let mut msg = Message::new(Viewtype::Image);
msg.set_file("foo.jpg", None);
assert_summary_texts(&msg, ctx, "📷 Image").await; let mut msg = Message::new(Viewtype::Image);
msg.set_text(some_text.to_string());
msg.set_file("foo.jpg", None);
assert_summary_texts(&msg, ctx, "📷 bla bla").await; let mut msg = Message::new(Viewtype::Video);
msg.set_file("foo.mp4", None);
assert_summary_texts(&msg, ctx, "🎥 Video").await; let mut msg = Message::new(Viewtype::Video);
msg.set_text(some_text.to_string());
msg.set_file("foo.mp4", None);
assert_summary_texts(&msg, ctx, "🎥 bla bla").await; let mut msg = Message::new(Viewtype::Gif);
msg.set_file("foo.gif", None);
assert_summary_texts(&msg, ctx, "GIF").await; let mut msg = Message::new(Viewtype::Gif);
msg.set_text(some_text.to_string());
msg.set_file("foo.gif", None);
assert_summary_texts(&msg, ctx, "GIF \u{2013} bla bla").await; let mut msg = Message::new(Viewtype::Sticker);
msg.set_file("foo.png", None);
assert_summary_texts(&msg, ctx, "Sticker").await; let mut msg = Message::new(Viewtype::Voice);
msg.set_file("foo.mp3", None);
assert_summary_texts(&msg, ctx, "🎤 Voice message").await; let mut msg = Message::new(Viewtype::Voice);
msg.set_text(some_text.clone());
msg.set_file("foo.mp3", None);
assert_summary_texts(&msg, ctx, "🎤 bla bla").await;
let mut msg = Message::new(Viewtype::Audio);
msg.set_file("foo.mp3", None);
assert_summary_texts(&msg, ctx, "🎵 foo.mp3").await; let mut msg = Message::new(Viewtype::Audio);
msg.set_file("foo.mp3", None);
assert_summary_texts(&msg, ctx, "🎵 foo.mp3").await; let mut msg = Message::new(Viewtype::Audio);
msg.set_text(some_text.clone());
msg.set_file("foo.mp3", None);
assert_summary_texts(&msg, ctx, "🎵 foo.mp3 \u{2013} bla bla").await; let mut msg = Message::new(Viewtype::File);
msg.set_file("foo.bar", None);
assert_summary_texts(&msg, ctx, "📎 foo.bar").await; let mut msg = Message::new(Viewtype::File);
msg.set_text(some_text.clone());
msg.set_file("foo.bar", None);
assert_summary_texts(&msg, ctx, "📎 foo.bar \u{2013} bla bla").await; let mut msg = Message::new(Viewtype::VideochatInvitation);
msg.set_text(some_text.clone());
msg.set_file("foo.bar", None);
assert_summary_texts(&msg, ctx, "Video chat invitation").await; let mut msg = Message::new(Viewtype::Text);
msg.set_text(some_text.clone());
msg.param.set_int(Param::Forwarded, 1);
assert_eq!(msg.get_summary_text(ctx).await, "Forwarded: bla bla"); assert_eq!(msg.get_summary_text_without_prefix(ctx).await, "bla bla"); let mut msg = Message::new(Viewtype::File);
msg.set_text(some_text.clone());
msg.set_file("foo.bar", None);
msg.param.set_int(Param::Forwarded, 1);
assert_eq!(
msg.get_summary_text(ctx).await,
"Forwarded: 📎 foo.bar \u{2013} bla bla"
);
assert_eq!(
msg.get_summary_text_without_prefix(ctx).await,
"📎 foo.bar \u{2013} bla bla"
); let mut msg = Message::new(Viewtype::File);
msg.set_text(some_text.clone());
msg.param.set(Param::File, "foo.bar");
msg.param.set_cmd(SystemMessage::AutocryptSetupMessage);
assert_summary_texts(&msg, ctx, "Autocrypt Setup Message").await; }
}