Basic inbox viewer

This commit is contained in:
FliegendeWurst 2021-03-27 20:37:49 +01:00 committed by Arne Keller
parent ffda7e2179
commit 1a6dbf29e7
5 changed files with 101 additions and 26 deletions

7
Cargo.lock generated
View File

@ -17,6 +17,11 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "ascii_table"
version = "3.0.2"
source = "git+https://gitlab.com/arnekeller/ascii-table.git?rev=7fffb5d93b8c63283fc1359ee3c6dbfcf12ed90b#7fffb5d93b8c63283fc1359ee3c6dbfcf12ed90b"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.0.1" version = "1.0.1"
@ -172,9 +177,11 @@ dependencies = [
name = "inboxid" name = "inboxid"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ascii_table",
"imap", "imap",
"itertools", "itertools",
"maildir", "maildir",
"mailparse",
"rustls-connector", "rustls-connector",
] ]

View File

@ -11,4 +11,6 @@ license = "GPL-3.0-or-later"
imap = { version = "2.4.1", default-features = false } imap = { version = "2.4.1", default-features = false }
itertools = "0.10.0" itertools = "0.10.0"
maildir = { path = "../maildir" } maildir = { path = "../maildir" }
mailparse = "0.13.2"
rustls-connector = "0.13.1" rustls-connector = "0.13.1"
ascii_table = { git = "https://gitlab.com/arnekeller/ascii-table.git", rev = "7fffb5d93b8c63283fc1359ee3c6dbfcf12ed90b" }

View File

@ -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 itertools::Itertools;
use maildir::Maildir; use maildir::Maildir;
use rustls_connector::RustlsConnector;
fn main() -> Result<(), Box<dyn Error>> { use inboxid::*;
fn main() -> Result<()> {
let host = env::var("MAILHOST").expect("missing envvar MAILHOST"); let host = env::var("MAILHOST").expect("missing envvar MAILHOST");
let user = env::var("MAILUSER").expect("missing envvar MAILUSER"); let user = env::var("MAILUSER").expect("missing envvar MAILUSER");
let password = env::var("MAILPASSWORD").expect("missing envvar MAILPASSWORD"); let password = env::var("MAILPASSWORD").expect("missing envvar MAILPASSWORD");
let maildir = env::var("MAILDIR").expect("missing envvar MAILDIR"); let maildir = get_maildir("INBOX")?;
let maildir = Maildir::from(maildir);
maildir.create_dirs()?;
let port = 993; 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( fn fetch_inbox_top(
host: &str, host: &str,
user: String, user: &str,
password: String, password: &str,
port: u16, port: u16,
mailbox: &str, mailbox: &str,
maildir: Maildir, maildir: Maildir,
) -> Result<(), Box<dyn Error>> { ) -> Result<()> {
println!("connecting.."); let mut imap_session = connect(host, port, user, password)?;
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)?;
println!("getting capabilities.."); println!("getting capabilities..");
let caps = imap_session.capabilities()?; let caps = imap_session.capabilities()?;
println!("capabilities: {}", caps.iter().map(|x| format!("{:?}", x)).join(" ")); println!("capabilities: {}", caps.iter().map(|x| format!("{:?}", x)).join(" "));
@ -95,16 +83,16 @@ fn fetch_inbox_top(
} }
trait MaildirExtension { trait MaildirExtension {
fn get_file(&self, name: &str) -> Result<String, io::Error>; fn get_file(&self, name: &str) -> std::result::Result<String, io::Error>;
fn save_file(&self, name: &str, content: &str) -> Result<(), io::Error>; fn save_file(&self, name: &str, content: &str) -> std::result::Result<(), io::Error>;
} }
impl MaildirExtension for Maildir { impl MaildirExtension for Maildir {
fn get_file(&self, name: &str) -> Result<String, io::Error> { fn get_file(&self, name: &str) -> std::result::Result<String, io::Error> {
fs::read_to_string(self.path().join(name)) 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) fs::write(self.path().join(name), content)
} }
} }

48
src/bin/view.rs Normal file
View File

@ -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(())
}

30
src/lib.rs Normal file
View File

@ -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<T> = std::result::Result<T, Box<dyn std::error::Error>>;
pub fn connect(host: &str, port: u16, user: &str, password: &str) -> Result<Session<StreamOwned<ClientSession, TcpStream>>> {
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<Maildir> {
let maildir = env::var("MAILDIR").expect("missing envvar MAILDIR");
let maildir = format!("{}/{}", maildir, mailbox);
let maildir = Maildir::from(maildir);
maildir.create_dirs()?;
Ok(maildir)
}