diff --git a/Cargo.lock b/Cargo.lock index bfd31e42b..97eb0eb3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1118,6 +1118,7 @@ dependencies = [ "chrono", "criterion", "deltachat_derive", + "deltachat_message_parser", "email", "encoded-words", "escaper", @@ -1259,6 +1260,18 @@ dependencies = [ "yerpc", ] +[[package]] +name = "deltachat_message_parser" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826f80f4b32f51457773351b2d821dc1f45273a38235e8fd3bdf662b67b70bcd" +dependencies = [ + "nom", + "serde", + "serde_derive", + "unic-idna-punycode", +] + [[package]] name = "der" version = "0.6.1" @@ -5221,6 +5234,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "unic-idna-punycode" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06feaedcbf9f1fc259144d833c0d630b8b15207b0486ab817d29258bc89f2f8a" + [[package]] name = "unicode-bidi" version = "0.3.13" diff --git a/Cargo.toml b/Cargo.toml index d5cd1b740..28245b9fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,6 +94,7 @@ toml = "0.7" trust-dns-resolver = "0.22" url = "2" uuid = { version = "1", features = ["serde", "v4"] } +deltachat_message_parser = "0.8.0" [dev-dependencies] ansi_term = "0.12.0" @@ -154,8 +155,4 @@ harness = false [features] default = ["vendored"] internals = [] -vendored = [ - "async-native-tls/vendored", - "rusqlite/bundled-sqlcipher-vendored-openssl", - "reqwest/native-tls-vendored" -] +vendored = ["async-native-tls/vendored", "rusqlite/bundled-sqlcipher-vendored-openssl", "reqwest/native-tls-vendored"] diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 3c3beb448..4d3ad1304 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -3993,6 +3993,36 @@ int64_t dc_msg_get_sort_timestamp (const dc_msg_t* msg); char* dc_msg_get_text (const dc_msg_t* msg); +#define MESSAGE_PARSER_MODE_ONLY_TEXT 0x00 +#define MESSAGE_PARSER_MODE_DESKTOP_SET 0x01 +#define MESSAGE_PARSER_MODE_MARKDOWN 0x02 + +/** + * Parse text with the message parser. + * + * @memberof dc_context_t + * @param input The text to parse. + * @param mode Sets the parsing mode, you can choose between MESSAGE_PARSER_MODE_ONLY_TEXT, MESSAGE_PARSER_MODE_DESKTOP_SET and MESSAGE_PARSER_MODE_MARKDOWN. + * Look at https://github.com/deltachat/message-parser/blob/master/spec.md#modes-of-the-parser to learn more about the parser modes. + * @return Abstract Syntax Tree for your message that you can use to display parts of a message specially like links. + * This ast is returned in json (look at the sourcecode for reference for the format: https://github.com/deltachat/message-parser/blob/master/src/parser/mod.rs#L11) + */ +char* dc_parse_message_text_to_ast_json (const char* input, int mode); + + +/** + * Parse the text of a message with the message parser. + * + * @memberof dc_msg_t + * @param msg The message object. + * @param mode Sets the parsing mode, you can choose between MESSAGE_PARSER_MODE_ONLY_TEXT, MESSAGE_PARSER_MODE_DESKTOP_SET and MESSAGE_PARSER_MODE_MARKDOWN. + * Look at https://github.com/deltachat/message-parser/blob/master/spec.md#modes-of-the-parser to learn more about the parser modes. + * @return Abstract Syntax Tree for your message that you can use to display parts of a message specially like links. + * This ast is returned in json (look at the sourcecode for reference for the format: https://github.com/deltachat/message-parser/blob/master/src/parser/mod.rs#L11) + */ +char* dc_msg_get_parsed_text_as_json (const dc_msg_t* msg, int mode); + + /** * Get the subject of the e-mail. * If there is no subject associated with the message, an empty string is returned. diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 86d142355..36697fe43 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -31,6 +31,7 @@ use deltachat::ephemeral::Timer as EphemeralTimer; use deltachat::imex::BackupProvider; use deltachat::key::{DcKey, DcSecretKey}; use deltachat::message::MsgId; +use deltachat::message_parser::parser; use deltachat::net::read_url_blob; use deltachat::qr_code_generator::{generate_backup_qr, get_securejoin_qr_svg}; use deltachat::reaction::{get_msg_reactions, send_reaction, Reactions}; @@ -3356,6 +3357,58 @@ pub unsafe extern "C" fn dc_msg_get_text(msg: *mut dc_msg_t) -> *mut libc::c_cha ffi_msg.message.get_text().strdup() } +#[no_mangle] +pub unsafe extern "C" fn dc_parse_message_text_to_ast_json( + text: *const libc::c_char, + mode: u32, +) -> *mut libc::c_char { + if text.is_null() { + eprintln!("ignoring careless call to dc_parse_message_text_to_ast_json()"); + } + let text = to_string_lossy(text); + let result = match mode { + 0 /* OnlyText */ => parser::parse_only_text(&text), + 1 /* DesktopSet */ => parser::parse_desktop_set(&text), + 2 /* Markdown */ => parser::parse_markdown_text(&text), + _ => { + eprintln!("ignoring careless call to dc_parse_message_text_to_ast_json() - invalid mode"); + return "".strdup(); + } + }; + if let Ok(result) = serde_json::to_string(&result) { + result.strdup() + } else { + "".strdup() + } +} + +#[no_mangle] +pub unsafe extern "C" fn dc_msg_get_parsed_text_as_json( + msg: *mut dc_msg_t, + mode: u32, +) -> *mut libc::c_char { + if msg.is_null() { + eprintln!("ignoring careless call to dc_msg_get_parsed_text_as_json()"); + return "".strdup(); + } + let ffi_msg = &*msg; + let text = ffi_msg.message.get_text(); + let result = match mode { + 0 /* OnlyText */ => parser::parse_only_text(&text), + 1 /* DesktopSet */ => parser::parse_desktop_set(&text), + 2 /* Markdown */ => parser::parse_markdown_text(&text), + _ => { + eprintln!("ignoring careless call to dc_msg_get_parsed_text_as_json() - invalid mode"); + return "".strdup(); + } + }; + if let Ok(result) = serde_json::to_string(&result) { + result.strdup() + } else { + "".strdup() + } +} + #[no_mangle] pub unsafe extern "C" fn dc_msg_get_subject(msg: *mut dc_msg_t) -> *mut libc::c_char { if msg.is_null() { diff --git a/deltachat-jsonrpc/src/api/mod.rs b/deltachat-jsonrpc/src/api/mod.rs index bbbf1fdf4..f0bd9d72b 100644 --- a/deltachat-jsonrpc/src/api/mod.rs +++ b/deltachat-jsonrpc/src/api/mod.rs @@ -43,11 +43,13 @@ use types::contact::ContactObject; use types::events::Event; use types::http::HttpResponse; use types::message::{MessageData, MessageObject, MessageReadReceipt}; +use types::message_parser; use types::provider_info::ProviderInfo; use types::reactions::JSONRPCReactions; use types::webxdc::WebxdcMessageInfo; use self::types::message::MessageLoadResult; +use self::types::message_parser::MessageParserMode; use self::types::{ chat::{BasicChat, JSONRPCChatVisibility, MuteDuration}, location::JsonrpcLocation, @@ -1081,6 +1083,31 @@ impl CommandApi { MsgId::new(message_id).get_html(&ctx).await } + // no specific typings because of https://github.com/dbeckwith/rust-typescript-type-def/issues/18 + async fn get_parsed_message_text_ast_json( + &self, + account_id: u32, + message_id: u32, + mode: MessageParserMode, + ) -> Result { + let ctx = self.get_context(account_id).await?; + let msg_text = Message::load_from_db(&ctx, MsgId::new(message_id)) + .await? + .get_text(); + let result = message_parser::parse_text(&msg_text, mode); + Ok(serde_json::to_value(result)?) + } + + // no specific typings because of https://github.com/dbeckwith/rust-typescript-type-def/issues/18 + async fn parse_text_to_ast_json( + &self, + text: String, + mode: MessageParserMode, + ) -> Result { + let result = message_parser::parse_text(&text, mode); + Ok(serde_json::to_value(result)?) + } + /// get multiple messages in one call, /// if loading one message fails the error is stored in the result object in it's place. /// diff --git a/deltachat-jsonrpc/src/api/types/message_parser.rs b/deltachat-jsonrpc/src/api/types/message_parser.rs new file mode 100644 index 000000000..e31a60f38 --- /dev/null +++ b/deltachat-jsonrpc/src/api/types/message_parser.rs @@ -0,0 +1,19 @@ +use deltachat::message_parser::parser::{self, Element}; +use serde::{Deserialize, Serialize}; +use typescript_type_def::TypeDef; + +#[repr(u8)] +#[derive(Serialize, Deserialize, TypeDef, schemars::JsonSchema)] +pub enum MessageParserMode { + OnlyText, + DesktopSet, + Markdown, +} + +pub fn parse_text(input: &str, mode: MessageParserMode) -> std::vec::Vec { + match mode { + MessageParserMode::OnlyText => parser::parse_only_text(input), + MessageParserMode::DesktopSet => parser::parse_desktop_set(input), + MessageParserMode::Markdown => parser::parse_markdown_text(input), + } +} diff --git a/deltachat-jsonrpc/src/api/types/mod.rs b/deltachat-jsonrpc/src/api/types/mod.rs index 8143be73d..1903351fe 100644 --- a/deltachat-jsonrpc/src/api/types/mod.rs +++ b/deltachat-jsonrpc/src/api/types/mod.rs @@ -6,6 +6,7 @@ pub mod events; pub mod http; pub mod location; pub mod message; +pub mod message_parser; pub mod provider_info; pub mod qr; pub mod reactions; diff --git a/src/lib.rs b/src/lib.rs index 057231a8f..4fcdd50b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,6 +110,8 @@ pub mod tools; pub mod accounts; pub mod reaction; +pub use deltachat_message_parser as message_parser; + /// If set IMAP/incoming and SMTP/outgoing MIME messages will be printed. pub const DCC_MIME_DEBUG: &str = "DCC_MIME_DEBUG";