From c8147ded6050204ef7544949224058ab1423a399 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Sun, 26 Nov 2023 20:51:53 -0800 Subject: [PATCH] web & server: add handling for google calendar and wellsfargo emails. --- server/src/graphql.rs | 93 +++++++++++++++++++++++++++------ web/graphql/schema.json | 16 ++++++ web/graphql/show_thread.graphql | 1 + web/index.html | 3 ++ 4 files changed, 98 insertions(+), 15 deletions(-) diff --git a/server/src/graphql.rs b/server/src/graphql.rs index ad4070f..d4cc4a6 100644 --- a/server/src/graphql.rs +++ b/server/src/graphql.rs @@ -8,7 +8,7 @@ use async_graphql::{ Context, EmptyMutation, EmptySubscription, Error, FieldResult, Object, Schema, SimpleObject, Union, }; -use log::{info, warn}; +use log::{error, info, warn}; use mailparse::{parse_mail, MailHeaderMap, ParsedMail}; use memmap::MmapOptions; use notmuch::Notmuch; @@ -58,6 +58,8 @@ pub struct Message { pub timestamp: Option, // The body contents pub body: Body, + // On disk location of message + pub path: String, } #[derive(Debug)] @@ -241,19 +243,7 @@ impl QueryRoot { .headers .get_first_value("date") .and_then(|d| mailparse::dateparse(&d).ok()); - let body = m.get_body()?; - let body = match m.ctype.mimetype.as_str() { - "text/plain" => Body::PlainText(PlainText { text: body }), - "text/html" => Body::Html(Html { html: body }), - _ => { - let msg = format!( - "Unhandled body content type:\n{}", - render_content_type_tree(&m) - ); - warn!("{}", msg); - Body::UnhandledContentType(UnhandledContentType { text: msg }) - } - }; + let body = extract_body(&m)?; messages.push(Message { from, to, @@ -261,6 +251,7 @@ impl QueryRoot { subject, timestamp, body, + path, }); } messages.reverse(); @@ -276,12 +267,84 @@ impl QueryRoot { } } +fn extract_body(m: &ParsedMail) -> Result { + let body = m.get_body()?; + let ret = match m.ctype.mimetype.as_str() { + "text/plain" => return Ok(Body::PlainText(PlainText { text: body })), + "text/html" => return Ok(Body::Html(Html { html: body })), + "multipart/mixed" => extract_mixed(m), + "multipart/alternative" => extract_alternative(m), + _ => extract_unhandled(m), + }; + if let Err(err) = ret { + error!("Failed to extract body: {err:?}"); + return Ok(extract_unhandled(m)?); + } + ret +} + +fn extract_unhandled(m: &ParsedMail) -> Result { + let msg = format!( + "Unhandled body content type:\n{}", + render_content_type_tree(m) + ); + warn!("{}", msg); + Ok(Body::UnhandledContentType(UnhandledContentType { + text: msg, + })) +} +fn extract_alternative(m: &ParsedMail) -> Result { + for sp in &m.subparts { + if sp.ctype.mimetype == "text/html" { + let body = sp.get_body()?; + return Ok(Body::Html(Html { html: body })); + } + } + for sp in &m.subparts { + if sp.ctype.mimetype == "text/plain" { + let body = sp.get_body()?; + return Ok(Body::PlainText(PlainText { text: body })); + } + } + Err("extract_alternative".into()) +} + +fn extract_mixed(m: &ParsedMail) -> Result { + for sp in &m.subparts { + if sp.ctype.mimetype == "multipart/alternative" { + return extract_alternative(sp); + } + } + for sp in &m.subparts { + if sp.ctype.mimetype == "multipart/related" { + return extract_related(sp); + } + } + Err("extract_mixed".into()) +} + +fn extract_related(m: &ParsedMail) -> Result { + // TODO(wathiede): collect related things and change return type to new Body arm. + for sp in &m.subparts { + if sp.ctype.mimetype == "text/html" { + let body = sp.get_body()?; + return Ok(Body::Html(Html { html: body })); + } + } + for sp in &m.subparts { + if sp.ctype.mimetype == "text/plain" { + let body = sp.get_body()?; + return Ok(Body::PlainText(PlainText { text: body })); + } + } + Err("extract_related".into()) +} + fn render_content_type_tree(m: &ParsedMail) -> String { const WIDTH: usize = 4; fn render_rec(m: &ParsedMail, depth: usize) -> String { let mut parts = Vec::new(); let msg = format!("{} {}", "-".repeat(depth * WIDTH), m.ctype.mimetype); - println!("{msg}",); parts.push(msg); if !m.ctype.charset.is_empty() { parts.push(format!( diff --git a/web/graphql/schema.json b/web/graphql/schema.json index ca63315..c60a30c 100644 --- a/web/graphql/schema.json +++ b/web/graphql/schema.json @@ -290,6 +290,22 @@ "ofType": null } } + }, + { + "args": [], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "path", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } } ], "inputFields": null, diff --git a/web/graphql/show_thread.graphql b/web/graphql/show_thread.graphql index 6ce4f30..163bd06 100644 --- a/web/graphql/show_thread.graphql +++ b/web/graphql/show_thread.graphql @@ -28,6 +28,7 @@ query ShowThreadQuery($threadId: String!) { contents } } + path } } tags { diff --git a/web/index.html b/web/index.html index 6e5a53c..b223fc9 100644 --- a/web/index.html +++ b/web/index.html @@ -124,12 +124,15 @@ input::placeholder, .input::placeholder{ float: right; } /* Hide quoted emails */ +/* div[name="quote"], blockquote[type="cite"], .gmail_quote { background-color: red; display: none; } +*/ + .desktop-main-content { display: grid; grid-template-columns: 12rem 1fr;