mirror of
https://github.com/soywod/himalaya.git
synced 2024-07-08 18:45:13 +00:00
complete merge email/msg
This commit is contained in:
parent
0a508f2e95
commit
04642859e8
229
src/email.rs
229
src/email.rs
|
@ -1,229 +0,0 @@
|
||||||
use imap;
|
|
||||||
use mailparse::{self, MailHeaderMap};
|
|
||||||
use rfc2047_decoder;
|
|
||||||
|
|
||||||
use crate::table::{self, DisplayCell, DisplayRow, DisplayTable};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Uid(pub u32);
|
|
||||||
|
|
||||||
impl Uid {
|
|
||||||
pub fn from_fetch(fetch: &imap::types::Fetch) -> Self {
|
|
||||||
Self(fetch.uid.unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DisplayCell for Uid {
|
|
||||||
fn styles(&self) -> &[table::Style] {
|
|
||||||
&[table::RED]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn value(&self) -> String {
|
|
||||||
self.0.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Flags<'a>(Vec<imap::types::Flag<'a>>);
|
|
||||||
|
|
||||||
impl Flags<'_> {
|
|
||||||
pub fn from_fetch(fetch: &imap::types::Fetch) -> Self {
|
|
||||||
let flags = fetch.flags().iter().fold(vec![], |mut flags, flag| {
|
|
||||||
use imap::types::Flag::*;
|
|
||||||
|
|
||||||
match flag {
|
|
||||||
Seen => flags.push(Seen),
|
|
||||||
Answered => flags.push(Answered),
|
|
||||||
Draft => flags.push(Draft),
|
|
||||||
Flagged => flags.push(Flagged),
|
|
||||||
_ => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
flags
|
|
||||||
});
|
|
||||||
|
|
||||||
Self(flags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DisplayCell for Flags<'_> {
|
|
||||||
fn styles(&self) -> &[table::Style] {
|
|
||||||
&[table::WHITE]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn value(&self) -> String {
|
|
||||||
// FIXME
|
|
||||||
// use imap::types::Flag::*;
|
|
||||||
|
|
||||||
// let flags = &self.0;
|
|
||||||
// let mut flags_str = String::new();
|
|
||||||
|
|
||||||
// flags_str.push_str(if flags.contains(&Seen) { &" " } else { &"N" });
|
|
||||||
// flags_str.push_str(if flags.contains(&Answered) {
|
|
||||||
// &"R"
|
|
||||||
// } else {
|
|
||||||
// &" "
|
|
||||||
// });
|
|
||||||
// flags_str.push_str(if flags.contains(&Draft) { &"D" } else { &" " });
|
|
||||||
// flags_str.push_str(if flags.contains(&Flagged) { &"F" } else { &" " });
|
|
||||||
|
|
||||||
// flags_str
|
|
||||||
|
|
||||||
String::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Sender(String);
|
|
||||||
|
|
||||||
impl Sender {
|
|
||||||
fn try_from_fetch(fetch: &imap::types::Fetch) -> Option<String> {
|
|
||||||
let addr = fetch.envelope()?.from.as_ref()?.first()?;
|
|
||||||
|
|
||||||
addr.name
|
|
||||||
.and_then(|bytes| rfc2047_decoder::decode(bytes).ok())
|
|
||||||
.or_else(|| {
|
|
||||||
let mbox = String::from_utf8(addr.mailbox?.to_vec()).ok()?;
|
|
||||||
let host = String::from_utf8(addr.host?.to_vec()).ok()?;
|
|
||||||
Some(format!("{}@{}", mbox, host))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_fetch(fetch: &imap::types::Fetch) -> Self {
|
|
||||||
Self(Self::try_from_fetch(fetch).unwrap_or(String::new()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DisplayCell for Sender {
|
|
||||||
fn styles(&self) -> &[table::Style] {
|
|
||||||
&[table::BLUE]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn value(&self) -> String {
|
|
||||||
self.0.to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Subject(String);
|
|
||||||
|
|
||||||
impl Subject {
|
|
||||||
fn try_from_fetch(fetch: &imap::types::Fetch) -> Option<String> {
|
|
||||||
fetch
|
|
||||||
.envelope()?
|
|
||||||
.subject
|
|
||||||
.and_then(|bytes| rfc2047_decoder::decode(bytes).ok())
|
|
||||||
.and_then(|subject| Some(subject.replace("\r", "")))
|
|
||||||
.and_then(|subject| Some(subject.replace("\n", "")))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_fetch(fetch: &imap::types::Fetch) -> Self {
|
|
||||||
Self(Self::try_from_fetch(fetch).unwrap_or(String::new()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DisplayCell for Subject {
|
|
||||||
fn styles(&self) -> &[table::Style] {
|
|
||||||
&[table::GREEN]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn value(&self) -> String {
|
|
||||||
self.0.to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Date(String);
|
|
||||||
|
|
||||||
impl Date {
|
|
||||||
fn try_from_fetch(fetch: &imap::types::Fetch) -> Option<String> {
|
|
||||||
fetch
|
|
||||||
.internal_date()
|
|
||||||
.and_then(|date| Some(date.to_rfc3339()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_fetch(fetch: &imap::types::Fetch) -> Self {
|
|
||||||
Self(Self::try_from_fetch(fetch).unwrap_or(String::new()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DisplayCell for Date {
|
|
||||||
fn styles(&self) -> &[table::Style] {
|
|
||||||
&[table::YELLOW]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn value(&self) -> String {
|
|
||||||
self.0.to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Email<'a> {
|
|
||||||
pub uid: Uid,
|
|
||||||
pub flags: Flags<'a>,
|
|
||||||
pub from: Sender,
|
|
||||||
pub subject: Subject,
|
|
||||||
pub date: Date,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Email<'_> {
|
|
||||||
pub fn from_fetch(fetch: &imap::types::Fetch) -> Self {
|
|
||||||
Self {
|
|
||||||
uid: Uid::from_fetch(fetch),
|
|
||||||
from: Sender::from_fetch(fetch),
|
|
||||||
subject: Subject::from_fetch(fetch),
|
|
||||||
date: Date::from_fetch(fetch),
|
|
||||||
flags: Flags::from_fetch(fetch),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DisplayRow for Email<'a> {
|
|
||||||
fn to_row(&self) -> Vec<table::Cell> {
|
|
||||||
vec![
|
|
||||||
self.uid.to_cell(),
|
|
||||||
self.flags.to_cell(),
|
|
||||||
self.from.to_cell(),
|
|
||||||
self.subject.to_cell(),
|
|
||||||
self.date.to_cell(),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DisplayTable<'a, Email<'a>> for Vec<Email<'a>> {
|
|
||||||
fn cols() -> &'a [&'a str] {
|
|
||||||
&["uid", "flags", "from", "subject", "date"]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rows(&self) -> &Vec<Email<'a>> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utils
|
|
||||||
|
|
||||||
fn extract_text_bodies_into(mime: &str, part: &mailparse::ParsedMail, parts: &mut Vec<String>) {
|
|
||||||
match part.subparts.len() {
|
|
||||||
0 => {
|
|
||||||
if part
|
|
||||||
.get_headers()
|
|
||||||
.get_first_value("content-type")
|
|
||||||
.and_then(|v| if v.starts_with(&mime) { Some(()) } else { None })
|
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
parts.push(part.get_body().unwrap_or(String::new()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
part.subparts
|
|
||||||
.iter()
|
|
||||||
.for_each(|part| extract_text_bodies_into(&mime, part, parts));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_text_bodies(mime: &str, email: &mailparse::ParsedMail) -> String {
|
|
||||||
let mut parts = vec![];
|
|
||||||
extract_text_bodies_into(&mime, email, &mut parts);
|
|
||||||
parts.join("\r\n")
|
|
||||||
}
|
|
50
src/imap.rs
50
src/imap.rs
|
@ -3,7 +3,6 @@ use native_tls::{self, TlsConnector, TlsStream};
|
||||||
use std::{fmt, net::TcpStream, result};
|
use std::{fmt, net::TcpStream, result};
|
||||||
|
|
||||||
use crate::config;
|
use crate::config;
|
||||||
use crate::email::{self, Email};
|
|
||||||
use crate::mbox::Mbox;
|
use crate::mbox::Mbox;
|
||||||
use crate::msg::Msg;
|
use crate::msg::Msg;
|
||||||
|
|
||||||
|
@ -114,49 +113,34 @@ impl<'a> ImapConnector<'a> {
|
||||||
Ok(msgs)
|
Ok(msgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_emails(&mut self, mbox: &str, query: &str) -> Result<Vec<Email<'_>>> {
|
pub fn search_msgs(
|
||||||
|
&mut self,
|
||||||
|
mbox: &str,
|
||||||
|
query: &str,
|
||||||
|
page_size: &usize,
|
||||||
|
page: &usize,
|
||||||
|
) -> Result<Vec<Msg>> {
|
||||||
self.sess.select(mbox)?;
|
self.sess.select(mbox)?;
|
||||||
|
|
||||||
|
let begin = page * page_size;
|
||||||
|
let end = begin + (page_size - 1);
|
||||||
let uids = self
|
let uids = self
|
||||||
.sess
|
.sess
|
||||||
.uid_search(query)?
|
.search(query)?
|
||||||
.iter()
|
.iter()
|
||||||
.map(|n| n.to_string())
|
.map(|seq| seq.to_string())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
let range = uids[begin..end.min(uids.len())].join(",");
|
||||||
|
|
||||||
let emails = self
|
let msgs = self
|
||||||
.sess
|
.sess
|
||||||
.uid_fetch(
|
.fetch(range, "(UID ENVELOPE INTERNALDATE)")?
|
||||||
uids[..20.min(uids.len())].join(","),
|
|
||||||
"(UID ENVELOPE INTERNALDATE)",
|
|
||||||
)?
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(Email::from_fetch)
|
.rev()
|
||||||
|
.map(Msg::from)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
Ok(emails)
|
Ok(msgs)
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_email_body(&mut self, mbox: &str, uid: &str, mime: &str) -> Result<String> {
|
|
||||||
self.sess.select(mbox)?;
|
|
||||||
|
|
||||||
match self.sess.uid_fetch(uid, "BODY[]")?.first() {
|
|
||||||
None => Err(Error::ReadEmailNotFoundError(uid.to_string())),
|
|
||||||
Some(fetch) => {
|
|
||||||
let bytes = fetch.body().unwrap_or(&[]);
|
|
||||||
let email = mailparse::parse_mail(bytes)?;
|
|
||||||
let bodies = email::extract_text_bodies(&mime, &email);
|
|
||||||
|
|
||||||
if bodies.is_empty() {
|
|
||||||
Err(Error::ReadEmailEmptyPartError(
|
|
||||||
uid.to_string(),
|
|
||||||
mime.to_string(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Ok(bodies)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_msg(&mut self, mbox: &str, uid: &str) -> Result<Vec<u8>> {
|
pub fn read_msg(&mut self, mbox: &str, uid: &str) -> Result<Vec<u8>> {
|
||||||
|
|
82
src/main.rs
82
src/main.rs
|
@ -1,5 +1,4 @@
|
||||||
mod config;
|
mod config;
|
||||||
mod email;
|
|
||||||
mod imap;
|
mod imap;
|
||||||
mod input;
|
mod input;
|
||||||
mod mbox;
|
mod mbox;
|
||||||
|
@ -15,8 +14,8 @@ 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_SIZE: usize = 10;
|
||||||
const DEFAULT_PAGE: u32 = 0;
|
const DEFAULT_PAGE: usize = 0;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
@ -91,9 +90,27 @@ fn uid_arg() -> Arg<'static, 'static> {
|
||||||
.required(true)
|
.required(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn page_size_arg<'a>(default: &'a str) -> Arg<'a, 'a> {
|
||||||
|
Arg::with_name("size")
|
||||||
|
.help("Page size")
|
||||||
|
.short("s")
|
||||||
|
.long("size")
|
||||||
|
.value_name("INT")
|
||||||
|
.default_value(default)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_arg<'a>(default: &'a str) -> Arg<'a, 'a> {
|
||||||
|
Arg::with_name("page")
|
||||||
|
.help("Page number")
|
||||||
|
.short("p")
|
||||||
|
.long("page")
|
||||||
|
.value_name("INT")
|
||||||
|
.default_value(default)
|
||||||
|
}
|
||||||
|
|
||||||
fn run() -> Result<()> {
|
fn run() -> Result<()> {
|
||||||
let default_page_size = &DEFAULT_PAGE_SIZE.to_string();
|
let default_page_size_str = &DEFAULT_PAGE_SIZE.to_string();
|
||||||
let default_page = &DEFAULT_PAGE.to_string();
|
let default_page_str = &DEFAULT_PAGE.to_string();
|
||||||
|
|
||||||
let matches = App::new("Himalaya")
|
let matches = App::new("Himalaya")
|
||||||
.version("0.1.0")
|
.version("0.1.0")
|
||||||
|
@ -110,28 +127,16 @@ fn run() -> Result<()> {
|
||||||
.aliases(&["lst", "l"])
|
.aliases(&["lst", "l"])
|
||||||
.about("Lists emails sorted by arrival date")
|
.about("Lists emails sorted by arrival date")
|
||||||
.arg(mailbox_arg())
|
.arg(mailbox_arg())
|
||||||
.arg(
|
.arg(page_size_arg(default_page_size_str))
|
||||||
Arg::with_name("size")
|
.arg(page_arg(default_page_str)),
|
||||||
.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"])
|
.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(page_size_arg(default_page_size_str))
|
||||||
|
.arg(page_arg(default_page_str))
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("query")
|
Arg::with_name("query")
|
||||||
.help("IMAP query (see https://tools.ietf.org/html/rfc3501#section-6.4.4)")
|
.help("IMAP query (see https://tools.ietf.org/html/rfc3501#section-6.4.4)")
|
||||||
|
@ -205,12 +210,12 @@ fn run() -> Result<()> {
|
||||||
.value_of("size")
|
.value_of("size")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap_or(DEFAULT_PAGE_SIZE);
|
.unwrap_or(DEFAULT_PAGE_SIZE as u32);
|
||||||
let page: u32 = matches
|
let page: u32 = matches
|
||||||
.value_of("page")
|
.value_of("page")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap_or(DEFAULT_PAGE);
|
.unwrap_or(DEFAULT_PAGE as u32);
|
||||||
|
|
||||||
let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?;
|
let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?;
|
||||||
println!("{}", msgs.to_table());
|
println!("{}", msgs.to_table());
|
||||||
|
@ -223,6 +228,16 @@ fn run() -> Result<()> {
|
||||||
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
let mut imap_conn = ImapConnector::new(&config.imap)?;
|
||||||
|
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
let page_size: usize = matches
|
||||||
|
.value_of("size")
|
||||||
|
.unwrap()
|
||||||
|
.parse()
|
||||||
|
.unwrap_or(DEFAULT_PAGE_SIZE);
|
||||||
|
let page: usize = matches
|
||||||
|
.value_of("page")
|
||||||
|
.unwrap()
|
||||||
|
.parse()
|
||||||
|
.unwrap_or(DEFAULT_PAGE);
|
||||||
let query = matches
|
let query = matches
|
||||||
.values_of("query")
|
.values_of("query")
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
|
@ -248,7 +263,7 @@ fn run() -> Result<()> {
|
||||||
.1
|
.1
|
||||||
.join(" ");
|
.join(" ");
|
||||||
|
|
||||||
let msgs = imap_conn.read_emails(&mbox, &query)?;
|
let msgs = imap_conn.search_msgs(&mbox, &query, &page_size, &page)?;
|
||||||
println!("{}", msgs.to_table());
|
println!("{}", msgs.to_table());
|
||||||
|
|
||||||
imap_conn.close();
|
imap_conn.close();
|
||||||
|
@ -262,8 +277,9 @@ fn run() -> Result<()> {
|
||||||
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 = imap_conn.read_email_body(&mbox, &uid, &mime)?;
|
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
|
||||||
println!("{}", body);
|
let text_bodies = msg.text_bodies(&mime)?;
|
||||||
|
println!("{}", text_bodies);
|
||||||
|
|
||||||
imap_conn.close();
|
imap_conn.close();
|
||||||
}
|
}
|
||||||
|
@ -275,8 +291,8 @@ fn run() -> Result<()> {
|
||||||
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 msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?.as_slice());
|
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
|
||||||
let parts = msg.extract_parts()?;
|
let parts = msg.extract_attachments()?;
|
||||||
|
|
||||||
if parts.is_empty() {
|
if parts.is_empty() {
|
||||||
println!("No attachment found for message {}", uid);
|
println!("No attachment found for message {}", uid);
|
||||||
|
@ -299,7 +315,7 @@ fn run() -> Result<()> {
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
input::ask_for_confirmation("Send the message?")?;
|
input::ask_for_confirmation("Send the message?")?;
|
||||||
|
|
||||||
|
@ -318,7 +334,7 @@ fn run() -> Result<()> {
|
||||||
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 msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?.as_slice());
|
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
|
||||||
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 {
|
||||||
|
@ -326,7 +342,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);
|
||||||
|
|
||||||
input::ask_for_confirmation("Send the message?")?;
|
input::ask_for_confirmation("Send the message?")?;
|
||||||
|
|
||||||
|
@ -345,10 +361,10 @@ fn run() -> Result<()> {
|
||||||
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 msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?.as_slice());
|
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
|
||||||
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);
|
||||||
|
|
||||||
input::ask_for_confirmation("Send the message?")?;
|
input::ask_for_confirmation("Send the message?")?;
|
||||||
|
|
||||||
|
|
55
src/msg.rs
55
src/msg.rs
|
@ -48,12 +48,22 @@ pub struct Msg {
|
||||||
raw: Vec<u8>,
|
raw: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&[u8]> for Msg {
|
impl From<String> for Msg {
|
||||||
fn from(item: &[u8]) -> Self {
|
fn from(item: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
uid: 0,
|
uid: 0,
|
||||||
flags: vec![],
|
flags: vec![],
|
||||||
raw: item.to_vec(),
|
raw: item.as_bytes().to_vec(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<u8>> for Msg {
|
||||||
|
fn from(item: Vec<u8>) -> Self {
|
||||||
|
Self {
|
||||||
|
uid: 0,
|
||||||
|
flags: vec![],
|
||||||
|
raw: item,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,7 +143,38 @@ impl<'a> Msg {
|
||||||
Ok(msg)
|
Ok(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_parts_into(part: &mailparse::ParsedMail, parts: &mut Vec<(String, Vec<u8>)>) {
|
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"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_attachments_into(part: &mailparse::ParsedMail, parts: &mut Vec<(String, Vec<u8>)>) {
|
||||||
match part.subparts.len() {
|
match part.subparts.len() {
|
||||||
0 => {
|
0 => {
|
||||||
let content_disp = part.get_content_disposition();
|
let content_disp = part.get_content_disposition();
|
||||||
|
@ -156,14 +197,14 @@ impl<'a> Msg {
|
||||||
_ => {
|
_ => {
|
||||||
part.subparts
|
part.subparts
|
||||||
.iter()
|
.iter()
|
||||||
.for_each(|part| Self::extract_parts_into(part, parts));
|
.for_each(|part| Self::extract_attachments_into(part, parts));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_parts(&self) -> Result<Vec<(String, Vec<u8>)>> {
|
pub fn extract_attachments(&self) -> Result<Vec<(String, Vec<u8>)>> {
|
||||||
let mut parts = vec![];
|
let mut parts = vec![];
|
||||||
Self::extract_parts_into(&self.parse()?, &mut parts);
|
Self::extract_attachments_into(&self.parse()?, &mut parts);
|
||||||
Ok(parts)
|
Ok(parts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue