Finalement fini ce script de ses morts

This commit is contained in:
jfelderh
2024-12-19 20:18:56 +01:00
parent c2b317ea71
commit 289d76c0d4
6 changed files with 2033 additions and 36 deletions

1850
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,10 @@ edition = "2021"
argh = "0.1.12"
csv = "1.2.2"
itertools = "0.13.0"
mail-send = "0.4.9"
rand = "0.8.5"
rand_chacha = "0.3.1"
rpassword = "7.3.1"
serde = "1.0.188"
serde_json = "1.0.107"
tokio = {version="1.42.0", features = ["full"]}

View File

@@ -7,7 +7,7 @@ use itertools::Itertools;
impl Repartition {
pub fn to_json(&self) -> String {
let participants = self
.0
.transferts
.iter()
.flat_map(|t| vec![t.from.clone(), t.to.clone()])
.dedup()
@@ -25,7 +25,7 @@ impl Repartition {
);
}
let mut str_edges = String::new();
for t in &self.0 {
for t in &self.transferts {
str_edges += &format!(
"{{
\"data\": {{
@@ -42,7 +42,7 @@ impl Repartition {
t.to.name,
)
}
"[".to_owned() + &str_nodes + "," + &str_edges + "]"
"{".to_owned() + "nodes:[" + &str_nodes + "], edges: [" + &str_edges + "]}"
}
}
@@ -53,8 +53,6 @@ const FORMAT_STRING: &'static str = "<html>
<body>
<div id=\"graph\" style=\"width: auto;height: 500px;\"></div>
<h3>Balance totale (doit être égal à zéro) : TOTAL_BALANCE €</h3>
<script type=\"text/javascript\">
var cy = cytoscape({
@@ -98,7 +96,6 @@ use super::repartition::*;
pub fn plot_repartition(rep: &Repartition) {
let text_html = FORMAT_STRING
.to_string()
.replace("TOTAL_BALANCE", &rep.total_balance().to_string())
.replace("ELEMENTS", &rep.to_json());
fs::write("/tmp/repartition.html", text_html).expect("Could not write into /tmp/");
Command::new("firefox")

111
src/mail.rs Normal file
View File

@@ -0,0 +1,111 @@
use itertools::Itertools;
use std::process::Command;
use crate::repartition::Repartition;
const FORMAT_MAIL_RECEIVER: &'static str = "
Bonjour $NOM,
Voilà venu le temps de la répartition des dépenses des Alpes 2024 (toutes mes excuses pour le retard).
Tu es dans le groupe des personnes qui **à qui on doit de l'argent**.
On te dois en tout $ARGENT€.
Voici les virements que tu dois recevoir:
$VIREMENTS
Il faudra contacter les gens directement !
Merci d'avance et des gros bisous.
Joël de la compta
";
const FORMAT_MAIL_PAYER: &'static str = "
Bonjour $NOM,
Voilà venu le temps de la répartition des dépenses des Alpes 2024 (toutes mes excuses pour le retard).
Tu es dans le groupe des personnes qui **qui doivent de l'argent**.
Tu dois en tout $ARGENT€.
Voici les virements que tu dois faire:
$VIREMENTS
Il faudra contacter les gens directement !
Merci d'avance et des gros bisous.
Joël de la compta
";
impl Repartition {
fn prepare_mail(&self, name: String, payer: bool) -> (String, String) {
let format_text = if payer {
FORMAT_MAIL_PAYER
} else {
FORMAT_MAIL_RECEIVER
};
let mut total_amount = 0;
let mut current_mail = String::new();
let total_virements = self
.transferts
.iter()
.filter(|t| (payer && t.from.name == name) || t.to.name == name)
.map(|t| {
if payer {
current_mail = t.from.email.clone();
} else {
current_mail = t.to.email.clone();
}
total_amount += t.amount;
t.to_string(payer)
})
.join("\n");
(
current_mail,
format_text
.replace("$NOM", &name)
.replace("$ARGENT", &format!("{:.2}", total_amount as f32 / 100.))
.replace("$VIREMENTS", &total_virements),
)
}
pub async fn send_all_mails(&self) {
use mail_send::*;
let sending_mail = "commu.alpes.site@gmail.com";
let password_mail = rpassword::prompt_password("Password?").unwrap();
let mut client = SmtpClientBuilder::new("smtp.gmail.com", 587)
.implicit_tls(false)
.credentials((sending_mail.as_ref(), password_mail.as_ref()))
.connect()
.await
.unwrap();
let mut mails = vec![];
for name in &self.payers {
let (to_mail, mail) = self.prepare_mail(name.clone(), true);
mails.push((name, to_mail, mail.clone()));
println!("{}", mail);
}
for name in &self.receivers {
let (to_mail, mail) = self.prepare_mail(name.clone(), false);
mails.push((name, to_mail, mail.clone()));
println!("{}", mail);
}
for (name, to_mail, mail) in mails {
let message = mail_builder::MessageBuilder::new()
.from(("Compta Alpes", "commu.alpes.site@gmail.com"))
.to(vec![(name.clone(), to_mail.clone())])
.cc(vec![("Joël de la compta")])
.subject("Compta Alpes 2024")
.text_body(mail.clone());
println!("Sending mail to {}", name);
println!("{}", mail);
client.send(message).await.unwrap();
let mut child = Command::new("sleep").arg("3").spawn().unwrap();
let _result = child.wait().unwrap();
}
}
}

View File

@@ -1,12 +1,16 @@
use std::fs;
use std::io as stdio;
use std::io::prelude::*;
mod io;
mod mail;
pub mod repartition;
use argh::FromArgs;
use rand::SeedableRng;
use rand_chacha::ChaCha8Rng;
extern crate tokio;
#[derive(FromArgs)]
/// Computation of repartition
struct Arguments {
@@ -27,7 +31,19 @@ struct Arguments {
transfert_list_filename: Option<String>,
}
fn main() {
fn pause() {
let mut stdin = stdio::stdin();
let mut stdout = stdio::stdout();
// We want the cursor to stay at the end of the line, so we print without a newline and flush manually.
write!(stdout, "Press any key to continue...").unwrap();
stdout.flush().unwrap();
// Read a single byte and discard
let _ = stdin.read(&mut [0u8]).unwrap();
}
#[tokio::main]
async fn main() {
use repartition::*;
let args: Arguments = argh::from_env();
if let Some(filename) = args.user_list_filename {
@@ -76,6 +92,8 @@ fn main() {
serde_json::from_reader(fs::File::open(filename).expect("not able to read file"))
.expect("unable to read file");
io::plot_repartition(&repart);
pause();
repart.send_all_mails().await;
} else {
println!("No parameter given, exiting");
}

View File

@@ -50,11 +50,14 @@ impl RandomRepartitionBuilder {
}
pub fn build_repartition(mut self) -> Repartition {
let mut rep = Repartition(Vec::new());
let mut transfer_vec = Vec::new();
let mut all_payers = HashSet::new();
let mut all_receivers = HashSet::new();
while !self.payers.is_empty() || !self.receivers.is_empty() {
let mut payer = self.get_next_payer();
all_payers.insert(payer.name.clone());
let mut receiver = self.get_next_receiver();
all_receivers.insert(receiver.name.clone());
let amount = min(-payer.balance_cents, receiver.balance_cents);
dbg!(amount);
let transfert = Transfer {
@@ -64,7 +67,7 @@ impl RandomRepartitionBuilder {
};
payer.balance_cents += amount;
receiver.balance_cents -= amount;
rep.0.push(transfert);
transfer_vec.push(transfert);
if payer.balance_cents < 0 {
self.payers.push(payer);
}
@@ -72,8 +75,14 @@ impl RandomRepartitionBuilder {
self.receivers.push(receiver);
}
}
assert_eq!(self.payers.len(), 0);
assert_eq!(self.receivers.len(), 0);
rep
Repartition {
transferts: transfer_vec,
payers: all_payers,
receivers: all_receivers,
}
}
}
@@ -83,16 +92,51 @@ pub struct Transfer {
pub to: User,
pub amount: u32,
}
impl Transfer {
pub fn to_string(&self, payer: bool) -> String {
if payer {
format!(
" - Virement de {:.2}€ à {} ({})",
self.amount as f32 / 100.,
self.to.name,
self.to.email
)
.to_string()
} else {
format!(
" - Virement de {:.2}€ de {} ({})",
self.amount as f32 / 100.,
self.from.name,
self.from.email
)
.to_string()
}
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Repartition(pub Vec<Transfer>);
pub struct Repartition {
pub transferts: Vec<Transfer>,
pub payers: HashSet<String>,
pub receivers: HashSet<String>,
}
type Evaluation = (usize);
type Evaluation = (i64);
impl Repartition {
pub fn total_balance(&self) -> i32 {
self.0.iter().map(|t| t.amount as i32).sum()
self.transferts
.iter()
.map(|t| {
if self.payers.contains(&t.from.name) {
t.amount as i32
} else {
-(t.amount as i32)
}
})
.sum()
}
pub fn evaluate(&self) -> Evaluation {
self.0.len()
(self.transferts.len() as i64) * 20
- (100 / (self.transferts.iter().map(|t| t.amount).min().unwrap() as f32) as i64)
}
}