mirror of
https://github.com/soywod/himalaya.git
synced 2024-07-08 18:45:13 +00:00
start merging email with msg, add list msgs command
This commit is contained in:
parent
a803800d1c
commit
0a508f2e95
30
src/imap.rs
30
src/imap.rs
|
@ -4,7 +4,8 @@ use std::{fmt, net::TcpStream, result};
|
||||||
|
|
||||||
use crate::config;
|
use crate::config;
|
||||||
use crate::email::{self, Email};
|
use crate::email::{self, Email};
|
||||||
use crate::mailbox::Mailbox;
|
use crate::mbox::Mbox;
|
||||||
|
use crate::msg::Msg;
|
||||||
|
|
||||||
// Error wrapper
|
// Error wrapper
|
||||||
|
|
||||||
|
@ -79,17 +80,40 @@ impl<'a> ImapConnector<'a> {
|
||||||
Ok(Self { config, sess })
|
Ok(Self { config, sess })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_mboxes(&mut self) -> Result<Vec<Mailbox<'_>>> {
|
pub fn close(&mut self) {
|
||||||
|
match self.sess.close() {
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_mboxes(&mut self) -> Result<Vec<Mbox<'_>>> {
|
||||||
let mboxes = self
|
let mboxes = self
|
||||||
.sess
|
.sess
|
||||||
.list(Some(""), Some("*"))?
|
.list(Some(""), Some("*"))?
|
||||||
.iter()
|
.iter()
|
||||||
.map(Mailbox::from_name)
|
.map(Mbox::from_name)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
Ok(mboxes)
|
Ok(mboxes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn list_msgs(&mut self, mbox: &str, page_size: &u32, page: &u32) -> Result<Vec<Msg>> {
|
||||||
|
let last_seq = self.sess.select(mbox)?.exists;
|
||||||
|
let begin = last_seq - (page * page_size);
|
||||||
|
let end = begin - (page_size - 1);
|
||||||
|
let range = format!("{}:{}", begin, end);
|
||||||
|
|
||||||
|
let msgs = self
|
||||||
|
.sess
|
||||||
|
.fetch(range, "(UID BODY.PEEK[])")?
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.map(Msg::from)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Ok(msgs)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read_emails(&mut self, mbox: &str, query: &str) -> Result<Vec<Email<'_>>> {
|
pub fn read_emails(&mut self, mbox: &str, query: &str) -> Result<Vec<Email<'_>>> {
|
||||||
self.sess.select(mbox)?;
|
self.sess.select(mbox)?;
|
||||||
|
|
||||||
|
|
173
src/main.rs
173
src/main.rs
|
@ -2,7 +2,7 @@ mod config;
|
||||||
mod email;
|
mod email;
|
||||||
mod imap;
|
mod imap;
|
||||||
mod input;
|
mod input;
|
||||||
mod mailbox;
|
mod mbox;
|
||||||
mod msg;
|
mod msg;
|
||||||
mod smtp;
|
mod smtp;
|
||||||
mod table;
|
mod table;
|
||||||
|
@ -15,6 +15,9 @@ use crate::imap::ImapConnector;
|
||||||
use crate::msg::Msg;
|
use crate::msg::Msg;
|
||||||
use crate::table::DisplayTable;
|
use crate::table::DisplayTable;
|
||||||
|
|
||||||
|
const DEFAULT_PAGE_SIZE: u32 = 10;
|
||||||
|
const DEFAULT_PAGE: u32 = 0;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
ConfigError(config::Error),
|
ConfigError(config::Error),
|
||||||
|
@ -89,14 +92,44 @@ fn uid_arg() -> Arg<'static, 'static> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run() -> Result<()> {
|
fn run() -> Result<()> {
|
||||||
|
let default_page_size = &DEFAULT_PAGE_SIZE.to_string();
|
||||||
|
let default_page = &DEFAULT_PAGE.to_string();
|
||||||
|
|
||||||
let matches = App::new("Himalaya")
|
let matches = App::new("Himalaya")
|
||||||
.version("0.1.0")
|
.version("0.1.0")
|
||||||
.about("📫 Minimalist CLI email client")
|
.about("📫 Minimalist CLI email client")
|
||||||
.author("soywod <clement.douin@posteo.net>")
|
.author("soywod <clement.douin@posteo.net>")
|
||||||
.setting(AppSettings::ArgRequiredElseHelp)
|
.setting(AppSettings::ArgRequiredElseHelp)
|
||||||
.subcommand(SubCommand::with_name("list").about("Lists all available mailboxes"))
|
.subcommand(
|
||||||
|
SubCommand::with_name("mailboxes")
|
||||||
|
.aliases(&["mboxes", "mb", "m"])
|
||||||
|
.about("Lists all available mailboxes"),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("list")
|
||||||
|
.aliases(&["lst", "l"])
|
||||||
|
.about("Lists emails sorted by arrival date")
|
||||||
|
.arg(mailbox_arg())
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("size")
|
||||||
|
.help("Page size")
|
||||||
|
.short("s")
|
||||||
|
.long("size")
|
||||||
|
.value_name("INT")
|
||||||
|
.default_value(default_page_size),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("page")
|
||||||
|
.help("Page number")
|
||||||
|
.short("p")
|
||||||
|
.long("page")
|
||||||
|
.value_name("INT")
|
||||||
|
.default_value(default_page),
|
||||||
|
),
|
||||||
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("search")
|
SubCommand::with_name("search")
|
||||||
|
.aliases(&["query", "q", "s"])
|
||||||
.about("Lists emails matching the given IMAP query")
|
.about("Lists emails matching the given IMAP query")
|
||||||
.arg(mailbox_arg())
|
.arg(mailbox_arg())
|
||||||
.arg(
|
.arg(
|
||||||
|
@ -109,6 +142,7 @@ fn run() -> Result<()> {
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("read")
|
SubCommand::with_name("read")
|
||||||
|
.aliases(&["r"])
|
||||||
.about("Reads text bodies of an email")
|
.about("Reads text bodies of an email")
|
||||||
.arg(uid_arg())
|
.arg(uid_arg())
|
||||||
.arg(mailbox_arg())
|
.arg(mailbox_arg())
|
||||||
|
@ -124,6 +158,7 @@ fn run() -> Result<()> {
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("attachments")
|
SubCommand::with_name("attachments")
|
||||||
|
.aliases(&["attach", "a"])
|
||||||
.about("Downloads all attachments from an email")
|
.about("Downloads all attachments from an email")
|
||||||
.arg(uid_arg())
|
.arg(uid_arg())
|
||||||
.arg(mailbox_arg()),
|
.arg(mailbox_arg()),
|
||||||
|
@ -131,6 +166,7 @@ fn run() -> Result<()> {
|
||||||
.subcommand(SubCommand::with_name("write").about("Writes a new email"))
|
.subcommand(SubCommand::with_name("write").about("Writes a new email"))
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("reply")
|
SubCommand::with_name("reply")
|
||||||
|
.aliases(&["rep", "re"])
|
||||||
.about("Answers to an email")
|
.about("Answers to an email")
|
||||||
.arg(uid_arg())
|
.arg(uid_arg())
|
||||||
.arg(mailbox_arg())
|
.arg(mailbox_arg())
|
||||||
|
@ -143,94 +179,127 @@ fn run() -> Result<()> {
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("forward")
|
SubCommand::with_name("forward")
|
||||||
|
.aliases(&["fwd", "f"])
|
||||||
.about("Forwards an email")
|
.about("Forwards an email")
|
||||||
.arg(uid_arg())
|
.arg(uid_arg())
|
||||||
.arg(mailbox_arg()),
|
.arg(mailbox_arg()),
|
||||||
)
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("list") {
|
if let Some(_) = matches.subcommand_matches("mailboxes") {
|
||||||
let config = Config::new_from_file()?;
|
let config = Config::new_from_file()?;
|
||||||
let mboxes = ImapConnector::new(&config.imap)?.list_mboxes()?.to_table();
|
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||||
|
|
||||||
println!("{}", mboxes);
|
let mboxes = imap_conn.list_mboxes()?;
|
||||||
|
println!("{}", mboxes.to_table());
|
||||||
|
|
||||||
|
imap_conn.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(matches) = matches.subcommand_matches("list") {
|
||||||
|
let config = Config::new_from_file()?;
|
||||||
|
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||||
|
|
||||||
|
let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
let page_size: u32 = matches
|
||||||
|
.value_of("size")
|
||||||
|
.unwrap()
|
||||||
|
.parse()
|
||||||
|
.unwrap_or(DEFAULT_PAGE_SIZE);
|
||||||
|
let page: u32 = matches
|
||||||
|
.value_of("page")
|
||||||
|
.unwrap()
|
||||||
|
.parse()
|
||||||
|
.unwrap_or(DEFAULT_PAGE);
|
||||||
|
|
||||||
|
let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?;
|
||||||
|
println!("{}", msgs.to_table());
|
||||||
|
|
||||||
|
imap_conn.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("search") {
|
if let Some(matches) = matches.subcommand_matches("search") {
|
||||||
let config = Config::new_from_file()?;
|
let config = Config::new_from_file()?;
|
||||||
|
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||||
|
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
let query = matches
|
||||||
if let Some(matches) = matches.values_of("query") {
|
.values_of("query")
|
||||||
let query = matches
|
.unwrap_or_default()
|
||||||
.fold((false, vec![]), |(escape, mut cmds), cmd| {
|
.fold((false, vec![]), |(escape, mut cmds), cmd| {
|
||||||
match (cmd, escape) {
|
match (cmd, escape) {
|
||||||
// Next command is an arg and needs to be escaped
|
// Next command is an arg and needs to be escaped
|
||||||
("subject", _) | ("body", _) | ("text", _) => {
|
("subject", _) | ("body", _) | ("text", _) => {
|
||||||
cmds.push(cmd.to_string());
|
cmds.push(cmd.to_string());
|
||||||
(true, cmds)
|
(true, cmds)
|
||||||
}
|
|
||||||
// Escaped arg commands
|
|
||||||
(_, true) => {
|
|
||||||
cmds.push(format!("\"{}\"", cmd));
|
|
||||||
(false, cmds)
|
|
||||||
}
|
|
||||||
// Regular commands
|
|
||||||
(_, false) => {
|
|
||||||
cmds.push(cmd.to_string());
|
|
||||||
(false, cmds)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
// Escaped arg commands
|
||||||
.1
|
(_, true) => {
|
||||||
.join(" ");
|
cmds.push(format!("\"{}\"", cmd));
|
||||||
|
(false, cmds)
|
||||||
|
}
|
||||||
|
// Regular commands
|
||||||
|
(_, false) => {
|
||||||
|
cmds.push(cmd.to_string());
|
||||||
|
(false, cmds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.1
|
||||||
|
.join(" ");
|
||||||
|
|
||||||
let emails = ImapConnector::new(&config.imap)?
|
let msgs = imap_conn.read_emails(&mbox, &query)?;
|
||||||
.read_emails(&mbox, &query)?
|
println!("{}", msgs.to_table());
|
||||||
.to_table();
|
|
||||||
|
|
||||||
println!("{}", emails);
|
imap_conn.close();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("read") {
|
if let Some(matches) = matches.subcommand_matches("read") {
|
||||||
let config = Config::new_from_file()?;
|
let config = Config::new_from_file()?;
|
||||||
|
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||||
|
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
let mbox = matches.value_of("mailbox").unwrap();
|
||||||
let uid = matches.value_of("uid").unwrap();
|
let uid = matches.value_of("uid").unwrap();
|
||||||
let mime = format!("text/{}", matches.value_of("mime-type").unwrap());
|
let mime = format!("text/{}", matches.value_of("mime-type").unwrap());
|
||||||
let body = ImapConnector::new(&config.imap)?.read_email_body(&mbox, &uid, &mime)?;
|
|
||||||
|
|
||||||
|
let body = imap_conn.read_email_body(&mbox, &uid, &mime)?;
|
||||||
println!("{}", body);
|
println!("{}", body);
|
||||||
|
|
||||||
|
imap_conn.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("attachments") {
|
if let Some(matches) = matches.subcommand_matches("attachments") {
|
||||||
let config = Config::new_from_file()?;
|
let config = Config::new_from_file()?;
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
|
||||||
let uid = matches.value_of("uid").unwrap();
|
|
||||||
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||||
|
|
||||||
let msg = imap_conn.read_msg(&mbox, &uid)?;
|
let mbox = matches.value_of("mailbox").unwrap();
|
||||||
let msg = Msg::from(&msg)?;
|
let uid = matches.value_of("uid").unwrap();
|
||||||
|
|
||||||
|
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?.as_slice());
|
||||||
let parts = msg.extract_parts()?;
|
let parts = msg.extract_parts()?;
|
||||||
|
|
||||||
if parts.is_empty() {
|
if parts.is_empty() {
|
||||||
println!("No attachment found for message {}", uid);
|
println!("No attachment found for message {}", uid);
|
||||||
} else {
|
} else {
|
||||||
println!("{} attachment(s) found for message {}", parts.len(), uid);
|
println!("{} attachment(s) found for message {}", parts.len(), uid);
|
||||||
msg.extract_parts()?.iter().for_each(|(filename, bytes)| {
|
parts.iter().for_each(|(filename, bytes)| {
|
||||||
let filepath = config.downloads_filepath(&filename);
|
let filepath = config.downloads_filepath(&filename);
|
||||||
println!("Downloading {} …", filename);
|
println!("Downloading {} …", filename);
|
||||||
fs::write(filepath, bytes).unwrap()
|
fs::write(filepath, bytes).unwrap()
|
||||||
});
|
});
|
||||||
println!("Done!");
|
println!("Done!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
imap_conn.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("write") {
|
if let Some(_) = matches.subcommand_matches("write") {
|
||||||
let config = Config::new_from_file()?;
|
let config = Config::new_from_file()?;
|
||||||
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||||
|
|
||||||
let tpl = Msg::build_new_tpl(&config)?;
|
let tpl = Msg::build_new_tpl(&config)?;
|
||||||
let content = input::open_editor_with_tpl(&tpl.as_bytes())?;
|
let content = input::open_editor_with_tpl(&tpl.as_bytes())?;
|
||||||
let msg = Msg::from(content.as_bytes())?;
|
let msg = Msg::from(content.as_bytes());
|
||||||
|
|
||||||
input::ask_for_confirmation("Send the message?")?;
|
input::ask_for_confirmation("Send the message?")?;
|
||||||
|
|
||||||
|
@ -238,17 +307,18 @@ fn run() -> Result<()> {
|
||||||
smtp::send(&config.smtp, &msg.to_sendable_msg()?)?;
|
smtp::send(&config.smtp, &msg.to_sendable_msg()?)?;
|
||||||
imap_conn.append_msg("Sent", &msg.to_vec()?)?;
|
imap_conn.append_msg("Sent", &msg.to_vec()?)?;
|
||||||
println!("Done!");
|
println!("Done!");
|
||||||
|
|
||||||
|
imap_conn.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("reply") {
|
if let Some(matches) = matches.subcommand_matches("reply") {
|
||||||
let config = Config::new_from_file()?;
|
let config = Config::new_from_file()?;
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
|
||||||
let uid = matches.value_of("uid").unwrap();
|
|
||||||
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||||
|
|
||||||
let msg = imap_conn.read_msg(&mbox, &uid)?;
|
let mbox = matches.value_of("mailbox").unwrap();
|
||||||
let msg = Msg::from(&msg)?;
|
let uid = matches.value_of("uid").unwrap();
|
||||||
|
|
||||||
|
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?.as_slice());
|
||||||
let tpl = if matches.is_present("reply-all") {
|
let tpl = if matches.is_present("reply-all") {
|
||||||
msg.build_reply_all_tpl(&config)?
|
msg.build_reply_all_tpl(&config)?
|
||||||
} else {
|
} else {
|
||||||
|
@ -256,7 +326,7 @@ fn run() -> Result<()> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let content = input::open_editor_with_tpl(&tpl.as_bytes())?;
|
let content = input::open_editor_with_tpl(&tpl.as_bytes())?;
|
||||||
let msg = Msg::from(content.as_bytes())?;
|
let msg = Msg::from(content.as_bytes());
|
||||||
|
|
||||||
input::ask_for_confirmation("Send the message?")?;
|
input::ask_for_confirmation("Send the message?")?;
|
||||||
|
|
||||||
|
@ -264,20 +334,21 @@ fn run() -> Result<()> {
|
||||||
smtp::send(&config.smtp, &msg.to_sendable_msg()?)?;
|
smtp::send(&config.smtp, &msg.to_sendable_msg()?)?;
|
||||||
imap_conn.append_msg("Sent", &msg.to_vec()?)?;
|
imap_conn.append_msg("Sent", &msg.to_vec()?)?;
|
||||||
println!("Done!");
|
println!("Done!");
|
||||||
|
|
||||||
|
imap_conn.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("forward") {
|
if let Some(matches) = matches.subcommand_matches("forward") {
|
||||||
let config = Config::new_from_file()?;
|
let config = Config::new_from_file()?;
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
|
||||||
let uid = matches.value_of("uid").unwrap();
|
|
||||||
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||||
|
|
||||||
let msg = imap_conn.read_msg(&mbox, &uid)?;
|
let mbox = matches.value_of("mailbox").unwrap();
|
||||||
let msg = Msg::from(&msg)?;
|
let uid = matches.value_of("uid").unwrap();
|
||||||
|
|
||||||
|
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?.as_slice());
|
||||||
let tpl = msg.build_forward_tpl(&config)?;
|
let tpl = msg.build_forward_tpl(&config)?;
|
||||||
let content = input::open_editor_with_tpl(&tpl.as_bytes())?;
|
let content = input::open_editor_with_tpl(&tpl.as_bytes())?;
|
||||||
let msg = Msg::from(content.as_bytes())?;
|
let msg = Msg::from(content.as_bytes());
|
||||||
|
|
||||||
input::ask_for_confirmation("Send the message?")?;
|
input::ask_for_confirmation("Send the message?")?;
|
||||||
|
|
||||||
|
@ -285,6 +356,8 @@ fn run() -> Result<()> {
|
||||||
smtp::send(&config.smtp, &msg.to_sendable_msg()?)?;
|
smtp::send(&config.smtp, &msg.to_sendable_msg()?)?;
|
||||||
imap_conn.append_msg("Sent", &msg.to_vec()?)?;
|
imap_conn.append_msg("Sent", &msg.to_vec()?)?;
|
||||||
println!("Done!");
|
println!("Done!");
|
||||||
|
|
||||||
|
imap_conn.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -83,13 +83,13 @@ impl DisplayCell for Attributes<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Mailbox<'a> {
|
pub struct Mbox<'a> {
|
||||||
pub delim: Delim,
|
pub delim: Delim,
|
||||||
pub name: Name,
|
pub name: Name,
|
||||||
pub attributes: Attributes<'a>,
|
pub attributes: Attributes<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mailbox<'_> {
|
impl Mbox<'_> {
|
||||||
pub fn from_name(name: &imap::types::Name) -> Self {
|
pub fn from_name(name: &imap::types::Name) -> Self {
|
||||||
Self {
|
Self {
|
||||||
delim: Delim::from_name(name),
|
delim: Delim::from_name(name),
|
||||||
|
@ -99,7 +99,7 @@ impl Mailbox<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DisplayRow for Mailbox<'a> {
|
impl<'a> DisplayRow for Mbox<'a> {
|
||||||
fn to_row(&self) -> Vec<table::Cell> {
|
fn to_row(&self) -> Vec<table::Cell> {
|
||||||
vec![
|
vec![
|
||||||
self.delim.to_cell(),
|
self.delim.to_cell(),
|
||||||
|
@ -109,12 +109,12 @@ impl<'a> DisplayRow for Mailbox<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DisplayTable<'a, Mailbox<'a>> for Vec<Mailbox<'a>> {
|
impl<'a> DisplayTable<'a, Mbox<'a>> for Vec<Mbox<'a>> {
|
||||||
fn cols() -> &'a [&'a str] {
|
fn cols() -> &'a [&'a str] {
|
||||||
&["delim", "name", "attributes"]
|
&["delim", "name", "attributes"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rows(&self) -> &Vec<Mailbox<'a>> {
|
fn rows(&self) -> &Vec<Mbox<'a>> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
104
src/msg.rs
104
src/msg.rs
|
@ -1,7 +1,8 @@
|
||||||
use lettre;
|
use lettre;
|
||||||
use mailparse::{self, MailHeaderMap};
|
use mailparse::{self, MailHeaderMap};
|
||||||
use std::{fmt, ops, result};
|
use std::{fmt, result};
|
||||||
|
|
||||||
|
use crate::table::{self, DisplayRow, DisplayTable};
|
||||||
use crate::Config;
|
use crate::Config;
|
||||||
|
|
||||||
// Error wrapper
|
// Error wrapper
|
||||||
|
@ -9,8 +10,7 @@ use crate::Config;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
ParseMsgError(mailparse::MailParseError),
|
ParseMsgError(mailparse::MailParseError),
|
||||||
BuildEmailError(lettre::error::Error),
|
BuildSendableMsgError(lettre::error::Error),
|
||||||
TryError,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
|
@ -18,8 +18,7 @@ impl fmt::Display for Error {
|
||||||
write!(f, "(msg): ")?;
|
write!(f, "(msg): ")?;
|
||||||
match self {
|
match self {
|
||||||
Error::ParseMsgError(err) => err.fmt(f),
|
Error::ParseMsgError(err) => err.fmt(f),
|
||||||
Error::BuildEmailError(err) => err.fmt(f),
|
Error::BuildSendableMsgError(err) => err.fmt(f),
|
||||||
Error::TryError => write!(f, "cannot parse"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +31,7 @@ impl From<mailparse::MailParseError> for Error {
|
||||||
|
|
||||||
impl From<lettre::error::Error> for Error {
|
impl From<lettre::error::Error> for Error {
|
||||||
fn from(err: lettre::error::Error) -> Error {
|
fn from(err: lettre::error::Error) -> Error {
|
||||||
Error::BuildEmailError(err)
|
Error::BuildSendableMsgError(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,28 +39,45 @@ impl From<lettre::error::Error> for Error {
|
||||||
|
|
||||||
type Result<T> = result::Result<T, Error>;
|
type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
// Wrapper around mailparse::ParsedMail and lettre::Message
|
// Msg
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Msg<'a>(mailparse::ParsedMail<'a>);
|
pub struct Msg {
|
||||||
|
pub uid: u32,
|
||||||
|
pub flags: Vec<String>,
|
||||||
|
raw: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> ops::Deref for Msg<'a> {
|
impl From<&[u8]> for Msg {
|
||||||
type Target = mailparse::ParsedMail<'a>;
|
fn from(item: &[u8]) -> Self {
|
||||||
|
Self {
|
||||||
fn deref(&self) -> &Self::Target {
|
uid: 0,
|
||||||
&self.0
|
flags: vec![],
|
||||||
|
raw: item.to_vec(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Msg<'a> {
|
impl From<&imap::types::Fetch> for Msg {
|
||||||
pub fn from(bytes: &'a [u8]) -> Result<Self> {
|
fn from(fetch: &imap::types::Fetch) -> Self {
|
||||||
Ok(Self(mailparse::parse_mail(bytes)?))
|
Self {
|
||||||
|
uid: fetch.uid.unwrap_or_default(),
|
||||||
|
flags: vec![],
|
||||||
|
raw: fetch.body().unwrap_or_default().to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Msg {
|
||||||
|
pub fn parse(&'a self) -> Result<mailparse::ParsedMail<'a>> {
|
||||||
|
Ok(mailparse::parse_mail(&self.raw)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_vec(&self) -> Result<Vec<u8>> {
|
pub fn to_vec(&self) -> Result<Vec<u8>> {
|
||||||
let headers = self.0.get_headers().get_raw_bytes().to_vec();
|
let parsed = self.parse()?;
|
||||||
|
let headers = parsed.get_headers().get_raw_bytes().to_vec();
|
||||||
let sep = "\r\n".as_bytes().to_vec();
|
let sep = "\r\n".as_bytes().to_vec();
|
||||||
let body = self.0.get_body()?.as_bytes().to_vec();
|
let body = parsed.get_body()?.as_bytes().to_vec();
|
||||||
|
|
||||||
Ok(vec![headers, sep, body].concat())
|
Ok(vec![headers, sep, body].concat())
|
||||||
}
|
}
|
||||||
|
@ -70,8 +86,8 @@ impl<'a> Msg<'a> {
|
||||||
use lettre::message::header::{ContentTransferEncoding, ContentType};
|
use lettre::message::header::{ContentTransferEncoding, ContentType};
|
||||||
use lettre::message::{Message, SinglePart};
|
use lettre::message::{Message, SinglePart};
|
||||||
|
|
||||||
let msg = self
|
let parsed = self.parse()?;
|
||||||
.0
|
let msg = parsed
|
||||||
.headers
|
.headers
|
||||||
.iter()
|
.iter()
|
||||||
.fold(Message::builder(), |msg, h| {
|
.fold(Message::builder(), |msg, h| {
|
||||||
|
@ -111,7 +127,7 @@ impl<'a> Msg<'a> {
|
||||||
SinglePart::builder()
|
SinglePart::builder()
|
||||||
.header(ContentType("text/plain; charset=utf-8".parse().unwrap()))
|
.header(ContentType("text/plain; charset=utf-8".parse().unwrap()))
|
||||||
.header(ContentTransferEncoding::Base64)
|
.header(ContentTransferEncoding::Base64)
|
||||||
.body(self.0.get_body_raw()?),
|
.body(parsed.get_body_raw()?),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(msg)
|
Ok(msg)
|
||||||
|
@ -147,7 +163,7 @@ impl<'a> Msg<'a> {
|
||||||
|
|
||||||
pub fn extract_parts(&self) -> Result<Vec<(String, Vec<u8>)>> {
|
pub fn extract_parts(&self) -> Result<Vec<(String, Vec<u8>)>> {
|
||||||
let mut parts = vec![];
|
let mut parts = vec![];
|
||||||
Self::extract_parts_into(&self.0, &mut parts);
|
Self::extract_parts_into(&self.parse()?, &mut parts);
|
||||||
Ok(parts)
|
Ok(parts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,7 +183,7 @@ impl<'a> Msg<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_reply_tpl(&self, config: &Config) -> Result<String> {
|
pub fn build_reply_tpl(&self, config: &Config) -> Result<String> {
|
||||||
let msg = &self.0;
|
let msg = &self.parse()?;
|
||||||
let headers = msg.get_headers();
|
let headers = msg.get_headers();
|
||||||
let mut tpl = vec![];
|
let mut tpl = vec![];
|
||||||
|
|
||||||
|
@ -207,7 +223,7 @@ impl<'a> Msg<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_reply_all_tpl(&self, config: &Config) -> Result<String> {
|
pub fn build_reply_all_tpl(&self, config: &Config) -> Result<String> {
|
||||||
let msg = &self.0;
|
let msg = &self.parse()?;
|
||||||
let headers = msg.get_headers();
|
let headers = msg.get_headers();
|
||||||
let mut tpl = vec![];
|
let mut tpl = vec![];
|
||||||
|
|
||||||
|
@ -289,7 +305,7 @@ impl<'a> Msg<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_forward_tpl(&self, config: &Config) -> Result<String> {
|
pub fn build_forward_tpl(&self, config: &Config) -> Result<String> {
|
||||||
let msg = &self.0;
|
let msg = &self.parse()?;
|
||||||
let headers = msg.get_headers();
|
let headers = msg.get_headers();
|
||||||
let mut tpl = vec![];
|
let mut tpl = vec![];
|
||||||
|
|
||||||
|
@ -313,3 +329,41 @@ impl<'a> Msg<'a> {
|
||||||
Ok(tpl.join("\r\n"))
|
Ok(tpl.join("\r\n"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DisplayRow for Msg {
|
||||||
|
fn to_row(&self) -> Vec<table::Cell> {
|
||||||
|
match self.parse() {
|
||||||
|
Err(_) => vec![],
|
||||||
|
Ok(parsed) => {
|
||||||
|
let headers = parsed.get_headers();
|
||||||
|
|
||||||
|
let uid = &self.uid.to_string();
|
||||||
|
let flags = String::new(); // TODO: render flags
|
||||||
|
let sender = headers
|
||||||
|
.get_first_value("reply-to")
|
||||||
|
.or(headers.get_first_value("from"))
|
||||||
|
.unwrap_or_default();
|
||||||
|
let subject = headers.get_first_value("subject").unwrap_or_default();
|
||||||
|
let date = headers.get_first_value("date").unwrap_or_default();
|
||||||
|
|
||||||
|
vec![
|
||||||
|
table::Cell::new(&[table::RED], &uid),
|
||||||
|
table::Cell::new(&[table::WHITE], &flags),
|
||||||
|
table::Cell::new(&[table::BLUE], &sender),
|
||||||
|
table::Cell::new(&[table::GREEN], &subject),
|
||||||
|
table::Cell::new(&[table::YELLOW], &date),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DisplayTable<'a, Msg> for Vec<Msg> {
|
||||||
|
fn cols() -> &'a [&'a str] {
|
||||||
|
&["uid", "flags", "sender", "subject", "date"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rows(&self) -> &Vec<Msg> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -78,7 +78,7 @@ pub trait DisplayRow {
|
||||||
fn to_row(&self) -> Vec<Cell>;
|
fn to_row(&self) -> Vec<Cell>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait DisplayTable<'a, T: DisplayRow> {
|
pub trait DisplayTable<'a, T: DisplayRow + 'a> {
|
||||||
fn cols() -> &'a [&'a str];
|
fn cols() -> &'a [&'a str];
|
||||||
fn rows(&self) -> &Vec<T>;
|
fn rows(&self) -> &Vec<T>;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue