2022-08-06 21:01:24 +00:00
|
|
|
mod cli;
|
|
|
|
mod feed;
|
|
|
|
mod message;
|
2022-06-12 19:42:18 +00:00
|
|
|
mod message_reader;
|
|
|
|
|
2022-08-06 21:01:24 +00:00
|
|
|
use chrono::Utc;
|
|
|
|
use clap::Parser;
|
|
|
|
use mail_parser::Message as ParsedMessage;
|
|
|
|
use message_reader::{DataDirectoryMessageReader, EmailReader, ImapReader};
|
|
|
|
use sha2::{Digest, Sha256};
|
2022-06-07 20:27:28 +00:00
|
|
|
use std::{
|
2022-08-06 21:01:24 +00:00
|
|
|
error::Error,
|
2022-08-05 00:27:10 +00:00
|
|
|
fs::{File, OpenOptions},
|
2022-06-07 20:27:28 +00:00
|
|
|
io::Write,
|
|
|
|
path::{Path, PathBuf},
|
2022-06-10 11:46:07 +00:00
|
|
|
};
|
2022-06-07 20:27:28 +00:00
|
|
|
|
2022-08-06 21:01:24 +00:00
|
|
|
pub(crate) use message::Message;
|
|
|
|
|
|
|
|
fn main() -> Result<(), Box<dyn Error>> {
|
|
|
|
let cli = cli::Cli::parse();
|
|
|
|
|
|
|
|
let data_directory = "data";
|
|
|
|
|
|
|
|
let result = match &cli.command {
|
|
|
|
cli::Command::FetchFromImap {
|
|
|
|
server,
|
|
|
|
port,
|
|
|
|
username,
|
|
|
|
password,
|
|
|
|
} => fetch_from_imap(
|
|
|
|
data_directory,
|
|
|
|
server.to_owned(),
|
|
|
|
*port,
|
|
|
|
username.to_owned(),
|
|
|
|
password.to_owned(),
|
|
|
|
),
|
2022-08-07 21:17:41 +00:00
|
|
|
cli::Command::BuildFeed { filename , hostname, include_html } => build_feed(&filename, hostname.to_owned(), *include_html),
|
2022-08-06 21:01:24 +00:00
|
|
|
_ => unimplemented!("This method is not yet implemented."),
|
|
|
|
};
|
2022-06-12 19:42:18 +00:00
|
|
|
|
2022-08-06 21:01:24 +00:00
|
|
|
result
|
2022-07-20 21:03:23 +00:00
|
|
|
}
|
|
|
|
|
2022-08-06 21:01:24 +00:00
|
|
|
fn create_directory<P: AsRef<Path>>(dir: P) -> Result<(), std::io::Error> {
|
|
|
|
if !dir.as_ref().exists() {
|
|
|
|
return std::fs::create_dir(&dir);
|
2022-07-20 21:03:23 +00:00
|
|
|
}
|
|
|
|
|
2022-08-06 21:01:24 +00:00
|
|
|
Ok(())
|
2022-07-20 21:03:23 +00:00
|
|
|
}
|
|
|
|
|
2022-08-07 21:17:41 +00:00
|
|
|
fn build_feed(filename: &PathBuf, hostname: String, include_html: bool) -> Result<(), Box<dyn Error>> {
|
2022-08-06 21:01:24 +00:00
|
|
|
let dir = filename.parent().ok_or(format!(
|
|
|
|
"Could not get parent directory of {}",
|
|
|
|
filename.display()
|
|
|
|
))?;
|
2022-06-07 20:27:28 +00:00
|
|
|
|
2022-08-06 21:01:24 +00:00
|
|
|
println!(
|
|
|
|
"Building the feed to {} in {}/",
|
|
|
|
filename.display(),
|
|
|
|
dir.display()
|
|
|
|
);
|
2022-08-05 00:27:10 +00:00
|
|
|
|
2022-08-06 21:01:24 +00:00
|
|
|
create_directory(dir)?;
|
2022-06-12 19:39:19 +00:00
|
|
|
|
2022-08-07 21:17:41 +00:00
|
|
|
let mut feed = feed::build_atom_feed(&hostname);
|
2022-08-06 21:01:24 +00:00
|
|
|
|
|
|
|
let mut reader = DataDirectoryMessageReader::new((&Path::new("data")).to_path_buf());
|
2022-06-07 20:27:28 +00:00
|
|
|
|
2022-08-06 21:01:24 +00:00
|
|
|
for msg in reader.read_rfc822_messages() {
|
2022-07-20 21:03:23 +00:00
|
|
|
let parsed = msg.get_parsed().expect("A parsed messsage.");
|
2022-06-07 20:27:28 +00:00
|
|
|
|
2022-08-06 21:01:24 +00:00
|
|
|
let date = parsed.get_date().ok_or(format!(
|
|
|
|
"Could not get the date of message {}",
|
|
|
|
msg.get_uid()
|
|
|
|
))?;
|
|
|
|
|
|
|
|
let subject = match parsed.get_subject() {
|
|
|
|
Some(subject) => subject,
|
|
|
|
None => "No subject",
|
|
|
|
};
|
|
|
|
|
|
|
|
println!(
|
|
|
|
"Processing message {} from {} with subject {}",
|
|
|
|
msg.get_uid(),
|
|
|
|
date.to_string(),
|
|
|
|
subject
|
|
|
|
);
|
|
|
|
|
2022-06-12 18:39:35 +00:00
|
|
|
let html_body = parsed.get_html_body(0).expect("Could not read html body");
|
2022-06-10 22:28:28 +00:00
|
|
|
let processed_html = process_html(&html_body).expect("Could not process the HTML");
|
2022-08-06 21:01:24 +00:00
|
|
|
|
2022-08-07 21:17:41 +00:00
|
|
|
if include_html {
|
|
|
|
let path : PathBuf = [dir, Path::new(&get_path(&parsed, &msg))].iter().collect();
|
|
|
|
write_file(&path, processed_html.as_bytes())?;
|
|
|
|
}
|
|
|
|
|
|
|
|
feed::add_entry_to_feed(&mut feed, &msg, &processed_html, &hostname, include_html);
|
2022-08-06 21:01:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if feed.entries.len() > 0 {
|
|
|
|
feed.set_updated(Utc::now());
|
|
|
|
println!("Writing feed to {}", filename.display());
|
|
|
|
let _ = feed.write_to(File::create(filename).unwrap());
|
|
|
|
}
|
|
|
|
|
|
|
|
println!("Finished building the feed.");
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn fetch_from_imap(
|
|
|
|
data_directory: &str,
|
|
|
|
server: String,
|
|
|
|
port: u16,
|
|
|
|
username: String,
|
|
|
|
password: String,
|
|
|
|
) -> Result<(), Box<dyn Error>> {
|
|
|
|
create_directory(data_directory)?;
|
|
|
|
|
|
|
|
print!("Getting mail from {} for mailbox {}", server, username);
|
|
|
|
|
|
|
|
let mut reader = ImapReader::new(
|
|
|
|
String::from(server),
|
|
|
|
port,
|
|
|
|
String::from(username),
|
|
|
|
String::from(password),
|
|
|
|
);
|
|
|
|
|
|
|
|
for msg in reader.read_rfc822_messages() {
|
|
|
|
let parsed = msg.get_parsed().ok_or(format!(
|
|
|
|
"Could not parse the message with id {}",
|
|
|
|
msg.get_uid()
|
|
|
|
))?;
|
|
|
|
|
|
|
|
let date = parsed.get_date().ok_or(format!(
|
|
|
|
"Could not get the date of message {}",
|
|
|
|
msg.get_uid()
|
|
|
|
))?;
|
|
|
|
|
|
|
|
let subject = match parsed.get_subject() {
|
|
|
|
Some(subject) => subject,
|
|
|
|
None => "No subject",
|
|
|
|
};
|
|
|
|
|
|
|
|
println!(
|
|
|
|
"Processing message {} from {} with subject {}",
|
|
|
|
msg.get_uid(),
|
|
|
|
date.to_string(),
|
|
|
|
subject
|
|
|
|
);
|
2022-06-10 11:46:07 +00:00
|
|
|
|
2022-08-05 00:27:10 +00:00
|
|
|
let path = get_path(&parsed, &msg);
|
2022-08-06 21:01:24 +00:00
|
|
|
let html_path: PathBuf = [
|
|
|
|
Path::new(data_directory),
|
|
|
|
Path::new(&format!("{}.eml", path)),
|
|
|
|
]
|
|
|
|
.iter()
|
|
|
|
.collect();
|
2022-06-12 18:39:35 +00:00
|
|
|
|
2022-08-06 21:01:24 +00:00
|
|
|
println!("Storing to {}", &html_path.display());
|
2022-08-05 00:27:10 +00:00
|
|
|
|
2022-08-07 21:17:41 +00:00
|
|
|
write_file(&html_path, msg.get_data())?;
|
2022-06-07 20:27:28 +00:00
|
|
|
}
|
2022-08-05 00:27:10 +00:00
|
|
|
|
2022-08-06 21:01:24 +00:00
|
|
|
Ok(())
|
2022-06-07 20:27:28 +00:00
|
|
|
}
|
|
|
|
|
2022-08-07 21:17:41 +00:00
|
|
|
|
|
|
|
|
2022-08-06 21:01:24 +00:00
|
|
|
fn get_path(parsed: &ParsedMessage, msg: &Message) -> String {
|
2022-08-02 21:07:01 +00:00
|
|
|
let date = parsed.get_date().expect("Could not extract date");
|
|
|
|
let date_str = format!(
|
|
|
|
"{:04}{:02}{:02}{:02}{:02}{:02}",
|
|
|
|
&date.year, &date.month, &date.day, &date.hour, &date.minute, &date.second
|
|
|
|
);
|
|
|
|
|
|
|
|
let hash = base16ct::lower::encode_string(&Sha256::digest(
|
|
|
|
&parsed.get_html_body(0).expect("Expected a body").as_bytes(),
|
|
|
|
));
|
|
|
|
|
2022-08-06 21:01:24 +00:00
|
|
|
let uid: i32 = msg.get_uid()
|
2022-08-02 21:07:01 +00:00
|
|
|
.parse()
|
2022-08-06 21:01:24 +00:00
|
|
|
.expect(&format!("Could not convert message id {} to an i32.", msg.get_uid()));
|
|
|
|
|
2022-08-02 21:07:01 +00:00
|
|
|
format!("{:05}_{}_{}.html", uid, date_str, &hash).to_owned()
|
|
|
|
}
|
|
|
|
|
2022-08-02 21:11:55 +00:00
|
|
|
fn process_html(input: &str) -> Result<String, ()> {
|
|
|
|
Ok(input.replace("src", "data-source"))
|
2022-06-10 22:28:28 +00:00
|
|
|
}
|
2022-08-07 21:17:41 +00:00
|
|
|
|
|
|
|
fn write_file(html_path: &PathBuf, data: &[u8]) -> Result<(), std::io::Error> {
|
|
|
|
OpenOptions::new()
|
|
|
|
.write(true)
|
|
|
|
.create(true)
|
|
|
|
.open(&html_path)
|
|
|
|
.expect(format!("Could not open file '{}' for writing", &html_path.display()).as_str())
|
|
|
|
.write_all(data)
|
|
|
|
}
|