diff --git a/Cargo.lock b/Cargo.lock index 5b6ad1c..644fe90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,6 +150,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "charset" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f927b07c74ba84c7e5fe4db2baeb3e996ab2688992e39ac68ce3220a677c7e" +dependencies = [ + "base64 0.22.1", + "encoding_rs", +] + [[package]] name = "chrono" version = "0.4.43" @@ -292,6 +302,12 @@ dependencies = [ "syn 2.0.116", ] +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + [[package]] name = "deltae" version = "0.3.2" @@ -354,6 +370,15 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -747,6 +772,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "mailparse" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da03d5980411a724e8aaf7b61a7b5e386ec55a7fb49ee3d0ff79efc7e5e7c7e" +dependencies = [ + "charset", + "data-encoding", + "quoted_printable", +] + [[package]] name = "memchr" version = "2.8.0" @@ -1095,6 +1131,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "quoted_printable" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" + [[package]] name = "r-efi" version = "5.3.0" @@ -1430,6 +1472,7 @@ dependencies = [ "chrono", "crossterm", "imap", + "mailparse", "native-tls", "ratatui", "serde", diff --git a/Cargo.toml b/Cargo.toml index 301342a..361b287 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,5 @@ imap = "2.4" native-tls = "0.2" serde = { version = "1.0", features = ["derive"] } toml = "1.0" -chrono = "0.4" \ No newline at end of file +chrono = "0.4" +mailparse = "0.15" \ No newline at end of file diff --git a/src/inbox.rs b/src/inbox.rs index 7364581..7d95efd 100644 --- a/src/inbox.rs +++ b/src/inbox.rs @@ -152,25 +152,49 @@ pub(crate) fn fetch_body( ) -> Result { let s = ensure_session(session, config)?; let range = seq.to_string(); - let body = match s { + let raw = match s { ImapSession::Plain(s) => { let messages = s - .fetch(&range, "BODY.PEEK[TEXT]") + .fetch(&range, "BODY.PEEK[]") .map_err(|e| e.to_string())?; - extract_body(&messages) + extract_raw_body(&messages) } ImapSession::Tls(s) => { let messages = s - .fetch(&range, "BODY.PEEK[TEXT]") + .fetch(&range, "BODY.PEEK[]") .map_err(|e| e.to_string())?; - extract_body(&messages) + extract_raw_body(&messages) } }; - body.ok_or_else(|| "No body found".to_string()) + let raw = raw.ok_or_else(|| "No body found".to_string())?; + extract_plain_text(&raw) } -fn extract_body(fetches: &[imap::types::Fetch]) -> Option { +fn extract_raw_body(fetches: &[imap::types::Fetch]) -> Option> { fetches.first().and_then(|f| { - f.text().map(|b| String::from_utf8_lossy(b).to_string()) + f.body().map(|b| b.to_vec()) }) } + +fn extract_plain_text(raw: &[u8]) -> Result { + let parsed = mailparse::parse_mail(raw).map_err(|e| e.to_string())?; + // Try to find a text/plain part + if let Some(text) = find_plain_text(&parsed) { + return Ok(text); + } + // Fallback: return the body of the top-level message + parsed.get_body().map_err(|e| e.to_string()) +} + +fn find_plain_text(mail: &mailparse::ParsedMail) -> Option { + let content_type = mail.ctype.mimetype.to_lowercase(); + if content_type == "text/plain" { + return mail.get_body().ok(); + } + for part in &mail.subparts { + if let Some(text) = find_plain_text(part) { + return Some(text); + } + } + None +}