From 1a6dbf29e72e76bdf3912784376a484f61ae1e2d Mon Sep 17 00:00:00 2001 From: FliegendeWurst <2012gdwu+github@posteo.de> Date: Sat, 27 Mar 2021 20:37:49 +0100 Subject: [PATCH] Basic inbox viewer --- Cargo.lock | 7 +++++++ Cargo.toml | 2 ++ src/bin/fetch.rs | 40 ++++++++++++++-------------------------- src/bin/view.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 30 ++++++++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 26 deletions(-) create mode 100644 src/bin/view.rs create mode 100644 src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 0bdf8eb..72f178e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,11 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "ascii_table" +version = "3.0.2" +source = "git+https://gitlab.com/arnekeller/ascii-table.git?rev=7fffb5d93b8c63283fc1359ee3c6dbfcf12ed90b#7fffb5d93b8c63283fc1359ee3c6dbfcf12ed90b" + [[package]] name = "autocfg" version = "1.0.1" @@ -172,9 +177,11 @@ dependencies = [ name = "inboxid" version = "0.1.0" dependencies = [ + "ascii_table", "imap", "itertools", "maildir", + "mailparse", "rustls-connector", ] diff --git a/Cargo.toml b/Cargo.toml index 53bb56f..482d7ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,6 @@ license = "GPL-3.0-or-later" imap = { version = "2.4.1", default-features = false } itertools = "0.10.0" maildir = { path = "../maildir" } +mailparse = "0.13.2" rustls-connector = "0.13.1" +ascii_table = { git = "https://gitlab.com/arnekeller/ascii-table.git", rev = "7fffb5d93b8c63283fc1359ee3c6dbfcf12ed90b" } diff --git a/src/bin/fetch.rs b/src/bin/fetch.rs index 6c37c5f..746b168 100644 --- a/src/bin/fetch.rs +++ b/src/bin/fetch.rs @@ -1,41 +1,29 @@ -use std::{cmp, env, error::Error, fs, io, net::TcpStream, time::Duration}; +use std::{cmp, env, fs, io, time::Duration}; use itertools::Itertools; use maildir::Maildir; -use rustls_connector::RustlsConnector; -fn main() -> Result<(), Box> { +use inboxid::*; + +fn main() -> Result<()> { let host = env::var("MAILHOST").expect("missing envvar MAILHOST"); let user = env::var("MAILUSER").expect("missing envvar MAILUSER"); let password = env::var("MAILPASSWORD").expect("missing envvar MAILPASSWORD"); - let maildir = env::var("MAILDIR").expect("missing envvar MAILDIR"); - let maildir = Maildir::from(maildir); - maildir.create_dirs()?; + let maildir = get_maildir("INBOX")?; let port = 993; - fetch_inbox_top(&host, user, password, port, "INBOX", maildir) + fetch_inbox_top(&host, &user, &password, port, "INBOX", maildir) } fn fetch_inbox_top( host: &str, - user: String, - password: String, + user: &str, + password: &str, port: u16, mailbox: &str, maildir: Maildir, -) -> Result<(), Box> { - println!("connecting.."); - let stream = TcpStream::connect((host, port))?; - let tls = RustlsConnector::new_with_native_certs()?; - println!("initializing TLS.."); - let tlsstream = tls.connect(host, stream)?; - println!("initializing client.."); - let client = imap::Client::new(tlsstream); - - // the client we have here is unauthenticated. - // to do anything useful with the e-mails, we need to log in - println!("logging in.."); - let mut imap_session = client.login(&user, &password).map_err(|e| e.0)?; +) -> Result<()> { + let mut imap_session = connect(host, port, user, password)?; println!("getting capabilities.."); let caps = imap_session.capabilities()?; println!("capabilities: {}", caps.iter().map(|x| format!("{:?}", x)).join(" ")); @@ -95,16 +83,16 @@ fn fetch_inbox_top( } trait MaildirExtension { - fn get_file(&self, name: &str) -> Result; - fn save_file(&self, name: &str, content: &str) -> Result<(), io::Error>; + fn get_file(&self, name: &str) -> std::result::Result; + fn save_file(&self, name: &str, content: &str) -> std::result::Result<(), io::Error>; } impl MaildirExtension for Maildir { - fn get_file(&self, name: &str) -> Result { + fn get_file(&self, name: &str) -> std::result::Result { fs::read_to_string(self.path().join(name)) } - fn save_file(&self, name: &str, content: &str) -> Result<(), io::Error> { + fn save_file(&self, name: &str, content: &str) -> std::result::Result<(), io::Error> { fs::write(self.path().join(name), content) } } diff --git a/src/bin/view.rs b/src/bin/view.rs new file mode 100644 index 0000000..e52e0cd --- /dev/null +++ b/src/bin/view.rs @@ -0,0 +1,48 @@ +use ascii_table::{Align, AsciiTable, Column}; +use mailparse::MailHeaderMap; + +use inboxid::*; + +fn main() -> Result<()> { + show_listing("INBOX") +} + +fn show_listing(mailbox: &str) -> Result<()> { + let maildir = get_maildir(mailbox)?; + + let mut rows = Vec::new(); + for mail in maildir.list_new_sorted(Box::new(|name| { + // sort by UID + name.splitn(2, '_').nth(1).map(|x| x.parse().unwrap_or(0)).unwrap_or(0) + })) { + match mail { + Ok(mut mail) => { + let mail = mail.parsed()?; + let headers = mail.get_headers(); + let from = headers.get_all_values("From").join(" "); + let subj = headers.get_all_values("Subject").join(" "); + rows.push(vec![from, subj]); + } + Err(e) => { + println!("error: {:?}", e); + } + } + } + + let mut ascii_table = AsciiTable::default(); + ascii_table.draw_lines = false; + ascii_table.max_width = usize::MAX; + for (i, &(header, align)) in [ + ("From", Align::Left), + ("Subject", Align::Left), + ].iter().enumerate() { + let mut column = Column::default(); + column.header = header.to_owned(); + column.align = align; + column.max_width = usize::MAX; + ascii_table.columns.insert(i, column); + } + ascii_table.print(rows); + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d8a002b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,30 @@ +use std::{env, net::TcpStream}; + +use imap::Session; +use maildir::Maildir; +use rustls_connector::{RustlsConnector, rustls::{ClientSession, StreamOwned}}; + +pub type Result = std::result::Result>; + +pub fn connect(host: &str, port: u16, user: &str, password: &str) -> Result>> { + println!("connecting.."); + let stream = TcpStream::connect((host, port))?; + let tls = RustlsConnector::new_with_native_certs()?; + println!("initializing TLS.."); + let tlsstream = tls.connect(host, stream)?; + println!("initializing client.."); + let client = imap::Client::new(tlsstream); + + // the client we have here is unauthenticated. + // to do anything useful with the e-mails, we need to log in + println!("logging in.."); + Ok(client.login(user, password).map_err(|e| e.0)?) +} + +pub fn get_maildir(mailbox: &str) -> Result { + let maildir = env::var("MAILDIR").expect("missing envvar MAILDIR"); + let maildir = format!("{}/{}", maildir, mailbox); + let maildir = Maildir::from(maildir); + maildir.create_dirs()?; + Ok(maildir) +}