actions mostly completed (with NYIs)

This commit is contained in:
nobody 2024-08-24 01:24:41 +02:00 committed by murmeldin
parent 92d87193a8
commit 06457bd1c5
3 changed files with 70 additions and 255 deletions

View file

@ -57,7 +57,6 @@
use crate::is_dry_run;
use crate::key_value::KeyValueStore as KV;
use crate::NYI;
use std::error::Error;
use std::io::{self, Write};
@ -218,7 +217,6 @@ impl<'a> CfgField<'a> {
/// Ensure all fields with known default values exist.
pub fn populate_defaults(spec: &CfgSpec, config: &KV) {
for group in spec.groups {
NYI!("asdf");
for field in group.fields {
match field {
CfgField::Silent { default, .. } => {

View file

@ -97,10 +97,21 @@ impl HedgeDoc {
}
}
pub fn extract_metadata(pad_content: String) -> String {
let re_yaml = Regex::new(r"(?s)---\s*(.*?)\s*(?:\.\.\.|---)").unwrap();
re_yaml.captures_iter(&pad_content).map(|c| c[1].to_string()).collect::<Vec<_>>().join("\n")
}
pub fn strip_metadata(pad_content: String) -> String {
let re_yaml = Regex::new(r"(?s)---\s*.*?\s*(?:\.\.\.|---)").unwrap();
let pad_content = re_yaml.replace_all(&pad_content, "").to_string();
let re_comment = Regex::new(r"(?s)<!--.*?-->").unwrap();
re_comment.replace_all(&pad_content, "").to_string()
}
pub fn summarize(pad_content: String) -> String {
// 1. remove HTML comments
let re_comment = Regex::new(r"(?s)<!--.*?-->").unwrap();
let pad_content = re_comment.replace_all(&pad_content, "").to_string();
let pad_content = strip_metadata(pad_content);
// 2. accumulate topic lines
let re_header = Regex::new(r"^\s*##(#*) TOP ([\d.]+\s*.*?)\s*#*$").unwrap();
let mut result: Vec<String> = Vec::new();

View file

@ -30,14 +30,11 @@ future improvements:
- search ADJ_TIMEYWIMEY to find places that need adjusting if the bot might run late
(that's an incomplete list, but tag things as you notice them)
*/
#![allow(dead_code)] // ≈100 warnings for yet-to-be-used stuff…
#![allow(unused_macros)]
use chrono::{Datelike, Local, NaiveDate, Weekday};
use chrono::{Local, NaiveDate};
use clap::{Arg, Command};
use colored::Colorize;
use regex::Regex;
use std::borrow::Cow;
use std::env;
use std::error::Error;
use std::fmt::Display;
@ -45,7 +42,6 @@ use std::io::IsTerminal;
use cccron_lib::config_spec::{self, CfgField, CfgGroup, CfgSpec};
use cccron_lib::key_value::KeyValueStore as KV;
mod variables_and_settings;
use cccron_lib::date;
use cccron_lib::email::{self, Email, SimpleEmail};
@ -54,8 +50,6 @@ use cccron_lib::is_dry_run;
use cccron_lib::mediawiki::{self, Mediawiki};
use cccron_lib::NYI;
const FALLBACK_TEMPLATE: &str = variables_and_settings::FALLBACK_TEMPLATE;
/* ***** Config Spec ***** */
const CONFIG_SPEC: CfgSpec<'static> = CfgSpec {
groups: &[
@ -102,6 +96,16 @@ const CONFIG_SPEC: CfgSpec<'static> = CfgSpec {
default: "\n\n[Diese Nachricht wurde automatisiert vom Plenumsbot erstellt und ist daher ohne Unterschrift gültig.]",
description: "Text added at the bottom of every email.",
},
CfgField::Default { key: "fallback-template",
default: "---\ndate: \"{{datum-iso}}\"\n...\n<!-- bitte oberhalb nichts verändern -->\n\n\
# Plenum vom {{datum}}\n**Beginn 20:XX**\n\n## Themen\n\n[toc]\n\n## Anwesend\nX\n\n\
<!--\nDamit Themen richtig erkannt und verarbeitet werden, bitte folgendes Format \
einhalten: 2+ Rauten, \"TOP \", Nummer, also z.B:\n\n\
## TOP 1: Der Vorstand berichtet\n\n### TOP 1.1: Auf der west.berlin nichts neues\n\n\
-->\n\n\
\n\n**Ende 2X:XX**\n\nNächstes Plenum: Am {{naechstes-plenum}} um 20 Uhr",
description: "Pad template used in case hedgedoc isn't reachable, so that we can write a new pad to the hedgedoc instance that we couldn't reach."
},
],
},
],
@ -345,214 +349,6 @@ fn main() -> Result<(), Box<dyn Error>> {
} else {
return Ok(());
}
/* ** dead code beyond this point ** */
let _ = (); // dummy line so linter doesn't highlight the entire following block
#[allow(unused_variables)]
#[rustfmt::skip]
{
// Dienstage diesen Monat
let all_tuesdays: Vec<NaiveDate> = get_all_weekdays(0, Weekday::Tue);
let zweiter_dienstag: String = all_tuesdays[1].to_string(); // z.B. 2024-07-09
let vierter_dienstag: String = all_tuesdays[3].to_string(); // z.B. 2024-07-23
//Dienstage des nächsten Monats definieren
let all_tuesdays_next_month: Vec<NaiveDate> = get_all_weekdays(1, Weekday::Tue);
let zweiter_dienstag_nächster_monat: String = all_tuesdays_next_month[1].to_string();
let vierter_dienstag_nächster_monat: String = all_tuesdays_next_month[3].to_string();
// Daten, die später benutzt werden, definieren
let today = Local::now();
let today_simple = today.date_naive();
let yesterday = today_simple.pred_opt().unwrap();
let in_1_day = today_simple.succ_opt().unwrap();
let in_2_days = in_1_day.succ_opt().unwrap();
let in_3_days = in_2_days.succ_opt().unwrap();
// Nächste Plena nachschauen:
let nächster_plenumtermin: &String = if all_tuesdays[1] >= yesterday {
// Für das Pad rumschicken am nächsten Tag wird das Datum einen Tag nach Hinten gesetzt,
&zweiter_dienstag
} else {
&vierter_dienstag
};
let übernächster_plenumtermin = if all_tuesdays[1] >= yesterday {
// hier das Gleiche.
&vierter_dienstag
} else {
&zweiter_dienstag_nächster_monat
};
let überübernächster_plenumtermin = if all_tuesdays[1] >= yesterday {
// hier das Gleiche.
&zweiter_dienstag_nächster_monat
} else {
&vierter_dienstag_nächster_monat
};
// Der Code muss nur für vor dem 2. und vor dem 4. Dienstag gebaut werden, weil im nächsten Monat der Code frühestens 7 Tage vor dem Plenum wieder passt.
let in_1_day_is_plenum: bool = check_if_plenum(nächster_plenumtermin.clone(), in_1_day);
let in_3_days_is_plenum: bool = check_if_plenum(nächster_plenumtermin.clone(), in_3_days);
let yesterday_was_plenum: bool = check_if_plenum(nächster_plenumtermin.clone(), yesterday);
// Pad-Links aus der Datenbank laden:
let current_pad_id = config
.get("hedgedoc-last-id")
.expect("ID des aktuellen Pads undefiniert. Bitte in der DB eintragen oder generieren.");
let future_pad_id = config
.get("hedgedoc-next-id")
.expect("ID des nächsten Pads undefiniert. Bitte in der DB eintragen oder generieren.");
let mut message_id: String;
// let in_3_days_is_plenum = true;
//TEMPORÄR ANFANG: BEI PRODUCTION MUSS DAS HIER RAUS
let top_anzahl: i32 = 0; // Muss noch gecodet werden
let yesterday_was_plenum = true; // Das ist nur zu Testzwecken, kommt noch weg
let auth_result = wiki.get_login_token()?;
println!("---AUTH RESULT:---\n{}\n-----------", auth_result);
// TEMPORÄR ENDE
// AAAAAAAAAA
let pad_content = hedgedoc.download(&current_pad_id).expect("Fehler beim Download des Pads!");
let pad_content_without_top_instructions = try_to_remove_top_instructions(pad_content);
println!("Pad-content geladen!");
let current_pad_link = hedgedoc.format_url(&current_pad_id);
let future_pad_link = hedgedoc.format_url(&future_pad_id);
if in_1_day_is_plenum {
println!("In 1 Tag ist Plenum, deshalb wird eine Erinnerung raus geschickt!");
let tldr_vec = create_tldr(&pad_content_without_top_instructions);
let mut tldr = String::new();
for element in tldr_vec {
tldr.push_str("\n");
tldr.push_str(&element)
}
if number_of_tops(&pad_content_without_top_instructions) != 0 {
// Mail an alle senden, findet statt
let betreff = "Morgen ist Plenum!".to_string(); // ADJ_TIMEYWIMEY
let message: String = format!(
r#"Es gibt Themen, deshalb wird das morgige Plenum statt finden. Anbei das Plenumspad:
{current_pad_link}
Und hier ein TL;DR von den aktuellen Themen:
{tldr}
Bis morgen, 20 Uhr!"#
); // ADJ_TIMEYWIMEY
message_id = send_email(&betreff, &message, &email, &config)?;
// .expect("Plenum findet statt. Mail wurde versucht zu senden, konnte aber nicht gesendet werden!")
} else {
// Mail an alle senden und absagen
let betreff = format!("Plenum am {} fällt mangels Themen aus", nächster_plenumtermin);
let message: String = format!(
r#"Es gibt keine Themen, deshalb wird das morgige Plenum leider nicht statt finden.
Hier ist der Link zum Pad vom nächsten Plenum, das am {} statt finden wird:
{future_pad_link}
Bis zum nächsten Plenum."#,
nächster_plenumtermin
);
message_id = send_email(&betreff, &message, &email, &config)?;
// .expect("Plenum wird abgesagt. Mail wurde versucht zu senden, konnte aber nicht gesendet werden!"))
}
} else if yesterday_was_plenum {
// This logic breaks on 02/2034, but on every other month it works
let old_pad_content =
hedgedoc.download(&current_pad_id).expect("Fehler beim Hedgedoc-Pad-Download!");
// MUSS WIEDER REIN NACH DEM TESTEN: generate_new_pad_for_following_date(übernächster_plenumtermin, überübernächster_plenumtermin, &config).await.expect("Fehler! Plenumspad konnte nicht generiert werden!");
println!(
"DATENBANK: aktuelles-plenumspad: {:?} und zukünftiges plenumspad: {:?}",
&config.get("hedgedoc-last-id"),
&config.get("hedgedoc-next-id")
);
let old_pad_content_without_top_instructions =
try_to_remove_top_instructions(old_pad_content);
let tldr_vec = create_tldr(&old_pad_content_without_top_instructions);
let tldr = tldr_vec.join("\n");
// XXX nächstes/übernächstes?
let message: String = format!(
r#"Anbei das gestrige Plenumspad. Hier sind die Links zum nächsten:
{current_pad_link}
und zum übernächsten Plenum:
{future_pad_link}
TL;DR:
{tldr}
Und hier ist das Protokoll des letzten Plenums:
{old_pad_content_without_top_instructions}"#
);
let betreff: String =
format!("Plenumsprotokoll vom {}: Es gab {} TOPs", nächster_plenumtermin, top_anzahl);
// XXX option x expect
message_id = send_email(&betreff, &message, &email, &config)?;
// .expect("Mail mit Plenumsprotokoll wurde versucht zu senden, konnte aber nicht gesendet werden!"));
mediawiki::pad_ins_wiki(old_pad_content_without_top_instructions);
}
println!("message id: {:?}", message_id);
println!("[ENDE]\n{}", "Aktueller Zustand der DB:".bold());
config
.dump_redacting(&[
"email-password",
"wiki-http-password",
"wiki-api-secret",
"matrix-password",
])
.ok();
};
Ok(())
}
fn get_all_weekdays(month_offset: i32, week_day: Weekday) -> Vec<NaiveDate> {
let date = Local::now().date_naive();
let date = match month_offset.signum() {
0 => Some(date),
1 => date.checked_add_months(chrono::Months::new(month_offset as u32)),
-1 => date.checked_sub_months(chrono::Months::new((-month_offset) as u32)),
_ => unreachable!(),
}
.expect("(very) invalid month offset");
let month = date.month();
let mut current_date = NaiveDate::from_ymd_opt(date.year(), date.month(), 1);
let mut dates = Vec::new();
while let Some(date) = current_date {
if date.month() == month && date.weekday() == week_day {
dates.push(date);
}
current_date = date.succ_opt();
}
dates
}
fn check_if_plenum(infrage_kommendes_plenum: String, date_to_check: NaiveDate) -> bool {
// Überprüfen, ob an dem Datum Plenum ist
let date_to_check = date_to_check.to_string();
return if infrage_kommendes_plenum == date_to_check { true } else { false };
}
fn number_of_tops(pad_content: &String) -> i32 {
// Logik: Wenn irgendwo TOP 2, top2, Top2 oder TOP2 steht, dann gibt es Themen
let re = Regex::new(r"^##+ *([Tt][Oo][Pp] +\d[.\d]*.*)$").unwrap();
let m = re.find(&pad_content);
m.iter().len() as i32
}
fn create_tldr(pad_content: &String) -> Vec<&str> {
// Logik: Wenn irgendwo TOP 2, top2, Top2 oder TOP2 steht, dann gibt es Themen
let re_top = Regex::new(r"^##+ *([Tt][Oo][Pp] +\d[.\d]*.*)$").unwrap();
let tldr: Vec<&str> = re_top.find_iter(&pad_content).map(|m| m.as_str()).collect();
//println!("{:?}", m);
tldr
//tldr_vec.append() = m.iter()
}
fn generate_new_pad_for_following_date(
@ -568,8 +364,8 @@ fn generate_new_pad_for_following_date(
let template_content: String = match config.get("hedgedoc-template-name") {
Ok(content) => hedgedoc
.download(&content.clone())
.unwrap_or_else(|_| FALLBACK_TEMPLATE.to_string()),
Err(_) => FALLBACK_TEMPLATE.to_string(),
.unwrap_or_else(|_| config.get("text-fallback-template").unwrap_or_default()),
Err(_) => config.get("text-fallback-template").unwrap_or_default(),
};
// XXX you don't just use the template as-is…
let template_modified: String = replace_placeholders(
@ -616,12 +412,6 @@ fn rotate(future_pad_id: &str, kv: &KV) {
}
}
fn try_to_remove_top_instructions(pad_content: String) -> String {
let re_top_instructions: Regex = Regex::new(r"(<!--(?:.||\n)*-->)").unwrap();
let result: Cow<str> = re_top_instructions.replace_all(&pad_content, "---");
result.to_string() // Wenn es nicht geklappt hat, wird einfach das Pad mit dem Kommentar zurückgegeben
}
/* ***** formatting helpers ***** */
fn relative_date(ttp: i64) -> String {
@ -662,26 +452,24 @@ fn topic_count(n: usize, dative: bool) -> String {
/* ***** repeating action parts ***** */
fn get_pad_info(config: &KV, hedgedoc: &HedgeDoc) -> (String, String, usize) {
fn get_pad_info(config: &KV, hedgedoc: &HedgeDoc) -> (String, String, String, usize) {
let current_pad_id = &config["hedgedoc-last-id"];
let pad_content = hedgedoc.download(current_pad_id).expect("Hedgedoc: Download-Fehler");
let toc = hedgedoc::summarize(pad_content);
let toc = hedgedoc::summarize(pad_content.clone());
verboseln!("Zusammenfassung des aktuellen Plenum-Pads:\n{}", toc.cyan());
let n_topics = toc.lines().count();
verboseln!("(Also {}.)", topic_count(n_topics, false).cyan());
(current_pad_id.to_string(), toc, n_topics)
(current_pad_id.to_string(), pad_content, toc, n_topics)
}
/* ***** transition actions ***** */
// BBBBBBBBBB
fn do_announcement(
ttp: i64, plenum_day: &NaiveDate, config: &KV, hedgedoc: &HedgeDoc, email: &SimpleEmail,
_wiki: &Mediawiki,
) -> Result<(), Box<dyn Error>> {
// fetch current pad contents & summarize
let (current_pad_id, toc, n_topics) = get_pad_info(config, hedgedoc);
let (current_pad_id, _pad_content, toc, n_topics) = get_pad_info(config, hedgedoc);
// construct email
let subject = format!(
"Plenum {} (am {}): bisher {}",
@ -700,7 +488,7 @@ fn do_announcement(
);
let body = format!("{line1}\n\n{line2}");
// send it
let message_id = send_email(&subject, &body, &email, &config)?;
let message_id = send_email(&subject, &body, email, config)?;
// on success, update state (ignore write errors, they'll be checked later)
config.set("email-message-id", &message_id).ok();
config.set("state-name", &ProgramState::Announced.to_string()).ok();
@ -713,39 +501,42 @@ fn do_reminder(
_wiki: &Mediawiki,
) -> Result<(), Box<dyn Error>> {
// fetch current pad contents & summarize
let (current_pad_id, toc, n_topics) = get_pad_info(config, hedgedoc);
let (current_pad_id, _pad_content, toc, n_topics) = get_pad_info(config, hedgedoc);
let old_toc = config.get("state-toc").unwrap_or_default();
// construct email
let subject_suffix = if n_topics == 0 {
" fällt aus (keine Themen)".to_string()
let human_date = plenum_day.format("%d.%m.%Y");
let subject = if n_topics == 0 {
format!("Plenum {} (am {}) fällt aus (keine Themen)", relative_date(ttp), human_date)
} else {
format!(" findet mit {} statt", topic_count(n_topics, true))
format!(
"{} ist Plenum mit {}!",
upper_first(&relative_date(ttp)),
topic_count(n_topics, true)
)
};
let subject = format!(
"Plenum {} (am {}) {}",
relative_date(ttp),
plenum_day.format("%d.%m.%Y"),
subject_suffix
);
let body_prefix = if old_toc == toc {
format!("Die Themen sind gleich geblieben. ")
} else if toc.is_empty() {
format!("Es gibt Themen, die aktuelle Liste ist:\n\n{toc}\n\n")
} else {
format!("Es gab nochmal Änderungen, die aktualisierten Themen für das Plenum sind:\n\n{toc}\n\n")
};
let body = if n_topics > 0 {
format!(
"{body_prefix}Die vollen Details findet ihr im Pad:\n {}",
hedgedoc.format_url(&current_pad_id)
"{body_prefix}Die vollen Details findet ihr im Pad:\n {}\n\nBis {} um 20:00!",
hedgedoc.format_url(&current_pad_id),
relative_date(ttp)
)
} else {
NYI!("generate link / pad for next plenum & include in this email");
"Da es immer noch keine Themen gibt fällt das Plenum aus.\n\n\
(Natürlich könnt ihr im Bedarfsfall immer noch kurzfristig ein Treffen einberufen, aber bitte \
kündigt das so früh wie möglich an, damit Leute sich darauf einstellen können.)"
.to_string()
(Natürlich könnt ihr im Bedarfsfall immer noch kurzfristig ein Treffen einberufen, aber \
bitte kündigt das so früh wie möglich an, damit insbesondere auch Leute mit längeren \
Wegen sich noch darauf einstellen können.)"
.to_string()
};
// send it
let _message_id = send_email(&subject, &body, &email, &config)?;
let _message_id = send_email(&subject, &body, email, config)?;
// on success, update state (ignore write errors, they'll be checked later)
if n_topics == 0 {
NYI!(
@ -762,11 +553,26 @@ fn do_protocol(
ttp: i64, plenum_day: &NaiveDate, config: &KV, hedgedoc: &HedgeDoc, email: &SimpleEmail,
wiki: &Mediawiki,
) -> Result<(), Box<dyn Error>> {
// TODO: get pad
// TODO: write to wiki
// TODO: write to email
// TODO: set state as Logged
todo!()
let (current_pad_id, pad_content, toc, n_topics) = get_pad_info(config, hedgedoc);
if toc.len() > 0 {
let human_date = plenum_day.format("%d.%m.%Y");
let pad_content = hedgedoc::strip_metadata(pad_content);
let subject = format!("Protokoll vom Plenum am {human_date}");
NYI!("link for next plenum");
NYI!("replace [toc] with actual table of contents");
let body = format!(
"Anbei das Protokoll vom {human_date}, ab sofort auch im Wiki zu finden.\n\n\
Das Pad für das nächste Plenum ist zu finden unter {}.\n\n-----\n\n{pad_content}",
"<FIXME>"
);
let _message_id = send_email(&subject, &body, email, config)?;
NYI!("convert to mediawiki");
NYI!("add to wiki");
config.set("state-name", &ProgramState::Logged.to_string()).ok();
} else {
NYI!("What do we do in the no topics / no plenum case?");
}
Ok(())
}
/// General cleanup function. Call as `(999, today, …)` for a complete reset