add new emails listing feature #6

This commit is contained in:
Clément DOUIN 2021-01-02 18:26:03 +01:00
parent 80fb4bd1d9
commit 2970828db9
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
6 changed files with 177 additions and 11 deletions

View file

@ -11,8 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Parse TOML config [#1] - Parse TOML config [#1]
- Populate Config struct from TOML [#2] - Populate Config struct from TOML [#2]
- Set up IMAP connection [#3]
- List new emails [#6]
[unreleased]: https://github.com/soywod/himalaya [unreleased]: https://github.com/soywod/himalaya
[#1]: https://github.com/soywod/himalaya/issues/1 [#1]: https://github.com/soywod/himalaya/issues/1
[#2]: https://github.com/soywod/himalaya/issues/2 [#2]: https://github.com/soywod/himalaya/issues/2
[#3]: https://github.com/soywod/himalaya/issues/3

54
Cargo.lock generated
View file

@ -21,6 +21,15 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "base64"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
dependencies = [
"byteorder",
]
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.13.0" version = "0.13.0"
@ -39,6 +48,12 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8"
[[package]]
name = "byteorder"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.66" version = "1.0.66"
@ -57,6 +72,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "charset"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f426e64df1c3de26cbf44593c6ffff5dbfd43bbf9de0d075058558126b3fc73"
dependencies = [
"base64 0.10.1",
"encoding_rs",
]
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.19" version = "0.4.19"
@ -86,6 +111,15 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
[[package]]
name = "encoding_rs"
version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "801bbab217d7f79c0062f4f7205b5d4427c6d1a7bd7aafdd1475f7c59d62b283"
dependencies = [
"cfg-if 1.0.0",
]
[[package]] [[package]]
name = "foreign-types" name = "foreign-types"
version = "0.3.2" version = "0.3.2"
@ -118,6 +152,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"imap", "imap",
"native-tls", "native-tls",
"rfc2047-decoder",
"serde", "serde",
"toml", "toml",
] ]
@ -128,7 +163,7 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b45e5e8d7783a68f2a0e2451bd19446412202fe21c24d34f4b282a510b91ede3" checksum = "b45e5e8d7783a68f2a0e2451bd19446412202fe21c24d34f4b282a510b91ede3"
dependencies = [ dependencies = [
"base64", "base64 0.13.0",
"bufstream", "bufstream",
"chrono", "chrono",
"imap-proto", "imap-proto",
@ -298,6 +333,12 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "quoted_printable"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b080c5db639b292ac79cbd34be0cfc5d36694768d8341109634d90b86930e2"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.7.3" version = "0.7.3"
@ -372,6 +413,17 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "rfc2047-decoder"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87ecf2ba387f446155e26796aabb727e9ae1427dd13ac9cc21773a3fbda19d77"
dependencies = [
"base64 0.13.0",
"charset",
"quoted_printable",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.5" version = "1.0.5"

View file

@ -5,10 +5,9 @@ version = "0.1.0"
authors = ["soywod <clement.douin@posteo.net>"] authors = ["soywod <clement.douin@posteo.net>"]
edition = "2018" edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
imap = "2.4.0" imap = "2.4.0"
native-tls = "0.2" native-tls = "0.2"
rfc2047-decoder = "0.1.2"
serde = { version = "1.0.118", features = ["derive"] } serde = { version = "1.0.118", features = ["derive"] }
toml = "0.5.8" toml = "0.5.8"

View file

@ -15,19 +15,19 @@ pub struct ServerInfo {
impl ServerInfo { impl ServerInfo {
pub fn get_host(&self) -> &str { pub fn get_host(&self) -> &str {
&self.host[..] &self.host
} }
pub fn get_addr(&self) -> (&str, u16) { pub fn get_addr(&self) -> (&str, u16) {
(&self.host[..], self.port) (&self.host, self.port)
} }
pub fn get_login(&self) -> &str { pub fn get_login(&self) -> &str {
&self.login[..] &self.login
} }
pub fn get_password(&self) -> &str { pub fn get_password(&self) -> &str {
&self.password[..] &self.password
} }
} }

View file

@ -1,8 +1,10 @@
use imap; use imap;
use native_tls::{TlsConnector, TlsStream}; use native_tls::{TlsConnector, TlsStream};
use rfc2047_decoder;
use std::net::TcpStream; use std::net::TcpStream;
use crate::config::{Config, ServerInfo}; use crate::config::{Config, ServerInfo};
use crate::table;
type ImapClient = imap::Client<TlsStream<TcpStream>>; type ImapClient = imap::Client<TlsStream<TcpStream>>;
type ImapSession = imap::Session<TlsStream<TcpStream>>; type ImapSession = imap::Session<TlsStream<TcpStream>>;
@ -40,6 +42,107 @@ pub fn create_imap_sess(client: ImapClient, server: &ServerInfo) -> ImapSession
pub fn login(config: &Config) -> ImapSession { pub fn login(config: &Config) -> ImapSession {
let tls = create_tls_connector(); let tls = create_tls_connector();
let client = create_imap_client(&config.imap, &tls); let client = create_imap_client(&config.imap, &tls);
let sess = create_imap_sess(client, &config.imap); let imap_sess = create_imap_sess(client, &config.imap);
sess imap_sess
}
fn subject_from_fetch(fetch: &imap::types::Fetch) -> String {
let envelope = fetch.envelope().expect("envelope is missing");
match &envelope.subject {
None => String::new(),
Some(bytes) => match rfc2047_decoder::decode(bytes) {
Err(_) => String::new(),
Ok(subject) => subject,
},
}
}
fn first_addr_from_fetch(fetch: &imap::types::Fetch) -> String {
let envelope = fetch.envelope().expect("envelope is missing");
match &envelope.from {
None => String::new(),
Some(addresses) => match addresses.first() {
None => String::new(),
Some(address) => {
let mbox = String::from_utf8(address.mailbox.expect("invalid addr mbox").to_vec())
.expect("invalid addr mbox");
let host = String::from_utf8(address.host.expect("invalid addr host").to_vec())
.expect("invalid addr host");
let email = format!("{}@{}", mbox, host);
match address.name {
None => email,
Some(name) => match rfc2047_decoder::decode(name) {
Err(_) => email,
Ok(name) => name,
},
}
}
},
}
}
fn date_from_fetch(fetch: &imap::types::Fetch) -> String {
let envelope = fetch.envelope().expect("envelope is missing");
match &envelope.date {
None => String::new(),
Some(date) => match String::from_utf8(date.to_vec()) {
Err(_) => String::new(),
Ok(date) => date,
},
}
}
pub fn read_new_emails(imap_sess: &mut ImapSession, mbox: &str) -> imap::Result<()> {
imap_sess.select(mbox)?;
// let mboxes = imap_sess.list(Some(""), Some("*"))?;
// println!("Mboxes {:?}", mboxes);
let seqs = imap_sess
.search("NOT SEEN")?
.iter()
.map(|n| n.to_string())
.collect::<Vec<_>>()
.join(",");
let table_head = vec![
table::Cell::new(
vec![table::BOLD, table::UNDERLINE, table::WHITE],
String::from("FLAGS"),
),
table::Cell::new(
vec![table::BOLD, table::UNDERLINE, table::WHITE],
String::from("FROM"),
),
table::Cell::new(
vec![table::BOLD, table::UNDERLINE, table::WHITE],
String::from("SUBJECT"),
),
table::Cell::new(
vec![table::BOLD, table::UNDERLINE, table::WHITE],
String::from("DATE"),
),
];
let mut table_rows = imap_sess
.fetch(seqs, "(INTERNALDATE ENVELOPE)")?
.iter()
.map(|fetch| {
vec![
table::Cell::new(vec![table::WHITE], String::from("!@")),
table::Cell::new(vec![table::BLUE], first_addr_from_fetch(fetch)),
table::Cell::new(vec![table::GREEN], subject_from_fetch(fetch)),
table::Cell::new(vec![table::YELLOW], date_from_fetch(fetch)),
]
})
.collect::<Vec<_>>();
table_rows.insert(0, table_head);
println!("{}", table::render(table_rows));
Ok(())
} }

View file

@ -1,8 +1,17 @@
mod config; mod config;
mod imap; mod imap;
mod table;
use std::env;
fn main() { fn main() {
let mbox = env::args().nth(1).unwrap_or(String::from("inbox"));
let args = env::args().skip(2).collect::<Vec<_>>().join(" ").to_owned();
let config = config::read_file(); let config = config::read_file();
let sess = imap::login(&config); let mut imap_sess = imap::login(&config);
println!("{:?}", sess);
match args.as_str() {
"read new" => imap::read_new_emails(&mut imap_sess, &mbox).unwrap(),
_ => println!("Himalaya: command not found e"),
}
} }