diff --git a/deltachat-jsonrpc/src/api.rs b/deltachat-jsonrpc/src/api.rs index 5d1a392ed..0179d55b2 100644 --- a/deltachat-jsonrpc/src/api.rs +++ b/deltachat-jsonrpc/src/api.rs @@ -39,6 +39,7 @@ use deltachat::{imex, info}; use sanitize_filename::is_sanitized; use tokio::fs; use tokio::sync::{watch, Mutex, RwLock}; +use types::basic_message::{BasicMessageLoadResult, BasicMessageObject}; use types::login_param::EnteredLoginParam; use walkdir::WalkDir; use yerpc::rpc; @@ -1266,6 +1267,48 @@ impl CommandApi { Ok(messages) } + async fn basic_get_message(&self, account_id: u32, msg_id: u32) -> Result { + let ctx = self.get_context(account_id).await?; + let msg_id = MsgId::new(msg_id); + let message_object = BasicMessageObject::from_msg_id(&ctx, msg_id) + .await + .with_context(|| format!("Failed to load message {msg_id} for account {account_id}"))? + .with_context(|| format!("Message {msg_id} does not exist for account {account_id}"))?; + Ok(message_object) + } + + /// get multiple messages in one call (but only basic properties) + /// + /// This is for optimized performance, the result of [get_messages] is more complete, but is more expensive. + /// + /// if loading one message fails the error is stored in the result object in it's place. + /// + /// this is the batch variant of [basic_get_message] + async fn basic_get_messages( + &self, + account_id: u32, + message_ids: Vec, + ) -> Result> { + let ctx = self.get_context(account_id).await?; + let mut messages: HashMap = HashMap::new(); + for message_id in message_ids { + let message_result = BasicMessageObject::from_msg_id(&ctx, MsgId::new(message_id)).await; + messages.insert( + message_id, + match message_result { + Ok(Some(message)) => BasicMessageLoadResult::Message(message), + Ok(None) => BasicMessageLoadResult::LoadingError { + error: "Message does not exist".to_string(), + }, + Err(error) => BasicMessageLoadResult::LoadingError { + error: format!("{error:#}"), + }, + }, + ); + } + Ok(messages) + } + /// Fetch info desktop needs for creating a notification for a message async fn get_message_notification_info( &self, diff --git a/deltachat-jsonrpc/src/api/types/basic_message.rs b/deltachat-jsonrpc/src/api/types/basic_message.rs new file mode 100644 index 000000000..2fa7e9b62 --- /dev/null +++ b/deltachat-jsonrpc/src/api/types/basic_message.rs @@ -0,0 +1,166 @@ +use anyhow::{Context as _, Result}; + +use deltachat::context::Context; +use deltachat::message::Message; +use deltachat::message::MsgId; +use num_traits::cast::ToPrimitive; +use serde::Serialize; +use typescript_type_def::TypeDef; + +use super::message::DownloadState; +use super::message::MessageViewtype; +use super::message::SystemMessageType; + +#[derive(Serialize, TypeDef, schemars::JsonSchema)] +#[serde(rename_all = "camelCase", tag = "kind")] +pub enum BasicMessageLoadResult { + Message(BasicMessageObject), + LoadingError { error: String }, +} + +/// Message that only has basic properties that doen't require additional db calls +#[derive(Serialize, TypeDef, schemars::JsonSchema)] +#[serde(rename = "BasicMessage", rename_all = "camelCase")] +pub struct BasicMessageObject { + id: u32, + chat_id: u32, + from_id: u32, + + text: String, + + is_edited: bool, + + /// Check if a message has a POI location bound to it. + /// These locations are also returned by `get_locations` method. + /// The UI may decide to display a special icon beside such messages. + has_location: bool, + has_html: bool, + view_type: MessageViewtype, + state: u32, + + /// An error text, if there is one. + error: Option, + + timestamp: i64, + sort_timestamp: i64, + received_timestamp: i64, + has_deviating_timestamp: bool, + + // summary - use/create another function if you need it + subject: String, + show_padlock: bool, + is_setupmessage: bool, + is_info: bool, + is_forwarded: bool, + + /// True if the message was sent by a bot. + is_bot: bool, + + /// when is_info is true this describes what type of system message it is + system_message_type: SystemMessageType, + + duration: i32, + dimensions_height: i32, + dimensions_width: i32, + + videochat_type: Option, + videochat_url: Option, + + override_sender_name: Option, + + setup_code_begin: Option, + + file: Option, + file_mime: Option, + file_name: Option, + + webxdc_href: Option, + + download_state: DownloadState, + + original_msg_id: Option, + + saved_message_id: Option, +} + +impl BasicMessageObject { + pub async fn from_msg_id(context: &Context, msg_id: MsgId) -> Result> { + let Some(message) = Message::load_from_db_optional(context, msg_id).await? else { + return Ok(None); + }; + + let override_sender_name = message.get_override_sender_name(); + + let download_state = message.download_state().into(); + + let message_object = Self { + id: msg_id.to_u32(), + chat_id: message.get_chat_id().to_u32(), + from_id: message.get_from_id().to_u32(), + text: message.get_text(), + is_edited: message.is_edited(), + has_location: message.has_location(), + has_html: message.has_html(), + view_type: message.get_viewtype().into(), + state: message + .get_state() + .to_u32() + .context("state conversion to number failed")?, + error: message.error(), + + timestamp: message.get_timestamp(), + sort_timestamp: message.get_sort_timestamp(), + received_timestamp: message.get_received_timestamp(), + has_deviating_timestamp: message.has_deviating_timestamp(), + + subject: message.get_subject().to_owned(), + show_padlock: message.get_showpadlock(), + is_setupmessage: message.is_setupmessage(), + is_info: message.is_info(), + is_forwarded: message.is_forwarded(), + is_bot: message.is_bot(), + system_message_type: message.get_info_type().into(), + + duration: message.get_duration(), + dimensions_height: message.get_height(), + dimensions_width: message.get_width(), + + videochat_type: match message.get_videochat_type() { + Some(vct) => Some( + vct.to_u32() + .context("videochat type conversion to number failed")?, + ), + None => None, + }, + videochat_url: message.get_videochat_url(), + + override_sender_name, + + setup_code_begin: message.get_setupcodebegin(context).await, + + file: match message.get_file(context) { + Some(path_buf) => path_buf.to_str().map(|s| s.to_owned()), + None => None, + }, //BLOBS + file_mime: message.get_filemime(), + file_name: message.get_filename(), + + // On a WebxdcInfoMessage this might include a hash holding + // information about a specific position or state in a webxdc app + webxdc_href: message.get_webxdc_href(), + + download_state, + + original_msg_id: message + .get_original_msg_id(context) + .await? + .map(|id| id.to_u32()), + + saved_message_id: message + .get_saved_msg_id(context) + .await? + .map(|id| id.to_u32()), + }; + Ok(Some(message_object)) + } +} diff --git a/deltachat-jsonrpc/src/api/types/mod.rs b/deltachat-jsonrpc/src/api/types/mod.rs index 995e931bf..393a3a7f5 100644 --- a/deltachat-jsonrpc/src/api/types/mod.rs +++ b/deltachat-jsonrpc/src/api/types/mod.rs @@ -1,4 +1,5 @@ pub mod account; +pub mod basic_message; pub mod chat; pub mod chat_list; pub mod contact;