added email capability

This commit is contained in:
Nickiel12 2023-09-10 21:38:30 -07:00
parent 4b7241f5a4
commit 0a9a6838bf
4 changed files with 595 additions and 9 deletions

View file

@ -9,6 +9,8 @@ edition = "2021"
chrono = "0.4.26"
chrono-tz = "0.8.3"
clap = { version = "4.4.0", features = ["derive"] }
csv = "1.2.2"
lettre = { version = "0.10.4", features = ["sendmail-transport", "builder"] }
log = "0.4.20"
reqwest = { version = "0.11.18", features = ["blocking", "json"] }
serde = { version = "1.0.184", features = ["serde_derive"] }

View file

@ -1,6 +1,10 @@
use chrono::{DateTime, Datelike, Utc};
use chrono_tz::US::Central;
use clap::Parser;
use lettre::{
message::{Message, header::ContentType, Attachment, MultiPart, SinglePart},
SendmailTransport, Transport,
};
use log::{debug, error};
use reqwest::blocking::Client;
use serde::{Deserialize, Serialize};
@ -41,6 +45,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
};
// --- END Loading and setup ---
let mut do_email_summary: bool = false;
// --- Get checked and unchecked ---
let primary_note = Client::new()
.get(format!(
@ -59,6 +65,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut checked: Vec<String> = vec![];
for line in primary_note_lines.iter() {
if line.starts_with("- [x] Send Summary") {
do_email_summary = true;
continue;
}
if line.starts_with("- [ ] Send Summary") {
continue;
}
// if line is a checkbox
if line.starts_with("- [") {
if line.starts_with("- [ ] ") {
@ -140,13 +153,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
body_content.push(format!(
"### {item} | {}h {}m | {}/{}/{}",
elapsed_time.num_hours(),
elapsed_time.num_minutes(),
elapsed_time.num_minutes() - elapsed_time.num_hours() * 60, // yes, I'm lazy
now.month(),
now.day(),
now.year()
));
} else if !line.is_empty() {
body_content.push(line.to_string());
body_content.push(line.to_string());
}
checked.retain(|val| val != &item);
}
@ -161,8 +174,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
})
.collect();
debug!("checked: {:#?}", checked);
debug!("unchecked: {:#?}", unchecked);
debug!("Creating new logs for: {:#?}", checked);
body_content.splice(2..2, checked);
@ -181,6 +193,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
})?)
.send()?;
if do_email_summary {
debug!("Send email selected");
reset_email_checkbox(&cfg);
send_email_summary(&cfg, body_content);
}
Ok(())
}
@ -205,6 +223,7 @@ struct NoteUpdate {
struct Config {
user: String,
pswd: String,
email_addr: String,
primary_note_id: String,
logging_note_id: String,
server_url: String,
@ -220,3 +239,149 @@ struct CliArgs {
#[arg(short, long)]
debug: bool,
}
// All unwraps are unrecoverable errors
// All calls after depend on the success of the one before, and cannot run
// without. So it would be too much work to make an error type to handle an error
// that would cause a crash anyways
fn reset_email_checkbox(config: &Config) {
let primary_note = Client::new()
.get(format!(
"https://{}/index.php/apps/notes/api/v1/notes/{}",
&config.server_url, &config.primary_note_id
))
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.basic_auth(&config.user, Some(&config.pswd))
.send()
.unwrap()
.json::<Note>()
.unwrap();
let primary_note_lines: Vec<&str> = primary_note.content.lines().collect();
let mut body_content: Vec<&str> = vec![];
for line in primary_note_lines.iter() {
if line.starts_with("- [x] Send Summary") {
body_content.push("- [ ] Send Summary");
} else {
body_content.push(line);
}
}
Client::new()
.put(format!(
"https://{}/index.php/apps/notes/api/v1/notes/{}",
&config.server_url, &config.primary_note_id
))
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.basic_auth(&config.user, Some(&config.pswd))
.body(
serde_json::to_string(&NoteUpdate {
content: body_content.join("\n"),
})
.unwrap(),
)
.send()
.unwrap();
}
#[derive(Serialize, Deserialize, Debug)]
struct SummaryRow {
date: String,
total_time: i64,
task_name: String,
}
// All unwraps are unrecoverable errors
// All calls after depend on the success of the one before, and cannot run
// without. So it would be too much work to make an error type to handle an error
// that would cause a crash anyways
fn send_email_summary(config: &Config, body_content: Vec<String>) {
let mut wtr = csv::Writer::from_writer(vec![]);
for line in body_content {
if line.starts_with("### ") {
let mut split: Vec<&str> = line.split('|').collect();
if split.len() != 3 {
error!("There was an issue with this line: {line}");
continue;
}
let date: String = match split.pop() {
Some(val) => val,
None => continue,
}
.to_string();
let time: i64 = {
// This should never error as the len should always be 3
let data = match split.pop() {
Some(val) => val,
None => continue,
}
.trim();
// There should always be an h and an m in the second item
let h_index = match data.chars().position(|c| c == 'h') {
Some(val) => val,
None => continue,
};
let m_index = match data.chars().position(|c| c == 'm') {
Some(val) => val,
None => continue,
};
// this should always be the "10" in "10h"
let hours = match data[0..h_index].parse::<i64>() {
Ok(val) => val,
Err(_) => continue,
};
// this should always be the "20" in "10h 20m"
let minutes = match data[(h_index + 2)..m_index].parse::<i64>() {
Ok(val) => val,
Err(_) => continue,
};
hours * 60 + minutes
};
let task_name: String = match split.pop() {
Some(val) => val.trim(),
None => continue,
}
.to_string()
.replace("### ", "");
wtr.serialize(SummaryRow { date: date.trim().to_string(), total_time: time, task_name }).unwrap();
}
}
let data = String::from_utf8(wtr.into_inner().unwrap()).unwrap();
debug!("{:#?}", data);
let attachment = Attachment::new("TimeSummary.csv".to_string()).body(data, ContentType::parse("text/csv").unwrap());
let email = Message::builder()
.from("NoReplay <noreply@nickiel.net>".parse().unwrap())
.to("Nicholas Young <nicholasyoungsumner@gmail.com>".parse().unwrap())
.subject("Testing email")
.header(ContentType::TEXT_PLAIN)
.multipart(
MultiPart::mixed()
.singlepart(SinglePart::plain("Hello World".to_string()))
.singlepart(attachment)
).unwrap();
let mailer = SendmailTransport::new();
match mailer.send(&email) {
Ok(val) => debug!("email sent: {:?}", val),
Err(e) => debug!("Couldn't send email {}", e)
};
}