mirror of
https://github.com/pimalaya/himalaya.git
synced 2026-06-17 13:17:55 +08:00
implement add attachement to msg feature
This commit is contained in:
+32
-2
@@ -56,6 +56,16 @@ fn page_arg<'a>() -> Arg<'a, 'a> {
|
||||
.default_value("0")
|
||||
}
|
||||
|
||||
fn attachment_arg<'a>() -> Arg<'a, 'a> {
|
||||
Arg::with_name("attachments")
|
||||
.help("Adds attachment to the message")
|
||||
.short("a")
|
||||
.long("attachment")
|
||||
.value_name("PATH")
|
||||
.multiple(true)
|
||||
.takes_value(true)
|
||||
}
|
||||
|
||||
pub fn msg_subcmds<'a>() -> Vec<App<'a, 'a>> {
|
||||
vec![
|
||||
SubCommand::with_name("list")
|
||||
@@ -75,7 +85,9 @@ pub fn msg_subcmds<'a>() -> Vec<App<'a, 'a>> {
|
||||
.multiple(true)
|
||||
.required(true),
|
||||
),
|
||||
SubCommand::with_name("write").about("Writes a new message"),
|
||||
SubCommand::with_name("write")
|
||||
.about("Writes a new message")
|
||||
.arg(attachment_arg()),
|
||||
SubCommand::with_name("send")
|
||||
.about("Sends a raw message")
|
||||
.arg(Arg::with_name("message").raw(true)),
|
||||
@@ -247,10 +259,16 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
|
||||
break;
|
||||
}
|
||||
|
||||
if let Some(_) = matches.subcommand_matches("write") {
|
||||
if let Some(matches) = matches.subcommand_matches("write") {
|
||||
let attachments = matches
|
||||
.values_of("attachments")
|
||||
.unwrap_or_default()
|
||||
.map(String::from)
|
||||
.collect::<Vec<_>>();
|
||||
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);
|
||||
msg.attachments = attachments;
|
||||
|
||||
loop {
|
||||
match input::post_edit_choice() {
|
||||
@@ -317,6 +335,11 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
|
||||
}
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("reply") {
|
||||
let attachments = matches
|
||||
.values_of("attachments")
|
||||
.unwrap_or_default()
|
||||
.map(String::from)
|
||||
.collect::<Vec<_>>();
|
||||
let uid = matches.value_of("uid").unwrap();
|
||||
|
||||
let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?);
|
||||
@@ -328,6 +351,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
|
||||
|
||||
let content = input::open_editor_with_tpl(&tpl.to_string().as_bytes())?;
|
||||
let mut msg = Msg::from(content);
|
||||
msg.attachments = attachments;
|
||||
|
||||
loop {
|
||||
match input::post_edit_choice() {
|
||||
@@ -360,12 +384,18 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> {
|
||||
}
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("forward") {
|
||||
let attachments = matches
|
||||
.values_of("attachments")
|
||||
.unwrap_or_default()
|
||||
.map(String::from)
|
||||
.collect::<Vec<_>>();
|
||||
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);
|
||||
msg.attachments = attachments;
|
||||
|
||||
loop {
|
||||
match input::post_edit_choice() {
|
||||
|
||||
+81
-63
@@ -6,7 +6,8 @@ use serde::{
|
||||
ser::{self, SerializeStruct},
|
||||
Serialize,
|
||||
};
|
||||
use std::{fmt, result};
|
||||
use std::{borrow::Cow, fmt, fs, path::PathBuf, result};
|
||||
use tree_magic;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::config::model::{Account, Config};
|
||||
@@ -171,25 +172,6 @@ impl<'a> ReadableMsg {
|
||||
|
||||
// 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,
|
||||
@@ -197,7 +179,8 @@ pub struct Msg<'m> {
|
||||
pub subject: String,
|
||||
pub sender: String,
|
||||
pub date: String,
|
||||
|
||||
#[serde(skip_serializing)]
|
||||
pub attachments: Vec<String>,
|
||||
#[serde(skip_serializing)]
|
||||
raw: Vec<u8>,
|
||||
}
|
||||
@@ -210,6 +193,7 @@ impl<'m> From<Vec<u8>> for Msg<'m> {
|
||||
subject: String::from(""),
|
||||
sender: String::from(""),
|
||||
date: String::from(""),
|
||||
attachments: vec![],
|
||||
raw,
|
||||
}
|
||||
}
|
||||
@@ -242,6 +226,7 @@ impl<'m> From<&'m imap::types::Fetch> for Msg<'m> {
|
||||
.internal_date()
|
||||
.map(|date| date.naive_local().to_string())
|
||||
.unwrap_or_default(),
|
||||
attachments: vec![],
|
||||
raw: fetch.body().unwrap_or_default().to_vec(),
|
||||
},
|
||||
}
|
||||
@@ -263,52 +248,85 @@ impl<'m> Msg<'m> {
|
||||
}
|
||||
|
||||
pub fn to_sendable_msg(&self) -> Result<lettre::Message> {
|
||||
use lettre::message::header::{ContentTransferEncoding, ContentType};
|
||||
use lettre::message::{Message, SinglePart};
|
||||
use lettre::message::{
|
||||
header::*,
|
||||
{Body, Message, MultiPart, 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", "");
|
||||
let msg_builder = 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),
|
||||
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,
|
||||
},
|
||||
"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()?),
|
||||
)?;
|
||||
}),
|
||||
"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,
|
||||
}
|
||||
});
|
||||
|
||||
let text_part = SinglePart::builder()
|
||||
.header(ContentType("text/plain; charset=utf-8".parse().unwrap()))
|
||||
.header(ContentTransferEncoding::Base64)
|
||||
.body(parsed.get_body_raw()?);
|
||||
|
||||
let msg = if self.attachments.is_empty() {
|
||||
msg_builder.singlepart(text_part)
|
||||
} else {
|
||||
let mut parts = MultiPart::mixed().singlepart(text_part);
|
||||
|
||||
for attachment in &self.attachments {
|
||||
let attachment_name = PathBuf::from(attachment);
|
||||
let attachment_name = attachment_name
|
||||
.file_name()
|
||||
.map(|fname| fname.to_string_lossy())
|
||||
.unwrap_or(Cow::from(Uuid::new_v4().to_string()));
|
||||
let attachment_content = fs::read(attachment)
|
||||
.chain_err(|| format!("Cannot read attachment `{}`", attachment))?;
|
||||
let attachment_ctype = tree_magic::from_u8(&attachment_content);
|
||||
|
||||
parts = parts.singlepart(
|
||||
SinglePart::builder()
|
||||
.header(ContentType(attachment_ctype.parse().chain_err(|| {
|
||||
format!("Could not parse content type `{}`", attachment_ctype)
|
||||
})?))
|
||||
.header(ContentDisposition {
|
||||
disposition: DispositionType::Attachment,
|
||||
parameters: vec![DispositionParam::Filename(
|
||||
Charset::Ext("utf-8".into()),
|
||||
None,
|
||||
attachment_name.as_bytes().into(),
|
||||
)],
|
||||
})
|
||||
.body(Body::new(attachment_content)),
|
||||
);
|
||||
}
|
||||
|
||||
msg_builder.multipart(parts)
|
||||
}?;
|
||||
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user