mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-25 04:20:22 +00:00
add new emails listing feature #6
This commit is contained in:
parent
80fb4bd1d9
commit
2970828db9
6 changed files with 177 additions and 11 deletions
|
@ -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
54
Cargo.lock
generated
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
107
src/imap.rs
107
src/imap.rs
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -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"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue