mirror of
https://github.com/soywod/himalaya.git
synced 2024-07-08 18:45:13 +00:00
extract mbox and msg logics
This commit is contained in:
parent
e108729491
commit
a0296a54d2
796
src/app.rs
796
src/app.rs
|
@ -3,545 +3,401 @@ use error_chain::error_chain;
|
||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{self, Config},
|
config::Config,
|
||||||
flag::cli::{flags_matches, flags_subcommand},
|
flag::cli::{flag_matches, flag_subcmds},
|
||||||
imap::{self, ImapConnector},
|
imap::ImapConnector,
|
||||||
input,
|
input,
|
||||||
msg::{self, Attachments, Msg, Msgs, ReadableMsg},
|
mbox::cli::{mbox_arg, mbox_matches, mbox_subcmds},
|
||||||
|
msg::cli::{msg_args, msg_matches, msg_subcmds},
|
||||||
output::{self, print},
|
output::{self, print},
|
||||||
smtp,
|
smtp,
|
||||||
};
|
};
|
||||||
|
|
||||||
error_chain! {
|
error_chain! {
|
||||||
links {
|
links {
|
||||||
Config(config::Error, config::ErrorKind);
|
Config(crate::config::Error, crate::config::ErrorKind);
|
||||||
Imap(imap::Error, imap::ErrorKind);
|
MboxCli(crate::mbox::cli::Error, crate::mbox::cli::ErrorKind);
|
||||||
Input(input::Error, input::ErrorKind);
|
MsgCli(crate::msg::cli::Error, crate::msg::cli::ErrorKind);
|
||||||
Message(msg::Error, msg::ErrorKind);
|
FlagCli(crate::flag::cli::Error, crate::flag::cli::ErrorKind);
|
||||||
Output(output::Error, output::ErrorKind);
|
|
||||||
Smtp(smtp::Error, smtp::ErrorKind);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct App<'a>(pub clap::App<'a, 'a>);
|
pub struct App<'a>(pub clap::App<'a, 'a>);
|
||||||
|
|
||||||
impl<'a> App<'a> {
|
impl<'a> App<'a> {
|
||||||
fn mailbox_arg() -> Arg<'a, 'a> {
|
// fn mailbox_arg() -> Arg<'a, 'a> {
|
||||||
Arg::with_name("mailbox")
|
// Arg::with_name("mailbox")
|
||||||
.short("m")
|
// .short("m")
|
||||||
.long("mailbox")
|
// .long("mailbox")
|
||||||
.help("Mailbox name")
|
// .help("Mailbox name")
|
||||||
.value_name("STRING")
|
// .value_name("STRING")
|
||||||
.default_value("INBOX")
|
// .default_value("INBOX")
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn uid_arg() -> Arg<'a, 'a> {
|
// fn uid_arg() -> Arg<'a, 'a> {
|
||||||
Arg::with_name("uid")
|
// Arg::with_name("uid")
|
||||||
.help("Message UID")
|
// .help("Message UID")
|
||||||
.value_name("UID")
|
// .value_name("UID")
|
||||||
.required(true)
|
// .required(true)
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn reply_all_arg() -> Arg<'a, 'a> {
|
// fn reply_all_arg() -> Arg<'a, 'a> {
|
||||||
Arg::with_name("reply-all")
|
// Arg::with_name("reply-all")
|
||||||
.help("Includes all recipients")
|
// .help("Includes all recipients")
|
||||||
.short("a")
|
// .short("a")
|
||||||
.long("all")
|
// .long("all")
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn page_size_arg() -> Arg<'a, 'a> {
|
// fn page_size_arg() -> Arg<'a, 'a> {
|
||||||
Arg::with_name("size")
|
// Arg::with_name("size")
|
||||||
.help("Page size")
|
// .help("Page size")
|
||||||
.short("s")
|
// .short("s")
|
||||||
.long("size")
|
// .long("size")
|
||||||
.value_name("INT")
|
// .value_name("INT")
|
||||||
.default_value("10")
|
// .default_value("10")
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn page_arg() -> Arg<'a, 'a> {
|
// fn page_arg() -> Arg<'a, 'a> {
|
||||||
Arg::with_name("page")
|
// Arg::with_name("page")
|
||||||
.help("Page number")
|
// .help("Page number")
|
||||||
.short("p")
|
// .short("p")
|
||||||
.long("page")
|
// .long("page")
|
||||||
.value_name("INT")
|
// .value_name("INT")
|
||||||
.default_value("0")
|
// .default_value("0")
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self(clap::App::new(env!("CARGO_PKG_NAME"))
|
let app = clap::App::new(env!("CARGO_PKG_NAME"))
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
.about(env!("CARGO_PKG_DESCRIPTION"))
|
.about(env!("CARGO_PKG_DESCRIPTION"))
|
||||||
.author(env!("CARGO_PKG_AUTHORS"))
|
.author(env!("CARGO_PKG_AUTHORS"))
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("output")
|
Arg::with_name("output")
|
||||||
.long("output")
|
.long("output")
|
||||||
.short("o")
|
.short("o")
|
||||||
.help("Formats the output")
|
.help("Defines the output format")
|
||||||
.value_name("STRING")
|
.value_name("STRING")
|
||||||
.possible_values(&["text", "json"])
|
.possible_values(&["plain", "json"])
|
||||||
.default_value("text"),
|
.default_value("plain"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("account")
|
Arg::with_name("account")
|
||||||
.long("account")
|
.long("account")
|
||||||
.short("a")
|
.short("a")
|
||||||
.help("Selects a specific account")
|
.help("Selects a specific account")
|
||||||
.value_name("STRING"),
|
.value_name("STRING"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(mbox_arg());
|
||||||
Arg::with_name("mailbox")
|
|
||||||
.short("m")
|
let app = app.subcommands(mbox_subcmds());
|
||||||
.long("mailbox")
|
let app = app.subcommands(msg_subcmds());
|
||||||
.help("Selects a specific mailbox")
|
let app = app.subcommands(flag_subcmds());
|
||||||
.value_name("STRING")
|
let app = app.args(&msg_args());
|
||||||
.default_value("INBOX")
|
|
||||||
)
|
Self(app)
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("mailboxes")
|
|
||||||
.aliases(&["mailbox", "mboxes", "mbox", "mb", "m"])
|
|
||||||
.about("Lists all available mailboxes"),
|
|
||||||
)
|
|
||||||
.subcommand(flags_subcommand())
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("list")
|
|
||||||
.aliases(&["lst", "l"])
|
|
||||||
.about("Lists emails sorted by arrival date")
|
|
||||||
.arg(Self::mailbox_arg())
|
|
||||||
.arg(Self::page_size_arg())
|
|
||||||
.arg(Self::page_arg()),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("search")
|
|
||||||
.aliases(&["query", "q", "s"])
|
|
||||||
.about("Lists emails matching the given IMAP query")
|
|
||||||
.arg(Self::mailbox_arg())
|
|
||||||
.arg(Self::page_size_arg())
|
|
||||||
.arg(Self::page_arg())
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("query")
|
|
||||||
.help("IMAP query (see https://tools.ietf.org/html/rfc3501#section-6.4.4)")
|
|
||||||
.value_name("QUERY")
|
|
||||||
.multiple(true)
|
|
||||||
.required(true),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("read")
|
|
||||||
.aliases(&["r"])
|
|
||||||
.about("Reads text bodies of an email")
|
|
||||||
.arg(Self::uid_arg())
|
|
||||||
.arg(Self::mailbox_arg())
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("mime-type")
|
|
||||||
.help("MIME type to use")
|
|
||||||
.short("t")
|
|
||||||
.long("mime-type")
|
|
||||||
.value_name("STRING")
|
|
||||||
.possible_values(&["plain", "html"])
|
|
||||||
.default_value("plain"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("attachments")
|
|
||||||
.aliases(&["attach", "att", "a"])
|
|
||||||
.about("Downloads all attachments from an email")
|
|
||||||
.arg(Self::uid_arg())
|
|
||||||
.arg(Self::mailbox_arg()),
|
|
||||||
)
|
|
||||||
.subcommand(SubCommand::with_name("write").about("Writes a new email"))
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("reply")
|
|
||||||
.aliases(&["rep", "re"])
|
|
||||||
.about("Answers to an email")
|
|
||||||
.arg(Self::uid_arg())
|
|
||||||
.arg(Self::mailbox_arg())
|
|
||||||
.arg(Self::reply_all_arg()),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("forward")
|
|
||||||
.aliases(&["fwd", "f"])
|
|
||||||
.about("Forwards an email")
|
|
||||||
.arg(Self::uid_arg())
|
|
||||||
.arg(Self::mailbox_arg()),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("send")
|
|
||||||
.about("Sends a raw message")
|
|
||||||
.arg(Arg::with_name("message").raw(true)),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("save")
|
|
||||||
.about("Saves a raw message in the given mailbox")
|
|
||||||
.arg(Self::mailbox_arg())
|
|
||||||
.arg(Arg::with_name("message").raw(true)),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("template")
|
|
||||||
.aliases(&["tpl", "t"])
|
|
||||||
.about("Generates a message template")
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("new")
|
|
||||||
.aliases(&["n"])
|
|
||||||
.about("Generates a new message template")
|
|
||||||
.arg(Self::mailbox_arg()),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("reply")
|
|
||||||
.aliases(&["rep", "r"])
|
|
||||||
.about("Generates a reply message template")
|
|
||||||
.arg(Self::uid_arg())
|
|
||||||
.arg(Self::mailbox_arg())
|
|
||||||
.arg(Self::reply_all_arg()),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("forward")
|
|
||||||
.aliases(&["fwd", "fw", "f"])
|
|
||||||
.about("Generates a forward message template")
|
|
||||||
.arg(Self::uid_arg())
|
|
||||||
.arg(Self::mailbox_arg()),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
SubCommand::with_name("idle")
|
|
||||||
.about("Starts the idle mode")
|
|
||||||
.arg(Self::mailbox_arg()),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(self) -> Result<()> {
|
pub fn run(self) -> Result<()> {
|
||||||
let matches = self.0.get_matches();
|
let matches = self.0.get_matches();
|
||||||
|
|
||||||
let output_type = matches.value_of("output").unwrap().to_owned();
|
let matched = mbox_matches(&matches)?;
|
||||||
let account = matches.value_of("account");
|
let matched = flag_matches(matched, &matches)?;
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
let matched = msg_matches(matched, &matches)?;
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("mailboxes") {
|
// if let Some(matches) = matches.subcommand_matches("search") {
|
||||||
let config = Config::new_from_file()?;
|
// let config = Config::new_from_file()?;
|
||||||
let account = config.find_account_by_name(account)?;
|
// let account = config.find_account_by_name(account)?;
|
||||||
let mut imap_conn = ImapConnector::new(&account)?;
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
// let page_size: usize = matches.value_of("size").unwrap().parse().unwrap();
|
||||||
|
// let page: usize = matches.value_of("page").unwrap().parse().unwrap();
|
||||||
|
// let query = matches
|
||||||
|
// .values_of("query")
|
||||||
|
// .unwrap_or_default()
|
||||||
|
// .fold((false, vec![]), |(escape, mut cmds), cmd| {
|
||||||
|
// match (cmd, escape) {
|
||||||
|
// // Next command is an arg and needs to be escaped
|
||||||
|
// ("subject", _) | ("body", _) | ("text", _) => {
|
||||||
|
// cmds.push(cmd.to_string());
|
||||||
|
// (true, cmds)
|
||||||
|
// }
|
||||||
|
// // Escaped arg commands
|
||||||
|
// (_, true) => {
|
||||||
|
// cmds.push(format!("\"{}\"", cmd));
|
||||||
|
// (false, cmds)
|
||||||
|
// }
|
||||||
|
// // Regular commands
|
||||||
|
// (_, false) => {
|
||||||
|
// cmds.push(cmd.to_string());
|
||||||
|
// (false, cmds)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .1
|
||||||
|
// .join(" ");
|
||||||
|
|
||||||
let mboxes = imap_conn.list_mboxes()?;
|
// let msgs = imap_conn.search_msgs(&mbox, &query, &page_size, &page)?;
|
||||||
print(&output_type, mboxes)?;
|
// let msgs = Msgs::from(&msgs);
|
||||||
|
|
||||||
imap_conn.logout();
|
// print(&output_type, msgs)?;
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("flags") {
|
// imap_conn.logout();
|
||||||
flags_matches(account, &mbox, &matches)
|
// }
|
||||||
.chain_err(|| "Could not handle flags arg matches")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("list") {
|
// if let Some(matches) = matches.subcommand_matches("read") {
|
||||||
let config = Config::new_from_file()?;
|
// let config = Config::new_from_file()?;
|
||||||
let account = config.find_account_by_name(account)?;
|
// let account = config.find_account_by_name(account)?;
|
||||||
let mut imap_conn = ImapConnector::new(&account)?;
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
let page_size: u32 = matches.value_of("size").unwrap().parse().unwrap();
|
// let uid = matches.value_of("uid").unwrap();
|
||||||
let page: u32 = matches.value_of("page").unwrap().parse().unwrap();
|
// let mime = format!("text/{}", matches.value_of("mime-type").unwrap());
|
||||||
|
|
||||||
let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?;
|
// let msg = imap_conn.read_msg(&mbox, &uid)?;
|
||||||
let msgs = Msgs::from(&msgs);
|
// let msg = ReadableMsg::from_bytes(&mime, &msg)?;
|
||||||
|
|
||||||
print(&output_type, msgs)?;
|
// print(&output_type, msg)?;
|
||||||
|
// imap_conn.logout();
|
||||||
|
// }
|
||||||
|
|
||||||
imap_conn.logout();
|
// if let Some(matches) = matches.subcommand_matches("attachments") {
|
||||||
}
|
// let config = Config::new_from_file()?;
|
||||||
|
// let account = config.find_account_by_name(account)?;
|
||||||
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("search") {
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
let config = Config::new_from_file()?;
|
// let uid = matches.value_of("uid").unwrap();
|
||||||
let account = config.find_account_by_name(account)?;
|
|
||||||
let mut imap_conn = ImapConnector::new(&account)?;
|
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
|
||||||
let page_size: usize = matches.value_of("size").unwrap().parse().unwrap();
|
|
||||||
let page: usize = matches.value_of("page").unwrap().parse().unwrap();
|
|
||||||
let query = matches
|
|
||||||
.values_of("query")
|
|
||||||
.unwrap_or_default()
|
|
||||||
.fold((false, vec![]), |(escape, mut cmds), cmd| {
|
|
||||||
match (cmd, escape) {
|
|
||||||
// Next command is an arg and needs to be escaped
|
|
||||||
("subject", _) | ("body", _) | ("text", _) => {
|
|
||||||
cmds.push(cmd.to_string());
|
|
||||||
(true, cmds)
|
|
||||||
}
|
|
||||||
// Escaped arg commands
|
|
||||||
(_, true) => {
|
|
||||||
cmds.push(format!("\"{}\"", cmd));
|
|
||||||
(false, cmds)
|
|
||||||
}
|
|
||||||
// Regular commands
|
|
||||||
(_, false) => {
|
|
||||||
cmds.push(cmd.to_string());
|
|
||||||
(false, cmds)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.1
|
|
||||||
.join(" ");
|
|
||||||
|
|
||||||
let msgs = imap_conn.search_msgs(&mbox, &query, &page_size, &page)?;
|
// let msg = imap_conn.read_msg(&mbox, &uid)?;
|
||||||
let msgs = Msgs::from(&msgs);
|
// let attachments = Attachments::from_bytes(&msg)?;
|
||||||
|
|
||||||
print(&output_type, msgs)?;
|
// match output_type.as_str() {
|
||||||
|
// "text" => {
|
||||||
|
// println!(
|
||||||
|
// "{} attachment(s) found for message {}",
|
||||||
|
// attachments.0.len(),
|
||||||
|
// uid
|
||||||
|
// );
|
||||||
|
|
||||||
imap_conn.logout();
|
// attachments.0.iter().for_each(|attachment| {
|
||||||
}
|
// let filepath = config.downloads_filepath(&account, &attachment.filename);
|
||||||
|
// println!("Downloading {}…", &attachment.filename);
|
||||||
|
// fs::write(filepath, &attachment.raw).unwrap()
|
||||||
|
// });
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("read") {
|
// println!("Done!");
|
||||||
let config = Config::new_from_file()?;
|
// }
|
||||||
let account = config.find_account_by_name(account)?;
|
// "json" => {
|
||||||
let mut imap_conn = ImapConnector::new(&account)?;
|
// attachments.0.iter().for_each(|attachment| {
|
||||||
|
// let filepath = config.downloads_filepath(&account, &attachment.filename);
|
||||||
|
// fs::write(filepath, &attachment.raw).unwrap()
|
||||||
|
// });
|
||||||
|
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
// print!("{{}}");
|
||||||
let uid = matches.value_of("uid").unwrap();
|
// }
|
||||||
let mime = format!("text/{}", matches.value_of("mime-type").unwrap());
|
// _ => (),
|
||||||
|
// }
|
||||||
|
|
||||||
let msg = imap_conn.read_msg(&mbox, &uid)?;
|
// imap_conn.logout();
|
||||||
let msg = ReadableMsg::from_bytes(&mime, &msg)?;
|
// }
|
||||||
|
|
||||||
print(&output_type, msg)?;
|
// if let Some(_) = matches.subcommand_matches("write") {
|
||||||
imap_conn.logout();
|
// let config = Config::new_from_file()?;
|
||||||
}
|
// let account = config.find_account_by_name(account)?;
|
||||||
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
// let tpl = Msg::build_new_tpl(&config, &account)?;
|
||||||
|
// let content = input::open_editor_with_tpl(tpl.to_string().as_bytes())?;
|
||||||
|
// let mut msg = Msg::from(content);
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("attachments") {
|
// loop {
|
||||||
let config = Config::new_from_file()?;
|
// match input::post_edit_choice() {
|
||||||
let account = config.find_account_by_name(account)?;
|
// Ok(choice) => match choice {
|
||||||
let mut imap_conn = ImapConnector::new(&account)?;
|
// input::Choice::Send => {
|
||||||
|
// println!("Sending…");
|
||||||
|
// let msg = msg.to_sendable_msg()?;
|
||||||
|
// smtp::send(&account, &msg)?;
|
||||||
|
// imap_conn.append_msg("Sent", &msg.formatted())?;
|
||||||
|
// println!("Done!");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// input::Choice::Draft => {
|
||||||
|
// println!("Saving to draft…");
|
||||||
|
// imap_conn.append_msg("Drafts", &msg.to_vec()?)?;
|
||||||
|
// println!("Done!");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// input::Choice::Edit => {
|
||||||
|
// let content = input::open_editor_with_draft()?;
|
||||||
|
// msg = Msg::from(content);
|
||||||
|
// }
|
||||||
|
// input::Choice::Quit => break,
|
||||||
|
// },
|
||||||
|
// Err(err) => eprintln!("{}", err),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
// imap_conn.logout();
|
||||||
let uid = matches.value_of("uid").unwrap();
|
// }
|
||||||
|
|
||||||
let msg = imap_conn.read_msg(&mbox, &uid)?;
|
// if let Some(matches) = matches.subcommand_matches("template") {
|
||||||
let attachments = Attachments::from_bytes(&msg)?;
|
// let config = Config::new_from_file()?;
|
||||||
|
// let account = config.find_account_by_name(account)?;
|
||||||
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
|
||||||
match output_type.as_str() {
|
// if let Some(_) = matches.subcommand_matches("new") {
|
||||||
"text" => {
|
// let tpl = Msg::build_new_tpl(&config, &account)?;
|
||||||
println!(
|
// print(&output_type, &tpl)?;
|
||||||
"{} attachment(s) found for message {}",
|
// }
|
||||||
attachments.0.len(),
|
|
||||||
uid
|
|
||||||
);
|
|
||||||
|
|
||||||
attachments.0.iter().for_each(|attachment| {
|
// if let Some(matches) = matches.subcommand_matches("reply") {
|
||||||
let filepath = config.downloads_filepath(&account, &attachment.filename);
|
// let uid = matches.value_of("uid").unwrap();
|
||||||
println!("Downloading {}…", &attachment.filename);
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
fs::write(filepath, &attachment.raw).unwrap()
|
|
||||||
});
|
|
||||||
|
|
||||||
println!("Done!");
|
// let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
|
||||||
}
|
// let tpl = if matches.is_present("reply-all") {
|
||||||
"json" => {
|
// msg.build_reply_all_tpl(&config, &account)?
|
||||||
attachments.0.iter().for_each(|attachment| {
|
// } else {
|
||||||
let filepath = config.downloads_filepath(&account, &attachment.filename);
|
// msg.build_reply_tpl(&config, &account)?
|
||||||
fs::write(filepath, &attachment.raw).unwrap()
|
// };
|
||||||
});
|
|
||||||
|
|
||||||
print!("{{}}");
|
// print(&output_type, &tpl)?;
|
||||||
}
|
// }
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
imap_conn.logout();
|
// if let Some(matches) = matches.subcommand_matches("forward") {
|
||||||
}
|
// let uid = matches.value_of("uid").unwrap();
|
||||||
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("write") {
|
// let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
|
||||||
let config = Config::new_from_file()?;
|
// let tpl = msg.build_forward_tpl(&config, &account)?;
|
||||||
let account = config.find_account_by_name(account)?;
|
|
||||||
let mut imap_conn = ImapConnector::new(&account)?;
|
|
||||||
let tpl = Msg::build_new_tpl(&config, &account)?;
|
|
||||||
let content = input::open_editor_with_tpl(tpl.to_string().as_bytes())?;
|
|
||||||
let mut msg = Msg::from(content);
|
|
||||||
|
|
||||||
loop {
|
// print(&output_type, &tpl)?;
|
||||||
match input::post_edit_choice() {
|
// }
|
||||||
Ok(choice) => match choice {
|
// }
|
||||||
input::Choice::Send => {
|
|
||||||
println!("Sending…");
|
|
||||||
let msg = msg.to_sendable_msg()?;
|
|
||||||
smtp::send(&account, &msg)?;
|
|
||||||
imap_conn.append_msg("Sent", &msg.formatted())?;
|
|
||||||
println!("Done!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
input::Choice::Draft => {
|
|
||||||
println!("Saving to draft…");
|
|
||||||
imap_conn.append_msg("Drafts", &msg.to_vec()?)?;
|
|
||||||
println!("Done!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
input::Choice::Edit => {
|
|
||||||
let content = input::open_editor_with_draft()?;
|
|
||||||
msg = Msg::from(content);
|
|
||||||
}
|
|
||||||
input::Choice::Quit => break,
|
|
||||||
},
|
|
||||||
Err(err) => eprintln!("{}", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
imap_conn.logout();
|
// if let Some(matches) = matches.subcommand_matches("reply") {
|
||||||
}
|
// let config = Config::new_from_file()?;
|
||||||
|
// let account = config.find_account_by_name(account)?;
|
||||||
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("template") {
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
let config = Config::new_from_file()?;
|
// let uid = matches.value_of("uid").unwrap();
|
||||||
let account = config.find_account_by_name(account)?;
|
|
||||||
let mut imap_conn = ImapConnector::new(&account)?;
|
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("new") {
|
// let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
|
||||||
let tpl = Msg::build_new_tpl(&config, &account)?;
|
// let tpl = if matches.is_present("reply-all") {
|
||||||
print(&output_type, &tpl)?;
|
// msg.build_reply_all_tpl(&config, &account)?
|
||||||
}
|
// } else {
|
||||||
|
// msg.build_reply_tpl(&config, &account)?
|
||||||
|
// };
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("reply") {
|
// let content = input::open_editor_with_tpl(&tpl.to_string().as_bytes())?;
|
||||||
let uid = matches.value_of("uid").unwrap();
|
// let mut msg = Msg::from(content);
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
|
||||||
|
|
||||||
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
|
// loop {
|
||||||
let tpl = if matches.is_present("reply-all") {
|
// match input::post_edit_choice() {
|
||||||
msg.build_reply_all_tpl(&config, &account)?
|
// Ok(choice) => match choice {
|
||||||
} else {
|
// input::Choice::Send => {
|
||||||
msg.build_reply_tpl(&config, &account)?
|
// println!("Sending…");
|
||||||
};
|
// smtp::send(&account, &msg.to_sendable_msg()?)?;
|
||||||
|
// imap_conn.append_msg("Sent", &msg.to_vec()?)?;
|
||||||
|
// imap_conn.add_flags(mbox, uid, "\\Answered")?;
|
||||||
|
// println!("Done!");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// input::Choice::Draft => {
|
||||||
|
// println!("Saving to draft…");
|
||||||
|
// imap_conn.append_msg("Drafts", &msg.to_vec()?)?;
|
||||||
|
// println!("Done!");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// input::Choice::Edit => {
|
||||||
|
// let content = input::open_editor_with_draft()?;
|
||||||
|
// msg = Msg::from(content);
|
||||||
|
// }
|
||||||
|
// input::Choice::Quit => break,
|
||||||
|
// },
|
||||||
|
// Err(err) => eprintln!("{}", err),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
print(&output_type, &tpl)?;
|
// imap_conn.logout();
|
||||||
}
|
// }
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("forward") {
|
// if let Some(matches) = matches.subcommand_matches("forward") {
|
||||||
let uid = matches.value_of("uid").unwrap();
|
// let config = Config::new_from_file()?;
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
// let account = config.find_account_by_name(account)?;
|
||||||
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
|
||||||
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
let tpl = msg.build_forward_tpl(&config, &account)?;
|
// let uid = matches.value_of("uid").unwrap();
|
||||||
|
|
||||||
print(&output_type, &tpl)?;
|
// let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
|
||||||
}
|
// let tpl = msg.build_forward_tpl(&config, &account)?;
|
||||||
}
|
// let content = input::open_editor_with_tpl(&tpl.to_string().as_bytes())?;
|
||||||
|
// let mut msg = Msg::from(content);
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("reply") {
|
// loop {
|
||||||
let config = Config::new_from_file()?;
|
// match input::post_edit_choice() {
|
||||||
let account = config.find_account_by_name(account)?;
|
// Ok(choice) => match choice {
|
||||||
let mut imap_conn = ImapConnector::new(&account)?;
|
// input::Choice::Send => {
|
||||||
|
// println!("Sending…");
|
||||||
|
// smtp::send(&account, &msg.to_sendable_msg()?)?;
|
||||||
|
// imap_conn.append_msg("Sent", &msg.to_vec()?)?;
|
||||||
|
// println!("Done!");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// input::Choice::Draft => {
|
||||||
|
// println!("Saving to draft…");
|
||||||
|
// imap_conn.append_msg("Drafts", &msg.to_vec()?)?;
|
||||||
|
// println!("Done!");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// input::Choice::Edit => {
|
||||||
|
// let content = input::open_editor_with_draft()?;
|
||||||
|
// msg = Msg::from(content);
|
||||||
|
// }
|
||||||
|
// input::Choice::Quit => break,
|
||||||
|
// },
|
||||||
|
// Err(err) => eprintln!("{}", err),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
// imap_conn.logout();
|
||||||
let uid = matches.value_of("uid").unwrap();
|
// }
|
||||||
|
|
||||||
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
|
// if let Some(matches) = matches.subcommand_matches("send") {
|
||||||
let tpl = if matches.is_present("reply-all") {
|
// let config = Config::new_from_file()?;
|
||||||
msg.build_reply_all_tpl(&config, &account)?
|
// let account = config.find_account_by_name(account)?;
|
||||||
} else {
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
msg.build_reply_tpl(&config, &account)?
|
|
||||||
};
|
|
||||||
|
|
||||||
let content = input::open_editor_with_tpl(&tpl.to_string().as_bytes())?;
|
// let msg = matches.value_of("message").unwrap();
|
||||||
let mut msg = Msg::from(content);
|
// let msg = Msg::from(msg.to_string());
|
||||||
|
// let msg = msg.to_sendable_msg()?;
|
||||||
|
|
||||||
loop {
|
// smtp::send(&account, &msg)?;
|
||||||
match input::post_edit_choice() {
|
// imap_conn.append_msg("Sent", &msg.formatted())?;
|
||||||
Ok(choice) => match choice {
|
// imap_conn.logout();
|
||||||
input::Choice::Send => {
|
// }
|
||||||
println!("Sending…");
|
|
||||||
smtp::send(&account, &msg.to_sendable_msg()?)?;
|
|
||||||
imap_conn.append_msg("Sent", &msg.to_vec()?)?;
|
|
||||||
imap_conn.add_flags(mbox, uid, "\\Answered")?;
|
|
||||||
println!("Done!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
input::Choice::Draft => {
|
|
||||||
println!("Saving to draft…");
|
|
||||||
imap_conn.append_msg("Drafts", &msg.to_vec()?)?;
|
|
||||||
println!("Done!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
input::Choice::Edit => {
|
|
||||||
let content = input::open_editor_with_draft()?;
|
|
||||||
msg = Msg::from(content);
|
|
||||||
}
|
|
||||||
input::Choice::Quit => break,
|
|
||||||
},
|
|
||||||
Err(err) => eprintln!("{}", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
imap_conn.logout();
|
// if let Some(matches) = matches.subcommand_matches("save") {
|
||||||
}
|
// let config = Config::new_from_file()?;
|
||||||
|
// let account = config.find_account_by_name(account)?;
|
||||||
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("forward") {
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
let config = Config::new_from_file()?;
|
// let msg = matches.value_of("message").unwrap();
|
||||||
let account = config.find_account_by_name(account)?;
|
// let msg = Msg::from(msg.to_string());
|
||||||
let mut imap_conn = ImapConnector::new(&account)?;
|
|
||||||
|
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
// imap_conn.append_msg(mbox, &msg.to_vec()?)?;
|
||||||
let uid = matches.value_of("uid").unwrap();
|
// imap_conn.logout();
|
||||||
|
// }
|
||||||
|
|
||||||
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
|
// if let Some(matches) = matches.subcommand_matches("idle") {
|
||||||
let tpl = msg.build_forward_tpl(&config, &account)?;
|
// let config = Config::new_from_file()?;
|
||||||
let content = input::open_editor_with_tpl(&tpl.to_string().as_bytes())?;
|
// let account = config.find_account_by_name(account)?;
|
||||||
let mut msg = Msg::from(content);
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
loop {
|
// imap_conn.idle(&config, &mbox)?;
|
||||||
match input::post_edit_choice() {
|
// }
|
||||||
Ok(choice) => match choice {
|
|
||||||
input::Choice::Send => {
|
|
||||||
println!("Sending…");
|
|
||||||
smtp::send(&account, &msg.to_sendable_msg()?)?;
|
|
||||||
imap_conn.append_msg("Sent", &msg.to_vec()?)?;
|
|
||||||
println!("Done!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
input::Choice::Draft => {
|
|
||||||
println!("Saving to draft…");
|
|
||||||
imap_conn.append_msg("Drafts", &msg.to_vec()?)?;
|
|
||||||
println!("Done!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
input::Choice::Edit => {
|
|
||||||
let content = input::open_editor_with_draft()?;
|
|
||||||
msg = Msg::from(content);
|
|
||||||
}
|
|
||||||
input::Choice::Quit => break,
|
|
||||||
},
|
|
||||||
Err(err) => eprintln!("{}", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
imap_conn.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("send") {
|
|
||||||
let config = Config::new_from_file()?;
|
|
||||||
let account = config.find_account_by_name(account)?;
|
|
||||||
let mut imap_conn = ImapConnector::new(&account)?;
|
|
||||||
|
|
||||||
let msg = matches.value_of("message").unwrap();
|
|
||||||
let msg = Msg::from(msg.to_string());
|
|
||||||
let msg = msg.to_sendable_msg()?;
|
|
||||||
|
|
||||||
smtp::send(&account, &msg)?;
|
|
||||||
imap_conn.append_msg("Sent", &msg.formatted())?;
|
|
||||||
imap_conn.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("save") {
|
|
||||||
let config = Config::new_from_file()?;
|
|
||||||
let account = config.find_account_by_name(account)?;
|
|
||||||
let mut imap_conn = ImapConnector::new(&account)?;
|
|
||||||
|
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
|
||||||
let msg = matches.value_of("message").unwrap();
|
|
||||||
let msg = Msg::from(msg.to_string());
|
|
||||||
|
|
||||||
imap_conn.append_msg(mbox, &msg.to_vec()?)?;
|
|
||||||
imap_conn.logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("idle") {
|
|
||||||
let config = Config::new_from_file()?;
|
|
||||||
let account = config.find_account_by_name(account)?;
|
|
||||||
let mut imap_conn = ImapConnector::new(&account)?;
|
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
|
||||||
imap_conn.idle(&config, &mbox)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,15 +23,15 @@ fn uid_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||||
fn flags_arg<'a, 'b>() -> Arg<'a, 'b> {
|
fn flags_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||||
Arg::with_name("flags")
|
Arg::with_name("flags")
|
||||||
.help("IMAP flags (see https://tools.ietf.org/html/rfc3501#page-11)")
|
.help("IMAP flags (see https://tools.ietf.org/html/rfc3501#page-11)")
|
||||||
.value_name("FLAGS")
|
.value_name("FLAGS…")
|
||||||
.multiple(true)
|
.multiple(true)
|
||||||
.required(true)
|
.required(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flags_subcommand<'a, 'b>() -> App<'a, 'b> {
|
pub fn flag_subcmds<'a>() -> Vec<App<'a, 'a>> {
|
||||||
SubCommand::with_name("flags")
|
vec![SubCommand::with_name("flags")
|
||||||
.aliases(&["flag", "fg"])
|
.aliases(&["flag"])
|
||||||
.about("Manages flags")
|
.about("Handles flags")
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("set")
|
SubCommand::with_name("set")
|
||||||
.aliases(&["s"])
|
.aliases(&["s"])
|
||||||
|
@ -52,31 +52,40 @@ pub fn flags_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||||
.about("Removes flags from a message")
|
.about("Removes flags from a message")
|
||||||
.arg(uid_arg())
|
.arg(uid_arg())
|
||||||
.arg(flags_arg()),
|
.arg(flags_arg()),
|
||||||
)
|
)]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flags_matches(account: Option<&str>, mbox: &str, matches: &ArgMatches) -> Result<()> {
|
pub fn flag_matches(matched: bool, matches: &ArgMatches) -> Result<bool> {
|
||||||
|
if matched {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
let config = Config::new_from_file()?;
|
let config = Config::new_from_file()?;
|
||||||
let account = config.find_account_by_name(account)?;
|
let account = config.find_account_by_name(matches.value_of("account"))?;
|
||||||
|
let output_fmt = matches.value_of("output").unwrap();
|
||||||
|
let mbox = matches.value_of("mailbox").unwrap();
|
||||||
let mut imap_conn = ImapConnector::new(&account)?;
|
let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("set") {
|
if let Some(matches) = matches.subcommand_matches("set") {
|
||||||
let uid = matches.value_of("uid").unwrap();
|
let uid = matches.value_of("uid").unwrap();
|
||||||
let flags = matches.value_of("flags").unwrap();
|
let flags = matches.value_of("flags").unwrap();
|
||||||
imap_conn.set_flags(mbox, uid, flags)?;
|
imap_conn.set_flags(mbox, uid, flags)?;
|
||||||
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("add") {
|
if let Some(matches) = matches.subcommand_matches("add") {
|
||||||
let uid = matches.value_of("uid").unwrap();
|
let uid = matches.value_of("uid").unwrap();
|
||||||
let flags = matches.value_of("flags").unwrap();
|
let flags = matches.value_of("flags").unwrap();
|
||||||
imap_conn.add_flags(mbox, uid, flags)?;
|
imap_conn.add_flags(mbox, uid, flags)?;
|
||||||
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("remove") {
|
if let Some(matches) = matches.subcommand_matches("remove") {
|
||||||
let uid = matches.value_of("uid").unwrap();
|
let uid = matches.value_of("uid").unwrap();
|
||||||
let flags = matches.value_of("flags").unwrap();
|
let flags = matches.value_of("flags").unwrap();
|
||||||
imap_conn.remove_flags(mbox, uid, flags)?;
|
imap_conn.remove_flags(mbox, uid, flags)?;
|
||||||
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ use std::net::TcpStream;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{self, Account, Config},
|
config::{self, Account, Config},
|
||||||
mbox::{Mbox, Mboxes},
|
mbox::model::{Mbox, Mboxes},
|
||||||
msg::Msg,
|
msg::model::Msg,
|
||||||
};
|
};
|
||||||
|
|
||||||
error_chain! {
|
error_chain! {
|
||||||
|
|
12
src/main.rs
12
src/main.rs
|
@ -2,8 +2,6 @@ mod app;
|
||||||
mod config;
|
mod config;
|
||||||
mod imap;
|
mod imap;
|
||||||
mod input;
|
mod input;
|
||||||
mod mbox;
|
|
||||||
mod msg;
|
|
||||||
mod output;
|
mod output;
|
||||||
mod smtp;
|
mod smtp;
|
||||||
mod table;
|
mod table;
|
||||||
|
@ -11,6 +9,14 @@ mod flag {
|
||||||
pub(crate) mod cli;
|
pub(crate) mod cli;
|
||||||
pub(crate) mod model;
|
pub(crate) mod model;
|
||||||
}
|
}
|
||||||
|
mod msg {
|
||||||
|
pub(crate) mod cli;
|
||||||
|
pub(crate) mod model;
|
||||||
|
}
|
||||||
|
mod mbox {
|
||||||
|
pub(crate) mod cli;
|
||||||
|
pub(crate) mod model;
|
||||||
|
}
|
||||||
|
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
|
|
||||||
|
@ -18,11 +24,11 @@ fn main() {
|
||||||
if let Err(ref errs) = App::new().run() {
|
if let Err(ref errs) = App::new().run() {
|
||||||
let mut errs = errs.iter();
|
let mut errs = errs.iter();
|
||||||
match errs.next() {
|
match errs.next() {
|
||||||
|
None => (),
|
||||||
Some(err) => {
|
Some(err) => {
|
||||||
eprintln!("{}", err);
|
eprintln!("{}", err);
|
||||||
errs.for_each(|err| eprintln!(" ↳ {}", err));
|
errs.for_each(|err| eprintln!(" ↳ {}", err));
|
||||||
}
|
}
|
||||||
None => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
44
src/mbox/cli.rs
Normal file
44
src/mbox/cli.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
use clap::{self, App, Arg, ArgMatches, SubCommand};
|
||||||
|
use error_chain::error_chain;
|
||||||
|
|
||||||
|
use crate::{config::Config, imap::ImapConnector, output::print};
|
||||||
|
|
||||||
|
error_chain! {
|
||||||
|
links {
|
||||||
|
Config(crate::config::Error, crate::config::ErrorKind);
|
||||||
|
Imap(crate::imap::Error, crate::imap::ErrorKind);
|
||||||
|
MsgCli(crate::msg::cli::Error, crate::msg::cli::ErrorKind);
|
||||||
|
Output(crate::output::Error, crate::output::ErrorKind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mbox_arg<'a>() -> Arg<'a, 'a> {
|
||||||
|
Arg::with_name("mailbox")
|
||||||
|
.short("m")
|
||||||
|
.long("mailbox")
|
||||||
|
.help("Selects a specific mailbox")
|
||||||
|
.value_name("STRING")
|
||||||
|
.default_value("INBOX")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mbox_subcmds<'a>() -> Vec<App<'a, 'a>> {
|
||||||
|
vec![SubCommand::with_name("mailboxes")
|
||||||
|
.aliases(&["mailbox", "mboxes", "mbox", "m"])
|
||||||
|
.about("Lists all mailboxes")]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mbox_matches(matches: &ArgMatches) -> Result<bool> {
|
||||||
|
if let Some(_) = matches.subcommand_matches("mailboxes") {
|
||||||
|
let config = Config::new_from_file()?;
|
||||||
|
let account = config.find_account_by_name(matches.value_of("account"))?;
|
||||||
|
let output_fmt = matches.value_of("output").unwrap();
|
||||||
|
let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
let mboxes = imap_conn.list_mboxes()?;
|
||||||
|
print(&output_fmt, mboxes)?;
|
||||||
|
imap_conn.logout();
|
||||||
|
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
63
src/mbox/model.rs
Normal file
63
src/mbox/model.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
use imap;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::table::{self, DisplayRow, DisplayTable};
|
||||||
|
|
||||||
|
// Mbox
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct Mbox {
|
||||||
|
pub delim: String,
|
||||||
|
pub name: String,
|
||||||
|
pub attributes: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mbox {
|
||||||
|
pub fn from_name(name: &imap::types::Name) -> Self {
|
||||||
|
Self {
|
||||||
|
delim: name.delimiter().unwrap_or_default().to_owned(),
|
||||||
|
name: name.name().to_owned(),
|
||||||
|
attributes: vec![], // TODO: set attributes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DisplayRow for Mbox {
|
||||||
|
fn to_row(&self) -> Vec<table::Cell> {
|
||||||
|
use crate::table::*;
|
||||||
|
|
||||||
|
vec![
|
||||||
|
Cell::new(&[BLUE], &self.delim),
|
||||||
|
Cell::new(&[GREEN], &self.name),
|
||||||
|
FlexCell::new(&[YELLOW], &self.attributes.join(", ")),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mboxes
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct Mboxes(pub Vec<Mbox>);
|
||||||
|
|
||||||
|
impl<'a> DisplayTable<'a, Mbox> for Mboxes {
|
||||||
|
fn header_row() -> Vec<table::Cell> {
|
||||||
|
use crate::table::*;
|
||||||
|
|
||||||
|
vec![
|
||||||
|
Cell::new(&[BOLD, UNDERLINE, WHITE], "DELIM"),
|
||||||
|
Cell::new(&[BOLD, UNDERLINE, WHITE], "NAME"),
|
||||||
|
FlexCell::new(&[BOLD, UNDERLINE, WHITE], "ATTRIBUTES"),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rows(&self) -> &Vec<Mbox> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Mboxes {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "\n{}", self.to_table())
|
||||||
|
}
|
||||||
|
}
|
536
src/msg/cli.rs
Normal file
536
src/msg/cli.rs
Normal file
|
@ -0,0 +1,536 @@
|
||||||
|
use clap::{self, App, Arg, ArgMatches, SubCommand};
|
||||||
|
use error_chain::error_chain;
|
||||||
|
use std::{env, fs};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::{self, Account, Config},
|
||||||
|
imap::{self, ImapConnector},
|
||||||
|
input,
|
||||||
|
// msg::{self, Attachments, Msg, Msgs, ReadableMsg},
|
||||||
|
msg::model::Msgs,
|
||||||
|
output::{self, print},
|
||||||
|
smtp,
|
||||||
|
};
|
||||||
|
|
||||||
|
error_chain! {
|
||||||
|
links {
|
||||||
|
Config(config::Error, config::ErrorKind);
|
||||||
|
Imap(imap::Error, imap::ErrorKind);
|
||||||
|
Input(input::Error, input::ErrorKind);
|
||||||
|
Output(output::Error, output::ErrorKind);
|
||||||
|
Smtp(smtp::Error, smtp::ErrorKind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mailbox_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||||
|
Arg::with_name("mailbox")
|
||||||
|
.short("m")
|
||||||
|
.long("mailbox")
|
||||||
|
.help("Mailbox name")
|
||||||
|
.value_name("STRING")
|
||||||
|
.default_value("INBOX")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uid_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||||
|
Arg::with_name("uid")
|
||||||
|
.help("Message UID")
|
||||||
|
.value_name("UID")
|
||||||
|
.required(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reply_all_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||||
|
Arg::with_name("reply-all")
|
||||||
|
.help("Includes all recipients")
|
||||||
|
.short("a")
|
||||||
|
.long("all")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_size_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||||
|
Arg::with_name("size")
|
||||||
|
.help("Page size")
|
||||||
|
.short("s")
|
||||||
|
.long("size")
|
||||||
|
.value_name("INT")
|
||||||
|
.default_value("10")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||||
|
Arg::with_name("page")
|
||||||
|
.help("Page number")
|
||||||
|
.short("p")
|
||||||
|
.long("page")
|
||||||
|
.value_name("INT")
|
||||||
|
.default_value("0")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn msg_args<'a>() -> Vec<Arg<'a, 'a>> {
|
||||||
|
vec![
|
||||||
|
Arg::with_name("page")
|
||||||
|
.help("Page number")
|
||||||
|
.short("p")
|
||||||
|
.long("page")
|
||||||
|
.value_name("INT")
|
||||||
|
.default_value("0"),
|
||||||
|
Arg::with_name("size")
|
||||||
|
.help("Page size")
|
||||||
|
.short("s")
|
||||||
|
.long("size")
|
||||||
|
.value_name("INT")
|
||||||
|
.default_value("10"),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn msg_subcmds<'a>() -> Vec<App<'a, 'a>> {
|
||||||
|
vec![SubCommand::with_name("list")
|
||||||
|
.aliases(&["lst", "l"])
|
||||||
|
.about("Lists messages sorted by UID")
|
||||||
|
.arg(page_size_arg())
|
||||||
|
.arg(page_arg())]
|
||||||
|
// SubCommand::with_name("messages")
|
||||||
|
// .aliases(&["messages", "message", "msgs", "msg"])
|
||||||
|
// .about("Handles message actions")
|
||||||
|
// .arg(mailbox_arg())
|
||||||
|
// .subcommand(
|
||||||
|
// SubCommand::with_name("list")
|
||||||
|
// .aliases(&["lst", "l"])
|
||||||
|
// .about("Lists messages sorted by UID")
|
||||||
|
// .arg(page_size_arg())
|
||||||
|
// .arg(page_arg()),
|
||||||
|
// )
|
||||||
|
// .subcommand(
|
||||||
|
// SubCommand::with_name("search")
|
||||||
|
// .aliases(&["query", "q", "s"])
|
||||||
|
// .about("Lists messages matching the given IMAP query")
|
||||||
|
// .arg(page_size_arg())
|
||||||
|
// .arg(page_arg())
|
||||||
|
// .arg(
|
||||||
|
// Arg::with_name("query")
|
||||||
|
// .help("IMAP query (see https://tools.ietf.org/html/rfc3501#section-6.4.4)")
|
||||||
|
// .value_name("QUERY")
|
||||||
|
// .multiple(true)
|
||||||
|
// .required(true),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// .subcommand(SubCommand::with_name("write").about("Writes a new message"))
|
||||||
|
// .subcommand(
|
||||||
|
// SubCommand::with_name("send")
|
||||||
|
// .about("Sends a raw message")
|
||||||
|
// .arg(Arg::with_name("message").raw(true)),
|
||||||
|
// )
|
||||||
|
// .subcommand(
|
||||||
|
// SubCommand::with_name("save")
|
||||||
|
// .about("Saves a raw message")
|
||||||
|
// .arg(Arg::with_name("message").raw(true)),
|
||||||
|
// )
|
||||||
|
// .subcommand(
|
||||||
|
// SubCommand::with_name("read")
|
||||||
|
// .aliases(&["r"])
|
||||||
|
// .about("Reads text bodies of a message")
|
||||||
|
// .arg(uid_arg())
|
||||||
|
// .arg(
|
||||||
|
// Arg::with_name("mime-type")
|
||||||
|
// .help("MIME type to use")
|
||||||
|
// .short("t")
|
||||||
|
// .long("mime-type")
|
||||||
|
// .value_name("STRING")
|
||||||
|
// .possible_values(&["plain", "html"])
|
||||||
|
// .default_value("plain"),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// .subcommand(
|
||||||
|
// SubCommand::with_name("attachments")
|
||||||
|
// .aliases(&["attach", "att", "a"])
|
||||||
|
// .about("Downloads all attachments from an email")
|
||||||
|
// .arg(uid_arg()),
|
||||||
|
// )
|
||||||
|
// .subcommand(
|
||||||
|
// SubCommand::with_name("reply")
|
||||||
|
// .aliases(&["rep", "re"])
|
||||||
|
// .about("Answers to an email")
|
||||||
|
// .arg(uid_arg())
|
||||||
|
// .arg(reply_all_arg()),
|
||||||
|
// )
|
||||||
|
// .subcommand(
|
||||||
|
// SubCommand::with_name("forward")
|
||||||
|
// .aliases(&["fwd", "f"])
|
||||||
|
// .about("Forwards an email")
|
||||||
|
// .arg(uid_arg()),
|
||||||
|
// )
|
||||||
|
// .subcommand(
|
||||||
|
// SubCommand::with_name("template")
|
||||||
|
// .aliases(&["tpl", "t"])
|
||||||
|
// .about("Generates a message template")
|
||||||
|
// .subcommand(
|
||||||
|
// SubCommand::with_name("new")
|
||||||
|
// .aliases(&["n"])
|
||||||
|
// .about("Generates a new message template"),
|
||||||
|
// )
|
||||||
|
// .subcommand(
|
||||||
|
// SubCommand::with_name("reply")
|
||||||
|
// .aliases(&["rep", "r"])
|
||||||
|
// .about("Generates a reply message template")
|
||||||
|
// .arg(uid_arg())
|
||||||
|
// .arg(reply_all_arg()),
|
||||||
|
// )
|
||||||
|
// .subcommand(
|
||||||
|
// SubCommand::with_name("forward")
|
||||||
|
// .aliases(&["fwd", "fw", "f"])
|
||||||
|
// .about("Generates a forward message template")
|
||||||
|
// .arg(uid_arg()),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn msg_matches(matched: bool, matches: &ArgMatches) -> Result<bool> {
|
||||||
|
if matched {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = Config::new_from_file()?;
|
||||||
|
let account = config.find_account_by_name(matches.value_of("account"))?;
|
||||||
|
let output_fmt = matches.value_of("output").unwrap();
|
||||||
|
let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
|
||||||
|
let page_size: u32 = matches.value_of("size").unwrap().parse().unwrap();
|
||||||
|
let page: u32 = matches.value_of("page").unwrap().parse().unwrap();
|
||||||
|
|
||||||
|
let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?;
|
||||||
|
let msgs = Msgs::from(&msgs);
|
||||||
|
|
||||||
|
print(&output_fmt, msgs)?;
|
||||||
|
imap_conn.logout();
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
// let output_type = matches.value_of("output").unwrap().to_owned();
|
||||||
|
// let account = matches.value_of("account");
|
||||||
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
|
||||||
|
// if let Some(_) = matches.subcommand_matches("mailboxes") {
|
||||||
|
// let config = Config::new_from_file()?;
|
||||||
|
// let account = config.find_account_by_name(account)?;
|
||||||
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
|
||||||
|
// let mboxes = imap_conn.list_mboxes()?;
|
||||||
|
// print(&output_type, mboxes)?;
|
||||||
|
|
||||||
|
// imap_conn.logout();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if let Some(matches) = matches.subcommand_matches("flags") {
|
||||||
|
// flags_matches(account, &mbox, &matches)
|
||||||
|
// .chain_err(|| "Could not handle flags arg matches")?;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if let Some(matches) = matches.subcommand_matches("list") {
|
||||||
|
// let config = Config::new_from_file()?;
|
||||||
|
// let account = config.find_account_by_name(account)?;
|
||||||
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
|
||||||
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
// let page_size: u32 = matches.value_of("size").unwrap().parse().unwrap();
|
||||||
|
// let page: u32 = matches.value_of("page").unwrap().parse().unwrap();
|
||||||
|
|
||||||
|
// let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?;
|
||||||
|
// let msgs = Msgs::from(&msgs);
|
||||||
|
|
||||||
|
// print(&output_type, msgs)?;
|
||||||
|
|
||||||
|
// imap_conn.logout();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if let Some(matches) = matches.subcommand_matches("search") {
|
||||||
|
// let config = Config::new_from_file()?;
|
||||||
|
// let account = config.find_account_by_name(account)?;
|
||||||
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
// let page_size: usize = matches.value_of("size").unwrap().parse().unwrap();
|
||||||
|
// let page: usize = matches.value_of("page").unwrap().parse().unwrap();
|
||||||
|
// let query = matches
|
||||||
|
// .values_of("query")
|
||||||
|
// .unwrap_or_default()
|
||||||
|
// .fold((false, vec![]), |(escape, mut cmds), cmd| {
|
||||||
|
// match (cmd, escape) {
|
||||||
|
// // Next command is an arg and needs to be escaped
|
||||||
|
// ("subject", _) | ("body", _) | ("text", _) => {
|
||||||
|
// cmds.push(cmd.to_string());
|
||||||
|
// (true, cmds)
|
||||||
|
// }
|
||||||
|
// // Escaped arg commands
|
||||||
|
// (_, true) => {
|
||||||
|
// cmds.push(format!("\"{}\"", cmd));
|
||||||
|
// (false, cmds)
|
||||||
|
// }
|
||||||
|
// // Regular commands
|
||||||
|
// (_, false) => {
|
||||||
|
// cmds.push(cmd.to_string());
|
||||||
|
// (false, cmds)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .1
|
||||||
|
// .join(" ");
|
||||||
|
|
||||||
|
// let msgs = imap_conn.search_msgs(&mbox, &query, &page_size, &page)?;
|
||||||
|
// let msgs = Msgs::from(&msgs);
|
||||||
|
|
||||||
|
// print(&output_type, msgs)?;
|
||||||
|
|
||||||
|
// imap_conn.logout();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if let Some(matches) = matches.subcommand_matches("read") {
|
||||||
|
// let config = Config::new_from_file()?;
|
||||||
|
// let account = config.find_account_by_name(account)?;
|
||||||
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
|
||||||
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
// let uid = matches.value_of("uid").unwrap();
|
||||||
|
// let mime = format!("text/{}", matches.value_of("mime-type").unwrap());
|
||||||
|
|
||||||
|
// let msg = imap_conn.read_msg(&mbox, &uid)?;
|
||||||
|
// let msg = ReadableMsg::from_bytes(&mime, &msg)?;
|
||||||
|
|
||||||
|
// print(&output_type, msg)?;
|
||||||
|
// imap_conn.logout();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if let Some(matches) = matches.subcommand_matches("attachments") {
|
||||||
|
// let config = Config::new_from_file()?;
|
||||||
|
// let account = config.find_account_by_name(account)?;
|
||||||
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
|
||||||
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
// let uid = matches.value_of("uid").unwrap();
|
||||||
|
|
||||||
|
// let msg = imap_conn.read_msg(&mbox, &uid)?;
|
||||||
|
// let attachments = Attachments::from_bytes(&msg)?;
|
||||||
|
|
||||||
|
// match output_type.as_str() {
|
||||||
|
// "text" => {
|
||||||
|
// println!(
|
||||||
|
// "{} attachment(s) found for message {}",
|
||||||
|
// attachments.0.len(),
|
||||||
|
// uid
|
||||||
|
// );
|
||||||
|
|
||||||
|
// attachments.0.iter().for_each(|attachment| {
|
||||||
|
// let filepath = config.downloads_filepath(&account, &attachment.filename);
|
||||||
|
// println!("Downloading {}…", &attachment.filename);
|
||||||
|
// fs::write(filepath, &attachment.raw).unwrap()
|
||||||
|
// });
|
||||||
|
|
||||||
|
// println!("Done!");
|
||||||
|
// }
|
||||||
|
// "json" => {
|
||||||
|
// attachments.0.iter().for_each(|attachment| {
|
||||||
|
// let filepath = config.downloads_filepath(&account, &attachment.filename);
|
||||||
|
// fs::write(filepath, &attachment.raw).unwrap()
|
||||||
|
// });
|
||||||
|
|
||||||
|
// print!("{{}}");
|
||||||
|
// }
|
||||||
|
// _ => (),
|
||||||
|
// }
|
||||||
|
|
||||||
|
// imap_conn.logout();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if let Some(_) = matches.subcommand_matches("write") {
|
||||||
|
// let config = Config::new_from_file()?;
|
||||||
|
// let account = config.find_account_by_name(account)?;
|
||||||
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
// let tpl = Msg::build_new_tpl(&config, &account)?;
|
||||||
|
// let content = input::open_editor_with_tpl(tpl.to_string().as_bytes())?;
|
||||||
|
// let mut msg = Msg::from(content);
|
||||||
|
|
||||||
|
// loop {
|
||||||
|
// match input::post_edit_choice() {
|
||||||
|
// Ok(choice) => match choice {
|
||||||
|
// input::Choice::Send => {
|
||||||
|
// println!("Sending…");
|
||||||
|
// let msg = msg.to_sendable_msg()?;
|
||||||
|
// smtp::send(&account, &msg)?;
|
||||||
|
// imap_conn.append_msg("Sent", &msg.formatted())?;
|
||||||
|
// println!("Done!");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// input::Choice::Draft => {
|
||||||
|
// println!("Saving to draft…");
|
||||||
|
// imap_conn.append_msg("Drafts", &msg.to_vec()?)?;
|
||||||
|
// println!("Done!");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// input::Choice::Edit => {
|
||||||
|
// let content = input::open_editor_with_draft()?;
|
||||||
|
// msg = Msg::from(content);
|
||||||
|
// }
|
||||||
|
// input::Choice::Quit => break,
|
||||||
|
// },
|
||||||
|
// Err(err) => eprintln!("{}", err),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// imap_conn.logout();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if let Some(matches) = matches.subcommand_matches("template") {
|
||||||
|
// let config = Config::new_from_file()?;
|
||||||
|
// let account = config.find_account_by_name(account)?;
|
||||||
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
|
||||||
|
// if let Some(_) = matches.subcommand_matches("new") {
|
||||||
|
// let tpl = Msg::build_new_tpl(&config, &account)?;
|
||||||
|
// print(&output_type, &tpl)?;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if let Some(matches) = matches.subcommand_matches("reply") {
|
||||||
|
// let uid = matches.value_of("uid").unwrap();
|
||||||
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
|
||||||
|
// let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
|
||||||
|
// let tpl = if matches.is_present("reply-all") {
|
||||||
|
// msg.build_reply_all_tpl(&config, &account)?
|
||||||
|
// } else {
|
||||||
|
// msg.build_reply_tpl(&config, &account)?
|
||||||
|
// };
|
||||||
|
|
||||||
|
// print(&output_type, &tpl)?;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if let Some(matches) = matches.subcommand_matches("forward") {
|
||||||
|
// let uid = matches.value_of("uid").unwrap();
|
||||||
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
|
||||||
|
// let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
|
||||||
|
// let tpl = msg.build_forward_tpl(&config, &account)?;
|
||||||
|
|
||||||
|
// print(&output_type, &tpl)?;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if let Some(matches) = matches.subcommand_matches("reply") {
|
||||||
|
// let config = Config::new_from_file()?;
|
||||||
|
// let account = config.find_account_by_name(account)?;
|
||||||
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
|
||||||
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
// let uid = matches.value_of("uid").unwrap();
|
||||||
|
|
||||||
|
// let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
|
||||||
|
// let tpl = if matches.is_present("reply-all") {
|
||||||
|
// msg.build_reply_all_tpl(&config, &account)?
|
||||||
|
// } else {
|
||||||
|
// msg.build_reply_tpl(&config, &account)?
|
||||||
|
// };
|
||||||
|
|
||||||
|
// let content = input::open_editor_with_tpl(&tpl.to_string().as_bytes())?;
|
||||||
|
// let mut msg = Msg::from(content);
|
||||||
|
|
||||||
|
// loop {
|
||||||
|
// match input::post_edit_choice() {
|
||||||
|
// Ok(choice) => match choice {
|
||||||
|
// input::Choice::Send => {
|
||||||
|
// println!("Sending…");
|
||||||
|
// smtp::send(&account, &msg.to_sendable_msg()?)?;
|
||||||
|
// imap_conn.append_msg("Sent", &msg.to_vec()?)?;
|
||||||
|
// imap_conn.add_flags(mbox, uid, "\\Answered")?;
|
||||||
|
// println!("Done!");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// input::Choice::Draft => {
|
||||||
|
// println!("Saving to draft…");
|
||||||
|
// imap_conn.append_msg("Drafts", &msg.to_vec()?)?;
|
||||||
|
// println!("Done!");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// input::Choice::Edit => {
|
||||||
|
// let content = input::open_editor_with_draft()?;
|
||||||
|
// msg = Msg::from(content);
|
||||||
|
// }
|
||||||
|
// input::Choice::Quit => break,
|
||||||
|
// },
|
||||||
|
// Err(err) => eprintln!("{}", err),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// imap_conn.logout();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if let Some(matches) = matches.subcommand_matches("forward") {
|
||||||
|
// let config = Config::new_from_file()?;
|
||||||
|
// let account = config.find_account_by_name(account)?;
|
||||||
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
|
||||||
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
// let uid = matches.value_of("uid").unwrap();
|
||||||
|
|
||||||
|
// let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
|
||||||
|
// let tpl = msg.build_forward_tpl(&config, &account)?;
|
||||||
|
// let content = input::open_editor_with_tpl(&tpl.to_string().as_bytes())?;
|
||||||
|
// let mut msg = Msg::from(content);
|
||||||
|
|
||||||
|
// loop {
|
||||||
|
// match input::post_edit_choice() {
|
||||||
|
// Ok(choice) => match choice {
|
||||||
|
// input::Choice::Send => {
|
||||||
|
// println!("Sending…");
|
||||||
|
// smtp::send(&account, &msg.to_sendable_msg()?)?;
|
||||||
|
// imap_conn.append_msg("Sent", &msg.to_vec()?)?;
|
||||||
|
// println!("Done!");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// input::Choice::Draft => {
|
||||||
|
// println!("Saving to draft…");
|
||||||
|
// imap_conn.append_msg("Drafts", &msg.to_vec()?)?;
|
||||||
|
// println!("Done!");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// input::Choice::Edit => {
|
||||||
|
// let content = input::open_editor_with_draft()?;
|
||||||
|
// msg = Msg::from(content);
|
||||||
|
// }
|
||||||
|
// input::Choice::Quit => break,
|
||||||
|
// },
|
||||||
|
// Err(err) => eprintln!("{}", err),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// imap_conn.logout();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if let Some(matches) = matches.subcommand_matches("send") {
|
||||||
|
// let config = Config::new_from_file()?;
|
||||||
|
// let account = config.find_account_by_name(account)?;
|
||||||
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
|
||||||
|
// let msg = matches.value_of("message").unwrap();
|
||||||
|
// let msg = Msg::from(msg.to_string());
|
||||||
|
// let msg = msg.to_sendable_msg()?;
|
||||||
|
|
||||||
|
// smtp::send(&account, &msg)?;
|
||||||
|
// imap_conn.append_msg("Sent", &msg.formatted())?;
|
||||||
|
// imap_conn.logout();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if let Some(matches) = matches.subcommand_matches("save") {
|
||||||
|
// let config = Config::new_from_file()?;
|
||||||
|
// let account = config.find_account_by_name(account)?;
|
||||||
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
|
||||||
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
// let msg = matches.value_of("message").unwrap();
|
||||||
|
// let msg = Msg::from(msg.to_string());
|
||||||
|
|
||||||
|
// imap_conn.append_msg(mbox, &msg.to_vec()?)?;
|
||||||
|
// imap_conn.logout();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if let Some(matches) = matches.subcommand_matches("idle") {
|
||||||
|
// let config = Config::new_from_file()?;
|
||||||
|
// let account = config.find_account_by_name(account)?;
|
||||||
|
// let mut imap_conn = ImapConnector::new(&account)?;
|
||||||
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
// imap_conn.idle(&config, &mbox)?;
|
||||||
|
// }
|
||||||
|
}
|
578
src/msg/model.rs
Normal file
578
src/msg/model.rs
Normal file
|
@ -0,0 +1,578 @@
|
||||||
|
use error_chain::error_chain;
|
||||||
|
use lettre;
|
||||||
|
use mailparse::{self, MailHeaderMap};
|
||||||
|
use rfc2047_decoder;
|
||||||
|
use serde::{
|
||||||
|
ser::{self, SerializeStruct},
|
||||||
|
Serialize,
|
||||||
|
};
|
||||||
|
use std::{fmt, result};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::config::{Account, Config};
|
||||||
|
use crate::flag::model::{Flag, Flags};
|
||||||
|
use crate::table::{self, DisplayRow, DisplayTable};
|
||||||
|
|
||||||
|
error_chain! {
|
||||||
|
foreign_links {
|
||||||
|
Mailparse(mailparse::MailParseError);
|
||||||
|
Lettre(lettre::error::Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Tpl(String);
|
||||||
|
|
||||||
|
impl fmt::Display for Tpl {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Tpl {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: ser::Serializer,
|
||||||
|
{
|
||||||
|
let mut state = serializer.serialize_struct("Tpl", 1)?;
|
||||||
|
state.serialize_field("template", &self.0)?;
|
||||||
|
state.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attachments
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Attachment {
|
||||||
|
pub filename: String,
|
||||||
|
pub raw: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Attachment {
|
||||||
|
// TODO: put in common with ReadableMsg
|
||||||
|
pub fn from_part(part: &'a mailparse::ParsedMail) -> Self {
|
||||||
|
Self {
|
||||||
|
filename: part
|
||||||
|
.get_content_disposition()
|
||||||
|
.params
|
||||||
|
.get("filename")
|
||||||
|
.unwrap_or(&Uuid::new_v4().to_simple().to_string())
|
||||||
|
.to_owned(),
|
||||||
|
raw: part.get_body_raw().unwrap_or_default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Attachments(pub Vec<Attachment>);
|
||||||
|
|
||||||
|
impl<'a> Attachments {
|
||||||
|
fn extract_from_part(&'a mut self, part: &'a mailparse::ParsedMail) {
|
||||||
|
if part.subparts.is_empty() {
|
||||||
|
let ctype = part
|
||||||
|
.get_headers()
|
||||||
|
.get_first_value("content-type")
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if !ctype.starts_with("text") {
|
||||||
|
self.0.push(Attachment::from_part(part));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
part.subparts
|
||||||
|
.iter()
|
||||||
|
.for_each(|part| self.extract_from_part(part));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
|
||||||
|
let msg = mailparse::parse_mail(bytes)?;
|
||||||
|
let mut attachments = Self(vec![]);
|
||||||
|
attachments.extract_from_part(&msg);
|
||||||
|
Ok(attachments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Readable message
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ReadableMsg {
|
||||||
|
pub content: String,
|
||||||
|
pub has_attachment: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for ReadableMsg {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: ser::Serializer,
|
||||||
|
{
|
||||||
|
let mut state = serializer.serialize_struct("ReadableMsg", 2)?;
|
||||||
|
state.serialize_field("content", &self.content)?;
|
||||||
|
state.serialize_field("hasAttachment", if self.has_attachment { &1 } else { &0 })?;
|
||||||
|
state.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ReadableMsg {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ReadableMsg {
|
||||||
|
fn flatten_parts(part: &'a mailparse::ParsedMail) -> Vec<&'a mailparse::ParsedMail<'a>> {
|
||||||
|
if part.subparts.is_empty() {
|
||||||
|
vec![part]
|
||||||
|
} else {
|
||||||
|
part.subparts
|
||||||
|
.iter()
|
||||||
|
.flat_map(Self::flatten_parts)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(mime: &str, bytes: &[u8]) -> Result<Self> {
|
||||||
|
let msg = mailparse::parse_mail(bytes)?;
|
||||||
|
let (text_part, html_part, has_attachment) = Self::flatten_parts(&msg).into_iter().fold(
|
||||||
|
(None, None, false),
|
||||||
|
|(mut text_part, mut html_part, mut has_attachment), part| {
|
||||||
|
let ctype = part
|
||||||
|
.get_headers()
|
||||||
|
.get_first_value("content-type")
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if text_part.is_none() && ctype.starts_with("text/plain") {
|
||||||
|
text_part = part.get_body().ok();
|
||||||
|
} else {
|
||||||
|
if html_part.is_none() && ctype.starts_with("text/html") {
|
||||||
|
html_part = part.get_body().ok();
|
||||||
|
} else {
|
||||||
|
has_attachment = true
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
(text_part, html_part, has_attachment)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let content = if mime == "text/plain" {
|
||||||
|
text_part.or(html_part).unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
html_part.or(text_part).unwrap_or_default()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
content,
|
||||||
|
has_attachment,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message
|
||||||
|
|
||||||
|
// #[derive(Debug, Serialize, PartialEq)]
|
||||||
|
// #[serde(rename_all = "lowercase")]
|
||||||
|
// pub enum Flag {
|
||||||
|
// Seen,
|
||||||
|
// Answered,
|
||||||
|
// Flagged,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl Flag {
|
||||||
|
// fn from_imap_flag(flag: &imap::types::Flag<'_>) -> Option<Self> {
|
||||||
|
// match flag {
|
||||||
|
// imap::types::Flag::Seen => Some(Self::Seen),
|
||||||
|
// imap::types::Flag::Answered => Some(Self::Answered),
|
||||||
|
// imap::types::Flag::Flagged => Some(Self::Flagged),
|
||||||
|
// _ => None,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct Msg<'m> {
|
||||||
|
pub uid: u32,
|
||||||
|
pub flags: Flags<'m>,
|
||||||
|
pub subject: String,
|
||||||
|
pub sender: String,
|
||||||
|
pub date: String,
|
||||||
|
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
raw: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'m> From<Vec<u8>> for Msg<'m> {
|
||||||
|
fn from(raw: Vec<u8>) -> Self {
|
||||||
|
Self {
|
||||||
|
uid: 0,
|
||||||
|
flags: Flags::new(&[]),
|
||||||
|
subject: String::from(""),
|
||||||
|
sender: String::from(""),
|
||||||
|
date: String::from(""),
|
||||||
|
raw,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'m> From<String> for Msg<'m> {
|
||||||
|
fn from(raw: String) -> Self {
|
||||||
|
Self::from(raw.as_bytes().to_vec())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'m> From<&'m imap::types::Fetch> for Msg<'m> {
|
||||||
|
fn from(fetch: &'m imap::types::Fetch) -> Self {
|
||||||
|
match fetch.envelope() {
|
||||||
|
None => Self::from(fetch.body().unwrap_or_default().to_vec()),
|
||||||
|
Some(envelope) => Self {
|
||||||
|
uid: fetch.uid.unwrap_or_default(),
|
||||||
|
flags: Flags::new(fetch.flags()),
|
||||||
|
subject: envelope
|
||||||
|
.subject
|
||||||
|
.and_then(|subj| rfc2047_decoder::decode(subj).ok())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
sender: envelope
|
||||||
|
.from
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|addrs| addrs.first()?.name)
|
||||||
|
.and_then(|name| rfc2047_decoder::decode(name).ok())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
date: fetch
|
||||||
|
.internal_date()
|
||||||
|
.map(|date| date.naive_local().to_string())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
raw: fetch.body().unwrap_or_default().to_vec(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'m> Msg<'m> {
|
||||||
|
pub fn parse(&'m self) -> Result<mailparse::ParsedMail<'m>> {
|
||||||
|
Ok(mailparse::parse_mail(&self.raw)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_vec(&self) -> Result<Vec<u8>> {
|
||||||
|
let parsed = self.parse()?;
|
||||||
|
let headers = parsed.get_headers().get_raw_bytes().to_vec();
|
||||||
|
let sep = "\r\n".as_bytes().to_vec();
|
||||||
|
let body = parsed.get_body()?.as_bytes().to_vec();
|
||||||
|
|
||||||
|
Ok(vec![headers, sep, body].concat())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_sendable_msg(&self) -> Result<lettre::Message> {
|
||||||
|
use lettre::message::header::{ContentTransferEncoding, ContentType};
|
||||||
|
use lettre::message::{Message, SinglePart};
|
||||||
|
|
||||||
|
let parsed = self.parse()?;
|
||||||
|
let msg = parsed
|
||||||
|
.headers
|
||||||
|
.iter()
|
||||||
|
.fold(Message::builder(), |msg, h| {
|
||||||
|
let value = String::from_utf8(h.get_value_raw().to_vec())
|
||||||
|
.unwrap()
|
||||||
|
.replace("\r", "");
|
||||||
|
|
||||||
|
match h.get_key().to_lowercase().as_str() {
|
||||||
|
"in-reply-to" => msg.in_reply_to(value.parse().unwrap()),
|
||||||
|
"from" => match value.parse() {
|
||||||
|
Ok(addr) => msg.from(addr),
|
||||||
|
Err(_) => msg,
|
||||||
|
},
|
||||||
|
"to" => value
|
||||||
|
.split(",")
|
||||||
|
.fold(msg, |msg, addr| match addr.trim().parse() {
|
||||||
|
Ok(addr) => msg.to(addr),
|
||||||
|
Err(_) => msg,
|
||||||
|
}),
|
||||||
|
"cc" => value
|
||||||
|
.split(",")
|
||||||
|
.fold(msg, |msg, addr| match addr.trim().parse() {
|
||||||
|
Ok(addr) => msg.cc(addr),
|
||||||
|
Err(_) => msg,
|
||||||
|
}),
|
||||||
|
"bcc" => value
|
||||||
|
.split(",")
|
||||||
|
.fold(msg, |msg, addr| match addr.trim().parse() {
|
||||||
|
Ok(addr) => msg.bcc(addr),
|
||||||
|
Err(_) => msg,
|
||||||
|
}),
|
||||||
|
"subject" => msg.subject(value),
|
||||||
|
_ => msg,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.singlepart(
|
||||||
|
SinglePart::builder()
|
||||||
|
.header(ContentType("text/plain; charset=utf-8".parse().unwrap()))
|
||||||
|
.header(ContentTransferEncoding::Base64)
|
||||||
|
.body(parsed.get_body_raw()?),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_text_bodies_into(part: &mailparse::ParsedMail, mime: &str, parts: &mut Vec<String>) {
|
||||||
|
match part.subparts.len() {
|
||||||
|
0 => {
|
||||||
|
let content_type = part
|
||||||
|
.get_headers()
|
||||||
|
.get_first_value("content-type")
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if content_type.starts_with(mime) {
|
||||||
|
parts.push(part.get_body().unwrap_or_default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
part.subparts
|
||||||
|
.iter()
|
||||||
|
.for_each(|part| Self::extract_text_bodies_into(part, mime, parts));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_text_bodies(&self, mime: &str) -> Result<Vec<String>> {
|
||||||
|
let mut parts = vec![];
|
||||||
|
Self::extract_text_bodies_into(&self.parse()?, mime, &mut parts);
|
||||||
|
Ok(parts)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text_bodies(&self, mime: &str) -> Result<String> {
|
||||||
|
let text_bodies = self.extract_text_bodies(mime)?;
|
||||||
|
Ok(text_bodies.join("\r\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_new_tpl(config: &Config, account: &Account) -> Result<Tpl> {
|
||||||
|
let mut tpl = vec![];
|
||||||
|
|
||||||
|
// "Content" headers
|
||||||
|
tpl.push("Content-Type: text/plain; charset=utf-8".to_string());
|
||||||
|
tpl.push("Content-Transfer-Encoding: 8bit".to_string());
|
||||||
|
|
||||||
|
// "From" header
|
||||||
|
tpl.push(format!("From: {}", config.address(account)));
|
||||||
|
|
||||||
|
// "To" header
|
||||||
|
tpl.push("To: ".to_string());
|
||||||
|
|
||||||
|
// "Subject" header
|
||||||
|
tpl.push("Subject: ".to_string());
|
||||||
|
|
||||||
|
Ok(Tpl(tpl.join("\r\n")))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_reply_tpl(&self, config: &Config, account: &Account) -> Result<Tpl> {
|
||||||
|
let msg = &self.parse()?;
|
||||||
|
let headers = msg.get_headers();
|
||||||
|
let mut tpl = vec![];
|
||||||
|
|
||||||
|
// "Content" headers
|
||||||
|
tpl.push("Content-Type: text/plain; charset=utf-8".to_string());
|
||||||
|
tpl.push("Content-Transfer-Encoding: 8bit".to_string());
|
||||||
|
|
||||||
|
// "From" header
|
||||||
|
tpl.push(format!("From: {}", config.address(account)));
|
||||||
|
|
||||||
|
// "In-Reply-To" header
|
||||||
|
if let Some(msg_id) = headers.get_first_value("message-id") {
|
||||||
|
tpl.push(format!("In-Reply-To: {}", msg_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// "To" header
|
||||||
|
let to = headers
|
||||||
|
.get_first_value("reply-to")
|
||||||
|
.or(headers.get_first_value("from"))
|
||||||
|
.unwrap_or(String::new());
|
||||||
|
tpl.push(format!("To: {}", to));
|
||||||
|
|
||||||
|
// "Subject" header
|
||||||
|
let subject = headers.get_first_value("subject").unwrap_or(String::new());
|
||||||
|
tpl.push(format!("Subject: Re: {}", subject));
|
||||||
|
|
||||||
|
// Separator between headers and body
|
||||||
|
tpl.push(String::new());
|
||||||
|
|
||||||
|
// Original msg prepend with ">"
|
||||||
|
let thread = self
|
||||||
|
.text_bodies("text/plain")?
|
||||||
|
.replace("\r", "")
|
||||||
|
.split("\n")
|
||||||
|
.map(|line| format!(">{}", line))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\r\n");
|
||||||
|
tpl.push(thread);
|
||||||
|
|
||||||
|
Ok(Tpl(tpl.join("\r\n")))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_reply_all_tpl(&self, config: &Config, account: &Account) -> Result<Tpl> {
|
||||||
|
let msg = &self.parse()?;
|
||||||
|
let headers = msg.get_headers();
|
||||||
|
let mut tpl = vec![];
|
||||||
|
|
||||||
|
// "Content" headers
|
||||||
|
tpl.push("Content-Type: text/plain; charset=utf-8".to_string());
|
||||||
|
tpl.push("Content-Transfer-Encoding: 8bit".to_string());
|
||||||
|
|
||||||
|
// "From" header
|
||||||
|
tpl.push(format!("From: {}", config.address(account)));
|
||||||
|
|
||||||
|
// "In-Reply-To" header
|
||||||
|
if let Some(msg_id) = headers.get_first_value("message-id") {
|
||||||
|
tpl.push(format!("In-Reply-To: {}", msg_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// "To" header
|
||||||
|
// All addresses coming from original "To" …
|
||||||
|
let email: lettre::Address = account.email.parse().unwrap();
|
||||||
|
let to = headers
|
||||||
|
.get_all_values("to")
|
||||||
|
.iter()
|
||||||
|
.flat_map(|addrs| addrs.split(","))
|
||||||
|
.fold(vec![], |mut mboxes, addr| {
|
||||||
|
match addr.trim().parse::<lettre::message::Mailbox>() {
|
||||||
|
Err(_) => mboxes,
|
||||||
|
Ok(mbox) => {
|
||||||
|
// … except current user's one (from config) …
|
||||||
|
if mbox.email != email {
|
||||||
|
mboxes.push(mbox.to_string());
|
||||||
|
}
|
||||||
|
mboxes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// … and the ones coming from either "Reply-To" or "From"
|
||||||
|
let reply_to = headers
|
||||||
|
.get_all_values("reply-to")
|
||||||
|
.iter()
|
||||||
|
.flat_map(|addrs| addrs.split(","))
|
||||||
|
.map(|addr| addr.trim().to_string())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
let reply_to = if reply_to.is_empty() {
|
||||||
|
headers
|
||||||
|
.get_all_values("from")
|
||||||
|
.iter()
|
||||||
|
.flat_map(|addrs| addrs.split(","))
|
||||||
|
.map(|addr| addr.trim().to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
} else {
|
||||||
|
reply_to
|
||||||
|
};
|
||||||
|
tpl.push(format!("To: {}", vec![reply_to, to].concat().join(", ")));
|
||||||
|
|
||||||
|
// "Cc" header
|
||||||
|
let cc = headers
|
||||||
|
.get_all_values("cc")
|
||||||
|
.iter()
|
||||||
|
.flat_map(|addrs| addrs.split(","))
|
||||||
|
.map(|addr| addr.trim().to_string())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
if !cc.is_empty() {
|
||||||
|
tpl.push(format!("Cc: {}", cc.join(", ")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Subject" header
|
||||||
|
let subject = headers.get_first_value("subject").unwrap_or(String::new());
|
||||||
|
tpl.push(format!("Subject: Re: {}", subject));
|
||||||
|
|
||||||
|
// Separator between headers and body
|
||||||
|
tpl.push(String::new());
|
||||||
|
|
||||||
|
// Original msg prepend with ">"
|
||||||
|
let thread = self
|
||||||
|
.text_bodies("text/plain")?
|
||||||
|
.split("\r\n")
|
||||||
|
.map(|line| format!(">{}", line))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\r\n");
|
||||||
|
tpl.push(thread);
|
||||||
|
|
||||||
|
Ok(Tpl(tpl.join("\r\n")))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_forward_tpl(&self, config: &Config, account: &Account) -> Result<Tpl> {
|
||||||
|
let msg = &self.parse()?;
|
||||||
|
let headers = msg.get_headers();
|
||||||
|
let mut tpl = vec![];
|
||||||
|
|
||||||
|
// "Content" headers
|
||||||
|
tpl.push("Content-Type: text/plain; charset=utf-8".to_string());
|
||||||
|
tpl.push("Content-Transfer-Encoding: 8bit".to_string());
|
||||||
|
|
||||||
|
// "From" header
|
||||||
|
tpl.push(format!("From: {}", config.address(account)));
|
||||||
|
|
||||||
|
// "To" header
|
||||||
|
tpl.push("To: ".to_string());
|
||||||
|
|
||||||
|
// "Subject" header
|
||||||
|
let subject = headers.get_first_value("subject").unwrap_or(String::new());
|
||||||
|
tpl.push(format!("Subject: Fwd: {}", subject));
|
||||||
|
|
||||||
|
// Separator between headers and body
|
||||||
|
tpl.push(String::new());
|
||||||
|
|
||||||
|
// Original msg
|
||||||
|
tpl.push("-------- Forwarded Message --------".to_string());
|
||||||
|
tpl.push(self.text_bodies("text/plain")?);
|
||||||
|
|
||||||
|
Ok(Tpl(tpl.join("\r\n")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'m> DisplayRow for Msg<'m> {
|
||||||
|
fn to_row(&self) -> Vec<table::Cell> {
|
||||||
|
use crate::table::*;
|
||||||
|
|
||||||
|
let unseen = if self.flags.contains(&Flag::Seen) {
|
||||||
|
RESET
|
||||||
|
} else {
|
||||||
|
BOLD
|
||||||
|
};
|
||||||
|
|
||||||
|
vec![
|
||||||
|
Cell::new(&[unseen.to_owned(), RED], &self.uid.to_string()),
|
||||||
|
Cell::new(&[unseen.to_owned(), WHITE], &self.flags.to_string()),
|
||||||
|
FlexCell::new(&[unseen.to_owned(), GREEN], &self.subject),
|
||||||
|
Cell::new(&[unseen.to_owned(), BLUE], &self.sender),
|
||||||
|
Cell::new(&[unseen.to_owned(), YELLOW], &self.date),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgs
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct Msgs<'m>(pub Vec<Msg<'m>>);
|
||||||
|
|
||||||
|
impl<'m> DisplayTable<'m, Msg<'m>> for Msgs<'m> {
|
||||||
|
fn header_row() -> Vec<table::Cell> {
|
||||||
|
use crate::table::*;
|
||||||
|
|
||||||
|
vec![
|
||||||
|
Cell::new(&[BOLD, UNDERLINE, WHITE], "UID"),
|
||||||
|
Cell::new(&[BOLD, UNDERLINE, WHITE], "FLAGS"),
|
||||||
|
FlexCell::new(&[BOLD, UNDERLINE, WHITE], "SUBJECT"),
|
||||||
|
Cell::new(&[BOLD, UNDERLINE, WHITE], "SENDER"),
|
||||||
|
Cell::new(&[BOLD, UNDERLINE, WHITE], "DATE"),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rows(&self) -> &Vec<Msg<'m>> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'m> From<&'m imap::types::ZeroCopy<Vec<imap::types::Fetch>>> for Msgs<'m> {
|
||||||
|
fn from(fetches: &'m imap::types::ZeroCopy<Vec<imap::types::Fetch>>) -> Self {
|
||||||
|
Self(fetches.iter().map(Msg::from).collect::<Vec<_>>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'m> fmt::Display for Msgs<'m> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "\n{}", self.to_table())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue